Extract shouldAddParenthesesToIdentifier (#18432)
This commit is contained in:
1 parent
b6cf29f3d0
commit
1f6ba74b87
4 files changed
+117
-99
No files matched your search
+1
-1
@@ -53,7 +53,7 @@ export default {
|
||||
fixable: "code",
|
||||
},
|
||||
create(context) {
|
||||
if (path.basename(context.getFilename()) !== "needs-parens.js") {
|
||||
if (path.basename(path.dirname(context.getFilename())) !== "parentheses") {
|
||||
return {};
|
||||
}
|
||||
const sourceCode = context.getSourceCode();
|
||||
@@ -33,7 +33,7 @@ test("await-cli-tests", {
|
||||
],
|
||||
});
|
||||
|
||||
test("better-parent-property-check-in-needs-parens", {
|
||||
test("better-parent-property-check-in-parentheses-check", {
|
||||
valid: ["function needsParens() {return parent.test === node;}"],
|
||||
invalid: [
|
||||
{
|
||||
@@ -70,7 +70,7 @@ test("better-parent-property-check-in-needs-parens", {
|
||||
output: testCase.output
|
||||
? `function needsParens() {${testCase.output}}`
|
||||
: null,
|
||||
filename: "needs-parens.js",
|
||||
filename: "parentheses/foo.js",
|
||||
})),
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
import {
|
||||
isBinaryCastExpression,
|
||||
startsWithNoLookaheadToken,
|
||||
} from "../utilities/index.js";
|
||||
|
||||
function shouldAddParenthesesToIdentifier(path) {
|
||||
const { node } = path;
|
||||
|
||||
// Identifiers never need parentheses.
|
||||
if (node.type !== "Identifier") {
|
||||
return false;
|
||||
}
|
||||
|
||||
// ...unless those identifiers are embed placeholders. They might be substituted by complex
|
||||
// expressions, so the parens around them should not be dropped. Example (JS-in-HTML-in-JS):
|
||||
// let tpl = html`<script> f((${expr}) / 2); </script>`;
|
||||
// If the inner JS formatter removes the parens, the expression might change its meaning:
|
||||
// f((a + b) / 2) vs f(a + b / 2)
|
||||
if (
|
||||
node.extra?.parenthesized &&
|
||||
/^PRETTIER_HTML_PLACEHOLDER_\d+_\d+_IN_JS$/u.test(node.name)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const { key, parent } = path;
|
||||
// `for ((async) of []);` and `for ((let) of []);`
|
||||
if (
|
||||
key === "left" &&
|
||||
((node.name === "async" && !parent.await) || node.name === "let") &&
|
||||
parent.type === "ForOfStatement"
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// `for ((let.a) of []);`
|
||||
if (node.name === "let") {
|
||||
const expression = path.findAncestor(
|
||||
(node) => node.type === "ForOfStatement",
|
||||
)?.left;
|
||||
if (
|
||||
expression &&
|
||||
startsWithNoLookaheadToken(
|
||||
expression,
|
||||
(leftmostNode) => leftmostNode === node,
|
||||
)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// `(let)[a] = 1`
|
||||
if (
|
||||
key === "object" &&
|
||||
node.name === "let" &&
|
||||
parent.type === "MemberExpression" &&
|
||||
parent.computed &&
|
||||
!parent.optional
|
||||
) {
|
||||
const statement = path.findAncestor(
|
||||
(node) =>
|
||||
node.type === "ExpressionStatement" ||
|
||||
node.type === "ForStatement" ||
|
||||
node.type === "ForInStatement",
|
||||
);
|
||||
const expression = !statement
|
||||
? undefined
|
||||
: statement.type === "ExpressionStatement"
|
||||
? statement.expression
|
||||
: statement.type === "ForStatement"
|
||||
? statement.init
|
||||
: statement.left;
|
||||
if (
|
||||
expression &&
|
||||
startsWithNoLookaheadToken(
|
||||
expression,
|
||||
(leftmostNode) => leftmostNode === node,
|
||||
)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// `(type) satisfies never;` and similar cases
|
||||
if (key === "expression") {
|
||||
switch (node.name) {
|
||||
case "await":
|
||||
case "interface":
|
||||
case "module":
|
||||
case "using":
|
||||
case "yield":
|
||||
case "let":
|
||||
case "component":
|
||||
case "hook":
|
||||
case "type": {
|
||||
const ancestorNeitherAsNorSatisfies = path.findAncestor(
|
||||
(node) => !isBinaryCastExpression(node),
|
||||
);
|
||||
if (
|
||||
ancestorNeitherAsNorSatisfies !== parent &&
|
||||
ancestorNeitherAsNorSatisfies.type === "ExpressionStatement"
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
export { shouldAddParenthesesToIdentifier };
|
||||
@@ -24,6 +24,7 @@ import {
|
||||
startsWithNoLookaheadToken,
|
||||
} from "../utilities/index.js";
|
||||
import { returnArgumentHasLeadingComment } from "../utilities/return-statement-has-leading-comment.js";
|
||||
import { shouldAddParenthesesToIdentifier } from "./identifier.js";
|
||||
|
||||
/**
|
||||
* @import AstPath from "../../common/ast-path.js"
|
||||
@@ -55,103 +56,8 @@ function needsParentheses(path, options) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Identifiers never need parentheses.
|
||||
if (node.type === "Identifier") {
|
||||
// ...unless those identifiers are embed placeholders. They might be substituted by complex
|
||||
// expressions, so the parens around them should not be dropped. Example (JS-in-HTML-in-JS):
|
||||
// let tpl = html`<script> f((${expr}) / 2); </script>`;
|
||||
// If the inner JS formatter removes the parens, the expression might change its meaning:
|
||||
// f((a + b) / 2) vs f(a + b / 2)
|
||||
if (
|
||||
node.extra?.parenthesized &&
|
||||
/^PRETTIER_HTML_PLACEHOLDER_\d+_\d+_IN_JS$/u.test(node.name)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// `for ((async) of []);` and `for ((let) of []);`
|
||||
if (
|
||||
key === "left" &&
|
||||
((node.name === "async" && !parent.await) || node.name === "let") &&
|
||||
parent.type === "ForOfStatement"
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// `for ((let.a) of []);`
|
||||
if (node.name === "let") {
|
||||
const expression = path.findAncestor(
|
||||
(node) => node.type === "ForOfStatement",
|
||||
)?.left;
|
||||
if (
|
||||
expression &&
|
||||
startsWithNoLookaheadToken(
|
||||
expression,
|
||||
(leftmostNode) => leftmostNode === node,
|
||||
)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// `(let)[a] = 1`
|
||||
if (
|
||||
key === "object" &&
|
||||
node.name === "let" &&
|
||||
parent.type === "MemberExpression" &&
|
||||
parent.computed &&
|
||||
!parent.optional
|
||||
) {
|
||||
const statement = path.findAncestor(
|
||||
(node) =>
|
||||
node.type === "ExpressionStatement" ||
|
||||
node.type === "ForStatement" ||
|
||||
node.type === "ForInStatement",
|
||||
);
|
||||
const expression = !statement
|
||||
? undefined
|
||||
: statement.type === "ExpressionStatement"
|
||||
? statement.expression
|
||||
: statement.type === "ForStatement"
|
||||
? statement.init
|
||||
: statement.left;
|
||||
if (
|
||||
expression &&
|
||||
startsWithNoLookaheadToken(
|
||||
expression,
|
||||
(leftmostNode) => leftmostNode === node,
|
||||
)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// `(type) satisfies never;` and similar cases
|
||||
if (key === "expression") {
|
||||
switch (node.name) {
|
||||
case "await":
|
||||
case "interface":
|
||||
case "module":
|
||||
case "using":
|
||||
case "yield":
|
||||
case "let":
|
||||
case "component":
|
||||
case "hook":
|
||||
case "type": {
|
||||
const ancestorNeitherAsNorSatisfies = path.findAncestor(
|
||||
(node) => !isBinaryCastExpression(node),
|
||||
);
|
||||
if (
|
||||
ancestorNeitherAsNorSatisfies !== parent &&
|
||||
ancestorNeitherAsNorSatisfies.type === "ExpressionStatement"
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
return shouldAddParenthesesToIdentifier(path);
|
||||
}
|
||||
|
||||
if (
|
||||
|
||||
Reference in new issue
Block a user