eslint 9.6.0 → 9.8.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 +6 -7
- package/lib/cli.js +0 -20
- package/lib/languages/js/index.js +10 -5
- package/lib/languages/js/source-code/source-code.js +27 -2
- package/lib/linter/apply-disable-directives.js +30 -15
- package/lib/linter/linter.js +51 -23
- package/lib/linter/node-event-generator.js +2 -4
- package/lib/linter/report-translator.js +10 -10
- package/lib/linter/rule-fixer.js +38 -15
- package/lib/linter/vfile.js +7 -0
- package/lib/rules/no-restricted-imports.js +12 -5
- package/lib/rules/no-unused-vars.js +1 -1
- package/lib/rules/no-useless-backreference.js +81 -31
- package/lib/rules/utils/regular-expressions.js +1 -1
- package/lib/shared/flags.js +2 -2
- package/package.json +7 -5
package/README.md
CHANGED
@@ -39,7 +39,7 @@ ESLint is a tool for identifying and reporting on patterns found in ECMAScript/J
|
|
39
39
|
10. [License](#license)
|
40
40
|
11. [Team](#team)
|
41
41
|
12. [Sponsors](#sponsors)
|
42
|
-
13. [Technology Sponsors](#technology-sponsors)
|
42
|
+
13. [Technology Sponsors](#technology-sponsors) <!-- markdownlint-disable-line MD051 -->
|
43
43
|
|
44
44
|
## Installation and Usage
|
45
45
|
|
@@ -294,14 +294,13 @@ The following companies, organizations, and individuals support ESLint's ongoing
|
|
294
294
|
<!-- NOTE: This section is autogenerated. Do not manually edit.-->
|
295
295
|
<!--sponsorsstart-->
|
296
296
|
<h3>Platinum Sponsors</h3>
|
297
|
-
<p><a href="https://automattic.com"><img src="https://images.opencollective.com/automattic/d0ef3e1/logo.png" alt="Automattic" height="
|
297
|
+
<p><a href="https://automattic.com"><img src="https://images.opencollective.com/automattic/d0ef3e1/logo.png" alt="Automattic" height="128"></a> <a href="https://www.airbnb.com/"><img src="https://images.opencollective.com/airbnb/d327d66/logo.png" alt="Airbnb" height="128"></a></p><h3>Gold Sponsors</h3>
|
298
298
|
<p><a href="#"><img src="https://images.opencollective.com/guest-bf377e88/avatar.png" alt="Eli Schleifer" height="96"></a> <a href="https://engineering.salesforce.com"><img src="https://images.opencollective.com/salesforce/ca8f997/logo.png" alt="Salesforce" height="96"></a></p><h3>Silver Sponsors</h3>
|
299
299
|
<p><a href="https://www.jetbrains.com/"><img src="https://images.opencollective.com/jetbrains/fe76f99/logo.png" alt="JetBrains" 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> <a href="https://www.workleap.com"><img src="https://avatars.githubusercontent.com/u/53535748?u=d1e55d7661d724bf2281c1bfd33cb8f99fe2465f&v=4" alt="Workleap" height="64"></a></p><h3>Bronze Sponsors</h3>
|
300
300
|
<p><a href="https://www.notion.so"><img src="https://images.opencollective.com/notion/bf3b117/logo.png" alt="notion" 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" 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://www.ignitionapp.com"><img src="https://avatars.githubusercontent.com/u/5753491?v=4" alt="Ignition" height="32"></a> <a href="https://nx.dev"><img src="https://avatars.githubusercontent.com/u/23692104?v=4" alt="Nx" 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://usenextbase.com"><img src="https://avatars.githubusercontent.com/u/145838380?v=4" alt="Nextbase Starter Kit" height="32"></a></p>
|
301
301
|
<!--sponsorsend-->
|
302
302
|
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
* Password management is sponsored by [1Password](https://www.1password.com)
|
303
|
+
<!--techsponsorsstart-->
|
304
|
+
<h2>Technology Sponsors</h2>
|
305
|
+
<p><a href="https://netlify.com"><img src="https://raw.githubusercontent.com/eslint/eslint.org/main/src/assets/images/techsponsors/netlify-icon.svg" alt="Netlify" height="32"></a> <a href="https://algolia.com"><img src="https://raw.githubusercontent.com/eslint/eslint.org/main/src/assets/images/techsponsors/algolia-icon.svg" alt="Algolia" height="32"></a> <a href="https://1password.com"><img src="https://raw.githubusercontent.com/eslint/eslint.org/main/src/assets/images/techsponsors/1password-icon.svg" alt="1Password" height="32"></a></p>
|
306
|
+
<!--techsponsorsend-->
|
package/lib/cli.js
CHANGED
@@ -26,7 +26,6 @@ const fs = require("node:fs"),
|
|
26
26
|
{ normalizeSeverityToString } = require("./shared/severity");
|
27
27
|
const { Legacy: { naming } } = require("@eslint/eslintrc");
|
28
28
|
const { ModuleImporter } = require("@humanwhocodes/module-importer");
|
29
|
-
const { inactiveFlags, activeFlags } = require("./shared/flags");
|
30
29
|
const debug = require("debug")("eslint:cli");
|
31
30
|
|
32
31
|
//------------------------------------------------------------------------------
|
@@ -488,25 +487,6 @@ const cli = {
|
|
488
487
|
|
489
488
|
const ActiveESLint = usingFlatConfig ? ESLint : LegacyESLint;
|
490
489
|
const eslintOptions = await translateOptions(options, usingFlatConfig ? "flat" : "eslintrc");
|
491
|
-
|
492
|
-
if (eslintOptions.flags) {
|
493
|
-
debug("Checking for inactive flags");
|
494
|
-
|
495
|
-
for (const flag of eslintOptions.flags) {
|
496
|
-
if (inactiveFlags.has(flag)) {
|
497
|
-
log.warn(`InactiveFlag: The '${flag}' flag is no longer active: ${inactiveFlags.get(flag)}`);
|
498
|
-
continue;
|
499
|
-
}
|
500
|
-
|
501
|
-
if (activeFlags.has(flag)) {
|
502
|
-
continue;
|
503
|
-
}
|
504
|
-
|
505
|
-
log.error(`InvalidFlag: The '${flag}' flag is invalid.`);
|
506
|
-
return 2;
|
507
|
-
}
|
508
|
-
}
|
509
|
-
|
510
490
|
const engine = new ActiveESLint(eslintOptions);
|
511
491
|
let results;
|
512
492
|
|
@@ -20,7 +20,9 @@ const { validateLanguageOptions } = require("./validate-language-options");
|
|
20
20
|
// Type Definitions
|
21
21
|
//-----------------------------------------------------------------------------
|
22
22
|
|
23
|
-
/** @typedef {import("
|
23
|
+
/** @typedef {import("@eslint/core").File} File */
|
24
|
+
/** @typedef {import("@eslint/core").Language} Language */
|
25
|
+
/** @typedef {import("@eslint/core").OkParseResult} OkParseResult */
|
24
26
|
|
25
27
|
//-----------------------------------------------------------------------------
|
26
28
|
// Helpers
|
@@ -56,6 +58,9 @@ function analyzeScope(ast, languageOptions, visitorKeys) {
|
|
56
58
|
// Exports
|
57
59
|
//-----------------------------------------------------------------------------
|
58
60
|
|
61
|
+
/**
|
62
|
+
* @type {Language}
|
63
|
+
*/
|
59
64
|
module.exports = {
|
60
65
|
|
61
66
|
fileType: "text",
|
@@ -143,7 +148,7 @@ module.exports = {
|
|
143
148
|
|
144
149
|
/**
|
145
150
|
* Parses the given file into an AST.
|
146
|
-
* @param {
|
151
|
+
* @param {File} file The virtual file to parse.
|
147
152
|
* @param {Object} options Additional options passed from ESLint.
|
148
153
|
* @param {LanguageOptions} options.languageOptions The language options.
|
149
154
|
* @returns {Object} The result of parsing.
|
@@ -200,7 +205,7 @@ module.exports = {
|
|
200
205
|
} catch (ex) {
|
201
206
|
|
202
207
|
// If the message includes a leading line number, strip it:
|
203
|
-
const message =
|
208
|
+
const message = ex.message.replace(/^line \d+:/iu, "").trim();
|
204
209
|
|
205
210
|
debug("%s\n%s", message, ex.stack);
|
206
211
|
|
@@ -218,8 +223,8 @@ module.exports = {
|
|
218
223
|
|
219
224
|
/**
|
220
225
|
* Creates a new `SourceCode` object from the given information.
|
221
|
-
* @param {
|
222
|
-
* @param {
|
226
|
+
* @param {File} file The virtual file to create a `SourceCode` object from.
|
227
|
+
* @param {OkParseResult} parseResult The result returned from `parse()`.
|
223
228
|
* @param {Object} options Additional options passed from ESLint.
|
224
229
|
* @param {LanguageOptions} options.languageOptions The language options.
|
225
230
|
* @returns {SourceCode} The new `SourceCode` object.
|
@@ -29,6 +29,10 @@ const
|
|
29
29
|
//------------------------------------------------------------------------------
|
30
30
|
|
31
31
|
/** @typedef {import("eslint-scope").Variable} Variable */
|
32
|
+
/** @typedef {import("eslint-scope").Scope} Scope */
|
33
|
+
/** @typedef {import("@eslint/core").SourceCode} ISourceCode */
|
34
|
+
/** @typedef {import("@eslint/core").Directive} IDirective */
|
35
|
+
/** @typedef {import("@eslint/core").TraversalStep} ITraversalStep */
|
32
36
|
|
33
37
|
//------------------------------------------------------------------------------
|
34
38
|
// Private
|
@@ -373,6 +377,7 @@ class TraversalStep {
|
|
373
377
|
|
374
378
|
/**
|
375
379
|
* A class to represent a directive comment.
|
380
|
+
* @implements {IDirective}
|
376
381
|
*/
|
377
382
|
class Directive {
|
378
383
|
|
@@ -429,12 +434,13 @@ const caches = Symbol("caches");
|
|
429
434
|
|
430
435
|
/**
|
431
436
|
* Represents parsed source code.
|
437
|
+
* @implements {ISourceCode}
|
432
438
|
*/
|
433
439
|
class SourceCode extends TokenStore {
|
434
440
|
|
435
441
|
/**
|
436
442
|
* The cache of steps that were taken while traversing the source code.
|
437
|
-
* @type {Array<
|
443
|
+
* @type {Array<ITraversalStep>}
|
438
444
|
*/
|
439
445
|
#steps;
|
440
446
|
|
@@ -838,7 +844,7 @@ class SourceCode extends TokenStore {
|
|
838
844
|
/**
|
839
845
|
* Gets the scope for the given node
|
840
846
|
* @param {ASTNode} currentNode The node to get the scope of
|
841
|
-
* @returns {
|
847
|
+
* @returns {Scope} The scope information for this node
|
842
848
|
* @throws {TypeError} If the `currentNode` argument is missing.
|
843
849
|
*/
|
844
850
|
getScope(currentNode) {
|
@@ -910,6 +916,25 @@ class SourceCode extends TokenStore {
|
|
910
916
|
|
911
917
|
return ancestorsStartingAtParent.reverse();
|
912
918
|
}
|
919
|
+
|
920
|
+
/**
|
921
|
+
* Returns the locatin of the given node or token.
|
922
|
+
* @param {ASTNode|Token} nodeOrToken The node or token to get the location of.
|
923
|
+
* @returns {SourceLocation} The location of the node or token.
|
924
|
+
*/
|
925
|
+
getLoc(nodeOrToken) {
|
926
|
+
return nodeOrToken.loc;
|
927
|
+
}
|
928
|
+
|
929
|
+
/**
|
930
|
+
* Returns the range of the given node or token.
|
931
|
+
* @param {ASTNode|Token} nodeOrToken The node or token to get the range of.
|
932
|
+
* @returns {[number, number]} The range of the node or token.
|
933
|
+
*/
|
934
|
+
getRange(nodeOrToken) {
|
935
|
+
return nodeOrToken.range;
|
936
|
+
}
|
937
|
+
|
913
938
|
/* eslint-enable class-methods-use-this -- node is owned by SourceCode */
|
914
939
|
|
915
940
|
/**
|
@@ -10,6 +10,9 @@
|
|
10
10
|
//------------------------------------------------------------------------------
|
11
11
|
|
12
12
|
/** @typedef {import("../shared/types").LintMessage} LintMessage */
|
13
|
+
/** @typedef {import("@eslint/core").Language} Language */
|
14
|
+
/** @typedef {import("@eslint/core").Position} Position */
|
15
|
+
/** @typedef {import("@eslint/core").RulesConfig} RulesConfig */
|
13
16
|
|
14
17
|
//------------------------------------------------------------------------------
|
15
18
|
// Module Definition
|
@@ -24,8 +27,8 @@ const {
|
|
24
27
|
|
25
28
|
/**
|
26
29
|
* Compares the locations of two objects in a source file
|
27
|
-
* @param {
|
28
|
-
* @param {
|
30
|
+
* @param {Position} itemA The first object
|
31
|
+
* @param {Position} itemB The second object
|
29
32
|
* @returns {number} A value less than 1 if itemA appears before itemB in the source file, greater than 1 if
|
30
33
|
* itemA appears after itemB in the source file, or 0 if itemA and itemB have the same location.
|
31
34
|
*/
|
@@ -58,15 +61,18 @@ function groupByParentDirective(directives) {
|
|
58
61
|
* Creates removal details for a set of directives within the same comment.
|
59
62
|
* @param {Directive[]} directives Unused directives to be removed.
|
60
63
|
* @param {Token} node The backing Comment token.
|
64
|
+
* @param {SourceCode} sourceCode The source code object for the file being linted.
|
61
65
|
* @returns {{ description, fix, unprocessedDirective }[]} Details for later creation of output Problems.
|
62
66
|
*/
|
63
|
-
function createIndividualDirectivesRemoval(directives, node) {
|
67
|
+
function createIndividualDirectivesRemoval(directives, node, sourceCode) {
|
68
|
+
|
69
|
+
const range = sourceCode.getRange(node);
|
64
70
|
|
65
71
|
/*
|
66
72
|
* `node.value` starts right after `//` or `/*`.
|
67
73
|
* All calculated offsets will be relative to this index.
|
68
74
|
*/
|
69
|
-
const commentValueStart =
|
75
|
+
const commentValueStart = range[0] + "//".length;
|
70
76
|
|
71
77
|
// Find where the list of rules starts. `\S+` matches with the directive name (e.g. `eslint-disable-line`)
|
72
78
|
const listStartOffset = /^\s*\S+\s+/u.exec(node.value)[0].length;
|
@@ -162,10 +168,11 @@ function createIndividualDirectivesRemoval(directives, node) {
|
|
162
168
|
* Creates a description of deleting an entire unused disable directive.
|
163
169
|
* @param {Directive[]} directives Unused directives to be removed.
|
164
170
|
* @param {Token} node The backing Comment token.
|
171
|
+
* @param {SourceCode} sourceCode The source code object for the file being linted.
|
165
172
|
* @returns {{ description, fix, unprocessedDirective }} Details for later creation of an output problem.
|
166
173
|
*/
|
167
|
-
function createDirectiveRemoval(directives, node) {
|
168
|
-
const
|
174
|
+
function createDirectiveRemoval(directives, node, sourceCode) {
|
175
|
+
const range = sourceCode.getRange(node);
|
169
176
|
const ruleIds = directives.filter(directive => directive.ruleId).map(directive => `'${directive.ruleId}'`);
|
170
177
|
|
171
178
|
return {
|
@@ -183,9 +190,10 @@ function createDirectiveRemoval(directives, node) {
|
|
183
190
|
/**
|
184
191
|
* Parses details from directives to create output Problems.
|
185
192
|
* @param {Iterable<Directive>} allDirectives Unused directives to be removed.
|
193
|
+
* @param {SourceCode} sourceCode The source code object for the file being linted.
|
186
194
|
* @returns {{ description, fix, unprocessedDirective }[]} Details for later creation of output Problems.
|
187
195
|
*/
|
188
|
-
function processUnusedDirectives(allDirectives) {
|
196
|
+
function processUnusedDirectives(allDirectives, sourceCode) {
|
189
197
|
const directiveGroups = groupByParentDirective(allDirectives);
|
190
198
|
|
191
199
|
return directiveGroups.flatMap(
|
@@ -198,8 +206,8 @@ function processUnusedDirectives(allDirectives) {
|
|
198
206
|
}
|
199
207
|
|
200
208
|
return remainingRuleIds.size
|
201
|
-
? createIndividualDirectivesRemoval(directives, parentDirective.node)
|
202
|
-
: [createDirectiveRemoval(directives, parentDirective.node)];
|
209
|
+
? createIndividualDirectivesRemoval(directives, parentDirective.node, sourceCode)
|
210
|
+
: [createDirectiveRemoval(directives, parentDirective.node, sourceCode)];
|
203
211
|
}
|
204
212
|
);
|
205
213
|
}
|
@@ -306,6 +314,7 @@ function collectUsedEnableDirectives(directives) {
|
|
306
314
|
function applyDirectives(options) {
|
307
315
|
const problems = [];
|
308
316
|
const usedDisableDirectives = new Set();
|
317
|
+
const { sourceCode } = options;
|
309
318
|
|
310
319
|
for (const problem of options.problems) {
|
311
320
|
let disableDirectivesForProblem = [];
|
@@ -367,8 +376,8 @@ function applyDirectives(options) {
|
|
367
376
|
}
|
368
377
|
}
|
369
378
|
|
370
|
-
const processed = processUnusedDirectives(unusedDisableDirectivesToReport)
|
371
|
-
.concat(processUnusedDirectives(unusedEnableDirectivesToReport));
|
379
|
+
const processed = processUnusedDirectives(unusedDisableDirectivesToReport, sourceCode)
|
380
|
+
.concat(processUnusedDirectives(unusedEnableDirectivesToReport, sourceCode));
|
372
381
|
const columnOffset = options.language.columnStart === 1 ? 0 : 1;
|
373
382
|
const lineOffset = options.language.lineStart === 1 ? 0 : 1;
|
374
383
|
|
@@ -387,11 +396,14 @@ function applyDirectives(options) {
|
|
387
396
|
? `Unused eslint-disable directive (no problems were reported from ${description}).`
|
388
397
|
: "Unused eslint-disable directive (no problems were reported).";
|
389
398
|
}
|
399
|
+
|
400
|
+
const loc = sourceCode.getLoc(parentDirective.node);
|
401
|
+
|
390
402
|
return {
|
391
403
|
ruleId: null,
|
392
404
|
message,
|
393
|
-
line: type === "disable-next-line" ?
|
394
|
-
column: type === "disable-next-line" ?
|
405
|
+
line: type === "disable-next-line" ? loc.start.line + lineOffset : line,
|
406
|
+
column: type === "disable-next-line" ? loc.start.column + columnOffset : column,
|
395
407
|
severity: options.reportUnusedDisableDirectives === "warn" ? 1 : 2,
|
396
408
|
nodeType: null,
|
397
409
|
...options.disableFixes ? {} : { fix }
|
@@ -406,6 +418,7 @@ function applyDirectives(options) {
|
|
406
418
|
* of reported problems, adds the suppression information to the problems.
|
407
419
|
* @param {Object} options Information about directives and problems
|
408
420
|
* @param {Language} options.language The language being linted.
|
421
|
+
* @param {SourceCode} options.sourceCode The source code object for the file being linted.
|
409
422
|
* @param {{
|
410
423
|
* type: ("disable"|"enable"|"disable-line"|"disable-next-line"),
|
411
424
|
* ruleId: (string|null),
|
@@ -418,13 +431,13 @@ function applyDirectives(options) {
|
|
418
431
|
* @param {{ruleId: (string|null), line: number, column: number}[]} options.problems
|
419
432
|
* A list of problems reported by rules, sorted by increasing location in the file, with one-based columns.
|
420
433
|
* @param {"off" | "warn" | "error"} options.reportUnusedDisableDirectives If `"warn"` or `"error"`, adds additional problems for unused directives
|
421
|
-
* @param {
|
434
|
+
* @param {RulesConfig} options.configuredRules The rules configuration.
|
422
435
|
* @param {Function} options.ruleFilter A predicate function to filter which rules should be executed.
|
423
436
|
* @param {boolean} options.disableFixes If true, it doesn't make `fix` properties.
|
424
437
|
* @returns {{ruleId: (string|null), line: number, column: number, suppressions?: {kind: string, justification: string}}[]}
|
425
438
|
* An object with a list of reported problems, the suppressed of which contain the suppression information.
|
426
439
|
*/
|
427
|
-
module.exports = ({ language, directives, disableFixes, problems, configuredRules, ruleFilter, reportUnusedDisableDirectives = "off" }) => {
|
440
|
+
module.exports = ({ language, sourceCode, directives, disableFixes, problems, configuredRules, ruleFilter, reportUnusedDisableDirectives = "off" }) => {
|
428
441
|
const blockDirectives = directives
|
429
442
|
.filter(directive => directive.type === "disable" || directive.type === "enable")
|
430
443
|
.map(directive => Object.assign({}, directive, { unprocessedDirective: directive }))
|
@@ -474,6 +487,7 @@ module.exports = ({ language, directives, disableFixes, problems, configuredRule
|
|
474
487
|
|
475
488
|
const blockDirectivesResult = applyDirectives({
|
476
489
|
language,
|
490
|
+
sourceCode,
|
477
491
|
problems,
|
478
492
|
directives: blockDirectives,
|
479
493
|
disableFixes,
|
@@ -482,6 +496,7 @@ module.exports = ({ language, directives, disableFixes, problems, configuredRule
|
|
482
496
|
});
|
483
497
|
const lineDirectivesResult = applyDirectives({
|
484
498
|
language,
|
499
|
+
sourceCode,
|
485
500
|
problems: blockDirectivesResult.problems,
|
486
501
|
directives: lineDirectives,
|
487
502
|
disableFixes,
|
package/lib/linter/linter.js
CHANGED
@@ -45,7 +45,7 @@ const { RuleValidator } = require("../config/rule-validator");
|
|
45
45
|
const { assertIsRuleSeverity } = require("../config/flat-config-schema");
|
46
46
|
const { normalizeSeverityToString } = require("../shared/severity");
|
47
47
|
const jslang = require("../languages/js");
|
48
|
-
const { activeFlags } = require("../shared/flags");
|
48
|
+
const { activeFlags, inactiveFlags } = require("../shared/flags");
|
49
49
|
const debug = require("debug")("eslint:linter");
|
50
50
|
const MAX_AUTOFIX_PASSES = 10;
|
51
51
|
const DEFAULT_PARSER_NAME = "espree";
|
@@ -72,6 +72,10 @@ const STEP_KIND_CALL = 2;
|
|
72
72
|
/** @typedef {import("../shared/types").Processor} Processor */
|
73
73
|
/** @typedef {import("../shared/types").Rule} Rule */
|
74
74
|
/** @typedef {import("../shared/types").Times} Times */
|
75
|
+
/** @typedef {import("@eslint/core").Language} Language */
|
76
|
+
/** @typedef {import("@eslint/core").RuleSeverity} RuleSeverity */
|
77
|
+
/** @typedef {import("@eslint/core").RuleConfig} RuleConfig */
|
78
|
+
|
75
79
|
|
76
80
|
/* eslint-disable jsdoc/valid-types -- https://github.com/jsdoc-type-pratt-parser/jsdoc-type-pratt-parser/issues/4#issuecomment-778805577 */
|
77
81
|
/**
|
@@ -276,7 +280,7 @@ function updateLocationInformation({ line, column, endLine, endColumn }, languag
|
|
276
280
|
* @param {string} [options.ruleId] the ruleId to report
|
277
281
|
* @param {Object} [options.loc] the loc to report
|
278
282
|
* @param {string} [options.message] the error message to report
|
279
|
-
* @param {
|
283
|
+
* @param {RuleSeverity} [options.severity] the error message to report
|
280
284
|
* @param {Language} [options.language] the language to use to adjust the location information
|
281
285
|
* @returns {LintMessage} created problem, returns a missing-rule problem if only provided ruleId.
|
282
286
|
* @private
|
@@ -319,9 +323,10 @@ function createLintingProblem(options) {
|
|
319
323
|
* @param {ASTNode|token} options.node The Comment node/token.
|
320
324
|
* @param {function(string): {create: Function}} ruleMapper A map from rule IDs to defined rules
|
321
325
|
* @param {Language} language The language to use to adjust the location information.
|
326
|
+
* @param {SourceCode} sourceCode The SourceCode object to get comments from.
|
322
327
|
* @returns {Object} Directives and problems from the comment
|
323
328
|
*/
|
324
|
-
function createDisableDirectives({ type, value, justification, node }, ruleMapper, language) {
|
329
|
+
function createDisableDirectives({ type, value, justification, node }, ruleMapper, language, sourceCode) {
|
325
330
|
const ruleIds = Object.keys(commentParser.parseListConfig(value));
|
326
331
|
const directiveRules = ruleIds.length ? ruleIds : [null];
|
327
332
|
const result = {
|
@@ -332,11 +337,15 @@ function createDisableDirectives({ type, value, justification, node }, ruleMappe
|
|
332
337
|
|
333
338
|
for (const ruleId of directiveRules) {
|
334
339
|
|
340
|
+
const loc = sourceCode.getLoc(node);
|
341
|
+
|
335
342
|
// push to directives, if the rule is defined(including null, e.g. /*eslint enable*/)
|
336
343
|
if (ruleId === null || !!ruleMapper(ruleId)) {
|
344
|
+
|
345
|
+
|
337
346
|
if (type === "disable-next-line") {
|
338
347
|
const { line, column } = updateLocationInformation(
|
339
|
-
|
348
|
+
loc.end,
|
340
349
|
language
|
341
350
|
);
|
342
351
|
|
@@ -350,7 +359,7 @@ function createDisableDirectives({ type, value, justification, node }, ruleMappe
|
|
350
359
|
});
|
351
360
|
} else {
|
352
361
|
const { line, column } = updateLocationInformation(
|
353
|
-
|
362
|
+
loc.start,
|
354
363
|
language
|
355
364
|
);
|
356
365
|
|
@@ -364,7 +373,7 @@ function createDisableDirectives({ type, value, justification, node }, ruleMappe
|
|
364
373
|
});
|
365
374
|
}
|
366
375
|
} else {
|
367
|
-
result.directiveProblems.push(createLintingProblem({ ruleId, loc
|
376
|
+
result.directiveProblems.push(createLintingProblem({ ruleId, loc, language }));
|
368
377
|
}
|
369
378
|
}
|
370
379
|
return result;
|
@@ -406,25 +415,27 @@ function getDirectiveComments(sourceCode, ruleMapper, warnInlineConfig, config)
|
|
406
415
|
return;
|
407
416
|
}
|
408
417
|
|
418
|
+
const loc = sourceCode.getLoc(comment);
|
419
|
+
|
409
420
|
if (warnInlineConfig) {
|
410
421
|
const kind = comment.type === "Block" ? `/*${directiveText}*/` : `//${directiveText}`;
|
411
422
|
|
412
423
|
problems.push(createLintingProblem({
|
413
424
|
ruleId: null,
|
414
425
|
message: `'${kind}' has no effect because you have 'noInlineConfig' setting in ${warnInlineConfig}.`,
|
415
|
-
loc
|
426
|
+
loc,
|
416
427
|
severity: 1
|
417
428
|
}));
|
418
429
|
return;
|
419
430
|
}
|
420
431
|
|
421
|
-
if (directiveText === "eslint-disable-line" &&
|
432
|
+
if (directiveText === "eslint-disable-line" && loc.start.line !== loc.end.line) {
|
422
433
|
const message = `${directiveText} comment should not span multiple lines.`;
|
423
434
|
|
424
435
|
problems.push(createLintingProblem({
|
425
436
|
ruleId: null,
|
426
437
|
message,
|
427
|
-
loc
|
438
|
+
loc
|
428
439
|
}));
|
429
440
|
return;
|
430
441
|
}
|
@@ -442,7 +453,7 @@ function getDirectiveComments(sourceCode, ruleMapper, warnInlineConfig, config)
|
|
442
453
|
value: directiveValue,
|
443
454
|
justification: justificationPart,
|
444
455
|
node: comment
|
445
|
-
}, ruleMapper, jslang);
|
456
|
+
}, ruleMapper, jslang, sourceCode);
|
446
457
|
|
447
458
|
disableDirectives.push(...directives);
|
448
459
|
problems.push(...directiveProblems);
|
@@ -463,7 +474,7 @@ function getDirectiveComments(sourceCode, ruleMapper, warnInlineConfig, config)
|
|
463
474
|
} catch (err) {
|
464
475
|
problems.push(createLintingProblem({
|
465
476
|
ruleId: null,
|
466
|
-
loc
|
477
|
+
loc,
|
467
478
|
message: err.message
|
468
479
|
}));
|
469
480
|
continue;
|
@@ -490,14 +501,14 @@ function getDirectiveComments(sourceCode, ruleMapper, warnInlineConfig, config)
|
|
490
501
|
const ruleValue = parseResult.config[name];
|
491
502
|
|
492
503
|
if (!rule) {
|
493
|
-
problems.push(createLintingProblem({ ruleId: name, loc
|
504
|
+
problems.push(createLintingProblem({ ruleId: name, loc }));
|
494
505
|
return;
|
495
506
|
}
|
496
507
|
|
497
508
|
if (Object.hasOwn(configuredRules, name)) {
|
498
509
|
problems.push(createLintingProblem({
|
499
510
|
message: `Rule "${name}" is already configured by another configuration comment in the preceding code. This configuration is ignored.`,
|
500
|
-
loc
|
511
|
+
loc
|
501
512
|
}));
|
502
513
|
return;
|
503
514
|
}
|
@@ -559,7 +570,7 @@ function getDirectiveComments(sourceCode, ruleMapper, warnInlineConfig, config)
|
|
559
570
|
problems.push(createLintingProblem({
|
560
571
|
ruleId: name,
|
561
572
|
message: err.message,
|
562
|
-
loc
|
573
|
+
loc
|
563
574
|
}));
|
564
575
|
|
565
576
|
// do not apply the config, if found invalid options.
|
@@ -571,7 +582,7 @@ function getDirectiveComments(sourceCode, ruleMapper, warnInlineConfig, config)
|
|
571
582
|
} else {
|
572
583
|
const problem = createLintingProblem({
|
573
584
|
ruleId: null,
|
574
|
-
loc
|
585
|
+
loc,
|
575
586
|
message: parseResult.error.message
|
576
587
|
});
|
577
588
|
|
@@ -619,7 +630,7 @@ function getDirectiveCommentsForFlatConfig(sourceCode, ruleMapper, language) {
|
|
619
630
|
})));
|
620
631
|
|
621
632
|
directivesSources.forEach(directive => {
|
622
|
-
const { directives, directiveProblems } = createDisableDirectives(directive, ruleMapper, language);
|
633
|
+
const { directives, directiveProblems } = createDisableDirectives(directive, ruleMapper, language, sourceCode);
|
623
634
|
|
624
635
|
disableDirectives.push(...directives);
|
625
636
|
problems.push(...directiveProblems);
|
@@ -877,7 +888,7 @@ function storeTime(time, timeOpts, slots) {
|
|
877
888
|
|
878
889
|
/**
|
879
890
|
* Get the options for a rule (not including severity), if any
|
880
|
-
* @param {
|
891
|
+
* @param {RuleConfig} ruleConfig rule configuration
|
881
892
|
* @returns {Array} of rule options, empty Array if none
|
882
893
|
*/
|
883
894
|
function getRuleOptions(ruleConfig) {
|
@@ -941,7 +952,7 @@ function parse(file, language, languageOptions) {
|
|
941
952
|
nodeType: null,
|
942
953
|
fatal: true,
|
943
954
|
severity: 2,
|
944
|
-
message: error.message
|
955
|
+
message: `Parsing error: ${error.message}`,
|
945
956
|
line: error.line,
|
946
957
|
column: error.column
|
947
958
|
}))
|
@@ -1278,9 +1289,20 @@ class Linter {
|
|
1278
1289
|
* @param {"flat"|"eslintrc"} [config.configType="flat"] the type of config used.
|
1279
1290
|
*/
|
1280
1291
|
constructor({ cwd, configType = "flat", flags = [] } = {}) {
|
1292
|
+
|
1293
|
+
flags.forEach(flag => {
|
1294
|
+
if (inactiveFlags.has(flag)) {
|
1295
|
+
throw new Error(`The flag '${flag}' is inactive: ${inactiveFlags.get(flag)}`);
|
1296
|
+
}
|
1297
|
+
|
1298
|
+
if (!activeFlags.has(flag)) {
|
1299
|
+
throw new Error(`Unknown flag '${flag}'.`);
|
1300
|
+
}
|
1301
|
+
});
|
1302
|
+
|
1281
1303
|
internalSlotsMap.set(this, {
|
1282
1304
|
cwd: normalizeCwd(cwd),
|
1283
|
-
flags
|
1305
|
+
flags,
|
1284
1306
|
lastConfigArray: null,
|
1285
1307
|
lastSourceCode: null,
|
1286
1308
|
lastSuppressedMessages: [],
|
@@ -1458,7 +1480,7 @@ class Linter {
|
|
1458
1480
|
debug("An error occurred while traversing");
|
1459
1481
|
debug("Filename:", options.filename);
|
1460
1482
|
if (err.currentNode) {
|
1461
|
-
const { line } = err.currentNode.
|
1483
|
+
const { line } = sourceCode.getLoc(err.currentNode).start;
|
1462
1484
|
|
1463
1485
|
debug("Line:", line);
|
1464
1486
|
err.message += `:${line}`;
|
@@ -1476,6 +1498,7 @@ class Linter {
|
|
1476
1498
|
|
1477
1499
|
return applyDisableDirectives({
|
1478
1500
|
language: jslang,
|
1501
|
+
sourceCode,
|
1479
1502
|
directives: commentDirectives.disableDirectives,
|
1480
1503
|
disableFixes: options.disableFixes,
|
1481
1504
|
problems: lintingProblems
|
@@ -1755,10 +1778,14 @@ class Linter {
|
|
1755
1778
|
if (options.warnInlineConfig) {
|
1756
1779
|
if (sourceCode.getInlineConfigNodes) {
|
1757
1780
|
sourceCode.getInlineConfigNodes().forEach(node => {
|
1781
|
+
|
1782
|
+
const loc = sourceCode.getLoc(node);
|
1783
|
+
const range = sourceCode.getRange(node);
|
1784
|
+
|
1758
1785
|
inlineConfigProblems.push(createLintingProblem({
|
1759
1786
|
ruleId: null,
|
1760
|
-
message: `'${sourceCode.text.slice(
|
1761
|
-
loc
|
1787
|
+
message: `'${sourceCode.text.slice(range[0], range[1])}' has no effect because you have 'noInlineConfig' setting in ${options.warnInlineConfig}.`,
|
1788
|
+
loc,
|
1762
1789
|
severity: 1,
|
1763
1790
|
language: config.language
|
1764
1791
|
}));
|
@@ -1938,7 +1965,7 @@ class Linter {
|
|
1938
1965
|
debug("An error occurred while traversing");
|
1939
1966
|
debug("Filename:", options.filename);
|
1940
1967
|
if (err.currentNode) {
|
1941
|
-
const { line } = err.currentNode.
|
1968
|
+
const { line } = sourceCode.getLoc(err.currentNode).start;
|
1942
1969
|
|
1943
1970
|
debug("Line:", line);
|
1944
1971
|
err.message += `:${line}`;
|
@@ -1957,6 +1984,7 @@ class Linter {
|
|
1957
1984
|
|
1958
1985
|
return applyDisableDirectives({
|
1959
1986
|
language: config.language,
|
1987
|
+
sourceCode,
|
1960
1988
|
directives: commentDirectives.disableDirectives,
|
1961
1989
|
disableFixes: options.disableFixes,
|
1962
1990
|
problems: lintingProblems
|
@@ -334,10 +334,8 @@ class NodeEventGenerator {
|
|
334
334
|
* @returns {void}
|
335
335
|
*/
|
336
336
|
enterNode(node) {
|
337
|
-
if (node.parent) {
|
338
|
-
this.currentAncestry.unshift(node.parent);
|
339
|
-
}
|
340
337
|
this.applySelectors(node, false);
|
338
|
+
this.currentAncestry.unshift(node);
|
341
339
|
}
|
342
340
|
|
343
341
|
/**
|
@@ -346,8 +344,8 @@ class NodeEventGenerator {
|
|
346
344
|
* @returns {void}
|
347
345
|
*/
|
348
346
|
leaveNode(node) {
|
349
|
-
this.applySelectors(node, true);
|
350
347
|
this.currentAncestry.shift();
|
348
|
+
this.applySelectors(node, true);
|
351
349
|
}
|
352
350
|
}
|
353
351
|
|
@@ -10,7 +10,7 @@
|
|
10
10
|
//------------------------------------------------------------------------------
|
11
11
|
|
12
12
|
const assert = require("node:assert");
|
13
|
-
const
|
13
|
+
const { RuleFixer } = require("./rule-fixer");
|
14
14
|
const { interpolate } = require("./interpolate");
|
15
15
|
|
16
16
|
//------------------------------------------------------------------------------
|
@@ -91,13 +91,10 @@ function assertValidNodeInfo(descriptor) {
|
|
91
91
|
* from the `node` of the original descriptor, or infers the `start` from the `loc` of the original descriptor.
|
92
92
|
*/
|
93
93
|
function normalizeReportLoc(descriptor) {
|
94
|
-
if (descriptor.loc) {
|
95
|
-
|
96
|
-
return descriptor.loc;
|
97
|
-
}
|
98
|
-
return { start: descriptor.loc, end: null };
|
94
|
+
if (descriptor.loc.start) {
|
95
|
+
return descriptor.loc;
|
99
96
|
}
|
100
|
-
return descriptor.
|
97
|
+
return { start: descriptor.loc, end: null };
|
101
98
|
}
|
102
99
|
|
103
100
|
/**
|
@@ -190,6 +187,8 @@ function normalizeFixes(descriptor, sourceCode) {
|
|
190
187
|
return null;
|
191
188
|
}
|
192
189
|
|
190
|
+
const ruleFixer = new RuleFixer({ sourceCode });
|
191
|
+
|
193
192
|
// @type {null | Fix | Fix[] | IterableIterator<Fix>}
|
194
193
|
const fix = descriptor.fix(ruleFixer);
|
195
194
|
|
@@ -335,6 +334,7 @@ module.exports = function createReportTranslator(metadata) {
|
|
335
334
|
return (...args) => {
|
336
335
|
const descriptor = normalizeMultiArgReportCall(...args);
|
337
336
|
const messages = metadata.messageIds;
|
337
|
+
const { sourceCode } = metadata;
|
338
338
|
|
339
339
|
assertValidNodeInfo(descriptor);
|
340
340
|
|
@@ -367,9 +367,9 @@ module.exports = function createReportTranslator(metadata) {
|
|
367
367
|
node: descriptor.node,
|
368
368
|
message: interpolate(computedMessage, descriptor.data),
|
369
369
|
messageId: descriptor.messageId,
|
370
|
-
loc: normalizeReportLoc(descriptor),
|
371
|
-
fix: metadata.disableFixes ? null : normalizeFixes(descriptor,
|
372
|
-
suggestions: metadata.disableFixes ? [] : mapSuggestions(descriptor,
|
370
|
+
loc: descriptor.loc ? normalizeReportLoc(descriptor) : sourceCode.getLoc(descriptor.node),
|
371
|
+
fix: metadata.disableFixes ? null : normalizeFixes(descriptor, sourceCode),
|
372
|
+
suggestions: metadata.disableFixes ? [] : mapSuggestions(descriptor, sourceCode, messages),
|
373
373
|
language: metadata.language
|
374
374
|
});
|
375
375
|
};
|
package/lib/linter/rule-fixer.js
CHANGED
@@ -4,6 +4,8 @@
|
|
4
4
|
*/
|
5
5
|
"use strict";
|
6
6
|
|
7
|
+
/* eslint class-methods-use-this: off -- Methods desired on instance */
|
8
|
+
|
7
9
|
//------------------------------------------------------------------------------
|
8
10
|
// Requirements
|
9
11
|
//------------------------------------------------------------------------------
|
@@ -35,8 +37,22 @@ function insertTextAt(index, text) {
|
|
35
37
|
/**
|
36
38
|
* Creates code fixing commands for rules.
|
37
39
|
*/
|
40
|
+
class RuleFixer {
|
38
41
|
|
39
|
-
|
42
|
+
/**
|
43
|
+
* The source code object representing the text to be fixed.
|
44
|
+
* @type {SourceCode}
|
45
|
+
*/
|
46
|
+
#sourceCode;
|
47
|
+
|
48
|
+
/**
|
49
|
+
* Creates a new instance.
|
50
|
+
* @param {Object} options The options for the fixer.
|
51
|
+
* @param {SourceCode} options.sourceCode The source code object representing the text to be fixed.
|
52
|
+
*/
|
53
|
+
constructor({ sourceCode }) {
|
54
|
+
this.#sourceCode = sourceCode;
|
55
|
+
}
|
40
56
|
|
41
57
|
/**
|
42
58
|
* Creates a fix command that inserts text after the given node or token.
|
@@ -46,8 +62,10 @@ const ruleFixer = Object.freeze({
|
|
46
62
|
* @returns {Object} The fix command.
|
47
63
|
*/
|
48
64
|
insertTextAfter(nodeOrToken, text) {
|
49
|
-
|
50
|
-
|
65
|
+
const range = this.#sourceCode.getRange(nodeOrToken);
|
66
|
+
|
67
|
+
return this.insertTextAfterRange(range, text);
|
68
|
+
}
|
51
69
|
|
52
70
|
/**
|
53
71
|
* Creates a fix command that inserts text after the specified range in the source text.
|
@@ -59,7 +77,7 @@ const ruleFixer = Object.freeze({
|
|
59
77
|
*/
|
60
78
|
insertTextAfterRange(range, text) {
|
61
79
|
return insertTextAt(range[1], text);
|
62
|
-
}
|
80
|
+
}
|
63
81
|
|
64
82
|
/**
|
65
83
|
* Creates a fix command that inserts text before the given node or token.
|
@@ -69,8 +87,10 @@ const ruleFixer = Object.freeze({
|
|
69
87
|
* @returns {Object} The fix command.
|
70
88
|
*/
|
71
89
|
insertTextBefore(nodeOrToken, text) {
|
72
|
-
|
73
|
-
|
90
|
+
const range = this.#sourceCode.getRange(nodeOrToken);
|
91
|
+
|
92
|
+
return this.insertTextBeforeRange(range, text);
|
93
|
+
}
|
74
94
|
|
75
95
|
/**
|
76
96
|
* Creates a fix command that inserts text before the specified range in the source text.
|
@@ -82,7 +102,7 @@ const ruleFixer = Object.freeze({
|
|
82
102
|
*/
|
83
103
|
insertTextBeforeRange(range, text) {
|
84
104
|
return insertTextAt(range[0], text);
|
85
|
-
}
|
105
|
+
}
|
86
106
|
|
87
107
|
/**
|
88
108
|
* Creates a fix command that replaces text at the node or token.
|
@@ -92,8 +112,10 @@ const ruleFixer = Object.freeze({
|
|
92
112
|
* @returns {Object} The fix command.
|
93
113
|
*/
|
94
114
|
replaceText(nodeOrToken, text) {
|
95
|
-
|
96
|
-
|
115
|
+
const range = this.#sourceCode.getRange(nodeOrToken);
|
116
|
+
|
117
|
+
return this.replaceTextRange(range, text);
|
118
|
+
}
|
97
119
|
|
98
120
|
/**
|
99
121
|
* Creates a fix command that replaces text at the specified range in the source text.
|
@@ -108,7 +130,7 @@ const ruleFixer = Object.freeze({
|
|
108
130
|
range,
|
109
131
|
text
|
110
132
|
};
|
111
|
-
}
|
133
|
+
}
|
112
134
|
|
113
135
|
/**
|
114
136
|
* Creates a fix command that removes the node or token from the source.
|
@@ -117,8 +139,10 @@ const ruleFixer = Object.freeze({
|
|
117
139
|
* @returns {Object} The fix command.
|
118
140
|
*/
|
119
141
|
remove(nodeOrToken) {
|
120
|
-
|
121
|
-
|
142
|
+
const range = this.#sourceCode.getRange(nodeOrToken);
|
143
|
+
|
144
|
+
return this.removeRange(range);
|
145
|
+
}
|
122
146
|
|
123
147
|
/**
|
124
148
|
* Creates a fix command that removes the specified range of text from the source.
|
@@ -133,8 +157,7 @@ const ruleFixer = Object.freeze({
|
|
133
157
|
text: ""
|
134
158
|
};
|
135
159
|
}
|
136
|
-
|
137
|
-
});
|
160
|
+
}
|
138
161
|
|
139
162
|
|
140
|
-
module.exports =
|
163
|
+
module.exports = { RuleFixer };
|
package/lib/linter/vfile.js
CHANGED
@@ -5,6 +5,12 @@
|
|
5
5
|
|
6
6
|
"use strict";
|
7
7
|
|
8
|
+
//-----------------------------------------------------------------------------
|
9
|
+
// Type Definitions
|
10
|
+
//-----------------------------------------------------------------------------
|
11
|
+
|
12
|
+
/** @typedef {import("@eslint/core").File} File */
|
13
|
+
|
8
14
|
//------------------------------------------------------------------------------
|
9
15
|
// Helpers
|
10
16
|
//------------------------------------------------------------------------------
|
@@ -54,6 +60,7 @@ function stripUnicodeBOM(value) {
|
|
54
60
|
|
55
61
|
/**
|
56
62
|
* Represents a virtual file inside of ESLint.
|
63
|
+
* @implements {File}
|
57
64
|
*/
|
58
65
|
class VFile {
|
59
66
|
|
@@ -89,6 +89,9 @@ const arrayOfStringsOrObjectPatterns = {
|
|
89
89
|
minItems: 1,
|
90
90
|
uniqueItems: true
|
91
91
|
},
|
92
|
+
regex: {
|
93
|
+
type: "string"
|
94
|
+
},
|
92
95
|
importNamePattern: {
|
93
96
|
type: "string"
|
94
97
|
},
|
@@ -104,7 +107,6 @@ const arrayOfStringsOrObjectPatterns = {
|
|
104
107
|
}
|
105
108
|
},
|
106
109
|
additionalProperties: false,
|
107
|
-
required: ["group"],
|
108
110
|
not: {
|
109
111
|
anyOf: [
|
110
112
|
{ required: ["importNames", "allowImportNames"] },
|
@@ -113,7 +115,11 @@ const arrayOfStringsOrObjectPatterns = {
|
|
113
115
|
{ required: ["importNamePattern", "allowImportNames"] },
|
114
116
|
{ required: ["allowImportNames", "allowImportNamePattern"] }
|
115
117
|
]
|
116
|
-
}
|
118
|
+
},
|
119
|
+
oneOf: [
|
120
|
+
{ required: ["group"] },
|
121
|
+
{ required: ["regex"] }
|
122
|
+
]
|
117
123
|
},
|
118
124
|
uniqueItems: true
|
119
125
|
}
|
@@ -235,9 +241,10 @@ module.exports = {
|
|
235
241
|
|
236
242
|
// relative paths are supported for this rule
|
237
243
|
const restrictedPatternGroups = restrictedPatterns.map(
|
238
|
-
({ group, message, caseSensitive, importNames, importNamePattern, allowImportNames, allowImportNamePattern }) => (
|
244
|
+
({ group, regex, message, caseSensitive, importNames, importNamePattern, allowImportNames, allowImportNamePattern }) => (
|
239
245
|
{
|
240
|
-
matcher: ignore({ allowRelativePaths: true, ignorecase: !caseSensitive }).add(group),
|
246
|
+
...(group ? { matcher: ignore({ allowRelativePaths: true, ignorecase: !caseSensitive }).add(group) } : {}),
|
247
|
+
...(typeof regex === "string" ? { regexMatcher: new RegExp(regex, caseSensitive ? "u" : "iu") } : {}),
|
241
248
|
customMessage: message,
|
242
249
|
importNames,
|
243
250
|
importNamePattern,
|
@@ -493,7 +500,7 @@ module.exports = {
|
|
493
500
|
* @private
|
494
501
|
*/
|
495
502
|
function isRestrictedPattern(importSource, group) {
|
496
|
-
return group.matcher.ignores(importSource);
|
503
|
+
return group.regexMatcher ? group.regexMatcher.test(importSource) : group.matcher.ignores(importSource);
|
497
504
|
}
|
498
505
|
|
499
506
|
/**
|
@@ -340,7 +340,7 @@ module.exports = {
|
|
340
340
|
/**
|
341
341
|
* Determines if a variable has a sibling rest property
|
342
342
|
* @param {Variable} variable eslint-scope variable object.
|
343
|
-
* @returns {boolean} True if the variable
|
343
|
+
* @returns {boolean} True if the variable has a sibling rest property, false if not.
|
344
344
|
* @private
|
345
345
|
*/
|
346
346
|
function hasRestSpreadSibling(variable) {
|
@@ -72,11 +72,11 @@ module.exports = {
|
|
72
72
|
schema: [],
|
73
73
|
|
74
74
|
messages: {
|
75
|
-
nested: "Backreference '{{ bref }}' will be ignored. It references group '{{ group }}' from within that group.",
|
76
|
-
forward: "Backreference '{{ bref }}' will be ignored. It references group '{{ group }}' which appears later in the pattern.",
|
77
|
-
backward: "Backreference '{{ bref }}' will be ignored. It references group '{{ group }}' which appears before in the same lookbehind.",
|
78
|
-
disjunctive: "Backreference '{{ bref }}' will be ignored. It references group '{{ group }}' which is in another alternative.",
|
79
|
-
intoNegativeLookaround: "Backreference '{{ bref }}' will be ignored. It references group '{{ group }}' which is in a negative lookaround."
|
75
|
+
nested: "Backreference '{{ bref }}' will be ignored. It references group '{{ group }}'{{ otherGroups }} from within that group.",
|
76
|
+
forward: "Backreference '{{ bref }}' will be ignored. It references group '{{ group }}'{{ otherGroups }} which appears later in the pattern.",
|
77
|
+
backward: "Backreference '{{ bref }}' will be ignored. It references group '{{ group }}'{{ otherGroups }} which appears before in the same lookbehind.",
|
78
|
+
disjunctive: "Backreference '{{ bref }}' will be ignored. It references group '{{ group }}'{{ otherGroups }} which is in another alternative.",
|
79
|
+
intoNegativeLookaround: "Backreference '{{ bref }}' will be ignored. It references group '{{ group }}'{{ otherGroups }} which is in a negative lookaround."
|
80
80
|
}
|
81
81
|
},
|
82
82
|
|
@@ -104,16 +104,21 @@ module.exports = {
|
|
104
104
|
|
105
105
|
visitRegExpAST(regExpAST, {
|
106
106
|
onBackreferenceEnter(bref) {
|
107
|
-
const
|
108
|
-
brefPath = getPathToRoot(bref)
|
109
|
-
groupPath = getPathToRoot(group);
|
110
|
-
let messageId = null;
|
107
|
+
const groups = [bref.resolved].flat(),
|
108
|
+
brefPath = getPathToRoot(bref);
|
111
109
|
|
112
|
-
|
110
|
+
const problems = groups.map(group => {
|
111
|
+
const groupPath = getPathToRoot(group);
|
112
|
+
|
113
|
+
if (brefPath.includes(group)) {
|
114
|
+
|
115
|
+
// group is bref's ancestor => bref is nested ('nested reference') => group hasn't matched yet when bref starts to match.
|
116
|
+
return {
|
117
|
+
messageId: "nested",
|
118
|
+
group
|
119
|
+
};
|
120
|
+
}
|
113
121
|
|
114
|
-
// group is bref's ancestor => bref is nested ('nested reference') => group hasn't matched yet when bref starts to match.
|
115
|
-
messageId = "nested";
|
116
|
-
} else {
|
117
122
|
|
118
123
|
// Start from the root to find the lowest common ancestor.
|
119
124
|
let i = brefPath.length - 1,
|
@@ -130,35 +135,80 @@ module.exports = {
|
|
130
135
|
lowestCommonLookaround = commonPath.find(isLookaround),
|
131
136
|
isMatchingBackward = lowestCommonLookaround && lowestCommonLookaround.kind === "lookbehind";
|
132
137
|
|
138
|
+
if (groupCut.at(-1).type === "Alternative") {
|
139
|
+
|
140
|
+
// group's and bref's ancestor nodes below the lowest common ancestor are sibling alternatives => they're disjunctive.
|
141
|
+
return {
|
142
|
+
messageId: "disjunctive",
|
143
|
+
group
|
144
|
+
};
|
145
|
+
}
|
133
146
|
if (!isMatchingBackward && bref.end <= group.start) {
|
134
147
|
|
135
148
|
// bref is left, group is right ('forward reference') => group hasn't matched yet when bref starts to match.
|
136
|
-
|
137
|
-
|
149
|
+
return {
|
150
|
+
messageId: "forward",
|
151
|
+
group
|
152
|
+
};
|
153
|
+
}
|
154
|
+
if (isMatchingBackward && group.end <= bref.start) {
|
138
155
|
|
139
156
|
// the opposite of the previous when the regex is matching backward in a lookbehind context.
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
157
|
+
return {
|
158
|
+
messageId: "backward",
|
159
|
+
group
|
160
|
+
};
|
161
|
+
}
|
162
|
+
if (groupCut.some(isNegativeLookaround)) {
|
146
163
|
|
147
164
|
// group is in a negative lookaround which isn't bref's ancestor => group has already failed when bref starts to match.
|
148
|
-
|
165
|
+
return {
|
166
|
+
messageId: "intoNegativeLookaround",
|
167
|
+
group
|
168
|
+
};
|
149
169
|
}
|
170
|
+
|
171
|
+
return null;
|
172
|
+
});
|
173
|
+
|
174
|
+
if (problems.length === 0 || problems.some(problem => !problem)) {
|
175
|
+
|
176
|
+
// If there are no problems or no problems with any group then do not report it.
|
177
|
+
return;
|
150
178
|
}
|
151
179
|
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
180
|
+
let problemsToReport;
|
181
|
+
|
182
|
+
// Gets problems that appear in the same disjunction.
|
183
|
+
const problemsInSameDisjunction = problems.filter(problem => problem.messageId !== "disjunctive");
|
184
|
+
|
185
|
+
if (problemsInSameDisjunction.length) {
|
186
|
+
|
187
|
+
// Only report problems that appear in the same disjunction.
|
188
|
+
problemsToReport = problemsInSameDisjunction;
|
189
|
+
} else {
|
190
|
+
|
191
|
+
// If all groups appear in different disjunctions, report it.
|
192
|
+
problemsToReport = problems;
|
161
193
|
}
|
194
|
+
|
195
|
+
const [{ messageId, group }, ...other] = problemsToReport;
|
196
|
+
let otherGroups = "";
|
197
|
+
|
198
|
+
if (other.length === 1) {
|
199
|
+
otherGroups = " and another group";
|
200
|
+
} else if (other.length > 1) {
|
201
|
+
otherGroups = ` and other ${other.length} groups`;
|
202
|
+
}
|
203
|
+
context.report({
|
204
|
+
node,
|
205
|
+
messageId,
|
206
|
+
data: {
|
207
|
+
bref: bref.raw,
|
208
|
+
group: group.raw,
|
209
|
+
otherGroups
|
210
|
+
}
|
211
|
+
});
|
162
212
|
}
|
163
213
|
});
|
164
214
|
}
|
package/lib/shared/flags.js
CHANGED
@@ -9,7 +9,7 @@
|
|
9
9
|
* @type {Map<string, string>}
|
10
10
|
*/
|
11
11
|
const activeFlags = new Map([
|
12
|
-
["test_only", "
|
12
|
+
["test_only", "Used only for testing."]
|
13
13
|
]);
|
14
14
|
|
15
15
|
/**
|
@@ -17,7 +17,7 @@ const activeFlags = new Map([
|
|
17
17
|
* @type {Map<string, string>}
|
18
18
|
*/
|
19
19
|
const inactiveFlags = new Map([
|
20
|
-
["test_only_old", "
|
20
|
+
["test_only_old", "Used only for testing."]
|
21
21
|
]);
|
22
22
|
|
23
23
|
module.exports = {
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "eslint",
|
3
|
-
"version": "9.
|
3
|
+
"version": "9.8.0",
|
4
4
|
"author": "Nicholas C. Zakas <nicholas+npm@nczconsulting.com>",
|
5
5
|
"description": "An AST-based pattern checker for JavaScript.",
|
6
6
|
"bin": {
|
@@ -67,10 +67,10 @@
|
|
67
67
|
"bugs": "https://github.com/eslint/eslint/issues/",
|
68
68
|
"dependencies": {
|
69
69
|
"@eslint-community/eslint-utils": "^4.2.0",
|
70
|
-
"@eslint-community/regexpp": "^4.
|
71
|
-
"@eslint/config-array": "^0.17.
|
70
|
+
"@eslint-community/regexpp": "^4.11.0",
|
71
|
+
"@eslint/config-array": "^0.17.1",
|
72
72
|
"@eslint/eslintrc": "^3.1.0",
|
73
|
-
"@eslint/js": "9.
|
73
|
+
"@eslint/js": "9.8.0",
|
74
74
|
"@humanwhocodes/module-importer": "^1.0.1",
|
75
75
|
"@humanwhocodes/retry": "^0.3.0",
|
76
76
|
"@nodelib/fs.walk": "^1.2.8",
|
@@ -79,7 +79,7 @@
|
|
79
79
|
"cross-spawn": "^7.0.2",
|
80
80
|
"debug": "^4.3.2",
|
81
81
|
"escape-string-regexp": "^4.0.0",
|
82
|
-
"eslint-scope": "^8.0.
|
82
|
+
"eslint-scope": "^8.0.2",
|
83
83
|
"eslint-visitor-keys": "^4.0.0",
|
84
84
|
"espree": "^10.1.0",
|
85
85
|
"esquery": "^1.5.0",
|
@@ -104,6 +104,8 @@
|
|
104
104
|
"devDependencies": {
|
105
105
|
"@babel/core": "^7.4.3",
|
106
106
|
"@babel/preset-env": "^7.4.3",
|
107
|
+
"@eslint/core": "^0.2.0",
|
108
|
+
"@eslint/json": "^0.2.0",
|
107
109
|
"@types/estree": "^1.0.5",
|
108
110
|
"@types/node": "^20.11.5",
|
109
111
|
"@wdio/browser-runner": "^8.38.3",
|