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  }