diff --git a/lib/rules/no-extra-parens.js b/lib/rules/no-extra-parens.js index a5488c3c1c6..a3dd5bab699 100644 --- a/lib/rules/no-extra-parens.js +++ b/lib/rules/no-extra-parens.js @@ -307,13 +307,13 @@ module.exports = { */ function requiresLeadingSpace(node) { const leftParenToken = sourceCode.getTokenBefore(node); - const tokenBeforeLeftParen = sourceCode.getTokenBefore(node, 1); - const firstToken = sourceCode.getFirstToken(node); + const tokenBeforeLeftParen = sourceCode.getTokenBefore(leftParenToken, { includeComments: true }); + const tokenAfterLeftParen = sourceCode.getTokenAfter(leftParenToken, { includeComments: true }); return tokenBeforeLeftParen && tokenBeforeLeftParen.range[1] === leftParenToken.range[0] && - leftParenToken.range[1] === firstToken.range[0] && - !astUtils.canTokensBeAdjacent(tokenBeforeLeftParen, firstToken); + leftParenToken.range[1] === tokenAfterLeftParen.range[0] && + !astUtils.canTokensBeAdjacent(tokenBeforeLeftParen, tokenAfterLeftParen); } /** diff --git a/lib/rules/operator-assignment.js b/lib/rules/operator-assignment.js index c4c8671f327..6820793439c 100644 --- a/lib/rules/operator-assignment.js +++ b/lib/rules/operator-assignment.js @@ -214,12 +214,12 @@ module.exports = { ) { rightText = `${sourceCode.text.slice(operatorToken.range[1], node.right.range[0])}(${sourceCode.getText(node.right)})`; } else { - const firstRightToken = sourceCode.getFirstToken(node.right); + const tokenAfterOperator = sourceCode.getTokenAfter(operatorToken, { includeComments: true }); let rightTextPrefix = ""; if ( - operatorToken.range[1] === firstRightToken.range[0] && - !astUtils.canTokensBeAdjacent({ type: "Punctuator", value: newOperator }, firstRightToken) + operatorToken.range[1] === tokenAfterOperator.range[0] && + !astUtils.canTokensBeAdjacent({ type: "Punctuator", value: newOperator }, tokenAfterOperator) ) { rightTextPrefix = " "; // foo+=+bar -> foo= foo+ +bar } diff --git a/lib/rules/utils/ast-utils.js b/lib/rules/utils/ast-utils.js index e10544dd61e..82156a34ec9 100644 --- a/lib/rules/utils/ast-utils.js +++ b/lib/rules/utils/ast-utils.js @@ -1357,17 +1357,65 @@ module.exports = { * next to each other, behavior is undefined (although it should return `true` in most cases). */ canTokensBeAdjacent(leftValue, rightValue) { + const espreeOptions = { + ecmaVersion: espree.latestEcmaVersion, + comment: true, + range: true + }; + let leftToken; if (typeof leftValue === "string") { - const leftTokens = espree.tokenize(leftValue, { ecmaVersion: 2015 }); + let tokens; + + try { + tokens = espree.tokenize(leftValue, espreeOptions); + } catch (e) { + return false; + } + + const comments = tokens.comments; + + leftToken = tokens[tokens.length - 1]; + if (comments.length) { + const lastComment = comments[comments.length - 1]; - leftToken = leftTokens[leftTokens.length - 1]; + if (lastComment.range[0] > leftToken.range[0]) { + leftToken = lastComment; + } + } } else { leftToken = leftValue; } - const rightToken = typeof rightValue === "string" ? espree.tokenize(rightValue, { ecmaVersion: 2015 })[0] : rightValue; + if (leftToken.type === "Shebang") { + return false; + } + + let rightToken; + + if (typeof rightValue === "string") { + let tokens; + + try { + tokens = espree.tokenize(rightValue, espreeOptions); + } catch (e) { + return false; + } + + const comments = tokens.comments; + + rightToken = tokens[0]; + if (comments.length) { + const firstComment = comments[0]; + + if (firstComment.range[0] < rightToken.range[0]) { + rightToken = firstComment; + } + } + } else { + rightToken = rightValue; + } if (leftToken.type === "Punctuator" || rightToken.type === "Punctuator") { if (leftToken.type === "Punctuator" && rightToken.type === "Punctuator") { @@ -1379,6 +1427,9 @@ module.exports = { MINUS_TOKENS.has(leftToken.value) && MINUS_TOKENS.has(rightToken.value) ); } + if (leftToken.type === "Punctuator" && leftToken.value === "/") { + return !["Block", "Line", "RegularExpression"].includes(rightToken.type); + } return true; } @@ -1393,6 +1444,10 @@ module.exports = { return true; } + if (leftToken.type === "Block" || rightToken.type === "Block" || rightToken.type === "Line") { + return true; + } + return false; }, diff --git a/package.json b/package.json index 1ed4064cf15..4bb294e8c60 100644 --- a/package.json +++ b/package.json @@ -55,7 +55,7 @@ "eslint-scope": "^5.0.0", "eslint-utils": "^1.4.3", "eslint-visitor-keys": "^1.1.0", - "espree": "^6.1.2", + "espree": "^6.2.0", "esquery": "^1.0.1", "esutils": "^2.0.2", "file-entry-cache": "^5.0.1", diff --git a/tests/lib/rules/no-extra-parens.js b/tests/lib/rules/no-extra-parens.js index 2766a135590..13df7dc1ed1 100644 --- a/tests/lib/rules/no-extra-parens.js +++ b/tests/lib/rules/no-extra-parens.js @@ -2221,6 +2221,20 @@ ruleTester.run("no-extra-parens", rule, { code: "var foo = { [((bar1, bar2))]: baz };", output: "var foo = { [(bar1, bar2)]: baz };", errors: [{ messageId: "unexpected" }] - } + }, + + // adjacent tokens tests for division operator, comments and regular expressions + invalid("a+/**/(/**/b)", "a+/**//**/b", "Identifier"), + invalid("a+/**/(//\nb)", "a+/**///\nb", "Identifier"), + invalid("a in(/**/b)", "a in/**/b", "Identifier"), + invalid("a in(//\nb)", "a in//\nb", "Identifier"), + invalid("a+(/**/b)", "a+/**/b", "Identifier"), + invalid("a+/**/(b)", "a+/**/b", "Identifier"), + invalid("a+(//\nb)", "a+//\nb", "Identifier"), + invalid("a+//\n(b)", "a+//\nb", "Identifier"), + invalid("a+(/^b$/)", "a+/^b$/", "Literal"), + invalid("a/(/**/b)", "a/ /**/b", "Identifier"), + invalid("a/(//\nb)", "a/ //\nb", "Identifier"), + invalid("a/(/^b$/)", "a/ /^b$/", "Literal") ] }); diff --git a/tests/lib/rules/no-implicit-coercion.js b/tests/lib/rules/no-implicit-coercion.js index 49e7b1e70a7..73bfe7df6d8 100644 --- a/tests/lib/rules/no-implicit-coercion.js +++ b/tests/lib/rules/no-implicit-coercion.js @@ -345,6 +345,16 @@ ruleTester.run("no-implicit-coercion", rule, { data: { recommendation: "Number(foo)" }, type: "UnaryExpression" }] + }, + { + code: "let x ='' + 1n;", + output: "let x =String(1n);", + parserOptions: { ecmaVersion: 2020 }, + errors: [{ + messageId: "useRecommendation", + data: { recommendation: "String(1n)" }, + type: "BinaryExpression" + }] } ] }); diff --git a/tests/lib/rules/operator-assignment.js b/tests/lib/rules/operator-assignment.js index b7174b9ee72..f690f7eb7f9 100644 --- a/tests/lib/rules/operator-assignment.js +++ b/tests/lib/rules/operator-assignment.js @@ -363,6 +363,21 @@ ruleTester.run("operator-assignment", rule, { output: "foo= foo/bar", // tokens can be adjacent options: ["never"], errors: UNEXPECTED_OPERATOR_ASSIGNMENT + }, { + code: "foo/=/**/bar", + output: "foo= foo/ /**/bar", // // tokens cannot be adjacent, insert a space between + options: ["never"], + errors: UNEXPECTED_OPERATOR_ASSIGNMENT + }, { + code: "foo/=//\nbar", + output: "foo= foo/ //\nbar", // // tokens cannot be adjacent, insert a space between + options: ["never"], + errors: UNEXPECTED_OPERATOR_ASSIGNMENT + }, { + code: "foo/=/^bar$/", + output: "foo= foo/ /^bar$/", // // tokens cannot be adjacent, insert a space between + options: ["never"], + errors: UNEXPECTED_OPERATOR_ASSIGNMENT }, { code: "foo+=+bar", output: "foo= foo+ +bar", // tokens cannot be adjacent, insert a space between diff --git a/tests/lib/rules/utils/ast-utils.js b/tests/lib/rules/utils/ast-utils.js index 1acc227015a..dfa494fb6dc 100644 --- a/tests/lib/rules/utils/ast-utils.js +++ b/tests/lib/rules/utils/ast-utils.js @@ -1342,7 +1342,27 @@ describe("ast-utils", () => { [["++", "+"], false], [["--", "-"], false], [["+", "++"], false], - [["-", "--"], false] + [["-", "--"], false], + [["a/", "b"], true], + [["a/", "+b"], true], + [["a+", "/^regex$/"], true], + [["a/", "/^regex$/"], false], + [["a+", "/**/b"], true], + [["a/", "/**/b"], false], + [["a+", "//\nb"], true], + [["a/", "//\nb"], false], + [["a/**/", "b"], true], + [["/**/a", "b"], false], + [["a", "/**/b"], true], + [["a", "b/**/"], false], + [["a", "//\nb"], true], + [["a", "b//"], false], + [["#!/usr/bin/env node", "("], false], + [["123invalidtoken", "("], false], + [["(", "123invalidtoken"], false], + [["(", "1n"], true], + [["1n", "+"], true], + [["1n", "in"], false] ]); CASES.forEach((expectedResult, tokenStrings) => { @@ -1350,6 +1370,16 @@ describe("ast-utils", () => { assert.strictEqual(astUtils.canTokensBeAdjacent(tokenStrings[0], tokenStrings[1]), expectedResult); }); }); + + it("#!/usr/bin/env node, (", () => { + assert.strictEqual( + astUtils.canTokensBeAdjacent( + { type: "Shebang", value: "#!/usr/bin/env node" }, + { type: "Punctuator", value: "(" } + ), + false + ); + }); }); describe("equalTokens", () => {