Files
test-repo/src/language-js/print/function.js
T

329 lines
8.0 KiB
JavaScript

import * as assert from "#universal/assert";
import {
group,
hardline,
ifBreak,
indent,
softline,
} from "../../document/builders.js";
import { printDanglingComments } from "../../main/comments/print.js";
import hasNewlineInRange from "../../utils/has-newline-in-range.js";
import { locEnd, locStart } from "../loc.js";
import {
CommentCheckFlags,
getCallArguments,
getFunctionParameters,
getLeftSide,
hasComment,
hasLeadingOwnLineComment,
hasNakedLeftSide,
isBinaryish,
isCallExpression,
isJsxElement,
isMethod,
} from "../utils/index.js";
import {
printFunctionParameters,
shouldBreakFunctionParameters,
shouldGroupFunctionParameters,
} from "./function-parameters.js";
import { printDeclareToken } from "./misc.js";
import { printPropertyKey } from "./property.js";
import { printTypeAnnotationProperty } from "./type-annotation.js";
/**
* @import AstPath from "../../common/ast-path.js"
* @import {Doc} from "../../document/builders.js"
*/
const isMethodValue = ({ node, key, parent }) =>
key === "value" &&
node.type === "FunctionExpression" &&
(parent.type === "ObjectMethod" ||
parent.type === "ClassMethod" ||
parent.type === "ClassPrivateMethod" ||
parent.type === "MethodDefinition" ||
parent.type === "TSAbstractMethodDefinition" ||
parent.type === "TSDeclareMethod" ||
(parent.type === "Property" && isMethod(parent)));
/*
- "FunctionDeclaration"
- "FunctionExpression"
- `TSDeclareFunction`(TypeScript)
*/
function printFunction(path, options, print, args) {
if (isMethodValue(path)) {
return printMethodValue(path, options, print);
}
const { node } = path;
let shouldExpandArgument = false;
if (
(node.type === "FunctionDeclaration" ||
node.type === "FunctionExpression") &&
args?.expandLastArg
) {
const { parent } = path;
if (
isCallExpression(parent) &&
(getCallArguments(parent).length > 1 ||
getFunctionParameters(node).every(
(param) => param.type === "Identifier" && !param.typeAnnotation,
))
) {
shouldExpandArgument = true;
}
}
const parts = [
printDeclareToken(path),
node.async ? "async " : "",
`function${node.generator ? "*" : ""} `,
node.id ? print("id") : "",
];
const parametersDoc = printFunctionParameters(
path,
options,
print,
shouldExpandArgument,
);
const returnTypeDoc = printReturnType(path, print);
const shouldGroupParameters = shouldGroupFunctionParameters(
node,
returnTypeDoc,
);
parts.push(
print("typeParameters"),
group([
shouldGroupParameters ? group(parametersDoc) : parametersDoc,
returnTypeDoc,
]),
node.body ? " " : "",
print("body"),
);
if (options.semi && (node.declare || !node.body)) {
parts.push(";");
}
return parts;
}
/*
- `ObjectMethod`
- `Property`
- `ObjectProperty`
- `ClassMethod`
- `ClassPrivateMethod`
- `MethodDefinition
- `TSAbstractMethodDefinition` (TypeScript)
- `TSDeclareMethod` (TypeScript)
*/
function printMethod(path, options, print) {
const { node } = path;
const { kind } = node;
const value = node.value || node;
const parts = [];
if (!kind || kind === "init" || kind === "method" || kind === "constructor") {
if (value.async) {
parts.push("async ");
}
} else {
assert.ok(kind === "get" || kind === "set");
parts.push(kind, " ");
}
// A `getter`/`setter` can't be a generator, but it's recoverable
if (value.generator) {
parts.push("*");
}
parts.push(
printPropertyKey(path, options, print),
node.optional ? "?" : "",
node === value ? printMethodValue(path, options, print) : print("value"),
);
return parts;
}
function printMethodValue(path, options, print) {
const { node } = path;
const parametersDoc = printFunctionParameters(path, options, print);
const returnTypeDoc = printReturnType(path, print);
const shouldBreakParameters = shouldBreakFunctionParameters(node);
const shouldGroupParameters = shouldGroupFunctionParameters(
node,
returnTypeDoc,
);
const parts = [
print("typeParameters"),
group([
shouldBreakParameters
? group(parametersDoc, { shouldBreak: true })
: shouldGroupParameters
? group(parametersDoc)
: parametersDoc,
returnTypeDoc,
]),
];
if (node.body) {
parts.push(" ", print("body"));
} else {
parts.push(options.semi ? ";" : "");
}
return parts;
}
function canPrintParamsWithoutParens(node) {
const parameters = getFunctionParameters(node);
return (
parameters.length === 1 &&
!node.typeParameters &&
!hasComment(node, CommentCheckFlags.Dangling) &&
parameters[0].type === "Identifier" &&
!parameters[0].typeAnnotation &&
!hasComment(parameters[0]) &&
!parameters[0].optional &&
!node.predicate &&
!node.returnType
);
}
function shouldPrintParamsWithoutParens(path, options) {
if (options.arrowParens === "always") {
return false;
}
if (options.arrowParens === "avoid") {
const { node } = path;
return canPrintParamsWithoutParens(node);
}
// Fallback default; should be unreachable
/* c8 ignore next */
return false;
}
/** @returns {Doc} */
function printReturnType(path, print) {
const { node } = path;
const returnType = printTypeAnnotationProperty(path, print, "returnType");
const parts = [returnType];
if (node.predicate) {
parts.push(print("predicate"));
}
return parts;
}
// `ReturnStatement` and `ThrowStatement`
function printReturnOrThrowArgument(path, options, print) {
const { node } = path;
const parts = [];
if (node.argument) {
let argumentDoc = print("argument");
if (returnArgumentHasLeadingComment(options, node.argument)) {
argumentDoc = ["(", indent([hardline, argumentDoc]), hardline, ")"];
} else if (
isBinaryish(node.argument) ||
(options.experimentalTernaries &&
node.argument.type === "ConditionalExpression" &&
(node.argument.consequent.type === "ConditionalExpression" ||
node.argument.alternate.type === "ConditionalExpression"))
) {
argumentDoc = group([
ifBreak("("),
indent([softline, argumentDoc]),
softline,
ifBreak(")"),
]);
}
parts.push(" ", argumentDoc);
}
const hasDanglingComments = hasComment(node, CommentCheckFlags.Dangling);
const shouldPrintSemiBeforeComments =
options.semi &&
hasDanglingComments &&
hasComment(node, CommentCheckFlags.Last | CommentCheckFlags.Line);
if (shouldPrintSemiBeforeComments) {
parts.push(";");
}
if (hasDanglingComments) {
parts.push(" ", printDanglingComments(path, options));
}
if (!shouldPrintSemiBeforeComments && options.semi) {
parts.push(";");
}
return parts;
}
function printReturnStatement(path, options, print) {
return ["return", printReturnOrThrowArgument(path, options, print)];
}
function printThrowStatement(path, options, print) {
return ["throw", printReturnOrThrowArgument(path, options, print)];
}
// This recurses the return argument, looking for the first token
// (the leftmost leaf node) and, if it (or its parents) has any
// leadingComments, returns true (so it can be wrapped in parens).
function returnArgumentHasLeadingComment(options, argument) {
if (
hasLeadingOwnLineComment(options.originalText, argument) ||
(hasComment(argument, CommentCheckFlags.Leading, (comment) =>
hasNewlineInRange(
options.originalText,
locStart(comment),
locEnd(comment),
),
) &&
!isJsxElement(argument))
) {
return true;
}
if (hasNakedLeftSide(argument)) {
let leftMost = argument;
let newLeftMost;
while ((newLeftMost = getLeftSide(leftMost))) {
leftMost = newLeftMost;
if (hasLeadingOwnLineComment(options.originalText, leftMost)) {
return true;
}
}
}
return false;
}
export {
printFunction,
printMethod,
printMethodValue,
printReturnStatement,
printReturnType,
printThrowStatement,
shouldPrintParamsWithoutParens,
};