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.53.0 to 8.55.0

24

lib/cli-engine/lint-result-cache.js

@@ -131,12 +131,24 @@ /**

const cachedResults = fileDescriptor.meta.results;
// Just in case, not sure if this can ever happen.
if (!cachedResults) {
return cachedResults;
}
/*
* Shallow clone the object to ensure that any properties added or modified afterwards
* will not be accidentally stored in the cache file when `reconcile()` is called.
* https://github.com/eslint/eslint/issues/13507
* All intentional changes to the cache file must be done through `setCachedLintResults()`.
*/
const results = { ...cachedResults };
// If source is present but null, need to reread the file from the filesystem.
if (
fileDescriptor.meta.results &&
fileDescriptor.meta.results.source === null
) {
if (results.source === null) {
debug(`Rereading cached result source from filesystem: ${filePath}`);
fileDescriptor.meta.results.source = fs.readFileSync(filePath, "utf-8");
results.source = fs.readFileSync(filePath, "utf-8");
}
return fileDescriptor.meta.results;
return results;
}

@@ -143,0 +155,0 @@

@@ -104,26 +104,33 @@ /**

}
return {
ForStatement(node) {
if (node.test && node.test.type === "BinaryExpression" && node.test.left.type === "Identifier" && node.update) {
const counter = node.test.left.name;
const operator = node.test.operator;
const update = node.update;
if (node.test && node.test.type === "BinaryExpression" && node.update) {
for (const counterPosition of ["left", "right"]) {
if (node.test[counterPosition].type !== "Identifier") {
continue;
}
let wrongDirection;
const counter = node.test[counterPosition].name;
const operator = node.test.operator;
const update = node.update;
if (operator === "<" || operator === "<=") {
wrongDirection = -1;
} else if (operator === ">" || operator === ">=") {
wrongDirection = 1;
} else {
return;
}
let wrongDirection;
if (update.type === "UpdateExpression") {
if (getUpdateDirection(update, counter) === wrongDirection) {
if (operator === "<" || operator === "<=") {
wrongDirection = counterPosition === "left" ? -1 : 1;
} else if (operator === ">" || operator === ">=") {
wrongDirection = counterPosition === "left" ? 1 : -1;
} else {
return;
}
if (update.type === "UpdateExpression") {
if (getUpdateDirection(update, counter) === wrongDirection) {
report(node);
}
} else if (update.type === "AssignmentExpression" && getAssignmentDirection(update, counter) === wrongDirection) {
report(node);
}
} else if (update.type === "AssignmentExpression" && getAssignmentDirection(update, counter) === wrongDirection) {
report(node);
}

@@ -130,0 +137,0 @@ }

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

//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const {
getVariableByName,
isClosingParenToken,
isOpeningParenToken,
isStartOfExpressionStatement,
needsPrecedingSemicolon
} = require("./utils/ast-utils");
//------------------------------------------------------------------------------
// Rule Definition

@@ -24,6 +36,10 @@ //------------------------------------------------------------------------------

hasSuggestions: true,
schema: [],
messages: {
preferLiteral: "The array literal notation [] is preferable."
preferLiteral: "The array literal notation [] is preferable.",
useLiteral: "Replace with an array literal.",
useLiteralAfterSemicolon: "Replace with an array literal, add preceding semicolon."
}

@@ -34,3 +50,29 @@ },

const sourceCode = context.sourceCode;
/**
* Gets the text between the calling parentheses of a CallExpression or NewExpression.
* @param {ASTNode} node A CallExpression or NewExpression node.
* @returns {string} The text between the calling parentheses, or an empty string if there are none.
*/
function getArgumentsText(node) {
const lastToken = sourceCode.getLastToken(node);
if (!isClosingParenToken(lastToken)) {
return "";
}
let firstToken = node.callee;
do {
firstToken = sourceCode.getTokenAfter(firstToken);
if (!firstToken || firstToken === lastToken) {
return "";
}
} while (!isOpeningParenToken(firstToken));
return sourceCode.text.slice(firstToken.range[1], lastToken.range[0]);
}
/**
* Disallow construction of dense arrays using the Array constructor

@@ -43,8 +85,45 @@ * @param {ASTNode} node node to evaluate

if (
node.arguments.length !== 1 &&
node.callee.type === "Identifier" &&
node.callee.name === "Array"
) {
context.report({ node, messageId: "preferLiteral" });
node.callee.type !== "Identifier" ||
node.callee.name !== "Array" ||
node.arguments.length === 1 &&
node.arguments[0].type !== "SpreadElement") {
return;
}
const variable = getVariableByName(sourceCode.getScope(node), "Array");
/*
* Check if `Array` is a predefined global variable: predefined globals have no declarations,
* meaning that the `identifiers` list of the variable object is empty.
*/
if (variable && variable.identifiers.length === 0) {
const argsText = getArgumentsText(node);
let fixText;
let messageId;
/*
* Check if the suggested change should include a preceding semicolon or not.
* Due to JavaScript's ASI rules, a missing semicolon may be inserted automatically
* before an expression like `Array()` or `new Array()`, but not when the expression
* is changed into an array literal like `[]`.
*/
if (isStartOfExpressionStatement(node) && needsPrecedingSemicolon(sourceCode, node)) {
fixText = `;[${argsText}]`;
messageId = "useLiteralAfterSemicolon";
} else {
fixText = `[${argsText}]`;
messageId = "useLiteral";
}
context.report({
node,
messageId: "preferLiteral",
suggest: [
{
messageId,
fix: fixer => fixer.replaceText(node, fixText)
}
]
});
}
}

@@ -51,0 +130,0 @@

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

hasSuggestions: true,
messages: {
unexpected: "Unexpected console statement."
unexpected: "Unexpected console statement.",
removeConsole: "Remove the console.{{ propertyName }}()."
}

@@ -99,2 +102,60 @@ },

/**
* Checks if removing the ExpressionStatement node will cause ASI to
* break.
* eg.
* foo()
* console.log();
* [1, 2, 3].forEach(a => doSomething(a))
*
* Removing the console.log(); statement should leave two statements, but
* here the two statements will become one because [ causes continuation after
* foo().
* @param {ASTNode} node The ExpressionStatement node to check.
* @returns {boolean} `true` if ASI will break after removing the ExpressionStatement
* node.
*/
function maybeAsiHazard(node) {
const SAFE_TOKENS_BEFORE = /^[:;{]$/u; // One of :;{
const UNSAFE_CHARS_AFTER = /^[-[(/+`]/u; // One of [(/+-`
const tokenBefore = sourceCode.getTokenBefore(node);
const tokenAfter = sourceCode.getTokenAfter(node);
return (
Boolean(tokenAfter) &&
UNSAFE_CHARS_AFTER.test(tokenAfter.value) &&
tokenAfter.value !== "++" &&
tokenAfter.value !== "--" &&
Boolean(tokenBefore) &&
!SAFE_TOKENS_BEFORE.test(tokenBefore.value)
);
}
/**
* Checks if the MemberExpression node's parent.parent.parent is a
* Program, BlockStatement, StaticBlock, or SwitchCase node. This check
* is necessary to avoid providing a suggestion that might cause a syntax error.
*
* eg. if (a) console.log(b), removing console.log() here will lead to a
* syntax error.
* if (a) { console.log(b) }, removing console.log() here is acceptable.
*
* Additionally, it checks if the callee of the CallExpression node is
* the node itself.
*
* eg. foo(console.log), cannot provide a suggestion here.
* @param {ASTNode} node The MemberExpression node to check.
* @returns {boolean} `true` if a suggestion can be provided for a node.
*/
function canProvideSuggestions(node) {
return (
node.parent.type === "CallExpression" &&
node.parent.callee === node &&
node.parent.parent.type === "ExpressionStatement" &&
astUtils.STATEMENT_LIST_PARENTS.has(node.parent.parent.parent.type) &&
!maybeAsiHazard(node.parent.parent)
);
}
/**
* Reports the given reference as a violation.

@@ -107,6 +168,17 @@ * @param {eslint-scope.Reference} reference The reference to report.

const propertyName = astUtils.getStaticPropertyName(node);
context.report({
node,
loc: node.loc,
messageId: "unexpected"
messageId: "unexpected",
suggest: canProvideSuggestions(node)
? [{
messageId: "removeConsole",
data: { propertyName },
fix(fixer) {
return fixer.remove(node.parent.parent);
}
}]
: []
});

@@ -113,0 +185,0 @@ }

@@ -12,65 +12,10 @@ /**

const { getVariableByName, isArrowToken, isClosingBraceToken, isClosingParenToken } = require("./utils/ast-utils");
const {
getVariableByName,
isArrowToken,
isStartOfExpressionStatement,
needsPrecedingSemicolon
} = require("./utils/ast-utils");
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
const BREAK_OR_CONTINUE = new Set(["BreakStatement", "ContinueStatement"]);
// Declaration types that must contain a string Literal node at the end.
const DECLARATIONS = new Set(["ExportAllDeclaration", "ExportNamedDeclaration", "ImportDeclaration"]);
const IDENTIFIER_OR_KEYWORD = new Set(["Identifier", "Keyword"]);
// Keywords that can immediately precede an ExpressionStatement node, mapped to the their node types.
const NODE_TYPES_BY_KEYWORD = {
__proto__: null,
break: "BreakStatement",
continue: "ContinueStatement",
debugger: "DebuggerStatement",
do: "DoWhileStatement",
else: "IfStatement",
return: "ReturnStatement",
yield: "YieldExpression"
};
/*
* Before an opening parenthesis, postfix `++` and `--` always trigger ASI;
* the tokens `:`, `;`, `{` and `=>` don't expect a semicolon, as that would count as an empty statement.
*/
const PUNCTUATORS = new Set([":", ";", "{", "=>", "++", "--"]);
/*
* Statements that can contain an `ExpressionStatement` after a closing parenthesis.
* DoWhileStatement is an exception in that it always triggers ASI after the closing parenthesis.
*/
const STATEMENTS = new Set([
"DoWhileStatement",
"ForInStatement",
"ForOfStatement",
"ForStatement",
"IfStatement",
"WhileStatement",
"WithStatement"
]);
/**
* Tests if a node appears at the beginning of an ancestor ExpressionStatement node.
* @param {ASTNode} node The node to check.
* @returns {boolean} Whether the node appears at the beginning of an ancestor ExpressionStatement node.
*/
function isStartOfExpressionStatement(node) {
const start = node.range[0];
let ancestor = node;
while ((ancestor = ancestor.parent) && ancestor.range[0] === start) {
if (ancestor.type === "ExpressionStatement") {
return true;
}
}
return false;
}
//------------------------------------------------------------------------------
// Rule Definition

@@ -125,46 +70,2 @@ //------------------------------------------------------------------------------

/**
* Determines whether a parenthesized object literal that replaces a specified node needs to be preceded by a semicolon.
* @param {ASTNode} node The node to be replaced. This node should be at the start of an `ExpressionStatement` or at the start of the body of an `ArrowFunctionExpression`.
* @returns {boolean} Whether a semicolon is required before the parenthesized object literal.
*/
function needsSemicolon(node) {
const prevToken = sourceCode.getTokenBefore(node);
if (!prevToken || prevToken.type === "Punctuator" && PUNCTUATORS.has(prevToken.value)) {
return false;
}
const prevNode = sourceCode.getNodeByRangeIndex(prevToken.range[0]);
if (isClosingParenToken(prevToken)) {
return !STATEMENTS.has(prevNode.type);
}
if (isClosingBraceToken(prevToken)) {
return (
prevNode.type === "BlockStatement" && prevNode.parent.type === "FunctionExpression" ||
prevNode.type === "ClassBody" && prevNode.parent.type === "ClassExpression" ||
prevNode.type === "ObjectExpression"
);
}
if (IDENTIFIER_OR_KEYWORD.has(prevToken.type)) {
if (BREAK_OR_CONTINUE.has(prevNode.parent.type)) {
return false;
}
const keyword = prevToken.value;
const nodeType = NODE_TYPES_BY_KEYWORD[keyword];
return prevNode.type !== nodeType;
}
if (prevToken.type === "String") {
return !DECLARATIONS.has(prevNode.parent.type);
}
return true;
}
/**
* Reports on nodes where the `Object` constructor is called without arguments.

@@ -188,3 +89,3 @@ * @param {ASTNode} node The node to evaluate.

replacement = "({})";
if (needsSemicolon(node)) {
if (needsPrecedingSemicolon(sourceCode, node)) {
fixText = ";({})";

@@ -191,0 +92,0 @@ messageId = "useLiteralAfterSemicolon";

@@ -77,2 +77,5 @@ /**

},
importNamePattern: {
type: "string"
},
message: {

@@ -119,4 +122,8 @@ type: "string",

patternAndEverything: "* import is invalid because '{{importNames}}' from '{{importSource}}' is restricted from being used by a pattern.",
patternAndEverythingWithRegexImportName: "* import is invalid because import name matching '{{importNames}}' pattern from '{{importSource}}' is restricted from being used.",
// eslint-disable-next-line eslint-plugin/report-message-format -- Custom message might not end in a period
patternAndEverythingWithCustomMessage: "* import is invalid because '{{importNames}}' from '{{importSource}}' is restricted from being used by a pattern. {{customMessage}}",
// eslint-disable-next-line eslint-plugin/report-message-format -- Custom message might not end in a period
patternAndEverythingWithRegexImportNameAndCustomMessage: "* import is invalid because import name matching '{{importNames}}' pattern from '{{importSource}}' is restricted from being used. {{customMessage}}",

@@ -180,6 +187,7 @@ everything: "* import is invalid because '{{importNames}}' from '{{importSource}}' is restricted.",

// relative paths are supported for this rule
const restrictedPatternGroups = restrictedPatterns.map(({ group, message, caseSensitive, importNames }) => ({
const restrictedPatternGroups = restrictedPatterns.map(({ group, message, caseSensitive, importNames, importNamePattern }) => ({
matcher: ignore({ allowRelativePaths: true, ignorecase: !caseSensitive }).add(group),
customMessage: message,
importNames
importNames,
importNamePattern
}));

@@ -268,2 +276,3 @@

const restrictedImportNames = group.importNames;
const restrictedImportNamePattern = group.importNamePattern ? new RegExp(group.importNamePattern, "u") : null;

@@ -274,3 +283,3 @@ /*

*/
if (!restrictedImportNames) {
if (!restrictedImportNames && !restrictedImportNamePattern) {
context.report({

@@ -287,36 +296,50 @@ node,

if (importNames.has("*")) {
const specifierData = importNames.get("*")[0];
importNames.forEach((specifiers, importName) => {
if (importName === "*") {
const [specifier] = specifiers;
context.report({
node,
messageId: customMessage ? "patternAndEverythingWithCustomMessage" : "patternAndEverything",
loc: specifierData.loc,
data: {
importSource,
importNames: restrictedImportNames,
customMessage
if (restrictedImportNames) {
context.report({
node,
messageId: customMessage ? "patternAndEverythingWithCustomMessage" : "patternAndEverything",
loc: specifier.loc,
data: {
importSource,
importNames: restrictedImportNames,
customMessage
}
});
} else {
context.report({
node,
messageId: customMessage ? "patternAndEverythingWithRegexImportNameAndCustomMessage" : "patternAndEverythingWithRegexImportName",
loc: specifier.loc,
data: {
importSource,
importNames: restrictedImportNamePattern,
customMessage
}
});
}
});
}
restrictedImportNames.forEach(importName => {
if (!importNames.has(importName)) {
return;
}
const specifiers = importNames.get(importName);
specifiers.forEach(specifier => {
context.report({
node,
messageId: customMessage ? "patternAndImportNameWithCustomMessage" : "patternAndImportName",
loc: specifier.loc,
data: {
importSource,
customMessage,
importName
}
if (
(restrictedImportNames && restrictedImportNames.includes(importName)) ||
(restrictedImportNamePattern && restrictedImportNamePattern.test(importName))
) {
specifiers.forEach(specifier => {
context.report({
node,
messageId: customMessage ? "patternAndImportNameWithCustomMessage" : "patternAndImportName",
loc: specifier.loc,
data: {
importSource,
customMessage,
importName
}
});
});
});
}
});

@@ -323,0 +346,0 @@ }

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

@@ -22,2 +22,3 @@ "description": "An AST-based pattern checker for JavaScript.",

"lint:docs:js": "node Makefile.js lintDocsJS",
"lint:docs:rule-examples": "node Makefile.js checkRuleExamples",
"lint:fix": "node Makefile.js lint -- fix",

@@ -45,2 +46,7 @@ "lint:fix:docs:js": "node Makefile.js lintDocsJS -- fix",

],
"docs/src/rules/*.md": [
"node tools/check-rule-examples.js",
"node tools/fetch-docs-links.js",
"git add docs/src/_data/further_reading_links.json"
],
"docs/**/*.svg": "npx svgo -r --multipass"

@@ -63,4 +69,4 @@ },

"@eslint-community/regexpp": "^4.6.1",
"@eslint/eslintrc": "^2.1.3",
"@eslint/js": "8.53.0",
"@eslint/eslintrc": "^2.1.4",
"@eslint/js": "8.55.0",
"@humanwhocodes/config-array": "^0.11.13",

@@ -134,6 +140,14 @@ "@humanwhocodes/module-importer": "^1.0.1",

"load-perf": "^0.2.0",
"markdownlint": "^0.25.1",
"markdownlint-cli": "^0.31.1",
"markdown-it": "^12.2.0",
"markdown-it-container": "^3.0.0",
"markdownlint": "^0.31.1",
"markdownlint-cli": "^0.37.0",
"marked": "^4.0.8",
"memfs": "^3.0.1",
"metascraper": "^5.25.7",
"metascraper-description": "^5.25.7",
"metascraper-image": "^5.29.3",
"metascraper-logo": "^5.25.7",
"metascraper-logo-favicon": "^5.25.7",
"metascraper-title": "^5.25.7",
"mocha": "^8.3.2",

@@ -146,4 +160,4 @@ "mocha-junit-reporter": "^2.0.0",

"proxyquire": "^2.0.1",
"recast": "^0.20.4",
"regenerator-runtime": "^0.13.2",
"recast": "^0.23.0",
"regenerator-runtime": "^0.14.0",
"rollup-plugin-node-polyfills": "^0.2.1",

@@ -153,3 +167,3 @@ "semver": "^7.5.3",

"sinon": "^11.0.0",
"vite-plugin-commonjs": "^0.8.2",
"vite-plugin-commonjs": "^0.10.0",
"webdriverio": "^8.14.6",

@@ -156,0 +170,0 @@ "webpack": "^5.23.0",

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