Skip to content

Commit eff1a84

Browse files
authored
analyzer: add options to cover all cases (#42)
* analyzer: add options to cover all cases integer-format error-format bool-format hex-format string-format
1 parent d9131b0 commit eff1a84

File tree

29 files changed

+8167
-103
lines changed

29 files changed

+8167
-103
lines changed

README.md

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,23 @@ go install github.com/catenacyber/perfsprint@latest
1717
perfsprint --fix ./...
1818
```
1919

20-
To disable int/uint cast, you can use the flag `-int-conversion=false`
20+
## Options
21+
22+
The 5 following options cover all optimizations proposed by the linter.
23+
24+
Some have suboptions for specific cases.
25+
26+
- integer-format (formatting integer with the package `strconv`)
27+
- int-conversion : disable when the optimization adds a int/uint cast (readability)
28+
- error-format (formatting errors)
29+
- errorf : known behavior change avoiding panic
30+
- err-error : known behavior change panicking for nil errors
31+
- string-format (formatting strings)
32+
- sprintf1 : known behavior change avoiding panic
33+
- strconcat : disable turning some `fmt.Sprintf` to a string concatenation (readability)
34+
- bool-format (formatting bool with `strconv.FormatBool`)
35+
- hex-format (formatting bytes with `hex.EncodeToString`)
36+
2137

2238
To disable `fmt.Errorf` optimization, you can use the flag `-errorf=false`
2339
This optimization is not always equivalent.

analyzer/analyzer.go

Lines changed: 67 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -16,23 +16,41 @@ import (
1616
"golang.org/x/tools/go/analysis"
1717
)
1818

19+
type optionInt struct {
20+
enabled bool
21+
intConv bool
22+
}
23+
24+
type optionErr struct {
25+
enabled bool
26+
errError bool
27+
errorf bool
28+
}
29+
30+
type optionStr struct {
31+
enabled bool
32+
sprintf1 bool
33+
strconcat bool
34+
}
35+
1936
type perfSprint struct {
20-
intConv bool
21-
errError bool
22-
errorf bool
23-
sprintf1 bool
37+
intFormat optionInt
38+
errFormat optionErr
39+
strFormat optionStr
40+
41+
boolFormat bool
42+
hexFormat bool
2443
fiximports bool
25-
strconcat bool
2644
}
2745

2846
func newPerfSprint() *perfSprint {
2947
return &perfSprint{
30-
intConv: true,
31-
errError: false,
32-
errorf: true,
33-
sprintf1: true,
48+
intFormat: optionInt{enabled: true, intConv: true},
49+
errFormat: optionErr{enabled: true, errError: false, errorf: true},
50+
strFormat: optionStr{enabled: true, sprintf1: true, strconcat: true},
51+
boolFormat: true,
52+
hexFormat: true,
3453
fiximports: true,
35-
strconcat: true,
3654
}
3755
}
3856

@@ -44,12 +62,31 @@ func New() *analysis.Analyzer {
4462
Run: n.run,
4563
Requires: []*analysis.Analyzer{inspect.Analyzer},
4664
}
47-
r.Flags.BoolVar(&n.intConv, "int-conversion", true, "optimizes even if it requires an int or uint type cast")
48-
r.Flags.BoolVar(&n.errError, "err-error", false, "optimizes into err.Error() even if it is only equivalent for non-nil errors")
49-
r.Flags.BoolVar(&n.errorf, "errorf", true, "optimizes fmt.Errorf")
50-
r.Flags.BoolVar(&n.sprintf1, "sprintf1", true, "optimizes fmt.Sprintf with only one argument")
65+
r.Flags.BoolVar(&n.intFormat.enabled, "integer-format", true, "enable/disable optimization of integer formatting")
66+
r.Flags.BoolVar(&n.intFormat.intConv, "int-conversion", true, "optimizes even if it requires an int or uint type cast")
67+
r.Flags.BoolVar(&n.errFormat.enabled, "error-format", true, "enable/disable optimization of error formatting")
68+
r.Flags.BoolVar(&n.errFormat.errError, "err-error", false, "optimizes into err.Error() even if it is only equivalent for non-nil errors")
69+
r.Flags.BoolVar(&n.errFormat.errorf, "errorf", true, "optimizes fmt.Errorf")
70+
r.Flags.BoolVar(&n.boolFormat, "bool-format", true, "enable/disable optimization of bool formatting")
71+
r.Flags.BoolVar(&n.hexFormat, "hex-format", true, "enable/disable optimization of hex formatting")
72+
r.Flags.BoolVar(&n.strFormat.enabled, "string-format", true, "enable/disable optimization of string formatting")
73+
r.Flags.BoolVar(&n.strFormat.sprintf1, "sprintf1", true, "optimizes fmt.Sprintf with only one argument")
74+
r.Flags.BoolVar(&n.strFormat.strconcat, "strconcat", true, "optimizes into strings concatenation")
5175
r.Flags.BoolVar(&n.fiximports, "fiximports", true, "fix needed imports from other fixes")
52-
r.Flags.BoolVar(&n.strconcat, "strconcat", true, "optimizes into strings concatenation")
76+
77+
if !n.intFormat.enabled {
78+
n.intFormat.intConv = false
79+
}
80+
81+
if !n.errFormat.enabled {
82+
n.errFormat.errError = false
83+
n.errFormat.errorf = false
84+
}
85+
if !n.strFormat.enabled {
86+
n.strFormat.sprintf1 = false
87+
n.strFormat.strconcat = false
88+
}
89+
5390
return r
5491
}
5592

@@ -102,7 +139,7 @@ func (n *perfSprint) run(pass *analysis.Pass) (interface{}, error) {
102139
err error
103140
)
104141
switch {
105-
case calledObj == fmtErrorfObj && len(call.Args) == 1 && n.errorf:
142+
case calledObj == fmtErrorfObj && len(call.Args) == 1 && n.errFormat.errorf:
106143
fn = "fmt.Errorf"
107144
verb = "%s"
108145
value = call.Args[0]
@@ -112,7 +149,7 @@ func (n *perfSprint) run(pass *analysis.Pass) (interface{}, error) {
112149
verb = "%v"
113150
value = call.Args[0]
114151

115-
case calledObj == fmtSprintfObj && len(call.Args) == 1 && n.sprintf1:
152+
case calledObj == fmtSprintfObj && len(call.Args) == 1 && n.strFormat.sprintf1:
116153
fn = "fmt.Sprintf"
117154
verb = "%s"
118155
value = call.Args[0]
@@ -141,7 +178,7 @@ func (n *perfSprint) run(pass *analysis.Pass) (interface{}, error) {
141178

142179
switch verb {
143180
default:
144-
if fn == "fmt.Sprintf" && isConcatable(verb) && n.strconcat {
181+
if fn == "fmt.Sprintf" && isConcatable(verb) && n.strFormat.strconcat {
145182
break
146183
}
147184
return
@@ -160,7 +197,7 @@ func (n *perfSprint) run(pass *analysis.Pass) (interface{}, error) {
160197
neededPackages[fname] = make(map[string]struct{})
161198
}
162199
removedFmtUsages[fname]++
163-
if fn == "fmt.Errorf" {
200+
if fn == "fmt.Errorf" && n.errFormat.enabled {
164201
neededPackages[fname]["errors"] = struct{}{}
165202
d = newAnalysisDiagnostic(
166203
"", // TODO: precise checker
@@ -177,7 +214,7 @@ func (n *perfSprint) run(pass *analysis.Pass) (interface{}, error) {
177214
},
178215
},
179216
)
180-
} else {
217+
} else if fn != "fmt.Errorf" && n.strFormat.enabled {
181218
d = newAnalysisDiagnostic(
182219
"", // TODO: precise checker
183220
call,
@@ -194,7 +231,7 @@ func (n *perfSprint) run(pass *analysis.Pass) (interface{}, error) {
194231
},
195232
)
196233
}
197-
case types.Implements(valueType, errIface) && oneOf(verb, "%v", "%s") && n.errError:
234+
case types.Implements(valueType, errIface) && oneOf(verb, "%v", "%s") && n.errFormat.errError:
198235
// known false positive if this error is nil
199236
// fmt.Sprint(nil) does not panic like nil.Error() does
200237
errMethodCall := formatNode(pass.Fset, value) + ".Error()"
@@ -216,7 +253,7 @@ func (n *perfSprint) run(pass *analysis.Pass) (interface{}, error) {
216253
},
217254
)
218255

219-
case isBasicType(valueType, types.Bool) && oneOf(verb, "%v", "%t"):
256+
case isBasicType(valueType, types.Bool) && oneOf(verb, "%v", "%t") && n.boolFormat:
220257
fname := pass.Fset.File(call.Pos()).Name()
221258
removedFmtUsages[fname]++
222259
if _, ok := neededPackages[fname]; !ok {
@@ -239,7 +276,7 @@ func (n *perfSprint) run(pass *analysis.Pass) (interface{}, error) {
239276
},
240277
)
241278

242-
case isArray && isBasicType(a.Elem(), types.Uint8) && oneOf(verb, "%x"):
279+
case isArray && isBasicType(a.Elem(), types.Uint8) && oneOf(verb, "%x") && n.hexFormat:
243280
if _, ok := value.(*ast.Ident); !ok {
244281
// Doesn't support array literals.
245282
return
@@ -273,7 +310,7 @@ func (n *perfSprint) run(pass *analysis.Pass) (interface{}, error) {
273310
},
274311
},
275312
)
276-
case isSlice && isBasicType(s.Elem(), types.Uint8) && oneOf(verb, "%x"):
313+
case isSlice && isBasicType(s.Elem(), types.Uint8) && oneOf(verb, "%x") && n.hexFormat:
277314
fname := pass.Fset.File(call.Pos()).Name()
278315
removedFmtUsages[fname]++
279316
if _, ok := neededPackages[fname]; !ok {
@@ -296,7 +333,7 @@ func (n *perfSprint) run(pass *analysis.Pass) (interface{}, error) {
296333
},
297334
)
298335

299-
case isBasicType(valueType, types.Int8, types.Int16, types.Int32) && oneOf(verb, "%v", "%d") && n.intConv:
336+
case isBasicType(valueType, types.Int8, types.Int16, types.Int32) && oneOf(verb, "%v", "%d") && n.intFormat.intConv:
300337
fname := pass.Fset.File(call.Pos()).Name()
301338
removedFmtUsages[fname]++
302339
if _, ok := neededPackages[fname]; !ok {
@@ -325,7 +362,7 @@ func (n *perfSprint) run(pass *analysis.Pass) (interface{}, error) {
325362
},
326363
},
327364
)
328-
case isBasicType(valueType, types.Int) && oneOf(verb, "%v", "%d"):
365+
case isBasicType(valueType, types.Int) && oneOf(verb, "%v", "%d") && n.intFormat.enabled:
329366
fname := pass.Fset.File(call.Pos()).Name()
330367
removedFmtUsages[fname]++
331368
if _, ok := neededPackages[fname]; !ok {
@@ -347,7 +384,7 @@ func (n *perfSprint) run(pass *analysis.Pass) (interface{}, error) {
347384
},
348385
},
349386
)
350-
case isBasicType(valueType, types.Int64) && oneOf(verb, "%v", "%d"):
387+
case isBasicType(valueType, types.Int64) && oneOf(verb, "%v", "%d") && n.intFormat.enabled:
351388
fname := pass.Fset.File(call.Pos()).Name()
352389
removedFmtUsages[fname]++
353390
if _, ok := neededPackages[fname]; !ok {
@@ -377,7 +414,7 @@ func (n *perfSprint) run(pass *analysis.Pass) (interface{}, error) {
377414
},
378415
)
379416

380-
case isBasicType(valueType, types.Uint8, types.Uint16, types.Uint32, types.Uint) && oneOf(verb, "%v", "%d", "%x") && n.intConv:
417+
case isBasicType(valueType, types.Uint8, types.Uint16, types.Uint32, types.Uint) && oneOf(verb, "%v", "%d", "%x") && n.intFormat.intConv:
381418
base := []byte("), 10")
382419
if verb == "%x" {
383420
base = []byte("), 16")
@@ -410,7 +447,7 @@ func (n *perfSprint) run(pass *analysis.Pass) (interface{}, error) {
410447
},
411448
},
412449
)
413-
case isBasicType(valueType, types.Uint64) && oneOf(verb, "%v", "%d", "%x"):
450+
case isBasicType(valueType, types.Uint64) && oneOf(verb, "%v", "%d", "%x") && n.intFormat.enabled:
414451
base := []byte(", 10")
415452
if verb == "%x" {
416453
base = []byte(", 16")
@@ -443,7 +480,7 @@ func (n *perfSprint) run(pass *analysis.Pass) (interface{}, error) {
443480
},
444481
},
445482
)
446-
case isBasicType(valueType, types.String) && fn == "fmt.Sprintf" && isConcatable(verb):
483+
case isBasicType(valueType, types.String) && fn == "fmt.Sprintf" && isConcatable(verb) && n.strFormat.enabled:
447484
var fix string
448485
if strings.HasSuffix(verb, "%s") {
449486
fix = strconv.Quote(verb[:len(verb)-2]) + "+" + formatNode(pass.Fset, value)

analyzer/analyzer_test.go

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package analyzer_test
22

33
import (
44
"encoding/hex"
5+
"flag"
56
"fmt"
67
"io"
78
"strconv"
@@ -14,31 +15,30 @@ import (
1415
func TestAnalyzer(t *testing.T) {
1516
t.Parallel()
1617
a := analyzer.New()
17-
err := a.Flags.Set("err-error", "true")
18-
if err != nil {
19-
t.Fatalf("failed to set err-error flag")
20-
}
21-
analysistest.RunWithSuggestedFixes(t, analysistest.TestData(), a, "p")
22-
}
23-
24-
func TestAnalyzerNoErrError(t *testing.T) {
25-
t.Parallel()
26-
a := analyzer.New()
27-
err := a.Flags.Set("err-error", "false")
28-
if err != nil {
29-
t.Fatalf("failed to set err-error flag")
30-
}
31-
analysistest.RunWithSuggestedFixes(t, analysistest.TestData(), a, "nilerror")
32-
}
18+
analysistest.RunWithSuggestedFixes(t, analysistest.TestData(), a, "default")
19+
a.Flags.VisitAll(func(f *flag.Flag) {
20+
if f.Name == "fiximports" {
21+
return
22+
}
23+
t.Run(f.Name, func(t *testing.T) {
24+
changedVal := "false"
25+
if f.DefValue == "false" {
26+
changedVal = "true"
27+
} else if f.DefValue != "true" {
28+
t.Fatalf("default value neither false or true")
29+
}
30+
err := a.Flags.Set(f.Name, changedVal)
31+
if err != nil {
32+
t.Fatalf("failed to set %q flag", f.Name)
33+
}
34+
analysistest.RunWithSuggestedFixes(t, analysistest.TestData(), a, f.Name)
35+
err = a.Flags.Set(f.Name, f.DefValue)
36+
if err != nil {
37+
t.Fatalf("failed to set %q flag", f.Name)
38+
}
39+
})
40+
})
3341

34-
func TestAnalyzerNoConv(t *testing.T) {
35-
t.Parallel()
36-
a := analyzer.New()
37-
err := a.Flags.Set("int-conversion", "false")
38-
if err != nil {
39-
t.Fatalf("failed to set int-conversion flag")
40-
}
41-
analysistest.RunWithSuggestedFixes(t, analysistest.TestData(), a, "noconv")
4242
}
4343

4444
func TestReplacements(t *testing.T) {

0 commit comments

Comments
 (0)