eslint 8.44.0 → 8.46.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 +1 -1
- package/lib/config/flat-config-schema.js +70 -0
- package/lib/eslint/flat-eslint.js +3 -6
- package/lib/linter/report-translator.js +18 -2
- package/lib/rules/indent.js +1 -1
- package/lib/rules/no-control-regex.js +15 -2
- package/lib/rules/no-empty-character-class.js +33 -12
- package/lib/rules/no-empty-pattern.js +38 -3
- package/lib/rules/no-invalid-regexp.js +22 -7
- package/lib/rules/no-loop-func.js +1 -1
- package/lib/rules/no-misleading-character-class.js +8 -2
- package/lib/rules/no-regex-spaces.js +18 -3
- package/lib/rules/no-return-await.js +5 -0
- package/lib/rules/no-useless-backreference.js +1 -1
- package/lib/rules/no-useless-escape.js +160 -81
- package/lib/rules/padding-line-between-statements.js +4 -42
- package/lib/rules/prefer-named-capture-group.js +8 -5
- package/lib/rules/prefer-regex-literals.js +9 -3
- package/lib/rules/require-unicode-regexp.js +3 -3
- package/lib/rules/utils/ast-utils.js +11 -1
- package/lib/rules/utils/regular-expressions.js +2 -2
- package/lib/unsupported-api.js +3 -1
- package/messages/eslintrc-incompat.js +98 -0
- package/messages/eslintrc-plugins.js +24 -0
- package/package.json +8 -11
package/README.md
CHANGED
@@ -284,7 +284,7 @@ The following companies, organizations, and individuals support ESLint's ongoing
|
|
284
284
|
<p><a href="#"><img src="https://images.opencollective.com/2021-frameworks-fund/logo.png" alt="Chrome Frameworks Fund" height="undefined"></a> <a href="https://automattic.com"><img src="https://images.opencollective.com/automattic/d0ef3e1/logo.png" alt="Automattic" height="undefined"></a></p><h3>Gold Sponsors</h3>
|
285
285
|
<p><a href="https://engineering.salesforce.com"><img src="https://images.opencollective.com/salesforce/ca8f997/logo.png" alt="Salesforce" height="96"></a> <a href="https://www.airbnb.com/"><img src="https://images.opencollective.com/airbnb/d327d66/logo.png" alt="Airbnb" height="96"></a></p><h3>Silver Sponsors</h3>
|
286
286
|
<p><a href="https://sentry.io"><img src="https://avatars.githubusercontent.com/u/1396951?v=4" alt="Sentry" height="64"></a> <a href="https://liftoff.io/"><img src="https://images.opencollective.com/liftoff/5c4fa84/logo.png" alt="Liftoff" height="64"></a> <a href="https://americanexpress.io"><img src="https://avatars.githubusercontent.com/u/3853301?v=4" alt="American Express" height="64"></a></p><h3>Bronze Sponsors</h3>
|
287
|
-
<p><a href="https://themeisle.com"><img src="https://images.opencollective.com/themeisle/d5592fe/logo.png" alt="ThemeIsle" height="32"></a> <a href="https://nx.dev"><img src="https://images.opencollective.com/nx/0efbe42/logo.png" alt="Nx (by Nrwl)" height="32"></a> <a href="https://www.crosswordsolver.org/anagram-solver/"><img src="https://images.opencollective.com/anagram-solver/2666271/logo.png" alt="Anagram Solver" height="32"></a> <a href="https://icons8.com"><img src="https://images.opencollective.com/icons8/7fa1641/logo.png" alt="Icons8: free icons, photos, illustrations, and music" height="32"></a> <a href="https://discord.com"><img src="https://images.opencollective.com/discordapp/f9645d9/logo.png" alt="Discord" height="32"></a> <a href="https://
|
287
|
+
<p><a href="https://themeisle.com"><img src="https://images.opencollective.com/themeisle/d5592fe/logo.png" alt="ThemeIsle" height="32"></a> <a href="https://nx.dev"><img src="https://images.opencollective.com/nx/0efbe42/logo.png" alt="Nx (by Nrwl)" height="32"></a> <a href="https://www.crosswordsolver.org/anagram-solver/"><img src="https://images.opencollective.com/anagram-solver/2666271/logo.png" alt="Anagram Solver" height="32"></a> <a href="https://icons8.com"><img src="https://images.opencollective.com/icons8/7fa1641/logo.png" alt="Icons8: free icons, photos, illustrations, and music" height="32"></a> <a href="https://discord.com"><img src="https://images.opencollective.com/discordapp/f9645d9/logo.png" alt="Discord" height="32"></a> <a href="https://iboysoft.com/"><img src="https://images.opencollective.com/iboysoft-software/7f9d60e/avatar.png" alt="iBoysoft" height="32"></a> <a href="https://www.bairesdev.com/sponsoring-open-source-projects/"><img src="https://images.opencollective.com/bairesdev/48bb773/logo.png" alt="BairesDev" height="32"></a> <a href="https://github.com/about"><img src="https://avatars.githubusercontent.com/u/9919?v=4" alt="GitHub" height="32"></a> <a href="https://transloadit.com/"><img src="https://avatars.githubusercontent.com/u/125754?v=4" alt="Transloadit" height="32"></a> <a href="https://www.ignitionapp.com"><img src="https://avatars.githubusercontent.com/u/5753491?v=4" alt="Ignition" height="32"></a> <a href="https://herocoders.com"><img src="https://avatars.githubusercontent.com/u/37549774?v=4" alt="HeroCoders" height="32"></a> <a href="https://quickbookstoolhub.com"><img src="https://avatars.githubusercontent.com/u/95090305?u=e5bc398ef775c9ed19f955c675cdc1fb6abf01df&v=4" alt="QuickBooks Tool hub" height="32"></a></p>
|
288
288
|
<!--sponsorsend-->
|
289
289
|
|
290
290
|
## Technology Sponsors
|
@@ -212,6 +212,38 @@ function assertIsObject(value) {
|
|
212
212
|
}
|
213
213
|
}
|
214
214
|
|
215
|
+
/**
|
216
|
+
* The error type when there's an eslintrc-style options in a flat config.
|
217
|
+
*/
|
218
|
+
class IncompatibleKeyError extends Error {
|
219
|
+
|
220
|
+
/**
|
221
|
+
* @param {string} key The invalid key.
|
222
|
+
*/
|
223
|
+
constructor(key) {
|
224
|
+
super("This appears to be in eslintrc format rather than flat config format.");
|
225
|
+
this.messageTemplate = "eslintrc-incompat";
|
226
|
+
this.messageData = { key };
|
227
|
+
}
|
228
|
+
}
|
229
|
+
|
230
|
+
/**
|
231
|
+
* The error type when there's an eslintrc-style plugins array found.
|
232
|
+
*/
|
233
|
+
class IncompatiblePluginsError extends Error {
|
234
|
+
|
235
|
+
/**
|
236
|
+
* Creates a new instance.
|
237
|
+
* @param {Array<string>} plugins The plugins array.
|
238
|
+
*/
|
239
|
+
constructor(plugins) {
|
240
|
+
super("This appears to be in eslintrc format (array of strings) rather than flat config format (object).");
|
241
|
+
this.messageTemplate = "eslintrc-plugins";
|
242
|
+
this.messageData = { plugins };
|
243
|
+
}
|
244
|
+
}
|
245
|
+
|
246
|
+
|
215
247
|
//-----------------------------------------------------------------------------
|
216
248
|
// Low-Level Schemas
|
217
249
|
//-----------------------------------------------------------------------------
|
@@ -303,6 +335,11 @@ const pluginsSchema = {
|
|
303
335
|
throw new TypeError("Expected an object.");
|
304
336
|
}
|
305
337
|
|
338
|
+
// make sure it's not an array, which would mean eslintrc-style is used
|
339
|
+
if (Array.isArray(value)) {
|
340
|
+
throw new IncompatiblePluginsError(value);
|
341
|
+
}
|
342
|
+
|
306
343
|
// second check the keys to make sure they are objects
|
307
344
|
for (const key of Object.keys(value)) {
|
308
345
|
|
@@ -438,11 +475,44 @@ const sourceTypeSchema = {
|
|
438
475
|
}
|
439
476
|
};
|
440
477
|
|
478
|
+
/**
|
479
|
+
* Creates a schema that always throws an error. Useful for warning
|
480
|
+
* about eslintrc-style keys.
|
481
|
+
* @param {string} key The eslintrc key to create a schema for.
|
482
|
+
* @returns {ObjectPropertySchema} The schema.
|
483
|
+
*/
|
484
|
+
function createEslintrcErrorSchema(key) {
|
485
|
+
return {
|
486
|
+
merge: "replace",
|
487
|
+
validate() {
|
488
|
+
throw new IncompatibleKeyError(key);
|
489
|
+
}
|
490
|
+
};
|
491
|
+
}
|
492
|
+
|
493
|
+
const eslintrcKeys = [
|
494
|
+
"env",
|
495
|
+
"extends",
|
496
|
+
"globals",
|
497
|
+
"ignorePatterns",
|
498
|
+
"noInlineConfig",
|
499
|
+
"overrides",
|
500
|
+
"parser",
|
501
|
+
"parserOptions",
|
502
|
+
"reportUnusedDisableDirectives",
|
503
|
+
"root"
|
504
|
+
];
|
505
|
+
|
441
506
|
//-----------------------------------------------------------------------------
|
442
507
|
// Full schema
|
443
508
|
//-----------------------------------------------------------------------------
|
444
509
|
|
445
510
|
exports.flatConfigSchema = {
|
511
|
+
|
512
|
+
// eslintrc-style keys that should always error
|
513
|
+
...Object.fromEntries(eslintrcKeys.map(key => [key, createEslintrcErrorSchema(key)])),
|
514
|
+
|
515
|
+
// flat config keys
|
446
516
|
settings: deepObjectAssignSchema,
|
447
517
|
linterOptions: {
|
448
518
|
schema: {
|
@@ -576,7 +576,6 @@ class FlatESLint {
|
|
576
576
|
cacheFilePath,
|
577
577
|
lintResultCache,
|
578
578
|
defaultConfigs,
|
579
|
-
defaultIgnores: () => false,
|
580
579
|
configs: null
|
581
580
|
});
|
582
581
|
|
@@ -715,12 +714,10 @@ class FlatESLint {
|
|
715
714
|
}
|
716
715
|
const rule = getRuleFromConfig(ruleId, config);
|
717
716
|
|
718
|
-
//
|
719
|
-
if (
|
720
|
-
|
717
|
+
// ignore unknown rules
|
718
|
+
if (rule) {
|
719
|
+
resultRules.set(ruleId, rule);
|
721
720
|
}
|
722
|
-
|
723
|
-
resultRules.set(ruleId, rule);
|
724
721
|
}
|
725
722
|
}
|
726
723
|
|
@@ -100,6 +100,22 @@ function normalizeReportLoc(descriptor) {
|
|
100
100
|
return descriptor.node.loc;
|
101
101
|
}
|
102
102
|
|
103
|
+
/**
|
104
|
+
* Clones the given fix object.
|
105
|
+
* @param {Fix|null} fix The fix to clone.
|
106
|
+
* @returns {Fix|null} Deep cloned fix object or `null` if `null` or `undefined` was passed in.
|
107
|
+
*/
|
108
|
+
function cloneFix(fix) {
|
109
|
+
if (!fix) {
|
110
|
+
return null;
|
111
|
+
}
|
112
|
+
|
113
|
+
return {
|
114
|
+
range: [fix.range[0], fix.range[1]],
|
115
|
+
text: fix.text
|
116
|
+
};
|
117
|
+
}
|
118
|
+
|
103
119
|
/**
|
104
120
|
* Check that a fix has a valid range.
|
105
121
|
* @param {Fix|null} fix The fix to validate.
|
@@ -137,7 +153,7 @@ function mergeFixes(fixes, sourceCode) {
|
|
137
153
|
return null;
|
138
154
|
}
|
139
155
|
if (fixes.length === 1) {
|
140
|
-
return fixes[0];
|
156
|
+
return cloneFix(fixes[0]);
|
141
157
|
}
|
142
158
|
|
143
159
|
fixes.sort(compareFixesByRange);
|
@@ -183,7 +199,7 @@ function normalizeFixes(descriptor, sourceCode) {
|
|
183
199
|
}
|
184
200
|
|
185
201
|
assertValidFix(fix);
|
186
|
-
return fix;
|
202
|
+
return cloneFix(fix);
|
187
203
|
}
|
188
204
|
|
189
205
|
/**
|
package/lib/rules/indent.js
CHANGED
@@ -1250,7 +1250,7 @@ module.exports = {
|
|
1250
1250
|
|
1251
1251
|
IfStatement(node) {
|
1252
1252
|
addBlocklessNodeIndent(node.consequent);
|
1253
|
-
if (node.alternate
|
1253
|
+
if (node.alternate) {
|
1254
1254
|
addBlocklessNodeIndent(node.alternate);
|
1255
1255
|
}
|
1256
1256
|
},
|
@@ -14,6 +14,16 @@ const collector = new (class {
|
|
14
14
|
}
|
15
15
|
|
16
16
|
onPatternEnter() {
|
17
|
+
|
18
|
+
/*
|
19
|
+
* `RegExpValidator` may parse the pattern twice in one `validatePattern`.
|
20
|
+
* So `this._controlChars` should be cleared here as well.
|
21
|
+
*
|
22
|
+
* For example, the `/(?<a>\x1f)/` regex will parse the pattern twice.
|
23
|
+
* This is based on the content described in Annex B.
|
24
|
+
* If the regex contains a `GroupName` and the `u` flag is not used, `ParseText` will be called twice.
|
25
|
+
* See https://tc39.es/ecma262/2023/multipage/additional-ecmascript-features-for-web-browsers.html#sec-parsepattern-annexb
|
26
|
+
*/
|
17
27
|
this._controlChars = [];
|
18
28
|
}
|
19
29
|
|
@@ -32,10 +42,13 @@ const collector = new (class {
|
|
32
42
|
|
33
43
|
collectControlChars(regexpStr, flags) {
|
34
44
|
const uFlag = typeof flags === "string" && flags.includes("u");
|
45
|
+
const vFlag = typeof flags === "string" && flags.includes("v");
|
46
|
+
|
47
|
+
this._controlChars = [];
|
48
|
+
this._source = regexpStr;
|
35
49
|
|
36
50
|
try {
|
37
|
-
this.
|
38
|
-
this._validator.validatePattern(regexpStr, void 0, void 0, uFlag); // Call onCharacter hook
|
51
|
+
this._validator.validatePattern(regexpStr, void 0, void 0, { unicode: uFlag, unicodeSets: vFlag }); // Call onCharacter hook
|
39
52
|
} catch {
|
40
53
|
|
41
54
|
// Ignore syntax errors in RegExp.
|
@@ -5,20 +5,18 @@
|
|
5
5
|
|
6
6
|
"use strict";
|
7
7
|
|
8
|
+
//------------------------------------------------------------------------------
|
9
|
+
// Requirements
|
10
|
+
//------------------------------------------------------------------------------
|
11
|
+
|
12
|
+
const { RegExpParser, visitRegExpAST } = require("@eslint-community/regexpp");
|
13
|
+
|
8
14
|
//------------------------------------------------------------------------------
|
9
15
|
// Helpers
|
10
16
|
//------------------------------------------------------------------------------
|
11
17
|
|
12
|
-
|
13
|
-
|
14
|
-
* 0. `^` fix the match at the beginning of the string
|
15
|
-
* 1. `([^\\[]|\\.|\[([^\\\]]|\\.)+\])*`: regexp contents; 0 or more of the following
|
16
|
-
* 1.0. `[^\\[]`: any character that's not a `\` or a `[` (anything but escape sequences and character classes)
|
17
|
-
* 1.1. `\\.`: an escape sequence
|
18
|
-
* 1.2. `\[([^\\\]]|\\.)+\]`: a character class that isn't empty
|
19
|
-
* 2. `$`: fix the match at the end of the string
|
20
|
-
*/
|
21
|
-
const regex = /^([^\\[]|\\.|\[([^\\\]]|\\.)+\])*$/u;
|
18
|
+
const parser = new RegExpParser();
|
19
|
+
const QUICK_TEST_REGEX = /\[\]/u;
|
22
20
|
|
23
21
|
//------------------------------------------------------------------------------
|
24
22
|
// Rule Definition
|
@@ -45,9 +43,32 @@ module.exports = {
|
|
45
43
|
create(context) {
|
46
44
|
return {
|
47
45
|
"Literal[regex]"(node) {
|
48
|
-
|
49
|
-
|
46
|
+
const { pattern, flags } = node.regex;
|
47
|
+
|
48
|
+
if (!QUICK_TEST_REGEX.test(pattern)) {
|
49
|
+
return;
|
50
50
|
}
|
51
|
+
|
52
|
+
let regExpAST;
|
53
|
+
|
54
|
+
try {
|
55
|
+
regExpAST = parser.parsePattern(pattern, 0, pattern.length, {
|
56
|
+
unicode: flags.includes("u"),
|
57
|
+
unicodeSets: flags.includes("v")
|
58
|
+
});
|
59
|
+
} catch {
|
60
|
+
|
61
|
+
// Ignore regular expressions that regexpp cannot parse
|
62
|
+
return;
|
63
|
+
}
|
64
|
+
|
65
|
+
visitRegExpAST(regExpAST, {
|
66
|
+
onCharacterClassEnter(characterClass) {
|
67
|
+
if (!characterClass.negate && characterClass.elements.length === 0) {
|
68
|
+
context.report({ node, messageId: "unexpected" });
|
69
|
+
}
|
70
|
+
}
|
71
|
+
});
|
51
72
|
}
|
52
73
|
};
|
53
74
|
|
@@ -4,6 +4,8 @@
|
|
4
4
|
*/
|
5
5
|
"use strict";
|
6
6
|
|
7
|
+
const astUtils = require("./utils/ast-utils");
|
8
|
+
|
7
9
|
//------------------------------------------------------------------------------
|
8
10
|
// Rule Definition
|
9
11
|
//------------------------------------------------------------------------------
|
@@ -19,7 +21,18 @@ module.exports = {
|
|
19
21
|
url: "https://eslint.org/docs/latest/rules/no-empty-pattern"
|
20
22
|
},
|
21
23
|
|
22
|
-
schema: [
|
24
|
+
schema: [
|
25
|
+
{
|
26
|
+
type: "object",
|
27
|
+
properties: {
|
28
|
+
allowObjectPatternsAsParameters: {
|
29
|
+
type: "boolean",
|
30
|
+
default: false
|
31
|
+
}
|
32
|
+
},
|
33
|
+
additionalProperties: false
|
34
|
+
}
|
35
|
+
],
|
23
36
|
|
24
37
|
messages: {
|
25
38
|
unexpected: "Unexpected empty {{type}} pattern."
|
@@ -27,11 +40,33 @@ module.exports = {
|
|
27
40
|
},
|
28
41
|
|
29
42
|
create(context) {
|
43
|
+
const options = context.options[0] || {},
|
44
|
+
allowObjectPatternsAsParameters = options.allowObjectPatternsAsParameters || false;
|
45
|
+
|
30
46
|
return {
|
31
47
|
ObjectPattern(node) {
|
32
|
-
|
33
|
-
|
48
|
+
|
49
|
+
if (node.properties.length > 0) {
|
50
|
+
return;
|
34
51
|
}
|
52
|
+
|
53
|
+
// Allow {} and {} = {} empty object patterns as parameters when allowObjectPatternsAsParameters is true
|
54
|
+
if (
|
55
|
+
allowObjectPatternsAsParameters &&
|
56
|
+
(
|
57
|
+
astUtils.isFunction(node.parent) ||
|
58
|
+
(
|
59
|
+
node.parent.type === "AssignmentPattern" &&
|
60
|
+
astUtils.isFunction(node.parent.parent) &&
|
61
|
+
node.parent.right.type === "ObjectExpression" &&
|
62
|
+
node.parent.right.properties.length === 0
|
63
|
+
)
|
64
|
+
)
|
65
|
+
) {
|
66
|
+
return;
|
67
|
+
}
|
68
|
+
|
69
|
+
context.report({ node, messageId: "unexpected", data: { type: "object" } });
|
35
70
|
},
|
36
71
|
ArrayPattern(node) {
|
37
72
|
if (node.elements.length === 0) {
|
@@ -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]/gu;
|
14
14
|
const undefined1 = void 0;
|
15
15
|
|
16
16
|
//------------------------------------------------------------------------------
|
@@ -108,12 +108,14 @@ module.exports = {
|
|
108
108
|
/**
|
109
109
|
* Check syntax error in a given pattern.
|
110
110
|
* @param {string} pattern The RegExp pattern to validate.
|
111
|
-
* @param {
|
111
|
+
* @param {Object} flags The RegExp flags to validate.
|
112
|
+
* @param {boolean} [flags.unicode] The Unicode flag.
|
113
|
+
* @param {boolean} [flags.unicodeSets] The UnicodeSets flag.
|
112
114
|
* @returns {string|null} The syntax error.
|
113
115
|
*/
|
114
|
-
function validateRegExpPattern(pattern,
|
116
|
+
function validateRegExpPattern(pattern, flags) {
|
115
117
|
try {
|
116
|
-
validator.validatePattern(pattern, undefined1, undefined1,
|
118
|
+
validator.validatePattern(pattern, undefined1, undefined1, flags);
|
117
119
|
return null;
|
118
120
|
} catch (err) {
|
119
121
|
return err.message;
|
@@ -131,10 +133,19 @@ module.exports = {
|
|
131
133
|
}
|
132
134
|
try {
|
133
135
|
validator.validateFlags(flags);
|
134
|
-
return null;
|
135
136
|
} catch {
|
136
137
|
return `Invalid flags supplied to RegExp constructor '${flags}'`;
|
137
138
|
}
|
139
|
+
|
140
|
+
/*
|
141
|
+
* `regexpp` checks the combination of `u` and `v` flags when parsing `Pattern` according to `ecma262`,
|
142
|
+
* but this rule may check only the flag when the pattern is unidentifiable, so check it here.
|
143
|
+
* https://tc39.es/ecma262/multipage/text-processing.html#sec-parsepattern
|
144
|
+
*/
|
145
|
+
if (flags.includes("u") && flags.includes("v")) {
|
146
|
+
return "Regex 'u' and 'v' flags cannot be used together";
|
147
|
+
}
|
148
|
+
return null;
|
138
149
|
}
|
139
150
|
|
140
151
|
return {
|
@@ -166,8 +177,12 @@ module.exports = {
|
|
166
177
|
|
167
178
|
// If flags are unknown, report the regex only if its pattern is invalid both with and without the "u" flag
|
168
179
|
flags === null
|
169
|
-
?
|
170
|
-
|
180
|
+
? (
|
181
|
+
validateRegExpPattern(pattern, { unicode: true, unicodeSets: false }) &&
|
182
|
+
validateRegExpPattern(pattern, { unicode: false, unicodeSets: true }) &&
|
183
|
+
validateRegExpPattern(pattern, { unicode: false, unicodeSets: false })
|
184
|
+
)
|
185
|
+
: validateRegExpPattern(pattern, { unicode: flags.includes("u"), unicodeSets: flags.includes("v") })
|
171
186
|
);
|
172
187
|
|
173
188
|
if (message) {
|
@@ -186,7 +186,7 @@ module.exports = {
|
|
186
186
|
}
|
187
187
|
|
188
188
|
const references = sourceCode.getScope(node).through;
|
189
|
-
const unsafeRefs = references.filter(r => !isSafe(loopNode, r)).map(r => r.identifier.name);
|
189
|
+
const unsafeRefs = references.filter(r => r.resolved && !isSafe(loopNode, r)).map(r => r.identifier.name);
|
190
190
|
|
191
191
|
if (unsafeRefs.length > 0) {
|
192
192
|
context.report({
|
@@ -18,7 +18,7 @@ const { isValidWithUnicodeFlag } = require("./utils/regular-expressions");
|
|
18
18
|
*
|
19
19
|
* CharacterClassRange syntax can steal a part of character sequence,
|
20
20
|
* so this function reverts CharacterClassRange syntax and restore the sequence.
|
21
|
-
* @param {regexpp.AST.CharacterClassElement[]} nodes The node list to iterate character sequences.
|
21
|
+
* @param {import('@eslint-community/regexpp').AST.CharacterClassElement[]} nodes The node list to iterate character sequences.
|
22
22
|
* @returns {IterableIterator<number[]>} The list of character sequences.
|
23
23
|
*/
|
24
24
|
function *iterateCharacterSequence(nodes) {
|
@@ -37,6 +37,9 @@ function *iterateCharacterSequence(nodes) {
|
|
37
37
|
break;
|
38
38
|
|
39
39
|
case "CharacterSet":
|
40
|
+
case "CharacterClass": // [[]] nesting character class
|
41
|
+
case "ClassStringDisjunction": // \q{...}
|
42
|
+
case "ExpressionCharacterClass": // [A--B]
|
40
43
|
if (seq.length > 0) {
|
41
44
|
yield seq;
|
42
45
|
seq = [];
|
@@ -144,7 +147,10 @@ module.exports = {
|
|
144
147
|
pattern,
|
145
148
|
0,
|
146
149
|
pattern.length,
|
147
|
-
|
150
|
+
{
|
151
|
+
unicode: flags.includes("u"),
|
152
|
+
unicodeSets: flags.includes("v")
|
153
|
+
}
|
148
154
|
);
|
149
155
|
} catch {
|
150
156
|
|
@@ -77,7 +77,7 @@ module.exports = {
|
|
77
77
|
let regExpAST;
|
78
78
|
|
79
79
|
try {
|
80
|
-
regExpAST = regExpParser.parsePattern(pattern, 0, pattern.length, flags.includes("u"));
|
80
|
+
regExpAST = regExpParser.parsePattern(pattern, 0, pattern.length, { unicode: flags.includes("u"), unicodeSets: flags.includes("v") });
|
81
81
|
} catch {
|
82
82
|
|
83
83
|
// Ignore regular expressions with syntax errors
|
@@ -155,13 +155,28 @@ module.exports = {
|
|
155
155
|
const regExpVar = astUtils.getVariableByName(scope, "RegExp");
|
156
156
|
const shadowed = regExpVar && regExpVar.defs.length > 0;
|
157
157
|
const patternNode = node.arguments[0];
|
158
|
-
const flagsNode = node.arguments[1];
|
159
158
|
|
160
159
|
if (node.callee.type === "Identifier" && node.callee.name === "RegExp" && isString(patternNode) && !shadowed) {
|
161
160
|
const pattern = patternNode.value;
|
162
161
|
const rawPattern = patternNode.raw.slice(1, -1);
|
163
162
|
const rawPatternStartRange = patternNode.range[0] + 1;
|
164
|
-
|
163
|
+
let flags;
|
164
|
+
|
165
|
+
if (node.arguments.length < 2) {
|
166
|
+
|
167
|
+
// It has no flags.
|
168
|
+
flags = "";
|
169
|
+
} else {
|
170
|
+
const flagsNode = node.arguments[1];
|
171
|
+
|
172
|
+
if (isString(flagsNode)) {
|
173
|
+
flags = flagsNode.value;
|
174
|
+
} else {
|
175
|
+
|
176
|
+
// The flags cannot be determined.
|
177
|
+
return;
|
178
|
+
}
|
179
|
+
}
|
165
180
|
|
166
181
|
checkRegex(
|
167
182
|
node,
|
@@ -1,6 +1,7 @@
|
|
1
1
|
/**
|
2
2
|
* @fileoverview Disallows unnecessary `return await`
|
3
3
|
* @author Jordan Harband
|
4
|
+
* @deprecated in ESLint v8.46.0
|
4
5
|
*/
|
5
6
|
"use strict";
|
6
7
|
|
@@ -26,6 +27,10 @@ module.exports = {
|
|
26
27
|
|
27
28
|
fixable: null,
|
28
29
|
|
30
|
+
deprecated: true,
|
31
|
+
|
32
|
+
replacedBy: [],
|
33
|
+
|
29
34
|
schema: [
|
30
35
|
],
|
31
36
|
|
@@ -95,7 +95,7 @@ module.exports = {
|
|
95
95
|
let regExpAST;
|
96
96
|
|
97
97
|
try {
|
98
|
-
regExpAST = parser.parsePattern(pattern, 0, pattern.length, flags.includes("u"));
|
98
|
+
regExpAST = parser.parsePattern(pattern, 0, pattern.length, { unicode: flags.includes("u"), unicodeSets: flags.includes("v") });
|
99
99
|
} catch {
|
100
100
|
|
101
101
|
// Ignore regular expressions with syntax errors
|
@@ -6,7 +6,12 @@
|
|
6
6
|
"use strict";
|
7
7
|
|
8
8
|
const astUtils = require("./utils/ast-utils");
|
9
|
+
const { RegExpParser, visitRegExpAST } = require("@eslint-community/regexpp");
|
9
10
|
|
11
|
+
/**
|
12
|
+
* @typedef {import('@eslint-community/regexpp').AST.CharacterClass} CharacterClass
|
13
|
+
* @typedef {import('@eslint-community/regexpp').AST.ExpressionCharacterClass} ExpressionCharacterClass
|
14
|
+
*/
|
10
15
|
//------------------------------------------------------------------------------
|
11
16
|
// Rule Definition
|
12
17
|
//------------------------------------------------------------------------------
|
@@ -28,55 +33,17 @@ const VALID_STRING_ESCAPES = union(new Set("\\nrvtbfux"), astUtils.LINEBREAKS);
|
|
28
33
|
const REGEX_GENERAL_ESCAPES = new Set("\\bcdDfnpPrsStvwWxu0123456789]");
|
29
34
|
const REGEX_NON_CHARCLASS_ESCAPES = union(REGEX_GENERAL_ESCAPES, new Set("^/.$*+?[{}|()Bk"));
|
30
35
|
|
31
|
-
|
32
|
-
*
|
33
|
-
*
|
34
|
-
* @returns {Object[]} A list of characters, each with info on escaping and whether they're in a character class.
|
35
|
-
* @example
|
36
|
-
*
|
37
|
-
* parseRegExp("a\\b[cd-]");
|
38
|
-
*
|
39
|
-
* // returns:
|
40
|
-
* [
|
41
|
-
* { text: "a", index: 0, escaped: false, inCharClass: false, startsCharClass: false, endsCharClass: false },
|
42
|
-
* { text: "b", index: 2, escaped: true, inCharClass: false, startsCharClass: false, endsCharClass: false },
|
43
|
-
* { text: "c", index: 4, escaped: false, inCharClass: true, startsCharClass: true, endsCharClass: false },
|
44
|
-
* { text: "d", index: 5, escaped: false, inCharClass: true, startsCharClass: false, endsCharClass: false },
|
45
|
-
* { text: "-", index: 6, escaped: false, inCharClass: true, startsCharClass: false, endsCharClass: false }
|
46
|
-
* ];
|
47
|
-
*
|
36
|
+
/*
|
37
|
+
* Set of characters that require escaping in character classes in `unicodeSets` mode.
|
38
|
+
* ( ) [ ] { } / - \ | are ClassSetSyntaxCharacter
|
48
39
|
*/
|
49
|
-
|
50
|
-
const charList = [];
|
40
|
+
const REGEX_CLASSSET_CHARACTER_ESCAPES = union(REGEX_GENERAL_ESCAPES, new Set("q/[{}|()-"));
|
51
41
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
if (char === "[" && !state.inCharClass) {
|
58
|
-
return Object.assign(state, { inCharClass: true, startingCharClass: true });
|
59
|
-
}
|
60
|
-
if (char === "]" && state.inCharClass) {
|
61
|
-
if (charList.length && charList[charList.length - 1].inCharClass) {
|
62
|
-
charList[charList.length - 1].endsCharClass = true;
|
63
|
-
}
|
64
|
-
return Object.assign(state, { inCharClass: false, startingCharClass: false });
|
65
|
-
}
|
66
|
-
}
|
67
|
-
charList.push({
|
68
|
-
text: char,
|
69
|
-
index,
|
70
|
-
escaped: state.escapeNextChar,
|
71
|
-
inCharClass: state.inCharClass,
|
72
|
-
startsCharClass: state.startingCharClass,
|
73
|
-
endsCharClass: false
|
74
|
-
});
|
75
|
-
return Object.assign(state, { escapeNextChar: false, startingCharClass: false });
|
76
|
-
}, { escapeNextChar: false, inCharClass: false, startingCharClass: false });
|
77
|
-
|
78
|
-
return charList;
|
79
|
-
}
|
42
|
+
/*
|
43
|
+
* A single character set of ClassSetReservedDoublePunctuator.
|
44
|
+
* && !! ## $$ %% ** ++ ,, .. :: ;; << == >> ?? @@ ^^ `` ~~ are ClassSetReservedDoublePunctuator
|
45
|
+
*/
|
46
|
+
const REGEX_CLASS_SET_RESERVED_DOUBLE_PUNCTUATOR = new Set("!#$%&*+,.:;<=>?@^`~");
|
80
47
|
|
81
48
|
/** @type {import('../shared/types').Rule} */
|
82
49
|
module.exports = {
|
@@ -94,6 +61,7 @@ module.exports = {
|
|
94
61
|
messages: {
|
95
62
|
unnecessaryEscape: "Unnecessary escape character: \\{{character}}.",
|
96
63
|
removeEscape: "Remove the `\\`. This maintains the current functionality.",
|
64
|
+
removeEscapeDoNotKeepSemantics: "Remove the `\\` if it was inserted by mistake.",
|
97
65
|
escapeBackslash: "Replace the `\\` with `\\\\` to include the actual backslash character."
|
98
66
|
},
|
99
67
|
|
@@ -102,15 +70,17 @@ module.exports = {
|
|
102
70
|
|
103
71
|
create(context) {
|
104
72
|
const sourceCode = context.sourceCode;
|
73
|
+
const parser = new RegExpParser();
|
105
74
|
|
106
75
|
/**
|
107
76
|
* Reports a node
|
108
77
|
* @param {ASTNode} node The node to report
|
109
78
|
* @param {number} startOffset The backslash's offset from the start of the node
|
110
79
|
* @param {string} character The uselessly escaped character (not including the backslash)
|
80
|
+
* @param {boolean} [disableEscapeBackslashSuggest] `true` if escapeBackslash suggestion should be turned off.
|
111
81
|
* @returns {void}
|
112
82
|
*/
|
113
|
-
function report(node, startOffset, character) {
|
83
|
+
function report(node, startOffset, character, disableEscapeBackslashSuggest) {
|
114
84
|
const rangeStart = node.range[0] + startOffset;
|
115
85
|
const range = [rangeStart, rangeStart + 1];
|
116
86
|
const start = sourceCode.getLocFromIndex(rangeStart);
|
@@ -125,17 +95,24 @@ module.exports = {
|
|
125
95
|
data: { character },
|
126
96
|
suggest: [
|
127
97
|
{
|
128
|
-
|
98
|
+
|
99
|
+
// Removing unnecessary `\` characters in a directive is not guaranteed to maintain functionality.
|
100
|
+
messageId: astUtils.isDirective(node.parent)
|
101
|
+
? "removeEscapeDoNotKeepSemantics" : "removeEscape",
|
129
102
|
fix(fixer) {
|
130
103
|
return fixer.removeRange(range);
|
131
104
|
}
|
132
105
|
},
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
106
|
+
...disableEscapeBackslashSuggest
|
107
|
+
? []
|
108
|
+
: [
|
109
|
+
{
|
110
|
+
messageId: "escapeBackslash",
|
111
|
+
fix(fixer) {
|
112
|
+
return fixer.insertTextBeforeRange(range, "\\");
|
113
|
+
}
|
114
|
+
}
|
115
|
+
]
|
139
116
|
]
|
140
117
|
});
|
141
118
|
}
|
@@ -178,6 +155,133 @@ module.exports = {
|
|
178
155
|
}
|
179
156
|
}
|
180
157
|
|
158
|
+
/**
|
159
|
+
* Checks if the escape character in given regexp is unnecessary.
|
160
|
+
* @private
|
161
|
+
* @param {ASTNode} node node to validate.
|
162
|
+
* @returns {void}
|
163
|
+
*/
|
164
|
+
function validateRegExp(node) {
|
165
|
+
const { pattern, flags } = node.regex;
|
166
|
+
let patternNode;
|
167
|
+
const unicode = flags.includes("u");
|
168
|
+
const unicodeSets = flags.includes("v");
|
169
|
+
|
170
|
+
try {
|
171
|
+
patternNode = parser.parsePattern(pattern, 0, pattern.length, { unicode, unicodeSets });
|
172
|
+
} catch {
|
173
|
+
|
174
|
+
// Ignore regular expressions with syntax errors
|
175
|
+
return;
|
176
|
+
}
|
177
|
+
|
178
|
+
/** @type {(CharacterClass | ExpressionCharacterClass)[]} */
|
179
|
+
const characterClassStack = [];
|
180
|
+
|
181
|
+
visitRegExpAST(patternNode, {
|
182
|
+
onCharacterClassEnter: characterClassNode => characterClassStack.unshift(characterClassNode),
|
183
|
+
onCharacterClassLeave: () => characterClassStack.shift(),
|
184
|
+
onExpressionCharacterClassEnter: characterClassNode => characterClassStack.unshift(characterClassNode),
|
185
|
+
onExpressionCharacterClassLeave: () => characterClassStack.shift(),
|
186
|
+
onCharacterEnter(characterNode) {
|
187
|
+
if (!characterNode.raw.startsWith("\\")) {
|
188
|
+
|
189
|
+
// It's not an escaped character.
|
190
|
+
return;
|
191
|
+
}
|
192
|
+
|
193
|
+
const escapedChar = characterNode.raw.slice(1);
|
194
|
+
|
195
|
+
if (escapedChar !== String.fromCodePoint(characterNode.value)) {
|
196
|
+
|
197
|
+
// It's a valid escape.
|
198
|
+
return;
|
199
|
+
}
|
200
|
+
let allowedEscapes;
|
201
|
+
|
202
|
+
if (characterClassStack.length) {
|
203
|
+
allowedEscapes = unicodeSets ? REGEX_CLASSSET_CHARACTER_ESCAPES : REGEX_GENERAL_ESCAPES;
|
204
|
+
} else {
|
205
|
+
allowedEscapes = REGEX_NON_CHARCLASS_ESCAPES;
|
206
|
+
}
|
207
|
+
if (allowedEscapes.has(escapedChar)) {
|
208
|
+
return;
|
209
|
+
}
|
210
|
+
|
211
|
+
const reportedIndex = characterNode.start + 1;
|
212
|
+
let disableEscapeBackslashSuggest = false;
|
213
|
+
|
214
|
+
if (characterClassStack.length) {
|
215
|
+
const characterClassNode = characterClassStack[0];
|
216
|
+
|
217
|
+
if (escapedChar === "^") {
|
218
|
+
|
219
|
+
/*
|
220
|
+
* The '^' character is also a special case; it must always be escaped outside of character classes, but
|
221
|
+
* it only needs to be escaped in character classes if it's at the beginning of the character class. To
|
222
|
+
* account for this, consider it to be a valid escape character outside of character classes, and filter
|
223
|
+
* out '^' characters that appear at the start of a character class.
|
224
|
+
*/
|
225
|
+
if (characterClassNode.start + 1 === characterNode.start) {
|
226
|
+
|
227
|
+
return;
|
228
|
+
}
|
229
|
+
}
|
230
|
+
if (!unicodeSets) {
|
231
|
+
if (escapedChar === "-") {
|
232
|
+
|
233
|
+
/*
|
234
|
+
* The '-' character is a special case, because it's only valid to escape it if it's in a character
|
235
|
+
* class, and is not at either edge of the character class. To account for this, don't consider '-'
|
236
|
+
* characters to be valid in general, and filter out '-' characters that appear in the middle of a
|
237
|
+
* character class.
|
238
|
+
*/
|
239
|
+
if (characterClassNode.start + 1 !== characterNode.start && characterNode.end !== characterClassNode.end - 1) {
|
240
|
+
|
241
|
+
return;
|
242
|
+
}
|
243
|
+
}
|
244
|
+
} else { // unicodeSets mode
|
245
|
+
if (REGEX_CLASS_SET_RESERVED_DOUBLE_PUNCTUATOR.has(escapedChar)) {
|
246
|
+
|
247
|
+
// Escaping is valid if it is a ClassSetReservedDoublePunctuator.
|
248
|
+
if (pattern[characterNode.end] === escapedChar) {
|
249
|
+
return;
|
250
|
+
}
|
251
|
+
if (pattern[characterNode.start - 1] === escapedChar) {
|
252
|
+
if (escapedChar !== "^") {
|
253
|
+
return;
|
254
|
+
}
|
255
|
+
|
256
|
+
// If the previous character is a `negate` caret(`^`), escape to caret is unnecessary.
|
257
|
+
|
258
|
+
if (!characterClassNode.negate) {
|
259
|
+
return;
|
260
|
+
}
|
261
|
+
const negateCaretIndex = characterClassNode.start + 1;
|
262
|
+
|
263
|
+
if (negateCaretIndex < characterNode.start - 1) {
|
264
|
+
return;
|
265
|
+
}
|
266
|
+
}
|
267
|
+
}
|
268
|
+
|
269
|
+
if (characterNode.parent.type === "ClassIntersection" || characterNode.parent.type === "ClassSubtraction") {
|
270
|
+
disableEscapeBackslashSuggest = true;
|
271
|
+
}
|
272
|
+
}
|
273
|
+
}
|
274
|
+
|
275
|
+
report(
|
276
|
+
node,
|
277
|
+
reportedIndex,
|
278
|
+
escapedChar,
|
279
|
+
disableEscapeBackslashSuggest
|
280
|
+
);
|
281
|
+
}
|
282
|
+
});
|
283
|
+
}
|
284
|
+
|
181
285
|
/**
|
182
286
|
* Checks if a node has an escape.
|
183
287
|
* @param {ASTNode} node node to check.
|
@@ -216,32 +320,7 @@ module.exports = {
|
|
216
320
|
validateString(node, match);
|
217
321
|
}
|
218
322
|
} else if (node.regex) {
|
219
|
-
|
220
|
-
|
221
|
-
/*
|
222
|
-
* The '-' character is a special case, because it's only valid to escape it if it's in a character
|
223
|
-
* class, and is not at either edge of the character class. To account for this, don't consider '-'
|
224
|
-
* characters to be valid in general, and filter out '-' characters that appear in the middle of a
|
225
|
-
* character class.
|
226
|
-
*/
|
227
|
-
.filter(charInfo => !(charInfo.text === "-" && charInfo.inCharClass && !charInfo.startsCharClass && !charInfo.endsCharClass))
|
228
|
-
|
229
|
-
/*
|
230
|
-
* The '^' character is also a special case; it must always be escaped outside of character classes, but
|
231
|
-
* it only needs to be escaped in character classes if it's at the beginning of the character class. To
|
232
|
-
* account for this, consider it to be a valid escape character outside of character classes, and filter
|
233
|
-
* out '^' characters that appear at the start of a character class.
|
234
|
-
*/
|
235
|
-
.filter(charInfo => !(charInfo.text === "^" && charInfo.startsCharClass))
|
236
|
-
|
237
|
-
// Filter out characters that aren't escaped.
|
238
|
-
.filter(charInfo => charInfo.escaped)
|
239
|
-
|
240
|
-
// Filter out characters that are valid to escape, based on their position in the regular expression.
|
241
|
-
.filter(charInfo => !(charInfo.inCharClass ? REGEX_GENERAL_ESCAPES : REGEX_NON_CHARCLASS_ESCAPES).has(charInfo.text))
|
242
|
-
|
243
|
-
// Report all the remaining characters.
|
244
|
-
.forEach(charInfo => report(node, charInfo.index, charInfo.text));
|
323
|
+
validateRegExp(node);
|
245
324
|
}
|
246
325
|
|
247
326
|
}
|
@@ -130,42 +130,6 @@ function isBlockLikeStatement(sourceCode, node) {
|
|
130
130
|
);
|
131
131
|
}
|
132
132
|
|
133
|
-
/**
|
134
|
-
* Check whether the given node is a directive or not.
|
135
|
-
* @param {ASTNode} node The node to check.
|
136
|
-
* @param {SourceCode} sourceCode The source code object to get tokens.
|
137
|
-
* @returns {boolean} `true` if the node is a directive.
|
138
|
-
*/
|
139
|
-
function isDirective(node, sourceCode) {
|
140
|
-
return (
|
141
|
-
astUtils.isTopLevelExpressionStatement(node) &&
|
142
|
-
node.expression.type === "Literal" &&
|
143
|
-
typeof node.expression.value === "string" &&
|
144
|
-
!astUtils.isParenthesised(sourceCode, node.expression)
|
145
|
-
);
|
146
|
-
}
|
147
|
-
|
148
|
-
/**
|
149
|
-
* Check whether the given node is a part of directive prologue or not.
|
150
|
-
* @param {ASTNode} node The node to check.
|
151
|
-
* @param {SourceCode} sourceCode The source code object to get tokens.
|
152
|
-
* @returns {boolean} `true` if the node is a part of directive prologue.
|
153
|
-
*/
|
154
|
-
function isDirectivePrologue(node, sourceCode) {
|
155
|
-
if (isDirective(node, sourceCode)) {
|
156
|
-
for (const sibling of node.parent.body) {
|
157
|
-
if (sibling === node) {
|
158
|
-
break;
|
159
|
-
}
|
160
|
-
if (!isDirective(sibling, sourceCode)) {
|
161
|
-
return false;
|
162
|
-
}
|
163
|
-
}
|
164
|
-
return true;
|
165
|
-
}
|
166
|
-
return false;
|
167
|
-
}
|
168
|
-
|
169
133
|
/**
|
170
134
|
* Gets the actual last token.
|
171
135
|
*
|
@@ -359,12 +323,10 @@ const StatementTypes = {
|
|
359
323
|
CJS_IMPORT.test(sourceCode.getText(node.declarations[0].init))
|
360
324
|
},
|
361
325
|
directive: {
|
362
|
-
test:
|
326
|
+
test: astUtils.isDirective
|
363
327
|
},
|
364
328
|
expression: {
|
365
|
-
test: (node
|
366
|
-
node.type === "ExpressionStatement" &&
|
367
|
-
!isDirectivePrologue(node, sourceCode)
|
329
|
+
test: node => node.type === "ExpressionStatement" && !astUtils.isDirective(node)
|
368
330
|
},
|
369
331
|
iife: {
|
370
332
|
test: isIIFEStatement
|
@@ -375,10 +337,10 @@ const StatementTypes = {
|
|
375
337
|
isBlockLikeStatement(sourceCode, node)
|
376
338
|
},
|
377
339
|
"multiline-expression": {
|
378
|
-
test:
|
340
|
+
test: node =>
|
379
341
|
node.loc.start.line !== node.loc.end.line &&
|
380
342
|
node.type === "ExpressionStatement" &&
|
381
|
-
!
|
343
|
+
!astUtils.isDirective(node)
|
382
344
|
},
|
383
345
|
|
384
346
|
"multiline-const": newMultilineKeywordTester("const"),
|
@@ -112,14 +112,17 @@ module.exports = {
|
|
112
112
|
* @param {string} pattern The regular expression pattern to be checked.
|
113
113
|
* @param {ASTNode} node AST node which contains the regular expression or a call/new expression.
|
114
114
|
* @param {ASTNode} regexNode AST node which contains the regular expression.
|
115
|
-
* @param {
|
115
|
+
* @param {string|null} flags The regular expression flags to be checked.
|
116
116
|
* @returns {void}
|
117
117
|
*/
|
118
|
-
function checkRegex(pattern, node, regexNode,
|
118
|
+
function checkRegex(pattern, node, regexNode, flags) {
|
119
119
|
let ast;
|
120
120
|
|
121
121
|
try {
|
122
|
-
ast = parser.parsePattern(pattern, 0, pattern.length,
|
122
|
+
ast = parser.parsePattern(pattern, 0, pattern.length, {
|
123
|
+
unicode: Boolean(flags && flags.includes("u")),
|
124
|
+
unicodeSets: Boolean(flags && flags.includes("v"))
|
125
|
+
});
|
123
126
|
} catch {
|
124
127
|
|
125
128
|
// ignore regex syntax errors
|
@@ -148,7 +151,7 @@ module.exports = {
|
|
148
151
|
return {
|
149
152
|
Literal(node) {
|
150
153
|
if (node.regex) {
|
151
|
-
checkRegex(node.regex.pattern, node, node, node.regex.flags
|
154
|
+
checkRegex(node.regex.pattern, node, node, node.regex.flags);
|
152
155
|
}
|
153
156
|
},
|
154
157
|
Program(node) {
|
@@ -166,7 +169,7 @@ module.exports = {
|
|
166
169
|
const flags = getStringIfConstant(refNode.arguments[1]);
|
167
170
|
|
168
171
|
if (regex) {
|
169
|
-
checkRegex(regex, refNode, refNode.arguments[0], flags
|
172
|
+
checkRegex(regex, refNode, refNode.arguments[0], flags);
|
170
173
|
}
|
171
174
|
}
|
172
175
|
}
|
@@ -241,7 +241,7 @@ module.exports = {
|
|
241
241
|
/**
|
242
242
|
* Returns a ecmaVersion compatible for regexpp.
|
243
243
|
* @param {number} ecmaVersion The ecmaVersion to convert.
|
244
|
-
* @returns {import("regexpp/ecma-versions").EcmaVersion} The resulting ecmaVersion compatible for regexpp.
|
244
|
+
* @returns {import("@eslint-community/regexpp/ecma-versions").EcmaVersion} The resulting ecmaVersion compatible for regexpp.
|
245
245
|
*/
|
246
246
|
function getRegexppEcmaVersion(ecmaVersion) {
|
247
247
|
if (ecmaVersion <= 5) {
|
@@ -297,7 +297,10 @@ module.exports = {
|
|
297
297
|
const validator = new RegExpValidator({ ecmaVersion: regexppEcmaVersion });
|
298
298
|
|
299
299
|
try {
|
300
|
-
validator.validatePattern(pattern, 0, pattern.length,
|
300
|
+
validator.validatePattern(pattern, 0, pattern.length, {
|
301
|
+
unicode: flags ? flags.includes("u") : false,
|
302
|
+
unicodeSets: flags ? flags.includes("v") : false
|
303
|
+
});
|
301
304
|
if (flags) {
|
302
305
|
validator.validateFlags(flags);
|
303
306
|
}
|
@@ -461,7 +464,10 @@ module.exports = {
|
|
461
464
|
if (regexContent && !noFix) {
|
462
465
|
let charIncrease = 0;
|
463
466
|
|
464
|
-
const ast = new RegExpParser({ ecmaVersion: regexppEcmaVersion }).parsePattern(regexContent, 0, regexContent.length,
|
467
|
+
const ast = new RegExpParser({ ecmaVersion: regexppEcmaVersion }).parsePattern(regexContent, 0, regexContent.length, {
|
468
|
+
unicode: flags ? flags.includes("u") : false,
|
469
|
+
unicodeSets: flags ? flags.includes("v") : false
|
470
|
+
});
|
465
471
|
|
466
472
|
visitRegExpAST(ast, {
|
467
473
|
onCharacterEnter(characterNode) {
|
@@ -28,7 +28,7 @@ module.exports = {
|
|
28
28
|
type: "suggestion",
|
29
29
|
|
30
30
|
docs: {
|
31
|
-
description: "Enforce the use of `u` flag on RegExp",
|
31
|
+
description: "Enforce the use of `u` or `v` flag on RegExp",
|
32
32
|
recommended: false,
|
33
33
|
url: "https://eslint.org/docs/latest/rules/require-unicode-regexp"
|
34
34
|
},
|
@@ -51,7 +51,7 @@ module.exports = {
|
|
51
51
|
"Literal[regex]"(node) {
|
52
52
|
const flags = node.regex.flags || "";
|
53
53
|
|
54
|
-
if (!flags.includes("u")) {
|
54
|
+
if (!flags.includes("u") && !flags.includes("v")) {
|
55
55
|
context.report({
|
56
56
|
messageId: "requireUFlag",
|
57
57
|
node,
|
@@ -85,7 +85,7 @@ module.exports = {
|
|
85
85
|
const pattern = getStringIfConstant(patternNode, scope);
|
86
86
|
const flags = getStringIfConstant(flagsNode, scope);
|
87
87
|
|
88
|
-
if (!flagsNode || (typeof flags === "string" && !flags.includes("u"))) {
|
88
|
+
if (!flagsNode || (typeof flags === "string" && !flags.includes("u") && !flags.includes("v"))) {
|
89
89
|
context.report({
|
90
90
|
messageId: "requireUFlag",
|
91
91
|
node: refNode,
|
@@ -1006,6 +1006,15 @@ function isTopLevelExpressionStatement(node) {
|
|
1006
1006
|
|
1007
1007
|
}
|
1008
1008
|
|
1009
|
+
/**
|
1010
|
+
* Check whether the given node is a part of a directive prologue or not.
|
1011
|
+
* @param {ASTNode} node The node to check.
|
1012
|
+
* @returns {boolean} `true` if the node is a part of directive prologue.
|
1013
|
+
*/
|
1014
|
+
function isDirective(node) {
|
1015
|
+
return node.type === "ExpressionStatement" && typeof node.directive === "string";
|
1016
|
+
}
|
1017
|
+
|
1009
1018
|
//------------------------------------------------------------------------------
|
1010
1019
|
// Public Interface
|
1011
1020
|
//------------------------------------------------------------------------------
|
@@ -2158,5 +2167,6 @@ module.exports = {
|
|
2158
2167
|
getSwitchCaseColonToken,
|
2159
2168
|
getModuleExportName,
|
2160
2169
|
isConstant,
|
2161
|
-
isTopLevelExpressionStatement
|
2170
|
+
isTopLevelExpressionStatement,
|
2171
|
+
isDirective
|
2162
2172
|
};
|
@@ -8,7 +8,7 @@
|
|
8
8
|
|
9
9
|
const { RegExpValidator } = require("@eslint-community/regexpp");
|
10
10
|
|
11
|
-
const REGEXPP_LATEST_ECMA_VERSION =
|
11
|
+
const REGEXPP_LATEST_ECMA_VERSION = 2024;
|
12
12
|
|
13
13
|
/**
|
14
14
|
* Checks if the given regular expression pattern would be valid with the `u` flag.
|
@@ -28,7 +28,7 @@ function isValidWithUnicodeFlag(ecmaVersion, pattern) {
|
|
28
28
|
});
|
29
29
|
|
30
30
|
try {
|
31
|
-
validator.validatePattern(pattern, void 0, void 0, /* uFlag = */ true);
|
31
|
+
validator.validatePattern(pattern, void 0, void 0, { unicode: /* uFlag = */ true });
|
32
32
|
} catch {
|
33
33
|
return false;
|
34
34
|
}
|
package/lib/unsupported-api.js
CHANGED
@@ -14,6 +14,7 @@
|
|
14
14
|
const { FileEnumerator } = require("./cli-engine/file-enumerator");
|
15
15
|
const { FlatESLint, shouldUseFlatConfig } = require("./eslint/flat-eslint");
|
16
16
|
const FlatRuleTester = require("./rule-tester/flat-rule-tester");
|
17
|
+
const { ESLint } = require("./eslint/eslint");
|
17
18
|
|
18
19
|
//-----------------------------------------------------------------------------
|
19
20
|
// Exports
|
@@ -24,5 +25,6 @@ module.exports = {
|
|
24
25
|
FlatESLint,
|
25
26
|
shouldUseFlatConfig,
|
26
27
|
FlatRuleTester,
|
27
|
-
FileEnumerator
|
28
|
+
FileEnumerator,
|
29
|
+
LegacyESLint: ESLint
|
28
30
|
};
|
@@ -0,0 +1,98 @@
|
|
1
|
+
"use strict";
|
2
|
+
|
3
|
+
/* eslint consistent-return: 0 -- no default case */
|
4
|
+
|
5
|
+
const messages = {
|
6
|
+
|
7
|
+
env: `
|
8
|
+
A config object is using the "env" key, which is not supported in flat config system.
|
9
|
+
|
10
|
+
Flat config uses "languageOptions.globals" to define global variables for your files.
|
11
|
+
|
12
|
+
Please see the following page for information on how to convert your config object into the correct format:
|
13
|
+
https://eslint.org/docs/latest/use/configure/migration-guide#configuring-language-options
|
14
|
+
`,
|
15
|
+
|
16
|
+
extends: `
|
17
|
+
A config object is using the "extends" key, which is not supported in flat config system.
|
18
|
+
|
19
|
+
Instead of "extends", you can include config objects that you'd like to extend from directly in the flat config array.
|
20
|
+
|
21
|
+
Please see the following page for more information:
|
22
|
+
https://eslint.org/docs/latest/use/configure/migration-guide#predefined-configs
|
23
|
+
`,
|
24
|
+
|
25
|
+
globals: `
|
26
|
+
A config object is using the "globals" key, which is not supported in flat config system.
|
27
|
+
|
28
|
+
Flat config uses "languageOptions.globals" to define global variables for your files.
|
29
|
+
|
30
|
+
Please see the following page for information on how to convert your config object into the correct format:
|
31
|
+
https://eslint.org/docs/latest/use/configure/migration-guide#configuring-language-options
|
32
|
+
`,
|
33
|
+
|
34
|
+
ignorePatterns: `
|
35
|
+
A config object is using the "ignorePatterns" key, which is not supported in flat config system.
|
36
|
+
|
37
|
+
Flat config uses "ignores" to specify files to ignore.
|
38
|
+
|
39
|
+
Please see the following page for information on how to convert your config object into the correct format:
|
40
|
+
https://eslint.org/docs/latest/use/configure/migration-guide#ignoring-files
|
41
|
+
`,
|
42
|
+
|
43
|
+
noInlineConfig: `
|
44
|
+
A config object is using the "noInlineConfig" key, which is not supported in flat config system.
|
45
|
+
|
46
|
+
Flat config uses "linterOptions.noInlineConfig" to specify files to ignore.
|
47
|
+
|
48
|
+
Please see the following page for information on how to convert your config object into the correct format:
|
49
|
+
https://eslint.org/docs/latest/use/configure/migration-guide#linter-options
|
50
|
+
`,
|
51
|
+
|
52
|
+
overrides: `
|
53
|
+
A config object is using the "overrides" key, which is not supported in flat config system.
|
54
|
+
|
55
|
+
Flat config is an array that acts like the eslintrc "overrides" array.
|
56
|
+
|
57
|
+
Please see the following page for information on how to convert your config object into the correct format:
|
58
|
+
https://eslint.org/docs/latest/use/configure/migration-guide#glob-based-configs
|
59
|
+
`,
|
60
|
+
|
61
|
+
parser: `
|
62
|
+
A config object is using the "parser" key, which is not supported in flat config system.
|
63
|
+
|
64
|
+
Flat config uses "languageOptions.parser" to override the default parser.
|
65
|
+
|
66
|
+
Please see the following page for information on how to convert your config object into the correct format:
|
67
|
+
https://eslint.org/docs/latest/use/configure/migration-guide#custom-parsers
|
68
|
+
`,
|
69
|
+
|
70
|
+
parserOptions: `
|
71
|
+
A config object is using the "parserOptions" key, which is not supported in flat config system.
|
72
|
+
|
73
|
+
Flat config uses "languageOptions.parserOptions" to specify parser options.
|
74
|
+
|
75
|
+
Please see the following page for information on how to convert your config object into the correct format:
|
76
|
+
https://eslint.org/docs/latest/use/configure/migration-guide#configuring-language-options
|
77
|
+
`,
|
78
|
+
|
79
|
+
reportUnusedDisableDirectives: `
|
80
|
+
A config object is using the "reportUnusedDisableDirectives" key, which is not supported in flat config system.
|
81
|
+
|
82
|
+
Flat config uses "linterOptions.reportUnusedDisableDirectives" to specify files to ignore.
|
83
|
+
|
84
|
+
Please see the following page for information on how to convert your config object into the correct format:
|
85
|
+
https://eslint.org/docs/latest/use/configure/migration-guide#linter-options
|
86
|
+
`,
|
87
|
+
|
88
|
+
root: `
|
89
|
+
A config object is using the "root" key, which is not supported in flat config system.
|
90
|
+
|
91
|
+
Flat configs always act as if they are the root config file, so this key can be safely removed.
|
92
|
+
`
|
93
|
+
};
|
94
|
+
|
95
|
+
module.exports = function({ key }) {
|
96
|
+
|
97
|
+
return messages[key].trim();
|
98
|
+
};
|
@@ -0,0 +1,24 @@
|
|
1
|
+
"use strict";
|
2
|
+
|
3
|
+
module.exports = function({ plugins }) {
|
4
|
+
|
5
|
+
const isArrayOfStrings = typeof plugins[0] === "string";
|
6
|
+
|
7
|
+
return `
|
8
|
+
A config object has a "plugins" key defined as an array${isArrayOfStrings ? " of strings" : ""}.
|
9
|
+
|
10
|
+
Flat config requires "plugins" to be an object in this form:
|
11
|
+
|
12
|
+
{
|
13
|
+
plugins: {
|
14
|
+
${isArrayOfStrings && plugins[0] ? plugins[0] : "namespace"}: pluginObject
|
15
|
+
}
|
16
|
+
}
|
17
|
+
|
18
|
+
Please see the following page for information on how to convert your config object into the correct format:
|
19
|
+
https://eslint.org/docs/latest/use/configure/migration-guide#importing-plugins-and-custom-parsers
|
20
|
+
|
21
|
+
If you're using a shareable config that you cannot rewrite in flat config format, then use the compatibility utility:
|
22
|
+
https://eslint.org/docs/latest/use/configure/migration-guide#using-eslintrc-configs-in-flat-config
|
23
|
+
`;
|
24
|
+
};
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "eslint",
|
3
|
-
"version": "8.
|
3
|
+
"version": "8.46.0",
|
4
4
|
"author": "Nicholas C. Zakas <nicholas+npm@nczconsulting.com>",
|
5
5
|
"description": "An AST-based pattern checker for JavaScript.",
|
6
6
|
"bin": {
|
@@ -61,21 +61,21 @@
|
|
61
61
|
"bugs": "https://github.com/eslint/eslint/issues/",
|
62
62
|
"dependencies": {
|
63
63
|
"@eslint-community/eslint-utils": "^4.2.0",
|
64
|
-
"@eslint-community/regexpp": "^4.
|
65
|
-
"@eslint/eslintrc": "^2.1.
|
66
|
-
"@eslint/js": "8.
|
64
|
+
"@eslint-community/regexpp": "^4.6.1",
|
65
|
+
"@eslint/eslintrc": "^2.1.1",
|
66
|
+
"@eslint/js": "^8.46.0",
|
67
67
|
"@humanwhocodes/config-array": "^0.11.10",
|
68
68
|
"@humanwhocodes/module-importer": "^1.0.1",
|
69
69
|
"@nodelib/fs.walk": "^1.2.8",
|
70
|
-
"ajv": "^6.
|
70
|
+
"ajv": "^6.12.4",
|
71
71
|
"chalk": "^4.0.0",
|
72
72
|
"cross-spawn": "^7.0.2",
|
73
73
|
"debug": "^4.3.2",
|
74
74
|
"doctrine": "^3.0.0",
|
75
75
|
"escape-string-regexp": "^4.0.0",
|
76
|
-
"eslint-scope": "^7.2.
|
77
|
-
"eslint-visitor-keys": "^3.4.
|
78
|
-
"espree": "^9.6.
|
76
|
+
"eslint-scope": "^7.2.2",
|
77
|
+
"eslint-visitor-keys": "^3.4.2",
|
78
|
+
"espree": "^9.6.1",
|
79
79
|
"esquery": "^1.4.2",
|
80
80
|
"esutils": "^2.0.2",
|
81
81
|
"fast-deep-equal": "^3.1.3",
|
@@ -85,7 +85,6 @@
|
|
85
85
|
"globals": "^13.19.0",
|
86
86
|
"graphemer": "^1.4.0",
|
87
87
|
"ignore": "^5.2.0",
|
88
|
-
"import-fresh": "^3.0.0",
|
89
88
|
"imurmurhash": "^0.1.4",
|
90
89
|
"is-glob": "^4.0.0",
|
91
90
|
"is-path-inside": "^3.0.3",
|
@@ -97,7 +96,6 @@
|
|
97
96
|
"natural-compare": "^1.4.0",
|
98
97
|
"optionator": "^0.9.3",
|
99
98
|
"strip-ansi": "^6.0.1",
|
100
|
-
"strip-json-comments": "^3.1.0",
|
101
99
|
"text-table": "^0.2.0"
|
102
100
|
},
|
103
101
|
"devDependencies": {
|
@@ -156,7 +154,6 @@
|
|
156
154
|
"semver": "^7.5.3",
|
157
155
|
"shelljs": "^0.8.2",
|
158
156
|
"sinon": "^11.0.0",
|
159
|
-
"temp": "^0.9.0",
|
160
157
|
"webpack": "^5.23.0",
|
161
158
|
"webpack-cli": "^4.5.0",
|
162
159
|
"yorkie": "^2.0.0"
|