Giant blob of minor changes
[dotfiles/.git] / .config / coc / extensions / coc-go-data / tools / pkg / mod / golang.org / x / tools@v0.0.0-20201028153306-37f0764111ff / go / packages / golist_overlay.go
1 // Copyright 2018 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
4
5 package packages
6
7 import (
8         "encoding/json"
9         "fmt"
10         "go/parser"
11         "go/token"
12         "log"
13         "os"
14         "path/filepath"
15         "regexp"
16         "sort"
17         "strconv"
18         "strings"
19
20         "golang.org/x/tools/internal/gocommand"
21 )
22
23 // processGolistOverlay provides rudimentary support for adding
24 // files that don't exist on disk to an overlay. The results can be
25 // sometimes incorrect.
26 // TODO(matloob): Handle unsupported cases, including the following:
27 // - determining the correct package to add given a new import path
28 func (state *golistState) processGolistOverlay(response *responseDeduper) (modifiedPkgs, needPkgs []string, err error) {
29         havePkgs := make(map[string]string) // importPath -> non-test package ID
30         needPkgsSet := make(map[string]bool)
31         modifiedPkgsSet := make(map[string]bool)
32
33         pkgOfDir := make(map[string][]*Package)
34         for _, pkg := range response.dr.Packages {
35                 // This is an approximation of import path to id. This can be
36                 // wrong for tests, vendored packages, and a number of other cases.
37                 havePkgs[pkg.PkgPath] = pkg.ID
38                 x := commonDir(pkg.GoFiles)
39                 if x != "" {
40                         pkgOfDir[x] = append(pkgOfDir[x], pkg)
41                 }
42         }
43
44         // If no new imports are added, it is safe to avoid loading any needPkgs.
45         // Otherwise, it's hard to tell which package is actually being loaded
46         // (due to vendoring) and whether any modified package will show up
47         // in the transitive set of dependencies (because new imports are added,
48         // potentially modifying the transitive set of dependencies).
49         var overlayAddsImports bool
50
51         // If both a package and its test package are created by the overlay, we
52         // need the real package first. Process all non-test files before test
53         // files, and make the whole process deterministic while we're at it.
54         var overlayFiles []string
55         for opath := range state.cfg.Overlay {
56                 overlayFiles = append(overlayFiles, opath)
57         }
58         sort.Slice(overlayFiles, func(i, j int) bool {
59                 iTest := strings.HasSuffix(overlayFiles[i], "_test.go")
60                 jTest := strings.HasSuffix(overlayFiles[j], "_test.go")
61                 if iTest != jTest {
62                         return !iTest // non-tests are before tests.
63                 }
64                 return overlayFiles[i] < overlayFiles[j]
65         })
66         for _, opath := range overlayFiles {
67                 contents := state.cfg.Overlay[opath]
68                 base := filepath.Base(opath)
69                 dir := filepath.Dir(opath)
70                 var pkg *Package           // if opath belongs to both a package and its test variant, this will be the test variant
71                 var testVariantOf *Package // if opath is a test file, this is the package it is testing
72                 var fileExists bool
73                 isTestFile := strings.HasSuffix(opath, "_test.go")
74                 pkgName, ok := extractPackageName(opath, contents)
75                 if !ok {
76                         // Don't bother adding a file that doesn't even have a parsable package statement
77                         // to the overlay.
78                         continue
79                 }
80                 // If all the overlay files belong to a different package, change the
81                 // package name to that package.
82                 maybeFixPackageName(pkgName, isTestFile, pkgOfDir[dir])
83         nextPackage:
84                 for _, p := range response.dr.Packages {
85                         if pkgName != p.Name && p.ID != "command-line-arguments" {
86                                 continue
87                         }
88                         for _, f := range p.GoFiles {
89                                 if !sameFile(filepath.Dir(f), dir) {
90                                         continue
91                                 }
92                                 // Make sure to capture information on the package's test variant, if needed.
93                                 if isTestFile && !hasTestFiles(p) {
94                                         // TODO(matloob): Are there packages other than the 'production' variant
95                                         // of a package that this can match? This shouldn't match the test main package
96                                         // because the file is generated in another directory.
97                                         testVariantOf = p
98                                         continue nextPackage
99                                 } else if !isTestFile && hasTestFiles(p) {
100                                         // We're examining a test variant, but the overlaid file is
101                                         // a non-test file. Because the overlay implementation
102                                         // (currently) only adds a file to one package, skip this
103                                         // package, so that we can add the file to the production
104                                         // variant of the package. (https://golang.org/issue/36857
105                                         // tracks handling overlays on both the production and test
106                                         // variant of a package).
107                                         continue nextPackage
108                                 }
109                                 if pkg != nil && p != pkg && pkg.PkgPath == p.PkgPath {
110                                         // We have already seen the production version of the
111                                         // for which p is a test variant.
112                                         if hasTestFiles(p) {
113                                                 testVariantOf = pkg
114                                         }
115                                 }
116                                 pkg = p
117                                 if filepath.Base(f) == base {
118                                         fileExists = true
119                                 }
120                         }
121                 }
122                 // The overlay could have included an entirely new package or an
123                 // ad-hoc package. An ad-hoc package is one that we have manually
124                 // constructed from inadequate `go list` results for a file= query.
125                 // It will have the ID command-line-arguments.
126                 if pkg == nil || pkg.ID == "command-line-arguments" {
127                         // Try to find the module or gopath dir the file is contained in.
128                         // Then for modules, add the module opath to the beginning.
129                         pkgPath, ok, err := state.getPkgPath(dir)
130                         if err != nil {
131                                 return nil, nil, err
132                         }
133                         if !ok {
134                                 break
135                         }
136                         var forTest string // only set for x tests
137                         isXTest := strings.HasSuffix(pkgName, "_test")
138                         if isXTest {
139                                 forTest = pkgPath
140                                 pkgPath += "_test"
141                         }
142                         id := pkgPath
143                         if isTestFile {
144                                 if isXTest {
145                                         id = fmt.Sprintf("%s [%s.test]", pkgPath, forTest)
146                                 } else {
147                                         id = fmt.Sprintf("%s [%s.test]", pkgPath, pkgPath)
148                                 }
149                         }
150                         if pkg != nil {
151                                 // TODO(rstambler): We should change the package's path and ID
152                                 // here. The only issue is that this messes with the roots.
153                         } else {
154                                 // Try to reclaim a package with the same ID, if it exists in the response.
155                                 for _, p := range response.dr.Packages {
156                                         if reclaimPackage(p, id, opath, contents) {
157                                                 pkg = p
158                                                 break
159                                         }
160                                 }
161                                 // Otherwise, create a new package.
162                                 if pkg == nil {
163                                         pkg = &Package{
164                                                 PkgPath: pkgPath,
165                                                 ID:      id,
166                                                 Name:    pkgName,
167                                                 Imports: make(map[string]*Package),
168                                         }
169                                         response.addPackage(pkg)
170                                         havePkgs[pkg.PkgPath] = id
171                                         // Add the production package's sources for a test variant.
172                                         if isTestFile && !isXTest && testVariantOf != nil {
173                                                 pkg.GoFiles = append(pkg.GoFiles, testVariantOf.GoFiles...)
174                                                 pkg.CompiledGoFiles = append(pkg.CompiledGoFiles, testVariantOf.CompiledGoFiles...)
175                                                 // Add the package under test and its imports to the test variant.
176                                                 pkg.forTest = testVariantOf.PkgPath
177                                                 for k, v := range testVariantOf.Imports {
178                                                         pkg.Imports[k] = &Package{ID: v.ID}
179                                                 }
180                                         }
181                                         if isXTest {
182                                                 pkg.forTest = forTest
183                                         }
184                                 }
185                         }
186                 }
187                 if !fileExists {
188                         pkg.GoFiles = append(pkg.GoFiles, opath)
189                         // TODO(matloob): Adding the file to CompiledGoFiles can exhibit the wrong behavior
190                         // if the file will be ignored due to its build tags.
191                         pkg.CompiledGoFiles = append(pkg.CompiledGoFiles, opath)
192                         modifiedPkgsSet[pkg.ID] = true
193                 }
194                 imports, err := extractImports(opath, contents)
195                 if err != nil {
196                         // Let the parser or type checker report errors later.
197                         continue
198                 }
199                 for _, imp := range imports {
200                         // TODO(rstambler): If the package is an x test and the import has
201                         // a test variant, make sure to replace it.
202                         if _, found := pkg.Imports[imp]; found {
203                                 continue
204                         }
205                         overlayAddsImports = true
206                         id, ok := havePkgs[imp]
207                         if !ok {
208                                 var err error
209                                 id, err = state.resolveImport(dir, imp)
210                                 if err != nil {
211                                         return nil, nil, err
212                                 }
213                         }
214                         pkg.Imports[imp] = &Package{ID: id}
215                         // Add dependencies to the non-test variant version of this package as well.
216                         if testVariantOf != nil {
217                                 testVariantOf.Imports[imp] = &Package{ID: id}
218                         }
219                 }
220         }
221
222         // toPkgPath guesses the package path given the id.
223         toPkgPath := func(sourceDir, id string) (string, error) {
224                 if i := strings.IndexByte(id, ' '); i >= 0 {
225                         return state.resolveImport(sourceDir, id[:i])
226                 }
227                 return state.resolveImport(sourceDir, id)
228         }
229
230         // Now that new packages have been created, do another pass to determine
231         // the new set of missing packages.
232         for _, pkg := range response.dr.Packages {
233                 for _, imp := range pkg.Imports {
234                         if len(pkg.GoFiles) == 0 {
235                                 return nil, nil, fmt.Errorf("cannot resolve imports for package %q with no Go files", pkg.PkgPath)
236                         }
237                         pkgPath, err := toPkgPath(filepath.Dir(pkg.GoFiles[0]), imp.ID)
238                         if err != nil {
239                                 return nil, nil, err
240                         }
241                         if _, ok := havePkgs[pkgPath]; !ok {
242                                 needPkgsSet[pkgPath] = true
243                         }
244                 }
245         }
246
247         if overlayAddsImports {
248                 needPkgs = make([]string, 0, len(needPkgsSet))
249                 for pkg := range needPkgsSet {
250                         needPkgs = append(needPkgs, pkg)
251                 }
252         }
253         modifiedPkgs = make([]string, 0, len(modifiedPkgsSet))
254         for pkg := range modifiedPkgsSet {
255                 modifiedPkgs = append(modifiedPkgs, pkg)
256         }
257         return modifiedPkgs, needPkgs, err
258 }
259
260 // resolveImport finds the ID of a package given its import path.
261 // In particular, it will find the right vendored copy when in GOPATH mode.
262 func (state *golistState) resolveImport(sourceDir, importPath string) (string, error) {
263         env, err := state.getEnv()
264         if err != nil {
265                 return "", err
266         }
267         if env["GOMOD"] != "" {
268                 return importPath, nil
269         }
270
271         searchDir := sourceDir
272         for {
273                 vendorDir := filepath.Join(searchDir, "vendor")
274                 exists, ok := state.vendorDirs[vendorDir]
275                 if !ok {
276                         info, err := os.Stat(vendorDir)
277                         exists = err == nil && info.IsDir()
278                         state.vendorDirs[vendorDir] = exists
279                 }
280
281                 if exists {
282                         vendoredPath := filepath.Join(vendorDir, importPath)
283                         if info, err := os.Stat(vendoredPath); err == nil && info.IsDir() {
284                                 // We should probably check for .go files here, but shame on anyone who fools us.
285                                 path, ok, err := state.getPkgPath(vendoredPath)
286                                 if err != nil {
287                                         return "", err
288                                 }
289                                 if ok {
290                                         return path, nil
291                                 }
292                         }
293                 }
294
295                 // We know we've hit the top of the filesystem when we Dir / and get /,
296                 // or C:\ and get C:\, etc.
297                 next := filepath.Dir(searchDir)
298                 if next == searchDir {
299                         break
300                 }
301                 searchDir = next
302         }
303         return importPath, nil
304 }
305
306 func hasTestFiles(p *Package) bool {
307         for _, f := range p.GoFiles {
308                 if strings.HasSuffix(f, "_test.go") {
309                         return true
310                 }
311         }
312         return false
313 }
314
315 // determineRootDirs returns a mapping from absolute directories that could
316 // contain code to their corresponding import path prefixes.
317 func (state *golistState) determineRootDirs() (map[string]string, error) {
318         env, err := state.getEnv()
319         if err != nil {
320                 return nil, err
321         }
322         if env["GOMOD"] != "" {
323                 state.rootsOnce.Do(func() {
324                         state.rootDirs, state.rootDirsError = state.determineRootDirsModules()
325                 })
326         } else {
327                 state.rootsOnce.Do(func() {
328                         state.rootDirs, state.rootDirsError = state.determineRootDirsGOPATH()
329                 })
330         }
331         return state.rootDirs, state.rootDirsError
332 }
333
334 func (state *golistState) determineRootDirsModules() (map[string]string, error) {
335         // List all of the modules--the first will be the directory for the main
336         // module. Any replaced modules will also need to be treated as roots.
337         // Editing files in the module cache isn't a great idea, so we don't
338         // plan to ever support that.
339         out, err := state.invokeGo("list", "-m", "-json", "all")
340         if err != nil {
341                 // 'go list all' will fail if we're outside of a module and
342                 // GO111MODULE=on. Try falling back without 'all'.
343                 var innerErr error
344                 out, innerErr = state.invokeGo("list", "-m", "-json")
345                 if innerErr != nil {
346                         return nil, err
347                 }
348         }
349         roots := map[string]string{}
350         modules := map[string]string{}
351         var i int
352         for dec := json.NewDecoder(out); dec.More(); {
353                 mod := new(gocommand.ModuleJSON)
354                 if err := dec.Decode(mod); err != nil {
355                         return nil, err
356                 }
357                 if mod.Dir != "" && mod.Path != "" {
358                         // This is a valid module; add it to the map.
359                         absDir, err := filepath.Abs(mod.Dir)
360                         if err != nil {
361                                 return nil, err
362                         }
363                         modules[absDir] = mod.Path
364                         // The first result is the main module.
365                         if i == 0 || mod.Replace != nil && mod.Replace.Path != "" {
366                                 roots[absDir] = mod.Path
367                         }
368                 }
369                 i++
370         }
371         return roots, nil
372 }
373
374 func (state *golistState) determineRootDirsGOPATH() (map[string]string, error) {
375         m := map[string]string{}
376         for _, dir := range filepath.SplitList(state.mustGetEnv()["GOPATH"]) {
377                 absDir, err := filepath.Abs(dir)
378                 if err != nil {
379                         return nil, err
380                 }
381                 m[filepath.Join(absDir, "src")] = ""
382         }
383         return m, nil
384 }
385
386 func extractImports(filename string, contents []byte) ([]string, error) {
387         f, err := parser.ParseFile(token.NewFileSet(), filename, contents, parser.ImportsOnly) // TODO(matloob): reuse fileset?
388         if err != nil {
389                 return nil, err
390         }
391         var res []string
392         for _, imp := range f.Imports {
393                 quotedPath := imp.Path.Value
394                 path, err := strconv.Unquote(quotedPath)
395                 if err != nil {
396                         return nil, err
397                 }
398                 res = append(res, path)
399         }
400         return res, nil
401 }
402
403 // reclaimPackage attempts to reuse a package that failed to load in an overlay.
404 //
405 // If the package has errors and has no Name, GoFiles, or Imports,
406 // then it's possible that it doesn't yet exist on disk.
407 func reclaimPackage(pkg *Package, id string, filename string, contents []byte) bool {
408         // TODO(rstambler): Check the message of the actual error?
409         // It differs between $GOPATH and module mode.
410         if pkg.ID != id {
411                 return false
412         }
413         if len(pkg.Errors) != 1 {
414                 return false
415         }
416         if pkg.Name != "" || pkg.ExportFile != "" {
417                 return false
418         }
419         if len(pkg.GoFiles) > 0 || len(pkg.CompiledGoFiles) > 0 || len(pkg.OtherFiles) > 0 {
420                 return false
421         }
422         if len(pkg.Imports) > 0 {
423                 return false
424         }
425         pkgName, ok := extractPackageName(filename, contents)
426         if !ok {
427                 return false
428         }
429         pkg.Name = pkgName
430         pkg.Errors = nil
431         return true
432 }
433
434 func extractPackageName(filename string, contents []byte) (string, bool) {
435         // TODO(rstambler): Check the message of the actual error?
436         // It differs between $GOPATH and module mode.
437         f, err := parser.ParseFile(token.NewFileSet(), filename, contents, parser.PackageClauseOnly) // TODO(matloob): reuse fileset?
438         if err != nil {
439                 return "", false
440         }
441         return f.Name.Name, true
442 }
443
444 func commonDir(a []string) string {
445         seen := make(map[string]bool)
446         x := append([]string{}, a...)
447         for _, f := range x {
448                 seen[filepath.Dir(f)] = true
449         }
450         if len(seen) > 1 {
451                 log.Fatalf("commonDir saw %v for %v", seen, x)
452         }
453         for k := range seen {
454                 // len(seen) == 1
455                 return k
456         }
457         return "" // no files
458 }
459
460 // It is possible that the files in the disk directory dir have a different package
461 // name from newName, which is deduced from the overlays. If they all have a different
462 // package name, and they all have the same package name, then that name becomes
463 // the package name.
464 // It returns true if it changes the package name, false otherwise.
465 func maybeFixPackageName(newName string, isTestFile bool, pkgsOfDir []*Package) {
466         names := make(map[string]int)
467         for _, p := range pkgsOfDir {
468                 names[p.Name]++
469         }
470         if len(names) != 1 {
471                 // some files are in different packages
472                 return
473         }
474         var oldName string
475         for k := range names {
476                 oldName = k
477         }
478         if newName == oldName {
479                 return
480         }
481         // We might have a case where all of the package names in the directory are
482         // the same, but the overlay file is for an x test, which belongs to its
483         // own package. If the x test does not yet exist on disk, we may not yet
484         // have its package name on disk, but we should not rename the packages.
485         //
486         // We use a heuristic to determine if this file belongs to an x test:
487         // The test file should have a package name whose package name has a _test
488         // suffix or looks like "newName_test".
489         maybeXTest := strings.HasPrefix(oldName+"_test", newName) || strings.HasSuffix(newName, "_test")
490         if isTestFile && maybeXTest {
491                 return
492         }
493         for _, p := range pkgsOfDir {
494                 p.Name = newName
495         }
496 }
497
498 // This function is copy-pasted from
499 // https://github.com/golang/go/blob/9706f510a5e2754595d716bd64be8375997311fb/src/cmd/go/internal/search/search.go#L360.
500 // It should be deleted when we remove support for overlays from go/packages.
501 //
502 // NOTE: This does not handle any ./... or ./ style queries, as this function
503 // doesn't know the working directory.
504 //
505 // matchPattern(pattern)(name) reports whether
506 // name matches pattern. Pattern is a limited glob
507 // pattern in which '...' means 'any string' and there
508 // is no other special syntax.
509 // Unfortunately, there are two special cases. Quoting "go help packages":
510 //
511 // First, /... at the end of the pattern can match an empty string,
512 // so that net/... matches both net and packages in its subdirectories, like net/http.
513 // Second, any slash-separated pattern element containing a wildcard never
514 // participates in a match of the "vendor" element in the path of a vendored
515 // package, so that ./... does not match packages in subdirectories of
516 // ./vendor or ./mycode/vendor, but ./vendor/... and ./mycode/vendor/... do.
517 // Note, however, that a directory named vendor that itself contains code
518 // is not a vendored package: cmd/vendor would be a command named vendor,
519 // and the pattern cmd/... matches it.
520 func matchPattern(pattern string) func(name string) bool {
521         // Convert pattern to regular expression.
522         // The strategy for the trailing /... is to nest it in an explicit ? expression.
523         // The strategy for the vendor exclusion is to change the unmatchable
524         // vendor strings to a disallowed code point (vendorChar) and to use
525         // "(anything but that codepoint)*" as the implementation of the ... wildcard.
526         // This is a bit complicated but the obvious alternative,
527         // namely a hand-written search like in most shell glob matchers,
528         // is too easy to make accidentally exponential.
529         // Using package regexp guarantees linear-time matching.
530
531         const vendorChar = "\x00"
532
533         if strings.Contains(pattern, vendorChar) {
534                 return func(name string) bool { return false }
535         }
536
537         re := regexp.QuoteMeta(pattern)
538         re = replaceVendor(re, vendorChar)
539         switch {
540         case strings.HasSuffix(re, `/`+vendorChar+`/\.\.\.`):
541                 re = strings.TrimSuffix(re, `/`+vendorChar+`/\.\.\.`) + `(/vendor|/` + vendorChar + `/\.\.\.)`
542         case re == vendorChar+`/\.\.\.`:
543                 re = `(/vendor|/` + vendorChar + `/\.\.\.)`
544         case strings.HasSuffix(re, `/\.\.\.`):
545                 re = strings.TrimSuffix(re, `/\.\.\.`) + `(/\.\.\.)?`
546         }
547         re = strings.ReplaceAll(re, `\.\.\.`, `[^`+vendorChar+`]*`)
548
549         reg := regexp.MustCompile(`^` + re + `$`)
550
551         return func(name string) bool {
552                 if strings.Contains(name, vendorChar) {
553                         return false
554                 }
555                 return reg.MatchString(replaceVendor(name, vendorChar))
556         }
557 }
558
559 // replaceVendor returns the result of replacing
560 // non-trailing vendor path elements in x with repl.
561 func replaceVendor(x, repl string) string {
562         if !strings.Contains(x, "vendor") {
563                 return x
564         }
565         elem := strings.Split(x, "/")
566         for i := 0; i < len(elem)-1; i++ {
567                 if elem[i] == "vendor" {
568                         elem[i] = repl
569                 }
570         }
571         return strings.Join(elem, "/")
572 }