eslint 7.4.0 → 7.8.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 +86 -0
- package/README.md +29 -17
- package/conf/config-schema.js +12 -0
- package/lib/cli-engine/cascading-config-array-factory.js +12 -0
- package/lib/cli-engine/cli-engine.js +2 -2
- package/lib/cli-engine/config-array/config-array.js +12 -0
- package/lib/cli-engine/config-array/config-dependency.js +12 -0
- package/lib/cli-engine/config-array/extracted-config.js +12 -0
- package/lib/cli-engine/config-array/ignore-pattern.js +12 -0
- package/lib/cli-engine/config-array/index.js +12 -0
- package/lib/cli-engine/config-array/override-tester.js +12 -0
- package/lib/cli-engine/config-array-factory.js +13 -1
- package/lib/cli-engine/formatters/checkstyle.js +2 -2
- package/lib/eslint/eslint.js +7 -1
- package/lib/init/autoconfig.js +1 -1
- package/lib/init/config-initializer.js +3 -3
- package/lib/linter/code-path-analysis/code-path-analyzer.js +76 -0
- package/lib/linter/code-path-analysis/code-path-segment.js +0 -1
- package/lib/linter/code-path-analysis/code-path-state.js +61 -2
- package/lib/linter/code-path-analysis/debug-helpers.js +26 -19
- package/lib/linter/config-comment-parser.js +1 -1
- package/lib/linter/linter.js +6 -3
- package/lib/rule-tester/rule-tester.js +10 -0
- package/lib/rules/accessor-pairs.js +1 -14
- package/lib/rules/array-callback-return.js +5 -7
- package/lib/rules/arrow-body-style.js +41 -6
- package/lib/rules/comma-dangle.js +1 -2
- package/lib/rules/consistent-return.js +1 -12
- package/lib/rules/constructor-super.js +18 -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 +233 -0
- package/lib/rules/id-length.js +19 -1
- package/lib/rules/indent.js +23 -3
- package/lib/rules/index.js +1 -3
- 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 +27 -7
- 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-loss-of-precision.js +10 -2
- package/lib/rules/no-magic-numbers.js +24 -11
- 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-underscore-dangle.js +66 -21
- 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-warning-comments.js +40 -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 +4 -43
- 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 +14 -13
- package/lib/rules/prefer-promise-reject-errors.js +1 -3
- package/lib/rules/prefer-regex-literals.js +2 -5
- 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 +363 -165
- package/lib/rules/wrap-iife.js +9 -2
- package/lib/rules/yoda.js +2 -55
- package/lib/shared/config-validator.js +14 -2
- package/lib/shared/relative-module-resolver.js +12 -0
- package/lib/shared/types.js +1 -1
- 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 +7 -7
- package/conf/environments.js +0 -168
- package/lib/shared/config-ops.js +0 -130
- package/lib/shared/naming.js +0 -97
@@ -234,6 +234,7 @@ class CodePathState {
|
|
234
234
|
this.tryContext = null;
|
235
235
|
this.loopContext = null;
|
236
236
|
this.breakContext = null;
|
237
|
+
this.chainContext = null;
|
237
238
|
|
238
239
|
this.currentSegments = [];
|
239
240
|
this.initialSegment = this.forkContext.head[0];
|
@@ -316,7 +317,7 @@ class CodePathState {
|
|
316
317
|
//--------------------------------------------------------------------------
|
317
318
|
|
318
319
|
/**
|
319
|
-
* Creates a context for ConditionalExpression, LogicalExpression,
|
320
|
+
* Creates a context for ConditionalExpression, LogicalExpression, AssignmentExpression (logical assignments only),
|
320
321
|
* IfStatement, WhileStatement, DoWhileStatement, or ForStatement.
|
321
322
|
*
|
322
323
|
* LogicalExpressions have cases that it goes different paths between the
|
@@ -338,7 +339,7 @@ class CodePathState {
|
|
338
339
|
* a -> b -> foo();
|
339
340
|
* a -> b -> bar();
|
340
341
|
* @param {string} kind A kind string.
|
341
|
-
* If the new context is LogicalExpression's, this is `"&&"` or `"||"`.
|
342
|
+
* If the new context is LogicalExpression's or AssignmentExpression's, this is `"&&"` or `"||"` or `"??"`.
|
342
343
|
* If it's IfStatement's or ConditionalExpression's, this is `"test"`.
|
343
344
|
* Otherwise, this is `"loop"`.
|
344
345
|
* @param {boolean} isForkingAsResult A flag that shows that goes different
|
@@ -555,6 +556,64 @@ class CodePathState {
|
|
555
556
|
);
|
556
557
|
}
|
557
558
|
|
559
|
+
//--------------------------------------------------------------------------
|
560
|
+
// ChainExpression
|
561
|
+
//--------------------------------------------------------------------------
|
562
|
+
|
563
|
+
/**
|
564
|
+
* Push a new `ChainExpression` context to the stack.
|
565
|
+
* This method is called on entering to each `ChainExpression` node.
|
566
|
+
* This context is used to count forking in the optional chain then merge them on the exiting from the `ChainExpression` node.
|
567
|
+
* @returns {void}
|
568
|
+
*/
|
569
|
+
pushChainContext() {
|
570
|
+
this.chainContext = {
|
571
|
+
upper: this.chainContext,
|
572
|
+
countChoiceContexts: 0
|
573
|
+
};
|
574
|
+
}
|
575
|
+
|
576
|
+
/**
|
577
|
+
* Pop a `ChainExpression` context from the stack.
|
578
|
+
* This method is called on exiting from each `ChainExpression` node.
|
579
|
+
* This merges all forks of the last optional chaining.
|
580
|
+
* @returns {void}
|
581
|
+
*/
|
582
|
+
popChainContext() {
|
583
|
+
const context = this.chainContext;
|
584
|
+
|
585
|
+
this.chainContext = context.upper;
|
586
|
+
|
587
|
+
// pop all choice contexts of this.
|
588
|
+
for (let i = context.countChoiceContexts; i > 0; --i) {
|
589
|
+
this.popChoiceContext();
|
590
|
+
}
|
591
|
+
}
|
592
|
+
|
593
|
+
/**
|
594
|
+
* Create a choice context for optional access.
|
595
|
+
* This method is called on entering to each `(Call|Member)Expression[optional=true]` node.
|
596
|
+
* This creates a choice context as similar to `LogicalExpression[operator="??"]` node.
|
597
|
+
* @returns {void}
|
598
|
+
*/
|
599
|
+
makeOptionalNode() {
|
600
|
+
if (this.chainContext) {
|
601
|
+
this.chainContext.countChoiceContexts += 1;
|
602
|
+
this.pushChoiceContext("??", false);
|
603
|
+
}
|
604
|
+
}
|
605
|
+
|
606
|
+
/**
|
607
|
+
* Create a fork.
|
608
|
+
* This method is called on entering to the `arguments|property` property of each `(Call|Member)Expression` node.
|
609
|
+
* @returns {void}
|
610
|
+
*/
|
611
|
+
makeOptionalRight() {
|
612
|
+
if (this.chainContext) {
|
613
|
+
this.makeLogicalRight();
|
614
|
+
}
|
615
|
+
}
|
616
|
+
|
558
617
|
//--------------------------------------------------------------------------
|
559
618
|
// SwitchStatement
|
560
619
|
//--------------------------------------------------------------------------
|
@@ -25,6 +25,22 @@ function getId(segment) { // eslint-disable-line jsdoc/require-jsdoc
|
|
25
25
|
return segment.id + (segment.reachable ? "" : "!");
|
26
26
|
}
|
27
27
|
|
28
|
+
/**
|
29
|
+
* Get string for the given node and operation.
|
30
|
+
* @param {ASTNode} node The node to convert.
|
31
|
+
* @param {"enter" | "exit" | undefined} label The operation label.
|
32
|
+
* @returns {string} The string representation.
|
33
|
+
*/
|
34
|
+
function nodeToString(node, label) {
|
35
|
+
const suffix = label ? `:${label}` : "";
|
36
|
+
|
37
|
+
switch (node.type) {
|
38
|
+
case "Identifier": return `${node.type}${suffix} (${node.name})`;
|
39
|
+
case "Literal": return `${node.type}${suffix} (${node.value})`;
|
40
|
+
default: return `${node.type}${suffix}`;
|
41
|
+
}
|
42
|
+
}
|
43
|
+
|
28
44
|
//------------------------------------------------------------------------------
|
29
45
|
// Public Interface
|
30
46
|
//------------------------------------------------------------------------------
|
@@ -56,9 +72,15 @@ module.exports = {
|
|
56
72
|
const segInternal = state.currentSegments[i].internal;
|
57
73
|
|
58
74
|
if (leaving) {
|
59
|
-
segInternal.
|
75
|
+
const last = segInternal.nodes.length - 1;
|
76
|
+
|
77
|
+
if (last >= 0 && segInternal.nodes[last] === nodeToString(node, "enter")) {
|
78
|
+
segInternal.nodes[last] = nodeToString(node, void 0);
|
79
|
+
} else {
|
80
|
+
segInternal.nodes.push(nodeToString(node, "exit"));
|
81
|
+
}
|
60
82
|
} else {
|
61
|
-
segInternal.nodes.push(node);
|
83
|
+
segInternal.nodes.push(nodeToString(node, "enter"));
|
62
84
|
}
|
63
85
|
}
|
64
86
|
|
@@ -104,23 +126,8 @@ module.exports = {
|
|
104
126
|
text += "style=\"rounded,dashed,filled\",fillcolor=\"#FF9800\",label=\"<<unreachable>>\\n";
|
105
127
|
}
|
106
128
|
|
107
|
-
if (segment.internal.nodes.length > 0
|
108
|
-
text +=
|
109
|
-
segment.internal.nodes.map(node => {
|
110
|
-
switch (node.type) {
|
111
|
-
case "Identifier": return `${node.type} (${node.name})`;
|
112
|
-
case "Literal": return `${node.type} (${node.value})`;
|
113
|
-
default: return node.type;
|
114
|
-
}
|
115
|
-
}),
|
116
|
-
segment.internal.exitNodes.map(node => {
|
117
|
-
switch (node.type) {
|
118
|
-
case "Identifier": return `${node.type}:exit (${node.name})`;
|
119
|
-
case "Literal": return `${node.type}:exit (${node.value})`;
|
120
|
-
default: return `${node.type}:exit`;
|
121
|
-
}
|
122
|
-
})
|
123
|
-
).join("\\n");
|
129
|
+
if (segment.internal.nodes.length > 0) {
|
130
|
+
text += segment.internal.nodes.join("\\n");
|
124
131
|
} else {
|
125
132
|
text += "????";
|
126
133
|
}
|
@@ -11,7 +11,7 @@
|
|
11
11
|
//------------------------------------------------------------------------------
|
12
12
|
|
13
13
|
const levn = require("levn"),
|
14
|
-
ConfigOps = require("
|
14
|
+
ConfigOps = require("@eslint/eslintrc/lib/shared/config-ops");
|
15
15
|
|
16
16
|
const debug = require("debug")("eslint:config-comment-parser");
|
17
17
|
|
package/lib/linter/linter.js
CHANGED
@@ -16,11 +16,11 @@ const
|
|
16
16
|
evk = require("eslint-visitor-keys"),
|
17
17
|
espree = require("espree"),
|
18
18
|
lodash = require("lodash"),
|
19
|
-
BuiltInEnvironments = require("
|
19
|
+
BuiltInEnvironments = require("@eslint/eslintrc/conf/environments"),
|
20
20
|
pkg = require("../../package.json"),
|
21
21
|
astUtils = require("../shared/ast-utils"),
|
22
|
-
ConfigOps = require("
|
23
|
-
|
22
|
+
ConfigOps = require("@eslint/eslintrc/lib/shared/config-ops"),
|
23
|
+
ConfigValidator = require("@eslint/eslintrc/lib/shared/config-validator"),
|
24
24
|
Traverser = require("../shared/traverser"),
|
25
25
|
{ SourceCode } = require("../source-code"),
|
26
26
|
CodePathAnalyzer = require("./code-path-analysis/code-path-analyzer"),
|
@@ -293,6 +293,9 @@ function getDirectiveComments(filename, ast, ruleMapper, warnInlineConfig) {
|
|
293
293
|
const exportedVariables = {};
|
294
294
|
const problems = [];
|
295
295
|
const disableDirectives = [];
|
296
|
+
const validator = new ConfigValidator({
|
297
|
+
builtInRules: Rules
|
298
|
+
});
|
296
299
|
|
297
300
|
ast.comments.filter(token => token.type !== "Shebang").forEach(comment => {
|
298
301
|
const trimmedCommentText = stripDirectiveComment(comment.value);
|
@@ -861,6 +861,16 @@ class RuleTester {
|
|
861
861
|
);
|
862
862
|
}
|
863
863
|
|
864
|
+
// Rules that produce fixes must have `meta.fixable` property.
|
865
|
+
if (result.output !== item.code) {
|
866
|
+
assert.ok(
|
867
|
+
hasOwnProperty(rule, "meta"),
|
868
|
+
"Fixable rules should export a `meta.fixable` property."
|
869
|
+
);
|
870
|
+
|
871
|
+
// Linter throws if a rule that produced a fix has `meta` but doesn't have `meta.fixable`.
|
872
|
+
}
|
873
|
+
|
864
874
|
assertASTDidntChange(result.beforeAST, result.afterAST);
|
865
875
|
}
|
866
876
|
|
@@ -86,16 +86,6 @@ function isAccessorKind(node) {
|
|
86
86
|
return node.kind === "get" || node.kind === "set";
|
87
87
|
}
|
88
88
|
|
89
|
-
/**
|
90
|
-
* Checks whether or not a given node is an `Identifier` node which was named a given name.
|
91
|
-
* @param {ASTNode} node A node to check.
|
92
|
-
* @param {string} name An expected name of the node.
|
93
|
-
* @returns {boolean} `true` if the node is an `Identifier` node which was named as expected.
|
94
|
-
*/
|
95
|
-
function isIdentifier(node, name) {
|
96
|
-
return node.type === "Identifier" && node.name === name;
|
97
|
-
}
|
98
|
-
|
99
89
|
/**
|
100
90
|
* Checks whether or not a given node is an argument of a specified method call.
|
101
91
|
* @param {ASTNode} node A node to check.
|
@@ -109,10 +99,7 @@ function isArgumentOfMethodCall(node, index, object, property) {
|
|
109
99
|
|
110
100
|
return (
|
111
101
|
parent.type === "CallExpression" &&
|
112
|
-
parent.callee
|
113
|
-
parent.callee.computed === false &&
|
114
|
-
isIdentifier(parent.callee.object, object) &&
|
115
|
-
isIdentifier(parent.callee.property, property) &&
|
102
|
+
astUtils.isSpecificMemberAccess(parent.callee, object, property) &&
|
116
103
|
parent.arguments[index] === node
|
117
104
|
);
|
118
105
|
}
|
@@ -28,17 +28,14 @@ function isReachable(segment) {
|
|
28
28
|
}
|
29
29
|
|
30
30
|
/**
|
31
|
-
* Checks a given node is a
|
31
|
+
* Checks a given node is a member access which has the specified name's
|
32
32
|
* property.
|
33
33
|
* @param {ASTNode} node A node to check.
|
34
|
-
* @returns {boolean} `true` if the node is a
|
35
|
-
* the specified name's property
|
34
|
+
* @returns {boolean} `true` if the node is a member access which has
|
35
|
+
* the specified name's property. The node may be a `(Chain|Member)Expression` node.
|
36
36
|
*/
|
37
37
|
function isTargetMethod(node) {
|
38
|
-
return (
|
39
|
-
node.type === "MemberExpression" &&
|
40
|
-
TARGET_METHODS.test(astUtils.getStaticPropertyName(node) || "")
|
41
|
-
);
|
38
|
+
return astUtils.isSpecificMemberAccess(node, null, TARGET_METHODS);
|
42
39
|
}
|
43
40
|
|
44
41
|
/**
|
@@ -76,6 +73,7 @@ function getArrayMethodName(node) {
|
|
76
73
|
*/
|
77
74
|
case "LogicalExpression":
|
78
75
|
case "ConditionalExpression":
|
76
|
+
case "ChainExpression":
|
79
77
|
currentNode = parent;
|
80
78
|
break;
|
81
79
|
|
@@ -75,6 +75,7 @@ module.exports = {
|
|
75
75
|
const never = options[0] === "never";
|
76
76
|
const requireReturnForObjectLiteral = options[1] && options[1].requireReturnForObjectLiteral;
|
77
77
|
const sourceCode = context.getSourceCode();
|
78
|
+
let funcInfo = null;
|
78
79
|
|
79
80
|
/**
|
80
81
|
* Checks whether the given node has ASI problem or not.
|
@@ -99,6 +100,21 @@ module.exports = {
|
|
99
100
|
return sourceCode.getTokenAfter(node);
|
100
101
|
}
|
101
102
|
|
103
|
+
/**
|
104
|
+
* Check whether the node is inside of a for loop's init
|
105
|
+
* @param {ASTNode} node node is inside for loop
|
106
|
+
* @returns {boolean} `true` if the node is inside of a for loop, else `false`
|
107
|
+
*/
|
108
|
+
function isInsideForLoopInitializer(node) {
|
109
|
+
if (node && node.parent) {
|
110
|
+
if (node.parent.type === "ForStatement" && node.parent.init === node) {
|
111
|
+
return true;
|
112
|
+
}
|
113
|
+
return isInsideForLoopInitializer(node.parent);
|
114
|
+
}
|
115
|
+
return false;
|
116
|
+
}
|
117
|
+
|
102
118
|
/**
|
103
119
|
* Determines whether a arrow function body needs braces
|
104
120
|
* @param {ASTNode} node The arrow function node.
|
@@ -178,11 +194,13 @@ module.exports = {
|
|
178
194
|
* If the first token of the reutrn value is `{` or the return value is a sequence expression,
|
179
195
|
* enclose the return value by parentheses to avoid syntax error.
|
180
196
|
*/
|
181
|
-
if (astUtils.isOpeningBraceToken(firstValueToken) || blockBody[0].argument.type === "SequenceExpression") {
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
197
|
+
if (astUtils.isOpeningBraceToken(firstValueToken) || blockBody[0].argument.type === "SequenceExpression" || (funcInfo.hasInOperator && isInsideForLoopInitializer(node))) {
|
198
|
+
if (!astUtils.isParenthesised(sourceCode, blockBody[0].argument)) {
|
199
|
+
fixes.push(
|
200
|
+
fixer.insertTextBefore(firstValueToken, "("),
|
201
|
+
fixer.insertTextAfter(lastValueToken, ")")
|
202
|
+
);
|
203
|
+
}
|
186
204
|
}
|
187
205
|
|
188
206
|
/*
|
@@ -245,7 +263,24 @@ module.exports = {
|
|
245
263
|
}
|
246
264
|
|
247
265
|
return {
|
248
|
-
"
|
266
|
+
"BinaryExpression[operator='in']"() {
|
267
|
+
let info = funcInfo;
|
268
|
+
|
269
|
+
while (info) {
|
270
|
+
info.hasInOperator = true;
|
271
|
+
info = info.upper;
|
272
|
+
}
|
273
|
+
},
|
274
|
+
ArrowFunctionExpression() {
|
275
|
+
funcInfo = {
|
276
|
+
upper: funcInfo,
|
277
|
+
hasInOperator: false
|
278
|
+
};
|
279
|
+
},
|
280
|
+
"ArrowFunctionExpression:exit"(node) {
|
281
|
+
validate(node);
|
282
|
+
funcInfo = funcInfo.upper;
|
283
|
+
}
|
249
284
|
};
|
250
285
|
}
|
251
286
|
};
|
@@ -9,23 +9,12 @@
|
|
9
9
|
//------------------------------------------------------------------------------
|
10
10
|
|
11
11
|
const lodash = require("lodash");
|
12
|
-
|
13
12
|
const astUtils = require("./utils/ast-utils");
|
14
13
|
|
15
14
|
//------------------------------------------------------------------------------
|
16
15
|
// Helpers
|
17
16
|
//------------------------------------------------------------------------------
|
18
17
|
|
19
|
-
/**
|
20
|
-
* Checks whether or not a given node is an `Identifier` node which was named a given name.
|
21
|
-
* @param {ASTNode} node A node to check.
|
22
|
-
* @param {string} name An expected name of the node.
|
23
|
-
* @returns {boolean} `true` if the node is an `Identifier` node which was named as expected.
|
24
|
-
*/
|
25
|
-
function isIdentifier(node, name) {
|
26
|
-
return node.type === "Identifier" && node.name === name;
|
27
|
-
}
|
28
|
-
|
29
18
|
/**
|
30
19
|
* Checks whether or not a given code path segment is unreachable.
|
31
20
|
* @param {CodePathSegment} segment A CodePathSegment to check.
|
@@ -165,7 +154,7 @@ module.exports = {
|
|
165
154
|
let hasReturnValue = Boolean(argument);
|
166
155
|
|
167
156
|
if (treatUndefinedAsUnspecified && hasReturnValue) {
|
168
|
-
hasReturnValue = !
|
157
|
+
hasReturnValue = !astUtils.isSpecificId(argument, "undefined") && argument.operator !== "void";
|
169
158
|
}
|
170
159
|
|
171
160
|
if (!funcInfo.hasReturn) {
|
@@ -50,6 +50,7 @@ function isPossibleConstructor(node) {
|
|
50
50
|
case "MemberExpression":
|
51
51
|
case "CallExpression":
|
52
52
|
case "NewExpression":
|
53
|
+
case "ChainExpression":
|
53
54
|
case "YieldExpression":
|
54
55
|
case "TaggedTemplateExpression":
|
55
56
|
case "MetaProperty":
|
@@ -59,7 +60,23 @@ function isPossibleConstructor(node) {
|
|
59
60
|
return node.name !== "undefined";
|
60
61
|
|
61
62
|
case "AssignmentExpression":
|
62
|
-
|
63
|
+
if (["=", "&&="].includes(node.operator)) {
|
64
|
+
return isPossibleConstructor(node.right);
|
65
|
+
}
|
66
|
+
|
67
|
+
if (["||=", "??="].includes(node.operator)) {
|
68
|
+
return (
|
69
|
+
isPossibleConstructor(node.left) ||
|
70
|
+
isPossibleConstructor(node.right)
|
71
|
+
);
|
72
|
+
}
|
73
|
+
|
74
|
+
/**
|
75
|
+
* All other assignment operators are mathematical assignment operators (arithmetic or bitwise).
|
76
|
+
* An assignment expression with a mathematical operator can either evaluate to a primitive value,
|
77
|
+
* or throw, depending on the operands. Thus, it cannot evaluate to a constructor function.
|
78
|
+
*/
|
79
|
+
return false;
|
63
80
|
|
64
81
|
case "LogicalExpression":
|
65
82
|
return (
|
@@ -52,31 +52,37 @@ module.exports = {
|
|
52
52
|
*/
|
53
53
|
function checkDotLocation(node) {
|
54
54
|
const property = node.property;
|
55
|
-
const
|
56
|
-
|
57
|
-
// `obj` expression can be parenthesized, but those paren tokens are not a part of the `obj` node.
|
58
|
-
const tokenBeforeDot = sourceCode.getTokenBefore(dot);
|
59
|
-
|
60
|
-
const textBeforeDot = sourceCode.getText().slice(tokenBeforeDot.range[1], dot.range[0]);
|
61
|
-
const textAfterDot = sourceCode.getText().slice(dot.range[1], property.range[0]);
|
55
|
+
const dotToken = sourceCode.getTokenBefore(property);
|
62
56
|
|
63
57
|
if (onObject) {
|
64
|
-
if (!astUtils.isTokenOnSameLine(tokenBeforeDot, dot)) {
|
65
|
-
const neededTextAfterToken = astUtils.isDecimalIntegerNumericToken(tokenBeforeDot) ? " " : "";
|
66
58
|
|
59
|
+
// `obj` expression can be parenthesized, but those paren tokens are not a part of the `obj` node.
|
60
|
+
const tokenBeforeDot = sourceCode.getTokenBefore(dotToken);
|
61
|
+
|
62
|
+
if (!astUtils.isTokenOnSameLine(tokenBeforeDot, dotToken)) {
|
67
63
|
context.report({
|
68
64
|
node,
|
69
|
-
loc:
|
65
|
+
loc: dotToken.loc,
|
70
66
|
messageId: "expectedDotAfterObject",
|
71
|
-
fix
|
67
|
+
*fix(fixer) {
|
68
|
+
if (dotToken.value.startsWith(".") && astUtils.isDecimalIntegerNumericToken(tokenBeforeDot)) {
|
69
|
+
yield fixer.insertTextAfter(tokenBeforeDot, ` ${dotToken.value}`);
|
70
|
+
} else {
|
71
|
+
yield fixer.insertTextAfter(tokenBeforeDot, dotToken.value);
|
72
|
+
}
|
73
|
+
yield fixer.remove(dotToken);
|
74
|
+
}
|
72
75
|
});
|
73
76
|
}
|
74
|
-
} else if (!astUtils.isTokenOnSameLine(
|
77
|
+
} else if (!astUtils.isTokenOnSameLine(dotToken, property)) {
|
75
78
|
context.report({
|
76
79
|
node,
|
77
|
-
loc:
|
80
|
+
loc: dotToken.loc,
|
78
81
|
messageId: "expectedDotBeforeProperty",
|
79
|
-
fix
|
82
|
+
*fix(fixer) {
|
83
|
+
yield fixer.remove(dotToken);
|
84
|
+
yield fixer.insertTextBefore(property, dotToken.value);
|
85
|
+
}
|
80
86
|
});
|
81
87
|
}
|
82
88
|
}
|
@@ -87,28 +87,36 @@ module.exports = {
|
|
87
87
|
data: {
|
88
88
|
key: formattedValue
|
89
89
|
},
|
90
|
-
fix(fixer) {
|
90
|
+
*fix(fixer) {
|
91
91
|
const leftBracket = sourceCode.getTokenAfter(node.object, astUtils.isOpeningBracketToken);
|
92
92
|
const rightBracket = sourceCode.getLastToken(node);
|
93
|
+
const nextToken = sourceCode.getTokenAfter(node);
|
93
94
|
|
94
|
-
|
95
|
-
|
96
|
-
//
|
97
|
-
return null;
|
95
|
+
// Don't perform any fixes if there are comments inside the brackets.
|
96
|
+
if (sourceCode.commentsExistBetween(leftBracket, rightBracket)) {
|
97
|
+
return; // eslint-disable-line eslint-plugin/fixer-return -- false positive
|
98
98
|
}
|
99
99
|
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
return fixer.replaceTextRange(
|
100
|
+
// Replace the brackets by an identifier.
|
101
|
+
if (!node.optional) {
|
102
|
+
yield fixer.insertTextBefore(
|
103
|
+
leftBracket,
|
104
|
+
astUtils.isDecimalInteger(node.object) ? " ." : "."
|
105
|
+
);
|
106
|
+
}
|
107
|
+
yield fixer.replaceTextRange(
|
109
108
|
[leftBracket.range[0], rightBracket.range[1]],
|
110
|
-
|
109
|
+
value
|
111
110
|
);
|
111
|
+
|
112
|
+
// Insert a space after the property if it will be connected to the next token.
|
113
|
+
if (
|
114
|
+
nextToken &&
|
115
|
+
rightBracket.range[1] === nextToken.range[0] &&
|
116
|
+
!astUtils.canTokensBeAdjacent(String(value), nextToken)
|
117
|
+
) {
|
118
|
+
yield fixer.insertTextAfter(node, " ");
|
119
|
+
}
|
112
120
|
}
|
113
121
|
});
|
114
122
|
}
|
@@ -141,29 +149,24 @@ module.exports = {
|
|
141
149
|
data: {
|
142
150
|
key: node.property.name
|
143
151
|
},
|
144
|
-
fix(fixer) {
|
145
|
-
const
|
146
|
-
const textAfterDot = sourceCode.text.slice(dot.range[1], node.property.range[0]);
|
147
|
-
|
148
|
-
if (textAfterDot.trim()) {
|
152
|
+
*fix(fixer) {
|
153
|
+
const dotToken = sourceCode.getTokenBefore(node.property);
|
149
154
|
|
150
|
-
|
151
|
-
|
155
|
+
// A statement that starts with `let[` is parsed as a destructuring variable declaration, not a MemberExpression.
|
156
|
+
if (node.object.type === "Identifier" && node.object.name === "let" && !node.optional) {
|
157
|
+
return; // eslint-disable-line eslint-plugin/fixer-return -- false positive
|
152
158
|
}
|
153
159
|
|
154
|
-
if
|
155
|
-
|
156
|
-
|
157
|
-
* A statement that starts with `let[` is parsed as a destructuring variable declaration, not
|
158
|
-
* a MemberExpression.
|
159
|
-
*/
|
160
|
-
return null;
|
160
|
+
// Don't perform any fixes if there are comments between the dot and the property name.
|
161
|
+
if (sourceCode.commentsExistBetween(dotToken, node.property)) {
|
162
|
+
return; // eslint-disable-line eslint-plugin/fixer-return -- false positive
|
161
163
|
}
|
162
164
|
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
165
|
+
// Replace the identifier to brackets.
|
166
|
+
if (!node.optional) {
|
167
|
+
yield fixer.remove(dotToken);
|
168
|
+
}
|
169
|
+
yield fixer.replaceText(node.property, `["${node.property.name}"]`);
|
167
170
|
}
|
168
171
|
});
|
169
172
|
}
|
@@ -126,15 +126,24 @@ module.exports = {
|
|
126
126
|
messageId: "unexpectedWhitespace",
|
127
127
|
fix(fixer) {
|
128
128
|
|
129
|
+
// Don't remove comments.
|
130
|
+
if (sourceCode.commentsExistBetween(leftToken, rightToken)) {
|
131
|
+
return null;
|
132
|
+
}
|
133
|
+
|
134
|
+
// If `?.` exsits, it doesn't hide no-undexpected-multiline errors
|
135
|
+
if (node.optional) {
|
136
|
+
return fixer.replaceTextRange([leftToken.range[1], rightToken.range[0]], "?.");
|
137
|
+
}
|
138
|
+
|
129
139
|
/*
|
130
140
|
* Only autofix if there is no newline
|
131
141
|
* https://github.com/eslint/eslint/issues/7787
|
132
142
|
*/
|
133
|
-
if (
|
134
|
-
return
|
143
|
+
if (hasNewline) {
|
144
|
+
return null;
|
135
145
|
}
|
136
|
-
|
137
|
-
return null;
|
146
|
+
return fixer.removeRange([leftToken.range[1], rightToken.range[0]]);
|
138
147
|
}
|
139
148
|
});
|
140
149
|
} else if (!never && !hasWhitespace) {
|
@@ -149,6 +158,9 @@ module.exports = {
|
|
149
158
|
},
|
150
159
|
messageId: "missing",
|
151
160
|
fix(fixer) {
|
161
|
+
if (node.optional) {
|
162
|
+
return null; // Not sure if inserting a space to either before/after `?.` token.
|
163
|
+
}
|
152
164
|
return fixer.insertTextBefore(rightToken, " ");
|
153
165
|
}
|
154
166
|
});
|
@@ -161,7 +173,31 @@ module.exports = {
|
|
161
173
|
},
|
162
174
|
messageId: "unexpectedNewline",
|
163
175
|
fix(fixer) {
|
164
|
-
|
176
|
+
|
177
|
+
/*
|
178
|
+
* Only autofix if there is no newline
|
179
|
+
* https://github.com/eslint/eslint/issues/7787
|
180
|
+
* But if `?.` exsits, it doesn't hide no-undexpected-multiline errors
|
181
|
+
*/
|
182
|
+
if (!node.optional) {
|
183
|
+
return null;
|
184
|
+
}
|
185
|
+
|
186
|
+
// Don't remove comments.
|
187
|
+
if (sourceCode.commentsExistBetween(leftToken, rightToken)) {
|
188
|
+
return null;
|
189
|
+
}
|
190
|
+
|
191
|
+
const range = [leftToken.range[1], rightToken.range[0]];
|
192
|
+
const qdToken = sourceCode.getTokenAfter(leftToken);
|
193
|
+
|
194
|
+
if (qdToken.range[0] === leftToken.range[1]) {
|
195
|
+
return fixer.replaceTextRange(range, "?. ");
|
196
|
+
}
|
197
|
+
if (qdToken.range[1] === rightToken.range[0]) {
|
198
|
+
return fixer.replaceTextRange(range, " ?.");
|
199
|
+
}
|
200
|
+
return fixer.replaceTextRange(range, " ?. ");
|
165
201
|
}
|
166
202
|
});
|
167
203
|
}
|
@@ -172,7 +208,7 @@ module.exports = {
|
|
172
208
|
const lastToken = sourceCode.getLastToken(node);
|
173
209
|
const lastCalleeToken = sourceCode.getLastToken(node.callee);
|
174
210
|
const parenToken = sourceCode.getFirstTokenBetween(lastCalleeToken, lastToken, astUtils.isOpeningParenToken);
|
175
|
-
const prevToken = parenToken && sourceCode.getTokenBefore(parenToken);
|
211
|
+
const prevToken = parenToken && sourceCode.getTokenBefore(parenToken, astUtils.isNotQuestionDotToken);
|
176
212
|
|
177
213
|
// Parens in NewExpression are optional
|
178
214
|
if (!(parenToken && parenToken.range[1] < node.range[1])) {
|