1
2
3
4
5 package imports
6
7 import (
8 "bytes"
9 "context"
10 "encoding/json"
11 "fmt"
12 "go/ast"
13 "go/build"
14 "go/parser"
15 "go/token"
16 "go/types"
17 "io/fs"
18 "io/ioutil"
19 "os"
20 "path"
21 "path/filepath"
22 "reflect"
23 "sort"
24 "strconv"
25 "strings"
26 "sync"
27 "unicode"
28 "unicode/utf8"
29
30 "golang.org/x/tools/go/ast/astutil"
31 "golang.org/x/tools/internal/event"
32 "golang.org/x/tools/internal/gocommand"
33 "golang.org/x/tools/internal/gopathwalk"
34 "golang.org/x/tools/internal/stdlib"
35 )
36
37
38
39 var importToGroup = []func(localPrefix, importPath string) (num int, ok bool){
40 func(localPrefix, importPath string) (num int, ok bool) {
41 if localPrefix == "" {
42 return
43 }
44 for _, p := range strings.Split(localPrefix, ",") {
45 if strings.HasPrefix(importPath, p) || strings.TrimSuffix(p, "/") == importPath {
46 return 3, true
47 }
48 }
49 return
50 },
51 func(_, importPath string) (num int, ok bool) {
52 if strings.HasPrefix(importPath, "appengine") {
53 return 2, true
54 }
55 return
56 },
57 func(_, importPath string) (num int, ok bool) {
58 firstComponent := strings.Split(importPath, "/")[0]
59 if strings.Contains(firstComponent, ".") {
60 return 1, true
61 }
62 return
63 },
64 }
65
66 func importGroup(localPrefix, importPath string) int {
67 for _, fn := range importToGroup {
68 if n, ok := fn(localPrefix, importPath); ok {
69 return n
70 }
71 }
72 return 0
73 }
74
75 type ImportFixType int
76
77 const (
78 AddImport ImportFixType = iota
79 DeleteImport
80 SetImportName
81 )
82
83 type ImportFix struct {
84
85 StmtInfo ImportInfo
86
87 IdentName string
88
89 FixType ImportFixType
90 Relevance float64
91 }
92
93
94 type ImportInfo struct {
95 ImportPath string
96 Name string
97 }
98
99
100 type packageInfo struct {
101 name string
102 exports map[string]bool
103 }
104
105
106
107 func parseOtherFiles(fset *token.FileSet, srcDir, filename string) []*ast.File {
108
109
110 considerTests := strings.HasSuffix(filename, "_test.go")
111
112 fileBase := filepath.Base(filename)
113 packageFileInfos, err := os.ReadDir(srcDir)
114 if err != nil {
115 return nil
116 }
117
118 var files []*ast.File
119 for _, fi := range packageFileInfos {
120 if fi.Name() == fileBase || !strings.HasSuffix(fi.Name(), ".go") {
121 continue
122 }
123 if !considerTests && strings.HasSuffix(fi.Name(), "_test.go") {
124 continue
125 }
126
127 f, err := parser.ParseFile(fset, filepath.Join(srcDir, fi.Name()), nil, 0)
128 if err != nil {
129 continue
130 }
131
132 files = append(files, f)
133 }
134
135 return files
136 }
137
138
139 func addGlobals(f *ast.File, globals map[string]bool) {
140 for _, decl := range f.Decls {
141 genDecl, ok := decl.(*ast.GenDecl)
142 if !ok {
143 continue
144 }
145
146 for _, spec := range genDecl.Specs {
147 valueSpec, ok := spec.(*ast.ValueSpec)
148 if !ok {
149 continue
150 }
151 globals[valueSpec.Names[0].Name] = true
152 }
153 }
154 }
155
156
157
158 func collectReferences(f *ast.File) references {
159 refs := references{}
160
161 var visitor visitFn
162 visitor = func(node ast.Node) ast.Visitor {
163 if node == nil {
164 return visitor
165 }
166 switch v := node.(type) {
167 case *ast.SelectorExpr:
168 xident, ok := v.X.(*ast.Ident)
169 if !ok {
170 break
171 }
172 if xident.Obj != nil {
173
174 break
175 }
176 if !ast.IsExported(v.Sel.Name) {
177
178 break
179 }
180 pkgName := xident.Name
181 r := refs[pkgName]
182 if r == nil {
183 r = make(map[string]bool)
184 refs[pkgName] = r
185 }
186 r[v.Sel.Name] = true
187 }
188 return visitor
189 }
190 ast.Walk(visitor, f)
191 return refs
192 }
193
194
195
196 func collectImports(f *ast.File) []*ImportInfo {
197 var imports []*ImportInfo
198 for _, imp := range f.Imports {
199 var name string
200 if imp.Name != nil {
201 name = imp.Name.Name
202 }
203 if imp.Path.Value == `"C"` || name == "_" || name == "." {
204 continue
205 }
206 path := strings.Trim(imp.Path.Value, `"`)
207 imports = append(imports, &ImportInfo{
208 Name: name,
209 ImportPath: path,
210 })
211 }
212 return imports
213 }
214
215
216
217 func (p *pass) findMissingImport(pkg string, syms map[string]bool) *ImportInfo {
218 for _, candidate := range p.candidates {
219 pkgInfo, ok := p.knownPackages[candidate.ImportPath]
220 if !ok {
221 continue
222 }
223 if p.importIdentifier(candidate) != pkg {
224 continue
225 }
226
227 allFound := true
228 for right := range syms {
229 if !pkgInfo.exports[right] {
230 allFound = false
231 break
232 }
233 }
234
235 if allFound {
236 return candidate
237 }
238 }
239 return nil
240 }
241
242
243
244
245 type references map[string]map[string]bool
246
247
248
249 type pass struct {
250
251 fset *token.FileSet
252 f *ast.File
253 srcDir string
254 env *ProcessEnv
255 loadRealPackageNames bool
256 otherFiles []*ast.File
257
258
259 existingImports map[string][]*ImportInfo
260 allRefs references
261 missingRefs references
262
263
264 lastTry bool
265 candidates []*ImportInfo
266 knownPackages map[string]*packageInfo
267 }
268
269
270 func (p *pass) loadPackageNames(imports []*ImportInfo) error {
271 if p.env.Logf != nil {
272 p.env.Logf("loading package names for %v packages", len(imports))
273 defer func() {
274 p.env.Logf("done loading package names for %v packages", len(imports))
275 }()
276 }
277 var unknown []string
278 for _, imp := range imports {
279 if _, ok := p.knownPackages[imp.ImportPath]; ok {
280 continue
281 }
282 unknown = append(unknown, imp.ImportPath)
283 }
284
285 resolver, err := p.env.GetResolver()
286 if err != nil {
287 return err
288 }
289
290 names, err := resolver.loadPackageNames(unknown, p.srcDir)
291 if err != nil {
292 return err
293 }
294
295 for path, name := range names {
296 p.knownPackages[path] = &packageInfo{
297 name: name,
298 exports: map[string]bool{},
299 }
300 }
301 return nil
302 }
303
304
305 func withoutVersion(nm string) string {
306 if v := path.Base(nm); len(v) > 0 && v[0] == 'v' {
307 if _, err := strconv.Atoi(v[1:]); err == nil {
308
309 if len(v) < len(nm) {
310 xnm := nm[:len(nm)-len(v)-1]
311 return path.Base(xnm)
312 }
313 }
314 }
315 return nm
316 }
317
318
319
320
321 func (p *pass) importIdentifier(imp *ImportInfo) string {
322 if imp.Name != "" {
323 return imp.Name
324 }
325 known := p.knownPackages[imp.ImportPath]
326 if known != nil && known.name != "" {
327 return withoutVersion(known.name)
328 }
329 return ImportPathToAssumedName(imp.ImportPath)
330 }
331
332
333
334
335 func (p *pass) load() ([]*ImportFix, bool) {
336 p.knownPackages = map[string]*packageInfo{}
337 p.missingRefs = references{}
338 p.existingImports = map[string][]*ImportInfo{}
339
340
341 p.allRefs = collectReferences(p.f)
342
343
344
345
346 globals := map[string]bool{}
347 for _, otherFile := range p.otherFiles {
348
349
350 if p.f.Name.Name == otherFile.Name.Name {
351 addGlobals(otherFile, globals)
352 }
353 p.candidates = append(p.candidates, collectImports(otherFile)...)
354 }
355
356
357
358 imports := collectImports(p.f)
359 if p.loadRealPackageNames {
360 err := p.loadPackageNames(append(imports, p.candidates...))
361 if err != nil {
362 if p.env.Logf != nil {
363 p.env.Logf("loading package names: %v", err)
364 }
365 return nil, false
366 }
367 }
368 for _, imp := range imports {
369 p.existingImports[p.importIdentifier(imp)] = append(p.existingImports[p.importIdentifier(imp)], imp)
370 }
371
372
373 for left, rights := range p.allRefs {
374 if globals[left] {
375 continue
376 }
377 _, ok := p.existingImports[left]
378 if !ok {
379 p.missingRefs[left] = rights
380 continue
381 }
382 }
383 if len(p.missingRefs) != 0 {
384 return nil, false
385 }
386
387 return p.fix()
388 }
389
390
391
392
393 func (p *pass) fix() ([]*ImportFix, bool) {
394
395 var selected []*ImportInfo
396 for left, rights := range p.missingRefs {
397 if imp := p.findMissingImport(left, rights); imp != nil {
398 selected = append(selected, imp)
399 }
400 }
401
402 if !p.lastTry && len(selected) != len(p.missingRefs) {
403 return nil, false
404 }
405
406
407 var fixes []*ImportFix
408 for _, identifierImports := range p.existingImports {
409 for _, imp := range identifierImports {
410
411
412
413
414
415 if _, ok := p.allRefs[p.importIdentifier(imp)]; !ok {
416 fixes = append(fixes, &ImportFix{
417 StmtInfo: *imp,
418 IdentName: p.importIdentifier(imp),
419 FixType: DeleteImport,
420 })
421 continue
422 }
423
424
425 if name := p.importSpecName(imp); name != imp.Name {
426 fixes = append(fixes, &ImportFix{
427 StmtInfo: ImportInfo{
428 Name: name,
429 ImportPath: imp.ImportPath,
430 },
431 IdentName: p.importIdentifier(imp),
432 FixType: SetImportName,
433 })
434 }
435 }
436 }
437
438
439 sortFixes(fixes)
440
441
442
443
444 var selectedFixes []*ImportFix
445 for _, imp := range selected {
446 selectedFixes = append(selectedFixes, &ImportFix{
447 StmtInfo: ImportInfo{
448 Name: p.importSpecName(imp),
449 ImportPath: imp.ImportPath,
450 },
451 IdentName: p.importIdentifier(imp),
452 FixType: AddImport,
453 })
454 }
455 sortFixes(selectedFixes)
456
457 return append(fixes, selectedFixes...), true
458 }
459
460 func sortFixes(fixes []*ImportFix) {
461 sort.Slice(fixes, func(i, j int) bool {
462 fi, fj := fixes[i], fixes[j]
463 if fi.StmtInfo.ImportPath != fj.StmtInfo.ImportPath {
464 return fi.StmtInfo.ImportPath < fj.StmtInfo.ImportPath
465 }
466 if fi.StmtInfo.Name != fj.StmtInfo.Name {
467 return fi.StmtInfo.Name < fj.StmtInfo.Name
468 }
469 if fi.IdentName != fj.IdentName {
470 return fi.IdentName < fj.IdentName
471 }
472 return fi.FixType < fj.FixType
473 })
474 }
475
476
477
478
479
480 func (p *pass) importSpecName(imp *ImportInfo) string {
481
482
483 if !p.loadRealPackageNames || imp.Name != "" {
484 return imp.Name
485 }
486
487 ident := p.importIdentifier(imp)
488 if ident == ImportPathToAssumedName(imp.ImportPath) {
489 return ""
490 }
491 return ident
492 }
493
494
495 func apply(fset *token.FileSet, f *ast.File, fixes []*ImportFix) {
496 for _, fix := range fixes {
497 switch fix.FixType {
498 case DeleteImport:
499 astutil.DeleteNamedImport(fset, f, fix.StmtInfo.Name, fix.StmtInfo.ImportPath)
500 case AddImport:
501 astutil.AddNamedImport(fset, f, fix.StmtInfo.Name, fix.StmtInfo.ImportPath)
502 case SetImportName:
503
504 for _, spec := range f.Imports {
505 path := strings.Trim(spec.Path.Value, `"`)
506 if path == fix.StmtInfo.ImportPath {
507 spec.Name = &ast.Ident{
508 Name: fix.StmtInfo.Name,
509 NamePos: spec.Pos(),
510 }
511 }
512 }
513 }
514 }
515 }
516
517
518
519 func (p *pass) assumeSiblingImportsValid() {
520 for _, f := range p.otherFiles {
521 refs := collectReferences(f)
522 imports := collectImports(f)
523 importsByName := map[string]*ImportInfo{}
524 for _, imp := range imports {
525 importsByName[p.importIdentifier(imp)] = imp
526 }
527 for left, rights := range refs {
528 if imp, ok := importsByName[left]; ok {
529 if m, ok := stdlib.PackageSymbols[imp.ImportPath]; ok {
530
531 rights = symbolNameSet(m)
532 }
533 p.addCandidate(imp, &packageInfo{
534
535 exports: rights,
536 })
537 }
538 }
539 }
540 }
541
542
543
544 func (p *pass) addCandidate(imp *ImportInfo, pkg *packageInfo) {
545 p.candidates = append(p.candidates, imp)
546 if existing, ok := p.knownPackages[imp.ImportPath]; ok {
547 if existing.name == "" {
548 existing.name = pkg.name
549 }
550 for export := range pkg.exports {
551 existing.exports[export] = true
552 }
553 } else {
554 p.knownPackages[imp.ImportPath] = pkg
555 }
556 }
557
558
559
560
561
562
563 var fixImports = fixImportsDefault
564
565 func fixImportsDefault(fset *token.FileSet, f *ast.File, filename string, env *ProcessEnv) error {
566 fixes, err := getFixes(context.Background(), fset, f, filename, env)
567 if err != nil {
568 return err
569 }
570 apply(fset, f, fixes)
571 return err
572 }
573
574
575
576 func getFixes(ctx context.Context, fset *token.FileSet, f *ast.File, filename string, env *ProcessEnv) ([]*ImportFix, error) {
577 abs, err := filepath.Abs(filename)
578 if err != nil {
579 return nil, err
580 }
581 srcDir := filepath.Dir(abs)
582 if env.Logf != nil {
583 env.Logf("fixImports(filename=%q), abs=%q, srcDir=%q ...", filename, abs, srcDir)
584 }
585
586
587
588
589
590 p := &pass{fset: fset, f: f, srcDir: srcDir, env: env}
591 if fixes, done := p.load(); done {
592 return fixes, nil
593 }
594
595 otherFiles := parseOtherFiles(fset, srcDir, filename)
596
597
598
599 p.otherFiles = otherFiles
600 if fixes, done := p.load(); done {
601 return fixes, nil
602 }
603
604
605 p.assumeSiblingImportsValid()
606 addStdlibCandidates(p, p.missingRefs)
607 if fixes, done := p.fix(); done {
608 return fixes, nil
609 }
610
611
612
613 p = &pass{fset: fset, f: f, srcDir: srcDir, env: env}
614 p.loadRealPackageNames = true
615 p.otherFiles = otherFiles
616 if fixes, done := p.load(); done {
617 return fixes, nil
618 }
619
620 if err := addStdlibCandidates(p, p.missingRefs); err != nil {
621 return nil, err
622 }
623 p.assumeSiblingImportsValid()
624 if fixes, done := p.fix(); done {
625 return fixes, nil
626 }
627
628
629
630 if err := addExternalCandidates(ctx, p, p.missingRefs, filename); err != nil {
631 return nil, err
632 }
633
634 p.lastTry = true
635 fixes, _ := p.fix()
636 return fixes, nil
637 }
638
639
640
641 const MaxRelevance = 7.0
642
643
644
645
646 func getCandidatePkgs(ctx context.Context, wrappedCallback *scanCallback, filename, filePkg string, env *ProcessEnv) error {
647 notSelf := func(p *pkg) bool {
648 return p.packageName != filePkg || p.dir != filepath.Dir(filename)
649 }
650 goenv, err := env.goEnv()
651 if err != nil {
652 return err
653 }
654
655 var mu sync.Mutex
656 dupCheck := map[string]struct{}{}
657
658
659 for importPath, symbols := range stdlib.PackageSymbols {
660 p := &pkg{
661 dir: filepath.Join(goenv["GOROOT"], "src", importPath),
662 importPathShort: importPath,
663 packageName: path.Base(importPath),
664 relevance: MaxRelevance,
665 }
666 dupCheck[importPath] = struct{}{}
667 if notSelf(p) && wrappedCallback.dirFound(p) && wrappedCallback.packageNameLoaded(p) {
668 var exports []stdlib.Symbol
669 for _, sym := range symbols {
670 switch sym.Kind {
671 case stdlib.Func, stdlib.Type, stdlib.Var, stdlib.Const:
672 exports = append(exports, sym)
673 }
674 }
675 wrappedCallback.exportsLoaded(p, exports)
676 }
677 }
678
679 scanFilter := &scanCallback{
680 rootFound: func(root gopathwalk.Root) bool {
681
682
683 return root.Type != gopathwalk.RootGOROOT && wrappedCallback.rootFound(root)
684 },
685 dirFound: wrappedCallback.dirFound,
686 packageNameLoaded: func(pkg *pkg) bool {
687 mu.Lock()
688 defer mu.Unlock()
689 if _, ok := dupCheck[pkg.importPathShort]; ok {
690 return false
691 }
692 dupCheck[pkg.importPathShort] = struct{}{}
693 return notSelf(pkg) && wrappedCallback.packageNameLoaded(pkg)
694 },
695 exportsLoaded: func(pkg *pkg, exports []stdlib.Symbol) {
696
697 if strings.HasSuffix(filePkg, "_test") && pkg.dir == filepath.Dir(filename) {
698 var err error
699 _, exports, err = loadExportsFromFiles(ctx, env, pkg.dir, true)
700 if err != nil {
701 return
702 }
703 }
704 wrappedCallback.exportsLoaded(pkg, exports)
705 },
706 }
707 resolver, err := env.GetResolver()
708 if err != nil {
709 return err
710 }
711 return resolver.scan(ctx, scanFilter)
712 }
713
714 func ScoreImportPaths(ctx context.Context, env *ProcessEnv, paths []string) (map[string]float64, error) {
715 result := make(map[string]float64)
716 resolver, err := env.GetResolver()
717 if err != nil {
718 return nil, err
719 }
720 for _, path := range paths {
721 result[path] = resolver.scoreImportPath(ctx, path)
722 }
723 return result, nil
724 }
725
726 func PrimeCache(ctx context.Context, resolver Resolver) error {
727
728 callback := &scanCallback{
729 rootFound: func(root gopathwalk.Root) bool {
730
731
732 return root.Type != gopathwalk.RootGOROOT
733 },
734 dirFound: func(pkg *pkg) bool {
735 return false
736 },
737
738 }
739
740 return resolver.scan(ctx, callback)
741 }
742
743 func candidateImportName(pkg *pkg) string {
744 if ImportPathToAssumedName(pkg.importPathShort) != pkg.packageName {
745 return pkg.packageName
746 }
747 return ""
748 }
749
750
751
752
753
754
755 func GetAllCandidates(ctx context.Context, wrapped func(ImportFix), searchPrefix, filename, filePkg string, env *ProcessEnv) error {
756 callback := &scanCallback{
757 rootFound: func(gopathwalk.Root) bool {
758 return true
759 },
760 dirFound: func(pkg *pkg) bool {
761 if !canUse(filename, pkg.dir) {
762 return false
763 }
764
765
766 return strings.HasPrefix(ImportPathToAssumedName(pkg.importPathShort), searchPrefix) ||
767 strings.HasPrefix(path.Base(pkg.importPathShort), searchPrefix)
768 },
769 packageNameLoaded: func(pkg *pkg) bool {
770 if !strings.HasPrefix(pkg.packageName, searchPrefix) {
771 return false
772 }
773 wrapped(ImportFix{
774 StmtInfo: ImportInfo{
775 ImportPath: pkg.importPathShort,
776 Name: candidateImportName(pkg),
777 },
778 IdentName: pkg.packageName,
779 FixType: AddImport,
780 Relevance: pkg.relevance,
781 })
782 return false
783 },
784 }
785 return getCandidatePkgs(ctx, callback, filename, filePkg, env)
786 }
787
788
789
790 func GetImportPaths(ctx context.Context, wrapped func(ImportFix), searchPrefix, filename, filePkg string, env *ProcessEnv) error {
791 callback := &scanCallback{
792 rootFound: func(gopathwalk.Root) bool {
793 return true
794 },
795 dirFound: func(pkg *pkg) bool {
796 if !canUse(filename, pkg.dir) {
797 return false
798 }
799 return strings.HasPrefix(pkg.importPathShort, searchPrefix)
800 },
801 packageNameLoaded: func(pkg *pkg) bool {
802 wrapped(ImportFix{
803 StmtInfo: ImportInfo{
804 ImportPath: pkg.importPathShort,
805 Name: candidateImportName(pkg),
806 },
807 IdentName: pkg.packageName,
808 FixType: AddImport,
809 Relevance: pkg.relevance,
810 })
811 return false
812 },
813 }
814 return getCandidatePkgs(ctx, callback, filename, filePkg, env)
815 }
816
817
818 type PackageExport struct {
819 Fix *ImportFix
820 Exports []stdlib.Symbol
821 }
822
823
824 func GetPackageExports(ctx context.Context, wrapped func(PackageExport), searchPkg, filename, filePkg string, env *ProcessEnv) error {
825 callback := &scanCallback{
826 rootFound: func(gopathwalk.Root) bool {
827 return true
828 },
829 dirFound: func(pkg *pkg) bool {
830 return pkgIsCandidate(filename, references{searchPkg: nil}, pkg)
831 },
832 packageNameLoaded: func(pkg *pkg) bool {
833 return pkg.packageName == searchPkg
834 },
835 exportsLoaded: func(pkg *pkg, exports []stdlib.Symbol) {
836 sortSymbols(exports)
837 wrapped(PackageExport{
838 Fix: &ImportFix{
839 StmtInfo: ImportInfo{
840 ImportPath: pkg.importPathShort,
841 Name: candidateImportName(pkg),
842 },
843 IdentName: pkg.packageName,
844 FixType: AddImport,
845 Relevance: pkg.relevance,
846 },
847 Exports: exports,
848 })
849 },
850 }
851 return getCandidatePkgs(ctx, callback, filename, filePkg, env)
852 }
853
854
855
856 var requiredGoEnvVars = []string{
857 "GO111MODULE",
858 "GOFLAGS",
859 "GOINSECURE",
860 "GOMOD",
861 "GOMODCACHE",
862 "GONOPROXY",
863 "GONOSUMDB",
864 "GOPATH",
865 "GOPROXY",
866 "GOROOT",
867 "GOSUMDB",
868 "GOWORK",
869 }
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888 type ProcessEnv struct {
889 GocmdRunner *gocommand.Runner
890
891 BuildFlags []string
892 ModFlag string
893
894
895
896
897 SkipPathInScan func(string) bool
898
899
900
901
902
903 Env map[string]string
904
905 WorkingDir string
906
907
908 Logf func(format string, args ...interface{})
909
910
911
912 ModCache *DirInfoCache
913
914 initialized bool
915
916
917
918
919 resolver Resolver
920 resolverErr error
921 }
922
923 func (e *ProcessEnv) goEnv() (map[string]string, error) {
924 if err := e.init(); err != nil {
925 return nil, err
926 }
927 return e.Env, nil
928 }
929
930 func (e *ProcessEnv) matchFile(dir, name string) (bool, error) {
931 bctx, err := e.buildContext()
932 if err != nil {
933 return false, err
934 }
935 return bctx.MatchFile(dir, name)
936 }
937
938
939 func (e *ProcessEnv) CopyConfig() *ProcessEnv {
940 copy := &ProcessEnv{
941 GocmdRunner: e.GocmdRunner,
942 initialized: e.initialized,
943 BuildFlags: e.BuildFlags,
944 Logf: e.Logf,
945 WorkingDir: e.WorkingDir,
946 resolver: nil,
947 Env: map[string]string{},
948 }
949 for k, v := range e.Env {
950 copy.Env[k] = v
951 }
952 return copy
953 }
954
955 func (e *ProcessEnv) init() error {
956 if e.initialized {
957 return nil
958 }
959
960 foundAllRequired := true
961 for _, k := range requiredGoEnvVars {
962 if _, ok := e.Env[k]; !ok {
963 foundAllRequired = false
964 break
965 }
966 }
967 if foundAllRequired {
968 e.initialized = true
969 return nil
970 }
971
972 if e.Env == nil {
973 e.Env = map[string]string{}
974 }
975
976 goEnv := map[string]string{}
977 stdout, err := e.invokeGo(context.TODO(), "env", append([]string{"-json"}, requiredGoEnvVars...)...)
978 if err != nil {
979 return err
980 }
981 if err := json.Unmarshal(stdout.Bytes(), &goEnv); err != nil {
982 return err
983 }
984 for k, v := range goEnv {
985 e.Env[k] = v
986 }
987 e.initialized = true
988 return nil
989 }
990
991 func (e *ProcessEnv) env() []string {
992 var env []string
993 for k, v := range e.Env {
994 env = append(env, k+"="+v)
995 }
996 return env
997 }
998
999 func (e *ProcessEnv) GetResolver() (Resolver, error) {
1000 if err := e.init(); err != nil {
1001 return nil, err
1002 }
1003
1004 if e.resolver == nil && e.resolverErr == nil {
1005
1006
1007
1008
1009
1010
1011 if len(e.Env["GOMOD"]) == 0 && len(e.Env["GOWORK"]) == 0 {
1012 e.resolver = newGopathResolver(e)
1013 } else if r, err := newModuleResolver(e, e.ModCache); err != nil {
1014 e.resolverErr = err
1015 } else {
1016 e.resolver = Resolver(r)
1017 }
1018 }
1019
1020 return e.resolver, e.resolverErr
1021 }
1022
1023
1024
1025
1026
1027 func (e *ProcessEnv) buildContext() (*build.Context, error) {
1028 ctx := build.Default
1029 goenv, err := e.goEnv()
1030 if err != nil {
1031 return nil, err
1032 }
1033 ctx.GOROOT = goenv["GOROOT"]
1034 ctx.GOPATH = goenv["GOPATH"]
1035
1036
1037
1038
1039 rc := reflect.ValueOf(&ctx).Elem()
1040 dir := rc.FieldByName("Dir")
1041 if dir.IsValid() && dir.Kind() == reflect.String {
1042 dir.SetString(e.WorkingDir)
1043 }
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054 ctx.ReadDir = ioutil.ReadDir
1055
1056 return &ctx, nil
1057 }
1058
1059 func (e *ProcessEnv) invokeGo(ctx context.Context, verb string, args ...string) (*bytes.Buffer, error) {
1060 inv := gocommand.Invocation{
1061 Verb: verb,
1062 Args: args,
1063 BuildFlags: e.BuildFlags,
1064 Env: e.env(),
1065 Logf: e.Logf,
1066 WorkingDir: e.WorkingDir,
1067 }
1068 return e.GocmdRunner.Run(ctx, inv)
1069 }
1070
1071 func addStdlibCandidates(pass *pass, refs references) error {
1072 goenv, err := pass.env.goEnv()
1073 if err != nil {
1074 return err
1075 }
1076 localbase := func(nm string) string {
1077 ans := path.Base(nm)
1078 if ans[0] == 'v' {
1079
1080 if _, err := strconv.Atoi(ans[1:]); err == nil {
1081 ix := strings.LastIndex(nm, ans)
1082 more := path.Base(nm[:ix])
1083 ans = path.Join(more, ans)
1084 }
1085 }
1086 return ans
1087 }
1088 add := func(pkg string) {
1089
1090 if path.Base(pkg) == pass.f.Name.Name && filepath.Join(goenv["GOROOT"], "src", pkg) == pass.srcDir {
1091 return
1092 }
1093 exports := symbolNameSet(stdlib.PackageSymbols[pkg])
1094 pass.addCandidate(
1095 &ImportInfo{ImportPath: pkg},
1096 &packageInfo{name: localbase(pkg), exports: exports})
1097 }
1098 for left := range refs {
1099 if left == "rand" {
1100
1101
1102 add("crypto/rand")
1103
1104
1105
1106 add("math/rand/v2")
1107 continue
1108 }
1109 for importPath := range stdlib.PackageSymbols {
1110 if path.Base(importPath) == left {
1111 add(importPath)
1112 }
1113 }
1114 }
1115 return nil
1116 }
1117
1118
1119 type Resolver interface {
1120
1121 loadPackageNames(importPaths []string, srcDir string) (map[string]string, error)
1122
1123
1124 scan(ctx context.Context, callback *scanCallback) error
1125
1126
1127
1128 loadExports(ctx context.Context, pkg *pkg, includeTest bool) (string, []stdlib.Symbol, error)
1129
1130
1131 scoreImportPath(ctx context.Context, path string) float64
1132
1133
1134
1135
1136
1137
1138 ClearForNewScan() Resolver
1139 }
1140
1141
1142
1143
1144 type scanCallback struct {
1145
1146
1147
1148 rootFound func(gopathwalk.Root) bool
1149
1150
1151
1152 dirFound func(pkg *pkg) bool
1153
1154
1155 packageNameLoaded func(pkg *pkg) bool
1156
1157 exportsLoaded func(pkg *pkg, exports []stdlib.Symbol)
1158 }
1159
1160 func addExternalCandidates(ctx context.Context, pass *pass, refs references, filename string) error {
1161 ctx, done := event.Start(ctx, "imports.addExternalCandidates")
1162 defer done()
1163
1164 var mu sync.Mutex
1165 found := make(map[string][]pkgDistance)
1166 callback := &scanCallback{
1167 rootFound: func(gopathwalk.Root) bool {
1168 return true
1169 },
1170 dirFound: func(pkg *pkg) bool {
1171 return pkgIsCandidate(filename, refs, pkg)
1172 },
1173 packageNameLoaded: func(pkg *pkg) bool {
1174 if _, want := refs[pkg.packageName]; !want {
1175 return false
1176 }
1177 if pkg.dir == pass.srcDir && pass.f.Name.Name == pkg.packageName {
1178
1179
1180 return false
1181 }
1182 if !canUse(filename, pkg.dir) {
1183 return false
1184 }
1185 mu.Lock()
1186 defer mu.Unlock()
1187 found[pkg.packageName] = append(found[pkg.packageName], pkgDistance{pkg, distance(pass.srcDir, pkg.dir)})
1188 return false
1189 },
1190 }
1191 resolver, err := pass.env.GetResolver()
1192 if err != nil {
1193 return err
1194 }
1195 if err = resolver.scan(context.Background(), callback); err != nil {
1196 return err
1197 }
1198
1199
1200 type result struct {
1201 imp *ImportInfo
1202 pkg *packageInfo
1203 }
1204 results := make(chan result, len(refs))
1205
1206 ctx, cancel := context.WithCancel(context.TODO())
1207 var wg sync.WaitGroup
1208 defer func() {
1209 cancel()
1210 wg.Wait()
1211 }()
1212 var (
1213 firstErr error
1214 firstErrOnce sync.Once
1215 )
1216 for pkgName, symbols := range refs {
1217 wg.Add(1)
1218 go func(pkgName string, symbols map[string]bool) {
1219 defer wg.Done()
1220
1221 found, err := findImport(ctx, pass, found[pkgName], pkgName, symbols)
1222
1223 if err != nil {
1224 firstErrOnce.Do(func() {
1225 firstErr = err
1226 cancel()
1227 })
1228 return
1229 }
1230
1231 if found == nil {
1232 return
1233 }
1234
1235 imp := &ImportInfo{
1236 ImportPath: found.importPathShort,
1237 }
1238
1239 pkg := &packageInfo{
1240 name: pkgName,
1241 exports: symbols,
1242 }
1243 results <- result{imp, pkg}
1244 }(pkgName, symbols)
1245 }
1246 go func() {
1247 wg.Wait()
1248 close(results)
1249 }()
1250
1251 for result := range results {
1252
1253
1254 if types.Universe.Lookup(result.pkg.name) != nil {
1255
1256
1257
1258
1259
1260
1261 continue
1262 }
1263 pass.addCandidate(result.imp, result.pkg)
1264 }
1265 return firstErr
1266 }
1267
1268
1269 func notIdentifier(ch rune) bool {
1270 return !('a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' ||
1271 '0' <= ch && ch <= '9' ||
1272 ch == '_' ||
1273 ch >= utf8.RuneSelf && (unicode.IsLetter(ch) || unicode.IsDigit(ch)))
1274 }
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284 func ImportPathToAssumedName(importPath string) string {
1285 base := path.Base(importPath)
1286 if strings.HasPrefix(base, "v") {
1287 if _, err := strconv.Atoi(base[1:]); err == nil {
1288 dir := path.Dir(importPath)
1289 if dir != "." {
1290 base = path.Base(dir)
1291 }
1292 }
1293 }
1294 base = strings.TrimPrefix(base, "go-")
1295 if i := strings.IndexFunc(base, notIdentifier); i >= 0 {
1296 base = base[:i]
1297 }
1298 return base
1299 }
1300
1301
1302 type gopathResolver struct {
1303 env *ProcessEnv
1304 walked bool
1305 cache *DirInfoCache
1306 scanSema chan struct{}
1307 }
1308
1309 func newGopathResolver(env *ProcessEnv) *gopathResolver {
1310 r := &gopathResolver{
1311 env: env,
1312 cache: NewDirInfoCache(),
1313 scanSema: make(chan struct{}, 1),
1314 }
1315 r.scanSema <- struct{}{}
1316 return r
1317 }
1318
1319 func (r *gopathResolver) ClearForNewScan() Resolver {
1320 return newGopathResolver(r.env)
1321 }
1322
1323 func (r *gopathResolver) loadPackageNames(importPaths []string, srcDir string) (map[string]string, error) {
1324 names := map[string]string{}
1325 bctx, err := r.env.buildContext()
1326 if err != nil {
1327 return nil, err
1328 }
1329 for _, path := range importPaths {
1330 names[path] = importPathToName(bctx, path, srcDir)
1331 }
1332 return names, nil
1333 }
1334
1335
1336 func importPathToName(bctx *build.Context, importPath, srcDir string) string {
1337
1338 if stdlib.HasPackage(importPath) {
1339 return path.Base(importPath)
1340 }
1341
1342 buildPkg, err := bctx.Import(importPath, srcDir, build.FindOnly)
1343 if err != nil {
1344 return ""
1345 }
1346 pkgName, err := packageDirToName(buildPkg.Dir)
1347 if err != nil {
1348 return ""
1349 }
1350 return pkgName
1351 }
1352
1353
1354
1355
1356
1357 func packageDirToName(dir string) (packageName string, err error) {
1358 d, err := os.Open(dir)
1359 if err != nil {
1360 return "", err
1361 }
1362 names, err := d.Readdirnames(-1)
1363 d.Close()
1364 if err != nil {
1365 return "", err
1366 }
1367 sort.Strings(names)
1368 var lastErr error
1369 var nfile int
1370 for _, name := range names {
1371 if !strings.HasSuffix(name, ".go") {
1372 continue
1373 }
1374 if strings.HasSuffix(name, "_test.go") {
1375 continue
1376 }
1377 nfile++
1378 fullFile := filepath.Join(dir, name)
1379
1380 fset := token.NewFileSet()
1381 f, err := parser.ParseFile(fset, fullFile, nil, parser.PackageClauseOnly)
1382 if err != nil {
1383 lastErr = err
1384 continue
1385 }
1386 pkgName := f.Name.Name
1387 if pkgName == "documentation" {
1388
1389
1390 continue
1391 }
1392 if pkgName == "main" {
1393
1394
1395 continue
1396 }
1397 return pkgName, nil
1398 }
1399 if lastErr != nil {
1400 return "", lastErr
1401 }
1402 return "", fmt.Errorf("no importable package found in %d Go files", nfile)
1403 }
1404
1405 type pkg struct {
1406 dir string
1407 importPathShort string
1408 packageName string
1409 relevance float64
1410 }
1411
1412 type pkgDistance struct {
1413 pkg *pkg
1414 distance int
1415 }
1416
1417
1418
1419 type byDistanceOrImportPathShortLength []pkgDistance
1420
1421 func (s byDistanceOrImportPathShortLength) Len() int { return len(s) }
1422 func (s byDistanceOrImportPathShortLength) Less(i, j int) bool {
1423 di, dj := s[i].distance, s[j].distance
1424 if di == -1 {
1425 return false
1426 }
1427 if dj == -1 {
1428 return true
1429 }
1430 if di != dj {
1431 return di < dj
1432 }
1433
1434 vi, vj := s[i].pkg.importPathShort, s[j].pkg.importPathShort
1435 if len(vi) != len(vj) {
1436 return len(vi) < len(vj)
1437 }
1438 return vi < vj
1439 }
1440 func (s byDistanceOrImportPathShortLength) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
1441
1442 func distance(basepath, targetpath string) int {
1443 p, err := filepath.Rel(basepath, targetpath)
1444 if err != nil {
1445 return -1
1446 }
1447 if p == "." {
1448 return 0
1449 }
1450 return strings.Count(p, string(filepath.Separator)) + 1
1451 }
1452
1453 func (r *gopathResolver) scan(ctx context.Context, callback *scanCallback) error {
1454 add := func(root gopathwalk.Root, dir string) {
1455
1456
1457 if _, ok := r.cache.Load(dir); ok {
1458 return
1459 }
1460
1461 importpath := filepath.ToSlash(dir[len(root.Path)+len("/"):])
1462 info := directoryPackageInfo{
1463 status: directoryScanned,
1464 dir: dir,
1465 rootType: root.Type,
1466 nonCanonicalImportPath: VendorlessPath(importpath),
1467 }
1468 r.cache.Store(dir, info)
1469 }
1470 processDir := func(info directoryPackageInfo) {
1471
1472 if scanned, err := info.reachedStatus(directoryScanned); !scanned || err != nil {
1473 return
1474 }
1475
1476 p := &pkg{
1477 importPathShort: info.nonCanonicalImportPath,
1478 dir: info.dir,
1479 relevance: MaxRelevance - 1,
1480 }
1481 if info.rootType == gopathwalk.RootGOROOT {
1482 p.relevance = MaxRelevance
1483 }
1484
1485 if !callback.dirFound(p) {
1486 return
1487 }
1488 var err error
1489 p.packageName, err = r.cache.CachePackageName(info)
1490 if err != nil {
1491 return
1492 }
1493
1494 if !callback.packageNameLoaded(p) {
1495 return
1496 }
1497 if _, exports, err := r.loadExports(ctx, p, false); err == nil {
1498 callback.exportsLoaded(p, exports)
1499 }
1500 }
1501 stop := r.cache.ScanAndListen(ctx, processDir)
1502 defer stop()
1503
1504 goenv, err := r.env.goEnv()
1505 if err != nil {
1506 return err
1507 }
1508 var roots []gopathwalk.Root
1509 roots = append(roots, gopathwalk.Root{Path: filepath.Join(goenv["GOROOT"], "src"), Type: gopathwalk.RootGOROOT})
1510 for _, p := range filepath.SplitList(goenv["GOPATH"]) {
1511 roots = append(roots, gopathwalk.Root{Path: filepath.Join(p, "src"), Type: gopathwalk.RootGOPATH})
1512 }
1513
1514 roots = filterRoots(roots, callback.rootFound)
1515
1516
1517 scanDone := make(chan struct{})
1518 go func() {
1519 select {
1520 case <-ctx.Done():
1521 return
1522 case <-r.scanSema:
1523 }
1524 defer func() { r.scanSema <- struct{}{} }()
1525 gopathwalk.Walk(roots, add, gopathwalk.Options{Logf: r.env.Logf, ModulesEnabled: false})
1526 close(scanDone)
1527 }()
1528 select {
1529 case <-ctx.Done():
1530 case <-scanDone:
1531 }
1532 return nil
1533 }
1534
1535 func (r *gopathResolver) scoreImportPath(ctx context.Context, path string) float64 {
1536 if stdlib.HasPackage(path) {
1537 return MaxRelevance
1538 }
1539 return MaxRelevance - 1
1540 }
1541
1542 func filterRoots(roots []gopathwalk.Root, include func(gopathwalk.Root) bool) []gopathwalk.Root {
1543 var result []gopathwalk.Root
1544 for _, root := range roots {
1545 if !include(root) {
1546 continue
1547 }
1548 result = append(result, root)
1549 }
1550 return result
1551 }
1552
1553 func (r *gopathResolver) loadExports(ctx context.Context, pkg *pkg, includeTest bool) (string, []stdlib.Symbol, error) {
1554 if info, ok := r.cache.Load(pkg.dir); ok && !includeTest {
1555 return r.cache.CacheExports(ctx, r.env, info)
1556 }
1557 return loadExportsFromFiles(ctx, r.env, pkg.dir, includeTest)
1558 }
1559
1560
1561
1562 func VendorlessPath(ipath string) string {
1563
1564 if i := strings.LastIndex(ipath, "/vendor/"); i >= 0 {
1565 return ipath[i+len("/vendor/"):]
1566 }
1567 if strings.HasPrefix(ipath, "vendor/") {
1568 return ipath[len("vendor/"):]
1569 }
1570 return ipath
1571 }
1572
1573 func loadExportsFromFiles(ctx context.Context, env *ProcessEnv, dir string, includeTest bool) (string, []stdlib.Symbol, error) {
1574
1575 all, err := os.ReadDir(dir)
1576 if err != nil {
1577 return "", nil, err
1578 }
1579 var files []fs.DirEntry
1580 for _, fi := range all {
1581 name := fi.Name()
1582 if !strings.HasSuffix(name, ".go") || (!includeTest && strings.HasSuffix(name, "_test.go")) {
1583 continue
1584 }
1585 match, err := env.matchFile(dir, fi.Name())
1586 if err != nil || !match {
1587 continue
1588 }
1589 files = append(files, fi)
1590 }
1591
1592 if len(files) == 0 {
1593 return "", nil, fmt.Errorf("dir %v contains no buildable, non-test .go files", dir)
1594 }
1595
1596 var pkgName string
1597 var exports []stdlib.Symbol
1598 fset := token.NewFileSet()
1599 for _, fi := range files {
1600 select {
1601 case <-ctx.Done():
1602 return "", nil, ctx.Err()
1603 default:
1604 }
1605
1606 fullFile := filepath.Join(dir, fi.Name())
1607 f, err := parser.ParseFile(fset, fullFile, nil, 0)
1608 if err != nil {
1609 if env.Logf != nil {
1610 env.Logf("error parsing %v: %v", fullFile, err)
1611 }
1612 continue
1613 }
1614 if f.Name.Name == "documentation" {
1615
1616
1617 continue
1618 }
1619 if includeTest && strings.HasSuffix(f.Name.Name, "_test") {
1620
1621 continue
1622 }
1623 pkgName = f.Name.Name
1624 for name, obj := range f.Scope.Objects {
1625 if ast.IsExported(name) {
1626 var kind stdlib.Kind
1627 switch obj.Kind {
1628 case ast.Con:
1629 kind = stdlib.Const
1630 case ast.Typ:
1631 kind = stdlib.Type
1632 case ast.Var:
1633 kind = stdlib.Var
1634 case ast.Fun:
1635 kind = stdlib.Func
1636 }
1637 exports = append(exports, stdlib.Symbol{
1638 Name: name,
1639 Kind: kind,
1640 Version: 0,
1641 })
1642 }
1643 }
1644 }
1645 sortSymbols(exports)
1646
1647 if env.Logf != nil {
1648 env.Logf("loaded exports in dir %v (package %v): %v", dir, pkgName, exports)
1649 }
1650 return pkgName, exports, nil
1651 }
1652
1653 func sortSymbols(syms []stdlib.Symbol) {
1654 sort.Slice(syms, func(i, j int) bool {
1655 return syms[i].Name < syms[j].Name
1656 })
1657 }
1658
1659
1660
1661 func findImport(ctx context.Context, pass *pass, candidates []pkgDistance, pkgName string, symbols map[string]bool) (*pkg, error) {
1662
1663
1664
1665
1666 sort.Sort(byDistanceOrImportPathShortLength(candidates))
1667 if pass.env.Logf != nil {
1668 for i, c := range candidates {
1669 pass.env.Logf("%s candidate %d/%d: %v in %v", pkgName, i+1, len(candidates), c.pkg.importPathShort, c.pkg.dir)
1670 }
1671 }
1672 resolver, err := pass.env.GetResolver()
1673 if err != nil {
1674 return nil, err
1675 }
1676
1677
1678 rescv := make([]chan *pkg, len(candidates))
1679 for i := range candidates {
1680 rescv[i] = make(chan *pkg, 1)
1681 }
1682 const maxConcurrentPackageImport = 4
1683 loadExportsSem := make(chan struct{}, maxConcurrentPackageImport)
1684
1685 ctx, cancel := context.WithCancel(ctx)
1686 var wg sync.WaitGroup
1687 defer func() {
1688 cancel()
1689 wg.Wait()
1690 }()
1691
1692 wg.Add(1)
1693 go func() {
1694 defer wg.Done()
1695 for i, c := range candidates {
1696 select {
1697 case loadExportsSem <- struct{}{}:
1698 case <-ctx.Done():
1699 return
1700 }
1701
1702 wg.Add(1)
1703 go func(c pkgDistance, resc chan<- *pkg) {
1704 defer func() {
1705 <-loadExportsSem
1706 wg.Done()
1707 }()
1708
1709 if pass.env.Logf != nil {
1710 pass.env.Logf("loading exports in dir %s (seeking package %s)", c.pkg.dir, pkgName)
1711 }
1712
1713 includeTest := strings.HasSuffix(pass.f.Name.Name, "_test") && c.pkg.dir == pass.srcDir
1714 _, exports, err := resolver.loadExports(ctx, c.pkg, includeTest)
1715 if err != nil {
1716 if pass.env.Logf != nil {
1717 pass.env.Logf("loading exports in dir %s (seeking package %s): %v", c.pkg.dir, pkgName, err)
1718 }
1719 resc <- nil
1720 return
1721 }
1722
1723 exportsMap := make(map[string]bool, len(exports))
1724 for _, sym := range exports {
1725 exportsMap[sym.Name] = true
1726 }
1727
1728
1729
1730 for symbol := range symbols {
1731 if !exportsMap[symbol] {
1732 resc <- nil
1733 return
1734 }
1735 }
1736 resc <- c.pkg
1737 }(c, rescv[i])
1738 }
1739 }()
1740
1741 for _, resc := range rescv {
1742 pkg := <-resc
1743 if pkg == nil {
1744 continue
1745 }
1746 return pkg, nil
1747 }
1748 return nil, nil
1749 }
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763 func pkgIsCandidate(filename string, refs references, pkg *pkg) bool {
1764
1765 if !canUse(filename, pkg.dir) {
1766 return false
1767 }
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781 for pkgIdent := range refs {
1782 lastTwo := lastTwoComponents(pkg.importPathShort)
1783 if strings.Contains(lastTwo, pkgIdent) {
1784 return true
1785 }
1786 if hasHyphenOrUpperASCII(lastTwo) && !hasHyphenOrUpperASCII(pkgIdent) {
1787 lastTwo = lowerASCIIAndRemoveHyphen(lastTwo)
1788 if strings.Contains(lastTwo, pkgIdent) {
1789 return true
1790 }
1791 }
1792 }
1793 return false
1794 }
1795
1796 func hasHyphenOrUpperASCII(s string) bool {
1797 for i := 0; i < len(s); i++ {
1798 b := s[i]
1799 if b == '-' || ('A' <= b && b <= 'Z') {
1800 return true
1801 }
1802 }
1803 return false
1804 }
1805
1806 func lowerASCIIAndRemoveHyphen(s string) (ret string) {
1807 buf := make([]byte, 0, len(s))
1808 for i := 0; i < len(s); i++ {
1809 b := s[i]
1810 switch {
1811 case b == '-':
1812 continue
1813 case 'A' <= b && b <= 'Z':
1814 buf = append(buf, b+('a'-'A'))
1815 default:
1816 buf = append(buf, b)
1817 }
1818 }
1819 return string(buf)
1820 }
1821
1822
1823
1824 func canUse(filename, dir string) bool {
1825
1826
1827
1828
1829 if !strings.Contains(dir, "vendor") && !strings.Contains(dir, "internal") {
1830 return true
1831 }
1832
1833 dirSlash := filepath.ToSlash(dir)
1834 if !strings.Contains(dirSlash, "/vendor/") && !strings.Contains(dirSlash, "/internal/") && !strings.HasSuffix(dirSlash, "/internal") {
1835 return true
1836 }
1837
1838
1839
1840
1841
1842
1843 absfile, err := filepath.Abs(filename)
1844 if err != nil {
1845 return false
1846 }
1847 absdir, err := filepath.Abs(dir)
1848 if err != nil {
1849 return false
1850 }
1851 rel, err := filepath.Rel(absfile, absdir)
1852 if err != nil {
1853 return false
1854 }
1855 relSlash := filepath.ToSlash(rel)
1856 if i := strings.LastIndex(relSlash, "../"); i >= 0 {
1857 relSlash = relSlash[i+len("../"):]
1858 }
1859 return !strings.Contains(relSlash, "/vendor/") && !strings.Contains(relSlash, "/internal/") && !strings.HasSuffix(relSlash, "/internal")
1860 }
1861
1862
1863
1864 func lastTwoComponents(v string) string {
1865 nslash := 0
1866 for i := len(v) - 1; i >= 0; i-- {
1867 if v[i] == '/' || v[i] == '\\' {
1868 nslash++
1869 if nslash == 2 {
1870 return v[i:]
1871 }
1872 }
1873 }
1874 return v
1875 }
1876
1877 type visitFn func(node ast.Node) ast.Visitor
1878
1879 func (fn visitFn) Visit(node ast.Node) ast.Visitor {
1880 return fn(node)
1881 }
1882
1883 func symbolNameSet(symbols []stdlib.Symbol) map[string]bool {
1884 names := make(map[string]bool)
1885 for _, sym := range symbols {
1886 switch sym.Kind {
1887 case stdlib.Const, stdlib.Var, stdlib.Type, stdlib.Func:
1888 names[sym.Name] = true
1889 }
1890 }
1891 return names
1892 }
1893
View as plain text