/
package_ident.go
106 lines (85 loc) · 2.42 KB
/
package_ident.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
package index
import (
"go/ast"
"math"
"path"
"strings"
"github.com/agnivade/levenshtein"
"github.com/sourcegraph/scip-go/internal/handler"
"golang.org/x/tools/go/packages"
)
func findBestPackageDefinitionPath(pkg *packages.Package) (*ast.File, error) {
if pkg.PkgPath == "builtin" {
return nil, nil
}
// Unsafe is special case for builtin
if pkg.PkgPath == "unsafe" {
return nil, nil
}
if len(pkg.Syntax) == 0 {
handler.Println("Missing |", pkg.ID, pkg.Module.Path)
return nil, nil
}
files := []*ast.File{}
filesWithDocs := []*ast.File{}
for _, f := range pkg.Syntax {
// pos := pkg.Fset.Position(f.Pos())
files = append(files, f)
if f.Doc != nil {
filesWithDocs = append(filesWithDocs, f)
}
}
// The idiomatic way is to _only_ have one .go file per package that has a docstring
// for the package. This should generally return here.
if len(filesWithDocs) == 1 {
return filesWithDocs[0], nil
}
// If we for some reason have more than one .go file per package that has a docstring,
// only consider returning paths that contain the docstring (instead of any of the possible
// paths).
if len(filesWithDocs) > 1 {
files = filesWithDocs
}
// Try to only pick non _test files for non _test packages and vice versa.
files = filterBasedOnTestFiles(pkg, files)
// Find the best remaining path.
// Chooses:
// 1. doc.go
// 2. exact match
// 3. computes levenshtein and picks best score
var bestFile *ast.File
minDistance := math.MaxInt32
for _, f := range files {
fPath := pkg.Fset.Position(f.Pos()).Filename
fileName := fileNameWithoutExtension(fPath)
if "doc.go" == path.Base(fPath) {
return f, nil
}
if pkg.Name == fileName {
return f, nil
}
distance := levenshtein.ComputeDistance(pkg.Name, fileName)
if distance < minDistance {
minDistance = distance
bestFile = f
}
}
return bestFile, nil
}
func fileNameWithoutExtension(fileName string) string {
return strings.TrimSuffix(fileName, path.Ext(fileName))
}
func filterBasedOnTestFiles(pkg *packages.Package, files []*ast.File) []*ast.File {
packageNameEndsWithTest := strings.HasSuffix(pkg.Name, "_test")
preferredFiles := []*ast.File{}
for _, f := range files {
fPath := pkg.Fset.Position(f.Pos())
if packageNameEndsWithTest == strings.HasSuffix(fPath.Filename, "_test.go") {
preferredFiles = append(preferredFiles, f)
}
}
if len(preferredFiles) > 0 {
return preferredFiles
}
return files
}