Socket
Socket
Sign inDemoInstall

eslint

Package Overview
Dependencies
Maintainers
4
Versions
367
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

eslint - npm Package Compare versions

Comparing version 8.44.0 to 8.50.0

lib/rules/no-object-constructor.js

82

lib/config/flat-config-schema.js

@@ -215,2 +215,34 @@ /**

/**
* 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 };
}
}
//-----------------------------------------------------------------------------

@@ -307,2 +339,7 @@ // Low-Level Schemas

// 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

@@ -443,2 +480,30 @@ for (const key of Object.keys(value)) {

/**
* 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"
];
//-----------------------------------------------------------------------------

@@ -448,3 +513,8 @@ // Full schema

exports.flatConfigSchema = {
const flatConfigSchema = {
// eslintrc-style keys that should always error
...Object.fromEntries(eslintrcKeys.map(key => [key, createEslintrcErrorSchema(key)])),
// flat config keys
settings: deepObjectAssignSchema,

@@ -470,1 +540,11 @@ linterOptions: {

};
//-----------------------------------------------------------------------------
// Exports
//-----------------------------------------------------------------------------
module.exports = {
flatConfigSchema,
assertIsRuleSeverity,
assertIsRuleOptions
};

3

lib/config/rule-validator.js

@@ -12,3 +12,4 @@ /**

const ajv = require("../shared/ajv")();
const ajvImport = require("../shared/ajv");
const ajv = ajvImport();
const {

@@ -15,0 +16,0 @@ parseRuleId,

@@ -579,3 +579,2 @@ /**

defaultConfigs,
defaultIgnores: () => false,
configs: null

@@ -719,8 +718,6 @@ });

// 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);
}

@@ -727,0 +724,0 @@ }

@@ -195,11 +195,14 @@ /**

if (currentSegment !== headSegment && currentSegment) {
debug.dump(`onCodePathSegmentEnd ${currentSegment.id}`);
if (currentSegment.reachable) {
analyzer.emitter.emit(
"onCodePathSegmentEnd",
currentSegment,
node
);
}
const eventName = currentSegment.reachable
? "onCodePathSegmentEnd"
: "onUnreachableCodePathSegmentEnd";
debug.dump(`${eventName} ${currentSegment.id}`);
analyzer.emitter.emit(
eventName,
currentSegment,
node
);
}

@@ -217,12 +220,15 @@ }

if (currentSegment !== headSegment && headSegment) {
debug.dump(`onCodePathSegmentStart ${headSegment.id}`);
const eventName = headSegment.reachable
? "onCodePathSegmentStart"
: "onUnreachableCodePathSegmentStart";
debug.dump(`${eventName} ${headSegment.id}`);
CodePathSegment.markUsed(headSegment);
if (headSegment.reachable) {
analyzer.emitter.emit(
"onCodePathSegmentStart",
headSegment,
node
);
}
analyzer.emitter.emit(
eventName,
headSegment,
node
);
}

@@ -246,11 +252,13 @@ }

const currentSegment = currentSegments[i];
const eventName = currentSegment.reachable
? "onCodePathSegmentEnd"
: "onUnreachableCodePathSegmentEnd";
debug.dump(`onCodePathSegmentEnd ${currentSegment.id}`);
if (currentSegment.reachable) {
analyzer.emitter.emit(
"onCodePathSegmentEnd",
currentSegment,
node
);
}
debug.dump(`${eventName} ${currentSegment.id}`);
analyzer.emitter.emit(
eventName,
currentSegment,
node
);
}

@@ -257,0 +265,0 @@

/**
* @fileoverview A class of the code path segment.
* @fileoverview The CodePathSegment class.
* @author Toru Nagashima

@@ -33,2 +33,13 @@ */

* 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.
*/

@@ -38,2 +49,3 @@ class CodePathSegment {

/**
* Creates a new instance.
* @param {string} id An identifier.

@@ -54,3 +66,3 @@ * @param {CodePathSegment[]} allPrevSegments An array of the previous segments.

/**
* An array of the next segments.
* An array of the next reachable segments.
* @type {CodePathSegment[]}

@@ -61,3 +73,3 @@ */

/**
* An array of the previous segments.
* An array of the previous reachable segments.
* @type {CodePathSegment[]}

@@ -68,4 +80,3 @@ */

/**
* An array of the next segments.
* This array includes unreachable segments.
* An array of all next segments including reachable and unreachable.
* @type {CodePathSegment[]}

@@ -76,4 +87,3 @@ */

/**
* An array of the previous segments.
* This array includes unreachable segments.
* An array of all previous segments including reachable and unreachable.
* @type {CodePathSegment[]}

@@ -92,3 +102,7 @@ */

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: []

@@ -123,5 +137,6 @@ }

/**
* 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.

@@ -138,3 +153,3 @@ */

/**
* Creates an unreachable segment that follows given segments.
* Creates an unreachable segment and appends it after the given segments.
* @param {string} id An identifier.

@@ -149,3 +164,3 @@ * @param {CodePathSegment[]} allPrevSegments An array of the previous segments.

* 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.
*/

@@ -170,3 +185,3 @@ CodePathSegment.markUsed(segment);

/**
* Makes a given segment being used.
* Marks a given segment as used.
*

@@ -186,2 +201,9 @@ * And this function registers the segment into the previous segments as a next.

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) {

@@ -194,2 +216,9 @@ const prevSegment = segment.allPrevSegments[i];

} 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) {

@@ -212,9 +241,10 @@ segment.allPrevSegments[i].allNextSegments.push(segment);

/**
* 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();

@@ -225,3 +255,3 @@ for (let i = 0; i < segments.length; ++i) {

// Ignores duplicated.
if (done[segment.id]) {
if (done.has(segment)) {
continue;

@@ -235,14 +265,12 @@ }

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];
}

@@ -249,0 +277,0 @@ }

@@ -120,2 +120,3 @@ /**

* @type {CodePathSegment[]}
* @deprecated
*/

@@ -122,0 +123,0 @@ get currentSegments() {

@@ -104,2 +104,18 @@ /**

/**
* 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.

@@ -141,3 +157,3 @@ * @param {Fix|null} fix The fix to validate.

if (fixes.length === 1) {
return fixes[0];
return cloneFix(fixes[0]);
}

@@ -188,3 +204,3 @@

assertValidFix(fix);
return fix;
return cloneFix(fix);
}

@@ -191,0 +207,0 @@

@@ -19,3 +19,5 @@ /**

{ getRuleOptionsSchema } = require("../config/flat-config-helpers"),
{ Linter, SourceCodeFixer, interpolate } = require("../linter");
{ Linter, SourceCodeFixer, interpolate } = require("../linter"),
CodePath = require("../linter/code-path-analysis/code-path");
const { FlatConfigArray } = require("../config/flat-config-array");

@@ -36,2 +38,3 @@ const { defaultConfig } = require("../config/default-config");

/** @typedef {import("../shared/types").LanguageOptions} LanguageOptions */
/** @typedef {import("../shared/types").Rule} Rule */

@@ -135,2 +138,11 @@

const forbiddenMethods = [
"applyInlineConfig",
"applyLanguageOptions",
"finalize"
];
/** @type {Map<string,WeakSet>} */
const forbiddenMethodCalls = new Map(forbiddenMethods.map(methodName => ([methodName, new WeakSet()])));
const hasOwnProperty = Function.call.bind(Object.hasOwnProperty);

@@ -279,2 +291,45 @@

/**
* Emit a deprecation warning if rule uses CodePath#currentSegments.
* @param {string} ruleName Name of the rule.
* @returns {void}
*/
function emitCodePathCurrentSegmentsWarning(ruleName) {
if (!emitCodePathCurrentSegmentsWarning[`warned-${ruleName}`]) {
emitCodePathCurrentSegmentsWarning[`warned-${ruleName}`] = true;
process.emitWarning(
`"${ruleName}" rule uses CodePath#currentSegments and will stop working in ESLint v9. Please read the documentation for how to update your code: https://eslint.org/docs/latest/extend/code-path-analysis#usage-examples`,
"DeprecationWarning"
);
}
}
/**
* Function to replace forbidden `SourceCode` methods. Allows just one call per method.
* @param {string} methodName The name of the method to forbid.
* @param {Function} prototype The prototype with the original method to call.
* @returns {Function} The function that throws the error.
*/
function throwForbiddenMethodError(methodName, prototype) {
const original = prototype[methodName];
return function(...args) {
const called = forbiddenMethodCalls.get(methodName);
/* eslint-disable no-invalid-this -- needed to operate as a method. */
if (!called.has(this)) {
called.add(this);
return original.apply(this, args);
}
/* eslint-enable no-invalid-this -- not needed past this point */
throw new Error(
`\`SourceCode#${methodName}()\` cannot be called inside a rule.`
);
};
}
//------------------------------------------------------------------------------

@@ -453,3 +508,3 @@ // Public Interface

* @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 {{

@@ -488,2 +543,3 @@ * valid: (ValidTestCase | string)[],

const baseConfig = [
{ files: ["**"] }, // Make sure the default config matches for all files
{

@@ -670,6 +726,2 @@ plugins: {

// Verify the code.
const { getComments } = SourceCode.prototype;
let messages;
// check for validation errors

@@ -684,9 +736,30 @@ try {

// Verify the code.
const { getComments, applyLanguageOptions, applyInlineConfig, finalize } = SourceCode.prototype;
const originalCurrentSegments = Object.getOwnPropertyDescriptor(CodePath.prototype, "currentSegments");
let messages;
try {
SourceCode.prototype.getComments = getCommentsDeprecation;
Object.defineProperty(CodePath.prototype, "currentSegments", {
get() {
emitCodePathCurrentSegmentsWarning(ruleName);
return originalCurrentSegments.get.call(this);
}
});
forbiddenMethods.forEach(methodName => {
SourceCode.prototype[methodName] = throwForbiddenMethodError(methodName, SourceCode.prototype);
});
messages = linter.verify(code, configs, filename);
} finally {
SourceCode.prototype.getComments = getComments;
Object.defineProperty(CodePath.prototype, "currentSegments", originalCurrentSegments);
SourceCode.prototype.applyInlineConfig = applyInlineConfig;
SourceCode.prototype.applyLanguageOptions = applyLanguageOptions;
SourceCode.prototype.finalize = finalize;
}
const fatalErrorMessage = messages.find(m => m.fatal);

@@ -1022,25 +1095,31 @@

* 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);
}
);
});
});
});
}
});

@@ -1047,0 +1126,0 @@ }

@@ -51,3 +51,4 @@ /**

{ getRuleOptionsSchema, validate } = require("../shared/config-validator"),
{ Linter, SourceCodeFixer, interpolate } = require("../linter");
{ Linter, SourceCodeFixer, interpolate } = require("../linter"),
CodePath = require("../linter/code-path-analysis/code-path");

@@ -66,2 +67,3 @@ const ajv = require("../shared/ajv")({ strictDefaults: true });

/** @typedef {import("../shared/types").Parser} Parser */
/** @typedef {import("../shared/types").Rule} Rule */

@@ -166,4 +168,39 @@

const forbiddenMethods = [
"applyInlineConfig",
"applyLanguageOptions",
"finalize"
];
const hasOwnProperty = Function.call.bind(Object.hasOwnProperty);
const DEPRECATED_SOURCECODE_PASSTHROUGHS = {
getSource: "getText",
getSourceLines: "getLines",
getAllComments: "getAllComments",
getNodeByRangeIndex: "getNodeByRangeIndex",
// getComments: "getComments", -- already handled by a separate error
getCommentsBefore: "getCommentsBefore",
getCommentsAfter: "getCommentsAfter",
getCommentsInside: "getCommentsInside",
getJSDocComment: "getJSDocComment",
getFirstToken: "getFirstToken",
getFirstTokens: "getFirstTokens",
getLastToken: "getLastToken",
getLastTokens: "getLastTokens",
getTokenAfter: "getTokenAfter",
getTokenBefore: "getTokenBefore",
getTokenByRangeStart: "getTokenByRangeStart",
getTokens: "getTokens",
getTokensAfter: "getTokensAfter",
getTokensBefore: "getTokensBefore",
getTokensBetween: "getTokensBetween",
getScope: "getScope",
getAncestors: "getAncestors",
getDeclaredVariables: "getDeclaredVariables",
markVariableAsUsed: "markVariableAsUsed"
};
/**

@@ -311,2 +348,15 @@ * Clones a given value deeply.

/**
* Function to replace forbidden `SourceCode` methods.
* @param {string} methodName The name of the method to forbid.
* @returns {Function} The function that throws the error.
*/
function throwForbiddenMethodError(methodName) {
return () => {
throw new Error(
`\`SourceCode#${methodName}()\` cannot be called inside a rule.`
);
};
}
/**
* Emit a deprecation warning if function-style format is being used.

@@ -341,2 +391,49 @@ * @param {string} ruleName Name of the rule.

/**
* Emit a deprecation warning if a rule uses a deprecated `context` method.
* @param {string} ruleName Name of the rule.
* @param {string} methodName The name of the method on `context` that was used.
* @returns {void}
*/
function emitDeprecatedContextMethodWarning(ruleName, methodName) {
if (!emitDeprecatedContextMethodWarning[`warned-${ruleName}-${methodName}`]) {
emitDeprecatedContextMethodWarning[`warned-${ruleName}-${methodName}`] = true;
process.emitWarning(
`"${ruleName}" rule is using \`context.${methodName}()\`, which is deprecated and will be removed in ESLint v9. Please use \`sourceCode.${DEPRECATED_SOURCECODE_PASSTHROUGHS[methodName]}()\` instead.`,
"DeprecationWarning"
);
}
}
/**
* Emit a deprecation warning if rule uses CodePath#currentSegments.
* @param {string} ruleName Name of the rule.
* @returns {void}
*/
function emitCodePathCurrentSegmentsWarning(ruleName) {
if (!emitCodePathCurrentSegmentsWarning[`warned-${ruleName}`]) {
emitCodePathCurrentSegmentsWarning[`warned-${ruleName}`] = true;
process.emitWarning(
`"${ruleName}" rule uses CodePath#currentSegments and will stop working in ESLint v9. Please read the documentation for how to update your code: https://eslint.org/docs/latest/extend/code-path-analysis#usage-examples`,
"DeprecationWarning"
);
}
}
/**
* Emit a deprecation warning if `context.parserServices` is used.
* @param {string} ruleName Name of the rule.
* @returns {void}
*/
function emitParserServicesWarning(ruleName) {
if (!emitParserServicesWarning[`warned-${ruleName}`]) {
emitParserServicesWarning[`warned-${ruleName}`] = true;
process.emitWarning(
`"${ruleName}" rule is using \`context.parserServices\`, which is deprecated and will be removed in ESLint v9. Please use \`sourceCode.parserServices\` instead.`,
"DeprecationWarning"
);
}
}
//------------------------------------------------------------------------------

@@ -516,6 +613,9 @@ // Public Interface

* @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;

@@ -527,3 +627,3 @@ }

* @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 {{

@@ -572,3 +672,34 @@ * valid: (ValidTestCase | string)[],

return (typeof rule === "function" ? rule : rule.create)(context);
// wrap all deprecated methods
const newContext = Object.create(
context,
Object.fromEntries(Object.keys(DEPRECATED_SOURCECODE_PASSTHROUGHS).map(methodName => [
methodName,
{
value(...args) {
// emit deprecation warning
emitDeprecatedContextMethodWarning(ruleName, methodName);
// call the original method
return context[methodName].call(this, ...args);
},
enumerable: true
}
]))
);
// emit warning about context.parserServices
const parserServices = context.parserServices;
Object.defineProperty(newContext, "parserServices", {
get() {
emitParserServicesWarning(ruleName);
return parserServices;
}
});
Object.freeze(newContext);
return (typeof rule === "function" ? rule : rule.create)(newContext);
}

@@ -692,3 +823,4 @@ }));

// Verify the code.
const { getComments } = SourceCode.prototype;
const { getComments, applyLanguageOptions, applyInlineConfig, finalize } = SourceCode.prototype;
const originalCurrentSegments = Object.getOwnPropertyDescriptor(CodePath.prototype, "currentSegments");
let messages;

@@ -698,5 +830,20 @@

SourceCode.prototype.getComments = getCommentsDeprecation;
Object.defineProperty(CodePath.prototype, "currentSegments", {
get() {
emitCodePathCurrentSegmentsWarning(ruleName);
return originalCurrentSegments.get.call(this);
}
});
forbiddenMethods.forEach(methodName => {
SourceCode.prototype[methodName] = throwForbiddenMethodError(methodName);
});
messages = linter.verify(code, config, filename);
} finally {
SourceCode.prototype.getComments = getComments;
Object.defineProperty(CodePath.prototype, "currentSegments", originalCurrentSegments);
SourceCode.prototype.applyInlineConfig = applyInlineConfig;
SourceCode.prototype.applyLanguageOptions = applyLanguageOptions;
SourceCode.prototype.finalize = finalize;
}

@@ -1034,25 +1181,31 @@

* 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);
}
);
});
});
});
}
});

@@ -1059,0 +1212,0 @@ }

@@ -22,11 +22,2 @@ /**

/**
* Checks a given code path segment is reachable.
* @param {CodePathSegment} segment A segment to check.
* @returns {boolean} `true` if the segment is reachable.
*/
function isReachable(segment) {
return segment.reachable;
}
/**
* Checks a given node is a member access which has the specified name's

@@ -43,2 +34,18 @@ * property.

/**
* Checks all segments in a set and returns true if any are reachable.
* @param {Set<CodePathSegment>} segments The segments to check.
* @returns {boolean} True if any segment is reachable; false otherwise.
*/
function isAnySegmentReachable(segments) {
for (const segment of segments) {
if (segment.reachable) {
return true;
}
}
return false;
}
/**
* Returns a human-legible description of an array method

@@ -134,2 +141,72 @@ * @param {string} arrayMethodName A method name to fully qualify

/**
* 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 isExpressionVoid(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) {
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, "}")
];
}
//------------------------------------------------------------------------------

@@ -150,2 +227,5 @@ // Rule Definition

// eslint-disable-next-line eslint-plugin/require-meta-has-suggestions -- false positive
hasSuggestions: true,
schema: [

@@ -162,2 +242,6 @@ {

default: false
},
allowVoid: {
type: "boolean",
default: false
}

@@ -173,3 +257,5 @@ },

expectedReturnValue: "{{arrayMethodName}}() expects a return value from {{name}}.",
expectedNoReturnValue: "{{arrayMethodName}}() expects no useless return value from {{name}}."
expectedNoReturnValue: "{{arrayMethodName}}() expects no useless return value from {{name}}.",
wrapBraces: "Wrap the expression in `{}`.",
prependVoid: "Prepend `void` to the expression."
}

@@ -180,3 +266,3 @@ },

const options = context.options[0] || { allowImplicit: false, checkForEach: false };
const options = context.options[0] || { allowImplicit: false, checkForEach: false, allowVoid: false };
const sourceCode = context.sourceCode;

@@ -208,15 +294,44 @@

let messageId = null;
const messageAndSuggestions = { messageId: "", suggest: [] };
if (funcInfo.arrayMethodName === "forEach") {
if (options.checkForEach && node.type === "ArrowFunctionExpression" && node.expression) {
messageId = "expectedNoReturnValue";
if (options.allowVoid) {
if (isExpressionVoid(node.body)) {
return;
}
messageAndSuggestions.messageId = "expectedNoReturnValue";
messageAndSuggestions.suggest = [
{
messageId: "wrapBraces",
fix(fixer) {
return curlyWrapFixer(sourceCode, node, fixer);
}
},
{
messageId: "prependVoid",
fix(fixer) {
return voidPrependFixer(sourceCode, node.body, fixer);
}
}
];
} else {
messageAndSuggestions.messageId = "expectedNoReturnValue";
messageAndSuggestions.suggest = [{
messageId: "wrapBraces",
fix(fixer) {
return curlyWrapFixer(sourceCode, node, fixer);
}
}];
}
}
} else {
if (node.body.type === "BlockStatement" && funcInfo.codePath.currentSegments.some(isReachable)) {
messageId = funcInfo.hasReturn ? "expectedAtEnd" : "expectedInside";
if (node.body.type === "BlockStatement" && isAnySegmentReachable(funcInfo.currentSegments)) {
messageAndSuggestions.messageId = funcInfo.hasReturn ? "expectedAtEnd" : "expectedInside";
}
}
if (messageId) {
if (messageAndSuggestions.messageId) {
const name = astUtils.getFunctionNameWithKind(node);

@@ -227,4 +342,5 @@

loc: astUtils.getFunctionHeadLoc(node, sourceCode),
messageId,
data: { name, arrayMethodName: fullMethodName(funcInfo.arrayMethodName) }
messageId: messageAndSuggestions.messageId,
data: { name, arrayMethodName: fullMethodName(funcInfo.arrayMethodName) },
suggest: messageAndSuggestions.suggest.length !== 0 ? messageAndSuggestions.suggest : null
});

@@ -254,3 +370,4 @@ }

!node.generator,
node
node,
currentSegments: new Set()
};

@@ -264,2 +381,19 @@ },

onUnreachableCodePathSegmentStart(segment) {
funcInfo.currentSegments.add(segment);
},
onUnreachableCodePathSegmentEnd(segment) {
funcInfo.currentSegments.delete(segment);
},
onCodePathSegmentStart(segment) {
funcInfo.currentSegments.add(segment);
},
onCodePathSegmentEnd(segment) {
funcInfo.currentSegments.delete(segment);
},
// Checks the return statement is valid.

@@ -274,3 +408,3 @@ ReturnStatement(node) {

let messageId = null;
const messageAndSuggestions = { messageId: "", suggest: [] };

@@ -281,3 +415,18 @@ if (funcInfo.arrayMethodName === "forEach") {

if (options.checkForEach && node.argument) {
messageId = "expectedNoReturnValue";
if (options.allowVoid) {
if (isExpressionVoid(node.argument)) {
return;
}
messageAndSuggestions.messageId = "expectedNoReturnValue";
messageAndSuggestions.suggest = [{
messageId: "prependVoid",
fix(fixer) {
return voidPrependFixer(sourceCode, node.argument, fixer);
}
}];
} else {
messageAndSuggestions.messageId = "expectedNoReturnValue";
}
}

@@ -288,14 +437,15 @@ } else {

if (!options.allowImplicit && !node.argument) {
messageId = "expectedReturnValue";
messageAndSuggestions.messageId = "expectedReturnValue";
}
}
if (messageId) {
if (messageAndSuggestions.messageId) {
context.report({
node,
messageId,
messageId: messageAndSuggestions.messageId,
data: {
name: astUtils.getFunctionNameWithKind(funcInfo.node),
arrayMethodName: fullMethodName(funcInfo.arrayMethodName)
}
},
suggest: messageAndSuggestions.suggest.length !== 0 ? messageAndSuggestions.suggest : null
});

@@ -302,0 +452,0 @@ }

@@ -19,8 +19,15 @@ /**

/**
* Checks whether or not a given code path segment is unreachable.
* @param {CodePathSegment} segment A CodePathSegment to check.
* @returns {boolean} `true` if the segment is unreachable.
* Checks all segments in a set and returns true if all are unreachable.
* @param {Set<CodePathSegment>} segments The segments to check.
* @returns {boolean} True if all segments are unreachable; false otherwise.
*/
function isUnreachable(segment) {
return !segment.reachable;
function areAllSegmentsUnreachable(segments) {
for (const segment of segments) {
if (segment.reachable) {
return false;
}
}
return true;
}

@@ -92,3 +99,3 @@

if (!funcInfo.hasReturnValue ||
funcInfo.codePath.currentSegments.every(isUnreachable) ||
areAllSegmentsUnreachable(funcInfo.currentSegments) ||
astUtils.isES5Constructor(node) ||

@@ -146,3 +153,4 @@ isClassConstructor(node)

messageId: "",
node
node,
currentSegments: new Set()
};

@@ -154,2 +162,19 @@ },

onUnreachableCodePathSegmentStart(segment) {
funcInfo.currentSegments.add(segment);
},
onUnreachableCodePathSegmentEnd(segment) {
funcInfo.currentSegments.delete(segment);
},
onCodePathSegmentStart(segment) {
funcInfo.currentSegments.add(segment);
},
onCodePathSegmentEnd(segment) {
funcInfo.currentSegments.delete(segment);
},
// Reports a given return statement if it's inconsistent.

@@ -156,0 +181,0 @@ ReturnStatement(node) {

@@ -13,8 +13,15 @@ /**

/**
* Checks whether a given code path segment is reachable or not.
* @param {CodePathSegment} segment A code path segment to check.
* @returns {boolean} `true` if the segment is reachable.
* Checks all segments in a set and returns true if any are reachable.
* @param {Set<CodePathSegment>} segments The segments to check.
* @returns {boolean} True if any segment is reachable; false otherwise.
*/
function isReachable(segment) {
return segment.reachable;
function isAnySegmentReachable(segments) {
for (const segment of segments) {
if (segment.reachable) {
return true;
}
}
return false;
}

@@ -214,3 +221,4 @@

superIsConstructor: isPossibleConstructor(superClass),
codePath
codePath,
currentSegments: new Set()
};

@@ -223,3 +231,4 @@ } else {

superIsConstructor: false,
codePath
codePath,
currentSegments: new Set()
};

@@ -267,2 +276,5 @@ }

onCodePathSegmentStart(segment) {
funcInfo.currentSegments.add(segment);
if (!(funcInfo && funcInfo.isConstructor && funcInfo.hasExtends)) {

@@ -288,2 +300,15 @@ return;

onUnreachableCodePathSegmentStart(segment) {
funcInfo.currentSegments.add(segment);
},
onUnreachableCodePathSegmentEnd(segment) {
funcInfo.currentSegments.delete(segment);
},
onCodePathSegmentEnd(segment) {
funcInfo.currentSegments.delete(segment);
},
/**

@@ -352,8 +377,7 @@ * Update information of the code path segment when a code path was

if (funcInfo.hasExtends) {
const segments = funcInfo.codePath.currentSegments;
const segments = funcInfo.currentSegments;
let duplicate = false;
let info = null;
for (let i = 0; i < segments.length; ++i) {
const segment = segments[i];
for (const segment of segments) {

@@ -383,3 +407,3 @@ if (segment.reachable) {

}
} else if (funcInfo.codePath.currentSegments.some(isReachable)) {
} else if (isAnySegmentReachable(funcInfo.currentSegments)) {
context.report({

@@ -408,6 +432,5 @@ messageId: "unexpected",

// Returning argument is a substitute of 'super()'.
const segments = funcInfo.codePath.currentSegments;
const segments = funcInfo.currentSegments;
for (let i = 0; i < segments.length; ++i) {
const segment = segments[i];
for (const segment of segments) {

@@ -414,0 +437,0 @@ if (segment.reachable) {

@@ -9,2 +9,8 @@ /**

//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const { getStaticValue } = require("@eslint-community/eslint-utils");
//------------------------------------------------------------------------------
// Rule Definition

@@ -33,2 +39,3 @@ //------------------------------------------------------------------------------

create(context) {
const { sourceCode } = context;

@@ -51,13 +58,13 @@ /**

* @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;
}

@@ -64,0 +71,0 @@

@@ -17,11 +17,19 @@ /**

//------------------------------------------------------------------------------
const TARGET_NODE_TYPE = /^(?:Arrow)?FunctionExpression$/u;
/**
* Checks a given code path segment is reachable.
* @param {CodePathSegment} segment A segment to check.
* @returns {boolean} `true` if the segment is reachable.
* Checks all segments in a set and returns true if any are reachable.
* @param {Set<CodePathSegment>} segments The segments to check.
* @returns {boolean} True if any segment is reachable; false otherwise.
*/
function isReachable(segment) {
return segment.reachable;
function isAnySegmentReachable(segments) {
for (const segment of segments) {
if (segment.reachable) {
return true;
}
}
return false;
}

@@ -75,3 +83,4 @@

shouldCheck: false,
node: null
node: null,
currentSegments: []
};

@@ -90,3 +99,3 @@

if (funcInfo.shouldCheck &&
funcInfo.codePath.currentSegments.some(isReachable)
isAnySegmentReachable(funcInfo.currentSegments)
) {

@@ -150,3 +159,4 @@ context.report({

shouldCheck: isGetter(node),
node
node,
currentSegments: new Set()
};

@@ -159,3 +169,18 @@ },

},
onUnreachableCodePathSegmentStart(segment) {
funcInfo.currentSegments.add(segment);
},
onUnreachableCodePathSegmentEnd(segment) {
funcInfo.currentSegments.delete(segment);
},
onCodePathSegmentStart(segment) {
funcInfo.currentSegments.add(segment);
},
onCodePathSegmentEnd(segment) {
funcInfo.currentSegments.delete(segment);
},
// Checks the return statement is valid.

@@ -162,0 +187,0 @@ ReturnStatement(node) {

@@ -178,2 +178,3 @@ /**

"no-obj-calls": () => require("./no-obj-calls"),
"no-object-constructor": () => require("./no-object-constructor"),
"no-octal": () => require("./no-octal"),

@@ -180,0 +181,0 @@ "no-octal-escape": () => require("./no-octal-escape"),

@@ -14,2 +14,17 @@ /**

//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
/**
* Types of class members.
* Those have `test` method to check it matches to the given class member.
* @private
*/
const ClassMemberTypes = {
"*": { test: () => true },
field: { test: node => node.type === "PropertyDefinition" },
method: { test: node => node.type === "MethodDefinition" }
};
//------------------------------------------------------------------------------
// Rule Definition

@@ -33,3 +48,28 @@ //------------------------------------------------------------------------------

{
enum: ["always", "never"]
anyOf: [
{
type: "object",
properties: {
enforce: {
type: "array",
items: {
type: "object",
properties: {
blankLine: { enum: ["always", "never"] },
prev: { enum: ["method", "field", "*"] },
next: { enum: ["method", "field", "*"] }
},
additionalProperties: false,
required: ["blankLine", "prev", "next"]
},
minItems: 1
}
},
additionalProperties: false,
required: ["enforce"]
},
{
enum: ["always", "never"]
}
]
},

@@ -60,2 +100,3 @@ {

const configureList = typeof options[0] === "object" ? options[0].enforce : [{ blankLine: options[0], prev: "*", next: "*" }];
const sourceCode = context.sourceCode;

@@ -150,2 +191,34 @@

/**
* Checks whether the given node matches the given type.
* @param {ASTNode} node The class member node to check.
* @param {string} type The class member type to check.
* @returns {boolean} `true` if the class member node matched the type.
* @private
*/
function match(node, type) {
return ClassMemberTypes[type].test(node);
}
/**
* Finds the last matched configuration from the configureList.
* @param {ASTNode} prevNode The previous node to match.
* @param {ASTNode} nextNode The current node to match.
* @returns {string|null} Padding type or `null` if no matches were found.
* @private
*/
function getPaddingType(prevNode, nextNode) {
for (let i = configureList.length - 1; i >= 0; --i) {
const configure = configureList[i];
const matched =
match(prevNode, configure.prev) &&
match(nextNode, configure.next);
if (matched) {
return configure.blankLine;
}
}
return null;
}
return {

@@ -165,8 +238,9 @@ ClassBody(node) {

const curLineLastToken = findLastConsecutiveTokenAfter(curLast, nextFirst, 0);
const paddingType = getPaddingType(body[i], body[i + 1]);
if ((options[0] === "always" && !skip && !isPadded) ||
(options[0] === "never" && isPadded)) {
if (paddingType === "never" && isPadded) {
context.report({
node: body[i + 1],
messageId: isPadded ? "never" : "always",
messageId: "never",
fix(fixer) {

@@ -176,8 +250,19 @@ if (hasTokenInPadding) {

}
return isPadded
? fixer.replaceTextRange([beforePadding.range[1], afterPadding.range[0]], "\n")
: fixer.insertTextAfter(curLineLastToken, "\n");
return fixer.replaceTextRange([beforePadding.range[1], afterPadding.range[0]], "\n");
}
});
} else if (paddingType === "always" && !skip && !isPadded) {
context.report({
node: body[i + 1],
messageId: "always",
fix(fixer) {
if (hasTokenInPadding) {
return null;
}
return fixer.insertTextAfter(curLineLastToken, "\n");
}
});
}
}

@@ -184,0 +269,0 @@ }

@@ -17,2 +17,12 @@ /**

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 = [];

@@ -36,6 +46,9 @@ }

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 {

@@ -42,0 +55,0 @@

@@ -9,15 +9,13 @@ /**

//------------------------------------------------------------------------------
// 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;

@@ -49,5 +47,28 @@ //------------------------------------------------------------------------------

"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" });
}
}
});
}

@@ -54,0 +75,0 @@ };

@@ -7,2 +7,4 @@ /**

const astUtils = require("./utils/ast-utils");
//------------------------------------------------------------------------------

@@ -23,3 +25,14 @@ // Rule Definition

schema: [],
schema: [
{
type: "object",
properties: {
allowObjectPatternsAsParameters: {
type: "boolean",
default: false
}
},
additionalProperties: false
}
],

@@ -32,7 +45,29 @@ messages: {

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" } });
},

@@ -39,0 +74,0 @@ ArrayPattern(node) {

@@ -20,2 +20,18 @@ /**

/**
* Checks all segments in a set and returns true if any are reachable.
* @param {Set<CodePathSegment>} segments The segments to check.
* @returns {boolean} True if any segment is reachable; false otherwise.
*/
function isAnySegmentReachable(segments) {
for (const segment of segments) {
if (segment.reachable) {
return true;
}
}
return false;
}
/**
* Checks whether or not a given comment string is really a fallthrough comment and not an ESLint directive.

@@ -56,11 +72,2 @@ * @param {string} comment The comment string to check.

/**
* Checks whether or not a given code path segment is reachable.
* @param {CodePathSegment} segment A CodePathSegment to check.
* @returns {boolean} `true` if the segment is reachable.
*/
function isReachable(segment) {
return segment.reachable;
}
/**
* Checks whether a node and a token are separated by blank lines

@@ -114,3 +121,4 @@ * @param {ASTNode} node The node to check

const options = context.options[0] || {};
let currentCodePath = null;
const codePathSegments = [];
let currentCodePathSegments = new Set();
const sourceCode = context.sourceCode;

@@ -132,9 +140,29 @@ const allowEmptyCase = options.allowEmptyCase || false;

return {
onCodePathStart(codePath) {
currentCodePath = codePath;
onCodePathStart() {
codePathSegments.push(currentCodePathSegments);
currentCodePathSegments = new Set();
},
onCodePathEnd() {
currentCodePath = currentCodePath.upper;
currentCodePathSegments = codePathSegments.pop();
},
onUnreachableCodePathSegmentStart(segment) {
currentCodePathSegments.add(segment);
},
onUnreachableCodePathSegmentEnd(segment) {
currentCodePathSegments.delete(segment);
},
onCodePathSegmentStart(segment) {
currentCodePathSegments.add(segment);
},
onCodePathSegmentEnd(segment) {
currentCodePathSegments.delete(segment);
},
SwitchCase(node) {

@@ -164,3 +192,3 @@

*/
if (currentCodePath.currentSegments.some(isReachable) &&
if (isAnySegmentReachable(currentCodePathSegments) &&
(node.consequent.length > 0 || (!allowEmptyCase && hasBlankLinesBetween(node, nextToken))) &&

@@ -167,0 +195,0 @@ node.parent.cases[node.parent.cases.length - 1] !== node) {

@@ -13,3 +13,3 @@ /**

const validator = new RegExpValidator();
const validFlags = /[dgimsuy]/gu;
const validFlags = /[dgimsuvy]/gu;
const undefined1 = void 0;

@@ -112,8 +112,10 @@

* @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;

@@ -136,6 +138,15 @@ } catch (err) {

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;
}

@@ -172,4 +183,8 @@

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") })
);

@@ -176,0 +191,0 @@

@@ -189,3 +189,3 @@ /**

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);

@@ -192,0 +192,0 @@ if (unsafeRefs.length > 0) {

@@ -17,2 +17,7 @@ /**

/**
* @typedef {import('@eslint-community/regexpp').AST.Character} Character
* @typedef {import('@eslint-community/regexpp').AST.CharacterClassElement} CharacterClassElement
*/
/**
* Iterate character sequences of a given nodes.

@@ -22,6 +27,8 @@ *

* so this function reverts CharacterClassRange syntax and restore the sequence.
* @param {regexpp.AST.CharacterClassElement[]} nodes The node list to iterate character sequences.
* @returns {IterableIterator<number[]>} The list of character sequences.
* @param {CharacterClassElement[]} nodes The node list to iterate character sequences.
* @returns {IterableIterator<Character[]>} The list of character sequences.
*/
function *iterateCharacterSequence(nodes) {
/** @type {Character[]} */
let seq = [];

@@ -32,12 +39,15 @@

case "Character":
seq.push(node.value);
seq.push(node);
break;
case "CharacterClassRange":
seq.push(node.min.value);
seq.push(node.min);
yield seq;
seq = [node.max.value];
seq = [node.max];
break;
case "CharacterSet":
case "CharacterClass": // [[]] nesting character class
case "ClassStringDisjunction": // \q{...}
case "ExpressionCharacterClass": // [A--B]
if (seq.length > 0) {

@@ -58,12 +68,54 @@ yield seq;

/**
* Checks whether the given character node is a Unicode code point escape or not.
* @param {Character} char the character node to check.
* @returns {boolean} `true` if the character node is a Unicode code point escape.
*/
function isUnicodeCodePointEscape(char) {
return /^\\u\{[\da-f]+\}$/iu.test(char.raw);
}
/**
* Each function returns `true` if it detects that kind of problem.
* @type {Record<string, (chars: Character[]) => boolean>}
*/
const hasCharacterSequence = {
surrogatePairWithoutUFlag(chars) {
return chars.some((c, i) => i !== 0 && isSurrogatePair(chars[i - 1], c));
return chars.some((c, i) => {
if (i === 0) {
return false;
}
const c1 = chars[i - 1];
return (
isSurrogatePair(c1.value, c.value) &&
!isUnicodeCodePointEscape(c1) &&
!isUnicodeCodePointEscape(c)
);
});
},
surrogatePair(chars) {
return chars.some((c, i) => {
if (i === 0) {
return false;
}
const c1 = chars[i - 1];
return (
isSurrogatePair(c1.value, c.value) &&
(
isUnicodeCodePointEscape(c1) ||
isUnicodeCodePointEscape(c)
)
);
});
},
combiningClass(chars) {
return chars.some((c, i) => (
i !== 0 &&
isCombiningCharacter(c) &&
!isCombiningCharacter(chars[i - 1])
isCombiningCharacter(c.value) &&
!isCombiningCharacter(chars[i - 1].value)
));

@@ -75,4 +127,4 @@ },

i !== 0 &&
isEmojiModifier(c) &&
!isEmojiModifier(chars[i - 1])
isEmojiModifier(c.value) &&
!isEmojiModifier(chars[i - 1].value)
));

@@ -84,4 +136,4 @@ },

i !== 0 &&
isRegionalIndicatorSymbol(c) &&
isRegionalIndicatorSymbol(chars[i - 1])
isRegionalIndicatorSymbol(c.value) &&
isRegionalIndicatorSymbol(chars[i - 1].value)
));

@@ -96,5 +148,5 @@ },

i !== lastIndex &&
c === 0x200d &&
chars[i - 1] !== 0x200d &&
chars[i + 1] !== 0x200d
c.value === 0x200d &&
chars[i - 1].value !== 0x200d &&
chars[i + 1].value !== 0x200d
));

@@ -127,2 +179,3 @@ }

surrogatePairWithoutUFlag: "Unexpected surrogate pair in character class. Use 'u' flag.",
surrogatePair: "Unexpected surrogate pair in character class.",
combiningClass: "Unexpected combined character in character class.",

@@ -155,3 +208,6 @@ emojiModifier: "Unexpected modified Emoji in character class.",

pattern.length,
flags.includes("u")
{
unicode: flags.includes("u"),
unicodeSets: flags.includes("v")
}
);

@@ -158,0 +214,0 @@ } catch {

/**
* @fileoverview A rule to disallow calls to the Object constructor
* @author Matt DuVall <http://www.mattduvall.com/>
* @deprecated in ESLint v8.50.0
*/

@@ -29,2 +30,8 @@

deprecated: true,
replacedBy: [
"no-object-constructor"
],
schema: [],

@@ -31,0 +38,0 @@

@@ -9,2 +9,8 @@ /**

//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const { getVariableByName } = require("./utils/ast-utils");
//------------------------------------------------------------------------------
// Rule Definition

@@ -32,2 +38,3 @@ //------------------------------------------------------------------------------

create(context) {
const { sourceCode } = context;

@@ -38,9 +45,14 @@ return {

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 }
});
}
}

@@ -47,0 +59,0 @@ }

@@ -13,2 +13,3 @@ /**

const { findVariable } = require("@eslint-community/eslint-utils");
const astUtils = require("./utils/ast-utils");

@@ -63,2 +64,74 @@ //------------------------------------------------------------------------------

/**
* 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, "}")
];
}
//------------------------------------------------------------------------------

@@ -79,6 +152,23 @@ // Rule Definition

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 `{}`."
}

@@ -91,12 +181,6 @@ },

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 {

@@ -107,7 +191,39 @@

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
});
}

@@ -121,5 +237,27 @@ },

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);
}
}]
});
}

@@ -126,0 +264,0 @@ };

@@ -80,3 +80,3 @@ /**

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 {

@@ -159,3 +159,2 @@

const patternNode = node.arguments[0];
const flagsNode = node.arguments[1];

@@ -166,4 +165,20 @@ if (node.callee.type === "Identifier" && node.callee.name === "RegExp" && isString(patternNode) && !shadowed) {

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(

@@ -170,0 +185,0 @@ node,

/**
* @fileoverview Disallows unnecessary `return await`
* @author Jordan Harband
* @deprecated in ESLint v8.46.0
*/

@@ -29,2 +30,6 @@ "use strict";

deprecated: true,
replacedBy: [],
schema: [

@@ -31,0 +36,0 @@ ],

@@ -94,2 +94,17 @@ /**

/**
* Determines if every segment in a set has been called.
* @param {Set<CodePathSegment>} segments The segments to search.
* @returns {boolean} True if every segment has been called; false otherwise.
*/
function isEverySegmentCalled(segments) {
for (const segment of segments) {
if (!isCalled(segment)) {
return false;
}
}
return true;
}
/**
* Checks whether or not this is before `super()` is called.

@@ -101,3 +116,3 @@ * @returns {boolean} `true` if this is before `super()` is called.

isInConstructorOfDerivedClass() &&
!funcInfo.codePath.currentSegments.every(isCalled)
!isEverySegmentCalled(funcInfo.currentSegments)
);

@@ -113,7 +128,5 @@ }

function setInvalid(node) {
const segments = funcInfo.codePath.currentSegments;
const segments = funcInfo.currentSegments;
for (let i = 0; i < segments.length; ++i) {
const segment = segments[i];
for (const segment of segments) {
if (segment.reachable) {

@@ -130,7 +143,5 @@ segInfoMap[segment.id].invalidNodes.push(node);

function setSuperCalled() {
const segments = funcInfo.codePath.currentSegments;
const segments = funcInfo.currentSegments;
for (let i = 0; i < segments.length; ++i) {
const segment = segments[i];
for (const segment of segments) {
if (segment.reachable) {

@@ -163,3 +174,4 @@ segInfoMap[segment.id].superCalled = true;

),
codePath
codePath,
currentSegments: new Set()
};

@@ -171,3 +183,4 @@ } else {

hasExtends: false,
codePath
codePath,
currentSegments: new Set()
};

@@ -220,2 +233,4 @@ }

onCodePathSegmentStart(segment) {
funcInfo.currentSegments.add(segment);
if (!isInConstructorOfDerivedClass()) {

@@ -235,2 +250,14 @@ return;

onUnreachableCodePathSegmentStart(segment) {
funcInfo.currentSegments.add(segment);
},
onUnreachableCodePathSegmentEnd(segment) {
funcInfo.currentSegments.delete(segment);
},
onCodePathSegmentEnd(segment) {
funcInfo.currentSegments.delete(segment);
},
/**

@@ -237,0 +264,0 @@ * Update information of the code path segment when a code path was

@@ -15,2 +15,18 @@ /**

/**
* Checks all segments in a set and returns true if any are reachable.
* @param {Set<CodePathSegment>} segments The segments to check.
* @returns {boolean} True if any segment is reachable; false otherwise.
*/
function isAnySegmentReachable(segments) {
for (const segment of segments) {
if (segment.reachable) {
return true;
}
}
return false;
}
/**
* Determines whether the given node is the first node in the code path to which a loop statement

@@ -94,25 +110,32 @@ * 'loops' for the next iteration.

let currentCodePath = null;
const codePathSegments = [];
let currentCodePathSegments = new Set();
return {
onCodePathStart(codePath) {
currentCodePath = codePath;
onCodePathStart() {
codePathSegments.push(currentCodePathSegments);
currentCodePathSegments = new Set();
},
onCodePathEnd() {
currentCodePath = currentCodePath.upper;
currentCodePathSegments = codePathSegments.pop();
},
[loopSelector](node) {
onUnreachableCodePathSegmentStart(segment) {
currentCodePathSegments.add(segment);
},
/**
* Ignore unreachable loop statements to avoid unnecessary complexity in the implementation, or false positives otherwise.
* For unreachable segments, the code path analysis does not raise events required for this implementation.
*/
if (currentCodePath.currentSegments.some(segment => segment.reachable)) {
loopsToReport.add(node);
}
onUnreachableCodePathSegmentEnd(segment) {
currentCodePathSegments.delete(segment);
},
onCodePathSegmentEnd(segment) {
currentCodePathSegments.delete(segment);
},
onCodePathSegmentStart(segment, node) {
currentCodePathSegments.add(segment);
if (isLoopingTarget(node)) {

@@ -145,2 +168,14 @@ const loop = node.parent;

[loopSelector](node) {
/**
* Ignore unreachable loop statements to avoid unnecessary complexity in the implementation, or false positives otherwise.
* For unreachable segments, the code path analysis does not raise events required for this implementation.
*/
if (isAnySegmentReachable(currentCodePathSegments)) {
loopsToReport.add(node);
}
},
"Program:exit"() {

@@ -147,0 +182,0 @@ loopsToReport.forEach(

@@ -27,8 +27,15 @@ /**

/**
* Checks whether or not a given code path segment is unreachable.
* @param {CodePathSegment} segment A CodePathSegment to check.
* @returns {boolean} `true` if the segment is unreachable.
* Checks all segments in a set and returns true if all are unreachable.
* @param {Set<CodePathSegment>} segments The segments to check.
* @returns {boolean} True if all segments are unreachable; false otherwise.
*/
function isUnreachable(segment) {
return !segment.reachable;
function areAllSegmentsUnreachable(segments) {
for (const segment of segments) {
if (segment.reachable) {
return false;
}
}
return true;
}

@@ -128,3 +135,2 @@

create(context) {
let currentCodePath = null;

@@ -137,2 +143,8 @@ /** @type {ConstructorInfo | null} */

/** @type {Array<Set<CodePathSegment>>} */
const codePathSegments = [];
/** @type {Set<CodePathSegment>} */
let currentCodePathSegments = new Set();
/**

@@ -146,3 +158,3 @@ * Reports a given node if it's unreachable.

if (node && (node.type === "PropertyDefinition" || currentCodePath.currentSegments.every(isUnreachable))) {
if (node && (node.type === "PropertyDefinition" || areAllSegmentsUnreachable(currentCodePathSegments))) {

@@ -188,10 +200,27 @@ // Store this statement to distinguish consecutive statements.

// Manages the current code path.
onCodePathStart(codePath) {
currentCodePath = codePath;
onCodePathStart() {
codePathSegments.push(currentCodePathSegments);
currentCodePathSegments = new Set();
},
onCodePathEnd() {
currentCodePath = currentCodePath.upper;
currentCodePathSegments = codePathSegments.pop();
},
onUnreachableCodePathSegmentStart(segment) {
currentCodePathSegments.add(segment);
},
onUnreachableCodePathSegmentEnd(segment) {
currentCodePathSegments.delete(segment);
},
onCodePathSegmentEnd(segment) {
currentCodePathSegments.delete(segment);
},
onCodePathSegmentStart(segment) {
currentCodePathSegments.add(segment);
},
// Registers for all statement nodes (excludes FunctionDeclaration).

@@ -198,0 +227,0 @@ BlockStatement: reportIfUnreachable,

@@ -98,3 +98,3 @@ /**

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 {

@@ -101,0 +101,0 @@

@@ -9,3 +9,8 @@ /**

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
*/
//------------------------------------------------------------------------------

@@ -32,52 +37,14 @@ // Rule Definition

/**
* 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} */

@@ -99,2 +66,3 @@ module.exports = {

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."

@@ -108,2 +76,3 @@ },

const sourceCode = context.sourceCode;
const parser = new RegExpParser();

@@ -115,5 +84,6 @@ /**

* @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;

@@ -133,3 +103,6 @@ const range = [rangeStart, rangeStart + 1];

{
messageId: "removeEscape",
// Removing unnecessary `\` characters in a directive is not guaranteed to maintain functionality.
messageId: astUtils.isDirective(node.parent)
? "removeEscapeDoNotKeepSemantics" : "removeEscape",
fix(fixer) {

@@ -139,8 +112,12 @@ return fixer.removeRange(range);

},
{
messageId: "escapeBackslash",
fix(fixer) {
return fixer.insertTextBeforeRange(range, "\\");
}
}
...disableEscapeBackslashSuggest
? []
: [
{
messageId: "escapeBackslash",
fix(fixer) {
return fixer.insertTextBeforeRange(range, "\\");
}
}
]
]

@@ -189,2 +166,129 @@ });

/**
* 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.

@@ -227,28 +331,3 @@ * @param {ASTNode} node node to check.

} 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);
}

@@ -255,0 +334,0 @@

@@ -60,2 +60,18 @@ /**

/**
* Checks all segments in a set and returns true if any are reachable.
* @param {Set<CodePathSegment>} segments The segments to check.
* @returns {boolean} True if any segment is reachable; false otherwise.
*/
function isAnySegmentReachable(segments) {
for (const segment of segments) {
if (segment.reachable) {
return true;
}
}
return false;
}
//------------------------------------------------------------------------------

@@ -209,3 +225,2 @@ // Rule Definition

scopeInfo
.codePath
.currentSegments

@@ -227,3 +242,4 @@ .forEach(segment => markReturnStatementsOnSegmentAsUsed(segment, new Set()));

traversedTryBlockStatements: [],
codePath
codePath,
currentSegments: new Set()
};

@@ -265,2 +281,5 @@ },

onCodePathSegmentStart(segment) {
scopeInfo.currentSegments.add(segment);
const info = {

@@ -275,2 +294,14 @@ uselessReturns: getUselessReturns([], segment.allPrevSegments),

onUnreachableCodePathSegmentStart(segment) {
scopeInfo.currentSegments.add(segment);
},
onUnreachableCodePathSegmentEnd(segment) {
scopeInfo.currentSegments.delete(segment);
},
onCodePathSegmentEnd(segment) {
scopeInfo.currentSegments.delete(segment);
},
// Adds ReturnStatement node to check whether it's useless or not.

@@ -287,3 +318,3 @@ ReturnStatement(node) {

// Ignore `return` statements in unreachable places (https://github.com/eslint/eslint/issues/11647).
!scopeInfo.codePath.currentSegments.some(s => s.reachable)
!isAnySegmentReachable(scopeInfo.currentSegments)
) {

@@ -293,3 +324,3 @@ return;

for (const segment of scopeInfo.codePath.currentSegments) {
for (const segment of scopeInfo.currentSegments) {
const info = segmentInfoMap.get(segment);

@@ -296,0 +327,0 @@

@@ -134,38 +134,2 @@ /**

/**
* 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.

@@ -363,8 +327,6 @@ *

directive: {
test: isDirectivePrologue
test: astUtils.isDirective
},
expression: {
test: (node, sourceCode) =>
node.type === "ExpressionStatement" &&
!isDirectivePrologue(node, sourceCode)
test: node => node.type === "ExpressionStatement" && !astUtils.isDirective(node)
},

@@ -380,6 +342,6 @@ iife: {

"multiline-expression": {
test: (node, sourceCode) =>
test: node =>
node.loc.start.line !== node.loc.end.line &&
node.type === "ExpressionStatement" &&
!isDirectivePrologue(node, sourceCode)
!astUtils.isDirective(node)
},

@@ -386,0 +348,0 @@

@@ -115,10 +115,13 @@ /**

* @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 {

@@ -152,3 +155,3 @@

if (node.regex) {
checkRegex(node.regex.pattern, node, node, node.regex.flags.includes("u"));
checkRegex(node.regex.pattern, node, node, node.regex.flags);
}

@@ -171,3 +174,3 @@ },

if (regex) {
checkRegex(regex, refNode, refNode.arguments[0], flags && flags.includes("u"));
checkRegex(regex, refNode, refNode.arguments[0], flags);
}

@@ -174,0 +177,0 @@ }

@@ -244,3 +244,3 @@ /**

* @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.
*/

@@ -301,3 +301,6 @@ function getRegexppEcmaVersion(ecmaVersion) {

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) {

@@ -466,3 +469,6 @@ validator.validateFlags(flags);

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
});

@@ -469,0 +475,0 @@ visitRegExpAST(ast, {

@@ -216,3 +216,4 @@ /**

codePath,
referenceMap: shouldVerify ? createReferenceMap(scope) : null
referenceMap: shouldVerify ? createReferenceMap(scope) : null,
currentSegments: new Set()
};

@@ -227,7 +228,21 @@ },

segmentInfo.initialize(segment);
stack.currentSegments.add(segment);
},
onUnreachableCodePathSegmentStart(segment) {
stack.currentSegments.add(segment);
},
onUnreachableCodePathSegmentEnd(segment) {
stack.currentSegments.delete(segment);
},
onCodePathSegmentEnd(segment) {
stack.currentSegments.delete(segment);
},
// Handle references to prepare verification.
Identifier(node) {
const { codePath, referenceMap } = stack;
const { referenceMap } = stack;
const reference = referenceMap && referenceMap.get(node);

@@ -245,3 +260,3 @@

if (reference.isRead() && !(writeExpr && writeExpr.parent.operator === "=")) {
segmentInfo.markAsRead(codePath.currentSegments, variable);
segmentInfo.markAsRead(stack.currentSegments, variable);
}

@@ -273,6 +288,5 @@

":expression:exit"(node) {
const { codePath, referenceMap } = stack;
// referenceMap exists if this is in a resumable function scope.
if (!referenceMap) {
if (!stack.referenceMap) {
return;

@@ -283,3 +297,3 @@ }

if (node.type === "AwaitExpression" || node.type === "YieldExpression") {
segmentInfo.makeOutdated(codePath.currentSegments);
segmentInfo.makeOutdated(stack.currentSegments);
}

@@ -296,3 +310,3 @@

if (segmentInfo.isOutdated(codePath.currentSegments, variable)) {
if (segmentInfo.isOutdated(stack.currentSegments, variable)) {
if (node.parent.left === reference.identifier) {

@@ -299,0 +313,0 @@ context.report({

@@ -31,3 +31,3 @@ /**

docs: {
description: "Enforce the use of `u` flag on RegExp",
description: "Enforce the use of `u` or `v` flag on RegExp",
recommended: false,

@@ -55,3 +55,3 @@ url: "https://eslint.org/docs/latest/rules/require-unicode-regexp"

if (!flags.includes("u")) {
if (!flags.includes("u") && !flags.includes("v")) {
context.report({

@@ -90,3 +90,3 @@ messageId: "requireUFlag",

if (!flagsNode || (typeof flags === "string" && !flags.includes("u"))) {
if (!flagsNode || (typeof flags === "string" && !flags.includes("u") && !flags.includes("v"))) {
context.report({

@@ -93,0 +93,0 @@ messageId: "requireUFlag",

@@ -11,3 +11,3 @@ /**

const REGEXPP_LATEST_ECMA_VERSION = 2022;
const REGEXPP_LATEST_ECMA_VERSION = 2024;

@@ -32,3 +32,3 @@ /**

try {
validator.validatePattern(pattern, void 0, void 0, /* uFlag = */ true);
validator.validatePattern(pattern, void 0, void 0, { unicode: /* uFlag = */ true });
} catch {

@@ -35,0 +35,0 @@ return false;

@@ -15,4 +15,12 @@ /**

astUtils = require("../shared/ast-utils"),
Traverser = require("../shared/traverser");
Traverser = require("../shared/traverser"),
globals = require("../../conf/globals"),
{
directivesPattern
} = require("../shared/directives"),
/* eslint-disable-next-line n/no-restricted-require -- Too messy to figure out right now. */
ConfigCommentParser = require("../linter/config-comment-parser"),
eslintScope = require("eslint-scope");
//------------------------------------------------------------------------------

@@ -28,2 +36,4 @@ // Type Definitions

const commentParser = new ConfigCommentParser();
/**

@@ -55,2 +65,25 @@ * Validates that the given AST has the required information.

/**
* Retrieves globals for the given ecmaVersion.
* @param {number} ecmaVersion The version to retrieve globals for.
* @returns {Object} The globals for the given ecmaVersion.
*/
function getGlobalsForEcmaVersion(ecmaVersion) {
switch (ecmaVersion) {
case 3:
return globals.es3;
case 5:
return globals.es5;
default:
if (ecmaVersion < 2015) {
return globals[`es${ecmaVersion + 2009}`];
}
return globals[`es${ecmaVersion}`];
}
}
/**
* Check to see if its a ES6 export declaration.

@@ -90,2 +123,32 @@ * @param {ASTNode} astNode An AST node.

/**
* Normalizes a value for a global in a config
* @param {(boolean|string|null)} configuredValue The value given for a global in configuration or in
* a global directive comment
* @returns {("readable"|"writeable"|"off")} The value normalized as a string
* @throws Error if global value is invalid
*/
function normalizeConfigGlobal(configuredValue) {
switch (configuredValue) {
case "off":
return "off";
case true:
case "true":
case "writeable":
case "writable":
return "writable";
case null:
case false:
case "false":
case "readable":
case "readonly":
return "readonly";
default:
throw new Error(`'${configuredValue}' is not a valid configuration for a global (use 'readonly', 'writable', or 'off')`);
}
}
/**
* Determines if two nodes or tokens overlap.

@@ -152,2 +215,112 @@ * @param {ASTNode|Token} first The first node or token to check.

//-----------------------------------------------------------------------------
// Directive Comments
//-----------------------------------------------------------------------------
/**
* Extract the directive and the justification from a given directive comment and trim them.
* @param {string} value The comment text to extract.
* @returns {{directivePart: string, justificationPart: string}} The extracted directive and justification.
*/
function extractDirectiveComment(value) {
const match = /\s-{2,}\s/u.exec(value);
if (!match) {
return { directivePart: value.trim(), justificationPart: "" };
}
const directive = value.slice(0, match.index).trim();
const justification = value.slice(match.index + match[0].length).trim();
return { directivePart: directive, justificationPart: justification };
}
/**
* Ensures that variables representing built-in properties of the Global Object,
* and any globals declared by special block comments, are present in the global
* scope.
* @param {Scope} globalScope The global scope.
* @param {Object|undefined} configGlobals The globals declared in configuration
* @param {Object|undefined} inlineGlobals The globals declared in the source code
* @returns {void}
*/
function addDeclaredGlobals(globalScope, configGlobals = {}, inlineGlobals = {}) {
// Define configured global variables.
for (const id of new Set([...Object.keys(configGlobals), ...Object.keys(inlineGlobals)])) {
/*
* `normalizeConfigGlobal` will throw an error if a configured global value is invalid. However, these errors would
* typically be caught when validating a config anyway (validity for inline global comments is checked separately).
*/
const configValue = configGlobals[id] === void 0 ? void 0 : normalizeConfigGlobal(configGlobals[id]);
const commentValue = inlineGlobals[id] && inlineGlobals[id].value;
const value = commentValue || configValue;
const sourceComments = inlineGlobals[id] && inlineGlobals[id].comments;
if (value === "off") {
continue;
}
let variable = globalScope.set.get(id);
if (!variable) {
variable = new eslintScope.Variable(id, globalScope);
globalScope.variables.push(variable);
globalScope.set.set(id, variable);
}
variable.eslintImplicitGlobalSetting = configValue;
variable.eslintExplicitGlobal = sourceComments !== void 0;
variable.eslintExplicitGlobalComments = sourceComments;
variable.writeable = (value === "writable");
}
/*
* "through" contains all references which definitions cannot be found.
* Since we augment the global scope using configuration, we need to update
* references and remove the ones that were added by configuration.
*/
globalScope.through = globalScope.through.filter(reference => {
const name = reference.identifier.name;
const variable = globalScope.set.get(name);
if (variable) {
/*
* Links the variable and the reference.
* And this reference is removed from `Scope#through`.
*/
reference.resolved = variable;
variable.references.push(reference);
return false;
}
return true;
});
}
/**
* Sets the given variable names as exported so they won't be triggered by
* the `no-unused-vars` rule.
* @param {eslint.Scope} globalScope The global scope to define exports in.
* @param {Record<string,string>} variables An object whose keys are the variable
* names to export.
* @returns {void}
*/
function markExportedVariables(globalScope, variables) {
Object.keys(variables).forEach(name => {
const variable = globalScope.set.get(name);
if (variable) {
variable.eslintUsed = true;
variable.eslintExported = true;
}
});
}
//------------------------------------------------------------------------------

@@ -195,3 +368,5 @@ // Public Interface

this[caches] = new Map([
["scopes", new WeakMap()]
["scopes", new WeakMap()],
["vars", new Map()],
["configNodes", void 0]
]);

@@ -275,3 +450,3 @@

// don't allow modification of this object
// don't allow further modification of this object
Object.freeze(this);

@@ -734,4 +909,176 @@ Object.freeze(this.lines);

/**
* Returns an array of all inline configuration nodes found in the
* source code.
* @returns {Array<Token>} An array of all inline configuration nodes.
*/
getInlineConfigNodes() {
// check the cache first
let configNodes = this[caches].get("configNodes");
if (configNodes) {
return configNodes;
}
// calculate fresh config nodes
configNodes = this.ast.comments.filter(comment => {
// shebang comments are never directives
if (comment.type === "Shebang") {
return false;
}
const { directivePart } = extractDirectiveComment(comment.value);
const directiveMatch = directivesPattern.exec(directivePart);
if (!directiveMatch) {
return false;
}
// only certain comment types are supported as line comments
return comment.type !== "Line" || !!/^eslint-disable-(next-)?line$/u.test(directiveMatch[1]);
});
this[caches].set("configNodes", configNodes);
return configNodes;
}
/**
* Applies language options sent in from the core.
* @param {Object} languageOptions The language options for this run.
* @returns {void}
*/
applyLanguageOptions(languageOptions) {
/*
* Add configured globals and language globals
*
* Using Object.assign instead of object spread for performance reasons
* https://github.com/eslint/eslint/issues/16302
*/
const configGlobals = Object.assign(
{},
getGlobalsForEcmaVersion(languageOptions.ecmaVersion),
languageOptions.sourceType === "commonjs" ? globals.commonjs : void 0,
languageOptions.globals
);
const varsCache = this[caches].get("vars");
varsCache.set("configGlobals", configGlobals);
}
/**
* Applies configuration found inside of the source code. This method is only
* called when ESLint is running with inline configuration allowed.
* @returns {{problems:Array<Problem>,configs:{config:FlatConfigArray,node:ASTNode}}} Information
* that ESLint needs to further process the inline configuration.
*/
applyInlineConfig() {
const problems = [];
const configs = [];
const exportedVariables = {};
const inlineGlobals = Object.create(null);
this.getInlineConfigNodes().forEach(comment => {
const { directivePart } = extractDirectiveComment(comment.value);
const match = directivesPattern.exec(directivePart);
const directiveText = match[1];
const directiveValue = directivePart.slice(match.index + directiveText.length);
switch (directiveText) {
case "exported":
Object.assign(exportedVariables, commentParser.parseStringConfig(directiveValue, comment));
break;
case "globals":
case "global":
for (const [id, { value }] of Object.entries(commentParser.parseStringConfig(directiveValue, comment))) {
let normalizedValue;
try {
normalizedValue = normalizeConfigGlobal(value);
} catch (err) {
problems.push({
ruleId: null,
loc: comment.loc,
message: err.message
});
continue;
}
if (inlineGlobals[id]) {
inlineGlobals[id].comments.push(comment);
inlineGlobals[id].value = normalizedValue;
} else {
inlineGlobals[id] = {
comments: [comment],
value: normalizedValue
};
}
}
break;
case "eslint": {
const parseResult = commentParser.parseJsonConfig(directiveValue, comment.loc);
if (parseResult.success) {
configs.push({
config: {
rules: parseResult.config
},
node: comment
});
} else {
problems.push(parseResult.error);
}
break;
}
// no default
}
});
// save all the new variables for later
const varsCache = this[caches].get("vars");
varsCache.set("inlineGlobals", inlineGlobals);
varsCache.set("exportedVariables", exportedVariables);
return {
configs,
problems
};
}
/**
* Called by ESLint core to indicate that it has finished providing
* information. We now add in all the missing variables and ensure that
* state-changing methods cannot be called by rules.
* @returns {void}
*/
finalize() {
// Step 1: ensure that all of the necessary variables are up to date
const varsCache = this[caches].get("vars");
const globalScope = this.scopeManager.scopes[0];
const configGlobals = varsCache.get("configGlobals");
const inlineGlobals = varsCache.get("inlineGlobals");
const exportedVariables = varsCache.get("exportedVariables");
addDeclaredGlobals(globalScope, configGlobals, inlineGlobals);
if (exportedVariables) {
markExportedVariables(globalScope, exportedVariables);
}
}
}
module.exports = SourceCode;

@@ -17,2 +17,3 @@ /**

const FlatRuleTester = require("./rule-tester/flat-rule-tester");
const { ESLint } = require("./eslint/eslint");

@@ -28,3 +29,4 @@ //-----------------------------------------------------------------------------

FlatRuleTester,
FileEnumerator
FileEnumerator,
LegacyESLint: ESLint
};
{
"name": "eslint",
"version": "8.44.0",
"version": "8.50.0",
"author": "Nicholas C. Zakas <nicholas+npm@nczconsulting.com>",

@@ -64,9 +64,9 @@ "description": "An AST-based pattern checker for JavaScript.",

"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.4.0",
"@eslint/eslintrc": "^2.1.0",
"@eslint/js": "8.44.0",
"@humanwhocodes/config-array": "^0.11.10",
"@eslint-community/regexpp": "^4.6.1",
"@eslint/eslintrc": "^2.1.2",
"@eslint/js": "8.50.0",
"@humanwhocodes/config-array": "^0.11.11",
"@humanwhocodes/module-importer": "^1.0.1",
"@nodelib/fs.walk": "^1.2.8",
"ajv": "^6.10.0",
"ajv": "^6.12.4",
"chalk": "^4.0.0",

@@ -77,5 +77,5 @@ "cross-spawn": "^7.0.2",

"escape-string-regexp": "^4.0.0",
"eslint-scope": "^7.2.0",
"eslint-visitor-keys": "^3.4.1",
"espree": "^9.6.0",
"eslint-scope": "^7.2.2",
"eslint-visitor-keys": "^3.4.3",
"espree": "^9.6.1",
"esquery": "^1.4.2",

@@ -90,3 +90,2 @@ "esutils": "^2.0.2",

"ignore": "^5.2.0",
"import-fresh": "^3.0.0",
"imurmurhash": "^0.1.4",

@@ -103,3 +102,2 @@ "is-glob": "^4.0.0",

"strip-ansi": "^6.0.1",
"strip-json-comments": "^3.1.0",
"text-table": "^0.2.0"

@@ -110,2 +108,7 @@ },

"@babel/preset-env": "^7.4.3",
"@wdio/browser-runner": "^8.14.6",
"@wdio/cli": "^8.14.6",
"@wdio/concise-reporter": "^8.14.0",
"@wdio/globals": "^8.14.6",
"@wdio/mocha-framework": "^8.14.0",
"babel-loader": "^8.0.5",

@@ -134,7 +137,2 @@ "c8": "^7.12.0",

"gray-matter": "^4.0.3",
"karma": "^6.1.1",
"karma-chrome-launcher": "^3.1.0",
"karma-mocha": "^2.0.1",
"karma-mocha-reporter": "^2.2.5",
"karma-webpack": "^5.0.0",
"lint-staged": "^11.0.0",

@@ -159,9 +157,10 @@ "load-perf": "^0.2.0",

"proxyquire": "^2.0.1",
"puppeteer": "^13.7.0",
"recast": "^0.20.4",
"regenerator-runtime": "^0.13.2",
"rollup-plugin-node-polyfills": "^0.2.1",
"semver": "^7.5.3",
"shelljs": "^0.8.2",
"sinon": "^11.0.0",
"temp": "^0.9.0",
"vite-plugin-commonjs": "^0.8.2",
"webdriverio": "^8.14.6",
"webpack": "^5.23.0",

@@ -168,0 +167,0 @@ "webpack-cli": "^4.5.0",

@@ -252,2 +252,7 @@ [![npm version](https://img.shields.io/npm/v/eslint.svg)](https://www.npmjs.com/package/eslint)

</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>

@@ -287,4 +292,4 @@

<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://opensource.mercedes-benz.com/"><img src="https://avatars.githubusercontent.com/u/34240465?v=4" alt="Mercedes-Benz Group" 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://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://opensource.siemens.com"><img src="https://avatars.githubusercontent.com/u/624020?v=4" alt="Siemens" 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" 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>
<!--sponsorsend-->

@@ -291,0 +296,0 @@

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc