eslint 9.0.0-alpha.0 → 9.0.0-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -1
- package/conf/ecma-version.js +16 -0
- package/lib/cli-engine/cli-engine.js +1 -1
- package/lib/cli-engine/lint-result-cache.js +2 -2
- package/lib/cli.js +14 -16
- package/lib/eslint/eslint.js +7 -0
- package/lib/linter/apply-disable-directives.js +2 -2
- 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 +7 -10
- package/lib/linter/linter.js +105 -4
- package/lib/linter/report-translator.js +2 -2
- package/lib/linter/source-code-fixer.js +1 -1
- package/lib/rule-tester/rule-tester.js +45 -26
- 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 -0
- 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 +5 -5
- package/lib/rules/no-constructor-return.js +1 -1
- 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-fallthrough.js +1 -1
- package/lib/rules/no-implicit-coercion.js +51 -25
- package/lib/rules/no-inner-declarations.js +22 -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-multiple-empty-lines.js +1 -1
- package/lib/rules/no-restricted-globals.js +1 -1
- package/lib/rules/no-restricted-imports.js +54 -44
- package/lib/rules/no-restricted-modules.js +2 -2
- package/lib/rules/no-return-await.js +1 -1
- package/lib/rules/no-this-before-super.js +17 -4
- 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-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/serialization.js +55 -0
- package/lib/source-code/source-code.js +4 -4
- package/lib/source-code/token-store/index.js +2 -2
- package/package.json +7 -7
- package/conf/config-schema.js +0 -93
- package/lib/shared/config-validator.js +0 -380
- package/lib/shared/relative-module-resolver.js +0 -50
package/README.md
CHANGED
@@ -117,7 +117,7 @@ Yes, ESLint natively supports parsing JSX syntax (this must be enabled in [confi
|
|
117
117
|
|
118
118
|
### What ECMAScript versions does ESLint support?
|
119
119
|
|
120
|
-
ESLint has full support for ECMAScript 3, 5
|
120
|
+
ESLint has full support for ECMAScript 3, 5, and every year from 2015 up until the most recent stage 4 specification (the default). You can set your desired ECMAScript syntax and other settings (like global variables) through [configuration](https://eslint.org/docs/latest/use/configure).
|
121
121
|
|
122
122
|
### What about experimental features?
|
123
123
|
|
@@ -245,6 +245,11 @@ The people who review and fix bugs and help triage issues.
|
|
245
245
|
Bryan Mishkin
|
246
246
|
</a>
|
247
247
|
</td><td align="center" valign="top" width="11%">
|
248
|
+
<a href="https://github.com/JoshuaKGoldberg">
|
249
|
+
<img src="https://github.com/JoshuaKGoldberg.png?s=75" width="75" height="75" alt="Josh Goldberg ✨'s Avatar"><br />
|
250
|
+
Josh Goldberg ✨
|
251
|
+
</a>
|
252
|
+
</td><td align="center" valign="top" width="11%">
|
248
253
|
<a href="https://github.com/fasttime">
|
249
254
|
<img src="https://github.com/fasttime.png?s=75" width="75" height="75" alt="Francesco Trotta's Avatar"><br />
|
250
255
|
Francesco Trotta
|
@@ -0,0 +1,16 @@
|
|
1
|
+
/**
|
2
|
+
* @fileoverview Configuration related to ECMAScript versions
|
3
|
+
* @author Milos Djermanovic
|
4
|
+
*/
|
5
|
+
|
6
|
+
"use strict";
|
7
|
+
|
8
|
+
/**
|
9
|
+
* The latest ECMAScript version supported by ESLint.
|
10
|
+
* @type {number} year-based ECMAScript version
|
11
|
+
*/
|
12
|
+
const LATEST_ECMA_VERSION = 2024;
|
13
|
+
|
14
|
+
module.exports = {
|
15
|
+
LATEST_ECMA_VERSION
|
16
|
+
};
|
@@ -732,7 +732,7 @@ class CLIEngine {
|
|
732
732
|
* @returns {void}
|
733
733
|
*/
|
734
734
|
static outputFixes(report) {
|
735
|
-
report.results.filter(result => Object.
|
735
|
+
report.results.filter(result => Object.hasOwn(result, "output")).forEach(result => {
|
736
736
|
fs.writeFileSync(result.filePath, result.output);
|
737
737
|
});
|
738
738
|
}
|
@@ -164,7 +164,7 @@ class LintResultCache {
|
|
164
164
|
* @returns {void}
|
165
165
|
*/
|
166
166
|
setCachedLintResults(filePath, config, result) {
|
167
|
-
if (result && Object.
|
167
|
+
if (result && Object.hasOwn(result, "output")) {
|
168
168
|
return;
|
169
169
|
}
|
170
170
|
|
@@ -181,7 +181,7 @@ class LintResultCache {
|
|
181
181
|
* In `getCachedLintResults`, if source is explicitly null, we will
|
182
182
|
* read the file from the filesystem to set the value again.
|
183
183
|
*/
|
184
|
-
if (Object.
|
184
|
+
if (Object.hasOwn(resultToSerialize, "source")) {
|
185
185
|
resultToSerialize.source = null;
|
186
186
|
}
|
187
187
|
|
package/lib/cli.js
CHANGED
@@ -285,25 +285,23 @@ async function printResults(engine, results, format, outputFile, resultsMeta) {
|
|
285
285
|
|
286
286
|
const output = await formatter.format(results, resultsMeta);
|
287
287
|
|
288
|
-
if (
|
289
|
-
|
290
|
-
const filePath = path.resolve(process.cwd(), outputFile);
|
288
|
+
if (outputFile) {
|
289
|
+
const filePath = path.resolve(process.cwd(), outputFile);
|
291
290
|
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
291
|
+
if (await isDirectory(filePath)) {
|
292
|
+
log.error("Cannot write to output file path, it is a directory: %s", outputFile);
|
293
|
+
return false;
|
294
|
+
}
|
296
295
|
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
}
|
304
|
-
} else {
|
305
|
-
log.info(output);
|
296
|
+
try {
|
297
|
+
await mkdir(path.dirname(filePath), { recursive: true });
|
298
|
+
await writeFile(filePath, output);
|
299
|
+
} catch (ex) {
|
300
|
+
log.error("There was a problem writing the output file:\n%s", ex);
|
301
|
+
return false;
|
306
302
|
}
|
303
|
+
} else if (output) {
|
304
|
+
log.info(output);
|
307
305
|
}
|
308
306
|
|
309
307
|
return true;
|
package/lib/eslint/eslint.js
CHANGED
@@ -613,6 +613,13 @@ class ESLint {
|
|
613
613
|
});
|
614
614
|
}
|
615
615
|
|
616
|
+
// Check for the .eslintignore file, and warn if it's present.
|
617
|
+
if (existsSync(path.resolve(processedOptions.cwd, ".eslintignore"))) {
|
618
|
+
process.emitWarning(
|
619
|
+
"The \".eslintignore\" file is no longer supported. Switch to using the \"ignores\" property in \"eslint.config.js\": https://eslint.org/docs/latest/use/configure/migration-guide#ignoring-files",
|
620
|
+
"ESLintIgnoreWarning"
|
621
|
+
);
|
622
|
+
}
|
616
623
|
}
|
617
624
|
|
618
625
|
/**
|
@@ -171,7 +171,7 @@ function createCommentRemoval(directives, commentToken) {
|
|
171
171
|
return {
|
172
172
|
description: ruleIds.length <= 2
|
173
173
|
? ruleIds.join(" or ")
|
174
|
-
: `${ruleIds.slice(0, ruleIds.length - 1).join(", ")}, or ${ruleIds
|
174
|
+
: `${ruleIds.slice(0, ruleIds.length - 1).join(", ")}, or ${ruleIds.at(-1)}`,
|
175
175
|
fix: {
|
176
176
|
range,
|
177
177
|
text: " "
|
@@ -342,7 +342,7 @@ function applyDirectives(options) {
|
|
342
342
|
problem.suppressions = problem.suppressions.concat(suppressions);
|
343
343
|
} else {
|
344
344
|
problem.suppressions = suppressions;
|
345
|
-
usedDisableDirectives.add(disableDirectivesForProblem
|
345
|
+
usedDisableDirectives.add(disableDirectivesForProblem.at(-1));
|
346
346
|
}
|
347
347
|
}
|
348
348
|
|
@@ -125,20 +125,6 @@ class CodePath {
|
|
125
125
|
return this.internal.thrownForkContext;
|
126
126
|
}
|
127
127
|
|
128
|
-
/**
|
129
|
-
* Tracks the traversal of the code path through each segment. This array
|
130
|
-
* starts empty and segments are added or removed as the code path is
|
131
|
-
* traversed. This array always ends up empty at the end of a code path
|
132
|
-
* traversal. The `CodePathState` uses this to track its progress through
|
133
|
-
* the code path.
|
134
|
-
* This is a passthrough to the underlying `CodePathState`.
|
135
|
-
* @type {CodePathSegment[]}
|
136
|
-
* @deprecated
|
137
|
-
*/
|
138
|
-
get currentSegments() {
|
139
|
-
return this.internal.currentSegments;
|
140
|
-
}
|
141
|
-
|
142
128
|
/**
|
143
129
|
* Traverses all segments in this code path.
|
144
130
|
*
|
@@ -180,9 +166,9 @@ class CodePath {
|
|
180
166
|
const lastSegment = resolvedOptions.last;
|
181
167
|
|
182
168
|
// set up initial location information
|
183
|
-
let record
|
184
|
-
let index
|
185
|
-
let end
|
169
|
+
let record;
|
170
|
+
let index;
|
171
|
+
let end;
|
186
172
|
let segment = null;
|
187
173
|
|
188
174
|
// segments that have already been visited during traversal
|
@@ -191,8 +177,8 @@ class CodePath {
|
|
191
177
|
// tracks the traversal steps
|
192
178
|
const stack = [[startSegment, 0]];
|
193
179
|
|
194
|
-
//
|
195
|
-
|
180
|
+
// segments that have been skipped during traversal
|
181
|
+
const skipped = new Set();
|
196
182
|
|
197
183
|
// indicates if we exited early from the traversal
|
198
184
|
let broken = false;
|
@@ -207,11 +193,7 @@ class CodePath {
|
|
207
193
|
* @returns {void}
|
208
194
|
*/
|
209
195
|
skip() {
|
210
|
-
|
211
|
-
broken = true;
|
212
|
-
} else {
|
213
|
-
skippedSegment = stack[stack.length - 2][0];
|
214
|
-
}
|
196
|
+
skipped.add(segment);
|
215
197
|
},
|
216
198
|
|
217
199
|
/**
|
@@ -236,6 +218,18 @@ class CodePath {
|
|
236
218
|
);
|
237
219
|
}
|
238
220
|
|
221
|
+
/**
|
222
|
+
* Checks if a given previous segment has been skipped.
|
223
|
+
* @param {CodePathSegment} prevSegment A previous segment to check.
|
224
|
+
* @returns {boolean} `true` if the segment has been skipped.
|
225
|
+
*/
|
226
|
+
function isSkipped(prevSegment) {
|
227
|
+
return (
|
228
|
+
skipped.has(prevSegment) ||
|
229
|
+
segment.isLoopedPrevSegment(prevSegment)
|
230
|
+
);
|
231
|
+
}
|
232
|
+
|
239
233
|
// the traversal
|
240
234
|
while (stack.length > 0) {
|
241
235
|
|
@@ -251,7 +245,7 @@ class CodePath {
|
|
251
245
|
* Otherwise, we just read the value and sometimes modify the
|
252
246
|
* record as we traverse.
|
253
247
|
*/
|
254
|
-
record = stack
|
248
|
+
record = stack.at(-1);
|
255
249
|
segment = record[0];
|
256
250
|
index = record[1];
|
257
251
|
|
@@ -272,17 +266,21 @@ class CodePath {
|
|
272
266
|
continue;
|
273
267
|
}
|
274
268
|
|
275
|
-
// Reset the skipping flag if all branches have been skipped.
|
276
|
-
if (skippedSegment && segment.prevSegments.includes(skippedSegment)) {
|
277
|
-
skippedSegment = null;
|
278
|
-
}
|
279
269
|
visited.add(segment);
|
280
270
|
|
271
|
+
|
272
|
+
// Skips the segment if all previous segments have been skipped.
|
273
|
+
const shouldSkip = (
|
274
|
+
skipped.size > 0 &&
|
275
|
+
segment.prevSegments.length > 0 &&
|
276
|
+
segment.prevSegments.every(isSkipped)
|
277
|
+
);
|
278
|
+
|
281
279
|
/*
|
282
280
|
* If the most recent segment hasn't been skipped, then we call
|
283
281
|
* the callback, passing in the segment and the controller.
|
284
282
|
*/
|
285
|
-
if (!
|
283
|
+
if (!shouldSkip) {
|
286
284
|
resolvedCallback.call(this, segment, controller);
|
287
285
|
|
288
286
|
// exit if we're at the last segment
|
@@ -298,6 +296,10 @@ class CodePath {
|
|
298
296
|
if (broken) {
|
299
297
|
break;
|
300
298
|
}
|
299
|
+
} else {
|
300
|
+
|
301
|
+
// If the most recent segment has been skipped, then mark it as skipped.
|
302
|
+
skipped.add(segment);
|
301
303
|
}
|
302
304
|
}
|
303
305
|
|
@@ -75,11 +75,9 @@ module.exports = class ConfigCommentParser {
|
|
75
75
|
parseJsonConfig(string, location) {
|
76
76
|
debug("Parsing JSON config");
|
77
77
|
|
78
|
-
let items = {};
|
79
|
-
|
80
78
|
// Parses a JSON-like comment by the same way as parsing CLI option.
|
81
79
|
try {
|
82
|
-
items = levn.parse("Object", string) || {};
|
80
|
+
const items = levn.parse("Object", string) || {};
|
83
81
|
|
84
82
|
// Some tests say that it should ignore invalid comments such as `/*eslint no-alert:abc*/`.
|
85
83
|
// Also, commaless notations have invalid severity:
|
@@ -102,11 +100,15 @@ module.exports = class ConfigCommentParser {
|
|
102
100
|
* Optionator cannot parse commaless notations.
|
103
101
|
* But we are supporting that. So this is a fallback for that.
|
104
102
|
*/
|
105
|
-
items = {};
|
106
103
|
const normalizedString = string.replace(/([-a-zA-Z0-9/]+):/gu, "\"$1\":").replace(/(\]|[0-9])\s+(?=")/u, "$1,");
|
107
104
|
|
108
105
|
try {
|
109
|
-
items = JSON.parse(`{${normalizedString}}`);
|
106
|
+
const items = JSON.parse(`{${normalizedString}}`);
|
107
|
+
|
108
|
+
return {
|
109
|
+
success: true,
|
110
|
+
config: items
|
111
|
+
};
|
110
112
|
} catch (ex) {
|
111
113
|
debug("Manual parsing failed.");
|
112
114
|
|
@@ -124,11 +126,6 @@ module.exports = class ConfigCommentParser {
|
|
124
126
|
};
|
125
127
|
|
126
128
|
}
|
127
|
-
|
128
|
-
return {
|
129
|
-
success: true,
|
130
|
-
config: items
|
131
|
-
};
|
132
129
|
}
|
133
130
|
|
134
131
|
/**
|
package/lib/linter/linter.js
CHANGED
@@ -52,6 +52,7 @@ 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");
|
55
56
|
|
56
57
|
//------------------------------------------------------------------------------
|
57
58
|
// Typedefs
|
@@ -231,7 +232,7 @@ function addDeclaredGlobals(globalScope, configGlobals, { exportedVariables, ena
|
|
231
232
|
* @private
|
232
233
|
*/
|
233
234
|
function createMissingRuleMessage(ruleId) {
|
234
|
-
return Object.
|
235
|
+
return Object.hasOwn(ruleReplacements.rules, ruleId)
|
235
236
|
? `Rule '${ruleId}' was removed and replaced by: ${ruleReplacements.rules[ruleId].join(", ")}`
|
236
237
|
: `Definition for rule '${ruleId}' was not found.`;
|
237
238
|
}
|
@@ -594,7 +595,7 @@ function normalizeEcmaVersionForLanguageOptions(ecmaVersion) {
|
|
594
595
|
* that is used for a number of processes inside of ESLint. It's normally
|
595
596
|
* safe to assume people want the latest unless otherwise specified.
|
596
597
|
*/
|
597
|
-
return
|
598
|
+
return LATEST_ECMA_VERSION;
|
598
599
|
}
|
599
600
|
|
600
601
|
const eslintEnvPattern = /\/\*\s*eslint-env\s(.+?)(?:\*\/|$)/gsu;
|
@@ -1331,7 +1332,56 @@ class Linter {
|
|
1331
1332
|
{ exportedVariables: commentDirectives.exportedVariables, enabledGlobals: commentDirectives.enabledGlobals }
|
1332
1333
|
);
|
1333
1334
|
|
1335
|
+
/*
|
1336
|
+
* Now we determine the final configurations for rules.
|
1337
|
+
* First, let all inline rule configurations override those from the config.
|
1338
|
+
* Then, check for a special case: if a rule is configured in both places,
|
1339
|
+
* inline rule configuration that only has severity should retain options from
|
1340
|
+
* the config and just override the severity.
|
1341
|
+
*
|
1342
|
+
* Example:
|
1343
|
+
*
|
1344
|
+
* {
|
1345
|
+
* rules: {
|
1346
|
+
* curly: ["error", "multi"]
|
1347
|
+
* }
|
1348
|
+
* }
|
1349
|
+
*
|
1350
|
+
* /* eslint curly: ["warn"] * /
|
1351
|
+
*
|
1352
|
+
* Results in:
|
1353
|
+
*
|
1354
|
+
* curly: ["warn", "multi"]
|
1355
|
+
*/
|
1334
1356
|
const configuredRules = Object.assign({}, config.rules, commentDirectives.configuredRules);
|
1357
|
+
|
1358
|
+
if (config.rules) {
|
1359
|
+
for (const [ruleId, ruleInlineConfig] of Object.entries(commentDirectives.configuredRules)) {
|
1360
|
+
if (
|
1361
|
+
|
1362
|
+
/*
|
1363
|
+
* If inline config for the rule has only severity
|
1364
|
+
*/
|
1365
|
+
(!Array.isArray(ruleInlineConfig) || ruleInlineConfig.length === 1) &&
|
1366
|
+
|
1367
|
+
/*
|
1368
|
+
* And provided config for the rule has options
|
1369
|
+
*/
|
1370
|
+
Object.hasOwn(config.rules, ruleId) &&
|
1371
|
+
(Array.isArray(config.rules[ruleId]) && config.rules[ruleId].length > 1)
|
1372
|
+
) {
|
1373
|
+
|
1374
|
+
/*
|
1375
|
+
* Then use severity from the inline config and options from the provided config
|
1376
|
+
*/
|
1377
|
+
configuredRules[ruleId] = [
|
1378
|
+
Array.isArray(ruleInlineConfig) ? ruleInlineConfig[0] : ruleInlineConfig, // severity from the inline config
|
1379
|
+
...config.rules[ruleId].slice(1) // options from the provided config
|
1380
|
+
];
|
1381
|
+
}
|
1382
|
+
}
|
1383
|
+
}
|
1384
|
+
|
1335
1385
|
let lintingProblems;
|
1336
1386
|
|
1337
1387
|
try {
|
@@ -1674,7 +1724,7 @@ class Linter {
|
|
1674
1724
|
[ruleId]: ruleOptions
|
1675
1725
|
}
|
1676
1726
|
});
|
1677
|
-
mergedInlineConfig.rules[ruleId] =
|
1727
|
+
mergedInlineConfig.rules[ruleId] = ruleOptions;
|
1678
1728
|
} catch (err) {
|
1679
1729
|
|
1680
1730
|
/*
|
@@ -1713,7 +1763,58 @@ class Linter {
|
|
1713
1763
|
)
|
1714
1764
|
: { problems: [], disableDirectives: [] };
|
1715
1765
|
|
1766
|
+
/*
|
1767
|
+
* Now we determine the final configurations for rules.
|
1768
|
+
* First, let all inline rule configurations override those from the config.
|
1769
|
+
* Then, check for a special case: if a rule is configured in both places,
|
1770
|
+
* inline rule configuration that only has severity should retain options from
|
1771
|
+
* the config and just override the severity.
|
1772
|
+
*
|
1773
|
+
* Example:
|
1774
|
+
*
|
1775
|
+
* {
|
1776
|
+
* rules: {
|
1777
|
+
* curly: ["error", "multi"]
|
1778
|
+
* }
|
1779
|
+
* }
|
1780
|
+
*
|
1781
|
+
* /* eslint curly: ["warn"] * /
|
1782
|
+
*
|
1783
|
+
* Results in:
|
1784
|
+
*
|
1785
|
+
* curly: ["warn", "multi"]
|
1786
|
+
*
|
1787
|
+
* At this point, all rule configurations are normalized to arrays.
|
1788
|
+
*/
|
1716
1789
|
const configuredRules = Object.assign({}, config.rules, mergedInlineConfig.rules);
|
1790
|
+
|
1791
|
+
if (config.rules) {
|
1792
|
+
for (const [ruleId, ruleInlineConfig] of Object.entries(mergedInlineConfig.rules)) {
|
1793
|
+
if (
|
1794
|
+
|
1795
|
+
/*
|
1796
|
+
* If inline config for the rule has only severity
|
1797
|
+
*/
|
1798
|
+
ruleInlineConfig.length === 1 &&
|
1799
|
+
|
1800
|
+
/*
|
1801
|
+
* And provided config for the rule has options
|
1802
|
+
*/
|
1803
|
+
Object.hasOwn(config.rules, ruleId) &&
|
1804
|
+
config.rules[ruleId].length > 1
|
1805
|
+
) {
|
1806
|
+
|
1807
|
+
/*
|
1808
|
+
* Then use severity from the inline config and options from the provided config
|
1809
|
+
*/
|
1810
|
+
configuredRules[ruleId] = [
|
1811
|
+
ruleInlineConfig[0], // severity from the inline config
|
1812
|
+
...config.rules[ruleId].slice(1) // options from the provided config
|
1813
|
+
];
|
1814
|
+
}
|
1815
|
+
}
|
1816
|
+
}
|
1817
|
+
|
1717
1818
|
let lintingProblems;
|
1718
1819
|
|
1719
1820
|
sourceCode.finalize();
|
@@ -2039,7 +2140,7 @@ class Linter {
|
|
2039
2140
|
* SourceCodeFixer.
|
2040
2141
|
*/
|
2041
2142
|
verifyAndFix(text, config, options) {
|
2042
|
-
let messages
|
2143
|
+
let messages,
|
2043
2144
|
fixedResult,
|
2044
2145
|
fixed = false,
|
2045
2146
|
passNumber = 0,
|
@@ -160,7 +160,7 @@ function mergeFixes(fixes, sourceCode) {
|
|
160
160
|
|
161
161
|
const originalText = sourceCode.text;
|
162
162
|
const start = fixes[0].range[0];
|
163
|
-
const end = fixes
|
163
|
+
const end = fixes.at(-1).range[1];
|
164
164
|
let text = "";
|
165
165
|
let lastPos = Number.MIN_SAFE_INTEGER;
|
166
166
|
|
@@ -343,7 +343,7 @@ module.exports = function createReportTranslator(metadata) {
|
|
343
343
|
if (descriptor.message) {
|
344
344
|
throw new TypeError("context.report() called with a message and a messageId. Please only pass one.");
|
345
345
|
}
|
346
|
-
if (!messages || !Object.
|
346
|
+
if (!messages || !Object.hasOwn(messages, id)) {
|
347
347
|
throw new TypeError(`context.report() called with a messageId of '${id}' which is not present in the 'messages' config: ${JSON.stringify(messages, null, 2)}`);
|
348
348
|
}
|
349
349
|
computedMessage = messages[id];
|
@@ -107,7 +107,7 @@ SourceCodeFixer.applyFixes = function(sourceText, messages, shouldFix) {
|
|
107
107
|
}
|
108
108
|
|
109
109
|
messages.forEach(problem => {
|
110
|
-
if (Object.
|
110
|
+
if (Object.hasOwn(problem, "fix")) {
|
111
111
|
fixes.push(problem);
|
112
112
|
} else {
|
113
113
|
remainingMessages.push(problem);
|
@@ -13,11 +13,12 @@
|
|
13
13
|
const
|
14
14
|
assert = require("assert"),
|
15
15
|
util = require("util"),
|
16
|
+
path = require("path"),
|
16
17
|
equal = require("fast-deep-equal"),
|
17
18
|
Traverser = require("../shared/traverser"),
|
18
19
|
{ getRuleOptionsSchema } = require("../config/flat-config-helpers"),
|
19
20
|
{ Linter, SourceCodeFixer, interpolate } = require("../linter"),
|
20
|
-
|
21
|
+
stringify = require("json-stable-stringify-without-jsonify");
|
21
22
|
|
22
23
|
const { FlatConfigArray } = require("../config/flat-config-array");
|
23
24
|
const { defaultConfig } = require("../config/default-config");
|
@@ -27,6 +28,7 @@ const ajv = require("../shared/ajv")({ strictDefaults: true });
|
|
27
28
|
const parserSymbol = Symbol.for("eslint.RuleTester.parser");
|
28
29
|
const { SourceCode } = require("../source-code");
|
29
30
|
const { ConfigArraySymbol } = require("@humanwhocodes/config-array");
|
31
|
+
const { isSerializable } = require("../shared/serialization");
|
30
32
|
|
31
33
|
//------------------------------------------------------------------------------
|
32
34
|
// Typedefs
|
@@ -274,21 +276,6 @@ function wrapParser(parser) {
|
|
274
276
|
};
|
275
277
|
}
|
276
278
|
|
277
|
-
/**
|
278
|
-
* Emit a deprecation warning if rule uses CodePath#currentSegments.
|
279
|
-
* @param {string} ruleName Name of the rule.
|
280
|
-
* @returns {void}
|
281
|
-
*/
|
282
|
-
function emitCodePathCurrentSegmentsWarning(ruleName) {
|
283
|
-
if (!emitCodePathCurrentSegmentsWarning[`warned-${ruleName}`]) {
|
284
|
-
emitCodePathCurrentSegmentsWarning[`warned-${ruleName}`] = true;
|
285
|
-
process.emitWarning(
|
286
|
-
`"${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`,
|
287
|
-
"DeprecationWarning"
|
288
|
-
);
|
289
|
-
}
|
290
|
-
}
|
291
|
-
|
292
279
|
/**
|
293
280
|
* Function to replace forbidden `SourceCode` methods. Allows just one call per method.
|
294
281
|
* @param {string} methodName The name of the method to forbid.
|
@@ -515,6 +502,9 @@ class RuleTester {
|
|
515
502
|
linter = this.linter,
|
516
503
|
ruleId = `rule-to-test/${ruleName}`;
|
517
504
|
|
505
|
+
const seenValidTestCases = new Set();
|
506
|
+
const seenInvalidTestCases = new Set();
|
507
|
+
|
518
508
|
if (!rule || typeof rule !== "object" || typeof rule.create !== "function") {
|
519
509
|
throw new TypeError("Rule must be an object with a `create` method");
|
520
510
|
}
|
@@ -593,7 +583,15 @@ class RuleTester {
|
|
593
583
|
* @private
|
594
584
|
*/
|
595
585
|
function runRuleForItem(item) {
|
596
|
-
const
|
586
|
+
const flatConfigArrayOptions = {
|
587
|
+
baseConfig
|
588
|
+
};
|
589
|
+
|
590
|
+
if (item.filename) {
|
591
|
+
flatConfigArrayOptions.basePath = path.parse(item.filename).root;
|
592
|
+
}
|
593
|
+
|
594
|
+
const configs = new FlatConfigArray(testerConfig, flatConfigArrayOptions);
|
597
595
|
|
598
596
|
/*
|
599
597
|
* Modify the returned config so that the parser is wrapped to catch
|
@@ -753,24 +751,15 @@ class RuleTester {
|
|
753
751
|
|
754
752
|
// Verify the code.
|
755
753
|
const { applyLanguageOptions, applyInlineConfig, finalize } = SourceCode.prototype;
|
756
|
-
const originalCurrentSegments = Object.getOwnPropertyDescriptor(CodePath.prototype, "currentSegments");
|
757
754
|
let messages;
|
758
755
|
|
759
756
|
try {
|
760
|
-
Object.defineProperty(CodePath.prototype, "currentSegments", {
|
761
|
-
get() {
|
762
|
-
emitCodePathCurrentSegmentsWarning(ruleName);
|
763
|
-
return originalCurrentSegments.get.call(this);
|
764
|
-
}
|
765
|
-
});
|
766
|
-
|
767
757
|
forbiddenMethods.forEach(methodName => {
|
768
758
|
SourceCode.prototype[methodName] = throwForbiddenMethodError(methodName, SourceCode.prototype);
|
769
759
|
});
|
770
760
|
|
771
761
|
messages = linter.verify(code, configs, filename);
|
772
762
|
} finally {
|
773
|
-
Object.defineProperty(CodePath.prototype, "currentSegments", originalCurrentSegments);
|
774
763
|
SourceCode.prototype.applyInlineConfig = applyInlineConfig;
|
775
764
|
SourceCode.prototype.applyLanguageOptions = applyLanguageOptions;
|
776
765
|
SourceCode.prototype.finalize = finalize;
|
@@ -819,6 +808,32 @@ class RuleTester {
|
|
819
808
|
}
|
820
809
|
}
|
821
810
|
|
811
|
+
/**
|
812
|
+
* Check if this test case is a duplicate of one we have seen before.
|
813
|
+
* @param {Object} item test case object
|
814
|
+
* @param {Set<string>} seenTestCases set of serialized test cases we have seen so far (managed by this function)
|
815
|
+
* @returns {void}
|
816
|
+
* @private
|
817
|
+
*/
|
818
|
+
function checkDuplicateTestCase(item, seenTestCases) {
|
819
|
+
if (!isSerializable(item)) {
|
820
|
+
|
821
|
+
/*
|
822
|
+
* If we can't serialize a test case (because it contains a function, RegExp, etc), skip the check.
|
823
|
+
* This might happen with properties like: options, plugins, settings, languageOptions.parser, languageOptions.parserOptions.
|
824
|
+
*/
|
825
|
+
return;
|
826
|
+
}
|
827
|
+
|
828
|
+
const serializedTestCase = stringify(item);
|
829
|
+
|
830
|
+
assert(
|
831
|
+
!seenTestCases.has(serializedTestCase),
|
832
|
+
"detected duplicate test case"
|
833
|
+
);
|
834
|
+
seenTestCases.add(serializedTestCase);
|
835
|
+
}
|
836
|
+
|
822
837
|
/**
|
823
838
|
* Check if the template is valid or not
|
824
839
|
* all valid cases go through this
|
@@ -834,6 +849,8 @@ class RuleTester {
|
|
834
849
|
assert.ok(typeof item.name === "string", "Optional test case property 'name' must be a string");
|
835
850
|
}
|
836
851
|
|
852
|
+
checkDuplicateTestCase(item, seenValidTestCases);
|
853
|
+
|
837
854
|
const result = runRuleForItem(item);
|
838
855
|
const messages = result.messages;
|
839
856
|
|
@@ -885,6 +902,8 @@ class RuleTester {
|
|
885
902
|
assert.fail("Invalid cases must have at least one error");
|
886
903
|
}
|
887
904
|
|
905
|
+
checkDuplicateTestCase(item, seenInvalidTestCases);
|
906
|
+
|
888
907
|
const ruleHasMetaMessages = hasOwnProperty(rule, "meta") && hasOwnProperty(rule.meta, "messages");
|
889
908
|
const friendlyIDList = ruleHasMetaMessages ? `[${Object.keys(rule.meta.messages).map(key => `'${key}'`).join(", ")}]` : null;
|
890
909
|
|
@@ -199,7 +199,7 @@ module.exports = {
|
|
199
199
|
: sourceCode.getLastToken(node),
|
200
200
|
penultimate = sourceCode.getTokenBefore(last),
|
201
201
|
firstElement = node.elements[0],
|
202
|
-
lastElement = node.elements
|
202
|
+
lastElement = node.elements.at(-1);
|
203
203
|
|
204
204
|
const openingBracketMustBeSpaced =
|
205
205
|
options.objectsInArraysException && isObjectType(firstElement) ||
|