Socket
Socket
Sign inDemoInstall

eslint

Package Overview
Dependencies
Maintainers
4
Versions
366
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.47.0 to 8.50.0

lib/rules/no-object-constructor.js

12

lib/config/flat-config-schema.js

@@ -510,3 +510,3 @@ /**

exports.flatConfigSchema = {
const flatConfigSchema = {

@@ -537,1 +537,11 @@ // eslintrc-style keys that should always error

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

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

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

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

@@ -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 {import('@eslint-community/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,9 +39,9 @@

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;

@@ -61,12 +68,54 @@

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

@@ -78,4 +127,4 @@ },

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

@@ -87,4 +136,4 @@ },

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

@@ -99,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
));

@@ -130,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.",

@@ -132,0 +182,0 @@ emojiModifier: "Unexpected modified Emoji in character class.",

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

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

@@ -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,

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

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

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

@@ -22,3 +22,3 @@ "use strict";

Please see the following page for more information:
https://eslint.org/docs/latest/use/configure/migration-guide#predefined-configs
https://eslint.org/docs/latest/use/configure/migration-guide#predefined-and-shareable-configs
`,

@@ -25,0 +25,0 @@

{
"name": "eslint",
"version": "8.47.0",
"version": "8.50.0",
"author": "Nicholas C. Zakas <nicholas+npm@nczconsulting.com>",

@@ -66,4 +66,4 @@ "description": "An AST-based pattern checker for JavaScript.",

"@eslint/eslintrc": "^2.1.2",
"@eslint/js": "^8.47.0",
"@humanwhocodes/config-array": "^0.11.10",
"@eslint/js": "8.50.0",
"@humanwhocodes/config-array": "^0.11.11",
"@humanwhocodes/module-importer": "^1.0.1",

@@ -105,2 +105,7 @@ "@nodelib/fs.walk": "^1.2.8",

"@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",

@@ -129,7 +134,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",

@@ -154,8 +154,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",
"vite-plugin-commonjs": "^0.8.2",
"webdriverio": "^8.14.6",
"webpack": "^5.23.0",

@@ -162,0 +164,0 @@ "webpack-cli": "^4.5.0",

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

<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://github.com/about"><img src="https://avatars.githubusercontent.com/u/9919?v=4" alt="GitHub" height="32"></a> <a href="https://transloadit.com/"><img src="https://avatars.githubusercontent.com/u/125754?v=4" alt="Transloadit" height="32"></a> <a href="https://www.ignitionapp.com"><img src="https://avatars.githubusercontent.com/u/5753491?v=4" alt="Ignition" height="32"></a> <a href="https://herocoders.com"><img src="https://avatars.githubusercontent.com/u/37549774?v=4" alt="HeroCoders" height="32"></a> <a href="https://quickbookstoolhub.com"><img src="https://avatars.githubusercontent.com/u/95090305?u=e5bc398ef775c9ed19f955c675cdc1fb6abf01df&v=4" alt="QuickBooks Tool hub" height="32"></a></p>
<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-->

@@ -295,0 +295,0 @@

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