tfdiags: Sort order for diagnostics
Because we gather together diagnostics from many different parts of the codebase, the list often ends up being in a non-ideal order. Here we define a partial ordering for diagnostics that should hopefully make them easier to scan when many are present, by grouping together diagnostics that are of the same severity and belong to the same file. We use sort.Stable here because we have a partial order and so we need to make sure that diagnostics that do not have a relative ordering will remain in their original order. This sorting is applied just in time before rendering the diagnostics in command.Meta.showDiagnostics.
This commit is contained in:
parent
27d90c7550
commit
0742e756e5
|
@ -488,6 +488,7 @@ func (m *Meta) confirm(opts *terraform.InputOpts) (bool, error) {
|
|||
func (m *Meta) showDiagnostics(vals ...interface{}) {
|
||||
var diags tfdiags.Diagnostics
|
||||
diags = diags.Append(vals...)
|
||||
diags.Sort()
|
||||
|
||||
for _, diag := range diags {
|
||||
// TODO: Actually measure the terminal width and pass it here.
|
||||
|
|
|
@ -3,6 +3,9 @@ package tfdiags
|
|||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/errwrap"
|
||||
multierror "github.com/hashicorp/go-multierror"
|
||||
|
@ -174,6 +177,18 @@ func (diags Diagnostics) NonFatalErr() error {
|
|||
return NonFatalError{diags}
|
||||
}
|
||||
|
||||
// Sort applies an ordering to the diagnostics in the receiver in-place.
|
||||
//
|
||||
// The ordering is: warnings before errors, sourceless before sourced,
|
||||
// short source paths before long source paths, and then ordering by
|
||||
// position within each file.
|
||||
//
|
||||
// Diagnostics that do not differ by any of these sortable characteristics
|
||||
// will remain in the same relative order after this method returns.
|
||||
func (diags Diagnostics) Sort() {
|
||||
sort.Stable(sortDiagnostics(diags))
|
||||
}
|
||||
|
||||
type diagnosticsAsError struct {
|
||||
Diagnostics
|
||||
}
|
||||
|
@ -258,3 +273,58 @@ func (woe NonFatalError) Error() string {
|
|||
return ret.String()
|
||||
}
|
||||
}
|
||||
|
||||
// sortDiagnostics is an implementation of sort.Interface
|
||||
type sortDiagnostics []Diagnostic
|
||||
|
||||
var _ sort.Interface = sortDiagnostics(nil)
|
||||
|
||||
func (sd sortDiagnostics) Len() int {
|
||||
return len(sd)
|
||||
}
|
||||
|
||||
func (sd sortDiagnostics) Less(i, j int) bool {
|
||||
iD, jD := sd[i], sd[j]
|
||||
iSev, jSev := iD.Severity(), jD.Severity()
|
||||
iSrc, jSrc := iD.Source(), jD.Source()
|
||||
|
||||
switch {
|
||||
|
||||
case iSev != jSev:
|
||||
return iSev == Warning
|
||||
|
||||
case (iSrc.Subject == nil) != (jSrc.Subject == nil):
|
||||
return iSrc.Subject == nil
|
||||
|
||||
case iSrc.Subject != nil && *iSrc.Subject != *jSrc.Subject:
|
||||
iSubj := iSrc.Subject
|
||||
jSubj := jSrc.Subject
|
||||
switch {
|
||||
case iSubj.Filename != jSubj.Filename:
|
||||
// Path with fewer segments goes first if they are different lengths
|
||||
sep := string(filepath.Separator)
|
||||
iCount := strings.Count(iSubj.Filename, sep)
|
||||
jCount := strings.Count(jSubj.Filename, sep)
|
||||
if iCount != jCount {
|
||||
return iCount < jCount
|
||||
}
|
||||
return iSubj.Filename < jSubj.Filename
|
||||
case iSubj.Start.Byte != jSubj.Start.Byte:
|
||||
return iSubj.Start.Byte < jSubj.Start.Byte
|
||||
case iSubj.End.Byte != jSubj.End.Byte:
|
||||
return iSubj.End.Byte < jSubj.End.Byte
|
||||
}
|
||||
fallthrough
|
||||
|
||||
default:
|
||||
// The remaining properties do not have a defined ordering, so
|
||||
// we'll leave it unspecified. Since we use sort.Stable in
|
||||
// the caller of this, the ordering of remaining items will
|
||||
// be preserved.
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (sd sortDiagnostics) Swap(i, j int) {
|
||||
sd[i], sd[j] = sd[j], sd[i]
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue