Files
test-repo/src/language-js/parentheses/needs-parentheses.js
T

1298 lines
36 KiB
JavaScript

import isNonEmptyArray from "../../utilities/is-non-empty-array.js";
import {
createTypeCheckFunction,
getFunctionParameters,
getLeftSidePathName,
getPrecedence,
hasComment,
hasNakedLeftSide,
hasNode,
isArrayExpression,
isBinaryCastExpression,
isBitwiseOperator,
isCallExpression,
isConditionalType,
isIntersectionType,
isMemberExpression,
isNullishCoalescing,
isNumericLiteral,
isObjectExpression,
isObjectProperty,
isReturnOrThrowStatement,
isUnionType,
shouldFlatten,
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"
*/
/**
* @param {AstPath} path
* @returns {boolean}
*/
function needsParentheses(path, options) {
if (path.isRoot) {
return false;
}
const { node, key, parent } = path;
// to avoid unexpected `}}` in HTML interpolations
if (
options.__isInHtmlInterpolation &&
!options.bracketSpacing &&
endsWithRightBracket(node) &&
isFollowedByRightBracket(path)
) {
return true;
}
// Only statements don't need parentheses.
if (isStatement(node)) {
return false;
}
if (node.type === "Identifier") {
return shouldAddParenthesesToIdentifier(path);
}
if (
node.type === "ObjectExpression" ||
node.type === "FunctionExpression" ||
node.type === "ClassExpression" ||
node.type === "DoExpression"
) {
const expression = path.findAncestor(
(node) => node.type === "ExpressionStatement",
)?.expression;
if (
expression &&
startsWithNoLookaheadToken(
expression,
(leftmostNode) => leftmostNode === node,
)
) {
return true;
}
}
if (node.type === "ObjectExpression") {
const arrowFunctionBody = path.findAncestor(
(node) => node.type === "ArrowFunctionExpression",
)?.body;
if (
arrowFunctionBody &&
arrowFunctionBody.type !== "SequenceExpression" && // these have parens added anyway
arrowFunctionBody.type !== "AssignmentExpression" &&
startsWithNoLookaheadToken(
arrowFunctionBody,
(leftmostNode) => leftmostNode === node,
)
) {
return true;
}
}
switch (parent.type) {
case "ReturnStatement":
case "ThrowStatement":
if (willReturnOrThrowStatementBreak(path, options)) {
return false;
}
break;
case "ParenthesizedExpression":
return false;
case "ClassDeclaration":
case "ClassExpression":
// Add parens around the extends clause of a class. It is needed for almost
// all expressions.
if (
key === "superClass" &&
(node.type === "ArrowFunctionExpression" ||
node.type === "AssignmentExpression" ||
node.type === "AwaitExpression" ||
node.type === "BinaryExpression" ||
node.type === "ConditionalExpression" ||
node.type === "LogicalExpression" ||
node.type === "NewExpression" ||
node.type === "ObjectExpression" ||
node.type === "SequenceExpression" ||
node.type === "TaggedTemplateExpression" ||
node.type === "UnaryExpression" ||
node.type === "UpdateExpression" ||
node.type === "YieldExpression" ||
node.type === "TSNonNullExpression" ||
(node.type === "ClassExpression" && isNonEmptyArray(node.decorators)))
) {
return true;
}
break;
case "ExportDefaultDeclaration":
// `export default function` or `export default class` can't be followed by
// anything after. So an expression like `export default (function(){}).toString()`
// needs to be followed by a parentheses
if (shouldWrapFunctionForExportDefault(path, options)) {
return true;
}
break;
case "Decorator":
if (
key === "expression" &&
!canDecoratorExpressionUnparenthesized(node)
) {
return true;
}
break;
case "TypeAnnotation":
if (
path.match(
undefined,
undefined,
(node, key) =>
key === "returnType" && node.type === "ArrowFunctionExpression",
) &&
includesFunctionTypeInObjectType(node)
) {
return true;
}
break;
// A user typing `!foo instanceof Bar` probably intended
// `!(foo instanceof Bar)`, so format to `(!foo) instance Bar` to what is
// really happening
case "BinaryExpression":
if (
key === "left" &&
(parent.operator === "in" || parent.operator === "instanceof") &&
node.type === "UnaryExpression"
) {
return true;
}
break;
case "VariableDeclarator":
// Legacy syntax
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Invalid_for-in_initializer
// `for (var a = 1 in b);`
if (
key === "init" &&
path.match(
undefined,
undefined,
(node, key) =>
key === "declarations" && node.type === "VariableDeclaration",
(node, key) => key === "left" && node.type === "ForInStatement",
)
) {
return true;
}
break;
}
switch (node.type) {
case "UpdateExpression":
if (parent.type === "UnaryExpression") {
return (
node.prefix &&
((node.operator === "++" && parent.operator === "+") ||
(node.operator === "--" && parent.operator === "-"))
);
}
// else fallthrough
case "UnaryExpression":
switch (parent.type) {
case "UnaryExpression":
return (
node.operator === parent.operator &&
(node.operator === "+" || node.operator === "-")
);
case "BindExpression":
return true;
case "MemberExpression":
case "OptionalMemberExpression":
return key === "object";
case "TaggedTemplateExpression":
return true;
case "NewExpression":
case "CallExpression":
case "OptionalCallExpression":
return key === "callee";
case "BinaryExpression":
return key === "left" && parent.operator === "**";
case "TSNonNullExpression":
return true;
default:
return false;
}
case "BinaryExpression":
if (parent.type === "UpdateExpression") {
return true;
}
// We add parentheses to any `a in b` inside `ForStatement` initializer
// https://github.com/prettier/prettier/issues/907#issuecomment-284304321
if (node.operator === "in" && isPathInForStatementInitializer(path)) {
return true;
}
if (node.operator === "|>" && node.extra?.parenthesized) {
const grandParent = path.grandparent;
if (
grandParent.type === "BinaryExpression" &&
grandParent.operator === "|>"
) {
return true;
}
}
// fallthrough
case "TSTypeAssertion":
case "TSAsExpression":
case "TSSatisfiesExpression":
case "AsExpression":
case "AsConstExpression":
case "SatisfiesExpression":
case "LogicalExpression":
switch (parent.type) {
case "TSAsExpression":
case "TSSatisfiesExpression":
case "AsExpression":
case "AsConstExpression":
case "SatisfiesExpression":
// examples:
// foo as unknown as Bar
// foo satisfies unknown satisfies Bar
// foo satisfies unknown as Bar
// foo as unknown satisfies Bar
return !isBinaryCastExpression(node);
case "ConditionalExpression":
return isBinaryCastExpression(node) || isNullishCoalescing(node);
case "CallExpression":
case "NewExpression":
case "OptionalCallExpression":
return key === "callee";
case "ClassExpression":
case "ClassDeclaration":
return key === "superClass";
case "TSTypeAssertion":
case "TaggedTemplateExpression":
case "JSXSpreadAttribute":
case "SpreadElement":
case "BindExpression":
case "AwaitExpression":
case "TSNonNullExpression":
case "UpdateExpression":
return true;
case "UnaryExpression":
// `UnaryExpression` adds parentheses and indention when argument has comment
if (!hasComment(node)) {
return true;
}
break;
case "MemberExpression":
case "OptionalMemberExpression":
return key === "object";
case "AssignmentExpression":
case "AssignmentPattern":
return (
key === "left" &&
(node.type === "TSTypeAssertion" || isBinaryCastExpression(node))
);
case "LogicalExpression":
if (node.type === "LogicalExpression") {
return parent.operator !== node.operator;
}
// else fallthrough
case "BinaryExpression": {
const { operator, type } = node;
if (!operator && type !== "TSTypeAssertion") {
return true;
}
const precedence = getPrecedence(operator);
const parentOperator = parent.operator;
const parentPrecedence = getPrecedence(parentOperator);
if (parentPrecedence > precedence) {
return true;
}
if (key === "right" && parentPrecedence === precedence) {
return true;
}
if (
parentPrecedence === precedence &&
!shouldFlatten(parentOperator, operator)
) {
return true;
}
if (
parentPrecedence < precedence &&
operator === "%" &&
(parentOperator === "+" || parentOperator === "-")
) {
return true;
}
// Add parenthesis when working with bitwise operators
// It's not strictly needed but helps with code understanding
if (isBitwiseOperator(parentOperator)) {
return true;
}
return false;
}
default:
return false;
}
break;
case "SequenceExpression":
// 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.
if (parent.type === "ForStatement") {
return false;
}
// Otherwise err on the side of overparenthesization, adding
// explicit exceptions above if this proves overzealous.
return true;
case "YieldExpression":
if (
parent.type === "AwaitExpression" ||
parent.type === "TSTypeAssertion"
) {
return true;
}
// else fallthrough
case "AwaitExpression":
switch (parent.type) {
case "TaggedTemplateExpression":
case "UnaryExpression":
case "LogicalExpression":
case "SpreadElement":
case "TSAsExpression":
case "TSSatisfiesExpression":
case "TSNonNullExpression":
case "AsExpression":
case "AsConstExpression":
case "SatisfiesExpression":
case "BindExpression":
return true;
case "MemberExpression":
case "OptionalMemberExpression":
return key === "object";
case "NewExpression":
case "CallExpression":
case "OptionalCallExpression":
return key === "callee";
case "ConditionalExpression":
return key === "test";
case "BinaryExpression":
if (!node.argument && parent.operator === "|>") {
return false;
}
return true;
default:
return false;
}
case "TSFunctionType":
if (
path.match(
(node) => node.type === "TSFunctionType",
(node, key) =>
key === "typeAnnotation" && node.type === "TSTypeAnnotation",
(node, key) =>
key === "returnType" && node.type === "ArrowFunctionExpression",
)
) {
return true;
}
// fallthrough
case "TSConditionalType":
case "TSConstructorType":
case "ConditionalTypeAnnotation":
if (
key === "extendsType" &&
isConditionalType(node) &&
parent.type === node.type
) {
return true;
}
if (key === "checkType" && isConditionalType(parent)) {
return true;
}
if (key === "extendsType" && parent.type === "TSConditionalType") {
let { typeAnnotation } = node.returnType || node.typeAnnotation;
if (
typeAnnotation.type === "TSTypePredicate" &&
typeAnnotation.typeAnnotation
) {
typeAnnotation = typeAnnotation.typeAnnotation.typeAnnotation;
}
if (
typeAnnotation.type === "TSInferType" &&
typeAnnotation.typeParameter.constraint
) {
return true;
}
}
// fallthrough
case "TSUnionType":
case "TSIntersectionType":
if (isUnionType(parent) || isIntersectionType(parent)) {
return true;
}
// fallthrough
case "TSInferType":
if (node.type === "TSInferType") {
if (parent.type === "TSRestType") {
return false;
}
if (
key === "types" &&
(parent.type === "TSUnionType" ||
parent.type === "TSIntersectionType") &&
node.typeParameter.type === "TSTypeParameter" &&
node.typeParameter.constraint
) {
return true;
}
}
// fallthrough
case "TSTypeOperator":
return (
parent.type === "TSArrayType" ||
parent.type === "TSOptionalType" ||
parent.type === "TSRestType" ||
(key === "objectType" && parent.type === "TSIndexedAccessType") ||
parent.type === "TSTypeOperator" ||
(parent.type === "TSTypeAnnotation" &&
path.grandparent.type.startsWith("TSJSDoc"))
);
case "TSTypeQuery":
return (
(key === "objectType" && parent.type === "TSIndexedAccessType") ||
(key === "elementType" && parent.type === "TSArrayType")
);
// Same as `TSTypeOperator`, but for Flow syntax
case "TypeOperator":
return (
parent.type === "ArrayTypeAnnotation" ||
parent.type === "NullableTypeAnnotation" ||
(key === "objectType" &&
(parent.type === "IndexedAccessType" ||
parent.type === "OptionalIndexedAccessType")) ||
parent.type === "TypeOperator"
);
// Same as `TSTypeQuery`, but for Flow syntax
case "TypeofTypeAnnotation":
return (
(key === "objectType" &&
(parent.type === "IndexedAccessType" ||
parent.type === "OptionalIndexedAccessType")) ||
(key === "elementType" && parent.type === "ArrayTypeAnnotation")
);
case "ArrayTypeAnnotation":
return parent.type === "NullableTypeAnnotation";
case "IntersectionTypeAnnotation":
case "UnionTypeAnnotation":
return (
parent.type === "TypeOperator" ||
parent.type === "KeyofTypeAnnotation" ||
parent.type === "ArrayTypeAnnotation" ||
parent.type === "NullableTypeAnnotation" ||
parent.type === "IntersectionTypeAnnotation" ||
parent.type === "UnionTypeAnnotation" ||
(key === "objectType" &&
(parent.type === "IndexedAccessType" ||
parent.type === "OptionalIndexedAccessType"))
);
case "InferTypeAnnotation":
case "NullableTypeAnnotation":
return (
parent.type === "ArrayTypeAnnotation" ||
(key === "objectType" &&
(parent.type === "IndexedAccessType" ||
parent.type === "OptionalIndexedAccessType"))
);
case "ComponentTypeAnnotation":
case "FunctionTypeAnnotation": {
if (
node.type === "ComponentTypeAnnotation" &&
(node.rendersType === null || node.rendersType === undefined)
) {
return false;
}
if (
path.match(
undefined,
(node, key) =>
key === "typeAnnotation" && node.type === "TypeAnnotation",
(node, key) =>
key === "returnType" && node.type === "ArrowFunctionExpression",
)
) {
return true;
}
/*
Matches the following case in Flow:
```
const a = (x: any): x is (number => string) => true;
```
This case is not necessary in TS since `number => string` is not a valid
arrow type there.
*/
if (
path.match(
undefined,
(node, key) =>
key === "typeAnnotation" && node.type === "TypePredicate",
(node, key) =>
key === "typeAnnotation" && node.type === "TypeAnnotation",
(node, key) =>
key === "returnType" && node.type === "ArrowFunctionExpression",
)
) {
return true;
}
const ancestor =
parent.type === "NullableTypeAnnotation" ? path.grandparent : parent;
return (
ancestor.type === "UnionTypeAnnotation" ||
ancestor.type === "IntersectionTypeAnnotation" ||
ancestor.type === "ArrayTypeAnnotation" ||
(key === "objectType" &&
(ancestor.type === "IndexedAccessType" ||
ancestor.type === "OptionalIndexedAccessType")) ||
(key === "checkType" && parent.type === "ConditionalTypeAnnotation") ||
(key === "extendsType" &&
parent.type === "ConditionalTypeAnnotation" &&
node.returnType?.type === "InferTypeAnnotation" &&
node.returnType?.typeParameter.bound) ||
// We should check ancestor's parent to know whether the parentheses
// are really needed, but since ??T doesn't make sense this check
// will almost never be true.
ancestor.type === "NullableTypeAnnotation" ||
// See #5283
(parent.type === "FunctionTypeParam" &&
parent.name === null &&
getFunctionParameters(node).some(
(param) => param.typeAnnotation?.type === "NullableTypeAnnotation",
))
);
}
// fallthrough
case "OptionalIndexedAccessType":
return key === "objectType" && parent.type === "IndexedAccessType";
case "StringLiteral":
case "NumericLiteral":
case "Literal":
if (
typeof node.value === "string" &&
parent.type === "ExpressionStatement" &&
typeof parent.directive !== "string"
) {
// To avoid becoming a directive
const grandParent = path.grandparent;
return (
grandParent.type === "Program" ||
grandParent.type === "BlockStatement"
);
}
return (
key === "object" && isMemberExpression(parent) && isNumericLiteral(node)
);
case "AssignmentExpression":
if (
(key === "init" || key === "update") &&
parent.type === "ForStatement"
) {
return false;
}
if (
key === "expression" &&
node.left.type !== "ObjectPattern" &&
parent.type === "ExpressionStatement"
) {
return false;
}
if (key === "key" && parent.type === "TSPropertySignature") {
return false;
}
if (parent.type === "AssignmentExpression") {
return false;
}
if (
key === "expressions" &&
parent.type === "SequenceExpression" &&
path.match(
undefined,
undefined,
(node, name) =>
(name === "init" || name === "update") &&
node.type === "ForStatement",
)
) {
return false;
}
if (
key === "value" &&
parent.type === "Property" &&
path.match(
undefined,
undefined,
(node, name) =>
name === "properties" && node.type === "ObjectPattern",
)
) {
return false;
}
if (parent.type === "NGChainedExpression") {
return false;
}
if (key === "node" && parent.type === "JsExpressionRoot") {
return false;
}
return true;
case "ConditionalExpression":
switch (parent.type) {
case "TaggedTemplateExpression":
case "UnaryExpression":
case "SpreadElement":
case "BinaryExpression":
case "LogicalExpression":
case "NGPipeExpression":
case "ExportDefaultDeclaration":
case "AwaitExpression":
case "JSXSpreadAttribute":
case "TSTypeAssertion":
case "TypeCastExpression":
case "TSAsExpression":
case "TSSatisfiesExpression":
case "AsExpression":
case "AsConstExpression":
case "SatisfiesExpression":
case "TSNonNullExpression":
return true;
case "NewExpression":
case "CallExpression":
case "OptionalCallExpression":
return key === "callee";
case "ConditionalExpression":
// TODO remove this case entirely once we've removed this flag.
if (!options.experimentalTernaries) {
return key === "test";
}
return false;
case "MemberExpression":
case "OptionalMemberExpression":
return key === "object";
default:
return false;
}
case "FunctionExpression":
switch (parent.type) {
case "NewExpression":
case "CallExpression":
case "OptionalCallExpression":
// Not always necessary, but it's clearer to the reader if IIFEs are wrapped in parentheses.
// Is necessary if it is `expression` of `ExpressionStatement`.
return key === "callee";
case "TaggedTemplateExpression":
return true; // This is basically a kind of IIFE.
case "ExportDefaultDeclaration":
return key === "declaration";
default:
return false;
}
case "ArrowFunctionExpression":
switch (parent.type) {
case "BinaryExpression":
return parent.operator !== "|>" || node.extra?.parenthesized;
case "NewExpression":
case "CallExpression":
case "OptionalCallExpression":
return key === "callee";
case "MemberExpression":
case "OptionalMemberExpression":
return key === "object";
case "TSAsExpression":
case "TSSatisfiesExpression":
case "AsExpression":
case "AsConstExpression":
case "SatisfiesExpression":
case "TSNonNullExpression":
case "BindExpression":
case "TaggedTemplateExpression":
case "UnaryExpression":
case "LogicalExpression":
case "AwaitExpression":
case "TSTypeAssertion":
case "MatchExpressionCase":
return true;
case "TSInstantiationExpression":
return key === "expression";
case "ConditionalExpression":
return key === "test";
default:
return false;
}
case "ClassExpression":
switch (parent.type) {
case "NewExpression":
return key === "callee";
case "ExportDefaultDeclaration":
return key === "declaration";
default:
return false;
}
case "OptionalMemberExpression":
case "OptionalCallExpression":
case "CallExpression":
case "MemberExpression":
if (shouldAddParenthesesToChainElement(path)) {
return true;
}
// fallthrough
case "TaggedTemplateExpression":
case "TSNonNullExpression":
if (
key === "callee" &&
(parent.type === "BindExpression" || parent.type === "NewExpression")
) {
let object = node;
while (object) {
switch (object.type) {
case "CallExpression":
case "OptionalCallExpression":
return true;
case "MemberExpression":
case "OptionalMemberExpression":
case "BindExpression":
object = object.object;
break;
// tagged templates are basically member expressions from a grammar perspective
// see https://tc39.github.io/ecma262/#prod-MemberExpression
case "TaggedTemplateExpression":
object = object.tag;
break;
case "TSNonNullExpression":
object = object.expression;
break;
default:
return false;
}
}
}
return false;
case "BindExpression":
return (
(key === "callee" &&
(parent.type === "BindExpression" ||
parent.type === "NewExpression")) ||
(key === "object" && isMemberExpression(parent))
);
case "NGPipeExpression":
if (
parent.type === "NGRoot" ||
parent.type === "NGMicrosyntaxExpression" ||
(parent.type === "ObjectProperty" &&
// Preserve parens for compatibility with AngularJS expressions
!node.extra?.parenthesized) ||
isArrayExpression(parent) ||
(key === "arguments" && isCallExpression(parent)) ||
(key === "right" && parent.type === "NGPipeExpression") ||
(key === "property" && parent.type === "MemberExpression") ||
parent.type === "AssignmentExpression"
) {
return false;
}
return true;
case "JSXFragment":
case "JSXElement":
return (
key === "callee" ||
(key === "left" &&
parent.type === "BinaryExpression" &&
parent.operator === "<") ||
(!isArrayExpression(parent) &&
parent.type !== "ArrowFunctionExpression" &&
parent.type !== "AssignmentExpression" &&
parent.type !== "AssignmentPattern" &&
parent.type !== "BinaryExpression" &&
parent.type !== "NewExpression" &&
parent.type !== "ConditionalExpression" &&
parent.type !== "ExpressionStatement" &&
parent.type !== "JsExpressionRoot" &&
parent.type !== "JSXAttribute" &&
parent.type !== "JSXElement" &&
parent.type !== "JSXExpressionContainer" &&
parent.type !== "JSXFragment" &&
parent.type !== "LogicalExpression" &&
!isCallExpression(parent) &&
!isObjectProperty(parent) &&
!isReturnOrThrowStatement(parent) &&
parent.type !== "TypeCastExpression" &&
parent.type !== "VariableDeclarator" &&
parent.type !== "YieldExpression" &&
parent.type !== "MatchExpressionCase")
);
case "TSInstantiationExpression":
return key === "object" && isMemberExpression(parent);
case "MatchOrPattern":
return parent.type === "MatchAsPattern";
}
return false;
}
const isStatement = createTypeCheckFunction([
"BlockStatement",
"BreakStatement",
"ComponentDeclaration",
"ClassBody",
"ClassDeclaration",
"ClassMethod",
"ClassProperty",
"PropertyDefinition",
"ClassPrivateProperty",
"ContinueStatement",
"DebuggerStatement",
"DeclareComponent",
"DeclareClass",
"DeclareExportAllDeclaration",
"DeclareExportDeclaration",
"DeclareFunction",
"DeclareHook",
"DeclareInterface",
"DeclareModule",
"DeclareModuleExports",
"DeclareNamespace",
"DeclareVariable",
"DeclareEnum",
"DoWhileStatement",
"EnumDeclaration",
"ExportAllDeclaration",
"ExportDefaultDeclaration",
"ExportNamedDeclaration",
"ExpressionStatement",
"ForInStatement",
"ForOfStatement",
"ForStatement",
"FunctionDeclaration",
"HookDeclaration",
"IfStatement",
"ImportDeclaration",
"InterfaceDeclaration",
"LabeledStatement",
"MethodDefinition",
"ReturnStatement",
"SwitchStatement",
"ThrowStatement",
"TryStatement",
"TSDeclareFunction",
"TSEnumDeclaration",
"TSImportEqualsDeclaration",
"TSInterfaceDeclaration",
"TSModuleDeclaration",
"TSNamespaceExportDeclaration",
"TypeAlias",
"VariableDeclaration",
"WhileStatement",
"WithStatement",
]);
/**
* @param {AstPath} path
* @returns {boolean}
*/
function isPathInForStatementInitializer(path) {
let i = 0;
let { node } = path;
while (node) {
const parent = path.getParentNode(i++);
if (parent?.type === "ForStatement" && parent.init === node) {
return true;
}
node = parent;
}
return false;
}
function includesFunctionTypeInObjectType(node) {
return hasNode(
node,
(node) =>
node.type === "ObjectTypeAnnotation" &&
hasNode(node, (node) => node.type === "FunctionTypeAnnotation"),
);
}
function endsWithRightBracket(node) {
return isObjectExpression(node);
}
/**
* @param {AstPath} path
* @returns {boolean}
*/
function isFollowedByRightBracket(path) {
const { parent, key } = path;
switch (parent.type) {
case "NGPipeExpression":
if (key === "arguments" && path.isLast) {
return path.callParent(isFollowedByRightBracket);
}
break;
case "ObjectProperty":
if (key === "value") {
return path.callParent(() => path.key === "properties" && path.isLast);
}
break;
case "BinaryExpression":
case "LogicalExpression":
if (key === "right") {
return path.callParent(isFollowedByRightBracket);
}
break;
case "ConditionalExpression":
if (key === "alternate") {
return path.callParent(isFollowedByRightBracket);
}
break;
case "UnaryExpression":
if (parent.prefix) {
return path.callParent(isFollowedByRightBracket);
}
break;
}
return false;
}
/**
* @param {AstPath} path
* @returns {boolean}
*/
function shouldWrapFunctionForExportDefault(path, options) {
const { node, parent } = path;
if (node.type === "FunctionExpression" || node.type === "ClassExpression") {
return (
parent.type === "ExportDefaultDeclaration" ||
// in some cases the function is already wrapped
// (e.g. `export default (function() {})();`)
// in this case we don't need to add extra parens
!needsParentheses(path, options)
);
}
if (
!hasNakedLeftSide(node) ||
(parent.type !== "ExportDefaultDeclaration" &&
needsParentheses(path, options))
) {
return false;
}
return path.call(
() => shouldWrapFunctionForExportDefault(path, options),
...getLeftSidePathName(node),
);
}
/*
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?.())();
(a?.b)``;
(a?.())``;
```
*/
/**
* @param {AstPath} path
* @returns {boolean}
*/
function shouldAddParenthesesToChainElement(path) {
if (
// ESTree
path.match(
undefined,
(node, name) => name === "expression" && node.type === "ChainExpression",
(node, name) =>
name === "tag" && node.type === "TaggedTemplateExpression",
) ||
// Babel
path.match(
(node) =>
node.type === "OptionalCallExpression" ||
node.type === "OptionalMemberExpression",
(node, name) =>
name === "tag" && node.type === "TaggedTemplateExpression",
) ||
// Babel-ts
// (a?.b)!``;
// (a?.b!)``;
path.match(
(node) =>
node.type === "OptionalCallExpression" ||
node.type === "OptionalMemberExpression",
(node, name) =>
name === "expression" && node.type === "TSNonNullExpression",
(node, name) =>
name === "tag" && node.type === "TaggedTemplateExpression",
) ||
// case (a?.b)!``; in Typescript
path.match(
undefined,
(node, name) => name === "expression" && node.type === "ChainExpression",
(node, name) =>
name === "expression" && node.type === "TSNonNullExpression",
(node, name) =>
name === "tag" && node.type === "TaggedTemplateExpression",
) ||
// case (a?.b!)``; in Typescript
path.match(
undefined,
(node, name) =>
name === "expression" && node.type === "TSNonNullExpression",
(node, name) => name === "expression" && node.type === "ChainExpression",
(node, name) =>
name === "tag" && node.type === "TaggedTemplateExpression",
)
) {
return true;
}
if (
path.match(
(node) =>
node.type === "OptionalMemberExpression" ||
node.type === "OptionalCallExpression",
(node, name) =>
(name === "object" && node.type === "MemberExpression") ||
(name === "callee" &&
(node.type === "CallExpression" || node.type === "NewExpression")),
) ||
path.match(
(node) =>
node.type === "OptionalMemberExpression" ||
node.type === "OptionalCallExpression",
(node, name) =>
name === "expression" && node.type === "TSNonNullExpression",
(node, name) =>
(name === "object" && node.type === "MemberExpression") ||
(name === "callee" && node.type === "CallExpression"),
)
) {
return true;
}
// ESTree, same logic as babel
if (
path.match(
(node) =>
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") ||
(name === "callee" && node.type === "CallExpression"),
))
) {
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) =>
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") ||
(name === "callee" && node.type === "CallExpression"),
)
) {
return true;
}
// This function only handle cases above
return false;
}
function isDecoratorMemberExpression(node) {
if (node.type === "Identifier") {
return true;
}
if (isMemberExpression(node)) {
return (
!node.computed &&
// @ts-expect-error -- doesn't exists on `MemberExpression`
!node.optional &&
node.property.type === "Identifier" &&
isDecoratorMemberExpression(node.object)
);
}
return false;
}
// Based on babel implementation
// https://github.com/nicolo-ribaudo/babel/blob/c4b88a4e5005364255f7e964fe324cf7bfdfb019/packages/babel-generator/src/node/index.ts#L111
function canDecoratorExpressionUnparenthesized(node) {
if (node.type === "ChainExpression") {
node = node.expression;
}
return (
isDecoratorMemberExpression(node) ||
(isCallExpression(node) &&
// @ts-expect-error -- doesn't exists on `CallExpression`
!node.optional &&
isDecoratorMemberExpression(node.callee))
);
}
function willReturnOrThrowStatementBreak(path, options) {
const { key, parent } = path;
if (!(key === "argument" && isReturnOrThrowStatement(parent))) {
return false;
}
/*
When `ReturnStatement` or `ThrowStatement` breaks, parentheses will be added around it's argument.
So don't need add parentheses again.
But we can't know how the argument printed, so only matches cases that will break for sure
*/
const { node } = path;
if (
(node.type === "SequenceExpression" ||
node.type === "AssignmentExpression") &&
returnArgumentHasLeadingComment(node, options)
) {
return true;
}
return false;
}
export default needsParentheses;