Support print ChainExpression (#13735)

This commit is contained in:
fisker Cheung authored and GitHub committed 2023-02-13 10:11:40 +08:00
1 parent 04ac860123
commit 5cdc3ab2dd
20 files changed
+232 -49

No files matched your search

+12
View File
@@ -203,6 +203,18 @@ function clean(ast, newObj, parent) {
) {
return newObj.types[0];
}
// We print `(a?.b!).c` as `(a?.b)!.c`, but `typescript` parse them differently
if (
ast.type === "ChainExpression" &&
ast.expression.type === "TSNonNullExpression"
) {
// Ideally, we should swap these two nodes, but `type` is the only difference
[newObj.type, newObj.expression.type] = [
newObj.expression.type,
newObj.type,
];
}
}
clean.ignoredProperties = ignoredProperties;
+3 -1
View File
@@ -22,7 +22,9 @@ function canAttachComment(node) {
node.type !== "TemplateElement" &&
node.type !== "Import" &&
// `babel-ts` doesn't have similar node for `class Foo { bar() /* bat */; }`
node.type !== "TSEmptyBodyFunctionExpression"
node.type !== "TSEmptyBodyFunctionExpression" &&
// `babel` doesn't have similar node to attach comments
node.type !== "ChainExpression"
);
}
+87 -16
View File
@@ -772,25 +772,15 @@ function needsParens(path, options) {
default:
return false;
}
case "OptionalMemberExpression":
case "OptionalCallExpression": {
const parentParent = path.grandparent;
if (
(key === "object" && parent.type === "MemberExpression") ||
(key === "callee" &&
(parent.type === "CallExpression" ||
parent.type === "NewExpression")) ||
(parent.type === "TSNonNullExpression" &&
parentParent.type === "MemberExpression" &&
parentParent.object === parent)
) {
return true;
}
}
// fallthrough
case "OptionalCallExpression":
case "CallExpression":
case "MemberExpression":
if (shouldAddParenthesesToChainElement(path)) {
return true;
}
// fallthrough
case "TaggedTemplateExpression":
case "TSNonNullExpression":
if (
@@ -1021,4 +1011,85 @@ function shouldWrapFunctionForExportDefault(path, options) {
);
}
/*
Matches following cases:
```js
(a?.b).c;
(a?.()).b;
(a?.b!).c;
(a?.()!).b;
(a?.b)!.c;
(a?.())!.b;
(a?.b)();
(a?.())();
new (a?.b)();
new (a?.())();
```
*/
function shouldAddParenthesesToChainElement(path) {
// Babel, this was implemented before #13735, can use `path.match` as estree does
const { node, parent, grandparent, key } = path;
if (
(node.type === "OptionalMemberExpression" ||
node.type === "OptionalCallExpression") &&
((key === "object" && parent.type === "MemberExpression") ||
(key === "callee" &&
(parent.type === "CallExpression" ||
parent.type === "NewExpression")) ||
(parent.type === "TSNonNullExpression" &&
grandparent.type === "MemberExpression" &&
grandparent.object === parent))
) {
return true;
}
// ESTree, same logic as babel
if (
path.match(
() => node.type === "CallExpression" || node.type === "MemberExpression",
(node, name) => name === "expression" && node.type === "ChainExpression"
) &&
(path.match(
undefined,
undefined,
(node, name) =>
(name === "callee" &&
((node.type === "CallExpression" && !node.optional) ||
node.type === "NewExpression")) ||
(name === "object" &&
node.type === "MemberExpression" &&
!node.optional)
) ||
path.match(
undefined,
undefined,
(node, name) =>
name === "expression" && node.type === "TSNonNullExpression",
(node, name) => name === "object" && node.type === "MemberExpression"
))
) {
return true;
}
// Babel treat `(a?.b!).c` and `(a?.b)!.c` the same, https://github.com/babel/babel/discussions/15077
// Use this to align with babel
if (
path.match(
() => node.type === "CallExpression" || node.type === "MemberExpression",
(node, name) =>
name === "expression" && node.type === "TSNonNullExpression",
(node, name) => name === "expression" && node.type === "ChainExpression",
(node, name) => name === "object" && node.type === "MemberExpression"
)
) {
return true;
}
// This function only handle cases above
return false;
}
export default needsParens;
@@ -61,10 +61,6 @@ function postprocess(ast, options) {
ast = visitNode(ast, (node) => {
switch (node.type) {
// Espree
case "ChainExpression":
return transformChainExpression(node.expression);
case "LogicalExpression":
// We remove unneeded parens around same-operator LogicalExpressions
if (isUnbalancedLogicalTree(node)) {
@@ -207,29 +203,6 @@ function postprocess(ast, options) {
}
}
// This is a workaround to transform `ChainExpression` from `acorn`, `espree`,
// `meriyah`, and `typescript` into `babel` shape AST, we should do the opposite,
// since `ChainExpression` is the standard `estree` AST for `optional chaining`
// https://github.com/estree/estree/blob/master/es2020.md
function transformChainExpression(node) {
switch (node.type) {
case "CallExpression":
node.type = "OptionalCallExpression";
node.callee = transformChainExpression(node.callee);
break;
case "MemberExpression":
node.type = "OptionalMemberExpression";
node.object = transformChainExpression(node.object);
break;
// typescript
case "TSNonNullExpression":
node.expression = transformChainExpression(node.expression);
break;
// No default
}
return node;
}
function isUnbalancedLogicalTree(node) {
return (
node.type === "LogicalExpression" &&
+1 -1
View File
@@ -331,7 +331,7 @@ function isPoorlyBreakableMemberOrCallChain(
const goDeeper = () =>
isPoorlyBreakableMemberOrCallChain(path, options, print, true);
if (node.type === "TSNonNullExpression") {
if (node.type === "ChainExpression" || node.type === "TSNonNullExpression") {
return path.call(goDeeper, "expression");
}
+2
View File
@@ -499,6 +499,8 @@ function printJsxExpressionContainer(path, options, print) {
(shouldInline(node.argument, node) ||
node.argument.type === "JSXElement")) ||
isCallExpression(node) ||
(node.type === "ChainExpression" &&
isCallExpression(node.expression)) ||
node.type === "FunctionExpression" ||
node.type === "TemplateLiteral" ||
node.type === "TaggedTemplateExpression" ||
+3 -2
View File
@@ -1,4 +1,5 @@
import { indent, line } from "../../document/builders.js";
import { isCallExpression, isMemberExpression } from "../utils/index.js";
import { printTypeAnnotationProperty } from "./type-annotation.js";
/**
@@ -21,8 +22,8 @@ function printOptionalToken(path) {
return "";
}
if (
node.type === "OptionalCallExpression" ||
(node.type === "OptionalMemberExpression" && node.computed) ||
isCallExpression(node) ||
(isMemberExpression(node) && node.computed) ||
node.type === "OptionalIndexedAccessType"
) {
return "?.";
+1
View File
@@ -150,6 +150,7 @@ function shouldExtraIndentForConditionalExpression(path) {
const node = path.getParentNode(ancestorCount);
if (
(node.type === "ChainExpression" && node.expression === child) ||
(isCallExpression(node) && node.callee === child) ||
(isMemberExpression(node) && node.object === child) ||
(node.type === "TSNonNullExpression" && node.expression === child)
+4
View File
@@ -247,6 +247,10 @@ function printPathNoParens(path, options, print, args) {
danglingComment ? [" ", danglingComment] : "",
];
}
case "ChainExpression":
return print("expression");
// Babel non-standard node. Used for Closure-style type casts. See postprocess.js.
case "ParenthesizedExpression": {
const shouldHug =
+7 -1
View File
@@ -63,7 +63,8 @@ function hasNakedLeftSide(node) {
node.type === "BindExpression" ||
(node.type === "UpdateExpression" && !node.prefix) ||
isTSTypeExpression(node) ||
node.type === "TSNonNullExpression"
node.type === "TSNonNullExpression" ||
node.type === "ChainExpression"
);
}
@@ -546,6 +547,10 @@ function isSimpleTemplateLiteral(node) {
return true;
}
if (expr.type === "ChainExpression") {
expr = expr.expression;
}
// Allow `a.b.c`, `a.b[c]`, and `this.x.y`
if (isMemberExpression(expr)) {
let head = expr;
@@ -897,6 +902,7 @@ function startsWithNoLookaheadToken(node, predicate) {
return node.object && startsWithNoLookaheadToken(node.object, predicate);
case "SequenceExpression":
return startsWithNoLookaheadToken(node.expressions[0], predicate);
case "ChainExpression":
case "TSSatisfiesExpression":
case "TSAsExpression":
case "TSNonNullExpression":
+1 -1
View File
@@ -94,7 +94,7 @@ const categoryParsers = new Map([
"typescript",
"babel-ts",
],
extensions: [".js"],
extensions: [".js", ".jsx"],
},
],
[
@@ -0,0 +1,29 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`test.js format 1`] = `
====================================options=====================================
parsers: ["babel", "typescript", "flow"]
printWidth: 80
| printWidth
=====================================input======================================
(a?.b).c;
(a?.()).b;
(a?.b)();
(a?.())();
new (a?.b)();
new (a?.())();
=====================================output=====================================
(a?.b).c;
(a?.()).b;
(a?.b)();
(a?.())();
new (a?.b)();
new (a?.())();
================================================================================
`;
@@ -0,0 +1 @@
run_spec(import.meta, ["babel", "typescript", "flow"]);
+8
View File
@@ -0,0 +1,8 @@
(a?.b).c;
(a?.()).b;
(a?.b)();
(a?.())();
new (a?.b)();
new (a?.())();
@@ -0,0 +1,35 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`optional-chaining.jsx format 1`] = `
====================================options=====================================
parsers: ["flow", "babel", "typescript"]
printWidth: 80
| printWidth
=====================================input======================================
function foo() {
// https://github.com/marmelab/react-admin/blob/5ae855aa958ba54438b144bf0907b1437c5a5d77/examples/demo/src/orders/Totals.tsx#L38-L43
return (
<TableCell className={classes.rightAlignedCell}>
{record?.delivery_fees.toLocaleString(undefined, {
style: 'currency',
currency: 'USD',
})}
</TableCell>
)
}
=====================================output=====================================
function foo() {
// https://github.com/marmelab/react-admin/blob/5ae855aa958ba54438b144bf0907b1437c5a5d77/examples/demo/src/orders/Totals.tsx#L38-L43
return (
<TableCell className={classes.rightAlignedCell}>
{record?.delivery_fees.toLocaleString(undefined, {
style: "currency",
currency: "USD",
})}
</TableCell>
);
}
================================================================================
`;
@@ -0,0 +1 @@
run_spec(import.meta, ["flow", "babel", "typescript"]);
@@ -0,0 +1,11 @@
function foo() {
// https://github.com/marmelab/react-admin/blob/5ae855aa958ba54438b144bf0907b1437c5a5d77/examples/demo/src/orders/Totals.tsx#L38-L43
return (
<TableCell className={classes.rightAlignedCell}>
{record?.delivery_fees.toLocaleString(undefined, {
style: 'currency',
currency: 'USD',
})}
</TableCell>
)
}
@@ -0,0 +1,21 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`test.ts format 1`] = `
====================================options=====================================
parsers: ["typescript"]
printWidth: 80
| printWidth
=====================================input======================================
(a?.b!).c;
(a?.()!).b;
(a?.b)!.c;
(a?.())!.b;
=====================================output=====================================
(a?.b)!.c;
(a?.())!.b;
(a?.b)!.c;
(a?.())!.b;
================================================================================
`;
@@ -0,0 +1 @@
run_spec(import.meta, ["typescript"]);
@@ -0,0 +1,4 @@
(a?.b!).c;
(a?.()!).b;
(a?.b)!.c;
(a?.())!.b;