@@ -26,6 +26,7 @@ import (
26
26
"golang.org/x/tools/go/internal/packagesdriver"
27
27
"golang.org/x/tools/internal/gopathwalk"
28
28
"golang.org/x/tools/internal/semver"
29
+ "golang.org/x/tools/internal/span"
29
30
)
30
31
31
32
// debug controls verbose logging.
@@ -281,31 +282,42 @@ func runContainsQueries(cfg *Config, driver driver, response *responseDeduper, q
281
282
return fmt .Errorf ("could not determine absolute path of file= query path %q: %v" , query , err )
282
283
}
283
284
dirResponse , err := driver (cfg , pattern )
284
- if err != nil || (len (dirResponse .Packages ) == 1 && len (dirResponse .Packages [0 ].Errors ) == 1 ) {
285
- // There was an error loading the package. Try to load the file as an ad-hoc package.
286
- // Usually the error will appear in a returned package, but may not if we're in modules mode
287
- // and the ad-hoc is located outside a module.
285
+ if err != nil {
288
286
var queryErr error
289
- dirResponse , queryErr = driver (cfg , query )
290
- if queryErr != nil {
291
- // Return the original error if the attempt to fall back failed.
292
- return err
287
+ if dirResponse , queryErr = adHocPackage (cfg , driver , pattern , query ); queryErr != nil {
288
+ return err // return the original error
293
289
}
294
- // Special case to handle issue #33482:
295
- // If this is a file= query for ad-hoc packages where the file only exists on an overlay,
296
- // and exists outside of a module, add the file in for the package.
297
- if len (dirResponse .Packages ) == 1 && (dirResponse .Packages [0 ].ID == "command-line-arguments" || dirResponse .Packages [0 ].PkgPath == filepath .ToSlash (query )) {
298
- if len (dirResponse .Packages [0 ].GoFiles ) == 0 {
299
- filename := filepath .Join (pattern , filepath .Base (query )) // avoid recomputing abspath
300
- // TODO(matloob): check if the file is outside of a root dir?
301
- for path := range cfg .Overlay {
302
- if path == filename {
303
- dirResponse .Packages [0 ].Errors = nil
304
- dirResponse .Packages [0 ].GoFiles = []string {path }
305
- dirResponse .Packages [0 ].CompiledGoFiles = []string {path }
306
- }
307
- }
290
+ }
291
+ // `go list` can report errors for files that are not listed as part of a package's GoFiles.
292
+ // In the case of an invalid Go file, we should assume that it is part of package if only
293
+ // one package is in the response. The file may have valid contents in an overlay.
294
+ if len (dirResponse .Packages ) == 1 {
295
+ pkg := dirResponse .Packages [0 ]
296
+ for i , err := range pkg .Errors {
297
+ s := errorSpan (err )
298
+ if ! s .IsValid () {
299
+ break
300
+ }
301
+ if len (pkg .CompiledGoFiles ) == 0 {
302
+ break
303
+ }
304
+ dir := filepath .Dir (pkg .CompiledGoFiles [0 ])
305
+ filename := filepath .Join (dir , filepath .Base (s .URI ().Filename ()))
306
+ if info , err := os .Stat (filename ); err != nil || info .IsDir () {
307
+ break
308
308
}
309
+ if ! contains (pkg .CompiledGoFiles , filename ) {
310
+ pkg .CompiledGoFiles = append (pkg .CompiledGoFiles , filename )
311
+ pkg .GoFiles = append (pkg .GoFiles , filename )
312
+ pkg .Errors = append (pkg .Errors [:i ], pkg .Errors [i + 1 :]... )
313
+ }
314
+ }
315
+ }
316
+ // A final attempt to construct an ad-hoc package.
317
+ if len (dirResponse .Packages ) == 1 && len (dirResponse .Packages [0 ].Errors ) == 1 {
318
+ var queryErr error
319
+ if dirResponse , queryErr = adHocPackage (cfg , driver , pattern , query ); queryErr != nil {
320
+ return err // return the original error
309
321
}
310
322
}
311
323
isRoot := make (map [string ]bool , len (dirResponse .Roots ))
@@ -333,6 +345,63 @@ func runContainsQueries(cfg *Config, driver driver, response *responseDeduper, q
333
345
return nil
334
346
}
335
347
348
+ // adHocPackage attempts to construct an ad-hoc package given a query that failed.
349
+ func adHocPackage (cfg * Config , driver driver , pattern , query string ) (* driverResponse , error ) {
350
+ // There was an error loading the package. Try to load the file as an ad-hoc package.
351
+ // Usually the error will appear in a returned package, but may not if we're in modules mode
352
+ // and the ad-hoc is located outside a module.
353
+ dirResponse , err := driver (cfg , query )
354
+ if err != nil {
355
+ return nil , err
356
+ }
357
+ // Special case to handle issue #33482:
358
+ // If this is a file= query for ad-hoc packages where the file only exists on an overlay,
359
+ // and exists outside of a module, add the file in for the package.
360
+ if len (dirResponse .Packages ) == 1 && (dirResponse .Packages [0 ].ID == "command-line-arguments" || dirResponse .Packages [0 ].PkgPath == filepath .ToSlash (query )) {
361
+ if len (dirResponse .Packages [0 ].GoFiles ) == 0 {
362
+ filename := filepath .Join (pattern , filepath .Base (query )) // avoid recomputing abspath
363
+ // TODO(matloob): check if the file is outside of a root dir?
364
+ for path := range cfg .Overlay {
365
+ if path == filename {
366
+ dirResponse .Packages [0 ].Errors = nil
367
+ dirResponse .Packages [0 ].GoFiles = []string {path }
368
+ dirResponse .Packages [0 ].CompiledGoFiles = []string {path }
369
+ }
370
+ }
371
+ }
372
+ }
373
+ return dirResponse , nil
374
+ }
375
+
376
+ func contains (files []string , filename string ) bool {
377
+ for _ , f := range files {
378
+ if f == filename {
379
+ return true
380
+ }
381
+ }
382
+ return false
383
+ }
384
+
385
+ // errorSpan attempts to parse a standard `go list` error message
386
+ // by stripping off the trailing error message.
387
+ //
388
+ // It works only on errors whose message is prefixed by colon,
389
+ // followed by a space (": "). For example:
390
+ //
391
+ // attributes.go:13:1: expected 'package', found 'type'
392
+ //
393
+ func errorSpan (err Error ) span.Span {
394
+ if err .Pos == "" {
395
+ input := strings .TrimSpace (err .Msg )
396
+ msgIndex := strings .Index (input , ": " )
397
+ if msgIndex < 0 {
398
+ return span .Parse (input )
399
+ }
400
+ return span .Parse (input [:msgIndex ])
401
+ }
402
+ return span .Parse (err .Pos )
403
+ }
404
+
336
405
// modCacheRegexp splits a path in a module cache into module, module version, and package.
337
406
var modCacheRegexp = regexp .MustCompile (`(.*)@([^/\\]*)(.*)` )
338
407
0 commit comments