github.com/thrasher-corp/golangci-lint@v1.17.3/pkg/lint/astcache/astcache.go (about) 1 package astcache 2 3 import ( 4 "go/ast" 5 "go/parser" 6 "go/token" 7 "path/filepath" 8 9 "golang.org/x/tools/go/packages" 10 11 "github.com/golangci/golangci-lint/pkg/fsutils" 12 "github.com/golangci/golangci-lint/pkg/logutils" 13 ) 14 15 type File struct { 16 F *ast.File 17 Fset *token.FileSet 18 Name string 19 Err error 20 } 21 22 type Cache struct { 23 m map[string]*File // map from absolute file path to file data 24 s []*File 25 log logutils.Log 26 } 27 28 func NewCache(log logutils.Log) *Cache { 29 return &Cache{ 30 m: map[string]*File{}, 31 log: log, 32 } 33 } 34 35 func (c Cache) ParsedFilenames() []string { 36 var keys []string 37 for k := range c.m { 38 keys = append(keys, k) 39 } 40 return keys 41 } 42 43 func (c Cache) normalizeFilename(filename string) string { 44 absPath := func() string { 45 if filepath.IsAbs(filename) { 46 return filepath.Clean(filename) 47 } 48 49 absFilename, err := filepath.Abs(filename) 50 if err != nil { 51 c.log.Warnf("Can't abs-ify filename %s: %s", filename, err) 52 return filename 53 } 54 55 return absFilename 56 }() 57 58 ret, err := fsutils.EvalSymlinks(absPath) 59 if err != nil { 60 c.log.Warnf("Failed to eval symlinks for %s: %s", absPath, err) 61 return absPath 62 } 63 64 return ret 65 } 66 67 func (c Cache) Get(filename string) *File { 68 return c.m[c.normalizeFilename(filename)] 69 } 70 71 func (c Cache) GetAllValidFiles() []*File { 72 return c.s 73 } 74 75 func (c *Cache) prepareValidFiles() { 76 files := make([]*File, 0, len(c.m)) 77 for _, f := range c.m { 78 if f.Err != nil || f.F == nil { 79 continue 80 } 81 files = append(files, f) 82 } 83 c.s = files 84 } 85 86 func LoadFromFilenames(log logutils.Log, filenames ...string) *Cache { 87 c := NewCache(log) 88 89 fset := token.NewFileSet() 90 for _, filename := range filenames { 91 c.parseFile(filename, fset) 92 } 93 94 c.prepareValidFiles() 95 return c 96 } 97 98 func LoadFromPackages(pkgs []*packages.Package, log logutils.Log) (*Cache, error) { 99 c := NewCache(log) 100 101 for _, pkg := range pkgs { 102 c.loadFromPackage(pkg) 103 } 104 105 c.prepareValidFiles() 106 return c, nil 107 } 108 109 func (c *Cache) extractFilenamesForAstFile(fset *token.FileSet, f *ast.File) []string { 110 var ret []string 111 112 // false ignores //line comments: name can be incorrect for generated files with //line directives 113 // mapping e.g. from .rl to .go files. 114 pos := fset.PositionFor(f.Pos(), false) 115 if pos.Filename != "" { 116 ret = append(ret, pos.Filename) 117 } 118 119 return ret 120 } 121 122 func (c *Cache) loadFromPackage(pkg *packages.Package) { 123 for _, f := range pkg.Syntax { 124 for _, filename := range c.extractFilenamesForAstFile(pkg.Fset, f) { 125 filePath := c.normalizeFilename(filename) 126 c.m[filePath] = &File{ 127 F: f, 128 Fset: pkg.Fset, 129 Name: filePath, 130 } 131 } 132 } 133 134 // some Go files sometimes aren't present in pkg.Syntax 135 fset := token.NewFileSet() // can't use pkg.Fset: it will overwrite offsets by preprocessed files 136 for _, filePath := range pkg.GoFiles { 137 filePath = c.normalizeFilename(filePath) 138 if c.m[filePath] == nil { 139 c.parseFile(filePath, fset) 140 } 141 } 142 } 143 144 func (c *Cache) parseFile(filePath string, fset *token.FileSet) { 145 if fset == nil { 146 fset = token.NewFileSet() 147 } 148 149 filePath = c.normalizeFilename(filePath) 150 151 // comments needed by e.g. golint 152 f, err := parser.ParseFile(fset, filePath, nil, parser.ParseComments) 153 c.m[filePath] = &File{ 154 F: f, 155 Fset: fset, 156 Err: err, 157 Name: filePath, 158 } 159 if err != nil { 160 c.log.Warnf("Can't parse AST of %s: %s", filePath, err) 161 } 162 }