eslint 9.0.0-alpha.0 → 9.0.0-alpha.2
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/README.md +6 -1
- package/conf/ecma-version.js +16 -0
- package/lib/cli-engine/cli-engine.js +1 -1
- package/lib/cli-engine/lint-result-cache.js +2 -2
- package/lib/cli.js +14 -16
- package/lib/eslint/eslint.js +7 -0
- package/lib/linter/apply-disable-directives.js +2 -2
- package/lib/linter/code-path-analysis/code-path.js +32 -30
- package/lib/linter/code-path-analysis/fork-context.js +1 -1
- package/lib/linter/config-comment-parser.js +7 -10
- package/lib/linter/linter.js +105 -4
- package/lib/linter/report-translator.js +2 -2
- package/lib/linter/source-code-fixer.js +1 -1
- package/lib/rule-tester/rule-tester.js +45 -26
- package/lib/rules/array-bracket-newline.js +1 -1
- package/lib/rules/array-bracket-spacing.js +1 -1
- package/lib/rules/block-scoped-var.js +1 -1
- package/lib/rules/callback-return.js +2 -2
- package/lib/rules/comma-dangle.js +1 -1
- package/lib/rules/comma-style.js +2 -2
- package/lib/rules/complexity.js +1 -1
- package/lib/rules/constructor-super.js +1 -1
- package/lib/rules/default-case.js +1 -1
- package/lib/rules/eol-last.js +2 -2
- package/lib/rules/function-paren-newline.js +2 -2
- package/lib/rules/indent-legacy.js +5 -5
- package/lib/rules/indent.js +5 -5
- package/lib/rules/index.js +1 -0
- package/lib/rules/key-spacing.js +2 -2
- package/lib/rules/line-comment-position.js +1 -1
- package/lib/rules/lines-around-directive.js +2 -2
- package/lib/rules/max-depth.js +1 -1
- package/lib/rules/max-len.js +3 -3
- package/lib/rules/max-lines.js +3 -3
- package/lib/rules/max-nested-callbacks.js +1 -1
- package/lib/rules/max-params.js +1 -1
- package/lib/rules/max-statements.js +1 -1
- package/lib/rules/multiline-comment-style.js +7 -7
- package/lib/rules/new-cap.js +1 -1
- package/lib/rules/newline-after-var.js +1 -1
- package/lib/rules/newline-before-return.js +1 -1
- package/lib/rules/no-constant-binary-expression.js +5 -5
- package/lib/rules/no-constructor-return.js +1 -1
- package/lib/rules/no-dupe-class-members.js +2 -2
- package/lib/rules/no-else-return.js +1 -1
- package/lib/rules/no-empty-function.js +2 -2
- package/lib/rules/no-fallthrough.js +1 -1
- package/lib/rules/no-implicit-coercion.js +51 -25
- package/lib/rules/no-inner-declarations.js +22 -1
- package/lib/rules/no-invalid-this.js +1 -1
- package/lib/rules/no-lone-blocks.js +2 -2
- package/lib/rules/no-loss-of-precision.js +1 -1
- package/lib/rules/no-misleading-character-class.js +174 -65
- package/lib/rules/no-multiple-empty-lines.js +1 -1
- package/lib/rules/no-restricted-globals.js +1 -1
- package/lib/rules/no-restricted-imports.js +54 -44
- package/lib/rules/no-restricted-modules.js +2 -2
- package/lib/rules/no-return-await.js +1 -1
- package/lib/rules/no-this-before-super.js +17 -4
- package/lib/rules/no-trailing-spaces.js +2 -3
- package/lib/rules/no-unneeded-ternary.js +1 -1
- package/lib/rules/no-unsafe-optional-chaining.js +1 -1
- package/lib/rules/no-unused-vars.js +6 -8
- package/lib/rules/no-useless-assignment.js +566 -0
- package/lib/rules/no-useless-backreference.js +1 -1
- package/lib/rules/object-curly-spacing.js +3 -3
- package/lib/rules/object-property-newline.js +1 -1
- package/lib/rules/one-var.js +5 -5
- package/lib/rules/padded-blocks.js +7 -7
- package/lib/rules/prefer-arrow-callback.js +3 -3
- package/lib/rules/prefer-reflect.js +1 -1
- package/lib/rules/prefer-regex-literals.js +1 -1
- package/lib/rules/prefer-template.js +1 -1
- package/lib/rules/radix.js +2 -2
- package/lib/rules/semi-style.js +1 -1
- package/lib/rules/sort-imports.js +1 -1
- package/lib/rules/sort-keys.js +1 -1
- package/lib/rules/sort-vars.js +1 -1
- package/lib/rules/space-unary-ops.js +1 -1
- package/lib/rules/strict.js +1 -1
- package/lib/rules/utils/ast-utils.js +7 -7
- package/lib/rules/yield-star-spacing.js +1 -1
- package/lib/shared/serialization.js +55 -0
- package/lib/source-code/source-code.js +4 -4
- package/lib/source-code/token-store/index.js +2 -2
- package/package.json +7 -7
- package/conf/config-schema.js +0 -93
- package/lib/shared/config-validator.js +0 -380
- package/lib/shared/relative-module-resolver.js +0 -50
@@ -188,6 +188,7 @@ function getNonEmptyOperand(node) {
|
|
188
188
|
/** @type {import('../shared/types').Rule} */
|
189
189
|
module.exports = {
|
190
190
|
meta: {
|
191
|
+
hasSuggestions: true,
|
191
192
|
type: "suggestion",
|
192
193
|
|
193
194
|
docs: {
|
@@ -229,7 +230,8 @@ module.exports = {
|
|
229
230
|
}],
|
230
231
|
|
231
232
|
messages: {
|
232
|
-
|
233
|
+
implicitCoercion: "Unexpected implicit coercion encountered. Use `{{recommendation}}` instead.",
|
234
|
+
useRecommendation: "Use `{{recommendation}}` instead."
|
233
235
|
}
|
234
236
|
},
|
235
237
|
|
@@ -241,32 +243,54 @@ module.exports = {
|
|
241
243
|
* Reports an error and autofixes the node
|
242
244
|
* @param {ASTNode} node An ast node to report the error on.
|
243
245
|
* @param {string} recommendation The recommended code for the issue
|
246
|
+
* @param {bool} shouldSuggest Whether this report should offer a suggestion
|
244
247
|
* @param {bool} shouldFix Whether this report should fix the node
|
245
248
|
* @returns {void}
|
246
249
|
*/
|
247
|
-
function report(node, recommendation, shouldFix) {
|
250
|
+
function report(node, recommendation, shouldSuggest, shouldFix) {
|
251
|
+
|
252
|
+
/**
|
253
|
+
* Fix function
|
254
|
+
* @param {RuleFixer} fixer The fixer to fix.
|
255
|
+
* @returns {Fix} The fix object.
|
256
|
+
*/
|
257
|
+
function fix(fixer) {
|
258
|
+
const tokenBefore = sourceCode.getTokenBefore(node);
|
259
|
+
|
260
|
+
if (
|
261
|
+
tokenBefore?.range[1] === node.range[0] &&
|
262
|
+
!astUtils.canTokensBeAdjacent(tokenBefore, recommendation)
|
263
|
+
) {
|
264
|
+
return fixer.replaceText(node, ` ${recommendation}`);
|
265
|
+
}
|
266
|
+
|
267
|
+
return fixer.replaceText(node, recommendation);
|
268
|
+
}
|
269
|
+
|
248
270
|
context.report({
|
249
271
|
node,
|
250
|
-
messageId: "
|
251
|
-
data: {
|
252
|
-
recommendation
|
253
|
-
},
|
272
|
+
messageId: "implicitCoercion",
|
273
|
+
data: { recommendation },
|
254
274
|
fix(fixer) {
|
255
275
|
if (!shouldFix) {
|
256
276
|
return null;
|
257
277
|
}
|
258
278
|
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
279
|
+
return fix(fixer);
|
280
|
+
},
|
281
|
+
suggest: [
|
282
|
+
{
|
283
|
+
messageId: "useRecommendation",
|
284
|
+
data: { recommendation },
|
285
|
+
fix(fixer) {
|
286
|
+
if (shouldFix || !shouldSuggest) {
|
287
|
+
return null;
|
288
|
+
}
|
289
|
+
|
290
|
+
return fix(fixer);
|
291
|
+
}
|
267
292
|
}
|
268
|
-
|
269
|
-
}
|
293
|
+
]
|
270
294
|
});
|
271
295
|
}
|
272
296
|
|
@@ -278,8 +302,10 @@ module.exports = {
|
|
278
302
|
operatorAllowed = options.allow.includes("!!");
|
279
303
|
if (!operatorAllowed && options.boolean && isDoubleLogicalNegating(node)) {
|
280
304
|
const recommendation = `Boolean(${sourceCode.getText(node.argument.argument)})`;
|
305
|
+
const variable = astUtils.getVariableByName(sourceCode.getScope(node), "Boolean");
|
306
|
+
const booleanExists = variable?.identifiers.length === 0;
|
281
307
|
|
282
|
-
report(node, recommendation, true);
|
308
|
+
report(node, recommendation, true, booleanExists);
|
283
309
|
}
|
284
310
|
|
285
311
|
// ~foo.indexOf(bar)
|
@@ -290,7 +316,7 @@ module.exports = {
|
|
290
316
|
const comparison = node.argument.type === "ChainExpression" ? ">= 0" : "!== -1";
|
291
317
|
const recommendation = `${sourceCode.getText(node.argument)} ${comparison}`;
|
292
318
|
|
293
|
-
report(node, recommendation, false);
|
319
|
+
report(node, recommendation, false, false);
|
294
320
|
}
|
295
321
|
|
296
322
|
// +foo
|
@@ -298,7 +324,7 @@ module.exports = {
|
|
298
324
|
if (!operatorAllowed && options.number && node.operator === "+" && !isNumeric(node.argument)) {
|
299
325
|
const recommendation = `Number(${sourceCode.getText(node.argument)})`;
|
300
326
|
|
301
|
-
report(node, recommendation, true);
|
327
|
+
report(node, recommendation, true, false);
|
302
328
|
}
|
303
329
|
|
304
330
|
// -(-foo)
|
@@ -306,7 +332,7 @@ module.exports = {
|
|
306
332
|
if (!operatorAllowed && options.number && node.operator === "-" && node.argument.type === "UnaryExpression" && node.argument.operator === "-" && !isNumeric(node.argument.argument)) {
|
307
333
|
const recommendation = `Number(${sourceCode.getText(node.argument.argument)})`;
|
308
334
|
|
309
|
-
report(node, recommendation, false);
|
335
|
+
report(node, recommendation, true, false);
|
310
336
|
}
|
311
337
|
},
|
312
338
|
|
@@ -322,7 +348,7 @@ module.exports = {
|
|
322
348
|
if (nonNumericOperand) {
|
323
349
|
const recommendation = `Number(${sourceCode.getText(nonNumericOperand)})`;
|
324
350
|
|
325
|
-
report(node, recommendation, true);
|
351
|
+
report(node, recommendation, true, false);
|
326
352
|
}
|
327
353
|
|
328
354
|
// foo - 0
|
@@ -330,7 +356,7 @@ module.exports = {
|
|
330
356
|
if (!operatorAllowed && options.number && node.operator === "-" && node.right.type === "Literal" && node.right.value === 0 && !isNumeric(node.left)) {
|
331
357
|
const recommendation = `Number(${sourceCode.getText(node.left)})`;
|
332
358
|
|
333
|
-
report(node, recommendation, true);
|
359
|
+
report(node, recommendation, true, false);
|
334
360
|
}
|
335
361
|
|
336
362
|
// "" + foo
|
@@ -338,7 +364,7 @@ module.exports = {
|
|
338
364
|
if (!operatorAllowed && options.string && isConcatWithEmptyString(node)) {
|
339
365
|
const recommendation = `String(${sourceCode.getText(getNonEmptyOperand(node))})`;
|
340
366
|
|
341
|
-
report(node, recommendation, true);
|
367
|
+
report(node, recommendation, true, false);
|
342
368
|
}
|
343
369
|
},
|
344
370
|
|
@@ -351,7 +377,7 @@ module.exports = {
|
|
351
377
|
const code = sourceCode.getText(getNonEmptyOperand(node));
|
352
378
|
const recommendation = `${code} = String(${code})`;
|
353
379
|
|
354
|
-
report(node, recommendation, true);
|
380
|
+
report(node, recommendation, true, false);
|
355
381
|
}
|
356
382
|
},
|
357
383
|
|
@@ -389,7 +415,7 @@ module.exports = {
|
|
389
415
|
const code = sourceCode.getText(node.expressions[0]);
|
390
416
|
const recommendation = `String(${code})`;
|
391
417
|
|
392
|
-
report(node, recommendation, true);
|
418
|
+
report(node, recommendation, true, false);
|
393
419
|
}
|
394
420
|
};
|
395
421
|
}
|
@@ -56,6 +56,15 @@ module.exports = {
|
|
56
56
|
schema: [
|
57
57
|
{
|
58
58
|
enum: ["functions", "both"]
|
59
|
+
},
|
60
|
+
{
|
61
|
+
type: "object",
|
62
|
+
properties: {
|
63
|
+
blockScopedFunctions: {
|
64
|
+
enum: ["allow", "disallow"]
|
65
|
+
}
|
66
|
+
},
|
67
|
+
additionalProperties: false
|
59
68
|
}
|
60
69
|
],
|
61
70
|
|
@@ -66,6 +75,10 @@ module.exports = {
|
|
66
75
|
|
67
76
|
create(context) {
|
68
77
|
|
78
|
+
const sourceCode = context.sourceCode;
|
79
|
+
const ecmaVersion = context.languageOptions.ecmaVersion;
|
80
|
+
const blockScopedFunctions = context.options[1]?.blockScopedFunctions ?? "allow";
|
81
|
+
|
69
82
|
/**
|
70
83
|
* Ensure that a given node is at a program or function body's root.
|
71
84
|
* @param {ASTNode} node Declaration node to check.
|
@@ -97,7 +110,15 @@ module.exports = {
|
|
97
110
|
|
98
111
|
return {
|
99
112
|
|
100
|
-
FunctionDeclaration
|
113
|
+
FunctionDeclaration(node) {
|
114
|
+
const isInStrictCode = sourceCode.getScope(node).upper.isStrict;
|
115
|
+
|
116
|
+
if (blockScopedFunctions === "allow" && ecmaVersion >= 2015 && isInStrictCode) {
|
117
|
+
return;
|
118
|
+
}
|
119
|
+
|
120
|
+
check(node);
|
121
|
+
},
|
101
122
|
VariableDeclaration(node) {
|
102
123
|
if (context.options[0] === "both" && node.kind === "var") {
|
103
124
|
check(node);
|
@@ -74,7 +74,7 @@ module.exports = {
|
|
74
74
|
* an object which has a flag that whether or not `this` keyword is valid.
|
75
75
|
*/
|
76
76
|
stack.getCurrent = function() {
|
77
|
-
const current = this
|
77
|
+
const current = this.at(-1);
|
78
78
|
|
79
79
|
if (!current.init) {
|
80
80
|
current.init = true;
|
@@ -78,7 +78,7 @@ module.exports = {
|
|
78
78
|
|
79
79
|
const block = node.parent;
|
80
80
|
|
81
|
-
if (loneBlocks
|
81
|
+
if (loneBlocks.at(-1) === block) {
|
82
82
|
loneBlocks.pop();
|
83
83
|
}
|
84
84
|
}
|
@@ -101,7 +101,7 @@ module.exports = {
|
|
101
101
|
}
|
102
102
|
},
|
103
103
|
"BlockStatement:exit"(node) {
|
104
|
-
if (loneBlocks.length > 0 && loneBlocks
|
104
|
+
if (loneBlocks.length > 0 && loneBlocks.at(-1) === node) {
|
105
105
|
loneBlocks.pop();
|
106
106
|
report(node);
|
107
107
|
} else if (
|
@@ -62,7 +62,6 @@ function *iterateCharacterSequence(nodes) {
|
|
62
62
|
}
|
63
63
|
}
|
64
64
|
|
65
|
-
|
66
65
|
/**
|
67
66
|
* Checks whether the given character node is a Unicode code point escape or not.
|
68
67
|
* @param {Character} char the character node to check.
|
@@ -73,80 +72,126 @@ function isUnicodeCodePointEscape(char) {
|
|
73
72
|
}
|
74
73
|
|
75
74
|
/**
|
76
|
-
* Each function returns
|
77
|
-
* @type {Record<string, (chars: Character[]) =>
|
75
|
+
* Each function returns matched characters if it detects that kind of problem.
|
76
|
+
* @type {Record<string, (chars: Character[]) => IterableIterator<Character[]>>}
|
78
77
|
*/
|
79
|
-
const
|
80
|
-
surrogatePairWithoutUFlag(chars) {
|
81
|
-
|
82
|
-
if (
|
83
|
-
|
78
|
+
const findCharacterSequences = {
|
79
|
+
*surrogatePairWithoutUFlag(chars) {
|
80
|
+
for (const [index, char] of chars.entries()) {
|
81
|
+
if (index === 0) {
|
82
|
+
continue;
|
83
|
+
}
|
84
|
+
const previous = chars[index - 1];
|
85
|
+
|
86
|
+
if (
|
87
|
+
isSurrogatePair(previous.value, char.value) &&
|
88
|
+
!isUnicodeCodePointEscape(previous) &&
|
89
|
+
!isUnicodeCodePointEscape(char)
|
90
|
+
) {
|
91
|
+
yield [previous, char];
|
84
92
|
}
|
85
|
-
|
86
|
-
|
87
|
-
return (
|
88
|
-
isSurrogatePair(c1.value, c.value) &&
|
89
|
-
!isUnicodeCodePointEscape(c1) &&
|
90
|
-
!isUnicodeCodePointEscape(c)
|
91
|
-
);
|
92
|
-
});
|
93
|
+
}
|
93
94
|
},
|
94
95
|
|
95
|
-
surrogatePair(chars) {
|
96
|
-
|
97
|
-
if (
|
98
|
-
|
96
|
+
*surrogatePair(chars) {
|
97
|
+
for (const [index, char] of chars.entries()) {
|
98
|
+
if (index === 0) {
|
99
|
+
continue;
|
99
100
|
}
|
100
|
-
const
|
101
|
+
const previous = chars[index - 1];
|
101
102
|
|
102
|
-
|
103
|
-
isSurrogatePair(
|
103
|
+
if (
|
104
|
+
isSurrogatePair(previous.value, char.value) &&
|
104
105
|
(
|
105
|
-
isUnicodeCodePointEscape(
|
106
|
-
isUnicodeCodePointEscape(
|
106
|
+
isUnicodeCodePointEscape(previous) ||
|
107
|
+
isUnicodeCodePointEscape(char)
|
107
108
|
)
|
108
|
-
)
|
109
|
-
|
109
|
+
) {
|
110
|
+
yield [previous, char];
|
111
|
+
}
|
112
|
+
}
|
110
113
|
},
|
111
114
|
|
112
|
-
combiningClass(chars) {
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
115
|
+
*combiningClass(chars) {
|
116
|
+
for (const [index, char] of chars.entries()) {
|
117
|
+
if (index === 0) {
|
118
|
+
continue;
|
119
|
+
}
|
120
|
+
const previous = chars[index - 1];
|
121
|
+
|
122
|
+
if (
|
123
|
+
isCombiningCharacter(char.value) &&
|
124
|
+
!isCombiningCharacter(previous.value)
|
125
|
+
) {
|
126
|
+
yield [previous, char];
|
127
|
+
}
|
128
|
+
}
|
118
129
|
},
|
119
130
|
|
120
|
-
emojiModifier(chars) {
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
131
|
+
*emojiModifier(chars) {
|
132
|
+
for (const [index, char] of chars.entries()) {
|
133
|
+
if (index === 0) {
|
134
|
+
continue;
|
135
|
+
}
|
136
|
+
const previous = chars[index - 1];
|
137
|
+
|
138
|
+
if (
|
139
|
+
isEmojiModifier(char.value) &&
|
140
|
+
!isEmojiModifier(previous.value)
|
141
|
+
) {
|
142
|
+
yield [previous, char];
|
143
|
+
}
|
144
|
+
}
|
126
145
|
},
|
127
146
|
|
128
|
-
regionalIndicatorSymbol(chars) {
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
147
|
+
*regionalIndicatorSymbol(chars) {
|
148
|
+
for (const [index, char] of chars.entries()) {
|
149
|
+
if (index === 0) {
|
150
|
+
continue;
|
151
|
+
}
|
152
|
+
const previous = chars[index - 1];
|
153
|
+
|
154
|
+
if (
|
155
|
+
isRegionalIndicatorSymbol(char.value) &&
|
156
|
+
isRegionalIndicatorSymbol(previous.value)
|
157
|
+
) {
|
158
|
+
yield [previous, char];
|
159
|
+
}
|
160
|
+
}
|
134
161
|
},
|
135
162
|
|
136
|
-
zwj(chars) {
|
137
|
-
|
163
|
+
*zwj(chars) {
|
164
|
+
let sequence = null;
|
138
165
|
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
166
|
+
for (const [index, char] of chars.entries()) {
|
167
|
+
if (index === 0 || index === chars.length - 1) {
|
168
|
+
continue;
|
169
|
+
}
|
170
|
+
if (
|
171
|
+
char.value === 0x200d &&
|
172
|
+
chars[index - 1].value !== 0x200d &&
|
173
|
+
chars[index + 1].value !== 0x200d
|
174
|
+
) {
|
175
|
+
if (sequence) {
|
176
|
+
if (sequence.at(-1) === chars[index - 1]) {
|
177
|
+
sequence.push(char, chars[index + 1]); // append to the sequence
|
178
|
+
} else {
|
179
|
+
yield sequence;
|
180
|
+
sequence = chars.slice(index - 1, index + 2);
|
181
|
+
}
|
182
|
+
} else {
|
183
|
+
sequence = chars.slice(index - 1, index + 2);
|
184
|
+
}
|
185
|
+
}
|
186
|
+
}
|
187
|
+
|
188
|
+
if (sequence) {
|
189
|
+
yield sequence;
|
190
|
+
}
|
146
191
|
}
|
147
192
|
};
|
148
193
|
|
149
|
-
const kinds = Object.keys(
|
194
|
+
const kinds = Object.keys(findCharacterSequences);
|
150
195
|
|
151
196
|
//------------------------------------------------------------------------------
|
152
197
|
// Rule Definition
|
@@ -181,6 +226,62 @@ module.exports = {
|
|
181
226
|
const sourceCode = context.sourceCode;
|
182
227
|
const parser = new RegExpParser();
|
183
228
|
|
229
|
+
/**
|
230
|
+
* Generates a granular loc for context.report, if directly calculable.
|
231
|
+
* @param {Character[]} chars Individual characters being reported on.
|
232
|
+
* @param {Node} node Parent string node to report within.
|
233
|
+
* @returns {Object | null} Granular loc for context.report, if directly calculable.
|
234
|
+
* @see https://github.com/eslint/eslint/pull/17515
|
235
|
+
*/
|
236
|
+
function generateReportLocation(chars, node) {
|
237
|
+
|
238
|
+
// Limit to to literals and expression-less templates with raw values === their value.
|
239
|
+
switch (node.type) {
|
240
|
+
case "TemplateLiteral":
|
241
|
+
if (node.expressions.length || sourceCode.getText(node).slice(1, -1) !== node.quasis[0].value.cooked) {
|
242
|
+
return null;
|
243
|
+
}
|
244
|
+
break;
|
245
|
+
|
246
|
+
case "Literal":
|
247
|
+
if (typeof node.value === "string" && node.value !== node.raw.slice(1, -1)) {
|
248
|
+
return null;
|
249
|
+
}
|
250
|
+
break;
|
251
|
+
|
252
|
+
default:
|
253
|
+
return null;
|
254
|
+
}
|
255
|
+
|
256
|
+
return {
|
257
|
+
start: sourceCode.getLocFromIndex(node.range[0] + 1 + chars[0].start),
|
258
|
+
end: sourceCode.getLocFromIndex(node.range[0] + 1 + chars.at(-1).end)
|
259
|
+
};
|
260
|
+
}
|
261
|
+
|
262
|
+
/**
|
263
|
+
* Finds the report loc(s) for a range of matches.
|
264
|
+
* @param {Character[][]} matches Characters that should trigger a report.
|
265
|
+
* @param {Node} node The node to report.
|
266
|
+
* @returns {Object | null} Node loc(s) for context.report.
|
267
|
+
*/
|
268
|
+
function getNodeReportLocations(matches, node) {
|
269
|
+
const locs = [];
|
270
|
+
|
271
|
+
for (const chars of matches) {
|
272
|
+
const loc = generateReportLocation(chars, node);
|
273
|
+
|
274
|
+
// If a report can't match to a range, don't report any others
|
275
|
+
if (!loc) {
|
276
|
+
return [node.loc];
|
277
|
+
}
|
278
|
+
|
279
|
+
locs.push(loc);
|
280
|
+
}
|
281
|
+
|
282
|
+
return locs;
|
283
|
+
}
|
284
|
+
|
184
285
|
/**
|
185
286
|
* Verify a given regular expression.
|
186
287
|
* @param {Node} node The node to report.
|
@@ -208,21 +309,24 @@ module.exports = {
|
|
208
309
|
return;
|
209
310
|
}
|
210
311
|
|
211
|
-
const
|
312
|
+
const foundKindMatches = new Map();
|
212
313
|
|
213
314
|
visitRegExpAST(patternNode, {
|
214
315
|
onCharacterClassEnter(ccNode) {
|
215
316
|
for (const chars of iterateCharacterSequence(ccNode.elements)) {
|
216
317
|
for (const kind of kinds) {
|
217
|
-
if (
|
218
|
-
|
318
|
+
if (foundKindMatches.has(kind)) {
|
319
|
+
foundKindMatches.get(kind).push(...findCharacterSequences[kind](chars));
|
320
|
+
} else {
|
321
|
+
foundKindMatches.set(kind, [...findCharacterSequences[kind](chars)]);
|
219
322
|
}
|
323
|
+
|
220
324
|
}
|
221
325
|
}
|
222
326
|
}
|
223
327
|
});
|
224
328
|
|
225
|
-
for (const kind of
|
329
|
+
for (const [kind, matches] of foundKindMatches) {
|
226
330
|
let suggest;
|
227
331
|
|
228
332
|
if (kind === "surrogatePairWithoutUFlag") {
|
@@ -232,11 +336,16 @@ module.exports = {
|
|
232
336
|
}];
|
233
337
|
}
|
234
338
|
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
339
|
+
const locs = getNodeReportLocations(matches, node);
|
340
|
+
|
341
|
+
for (const loc of locs) {
|
342
|
+
context.report({
|
343
|
+
node,
|
344
|
+
loc,
|
345
|
+
messageId: kind,
|
346
|
+
suggest
|
347
|
+
});
|
348
|
+
}
|
240
349
|
}
|
241
350
|
}
|
242
351
|
|
@@ -267,7 +376,7 @@ module.exports = {
|
|
267
376
|
const flags = getStringIfConstant(flagsNode, scope);
|
268
377
|
|
269
378
|
if (typeof pattern === "string") {
|
270
|
-
verify(
|
379
|
+
verify(patternNode, pattern, flags || "", fixer => {
|
271
380
|
|
272
381
|
if (!isValidWithUnicodeFlag(context.languageOptions.ecmaVersion, pattern)) {
|
273
382
|
return null;
|
@@ -70,7 +70,7 @@ module.exports = {
|
|
70
70
|
const sourceCode = context.sourceCode;
|
71
71
|
|
72
72
|
// Swallow the final newline, as some editors add it automatically and we don't want it to cause an issue
|
73
|
-
const allLines = sourceCode.lines
|
73
|
+
const allLines = sourceCode.lines.at(-1) === "" ? sourceCode.lines.slice(0, -1) : sourceCode.lines;
|
74
74
|
const templateLiteralLines = new Set();
|
75
75
|
|
76
76
|
//--------------------------------------------------------------------------
|