diff --git a/tools/node_modules/eslint/README.md b/tools/node_modules/eslint/README.md index 5f5be9c730927b..b75051e6ecd53b 100644 --- a/tools/node_modules/eslint/README.md +++ b/tools/node_modules/eslint/README.md @@ -252,7 +252,7 @@ The following companies, organizations, and individuals support ESLint's ongoing

Gold Sponsors

Shopify Salesforce MagicLab Airbnb Facebook Open Source

Silver Sponsors

AMP Project

Bronze Sponsors

-

BonusFinder Deutschland Top Web Design Agencies Bugsnag Stability Monitoring Crosswordsolver Mixpanel VPS Server Free Icons by Icons8 UI UX Design Agencies clay Discord ThemeIsle TekHattan Marfeel Fire Stick Tricks JSHeroes

+

Nettikasinot.org BonusFinder Deutschland Top Web Design Agencies Bugsnag Stability Monitoring Crosswordsolver Mixpanel VPS Server Free Icons by Icons8 UI UX Design Agencies clay Discord ThemeIsle TekHattan Marfeel Fire Stick Tricks JSHeroes

## Technology Sponsors diff --git a/tools/node_modules/eslint/conf/category-list.json b/tools/node_modules/eslint/conf/category-list.json index 5427667b09e332..6609734950a443 100644 --- a/tools/node_modules/eslint/conf/category-list.json +++ b/tools/node_modules/eslint/conf/category-list.json @@ -10,12 +10,12 @@ ], "deprecated": { "name": "Deprecated", - "description": "These rules have been deprecated in accordance with the [deprecation policy](/docs/user-guide/rule-deprecation), and replaced by newer rules:", + "description": "These rules have been deprecated in accordance with the deprecation policy, and replaced by newer rules:", "rules": [] }, "removed": { "name": "Removed", - "description": "These rules from older versions of ESLint (before the [deprecation policy](/docs/user-guide/rule-deprecation) existed) have been replaced by newer rules:", + "description": "These rules from older versions of ESLint (before the deprecation policy existed) have been replaced by newer rules:", "rules": [ { "removed": "generator-star", "replacedBy": ["generator-star-spacing"] }, { "removed": "global-strict", "replacedBy": ["strict"] }, diff --git a/tools/node_modules/eslint/conf/eslint-recommended.js b/tools/node_modules/eslint/conf/eslint-recommended.js index e915ec449040e9..2137685fb7c63e 100644 --- a/tools/node_modules/eslint/conf/eslint-recommended.js +++ b/tools/node_modules/eslint/conf/eslint-recommended.js @@ -26,6 +26,7 @@ module.exports = { "no-delete-var": "error", "no-dupe-args": "error", "no-dupe-class-members": "error", + "no-dupe-else-if": "error", "no-dupe-keys": "error", "no-duplicate-case": "error", "no-empty": "error", @@ -37,6 +38,7 @@ module.exports = { "no-fallthrough": "error", "no-func-assign": "error", "no-global-assign": "error", + "no-import-assign": "error", "no-inner-declarations": "error", "no-invalid-regexp": "error", "no-irregular-whitespace": "error", @@ -49,6 +51,7 @@ module.exports = { "no-redeclare": "error", "no-regex-spaces": "error", "no-self-assign": "error", + "no-setter-return": "error", "no-shadow-restricted-names": "error", "no-sparse-arrays": "error", "no-this-before-super": "error", diff --git a/tools/node_modules/eslint/lib/rule-tester/rule-tester.js b/tools/node_modules/eslint/lib/rule-tester/rule-tester.js index 2ac26534fcc0ea..5180f87a316f85 100644 --- a/tools/node_modules/eslint/lib/rule-tester/rule-tester.js +++ b/tools/node_modules/eslint/lib/rule-tester/rule-tester.js @@ -119,6 +119,32 @@ const RuleTesterParameters = [ "output" ]; +/* + * All allowed property names in error objects. + */ +const errorObjectParameters = new Set([ + "message", + "messageId", + "data", + "type", + "line", + "column", + "endLine", + "endColumn", + "suggestions" +]); +const friendlyErrorObjectParameterList = `[${[...errorObjectParameters].map(key => `'${key}'`).join(", ")}]`; + +/* + * All allowed property names in suggestion objects. + */ +const suggestionObjectParameters = new Set([ + "desc", + "messageId", + "output" +]); +const friendlySuggestionObjectParameterList = `[${[...suggestionObjectParameters].map(key => `'${key}'`).join(", ")}]`; + const hasOwnProperty = Function.call.bind(Object.hasOwnProperty); /** @@ -573,13 +599,21 @@ class RuleTester { // Just an error message. assertMessageMatches(message.message, error); - } else if (typeof error === "object") { + } else if (typeof error === "object" && error !== null) { /* * Error object. * This may have a message, messageId, data, node type, line, and/or * column. */ + + Object.keys(error).forEach(propertyName => { + assert.ok( + errorObjectParameters.has(propertyName), + `Invalid error property name '${propertyName}'. Expected one of ${friendlyErrorObjectParameterList}.` + ); + }); + if (hasOwnProperty(error, "message")) { assert.ok(!hasOwnProperty(error, "messageId"), "Error should not specify both 'message' and a 'messageId'."); assert.ok(!hasOwnProperty(error, "data"), "Error should not specify both 'data' and 'message'."); @@ -654,6 +688,17 @@ class RuleTester { assert.strictEqual(message.suggestions.length, error.suggestions.length, `Error should have ${error.suggestions.length} suggestions. Instead found ${message.suggestions.length} suggestions`); error.suggestions.forEach((expectedSuggestion, index) => { + assert.ok( + typeof expectedSuggestion === "object" && expectedSuggestion !== null, + "Test suggestion in 'suggestions' array must be an object." + ); + Object.keys(expectedSuggestion).forEach(propertyName => { + assert.ok( + suggestionObjectParameters.has(propertyName), + `Invalid suggestion property name '${propertyName}'. Expected one of ${friendlySuggestionObjectParameterList}.` + ); + }); + const actualSuggestion = message.suggestions[index]; /** diff --git a/tools/node_modules/eslint/lib/rules/accessor-pairs.js b/tools/node_modules/eslint/lib/rules/accessor-pairs.js index 3a32db6eac7214..02548258ca2e28 100644 --- a/tools/node_modules/eslint/lib/rules/accessor-pairs.js +++ b/tools/node_modules/eslint/lib/rules/accessor-pairs.js @@ -171,7 +171,7 @@ module.exports = { }, enforceForClassMembers: { type: "boolean", - default: false + default: true } }, additionalProperties: false @@ -190,7 +190,7 @@ module.exports = { const config = context.options[0] || {}; const checkGetWithoutSet = config.getWithoutSet === true; const checkSetWithoutGet = config.setWithoutGet !== false; - const enforceForClassMembers = config.enforceForClassMembers === true; + const enforceForClassMembers = config.enforceForClassMembers !== false; const sourceCode = context.getSourceCode(); /** diff --git a/tools/node_modules/eslint/lib/rules/arrow-body-style.js b/tools/node_modules/eslint/lib/rules/arrow-body-style.js index 8d3b400037a6d5..9d5c77d8573d6a 100644 --- a/tools/node_modules/eslint/lib/rules/arrow-body-style.js +++ b/tools/node_modules/eslint/lib/rules/arrow-body-style.js @@ -91,7 +91,7 @@ module.exports = { * @returns {Token} The found closing parenthesis token. */ function findClosingParen(token) { - let node = sourceCode.getNodeByRangeIndex(token.range[1]); + let node = sourceCode.getNodeByRangeIndex(token.range[0]); while (!astUtils.isParenthesised(sourceCode, node)) { node = node.parent; @@ -206,24 +206,35 @@ module.exports = { fix(fixer) { const fixes = []; const arrowToken = sourceCode.getTokenBefore(arrowBody, astUtils.isArrowToken); - const firstBodyToken = sourceCode.getTokenAfter(arrowToken); - const lastBodyToken = sourceCode.getLastToken(node); + const [firstTokenAfterArrow, secondTokenAfterArrow] = sourceCode.getTokensAfter(arrowToken, { count: 2 }); + const lastToken = sourceCode.getLastToken(node); const isParenthesisedObjectLiteral = - astUtils.isOpeningParenToken(firstBodyToken) && - astUtils.isOpeningBraceToken(sourceCode.getTokenAfter(firstBodyToken)); - - // Wrap the value by a block and a return statement. - fixes.push( - fixer.insertTextBefore(firstBodyToken, "{return "), - fixer.insertTextAfter(lastBodyToken, "}") - ); + astUtils.isOpeningParenToken(firstTokenAfterArrow) && + astUtils.isOpeningBraceToken(secondTokenAfterArrow); // If the value is object literal, remove parentheses which were forced by syntax. if (isParenthesisedObjectLiteral) { - fixes.push( - fixer.remove(firstBodyToken), - fixer.remove(findClosingParen(firstBodyToken)) - ); + const openingParenToken = firstTokenAfterArrow; + const openingBraceToken = secondTokenAfterArrow; + + if (astUtils.isTokenOnSameLine(openingParenToken, openingBraceToken)) { + fixes.push(fixer.replaceText(openingParenToken, "{return ")); + } else { + + // Avoid ASI + fixes.push( + fixer.replaceText(openingParenToken, "{"), + fixer.insertTextBefore(openingBraceToken, "return ") + ); + } + + // Closing paren for the object doesn't have to be lastToken, e.g.: () => ({}).foo() + fixes.push(fixer.remove(findClosingParen(openingBraceToken))); + fixes.push(fixer.insertTextAfter(lastToken, "}")); + + } else { + fixes.push(fixer.insertTextBefore(firstTokenAfterArrow, "{return ")); + fixes.push(fixer.insertTextAfter(lastToken, "}")); } return fixes; diff --git a/tools/node_modules/eslint/lib/rules/computed-property-spacing.js b/tools/node_modules/eslint/lib/rules/computed-property-spacing.js index 48bea6a6fdc8f4..53fdb8f4e41f95 100644 --- a/tools/node_modules/eslint/lib/rules/computed-property-spacing.js +++ b/tools/node_modules/eslint/lib/rules/computed-property-spacing.js @@ -32,7 +32,7 @@ module.exports = { properties: { enforceForClassMembers: { type: "boolean", - default: false + default: true } }, additionalProperties: false @@ -51,7 +51,7 @@ module.exports = { create(context) { const sourceCode = context.getSourceCode(); const propertyNameMustBeSpaced = context.options[0] === "always"; // default is "never" - const enforceForClassMembers = context.options[1] && context.options[1].enforceForClassMembers; + const enforceForClassMembers = !context.options[1] || context.options[1].enforceForClassMembers; //-------------------------------------------------------------------------- // Helpers diff --git a/tools/node_modules/eslint/lib/rules/curly.js b/tools/node_modules/eslint/lib/rules/curly.js index ee2fe4dceb82bf..29f00c0ad0b617 100644 --- a/tools/node_modules/eslint/lib/rules/curly.js +++ b/tools/node_modules/eslint/lib/rules/curly.js @@ -141,33 +141,14 @@ module.exports = { } /** - * Checks a given IfStatement node requires braces of the consequent chunk. - * This returns `true` when below: - * - * 1. The given node has the `alternate` node. - * 2. There is a `IfStatement` which doesn't have `alternate` node in the - * trailing statement chain of the `consequent` node. - * @param {ASTNode} node A IfStatement node to check. - * @returns {boolean} `true` if the node requires braces of the consequent chunk. + * Determines whether the given node has an `else` keyword token as the first token after. + * @param {ASTNode} node The node to check. + * @returns {boolean} `true` if the node is followed by an `else` keyword token. */ - function requiresBraceOfConsequent(node) { - if (node.alternate && node.consequent.type === "BlockStatement") { - if (node.consequent.body.length >= 2) { - return true; - } - - for ( - let currentNode = node.consequent.body[0]; - currentNode; - currentNode = astUtils.getTrailingStatement(currentNode) - ) { - if (currentNode.type === "IfStatement" && !currentNode.alternate) { - return true; - } - } - } + function isFollowedByElseKeyword(node) { + const nextToken = sourceCode.getTokenAfter(node); - return false; + return Boolean(nextToken) && isElseKeywordToken(nextToken); } /** @@ -224,6 +205,110 @@ module.exports = { return false; } + /** + * Determines whether the code represented by the given node contains an `if` statement + * that would become associated with an `else` keyword directly appended to that code. + * + * Examples where it returns `true`: + * + * if (a) + * foo(); + * + * if (a) { + * foo(); + * } + * + * if (a) + * foo(); + * else if (b) + * bar(); + * + * while (a) + * if (b) + * if(c) + * foo(); + * else + * bar(); + * + * Examples where it returns `false`: + * + * if (a) + * foo(); + * else + * bar(); + * + * while (a) { + * if (b) + * if(c) + * foo(); + * else + * bar(); + * } + * + * while (a) + * if (b) { + * if(c) + * foo(); + * } + * else + * bar(); + * @param {ASTNode} node Node representing the code to check. + * @returns {boolean} `true` if an `if` statement within the code would become associated with an `else` appended to that code. + */ + function hasUnsafeIf(node) { + switch (node.type) { + case "IfStatement": + if (!node.alternate) { + return true; + } + return hasUnsafeIf(node.alternate); + case "ForStatement": + case "ForInStatement": + case "ForOfStatement": + case "LabeledStatement": + case "WithStatement": + case "WhileStatement": + return hasUnsafeIf(node.body); + default: + return false; + } + } + + /** + * Determines whether the existing curly braces around the single statement are necessary to preserve the semantics of the code. + * The braces, which make the given block body, are necessary in either of the following situations: + * + * 1. The statement is a lexical declaration. + * 2. Without the braces, an `if` within the statement would become associated with an `else` after the closing brace: + * + * if (a) { + * if (b) + * foo(); + * } + * else + * bar(); + * + * if (a) + * while (b) + * while (c) { + * while (d) + * if (e) + * while(f) + * foo(); + * } + * else + * bar(); + * @param {ASTNode} node `BlockStatement` body with exactly one statement directly inside. The statement can have its own nested statements. + * @returns {boolean} `true` if the braces are necessary - removing them (replacing the given `BlockStatement` body with its single statement content) + * would change the semantics of the code or produce a syntax error. + */ + function areBracesNecessary(node) { + const statement = node.body[0]; + + return isLexicalDeclaration(statement) || + hasUnsafeIf(statement) && isFollowedByElseKeyword(node); + } + /** * Prepares to check the body of a node to see if it's a block statement. * @param {ASTNode} node The node to report if there's a problem. @@ -242,30 +327,29 @@ module.exports = { const hasBlock = (body.type === "BlockStatement"); let expected = null; - if (node.type === "IfStatement" && node.consequent === body && requiresBraceOfConsequent(node)) { + if (hasBlock && (body.body.length !== 1 || areBracesNecessary(body))) { expected = true; } else if (multiOnly) { - if (hasBlock && body.body.length === 1 && !isLexicalDeclaration(body.body[0])) { - expected = false; - } + expected = false; } else if (multiLine) { if (!isCollapsedOneLiner(body)) { expected = true; } + + // otherwise, the body is allowed to have braces or not to have braces + } else if (multiOrNest) { - if (hasBlock && body.body.length === 1 && isOneLiner(body.body[0])) { - const leadingComments = sourceCode.getCommentsBefore(body.body[0]); - const isLexDef = isLexicalDeclaration(body.body[0]); - - if (isLexDef) { - expected = true; - } else { - expected = leadingComments.length > 0; - } - } else if (!isOneLiner(body)) { - expected = true; + if (hasBlock) { + const statement = body.body[0]; + const leadingCommentsInBlock = sourceCode.getCommentsBefore(statement); + + expected = !isOneLiner(statement) || leadingCommentsInBlock.length > 0; + } else { + expected = !isOneLiner(body); } } else { + + // default "all" expected = true; } diff --git a/tools/node_modules/eslint/lib/rules/func-names.js b/tools/node_modules/eslint/lib/rules/func-names.js index 1341d03630823f..ecfedb9e0e99c7 100644 --- a/tools/node_modules/eslint/lib/rules/func-names.js +++ b/tools/node_modules/eslint/lib/rules/func-names.js @@ -119,8 +119,7 @@ module.exports = { (parent.type === "VariableDeclarator" && parent.id.type === "Identifier" && parent.init === node) || (parent.type === "Property" && parent.value === node) || (parent.type === "AssignmentExpression" && parent.left.type === "Identifier" && parent.right === node) || - (parent.type === "ExportDefaultDeclaration" && parent.declaration === node) || - (parent.type === "AssignmentPattern" && parent.right === node); + (parent.type === "AssignmentPattern" && parent.left.type === "Identifier" && parent.right === node); } /** @@ -151,33 +150,41 @@ module.exports = { }); } - return { - "FunctionExpression:exit"(node) { + /** + * The listener for function nodes. + * @param {ASTNode} node function node + * @returns {void} + */ + function handleFunction(node) { - // Skip recursive functions. - const nameVar = context.getDeclaredVariables(node)[0]; + // Skip recursive functions. + const nameVar = context.getDeclaredVariables(node)[0]; - if (isFunctionName(nameVar) && nameVar.references.length > 0) { - return; - } + if (isFunctionName(nameVar) && nameVar.references.length > 0) { + return; + } - const hasName = Boolean(node.id && node.id.name); - const config = getConfigForNode(node); - - if (config === "never") { - if (hasName) { - reportUnexpectedNamedFunction(node); - } - } else if (config === "as-needed") { - if (!hasName && !hasInferredName(node)) { - reportUnexpectedUnnamedFunction(node); - } - } else { - if (!hasName && !isObjectOrClassMethod(node)) { - reportUnexpectedUnnamedFunction(node); - } + const hasName = Boolean(node.id && node.id.name); + const config = getConfigForNode(node); + + if (config === "never") { + if (hasName && node.type !== "FunctionDeclaration") { + reportUnexpectedNamedFunction(node); + } + } else if (config === "as-needed") { + if (!hasName && !hasInferredName(node)) { + reportUnexpectedUnnamedFunction(node); + } + } else { + if (!hasName && !isObjectOrClassMethod(node)) { + reportUnexpectedUnnamedFunction(node); } } + } + + return { + "FunctionExpression:exit": handleFunction, + "ExportDefaultDeclaration > FunctionDeclaration": handleFunction }; } }; diff --git a/tools/node_modules/eslint/lib/rules/id-blacklist.js b/tools/node_modules/eslint/lib/rules/id-blacklist.js index cb6e5e215f6c60..ddff9363aee8a8 100644 --- a/tools/node_modules/eslint/lib/rules/id-blacklist.js +++ b/tools/node_modules/eslint/lib/rules/id-blacklist.js @@ -81,6 +81,28 @@ module.exports = { ); } + /** + * Checks whether the given node is a renamed identifier node in an ObjectPattern destructuring. + * + * Examples: + * const { a : b } = foo; // node `a` is renamed node. + * @param {ASTNode} node `Identifier` node to check. + * @returns {boolean} `true` if the node is a renamed node in an ObjectPattern destructuring. + */ + function isRenamedInDestructuring(node) { + const parent = node.parent; + + return ( + ( + !parent.computed && + parent.type === "Property" && + parent.parent.type === "ObjectPattern" && + parent.value !== node && + parent.key === node + ) + ); + } + /** * Verifies if we should report an error or not. * @param {ASTNode} node The node to check @@ -92,8 +114,8 @@ module.exports = { return ( parent.type !== "CallExpression" && parent.type !== "NewExpression" && - parent.parent.type !== "ObjectPattern" && !isRenamedImport(node) && + !isRenamedInDestructuring(node) && isInvalid(node.name) ); } @@ -141,6 +163,24 @@ module.exports = { if (isInvalid(name)) { report(node); } + + // Report the last identifier in an ObjectPattern destructuring. + } else if ( + ( + effectiveParent.type === "Property" && + effectiveParent.value === node.parent && + effectiveParent.parent.type === "ObjectPattern" + ) || + effectiveParent.type === "RestElement" || + effectiveParent.type === "ArrayPattern" || + ( + effectiveParent.type === "AssignmentPattern" && + effectiveParent.left === node.parent + ) + ) { + if (isInvalid(name)) { + report(node); + } } } else if (shouldReport(node)) { diff --git a/tools/node_modules/eslint/lib/rules/id-length.js b/tools/node_modules/eslint/lib/rules/id-length.js index c8586ea3481895..a68873ac06289b 100644 --- a/tools/node_modules/eslint/lib/rules/id-length.js +++ b/tools/node_modules/eslint/lib/rules/id-length.js @@ -63,6 +63,7 @@ module.exports = { return obj; }, {}); + const reportedNode = new Set(); const SUPPORTED_EXPRESSIONS = { MemberExpression: properties && function(parent) { @@ -82,8 +83,15 @@ module.exports = { VariableDeclarator(parent, node) { return parent.id === node; }, - Property: properties && function(parent, node) { - return parent.key === node; + Property(parent, node) { + + if (parent.parent.type === "ObjectPattern") { + return ( + parent.value !== parent.key && parent.value === node || + parent.value === parent.key && parent.key === node && properties + ); + } + return properties && !parent.computed && parent.key === node; }, ImportDefaultSpecifier: true, RestElement: true, @@ -92,7 +100,8 @@ module.exports = { ClassDeclaration: true, FunctionDeclaration: true, MethodDefinition: true, - CatchClause: true + CatchClause: true, + ArrayPattern: true }; return { @@ -109,7 +118,8 @@ module.exports = { const isValidExpression = SUPPORTED_EXPRESSIONS[parent.type]; - if (isValidExpression && (isValidExpression === true || isValidExpression(parent, node))) { + if (isValidExpression && !reportedNode.has(node) && (isValidExpression === true || isValidExpression(parent, node))) { + reportedNode.add(node); context.report({ node, messageId: isShort ? "tooShort" : "tooLong", diff --git a/tools/node_modules/eslint/lib/rules/indent-legacy.js b/tools/node_modules/eslint/lib/rules/indent-legacy.js index f1c024c368475d..50010d3f7acb16 100644 --- a/tools/node_modules/eslint/lib/rules/indent-legacy.js +++ b/tools/node_modules/eslint/lib/rules/indent-legacy.js @@ -696,20 +696,6 @@ module.exports = { return startLine === endLine; } - /** - * Check to see if the first element inside an array is an object and on the same line as the node - * If the node is not an array then it will return false. - * @param {ASTNode} node node to check - * @returns {boolean} success/failure - */ - function isFirstArrayElementOnSameLine(node) { - if (node.type === "ArrayExpression" && node.elements[0]) { - return node.elements[0].loc.start.line === node.loc.start.line && node.elements[0].type === "ObjectExpression"; - } - return false; - - } - /** * Check indent for array block content or object block content * @param {ASTNode} node node to examine @@ -776,8 +762,6 @@ module.exports = { nodeIndent += indentSize; } } - } else if (!parentVarNode && !isFirstArrayElementOnSameLine(parent) && parent.type !== "MemberExpression" && parent.type !== "ExpressionStatement" && parent.type !== "AssignmentExpression" && parent.type !== "Property") { - nodeIndent += indentSize; } checkFirstNodeLineIndent(node, nodeIndent); diff --git a/tools/node_modules/eslint/lib/rules/no-dupe-else-if.js b/tools/node_modules/eslint/lib/rules/no-dupe-else-if.js index a165e16607d2f0..cbeb437da1e7ec 100644 --- a/tools/node_modules/eslint/lib/rules/no-dupe-else-if.js +++ b/tools/node_modules/eslint/lib/rules/no-dupe-else-if.js @@ -53,7 +53,7 @@ module.exports = { docs: { description: "disallow duplicate conditions in if-else-if chains", category: "Possible Errors", - recommended: false, + recommended: true, url: "https://eslint.org/docs/rules/no-dupe-else-if" }, diff --git a/tools/node_modules/eslint/lib/rules/no-eval.js b/tools/node_modules/eslint/lib/rules/no-eval.js index 9e56fb00b9fc5b..a293b04aa37de2 100644 --- a/tools/node_modules/eslint/lib/rules/no-eval.js +++ b/tools/node_modules/eslint/lib/rules/no-eval.js @@ -158,7 +158,7 @@ module.exports = { context.report({ node: reportNode, - loc: locationNode.loc.start, + loc: locationNode.loc, messageId: "unexpected" }); } diff --git a/tools/node_modules/eslint/lib/rules/no-extra-boolean-cast.js b/tools/node_modules/eslint/lib/rules/no-extra-boolean-cast.js index 336f601d1652de..8ccd0bce9060b1 100644 --- a/tools/node_modules/eslint/lib/rules/no-extra-boolean-cast.js +++ b/tools/node_modules/eslint/lib/rules/no-extra-boolean-cast.js @@ -26,7 +26,16 @@ module.exports = { url: "https://eslint.org/docs/rules/no-extra-boolean-cast" }, - schema: [], + schema: [{ + type: "object", + properties: { + enforceForLogicalOperands: { + type: "boolean", + default: false + } + }, + additionalProperties: false + }], fixable: "code", messages: { @@ -47,23 +56,67 @@ module.exports = { "ForStatement" ]; + /** + * Check if a node is a Boolean function or constructor. + * @param {ASTNode} node the node + * @returns {boolean} If the node is Boolean function or constructor + */ + function isBooleanFunctionOrConstructorCall(node) { + + // Boolean() and new Boolean() + return (node.type === "CallExpression" || node.type === "NewExpression") && + node.callee.type === "Identifier" && + node.callee.name === "Boolean"; + } + + /** + * Checks whether the node is a logical expression and that the option is enabled + * @param {ASTNode} node the node + * @returns {boolean} if the node is a logical expression and option is enabled + */ + function isLogicalContext(node) { + return node.type === "LogicalExpression" && + (node.operator === "||" || node.operator === "&&") && + (context.options.length && context.options[0].enforceForLogicalOperands === true); + + } + + /** * Check if a node is in a context where its value would be coerced to a boolean at runtime. * @param {ASTNode} node The node - * @param {ASTNode} parent Its parent * @returns {boolean} If it is in a boolean context */ - function isInBooleanContext(node, parent) { + function isInBooleanContext(node) { return ( - (BOOLEAN_NODE_TYPES.indexOf(parent.type) !== -1 && - node === parent.test) || + (isBooleanFunctionOrConstructorCall(node.parent) && + node === node.parent.arguments[0]) || + + (BOOLEAN_NODE_TYPES.indexOf(node.parent.type) !== -1 && + node === node.parent.test) || // ! - (parent.type === "UnaryExpression" && - parent.operator === "!") + (node.parent.type === "UnaryExpression" && + node.parent.operator === "!") + ); + } + + /** + * Checks whether the node is a context that should report an error + * Acts recursively if it is in a logical context + * @param {ASTNode} node the node + * @returns {boolean} If the node is in one of the flagged contexts + */ + function isInFlaggedContext(node) { + return isInBooleanContext(node) || + (isLogicalContext(node.parent) && + + // For nested logical statements + isInFlaggedContext(node.parent) ); } + /** * Check if a node has comments inside. * @param {ASTNode} node The node to check. @@ -75,24 +128,18 @@ module.exports = { return { UnaryExpression(node) { - const ancestors = context.getAncestors(), - parent = ancestors.pop(), - grandparent = ancestors.pop(); + const parent = node.parent; + // Exit early if it's guaranteed not to match if (node.operator !== "!" || - parent.type !== "UnaryExpression" || - parent.operator !== "!") { + parent.type !== "UnaryExpression" || + parent.operator !== "!") { return; } - if (isInBooleanContext(parent, grandparent) || - // Boolean() and new Boolean() - ((grandparent.type === "CallExpression" || grandparent.type === "NewExpression") && - grandparent.callee.type === "Identifier" && - grandparent.callee.name === "Boolean") - ) { + if (isInFlaggedContext(parent)) { context.report({ node: parent, messageId: "unexpectedNegation", @@ -110,6 +157,10 @@ module.exports = { prefix = " "; } + if (astUtils.getPrecedence(node.argument) < astUtils.getPrecedence(parent.parent)) { + return fixer.replaceText(parent, `(${sourceCode.getText(node.argument)})`); + } + return fixer.replaceText(parent, prefix + sourceCode.getText(node.argument)); } }); @@ -122,7 +173,7 @@ module.exports = { return; } - if (isInBooleanContext(node, parent)) { + if (isInFlaggedContext(node)) { context.report({ node, messageId: "unexpectedCall", diff --git a/tools/node_modules/eslint/lib/rules/no-import-assign.js b/tools/node_modules/eslint/lib/rules/no-import-assign.js index 0865cf9a97704d..32e445ff68b3ef 100644 --- a/tools/node_modules/eslint/lib/rules/no-import-assign.js +++ b/tools/node_modules/eslint/lib/rules/no-import-assign.js @@ -180,7 +180,7 @@ module.exports = { docs: { description: "disallow assigning to imported bindings", category: "Possible Errors", - recommended: false, + recommended: true, url: "https://eslint.org/docs/rules/no-import-assign" }, diff --git a/tools/node_modules/eslint/lib/rules/no-restricted-modules.js b/tools/node_modules/eslint/lib/rules/no-restricted-modules.js index abbc30a05fb53d..abd8d5cbe29381 100644 --- a/tools/node_modules/eslint/lib/rules/no-restricted-modules.js +++ b/tools/node_modules/eslint/lib/rules/no-restricted-modules.js @@ -106,10 +106,19 @@ module.exports = { * @param {ASTNode} node The node to check. * @returns {boolean} If the node is a string literal. */ - function isString(node) { + function isStringLiteral(node) { return node && node.type === "Literal" && typeof node.value === "string"; } + /** + * Function to check if a node is a static string template literal. + * @param {ASTNode} node The node to check. + * @returns {boolean} If the node is a string template literal. + */ + function isStaticTemplateLiteral(node) { + return node && node.type === "TemplateLiteral" && node.expressions.length === 0; + } + /** * Function to check if a node is a require call. * @param {ASTNode} node The node to check. @@ -119,14 +128,31 @@ module.exports = { return node.callee.type === "Identifier" && node.callee.name === "require"; } + /** + * Extract string from Literal or TemplateLiteral node + * @param {ASTNode} node The node to extract from + * @returns {string|null} Extracted string or null if node doesn't represent a string + */ + function getFirstArgumentString(node) { + if (isStringLiteral(node)) { + return node.value.trim(); + } + + if (isStaticTemplateLiteral(node)) { + return node.quasis[0].value.cooked.trim(); + } + + return null; + } + /** * Report a restricted path. * @param {node} node representing the restricted path reference + * @param {string} name restricted path * @returns {void} * @private */ - function reportPath(node) { - const name = node.arguments[0].value.trim(); + function reportPath(node, name) { const customMessage = restrictedPathMessages[name]; const messageId = customMessage ? "customMessage" @@ -156,21 +182,25 @@ module.exports = { CallExpression(node) { if (isRequireCall(node)) { - // node has arguments and first argument is string - if (node.arguments.length && isString(node.arguments[0])) { - const name = node.arguments[0].value.trim(); - - // check if argument value is in restricted modules array - if (isRestrictedPath(name)) { - reportPath(node); - } - - if (restrictedPatterns.length > 0 && ig.ignores(name)) { - context.report({ - node, - messageId: "patternMessage", - data: { name } - }); + // node has arguments + if (node.arguments.length) { + const name = getFirstArgumentString(node.arguments[0]); + + // if first argument is a string literal or a static string template literal + if (name) { + + // check if argument value is in restricted modules array + if (isRestrictedPath(name)) { + reportPath(node, name); + } + + if (restrictedPatterns.length > 0 && ig.ignores(name)) { + context.report({ + node, + messageId: "patternMessage", + data: { name } + }); + } } } } diff --git a/tools/node_modules/eslint/lib/rules/no-setter-return.js b/tools/node_modules/eslint/lib/rules/no-setter-return.js index e0948696c347f5..a558640c357d92 100644 --- a/tools/node_modules/eslint/lib/rules/no-setter-return.js +++ b/tools/node_modules/eslint/lib/rules/no-setter-return.js @@ -145,7 +145,7 @@ module.exports = { docs: { description: "disallow returning values from setters", category: "Possible Errors", - recommended: false, + recommended: true, url: "https://eslint.org/docs/rules/no-setter-return" }, diff --git a/tools/node_modules/eslint/lib/rules/no-underscore-dangle.js b/tools/node_modules/eslint/lib/rules/no-underscore-dangle.js index 1468198ac4e958..cac594e10047e8 100644 --- a/tools/node_modules/eslint/lib/rules/no-underscore-dangle.js +++ b/tools/node_modules/eslint/lib/rules/no-underscore-dangle.js @@ -205,7 +205,7 @@ module.exports = { const identifier = node.key.name; const isMethod = node.type === "MethodDefinition" || node.type === "Property" && node.method; - if (typeof identifier !== "undefined" && enforceInMethodNames && isMethod && hasTrailingUnderscore(identifier)) { + if (typeof identifier !== "undefined" && enforceInMethodNames && isMethod && hasTrailingUnderscore(identifier) && !isAllowed(identifier)) { context.report({ node, messageId: "unexpectedUnderscore", diff --git a/tools/node_modules/eslint/lib/rules/wrap-iife.js b/tools/node_modules/eslint/lib/rules/wrap-iife.js index 5e590be13e28b5..896aed63de5222 100644 --- a/tools/node_modules/eslint/lib/rules/wrap-iife.js +++ b/tools/node_modules/eslint/lib/rules/wrap-iife.js @@ -10,6 +10,21 @@ //------------------------------------------------------------------------------ const astUtils = require("./utils/ast-utils"); +const eslintUtils = require("eslint-utils"); + +//---------------------------------------------------------------------- +// Helpers +//---------------------------------------------------------------------- + +/** + * Check if the given node is callee of a `NewExpression` node + * @param {ASTNode} node node to check + * @returns {boolean} True if the node is callee of a `NewExpression` node + * @private + */ +function isCalleeOfNewExpression(node) { + return node.parent.type === "NewExpression" && node.parent.callee === node; +} //------------------------------------------------------------------------------ // Rule Definition @@ -58,15 +73,25 @@ module.exports = { const sourceCode = context.getSourceCode(); /** - * Check if the node is wrapped in () + * Check if the node is wrapped in any (). All parens count: grouping parens and parens for constructs such as if() * @param {ASTNode} node node to evaluate - * @returns {boolean} True if it is wrapped + * @returns {boolean} True if it is wrapped in any parens * @private */ - function wrapped(node) { + function isWrappedInAnyParens(node) { return astUtils.isParenthesised(sourceCode, node); } + /** + * Check if the node is wrapped in grouping (). Parens for constructs such as if() don't count + * @param {ASTNode} node node to evaluate + * @returns {boolean} True if it is wrapped in grouping parens + * @private + */ + function isWrappedInGroupingParens(node) { + return eslintUtils.isParenthesized(1, node, sourceCode); + } + /** * Get the function node from an IIFE * @param {ASTNode} node node to evaluate @@ -99,10 +124,10 @@ module.exports = { return; } - const callExpressionWrapped = wrapped(node), - functionExpressionWrapped = wrapped(innerNode); + const isCallExpressionWrapped = isWrappedInAnyParens(node), + isFunctionExpressionWrapped = isWrappedInAnyParens(innerNode); - if (!callExpressionWrapped && !functionExpressionWrapped) { + if (!isCallExpressionWrapped && !isFunctionExpressionWrapped) { context.report({ node, messageId: "wrapInvocation", @@ -112,27 +137,39 @@ module.exports = { return fixer.replaceText(nodeToSurround, `(${sourceCode.getText(nodeToSurround)})`); } }); - } else if (style === "inside" && !functionExpressionWrapped) { + } else if (style === "inside" && !isFunctionExpressionWrapped) { context.report({ node, messageId: "wrapExpression", fix(fixer) { + // The outer call expression will always be wrapped at this point. + + if (isWrappedInGroupingParens(node) && !isCalleeOfNewExpression(node)) { + + /* + * Parenthesize the function expression and remove unnecessary grouping parens around the call expression. + * Replace the range between the end of the function expression and the end of the call expression. + * for example, in `(function(foo) {}(bar))`, the range `(bar))` should get replaced with `)(bar)`. + */ + + const parenAfter = sourceCode.getTokenAfter(node); + + return fixer.replaceTextRange( + [innerNode.range[1], parenAfter.range[1]], + `)${sourceCode.getText().slice(innerNode.range[1], parenAfter.range[0])}` + ); + } + /* - * The outer call expression will always be wrapped at this point. - * Replace the range between the end of the function expression and the end of the call expression. - * for example, in `(function(foo) {}(bar))`, the range `(bar))` should get replaced with `)(bar)`. - * Replace the parens from the outer expression, and parenthesize the function expression. + * Call expression is wrapped in mandatory parens such as if(), or in necessary grouping parens. + * These parens cannot be removed, so just parenthesize the function expression. */ - const parenAfter = sourceCode.getTokenAfter(node); - return fixer.replaceTextRange( - [innerNode.range[1], parenAfter.range[1]], - `)${sourceCode.getText().slice(innerNode.range[1], parenAfter.range[0])}` - ); + return fixer.replaceText(innerNode, `(${sourceCode.getText(innerNode)})`); } }); - } else if (style === "outside" && !callExpressionWrapped) { + } else if (style === "outside" && !isCallExpressionWrapped) { context.report({ node, messageId: "moveInvocation", diff --git a/tools/node_modules/eslint/package.json b/tools/node_modules/eslint/package.json index 2a30f80d3bd096..dd7d22ef9a0f85 100644 --- a/tools/node_modules/eslint/package.json +++ b/tools/node_modules/eslint/package.json @@ -152,5 +152,5 @@ "test:cli": "mocha", "webpack": "node Makefile.js webpack" }, - "version": "7.0.0-alpha.1" + "version": "7.0.0-alpha.2" } \ No newline at end of file