Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix: Check division operator in astUtils.canTokensBeAdjacent #12879

Merged
merged 3 commits into from Mar 9, 2020
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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
57 changes: 54 additions & 3 deletions lib/rules/utils/ast-utils.js
Expand Up @@ -1357,17 +1357,61 @@ module.exports = {
* next to each other, behavior is undefined (although it should return `true` in most cases).
*/
canTokensBeAdjacent(leftValue, rightValue) {
const espreeOptions = { ecmaVersion: 2020, comment: true, range: true };
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-blocker for this PR, but it could be nice to add the ability to specify “latest” somehow, so that this never gets out of date.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That would be great! But, I'm not sure if there is a way to do that at the moment? Does espree support something like "latest" or provides a list of available versions?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

v/_ \ I don't think espree has such a thing.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn’t, no! Definitely not a blocker for this - just an idea I had and wanted to write down for posterity 😄

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A list of all versions could be also useful for the online demo, it's hard-coded there and has to be updated manually.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR/proposal here: eslint/espree#430


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;
}

leftToken = leftTokens[leftTokens.length - 1];
const comments = tokens.comments;

leftToken = tokens[tokens.length - 1];
if (comments.length) {
const lastComment = comments[comments.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 +1423,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 +1440,10 @@ module.exports = {
return true;
}

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

return false;
},

Expand Down
16 changes: 15 additions & 1 deletion tests/lib/rules/no-extra-parens.js
Expand Up @@ -2192,6 +2192,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
29 changes: 28 additions & 1 deletion tests/lib/rules/utils/ast-utils.js
Expand Up @@ -1342,14 +1342,41 @@ 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]
]);

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