eslint 9.9.0 → 9.10.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 +43 -38
- package/lib/config/config.js +278 -0
- package/lib/config/flat-config-array.js +3 -204
- package/lib/eslint/eslint.js +24 -11
- package/lib/languages/js/source-code/source-code.js +29 -94
- package/lib/linter/apply-disable-directives.js +17 -28
- package/lib/linter/file-context.js +134 -0
- package/lib/linter/linter.js +49 -87
- package/lib/rules/id-length.js +1 -0
- package/lib/rules/no-invalid-regexp.js +34 -18
- package/lib/rules/require-unicode-regexp.js +95 -14
- package/lib/rules/utils/regular-expressions.js +11 -3
- package/lib/services/parser-service.js +65 -0
- package/lib/types/index.d.ts +1635 -0
- package/lib/types/rules/best-practices.d.ts +1075 -0
- package/lib/types/rules/deprecated.d.ts +294 -0
- package/lib/types/rules/ecmascript-6.d.ts +561 -0
- package/lib/types/rules/index.d.ts +50 -0
- package/lib/types/rules/node-commonjs.d.ts +160 -0
- package/lib/types/rules/possible-errors.d.ts +598 -0
- package/lib/types/rules/strict-mode.d.ts +38 -0
- package/lib/types/rules/stylistic-issues.d.ts +1932 -0
- package/lib/types/rules/variables.d.ts +221 -0
- package/lib/types/use-at-your-own-risk.d.ts +85 -0
- package/package.json +34 -23
- package/lib/linter/config-comment-parser.js +0 -169
package/lib/linter/linter.js
CHANGED
@@ -17,9 +17,6 @@ const
|
|
17
17
|
espree = require("espree"),
|
18
18
|
merge = require("lodash.merge"),
|
19
19
|
pkg = require("../../package.json"),
|
20
|
-
{
|
21
|
-
directivesPattern
|
22
|
-
} = require("../shared/directives"),
|
23
20
|
{
|
24
21
|
Legacy: {
|
25
22
|
ConfigOps,
|
@@ -30,7 +27,7 @@ const
|
|
30
27
|
Traverser = require("../shared/traverser"),
|
31
28
|
{ SourceCode } = require("../languages/js/source-code"),
|
32
29
|
applyDisableDirectives = require("./apply-disable-directives"),
|
33
|
-
ConfigCommentParser = require("
|
30
|
+
{ ConfigCommentParser } = require("@eslint/plugin-kit"),
|
34
31
|
NodeEventGenerator = require("./node-event-generator"),
|
35
32
|
createReportTranslator = require("./report-translator"),
|
36
33
|
Rules = require("./rules"),
|
@@ -55,6 +52,8 @@ const DEFAULT_ERROR_LOC = { start: { line: 1, column: 0 }, end: { line: 1, colum
|
|
55
52
|
const parserSymbol = Symbol.for("eslint.RuleTester.parser");
|
56
53
|
const { LATEST_ECMA_VERSION } = require("../../conf/ecma-version");
|
57
54
|
const { VFile } = require("./vfile");
|
55
|
+
const { ParserService } = require("../services/parser-service");
|
56
|
+
const { FileContext } = require("./file-context");
|
58
57
|
const STEP_KIND_VISIT = 1;
|
59
58
|
const STEP_KIND_CALL = 2;
|
60
59
|
|
@@ -333,7 +332,7 @@ function createDisableDirectives({ type, value, justification, node }, ruleMappe
|
|
333
332
|
directives: [], // valid disable directives
|
334
333
|
directiveProblems: [] // problems in directives
|
335
334
|
};
|
336
|
-
const parentDirective = { node, ruleIds };
|
335
|
+
const parentDirective = { node, value, ruleIds };
|
337
336
|
|
338
337
|
for (const ruleId of directiveRules) {
|
339
338
|
|
@@ -401,15 +400,20 @@ function getDirectiveComments(sourceCode, ruleMapper, warnInlineConfig, config)
|
|
401
400
|
});
|
402
401
|
|
403
402
|
sourceCode.getInlineConfigNodes().filter(token => token.type !== "Shebang").forEach(comment => {
|
404
|
-
const { directivePart, justificationPart } = commentParser.extractDirectiveComment(comment.value);
|
405
403
|
|
406
|
-
const
|
404
|
+
const directive = commentParser.parseDirective(comment.value);
|
407
405
|
|
408
|
-
if (!
|
406
|
+
if (!directive) {
|
409
407
|
return;
|
410
408
|
}
|
411
|
-
|
412
|
-
const
|
409
|
+
|
410
|
+
const {
|
411
|
+
label,
|
412
|
+
value,
|
413
|
+
justification: justificationPart
|
414
|
+
} = directive;
|
415
|
+
|
416
|
+
const lineCommentSupported = /^eslint-disable-(next-)?line$/u.test(label);
|
413
417
|
|
414
418
|
if (comment.type === "Line" && !lineCommentSupported) {
|
415
419
|
return;
|
@@ -418,7 +422,7 @@ function getDirectiveComments(sourceCode, ruleMapper, warnInlineConfig, config)
|
|
418
422
|
const loc = sourceCode.getLoc(comment);
|
419
423
|
|
420
424
|
if (warnInlineConfig) {
|
421
|
-
const kind = comment.type === "Block" ? `/*${
|
425
|
+
const kind = comment.type === "Block" ? `/*${label}*/` : `//${label}`;
|
422
426
|
|
423
427
|
problems.push(createLintingProblem({
|
424
428
|
ruleId: null,
|
@@ -429,8 +433,8 @@ function getDirectiveComments(sourceCode, ruleMapper, warnInlineConfig, config)
|
|
429
433
|
return;
|
430
434
|
}
|
431
435
|
|
432
|
-
if (
|
433
|
-
const message = `${
|
436
|
+
if (label === "eslint-disable-line" && loc.start.line !== loc.end.line) {
|
437
|
+
const message = `${label} comment should not span multiple lines.`;
|
434
438
|
|
435
439
|
problems.push(createLintingProblem({
|
436
440
|
ruleId: null,
|
@@ -440,17 +444,15 @@ function getDirectiveComments(sourceCode, ruleMapper, warnInlineConfig, config)
|
|
440
444
|
return;
|
441
445
|
}
|
442
446
|
|
443
|
-
|
444
|
-
|
445
|
-
switch (directiveText) {
|
447
|
+
switch (label) {
|
446
448
|
case "eslint-disable":
|
447
449
|
case "eslint-enable":
|
448
450
|
case "eslint-disable-next-line":
|
449
451
|
case "eslint-disable-line": {
|
450
|
-
const directiveType =
|
452
|
+
const directiveType = label.slice("eslint-".length);
|
451
453
|
const { directives, directiveProblems } = createDisableDirectives({
|
452
454
|
type: directiveType,
|
453
|
-
value
|
455
|
+
value,
|
454
456
|
justification: justificationPart,
|
455
457
|
node: comment
|
456
458
|
}, ruleMapper, jslang, sourceCode);
|
@@ -461,16 +463,16 @@ function getDirectiveComments(sourceCode, ruleMapper, warnInlineConfig, config)
|
|
461
463
|
}
|
462
464
|
|
463
465
|
case "exported":
|
464
|
-
Object.assign(exportedVariables, commentParser.parseListConfig(
|
466
|
+
Object.assign(exportedVariables, commentParser.parseListConfig(value));
|
465
467
|
break;
|
466
468
|
|
467
469
|
case "globals":
|
468
470
|
case "global":
|
469
|
-
for (const [id,
|
471
|
+
for (const [id, idSetting] of Object.entries(commentParser.parseStringConfig(value))) {
|
470
472
|
let normalizedValue;
|
471
473
|
|
472
474
|
try {
|
473
|
-
normalizedValue = ConfigOps.normalizeConfigGlobal(
|
475
|
+
normalizedValue = ConfigOps.normalizeConfigGlobal(idSetting);
|
474
476
|
} catch (err) {
|
475
477
|
problems.push(createLintingProblem({
|
476
478
|
ruleId: null,
|
@@ -493,9 +495,9 @@ function getDirectiveComments(sourceCode, ruleMapper, warnInlineConfig, config)
|
|
493
495
|
break;
|
494
496
|
|
495
497
|
case "eslint": {
|
496
|
-
const parseResult = commentParser.
|
498
|
+
const parseResult = commentParser.parseJSONLikeConfig(value);
|
497
499
|
|
498
|
-
if (parseResult.
|
500
|
+
if (parseResult.ok) {
|
499
501
|
Object.keys(parseResult.config).forEach(name => {
|
500
502
|
const rule = ruleMapper(name);
|
501
503
|
const ruleValue = parseResult.config[name];
|
@@ -712,7 +714,7 @@ function findEslintEnv(text) {
|
|
712
714
|
if (match[0].endsWith("*/")) {
|
713
715
|
retv = Object.assign(
|
714
716
|
retv || {},
|
715
|
-
commentParser.parseListConfig(commentParser.
|
717
|
+
commentParser.parseListConfig(commentParser.parseDirective(match[0].slice(2, -2)).value)
|
716
718
|
);
|
717
719
|
}
|
718
720
|
}
|
@@ -922,43 +924,6 @@ function analyzeScope(ast, languageOptions, visitorKeys) {
|
|
922
924
|
});
|
923
925
|
}
|
924
926
|
|
925
|
-
/**
|
926
|
-
* Parses file into an AST. Moved out here because the try-catch prevents
|
927
|
-
* optimization of functions, so it's best to keep the try-catch as isolated
|
928
|
-
* as possible
|
929
|
-
* @param {VFile} file The file to parse.
|
930
|
-
* @param {Language} language The language to use.
|
931
|
-
* @param {LanguageOptions} languageOptions Options to pass to the parser
|
932
|
-
* @returns {{success: false, error: LintMessage}|{success: true, sourceCode: SourceCode}}
|
933
|
-
* An object containing the AST and parser services if parsing was successful, or the error if parsing failed
|
934
|
-
* @private
|
935
|
-
*/
|
936
|
-
function parse(file, language, languageOptions) {
|
937
|
-
|
938
|
-
const result = language.parse(file, { languageOptions });
|
939
|
-
|
940
|
-
if (result.ok) {
|
941
|
-
return {
|
942
|
-
success: true,
|
943
|
-
sourceCode: language.createSourceCode(file, result, { languageOptions })
|
944
|
-
};
|
945
|
-
}
|
946
|
-
|
947
|
-
// if we made it to here there was an error
|
948
|
-
return {
|
949
|
-
success: false,
|
950
|
-
errors: result.errors.map(error => ({
|
951
|
-
ruleId: null,
|
952
|
-
nodeType: null,
|
953
|
-
fatal: true,
|
954
|
-
severity: 2,
|
955
|
-
message: `Parsing error: ${error.message}`,
|
956
|
-
line: error.line,
|
957
|
-
column: error.column
|
958
|
-
}))
|
959
|
-
};
|
960
|
-
}
|
961
|
-
|
962
927
|
/**
|
963
928
|
* Runs a rule, and gets its listeners
|
964
929
|
* @param {Rule} rule A rule object
|
@@ -1015,24 +980,18 @@ function runRules(
|
|
1015
980
|
* All rule contexts will inherit from this object. This avoids the performance penalty of copying all the
|
1016
981
|
* properties once for each rule.
|
1017
982
|
*/
|
1018
|
-
const sharedTraversalContext =
|
1019
|
-
|
1020
|
-
|
1021
|
-
|
1022
|
-
|
1023
|
-
|
1024
|
-
|
1025
|
-
|
1026
|
-
|
1027
|
-
|
1028
|
-
|
1029
|
-
|
1030
|
-
},
|
1031
|
-
parserPath: parserName,
|
1032
|
-
languageOptions,
|
1033
|
-
settings
|
1034
|
-
}
|
1035
|
-
);
|
983
|
+
const sharedTraversalContext = new FileContext({
|
984
|
+
cwd,
|
985
|
+
filename,
|
986
|
+
physicalFilename: physicalFilename || filename,
|
987
|
+
sourceCode,
|
988
|
+
parserOptions: {
|
989
|
+
...languageOptions.parserOptions
|
990
|
+
},
|
991
|
+
parserPath: parserName,
|
992
|
+
languageOptions,
|
993
|
+
settings
|
994
|
+
});
|
1036
995
|
|
1037
996
|
const lintingProblems = [];
|
1038
997
|
|
@@ -1407,10 +1366,13 @@ class Linter {
|
|
1407
1366
|
t = startTime();
|
1408
1367
|
}
|
1409
1368
|
|
1410
|
-
const
|
1369
|
+
const parserService = new ParserService();
|
1370
|
+
const parseResult = parserService.parseSync(
|
1411
1371
|
file,
|
1412
|
-
|
1413
|
-
|
1372
|
+
{
|
1373
|
+
language: jslang,
|
1374
|
+
languageOptions
|
1375
|
+
}
|
1414
1376
|
);
|
1415
1377
|
|
1416
1378
|
if (options.stats) {
|
@@ -1420,7 +1382,7 @@ class Linter {
|
|
1420
1382
|
storeTime(time, timeOpts, slots);
|
1421
1383
|
}
|
1422
1384
|
|
1423
|
-
if (!parseResult.
|
1385
|
+
if (!parseResult.ok) {
|
1424
1386
|
return parseResult.errors;
|
1425
1387
|
}
|
1426
1388
|
|
@@ -1712,10 +1674,10 @@ class Linter {
|
|
1712
1674
|
t = startTime();
|
1713
1675
|
}
|
1714
1676
|
|
1715
|
-
const
|
1677
|
+
const parserService = new ParserService();
|
1678
|
+
const parseResult = parserService.parseSync(
|
1716
1679
|
file,
|
1717
|
-
config
|
1718
|
-
languageOptions
|
1680
|
+
config
|
1719
1681
|
);
|
1720
1682
|
|
1721
1683
|
if (options.stats) {
|
@@ -1724,7 +1686,7 @@ class Linter {
|
|
1724
1686
|
storeTime(time, { type: "parse" }, slots);
|
1725
1687
|
}
|
1726
1688
|
|
1727
|
-
if (!parseResult.
|
1689
|
+
if (!parseResult.ok) {
|
1728
1690
|
return parseResult.errors;
|
1729
1691
|
}
|
1730
1692
|
|
package/lib/rules/id-length.js
CHANGED
@@ -117,6 +117,7 @@ module.exports = {
|
|
117
117
|
return properties && !parent.computed && parent.key.name === node.name;
|
118
118
|
},
|
119
119
|
ImportDefaultSpecifier: true,
|
120
|
+
ImportNamespaceSpecifier: true,
|
120
121
|
RestElement: true,
|
121
122
|
FunctionExpression: true,
|
122
123
|
ArrowFunctionExpression: true,
|
@@ -10,7 +10,7 @@
|
|
10
10
|
|
11
11
|
const RegExpValidator = require("@eslint-community/regexpp").RegExpValidator;
|
12
12
|
const validator = new RegExpValidator();
|
13
|
-
const validFlags =
|
13
|
+
const validFlags = "dgimsuvy";
|
14
14
|
const undefined1 = void 0;
|
15
15
|
|
16
16
|
//------------------------------------------------------------------------------
|
@@ -49,13 +49,13 @@ module.exports = {
|
|
49
49
|
create(context) {
|
50
50
|
|
51
51
|
const options = context.options[0];
|
52
|
-
let allowedFlags =
|
52
|
+
let allowedFlags = [];
|
53
53
|
|
54
54
|
if (options && options.allowConstructorFlags) {
|
55
|
-
const temp = options.allowConstructorFlags.join("").replace(validFlags, "");
|
55
|
+
const temp = options.allowConstructorFlags.join("").replace(new RegExp(`[${validFlags}]`, "gu"), "");
|
56
56
|
|
57
57
|
if (temp) {
|
58
|
-
allowedFlags = new
|
58
|
+
allowedFlags = [...new Set(temp)];
|
59
59
|
}
|
60
60
|
}
|
61
61
|
|
@@ -125,16 +125,19 @@ module.exports = {
|
|
125
125
|
/**
|
126
126
|
* Check syntax error in a given flags.
|
127
127
|
* @param {string|null} flags The RegExp flags to validate.
|
128
|
+
* @param {string|null} flagsToCheck The RegExp invalid flags.
|
129
|
+
* @param {string} allFlags all valid and allowed flags.
|
128
130
|
* @returns {string|null} The syntax error.
|
129
131
|
*/
|
130
|
-
function validateRegExpFlags(flags) {
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
132
|
+
function validateRegExpFlags(flags, flagsToCheck, allFlags) {
|
133
|
+
const duplicateFlags = [];
|
134
|
+
|
135
|
+
if (typeof flagsToCheck === "string") {
|
136
|
+
for (const flag of flagsToCheck) {
|
137
|
+
if (allFlags.includes(flag)) {
|
138
|
+
duplicateFlags.push(flag);
|
139
|
+
}
|
140
|
+
}
|
138
141
|
}
|
139
142
|
|
140
143
|
/*
|
@@ -142,10 +145,19 @@ module.exports = {
|
|
142
145
|
* but this rule may check only the flag when the pattern is unidentifiable, so check it here.
|
143
146
|
* https://tc39.es/ecma262/multipage/text-processing.html#sec-parsepattern
|
144
147
|
*/
|
145
|
-
if (flags.includes("u") && flags.includes("v")) {
|
148
|
+
if (flags && flags.includes("u") && flags.includes("v")) {
|
146
149
|
return "Regex 'u' and 'v' flags cannot be used together";
|
147
150
|
}
|
148
|
-
|
151
|
+
|
152
|
+
if (duplicateFlags.length > 0) {
|
153
|
+
return `Duplicate flags ('${duplicateFlags.join("")}') supplied to RegExp constructor`;
|
154
|
+
}
|
155
|
+
|
156
|
+
if (!flagsToCheck) {
|
157
|
+
return null;
|
158
|
+
}
|
159
|
+
|
160
|
+
return `Invalid flags supplied to RegExp constructor '${flagsToCheck}'`;
|
149
161
|
}
|
150
162
|
|
151
163
|
return {
|
@@ -154,13 +166,17 @@ module.exports = {
|
|
154
166
|
return;
|
155
167
|
}
|
156
168
|
|
157
|
-
|
169
|
+
const flags = getFlags(node);
|
170
|
+
let flagsToCheck = flags;
|
171
|
+
const allFlags = allowedFlags.length > 0 ? validFlags.split("").concat(allowedFlags) : validFlags.split("");
|
158
172
|
|
159
|
-
if (flags
|
160
|
-
|
173
|
+
if (flags) {
|
174
|
+
allFlags.forEach(flag => {
|
175
|
+
flagsToCheck = flagsToCheck.replace(flag, "");
|
176
|
+
});
|
161
177
|
}
|
162
178
|
|
163
|
-
let message = validateRegExpFlags(flags);
|
179
|
+
let message = validateRegExpFlags(flags, flagsToCheck, allFlags);
|
164
180
|
|
165
181
|
if (message) {
|
166
182
|
report(node, message);
|
@@ -18,6 +18,26 @@ const {
|
|
18
18
|
const astUtils = require("./utils/ast-utils.js");
|
19
19
|
const { isValidWithUnicodeFlag } = require("./utils/regular-expressions");
|
20
20
|
|
21
|
+
/**
|
22
|
+
* Checks whether the flag configuration should be treated as a missing flag.
|
23
|
+
* @param {"u"|"v"|undefined} requireFlag A particular flag to require
|
24
|
+
* @param {string} flags The regex flags
|
25
|
+
* @returns {boolean} Whether the flag configuration results in a missing flag.
|
26
|
+
*/
|
27
|
+
function checkFlags(requireFlag, flags) {
|
28
|
+
let missingFlag;
|
29
|
+
|
30
|
+
if (requireFlag === "v") {
|
31
|
+
missingFlag = !flags.includes("v");
|
32
|
+
} else if (requireFlag === "u") {
|
33
|
+
missingFlag = !flags.includes("u");
|
34
|
+
} else {
|
35
|
+
missingFlag = !flags.includes("u") && !flags.includes("v");
|
36
|
+
}
|
37
|
+
|
38
|
+
return missingFlag;
|
39
|
+
}
|
40
|
+
|
21
41
|
//------------------------------------------------------------------------------
|
22
42
|
// Rule Definition
|
23
43
|
//------------------------------------------------------------------------------
|
@@ -37,31 +57,65 @@ module.exports = {
|
|
37
57
|
|
38
58
|
messages: {
|
39
59
|
addUFlag: "Add the 'u' flag.",
|
40
|
-
|
60
|
+
addVFlag: "Add the 'v' flag.",
|
61
|
+
requireUFlag: "Use the 'u' flag.",
|
62
|
+
requireVFlag: "Use the 'v' flag."
|
41
63
|
},
|
42
64
|
|
43
|
-
schema: [
|
65
|
+
schema: [
|
66
|
+
{
|
67
|
+
type: "object",
|
68
|
+
properties: {
|
69
|
+
requireFlag: {
|
70
|
+
enum: ["u", "v"]
|
71
|
+
}
|
72
|
+
},
|
73
|
+
additionalProperties: false
|
74
|
+
}
|
75
|
+
]
|
44
76
|
},
|
45
77
|
|
46
78
|
create(context) {
|
47
79
|
|
48
80
|
const sourceCode = context.sourceCode;
|
49
81
|
|
82
|
+
const {
|
83
|
+
requireFlag
|
84
|
+
} = context.options[0] ?? {};
|
85
|
+
|
50
86
|
return {
|
51
87
|
"Literal[regex]"(node) {
|
52
88
|
const flags = node.regex.flags || "";
|
53
89
|
|
54
|
-
|
90
|
+
const missingFlag = checkFlags(requireFlag, flags);
|
91
|
+
|
92
|
+
if (missingFlag) {
|
55
93
|
context.report({
|
56
|
-
messageId: "requireUFlag",
|
94
|
+
messageId: requireFlag === "v" ? "requireVFlag" : "requireUFlag",
|
57
95
|
node,
|
58
|
-
suggest: isValidWithUnicodeFlag(context.languageOptions.ecmaVersion, node.regex.pattern)
|
96
|
+
suggest: isValidWithUnicodeFlag(context.languageOptions.ecmaVersion, node.regex.pattern, requireFlag)
|
59
97
|
? [
|
60
98
|
{
|
61
99
|
fix(fixer) {
|
62
|
-
|
100
|
+
const replaceFlag = requireFlag ?? "u";
|
101
|
+
const regex = sourceCode.getText(node);
|
102
|
+
const slashPos = regex.lastIndexOf("/");
|
103
|
+
|
104
|
+
if (requireFlag) {
|
105
|
+
const flag = requireFlag === "u" ? "v" : "u";
|
106
|
+
|
107
|
+
if (regex.includes(flag, slashPos)) {
|
108
|
+
return fixer.replaceText(
|
109
|
+
node,
|
110
|
+
regex.slice(0, slashPos) +
|
111
|
+
regex.slice(slashPos).replace(flag, requireFlag)
|
112
|
+
);
|
113
|
+
}
|
114
|
+
}
|
115
|
+
|
116
|
+
return fixer.insertTextAfter(node, replaceFlag);
|
63
117
|
},
|
64
|
-
messageId: "addUFlag"
|
118
|
+
messageId: requireFlag === "v" ? "addVFlag" : "addUFlag"
|
65
119
|
}
|
66
120
|
]
|
67
121
|
: null
|
@@ -85,22 +139,49 @@ module.exports = {
|
|
85
139
|
const pattern = getStringIfConstant(patternNode, scope);
|
86
140
|
const flags = getStringIfConstant(flagsNode, scope);
|
87
141
|
|
88
|
-
|
142
|
+
let missingFlag = !flagsNode;
|
143
|
+
|
144
|
+
if (typeof flags === "string") {
|
145
|
+
missingFlag = checkFlags(requireFlag, flags);
|
146
|
+
}
|
147
|
+
|
148
|
+
if (missingFlag) {
|
89
149
|
context.report({
|
90
|
-
messageId: "requireUFlag",
|
150
|
+
messageId: requireFlag === "v" ? "requireVFlag" : "requireUFlag",
|
91
151
|
node: refNode,
|
92
|
-
suggest: typeof pattern === "string" && isValidWithUnicodeFlag(context.languageOptions.ecmaVersion, pattern)
|
152
|
+
suggest: typeof pattern === "string" && isValidWithUnicodeFlag(context.languageOptions.ecmaVersion, pattern, requireFlag)
|
93
153
|
? [
|
94
154
|
{
|
95
155
|
fix(fixer) {
|
156
|
+
const replaceFlag = requireFlag ?? "u";
|
157
|
+
|
96
158
|
if (flagsNode) {
|
97
159
|
if ((flagsNode.type === "Literal" && typeof flagsNode.value === "string") || flagsNode.type === "TemplateLiteral") {
|
98
160
|
const flagsNodeText = sourceCode.getText(flagsNode);
|
161
|
+
const flag = requireFlag === "u" ? "v" : "u";
|
162
|
+
|
163
|
+
if (flags.includes(flag)) {
|
164
|
+
|
165
|
+
// Avoid replacing "u" in escapes like `\uXXXX`
|
166
|
+
if (flagsNode.type === "Literal" && flagsNode.raw.includes("\\")) {
|
167
|
+
return null;
|
168
|
+
}
|
169
|
+
|
170
|
+
// Avoid replacing "u" in expressions like "`${regularFlags}g`"
|
171
|
+
if (flagsNode.type === "TemplateLiteral" && (
|
172
|
+
flagsNode.expressions.length ||
|
173
|
+
flagsNode.quasis.some(({ value: { raw } }) => raw.includes("\\"))
|
174
|
+
)) {
|
175
|
+
return null;
|
176
|
+
}
|
177
|
+
|
178
|
+
return fixer.replaceText(flagsNode, flagsNodeText.replace(flag, replaceFlag));
|
179
|
+
}
|
99
180
|
|
100
181
|
return fixer.replaceText(flagsNode, [
|
101
182
|
flagsNodeText.slice(0, flagsNodeText.length - 1),
|
102
183
|
flagsNodeText.slice(flagsNodeText.length - 1)
|
103
|
-
].join(
|
184
|
+
].join(replaceFlag));
|
104
185
|
}
|
105
186
|
|
106
187
|
// We intentionally don't suggest concatenating + "u" to non-literals
|
@@ -112,11 +193,11 @@ module.exports = {
|
|
112
193
|
return fixer.insertTextAfter(
|
113
194
|
penultimateToken,
|
114
195
|
astUtils.isCommaToken(penultimateToken)
|
115
|
-
?
|
116
|
-
:
|
196
|
+
? ` "${replaceFlag}",`
|
197
|
+
: `, "${replaceFlag}"`
|
117
198
|
);
|
118
199
|
},
|
119
|
-
messageId: "addUFlag"
|
200
|
+
messageId: requireFlag === "v" ? "addVFlag" : "addUFlag"
|
120
201
|
}
|
121
202
|
]
|
122
203
|
: null
|
@@ -14,12 +14,16 @@ const REGEXPP_LATEST_ECMA_VERSION = 2025;
|
|
14
14
|
* Checks if the given regular expression pattern would be valid with the `u` flag.
|
15
15
|
* @param {number} ecmaVersion ECMAScript version to parse in.
|
16
16
|
* @param {string} pattern The regular expression pattern to verify.
|
17
|
+
* @param {"u"|"v"} flag The type of Unicode flag
|
17
18
|
* @returns {boolean} `true` if the pattern would be valid with the `u` flag.
|
18
19
|
* `false` if the pattern would be invalid with the `u` flag or the configured
|
19
20
|
* ecmaVersion doesn't support the `u` flag.
|
20
21
|
*/
|
21
|
-
function isValidWithUnicodeFlag(ecmaVersion, pattern) {
|
22
|
-
if (ecmaVersion <= 5) { // ecmaVersion <= 5 doesn't support the 'u' flag
|
22
|
+
function isValidWithUnicodeFlag(ecmaVersion, pattern, flag = "u") {
|
23
|
+
if (flag === "u" && ecmaVersion <= 5) { // ecmaVersion <= 5 doesn't support the 'u' flag
|
24
|
+
return false;
|
25
|
+
}
|
26
|
+
if (flag === "v" && ecmaVersion <= 2023) {
|
23
27
|
return false;
|
24
28
|
}
|
25
29
|
|
@@ -28,7 +32,11 @@ function isValidWithUnicodeFlag(ecmaVersion, pattern) {
|
|
28
32
|
});
|
29
33
|
|
30
34
|
try {
|
31
|
-
validator.validatePattern(pattern, void 0, void 0,
|
35
|
+
validator.validatePattern(pattern, void 0, void 0, flag === "u" ? {
|
36
|
+
unicode: /* uFlag = */ true
|
37
|
+
} : {
|
38
|
+
unicodeSets: true
|
39
|
+
});
|
32
40
|
} catch {
|
33
41
|
return false;
|
34
42
|
}
|
@@ -0,0 +1,65 @@
|
|
1
|
+
/**
|
2
|
+
* @fileoverview ESLint Parser
|
3
|
+
* @author Nicholas C. Zakas
|
4
|
+
*/
|
5
|
+
/* eslint class-methods-use-this: off -- Anticipate future constructor arguments. */
|
6
|
+
|
7
|
+
"use strict";
|
8
|
+
|
9
|
+
//-----------------------------------------------------------------------------
|
10
|
+
// Types
|
11
|
+
//-----------------------------------------------------------------------------
|
12
|
+
|
13
|
+
/** @typedef {import("../linter/vfile.js").VFile} VFile */
|
14
|
+
/** @typedef {import("@eslint/core").Language} Language */
|
15
|
+
/** @typedef {import("@eslint/core").LanguageOptions} LanguageOptions */
|
16
|
+
|
17
|
+
//-----------------------------------------------------------------------------
|
18
|
+
// Exports
|
19
|
+
//-----------------------------------------------------------------------------
|
20
|
+
|
21
|
+
/**
|
22
|
+
* The parser for ESLint.
|
23
|
+
*/
|
24
|
+
class ParserService {
|
25
|
+
|
26
|
+
/**
|
27
|
+
* Parses the given file synchronously.
|
28
|
+
* @param {VFile} file The file to parse.
|
29
|
+
* @param {{language:Language,languageOptions:LanguageOptions}} config The configuration to use.
|
30
|
+
* @returns {Object} An object with the parsed source code or errors.
|
31
|
+
* @throws {Error} If the parser returns a promise.
|
32
|
+
*/
|
33
|
+
parseSync(file, config) {
|
34
|
+
|
35
|
+
const { language, languageOptions } = config;
|
36
|
+
const result = language.parse(file, { languageOptions });
|
37
|
+
|
38
|
+
if (typeof result.then === "function") {
|
39
|
+
throw new Error("Unsupported: Language parser returned a promise.");
|
40
|
+
}
|
41
|
+
|
42
|
+
if (result.ok) {
|
43
|
+
return {
|
44
|
+
ok: true,
|
45
|
+
sourceCode: language.createSourceCode(file, result, { languageOptions })
|
46
|
+
};
|
47
|
+
}
|
48
|
+
|
49
|
+
// if we made it to here there was an error
|
50
|
+
return {
|
51
|
+
ok: false,
|
52
|
+
errors: result.errors.map(error => ({
|
53
|
+
ruleId: null,
|
54
|
+
nodeType: null,
|
55
|
+
fatal: true,
|
56
|
+
severity: 2,
|
57
|
+
message: `Parsing error: ${error.message}`,
|
58
|
+
line: error.line,
|
59
|
+
column: error.column
|
60
|
+
}))
|
61
|
+
};
|
62
|
+
}
|
63
|
+
}
|
64
|
+
|
65
|
+
module.exports = { ParserService };
|