Skip to content

Commit 0bb5a05

Browse files
committed
internal/lsp: use the AST to get correct ranges
Fixes golang/go#29150 Change-Id: I0cb8be25f7f40f7f8becedf2e0002c58620c72da Reviewed-on: https://go-review.googlesource.com/c/tools/+/202542 Run-TryBot: Rebecca Stambler <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Ian Cottrell <[email protected]>
1 parent 774d2ec commit 0bb5a05

File tree

2 files changed

+92
-32
lines changed

2 files changed

+92
-32
lines changed

internal/lsp/cache/errors.go

Lines changed: 91 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,19 @@ package cache
33
import (
44
"bytes"
55
"context"
6+
"go/ast"
67
"go/scanner"
8+
"go/token"
79
"go/types"
810
"strings"
911

1012
"golang.org/x/tools/go/analysis"
13+
"golang.org/x/tools/go/ast/astutil"
1114
"golang.org/x/tools/go/packages"
1215
"golang.org/x/tools/internal/lsp/protocol"
1316
"golang.org/x/tools/internal/lsp/source"
1417
"golang.org/x/tools/internal/span"
18+
errors "golang.org/x/xerrors"
1519
)
1620

1721
func sourceError(ctx context.Context, pkg *pkg, e interface{}) (*source.Error, error) {
@@ -23,6 +27,7 @@ func sourceError(ctx context.Context, pkg *pkg, e interface{}) (*source.Error, e
2327
fixes []source.SuggestedFix
2428
related []source.RelatedInformation
2529
)
30+
fset := pkg.snapshot.view.session.cache.fset
2631
switch e := e.(type) {
2732
case packages.Error:
2833
if e.Pos == "" {
@@ -32,21 +37,32 @@ func sourceError(ctx context.Context, pkg *pkg, e interface{}) (*source.Error, e
3237
}
3338
msg = e.Msg
3439
kind = toSourceErrorKind(e.Kind)
40+
3541
case *scanner.Error:
3642
msg = e.Msg
3743
kind = source.ParseError
38-
spn = span.Parse(e.Pos.String())
44+
spn, err = scannerErrorRange(ctx, fset, pkg, e.Pos)
45+
if err != nil {
46+
return nil, err
47+
}
48+
3949
case scanner.ErrorList:
4050
// The first parser error is likely the root cause of the problem.
41-
if e.Len() > 0 {
42-
spn = span.Parse(e[0].Pos.String())
43-
msg = e[0].Msg
44-
kind = source.ParseError
51+
if e.Len() <= 0 {
52+
return nil, errors.Errorf("no errors in %v", e)
4553
}
54+
msg = e[0].Msg
55+
kind = source.ParseError
56+
spn, err = scannerErrorRange(ctx, fset, pkg, e[0].Pos)
57+
if err != nil {
58+
return nil, err
59+
}
60+
4661
case types.Error:
47-
spn = span.Parse(pkg.snapshot.view.session.cache.fset.Position(e.Pos).String())
4862
msg = e.Msg
4963
kind = source.TypeError
64+
spn, err = typeErrorRange(ctx, fset, pkg, e.Pos)
65+
5066
case *analysis.Diagnostic:
5167
spn, err = span.NewRange(pkg.snapshot.view.session.cache.fset, e.Pos, e.End).Span()
5268
if err != nil {
@@ -64,7 +80,7 @@ func sourceError(ctx context.Context, pkg *pkg, e interface{}) (*source.Error, e
6480
return nil, err
6581
}
6682
}
67-
rng, err := spanToRange(ctx, pkg, spn, kind == source.TypeError)
83+
rng, err := spanToRange(ctx, pkg, spn)
6884
if err != nil {
6985
return nil, err
7086
}
@@ -88,7 +104,7 @@ func suggestedFixes(ctx context.Context, pkg *pkg, diag *analysis.Diagnostic) ([
88104
if err != nil {
89105
return nil, err
90106
}
91-
rng, err := spanToRange(ctx, pkg, spn, false)
107+
rng, err := spanToRange(ctx, pkg, spn)
92108
if err != nil {
93109
return nil, err
94110
}
@@ -112,7 +128,7 @@ func relatedInformation(ctx context.Context, pkg *pkg, diag *analysis.Diagnostic
112128
if err != nil {
113129
return nil, err
114130
}
115-
rng, err := spanToRange(ctx, pkg, spn, false)
131+
rng, err := spanToRange(ctx, pkg, spn)
116132
if err != nil {
117133
return nil, err
118134
}
@@ -138,32 +154,81 @@ func toSourceErrorKind(kind packages.ErrorKind) source.ErrorKind {
138154
}
139155
}
140156

157+
func typeErrorRange(ctx context.Context, fset *token.FileSet, pkg *pkg, pos token.Pos) (span.Span, error) {
158+
spn, err := span.NewRange(fset, pos, pos).Span()
159+
if err != nil {
160+
return span.Span{}, err
161+
}
162+
posn := fset.Position(pos)
163+
ph, _, err := pkg.FindFile(ctx, span.FileURI(posn.Filename))
164+
if err != nil {
165+
return span.Span{}, err
166+
}
167+
file, m, _, err := ph.Cached(ctx)
168+
if err != nil {
169+
return span.Span{}, err
170+
}
171+
path, _ := astutil.PathEnclosingInterval(file, pos, pos)
172+
if len(path) > 0 {
173+
if s, err := span.NewRange(fset, path[0].Pos(), path[0].End()).Span(); err == nil {
174+
return s, nil
175+
}
176+
}
177+
s, err := spn.WithOffset(m.Converter)
178+
if err != nil {
179+
return span.Span{}, err
180+
}
181+
data, _, err := ph.File().Read(ctx)
182+
if err != nil {
183+
return span.Span{}, err
184+
}
185+
start := s.Start()
186+
offset := start.Offset()
187+
if offset < len(data) {
188+
if width := bytes.IndexAny(data[offset:], " \n,():;[]"); width > 0 {
189+
return span.New(spn.URI(), start, span.NewPoint(start.Line(), start.Column()+width, offset+width)), nil
190+
}
191+
}
192+
return spn, nil
193+
}
194+
195+
func scannerErrorRange(ctx context.Context, fset *token.FileSet, pkg *pkg, posn token.Position) (span.Span, error) {
196+
ph, _, err := pkg.FindFile(ctx, span.FileURI(posn.Filename))
197+
if err != nil {
198+
return span.Span{}, err
199+
}
200+
file, _, _, err := ph.Cached(ctx)
201+
if err != nil {
202+
return span.Span{}, err
203+
}
204+
tok := fset.File(file.Pos())
205+
if tok == nil {
206+
return span.Span{}, errors.Errorf("no token.File for %s", ph.File().Identity().URI)
207+
}
208+
pos := tok.Pos(posn.Offset)
209+
path, _ := astutil.PathEnclosingInterval(file, pos, pos)
210+
if len(path) > 0 {
211+
switch n := path[0].(type) {
212+
case *ast.BadDecl, *ast.BadExpr, *ast.BadStmt:
213+
if s, err := span.NewRange(fset, n.Pos(), n.End()).Span(); err == nil {
214+
return s, nil
215+
}
216+
}
217+
}
218+
return span.NewRange(fset, pos, pos).Span()
219+
}
220+
141221
// spanToRange converts a span.Span to a protocol.Range,
142222
// assuming that the span belongs to the package whose diagnostics are being computed.
143-
func spanToRange(ctx context.Context, pkg *pkg, spn span.Span, isTypeError bool) (protocol.Range, error) {
144-
ph, err := pkg.File(spn.URI())
223+
func spanToRange(ctx context.Context, pkg *pkg, spn span.Span) (protocol.Range, error) {
224+
ph, _, err := pkg.FindFile(ctx, spn.URI())
145225
if err != nil {
146226
return protocol.Range{}, err
147227
}
148228
_, m, _, err := ph.Cached(ctx)
149229
if err != nil {
150230
return protocol.Range{}, err
151231
}
152-
if spn.IsPoint() && isTypeError {
153-
data, _, err := ph.File().Read(ctx)
154-
if err != nil {
155-
return protocol.Range{}, err
156-
}
157-
if s, err := spn.WithOffset(m.Converter); err == nil {
158-
start := s.Start()
159-
offset := start.Offset()
160-
if offset < len(data) {
161-
if width := bytes.IndexAny(data[offset:], " \n,():;[]"); width > 0 {
162-
spn = span.New(spn.URI(), start, span.NewPoint(start.Line(), start.Column()+width, offset+width))
163-
}
164-
}
165-
}
166-
}
167232
return m.Range(spn)
168233
}
169234

internal/lsp/tests/diagnostics.go

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,7 @@ func DiffDiagnostics(want, got []source.Diagnostic) string {
3838
if protocol.ComparePosition(w.Range.Start, g.Range.Start) != 0 {
3939
return summarizeDiagnostics(i, want, got, "incorrect Start got %v want %v", g.Range.Start, w.Range.Start)
4040
}
41-
// Special case for diagnostics on parse errors.
42-
if strings.Contains(string(g.URI), "noparse") {
43-
if protocol.ComparePosition(g.Range.Start, g.Range.End) != 0 || protocol.ComparePosition(w.Range.Start, g.Range.End) != 0 {
44-
return summarizeDiagnostics(i, want, got, "incorrect End got %v want %v", g.Range.End, w.Range.Start)
45-
}
46-
} else if !protocol.IsPoint(g.Range) { // Accept any 'want' range if the diagnostic returns a zero-length range.
41+
if !protocol.IsPoint(g.Range) { // Accept any 'want' range if the diagnostic returns a zero-length range.
4742
if protocol.ComparePosition(w.Range.End, g.Range.End) != 0 {
4843
return summarizeDiagnostics(i, want, got, "incorrect End got %v want %v", g.Range.End, w.Range.End)
4944
}

0 commit comments

Comments
 (0)