Comparing version 9.0.0-alpha.1 to 9.0.0-alpha.2
@@ -616,2 +616,9 @@ /** | ||
// Check for the .eslintignore file, and warn if it's present. | ||
if (existsSync(path.resolve(processedOptions.cwd, ".eslintignore"))) { | ||
process.emitWarning( | ||
"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", | ||
"ESLintIgnoreWarning" | ||
); | ||
} | ||
} | ||
@@ -618,0 +625,0 @@ |
@@ -180,4 +180,4 @@ /** | ||
// tracks the last skipped segment during traversal | ||
let skippedSegment = null; | ||
// segments that have been skipped during traversal | ||
const skipped = new Set(); | ||
@@ -197,7 +197,3 @@ // indicates if we exited early from the traversal | ||
skip() { | ||
if (stack.length <= 1) { | ||
broken = true; | ||
} else { | ||
skippedSegment = stack.at(-2)[0]; | ||
} | ||
skipped.add(segment); | ||
}, | ||
@@ -227,2 +223,14 @@ | ||
/** | ||
* Checks if a given previous segment has been skipped. | ||
* @param {CodePathSegment} prevSegment A previous segment to check. | ||
* @returns {boolean} `true` if the segment has been skipped. | ||
*/ | ||
function isSkipped(prevSegment) { | ||
return ( | ||
skipped.has(prevSegment) || | ||
segment.isLoopedPrevSegment(prevSegment) | ||
); | ||
} | ||
// the traversal | ||
@@ -264,8 +272,12 @@ while (stack.length > 0) { | ||
// Reset the skipping flag if all branches have been skipped. | ||
if (skippedSegment && segment.prevSegments.includes(skippedSegment)) { | ||
skippedSegment = null; | ||
} | ||
visited.add(segment); | ||
// Skips the segment if all previous segments have been skipped. | ||
const shouldSkip = ( | ||
skipped.size > 0 && | ||
segment.prevSegments.length > 0 && | ||
segment.prevSegments.every(isSkipped) | ||
); | ||
/* | ||
@@ -275,3 +287,3 @@ * If the most recent segment hasn't been skipped, then we call | ||
*/ | ||
if (!skippedSegment) { | ||
if (!shouldSkip) { | ||
resolvedCallback.call(this, segment, controller); | ||
@@ -292,2 +304,6 @@ | ||
} | ||
} else { | ||
// If the most recent segment has been skipped, then mark it as skipped. | ||
skipped.add(segment); | ||
} | ||
@@ -294,0 +310,0 @@ } |
@@ -16,6 +16,8 @@ /** | ||
util = require("util"), | ||
path = require("path"), | ||
equal = require("fast-deep-equal"), | ||
Traverser = require("../shared/traverser"), | ||
{ getRuleOptionsSchema } = require("../config/flat-config-helpers"), | ||
{ Linter, SourceCodeFixer, interpolate } = require("../linter"); | ||
{ Linter, SourceCodeFixer, interpolate } = require("../linter"), | ||
stringify = require("json-stable-stringify-without-jsonify"); | ||
@@ -30,2 +32,3 @@ const { FlatConfigArray } = require("../config/flat-config-array"); | ||
const { ConfigArraySymbol } = require("@humanwhocodes/config-array"); | ||
const { isSerializable } = require("../shared/serialization"); | ||
@@ -504,2 +507,5 @@ //------------------------------------------------------------------------------ | ||
const seenValidTestCases = new Set(); | ||
const seenInvalidTestCases = new Set(); | ||
if (!rule || typeof rule !== "object" || typeof rule.create !== "function") { | ||
@@ -583,4 +589,12 @@ throw new TypeError("Rule must be an object with a `create` method"); | ||
function runRuleForItem(item) { | ||
const configs = new FlatConfigArray(testerConfig, { baseConfig }); | ||
const flatConfigArrayOptions = { | ||
baseConfig | ||
}; | ||
if (item.filename) { | ||
flatConfigArrayOptions.basePath = path.parse(item.filename).root; | ||
} | ||
const configs = new FlatConfigArray(testerConfig, flatConfigArrayOptions); | ||
/* | ||
@@ -802,2 +816,28 @@ * Modify the returned config so that the parser is wrapped to catch | ||
/** | ||
* Check if this test case is a duplicate of one we have seen before. | ||
* @param {Object} item test case object | ||
* @param {Set<string>} seenTestCases set of serialized test cases we have seen so far (managed by this function) | ||
* @returns {void} | ||
* @private | ||
*/ | ||
function checkDuplicateTestCase(item, seenTestCases) { | ||
if (!isSerializable(item)) { | ||
/* | ||
* If we can't serialize a test case (because it contains a function, RegExp, etc), skip the check. | ||
* This might happen with properties like: options, plugins, settings, languageOptions.parser, languageOptions.parserOptions. | ||
*/ | ||
return; | ||
} | ||
const serializedTestCase = stringify(item); | ||
assert( | ||
!seenTestCases.has(serializedTestCase), | ||
"detected duplicate test case" | ||
); | ||
seenTestCases.add(serializedTestCase); | ||
} | ||
/** | ||
* Check if the template is valid or not | ||
@@ -817,2 +857,4 @@ * all valid cases go through this | ||
checkDuplicateTestCase(item, seenValidTestCases); | ||
const result = runRuleForItem(item); | ||
@@ -869,2 +911,4 @@ const messages = result.messages; | ||
checkDuplicateTestCase(item, seenInvalidTestCases); | ||
const ruleHasMetaMessages = hasOwnProperty(rule, "meta") && hasOwnProperty(rule.meta, "messages"); | ||
@@ -871,0 +915,0 @@ const friendlyIDList = ruleHasMetaMessages ? `[${Object.keys(rule.meta.messages).map(key => `'${key}'`).join(", ")}]` : null; |
@@ -191,2 +191,3 @@ /** | ||
meta: { | ||
hasSuggestions: true, | ||
type: "suggestion", | ||
@@ -233,3 +234,4 @@ | ||
messages: { | ||
useRecommendation: "use `{{recommendation}}` instead." | ||
implicitCoercion: "Unexpected implicit coercion encountered. Use `{{recommendation}}` instead.", | ||
useRecommendation: "Use `{{recommendation}}` instead." | ||
} | ||
@@ -246,12 +248,30 @@ }, | ||
* @param {string} recommendation The recommended code for the issue | ||
* @param {bool} shouldSuggest Whether this report should offer a suggestion | ||
* @param {bool} shouldFix Whether this report should fix the node | ||
* @returns {void} | ||
*/ | ||
function report(node, recommendation, shouldFix) { | ||
function report(node, recommendation, shouldSuggest, shouldFix) { | ||
/** | ||
* Fix function | ||
* @param {RuleFixer} fixer The fixer to fix. | ||
* @returns {Fix} The fix object. | ||
*/ | ||
function fix(fixer) { | ||
const tokenBefore = sourceCode.getTokenBefore(node); | ||
if ( | ||
tokenBefore?.range[1] === node.range[0] && | ||
!astUtils.canTokensBeAdjacent(tokenBefore, recommendation) | ||
) { | ||
return fixer.replaceText(node, ` ${recommendation}`); | ||
} | ||
return fixer.replaceText(node, recommendation); | ||
} | ||
context.report({ | ||
node, | ||
messageId: "useRecommendation", | ||
data: { | ||
recommendation | ||
}, | ||
messageId: "implicitCoercion", | ||
data: { recommendation }, | ||
fix(fixer) { | ||
@@ -262,13 +282,17 @@ if (!shouldFix) { | ||
const tokenBefore = sourceCode.getTokenBefore(node); | ||
return fix(fixer); | ||
}, | ||
suggest: [ | ||
{ | ||
messageId: "useRecommendation", | ||
data: { recommendation }, | ||
fix(fixer) { | ||
if (shouldFix || !shouldSuggest) { | ||
return null; | ||
} | ||
if ( | ||
tokenBefore && | ||
tokenBefore.range[1] === node.range[0] && | ||
!astUtils.canTokensBeAdjacent(tokenBefore, recommendation) | ||
) { | ||
return fixer.replaceText(node, ` ${recommendation}`); | ||
return fix(fixer); | ||
} | ||
} | ||
return fixer.replaceText(node, recommendation); | ||
} | ||
] | ||
}); | ||
@@ -285,4 +309,6 @@ } | ||
const recommendation = `Boolean(${sourceCode.getText(node.argument.argument)})`; | ||
const variable = astUtils.getVariableByName(sourceCode.getScope(node), "Boolean"); | ||
const booleanExists = variable?.identifiers.length === 0; | ||
report(node, recommendation, true); | ||
report(node, recommendation, true, booleanExists); | ||
} | ||
@@ -298,3 +324,3 @@ | ||
report(node, recommendation, false); | ||
report(node, recommendation, false, false); | ||
} | ||
@@ -307,3 +333,3 @@ | ||
report(node, recommendation, true); | ||
report(node, recommendation, true, false); | ||
} | ||
@@ -316,3 +342,3 @@ | ||
report(node, recommendation, false); | ||
report(node, recommendation, true, false); | ||
} | ||
@@ -333,3 +359,3 @@ }, | ||
report(node, recommendation, true); | ||
report(node, recommendation, true, false); | ||
} | ||
@@ -342,3 +368,3 @@ | ||
report(node, recommendation, true); | ||
report(node, recommendation, true, false); | ||
} | ||
@@ -351,3 +377,3 @@ | ||
report(node, recommendation, true); | ||
report(node, recommendation, true, false); | ||
} | ||
@@ -365,3 +391,3 @@ }, | ||
report(node, recommendation, true); | ||
report(node, recommendation, true, false); | ||
} | ||
@@ -404,3 +430,3 @@ }, | ||
report(node, recommendation, true); | ||
report(node, recommendation, true, false); | ||
} | ||
@@ -407,0 +433,0 @@ }; |
@@ -164,13 +164,21 @@ /** | ||
const restrictedPaths = (isPathAndPatternsObject ? options[0].paths : context.options) || []; | ||
const restrictedPathMessages = restrictedPaths.reduce((memo, importSource) => { | ||
const groupedRestrictedPaths = restrictedPaths.reduce((memo, importSource) => { | ||
const path = typeof importSource === "string" | ||
? importSource | ||
: importSource.name; | ||
if (!memo[path]) { | ||
memo[path] = []; | ||
} | ||
if (typeof importSource === "string") { | ||
memo[importSource] = { message: null }; | ||
memo[path].push({}); | ||
} else { | ||
memo[importSource.name] = { | ||
memo[path].push({ | ||
message: importSource.message, | ||
importNames: importSource.importNames | ||
}; | ||
}); | ||
} | ||
return memo; | ||
}, {}); | ||
}, Object.create(null)); | ||
@@ -207,20 +215,50 @@ // Handle patterns too, either as strings or groups | ||
function checkRestrictedPathAndReport(importSource, importNames, node) { | ||
if (!Object.hasOwn(restrictedPathMessages, importSource)) { | ||
if (!Object.hasOwn(groupedRestrictedPaths, importSource)) { | ||
return; | ||
} | ||
const customMessage = restrictedPathMessages[importSource].message; | ||
const restrictedImportNames = restrictedPathMessages[importSource].importNames; | ||
groupedRestrictedPaths[importSource].forEach(restrictedPathEntry => { | ||
const customMessage = restrictedPathEntry.message; | ||
const restrictedImportNames = restrictedPathEntry.importNames; | ||
if (restrictedImportNames) { | ||
if (importNames.has("*")) { | ||
const specifierData = importNames.get("*")[0]; | ||
if (restrictedImportNames) { | ||
if (importNames.has("*")) { | ||
const specifierData = importNames.get("*")[0]; | ||
context.report({ | ||
node, | ||
messageId: customMessage ? "everythingWithCustomMessage" : "everything", | ||
loc: specifierData.loc, | ||
data: { | ||
importSource, | ||
importNames: restrictedImportNames, | ||
customMessage | ||
} | ||
}); | ||
} | ||
restrictedImportNames.forEach(importName => { | ||
if (importNames.has(importName)) { | ||
const specifiers = importNames.get(importName); | ||
specifiers.forEach(specifier => { | ||
context.report({ | ||
node, | ||
messageId: customMessage ? "importNameWithCustomMessage" : "importName", | ||
loc: specifier.loc, | ||
data: { | ||
importSource, | ||
customMessage, | ||
importName | ||
} | ||
}); | ||
}); | ||
} | ||
}); | ||
} else { | ||
context.report({ | ||
node, | ||
messageId: customMessage ? "everythingWithCustomMessage" : "everything", | ||
loc: specifierData.loc, | ||
messageId: customMessage ? "pathWithCustomMessage" : "path", | ||
data: { | ||
importSource, | ||
importNames: restrictedImportNames, | ||
customMessage | ||
@@ -230,31 +268,3 @@ } | ||
} | ||
restrictedImportNames.forEach(importName => { | ||
if (importNames.has(importName)) { | ||
const specifiers = importNames.get(importName); | ||
specifiers.forEach(specifier => { | ||
context.report({ | ||
node, | ||
messageId: customMessage ? "importNameWithCustomMessage" : "importName", | ||
loc: specifier.loc, | ||
data: { | ||
importSource, | ||
customMessage, | ||
importName | ||
} | ||
}); | ||
}); | ||
} | ||
}); | ||
} else { | ||
context.report({ | ||
node, | ||
messageId: customMessage ? "pathWithCustomMessage" : "path", | ||
data: { | ||
importSource, | ||
customMessage | ||
} | ||
}); | ||
} | ||
}); | ||
} | ||
@@ -261,0 +271,0 @@ |
@@ -200,8 +200,23 @@ /** | ||
/** | ||
* A collection of nodes to avoid duplicate reports. | ||
* @type {Set<ASTNode>} | ||
*/ | ||
const reported = new Set(); | ||
codePath.traverseSegments((segment, controller) => { | ||
const info = segInfoMap[segment.id]; | ||
const invalidNodes = info.invalidNodes | ||
.filter( | ||
for (let i = 0; i < info.invalidNodes.length; ++i) { | ||
const invalidNode = info.invalidNodes[i]; | ||
/* | ||
* Avoid duplicate reports. | ||
* When there is a `finally`, invalidNodes may contain already reported node. | ||
*/ | ||
node => !reported.has(node) | ||
); | ||
for (const invalidNode of invalidNodes) { | ||
reported.add(invalidNode); | ||
context.report({ | ||
@@ -277,3 +292,2 @@ messageId: "noBeforeSuper", | ||
if (info.superCalled) { | ||
info.invalidNodes = []; | ||
controller.skip(); | ||
@@ -285,3 +299,2 @@ } else if ( | ||
info.superCalled = true; | ||
info.invalidNodes = []; | ||
} | ||
@@ -288,0 +301,0 @@ } |
{ | ||
"name": "eslint", | ||
"version": "9.0.0-alpha.1", | ||
"version": "9.0.0-alpha.2", | ||
"author": "Nicholas C. Zakas <nicholas+npm@nczconsulting.com>", | ||
@@ -69,3 +69,3 @@ "description": "An AST-based pattern checker for JavaScript.", | ||
"@eslint/eslintrc": "^3.0.0", | ||
"@eslint/js": "9.0.0-alpha.0", | ||
"@eslint/js": "9.0.0-alpha.2", | ||
"@humanwhocodes/config-array": "^0.11.14", | ||
@@ -81,3 +81,3 @@ "@humanwhocodes/module-importer": "^1.0.1", | ||
"eslint-visitor-keys": "^3.4.3", | ||
"espree": "^9.6.1", | ||
"espree": "^10.0.0", | ||
"esquery": "^1.4.2", | ||
@@ -140,3 +140,3 @@ "esutils": "^2.0.2", | ||
"markdown-it-container": "^3.0.0", | ||
"markdownlint": "^0.32.0", | ||
"markdownlint": "^0.33.0", | ||
"markdownlint-cli": "^0.38.0", | ||
@@ -143,0 +143,0 @@ "marked": "^4.0.8", |
@@ -120,3 +120,3 @@ [![npm version](https://img.shields.io/npm/v/eslint.svg)](https://www.npmjs.com/package/eslint) | ||
ESLint has full support for ECMAScript 3, 5 (default), 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, and 2023. You can set your desired ECMAScript syntax (and other settings, like global variables or your target environments) through [configuration](https://eslint.org/docs/latest/use/configure). | ||
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). | ||
@@ -123,0 +123,0 @@ ### What about experimental features? |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
2973447
398
70064
+ Added@eslint/js@9.0.0-alpha.2(transitive)
- Removed@eslint/js@9.0.0-alpha.0(transitive)
- Removedespree@9.6.1(transitive)
Updated@eslint/js@9.0.0-alpha.2
Updatedespree@^10.0.0