Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down
8 changes: 4 additions & 4 deletions go.sum

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions internal/cache/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

Expand Down
54 changes: 54 additions & 0 deletions internal/cache/position.go
Original file line number Diff line number Diff line change
@@ -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 == ""
}
54 changes: 54 additions & 0 deletions internal/cache/position_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
4 changes: 3 additions & 1 deletion pkg/commands/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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 {
Expand Down
4 changes: 2 additions & 2 deletions pkg/goanalysis/runners_cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
Loading