Skip to content

Commit

Permalink
Fix: Check division operator in astUtils.canTokensBeAdjacent (#12879)
Browse files Browse the repository at this point in the history
* Fix: Check division operator in astUtils.canTokensBeAdjacent

* Add try-catch and Shebang check

* Upgrade espree, use latestEcmaVersion
  • Loading branch information
mdjermanovic committed Mar 9, 2020
1 parent fd8e1f5 commit 6cef0d5
Show file tree
Hide file tree
Showing 8 changed files with 137 additions and 13 deletions.
8 changes: 4 additions & 4 deletions lib/rules/no-extra-parens.js
Expand Up @@ -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);
}

/**
Expand Down
6 changes: 3 additions & 3 deletions lib/rules/operator-assignment.js
Expand Up @@ -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
}
Expand Down
61 changes: 58 additions & 3 deletions lib/rules/utils/ast-utils.js
Expand Up @@ -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") {
Expand All @@ -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;
}

Expand All @@ -1393,6 +1444,10 @@ module.exports = {
return true;
}

if (leftToken.type === "Block" || rightToken.type === "Block" || rightToken.type === "Line") {
return true;
}

return false;
},

Expand Down
2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -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",
Expand Down
16 changes: 15 additions & 1 deletion tests/lib/rules/no-extra-parens.js
Expand Up @@ -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")
]
});
10 changes: 10 additions & 0 deletions tests/lib/rules/no-implicit-coercion.js
Expand Up @@ -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"
}]
}
]
});
15 changes: 15 additions & 0 deletions tests/lib/rules/operator-assignment.js
Expand Up @@ -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
Expand Down
32 changes: 31 additions & 1 deletion tests/lib/rules/utils/ast-utils.js
Expand Up @@ -1342,14 +1342,44 @@ 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) => {
it(tokenStrings.join(", "), () => {
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", () => {
Expand Down

0 comments on commit 6cef0d5

Please sign in to comment.