eslint 7.2.0 → 7.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +78 -0
- package/README.md +14 -7
- package/lib/init/config-initializer.js +89 -68
- package/lib/linter/code-path-analysis/code-path-analyzer.js +38 -0
- package/lib/linter/code-path-analysis/code-path-segment.js +0 -1
- package/lib/linter/code-path-analysis/code-path-state.js +59 -0
- package/lib/linter/code-path-analysis/debug-helpers.js +26 -19
- package/lib/rule-tester/rule-tester.js +9 -0
- package/lib/rules/accessor-pairs.js +1 -14
- package/lib/rules/array-callback-return.js +26 -17
- package/lib/rules/arrow-body-style.js +43 -8
- package/lib/rules/arrow-parens.js +91 -108
- package/lib/rules/camelcase.js +47 -0
- package/lib/rules/comma-dangle.js +2 -1
- package/lib/rules/consistent-return.js +1 -12
- package/lib/rules/constructor-super.js +1 -0
- package/lib/rules/curly.js +8 -1
- package/lib/rules/dot-location.js +20 -14
- package/lib/rules/dot-notation.js +36 -33
- package/lib/rules/func-call-spacing.js +42 -6
- package/lib/rules/func-name-matching.js +1 -4
- package/lib/rules/global-require.js +2 -1
- package/lib/rules/id-blacklist.js +14 -11
- package/lib/rules/id-denylist.js +230 -0
- package/lib/rules/id-match.js +2 -1
- package/lib/rules/indent.js +19 -0
- package/lib/rules/index.js +3 -0
- package/lib/rules/key-spacing.js +6 -2
- package/lib/rules/keyword-spacing.js +2 -2
- package/lib/rules/max-len.js +13 -2
- package/lib/rules/max-lines.js +34 -8
- package/lib/rules/new-cap.js +10 -14
- package/lib/rules/newline-per-chained-call.js +15 -5
- package/lib/rules/no-alert.js +10 -3
- package/lib/rules/no-eval.js +8 -38
- package/lib/rules/no-extend-native.js +37 -40
- package/lib/rules/no-extra-bind.js +57 -17
- package/lib/rules/no-extra-boolean-cast.js +7 -0
- package/lib/rules/no-extra-parens.js +48 -10
- package/lib/rules/no-implicit-coercion.js +11 -6
- package/lib/rules/no-implied-eval.js +7 -28
- package/lib/rules/no-import-assign.js +33 -32
- package/lib/rules/no-irregular-whitespace.js +22 -12
- package/lib/rules/no-magic-numbers.js +4 -8
- package/lib/rules/no-obj-calls.js +7 -4
- package/lib/rules/no-promise-executor-return.js +121 -0
- package/lib/rules/no-prototype-builtins.js +13 -3
- package/lib/rules/no-self-assign.js +3 -53
- package/lib/rules/no-setter-return.js +5 -8
- package/lib/rules/no-unexpected-multiline.js +2 -2
- package/lib/rules/no-unneeded-ternary.js +0 -2
- package/lib/rules/no-unreachable-loop.js +150 -0
- package/lib/rules/no-unused-expressions.js +55 -23
- package/lib/rules/no-unused-vars.js +2 -1
- package/lib/rules/no-useless-call.js +10 -7
- package/lib/rules/no-whitespace-before-property.js +16 -4
- package/lib/rules/object-curly-newline.js +4 -4
- package/lib/rules/object-property-newline.js +1 -1
- package/lib/rules/operator-assignment.js +3 -42
- package/lib/rules/operator-linebreak.js +2 -5
- package/lib/rules/padded-blocks.js +2 -1
- package/lib/rules/padding-line-between-statements.js +2 -2
- package/lib/rules/prefer-arrow-callback.js +90 -25
- package/lib/rules/prefer-exponentiation-operator.js +1 -1
- package/lib/rules/prefer-numeric-literals.js +4 -13
- package/lib/rules/prefer-promise-reject-errors.js +1 -3
- package/lib/rules/prefer-regex-literals.js +68 -13
- package/lib/rules/prefer-spread.js +2 -6
- package/lib/rules/radix.js +5 -2
- package/lib/rules/semi-spacing.js +1 -0
- package/lib/rules/sort-imports.js +28 -0
- package/lib/rules/use-isnan.js +1 -1
- package/lib/rules/utils/ast-utils.js +317 -153
- package/lib/rules/wrap-iife.js +9 -2
- package/lib/rules/yoda.js +2 -55
- package/package.json +7 -7
@@ -39,15 +39,12 @@ function isGlobalReference(node, scope) {
|
|
39
39
|
* @returns {boolean} `true` if the node is argument at the given position.
|
40
40
|
*/
|
41
41
|
function isArgumentOfGlobalMethodCall(node, scope, objectName, methodName, index) {
|
42
|
-
const
|
42
|
+
const callNode = node.parent;
|
43
43
|
|
44
|
-
return
|
45
|
-
|
46
|
-
|
47
|
-
astUtils.
|
48
|
-
parent.callee.object.type === "Identifier" &&
|
49
|
-
parent.callee.object.name === objectName &&
|
50
|
-
isGlobalReference(parent.callee.object, scope);
|
44
|
+
return callNode.type === "CallExpression" &&
|
45
|
+
callNode.arguments[index] === node &&
|
46
|
+
astUtils.isSpecificMemberAccess(callNode.callee, objectName, methodName) &&
|
47
|
+
isGlobalReference(astUtils.skipChainExpression(callNode.callee).object, scope);
|
51
48
|
}
|
52
49
|
|
53
50
|
/**
|
@@ -68,7 +68,7 @@ module.exports = {
|
|
68
68
|
return {
|
69
69
|
|
70
70
|
MemberExpression(node) {
|
71
|
-
if (!node.computed) {
|
71
|
+
if (!node.computed || node.optional) {
|
72
72
|
return;
|
73
73
|
}
|
74
74
|
checkForBreakAfter(node.object, "property");
|
@@ -96,7 +96,7 @@ module.exports = {
|
|
96
96
|
},
|
97
97
|
|
98
98
|
CallExpression(node) {
|
99
|
-
if (node.arguments.length === 0) {
|
99
|
+
if (node.arguments.length === 0 || node.optional) {
|
100
100
|
return;
|
101
101
|
}
|
102
102
|
checkForBreakAfter(node.callee, "function");
|
@@ -122,7 +122,6 @@ module.exports = {
|
|
122
122
|
if (isBooleanLiteral(node.alternate) && isBooleanLiteral(node.consequent)) {
|
123
123
|
context.report({
|
124
124
|
node,
|
125
|
-
loc: node.consequent.loc.start,
|
126
125
|
messageId: "unnecessaryConditionalExpression",
|
127
126
|
fix(fixer) {
|
128
127
|
if (node.consequent.value === node.alternate.value) {
|
@@ -144,7 +143,6 @@ module.exports = {
|
|
144
143
|
} else if (!defaultAssignment && matchesDefaultAssignment(node)) {
|
145
144
|
context.report({
|
146
145
|
node,
|
147
|
-
loc: node.consequent.loc.start,
|
148
146
|
messageId: "unnecessaryConditionalAssignment",
|
149
147
|
fix: fixer => {
|
150
148
|
const shouldParenthesizeAlternate =
|
@@ -0,0 +1,150 @@
|
|
1
|
+
/**
|
2
|
+
* @fileoverview Rule to disallow loops with a body that allows only one iteration
|
3
|
+
* @author Milos Djermanovic
|
4
|
+
*/
|
5
|
+
|
6
|
+
"use strict";
|
7
|
+
|
8
|
+
//------------------------------------------------------------------------------
|
9
|
+
// Helpers
|
10
|
+
//------------------------------------------------------------------------------
|
11
|
+
|
12
|
+
const allLoopTypes = ["WhileStatement", "DoWhileStatement", "ForStatement", "ForInStatement", "ForOfStatement"];
|
13
|
+
|
14
|
+
/**
|
15
|
+
* Determines whether the given node is the first node in the code path to which a loop statement
|
16
|
+
* 'loops' for the next iteration.
|
17
|
+
* @param {ASTNode} node The node to check.
|
18
|
+
* @returns {boolean} `true` if the node is a looping target.
|
19
|
+
*/
|
20
|
+
function isLoopingTarget(node) {
|
21
|
+
const parent = node.parent;
|
22
|
+
|
23
|
+
if (parent) {
|
24
|
+
switch (parent.type) {
|
25
|
+
case "WhileStatement":
|
26
|
+
return node === parent.test;
|
27
|
+
case "DoWhileStatement":
|
28
|
+
return node === parent.body;
|
29
|
+
case "ForStatement":
|
30
|
+
return node === (parent.update || parent.test || parent.body);
|
31
|
+
case "ForInStatement":
|
32
|
+
case "ForOfStatement":
|
33
|
+
return node === parent.left;
|
34
|
+
|
35
|
+
// no default
|
36
|
+
}
|
37
|
+
}
|
38
|
+
|
39
|
+
return false;
|
40
|
+
}
|
41
|
+
|
42
|
+
/**
|
43
|
+
* Creates an array with elements from the first given array that are not included in the second given array.
|
44
|
+
* @param {Array} arrA The array to compare from.
|
45
|
+
* @param {Array} arrB The array to compare against.
|
46
|
+
* @returns {Array} a new array that represents `arrA \ arrB`.
|
47
|
+
*/
|
48
|
+
function getDifference(arrA, arrB) {
|
49
|
+
return arrA.filter(a => !arrB.includes(a));
|
50
|
+
}
|
51
|
+
|
52
|
+
//------------------------------------------------------------------------------
|
53
|
+
// Rule Definition
|
54
|
+
//------------------------------------------------------------------------------
|
55
|
+
|
56
|
+
module.exports = {
|
57
|
+
meta: {
|
58
|
+
type: "problem",
|
59
|
+
|
60
|
+
docs: {
|
61
|
+
description: "disallow loops with a body that allows only one iteration",
|
62
|
+
category: "Possible Errors",
|
63
|
+
recommended: false,
|
64
|
+
url: "https://eslint.org/docs/rules/no-unreachable-loop"
|
65
|
+
},
|
66
|
+
|
67
|
+
schema: [{
|
68
|
+
type: "object",
|
69
|
+
properties: {
|
70
|
+
ignore: {
|
71
|
+
type: "array",
|
72
|
+
items: {
|
73
|
+
enum: allLoopTypes
|
74
|
+
},
|
75
|
+
uniqueItems: true
|
76
|
+
}
|
77
|
+
},
|
78
|
+
additionalProperties: false
|
79
|
+
}],
|
80
|
+
|
81
|
+
messages: {
|
82
|
+
invalid: "Invalid loop. Its body allows only one iteration."
|
83
|
+
}
|
84
|
+
},
|
85
|
+
|
86
|
+
create(context) {
|
87
|
+
const ignoredLoopTypes = context.options[0] && context.options[0].ignore || [],
|
88
|
+
loopTypesToCheck = getDifference(allLoopTypes, ignoredLoopTypes),
|
89
|
+
loopSelector = loopTypesToCheck.join(","),
|
90
|
+
loopsByTargetSegments = new Map(),
|
91
|
+
loopsToReport = new Set();
|
92
|
+
|
93
|
+
let currentCodePath = null;
|
94
|
+
|
95
|
+
return {
|
96
|
+
onCodePathStart(codePath) {
|
97
|
+
currentCodePath = codePath;
|
98
|
+
},
|
99
|
+
|
100
|
+
onCodePathEnd() {
|
101
|
+
currentCodePath = currentCodePath.upper;
|
102
|
+
},
|
103
|
+
|
104
|
+
[loopSelector](node) {
|
105
|
+
|
106
|
+
/**
|
107
|
+
* Ignore unreachable loop statements to avoid unnecessary complexity in the implementation, or false positives otherwise.
|
108
|
+
* For unreachable segments, the code path analysis does not raise events required for this implementation.
|
109
|
+
*/
|
110
|
+
if (currentCodePath.currentSegments.some(segment => segment.reachable)) {
|
111
|
+
loopsToReport.add(node);
|
112
|
+
}
|
113
|
+
},
|
114
|
+
|
115
|
+
onCodePathSegmentStart(segment, node) {
|
116
|
+
if (isLoopingTarget(node)) {
|
117
|
+
const loop = node.parent;
|
118
|
+
|
119
|
+
loopsByTargetSegments.set(segment, loop);
|
120
|
+
}
|
121
|
+
},
|
122
|
+
|
123
|
+
onCodePathSegmentLoop(_, toSegment, node) {
|
124
|
+
const loop = loopsByTargetSegments.get(toSegment);
|
125
|
+
|
126
|
+
/**
|
127
|
+
* The second iteration is reachable, meaning that the loop is valid by the logic of this rule,
|
128
|
+
* only if there is at least one loop event with the appropriate target (which has been already
|
129
|
+
* determined in the `loopsByTargetSegments` map), raised from either:
|
130
|
+
*
|
131
|
+
* - the end of the loop's body (in which case `node === loop`)
|
132
|
+
* - a `continue` statement
|
133
|
+
*
|
134
|
+
* This condition skips loop events raised from `ForInStatement > .right` and `ForOfStatement > .right` nodes.
|
135
|
+
*/
|
136
|
+
if (node === loop || node.type === "ContinueStatement") {
|
137
|
+
|
138
|
+
// Removes loop if it exists in the set. Otherwise, `Set#delete` has no effect and doesn't throw.
|
139
|
+
loopsToReport.delete(loop);
|
140
|
+
}
|
141
|
+
},
|
142
|
+
|
143
|
+
"Program:exit"() {
|
144
|
+
loopsToReport.forEach(
|
145
|
+
node => context.report({ node, messageId: "invalid" })
|
146
|
+
);
|
147
|
+
}
|
148
|
+
};
|
149
|
+
}
|
150
|
+
};
|
@@ -8,6 +8,22 @@
|
|
8
8
|
// Rule Definition
|
9
9
|
//------------------------------------------------------------------------------
|
10
10
|
|
11
|
+
/**
|
12
|
+
* Returns `true`.
|
13
|
+
* @returns {boolean} `true`.
|
14
|
+
*/
|
15
|
+
function alwaysTrue() {
|
16
|
+
return true;
|
17
|
+
}
|
18
|
+
|
19
|
+
/**
|
20
|
+
* Returns `false`.
|
21
|
+
* @returns {boolean} `false`.
|
22
|
+
*/
|
23
|
+
function alwaysFalse() {
|
24
|
+
return false;
|
25
|
+
}
|
26
|
+
|
11
27
|
module.exports = {
|
12
28
|
meta: {
|
13
29
|
type: "suggestion",
|
@@ -101,40 +117,56 @@ module.exports = {
|
|
101
117
|
}
|
102
118
|
|
103
119
|
/**
|
104
|
-
*
|
105
|
-
*
|
106
|
-
* @returns {boolean} whether the given node is a valid expression
|
120
|
+
* The member functions return `true` if the type has no side-effects.
|
121
|
+
* Unknown nodes are handled as `false`, then this rule ignores those.
|
107
122
|
*/
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
123
|
+
const Checker = Object.assign(Object.create(null), {
|
124
|
+
isDisallowed(node) {
|
125
|
+
return (Checker[node.type] || alwaysFalse)(node);
|
126
|
+
},
|
127
|
+
|
128
|
+
ArrayExpression: alwaysTrue,
|
129
|
+
ArrowFunctionExpression: alwaysTrue,
|
130
|
+
BinaryExpression: alwaysTrue,
|
131
|
+
ChainExpression(node) {
|
132
|
+
return Checker.isDisallowed(node.expression);
|
133
|
+
},
|
134
|
+
ClassExpression: alwaysTrue,
|
135
|
+
ConditionalExpression(node) {
|
136
|
+
if (allowTernary) {
|
137
|
+
return Checker.isDisallowed(node.consequent) || Checker.isDisallowed(node.alternate);
|
114
138
|
}
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
139
|
+
return true;
|
140
|
+
},
|
141
|
+
FunctionExpression: alwaysTrue,
|
142
|
+
Identifier: alwaysTrue,
|
143
|
+
Literal: alwaysTrue,
|
144
|
+
LogicalExpression(node) {
|
145
|
+
if (allowShortCircuit) {
|
146
|
+
return Checker.isDisallowed(node.right);
|
120
147
|
}
|
121
|
-
}
|
122
|
-
|
123
|
-
if (allowTaggedTemplates && node.type === "TaggedTemplateExpression") {
|
124
148
|
return true;
|
149
|
+
},
|
150
|
+
MemberExpression: alwaysTrue,
|
151
|
+
MetaProperty: alwaysTrue,
|
152
|
+
ObjectExpression: alwaysTrue,
|
153
|
+
SequenceExpression: alwaysTrue,
|
154
|
+
TaggedTemplateExpression() {
|
155
|
+
return !allowTaggedTemplates;
|
156
|
+
},
|
157
|
+
TemplateLiteral: alwaysTrue,
|
158
|
+
ThisExpression: alwaysTrue,
|
159
|
+
UnaryExpression(node) {
|
160
|
+
return node.operator !== "void" && node.operator !== "delete";
|
125
161
|
}
|
126
|
-
|
127
|
-
return /^(?:Assignment|Call|New|Update|Yield|Await|Import)Expression$/u.test(node.type) ||
|
128
|
-
(node.type === "UnaryExpression" && ["delete", "void"].indexOf(node.operator) >= 0);
|
129
|
-
}
|
162
|
+
});
|
130
163
|
|
131
164
|
return {
|
132
165
|
ExpressionStatement(node) {
|
133
|
-
if (
|
166
|
+
if (Checker.isDisallowed(node.expression) && !isDirective(node, context.getAncestors())) {
|
134
167
|
context.report({ node, messageId: "unusedExpression" });
|
135
168
|
}
|
136
169
|
}
|
137
170
|
};
|
138
|
-
|
139
171
|
}
|
140
172
|
};
|
@@ -17,13 +17,15 @@ const astUtils = require("./utils/ast-utils");
|
|
17
17
|
* @returns {boolean} Whether or not the node is a `.call()`/`.apply()`.
|
18
18
|
*/
|
19
19
|
function isCallOrNonVariadicApply(node) {
|
20
|
+
const callee = astUtils.skipChainExpression(node.callee);
|
21
|
+
|
20
22
|
return (
|
21
|
-
|
22
|
-
|
23
|
-
|
23
|
+
callee.type === "MemberExpression" &&
|
24
|
+
callee.property.type === "Identifier" &&
|
25
|
+
callee.computed === false &&
|
24
26
|
(
|
25
|
-
(
|
26
|
-
(
|
27
|
+
(callee.property.name === "call" && node.arguments.length >= 1) ||
|
28
|
+
(callee.property.name === "apply" && node.arguments.length === 2 && node.arguments[1].type === "ArrayExpression")
|
27
29
|
)
|
28
30
|
);
|
29
31
|
}
|
@@ -74,12 +76,13 @@ module.exports = {
|
|
74
76
|
return;
|
75
77
|
}
|
76
78
|
|
77
|
-
const
|
79
|
+
const callee = astUtils.skipChainExpression(node.callee);
|
80
|
+
const applied = astUtils.skipChainExpression(callee.object);
|
78
81
|
const expectedThis = (applied.type === "MemberExpression") ? applied.object : null;
|
79
82
|
const thisArg = node.arguments[0];
|
80
83
|
|
81
84
|
if (isValidThisArg(expectedThis, thisArg, sourceCode)) {
|
82
|
-
context.report({ node, messageId: "unnecessaryCall", data: { name:
|
85
|
+
context.report({ node, messageId: "unnecessaryCall", data: { name: callee.property.name } });
|
83
86
|
}
|
84
87
|
}
|
85
88
|
};
|
@@ -49,8 +49,6 @@ module.exports = {
|
|
49
49
|
* @private
|
50
50
|
*/
|
51
51
|
function reportError(node, leftToken, rightToken) {
|
52
|
-
const replacementText = node.computed ? "" : ".";
|
53
|
-
|
54
52
|
context.report({
|
55
53
|
node,
|
56
54
|
messageId: "unexpectedWhitespace",
|
@@ -58,7 +56,9 @@ module.exports = {
|
|
58
56
|
propName: sourceCode.getText(node.property)
|
59
57
|
},
|
60
58
|
fix(fixer) {
|
61
|
-
|
59
|
+
let replacementText = "";
|
60
|
+
|
61
|
+
if (!node.computed && !node.optional && astUtils.isDecimalInteger(node.object)) {
|
62
62
|
|
63
63
|
/*
|
64
64
|
* If the object is a number literal, fixing it to something like 5.toString() would cause a SyntaxError.
|
@@ -66,6 +66,18 @@ module.exports = {
|
|
66
66
|
*/
|
67
67
|
return null;
|
68
68
|
}
|
69
|
+
|
70
|
+
// Don't fix if comments exist.
|
71
|
+
if (sourceCode.commentsExistBetween(leftToken, rightToken)) {
|
72
|
+
return null;
|
73
|
+
}
|
74
|
+
|
75
|
+
if (node.optional) {
|
76
|
+
replacementText = "?.";
|
77
|
+
} else if (!node.computed) {
|
78
|
+
replacementText = ".";
|
79
|
+
}
|
80
|
+
|
69
81
|
return fixer.replaceTextRange([leftToken.range[1], rightToken.range[0]], replacementText);
|
70
82
|
}
|
71
83
|
});
|
@@ -86,7 +98,7 @@ module.exports = {
|
|
86
98
|
|
87
99
|
if (node.computed) {
|
88
100
|
rightToken = sourceCode.getTokenBefore(node.property, astUtils.isOpeningBracketToken);
|
89
|
-
leftToken = sourceCode.getTokenBefore(rightToken);
|
101
|
+
leftToken = sourceCode.getTokenBefore(rightToken, node.optional ? 1 : 0);
|
90
102
|
} else {
|
91
103
|
rightToken = sourceCode.getFirstToken(node.property);
|
92
104
|
leftToken = sourceCode.getTokenBefore(rightToken, 1);
|
@@ -224,7 +224,7 @@ module.exports = {
|
|
224
224
|
context.report({
|
225
225
|
messageId: "expectedLinebreakAfterOpeningBrace",
|
226
226
|
node,
|
227
|
-
loc: openBrace.loc
|
227
|
+
loc: openBrace.loc,
|
228
228
|
fix(fixer) {
|
229
229
|
if (hasCommentsFirstToken) {
|
230
230
|
return null;
|
@@ -238,7 +238,7 @@ module.exports = {
|
|
238
238
|
context.report({
|
239
239
|
messageId: "expectedLinebreakBeforeClosingBrace",
|
240
240
|
node,
|
241
|
-
loc: closeBrace.loc
|
241
|
+
loc: closeBrace.loc,
|
242
242
|
fix(fixer) {
|
243
243
|
if (hasCommentsLastToken) {
|
244
244
|
return null;
|
@@ -260,7 +260,7 @@ module.exports = {
|
|
260
260
|
context.report({
|
261
261
|
messageId: "unexpectedLinebreakAfterOpeningBrace",
|
262
262
|
node,
|
263
|
-
loc: openBrace.loc
|
263
|
+
loc: openBrace.loc,
|
264
264
|
fix(fixer) {
|
265
265
|
if (hasCommentsFirstToken) {
|
266
266
|
return null;
|
@@ -280,7 +280,7 @@ module.exports = {
|
|
280
280
|
context.report({
|
281
281
|
messageId: "unexpectedLinebreakBeforeClosingBrace",
|
282
282
|
node,
|
283
|
-
loc: closeBrace.loc
|
283
|
+
loc: closeBrace.loc,
|
284
284
|
fix(fixer) {
|
285
285
|
if (hasCommentsLastToken) {
|
286
286
|
return null;
|
@@ -77,7 +77,7 @@ module.exports = {
|
|
77
77
|
if (lastTokenOfPreviousProperty.loc.end.line === firstTokenOfCurrentProperty.loc.start.line) {
|
78
78
|
context.report({
|
79
79
|
node,
|
80
|
-
loc: firstTokenOfCurrentProperty.loc
|
80
|
+
loc: firstTokenOfCurrentProperty.loc,
|
81
81
|
messageId,
|
82
82
|
fix(fixer) {
|
83
83
|
const comma = sourceCode.getTokenBefore(firstTokenOfCurrentProperty);
|
@@ -40,45 +40,6 @@ function isNonCommutativeOperatorWithShorthand(operator) {
|
|
40
40
|
// Rule Definition
|
41
41
|
//------------------------------------------------------------------------------
|
42
42
|
|
43
|
-
/**
|
44
|
-
* Checks whether two expressions reference the same value. For example:
|
45
|
-
* a = a
|
46
|
-
* a.b = a.b
|
47
|
-
* a[0] = a[0]
|
48
|
-
* a['b'] = a['b']
|
49
|
-
* @param {ASTNode} a Left side of the comparison.
|
50
|
-
* @param {ASTNode} b Right side of the comparison.
|
51
|
-
* @returns {boolean} True if both sides match and reference the same value.
|
52
|
-
*/
|
53
|
-
function same(a, b) {
|
54
|
-
if (a.type !== b.type) {
|
55
|
-
return false;
|
56
|
-
}
|
57
|
-
|
58
|
-
switch (a.type) {
|
59
|
-
case "Identifier":
|
60
|
-
return a.name === b.name;
|
61
|
-
|
62
|
-
case "Literal":
|
63
|
-
return a.value === b.value;
|
64
|
-
|
65
|
-
case "MemberExpression":
|
66
|
-
|
67
|
-
/*
|
68
|
-
* x[0] = x[0]
|
69
|
-
* x[y] = x[y]
|
70
|
-
* x.y = x.y
|
71
|
-
*/
|
72
|
-
return same(a.object, b.object) && same(a.property, b.property);
|
73
|
-
|
74
|
-
case "ThisExpression":
|
75
|
-
return true;
|
76
|
-
|
77
|
-
default:
|
78
|
-
return false;
|
79
|
-
}
|
80
|
-
}
|
81
|
-
|
82
43
|
/**
|
83
44
|
* Determines if the left side of a node can be safely fixed (i.e. if it activates the same getters/setters and)
|
84
45
|
* toString calls regardless of whether assignment shorthand is used)
|
@@ -148,12 +109,12 @@ module.exports = {
|
|
148
109
|
const operator = expr.operator;
|
149
110
|
|
150
111
|
if (isCommutativeOperatorWithShorthand(operator) || isNonCommutativeOperatorWithShorthand(operator)) {
|
151
|
-
if (
|
112
|
+
if (astUtils.isSameReference(left, expr.left, true)) {
|
152
113
|
context.report({
|
153
114
|
node,
|
154
115
|
messageId: "replaced",
|
155
116
|
fix(fixer) {
|
156
|
-
if (canBeFixed(left)) {
|
117
|
+
if (canBeFixed(left) && canBeFixed(expr.left)) {
|
157
118
|
const equalsToken = getOperatorToken(node);
|
158
119
|
const operatorToken = getOperatorToken(expr);
|
159
120
|
const leftText = sourceCode.getText().slice(node.range[0], equalsToken.range[0]);
|
@@ -169,7 +130,7 @@ module.exports = {
|
|
169
130
|
return null;
|
170
131
|
}
|
171
132
|
});
|
172
|
-
} else if (
|
133
|
+
} else if (astUtils.isSameReference(left, expr.right, true) && isCommutativeOperatorWithShorthand(operator)) {
|
173
134
|
|
174
135
|
/*
|
175
136
|
* This case can't be fixed safely.
|
@@ -35,11 +35,8 @@ module.exports = {
|
|
35
35
|
properties: {
|
36
36
|
overrides: {
|
37
37
|
type: "object",
|
38
|
-
|
39
|
-
|
40
|
-
type: "string",
|
41
|
-
enum: ["after", "before", "none", "ignore"]
|
42
|
-
}
|
38
|
+
additionalProperties: {
|
39
|
+
enum: ["after", "before", "none", "ignore"]
|
43
40
|
}
|
44
41
|
}
|
45
42
|
},
|
@@ -85,10 +85,10 @@ function newNodeTypeTester(type) {
|
|
85
85
|
*/
|
86
86
|
function isIIFEStatement(node) {
|
87
87
|
if (node.type === "ExpressionStatement") {
|
88
|
-
let call = node.expression;
|
88
|
+
let call = astUtils.skipChainExpression(node.expression);
|
89
89
|
|
90
90
|
if (call.type === "UnaryExpression") {
|
91
|
-
call = call.argument;
|
91
|
+
call = astUtils.skipChainExpression(call.argument);
|
92
92
|
}
|
93
93
|
return call.type === "CallExpression" && astUtils.isFunction(call.callee);
|
94
94
|
}
|