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)