From 891129104835573606fcf572331e98cb4c9bad0e Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Fri, 10 Jan 2025 23:05:37 +0100 Subject: [PATCH 1/6] refactor: rewrite IdentifierMarker --- pkg/result/processors/identifier_marker.go | 222 +++++++++--------- .../processors/identifier_marker_test.go | 141 +++++++---- 2 files changed, 206 insertions(+), 157 deletions(-) diff --git a/pkg/result/processors/identifier_marker.go b/pkg/result/processors/identifier_marker.go index 876fd3bd3e30..a1590dfacfca 100644 --- a/pkg/result/processors/identifier_marker.go +++ b/pkg/result/processors/identifier_marker.go @@ -9,146 +9,134 @@ import ( var _ Processor = (*IdentifierMarker)(nil) type replacePattern struct { - re string + exp *regexp.Regexp repl string } -type replaceRegexp struct { - re *regexp.Regexp - repl string -} - -var replacePatterns = []replacePattern{ - // unparam - {`^(\S+) - (\S+) is unused$`, "`${1}` - `${2}` is unused"}, - {`^(\S+) - (\S+) always receives (\S+) \((.*)\)$`, "`${1}` - `${2}` always receives `${3}` (`${4}`)"}, - {`^(\S+) - (\S+) always receives (.*)$`, "`${1}` - `${2}` always receives `${3}`"}, - {`^(\S+) - result (\S+) is always (\S+)`, "`${1}` - result `${2}` is always `${3}`"}, - - // interfacer - {`^(\S+) can be (\S+)$`, "`${1}` can be `${2}`"}, - - // govet - {`^printf: (\S+) arg list ends with redundant newline$`, "printf: `${1}` arg list ends with redundant newline"}, - {`^composites: (\S+) composite literal uses unkeyed fields$`, "composites: `${1}` composite literal uses unkeyed fields"}, - - // gosec - { - `^(\S+): Blacklisted import (\S+): weak cryptographic primitive$`, - "${1}: Blacklisted import `${2}`: weak cryptographic primitive", - }, - {`^TLS InsecureSkipVerify set true.$`, "TLS `InsecureSkipVerify` set true."}, - - // gosimple - {`should replace loop with (.*)$`, "should replace loop with `${1}`"}, - { - `should use a simple channel send/receive instead of select with a single case`, - "should use a simple channel send/receive instead of `select` with a single case", - }, - { - `should omit comparison to bool constant, can be simplified to (.+)$`, - "should omit comparison to bool constant, can be simplified to `${1}`", - }, - {`should write (.+) instead of (.+)$`, "should write `${1}` instead of `${2}`"}, - {`redundant return statement$`, "redundant `return` statement"}, - { - `should replace this if statement with an unconditional strings.TrimPrefix`, - "should replace this `if` statement with an unconditional `strings.TrimPrefix`", - }, - - // staticcheck - {`this value of (\S+) is never used$`, "this value of `${1}` is never used"}, - { - `should use time.Since instead of time.Now\(\).Sub$`, - "should use `time.Since` instead of `time.Now().Sub`", - }, - { - `should check returned error before deferring response.Close\(\)$`, - "should check returned error before deferring `response.Close()`", - }, - {`no value of type uint is less than 0$`, "no value of type `uint` is less than `0`"}, - - // unused - {`(func|const|field|type|var) (\S+) is unused$`, "${1} `${2}` is unused"}, - - // typecheck - {`^unknown field (\S+) in struct literal$`, "unknown field `${1}` in struct literal"}, - { - `^invalid operation: (\S+) \(variable of type (\S+)\) has no field or method (\S+)$`, - "invalid operation: `${1}` (variable of type `${2}`) has no field or method `${3}`", - }, - {`^undeclared name: (\S+)$`, "undeclared name: `${1}`"}, - { - `^cannot use addr \(variable of type (\S+)\) as (\S+) value in argument to (\S+)$`, - "cannot use addr (variable of type `${1}`) as `${2}` value in argument to `${3}`", - }, - {`^other declaration of (\S+)$`, "other declaration of `${1}`"}, - {`^(\S+) redeclared in this block$`, "`${1}` redeclared in this block"}, - - // golint - { - `^exported (type|method|function|var|const) (\S+) should have comment or be unexported$`, - "exported ${1} `${2}` should have comment or be unexported", - }, - { - `^comment on exported (type|method|function|var|const) (\S+) should be of the form "(\S+) ..."$`, - "comment on exported ${1} `${2}` should be of the form `${3} ...`", - }, - {`^should replace (.+) with (.+)$`, "should replace `${1}` with `${2}`"}, - { - `^if block ends with a return statement, so drop this else and outdent its block$`, - "`if` block ends with a `return` statement, so drop this `else` and outdent its block", - }, - { - `^(struct field|var|range var|const|type|(?:func|method|interface method) (?:parameter|result)) (\S+) should be (\S+)$`, - "${1} `${2}` should be `${3}`", - }, - { - `^don't use underscores in Go names; var (\S+) should be (\S+)$`, - "don't use underscores in Go names; var `${1}` should be `${2}`", - }, -} - type IdentifierMarker struct { - replaceRegexps []replaceRegexp + patterns map[string][]replacePattern } func NewIdentifierMarker() *IdentifierMarker { - var replaceRegexps []replaceRegexp - for _, p := range replacePatterns { - r := replaceRegexp{ - re: regexp.MustCompile(p.re), - repl: p.repl, - } - replaceRegexps = append(replaceRegexps, r) - } - return &IdentifierMarker{ - replaceRegexps: replaceRegexps, + patterns: map[string][]replacePattern{ + "unparam": { + { + exp: regexp.MustCompile(`^(\S+) - (\S+) is unused$`), + repl: "`${1}` - `${2}` is unused", + }, + { + exp: regexp.MustCompile(`^(\S+) - (\S+) always receives (\S+) \((.*)\)$`), + repl: "`${1}` - `${2}` always receives `${3}` (`${4}`)", + }, + { + exp: regexp.MustCompile(`^(\S+) - (\S+) always receives (.*)$`), + repl: "`${1}` - `${2}` always receives `${3}`", + }, + { + exp: regexp.MustCompile(`^(\S+) - result (\S+) is always (\S+)`), + repl: "`${1}` - result `${2}` is always `${3}`", + }, + }, + "govet": { + { + // printf + exp: regexp.MustCompile(`^printf: (\S+) arg list ends with redundant newline$`), + repl: "printf: `${1}` arg list ends with redundant newline", + }, + }, + "gosec": { + { + exp: regexp.MustCompile(`^TLS InsecureSkipVerify set true.$`), + repl: "TLS `InsecureSkipVerify` set true.", + }, + }, + "gosimple": { + { + // s1011 + exp: regexp.MustCompile(`should replace loop with (.*)$`), + repl: "should replace loop with `${1}`", + }, + { + // s1000 + exp: regexp.MustCompile(`should use a simple channel send/receive instead of select with a single case`), + repl: "should use a simple channel send/receive instead of `select` with a single case", + }, + { + // s1002 + exp: regexp.MustCompile(`should omit comparison to bool constant, can be simplified to (.+)$`), + repl: "should omit comparison to bool constant, can be simplified to `${1}`", + }, + { + // s1023 + exp: regexp.MustCompile(`redundant return statement$`), + repl: "redundant `return` statement", + }, + { + // s1017 + exp: regexp.MustCompile(`should replace this if statement with an unconditional strings.TrimPrefix`), + repl: "should replace this `if` statement with an unconditional `strings.TrimPrefix`", + }, + }, + "staticcheck": { + { + // sa4006 + exp: regexp.MustCompile(`this value of (\S+) is never used$`), + repl: "this value of `${1}` is never used", + }, + { + // s1012 + exp: regexp.MustCompile(`should use time.Since instead of time.Now\(\).Sub$`), + repl: "should use `time.Since` instead of `time.Now().Sub`", + }, + { + // sa5001 + exp: regexp.MustCompile(`should check returned error before deferring response.Close\(\)$`), + repl: "should check returned error before deferring `response.Close()`", + }, + { + // sa4003 + exp: regexp.MustCompile(`no value of type uint is less than 0$`), + repl: "no value of type `uint` is less than `0`", + }, + }, + "unused": { + { + exp: regexp.MustCompile(`(func|const|field|type|var) (\S+) is unused$`), + repl: "${1} `${2}` is unused", + }, + }, + }, } } -func (IdentifierMarker) Name() string { +func (*IdentifierMarker) Name() string { return "identifier_marker" } -func (p IdentifierMarker) Process(issues []result.Issue) ([]result.Issue, error) { +func (p *IdentifierMarker) Process(issues []result.Issue) ([]result.Issue, error) { return transformIssues(issues, func(issue *result.Issue) *result.Issue { + re, ok := p.patterns[issue.FromLinter] + if !ok { + return issue + } + newIssue := *issue - newIssue.Text = p.markIdentifiers(newIssue.Text) + newIssue.Text = markIdentifiers(re, newIssue.Text) + return &newIssue }), nil } -func (IdentifierMarker) Finish() {} +func (*IdentifierMarker) Finish() {} -func (p IdentifierMarker) markIdentifiers(s string) string { - for _, rr := range p.replaceRegexps { - rs := rr.re.ReplaceAllString(s, rr.repl) - if rs != s { +func markIdentifiers(re []replacePattern, text string) string { + for _, rr := range re { + rs := rr.exp.ReplaceAllString(text, rr.repl) + if rs != text { return rs } } - return s + return text } diff --git a/pkg/result/processors/identifier_marker_test.go b/pkg/result/processors/identifier_marker_test.go index beafab903f1f..b03465b22445 100644 --- a/pkg/result/processors/identifier_marker_test.go +++ b/pkg/result/processors/identifier_marker_test.go @@ -1,6 +1,7 @@ package processors import ( + "fmt" "testing" "github.com/stretchr/testify/assert" @@ -9,70 +10,130 @@ import ( "github.com/golangci/golangci-lint/pkg/result" ) -func TestIdentifierMarker(t *testing.T) { - cases := []struct{ in, out string }{ - {"unknown field Address in struct literal", "unknown field `Address` in struct literal"}, +func TestIdentifierMarker_Process(t *testing.T) { + testCases := []struct { + desc string + linter string + in string + out string + }{ + // unparam { - "invalid operation: res (variable of type github.com/iotexproject/iotex-core/explorer/idl/explorer.GetBlkOrActResponse) has no field or method Address", - "invalid operation: `res` (variable of type `github.com/iotexproject/iotex-core/explorer/idl/explorer.GetBlkOrActResponse`) has no field or method `Address`", + linter: "unparam", + in: "foo - bar is unused", + out: "`foo` - `bar` is unused", }, { - "should use a simple channel send/receive instead of select with a single case", - "should use a simple channel send/receive instead of `select` with a single case", + linter: "unparam", + in: "foo - bar always receives fii (abc)", + out: "`foo` - `bar` always receives `fii` (`abc`)", }, - {"var testInputs is unused", "var `testInputs` is unused"}, - {"undeclared name: stateIDLabel", "undeclared name: `stateIDLabel`"}, { - "exported type Metrics should have comment or be unexported", - "exported type `Metrics` should have comment or be unexported", + linter: "unparam", + in: "foo - bar always receives fii", + out: "`foo` - `bar` always receives `fii`", }, { - `comment on exported function NewMetrics should be of the form "NewMetrics ..."`, - "comment on exported function `NewMetrics` should be of the form `NewMetrics ...`", + linter: "unparam", + in: "createEntry - result err is always nil", + out: "`createEntry` - result `err` is always `nil`", }, + + // govet + { + linter: "govet", + in: "printf: foo arg list ends with redundant newline", + out: "printf: `foo` arg list ends with redundant newline", + }, + + // gosec + { + linter: "gosec", + in: "TLS InsecureSkipVerify set true.", + out: "TLS `InsecureSkipVerify` set true.", + }, + + // gosimple + { + linter: "gosimple", + in: "should replace loop with foo", + out: "should replace loop with `foo`", + }, + { + linter: "gosimple", + in: "should use a simple channel send/receive instead of select with a single case", + out: "should use a simple channel send/receive instead of `select` with a single case", + }, + { + linter: "gosimple", + in: "should omit comparison to bool constant, can be simplified to !projectIntegration.Model.Storage", + out: "should omit comparison to bool constant, can be simplified to `!projectIntegration.Model.Storage`", + }, + { + linter: "gosimple", + in: "redundant return statement", + out: "redundant `return` statement", + }, + { + linter: "gosimple", + in: "S1017: should replace this if statement with an unconditional strings.TrimPrefix", + out: "S1017: should replace this `if` statement with an unconditional `strings.TrimPrefix`", + }, + + // staticcheck { - "cannot use addr (variable of type string) as github.com/iotexproject/iotex-core/pkg/keypair.PublicKey value in argument to action.FakeSeal", - "cannot use addr (variable of type `string`) as `github.com/iotexproject/iotex-core/pkg/keypair.PublicKey` value in argument to `action.FakeSeal`", + linter: "staticcheck", + in: "this value of foo is never used", + out: "this value of `foo` is never used", }, - {"other declaration of out", "other declaration of `out`"}, - {"should check returned error before deferring response.Close()", "should check returned error before deferring `response.Close()`"}, - {"should use time.Since instead of time.Now().Sub", "should use `time.Since` instead of `time.Now().Sub`"}, - {"TestFibZeroCount redeclared in this block", "`TestFibZeroCount` redeclared in this block"}, - {"should replace i += 1 with i++", "should replace `i += 1` with `i++`"}, - {"createEntry - result err is always nil", "`createEntry` - result `err` is always `nil`"}, { - "should omit comparison to bool constant, can be simplified to !projectIntegration.Model.Storage", - "should omit comparison to bool constant, can be simplified to `!projectIntegration.Model.Storage`", + linter: "staticcheck", + in: "should use time.Since instead of time.Now().Sub", + out: "should use `time.Since` instead of `time.Now().Sub`", }, { - "if block ends with a return statement, so drop this else and outdent its block", - "`if` block ends with a `return` statement, so drop this `else` and outdent its block", + linter: "staticcheck", + in: "should check returned error before deferring response.Close()", + out: "should check returned error before deferring `response.Close()`", }, { - "should write pupData := ms.m[pupID] instead of pupData, _ := ms.m[pupID]", - "should write `pupData := ms.m[pupID]` instead of `pupData, _ := ms.m[pupID]`", + linter: "staticcheck", + in: "no value of type uint is less than 0", + out: "no value of type `uint` is less than `0`", }, - {"no value of type uint is less than 0", "no value of type `uint` is less than `0`"}, - {"redundant return statement", "redundant `return` statement"}, - {"struct field Id should be ID", "struct field `Id` should be `ID`"}, + + // unused { - "don't use underscores in Go names; var Go_lint should be GoLint", - "don't use underscores in Go names; var `Go_lint` should be `GoLint`", + linter: "unused", + in: "var testInputs is unused", + out: "var `testInputs` is unused", }, + + // From a linter without patterns. { - "G501: Blacklisted import crypto/md5: weak cryptographic primitive", - "G501: Blacklisted import `crypto/md5`: weak cryptographic primitive", + linter: "foo", + in: "var testInputs is unused", + out: "var testInputs is unused", }, + + // Non-matching text. { - "S1017: should replace this if statement with an unconditional strings.TrimPrefix", - "S1017: should replace this `if` statement with an unconditional `strings.TrimPrefix`", + linter: "unused", + in: "foo is a foo", + out: "foo is a foo", }, } + p := NewIdentifierMarker() - for _, c := range cases { - out, err := p.Process([]result.Issue{{Text: c.in}}) - require.NoError(t, err) - assert.Equal(t, []result.Issue{{Text: c.out}}, out) + for _, test := range testCases { + t.Run(fmt.Sprintf("%s: %s", test.linter, test.in), func(t *testing.T) { + t.Parallel() + + out, err := p.Process([]result.Issue{{FromLinter: test.linter, Text: test.in}}) + require.NoError(t, err) + + assert.Equal(t, []result.Issue{{FromLinter: test.linter, Text: test.out}}, out) + }) } } From 25310621a9842d71e75049aae6a05bfd7648a426 Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Fri, 10 Jan 2025 23:23:06 +0100 Subject: [PATCH 2/6] refactor: improve Diff processor readability --- pkg/result/processors/diff.go | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/pkg/result/processors/diff.go b/pkg/result/processors/diff.go index 536889b8665f..b88d4dd763bd 100644 --- a/pkg/result/processors/diff.go +++ b/pkg/result/processors/diff.go @@ -36,32 +36,36 @@ func NewDiff(cfg *config.Issues) *Diff { } } -func (Diff) Name() string { +func (*Diff) Name() string { return "diff" } -func (p Diff) Process(issues []result.Issue) ([]result.Issue, error) { - if !p.onlyNew && p.fromRev == "" && p.patchFilePath == "" && p.patch == "" { // no need to work +func (p *Diff) Process(issues []result.Issue) ([]result.Issue, error) { + if !p.onlyNew && p.fromRev == "" && p.patchFilePath == "" && p.patch == "" { return issues, nil } var patchReader io.Reader - if p.patchFilePath != "" { + switch { + case p.patchFilePath != "": patch, err := os.ReadFile(p.patchFilePath) if err != nil { return nil, fmt.Errorf("can't read from patch file %s: %w", p.patchFilePath, err) } + patchReader = bytes.NewReader(patch) - } else if p.patch != "" { + + case p.patch != "": patchReader = strings.NewReader(p.patch) } - c := revgrep.Checker{ + checker := revgrep.Checker{ Patch: patchReader, RevisionFrom: p.fromRev, WholeFiles: p.wholeFiles, } - if err := c.Prepare(context.Background()); err != nil { + + if err := checker.Prepare(context.Background()); err != nil { return nil, fmt.Errorf("can't prepare diff by revgrep: %w", err) } @@ -71,15 +75,16 @@ func (p Diff) Process(issues []result.Issue) ([]result.Issue, error) { return issue } - hunkPos, isNew := c.IsNewIssue(issue) + hunkPos, isNew := checker.IsNewIssue(issue) if !isNew { return nil } newIssue := *issue newIssue.HunkPos = hunkPos + return &newIssue }), nil } -func (Diff) Finish() {} +func (*Diff) Finish() {} From f8a0de2e3e1cc7c31c1daa04ba6e896bf483a1b4 Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Fri, 10 Jan 2025 23:53:13 +0100 Subject: [PATCH 3/6] refactor: create PathAbsoluter --- pkg/lint/runner.go | 7 +++- pkg/logutils/logutils.go | 1 + pkg/result/processors/cgo.go | 30 +++++-------- pkg/result/processors/filename_unadjuster.go | 25 +++++------ pkg/result/processors/path_absoluter.go | 44 ++++++++++++++++++++ 5 files changed, 70 insertions(+), 37 deletions(-) create mode 100644 pkg/result/processors/path_absoluter.go diff --git a/pkg/lint/runner.go b/pkg/lint/runner.go index 157fde715f7b..a786dc11d847 100644 --- a/pkg/lint/runner.go +++ b/pkg/lint/runner.go @@ -68,12 +68,15 @@ func NewRunner(log logutils.Log, cfg *config.Config, args []string, goenv *gouti return &Runner{ Processors: []processors.Processor{ + // Must be the first processor. + processors.NewPathAbsoluter(log), + processors.NewCgo(goenv), - // Must go after Cgo. + // Must be after Cgo. processors.NewFilenameUnadjuster(lintCtx.Packages, log.Child(logutils.DebugKeyFilenameUnadjuster)), - // Must go after FilenameUnadjuster. + // Must be after FilenameUnadjuster. processors.NewInvalidIssue(log.Child(logutils.DebugKeyInvalidIssue)), // Must be before diff, nolint and exclude autogenerated processor at least. diff --git a/pkg/logutils/logutils.go b/pkg/logutils/logutils.go index fcda4dcc92b9..61faf7be1324 100644 --- a/pkg/logutils/logutils.go +++ b/pkg/logutils/logutils.go @@ -36,6 +36,7 @@ const ( DebugKeyLoader = "loader" // Debugs packages loading (including `go/packages` internal debugging). DebugKeyMaxFromLinter = "max_from_linter" DebugKeyMaxSameIssues = "max_same_issues" + DebugKeyPathAbsoluter = "path_absoluter" DebugKeyPkgCache = "pkgcache" DebugKeyRunner = "runner" DebugKeySeverityRules = "severity_rules" diff --git a/pkg/result/processors/cgo.go b/pkg/result/processors/cgo.go index 0e659f0f3e59..2fc907b401c7 100644 --- a/pkg/result/processors/cgo.go +++ b/pkg/result/processors/cgo.go @@ -1,7 +1,6 @@ package processors import ( - "fmt" "path/filepath" "strings" @@ -11,6 +10,10 @@ import ( var _ Processor = (*Cgo)(nil) +// Cgo some linters (e.g. gosec, deadcode) return incorrect filepaths for cgo issues, +// also cgo files have strange issues looking like false positives. +// +// Require absolute filepath. type Cgo struct { goCacheDir string } @@ -21,32 +24,19 @@ func NewCgo(goenv *goutil.Env) *Cgo { } } -func (Cgo) Name() string { +func (*Cgo) Name() string { return "cgo" } -func (p Cgo) Process(issues []result.Issue) ([]result.Issue, error) { +func (p *Cgo) Process(issues []result.Issue) ([]result.Issue, error) { return filterIssuesErr(issues, p.shouldPassIssue) } -func (Cgo) Finish() {} +func (*Cgo) Finish() {} -func (p Cgo) shouldPassIssue(issue *result.Issue) (bool, error) { - // some linters (e.g. gosec, deadcode) return incorrect filepaths for cgo issues, - // also cgo files have strange issues looking like false positives. - - // cache dir contains all preprocessed files including cgo files - - issueFilePath := issue.FilePath() - if !filepath.IsAbs(issue.FilePath()) { - absPath, err := filepath.Abs(issue.FilePath()) - if err != nil { - return false, fmt.Errorf("failed to build abs path for %q: %w", issue.FilePath(), err) - } - issueFilePath = absPath - } - - if p.goCacheDir != "" && strings.HasPrefix(issueFilePath, p.goCacheDir) { +func (p *Cgo) shouldPassIssue(issue *result.Issue) (bool, error) { + // [p.goCacheDir] contains all preprocessed files including cgo files. + if p.goCacheDir != "" && strings.HasPrefix(issue.FilePath(), p.goCacheDir) { return false, nil } diff --git a/pkg/result/processors/filename_unadjuster.go b/pkg/result/processors/filename_unadjuster.go index 6a1387c872e3..dbbc536dbef0 100644 --- a/pkg/result/processors/filename_unadjuster.go +++ b/pkg/result/processors/filename_unadjuster.go @@ -3,7 +3,6 @@ package processors import ( "go/parser" "go/token" - "path/filepath" "strings" "sync" "time" @@ -23,9 +22,11 @@ type adjustMap struct { 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. +// 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. +// +// Require absolute filepath. type FilenameUnadjuster struct { m map[string]posMapper // map from adjusted filename to position mapper: adjusted -> unadjusted position log logutils.Log @@ -36,8 +37,10 @@ func NewFilenameUnadjuster(pkgs []*packages.Package, log logutils.Log) *Filename 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 @@ -45,7 +48,9 @@ func NewFilenameUnadjuster(pkgs []*packages.Package, log logutils.Log) *Filename wg.Done() }(pkg) } + wg.Wait() + log.Infof("Pre-built %d adjustments in %s", len(m.m), time.Since(startedAt)) return &FilenameUnadjuster{ @@ -61,17 +66,7 @@ func (*FilenameUnadjuster) Name() string { func (p *FilenameUnadjuster) Process(issues []result.Issue) ([]result.Issue, error) { return transformIssues(issues, func(issue *result.Issue) *result.Issue { - issueFilePath := issue.FilePath() - if !filepath.IsAbs(issue.FilePath()) { - absPath, err := filepath.Abs(issue.FilePath()) - if err != nil { - p.log.Warnf("failed to build abs path for %q: %s", issue.FilePath(), err) - return issue - } - issueFilePath = absPath - } - - mapper := p.m[issueFilePath] + mapper := p.m[issue.FilePath()] if mapper == nil { return issue } diff --git a/pkg/result/processors/path_absoluter.go b/pkg/result/processors/path_absoluter.go new file mode 100644 index 000000000000..a649716d5db5 --- /dev/null +++ b/pkg/result/processors/path_absoluter.go @@ -0,0 +1,44 @@ +package processors + +import ( + "path/filepath" + + "github.com/golangci/golangci-lint/pkg/logutils" + "github.com/golangci/golangci-lint/pkg/result" +) + +var _ Processor = (*PathAbsoluter)(nil) + +// PathAbsoluter ensures that representation of path are absolute. +type PathAbsoluter struct { + log logutils.Log +} + +func NewPathAbsoluter(log logutils.Log) *PathAbsoluter { + return &PathAbsoluter{log: log.Child(logutils.DebugKeyPathAbsoluter)} +} + +func (*PathAbsoluter) Name() string { + return "path_absoluter" +} + +func (p *PathAbsoluter) Process(issues []result.Issue) ([]result.Issue, error) { + return transformIssues(issues, func(issue *result.Issue) *result.Issue { + if filepath.IsAbs(issue.FilePath()) { + return issue + } + + absPath, err := filepath.Abs(issue.FilePath()) + if err != nil { + p.log.Warnf("failed to get absolute path for %q: %v", issue.FilePath(), err) + return nil + } + + newIssue := issue + newIssue.Pos.Filename = absPath + + return newIssue + }), nil +} + +func (*PathAbsoluter) Finish() {} From b0a1ae132fb2105dc7350eec5023d5c9f7d98aac Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Sat, 11 Jan 2025 16:22:20 +0100 Subject: [PATCH 4/6] refactor: add logs inside PathPrettifier --- pkg/lint/runner.go | 4 +++- pkg/logutils/logutils.go | 1 + pkg/result/processors/path_prettifier.go | 13 ++++++++----- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/pkg/lint/runner.go b/pkg/lint/runner.go index a786dc11d847..c1f47e58fe17 100644 --- a/pkg/lint/runner.go +++ b/pkg/lint/runner.go @@ -80,7 +80,7 @@ func NewRunner(log logutils.Log, cfg *config.Config, args []string, goenv *gouti processors.NewInvalidIssue(log.Child(logutils.DebugKeyInvalidIssue)), // Must be before diff, nolint and exclude autogenerated processor at least. - processors.NewPathPrettifier(), + processors.NewPathPrettifier(log), skipFilesProcessor, skipDirsProcessor, // must be after path prettifier @@ -91,6 +91,7 @@ func NewRunner(log logutils.Log, cfg *config.Config, args []string, goenv *gouti processors.NewExclude(&cfg.Issues), processors.NewExcludeRules(log.Child(logutils.DebugKeyExcludeRules), files, &cfg.Issues), + processors.NewNolint(log.Child(logutils.DebugKeyNolint), dbManager, enabledLinters), processors.NewUniqByLine(cfg), @@ -98,6 +99,7 @@ func NewRunner(log logutils.Log, cfg *config.Config, args []string, goenv *gouti processors.NewMaxPerFileFromLinter(cfg), processors.NewMaxSameIssues(cfg.Issues.MaxSameIssues, log.Child(logutils.DebugKeyMaxSameIssues), cfg), processors.NewMaxFromLinter(cfg.Issues.MaxIssuesPerLinter, log.Child(logutils.DebugKeyMaxFromLinter), cfg), + processors.NewSourceCode(lineCache, log.Child(logutils.DebugKeySourceCode)), processors.NewPathShortener(), processors.NewSeverity(log.Child(logutils.DebugKeySeverityRules), files, &cfg.Severity), diff --git a/pkg/logutils/logutils.go b/pkg/logutils/logutils.go index 61faf7be1324..827e2341d7c5 100644 --- a/pkg/logutils/logutils.go +++ b/pkg/logutils/logutils.go @@ -37,6 +37,7 @@ const ( DebugKeyMaxFromLinter = "max_from_linter" DebugKeyMaxSameIssues = "max_same_issues" DebugKeyPathAbsoluter = "path_absoluter" + DebugKeyPathPrettifier = "path_prettifier" DebugKeyPkgCache = "pkgcache" DebugKeyRunner = "runner" DebugKeySeverityRules = "severity_rules" diff --git a/pkg/result/processors/path_prettifier.go b/pkg/result/processors/path_prettifier.go index c5c27357c60f..4135265a6f18 100644 --- a/pkg/result/processors/path_prettifier.go +++ b/pkg/result/processors/path_prettifier.go @@ -4,23 +4,25 @@ import ( "path/filepath" "github.com/golangci/golangci-lint/pkg/fsutils" + "github.com/golangci/golangci-lint/pkg/logutils" "github.com/golangci/golangci-lint/pkg/result" ) var _ Processor = (*PathPrettifier)(nil) type PathPrettifier struct { + log logutils.Log } -func NewPathPrettifier() *PathPrettifier { - return &PathPrettifier{} +func NewPathPrettifier(log logutils.Log) *PathPrettifier { + return &PathPrettifier{log: log.Child(logutils.DebugKeyPathPrettifier)} } -func (PathPrettifier) Name() string { +func (*PathPrettifier) Name() string { return "path_prettifier" } -func (PathPrettifier) Process(issues []result.Issue) ([]result.Issue, error) { +func (p *PathPrettifier) Process(issues []result.Issue) ([]result.Issue, error) { return transformIssues(issues, func(issue *result.Issue) *result.Issue { if !filepath.IsAbs(issue.FilePath()) { return issue @@ -28,6 +30,7 @@ func (PathPrettifier) Process(issues []result.Issue) ([]result.Issue, error) { rel, err := fsutils.ShortestRelPath(issue.FilePath(), "") if err != nil { + p.log.Warnf("shortest relative path: %v", err) return issue } @@ -37,4 +40,4 @@ func (PathPrettifier) Process(issues []result.Issue) ([]result.Issue, error) { }), nil } -func (PathPrettifier) Finish() {} +func (*PathPrettifier) Finish() {} From 7f854b34b5854836d32a2c1837d95559d8562961 Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Sun, 12 Jan 2025 15:45:44 +0100 Subject: [PATCH 5/6] refactor: remove useless call to fileCache --- pkg/commands/run.go | 2 +- pkg/golinters/misspell/misspell.go | 57 ++++++++++++------------------ pkg/lint/context.go | 8 ++--- pkg/lint/linter/context.go | 6 ++-- 4 files changed, 28 insertions(+), 45 deletions(-) diff --git a/pkg/commands/run.go b/pkg/commands/run.go index d9aa7578cd70..08de8f53c9fc 100644 --- a/pkg/commands/run.go +++ b/pkg/commands/run.go @@ -217,7 +217,7 @@ func (c *runCommand) preRunE(_ *cobra.Command, args []string) error { pkgLoader := lint.NewPackageLoader(c.log.Child(logutils.DebugKeyLoader), c.cfg, args, c.goenv, guard) - c.contextBuilder = lint.NewContextBuilder(c.cfg, pkgLoader, c.fileCache, pkgCache, guard) + c.contextBuilder = lint.NewContextBuilder(c.cfg, pkgLoader, pkgCache, guard) if err = initHashSalt(c.buildInfo.Version, c.cfg); err != nil { return fmt.Errorf("failed to init hash salt: %w", err) diff --git a/pkg/golinters/misspell/misspell.go b/pkg/golinters/misspell/misspell.go index 3ace5fddb9f8..c6eed6b76831 100644 --- a/pkg/golinters/misspell/misspell.go +++ b/pkg/golinters/misspell/misspell.go @@ -4,6 +4,7 @@ import ( "fmt" "go/ast" "go/token" + "os" "strings" "unicode" @@ -12,50 +13,38 @@ import ( "github.com/golangci/golangci-lint/pkg/config" "github.com/golangci/golangci-lint/pkg/goanalysis" - "github.com/golangci/golangci-lint/pkg/lint/linter" + "github.com/golangci/golangci-lint/pkg/golinters/internal" ) const linterName = "misspell" func New(settings *config.MisspellSettings) *goanalysis.Linter { - analyzer := &analysis.Analyzer{ - Name: linterName, - Doc: goanalysis.TheOnlyanalyzerDoc, - Run: goanalysis.DummyRun, + replacer, err := createMisspellReplacer(settings) + if err != nil { + internal.LinterLogger.Fatalf("%s: %v", linterName, err) } - return goanalysis.NewLinter( - linterName, - "Finds commonly misspelled English words", - []*analysis.Analyzer{analyzer}, - nil, - ).WithContextSetter(func(lintCtx *linter.Context) { - replacer, ruleErr := createMisspellReplacer(settings) - - analyzer.Run = func(pass *analysis.Pass) (any, error) { - if ruleErr != nil { - return nil, ruleErr - } - - err := runMisspell(lintCtx, pass, replacer, settings.Mode) - if err != nil { - return nil, err + a := &analysis.Analyzer{ + Name: linterName, + Doc: "Finds commonly misspelled English words", + Run: func(pass *analysis.Pass) (any, error) { + for _, file := range pass.Files { + err := runMisspellOnFile(pass, file, replacer, settings.Mode) + if err != nil { + return nil, err + } } return nil, nil - } - }).WithLoadMode(goanalysis.LoadModeSyntax) -} - -func runMisspell(lintCtx *linter.Context, pass *analysis.Pass, replacer *misspell.Replacer, mode string) error { - for _, file := range pass.Files { - err := runMisspellOnFile(lintCtx, pass, file, replacer, mode) - if err != nil { - return err - } + }, } - return nil + return goanalysis.NewLinter( + a.Name, + a.Doc, + []*analysis.Analyzer{a}, + nil, + ).WithLoadMode(goanalysis.LoadModeSyntax) } func createMisspellReplacer(settings *config.MisspellSettings) (*misspell.Replacer, error) { @@ -90,13 +79,13 @@ func createMisspellReplacer(settings *config.MisspellSettings) (*misspell.Replac return replacer, nil } -func runMisspellOnFile(lintCtx *linter.Context, pass *analysis.Pass, file *ast.File, replacer *misspell.Replacer, mode string) error { +func runMisspellOnFile(pass *analysis.Pass, file *ast.File, replacer *misspell.Replacer, mode string) error { position, isGoFile := goanalysis.GetGoFilePosition(pass, file) if !isGoFile { return nil } - fileContent, err := lintCtx.FileCache.GetFileBytes(position.Filename) + fileContent, err := os.ReadFile(position.Filename) if err != nil { return fmt.Errorf("can't get file %s contents: %w", position.Filename, err) } diff --git a/pkg/lint/context.go b/pkg/lint/context.go index d04a11b81f1a..2ac5a2d2c4fc 100644 --- a/pkg/lint/context.go +++ b/pkg/lint/context.go @@ -7,7 +7,6 @@ import ( "github.com/golangci/golangci-lint/internal/cache" "github.com/golangci/golangci-lint/pkg/config" "github.com/golangci/golangci-lint/pkg/exitcodes" - "github.com/golangci/golangci-lint/pkg/fsutils" "github.com/golangci/golangci-lint/pkg/goanalysis/load" "github.com/golangci/golangci-lint/pkg/lint/linter" "github.com/golangci/golangci-lint/pkg/logutils" @@ -18,19 +17,17 @@ type ContextBuilder struct { pkgLoader *PackageLoader - fileCache *fsutils.FileCache - pkgCache *cache.Cache + pkgCache *cache.Cache loadGuard *load.Guard } func NewContextBuilder(cfg *config.Config, pkgLoader *PackageLoader, - fileCache *fsutils.FileCache, pkgCache *cache.Cache, loadGuard *load.Guard, + pkgCache *cache.Cache, loadGuard *load.Guard, ) *ContextBuilder { return &ContextBuilder{ cfg: cfg, pkgLoader: pkgLoader, - fileCache: fileCache, pkgCache: pkgCache, loadGuard: loadGuard, } @@ -55,7 +52,6 @@ func (cl *ContextBuilder) Build(ctx context.Context, log logutils.Log, linters [ Cfg: cl.cfg, Log: log, - FileCache: cl.fileCache, PkgCache: cl.pkgCache, LoadGuard: cl.loadGuard, } diff --git a/pkg/lint/linter/context.go b/pkg/lint/linter/context.go index 9f29b5c4c884..6986b6231477 100644 --- a/pkg/lint/linter/context.go +++ b/pkg/lint/linter/context.go @@ -7,7 +7,6 @@ import ( "github.com/golangci/golangci-lint/internal/cache" "github.com/golangci/golangci-lint/pkg/config" - "github.com/golangci/golangci-lint/pkg/fsutils" "github.com/golangci/golangci-lint/pkg/goanalysis/load" "github.com/golangci/golangci-lint/pkg/logutils" ) @@ -20,9 +19,8 @@ type Context struct { // version for each of packages OriginalPackages []*packages.Package - Cfg *config.Config - FileCache *fsutils.FileCache - Log logutils.Log + Cfg *config.Config + Log logutils.Log PkgCache *cache.Cache LoadGuard *load.Guard From 2f1cf5007b286819e616c179c07a5b29c9ea0640 Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Sun, 12 Jan 2025 16:48:55 +0100 Subject: [PATCH 6/6] fix: misspell and cgo --- pkg/golinters/misspell/misspell.go | 5 +++-- pkg/golinters/misspell/testdata/misspell_cgo.go | 9 +++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/pkg/golinters/misspell/misspell.go b/pkg/golinters/misspell/misspell.go index c6eed6b76831..9d19780aca2b 100644 --- a/pkg/golinters/misspell/misspell.go +++ b/pkg/golinters/misspell/misspell.go @@ -4,7 +4,6 @@ import ( "fmt" "go/ast" "go/token" - "os" "strings" "unicode" @@ -85,7 +84,9 @@ func runMisspellOnFile(pass *analysis.Pass, file *ast.File, replacer *misspell.R return nil } - fileContent, err := os.ReadFile(position.Filename) + // Uses the non-adjusted file to work with cgo: + // if we read the real file, the positions are wrong in some cases. + fileContent, err := pass.ReadFile(pass.Fset.PositionFor(file.Pos(), false).Filename) if err != nil { return fmt.Errorf("can't get file %s contents: %w", position.Filename, err) } diff --git a/pkg/golinters/misspell/testdata/misspell_cgo.go b/pkg/golinters/misspell/testdata/misspell_cgo.go index d8b93114d93d..2306fd5dbfef 100644 --- a/pkg/golinters/misspell/testdata/misspell_cgo.go +++ b/pkg/golinters/misspell/testdata/misspell_cgo.go @@ -13,6 +13,7 @@ package testdata import "C" import ( + "fmt" "unsafe" ) @@ -28,3 +29,11 @@ func Misspell() { // the word langauge should be ignored here: it's set in config // the word Dialogue should be ignored here: it's set in config + +func _() error { + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) + + return fmt.Errorf("an unknown error ocurred") // want "`ocurred` is a misspelling of `occurred`" +}