From dd949aedb81fa772e10568920156daf075d25ea2 Mon Sep 17 00:00:00 2001 From: Toru Nagashima Date: Fri, 5 Jun 2020 06:53:43 +0900 Subject: [PATCH] Update: support `??` operator, import.meta, and `export * as ns` (#13196) * upgrade deps * update code path analysis for nullish coalescing * update rules for `export * as ns from "source"` * add a few tests for import.meta * update rules for nullish coalescing * update deps * add tests to no-restricted-imports * add tests to camelcase * add tests to id-blacklist * add tests for id-match --- docs/rules/no-mixed-operators.md | 1 + .../code-path-analysis/code-path-analyzer.js | 4 +- .../code-path-analysis/code-path-state.js | 46 +++++++--- lib/rules/keyword-spacing.js | 6 ++ lib/rules/no-extra-boolean-cast.js | 3 + lib/rules/no-extra-parens.js | 2 + lib/rules/no-mixed-operators.js | 5 +- lib/rules/no-restricted-exports.js | 6 ++ lib/rules/no-unneeded-ternary.js | 10 ++- lib/rules/utils/ast-utils.js | 54 +++++++++++- package.json | 8 +- .../logical--do-while-qq-1.js | 23 +++++ .../logical--do-while-qq-2.js | 33 ++++++++ .../logical--if-mix-and-qq-1.js | 33 ++++++++ .../logical--if-mix-and-qq-2.js | 33 ++++++++ .../logical--if-mix-or-qq-1.js | 33 ++++++++ .../logical--if-mix-or-qq-2.js | 33 ++++++++ .../code-path-analysis/logical--if-qq-1.js | 25 ++++++ .../code-path-analysis/logical--if-qq-2.js | 30 +++++++ .../code-path-analysis/logical--if-qq-3.js | 40 +++++++++ .../code-path-analysis/logical--if-qq-4.js | 40 +++++++++ .../code-path-analysis/logical--if-qq-5.js | 44 ++++++++++ .../code-path-analysis/logical--simple-3.js | 24 ++++++ .../code-path-analysis/logical--while-qq-1.js | 24 ++++++ .../code-path-analysis/logical--while-qq-2.js | 34 ++++++++ .../code-path-analysis/code-path-analyzer.js | 5 +- tests/lib/rules/camelcase.js | 11 +++ tests/lib/rules/id-blacklist.js | 8 ++ tests/lib/rules/id-match.js | 11 +++ tests/lib/rules/keyword-spacing.js | 31 +++++++ tests/lib/rules/no-extra-boolean-cast.js | 13 +++ tests/lib/rules/no-extra-parens.js | 83 ++++++++++++++++++- tests/lib/rules/no-mixed-operators.js | 25 ++++++ tests/lib/rules/no-restricted-exports.js | 8 +- tests/lib/rules/no-restricted-imports.js | 28 ++++++- tests/lib/rules/no-undef.js | 14 +++- tests/lib/rules/no-unneeded-ternary.js | 12 +++ tests/lib/rules/no-unused-vars.js | 12 +++ tests/lib/rules/operator-linebreak.js | 10 +++ 39 files changed, 834 insertions(+), 31 deletions(-) create mode 100644 tests/fixtures/code-path-analysis/logical--do-while-qq-1.js create mode 100644 tests/fixtures/code-path-analysis/logical--do-while-qq-2.js create mode 100644 tests/fixtures/code-path-analysis/logical--if-mix-and-qq-1.js create mode 100644 tests/fixtures/code-path-analysis/logical--if-mix-and-qq-2.js create mode 100644 tests/fixtures/code-path-analysis/logical--if-mix-or-qq-1.js create mode 100644 tests/fixtures/code-path-analysis/logical--if-mix-or-qq-2.js create mode 100644 tests/fixtures/code-path-analysis/logical--if-qq-1.js create mode 100644 tests/fixtures/code-path-analysis/logical--if-qq-2.js create mode 100644 tests/fixtures/code-path-analysis/logical--if-qq-3.js create mode 100644 tests/fixtures/code-path-analysis/logical--if-qq-4.js create mode 100644 tests/fixtures/code-path-analysis/logical--if-qq-5.js create mode 100644 tests/fixtures/code-path-analysis/logical--simple-3.js create mode 100644 tests/fixtures/code-path-analysis/logical--while-qq-1.js create mode 100644 tests/fixtures/code-path-analysis/logical--while-qq-2.js diff --git a/docs/rules/no-mixed-operators.md b/docs/rules/no-mixed-operators.md index 3206d18fb7d..674a847f7fb 100644 --- a/docs/rules/no-mixed-operators.md +++ b/docs/rules/no-mixed-operators.md @@ -100,6 +100,7 @@ The following operators can be used in `groups` option: * Bitwise Operators: `"&"`, `"|"`, `"^"`, `"~"`, `"<<"`, `">>"`, `">>>"` * Comparison Operators: `"=="`, `"!="`, `"==="`, `"!=="`, `">"`, `">="`, `"<"`, `"<="` * Logical Operators: `"&&"`, `"||"` +* Coalesce Operator: `"??"` * Relational Operators: `"in"`, `"instanceof"` * Ternary Operator: `?:` diff --git a/lib/linter/code-path-analysis/code-path-analyzer.js b/lib/linter/code-path-analysis/code-path-analyzer.js index 8a623e33ea0..b612cf43566 100644 --- a/lib/linter/code-path-analysis/code-path-analyzer.js +++ b/lib/linter/code-path-analysis/code-path-analyzer.js @@ -33,10 +33,10 @@ function isCaseNode(node) { * Checks whether the given logical operator is taken into account for the code * path analysis. * @param {string} operator The operator found in the LogicalExpression node - * @returns {boolean} `true` if the operator is "&&" or "||" + * @returns {boolean} `true` if the operator is "&&" or "||" or "??" */ function isHandledLogicalOperator(operator) { - return operator === "&&" || operator === "||"; + return operator === "&&" || operator === "||" || operator === "??"; } /** diff --git a/lib/linter/code-path-analysis/code-path-state.js b/lib/linter/code-path-analysis/code-path-state.js index 75de1bc6ce4..9e760601a0f 100644 --- a/lib/linter/code-path-analysis/code-path-state.js +++ b/lib/linter/code-path-analysis/code-path-state.js @@ -201,6 +201,7 @@ function finalizeTestSegmentsOfFor(context, choiceContext, head) { if (!choiceContext.processed) { choiceContext.trueForkContext.add(head); choiceContext.falseForkContext.add(head); + choiceContext.qqForkContext.add(head); } if (context.test !== true) { @@ -351,6 +352,7 @@ class CodePathState { isForkingAsResult, trueForkContext: ForkContext.newEmpty(this.forkContext), falseForkContext: ForkContext.newEmpty(this.forkContext), + qqForkContext: ForkContext.newEmpty(this.forkContext), processed: false }; } @@ -370,6 +372,7 @@ class CodePathState { switch (context.kind) { case "&&": case "||": + case "??": /* * If any result were not transferred from child contexts, @@ -379,6 +382,7 @@ class CodePathState { if (!context.processed) { context.trueForkContext.add(headSegments); context.falseForkContext.add(headSegments); + context.qqForkContext.add(headSegments); } /* @@ -390,6 +394,7 @@ class CodePathState { parentContext.trueForkContext.addAll(context.trueForkContext); parentContext.falseForkContext.addAll(context.falseForkContext); + parentContext.qqForkContext.addAll(context.qqForkContext); parentContext.processed = true; return context; @@ -456,13 +461,24 @@ class CodePathState { * This got segments already from the child choice context. * Creates the next path from own true/false fork context. */ - const prevForkContext = - context.kind === "&&" ? context.trueForkContext - /* kind === "||" */ : context.falseForkContext; + let prevForkContext; + + switch (context.kind) { + case "&&": // if true then go to the right-hand side. + prevForkContext = context.trueForkContext; + break; + case "||": // if false then go to the right-hand side. + prevForkContext = context.falseForkContext; + break; + case "??": // Both true/false can short-circuit, so needs the third path to go to the right-hand side. That's qqForkContext. + prevForkContext = context.qqForkContext; + break; + default: + throw new Error("unreachable"); + } forkContext.replaceHead(prevForkContext.makeNext(0, -1)); prevForkContext.clear(); - context.processed = false; } else { @@ -471,14 +487,19 @@ class CodePathState { * So addresses the head segments. * The head segments are the path of the left-hand operand. */ - if (context.kind === "&&") { - - // The path does short-circuit if false. - context.falseForkContext.add(forkContext.head); - } else { - - // The path does short-circuit if true. - context.trueForkContext.add(forkContext.head); + switch (context.kind) { + case "&&": // the false path can short-circuit. + context.falseForkContext.add(forkContext.head); + break; + case "||": // the true path can short-circuit. + context.trueForkContext.add(forkContext.head); + break; + case "??": // both can short-circuit. + context.trueForkContext.add(forkContext.head); + context.falseForkContext.add(forkContext.head); + break; + default: + throw new Error("unreachable"); } forkContext.replaceHead(forkContext.makeNext(-1, -1)); @@ -501,6 +522,7 @@ class CodePathState { if (!context.processed) { context.trueForkContext.add(forkContext.head); context.falseForkContext.add(forkContext.head); + context.qqForkContext.add(forkContext.head); } context.processed = false; diff --git a/lib/rules/keyword-spacing.js b/lib/rules/keyword-spacing.js index 2b3fef33bd7..1829aed37ad 100644 --- a/lib/rules/keyword-spacing.js +++ b/lib/rules/keyword-spacing.js @@ -442,6 +442,12 @@ module.exports = { checkSpacingAround(sourceCode.getTokenAfter(firstToken)); } + if (node.type === "ExportAllDeclaration" && node.exported) { + const asToken = sourceCode.getTokenBefore(node.exported); + + checkSpacingBefore(asToken, PREV_TOKEN_M); + } + if (node.source) { const fromToken = sourceCode.getTokenBefore(node.source); diff --git a/lib/rules/no-extra-boolean-cast.js b/lib/rules/no-extra-boolean-cast.js index aba8e63e086..b90757b1126 100644 --- a/lib/rules/no-extra-boolean-cast.js +++ b/lib/rules/no-extra-boolean-cast.js @@ -172,6 +172,9 @@ module.exports = { case "UnaryExpression": return precedence(node) < precedence(parent); case "LogicalExpression": + if (astUtils.isMixedLogicalAndCoalesceExpressions(node, parent)) { + return true; + } if (previousNode === parent.left) { return precedence(node) < precedence(parent); } diff --git a/lib/rules/no-extra-parens.js b/lib/rules/no-extra-parens.js index 7cbb7522ebe..dea1835b903 100644 --- a/lib/rules/no-extra-parens.js +++ b/lib/rules/no-extra-parens.js @@ -478,6 +478,7 @@ module.exports = { if (!shouldSkipLeft && hasExcessParens(node.left)) { if ( !(node.left.type === "UnaryExpression" && isExponentiation) && + !astUtils.isMixedLogicalAndCoalesceExpressions(node.left, node) && (leftPrecedence > prec || (leftPrecedence === prec && !isExponentiation)) || isParenthesisedTwice(node.left) ) { @@ -487,6 +488,7 @@ module.exports = { if (!shouldSkipRight && hasExcessParens(node.right)) { if ( + !astUtils.isMixedLogicalAndCoalesceExpressions(node.right, node) && (rightPrecedence > prec || (rightPrecedence === prec && isExponentiation)) || isParenthesisedTwice(node.right) ) { diff --git a/lib/rules/no-mixed-operators.js b/lib/rules/no-mixed-operators.js index 37e8906e827..f0290e93ece 100644 --- a/lib/rules/no-mixed-operators.js +++ b/lib/rules/no-mixed-operators.js @@ -21,13 +21,15 @@ const COMPARISON_OPERATORS = ["==", "!=", "===", "!==", ">", ">=", "<", "<="]; const LOGICAL_OPERATORS = ["&&", "||"]; const RELATIONAL_OPERATORS = ["in", "instanceof"]; const TERNARY_OPERATOR = ["?:"]; +const COALESCE_OPERATOR = ["??"]; const ALL_OPERATORS = [].concat( ARITHMETIC_OPERATORS, BITWISE_OPERATORS, COMPARISON_OPERATORS, LOGICAL_OPERATORS, RELATIONAL_OPERATORS, - TERNARY_OPERATOR + TERNARY_OPERATOR, + COALESCE_OPERATOR ); const DEFAULT_GROUPS = [ ARITHMETIC_OPERATORS, @@ -236,7 +238,6 @@ module.exports = { return { BinaryExpression: check, LogicalExpression: check - }; } }; diff --git a/lib/rules/no-restricted-exports.js b/lib/rules/no-restricted-exports.js index 5b5c7d9bffb..6031e26de2c 100644 --- a/lib/rules/no-restricted-exports.js +++ b/lib/rules/no-restricted-exports.js @@ -61,6 +61,12 @@ module.exports = { } return { + ExportAllDeclaration(node) { + if (node.exported) { + checkExportedName(node.exported); + } + }, + ExportNamedDeclaration(node) { const declaration = node.declaration; diff --git a/lib/rules/no-unneeded-ternary.js b/lib/rules/no-unneeded-ternary.js index d4438e2fe08..0fefc42b909 100644 --- a/lib/rules/no-unneeded-ternary.js +++ b/lib/rules/no-unneeded-ternary.js @@ -147,10 +147,12 @@ module.exports = { loc: node.consequent.loc.start, messageId: "unnecessaryConditionalAssignment", fix: fixer => { - const shouldParenthesizeAlternate = ( - astUtils.getPrecedence(node.alternate) < OR_PRECEDENCE && - !astUtils.isParenthesised(sourceCode, node.alternate) - ); + const shouldParenthesizeAlternate = + ( + astUtils.getPrecedence(node.alternate) < OR_PRECEDENCE || + astUtils.isCoalesceExpression(node.alternate) + ) && + !astUtils.isParenthesised(sourceCode, node.alternate); const alternateText = shouldParenthesizeAlternate ? `(${sourceCode.getText(node.alternate)})` : astUtils.getParenthesisedText(sourceCode, node.alternate); diff --git a/lib/rules/utils/ast-utils.js b/lib/rules/utils/ast-utils.js index c38239ed076..ecea6948da2 100644 --- a/lib/rules/utils/ast-utils.js +++ b/lib/rules/utils/ast-utils.js @@ -416,6 +416,53 @@ function equalTokens(left, right, sourceCode) { return true; } +/** + * Check if the given node is a true logical expression or not. + * + * The three binary expressions logical-or (`||`), logical-and (`&&`), and + * coalesce (`??`) are known as `ShortCircuitExpression`. + * But ESTree represents those by `LogicalExpression` node. + * + * This function rejects coalesce expressions of `LogicalExpression` node. + * @param {ASTNode} node The node to check. + * @returns {boolean} `true` if the node is `&&` or `||`. + * @see https://tc39.es/ecma262/#prod-ShortCircuitExpression + */ +function isLogicalExpression(node) { + return ( + node.type === "LogicalExpression" && + (node.operator === "&&" || node.operator === "||") + ); +} + +/** + * Check if the given node is a nullish coalescing expression or not. + * + * The three binary expressions logical-or (`||`), logical-and (`&&`), and + * coalesce (`??`) are known as `ShortCircuitExpression`. + * But ESTree represents those by `LogicalExpression` node. + * + * This function finds only coalesce expressions of `LogicalExpression` node. + * @param {ASTNode} node The node to check. + * @returns {boolean} `true` if the node is `??`. + */ +function isCoalesceExpression(node) { + return node.type === "LogicalExpression" && node.operator === "??"; +} + +/** + * Check if given two nodes are the pair of a logical expression and a coalesce expression. + * @param {ASTNode} left A node to check. + * @param {ASTNode} right Another node to check. + * @returns {boolean} `true` if the two nodes are the pair of a logical expression and a coalesce expression. + */ +function isMixedLogicalAndCoalesceExpressions(left, right) { + return ( + (isLogicalExpression(left) && isCoalesceExpression(right)) || + (isCoalesceExpression(left) && isLogicalExpression(right)) + ); +} + //------------------------------------------------------------------------------ // Public Interface //------------------------------------------------------------------------------ @@ -779,6 +826,7 @@ module.exports = { case "LogicalExpression": switch (node.operator) { case "||": + case "??": return 4; case "&&": return 5; @@ -1538,5 +1586,9 @@ module.exports = { */ hasOctalEscapeSequence(rawString) { return OCTAL_ESCAPE_PATTERN.test(rawString); - } + }, + + isLogicalExpression, + isCoalesceExpression, + isMixedLogicalAndCoalesceExpressions }; diff --git a/package.json b/package.json index dddf620c0dd..5cb23464da3 100644 --- a/package.json +++ b/package.json @@ -52,10 +52,10 @@ "cross-spawn": "^7.0.2", "debug": "^4.0.1", "doctrine": "^3.0.0", - "eslint-scope": "^5.0.0", + "eslint-scope": "^5.1.0", "eslint-utils": "^2.0.0", - "eslint-visitor-keys": "^1.1.0", - "espree": "^7.0.0", + "eslint-visitor-keys": "^1.2.0", + "espree": "^7.1.0", "esquery": "^1.2.0", "esutils": "^2.0.2", "file-entry-cache": "^5.0.1", @@ -86,7 +86,7 @@ "devDependencies": { "@babel/core": "^7.4.3", "@babel/preset-env": "^7.4.3", - "acorn": "^7.1.1", + "acorn": "^7.2.0", "babel-loader": "^8.0.5", "chai": "^4.0.1", "cheerio": "^0.22.0", diff --git a/tests/fixtures/code-path-analysis/logical--do-while-qq-1.js b/tests/fixtures/code-path-analysis/logical--do-while-qq-1.js new file mode 100644 index 00000000000..4b685677227 --- /dev/null +++ b/tests/fixtures/code-path-analysis/logical--do-while-qq-1.js @@ -0,0 +1,23 @@ +/*expected +initial->s1_1->s1_2->s1_3->s1_2->s1_2; +s1_3->s1_4; +s1_2->s1_4->final; +*/ +do { + foo(); +} while (a ?? b); + +/*DOT +digraph { + node[shape=box,style="rounded,filled",fillcolor=white]; + initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25]; + final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25]; + s1_1[label="Program\nDoWhileStatement"]; + s1_2[label="BlockStatement\nExpressionStatement\nCallExpression\nIdentifier (foo)\nLogicalExpression\nIdentifier (a)\nIdentifier:exit (foo)\nCallExpression:exit\nExpressionStatement:exit\nBlockStatement:exit\nIdentifier:exit (a)"]; + s1_3[label="Identifier (b)\nIdentifier:exit (b)\nLogicalExpression:exit"]; + s1_4[label="DoWhileStatement:exit\nProgram:exit"]; + initial->s1_1->s1_2->s1_3->s1_2->s1_2; + s1_3->s1_4; + s1_2->s1_4->final; +} +*/ diff --git a/tests/fixtures/code-path-analysis/logical--do-while-qq-2.js b/tests/fixtures/code-path-analysis/logical--do-while-qq-2.js new file mode 100644 index 00000000000..c033142d83e --- /dev/null +++ b/tests/fixtures/code-path-analysis/logical--do-while-qq-2.js @@ -0,0 +1,33 @@ +/*expected +initial->s1_1->s1_2->s1_3->s1_4->s1_5->s1_2->s1_2; +s1_3->s1_2; +s1_4->s1_2; +s1_5->s1_6; +s1_2->s1_6; +s1_3->s1_6; +s1_4->s1_6->final; +*/ +do { + foo(); +} while (a ?? b ?? c ?? d); + +/*DOT +digraph { + node[shape=box,style="rounded,filled",fillcolor=white]; + initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25]; + final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25]; + s1_1[label="Program\nDoWhileStatement"]; + s1_2[label="BlockStatement\nExpressionStatement\nCallExpression\nIdentifier (foo)\nLogicalExpression\nLogicalExpression\nLogicalExpression\nIdentifier (a)\nIdentifier:exit (foo)\nCallExpression:exit\nExpressionStatement:exit\nBlockStatement:exit\nIdentifier:exit (a)"]; + s1_3[label="Identifier (b)\nIdentifier:exit (b)\nLogicalExpression:exit"]; + s1_4[label="Identifier (c)\nIdentifier:exit (c)\nLogicalExpression:exit"]; + s1_5[label="Identifier (d)\nIdentifier:exit (d)\nLogicalExpression:exit"]; + s1_6[label="DoWhileStatement:exit\nProgram:exit"]; + initial->s1_1->s1_2->s1_3->s1_4->s1_5->s1_2->s1_2; + s1_3->s1_2; + s1_4->s1_2; + s1_5->s1_6; + s1_2->s1_6; + s1_3->s1_6; + s1_4->s1_6->final; +} +*/ diff --git a/tests/fixtures/code-path-analysis/logical--if-mix-and-qq-1.js b/tests/fixtures/code-path-analysis/logical--if-mix-and-qq-1.js new file mode 100644 index 00000000000..4863ac81db3 --- /dev/null +++ b/tests/fixtures/code-path-analysis/logical--if-mix-and-qq-1.js @@ -0,0 +1,33 @@ +/*expected +initial->s1_1->s1_2->s1_3->s1_4->s1_6; +s1_1->s1_5->s1_6; +s1_2->s1_4; +s1_3->s1_5; +s1_2->s1_5; +s1_6->final; +*/ +if ((a && b) ?? c) { + foo(); +} else { + bar(); +} + +/*DOT +digraph { + node[shape=box,style="rounded,filled",fillcolor=white]; + initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25]; + final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25]; + s1_1[label="Program\nIfStatement\nLogicalExpression\nLogicalExpression\nIdentifier (a)\nIdentifier:exit (a)"]; + s1_2[label="Identifier (b)\nIdentifier:exit (b)\nLogicalExpression:exit"]; + s1_3[label="Identifier (c)\nIdentifier:exit (c)\nLogicalExpression:exit"]; + s1_4[label="BlockStatement\nExpressionStatement\nCallExpression\nIdentifier (foo)\nIdentifier:exit (foo)\nCallExpression:exit\nExpressionStatement:exit\nBlockStatement:exit"]; + s1_6[label="IfStatement:exit\nProgram:exit"]; + s1_5[label="BlockStatement\nExpressionStatement\nCallExpression\nIdentifier (bar)\nIdentifier:exit (bar)\nCallExpression:exit\nExpressionStatement:exit\nBlockStatement:exit"]; + initial->s1_1->s1_2->s1_3->s1_4->s1_6; + s1_1->s1_5->s1_6; + s1_2->s1_4; + s1_3->s1_5; + s1_2->s1_5; + s1_6->final; +} +*/ diff --git a/tests/fixtures/code-path-analysis/logical--if-mix-and-qq-2.js b/tests/fixtures/code-path-analysis/logical--if-mix-and-qq-2.js new file mode 100644 index 00000000000..0dd1f2a1616 --- /dev/null +++ b/tests/fixtures/code-path-analysis/logical--if-mix-and-qq-2.js @@ -0,0 +1,33 @@ +/*expected +initial->s1_1->s1_2->s1_3->s1_4->s1_6; +s1_1->s1_5->s1_6; +s1_2->s1_4; +s1_3->s1_5; +s1_2->s1_5; +s1_6->final; +*/ +if (a && (b ?? c)) { + foo(); +} else { + bar(); +} + +/*DOT +digraph { + node[shape=box,style="rounded,filled",fillcolor=white]; + initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25]; + final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25]; + s1_1[label="Program\nIfStatement\nLogicalExpression\nIdentifier (a)\nIdentifier:exit (a)"]; + s1_2[label="LogicalExpression\nIdentifier (b)\nIdentifier:exit (b)"]; + s1_3[label="Identifier (c)\nIdentifier:exit (c)\nLogicalExpression:exit\nLogicalExpression:exit"]; + s1_4[label="BlockStatement\nExpressionStatement\nCallExpression\nIdentifier (foo)\nIdentifier:exit (foo)\nCallExpression:exit\nExpressionStatement:exit\nBlockStatement:exit"]; + s1_6[label="IfStatement:exit\nProgram:exit"]; + s1_5[label="BlockStatement\nExpressionStatement\nCallExpression\nIdentifier (bar)\nIdentifier:exit (bar)\nCallExpression:exit\nExpressionStatement:exit\nBlockStatement:exit"]; + initial->s1_1->s1_2->s1_3->s1_4->s1_6; + s1_1->s1_5->s1_6; + s1_2->s1_4; + s1_3->s1_5; + s1_2->s1_5; + s1_6->final; +} +*/ diff --git a/tests/fixtures/code-path-analysis/logical--if-mix-or-qq-1.js b/tests/fixtures/code-path-analysis/logical--if-mix-or-qq-1.js new file mode 100644 index 00000000000..e598999e11e --- /dev/null +++ b/tests/fixtures/code-path-analysis/logical--if-mix-or-qq-1.js @@ -0,0 +1,33 @@ +/*expected +initial->s1_1->s1_2->s1_3->s1_4->s1_6; +s1_1->s1_4; +s1_2->s1_4; +s1_3->s1_5->s1_6; +s1_2->s1_5; +s1_6->final; +*/ +if ((a || b) ?? c) { + foo(); +} else { + bar(); +} + +/*DOT +digraph { + node[shape=box,style="rounded,filled",fillcolor=white]; + initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25]; + final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25]; + s1_1[label="Program\nIfStatement\nLogicalExpression\nLogicalExpression\nIdentifier (a)\nIdentifier:exit (a)"]; + s1_2[label="Identifier (b)\nIdentifier:exit (b)\nLogicalExpression:exit"]; + s1_3[label="Identifier (c)\nIdentifier:exit (c)\nLogicalExpression:exit"]; + s1_4[label="BlockStatement\nExpressionStatement\nCallExpression\nIdentifier (foo)\nIdentifier:exit (foo)\nCallExpression:exit\nExpressionStatement:exit\nBlockStatement:exit"]; + s1_6[label="IfStatement:exit\nProgram:exit"]; + s1_5[label="BlockStatement\nExpressionStatement\nCallExpression\nIdentifier (bar)\nIdentifier:exit (bar)\nCallExpression:exit\nExpressionStatement:exit\nBlockStatement:exit"]; + initial->s1_1->s1_2->s1_3->s1_4->s1_6; + s1_1->s1_4; + s1_2->s1_4; + s1_3->s1_5->s1_6; + s1_2->s1_5; + s1_6->final; +} +*/ diff --git a/tests/fixtures/code-path-analysis/logical--if-mix-or-qq-2.js b/tests/fixtures/code-path-analysis/logical--if-mix-or-qq-2.js new file mode 100644 index 00000000000..f98962dc150 --- /dev/null +++ b/tests/fixtures/code-path-analysis/logical--if-mix-or-qq-2.js @@ -0,0 +1,33 @@ +/*expected +initial->s1_1->s1_2->s1_3->s1_4->s1_6; +s1_1->s1_4; +s1_2->s1_4; +s1_3->s1_5->s1_6; +s1_2->s1_5; +s1_6->final; +*/ +if (a || (b ?? c)) { + foo(); +} else { + bar(); +} + +/*DOT +digraph { + node[shape=box,style="rounded,filled",fillcolor=white]; + initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25]; + final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25]; + s1_1[label="Program\nIfStatement\nLogicalExpression\nLogicalExpression\nIdentifier (a)\nIdentifier:exit (a)"]; + s1_2[label="Identifier (b)\nIdentifier:exit (b)\nLogicalExpression:exit"]; + s1_3[label="Identifier (c)\nIdentifier:exit (c)\nLogicalExpression:exit"]; + s1_4[label="BlockStatement\nExpressionStatement\nCallExpression\nIdentifier (foo)\nIdentifier:exit (foo)\nCallExpression:exit\nExpressionStatement:exit\nBlockStatement:exit"]; + s1_6[label="IfStatement:exit\nProgram:exit"]; + s1_5[label="BlockStatement\nExpressionStatement\nCallExpression\nIdentifier (bar)\nIdentifier:exit (bar)\nCallExpression:exit\nExpressionStatement:exit\nBlockStatement:exit"]; + initial->s1_1->s1_2->s1_3->s1_4->s1_6; + s1_1->s1_4; + s1_2->s1_4; + s1_3->s1_5->s1_6; + s1_2->s1_5; + s1_6->final; +} +*/ diff --git a/tests/fixtures/code-path-analysis/logical--if-qq-1.js b/tests/fixtures/code-path-analysis/logical--if-qq-1.js new file mode 100644 index 00000000000..80a7728e715 --- /dev/null +++ b/tests/fixtures/code-path-analysis/logical--if-qq-1.js @@ -0,0 +1,25 @@ +/*expected +initial->s1_1->s1_2->s1_3->s1_4; +s1_1->s1_3; +s1_2->s1_4; +s1_1->s1_4->final; +*/ +if (a ?? b) { + foo(); +} + +/*DOT +digraph { + node[shape=box,style="rounded,filled",fillcolor=white]; + initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25]; + final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25]; + s1_1[label="Program\nIfStatement\nLogicalExpression\nIdentifier (a)\nIdentifier:exit (a)"]; + s1_2[label="Identifier (b)\nIdentifier:exit (b)\nLogicalExpression:exit"]; + s1_3[label="BlockStatement\nExpressionStatement\nCallExpression\nIdentifier (foo)\nIdentifier:exit (foo)\nCallExpression:exit\nExpressionStatement:exit\nBlockStatement:exit"]; + s1_4[label="IfStatement:exit\nProgram:exit"]; + initial->s1_1->s1_2->s1_3->s1_4; + s1_1->s1_3; + s1_2->s1_4; + s1_1->s1_4->final; +} +*/ diff --git a/tests/fixtures/code-path-analysis/logical--if-qq-2.js b/tests/fixtures/code-path-analysis/logical--if-qq-2.js new file mode 100644 index 00000000000..e9604bfc081 --- /dev/null +++ b/tests/fixtures/code-path-analysis/logical--if-qq-2.js @@ -0,0 +1,30 @@ +/*expected +initial->s1_1->s1_2->s1_3->s1_5; +s1_1->s1_3; +s1_2->s1_4->s1_5; +s1_1->s1_4; +s1_5->final; +*/ +if (a ?? b) { + foo(); +} else { + bar(); +} + +/*DOT +digraph { + node[shape=box,style="rounded,filled",fillcolor=white]; + initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25]; + final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25]; + s1_1[label="Program\nIfStatement\nLogicalExpression\nIdentifier (a)\nIdentifier:exit (a)"]; + s1_2[label="Identifier (b)\nIdentifier:exit (b)\nLogicalExpression:exit"]; + s1_3[label="BlockStatement\nExpressionStatement\nCallExpression\nIdentifier (foo)\nIdentifier:exit (foo)\nCallExpression:exit\nExpressionStatement:exit\nBlockStatement:exit"]; + s1_5[label="IfStatement:exit\nProgram:exit"]; + s1_4[label="BlockStatement\nExpressionStatement\nCallExpression\nIdentifier (bar)\nIdentifier:exit (bar)\nCallExpression:exit\nExpressionStatement:exit\nBlockStatement:exit"]; + initial->s1_1->s1_2->s1_3->s1_5; + s1_1->s1_3; + s1_2->s1_4->s1_5; + s1_1->s1_4; + s1_5->final; +} +*/ diff --git a/tests/fixtures/code-path-analysis/logical--if-qq-3.js b/tests/fixtures/code-path-analysis/logical--if-qq-3.js new file mode 100644 index 00000000000..3586ce3771d --- /dev/null +++ b/tests/fixtures/code-path-analysis/logical--if-qq-3.js @@ -0,0 +1,40 @@ +/*expected +initial->s1_1->s1_2->s1_3->s1_9; +s1_1->s1_3; +s1_2->s1_4->s1_5->s1_6->s1_8->s1_9; +s1_1->s1_4->s1_6; +s1_5->s1_7->s1_8; +s1_4->s1_7; +s1_9->final; +*/ +if (a ?? b) { + foo(); +} else if (c ?? d) { + bar(); +} else { + qiz(); +} + +/*DOT +digraph { + node[shape=box,style="rounded,filled",fillcolor=white]; + initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25]; + final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25]; + s1_1[label="Program\nIfStatement\nLogicalExpression\nIdentifier (a)\nIdentifier:exit (a)"]; + s1_2[label="Identifier (b)\nIdentifier:exit (b)\nLogicalExpression:exit"]; + s1_3[label="BlockStatement\nExpressionStatement\nCallExpression\nIdentifier (foo)\nIdentifier:exit (foo)\nCallExpression:exit\nExpressionStatement:exit\nBlockStatement:exit"]; + s1_9[label="IfStatement:exit\nProgram:exit"]; + s1_4[label="IfStatement\nLogicalExpression\nIdentifier (c)\nIdentifier:exit (c)"]; + s1_5[label="Identifier (d)\nIdentifier:exit (d)\nLogicalExpression:exit"]; + s1_6[label="BlockStatement\nExpressionStatement\nCallExpression\nIdentifier (bar)\nIdentifier:exit (bar)\nCallExpression:exit\nExpressionStatement:exit\nBlockStatement:exit"]; + s1_8[label="IfStatement:exit"]; + s1_7[label="BlockStatement\nExpressionStatement\nCallExpression\nIdentifier (qiz)\nIdentifier:exit (qiz)\nCallExpression:exit\nExpressionStatement:exit\nBlockStatement:exit"]; + initial->s1_1->s1_2->s1_3->s1_9; + s1_1->s1_3; + s1_2->s1_4->s1_5->s1_6->s1_8->s1_9; + s1_1->s1_4->s1_6; + s1_5->s1_7->s1_8; + s1_4->s1_7; + s1_9->final; +} +*/ diff --git a/tests/fixtures/code-path-analysis/logical--if-qq-4.js b/tests/fixtures/code-path-analysis/logical--if-qq-4.js new file mode 100644 index 00000000000..ba81360895b --- /dev/null +++ b/tests/fixtures/code-path-analysis/logical--if-qq-4.js @@ -0,0 +1,40 @@ +/*expected +initial->s1_1->s1_2->s1_3->s1_4->s1_5->s1_7; +s1_1->s1_5; +s1_2->s1_5; +s1_3->s1_5; +s1_4->s1_6->s1_7; +s1_1->s1_6; +s1_2->s1_6; +s1_3->s1_6; +s1_7->final; +*/ +if (a ?? b ?? c ?? d) { + foo(); +} else { + bar(); +} + +/*DOT +digraph { + node[shape=box,style="rounded,filled",fillcolor=white]; + initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25]; + final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25]; + s1_1[label="Program\nIfStatement\nLogicalExpression\nLogicalExpression\nLogicalExpression\nIdentifier (a)\nIdentifier:exit (a)"]; + s1_2[label="Identifier (b)\nIdentifier:exit (b)\nLogicalExpression:exit"]; + s1_3[label="Identifier (c)\nIdentifier:exit (c)\nLogicalExpression:exit"]; + s1_4[label="Identifier (d)\nIdentifier:exit (d)\nLogicalExpression:exit"]; + s1_5[label="BlockStatement\nExpressionStatement\nCallExpression\nIdentifier (foo)\nIdentifier:exit (foo)\nCallExpression:exit\nExpressionStatement:exit\nBlockStatement:exit"]; + s1_7[label="IfStatement:exit\nProgram:exit"]; + s1_6[label="BlockStatement\nExpressionStatement\nCallExpression\nIdentifier (bar)\nIdentifier:exit (bar)\nCallExpression:exit\nExpressionStatement:exit\nBlockStatement:exit"]; + initial->s1_1->s1_2->s1_3->s1_4->s1_5->s1_7; + s1_1->s1_5; + s1_2->s1_5; + s1_3->s1_5; + s1_4->s1_6->s1_7; + s1_1->s1_6; + s1_2->s1_6; + s1_3->s1_6; + s1_7->final; +} +*/ diff --git a/tests/fixtures/code-path-analysis/logical--if-qq-5.js b/tests/fixtures/code-path-analysis/logical--if-qq-5.js new file mode 100644 index 00000000000..ba2d22267a6 --- /dev/null +++ b/tests/fixtures/code-path-analysis/logical--if-qq-5.js @@ -0,0 +1,44 @@ +/*expected +initial->s1_1->s1_2->s1_3->s1_4->s1_5->s1_7->s1_9; +s1_1->s1_3; +s1_2->s1_8->s1_9; +s1_3->s1_5; +s1_4->s1_6->s1_7; +s1_1->s1_8; +s1_3->s1_6; +s1_9->final; +*/ +if (a ?? b) { + if (c ?? d) { + foo(); + } else { + bar(); + } +} else { + qiz(); +} + +/*DOT +digraph { + node[shape=box,style="rounded,filled",fillcolor=white]; + initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25]; + final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25]; + s1_1[label="Program\nIfStatement\nLogicalExpression\nIdentifier (a)\nIdentifier:exit (a)"]; + s1_2[label="Identifier (b)\nIdentifier:exit (b)\nLogicalExpression:exit"]; + s1_3[label="BlockStatement\nIfStatement\nLogicalExpression\nIdentifier (c)\nIdentifier:exit (c)"]; + s1_4[label="Identifier (d)\nIdentifier:exit (d)\nLogicalExpression:exit"]; + s1_5[label="BlockStatement\nExpressionStatement\nCallExpression\nIdentifier (foo)\nIdentifier:exit (foo)\nCallExpression:exit\nExpressionStatement:exit\nBlockStatement:exit"]; + s1_7[label="IfStatement:exit\nBlockStatement:exit"]; + s1_9[label="IfStatement:exit\nProgram:exit"]; + s1_8[label="BlockStatement\nExpressionStatement\nCallExpression\nIdentifier (qiz)\nIdentifier:exit (qiz)\nCallExpression:exit\nExpressionStatement:exit\nBlockStatement:exit"]; + s1_6[label="BlockStatement\nExpressionStatement\nCallExpression\nIdentifier (bar)\nIdentifier:exit (bar)\nCallExpression:exit\nExpressionStatement:exit\nBlockStatement:exit"]; + initial->s1_1->s1_2->s1_3->s1_4->s1_5->s1_7->s1_9; + s1_1->s1_3; + s1_2->s1_8->s1_9; + s1_3->s1_5; + s1_4->s1_6->s1_7; + s1_1->s1_8; + s1_3->s1_6; + s1_9->final; +} +*/ diff --git a/tests/fixtures/code-path-analysis/logical--simple-3.js b/tests/fixtures/code-path-analysis/logical--simple-3.js new file mode 100644 index 00000000000..0ae12a39906 --- /dev/null +++ b/tests/fixtures/code-path-analysis/logical--simple-3.js @@ -0,0 +1,24 @@ +/*expected +initial->s1_1->s1_2->s1_3->s1_4->s1_5; +s1_1->s1_5; +s1_2->s1_5; +s1_3->s1_5->final; +*/ +a ?? b ?? c ?? d; + +/*DOT +digraph { + node[shape=box,style="rounded,filled",fillcolor=white]; + initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25]; + final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25]; + s1_1[label="Program\nExpressionStatement\nLogicalExpression\nLogicalExpression\nLogicalExpression\nIdentifier (a)"]; + s1_2[label="Identifier (b)"]; + s1_3[label="Identifier (c)"]; + s1_4[label="Identifier (d)"]; + s1_5[label="LogicalExpression:exit\nExpressionStatement:exit\nProgram:exit"]; + initial->s1_1->s1_2->s1_3->s1_4->s1_5; + s1_1->s1_5; + s1_2->s1_5; + s1_3->s1_5->final; +} +*/ diff --git a/tests/fixtures/code-path-analysis/logical--while-qq-1.js b/tests/fixtures/code-path-analysis/logical--while-qq-1.js new file mode 100644 index 00000000000..de729d78b87 --- /dev/null +++ b/tests/fixtures/code-path-analysis/logical--while-qq-1.js @@ -0,0 +1,24 @@ +/*expected +initial->s1_1->s1_2->s1_3->s1_4->s1_2->s1_4; +s1_3->s1_5; +s1_2->s1_5->final; +*/ +while (a ?? b) { + foo(); +} + +/*DOT +digraph { + node[shape=box,style="rounded,filled",fillcolor=white]; + initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25]; + final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25]; + s1_1[label="Program\nWhileStatement"]; + s1_2[label="LogicalExpression\nIdentifier (a)\nIdentifier:exit (a)"]; + s1_3[label="Identifier (b)\nIdentifier:exit (b)\nLogicalExpression:exit"]; + s1_4[label="BlockStatement\nExpressionStatement\nCallExpression\nIdentifier (foo)\nIdentifier:exit (foo)\nCallExpression:exit\nExpressionStatement:exit\nBlockStatement:exit"]; + s1_5[label="WhileStatement:exit\nProgram:exit"]; + initial->s1_1->s1_2->s1_3->s1_4->s1_2->s1_4; + s1_3->s1_5; + s1_2->s1_5->final; +} +*/ diff --git a/tests/fixtures/code-path-analysis/logical--while-qq-2.js b/tests/fixtures/code-path-analysis/logical--while-qq-2.js new file mode 100644 index 00000000000..fac385df01e --- /dev/null +++ b/tests/fixtures/code-path-analysis/logical--while-qq-2.js @@ -0,0 +1,34 @@ +/*expected +initial->s1_1->s1_2->s1_3->s1_4->s1_5->s1_6->s1_2->s1_6; +s1_3->s1_6; +s1_4->s1_6; +s1_5->s1_7; +s1_2->s1_7; +s1_3->s1_7; +s1_4->s1_7->final; +*/ +while (a ?? b ?? c ?? d) { + foo(); +} + +/*DOT +digraph { + node[shape=box,style="rounded,filled",fillcolor=white]; + initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25]; + final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25]; + s1_1[label="Program\nWhileStatement"]; + s1_2[label="LogicalExpression\nLogicalExpression\nLogicalExpression\nIdentifier (a)\nIdentifier:exit (a)"]; + s1_3[label="Identifier (b)\nIdentifier:exit (b)\nLogicalExpression:exit"]; + s1_4[label="Identifier (c)\nIdentifier:exit (c)\nLogicalExpression:exit"]; + s1_5[label="Identifier (d)\nIdentifier:exit (d)\nLogicalExpression:exit"]; + s1_6[label="BlockStatement\nExpressionStatement\nCallExpression\nIdentifier (foo)\nIdentifier:exit (foo)\nCallExpression:exit\nExpressionStatement:exit\nBlockStatement:exit"]; + s1_7[label="WhileStatement:exit\nProgram:exit"]; + initial->s1_1->s1_2->s1_3->s1_4->s1_5->s1_6->s1_2->s1_6; + s1_3->s1_6; + s1_4->s1_6; + s1_5->s1_7; + s1_2->s1_7; + s1_3->s1_7; + s1_4->s1_7->final; +} +*/ diff --git a/tests/lib/linter/code-path-analysis/code-path-analyzer.js b/tests/lib/linter/code-path-analysis/code-path-analyzer.js index 6460ba1b676..ec516c54a02 100644 --- a/tests/lib/linter/code-path-analysis/code-path-analyzer.js +++ b/tests/lib/linter/code-path-analysis/code-path-analyzer.js @@ -556,7 +556,10 @@ describe("CodePathAnalyzer", () => { actual.push(debug.makeDotArrows(codePath)); } })); - const messages = linter.verify(source, { rules: { test: 2 }, env: { es6: true } }); + const messages = linter.verify(source, { + parserOptions: { ecmaVersion: 2020 }, + rules: { test: 2 } + }); assert.strictEqual(messages.length, 0); assert.strictEqual(actual.length, expected.length, "a count of code paths is wrong."); diff --git a/tests/lib/rules/camelcase.js b/tests/lib/rules/camelcase.js index 1e3eecc2110..7c7a4576c77 100644 --- a/tests/lib/rules/camelcase.js +++ b/tests/lib/rules/camelcase.js @@ -652,6 +652,17 @@ ruleTester.run("camelcase", rule, { } ] }, + { + code: "export * as snake_cased from 'mod'", + parserOptions: { ecmaVersion: 2020, sourceType: "module" }, + errors: [ + { + messageId: "notCamelCase", + data: { name: "snake_cased" }, + type: "Identifier" + } + ] + }, { code: "function foo({ no_camelcased }) {};", parserOptions: { ecmaVersion: 6 }, diff --git a/tests/lib/rules/id-blacklist.js b/tests/lib/rules/id-blacklist.js index a900924373f..6c3f729e17a 100644 --- a/tests/lib/rules/id-blacklist.js +++ b/tests/lib/rules/id-blacklist.js @@ -251,6 +251,14 @@ ruleTester.run("id-blacklist", rule, { error ] }, + { + code: "export * as foo from 'mod'", + options: ["foo"], + parserOptions: { ecmaVersion: 2020, sourceType: "module" }, + errors: [ + error + ] + }, { code: "import { foo } from 'mod'", options: ["foo"], diff --git a/tests/lib/rules/id-match.js b/tests/lib/rules/id-match.js index 77aee182486..f733dccbdff 100644 --- a/tests/lib/rules/id-match.js +++ b/tests/lib/rules/id-match.js @@ -401,6 +401,17 @@ ruleTester.run("id-match", rule, { } ] }, + { + code: "export * as no_camelcased from \"external-module\";", + options: ["^[^_]+$"], + parserOptions: { ecmaVersion: 2020, sourceType: "module" }, + errors: [ + { + message: "Identifier 'no_camelcased' does not match the pattern '^[^_]+$'.", + type: "Identifier" + } + ] + }, { code: "import { no_camelcased } from \"external-module\";", options: ["^[^_]+$", { diff --git a/tests/lib/rules/keyword-spacing.js b/tests/lib/rules/keyword-spacing.js index f7f5ccab2b7..08e93394307 100644 --- a/tests/lib/rules/keyword-spacing.js +++ b/tests/lib/rules/keyword-spacing.js @@ -129,6 +129,10 @@ ruleTester.run("keyword-spacing", rule, { { code: "import*as a from\"foo\"", options: [NEITHER], parserOptions: { ecmaVersion: 6, sourceType: "module" } }, { code: "import* as a from\"foo\"", options: [override("as", BOTH)], parserOptions: { ecmaVersion: 6, sourceType: "module" } }, { code: "import *as a from \"foo\"", options: [override("as", NEITHER)], parserOptions: { ecmaVersion: 6, sourceType: "module" } }, + { code: "export * as a from \"foo\"", parserOptions: { ecmaVersion: 2020, sourceType: "module" } }, + { code: "export*as a from\"foo\"", options: [NEITHER], parserOptions: { ecmaVersion: 2020, sourceType: "module" } }, + { code: "export* as a from\"foo\"", options: [override("as", BOTH)], parserOptions: { ecmaVersion: 2020, sourceType: "module" } }, + { code: "export *as a from \"foo\"", options: [override("as", NEITHER)], parserOptions: { ecmaVersion: 2020, sourceType: "module" } }, //---------------------------------------------------------------------- // async @@ -1383,6 +1387,33 @@ ruleTester.run("keyword-spacing", rule, { parserOptions: { ecmaVersion: 6, sourceType: "module" }, errors: unexpectedBefore("as") }, + { + code: "export *as a from \"foo\"", + output: "export * as a from \"foo\"", + parserOptions: { ecmaVersion: 2020, sourceType: "module" }, + errors: expectedBefore("as") + }, + { + code: "export* as a from\"foo\"", + output: "export*as a from\"foo\"", + options: [NEITHER], + parserOptions: { ecmaVersion: 2020, sourceType: "module" }, + errors: unexpectedBefore("as") + }, + { + code: "export*as a from\"foo\"", + output: "export* as a from\"foo\"", + options: [override("as", BOTH)], + parserOptions: { ecmaVersion: 2020, sourceType: "module" }, + errors: expectedBefore("as") + }, + { + code: "export * as a from \"foo\"", + output: "export *as a from \"foo\"", + options: [override("as", NEITHER)], + parserOptions: { ecmaVersion: 2020, sourceType: "module" }, + errors: unexpectedBefore("as") + }, //---------------------------------------------------------------------- // async diff --git a/tests/lib/rules/no-extra-boolean-cast.js b/tests/lib/rules/no-extra-boolean-cast.js index 715fe91f5ba..70ec8bd4f61 100644 --- a/tests/lib/rules/no-extra-boolean-cast.js +++ b/tests/lib/rules/no-extra-boolean-cast.js @@ -105,6 +105,11 @@ ruleTester.run("no-extra-boolean-cast", rule, { { code: "if ((!!foo || bar) === baz) {}", options: [{ enforceForLogicalOperands: true }] + }, + { + code: "if (!!foo ?? bar) {}", + options: [{ enforceForLogicalOperands: true }], + parserOptions: { ecmaVersion: 2020 } } ], @@ -2395,6 +2400,14 @@ ruleTester.run("no-extra-boolean-cast", rule, { { messageId: "unexpectedNegation", type: "UnaryExpression" }, { messageId: "unexpectedCall", type: "CallExpression" } ] + }, + + { + code: "if (Boolean(a ?? b) || c) {}", + output: "if ((a ?? b) || c) {}", + options: [{ enforceForLogicalOperands: true }], + parserOptions: { ecmaVersion: 2020 }, + errors: [{ messageId: "unexpectedCall", type: "CallExpression" }] } ] }); diff --git a/tests/lib/rules/no-extra-parens.js b/tests/lib/rules/no-extra-parens.js index 5062857e699..7dac725f568 100644 --- a/tests/lib/rules/no-extra-parens.js +++ b/tests/lib/rules/no-extra-parens.js @@ -589,7 +589,17 @@ ruleTester.run("no-extra-parens", rule, { "for (let a = b; a; a); a; a;", "for (a; a; a); a; a;", "for (; a; a); a; a;", - "for (let a = (b && c) === d; ;);" + "for (let a = (b && c) === d; ;);", + + // Nullish coalescing + { code: "var v = (a ?? b) || c", parserOptions: { ecmaVersion: 2020 } }, + { code: "var v = a ?? (b || c)", parserOptions: { ecmaVersion: 2020 } }, + { code: "var v = (a ?? b) && c", parserOptions: { ecmaVersion: 2020 } }, + { code: "var v = a ?? (b && c)", parserOptions: { ecmaVersion: 2020 } }, + { code: "var v = (a || b) ?? c", parserOptions: { ecmaVersion: 2020 } }, + { code: "var v = a || (b ?? c)", parserOptions: { ecmaVersion: 2020 } }, + { code: "var v = (a && b) ?? c", parserOptions: { ecmaVersion: 2020 } }, + { code: "var v = a && (b ?? c)", parserOptions: { ecmaVersion: 2020 } } ], invalid: [ @@ -2288,6 +2298,75 @@ ruleTester.run("no-extra-parens", rule, { invalid("a+(/^b$/)", "a+/^b$/", "Literal"), invalid("a/(/**/b)", "a/ /**/b", "Identifier"), invalid("a/(//\nb)", "a/ //\nb", "Identifier"), - invalid("a/(/^b$/)", "a/ /^b$/", "Literal") + invalid("a/(/^b$/)", "a/ /^b$/", "Literal"), + + + // Nullish coalescing + { + code: "var v = ((a ?? b)) || c", + output: "var v = (a ?? b) || c", + parserOptions: { ecmaVersion: 2020 }, + errors: [{ messageId: "unexpected" }] + }, + { + code: "var v = a ?? ((b || c))", + output: "var v = a ?? (b || c)", + parserOptions: { ecmaVersion: 2020 }, + errors: [{ messageId: "unexpected" }] + }, + { + code: "var v = ((a ?? b)) && c", + output: "var v = (a ?? b) && c", + parserOptions: { ecmaVersion: 2020 }, + errors: [{ messageId: "unexpected" }] + }, + { + code: "var v = a ?? ((b && c))", + output: "var v = a ?? (b && c)", + parserOptions: { ecmaVersion: 2020 }, + errors: [{ messageId: "unexpected" }] + }, + { + code: "var v = ((a || b)) ?? c", + output: "var v = (a || b) ?? c", + parserOptions: { ecmaVersion: 2020 }, + errors: [{ messageId: "unexpected" }] + }, + { + code: "var v = a || ((b ?? c))", + output: "var v = a || (b ?? c)", + parserOptions: { ecmaVersion: 2020 }, + errors: [{ messageId: "unexpected" }] + }, + { + code: "var v = ((a && b)) ?? c", + output: "var v = (a && b) ?? c", + parserOptions: { ecmaVersion: 2020 }, + errors: [{ messageId: "unexpected" }] + }, + { + code: "var v = a && ((b ?? c))", + output: "var v = a && (b ?? c)", + parserOptions: { ecmaVersion: 2020 }, + errors: [{ messageId: "unexpected" }] + }, + { + code: "var v = (a ?? b) ? b : c", + output: "var v = a ?? b ? b : c", + parserOptions: { ecmaVersion: 2020 }, + errors: [{ messageId: "unexpected" }] + }, + { + code: "var v = (a | b) ?? c | d", + output: "var v = a | b ?? c | d", + parserOptions: { ecmaVersion: 2020 }, + errors: [{ messageId: "unexpected" }] + }, + { + code: "var v = a | b ?? (c | d)", + output: "var v = a | b ?? c | d", + parserOptions: { ecmaVersion: 2020 }, + errors: [{ messageId: "unexpected" }] + } ] }); diff --git a/tests/lib/rules/no-mixed-operators.js b/tests/lib/rules/no-mixed-operators.js index 0e79b56058c..3650cd09fce 100644 --- a/tests/lib/rules/no-mixed-operators.js +++ b/tests/lib/rules/no-mixed-operators.js @@ -363,6 +363,31 @@ ruleTester.run("no-mixed-operators", rule, { } } ] + }, + { + code: "a + b ?? c", + options: [{ groups: [["+", "??"]] }], + parserOptions: { ecmaVersion: 2020 }, + errors: [ + { + column: 3, + endColumn: 4, + messageId: "unexpectedMixedOperator", + data: { + leftOperator: "+", + rightOperator: "??" + } + }, + { + column: 7, + endColumn: 9, + messageId: "unexpectedMixedOperator", + data: { + leftOperator: "+", + rightOperator: "??" + } + } + ] } ] }); diff --git a/tests/lib/rules/no-restricted-exports.js b/tests/lib/rules/no-restricted-exports.js index 6d0e693b57d..31458f15fc1 100644 --- a/tests/lib/rules/no-restricted-exports.js +++ b/tests/lib/rules/no-restricted-exports.js @@ -16,7 +16,7 @@ const { RuleTester } = require("../../../lib/rule-tester"); // Tests //------------------------------------------------------------------------------ -const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2018, sourceType: "module" } }); +const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2020, sourceType: "module" } }); ruleTester.run("no-restricted-exports", rule, { valid: [ @@ -73,6 +73,7 @@ ruleTester.run("no-restricted-exports", rule, { // does not check source in re-export declarations { code: "export { b } from 'a';", options: [{ restrictedNamedExports: ["a"] }] }, + { code: "export * as b from 'a';", options: [{ restrictedNamedExports: ["a"] }] }, // does not check non-export declarations { code: "var a;", options: [{ restrictedNamedExports: ["a"] }] }, @@ -332,6 +333,11 @@ ruleTester.run("no-restricted-exports", rule, { options: [{ restrictedNamedExports: ["a"] }], errors: [{ messageId: "restrictedNamed", data: { name: "a" }, type: "Identifier", column: 15 }] }, + { + code: "export * as a from 'a';", + options: [{ restrictedNamedExports: ["a"] }], + errors: [{ messageId: "restrictedNamed", data: { name: "a" }, type: "Identifier", column: 13 }] + }, // Note: duplicate identifiers in the same export declaration are a 'duplicate export' syntax error. Example: export var a, a; diff --git a/tests/lib/rules/no-restricted-imports.js b/tests/lib/rules/no-restricted-imports.js index 680bc1352e3..363229712c9 100644 --- a/tests/lib/rules/no-restricted-imports.js +++ b/tests/lib/rules/no-restricted-imports.js @@ -16,7 +16,7 @@ const rule = require("../../../lib/rules/no-restricted-imports"), // Tests //------------------------------------------------------------------------------ -const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 6, sourceType: "module" } }); +const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2020, sourceType: "module" } }); ruleTester.run("no-restricted-imports", rule, { valid: [ @@ -261,6 +261,16 @@ ruleTester.run("no-restricted-imports", rule, { column: 1, endColumn: 20 }] + }, { + code: "export * as ns from \"fs\";", + options: ["fs"], + errors: [{ + message: "'fs' import is restricted from being used.", + type: "ExportAllDeclaration", + line: 1, + column: 1, + endColumn: 26 + }] }, { code: "export {a} from \"fs\";", options: ["fs"], @@ -287,6 +297,22 @@ ruleTester.run("no-restricted-imports", rule, { column: 9, endColumn: 17 }] + }, { + code: "export * as ns from \"fs\";", + options: [{ + paths: [{ + name: "fs", + importNames: ["foo"], + message: "Don't import 'foo'." + }] + }], + errors: [{ + message: "* import is invalid because 'foo' from 'fs' is restricted. Don't import 'foo'.", + type: "ExportAllDeclaration", + line: 1, + column: 8, + endColumn: 9 + }] }, { code: "import withGitignores from \"foo\";", options: [{ diff --git a/tests/lib/rules/no-undef.js b/tests/lib/rules/no-undef.js index 13bca8c3d22..8c9c053a55a 100644 --- a/tests/lib/rules/no-undef.js +++ b/tests/lib/rules/no-undef.js @@ -71,13 +71,25 @@ ruleTester.run("no-undef", rule, { // new.target: https://github.com/eslint/eslint/issues/5420 { code: "class A { constructor() { new.target; } }", parserOptions: { ecmaVersion: 6 } }, - // Experimental, + // Rest property { code: "var {bacon, ...others} = stuff; foo(others)", parserOptions: { ecmaVersion: 2018 }, globals: { stuff: false, foo: false } + }, + + // export * as ns from "source" + { + code: 'export * as ns from "source"', + parserOptions: { ecmaVersion: 2020, sourceType: "module" } + }, + + // import.meta + { + code: "import.meta", + parserOptions: { ecmaVersion: 2020, sourceType: "module" } } ], invalid: [ diff --git a/tests/lib/rules/no-unneeded-ternary.js b/tests/lib/rules/no-unneeded-ternary.js index 25aab8a0cd1..82479a94cb3 100644 --- a/tests/lib/rules/no-unneeded-ternary.js +++ b/tests/lib/rules/no-unneeded-ternary.js @@ -357,6 +357,18 @@ ruleTester.run("no-unneeded-ternary", rule, { line: 1, column: 15 }] + }, + { + code: "var a = foo ? foo : a ?? b;", + output: "var a = foo || (a ?? b);", + options: [{ defaultAssignment: false }], + parserOptions: { ecmaVersion: 2020 }, + errors: [{ + messageId: "unnecessaryConditionalAssignment", + type: "ConditionalExpression", + line: 1, + column: 15 + }] } ] }); diff --git a/tests/lib/rules/no-unused-vars.js b/tests/lib/rules/no-unused-vars.js index 2ec382b95df..061ac08d51f 100644 --- a/tests/lib/rules/no-unused-vars.js +++ b/tests/lib/rules/no-unused-vars.js @@ -304,6 +304,18 @@ ruleTester.run("no-unused-vars", rule, { { code: "const a = () => () => { a(); }; a();", parserOptions: { ecmaVersion: 2015 } + }, + + // export * as ns from "source" + { + code: 'export * as ns from "source"', + parserOptions: { ecmaVersion: 2020, sourceType: "module" } + }, + + // import.meta + { + code: "import.meta", + parserOptions: { ecmaVersion: 2020, sourceType: "module" } } ], invalid: [ diff --git a/tests/lib/rules/operator-linebreak.js b/tests/lib/rules/operator-linebreak.js index 3910ac8a0e7..18a37e3fc80 100644 --- a/tests/lib/rules/operator-linebreak.js +++ b/tests/lib/rules/operator-linebreak.js @@ -628,6 +628,16 @@ ruleTester.run("operator-linebreak", rule, { endLine: 1, endColumn: 14 }] + }, + { + code: "foo ??\n bar", + output: "foo\n ?? bar", + options: ["after", { overrides: { "??": "before" } }], + parserOptions: { ecmaVersion: 2020 }, + errors: [{ + messageId: "operatorAtBeginning", + data: { operator: "??" } + }] } ] });