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
@@ -100,10 +100,18 @@ module.exports = {
|
|
100
100
|
* @private
|
101
101
|
*/
|
102
102
|
function isImmediateFunctionPrototypeMethodCall(node) {
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
103
|
+
const callNode = astUtils.skipChainExpression(node);
|
104
|
+
|
105
|
+
if (callNode.type !== "CallExpression") {
|
106
|
+
return false;
|
107
|
+
}
|
108
|
+
const callee = astUtils.skipChainExpression(callNode.callee);
|
109
|
+
|
110
|
+
return (
|
111
|
+
callee.type === "MemberExpression" &&
|
112
|
+
callee.object.type === "FunctionExpression" &&
|
113
|
+
["call", "apply"].includes(astUtils.getStaticPropertyName(callee))
|
114
|
+
);
|
107
115
|
}
|
108
116
|
|
109
117
|
/**
|
@@ -360,7 +368,9 @@ module.exports = {
|
|
360
368
|
* @returns {boolean} `true` if the given node is an IIFE
|
361
369
|
*/
|
362
370
|
function isIIFE(node) {
|
363
|
-
|
371
|
+
const maybeCallNode = astUtils.skipChainExpression(node);
|
372
|
+
|
373
|
+
return maybeCallNode.type === "CallExpression" && maybeCallNode.callee.type === "FunctionExpression";
|
364
374
|
}
|
365
375
|
|
366
376
|
/**
|
@@ -466,13 +476,16 @@ module.exports = {
|
|
466
476
|
|
467
477
|
if (
|
468
478
|
hasDoubleExcessParens(callee) ||
|
469
|
-
!isIIFE(node) &&
|
479
|
+
!isIIFE(node) &&
|
480
|
+
!hasNewParensException &&
|
481
|
+
!(
|
470
482
|
|
471
483
|
// Allow extra parens around a new expression if they are intervening parentheses.
|
472
484
|
node.type === "NewExpression" &&
|
473
485
|
callee.type === "MemberExpression" &&
|
474
486
|
doesMemberExpressionContainCallExpression(callee)
|
475
|
-
)
|
487
|
+
) &&
|
488
|
+
!(!node.optional && callee.type === "ChainExpression")
|
476
489
|
) {
|
477
490
|
report(node.callee);
|
478
491
|
}
|
@@ -710,6 +723,20 @@ module.exports = {
|
|
710
723
|
reportsBuffer.reports = reportsBuffer.reports.filter(r => r.node !== node);
|
711
724
|
}
|
712
725
|
|
726
|
+
/**
|
727
|
+
* Checks whether a node is a MemberExpression at NewExpression's callee.
|
728
|
+
* @param {ASTNode} node node to check.
|
729
|
+
* @returns {boolean} True if the node is a MemberExpression at NewExpression's callee. false otherwise.
|
730
|
+
*/
|
731
|
+
function isMemberExpInNewCallee(node) {
|
732
|
+
if (node.type === "MemberExpression") {
|
733
|
+
return node.parent.type === "NewExpression" && node.parent.callee === node
|
734
|
+
? true
|
735
|
+
: node.parent.object === node && isMemberExpInNewCallee(node.parent);
|
736
|
+
}
|
737
|
+
return false;
|
738
|
+
}
|
739
|
+
|
713
740
|
return {
|
714
741
|
ArrayExpression(node) {
|
715
742
|
node.elements
|
@@ -950,7 +977,11 @@ module.exports = {
|
|
950
977
|
LogicalExpression: checkBinaryLogical,
|
951
978
|
|
952
979
|
MemberExpression(node) {
|
953
|
-
const
|
980
|
+
const shouldAllowWrapOnce = isMemberExpInNewCallee(node) &&
|
981
|
+
doesMemberExpressionContainCallExpression(node);
|
982
|
+
const nodeObjHasExcessParens = shouldAllowWrapOnce
|
983
|
+
? hasDoubleExcessParens(node.object)
|
984
|
+
: hasExcessParens(node.object) &&
|
954
985
|
!(
|
955
986
|
isImmediateFunctionPrototypeMethodCall(node.parent) &&
|
956
987
|
node.parent.callee === node &&
|
@@ -974,8 +1005,8 @@ module.exports = {
|
|
974
1005
|
}
|
975
1006
|
|
976
1007
|
if (nodeObjHasExcessParens &&
|
977
|
-
node.object.type === "CallExpression"
|
978
|
-
|
1008
|
+
node.object.type === "CallExpression"
|
1009
|
+
) {
|
979
1010
|
report(node.object);
|
980
1011
|
}
|
981
1012
|
|
@@ -986,6 +1017,13 @@ module.exports = {
|
|
986
1017
|
report(node.object);
|
987
1018
|
}
|
988
1019
|
|
1020
|
+
if (nodeObjHasExcessParens &&
|
1021
|
+
node.optional &&
|
1022
|
+
node.object.type === "ChainExpression"
|
1023
|
+
) {
|
1024
|
+
report(node.object);
|
1025
|
+
}
|
1026
|
+
|
989
1027
|
if (node.computed && hasExcessParens(node.property)) {
|
990
1028
|
report(node.property);
|
991
1029
|
}
|
@@ -47,12 +47,14 @@ function isDoubleLogicalNegating(node) {
|
|
47
47
|
* @returns {boolean} Whether or not the node is a binary negating of `.indexOf()` method calling.
|
48
48
|
*/
|
49
49
|
function isBinaryNegatingOfIndexOf(node) {
|
50
|
+
if (node.operator !== "~") {
|
51
|
+
return false;
|
52
|
+
}
|
53
|
+
const callNode = astUtils.skipChainExpression(node.argument);
|
54
|
+
|
50
55
|
return (
|
51
|
-
|
52
|
-
|
53
|
-
node.argument.callee.type === "MemberExpression" &&
|
54
|
-
node.argument.callee.property.type === "Identifier" &&
|
55
|
-
INDEX_OF_PATTERN.test(node.argument.callee.property.name)
|
56
|
+
callNode.type === "CallExpression" &&
|
57
|
+
astUtils.isSpecificMemberAccess(callNode.callee, null, INDEX_OF_PATTERN)
|
56
58
|
);
|
57
59
|
}
|
58
60
|
|
@@ -246,7 +248,10 @@ module.exports = {
|
|
246
248
|
// ~foo.indexOf(bar)
|
247
249
|
operatorAllowed = options.allow.indexOf("~") >= 0;
|
248
250
|
if (!operatorAllowed && options.boolean && isBinaryNegatingOfIndexOf(node)) {
|
249
|
-
|
251
|
+
|
252
|
+
// `foo?.indexOf(bar) !== -1` will be true (== found) if the `foo` is nullish. So use `>= 0` in that case.
|
253
|
+
const comparison = node.argument.type === "ChainExpression" ? ">= 0" : "!== -1";
|
254
|
+
const recommendation = `${sourceCode.getText(node.argument)} ${comparison}`;
|
250
255
|
|
251
256
|
report(node, recommendation, false);
|
252
257
|
}
|
@@ -35,8 +35,8 @@ module.exports = {
|
|
35
35
|
},
|
36
36
|
|
37
37
|
create(context) {
|
38
|
-
const EVAL_LIKE_FUNCS = Object.freeze(["setTimeout", "execScript", "setInterval"]);
|
39
38
|
const GLOBAL_CANDIDATES = Object.freeze(["global", "window", "globalThis"]);
|
39
|
+
const EVAL_LIKE_FUNC_PATTERN = /^(?:set(?:Interval|Timeout)|execScript)$/u;
|
40
40
|
|
41
41
|
/**
|
42
42
|
* Checks whether a node is evaluated as a string or not.
|
@@ -56,28 +56,6 @@ module.exports = {
|
|
56
56
|
return false;
|
57
57
|
}
|
58
58
|
|
59
|
-
/**
|
60
|
-
* Checks whether a node is an Identifier node named one of the specified names.
|
61
|
-
* @param {ASTNode} node A node to check.
|
62
|
-
* @param {string[]} specifiers Array of specified name.
|
63
|
-
* @returns {boolean} True if the node is a Identifier node which has specified name.
|
64
|
-
*/
|
65
|
-
function isSpecifiedIdentifier(node, specifiers) {
|
66
|
-
return node.type === "Identifier" && specifiers.includes(node.name);
|
67
|
-
}
|
68
|
-
|
69
|
-
/**
|
70
|
-
* Checks a given node is a MemberExpression node which has the specified name's
|
71
|
-
* property.
|
72
|
-
* @param {ASTNode} node A node to check.
|
73
|
-
* @param {string[]} specifiers Array of specified name.
|
74
|
-
* @returns {boolean} `true` if the node is a MemberExpression node which has
|
75
|
-
* the specified name's property
|
76
|
-
*/
|
77
|
-
function isSpecifiedMember(node, specifiers) {
|
78
|
-
return node.type === "MemberExpression" && specifiers.includes(astUtils.getStaticPropertyName(node));
|
79
|
-
}
|
80
|
-
|
81
59
|
/**
|
82
60
|
* Reports if the `CallExpression` node has evaluated argument.
|
83
61
|
* @param {ASTNode} node A CallExpression to check.
|
@@ -114,14 +92,15 @@ module.exports = {
|
|
114
92
|
const identifier = ref.identifier;
|
115
93
|
let node = identifier.parent;
|
116
94
|
|
117
|
-
while (
|
95
|
+
while (astUtils.isSpecificMemberAccess(node, null, name)) {
|
118
96
|
node = node.parent;
|
119
97
|
}
|
120
98
|
|
121
|
-
if (
|
122
|
-
const
|
99
|
+
if (astUtils.isSpecificMemberAccess(node, null, EVAL_LIKE_FUNC_PATTERN)) {
|
100
|
+
const calleeNode = node.parent.type === "ChainExpression" ? node.parent : node;
|
101
|
+
const parent = calleeNode.parent;
|
123
102
|
|
124
|
-
if (parent.type === "CallExpression" && parent.callee ===
|
103
|
+
if (parent.type === "CallExpression" && parent.callee === calleeNode) {
|
125
104
|
reportImpliedEvalCallExpression(parent);
|
126
105
|
}
|
127
106
|
}
|
@@ -134,7 +113,7 @@ module.exports = {
|
|
134
113
|
|
135
114
|
return {
|
136
115
|
CallExpression(node) {
|
137
|
-
if (
|
116
|
+
if (astUtils.isSpecificId(node.callee, EVAL_LIKE_FUNC_PATTERN)) {
|
138
117
|
reportImpliedEvalCallExpression(node);
|
139
118
|
}
|
140
119
|
},
|
@@ -9,16 +9,12 @@
|
|
9
9
|
// Helpers
|
10
10
|
//------------------------------------------------------------------------------
|
11
11
|
|
12
|
-
const { findVariable
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
]),
|
19
|
-
Reflect: new Set([
|
20
|
-
"defineProperty", "deleteProperty", "set", "setPrototypeOf"
|
21
|
-
])
|
12
|
+
const { findVariable } = require("eslint-utils");
|
13
|
+
const astUtils = require("./utils/ast-utils");
|
14
|
+
|
15
|
+
const WellKnownMutationFunctions = {
|
16
|
+
Object: /^(?:assign|definePropert(?:y|ies)|freeze|setPrototypeOf)$/u,
|
17
|
+
Reflect: /^(?:(?:define|delete)Property|set(?:PrototypeOf)?)$/u
|
22
18
|
};
|
23
19
|
|
24
20
|
/**
|
@@ -56,17 +52,20 @@ function isAssignmentLeft(node) {
|
|
56
52
|
* @returns {boolean} `true` if the node is the operand of mutation unary operator.
|
57
53
|
*/
|
58
54
|
function isOperandOfMutationUnaryOperator(node) {
|
59
|
-
const
|
55
|
+
const argumentNode = node.parent.type === "ChainExpression"
|
56
|
+
? node.parent
|
57
|
+
: node;
|
58
|
+
const { parent } = argumentNode;
|
60
59
|
|
61
60
|
return (
|
62
61
|
(
|
63
62
|
parent.type === "UpdateExpression" &&
|
64
|
-
parent.argument ===
|
63
|
+
parent.argument === argumentNode
|
65
64
|
) ||
|
66
65
|
(
|
67
66
|
parent.type === "UnaryExpression" &&
|
68
67
|
parent.operator === "delete" &&
|
69
|
-
parent.argument ===
|
68
|
+
parent.argument === argumentNode
|
70
69
|
)
|
71
70
|
);
|
72
71
|
}
|
@@ -92,35 +91,37 @@ function isIterationVariable(node) {
|
|
92
91
|
}
|
93
92
|
|
94
93
|
/**
|
95
|
-
* Check if a given node is the
|
94
|
+
* Check if a given node is at the first argument of a well-known mutation function.
|
95
|
+
* - `Object.assign`
|
96
|
+
* - `Object.defineProperty`
|
97
|
+
* - `Object.defineProperties`
|
98
|
+
* - `Object.freeze`
|
99
|
+
* - `Object.setPrototypeOf`
|
100
|
+
* - `Refrect.defineProperty`
|
101
|
+
* - `Refrect.deleteProperty`
|
102
|
+
* - `Refrect.set`
|
103
|
+
* - `Refrect.setPrototypeOf`
|
96
104
|
* @param {ASTNode} node The node to check.
|
97
105
|
* @param {Scope} scope A `escope.Scope` object to find variable (whichever).
|
98
|
-
* @returns {boolean} `true` if the node is the
|
106
|
+
* @returns {boolean} `true` if the node is at the first argument of a well-known mutation function.
|
99
107
|
*/
|
100
108
|
function isArgumentOfWellKnownMutationFunction(node, scope) {
|
101
109
|
const { parent } = node;
|
102
110
|
|
111
|
+
if (parent.type !== "CallExpression" || parent.arguments[0] !== node) {
|
112
|
+
return false;
|
113
|
+
}
|
114
|
+
const callee = astUtils.skipChainExpression(parent.callee);
|
115
|
+
|
103
116
|
if (
|
104
|
-
|
105
|
-
|
106
|
-
parent.callee.type === "MemberExpression" &&
|
107
|
-
parent.callee.object.type === "Identifier"
|
117
|
+
!astUtils.isSpecificMemberAccess(callee, "Object", WellKnownMutationFunctions.Object) &&
|
118
|
+
!astUtils.isSpecificMemberAccess(callee, "Reflect", WellKnownMutationFunctions.Reflect)
|
108
119
|
) {
|
109
|
-
|
110
|
-
const { object } = callee;
|
111
|
-
|
112
|
-
if (Object.keys(MutationMethods).includes(object.name)) {
|
113
|
-
const variable = findVariable(scope, object);
|
114
|
-
|
115
|
-
return (
|
116
|
-
variable !== null &&
|
117
|
-
variable.scope.type === "global" &&
|
118
|
-
MutationMethods[object.name].has(getPropertyName(callee, scope))
|
119
|
-
);
|
120
|
-
}
|
120
|
+
return false;
|
121
121
|
}
|
122
|
+
const variable = findVariable(scope, callee.object);
|
122
123
|
|
123
|
-
return
|
124
|
+
return variable !== null && variable.scope.type === "global";
|
124
125
|
}
|
125
126
|
|
126
127
|
/**
|
@@ -91,7 +91,7 @@ module.exports = {
|
|
91
91
|
const locStart = node.loc.start;
|
92
92
|
const locEnd = node.loc.end;
|
93
93
|
|
94
|
-
errors = errors.filter(({ loc: errorLoc }) => {
|
94
|
+
errors = errors.filter(({ loc: { start: errorLoc } }) => {
|
95
95
|
if (errorLoc.line >= locStart.line && errorLoc.line <= locEnd.line) {
|
96
96
|
if (errorLoc.column >= locStart.column && (errorLoc.column <= locEnd.column || errorLoc.line < locEnd.line)) {
|
97
97
|
return false;
|
@@ -160,15 +160,19 @@ module.exports = {
|
|
160
160
|
let match;
|
161
161
|
|
162
162
|
while ((match = IRREGULAR_WHITESPACE.exec(sourceLine)) !== null) {
|
163
|
-
const location = {
|
164
|
-
line: lineNumber,
|
165
|
-
column: match.index
|
166
|
-
};
|
167
|
-
|
168
163
|
errors.push({
|
169
164
|
node,
|
170
165
|
messageId: "noIrregularWhitespace",
|
171
|
-
loc:
|
166
|
+
loc: {
|
167
|
+
start: {
|
168
|
+
line: lineNumber,
|
169
|
+
column: match.index
|
170
|
+
},
|
171
|
+
end: {
|
172
|
+
line: lineNumber,
|
173
|
+
column: match.index + match[0].length
|
174
|
+
}
|
175
|
+
}
|
172
176
|
});
|
173
177
|
}
|
174
178
|
});
|
@@ -189,16 +193,22 @@ module.exports = {
|
|
189
193
|
|
190
194
|
while ((match = IRREGULAR_LINE_TERMINATORS.exec(source)) !== null) {
|
191
195
|
const lineIndex = linebreaks.indexOf(match[0], lastLineIndex + 1) || 0;
|
192
|
-
const location = {
|
193
|
-
line: lineIndex + 1,
|
194
|
-
column: sourceLines[lineIndex].length
|
195
|
-
};
|
196
196
|
|
197
197
|
errors.push({
|
198
198
|
node,
|
199
199
|
messageId: "noIrregularWhitespace",
|
200
|
-
loc:
|
200
|
+
loc: {
|
201
|
+
start: {
|
202
|
+
line: lineIndex + 1,
|
203
|
+
column: sourceLines[lineIndex].length
|
204
|
+
},
|
205
|
+
end: {
|
206
|
+
line: lineIndex + 2,
|
207
|
+
column: 0
|
208
|
+
}
|
209
|
+
}
|
201
210
|
});
|
211
|
+
|
202
212
|
lastLineIndex = lineIndex;
|
203
213
|
}
|
204
214
|
}
|
@@ -5,7 +5,7 @@
|
|
5
5
|
|
6
6
|
"use strict";
|
7
7
|
|
8
|
-
const
|
8
|
+
const astUtils = require("./utils/ast-utils");
|
9
9
|
|
10
10
|
// Maximum array length by the ECMAScript Specification.
|
11
11
|
const MAX_ARRAY_LENGTH = 2 ** 32 - 1;
|
@@ -100,12 +100,8 @@ module.exports = {
|
|
100
100
|
|
101
101
|
return parent.type === "CallExpression" && fullNumberNode === parent.arguments[1] &&
|
102
102
|
(
|
103
|
-
parent.callee
|
104
|
-
(
|
105
|
-
parent.callee.type === "MemberExpression" &&
|
106
|
-
parent.callee.object.name === "Number" &&
|
107
|
-
parent.callee.property.name === "parseInt"
|
108
|
-
)
|
103
|
+
astUtils.isSpecificId(parent.callee, "parseInt") ||
|
104
|
+
astUtils.isSpecificMemberAccess(parent.callee, "Number", "parseInt")
|
109
105
|
);
|
110
106
|
}
|
111
107
|
|
@@ -157,7 +153,7 @@ module.exports = {
|
|
157
153
|
|
158
154
|
return {
|
159
155
|
Literal(node) {
|
160
|
-
if (!isNumericLiteral(node)) {
|
156
|
+
if (!astUtils.isNumericLiteral(node)) {
|
161
157
|
return;
|
162
158
|
}
|
163
159
|
|
@@ -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
|
|
@@ -0,0 +1,121 @@
|
|
1
|
+
/**
|
2
|
+
* @fileoverview Rule to disallow returning values from Promise executor functions
|
3
|
+
* @author Milos Djermanovic
|
4
|
+
*/
|
5
|
+
|
6
|
+
"use strict";
|
7
|
+
|
8
|
+
//------------------------------------------------------------------------------
|
9
|
+
// Requirements
|
10
|
+
//------------------------------------------------------------------------------
|
11
|
+
|
12
|
+
const { findVariable } = require("eslint-utils");
|
13
|
+
|
14
|
+
//------------------------------------------------------------------------------
|
15
|
+
// Helpers
|
16
|
+
//------------------------------------------------------------------------------
|
17
|
+
|
18
|
+
const functionTypesToCheck = new Set(["ArrowFunctionExpression", "FunctionExpression"]);
|
19
|
+
|
20
|
+
/**
|
21
|
+
* Determines whether the given identifier node is a reference to a global variable.
|
22
|
+
* @param {ASTNode} node `Identifier` node to check.
|
23
|
+
* @param {Scope} scope Scope to which the node belongs.
|
24
|
+
* @returns {boolean} True if the identifier is a reference to a global variable.
|
25
|
+
*/
|
26
|
+
function isGlobalReference(node, scope) {
|
27
|
+
const variable = findVariable(scope, node);
|
28
|
+
|
29
|
+
return variable !== null && variable.scope.type === "global" && variable.defs.length === 0;
|
30
|
+
}
|
31
|
+
|
32
|
+
/**
|
33
|
+
* Finds function's outer scope.
|
34
|
+
* @param {Scope} scope Function's own scope.
|
35
|
+
* @returns {Scope} Function's outer scope.
|
36
|
+
*/
|
37
|
+
function getOuterScope(scope) {
|
38
|
+
const upper = scope.upper;
|
39
|
+
|
40
|
+
if (upper.type === "function-expression-name") {
|
41
|
+
return upper.upper;
|
42
|
+
}
|
43
|
+
return upper;
|
44
|
+
}
|
45
|
+
|
46
|
+
/**
|
47
|
+
* Determines whether the given function node is used as a Promise executor.
|
48
|
+
* @param {ASTNode} node The node to check.
|
49
|
+
* @param {Scope} scope Function's own scope.
|
50
|
+
* @returns {boolean} `true` if the node is a Promise executor.
|
51
|
+
*/
|
52
|
+
function isPromiseExecutor(node, scope) {
|
53
|
+
const parent = node.parent;
|
54
|
+
|
55
|
+
return parent.type === "NewExpression" &&
|
56
|
+
parent.arguments[0] === node &&
|
57
|
+
parent.callee.type === "Identifier" &&
|
58
|
+
parent.callee.name === "Promise" &&
|
59
|
+
isGlobalReference(parent.callee, getOuterScope(scope));
|
60
|
+
}
|
61
|
+
|
62
|
+
//------------------------------------------------------------------------------
|
63
|
+
// Rule Definition
|
64
|
+
//------------------------------------------------------------------------------
|
65
|
+
|
66
|
+
module.exports = {
|
67
|
+
meta: {
|
68
|
+
type: "problem",
|
69
|
+
|
70
|
+
docs: {
|
71
|
+
description: "disallow returning values from Promise executor functions",
|
72
|
+
category: "Possible Errors",
|
73
|
+
recommended: false,
|
74
|
+
url: "https://eslint.org/docs/rules/no-promise-executor-return"
|
75
|
+
},
|
76
|
+
|
77
|
+
schema: [],
|
78
|
+
|
79
|
+
messages: {
|
80
|
+
returnsValue: "Return values from promise executor functions cannot be read."
|
81
|
+
}
|
82
|
+
},
|
83
|
+
|
84
|
+
create(context) {
|
85
|
+
|
86
|
+
let funcInfo = null;
|
87
|
+
|
88
|
+
/**
|
89
|
+
* Reports the given node.
|
90
|
+
* @param {ASTNode} node Node to report.
|
91
|
+
* @returns {void}
|
92
|
+
*/
|
93
|
+
function report(node) {
|
94
|
+
context.report({ node, messageId: "returnsValue" });
|
95
|
+
}
|
96
|
+
|
97
|
+
return {
|
98
|
+
|
99
|
+
onCodePathStart(_, node) {
|
100
|
+
funcInfo = {
|
101
|
+
upper: funcInfo,
|
102
|
+
shouldCheck: functionTypesToCheck.has(node.type) && isPromiseExecutor(node, context.getScope())
|
103
|
+
};
|
104
|
+
|
105
|
+
if (funcInfo.shouldCheck && node.type === "ArrowFunctionExpression" && node.expression) {
|
106
|
+
report(node.body);
|
107
|
+
}
|
108
|
+
},
|
109
|
+
|
110
|
+
onCodePathEnd() {
|
111
|
+
funcInfo = funcInfo.upper;
|
112
|
+
},
|
113
|
+
|
114
|
+
ReturnStatement(node) {
|
115
|
+
if (funcInfo.shouldCheck && node.argument) {
|
116
|
+
report(node);
|
117
|
+
}
|
118
|
+
}
|
119
|
+
};
|
120
|
+
}
|
121
|
+
};
|
@@ -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
|
}
|