prettierx
Version:
prettierX - a less opinionated fork of the Prettier code formatter
252 lines (220 loc) • 5.99 kB
JavaScript
"use strict";
const {
builders: { hardline },
} = require("../../document");
const pathNeedsParens = require("../needs-parens");
const {
getLeftSidePathName,
hasNakedLeftSide,
isJsxNode,
isTheOnlyJsxElementInMarkdown,
hasComment,
CommentCheckFlags,
isNextLineEmpty,
} = require("../utils");
const { shouldPrintParamsWithoutParens } = require("./function");
/**
* @typedef {import("../../document").Doc} Doc
* @typedef {import("../../common/ast-path")} AstPath
*/
function printStatementSequence(path, options, print, property) {
const node = path.getValue();
const parts = [];
const isClassBody = node.type === "ClassBody";
const lastStatement = getLastStatement(node[property]);
path.each((path, index, statements) => {
const node = path.getValue();
// Skip printing EmptyStatement nodes to avoid leaving stray
// semicolons lying around.
if (node.type === "EmptyStatement") {
return;
}
const printed = print();
// in no-semi mode, prepend statement with semicolon if it might break ASI
// don't prepend the only JSX element in a program with semicolon
if (
!options.semi &&
!isClassBody &&
!isTheOnlyJsxElementInMarkdown(options, path) &&
statementNeedsASIProtection(path, options)
) {
if (hasComment(node, CommentCheckFlags.Leading)) {
parts.push(print([], { needsSemi: true }));
} else {
parts.push(";", printed);
}
} else {
parts.push(printed);
}
if (
!options.semi &&
isClassBody &&
isClassProperty(node) &&
// `ClassBody` don't allow `EmptyStatement`,
// so we can use `statements` to get next node
shouldPrintSemicolonAfterClassProperty(node, statements[index + 1])
) {
parts.push(";");
}
if (node !== lastStatement) {
parts.push(hardline);
if (isNextLineEmpty(node, options)) {
parts.push(hardline);
}
}
}, property);
return parts;
}
function getLastStatement(statements) {
for (let i = statements.length - 1; i >= 0; i--) {
const statement = statements[i];
if (statement.type !== "EmptyStatement") {
return statement;
}
}
}
function statementNeedsASIProtection(path, options) {
const node = path.getNode();
if (node.type !== "ExpressionStatement") {
return false;
}
return path.call(
(childPath) => expressionNeedsASIProtection(childPath, options),
"expression"
);
}
function expressionNeedsASIProtection(path, options) {
const node = path.getValue();
switch (node.type) {
case "ParenthesizedExpression":
case "TypeCastExpression":
case "ArrayExpression":
case "ArrayPattern":
case "TemplateLiteral":
case "TemplateElement":
case "RegExpLiteral":
return true;
case "ArrowFunctionExpression": {
if (!shouldPrintParamsWithoutParens(path, options)) {
return true;
}
break;
}
case "UnaryExpression": {
const { prefix, operator } = node;
if (prefix && (operator === "+" || operator === "-")) {
return true;
}
break;
}
case "BindExpression": {
if (!node.object) {
return true;
}
break;
}
case "Literal": {
if (node.regex) {
return true;
}
break;
}
// [prettierx] --space-in-parens option support (...)
// needsParens is false for binaryish expr inside memberexpr, but is parenthesized nevertheless
case "MemberExpression":
if (
!node.computed &&
(node.object.type === "LogicalExpression" ||
node.object.type === "BinaryExpression")
) {
return true;
}
break;
default: {
if (isJsxNode(node)) {
return true;
}
}
}
if (pathNeedsParens(path, options)) {
return true;
}
if (!hasNakedLeftSide(node)) {
return false;
}
return path.call(
(childPath) => expressionNeedsASIProtection(childPath, options),
...getLeftSidePathName(path, node)
);
}
function printBody(path, options, print) {
return printStatementSequence(path, options, print, "body");
}
function printSwitchCaseConsequent(path, options, print) {
return printStatementSequence(path, options, print, "consequent");
}
const isClassProperty = ({ type }) =>
type === "ClassProperty" ||
type === "PropertyDefinition" ||
type === "ClassPrivateProperty";
/**
* @returns {boolean}
*/
function shouldPrintSemicolonAfterClassProperty(node, nextNode) {
const name = node.key && node.key.name;
// this isn't actually possible yet with most parsers available today
// so isn't properly tested yet.
if (
(name === "static" || name === "get" || name === "set") &&
!node.value &&
!node.typeAnnotation
) {
return true;
}
if (!nextNode) {
return false;
}
if (
nextNode.static ||
nextNode.accessibility // TypeScript
) {
return false;
}
if (!nextNode.computed) {
const name = nextNode.key && nextNode.key.name;
if (name === "in" || name === "instanceof") {
return true;
}
}
switch (nextNode.type) {
case "ClassProperty":
case "PropertyDefinition":
case "TSAbstractClassProperty":
return nextNode.computed;
case "MethodDefinition": // Flow
case "TSAbstractMethodDefinition": // TypeScript
case "ClassMethod":
case "ClassPrivateMethod": {
// Babel
const isAsync = nextNode.value ? nextNode.value.async : nextNode.async;
if (isAsync || nextNode.kind === "get" || nextNode.kind === "set") {
return false;
}
const isGenerator = nextNode.value
? nextNode.value.generator
: nextNode.generator;
if (nextNode.computed || isGenerator) {
return true;
}
return false;
}
case "TSIndexSignature":
return true;
}
/* istanbul ignore next */
return false;
}
module.exports = {
printBody,
printSwitchCaseConsequent,
};