1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
|
/**
* @fileoverview Rule to replace assignment expressions with operator assignment
* @author Brandon Mills
*/
"use strict";
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
/**
* Checks whether an operator is commutative and has an operator assignment
* shorthand form.
* @param {string} operator Operator to check.
* @returns {boolean} True if the operator is commutative and has a
* shorthand form.
*/
function isCommutativeOperatorWithShorthand(operator) {
return ["*", "&", "^", "|"].indexOf(operator) >= 0;
}
/**
* Checks whether an operator is not commuatative and has an operator assignment
* shorthand form.
* @param {string} operator Operator to check.
* @returns {boolean} True if the operator is not commuatative and has
* a shorthand form.
*/
function isNonCommutativeOperatorWithShorthand(operator) {
return ["+", "-", "/", "%", "<<", ">>", ">>>"].indexOf(operator) >= 0;
}
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
/**
* Checks whether two expressions reference the same value. For example:
* a = a
* a.b = a.b
* a[0] = a[0]
* a['b'] = a['b']
* @param {ASTNode} a Left side of the comparison.
* @param {ASTNode} b Right side of the comparison.
* @returns {boolean} True if both sides match and reference the same value.
*/
function same(a, b) {
if (a.type !== b.type) {
return false;
}
switch (a.type) {
case "Identifier":
return a.name === b.name;
case "Literal":
return a.value === b.value;
case "MemberExpression":
/*
* x[0] = x[0]
* x[y] = x[y]
* x.y = x.y
*/
return same(a.object, b.object) && same(a.property, b.property);
default:
return false;
}
}
module.exports = {
meta: {
docs: {
description: "require or disallow assignment operator shorthand where possible",
category: "Stylistic Issues",
recommended: false
},
schema: [
{
enum: ["always", "never"]
}
]
},
create: function(context) {
/**
* Ensures that an assignment uses the shorthand form where possible.
* @param {ASTNode} node An AssignmentExpression node.
* @returns {void}
*/
function verify(node) {
var expr, left, operator;
if (node.operator !== "=" || node.right.type !== "BinaryExpression") {
return;
}
left = node.left;
expr = node.right;
operator = expr.operator;
if (isCommutativeOperatorWithShorthand(operator)) {
if (same(left, expr.left) || same(left, expr.right)) {
context.report(node, "Assignment can be replaced with operator assignment.");
}
} else if (isNonCommutativeOperatorWithShorthand(operator)) {
if (same(left, expr.left)) {
context.report(node, "Assignment can be replaced with operator assignment.");
}
}
}
/**
* Warns if an assignment expression uses operator assignment shorthand.
* @param {ASTNode} node An AssignmentExpression node.
* @returns {void}
*/
function prohibit(node) {
if (node.operator !== "=") {
context.report(node, "Unexpected operator assignment shorthand.");
}
}
return {
AssignmentExpression: context.options[0] !== "never" ? verify : prohibit
};
}
};
|