diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d8c3c92..2c013978 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## v1.27.1 (unreleased) + +### Bug Fixes (backported) + +- **cache**: Reuse cached results across module locations ([golangci-lint#6762](https://github.com/golangci/golangci-lint/pull/6762)). Cache keys and cached issue positions embedded absolute paths, so a module checked out at a different path — CI runners, containers, renamed worktrees — never hit the cache. Paths are now stored relative to the module directory. This release invalidates existing cache entries once. +- **staticcheck**: Fix two SA4023 false positives introduced in v1.27.0 ([v0.8.1](https://github.com/dominikh/go-tools/releases/tag/v0.8.1)). Unconstrained generics are no longer treated as never-nil ([#1736](https://github.com/dominikh/go-tools/issues/1736)), and comparisons not involving typed nils are no longer flagged ([#1737](https://github.com/dominikh/go-tools/issues/1737)) — `errors.New("") == nil` and similar defensive checks. +- **unparam**: Fix panic on type parameters used by closures ([mvdan/unparam#2fa3d84](https://github.com/mvdan/unparam/commit/2fa3d841b0c8)). Packages that previously crashed now lint normally, so new findings may appear in generic code. Parameters of zero-sized generic types with phantom type parameters are no longer reported, matching `unparam`'s zero-size skip rule. + ## v1.27.0 (2026-08-20) Rebased onto `golangci-lint` v2.13.1, targeting Go 1.27. This pulls forward everything from diff --git a/go.mod b/go.mod index d6769494..a66a8557 100644 --- a/go.mod +++ b/go.mod @@ -154,9 +154,9 @@ require ( golang.org/x/sync v0.22.0 golang.org/x/sys v0.47.0 golang.org/x/tools v0.49.0 - honnef.co/go/tools v0.8.0 + honnef.co/go/tools v0.8.1 mvdan.cc/gofumpt v0.11.0 - mvdan.cc/unparam v0.0.0-20260818115549-3f964bcb5673 + mvdan.cc/unparam v0.0.0-20260823230713-2fa3d841b0c8 ) require ( diff --git a/go.sum b/go.sum index 6945171c..0c6c9b45 100644 --- a/go.sum +++ b/go.sum @@ -985,12 +985,12 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.8.0 h1:UacpzPr7D6i5BAjTkA7sNVcx4kIbhAZcQ4zYtKiXx68= -honnef.co/go/tools v0.8.0/go.mod h1:XA+OnlRA9EDh/ukGvXMNSZNKGwFQJ+5dER0ioUkOxks= +honnef.co/go/tools v0.8.1 h1:+JKf3xJ1ni4CwrhVg4/pqsfPGP6vNAXcKbMXJodYx3w= +honnef.co/go/tools v0.8.1/go.mod h1:XA+OnlRA9EDh/ukGvXMNSZNKGwFQJ+5dER0ioUkOxks= mvdan.cc/gofumpt v0.11.0 h1:0H01XB95PnN2QgCSR9ELdZyTlJqNZ7181B0BTMh5VZc= mvdan.cc/gofumpt v0.11.0/go.mod h1:BeT5wCsOJt6J9zT2MZIOGszjUHzFkn1/l9g6xAzqsXo= -mvdan.cc/unparam v0.0.0-20260818115549-3f964bcb5673 h1:dEE6li4OPIE54oojY2qaayFS1fSp17G14si0gXRxl0U= -mvdan.cc/unparam v0.0.0-20260818115549-3f964bcb5673/go.mod h1:62roFV3D3nYOWIXv3PfGO4UYEKAotz2WgLywT87ONd8= +mvdan.cc/unparam v0.0.0-20260823230713-2fa3d841b0c8 h1:Re1NRyLpiAt9kB+ImaaoapwWiQXrKwER4tY8fLOkDew= +mvdan.cc/unparam v0.0.0-20260823230713-2fa3d841b0c8/go.mod h1:MrS/+zJ1M2xvGXhKktiHbNQeQPyg3Qel+KkzT+f7/i4= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/internal/cache/cache.go b/internal/cache/cache.go index d1a2c3ff..72ee323b 100644 --- a/internal/cache/cache.go +++ b/internal/cache/cache.go @@ -172,6 +172,10 @@ func (c *Cache) computePkgHash(pkg *packages.Package) (hashResults, error) { return nil, fmt.Errorf("failed to calculate file %s hash: %w", f, fErr) } + if rel, ok := toRelativePath(pkg.Module, f); ok { + f = pkg.Module.Path + "/" + rel + } + fmt.Fprintf(key, "file %s %x\n", f, h) } diff --git a/internal/cache/position.go b/internal/cache/position.go new file mode 100644 index 00000000..88cf2b4a --- /dev/null +++ b/internal/cache/position.go @@ -0,0 +1,54 @@ +package cache + +import ( + "go/token" + "path/filepath" + "strings" + + "golang.org/x/tools/go/packages" +) + +// RelativePosition creates a [token.Position] with a filename relative to the module directory. +func RelativePosition(mod *packages.Module, pos token.Position) token.Position { + pos.Filename, _ = toRelativePath(mod, pos.Filename) + + return pos +} + +// AbsolutionPosition creates the absolute path base on the module directory. +func AbsolutionPosition(mod *packages.Module, pos token.Position) token.Position { + pos.Filename = toAbsolutePath(mod, pos.Filename) + + return pos +} + +func toRelativePath(mod *packages.Module, filename string) (string, bool) { + if filename == "" || !isCurrentModule(mod) { + return filename, false + } + + rel, err := filepath.Rel(mod.Dir, filename) + if err != nil { + return filename, false + } + + rel = filepath.ToSlash(rel) + + if rel == ".." || strings.HasPrefix(rel, "../") { + return filename, false + } + + return rel, true +} + +func toAbsolutePath(mod *packages.Module, filename string) string { + if filename == "" || !isCurrentModule(mod) || filepath.IsAbs(filename) { + return filename + } + + return filepath.Join(mod.Dir, filepath.FromSlash(filename)) +} + +func isCurrentModule(mod *packages.Module) bool { + return mod != nil && mod.Dir != "" && mod.Version == "" +} diff --git a/internal/cache/position_test.go b/internal/cache/position_test.go new file mode 100644 index 00000000..b03edb9f --- /dev/null +++ b/internal/cache/position_test.go @@ -0,0 +1,54 @@ +package cache + +import ( + "go/token" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "golang.org/x/tools/go/packages" +) + +func TestRelativePosition(t *testing.T) { + mod := &packages.Module{Dir: "/my/path"} + + initialPos := token.Position{ + Filename: filepath.FromSlash("/my/path/foo.go"), + Offset: 1, + Line: 2, + Column: 3, + } + + pos := RelativePosition(mod, initialPos) + + expected := token.Position{ + Filename: "foo.go", + Offset: 1, + Line: 2, + Column: 3, + } + + assert.Equal(t, expected, pos) +} + +func TestAbsolutionPosition(t *testing.T) { + mod := &packages.Module{Dir: "/my/other/path"} + + initialPos := token.Position{ + Filename: "foo.go", + Offset: 1, + Line: 2, + Column: 3, + } + + pos := AbsolutionPosition(mod, initialPos) + + expected := token.Position{ + Filename: filepath.FromSlash("/my/other/path/foo.go"), + Offset: 1, + Line: 2, + Column: 3, + } + + assert.Equal(t, expected, pos) +} diff --git a/pkg/commands/run.go b/pkg/commands/run.go index 4f8478f5..fce5b0ad 100644 --- a/pkg/commands/run.go +++ b/pkg/commands/run.go @@ -640,6 +640,7 @@ func initHashSalt(logger logutils.Log, version string, cfg *config.Config) error } b := bytes.NewBuffer(binSalt) + b.WriteString("golangci-lint-cache/v2") b.Write(configSalt) b.WriteString(goModSalt) @@ -712,7 +713,8 @@ func computeGoModSalt() (string, error) { return "", fmt.Errorf("failed to read go.mod: %w", err) } - sum, err := dirhash.Hash1([]string{goModPath}, func(string) (io.ReadCloser, error) { + // NOTE: the variable `goModPath` is not used here to ensure getting the same hash, independently of the location, for the same content. + sum, err := dirhash.Hash1([]string{"go.mod"}, func(string) (io.ReadCloser, error) { return io.NopCloser(bytes.NewReader(data)), nil }) if err != nil { diff --git a/pkg/goanalysis/runners_cache.go b/pkg/goanalysis/runners_cache.go index 80d8a3c1..513fdf78 100644 --- a/pkg/goanalysis/runners_cache.go +++ b/pkg/goanalysis/runners_cache.go @@ -42,7 +42,7 @@ func saveIssuesToCache(allPkgs []*packages.Package, pkgsFromCache map[*packages. FromLinter: issue.FromLinter, Text: issue.Text, Severity: issue.Severity, - Pos: issue.Pos, + Pos: cache.RelativePosition(pkg.Module, issue.Pos), LineRange: issue.LineRange, SuggestedFixes: issue.SuggestedFixes, ExpectNoLint: issue.ExpectNoLint, @@ -114,7 +114,7 @@ func loadIssuesFromCache(pkgs []*packages.Package, lintCtx *linter.Context, FromLinter: issue.FromLinter, Text: issue.Text, Severity: issue.Severity, - Pos: issue.Pos, + Pos: cache.AbsolutionPosition(pkg.Module, issue.Pos), LineRange: issue.LineRange, SuggestedFixes: issue.SuggestedFixes, Pkg: pkg,