Files
test-repo/src/language-js/print/call-arguments.js
T

301 lines
8.6 KiB
JavaScript

import { printDanglingComments } from "../../main/comments.js";
import { getLast, getPenultimate } from "../../common/util.js";
import {
getFunctionParameters,
hasComment,
CommentCheckFlags,
isFunctionCompositionArgs,
isJsxNode,
isLongCurriedCallExpression,
shouldPrintComma,
getCallArguments,
iterateCallArgumentsPath,
isNextLineEmpty,
isCallExpression,
isStringLiteral,
isObjectProperty,
} from "../utils/index.js";
import {
line,
hardline,
softline,
group,
indent,
conditionalGroup,
ifBreak,
breakParent,
} from "../../document/builders.js";
import { willBreak } from "../../document/utils.js";
import { ArgExpansionBailout } from "../../common/errors.js";
import { isConciselyPrintedArray } from "./array.js";
function printCallArguments(path, options, print) {
const node = path.getValue();
const isDynamicImport = node.type === "ImportExpression";
const args = getCallArguments(node);
if (args.length === 0) {
return [
"(",
printDanglingComments(path, options, /* sameIndent */ true),
")",
];
}
// useEffect(() => { ... }, [foo, bar, baz])
if (isReactHookCallWithDepsArray(args)) {
return ["(", print(["arguments", 0]), ", ", print(["arguments", 1]), ")"];
}
let anyArgEmptyLine = false;
let hasEmptyLineFollowingFirstArg = false;
const lastArgIndex = args.length - 1;
const printedArguments = [];
iterateCallArgumentsPath(path, (argPath, index) => {
const arg = argPath.getNode();
const parts = [print()];
if (index === lastArgIndex) {
// do nothing
} else if (isNextLineEmpty(arg, options)) {
if (index === 0) {
hasEmptyLineFollowingFirstArg = true;
}
anyArgEmptyLine = true;
parts.push(",", hardline, hardline);
} else {
parts.push(",", line);
}
printedArguments.push(parts);
});
const maybeTrailingComma =
// Dynamic imports cannot have trailing commas
!(isDynamicImport || (node.callee && node.callee.type === "Import")) &&
shouldPrintComma(options, "all")
? ","
: "";
function allArgsBrokenOut() {
return group(
["(", indent([line, ...printedArguments]), maybeTrailingComma, line, ")"],
{ shouldBreak: true }
);
}
if (
anyArgEmptyLine ||
(path.getParentNode().type !== "Decorator" &&
isFunctionCompositionArgs(args))
) {
return allArgsBrokenOut();
}
const shouldGroupFirst = shouldGroupFirstArg(args);
const shouldGroupLast = shouldGroupLastArg(args, options);
if (shouldGroupFirst || shouldGroupLast) {
if (
shouldGroupFirst
? printedArguments.slice(1).some(willBreak)
: printedArguments.slice(0, -1).some(willBreak)
) {
return allArgsBrokenOut();
}
// We want to print the last argument with a special flag
let printedExpanded = [];
try {
iterateCallArgumentsPath(path, (argPath, i) => {
if (shouldGroupFirst && i === 0) {
printedExpanded = [
[
print([], { expandFirstArg: true }),
printedArguments.length > 1 ? "," : "",
hasEmptyLineFollowingFirstArg ? hardline : line,
hasEmptyLineFollowingFirstArg ? hardline : "",
],
...printedArguments.slice(1),
];
}
if (shouldGroupLast && i === lastArgIndex) {
printedExpanded = [
...printedArguments.slice(0, -1),
print([], { expandLastArg: true }),
];
}
});
} catch (caught) {
if (caught instanceof ArgExpansionBailout) {
return allArgsBrokenOut();
}
/* istanbul ignore next */
throw caught;
}
return [
printedArguments.some(willBreak) ? breakParent : "",
conditionalGroup([
["(", ...printedExpanded, ")"],
shouldGroupFirst
? [
"(",
group(printedExpanded[0], { shouldBreak: true }),
...printedExpanded.slice(1),
")",
]
: [
"(",
...printedArguments.slice(0, -1),
group(getLast(printedExpanded), { shouldBreak: true }),
")",
],
allArgsBrokenOut(),
]),
];
}
const contents = [
"(",
indent([softline, ...printedArguments]),
ifBreak(maybeTrailingComma),
softline,
")",
];
if (isLongCurriedCallExpression(path)) {
// By not wrapping the arguments in a group, the printer prioritizes
// breaking up these arguments rather than the args of the parent call.
return contents;
}
return group(contents, {
shouldBreak: printedArguments.some(willBreak) || anyArgEmptyLine,
});
}
function couldGroupArg(arg, arrowChainRecursion = false) {
return (
(arg.type === "ObjectExpression" &&
(arg.properties.length > 0 || hasComment(arg))) ||
(arg.type === "ArrayExpression" &&
(arg.elements.length > 0 || hasComment(arg))) ||
(arg.type === "TSTypeAssertion" && couldGroupArg(arg.expression)) ||
(arg.type === "TSAsExpression" && couldGroupArg(arg.expression)) ||
arg.type === "FunctionExpression" ||
(arg.type === "ArrowFunctionExpression" &&
// we want to avoid breaking inside composite return types but not simple keywords
// https://github.com/prettier/prettier/issues/4070
// export class Thing implements OtherThing {
// do: (type: Type) => Provider<Prop> = memoize(
// (type: ObjectType): Provider<Opts> => {}
// );
// }
// https://github.com/prettier/prettier/issues/6099
// app.get("/", (req, res): void => {
// res.send("Hello World!");
// });
(!arg.returnType ||
!arg.returnType.typeAnnotation ||
arg.returnType.typeAnnotation.type !== "TSTypeReference" ||
// https://github.com/prettier/prettier/issues/7542
isNonEmptyBlockStatement(arg.body)) &&
(arg.body.type === "BlockStatement" ||
(arg.body.type === "ArrowFunctionExpression" &&
couldGroupArg(arg.body, true)) ||
arg.body.type === "ObjectExpression" ||
arg.body.type === "ArrayExpression" ||
(!arrowChainRecursion &&
(isCallExpression(arg.body) ||
arg.body.type === "ConditionalExpression")) ||
isJsxNode(arg.body))) ||
arg.type === "DoExpression" ||
arg.type === "ModuleExpression"
);
}
function shouldGroupLastArg(args, options) {
const lastArg = getLast(args);
const penultimateArg = getPenultimate(args);
return (
!hasComment(lastArg, CommentCheckFlags.Leading) &&
!hasComment(lastArg, CommentCheckFlags.Trailing) &&
couldGroupArg(lastArg) &&
// If the last two arguments are of the same type,
// disable last element expansion.
(!penultimateArg || penultimateArg.type !== lastArg.type) &&
// useMemo(() => func(), [foo, bar, baz])
(args.length !== 2 ||
penultimateArg.type !== "ArrowFunctionExpression" ||
lastArg.type !== "ArrayExpression") &&
!(
args.length > 1 &&
lastArg.type === "ArrayExpression" &&
isConciselyPrintedArray(lastArg, options)
)
);
}
function shouldGroupFirstArg(args) {
if (args.length !== 2) {
return false;
}
const [firstArg, secondArg] = args;
if (
firstArg.type === "ModuleExpression" &&
isTypeModuleObjectExpression(secondArg)
) {
return true;
}
return (
!hasComment(firstArg) &&
(firstArg.type === "FunctionExpression" ||
(firstArg.type === "ArrowFunctionExpression" &&
firstArg.body.type === "BlockStatement")) &&
secondArg.type !== "FunctionExpression" &&
secondArg.type !== "ArrowFunctionExpression" &&
secondArg.type !== "ConditionalExpression" &&
!couldGroupArg(secondArg)
);
}
function isReactHookCallWithDepsArray(args) {
return (
args.length === 2 &&
args[0].type === "ArrowFunctionExpression" &&
getFunctionParameters(args[0]).length === 0 &&
args[0].body.type === "BlockStatement" &&
args[1].type === "ArrayExpression" &&
!args.some((arg) => hasComment(arg))
);
}
function isNonEmptyBlockStatement(node) {
return (
node.type === "BlockStatement" &&
(node.body.some((node) => node.type !== "EmptyStatement") ||
hasComment(node, CommentCheckFlags.Dangling))
);
}
// { type: "module" }
function isTypeModuleObjectExpression(node) {
return (
node.type === "ObjectExpression" &&
node.properties.length === 1 &&
isObjectProperty(node.properties[0]) &&
node.properties[0].key.type === "Identifier" &&
node.properties[0].key.name === "type" &&
isStringLiteral(node.properties[0].value) &&
node.properties[0].value.value === "module"
);
}
export default printCallArguments;