eslint 7.3.0 → 7.6.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 +63 -0
- package/README.md +5 -3
- package/lib/cli-engine/config-array-factory.js +1 -28
- package/lib/cli-engine/formatters/checkstyle.js +2 -2
- 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 +10 -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/consistent-return.js +1 -12
- package/lib/rules/constructor-super.js +1 -0
- 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/indent.js +19 -0
- package/lib/rules/index.js +1 -0
- package/lib/rules/keyword-spacing.js +2 -2
- package/lib/rules/max-len.js +13 -2
- 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-duplicate-case.js +23 -4
- 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-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-unused-expressions.js +55 -23
- 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/operator-assignment.js +3 -42
- 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/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/messages/extend-config-missing.txt +1 -1
- package/messages/no-config-found.txt +1 -1
- package/messages/plugin-conflict.txt +1 -1
- package/messages/plugin-missing.txt +1 -1
- package/messages/whitespace-found.txt +1 -1
- package/package.json +6 -6
@@ -24,10 +24,13 @@ const nonCallableGlobals = ["Atomics", "JSON", "Math", "Reflect"];
|
|
24
24
|
* @returns {string} name to report
|
25
25
|
*/
|
26
26
|
function getReportNodeName(node) {
|
27
|
-
if (node.
|
28
|
-
return
|
27
|
+
if (node.type === "ChainExpression") {
|
28
|
+
return getReportNodeName(node.expression);
|
29
29
|
}
|
30
|
-
|
30
|
+
if (node.type === "MemberExpression") {
|
31
|
+
return getPropertyName(node);
|
32
|
+
}
|
33
|
+
return node.name;
|
31
34
|
}
|
32
35
|
|
33
36
|
//------------------------------------------------------------------------------
|
@@ -69,7 +72,7 @@ module.exports = {
|
|
69
72
|
}
|
70
73
|
|
71
74
|
for (const { node, path } of tracker.iterateGlobalReferences(traceMap)) {
|
72
|
-
const name = getReportNodeName(node);
|
75
|
+
const name = getReportNodeName(node.callee);
|
73
76
|
const ref = path[0];
|
74
77
|
const messageId = name === ref ? "unexpectedCall" : "unexpectedRefCall";
|
75
78
|
|
@@ -4,6 +4,12 @@
|
|
4
4
|
*/
|
5
5
|
"use strict";
|
6
6
|
|
7
|
+
//------------------------------------------------------------------------------
|
8
|
+
// Requirements
|
9
|
+
//------------------------------------------------------------------------------
|
10
|
+
|
11
|
+
const astUtils = require("./utils/ast-utils");
|
12
|
+
|
7
13
|
//------------------------------------------------------------------------------
|
8
14
|
// Rule Definition
|
9
15
|
//------------------------------------------------------------------------------
|
@@ -39,15 +45,19 @@ module.exports = {
|
|
39
45
|
* @returns {void}
|
40
46
|
*/
|
41
47
|
function disallowBuiltIns(node) {
|
42
|
-
|
48
|
+
|
49
|
+
// TODO: just use `astUtils.getStaticPropertyName(node.callee)`
|
50
|
+
const callee = astUtils.skipChainExpression(node.callee);
|
51
|
+
|
52
|
+
if (callee.type !== "MemberExpression" || callee.computed) {
|
43
53
|
return;
|
44
54
|
}
|
45
|
-
const propName =
|
55
|
+
const propName = callee.property.name;
|
46
56
|
|
47
57
|
if (DISALLOWED_PROPS.indexOf(propName) > -1) {
|
48
58
|
context.report({
|
49
59
|
messageId: "prototypeBuildIn",
|
50
|
-
loc:
|
60
|
+
loc: callee.property.loc,
|
51
61
|
data: { prop: propName },
|
52
62
|
node
|
53
63
|
});
|
@@ -17,56 +17,6 @@ const astUtils = require("./utils/ast-utils");
|
|
17
17
|
|
18
18
|
const SPACES = /\s+/gu;
|
19
19
|
|
20
|
-
/**
|
21
|
-
* Checks whether the property of 2 given member expression nodes are the same
|
22
|
-
* property or not.
|
23
|
-
* @param {ASTNode} left A member expression node to check.
|
24
|
-
* @param {ASTNode} right Another member expression node to check.
|
25
|
-
* @returns {boolean} `true` if the member expressions have the same property.
|
26
|
-
*/
|
27
|
-
function isSameProperty(left, right) {
|
28
|
-
if (left.property.type === "Identifier" &&
|
29
|
-
left.property.type === right.property.type &&
|
30
|
-
left.property.name === right.property.name &&
|
31
|
-
left.computed === right.computed
|
32
|
-
) {
|
33
|
-
return true;
|
34
|
-
}
|
35
|
-
|
36
|
-
const lname = astUtils.getStaticPropertyName(left);
|
37
|
-
const rname = astUtils.getStaticPropertyName(right);
|
38
|
-
|
39
|
-
return lname !== null && lname === rname;
|
40
|
-
}
|
41
|
-
|
42
|
-
/**
|
43
|
-
* Checks whether 2 given member expression nodes are the reference to the same
|
44
|
-
* property or not.
|
45
|
-
* @param {ASTNode} left A member expression node to check.
|
46
|
-
* @param {ASTNode} right Another member expression node to check.
|
47
|
-
* @returns {boolean} `true` if the member expressions are the reference to the
|
48
|
-
* same property or not.
|
49
|
-
*/
|
50
|
-
function isSameMember(left, right) {
|
51
|
-
if (!isSameProperty(left, right)) {
|
52
|
-
return false;
|
53
|
-
}
|
54
|
-
|
55
|
-
const lobj = left.object;
|
56
|
-
const robj = right.object;
|
57
|
-
|
58
|
-
if (lobj.type !== robj.type) {
|
59
|
-
return false;
|
60
|
-
}
|
61
|
-
if (lobj.type === "MemberExpression") {
|
62
|
-
return isSameMember(lobj, robj);
|
63
|
-
}
|
64
|
-
if (lobj.type === "ThisExpression") {
|
65
|
-
return true;
|
66
|
-
}
|
67
|
-
return lobj.type === "Identifier" && lobj.name === robj.name;
|
68
|
-
}
|
69
|
-
|
70
20
|
/**
|
71
21
|
* Traverses 2 Pattern nodes in parallel, then reports self-assignments.
|
72
22
|
* @param {ASTNode|null} left A left node to traverse. This is a Pattern or
|
@@ -162,9 +112,9 @@ function eachSelfAssignment(left, right, props, report) {
|
|
162
112
|
}
|
163
113
|
} else if (
|
164
114
|
props &&
|
165
|
-
left.type === "MemberExpression" &&
|
166
|
-
right.type === "MemberExpression" &&
|
167
|
-
|
115
|
+
astUtils.skipChainExpression(left).type === "MemberExpression" &&
|
116
|
+
astUtils.skipChainExpression(right).type === "MemberExpression" &&
|
117
|
+
astUtils.isSameReference(left, right)
|
168
118
|
) {
|
169
119
|
report(right);
|
170
120
|
}
|
@@ -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 =
|
@@ -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;
|
@@ -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.
|
@@ -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
|
}
|
@@ -5,6 +5,8 @@
|
|
5
5
|
|
6
6
|
"use strict";
|
7
7
|
|
8
|
+
const astUtils = require("./utils/ast-utils");
|
9
|
+
|
8
10
|
//------------------------------------------------------------------------------
|
9
11
|
// Helpers
|
10
12
|
//------------------------------------------------------------------------------
|
@@ -66,6 +68,7 @@ function getCallbackInfo(node) {
|
|
66
68
|
const retv = { isCallback: false, isLexicalThis: false };
|
67
69
|
let currentNode = node;
|
68
70
|
let parent = node.parent;
|
71
|
+
let bound = false;
|
69
72
|
|
70
73
|
while (currentNode) {
|
71
74
|
switch (parent.type) {
|
@@ -73,23 +76,34 @@ function getCallbackInfo(node) {
|
|
73
76
|
// Checks parents recursively.
|
74
77
|
|
75
78
|
case "LogicalExpression":
|
79
|
+
case "ChainExpression":
|
76
80
|
case "ConditionalExpression":
|
77
81
|
break;
|
78
82
|
|
79
83
|
// Checks whether the parent node is `.bind(this)` call.
|
80
84
|
case "MemberExpression":
|
81
|
-
if (
|
85
|
+
if (
|
86
|
+
parent.object === currentNode &&
|
82
87
|
!parent.property.computed &&
|
83
88
|
parent.property.type === "Identifier" &&
|
84
|
-
parent.property.name === "bind"
|
85
|
-
parent.parent.type === "CallExpression" &&
|
86
|
-
parent.parent.callee === parent
|
89
|
+
parent.property.name === "bind"
|
87
90
|
) {
|
88
|
-
|
89
|
-
parent.parent
|
90
|
-
parent
|
91
|
-
|
92
|
-
|
91
|
+
const maybeCallee = parent.parent.type === "ChainExpression"
|
92
|
+
? parent.parent
|
93
|
+
: parent;
|
94
|
+
|
95
|
+
if (astUtils.isCallee(maybeCallee)) {
|
96
|
+
if (!bound) {
|
97
|
+
bound = true; // Use only the first `.bind()` to make `isLexicalThis` value.
|
98
|
+
retv.isLexicalThis = (
|
99
|
+
maybeCallee.parent.arguments.length === 1 &&
|
100
|
+
maybeCallee.parent.arguments[0].type === "ThisExpression"
|
101
|
+
);
|
102
|
+
}
|
103
|
+
parent = maybeCallee.parent;
|
104
|
+
} else {
|
105
|
+
return retv;
|
106
|
+
}
|
93
107
|
} else {
|
94
108
|
return retv;
|
95
109
|
}
|
@@ -272,7 +286,7 @@ module.exports = {
|
|
272
286
|
context.report({
|
273
287
|
node,
|
274
288
|
messageId: "preferArrowCallback",
|
275
|
-
fix(fixer) {
|
289
|
+
*fix(fixer) {
|
276
290
|
if ((!callbackInfo.isLexicalThis && scopeInfo.this) || hasDuplicateParams(node.params)) {
|
277
291
|
|
278
292
|
/*
|
@@ -281,30 +295,81 @@ module.exports = {
|
|
281
295
|
* If the callback function has duplicates in its list of parameters (possible in sloppy mode),
|
282
296
|
* don't replace it with an arrow function, because this is a SyntaxError with arrow functions.
|
283
297
|
*/
|
284
|
-
return
|
298
|
+
return; // eslint-disable-line eslint-plugin/fixer-return -- false positive
|
285
299
|
}
|
286
300
|
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
const paramsFullText = sourceCode.text.slice(paramsLeftParen.range[0], paramsRightParen.range[1]);
|
291
|
-
const arrowFunctionText = `${asyncKeyword}${paramsFullText} => ${sourceCode.getText(node.body)}`;
|
301
|
+
// Remove `.bind(this)` if exists.
|
302
|
+
if (callbackInfo.isLexicalThis) {
|
303
|
+
const memberNode = node.parent;
|
292
304
|
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
305
|
+
/*
|
306
|
+
* If `.bind(this)` exists but the parent is not `.bind(this)`, don't remove it automatically.
|
307
|
+
* E.g. `(foo || function(){}).bind(this)`
|
308
|
+
*/
|
309
|
+
if (memberNode.type !== "MemberExpression") {
|
310
|
+
return; // eslint-disable-line eslint-plugin/fixer-return -- false positive
|
311
|
+
}
|
312
|
+
|
313
|
+
const callNode = memberNode.parent;
|
314
|
+
const firstTokenToRemove = sourceCode.getTokenAfter(memberNode.object, astUtils.isNotClosingParenToken);
|
315
|
+
const lastTokenToRemove = sourceCode.getLastToken(callNode);
|
316
|
+
|
317
|
+
/*
|
318
|
+
* If the member expression is parenthesized, don't remove the right paren.
|
319
|
+
* E.g. `(function(){}.bind)(this)`
|
320
|
+
* ^^^^^^^^^^^^
|
321
|
+
*/
|
322
|
+
if (astUtils.isParenthesised(sourceCode, memberNode)) {
|
323
|
+
return; // eslint-disable-line eslint-plugin/fixer-return -- false positive
|
324
|
+
}
|
325
|
+
|
326
|
+
// If comments exist in the `.bind(this)`, don't remove those.
|
327
|
+
if (sourceCode.commentsExistBetween(firstTokenToRemove, lastTokenToRemove)) {
|
328
|
+
return; // eslint-disable-line eslint-plugin/fixer-return -- false positive
|
329
|
+
}
|
330
|
+
|
331
|
+
yield fixer.removeRange([firstTokenToRemove.range[0], lastTokenToRemove.range[1]]);
|
332
|
+
}
|
333
|
+
|
334
|
+
// Convert the function expression to an arrow function.
|
335
|
+
const functionToken = sourceCode.getFirstToken(node, node.async ? 1 : 0);
|
336
|
+
const leftParenToken = sourceCode.getTokenAfter(functionToken, astUtils.isOpeningParenToken);
|
337
|
+
|
338
|
+
if (sourceCode.commentsExistBetween(functionToken, leftParenToken)) {
|
339
|
+
|
340
|
+
// Remove only extra tokens to keep comments.
|
341
|
+
yield fixer.remove(functionToken);
|
342
|
+
if (node.id) {
|
343
|
+
yield fixer.remove(node.id);
|
344
|
+
}
|
345
|
+
} else {
|
346
|
+
|
347
|
+
// Remove extra tokens and spaces.
|
348
|
+
yield fixer.removeRange([functionToken.range[0], leftParenToken.range[0]]);
|
349
|
+
}
|
350
|
+
yield fixer.insertTextBefore(node.body, "=> ");
|
351
|
+
|
352
|
+
// Get the node that will become the new arrow function.
|
353
|
+
let replacedNode = callbackInfo.isLexicalThis ? node.parent.parent : node;
|
354
|
+
|
355
|
+
if (replacedNode.type === "ChainExpression") {
|
356
|
+
replacedNode = replacedNode.parent;
|
357
|
+
}
|
298
358
|
|
299
359
|
/*
|
300
360
|
* If the replaced node is part of a BinaryExpression, LogicalExpression, or MemberExpression, then
|
301
361
|
* the arrow function needs to be parenthesized, because `foo || () => {}` is invalid syntax even
|
302
362
|
* though `foo || function() {}` is valid.
|
303
363
|
*/
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
364
|
+
if (
|
365
|
+
replacedNode.parent.type !== "CallExpression" &&
|
366
|
+
replacedNode.parent.type !== "ConditionalExpression" &&
|
367
|
+
!astUtils.isParenthesised(sourceCode, replacedNode) &&
|
368
|
+
!astUtils.isParenthesised(sourceCode, node)
|
369
|
+
) {
|
370
|
+
yield fixer.insertTextBefore(replacedNode, "(");
|
371
|
+
yield fixer.insertTextAfter(replacedNode, ")");
|
372
|
+
}
|
308
373
|
}
|
309
374
|
});
|
310
375
|
}
|