diff --git a/Makefile b/Makefile index 1f94ecb691be..1ba54cb23ebe 100644 --- a/Makefile +++ b/Makefile @@ -29,6 +29,12 @@ test: build GL_TEST_RUN=1 go test -v -parallel 2 ./... .PHONY: test +# ex: T=gofmt.go make test_fix +# the value of `T` is the name of a file from `test/testdata/fix` +test_fix: build + GL_TEST_RUN=1 go test -v ./test -count 1 -run TestFix/$T +.PHONY: test_fix + test_race: build_race GL_TEST_RUN=1 ./golangci-lint run -v --timeout=5m .PHONY: test_race diff --git a/pkg/golinters/gocritic.go b/pkg/golinters/gocritic.go index d292aacd71ae..ea3a3cbcbe8b 100644 --- a/pkg/golinters/gocritic.go +++ b/pkg/golinters/gocritic.go @@ -48,8 +48,9 @@ Dynamic rules are written declaratively with AST patterns, filters, report messa } linterCtx.SetPackageInfo(pass.TypesInfo, pass.Pkg) - var res []goanalysis.Issue pkgIssues := runGocriticOnPackage(linterCtx, enabledCheckers, pass.Files) + res := make([]goanalysis.Issue, 0, len(pkgIssues)) + for i := range pkgIssues { res = append(res, goanalysis.NewIssue(&pkgIssues[i], pass)) } @@ -179,11 +180,23 @@ func runGocriticOnFile(ctx *gocriticlinter.Context, f *ast.File, checkers []*goc // as read-only structure, so no copying is required. for _, warn := range c.Check(f) { pos := ctx.FileSet.Position(warn.Node.Pos()) - res = append(res, result.Issue{ + issue := result.Issue{ Pos: pos, Text: fmt.Sprintf("%s: %s", c.Info.Name, warn.Text), FromLinter: gocriticName, - }) + } + + if warn.HasQuickFix() { + issue.Replacement = &result.Replacement{ + Inline: &result.InlineFix{ + StartCol: pos.Column - 1, + Length: int(warn.Node.End() - warn.Node.Pos()), + NewString: string(warn.Suggestion.Replacement), + }, + } + } + + res = append(res, issue) } } diff --git a/test/ruleguard/rangeExprCopy.go b/test/ruleguard/rangeExprCopy.go new file mode 100644 index 000000000000..d68b45e61158 --- /dev/null +++ b/test/ruleguard/rangeExprCopy.go @@ -0,0 +1,14 @@ +// go:build ruleguard +package ruleguard + +import ( + "github.com/quasilyte/go-ruleguard/dsl" +) + +func RangeExprVal(m dsl.Matcher) { + m.Match(`for _, $_ := range $x { $*_ }`, `for _, $_ = range $x { $*_ }`). + Where(m["x"].Addressable && m["x"].Type.Size >= 512). + Report(`$x copy can be avoided with &$x`). + At(m["x"]). + Suggest(`&$x`) +} diff --git a/test/testdata/fix/in/gocritic.go b/test/testdata/fix/in/gocritic.go new file mode 100644 index 000000000000..4191eea1319a --- /dev/null +++ b/test/testdata/fix/in/gocritic.go @@ -0,0 +1,22 @@ +//args: -Egocritic +//config: linters-settings.gocritic.enabled-checks=ruleguard +//config: linters-settings.gocritic.settings.ruleguard.rules=ruleguard/rangeExprCopy.go,ruleguard/strings_simplify.go +package p + +import ( + "strings" +) + +func gocritic() { + var xs [2048]byte + + // xs -> &xs + for _, x := range xs { + print(x) + } + + // strings.Count("foo", "bar") == 0 -> !strings.Contains("foo", "bar") + if strings.Count("foo", "bar") == 0 { + print("qu") + } +} diff --git a/test/testdata/fix/out/gocritic.go b/test/testdata/fix/out/gocritic.go new file mode 100644 index 000000000000..2c66ef21b9f5 --- /dev/null +++ b/test/testdata/fix/out/gocritic.go @@ -0,0 +1,22 @@ +//args: -Egocritic +//config: linters-settings.gocritic.enabled-checks=ruleguard +//config: linters-settings.gocritic.settings.ruleguard.rules=ruleguard/rangeExprCopy.go,ruleguard/strings_simplify.go +package p + +import ( + "strings" +) + +func gocritic() { + var xs [2048]byte + + // xs -> &xs + for _, x := range &xs { + print(x) + } + + // strings.Count("foo", "bar") == 0 -> !strings.Contains("foo", "bar") + if !strings.Contains("foo", "bar") { + print("qu") + } +}