go/analysis: add an End field to Diagnostic
This will allow diagnostics to denote the range they apply to. The ranges are now interpreted using the internal/span library. This is primarily intended for the benefit of the LSP, which will be able to (in future CLs) more accurately highlight the part of the code a diagnostic applies to. Change-Id: Ic35cec2b21060c9dc6a8f5ebb7faa62d81a07435 Reviewed-on: https://go-review.googlesource.com/c/tools/+/179237 Run-TryBot: Michael Matloob <matloob@golang.org> Reviewed-by: Ian Cottrell <iancottrell@google.com>
This commit is contained in:
parent
12d7342421
commit
2b03ca6e44
|
@ -161,6 +161,15 @@ func (pass *Pass) Reportf(pos token.Pos, format string, args ...interface{}) {
|
||||||
pass.Report(Diagnostic{Pos: pos, Message: msg})
|
pass.Report(Diagnostic{Pos: pos, Message: msg})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// reportNodef is a helper function that reports a Diagnostic using the
|
||||||
|
// range denoted by the AST node.
|
||||||
|
//
|
||||||
|
// WARNING: This is an experimental API and may change in the future.
|
||||||
|
func (pass *Pass) reportNodef(node ast.Node, format string, args ...interface{}) {
|
||||||
|
msg := fmt.Sprintf(format, args...)
|
||||||
|
pass.Report(Diagnostic{Pos: node.Pos(), End: node.End(), Message: msg})
|
||||||
|
}
|
||||||
|
|
||||||
func (pass *Pass) String() string {
|
func (pass *Pass) String() string {
|
||||||
return fmt.Sprintf("%s@%s", pass.Analyzer.Name, pass.Pkg.Path())
|
return fmt.Sprintf("%s@%s", pass.Analyzer.Name, pass.Pkg.Path())
|
||||||
}
|
}
|
||||||
|
@ -203,13 +212,17 @@ type Fact interface {
|
||||||
AFact() // dummy method to avoid type errors
|
AFact() // dummy method to avoid type errors
|
||||||
}
|
}
|
||||||
|
|
||||||
// A Diagnostic is a message associated with a source location.
|
// A Diagnostic is a message associated with a source location or range.
|
||||||
//
|
//
|
||||||
// An Analyzer may return a variety of diagnostics; the optional Category,
|
// An Analyzer may return a variety of diagnostics; the optional Category,
|
||||||
// which should be a constant, may be used to classify them.
|
// which should be a constant, may be used to classify them.
|
||||||
// It is primarily intended to make it easy to look up documentation.
|
// It is primarily intended to make it easy to look up documentation.
|
||||||
|
//
|
||||||
|
// If End is provided, the diagnostic is specified to apply to the range between
|
||||||
|
// Pos and End.
|
||||||
type Diagnostic struct {
|
type Diagnostic struct {
|
||||||
Pos token.Pos
|
Pos token.Pos
|
||||||
|
End token.Pos // optional
|
||||||
Category string // optional
|
Category string // optional
|
||||||
Message string
|
Message string
|
||||||
}
|
}
|
||||||
|
|
|
@ -257,6 +257,7 @@ func check(t Testing, gopath string, pass *analysis.Pass, diagnostics []analysis
|
||||||
|
|
||||||
// Check the diagnostics match expectations.
|
// Check the diagnostics match expectations.
|
||||||
for _, f := range diagnostics {
|
for _, f := range diagnostics {
|
||||||
|
// TODO(matloob): Support ranges in analysistest.
|
||||||
posn := pass.Fset.Position(f.Pos)
|
posn := pass.Fset.Position(f.Pos)
|
||||||
checkMessage(posn, "diagnostic", "", f.Message)
|
checkMessage(posn, "diagnostic", "", f.Message)
|
||||||
}
|
}
|
||||||
|
|
|
@ -323,9 +323,14 @@ func PrintPlain(fset *token.FileSet, diag analysis.Diagnostic) {
|
||||||
|
|
||||||
// -c=N: show offending line plus N lines of context.
|
// -c=N: show offending line plus N lines of context.
|
||||||
if Context >= 0 {
|
if Context >= 0 {
|
||||||
|
posn := fset.Position(diag.Pos)
|
||||||
|
end := fset.Position(diag.End)
|
||||||
|
if !end.IsValid() {
|
||||||
|
end = posn
|
||||||
|
}
|
||||||
data, _ := ioutil.ReadFile(posn.Filename)
|
data, _ := ioutil.ReadFile(posn.Filename)
|
||||||
lines := strings.Split(string(data), "\n")
|
lines := strings.Split(string(data), "\n")
|
||||||
for i := posn.Line - Context; i <= posn.Line+Context; i++ {
|
for i := posn.Line - Context; i <= end.Line+Context; i++ {
|
||||||
if 1 <= i && i <= len(lines) {
|
if 1 <= i && i <= len(lines) {
|
||||||
fmt.Fprintf(os.Stderr, "%d\t%s\n", i, lines[i-1])
|
fmt.Fprintf(os.Stderr, "%d\t%s\n", i, lines[i-1])
|
||||||
}
|
}
|
||||||
|
@ -353,6 +358,8 @@ func (tree JSONTree) Add(fset *token.FileSet, id, name string, diags []analysis.
|
||||||
Message string `json:"message"`
|
Message string `json:"message"`
|
||||||
}
|
}
|
||||||
var diagnostics []jsonDiagnostic
|
var diagnostics []jsonDiagnostic
|
||||||
|
// TODO(matloob): Should the JSON diagnostics contain ranges?
|
||||||
|
// If so, how should they be formatted?
|
||||||
for _, f := range diags {
|
for _, f := range diags {
|
||||||
diagnostics = append(diagnostics, jsonDiagnostic{
|
diagnostics = append(diagnostics, jsonDiagnostic{
|
||||||
Category: f.Category,
|
Category: f.Category,
|
||||||
|
|
|
@ -295,7 +295,8 @@ func printDiagnostics(roots []*action) (exitcode int) {
|
||||||
// avoid double-reporting in source files that belong to
|
// avoid double-reporting in source files that belong to
|
||||||
// multiple packages, such as foo and foo.test.
|
// multiple packages, such as foo and foo.test.
|
||||||
type key struct {
|
type key struct {
|
||||||
token.Position
|
pos token.Position
|
||||||
|
end token.Position
|
||||||
*analysis.Analyzer
|
*analysis.Analyzer
|
||||||
message string
|
message string
|
||||||
}
|
}
|
||||||
|
@ -313,7 +314,8 @@ func printDiagnostics(roots []*action) (exitcode int) {
|
||||||
// as most users don't care.
|
// as most users don't care.
|
||||||
|
|
||||||
posn := act.pkg.Fset.Position(diag.Pos)
|
posn := act.pkg.Fset.Position(diag.Pos)
|
||||||
k := key{posn, act.a, diag.Message}
|
end := act.pkg.Fset.Position(diag.End)
|
||||||
|
k := key{posn, end, act.a, diag.Message}
|
||||||
if seen[k] {
|
if seen[k] {
|
||||||
continue // duplicate
|
continue // duplicate
|
||||||
}
|
}
|
||||||
|
|
|
@ -133,7 +133,7 @@ func diagnostics(ctx context.Context, v View, pkg Package, reports map[span.URI]
|
||||||
func analyses(ctx context.Context, v View, pkg Package, reports map[span.URI][]Diagnostic) error {
|
func analyses(ctx context.Context, v View, pkg Package, reports map[span.URI][]Diagnostic) error {
|
||||||
// Type checking and parsing succeeded. Run analyses.
|
// Type checking and parsing succeeded. Run analyses.
|
||||||
if err := runAnalyses(ctx, v, pkg, func(a *analysis.Analyzer, diag analysis.Diagnostic) error {
|
if err := runAnalyses(ctx, v, pkg, func(a *analysis.Analyzer, diag analysis.Diagnostic) error {
|
||||||
r := span.NewRange(v.Session().Cache().FileSet(), diag.Pos, 0)
|
r := span.NewRange(v.Session().Cache().FileSet(), diag.Pos, diag.End)
|
||||||
s, err := r.Span()
|
s, err := r.Span()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// The diagnostic has an invalid position, so we don't have a valid span.
|
// The diagnostic has an invalid position, so we don't have a valid span.
|
||||||
|
|
Loading…
Reference in New Issue