diff --git a/test/datarace_test.go b/test/datarace_test.go index c7bd6bfc4..dfec5e9fc 100644 --- a/test/datarace_test.go +++ b/test/datarace_test.go @@ -9,3 +9,7 @@ import ( func TestDatarace(t *testing.T) { testRule(t, "datarace", &rule.DataRaceRule{}) } + +func TestDataraceAfterGo1_22(t *testing.T) { + testRule(t, "go1.22/datarace", &rule.DataRaceRule{}) +} diff --git a/test/file-filter_test.go b/test/file-filter_test.go index 7fce3b092..7729a71f7 100644 --- a/test/file-filter_test.go +++ b/test/file-filter_test.go @@ -38,7 +38,7 @@ func TestFileExcludeFilterAtRuleLevel(t *testing.T) { t.Run("not called if exclude not match", func(t *testing.T) { rule := &TestFileFilterRule{} - cfg := &lint.RuleConfig{Exclude: []string{"file-to-exclude.go"}} + cfg := &lint.RuleConfig{Exclude: []string{"../testdata/file-to-exclude.go"}} cfg.Initialize() testRule(t, "file-to-exclude", rule, cfg) if rule.WasApplyed { diff --git a/test/golint_test.go b/test/golint_test.go index 7d12625d8..425413078 100644 --- a/test/golint_test.go +++ b/test/golint_test.go @@ -64,7 +64,7 @@ func TestAll(t *testing.T) { t.Fatalf("Failed reading %s: %v", fi.Name(), err) } - if err := assertFailures(t, baseDir, fileInfo, src, rules, map[string]lint.RuleConfig{}); err != nil { + if err := assertFailures(t, path.Dir(baseDir), fileInfo, src, rules, map[string]lint.RuleConfig{}); err != nil { t.Errorf("Linting %s: %v", fi.Name(), err) } }) diff --git a/test/range-val-address_test.go b/test/range-val-address_test.go index 1cfb11d2f..821187cdf 100644 --- a/test/range-val-address_test.go +++ b/test/range-val-address_test.go @@ -10,3 +10,7 @@ import ( func TestRangeValAddress(t *testing.T) { testRule(t, "range-val-address", &rule.RangeValAddress{}, &lint.RuleConfig{}) } + +func TestRangeValAddressAfterGo1_22(t *testing.T) { + testRule(t, "go1.22/range-val-address", &rule.RangeValAddress{}, &lint.RuleConfig{}) +} diff --git a/test/range-val-in-closure_test.go b/test/range-val-in-closure_test.go index f446ce1e9..a84ce0835 100644 --- a/test/range-val-in-closure_test.go +++ b/test/range-val-in-closure_test.go @@ -10,3 +10,7 @@ import ( func TestRangeValInClosure(t *testing.T) { testRule(t, "range-val-in-closure", &rule.RangeValInClosureRule{}, &lint.RuleConfig{}) } + +func TestRangeValInClosureAfterGo1_22(t *testing.T) { + testRule(t, "go1.22/range-val-in-closure", &rule.RangeValInClosureRule{}, &lint.RuleConfig{}) +} diff --git a/test/redefines-builtin-id_test.go b/test/redefines-builtin-id_test.go index d7fac96dc..18da663e9 100644 --- a/test/redefines-builtin-id_test.go +++ b/test/redefines-builtin-id_test.go @@ -10,3 +10,7 @@ import ( func TestRedefinesBuiltinID(t *testing.T) { testRule(t, "redefines-builtin-id", &rule.RedefinesBuiltinIDRule{}) } + +func TestRedefinesBuiltinIDAfterGo1_21(t *testing.T) { + testRule(t, "go1.21/redefines-builtin-id", &rule.RedefinesBuiltinIDRule{}) +} diff --git a/test/utils_test.go b/test/utils_test.go index 39d1a9969..6e9b4d6c7 100644 --- a/test/utils_test.go +++ b/test/utils_test.go @@ -10,6 +10,7 @@ import ( "go/token" "go/types" "os" + "path" "strconv" "strings" "testing" @@ -18,13 +19,14 @@ import ( ) func testRule(t *testing.T, filename string, rule lint.Rule, config ...*lint.RuleConfig) { - baseDir := "../testdata/" - filename = filename + ".go" - src, err := os.ReadFile(baseDir + filename) + baseDir := path.Join("../testdata/" + path.Dir(filename)) + filename = path.Base(filename) + ".go" + fullFilePath := path.Join(baseDir, filename) + src, err := os.ReadFile(fullFilePath) if err != nil { t.Fatalf("Bad filename path in test for %s: %v", rule.Name(), err) } - stat, err := os.Stat(baseDir + filename) + stat, err := os.Stat(fullFilePath) if err != nil { t.Fatalf("Cannot get file info for %s: %v", rule.Name(), err) } @@ -32,7 +34,7 @@ func testRule(t *testing.T, filename string, rule lint.Rule, config ...*lint.Rul if config != nil { c[rule.Name()] = *config[0] } - if parseInstructions(t, filename, src) == nil { + if parseInstructions(t, fullFilePath, src) == nil { assertSuccess(t, baseDir, stat, []lint.Rule{rule}, c) return } @@ -41,10 +43,11 @@ func testRule(t *testing.T, filename string, rule lint.Rule, config ...*lint.Rul func assertSuccess(t *testing.T, baseDir string, fi os.FileInfo, rules []lint.Rule, config map[string]lint.RuleConfig) error { l := lint.New(func(file string) ([]byte, error) { - return os.ReadFile(baseDir + file) + return os.ReadFile(file) }, 0) - ps, err := l.Lint([][]string{{fi.Name()}}, rules, lint.Config{ + filePath := path.Join(baseDir, fi.Name()) + ps, err := l.Lint([][]string{{filePath}}, rules, lint.Config{ Rules: config, }) if err != nil { @@ -63,15 +66,15 @@ func assertSuccess(t *testing.T, baseDir string, fi os.FileInfo, rules []lint.Ru func assertFailures(t *testing.T, baseDir string, fi os.FileInfo, src []byte, rules []lint.Rule, config map[string]lint.RuleConfig) error { l := lint.New(func(file string) ([]byte, error) { - return os.ReadFile(baseDir + file) + return os.ReadFile(file) }, 0) - ins := parseInstructions(t, fi.Name(), src) + ins := parseInstructions(t, path.Join(baseDir, fi.Name()), src) if ins == nil { return fmt.Errorf("Test file %v does not have instructions", fi.Name()) } - ps, err := l.Lint([][]string{{fi.Name()}}, rules, lint.Config{ + ps, err := l.Lint([][]string{{path.Join(baseDir, fi.Name())}}, rules, lint.Config{ Rules: config, }) if err != nil { diff --git a/testdata/go.mod b/testdata/go.mod new file mode 100644 index 000000000..c77c64179 --- /dev/null +++ b/testdata/go.mod @@ -0,0 +1,5 @@ +module github.com/mgechev/revive/testdata + +// set the lowest go version +// to trigger testing of all rules +go 1.0 diff --git a/testdata/go1.21/go.mod b/testdata/go1.21/go.mod new file mode 100644 index 000000000..53cb948dc --- /dev/null +++ b/testdata/go1.21/go.mod @@ -0,0 +1,3 @@ +module github.com/mgechev/revive/testdata + +go 1.21 diff --git a/testdata/go1.21/redefines-builtin-id.go b/testdata/go1.21/redefines-builtin-id.go new file mode 100644 index 000000000..2426b1019 --- /dev/null +++ b/testdata/go1.21/redefines-builtin-id.go @@ -0,0 +1,54 @@ +package fixtures + +func (this data) vmethod() { + nil := true // MATCH /assignment creates a shadow of built-in identifier nil/ + iota = 1 // MATCH /assignment modifies built-in identifier iota/ +} + +func append(i, j int) { // MATCH /redefinition of the built-in function append/ + +} + +type string int16 // MATCH /redefinition of the built-in type string/ + +func delete(set []int64, i int) (y []int64) { // MATCH /redefinition of the built-in function delete/ + for j, v := range set { + if j != i { + y = append(y, v) + } + } + return +} + +type any int // MATCH /redefinition of the built-in type any/ + +func any() {} // MATCH /redefinition of the built-in type any/ + +var any int // MATCH /redefinition of the built-in type any/ + +const any = 1 // MATCH /redefinition of the built-in type any/ + +var i, copy int // MATCH /redefinition of the built-in function copy/ + +// issue #792 +type () + +func foo() { + clear := 0 // MATCH /redefinition of the built-in function clear/ + max := 0 // MATCH /redefinition of the built-in function max/ + min := 0 // MATCH /redefinition of the built-in function min/ + _ = clear + _ = max + _ = min +} + +func foo1(new int) { // MATCH /redefinition of the built-in function new/ + _ = new +} + +func foo2() (new int) { // MATCH /redefinition of the built-in function new/ + return +} + +func foo3[new any]() { // MATCH /redefinition of the built-in function new/ +} diff --git a/testdata/go1.22/datarace.go b/testdata/go1.22/datarace.go new file mode 100644 index 000000000..8ffe1c5f0 --- /dev/null +++ b/testdata/go1.22/datarace.go @@ -0,0 +1,30 @@ +package fixtures + +func datarace() (r int, c char) { + for _, p := range []int{1, 2} { + go func() { + print(r) // MATCH /potential datarace: return value r is captured (by-reference) in goroutine/ + print(p) // Shall not match /datarace: range value p is captured (by-reference) in goroutine/ + }() + for i, p1 := range []int{1, 2} { + a := p1 + go func() { + print(r) // MATCH /potential datarace: return value r is captured (by-reference) in goroutine/ + print(p) // Shall not match /datarace: range value p is captured (by-reference) in goroutine/ + print(p1) // Shall not match /datarace: range value p1 is captured (by-reference) in goroutine/ + print(a) + print(i) // Shall not match /datarace: range value i is captured (by-reference) in goroutine/ + }() + print(i) + print(p) + go func() { + _ = c // MATCH /potential datarace: return value c is captured (by-reference) in goroutine/ + }() + } + print(p1) + } + go func() { + print(r) // MATCH /potential datarace: return value r is captured (by-reference) in goroutine/ + }() + print(r) +} diff --git a/testdata/go1.22/go.mod b/testdata/go1.22/go.mod new file mode 100644 index 000000000..0ddf345e3 --- /dev/null +++ b/testdata/go1.22/go.mod @@ -0,0 +1,3 @@ +module github.com/mgechev/revive/testdata + +go 1.22 diff --git a/testdata/go1.22/range-val-address.go b/testdata/go1.22/range-val-address.go new file mode 100644 index 000000000..2663e85ef --- /dev/null +++ b/testdata/go1.22/range-val-address.go @@ -0,0 +1,163 @@ +package fixtures + +func rangeValAddress() { + m := map[string]*string{} + + mySlice := []string{"A", "B", "C"} + for _, value := range mySlice { + m["address"] = &value // Shall not match /suspicious assignment of 'value'. range-loop variables always have the same address/ + } +} + +func rangeValAddress2() { + m := map[string]*string{} + + mySlice := []string{"A", "B", "C"} + for i := range mySlice { + m["address"] = &mySlice[i] + } +} + +func rangeValAddress3() { + m := map[string]*string{} + + mySlice := []string{"A", "B", "C"} + for _, value := range mySlice { + a := &value // Shall not match /suspicious assignment of 'value'. range-loop variables always have the same address/ + m["address"] = a + } +} + +func rangeValAddress4() { + m := []*string{} + + mySlice := []string{"A", "B", "C"} + for _, value := range mySlice { + m = append(m, &value) // Shall not match /suspicious assignment of 'value'. range-loop variables always have the same address/ + } +} + +func rangeValAddress5() { + m := map[*string]string{} + + mySlice := []string{"A", "B", "C"} + for _, value := range mySlice { + m[&value] = value // Shall not match /suspicious assignment of 'value'. range-loop variables always have the same address/ + } +} + +func rangeValAddress6() { + type v struct { + id string + } + m := []*string{} + + mySlice := []v{{id: "A"}, {id: "B"}, {id: "C"}} + for _, value := range mySlice { + m = append(m, &value.id) // Shall not match /suspicious assignment of 'value'. range-loop variables always have the same address/ + } +} + +func rangeValAddress7() { + type v struct { + id string + } + m := []*string{} + + for _, value := range []v{{id: "A"}, {id: "B"}, {id: "C"}} { + m = append(m, &value.id) // Shall not match /suspicious assignment of 'value'. range-loop variables always have the same address/ + } +} + +func rangeValAddress8() { + type v struct { + id string + } + m := []*string{} + + mySlice := []*v{{id: "A"}, {id: "B"}, {id: "C"}} + for _, value := range mySlice { + m = append(m, &value.id) + } +} + +func rangeValAddress9() { + type v struct { + id string + } + m := []*string{} + + mySlice := map[string]*v{"a": {id: "A"}, "b": {id: "B"}, "c": {id: "C"}} + for _, value := range mySlice { + m = append(m, &value.id) + } +} + +func rangeValAddress10() { + type v struct { + id string + } + m := []*string{} + + for _, value := range map[string]*v{"a": {id: "A"}, "b": {id: "B"}, "c": {id: "C"}} { + m = append(m, &value.id) + } +} + +func rangeValAddress11() { + type v struct { + id string + } + m := map[string]*string{} + + for key, value := range map[string]*v{"a": {id: "A"}, "b": {id: "B"}, "c": {id: "C"}} { + m[key] = &value.id + } +} + +func rangeValAddress12() { + type v struct { + id string + } + m := map[string]*string{} + + for key, value := range map[string]v{"a": {id: "A"}, "b": {id: "B"}, "c": {id: "C"}} { + m[key] = &value.id // Shall not match /suspicious assignment of 'value'. range-loop variables always have the same address/ + } +} + +func rangeValAddress13() { + type v struct { + id string + } + m := []*string{} + + otherSlice := map[string]*v{"a": {id: "A"}, "b": {id: "B"}, "c": {id: "C"}} + mySlice := otherSlice + for _, value := range mySlice { + m = append(m, &value.id) + } +} + +func rangeValAddress14() { + type v struct { + id *string + } + + m := []v{} + for _, value := range []string{"A", "B", "C"} { + a := v{id: &value} // Shall not match /suspicious assignment of 'value'. range-loop variables always have the same address/ + m = append(m, a) + } +} + +func rangeValAddress15() { + type v struct { + id *string + } + + m := []v{} + for _, value := range []string{"A", "B", "C"} { + m = append(m, v{id: &value}) // Shall not match /suspicious assignment of 'value'. range-loop variables always have the same address/ + } +} diff --git a/testdata/go1.22/range-val-in-closure.go b/testdata/go1.22/range-val-in-closure.go new file mode 100644 index 000000000..916e52315 --- /dev/null +++ b/testdata/go1.22/range-val-in-closure.go @@ -0,0 +1,49 @@ +package fixtures + +import "fmt" + +func foo() { + mySlice := []string{"A", "B", "C"} + for index, value := range mySlice { + go func() { + fmt.Printf("Index: %d\n", index) // Shall not match + fmt.Printf("Value: %s\n", value) // Shall not match + }() + } + + myDict := make(map[string]int) + myDict["A"] = 1 + myDict["B"] = 2 + myDict["C"] = 3 + for key, value := range myDict { + defer func() { + fmt.Printf("Index: %d\n", key) // Shall not match + fmt.Printf("Value: %s\n", value) // Shall not match + }() + } + + for i, newg := range groups { + go func(newg int) { + newg.run(m.opts.Context, i) // Shall not match + }(newg) + } + + for i, newg := range groups { + newg := newg + go func() { + newg.run(m.opts.Context, i) // Shall not match + }() + } +} + +func issue637() { + for key := range m { + myKey := key + go func() { + println(t{ + key: myKey, + otherField: (10 + key), // Shall not match + }) + }() + } +} diff --git a/testdata/redefines-builtin-id.go b/testdata/redefines-builtin-id.go index 2426b1019..48edad2b9 100644 --- a/testdata/redefines-builtin-id.go +++ b/testdata/redefines-builtin-id.go @@ -34,9 +34,9 @@ var i, copy int // MATCH /redefinition of the built-in function copy/ type () func foo() { - clear := 0 // MATCH /redefinition of the built-in function clear/ - max := 0 // MATCH /redefinition of the built-in function max/ - min := 0 // MATCH /redefinition of the built-in function min/ + clear := 0 // Shall not match /redefinition of the built-in function clear/ + max := 0 // Shall not match /redefinition of the built-in function max/ + min := 0 // Shall not match /redefinition of the built-in function min/ _ = clear _ = max _ = min