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
package/lib/rules/key-spacing.js
CHANGED
@@ -433,11 +433,15 @@ module.exports = {
|
|
433
433
|
tokenBeforeColon = sourceCode.getTokenBefore(nextColon, { includeComments: true }),
|
434
434
|
tokenAfterColon = sourceCode.getTokenAfter(nextColon, { includeComments: true }),
|
435
435
|
isKeySide = side === "key",
|
436
|
-
locStart = isKeySide ? tokenBeforeColon.loc.start : tokenAfterColon.loc.start,
|
437
436
|
isExtra = diff > 0,
|
438
437
|
diffAbs = Math.abs(diff),
|
439
438
|
spaces = Array(diffAbs + 1).join(" ");
|
440
439
|
|
440
|
+
const locStart = isKeySide ? tokenBeforeColon.loc.end : nextColon.loc.start;
|
441
|
+
const locEnd = isKeySide ? nextColon.loc.start : tokenAfterColon.loc.start;
|
442
|
+
const missingLoc = isKeySide ? tokenBeforeColon.loc : tokenAfterColon.loc;
|
443
|
+
const loc = isExtra ? { start: locStart, end: locEnd } : missingLoc;
|
444
|
+
|
441
445
|
if ((
|
442
446
|
diff && mode === "strict" ||
|
443
447
|
diff < 0 && mode === "minimum" ||
|
@@ -482,7 +486,7 @@ module.exports = {
|
|
482
486
|
|
483
487
|
context.report({
|
484
488
|
node: property[side],
|
485
|
-
loc
|
489
|
+
loc,
|
486
490
|
messageId,
|
487
491
|
data: {
|
488
492
|
computed: property.computed ? "computed " : "",
|
@@ -126,7 +126,7 @@ module.exports = {
|
|
126
126
|
!sourceCode.isSpaceBetweenTokens(prevToken, token)
|
127
127
|
) {
|
128
128
|
context.report({
|
129
|
-
loc: token.loc
|
129
|
+
loc: token.loc,
|
130
130
|
messageId: "expectedBefore",
|
131
131
|
data: token,
|
132
132
|
fix(fixer) {
|
@@ -178,7 +178,7 @@ module.exports = {
|
|
178
178
|
!sourceCode.isSpaceBetweenTokens(token, nextToken)
|
179
179
|
) {
|
180
180
|
context.report({
|
181
|
-
loc: token.loc
|
181
|
+
loc: token.loc,
|
182
182
|
messageId: "expectedAfter",
|
183
183
|
data: token,
|
184
184
|
fix(fixer) {
|
package/lib/rules/max-len.js
CHANGED
@@ -383,11 +383,22 @@ module.exports = {
|
|
383
383
|
return;
|
384
384
|
}
|
385
385
|
|
386
|
+
const loc = {
|
387
|
+
start: {
|
388
|
+
line: lineNumber,
|
389
|
+
column: 0
|
390
|
+
},
|
391
|
+
end: {
|
392
|
+
line: lineNumber,
|
393
|
+
column: textToMeasure.length
|
394
|
+
}
|
395
|
+
};
|
396
|
+
|
386
397
|
if (commentLengthApplies) {
|
387
398
|
if (lineLength > maxCommentLength) {
|
388
399
|
context.report({
|
389
400
|
node,
|
390
|
-
loc
|
401
|
+
loc,
|
391
402
|
messageId: "maxComment",
|
392
403
|
data: {
|
393
404
|
lineLength,
|
@@ -398,7 +409,7 @@ module.exports = {
|
|
398
409
|
} else if (lineLength > maxLength) {
|
399
410
|
context.report({
|
400
411
|
node,
|
401
|
-
loc
|
412
|
+
loc,
|
402
413
|
messageId: "max",
|
403
414
|
data: {
|
404
415
|
lineLength,
|
package/lib/rules/max-lines.js
CHANGED
@@ -53,7 +53,8 @@ module.exports = {
|
|
53
53
|
}
|
54
54
|
],
|
55
55
|
messages: {
|
56
|
-
exceed:
|
56
|
+
exceed:
|
57
|
+
"File has too many lines ({{actual}}). Maximum allowed is {{max}}."
|
57
58
|
}
|
58
59
|
},
|
59
60
|
|
@@ -61,7 +62,10 @@ module.exports = {
|
|
61
62
|
const option = context.options[0];
|
62
63
|
let max = 300;
|
63
64
|
|
64
|
-
if (
|
65
|
+
if (
|
66
|
+
typeof option === "object" &&
|
67
|
+
Object.prototype.hasOwnProperty.call(option, "max")
|
68
|
+
) {
|
65
69
|
max = option.max;
|
66
70
|
} else if (typeof option === "number") {
|
67
71
|
max = option;
|
@@ -94,7 +98,9 @@ module.exports = {
|
|
94
98
|
|
95
99
|
token = comment;
|
96
100
|
do {
|
97
|
-
token = sourceCode.getTokenBefore(token, {
|
101
|
+
token = sourceCode.getTokenBefore(token, {
|
102
|
+
includeComments: true
|
103
|
+
});
|
98
104
|
} while (isCommentNodeType(token));
|
99
105
|
|
100
106
|
if (token && astUtils.isTokenOnSameLine(token, comment)) {
|
@@ -103,7 +109,9 @@ module.exports = {
|
|
103
109
|
|
104
110
|
token = comment;
|
105
111
|
do {
|
106
|
-
token = sourceCode.getTokenAfter(token, {
|
112
|
+
token = sourceCode.getTokenAfter(token, {
|
113
|
+
includeComments: true
|
114
|
+
});
|
107
115
|
} while (isCommentNodeType(token));
|
108
116
|
|
109
117
|
if (token && astUtils.isTokenOnSameLine(comment, token)) {
|
@@ -118,7 +126,10 @@ module.exports = {
|
|
118
126
|
|
119
127
|
return {
|
120
128
|
"Program:exit"() {
|
121
|
-
let lines = sourceCode.lines.map((text, i) => ({
|
129
|
+
let lines = sourceCode.lines.map((text, i) => ({
|
130
|
+
lineNumber: i + 1,
|
131
|
+
text
|
132
|
+
}));
|
122
133
|
|
123
134
|
if (skipBlankLines) {
|
124
135
|
lines = lines.filter(l => l.text.trim() !== "");
|
@@ -127,14 +138,29 @@ module.exports = {
|
|
127
138
|
if (skipComments) {
|
128
139
|
const comments = sourceCode.getAllComments();
|
129
140
|
|
130
|
-
const commentLines = lodash.flatten(
|
141
|
+
const commentLines = lodash.flatten(
|
142
|
+
comments.map(comment => getLinesWithoutCode(comment))
|
143
|
+
);
|
131
144
|
|
132
|
-
lines = lines.filter(
|
145
|
+
lines = lines.filter(
|
146
|
+
l => !lodash.includes(commentLines, l.lineNumber)
|
147
|
+
);
|
133
148
|
}
|
134
149
|
|
135
150
|
if (lines.length > max) {
|
151
|
+
const loc = {
|
152
|
+
start: {
|
153
|
+
line: lines[max].lineNumber,
|
154
|
+
column: 0
|
155
|
+
},
|
156
|
+
end: {
|
157
|
+
line: sourceCode.lines.length,
|
158
|
+
column: lodash.last(sourceCode.lines).length
|
159
|
+
}
|
160
|
+
};
|
161
|
+
|
136
162
|
context.report({
|
137
|
-
loc
|
163
|
+
loc,
|
138
164
|
messageId: "exceed",
|
139
165
|
data: {
|
140
166
|
max,
|
package/lib/rules/new-cap.js
CHANGED
@@ -158,15 +158,9 @@ module.exports = {
|
|
158
158
|
* @returns {string} name
|
159
159
|
*/
|
160
160
|
function extractNameFromExpression(node) {
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
if (node.callee.type === "MemberExpression") {
|
165
|
-
name = astUtils.getStaticPropertyName(node.callee) || "";
|
166
|
-
} else {
|
167
|
-
name = node.callee.name;
|
168
|
-
}
|
169
|
-
return name;
|
161
|
+
return node.callee.type === "Identifier"
|
162
|
+
? node.callee.name
|
163
|
+
: astUtils.getStaticPropertyName(node.callee) || "";
|
170
164
|
}
|
171
165
|
|
172
166
|
/**
|
@@ -212,14 +206,16 @@ module.exports = {
|
|
212
206
|
return true;
|
213
207
|
}
|
214
208
|
|
215
|
-
|
209
|
+
const callee = astUtils.skipChainExpression(node.callee);
|
210
|
+
|
211
|
+
if (calleeName === "UTC" && callee.type === "MemberExpression") {
|
216
212
|
|
217
213
|
// allow if callee is Date.UTC
|
218
|
-
return
|
219
|
-
|
214
|
+
return callee.object.type === "Identifier" &&
|
215
|
+
callee.object.name === "Date";
|
220
216
|
}
|
221
217
|
|
222
|
-
return skipProperties &&
|
218
|
+
return skipProperties && callee.type === "MemberExpression";
|
223
219
|
}
|
224
220
|
|
225
221
|
/**
|
@@ -229,7 +225,7 @@ module.exports = {
|
|
229
225
|
* @returns {void}
|
230
226
|
*/
|
231
227
|
function report(node, messageId) {
|
232
|
-
let callee = node.callee;
|
228
|
+
let callee = astUtils.skipChainExpression(node.callee);
|
233
229
|
|
234
230
|
if (callee.type === "MemberExpression") {
|
235
231
|
callee = callee.property;
|
@@ -57,7 +57,16 @@ module.exports = {
|
|
57
57
|
* @returns {string} The prefix of the node.
|
58
58
|
*/
|
59
59
|
function getPrefix(node) {
|
60
|
-
|
60
|
+
if (node.computed) {
|
61
|
+
if (node.optional) {
|
62
|
+
return "?.[";
|
63
|
+
}
|
64
|
+
return "[";
|
65
|
+
}
|
66
|
+
if (node.optional) {
|
67
|
+
return "?.";
|
68
|
+
}
|
69
|
+
return ".";
|
61
70
|
}
|
62
71
|
|
63
72
|
/**
|
@@ -76,17 +85,18 @@ module.exports = {
|
|
76
85
|
|
77
86
|
return {
|
78
87
|
"CallExpression:exit"(node) {
|
79
|
-
|
88
|
+
const callee = astUtils.skipChainExpression(node.callee);
|
89
|
+
|
90
|
+
if (callee.type !== "MemberExpression") {
|
80
91
|
return;
|
81
92
|
}
|
82
93
|
|
83
|
-
|
84
|
-
let parent = callee.object;
|
94
|
+
let parent = astUtils.skipChainExpression(callee.object);
|
85
95
|
let depth = 1;
|
86
96
|
|
87
97
|
while (parent && parent.callee) {
|
88
98
|
depth += 1;
|
89
|
-
parent = parent.callee.object;
|
99
|
+
parent = astUtils.skipChainExpression(astUtils.skipChainExpression(parent.callee).object);
|
90
100
|
}
|
91
101
|
|
92
102
|
if (depth > ignoreChainWithDepth && astUtils.isTokenOnSameLine(callee.object, callee.property)) {
|
package/lib/rules/no-alert.js
CHANGED
@@ -10,7 +10,8 @@
|
|
10
10
|
|
11
11
|
const {
|
12
12
|
getStaticPropertyName: getPropertyName,
|
13
|
-
getVariableByName
|
13
|
+
getVariableByName,
|
14
|
+
skipChainExpression
|
14
15
|
} = require("./utils/ast-utils");
|
15
16
|
|
16
17
|
//------------------------------------------------------------------------------
|
@@ -64,7 +65,13 @@ function isGlobalThisReferenceOrGlobalWindow(scope, node) {
|
|
64
65
|
if (scope.type === "global" && node.type === "ThisExpression") {
|
65
66
|
return true;
|
66
67
|
}
|
67
|
-
if (
|
68
|
+
if (
|
69
|
+
node.type === "Identifier" &&
|
70
|
+
(
|
71
|
+
node.name === "window" ||
|
72
|
+
(node.name === "globalThis" && getVariableByName(scope, "globalThis"))
|
73
|
+
)
|
74
|
+
) {
|
68
75
|
return !isShadowed(scope, node);
|
69
76
|
}
|
70
77
|
|
@@ -96,7 +103,7 @@ module.exports = {
|
|
96
103
|
create(context) {
|
97
104
|
return {
|
98
105
|
CallExpression(node) {
|
99
|
-
const callee = node.callee,
|
106
|
+
const callee = skipChainExpression(node.callee),
|
100
107
|
currentScope = context.getScope();
|
101
108
|
|
102
109
|
// without window.
|
package/lib/rules/no-eval.js
CHANGED
@@ -21,38 +21,6 @@ const candidatesOfGlobalObject = Object.freeze([
|
|
21
21
|
"globalThis"
|
22
22
|
]);
|
23
23
|
|
24
|
-
/**
|
25
|
-
* Checks a given node is a Identifier node of the specified name.
|
26
|
-
* @param {ASTNode} node A node to check.
|
27
|
-
* @param {string} name A name to check.
|
28
|
-
* @returns {boolean} `true` if the node is a Identifier node of the name.
|
29
|
-
*/
|
30
|
-
function isIdentifier(node, name) {
|
31
|
-
return node.type === "Identifier" && node.name === name;
|
32
|
-
}
|
33
|
-
|
34
|
-
/**
|
35
|
-
* Checks a given node is a Literal node of the specified string value.
|
36
|
-
* @param {ASTNode} node A node to check.
|
37
|
-
* @param {string} name A name to check.
|
38
|
-
* @returns {boolean} `true` if the node is a Literal node of the name.
|
39
|
-
*/
|
40
|
-
function isConstant(node, name) {
|
41
|
-
switch (node.type) {
|
42
|
-
case "Literal":
|
43
|
-
return node.value === name;
|
44
|
-
|
45
|
-
case "TemplateLiteral":
|
46
|
-
return (
|
47
|
-
node.expressions.length === 0 &&
|
48
|
-
node.quasis[0].value.cooked === name
|
49
|
-
);
|
50
|
-
|
51
|
-
default:
|
52
|
-
return false;
|
53
|
-
}
|
54
|
-
}
|
55
|
-
|
56
24
|
/**
|
57
25
|
* Checks a given node is a MemberExpression node which has the specified name's
|
58
26
|
* property.
|
@@ -62,10 +30,7 @@ function isConstant(node, name) {
|
|
62
30
|
* the specified name's property
|
63
31
|
*/
|
64
32
|
function isMember(node, name) {
|
65
|
-
return (
|
66
|
-
node.type === "MemberExpression" &&
|
67
|
-
(node.computed ? isConstant : isIdentifier)(node.property, name)
|
68
|
-
);
|
33
|
+
return astUtils.isSpecificMemberAccess(node, null, name);
|
69
34
|
}
|
70
35
|
|
71
36
|
//------------------------------------------------------------------------------
|
@@ -230,7 +195,12 @@ module.exports = {
|
|
230
195
|
"CallExpression:exit"(node) {
|
231
196
|
const callee = node.callee;
|
232
197
|
|
233
|
-
|
198
|
+
/*
|
199
|
+
* Optional call (`eval?.("code")`) is not direct eval.
|
200
|
+
* The direct eval is only step 6.a.vi of https://tc39.es/ecma262/#sec-function-calls-runtime-semantics-evaluation
|
201
|
+
* But the optional call is https://tc39.es/ecma262/#sec-optional-chaining-chain-evaluation
|
202
|
+
*/
|
203
|
+
if (!node.optional && astUtils.isSpecificId(callee, "eval")) {
|
234
204
|
report(callee);
|
235
205
|
}
|
236
206
|
}
|
@@ -241,7 +211,7 @@ module.exports = {
|
|
241
211
|
"CallExpression:exit"(node) {
|
242
212
|
const callee = node.callee;
|
243
213
|
|
244
|
-
if (
|
214
|
+
if (astUtils.isSpecificId(callee, "eval")) {
|
245
215
|
report(callee);
|
246
216
|
}
|
247
217
|
},
|
@@ -12,12 +12,6 @@
|
|
12
12
|
const astUtils = require("./utils/ast-utils");
|
13
13
|
const globals = require("globals");
|
14
14
|
|
15
|
-
//------------------------------------------------------------------------------
|
16
|
-
// Helpers
|
17
|
-
//------------------------------------------------------------------------------
|
18
|
-
|
19
|
-
const propertyDefinitionMethods = new Set(["defineProperty", "defineProperties"]);
|
20
|
-
|
21
15
|
//------------------------------------------------------------------------------
|
22
16
|
// Rule Definition
|
23
17
|
//------------------------------------------------------------------------------
|
@@ -100,40 +94,30 @@ module.exports = {
|
|
100
94
|
}
|
101
95
|
|
102
96
|
/**
|
103
|
-
*
|
104
|
-
*
|
105
|
-
*
|
106
|
-
* @
|
107
|
-
* @returns {boolean} True if the identifier's prototype is modified.
|
97
|
+
* Check if it's an assignment to the property of the given node.
|
98
|
+
* Example: `*.prop = 0` // the `*` is the given node.
|
99
|
+
* @param {ASTNode} node The node to check.
|
100
|
+
* @returns {boolean} True if an assignment to the property of the node.
|
108
101
|
*/
|
109
|
-
function
|
110
|
-
return
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
102
|
+
function isAssigningToPropertyOf(node) {
|
103
|
+
return (
|
104
|
+
node.parent.type === "MemberExpression" &&
|
105
|
+
node.parent.object === node &&
|
106
|
+
node.parent.parent.type === "AssignmentExpression" &&
|
107
|
+
node.parent.parent.left === node.parent
|
115
108
|
);
|
116
109
|
}
|
117
110
|
|
118
111
|
/**
|
119
|
-
* Checks
|
120
|
-
*
|
121
|
-
* Object.
|
122
|
-
* Example: Object.defineProperty(Array.prototype, "foo", ...)
|
123
|
-
* Example: Object.defineProperties(Array.prototype, ...)
|
124
|
-
* @param {ASTNode} identifierNode The identifier to check.
|
125
|
-
* @returns {boolean} True if the identifier's prototype is modified.
|
112
|
+
* Checks if the given node is at the first argument of the method call of `Object.defineProperty()` or `Object.defineProperties()`.
|
113
|
+
* @param {ASTNode} node The node to check.
|
114
|
+
* @returns {boolean} True if the node is at the first argument of the method call of `Object.defineProperty()` or `Object.defineProperties()`.
|
126
115
|
*/
|
127
|
-
function isInDefinePropertyCall(
|
128
|
-
return
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
identifierNode.parent.parent.callee.type === "MemberExpression" &&
|
133
|
-
identifierNode.parent.parent.callee.object.type === "Identifier" &&
|
134
|
-
identifierNode.parent.parent.callee.object.name === "Object" &&
|
135
|
-
identifierNode.parent.parent.callee.property.type === "Identifier" &&
|
136
|
-
propertyDefinitionMethods.has(identifierNode.parent.parent.callee.property.name)
|
116
|
+
function isInDefinePropertyCall(node) {
|
117
|
+
return (
|
118
|
+
node.parent.type === "CallExpression" &&
|
119
|
+
node.parent.arguments[0] === node &&
|
120
|
+
astUtils.isSpecificMemberAccess(node.parent.callee, "Object", /^definePropert(?:y|ies)$/u)
|
137
121
|
);
|
138
122
|
}
|
139
123
|
|
@@ -149,14 +133,27 @@ module.exports = {
|
|
149
133
|
* @returns {void}
|
150
134
|
*/
|
151
135
|
function checkAndReportPrototypeExtension(identifierNode) {
|
152
|
-
if (
|
136
|
+
if (!isPrototypePropertyAccessed(identifierNode)) {
|
137
|
+
return; // This is not `*.prototype` access.
|
138
|
+
}
|
139
|
+
|
140
|
+
/*
|
141
|
+
* `identifierNode.parent` is a MamberExpression `*.prototype`.
|
142
|
+
* If it's an optional member access, it may be wrapped by a `ChainExpression` node.
|
143
|
+
*/
|
144
|
+
const prototypeNode =
|
145
|
+
identifierNode.parent.parent.type === "ChainExpression"
|
146
|
+
? identifierNode.parent.parent
|
147
|
+
: identifierNode.parent;
|
148
|
+
|
149
|
+
if (isAssigningToPropertyOf(prototypeNode)) {
|
153
150
|
|
154
|
-
//
|
155
|
-
reportNode(
|
156
|
-
} else if (isInDefinePropertyCall(
|
151
|
+
// `*.prototype` -> MemberExpression -> AssignmentExpression
|
152
|
+
reportNode(prototypeNode.parent.parent, identifierNode.name);
|
153
|
+
} else if (isInDefinePropertyCall(prototypeNode)) {
|
157
154
|
|
158
|
-
//
|
159
|
-
reportNode(
|
155
|
+
// `*.prototype` -> CallExpression
|
156
|
+
reportNode(prototypeNode.parent, identifierNode.name);
|
160
157
|
}
|
161
158
|
}
|
162
159
|
|
@@ -61,24 +61,62 @@ module.exports = {
|
|
61
61
|
* @returns {void}
|
62
62
|
*/
|
63
63
|
function report(node) {
|
64
|
+
const memberNode = node.parent;
|
65
|
+
const callNode = memberNode.parent.type === "ChainExpression"
|
66
|
+
? memberNode.parent.parent
|
67
|
+
: memberNode.parent;
|
68
|
+
|
64
69
|
context.report({
|
65
|
-
node:
|
70
|
+
node: callNode,
|
66
71
|
messageId: "unexpected",
|
67
|
-
loc:
|
72
|
+
loc: memberNode.property.loc,
|
73
|
+
|
68
74
|
fix(fixer) {
|
69
|
-
if (
|
75
|
+
if (!isSideEffectFree(callNode.arguments[0])) {
|
70
76
|
return null;
|
71
77
|
}
|
72
78
|
|
73
|
-
|
74
|
-
|
75
|
-
|
79
|
+
/*
|
80
|
+
* The list of the first/last token pair of a removal range.
|
81
|
+
* This is two parts because closing parentheses may exist between the method name and arguments.
|
82
|
+
* E.g. `(function(){}.bind ) (obj)`
|
83
|
+
* ^^^^^ ^^^^^ < removal ranges
|
84
|
+
* E.g. `(function(){}?.['bind'] ) ?.(obj)`
|
85
|
+
* ^^^^^^^^^^ ^^^^^^^ < removal ranges
|
86
|
+
*/
|
87
|
+
const tokenPairs = [
|
88
|
+
[
|
89
|
+
|
90
|
+
// `.`, `?.`, or `[` token.
|
91
|
+
sourceCode.getTokenAfter(
|
92
|
+
memberNode.object,
|
93
|
+
astUtils.isNotClosingParenToken
|
94
|
+
),
|
95
|
+
|
96
|
+
// property name or `]` token.
|
97
|
+
sourceCode.getLastToken(memberNode)
|
98
|
+
],
|
99
|
+
[
|
100
|
+
|
101
|
+
// `?.` or `(` token of arguments.
|
102
|
+
sourceCode.getTokenAfter(
|
103
|
+
memberNode,
|
104
|
+
astUtils.isNotClosingParenToken
|
105
|
+
),
|
106
|
+
|
107
|
+
// `)` token of arguments.
|
108
|
+
sourceCode.getLastToken(callNode)
|
109
|
+
]
|
110
|
+
];
|
111
|
+
const firstTokenToRemove = tokenPairs[0][0];
|
112
|
+
const lastTokenToRemove = tokenPairs[1][1];
|
76
113
|
|
77
114
|
if (sourceCode.commentsExistBetween(firstTokenToRemove, lastTokenToRemove)) {
|
78
115
|
return null;
|
79
116
|
}
|
80
117
|
|
81
|
-
return
|
118
|
+
return tokenPairs.map(([start, end]) =>
|
119
|
+
fixer.removeRange([start.range[0], end.range[1]]));
|
82
120
|
}
|
83
121
|
});
|
84
122
|
}
|
@@ -93,18 +131,20 @@ module.exports = {
|
|
93
131
|
* @returns {boolean} `true` if the node is the callee of `.bind()` method.
|
94
132
|
*/
|
95
133
|
function isCalleeOfBindMethod(node) {
|
96
|
-
|
97
|
-
|
134
|
+
if (!astUtils.isSpecificMemberAccess(node.parent, null, "bind")) {
|
135
|
+
return false;
|
136
|
+
}
|
137
|
+
|
138
|
+
// The node of `*.bind` member access.
|
139
|
+
const bindNode = node.parent.parent.type === "ChainExpression"
|
140
|
+
? node.parent.parent
|
141
|
+
: node.parent;
|
98
142
|
|
99
143
|
return (
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
grandparent.arguments[0].type !== "SpreadElement" &&
|
105
|
-
parent.type === "MemberExpression" &&
|
106
|
-
parent.object === node &&
|
107
|
-
astUtils.getStaticPropertyName(parent) === "bind"
|
144
|
+
bindNode.parent.type === "CallExpression" &&
|
145
|
+
bindNode.parent.callee === bindNode &&
|
146
|
+
bindNode.parent.arguments.length === 1 &&
|
147
|
+
bindNode.parent.arguments[0].type !== "SpreadElement"
|
108
148
|
);
|
109
149
|
}
|
110
150
|
|
@@ -111,6 +111,10 @@ module.exports = {
|
|
111
111
|
* @returns {boolean} If the node is in one of the flagged contexts
|
112
112
|
*/
|
113
113
|
function isInFlaggedContext(node) {
|
114
|
+
if (node.parent.type === "ChainExpression") {
|
115
|
+
return isInFlaggedContext(node.parent);
|
116
|
+
}
|
117
|
+
|
114
118
|
return isInBooleanContext(node) ||
|
115
119
|
(isLogicalContext(node.parent) &&
|
116
120
|
|
@@ -149,6 +153,9 @@ module.exports = {
|
|
149
153
|
* @returns {boolean} `true` if the node needs to be parenthesized.
|
150
154
|
*/
|
151
155
|
function needsParens(previousNode, node) {
|
156
|
+
if (previousNode.parent.type === "ChainExpression") {
|
157
|
+
return needsParens(previousNode.parent, node);
|
158
|
+
}
|
152
159
|
if (isParenthesized(previousNode)) {
|
153
160
|
|
154
161
|
// parentheses around the previous node will stay, so there is no need for an additional pair
|