diff options
| author | dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> | 2024-03-04 17:40:11 +0000 |
|---|---|---|
| committer | Taras Madan <tarasmadan@google.com> | 2024-03-04 18:34:55 +0000 |
| commit | 5fc5366972c874b919f93165bb4ed4e2bcb7c350 (patch) | |
| tree | 287c3361a0dee0c72af80d9a1a66714a06e98a62 /vendor/github.com/catenacyber | |
| parent | 1be5ce38a9059c356eb193a8c34d60d61c9fc31f (diff) | |
mod: bump github.com/golangci/golangci-lint from 1.55.2 to 1.56.2
Bumps [github.com/golangci/golangci-lint](https://github.com/golangci/golangci-lint) from 1.55.2 to 1.56.2.
- [Release notes](https://github.com/golangci/golangci-lint/releases)
- [Changelog](https://github.com/golangci/golangci-lint/blob/master/CHANGELOG.md)
- [Commits](https://github.com/golangci/golangci-lint/compare/v1.55.2...v1.56.2)
---
updated-dependencies:
- dependency-name: github.com/golangci/golangci-lint
dependency-type: direct:production
update-type: version-update:semver-minor
...
Signed-off-by: dependabot[bot] <support@github.com>
Diffstat (limited to 'vendor/github.com/catenacyber')
| -rw-r--r-- | vendor/github.com/catenacyber/perfsprint/analyzer/analyzer.go | 322 |
1 files changed, 293 insertions, 29 deletions
diff --git a/vendor/github.com/catenacyber/perfsprint/analyzer/analyzer.go b/vendor/github.com/catenacyber/perfsprint/analyzer/analyzer.go index 69087802a..ad312ca69 100644 --- a/vendor/github.com/catenacyber/perfsprint/analyzer/analyzer.go +++ b/vendor/github.com/catenacyber/perfsprint/analyzer/analyzer.go @@ -6,7 +6,9 @@ import ( "go/format" "go/token" "go/types" + "sort" "strconv" + "strings" "golang.org/x/tools/go/analysis/passes/inspect" "golang.org/x/tools/go/ast/inspector" @@ -14,24 +16,66 @@ import ( "golang.org/x/tools/go/analysis" ) -var Analyzer = &analysis.Analyzer{ - Name: "perfsprint", - Doc: "Checks that fmt.Sprintf can be replaced with a faster alternative.", - Run: run, - Requires: []*analysis.Analyzer{inspect.Analyzer}, +type perfSprint struct { + intConv bool + errError bool + errorf bool + sprintf1 bool + fiximports bool } -func run(pass *analysis.Pass) (interface{}, error) { - var fmtSprintObj, fmtSprintfObj types.Object +func newPerfSprint() *perfSprint { + return &perfSprint{ + intConv: true, + errError: false, + errorf: true, + sprintf1: true, + fiximports: true, + } +} + +func New() *analysis.Analyzer { + n := newPerfSprint() + r := &analysis.Analyzer{ + Name: "perfsprint", + Doc: "Checks that fmt.Sprintf can be replaced with a faster alternative.", + Run: n.run, + Requires: []*analysis.Analyzer{inspect.Analyzer}, + } + r.Flags.BoolVar(&n.intConv, "int-conversion", true, "optimizes even if it requires an int or uint type cast") + r.Flags.BoolVar(&n.errError, "err-error", false, "optimizes into err.Error() even if it is only equivalent for non-nil errors") + r.Flags.BoolVar(&n.errorf, "errorf", true, "optimizes fmt.Errorf") + r.Flags.BoolVar(&n.sprintf1, "sprintf1", true, "optimizes fmt.Sprintf with only one argument") + r.Flags.BoolVar(&n.fiximports, "fiximports", true, "fix needed imports from other fixes") + return r +} + +// true if verb is a format string that could be replaced with concatenation. +func isConcatable(verb string) bool { + hasPrefix := + (strings.HasPrefix(verb, "%s") && !strings.Contains(verb, "%[1]s")) || + (strings.HasPrefix(verb, "%[1]s") && !strings.Contains(verb, "%s")) + hasSuffix := + (strings.HasSuffix(verb, "%s") && !strings.Contains(verb, "%[1]s")) || + (strings.HasSuffix(verb, "%[1]s") && !strings.Contains(verb, "%s")) + + return (hasPrefix || hasSuffix) && !(hasPrefix && hasSuffix) +} + +func (n *perfSprint) run(pass *analysis.Pass) (interface{}, error) { + var fmtSprintObj, fmtSprintfObj, fmtErrorfObj types.Object for _, pkg := range pass.Pkg.Imports() { if pkg.Path() == "fmt" { fmtSprintObj = pkg.Scope().Lookup("Sprint") fmtSprintfObj = pkg.Scope().Lookup("Sprintf") + fmtErrorfObj = pkg.Scope().Lookup("Errorf") } } - if fmtSprintfObj == nil { + if fmtSprintfObj == nil && fmtSprintObj == nil && fmtErrorfObj == nil { return nil, nil } + removedFmtUsages := make(map[string]int) + neededPackages := make(map[string]map[string]bool) insp := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) nodeFilter := []ast.Node{ @@ -52,11 +96,29 @@ func run(pass *analysis.Pass) (interface{}, error) { err error ) switch { + case calledObj == fmtErrorfObj && len(call.Args) == 1: + if n.errorf { + fn = "fmt.Errorf" + verb = "%s" + value = call.Args[0] + } else { + return + } + case calledObj == fmtSprintObj && len(call.Args) == 1: fn = "fmt.Sprint" verb = "%v" value = call.Args[0] + case calledObj == fmtSprintfObj && len(call.Args) == 1: + if n.sprintf1 { + fn = "fmt.Sprintf" + verb = "%s" + value = call.Args[0] + } else { + return + } + case calledObj == fmtSprintfObj && len(call.Args) == 2: verbLit, ok := call.Args[0].(*ast.BasicLit) if !ok { @@ -67,6 +129,10 @@ func run(pass *analysis.Pass) (interface{}, error) { // Probably unreachable. return } + // one single explicit arg is simplified + if strings.HasPrefix(verb, "%[1]") { + verb = "%" + verb[4:] + } fn = "fmt.Sprintf" value = call.Args[1] @@ -77,6 +143,9 @@ func run(pass *analysis.Pass) (interface{}, error) { switch verb { default: + if fn == "fmt.Sprintf" && isConcatable(verb) { + break + } return case "%d", "%v", "%x", "%t", "%s": } @@ -88,24 +157,52 @@ func run(pass *analysis.Pass) (interface{}, error) { var d *analysis.Diagnostic switch { case isBasicType(valueType, types.String) && oneOf(verb, "%v", "%s"): - d = &analysis.Diagnostic{ - Pos: call.Pos(), - End: call.End(), - Message: fn + " can be replaced with just using the string", - SuggestedFixes: []analysis.SuggestedFix{ - { - Message: "Just use string value", - TextEdits: []analysis.TextEdit{{ - Pos: call.Pos(), - End: call.End(), - NewText: []byte(formatNode(pass.Fset, value)), - }}, + fname := pass.Fset.File(call.Pos()).Name() + _, ok := neededPackages[fname] + if !ok { + neededPackages[fname] = make(map[string]bool) + } + removedFmtUsages[fname]++ + if fn == "fmt.Errorf" { + neededPackages[fname]["errors"] = true + d = &analysis.Diagnostic{ + Pos: call.Pos(), + End: call.End(), + Message: fn + " can be replaced with errors.New", + SuggestedFixes: []analysis.SuggestedFix{ + { + Message: "Use errors.New", + TextEdits: []analysis.TextEdit{{ + Pos: call.Pos(), + End: value.Pos(), + NewText: []byte("errors.New("), + }}, + }, }, - }, + } + } else { + d = &analysis.Diagnostic{ + Pos: call.Pos(), + End: call.End(), + Message: fn + " can be replaced with just using the string", + SuggestedFixes: []analysis.SuggestedFix{ + { + Message: "Just use string value", + TextEdits: []analysis.TextEdit{{ + Pos: call.Pos(), + End: call.End(), + NewText: []byte(formatNode(pass.Fset, value)), + }}, + }, + }, + } } - - case types.Implements(valueType, errIface) && oneOf(verb, "%v", "%s"): + case types.Implements(valueType, errIface) && oneOf(verb, "%v", "%s") && n.errError: + // known false positive if this error is nil + // fmt.Sprint(nil) does not panic like nil.Error() does errMethodCall := formatNode(pass.Fset, value) + ".Error()" + fname := pass.Fset.File(call.Pos()).Name() + removedFmtUsages[fname]++ d = &analysis.Diagnostic{ Pos: call.Pos(), End: call.End(), @@ -123,6 +220,13 @@ func run(pass *analysis.Pass) (interface{}, error) { } case isBasicType(valueType, types.Bool) && oneOf(verb, "%v", "%t"): + fname := pass.Fset.File(call.Pos()).Name() + removedFmtUsages[fname]++ + _, ok := neededPackages[fname] + if !ok { + neededPackages[fname] = make(map[string]bool) + } + neededPackages[fname]["strconv"] = true d = &analysis.Diagnostic{ Pos: call.Pos(), End: call.End(), @@ -145,6 +249,13 @@ func run(pass *analysis.Pass) (interface{}, error) { return } + fname := pass.Fset.File(call.Pos()).Name() + removedFmtUsages[fname]++ + _, ok := neededPackages[fname] + if !ok { + neededPackages[fname] = make(map[string]bool) + } + neededPackages[fname]["encoding/hex"] = true d = &analysis.Diagnostic{ Pos: call.Pos(), End: call.End(), @@ -168,6 +279,13 @@ func run(pass *analysis.Pass) (interface{}, error) { }, } case isSlice && isBasicType(s.Elem(), types.Uint8) && oneOf(verb, "%x"): + fname := pass.Fset.File(call.Pos()).Name() + removedFmtUsages[fname]++ + _, ok := neededPackages[fname] + if !ok { + neededPackages[fname] = make(map[string]bool) + } + neededPackages[fname]["encoding/hex"] = true d = &analysis.Diagnostic{ Pos: call.Pos(), End: call.End(), @@ -184,7 +302,14 @@ func run(pass *analysis.Pass) (interface{}, error) { }, } - case isBasicType(valueType, types.Int8, types.Int16, types.Int32) && oneOf(verb, "%v", "%d"): + case isBasicType(valueType, types.Int8, types.Int16, types.Int32) && oneOf(verb, "%v", "%d") && n.intConv: + fname := pass.Fset.File(call.Pos()).Name() + removedFmtUsages[fname]++ + _, ok := neededPackages[fname] + if !ok { + neededPackages[fname] = make(map[string]bool) + } + neededPackages[fname]["strconv"] = true d = &analysis.Diagnostic{ Pos: call.Pos(), End: call.End(), @@ -208,6 +333,13 @@ func run(pass *analysis.Pass) (interface{}, error) { }, } case isBasicType(valueType, types.Int) && oneOf(verb, "%v", "%d"): + fname := pass.Fset.File(call.Pos()).Name() + removedFmtUsages[fname]++ + _, ok := neededPackages[fname] + if !ok { + neededPackages[fname] = make(map[string]bool) + } + neededPackages[fname]["strconv"] = true d = &analysis.Diagnostic{ Pos: call.Pos(), End: call.End(), @@ -224,6 +356,13 @@ func run(pass *analysis.Pass) (interface{}, error) { }, } case isBasicType(valueType, types.Int64) && oneOf(verb, "%v", "%d"): + fname := pass.Fset.File(call.Pos()).Name() + removedFmtUsages[fname]++ + _, ok := neededPackages[fname] + if !ok { + neededPackages[fname] = make(map[string]bool) + } + neededPackages[fname]["strconv"] = true d = &analysis.Diagnostic{ Pos: call.Pos(), End: call.End(), @@ -247,7 +386,18 @@ func run(pass *analysis.Pass) (interface{}, error) { }, } - case isBasicType(valueType, types.Uint8, types.Uint16, types.Uint32, types.Uint) && oneOf(verb, "%v", "%d"): + case isBasicType(valueType, types.Uint8, types.Uint16, types.Uint32, types.Uint) && oneOf(verb, "%v", "%d", "%x") && n.intConv: + base := []byte("), 10") + if verb == "%x" { + base = []byte("), 16") + } + fname := pass.Fset.File(call.Pos()).Name() + removedFmtUsages[fname]++ + _, ok := neededPackages[fname] + if !ok { + neededPackages[fname] = make(map[string]bool) + } + neededPackages[fname]["strconv"] = true d = &analysis.Diagnostic{ Pos: call.Pos(), End: call.End(), @@ -264,13 +414,24 @@ func run(pass *analysis.Pass) (interface{}, error) { { Pos: value.End(), End: value.End(), - NewText: []byte("), 10"), + NewText: base, }, }, }, }, } - case isBasicType(valueType, types.Uint64) && oneOf(verb, "%v", "%d"): + case isBasicType(valueType, types.Uint64) && oneOf(verb, "%v", "%d", "%x"): + base := []byte(", 10") + if verb == "%x" { + base = []byte(", 16") + } + fname := pass.Fset.File(call.Pos()).Name() + removedFmtUsages[fname]++ + _, ok := neededPackages[fname] + if !ok { + neededPackages[fname] = make(map[string]bool) + } + neededPackages[fname]["strconv"] = true d = &analysis.Diagnostic{ Pos: call.Pos(), End: call.End(), @@ -287,20 +448,123 @@ func run(pass *analysis.Pass) (interface{}, error) { { Pos: value.End(), End: value.End(), - NewText: []byte(", 10"), + NewText: base, }, }, }, }, } + case isBasicType(valueType, types.String) && fn == "fmt.Sprintf" && isConcatable(verb): + var fix string + if strings.HasSuffix(verb, "%s") { + fix = strconv.Quote(verb[:len(verb)-2]) + "+" + formatNode(pass.Fset, value) + } else if strings.HasSuffix(verb, "%[1]s") { + fix = strconv.Quote(verb[:len(verb)-5]) + "+" + formatNode(pass.Fset, value) + } else if strings.HasPrefix(verb, "%s") { + fix = formatNode(pass.Fset, value) + "+" + strconv.Quote(verb[2:]) + } else { + fix = formatNode(pass.Fset, value) + "+" + strconv.Quote(verb[5:]) + } + fname := pass.Fset.File(call.Pos()).Name() + removedFmtUsages[fname]++ + d = &analysis.Diagnostic{ + Pos: call.Pos(), + End: call.End(), + Message: fn + " can be replaced with string addition", + SuggestedFixes: []analysis.SuggestedFix{ + { + Message: "Use string addition", + TextEdits: []analysis.TextEdit{{ + Pos: call.Pos(), + End: call.End(), + NewText: []byte(fix), + }}, + }, + }, + } } if d != nil { - // Need to run goimports to fix using of fmt, strconv or encoding/hex afterwards. pass.Report(*d) } }) + if len(removedFmtUsages) > 0 && n.fiximports { + for _, pkg := range pass.Pkg.Imports() { + if pkg.Path() == "fmt" { + insp = pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) + nodeFilter = []ast.Node{ + (*ast.SelectorExpr)(nil), + } + insp.Preorder(nodeFilter, func(node ast.Node) { + selec := node.(*ast.SelectorExpr) + selecok, ok := selec.X.(*ast.Ident) + if ok { + pkgname, ok := pass.TypesInfo.ObjectOf(selecok).(*types.PkgName) + if ok && pkgname.Name() == pkg.Name() { + fname := pass.Fset.File(pkgname.Pos()).Name() + removedFmtUsages[fname]-- + } + } + }) + } else if pkg.Path() == "errors" || pkg.Path() == "strconv" || pkg.Path() == "encoding/hex" { + insp = pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) + nodeFilter = []ast.Node{ + (*ast.ImportSpec)(nil), + } + insp.Preorder(nodeFilter, func(node ast.Node) { + gd := node.(*ast.ImportSpec) + if gd.Path.Value == strconv.Quote(pkg.Path()) { + fname := pass.Fset.File(gd.Pos()).Name() + _, ok := neededPackages[fname] + if ok { + delete(neededPackages[fname], pkg.Path()) + } + } + }) + } + } + insp = pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) + nodeFilter = []ast.Node{ + (*ast.ImportSpec)(nil), + } + insp.Preorder(nodeFilter, func(node ast.Node) { + gd := node.(*ast.ImportSpec) + if gd.Path.Value == `"fmt"` { + fix := "" + fname := pass.Fset.File(gd.Pos()).Name() + if removedFmtUsages[fname] < 0 { + fix += `"fmt"` + if len(neededPackages[fname]) == 0 { + return + } + } + keys := make([]string, 0, len(neededPackages[fname])) + for k := range neededPackages[fname] { + keys = append(keys, k) + } + sort.Strings(keys) + for _, k := range keys { + fix = fix + "\n\t\"" + k + `"` + } + pass.Report(analysis.Diagnostic{ + Pos: gd.Pos(), + End: gd.End(), + Message: "Fix imports", + SuggestedFixes: []analysis.SuggestedFix{ + { + Message: "Fix imports", + TextEdits: []analysis.TextEdit{{ + Pos: gd.Pos(), + End: gd.End(), + NewText: []byte(fix), + }}, + }, + }}) + } + }) + } + return nil, nil } |
