eslint 8.57.0 → 9.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +31 -28
- package/bin/eslint.js +4 -3
- package/conf/ecma-version.js +16 -0
- package/conf/globals.js +1 -0
- package/conf/rule-type-list.json +3 -1
- package/lib/api.js +7 -11
- package/lib/cli-engine/cli-engine.js +14 -3
- package/lib/cli-engine/formatters/formatters-meta.json +1 -29
- package/lib/cli-engine/lint-result-cache.js +2 -2
- package/lib/cli.js +115 -36
- package/lib/config/default-config.js +3 -0
- package/lib/config/flat-config-array.js +110 -24
- package/lib/config/flat-config-helpers.js +41 -20
- package/lib/config/flat-config-schema.js +1 -7
- package/lib/config/rule-validator.js +42 -6
- package/lib/eslint/eslint-helpers.js +116 -58
- package/lib/eslint/eslint.js +892 -377
- package/lib/eslint/index.js +2 -2
- package/lib/eslint/legacy-eslint.js +728 -0
- package/lib/linter/apply-disable-directives.js +59 -31
- package/lib/linter/code-path-analysis/code-path-analyzer.js +0 -1
- package/lib/linter/code-path-analysis/code-path.js +32 -30
- package/lib/linter/code-path-analysis/fork-context.js +1 -1
- package/lib/linter/config-comment-parser.js +8 -11
- package/lib/linter/index.js +1 -3
- package/lib/linter/interpolate.js +24 -2
- package/lib/linter/linter.js +428 -207
- package/lib/linter/report-translator.js +3 -3
- package/lib/linter/rules.js +6 -15
- package/lib/linter/source-code-fixer.js +1 -1
- package/lib/linter/timing.js +16 -8
- package/lib/options.js +35 -3
- package/lib/rule-tester/index.js +3 -1
- package/lib/rule-tester/rule-tester.js +424 -347
- package/lib/rules/array-bracket-newline.js +1 -1
- package/lib/rules/array-bracket-spacing.js +1 -1
- package/lib/rules/block-scoped-var.js +1 -1
- package/lib/rules/callback-return.js +2 -2
- package/lib/rules/camelcase.js +3 -5
- package/lib/rules/capitalized-comments.js +10 -7
- package/lib/rules/comma-dangle.js +1 -1
- package/lib/rules/comma-style.js +2 -2
- package/lib/rules/complexity.js +14 -1
- package/lib/rules/constructor-super.js +99 -100
- package/lib/rules/default-case.js +1 -1
- package/lib/rules/eol-last.js +2 -2
- package/lib/rules/function-paren-newline.js +2 -2
- package/lib/rules/indent-legacy.js +5 -5
- package/lib/rules/indent.js +5 -5
- package/lib/rules/index.js +1 -2
- package/lib/rules/key-spacing.js +2 -2
- package/lib/rules/line-comment-position.js +1 -1
- package/lib/rules/lines-around-directive.js +2 -2
- package/lib/rules/max-depth.js +1 -1
- package/lib/rules/max-len.js +3 -3
- package/lib/rules/max-lines.js +3 -3
- package/lib/rules/max-nested-callbacks.js +1 -1
- package/lib/rules/max-params.js +1 -1
- package/lib/rules/max-statements.js +1 -1
- package/lib/rules/multiline-comment-style.js +7 -7
- package/lib/rules/new-cap.js +1 -1
- package/lib/rules/newline-after-var.js +1 -1
- package/lib/rules/newline-before-return.js +1 -1
- package/lib/rules/no-case-declarations.js +13 -1
- package/lib/rules/no-constant-binary-expression.js +7 -8
- package/lib/rules/no-constant-condition.js +18 -7
- package/lib/rules/no-constructor-return.js +2 -2
- package/lib/rules/no-dupe-class-members.js +2 -2
- package/lib/rules/no-else-return.js +1 -1
- package/lib/rules/no-empty-function.js +2 -2
- package/lib/rules/no-empty-static-block.js +1 -1
- package/lib/rules/no-extend-native.js +1 -2
- package/lib/rules/no-extra-semi.js +1 -1
- package/lib/rules/no-fallthrough.js +41 -16
- package/lib/rules/no-implicit-coercion.js +66 -24
- package/lib/rules/no-inner-declarations.js +23 -2
- package/lib/rules/no-invalid-regexp.js +1 -1
- package/lib/rules/no-invalid-this.js +1 -1
- package/lib/rules/no-lone-blocks.js +3 -3
- package/lib/rules/no-loss-of-precision.js +1 -1
- package/lib/rules/no-misleading-character-class.js +225 -69
- package/lib/rules/no-mixed-spaces-and-tabs.js +1 -1
- package/lib/rules/no-multiple-empty-lines.js +1 -1
- package/lib/rules/no-new-native-nonconstructor.js +1 -1
- package/lib/rules/no-new-symbol.js +8 -1
- package/lib/rules/no-restricted-globals.js +1 -1
- package/lib/rules/no-restricted-imports.js +186 -40
- package/lib/rules/no-restricted-modules.js +2 -2
- package/lib/rules/no-return-await.js +1 -1
- package/lib/rules/no-sequences.js +1 -0
- package/lib/rules/no-this-before-super.js +45 -13
- package/lib/rules/no-trailing-spaces.js +2 -3
- package/lib/rules/no-unneeded-ternary.js +1 -1
- package/lib/rules/no-unsafe-optional-chaining.js +1 -1
- package/lib/rules/no-unused-private-class-members.js +1 -1
- package/lib/rules/no-unused-vars.js +197 -36
- package/lib/rules/no-useless-assignment.js +566 -0
- package/lib/rules/no-useless-backreference.js +1 -1
- package/lib/rules/no-useless-computed-key.js +2 -2
- package/lib/rules/no-useless-return.js +7 -2
- package/lib/rules/object-curly-spacing.js +3 -3
- package/lib/rules/object-property-newline.js +1 -1
- package/lib/rules/one-var.js +5 -5
- package/lib/rules/padded-blocks.js +7 -7
- package/lib/rules/prefer-arrow-callback.js +3 -3
- package/lib/rules/prefer-reflect.js +1 -1
- package/lib/rules/prefer-regex-literals.js +1 -1
- package/lib/rules/prefer-template.js +1 -1
- package/lib/rules/radix.js +2 -2
- package/lib/rules/semi-style.js +1 -1
- package/lib/rules/sort-imports.js +1 -1
- package/lib/rules/sort-keys.js +1 -1
- package/lib/rules/sort-vars.js +1 -1
- package/lib/rules/space-unary-ops.js +1 -1
- package/lib/rules/strict.js +1 -1
- package/lib/rules/use-isnan.js +101 -7
- package/lib/rules/utils/ast-utils.js +16 -7
- package/lib/rules/utils/char-source.js +240 -0
- package/lib/rules/utils/lazy-loading-rule-map.js +1 -1
- package/lib/rules/utils/unicode/index.js +9 -4
- package/lib/rules/yield-star-spacing.js +1 -1
- package/lib/shared/runtime-info.js +1 -0
- package/lib/shared/serialization.js +55 -0
- package/lib/shared/stats.js +30 -0
- package/lib/shared/string-utils.js +9 -11
- package/lib/shared/types.js +35 -1
- package/lib/source-code/index.js +3 -1
- package/lib/source-code/source-code.js +299 -85
- package/lib/source-code/token-store/backward-token-cursor.js +3 -3
- package/lib/source-code/token-store/cursors.js +4 -2
- package/lib/source-code/token-store/forward-token-comment-cursor.js +3 -3
- package/lib/source-code/token-store/forward-token-cursor.js +3 -3
- package/lib/source-code/token-store/index.js +2 -2
- package/lib/unsupported-api.js +3 -5
- package/messages/no-config-found.js +1 -1
- package/messages/plugin-conflict.js +1 -1
- package/messages/plugin-invalid.js +1 -1
- package/messages/plugin-missing.js +1 -1
- package/package.json +32 -29
- package/conf/config-schema.js +0 -93
- package/lib/cli-engine/formatters/checkstyle.js +0 -60
- package/lib/cli-engine/formatters/compact.js +0 -60
- package/lib/cli-engine/formatters/jslint-xml.js +0 -41
- package/lib/cli-engine/formatters/junit.js +0 -82
- package/lib/cli-engine/formatters/tap.js +0 -95
- package/lib/cli-engine/formatters/unix.js +0 -58
- package/lib/cli-engine/formatters/visualstudio.js +0 -63
- package/lib/cli-engine/xml-escape.js +0 -34
- package/lib/eslint/flat-eslint.js +0 -1155
- package/lib/rule-tester/flat-rule-tester.js +0 -1131
- package/lib/rules/require-jsdoc.js +0 -122
- package/lib/rules/utils/patterns/letters.js +0 -36
- package/lib/rules/valid-jsdoc.js +0 -516
- package/lib/shared/config-validator.js +0 -347
- package/lib/shared/deprecation-warnings.js +0 -58
- package/lib/shared/relative-module-resolver.js +0 -50
package/lib/linter/linter.js
CHANGED
@@ -30,7 +30,6 @@ const
|
|
30
30
|
} = require("@eslint/eslintrc/universal"),
|
31
31
|
Traverser = require("../shared/traverser"),
|
32
32
|
{ SourceCode } = require("../source-code"),
|
33
|
-
CodePathAnalyzer = require("./code-path-analysis/code-path-analyzer"),
|
34
33
|
applyDisableDirectives = require("./apply-disable-directives"),
|
35
34
|
ConfigCommentParser = require("./config-comment-parser"),
|
36
35
|
NodeEventGenerator = require("./node-event-generator"),
|
@@ -42,8 +41,9 @@ const
|
|
42
41
|
ruleReplacements = require("../../conf/replacements.json");
|
43
42
|
const { getRuleFromConfig } = require("../config/flat-config-helpers");
|
44
43
|
const { FlatConfigArray } = require("../config/flat-config-array");
|
44
|
+
const { startTime, endTime } = require("../shared/stats");
|
45
45
|
const { RuleValidator } = require("../config/rule-validator");
|
46
|
-
const {
|
46
|
+
const { assertIsRuleSeverity } = require("../config/flat-config-schema");
|
47
47
|
const { normalizeSeverityToString } = require("../shared/severity");
|
48
48
|
const debug = require("debug")("eslint:linter");
|
49
49
|
const MAX_AUTOFIX_PASSES = 10;
|
@@ -52,13 +52,14 @@ const DEFAULT_ECMA_VERSION = 5;
|
|
52
52
|
const commentParser = new ConfigCommentParser();
|
53
53
|
const DEFAULT_ERROR_LOC = { start: { line: 1, column: 0 }, end: { line: 1, column: 1 } };
|
54
54
|
const parserSymbol = Symbol.for("eslint.RuleTester.parser");
|
55
|
+
const { LATEST_ECMA_VERSION } = require("../../conf/ecma-version");
|
56
|
+
const STEP_KIND_VISIT = 1;
|
57
|
+
const STEP_KIND_CALL = 2;
|
55
58
|
|
56
59
|
//------------------------------------------------------------------------------
|
57
60
|
// Typedefs
|
58
61
|
//------------------------------------------------------------------------------
|
59
62
|
|
60
|
-
/** @typedef {InstanceType<import("../cli-engine/config-array").ConfigArray>} ConfigArray */
|
61
|
-
/** @typedef {InstanceType<import("../cli-engine/config-array").ExtractedConfig>} ExtractedConfig */
|
62
63
|
/** @typedef {import("../shared/types").ConfigData} ConfigData */
|
63
64
|
/** @typedef {import("../shared/types").Environment} Environment */
|
64
65
|
/** @typedef {import("../shared/types").GlobalConf} GlobalConf */
|
@@ -68,6 +69,7 @@ const parserSymbol = Symbol.for("eslint.RuleTester.parser");
|
|
68
69
|
/** @typedef {import("../shared/types").LanguageOptions} LanguageOptions */
|
69
70
|
/** @typedef {import("../shared/types").Processor} Processor */
|
70
71
|
/** @typedef {import("../shared/types").Rule} Rule */
|
72
|
+
/** @typedef {import("../shared/types").Times} Times */
|
71
73
|
|
72
74
|
/* eslint-disable jsdoc/valid-types -- https://github.com/jsdoc-type-pratt-parser/jsdoc-type-pratt-parser/issues/4#issuecomment-778805577 */
|
73
75
|
/**
|
@@ -92,6 +94,7 @@ const parserSymbol = Symbol.for("eslint.RuleTester.parser");
|
|
92
94
|
* @property {SourceCode|null} lastSourceCode The `SourceCode` instance that the last `verify()` call used.
|
93
95
|
* @property {SuppressedLintMessage[]} lastSuppressedMessages The `SuppressedLintMessage[]` instance that the last `verify()` call produced.
|
94
96
|
* @property {Map<string, Parser>} parserMap The loaded parsers.
|
97
|
+
* @property {Times} times The times spent on applying a rule to a file (see `stats` option).
|
95
98
|
* @property {Rules} ruleMap The loaded rules.
|
96
99
|
*/
|
97
100
|
|
@@ -105,6 +108,7 @@ const parserSymbol = Symbol.for("eslint.RuleTester.parser");
|
|
105
108
|
* @property {string} [filename] the filename of the source code.
|
106
109
|
* @property {boolean | "off" | "warn" | "error"} [reportUnusedDisableDirectives] Adds reported errors for
|
107
110
|
* unused `eslint-disable` directives.
|
111
|
+
* @property {Function} [ruleFilter] A predicate function that determines whether a given rule should run.
|
108
112
|
*/
|
109
113
|
|
110
114
|
/**
|
@@ -230,7 +234,7 @@ function addDeclaredGlobals(globalScope, configGlobals, { exportedVariables, ena
|
|
230
234
|
* @private
|
231
235
|
*/
|
232
236
|
function createMissingRuleMessage(ruleId) {
|
233
|
-
return Object.
|
237
|
+
return Object.hasOwn(ruleReplacements.rules, ruleId)
|
234
238
|
? `Rule '${ruleId}' was removed and replaced by: ${ruleReplacements.rules[ruleId].join(", ")}`
|
235
239
|
: `Definition for rule '${ruleId}' was not found.`;
|
236
240
|
}
|
@@ -269,23 +273,21 @@ function createLintingProblem(options) {
|
|
269
273
|
* Creates a collection of disable directives from a comment
|
270
274
|
* @param {Object} options to create disable directives
|
271
275
|
* @param {("disable"|"enable"|"disable-line"|"disable-next-line")} options.type The type of directive comment
|
272
|
-
* @param {token} options.commentToken The Comment token
|
273
276
|
* @param {string} options.value The value after the directive in the comment
|
274
277
|
* comment specified no specific rules, so it applies to all rules (e.g. `eslint-disable`)
|
275
278
|
* @param {string} options.justification The justification of the directive
|
276
|
-
* @param {
|
279
|
+
* @param {ASTNode|token} options.node The Comment node/token.
|
280
|
+
* @param {function(string): {create: Function}} ruleMapper A map from rule IDs to defined rules
|
277
281
|
* @returns {Object} Directives and problems from the comment
|
278
282
|
*/
|
279
|
-
function createDisableDirectives(
|
280
|
-
const { commentToken, type, value, justification, ruleMapper } = options;
|
283
|
+
function createDisableDirectives({ type, value, justification, node }, ruleMapper) {
|
281
284
|
const ruleIds = Object.keys(commentParser.parseListConfig(value));
|
282
285
|
const directiveRules = ruleIds.length ? ruleIds : [null];
|
283
286
|
const result = {
|
284
287
|
directives: [], // valid disable directives
|
285
288
|
directiveProblems: [] // problems in directives
|
286
289
|
};
|
287
|
-
|
288
|
-
const parentComment = { commentToken, ruleIds };
|
290
|
+
const parentDirective = { node, ruleIds };
|
289
291
|
|
290
292
|
for (const ruleId of directiveRules) {
|
291
293
|
|
@@ -293,25 +295,25 @@ function createDisableDirectives(options) {
|
|
293
295
|
if (ruleId === null || !!ruleMapper(ruleId)) {
|
294
296
|
if (type === "disable-next-line") {
|
295
297
|
result.directives.push({
|
296
|
-
|
298
|
+
parentDirective,
|
297
299
|
type,
|
298
|
-
line:
|
299
|
-
column:
|
300
|
+
line: node.loc.end.line,
|
301
|
+
column: node.loc.end.column + 1,
|
300
302
|
ruleId,
|
301
303
|
justification
|
302
304
|
});
|
303
305
|
} else {
|
304
306
|
result.directives.push({
|
305
|
-
|
307
|
+
parentDirective,
|
306
308
|
type,
|
307
|
-
line:
|
308
|
-
column:
|
309
|
+
line: node.loc.start.line,
|
310
|
+
column: node.loc.start.column + 1,
|
309
311
|
ruleId,
|
310
312
|
justification
|
311
313
|
});
|
312
314
|
}
|
313
315
|
} else {
|
314
|
-
result.directiveProblems.push(createLintingProblem({ ruleId, loc:
|
316
|
+
result.directiveProblems.push(createLintingProblem({ ruleId, loc: node.loc }));
|
315
317
|
}
|
316
318
|
}
|
317
319
|
return result;
|
@@ -324,10 +326,11 @@ function createDisableDirectives(options) {
|
|
324
326
|
* @param {SourceCode} sourceCode The SourceCode object to get comments from.
|
325
327
|
* @param {function(string): {create: Function}} ruleMapper A map from rule IDs to defined rules
|
326
328
|
* @param {string|null} warnInlineConfig If a string then it should warn directive comments as disabled. The string value is the config name what the setting came from.
|
329
|
+
* @param {ConfigData} config Provided config.
|
327
330
|
* @returns {{configuredRules: Object, enabledGlobals: {value:string,comment:Token}[], exportedVariables: Object, problems: LintMessage[], disableDirectives: DisableDirective[]}}
|
328
331
|
* A collection of the directive comments that were found, along with any problems that occurred when parsing
|
329
332
|
*/
|
330
|
-
function getDirectiveComments(sourceCode, ruleMapper, warnInlineConfig) {
|
333
|
+
function getDirectiveComments(sourceCode, ruleMapper, warnInlineConfig, config) {
|
331
334
|
const configuredRules = {};
|
332
335
|
const enabledGlobals = Object.create(null);
|
333
336
|
const exportedVariables = {};
|
@@ -383,8 +386,12 @@ function getDirectiveComments(sourceCode, ruleMapper, warnInlineConfig) {
|
|
383
386
|
case "eslint-disable-next-line":
|
384
387
|
case "eslint-disable-line": {
|
385
388
|
const directiveType = directiveText.slice("eslint-".length);
|
386
|
-
const
|
387
|
-
|
389
|
+
const { directives, directiveProblems } = createDisableDirectives({
|
390
|
+
type: directiveType,
|
391
|
+
value: directiveValue,
|
392
|
+
justification: justificationPart,
|
393
|
+
node: comment
|
394
|
+
}, ruleMapper);
|
388
395
|
|
389
396
|
disableDirectives.push(...directives);
|
390
397
|
problems.push(...directiveProblems);
|
@@ -392,7 +399,7 @@ function getDirectiveComments(sourceCode, ruleMapper, warnInlineConfig) {
|
|
392
399
|
}
|
393
400
|
|
394
401
|
case "exported":
|
395
|
-
Object.assign(exportedVariables, commentParser.
|
402
|
+
Object.assign(exportedVariables, commentParser.parseListConfig(directiveValue, comment));
|
396
403
|
break;
|
397
404
|
|
398
405
|
case "globals":
|
@@ -436,9 +443,68 @@ function getDirectiveComments(sourceCode, ruleMapper, warnInlineConfig) {
|
|
436
443
|
return;
|
437
444
|
}
|
438
445
|
|
446
|
+
if (Object.hasOwn(configuredRules, name)) {
|
447
|
+
problems.push(createLintingProblem({
|
448
|
+
message: `Rule "${name}" is already configured by another configuration comment in the preceding code. This configuration is ignored.`,
|
449
|
+
loc: comment.loc
|
450
|
+
}));
|
451
|
+
return;
|
452
|
+
}
|
453
|
+
|
454
|
+
let ruleOptions = Array.isArray(ruleValue) ? ruleValue : [ruleValue];
|
455
|
+
|
456
|
+
/*
|
457
|
+
* If the rule was already configured, inline rule configuration that
|
458
|
+
* only has severity should retain options from the config and just override the severity.
|
459
|
+
*
|
460
|
+
* Example:
|
461
|
+
*
|
462
|
+
* {
|
463
|
+
* rules: {
|
464
|
+
* curly: ["error", "multi"]
|
465
|
+
* }
|
466
|
+
* }
|
467
|
+
*
|
468
|
+
* /* eslint curly: ["warn"] * /
|
469
|
+
*
|
470
|
+
* Results in:
|
471
|
+
*
|
472
|
+
* curly: ["warn", "multi"]
|
473
|
+
*/
|
474
|
+
if (
|
475
|
+
|
476
|
+
/*
|
477
|
+
* If inline config for the rule has only severity
|
478
|
+
*/
|
479
|
+
ruleOptions.length === 1 &&
|
480
|
+
|
481
|
+
/*
|
482
|
+
* And the rule was already configured
|
483
|
+
*/
|
484
|
+
config.rules && Object.hasOwn(config.rules, name)
|
485
|
+
) {
|
486
|
+
|
487
|
+
/*
|
488
|
+
* Then use severity from the inline config and options from the provided config
|
489
|
+
*/
|
490
|
+
ruleOptions = [
|
491
|
+
ruleOptions[0], // severity from the inline config
|
492
|
+
...Array.isArray(config.rules[name]) ? config.rules[name].slice(1) : [] // options from the provided config
|
493
|
+
];
|
494
|
+
}
|
495
|
+
|
439
496
|
try {
|
440
|
-
validator.validateRuleOptions(rule, name,
|
497
|
+
validator.validateRuleOptions(rule, name, ruleOptions);
|
441
498
|
} catch (err) {
|
499
|
+
|
500
|
+
/*
|
501
|
+
* If the rule has invalid `meta.schema`, throw the error because
|
502
|
+
* this is not an invalid inline configuration but an invalid rule.
|
503
|
+
*/
|
504
|
+
if (err.code === "ESLINT_INVALID_RULE_OPTIONS_SCHEMA") {
|
505
|
+
throw err;
|
506
|
+
}
|
507
|
+
|
442
508
|
problems.push(createLintingProblem({
|
443
509
|
ruleId: name,
|
444
510
|
message: err.message,
|
@@ -449,7 +515,7 @@ function getDirectiveComments(sourceCode, ruleMapper, warnInlineConfig) {
|
|
449
515
|
return;
|
450
516
|
}
|
451
517
|
|
452
|
-
configuredRules[name] =
|
518
|
+
configuredRules[name] = ruleOptions;
|
453
519
|
});
|
454
520
|
} else {
|
455
521
|
problems.push(parseResult.error);
|
@@ -479,53 +545,21 @@ function getDirectiveComments(sourceCode, ruleMapper, warnInlineConfig) {
|
|
479
545
|
* A collection of the directive comments that were found, along with any problems that occurred when parsing
|
480
546
|
*/
|
481
547
|
function getDirectiveCommentsForFlatConfig(sourceCode, ruleMapper) {
|
482
|
-
const problems = [];
|
483
548
|
const disableDirectives = [];
|
549
|
+
const problems = [];
|
484
550
|
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
if (!match) {
|
491
|
-
return;
|
492
|
-
}
|
493
|
-
const directiveText = match[1];
|
494
|
-
const lineCommentSupported = /^eslint-disable-(next-)?line$/u.test(directiveText);
|
495
|
-
|
496
|
-
if (comment.type === "Line" && !lineCommentSupported) {
|
497
|
-
return;
|
498
|
-
}
|
499
|
-
|
500
|
-
if (directiveText === "eslint-disable-line" && comment.loc.start.line !== comment.loc.end.line) {
|
501
|
-
const message = `${directiveText} comment should not span multiple lines.`;
|
502
|
-
|
503
|
-
problems.push(createLintingProblem({
|
504
|
-
ruleId: null,
|
505
|
-
message,
|
506
|
-
loc: comment.loc
|
507
|
-
}));
|
508
|
-
return;
|
509
|
-
}
|
510
|
-
|
511
|
-
const directiveValue = directivePart.slice(match.index + directiveText.length);
|
551
|
+
const {
|
552
|
+
directives: directivesSources,
|
553
|
+
problems: directivesProblems
|
554
|
+
} = sourceCode.getDisableDirectives();
|
512
555
|
|
513
|
-
|
514
|
-
case "eslint-disable":
|
515
|
-
case "eslint-enable":
|
516
|
-
case "eslint-disable-next-line":
|
517
|
-
case "eslint-disable-line": {
|
518
|
-
const directiveType = directiveText.slice("eslint-".length);
|
519
|
-
const options = { commentToken: comment, type: directiveType, value: directiveValue, justification: justificationPart, ruleMapper };
|
520
|
-
const { directives, directiveProblems } = createDisableDirectives(options);
|
556
|
+
problems.push(...directivesProblems.map(createLintingProblem));
|
521
557
|
|
522
|
-
|
523
|
-
|
524
|
-
break;
|
525
|
-
}
|
558
|
+
directivesSources.forEach(directive => {
|
559
|
+
const { directives, directiveProblems } = createDisableDirectives(directive, ruleMapper);
|
526
560
|
|
527
|
-
|
528
|
-
|
561
|
+
disableDirectives.push(...directives);
|
562
|
+
problems.push(...directiveProblems);
|
529
563
|
});
|
530
564
|
|
531
565
|
return {
|
@@ -584,7 +618,7 @@ function normalizeEcmaVersionForLanguageOptions(ecmaVersion) {
|
|
584
618
|
* that is used for a number of processes inside of ESLint. It's normally
|
585
619
|
* safe to assume people want the latest unless otherwise specified.
|
586
620
|
*/
|
587
|
-
return
|
621
|
+
return LATEST_ECMA_VERSION;
|
588
622
|
}
|
589
623
|
|
590
624
|
const eslintEnvPattern = /\/\*\s*eslint-env\s(.+?)(?:\*\/|$)/gsu;
|
@@ -661,6 +695,12 @@ function normalizeVerifyOptions(providedOptions, config) {
|
|
661
695
|
}
|
662
696
|
}
|
663
697
|
|
698
|
+
let ruleFilter = providedOptions.ruleFilter;
|
699
|
+
|
700
|
+
if (typeof ruleFilter !== "function") {
|
701
|
+
ruleFilter = () => true;
|
702
|
+
}
|
703
|
+
|
664
704
|
return {
|
665
705
|
filename: normalizeFilename(providedOptions.filename || "<input>"),
|
666
706
|
allowInlineConfig: !ignoreInlineConfig,
|
@@ -668,7 +708,9 @@ function normalizeVerifyOptions(providedOptions, config) {
|
|
668
708
|
? `your config${configNameOfNoInlineConfig}`
|
669
709
|
: null,
|
670
710
|
reportUnusedDisableDirectives,
|
671
|
-
disableFixes: Boolean(providedOptions.disableFixes)
|
711
|
+
disableFixes: Boolean(providedOptions.disableFixes),
|
712
|
+
stats: providedOptions.stats,
|
713
|
+
ruleFilter
|
672
714
|
};
|
673
715
|
}
|
674
716
|
|
@@ -733,7 +775,7 @@ function createLanguageOptions({ globals: configuredGlobals, parser, parserOptio
|
|
733
775
|
*/
|
734
776
|
function resolveGlobals(providedGlobals, enabledEnvironments) {
|
735
777
|
return Object.assign(
|
736
|
-
|
778
|
+
Object.create(null),
|
737
779
|
...enabledEnvironments.filter(env => env.globals).map(env => env.globals),
|
738
780
|
providedGlobals
|
739
781
|
);
|
@@ -757,6 +799,36 @@ function stripUnicodeBOM(text) {
|
|
757
799
|
return text;
|
758
800
|
}
|
759
801
|
|
802
|
+
/**
|
803
|
+
* Store time measurements in map
|
804
|
+
* @param {number} time Time measurement
|
805
|
+
* @param {Object} timeOpts Options relating which time was measured
|
806
|
+
* @param {WeakMap<Linter, LinterInternalSlots>} slots Linter internal slots map
|
807
|
+
* @returns {void}
|
808
|
+
*/
|
809
|
+
function storeTime(time, timeOpts, slots) {
|
810
|
+
const { type, key } = timeOpts;
|
811
|
+
|
812
|
+
if (!slots.times) {
|
813
|
+
slots.times = { passes: [{}] };
|
814
|
+
}
|
815
|
+
|
816
|
+
const passIndex = slots.fixPasses;
|
817
|
+
|
818
|
+
if (passIndex > slots.times.passes.length - 1) {
|
819
|
+
slots.times.passes.push({});
|
820
|
+
}
|
821
|
+
|
822
|
+
if (key) {
|
823
|
+
slots.times.passes[passIndex][type] ??= {};
|
824
|
+
slots.times.passes[passIndex][type][key] ??= { total: 0 };
|
825
|
+
slots.times.passes[passIndex][type][key].total += time;
|
826
|
+
} else {
|
827
|
+
slots.times.passes[passIndex][type] ??= { total: 0 };
|
828
|
+
slots.times.passes[passIndex][type].total += time;
|
829
|
+
}
|
830
|
+
}
|
831
|
+
|
760
832
|
/**
|
761
833
|
* Get the options for a rule (not including severity), if any
|
762
834
|
* @param {Array|number} ruleConfig rule configuration
|
@@ -885,12 +957,18 @@ function parse(text, languageOptions, filePath) {
|
|
885
957
|
|
886
958
|
/**
|
887
959
|
* Runs a rule, and gets its listeners
|
888
|
-
* @param {Rule} rule A
|
960
|
+
* @param {Rule} rule A rule object
|
889
961
|
* @param {Context} ruleContext The context that should be passed to the rule
|
962
|
+
* @throws {TypeError} If `rule` is not an object with a `create` method
|
890
963
|
* @throws {any} Any error during the rule's `create`
|
891
964
|
* @returns {Object} A map of selector listeners provided by the rule
|
892
965
|
*/
|
893
966
|
function createRuleListeners(rule, ruleContext) {
|
967
|
+
|
968
|
+
if (!rule || typeof rule !== "object" || typeof rule.create !== "function") {
|
969
|
+
throw new TypeError(`Error while loading rule '${ruleContext.id}': Rule must be an object with a \`create\` method`);
|
970
|
+
}
|
971
|
+
|
894
972
|
try {
|
895
973
|
return rule.create(ruleContext);
|
896
974
|
} catch (ex) {
|
@@ -899,43 +977,6 @@ function createRuleListeners(rule, ruleContext) {
|
|
899
977
|
}
|
900
978
|
}
|
901
979
|
|
902
|
-
// methods that exist on SourceCode object
|
903
|
-
const DEPRECATED_SOURCECODE_PASSTHROUGHS = {
|
904
|
-
getSource: "getText",
|
905
|
-
getSourceLines: "getLines",
|
906
|
-
getAllComments: "getAllComments",
|
907
|
-
getNodeByRangeIndex: "getNodeByRangeIndex",
|
908
|
-
getComments: "getComments",
|
909
|
-
getCommentsBefore: "getCommentsBefore",
|
910
|
-
getCommentsAfter: "getCommentsAfter",
|
911
|
-
getCommentsInside: "getCommentsInside",
|
912
|
-
getJSDocComment: "getJSDocComment",
|
913
|
-
getFirstToken: "getFirstToken",
|
914
|
-
getFirstTokens: "getFirstTokens",
|
915
|
-
getLastToken: "getLastToken",
|
916
|
-
getLastTokens: "getLastTokens",
|
917
|
-
getTokenAfter: "getTokenAfter",
|
918
|
-
getTokenBefore: "getTokenBefore",
|
919
|
-
getTokenByRangeStart: "getTokenByRangeStart",
|
920
|
-
getTokens: "getTokens",
|
921
|
-
getTokensAfter: "getTokensAfter",
|
922
|
-
getTokensBefore: "getTokensBefore",
|
923
|
-
getTokensBetween: "getTokensBetween"
|
924
|
-
};
|
925
|
-
|
926
|
-
|
927
|
-
const BASE_TRAVERSAL_CONTEXT = Object.freeze(
|
928
|
-
Object.keys(DEPRECATED_SOURCECODE_PASSTHROUGHS).reduce(
|
929
|
-
(contextInfo, methodName) =>
|
930
|
-
Object.assign(contextInfo, {
|
931
|
-
[methodName](...args) {
|
932
|
-
return this.sourceCode[DEPRECATED_SOURCECODE_PASSTHROUGHS[methodName]](...args);
|
933
|
-
}
|
934
|
-
}),
|
935
|
-
{}
|
936
|
-
)
|
937
|
-
);
|
938
|
-
|
939
980
|
/**
|
940
981
|
* Runs the given rules on the given SourceCode object
|
941
982
|
* @param {SourceCode} sourceCode A SourceCode object for the given text
|
@@ -948,23 +989,18 @@ const BASE_TRAVERSAL_CONTEXT = Object.freeze(
|
|
948
989
|
* @param {boolean} disableFixes If true, it doesn't make `fix` properties.
|
949
990
|
* @param {string | undefined} cwd cwd of the cli
|
950
991
|
* @param {string} physicalFilename The full path of the file on disk without any code block information
|
992
|
+
* @param {Function} ruleFilter A predicate function to filter which rules should be executed.
|
993
|
+
* @param {boolean} stats If true, stats are collected appended to the result
|
994
|
+
* @param {WeakMap<Linter, LinterInternalSlots>} slots InternalSlotsMap of linter
|
951
995
|
* @returns {LintMessage[]} An array of reported problems
|
996
|
+
* @throws {Error} If traversal into a node fails.
|
952
997
|
*/
|
953
|
-
function runRules(sourceCode, configuredRules, ruleMapper, parserName, languageOptions, settings, filename, disableFixes, cwd, physicalFilename
|
998
|
+
function runRules(sourceCode, configuredRules, ruleMapper, parserName, languageOptions, settings, filename, disableFixes, cwd, physicalFilename, ruleFilter,
|
999
|
+
stats, slots) {
|
954
1000
|
const emitter = createEmitter();
|
955
|
-
|
956
|
-
|
957
|
-
|
958
|
-
Traverser.traverse(sourceCode.ast, {
|
959
|
-
enter(node, parent) {
|
960
|
-
node.parent = parent;
|
961
|
-
nodeQueue.push({ isEntering: true, node });
|
962
|
-
},
|
963
|
-
leave(node) {
|
964
|
-
nodeQueue.push({ isEntering: false, node });
|
965
|
-
},
|
966
|
-
visitorKeys: sourceCode.visitorKeys
|
967
|
-
});
|
1001
|
+
|
1002
|
+
// must happen first to assign all node.parent properties
|
1003
|
+
const eventQueue = sourceCode.traverse();
|
968
1004
|
|
969
1005
|
/*
|
970
1006
|
* Create a frozen object with the ruleContext properties and methods that are shared by all rules.
|
@@ -972,30 +1008,22 @@ function runRules(sourceCode, configuredRules, ruleMapper, parserName, languageO
|
|
972
1008
|
* properties once for each rule.
|
973
1009
|
*/
|
974
1010
|
const sharedTraversalContext = Object.freeze(
|
975
|
-
|
976
|
-
|
977
|
-
|
978
|
-
|
979
|
-
|
980
|
-
|
981
|
-
|
982
|
-
|
983
|
-
|
984
|
-
|
985
|
-
|
986
|
-
|
987
|
-
|
988
|
-
|
989
|
-
|
990
|
-
|
991
|
-
...languageOptions.parserOptions
|
992
|
-
},
|
993
|
-
parserPath: parserName,
|
994
|
-
languageOptions,
|
995
|
-
parserServices: sourceCode.parserServices,
|
996
|
-
settings
|
997
|
-
}
|
998
|
-
)
|
1011
|
+
{
|
1012
|
+
getCwd: () => cwd,
|
1013
|
+
cwd,
|
1014
|
+
getFilename: () => filename,
|
1015
|
+
filename,
|
1016
|
+
getPhysicalFilename: () => physicalFilename || filename,
|
1017
|
+
physicalFilename: physicalFilename || filename,
|
1018
|
+
getSourceCode: () => sourceCode,
|
1019
|
+
sourceCode,
|
1020
|
+
parserOptions: {
|
1021
|
+
...languageOptions.parserOptions
|
1022
|
+
},
|
1023
|
+
parserPath: parserName,
|
1024
|
+
languageOptions,
|
1025
|
+
settings
|
1026
|
+
}
|
999
1027
|
);
|
1000
1028
|
|
1001
1029
|
const lintingProblems = [];
|
@@ -1008,6 +1036,10 @@ function runRules(sourceCode, configuredRules, ruleMapper, parserName, languageO
|
|
1008
1036
|
return;
|
1009
1037
|
}
|
1010
1038
|
|
1039
|
+
if (ruleFilter && !ruleFilter({ ruleId, severity })) {
|
1040
|
+
return;
|
1041
|
+
}
|
1042
|
+
|
1011
1043
|
const rule = ruleMapper(ruleId);
|
1012
1044
|
|
1013
1045
|
if (!rule) {
|
@@ -1063,7 +1095,14 @@ function runRules(sourceCode, configuredRules, ruleMapper, parserName, languageO
|
|
1063
1095
|
)
|
1064
1096
|
);
|
1065
1097
|
|
1066
|
-
const
|
1098
|
+
const ruleListenersReturn = (timing.enabled || stats)
|
1099
|
+
? timing.time(ruleId, createRuleListeners, stats)(rule, ruleContext) : createRuleListeners(rule, ruleContext);
|
1100
|
+
|
1101
|
+
const ruleListeners = stats ? ruleListenersReturn.result : ruleListenersReturn;
|
1102
|
+
|
1103
|
+
if (stats) {
|
1104
|
+
storeTime(ruleListenersReturn.tdiff, { type: "rules", key: ruleId }, slots);
|
1105
|
+
}
|
1067
1106
|
|
1068
1107
|
/**
|
1069
1108
|
* Include `ruleId` in error logs
|
@@ -1073,7 +1112,15 @@ function runRules(sourceCode, configuredRules, ruleMapper, parserName, languageO
|
|
1073
1112
|
function addRuleErrorHandler(ruleListener) {
|
1074
1113
|
return function ruleErrorHandler(...listenerArgs) {
|
1075
1114
|
try {
|
1076
|
-
|
1115
|
+
const ruleListenerReturn = ruleListener(...listenerArgs);
|
1116
|
+
|
1117
|
+
const ruleListenerResult = stats ? ruleListenerReturn.result : ruleListenerReturn;
|
1118
|
+
|
1119
|
+
if (stats) {
|
1120
|
+
storeTime(ruleListenerReturn.tdiff, { type: "rules", key: ruleId }, slots);
|
1121
|
+
}
|
1122
|
+
|
1123
|
+
return ruleListenerResult;
|
1077
1124
|
} catch (e) {
|
1078
1125
|
e.ruleId = ruleId;
|
1079
1126
|
throw e;
|
@@ -1087,9 +1134,8 @@ function runRules(sourceCode, configuredRules, ruleMapper, parserName, languageO
|
|
1087
1134
|
|
1088
1135
|
// add all the selectors from the rule as listeners
|
1089
1136
|
Object.keys(ruleListeners).forEach(selector => {
|
1090
|
-
const ruleListener = timing.enabled
|
1091
|
-
? timing.time(ruleId, ruleListeners[selector])
|
1092
|
-
: ruleListeners[selector];
|
1137
|
+
const ruleListener = (timing.enabled || stats)
|
1138
|
+
? timing.time(ruleId, ruleListeners[selector], stats) : ruleListeners[selector];
|
1093
1139
|
|
1094
1140
|
emitter.on(
|
1095
1141
|
selector,
|
@@ -1098,25 +1144,34 @@ function runRules(sourceCode, configuredRules, ruleMapper, parserName, languageO
|
|
1098
1144
|
});
|
1099
1145
|
});
|
1100
1146
|
|
1101
|
-
|
1102
|
-
const eventGenerator = nodeQueue[0].node.type === "Program"
|
1103
|
-
? new CodePathAnalyzer(new NodeEventGenerator(emitter, { visitorKeys: sourceCode.visitorKeys, fallback: Traverser.getKeys }))
|
1104
|
-
: new NodeEventGenerator(emitter, { visitorKeys: sourceCode.visitorKeys, fallback: Traverser.getKeys });
|
1147
|
+
const eventGenerator = new NodeEventGenerator(emitter, { visitorKeys: sourceCode.visitorKeys, fallback: Traverser.getKeys });
|
1105
1148
|
|
1106
|
-
|
1107
|
-
|
1149
|
+
for (const step of eventQueue) {
|
1150
|
+
switch (step.kind) {
|
1151
|
+
case STEP_KIND_VISIT: {
|
1152
|
+
try {
|
1153
|
+
if (step.phase === 1) {
|
1154
|
+
eventGenerator.enterNode(step.target);
|
1155
|
+
} else {
|
1156
|
+
eventGenerator.leaveNode(step.target);
|
1157
|
+
}
|
1158
|
+
} catch (err) {
|
1159
|
+
err.currentNode = step.target;
|
1160
|
+
throw err;
|
1161
|
+
}
|
1162
|
+
break;
|
1163
|
+
}
|
1108
1164
|
|
1109
|
-
|
1110
|
-
|
1111
|
-
|
1112
|
-
} else {
|
1113
|
-
eventGenerator.leaveNode(currentNode);
|
1165
|
+
case STEP_KIND_CALL: {
|
1166
|
+
emitter.emit(step.target, ...step.args);
|
1167
|
+
break;
|
1114
1168
|
}
|
1115
|
-
|
1116
|
-
|
1117
|
-
|
1169
|
+
|
1170
|
+
default:
|
1171
|
+
throw new Error(`Invalid traversal step found: "${step.type}".`);
|
1118
1172
|
}
|
1119
|
-
|
1173
|
+
|
1174
|
+
}
|
1120
1175
|
|
1121
1176
|
return lintingProblems;
|
1122
1177
|
}
|
@@ -1202,7 +1257,6 @@ function assertEslintrcConfig(linter) {
|
|
1202
1257
|
}
|
1203
1258
|
}
|
1204
1259
|
|
1205
|
-
|
1206
1260
|
//------------------------------------------------------------------------------
|
1207
1261
|
// Public Interface
|
1208
1262
|
//------------------------------------------------------------------------------
|
@@ -1217,9 +1271,9 @@ class Linter {
|
|
1217
1271
|
* Initialize the Linter.
|
1218
1272
|
* @param {Object} [config] the config object
|
1219
1273
|
* @param {string} [config.cwd] path to a directory that should be considered as the current working directory, can be undefined.
|
1220
|
-
* @param {"flat"|"eslintrc"} [config.configType="
|
1274
|
+
* @param {"flat"|"eslintrc"} [config.configType="flat"] the type of config used.
|
1221
1275
|
*/
|
1222
|
-
constructor({ cwd, configType } = {}) {
|
1276
|
+
constructor({ cwd, configType = "flat" } = {}) {
|
1223
1277
|
internalSlotsMap.set(this, {
|
1224
1278
|
cwd: normalizeCwd(cwd),
|
1225
1279
|
lastConfigArray: null,
|
@@ -1308,12 +1362,25 @@ class Linter {
|
|
1308
1362
|
});
|
1309
1363
|
|
1310
1364
|
if (!slots.lastSourceCode) {
|
1365
|
+
let t;
|
1366
|
+
|
1367
|
+
if (options.stats) {
|
1368
|
+
t = startTime();
|
1369
|
+
}
|
1370
|
+
|
1311
1371
|
const parseResult = parse(
|
1312
1372
|
text,
|
1313
1373
|
languageOptions,
|
1314
1374
|
options.filename
|
1315
1375
|
);
|
1316
1376
|
|
1377
|
+
if (options.stats) {
|
1378
|
+
const time = endTime(t);
|
1379
|
+
const timeOpts = { type: "parse" };
|
1380
|
+
|
1381
|
+
storeTime(time, timeOpts, slots);
|
1382
|
+
}
|
1383
|
+
|
1317
1384
|
if (!parseResult.success) {
|
1318
1385
|
return [parseResult.error];
|
1319
1386
|
}
|
@@ -1338,7 +1405,7 @@ class Linter {
|
|
1338
1405
|
|
1339
1406
|
const sourceCode = slots.lastSourceCode;
|
1340
1407
|
const commentDirectives = options.allowInlineConfig
|
1341
|
-
? getDirectiveComments(sourceCode, ruleId => getRule(slots, ruleId), options.warnInlineConfig)
|
1408
|
+
? getDirectiveComments(sourceCode, ruleId => getRule(slots, ruleId), options.warnInlineConfig, config)
|
1342
1409
|
: { configuredRules: {}, enabledGlobals: {}, exportedVariables: {}, problems: [], disableDirectives: [] };
|
1343
1410
|
|
1344
1411
|
// augment global scope with declared global variables
|
@@ -1349,6 +1416,7 @@ class Linter {
|
|
1349
1416
|
);
|
1350
1417
|
|
1351
1418
|
const configuredRules = Object.assign({}, config.rules, commentDirectives.configuredRules);
|
1419
|
+
|
1352
1420
|
let lintingProblems;
|
1353
1421
|
|
1354
1422
|
try {
|
@@ -1362,7 +1430,10 @@ class Linter {
|
|
1362
1430
|
options.filename,
|
1363
1431
|
options.disableFixes,
|
1364
1432
|
slots.cwd,
|
1365
|
-
providedOptions.physicalFilename
|
1433
|
+
providedOptions.physicalFilename,
|
1434
|
+
null,
|
1435
|
+
options.stats,
|
1436
|
+
slots
|
1366
1437
|
);
|
1367
1438
|
} catch (err) {
|
1368
1439
|
err.message += `\nOccurred while linting ${options.filename}`;
|
@@ -1413,29 +1484,29 @@ class Linter {
|
|
1413
1484
|
? { filename: filenameOrOptions }
|
1414
1485
|
: filenameOrOptions || {};
|
1415
1486
|
|
1416
|
-
|
1417
|
-
if (configType === "flat") {
|
1418
|
-
|
1419
|
-
/*
|
1420
|
-
* Because of how Webpack packages up the files, we can't
|
1421
|
-
* compare directly to `FlatConfigArray` using `instanceof`
|
1422
|
-
* because it's not the same `FlatConfigArray` as in the tests.
|
1423
|
-
* So, we work around it by assuming an array is, in fact, a
|
1424
|
-
* `FlatConfigArray` if it has a `getConfig()` method.
|
1425
|
-
*/
|
1426
|
-
let configArray = config;
|
1427
|
-
|
1428
|
-
if (!Array.isArray(config) || typeof config.getConfig !== "function") {
|
1429
|
-
configArray = new FlatConfigArray(config, { basePath: cwd });
|
1430
|
-
configArray.normalizeSync();
|
1431
|
-
}
|
1487
|
+
const configToUse = config ?? {};
|
1432
1488
|
|
1433
|
-
|
1434
|
-
}
|
1489
|
+
if (configType !== "eslintrc") {
|
1435
1490
|
|
1436
|
-
|
1437
|
-
|
1491
|
+
/*
|
1492
|
+
* Because of how Webpack packages up the files, we can't
|
1493
|
+
* compare directly to `FlatConfigArray` using `instanceof`
|
1494
|
+
* because it's not the same `FlatConfigArray` as in the tests.
|
1495
|
+
* So, we work around it by assuming an array is, in fact, a
|
1496
|
+
* `FlatConfigArray` if it has a `getConfig()` method.
|
1497
|
+
*/
|
1498
|
+
let configArray = configToUse;
|
1499
|
+
|
1500
|
+
if (!Array.isArray(configToUse) || typeof configToUse.getConfig !== "function") {
|
1501
|
+
configArray = new FlatConfigArray(configToUse, { basePath: cwd });
|
1502
|
+
configArray.normalizeSync();
|
1438
1503
|
}
|
1504
|
+
|
1505
|
+
return this._distinguishSuppressedMessages(this._verifyWithFlatConfigArray(textOrSourceCode, configArray, options, true));
|
1506
|
+
}
|
1507
|
+
|
1508
|
+
if (typeof configToUse.extractConfig === "function") {
|
1509
|
+
return this._distinguishSuppressedMessages(this._verifyWithConfigArray(textOrSourceCode, configToUse, options));
|
1439
1510
|
}
|
1440
1511
|
|
1441
1512
|
/*
|
@@ -1448,9 +1519,9 @@ class Linter {
|
|
1448
1519
|
* So we cannot apply multiple processors.
|
1449
1520
|
*/
|
1450
1521
|
if (options.preprocess || options.postprocess) {
|
1451
|
-
return this._distinguishSuppressedMessages(this._verifyWithProcessor(textOrSourceCode,
|
1522
|
+
return this._distinguishSuppressedMessages(this._verifyWithProcessor(textOrSourceCode, configToUse, options));
|
1452
1523
|
}
|
1453
|
-
return this._distinguishSuppressedMessages(this._verifyWithoutProcessors(textOrSourceCode,
|
1524
|
+
return this._distinguishSuppressedMessages(this._verifyWithoutProcessors(textOrSourceCode, configToUse, options));
|
1454
1525
|
}
|
1455
1526
|
|
1456
1527
|
/**
|
@@ -1590,12 +1661,24 @@ class Linter {
|
|
1590
1661
|
const settings = config.settings || {};
|
1591
1662
|
|
1592
1663
|
if (!slots.lastSourceCode) {
|
1664
|
+
let t;
|
1665
|
+
|
1666
|
+
if (options.stats) {
|
1667
|
+
t = startTime();
|
1668
|
+
}
|
1669
|
+
|
1593
1670
|
const parseResult = parse(
|
1594
1671
|
text,
|
1595
1672
|
languageOptions,
|
1596
1673
|
options.filename
|
1597
1674
|
);
|
1598
1675
|
|
1676
|
+
if (options.stats) {
|
1677
|
+
const time = endTime(t);
|
1678
|
+
|
1679
|
+
storeTime(time, { type: "parse" }, slots);
|
1680
|
+
}
|
1681
|
+
|
1599
1682
|
if (!parseResult.success) {
|
1600
1683
|
return [parseResult.error];
|
1601
1684
|
}
|
@@ -1677,22 +1760,88 @@ class Linter {
|
|
1677
1760
|
return;
|
1678
1761
|
}
|
1679
1762
|
|
1763
|
+
if (Object.hasOwn(mergedInlineConfig.rules, ruleId)) {
|
1764
|
+
inlineConfigProblems.push(createLintingProblem({
|
1765
|
+
message: `Rule "${ruleId}" is already configured by another configuration comment in the preceding code. This configuration is ignored.`,
|
1766
|
+
loc: node.loc
|
1767
|
+
}));
|
1768
|
+
return;
|
1769
|
+
}
|
1770
|
+
|
1680
1771
|
try {
|
1681
1772
|
|
1682
|
-
|
1773
|
+
let ruleOptions = Array.isArray(ruleValue) ? ruleValue : [ruleValue];
|
1683
1774
|
|
1684
|
-
assertIsRuleOptions(ruleId, ruleValue);
|
1685
1775
|
assertIsRuleSeverity(ruleId, ruleOptions[0]);
|
1686
1776
|
|
1687
|
-
|
1688
|
-
|
1689
|
-
|
1690
|
-
|
1777
|
+
/*
|
1778
|
+
* If the rule was already configured, inline rule configuration that
|
1779
|
+
* only has severity should retain options from the config and just override the severity.
|
1780
|
+
*
|
1781
|
+
* Example:
|
1782
|
+
*
|
1783
|
+
* {
|
1784
|
+
* rules: {
|
1785
|
+
* curly: ["error", "multi"]
|
1786
|
+
* }
|
1787
|
+
* }
|
1788
|
+
*
|
1789
|
+
* /* eslint curly: ["warn"] * /
|
1790
|
+
*
|
1791
|
+
* Results in:
|
1792
|
+
*
|
1793
|
+
* curly: ["warn", "multi"]
|
1794
|
+
*/
|
1795
|
+
|
1796
|
+
let shouldValidateOptions = true;
|
1797
|
+
|
1798
|
+
if (
|
1799
|
+
|
1800
|
+
/*
|
1801
|
+
* If inline config for the rule has only severity
|
1802
|
+
*/
|
1803
|
+
ruleOptions.length === 1 &&
|
1804
|
+
|
1805
|
+
/*
|
1806
|
+
* And the rule was already configured
|
1807
|
+
*/
|
1808
|
+
config.rules && Object.hasOwn(config.rules, ruleId)
|
1809
|
+
) {
|
1810
|
+
|
1811
|
+
/*
|
1812
|
+
* Then use severity from the inline config and options from the provided config
|
1813
|
+
*/
|
1814
|
+
ruleOptions = [
|
1815
|
+
ruleOptions[0], // severity from the inline config
|
1816
|
+
...config.rules[ruleId].slice(1) // options from the provided config
|
1817
|
+
];
|
1818
|
+
|
1819
|
+
// if the rule was enabled, the options have already been validated
|
1820
|
+
if (config.rules[ruleId][0] > 0) {
|
1821
|
+
shouldValidateOptions = false;
|
1691
1822
|
}
|
1692
|
-
}
|
1693
|
-
|
1823
|
+
}
|
1824
|
+
|
1825
|
+
if (shouldValidateOptions) {
|
1826
|
+
ruleValidator.validate({
|
1827
|
+
plugins: config.plugins,
|
1828
|
+
rules: {
|
1829
|
+
[ruleId]: ruleOptions
|
1830
|
+
}
|
1831
|
+
});
|
1832
|
+
}
|
1833
|
+
|
1834
|
+
mergedInlineConfig.rules[ruleId] = ruleOptions;
|
1694
1835
|
} catch (err) {
|
1695
1836
|
|
1837
|
+
/*
|
1838
|
+
* If the rule has invalid `meta.schema`, throw the error because
|
1839
|
+
* this is not an invalid inline configuration but an invalid rule.
|
1840
|
+
*/
|
1841
|
+
if (err.code === "ESLINT_INVALID_RULE_OPTIONS_SCHEMA") {
|
1842
|
+
throw err;
|
1843
|
+
}
|
1844
|
+
|
1696
1845
|
let baseMessage = err.message.slice(
|
1697
1846
|
err.message.startsWith("Key \"rules\":")
|
1698
1847
|
? err.message.indexOf(":", 12) + 1
|
@@ -1722,6 +1871,7 @@ class Linter {
|
|
1722
1871
|
: { problems: [], disableDirectives: [] };
|
1723
1872
|
|
1724
1873
|
const configuredRules = Object.assign({}, config.rules, mergedInlineConfig.rules);
|
1874
|
+
|
1725
1875
|
let lintingProblems;
|
1726
1876
|
|
1727
1877
|
sourceCode.finalize();
|
@@ -1737,7 +1887,10 @@ class Linter {
|
|
1737
1887
|
options.filename,
|
1738
1888
|
options.disableFixes,
|
1739
1889
|
slots.cwd,
|
1740
|
-
providedOptions.physicalFilename
|
1890
|
+
providedOptions.physicalFilename,
|
1891
|
+
options.ruleFilter,
|
1892
|
+
options.stats,
|
1893
|
+
slots
|
1741
1894
|
);
|
1742
1895
|
} catch (err) {
|
1743
1896
|
err.message += `\nOccurred while linting ${options.filename}`;
|
@@ -1768,7 +1921,9 @@ class Linter {
|
|
1768
1921
|
.concat(commentDirectives.problems)
|
1769
1922
|
.concat(inlineConfigProblems)
|
1770
1923
|
.sort((problemA, problemB) => problemA.line - problemB.line || problemA.column - problemB.column),
|
1771
|
-
reportUnusedDisableDirectives: options.reportUnusedDisableDirectives
|
1924
|
+
reportUnusedDisableDirectives: options.reportUnusedDisableDirectives,
|
1925
|
+
ruleFilter: options.ruleFilter,
|
1926
|
+
configuredRules
|
1772
1927
|
});
|
1773
1928
|
}
|
1774
1929
|
|
@@ -1975,6 +2130,22 @@ class Linter {
|
|
1975
2130
|
return internalSlotsMap.get(this).lastSourceCode;
|
1976
2131
|
}
|
1977
2132
|
|
2133
|
+
/**
|
2134
|
+
* Gets the times spent on (parsing, fixing, linting) a file.
|
2135
|
+
* @returns {LintTimes} The times.
|
2136
|
+
*/
|
2137
|
+
getTimes() {
|
2138
|
+
return internalSlotsMap.get(this).times ?? { passes: [] };
|
2139
|
+
}
|
2140
|
+
|
2141
|
+
/**
|
2142
|
+
* Gets the number of autofix passes that were made in the last run.
|
2143
|
+
* @returns {number} The number of autofix passes.
|
2144
|
+
*/
|
2145
|
+
getFixPassCount() {
|
2146
|
+
return internalSlotsMap.get(this).fixPasses ?? 0;
|
2147
|
+
}
|
2148
|
+
|
1978
2149
|
/**
|
1979
2150
|
* Gets the list of SuppressedLintMessage produced in the last running.
|
1980
2151
|
* @returns {SuppressedLintMessage[]} The list of SuppressedLintMessage
|
@@ -1986,17 +2157,17 @@ class Linter {
|
|
1986
2157
|
/**
|
1987
2158
|
* Defines a new linting rule.
|
1988
2159
|
* @param {string} ruleId A unique rule identifier
|
1989
|
-
* @param {
|
2160
|
+
* @param {Rule} rule A rule object
|
1990
2161
|
* @returns {void}
|
1991
2162
|
*/
|
1992
|
-
defineRule(ruleId,
|
2163
|
+
defineRule(ruleId, rule) {
|
1993
2164
|
assertEslintrcConfig(this);
|
1994
|
-
internalSlotsMap.get(this).ruleMap.define(ruleId,
|
2165
|
+
internalSlotsMap.get(this).ruleMap.define(ruleId, rule);
|
1995
2166
|
}
|
1996
2167
|
|
1997
2168
|
/**
|
1998
2169
|
* Defines many new linting rules.
|
1999
|
-
* @param {Record<string,
|
2170
|
+
* @param {Record<string, Rule>} rulesToDefine map from unique rule identifier to rule
|
2000
2171
|
* @returns {void}
|
2001
2172
|
*/
|
2002
2173
|
defineRules(rulesToDefine) {
|
@@ -2044,13 +2215,14 @@ class Linter {
|
|
2044
2215
|
* SourceCodeFixer.
|
2045
2216
|
*/
|
2046
2217
|
verifyAndFix(text, config, options) {
|
2047
|
-
let messages
|
2218
|
+
let messages,
|
2048
2219
|
fixedResult,
|
2049
2220
|
fixed = false,
|
2050
2221
|
passNumber = 0,
|
2051
2222
|
currentText = text;
|
2052
2223
|
const debugTextDescription = options && options.filename || `${text.slice(0, 10)}...`;
|
2053
2224
|
const shouldFix = options && typeof options.fix !== "undefined" ? options.fix : true;
|
2225
|
+
const stats = options?.stats;
|
2054
2226
|
|
2055
2227
|
/**
|
2056
2228
|
* This loop continues until one of the following is true:
|
@@ -2061,15 +2233,46 @@ class Linter {
|
|
2061
2233
|
* That means anytime a fix is successfully applied, there will be another pass.
|
2062
2234
|
* Essentially, guaranteeing a minimum of two passes.
|
2063
2235
|
*/
|
2236
|
+
const slots = internalSlotsMap.get(this);
|
2237
|
+
|
2238
|
+
// Remove lint times from the last run.
|
2239
|
+
if (stats) {
|
2240
|
+
delete slots.times;
|
2241
|
+
slots.fixPasses = 0;
|
2242
|
+
}
|
2243
|
+
|
2064
2244
|
do {
|
2065
2245
|
passNumber++;
|
2246
|
+
let tTotal;
|
2247
|
+
|
2248
|
+
if (stats) {
|
2249
|
+
tTotal = startTime();
|
2250
|
+
}
|
2066
2251
|
|
2067
2252
|
debug(`Linting code for ${debugTextDescription} (pass ${passNumber})`);
|
2068
2253
|
messages = this.verify(currentText, config, options);
|
2069
2254
|
|
2070
2255
|
debug(`Generating fixed text for ${debugTextDescription} (pass ${passNumber})`);
|
2256
|
+
let t;
|
2257
|
+
|
2258
|
+
if (stats) {
|
2259
|
+
t = startTime();
|
2260
|
+
}
|
2261
|
+
|
2071
2262
|
fixedResult = SourceCodeFixer.applyFixes(currentText, messages, shouldFix);
|
2072
2263
|
|
2264
|
+
if (stats) {
|
2265
|
+
|
2266
|
+
if (fixedResult.fixed) {
|
2267
|
+
const time = endTime(t);
|
2268
|
+
|
2269
|
+
storeTime(time, { type: "fix" }, slots);
|
2270
|
+
slots.fixPasses++;
|
2271
|
+
} else {
|
2272
|
+
storeTime(0, { type: "fix" }, slots);
|
2273
|
+
}
|
2274
|
+
}
|
2275
|
+
|
2073
2276
|
/*
|
2074
2277
|
* stop if there are any syntax errors.
|
2075
2278
|
* 'fixedResult.output' is a empty string.
|
@@ -2084,6 +2287,13 @@ class Linter {
|
|
2084
2287
|
// update to use the fixed output instead of the original text
|
2085
2288
|
currentText = fixedResult.output;
|
2086
2289
|
|
2290
|
+
if (stats) {
|
2291
|
+
tTotal = endTime(tTotal);
|
2292
|
+
const passIndex = slots.times.passes.length - 1;
|
2293
|
+
|
2294
|
+
slots.times.passes[passIndex].total = tTotal;
|
2295
|
+
}
|
2296
|
+
|
2087
2297
|
} while (
|
2088
2298
|
fixedResult.fixed &&
|
2089
2299
|
passNumber < MAX_AUTOFIX_PASSES
|
@@ -2094,7 +2304,18 @@ class Linter {
|
|
2094
2304
|
* the most up-to-date information.
|
2095
2305
|
*/
|
2096
2306
|
if (fixedResult.fixed) {
|
2307
|
+
let tTotal;
|
2308
|
+
|
2309
|
+
if (stats) {
|
2310
|
+
tTotal = startTime();
|
2311
|
+
}
|
2312
|
+
|
2097
2313
|
fixedResult.messages = this.verify(currentText, config, options);
|
2314
|
+
|
2315
|
+
if (stats) {
|
2316
|
+
storeTime(0, { type: "fix" }, slots);
|
2317
|
+
slots.times.passes.at(-1).total = endTime(tTotal);
|
2318
|
+
}
|
2098
2319
|
}
|
2099
2320
|
|
2100
2321
|
// ensure the last result properly reflects if fixes were done
|