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() {}