packages/babel-generator/src/node/parentheses.js
import * as t from "@babel/types";
const PRECEDENCE = {
"||": 0,
"??": 0,
"&&": 1,
"|": 2,
"^": 3,
"&": 4,
"==": 5,
"===": 5,
"!=": 5,
"!==": 5,
"<": 6,
">": 6,
"<=": 6,
">=": 6,
in: 6,
instanceof: 6,
">>": 7,
"<<": 7,
">>>": 7,
"+": 8,
"-": 8,
"*": 9,
"/": 9,
"%": 9,
"**": 10,
};
const isClassExtendsClause = (node: Object, parent: Object): boolean =>
(t.isClassDeclaration(parent) || t.isClassExpression(parent)) &&
parent.superClass === node;
const hasPostfixPart = (node: Object, parent: Object) =>
((t.isMemberExpression(parent) || t.isOptionalMemberExpression(parent)) &&
parent.object === node) ||
((t.isCallExpression(parent) ||
t.isOptionalCallExpression(parent) ||
t.isNewExpression(parent)) &&
parent.callee === node) ||
(t.isTaggedTemplateExpression(parent) && parent.tag === node) ||
t.isTSNonNullExpression(parent);
export function NullableTypeAnnotation(node: Object, parent: Object): boolean {
return t.isArrayTypeAnnotation(parent);
}
export function FunctionTypeAnnotation(
node: Object,
parent: Object,
printStack: Array<Object>,
): boolean {
return (
// (() => A) | (() => B)
t.isUnionTypeAnnotation(parent) ||
// (() => A) & (() => B)
t.isIntersectionTypeAnnotation(parent) ||
// (() => A)[]
t.isArrayTypeAnnotation(parent) ||
// <T>(A: T): (T => T[]) => B => [A, B]
(t.isTypeAnnotation(parent) &&
// Check grandparent
t.isArrowFunctionExpression(printStack[printStack.length - 3]))
);
}
export function UpdateExpression(node: Object, parent: Object): boolean {
return hasPostfixPart(node, parent) || isClassExtendsClause(node, parent);
}
export function ObjectExpression(
node: Object,
parent: Object,
printStack: Array<Object>,
): boolean {
return isFirstInStatement(printStack, { considerArrow: true });
}
export function DoExpression(
node: Object,
parent: Object,
printStack: Array<Object>,
): boolean {
return isFirstInStatement(printStack);
}
export function Binary(node: Object, parent: Object): boolean {
if (
node.operator === "**" &&
t.isBinaryExpression(parent, { operator: "**" })
) {
return parent.left === node;
}
if (isClassExtendsClause(node, parent)) {
return true;
}
if (
hasPostfixPart(node, parent) ||
t.isUnaryLike(parent) ||
t.isAwaitExpression(parent)
) {
return true;
}
if (t.isBinary(parent)) {
const parentOp = parent.operator;
const parentPos = PRECEDENCE[parentOp];
const nodeOp = node.operator;
const nodePos = PRECEDENCE[nodeOp];
if (
// Logical expressions with the same precedence don't need parens.
(parentPos === nodePos &&
parent.right === node &&
!t.isLogicalExpression(parent)) ||
parentPos > nodePos
) {
return true;
}
}
}
export function UnionTypeAnnotation(node: Object, parent: Object): boolean {
return (
t.isArrayTypeAnnotation(parent) ||
t.isNullableTypeAnnotation(parent) ||
t.isIntersectionTypeAnnotation(parent) ||
t.isUnionTypeAnnotation(parent)
);
}
export { UnionTypeAnnotation as IntersectionTypeAnnotation };
export function TSAsExpression() {
return true;
}
export function TSTypeAssertion() {
return true;
}
export function TSUnionType(node: Object, parent: Object): boolean {
return (
t.isTSArrayType(parent) ||
t.isTSOptionalType(parent) ||
t.isTSIntersectionType(parent) ||
t.isTSUnionType(parent) ||
t.isTSRestType(parent)
);
}
export { TSUnionType as TSIntersectionType };
export function TSInferType(node: Object, parent: Object): boolean {
return t.isTSArrayType(parent) || t.isTSOptionalType(parent);
}
export function BinaryExpression(node: Object, parent: Object): boolean {
// let i = (1 in []);
// for ((1 in []);;);
return (
node.operator === "in" &&
(t.isVariableDeclarator(parent) || t.isFor(parent))
);
}
export function SequenceExpression(node: Object, parent: Object): boolean {
if (
// Although parentheses wouldn"t hurt around sequence
// expressions in the head of for loops, traditional style
// dictates that e.g. i++, j++ should not be wrapped with
// parentheses.
t.isForStatement(parent) ||
t.isThrowStatement(parent) ||
t.isReturnStatement(parent) ||
(t.isIfStatement(parent) && parent.test === node) ||
(t.isWhileStatement(parent) && parent.test === node) ||
(t.isForInStatement(parent) && parent.right === node) ||
(t.isSwitchStatement(parent) && parent.discriminant === node) ||
(t.isExpressionStatement(parent) && parent.expression === node)
) {
return false;
}
// Otherwise err on the side of overparenthesization, adding
// explicit exceptions above if this proves overzealous.
return true;
}
export function YieldExpression(node: Object, parent: Object): boolean {
return (
t.isBinary(parent) ||
t.isUnaryLike(parent) ||
hasPostfixPart(node, parent) ||
(t.isAwaitExpression(parent) && t.isYieldExpression(node)) ||
(t.isConditionalExpression(parent) && node === parent.test) ||
isClassExtendsClause(node, parent)
);
}
export { YieldExpression as AwaitExpression };
export function ClassExpression(
node: Object,
parent: Object,
printStack: Array<Object>,
): boolean {
return isFirstInStatement(printStack, { considerDefaultExports: true });
}
export function UnaryLike(node: Object, parent: Object): boolean {
return (
hasPostfixPart(node, parent) ||
t.isBinaryExpression(parent, { operator: "**", left: node }) ||
isClassExtendsClause(node, parent)
);
}
export function FunctionExpression(
node: Object,
parent: Object,
printStack: Array<Object>,
): boolean {
return isFirstInStatement(printStack, { considerDefaultExports: true });
}
export function ArrowFunctionExpression(node: Object, parent: Object): boolean {
return t.isExportDeclaration(parent) || ConditionalExpression(node, parent);
}
export function ConditionalExpression(node: Object, parent: Object): boolean {
if (
t.isUnaryLike(parent) ||
t.isBinary(parent) ||
t.isConditionalExpression(parent, { test: node }) ||
t.isAwaitExpression(parent) ||
t.isTSTypeAssertion(parent) ||
t.isTSAsExpression(parent)
) {
return true;
}
return UnaryLike(node, parent);
}
export function OptionalMemberExpression(
node: Object,
parent: Object,
): boolean {
return (
t.isCallExpression(parent, { callee: node }) ||
t.isMemberExpression(parent, { object: node })
);
}
export { OptionalMemberExpression as OptionalCallExpression };
export function AssignmentExpression(
node: Object,
parent: Object,
printStack: Array<Object>,
): boolean {
if (t.isObjectPattern(node.left)) {
return true;
} else {
return ConditionalExpression(node, parent, printStack);
}
}
export function LogicalExpression(node: Object, parent: Object): boolean {
switch (node.operator) {
case "||":
if (!t.isLogicalExpression(parent)) return false;
return parent.operator === "??" || parent.operator === "&&";
case "&&":
return t.isLogicalExpression(parent, { operator: "??" });
case "??":
return t.isLogicalExpression(parent) && parent.operator !== "??";
}
}
// Walk up the print stack to determine if our node can come first
// in statement.
function isFirstInStatement(
printStack: Array<Object>,
{ considerArrow = false, considerDefaultExports = false } = {},
): boolean {
let i = printStack.length - 1;
let node = printStack[i];
i--;
let parent = printStack[i];
while (i > 0) {
if (
t.isExpressionStatement(parent, { expression: node }) ||
(considerDefaultExports &&
t.isExportDefaultDeclaration(parent, { declaration: node })) ||
(considerArrow && t.isArrowFunctionExpression(parent, { body: node }))
) {
return true;
}
if (
(hasPostfixPart(node, parent) && !t.isNewExpression(parent)) ||
(t.isSequenceExpression(parent) && parent.expressions[0] === node) ||
t.isConditional(parent, { test: node }) ||
t.isBinary(parent, { left: node }) ||
t.isAssignmentExpression(parent, { left: node })
) {
node = parent;
i--;
parent = printStack[i];
} else {
return false;
}
}
return false;
}