Bump packages to fix linter

This commit is contained in:
Henry Mercer 2023-01-18 20:50:03 +00:00
parent ed9506bbaf
commit 0a11e3fdd9
6063 changed files with 378752 additions and 306784 deletions

View file

@ -134,13 +134,13 @@ function isPropertyDescriptor(node) {
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "suggestion",
docs: {
description: "enforce getter and setter pairs in objects and classes",
category: "Best Practices",
description: "Enforce getter and setter pairs in objects and classes",
recommended: false,
url: "https://eslint.org/docs/rules/accessor-pairs"
},
@ -299,12 +299,12 @@ module.exports = {
* @private
*/
function checkPropertyDescriptor(node) {
const namesToCheck = node.properties
const namesToCheck = new Set(node.properties
.filter(p => p.type === "Property" && p.kind === "init" && !p.computed)
.map(({ key }) => key.name);
.map(({ key }) => key.name));
const hasGetter = namesToCheck.includes("get");
const hasSetter = namesToCheck.includes("set");
const hasGetter = namesToCheck.has("get");
const hasSetter = namesToCheck.has("set");
if (checkSetWithoutGet && hasSetter && !hasGetter) {
report(node, "missingGetter");

View file

@ -11,13 +11,13 @@ const astUtils = require("./utils/ast-utils");
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "layout",
docs: {
description: "enforce linebreaks after opening and before closing array brackets",
category: "Stylistic Issues",
description: "Enforce linebreaks after opening and before closing array brackets",
recommended: false,
url: "https://eslint.org/docs/rules/array-bracket-newline"
},

View file

@ -10,13 +10,13 @@ const astUtils = require("./utils/ast-utils");
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "layout",
docs: {
description: "enforce consistent spacing inside array brackets",
category: "Stylistic Issues",
description: "Enforce consistent spacing inside array brackets",
recommended: false,
url: "https://eslint.org/docs/rules/array-bracket-spacing"
},

View file

@ -16,7 +16,7 @@ const astUtils = require("./utils/ast-utils");
//------------------------------------------------------------------------------
const TARGET_NODE_TYPE = /^(?:Arrow)?FunctionExpression$/u;
const TARGET_METHODS = /^(?:every|filter|find(?:Index)?|flatMap|forEach|map|reduce(?:Right)?|some|sort)$/u;
const TARGET_METHODS = /^(?:every|filter|find(?:Last)?(?:Index)?|flatMap|forEach|map|reduce(?:Right)?|some|sort)$/u;
/**
* Checks a given code path segment is reachable.
@ -125,7 +125,7 @@ function getArrayMethodName(node) {
}
}
/* istanbul ignore next: unreachable */
/* c8 ignore next */
return null;
}
@ -133,13 +133,13 @@ function getArrayMethodName(node) {
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "problem",
docs: {
description: "enforce `return` statements in callbacks of array methods",
category: "Best Practices",
description: "Enforce `return` statements in callbacks of array methods",
recommended: false,
url: "https://eslint.org/docs/rules/array-callback-return"
},

View file

@ -11,13 +11,13 @@ const astUtils = require("./utils/ast-utils");
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "layout",
docs: {
description: "enforce line breaks after each array element",
category: "Stylistic Issues",
description: "Enforce line breaks after each array element",
recommended: false,
url: "https://eslint.org/docs/rules/array-element-newline"
},

View file

@ -14,13 +14,13 @@ const astUtils = require("./utils/ast-utils");
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "suggestion",
docs: {
description: "require braces around arrow function bodies",
category: "ECMAScript 6",
description: "Require braces around arrow function bodies",
recommended: false,
url: "https://eslint.org/docs/rules/arrow-body-style"
},

View file

@ -27,13 +27,13 @@ function hasBlockBody(node) {
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "layout",
docs: {
description: "require parentheses around arrow function arguments",
category: "ECMAScript 6",
description: "Require parentheses around arrow function arguments",
recommended: false,
url: "https://eslint.org/docs/rules/arrow-parens"
},

View file

@ -14,13 +14,13 @@ const astUtils = require("./utils/ast-utils");
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "layout",
docs: {
description: "enforce consistent spacing before and after the arrow in arrow functions",
category: "ECMAScript 6",
description: "Enforce consistent spacing before and after the arrow in arrow functions",
recommended: false,
url: "https://eslint.org/docs/rules/arrow-spacing"
},

View file

@ -8,13 +8,13 @@
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "suggestion",
docs: {
description: "enforce the use of variables within the scope they are defined",
category: "Best Practices",
description: "Enforce the use of variables within the scope they are defined",
recommended: false,
url: "https://eslint.org/docs/rules/block-scoped-var"
},
@ -113,6 +113,8 @@ module.exports = {
"SwitchStatement:exit": exitScope,
CatchClause: enterScope,
"CatchClause:exit": exitScope,
StaticBlock: enterScope,
"StaticBlock:exit": exitScope,
// Finds and reports references which are outside of valid scope.
VariableDeclaration: checkForVariables

View file

@ -11,13 +11,13 @@ const util = require("./utils/ast-utils");
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "layout",
docs: {
description: "disallow or enforce spaces inside of blocks after opening block and before closing block",
category: "Stylistic Issues",
description: "Disallow or enforce spaces inside of blocks after opening block and before closing block",
recommended: false,
url: "https://eslint.org/docs/rules/block-spacing"
},
@ -41,7 +41,7 @@ module.exports = {
/**
* Gets the open brace token from a given node.
* @param {ASTNode} node A BlockStatement/SwitchStatement node to get.
* @param {ASTNode} node A BlockStatement/StaticBlock/SwitchStatement node to get.
* @returns {Token} The token of the open brace.
*/
function getOpenBrace(node) {
@ -51,6 +51,12 @@ module.exports = {
}
return sourceCode.getLastToken(node, 1);
}
if (node.type === "StaticBlock") {
return sourceCode.getFirstToken(node, { skip: 1 }); // skip the `static` token
}
// "BlockStatement"
return sourceCode.getFirstToken(node);
}
@ -73,8 +79,8 @@ module.exports = {
}
/**
* Reports invalid spacing style inside braces.
* @param {ASTNode} node A BlockStatement/SwitchStatement node to get.
* Checks and reports invalid spacing style inside braces.
* @param {ASTNode} node A BlockStatement/StaticBlock/SwitchStatement node to check.
* @returns {void}
*/
function checkSpacingInsideBraces(node) {
@ -158,6 +164,7 @@ module.exports = {
return {
BlockStatement: checkSpacingInsideBraces,
StaticBlock: checkSpacingInsideBraces,
SwitchStatement: checkSpacingInsideBraces
};
}

View file

@ -11,13 +11,13 @@ const astUtils = require("./utils/ast-utils");
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "layout",
docs: {
description: "enforce consistent brace style for blocks",
category: "Stylistic Issues",
description: "Enforce consistent brace style for blocks",
recommended: false,
url: "https://eslint.org/docs/rules/brace-style"
},
@ -156,6 +156,12 @@ module.exports = {
validateCurlyPair(sourceCode.getFirstToken(node), sourceCode.getLastToken(node));
}
},
StaticBlock(node) {
validateCurlyPair(
sourceCode.getFirstToken(node, { skip: 1 }), // skip the `static` token
sourceCode.getLastToken(node)
);
},
ClassBody(node) {
validateCurlyPair(sourceCode.getFirstToken(node), sourceCode.getLastToken(node));
},

View file

@ -1,6 +1,7 @@
/**
* @fileoverview Enforce return after a callback.
* @author Jamund Ferguson
* @deprecated in ESLint v7.0.0
*/
"use strict";
@ -8,6 +9,7 @@
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
deprecated: true,
@ -17,8 +19,7 @@ module.exports = {
type: "suggestion",
docs: {
description: "require `return` statements after callbacks",
category: "Node.js and CommonJS",
description: "Require `return` statements after callbacks",
recommended: false,
url: "https://eslint.org/docs/rules/callback-return"
},
@ -52,7 +53,7 @@ module.exports = {
if (!node.parent) {
return null;
}
if (types.indexOf(node.parent.type) === -1) {
if (!types.includes(node.parent.type)) {
return findClosestParentOfType(node.parent, types);
}
return node.parent;
@ -86,7 +87,7 @@ module.exports = {
* @returns {boolean} Whether or not this function matches our callback name.
*/
function isCallback(node) {
return containsOnlyIdentifiers(node.callee) && callbacks.indexOf(sourceCode.getText(node.callee)) > -1;
return containsOnlyIdentifiers(node.callee) && callbacks.includes(sourceCode.getText(node.callee));
}
/**

View file

@ -5,17 +5,23 @@
"use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const astUtils = require("./utils/ast-utils");
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "suggestion",
docs: {
description: "enforce camelcase naming convention",
category: "Stylistic Issues",
description: "Enforce camelcase naming convention",
recommended: false,
url: "https://eslint.org/docs/rules/camelcase"
},
@ -55,32 +61,25 @@ module.exports = {
],
messages: {
notCamelCase: "Identifier '{{name}}' is not in camel case."
notCamelCase: "Identifier '{{name}}' is not in camel case.",
notCamelCasePrivate: "#{{name}} is not in camel case."
}
},
create(context) {
const options = context.options[0] || {};
let properties = options.properties || "";
const properties = options.properties === "never" ? "never" : "always";
const ignoreDestructuring = options.ignoreDestructuring;
const ignoreImports = options.ignoreImports;
const ignoreGlobals = options.ignoreGlobals;
const allow = options.allow || [];
let globalScope;
if (properties !== "always" && properties !== "never") {
properties = "always";
}
//--------------------------------------------------------------------------
// Helpers
//--------------------------------------------------------------------------
// contains reported nodes to avoid reporting twice on destructuring with shorthand notation
const reported = [];
const ALLOWED_PARENT_TYPES = new Set(["CallExpression", "NewExpression"]);
const reported = new Set();
/**
* Checks if a string contains an underscore and isn't all upper-case
@ -89,9 +88,10 @@ module.exports = {
* @private
*/
function isUnderscored(name) {
const nameBody = name.replace(/^_+|_+$/gu, "");
// if there's an underscore, it might be A_CONSTANT, which is okay
return name.includes("_") && name !== name.toUpperCase();
return nameBody.includes("_") && nameBody !== nameBody.toUpperCase();
}
/**
@ -107,94 +107,76 @@ module.exports = {
}
/**
* Checks if a parent of a node is an ObjectPattern.
* @param {ASTNode} node The node to check.
* @returns {boolean} if the node is inside an ObjectPattern
* Checks if a given name is good or not.
* @param {string} name The name to check.
* @returns {boolean} `true` if the name is good.
* @private
*/
function isInsideObjectPattern(node) {
let current = node;
while (current) {
const parent = current.parent;
if (parent && parent.type === "Property" && parent.computed && parent.key === current) {
return false;
}
if (current.type === "ObjectPattern") {
return true;
}
current = parent;
}
return false;
function isGoodName(name) {
return !isUnderscored(name) || isAllowed(name);
}
/**
* Checks whether the given node represents assignment target property in destructuring.
*
* For examples:
* ({a: b.foo} = c); // => true for `foo`
* ([a.foo] = b); // => true for `foo`
* ([a.foo = 1] = b); // => true for `foo`
* ({...a.foo} = b); // => true for `foo`
* @param {ASTNode} node An Identifier node to check
* @returns {boolean} True if the node is an assignment target property in destructuring.
* Checks if a given identifier reference or member expression is an assignment
* target.
* @param {ASTNode} node The node to check.
* @returns {boolean} `true` if the node is an assignment target.
*/
function isAssignmentTargetPropertyInDestructuring(node) {
if (
node.parent.type === "MemberExpression" &&
node.parent.property === node &&
!node.parent.computed
) {
const effectiveParent = node.parent.parent;
return (
effectiveParent.type === "Property" &&
effectiveParent.value === node.parent &&
effectiveParent.parent.type === "ObjectPattern" ||
effectiveParent.type === "ArrayPattern" ||
effectiveParent.type === "RestElement" ||
(
effectiveParent.type === "AssignmentPattern" &&
effectiveParent.left === node.parent
)
);
}
return false;
}
/**
* Checks whether the given node represents a reference to a global variable that is not declared in the source code.
* These identifiers will be allowed, as it is assumed that user has no control over the names of external global variables.
* @param {ASTNode} node `Identifier` node to check.
* @returns {boolean} `true` if the node is a reference to a global variable.
*/
function isReferenceToGlobalVariable(node) {
const variable = globalScope.set.get(node.name);
return variable && variable.defs.length === 0 &&
variable.references.some(ref => ref.identifier === node);
}
/**
* Checks whether the given node represents a reference to a property of an object in an object literal expression.
* This allows to differentiate between a global variable that is allowed to be used as a reference, and the key
* of the expressed object (which shouldn't be allowed).
* @param {ASTNode} node `Identifier` node to check.
* @returns {boolean} `true` if the node is a property name of an object literal expression
*/
function isPropertyNameInObjectLiteral(node) {
function isAssignmentTarget(node) {
const parent = node.parent;
return (
parent.type === "Property" &&
parent.parent.type === "ObjectExpression" &&
!parent.computed &&
parent.key === node
);
switch (parent.type) {
case "AssignmentExpression":
case "AssignmentPattern":
return parent.left === node;
case "Property":
return (
parent.parent.type === "ObjectPattern" &&
parent.value === node
);
case "ArrayPattern":
case "RestElement":
return true;
default:
return false;
}
}
/**
* Checks if a given binding identifier uses the original name as-is.
* - If it's in object destructuring or object expression, the original name is its property name.
* - If it's in import declaration, the original name is its exported name.
* @param {ASTNode} node The `Identifier` node to check.
* @returns {boolean} `true` if the identifier uses the original name as-is.
*/
function equalsToOriginalName(node) {
const localName = node.name;
const valueNode = node.parent.type === "AssignmentPattern"
? node.parent
: node;
const parent = valueNode.parent;
switch (parent.type) {
case "Property":
return (
(parent.parent.type === "ObjectPattern" || parent.parent.type === "ObjectExpression") &&
parent.value === valueNode &&
!parent.computed &&
parent.key.type === "Identifier" &&
parent.key.name === localName
);
case "ImportSpecifier":
return (
parent.local === node &&
astUtils.getModuleExportName(parent.imported) === localName
);
default:
return false;
}
}
/**
@ -204,122 +186,213 @@ module.exports = {
* @private
*/
function report(node) {
if (!reported.includes(node)) {
reported.push(node);
context.report({ node, messageId: "notCamelCase", data: { name: node.name } });
if (reported.has(node.range[0])) {
return;
}
reported.add(node.range[0]);
// Report it.
context.report({
node,
messageId: node.type === "PrivateIdentifier"
? "notCamelCasePrivate"
: "notCamelCase",
data: { name: node.name }
});
}
/**
* Reports an identifier reference or a binding identifier.
* @param {ASTNode} node The `Identifier` node to report.
* @returns {void}
*/
function reportReferenceId(node) {
/*
* For backward compatibility, if it's in callings then ignore it.
* Not sure why it is.
*/
if (
node.parent.type === "CallExpression" ||
node.parent.type === "NewExpression"
) {
return;
}
/*
* For backward compatibility, if it's a default value of
* destructuring/parameters then ignore it.
* Not sure why it is.
*/
if (
node.parent.type === "AssignmentPattern" &&
node.parent.right === node
) {
return;
}
/*
* The `ignoreDestructuring` flag skips the identifiers that uses
* the property name as-is.
*/
if (ignoreDestructuring && equalsToOriginalName(node)) {
return;
}
report(node);
}
return {
// Report camelcase of global variable references ------------------
Program() {
globalScope = context.getScope();
const scope = context.getScope();
if (!ignoreGlobals) {
// Defined globals in config files or directive comments.
for (const variable of scope.variables) {
if (
variable.identifiers.length > 0 ||
isGoodName(variable.name)
) {
continue;
}
for (const reference of variable.references) {
/*
* For backward compatibility, this rule reports read-only
* references as well.
*/
reportReferenceId(reference.identifier);
}
}
}
// Undefined globals.
for (const reference of scope.through) {
const id = reference.identifier;
if (isGoodName(id.name)) {
continue;
}
/*
* For backward compatibility, this rule reports read-only
* references as well.
*/
reportReferenceId(id);
}
},
Identifier(node) {
// Report camelcase of declared variables --------------------------
[[
"VariableDeclaration",
"FunctionDeclaration",
"FunctionExpression",
"ArrowFunctionExpression",
"ClassDeclaration",
"ClassExpression",
"CatchClause"
]](node) {
for (const variable of context.getDeclaredVariables(node)) {
if (isGoodName(variable.name)) {
continue;
}
const id = variable.identifiers[0];
/*
* Leading and trailing underscores are commonly used to flag
* private/protected identifiers, strip them before checking if underscored
*/
const name = node.name,
nameIsUnderscored = isUnderscored(name.replace(/^_+|_+$/gu, "")),
effectiveParent = (node.parent.type === "MemberExpression") ? node.parent.parent : node.parent;
// Report declaration.
if (!(ignoreDestructuring && equalsToOriginalName(id))) {
report(id);
}
// First, we ignore the node if it match the ignore list
if (isAllowed(name)) {
/*
* For backward compatibility, report references as well.
* It looks unnecessary because declarations are reported.
*/
for (const reference of variable.references) {
if (reference.init) {
continue; // Skip the write references of initializers.
}
reportReferenceId(reference.identifier);
}
}
},
// Report camelcase in properties ----------------------------------
[[
"ObjectExpression > Property[computed!=true] > Identifier.key",
"MethodDefinition[computed!=true] > Identifier.key",
"PropertyDefinition[computed!=true] > Identifier.key",
"MethodDefinition > PrivateIdentifier.key",
"PropertyDefinition > PrivateIdentifier.key"
]](node) {
if (properties === "never" || isGoodName(node.name)) {
return;
}
// Check if it's a global variable
if (ignoreGlobals && isReferenceToGlobalVariable(node) && !isPropertyNameInObjectLiteral(node)) {
report(node);
},
"MemberExpression[computed!=true] > Identifier.property"(node) {
if (
properties === "never" ||
!isAssignmentTarget(node.parent) || // ← ignore read-only references.
isGoodName(node.name)
) {
return;
}
report(node);
},
// MemberExpressions get special rules
if (node.parent.type === "MemberExpression") {
// Report camelcase in import --------------------------------------
ImportDeclaration(node) {
for (const variable of context.getDeclaredVariables(node)) {
if (isGoodName(variable.name)) {
continue;
}
const id = variable.identifiers[0];
// "never" check properties
if (properties === "never") {
return;
// Report declaration.
if (!(ignoreImports && equalsToOriginalName(id))) {
report(id);
}
// Always report underscored object names
if (node.parent.object.type === "Identifier" && node.parent.object.name === node.name && nameIsUnderscored) {
report(node);
// Report AssignmentExpressions only if they are the left side of the assignment
} else if (effectiveParent.type === "AssignmentExpression" && nameIsUnderscored && (effectiveParent.right.type !== "MemberExpression" || effectiveParent.left.type === "MemberExpression" && effectiveParent.left.property.name === node.name)) {
report(node);
} else if (isAssignmentTargetPropertyInDestructuring(node) && nameIsUnderscored) {
report(node);
/*
* For backward compatibility, report references as well.
* It looks unnecessary because declarations are reported.
*/
for (const reference of variable.references) {
reportReferenceId(reference.identifier);
}
}
},
// Report camelcase in re-export -----------------------------------
[[
"ExportAllDeclaration > Identifier.exported",
"ExportSpecifier > Identifier.exported"
]](node) {
if (isGoodName(node.name)) {
return;
}
report(node);
},
// Report camelcase in labels --------------------------------------
[[
"LabeledStatement > Identifier.label",
/*
* Properties have their own rules, and
* AssignmentPattern nodes can be treated like Properties:
* e.g.: const { no_camelcased = false } = bar;
* For backward compatibility, report references as well.
* It looks unnecessary because declarations are reported.
*/
} else if (node.parent.type === "Property" || node.parent.type === "AssignmentPattern") {
if (node.parent.parent && node.parent.parent.type === "ObjectPattern") {
if (node.parent.shorthand && node.parent.value.left && nameIsUnderscored) {
report(node);
}
const assignmentKeyEqualsValue = node.parent.key.name === node.parent.value.name;
if (nameIsUnderscored && node.parent.computed) {
report(node);
}
// prevent checking righthand side of destructured object
if (node.parent.key === node && node.parent.value !== node) {
return;
}
const valueIsUnderscored = node.parent.value.name && nameIsUnderscored;
// ignore destructuring if the option is set, unless a new identifier is created
if (valueIsUnderscored && !(assignmentKeyEqualsValue && ignoreDestructuring)) {
report(node);
}
}
// "never" check properties or always ignore destructuring
if (properties === "never" || (ignoreDestructuring && isInsideObjectPattern(node))) {
return;
}
// don't check right hand side of AssignmentExpression to prevent duplicate warnings
if (nameIsUnderscored && !ALLOWED_PARENT_TYPES.has(effectiveParent.type) && !(node.parent.right === node)) {
report(node);
}
// Check if it's an import specifier
} else if (["ImportSpecifier", "ImportNamespaceSpecifier", "ImportDefaultSpecifier"].includes(node.parent.type)) {
if (node.parent.type === "ImportSpecifier" && ignoreImports) {
return;
}
// Report only if the local imported identifier is underscored
if (
node.parent.local &&
node.parent.local.name === node.name &&
nameIsUnderscored
) {
report(node);
}
// Report anything that is underscored that isn't a CallExpression
} else if (nameIsUnderscored && !ALLOWED_PARENT_TYPES.has(effectiveParent.type)) {
report(node);
"BreakStatement > Identifier.label",
"ContinueStatement > Identifier.label"
]](node) {
if (isGoodName(node.name)) {
return;
}
report(node);
}
};
}
};

View file

@ -99,13 +99,13 @@ function createRegExpForIgnorePatterns(normalizedOptions) {
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "suggestion",
docs: {
description: "enforce or disallow capitalization of the first letter of a comment",
category: "Stylistic Issues",
description: "Enforce or disallow capitalization of the first letter of a comment",
recommended: false,
url: "https://eslint.org/docs/rules/capitalized-comments"
},
@ -185,7 +185,7 @@ module.exports = {
return Boolean(
previousTokenOrComment &&
["Block", "Line"].indexOf(previousTokenOrComment.type) !== -1
["Block", "Line"].includes(previousTokenOrComment.type)
);
}

View file

@ -15,13 +15,13 @@ const astUtils = require("./utils/ast-utils");
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "suggestion",
docs: {
description: "enforce that class methods utilize `this`",
category: "Best Practices",
description: "Enforce that class methods utilize `this`",
recommended: false,
url: "https://eslint.org/docs/rules/class-methods-use-this"
},
@ -34,6 +34,10 @@ module.exports = {
items: {
type: "string"
}
},
enforceForClassFields: {
type: "boolean",
default: true
}
},
additionalProperties: false
@ -45,10 +49,27 @@ module.exports = {
},
create(context) {
const config = Object.assign({}, context.options[0]);
const enforceForClassFields = config.enforceForClassFields !== false;
const exceptMethods = new Set(config.exceptMethods || []);
const stack = [];
/**
* Push `this` used flag initialized with `false` onto the stack.
* @returns {void}
*/
function pushContext() {
stack.push(false);
}
/**
* Pop `this` used flag from the stack.
* @returns {boolean | undefined} `this` used flag
*/
function popContext() {
return stack.pop();
}
/**
* Initializes the current context to false and pushes it onto the stack.
* These booleans represent whether 'this' has been used in the context.
@ -56,7 +77,7 @@ module.exports = {
* @private
*/
function enterFunction() {
stack.push(false);
pushContext();
}
/**
@ -66,7 +87,14 @@ module.exports = {
* @private
*/
function isInstanceMethod(node) {
return !node.static && node.kind !== "constructor" && node.type === "MethodDefinition";
switch (node.type) {
case "MethodDefinition":
return !node.static && node.kind !== "constructor";
case "PropertyDefinition":
return !node.static && enforceForClassFields;
default:
return false;
}
}
/**
@ -76,8 +104,19 @@ module.exports = {
* @private
*/
function isIncludedInstanceMethod(node) {
return isInstanceMethod(node) &&
(node.computed || !exceptMethods.has(node.key.name));
if (isInstanceMethod(node)) {
if (node.computed) {
return true;
}
const hashIfNeeded = node.key.type === "PrivateIdentifier" ? "#" : "";
const name = node.key.type === "Literal"
? astUtils.getStaticStringValue(node.key)
: (node.key.name || "");
return !exceptMethods.has(hashIfNeeded + name);
}
return false;
}
/**
@ -89,11 +128,12 @@ module.exports = {
* @private
*/
function exitFunction(node) {
const methodUsesThis = stack.pop();
const methodUsesThis = popContext();
if (isIncludedInstanceMethod(node.parent) && !methodUsesThis) {
context.report({
node,
loc: astUtils.getFunctionHeadLoc(node, context.getSourceCode()),
messageId: "missingThis",
data: {
name: astUtils.getFunctionNameWithKind(node)
@ -118,8 +158,30 @@ module.exports = {
"FunctionDeclaration:exit": exitFunction,
FunctionExpression: enterFunction,
"FunctionExpression:exit": exitFunction,
/*
* Class field value are implicit functions.
*/
"PropertyDefinition > *.key:exit": pushContext,
"PropertyDefinition:exit": popContext,
/*
* Class static blocks are implicit functions. They aren't required to use `this`,
* but we have to push context so that it captures any use of `this` in the static block
* separately from enclosing contexts, because static blocks have their own `this` and it
* shouldn't count as used `this` in enclosing contexts.
*/
StaticBlock: pushContext,
"StaticBlock:exit": popContext,
ThisExpression: markThisUsed,
Super: markThisUsed
Super: markThisUsed,
...(
enforceForClassFields && {
"PropertyDefinition > ArrowFunctionExpression.value": enterFunction,
"PropertyDefinition > ArrowFunctionExpression.value:exit": exitFunction
}
)
};
}
};

View file

@ -50,7 +50,7 @@ function normalizeOptions(optionValue, ecmaVersion) {
objects: optionValue,
imports: optionValue,
exports: optionValue,
functions: (!ecmaVersion || ecmaVersion < 8) ? "ignore" : optionValue
functions: ecmaVersion < 2017 ? "ignore" : optionValue
};
}
if (typeof optionValue === "object" && optionValue !== null) {
@ -70,13 +70,13 @@ function normalizeOptions(optionValue, ecmaVersion) {
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "layout",
docs: {
description: "require or disallow trailing commas",
category: "Stylistic Issues",
description: "Require or disallow trailing commas",
recommended: false,
url: "https://eslint.org/docs/rules/comma-dangle"
},
@ -123,7 +123,8 @@ module.exports = {
}
]
}
]
],
additionalItems: false
},
messages: {
@ -133,7 +134,7 @@ module.exports = {
},
create(context) {
const options = normalizeOptions(context.options[0], context.parserOptions.ecmaVersion);
const options = normalizeOptions(context.options[0], context.languageOptions.ecmaVersion);
const sourceCode = context.getSourceCode();
@ -242,8 +243,18 @@ module.exports = {
node: lastItem,
loc: trailingToken.loc,
messageId: "unexpected",
fix(fixer) {
return fixer.remove(trailingToken);
*fix(fixer) {
yield fixer.remove(trailingToken);
/*
* Extend the range of the fix to include surrounding tokens to ensure
* that the element after which the comma is removed stays _last_.
* This intentionally makes conflicts in fix ranges with rules that may be
* adding or removing elements in the same autofix pass.
* https://github.com/eslint/eslint/issues/15660
*/
yield fixer.insertTextBefore(sourceCode.getTokenBefore(trailingToken), "");
yield fixer.insertTextAfter(sourceCode.getTokenAfter(trailingToken), "");
}
});
}
@ -281,8 +292,18 @@ module.exports = {
end: astUtils.getNextLocation(sourceCode, trailingToken.loc.end)
},
messageId: "missing",
fix(fixer) {
return fixer.insertTextAfter(trailingToken, ",");
*fix(fixer) {
yield fixer.insertTextAfter(trailingToken, ",");
/*
* Extend the range of the fix to include surrounding tokens to ensure
* that the element after which the comma is inserted stays _last_.
* This intentionally makes conflicts in fix ranges with rules that may be
* adding or removing elements in the same autofix pass.
* https://github.com/eslint/eslint/issues/15660
*/
yield fixer.insertTextBefore(trailingToken, "");
yield fixer.insertTextAfter(sourceCode.getTokenAfter(trailingToken), "");
}
});
}
@ -325,7 +346,7 @@ module.exports = {
"always-multiline": forceTrailingCommaIfMultiline,
"only-multiline": allowTrailingCommaIfMultiline,
never: forbidTrailingComma,
ignore: () => {}
ignore() {}
};
return {

View file

@ -10,13 +10,13 @@ const astUtils = require("./utils/ast-utils");
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "layout",
docs: {
description: "enforce consistent spacing before and after commas",
category: "Stylistic Issues",
description: "Enforce consistent spacing before and after commas",
recommended: false,
url: "https://eslint.org/docs/rules/comma-spacing"
},
@ -103,38 +103,6 @@ module.exports = {
});
}
/**
* Validates the spacing around a comma token.
* @param {Object} tokens The tokens to be validated.
* @param {Token} tokens.comma The token representing the comma.
* @param {Token} [tokens.left] The last token before the comma.
* @param {Token} [tokens.right] The first token after the comma.
* @param {Token|ASTNode} reportItem The item to use when reporting an error.
* @returns {void}
* @private
*/
function validateCommaItemSpacing(tokens, reportItem) {
if (tokens.left && astUtils.isTokenOnSameLine(tokens.left, tokens.comma) &&
(options.before !== sourceCode.isSpaceBetweenTokens(tokens.left, tokens.comma))
) {
report(reportItem, "before", tokens.left);
}
if (tokens.right && astUtils.isClosingParenToken(tokens.right)) {
return;
}
if (tokens.right && !options.after && tokens.right.type === "Line") {
return;
}
if (tokens.right && astUtils.isTokenOnSameLine(tokens.comma, tokens.right) &&
(options.after !== sourceCode.isSpaceBetweenTokens(tokens.comma, tokens.right))
) {
report(reportItem, "after", tokens.right);
}
}
/**
* Adds null elements of the given ArrayExpression or ArrayPattern node to the ignore list.
* @param {ASTNode} node An ArrayExpression or ArrayPattern node.
@ -172,18 +140,44 @@ module.exports = {
return;
}
if (token && token.type === "JSXText") {
return;
}
const previousToken = tokensAndComments[i - 1];
const nextToken = tokensAndComments[i + 1];
validateCommaItemSpacing({
comma: token,
left: astUtils.isCommaToken(previousToken) || commaTokensToIgnore.includes(token) ? null : previousToken,
right: astUtils.isCommaToken(nextToken) ? null : nextToken
}, token);
if (
previousToken &&
!astUtils.isCommaToken(previousToken) && // ignore spacing between two commas
/*
* `commaTokensToIgnore` are ending commas of `null` elements (array holes/elisions).
* In addition to spacing between two commas, this can also ignore:
*
* - Spacing after `[` (controlled by array-bracket-spacing)
* Example: [ , ]
* ^
* - Spacing after a comment (for backwards compatibility, this was possibly unintentional)
* Example: [a, /* * / ,]
* ^
*/
!commaTokensToIgnore.includes(token) &&
astUtils.isTokenOnSameLine(previousToken, token) &&
options.before !== sourceCode.isSpaceBetweenTokens(previousToken, token)
) {
report(token, "before", previousToken);
}
if (
nextToken &&
!astUtils.isCommaToken(nextToken) && // ignore spacing between two commas
!astUtils.isClosingParenToken(nextToken) && // controlled by space-in-parens
!astUtils.isClosingBracketToken(nextToken) && // controlled by array-bracket-spacing
!astUtils.isClosingBraceToken(nextToken) && // controlled by object-curly-spacing
!(!options.after && nextToken.type === "Line") && // special case, allow space before line comment
astUtils.isTokenOnSameLine(token, nextToken) &&
options.after !== sourceCode.isSpaceBetweenTokens(token, nextToken)
) {
report(token, "after", nextToken);
}
});
},
ArrayExpression: addNullElementsToIgnoreList,

View file

@ -11,13 +11,13 @@ const astUtils = require("./utils/ast-utils");
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "layout",
docs: {
description: "enforce consistent comma style",
category: "Stylistic Issues",
description: "Enforce consistent comma style",
recommended: false,
url: "https://eslint.org/docs/rules/comma-style"
},

View file

@ -17,13 +17,13 @@ const { upperCaseFirst } = require("../shared/string-utils");
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "suggestion",
docs: {
description: "enforce a maximum cyclomatic complexity allowed in a program",
category: "Best Practices",
description: "Enforce a maximum cyclomatic complexity allowed in a program",
recommended: false,
url: "https://eslint.org/docs/rules/complexity"
},
@ -75,60 +75,16 @@ module.exports = {
// Helpers
//--------------------------------------------------------------------------
// Using a stack to store complexity (handling nested functions)
const fns = [];
// Using a stack to store complexity per code path
const complexities = [];
/**
* When parsing a new function, store it in our function stack
* @returns {void}
* @private
*/
function startFunction() {
fns.push(1);
}
/**
* Evaluate the node at the end of function
* @param {ASTNode} node node to evaluate
* @returns {void}
* @private
*/
function endFunction(node) {
const name = upperCaseFirst(astUtils.getFunctionNameWithKind(node));
const complexity = fns.pop();
if (complexity > THRESHOLD) {
context.report({
node,
messageId: "complex",
data: { name, complexity, max: THRESHOLD }
});
}
}
/**
* Increase the complexity of the function in context
* Increase the complexity of the code path in context
* @returns {void}
* @private
*/
function increaseComplexity() {
if (fns.length) {
fns[fns.length - 1]++;
}
}
/**
* Increase the switch complexity in context
* @param {ASTNode} node node to evaluate
* @returns {void}
* @private
*/
function increaseSwitchComplexity(node) {
// Avoiding `default`
if (node.test) {
increaseComplexity();
}
complexities[complexities.length - 1]++;
}
//--------------------------------------------------------------------------
@ -136,13 +92,14 @@ module.exports = {
//--------------------------------------------------------------------------
return {
FunctionDeclaration: startFunction,
FunctionExpression: startFunction,
ArrowFunctionExpression: startFunction,
"FunctionDeclaration:exit": endFunction,
"FunctionExpression:exit": endFunction,
"ArrowFunctionExpression:exit": endFunction,
onCodePathStart() {
// The initial complexity is 1, representing one execution path in the CodePath
complexities.push(1);
},
// Each branching in the code adds 1 to the complexity
CatchClause: increaseComplexity,
ConditionalExpression: increaseComplexity,
LogicalExpression: increaseComplexity,
@ -150,14 +107,57 @@ module.exports = {
ForInStatement: increaseComplexity,
ForOfStatement: increaseComplexity,
IfStatement: increaseComplexity,
SwitchCase: increaseSwitchComplexity,
WhileStatement: increaseComplexity,
DoWhileStatement: increaseComplexity,
// Avoid `default`
"SwitchCase[test]": increaseComplexity,
// Logical assignment operators have short-circuiting behavior
AssignmentExpression(node) {
if (astUtils.isLogicalAssignmentOperator(node.operator)) {
increaseComplexity();
}
},
onCodePathEnd(codePath, node) {
const complexity = complexities.pop();
/*
* This rule only evaluates complexity of functions, so "program" is excluded.
* Class field initializers and class static blocks are implicit functions. Therefore,
* they shouldn't contribute to the enclosing function's complexity, but their
* own complexity should be evaluated.
*/
if (
codePath.origin !== "function" &&
codePath.origin !== "class-field-initializer" &&
codePath.origin !== "class-static-block"
) {
return;
}
if (complexity > THRESHOLD) {
let name;
if (codePath.origin === "class-field-initializer") {
name = "class field initializer";
} else if (codePath.origin === "class-static-block") {
name = "class static block";
} else {
name = astUtils.getFunctionNameWithKind(node);
}
context.report({
node,
messageId: "complex",
data: {
name: upperCaseFirst(name),
complexity,
max: THRESHOLD
}
});
}
}
};

View file

@ -10,13 +10,13 @@ const astUtils = require("./utils/ast-utils");
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "layout",
docs: {
description: "enforce consistent spacing inside computed property brackets",
category: "Stylistic Issues",
description: "Enforce consistent spacing inside computed property brackets",
recommended: false,
url: "https://eslint.org/docs/rules/computed-property-spacing"
},
@ -195,7 +195,8 @@ module.exports = {
};
if (enforceForClassMembers) {
listeners.MethodDefinition = checkSpacing("key");
listeners.MethodDefinition =
listeners.PropertyDefinition = listeners.Property;
}
return listeners;

View file

@ -40,13 +40,13 @@ function isClassConstructor(node) {
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "suggestion",
docs: {
description: "require `return` statements to either always or never specify values",
category: "Best Practices",
description: "Require `return` statements to either always or never specify values",
recommended: false,
url: "https://eslint.org/docs/rules/consistent-return"
},

View file

@ -8,13 +8,13 @@
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "suggestion",
docs: {
description: "enforce consistent naming when capturing the current execution context",
category: "Stylistic Issues",
description: "Enforce consistent naming when capturing the current execution context",
recommended: false,
url: "https://eslint.org/docs/rules/consistent-this"
},
@ -47,7 +47,7 @@ module.exports = {
* Reports that a variable declarator or assignment expression is assigning
* a non-'this' value to the specified alias.
* @param {ASTNode} node The assigning node.
* @param {string} name the name of the alias that was incorrectly used.
* @param {string} name the name of the alias that was incorrectly used.
* @returns {void}
*/
function reportBadAssignment(node, name) {
@ -65,7 +65,7 @@ module.exports = {
function checkAssignment(node, name, value) {
const isThis = value.type === "ThisExpression";
if (aliases.indexOf(name) !== -1) {
if (aliases.includes(name)) {
if (!isThis || node.operator && node.operator !== "=") {
reportBadAssignment(node, name);
}

View file

@ -116,13 +116,13 @@ function isPossibleConstructor(node) {
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "problem",
docs: {
description: "require `super()` calls in constructors",
category: "ECMAScript 6",
description: "Require `super()` calls in constructors",
recommended: true,
url: "https://eslint.org/docs/rules/constructor-super"
},

View file

@ -14,13 +14,13 @@ const astUtils = require("./utils/ast-utils");
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "suggestion",
docs: {
description: "enforce consistent brace style for all control statements",
category: "Best Practices",
description: "Enforce consistent brace style for all control statements",
recommended: false,
url: "https://eslint.org/docs/rules/curly"
},

View file

@ -9,13 +9,13 @@
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "suggestion",
docs: {
description: "enforce default clauses in switch statements to be last",
category: "Best Practices",
description: "Enforce default clauses in switch statements to be last",
recommended: false,
url: "https://eslint.org/docs/rules/default-case-last"
},

View file

@ -10,13 +10,13 @@ const DEFAULT_COMMENT_PATTERN = /^no default$/iu;
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "suggestion",
docs: {
description: "require `default` cases in `switch` statements",
category: "Best Practices",
description: "Require `default` cases in `switch` statements",
recommended: false,
url: "https://eslint.org/docs/rules/default-case"
},
@ -50,8 +50,8 @@ module.exports = {
/**
* Shortcut to get last element of array
* @param {*[]} collection Array
* @returns {*} Last element
* @param {*[]} collection Array
* @returns {any} Last element
*/
function last(collection) {
return collection[collection.length - 1];

View file

@ -5,13 +5,13 @@
"use strict";
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "suggestion",
docs: {
description: "enforce default parameters to be last",
category: "Best Practices",
description: "Enforce default parameters to be last",
recommended: false,
url: "https://eslint.org/docs/rules/default-param-last"
},
@ -25,8 +25,8 @@ module.exports = {
create(context) {
// eslint-disable-next-line jsdoc/require-description
/**
* Handler for function contexts.
* @param {ASTNode} node function node
* @returns {void}
*/

View file

@ -11,13 +11,13 @@ const astUtils = require("./utils/ast-utils");
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "layout",
docs: {
description: "enforce consistent newlines before and after dots",
category: "Best Practices",
description: "Enforce consistent newlines before and after dots",
recommended: false,
url: "https://eslint.org/docs/rules/dot-location"
},

View file

@ -20,13 +20,13 @@ const validIdentifier = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/u;
// `null` literal must be handled separately.
const literalTypesToCheck = new Set(["string", "boolean"]);
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "suggestion",
docs: {
description: "enforce dot notation whenever possible",
category: "Best Practices",
description: "Enforce dot notation whenever possible",
recommended: false,
url: "https://eslint.org/docs/rules/dot-notation"
},
@ -76,7 +76,7 @@ module.exports = {
function checkComputedProperty(node, value) {
if (
validIdentifier.test(value) &&
(allowKeywords || keywords.indexOf(String(value)) === -1) &&
(allowKeywords || !keywords.includes(String(value))) &&
!(allowPattern && allowPattern.test(value))
) {
const formattedValue = node.property.type === "Literal" ? JSON.stringify(value) : `\`${value}\``;
@ -141,7 +141,8 @@ module.exports = {
if (
!allowKeywords &&
!node.computed &&
keywords.indexOf(String(node.property.name)) !== -1
node.property.type === "Identifier" &&
keywords.includes(String(node.property.name))
) {
context.report({
node: node.property,

View file

@ -8,13 +8,13 @@
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "layout",
docs: {
description: "require or disallow newline at the end of files",
category: "Stylistic Issues",
description: "Require or disallow newline at the end of files",
recommended: false,
url: "https://eslint.org/docs/rules/eol-last"
},
@ -86,10 +86,15 @@ module.exports = {
});
} else if (mode === "never" && endsWithNewline) {
const secondLastLine = sourceCode.lines[sourceCode.lines.length - 2];
// File is newline-terminated, but shouldn't be
context.report({
node,
loc: location,
loc: {
start: { line: sourceCode.lines.length - 1, column: secondLastLine.length },
end: { line: sourceCode.lines.length, column: 0 }
},
messageId: "unexpected",
fix(fixer) {
const finalEOLs = /(?:\r?\n)+$/u,

View file

@ -15,13 +15,13 @@ const astUtils = require("./utils/ast-utils");
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "suggestion",
docs: {
description: "require the use of `===` and `!==`",
category: "Best Practices",
description: "Require the use of `===` and `!==`",
recommended: false,
url: "https://eslint.org/docs/rules/eqeqeq"
},
@ -78,7 +78,7 @@ module.exports = {
/**
* Checks if an expression is a typeof expression
* @param {ASTNode} node The node to check
* @param {ASTNode} node The node to check
* @returns {boolean} if the node is a typeof expression
*/
function isTypeOf(node) {

View file

@ -9,13 +9,13 @@
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "problem",
docs: {
description: "enforce \"for\" loop update clause moving the counter in the right direction.",
category: "Possible Errors",
description: "Enforce \"for\" loop update clause moving the counter in the right direction",
recommended: true,
url: "https://eslint.org/docs/rules/for-direction"
},

View file

@ -15,13 +15,13 @@ const astUtils = require("./utils/ast-utils");
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "layout",
docs: {
description: "require or disallow spacing between function identifiers and their invocations",
category: "Stylistic Issues",
description: "Require or disallow spacing between function identifiers and their invocations",
recommended: false,
url: "https://eslint.org/docs/rules/func-call-spacing"
},

View file

@ -44,7 +44,7 @@ function isModuleExports(pattern) {
* @returns {boolean} True if the string is a valid identifier
*/
function isIdentifier(name, ecmaVersion) {
if (ecmaVersion >= 6) {
if (ecmaVersion >= 2015) {
return esutils.keyword.isIdentifierES6(name);
}
return esutils.keyword.isIdentifierES5(name);
@ -68,13 +68,13 @@ const optionsObject = {
additionalProperties: false
};
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "suggestion",
docs: {
description: "require function names to match the name of the variable or property to which they are assigned",
category: "Stylistic Issues",
description: "Require function names to match the name of the variable or property to which they are assigned",
recommended: false,
url: "https://eslint.org/docs/rules/func-name-matching"
},
@ -104,7 +104,7 @@ module.exports = {
const nameMatches = typeof context.options[0] === "string" ? context.options[0] : "always";
const considerPropertyDescriptor = options.considerPropertyDescriptor;
const includeModuleExports = options.includeCommonJSModuleExports;
const ecmaVersion = context.parserOptions && context.parserOptions.ecmaVersion ? context.parserOptions.ecmaVersion : 5;
const ecmaVersion = context.languageOptions.ecmaVersion;
/**
* Check whether node is a certain CallExpression.
@ -196,21 +196,25 @@ module.exports = {
const isProp = node.left.type === "MemberExpression";
const name = isProp ? astUtils.getStaticPropertyName(node.left) : node.left.name;
if (node.right.id && isIdentifier(name) && shouldWarn(name, node.right.id.name)) {
if (node.right.id && name && isIdentifier(name) && shouldWarn(name, node.right.id.name)) {
report(node, name, node.right.id.name, isProp);
}
},
Property(node) {
if (node.value.type !== "FunctionExpression" || !node.value.id || node.computed && !isStringLiteral(node.key)) {
"Property, PropertyDefinition[value]"(node) {
if (!(node.value.type === "FunctionExpression" && node.value.id)) {
return;
}
if (node.key.type === "Identifier") {
if (node.key.type === "Identifier" && !node.computed) {
const functionName = node.value.id.name;
let propertyName = node.key.name;
if (considerPropertyDescriptor && propertyName === "value") {
if (
considerPropertyDescriptor &&
propertyName === "value" &&
node.parent.type === "ObjectExpression"
) {
if (isPropertyCall("Object", "defineProperty", node.parent.parent) || isPropertyCall("Reflect", "defineProperty", node.parent.parent)) {
const property = node.parent.parent.arguments[1];

View file

@ -24,13 +24,13 @@ function isFunctionName(variable) {
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "suggestion",
docs: {
description: "require or disallow named `function` expressions",
category: "Stylistic Issues",
description: "Require or disallow named `function` expressions",
recommended: false,
url: "https://eslint.org/docs/rules/func-names"
},
@ -118,6 +118,7 @@ module.exports = {
return isObjectOrClassMethod(node) ||
(parent.type === "VariableDeclarator" && parent.id.type === "Identifier" && parent.init === node) ||
(parent.type === "Property" && parent.value === node) ||
(parent.type === "PropertyDefinition" && parent.value === node) ||
(parent.type === "AssignmentExpression" && parent.left.type === "Identifier" && parent.right === node) ||
(parent.type === "AssignmentPattern" && parent.left.type === "Identifier" && parent.right === node);
}

View file

@ -8,13 +8,13 @@
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "suggestion",
docs: {
description: "enforce the consistent use of either `function` declarations or expressions",
category: "Stylistic Issues",
description: "Enforce the consistent use of either `function` declarations or expressions",
recommended: false,
url: "https://eslint.org/docs/rules/func-style"
},

View file

@ -9,13 +9,13 @@
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "layout",
docs: {
description: "enforce line breaks between arguments of a function call",
category: "Stylistic Issues",
description: "Enforce line breaks between arguments of a function call",
recommended: false,
url: "https://eslint.org/docs/rules/function-call-argument-newline"
},

View file

@ -14,13 +14,13 @@ const astUtils = require("./utils/ast-utils");
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "layout",
docs: {
description: "enforce consistent line breaks inside function parentheses",
category: "Stylistic Issues",
description: "Enforce consistent line breaks inside function parentheses",
recommended: false,
url: "https://eslint.org/docs/rules/function-paren-newline"
},
@ -183,6 +183,7 @@ module.exports = {
/**
* Gets the left paren and right paren tokens of a node.
* @param {ASTNode} node The node with parens
* @throws {TypeError} Unexpected node type.
* @returns {Object} An object with keys `leftParen` for the left paren token, and `rightParen` for the right paren token.
* Can also return `null` if an expression has no parens (e.g. a NewExpression with no arguments, or an ArrowFunctionExpression
* with a single parameter)
@ -190,10 +191,13 @@ module.exports = {
function getParenTokens(node) {
switch (node.type) {
case "NewExpression":
if (!node.arguments.length && !(
astUtils.isOpeningParenToken(sourceCode.getLastToken(node, { skip: 1 })) &&
astUtils.isClosingParenToken(sourceCode.getLastToken(node))
)) {
if (!node.arguments.length &&
!(
astUtils.isOpeningParenToken(sourceCode.getLastToken(node, { skip: 1 })) &&
astUtils.isClosingParenToken(sourceCode.getLastToken(node)) &&
node.callee.range[1] < node.range[1]
)
) {
// If the NewExpression does not have parens (e.g. `new Foo`), return null.
return null;
@ -226,9 +230,13 @@ module.exports = {
return null;
}
const rightParen = node.params.length
? sourceCode.getTokenAfter(node.params[node.params.length - 1], astUtils.isClosingParenToken)
: sourceCode.getTokenAfter(firstToken);
return {
leftParen: firstToken,
rightParen: sourceCode.getTokenBefore(node.body, astUtils.isClosingParenToken)
rightParen
};
}

View file

@ -25,13 +25,13 @@ const OVERRIDE_SCHEMA = {
]
};
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "layout",
docs: {
description: "enforce consistent spacing around `*` operators in generator functions",
category: "ECMAScript 6",
description: "Enforce consistent spacing around `*` operators in generator functions",
recommended: false,
url: "https://eslint.org/docs/rules/generator-star-spacing"
},

View file

@ -29,13 +29,13 @@ function isReachable(segment) {
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "problem",
docs: {
description: "enforce `return` statements in getters",
category: "Possible Errors",
description: "Enforce `return` statements in getters",
recommended: true,
url: "https://eslint.org/docs/rules/getter-return"
},
@ -112,18 +112,24 @@ module.exports = {
}
if (parent.type === "Property" && astUtils.getStaticPropertyName(parent) === "get" && parent.parent.type === "ObjectExpression") {
// Object.defineProperty()
if (parent.parent.parent.type === "CallExpression" &&
astUtils.getStaticPropertyName(parent.parent.parent.callee) === "defineProperty") {
return true;
// Object.defineProperty() or Reflect.defineProperty()
if (parent.parent.parent.type === "CallExpression") {
const callNode = parent.parent.parent.callee;
if (astUtils.isSpecificMemberAccess(callNode, "Object", "defineProperty") ||
astUtils.isSpecificMemberAccess(callNode, "Reflect", "defineProperty")) {
return true;
}
}
// Object.defineProperties()
// Object.defineProperties() or Object.create()
if (parent.parent.parent.type === "Property" &&
parent.parent.parent.parent.type === "ObjectExpression" &&
parent.parent.parent.parent.parent.type === "CallExpression" &&
astUtils.getStaticPropertyName(parent.parent.parent.parent.parent.callee) === "defineProperties") {
return true;
parent.parent.parent.parent.parent.type === "CallExpression") {
const callNode = parent.parent.parent.parent.parent.callee;
return astUtils.isSpecificMemberAccess(callNode, "Object", "defineProperties") ||
astUtils.isSpecificMemberAccess(callNode, "Object", "create");
}
}
}

View file

@ -1,11 +1,12 @@
/**
* @fileoverview Rule for disallowing require() outside of the top-level module context
* @author Jamund Ferguson
* @deprecated in ESLint v7.0.0
*/
"use strict";
const ACCEPTABLE_PARENTS = [
const ACCEPTABLE_PARENTS = new Set([
"AssignmentExpression",
"VariableDeclarator",
"MemberExpression",
@ -15,7 +16,7 @@ const ACCEPTABLE_PARENTS = [
"Program",
"VariableDeclaration",
"ChainExpression"
];
]);
/**
* Finds the eslint-scope reference in the given scope.
@ -27,10 +28,11 @@ function findReference(scope, node) {
const references = scope.references.filter(reference => reference.identifier.range[0] === node.range[0] &&
reference.identifier.range[1] === node.range[1]);
/* istanbul ignore else: correctly returns null */
if (references.length === 1) {
return references[0];
}
/* c8 ignore next */
return null;
}
@ -47,6 +49,7 @@ function isShadowed(scope, node) {
return reference && reference.resolved && reference.resolved.defs.length > 0;
}
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
deprecated: true,
@ -56,8 +59,7 @@ module.exports = {
type: "suggestion",
docs: {
description: "require `require()` calls to be placed at top-level module scope",
category: "Node.js and CommonJS",
description: "Require `require()` calls to be placed at top-level module scope",
recommended: false,
url: "https://eslint.org/docs/rules/global-require"
},
@ -74,7 +76,7 @@ module.exports = {
const currentScope = context.getScope();
if (node.callee.name === "require" && !isShadowed(currentScope, node.callee)) {
const isGoodRequire = context.getAncestors().every(parent => ACCEPTABLE_PARENTS.indexOf(parent.type) > -1);
const isGoodRequire = context.getAncestors().every(parent => ACCEPTABLE_PARENTS.has(parent.type));
if (!isGoodRequire) {
context.report({ node, messageId: "unexpected" });

View file

@ -90,13 +90,13 @@ function isAccessorKind(node) {
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "suggestion",
docs: {
description: "require grouped accessor pairs in object literals and classes",
category: "Best Practices",
description: "Require grouped accessor pairs in object literals and classes",
recommended: false,
url: "https://eslint.org/docs/rules/grouped-accessor-pairs"
},

View file

@ -9,13 +9,13 @@
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "suggestion",
docs: {
description: "require `for-in` loops to include an `if` statement",
category: "Best Practices",
description: "Require `for-in` loops to include an `if` statement",
recommended: false,
url: "https://eslint.org/docs/rules/guard-for-in"
},

View file

@ -1,6 +1,7 @@
/**
* @fileoverview Ensure handling of errors when we know they exist.
* @author Jamund Ferguson
* @deprecated in ESLint v7.0.0
*/
"use strict";
@ -9,6 +10,7 @@
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
deprecated: true,
@ -18,8 +20,7 @@ module.exports = {
type: "suggestion",
docs: {
description: "require error handling in callbacks",
category: "Node.js and CommonJS",
description: "Require error handling in callbacks",
recommended: false,
url: "https://eslint.org/docs/rules/handle-callback-err"
},

View file

@ -2,6 +2,7 @@
* @fileoverview Rule that warns when identifier names that are
* specified in the configuration are used.
* @author Keith Cirkel (http://keithcirkel.co.uk)
* @deprecated in ESLint v7.5.0
*/
"use strict";
@ -109,6 +110,7 @@ function isShorthandPropertyDefinition(node) {
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
deprecated: true,
@ -117,8 +119,7 @@ module.exports = {
type: "suggestion",
docs: {
description: "disallow specified identifiers",
category: "Stylistic Issues",
description: "Disallow specified identifiers",
recommended: false,
url: "https://eslint.org/docs/rules/id-blacklist"
},
@ -205,7 +206,17 @@ module.exports = {
* @private
*/
function report(node) {
if (!reportedNodes.has(node)) {
/*
* We used the range instead of the node because it's possible
* for the same identifier to be represented by two different
* nodes, with the most clear example being shorthand properties:
* { foo }
* In this case, "foo" is represented by one node for the name
* and one for the value. The only way to know they are the same
* is to look at the range.
*/
if (!reportedNodes.has(node.range.toString())) {
context.report({
node,
messageId: "restricted",
@ -213,8 +224,9 @@ module.exports = {
name: node.name
}
});
reportedNodes.add(node);
reportedNodes.add(node.range.toString());
}
}
return {

View file

@ -69,14 +69,14 @@ function isRenamedImport(node) {
}
/**
* Checks whether the given node is a renamed identifier node in an ObjectPattern destructuring.
* Checks whether the given node is an ObjectPattern destructuring.
*
* Examples:
* const { a : b } = foo; // node `a` is renamed node.
* const { a : b } = foo;
* @param {ASTNode} node `Identifier` node to check.
* @returns {boolean} `true` if the node is a renamed node in an ObjectPattern destructuring.
* @returns {boolean} `true` if the node is in an ObjectPattern destructuring.
*/
function isRenamedInDestructuring(node) {
function isPropertyNameInDestructuring(node) {
const parent = node.parent;
return (
@ -84,38 +84,22 @@ function isRenamedInDestructuring(node) {
!parent.computed &&
parent.type === "Property" &&
parent.parent.type === "ObjectPattern" &&
parent.value !== node &&
parent.key === node
)
);
}
/**
* Checks whether the given node represents shorthand definition of a property in an object literal.
* @param {ASTNode} node `Identifier` node to check.
* @returns {boolean} `true` if the node is a shorthand property definition.
*/
function isShorthandPropertyDefinition(node) {
const parent = node.parent;
return (
parent.type === "Property" &&
parent.parent.type === "ObjectExpression" &&
parent.shorthand
);
}
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "suggestion",
docs: {
description: "disallow specified identifiers",
category: "Stylistic Issues",
description: "Disallow specified identifiers",
recommended: false,
url: "https://eslint.org/docs/rules/id-denylist"
},
@ -128,7 +112,8 @@ module.exports = {
uniqueItems: true
},
messages: {
restricted: "Identifier '{{name}}' is restricted."
restricted: "Identifier '{{name}}' is restricted.",
restrictedPrivate: "Identifier '#{{name}}' is restricted."
}
},
@ -187,11 +172,8 @@ module.exports = {
parent.type !== "CallExpression" &&
parent.type !== "NewExpression" &&
!isRenamedImport(node) &&
!isRenamedInDestructuring(node) &&
!(
isReferenceToGlobalVariable(node) &&
!isShorthandPropertyDefinition(node)
)
!isPropertyNameInDestructuring(node) &&
!isReferenceToGlobalVariable(node)
);
}
@ -202,15 +184,27 @@ module.exports = {
* @private
*/
function report(node) {
if (!reportedNodes.has(node)) {
/*
* We used the range instead of the node because it's possible
* for the same identifier to be represented by two different
* nodes, with the most clear example being shorthand properties:
* { foo }
* In this case, "foo" is represented by one node for the name
* and one for the value. The only way to know they are the same
* is to look at the range.
*/
if (!reportedNodes.has(node.range.toString())) {
const isPrivate = node.type === "PrivateIdentifier";
context.report({
node,
messageId: "restricted",
messageId: isPrivate ? "restrictedPrivate" : "restricted",
data: {
name: node.name
}
});
reportedNodes.add(node);
reportedNodes.add(node.range.toString());
}
}
@ -220,7 +214,10 @@ module.exports = {
globalScope = context.getScope();
},
Identifier(node) {
[[
"Identifier",
"PrivateIdentifier"
]](node) {
if (isRestricted(node.name) && shouldCheck(node)) {
report(node);
}

View file

@ -6,17 +6,56 @@
"use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const GraphemeSplitter = require("grapheme-splitter");
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
/**
* Checks if the string given as argument is ASCII or not.
* @param {string} value A string that you want to know if it is ASCII or not.
* @returns {boolean} `true` if `value` is ASCII string.
*/
function isASCII(value) {
if (typeof value !== "string") {
return false;
}
return /^[\u0020-\u007f]*$/u.test(value);
}
/** @type {GraphemeSplitter | undefined} */
let splitter;
/**
* Gets the length of the string. If the string is not in ASCII, counts graphemes.
* @param {string} value A string that you want to get the length.
* @returns {number} The length of `value`.
*/
function getStringLength(value) {
if (isASCII(value)) {
return value.length;
}
if (!splitter) {
splitter = new GraphemeSplitter();
}
return splitter.countGraphemes(value);
}
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "suggestion",
docs: {
description: "enforce minimum and maximum identifier lengths",
category: "Stylistic Issues",
description: "Enforce minimum and maximum identifier lengths",
recommended: false,
url: "https://eslint.org/docs/rules/id-length"
},
@ -55,7 +94,9 @@ module.exports = {
],
messages: {
tooShort: "Identifier name '{{name}}' is too short (< {{min}}).",
tooLong: "Identifier name '{{name}}' is too long (> {{max}})."
tooShortPrivate: "Identifier name '#{{name}}' is too short (< {{min}}).",
tooLong: "Identifier name '{{name}}' is too long (> {{max}}).",
tooLongPrivate: "Identifier name #'{{name}}' is too long (> {{max}})."
}
},
@ -66,7 +107,7 @@ module.exports = {
const properties = options.properties !== "never";
const exceptions = new Set(options.exceptions);
const exceptionPatterns = (options.exceptionPatterns || []).map(pattern => new RegExp(pattern, "u"));
const reportedNode = new Set();
const reportedNodes = new Set();
/**
* Checks if a string matches the provided exception patterns
@ -99,12 +140,14 @@ module.exports = {
Property(parent, node) {
if (parent.parent.type === "ObjectPattern") {
const isKeyAndValueSame = parent.value.name === parent.key.name;
return (
parent.value !== parent.key && parent.value === node ||
parent.value === parent.key && parent.key === node && properties
!isKeyAndValueSame && parent.value === node ||
isKeyAndValueSame && parent.key === node && properties
);
}
return properties && !parent.computed && parent.key === node;
return properties && !parent.computed && parent.key.name === node.name;
},
ImportDefaultSpecifier: true,
RestElement: true,
@ -113,17 +156,23 @@ module.exports = {
ClassDeclaration: true,
FunctionDeclaration: true,
MethodDefinition: true,
PropertyDefinition: true,
CatchClause: true,
ArrayPattern: true
};
return {
Identifier(node) {
[[
"Identifier",
"PrivateIdentifier"
]](node) {
const name = node.name;
const parent = node.parent;
const isShort = name.length < minLength;
const isLong = name.length > maxLength;
const nameLength = getStringLength(name);
const isShort = nameLength < minLength;
const isLong = nameLength > maxLength;
if (!(isShort || isLong) || exceptions.has(name) || matchesExceptionPattern(name)) {
return; // Nothing to report
@ -131,11 +180,27 @@ module.exports = {
const isValidExpression = SUPPORTED_EXPRESSIONS[parent.type];
if (isValidExpression && !reportedNode.has(node) && (isValidExpression === true || isValidExpression(parent, node))) {
reportedNode.add(node);
/*
* We used the range instead of the node because it's possible
* for the same identifier to be represented by two different
* nodes, with the most clear example being shorthand properties:
* { foo }
* In this case, "foo" is represented by one node for the name
* and one for the value. The only way to know they are the same
* is to look at the range.
*/
if (isValidExpression && !reportedNodes.has(node.range.toString()) && (isValidExpression === true || isValidExpression(parent, node))) {
reportedNodes.add(node.range.toString());
let messageId = isShort ? "tooShort" : "tooLong";
if (node.type === "PrivateIdentifier") {
messageId += "Private";
}
context.report({
node,
messageId: isShort ? "tooShort" : "tooLong",
messageId,
data: { name, min: minLength, max: maxLength }
});
}

View file

@ -9,13 +9,13 @@
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "suggestion",
docs: {
description: "require identifiers to match a specified regular expression",
category: "Stylistic Issues",
description: "Require identifiers to match a specified regular expression",
recommended: false,
url: "https://eslint.org/docs/rules/id-match"
},
@ -31,6 +31,10 @@ module.exports = {
type: "boolean",
default: false
},
classFields: {
type: "boolean",
default: false
},
onlyDeclarations: {
type: "boolean",
default: false
@ -44,7 +48,8 @@ module.exports = {
}
],
messages: {
notMatch: "Identifier '{{name}}' does not match the pattern '{{pattern}}'."
notMatch: "Identifier '{{name}}' does not match the pattern '{{pattern}}'.",
notMatchPrivate: "Identifier '#{{name}}' does not match the pattern '{{pattern}}'."
}
},
@ -57,20 +62,36 @@ module.exports = {
regexp = new RegExp(pattern, "u");
const options = context.options[1] || {},
properties = !!options.properties,
checkProperties = !!options.properties,
checkClassFields = !!options.classFields,
onlyDeclarations = !!options.onlyDeclarations,
ignoreDestructuring = !!options.ignoreDestructuring;
let globalScope;
//--------------------------------------------------------------------------
// Helpers
//--------------------------------------------------------------------------
// contains reported nodes to avoid reporting twice on destructuring with shorthand notation
const reported = new Map();
const reportedNodes = new Set();
const ALLOWED_PARENT_TYPES = new Set(["CallExpression", "NewExpression"]);
const DECLARATION_TYPES = new Set(["FunctionDeclaration", "VariableDeclarator"]);
const IMPORT_TYPES = new Set(["ImportSpecifier", "ImportNamespaceSpecifier", "ImportDefaultSpecifier"]);
/**
* Checks whether the given node represents a reference to a global variable that is not declared in the source code.
* These identifiers will be allowed, as it is assumed that user has no control over the names of external global variables.
* @param {ASTNode} node `Identifier` node to check.
* @returns {boolean} `true` if the node is a reference to a global variable.
*/
function isReferenceToGlobalVariable(node) {
const variable = globalScope.set.get(node.name);
return variable && variable.defs.length === 0 &&
variable.references.some(ref => ref.identifier === node);
}
/**
* Checks if a string matches the provided pattern
* @param {string} name The string to check.
@ -120,29 +141,51 @@ module.exports = {
* @private
*/
function report(node) {
if (!reported.has(node)) {
/*
* We used the range instead of the node because it's possible
* for the same identifier to be represented by two different
* nodes, with the most clear example being shorthand properties:
* { foo }
* In this case, "foo" is represented by one node for the name
* and one for the value. The only way to know they are the same
* is to look at the range.
*/
if (!reportedNodes.has(node.range.toString())) {
const messageId = (node.type === "PrivateIdentifier")
? "notMatchPrivate" : "notMatch";
context.report({
node,
messageId: "notMatch",
messageId,
data: {
name: node.name,
pattern
}
});
reported.set(node, true);
reportedNodes.add(node.range.toString());
}
}
return {
Program() {
globalScope = context.getScope();
},
Identifier(node) {
const name = node.name,
parent = node.parent,
effectiveParent = (parent.type === "MemberExpression") ? parent.parent : parent;
if (isReferenceToGlobalVariable(node)) {
return;
}
if (parent.type === "MemberExpression") {
if (!properties) {
if (!checkProperties) {
return;
}
@ -168,6 +211,17 @@ module.exports = {
}
}
// For https://github.com/eslint/eslint/issues/15123
} else if (
parent.type === "Property" &&
parent.parent.type === "ObjectExpression" &&
parent.key === node &&
!parent.computed
) {
if (checkProperties && isInvalid(name)) {
report(node);
}
/*
* Properties have their own rules, and
* AssignmentPattern nodes can be treated like Properties:
@ -176,8 +230,7 @@ module.exports = {
} else if (parent.type === "Property" || parent.type === "AssignmentPattern") {
if (parent.parent && parent.parent.type === "ObjectPattern") {
if (parent.shorthand && parent.value.left && isInvalid(name)) {
if (!ignoreDestructuring && parent.shorthand && parent.value.left && isInvalid(name)) {
report(node);
}
@ -197,7 +250,7 @@ module.exports = {
}
// never check properties or always ignore destructuring
if (!properties || (ignoreDestructuring && isInsideObjectPattern(node))) {
if ((!checkProperties && !parent.computed) || (ignoreDestructuring && isInsideObjectPattern(node))) {
return;
}
@ -214,10 +267,29 @@ module.exports = {
report(node);
}
} else if (parent.type === "PropertyDefinition") {
if (checkClassFields && isInvalid(name)) {
report(node);
}
// Report anything that is invalid that isn't a CallExpression
} else if (shouldReport(effectiveParent, name)) {
report(node);
}
},
"PrivateIdentifier"(node) {
const isClassField = node.parent.type === "PropertyDefinition";
if (isClassField && !checkClassFields) {
return;
}
if (isInvalid(node.name)) {
report(node);
}
}
};

View file

@ -9,13 +9,13 @@ const { isCommentToken, isNotOpeningParenToken } = require("./utils/ast-utils");
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "layout",
docs: {
description: "enforce the location of arrow function bodies",
category: "Stylistic Issues",
description: "Enforce the location of arrow function bodies",
recommended: false,
url: "https://eslint.org/docs/rules/implicit-arrow-linebreak"
},

View file

@ -4,6 +4,7 @@
* This rule has been ported and modified from nodeca.
* @author Vitaly Puzrin
* @author Gyandeep Singh
* @deprecated in ESLint v4.0.0
*/
"use strict";
@ -17,15 +18,15 @@ const astUtils = require("./utils/ast-utils");
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
/* istanbul ignore next: this rule has known coverage issues, but it's deprecated and shouldn't be updated in the future anyway. */
// this rule has known coverage issues, but it's deprecated and shouldn't be updated in the future anyway.
/* c8 ignore next */
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "layout",
docs: {
description: "enforce consistent indentation",
category: "Stylistic Issues",
description: "Enforce consistent indentation",
recommended: false,
url: "https://eslint.org/docs/rules/indent-legacy"
},
@ -211,10 +212,10 @@ module.exports = {
if (context.options[0] === "tab") {
indentSize = 1;
indentType = "tab";
} else /* istanbul ignore else : this will be caught by options validation */ if (typeof context.options[0] === "number") {
} else /* c8 ignore start */ if (typeof context.options[0] === "number") {
indentSize = context.options[0];
indentType = "space";
}
}/* c8 ignore stop */
if (context.options[1]) {
const opts = context.options[1];
@ -752,7 +753,7 @@ module.exports = {
if (typeof options.CallExpression.arguments === "number") {
nodeIndent += options.CallExpression.arguments * indentSize;
} else if (options.CallExpression.arguments === "first") {
if (parent.arguments.indexOf(node) !== -1) {
if (parent.arguments.includes(node)) {
nodeIndent = parent.arguments[0].loc.start.column;
}
} else {
@ -839,7 +840,7 @@ module.exports = {
"IfStatement", "WhileStatement", "ForStatement", "ForInStatement", "ForOfStatement", "DoWhileStatement", "ClassDeclaration", "TryStatement"
];
if (node.parent && statementsWithProperties.indexOf(node.parent.type) !== -1 && isNodeBodyBlock(node)) {
if (node.parent && statementsWithProperties.includes(node.parent.type) && isNodeBodyBlock(node)) {
indent = getNodeIndent(node.parent).goodChar;
} else if (node.parent && node.parent.type === "CatchClause") {
indent = getNodeIndent(node.parent.parent).goodChar;

View file

@ -12,7 +12,7 @@
// Requirements
//------------------------------------------------------------------------------
const createTree = require("functional-red-black-tree");
const { OrderedMap } = require("js-sdsl");
const astUtils = require("./utils/ast-utils");
@ -60,12 +60,15 @@ const KNOWN_NODES = new Set([
"NewExpression",
"ObjectExpression",
"ObjectPattern",
"PrivateIdentifier",
"Program",
"Property",
"PropertyDefinition",
"RestElement",
"ReturnStatement",
"SequenceExpression",
"SpreadElement",
"StaticBlock",
"Super",
"SwitchCase",
"SwitchStatement",
@ -132,23 +135,18 @@ class BinarySearchTree {
* Creates an empty tree
*/
constructor() {
this._rbTree = createTree();
this._orderedMap = new OrderedMap();
this._orderedMapEnd = this._orderedMap.end();
}
/**
* Inserts an entry into the tree.
* @param {number} key The entry's key
* @param {*} value The entry's value
* @param {any} value The entry's value
* @returns {void}
*/
insert(key, value) {
const iterator = this._rbTree.find(key);
if (iterator.valid) {
this._rbTree = iterator.update(value);
} else {
this._rbTree = this._rbTree.insert(key, value);
}
this._orderedMap.setElement(key, value);
}
/**
@ -157,9 +155,13 @@ class BinarySearchTree {
* @returns {{key: number, value: *}|null} The found entry, or null if no such entry exists.
*/
findLe(key) {
const iterator = this._rbTree.le(key);
const iterator = this._orderedMap.reverseLowerBound(key);
return iterator && { key: iterator.key, value: iterator.value };
if (iterator.equals(this._orderedMapEnd)) {
return {};
}
return { key: iterator.pointer[0], value: iterator.pointer[1] };
}
/**
@ -174,11 +176,20 @@ class BinarySearchTree {
if (start === end) {
return;
}
const iterator = this._rbTree.ge(start);
const iterator = this._orderedMap.lowerBound(start);
while (iterator.valid && iterator.key < end) {
this._rbTree = this._rbTree.remove(iterator.key);
iterator.next();
if (iterator.equals(this._orderedMapEnd)) {
return;
}
if (end > this._orderedMap.back()[0]) {
while (!iterator.equals(this._orderedMapEnd)) {
this._orderedMap.eraseElementByIterator(iterator);
}
} else {
while (iterator.pointer[0] < end) {
this._orderedMap.eraseElementByIterator(iterator);
}
}
}
}
@ -188,7 +199,6 @@ class BinarySearchTree {
*/
class TokenInfo {
// eslint-disable-next-line jsdoc/require-description
/**
* @param {SourceCode} sourceCode A SourceCode object
*/
@ -238,7 +248,6 @@ class TokenInfo {
*/
class OffsetStorage {
// eslint-disable-next-line jsdoc/require-description
/**
* @param {TokenInfo} tokenInfo a TokenInfo instance
* @param {number} indentSize The desired size of each indentation level
@ -263,7 +272,7 @@ class OffsetStorage {
/**
* Sets the offset column of token B to match the offset column of token A.
* **WARNING**: This matches a *column*, even if baseToken is not the first token on its line. In
* - **WARNING**: This matches a *column*, even if baseToken is not the first token on its line. In
* most cases, `setDesiredOffset` should be used instead.
* @param {Token} baseToken The first token
* @param {Token} offsetToken The second token, whose offset should be matched to the first token
@ -352,11 +361,11 @@ class OffsetStorage {
* Instead, the offset tree is represented as a collection of contiguous offset ranges in a file. For example, the following
* list could represent the state of the offset tree at a given point:
*
* * Tokens starting in the interval [0, 15) are aligned with the beginning of the file
* * Tokens starting in the interval [15, 30) are offset by 1 indent level from the `bar` token
* * Tokens starting in the interval [30, 43) are offset by 1 indent level from the `foo` token
* * Tokens starting in the interval [43, 820) are offset by 2 indent levels from the `bar` token
* * Tokens starting in the interval [820, ) are offset by 1 indent level from the `baz` token
* - Tokens starting in the interval [0, 15) are aligned with the beginning of the file
* - Tokens starting in the interval [15, 30) are offset by 1 indent level from the `bar` token
* - Tokens starting in the interval [30, 43) are offset by 1 indent level from the `foo` token
* - Tokens starting in the interval [43, 820) are offset by 2 indent levels from the `bar` token
* - Tokens starting in the interval [820, ) are offset by 1 indent level from the `baz` token
*
* The `setDesiredOffsets` methods inserts ranges like the ones above. The third line above would be inserted by using:
* `setDesiredOffsets([30, 43], fooToken, 1);`
@ -493,13 +502,13 @@ const ELEMENT_LIST_SCHEMA = {
]
};
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "layout",
docs: {
description: "enforce consistent indentation",
category: "Stylistic Issues",
description: "Enforce consistent indentation",
recommended: false,
url: "https://eslint.org/docs/rules/indent"
},
@ -584,6 +593,16 @@ module.exports = {
},
additionalProperties: false
},
StaticBlock: {
type: "object",
properties: {
body: {
type: "integer",
minimum: 0
}
},
additionalProperties: false
},
CallExpression: {
type: "object",
properties: {
@ -647,6 +666,9 @@ module.exports = {
parameters: DEFAULT_PARAMETER_INDENT,
body: DEFAULT_FUNCTION_BODY_INDENT
},
StaticBlock: {
body: DEFAULT_FUNCTION_BODY_INDENT
},
CallExpression: {
arguments: DEFAULT_PARAMETER_INDENT
},
@ -782,7 +804,7 @@ module.exports = {
let statement = node.parent && node.parent.parent;
while (
statement.type === "UnaryExpression" && ["!", "~", "+", "-"].indexOf(statement.operator) > -1 ||
statement.type === "UnaryExpression" && ["!", "~", "+", "-"].includes(statement.operator) ||
statement.type === "AssignmentExpression" ||
statement.type === "LogicalExpression" ||
statement.type === "SequenceExpression" ||
@ -902,18 +924,6 @@ module.exports = {
}
offsets.setDesiredOffsets([firstBodyToken.range[0], lastBodyToken.range[1]], lastParentToken, 1);
/*
* For blockless nodes with semicolon-first style, don't indent the semicolon.
* e.g.
* if (foo) bar()
* ; [1, 2, 3].map(foo)
*/
const lastToken = sourceCode.getLastToken(node);
if (node.type !== "EmptyStatement" && astUtils.isSemicolonToken(lastToken)) {
offsets.setDesiredOffset(lastToken, lastParentToken, 0);
}
}
}
@ -1209,7 +1219,7 @@ module.exports = {
}
},
"DoWhileStatement, WhileStatement, ForInStatement, ForOfStatement": node => addBlocklessNodeIndent(node.body),
"DoWhileStatement, WhileStatement, ForInStatement, ForOfStatement, WithStatement": node => addBlocklessNodeIndent(node.body),
ExportNamedDeclaration(node) {
if (node.declaration === null) {
@ -1257,6 +1267,50 @@ module.exports = {
}
},
/*
* For blockless nodes with semicolon-first style, don't indent the semicolon.
* e.g.
* if (foo)
* bar()
* ; [1, 2, 3].map(foo)
*
* Traversal into the node sets indentation of the semicolon, so we need to override it on exit.
*/
":matches(DoWhileStatement, ForStatement, ForInStatement, ForOfStatement, IfStatement, WhileStatement, WithStatement):exit"(node) {
let nodesToCheck;
if (node.type === "IfStatement") {
nodesToCheck = [node.consequent];
if (node.alternate) {
nodesToCheck.push(node.alternate);
}
} else {
nodesToCheck = [node.body];
}
for (const nodeToCheck of nodesToCheck) {
const lastToken = sourceCode.getLastToken(nodeToCheck);
if (astUtils.isSemicolonToken(lastToken)) {
const tokenBeforeLast = sourceCode.getTokenBefore(lastToken);
const tokenAfterLast = sourceCode.getTokenAfter(lastToken);
// override indentation of `;` only if its line looks like a semicolon-first style line
if (
!astUtils.isTokenOnSameLine(tokenBeforeLast, lastToken) &&
tokenAfterLast &&
astUtils.isTokenOnSameLine(lastToken, tokenAfterLast)
) {
offsets.setDesiredOffset(
lastToken,
sourceCode.getFirstToken(node),
0
);
}
}
}
},
ImportDeclaration(node) {
if (node.specifiers.some(specifier => specifier.type === "ImportSpecifier")) {
const openingCurly = sourceCode.getFirstToken(node, astUtils.isOpeningBraceToken);
@ -1359,6 +1413,52 @@ module.exports = {
}
},
PropertyDefinition(node) {
const firstToken = sourceCode.getFirstToken(node);
const maybeSemicolonToken = sourceCode.getLastToken(node);
let keyLastToken = null;
// Indent key.
if (node.computed) {
const bracketTokenL = sourceCode.getTokenBefore(node.key, astUtils.isOpeningBracketToken);
const bracketTokenR = keyLastToken = sourceCode.getTokenAfter(node.key, astUtils.isClosingBracketToken);
const keyRange = [bracketTokenL.range[1], bracketTokenR.range[0]];
if (bracketTokenL !== firstToken) {
offsets.setDesiredOffset(bracketTokenL, firstToken, 0);
}
offsets.setDesiredOffsets(keyRange, bracketTokenL, 1);
offsets.setDesiredOffset(bracketTokenR, bracketTokenL, 0);
} else {
const idToken = keyLastToken = sourceCode.getFirstToken(node.key);
if (idToken !== firstToken) {
offsets.setDesiredOffset(idToken, firstToken, 1);
}
}
// Indent initializer.
if (node.value) {
const eqToken = sourceCode.getTokenBefore(node.value, astUtils.isEqToken);
const valueToken = sourceCode.getTokenAfter(eqToken);
offsets.setDesiredOffset(eqToken, keyLastToken, 1);
offsets.setDesiredOffset(valueToken, eqToken, 1);
if (astUtils.isSemicolonToken(maybeSemicolonToken)) {
offsets.setDesiredOffset(maybeSemicolonToken, eqToken, 1);
}
} else if (astUtils.isSemicolonToken(maybeSemicolonToken)) {
offsets.setDesiredOffset(maybeSemicolonToken, keyLastToken, 1);
}
},
StaticBlock(node) {
const openingCurly = sourceCode.getFirstToken(node, { skip: 1 }); // skip the `static` token
const closingCurly = sourceCode.getLastToken(node);
addElementListIndent(node.body, openingCurly, closingCurly, options.StaticBlock.body);
},
SwitchStatement(node) {
const openingCurly = sourceCode.getTokenAfter(node.discriminant, astUtils.isOpeningBraceToken);
const closingCurly = sourceCode.getLastToken(node);

View file

@ -6,7 +6,7 @@
"use strict";
/* eslint sort-keys: ["error", "asc"] */
/* eslint sort-keys: ["error", "asc"] -- More readable for long list */
const { LazyLoadingRuleMap } = require("./utils/lazy-loading-rule-map");
@ -72,6 +72,7 @@ module.exports = new LazyLoadingRuleMap(Object.entries({
"lines-around-comment": () => require("./lines-around-comment"),
"lines-around-directive": () => require("./lines-around-directive"),
"lines-between-class-members": () => require("./lines-between-class-members"),
"logical-assignment-operators": () => require("./logical-assignment-operators"),
"max-classes-per-file": () => require("./max-classes-per-file"),
"max-depth": () => require("./max-depth"),
"max-len": () => require("./max-len"),
@ -103,6 +104,7 @@ module.exports = new LazyLoadingRuleMap(Object.entries({
"no-confusing-arrow": () => require("./no-confusing-arrow"),
"no-console": () => require("./no-console"),
"no-const-assign": () => require("./no-const-assign"),
"no-constant-binary-expression": () => require("./no-constant-binary-expression"),
"no-constant-condition": () => require("./no-constant-condition"),
"no-constructor-return": () => require("./no-constructor-return"),
"no-continue": () => require("./no-continue"),
@ -121,6 +123,7 @@ module.exports = new LazyLoadingRuleMap(Object.entries({
"no-empty-character-class": () => require("./no-empty-character-class"),
"no-empty-function": () => require("./no-empty-function"),
"no-empty-pattern": () => require("./no-empty-pattern"),
"no-empty-static-block": () => require("./no-empty-static-block"),
"no-eq-null": () => require("./no-eq-null"),
"no-eval": () => require("./no-eval"),
"no-ex-assign": () => require("./no-ex-assign"),
@ -165,6 +168,7 @@ module.exports = new LazyLoadingRuleMap(Object.entries({
"no-nested-ternary": () => require("./no-nested-ternary"),
"no-new": () => require("./no-new"),
"no-new-func": () => require("./no-new-func"),
"no-new-native-nonconstructor": () => require("./no-new-native-nonconstructor"),
"no-new-object": () => require("./no-new-object"),
"no-new-require": () => require("./no-new-require"),
"no-new-symbol": () => require("./no-new-symbol"),
@ -221,6 +225,7 @@ module.exports = new LazyLoadingRuleMap(Object.entries({
"no-unsafe-optional-chaining": () => require("./no-unsafe-optional-chaining"),
"no-unused-expressions": () => require("./no-unused-expressions"),
"no-unused-labels": () => require("./no-unused-labels"),
"no-unused-private-class-members": () => require("./no-unused-private-class-members"),
"no-unused-vars": () => require("./no-unused-vars"),
"no-use-before-define": () => require("./no-use-before-define"),
"no-useless-backreference": () => require("./no-useless-backreference"),
@ -254,6 +259,7 @@ module.exports = new LazyLoadingRuleMap(Object.entries({
"prefer-exponentiation-operator": () => require("./prefer-exponentiation-operator"),
"prefer-named-capture-group": () => require("./prefer-named-capture-group"),
"prefer-numeric-literals": () => require("./prefer-numeric-literals"),
"prefer-object-has-own": () => require("./prefer-object-has-own"),
"prefer-object-spread": () => require("./prefer-object-spread"),
"prefer-promise-reject-errors": () => require("./prefer-promise-reject-errors"),
"prefer-reflect": () => require("./prefer-reflect"),

View file

@ -42,13 +42,13 @@ function isInitialized(node) {
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "suggestion",
docs: {
description: "require or disallow initialization in variable declarations",
category: "Variables",
description: "Require or disallow initialization in variable declarations",
recommended: false,
url: "https://eslint.org/docs/rules/init-declarations"
},

View file

@ -36,13 +36,13 @@ const QUOTE_SETTINGS = {
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "layout",
docs: {
description: "enforce the consistent use of either double or single quotes in JSX attributes",
category: "Stylistic Issues",
description: "Enforce the consistent use of either double or single quotes in JSX attributes",
recommended: false,
url: "https://eslint.org/docs/rules/jsx-quotes"
},
@ -70,7 +70,7 @@ module.exports = {
* @public
*/
function usesExpectedQuotes(node) {
return node.value.indexOf(setting.quote) !== -1 || astUtils.isSurroundedBy(node.raw, setting.quote);
return node.value.includes(setting.quote) || astUtils.isSurroundedBy(node.raw, setting.quote);
}
return {

View file

@ -9,6 +9,9 @@
//------------------------------------------------------------------------------
const astUtils = require("./utils/ast-utils");
const GraphemeSplitter = require("grapheme-splitter");
const splitter = new GraphemeSplitter();
//------------------------------------------------------------------------------
// Helpers
@ -133,13 +136,13 @@ function initOptions(toOptions, fromOptions) {
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "layout",
docs: {
description: "enforce consistent spacing between keys and values in object literal properties",
category: "Stylistic Issues",
description: "Enforce consistent spacing between keys and values in object literal properties",
recommended: false,
url: "https://eslint.org/docs/rules/key-spacing"
},
@ -331,43 +334,6 @@ module.exports = {
const sourceCode = context.getSourceCode();
/**
* Checks whether a property is a member of the property group it follows.
* @param {ASTNode} lastMember The last Property known to be in the group.
* @param {ASTNode} candidate The next Property that might be in the group.
* @returns {boolean} True if the candidate property is part of the group.
*/
function continuesPropertyGroup(lastMember, candidate) {
const groupEndLine = lastMember.loc.start.line,
candidateStartLine = candidate.loc.start.line;
if (candidateStartLine - groupEndLine <= 1) {
return true;
}
/*
* Check that the first comment is adjacent to the end of the group, the
* last comment is adjacent to the candidate property, and that successive
* comments are adjacent to each other.
*/
const leadingComments = sourceCode.getCommentsBefore(candidate);
if (
leadingComments.length &&
leadingComments[0].loc.start.line - groupEndLine <= 1 &&
candidateStartLine - last(leadingComments).loc.end.line <= 1
) {
for (let i = 1; i < leadingComments.length; i++) {
if (leadingComments[i].loc.start.line - leadingComments[i - 1].loc.end.line > 1) {
return false;
}
}
return true;
}
return false;
}
/**
* Determines if the given property is key-value property.
* @param {ASTNode} property Property node to check.
@ -382,19 +348,7 @@ module.exports = {
}
/**
* Starting from the given a node (a property.key node here) looks forward
* until it finds the last token before a colon punctuator and returns it.
* @param {ASTNode} node The node to start looking from.
* @returns {ASTNode} The last token before a colon punctuator.
*/
function getLastTokenBeforeColon(node) {
const colonToken = sourceCode.getTokenAfter(node, astUtils.isColonToken);
return sourceCode.getTokenBefore(colonToken);
}
/**
* Starting from the given a node (a property.key node here) looks forward
* Starting from the given node (a property.key node here) looks forward
* until it finds the colon punctuator and returns it.
* @param {ASTNode} node The node to start looking from.
* @returns {ASTNode} The colon punctuator.
@ -403,6 +357,67 @@ module.exports = {
return sourceCode.getTokenAfter(node, astUtils.isColonToken);
}
/**
* Starting from the given node (a property.key node here) looks forward
* until it finds the last token before a colon punctuator and returns it.
* @param {ASTNode} node The node to start looking from.
* @returns {ASTNode} The last token before a colon punctuator.
*/
function getLastTokenBeforeColon(node) {
const colonToken = getNextColon(node);
return sourceCode.getTokenBefore(colonToken);
}
/**
* Starting from the given node (a property.key node here) looks forward
* until it finds the first token after a colon punctuator and returns it.
* @param {ASTNode} node The node to start looking from.
* @returns {ASTNode} The first token after a colon punctuator.
*/
function getFirstTokenAfterColon(node) {
const colonToken = getNextColon(node);
return sourceCode.getTokenAfter(colonToken);
}
/**
* Checks whether a property is a member of the property group it follows.
* @param {ASTNode} lastMember The last Property known to be in the group.
* @param {ASTNode} candidate The next Property that might be in the group.
* @returns {boolean} True if the candidate property is part of the group.
*/
function continuesPropertyGroup(lastMember, candidate) {
const groupEndLine = lastMember.loc.start.line,
candidateValueStartLine = (isKeyValueProperty(candidate) ? getFirstTokenAfterColon(candidate.key) : candidate).loc.start.line;
if (candidateValueStartLine - groupEndLine <= 1) {
return true;
}
/*
* Check that the first comment is adjacent to the end of the group, the
* last comment is adjacent to the candidate property, and that successive
* comments are adjacent to each other.
*/
const leadingComments = sourceCode.getCommentsBefore(candidate);
if (
leadingComments.length &&
leadingComments[0].loc.start.line - groupEndLine <= 1 &&
candidateValueStartLine - last(leadingComments).loc.end.line <= 1
) {
for (let i = 1; i < leadingComments.length; i++) {
if (leadingComments[i].loc.start.line - leadingComments[i - 1].loc.end.line > 1) {
return false;
}
}
return true;
}
return false;
}
/**
* Gets an object literal property's key as the identifier name or string value.
* @param {ASTNode} property Property node whose key to retrieve.
@ -428,19 +443,7 @@ module.exports = {
* @returns {void}
*/
function report(property, side, whitespace, expected, mode) {
const diff = whitespace.length - expected,
nextColon = getNextColon(property.key),
tokenBeforeColon = sourceCode.getTokenBefore(nextColon, { includeComments: true }),
tokenAfterColon = sourceCode.getTokenAfter(nextColon, { includeComments: true }),
isKeySide = side === "key",
isExtra = diff > 0,
diffAbs = Math.abs(diff),
spaces = Array(diffAbs + 1).join(" ");
const locStart = isKeySide ? tokenBeforeColon.loc.end : nextColon.loc.start;
const locEnd = isKeySide ? nextColon.loc.start : tokenAfterColon.loc.start;
const missingLoc = isKeySide ? tokenBeforeColon.loc : tokenAfterColon.loc;
const loc = isExtra ? { start: locStart, end: locEnd } : missingLoc;
const diff = whitespace.length - expected;
if ((
diff && mode === "strict" ||
@ -448,6 +451,19 @@ module.exports = {
diff > 0 && !expected && mode === "minimum") &&
!(expected && containsLineTerminator(whitespace))
) {
const nextColon = getNextColon(property.key),
tokenBeforeColon = sourceCode.getTokenBefore(nextColon, { includeComments: true }),
tokenAfterColon = sourceCode.getTokenAfter(nextColon, { includeComments: true }),
isKeySide = side === "key",
isExtra = diff > 0,
diffAbs = Math.abs(diff),
spaces = Array(diffAbs + 1).join(" ");
const locStart = isKeySide ? tokenBeforeColon.loc.end : nextColon.loc.start;
const locEnd = isKeySide ? nextColon.loc.start : tokenAfterColon.loc.start;
const missingLoc = isKeySide ? tokenBeforeColon.loc : tokenAfterColon.loc;
const loc = isExtra ? { start: locStart, end: locEnd } : missingLoc;
let fix;
if (isExtra) {
@ -507,7 +523,7 @@ module.exports = {
const startToken = sourceCode.getFirstToken(property);
const endToken = getLastTokenBeforeColon(property.key);
return endToken.range[1] - startToken.range[0];
return splitter.countGraphemes(sourceCode.getText().slice(startToken.range[0], endToken.range[1]));
}
/**
@ -531,8 +547,8 @@ module.exports = {
/**
* Creates groups of properties.
* @param {ASTNode} node ObjectExpression node being evaluated.
* @returns {Array.<ASTNode[]>} Groups of property AST node lists.
* @param {ASTNode} node ObjectExpression node being evaluated.
* @returns {Array<ASTNode[]>} Groups of property AST node lists.
*/
function createGroups(node) {
if (node.properties.length === 1) {
@ -600,7 +616,7 @@ module.exports = {
/**
* Verifies spacing of property conforms to specified options.
* @param {ASTNode} node Property node being evaluated.
* @param {ASTNode} node Property node being evaluated.
* @param {Object} lineOptions Configured singleLine or multiLine options
* @returns {void}
*/
@ -629,7 +645,7 @@ module.exports = {
/**
* Verifies vertical alignment, taking into account groups of properties.
* @param {ASTNode} node ObjectExpression node being evaluated.
* @param {ASTNode} node ObjectExpression node being evaluated.
* @returns {void}
*/
function verifyAlignment(node) {

View file

@ -22,7 +22,7 @@ const PREV_TOKEN_M = /^[)\]}>*]$/u;
const NEXT_TOKEN_M = /^[{*]$/u;
const TEMPLATE_OPEN_PAREN = /\$\{$/u;
const TEMPLATE_CLOSE_PAREN = /^\}/u;
const CHECK_TYPE = /^(?:JSXElement|RegularExpression|String|Template)$/u;
const CHECK_TYPE = /^(?:JSXElement|RegularExpression|String|Template|PrivateIdentifier)$/u;
const KEYS = keywords.concat(["as", "async", "await", "from", "get", "let", "of", "set", "yield"]);
// check duplications.
@ -61,13 +61,13 @@ function isCloseParenOfTemplate(token) {
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "layout",
docs: {
description: "enforce consistent spacing before and after keywords",
category: "Stylistic Issues",
description: "Enforce consistent spacing before and after keywords",
recommended: false,
url: "https://eslint.org/docs/rules/keyword-spacing"
},
@ -110,6 +110,8 @@ module.exports = {
create(context) {
const sourceCode = context.getSourceCode();
const tokensToIgnore = new WeakSet();
/**
* Reports a given token if there are not space(s) before the token.
* @param {Token} token A token to report.
@ -122,6 +124,7 @@ module.exports = {
if (prevToken &&
(CHECK_TYPE.test(prevToken.type) || pattern.test(prevToken.value)) &&
!isOpenParenOfTemplate(prevToken) &&
!tokensToIgnore.has(prevToken) &&
astUtils.isTokenOnSameLine(prevToken, token) &&
!sourceCode.isSpaceBetweenTokens(prevToken, token)
) {
@ -148,6 +151,7 @@ module.exports = {
if (prevToken &&
(CHECK_TYPE.test(prevToken.type) || pattern.test(prevToken.value)) &&
!isOpenParenOfTemplate(prevToken) &&
!tokensToIgnore.has(prevToken) &&
astUtils.isTokenOnSameLine(prevToken, token) &&
sourceCode.isSpaceBetweenTokens(prevToken, token)
) {
@ -174,6 +178,7 @@ module.exports = {
if (nextToken &&
(CHECK_TYPE.test(nextToken.type) || pattern.test(nextToken.value)) &&
!isCloseParenOfTemplate(nextToken) &&
!tokensToIgnore.has(nextToken) &&
astUtils.isTokenOnSameLine(token, nextToken) &&
!sourceCode.isSpaceBetweenTokens(token, nextToken)
) {
@ -200,6 +205,7 @@ module.exports = {
if (nextToken &&
(CHECK_TYPE.test(nextToken.type) || pattern.test(nextToken.value)) &&
!isCloseParenOfTemplate(nextToken) &&
!tokensToIgnore.has(nextToken) &&
astUtils.isTokenOnSameLine(token, nextToken) &&
sourceCode.isSpaceBetweenTokens(token, nextToken)
) {
@ -403,7 +409,15 @@ module.exports = {
*/
function checkSpacingForForInStatement(node) {
checkSpacingAroundFirstToken(node);
checkSpacingAroundTokenBefore(node.right);
const inToken = sourceCode.getTokenBefore(node.right, astUtils.isNotOpeningParenToken);
const previousToken = sourceCode.getTokenBefore(inToken);
if (previousToken.type !== "PrivateIdentifier") {
checkSpacingBefore(inToken);
}
checkSpacingAfter(inToken);
}
/**
@ -419,7 +433,15 @@ module.exports = {
} else {
checkSpacingAroundFirstToken(node);
}
checkSpacingAround(sourceCode.getTokenBefore(node.right, astUtils.isNotOpeningParenToken));
const ofToken = sourceCode.getTokenBefore(node.right, astUtils.isNotOpeningParenToken);
const previousToken = sourceCode.getTokenBefore(ofToken);
if (previousToken.type !== "PrivateIdentifier") {
checkSpacingBefore(ofToken);
}
checkSpacingAfter(ofToken);
}
/**
@ -447,6 +469,7 @@ module.exports = {
const asToken = sourceCode.getTokenBefore(node.exported);
checkSpacingBefore(asToken, PREV_TOKEN_M);
checkSpacingAfter(asToken, NEXT_TOKEN_M);
}
if (node.source) {
@ -457,6 +480,35 @@ module.exports = {
}
}
/**
* Reports `as` keyword of a given node if usage of spacing around this
* keyword is invalid.
* @param {ASTNode} node An `ImportSpecifier` node to check.
* @returns {void}
*/
function checkSpacingForImportSpecifier(node) {
if (node.imported.range[0] !== node.local.range[0]) {
const asToken = sourceCode.getTokenBefore(node.local);
checkSpacingBefore(asToken, PREV_TOKEN_M);
}
}
/**
* Reports `as` keyword of a given node if usage of spacing around this
* keyword is invalid.
* @param {ASTNode} node An `ExportSpecifier` node to check.
* @returns {void}
*/
function checkSpacingForExportSpecifier(node) {
if (node.local.range[0] !== node.exported.range[0]) {
const asToken = sourceCode.getTokenBefore(node.exported);
checkSpacingBefore(asToken, PREV_TOKEN_M);
checkSpacingAfter(asToken, NEXT_TOKEN_M);
}
}
/**
* Reports `as` keyword of a given node if usage of spacing around this
* keyword is invalid.
@ -473,6 +525,7 @@ module.exports = {
* Reports `static`, `get`, and `set` keywords of a given node if usage of
* spacing around those keywords is invalid.
* @param {ASTNode} node A node to report.
* @throws {Error} If unable to find token get, set, or async beside method name.
* @returns {void}
*/
function checkSpacingForProperty(node) {
@ -565,9 +618,20 @@ module.exports = {
YieldExpression: checkSpacingBeforeFirstToken,
// Others
ImportSpecifier: checkSpacingForImportSpecifier,
ExportSpecifier: checkSpacingForExportSpecifier,
ImportNamespaceSpecifier: checkSpacingForImportNamespaceSpecifier,
MethodDefinition: checkSpacingForProperty,
Property: checkSpacingForProperty
PropertyDefinition: checkSpacingForProperty,
StaticBlock: checkSpacingAroundFirstToken,
Property: checkSpacingForProperty,
// To avoid conflicts with `space-infix-ops`, e.g. `a > this.b`
"BinaryExpression[operator='>']"(node) {
const operatorToken = sourceCode.getTokenBefore(node.right, astUtils.isNotOpeningParenToken);
tokensToIgnore.add(operatorToken);
}
};
}
};

View file

@ -10,13 +10,13 @@ const astUtils = require("./utils/ast-utils");
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "layout",
docs: {
description: "enforce position of line comments",
category: "Stylistic Issues",
description: "Enforce position of line comments",
recommended: false,
url: "https://eslint.org/docs/rules/line-comment-position"
},

View file

@ -15,13 +15,13 @@ const astUtils = require("./utils/ast-utils");
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "layout",
docs: {
description: "enforce consistent linebreak style",
category: "Stylistic Issues",
description: "Enforce consistent linebreak style",
recommended: false,
url: "https://eslint.org/docs/rules/linebreak-style"
},

View file

@ -15,7 +15,7 @@ const astUtils = require("./utils/ast-utils");
//------------------------------------------------------------------------------
/**
* Return an array with with any line numbers that are empty.
* Return an array with any line numbers that are empty.
* @param {Array} lines An array of each line of the file.
* @returns {Array} An array of line numbers.
*/
@ -29,7 +29,7 @@ function getEmptyLineNums(lines) {
}
/**
* Return an array with with any line numbers that contain comments.
* Return an array with any line numbers that contain comments.
* @param {Array} comments An array of comment tokens.
* @returns {Array} An array of line numbers.
*/
@ -49,13 +49,13 @@ function getCommentLineNums(comments) {
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "layout",
docs: {
description: "require empty lines around comments",
category: "Stylistic Issues",
description: "Require empty lines around comments",
recommended: false,
url: "https://eslint.org/docs/rules/lines-around-comment"
},
@ -141,7 +141,7 @@ module.exports = {
comments = sourceCode.getAllComments(),
commentLines = getCommentLineNums(comments),
emptyLines = getEmptyLineNums(lines),
commentAndEmptyLines = commentLines.concat(emptyLines);
commentAndEmptyLines = new Set(commentLines.concat(emptyLines));
/**
* Returns whether or not comments are on lines starting with or ending with code
@ -186,10 +186,39 @@ module.exports = {
/**
* Returns the parent node that contains the given token.
* @param {token} token The token to check.
* @returns {ASTNode} The parent node that contains the given token.
* @returns {ASTNode|null} The parent node that contains the given token.
*/
function getParentNodeOfToken(token) {
return sourceCode.getNodeByRangeIndex(token.range[0]);
const node = sourceCode.getNodeByRangeIndex(token.range[0]);
/*
* For the purpose of this rule, the comment token is in a `StaticBlock` node only
* if it's inside the braces of that `StaticBlock` node.
*
* Example where this function returns `null`:
*
* static
* // comment
* {
* }
*
* Example where this function returns `StaticBlock` node:
*
* static
* {
* // comment
* }
*
*/
if (node && node.type === "StaticBlock") {
const openingBrace = sourceCode.getFirstToken(node, { skip: 1 }); // skip the `static` token
return token.range[0] >= openingBrace.range[0]
? node
: null;
}
return node;
}
/**
@ -201,8 +230,21 @@ module.exports = {
function isCommentAtParentStart(token, nodeType) {
const parent = getParentNodeOfToken(token);
return parent && isParentNodeType(parent, nodeType) &&
token.loc.start.line - parent.loc.start.line === 1;
if (parent && isParentNodeType(parent, nodeType)) {
let parentStartNodeOrToken = parent;
if (parent.type === "StaticBlock") {
parentStartNodeOrToken = sourceCode.getFirstToken(parent, { skip: 1 }); // opening brace of the static block
} else if (parent.type === "SwitchStatement") {
parentStartNodeOrToken = sourceCode.getTokenAfter(parent.discriminant, {
filter: astUtils.isOpeningBraceToken
}); // opening brace of the switch statement
}
return token.loc.start.line - parentStartNodeOrToken.loc.start.line === 1;
}
return false;
}
/**
@ -214,7 +256,7 @@ module.exports = {
function isCommentAtParentEnd(token, nodeType) {
const parent = getParentNodeOfToken(token);
return parent && isParentNodeType(parent, nodeType) &&
return !!parent && isParentNodeType(parent, nodeType) &&
parent.loc.end.line - token.loc.end.line === 1;
}
@ -224,7 +266,13 @@ module.exports = {
* @returns {boolean} True if the comment is at block start.
*/
function isCommentAtBlockStart(token) {
return isCommentAtParentStart(token, "ClassBody") || isCommentAtParentStart(token, "BlockStatement") || isCommentAtParentStart(token, "SwitchCase");
return (
isCommentAtParentStart(token, "ClassBody") ||
isCommentAtParentStart(token, "BlockStatement") ||
isCommentAtParentStart(token, "StaticBlock") ||
isCommentAtParentStart(token, "SwitchCase") ||
isCommentAtParentStart(token, "SwitchStatement")
);
}
/**
@ -233,7 +281,13 @@ module.exports = {
* @returns {boolean} True if the comment is at block end.
*/
function isCommentAtBlockEnd(token) {
return isCommentAtParentEnd(token, "ClassBody") || isCommentAtParentEnd(token, "BlockStatement") || isCommentAtParentEnd(token, "SwitchCase") || isCommentAtParentEnd(token, "SwitchStatement");
return (
isCommentAtParentEnd(token, "ClassBody") ||
isCommentAtParentEnd(token, "BlockStatement") ||
isCommentAtParentEnd(token, "StaticBlock") ||
isCommentAtParentEnd(token, "SwitchCase") ||
isCommentAtParentEnd(token, "SwitchStatement")
);
}
/**
@ -346,7 +400,7 @@ module.exports = {
const nextTokenOrComment = sourceCode.getTokenAfter(token, { includeComments: true });
// check for newline before
if (!exceptionStartAllowed && before && !commentAndEmptyLines.includes(prevLineNum) &&
if (!exceptionStartAllowed && before && !commentAndEmptyLines.has(prevLineNum) &&
!(astUtils.isCommentToken(previousTokenOrComment) && astUtils.isTokenOnSameLine(previousTokenOrComment, token))) {
const lineStart = token.range[0] - token.loc.start.column;
const range = [lineStart, lineStart];
@ -361,7 +415,7 @@ module.exports = {
}
// check for newline after
if (!exceptionEndAllowed && after && !commentAndEmptyLines.includes(nextLineNum) &&
if (!exceptionEndAllowed && after && !commentAndEmptyLines.has(nextLineNum) &&
!(astUtils.isCommentToken(nextTokenOrComment) && astUtils.isTokenOnSameLine(token, nextTokenOrComment))) {
context.report({
node: token,

View file

@ -1,7 +1,7 @@
/**
* @fileoverview Require or disallow newlines around directives.
* @author Kai Cataldo
* @deprecated
* @deprecated in ESLint v4.0.0
*/
"use strict";
@ -12,13 +12,13 @@ const astUtils = require("./utils/ast-utils");
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "layout",
docs: {
description: "require or disallow newlines around directives",
category: "Stylistic Issues",
description: "Require or disallow newlines around directives",
recommended: false,
url: "https://eslint.org/docs/rules/lines-around-directive"
},

View file

@ -4,19 +4,23 @@
*/
"use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const astUtils = require("./utils/ast-utils");
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "layout",
docs: {
description: "require or disallow an empty line between class members",
category: "Stylistic Issues",
description: "Require or disallow an empty line between class members",
recommended: false,
url: "https://eslint.org/docs/rules/lines-between-class-members"
},
@ -53,6 +57,51 @@ module.exports = {
const sourceCode = context.getSourceCode();
/**
* Gets a pair of tokens that should be used to check lines between two class member nodes.
*
* In most cases, this returns the very last token of the current node and
* the very first token of the next node.
* For example:
*
* class C {
* x = 1; // curLast: `;` nextFirst: `in`
* in = 2
* }
*
* There is only one exception. If the given node ends with a semicolon, and it looks like
* a semicolon-less style's semicolon - one that is not on the same line as the preceding
* token, but is on the line where the next class member starts - this returns the preceding
* token and the semicolon as boundary tokens.
* For example:
*
* class C {
* x = 1 // curLast: `1` nextFirst: `;`
* ;in = 2
* }
* When determining the desired layout of the code, we should treat this semicolon as
* a part of the next class member node instead of the one it technically belongs to.
* @param {ASTNode} curNode Current class member node.
* @param {ASTNode} nextNode Next class member node.
* @returns {Token} The actual last token of `node`.
* @private
*/
function getBoundaryTokens(curNode, nextNode) {
const lastToken = sourceCode.getLastToken(curNode);
const prevToken = sourceCode.getTokenBefore(lastToken);
const nextToken = sourceCode.getFirstToken(nextNode); // skip possible lone `;` between nodes
const isSemicolonLessStyle = (
astUtils.isSemicolonToken(lastToken) &&
!astUtils.isTokenOnSameLine(prevToken, lastToken) &&
astUtils.isTokenOnSameLine(lastToken, nextToken)
);
return isSemicolonLessStyle
? { curLast: prevToken, nextFirst: lastToken }
: { curLast: lastToken, nextFirst: nextToken };
}
/**
* Return the last token among the consecutive tokens that have no exceed max line difference in between, before the first token in the next member.
* @param {Token} prevLastToken The last token in the previous member node.
@ -101,8 +150,7 @@ module.exports = {
for (let i = 0; i < body.length - 1; i++) {
const curFirst = sourceCode.getFirstToken(body[i]);
const curLast = sourceCode.getLastToken(body[i]);
const nextFirst = sourceCode.getFirstToken(body[i + 1]);
const { curLast, nextFirst } = getBoundaryTokens(body[i], body[i + 1]);
const isMulti = !astUtils.isTokenOnSameLine(curFirst, curLast);
const skip = !isMulti && options[1].exceptAfterSingleLine;
const beforePadding = findLastConsecutiveTokenAfter(curLast, nextFirst, 1);

View file

@ -0,0 +1,474 @@
/**
* @fileoverview Rule to replace assignment expressions with logical operator assignment
* @author Daniel Martens
*/
"use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const astUtils = require("./utils/ast-utils.js");
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
const baseTypes = new Set(["Identifier", "Super", "ThisExpression"]);
/**
* Returns true iff either "undefined" or a void expression (eg. "void 0")
* @param {ASTNode} expression Expression to check
* @param {import('eslint-scope').Scope} scope Scope of the expression
* @returns {boolean} True iff "undefined" or "void ..."
*/
function isUndefined(expression, scope) {
if (expression.type === "Identifier" && expression.name === "undefined") {
return astUtils.isReferenceToGlobalVariable(scope, expression);
}
return expression.type === "UnaryExpression" &&
expression.operator === "void" &&
expression.argument.type === "Literal" &&
expression.argument.value === 0;
}
/**
* Returns true iff the reference is either an identifier or member expression
* @param {ASTNode} expression Expression to check
* @returns {boolean} True for identifiers and member expressions
*/
function isReference(expression) {
return (expression.type === "Identifier" && expression.name !== "undefined") ||
expression.type === "MemberExpression";
}
/**
* Returns true iff the expression checks for nullish with loose equals.
* Examples: value == null, value == void 0
* @param {ASTNode} expression Test condition
* @param {import('eslint-scope').Scope} scope Scope of the expression
* @returns {boolean} True iff implicit nullish comparison
*/
function isImplicitNullishComparison(expression, scope) {
if (expression.type !== "BinaryExpression" || expression.operator !== "==") {
return false;
}
const reference = isReference(expression.left) ? "left" : "right";
const nullish = reference === "left" ? "right" : "left";
return isReference(expression[reference]) &&
(astUtils.isNullLiteral(expression[nullish]) || isUndefined(expression[nullish], scope));
}
/**
* Condition with two equal comparisons.
* @param {ASTNode} expression Condition
* @returns {boolean} True iff matches ? === ? || ? === ?
*/
function isDoubleComparison(expression) {
return expression.type === "LogicalExpression" &&
expression.operator === "||" &&
expression.left.type === "BinaryExpression" &&
expression.left.operator === "===" &&
expression.right.type === "BinaryExpression" &&
expression.right.operator === "===";
}
/**
* Returns true iff the expression checks for undefined and null.
* Example: value === null || value === undefined
* @param {ASTNode} expression Test condition
* @param {import('eslint-scope').Scope} scope Scope of the expression
* @returns {boolean} True iff explicit nullish comparison
*/
function isExplicitNullishComparison(expression, scope) {
if (!isDoubleComparison(expression)) {
return false;
}
const leftReference = isReference(expression.left.left) ? "left" : "right";
const leftNullish = leftReference === "left" ? "right" : "left";
const rightReference = isReference(expression.right.left) ? "left" : "right";
const rightNullish = rightReference === "left" ? "right" : "left";
return astUtils.isSameReference(expression.left[leftReference], expression.right[rightReference]) &&
((astUtils.isNullLiteral(expression.left[leftNullish]) && isUndefined(expression.right[rightNullish], scope)) ||
(isUndefined(expression.left[leftNullish], scope) && astUtils.isNullLiteral(expression.right[rightNullish])));
}
/**
* Returns true for Boolean(arg) calls
* @param {ASTNode} expression Test condition
* @param {import('eslint-scope').Scope} scope Scope of the expression
* @returns {boolean} Whether the expression is a boolean cast
*/
function isBooleanCast(expression, scope) {
return expression.type === "CallExpression" &&
expression.callee.name === "Boolean" &&
expression.arguments.length === 1 &&
astUtils.isReferenceToGlobalVariable(scope, expression.callee);
}
/**
* Returns true for:
* truthiness checks: value, Boolean(value), !!value
* falsyness checks: !value, !Boolean(value)
* nullish checks: value == null, value === undefined || value === null
* @param {ASTNode} expression Test condition
* @param {import('eslint-scope').Scope} scope Scope of the expression
* @returns {?{ reference: ASTNode, operator: '??'|'||'|'&&'}} Null if not a known existence
*/
function getExistence(expression, scope) {
const isNegated = expression.type === "UnaryExpression" && expression.operator === "!";
const base = isNegated ? expression.argument : expression;
switch (true) {
case isReference(base):
return { reference: base, operator: isNegated ? "||" : "&&" };
case base.type === "UnaryExpression" && base.operator === "!" && isReference(base.argument):
return { reference: base.argument, operator: "&&" };
case isBooleanCast(base, scope) && isReference(base.arguments[0]):
return { reference: base.arguments[0], operator: isNegated ? "||" : "&&" };
case isImplicitNullishComparison(expression, scope):
return { reference: isReference(expression.left) ? expression.left : expression.right, operator: "??" };
case isExplicitNullishComparison(expression, scope):
return { reference: isReference(expression.left.left) ? expression.left.left : expression.left.right, operator: "??" };
default: return null;
}
}
/**
* Returns true iff the node is inside a with block
* @param {ASTNode} node Node to check
* @returns {boolean} True iff passed node is inside a with block
*/
function isInsideWithBlock(node) {
if (node.type === "Program") {
return false;
}
return node.parent.type === "WithStatement" && node.parent.body === node ? true : isInsideWithBlock(node.parent);
}
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "suggestion",
docs: {
description: "Require or disallow logical assignment logical operator shorthand",
recommended: false,
url: "https://eslint.org/docs/rules/logical-assignment-operators"
},
schema: {
type: "array",
oneOf: [{
items: [
{ const: "always" },
{
type: "object",
properties: {
enforceForIfStatements: {
type: "boolean"
}
},
additionalProperties: false
}
],
minItems: 0, // 0 for allowing passing no options
maxItems: 2
}, {
items: [{ const: "never" }],
minItems: 1,
maxItems: 1
}]
},
fixable: "code",
// eslint-disable-next-line eslint-plugin/require-meta-has-suggestions -- Does not detect conditional suggestions
hasSuggestions: true,
messages: {
assignment: "Assignment (=) can be replaced with operator assignment ({{operator}}).",
useLogicalOperator: "Convert this assignment to use the operator {{ operator }}.",
logical: "Logical expression can be replaced with an assignment ({{ operator }}).",
convertLogical: "Replace this logical expression with an assignment with the operator {{ operator }}.",
if: "'if' statement can be replaced with a logical operator assignment with operator {{ operator }}.",
convertIf: "Replace this 'if' statement with a logical assignment with operator {{ operator }}.",
unexpected: "Unexpected logical operator assignment ({{operator}}) shorthand.",
separate: "Separate the logical assignment into an assignment with a logical operator."
}
},
create(context) {
const mode = context.options[0] === "never" ? "never" : "always";
const checkIf = mode === "always" && context.options.length > 1 && context.options[1].enforceForIfStatements;
const sourceCode = context.getSourceCode();
const isStrict = context.getScope().isStrict;
/**
* Returns false if the access could be a getter
* @param {ASTNode} node Assignment expression
* @returns {boolean} True iff the fix is safe
*/
function cannotBeGetter(node) {
return node.type === "Identifier" &&
(isStrict || !isInsideWithBlock(node));
}
/**
* Check whether only a single property is accessed
* @param {ASTNode} node reference
* @returns {boolean} True iff a single property is accessed
*/
function accessesSingleProperty(node) {
if (!isStrict && isInsideWithBlock(node)) {
return node.type === "Identifier";
}
return node.type === "MemberExpression" &&
baseTypes.has(node.object.type) &&
(!node.computed || (node.property.type !== "MemberExpression" && node.property.type !== "ChainExpression"));
}
/**
* Adds a fixer or suggestion whether on the fix is safe.
* @param {{ messageId: string, node: ASTNode }} descriptor Report descriptor without fix or suggest
* @param {{ messageId: string, fix: Function }} suggestion Adds the fix or the whole suggestion as only element in "suggest" to suggestion
* @param {boolean} shouldBeFixed Fix iff the condition is true
* @returns {Object} Descriptor with either an added fix or suggestion
*/
function createConditionalFixer(descriptor, suggestion, shouldBeFixed) {
if (shouldBeFixed) {
return {
...descriptor,
fix: suggestion.fix
};
}
return {
...descriptor,
suggest: [suggestion]
};
}
/**
* Returns the operator token for assignments and binary expressions
* @param {ASTNode} node AssignmentExpression or BinaryExpression
* @returns {import('eslint').AST.Token} Operator token between the left and right expression
*/
function getOperatorToken(node) {
return sourceCode.getFirstTokenBetween(node.left, node.right, token => token.value === node.operator);
}
if (mode === "never") {
return {
// foo ||= bar
"AssignmentExpression"(assignment) {
if (!astUtils.isLogicalAssignmentOperator(assignment.operator)) {
return;
}
const descriptor = {
messageId: "unexpected",
node: assignment,
data: { operator: assignment.operator }
};
const suggestion = {
messageId: "separate",
*fix(ruleFixer) {
if (sourceCode.getCommentsInside(assignment).length > 0) {
return;
}
const operatorToken = getOperatorToken(assignment);
// -> foo = bar
yield ruleFixer.replaceText(operatorToken, "=");
const assignmentText = sourceCode.getText(assignment.left);
const operator = assignment.operator.slice(0, -1);
// -> foo = foo || bar
yield ruleFixer.insertTextAfter(operatorToken, ` ${assignmentText} ${operator}`);
const precedence = astUtils.getPrecedence(assignment.right) <= astUtils.getPrecedence({ type: "LogicalExpression", operator });
// ?? and || / && cannot be mixed but have same precedence
const mixed = assignment.operator === "??=" && astUtils.isLogicalExpression(assignment.right);
if (!astUtils.isParenthesised(sourceCode, assignment.right) && (precedence || mixed)) {
// -> foo = foo || (bar)
yield ruleFixer.insertTextBefore(assignment.right, "(");
yield ruleFixer.insertTextAfter(assignment.right, ")");
}
}
};
context.report(createConditionalFixer(descriptor, suggestion, cannotBeGetter(assignment.left)));
}
};
}
return {
// foo = foo || bar
"AssignmentExpression[operator='='][right.type='LogicalExpression']"(assignment) {
if (!astUtils.isSameReference(assignment.left, assignment.right.left)) {
return;
}
const descriptor = {
messageId: "assignment",
node: assignment,
data: { operator: `${assignment.right.operator}=` }
};
const suggestion = {
messageId: "useLogicalOperator",
data: { operator: `${assignment.right.operator}=` },
*fix(ruleFixer) {
if (sourceCode.getCommentsInside(assignment).length > 0) {
return;
}
// No need for parenthesis around the assignment based on precedence as the precedence stays the same even with changed operator
const assignmentOperatorToken = getOperatorToken(assignment);
// -> foo ||= foo || bar
yield ruleFixer.insertTextBefore(assignmentOperatorToken, assignment.right.operator);
// -> foo ||= bar
const logicalOperatorToken = getOperatorToken(assignment.right);
const firstRightOperandToken = sourceCode.getTokenAfter(logicalOperatorToken);
yield ruleFixer.removeRange([assignment.right.range[0], firstRightOperandToken.range[0]]);
}
};
context.report(createConditionalFixer(descriptor, suggestion, cannotBeGetter(assignment.left)));
},
// foo || (foo = bar)
'LogicalExpression[right.type="AssignmentExpression"][right.operator="="]'(logical) {
// Right side has to be parenthesized, otherwise would be parsed as (foo || foo) = bar which is illegal
if (isReference(logical.left) && astUtils.isSameReference(logical.left, logical.right.left)) {
const descriptor = {
messageId: "logical",
node: logical,
data: { operator: `${logical.operator}=` }
};
const suggestion = {
messageId: "convertLogical",
data: { operator: `${logical.operator}=` },
*fix(ruleFixer) {
if (sourceCode.getCommentsInside(logical).length > 0) {
return;
}
const requiresOuterParenthesis = logical.parent.type !== "ExpressionStatement" &&
(astUtils.getPrecedence({ type: "AssignmentExpression" }) < astUtils.getPrecedence(logical.parent));
if (!astUtils.isParenthesised(sourceCode, logical) && requiresOuterParenthesis) {
yield ruleFixer.insertTextBefore(logical, "(");
yield ruleFixer.insertTextAfter(logical, ")");
}
// Also removes all opening parenthesis
yield ruleFixer.removeRange([logical.range[0], logical.right.range[0]]); // -> foo = bar)
// Also removes all ending parenthesis
yield ruleFixer.removeRange([logical.right.range[1], logical.range[1]]); // -> foo = bar
const operatorToken = getOperatorToken(logical.right);
yield ruleFixer.insertTextBefore(operatorToken, logical.operator); // -> foo ||= bar
}
};
const fix = cannotBeGetter(logical.left) || accessesSingleProperty(logical.left);
context.report(createConditionalFixer(descriptor, suggestion, fix));
}
},
// if (foo) foo = bar
"IfStatement[alternate=null]"(ifNode) {
if (!checkIf) {
return;
}
const hasBody = ifNode.consequent.type === "BlockStatement";
if (hasBody && ifNode.consequent.body.length !== 1) {
return;
}
const body = hasBody ? ifNode.consequent.body[0] : ifNode.consequent;
const scope = context.getScope();
const existence = getExistence(ifNode.test, scope);
if (
body.type === "ExpressionStatement" &&
body.expression.type === "AssignmentExpression" &&
body.expression.operator === "=" &&
existence !== null &&
astUtils.isSameReference(existence.reference, body.expression.left)
) {
const descriptor = {
messageId: "if",
node: ifNode,
data: { operator: `${existence.operator}=` }
};
const suggestion = {
messageId: "convertIf",
data: { operator: `${existence.operator}=` },
*fix(ruleFixer) {
if (sourceCode.getCommentsInside(ifNode).length > 0) {
return;
}
const firstBodyToken = sourceCode.getFirstToken(body);
const prevToken = sourceCode.getTokenBefore(ifNode);
if (
prevToken !== null &&
prevToken.value !== ";" &&
prevToken.value !== "{" &&
firstBodyToken.type !== "Identifier" &&
firstBodyToken.type !== "Keyword"
) {
// Do not fix if the fixed statement could be part of the previous statement (eg. fn() if (a == null) (a) = b --> fn()(a) ??= b)
return;
}
const operatorToken = getOperatorToken(body.expression);
yield ruleFixer.insertTextBefore(operatorToken, existence.operator); // -> if (foo) foo ||= bar
yield ruleFixer.removeRange([ifNode.range[0], body.range[0]]); // -> foo ||= bar
yield ruleFixer.removeRange([body.range[1], ifNode.range[1]]); // -> foo ||= bar, only present if "if" had a body
const nextToken = sourceCode.getTokenAfter(body.expression);
if (hasBody && (nextToken !== null && nextToken.value !== ";")) {
yield ruleFixer.insertTextAfter(ifNode, ";");
}
}
};
const shouldBeFixed = cannotBeGetter(existence.reference) ||
(ifNode.test.type !== "LogicalExpression" && accessesSingleProperty(existence.reference));
context.report(createConditionalFixer(descriptor, suggestion, shouldBeFixed));
}
}
};
}
};

View file

@ -13,21 +13,38 @@
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "suggestion",
docs: {
description: "enforce a maximum number of classes per file",
category: "Best Practices",
description: "Enforce a maximum number of classes per file",
recommended: false,
url: "https://eslint.org/docs/rules/max-classes-per-file"
},
schema: [
{
type: "integer",
minimum: 1
oneOf: [
{
type: "integer",
minimum: 1
},
{
type: "object",
properties: {
ignoreExpressions: {
type: "boolean"
},
max: {
type: "integer",
minimum: 1
}
},
additionalProperties: false
}
]
}
],
@ -36,8 +53,10 @@ module.exports = {
}
},
create(context) {
const maxClasses = context.options[0] || 1;
const [option = {}] = context.options;
const [ignoreExpressions, max] = typeof option === "number"
? [false, option || 1]
: [option.ignoreExpressions, option.max || 1];
let classCount = 0;
@ -46,19 +65,24 @@ module.exports = {
classCount = 0;
},
"Program:exit"(node) {
if (classCount > maxClasses) {
if (classCount > max) {
context.report({
node,
messageId: "maximumExceeded",
data: {
classCount,
max: maxClasses
max
}
});
}
},
"ClassDeclaration, ClassExpression"() {
"ClassDeclaration"() {
classCount++;
},
"ClassExpression"() {
if (!ignoreExpressions) {
classCount++;
}
}
};
}

View file

@ -9,13 +9,13 @@
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "suggestion",
docs: {
description: "enforce a maximum depth that blocks can be nested",
category: "Stylistic Issues",
description: "Enforce a maximum depth that blocks can be nested",
recommended: false,
url: "https://eslint.org/docs/rules/max-depth"
},
@ -119,6 +119,7 @@ module.exports = {
FunctionDeclaration: startFunction,
FunctionExpression: startFunction,
ArrowFunctionExpression: startFunction,
StaticBlock: startFunction,
IfStatement(node) {
if (node.parent.type !== "IfStatement") {
@ -147,6 +148,7 @@ module.exports = {
"FunctionDeclaration:exit": endFunction,
"FunctionExpression:exit": endFunction,
"ArrowFunctionExpression:exit": endFunction,
"StaticBlock:exit": endFunction,
"Program:exit": endFunction
};

View file

@ -63,13 +63,13 @@ const OPTIONS_OR_INTEGER_SCHEMA = {
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "layout",
docs: {
description: "enforce a maximum line length",
category: "Stylistic Issues",
description: "Enforce a maximum line length",
recommended: false,
url: "https://eslint.org/docs/rules/max-len"
},
@ -215,7 +215,7 @@ module.exports = {
* Ensure that an array exists at [key] on `object`, and add `value` to it.
* @param {Object} object the object to mutate
* @param {string} key the object's key
* @param {*} value the value to add
* @param {any} value the value to add
* @returns {void}
* @private
*/

View file

@ -48,7 +48,7 @@ const OPTIONS_OR_INTEGER_SCHEMA = {
/**
* Given a list of comment nodes, return a map with numeric keys (source code line numbers) and comment token values.
* @param {Array} comments An array of comment nodes.
* @returns {Map.<string,Node>} A map with numeric keys (source code line numbers) and comment token values.
* @returns {Map<string, Node>} A map with numeric keys (source code line numbers) and comment token values.
*/
function getCommentLineNumbers(comments) {
const map = new Map();
@ -65,13 +65,13 @@ function getCommentLineNumbers(comments) {
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "suggestion",
docs: {
description: "enforce a maximum number of lines of code in a function",
category: "Stylistic Issues",
description: "Enforce a maximum number of lines of code in a function",
recommended: false,
url: "https://eslint.org/docs/rules/max-lines-per-function"
},

View file

@ -28,13 +28,13 @@ function range(start, end) {
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "suggestion",
docs: {
description: "enforce a maximum number of lines per file",
category: "Stylistic Issues",
description: "Enforce a maximum number of lines per file",
recommended: false,
url: "https://eslint.org/docs/rules/max-lines"
},
@ -137,20 +137,6 @@ module.exports = {
return [];
}
/**
* Returns a new array formed by applying a given callback function to each element of the array, and then flattening the result by one level.
* TODO(stephenwade): Replace this with array.flatMap when we drop support for Node v10
* @param {any[]} array The array to process
* @param {Function} fn The function to use
* @returns {any[]} The result array
*/
function flatMap(array, fn) {
const mapped = array.map(fn);
const flattened = [].concat(...mapped);
return flattened;
}
return {
"Program:exit"() {
let lines = sourceCode.lines.map((text, i) => ({
@ -173,10 +159,10 @@ module.exports = {
if (skipComments) {
const comments = sourceCode.getAllComments();
const commentLines = flatMap(comments, comment => getLinesWithoutCode(comment));
const commentLines = new Set(comments.flatMap(getLinesWithoutCode));
lines = lines.filter(
l => !commentLines.includes(l.lineNumber)
l => !commentLines.has(l.lineNumber)
);
}

View file

@ -9,13 +9,13 @@
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "suggestion",
docs: {
description: "enforce a maximum depth that callbacks can be nested",
category: "Stylistic Issues",
description: "Enforce a maximum depth that callbacks can be nested",
recommended: false,
url: "https://eslint.org/docs/rules/max-nested-callbacks"
},

View file

@ -16,13 +16,13 @@ const { upperCaseFirst } = require("../shared/string-utils");
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "suggestion",
docs: {
description: "enforce a maximum number of parameters in function definitions",
category: "Stylistic Issues",
description: "Enforce a maximum number of parameters in function definitions",
recommended: false,
url: "https://eslint.org/docs/rules/max-params"
},

View file

@ -14,13 +14,13 @@ const astUtils = require("./utils/ast-utils");
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "layout",
docs: {
description: "enforce a maximum number of statements allowed per line",
category: "Stylistic Issues",
description: "Enforce a maximum number of statements allowed per line",
recommended: false,
url: "https://eslint.org/docs/rules/max-statements-per-line"
},

View file

@ -16,13 +16,13 @@ const { upperCaseFirst } = require("../shared/string-utils");
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "suggestion",
docs: {
description: "enforce a maximum number of statements allowed in function blocks",
category: "Stylistic Issues",
description: "Enforce a maximum number of statements allowed in function blocks",
recommended: false,
url: "https://eslint.org/docs/rules/max-statements"
},
@ -124,6 +124,14 @@ module.exports = {
function endFunction(node) {
const count = functionStack.pop();
/*
* This rule does not apply to class static blocks, but we have to track them so
* that statements in them do not count as statements in the enclosing function.
*/
if (node.type === "StaticBlock") {
return;
}
if (ignoreTopLevelFunctions && functionStack.length === 0) {
topLevelFunctions.push({ node, count });
} else {
@ -149,12 +157,14 @@ module.exports = {
FunctionDeclaration: startFunction,
FunctionExpression: startFunction,
ArrowFunctionExpression: startFunction,
StaticBlock: startFunction,
BlockStatement: countStatements,
"FunctionDeclaration:exit": endFunction,
"FunctionExpression:exit": endFunction,
"ArrowFunctionExpression:exit": endFunction,
"StaticBlock:exit": endFunction,
"Program:exit"() {
if (topLevelFunctions.length === 1) {

View file

@ -10,13 +10,13 @@ const astUtils = require("./utils/ast-utils");
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "suggestion",
docs: {
description: "enforce a particular style for multiline comments",
category: "Stylistic Issues",
description: "Enforce a particular style for multiline comments",
recommended: false,
url: "https://eslint.org/docs/rules/multiline-comment-style"
},

View file

@ -11,13 +11,13 @@ const astUtils = require("./utils/ast-utils");
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "layout",
docs: {
description: "enforce newlines between operands of ternary expressions",
category: "Stylistic Issues",
description: "Enforce newlines between operands of ternary expressions",
recommended: false,
url: "https://eslint.org/docs/rules/multiline-ternary"
},
@ -73,7 +73,7 @@ module.exports = {
end: lastTokenOfTest.loc.end
},
messageId: "unexpectedTestCons",
fix: fixer => {
fix(fixer) {
if (hasComments) {
return null;
}
@ -101,7 +101,7 @@ module.exports = {
end: lastTokenOfConsequent.loc.end
},
messageId: "unexpectedConsAlt",
fix: fixer => {
fix(fixer) {
if (hasComments) {
return null;
}

View file

@ -33,15 +33,16 @@ const CAPS_ALLOWED = [
* Ensure that if the key is provided, it must be an array.
* @param {Object} obj Object to check with `key`.
* @param {string} key Object key to check on `obj`.
* @param {*} fallback If obj[key] is not present, this will be returned.
* @param {any} fallback If obj[key] is not present, this will be returned.
* @throws {TypeError} If key is not an own array type property of `obj`.
* @returns {string[]} Returns obj[key] if it's an Array, otherwise `fallback`
*/
function checkArray(obj, key, fallback) {
/* istanbul ignore if */
/* c8 ignore start */
if (Object.prototype.hasOwnProperty.call(obj, key) && !Array.isArray(obj[key])) {
throw new TypeError(`${key}, if provided, must be an Array`);
}
}/* c8 ignore stop */
return obj[key] || fallback;
}
@ -75,13 +76,13 @@ function calculateCapIsNewExceptions(config) {
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "suggestion",
docs: {
description: "require constructor names to begin with a capital letter",
category: "Stylistic Issues",
description: "Require constructor names to begin with a capital letter",
recommended: false,
url: "https://eslint.org/docs/rules/new-cap"
},

View file

@ -19,13 +19,13 @@ const astUtils = require("./utils/ast-utils");
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "layout",
docs: {
description: "enforce or disallow parentheses when invoking a constructor with no arguments",
category: "Stylistic Issues",
description: "Enforce or disallow parentheses when invoking a constructor with no arguments",
recommended: false,
url: "https://eslint.org/docs/rules/new-parens"
},

View file

@ -1,7 +1,7 @@
/**
* @fileoverview Rule to check empty newline after "var" statement
* @author Gopal Venkatesan
* @deprecated
* @deprecated in ESLint v4.0.0
*/
"use strict";
@ -16,13 +16,13 @@ const astUtils = require("./utils/ast-utils");
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "layout",
docs: {
description: "require or disallow an empty line after variable declarations",
category: "Stylistic Issues",
description: "Require or disallow an empty line after variable declarations",
recommended: false,
url: "https://eslint.org/docs/rules/newline-after-var"
},
@ -145,9 +145,9 @@ module.exports = {
/**
* Determine if a token starts more than one line after a comment ends
* @param {token} token The token being checked
* @param {integer} commentStartLine The line number on which the comment starts
* @returns {boolean} True if `token` does not start immediately after a comment
* @param {token} token The token being checked
* @param {integer} commentStartLine The line number on which the comment starts
* @returns {boolean} True if `token` does not start immediately after a comment
*/
function hasBlankLineAfterComment(token, commentStartLine) {
return token.loc.start.line > getLastCommentLineOfBlock(commentStartLine) + 1;

View file

@ -1,7 +1,7 @@
/**
* @fileoverview Rule to require newlines before `return` statement
* @author Kai Cataldo
* @deprecated
* @deprecated in ESLint v4.0.0
*/
"use strict";
@ -9,13 +9,13 @@
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "layout",
docs: {
description: "require an empty line before `return` statements",
category: "Stylistic Issues",
description: "Require an empty line before `return` statements",
recommended: false,
url: "https://eslint.org/docs/rules/newline-before-return"
},
@ -47,7 +47,7 @@ module.exports = {
function isPrecededByTokens(node, testTokens) {
const tokenBefore = sourceCode.getTokenBefore(node);
return testTokens.some(token => tokenBefore.value === token);
return testTokens.includes(tokenBefore.value);
}
/**

View file

@ -12,13 +12,13 @@ const astUtils = require("./utils/ast-utils");
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "layout",
docs: {
description: "require a newline after each call in a method chain",
category: "Stylistic Issues",
description: "Require a newline after each call in a method chain",
recommended: false,
url: "https://eslint.org/docs/rules/newline-per-chained-call"
},
@ -53,7 +53,7 @@ module.exports = {
* Get the prefix of a given MemberExpression node.
* If the MemberExpression node is a computed value it returns a
* left bracket. If not it returns a period.
* @param {ASTNode} node A MemberExpression node to get
* @param {ASTNode} node A MemberExpression node to get
* @returns {string} The prefix of the node.
*/
function getPrefix(node) {

View file

@ -82,13 +82,13 @@ function isGlobalThisReferenceOrGlobalWindow(scope, node) {
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "suggestion",
docs: {
description: "disallow the use of `alert`, `confirm`, and `prompt`",
category: "Best Practices",
description: "Disallow the use of `alert`, `confirm`, and `prompt`",
recommended: false,
url: "https://eslint.org/docs/rules/no-alert"
},

View file

@ -9,13 +9,13 @@
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "suggestion",
docs: {
description: "disallow `Array` constructors",
category: "Stylistic Issues",
description: "Disallow `Array` constructors",
recommended: false,
url: "https://eslint.org/docs/rules/no-array-constructor"
},

View file

@ -8,13 +8,13 @@
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "problem",
docs: {
description: "disallow using an async function as a Promise executor",
category: "Possible Errors",
description: "Disallow using an async function as a Promise executor",
recommended: true,
url: "https://eslint.org/docs/rules/no-async-promise-executor"
},

View file

@ -53,13 +53,13 @@ function isLooped(node, parent) {
}
}
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "problem",
docs: {
description: "disallow `await` inside of loops",
category: "Possible Errors",
description: "Disallow `await` inside of loops",
recommended: false,
url: "https://eslint.org/docs/rules/no-await-in-loop"
},

View file

@ -20,13 +20,13 @@ const BITWISE_OPERATORS = [
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "suggestion",
docs: {
description: "disallow bitwise operators",
category: "Stylistic Issues",
description: "Disallow bitwise operators",
recommended: false,
url: "https://eslint.org/docs/rules/no-bitwise"
},
@ -63,7 +63,7 @@ module.exports = {
/**
* Reports an unexpected use of a bitwise operator.
* @param {ASTNode} node Node which contains the bitwise operator.
* @param {ASTNode} node Node which contains the bitwise operator.
* @returns {void}
*/
function report(node) {
@ -72,25 +72,25 @@ module.exports = {
/**
* Checks if the given node has a bitwise operator.
* @param {ASTNode} node The node to check.
* @param {ASTNode} node The node to check.
* @returns {boolean} Whether or not the node has a bitwise operator.
*/
function hasBitwiseOperator(node) {
return BITWISE_OPERATORS.indexOf(node.operator) !== -1;
return BITWISE_OPERATORS.includes(node.operator);
}
/**
* Checks if exceptions were provided, e.g. `{ allow: ['~', '|'] }`.
* @param {ASTNode} node The node to check.
* @param {ASTNode} node The node to check.
* @returns {boolean} Whether or not the node has a bitwise operator.
*/
function allowedOperator(node) {
return allowed.indexOf(node.operator) !== -1;
return allowed.includes(node.operator);
}
/**
* Checks if the given bitwise operator is used for integer typecasting, i.e. "|0"
* @param {ASTNode} node The node to check.
* @param {ASTNode} node The node to check.
* @returns {boolean} whether the node is used in integer typecasting.
*/
function isInt32Hint(node) {
@ -100,7 +100,7 @@ module.exports = {
/**
* Report if the given node contains a bitwise operator.
* @param {ASTNode} node The node to check.
* @param {ASTNode} node The node to check.
* @returns {void}
*/
function checkNodeForBitwiseOperator(node) {

View file

@ -1,6 +1,7 @@
/**
* @fileoverview disallow use of the Buffer() constructor
* @author Teddy Katz
* @deprecated in ESLint v7.0.0
*/
"use strict";
@ -8,6 +9,7 @@
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
deprecated: true,
@ -17,8 +19,7 @@ module.exports = {
type: "problem",
docs: {
description: "disallow use of the `Buffer()` constructor",
category: "Node.js and CommonJS",
description: "Disallow use of the `Buffer()` constructor",
recommended: false,
url: "https://eslint.org/docs/rules/no-buffer-constructor"
},

View file

@ -9,13 +9,13 @@
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "suggestion",
docs: {
description: "disallow the use of `arguments.caller` or `arguments.callee`",
category: "Best Practices",
description: "Disallow the use of `arguments.caller` or `arguments.callee`",
recommended: false,
url: "https://eslint.org/docs/rules/no-caller"
},

View file

@ -8,13 +8,13 @@
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "suggestion",
docs: {
description: "disallow lexical declarations in case clauses",
category: "Best Practices",
description: "Disallow lexical declarations in case clauses",
recommended: true,
url: "https://eslint.org/docs/rules/no-case-declarations"
},

View file

@ -16,13 +16,13 @@ const astUtils = require("./utils/ast-utils");
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "suggestion",
docs: {
description: "disallow `catch` clause parameters from shadowing variables in the outer scope",
category: "Variables",
description: "Disallow `catch` clause parameters from shadowing variables in the outer scope",
recommended: false,
url: "https://eslint.org/docs/rules/no-catch-shadow"
},

View file

@ -11,13 +11,13 @@ const astUtils = require("./utils/ast-utils");
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "problem",
docs: {
description: "disallow reassigning class members",
category: "ECMAScript 6",
description: "Disallow reassigning class members",
recommended: true,
url: "https://eslint.org/docs/rules/no-class-assign"
},

View file

@ -8,13 +8,13 @@
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "problem",
docs: {
description: "disallow comparing against -0",
category: "Possible Errors",
description: "Disallow comparing against -0",
recommended: true,
url: "https://eslint.org/docs/rules/no-compare-neg-zero"
},

View file

@ -28,13 +28,13 @@ const NODE_DESCRIPTIONS = {
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "problem",
docs: {
description: "disallow assignment operators in conditional expressions",
category: "Possible Errors",
description: "Disallow assignment operators in conditional expressions",
recommended: true,
url: "https://eslint.org/docs/rules/no-cond-assign"
},

View file

@ -25,13 +25,13 @@ function isConditional(node) {
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "suggestion",
docs: {
description: "disallow arrow functions where they could be confused with comparisons",
category: "ECMAScript 6",
description: "Disallow arrow functions where they could be confused with comparisons",
recommended: false,
url: "https://eslint.org/docs/rules/no-confusing-arrow"
},
@ -41,7 +41,8 @@ module.exports = {
schema: [{
type: "object",
properties: {
allowParens: { type: "boolean", default: true }
allowParens: { type: "boolean", default: true },
onlyOneSimpleParam: { type: "boolean", default: false }
},
additionalProperties: false
}],
@ -54,6 +55,7 @@ module.exports = {
create(context) {
const config = context.options[0] || {};
const allowParens = config.allowParens || (config.allowParens === void 0);
const onlyOneSimpleParam = config.onlyOneSimpleParam;
const sourceCode = context.getSourceCode();
@ -65,7 +67,9 @@ module.exports = {
function checkArrowFunc(node) {
const body = node.body;
if (isConditional(body) && !(allowParens && astUtils.isParenthesised(sourceCode, body))) {
if (isConditional(body) &&
!(allowParens && astUtils.isParenthesised(sourceCode, body)) &&
!(onlyOneSimpleParam && !(node.params.length === 1 && node.params[0].type === "Identifier"))) {
context.report({
node,
messageId: "confusing",

View file

@ -15,13 +15,13 @@ const astUtils = require("./utils/ast-utils");
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "suggestion",
docs: {
description: "disallow the use of `console`",
category: "Possible Errors",
description: "Disallow the use of `console`",
recommended: false,
url: "https://eslint.org/docs/rules/no-console"
},
@ -72,7 +72,7 @@ module.exports = {
function isAllowed(node) {
const propertyName = astUtils.getStaticPropertyName(node);
return propertyName && allowed.indexOf(propertyName) !== -1;
return propertyName && allowed.includes(propertyName);
}
/**

View file

@ -11,13 +11,13 @@ const astUtils = require("./utils/ast-utils");
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "problem",
docs: {
description: "disallow reassigning `const` variables",
category: "ECMAScript 6",
description: "Disallow reassigning `const` variables",
recommended: true,
url: "https://eslint.org/docs/rules/no-const-assign"
},

View file

@ -0,0 +1,500 @@
/**
* @fileoverview Rule to flag constant comparisons and logical expressions that always/never short circuit
* @author Jordan Eldredge <https://jordaneldredge.com>
*/
"use strict";
const globals = require("globals");
const { isNullLiteral, isConstant, isReferenceToGlobalVariable, isLogicalAssignmentOperator } = require("./utils/ast-utils");
const NUMERIC_OR_STRING_BINARY_OPERATORS = new Set(["+", "-", "*", "/", "%", "|", "^", "&", "**", "<<", ">>", ">>>"]);
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
/**
* Test if an AST node has a statically knowable constant nullishness. Meaning,
* it will always resolve to a constant value of either: `null`, `undefined`
* or not `null` _or_ `undefined`. An expression that can vary between those
* three states at runtime would return `false`.
* @param {Scope} scope The scope in which the node was found.
* @param {ASTNode} node The AST node being tested.
* @returns {boolean} Does `node` have constant nullishness?
*/
function hasConstantNullishness(scope, node) {
switch (node.type) {
case "ObjectExpression": // Objects are never nullish
case "ArrayExpression": // Arrays are never nullish
case "ArrowFunctionExpression": // Functions never nullish
case "FunctionExpression": // Functions are never nullish
case "ClassExpression": // Classes are never nullish
case "NewExpression": // Objects are never nullish
case "Literal": // Nullish, or non-nullish, literals never change
case "TemplateLiteral": // A string is never nullish
case "UpdateExpression": // Numbers are never nullish
case "BinaryExpression": // Numbers, strings, or booleans are never nullish
return true;
case "CallExpression": {
if (node.callee.type !== "Identifier") {
return false;
}
const functionName = node.callee.name;
return (functionName === "Boolean" || functionName === "String" || functionName === "Number") &&
isReferenceToGlobalVariable(scope, node.callee);
}
case "AssignmentExpression":
if (node.operator === "=") {
return hasConstantNullishness(scope, node.right);
}
/*
* Handling short-circuiting assignment operators would require
* walking the scope. We won't attempt that (for now...) /
*/
if (isLogicalAssignmentOperator(node.operator)) {
return false;
}
/*
* The remaining assignment expressions all result in a numeric or
* string (non-nullish) value:
* "+=", "-=", "*=", "/=", "%=", "<<=", ">>=", ">>>=", "|=", "^=", "&="
*/
return true;
case "UnaryExpression":
/*
* "void" Always returns `undefined`
* "typeof" All types are strings, and thus non-nullish
* "!" Boolean is never nullish
* "delete" Returns a boolean, which is never nullish
* Math operators always return numbers or strings, neither of which
* are non-nullish "+", "-", "~"
*/
return true;
case "SequenceExpression": {
const last = node.expressions[node.expressions.length - 1];
return hasConstantNullishness(scope, last);
}
case "Identifier":
return node.name === "undefined" && isReferenceToGlobalVariable(scope, node);
case "JSXElement": // ESLint has a policy of not assuming any specific JSX behavior.
case "JSXFragment":
return false;
default:
return false;
}
}
/**
* Test if an AST node is a boolean value that never changes. Specifically we
* test for:
* 1. Literal booleans (`true` or `false`)
* 2. Unary `!` expressions with a constant value
* 3. Constant booleans created via the `Boolean` global function
* @param {Scope} scope The scope in which the node was found.
* @param {ASTNode} node The node to test
* @returns {boolean} Is `node` guaranteed to be a boolean?
*/
function isStaticBoolean(scope, node) {
switch (node.type) {
case "Literal":
return typeof node.value === "boolean";
case "CallExpression":
return node.callee.type === "Identifier" && node.callee.name === "Boolean" &&
isReferenceToGlobalVariable(scope, node.callee) &&
(node.arguments.length === 0 || isConstant(scope, node.arguments[0], true));
case "UnaryExpression":
return node.operator === "!" && isConstant(scope, node.argument, true);
default:
return false;
}
}
/**
* Test if an AST node will always give the same result when compared to a
* boolean value. Note that comparison to boolean values is different than
* truthiness.
* https://262.ecma-international.org/5.1/#sec-11.9.3
*
* Javascript `==` operator works by converting the boolean to `1` (true) or
* `+0` (false) and then checks the values `==` equality to that number.
* @param {Scope} scope The scope in which node was found.
* @param {ASTNode} node The node to test.
* @returns {boolean} Will `node` always coerce to the same boolean value?
*/
function hasConstantLooseBooleanComparison(scope, node) {
switch (node.type) {
case "ObjectExpression":
case "ClassExpression":
/**
* In theory objects like:
*
* `{toString: () => a}`
* `{valueOf: () => a}`
*
* Or a classes like:
*
* `class { static toString() { return a } }`
* `class { static valueOf() { return a } }`
*
* Are not constant verifiably when `inBooleanPosition` is
* false, but it's an edge case we've opted not to handle.
*/
return true;
case "ArrayExpression": {
const nonSpreadElements = node.elements.filter(e =>
// Elements can be `null` in sparse arrays: `[,,]`;
e !== null && e.type !== "SpreadElement");
/*
* Possible future direction if needed: We could check if the
* single value would result in variable boolean comparison.
* For now we will err on the side of caution since `[x]` could
* evaluate to `[0]` or `[1]`.
*/
return node.elements.length === 0 || nonSpreadElements.length > 1;
}
case "ArrowFunctionExpression":
case "FunctionExpression":
return true;
case "UnaryExpression":
if (node.operator === "void" || // Always returns `undefined`
node.operator === "typeof" // All `typeof` strings, when coerced to number, are not 0 or 1.
) {
return true;
}
if (node.operator === "!") {
return isConstant(scope, node.argument, true);
}
/*
* We won't try to reason about +, -, ~, or delete
* In theory, for the mathematical operators, we could look at the
* argument and try to determine if it coerces to a constant numeric
* value.
*/
return false;
case "NewExpression": // Objects might have custom `.valueOf` or `.toString`.
return false;
case "CallExpression": {
if (node.callee.type === "Identifier" &&
node.callee.name === "Boolean" &&
isReferenceToGlobalVariable(scope, node.callee)
) {
return node.arguments.length === 0 || isConstant(scope, node.arguments[0], true);
}
return false;
}
case "Literal": // True or false, literals never change
return true;
case "Identifier":
return node.name === "undefined" && isReferenceToGlobalVariable(scope, node);
case "TemplateLiteral":
/*
* In theory we could try to check if the quasi are sufficient to
* prove that the expression will always be true, but it would be
* tricky to get right. For example: `000.${foo}000`
*/
return node.expressions.length === 0;
case "AssignmentExpression":
if (node.operator === "=") {
return hasConstantLooseBooleanComparison(scope, node.right);
}
/*
* Handling short-circuiting assignment operators would require
* walking the scope. We won't attempt that (for now...)
*
* The remaining assignment expressions all result in a numeric or
* string (non-nullish) values which could be truthy or falsy:
* "+=", "-=", "*=", "/=", "%=", "<<=", ">>=", ">>>=", "|=", "^=", "&="
*/
return false;
case "SequenceExpression": {
const last = node.expressions[node.expressions.length - 1];
return hasConstantLooseBooleanComparison(scope, last);
}
case "JSXElement": // ESLint has a policy of not assuming any specific JSX behavior.
case "JSXFragment":
return false;
default:
return false;
}
}
/**
* Test if an AST node will always give the same result when _strictly_ compared
* to a boolean value. This can happen if the expression can never be boolean, or
* if it is always the same boolean value.
* @param {Scope} scope The scope in which the node was found.
* @param {ASTNode} node The node to test
* @returns {boolean} Will `node` always give the same result when compared to a
* static boolean value?
*/
function hasConstantStrictBooleanComparison(scope, node) {
switch (node.type) {
case "ObjectExpression": // Objects are not booleans
case "ArrayExpression": // Arrays are not booleans
case "ArrowFunctionExpression": // Functions are not booleans
case "FunctionExpression":
case "ClassExpression": // Classes are not booleans
case "NewExpression": // Objects are not booleans
case "TemplateLiteral": // Strings are not booleans
case "Literal": // True, false, or not boolean, literals never change.
case "UpdateExpression": // Numbers are not booleans
return true;
case "BinaryExpression":
return NUMERIC_OR_STRING_BINARY_OPERATORS.has(node.operator);
case "UnaryExpression": {
if (node.operator === "delete") {
return false;
}
if (node.operator === "!") {
return isConstant(scope, node.argument, true);
}
/*
* The remaining operators return either strings or numbers, neither
* of which are boolean.
*/
return true;
}
case "SequenceExpression": {
const last = node.expressions[node.expressions.length - 1];
return hasConstantStrictBooleanComparison(scope, last);
}
case "Identifier":
return node.name === "undefined" && isReferenceToGlobalVariable(scope, node);
case "AssignmentExpression":
if (node.operator === "=") {
return hasConstantStrictBooleanComparison(scope, node.right);
}
/*
* Handling short-circuiting assignment operators would require
* walking the scope. We won't attempt that (for now...)
*/
if (isLogicalAssignmentOperator(node.operator)) {
return false;
}
/*
* The remaining assignment expressions all result in either a number
* or a string, neither of which can ever be boolean.
*/
return true;
case "CallExpression": {
if (node.callee.type !== "Identifier") {
return false;
}
const functionName = node.callee.name;
if (
(functionName === "String" || functionName === "Number") &&
isReferenceToGlobalVariable(scope, node.callee)
) {
return true;
}
if (functionName === "Boolean" && isReferenceToGlobalVariable(scope, node.callee)) {
return (
node.arguments.length === 0 || isConstant(scope, node.arguments[0], true));
}
return false;
}
case "JSXElement": // ESLint has a policy of not assuming any specific JSX behavior.
case "JSXFragment":
return false;
default:
return false;
}
}
/**
* Test if an AST node will always result in a newly constructed object
* @param {Scope} scope The scope in which the node was found.
* @param {ASTNode} node The node to test
* @returns {boolean} Will `node` always be new?
*/
function isAlwaysNew(scope, node) {
switch (node.type) {
case "ObjectExpression":
case "ArrayExpression":
case "ArrowFunctionExpression":
case "FunctionExpression":
case "ClassExpression":
return true;
case "NewExpression": {
if (node.callee.type !== "Identifier") {
return false;
}
/*
* All the built-in constructors are always new, but
* user-defined constructors could return a sentinel
* object.
*
* Catching these is especially useful for primitive constructures
* which return boxed values, a surprising gotcha' in JavaScript.
*/
return Object.hasOwnProperty.call(globals.builtin, node.callee.name) &&
isReferenceToGlobalVariable(scope, node.callee);
}
case "Literal":
// Regular expressions are objects, and thus always new
return typeof node.regex === "object";
case "SequenceExpression": {
const last = node.expressions[node.expressions.length - 1];
return isAlwaysNew(scope, last);
}
case "AssignmentExpression":
if (node.operator === "=") {
return isAlwaysNew(scope, node.right);
}
return false;
case "ConditionalExpression":
return isAlwaysNew(scope, node.consequent) && isAlwaysNew(scope, node.alternate);
case "JSXElement": // ESLint has a policy of not assuming any specific JSX behavior.
case "JSXFragment":
return false;
default:
return false;
}
}
/**
* Checks whether or not a node is `null` or `undefined`. Similar to the one
* found in ast-utils.js, but this one correctly handles the edge case that
* `undefined` has been redefined.
* @param {Scope} scope Scope in which the expression was found.
* @param {ASTNode} node A node to check.
* @returns {boolean} Whether or not the node is a `null` or `undefined`.
* @public
*/
function isNullOrUndefined(scope, node) {
return (
isNullLiteral(node) ||
(node.type === "Identifier" && node.name === "undefined" && isReferenceToGlobalVariable(scope, node)) ||
(node.type === "UnaryExpression" && node.operator === "void")
);
}
/**
* Checks if one operand will cause the result to be constant.
* @param {Scope} scope Scope in which the expression was found.
* @param {ASTNode} a One side of the expression
* @param {ASTNode} b The other side of the expression
* @param {string} operator The binary expression operator
* @returns {ASTNode | null} The node which will cause the expression to have a constant result.
*/
function findBinaryExpressionConstantOperand(scope, a, b, operator) {
if (operator === "==" || operator === "!=") {
if (
(isNullOrUndefined(scope, a) && hasConstantNullishness(scope, b)) ||
(isStaticBoolean(scope, a) && hasConstantLooseBooleanComparison(scope, b))
) {
return b;
}
} else if (operator === "===" || operator === "!==") {
if (
(isNullOrUndefined(scope, a) && hasConstantNullishness(scope, b)) ||
(isStaticBoolean(scope, a) && hasConstantStrictBooleanComparison(scope, b))
) {
return b;
}
}
return null;
}
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "problem",
docs: {
description: "Disallow expressions where the operation doesn't affect the value",
recommended: false,
url: "https://eslint.org/docs/rules/no-constant-binary-expression"
},
schema: [],
messages: {
constantBinaryOperand: "Unexpected constant binary expression. Compares constantly with the {{otherSide}}-hand side of the `{{operator}}`.",
constantShortCircuit: "Unexpected constant {{property}} on the left-hand side of a `{{operator}}` expression.",
alwaysNew: "Unexpected comparison to newly constructed object. These two values can never be equal.",
bothAlwaysNew: "Unexpected comparison of two newly constructed objects. These two values can never be equal."
}
},
create(context) {
return {
LogicalExpression(node) {
const { operator, left } = node;
const scope = context.getScope();
if ((operator === "&&" || operator === "||") && isConstant(scope, left, true)) {
context.report({ node: left, messageId: "constantShortCircuit", data: { property: "truthiness", operator } });
} else if (operator === "??" && hasConstantNullishness(scope, left)) {
context.report({ node: left, messageId: "constantShortCircuit", data: { property: "nullishness", operator } });
}
},
BinaryExpression(node) {
const scope = context.getScope();
const { right, left, operator } = node;
const rightConstantOperand = findBinaryExpressionConstantOperand(scope, left, right, operator);
const leftConstantOperand = findBinaryExpressionConstantOperand(scope, right, left, operator);
if (rightConstantOperand) {
context.report({ node: rightConstantOperand, messageId: "constantBinaryOperand", data: { operator, otherSide: "left" } });
} else if (leftConstantOperand) {
context.report({ node: leftConstantOperand, messageId: "constantBinaryOperand", data: { operator, otherSide: "right" } });
} else if (operator === "===" || operator === "!==") {
if (isAlwaysNew(scope, left)) {
context.report({ node: left, messageId: "alwaysNew" });
} else if (isAlwaysNew(scope, right)) {
context.report({ node: right, messageId: "alwaysNew" });
}
} else if (operator === "==" || operator === "!=") {
/*
* If both sides are "new", then both sides are objects and
* therefore they will be compared by reference even with `==`
* equality.
*/
if (isAlwaysNew(scope, left) && isAlwaysNew(scope, right)) {
context.report({ node: left, messageId: "bothAlwaysNew" });
}
}
}
/*
* In theory we could handle short-circuiting assignment operators,
* for some constant values, but that would require walking the
* scope to find the value of the variable being assigned. This is
* dependant on https://github.com/eslint/eslint/issues/13776
*
* AssignmentExpression() {},
*/
};
}
};

View file

@ -5,6 +5,8 @@
"use strict";
const { isConstant } = require("./utils/ast-utils");
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
@ -13,13 +15,13 @@
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "problem",
docs: {
description: "disallow constant expressions in conditions",
category: "Possible Errors",
description: "Disallow constant expressions in conditions",
recommended: true,
url: "https://eslint.org/docs/rules/no-constant-condition"
},
@ -53,153 +55,6 @@ module.exports = {
// Helpers
//--------------------------------------------------------------------------
/**
* Returns literal's value converted to the Boolean type
* @param {ASTNode} node any `Literal` node
* @returns {boolean | null} `true` when node is truthy, `false` when node is falsy,
* `null` when it cannot be determined.
*/
function getBooleanValue(node) {
if (node.value === null) {
/*
* it might be a null literal or bigint/regex literal in unsupported environments .
* https://github.com/estree/estree/blob/14df8a024956ea289bd55b9c2226a1d5b8a473ee/es5.md#regexpliteral
* https://github.com/estree/estree/blob/14df8a024956ea289bd55b9c2226a1d5b8a473ee/es2020.md#bigintliteral
*/
if (node.raw === "null") {
return false;
}
// regex is always truthy
if (typeof node.regex === "object") {
return true;
}
return null;
}
return !!node.value;
}
/**
* Checks if a branch node of LogicalExpression short circuits the whole condition
* @param {ASTNode} node The branch of main condition which needs to be checked
* @param {string} operator The operator of the main LogicalExpression.
* @returns {boolean} true when condition short circuits whole condition
*/
function isLogicalIdentity(node, operator) {
switch (node.type) {
case "Literal":
return (operator === "||" && getBooleanValue(node) === true) ||
(operator === "&&" && getBooleanValue(node) === false);
case "UnaryExpression":
return (operator === "&&" && node.operator === "void");
case "LogicalExpression":
/*
* handles `a && false || b`
* `false` is an identity element of `&&` but not `||`
*/
return operator === node.operator &&
(
isLogicalIdentity(node.left, operator) ||
isLogicalIdentity(node.right, operator)
);
case "AssignmentExpression":
return ["||=", "&&="].includes(node.operator) &&
operator === node.operator.slice(0, -1) &&
isLogicalIdentity(node.right, operator);
// no default
}
return false;
}
/**
* Checks if a node has a constant truthiness value.
* @param {ASTNode} node The AST node to check.
* @param {boolean} inBooleanPosition `false` if checking branch of a condition.
* `true` in all other cases
* @returns {Bool} true when node's truthiness is constant
* @private
*/
function isConstant(node, inBooleanPosition) {
// node.elements can return null values in the case of sparse arrays ex. [,]
if (!node) {
return true;
}
switch (node.type) {
case "Literal":
case "ArrowFunctionExpression":
case "FunctionExpression":
case "ObjectExpression":
return true;
case "TemplateLiteral":
return (inBooleanPosition && node.quasis.some(quasi => quasi.value.cooked.length)) ||
node.expressions.every(exp => isConstant(exp, inBooleanPosition));
case "ArrayExpression": {
if (node.parent.type === "BinaryExpression" && node.parent.operator === "+") {
return node.elements.every(element => isConstant(element, false));
}
return true;
}
case "UnaryExpression":
if (
node.operator === "void" ||
node.operator === "typeof" && inBooleanPosition
) {
return true;
}
if (node.operator === "!") {
return isConstant(node.argument, true);
}
return isConstant(node.argument, false);
case "BinaryExpression":
return isConstant(node.left, false) &&
isConstant(node.right, false) &&
node.operator !== "in";
case "LogicalExpression": {
const isLeftConstant = isConstant(node.left, inBooleanPosition);
const isRightConstant = isConstant(node.right, inBooleanPosition);
const isLeftShortCircuit = (isLeftConstant && isLogicalIdentity(node.left, node.operator));
const isRightShortCircuit = (inBooleanPosition && isRightConstant && isLogicalIdentity(node.right, node.operator));
return (isLeftConstant && isRightConstant) ||
isLeftShortCircuit ||
isRightShortCircuit;
}
case "AssignmentExpression":
if (node.operator === "=") {
return isConstant(node.right, inBooleanPosition);
}
if (["||=", "&&="].includes(node.operator) && inBooleanPosition) {
return isLogicalIdentity(node.right, node.operator.slice(0, -1));
}
return false;
case "SequenceExpression":
return isConstant(node.expressions[node.expressions.length - 1], inBooleanPosition);
// no default
}
return false;
}
/**
* Tracks when the given node contains a constant condition.
* @param {ASTNode} node The AST node to check.
@ -207,7 +62,7 @@ module.exports = {
* @private
*/
function trackConstantConditionLoop(node) {
if (node.test && isConstant(node.test, true)) {
if (node.test && isConstant(context.getScope(), node.test, true)) {
loopsInCurrentScope.add(node);
}
}
@ -232,7 +87,7 @@ module.exports = {
* @private
*/
function reportIfConstant(node) {
if (node.test && isConstant(node.test, true)) {
if (node.test && isConstant(context.getScope(), node.test, true)) {
context.report({ node: node.test, messageId: "unexpected" });
}
}

View file

@ -9,13 +9,13 @@
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "problem",
docs: {
description: "disallow returning value from constructor",
category: "Best Practices",
description: "Disallow returning value from constructor",
recommended: false,
url: "https://eslint.org/docs/rules/no-constructor-return"
},

View file

@ -9,13 +9,13 @@
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "suggestion",
docs: {
description: "disallow `continue` statements",
category: "Stylistic Issues",
description: "Disallow `continue` statements",
recommended: false,
url: "https://eslint.org/docs/rules/no-continue"
},

View file

@ -30,10 +30,12 @@ const collector = new (class {
}
}
collectControlChars(regexpStr) {
collectControlChars(regexpStr, flags) {
const uFlag = typeof flags === "string" && flags.includes("u");
try {
this._source = regexpStr;
this._validator.validatePattern(regexpStr); // Call onCharacter hook
this._validator.validatePattern(regexpStr, void 0, void 0, uFlag); // Call onCharacter hook
} catch {
// Ignore syntax errors in RegExp.
@ -46,13 +48,13 @@ const collector = new (class {
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "problem",
docs: {
description: "disallow control characters in regular expressions",
category: "Possible Errors",
description: "Disallow control characters in regular expressions",
recommended: true,
url: "https://eslint.org/docs/rules/no-control-regex"
},
@ -68,13 +70,15 @@ module.exports = {
/**
* Get the regex expression
* @param {ASTNode} node node to evaluate
* @returns {RegExp|null} Regex if found else null
* @param {ASTNode} node `Literal` node to evaluate
* @returns {{ pattern: string, flags: string | null } | null} Regex if found (the given node is either a regex literal
* or a string literal that is the pattern argument of a RegExp constructor call). Otherwise `null`. If flags cannot be determined,
* the `flags` property will be `null`.
* @private
*/
function getRegExpPattern(node) {
function getRegExp(node) {
if (node.regex) {
return node.regex.pattern;
return node.regex;
}
if (typeof node.value === "string" &&
(node.parent.type === "NewExpression" || node.parent.type === "CallExpression") &&
@ -82,7 +86,15 @@ module.exports = {
node.parent.callee.name === "RegExp" &&
node.parent.arguments[0] === node
) {
return node.value;
const pattern = node.value;
const flags =
node.parent.arguments.length > 1 &&
node.parent.arguments[1].type === "Literal" &&
typeof node.parent.arguments[1].value === "string"
? node.parent.arguments[1].value
: null;
return { pattern, flags };
}
return null;
@ -90,10 +102,11 @@ module.exports = {
return {
Literal(node) {
const pattern = getRegExpPattern(node);
const regExp = getRegExp(node);
if (pattern) {
const controlCharacters = collector.collectControlChars(pattern);
if (regExp) {
const { pattern, flags } = regExp;
const controlCharacters = collector.collectControlChars(pattern, flags);
if (controlCharacters.length > 0) {
context.report({

View file

@ -9,13 +9,13 @@
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "problem",
docs: {
description: "disallow the use of `debugger`",
category: "Possible Errors",
description: "Disallow the use of `debugger`",
recommended: true,
url: "https://eslint.org/docs/rules/no-debugger"
},

View file

@ -9,13 +9,13 @@
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "suggestion",
docs: {
description: "disallow deleting variables",
category: "Variables",
description: "Disallow deleting variables",
recommended: true,
url: "https://eslint.org/docs/rules/no-delete-var"
},

Some files were not shown because too many files have changed in this diff Show more