mirror of
https://github.com/golang/go
synced 2024-11-19 01:54:39 -07:00
2f43c6d1a2
Change span to hide its fields and have validating accessors This catches the cases where either the offset or the position is being used when it was not set. It also normalizes the forms as the API now controls them, and allows us to simplify some of the logic. The converters are now allowed to return an error, which lets us cleanly propagate bad cases. The lsp was then converted to the new format, and also had some error checking of its own added on the top. All this allowed me to find and fix a few issues, most notably a case where the wrong column mapper was being used during the conversion of definition results. Change-Id: Iebdf8901e8269b28aaef60caf76574baa25c46d4 Reviewed-on: https://go-review.googlesource.com/c/tools/+/167858 Run-TryBot: Ian Cottrell <iancottrell@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Rebecca Stambler <rstambler@golang.org>
188 lines
5.3 KiB
Go
188 lines
5.3 KiB
Go
// Copyright 2018 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package source
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
"log"
|
|
|
|
"golang.org/x/tools/go/analysis"
|
|
"golang.org/x/tools/go/analysis/passes/asmdecl"
|
|
"golang.org/x/tools/go/analysis/passes/assign"
|
|
"golang.org/x/tools/go/analysis/passes/atomic"
|
|
"golang.org/x/tools/go/analysis/passes/atomicalign"
|
|
"golang.org/x/tools/go/analysis/passes/bools"
|
|
"golang.org/x/tools/go/analysis/passes/buildtag"
|
|
"golang.org/x/tools/go/analysis/passes/cgocall"
|
|
"golang.org/x/tools/go/analysis/passes/composite"
|
|
"golang.org/x/tools/go/analysis/passes/copylock"
|
|
"golang.org/x/tools/go/analysis/passes/httpresponse"
|
|
"golang.org/x/tools/go/analysis/passes/loopclosure"
|
|
"golang.org/x/tools/go/analysis/passes/lostcancel"
|
|
"golang.org/x/tools/go/analysis/passes/nilfunc"
|
|
"golang.org/x/tools/go/analysis/passes/printf"
|
|
"golang.org/x/tools/go/analysis/passes/shift"
|
|
"golang.org/x/tools/go/analysis/passes/stdmethods"
|
|
"golang.org/x/tools/go/analysis/passes/structtag"
|
|
"golang.org/x/tools/go/analysis/passes/tests"
|
|
"golang.org/x/tools/go/analysis/passes/unmarshal"
|
|
"golang.org/x/tools/go/analysis/passes/unreachable"
|
|
"golang.org/x/tools/go/analysis/passes/unsafeptr"
|
|
"golang.org/x/tools/go/analysis/passes/unusedresult"
|
|
"golang.org/x/tools/go/packages"
|
|
"golang.org/x/tools/internal/span"
|
|
)
|
|
|
|
type Diagnostic struct {
|
|
span.Span
|
|
Message string
|
|
Source string
|
|
Severity DiagnosticSeverity
|
|
}
|
|
|
|
type DiagnosticSeverity int
|
|
|
|
const (
|
|
SeverityWarning DiagnosticSeverity = iota
|
|
SeverityError
|
|
)
|
|
|
|
func Diagnostics(ctx context.Context, v View, uri span.URI) (map[span.URI][]Diagnostic, error) {
|
|
f, err := v.GetFile(ctx, uri)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
pkg := f.GetPackage(ctx)
|
|
if pkg == nil {
|
|
return nil, fmt.Errorf("no package found for %v", f.URI())
|
|
}
|
|
// Prepare the reports we will send for this package.
|
|
reports := make(map[span.URI][]Diagnostic)
|
|
for _, filename := range pkg.GetFilenames() {
|
|
reports[span.FileURI(filename)] = []Diagnostic{}
|
|
}
|
|
var listErrors, parseErrors, typeErrors []packages.Error
|
|
for _, err := range pkg.GetErrors() {
|
|
switch err.Kind {
|
|
case packages.ParseError:
|
|
parseErrors = append(parseErrors, err)
|
|
case packages.TypeError:
|
|
typeErrors = append(typeErrors, err)
|
|
default:
|
|
listErrors = append(listErrors, err)
|
|
}
|
|
}
|
|
// Don't report type errors if there are parse errors or list errors.
|
|
diags := typeErrors
|
|
if len(parseErrors) > 0 {
|
|
diags = parseErrors
|
|
} else if len(listErrors) > 0 {
|
|
diags = listErrors
|
|
}
|
|
for _, diag := range diags {
|
|
spn := span.Parse(diag.Pos)
|
|
if spn.IsPoint() && diag.Kind == packages.TypeError {
|
|
// Don't set a range if it's anything other than a type error.
|
|
if diagFile, err := v.GetFile(ctx, spn.URI()); err == nil {
|
|
tok := diagFile.GetToken(ctx)
|
|
if tok == nil {
|
|
continue // ignore errors
|
|
}
|
|
content := diagFile.GetContent(ctx)
|
|
c := span.NewTokenConverter(diagFile.GetFileSet(ctx), tok)
|
|
s, err := spn.WithOffset(c)
|
|
//we just don't bother producing an error if this failed
|
|
if err == nil {
|
|
start := s.Start()
|
|
offset := start.Offset()
|
|
if l := bytes.IndexAny(content[offset:], " \n,():;[]"); l > 0 {
|
|
spn = span.New(spn.URI(), start, span.NewPoint(start.Line(), start.Column()+l, offset+l))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
diagnostic := Diagnostic{
|
|
Span: spn,
|
|
Message: diag.Msg,
|
|
Severity: SeverityError,
|
|
}
|
|
if _, ok := reports[spn.URI()]; ok {
|
|
reports[spn.URI()] = append(reports[spn.URI()], diagnostic)
|
|
}
|
|
}
|
|
if len(diags) > 0 {
|
|
return reports, nil
|
|
}
|
|
// Type checking and parsing succeeded. Run analyses.
|
|
runAnalyses(ctx, v, pkg, func(a *analysis.Analyzer, diag analysis.Diagnostic) {
|
|
r := span.NewRange(v.FileSet(), diag.Pos, 0)
|
|
s, err := r.Span()
|
|
if err != nil {
|
|
//TODO: we could not process the diag.Pos, and thus have no valid span
|
|
//we don't have anywhere to put this error though
|
|
log.Print(err)
|
|
}
|
|
category := a.Name
|
|
if diag.Category != "" {
|
|
category += "." + category
|
|
}
|
|
|
|
reports[s.URI()] = append(reports[s.URI()], Diagnostic{
|
|
Source: category,
|
|
Span: s,
|
|
Message: fmt.Sprintf(diag.Message),
|
|
Severity: SeverityWarning,
|
|
})
|
|
})
|
|
|
|
return reports, nil
|
|
}
|
|
|
|
func runAnalyses(ctx context.Context, v View, pkg Package, report func(a *analysis.Analyzer, diag analysis.Diagnostic)) error {
|
|
// the traditional vet suite:
|
|
analyzers := []*analysis.Analyzer{
|
|
asmdecl.Analyzer,
|
|
assign.Analyzer,
|
|
atomic.Analyzer,
|
|
atomicalign.Analyzer,
|
|
bools.Analyzer,
|
|
buildtag.Analyzer,
|
|
cgocall.Analyzer,
|
|
composite.Analyzer,
|
|
copylock.Analyzer,
|
|
httpresponse.Analyzer,
|
|
loopclosure.Analyzer,
|
|
lostcancel.Analyzer,
|
|
nilfunc.Analyzer,
|
|
printf.Analyzer,
|
|
shift.Analyzer,
|
|
stdmethods.Analyzer,
|
|
structtag.Analyzer,
|
|
tests.Analyzer,
|
|
unmarshal.Analyzer,
|
|
unreachable.Analyzer,
|
|
unsafeptr.Analyzer,
|
|
unusedresult.Analyzer,
|
|
}
|
|
|
|
roots := analyze(ctx, v, []Package{pkg}, analyzers)
|
|
|
|
// Report diagnostics and errors from root analyzers.
|
|
for _, r := range roots {
|
|
for _, diag := range r.diagnostics {
|
|
if r.err != nil {
|
|
// TODO(matloob): This isn't quite right: we might return a failed prerequisites error,
|
|
// which isn't super useful...
|
|
return r.err
|
|
}
|
|
report(r.Analyzer, diag)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|