mirror of
https://github.com/golang/go.git
synced 2025-05-05 23:53:05 +00:00
internal/lsp: move error range computations into cache package
A continuation of CL 202298, only for analysis errors. Change-Id: I957d52cef31938ef66be73463e92695a5b56869c Reviewed-on: https://go-review.googlesource.com/c/tools/+/202540 Run-TryBot: Rebecca Stambler <rstambler@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Ian Cottrell <iancottrell@google.com>
This commit is contained in:
parent
d2fffb4b84
commit
3057e18543
41
internal/lsp/cache/analysis.go
vendored
41
internal/lsp/cache/analysis.go
vendored
@ -17,7 +17,7 @@ import (
|
|||||||
errors "golang.org/x/xerrors"
|
errors "golang.org/x/xerrors"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *snapshot) Analyze(ctx context.Context, id string, analyzers []*analysis.Analyzer) (map[*analysis.Analyzer][]*analysis.Diagnostic, error) {
|
func (s *snapshot) Analyze(ctx context.Context, id string, analyzers []*analysis.Analyzer) (map[*analysis.Analyzer][]*source.Error, error) {
|
||||||
var roots []*actionHandle
|
var roots []*actionHandle
|
||||||
|
|
||||||
for _, a := range analyzers {
|
for _, a := range analyzers {
|
||||||
@ -34,7 +34,7 @@ func (s *snapshot) Analyze(ctx context.Context, id string, analyzers []*analysis
|
|||||||
return nil, ctx.Err()
|
return nil, ctx.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
results := make(map[*analysis.Analyzer][]*analysis.Diagnostic)
|
results := make(map[*analysis.Analyzer][]*source.Error)
|
||||||
for _, ah := range roots {
|
for _, ah := range roots {
|
||||||
diagnostics, _, err := ah.analyze(ctx)
|
diagnostics, _, err := ah.analyze(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -63,7 +63,7 @@ type actionHandle struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type actionData struct {
|
type actionData struct {
|
||||||
diagnostics []*analysis.Diagnostic
|
diagnostics []*source.Error
|
||||||
result interface{}
|
result interface{}
|
||||||
err error
|
err error
|
||||||
}
|
}
|
||||||
@ -133,7 +133,7 @@ func (s *snapshot) actionHandle(ctx context.Context, id packageID, mode source.P
|
|||||||
return ah, nil
|
return ah, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (act *actionHandle) analyze(ctx context.Context) ([]*analysis.Diagnostic, interface{}, error) {
|
func (act *actionHandle) analyze(ctx context.Context) ([]*source.Error, interface{}, error) {
|
||||||
v := act.handle.Get(ctx)
|
v := act.handle.Get(ctx)
|
||||||
if v == nil {
|
if v == nil {
|
||||||
return nil, nil, errors.Errorf("no analyses for %s", act.pkg.ID())
|
return nil, nil, errors.Errorf("no analyses for %s", act.pkg.ID())
|
||||||
@ -150,10 +150,10 @@ func (act *actionHandle) String() string {
|
|||||||
return fmt.Sprintf("%s@%s", act.analyzer, act.pkg.PkgPath())
|
return fmt.Sprintf("%s@%s", act.analyzer, act.pkg.PkgPath())
|
||||||
}
|
}
|
||||||
|
|
||||||
func execAll(ctx context.Context, fset *token.FileSet, actions []*actionHandle) (map[*actionHandle][]*analysis.Diagnostic, map[*actionHandle]interface{}, error) {
|
func execAll(ctx context.Context, fset *token.FileSet, actions []*actionHandle) (map[*actionHandle][]*source.Error, map[*actionHandle]interface{}, error) {
|
||||||
var (
|
var (
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
diagnostics = make(map[*actionHandle][]*analysis.Diagnostic)
|
diagnostics = make(map[*actionHandle][]*source.Error)
|
||||||
results = make(map[*actionHandle]interface{})
|
results = make(map[*actionHandle]interface{})
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -178,7 +178,7 @@ func execAll(ctx context.Context, fset *token.FileSet, actions []*actionHandle)
|
|||||||
return diagnostics, results, g.Wait()
|
return diagnostics, results, g.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (act *actionHandle) exec(ctx context.Context, fset *token.FileSet) (diagnostics []*analysis.Diagnostic, result interface{}, err error) {
|
func (act *actionHandle) exec(ctx context.Context, fset *token.FileSet) ([]*source.Error, interface{}, error) {
|
||||||
// Analyze dependencies.
|
// Analyze dependencies.
|
||||||
_, depResults, err := execAll(ctx, fset, act.deps)
|
_, depResults, err := execAll(ctx, fset, act.deps)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -204,6 +204,8 @@ func (act *actionHandle) exec(ctx context.Context, fset *token.FileSet) (diagnos
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var diagnostics []*analysis.Diagnostic
|
||||||
|
|
||||||
// Run the analysis.
|
// Run the analysis.
|
||||||
pass := &analysis.Pass{
|
pass := &analysis.Pass{
|
||||||
Analyzer: act.analyzer,
|
Analyzer: act.analyzer,
|
||||||
@ -225,14 +227,13 @@ func (act *actionHandle) exec(ctx context.Context, fset *token.FileSet) (diagnos
|
|||||||
|
|
||||||
if act.pkg.IsIllTyped() {
|
if act.pkg.IsIllTyped() {
|
||||||
return nil, nil, errors.Errorf("analysis skipped due to errors in package: %v", act.pkg.GetErrors())
|
return nil, nil, errors.Errorf("analysis skipped due to errors in package: %v", act.pkg.GetErrors())
|
||||||
} else {
|
}
|
||||||
result, err = pass.Analyzer.Run(pass)
|
result, err := pass.Analyzer.Run(pass)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
if got, want := reflect.TypeOf(result), pass.Analyzer.ResultType; got != want {
|
if got, want := reflect.TypeOf(result), pass.Analyzer.ResultType; got != want {
|
||||||
err = errors.Errorf(
|
err = errors.Errorf(
|
||||||
"internal error: on package %s, analyzer %s returned a result of type %v, but declared ResultType %v",
|
"internal error: on package %s, analyzer %s returned a result of type %v, but declared ResultType %v",
|
||||||
pass.Pkg.Path(), pass.Analyzer, got, want)
|
pass.Pkg.Path(), pass.Analyzer, got, want)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -240,7 +241,15 @@ func (act *actionHandle) exec(ctx context.Context, fset *token.FileSet) (diagnos
|
|||||||
pass.ExportObjectFact = nil
|
pass.ExportObjectFact = nil
|
||||||
pass.ExportPackageFact = nil
|
pass.ExportPackageFact = nil
|
||||||
|
|
||||||
return diagnostics, result, err
|
var errors []*source.Error
|
||||||
|
for _, diag := range diagnostics {
|
||||||
|
srcErr, err := sourceError(ctx, act.pkg, diag)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
errors = append(errors, srcErr)
|
||||||
|
}
|
||||||
|
return errors, result, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// inheritFacts populates act.facts with
|
// inheritFacts populates act.facts with
|
||||||
|
4
internal/lsp/cache/check.go
vendored
4
internal/lsp/cache/check.go
vendored
@ -337,11 +337,11 @@ func (imp *importer) typeCheck(ctx context.Context, cph *checkPackageHandle) (*p
|
|||||||
_ = check.Files(files)
|
_ = check.Files(files)
|
||||||
|
|
||||||
for _, e := range rawErrors {
|
for _, e := range rawErrors {
|
||||||
srcErr, err := sourceError(ctx, imp.snapshot.view, pkg, e)
|
srcErr, err := sourceError(ctx, pkg, e)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
pkg.errors = append(pkg.errors, *srcErr)
|
pkg.errors = append(pkg.errors, srcErr)
|
||||||
}
|
}
|
||||||
|
|
||||||
return pkg, nil
|
return pkg, nil
|
||||||
|
118
internal/lsp/cache/errors.go
vendored
118
internal/lsp/cache/errors.go
vendored
@ -7,17 +7,21 @@ import (
|
|||||||
"go/types"
|
"go/types"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"golang.org/x/tools/go/analysis"
|
||||||
"golang.org/x/tools/go/packages"
|
"golang.org/x/tools/go/packages"
|
||||||
"golang.org/x/tools/internal/lsp/protocol"
|
"golang.org/x/tools/internal/lsp/protocol"
|
||||||
"golang.org/x/tools/internal/lsp/source"
|
"golang.org/x/tools/internal/lsp/source"
|
||||||
"golang.org/x/tools/internal/span"
|
"golang.org/x/tools/internal/span"
|
||||||
)
|
)
|
||||||
|
|
||||||
func sourceError(ctx context.Context, view *view, pkg *pkg, e error) (*source.Error, error) {
|
func sourceError(ctx context.Context, pkg *pkg, e interface{}) (*source.Error, error) {
|
||||||
var (
|
var (
|
||||||
spn span.Span
|
spn span.Span
|
||||||
msg string
|
err error
|
||||||
kind packages.ErrorKind
|
msg, category string
|
||||||
|
kind source.ErrorKind
|
||||||
|
fixes []source.SuggestedFix
|
||||||
|
related []source.RelatedInformation
|
||||||
)
|
)
|
||||||
switch e := e.(type) {
|
switch e := e.(type) {
|
||||||
case packages.Error:
|
case packages.Error:
|
||||||
@ -27,35 +31,113 @@ func sourceError(ctx context.Context, view *view, pkg *pkg, e error) (*source.Er
|
|||||||
spn = span.Parse(e.Pos)
|
spn = span.Parse(e.Pos)
|
||||||
}
|
}
|
||||||
msg = e.Msg
|
msg = e.Msg
|
||||||
kind = e.Kind
|
kind = toSourceErrorKind(e.Kind)
|
||||||
case *scanner.Error:
|
case *scanner.Error:
|
||||||
msg = e.Msg
|
msg = e.Msg
|
||||||
kind = packages.ParseError
|
kind = source.ParseError
|
||||||
spn = span.Parse(e.Pos.String())
|
spn = span.Parse(e.Pos.String())
|
||||||
case scanner.ErrorList:
|
case scanner.ErrorList:
|
||||||
// The first parser error is likely the root cause of the problem.
|
// The first parser error is likely the root cause of the problem.
|
||||||
if e.Len() > 0 {
|
if e.Len() > 0 {
|
||||||
spn = span.Parse(e[0].Pos.String())
|
spn = span.Parse(e[0].Pos.String())
|
||||||
msg = e[0].Msg
|
msg = e[0].Msg
|
||||||
kind = packages.ParseError
|
kind = source.ParseError
|
||||||
}
|
}
|
||||||
case types.Error:
|
case types.Error:
|
||||||
spn = span.Parse(view.session.cache.fset.Position(e.Pos).String())
|
spn = span.Parse(pkg.snapshot.view.session.cache.fset.Position(e.Pos).String())
|
||||||
msg = e.Msg
|
msg = e.Msg
|
||||||
kind = packages.TypeError
|
kind = source.TypeError
|
||||||
|
case *analysis.Diagnostic:
|
||||||
|
spn, err = span.NewRange(pkg.snapshot.view.session.cache.fset, e.Pos, e.End).Span()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
msg = e.Message
|
||||||
|
kind = source.Analysis
|
||||||
|
category = e.Category
|
||||||
|
fixes, err = suggestedFixes(ctx, pkg, e)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
related, err = relatedInformation(ctx, pkg, e)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
rng, err := spanToRange(ctx, pkg, spn, kind == packages.TypeError)
|
rng, err := spanToRange(ctx, pkg, spn, kind == source.TypeError)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &source.Error{
|
return &source.Error{
|
||||||
URI: spn.URI(),
|
URI: spn.URI(),
|
||||||
Range: rng,
|
Range: rng,
|
||||||
Msg: msg,
|
Message: msg,
|
||||||
Kind: kind,
|
Kind: kind,
|
||||||
|
Category: category,
|
||||||
|
SuggestedFixes: fixes,
|
||||||
|
Related: related,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func suggestedFixes(ctx context.Context, pkg *pkg, diag *analysis.Diagnostic) ([]source.SuggestedFix, error) {
|
||||||
|
var fixes []source.SuggestedFix
|
||||||
|
for _, fix := range diag.SuggestedFixes {
|
||||||
|
edits := make(map[span.URI][]protocol.TextEdit)
|
||||||
|
for _, e := range fix.TextEdits {
|
||||||
|
spn, err := span.NewRange(pkg.Snapshot().View().Session().Cache().FileSet(), e.Pos, e.End).Span()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
rng, err := spanToRange(ctx, pkg, spn, false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
edits[spn.URI()] = append(edits[spn.URI()], protocol.TextEdit{
|
||||||
|
Range: rng,
|
||||||
|
NewText: string(e.NewText),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
fixes = append(fixes, source.SuggestedFix{
|
||||||
|
Title: fix.Message,
|
||||||
|
Edits: edits,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return fixes, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func relatedInformation(ctx context.Context, pkg *pkg, diag *analysis.Diagnostic) ([]source.RelatedInformation, error) {
|
||||||
|
var out []source.RelatedInformation
|
||||||
|
for _, related := range diag.Related {
|
||||||
|
spn, err := span.NewRange(pkg.Snapshot().View().Session().Cache().FileSet(), related.Pos, related.End).Span()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
rng, err := spanToRange(ctx, pkg, spn, false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
out = append(out, source.RelatedInformation{
|
||||||
|
URI: spn.URI(),
|
||||||
|
Range: rng,
|
||||||
|
Message: related.Message,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func toSourceErrorKind(kind packages.ErrorKind) source.ErrorKind {
|
||||||
|
switch kind {
|
||||||
|
case packages.ListError:
|
||||||
|
return source.ListError
|
||||||
|
case packages.ParseError:
|
||||||
|
return source.ParseError
|
||||||
|
case packages.TypeError:
|
||||||
|
return source.TypeError
|
||||||
|
default:
|
||||||
|
return source.UnknownError
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// spanToRange converts a span.Span to a protocol.Range,
|
// spanToRange converts a span.Span to a protocol.Range,
|
||||||
// assuming that the span belongs to the package whose diagnostics are being computed.
|
// assuming that the span belongs to the package whose diagnostics are being computed.
|
||||||
func spanToRange(ctx context.Context, pkg *pkg, spn span.Span, isTypeError bool) (protocol.Range, error) {
|
func spanToRange(ctx context.Context, pkg *pkg, spn span.Span, isTypeError bool) (protocol.Range, error) {
|
||||||
@ -67,11 +149,11 @@ func spanToRange(ctx context.Context, pkg *pkg, spn span.Span, isTypeError bool)
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return protocol.Range{}, err
|
return protocol.Range{}, err
|
||||||
}
|
}
|
||||||
data, _, err := ph.File().Read(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return protocol.Range{}, err
|
|
||||||
}
|
|
||||||
if spn.IsPoint() && isTypeError {
|
if spn.IsPoint() && isTypeError {
|
||||||
|
data, _, err := ph.File().Read(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return protocol.Range{}, err
|
||||||
|
}
|
||||||
if s, err := spn.WithOffset(m.Converter); err == nil {
|
if s, err := spn.WithOffset(m.Converter); err == nil {
|
||||||
start := s.Start()
|
start := s.Start()
|
||||||
offset := start.Offset()
|
offset := start.Offset()
|
||||||
|
4
internal/lsp/cache/pkg.go
vendored
4
internal/lsp/cache/pkg.go
vendored
@ -27,7 +27,7 @@ type pkg struct {
|
|||||||
mode source.ParseMode
|
mode source.ParseMode
|
||||||
|
|
||||||
files []source.ParseGoHandle
|
files []source.ParseGoHandle
|
||||||
errors []source.Error
|
errors []*source.Error
|
||||||
imports map[packagePath]*pkg
|
imports map[packagePath]*pkg
|
||||||
types *types.Package
|
types *types.Package
|
||||||
typesInfo *types.Info
|
typesInfo *types.Info
|
||||||
@ -79,7 +79,7 @@ func (p *pkg) GetSyntax(ctx context.Context) []*ast.File {
|
|||||||
return syntax
|
return syntax
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *pkg) GetErrors() []source.Error {
|
func (p *pkg) GetErrors() []*source.Error {
|
||||||
return p.errors
|
return p.errors
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,12 +5,10 @@
|
|||||||
package source
|
package source
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"golang.org/x/tools/go/analysis"
|
"golang.org/x/tools/go/analysis"
|
||||||
"golang.org/x/tools/go/packages"
|
|
||||||
"golang.org/x/tools/internal/lsp/protocol"
|
"golang.org/x/tools/internal/lsp/protocol"
|
||||||
"golang.org/x/tools/internal/lsp/telemetry"
|
"golang.org/x/tools/internal/lsp/telemetry"
|
||||||
"golang.org/x/tools/internal/span"
|
"golang.org/x/tools/internal/span"
|
||||||
@ -30,19 +28,17 @@ type Diagnostic struct {
|
|||||||
Related []RelatedInformation
|
Related []RelatedInformation
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SuggestedFix struct {
|
||||||
|
Title string
|
||||||
|
Edits map[span.URI][]protocol.TextEdit
|
||||||
|
}
|
||||||
|
|
||||||
type RelatedInformation struct {
|
type RelatedInformation struct {
|
||||||
URI span.URI
|
URI span.URI
|
||||||
Range protocol.Range
|
Range protocol.Range
|
||||||
Message string
|
Message string
|
||||||
}
|
}
|
||||||
|
|
||||||
type DiagnosticSeverity int
|
|
||||||
|
|
||||||
const (
|
|
||||||
SeverityWarning DiagnosticSeverity = iota
|
|
||||||
SeverityError
|
|
||||||
)
|
|
||||||
|
|
||||||
func Diagnostics(ctx context.Context, view View, f File, disabledAnalyses map[string]struct{}) (map[span.URI][]Diagnostic, string, error) {
|
func Diagnostics(ctx context.Context, view View, f File, disabledAnalyses map[string]struct{}) (map[span.URI][]Diagnostic, string, error) {
|
||||||
ctx, done := trace.StartSpan(ctx, "source.Diagnostics", telemetry.File.Of(f.URI()))
|
ctx, done := trace.StartSpan(ctx, "source.Diagnostics", telemetry.File.Of(f.URI()))
|
||||||
defer done()
|
defer done()
|
||||||
@ -79,7 +75,7 @@ func Diagnostics(ctx context.Context, view View, f File, disabledAnalyses map[st
|
|||||||
|
|
||||||
// Prepare any additional reports for the errors in this package.
|
// Prepare any additional reports for the errors in this package.
|
||||||
for _, err := range pkg.GetErrors() {
|
for _, err := range pkg.GetErrors() {
|
||||||
if err.Kind != packages.ListError {
|
if err.Kind != ListError {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
clearReports(view, reports, err.URI)
|
clearReports(view, reports, err.URI)
|
||||||
@ -119,7 +115,7 @@ func diagnostics(ctx context.Context, view View, pkg Package, reports map[span.U
|
|||||||
for _, err := range pkg.GetErrors() {
|
for _, err := range pkg.GetErrors() {
|
||||||
diag := &Diagnostic{
|
diag := &Diagnostic{
|
||||||
URI: err.URI,
|
URI: err.URI,
|
||||||
Message: err.Msg,
|
Message: err.Message,
|
||||||
Range: err.Range,
|
Range: err.Range,
|
||||||
Severity: protocol.SeverityError,
|
Severity: protocol.SeverityError,
|
||||||
}
|
}
|
||||||
@ -129,13 +125,13 @@ func diagnostics(ctx context.Context, view View, pkg Package, reports map[span.U
|
|||||||
diagSets[diag.URI] = set
|
diagSets[diag.URI] = set
|
||||||
}
|
}
|
||||||
switch err.Kind {
|
switch err.Kind {
|
||||||
case packages.ParseError:
|
case ParseError:
|
||||||
set.parseErrors = append(set.parseErrors, diag)
|
set.parseErrors = append(set.parseErrors, diag)
|
||||||
diag.Source = "syntax"
|
diag.Source = "syntax"
|
||||||
case packages.TypeError:
|
case TypeError:
|
||||||
set.typeErrors = append(set.typeErrors, diag)
|
set.typeErrors = append(set.typeErrors, diag)
|
||||||
diag.Source = "compiler"
|
diag.Source = "compiler"
|
||||||
default:
|
case ListError:
|
||||||
set.listErrors = append(set.listErrors, diag)
|
set.listErrors = append(set.listErrors, diag)
|
||||||
diag.Source = "go list"
|
diag.Source = "go list"
|
||||||
}
|
}
|
||||||
@ -162,148 +158,39 @@ func diagnostics(ctx context.Context, view View, pkg Package, reports map[span.U
|
|||||||
}
|
}
|
||||||
|
|
||||||
func analyses(ctx context.Context, snapshot Snapshot, cph CheckPackageHandle, disabledAnalyses map[string]struct{}, reports map[span.URI][]Diagnostic) error {
|
func analyses(ctx context.Context, snapshot Snapshot, cph CheckPackageHandle, disabledAnalyses map[string]struct{}, reports map[span.URI][]Diagnostic) error {
|
||||||
// Type checking and parsing succeeded. Run analyses.
|
var analyzers []*analysis.Analyzer
|
||||||
if err := runAnalyses(ctx, snapshot, cph, disabledAnalyses, func(diags []*analysis.Diagnostic, a *analysis.Analyzer) error {
|
for _, a := range snapshot.View().Options().Analyzers {
|
||||||
for _, diag := range diags {
|
if _, ok := disabledAnalyses[a.Name]; ok {
|
||||||
diagnostic, err := toDiagnostic(ctx, snapshot.View(), diag, a.Name)
|
continue
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
addReport(snapshot.View(), reports, diagnostic.URI, diagnostic)
|
|
||||||
}
|
}
|
||||||
return nil
|
analyzers = append(analyzers, a)
|
||||||
}); err != nil {
|
}
|
||||||
|
|
||||||
|
diagnostics, err := snapshot.Analyze(ctx, cph.ID(), analyzers)
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For caching diagnostics.
|
||||||
|
// TODO(https://golang.org/issue/32443): Cache diagnostics on the snapshot.
|
||||||
|
pkg, err := cph.Check(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Report diagnostics and errors from root analyzers.
|
||||||
|
var sdiags []Diagnostic
|
||||||
|
for a, diags := range diagnostics {
|
||||||
|
for _, diag := range diags {
|
||||||
|
sdiag := toDiagnostic(diag, a.Name)
|
||||||
|
addReport(snapshot.View(), reports, sdiag)
|
||||||
|
sdiags = append(sdiags, sdiag)
|
||||||
|
}
|
||||||
|
pkg.SetDiagnostics(a, sdiags)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func packageForSpan(ctx context.Context, view View, spn span.Span) (Package, error) {
|
|
||||||
f, err := view.GetFile(ctx, spn.URI())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
// If the package has changed since these diagnostics were computed,
|
|
||||||
// this may be incorrect. Should the package be associated with the diagnostic?
|
|
||||||
_, cphs, err := view.CheckPackageHandles(ctx, f)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
cph, err := NarrowestCheckPackageHandle(cphs)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return cph.Cached(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
func toDiagnostic(ctx context.Context, view View, diag *analysis.Diagnostic, category string) (Diagnostic, error) {
|
|
||||||
spn, err := span.NewRange(view.Session().Cache().FileSet(), diag.Pos, diag.End).Span()
|
|
||||||
if err != nil {
|
|
||||||
return Diagnostic{}, err
|
|
||||||
}
|
|
||||||
pkg, err := packageForSpan(ctx, view, spn)
|
|
||||||
if err != nil {
|
|
||||||
return Diagnostic{}, err
|
|
||||||
}
|
|
||||||
ph, err := pkg.File(spn.URI())
|
|
||||||
if err != nil {
|
|
||||||
return Diagnostic{}, err
|
|
||||||
}
|
|
||||||
_, m, _, err := ph.Cached(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return Diagnostic{}, err
|
|
||||||
}
|
|
||||||
rng, err := m.Range(spn)
|
|
||||||
if err != nil {
|
|
||||||
return Diagnostic{}, err
|
|
||||||
}
|
|
||||||
fixes, err := suggestedFixes(ctx, view, pkg, diag)
|
|
||||||
if err != nil {
|
|
||||||
return Diagnostic{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
related, err := relatedInformation(ctx, view, diag)
|
|
||||||
if err != nil {
|
|
||||||
return Diagnostic{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is a bit of a hack, but clients > 3.15 will be able to grey out unnecessary code.
|
|
||||||
// If we are deleting code as part of all of our suggested fixes, assume that this is dead code.
|
|
||||||
// TODO(golang/go/#34508): Return these codes from the diagnostics themselves.
|
|
||||||
var tags []protocol.DiagnosticTag
|
|
||||||
if onlyDeletions(fixes) {
|
|
||||||
tags = append(tags, protocol.Unnecessary)
|
|
||||||
}
|
|
||||||
if diag.Category != "" {
|
|
||||||
category += "." + diag.Category
|
|
||||||
}
|
|
||||||
return Diagnostic{
|
|
||||||
URI: spn.URI(),
|
|
||||||
Range: rng,
|
|
||||||
Source: category,
|
|
||||||
Message: diag.Message,
|
|
||||||
Severity: protocol.SeverityWarning,
|
|
||||||
SuggestedFixes: fixes,
|
|
||||||
Tags: tags,
|
|
||||||
Related: related,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func relatedInformation(ctx context.Context, view View, diag *analysis.Diagnostic) ([]RelatedInformation, error) {
|
|
||||||
var out []RelatedInformation
|
|
||||||
for _, related := range diag.Related {
|
|
||||||
r := span.NewRange(view.Session().Cache().FileSet(), related.Pos, related.End)
|
|
||||||
spn, err := r.Span()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
pkg, err := packageForSpan(ctx, view, spn)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
rng, err := spanToRange(ctx, view, pkg, spn, false)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
out = append(out, RelatedInformation{
|
|
||||||
URI: spn.URI(),
|
|
||||||
Range: rng,
|
|
||||||
Message: related.Message,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return out, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// spanToRange converts a span.Span to a protocol.Range,
|
|
||||||
// assuming that the span belongs to the package whose diagnostics are being computed.
|
|
||||||
func spanToRange(ctx context.Context, view View, pkg Package, spn span.Span, isTypeError bool) (protocol.Range, error) {
|
|
||||||
ph, err := pkg.File(spn.URI())
|
|
||||||
if err != nil {
|
|
||||||
return protocol.Range{}, err
|
|
||||||
}
|
|
||||||
_, m, _, err := ph.Cached(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return protocol.Range{}, err
|
|
||||||
}
|
|
||||||
data, _, err := ph.File().Read(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return protocol.Range{}, err
|
|
||||||
}
|
|
||||||
// Try to get a range for the diagnostic.
|
|
||||||
// TODO: Don't just limit ranges to type errors.
|
|
||||||
if spn.IsPoint() && isTypeError {
|
|
||||||
if s, err := spn.WithOffset(m.Converter); err == nil {
|
|
||||||
start := s.Start()
|
|
||||||
offset := start.Offset()
|
|
||||||
if width := bytes.IndexAny(data[offset:], " \n,():;[]"); width > 0 {
|
|
||||||
spn = span.New(spn.URI(), start, span.NewPoint(start.Line(), start.Column()+width, offset+width))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return m.Range(spn)
|
|
||||||
}
|
|
||||||
|
|
||||||
func clearReports(v View, reports map[span.URI][]Diagnostic, uri span.URI) {
|
func clearReports(v View, reports map[span.URI][]Diagnostic, uri span.URI) {
|
||||||
if v.Ignore(uri) {
|
if v.Ignore(uri) {
|
||||||
return
|
return
|
||||||
@ -311,12 +198,12 @@ func clearReports(v View, reports map[span.URI][]Diagnostic, uri span.URI) {
|
|||||||
reports[uri] = []Diagnostic{}
|
reports[uri] = []Diagnostic{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func addReport(v View, reports map[span.URI][]Diagnostic, uri span.URI, diagnostic Diagnostic) {
|
func addReport(v View, reports map[span.URI][]Diagnostic, diagnostic Diagnostic) {
|
||||||
if v.Ignore(uri) {
|
if v.Ignore(diagnostic.URI) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if _, ok := reports[uri]; ok {
|
if _, ok := reports[diagnostic.URI]; ok {
|
||||||
reports[uri] = append(reports[uri], diagnostic)
|
reports[diagnostic.URI] = append(reports[diagnostic.URI], diagnostic)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -332,38 +219,42 @@ func singleDiagnostic(uri span.URI, format string, a ...interface{}) map[span.UR
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func runAnalyses(ctx context.Context, snapshot Snapshot, cph CheckPackageHandle, disabledAnalyses map[string]struct{}, report func(diag []*analysis.Diagnostic, a *analysis.Analyzer) error) error {
|
func toDiagnostic(e *Error, category string) Diagnostic {
|
||||||
var analyzers []*analysis.Analyzer
|
// This is a bit of a hack, but clients > 3.15 will be able to grey out unnecessary code.
|
||||||
for _, a := range snapshot.View().Options().Analyzers {
|
// If we are deleting code as part of all of our suggested fixes, assume that this is dead code.
|
||||||
if _, ok := disabledAnalyses[a.Name]; ok {
|
// TODO(golang/go/#34508): Return these codes from the diagnostics themselves.
|
||||||
continue
|
var tags []protocol.DiagnosticTag
|
||||||
}
|
if onlyDeletions(e.SuggestedFixes) {
|
||||||
analyzers = append(analyzers, a)
|
tags = append(tags, protocol.Unnecessary)
|
||||||
}
|
}
|
||||||
|
if e.Category != "" {
|
||||||
diagnostics, err := snapshot.Analyze(ctx, cph.ID(), analyzers)
|
category += "." + e.Category
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
return Diagnostic{
|
||||||
// Report diagnostics and errors from root analyzers.
|
URI: e.URI,
|
||||||
var sdiags []Diagnostic
|
Range: e.Range,
|
||||||
for a, diags := range diagnostics {
|
Message: e.Message,
|
||||||
if err := report(diags, a); err != nil {
|
Source: category,
|
||||||
return err
|
Severity: protocol.SeverityWarning,
|
||||||
}
|
Tags: tags,
|
||||||
for _, diag := range diags {
|
SuggestedFixes: e.SuggestedFixes,
|
||||||
sdiag, err := toDiagnostic(ctx, snapshot.View(), diag, a.Name)
|
Related: e.Related,
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
sdiags = append(sdiags, sdiag)
|
|
||||||
}
|
|
||||||
pkg, err := cph.Check(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
pkg.SetDiagnostics(a, sdiags)
|
|
||||||
}
|
}
|
||||||
return nil
|
}
|
||||||
|
|
||||||
|
// onlyDeletions returns true if all of the suggested fixes are deletions.
|
||||||
|
func onlyDeletions(fixes []SuggestedFix) bool {
|
||||||
|
for _, fix := range fixes {
|
||||||
|
for _, edits := range fix.Edits {
|
||||||
|
for _, edit := range edits {
|
||||||
|
if edit.NewText != "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if protocol.ComparePosition(edit.Range.Start, edit.Range.End) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"go/format"
|
"go/format"
|
||||||
|
|
||||||
"golang.org/x/tools/go/packages"
|
|
||||||
"golang.org/x/tools/internal/imports"
|
"golang.org/x/tools/internal/imports"
|
||||||
"golang.org/x/tools/internal/lsp/diff"
|
"golang.org/x/tools/internal/lsp/diff"
|
||||||
"golang.org/x/tools/internal/lsp/protocol"
|
"golang.org/x/tools/internal/lsp/protocol"
|
||||||
@ -269,7 +268,7 @@ func CandidateImports(ctx context.Context, view View, filename string) (pkgs []i
|
|||||||
// hasParseErrors returns true if the given file has parse errors.
|
// hasParseErrors returns true if the given file has parse errors.
|
||||||
func hasParseErrors(pkg Package, uri span.URI) bool {
|
func hasParseErrors(pkg Package, uri span.URI) bool {
|
||||||
for _, err := range pkg.GetErrors() {
|
for _, err := range pkg.GetErrors() {
|
||||||
if err.URI == uri && err.Kind == packages.ParseError {
|
if err.URI == uri && err.Kind == ParseError {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -278,7 +277,7 @@ func hasParseErrors(pkg Package, uri span.URI) bool {
|
|||||||
|
|
||||||
func hasListErrors(pkg Package) bool {
|
func hasListErrors(pkg Package) bool {
|
||||||
for _, err := range pkg.GetErrors() {
|
for _, err := range pkg.GetErrors() {
|
||||||
if err.Kind == packages.ListError {
|
if err.Kind == ListError {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,67 +1 @@
|
|||||||
package source
|
package source
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"golang.org/x/tools/go/analysis"
|
|
||||||
"golang.org/x/tools/internal/lsp/protocol"
|
|
||||||
"golang.org/x/tools/internal/span"
|
|
||||||
)
|
|
||||||
|
|
||||||
type SuggestedFix struct {
|
|
||||||
Title string
|
|
||||||
Edits map[span.URI][]protocol.TextEdit
|
|
||||||
}
|
|
||||||
|
|
||||||
func suggestedFixes(ctx context.Context, view View, pkg Package, diag *analysis.Diagnostic) ([]SuggestedFix, error) {
|
|
||||||
var fixes []SuggestedFix
|
|
||||||
for _, fix := range diag.SuggestedFixes {
|
|
||||||
edits := make(map[span.URI][]protocol.TextEdit)
|
|
||||||
for _, e := range fix.TextEdits {
|
|
||||||
posn := view.Session().Cache().FileSet().Position(e.Pos)
|
|
||||||
uri := span.FileURI(posn.Filename)
|
|
||||||
ph, _, err := pkg.FindFile(ctx, uri)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
_, m, _, err := ph.Cached(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
mrng, err := posToRange(ctx, view, m, e.Pos, e.End)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
rng, err := mrng.Range()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
edits[uri] = append(edits[uri], protocol.TextEdit{
|
|
||||||
Range: rng,
|
|
||||||
NewText: string(e.NewText),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
fixes = append(fixes, SuggestedFix{
|
|
||||||
Title: fix.Message,
|
|
||||||
Edits: edits,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return fixes, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// onlyDeletions returns true if all of the suggested fixes are deletions.
|
|
||||||
func onlyDeletions(fixes []SuggestedFix) bool {
|
|
||||||
for _, fix := range fixes {
|
|
||||||
for _, edits := range fix.Edits {
|
|
||||||
for _, edit := range edits {
|
|
||||||
if edit.NewText != "" {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if protocol.ComparePosition(edit.Range.Start, edit.Range.End) == 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
@ -262,7 +262,7 @@ type Snapshot interface {
|
|||||||
View() View
|
View() View
|
||||||
|
|
||||||
// Analyze runs the analyses for the given package at this snapshot.
|
// Analyze runs the analyses for the given package at this snapshot.
|
||||||
Analyze(ctx context.Context, id string, analyzers []*analysis.Analyzer) (map[*analysis.Analyzer][]*analysis.Diagnostic, error)
|
Analyze(ctx context.Context, id string, analyzers []*analysis.Analyzer) (map[*analysis.Analyzer][]*Error, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// File represents a source file of any type.
|
// File represents a source file of any type.
|
||||||
@ -280,7 +280,7 @@ type Package interface {
|
|||||||
Files() []ParseGoHandle
|
Files() []ParseGoHandle
|
||||||
File(uri span.URI) (ParseGoHandle, error)
|
File(uri span.URI) (ParseGoHandle, error)
|
||||||
GetSyntax(context.Context) []*ast.File
|
GetSyntax(context.Context) []*ast.File
|
||||||
GetErrors() []Error
|
GetErrors() []*Error
|
||||||
GetTypes() *types.Package
|
GetTypes() *types.Package
|
||||||
GetTypesInfo() *types.Info
|
GetTypesInfo() *types.Info
|
||||||
GetTypesSizes() types.Sizes
|
GetTypesSizes() types.Sizes
|
||||||
@ -298,14 +298,27 @@ type Package interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Error struct {
|
type Error struct {
|
||||||
Msg string
|
URI span.URI
|
||||||
URI span.URI
|
Range protocol.Range
|
||||||
Range protocol.Range
|
Kind ErrorKind
|
||||||
Kind packages.ErrorKind
|
Message string
|
||||||
|
Category string
|
||||||
|
SuggestedFixes []SuggestedFix
|
||||||
|
Related []RelatedInformation
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ErrorKind int
|
||||||
|
|
||||||
|
const (
|
||||||
|
UnknownError = ErrorKind(iota)
|
||||||
|
ListError
|
||||||
|
ParseError
|
||||||
|
TypeError
|
||||||
|
Analysis
|
||||||
|
)
|
||||||
|
|
||||||
func (e *Error) Error() string {
|
func (e *Error) Error() string {
|
||||||
return fmt.Sprintf("%s:%s: %s", e.URI, e.Range, e.Msg)
|
return fmt.Sprintf("%s:%s: %s", e.URI, e.Range, e.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
type BuiltinPackage interface {
|
type BuiltinPackage interface {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user