From 5e4827db551ecf6840cf6f713f1a04890929832d Mon Sep 17 00:00:00 2001 From: Oleg Butuzov Date: Fri, 12 May 2023 10:34:23 +0300 Subject: [PATCH] Refactoring mirror so we can use analysis.SuggestedFixes (#20) * refactor: initial refactoring - Simplified checker (instead of separate checekrs) - Violations redone (& test generation) - Fixed few mistakes in violations * chores: update task & make * feature: analysis.SuggestedFix --- Makefile | 6 +- Taskfile.yml | 38 +-- analyzer.go | 138 +++++---- checkers_bufio.go | 41 ++- checkers_bytes.go | 385 ++++++++++++------------- checkers_httptest.go | 41 ++- checkers_maphash.go | 41 ++- checkers_os.go | 41 ++- checkers_regexp.go | 221 +++++++------- checkers_strings.go | 352 +++++++++++----------- checkers_utf8.go | 177 +++++------- cmd/internal/generate-tests/support.go | 28 +- debug.go | 22 -- internal/checker/checker.go | 203 +++++-------- internal/checker/imports.go | 12 +- internal/checker/violation.go | 125 ++++++-- testdata/bufio.go | 73 +++++ testdata/bytes.go | 6 +- testdata/regexp.go | 6 +- testdata/strings.go | 6 +- 20 files changed, 978 insertions(+), 984 deletions(-) delete mode 100644 debug.go create mode 100644 testdata/bufio.go diff --git a/Makefile b/Makefile index 4f205a0..8608d32 100644 --- a/Makefile +++ b/Makefile @@ -9,6 +9,10 @@ build: @ go build -trimpath -ldflags="-w -s" \ -o bin/mirror ./cmd/mirror/ +build-race: + @ go build -race -trimpath -ldflags="-w -s" \ + -o bin/mirror ./cmd/mirror/ + tests: go test -v -count=1 -race \ -failfast \ @@ -25,7 +29,7 @@ tests-summary: -covermode=atomic \ -coverpkg=$(GOPKGS) -coverprofile=coverage.cov --json ./... | tparse -all -generate: +test-generate: go run ./cmd/internal/generate-tests/ "$(PWD)/testdata" lints: diff --git a/Taskfile.yml b/Taskfile.yml index 021ebc4..dd4d685 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -4,46 +4,18 @@ tasks: default: sources: - "./**/*.go" - method: checksum + method: timestamp cmds: - clear - - make tests - - make lints - make build - - ls -lah bin/ - - testcase: go test -v -failfast -count=1 -run "TestAll/{{ .Case }}" ./... - - regen: - sources: - - ./cmd/internal/generate-tests/*.go - - ./checkers_*.go - - ./cmd/internal/generate-tests/templates/*.tmpl - method: timestamp - cmds: - - go build -o bin/genrate-tests ./cmd/internal/generate-tests/ - - ./bin/genrate-tests ./testdata - - make tests - - tdd: - dir: ./testdata - sources: - - ./**/*.go - method: timestamp - cmds: - - cmd: clear - - cmd: date - - task: build - - cmd: go run ../cmd/mirror --with-tests --with-debug ./... - ignore_error: true + - make build-race - task: lints - ignore_error: true + - make test-generate - task: tests + - cmd: go run ./cmd/mirror/ --with-tests --with-debug ./demo ignore_error: true - - echo "done" - install: make install - build: make build + testcase: go test -v -failfast -count=1 -run "TestAll/{{ .Case }}" ./... tests: cmds: diff --git a/analyzer.go b/analyzer.go index 28f4ef1..53a991c 100644 --- a/analyzer.go +++ b/analyzer.go @@ -14,8 +14,6 @@ import ( ) type analyzer struct { - checkers map[string]*checker.Checker // Available checkers. - withTests bool withDebug bool @@ -25,18 +23,7 @@ type analyzer struct { func NewAnalyzer() *analysis.Analyzer { flags := flags() - a := analyzer{ - checkers: make(map[string]*checker.Checker), - } - - a.register(newRegexpChecker()) - a.register(newStringsChecker()) - a.register(newBytesChecker()) - a.register(newMaphashChecker()) - a.register(newBufioChecker()) - a.register(newOsChecker()) - a.register(newUTF8Checker()) - a.register(newHTTPTestChecker()) + a := analyzer{} return &analysis.Analyzer{ Name: "mirror", @@ -50,23 +37,26 @@ func NewAnalyzer() *analysis.Analyzer { } func (a *analyzer) run(pass *analysis.Pass) (interface{}, error) { - issues := []*analysis.Diagnostic{} - // --- Read the flags -------------------------------------------------------- - a.once.Do(a.setup(pass.Analyzer.Flags)) + // --- Setup ----------------------------------------------------------------- + + check := checker.New( + BytesFunctions, BytesBufferMethods, + RegexpFunctions, RegexpRegexpMethods, + StringFunctions, StringsBuilderMethods, + BufioMethods, HTTPTestMethods, + OsFileMethods, MaphashMethods, + UTF8Functions, + ) - ins, _ := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) + check.Type = checker.WrapType(pass.TypesInfo) + check.Print = checker.WrapPrint(pass.Fset) - // fmt.Println(pass.Pkg.Path()) - var ( - imports = checker.Load(pass.Fset, ins) - fileCheckers = make(map[string][]*checker.Checker) - debugFn = debugNoOp - fileImports []checker.Import - ) + violations := []*checker.Violation{} - if a.withDebug { - debugFn = debug(pass.Fset) - } + a.once.Do(a.setup(pass.Analyzer.Flags)) // loading flags info + + ins, _ := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) + imports := checker.Load(pass.Fset, ins) // --- Preorder Checker ------------------------------------------------------ ins.Preorder([]ast.Node{(*ast.CallExpr)(nil)}, func(n ast.Node) { @@ -77,51 +67,69 @@ func (a *analyzer) run(pass *analysis.Pass) (interface{}, error) { return } - fileImports = imports.Lookup(fileName) - if _, ok := fileCheckers[fileName]; !ok { - fileCheckers[fileName] = a.filter(fileImports) - } + // ------------------------------------------------------------------------- + switch expr := callExpr.Fun.(type) { + // NOTE(butuzov): Regular calls (`*ast.SelectorExpr`) like strings.HasPrefix + // or re.Match are handled by this check + case *ast.SelectorExpr: - for i := range fileCheckers[fileName] { - c := fileCheckers[fileName][i].With(pass, fileImports, debugFn) - if violation := c.Check(callExpr); violation != nil { - issues = append(issues, violation.Diagnostic(n.Pos(), n.End())) + x, ok := expr.X.(*ast.Ident) + if !ok { return } - } - }) - // --- Reporting issues ------------------------------------------------------ - for _, issue := range issues { - pass.Report(*issue) - } - - return nil, nil -} + // TODO(butuzov): Add check for the ast.ParenExpr in e.Fun so we can + // target the constructions like this (and other calls) + // ----------------------------------------------------------------------- + // Example: + // (&maphash.Hash{}).Write([]byte("foobar")) + // ----------------------------------------------------------------------- + + // Case 1: Is this is a function call? + pkgName, name := x.Name, expr.Sel.Name + if pkg, ok := imports.Lookup(fileName, pkgName); ok { + if v := check.Match(pkg, name); v != nil { + if args, found := check.Handle(v, callExpr); found { + violations = append(violations, v.With(check.Print(expr.X), callExpr, args)) + } + return + } + } -func (a *analyzer) register(c *checker.Checker) { - a.checkers[c.Package] = c -} + // Case 2: Is this is a method call? + tv := pass.TypesInfo.Types[expr.X] + if !tv.IsValue() || tv.Type == nil { + return + } -func (a *analyzer) filter(imports []checker.Import) []*checker.Checker { - out := make([]*checker.Checker, 0, len(a.checkers)) - seen := make(map[string]bool, len(imports)) + pkgStruct, name := cleanAsterisk(tv.Type.String()), expr.Sel.Name + if v := check.Match(pkgStruct, name); v != nil { + if args, found := check.Handle(v, callExpr); found { + violations = append(violations, v.With(check.Print(expr.X), callExpr, args)) + } + return + } - var key string - for i := range imports { - key = imports[i].Pkg + case *ast.Ident: + // NOTE(butuzov): Special case of "." imported packages, only functions. - if _, ok := seen[key]; ok { - continue + if pkg, ok := imports.Lookup(fileName, "."); ok { + if v := check.Match(pkg, expr.Name); v != nil { + if args, found := check.Handle(v, callExpr); found { + violations = append(violations, v.With(nil, callExpr, args)) + } + return + } + } } - seen[key] = true + }) - if _, ok := a.checkers[key]; ok { - out = append(out, a.checkers[key]) - } + // --- Reporting violations via issues --------------------------------------- + for _, violation := range violations { + pass.Report(violation.Issue(pass.Fset)) } - return out + return nil, nil } func (a *analyzer) setup(f flag.FlagSet) func() { @@ -137,3 +145,11 @@ func flags() flag.FlagSet { set.Bool("with-debug", false, "debug linter run (development only)") return *set } + +func cleanAsterisk(s string) string { + if strings.HasPrefix(s, "*") { + return s[1:] + } + + return s +} diff --git a/checkers_bufio.go b/checkers_bufio.go index 2e52921..ed6d153 100644 --- a/checkers_bufio.go +++ b/checkers_bufio.go @@ -2,36 +2,31 @@ package mirror import "github.com/butuzov/mirror/internal/checker" -func newBufioChecker() *checker.Checker { - c := checker.New("bufio") - c.Methods["bufio.Writer"] = BufioMethods +var BufioMethods = []checker.Violation{ + { // (*bufio.Writer).Write + Targets: checker.Bytes, + Type: checker.Method, + Package: "bufio", + Struct: "Writer", + Caller: "Write", + Args: []int{0}, + AltCaller: "WriteString", - return c -} - -var BufioMethods = map[string]checker.Violation{ - "Write": { - Type: checker.Method, - Message: "avoid allocations with (*bufio.Writer).WriteString", - Args: []int{0}, - StringTargeted: false, - Alternative: checker.Alternative{ - Method: "WriteString", - }, Generate: &checker.Generate{ PreCondition: `b := bufio.Writer{}`, Pattern: `Write($0)`, Returns: 2, }, }, - "WriteString": { - Type: checker.Method, - Message: "avoid allocations with (*bufio.Writer).Write", - Args: []int{0}, - StringTargeted: true, - Alternative: checker.Alternative{ - Method: "Write", - }, + { // (*bufio.Writer).WriteString + Type: checker.Method, + Targets: checker.Strings, + Package: "bufio", + Struct: "Writer", + Caller: "WriteString", + Args: []int{0}, + AltCaller: "Write", + Generate: &checker.Generate{ PreCondition: `b := bufio.Writer{}`, Pattern: `WriteString($0)`, diff --git a/checkers_bytes.go b/checkers_bytes.go index 9ad9cbc..c544368 100644 --- a/checkers_bytes.go +++ b/checkers_bytes.go @@ -2,279 +2,268 @@ package mirror import "github.com/butuzov/mirror/internal/checker" -func newBytesChecker() *checker.Checker { - c := checker.New("bytes") - c.Functions = BytesFunctions - c.Methods["bytes.Buffer"] = BytesBufferMethods - - return c -} - var ( - BytesFunctions = map[string]checker.Violation{ - "NewBuffer": { - Type: checker.Function, - Message: "avoid allocations with bytes.NewBufferString", - Args: []int{0}, - StringTargeted: false, - Alternative: checker.Alternative{ - Package: "bytes", - Function: "NewBufferString", - }, + BytesFunctions = []checker.Violation{ + { // bytes.NewBuffer + Targets: checker.Bytes, + Type: checker.Function, + Package: "bytes", + Caller: "NewBuffer", + Args: []int{0}, + AltCaller: "NewBufferString", + Generate: &checker.Generate{ Pattern: `NewBuffer($0)`, Returns: 1, }, }, - "NewBufferString": { - Type: checker.Function, - Message: "avoid allocations with bytes.NewBuffer", - Args: []int{0}, - StringTargeted: true, - Alternative: checker.Alternative{ - Package: "bytes", - Function: "NewBuffer", - }, + { // bytes.NewBufferString + Targets: checker.Strings, + Type: checker.Function, + Package: "bytes", + Caller: "NewBufferString", + Args: []int{0}, + AltCaller: "NewBuffer", + Generate: &checker.Generate{ Pattern: `NewBufferString($0)`, Returns: 1, }, }, - "Compare": { - Type: checker.Function, - Message: "avoid allocations with strings.Compare", - Args: []int{0, 1}, - StringTargeted: false, - Alternative: checker.Alternative{ - Package: "strings", - Function: "Compare", - }, + { // bytes.Compare: + Targets: checker.Bytes, + Type: checker.Function, + Package: "bytes", + Caller: "Compare", + Args: []int{0, 1}, + AltPackage: "strings", + AltCaller: "Compare", + Generate: &checker.Generate{ Pattern: `Compare($0, $1)`, Returns: 1, }, }, - "Contains": { - Type: checker.Function, - Message: "avoid allocations with strings.Contains", - Args: []int{0, 1}, - StringTargeted: false, - Alternative: checker.Alternative{ - Package: "strings", - Function: "Contains", - }, + { // bytes.Contains: + Targets: checker.Bytes, + Type: checker.Function, + Package: "bytes", + Caller: "Contains", + Args: []int{0, 1}, + AltPackage: "strings", + AltCaller: "Contains", + Generate: &checker.Generate{ Pattern: `Contains($0, $1)`, Returns: 1, }, }, - "ContainsAny": { - Type: checker.Function, - Message: "avoid allocations with strings.ContainsAny", - Args: []int{0}, - StringTargeted: false, - Alternative: checker.Alternative{ - Package: "strings", - Function: "ContainsAny", - }, + { // bytes.ContainsAny + Targets: checker.Bytes, + Type: checker.Function, + Package: "bytes", + Caller: "ContainsAny", + Args: []int{0}, + AltPackage: "strings", + AltCaller: "ContainsAny", + Generate: &checker.Generate{ Pattern: `ContainsAny($0, "f")`, Returns: 1, }, }, - "ContainsRune": { - Type: checker.Function, - Message: "avoid allocations with strings.ContainsRune", - Args: []int{0}, - StringTargeted: false, - Alternative: checker.Alternative{ - Package: "strings", - Function: "ContainsRune", - }, + { // bytes.ContainsRune + Targets: checker.Bytes, + Type: checker.Function, + Package: "bytes", + Caller: "ContainsRune", + Args: []int{0}, + AltPackage: "strings", + AltCaller: "ContainsRune", + Generate: &checker.Generate{ Pattern: `ContainsRune($0, 'ф')`, Returns: 1, }, }, - "Count": { - Type: checker.Function, - Message: "avoid allocations with strings.Count", - Args: []int{0, 1}, - StringTargeted: false, - Alternative: checker.Alternative{ - Package: "strings", - Function: "Count", - }, + { // bytes.Count + Targets: checker.Bytes, + Type: checker.Function, + Package: "bytes", + Caller: "Count", + Args: []int{0, 1}, + AltPackage: "strings", + AltCaller: "Count", + Generate: &checker.Generate{ Pattern: `Count($0, $1)`, Returns: 1, }, }, - "EqualFold": { - Type: checker.Function, - Message: "avoid allocations with strings.EqualFold", - Args: []int{0, 1}, - StringTargeted: false, - Alternative: checker.Alternative{ - Package: "strings", - Function: "EqualFold", - }, + { // bytes.EqualFold + Targets: checker.Bytes, + Type: checker.Function, + Package: "bytes", + Caller: "EqualFold", + Args: []int{0, 1}, + AltPackage: "strings", + AltCaller: "EqualFold", + Generate: &checker.Generate{ Pattern: `EqualFold($0, $1)`, Returns: 1, }, }, - "HasPrefix": { - Type: checker.Function, - Message: "avoid allocations with strings.HasPrefix", - Args: []int{0, 1}, - StringTargeted: false, - Alternative: checker.Alternative{ - Package: "strings", - Function: "HasPrefix", - }, + { // bytes.HasPrefix + Targets: checker.Bytes, + Type: checker.Function, + Package: "bytes", + Caller: "HasPrefix", + Args: []int{0, 1}, + AltPackage: "strings", + AltCaller: "HasPrefix", + Generate: &checker.Generate{ Pattern: `HasPrefix($0, $1)`, Returns: 1, }, }, - "HasSuffix": { - Type: checker.Function, - Message: "avoid allocations with strings.HasSuffix", - Args: []int{0, 1}, - StringTargeted: false, - Alternative: checker.Alternative{ - Package: "strings", - Function: "HasSuffix", - }, + { // bytes.HasSuffix + Targets: checker.Bytes, + Type: checker.Function, + Package: "bytes", + Caller: "HasSuffix", + Args: []int{0, 1}, + AltPackage: "strings", + AltCaller: "HasSuffix", + Generate: &checker.Generate{ Pattern: `HasSuffix($0, $1)`, Returns: 1, }, }, - "Index": { - Type: checker.Function, - Message: "avoid allocations with strings.Index", - Args: []int{0, 1}, - StringTargeted: false, - Alternative: checker.Alternative{ - Package: "strings", - Function: "Index", - }, + { // bytes.Index + Targets: checker.Bytes, + Type: checker.Function, + Package: "bytes", + Caller: "Index", + Args: []int{0, 1}, + AltPackage: "strings", + AltCaller: "Index", + Generate: &checker.Generate{ Pattern: `Index($0, $1)`, Returns: 1, }, }, - "IndexAny": { - Type: checker.Function, - Message: "avoid allocations with strings.IndexAny", - Args: []int{0}, - StringTargeted: false, - Alternative: checker.Alternative{ - Package: "strings", - Function: "IndexAny", - }, + { // bytes.IndexAny + Targets: checker.Bytes, + Type: checker.Function, + Package: "bytes", + Caller: "IndexAny", + Args: []int{0}, + AltPackage: "strings", + AltCaller: "IndexAny", + Generate: &checker.Generate{ Pattern: `IndexAny($0, "f")`, Returns: 1, }, }, - "IndexByte": { - Type: checker.Function, - Message: "avoid allocations with strings.IndexByte", - Args: []int{0}, - StringTargeted: false, - Alternative: checker.Alternative{ - Package: "strings", - Function: "IndexByte", - }, + { // bytes.IndexByte + Targets: checker.Bytes, + Type: checker.Function, + Package: "bytes", + Caller: "IndexByte", + Args: []int{0}, + AltPackage: "strings", + AltCaller: "IndexByte", + Generate: &checker.Generate{ Pattern: `IndexByte($0, 'f')`, Returns: 1, }, }, - "IndexFunc": { - Type: checker.Function, - Message: "avoid allocations with strings.IndexFunc", - Args: []int{0}, - StringTargeted: false, - Alternative: checker.Alternative{ - Package: "strings", - Function: "IndexFunc", - }, + { // bytes.IndexFunc + Targets: checker.Bytes, + Type: checker.Function, + Package: "bytes", + Caller: "IndexFunc", + Args: []int{0}, + AltPackage: "strings", + AltCaller: "IndexFunc", + Generate: &checker.Generate{ Pattern: `IndexFunc($0, func(rune) bool {return true })`, Returns: 1, }, }, - "IndexRune": { - Type: checker.Function, - Message: "avoid allocations with strings.IndexRune", - Args: []int{0}, - StringTargeted: false, - Alternative: checker.Alternative{ - Package: "strings", - Function: "IndexRune", - }, + { // bytes.IndexRune + Targets: checker.Bytes, + Type: checker.Function, + Package: "bytes", + Caller: "IndexRune", + Args: []int{0}, + AltPackage: "strings", + AltCaller: "IndexRune", + Generate: &checker.Generate{ Pattern: `IndexRune($0, rune('ф'))`, Returns: 1, }, }, - "LastIndex": { - Type: checker.Function, - Message: "avoid allocations with strings.LastIndex", - Args: []int{0, 1}, - StringTargeted: false, - Alternative: checker.Alternative{ - Package: "strings", - Function: "LastIndex", - }, + { // bytes.LastIndex + Targets: checker.Bytes, + Type: checker.Function, + Package: "bytes", + Caller: "LastIndex", + Args: []int{0, 1}, + AltPackage: "strings", + AltCaller: "LastIndex", + Generate: &checker.Generate{ Pattern: `LastIndex($0, $1)`, Returns: 1, }, }, - "LastIndexAny": { - Type: checker.Function, - Message: "avoid allocations with strings.LastIndexAny", - Args: []int{0}, - StringTargeted: false, - Alternative: checker.Alternative{ - Package: "strings", - Function: "LastIndexAny", - }, + { // bytes.LastIndexAny + Targets: checker.Bytes, + Type: checker.Function, + Package: "bytes", + Caller: "LastIndexAny", + Args: []int{0}, + AltPackage: "strings", + AltCaller: "LastIndexAny", + Generate: &checker.Generate{ Pattern: `LastIndexAny($0, "ф")`, Returns: 1, }, }, + { // bytes.LastIndexByte + Targets: checker.Bytes, + Type: checker.Function, + Package: "bytes", + Caller: "LastIndexByte", + Args: []int{0}, + AltPackage: "strings", + AltCaller: "LastIndexByte", - "LastIndexByte": { - Type: checker.Function, - Message: "avoid allocations with strings.LastIndexByte", - Args: []int{0}, - StringTargeted: false, - Alternative: checker.Alternative{ - Package: "strings", - Function: "LastIndexByte", - }, Generate: &checker.Generate{ Pattern: `LastIndexByte($0, 'f')`, Returns: 1, }, }, - "LastIndexFunc": { - Type: checker.Function, - Message: "avoid allocations with strings.LastIndexAny", - Args: []int{0}, - StringTargeted: false, - Alternative: checker.Alternative{ - Package: "strings", - Function: "LastIndexAny", - }, + { // bytes.LastIndexFunc + Targets: checker.Bytes, + Type: checker.Function, + Package: "bytes", + Caller: "LastIndexFunc", + Args: []int{0}, + AltPackage: "strings", + AltCaller: "LastIndexFunc", + Generate: &checker.Generate{ Pattern: `LastIndexFunc($0, func(rune) bool {return true })`, Returns: 1, @@ -282,29 +271,31 @@ var ( }, } - BytesBufferMethods = map[string]checker.Violation{ - "Write": { - Type: checker.Method, - Message: "avoid allocations with (*bytes.Buffer).WriteString", - Args: []int{0}, - StringTargeted: false, - Alternative: checker.Alternative{ - Method: "WriteString", - }, + BytesBufferMethods = []checker.Violation{ + { // (*bytes.Buffer).Write + Targets: checker.Bytes, + Type: checker.Method, + Package: "bytes", + Struct: "Buffer", + Caller: "Write", + Args: []int{0}, + AltCaller: "WriteString", + Generate: &checker.Generate{ PreCondition: `bb := bytes.Buffer{}`, Pattern: `Write($0)`, Returns: 2, }, }, - "WriteString": { - Type: checker.Method, - Message: "avoid allocations with (*bytes.Buffer).Write", - Args: []int{0}, - StringTargeted: true, - Alternative: checker.Alternative{ - Method: "Write", - }, + { // (*bytes.Buffer).WriteString + Targets: checker.Strings, + Type: checker.Method, + Package: "bytes", + Struct: "Buffer", + Caller: "WriteString", + Args: []int{0}, + AltCaller: "Write", + Generate: &checker.Generate{ PreCondition: `bb := bytes.Buffer{}`, Pattern: `WriteString($0)`, diff --git a/checkers_httptest.go b/checkers_httptest.go index 644d1cb..ae67509 100644 --- a/checkers_httptest.go +++ b/checkers_httptest.go @@ -2,36 +2,31 @@ package mirror import "github.com/butuzov/mirror/internal/checker" -func newHTTPTestChecker() *checker.Checker { - c := checker.New("net/http/httptest") - c.Methods["net/http/httptest.ResponseRecorder"] = HTTPTestMethods +var HTTPTestMethods = []checker.Violation{ + { // (*net/http/httptest.ResponseRecorder).Write + Targets: checker.Bytes, + Type: checker.Method, + Package: "net/http/httptest", + Struct: "ResponseRecorder", + Caller: "Write", + Args: []int{0}, + AltCaller: "WriteString", - return c -} - -var HTTPTestMethods = map[string]checker.Violation{ - "Write": { - Type: checker.Method, - Message: "avoid allocations with (*httptest.ResponseRecorder).WriteString", - Args: []int{0}, - StringTargeted: false, - Alternative: checker.Alternative{ - Method: "WriteString", - }, Generate: &checker.Generate{ PreCondition: `h := httptest.ResponseRecorder{}`, Pattern: `Write($0)`, Returns: 2, }, }, - "WriteString": { - Type: checker.Method, - Message: "avoid allocations with (*httptest.ResponseRecorder).Write", - Args: []int{0}, - StringTargeted: true, - Alternative: checker.Alternative{ - Method: "Write", - }, + { // (*net/http/httptest.ResponseRecorder).WriteString + Targets: checker.Strings, + Type: checker.Method, + Package: "net/http/httptest", + Struct: "ResponseRecorder", + Caller: "WriteString", + Args: []int{0}, + AltCaller: "Write", + Generate: &checker.Generate{ PreCondition: `h := httptest.ResponseRecorder{}`, Pattern: `WriteString($0)`, diff --git a/checkers_maphash.go b/checkers_maphash.go index 196680a..4d184d2 100644 --- a/checkers_maphash.go +++ b/checkers_maphash.go @@ -2,36 +2,31 @@ package mirror import "github.com/butuzov/mirror/internal/checker" -func newMaphashChecker() *checker.Checker { - c := checker.New("hash/maphash") - c.Methods["hash/maphash.Hash"] = MaphashMethods +var MaphashMethods = []checker.Violation{ + { // (*hash/maphash).Write + Targets: checker.Bytes, + Type: checker.Method, + Package: "hash/maphash", + Struct: "Hash", + Caller: "Write", + Args: []int{0}, + AltCaller: "WriteString", - return c -} - -var MaphashMethods = map[string]checker.Violation{ - "Write": { - Type: checker.Method, - Message: "avoid allocations with (*maphash.Hash).WriteString", - Args: []int{0}, - StringTargeted: false, - Alternative: checker.Alternative{ - Method: "WriteString", - }, Generate: &checker.Generate{ PreCondition: `h := maphash.Hash{}`, Pattern: `Write($0)`, Returns: 2, }, }, - "WriteString": { - Type: checker.Method, - Message: "avoid allocations with (*maphash.Hash).Write", - Args: []int{0}, - StringTargeted: true, - Alternative: checker.Alternative{ - Method: "Write", - }, + { // (*hash/maphash).WriteString + Targets: checker.Strings, + Type: checker.Method, + Package: "hash/maphash", + Struct: "Hash", + Caller: "WriteString", + Args: []int{0}, + AltCaller: "Write", + Generate: &checker.Generate{ PreCondition: `h := maphash.Hash{}`, Pattern: `WriteString($0)`, diff --git a/checkers_os.go b/checkers_os.go index acb1ce9..09f5a18 100644 --- a/checkers_os.go +++ b/checkers_os.go @@ -2,36 +2,31 @@ package mirror import "github.com/butuzov/mirror/internal/checker" -func newOsChecker() *checker.Checker { - c := checker.New("os") - c.Methods["os.File"] = OsFileMethods +var OsFileMethods = []checker.Violation{ + { // (*os.File).Write + Targets: checker.Bytes, + Type: checker.Method, + Package: "os", + Struct: "File", + Caller: "Write", + Args: []int{0}, + AltCaller: "WriteString", - return c -} - -var OsFileMethods = map[string]checker.Violation{ - "Write": { - Type: checker.Method, - Message: "avoid allocations with (*os.File).WriteString", - Args: []int{0}, - StringTargeted: false, - Alternative: checker.Alternative{ - Method: "WriteString", - }, Generate: &checker.Generate{ PreCondition: `f := &os.File{}`, Pattern: `Write($0)`, Returns: 2, }, }, - "WriteString": { - Type: checker.Method, - Message: "avoid allocations with (*os.File).Write", - Args: []int{0}, - StringTargeted: true, - Alternative: checker.Alternative{ - Method: "Write", - }, + { // (*os.File).WriteString + Targets: checker.Strings, + Type: checker.Method, + Package: "os", + Struct: "File", + Caller: "WriteString", + Args: []int{0}, + AltCaller: "Write", + Generate: &checker.Generate{ PreCondition: `f := &os.File{}`, Pattern: `WriteString($0)`, diff --git a/checkers_regexp.go b/checkers_regexp.go index 3e35ea4..17175e0 100644 --- a/checkers_regexp.go +++ b/checkers_regexp.go @@ -2,39 +2,29 @@ package mirror import "github.com/butuzov/mirror/internal/checker" -func newRegexpChecker() *checker.Checker { - c := checker.New("regexp") - c.Functions = RegexpFunctions - c.Methods["regexp.Regexp"] = RegexpRegexpMethods - - return c -} - var ( - RegexpFunctions = map[string]checker.Violation{ - "Match": { - Type: checker.Function, - Message: "avoid allocations with regexp.MatchString", - Args: []int{1}, - StringTargeted: false, - Alternative: checker.Alternative{ - Package: "regexp", - Function: "MatchString", - }, + RegexpFunctions = []checker.Violation{ + { // regexp.Match + Targets: checker.Bytes, + Type: checker.Function, + Package: "regexp", + Caller: "Match", + Args: []int{1}, + AltCaller: "MatchString", + Generate: &checker.Generate{ Pattern: `Match("foo", $0)`, Returns: 2, }, }, - "MatchString": { - Type: checker.Function, - Message: "avoid allocations with regexp.Match", - Args: []int{1}, - StringTargeted: true, - Alternative: checker.Alternative{ - Package: "regexp", - Function: "Match", - }, + { // regexp.MatchString + Targets: checker.Strings, + Type: checker.Function, + Package: "regexp", + Caller: "MatchString", + Args: []int{1}, + AltCaller: "Match", + Generate: &checker.Generate{ Pattern: `MatchString("foo", $0)`, Returns: 2, @@ -42,144 +32,151 @@ var ( }, } - // As you see we are not using all of the regexp method because - // nes we missing return concrete types (bytes or strings) - // which most probably was intentional. - RegexpRegexpMethods = map[string]checker.Violation{ - "Match": { - Type: checker.Method, - Message: "avoid allocations with (*regexp.Regexp).MatchString", - Args: []int{0}, - StringTargeted: false, - Alternative: checker.Alternative{ - Method: "MatchString", - }, + RegexpRegexpMethods = []checker.Violation{ + { // (*regexp.Regexp).Match + Targets: checker.Bytes, + Type: checker.Method, + Package: "regexp", + Struct: "Regexp", + Caller: "Match", + Args: []int{0}, + AltCaller: "MatchString", + Generate: &checker.Generate{ PreCondition: `re := regexp.MustCompile(".*")`, Pattern: `Match($0)`, Returns: 1, }, }, - "MatchString": { - Type: checker.Method, - Message: "avoid allocations with (*regexp.Regexp).Match", - Args: []int{0}, - StringTargeted: true, - Alternative: checker.Alternative{ - Method: "Match", - }, + { // (*regexp.Regexp).MatchString + Targets: checker.Strings, + Type: checker.Method, + Package: "regexp", + Struct: "Regexp", + Caller: "MatchString", + Args: []int{0}, + AltCaller: "Match", + Generate: &checker.Generate{ PreCondition: `re := regexp.MustCompile(".*")`, Pattern: `MatchString($0)`, Returns: 1, }, }, - "FindAllIndex": { - Type: checker.Method, - Message: "avoid allocations with (*regexp.Regexp).FindAllStringIndex", - Args: []int{0}, - StringTargeted: false, - Alternative: checker.Alternative{ - Method: "FindAllStringIndex", - }, + { // (*regexp.Regexp).FindAllIndex + Targets: checker.Bytes, + Type: checker.Method, + Package: "regexp", + Struct: "Regexp", + Caller: "FindAllIndex", + Args: []int{0}, + AltCaller: "FindAllStringIndex", + Generate: &checker.Generate{ PreCondition: `re := regexp.MustCompile(".*")`, Pattern: `FindAllIndex($0, 1)`, Returns: 1, }, }, - "FindAllStringIndex": { - Type: checker.Method, - Message: "avoid allocations with (*regexp.Regexp).FindAllIndex", - Args: []int{0}, - StringTargeted: true, - Alternative: checker.Alternative{ - Method: "FindAllIndex", - }, + { // (*regexp.Regexp).FindAllStringIndex + Targets: checker.Strings, + Type: checker.Method, + Package: "regexp", + Struct: "Regexp", + Caller: "FindAllStringIndex", + Args: []int{0}, + AltCaller: "FindAllIndex", + Generate: &checker.Generate{ PreCondition: `re := regexp.MustCompile(".*")`, Pattern: `FindAllStringIndex($0, 1)`, Returns: 1, }, }, - "FindAllSubmatchIndex": { - Type: checker.Method, - Message: "avoid allocations with (*regexp.Regexp).FindAllStringSubmatchIndex", - Args: []int{0}, - StringTargeted: false, - Alternative: checker.Alternative{ - Method: "FindAllStringSubmatchIndex", - }, + { // (*regexp.Regexp).FindAllSubmatchIndex + Targets: checker.Bytes, + Type: checker.Method, + Package: "regexp", + Struct: "Regexp", + Caller: "FindAllSubmatchIndex", + Args: []int{0}, + AltCaller: "FindAllStringSubmatchIndex", + Generate: &checker.Generate{ PreCondition: `re := regexp.MustCompile(".*")`, Pattern: `FindAllSubmatchIndex($0, 1)`, Returns: 1, }, - }, // - "FindAllStringSubmatchIndex": { - Type: checker.Method, - Message: "avoid allocations with (*regexp.Regexp).FindAllSubmatchIndex", - Args: []int{0}, - StringTargeted: true, - Alternative: checker.Alternative{ - Method: "FindAllSubmatchIndex", - }, + }, + { // (*regexp.Regexp).FindAllStringSubmatchIndex + Targets: checker.Strings, + Type: checker.Method, + Package: "regexp", + Struct: "Regexp", + Caller: "FindAllStringSubmatchIndex", + Args: []int{0}, + AltCaller: "FindAllSubmatchIndex", + Generate: &checker.Generate{ PreCondition: `re := regexp.MustCompile(".*")`, Pattern: `FindAllStringSubmatchIndex($0, 1)`, Returns: 1, }, }, - "FindIndex": { - Type: checker.Method, - Message: "avoid allocations with (*regexp.Regexp).FindStringIndex", - Args: []int{0}, - StringTargeted: false, - Alternative: checker.Alternative{ - Method: "FindStringIndex", - }, + { // (*regexp.Regexp).FindIndex + Targets: checker.Bytes, + Type: checker.Method, + Package: "regexp", + Struct: "Regexp", + Caller: "FindIndex", + Args: []int{0}, + AltCaller: "FindStringIndex", + Generate: &checker.Generate{ PreCondition: `re := regexp.MustCompile(".*")`, Pattern: `FindIndex($0)`, Returns: 1, }, }, - "FindStringIndex": { - Type: checker.Method, - Message: "avoid allocations with (*regexp.Regexp).FindStringIndex", - Args: []int{0}, - StringTargeted: true, - Alternative: checker.Alternative{ - Method: "FindIndex", - }, + { // (*regexp.Regexp).FindStringIndex + Targets: checker.Strings, + Type: checker.Method, + Package: "regexp", + Struct: "Regexp", + Caller: "FindStringIndex", + Args: []int{0}, + AltCaller: "FindIndex", + Generate: &checker.Generate{ PreCondition: `re := regexp.MustCompile(".*")`, Pattern: `FindStringIndex($0)`, Returns: 1, }, }, - "FindSubmatchIndex": { - Type: checker.Method, - Message: "avoid allocations with (*regexp.Regexp).FindStringSubmatchIndex", - Args: []int{0}, - StringTargeted: false, - Alternative: checker.Alternative{ - Method: "FindStringSubmatchIndex", - }, + { // (*regexp.Regexp).FindSubmatchIndex + Targets: checker.Bytes, + Type: checker.Method, + Package: "regexp", + Struct: "Regexp", + Caller: "FindSubmatchIndex", + Args: []int{0}, + AltCaller: "FindStringSubmatchIndex", + Generate: &checker.Generate{ PreCondition: `re := regexp.MustCompile(".*")`, Pattern: `FindSubmatchIndex($0)`, Returns: 1, }, }, - "FindStringSubmatchIndex": { - Type: checker.Method, - Message: "avoid allocations with (*regexp.Regexp).FindSubmatchIndex", - Args: []int{0}, - StringTargeted: true, - Alternative: checker.Alternative{ - Method: "FindSubmatchIndex", - }, + { // (*regexp.Regexp).FindStringSubmatchIndex + Targets: checker.Strings, + Type: checker.Method, + Package: "regexp", + Struct: "Regexp", + Caller: "FindStringSubmatchIndex", + Args: []int{0}, + AltCaller: "FindSubmatchIndex", + Generate: &checker.Generate{ PreCondition: `re := regexp.MustCompile(".*")`, Pattern: `FindStringSubmatchIndex($0)`, diff --git a/checkers_strings.go b/checkers_strings.go index c29c244..456c06d 100644 --- a/checkers_strings.go +++ b/checkers_strings.go @@ -2,249 +2,241 @@ package mirror import "github.com/butuzov/mirror/internal/checker" -func newStringsChecker() *checker.Checker { - c := checker.New("strings") - c.Functions = StringFunctions - c.Methods["strings.Builder"] = StringsBuilderMethods - - return c -} - var ( - StringFunctions = map[string]checker.Violation{ - "Compare": { - Type: checker.Function, - Message: "avoid allocations with bytes.Compare", - Args: []int{0, 1}, - StringTargeted: true, - Alternative: checker.Alternative{ - Package: `bytes`, - Function: `Compare`, - }, + StringFunctions = []checker.Violation{ + { // strings.Compare + Targets: checker.Strings, + Type: checker.Function, + Package: "strings", + Caller: "Compare", + Args: []int{0, 1}, + AltPackage: "bytes", + AltCaller: "Compare", + Generate: &checker.Generate{ Pattern: `Compare($0,$1)`, Returns: 1, }, }, - "Contains": { - Type: checker.Function, - Message: "avoid allocations with bytes.Contains", - Args: []int{0, 1}, - StringTargeted: true, - Alternative: checker.Alternative{ - Package: `bytes`, - Function: `Contains`, - }, + { // strings.Contains + Targets: checker.Strings, + Type: checker.Function, + Package: "strings", + Caller: "Contains", + Args: []int{0, 1}, + AltPackage: "bytes", + AltCaller: "Contains", + Generate: &checker.Generate{ Pattern: `Contains($0,$1)`, Returns: 1, }, }, - "ContainsAny": { - Type: checker.Function, - Message: "avoid allocations with bytes.ContainsAny", - Args: []int{0}, - StringTargeted: true, - Alternative: checker.Alternative{ - Package: `bytes`, - Function: `ContainsAny`, - }, + { // strings.ContainsAny + Targets: checker.Strings, + Type: checker.Function, + Package: "strings", + Caller: "ContainsAny", + Args: []int{0}, + AltPackage: "bytes", + AltCaller: "ContainsAny", + Generate: &checker.Generate{ Pattern: `ContainsAny($0,"foobar")`, Returns: 1, }, }, - "ContainsRune": { - Type: checker.Function, - Message: "avoid allocations with bytes.ContainsRune", - Args: []int{0}, - StringTargeted: true, - Alternative: checker.Alternative{ - Package: `bytes`, - Function: `ContainsRune`, - }, + { // strings.ContainsRune + Targets: checker.Strings, + Type: checker.Function, + Package: "strings", + Caller: "ContainsRune", + Args: []int{0}, + AltPackage: "bytes", + AltCaller: "ContainsRune", + Generate: &checker.Generate{ Pattern: `ContainsRune($0,'ф')`, Returns: 1, }, }, - "Count": { - Type: checker.Function, - Message: "avoid allocations with bytes.Count", - Args: []int{0, 1}, - StringTargeted: true, - Alternative: checker.Alternative{ - Package: `bytes`, - Function: `Count`, - }, + { // strings.Count + Targets: checker.Strings, + Type: checker.Function, + Package: "strings", + Caller: "Count", + Args: []int{0, 1}, + AltPackage: "bytes", + AltCaller: "Count", + Generate: &checker.Generate{ Pattern: `Count($0, $1)`, Returns: 1, }, }, - "EqualFold": { - Type: checker.Function, - Message: "avoid allocations with bytes.EqualFold", - Args: []int{0, 1}, - StringTargeted: true, - Alternative: checker.Alternative{ - Package: `bytes`, - Function: `EqualFold`, - }, + { // strings.EqualFold + Targets: checker.Strings, + Type: checker.Function, + Package: "strings", + Caller: "EqualFold", + Args: []int{0, 1}, + AltPackage: "bytes", + AltCaller: "EqualFold", + Generate: &checker.Generate{ Pattern: `EqualFold($0,$1)`, Returns: 1, }, }, - "HasPrefix": { - Type: checker.Function, - Message: "avoid allocations with bytes.HasPrefix", - Args: []int{0, 1}, - StringTargeted: true, - Alternative: checker.Alternative{ - Package: `bytes`, - Function: `HasPrefix`, - }, + { // strings.HasPrefix + Targets: checker.Strings, + Type: checker.Function, + Package: "strings", + Caller: "HasPrefix", + Args: []int{0, 1}, + AltPackage: "bytes", + AltCaller: "HasPrefix", + Generate: &checker.Generate{ Pattern: `HasPrefix($0,$1)`, Returns: 1, }, }, - "HasSuffix": { - Type: checker.Function, - Message: "avoid allocations with bytes.HasSuffix", - Args: []int{0, 1}, - StringTargeted: true, - Alternative: checker.Alternative{ - Package: `bytes`, - Function: `HasSuffix`, - }, + { // strings.HasSuffix + Targets: checker.Strings, + Type: checker.Function, + Package: "strings", + Caller: "HasSuffix", + Args: []int{0, 1}, + AltPackage: "bytes", + AltCaller: "HasSuffix", + Generate: &checker.Generate{ Pattern: `HasSuffix($0,$1)`, Returns: 1, }, }, - "Index": { - Type: checker.Function, - Message: "avoid allocations with bytes.Index", - Args: []int{0, 1}, - StringTargeted: true, - Alternative: checker.Alternative{ - Package: `bytes`, - Function: `Index`, - }, + { // strings.Index + Targets: checker.Strings, + Type: checker.Function, + Package: "strings", + Caller: "Index", + Args: []int{0, 1}, + AltPackage: "bytes", + AltCaller: "Index", + Generate: &checker.Generate{ Pattern: `Index($0,$1)`, Returns: 1, }, }, - "IndexAny": { - Type: checker.Function, - Message: "avoid allocations with bytes.IndexAny", - Args: []int{0}, - StringTargeted: true, - Alternative: checker.Alternative{ - Package: `bytes`, - Function: `IndexAny`, - }, + { // strings.IndexAny + Targets: checker.Strings, + Type: checker.Function, + Package: "strings", + Caller: "IndexAny", + Args: []int{0}, + AltPackage: "bytes", + AltCaller: "IndexAny", + Generate: &checker.Generate{ Pattern: `IndexAny($0, "f")`, Returns: 1, }, }, - "IndexByte": { - Type: checker.Function, - Message: "avoid allocations with bytes.IndexByte", - Args: []int{0}, - StringTargeted: true, - Alternative: checker.Alternative{ - Package: `bytes`, - Function: `IndexByte`, - }, + { // strings.IndexByte + Targets: checker.Strings, + Type: checker.Function, + Package: "strings", + Caller: "IndexByte", + Args: []int{0}, + AltPackage: "bytes", + AltCaller: "IndexByte", + Generate: &checker.Generate{ Pattern: `IndexByte($0, byte('f'))`, Returns: 1, }, }, - "IndexFunc": { - Type: checker.Function, - Message: "avoid allocations with bytes.IndexFunc", - Args: []int{0}, - StringTargeted: true, - Alternative: checker.Alternative{ - Package: `bytes`, - Function: `IndexFunc`, - }, + { // strings.IndexFunc + Targets: checker.Strings, + Type: checker.Function, + Package: "strings", + Caller: "IndexFunc", + Args: []int{0}, + AltPackage: "bytes", + AltCaller: "IndexFunc", + Generate: &checker.Generate{ Pattern: `IndexFunc($0,func(r rune) bool { return true })`, Returns: 1, }, }, - "IndexRune": { - Type: checker.Function, - Message: "avoid allocations with bytes.IndexRune", - Args: []int{0}, - StringTargeted: true, - Alternative: checker.Alternative{ - Package: `bytes`, - Function: `IndexRune`, - }, + { // strings.IndexRune + Targets: checker.Strings, + Type: checker.Function, + Package: "strings", + Caller: "IndexRune", + Args: []int{0}, + AltPackage: "bytes", + AltCaller: "IndexRune", + Generate: &checker.Generate{ Pattern: `IndexRune($0, rune('ф'))`, Returns: 1, }, }, - "LastIndex": { - Type: checker.Function, - Message: "avoid allocations with bytes.LastIndex", - Args: []int{0, 1}, - StringTargeted: true, - Alternative: checker.Alternative{ - Package: `bytes`, - Function: `LastIndex`, - }, + { // strings.LastIndex + Targets: checker.Strings, + Type: checker.Function, + Package: "strings", + Caller: "LastIndex", + Args: []int{0, 1}, + AltPackage: "bytes", + AltCaller: "LastIndex", + Generate: &checker.Generate{ Pattern: `LastIndex($0,$1)`, Returns: 1, }, }, - "LastIndexAny": { - Type: checker.Function, - Message: "avoid allocations with bytes.LastIndexAny", - Args: []int{0}, - StringTargeted: true, - Alternative: checker.Alternative{ - Package: `bytes`, - Function: `LastIndexAny`, - }, + { // strings.LastIndexAny + Targets: checker.Strings, + Type: checker.Function, + Package: "strings", + Caller: "LastIndexAny", + Args: []int{0}, + AltPackage: "bytes", + AltCaller: "LastIndexAny", + Generate: &checker.Generate{ Pattern: `LastIndexAny($0,"f")`, Returns: 1, }, }, - "LastIndexByte": { - Type: checker.Function, - Message: "avoid allocations with bytes.LastIndexByte", - Args: []int{0}, - StringTargeted: true, - Alternative: checker.Alternative{ - Package: `bytes`, - Function: `LastIndexByte`, - }, + { // strings.LastIndexByte + Targets: checker.Strings, + Type: checker.Function, + Package: "strings", + Caller: "LastIndexByte", + Args: []int{0}, + AltPackage: "bytes", + AltCaller: "LastIndexByte", + Generate: &checker.Generate{ Pattern: `LastIndexByte($0, byte('f'))`, Returns: 1, }, }, - "LastIndexFunc": { - Type: checker.Function, - Message: "avoid allocations with bytes.LastIndexAny", - Args: []int{0}, - StringTargeted: true, - Alternative: checker.Alternative{ - Package: `bytes`, - Function: `LastIndexAny`, - }, + { // strings.LastIndexFunc + Targets: checker.Strings, + Type: checker.Function, + Package: "strings", + Caller: "LastIndexFunc", + Args: []int{0}, + AltPackage: "bytes", + AltCaller: "LastIndexFunc", + Generate: &checker.Generate{ Pattern: `LastIndexFunc($0, func(r rune) bool { return true })`, Returns: 1, @@ -252,29 +244,31 @@ var ( }, } - StringsBuilderMethods = map[string]checker.Violation{ - "Write": { - Type: checker.Method, - Message: "avoid allocations with (*strings.Builder).WriteString", - Args: []int{0}, - StringTargeted: false, - Alternative: checker.Alternative{ - Method: `WriteString`, - }, + StringsBuilderMethods = []checker.Violation{ + { // (*strings.Builder).Write + Targets: checker.Bytes, + Type: checker.Method, + Package: "strings", + Struct: "Builder", + Caller: "Write", + Args: []int{0}, + AltCaller: "WriteString", + Generate: &checker.Generate{ PreCondition: `builder := strings.Builder{}`, Pattern: `Write($0)`, Returns: 2, }, }, - "WriteString": { - Type: checker.Method, - Message: "avoid allocations with (*strings.Builder).Write", - Args: []int{0}, - StringTargeted: true, - Alternative: checker.Alternative{ - Method: `Write`, - }, + { // (*strings.Builder).WriteString + Targets: checker.Strings, + Type: checker.Method, + Package: "strings", + Struct: "Builder", + Caller: "WriteString", + Args: []int{0}, + AltCaller: "Write", + Generate: &checker.Generate{ PreCondition: `builder := strings.Builder{}`, Pattern: `WriteString($0)`, diff --git a/checkers_utf8.go b/checkers_utf8.go index f0cce64..e7c4d5b 100644 --- a/checkers_utf8.go +++ b/checkers_utf8.go @@ -2,151 +2,134 @@ package mirror import "github.com/butuzov/mirror/internal/checker" -func newUTF8Checker() *checker.Checker { - c := checker.New("unicode/utf8") - c.Functions = UTF8Functions +var UTF8Functions = []checker.Violation{ + { // utf8.Valid + Type: checker.Function, + Targets: checker.Bytes, + Package: "unicode/utf8", + Caller: "Valid", + Args: []int{0}, + AltCaller: "ValidString", - return c -} - -var UTF8Functions = map[string]checker.Violation{ - "Valid": { - Type: checker.Function, - Message: "avoid allocations with utf8.ValidString", - Args: []int{0}, - StringTargeted: false, - Alternative: checker.Alternative{ - Package: "unicode/utf8", - Function: "ValidString", - }, Generate: &checker.Generate{ Pattern: `Valid($0)`, Returns: 1, }, }, - "ValidString": { - Type: checker.Function, - Message: "avoid allocations with utf8.Valid", - Args: []int{0}, - StringTargeted: true, - Alternative: checker.Alternative{ - Package: "unicode/utf8", - Function: "Valid", - }, + { // utf8.ValidString + Targets: checker.Strings, + Type: checker.Function, + Package: "unicode/utf8", + Caller: "ValidString", + Args: []int{0}, + AltCaller: "Valid", + Generate: &checker.Generate{ Pattern: `ValidString($0)`, Returns: 1, }, }, - "FullRune": { - Type: checker.Function, - Message: "avoid allocations with utf8.FullRuneInString", - Args: []int{0}, - StringTargeted: false, - Alternative: checker.Alternative{ - Package: "unicode/utf8", - Function: "FullRuneInString", - }, + { // utf8.FullRune + Targets: checker.Bytes, + Type: checker.Function, + Package: "unicode/utf8", + Caller: "FullRune", + Args: []int{0}, + AltCaller: "FullRuneInString", + Generate: &checker.Generate{ Pattern: `FullRune($0)`, Returns: 1, }, }, - "FullRuneInString": { - Type: checker.Function, - Message: "avoid allocations with utf8.FullRune", - Args: []int{0}, - StringTargeted: true, - Alternative: checker.Alternative{ - Package: "unicode/utf8", - Function: "FullRune", - }, + { // utf8.FullRuneInString + Targets: checker.Strings, + Type: checker.Function, + Package: "unicode/utf8", + Caller: "FullRuneInString", + Args: []int{0}, + AltCaller: "FullRune", + Generate: &checker.Generate{ Pattern: `FullRuneInString($0)`, Returns: 1, }, }, - "RuneCount": { - Type: checker.Function, - Message: "avoid allocations with utf8.RuneCountInString", - Args: []int{0}, - StringTargeted: false, - Alternative: checker.Alternative{ - Package: "unicode/utf8", - Function: "RuneCountInString", - }, + { // bytes.RuneCount + Targets: checker.Bytes, + Type: checker.Function, + Package: "unicode/utf8", + Caller: "RuneCount", + Args: []int{0}, + AltCaller: "RuneCountInString", + Generate: &checker.Generate{ Pattern: `RuneCount($0)`, Returns: 1, }, }, - "RuneCountInString": { - Type: checker.Function, - Message: "avoid allocations with utf8.RuneCount", - Args: []int{0}, - StringTargeted: true, - Alternative: checker.Alternative{ - Package: "unicode/utf8", - Function: "RuneCount", - }, + { // bytes.RuneCountInString + Targets: checker.Strings, + Type: checker.Function, + Package: "unicode/utf8", + Caller: "RuneCountInString", + Args: []int{0}, + AltCaller: "RuneCount", + Generate: &checker.Generate{ Pattern: `RuneCountInString($0)`, Returns: 1, }, }, - "DecodeLastRune": { - Type: checker.Function, - Message: "avoid allocations with utf8.DecodeLastRuneInString", - Args: []int{0}, - StringTargeted: false, - Alternative: checker.Alternative{ - Package: "unicode/utf8", - Function: "DecodeLastRuneInString", - }, + { // bytes.DecodeLastRune + Targets: checker.Bytes, + Type: checker.Function, + Package: "unicode/utf8", + Caller: "DecodeLastRune", + Args: []int{0}, + AltCaller: "DecodeLastRuneInString", + Generate: &checker.Generate{ Pattern: `DecodeLastRune($0)`, Returns: 2, }, }, - "DecodeLastRuneInString": { - Type: checker.Function, - Message: "avoid allocations with utf8.DecodeLastRune", - Args: []int{0}, - StringTargeted: true, - Alternative: checker.Alternative{ - Package: "unicode/utf8", - Function: "DecodeLastRune", - }, + { // utf8.DecodeLastRuneInString + Targets: checker.Strings, + Type: checker.Function, + Package: "unicode/utf8", + Caller: "DecodeLastRuneInString", + Args: []int{0}, + AltCaller: "DecodeLastRune", + Generate: &checker.Generate{ Pattern: `DecodeLastRuneInString($0)`, Returns: 2, }, }, - "DecodeRune": { - Type: checker.Function, - Message: "avoid allocations with utf8.DecodeRuneInString", - Args: []int{0}, - StringTargeted: false, - Alternative: checker.Alternative{ - Package: "unicode/utf8", - Function: "DecodeRuneInString", - }, + { // utf8.DecodeRune + Targets: checker.Bytes, + Type: checker.Function, + Package: "unicode/utf8", + Caller: "DecodeRune", + Args: []int{0}, + AltCaller: "DecodeRuneInString", + Generate: &checker.Generate{ Pattern: `DecodeRune($0)`, Returns: 2, }, }, - "DecodeRuneInString": { - Type: checker.Function, - Message: "avoid allocations with utf8.DecodeRune", - Args: []int{0}, - StringTargeted: true, - Alternative: checker.Alternative{ - Package: "unicode/utf8", - Function: "DecodeRune", - }, + { // utf8.DecodeRuneInString + Targets: checker.Strings, + Type: checker.Function, + Package: "unicode/utf8", + Args: []int{0}, + Caller: "DecodeRuneInString", + AltCaller: "DecodeRune", + Generate: &checker.Generate{ Pattern: `DecodeRuneInString($0)`, Returns: 2, diff --git a/cmd/internal/generate-tests/support.go b/cmd/internal/generate-tests/support.go index 40b0c84..b14f740 100644 --- a/cmd/internal/generate-tests/support.go +++ b/cmd/internal/generate-tests/support.go @@ -135,18 +135,15 @@ func makeFuncInline(pattern string, replaces []string) string { return pattern } -func generateTests(pkgName string, list map[string]checker.Violation) []string { +func generateTests(pkgName string, list []checker.Violation) []string { var tests []string - keys := make([]string, 0, len(list)) - for k := range list { - keys = append(keys, k) - } - sort.Strings(keys) + sort.Slice(list, func(i, j int) bool { + return list[i].Caller < list[j].Caller + }) // tests variance - for _, key := range keys { - test := list[key] + for _, test := range list { if test.Generate == nil || test.Generate.Pattern == "" { log.Printf("required fields not supported: %#v\n", test) @@ -157,10 +154,10 @@ func generateTests(pkgName string, list map[string]checker.Violation) []string { for _, variance := range PossibleVariations(len(test.Args)) { wantStr := "" if !strings.Contains(variance, "0") { - wantStr = QuoteRegexp(test.Message) + wantStr = QuoteRegexp(test.Message()) } - var b1 bytes.Buffer + var buf bytes.Buffer pkgInTest := pkg preCondition := test.Generate.PreCondition @@ -176,17 +173,18 @@ func generateTests(pkgName string, list map[string]checker.Violation) []string { } - templates.ExecuteTemplate(&b1, "case.tmpl", TestCase{ + templates.ExecuteTemplate(&buf, "case.tmpl", TestCase{ Arguments: []string{}, Returns: GenReturnelements(test.Generate.Returns), Package: pkgInTest, PreCond: preCondition, - Func: makeFuncInline(test.Generate.Pattern, variate(variance, test.StringTargeted)), - Want: wantStr, + Func: makeFuncInline(test.Generate.Pattern, + variate(variance, test.Targets == checker.Strings)), + Want: wantStr, }) - tests = append(tests, b1.String()) - b1.Reset() + tests = append(tests, buf.String()) + buf.Reset() } } diff --git a/debug.go b/debug.go deleted file mode 100644 index 562ed0b..0000000 --- a/debug.go +++ /dev/null @@ -1,22 +0,0 @@ -package mirror - -import ( - "fmt" - "go/ast" - "go/printer" - "go/token" - "os" - "strings" -) - -// --- debug ------------------------------------------------------------------- - -func debug(f *token.FileSet) func(ast.Expr, string, ...any) { - return func(node ast.Expr, format string, a ...any) { - printer.Fprint(os.Stderr, f, node) - fmt.Fprintln(os.Stderr, "\n"+strings.Repeat("^", 80)) - fmt.Fprintf(os.Stderr, format, a...) - } -} - -func debugNoOp(_ ast.Expr, _ string, _ ...any) {} diff --git a/internal/checker/checker.go b/internal/checker/checker.go index aaf2ac0..1fa7bd2 100644 --- a/internal/checker/checker.go +++ b/internal/checker/checker.go @@ -1,89 +1,59 @@ package checker import ( + "bytes" "go/ast" + "go/printer" "go/token" "go/types" "strings" - - "golang.org/x/tools/go/analysis" ) -// Checker will perform standart check on package and its methods +// Checker will perform standart check on package and its methods. type Checker struct { - Package string - Functions map[string]Violation - Methods map[string]map[string]Violation - - types *types.Info // used for checking types - fset *token.FileSet // debug info - imports []Import // imports (of current file) - debug func(ast.Expr, string, ...any) + Violations []Violation // List of available violations + Packages map[string][]int // Storing indexes of Violations per pkg/kg.Struct + Type func(ast.Expr) string // Type Checker closure. + Print func(ast.Node) []byte // String representation of the expresion. } -// New will accept a name for package (like `text/template` or `strings`) and -// returns a pointer to initial checker object. -func New(importedPackage string) *Checker { - return &Checker{ - Package: importedPackage, - Functions: make(map[string]Violation), - Methods: make(map[string]map[string]Violation), +func New(violations ...[]Violation) Checker { + c := Checker{ + Packages: make(map[string][]int), } -} -// Check perform check on call expression in order to find out can the call -// expression to be substituted with an alternative method/function. -func (c *Checker) Check(e *ast.CallExpr) *Violation { - switch expr := e.Fun.(type) { - - // Regular calls (`*ast.SelectorExpr`) like strings.HasPrefix or re.Match are - // handled by this check - case *ast.SelectorExpr: + for i := range violations { + c.register(violations[i]) + } - x, ok := expr.X.(*ast.Ident) + return c +} - // TODO(butuzov): add check for the ast.ParenExpr in e.Fun so we can target - // the constructions like this - // Example: - // (&maphash.Hash{}).Write([]byte("foobar")) - // +// Match will check the available violations we got from checks against +// the `name` caller from package `pkgName`. +func (c *Checker) Match(pkgName, name string) *Violation { + // Does it have struct? + checkStruct := strings.Contains(pkgName, ".") - if !ok { - return nil // can't be matched, so can't be checked. - } - - // does call expression violates diagnostic rule for package function? - if v := c.HandleFunction(x.Name, expr.Sel.Name); v != nil { - if argsFixed, found := c.handleViolation(v, e); found { - return v.WithAltArgs(argsFixed) + for _, idx := range c.Packages[pkgName] { + if c.Violations[idx].Caller == name { + if checkStruct == (len(c.Violations[idx].Struct) == 0) { + continue } - } - // does call expression violates diagnostic rule for package struct method? - if v := c.HandleMethod(expr.X, expr.Sel.Name); v != nil { - if argsFixed, found := c.handleViolation(v, e); found { - return v.WithAltArgs(argsFixed) - } - } + // copy violation + v := c.Violations[idx] - // Special case of "." imported packages - case *ast.Ident: - // special case of "." imported package - if v := c.HandleFunction(".", expr.Name); v != nil { - // does call expression violates diagnostic rule for package function? - if argsFixed, found := c.handleViolation(v, e); found { - return v.WithAltArgs(argsFixed) - } + return &v } } + return nil } -func (c *Checker) handleViolation(v *Violation, ce *ast.CallExpr) (map[int]ast.Expr, bool) { +func (c *Checker) Handle(v *Violation, ce *ast.CallExpr) (map[int]ast.Expr, bool) { m := map[int]ast.Expr{} - // any of ce can be a nil - // We going to check each of elements we mark for checking, in order to find, // a call that violates our rules. for _, i := range v.Args { @@ -96,108 +66,73 @@ func (c *Checker) handleViolation(v *Violation, ce *ast.CallExpr) (map[int]ast.E continue } - if !c.isConverterCall(call) { + // is it convertsion call + if !c.callConverts(call) { continue } + // somehow no argument of call if len(call.Args) == 0 { continue } - // checking whats argument - if v.Targets() != c.Type(call.Args[0]) { - m[i] = call.Args[0] + // wrong argument type + if v.Targets == c.Type(call.Args[0]) { + continue } - } - - return m, len(m) == len(v.Args) -} -// HandleFunction will return Violation for next processing if function/method -// allows to be violated, so we can check its arguments, after confirming that -// we indeed have method from imported package. -func (c *Checker) HandleFunction(pkgName, methodName string) *Violation { - m, ok := c.Functions[methodName] - if !ok || !c.isImported(c.Package, pkgName) { - return nil + m[i] = call.Args[0] } - return &m + return m, len(m) == len(v.Args) } -func (c *Checker) HandleMethod(receiver ast.Expr, method string) *Violation { - if c.types == nil { - return nil - } - - tv := c.types.Types[receiver] - if !tv.IsValue() || tv.Type == nil { - return nil - } +func (c *Checker) callConverts(ce *ast.CallExpr) bool { + switch ce.Fun.(type) { + case *ast.ArrayType, *ast.Ident: + res := c.Type(ce.Fun) - key := cleanAsterisk(tv.Type.String()) - if methods, ok := c.Methods[key]; !ok { - return nil - } else if violation, ok := methods[method]; ok { - return &violation + return res == "[]byte" || res == "string" } - return nil + return false } -func cleanAsterisk(s string) string { - if strings.HasPrefix(s, "*") { - return s[1:] +// register violations. +func (c *Checker) register(violations []Violation) { + for _, v := range violations { // nolint: gocritic + c.Violations = append(c.Violations, v) + if len(v.Struct) > 0 { + c.registerIdxPer(v.Package + "." + v.Struct) + } + c.registerIdxPer(v.Package) } - - return s } -// isImported will check if package exists in provided imports. -func (c *Checker) isImported(pkg, name string) bool { - if len(c.imports) == 0 { - return false - } +// registerIdxPer will register last added violation element +// under pkg string. +func (c *Checker) registerIdxPer(pkg string) { + c.Packages[pkg] = append(c.Packages[pkg], len(c.Violations)-1) +} - for _, v := range c.imports { - if v.Pkg == pkg && v.Name == name { - return true +func WrapType(info *types.Info) func(node ast.Expr) string { + return func(node ast.Expr) string { + if t := info.TypeOf(node); t != nil { + return t.String() } - } - - return false -} -func (c *Checker) isConverterCall(ce *ast.CallExpr) bool { - switch ce.Fun.(type) { - case *ast.ArrayType, *ast.Ident: - res := c.Type(ce.Fun) + if tv, ok := info.Types[node]; ok { + return tv.Type.Underlying().String() + } - return res == "[]byte" || res == "string" + return "" } - - return false } -func (c *Checker) Type(node ast.Expr) string { - // Sometimes it gives what it all about... sometimes not. - if t := c.types.TypeOf(node); t != nil { - return t.String() +func WrapPrint(fSet *token.FileSet) func(ast.Node) []byte { + return func(node ast.Node) []byte { + var buf bytes.Buffer + printer.Fprint(&buf, fSet, node) + return buf.Bytes() } - - if tv, ok := c.types.Types[node]; ok { - return tv.Type.Underlying().String() - } - - return "" -} - -func (c *Checker) With(pass *analysis.Pass, i []Import, debugFn func(ast.Expr, string, ...any)) *Checker { - // pass *analysis.Pass - c.fset = pass.Fset - c.types = pass.TypesInfo - c.imports = i - c.debug = debugFn - - return c } diff --git a/internal/checker/imports.go b/internal/checker/imports.go index 6293499..4015de5 100644 --- a/internal/checker/imports.go +++ b/internal/checker/imports.go @@ -76,10 +76,14 @@ func (i *Imports) sort() { } } -func (i Imports) Lookup(file string) []Import { - if v, ok := i[file]; ok { - return v +func (i Imports) Lookup(file, pkg string) (string, bool) { + if _, ok := i[file]; ok { + for idx := range i[file] { + if i[file][idx].Name == pkg { + return i[file][idx].Pkg, true + } + } } - return nil + return "", false } diff --git a/internal/checker/violation.go b/internal/checker/violation.go index acf5e0c..570678f 100644 --- a/internal/checker/violation.go +++ b/internal/checker/violation.go @@ -1,8 +1,12 @@ package checker import ( + "bytes" + "fmt" "go/ast" + "go/printer" "go/token" + "path" "golang.org/x/tools/go/analysis" ) @@ -15,21 +19,30 @@ const ( Method ) -// Violation describes what message we going to give to a particular code violation +const ( + Strings string = "string" + Bytes string = "[]byte" +) + +// Violation describs what message we going to give to a particular code violation type Violation struct { - Type ViolationType // What type is violation? Method or Function? - Message string // Message on violation detection - Args []int // Indexes of the arguments needs to be checked + Type ViolationType // + Args []int // Indexes of the arguments needs to be checked - StringTargeted bool // String is expected? []byte otherwise. - Alternative Alternative // Alternative methods/functions to use. - Generate *Generate // Rules for generation of tests. -} + Targets string + Package string + AltPackage string + Struct string + Caller string + AltCaller string -type Alternative struct { - Package string - Function string - Method string + // --- tests generation information + Generate *Generate + + // --- suggestions related info about violation of rules. + base []byte // receiver of the method or pkg name + callExpr *ast.CallExpr // actual call expression, to extract arguments + arguments map[int]ast.Expr // fixed arguments } // Tests (generation) related struct. @@ -39,28 +52,84 @@ type Generate struct { Returns int // Expected to return n elements } -func (v *Violation) Diagnostic(start, end token.Pos) *analysis.Diagnostic { - return &analysis.Diagnostic{ - Pos: start, - End: end, - Message: v.Message, - } +func (v *Violation) With(base []byte, e *ast.CallExpr, args map[int]ast.Expr) *Violation { + v.base = base + v.callExpr = e + v.arguments = args + + return v } -func (v *Violation) Handle(ce *ast.CallExpr) (m map[int]ast.Expr, ok bool) { - return m, len(m) == len(v.Args) +func (v *Violation) Message() string { + if v.Type == Method { + return fmt.Sprintf("avoid allocations with (*%s.%s).%s", + path.Base(v.Package), v.Struct, v.AltCaller) + } + + pkg := v.Package + if len(v.AltPackage) > 0 { + pkg = v.AltPackage + } + + return fmt.Sprintf("avoid allocations with %s.%s", path.Base(pkg), v.AltCaller) } -func (v *Violation) Targets() string { - if !v.StringTargeted { - return "[]byte" +func (v *Violation) suggest(fSet *token.FileSet) []byte { + var buf bytes.Buffer + + if len(v.base) > 0 { + buf.Write(v.base) + buf.WriteString(".") } - return "string" + buf.WriteString(v.AltCaller) + buf.WriteByte('(') + for idx := range v.callExpr.Args { + if arg, ok := v.arguments[idx]; ok { + printer.Fprint(&buf, fSet, arg) + } else { + printer.Fprint(&buf, fSet, v.callExpr.Args[idx]) + } + + if idx != len(v.callExpr.Args)-1 { + buf.WriteString(", ") + } + } + buf.WriteByte(')') + + return buf.Bytes() } -// TODO: not implemented -func (v *Violation) WithAltArgs(m map[int]ast.Expr) *Violation { - // v.alternativeArgs = m - return v +func (v *Violation) Issue(fSet *token.FileSet) analysis.Diagnostic { + diagnostic := analysis.Diagnostic{ + Pos: v.callExpr.Pos(), + End: v.callExpr.Pos(), + Message: v.Message(), + } + + // fmt.Println(string(v.suggest(fSet))) + + // Struct based fix. + if v.Type == Method { + diagnostic.SuggestedFixes = []analysis.SuggestedFix{{ + Message: "Fix Issue With", + TextEdits: []analysis.TextEdit{{ + Pos: v.callExpr.Pos(), End: v.callExpr.End(), NewText: v.suggest(fSet), + }}, + }} + } + + // Hooray! we dont need to change package and redo imports. + if v.Type == Function && len(v.AltPackage) == 0 { + diagnostic.SuggestedFixes = []analysis.SuggestedFix{{ + Message: "Fix Issue With", + TextEdits: []analysis.TextEdit{{ + Pos: v.callExpr.Pos(), End: v.callExpr.End(), NewText: v.suggest(fSet), + }}, + }} + } + + // do not change + + return diagnostic } diff --git a/testdata/bufio.go b/testdata/bufio.go new file mode 100644 index 0000000..a82f65c --- /dev/null +++ b/testdata/bufio.go @@ -0,0 +1,73 @@ +// Code generated by generate-tests; DO NOT EDIT. + +package main + +import ( + "bufio" + . "bufio" + pkg "bufio" +) + + +func main_bufio() { + { + b := bufio.Writer{} + _,_ = b.Write([]byte("foobar")) // want `avoid allocations with \(\*bufio\.Writer\)\.WriteString` + } + + { + b := bufio.Writer{} + _,_ = b.Write([]byte{'f','o','o','b','a','r'}) + } + + { + b := Writer{} + _,_ = b.Write([]byte("foobar")) // want `avoid allocations with \(\*bufio\.Writer\)\.WriteString` + } + + { + b := Writer{} + _,_ = b.Write([]byte{'f','o','o','b','a','r'}) + } + + { + b := pkg.Writer{} + _,_ = b.Write([]byte("foobar")) // want `avoid allocations with \(\*bufio\.Writer\)\.WriteString` + } + + { + b := pkg.Writer{} + _,_ = b.Write([]byte{'f','o','o','b','a','r'}) + } + + { + b := bufio.Writer{} + _,_ = b.WriteString(string([]byte{'f','o','o','b','a','r'})) // want `avoid allocations with \(\*bufio\.Writer\)\.Write` + } + + { + b := bufio.Writer{} + _,_ = b.WriteString("foobar") + } + + { + b := Writer{} + _,_ = b.WriteString(string([]byte{'f','o','o','b','a','r'})) // want `avoid allocations with \(\*bufio\.Writer\)\.Write` + } + + { + b := Writer{} + _,_ = b.WriteString("foobar") + } + + { + b := pkg.Writer{} + _,_ = b.WriteString(string([]byte{'f','o','o','b','a','r'})) // want `avoid allocations with \(\*bufio\.Writer\)\.Write` + } + + { + b := pkg.Writer{} + _,_ = b.WriteString("foobar") + } + +} diff --git a/testdata/bytes.go b/testdata/bytes.go index f2bc26c..3fe067d 100644 --- a/testdata/bytes.go +++ b/testdata/bytes.go @@ -732,7 +732,7 @@ func main_bytes() { { - _ = bytes.LastIndexFunc([]byte("foobar"), func(rune) bool {return true }) // want `avoid allocations with strings\.LastIndexAny` + _ = bytes.LastIndexFunc([]byte("foobar"), func(rune) bool {return true }) // want `avoid allocations with strings\.LastIndexFunc` } { @@ -742,7 +742,7 @@ func main_bytes() { { - _ = LastIndexFunc([]byte("foobar"), func(rune) bool {return true }) // want `avoid allocations with strings\.LastIndexAny` + _ = LastIndexFunc([]byte("foobar"), func(rune) bool {return true }) // want `avoid allocations with strings\.LastIndexFunc` } { @@ -752,7 +752,7 @@ func main_bytes() { { - _ = pkg.LastIndexFunc([]byte("foobar"), func(rune) bool {return true }) // want `avoid allocations with strings\.LastIndexAny` + _ = pkg.LastIndexFunc([]byte("foobar"), func(rune) bool {return true }) // want `avoid allocations with strings\.LastIndexFunc` } { diff --git a/testdata/regexp.go b/testdata/regexp.go index e97b73b..fb063a2 100644 --- a/testdata/regexp.go +++ b/testdata/regexp.go @@ -222,7 +222,7 @@ func main_regexp() { { re := regexp.MustCompile(".*") - _ = re.FindStringIndex(string([]byte{'f','o','o','b','a','r'})) // want `avoid allocations with \(\*regexp\.Regexp\)\.FindStringIndex` + _ = re.FindStringIndex(string([]byte{'f','o','o','b','a','r'})) // want `avoid allocations with \(\*regexp\.Regexp\)\.FindIndex` } { @@ -232,7 +232,7 @@ func main_regexp() { { re := MustCompile(".*") - _ = re.FindStringIndex(string([]byte{'f','o','o','b','a','r'})) // want `avoid allocations with \(\*regexp\.Regexp\)\.FindStringIndex` + _ = re.FindStringIndex(string([]byte{'f','o','o','b','a','r'})) // want `avoid allocations with \(\*regexp\.Regexp\)\.FindIndex` } { @@ -242,7 +242,7 @@ func main_regexp() { { re := pkg.MustCompile(".*") - _ = re.FindStringIndex(string([]byte{'f','o','o','b','a','r'})) // want `avoid allocations with \(\*regexp\.Regexp\)\.FindStringIndex` + _ = re.FindStringIndex(string([]byte{'f','o','o','b','a','r'})) // want `avoid allocations with \(\*regexp\.Regexp\)\.FindIndex` } { diff --git a/testdata/strings.go b/testdata/strings.go index a378024..49e04f3 100644 --- a/testdata/strings.go +++ b/testdata/strings.go @@ -732,7 +732,7 @@ func main_strings() { { - _ = strings.LastIndexFunc(string([]byte{'f','o','o','b','a','r'}), func(r rune) bool { return true }) // want `avoid allocations with bytes\.LastIndexAny` + _ = strings.LastIndexFunc(string([]byte{'f','o','o','b','a','r'}), func(r rune) bool { return true }) // want `avoid allocations with bytes\.LastIndexFunc` } { @@ -742,7 +742,7 @@ func main_strings() { { - _ = LastIndexFunc(string([]byte{'f','o','o','b','a','r'}), func(r rune) bool { return true }) // want `avoid allocations with bytes\.LastIndexAny` + _ = LastIndexFunc(string([]byte{'f','o','o','b','a','r'}), func(r rune) bool { return true }) // want `avoid allocations with bytes\.LastIndexFunc` } { @@ -752,7 +752,7 @@ func main_strings() { { - _ = pkg.LastIndexFunc(string([]byte{'f','o','o','b','a','r'}), func(r rune) bool { return true }) // want `avoid allocations with bytes\.LastIndexAny` + _ = pkg.LastIndexFunc(string([]byte{'f','o','o','b','a','r'}), func(r rune) bool { return true }) // want `avoid allocations with bytes\.LastIndexFunc` } {