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