@@ -9,8 +9,10 @@ package cache
9
9
// source.Diagnostic form, and suggesting quick fixes.
10
10
11
11
import (
12
+ "context"
12
13
"fmt"
13
14
"go/scanner"
15
+ "go/token"
14
16
"go/types"
15
17
"log"
16
18
"regexp"
@@ -28,20 +30,26 @@ import (
28
30
"golang.org/x/tools/internal/typesinternal"
29
31
)
30
32
31
- func goPackagesErrorDiagnostics (e packages.Error , pkg * syntaxPackage , fromDir string ) (diags []* source.Diagnostic , rerr error ) {
32
- if diag , ok := parseGoListImportCycleError (e , pkg ); ok {
33
+ // goPackagesErrorDiagnostics translates the given go/packages Error into a
34
+ // diagnostic, using the provided metadata and filesource.
35
+ //
36
+ // The slice of diagnostics may be empty.
37
+ func goPackagesErrorDiagnostics (ctx context.Context , e packages.Error , m * source.Metadata , fs source.FileSource ) ([]* source.Diagnostic , error ) {
38
+ if diag , err := parseGoListImportCycleError (ctx , e , m , fs ); err != nil {
39
+ return nil , err
40
+ } else if diag != nil {
33
41
return []* source.Diagnostic {diag }, nil
34
42
}
35
43
36
44
var spn span.Span
37
45
if e .Pos == "" {
38
- spn = parseGoListError (e .Msg , fromDir )
46
+ spn = parseGoListError (e .Msg , m . LoadDir )
39
47
// We may not have been able to parse a valid span. Apply the errors to all files.
40
- if _ , err := spanToRange (pkg , spn ); err != nil {
48
+ if _ , err := spanToRange (ctx , fs , spn ); err != nil {
41
49
var diags []* source.Diagnostic
42
- for _ , pgf := range pkg . compiledGoFiles {
50
+ for _ , uri := range m . CompiledGoFiles {
43
51
diags = append (diags , & source.Diagnostic {
44
- URI : pgf . URI ,
52
+ URI : uri ,
45
53
Severity : protocol .SeverityError ,
46
54
Source : source .ListError ,
47
55
Message : e .Msg ,
@@ -50,7 +58,7 @@ func goPackagesErrorDiagnostics(e packages.Error, pkg *syntaxPackage, fromDir st
50
58
return diags , nil
51
59
}
52
60
} else {
53
- spn = span .ParseInDir (e .Pos , fromDir )
61
+ spn = span .ParseInDir (e .Pos , m . LoadDir )
54
62
}
55
63
56
64
// TODO(rfindley): in some cases the go command outputs invalid spans, for
@@ -64,7 +72,7 @@ func goPackagesErrorDiagnostics(e packages.Error, pkg *syntaxPackage, fromDir st
64
72
// likely because *token.File lacks information about newline termination.
65
73
//
66
74
// We could do better here by handling that case.
67
- rng , err := spanToRange (pkg , spn )
75
+ rng , err := spanToRange (ctx , fs , spn )
68
76
if err != nil {
69
77
return nil , err
70
78
}
@@ -136,7 +144,7 @@ func typeErrorDiagnostics(snapshot *snapshot, pkg *syntaxPackage, e extendedErro
136
144
}
137
145
138
146
if match := importErrorRe .FindStringSubmatch (e .primary .Msg ); match != nil {
139
- diag .SuggestedFixes , err = goGetQuickFixes (snapshot , loc .URI .SpanURI (), match [1 ])
147
+ diag .SuggestedFixes , err = goGetQuickFixes (snapshot . moduleMode () , loc .URI .SpanURI (), match [1 ])
140
148
if err != nil {
141
149
return nil , err
142
150
}
@@ -150,9 +158,8 @@ func typeErrorDiagnostics(snapshot *snapshot, pkg *syntaxPackage, e extendedErro
150
158
return []* source.Diagnostic {diag }, nil
151
159
}
152
160
153
- func goGetQuickFixes (snapshot * snapshot , uri span.URI , pkg string ) ([]source.SuggestedFix , error ) {
154
- // Go get only supports module mode for now.
155
- if snapshot .workspaceMode ()& moduleMode == 0 {
161
+ func goGetQuickFixes (moduleMode bool , uri span.URI , pkg string ) ([]source.SuggestedFix , error ) {
162
+ if ! moduleMode {
156
163
return nil , nil
157
164
}
158
165
title := fmt .Sprintf ("go get package %v" , pkg )
@@ -312,14 +319,20 @@ func typeErrorData(pkg *syntaxPackage, terr types.Error) (typesinternal.ErrorCod
312
319
return ecode , loc , err
313
320
}
314
321
315
- // spanToRange converts a span.Span to a protocol.Range,
316
- // assuming that the span belongs to the package whose diagnostics are being computed.
317
- func spanToRange (pkg * syntaxPackage , spn span.Span ) (protocol.Range , error ) {
318
- pgf , err := pkg .File (spn .URI ())
322
+ // spanToRange converts a span.Span to a protocol.Range, by mapping content
323
+ // contained in the provided FileSource.
324
+ func spanToRange (ctx context.Context , fs source.FileSource , spn span.Span ) (protocol.Range , error ) {
325
+ uri := spn .URI ()
326
+ fh , err := fs .GetFile (ctx , uri )
327
+ if err != nil {
328
+ return protocol.Range {}, err
329
+ }
330
+ content , err := fh .Read ()
319
331
if err != nil {
320
332
return protocol.Range {}, err
321
333
}
322
- return pgf .Mapper .SpanRange (spn )
334
+ mapper := protocol .NewMapper (uri , content )
335
+ return mapper .SpanRange (spn )
323
336
}
324
337
325
338
// parseGoListError attempts to parse a standard `go list` error message
@@ -338,28 +351,36 @@ func parseGoListError(input, wd string) span.Span {
338
351
return span .ParseInDir (input [:msgIndex ], wd )
339
352
}
340
353
341
- func parseGoListImportCycleError (e packages.Error , pkg * syntaxPackage ) (* source.Diagnostic , bool ) {
354
+ // parseGoListImportCycleError attempts to parse the given go/packages error as
355
+ // an import cycle, returning a diagnostic if successful.
356
+ //
357
+ // If the error is not detected as an import cycle error, it returns nil, nil.
358
+ func parseGoListImportCycleError (ctx context.Context , e packages.Error , m * source.Metadata , fs source.FileSource ) (* source.Diagnostic , error ) {
342
359
re := regexp .MustCompile (`(.*): import stack: \[(.+)\]` )
343
360
matches := re .FindStringSubmatch (strings .TrimSpace (e .Msg ))
344
361
if len (matches ) < 3 {
345
- return nil , false
362
+ return nil , nil
346
363
}
347
364
msg := matches [1 ]
348
365
importList := strings .Split (matches [2 ], " " )
349
366
// Since the error is relative to the current package. The import that is causing
350
367
// the import cycle error is the second one in the list.
351
368
if len (importList ) < 2 {
352
- return nil , false
369
+ return nil , nil
353
370
}
354
371
// Imports have quotation marks around them.
355
372
circImp := strconv .Quote (importList [1 ])
356
- for _ , pgf := range pkg .compiledGoFiles {
373
+ for _ , uri := range m .CompiledGoFiles {
374
+ pgf , err := parseGoURI (ctx , fs , uri , source .ParseHeader )
375
+ if err != nil {
376
+ return nil , err
377
+ }
357
378
// Search file imports for the import that is causing the import cycle.
358
379
for _ , imp := range pgf .File .Imports {
359
380
if imp .Path .Value == circImp {
360
381
rng , err := pgf .NodeMappedRange (imp )
361
382
if err != nil {
362
- return nil , false
383
+ return nil , nil
363
384
}
364
385
365
386
return & source.Diagnostic {
@@ -368,9 +389,35 @@ func parseGoListImportCycleError(e packages.Error, pkg *syntaxPackage) (*source.
368
389
Severity : protocol .SeverityError ,
369
390
Source : source .ListError ,
370
391
Message : msg ,
371
- }, true
392
+ }, nil
372
393
}
373
394
}
374
395
}
375
- return nil , false
396
+ return nil , nil
397
+ }
398
+
399
+ // parseGoURI is a helper to parse the Go file at the given URI from the file
400
+ // source fs. The resulting syntax and token.File belong to an ephemeral,
401
+ // encapsulated FileSet, so this file stands only on its own: it's not suitable
402
+ // to use in a list of file of a package, for example.
403
+ //
404
+ // It returns an error if the file could not be read.
405
+ func parseGoURI (ctx context.Context , fs source.FileSource , uri span.URI , mode source.ParseMode ) (* source.ParsedGoFile , error ) {
406
+ fh , err := fs .GetFile (ctx , uri )
407
+ if err != nil {
408
+ return nil , err
409
+ }
410
+ return parseGoImpl (ctx , token .NewFileSet (), fh , source .ParseHeader )
411
+ }
412
+
413
+ // parseModURI is a helper to parse the Mod file at the given URI from the file
414
+ // source fs.
415
+ //
416
+ // It returns an error if the file could not be read.
417
+ func parseModURI (ctx context.Context , fs source.FileSource , uri span.URI ) (* source.ParsedModule , error ) {
418
+ fh , err := fs .GetFile (ctx , uri )
419
+ if err != nil {
420
+ return nil , err
421
+ }
422
+ return parseModImpl (ctx , fh )
376
423
}
0 commit comments