From 4242f24f4df2f1d58a7ec639fe9b365cf5a5714c Mon Sep 17 00:00:00 2001 From: dominiquelefevre <54854047+dominiquelefevre@users.noreply.github.com> Date: Sun, 2 Jun 2024 12:55:26 +0300 Subject: [PATCH] Add support for the new implementation of for loop variables in go 1.22. (#993) * Add support for the new behaviour of for loops in go 1.22. Go 1.22 has changed the behaviour of for loops. Every iteration makes new loop variables. It is now safe to take their addresses because they are guaranteed to be unique. Similarly, it is now safe to capture loop variables in functions. * adds documentation for public function --------- Co-authored-by: chavacava --- go.mod | 4 ++- go.sum | 27 +++--------------- lint/linter.go | 53 ++++++++++++++++++++++++++++++++++-- lint/package.go | 14 ++++++++-- rule/datarace.go | 8 ++++-- rule/range-val-address.go | 4 +++ rule/range-val-in-closure.go | 4 +++ 7 files changed, 83 insertions(+), 31 deletions(-) diff --git a/go.mod b/go.mod index cd5de19..5346aed 100644 --- a/go.mod +++ b/go.mod @@ -1,12 +1,13 @@ module github.com/mgechev/revive -go 1.20 +go 1.21 require ( github.com/BurntSushi/toml v1.4.0 github.com/chavacava/garif v0.1.0 github.com/fatih/color v1.17.0 github.com/fatih/structtag v1.2.0 + github.com/hashicorp/go-version v1.7.0 github.com/mgechev/dots v0.0.0-20210922191527-e955255bf517 github.com/mitchellh/go-homedir v1.1.0 github.com/olekukonko/tablewriter v0.0.5 @@ -19,6 +20,7 @@ require ( github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.9 // indirect + github.com/stretchr/testify v1.9.0 // indirect golang.org/x/sys v0.20.0 // indirect golang.org/x/text v0.14.0 // indirect ) diff --git a/go.sum b/go.sum index d1bfbbf..5766d4c 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,3 @@ -github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= -github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/chavacava/garif v0.1.0 h1:2JHa3hbYf5D9dsgseMKAmc/MZ109otzgNFk5s87H9Pc= @@ -7,12 +5,12 @@ github.com/chavacava/garif v0.1.0/go.mod h1:XMyYCkEL58DF0oyW4qDjjnPWONs2HBqYKI+U github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= -github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4= github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= +github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= +github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= @@ -37,32 +35,15 @@ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSS github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= -golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= -golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= -golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= -golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/tools v0.16.1 h1:TLyB3WofjdOEepBHAU20JdNC1Zbg87elYofWYAY5oZA= -golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= -golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc= -golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= -golang.org/x/tools v0.18.0 h1:k8NLag8AGHnn+PHbl7g43CtqZAwG60vZkLqgyZgIHgQ= -golang.org/x/tools v0.18.0/go.mod h1:GL7B4CwcLLeo59yx/9UWWuNOW1n3VZ4f5axWfML7Lcg= -golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw= -golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc= -golang.org/x/tools v0.20.0 h1:hz/CVckiOxybQvFw6h7b/q80NTr9IUQb4s1IIzW7KNY= -golang.org/x/tools v0.20.0/go.mod h1:WvitBU7JJf6A4jOdg4S1tviW9bhUxkgeCui/0JHctQg= golang.org/x/tools v0.21.0 h1:qc0xYgIbsSDt9EyWz05J5wfa7LOVW0YTLOXrqdLAWIw= golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/lint/linter.go b/lint/linter.go index fb1ab6f..18ab523 100644 --- a/lint/linter.go +++ b/lint/linter.go @@ -3,12 +3,18 @@ package lint import ( "bufio" "bytes" + "encoding/json" "fmt" "go/token" "os" + "os/exec" + "path/filepath" "regexp" "strconv" + "strings" "sync" + + goversion "github.com/hashicorp/go-version" ) // ReadFile defines an abstraction for reading files. @@ -78,9 +84,19 @@ func (l *Linter) Lint(packages [][]string, ruleSet []Rule, config Config) (<-cha } func (l *Linter) lintPackage(filenames []string, ruleSet []Rule, config Config, failures chan Failure) error { + if len(filenames) == 0 { + return nil + } + + goVersion, err := detectGoVersion(filepath.Dir(filenames[0])) + if err != nil { + return err + } + pkg := &Package{ - fset: token.NewFileSet(), - files: map[string]*File{}, + fset: token.NewFileSet(), + files: map[string]*File{}, + goVersion: goVersion, } for _, filename := range filenames { content, err := l.readFile(filename) @@ -108,6 +124,39 @@ func (l *Linter) lintPackage(filenames []string, ruleSet []Rule, config Config, return nil } +func detectGoVersion(dir string) (ver *goversion.Version, err error) { + // https://github.com/golang/go/issues/44753#issuecomment-790089020 + cmd := exec.Command("go", "list", "-m", "-json") + cmd.Dir = dir + + raw, err := cmd.Output() + if err != nil { + return nil, fmt.Errorf("command go list: %w", err) + } + + var v struct { + GoMod string `json:"GoMod"` + GoVersion string `json:"GoVersion"` + } + if err = json.Unmarshal(raw, &v); err != nil { + return nil, fmt.Errorf("can't parse the output of go list: %w", err) + } + + if v.GoMod == "" { + // this package is outside a module, so assume + // an old-style source directory + + if v := os.Getenv("GOVERSION"); v != "" { + return goversion.NewVersion(strings.TrimPrefix(v, "go")) + } + + // assume the last version that does not have generics + return goversion.Must(goversion.NewVersion("1.17")), nil + } + + return goversion.NewVersion(strings.TrimPrefix(v.GoVersion, "go")) +} + // isGenerated reports whether the source file is generated code // according the rules from https://golang.org/s/generatedcode. // This is inherited from the original go lint. diff --git a/lint/package.go b/lint/package.go index 5976acf..b4a0a72 100644 --- a/lint/package.go +++ b/lint/package.go @@ -7,13 +7,16 @@ import ( "go/types" "sync" + goversion "github.com/hashicorp/go-version" + "github.com/mgechev/revive/internal/typeparams" ) // Package represents a package in the project. type Package struct { - fset *token.FileSet - files map[string]*File + fset *token.FileSet + files map[string]*File + goVersion *goversion.Version typesPkg *types.Package typesInfo *types.Info @@ -29,6 +32,8 @@ var ( trueValue = 1 falseValue = 2 notSet = 3 + + go122 = goversion.Must(goversion.NewVersion("1.22")) ) // Files return package's files. @@ -188,3 +193,8 @@ func (p *Package) lint(rules []Rule, config Config, failures chan Failure) { } wg.Wait() } + +// IsAtLeastGo122 returns true if the Go version for this package is 1.22 or higher, false otherwise +func (p *Package) IsAtLeastGo122() bool { + return p.goVersion.GreaterThanOrEqual(go122) +} diff --git a/rule/datarace.go b/rule/datarace.go index 39e9669..86ec6e1 100644 --- a/rule/datarace.go +++ b/rule/datarace.go @@ -16,7 +16,7 @@ func (*DataRaceRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure { onFailure := func(failure lint.Failure) { failures = append(failures, failure) } - w := lintDataRaces{onFailure: onFailure} + w := lintDataRaces{onFailure: onFailure, go122for: file.Pkg.IsAtLeastGo122()} ast.Walk(w, file.AST) @@ -30,6 +30,7 @@ func (*DataRaceRule) Name() string { type lintDataRaces struct { onFailure func(failure lint.Failure) + go122for bool } func (w lintDataRaces) Visit(n ast.Node) ast.Visitor { @@ -47,7 +48,7 @@ func (w lintDataRaces) Visit(n ast.Node) ast.Visitor { if results != nil { returnIDs = w.ExtractReturnIDs(results.List) } - fl := &lintFunctionForDataRaces{onFailure: w.onFailure, returnIDs: returnIDs, rangeIDs: map[*ast.Object]struct{}{}} + fl := &lintFunctionForDataRaces{onFailure: w.onFailure, returnIDs: returnIDs, rangeIDs: map[*ast.Object]struct{}{}, go122for: w.go122for} ast.Walk(fl, node.Body) return nil @@ -69,6 +70,7 @@ type lintFunctionForDataRaces struct { onFailure func(failure lint.Failure) returnIDs map[*ast.Object]struct{} rangeIDs map[*ast.Object]struct{} + go122for bool } func (w lintFunctionForDataRaces) Visit(node ast.Node) ast.Visitor { @@ -118,7 +120,7 @@ func (w lintFunctionForDataRaces) Visit(node ast.Node) ast.Visitor { _, isReturnID := w.returnIDs[id.Obj] switch { - case isRangeID: + case isRangeID && !w.go122for: w.onFailure(lint.Failure{ Confidence: 1, Node: id, diff --git a/rule/range-val-address.go b/rule/range-val-address.go index 51ad8e1..d2ab039 100644 --- a/rule/range-val-address.go +++ b/rule/range-val-address.go @@ -16,6 +16,10 @@ type RangeValAddress struct{} func (*RangeValAddress) Apply(file *lint.File, _ lint.Arguments) []lint.Failure { var failures []lint.Failure + if file.Pkg.IsAtLeastGo122() { + return failures + } + walker := rangeValAddress{ file: file, onFailure: func(failure lint.Failure) { diff --git a/rule/range-val-in-closure.go b/rule/range-val-in-closure.go index 1e85d0d..6f9255a 100644 --- a/rule/range-val-in-closure.go +++ b/rule/range-val-in-closure.go @@ -14,6 +14,10 @@ type RangeValInClosureRule struct{} func (*RangeValInClosureRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure { var failures []lint.Failure + if file.Pkg.IsAtLeastGo122() { + return failures + } + walker := rangeValInClosure{ onFailure: func(failure lint.Failure) { failures = append(failures, failure)