github.com/vanstinator/golangci-lint@v0.0.0-20240223191551-cc572f00d9d1/pkg/result/processors/filename_unadjuster.go (about) 1 package processors 2 3 import ( 4 "go/parser" 5 "go/token" 6 "path/filepath" 7 "strings" 8 "sync" 9 "time" 10 11 "golang.org/x/tools/go/packages" 12 13 "github.com/vanstinator/golangci-lint/pkg/logutils" 14 "github.com/vanstinator/golangci-lint/pkg/result" 15 ) 16 17 type posMapper func(pos token.Position) token.Position 18 19 type adjustMap struct { 20 sync.Mutex 21 m map[string]posMapper 22 } 23 24 // FilenameUnadjuster is needed because a lot of linters use fset.Position(f.Pos()) 25 // to get filename. And they return adjusted filename (e.g. *.qtpl) for an issue. We need 26 // restore real .go filename to properly output it, parse it, etc. 27 type FilenameUnadjuster struct { 28 m map[string]posMapper // map from adjusted filename to position mapper: adjusted -> unadjusted position 29 log logutils.Log 30 loggedUnadjustments map[string]bool 31 } 32 33 var _ Processor = &FilenameUnadjuster{} 34 35 func processUnadjusterPkg(m *adjustMap, pkg *packages.Package, log logutils.Log) { 36 fset := token.NewFileSet() // it's more memory efficient to not store all in one fset 37 38 for _, filename := range pkg.CompiledGoFiles { 39 // It's important to call func here to run GC 40 processUnadjusterFile(filename, m, log, fset) 41 } 42 } 43 44 func processUnadjusterFile(filename string, m *adjustMap, log logutils.Log, fset *token.FileSet) { 45 syntax, err := parser.ParseFile(fset, filename, nil, parser.ParseComments) 46 if err != nil { 47 // Error will be reported by typecheck 48 return 49 } 50 51 adjustedFilename := fset.PositionFor(syntax.Pos(), true).Filename 52 if adjustedFilename == "" { 53 return 54 } 55 56 unadjustedFilename := fset.PositionFor(syntax.Pos(), false).Filename 57 if unadjustedFilename == "" || unadjustedFilename == adjustedFilename { 58 return 59 } 60 61 if !strings.HasSuffix(unadjustedFilename, ".go") { 62 return // file.go -> /caches/cgo-xxx 63 } 64 65 m.Lock() 66 defer m.Unlock() 67 m.m[adjustedFilename] = func(adjustedPos token.Position) token.Position { 68 tokenFile := fset.File(syntax.Pos()) 69 if tokenFile == nil { 70 log.Warnf("Failed to get token file for %s", adjustedFilename) 71 return adjustedPos 72 } 73 return fset.PositionFor(tokenFile.Pos(adjustedPos.Offset), false) 74 } 75 } 76 77 func NewFilenameUnadjuster(pkgs []*packages.Package, log logutils.Log) *FilenameUnadjuster { 78 m := adjustMap{m: map[string]posMapper{}} 79 80 startedAt := time.Now() 81 var wg sync.WaitGroup 82 wg.Add(len(pkgs)) 83 for _, pkg := range pkgs { 84 go func(pkg *packages.Package) { 85 // It's important to call func here to run GC 86 processUnadjusterPkg(&m, pkg, log) 87 wg.Done() 88 }(pkg) 89 } 90 wg.Wait() 91 log.Infof("Pre-built %d adjustments in %s", len(m.m), time.Since(startedAt)) 92 93 return &FilenameUnadjuster{ 94 m: m.m, 95 log: log, 96 loggedUnadjustments: map[string]bool{}, 97 } 98 } 99 100 func (p *FilenameUnadjuster) Name() string { 101 return "filename_unadjuster" 102 } 103 104 func (p *FilenameUnadjuster) Process(issues []result.Issue) ([]result.Issue, error) { 105 return transformIssues(issues, func(i *result.Issue) *result.Issue { 106 issueFilePath := i.FilePath() 107 if !filepath.IsAbs(i.FilePath()) { 108 absPath, err := filepath.Abs(i.FilePath()) 109 if err != nil { 110 p.log.Warnf("failed to build abs path for %q: %s", i.FilePath(), err) 111 return i 112 } 113 issueFilePath = absPath 114 } 115 116 mapper := p.m[issueFilePath] 117 if mapper == nil { 118 return i 119 } 120 121 newI := *i 122 newI.Pos = mapper(i.Pos) 123 if !p.loggedUnadjustments[i.Pos.Filename] { 124 p.log.Infof("Unadjusted from %v to %v", i.Pos, newI.Pos) 125 p.loggedUnadjustments[i.Pos.Filename] = true 126 } 127 return &newI 128 }), nil 129 } 130 131 func (p *FilenameUnadjuster) Finish() {}