prettierx
Version:
prettierX - a less opinionated fork of the Prettier code formatter
887 lines (804 loc) • 26.4 kB
JavaScript
"use strict";
/** @typedef {import("../document").Doc} Doc */
// TODO(azz): anything that imports from main shouldn't be in a `language-*` dir.
const { printDanglingComments } = require("../main/comments");
const { hasNewline } = require("../common/util");
const {
builders: { join, line, hardline, softline, group, indent },
utils: { replaceNewlinesWithLiterallines },
} = require("../document");
const embed = require("./embed");
const clean = require("./clean");
const { insertPragma } = require("./pragma");
const handleComments = require("./comments");
const pathNeedsParens = require("./needs-parens");
const preprocess = require("./print-preprocess");
const {
hasFlowShorthandAnnotationComment,
hasComment,
// [prettierx]: for --space-in-parens option support
hasAddedLine,
CommentCheckFlags,
isTheOnlyJsxElementInMarkdown,
isBlockComment,
isLineComment,
isNextLineEmpty,
needsHardlineAfterDanglingComment,
rawText,
hasIgnoreComment,
isCallExpression,
isMemberExpression,
// [prettierx]: for --space-in-parens option support
startsWithSpace,
} = require("./utils");
const { locStart, locEnd } = require("./loc");
const {
printHtmlBinding,
isVueEventBindingExpression,
} = require("./print/html-binding");
const { printAngular } = require("./print/angular");
const { printJsx, hasJsxIgnoreComment } = require("./print/jsx");
const { printFlow } = require("./print/flow");
const { printTypescript } = require("./print/typescript");
const {
printOptionalToken,
printBindExpressionCallee,
printTypeAnnotation,
adjustClause,
printRestSpread,
} = require("./print/misc");
const {
printImportDeclaration,
printExportDeclaration,
printExportAllDeclaration,
printModuleSpecifier,
} = require("./print/module");
const { printTernary } = require("./print/ternary");
const { printTemplateLiteral } = require("./print/template-literal");
const { printArray } = require("./print/array");
const { printObject } = require("./print/object");
const {
printClass,
printClassMethod,
printClassProperty,
} = require("./print/class");
const { printProperty } = require("./print/property");
const {
printFunction,
printArrowFunction,
printMethod,
printReturnStatement,
printThrowStatement,
} = require("./print/function");
const { printCallExpression } = require("./print/call-expression");
const {
printVariableDeclarator,
printAssignmentExpression,
} = require("./print/assignment");
const { printBinaryishExpression } = require("./print/binaryish");
const { printSwitchCaseConsequent } = require("./print/statement");
const { printMemberExpression } = require("./print/member");
const { printBlock, printBlockBody } = require("./print/block");
const { printComment } = require("./print/comment");
const { printLiteral } = require("./print/literal");
const { printDecorators } = require("./print/decorators");
function genericPrint(path, options, print, args) {
const printed = printPathNoParens(path, options, print, args);
if (!printed) {
return "";
}
const node = path.getValue();
const { type } = node;
// Their decorators are handled themselves, and they can't have parentheses
if (
type === "ClassMethod" ||
type === "ClassPrivateMethod" ||
type === "ClassProperty" ||
type === "PropertyDefinition" ||
type === "TSAbstractClassProperty" ||
type === "ClassPrivateProperty" ||
type === "MethodDefinition" ||
type === "TSAbstractMethodDefinition" ||
type === "TSDeclareMethod"
) {
return printed;
}
const printedDecorators = printDecorators(path, options, print);
// Nodes with decorators can't have parentheses and don't need leading semicolons
if (printedDecorators) {
return group([...printedDecorators, printed]);
}
const needsParens = pathNeedsParens(path, options);
if (!needsParens) {
return args && args.needsSemi ? [";", printed] : printed;
}
// [prettierx] --space-in-parens option support (...)
const insideSpace = options.spaceInParens ? " " : "";
const addLeadingSpace = !startsWithSpace(printed);
const addTrailingSpace = !hasAddedLine(printed);
const parts = [
args && args.needsSemi ? ";(" : "(",
addLeadingSpace ? insideSpace : "",
printed,
];
if (hasFlowShorthandAnnotationComment(node)) {
const [comment] = node.trailingComments;
parts.push(" /*", comment.value.trimStart(), "*/");
comment.printed = true;
}
// [prettierx] --space-in-parens option support (...)
parts.push(addTrailingSpace ? insideSpace : "", ")");
return parts;
}
function printPathNoParens(path, options, print, args) {
const node = path.getValue();
const semi = options.semi ? ";" : "";
// [prettierx] --space-in-parens option support (...)
const insideSpace = options.spaceInParens ? " " : "";
const innerLineBreak = options.spaceInParens ? line : softline;
if (!node) {
return "";
}
if (typeof node === "string") {
return node;
}
for (const printer of [
printLiteral,
printHtmlBinding,
printAngular,
printJsx,
printFlow,
printTypescript,
]) {
const printed = printer(path, options, print);
if (typeof printed !== "undefined") {
return printed;
}
}
/** @type{Doc[]} */
let parts = [];
switch (node.type) {
case "JsExpressionRoot":
return print("node");
case "JsonRoot":
return [print("node"), hardline];
case "File":
// Print @babel/parser's InterpreterDirective here so that
// leading comments on the `Program` node get printed after the hashbang.
if (node.program && node.program.interpreter) {
parts.push(print(["program", "interpreter"]));
}
parts.push(print("program"));
return parts;
case "Program":
return printBlockBody(path, options, print);
// Babel extension.
case "EmptyStatement":
return "";
case "ExpressionStatement":
// Detect Flow and TypeScript directives
if (node.directive) {
return [printDirective(node.expression, options), semi];
}
if (options.parser === "__vue_event_binding") {
const parent = path.getParentNode();
if (
parent.type === "Program" &&
parent.body.length === 1 &&
parent.body[0] === node
) {
return [
print("expression"),
isVueEventBindingExpression(node.expression) ? ";" : "",
];
}
}
// Do not append semicolon after the only JSX element in a program
return [
print("expression"),
isTheOnlyJsxElementInMarkdown(options, path) ? "" : semi,
];
// prettierx: --space-in-parens option support (...)
// Babel non-standard node. Used for Closure-style type casts. See postprocess.js.
case "ParenthesizedExpression": {
const shouldHug =
!hasComment(node.expression) &&
(node.expression.type === "ObjectExpression" ||
node.expression.type === "ArrayExpression");
// [prettierx] --space-in-parens option support (...)
if (shouldHug) {
return ["(", insideSpace, print("expression"), insideSpace, ")"];
}
return group([
"(",
// [prettierx] --space-in-parens option support (...)
indent([innerLineBreak, print("expression")]),
innerLineBreak,
")",
]);
}
case "AssignmentExpression":
return printAssignmentExpression(path, options, print);
case "VariableDeclarator":
return printVariableDeclarator(path, options, print);
case "BinaryExpression":
case "LogicalExpression":
return printBinaryishExpression(path, options, print);
case "AssignmentPattern":
return [print("left"), " = ", print("right")];
case "OptionalMemberExpression":
case "MemberExpression": {
return printMemberExpression(path, options, print);
}
case "MetaProperty":
return [print("meta"), ".", print("property")];
case "BindExpression":
if (node.object) {
parts.push(print("object"));
}
parts.push(
group(
indent([softline, printBindExpressionCallee(path, options, print)])
)
);
return parts;
case "Identifier": {
return [
node.name,
printOptionalToken(path),
printTypeAnnotation(path, options, print),
];
}
case "V8IntrinsicIdentifier":
return ["%", node.name];
case "SpreadElement":
case "SpreadElementPattern":
case "SpreadProperty":
case "SpreadPropertyPattern":
case "RestElement":
return printRestSpread(path, options, print);
case "FunctionDeclaration":
case "FunctionExpression":
return printFunction(path, print, options, args);
case "ArrowFunctionExpression":
return printArrowFunction(path, options, print, args);
case "YieldExpression":
parts.push("yield");
if (node.delegate) {
// [prettierx] --yield-star-spacing option support (...)
if (options.yieldStarSpacing) {
parts.push(" ");
}
parts.push("*");
}
if (node.argument) {
parts.push(" ", print("argument"));
}
return parts;
case "AwaitExpression": {
parts.push("await");
if (node.argument) {
parts.push(" ", print("argument"));
const parent = path.getParentNode();
if (
(isCallExpression(parent) && parent.callee === node) ||
(isMemberExpression(parent) && parent.object === node)
) {
parts = [indent([softline, ...parts]), softline];
const parentAwaitOrBlock = path.findAncestor(
(node) =>
node.type === "AwaitExpression" || node.type === "BlockStatement"
);
if (
!parentAwaitOrBlock ||
parentAwaitOrBlock.type !== "AwaitExpression"
) {
return group(parts);
}
}
}
return parts;
}
case "ExportDefaultDeclaration":
case "ExportNamedDeclaration":
return printExportDeclaration(path, options, print);
case "ExportAllDeclaration":
return printExportAllDeclaration(path, options, print);
case "ImportDeclaration":
return printImportDeclaration(path, options, print);
case "ImportSpecifier":
case "ExportSpecifier":
case "ImportNamespaceSpecifier":
case "ExportNamespaceSpecifier":
case "ImportDefaultSpecifier":
case "ExportDefaultSpecifier":
return printModuleSpecifier(path, options, print);
case "ImportAttribute":
return [print("key"), ": ", print("value")];
case "Import":
return "import";
case "BlockStatement":
case "StaticBlock":
case "ClassBody":
return printBlock(path, options, print);
case "ThrowStatement":
return printThrowStatement(path, options, print);
case "ReturnStatement":
return printReturnStatement(path, options, print);
case "NewExpression":
case "ImportExpression":
case "OptionalCallExpression":
case "CallExpression":
return printCallExpression(path, options, print);
case "ObjectExpression":
case "ObjectPattern":
case "RecordExpression":
return printObject(path, options, print);
// Babel 6
case "ObjectProperty": // Non-standard AST node type.
case "Property":
if (node.method || node.kind === "get" || node.kind === "set") {
return printMethod(path, options, print);
}
return printProperty(path, options, print);
case "ObjectMethod":
return printMethod(path, options, print);
case "Decorator":
return ["@", print("expression")];
case "ArrayExpression":
case "ArrayPattern":
case "TupleExpression":
return printArray(path, options, print);
case "SequenceExpression": {
const parent = path.getParentNode(0);
if (
parent.type === "ExpressionStatement" ||
parent.type === "ForStatement"
) {
// For ExpressionStatements and for-loop heads, which are among
// the few places a SequenceExpression appears unparenthesized, we want
// to indent expressions after the first.
const parts = [];
path.each((expressionPath, index) => {
if (index === 0) {
parts.push(print());
} else {
parts.push(",", indent([line, print()]));
}
}, "expressions");
return group(parts);
}
return group(join([",", line], path.map(print, "expressions")));
}
case "ThisExpression":
return "this";
case "Super":
return "super";
case "Directive":
return [print("value"), semi]; // Babel 6
case "DirectiveLiteral":
return printDirective(node, options);
case "UnaryExpression":
parts.push(node.operator);
// [prettierx] spaceUnaryOps option support (...)
if (
/[a-z]$/.test(node.operator) ||
(options.spaceUnaryOps &&
// [prettierx] no space between `!!`
!(
node.operator === "!" &&
node.argument &&
node.argument.type === "UnaryExpression" &&
node.argument.operator === "!"
))
) {
parts.push(" ");
}
if (hasComment(node.argument)) {
parts.push(
group(["(", indent([softline, print("argument")]), softline, ")"])
);
} else {
parts.push(print("argument"));
}
return parts;
case "UpdateExpression":
parts.push(print("argument"), node.operator);
if (node.prefix) {
parts.reverse();
}
return parts;
case "ConditionalExpression":
return printTernary(path, options, print);
case "VariableDeclaration": {
const printed = path.map(print, "declarations");
// We generally want to terminate all variable declarations with a
// semicolon, except when they in the () part of for loops.
const parentNode = path.getParentNode();
const isParentForLoop =
parentNode.type === "ForStatement" ||
parentNode.type === "ForInStatement" ||
parentNode.type === "ForOfStatement";
const hasValue = node.declarations.some((decl) => decl.init);
let firstVariable;
if (printed.length === 1 && !hasComment(node.declarations[0])) {
firstVariable = printed[0];
} else if (printed.length > 0) {
// Indent first var to comply with eslint one-var rule
firstVariable = indent(printed[0]);
}
parts = [
node.declare ? "declare " : "",
node.kind,
firstVariable ? [" ", firstVariable] : "",
indent(
printed
.slice(1)
.map((p) => [
",",
hasValue && !isParentForLoop ? hardline : line,
p,
])
),
];
if (!(isParentForLoop && parentNode.body !== node)) {
parts.push(semi);
}
return group(parts);
}
case "WithStatement":
// [prettierx] --space-in-parens option support (...)
return group([
"with (",
// [prettierx] --space-in-parens option support (...)
insideSpace,
print("object"),
// [prettierx] --space-in-parens option support (...)
insideSpace,
")",
adjustClause(node.body, print("body")),
]);
case "IfStatement": {
const con = adjustClause(node.consequent, print("consequent"));
const opening = group([
"if (",
// [prettierx] --space-in-parens option support (...)
group([indent([innerLineBreak, print("test")]), innerLineBreak]),
")",
con,
]);
parts.push(opening);
if (node.alternate) {
// [prettierx] --break-before-else option support (...)
const commentOnOwnLine =
hasComment(
node.consequent,
options.breakBeforeElse
? CommentCheckFlags.Trailing
: CommentCheckFlags.Trailing | CommentCheckFlags.Line
) || needsHardlineAfterDanglingComment(node);
// [prettierx] --break-before-else option support (...)
const elseOnSameLine =
node.consequent.type === "BlockStatement" &&
!commentOnOwnLine &&
!options.breakBeforeElse;
parts.push(elseOnSameLine ? " " : hardline);
if (hasComment(node, CommentCheckFlags.Dangling)) {
parts.push(
printDanglingComments(path, options, true),
commentOnOwnLine || options.breakBeforeElse ? hardline : " "
);
}
parts.push(
"else",
group(
adjustClause(
node.alternate,
print("alternate"),
node.alternate.type === "IfStatement"
)
)
);
}
return parts;
}
case "ForStatement": {
const body = adjustClause(node.body, print("body"));
// We want to keep dangling comments above the loop to stay consistent.
// Any comment positioned between the for statement and the parentheses
// is going to be printed before the statement.
const dangling = printDanglingComments(
path,
options,
/* sameLine */ true
);
const printedComments = dangling ? [dangling, softline] : "";
if (!node.init && !node.test && !node.update) {
return [printedComments, group(["for (;;)", body])];
}
return [
printedComments,
group([
// [prettierx] with --space-in-parens option support (...)
"for (",
group([
indent([
// [prettierx] --space-in-parens option support (...)
innerLineBreak,
print("init"),
";",
line,
print("test"),
";",
line,
print("update"),
]),
// [prettierx] --space-in-parens option support (...)
innerLineBreak,
]),
")",
body,
]),
];
}
case "WhileStatement":
return group([
"while (",
// [prettierx] --space-in-parens option support (...)
group([indent([innerLineBreak, print("test")]), innerLineBreak]),
")",
adjustClause(node.body, print("body")),
]);
case "ForInStatement":
// [prettierx] --space-in-parens option support (...)
return group([
"for (",
// [prettierx] --space-in-parens option support (...)
insideSpace,
print("left"),
" in ",
print("right"),
// [prettierx] --space-in-parens option support (...)
insideSpace,
")",
adjustClause(node.body, print("body")),
]);
case "ForOfStatement":
// [prettierx] --space-in-parens option support (...)
return group([
"for",
node.await ? " await" : "",
" (",
// [prettierx] --space-in-parens option support (...)
insideSpace,
print("left"),
" of ",
print("right"),
// [prettierx] --space-in-parens option support (...)
insideSpace,
")",
adjustClause(node.body, print("body")),
]);
case "DoWhileStatement": {
const clause = adjustClause(node.body, print("body"));
const doBody = group(["do", clause]);
parts = [doBody];
if (node.body.type === "BlockStatement") {
parts.push(" ");
} else {
parts.push(hardline);
}
parts.push(
"while (",
// [prettierx] --space-in-parens option support (...)
group([indent([innerLineBreak, print("test")]), innerLineBreak]),
")",
semi
);
return parts;
}
case "DoExpression":
return [node.async ? "async " : "", "do ", print("body")];
case "BreakStatement":
parts.push("break");
if (node.label) {
parts.push(" ", print("label"));
}
parts.push(semi);
return parts;
case "ContinueStatement":
parts.push("continue");
if (node.label) {
parts.push(" ", print("label"));
}
parts.push(semi);
return parts;
case "LabeledStatement":
if (node.body.type === "EmptyStatement") {
return [print("label"), ":;"];
}
return [print("label"), ": ", print("body")];
case "TryStatement":
return [
"try ",
print("block"),
node.handler ? [" ", print("handler")] : "",
node.finalizer ? [" finally ", print("finalizer")] : "",
];
case "CatchClause":
if (node.param) {
const parameterHasComments = hasComment(
node.param,
(comment) =>
!isBlockComment(comment) ||
(comment.leading &&
hasNewline(options.originalText, locEnd(comment))) ||
(comment.trailing &&
hasNewline(options.originalText, locStart(comment), {
backwards: true,
}))
);
const param = print("param");
return [
"catch ",
// [prettierx] --space-in-parens option support (...)
parameterHasComments
? ["(", indent([innerLineBreak, param]), innerLineBreak, ") "]
: ["(", insideSpace, param, insideSpace, ") "],
print("body"),
];
}
return ["catch ", print("body")];
// Note: ignoring n.lexical because it has no printing consequences.
case "SwitchStatement":
return [
// [prettierx] --space-in-parens option support (...)
group([
"switch (",
// [prettierx] --space-in-parens option support (...)
indent([innerLineBreak, print("discriminant")]),
innerLineBreak,
")",
]),
" {",
node.cases.length > 0
? indent([
hardline,
join(
hardline,
path.map((casePath, index, cases) => {
const caseNode = casePath.getValue();
return [
print(),
index !== cases.length - 1 &&
isNextLineEmpty(caseNode, options)
? hardline
: "",
];
}, "cases")
),
])
: "",
hardline,
"}",
];
case "SwitchCase": {
if (node.test) {
parts.push("case ", print("test"), ":");
} else {
parts.push("default:");
}
const consequent = node.consequent.filter(
(node) => node.type !== "EmptyStatement"
);
if (consequent.length > 0) {
const cons = printSwitchCaseConsequent(path, options, print);
parts.push(
consequent.length === 1 && consequent[0].type === "BlockStatement"
? [" ", cons]
: indent([hardline, cons])
);
}
return parts;
}
// JSX extensions below.
case "DebuggerStatement":
return ["debugger", semi];
case "ClassDeclaration":
case "ClassExpression":
return printClass(path, options, print);
case "ClassMethod":
case "ClassPrivateMethod":
case "MethodDefinition":
return printClassMethod(path, options, print);
case "ClassProperty":
case "PropertyDefinition":
case "ClassPrivateProperty":
return printClassProperty(path, options, print);
case "TemplateElement":
return replaceNewlinesWithLiterallines(node.value.raw);
case "TemplateLiteral":
return printTemplateLiteral(path, print, options);
case "TaggedTemplateExpression":
return [print("tag"), print("typeParameters"), print("quasi")];
case "PrivateIdentifier":
return ["#", print("name")];
case "PrivateName":
return ["#", print("id")];
case "InterpreterDirective":
parts.push("#!", node.value, hardline);
if (isNextLineEmpty(node, options)) {
parts.push(hardline);
}
return parts;
case "PipelineBareFunction":
return print("callee");
case "PipelineTopicExpression":
return print("expression");
case "PipelinePrimaryTopicReference":
return "#";
case "ArgumentPlaceholder":
return "?";
case "ModuleExpression": {
parts.push("module {");
const printed = print("body");
if (printed) {
parts.push(indent([hardline, printed]), hardline);
}
parts.push("}");
return parts;
}
default:
/* istanbul ignore next */
throw new Error("unknown type: " + JSON.stringify(node.type));
}
}
function printDirective(node, options) {
const raw = rawText(node);
const rawContent = raw.slice(1, -1);
// Check for the alternate quote, to determine if we're allowed to swap
// the quotes on a DirectiveLiteral.
if (rawContent.includes('"') || rawContent.includes("'")) {
return raw;
}
const enclosingQuote = options.singleQuote ? "'" : '"';
// Directives are exact code unit sequences, which means that you can't
// change the escape sequences they use.
// See https://github.com/prettier/prettier/issues/1555
// and https://tc39.github.io/ecma262/#directive-prologue
return enclosingQuote + rawContent + enclosingQuote;
}
function canAttachComment(node) {
return (
node.type &&
!isBlockComment(node) &&
!isLineComment(node) &&
node.type !== "EmptyStatement" &&
node.type !== "TemplateElement" &&
node.type !== "Import" &&
// `babel-ts` don't have similar node for `class Foo { bar() /* bat */; }`
node.type !== "TSEmptyBodyFunctionExpression"
);
}
module.exports = {
preprocess,
print: genericPrint,
embed,
insertPragma,
massageAstNode: clean,
hasPrettierIgnore(path) {
return hasIgnoreComment(path) || hasJsxIgnoreComment(path);
},
willPrintOwnComments: handleComments.willPrintOwnComments,
canAttachComment,
printComment,
isBlockComment,
handleComments: {
// TODO: Make this as default behavior
avoidAstMutation: true,
ownLine: handleComments.handleOwnLineComment,
endOfLine: handleComments.handleEndOfLineComment,
remaining: handleComments.handleRemainingComment,
},
getCommentChildNodes: handleComments.getCommentChildNodes,
};