-
Notifications
You must be signed in to change notification settings - Fork 5
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Rules definition #41
Rules definition #41
Changes from all commits
486e799
f82c04f
aef67a1
278b251
e936ca5
87f7e3c
2518a2c
0375e34
6c34683
b1b230a
8686a80
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,23 +16,41 @@ import ( | |
"golang.org/x/tools/go/analysis" | ||
) | ||
|
||
type optionInt struct { | ||
deactivated bool | ||
intConv bool | ||
} | ||
|
||
type optionErr struct { | ||
deactivated bool | ||
errError bool | ||
errorf bool | ||
} | ||
|
||
type optionStr struct { | ||
deactivated bool | ||
sprintf1 bool | ||
strconcat bool | ||
} | ||
|
||
type perfSprint struct { | ||
intConv bool | ||
errError bool | ||
errorf bool | ||
sprintf1 bool | ||
intFormat optionInt | ||
errFormat optionErr | ||
strFormat optionStr | ||
|
||
boolFormat bool | ||
hexFormat bool | ||
fiximports bool | ||
strconcat bool | ||
} | ||
|
||
func newPerfSprint() *perfSprint { | ||
return &perfSprint{ | ||
intConv: true, | ||
errError: false, | ||
errorf: true, | ||
sprintf1: true, | ||
intFormat: optionInt{intConv: true}, | ||
errFormat: optionErr{errError: false, errorf: true}, | ||
strFormat: optionStr{sprintf1: true, strconcat: true}, | ||
boolFormat: true, | ||
hexFormat: true, | ||
fiximports: true, | ||
strconcat: true, | ||
} | ||
} | ||
|
||
|
@@ -44,12 +62,31 @@ func New() *analysis.Analyzer { | |
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.intFormat.deactivated, "integer-format", false, "enable/disable optimization of integer formatting") | ||
r.Flags.BoolVar(&n.intFormat.intConv, "int-conversion", true, "optimizes even if it requires an int or uint type cast") | ||
r.Flags.BoolVar(&n.errFormat.deactivated, "error-format", false, "enable/disable optimization of error formatting") | ||
r.Flags.BoolVar(&n.errFormat.errError, "err-error", false, "optimizes into err.Error() even if it is only equivalent for non-nil errors") | ||
r.Flags.BoolVar(&n.errFormat.errorf, "errorf", true, "optimizes fmt.Errorf") | ||
r.Flags.BoolVar(&n.boolFormat, "bool-format", true, "enable/disable optimization of bool formatting") | ||
r.Flags.BoolVar(&n.hexFormat, "hex-format", true, "enable/disable optimization of hex formatting") | ||
r.Flags.BoolVar(&n.strFormat.deactivated, "string-format", false, "enable/disable optimization of string formatting") | ||
r.Flags.BoolVar(&n.strFormat.sprintf1, "sprintf1", true, "optimizes fmt.Sprintf with only one argument") | ||
r.Flags.BoolVar(&n.strFormat.strconcat, "strconcat", true, "optimizes into strings concatenation") | ||
r.Flags.BoolVar(&n.fiximports, "fiximports", true, "fix needed imports from other fixes") | ||
r.Flags.BoolVar(&n.strconcat, "strconcat", true, "optimizes into strings concatenation") | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You should not need all of that. Your algorithm shall start by checking if a format is activated and then trigger check their option. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. But some default options being true, I still need to do the check |
||
if n.intFormat.deactivated { | ||
n.intFormat.intConv = false | ||
} | ||
|
||
if n.errFormat.deactivated { | ||
n.errFormat.errError = false | ||
n.errFormat.errorf = false | ||
} | ||
if n.strFormat.deactivated { | ||
n.strFormat.sprintf1 = false | ||
n.strFormat.strconcat = false | ||
} | ||
|
||
return r | ||
} | ||
|
||
|
@@ -102,7 +139,7 @@ func (n *perfSprint) run(pass *analysis.Pass) (interface{}, error) { | |
err error | ||
) | ||
switch { | ||
case calledObj == fmtErrorfObj && len(call.Args) == 1 && n.errorf: | ||
case calledObj == fmtErrorfObj && len(call.Args) == 1 && n.errFormat.errorf: | ||
fn = "fmt.Errorf" | ||
verb = "%s" | ||
value = call.Args[0] | ||
|
@@ -112,7 +149,7 @@ func (n *perfSprint) run(pass *analysis.Pass) (interface{}, error) { | |
verb = "%v" | ||
value = call.Args[0] | ||
|
||
case calledObj == fmtSprintfObj && len(call.Args) == 1 && n.sprintf1: | ||
case calledObj == fmtSprintfObj && len(call.Args) == 1 && n.strFormat.sprintf1: | ||
fn = "fmt.Sprintf" | ||
verb = "%s" | ||
value = call.Args[0] | ||
|
@@ -141,7 +178,7 @@ func (n *perfSprint) run(pass *analysis.Pass) (interface{}, error) { | |
|
||
switch verb { | ||
default: | ||
if fn == "fmt.Sprintf" && isConcatable(verb) && n.strconcat { | ||
if fn == "fmt.Sprintf" && isConcatable(verb) && n.strFormat.strconcat { | ||
catenacyber marked this conversation as resolved.
Show resolved
Hide resolved
|
||
break | ||
} | ||
return | ||
|
@@ -160,7 +197,7 @@ func (n *perfSprint) run(pass *analysis.Pass) (interface{}, error) { | |
neededPackages[fname] = make(map[string]struct{}) | ||
} | ||
removedFmtUsages[fname]++ | ||
if fn == "fmt.Errorf" { | ||
if fn == "fmt.Errorf" && !n.errFormat.deactivated { | ||
neededPackages[fname]["errors"] = struct{}{} | ||
d = newAnalysisDiagnostic( | ||
"", // TODO: precise checker | ||
|
@@ -177,7 +214,7 @@ func (n *perfSprint) run(pass *analysis.Pass) (interface{}, error) { | |
}, | ||
}, | ||
) | ||
} else { | ||
} else if !n.strFormat.deactivated { | ||
catenacyber marked this conversation as resolved.
Show resolved
Hide resolved
|
||
d = newAnalysisDiagnostic( | ||
"", // TODO: precise checker | ||
call, | ||
|
@@ -194,7 +231,7 @@ func (n *perfSprint) run(pass *analysis.Pass) (interface{}, error) { | |
}, | ||
) | ||
} | ||
case types.Implements(valueType, errIface) && oneOf(verb, "%v", "%s") && n.errError: | ||
case types.Implements(valueType, errIface) && oneOf(verb, "%v", "%s") && n.errFormat.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()" | ||
|
@@ -216,7 +253,7 @@ func (n *perfSprint) run(pass *analysis.Pass) (interface{}, error) { | |
}, | ||
) | ||
|
||
case isBasicType(valueType, types.Bool) && oneOf(verb, "%v", "%t"): | ||
case isBasicType(valueType, types.Bool) && oneOf(verb, "%v", "%t") && n.boolFormat: | ||
fname := pass.Fset.File(call.Pos()).Name() | ||
removedFmtUsages[fname]++ | ||
if _, ok := neededPackages[fname]; !ok { | ||
|
@@ -239,7 +276,7 @@ func (n *perfSprint) run(pass *analysis.Pass) (interface{}, error) { | |
}, | ||
) | ||
|
||
case isArray && isBasicType(a.Elem(), types.Uint8) && oneOf(verb, "%x"): | ||
case isArray && isBasicType(a.Elem(), types.Uint8) && oneOf(verb, "%x") && n.hexFormat: | ||
if _, ok := value.(*ast.Ident); !ok { | ||
// Doesn't support array literals. | ||
return | ||
|
@@ -273,7 +310,7 @@ func (n *perfSprint) run(pass *analysis.Pass) (interface{}, error) { | |
}, | ||
}, | ||
) | ||
case isSlice && isBasicType(s.Elem(), types.Uint8) && oneOf(verb, "%x"): | ||
case isSlice && isBasicType(s.Elem(), types.Uint8) && oneOf(verb, "%x") && n.hexFormat: | ||
fname := pass.Fset.File(call.Pos()).Name() | ||
removedFmtUsages[fname]++ | ||
if _, ok := neededPackages[fname]; !ok { | ||
|
@@ -296,7 +333,7 @@ func (n *perfSprint) run(pass *analysis.Pass) (interface{}, error) { | |
}, | ||
) | ||
|
||
case isBasicType(valueType, types.Int8, types.Int16, types.Int32) && oneOf(verb, "%v", "%d") && n.intConv: | ||
case isBasicType(valueType, types.Int8, types.Int16, types.Int32) && oneOf(verb, "%v", "%d") && n.intFormat.intConv: | ||
fname := pass.Fset.File(call.Pos()).Name() | ||
removedFmtUsages[fname]++ | ||
if _, ok := neededPackages[fname]; !ok { | ||
|
@@ -325,7 +362,7 @@ func (n *perfSprint) run(pass *analysis.Pass) (interface{}, error) { | |
}, | ||
}, | ||
) | ||
case isBasicType(valueType, types.Int) && oneOf(verb, "%v", "%d"): | ||
case isBasicType(valueType, types.Int) && oneOf(verb, "%v", "%d") && !n.intFormat.deactivated: | ||
fname := pass.Fset.File(call.Pos()).Name() | ||
removedFmtUsages[fname]++ | ||
if _, ok := neededPackages[fname]; !ok { | ||
|
@@ -347,7 +384,7 @@ func (n *perfSprint) run(pass *analysis.Pass) (interface{}, error) { | |
}, | ||
}, | ||
) | ||
case isBasicType(valueType, types.Int64) && oneOf(verb, "%v", "%d"): | ||
case isBasicType(valueType, types.Int64) && oneOf(verb, "%v", "%d") && !n.intFormat.deactivated: | ||
fname := pass.Fset.File(call.Pos()).Name() | ||
removedFmtUsages[fname]++ | ||
if _, ok := neededPackages[fname]; !ok { | ||
|
@@ -377,7 +414,7 @@ func (n *perfSprint) run(pass *analysis.Pass) (interface{}, error) { | |
}, | ||
) | ||
|
||
case isBasicType(valueType, types.Uint8, types.Uint16, types.Uint32, types.Uint) && oneOf(verb, "%v", "%d", "%x") && n.intConv: | ||
case isBasicType(valueType, types.Uint8, types.Uint16, types.Uint32, types.Uint) && oneOf(verb, "%v", "%d", "%x") && n.intFormat.intConv: | ||
base := []byte("), 10") | ||
if verb == "%x" { | ||
base = []byte("), 16") | ||
|
@@ -410,7 +447,7 @@ func (n *perfSprint) run(pass *analysis.Pass) (interface{}, error) { | |
}, | ||
}, | ||
) | ||
case isBasicType(valueType, types.Uint64) && oneOf(verb, "%v", "%d", "%x"): | ||
case isBasicType(valueType, types.Uint64) && oneOf(verb, "%v", "%d", "%x") && !n.intFormat.deactivated: | ||
base := []byte(", 10") | ||
if verb == "%x" { | ||
base = []byte(", 16") | ||
|
@@ -443,7 +480,7 @@ func (n *perfSprint) run(pass *analysis.Pass) (interface{}, error) { | |
}, | ||
}, | ||
) | ||
case isBasicType(valueType, types.String) && fn == "fmt.Sprintf" && isConcatable(verb): | ||
case isBasicType(valueType, types.String) && fn == "fmt.Sprintf" && isConcatable(verb) && !n.strFormat.deactivated: | ||
var fix string | ||
if strings.HasSuffix(verb, "%s") { | ||
fix = strconv.Quote(verb[:len(verb)-2]) + "+" + formatNode(pass.Fset, value) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it possible to write tests to ensure we don't break anything with all these new options?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree.
What is the best way ?
Have
analysistest.RunWithSuggestedFixes(t, analysistest.TestData(), a, "somedir")
for all 10 options with a different value than the default one ?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The tests caught an error with fmt.Errorf("toto") becoming just a string when disabling error-format ;-)