eslint 8.57.0 → 9.2.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/README.md +31 -28
- package/bin/eslint.js +4 -3
- package/conf/ecma-version.js +16 -0
- package/conf/globals.js +1 -0
- package/conf/rule-type-list.json +3 -1
- package/lib/api.js +7 -11
- package/lib/cli-engine/cli-engine.js +14 -3
- package/lib/cli-engine/formatters/formatters-meta.json +1 -29
- package/lib/cli-engine/lint-result-cache.js +2 -2
- package/lib/cli.js +115 -36
- package/lib/config/default-config.js +3 -0
- package/lib/config/flat-config-array.js +110 -24
- package/lib/config/flat-config-helpers.js +41 -20
- package/lib/config/flat-config-schema.js +1 -7
- package/lib/config/rule-validator.js +42 -6
- package/lib/eslint/eslint-helpers.js +116 -58
- package/lib/eslint/eslint.js +892 -377
- package/lib/eslint/index.js +2 -2
- package/lib/eslint/legacy-eslint.js +728 -0
- package/lib/linter/apply-disable-directives.js +59 -31
- package/lib/linter/code-path-analysis/code-path-analyzer.js +0 -1
- 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 +8 -11
- package/lib/linter/index.js +1 -3
- package/lib/linter/interpolate.js +24 -2
- package/lib/linter/linter.js +428 -207
- package/lib/linter/report-translator.js +3 -3
- package/lib/linter/rules.js +6 -15
- package/lib/linter/source-code-fixer.js +1 -1
- package/lib/linter/timing.js +16 -8
- package/lib/options.js +35 -3
- package/lib/rule-tester/index.js +3 -1
- package/lib/rule-tester/rule-tester.js +424 -347
- 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/camelcase.js +3 -5
- package/lib/rules/capitalized-comments.js +10 -7
- package/lib/rules/comma-dangle.js +1 -1
- package/lib/rules/comma-style.js +2 -2
- package/lib/rules/complexity.js +14 -1
- package/lib/rules/constructor-super.js +99 -100
- 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 -2
- 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-case-declarations.js +13 -1
- package/lib/rules/no-constant-binary-expression.js +7 -8
- package/lib/rules/no-constant-condition.js +18 -7
- package/lib/rules/no-constructor-return.js +2 -2
- 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-empty-static-block.js +1 -1
- package/lib/rules/no-extend-native.js +1 -2
- package/lib/rules/no-extra-semi.js +1 -1
- package/lib/rules/no-fallthrough.js +41 -16
- package/lib/rules/no-implicit-coercion.js +66 -24
- package/lib/rules/no-inner-declarations.js +23 -2
- package/lib/rules/no-invalid-regexp.js +1 -1
- package/lib/rules/no-invalid-this.js +1 -1
- package/lib/rules/no-lone-blocks.js +3 -3
- package/lib/rules/no-loss-of-precision.js +1 -1
- package/lib/rules/no-misleading-character-class.js +225 -69
- package/lib/rules/no-mixed-spaces-and-tabs.js +1 -1
- package/lib/rules/no-multiple-empty-lines.js +1 -1
- package/lib/rules/no-new-native-nonconstructor.js +1 -1
- package/lib/rules/no-new-symbol.js +8 -1
- package/lib/rules/no-restricted-globals.js +1 -1
- package/lib/rules/no-restricted-imports.js +186 -40
- package/lib/rules/no-restricted-modules.js +2 -2
- package/lib/rules/no-return-await.js +1 -1
- package/lib/rules/no-sequences.js +1 -0
- package/lib/rules/no-this-before-super.js +45 -13
- 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-private-class-members.js +1 -1
- package/lib/rules/no-unused-vars.js +197 -36
- package/lib/rules/no-useless-assignment.js +566 -0
- package/lib/rules/no-useless-backreference.js +1 -1
- package/lib/rules/no-useless-computed-key.js +2 -2
- package/lib/rules/no-useless-return.js +7 -2
- 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/use-isnan.js +101 -7
- package/lib/rules/utils/ast-utils.js +16 -7
- package/lib/rules/utils/char-source.js +240 -0
- package/lib/rules/utils/lazy-loading-rule-map.js +1 -1
- package/lib/rules/utils/unicode/index.js +9 -4
- package/lib/rules/yield-star-spacing.js +1 -1
- package/lib/shared/runtime-info.js +1 -0
- package/lib/shared/serialization.js +55 -0
- package/lib/shared/stats.js +30 -0
- package/lib/shared/string-utils.js +9 -11
- package/lib/shared/types.js +35 -1
- package/lib/source-code/index.js +3 -1
- package/lib/source-code/source-code.js +299 -85
- package/lib/source-code/token-store/backward-token-cursor.js +3 -3
- package/lib/source-code/token-store/cursors.js +4 -2
- package/lib/source-code/token-store/forward-token-comment-cursor.js +3 -3
- package/lib/source-code/token-store/forward-token-cursor.js +3 -3
- package/lib/source-code/token-store/index.js +2 -2
- package/lib/unsupported-api.js +3 -5
- package/messages/no-config-found.js +1 -1
- package/messages/plugin-conflict.js +1 -1
- package/messages/plugin-invalid.js +1 -1
- package/messages/plugin-missing.js +1 -1
- package/package.json +32 -29
- package/conf/config-schema.js +0 -93
- package/lib/cli-engine/formatters/checkstyle.js +0 -60
- package/lib/cli-engine/formatters/compact.js +0 -60
- package/lib/cli-engine/formatters/jslint-xml.js +0 -41
- package/lib/cli-engine/formatters/junit.js +0 -82
- package/lib/cli-engine/formatters/tap.js +0 -95
- package/lib/cli-engine/formatters/unix.js +0 -58
- package/lib/cli-engine/formatters/visualstudio.js +0 -63
- package/lib/cli-engine/xml-escape.js +0 -34
- package/lib/eslint/flat-eslint.js +0 -1155
- package/lib/rule-tester/flat-rule-tester.js +0 -1131
- package/lib/rules/require-jsdoc.js +0 -122
- package/lib/rules/utils/patterns/letters.js +0 -36
- package/lib/rules/valid-jsdoc.js +0 -516
- package/lib/shared/config-validator.js +0 -347
- package/lib/shared/deprecation-warnings.js +0 -58
- package/lib/shared/relative-module-resolver.js +0 -50
@@ -12,7 +12,7 @@ const astUtils = require("./utils/ast-utils");
|
|
12
12
|
//------------------------------------------------------------------------------
|
13
13
|
|
14
14
|
const INDEX_OF_PATTERN = /^(?:i|lastI)ndexOf$/u;
|
15
|
-
const ALLOWABLE_OPERATORS = ["~", "!!", "+", "*"];
|
15
|
+
const ALLOWABLE_OPERATORS = ["~", "!!", "+", "- -", "-", "*"];
|
16
16
|
|
17
17
|
/**
|
18
18
|
* Parses and normalizes an option object.
|
@@ -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,15 @@ 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);
|
328
|
+
}
|
329
|
+
|
330
|
+
// -(-foo)
|
331
|
+
operatorAllowed = options.allow.includes("- -");
|
332
|
+
if (!operatorAllowed && options.number && node.operator === "-" && node.argument.type === "UnaryExpression" && node.argument.operator === "-" && !isNumeric(node.argument.argument)) {
|
333
|
+
const recommendation = `Number(${sourceCode.getText(node.argument.argument)})`;
|
334
|
+
|
335
|
+
report(node, recommendation, true, false);
|
302
336
|
}
|
303
337
|
},
|
304
338
|
|
@@ -314,7 +348,15 @@ module.exports = {
|
|
314
348
|
if (nonNumericOperand) {
|
315
349
|
const recommendation = `Number(${sourceCode.getText(nonNumericOperand)})`;
|
316
350
|
|
317
|
-
report(node, recommendation, true);
|
351
|
+
report(node, recommendation, true, false);
|
352
|
+
}
|
353
|
+
|
354
|
+
// foo - 0
|
355
|
+
operatorAllowed = options.allow.includes("-");
|
356
|
+
if (!operatorAllowed && options.number && node.operator === "-" && node.right.type === "Literal" && node.right.value === 0 && !isNumeric(node.left)) {
|
357
|
+
const recommendation = `Number(${sourceCode.getText(node.left)})`;
|
358
|
+
|
359
|
+
report(node, recommendation, true, false);
|
318
360
|
}
|
319
361
|
|
320
362
|
// "" + foo
|
@@ -322,7 +364,7 @@ module.exports = {
|
|
322
364
|
if (!operatorAllowed && options.string && isConcatWithEmptyString(node)) {
|
323
365
|
const recommendation = `String(${sourceCode.getText(getNonEmptyOperand(node))})`;
|
324
366
|
|
325
|
-
report(node, recommendation, true);
|
367
|
+
report(node, recommendation, true, false);
|
326
368
|
}
|
327
369
|
},
|
328
370
|
|
@@ -335,7 +377,7 @@ module.exports = {
|
|
335
377
|
const code = sourceCode.getText(getNonEmptyOperand(node));
|
336
378
|
const recommendation = `${code} = String(${code})`;
|
337
379
|
|
338
|
-
report(node, recommendation, true);
|
380
|
+
report(node, recommendation, true, false);
|
339
381
|
}
|
340
382
|
},
|
341
383
|
|
@@ -373,7 +415,7 @@ module.exports = {
|
|
373
415
|
const code = sourceCode.getText(node.expressions[0]);
|
374
416
|
const recommendation = `String(${code})`;
|
375
417
|
|
376
|
-
report(node, recommendation, true);
|
418
|
+
report(node, recommendation, true, false);
|
377
419
|
}
|
378
420
|
};
|
379
421
|
}
|
@@ -49,13 +49,22 @@ module.exports = {
|
|
49
49
|
|
50
50
|
docs: {
|
51
51
|
description: "Disallow variable or `function` declarations in nested blocks",
|
52
|
-
recommended:
|
52
|
+
recommended: false,
|
53
53
|
url: "https://eslint.org/docs/latest/rules/no-inner-declarations"
|
54
54
|
},
|
55
55
|
|
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 (
|
@@ -117,7 +117,7 @@ module.exports = {
|
|
117
117
|
};
|
118
118
|
|
119
119
|
ruleDef.VariableDeclaration = function(node) {
|
120
|
-
if (node.kind
|
120
|
+
if (node.kind !== "var") {
|
121
121
|
markLoneBlock(node);
|
122
122
|
}
|
123
123
|
};
|
@@ -3,11 +3,18 @@
|
|
3
3
|
*/
|
4
4
|
"use strict";
|
5
5
|
|
6
|
-
const {
|
6
|
+
const {
|
7
|
+
CALL,
|
8
|
+
CONSTRUCT,
|
9
|
+
ReferenceTracker,
|
10
|
+
getStaticValue,
|
11
|
+
getStringIfConstant
|
12
|
+
} = require("@eslint-community/eslint-utils");
|
7
13
|
const { RegExpParser, visitRegExpAST } = require("@eslint-community/regexpp");
|
8
14
|
const { isCombiningCharacter, isEmojiModifier, isRegionalIndicatorSymbol, isSurrogatePair } = require("./utils/unicode");
|
9
15
|
const astUtils = require("./utils/ast-utils.js");
|
10
16
|
const { isValidWithUnicodeFlag } = require("./utils/regular-expressions");
|
17
|
+
const { parseStringLiteral, parseTemplateToken } = require("./utils/char-source");
|
11
18
|
|
12
19
|
//------------------------------------------------------------------------------
|
13
20
|
// Helpers
|
@@ -62,7 +69,6 @@ function *iterateCharacterSequence(nodes) {
|
|
62
69
|
}
|
63
70
|
}
|
64
71
|
|
65
|
-
|
66
72
|
/**
|
67
73
|
* Checks whether the given character node is a Unicode code point escape or not.
|
68
74
|
* @param {Character} char the character node to check.
|
@@ -73,80 +79,153 @@ function isUnicodeCodePointEscape(char) {
|
|
73
79
|
}
|
74
80
|
|
75
81
|
/**
|
76
|
-
* Each function returns
|
77
|
-
* @type {Record<string, (chars: Character[]) =>
|
82
|
+
* Each function returns matched characters if it detects that kind of problem.
|
83
|
+
* @type {Record<string, (chars: Character[]) => IterableIterator<Character[]>>}
|
78
84
|
*/
|
79
|
-
const
|
80
|
-
surrogatePairWithoutUFlag(chars) {
|
81
|
-
|
82
|
-
if (
|
83
|
-
|
85
|
+
const findCharacterSequences = {
|
86
|
+
*surrogatePairWithoutUFlag(chars) {
|
87
|
+
for (const [index, char] of chars.entries()) {
|
88
|
+
if (index === 0) {
|
89
|
+
continue;
|
90
|
+
}
|
91
|
+
const previous = chars[index - 1];
|
92
|
+
|
93
|
+
if (
|
94
|
+
isSurrogatePair(previous.value, char.value) &&
|
95
|
+
!isUnicodeCodePointEscape(previous) &&
|
96
|
+
!isUnicodeCodePointEscape(char)
|
97
|
+
) {
|
98
|
+
yield [previous, char];
|
84
99
|
}
|
85
|
-
|
86
|
-
|
87
|
-
return (
|
88
|
-
isSurrogatePair(c1.value, c.value) &&
|
89
|
-
!isUnicodeCodePointEscape(c1) &&
|
90
|
-
!isUnicodeCodePointEscape(c)
|
91
|
-
);
|
92
|
-
});
|
100
|
+
}
|
93
101
|
},
|
94
102
|
|
95
|
-
surrogatePair(chars) {
|
96
|
-
|
97
|
-
if (
|
98
|
-
|
103
|
+
*surrogatePair(chars) {
|
104
|
+
for (const [index, char] of chars.entries()) {
|
105
|
+
if (index === 0) {
|
106
|
+
continue;
|
99
107
|
}
|
100
|
-
const
|
108
|
+
const previous = chars[index - 1];
|
101
109
|
|
102
|
-
|
103
|
-
isSurrogatePair(
|
110
|
+
if (
|
111
|
+
isSurrogatePair(previous.value, char.value) &&
|
104
112
|
(
|
105
|
-
isUnicodeCodePointEscape(
|
106
|
-
isUnicodeCodePointEscape(
|
113
|
+
isUnicodeCodePointEscape(previous) ||
|
114
|
+
isUnicodeCodePointEscape(char)
|
107
115
|
)
|
108
|
-
)
|
109
|
-
|
116
|
+
) {
|
117
|
+
yield [previous, char];
|
118
|
+
}
|
119
|
+
}
|
110
120
|
},
|
111
121
|
|
112
|
-
combiningClass(chars) {
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
122
|
+
*combiningClass(chars) {
|
123
|
+
for (const [index, char] of chars.entries()) {
|
124
|
+
if (index === 0) {
|
125
|
+
continue;
|
126
|
+
}
|
127
|
+
const previous = chars[index - 1];
|
128
|
+
|
129
|
+
if (
|
130
|
+
isCombiningCharacter(char.value) &&
|
131
|
+
!isCombiningCharacter(previous.value)
|
132
|
+
) {
|
133
|
+
yield [previous, char];
|
134
|
+
}
|
135
|
+
}
|
118
136
|
},
|
119
137
|
|
120
|
-
emojiModifier(chars) {
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
138
|
+
*emojiModifier(chars) {
|
139
|
+
for (const [index, char] of chars.entries()) {
|
140
|
+
if (index === 0) {
|
141
|
+
continue;
|
142
|
+
}
|
143
|
+
const previous = chars[index - 1];
|
144
|
+
|
145
|
+
if (
|
146
|
+
isEmojiModifier(char.value) &&
|
147
|
+
!isEmojiModifier(previous.value)
|
148
|
+
) {
|
149
|
+
yield [previous, char];
|
150
|
+
}
|
151
|
+
}
|
126
152
|
},
|
127
153
|
|
128
|
-
regionalIndicatorSymbol(chars) {
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
154
|
+
*regionalIndicatorSymbol(chars) {
|
155
|
+
for (const [index, char] of chars.entries()) {
|
156
|
+
if (index === 0) {
|
157
|
+
continue;
|
158
|
+
}
|
159
|
+
const previous = chars[index - 1];
|
160
|
+
|
161
|
+
if (
|
162
|
+
isRegionalIndicatorSymbol(char.value) &&
|
163
|
+
isRegionalIndicatorSymbol(previous.value)
|
164
|
+
) {
|
165
|
+
yield [previous, char];
|
166
|
+
}
|
167
|
+
}
|
134
168
|
},
|
135
169
|
|
136
|
-
zwj(chars) {
|
137
|
-
|
170
|
+
*zwj(chars) {
|
171
|
+
let sequence = null;
|
172
|
+
|
173
|
+
for (const [index, char] of chars.entries()) {
|
174
|
+
if (index === 0 || index === chars.length - 1) {
|
175
|
+
continue;
|
176
|
+
}
|
177
|
+
if (
|
178
|
+
char.value === 0x200d &&
|
179
|
+
chars[index - 1].value !== 0x200d &&
|
180
|
+
chars[index + 1].value !== 0x200d
|
181
|
+
) {
|
182
|
+
if (sequence) {
|
183
|
+
if (sequence.at(-1) === chars[index - 1]) {
|
184
|
+
sequence.push(char, chars[index + 1]); // append to the sequence
|
185
|
+
} else {
|
186
|
+
yield sequence;
|
187
|
+
sequence = chars.slice(index - 1, index + 2);
|
188
|
+
}
|
189
|
+
} else {
|
190
|
+
sequence = chars.slice(index - 1, index + 2);
|
191
|
+
}
|
192
|
+
}
|
193
|
+
}
|
138
194
|
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
c.value === 0x200d &&
|
143
|
-
chars[i - 1].value !== 0x200d &&
|
144
|
-
chars[i + 1].value !== 0x200d
|
145
|
-
));
|
195
|
+
if (sequence) {
|
196
|
+
yield sequence;
|
197
|
+
}
|
146
198
|
}
|
147
199
|
};
|
148
200
|
|
149
|
-
const kinds = Object.keys(
|
201
|
+
const kinds = Object.keys(findCharacterSequences);
|
202
|
+
|
203
|
+
/**
|
204
|
+
* Gets the value of the given node if it's a static value other than a regular expression object,
|
205
|
+
* or the node's `regex` property.
|
206
|
+
* The purpose of this method is to provide a replacement for `getStaticValue` in environments where certain regular expressions cannot be evaluated.
|
207
|
+
* A known example is Node.js 18 which does not support the `v` flag.
|
208
|
+
* Calling `getStaticValue` on a regular expression node with the `v` flag on Node.js 18 always returns `null`.
|
209
|
+
* A limitation of this method is that it can only detect a regular expression if the specified node is itself a regular expression literal node.
|
210
|
+
* @param {ASTNode | undefined} node The node to be inspected.
|
211
|
+
* @param {Scope} initialScope Scope to start finding variables. This function tries to resolve identifier references which are in the given scope.
|
212
|
+
* @returns {{ value: any } | { regex: { pattern: string, flags: string } } | null} The static value of the node, or `null`.
|
213
|
+
*/
|
214
|
+
function getStaticValueOrRegex(node, initialScope) {
|
215
|
+
if (!node) {
|
216
|
+
return null;
|
217
|
+
}
|
218
|
+
if (node.type === "Literal" && node.regex) {
|
219
|
+
return { regex: node.regex };
|
220
|
+
}
|
221
|
+
|
222
|
+
const staticValue = getStaticValue(node, initialScope);
|
223
|
+
|
224
|
+
if (staticValue?.value instanceof RegExp) {
|
225
|
+
return null;
|
226
|
+
}
|
227
|
+
return staticValue;
|
228
|
+
}
|
150
229
|
|
151
230
|
//------------------------------------------------------------------------------
|
152
231
|
// Rule Definition
|
@@ -180,6 +259,7 @@ module.exports = {
|
|
180
259
|
create(context) {
|
181
260
|
const sourceCode = context.sourceCode;
|
182
261
|
const parser = new RegExpParser();
|
262
|
+
const checkedPatternNodes = new Set();
|
183
263
|
|
184
264
|
/**
|
185
265
|
* Verify a given regular expression.
|
@@ -208,21 +288,70 @@ module.exports = {
|
|
208
288
|
return;
|
209
289
|
}
|
210
290
|
|
211
|
-
const
|
291
|
+
const foundKindMatches = new Map();
|
212
292
|
|
213
293
|
visitRegExpAST(patternNode, {
|
214
294
|
onCharacterClassEnter(ccNode) {
|
215
295
|
for (const chars of iterateCharacterSequence(ccNode.elements)) {
|
216
296
|
for (const kind of kinds) {
|
217
|
-
if (
|
218
|
-
|
297
|
+
if (foundKindMatches.has(kind)) {
|
298
|
+
foundKindMatches.get(kind).push(...findCharacterSequences[kind](chars));
|
299
|
+
} else {
|
300
|
+
foundKindMatches.set(kind, [...findCharacterSequences[kind](chars)]);
|
219
301
|
}
|
220
302
|
}
|
221
303
|
}
|
222
304
|
}
|
223
305
|
});
|
224
306
|
|
225
|
-
|
307
|
+
let codeUnits = null;
|
308
|
+
|
309
|
+
/**
|
310
|
+
* Finds the report loc(s) for a range of matches.
|
311
|
+
* Only literals and expression-less templates generate granular errors.
|
312
|
+
* @param {Character[][]} matches Lists of individual characters being reported on.
|
313
|
+
* @returns {Location[]} locs for context.report.
|
314
|
+
* @see https://github.com/eslint/eslint/pull/17515
|
315
|
+
*/
|
316
|
+
function getNodeReportLocations(matches) {
|
317
|
+
if (!astUtils.isStaticTemplateLiteral(node) && node.type !== "Literal") {
|
318
|
+
return matches.length ? [node.loc] : [];
|
319
|
+
}
|
320
|
+
return matches.map(chars => {
|
321
|
+
const firstIndex = chars[0].start;
|
322
|
+
const lastIndex = chars.at(-1).end - 1;
|
323
|
+
let start;
|
324
|
+
let end;
|
325
|
+
|
326
|
+
if (node.type === "TemplateLiteral") {
|
327
|
+
const source = sourceCode.getText(node);
|
328
|
+
const offset = node.range[0];
|
329
|
+
|
330
|
+
codeUnits ??= parseTemplateToken(source);
|
331
|
+
start = offset + codeUnits[firstIndex].start;
|
332
|
+
end = offset + codeUnits[lastIndex].end;
|
333
|
+
} else if (typeof node.value === "string") { // String Literal
|
334
|
+
const source = node.raw;
|
335
|
+
const offset = node.range[0];
|
336
|
+
|
337
|
+
codeUnits ??= parseStringLiteral(source);
|
338
|
+
start = offset + codeUnits[firstIndex].start;
|
339
|
+
end = offset + codeUnits[lastIndex].end;
|
340
|
+
} else { // RegExp Literal
|
341
|
+
const offset = node.range[0] + 1; // Add 1 to skip the leading slash.
|
342
|
+
|
343
|
+
start = offset + firstIndex;
|
344
|
+
end = offset + lastIndex + 1;
|
345
|
+
}
|
346
|
+
|
347
|
+
return {
|
348
|
+
start: sourceCode.getLocFromIndex(start),
|
349
|
+
end: sourceCode.getLocFromIndex(end)
|
350
|
+
};
|
351
|
+
});
|
352
|
+
}
|
353
|
+
|
354
|
+
for (const [kind, matches] of foundKindMatches) {
|
226
355
|
let suggest;
|
227
356
|
|
228
357
|
if (kind === "surrogatePairWithoutUFlag") {
|
@@ -232,16 +361,24 @@ module.exports = {
|
|
232
361
|
}];
|
233
362
|
}
|
234
363
|
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
364
|
+
const locs = getNodeReportLocations(matches);
|
365
|
+
|
366
|
+
for (const loc of locs) {
|
367
|
+
context.report({
|
368
|
+
node,
|
369
|
+
loc,
|
370
|
+
messageId: kind,
|
371
|
+
suggest
|
372
|
+
});
|
373
|
+
}
|
240
374
|
}
|
241
375
|
}
|
242
376
|
|
243
377
|
return {
|
244
378
|
"Literal[regex]"(node) {
|
379
|
+
if (checkedPatternNodes.has(node)) {
|
380
|
+
return;
|
381
|
+
}
|
245
382
|
verify(node, node.regex.pattern, node.regex.flags, fixer => {
|
246
383
|
if (!isValidWithUnicodeFlag(context.languageOptions.ecmaVersion, node.regex.pattern)) {
|
247
384
|
return null;
|
@@ -262,12 +399,31 @@ module.exports = {
|
|
262
399
|
for (const { node: refNode } of tracker.iterateGlobalReferences({
|
263
400
|
RegExp: { [CALL]: true, [CONSTRUCT]: true }
|
264
401
|
})) {
|
402
|
+
let pattern, flags;
|
265
403
|
const [patternNode, flagsNode] = refNode.arguments;
|
266
|
-
const
|
267
|
-
|
404
|
+
const evaluatedPattern = getStaticValueOrRegex(patternNode, scope);
|
405
|
+
|
406
|
+
if (!evaluatedPattern) {
|
407
|
+
continue;
|
408
|
+
}
|
409
|
+
if (flagsNode) {
|
410
|
+
if (evaluatedPattern.regex) {
|
411
|
+
pattern = evaluatedPattern.regex.pattern;
|
412
|
+
checkedPatternNodes.add(patternNode);
|
413
|
+
} else {
|
414
|
+
pattern = String(evaluatedPattern.value);
|
415
|
+
}
|
416
|
+
flags = getStringIfConstant(flagsNode, scope);
|
417
|
+
} else {
|
418
|
+
if (evaluatedPattern.regex) {
|
419
|
+
continue;
|
420
|
+
}
|
421
|
+
pattern = String(evaluatedPattern.value);
|
422
|
+
flags = "";
|
423
|
+
}
|
268
424
|
|
269
|
-
if (typeof
|
270
|
-
verify(
|
425
|
+
if (typeof flags === "string") {
|
426
|
+
verify(patternNode, pattern, flags, fixer => {
|
271
427
|
|
272
428
|
if (!isValidWithUnicodeFlag(context.languageOptions.ecmaVersion, pattern)) {
|
273
429
|
return null;
|