import { builders, utils } from "prettier/doc"; import { call, definedKeys, each, findBaseIndent, flatMap, hasLeadingComments, indentInParentheses, isBinaryExpression, isNonTerminal, isTerminal, map, onlyDefinedKey, printDanglingComments, printList, printName, printSingle } from "./helpers.js"; const { breakParent, conditionalGroup, group, hardline, ifBreak, indent, indentIfBreak, join, line, lineSuffixBoundary, softline } = builders; const { removeLines, willBreak } = utils; export default { expression: printSingle, lambdaExpression(path, print, _, args = {}) { var _a; const hug = (_a = args.hug) !== null && _a !== void 0 ? _a : false; const parameters = call(path, print, "lambdaParameters"); const expression = [hug ? removeLines(parameters) : parameters, " ->"]; const lambdaExpression = path.node.children.lambdaBody[0].children.expression; const body = call(path, print, "lambdaBody"); if (lambdaExpression) { const suffix = indent([line, body]); expression.push(group(hug ? [suffix, softline] : suffix)); } else { expression.push(" ", body); } return expression; }, lambdaParameters(path, print, options) { const parameters = printSingle(path, print); return !path.node.children.lambdaParametersWithBraces && options.arrowParens === "always" ? ["(", parameters, ")"] : parameters; }, lambdaParametersWithBraces(path, print, options) { var _a; const { lambdaParameterList } = path.node.children; if (!lambdaParameterList) { return "()"; } const { conciseLambdaParameterList, normalLambdaParameterList } = lambdaParameterList[0].children; const parameterCount = ((_a = conciseLambdaParameterList === null || conciseLambdaParameterList === void 0 ? void 0 : conciseLambdaParameterList[0].children.conciseLambdaParameter) !== null && _a !== void 0 ? _a : normalLambdaParameterList === null || normalLambdaParameterList === void 0 ? void 0 : normalLambdaParameterList[0].children.normalLambdaParameter).length; const parameters = call(path, print, "lambdaParameterList"); if (parameterCount > 1) { return indentInParentheses(parameters); } return conciseLambdaParameterList && options.arrowParens === "avoid" ? parameters : ["(", parameters, ")"]; }, lambdaParameterList: printSingle, conciseLambdaParameterList(path, print) { return printList(path, print, "conciseLambdaParameter"); }, normalLambdaParameterList(path, print) { return printList(path, print, "normalLambdaParameter"); }, normalLambdaParameter: printSingle, regularLambdaParameter(path, print) { return join(" ", [ ...map(path, print, "variableModifier"), call(path, print, "lambdaParameterType"), call(path, print, "variableDeclaratorId") ]); }, lambdaParameterType: printSingle, conciseLambdaParameter: printSingle, lambdaBody: printSingle, conditionalExpression(path, print) { var _a; const binaryExpression = call(path, print, "binaryExpression"); if (!path.node.children.QuestionMark) { return binaryExpression; } const expressions = map(path, print, "expression"); const contents = indent(join(line, [ binaryExpression, ["? ", expressions[0]], [": ", expressions[1]] ])); const isNestedTernary = ((_a = path.getNode(4)) === null || _a === void 0 ? void 0 : _a.name) === "conditionalExpression"; return isNestedTernary ? contents : group(contents); }, binaryExpression(path, print, options) { var _a, _b; const { children } = path.node; const operands = flatMap(path, print, definedKeys(children, [ "expression", "pattern", "referenceType", "unaryExpression" ])); const operators = flatMap(path, operatorPath => { const { node } = operatorPath; let image; if (isTerminal(node)) { image = node.image; } else if (node.children.Less) { image = "<<"; } else { image = node.children.Greater.length === 2 ? ">>" : ">>>"; } return { image, doc: print(operatorPath) }; }, definedKeys(children, [ "AssignmentOperator", "BinaryOperator", "Instanceof", "shiftOperator" ])); const hasNonAssignmentOperators = (operators.length > 0 && !children.AssignmentOperator) || (children.expression !== undefined && isBinaryExpression(children.expression[0])); const isInList = ((_a = path.getNode(4)) === null || _a === void 0 ? void 0 : _a.name) === "elementValue" || ((_b = path.getNode(6)) === null || _b === void 0 ? void 0 : _b.name) === "argumentList"; return binary(operands, operators, { hasNonAssignmentOperators, isInList, isRoot: true, operatorPosition: options.experimentalOperatorPosition }); }, unaryExpression(path, print) { return [ ...map(path, print, "UnaryPrefixOperator"), call(path, print, "primary"), ...map(path, print, "UnarySuffixOperator") ]; }, unaryExpressionNotPlusMinus(path, print) { const { children } = path.node; const expression = []; if (children.UnaryPrefixOperatorNotPlusMinus) { expression.push(...map(path, print, "UnaryPrefixOperatorNotPlusMinus")); } expression.push(call(path, print, "primary")); if (children.UnarySuffixOperator) { expression.push(...map(path, print, "UnarySuffixOperator")); } return join(" ", expression); }, primary(path, print) { var _a, _b; const { children } = path.node; if (!children.primarySuffix) { return call(path, print, "primaryPrefix"); } const methodInvocations = children.primarySuffix .filter(({ children }) => children.methodInvocationSuffix) .map(({ children }) => children.methodInvocationSuffix[0].children); const hasLambdaMethodParameter = methodInvocations.some(({ argumentList }) => argumentList === null || argumentList === void 0 ? void 0 : argumentList[0].children.expression.some(({ children }) => children.lambdaExpression)); const prefixIsCallExpression = children.primaryPrefix[0].children.newExpression; const callExpressionCount = methodInvocations.length + (prefixIsCallExpression ? 1 : 0) + children.primarySuffix.filter(({ children }) => children.unqualifiedClassInstanceCreationExpression).length; const fqnOrRefType = (_a = children.primaryPrefix[0].children.fqnOrRefType) === null || _a === void 0 ? void 0 : _a[0].children; const prefixIsMethodInvocation = (fqnOrRefType === null || fqnOrRefType === void 0 ? void 0 : fqnOrRefType.fqnOrRefTypePartRest) !== undefined && ((_b = children.primarySuffix) === null || _b === void 0 ? void 0 : _b[0].children.methodInvocationSuffix) !== undefined; const prefixIsStaticMethodInvocation = prefixIsMethodInvocation && isCapitalizedIdentifier(fqnOrRefType); const prefixIsInstanceMethodInvocation = prefixIsMethodInvocation && !prefixIsStaticMethodInvocation; const mustBreakForCallExpressions = methodInvocations.length > 2 && hasLambdaMethodParameter; const separator = mustBreakForCallExpressions ? hardline : softline; const prefix = [ call(path, prefixPath => print(prefixPath, { lastSeparator: prefixIsStaticMethodInvocation || (prefixIsInstanceMethodInvocation && callExpressionCount === 1) ? "" : separator }), "primaryPrefix") ]; const canBreakForCallExpressions = callExpressionCount > 2 || (callExpressionCount === 2 && prefixIsInstanceMethodInvocation) || willBreak(prefix); const suffixes = []; each(path, suffixPath => { const { node, previous } = suffixPath; const suffix = print(suffixPath); if (node.children.Dot) { if ((canBreakForCallExpressions && ((!previous && prefixIsCallExpression) || (previous === null || previous === void 0 ? void 0 : previous.children.methodInvocationSuffix) || (previous === null || previous === void 0 ? void 0 : previous.children.unqualifiedClassInstanceCreationExpression))) || (!node.children.templateArgument && willBreak(suffix))) { suffixes.push(separator); } suffixes.push(suffix); } else if (previous) { suffixes.push(suffix); } else { prefix.push(prefixIsInstanceMethodInvocation && callExpressionCount >= 2 ? indent(suffix) : suffix); } }, "primarySuffix"); const hasSuffixComments = children.primarySuffix.some(suffix => hasLeadingComments(suffix)); return group(canBreakForCallExpressions || hasSuffixComments ? [prefix, indent(suffixes)] : [prefix, ...suffixes]); }, primaryPrefix: printSingle, primarySuffix(path, print) { const { children } = path.node; if (!children.Dot) { return printSingle(path, print); } const suffix = ["."]; if (children.This) { suffix.push("this"); } else if (children.Identifier) { if (children.typeArguments) { suffix.push(call(path, print, "typeArguments")); } suffix.push(call(path, print, "Identifier")); } else { const suffixKey = onlyDefinedKey(children, [ "templateArgument", "unqualifiedClassInstanceCreationExpression" ]); suffix.push(call(path, print, suffixKey)); } return suffix; }, fqnOrRefType(path, print, _, args) { var _a; const lastSeparator = (_a = args.lastSeparator) !== null && _a !== void 0 ? _a : ""; const fqnOrRefType = [ call(path, print, "fqnOrRefTypePartFirst"), ...map(path, partPath => { const part = print(partPath); return partPath.isLast ? [willBreak(part) ? hardline : lastSeparator, part] : part; }, "fqnOrRefTypePartRest") ]; fqnOrRefType.push(indent(fqnOrRefType.pop())); return path.node.children.dims ? [fqnOrRefType, call(path, print, "dims")] : fqnOrRefType; }, fqnOrRefTypePartFirst(path, print) { return join(" ", [ ...map(path, print, "annotation"), call(path, print, "fqnOrRefTypePartCommon") ]); }, fqnOrRefTypePartRest(path, print) { const common = call(path, print, "fqnOrRefTypePartCommon"); const type = path.node.children.typeArguments ? [call(path, print, "typeArguments"), common] : common; return [".", ...join(" ", [...map(path, print, "annotation"), type])]; }, fqnOrRefTypePartCommon(path, print) { const { children } = path.node; const keywordKey = onlyDefinedKey(children, ["Identifier", "Super"]); const keyword = call(path, print, keywordKey); return children.typeArguments ? [keyword, call(path, print, "typeArguments")] : keyword; }, parenthesisExpression(path, print) { var _a; const expression = call(path, print, "expression"); const ancestorName = (_a = path.getNode(14)) === null || _a === void 0 ? void 0 : _a.name; const binaryExpression = path.getNode(8); return ancestorName && ["guard", "returnStatement"].includes(ancestorName) && binaryExpression && binaryExpression.name === "binaryExpression" && Object.keys(binaryExpression.children).length === 1 ? indentInParentheses(expression) : ["(", indent(expression), ")"]; }, castExpression: printSingle, primitiveCastExpression(path, print) { return [ "(", call(path, print, "primitiveType"), ") ", call(path, print, "unaryExpression") ]; }, referenceTypeCastExpression(path, print) { const { children } = path.node; const type = call(path, print, "referenceType"); const cast = children.additionalBound ? indentInParentheses(join(line, [type, ...map(path, print, "additionalBound")])) : ["(", type, ")"]; const expressionKey = onlyDefinedKey(children, [ "lambdaExpression", "unaryExpressionNotPlusMinus" ]); return [cast, " ", call(path, print, expressionKey)]; }, newExpression: printSingle, unqualifiedClassInstanceCreationExpression(path, print) { const { children } = path.node; const expression = ["new "]; if (children.typeArguments) { expression.push(call(path, print, "typeArguments")); } expression.push(call(path, print, "classOrInterfaceTypeToInstantiate"), children.argumentList ? group(["(", call(path, print, "argumentList"), ")"]) : "()"); if (children.classBody) { expression.push(" ", call(path, print, "classBody")); } return expression; }, classOrInterfaceTypeToInstantiate(path, print) { const { children } = path.node; const type = children.annotation ? flatMap(path, childPath => [ print(childPath), isNonTerminal(childPath.node) ? " " : "." ], ["annotation", "Identifier"]) : printName(path, print); if (children.typeArgumentsOrDiamond) { type.push(call(path, print, "typeArgumentsOrDiamond")); } return type; }, typeArgumentsOrDiamond: printSingle, diamond() { return "<>"; }, methodInvocationSuffix(path, print) { return path.node.children.argumentList ? group(["(", call(path, print, "argumentList"), ")"]) : indentInParentheses(printDanglingComments(path), { shouldBreak: true }); }, argumentList(path, print) { var _a, _b, _c, _d; const expressions = path.node.children.expression; const lastExpression = expressions.at(-1); const lastExpressionLambdaBodyExpression = (_b = (_a = lastExpression.children.lambdaExpression) === null || _a === void 0 ? void 0 : _a[0].children.lambdaBody[0].children.expression) === null || _b === void 0 ? void 0 : _b[0].children; const lastExpressionLambdaBodyTernaryExpression = (_c = lastExpressionLambdaBodyExpression === null || lastExpressionLambdaBodyExpression === void 0 ? void 0 : lastExpressionLambdaBodyExpression.conditionalExpression) === null || _c === void 0 ? void 0 : _c[0].children; const isHuggable = !lastExpression.comments && (!lastExpressionLambdaBodyExpression || (lastExpressionLambdaBodyTernaryExpression === null || lastExpressionLambdaBodyTernaryExpression === void 0 ? void 0 : lastExpressionLambdaBodyTernaryExpression.QuestionMark) !== undefined || ((_d = lastExpressionLambdaBodyTernaryExpression === null || lastExpressionLambdaBodyTernaryExpression === void 0 ? void 0 : lastExpressionLambdaBodyTernaryExpression.binaryExpression) === null || _d === void 0 ? void 0 : _d[0].children.unaryExpression.length) === 1) && expressions.findIndex(({ children }) => children.lambdaExpression) === expressions.length - 1; const args = map(path, print, "expression"); const allArgsExpandable = [ indent([softline, ...join([",", line], args)]), softline ]; if (!isHuggable || willBreak(args.at(-1)[0])) { return allArgsExpandable; } const headArgs = args.slice(0, -1); const huggedLastArg = path.call(argPath => print(argPath, { hug: true }), "children", "expression", args.length - 1); const lastArgExpanded = join(", ", [ ...headArgs, group(huggedLastArg, { shouldBreak: true }) ]); if (willBreak(huggedLastArg)) { return [ breakParent, conditionalGroup([lastArgExpanded, allArgsExpandable]) ]; } return conditionalGroup([ join(", ", [...headArgs, huggedLastArg]), lastArgExpanded, allArgsExpandable ]); }, arrayCreationExpression(path, print) { const { children } = path.node; const typeKey = onlyDefinedKey(children, [ "classOrInterfaceType", "primitiveType" ]); const suffixKey = onlyDefinedKey(children, [ "arrayCreationExpressionWithoutInitializerSuffix", "arrayCreationWithInitializerSuffix" ]); return ["new ", call(path, print, typeKey), call(path, print, suffixKey)]; }, arrayCreationExpressionWithoutInitializerSuffix(path, print) { const expressions = call(path, print, "dimExprs"); return path.node.children.dims ? [expressions, call(path, print, "dims")] : expressions; }, arrayCreationWithInitializerSuffix(path, print) { return [ call(path, print, "dims"), " ", call(path, print, "arrayInitializer") ]; }, dimExprs(path, print) { return map(path, print, "dimExpr"); }, dimExpr(path, print) { return join(" ", [ ...map(path, print, "annotation"), ["[", call(path, print, "expression"), "]"] ]); }, classLiteralSuffix(path, print) { const lSquares = map(path, print, "LSquare"); const rSquares = map(path, print, "RSquare"); return [ ...lSquares.flatMap((lSquare, index) => [lSquare, rSquares[index]]), ".class" ]; }, arrayAccessSuffix(path, print) { return ["[", call(path, print, "expression"), "]"]; }, methodReferenceSuffix(path, print) { const { children } = path.node; const reference = ["::"]; if (children.typeArguments) { reference.push(call(path, print, "typeArguments")); } reference.push(call(path, print, onlyDefinedKey(children, ["Identifier", "New"]))); return reference; }, templateArgument: printSingle, template: printSingle, stringTemplate(path, print) { return printTemplate(path, print, "StringTemplateBegin", "StringTemplateMid", "StringTemplateEnd"); }, textBlockTemplate(path, print) { return printTemplate(path, print, "TextBlockTemplateBegin", "TextBlockTemplateMid", "TextBlockTemplateEnd"); }, embeddedExpression: printSingle, pattern: printSingle, typePattern: printSingle, recordPattern(path, print) { const patterns = path.node.children.componentPatternList ? indentInParentheses(call(path, print, "componentPatternList")) : "()"; return [call(path, print, "referenceType"), patterns]; }, componentPatternList(path, print) { return printList(path, print, "componentPattern"); }, componentPattern: printSingle, matchAllPattern: printSingle, guard(path, print) { var _a; const expression = call(path, print, "expression"); const hasParentheses = ((_a = path.node.children.expression[0].children.conditionalExpression) === null || _a === void 0 ? void 0 : _a[0].children.binaryExpression[0].children.unaryExpression[0].children.primary[0].children.primaryPrefix[0].children.parenthesisExpression) !== undefined; return [ "when ", hasParentheses ? expression : group([ ifBreak("("), indent([softline, expression]), softline, ifBreak(")") ]) ]; } }; function binary(operands, operators, { hasNonAssignmentOperators = false, isInList = false, isRoot = false, operatorPosition }) { let levelOperator; let levelPrecedence; let level = []; while (operators.length) { const nextOperator = operators[0].image; const nextPrecedence = getOperatorPrecedence(nextOperator); if (levelPrecedence === undefined || nextPrecedence === levelPrecedence) { const { image: operator, doc: operatorDoc } = operators.shift(); level.push(operands.shift()); if (levelOperator !== undefined && needsParentheses(levelOperator, operator)) { level = [["(", group(indent(level)), ")"]]; } const parts = [" ", operatorDoc, line]; if (operatorPosition === "start" && !isAssignmentOperator(operator)) { parts.reverse(); } level.push(parts); levelOperator = operator; levelPrecedence = nextPrecedence; } else if (nextPrecedence < levelPrecedence) { if (!isRoot) { break; } level.push(operands.shift()); const content = group(indent(level)); operands.unshift(levelOperator !== undefined && needsParentheses(levelOperator, nextOperator) ? ["(", content, ")"] : content); level = []; levelOperator = undefined; levelPrecedence = undefined; } else { const content = binary(operands, operators, { operatorPosition }); operands.unshift(levelOperator !== undefined && needsParentheses(nextOperator, levelOperator) ? ["(", indent(content), ")"] : content); } } level.push(operands.shift()); if (!levelOperator || (!isInList && !isAssignmentOperator(levelOperator) && levelOperator !== "instanceof")) { return group(level); } if (!isRoot || hasNonAssignmentOperators) { return group(indent(level)); } const groupId = Symbol("assignment"); return group([ level[0], group(indent(level[1]), { id: groupId }), indentIfBreak(level[2], { groupId }) ]); } const precedencesByOperator = new Map([ ["||"], ["&&"], ["|"], ["^"], ["&"], ["==", "!="], ["<", ">", "<=", ">=", "instanceof"], ["<<", ">>", ">>>"], ["+", "-"], ["*", "/", "%"] ].flatMap((operators, index) => operators.map(operator => [operator, index]))); function getOperatorPrecedence(operator) { var _a; return (_a = precedencesByOperator.get(operator)) !== null && _a !== void 0 ? _a : -1; } function needsParentheses(operator, parentOperator) { return ((operator === "&&" && parentOperator === "||") || (["|", "^", "&", "<<", ">>", ">>>"].includes(parentOperator) && getOperatorPrecedence(operator) > getOperatorPrecedence(parentOperator)) || [operator, parentOperator].every(o => ["==", "!="].includes(o)) || [operator, parentOperator].every(o => ["<<", ">>", ">>>"].includes(o)) || (operator === "*" && parentOperator === "/") || (operator === "/" && parentOperator === "*") || (operator === "%" && ["+", "-", "*", "/"].includes(parentOperator)) || (["*", "/"].includes(operator) && parentOperator === "%")); } const assignmentOperators = new Set([ "=", "*=", "/=", "%=", "+=", "-=", "<<=", ">>=", ">>>=", "&=", "^=", "|=" ]); function isAssignmentOperator(operator) { return assignmentOperators.has(operator); } function isCapitalizedIdentifier(fqnOrRefType) { var _a, _b, _c; const nextToLastIdentifier = (_c = (_b = [ fqnOrRefType.fqnOrRefTypePartFirst[0], ...((_a = fqnOrRefType.fqnOrRefTypePartRest) !== null && _a !== void 0 ? _a : []) ].at(-2)) === null || _b === void 0 ? void 0 : _b.children.fqnOrRefTypePartCommon[0].children.Identifier) === null || _c === void 0 ? void 0 : _c[0].image; return /^\p{Uppercase_Letter}/u.test(nextToLastIdentifier !== null && nextToLastIdentifier !== void 0 ? nextToLastIdentifier : ""); } function printTemplate(path, print, beginKey, midKey, endKey) { const begin = call(path, ({ node }) => node.image, beginKey); const mids = map(path, ({ node }) => node.image, midKey); const end = call(path, ({ node }) => node.image, endKey); const lines = [begin, ...mids, end].join("").split("\n").slice(1); const baseIndent = findBaseIndent(lines); const prefix = "\n" + " ".repeat(baseIndent); const parts = [begin, ...mids, end].map(image => join(hardline, image.split(prefix))); return indent([ parts[0], ...map(path, (expressionPath, index) => { const expression = group([ indent([softline, print(expressionPath), lineSuffixBoundary]), softline ]); return index === 0 ? expression : [parts[index], expression]; }, "embeddedExpression"), parts.at(-1) ]); }