UNPKG

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