306 lines
8.8 KiB
JavaScript
306 lines
8.8 KiB
JavaScript
import { ArgExpansionBailout } from "../../common/errors.js";
|
|
import {
|
|
group,
|
|
hardline,
|
|
ifBreak,
|
|
indent,
|
|
line,
|
|
softline,
|
|
} from "../../document/builders.js";
|
|
import { removeLines, willBreak } from "../../document/utils.js";
|
|
import { printDanglingComments } from "../../main/comments/print.js";
|
|
import getNextNonSpaceNonCommentCharacter from "../../utils/get-next-non-space-non-comment-character.js";
|
|
import isNonEmptyArray from "../../utils/is-non-empty-array.js";
|
|
import { locEnd } from "../loc.js";
|
|
import {
|
|
getFunctionParameters,
|
|
hasComment,
|
|
hasRestParameter,
|
|
isArrayExpression,
|
|
isFlowObjectTypePropertyAFunction,
|
|
isNextLineEmpty,
|
|
isObjectExpression,
|
|
isObjectType,
|
|
isSimpleType,
|
|
isTestCall,
|
|
isTypeAnnotationAFunction,
|
|
iterateFunctionParametersPath,
|
|
shouldPrintComma,
|
|
} from "../utils/index.js";
|
|
|
|
/** @import AstPath from "../../common/ast-path.js" */
|
|
|
|
function printFunctionParameters(
|
|
path,
|
|
options,
|
|
print,
|
|
shouldExpandArgument,
|
|
shouldPrintTypeParameters,
|
|
) {
|
|
const functionNode = path.node;
|
|
const parameters = getFunctionParameters(functionNode);
|
|
const typeParametersDoc =
|
|
shouldPrintTypeParameters && functionNode.typeParameters
|
|
? print("typeParameters")
|
|
: "";
|
|
|
|
if (parameters.length === 0) {
|
|
return [
|
|
typeParametersDoc,
|
|
"(",
|
|
printDanglingComments(path, options, {
|
|
filter: (comment) =>
|
|
getNextNonSpaceNonCommentCharacter(
|
|
options.originalText,
|
|
locEnd(comment),
|
|
) === ")",
|
|
}),
|
|
")",
|
|
];
|
|
}
|
|
|
|
const { parent } = path;
|
|
const isParametersInTestCall = isTestCall(parent);
|
|
const shouldHugParameters = shouldHugTheOnlyFunctionParameter(functionNode);
|
|
const printed = [];
|
|
iterateFunctionParametersPath(path, (parameterPath, index) => {
|
|
const isLastParameter = index === parameters.length - 1;
|
|
if (isLastParameter && functionNode.rest) {
|
|
printed.push("...");
|
|
}
|
|
printed.push(print());
|
|
if (isLastParameter) {
|
|
return;
|
|
}
|
|
printed.push(",");
|
|
if (isParametersInTestCall || shouldHugParameters) {
|
|
printed.push(" ");
|
|
} else if (isNextLineEmpty(parameters[index], options)) {
|
|
printed.push(hardline, hardline);
|
|
} else {
|
|
printed.push(line);
|
|
}
|
|
});
|
|
|
|
// If the parent is a call with the first/last argument expansion and this is the
|
|
// params of the first/last argument, we don't want the arguments to break and instead
|
|
// want the whole expression to be on a new line.
|
|
//
|
|
// Good: Bad:
|
|
// verylongcall( verylongcall((
|
|
// (a, b) => { a,
|
|
// } b,
|
|
// ) ) => {
|
|
// })
|
|
if (shouldExpandArgument && !isDecoratedFunction(path)) {
|
|
if (willBreak(typeParametersDoc) || willBreak(printed)) {
|
|
// Removing lines in this case leads to broken or ugly output
|
|
throw new ArgExpansionBailout();
|
|
}
|
|
return group([
|
|
removeLines(typeParametersDoc),
|
|
"(",
|
|
removeLines(printed),
|
|
")",
|
|
]);
|
|
}
|
|
|
|
// Single object destructuring should hug
|
|
//
|
|
// function({
|
|
// a,
|
|
// b,
|
|
// c
|
|
// }) {}
|
|
const hasNotParameterDecorator = parameters.every(
|
|
(node) => !isNonEmptyArray(node.decorators),
|
|
);
|
|
if (shouldHugParameters && hasNotParameterDecorator) {
|
|
return [typeParametersDoc, "(", ...printed, ")"];
|
|
}
|
|
|
|
// don't break in specs, eg; `it("should maintain parens around done even when long", (done) => {})`
|
|
if (isParametersInTestCall) {
|
|
return [typeParametersDoc, "(", ...printed, ")"];
|
|
}
|
|
|
|
const isFlowShorthandWithOneArg =
|
|
(isFlowObjectTypePropertyAFunction(parent) ||
|
|
isTypeAnnotationAFunction(parent) ||
|
|
parent.type === "TypeAlias" ||
|
|
parent.type === "UnionTypeAnnotation" ||
|
|
parent.type === "IntersectionTypeAnnotation" ||
|
|
(parent.type === "FunctionTypeAnnotation" &&
|
|
parent.returnType === functionNode)) &&
|
|
parameters.length === 1 &&
|
|
parameters[0].name === null &&
|
|
// `type q = (this: string) => void;`
|
|
functionNode.this !== parameters[0] &&
|
|
parameters[0].typeAnnotation &&
|
|
functionNode.typeParameters === null &&
|
|
isSimpleType(parameters[0].typeAnnotation) &&
|
|
!functionNode.rest;
|
|
|
|
if (isFlowShorthandWithOneArg) {
|
|
if (
|
|
options.arrowParens === "always" ||
|
|
functionNode.type === "HookTypeAnnotation"
|
|
) {
|
|
return ["(", ...printed, ")"];
|
|
}
|
|
return printed;
|
|
}
|
|
|
|
return [
|
|
typeParametersDoc,
|
|
"(",
|
|
indent([softline, ...printed]),
|
|
ifBreak(
|
|
!hasRestParameter(functionNode) && shouldPrintComma(options, "all")
|
|
? ","
|
|
: "",
|
|
),
|
|
softline,
|
|
")",
|
|
];
|
|
}
|
|
|
|
function shouldHugTheOnlyFunctionParameter(node) {
|
|
if (!node) {
|
|
return false;
|
|
}
|
|
const parameters = getFunctionParameters(node);
|
|
if (parameters.length !== 1) {
|
|
return false;
|
|
}
|
|
const [parameter] = parameters;
|
|
return (
|
|
!hasComment(parameter) &&
|
|
(parameter.type === "ObjectPattern" ||
|
|
parameter.type === "ArrayPattern" ||
|
|
(parameter.type === "Identifier" &&
|
|
parameter.typeAnnotation &&
|
|
(parameter.typeAnnotation.type === "TypeAnnotation" ||
|
|
parameter.typeAnnotation.type === "TSTypeAnnotation") &&
|
|
isObjectType(parameter.typeAnnotation.typeAnnotation)) ||
|
|
(parameter.type === "FunctionTypeParam" &&
|
|
isObjectType(parameter.typeAnnotation) &&
|
|
parameter !== node.rest) ||
|
|
(parameter.type === "AssignmentPattern" &&
|
|
(parameter.left.type === "ObjectPattern" ||
|
|
parameter.left.type === "ArrayPattern") &&
|
|
(parameter.right.type === "Identifier" ||
|
|
(isObjectExpression(parameter.right) &&
|
|
parameter.right.properties.length === 0) ||
|
|
(isArrayExpression(parameter.right) &&
|
|
parameter.right.elements.length === 0))))
|
|
);
|
|
}
|
|
|
|
function getReturnTypeNode(functionNode) {
|
|
let returnTypeNode;
|
|
if (functionNode.returnType) {
|
|
returnTypeNode = functionNode.returnType;
|
|
if (returnTypeNode.typeAnnotation) {
|
|
returnTypeNode = returnTypeNode.typeAnnotation;
|
|
}
|
|
} else if (functionNode.typeAnnotation) {
|
|
returnTypeNode = functionNode.typeAnnotation;
|
|
}
|
|
return returnTypeNode;
|
|
}
|
|
|
|
// When parameters are grouped, the return type annotation breaks first.
|
|
function shouldGroupFunctionParameters(functionNode, returnTypeDoc) {
|
|
const returnTypeNode = getReturnTypeNode(functionNode);
|
|
if (!returnTypeNode) {
|
|
return false;
|
|
}
|
|
|
|
const typeParameters = functionNode.typeParameters?.params;
|
|
if (typeParameters) {
|
|
if (typeParameters.length > 1) {
|
|
return false;
|
|
}
|
|
if (typeParameters.length === 1) {
|
|
const typeParameter = typeParameters[0];
|
|
if (typeParameter.constraint || typeParameter.default) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return (
|
|
getFunctionParameters(functionNode).length === 1 &&
|
|
(isObjectType(returnTypeNode) || willBreak(returnTypeDoc))
|
|
);
|
|
}
|
|
|
|
/**
|
|
* The "decorated function" pattern.
|
|
* The arrow function should be kept hugged even if its signature breaks.
|
|
*
|
|
* ```
|
|
* const decoratedFn = decorator(param1, param2)((
|
|
* ...
|
|
* ) => {
|
|
* ...
|
|
* });
|
|
* ```
|
|
* @param {AstPath} path
|
|
*/
|
|
function isDecoratedFunction(path) {
|
|
return path.match(
|
|
(node) =>
|
|
node.type === "ArrowFunctionExpression" &&
|
|
node.body.type === "BlockStatement",
|
|
(node, name) => {
|
|
if (
|
|
node.type === "CallExpression" &&
|
|
name === "arguments" &&
|
|
node.arguments.length === 1 &&
|
|
node.callee.type === "CallExpression"
|
|
) {
|
|
const decorator = node.callee.callee;
|
|
return (
|
|
decorator.type === "Identifier" ||
|
|
(decorator.type === "MemberExpression" &&
|
|
!decorator.computed &&
|
|
decorator.object.type === "Identifier" &&
|
|
decorator.property.type === "Identifier")
|
|
);
|
|
}
|
|
return false;
|
|
},
|
|
(node, name) =>
|
|
(node.type === "VariableDeclarator" && name === "init") ||
|
|
(node.type === "ExportDefaultDeclaration" && name === "declaration") ||
|
|
(node.type === "TSExportAssignment" && name === "expression") ||
|
|
(node.type === "AssignmentExpression" &&
|
|
name === "right" &&
|
|
node.left.type === "MemberExpression" &&
|
|
node.left.object.type === "Identifier" &&
|
|
node.left.object.name === "module" &&
|
|
node.left.property.type === "Identifier" &&
|
|
node.left.property.name === "exports"),
|
|
(node) =>
|
|
node.type !== "VariableDeclaration" ||
|
|
(node.kind === "const" && node.declarations.length === 1),
|
|
);
|
|
}
|
|
|
|
function shouldBreakFunctionParameters(functionNode) {
|
|
const parameters = getFunctionParameters(functionNode);
|
|
return (
|
|
parameters.length > 1 &&
|
|
parameters.some((parameter) => parameter.type === "TSParameterProperty")
|
|
);
|
|
}
|
|
|
|
export {
|
|
printFunctionParameters,
|
|
shouldBreakFunctionParameters,
|
|
shouldGroupFunctionParameters,
|
|
shouldHugTheOnlyFunctionParameter,
|
|
};
|