Skip to content

Commit 5d04216

Browse files
doniacldchavacava
andauthored
Add optimize-operands-order rule (#599) (#603)
Co-authored-by: SalvadorC <[email protected]>
1 parent 8a3653c commit 5d04216

File tree

7 files changed

+139
-1
lines changed

7 files changed

+139
-1
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -477,6 +477,7 @@ List of all available rules. The rules ported from `golint` are left unchanged a
477477
| [`nested-structs`](./RULES_DESCRIPTIONS.md#nested-structs) | n/a | Warns on structs within structs | no | no |
478478
| [`useless-break`](./RULES_DESCRIPTIONS.md#useless-break) | n/a | Warns on useless `break` statements in case clauses | no | no |
479479
| [`banned-characters`](./RULES_DESCRIPTIONS.md#banned-characters) | n/a | Checks banned characters in identifiers | no | no |
480+
| [`optimize-operands-order`](./RULES_DESCRIPTIONS.md#optimize-operands-order) | n/a | Checks inefficient conditional expressions | no | no |
480481

481482
## Configurable rules
482483

RULES_DESCRIPTIONS.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ List of all available rules.
4747
- [modifies-parameter](#modifies-parameter)
4848
- [modifies-value-receiver](#modifies-value-receiver)
4949
- [nested-structs](#nested-structs)
50+
- [optimize-operands-order](#optimize-operands-order)
5051
- [package-comments](#package-comments)
5152
- [range](#range)
5253
- [range-val-in-closure](#range-val-in-closure)
@@ -480,6 +481,19 @@ _Description_: Packages declaring structs that contain other inline struct defin
480481

481482
_Configuration_: N/A
482483

484+
## optimize-operands-order
485+
486+
_Description_: conditional expressions can be written to take advantage of short circuit evaluation and speed up its average evaluation time by forcing the evaluation of less time-consuming terms before more costly ones. This rule spots logical expressions where the order of evaluation of terms seems non optimal. Please notice that confidence of this rule is low and is up to the user to decide if the suggested rewrite of the expression keeps the semantics of the original one.
487+
488+
_Configuration_: N/A
489+
490+
Example:
491+
492+
if isGenerated(content) && !config.IgnoreGeneratedHeader {
493+
Swap left and right side :
494+
495+
if !config.IgnoreGeneratedHeader && isGenerated(content) {
496+
483497
## package-comments
484498

485499
_Description_: Packages should have comments. This rule warns on undocumented packages and when packages comments are detached to the `package` keyword.

config/config.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ var allRules = append([]lint.Rule{
8383
&rule.UselessBreak{},
8484
&rule.TimeEqualRule{},
8585
&rule.BannedCharsRule{},
86+
&rule.OptimizeOperandsOrderRule{},
8687
}, defaultRules...)
8788

8889
var allFormatters = []lint.Formatter{

rule/optimize-operands-order.go

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package rule
2+
3+
import (
4+
"fmt"
5+
"go/ast"
6+
"go/token"
7+
8+
"github.com/mgechev/revive/lint"
9+
)
10+
11+
// OptimizeOperandsOrderRule lints given else constructs.
12+
type OptimizeOperandsOrderRule struct{}
13+
14+
// Apply applies the rule to given file.
15+
func (r *OptimizeOperandsOrderRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
16+
var failures []lint.Failure
17+
18+
onFailure := func(failure lint.Failure) {
19+
failures = append(failures, failure)
20+
}
21+
w := lintOptimizeOperandsOrderlExpr{
22+
onFailure: onFailure,
23+
}
24+
ast.Walk(w, file.AST)
25+
return failures
26+
}
27+
28+
// Name returns the rule name.
29+
func (r *OptimizeOperandsOrderRule) Name() string {
30+
return "optimize-operands-order"
31+
}
32+
33+
type lintOptimizeOperandsOrderlExpr struct {
34+
onFailure func(failure lint.Failure)
35+
}
36+
37+
// Visit checks boolean AND and OR expressions to determine
38+
// if swapping their operands may result in an execution speedup.
39+
func (w lintOptimizeOperandsOrderlExpr) Visit(node ast.Node) ast.Visitor {
40+
binExpr, ok := node.(*ast.BinaryExpr)
41+
if !ok {
42+
return w
43+
}
44+
45+
switch binExpr.Op {
46+
case token.LAND, token.LOR:
47+
default:
48+
return w
49+
}
50+
51+
isCaller := func(n ast.Node) bool {
52+
_, ok := n.(*ast.CallExpr)
53+
return ok
54+
}
55+
56+
// check if the left sub-expression contains a function call
57+
nodes := pick(binExpr.X, isCaller, nil)
58+
if len(nodes) < 1 {
59+
return w
60+
}
61+
62+
// check if the right sub-expression does not contain a function call
63+
nodes = pick(binExpr.Y, isCaller, nil)
64+
if len(nodes) > 0 {
65+
return w
66+
}
67+
68+
newExpr := ast.BinaryExpr{X: binExpr.Y, Y: binExpr.X, Op: binExpr.Op}
69+
w.onFailure(lint.Failure{
70+
Failure: fmt.Sprintf("for better performance '%v' might be rewritten as '%v'", gofmt(binExpr), gofmt(&newExpr)),
71+
Node: node,
72+
Category: "optimization",
73+
Confidence: 0.3,
74+
})
75+
76+
return w
77+
}

rule/utils.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ func srcLine(src []byte, p token.Position) string {
111111

112112
// pick yields a list of nodes by picking them from a sub-ast with root node n.
113113
// Nodes are selected by applying the fselect function
114-
// f function is applied to each selected node before inseting it in the final result.
114+
// f function is applied to each selected node before inserting it in the final result.
115115
// If f==nil then it defaults to the identity function (ie it returns the node itself)
116116
func pick(n ast.Node, fselect func(n ast.Node) bool, f func(n ast.Node) []ast.Node) []ast.Node {
117117
var result []ast.Node

test/optimize-operands-order_test.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package test
2+
3+
import (
4+
"testing"
5+
6+
"github.com/mgechev/revive/lint"
7+
"github.com/mgechev/revive/rule"
8+
)
9+
10+
// Test that left and right side of Binary operators (only AND, OR) are swapable
11+
func TestOptimizeOperandsOrder(t *testing.T) {
12+
testRule(t, "optimize-operands-order", &rule.OptimizeOperandsOrderRule{}, &lint.RuleConfig{})
13+
}

testdata/optimize-operands-order.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package fixtures
2+
3+
func conditionalExpr(x, y bool) bool {
4+
equal := x == y // should not match, not AND or OR operators
5+
if x || y { // should not match, no caller
6+
return true
7+
}
8+
or := caller(x, y) || y // MATCH /for better performance 'caller(x, y) || y' might be rewritten as 'y || caller(x, y)'/
9+
if caller(x, y) || y { // MATCH /for better performance 'caller(x, y) || y' might be rewritten as 'y || caller(x, y)'/
10+
return true
11+
}
12+
13+
switch {
14+
case x == y:
15+
return y
16+
case caller(x, y) && y: // MATCH /for better performance 'caller(x, y) && y' might be rewritten as 'y && caller(x, y)'/
17+
return x
18+
}
19+
20+
complexExpr := caller(caller(x, y) && y, y) || y
21+
// MATCH:20 /for better performance 'caller(caller(x, y) && y, y) || y' might be rewritten as 'y || caller(caller(x, y) && y, y)'/
22+
// MATCH:20 /for better performance 'caller(x, y) && y' might be rewritten as 'y && caller(x, y)'/
23+
24+
noSwap := caller(x, y) || (x && caller(y, x)) // should not match, caller in the right operand
25+
26+
callRight := caller(x, y) && (x && caller(y, x)) // should not match, caller in the right operand
27+
return caller(x, y) && y // MATCH /for better performance 'caller(x, y) && y' might be rewritten as 'y && caller(x, y)'/
28+
}
29+
30+
func caller(x, y bool) bool {
31+
return true
32+
}

0 commit comments

Comments
 (0)