aboutsummaryrefslogtreecommitdiffstats
path: root/vendor/github.com/catenacyber
diff options
context:
space:
mode:
authordependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>2024-03-04 17:40:11 +0000
committerTaras Madan <tarasmadan@google.com>2024-03-04 18:34:55 +0000
commit5fc5366972c874b919f93165bb4ed4e2bcb7c350 (patch)
tree287c3361a0dee0c72af80d9a1a66714a06e98a62 /vendor/github.com/catenacyber
parent1be5ce38a9059c356eb193a8c34d60d61c9fc31f (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.go322
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
}