UNPKG

prettierx

Version:

prettierX - a less opinionated fork of the Prettier code formatter

965 lines (866 loc) 28.6 kB
"use strict"; const getLast = require("../utils/get-last"); const { getFunctionParameters, getLeftSidePathName, hasFlowShorthandAnnotationComment, hasNakedLeftSide, hasNode, isBitwiseOperator, startsWithNoLookaheadToken, shouldFlatten, getPrecedence, isCallExpression, isMemberExpression, isObjectProperty, } = require("./utils"); function needsParens(path, options) { const parent = path.getParentNode(); if (!parent) { return false; } const name = path.getName(); const node = path.getNode(); // to avoid unexpected `}}` in HTML interpolations if ( options.__isInHtmlInterpolation && // [prettierx]: --no-object-curly-spacing option support !options.objectCurlySpacing && endsWithRightBracket(node) && isFollowedByRightBracket(path) ) { return true; } // Only statements don't need parentheses. if (isStatement(node)) { return false; } if ( // Preserve parens if we have a Flow annotation comment, unless we're using the Flow // parser. The Flow parser turns Flow comments into type annotation nodes in its // AST, which we handle separately. options.parser !== "flow" && hasFlowShorthandAnnotationComment(path.getValue()) ) { return true; } // 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 && node.extra.parenthesized && /^PRETTIER_HTML_PLACEHOLDER_\d+_\d+_IN_JS$/.test(node.name) ) { return true; } // `for (async of []);` is invalid if ( name === "left" && node.name === "async" && parent.type === "ForOfStatement" && !parent.await ) { return true; } return false; } switch (parent.type) { 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 ( name === "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 === "ParenthesizedExpression" || node.type === "SequenceExpression" || node.type === "TaggedTemplateExpression" || node.type === "UnaryExpression" || node.type === "UpdateExpression" || node.type === "YieldExpression" || node.type === "TSNonNullExpression") ) { return true; } break; } case "ExportDefaultDeclaration": { return ( // `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 shouldWrapFunctionForExportDefault(path, options) || // `export default (foo, bar)` also needs parentheses node.type === "SequenceExpression" ); } case "Decorator": { if (name === "expression") { let hasCallExpression = false; let hasMemberExpression = false; let current = node; while (current) { switch (current.type) { case "MemberExpression": hasMemberExpression = true; current = current.object; break; case "CallExpression": if ( /** @(x().y) */ hasMemberExpression || /** @(x().y()) */ hasCallExpression ) { return true; } hasCallExpression = true; current = current.callee; break; case "Identifier": return false; default: return true; } } return true; } break; } case "ExpressionStatement": { if ( startsWithNoLookaheadToken( node, /* forbidFunctionClassAndDoExpr */ true ) ) { return true; } break; } case "ArrowFunctionExpression": { if ( name === "body" && node.type !== "SequenceExpression" && // these have parens added anyway startsWithNoLookaheadToken( node, /* forbidFunctionClassAndDoExpr */ false ) ) { 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 name === "object"; case "TaggedTemplateExpression": return true; case "NewExpression": case "CallExpression": case "OptionalCallExpression": return name === "callee"; case "BinaryExpression": return name === "left" && parent.operator === "**"; case "TSNonNullExpression": return true; default: return false; } case "BinaryExpression": { if ( parent.type === "UpdateExpression" || (parent.type === "PipelineTopicExpression" && node.operator === "|>") ) { 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 && node.extra.parenthesized) { const grandParent = path.getParentNode(1); if ( grandParent.type === "BinaryExpression" && grandParent.operator === "|>" ) { return true; } } } // fallthrough case "TSTypeAssertion": case "TSAsExpression": case "LogicalExpression": switch (parent.type) { case "TSAsExpression": // example: foo as unknown as Bar return node.type !== "TSAsExpression"; case "ConditionalExpression": return node.type === "TSAsExpression"; case "CallExpression": case "NewExpression": case "OptionalCallExpression": // [prettierx] --space-in-parens option support NO LONGER NEEDED HERE return name === "callee"; case "ClassExpression": case "ClassDeclaration": return name === "superClass"; case "TSTypeAssertion": case "TaggedTemplateExpression": case "UnaryExpression": case "JSXSpreadAttribute": case "SpreadElement": case "SpreadProperty": case "BindExpression": case "AwaitExpression": case "TSNonNullExpression": case "UpdateExpression": // [prettierx] --space-in-parens option support NO LONGER NEEDED HERE return true; case "MemberExpression": case "OptionalMemberExpression": // [prettierx] --space-in-parens option support NO LONGER NEEDED HERE return name === "object"; case "AssignmentExpression": case "AssignmentPattern": return ( name === "left" && (node.type === "TSTypeAssertion" || node.type === "TSAsExpression") ); 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 (name === "right" && parentPrecedence === precedence) { return true; } if ( parentPrecedence === precedence && !shouldFlatten(parentOperator, operator) ) { return true; } if (parentPrecedence < precedence && operator === "%") { return parentOperator === "+" || parentOperator === "-"; } // 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; } case "SequenceExpression": switch (parent.type) { case "ReturnStatement": return false; case "ForStatement": // 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. return false; case "ExpressionStatement": return name !== "expression"; case "ArrowFunctionExpression": // We do need parentheses, but SequenceExpressions are handled // specially when printing bodies of arrow functions. return name !== "body"; default: // Otherwise err on the side of overparenthesization, adding // explicit exceptions above if this proves overzealous. return true; } case "YieldExpression": if ( parent.type === "UnaryExpression" || parent.type === "AwaitExpression" || parent.type === "TSAsExpression" || parent.type === "TSNonNullExpression" ) { return true; } if ( name === "expression" && node.argument && node.argument.type === "PipelinePrimaryTopicReference" && parent.type === "PipelineTopicExpression" ) { return true; } // else fallthrough case "AwaitExpression": switch (parent.type) { case "TaggedTemplateExpression": case "UnaryExpression": case "LogicalExpression": case "SpreadElement": case "SpreadProperty": case "TSAsExpression": case "TSNonNullExpression": case "BindExpression": return true; case "MemberExpression": case "OptionalMemberExpression": return name === "object"; case "NewExpression": case "CallExpression": case "OptionalCallExpression": return name === "callee"; case "ConditionalExpression": return name === "test"; case "BinaryExpression": { if (!node.argument && parent.operator === "|>") { return false; } return true; } default: return false; } case "TSConditionalType": if (name === "extendsType" && parent.type === "TSConditionalType") { return true; } // fallthrough case "TSFunctionType": case "TSConstructorType": if (name === "checkType" && parent.type === "TSConditionalType") { return true; } // fallthrough case "TSUnionType": case "TSIntersectionType": if ( (parent.type === "TSUnionType" || parent.type === "TSIntersectionType") && parent.types.length > 1 && (!node.types || node.types.length > 1) ) { return true; } // fallthrough case "TSInferType": if (node.type === "TSInferType" && parent.type === "TSRestType") { return false; } // fallthrough case "TSTypeOperator": return ( parent.type === "TSArrayType" || parent.type === "TSOptionalType" || parent.type === "TSRestType" || (name === "objectType" && parent.type === "TSIndexedAccessType") || parent.type === "TSTypeOperator" || (parent.type === "TSTypeAnnotation" && /^TSJSDoc/.test(path.getParentNode(1).type)) ); case "ArrayTypeAnnotation": return parent.type === "NullableTypeAnnotation"; case "IntersectionTypeAnnotation": case "UnionTypeAnnotation": return ( parent.type === "ArrayTypeAnnotation" || parent.type === "NullableTypeAnnotation" || parent.type === "IntersectionTypeAnnotation" || parent.type === "UnionTypeAnnotation" || (name === "objectType" && (parent.type === "IndexedAccessType" || parent.type === "OptionalIndexedAccessType")) ); case "NullableTypeAnnotation": return ( parent.type === "ArrayTypeAnnotation" || (name === "objectType" && (parent.type === "IndexedAccessType" || parent.type === "OptionalIndexedAccessType")) ); case "FunctionTypeAnnotation": { const ancestor = parent.type === "NullableTypeAnnotation" ? path.getParentNode(1) : parent; return ( ancestor.type === "UnionTypeAnnotation" || ancestor.type === "IntersectionTypeAnnotation" || ancestor.type === "ArrayTypeAnnotation" || (name === "objectType" && (ancestor.type === "IndexedAccessType" || ancestor.type === "OptionalIndexedAccessType")) || // 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 && param.typeAnnotation.type === "NullableTypeAnnotation" )) ); } case "OptionalIndexedAccessType": return name === "objectType" && parent.type === "IndexedAccessType"; case "TypeofTypeAnnotation": return ( name === "objectType" && (parent.type === "IndexedAccessType" || parent.type === "OptionalIndexedAccessType") ); case "StringLiteral": case "NumericLiteral": case "Literal": if ( typeof node.value === "string" && parent.type === "ExpressionStatement" && !parent.directive ) { // To avoid becoming a directive const grandParent = path.getParentNode(1); return ( grandParent.type === "Program" || grandParent.type === "BlockStatement" ); } return ( // [prettierx] --space-in-parens option support NO LONGER NEEDED HERE name === "object" && parent.type === "MemberExpression" && typeof node.value === "number" ); case "AssignmentExpression": { const grandParent = path.getParentNode(1); if (name === "body" && parent.type === "ArrowFunctionExpression") { return true; } if ( name === "key" && (parent.type === "ClassProperty" || parent.type === "PropertyDefinition") && parent.computed ) { return false; } if ( (name === "init" || name === "update") && parent.type === "ForStatement" ) { return false; } if (parent.type === "ExpressionStatement") { return node.left.type === "ObjectPattern"; } if (name === "key" && parent.type === "TSPropertySignature") { return false; } if (parent.type === "AssignmentExpression") { return false; } if ( parent.type === "SequenceExpression" && grandParent && grandParent.type === "ForStatement" && (grandParent.init === parent || grandParent.update === parent) ) { return false; } if ( name === "value" && parent.type === "Property" && grandParent && grandParent.type === "ObjectPattern" && grandParent.properties.includes(parent) ) { return false; } if (parent.type === "NGChainedExpression") { return false; } return true; } case "ConditionalExpression": switch (parent.type) { case "TaggedTemplateExpression": case "UnaryExpression": case "SpreadElement": case "SpreadProperty": case "BinaryExpression": case "LogicalExpression": case "NGPipeExpression": case "ExportDefaultDeclaration": case "AwaitExpression": case "JSXSpreadAttribute": case "TSTypeAssertion": case "TypeCastExpression": case "TSAsExpression": case "TSNonNullExpression": return true; case "NewExpression": case "CallExpression": case "OptionalCallExpression": return name === "callee"; case "ConditionalExpression": return name === "test"; case "MemberExpression": case "OptionalMemberExpression": return name === "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 name === "callee"; case "TaggedTemplateExpression": return true; // This is basically a kind of IIFE. default: return false; } case "ArrowFunctionExpression": switch (parent.type) { case "PipelineTopicExpression": return Boolean(node.extra && node.extra.parenthesized); case "BinaryExpression": return ( parent.operator !== "|>" || (node.extra && node.extra.parenthesized) ); case "NewExpression": case "CallExpression": case "OptionalCallExpression": return name === "callee"; case "MemberExpression": case "OptionalMemberExpression": return name === "object"; case "TSAsExpression": case "TSNonNullExpression": case "BindExpression": case "TaggedTemplateExpression": case "UnaryExpression": case "LogicalExpression": case "AwaitExpression": case "TSTypeAssertion": return true; case "ConditionalExpression": return name === "test"; default: return false; } case "ClassExpression": switch (parent.type) { case "NewExpression": return name === "callee"; default: return false; } case "OptionalMemberExpression": case "OptionalCallExpression": { const parentParent = path.getParentNode(1); if ( (name === "object" && parent.type === "MemberExpression") || (name === "callee" && (parent.type === "CallExpression" || parent.type === "NewExpression")) || (parent.type === "TSNonNullExpression" && parentParent.type === "MemberExpression" && parentParent.object === parent) ) { return true; } } // fallthrough case "CallExpression": case "MemberExpression": case "TaggedTemplateExpression": case "TSNonNullExpression": if ( name === "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 ( (name === "callee" && (parent.type === "BindExpression" || parent.type === "NewExpression")) || (name === "object" && isMemberExpression(parent)) ); case "NGPipeExpression": if ( parent.type === "NGRoot" || parent.type === "NGMicrosyntaxExpression" || (parent.type === "ObjectProperty" && // Preserve parens for compatibility with AngularJS expressions !(node.extra && node.extra.parenthesized)) || parent.type === "ArrayExpression" || (isCallExpression(parent) && parent.arguments[name] === node) || (name === "right" && parent.type === "NGPipeExpression") || (name === "property" && parent.type === "MemberExpression") || // [prettierx] --space-in-parens option support NO LONGER NEEDED HERE parent.type === "AssignmentExpression" ) { return false; } return true; case "JSXFragment": case "JSXElement": return ( name === "callee" || (name === "left" && parent.type === "BinaryExpression" && parent.operator === "<") || (parent.type !== "ArrayExpression" && 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) && parent.type !== "ReturnStatement" && parent.type !== "ThrowStatement" && parent.type !== "TypeCastExpression" && parent.type !== "VariableDeclarator" && parent.type !== "YieldExpression") ); case "TypeAnnotation": return ( name === "returnType" && parent.type === "ArrowFunctionExpression" && includesFunctionTypeInObjectType(node) ); } return false; } function isStatement(node) { return ( node.type === "BlockStatement" || node.type === "BreakStatement" || node.type === "ClassBody" || node.type === "ClassDeclaration" || node.type === "ClassMethod" || node.type === "ClassProperty" || node.type === "PropertyDefinition" || node.type === "ClassPrivateProperty" || node.type === "ContinueStatement" || node.type === "DebuggerStatement" || node.type === "DeclareClass" || node.type === "DeclareExportAllDeclaration" || node.type === "DeclareExportDeclaration" || node.type === "DeclareFunction" || node.type === "DeclareInterface" || node.type === "DeclareModule" || node.type === "DeclareModuleExports" || node.type === "DeclareVariable" || node.type === "DoWhileStatement" || node.type === "EnumDeclaration" || node.type === "ExportAllDeclaration" || node.type === "ExportDefaultDeclaration" || node.type === "ExportNamedDeclaration" || node.type === "ExpressionStatement" || node.type === "ForInStatement" || node.type === "ForOfStatement" || node.type === "ForStatement" || node.type === "FunctionDeclaration" || node.type === "IfStatement" || node.type === "ImportDeclaration" || node.type === "InterfaceDeclaration" || node.type === "LabeledStatement" || node.type === "MethodDefinition" || node.type === "ReturnStatement" || node.type === "SwitchStatement" || node.type === "ThrowStatement" || node.type === "TryStatement" || node.type === "TSDeclareFunction" || node.type === "TSEnumDeclaration" || node.type === "TSImportEqualsDeclaration" || node.type === "TSInterfaceDeclaration" || node.type === "TSModuleDeclaration" || node.type === "TSNamespaceExportDeclaration" || node.type === "TypeAlias" || node.type === "VariableDeclaration" || node.type === "WhileStatement" || node.type === "WithStatement" ); } function isPathInForStatementInitializer(path) { let i = 0; let node = path.getValue(); while (node) { const parent = path.getParentNode(i++); if (parent && parent.type === "ForStatement" && parent.init === node) { return true; } node = parent; } return false; } function includesFunctionTypeInObjectType(node) { return hasNode( node, (n1) => (n1.type === "ObjectTypeAnnotation" && hasNode( n1, (n2) => n2.type === "FunctionTypeAnnotation" || undefined )) || undefined ); } function endsWithRightBracket(node) { switch (node.type) { case "ObjectExpression": return true; default: return false; } } function isFollowedByRightBracket(path) { const node = path.getValue(); const parent = path.getParentNode(); const name = path.getName(); switch (parent.type) { case "NGPipeExpression": if ( typeof name === "number" && parent.arguments[name] === node && parent.arguments.length - 1 === name ) { return path.callParent(isFollowedByRightBracket); } break; case "ObjectProperty": if (name === "value") { const parentParent = path.getParentNode(1); return getLast(parentParent.properties) === parent; } break; case "BinaryExpression": case "LogicalExpression": if (name === "right") { return path.callParent(isFollowedByRightBracket); } break; case "ConditionalExpression": if (name === "alternate") { return path.callParent(isFollowedByRightBracket); } break; case "UnaryExpression": if (parent.prefix) { return path.callParent(isFollowedByRightBracket); } break; } return false; } function shouldWrapFunctionForExportDefault(path, options) { const node = path.getValue(); const parent = path.getParentNode(); 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 !needsParens(path, options) ); } if ( !hasNakedLeftSide(node) || (parent.type !== "ExportDefaultDeclaration" && needsParens(path, options)) ) { return false; } return path.call( (childPath) => shouldWrapFunctionForExportDefault(childPath, options), ...getLeftSidePathName(path, node) ); } module.exports = needsParens;