eslint 8.56.0 → 9.0.0-alpha.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 +2 -2
- package/conf/rule-type-list.json +3 -1
- package/lib/api.js +1 -1
- package/lib/cli-engine/cli-engine.js +13 -2
- package/lib/cli-engine/formatters/formatters-meta.json +1 -29
- package/lib/cli.js +32 -9
- 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 +33 -5
- package/lib/linter/config-comment-parser.js +1 -1
- package/lib/linter/linter.js +91 -96
- package/lib/linter/rules.js +6 -15
- package/lib/options.js +9 -1
- package/lib/rule-tester/rule-tester.js +240 -272
- package/lib/rules/index.js +0 -2
- package/lib/rules/no-constant-binary-expression.js +1 -1
- package/lib/rules/no-constructor-return.js +1 -1
- package/lib/rules/no-empty-static-block.js +1 -1
- package/lib/rules/no-extra-semi.js +1 -1
- package/lib/rules/no-implicit-coercion.js +17 -1
- package/lib/rules/no-inner-declarations.js +1 -1
- package/lib/rules/no-invalid-regexp.js +1 -1
- package/lib/rules/no-mixed-spaces-and-tabs.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-sequences.js +1 -0
- package/lib/rules/no-unused-private-class-members.js +1 -1
- package/lib/shared/config-validator.js +44 -11
- package/lib/shared/types.js +1 -1
- package/lib/source-code/source-code.js +1 -79
- package/lib/unsupported-api.js +3 -5
- package/package.json +9 -11
- 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
@@ -1,68 +1,39 @@
|
|
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
|
17
|
+
Traverser = require("../shared/traverser"),
|
18
|
+
{ getRuleOptionsSchema } = require("../config/flat-config-helpers"),
|
51
19
|
{ Linter, SourceCodeFixer, interpolate } = require("../linter"),
|
52
20
|
CodePath = require("../linter/code-path-analysis/code-path");
|
53
21
|
|
22
|
+
const { FlatConfigArray } = require("../config/flat-config-array");
|
23
|
+
const { defaultConfig } = require("../config/default-config");
|
24
|
+
|
54
25
|
const ajv = require("../shared/ajv")({ strictDefaults: true });
|
55
26
|
|
56
|
-
const espreePath = require.resolve("espree");
|
57
27
|
const parserSymbol = Symbol.for("eslint.RuleTester.parser");
|
58
|
-
|
59
28
|
const { SourceCode } = require("../source-code");
|
29
|
+
const { ConfigArraySymbol } = require("@humanwhocodes/config-array");
|
60
30
|
|
61
31
|
//------------------------------------------------------------------------------
|
62
32
|
// Typedefs
|
63
33
|
//------------------------------------------------------------------------------
|
64
34
|
|
65
35
|
/** @typedef {import("../shared/types").Parser} Parser */
|
36
|
+
/** @typedef {import("../shared/types").LanguageOptions} LanguageOptions */
|
66
37
|
/** @typedef {import("../shared/types").Rule} Rule */
|
67
38
|
|
68
39
|
|
@@ -72,12 +43,9 @@ const { SourceCode } = require("../source-code");
|
|
72
43
|
* @property {string} [name] Name for the test case.
|
73
44
|
* @property {string} code Code for the test case.
|
74
45
|
* @property {any[]} [options] Options for the test case.
|
46
|
+
* @property {LanguageOptions} [languageOptions] The language options to use in the test case.
|
75
47
|
* @property {{ [name: string]: any }} [settings] Settings for the test case.
|
76
48
|
* @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
49
|
* @property {boolean} [only] Run only this test case or the subset of test cases with this property.
|
82
50
|
*/
|
83
51
|
|
@@ -91,10 +59,7 @@ const { SourceCode } = require("../source-code");
|
|
91
59
|
* @property {any[]} [options] Options for the test case.
|
92
60
|
* @property {{ [name: string]: any }} [settings] Settings for the test case.
|
93
61
|
* @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.
|
62
|
+
* @property {LanguageOptions} [languageOptions] The language options to use in the test case.
|
98
63
|
* @property {boolean} [only] Run only this test case or the subset of test cases with this property.
|
99
64
|
*/
|
100
65
|
|
@@ -120,7 +85,12 @@ const { SourceCode } = require("../source-code");
|
|
120
85
|
* the initial default configuration
|
121
86
|
*/
|
122
87
|
const testerDefaultConfig = { rules: {} };
|
123
|
-
|
88
|
+
|
89
|
+
/*
|
90
|
+
* RuleTester uses this config as its default. This can be overwritten via
|
91
|
+
* setDefaultConfig().
|
92
|
+
*/
|
93
|
+
let sharedDefaultConfig = { rules: {} };
|
124
94
|
|
125
95
|
/*
|
126
96
|
* List every parameters possible on a test case that are not related to eslint
|
@@ -169,36 +139,10 @@ const forbiddenMethods = [
|
|
169
139
|
"finalize"
|
170
140
|
];
|
171
141
|
|
172
|
-
|
142
|
+
/** @type {Map<string,WeakSet>} */
|
143
|
+
const forbiddenMethodCalls = new Map(forbiddenMethods.map(methodName => ([methodName, new WeakSet()])));
|
173
144
|
|
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
|
-
};
|
145
|
+
const hasOwnProperty = Function.call.bind(Object.hasOwnProperty);
|
202
146
|
|
203
147
|
/**
|
204
148
|
* Clones a given value deeply.
|
@@ -330,76 +274,6 @@ function wrapParser(parser) {
|
|
330
274
|
};
|
331
275
|
}
|
332
276
|
|
333
|
-
/**
|
334
|
-
* Function to replace `SourceCode.prototype.getComments`.
|
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.
|
346
|
-
* @param {string} methodName The name of the method to forbid.
|
347
|
-
* @returns {Function} The function that throws the error.
|
348
|
-
*/
|
349
|
-
function throwForbiddenMethodError(methodName) {
|
350
|
-
return () => {
|
351
|
-
throw new Error(
|
352
|
-
`\`SourceCode#${methodName}()\` cannot be called inside a rule.`
|
353
|
-
);
|
354
|
-
};
|
355
|
-
}
|
356
|
-
|
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
|
-
}
|
371
|
-
|
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
|
-
}
|
386
|
-
|
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
|
-
}
|
402
|
-
|
403
277
|
/**
|
404
278
|
* Emit a deprecation warning if rule uses CodePath#currentSegments.
|
405
279
|
* @param {string} ruleName Name of the rule.
|
@@ -416,20 +290,40 @@ function emitCodePathCurrentSegmentsWarning(ruleName) {
|
|
416
290
|
}
|
417
291
|
|
418
292
|
/**
|
419
|
-
*
|
420
|
-
* @param {string}
|
421
|
-
* @
|
293
|
+
* Function to replace forbidden `SourceCode` methods. Allows just one call per method.
|
294
|
+
* @param {string} methodName The name of the method to forbid.
|
295
|
+
* @param {Function} prototype The prototype with the original method to call.
|
296
|
+
* @returns {Function} The function that throws the error.
|
422
297
|
*/
|
423
|
-
function
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
298
|
+
function throwForbiddenMethodError(methodName, prototype) {
|
299
|
+
|
300
|
+
const original = prototype[methodName];
|
301
|
+
|
302
|
+
return function(...args) {
|
303
|
+
|
304
|
+
const called = forbiddenMethodCalls.get(methodName);
|
305
|
+
|
306
|
+
/* eslint-disable no-invalid-this -- needed to operate as a method. */
|
307
|
+
if (!called.has(this)) {
|
308
|
+
called.add(this);
|
309
|
+
|
310
|
+
return original.apply(this, args);
|
311
|
+
}
|
312
|
+
/* eslint-enable no-invalid-this -- not needed past this point */
|
313
|
+
|
314
|
+
throw new Error(
|
315
|
+
`\`SourceCode#${methodName}()\` cannot be called inside a rule.`
|
429
316
|
);
|
430
|
-
}
|
317
|
+
};
|
431
318
|
}
|
432
319
|
|
320
|
+
const metaSchemaDescription = `
|
321
|
+
\t- If the rule has options, set \`meta.schema\` to an array or non-empty object to enable options validation.
|
322
|
+
\t- If the rule doesn't have options, omit \`meta.schema\` to enforce that no options can be passed to the rule.
|
323
|
+
\t- You can also set \`meta.schema\` to \`false\` to opt-out of options validation (not recommended).
|
324
|
+
|
325
|
+
\thttps://eslint.org/docs/latest/extend/custom-rules#options-schemas
|
326
|
+
`;
|
433
327
|
|
434
328
|
//------------------------------------------------------------------------------
|
435
329
|
// Public Interface
|
@@ -479,26 +373,20 @@ class RuleTester {
|
|
479
373
|
* Creates a new instance of RuleTester.
|
480
374
|
* @param {Object} [testerConfig] Optional, extra configuration for the tester
|
481
375
|
*/
|
482
|
-
constructor(testerConfig) {
|
376
|
+
constructor(testerConfig = {}) {
|
483
377
|
|
484
378
|
/**
|
485
379
|
* The configuration to use for this tester. Combination of the tester
|
486
380
|
* configuration and the default configuration.
|
487
381
|
* @type {Object}
|
488
382
|
*/
|
489
|
-
this.testerConfig =
|
490
|
-
|
491
|
-
defaultConfig,
|
383
|
+
this.testerConfig = [
|
384
|
+
sharedDefaultConfig,
|
492
385
|
testerConfig,
|
493
386
|
{ rules: { "rule-tester/validate-ast": "error" } }
|
494
|
-
|
387
|
+
];
|
495
388
|
|
496
|
-
|
497
|
-
* Rule definitions to define before tests.
|
498
|
-
* @type {Object}
|
499
|
-
*/
|
500
|
-
this.rules = {};
|
501
|
-
this.linter = new Linter();
|
389
|
+
this.linter = new Linter({ configType: "flat" });
|
502
390
|
}
|
503
391
|
|
504
392
|
/**
|
@@ -511,10 +399,10 @@ class RuleTester {
|
|
511
399
|
if (typeof config !== "object" || config === null) {
|
512
400
|
throw new TypeError("RuleTester.setDefaultConfig: config must be an object");
|
513
401
|
}
|
514
|
-
|
402
|
+
sharedDefaultConfig = config;
|
515
403
|
|
516
404
|
// Make sure the rules object exists since it is assumed to exist later
|
517
|
-
|
405
|
+
sharedDefaultConfig.rules = sharedDefaultConfig.rules || {};
|
518
406
|
}
|
519
407
|
|
520
408
|
/**
|
@@ -522,7 +410,7 @@ class RuleTester {
|
|
522
410
|
* @returns {Object} the current configuration
|
523
411
|
*/
|
524
412
|
static getDefaultConfig() {
|
525
|
-
return
|
413
|
+
return sharedDefaultConfig;
|
526
414
|
}
|
527
415
|
|
528
416
|
/**
|
@@ -531,7 +419,11 @@ class RuleTester {
|
|
531
419
|
* @returns {void}
|
532
420
|
*/
|
533
421
|
static resetDefaultConfig() {
|
534
|
-
|
422
|
+
sharedDefaultConfig = {
|
423
|
+
rules: {
|
424
|
+
...testerDefaultConfig.rules
|
425
|
+
}
|
426
|
+
};
|
535
427
|
}
|
536
428
|
|
537
429
|
|
@@ -602,29 +494,17 @@ class RuleTester {
|
|
602
494
|
this[IT_ONLY] = value;
|
603
495
|
}
|
604
496
|
|
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
497
|
|
618
498
|
/**
|
619
499
|
* Adds a new rule test to execute.
|
620
500
|
* @param {string} ruleName The name of the rule to run.
|
621
|
-
* @param {
|
501
|
+
* @param {Rule} rule The rule to test.
|
622
502
|
* @param {{
|
623
503
|
* valid: (ValidTestCase | string)[],
|
624
504
|
* invalid: InvalidTestCase[]
|
625
505
|
* }} test The collection of tests to run.
|
626
|
-
* @throws {TypeError|Error} If
|
627
|
-
* scenario of the given type is missing.
|
506
|
+
* @throws {TypeError|Error} If `rule` is not an object with a `create` method,
|
507
|
+
* or if non-object `test`, or if a required scenario of the given type is missing.
|
628
508
|
* @returns {void}
|
629
509
|
*/
|
630
510
|
run(ruleName, rule, test) {
|
@@ -632,7 +512,12 @@ class RuleTester {
|
|
632
512
|
const testerConfig = this.testerConfig,
|
633
513
|
requiredScenarios = ["valid", "invalid"],
|
634
514
|
scenarioErrors = [],
|
635
|
-
linter = this.linter
|
515
|
+
linter = this.linter,
|
516
|
+
ruleId = `rule-to-test/${ruleName}`;
|
517
|
+
|
518
|
+
if (!rule || typeof rule !== "object" || typeof rule.create !== "function") {
|
519
|
+
throw new TypeError("Rule must be an object with a `create` method");
|
520
|
+
}
|
636
521
|
|
637
522
|
if (!test || typeof test !== "object") {
|
638
523
|
throw new TypeError(`Test Scenarios for rule ${ruleName} : Could not find test scenario object`);
|
@@ -650,54 +535,55 @@ class RuleTester {
|
|
650
535
|
].concat(scenarioErrors).join("\n"));
|
651
536
|
}
|
652
537
|
|
653
|
-
|
654
|
-
|
655
|
-
|
538
|
+
const baseConfig = [
|
539
|
+
{ files: ["**"] }, // Make sure the default config matches for all files
|
540
|
+
{
|
541
|
+
plugins: {
|
656
542
|
|
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
|
-
);
|
543
|
+
// copy root plugin over
|
544
|
+
"@": {
|
683
545
|
|
684
|
-
|
685
|
-
|
546
|
+
/*
|
547
|
+
* Parsers are wrapped to detect more errors, so this needs
|
548
|
+
* to be a new object for each call to run(), otherwise the
|
549
|
+
* parsers will be wrapped multiple times.
|
550
|
+
*/
|
551
|
+
parsers: {
|
552
|
+
...defaultConfig[0].plugins["@"].parsers
|
553
|
+
},
|
686
554
|
|
687
|
-
|
688
|
-
|
689
|
-
|
690
|
-
|
691
|
-
|
692
|
-
|
555
|
+
/*
|
556
|
+
* The rules key on the default plugin is a proxy to lazy-load
|
557
|
+
* just the rules that are needed. So, don't create a new object
|
558
|
+
* here, just use the default one to keep that performance
|
559
|
+
* enhancement.
|
560
|
+
*/
|
561
|
+
rules: defaultConfig[0].plugins["@"].rules
|
562
|
+
},
|
563
|
+
"rule-to-test": {
|
564
|
+
rules: {
|
565
|
+
[ruleName]: Object.assign({}, rule, {
|
693
566
|
|
694
|
-
|
567
|
+
// Create a wrapper rule that freezes the `context` properties.
|
568
|
+
create(context) {
|
569
|
+
freezeDeeply(context.options);
|
570
|
+
freezeDeeply(context.settings);
|
571
|
+
freezeDeeply(context.parserOptions);
|
695
572
|
|
696
|
-
|
697
|
-
}
|
698
|
-
}));
|
573
|
+
// freezeDeeply(context.languageOptions);
|
699
574
|
|
700
|
-
|
575
|
+
return rule.create(context);
|
576
|
+
}
|
577
|
+
})
|
578
|
+
}
|
579
|
+
}
|
580
|
+
},
|
581
|
+
languageOptions: {
|
582
|
+
...defaultConfig[0].languageOptions
|
583
|
+
}
|
584
|
+
},
|
585
|
+
...defaultConfig.slice(1)
|
586
|
+
];
|
701
587
|
|
702
588
|
/**
|
703
589
|
* Run the rule for the given item
|
@@ -707,8 +593,26 @@ class RuleTester {
|
|
707
593
|
* @private
|
708
594
|
*/
|
709
595
|
function runRuleForItem(item) {
|
710
|
-
|
711
|
-
|
596
|
+
const configs = new FlatConfigArray(testerConfig, { baseConfig });
|
597
|
+
|
598
|
+
/*
|
599
|
+
* Modify the returned config so that the parser is wrapped to catch
|
600
|
+
* access of the start/end properties. This method is called just
|
601
|
+
* once per code snippet being tested, so each test case gets a clean
|
602
|
+
* parser.
|
603
|
+
*/
|
604
|
+
configs[ConfigArraySymbol.finalizeConfig] = function(...args) {
|
605
|
+
|
606
|
+
// can't do super here :(
|
607
|
+
const proto = Object.getPrototypeOf(this);
|
608
|
+
const calculatedConfig = proto[ConfigArraySymbol.finalizeConfig].apply(this, args);
|
609
|
+
|
610
|
+
// wrap the parser to catch start/end property access
|
611
|
+
calculatedConfig.languageOptions.parser = wrapParser(calculatedConfig.languageOptions.parser);
|
612
|
+
return calculatedConfig;
|
613
|
+
};
|
614
|
+
|
615
|
+
let code, filename, output, beforeAST, afterAST;
|
712
616
|
|
713
617
|
if (typeof item === "string") {
|
714
618
|
code = item;
|
@@ -725,64 +629,93 @@ class RuleTester {
|
|
725
629
|
delete itemConfig[parameter];
|
726
630
|
}
|
727
631
|
|
632
|
+
// wrap any parsers
|
633
|
+
if (itemConfig.languageOptions && itemConfig.languageOptions.parser) {
|
634
|
+
|
635
|
+
const parser = itemConfig.languageOptions.parser;
|
636
|
+
|
637
|
+
if (parser && typeof parser !== "object") {
|
638
|
+
throw new Error("Parser must be an object with a parse() or parseForESLint() method.");
|
639
|
+
}
|
640
|
+
|
641
|
+
}
|
642
|
+
|
728
643
|
/*
|
729
644
|
* Create the config object from the tester config and this item
|
730
645
|
* specific configurations.
|
731
646
|
*/
|
732
|
-
|
733
|
-
config,
|
734
|
-
itemConfig
|
735
|
-
);
|
647
|
+
configs.push(itemConfig);
|
736
648
|
}
|
737
649
|
|
738
650
|
if (item.filename) {
|
739
651
|
filename = item.filename;
|
740
652
|
}
|
741
653
|
|
654
|
+
let ruleConfig = 1;
|
655
|
+
|
742
656
|
if (hasOwnProperty(item, "options")) {
|
743
657
|
assert(Array.isArray(item.options), "options must be an array");
|
744
|
-
|
745
|
-
|
746
|
-
|
747
|
-
|
748
|
-
|
749
|
-
|
750
|
-
) {
|
751
|
-
emitMissingSchemaWarning(ruleName);
|
658
|
+
ruleConfig = [1, ...item.options];
|
659
|
+
}
|
660
|
+
|
661
|
+
configs.push({
|
662
|
+
rules: {
|
663
|
+
[ruleId]: ruleConfig
|
752
664
|
}
|
753
|
-
|
754
|
-
|
755
|
-
|
665
|
+
});
|
666
|
+
|
667
|
+
let schema;
|
668
|
+
|
669
|
+
try {
|
670
|
+
schema = getRuleOptionsSchema(rule);
|
671
|
+
} catch (err) {
|
672
|
+
err.message += metaSchemaDescription;
|
673
|
+
throw err;
|
756
674
|
}
|
757
675
|
|
758
|
-
|
676
|
+
/*
|
677
|
+
* Check and throw an error if the schema is an empty object (`schema:{}`), because such schema
|
678
|
+
* doesn't validate or enforce anything and is therefore considered a possible error. If the intent
|
679
|
+
* was to skip options validation, `schema:false` should be set instead (explicit opt-out).
|
680
|
+
*
|
681
|
+
* For this purpose, a schema object is considered empty if it doesn't have any own enumerable string-keyed
|
682
|
+
* properties. While `ajv.compile()` does use enumerable properties from the prototype chain as well,
|
683
|
+
* it caches compiled schemas by serializing only own enumerable properties, so it's generally not a good idea
|
684
|
+
* to use inherited properties in schemas because schemas that differ only in inherited properties would end up
|
685
|
+
* having the same cache entry that would be correct for only one of them.
|
686
|
+
*
|
687
|
+
* At this point, `schema` can only be an object or `null`.
|
688
|
+
*/
|
689
|
+
if (schema && Object.keys(schema).length === 0) {
|
690
|
+
throw new Error(`\`schema: {}\` is a no-op${metaSchemaDescription}`);
|
691
|
+
}
|
759
692
|
|
760
693
|
/*
|
761
694
|
* Setup AST getters.
|
762
695
|
* The goal is to check whether or not AST was modified when
|
763
696
|
* running the rule under test.
|
764
697
|
*/
|
765
|
-
|
766
|
-
|
767
|
-
|
768
|
-
|
769
|
-
|
770
|
-
|
771
|
-
|
772
|
-
|
698
|
+
configs.push({
|
699
|
+
plugins: {
|
700
|
+
"rule-tester": {
|
701
|
+
rules: {
|
702
|
+
"validate-ast": {
|
703
|
+
create() {
|
704
|
+
return {
|
705
|
+
Program(node) {
|
706
|
+
beforeAST = cloneDeeplyExcludesParent(node);
|
707
|
+
},
|
708
|
+
"Program:exit"(node) {
|
709
|
+
afterAST = node;
|
710
|
+
}
|
711
|
+
};
|
712
|
+
}
|
713
|
+
}
|
773
714
|
}
|
774
|
-
}
|
715
|
+
}
|
775
716
|
}
|
776
717
|
});
|
777
718
|
|
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
719
|
if (schema) {
|
787
720
|
ajv.validateSchema(schema);
|
788
721
|
|
@@ -809,15 +742,21 @@ class RuleTester {
|
|
809
742
|
}
|
810
743
|
}
|
811
744
|
|
812
|
-
|
745
|
+
// check for validation errors
|
746
|
+
try {
|
747
|
+
configs.normalizeSync();
|
748
|
+
configs.getConfig("test.js");
|
749
|
+
} catch (error) {
|
750
|
+
error.message = `ESLint configuration in rule-tester is invalid: ${error.message}`;
|
751
|
+
throw error;
|
752
|
+
}
|
813
753
|
|
814
754
|
// Verify the code.
|
815
|
-
const {
|
755
|
+
const { applyLanguageOptions, applyInlineConfig, finalize } = SourceCode.prototype;
|
816
756
|
const originalCurrentSegments = Object.getOwnPropertyDescriptor(CodePath.prototype, "currentSegments");
|
817
757
|
let messages;
|
818
758
|
|
819
759
|
try {
|
820
|
-
SourceCode.prototype.getComments = getCommentsDeprecation;
|
821
760
|
Object.defineProperty(CodePath.prototype, "currentSegments", {
|
822
761
|
get() {
|
823
762
|
emitCodePathCurrentSegmentsWarning(ruleName);
|
@@ -826,18 +765,18 @@ class RuleTester {
|
|
826
765
|
});
|
827
766
|
|
828
767
|
forbiddenMethods.forEach(methodName => {
|
829
|
-
SourceCode.prototype[methodName] = throwForbiddenMethodError(methodName);
|
768
|
+
SourceCode.prototype[methodName] = throwForbiddenMethodError(methodName, SourceCode.prototype);
|
830
769
|
});
|
831
770
|
|
832
|
-
messages = linter.verify(code,
|
771
|
+
messages = linter.verify(code, configs, filename);
|
833
772
|
} finally {
|
834
|
-
SourceCode.prototype.getComments = getComments;
|
835
773
|
Object.defineProperty(CodePath.prototype, "currentSegments", originalCurrentSegments);
|
836
774
|
SourceCode.prototype.applyInlineConfig = applyInlineConfig;
|
837
775
|
SourceCode.prototype.applyLanguageOptions = applyLanguageOptions;
|
838
776
|
SourceCode.prototype.finalize = finalize;
|
839
777
|
}
|
840
778
|
|
779
|
+
|
841
780
|
const fatalErrorMessage = messages.find(m => m.fatal);
|
842
781
|
|
843
782
|
assert(!fatalErrorMessage, `A fatal parsing error occurred: ${fatalErrorMessage && fatalErrorMessage.message}`);
|
@@ -845,7 +784,7 @@ class RuleTester {
|
|
845
784
|
// Verify if autofix makes a syntax error or not.
|
846
785
|
if (messages.some(m => m.fix)) {
|
847
786
|
output = SourceCodeFixer.applyFixes(code, messages).output;
|
848
|
-
const errorMessageInFix = linter.verify(output,
|
787
|
+
const errorMessageInFix = linter.verify(output, configs, filename).find(m => m.fatal);
|
849
788
|
|
850
789
|
assert(!errorMessageInFix, [
|
851
790
|
"A fatal parsing error occurred in autofix.",
|
@@ -861,7 +800,9 @@ class RuleTester {
|
|
861
800
|
messages,
|
862
801
|
output,
|
863
802
|
beforeAST,
|
864
|
-
afterAST: cloneDeeplyExcludesParent(afterAST)
|
803
|
+
afterAST: cloneDeeplyExcludesParent(afterAST),
|
804
|
+
configs,
|
805
|
+
filename
|
865
806
|
};
|
866
807
|
}
|
867
808
|
|
@@ -950,6 +891,22 @@ class RuleTester {
|
|
950
891
|
const result = runRuleForItem(item);
|
951
892
|
const messages = result.messages;
|
952
893
|
|
894
|
+
for (const message of messages) {
|
895
|
+
if (hasOwnProperty(message, "suggestions")) {
|
896
|
+
|
897
|
+
/** @type {Map<string, number>} */
|
898
|
+
const seenMessageIndices = new Map();
|
899
|
+
|
900
|
+
for (let i = 0; i < message.suggestions.length; i += 1) {
|
901
|
+
const suggestionMessage = message.suggestions[i].desc;
|
902
|
+
const previous = seenMessageIndices.get(suggestionMessage);
|
903
|
+
|
904
|
+
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.`);
|
905
|
+
seenMessageIndices.set(suggestionMessage, i);
|
906
|
+
}
|
907
|
+
}
|
908
|
+
}
|
909
|
+
|
953
910
|
if (typeof item.errors === "number") {
|
954
911
|
|
955
912
|
if (item.errors === 0) {
|
@@ -972,7 +929,7 @@ class RuleTester {
|
|
972
929
|
)
|
973
930
|
);
|
974
931
|
|
975
|
-
const hasMessageOfThisRule = messages.some(m => m.ruleId ===
|
932
|
+
const hasMessageOfThisRule = messages.some(m => m.ruleId === ruleId);
|
976
933
|
|
977
934
|
for (let i = 0, l = item.errors.length; i < l; i++) {
|
978
935
|
const error = item.errors[i];
|
@@ -1131,6 +1088,17 @@ class RuleTester {
|
|
1131
1088
|
if (hasOwnProperty(expectedSuggestion, "output")) {
|
1132
1089
|
const codeWithAppliedSuggestion = SourceCodeFixer.applyFixes(item.code, [actualSuggestion]).output;
|
1133
1090
|
|
1091
|
+
// Verify if suggestion fix makes a syntax error or not.
|
1092
|
+
const errorMessageInSuggestion =
|
1093
|
+
linter.verify(codeWithAppliedSuggestion, result.configs, result.filename).find(m => m.fatal);
|
1094
|
+
|
1095
|
+
assert(!errorMessageInSuggestion, [
|
1096
|
+
"A fatal parsing error occurred in suggestion fix.",
|
1097
|
+
`Error: ${errorMessageInSuggestion && errorMessageInSuggestion.message}`,
|
1098
|
+
"Suggestion output:",
|
1099
|
+
codeWithAppliedSuggestion
|
1100
|
+
].join("\n"));
|
1101
|
+
|
1134
1102
|
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
1103
|
}
|
1136
1104
|
});
|