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>
diff --git a/go/analysis/analysis.go b/go/analysis/analysis.go
index 8eb7316..19e1e42 100644
--- a/go/analysis/analysis.go
+++ b/go/analysis/analysis.go
@@ -161,6 +161,15 @@
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 {
return fmt.Sprintf("%s@%s", pass.Analyzer.Name, pass.Pkg.Path())
}
@@ -203,13 +212,17 @@
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,
// which should be a constant, may be used to classify them.
// 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 {
Pos token.Pos
- Category string // optional
+ End token.Pos // optional
+ Category string // optional
Message string
}
diff --git a/go/analysis/analysistest/analysistest.go b/go/analysis/analysistest/analysistest.go
index ad979c1..c81e020 100644
--- a/go/analysis/analysistest/analysistest.go
+++ b/go/analysis/analysistest/analysistest.go
@@ -257,6 +257,7 @@
// Check the diagnostics match expectations.
for _, f := range diagnostics {
+ // TODO(matloob): Support ranges in analysistest.
posn := pass.Fset.Position(f.Pos)
checkMessage(posn, "diagnostic", "", f.Message)
}
diff --git a/go/analysis/internal/analysisflags/flags.go b/go/analysis/internal/analysisflags/flags.go
index 062d062..a3c2f09 100644
--- a/go/analysis/internal/analysisflags/flags.go
+++ b/go/analysis/internal/analysisflags/flags.go
@@ -323,9 +323,14 @@
// -c=N: show offending line plus N lines of context.
if Context >= 0 {
+ posn := fset.Position(diag.Pos)
+ end := fset.Position(diag.End)
+ if !end.IsValid() {
+ end = posn
+ }
data, _ := ioutil.ReadFile(posn.Filename)
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) {
fmt.Fprintf(os.Stderr, "%d\t%s\n", i, lines[i-1])
}
@@ -353,6 +358,8 @@
Message string `json:"message"`
}
var diagnostics []jsonDiagnostic
+ // TODO(matloob): Should the JSON diagnostics contain ranges?
+ // If so, how should they be formatted?
for _, f := range diags {
diagnostics = append(diagnostics, jsonDiagnostic{
Category: f.Category,
diff --git a/go/analysis/internal/checker/checker.go b/go/analysis/internal/checker/checker.go
index 4cade77..e3b0506 100644
--- a/go/analysis/internal/checker/checker.go
+++ b/go/analysis/internal/checker/checker.go
@@ -295,7 +295,8 @@
// avoid double-reporting in source files that belong to
// multiple packages, such as foo and foo.test.
type key struct {
- token.Position
+ pos token.Position
+ end token.Position
*analysis.Analyzer
message string
}
@@ -313,7 +314,8 @@
// as most users don't care.
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] {
continue // duplicate
}
diff --git a/internal/lsp/source/diagnostics.go b/internal/lsp/source/diagnostics.go
index 3f88a88..bd0cd2d 100644
--- a/internal/lsp/source/diagnostics.go
+++ b/internal/lsp/source/diagnostics.go
@@ -133,7 +133,7 @@
func analyses(ctx context.Context, v View, pkg Package, reports map[span.URI][]Diagnostic) error {
// Type checking and parsing succeeded. Run analyses.
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()
if err != nil {
// The diagnostic has an invalid position, so we don't have a valid span.