eslint 7.0.0 → 7.3.1
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 -10
- package/lib/cli-engine/cli-engine.js +2 -2
- package/lib/cli-engine/config-array-factory.js +34 -2
- package/lib/init/config-initializer.js +92 -74
- package/lib/linter/code-path-analysis/code-path-analyzer.js +2 -2
- package/lib/linter/code-path-analysis/code-path-state.js +34 -12
- package/lib/linter/config-comment-parser.js +1 -1
- package/lib/linter/linter.js +2 -1
- package/lib/rule-tester/rule-tester.js +9 -0
- package/lib/rules/accessor-pairs.js +1 -1
- package/lib/rules/arrow-parens.js +19 -3
- package/lib/rules/block-spacing.js +19 -2
- package/lib/rules/callback-return.js +1 -1
- package/lib/rules/comma-dangle.js +2 -1
- package/lib/rules/curly.js +8 -1
- package/lib/rules/func-call-spacing.js +18 -3
- package/lib/rules/global-require.js +1 -1
- package/lib/rules/handle-callback-err.js +1 -1
- package/lib/rules/id-match.js +2 -1
- package/lib/rules/index.js +3 -0
- package/lib/rules/key-spacing.js +6 -2
- package/lib/rules/keyword-spacing.js +9 -2
- package/lib/rules/linebreak-style.js +8 -2
- package/lib/rules/max-lines-per-function.js +1 -1
- package/lib/rules/max-lines.js +34 -8
- package/lib/rules/multiline-ternary.js +44 -25
- package/lib/rules/no-buffer-constructor.js +1 -1
- package/lib/rules/no-control-regex.js +1 -1
- package/lib/rules/no-extra-boolean-cast.js +3 -0
- package/lib/rules/no-extra-parens.js +30 -2
- package/lib/rules/no-invalid-regexp.js +1 -1
- package/lib/rules/no-loss-of-precision.js +198 -0
- package/lib/rules/no-misleading-character-class.js +1 -1
- package/lib/rules/no-mixed-operators.js +3 -2
- package/lib/rules/no-mixed-requires.js +1 -1
- package/lib/rules/no-mixed-spaces-and-tabs.js +14 -6
- package/lib/rules/no-new-func.js +22 -19
- package/lib/rules/no-new-require.js +1 -1
- package/lib/rules/no-new-symbol.js +2 -1
- package/lib/rules/no-path-concat.js +1 -1
- package/lib/rules/no-process-env.js +1 -1
- package/lib/rules/no-process-exit.js +1 -1
- package/lib/rules/no-promise-executor-return.js +121 -0
- package/lib/rules/no-regex-spaces.js +1 -1
- package/lib/rules/no-restricted-exports.js +6 -0
- package/lib/rules/no-restricted-modules.js +1 -1
- package/lib/rules/no-sync.js +1 -1
- package/lib/rules/no-unneeded-ternary.js +6 -4
- package/lib/rules/no-unreachable-loop.js +150 -0
- package/lib/rules/no-unused-expressions.js +1 -1
- package/lib/rules/no-unused-vars.js +5 -2
- package/lib/rules/no-useless-backreference.js +1 -1
- package/lib/rules/object-property-newline.js +1 -1
- package/lib/rules/one-var-declaration-per-line.js +1 -1
- package/lib/rules/operator-linebreak.js +2 -5
- package/lib/rules/padded-blocks.js +19 -5
- package/lib/rules/prefer-named-capture-group.js +1 -1
- package/lib/rules/quote-props.js +2 -2
- package/lib/rules/rest-spread-spacing.js +3 -6
- package/lib/rules/semi-spacing.js +33 -8
- package/lib/rules/template-tag-spacing.js +8 -2
- package/lib/rules/utils/ast-utils.js +106 -9
- package/lib/source-code/source-code.js +1 -0
- 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
@@ -147,10 +147,12 @@ module.exports = {
|
|
147
147
|
loc: node.consequent.loc.start,
|
148
148
|
messageId: "unnecessaryConditionalAssignment",
|
149
149
|
fix: fixer => {
|
150
|
-
const shouldParenthesizeAlternate =
|
151
|
-
|
152
|
-
|
153
|
-
|
150
|
+
const shouldParenthesizeAlternate =
|
151
|
+
(
|
152
|
+
astUtils.getPrecedence(node.alternate) < OR_PRECEDENCE ||
|
153
|
+
astUtils.isCoalesceExpression(node.alternate)
|
154
|
+
) &&
|
155
|
+
!astUtils.isParenthesised(sourceCode, node.alternate);
|
154
156
|
const alternateText = shouldParenthesizeAlternate
|
155
157
|
? `(${sourceCode.getText(node.alternate)})`
|
156
158
|
: astUtils.getParenthesisedText(sourceCode, node.alternate);
|
@@ -0,0 +1,150 @@
|
|
1
|
+
/**
|
2
|
+
* @fileoverview Rule to disallow loops with a body that allows only one iteration
|
3
|
+
* @author Milos Djermanovic
|
4
|
+
*/
|
5
|
+
|
6
|
+
"use strict";
|
7
|
+
|
8
|
+
//------------------------------------------------------------------------------
|
9
|
+
// Helpers
|
10
|
+
//------------------------------------------------------------------------------
|
11
|
+
|
12
|
+
const allLoopTypes = ["WhileStatement", "DoWhileStatement", "ForStatement", "ForInStatement", "ForOfStatement"];
|
13
|
+
|
14
|
+
/**
|
15
|
+
* Determines whether the given node is the first node in the code path to which a loop statement
|
16
|
+
* 'loops' for the next iteration.
|
17
|
+
* @param {ASTNode} node The node to check.
|
18
|
+
* @returns {boolean} `true` if the node is a looping target.
|
19
|
+
*/
|
20
|
+
function isLoopingTarget(node) {
|
21
|
+
const parent = node.parent;
|
22
|
+
|
23
|
+
if (parent) {
|
24
|
+
switch (parent.type) {
|
25
|
+
case "WhileStatement":
|
26
|
+
return node === parent.test;
|
27
|
+
case "DoWhileStatement":
|
28
|
+
return node === parent.body;
|
29
|
+
case "ForStatement":
|
30
|
+
return node === (parent.update || parent.test || parent.body);
|
31
|
+
case "ForInStatement":
|
32
|
+
case "ForOfStatement":
|
33
|
+
return node === parent.left;
|
34
|
+
|
35
|
+
// no default
|
36
|
+
}
|
37
|
+
}
|
38
|
+
|
39
|
+
return false;
|
40
|
+
}
|
41
|
+
|
42
|
+
/**
|
43
|
+
* Creates an array with elements from the first given array that are not included in the second given array.
|
44
|
+
* @param {Array} arrA The array to compare from.
|
45
|
+
* @param {Array} arrB The array to compare against.
|
46
|
+
* @returns {Array} a new array that represents `arrA \ arrB`.
|
47
|
+
*/
|
48
|
+
function getDifference(arrA, arrB) {
|
49
|
+
return arrA.filter(a => !arrB.includes(a));
|
50
|
+
}
|
51
|
+
|
52
|
+
//------------------------------------------------------------------------------
|
53
|
+
// Rule Definition
|
54
|
+
//------------------------------------------------------------------------------
|
55
|
+
|
56
|
+
module.exports = {
|
57
|
+
meta: {
|
58
|
+
type: "problem",
|
59
|
+
|
60
|
+
docs: {
|
61
|
+
description: "disallow loops with a body that allows only one iteration",
|
62
|
+
category: "Possible Errors",
|
63
|
+
recommended: false,
|
64
|
+
url: "https://eslint.org/docs/rules/no-unreachable-loop"
|
65
|
+
},
|
66
|
+
|
67
|
+
schema: [{
|
68
|
+
type: "object",
|
69
|
+
properties: {
|
70
|
+
ignore: {
|
71
|
+
type: "array",
|
72
|
+
items: {
|
73
|
+
enum: allLoopTypes
|
74
|
+
},
|
75
|
+
uniqueItems: true
|
76
|
+
}
|
77
|
+
},
|
78
|
+
additionalProperties: false
|
79
|
+
}],
|
80
|
+
|
81
|
+
messages: {
|
82
|
+
invalid: "Invalid loop. Its body allows only one iteration."
|
83
|
+
}
|
84
|
+
},
|
85
|
+
|
86
|
+
create(context) {
|
87
|
+
const ignoredLoopTypes = context.options[0] && context.options[0].ignore || [],
|
88
|
+
loopTypesToCheck = getDifference(allLoopTypes, ignoredLoopTypes),
|
89
|
+
loopSelector = loopTypesToCheck.join(","),
|
90
|
+
loopsByTargetSegments = new Map(),
|
91
|
+
loopsToReport = new Set();
|
92
|
+
|
93
|
+
let currentCodePath = null;
|
94
|
+
|
95
|
+
return {
|
96
|
+
onCodePathStart(codePath) {
|
97
|
+
currentCodePath = codePath;
|
98
|
+
},
|
99
|
+
|
100
|
+
onCodePathEnd() {
|
101
|
+
currentCodePath = currentCodePath.upper;
|
102
|
+
},
|
103
|
+
|
104
|
+
[loopSelector](node) {
|
105
|
+
|
106
|
+
/**
|
107
|
+
* Ignore unreachable loop statements to avoid unnecessary complexity in the implementation, or false positives otherwise.
|
108
|
+
* For unreachable segments, the code path analysis does not raise events required for this implementation.
|
109
|
+
*/
|
110
|
+
if (currentCodePath.currentSegments.some(segment => segment.reachable)) {
|
111
|
+
loopsToReport.add(node);
|
112
|
+
}
|
113
|
+
},
|
114
|
+
|
115
|
+
onCodePathSegmentStart(segment, node) {
|
116
|
+
if (isLoopingTarget(node)) {
|
117
|
+
const loop = node.parent;
|
118
|
+
|
119
|
+
loopsByTargetSegments.set(segment, loop);
|
120
|
+
}
|
121
|
+
},
|
122
|
+
|
123
|
+
onCodePathSegmentLoop(_, toSegment, node) {
|
124
|
+
const loop = loopsByTargetSegments.get(toSegment);
|
125
|
+
|
126
|
+
/**
|
127
|
+
* The second iteration is reachable, meaning that the loop is valid by the logic of this rule,
|
128
|
+
* only if there is at least one loop event with the appropriate target (which has been already
|
129
|
+
* determined in the `loopsByTargetSegments` map), raised from either:
|
130
|
+
*
|
131
|
+
* - the end of the loop's body (in which case `node === loop`)
|
132
|
+
* - a `continue` statement
|
133
|
+
*
|
134
|
+
* This condition skips loop events raised from `ForInStatement > .right` and `ForOfStatement > .right` nodes.
|
135
|
+
*/
|
136
|
+
if (node === loop || node.type === "ContinueStatement") {
|
137
|
+
|
138
|
+
// Removes loop if it exists in the set. Otherwise, `Set#delete` has no effect and doesn't throw.
|
139
|
+
loopsToReport.delete(loop);
|
140
|
+
}
|
141
|
+
},
|
142
|
+
|
143
|
+
"Program:exit"() {
|
144
|
+
loopsToReport.forEach(
|
145
|
+
node => context.report({ node, messageId: "invalid" })
|
146
|
+
);
|
147
|
+
}
|
148
|
+
};
|
149
|
+
}
|
150
|
+
};
|
@@ -124,7 +124,7 @@ module.exports = {
|
|
124
124
|
return true;
|
125
125
|
}
|
126
126
|
|
127
|
-
return /^(?:Assignment|Call|New|Update|Yield|Await)Expression$/u.test(node.type) ||
|
127
|
+
return /^(?:Assignment|Call|New|Update|Yield|Await|Import)Expression$/u.test(node.type) ||
|
128
128
|
(node.type === "UnaryExpression" && ["delete", "void"].indexOf(node.operator) >= 0);
|
129
129
|
}
|
130
130
|
|
@@ -68,7 +68,8 @@ module.exports = {
|
|
68
68
|
caughtErrorsIgnorePattern: {
|
69
69
|
type: "string"
|
70
70
|
}
|
71
|
-
}
|
71
|
+
},
|
72
|
+
additionalProperties: false
|
72
73
|
}
|
73
74
|
]
|
74
75
|
}
|
@@ -619,7 +620,9 @@ module.exports = {
|
|
619
620
|
// Report the first declaration.
|
620
621
|
if (unusedVar.defs.length > 0) {
|
621
622
|
context.report({
|
622
|
-
node: unusedVar.
|
623
|
+
node: unusedVar.references.length ? unusedVar.references[
|
624
|
+
unusedVar.references.length - 1
|
625
|
+
].identifier : unusedVar.identifiers[0],
|
623
626
|
messageId: "unusedVar",
|
624
627
|
data: unusedVar.references.some(ref => ref.isWrite())
|
625
628
|
? getAssignedMessageData(unusedVar)
|
@@ -77,7 +77,7 @@ module.exports = {
|
|
77
77
|
if (lastTokenOfPreviousProperty.loc.end.line === firstTokenOfCurrentProperty.loc.start.line) {
|
78
78
|
context.report({
|
79
79
|
node,
|
80
|
-
loc: firstTokenOfCurrentProperty.loc
|
80
|
+
loc: firstTokenOfCurrentProperty.loc,
|
81
81
|
messageId,
|
82
82
|
fix(fixer) {
|
83
83
|
const comma = sourceCode.getTokenBefore(firstTokenOfCurrentProperty);
|
@@ -35,11 +35,8 @@ module.exports = {
|
|
35
35
|
properties: {
|
36
36
|
overrides: {
|
37
37
|
type: "object",
|
38
|
-
|
39
|
-
|
40
|
-
type: "string",
|
41
|
-
enum: ["after", "before", "none", "ignore"]
|
42
|
-
}
|
38
|
+
additionalProperties: {
|
39
|
+
enum: ["after", "before", "none", "ignore"]
|
43
40
|
}
|
44
41
|
}
|
45
42
|
},
|
@@ -58,7 +58,8 @@ module.exports = {
|
|
58
58
|
allowSingleLineBlocks: {
|
59
59
|
type: "boolean"
|
60
60
|
}
|
61
|
-
}
|
61
|
+
},
|
62
|
+
additionalProperties: false
|
62
63
|
}
|
63
64
|
],
|
64
65
|
|
@@ -203,10 +204,14 @@ module.exports = {
|
|
203
204
|
}
|
204
205
|
|
205
206
|
if (requirePaddingFor(node)) {
|
207
|
+
|
206
208
|
if (!blockHasTopPadding) {
|
207
209
|
context.report({
|
208
210
|
node,
|
209
|
-
loc: {
|
211
|
+
loc: {
|
212
|
+
start: tokenBeforeFirst.loc.start,
|
213
|
+
end: firstBlockToken.loc.start
|
214
|
+
},
|
210
215
|
fix(fixer) {
|
211
216
|
return fixer.insertTextAfter(tokenBeforeFirst, "\n");
|
212
217
|
},
|
@@ -216,7 +221,10 @@ module.exports = {
|
|
216
221
|
if (!blockHasBottomPadding) {
|
217
222
|
context.report({
|
218
223
|
node,
|
219
|
-
loc: {
|
224
|
+
loc: {
|
225
|
+
end: tokenAfterLast.loc.start,
|
226
|
+
start: lastBlockToken.loc.end
|
227
|
+
},
|
220
228
|
fix(fixer) {
|
221
229
|
return fixer.insertTextBefore(tokenAfterLast, "\n");
|
222
230
|
},
|
@@ -228,7 +236,10 @@ module.exports = {
|
|
228
236
|
|
229
237
|
context.report({
|
230
238
|
node,
|
231
|
-
loc: {
|
239
|
+
loc: {
|
240
|
+
start: tokenBeforeFirst.loc.start,
|
241
|
+
end: firstBlockToken.loc.start
|
242
|
+
},
|
232
243
|
fix(fixer) {
|
233
244
|
return fixer.replaceTextRange([tokenBeforeFirst.range[1], firstBlockToken.range[0] - firstBlockToken.loc.start.column], "\n");
|
234
245
|
},
|
@@ -240,7 +251,10 @@ module.exports = {
|
|
240
251
|
|
241
252
|
context.report({
|
242
253
|
node,
|
243
|
-
loc: {
|
254
|
+
loc: {
|
255
|
+
end: tokenAfterLast.loc.start,
|
256
|
+
start: lastBlockToken.loc.end
|
257
|
+
},
|
244
258
|
messageId: "neverPadBlock",
|
245
259
|
fix(fixer) {
|
246
260
|
return fixer.replaceTextRange([lastBlockToken.range[1], tokenAfterLast.range[0] - tokenAfterLast.loc.start.column], "\n");
|
package/lib/rules/quote-props.js
CHANGED
@@ -154,7 +154,7 @@ module.exports = {
|
|
154
154
|
|
155
155
|
try {
|
156
156
|
tokens = espree.tokenize(key.value);
|
157
|
-
} catch
|
157
|
+
} catch {
|
158
158
|
return;
|
159
159
|
}
|
160
160
|
|
@@ -239,7 +239,7 @@ module.exports = {
|
|
239
239
|
|
240
240
|
try {
|
241
241
|
tokens = espree.tokenize(key.value);
|
242
|
-
} catch
|
242
|
+
} catch {
|
243
243
|
necessaryQuotes = true;
|
244
244
|
return;
|
245
245
|
}
|
@@ -79,10 +79,7 @@ module.exports = {
|
|
79
79
|
if (alwaysSpace && !hasWhitespace) {
|
80
80
|
context.report({
|
81
81
|
node,
|
82
|
-
loc:
|
83
|
-
line: operator.loc.end.line,
|
84
|
-
column: operator.loc.end.column
|
85
|
-
},
|
82
|
+
loc: operator.loc,
|
86
83
|
messageId: "expectedWhitespace",
|
87
84
|
data: {
|
88
85
|
type
|
@@ -95,8 +92,8 @@ module.exports = {
|
|
95
92
|
context.report({
|
96
93
|
node,
|
97
94
|
loc: {
|
98
|
-
|
99
|
-
|
95
|
+
start: operator.loc.end,
|
96
|
+
end: nextToken.loc.start
|
100
97
|
},
|
101
98
|
messageId: "unexpectedWhitespace",
|
102
99
|
data: {
|
@@ -117,6 +117,18 @@ module.exports = {
|
|
117
117
|
}
|
118
118
|
|
119
119
|
/**
|
120
|
+
* Report location example :
|
121
|
+
*
|
122
|
+
* for unexpected space `before`
|
123
|
+
*
|
124
|
+
* var a = 'b' ;
|
125
|
+
* ^^^
|
126
|
+
*
|
127
|
+
* for unexpected space `after`
|
128
|
+
*
|
129
|
+
* var a = 'b'; c = 10;
|
130
|
+
* ^^
|
131
|
+
*
|
120
132
|
* Reports if the given token has invalid spacing.
|
121
133
|
* @param {Token} token The semicolon token to check.
|
122
134
|
* @param {ASTNode} node The corresponding node of the token.
|
@@ -124,16 +136,19 @@ module.exports = {
|
|
124
136
|
*/
|
125
137
|
function checkSemicolonSpacing(token, node) {
|
126
138
|
if (astUtils.isSemicolonToken(token)) {
|
127
|
-
const location = token.loc.start;
|
128
|
-
|
129
139
|
if (hasLeadingSpace(token)) {
|
130
140
|
if (!requireSpaceBefore) {
|
141
|
+
const tokenBefore = sourceCode.getTokenBefore(token);
|
142
|
+
const loc = {
|
143
|
+
start: tokenBefore.loc.end,
|
144
|
+
end: token.loc.start
|
145
|
+
};
|
146
|
+
|
131
147
|
context.report({
|
132
148
|
node,
|
133
|
-
loc
|
149
|
+
loc,
|
134
150
|
messageId: "unexpectedWhitespaceBefore",
|
135
151
|
fix(fixer) {
|
136
|
-
const tokenBefore = sourceCode.getTokenBefore(token);
|
137
152
|
|
138
153
|
return fixer.removeRange([tokenBefore.range[1], token.range[0]]);
|
139
154
|
}
|
@@ -141,9 +156,11 @@ module.exports = {
|
|
141
156
|
}
|
142
157
|
} else {
|
143
158
|
if (requireSpaceBefore) {
|
159
|
+
const loc = token.loc;
|
160
|
+
|
144
161
|
context.report({
|
145
162
|
node,
|
146
|
-
loc
|
163
|
+
loc,
|
147
164
|
messageId: "missingWhitespaceBefore",
|
148
165
|
fix(fixer) {
|
149
166
|
return fixer.insertTextBefore(token, " ");
|
@@ -155,12 +172,17 @@ module.exports = {
|
|
155
172
|
if (!isFirstTokenInCurrentLine(token) && !isLastTokenInCurrentLine(token) && !isBeforeClosingParen(token)) {
|
156
173
|
if (hasTrailingSpace(token)) {
|
157
174
|
if (!requireSpaceAfter) {
|
175
|
+
const tokenAfter = sourceCode.getTokenAfter(token);
|
176
|
+
const loc = {
|
177
|
+
start: token.loc.end,
|
178
|
+
end: tokenAfter.loc.start
|
179
|
+
};
|
180
|
+
|
158
181
|
context.report({
|
159
182
|
node,
|
160
|
-
loc
|
183
|
+
loc,
|
161
184
|
messageId: "unexpectedWhitespaceAfter",
|
162
185
|
fix(fixer) {
|
163
|
-
const tokenAfter = sourceCode.getTokenAfter(token);
|
164
186
|
|
165
187
|
return fixer.removeRange([token.range[1], tokenAfter.range[0]]);
|
166
188
|
}
|
@@ -168,9 +190,11 @@ module.exports = {
|
|
168
190
|
}
|
169
191
|
} else {
|
170
192
|
if (requireSpaceAfter) {
|
193
|
+
const loc = token.loc;
|
194
|
+
|
171
195
|
context.report({
|
172
196
|
node,
|
173
|
-
loc
|
197
|
+
loc,
|
174
198
|
messageId: "missingWhitespaceAfter",
|
175
199
|
fix(fixer) {
|
176
200
|
return fixer.insertTextAfter(token, " ");
|
@@ -199,6 +223,7 @@ module.exports = {
|
|
199
223
|
BreakStatement: checkNode,
|
200
224
|
ContinueStatement: checkNode,
|
201
225
|
DebuggerStatement: checkNode,
|
226
|
+
DoWhileStatement: checkNode,
|
202
227
|
ReturnStatement: checkNode,
|
203
228
|
ThrowStatement: checkNode,
|
204
229
|
ImportDeclaration: checkNode,
|
@@ -49,7 +49,10 @@ module.exports = {
|
|
49
49
|
if (never && hasWhitespace) {
|
50
50
|
context.report({
|
51
51
|
node,
|
52
|
-
loc:
|
52
|
+
loc: {
|
53
|
+
start: tagToken.loc.end,
|
54
|
+
end: literalToken.loc.start
|
55
|
+
},
|
53
56
|
messageId: "unexpected",
|
54
57
|
fix(fixer) {
|
55
58
|
const comments = sourceCode.getCommentsBefore(node.quasi);
|
@@ -68,7 +71,10 @@ module.exports = {
|
|
68
71
|
} else if (!never && !hasWhitespace) {
|
69
72
|
context.report({
|
70
73
|
node,
|
71
|
-
loc:
|
74
|
+
loc: {
|
75
|
+
start: node.loc.start,
|
76
|
+
end: literalToken.loc.start
|
77
|
+
},
|
72
78
|
messageId: "missing",
|
73
79
|
fix(fixer) {
|
74
80
|
return fixer.insertTextAfter(tagToken, " ");
|
@@ -416,6 +416,53 @@ function equalTokens(left, right, sourceCode) {
|
|
416
416
|
return true;
|
417
417
|
}
|
418
418
|
|
419
|
+
/**
|
420
|
+
* Check if the given node is a true logical expression or not.
|
421
|
+
*
|
422
|
+
* The three binary expressions logical-or (`||`), logical-and (`&&`), and
|
423
|
+
* coalesce (`??`) are known as `ShortCircuitExpression`.
|
424
|
+
* But ESTree represents those by `LogicalExpression` node.
|
425
|
+
*
|
426
|
+
* This function rejects coalesce expressions of `LogicalExpression` node.
|
427
|
+
* @param {ASTNode} node The node to check.
|
428
|
+
* @returns {boolean} `true` if the node is `&&` or `||`.
|
429
|
+
* @see https://tc39.es/ecma262/#prod-ShortCircuitExpression
|
430
|
+
*/
|
431
|
+
function isLogicalExpression(node) {
|
432
|
+
return (
|
433
|
+
node.type === "LogicalExpression" &&
|
434
|
+
(node.operator === "&&" || node.operator === "||")
|
435
|
+
);
|
436
|
+
}
|
437
|
+
|
438
|
+
/**
|
439
|
+
* Check if the given node is a nullish coalescing expression or not.
|
440
|
+
*
|
441
|
+
* The three binary expressions logical-or (`||`), logical-and (`&&`), and
|
442
|
+
* coalesce (`??`) are known as `ShortCircuitExpression`.
|
443
|
+
* But ESTree represents those by `LogicalExpression` node.
|
444
|
+
*
|
445
|
+
* This function finds only coalesce expressions of `LogicalExpression` node.
|
446
|
+
* @param {ASTNode} node The node to check.
|
447
|
+
* @returns {boolean} `true` if the node is `??`.
|
448
|
+
*/
|
449
|
+
function isCoalesceExpression(node) {
|
450
|
+
return node.type === "LogicalExpression" && node.operator === "??";
|
451
|
+
}
|
452
|
+
|
453
|
+
/**
|
454
|
+
* Check if given two nodes are the pair of a logical expression and a coalesce expression.
|
455
|
+
* @param {ASTNode} left A node to check.
|
456
|
+
* @param {ASTNode} right Another node to check.
|
457
|
+
* @returns {boolean} `true` if the two nodes are the pair of a logical expression and a coalesce expression.
|
458
|
+
*/
|
459
|
+
function isMixedLogicalAndCoalesceExpressions(left, right) {
|
460
|
+
return (
|
461
|
+
(isLogicalExpression(left) && isCoalesceExpression(right)) ||
|
462
|
+
(isCoalesceExpression(left) && isLogicalExpression(right))
|
463
|
+
);
|
464
|
+
}
|
465
|
+
|
419
466
|
//------------------------------------------------------------------------------
|
420
467
|
// Public Interface
|
421
468
|
//------------------------------------------------------------------------------
|
@@ -779,6 +826,7 @@ module.exports = {
|
|
779
826
|
case "LogicalExpression":
|
780
827
|
switch (node.operator) {
|
781
828
|
case "||":
|
829
|
+
case "??":
|
782
830
|
return 4;
|
783
831
|
case "&&":
|
784
832
|
return 5;
|
@@ -1243,19 +1291,64 @@ module.exports = {
|
|
1243
1291
|
|
1244
1292
|
/**
|
1245
1293
|
* Gets next location when the result is not out of bound, otherwise returns null.
|
1294
|
+
*
|
1295
|
+
* Assumptions:
|
1296
|
+
*
|
1297
|
+
* - The given location represents a valid location in the given source code.
|
1298
|
+
* - Columns are 0-based.
|
1299
|
+
* - Lines are 1-based.
|
1300
|
+
* - Column immediately after the last character in a line (not incl. linebreaks) is considered to be a valid location.
|
1301
|
+
* - If the source code ends with a linebreak, `sourceCode.lines` array will have an extra element (empty string) at the end.
|
1302
|
+
* The start (column 0) of that extra line is considered to be a valid location.
|
1303
|
+
*
|
1304
|
+
* Examples of successive locations (line, column):
|
1305
|
+
*
|
1306
|
+
* code: foo
|
1307
|
+
* locations: (1, 0) -> (1, 1) -> (1, 2) -> (1, 3) -> null
|
1308
|
+
*
|
1309
|
+
* code: foo<LF>
|
1310
|
+
* locations: (1, 0) -> (1, 1) -> (1, 2) -> (1, 3) -> (2, 0) -> null
|
1311
|
+
*
|
1312
|
+
* code: foo<CR><LF>
|
1313
|
+
* locations: (1, 0) -> (1, 1) -> (1, 2) -> (1, 3) -> (2, 0) -> null
|
1314
|
+
*
|
1315
|
+
* code: a<LF>b
|
1316
|
+
* locations: (1, 0) -> (1, 1) -> (2, 0) -> (2, 1) -> null
|
1317
|
+
*
|
1318
|
+
* code: a<LF>b<LF>
|
1319
|
+
* locations: (1, 0) -> (1, 1) -> (2, 0) -> (2, 1) -> (3, 0) -> null
|
1320
|
+
*
|
1321
|
+
* code: a<CR><LF>b<CR><LF>
|
1322
|
+
* locations: (1, 0) -> (1, 1) -> (2, 0) -> (2, 1) -> (3, 0) -> null
|
1323
|
+
*
|
1324
|
+
* code: a<LF><LF>
|
1325
|
+
* locations: (1, 0) -> (1, 1) -> (2, 0) -> (3, 0) -> null
|
1326
|
+
*
|
1327
|
+
* code: <LF>
|
1328
|
+
* locations: (1, 0) -> (2, 0) -> null
|
1329
|
+
*
|
1330
|
+
* code:
|
1331
|
+
* locations: (1, 0) -> null
|
1246
1332
|
* @param {SourceCode} sourceCode The sourceCode
|
1247
1333
|
* @param {{line: number, column: number}} location The location
|
1248
1334
|
* @returns {{line: number, column: number} | null} Next location
|
1249
1335
|
*/
|
1250
|
-
getNextLocation(sourceCode,
|
1251
|
-
|
1336
|
+
getNextLocation(sourceCode, { line, column }) {
|
1337
|
+
if (column < sourceCode.lines[line - 1].length) {
|
1338
|
+
return {
|
1339
|
+
line,
|
1340
|
+
column: column + 1
|
1341
|
+
};
|
1342
|
+
}
|
1252
1343
|
|
1253
|
-
|
1254
|
-
|
1255
|
-
|
1344
|
+
if (line < sourceCode.lines.length) {
|
1345
|
+
return {
|
1346
|
+
line: line + 1,
|
1347
|
+
column: 0
|
1348
|
+
};
|
1256
1349
|
}
|
1257
1350
|
|
1258
|
-
return
|
1351
|
+
return null;
|
1259
1352
|
},
|
1260
1353
|
|
1261
1354
|
/**
|
@@ -1370,7 +1463,7 @@ module.exports = {
|
|
1370
1463
|
|
1371
1464
|
try {
|
1372
1465
|
tokens = espree.tokenize(leftValue, espreeOptions);
|
1373
|
-
} catch
|
1466
|
+
} catch {
|
1374
1467
|
return false;
|
1375
1468
|
}
|
1376
1469
|
|
@@ -1399,7 +1492,7 @@ module.exports = {
|
|
1399
1492
|
|
1400
1493
|
try {
|
1401
1494
|
tokens = espree.tokenize(rightValue, espreeOptions);
|
1402
|
-
} catch
|
1495
|
+
} catch {
|
1403
1496
|
return false;
|
1404
1497
|
}
|
1405
1498
|
|
@@ -1493,5 +1586,9 @@ module.exports = {
|
|
1493
1586
|
*/
|
1494
1587
|
hasOctalEscapeSequence(rawString) {
|
1495
1588
|
return OCTAL_ESCAPE_PATTERN.test(rawString);
|
1496
|
-
}
|
1589
|
+
},
|
1590
|
+
|
1591
|
+
isLogicalExpression,
|
1592
|
+
isCoalesceExpression,
|
1593
|
+
isMixedLogicalAndCoalesceExpressions
|
1497
1594
|
};
|
@@ -305,6 +305,7 @@ class SourceCode extends TokenStore {
|
|
305
305
|
* @returns {Object} An object containing a leading and trailing array
|
306
306
|
* of comments indexed by their position.
|
307
307
|
* @public
|
308
|
+
* @deprecated replaced by getCommentsBefore(), getCommentsAfter(), and getCommentsInside().
|
308
309
|
*/
|
309
310
|
getComments(node) {
|
310
311
|
if (this._commentCache.has(node)) {
|
@@ -2,4 +2,4 @@ ESLint couldn't find the config "<%- configName %>" to extend from. Please check
|
|
2
2
|
|
3
3
|
The config "<%- configName %>" was referenced from the config file in "<%- importerName %>".
|
4
4
|
|
5
|
-
If you still have problems, please stop by https://
|
5
|
+
If you still have problems, please stop by https://eslint.org/chat to chat with the team.
|
@@ -4,4 +4,4 @@ ESLint couldn't find a configuration file. To set up a configuration file for th
|
|
4
4
|
|
5
5
|
ESLint looked for configuration files in <%= directoryPath %> and its ancestors. If it found none, it then looked in your home directory.
|
6
6
|
|
7
|
-
If you think you already have a configuration file or if you need more help, please stop by the ESLint chat room: https://
|
7
|
+
If you think you already have a configuration file or if you need more help, please stop by the ESLint chat room: https://eslint.org/chat
|
@@ -4,4 +4,4 @@ ESLint couldn't determine the plugin "<%- pluginId %>" uniquely.
|
|
4
4
|
|
5
5
|
Please remove the "plugins" setting from either config or remove either plugin installation.
|
6
6
|
|
7
|
-
If you still can't figure out the problem, please stop by https://
|
7
|
+
If you still can't figure out the problem, please stop by https://eslint.org/chat to chat with the team.
|