-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
chore(deps-dev): bump eslint from 8.43.0 to 8.48.0 #977
Merged
Conversation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Bumps [eslint](https://github.com/eslint/eslint) from 8.43.0 to 8.48.0. - [Release notes](https://github.com/eslint/eslint/releases) - [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md) - [Commits](eslint/eslint@v8.43.0...v8.48.0) --- updated-dependencies: - dependency-name: eslint dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com>
dependabot
bot
added
dependencies
Pull requests that update a dependency file
javascript
Pull requests that update Javascript code
labels
Sep 1, 2023
Diff between eslint 8.43.0 and 8.48.0diff --git a/lib/rules/accessor-pairs.js b/lib/rules/accessor-pairs.js
index v8.43.0..v8.48.0 100644
--- a/lib/rules/accessor-pairs.js
+++ b/lib/rules/accessor-pairs.js
@@ -225,51 +225,43 @@
/**
- * Creates a new `AccessorData` object for the given getter or setter node.
- * @param {ASTNode} node A getter or setter node.
- * @returns {AccessorData} New `AccessorData` object that contains the given node.
+ * Checks accessor pairs in the given list of nodes.
+ * @param {ASTNode[]} nodes The list to check.
+ * @returns {void}
* @private
*/
- function createAccessorData(node) {
- const name = astUtils.getStaticPropertyName(node);
- const key = (name !== null) ? name : sourceCode.getTokens(node.key);
+ function checkList(nodes) {
+ const accessors = [];
+ let found = false;
- return {
- key,
- getters: node.kind === "get" ? [node] : [],
- setters: node.kind === "set" ? [node] : []
- };
- }
+ for (let i = 0; i < nodes.length; i++) {
+ const node = nodes[i];
- /**
- * Merges the given `AccessorData` object into the given accessors list.
- * @param {AccessorData[]} accessors The list to merge into.
- * @param {AccessorData} accessorData The object to merge.
- * @returns {AccessorData[]} The same instance with the merged object.
- * @private
- */
- function mergeAccessorData(accessors, accessorData) {
- const equalKeyElement = accessors.find(a => areEqualKeys(a.key, accessorData.key));
+ if (isAccessorKind(node)) {
- if (equalKeyElement) {
- equalKeyElement.getters.push(...accessorData.getters);
- equalKeyElement.setters.push(...accessorData.setters);
- } else {
- accessors.push(accessorData);
- }
+ // Creates a new `AccessorData` object for the given getter or setter node.
+ const name = astUtils.getStaticPropertyName(node);
+ const key = (name !== null) ? name : sourceCode.getTokens(node.key);
- return accessors;
- }
+ // Merges the given `AccessorData` object into the given accessors list.
+ for (let j = 0; j < accessors.length; j++) {
+ const accessor = accessors[j];
- /**
- * Checks accessor pairs in the given list of nodes.
- * @param {ASTNode[]} nodes The list to check.
- * @returns {void}
- * @private
- */
- function checkList(nodes) {
- const accessors = nodes
- .filter(isAccessorKind)
- .map(createAccessorData)
- .reduce(mergeAccessorData, []);
+ if (areEqualKeys(accessor.key, key)) {
+ accessor.getters.push(...node.kind === "get" ? [node] : []);
+ accessor.setters.push(...node.kind === "set" ? [node] : []);
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ accessors.push({
+ key,
+ getters: node.kind === "get" ? [node] : [],
+ setters: node.kind === "set" ? [node] : []
+ });
+ }
+ found = false;
+ }
+ }
for (const { getters, setters } of accessors) {
diff --git a/lib/rules/array-element-newline.js b/lib/rules/array-element-newline.js
index v8.43.0..v8.48.0 100644
--- a/lib/rules/array-element-newline.js
+++ b/lib/rules/array-element-newline.js
@@ -241,9 +241,13 @@
}
- const linebreaksCount = node.elements.map((element, i) => {
+ let linebreaksCount = 0;
+
+ for (let i = 0; i < node.elements.length; i++) {
+ const element = node.elements[i];
+
const previousElement = elements[i - 1];
if (i === 0 || element === null || previousElement === null) {
- return false;
+ continue;
}
@@ -252,6 +256,8 @@
const firstTokenOfCurrentElement = sourceCode.getTokenAfter(commaToken);
- return !astUtils.isTokenOnSameLine(lastTokenOfPreviousElement, firstTokenOfCurrentElement);
- }).filter(isBreak => isBreak === true).length;
+ if (!astUtils.isTokenOnSameLine(lastTokenOfPreviousElement, firstTokenOfCurrentElement)) {
+ linebreaksCount++;
+ }
+ }
const needsLinebreaks = (
diff --git a/lib/rules/utils/ast-utils.js b/lib/rules/utils/ast-utils.js
index v8.43.0..v8.48.0 100644
--- a/lib/rules/utils/ast-utils.js
+++ b/lib/rules/utils/ast-utils.js
@@ -10,4 +10,5 @@
//------------------------------------------------------------------------------
+const { KEYS: eslintVisitorKeys } = require("eslint-visitor-keys");
const esutils = require("esutils");
const espree = require("espree");
@@ -26,6 +27,6 @@
const anyFunctionPattern = /^(?:Function(?:Declaration|Expression)|ArrowFunctionExpression)$/u;
const anyLoopPattern = /^(?:DoWhile|For|ForIn|ForOf|While)Statement$/u;
+const arrayMethodWithThisArgPattern = /^(?:every|filter|find(?:Last)?(?:Index)?|flatMap|forEach|map|some)$/u;
const arrayOrTypedArrayPattern = /Array$/u;
-const arrayMethodPattern = /^(?:every|filter|find|findIndex|forEach|map|some)$/u;
const bindOrCallOrApplyPattern = /^(?:bind|call|apply)$/u;
const thisTagPattern = /^[\s*]*@this/mu;
@@ -467,10 +468,10 @@
/**
- * Checks whether or not a node is a method which has `thisArg`.
+ * Checks whether or not a node is a method which expects a function as a first argument, and `thisArg` as a second argument.
* @param {ASTNode} node A node to check.
- * @returns {boolean} Whether or not the node is a method which has `thisArg`.
+ * @returns {boolean} Whether or not the node is a method which expects a function as a first argument, and `thisArg` as a second argument.
*/
function isMethodWhichHasThisArg(node) {
- return isSpecificMemberAccess(node, null, arrayMethodPattern);
+ return isSpecificMemberAccess(node, null, arrayMethodWithThisArgPattern);
}
@@ -1006,4 +1007,13 @@
}
+/**
+ * Check whether the given node is a part of a directive prologue or not.
+ * @param {ASTNode} node The node to check.
+ * @returns {boolean} `true` if the node is a part of directive prologue.
+ */
+function isDirective(node) {
+ return node.type === "ExpressionStatement" && typeof node.directive === "string";
+}
+
//------------------------------------------------------------------------------
// Public Interface
@@ -1462,5 +1472,14 @@
default:
- return 20;
+ if (node.type in eslintVisitorKeys) {
+ return 20;
+ }
+
+ /*
+ * if the node is not a standard node that we know about, then assume it has the lowest precedence
+ * this will mean that rules will wrap unknown nodes in parentheses where applicable instead of
+ * unwrapping them and potentially changing the meaning of the code or introducing a syntax error.
+ */
+ return -1;
}
},
@@ -2124,4 +2143,13 @@
},
+ /**
+ * Determines whether the given node is a template literal without expressions.
+ * @param {ASTNode} node Node to check.
+ * @returns {boolean} True if the node is a template literal without expressions.
+ */
+ isStaticTemplateLiteral(node) {
+ return node.type === "TemplateLiteral" && node.expressions.length === 0;
+ },
+
isReferenceToGlobalVariable,
isLogicalExpression,
@@ -2140,4 +2168,5 @@
getModuleExportName,
isConstant,
- isTopLevelExpressionStatement
+ isTopLevelExpressionStatement,
+ isDirective
};
diff --git a/lib/cli-engine/cli-engine.js b/lib/cli-engine/cli-engine.js
index v8.43.0..v8.48.0 100644
--- a/lib/cli-engine/cli-engine.js
+++ b/lib/cli-engine/cli-engine.js
@@ -159,5 +159,15 @@
*/
function calculateStatsPerFile(messages) {
- return messages.reduce((stat, message) => {
+ const stat = {
+ errorCount: 0,
+ fatalErrorCount: 0,
+ warningCount: 0,
+ fixableErrorCount: 0,
+ fixableWarningCount: 0
+ };
+
+ for (let i = 0; i < messages.length; i++) {
+ const message = messages[i];
+
if (message.fatal || message.severity === 2) {
stat.errorCount++;
@@ -174,12 +184,6 @@
}
}
- return stat;
- }, {
- errorCount: 0,
- fatalErrorCount: 0,
- warningCount: 0,
- fixableErrorCount: 0,
- fixableWarningCount: 0
- });
+ }
+ return stat;
}
@@ -191,5 +195,15 @@
*/
function calculateStatsPerRun(results) {
- return results.reduce((stat, result) => {
+ const stat = {
+ errorCount: 0,
+ fatalErrorCount: 0,
+ warningCount: 0,
+ fixableErrorCount: 0,
+ fixableWarningCount: 0
+ };
+
+ for (let i = 0; i < results.length; i++) {
+ const result = results[i];
+
stat.errorCount += result.errorCount;
stat.fatalErrorCount += result.fatalErrorCount;
@@ -197,12 +211,7 @@
stat.fixableErrorCount += result.fixableErrorCount;
stat.fixableWarningCount += result.fixableWarningCount;
- return stat;
- }, {
- errorCount: 0,
- fatalErrorCount: 0,
- warningCount: 0,
- fixableErrorCount: 0,
- fixableWarningCount: 0
- });
+ }
+
+ return stat;
}
diff --git a/lib/linter/code-path-analysis/code-path-segment.js b/lib/linter/code-path-analysis/code-path-segment.js
index v8.43.0..v8.48.0 100644
--- a/lib/linter/code-path-analysis/code-path-segment.js
+++ b/lib/linter/code-path-analysis/code-path-segment.js
@@ -1,4 +1,4 @@
/**
- * @fileoverview A class of the code path segment.
+ * @fileoverview The CodePathSegment class.
* @author Toru Nagashima
*/
@@ -31,8 +31,20 @@
/**
* A code path segment.
+ *
+ * Each segment is arranged in a series of linked lists (implemented by arrays)
+ * that keep track of the previous and next segments in a code path. In this way,
+ * you can navigate between all segments in any code path so long as you have a
+ * reference to any segment in that code path.
+ *
+ * When first created, the segment is in a detached state, meaning that it knows the
+ * segments that came before it but those segments don't know that this new segment
+ * follows it. Only when `CodePathSegment#markUsed()` is called on a segment does it
+ * officially become part of the code path by updating the previous segments to know
+ * that this new segment follows.
*/
class CodePathSegment {
/**
+ * Creates a new instance.
* @param {string} id An identifier.
* @param {CodePathSegment[]} allPrevSegments An array of the previous segments.
@@ -50,5 +62,5 @@
/**
- * An array of the next segments.
+ * An array of the next reachable segments.
* @type {CodePathSegment[]}
*/
@@ -56,5 +68,5 @@
/**
- * An array of the previous segments.
+ * An array of the previous reachable segments.
* @type {CodePathSegment[]}
*/
@@ -62,6 +74,5 @@
/**
- * An array of the next segments.
- * This array includes unreachable segments.
+ * An array of all next segments including reachable and unreachable.
* @type {CodePathSegment[]}
*/
@@ -69,6 +80,5 @@
/**
- * An array of the previous segments.
- * This array includes unreachable segments.
+ * An array of all previous segments including reachable and unreachable.
* @type {CodePathSegment[]}
*/
@@ -84,5 +94,9 @@
Object.defineProperty(this, "internal", {
value: {
+
+ // determines if the segment has been attached to the code path
used: false,
+
+ // array of previous segments coming from the end of a loop
loopedPrevSegments: []
}
@@ -114,7 +128,8 @@
/**
- * Creates a segment that follows given segments.
+ * Creates a new segment and appends it after the given segments.
* @param {string} id An identifier.
- * @param {CodePathSegment[]} allPrevSegments An array of the previous segments.
+ * @param {CodePathSegment[]} allPrevSegments An array of the previous segments
+ * to append to.
* @returns {CodePathSegment} The created segment.
*/
@@ -128,5 +143,5 @@
/**
- * Creates an unreachable segment that follows given segments.
+ * Creates an unreachable segment and appends it after the given segments.
* @param {string} id An identifier.
* @param {CodePathSegment[]} allPrevSegments An array of the previous segments.
@@ -138,5 +153,5 @@
/*
* In `if (a) return a; foo();` case, the unreachable segment preceded by
- * the return statement is not used but must not be remove.
+ * the return statement is not used but must not be removed.
*/
CodePathSegment.markUsed(segment);
@@ -158,5 +173,5 @@
/**
- * Makes a given segment being used.
+ * Marks a given segment as used.
*
* And this function registers the segment into the previous segments as a next.
@@ -173,4 +188,11 @@
if (segment.reachable) {
+
+ /*
+ * If the segment is reachable, then it's officially part of the
+ * code path. This loops through all previous segments to update
+ * their list of next segments. Because the segment is reachable,
+ * it's added to both `nextSegments` and `allNextSegments`.
+ */
for (i = 0; i < segment.allPrevSegments.length; ++i) {
const prevSegment = segment.allPrevSegments[i];
@@ -180,4 +202,11 @@
}
} else {
+
+ /*
+ * If the segment is not reachable, then it's not officially part of the
+ * code path. This loops through all previous segments to update
+ * their list of next segments. Because the segment is not reachable,
+ * it's added only to `allNextSegments`.
+ */
for (i = 0; i < segment.allPrevSegments.length; ++i) {
segment.allPrevSegments[i].allNextSegments.push(segment);
@@ -197,11 +226,12 @@
/**
- * Replaces unused segments with the previous segments of each unused segment.
- * @param {CodePathSegment[]} segments An array of segments to replace.
- * @returns {CodePathSegment[]} The replaced array.
+ * Creates a new array based on an array of segments. If any segment in the
+ * array is unused, then it is replaced by all of its previous segments.
+ * All used segments are returned as-is without replacement.
+ * @param {CodePathSegment[]} segments The array of segments to flatten.
+ * @returns {CodePathSegment[]} The flattened array.
*/
static flattenUnusedSegments(segments) {
- const done = Object.create(null);
- const retv = [];
+ const done = new Set();
for (let i = 0; i < segments.length; ++i) {
@@ -209,5 +239,5 @@
// Ignores duplicated.
- if (done[segment.id]) {
+ if (done.has(segment)) {
continue;
}
@@ -218,16 +248,14 @@
const prevSegment = segment.allPrevSegments[j];
- if (!done[prevSegment.id]) {
- done[prevSegment.id] = true;
- retv.push(prevSegment);
+ if (!done.has(prevSegment)) {
+ done.add(prevSegment);
}
}
} else {
- done[segment.id] = true;
- retv.push(segment);
+ done.add(segment);
}
}
- return retv;
+ return [...done];
}
}
diff --git a/lib/rules/dot-notation.js b/lib/rules/dot-notation.js
index v8.43.0..v8.48.0 100644
--- a/lib/rules/dot-notation.js
+++ b/lib/rules/dot-notation.js
@@ -134,6 +134,5 @@
if (
node.computed &&
- node.property.type === "TemplateLiteral" &&
- node.property.expressions.length === 0
+ astUtils.isStaticTemplateLiteral(node.property)
) {
checkComputedProperty(node, node.property.quasis[0].value.cooked);
diff --git a/lib/config/flat-config-schema.js b/lib/config/flat-config-schema.js
index v8.43.0..v8.48.0 100644
--- a/lib/config/flat-config-schema.js
+++ b/lib/config/flat-config-schema.js
@@ -213,4 +213,36 @@
}
+/**
+ * The error type when there's an eslintrc-style options in a flat config.
+ */
+class IncompatibleKeyError extends Error {
+
+ /**
+ * @param {string} key The invalid key.
+ */
+ constructor(key) {
+ super("This appears to be in eslintrc format rather than flat config format.");
+ this.messageTemplate = "eslintrc-incompat";
+ this.messageData = { key };
+ }
+}
+
+/**
+ * The error type when there's an eslintrc-style plugins array found.
+ */
+class IncompatiblePluginsError extends Error {
+
+ /**
+ * Creates a new instance.
+ * @param {Array<string>} plugins The plugins array.
+ */
+ constructor(plugins) {
+ super("This appears to be in eslintrc format (array of strings) rather than flat config format (object).");
+ this.messageTemplate = "eslintrc-plugins";
+ this.messageData = { plugins };
+ }
+}
+
+
//-----------------------------------------------------------------------------
// Low-Level Schemas
@@ -304,4 +336,9 @@
}
+ // make sure it's not an array, which would mean eslintrc-style is used
+ if (Array.isArray(value)) {
+ throw new IncompatiblePluginsError(value);
+ }
+
// second check the keys to make sure they are objects
for (const key of Object.keys(value)) {
@@ -439,4 +476,32 @@
};
+/**
+ * Creates a schema that always throws an error. Useful for warning
+ * about eslintrc-style keys.
+ * @param {string} key The eslintrc key to create a schema for.
+ * @returns {ObjectPropertySchema} The schema.
+ */
+function createEslintrcErrorSchema(key) {
+ return {
+ merge: "replace",
+ validate() {
+ throw new IncompatibleKeyError(key);
+ }
+ };
+}
+
+const eslintrcKeys = [
+ "env",
+ "extends",
+ "globals",
+ "ignorePatterns",
+ "noInlineConfig",
+ "overrides",
+ "parser",
+ "parserOptions",
+ "reportUnusedDisableDirectives",
+ "root"
+];
+
//-----------------------------------------------------------------------------
// Full schema
@@ -444,4 +509,9 @@
exports.flatConfigSchema = {
+
+ // eslintrc-style keys that should always error
+ ...Object.fromEntries(eslintrcKeys.map(key => [key, createEslintrcErrorSchema(key)])),
+
+ // flat config keys
settings: deepObjectAssignSchema,
linterOptions: {
diff --git a/lib/eslint/flat-eslint.js b/lib/eslint/flat-eslint.js
index v8.43.0..v8.48.0 100644
--- a/lib/eslint/flat-eslint.js
+++ b/lib/eslint/flat-eslint.js
@@ -104,5 +104,15 @@
*/
function calculateStatsPerFile(messages) {
- return messages.reduce((stat, message) => {
+ const stat = {
+ errorCount: 0,
+ fatalErrorCount: 0,
+ warningCount: 0,
+ fixableErrorCount: 0,
+ fixableWarningCount: 0
+ };
+
+ for (let i = 0; i < messages.length; i++) {
+ const message = messages[i];
+
if (message.fatal || message.severity === 2) {
stat.errorCount++;
@@ -119,38 +129,9 @@
}
}
- return stat;
- }, {
- errorCount: 0,
- fatalErrorCount: 0,
- warningCount: 0,
- fixableErrorCount: 0,
- fixableWarningCount: 0
- });
+ }
+ return stat;
}
/**
- * It will calculate the error and warning count for collection of results from all files
- * @param {LintResult[]} results Collection of messages from all the files
- * @returns {Object} Contains the stats
- * @private
- */
-function calculateStatsPerRun(results) {
- return results.reduce((stat, result) => {
- stat.errorCount += result.errorCount;
- stat.fatalErrorCount += result.fatalErrorCount;
- stat.warningCount += result.warningCount;
- stat.fixableErrorCount += result.fixableErrorCount;
- stat.fixableWarningCount += result.fixableWarningCount;
- return stat;
- }, {
- errorCount: 0,
- fatalErrorCount: 0,
- warningCount: 0,
- fixableErrorCount: 0,
- fixableWarningCount: 0
- });
-}
-
-/**
* Create rulesMeta object.
* @param {Map<string,Rule>} rules a map of rules from which to generate the object.
@@ -552,41 +533,4 @@
/**
- * Collect used deprecated rules.
- * @param {Array<FlatConfig>} configs The configs to evaluate.
- * @returns {IterableIterator<DeprecatedRuleInfo>} Used deprecated rules.
- */
-function *iterateRuleDeprecationWarnings(configs) {
- const processedRuleIds = new Set();
-
- for (const config of configs) {
- for (const [ruleId, ruleConfig] of Object.entries(config.rules)) {
-
- // Skip if it was processed.
- if (processedRuleIds.has(ruleId)) {
- continue;
- }
- processedRuleIds.add(ruleId);
-
- // Skip if it's not used.
- if (!getRuleSeverity(ruleConfig)) {
- continue;
- }
- const rule = getRuleFromConfig(ruleId, config);
-
- // Skip if it's not deprecated.
- if (!(rule && rule.meta && rule.meta.deprecated)) {
- continue;
- }
-
- // This rule was used and deprecated.
- yield {
- ruleId,
- replacedBy: rule.meta.replacedBy || []
- };
- }
- }
-}
-
-/**
* Creates an error to be thrown when an array of results passed to `getRulesMetaForResults` was not created by the current engine.
* @returns {TypeError} An error object.
@@ -633,5 +577,4 @@
lintResultCache,
defaultConfigs,
- defaultIgnores: () => false,
configs: null
});
@@ -772,10 +715,8 @@
const rule = getRuleFromConfig(ruleId, config);
- // ensure the rule exists
- if (!rule) {
- throw new TypeError(`Could not find the rule "${ruleId}".`);
+ // ignore unknown rules
+ if (rule) {
+ resultRules.set(ruleId, rule);
}
-
- resultRules.set(ruleId, rule);
}
}
@@ -812,5 +753,4 @@
} = eslintOptions;
const startTime = Date.now();
- const usedConfigs = [];
const fixTypesSet = fixTypes ? new Set(fixTypes) : null;
@@ -870,13 +810,4 @@
}
- /*
- * Store used configs for:
- * - this method uses to collect used deprecated rules.
- * - `--fix-type` option uses to get the loaded rule's meta data.
- */
- if (!usedConfigs.includes(config)) {
- usedConfigs.push(config);
- }
-
// Skip if there is cached result.
if (lintResultCache) {
@@ -947,20 +878,8 @@
}
- let usedDeprecatedRules;
const finalResults = results.filter(result => !!result);
return processLintReport(this, {
- results: finalResults,
- ...calculateStatsPerRun(finalResults),
-
- // Initialize it lazily because CLI and `ESLint` API don't use it.
- get usedDeprecatedRules() {
- if (!usedDeprecatedRules) {
- usedDeprecatedRules = Array.from(
- iterateRuleDeprecationWarnings(usedConfigs)
- );
- }
- return usedDeprecatedRules;
- }
+ results: finalResults
});
}
@@ -1024,5 +943,4 @@
const startTime = Date.now();
const resolvedFilename = path.resolve(cwd, filePath || "__placeholder__.js");
- let config;
// Clear the last used config arrays.
@@ -1033,7 +951,4 @@
} else {
- // TODO: Needed?
- config = configs.getConfig(resolvedFilename);
-
// Do lint.
results.push(verifyText({
@@ -1050,19 +965,7 @@
debug(`Linting complete in: ${Date.now() - startTime}ms`);
- let usedDeprecatedRules;
return processLintReport(this, {
- results,
- ...calculateStatsPerRun(results),
-
- // Initialize it lazily because CLI and `ESLint` API don't use it.
- get usedDeprecatedRules() {
- if (!usedDeprecatedRules) {
- usedDeprecatedRules = Array.from(
- iterateRuleDeprecationWarnings(config)
- );
- }
- return usedDeprecatedRules;
- }
+ results
});
diff --git a/lib/rule-tester/flat-rule-tester.js b/lib/rule-tester/flat-rule-tester.js
index v8.43.0..v8.48.0 100644
--- a/lib/rule-tester/flat-rule-tester.js
+++ b/lib/rule-tester/flat-rule-tester.js
@@ -33,6 +33,7 @@
/** @typedef {import("../shared/types").Parser} Parser */
/** @typedef {import("../shared/types").LanguageOptions} LanguageOptions */
+/** @typedef {import("../shared/types").Rule} Rule */
-/* eslint-disable jsdoc/valid-types -- https://github.com/jsdoc-type-pratt-parser/jsdoc-type-pratt-parser/issues/4#issuecomment-778805577 */
+
/**
* A test case that is expected to pass lint.
@@ -73,5 +74,4 @@
* @property {number} [endColumn] The 1-based column number of the reported end location.
*/
-/* eslint-enable jsdoc/valid-types -- https://github.com/jsdoc-type-pratt-parser/jsdoc-type-pratt-parser/issues/4#issuecomment-778805577 */
//------------------------------------------------------------------------------
@@ -448,5 +448,5 @@
* Adds a new rule test to execute.
* @param {string} ruleName The name of the rule to run.
- * @param {Function} rule The rule to test.
+ * @param {Function | Rule} rule The rule to test.
* @param {{
* valid: (ValidTestCase | string)[],
@@ -1013,27 +1013,33 @@
* This creates a mocha test suite and pipes all supplied info through
* one of the templates above.
+ * The test suites for valid/invalid are created conditionally as
+ * test runners (eg. vitest) fail for empty test suites.
*/
this.constructor.describe(ruleName, () => {
- this.constructor.describe("valid", () => {
- test.valid.forEach(valid => {
- this.constructor[valid.only ? "itOnly" : "it"](
- sanitize(typeof valid === "object" ? valid.name || valid.code : valid),
- () => {
- testValidTemplate(valid);
- }
- );
+ if (test.valid.length > 0) {
+ this.constructor.describe("valid", () => {
+ test.valid.forEach(valid => {
+ this.constructor[valid.only ? "itOnly" : "it"](
+ sanitize(typeof valid === "object" ? valid.name || valid.code : valid),
+ () => {
+ testValidTemplate(valid);
+ }
+ );
+ });
});
- });
+ }
- this.constructor.describe("invalid", () => {
- test.invalid.forEach(invalid => {
- this.constructor[invalid.only ? "itOnly" : "it"](
- sanitize(invalid.name || invalid.code),
- () => {
- testInvalidTemplate(invalid);
- }
- );
+ if (test.invalid.length > 0) {
+ this.constructor.describe("invalid", () => {
+ test.invalid.forEach(invalid => {
+ this.constructor[invalid.only ? "itOnly" : "it"](
+ sanitize(invalid.name || invalid.code),
+ () => {
+ testInvalidTemplate(invalid);
+ }
+ );
+ });
});
- });
+ }
});
}
diff --git a/lib/rules/for-direction.js b/lib/rules/for-direction.js
index v8.43.0..v8.48.0 100644
--- a/lib/rules/for-direction.js
+++ b/lib/rules/for-direction.js
@@ -7,4 +7,10 @@
//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const { getStaticValue } = require("@eslint-community/eslint-utils");
+
+//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
@@ -30,4 +36,5 @@
create(context) {
+ const { sourceCode } = context;
/**
@@ -47,15 +54,15 @@
* @param {ASTNode} update UpdateExpression to check
* @param {int} dir expected direction that could either be turned around or invalidated
- * @returns {int} return dir, the negated dir or zero if it's not clear for identifiers
+ * @returns {int} return dir, the negated dir, or zero if the counter does not change or the direction is not clear
*/
function getRightDirection(update, dir) {
- if (update.right.type === "UnaryExpression") {
- if (update.right.operator === "-") {
- return -dir;
- }
- } else if (update.right.type === "Identifier") {
- return 0;
+ const staticValue = getStaticValue(update.right, sourceCode.getScope(update));
+
+ if (staticValue && ["bigint", "boolean", "number"].includes(typeof staticValue.value)) {
+ const sign = Math.sign(Number(staticValue.value)) || 0; // convert NaN to 0
+
+ return dir * sign;
}
- return dir;
+ return 0;
}
diff --git a/conf/globals.js b/conf/globals.js
index v8.43.0..v8.48.0 100644
--- a/conf/globals.js
+++ b/conf/globals.js
@@ -129,5 +129,9 @@
};
+const es2024 = {
+ ...es2023
+};
+
//-----------------------------------------------------------------------------
// Exports
@@ -146,4 +150,5 @@
es2021,
es2022,
- es2023
+ es2023,
+ es2024
};
diff --git a/lib/rules/grouped-accessor-pairs.js b/lib/rules/grouped-accessor-pairs.js
index v8.43.0..v8.48.0 100644
--- a/lib/rules/grouped-accessor-pairs.js
+++ b/lib/rules/grouped-accessor-pairs.js
@@ -139,41 +139,4 @@
/**
- * Creates a new `AccessorData` object for the given getter or setter node.
- * @param {ASTNode} node A getter or setter node.
- * @returns {AccessorData} New `AccessorData` object that contains the given node.
- * @private
- */
- function createAccessorData(node) {
- const name = astUtils.getStaticPropertyName(node);
- const key = (name !== null) ? name : sourceCode.getTokens(node.key);
-
- return {
- key,
- getters: node.kind === "get" ? [node] : [],
- setters: node.kind === "set" ? [node] : []
- };
- }
-
- /**
- * Merges the given `AccessorData` object into the given accessors list.
- * @param {AccessorData[]} accessors The list to merge into.
- * @param {AccessorData} accessorData The object to merge.
- * @returns {AccessorData[]} The same instance with the merged object.
- * @private
- */
- function mergeAccessorData(accessors, accessorData) {
- const equalKeyElement = accessors.find(a => areEqualKeys(a.key, accessorData.key));
-
- if (equalKeyElement) {
- equalKeyElement.getters.push(...accessorData.getters);
- equalKeyElement.setters.push(...accessorData.setters);
- } else {
- accessors.push(accessorData);
- }
-
- return accessors;
- }
-
- /**
* Checks accessor pairs in the given list of nodes.
* @param {ASTNode[]} nodes The list to check.
@@ -183,10 +146,38 @@
*/
function checkList(nodes, shouldCheck) {
- const accessors = nodes
- .filter(shouldCheck)
- .filter(isAccessorKind)
- .map(createAccessorData)
- .reduce(mergeAccessorData, []);
+ const accessors = [];
+ let found = false;
+ for (let i = 0; i < nodes.length; i++) {
+ const node = nodes[i];
+
+ if (shouldCheck(node) && isAccessorKind(node)) {
+
+ // Creates a new `AccessorData` object for the given getter or setter node.
+ const name = astUtils.getStaticPropertyName(node);
+ const key = (name !== null) ? name : sourceCode.getTokens(node.key);
+
+ // Merges the given `AccessorData` object into the given accessors list.
+ for (let j = 0; j < accessors.length; j++) {
+ const accessor = accessors[j];
+
+ if (areEqualKeys(accessor.key, key)) {
+ accessor.getters.push(...node.kind === "get" ? [node] : []);
+ accessor.setters.push(...node.kind === "set" ? [node] : []);
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ accessors.push({
+ key,
+ getters: node.kind === "get" ? [node] : [],
+ setters: node.kind === "set" ? [node] : []
+ });
+ }
+ found = false;
+ }
+ }
+
for (const { getters, setters } of accessors) {
diff --git a/lib/rules/indent.js b/lib/rules/indent.js
index v8.43.0..v8.48.0 100644
--- a/lib/rules/indent.js
+++ b/lib/rules/indent.js
@@ -189,13 +189,17 @@
constructor(sourceCode) {
this.sourceCode = sourceCode;
- this.firstTokensByLineNumber = sourceCode.tokensAndComments.reduce((map, token) => {
- if (!map.has(token.loc.start.line)) {
- map.set(token.loc.start.line, token);
+ this.firstTokensByLineNumber = new Map();
+ const tokens = sourceCode.tokensAndComments;
+
+ for (let i = 0; i < tokens.length; i++) {
+ const token = tokens[i];
+
+ if (!this.firstTokensByLineNumber.has(token.loc.start.line)) {
+ this.firstTokensByLineNumber.set(token.loc.start.line, token);
}
- if (!map.has(token.loc.end.line) && sourceCode.text.slice(token.range[1] - token.loc.end.column, token.range[1]).trim()) {
- map.set(token.loc.end.line, token);
+ if (!this.firstTokensByLineNumber.has(token.loc.end.line) && sourceCode.text.slice(token.range[1] - token.loc.end.column, token.range[1]).trim()) {
+ this.firstTokensByLineNumber.set(token.loc.end.line, token);
}
- return map;
- }, new Map());
+ }
}
@@ -965,17 +969,17 @@
const parenPairs = [];
- tokens.forEach(nextToken => {
+ for (let i = 0; i < tokens.length; i++) {
+ const nextToken = tokens[i];
- // Accumulate a list of parenthesis pairs
if (astUtils.isOpeningParenToken(nextToken)) {
parenStack.push(nextToken);
} else if (astUtils.isClosingParenToken(nextToken)) {
- parenPairs.unshift({ left: parenStack.pop(), right: nextToken });
+ parenPairs.push({ left: parenStack.pop(), right: nextToken });
}
- });
+ }
- parenPairs.forEach(pair => {
- const leftParen = pair.left;
- const rightParen = pair.right;
+ for (let i = parenPairs.length - 1; i >= 0; i--) {
+ const leftParen = parenPairs[i].left;
+ const rightParen = parenPairs[i].right;
// We only want to handle parens around expressions, so exclude parentheses that are in function parameters and function call arguments.
@@ -991,5 +995,5 @@
offsets.setDesiredOffset(rightParen, leftParen, 0);
- });
+ }
}
@@ -1247,5 +1251,5 @@
IfStatement(node) {
addBlocklessNodeIndent(node.consequent);
- if (node.alternate && node.alternate.type !== "IfStatement") {
+ if (node.alternate) {
addBlocklessNodeIndent(node.alternate);
}
@@ -1712,8 +1716,12 @@
// Invoke the queued offset listeners for the nodes that aren't ignored.
- listenerCallQueue
- .filter(nodeInfo => !ignoredNodes.has(nodeInfo.node))
- .forEach(nodeInfo => nodeInfo.listener(nodeInfo.node));
+ for (let i = 0; i < listenerCallQueue.length; i++) {
+ const nodeInfo = listenerCallQueue[i];
+ if (!ignoredNodes.has(nodeInfo.node)) {
+ nodeInfo.listener(nodeInfo.node);
+ }
+ }
+
// Update the offsets for ignored nodes to prevent their child tokens from being reported.
ignoredNodes.forEach(ignoreNode);
@@ -1725,25 +1733,29 @@
* This is necessary because sourceCode.getTokenBefore does not handle a comment as an argument correctly.
*/
- const precedingTokens = sourceCode.ast.comments.reduce((commentMap, comment) => {
+ const precedingTokens = new WeakMap();
+
+ for (let i = 0; i < sourceCode.ast.comments.length; i++) {
+ const comment = sourceCode.ast.comments[i];
+
const tokenOrCommentBefore = sourceCode.getTokenBefore(comment, { includeComments: true });
+ const hasToken = precedingTokens.has(tokenOrCommentBefore) ? precedingTokens.get(tokenOrCommentBefore) : tokenOrCommentBefore;
- return commentMap.set(comment, commentMap.has(tokenOrCommentBefore) ? commentMap.get(tokenOrCommentBefore) : tokenOrCommentBefore);
- }, new WeakMap());
+ precedingTokens.set(comment, hasToken);
+ }
- sourceCode.lines.forEach((line, lineIndex) => {
- const lineNumber = lineIndex + 1;
+ for (let i = 1; i < sourceCode.lines.length + 1; i++) {
- if (!tokenInfo.firstTokensByLineNumber.has(lineNumber)) {
+ if (!tokenInfo.firstTokensByLineNumber.has(i)) {
// Don't check indentation on blank lines
- return;
+ continue;
}
- const firstTokenOfLine = tokenInfo.firstTokensByLineNumber.get(lineNumber);
+ const firstTokenOfLine = tokenInfo.firstTokensByLineNumber.get(i);
- if (firstTokenOfLine.loc.start.line !== lineNumber) {
+ if (firstTokenOfLine.loc.start.line !== i) {
// Don't check the indentation of multi-line tokens (e.g. template literals or block comments) twice.
- return;
+ continue;
}
@@ -1770,5 +1782,5 @@
mayAlignWithAfter && validateTokenIndent(firstTokenOfLine, offsets.getDesiredIndent(tokenAfter))
) {
- return;
+ continue;
}
}
@@ -1776,10 +1788,10 @@
// If the token matches the expected indentation, don't report it.
if (validateTokenIndent(firstTokenOfLine, offsets.getDesiredIndent(firstTokenOfLine))) {
- return;
+ continue;
}
// Otherwise, report the token/comment.
report(firstTokenOfLine, offsets.getDesiredIndent(firstTokenOfLine));
- });
+ }
}
}
diff --git a/lib/rules/logical-assignment-operators.js b/lib/rules/logical-assignment-operators.js
index v8.43.0..v8.48.0 100644
--- a/lib/rules/logical-assignment-operators.js
+++ b/lib/rules/logical-assignment-operators.js
@@ -371,6 +371,9 @@
}
- const requiresOuterParenthesis = logical.parent.type !== "ExpressionStatement" &&
- (astUtils.getPrecedence({ type: "AssignmentExpression" }) < astUtils.getPrecedence(logical.parent));
+ const parentPrecedence = astUtils.getPrecedence(logical.parent);
+ const requiresOuterParenthesis = logical.parent.type !== "ExpressionStatement" && (
+ parentPrecedence === -1 ||
+ astUtils.getPrecedence({ type: "AssignmentExpression" }) < parentPrecedence
+ );
if (!astUtils.isParenthesised(sourceCode, logical) && requiresOuterParenthesis) {
diff --git a/lib/rules/max-len.js b/lib/rules/max-len.js
index v8.43.0..v8.48.0 100644
--- a/lib/rules/max-len.js
+++ b/lib/rules/max-len.js
@@ -253,17 +253,21 @@
}
-
/**
- * A reducer to group an AST node by line number, both start and end.
- * @param {Object} acc the accumulator
- * @param {ASTNode} node the AST node in question
- * @returns {Object} the modified accumulator
- * @private
+ *
+ * reduce an array of AST nodes by line number, both start and end.
+ * @param {ASTNode[]} arr array of AST nodes
+ * @returns {Object} accululated AST nodes
*/
- function groupByLineNumber(acc, node) {
- for (let i = node.loc.start.line; i <= node.loc.end.line; ++i) {
- ensureArrayAndPush(acc, i, node);
+ function groupArrayByLineNumber(arr) {
+ const obj = {};
+
+ for (let i = 0; i < arr.length; i++) {
+ const node = arr[i];
+
+ for (let j = node.loc.start.line; j <= node.loc.end.line; ++j) {
+ ensureArrayAndPush(obj, j, node);
+ }
}
- return acc;
+ return obj;
}
@@ -313,11 +317,11 @@
const strings = getAllStrings();
- const stringsByLine = strings.reduce(groupByLineNumber, {});
+ const stringsByLine = groupArrayByLineNumber(strings);
const templateLiterals = getAllTemplateLiterals();
- const templateLiteralsByLine = templateLiterals.reduce(groupByLineNumber, {});
+ const templateLiteralsByLine = groupArrayByLineNumber(templateLiterals);
const regExpLiterals = getAllRegExpLiterals();
- const regExpLiteralsByLine = regExpLiterals.reduce(groupByLineNumber, {});
+ const regExpLiteralsByLine = groupArrayByLineNumber(regExpLiterals);
lines.forEach((line, i) => {
diff --git a/lib/rules/no-control-regex.js b/lib/rules/no-control-regex.js
index v8.43.0..v8.48.0 100644
--- a/lib/rules/no-control-regex.js
+++ b/lib/rules/no-control-regex.js
@@ -15,4 +15,14 @@
onPatternEnter() {
+
+ /*
+ * `RegExpValidator` may parse the pattern twice in one `validatePattern`.
+ * So `this._controlChars` should be cleared here as well.
+ *
+ * For example, the `/(?<a>\x1f)/` regex will parse the pattern twice.
+ * This is based on the content described in Annex B.
+ * If the regex contains a `GroupName` and the `u` flag is not used, `ParseText` will be called twice.
+ * See https://tc39.es/ecma262/2023/multipage/additional-ecmascript-features-for-web-browsers.html#sec-parsepattern-annexb
+ */
this._controlChars = [];
}
@@ -33,8 +43,11 @@
collectControlChars(regexpStr, flags) {
const uFlag = typeof flags === "string" && flags.includes("u");
+ const vFlag = typeof flags === "string" && flags.includes("v");
+ this._controlChars = [];
+ this._source = regexpStr;
+
try {
- this._source = regexpStr;
- this._validator.validatePattern(regexpStr, void 0, void 0, uFlag); // Call onCharacter hook
+ this._validator.validatePattern(regexpStr, void 0, void 0, { unicode: uFlag, unicodeSets: vFlag }); // Call onCharacter hook
} catch {
diff --git a/lib/rules/no-empty-character-class.js b/lib/rules/no-empty-character-class.js
index v8.43.0..v8.48.0 100644
--- a/lib/rules/no-empty-character-class.js
+++ b/lib/rules/no-empty-character-class.js
@@ -7,17 +7,15 @@
//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const { RegExpParser, visitRegExpAST } = require("@eslint-community/regexpp");
+
+//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
-/*
- * plain-English description of the following regexp:
- * 0. `^` fix the match at the beginning of the string
- * 1. `([^\\[]|\\.|\[([^\\\]]|\\.)+\])*`: regexp contents; 0 or more of the following
- * 1.0. `[^\\[]`: any character that's not a `\` or a `[` (anything but escape sequences and character classes)
- * 1.1. `\\.`: an escape sequence
- * 1.2. `\[([^\\\]]|\\.)+\]`: a character class that isn't empty
- * 2. `$`: fix the match at the end of the string
- */
-const regex = /^([^\\[]|\\.|\[([^\\\]]|\\.)+\])*$/u;
+const parser = new RegExpParser();
+const QUICK_TEST_REGEX = /\[\]/u;
//------------------------------------------------------------------------------
@@ -46,7 +44,30 @@
return {
"Literal[regex]"(node) {
- if (!regex.test(node.regex.pattern)) {
- context.report({ node, messageId: "unexpected" });
+ const { pattern, flags } = node.regex;
+
+ if (!QUICK_TEST_REGEX.test(pattern)) {
+ return;
}
+
+ let regExpAST;
+
+ try {
+ regExpAST = parser.parsePattern(pattern, 0, pattern.length, {
+ unicode: flags.includes("u"),
+ unicodeSets: flags.includes("v")
+ });
+ } catch {
+
+ // Ignore regular expressions that regexpp cannot parse
+ return;
+ }
+
+ visitRegExpAST(regExpAST, {
+ onCharacterClassEnter(characterClass) {
+ if (!characterClass.negate && characterClass.elements.length === 0) {
+ context.report({ node, messageId: "unexpected" });
+ }
+ }
+ });
}
};
diff --git a/lib/rules/no-empty-pattern.js b/lib/rules/no-empty-pattern.js
index v8.43.0..v8.48.0 100644
--- a/lib/rules/no-empty-pattern.js
+++ b/lib/rules/no-empty-pattern.js
@@ -5,4 +5,6 @@
"use strict";
+const astUtils = require("./utils/ast-utils");
+
//------------------------------------------------------------------------------
// Rule Definition
@@ -20,5 +22,16 @@
},
- schema: [],
+ schema: [
+ {
+ type: "object",
+ properties: {
+ allowObjectPatternsAsParameters: {
+ type: "boolean",
+ default: false
+ }
+ },
+ additionalProperties: false
+ }
+ ],
messages: {
@@ -28,9 +41,31 @@
create(context) {
+ const options = context.options[0] || {},
+ allowObjectPatternsAsParameters = options.allowObjectPatternsAsParameters || false;
+
return {
ObjectPattern(node) {
- if (node.properties.length === 0) {
- context.report({ node, messageId: "unexpected", data: { type: "object" } });
+
+ if (node.properties.length > 0) {
+ return;
}
+
+ // Allow {} and {} = {} empty object patterns as parameters when allowObjectPatternsAsParameters is true
+ if (
+ allowObjectPatternsAsParameters &&
+ (
+ astUtils.isFunction(node.parent) ||
+ (
+ node.parent.type === "AssignmentPattern" &&
+ astUtils.isFunction(node.parent.parent) &&
+ node.parent.right.type === "ObjectExpression" &&
+ node.parent.right.properties.length === 0
+ )
+ )
+ ) {
+ return;
+ }
+
+ context.report({ node, messageId: "unexpected", data: { type: "object" } });
},
ArrayPattern(node) {
diff --git a/lib/rules/no-extra-parens.js b/lib/rules/no-extra-parens.js
index v8.43.0..v8.48.0 100644
--- a/lib/rules/no-extra-parens.js
+++ b/lib/rules/no-extra-parens.js
@@ -47,4 +47,5 @@
properties: {
conditionalAssign: { type: "boolean" },
+ ternaryOperandBinaryExpressions: { type: "boolean" },
nestedBinaryExpressions: { type: "boolean" },
returnAssign: { type: "boolean" },
@@ -77,4 +78,5 @@
const ALL_NODES = context.options[0] !== "functions";
const EXCEPT_COND_ASSIGN = ALL_NODES && context.options[1] && context.options[1].conditionalAssign === false;
+ const EXCEPT_COND_TERNARY = ALL_NODES && context.options[1] && context.options[1].ternaryOperandBinaryExpressions === false;
const NESTED_BINARY = ALL_NODES && context.options[1] && context.options[1].nestedBinaryExpressions === false;
const EXCEPT_RETURN_ASSIGN = ALL_NODES && context.options[1] && context.options[1].returnAssign === false;
@@ -887,5 +889,9 @@
return;
}
+
+ const availableTypes = new Set(["BinaryExpression", "LogicalExpression"]);
+
if (
+ !(EXCEPT_COND_TERNARY && availableTypes.has(node.test.type)) &&
!isCondAssignException(node) &&
hasExcessParensWithPrecedence(node.test, precedence({ type: "LogicalExpression", operator: "||" }))
@@ -894,9 +900,13 @@
}
- if (hasExcessParensWithPrecedence(node.consequent, PRECEDENCE_OF_ASSIGNMENT_EXPR)) {
+ if (
+ !(EXCEPT_COND_TERNARY && availableTypes.has(node.consequent.type)) &&
+ hasExcessParensWithPrecedence(node.consequent, PRECEDENCE_OF_ASSIGNMENT_EXPR)) {
report(node.consequent);
}
- if (hasExcessParensWithPrecedence(node.alternate, PRECEDENCE_OF_ASSIGNMENT_EXPR)) {
+ if (
+ !(EXCEPT_COND_TERNARY && availableTypes.has(node.alternate.type)) &&
+ hasExcessParensWithPrecedence(node.alternate, PRECEDENCE_OF_ASSIGNMENT_EXPR)) {
report(node.alternate);
}
diff --git a/lib/rules/no-extra-semi.js b/lib/rules/no-extra-semi.js
index v8.43.0..v8.48.0 100644
--- a/lib/rules/no-extra-semi.js
+++ b/lib/rules/no-extra-semi.js
@@ -40,4 +40,21 @@
/**
+ * Checks if a node or token is fixable.
+ * A node is fixable if it can be removed without turning a subsequent statement into a directive after fixing other nodes.
+ * @param {Token} nodeOrToken The node or token to check.
+ * @returns {boolean} Whether or not the node is fixable.
+ */
+ function isFixable(nodeOrToken) {
+ const nextToken = sourceCode.getTokenAfter(nodeOrToken);
+
+ if (!nextToken || nextToken.type !== "String") {
+ return true;
+ }
+ const stringNode = sourceCode.getNodeByRangeIndex(nextToken.range[0]);
+
+ return !astUtils.isTopLevelExpressionStatement(stringNode.parent);
+ }
+
+ /**
* Reports an unnecessary semicolon error.
* @param {Node|Token} nodeOrToken A node or a token to be reported.
@@ -48,15 +65,16 @@
node: nodeOrToken,
messageId: "unexpected",
- fix(fixer) {
+ fix: isFixable(nodeOrToken)
+ ? fixer =>
- /*
- * Expand the replacement range to include the surrounding
- * tokens to avoid conflicting with semi.
- * https://github.com/eslint/eslint/issues/7928
- */
- return new FixTracker(fixer, context.sourceCode)
- .retainSurroundingTokens(nodeOrToken)
- .remove(nodeOrToken);
- }
+ /*
+ * Expand the replacement range to include the surrounding
+ * tokens to avoid conflicting with semi.
+ * https://github.com/eslint/eslint/issues/7928
+ */
+ new FixTracker(fixer, context.sourceCode)
+ .retainSurroundingTokens(nodeOrToken)
+ .remove(nodeOrToken)
+ : null
});
}
diff --git a/lib/rules/no-invalid-regexp.js b/lib/rules/no-invalid-regexp.js
index v8.43.0..v8.48.0 100644
--- a/lib/rules/no-invalid-regexp.js
+++ b/lib/rules/no-invalid-regexp.js
@@ -11,5 +11,5 @@
const RegExpValidator = require("@eslint-community/regexpp").RegExpValidator;
const validator = new RegExpValidator();
-const validFlags = /[dgimsuy]/gu;
+const validFlags = /[dgimsuvy]/gu;
const undefined1 = void 0;
@@ -109,10 +109,12 @@
* Check syntax error in a given pattern.
* @param {string} pattern The RegExp pattern to validate.
- * @param {boolean} uFlag The Unicode flag.
+ * @param {Object} flags The RegExp flags to validate.
+ * @param {boolean} [flags.unicode] The Unicode flag.
+ * @param {boolean} [flags.unicodeSets] The UnicodeSets flag.
* @returns {string|null} The syntax error.
*/
- function validateRegExpPattern(pattern, uFlag) {
+ function validateRegExpPattern(pattern, flags) {
try {
- validator.validatePattern(pattern, undefined1, undefined1, uFlag);
+ validator.validatePattern(pattern, undefined1, undefined1, flags);
return null;
} catch (err) {
@@ -132,8 +134,17 @@
try {
validator.validateFlags(flags);
- return null;
} catch {
return `Invalid flags supplied to RegExp constructor '${flags}'`;
}
+
+ /*
+ * `regexpp` checks the combination of `u` and `v` flags when parsing `Pattern` according to `ecma262`,
+ * but this rule may check only the flag when the pattern is unidentifiable, so check it here.
+ * https://tc39.es/ecma262/multipage/text-processing.html#sec-parsepattern
+ */
+ if (flags.includes("u") && flags.includes("v")) {
+ return "Regex 'u' and 'v' flags cannot be used together";
+ }
+ return null;
}
@@ -167,6 +178,10 @@
// If flags are unknown, report the regex only if its pattern is invalid both with and without the "u" flag
flags === null
- ? validateRegExpPattern(pattern, true) && validateRegExpPattern(pattern, false)
- : validateRegExpPattern(pattern, flags.includes("u"))
+ ? (
+ validateRegExpPattern(pattern, { unicode: true, unicodeSets: false }) &&
+ validateRegExpPattern(pattern, { unicode: false, unicodeSets: true }) &&
+ validateRegExpPattern(pattern, { unicode: false, unicodeSets: false })
+ )
+ : validateRegExpPattern(pattern, { unicode: flags.includes("u"), unicodeSets: flags.includes("v") })
);
diff --git a/lib/rules/no-loop-func.js b/lib/rules/no-loop-func.js
index v8.43.0..v8.48.0 100644
--- a/lib/rules/no-loop-func.js
+++ b/lib/rules/no-loop-func.js
@@ -187,5 +187,5 @@
const references = sourceCode.getScope(node).through;
- const unsafeRefs = references.filter(r => !isSafe(loopNode, r)).map(r => r.identifier.name);
+ const unsafeRefs = references.filter(r => r.resolved && !isSafe(loopNode, r)).map(r => r.identifier.name);
if (unsafeRefs.length > 0) {
diff --git a/lib/rules/no-loss-of-precision.js b/lib/rules/no-loss-of-precision.js
index v8.43.0..v8.48.0 100644
--- a/lib/rules/no-loss-of-precision.js
+++ b/lib/rules/no-loss-of-precision.js
@@ -84,5 +84,5 @@
*/
function addDecimalPointToNumber(stringNumber) {
- return `${stringNumber.slice(0, 1)}.${stringNumber.slice(1)}`;
+ return `${stringNumber[0]}.${stringNumber.slice(1)}`;
}
@@ -93,5 +93,10 @@
*/
function removeLeadingZeros(numberAsString) {
- return numberAsString.replace(/^0*/u, "");
+ for (let i = 0; i < numberAsString.length; i++) {
+ if (numberAsString[i] !== "0") {
+ return numberAsString.slice(i);
+ }
+ }
+ return numberAsString;
}
@@ -102,5 +107,10 @@
*/
function removeTrailingZeros(numberAsString) {
- return numberAsString.replace(/0*$/u, "");
+ for (let i = numberAsString.length - 1; i >= 0; i--) {
+ if (numberAsString[i] !== "0") {
+ return numberAsString.slice(0, i + 1);
+ }
+ }
+ return numberAsString;
}
@@ -129,5 +139,5 @@
if (trimmedFloat.startsWith(".")) {
- const decimalDigits = trimmedFloat.split(".").pop();
+ const decimalDigits = trimmedFloat.slice(1);
const significantDigits = removeLeadingZeros(decimalDigits);
@@ -145,5 +155,4 @@
}
-
/**
* Converts a base ten number to proper scientific notation
@@ -161,5 +170,4 @@
return `${normalizedCoefficient}e${magnitude}`;
-
}
diff --git a/lib/rules/no-misleading-character-class.js b/lib/rules/no-misleading-character-class.js
index v8.43.0..v8.48.0 100644
--- a/lib/rules/no-misleading-character-class.js
+++ b/lib/rules/no-misleading-character-class.js
@@ -19,5 +19,5 @@
* CharacterClassRange syntax can steal a part of character sequence,
* so this function reverts CharacterClassRange syntax and restore the sequence.
- * @param {regexpp.AST.CharacterClassElement[]} nodes The node list to iterate character sequences.
+ * @param {import('@eslint-community/regexpp').AST.CharacterClassElement[]} nodes The node list to iterate character sequences.
* @returns {IterableIterator<number[]>} The list of character sequences.
*/
@@ -38,4 +38,7 @@
case "CharacterSet":
+ case "CharacterClass": // [[]] nesting character class
+ case "ClassStringDisjunction": // \q{...}
+ case "ExpressionCharacterClass": // [A--B]
if (seq.length > 0) {
yield seq;
@@ -145,5 +148,8 @@
0,
pattern.length,
- flags.includes("u")
+ {
+ unicode: flags.includes("u"),
+ unicodeSets: flags.includes("v")
+ }
);
} catch {
diff --git a/lib/rules/no-new-wrappers.js b/lib/rules/no-new-wrappers.js
index v8.43.0..v8.48.0 100644
--- a/lib/rules/no-new-wrappers.js
+++ b/lib/rules/no-new-wrappers.js
@@ -7,4 +7,10 @@
//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const { getVariableByName } = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
@@ -29,4 +35,5 @@
create(context) {
+ const { sourceCode } = context;
return {
@@ -34,11 +41,16 @@
NewExpression(node) {
const wrapperObjects = ["String", "Number", "Boolean"];
+ const { name } = node.callee;
- if (wrapperObjects.includes(node.callee.name)) {
- context.report({
- node,
- messageId: "noConstructor",
- data: { fn: node.callee.name }
- });
+ if (wrapperObjects.includes(name)) {
+ const variable = getVariableByName(sourceCode.getScope(node), name);
+
+ if (variable && variable.identifiers.length === 0) {
+ context.report({
+ node,
+ messageId: "noConstructor",
+ data: { fn: name }
+ });
+ }
}
}
diff --git a/lib/rules/no-promise-executor-return.js b/lib/rules/no-promise-executor-return.js
index v8.43.0..v8.48.0 100644
--- a/lib/rules/no-promise-executor-return.js
+++ b/lib/rules/no-promise-executor-return.js
@@ -11,4 +11,5 @@
const { findVariable } = require("@eslint-community/eslint-utils");
+const astUtils = require("./utils/ast-utils");
//------------------------------------------------------------------------------
@@ -60,4 +61,76 @@
}
+/**
+ * Checks if the given node is a void expression.
+ * @param {ASTNode} node The node to check.
+ * @returns {boolean} - `true` if the node is a void expression
+ */
+function expressionIsVoid(node) {
+ return node.type === "UnaryExpression" && node.operator === "void";
+}
+
+/**
+ * Fixes the linting error by prepending "void " to the given node
+ * @param {Object} sourceCode context given by context.sourceCode
+ * @param {ASTNode} node The node to fix.
+ * @param {Object} fixer The fixer object provided by ESLint.
+ * @returns {Array<Object>} - An array of fix objects to apply to the node.
+ */
+function voidPrependFixer(sourceCode, node, fixer) {
+
+ const requiresParens =
+
+ // prepending `void ` will fail if the node has a lower precedence than void
+ astUtils.getPrecedence(node) < astUtils.getPrecedence({ type: "UnaryExpression", operator: "void" }) &&
+
+ // check if there are parentheses around the node to avoid redundant parentheses
+ !astUtils.isParenthesised(sourceCode, node);
+
+ // avoid parentheses issues
+ const returnOrArrowToken = sourceCode.getTokenBefore(
+ node,
+ node.parent.type === "ArrowFunctionExpression"
+ ? astUtils.isArrowToken
+
+ // isReturnToken
+ : token => token.type === "Keyword" && token.value === "return"
+ );
+
+ const firstToken = sourceCode.getTokenAfter(returnOrArrowToken);
+
+ const prependSpace =
+
+ // is return token, as => allows void to be adjacent
+ returnOrArrowToken.value === "return" &&
+
+ // If two tokens (return and "(") are adjacent
+ returnOrArrowToken.range[1] === firstToken.range[0];
+
+ return [
+ fixer.insertTextBefore(firstToken, `${prependSpace ? " " : ""}void ${requiresParens ? "(" : ""}`),
+ fixer.insertTextAfter(node, requiresParens ? ")" : "")
+ ];
+}
+
+/**
+ * Fixes the linting error by `wrapping {}` around the given node's body.
+ * @param {Object} sourceCode context given by context.sourceCode
+ * @param {ASTNode} node The node to fix.
+ * @param {Object} fixer The fixer object provided by ESLint.
+ * @returns {Array<Object>} - An array of fix objects to apply to the node.
+ */
+function curlyWrapFixer(sourceCode, node, fixer) {
+
+ // https://github.com/eslint/eslint/pull/17282#issuecomment-1592795923
+ const arrowToken = sourceCode.getTokenBefore(node.body, astUtils.isArrowToken);
+ const firstToken = sourceCode.getTokenAfter(arrowToken);
+ const lastToken = sourceCode.getLastToken(node);
+
+ return [
+ fixer.insertTextBefore(firstToken, "{"),
+ fixer.insertTextAfter(lastToken, "}")
+ ];
+}
+
//------------------------------------------------------------------------------
// Rule Definition
@@ -75,8 +148,25 @@
},
- schema: [],
+ hasSuggestions: true,
+ schema: [{
+ type: "object",
+ properties: {
+ allowVoid: {
+ type: "boolean",
+ default: false
+ }
+ },
+ additionalProperties: false
+ }],
+
messages: {
- returnsValue: "Return values from promise executor functions cannot be read."
+ returnsValue: "Return values from promise executor functions cannot be read.",
+
+ // arrow and function suggestions
+ prependVoid: "Prepend `void` to the expression.",
+
+ // only arrow suggestions
+ wrapBraces: "Wrap the expression in `{}`."
}
},
@@ -86,14 +176,8 @@
let funcInfo = null;
const sourceCode = context.sourceCode;
+ const {
+ allowVoid = false
+ } = context.options[0] || {};
- /**
- * Reports the given node.
- * @param {ASTNode} node Node to report.
- * @returns {void}
- */
- function report(node) {
- context.report({ node, messageId: "returnsValue" });
- }
-
return {
@@ -101,9 +185,41 @@
funcInfo = {
upper: funcInfo,
- shouldCheck: functionTypesToCheck.has(node.type) && isPromiseExecutor(node, sourceCode.getScope(node))
+ shouldCheck:
+ functionTypesToCheck.has(node.type) &&
+ isPromiseExecutor(node, sourceCode.getScope(node))
};
- if (funcInfo.shouldCheck && node.type === "ArrowFunctionExpression" && node.expression) {
- report(node.body);
+ if (// Is a Promise executor
+ funcInfo.shouldCheck &&
+ node.type === "ArrowFunctionExpression" &&
+ node.expression &&
+
+ // Except void
+ !(allowVoid && expressionIsVoid(node.body))
+ ) {
+ const suggest = [];
+
+ // prevent useless refactors
+ if (allowVoid) {
+ suggest.push({
+ messageId: "prependVoid",
+ fix(fixer) {
+ return voidPrependFixer(sourceCode, node.body, fixer);
+ }
+ });
+ }
+
+ suggest.push({
+ messageId: "wrapBraces",
+ fix(fixer) {
+ return curlyWrapFixer(sourceCode, node, fixer);
+ }
+ });
+
+ context.report({
+ node: node.body,
+ messageId: "returnsValue",
+ suggest
+ });
}
},
@@ -114,7 +230,29 @@
ReturnStatement(node) {
- if (funcInfo.shouldCheck && node.argument) {
- report(node);
+ if (!(funcInfo.shouldCheck && node.argument)) {
+ return;
}
+
+ // node is `return <expression>`
+ if (!allowVoid) {
+ context.report({ node, messageId: "returnsValue" });
+ return;
+ }
+
+ if (expressionIsVoid(node.argument)) {
+ return;
+ }
+
+ // allowVoid && !expressionIsVoid
+ context.report({
+ node,
+ messageId: "returnsValue",
+ suggest: [{
+ messageId: "prependVoid",
+ fix(fixer) {
+ return voidPrependFixer(sourceCode, node.argument, fixer);
+ }
+ }]
+ });
}
};
diff --git a/lib/rules/no-regex-spaces.js b/lib/rules/no-regex-spaces.js
index v8.43.0..v8.48.0 100644
--- a/lib/rules/no-regex-spaces.js
+++ b/lib/rules/no-regex-spaces.js
@@ -78,5 +78,5 @@
try {
- regExpAST = regExpParser.parsePattern(pattern, 0, pattern.length, flags.includes("u"));
+ regExpAST = regExpParser.parsePattern(pattern, 0, pattern.length, { unicode: flags.includes("u"), unicodeSets: flags.includes("v") });
} catch {
@@ -156,5 +156,4 @@
const shadowed = regExpVar && regExpVar.defs.length > 0;
const patternNode = node.arguments[0];
- const flagsNode = node.arguments[1];
if (node.callee.type === "Identifier" && node.callee.name === "RegExp" && isString(patternNode) && !shadowed) {
@@ -162,6 +161,22 @@
const rawPattern = patternNode.raw.slice(1, -1);
const rawPatternStartRange = patternNode.range[0] + 1;
- const flags = isString(flagsNode) ? flagsNode.value : "";
+ let flags;
+ if (node.arguments.length < 2) {
+
+ // It has no flags.
+ flags = "";
+ } else {
+ const flagsNode = node.arguments[1];
+
+ if (isString(flagsNode)) {
+ flags = flagsNode.value;
+ } else {
+
+ // The flags cannot be determined.
+ return;
+ }
+ }
+
checkRegex(
node,
diff --git a/lib/rules/no-restricted-modules.js b/lib/rules/no-restricted-modules.js
index v8.43.0..v8.48.0 100644
--- a/lib/rules/no-restricted-modules.js
+++ b/lib/rules/no-restricted-modules.js
@@ -7,4 +7,10 @@
//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
@@ -118,13 +124,4 @@
/**
- * Function to check if a node is a static string template literal.
- * @param {ASTNode} node The node to check.
- * @returns {boolean} If the node is a string template literal.
- */
- function isStaticTemplateLiteral(node) {
- return node && node.type === "TemplateLiteral" && node.expressions.length === 0;
- }
-
- /**
* Function to check if a node is a require call.
* @param {ASTNode} node The node to check.
@@ -145,5 +142,5 @@
}
- if (isStaticTemplateLiteral(node)) {
+ if (astUtils.isStaticTemplateLiteral(node)) {
return node.quasis[0].value.cooked.trim();
}
diff --git a/lib/rules/no-return-await.js b/lib/rules/no-return-await.js
index v8.43.0..v8.48.0 100644
--- a/lib/rules/no-return-await.js
+++ b/lib/rules/no-return-await.js
@@ -2,4 +2,5 @@
* @fileoverview Disallows unnecessary `return await`
* @author Jordan Harband
+ * @deprecated in ESLint v8.46.0
*/
"use strict";
@@ -27,4 +28,8 @@
fixable: null,
+ deprecated: true,
+
+ replacedBy: [],
+
schema: [
],
diff --git a/lib/rules/no-unused-labels.js b/lib/rules/no-unused-labels.js
index v8.43.0..v8.48.0 100644
--- a/lib/rules/no-unused-labels.js
+++ b/lib/rules/no-unused-labels.js
@@ -7,4 +7,10 @@
//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
@@ -48,4 +54,43 @@
/**
+ * Checks if a `LabeledStatement` node is fixable.
+ * For a node to be fixable, there must be no comments between the label and the body.
+ * Furthermore, is must be possible to remove the label without turning the body statement into a
+ * directive after other fixes are applied.
+ * @param {ASTNode} node The node to evaluate.
+ * @returns {boolean} Whether or not the node is fixable.
+ */
+ function isFixable(node) {
+
+ /*
+ * Only perform a fix if there are no comments between the label and the body. This will be the case
+ * when there is exactly one token/comment (the ":") between the label and the body.
+ */
+ if (sourceCode.getTokenAfter(node.label, { includeComments: true }) !==
+ sourceCode.getTokenBefore(node.body, { includeComments: true })) {
+ return false;
+ }
+
+ // Looking for the node's deepest ancestor which is not a `LabeledStatement`.
+ let ancestor = node.parent;
+
+ while (ancestor.type === "LabeledStatement") {
+ ancestor = ancestor.parent;
+ }
+
+ if (ancestor.type === "Program" ||
+ (ancestor.type === "BlockStatement" && astUtils.isFunction(ancestor.parent))) {
+ const { body } = node;
+
+ if (body.type === "ExpressionStatement" &&
+ ((body.expression.type === "Literal" && typeof body.expression.value === "string") ||
+ astUtils.isStaticTemplateLiteral(body.expression))) {
+ return false; // potential directive
+ }
+ }
+ return true;
+ }
+
+ /**
* Removes the top of the stack.
* At the same time, this reports the label if it's never used.
@@ -59,17 +104,5 @@
messageId: "unused",
data: node.label,
- fix(fixer) {
-
- /*
- * Only perform a fix if there are no comments between the label and the body. This will be the case
- * when there is exactly one token/comment (the ":") between the label and the body.
- */
- if (sourceCode.getTokenAfter(node.label, { includeComments: true }) ===
- sourceCode.getTokenBefore(node.body, { includeComments: true })) {
- return fixer.removeRange([node.range[0], node.body.range[0]]);
- }
-
- return null;
- }
+ fix: isFixable(node) ? fixer => fixer.removeRange([node.range[0], node.body.range[0]]) : null
});
}
diff --git a/lib/rules/no-unused-vars.js b/lib/rules/no-unused-vars.js
index v8.43.0..v8.48.0 100644
--- a/lib/rules/no-unused-vars.js
+++ b/lib/rules/no-unused-vars.js
@@ -467,5 +467,6 @@
parent.type === "AssignmentExpression" &&
parent.left === id &&
- isUnusedExpression(parent)
+ isUnusedExpression(parent) &&
+ !astUtils.isLogicalAssignmentOperator(parent.operator)
) ||
(
diff --git a/lib/rules/no-useless-backreference.js b/lib/rules/no-useless-backreference.js
index v8.43.0..v8.48.0 100644
--- a/lib/rules/no-useless-backreference.js
+++ b/lib/rules/no-useless-backreference.js
@@ -96,5 +96,5 @@
try {
- regExpAST = parser.parsePattern(pattern, 0, pattern.length, flags.includes("u"));
+ regExpAST = parser.parsePattern(pattern, 0, pattern.length, { unicode: flags.includes("u"), unicodeSets: flags.includes("v") });
} catch {
diff --git a/lib/rules/no-useless-escape.js b/lib/rules/no-useless-escape.js
index v8.43.0..v8.48.0 100644
--- a/lib/rules/no-useless-escape.js
+++ b/lib/rules/no-useless-escape.js
@@ -7,5 +7,10 @@
const astUtils = require("./utils/ast-utils");
+const { RegExpParser, visitRegExpAST } = require("@eslint-community/regexpp");
+/**
+ * @typedef {import('@eslint-community/regexpp').AST.CharacterClass} CharacterClass
+ * @typedef {import('@eslint-community/regexpp').AST.ExpressionCharacterClass} ExpressionCharacterClass
+ */
//------------------------------------------------------------------------------
// Rule Definition
@@ -29,54 +34,16 @@
const REGEX_NON_CHARCLASS_ESCAPES = union(REGEX_GENERAL_ESCAPES, new Set("^/.$*+?[{}|()Bk"));
-/**
- * Parses a regular expression into a list of characters with character class info.
- * @param {string} regExpText The raw text used to create the regular expression
- * @returns {Object[]} A list of characters, each with info on escaping and whether they're in a character class.
- * @example
- *
- * parseRegExp("a\\b[cd-]");
- *
- * // returns:
- * [
- * { text: "a", index: 0, escaped: false, inCharClass: false, startsCharClass: false, endsCharClass: false },
- * { text: "b", index: 2, escaped: true, inCharClass: false, startsCharClass: false, endsCharClass: false },
- * { text: "c", index: 4, escaped: false, inCharClass: true, startsCharClass: true, endsCharClass: false },
- * { text: "d", index: 5, escaped: false, inCharClass: true, startsCharClass: false, endsCharClass: false },
- * { text: "-", index: 6, escaped: false, inCharClass: true, startsCharClass: false, endsCharClass: false }
- * ];
- *
+/*
+ * Set of characters that require escaping in character classes in `unicodeSets` mode.
+ * ( ) [ ] { } / - \ | are ClassSetSyntaxCharacter
*/
-function parseRegExp(regExpText) {
- const charList = [];
+const REGEX_CLASSSET_CHARACTER_ESCAPES = union(REGEX_GENERAL_ESCAPES, new Set("q/[{}|()-"));
- regExpText.split("").reduce((state, char, index) => {
- if (!state.escapeNextChar) {
- if (char === "\\") {
- return Object.assign(state, { escapeNextChar: true });
- }
- if (char === "[" && !state.inCharClass) {
- return Object.assign(state, { inCharClass: true, startingCharClass: true });
- }
- if (char === "]" && state.inCharClass) {
- if (charList.length && charList[charList.length - 1].inCharClass) {
- charList[charList.length - 1].endsCharClass = true;
- }
- return Object.assign(state, { inCharClass: false, startingCharClass: false });
- }
- }
- charList.push({
- text: char,
- index,
- escaped: state.escapeNextChar,
- inCharClass: state.inCharClass,
- startsCharClass: state.startingCharClass,
- endsCharClass: false
- });
- return Object.assign(state, { escapeNextChar: false, startingCharClass: false });
- }, { escapeNextChar: false, inCharClass: false, startingCharClass: false });
+/*
+ * A single character set of ClassSetReservedDoublePunctuator.
+ * && !! ## $$ %% ** ++ ,, .. :: ;; << == >> ?? @@ ^^ `` ~~ are ClassSetReservedDoublePunctuator
+ */
+const REGEX_CLASS_SET_RESERVED_DOUBLE_PUNCTUATOR = new Set("!#$%&*+,.:;<=>?@^`~");
- return charList;
-}
-
/** @type {import('../shared/types').Rule} */
module.exports = {
@@ -95,4 +62,5 @@
unnecessaryEscape: "Unnecessary escape character: \\{{character}}.",
removeEscape: "Remove the `\\`. This maintains the current functionality.",
+ removeEscapeDoNotKeepSemantics: "Remove the `\\` if it was inserted by mistake.",
escapeBackslash: "Replace the `\\` with `\\\\` to include the actual backslash character."
},
@@ -103,4 +71,5 @@
create(context) {
const sourceCode = context.sourceCode;
+ const parser = new RegExpParser();
/**
@@ -109,7 +78,8 @@
* @param {number} startOffset The backslash's offset from the start of the node
* @param {string} character The uselessly escaped character (not including the backslash)
+ * @param {boolean} [disableEscapeBackslashSuggest] `true` if escapeBackslash suggestion should be turned off.
* @returns {void}
*/
- function report(node, startOffset, character) {
+ function report(node, startOffset, character, disableEscapeBackslashSuggest) {
const rangeStart = node.range[0] + startOffset;
const range = [rangeStart, rangeStart + 1];
@@ -126,15 +96,22 @@
suggest: [
{
- messageId: "removeEscape",
+
+ // Removing unnecessary `\` characters in a directive is not guaranteed to maintain functionality.
+ messageId: astUtils.isDirective(node.parent)
+ ? "removeEscapeDoNotKeepSemantics" : "removeEscape",
fix(fixer) {
return fixer.removeRange(range);
}
},
- {
- messageId: "escapeBackslash",
- fix(fixer) {
- return fixer.insertTextBeforeRange(range, "\\");
- }
- }
+ ...disableEscapeBackslashSuggest
+ ? []
+ : [
+ {
+ messageId: "escapeBackslash",
+ fix(fixer) {
+ return fixer.insertTextBeforeRange(range, "\\");
+ }
+ }
+ ]
]
});
@@ -180,4 +157,131 @@
/**
+ * Checks if the escape character in given regexp is unnecessary.
+ * @private
+ * @param {ASTNode} node node to validate.
+ * @returns {void}
+ */
+ function validateRegExp(node) {
+ const { pattern, flags } = node.regex;
+ let patternNode;
+ const unicode = flags.includes("u");
+ const unicodeSets = flags.includes("v");
+
+ try {
+ patternNode = parser.parsePattern(pattern, 0, pattern.length, { unicode, unicodeSets });
+ } catch {
+
+ // Ignore regular expressions with syntax errors
+ return;
+ }
+
+ /** @type {(CharacterClass | ExpressionCharacterClass)[]} */
+ const characterClassStack = [];
+
+ visitRegExpAST(patternNode, {
+ onCharacterClassEnter: characterClassNode => characterClassStack.unshift(characterClassNode),
+ onCharacterClassLeave: () => characterClassStack.shift(),
+ onExpressionCharacterClassEnter: characterClassNode => characterClassStack.unshift(characterClassNode),
+ onExpressionCharacterClassLeave: () => characterClassStack.shift(),
+ onCharacterEnter(characterNode) {
+ if (!characterNode.raw.startsWith("\\")) {
+
+ // It's not an escaped character.
+ return;
+ }
+
+ const escapedChar = characterNode.raw.slice(1);
+
+ if (escapedChar !== String.fromCodePoint(characterNode.value)) {
+
+ // It's a valid escape.
+ return;
+ }
+ let allowedEscapes;
+
+ if (characterClassStack.length) {
+ allowedEscapes = unicodeSets ? REGEX_CLASSSET_CHARACTER_ESCAPES : REGEX_GENERAL_ESCAPES;
+ } else {
+ allowedEscapes = REGEX_NON_CHARCLASS_ESCAPES;
+ }
+ if (allowedEscapes.has(escapedChar)) {
+ return;
+ }
+
+ const reportedIndex = characterNode.start + 1;
+ let disableEscapeBackslashSuggest = false;
+
+ if (characterClassStack.length) {
+ const characterClassNode = characterClassStack[0];
+
+ if (escapedChar === "^") {
+
+ /*
+ * The '^' character is also a special case; it must always be escaped outside of character classes, but
+ * it only needs to be escaped in character classes if it's at the beginning of the character class. To
+ * account for this, consider it to be a valid escape character outside of character classes, and filter
+ * out '^' characters that appear at the start of a character class.
+ */
+ if (characterClassNode.start + 1 === characterNode.start) {
+
+ return;
+ }
+ }
+ if (!unicodeSets) {
+ if (escapedChar === "-") {
+
+ /*
+ * The '-' character is a special case, because it's only valid to escape it if it's in a character
+ * class, and is not at either edge of the character class. To account for this, don't consider '-'
+ * characters to be valid in general, and filter out '-' characters that appear in the middle of a
+ * character class.
+ */
+ if (characterClassNode.start + 1 !== characterNode.start && characterNode.end !== characterClassNode.end - 1) {
+
+ return;
+ }
+ }
+ } else { // unicodeSets mode
+ if (REGEX_CLASS_SET_RESERVED_DOUBLE_PUNCTUATOR.has(escapedChar)) {
+
+ // Escaping is valid if it is a ClassSetReservedDoublePunctuator.
+ if (pattern[characterNode.end] === escapedChar) {
+ return;
+ }
+ if (pattern[characterNode.start - 1] === escapedChar) {
+ if (escapedChar !== "^") {
+ return;
+ }
+
+ // If the previous character is a `negate` caret(`^`), escape to caret is unnecessary.
+
+ if (!characterClassNode.negate) {
+ return;
+ }
+ const negateCaretIndex = characterClassNode.start + 1;
+
+ if (negateCaretIndex < characterNode.start - 1) {
+ return;
+ }
+ }
+ }
+
+ if (characterNode.parent.type === "ClassIntersection" || characterNode.parent.type === "ClassSubtraction") {
+ disableEscapeBackslashSuggest = true;
+ }
+ }
+ }
+
+ report(
+ node,
+ reportedIndex,
+ escapedChar,
+ disableEscapeBackslashSuggest
+ );
+ }
+ });
+ }
+
+ /**
* Checks if a node has an escape.
* @param {ASTNode} node node to check.
@@ -217,30 +321,5 @@
}
} else if (node.regex) {
- parseRegExp(node.regex.pattern)
-
- /*
- * The '-' character is a special case, because it's only valid to escape it if it's in a character
- * class, and is not at either edge of the character class. To account for this, don't consider '-'
- * characters to be valid in general, and filter out '-' characters that appear in the middle of a
- * character class.
- */
- .filter(charInfo => !(charInfo.text === "-" && charInfo.inCharClass && !charInfo.startsCharClass && !charInfo.endsCharClass))
-
- /*
- * The '^' character is also a special case; it must always be escaped outside of character classes, but
- * it only needs to be escaped in character classes if it's at the beginning of the character class. To
- * account for this, consider it to be a valid escape character outside of character classes, and filter
- * out '^' characters that appear at the start of a character class.
- */
- .filter(charInfo => !(charInfo.text === "^" && charInfo.startsCharClass))
-
- // Filter out characters that aren't escaped.
- .filter(charInfo => charInfo.escaped)
-
- // Filter out characters that are valid to escape, based on their position in the regular expression.
- .filter(charInfo => !(charInfo.inCharClass ? REGEX_GENERAL_ESCAPES : REGEX_NON_CHARCLASS_ESCAPES).has(charInfo.text))
-
- // Report all the remaining characters.
- .forEach(charInfo => report(node, charInfo.index, charInfo.text));
+ validateRegExp(node);
}
diff --git a/lib/rules/padding-line-between-statements.js b/lib/rules/padding-line-between-statements.js
index v8.43.0..v8.48.0 100644
--- a/lib/rules/padding-line-between-statements.js
+++ b/lib/rules/padding-line-between-statements.js
@@ -132,40 +132,4 @@
/**
- * Check whether the given node is a directive or not.
- * @param {ASTNode} node The node to check.
- * @param {SourceCode} sourceCode The source code object to get tokens.
- * @returns {boolean} `true` if the node is a directive.
- */
-function isDirective(node, sourceCode) {
- return (
- astUtils.isTopLevelExpressionStatement(node) &&
- node.expression.type === "Literal" &&
- typeof node.expression.value === "string" &&
- !astUtils.isParenthesised(sourceCode, node.expression)
- );
-}
-
-/**
- * Check whether the given node is a part of directive prologue or not.
- * @param {ASTNode} node The node to check.
- * @param {SourceCode} sourceCode The source code object to get tokens.
- * @returns {boolean} `true` if the node is a part of directive prologue.
- */
-function isDirectivePrologue(node, sourceCode) {
- if (isDirective(node, sourceCode)) {
- for (const sibling of node.parent.body) {
- if (sibling === node) {
- break;
- }
- if (!isDirective(sibling, sourceCode)) {
- return false;
- }
- }
- return true;
- }
- return false;
-}
-
-/**
* Gets the actual last token.
*
@@ -360,10 +324,8 @@
},
directive: {
- test: isDirectivePrologue
+ test: astUtils.isDirective
},
expression: {
- test: (node, sourceCode) =>
- node.type === "ExpressionStatement" &&
- !isDirectivePrologue(node, sourceCode)
+ test: node => node.type === "ExpressionStatement" && !astUtils.isDirective(node)
},
iife: {
@@ -376,8 +338,8 @@
},
"multiline-expression": {
- test: (node, sourceCode) =>
+ test: node =>
node.loc.start.line !== node.loc.end.line &&
node.type === "ExpressionStatement" &&
- !isDirectivePrologue(node, sourceCode)
+ !astUtils.isDirective(node)
},
diff --git a/lib/rules/prefer-exponentiation-operator.js b/lib/rules/prefer-exponentiation-operator.js
index v8.43.0..v8.48.0 100644
--- a/lib/rules/prefer-exponentiation-operator.js
+++ b/lib/rules/prefer-exponentiation-operator.js
@@ -56,9 +56,10 @@
const parent = node.parent.type === "ChainExpression" ? node.parent.parent : node.parent;
+ const parentPrecedence = astUtils.getPrecedence(parent);
const needsParens = (
parent.type === "ClassDeclaration" ||
(
parent.type.endsWith("Expression") &&
- astUtils.getPrecedence(parent) >= PRECEDENCE_OF_EXPONENTIATION_EXPR &&
+ (parentPrecedence === -1 || parentPrecedence >= PRECEDENCE_OF_EXPONENTIATION_EXPR) &&
!(parent.type === "BinaryExpression" && parent.operator === "**" && parent.right === node) &&
!((parent.type === "CallExpression" || parent.type === "NewExpression") && parent.arguments.includes(node)) &&
diff --git a/lib/rules/prefer-named-capture-group.js b/lib/rules/prefer-named-capture-group.js
index v8.43.0..v8.48.0 100644
--- a/lib/rules/prefer-named-capture-group.js
+++ b/lib/rules/prefer-named-capture-group.js
@@ -113,12 +113,15 @@
* @param {ASTNode} node AST node which contains the regular expression or a call/new expression.
* @param {ASTNode} regexNode AST node which contains the regular expression.
- * @param {boolean} uFlag Flag indicates whether unicode mode is enabled or not.
+ * @param {string|null} flags The regular expression flags to be checked.
* @returns {void}
*/
- function checkRegex(pattern, node, regexNode, uFlag) {
+ function checkRegex(pattern, node, regexNode, flags) {
let ast;
try {
- ast = parser.parsePattern(pattern, 0, pattern.length, uFlag);
+ ast = parser.parsePattern(pattern, 0, pattern.length, {
+ unicode: Boolean(flags && flags.includes("u")),
+ unicodeSets: Boolean(flags && flags.includes("v"))
+ });
} catch {
@@ -149,5 +152,5 @@
Literal(node) {
if (node.regex) {
- checkRegex(node.regex.pattern, node, node, node.regex.flags.includes("u"));
+ checkRegex(node.regex.pattern, node, node, node.regex.flags);
}
},
@@ -167,5 +170,5 @@
if (regex) {
- checkRegex(regex, refNode, refNode.arguments[0], flags && flags.includes("u"));
+ checkRegex(regex, refNode, refNode.arguments[0], flags);
}
}
diff --git a/lib/rules/prefer-regex-literals.js b/lib/rules/prefer-regex-literals.js
index v8.43.0..v8.48.0 100644
--- a/lib/rules/prefer-regex-literals.js
+++ b/lib/rules/prefer-regex-literals.js
@@ -38,13 +38,4 @@
}
-/**
- * Determines whether the given node is a template literal without expressions.
- * @param {ASTNode} node Node to check.
- * @returns {boolean} True if the node is a template literal without expressions.
- */
-function isStaticTemplateLiteral(node) {
- return node.type === "TemplateLiteral" && node.expressions.length === 0;
-}
-
const validPrecedingTokens = new Set([
"(",
@@ -179,5 +170,5 @@
astUtils.isSpecificMemberAccess(node.tag, "String", "raw") &&
isGlobalReference(astUtils.skipChainExpression(node.tag).object) &&
- isStaticTemplateLiteral(node.quasi);
+ astUtils.isStaticTemplateLiteral(node.quasi);
}
@@ -192,5 +183,5 @@
}
- if (isStaticTemplateLiteral(node)) {
+ if (astUtils.isStaticTemplateLiteral(node)) {
return node.quasis[0].value.cooked;
}
@@ -210,5 +201,5 @@
function isStaticString(node) {
return isStringLiteral(node) ||
- isStaticTemplateLiteral(node) ||
+ astUtils.isStaticTemplateLiteral(node) ||
isStringRawTaggedStaticTemplateLiteral(node);
}
@@ -251,5 +242,5 @@
* Returns a ecmaVersion compatible for regexpp.
* @param {number} ecmaVersion The ecmaVersion to convert.
- * @returns {import("regexpp/ecma-versions").EcmaVersion} The resulting ecmaVersion compatible for regexpp.
+ * @returns {import("@eslint-community/regexpp/ecma-versions").EcmaVersion} The resulting ecmaVersion compatible for regexpp.
*/
function getRegexppEcmaVersion(ecmaVersion) {
@@ -307,5 +298,8 @@
try {
- validator.validatePattern(pattern, 0, pattern.length, flags ? flags.includes("u") : false);
+ validator.validatePattern(pattern, 0, pattern.length, {
+ unicode: flags ? flags.includes("u") : false,
+ unicodeSets: flags ? flags.includes("v") : false
+ });
if (flags) {
validator.validateFlags(flags);
@@ -471,5 +465,8 @@
let charIncrease = 0;
- const ast = new RegExpParser({ ecmaVersion: regexppEcmaVersion }).parsePattern(regexContent, 0, regexContent.length, flags ? flags.includes("u") : false);
+ const ast = new RegExpParser({ ecmaVersion: regexppEcmaVersion }).parsePattern(regexContent, 0, regexContent.length, {
+ unicode: flags ? flags.includes("u") : false,
+ unicodeSets: flags ? flags.includes("v") : false
+ });
visitRegExpAST(ast, {
diff --git a/lib/rules/utils/regular-expressions.js b/lib/rules/utils/regular-expressions.js
index v8.43.0..v8.48.0 100644
--- a/lib/rules/utils/regular-expressions.js
+++ b/lib/rules/utils/regular-expressions.js
@@ -9,5 +9,5 @@
const { RegExpValidator } = require("@eslint-community/regexpp");
-const REGEXPP_LATEST_ECMA_VERSION = 2022;
+const REGEXPP_LATEST_ECMA_VERSION = 2024;
/**
@@ -29,5 +29,5 @@
try {
- validator.validatePattern(pattern, void 0, void 0, /* uFlag = */ true);
+ validator.validatePattern(pattern, void 0, void 0, { unicode: /* uFlag = */ true });
} catch {
return false;
diff --git a/lib/linter/report-translator.js b/lib/linter/report-translator.js
index v8.43.0..v8.48.0 100644
--- a/lib/linter/report-translator.js
+++ b/lib/linter/report-translator.js
@@ -102,4 +102,20 @@
/**
+ * Clones the given fix object.
+ * @param {Fix|null} fix The fix to clone.
+ * @returns {Fix|null} Deep cloned fix object or `null` if `null` or `undefined` was passed in.
+ */
+function cloneFix(fix) {
+ if (!fix) {
+ return null;
+ }
+
+ return {
+ range: [fix.range[0], fix.range[1]],
+ text: fix.text
+ };
+}
+
+/**
* Check that a fix has a valid range.
* @param {Fix|null} fix The fix to validate.
@@ -138,5 +154,5 @@
}
if (fixes.length === 1) {
- return fixes[0];
+ return cloneFix(fixes[0]);
}
@@ -184,5 +200,5 @@
assertValidFix(fix);
- return fix;
+ return cloneFix(fix);
}
diff --git a/lib/rules/require-unicode-regexp.js b/lib/rules/require-unicode-regexp.js
index v8.43.0..v8.48.0 100644
--- a/lib/rules/require-unicode-regexp.js
+++ b/lib/rules/require-unicode-regexp.js
@@ -29,5 +29,5 @@
docs: {
- description: "Enforce the use of `u` flag on RegExp",
+ description: "Enforce the use of `u` or `v` flag on RegExp",
recommended: false,
url: "https://eslint.org/docs/latest/rules/require-unicode-regexp"
@@ -52,5 +52,5 @@
const flags = node.regex.flags || "";
- if (!flags.includes("u")) {
+ if (!flags.includes("u") && !flags.includes("v")) {
context.report({
messageId: "requireUFlag",
@@ -86,5 +86,5 @@
const flags = getStringIfConstant(flagsNode, scope);
- if (!flagsNode || (typeof flags === "string" && !flags.includes("u"))) {
+ if (!flagsNode || (typeof flags === "string" && !flags.includes("u") && !flags.includes("v"))) {
context.report({
messageId: "requireUFlag",
diff --git a/lib/rule-tester/rule-tester.js b/lib/rule-tester/rule-tester.js
index v8.43.0..v8.48.0 100644
--- a/lib/rule-tester/rule-tester.js
+++ b/lib/rule-tester/rule-tester.js
@@ -63,6 +63,7 @@
/** @typedef {import("../shared/types").Parser} Parser */
+/** @typedef {import("../shared/types").Rule} Rule */
-/* eslint-disable jsdoc/valid-types -- https://github.com/jsdoc-type-pratt-parser/jsdoc-type-pratt-parser/issues/4#issuecomment-778805577 */
+
/**
* A test case that is expected to pass lint.
@@ -109,5 +110,4 @@
* @property {number} [endColumn] The 1-based column number of the reported end location.
*/
-/* eslint-enable jsdoc/valid-types -- https://github.com/jsdoc-type-pratt-parser/jsdoc-type-pratt-parser/issues/4#issuecomment-778805577 */
//------------------------------------------------------------------------------
@@ -510,8 +510,11 @@
* Define a rule for one particular run of tests.
* @param {string} name The name of the rule to define.
- * @param {Function} rule The rule definition.
+ * @param {Function | Rule} rule The rule definition.
* @returns {void}
*/
defineRule(name, rule) {
+ if (typeof rule === "function") {
+ emitLegacyRuleAPIWarning(name);
+ }
this.rules[name] = rule;
}
@@ -520,5 +523,5 @@
* Adds a new rule test to execute.
* @param {string} ruleName The name of the rule to run.
- * @param {Function} rule The rule to test.
+ * @param {Function | Rule} rule The rule to test.
* @param {{
* valid: (ValidTestCase | string)[],
@@ -1023,27 +1026,33 @@
* This creates a mocha test suite and pipes all supplied info through
* one of the templates above.
+ * The test suites for valid/invalid are created conditionally as
+ * test runners (eg. vitest) fail for empty test suites.
*/
this.constructor.describe(ruleName, () => {
- this.constructor.describe("valid", () => {
- test.valid.forEach(valid => {
- this.constructor[valid.only ? "itOnly" : "it"](
- sanitize(typeof valid === "object" ? valid.name || valid.code : valid),
- () => {
- testValidTemplate(valid);
- }
- );
+ if (test.valid.length > 0) {
+ this.constructor.describe("valid", () => {
+ test.valid.forEach(valid => {
+ this.constructor[valid.only ? "itOnly" : "it"](
+ sanitize(typeof valid === "object" ? valid.name || valid.code : valid),
+ () => {
+ testValidTemplate(valid);
+ }
+ );
+ });
});
- });
+ }
- this.constructor.describe("invalid", () => {
- test.invalid.forEach(invalid => {
- this.constructor[invalid.only ? "itOnly" : "it"](
- sanitize(invalid.name || invalid.code),
- () => {
- testInvalidTemplate(invalid);
- }
- );
+ if (test.invalid.length > 0) {
+ this.constructor.describe("invalid", () => {
+ test.invalid.forEach(invalid => {
+ this.constructor[invalid.only ? "itOnly" : "it"](
+ sanitize(invalid.name || invalid.code),
+ () => {
+ testInvalidTemplate(invalid);
+ }
+ );
+ });
});
- });
+ }
});
}
diff --git a/lib/shared/types.js b/lib/shared/types.js
index v8.43.0..v8.48.0 100644
--- a/lib/shared/types.js
+++ b/lib/shared/types.js
@@ -22,5 +22,5 @@
* @typedef {Object} ParserOptions
* @property {EcmaFeatures} [ecmaFeatures] The optional features.
- * @property {3|5|6|7|8|9|10|11|12|13|14|2015|2016|2017|2018|2019|2020|2021|2022|2023} [ecmaVersion] The ECMAScript version (or revision number).
+ * @property {3|5|6|7|8|9|10|11|12|13|14|15|2015|2016|2017|2018|2019|2020|2021|2022|2023|2024} [ecmaVersion] The ECMAScript version (or revision number).
* @property {"script"|"module"} [sourceType] The source code type.
* @property {boolean} [allowReserved] Allowing the use of reserved words as identifiers in ES3.
diff --git a/lib/unsupported-api.js b/lib/unsupported-api.js
index v8.43.0..v8.48.0 100644
--- a/lib/unsupported-api.js
+++ b/lib/unsupported-api.js
@@ -15,4 +15,5 @@
const { FlatESLint, shouldUseFlatConfig } = require("./eslint/flat-eslint");
const FlatRuleTester = require("./rule-tester/flat-rule-tester");
+const { ESLint } = require("./eslint/eslint");
//-----------------------------------------------------------------------------
@@ -25,4 +26,5 @@
shouldUseFlatConfig,
FlatRuleTester,
- FileEnumerator
+ FileEnumerator,
+ LegacyESLint: ESLint
};
diff --git a/lib/rules/valid-typeof.js b/lib/rules/valid-typeof.js
index v8.43.0..v8.48.0 100644
--- a/lib/rules/valid-typeof.js
+++ b/lib/rules/valid-typeof.js
@@ -6,4 +6,10 @@
//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
@@ -89,5 +95,5 @@
const sibling = parent.left === node ? parent.right : parent.left;
- if (sibling.type === "Literal" || sibling.type === "TemplateLiteral" && !sibling.expressions.length) {
+ if (sibling.type === "Literal" || astUtils.isStaticTemplateLiteral(sibling)) {
const value = sibling.type === "Literal" ? sibling.value : sibling.quasis[0].value.cooked;
diff --git a/lib/rules/yoda.js b/lib/rules/yoda.js
index v8.43.0..v8.48.0 100644
--- a/lib/rules/yoda.js
+++ b/lib/rules/yoda.js
@@ -60,13 +60,4 @@
/**
- * Determines whether a node is a Template Literal which can be determined statically.
- * @param {ASTNode} node Node to test
- * @returns {boolean} True if the node is a Template Literal without expression.
- */
-function isStaticTemplateLiteral(node) {
- return node.type === "TemplateLiteral" && node.expressions.length === 0;
-}
-
-/**
* Determines whether a non-Literal node should be treated as a single Literal node.
* @param {ASTNode} node Node to test
@@ -74,5 +65,5 @@
*/
function looksLikeLiteral(node) {
- return isNegativeNumericLiteral(node) || isStaticTemplateLiteral(node);
+ return isNegativeNumericLiteral(node) || astUtils.isStaticTemplateLiteral(node);
}
@@ -101,5 +92,5 @@
}
- if (isStaticTemplateLiteral(node)) {
+ if (astUtils.isStaticTemplateLiteral(node)) {
return {
type: "Literal",
diff --git a/package.json b/package.json
index v8.43.0..v8.48.0 100644
--- a/package.json
+++ b/package.json
@@ -1,5 +1,5 @@
{
"name": "eslint",
- "version": "8.43.0",
+ "version": "8.48.0",
"author": "Nicholas C. Zakas <nicholas+npm@nczconsulting.com>",
"description": "An AST-based pattern checker for JavaScript.",
@@ -62,11 +62,11 @@
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
- "@eslint-community/regexpp": "^4.4.0",
- "@eslint/eslintrc": "^2.0.3",
- "@eslint/js": "8.43.0",
+ "@eslint-community/regexpp": "^4.6.1",
+ "@eslint/eslintrc": "^2.1.2",
+ "@eslint/js": "8.48.0",
"@humanwhocodes/config-array": "^0.11.10",
"@humanwhocodes/module-importer": "^1.0.1",
"@nodelib/fs.walk": "^1.2.8",
- "ajv": "^6.10.0",
+ "ajv": "^6.12.4",
"chalk": "^4.0.0",
"cross-spawn": "^7.0.2",
@@ -74,7 +74,7 @@
"doctrine": "^3.0.0",
"escape-string-regexp": "^4.0.0",
- "eslint-scope": "^7.2.0",
- "eslint-visitor-keys": "^3.4.1",
- "espree": "^9.5.2",
+ "eslint-scope": "^7.2.2",
+ "eslint-visitor-keys": "^3.4.3",
+ "espree": "^9.6.1",
"esquery": "^1.4.2",
"esutils": "^2.0.2",
@@ -86,5 +86,4 @@
"graphemer": "^1.4.0",
"ignore": "^5.2.0",
- "import-fresh": "^3.0.0",
"imurmurhash": "^0.1.4",
"is-glob": "^4.0.0",
@@ -96,7 +95,6 @@
"minimatch": "^3.1.2",
"natural-compare": "^1.4.0",
- "optionator": "^0.9.1",
+ "optionator": "^0.9.3",
"strip-ansi": "^6.0.1",
- "strip-json-comments": "^3.1.0",
"text-table": "^0.2.0"
},
@@ -116,6 +114,6 @@
"eslint-plugin-eslint-plugin": "^5.1.0",
"eslint-plugin-internal-rules": "file:tools/internal-rules",
- "eslint-plugin-jsdoc": "^38.1.6",
- "eslint-plugin-n": "^15.2.4",
+ "eslint-plugin-jsdoc": "^46.2.5",
+ "eslint-plugin-n": "^16.0.0",
"eslint-plugin-unicorn": "^42.0.0",
"eslint-release": "^3.2.0",
@@ -154,8 +152,7 @@
"recast": "^0.20.4",
"regenerator-runtime": "^0.13.2",
- "semver": "^7.3.5",
+ "semver": "^7.5.3",
"shelljs": "^0.8.2",
"sinon": "^11.0.0",
- "temp": "^0.9.0",
"webpack": "^5.23.0",
"webpack-cli": "^4.5.0",
diff --git a/README.md b/README.md
index v8.43.0..v8.48.0 100644
--- a/README.md
+++ b/README.md
@@ -118,5 +118,5 @@
### What ECMAScript versions does ESLint support?
-ESLint has full support for ECMAScript 3, 5 (default), 2015, 2016, 2017, 2018, 2019, 2020, 2021 and 2022. 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 (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).
### What about experimental features?
@@ -250,4 +250,9 @@
Francesco Trotta
</a>
+</td><td align="center" valign="top" width="11%">
+<a href="https://github.com/ota-meshi">
+<img src="https://github.com/ota-meshi.png?s=75" width="75" height="75"><br />
+Yosuke Ota
+</a>
</td></tr></tbody></table>
@@ -285,5 +290,5 @@
<p><a href="https://engineering.salesforce.com"><img src="https://images.opencollective.com/salesforce/ca8f997/logo.png" alt="Salesforce" height="96"></a> <a href="https://www.airbnb.com/"><img src="https://images.opencollective.com/airbnb/d327d66/logo.png" alt="Airbnb" height="96"></a></p><h3>Silver Sponsors</h3>
<p><a href="https://sentry.io"><img src="https://avatars.githubusercontent.com/u/1396951?v=4" alt="Sentry" height="64"></a> <a href="https://liftoff.io/"><img src="https://images.opencollective.com/liftoff/5c4fa84/logo.png" alt="Liftoff" height="64"></a> <a href="https://americanexpress.io"><img src="https://avatars.githubusercontent.com/u/3853301?v=4" alt="American Express" height="64"></a></p><h3>Bronze Sponsors</h3>
-<p><a href="https://themeisle.com"><img src="https://images.opencollective.com/themeisle/d5592fe/logo.png" alt="ThemeIsle" height="32"></a> <a href="https://nx.dev"><img src="https://images.opencollective.com/nx/0efbe42/logo.png" alt="Nx (by Nrwl)" height="32"></a> <a href="https://www.crosswordsolver.org/anagram-solver/"><img src="https://images.opencollective.com/anagram-solver/2666271/logo.png" alt="Anagram Solver" height="32"></a> <a href="https://icons8.com"><img src="https://images.opencollective.com/icons8/7fa1641/logo.png" alt="Icons8: free icons, photos, illustrations, and music" height="32"></a> <a href="https://discord.com"><img src="https://images.opencollective.com/discordapp/f9645d9/logo.png" alt="Discord" height="32"></a> <a href="https://transloadit.com/"><img src="https://avatars.githubusercontent.com/u/125754?v=4" alt="Transloadit" height="32"></a> <a href="https://www.ignitionapp.com"><img src="https://avatars.githubusercontent.com/u/5753491?v=4" alt="Ignition" height="32"></a> <a href="https://herocoders.com"><img src="https://avatars.githubusercontent.com/u/37549774?v=4" alt="HeroCoders" height="32"></a> <a href="https://quickbookstoolhub.com"><img src="https://avatars.githubusercontent.com/u/95090305?u=e5bc398ef775c9ed19f955c675cdc1fb6abf01df&v=4" alt="QuickBooks Tool hub" height="32"></a></p>
+<p><a href="https://themeisle.com"><img src="https://images.opencollective.com/themeisle/d5592fe/logo.png" alt="ThemeIsle" height="32"></a> <a href="https://nx.dev"><img src="https://images.opencollective.com/nx/0efbe42/logo.png" alt="Nx (by Nrwl)" height="32"></a> <a href="https://www.crosswordsolver.org/anagram-solver/"><img src="https://images.opencollective.com/anagram-solver/2666271/logo.png" alt="Anagram Solver" height="32"></a> <a href="https://icons8.com"><img src="https://images.opencollective.com/icons8/7fa1641/logo.png" alt="Icons8: free icons, photos, illustrations, and music" height="32"></a> <a href="https://discord.com"><img src="https://images.opencollective.com/discordapp/f9645d9/logo.png" alt="Discord" height="32"></a> <a href="https://github.com/about"><img src="https://avatars.githubusercontent.com/u/9919?v=4" alt="GitHub" height="32"></a> <a href="https://transloadit.com/"><img src="https://avatars.githubusercontent.com/u/125754?v=4" alt="Transloadit" height="32"></a> <a href="https://www.ignitionapp.com"><img src="https://avatars.githubusercontent.com/u/5753491?v=4" alt="Ignition" height="32"></a> <a href="https://herocoders.com"><img src="https://avatars.githubusercontent.com/u/37549774?v=4" alt="HeroCoders" height="32"></a> <a href="https://quickbookstoolhub.com"><img src="https://avatars.githubusercontent.com/u/95090305?u=e5bc398ef775c9ed19f955c675cdc1fb6abf01df&v=4" alt="QuickBooks Tool hub" height="32"></a></p>
<!--sponsorsend-->
diff --git a/messages/eslintrc-incompat.js b/messages/eslintrc-incompat.js
new file mode 100644
index v8.43.0..v8.48.0
--- a/messages/eslintrc-incompat.js
+++ b/messages/eslintrc-incompat.js
@@ -0,0 +1,98 @@
+"use strict";
+
+/* eslint consistent-return: 0 -- no default case */
+
+const messages = {
+
+ env: `
+A config object is using the "env" key, which is not supported in flat config system.
+
+Flat config uses "languageOptions.globals" to define global variables for your files.
+
+Please see the following page for information on how to convert your config object into the correct format:
+https://eslint.org/docs/latest/use/configure/migration-guide#configuring-language-options
+`,
+
+ extends: `
+A config object is using the "extends" key, which is not supported in flat config system.
+
+Instead of "extends", you can include config objects that you'd like to extend from directly in the flat config array.
+
+Please see the following page for more information:
+https://eslint.org/docs/latest/use/configure/migration-guide#predefined-and-shareable-configs
+`,
+
+ globals: `
+A config object is using the "globals" key, which is not supported in flat config system.
+
+Flat config uses "languageOptions.globals" to define global variables for your files.
+
+Please see the following page for information on how to convert your config object into the correct format:
+https://eslint.org/docs/latest/use/configure/migration-guide#configuring-language-options
+`,
+
+ ignorePatterns: `
+A config object is using the "ignorePatterns" key, which is not supported in flat config system.
+
+Flat config uses "ignores" to specify files to ignore.
+
+Please see the following page for information on how to convert your config object into the correct format:
+https://eslint.org/docs/latest/use/configure/migration-guide#ignoring-files
+`,
+
+ noInlineConfig: `
+A config object is using the "noInlineConfig" key, which is not supported in flat config system.
+
+Flat config uses "linterOptions.noInlineConfig" to specify files to ignore.
+
+Please see the following page for information on how to convert your config object into the correct format:
+https://eslint.org/docs/latest/use/configure/migration-guide#linter-options
+`,
+
+ overrides: `
+A config object is using the "overrides" key, which is not supported in flat config system.
+
+Flat config is an array that acts like the eslintrc "overrides" array.
+
+Please see the following page for information on how to convert your config object into the correct format:
+https://eslint.org/docs/latest/use/configure/migration-guide#glob-based-configs
+`,
+
+ parser: `
+A config object is using the "parser" key, which is not supported in flat config system.
+
+Flat config uses "languageOptions.parser" to override the default parser.
+
+Please see the following page for information on how to convert your config object into the correct format:
+https://eslint.org/docs/latest/use/configure/migration-guide#custom-parsers
+`,
+
+ parserOptions: `
+A config object is using the "parserOptions" key, which is not supported in flat config system.
+
+Flat config uses "languageOptions.parserOptions" to specify parser options.
+
+Please see the following page for information on how to convert your config object into the correct format:
+https://eslint.org/docs/latest/use/configure/migration-guide#configuring-language-options
+`,
+
+ reportUnusedDisableDirectives: `
+A config object is using the "reportUnusedDisableDirectives" key, which is not supported in flat config system.
+
+Flat config uses "linterOptions.reportUnusedDisableDirectives" to specify files to ignore.
+
+Please see the following page for information on how to convert your config object into the correct format:
+https://eslint.org/docs/latest/use/configure/migration-guide#linter-options
+`,
+
+ root: `
+A config object is using the "root" key, which is not supported in flat config system.
+
+Flat configs always act as if they are the root config file, so this key can be safely removed.
+`
+};
+
+module.exports = function({ key }) {
+
+ return messages[key].trim();
+};
diff --git a/messages/eslintrc-plugins.js b/messages/eslintrc-plugins.js
new file mode 100644
index v8.43.0..v8.48.0
--- a/messages/eslintrc-plugins.js
+++ b/messages/eslintrc-plugins.js
@@ -0,0 +1,24 @@
+"use strict";
+
+module.exports = function({ plugins }) {
+
+ const isArrayOfStrings = typeof plugins[0] === "string";
+
+ return `
+A config object has a "plugins" key defined as an array${isArrayOfStrings ? " of strings" : ""}.
+
+Flat config requires "plugins" to be an object in this form:
+
+ {
+ plugins: {
+ ${isArrayOfStrings && plugins[0] ? plugins[0] : "namespace"}: pluginObject
+ }
+ }
+
+Please see the following page for information on how to convert your config object into the correct format:
+https://eslint.org/docs/latest/use/configure/migration-guide#importing-plugins-and-custom-parsers
+
+If you're using a shareable config that you cannot rewrite in flat config format, then use the compatibility utility:
+https://eslint.org/docs/latest/use/configure/migration-guide#using-eslintrc-configs-in-flat-config
+`;
+};
Command detailsnpm diff --diff=eslint@8.43.0 --diff=eslint@8.48.0 --diff-unified=2 See also the Reported by ybiquitous/npm-diff-action@v1.4.1 (Node.js 18.17.1 and npm 10.0.0) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Labels
dependencies
Pull requests that update a dependency file
javascript
Pull requests that update Javascript code
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Bumps eslint from 8.43.0 to 8.48.0.
Release notes
Sourced from eslint's releases.
... (truncated)
Changelog
Sourced from eslint's changelog.
... (truncated)
Commits
10c4f85
8.48.05013ad7
Build: changelog update for 8.48.08dd3cec
chore: upgrade@eslint/js
@8
.48.0 (#17501)6d0496e
chore: package.json update for@eslint/js
release7a51d77
docs: no-param-reassign mention strict mode (#17494)9cd7ac2
docs: addfetch
script to package.json conventions (#17459)7234f6a
fix: update RuleTester JSDoc and deprecations (#17496)1fbb3b0
feat: correct update direction infor-direction
(#17483)9d4216d
chore: Refactor and document CodePathSegment (#17474)cab21e6
docs: advice for inline disabling of rules (#17458)Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting
@dependabot rebase
.Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR:
@dependabot rebase
will rebase this PR@dependabot recreate
will recreate this PR, overwriting any edits that have been made to it@dependabot merge
will merge this PR after your CI passes on it@dependabot squash and merge
will squash and merge this PR after your CI passes on it@dependabot cancel merge
will cancel a previously requested merge and block automerging@dependabot reopen
will reopen this PR if it is closed@dependabot close
will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually@dependabot show <dependency name> ignore conditions
will show all of the ignore conditions of the specified dependency@dependabot ignore this major version
will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)@dependabot ignore this minor version
will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)@dependabot ignore this dependency
will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)