132 lines
		
	
	
		
			3.5 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			132 lines
		
	
	
		
			3.5 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
package processors
 | 
						|
 | 
						|
import (
 | 
						|
	"go/parser"
 | 
						|
	"go/token"
 | 
						|
	"path/filepath"
 | 
						|
	"strings"
 | 
						|
	"sync"
 | 
						|
	"time"
 | 
						|
 | 
						|
	"golang.org/x/tools/go/packages"
 | 
						|
 | 
						|
	"github.com/golangci/golangci-lint/pkg/logutils"
 | 
						|
	"github.com/golangci/golangci-lint/pkg/result"
 | 
						|
)
 | 
						|
 | 
						|
type posMapper func(pos token.Position) token.Position
 | 
						|
 | 
						|
type adjustMap struct {
 | 
						|
	sync.Mutex
 | 
						|
	m map[string]posMapper
 | 
						|
}
 | 
						|
 | 
						|
// FilenameUnadjuster is needed because a lot of linters use fset.Position(f.Pos())
 | 
						|
// to get filename. And they return adjusted filename (e.g. *.qtpl) for an issue. We need
 | 
						|
// restore real .go filename to properly output it, parse it, etc.
 | 
						|
type FilenameUnadjuster struct {
 | 
						|
	m                   map[string]posMapper // map from adjusted filename to position mapper: adjusted -> unadjusted position
 | 
						|
	log                 logutils.Log
 | 
						|
	loggedUnadjustments map[string]bool
 | 
						|
}
 | 
						|
 | 
						|
var _ Processor = &FilenameUnadjuster{}
 | 
						|
 | 
						|
func processUnadjusterPkg(m *adjustMap, pkg *packages.Package, log logutils.Log) {
 | 
						|
	fset := token.NewFileSet() // it's more memory efficient to not store all in one fset
 | 
						|
 | 
						|
	for _, filename := range pkg.CompiledGoFiles {
 | 
						|
		// It's important to call func here to run GC
 | 
						|
		processUnadjusterFile(filename, m, log, fset)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func processUnadjusterFile(filename string, m *adjustMap, log logutils.Log, fset *token.FileSet) {
 | 
						|
	syntax, err := parser.ParseFile(fset, filename, nil, parser.ParseComments)
 | 
						|
	if err != nil {
 | 
						|
		// Error will be reported by typecheck
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	adjustedFilename := fset.PositionFor(syntax.Pos(), true).Filename
 | 
						|
	if adjustedFilename == "" {
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	unadjustedFilename := fset.PositionFor(syntax.Pos(), false).Filename
 | 
						|
	if unadjustedFilename == "" || unadjustedFilename == adjustedFilename {
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	if !strings.HasSuffix(unadjustedFilename, ".go") {
 | 
						|
		return // file.go -> /caches/cgo-xxx
 | 
						|
	}
 | 
						|
 | 
						|
	m.Lock()
 | 
						|
	defer m.Unlock()
 | 
						|
	m.m[adjustedFilename] = func(adjustedPos token.Position) token.Position {
 | 
						|
		tokenFile := fset.File(syntax.Pos())
 | 
						|
		if tokenFile == nil {
 | 
						|
			log.Warnf("Failed to get token file for %s", adjustedFilename)
 | 
						|
			return adjustedPos
 | 
						|
		}
 | 
						|
		return fset.PositionFor(tokenFile.Pos(adjustedPos.Offset), false)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func NewFilenameUnadjuster(pkgs []*packages.Package, log logutils.Log) *FilenameUnadjuster {
 | 
						|
	m := adjustMap{m: map[string]posMapper{}}
 | 
						|
 | 
						|
	startedAt := time.Now()
 | 
						|
	var wg sync.WaitGroup
 | 
						|
	wg.Add(len(pkgs))
 | 
						|
	for _, pkg := range pkgs {
 | 
						|
		go func(pkg *packages.Package) {
 | 
						|
			// It's important to call func here to run GC
 | 
						|
			processUnadjusterPkg(&m, pkg, log)
 | 
						|
			wg.Done()
 | 
						|
		}(pkg)
 | 
						|
	}
 | 
						|
	wg.Wait()
 | 
						|
	log.Infof("Pre-built %d adjustments in %s", len(m.m), time.Since(startedAt))
 | 
						|
 | 
						|
	return &FilenameUnadjuster{
 | 
						|
		m:                   m.m,
 | 
						|
		log:                 log,
 | 
						|
		loggedUnadjustments: map[string]bool{},
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func (p FilenameUnadjuster) Name() string {
 | 
						|
	return "filename_unadjuster"
 | 
						|
}
 | 
						|
 | 
						|
func (p *FilenameUnadjuster) Process(issues []result.Issue) ([]result.Issue, error) {
 | 
						|
	return transformIssues(issues, func(i *result.Issue) *result.Issue {
 | 
						|
		issueFilePath := i.FilePath()
 | 
						|
		if !filepath.IsAbs(i.FilePath()) {
 | 
						|
			absPath, err := filepath.Abs(i.FilePath())
 | 
						|
			if err != nil {
 | 
						|
				p.log.Warnf("failed to build abs path for %q: %s", i.FilePath(), err)
 | 
						|
				return i
 | 
						|
			}
 | 
						|
			issueFilePath = absPath
 | 
						|
		}
 | 
						|
 | 
						|
		mapper := p.m[issueFilePath]
 | 
						|
		if mapper == nil {
 | 
						|
			return i
 | 
						|
		}
 | 
						|
 | 
						|
		newI := *i
 | 
						|
		newI.Pos = mapper(i.Pos)
 | 
						|
		if !p.loggedUnadjustments[i.Pos.Filename] {
 | 
						|
			p.log.Infof("Unadjusted from %v to %v", i.Pos, newI.Pos)
 | 
						|
			p.loggedUnadjustments[i.Pos.Filename] = true
 | 
						|
		}
 | 
						|
		return &newI
 | 
						|
	}), nil
 | 
						|
}
 | 
						|
 | 
						|
func (FilenameUnadjuster) Finish() {}
 |