eslint 8.56.0 → 9.0.0-alpha.1
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 +7 -2
- package/conf/ecma-version.js +16 -0
- package/conf/rule-type-list.json +3 -1
- package/lib/api.js +1 -1
- 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 +46 -25
- package/lib/config/default-config.js +3 -0
- package/lib/config/flat-config-array.js +0 -20
- package/lib/config/flat-config-helpers.js +41 -20
- package/lib/config/flat-config-schema.js +35 -25
- package/lib/config/rule-validator.js +27 -4
- package/lib/eslint/eslint-helpers.js +32 -12
- package/lib/eslint/eslint.js +856 -373
- package/lib/eslint/index.js +2 -2
- package/lib/eslint/legacy-eslint.js +722 -0
- package/lib/linter/apply-disable-directives.js +35 -7
- package/lib/linter/code-path-analysis/code-path.js +5 -19
- package/lib/linter/code-path-analysis/fork-context.js +1 -1
- package/lib/linter/config-comment-parser.js +8 -11
- package/lib/linter/linter.js +196 -100
- package/lib/linter/report-translator.js +2 -2
- package/lib/linter/rules.js +6 -15
- package/lib/linter/source-code-fixer.js +1 -1
- package/lib/options.js +9 -1
- package/lib/rule-tester/rule-tester.js +234 -291
- 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/comma-dangle.js +1 -1
- package/lib/rules/comma-style.js +2 -2
- package/lib/rules/complexity.js +1 -1
- package/lib/rules/constructor-super.js +1 -1
- 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-constant-binary-expression.js +6 -6
- 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-extra-semi.js +1 -1
- package/lib/rules/no-fallthrough.js +1 -1
- package/lib/rules/no-implicit-coercion.js +17 -1
- 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 +2 -2
- package/lib/rules/no-loss-of-precision.js +1 -1
- package/lib/rules/no-misleading-character-class.js +174 -65
- 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 +2 -2
- 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-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 +6 -8
- package/lib/rules/no-useless-assignment.js +566 -0
- package/lib/rules/no-useless-backreference.js +1 -1
- 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/utils/ast-utils.js +7 -7
- package/lib/rules/yield-star-spacing.js +1 -1
- package/lib/shared/types.js +1 -1
- package/lib/source-code/source-code.js +5 -83
- package/lib/source-code/token-store/index.js +2 -2
- package/lib/unsupported-api.js +3 -5
- package/package.json +12 -14
- 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/eslint/flat-eslint.js +0 -1142
- package/lib/rule-tester/flat-rule-tester.js +0 -1122
- package/lib/rules/require-jsdoc.js +0 -122
- package/lib/rules/valid-jsdoc.js +0 -516
- package/lib/shared/config-validator.js +0 -347
- package/lib/shared/relative-module-resolver.js +0 -50
@@ -1,68 +1,38 @@
|
|
1
1
|
/**
|
2
|
-
* @fileoverview Mocha test wrapper
|
2
|
+
* @fileoverview Mocha/Jest test wrapper
|
3
3
|
* @author Ilya Volodin
|
4
4
|
*/
|
5
5
|
"use strict";
|
6
6
|
|
7
7
|
/* globals describe, it -- Mocha globals */
|
8
8
|
|
9
|
-
/*
|
10
|
-
* This is a wrapper around mocha to allow for DRY unittests for eslint
|
11
|
-
* Format:
|
12
|
-
* RuleTester.run("{ruleName}", {
|
13
|
-
* valid: [
|
14
|
-
* "{code}",
|
15
|
-
* { code: "{code}", options: {options}, globals: {globals}, parser: "{parser}", settings: {settings} }
|
16
|
-
* ],
|
17
|
-
* invalid: [
|
18
|
-
* { code: "{code}", errors: {numErrors} },
|
19
|
-
* { code: "{code}", errors: ["{errorMessage}"] },
|
20
|
-
* { code: "{code}", options: {options}, globals: {globals}, parser: "{parser}", settings: {settings}, errors: [{ message: "{errorMessage}", type: "{errorNodeType}"}] }
|
21
|
-
* ]
|
22
|
-
* });
|
23
|
-
*
|
24
|
-
* Variables:
|
25
|
-
* {code} - String that represents the code to be tested
|
26
|
-
* {options} - Arguments that are passed to the configurable rules.
|
27
|
-
* {globals} - An object representing a list of variables that are
|
28
|
-
* registered as globals
|
29
|
-
* {parser} - String representing the parser to use
|
30
|
-
* {settings} - An object representing global settings for all rules
|
31
|
-
* {numErrors} - If failing case doesn't need to check error message,
|
32
|
-
* this integer will specify how many errors should be
|
33
|
-
* received
|
34
|
-
* {errorMessage} - Message that is returned by the rule on failure
|
35
|
-
* {errorNodeType} - AST node type that is returned by they rule as
|
36
|
-
* a cause of the failure.
|
37
|
-
*/
|
38
|
-
|
39
9
|
//------------------------------------------------------------------------------
|
40
10
|
// Requirements
|
41
11
|
//------------------------------------------------------------------------------
|
42
12
|
|
43
13
|
const
|
44
14
|
assert = require("assert"),
|
45
|
-
path = require("path"),
|
46
15
|
util = require("util"),
|
47
|
-
merge = require("lodash.merge"),
|
48
16
|
equal = require("fast-deep-equal"),
|
49
|
-
Traverser = require("
|
50
|
-
{ getRuleOptionsSchema
|
51
|
-
{ Linter, SourceCodeFixer, interpolate } = require("../linter")
|
52
|
-
|
17
|
+
Traverser = require("../shared/traverser"),
|
18
|
+
{ getRuleOptionsSchema } = require("../config/flat-config-helpers"),
|
19
|
+
{ Linter, SourceCodeFixer, interpolate } = require("../linter");
|
20
|
+
|
21
|
+
const { FlatConfigArray } = require("../config/flat-config-array");
|
22
|
+
const { defaultConfig } = require("../config/default-config");
|
53
23
|
|
54
24
|
const ajv = require("../shared/ajv")({ strictDefaults: true });
|
55
25
|
|
56
|
-
const espreePath = require.resolve("espree");
|
57
26
|
const parserSymbol = Symbol.for("eslint.RuleTester.parser");
|
58
|
-
|
59
27
|
const { SourceCode } = require("../source-code");
|
28
|
+
const { ConfigArraySymbol } = require("@humanwhocodes/config-array");
|
60
29
|
|
61
30
|
//------------------------------------------------------------------------------
|
62
31
|
// Typedefs
|
63
32
|
//------------------------------------------------------------------------------
|
64
33
|
|
65
34
|
/** @typedef {import("../shared/types").Parser} Parser */
|
35
|
+
/** @typedef {import("../shared/types").LanguageOptions} LanguageOptions */
|
66
36
|
/** @typedef {import("../shared/types").Rule} Rule */
|
67
37
|
|
68
38
|
|
@@ -72,12 +42,9 @@ const { SourceCode } = require("../source-code");
|
|
72
42
|
* @property {string} [name] Name for the test case.
|
73
43
|
* @property {string} code Code for the test case.
|
74
44
|
* @property {any[]} [options] Options for the test case.
|
45
|
+
* @property {LanguageOptions} [languageOptions] The language options to use in the test case.
|
75
46
|
* @property {{ [name: string]: any }} [settings] Settings for the test case.
|
76
47
|
* @property {string} [filename] The fake filename for the test case. Useful for rules that make assertion about filenames.
|
77
|
-
* @property {string} [parser] The absolute path for the parser.
|
78
|
-
* @property {{ [name: string]: any }} [parserOptions] Options for the parser.
|
79
|
-
* @property {{ [name: string]: "readonly" | "writable" | "off" }} [globals] The additional global variables.
|
80
|
-
* @property {{ [name: string]: boolean }} [env] Environments for the test case.
|
81
48
|
* @property {boolean} [only] Run only this test case or the subset of test cases with this property.
|
82
49
|
*/
|
83
50
|
|
@@ -91,10 +58,7 @@ const { SourceCode } = require("../source-code");
|
|
91
58
|
* @property {any[]} [options] Options for the test case.
|
92
59
|
* @property {{ [name: string]: any }} [settings] Settings for the test case.
|
93
60
|
* @property {string} [filename] The fake filename for the test case. Useful for rules that make assertion about filenames.
|
94
|
-
* @property {
|
95
|
-
* @property {{ [name: string]: any }} [parserOptions] Options for the parser.
|
96
|
-
* @property {{ [name: string]: "readonly" | "writable" | "off" }} [globals] The additional global variables.
|
97
|
-
* @property {{ [name: string]: boolean }} [env] Environments for the test case.
|
61
|
+
* @property {LanguageOptions} [languageOptions] The language options to use in the test case.
|
98
62
|
* @property {boolean} [only] Run only this test case or the subset of test cases with this property.
|
99
63
|
*/
|
100
64
|
|
@@ -120,7 +84,12 @@ const { SourceCode } = require("../source-code");
|
|
120
84
|
* the initial default configuration
|
121
85
|
*/
|
122
86
|
const testerDefaultConfig = { rules: {} };
|
123
|
-
|
87
|
+
|
88
|
+
/*
|
89
|
+
* RuleTester uses this config as its default. This can be overwritten via
|
90
|
+
* setDefaultConfig().
|
91
|
+
*/
|
92
|
+
let sharedDefaultConfig = { rules: {} };
|
124
93
|
|
125
94
|
/*
|
126
95
|
* List every parameters possible on a test case that are not related to eslint
|
@@ -169,36 +138,10 @@ const forbiddenMethods = [
|
|
169
138
|
"finalize"
|
170
139
|
];
|
171
140
|
|
172
|
-
|
141
|
+
/** @type {Map<string,WeakSet>} */
|
142
|
+
const forbiddenMethodCalls = new Map(forbiddenMethods.map(methodName => ([methodName, new WeakSet()])));
|
173
143
|
|
174
|
-
const
|
175
|
-
getSource: "getText",
|
176
|
-
getSourceLines: "getLines",
|
177
|
-
getAllComments: "getAllComments",
|
178
|
-
getNodeByRangeIndex: "getNodeByRangeIndex",
|
179
|
-
|
180
|
-
// getComments: "getComments", -- already handled by a separate error
|
181
|
-
getCommentsBefore: "getCommentsBefore",
|
182
|
-
getCommentsAfter: "getCommentsAfter",
|
183
|
-
getCommentsInside: "getCommentsInside",
|
184
|
-
getJSDocComment: "getJSDocComment",
|
185
|
-
getFirstToken: "getFirstToken",
|
186
|
-
getFirstTokens: "getFirstTokens",
|
187
|
-
getLastToken: "getLastToken",
|
188
|
-
getLastTokens: "getLastTokens",
|
189
|
-
getTokenAfter: "getTokenAfter",
|
190
|
-
getTokenBefore: "getTokenBefore",
|
191
|
-
getTokenByRangeStart: "getTokenByRangeStart",
|
192
|
-
getTokens: "getTokens",
|
193
|
-
getTokensAfter: "getTokensAfter",
|
194
|
-
getTokensBefore: "getTokensBefore",
|
195
|
-
getTokensBetween: "getTokensBetween",
|
196
|
-
|
197
|
-
getScope: "getScope",
|
198
|
-
getAncestors: "getAncestors",
|
199
|
-
getDeclaredVariables: "getDeclaredVariables",
|
200
|
-
markVariableAsUsed: "markVariableAsUsed"
|
201
|
-
};
|
144
|
+
const hasOwnProperty = Function.call.bind(Object.hasOwnProperty);
|
202
145
|
|
203
146
|
/**
|
204
147
|
* Clones a given value deeply.
|
@@ -331,105 +274,40 @@ function wrapParser(parser) {
|
|
331
274
|
}
|
332
275
|
|
333
276
|
/**
|
334
|
-
* Function to replace `SourceCode.
|
335
|
-
* @returns {void}
|
336
|
-
* @throws {Error} Deprecation message.
|
337
|
-
*/
|
338
|
-
function getCommentsDeprecation() {
|
339
|
-
throw new Error(
|
340
|
-
"`SourceCode#getComments()` is deprecated and will be removed in a future major version. Use `getCommentsBefore()`, `getCommentsAfter()`, and `getCommentsInside()` instead."
|
341
|
-
);
|
342
|
-
}
|
343
|
-
|
344
|
-
/**
|
345
|
-
* Function to replace forbidden `SourceCode` methods.
|
277
|
+
* Function to replace forbidden `SourceCode` methods. Allows just one call per method.
|
346
278
|
* @param {string} methodName The name of the method to forbid.
|
279
|
+
* @param {Function} prototype The prototype with the original method to call.
|
347
280
|
* @returns {Function} The function that throws the error.
|
348
281
|
*/
|
349
|
-
function throwForbiddenMethodError(methodName) {
|
350
|
-
return () => {
|
351
|
-
throw new Error(
|
352
|
-
`\`SourceCode#${methodName}()\` cannot be called inside a rule.`
|
353
|
-
);
|
354
|
-
};
|
355
|
-
}
|
282
|
+
function throwForbiddenMethodError(methodName, prototype) {
|
356
283
|
|
357
|
-
|
358
|
-
* Emit a deprecation warning if function-style format is being used.
|
359
|
-
* @param {string} ruleName Name of the rule.
|
360
|
-
* @returns {void}
|
361
|
-
*/
|
362
|
-
function emitLegacyRuleAPIWarning(ruleName) {
|
363
|
-
if (!emitLegacyRuleAPIWarning[`warned-${ruleName}`]) {
|
364
|
-
emitLegacyRuleAPIWarning[`warned-${ruleName}`] = true;
|
365
|
-
process.emitWarning(
|
366
|
-
`"${ruleName}" rule is using the deprecated function-style format and will stop working in ESLint v9. Please use object-style format: https://eslint.org/docs/latest/extend/custom-rules`,
|
367
|
-
"DeprecationWarning"
|
368
|
-
);
|
369
|
-
}
|
370
|
-
}
|
284
|
+
const original = prototype[methodName];
|
371
285
|
|
372
|
-
|
373
|
-
* Emit a deprecation warning if rule has options but is missing the "meta.schema" property
|
374
|
-
* @param {string} ruleName Name of the rule.
|
375
|
-
* @returns {void}
|
376
|
-
*/
|
377
|
-
function emitMissingSchemaWarning(ruleName) {
|
378
|
-
if (!emitMissingSchemaWarning[`warned-${ruleName}`]) {
|
379
|
-
emitMissingSchemaWarning[`warned-${ruleName}`] = true;
|
380
|
-
process.emitWarning(
|
381
|
-
`"${ruleName}" rule has options but is missing the "meta.schema" property and will stop working in ESLint v9. Please add a schema: https://eslint.org/docs/latest/extend/custom-rules#options-schemas`,
|
382
|
-
"DeprecationWarning"
|
383
|
-
);
|
384
|
-
}
|
385
|
-
}
|
286
|
+
return function(...args) {
|
386
287
|
|
387
|
-
|
388
|
-
* Emit a deprecation warning if a rule uses a deprecated `context` method.
|
389
|
-
* @param {string} ruleName Name of the rule.
|
390
|
-
* @param {string} methodName The name of the method on `context` that was used.
|
391
|
-
* @returns {void}
|
392
|
-
*/
|
393
|
-
function emitDeprecatedContextMethodWarning(ruleName, methodName) {
|
394
|
-
if (!emitDeprecatedContextMethodWarning[`warned-${ruleName}-${methodName}`]) {
|
395
|
-
emitDeprecatedContextMethodWarning[`warned-${ruleName}-${methodName}`] = true;
|
396
|
-
process.emitWarning(
|
397
|
-
`"${ruleName}" rule is using \`context.${methodName}()\`, which is deprecated and will be removed in ESLint v9. Please use \`sourceCode.${DEPRECATED_SOURCECODE_PASSTHROUGHS[methodName]}()\` instead.`,
|
398
|
-
"DeprecationWarning"
|
399
|
-
);
|
400
|
-
}
|
401
|
-
}
|
288
|
+
const called = forbiddenMethodCalls.get(methodName);
|
402
289
|
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
* @returns {void}
|
407
|
-
*/
|
408
|
-
function emitCodePathCurrentSegmentsWarning(ruleName) {
|
409
|
-
if (!emitCodePathCurrentSegmentsWarning[`warned-${ruleName}`]) {
|
410
|
-
emitCodePathCurrentSegmentsWarning[`warned-${ruleName}`] = true;
|
411
|
-
process.emitWarning(
|
412
|
-
`"${ruleName}" rule uses CodePath#currentSegments and will stop working in ESLint v9. Please read the documentation for how to update your code: https://eslint.org/docs/latest/extend/code-path-analysis#usage-examples`,
|
413
|
-
"DeprecationWarning"
|
414
|
-
);
|
415
|
-
}
|
416
|
-
}
|
290
|
+
/* eslint-disable no-invalid-this -- needed to operate as a method. */
|
291
|
+
if (!called.has(this)) {
|
292
|
+
called.add(this);
|
417
293
|
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
if (!emitParserServicesWarning[`warned-${ruleName}`]) {
|
425
|
-
emitParserServicesWarning[`warned-${ruleName}`] = true;
|
426
|
-
process.emitWarning(
|
427
|
-
`"${ruleName}" rule is using \`context.parserServices\`, which is deprecated and will be removed in ESLint v9. Please use \`sourceCode.parserServices\` instead.`,
|
428
|
-
"DeprecationWarning"
|
294
|
+
return original.apply(this, args);
|
295
|
+
}
|
296
|
+
/* eslint-enable no-invalid-this -- not needed past this point */
|
297
|
+
|
298
|
+
throw new Error(
|
299
|
+
`\`SourceCode#${methodName}()\` cannot be called inside a rule.`
|
429
300
|
);
|
430
|
-
}
|
301
|
+
};
|
431
302
|
}
|
432
303
|
|
304
|
+
const metaSchemaDescription = `
|
305
|
+
\t- If the rule has options, set \`meta.schema\` to an array or non-empty object to enable options validation.
|
306
|
+
\t- If the rule doesn't have options, omit \`meta.schema\` to enforce that no options can be passed to the rule.
|
307
|
+
\t- You can also set \`meta.schema\` to \`false\` to opt-out of options validation (not recommended).
|
308
|
+
|
309
|
+
\thttps://eslint.org/docs/latest/extend/custom-rules#options-schemas
|
310
|
+
`;
|
433
311
|
|
434
312
|
//------------------------------------------------------------------------------
|
435
313
|
// Public Interface
|
@@ -479,26 +357,20 @@ class RuleTester {
|
|
479
357
|
* Creates a new instance of RuleTester.
|
480
358
|
* @param {Object} [testerConfig] Optional, extra configuration for the tester
|
481
359
|
*/
|
482
|
-
constructor(testerConfig) {
|
360
|
+
constructor(testerConfig = {}) {
|
483
361
|
|
484
362
|
/**
|
485
363
|
* The configuration to use for this tester. Combination of the tester
|
486
364
|
* configuration and the default configuration.
|
487
365
|
* @type {Object}
|
488
366
|
*/
|
489
|
-
this.testerConfig =
|
490
|
-
|
491
|
-
defaultConfig,
|
367
|
+
this.testerConfig = [
|
368
|
+
sharedDefaultConfig,
|
492
369
|
testerConfig,
|
493
370
|
{ rules: { "rule-tester/validate-ast": "error" } }
|
494
|
-
|
371
|
+
];
|
495
372
|
|
496
|
-
|
497
|
-
* Rule definitions to define before tests.
|
498
|
-
* @type {Object}
|
499
|
-
*/
|
500
|
-
this.rules = {};
|
501
|
-
this.linter = new Linter();
|
373
|
+
this.linter = new Linter({ configType: "flat" });
|
502
374
|
}
|
503
375
|
|
504
376
|
/**
|
@@ -511,10 +383,10 @@ class RuleTester {
|
|
511
383
|
if (typeof config !== "object" || config === null) {
|
512
384
|
throw new TypeError("RuleTester.setDefaultConfig: config must be an object");
|
513
385
|
}
|
514
|
-
|
386
|
+
sharedDefaultConfig = config;
|
515
387
|
|
516
388
|
// Make sure the rules object exists since it is assumed to exist later
|
517
|
-
|
389
|
+
sharedDefaultConfig.rules = sharedDefaultConfig.rules || {};
|
518
390
|
}
|
519
391
|
|
520
392
|
/**
|
@@ -522,7 +394,7 @@ class RuleTester {
|
|
522
394
|
* @returns {Object} the current configuration
|
523
395
|
*/
|
524
396
|
static getDefaultConfig() {
|
525
|
-
return
|
397
|
+
return sharedDefaultConfig;
|
526
398
|
}
|
527
399
|
|
528
400
|
/**
|
@@ -531,7 +403,11 @@ class RuleTester {
|
|
531
403
|
* @returns {void}
|
532
404
|
*/
|
533
405
|
static resetDefaultConfig() {
|
534
|
-
|
406
|
+
sharedDefaultConfig = {
|
407
|
+
rules: {
|
408
|
+
...testerDefaultConfig.rules
|
409
|
+
}
|
410
|
+
};
|
535
411
|
}
|
536
412
|
|
537
413
|
|
@@ -602,29 +478,17 @@ class RuleTester {
|
|
602
478
|
this[IT_ONLY] = value;
|
603
479
|
}
|
604
480
|
|
605
|
-
/**
|
606
|
-
* Define a rule for one particular run of tests.
|
607
|
-
* @param {string} name The name of the rule to define.
|
608
|
-
* @param {Function | Rule} rule The rule definition.
|
609
|
-
* @returns {void}
|
610
|
-
*/
|
611
|
-
defineRule(name, rule) {
|
612
|
-
if (typeof rule === "function") {
|
613
|
-
emitLegacyRuleAPIWarning(name);
|
614
|
-
}
|
615
|
-
this.rules[name] = rule;
|
616
|
-
}
|
617
481
|
|
618
482
|
/**
|
619
483
|
* Adds a new rule test to execute.
|
620
484
|
* @param {string} ruleName The name of the rule to run.
|
621
|
-
* @param {
|
485
|
+
* @param {Rule} rule The rule to test.
|
622
486
|
* @param {{
|
623
487
|
* valid: (ValidTestCase | string)[],
|
624
488
|
* invalid: InvalidTestCase[]
|
625
489
|
* }} test The collection of tests to run.
|
626
|
-
* @throws {TypeError|Error} If
|
627
|
-
* scenario of the given type is missing.
|
490
|
+
* @throws {TypeError|Error} If `rule` is not an object with a `create` method,
|
491
|
+
* or if non-object `test`, or if a required scenario of the given type is missing.
|
628
492
|
* @returns {void}
|
629
493
|
*/
|
630
494
|
run(ruleName, rule, test) {
|
@@ -632,7 +496,12 @@ class RuleTester {
|
|
632
496
|
const testerConfig = this.testerConfig,
|
633
497
|
requiredScenarios = ["valid", "invalid"],
|
634
498
|
scenarioErrors = [],
|
635
|
-
linter = this.linter
|
499
|
+
linter = this.linter,
|
500
|
+
ruleId = `rule-to-test/${ruleName}`;
|
501
|
+
|
502
|
+
if (!rule || typeof rule !== "object" || typeof rule.create !== "function") {
|
503
|
+
throw new TypeError("Rule must be an object with a `create` method");
|
504
|
+
}
|
636
505
|
|
637
506
|
if (!test || typeof test !== "object") {
|
638
507
|
throw new TypeError(`Test Scenarios for rule ${ruleName} : Could not find test scenario object`);
|
@@ -650,54 +519,55 @@ class RuleTester {
|
|
650
519
|
].concat(scenarioErrors).join("\n"));
|
651
520
|
}
|
652
521
|
|
653
|
-
|
654
|
-
|
655
|
-
|
522
|
+
const baseConfig = [
|
523
|
+
{ files: ["**"] }, // Make sure the default config matches for all files
|
524
|
+
{
|
525
|
+
plugins: {
|
656
526
|
|
657
|
-
|
658
|
-
|
659
|
-
// Create a wrapper rule that freezes the `context` properties.
|
660
|
-
create(context) {
|
661
|
-
freezeDeeply(context.options);
|
662
|
-
freezeDeeply(context.settings);
|
663
|
-
freezeDeeply(context.parserOptions);
|
664
|
-
|
665
|
-
// wrap all deprecated methods
|
666
|
-
const newContext = Object.create(
|
667
|
-
context,
|
668
|
-
Object.fromEntries(Object.keys(DEPRECATED_SOURCECODE_PASSTHROUGHS).map(methodName => [
|
669
|
-
methodName,
|
670
|
-
{
|
671
|
-
value(...args) {
|
672
|
-
|
673
|
-
// emit deprecation warning
|
674
|
-
emitDeprecatedContextMethodWarning(ruleName, methodName);
|
675
|
-
|
676
|
-
// call the original method
|
677
|
-
return context[methodName].call(this, ...args);
|
678
|
-
},
|
679
|
-
enumerable: true
|
680
|
-
}
|
681
|
-
]))
|
682
|
-
);
|
527
|
+
// copy root plugin over
|
528
|
+
"@": {
|
683
529
|
|
684
|
-
|
685
|
-
|
530
|
+
/*
|
531
|
+
* Parsers are wrapped to detect more errors, so this needs
|
532
|
+
* to be a new object for each call to run(), otherwise the
|
533
|
+
* parsers will be wrapped multiple times.
|
534
|
+
*/
|
535
|
+
parsers: {
|
536
|
+
...defaultConfig[0].plugins["@"].parsers
|
537
|
+
},
|
686
538
|
|
687
|
-
|
688
|
-
|
689
|
-
|
690
|
-
|
691
|
-
|
692
|
-
|
539
|
+
/*
|
540
|
+
* The rules key on the default plugin is a proxy to lazy-load
|
541
|
+
* just the rules that are needed. So, don't create a new object
|
542
|
+
* here, just use the default one to keep that performance
|
543
|
+
* enhancement.
|
544
|
+
*/
|
545
|
+
rules: defaultConfig[0].plugins["@"].rules
|
546
|
+
},
|
547
|
+
"rule-to-test": {
|
548
|
+
rules: {
|
549
|
+
[ruleName]: Object.assign({}, rule, {
|
693
550
|
|
694
|
-
|
551
|
+
// Create a wrapper rule that freezes the `context` properties.
|
552
|
+
create(context) {
|
553
|
+
freezeDeeply(context.options);
|
554
|
+
freezeDeeply(context.settings);
|
555
|
+
freezeDeeply(context.parserOptions);
|
695
556
|
|
696
|
-
|
697
|
-
}
|
698
|
-
}));
|
557
|
+
// freezeDeeply(context.languageOptions);
|
699
558
|
|
700
|
-
|
559
|
+
return rule.create(context);
|
560
|
+
}
|
561
|
+
})
|
562
|
+
}
|
563
|
+
}
|
564
|
+
},
|
565
|
+
languageOptions: {
|
566
|
+
...defaultConfig[0].languageOptions
|
567
|
+
}
|
568
|
+
},
|
569
|
+
...defaultConfig.slice(1)
|
570
|
+
];
|
701
571
|
|
702
572
|
/**
|
703
573
|
* Run the rule for the given item
|
@@ -707,8 +577,26 @@ class RuleTester {
|
|
707
577
|
* @private
|
708
578
|
*/
|
709
579
|
function runRuleForItem(item) {
|
710
|
-
|
711
|
-
|
580
|
+
const configs = new FlatConfigArray(testerConfig, { baseConfig });
|
581
|
+
|
582
|
+
/*
|
583
|
+
* Modify the returned config so that the parser is wrapped to catch
|
584
|
+
* access of the start/end properties. This method is called just
|
585
|
+
* once per code snippet being tested, so each test case gets a clean
|
586
|
+
* parser.
|
587
|
+
*/
|
588
|
+
configs[ConfigArraySymbol.finalizeConfig] = function(...args) {
|
589
|
+
|
590
|
+
// can't do super here :(
|
591
|
+
const proto = Object.getPrototypeOf(this);
|
592
|
+
const calculatedConfig = proto[ConfigArraySymbol.finalizeConfig].apply(this, args);
|
593
|
+
|
594
|
+
// wrap the parser to catch start/end property access
|
595
|
+
calculatedConfig.languageOptions.parser = wrapParser(calculatedConfig.languageOptions.parser);
|
596
|
+
return calculatedConfig;
|
597
|
+
};
|
598
|
+
|
599
|
+
let code, filename, output, beforeAST, afterAST;
|
712
600
|
|
713
601
|
if (typeof item === "string") {
|
714
602
|
code = item;
|
@@ -725,64 +613,93 @@ class RuleTester {
|
|
725
613
|
delete itemConfig[parameter];
|
726
614
|
}
|
727
615
|
|
616
|
+
// wrap any parsers
|
617
|
+
if (itemConfig.languageOptions && itemConfig.languageOptions.parser) {
|
618
|
+
|
619
|
+
const parser = itemConfig.languageOptions.parser;
|
620
|
+
|
621
|
+
if (parser && typeof parser !== "object") {
|
622
|
+
throw new Error("Parser must be an object with a parse() or parseForESLint() method.");
|
623
|
+
}
|
624
|
+
|
625
|
+
}
|
626
|
+
|
728
627
|
/*
|
729
628
|
* Create the config object from the tester config and this item
|
730
629
|
* specific configurations.
|
731
630
|
*/
|
732
|
-
|
733
|
-
config,
|
734
|
-
itemConfig
|
735
|
-
);
|
631
|
+
configs.push(itemConfig);
|
736
632
|
}
|
737
633
|
|
738
634
|
if (item.filename) {
|
739
635
|
filename = item.filename;
|
740
636
|
}
|
741
637
|
|
638
|
+
let ruleConfig = 1;
|
639
|
+
|
742
640
|
if (hasOwnProperty(item, "options")) {
|
743
641
|
assert(Array.isArray(item.options), "options must be an array");
|
744
|
-
|
745
|
-
|
746
|
-
|
747
|
-
|
748
|
-
|
749
|
-
|
750
|
-
) {
|
751
|
-
emitMissingSchemaWarning(ruleName);
|
642
|
+
ruleConfig = [1, ...item.options];
|
643
|
+
}
|
644
|
+
|
645
|
+
configs.push({
|
646
|
+
rules: {
|
647
|
+
[ruleId]: ruleConfig
|
752
648
|
}
|
753
|
-
|
754
|
-
|
755
|
-
|
649
|
+
});
|
650
|
+
|
651
|
+
let schema;
|
652
|
+
|
653
|
+
try {
|
654
|
+
schema = getRuleOptionsSchema(rule);
|
655
|
+
} catch (err) {
|
656
|
+
err.message += metaSchemaDescription;
|
657
|
+
throw err;
|
756
658
|
}
|
757
659
|
|
758
|
-
|
660
|
+
/*
|
661
|
+
* Check and throw an error if the schema is an empty object (`schema:{}`), because such schema
|
662
|
+
* doesn't validate or enforce anything and is therefore considered a possible error. If the intent
|
663
|
+
* was to skip options validation, `schema:false` should be set instead (explicit opt-out).
|
664
|
+
*
|
665
|
+
* For this purpose, a schema object is considered empty if it doesn't have any own enumerable string-keyed
|
666
|
+
* properties. While `ajv.compile()` does use enumerable properties from the prototype chain as well,
|
667
|
+
* it caches compiled schemas by serializing only own enumerable properties, so it's generally not a good idea
|
668
|
+
* to use inherited properties in schemas because schemas that differ only in inherited properties would end up
|
669
|
+
* having the same cache entry that would be correct for only one of them.
|
670
|
+
*
|
671
|
+
* At this point, `schema` can only be an object or `null`.
|
672
|
+
*/
|
673
|
+
if (schema && Object.keys(schema).length === 0) {
|
674
|
+
throw new Error(`\`schema: {}\` is a no-op${metaSchemaDescription}`);
|
675
|
+
}
|
759
676
|
|
760
677
|
/*
|
761
678
|
* Setup AST getters.
|
762
679
|
* The goal is to check whether or not AST was modified when
|
763
680
|
* running the rule under test.
|
764
681
|
*/
|
765
|
-
|
766
|
-
|
767
|
-
|
768
|
-
|
769
|
-
|
770
|
-
|
771
|
-
|
772
|
-
|
682
|
+
configs.push({
|
683
|
+
plugins: {
|
684
|
+
"rule-tester": {
|
685
|
+
rules: {
|
686
|
+
"validate-ast": {
|
687
|
+
create() {
|
688
|
+
return {
|
689
|
+
Program(node) {
|
690
|
+
beforeAST = cloneDeeplyExcludesParent(node);
|
691
|
+
},
|
692
|
+
"Program:exit"(node) {
|
693
|
+
afterAST = node;
|
694
|
+
}
|
695
|
+
};
|
696
|
+
}
|
697
|
+
}
|
773
698
|
}
|
774
|
-
}
|
699
|
+
}
|
775
700
|
}
|
776
701
|
});
|
777
702
|
|
778
|
-
if (typeof config.parser === "string") {
|
779
|
-
assert(path.isAbsolute(config.parser), "Parsers provided as strings to RuleTester must be absolute paths");
|
780
|
-
} else {
|
781
|
-
config.parser = espreePath;
|
782
|
-
}
|
783
|
-
|
784
|
-
linter.defineParser(config.parser, wrapParser(require(config.parser)));
|
785
|
-
|
786
703
|
if (schema) {
|
787
704
|
ajv.validateSchema(schema);
|
788
705
|
|
@@ -809,35 +726,32 @@ class RuleTester {
|
|
809
726
|
}
|
810
727
|
}
|
811
728
|
|
812
|
-
|
729
|
+
// check for validation errors
|
730
|
+
try {
|
731
|
+
configs.normalizeSync();
|
732
|
+
configs.getConfig("test.js");
|
733
|
+
} catch (error) {
|
734
|
+
error.message = `ESLint configuration in rule-tester is invalid: ${error.message}`;
|
735
|
+
throw error;
|
736
|
+
}
|
813
737
|
|
814
738
|
// Verify the code.
|
815
|
-
const {
|
816
|
-
const originalCurrentSegments = Object.getOwnPropertyDescriptor(CodePath.prototype, "currentSegments");
|
739
|
+
const { applyLanguageOptions, applyInlineConfig, finalize } = SourceCode.prototype;
|
817
740
|
let messages;
|
818
741
|
|
819
742
|
try {
|
820
|
-
SourceCode.prototype.getComments = getCommentsDeprecation;
|
821
|
-
Object.defineProperty(CodePath.prototype, "currentSegments", {
|
822
|
-
get() {
|
823
|
-
emitCodePathCurrentSegmentsWarning(ruleName);
|
824
|
-
return originalCurrentSegments.get.call(this);
|
825
|
-
}
|
826
|
-
});
|
827
|
-
|
828
743
|
forbiddenMethods.forEach(methodName => {
|
829
|
-
SourceCode.prototype[methodName] = throwForbiddenMethodError(methodName);
|
744
|
+
SourceCode.prototype[methodName] = throwForbiddenMethodError(methodName, SourceCode.prototype);
|
830
745
|
});
|
831
746
|
|
832
|
-
messages = linter.verify(code,
|
747
|
+
messages = linter.verify(code, configs, filename);
|
833
748
|
} finally {
|
834
|
-
SourceCode.prototype.getComments = getComments;
|
835
|
-
Object.defineProperty(CodePath.prototype, "currentSegments", originalCurrentSegments);
|
836
749
|
SourceCode.prototype.applyInlineConfig = applyInlineConfig;
|
837
750
|
SourceCode.prototype.applyLanguageOptions = applyLanguageOptions;
|
838
751
|
SourceCode.prototype.finalize = finalize;
|
839
752
|
}
|
840
753
|
|
754
|
+
|
841
755
|
const fatalErrorMessage = messages.find(m => m.fatal);
|
842
756
|
|
843
757
|
assert(!fatalErrorMessage, `A fatal parsing error occurred: ${fatalErrorMessage && fatalErrorMessage.message}`);
|
@@ -845,7 +759,7 @@ class RuleTester {
|
|
845
759
|
// Verify if autofix makes a syntax error or not.
|
846
760
|
if (messages.some(m => m.fix)) {
|
847
761
|
output = SourceCodeFixer.applyFixes(code, messages).output;
|
848
|
-
const errorMessageInFix = linter.verify(output,
|
762
|
+
const errorMessageInFix = linter.verify(output, configs, filename).find(m => m.fatal);
|
849
763
|
|
850
764
|
assert(!errorMessageInFix, [
|
851
765
|
"A fatal parsing error occurred in autofix.",
|
@@ -861,7 +775,9 @@ class RuleTester {
|
|
861
775
|
messages,
|
862
776
|
output,
|
863
777
|
beforeAST,
|
864
|
-
afterAST: cloneDeeplyExcludesParent(afterAST)
|
778
|
+
afterAST: cloneDeeplyExcludesParent(afterAST),
|
779
|
+
configs,
|
780
|
+
filename
|
865
781
|
};
|
866
782
|
}
|
867
783
|
|
@@ -950,6 +866,22 @@ class RuleTester {
|
|
950
866
|
const result = runRuleForItem(item);
|
951
867
|
const messages = result.messages;
|
952
868
|
|
869
|
+
for (const message of messages) {
|
870
|
+
if (hasOwnProperty(message, "suggestions")) {
|
871
|
+
|
872
|
+
/** @type {Map<string, number>} */
|
873
|
+
const seenMessageIndices = new Map();
|
874
|
+
|
875
|
+
for (let i = 0; i < message.suggestions.length; i += 1) {
|
876
|
+
const suggestionMessage = message.suggestions[i].desc;
|
877
|
+
const previous = seenMessageIndices.get(suggestionMessage);
|
878
|
+
|
879
|
+
assert.ok(!seenMessageIndices.has(suggestionMessage), `Suggestion message '${suggestionMessage}' reported from suggestion ${i} was previously reported by suggestion ${previous}. Suggestion messages should be unique within an error.`);
|
880
|
+
seenMessageIndices.set(suggestionMessage, i);
|
881
|
+
}
|
882
|
+
}
|
883
|
+
}
|
884
|
+
|
953
885
|
if (typeof item.errors === "number") {
|
954
886
|
|
955
887
|
if (item.errors === 0) {
|
@@ -972,7 +904,7 @@ class RuleTester {
|
|
972
904
|
)
|
973
905
|
);
|
974
906
|
|
975
|
-
const hasMessageOfThisRule = messages.some(m => m.ruleId ===
|
907
|
+
const hasMessageOfThisRule = messages.some(m => m.ruleId === ruleId);
|
976
908
|
|
977
909
|
for (let i = 0, l = item.errors.length; i < l; i++) {
|
978
910
|
const error = item.errors[i];
|
@@ -1131,6 +1063,17 @@ class RuleTester {
|
|
1131
1063
|
if (hasOwnProperty(expectedSuggestion, "output")) {
|
1132
1064
|
const codeWithAppliedSuggestion = SourceCodeFixer.applyFixes(item.code, [actualSuggestion]).output;
|
1133
1065
|
|
1066
|
+
// Verify if suggestion fix makes a syntax error or not.
|
1067
|
+
const errorMessageInSuggestion =
|
1068
|
+
linter.verify(codeWithAppliedSuggestion, result.configs, result.filename).find(m => m.fatal);
|
1069
|
+
|
1070
|
+
assert(!errorMessageInSuggestion, [
|
1071
|
+
"A fatal parsing error occurred in suggestion fix.",
|
1072
|
+
`Error: ${errorMessageInSuggestion && errorMessageInSuggestion.message}`,
|
1073
|
+
"Suggestion output:",
|
1074
|
+
codeWithAppliedSuggestion
|
1075
|
+
].join("\n"));
|
1076
|
+
|
1134
1077
|
assert.strictEqual(codeWithAppliedSuggestion, expectedSuggestion.output, `Expected the applied suggestion fix to match the test suggestion output for suggestion at index: ${index} on error with message: "${message.message}"`);
|
1135
1078
|
}
|
1136
1079
|
});
|