diff --git a/src/language-js/print/block.js b/src/language-js/print/block.js index e2f4bee63..c5b7e2d4a 100644 --- a/src/language-js/print/block.js +++ b/src/language-js/print/block.js @@ -6,9 +6,7 @@ import { CommentCheckFlags, isNextLineEmpty, } from "../utils/index.js"; -import { printHardlineAfterHeritage } from "./class.js"; - -import { printBody } from "./statement.js"; +import { printStatementSequence } from "./statement.js"; /** @typedef {import("../../document/builders.js").Doc} Doc */ @@ -20,11 +18,6 @@ function printBlock(path, options, print) { parts.push("static "); } - if (node.type === "ClassBody" && isNonEmptyArray(node.body)) { - const { parent } = path; - parts.push(printHardlineAfterHeritage(parent)); - } - parts.push("{"); const printed = printBlockBody(path, options, print); if (printed) { @@ -47,8 +40,7 @@ function printBlock(path, options, print) { (parent.type === "CatchClause" && !parentParent.finalizer) || parent.type === "TSModuleDeclaration" || parent.type === "TSDeclareFunction" || - node.type === "StaticBlock" || - node.type === "ClassBody" + node.type === "StaticBlock" ) ) { parts.push(hardline); @@ -86,7 +78,7 @@ function printBlockBody(path, options, print) { } if (nodeHasBody) { - parts.push(printBody(path, options, print)); + parts.push(printStatementSequence(path, options, print)); } if (nodeHasComment) { diff --git a/src/language-js/print/class.js b/src/language-js/print/class.js index 764497a42..ab82586f4 100644 --- a/src/language-js/print/class.js +++ b/src/language-js/print/class.js @@ -9,7 +9,12 @@ import { indent, ifBreak, } from "../../document/builders.js"; -import { hasComment, CommentCheckFlags } from "../utils/index.js"; +import { + hasComment, + CommentCheckFlags, + createTypeCheckFunction, + isNextLineEmpty, +} from "../utils/index.js"; import { getTypeParametersGroupId } from "./type-parameters.js"; import { printMethod } from "./function.js"; import { @@ -26,6 +31,16 @@ import { printClassMemberDecorators } from "./decorators.js"; * @typedef {import("../../document/builders.js").Doc} Doc */ +const isClassProperty = createTypeCheckFunction([ + "ClassProperty", + "PropertyDefinition", + "ClassPrivateProperty", + "ClassAccessorProperty", + "AccessorProperty", + "TSAbstractPropertyDefinition", + "TSAbstractAccessorProperty", +]); + /* - `ClassDeclaration` - `ClassExpression` @@ -250,9 +265,127 @@ function printClassProperty(path, options, print) { ]; } +function printClassBody(path, options, print) { + const { node } = path; + const parts = []; + + path.each(({ node, next, isLast }) => { + parts.push(print()); + + if ( + !options.semi && + isClassProperty(node) && + shouldPrintSemicolonAfterClassProperty(node, next) + ) { + parts.push(";"); + } + + if (!isLast) { + parts.push(hardline); + + if (isNextLineEmpty(node, options)) { + parts.push(hardline); + } + } + }, "body"); + + if (hasComment(node, CommentCheckFlags.Dangling)) { + parts.push(printDanglingComments(path, options, /* sameIndent */ true)); + } + + return [ + isNonEmptyArray(node.body) ? printHardlineAfterHeritage(path.parent) : "", + "{", + parts.length > 0 ? [indent([hardline, parts]), hardline] : "", + "}", + ]; +} + +/** + * @returns {boolean} + */ +function shouldPrintSemicolonAfterClassProperty(node, nextNode) { + const { type, name } = node.key; + if ( + !node.computed && + type === "Identifier" && + (name === "static" || + name === "get" || + name === "set" || + // TODO: Remove this https://github.com/microsoft/TypeScript/issues/51707 is fixed + name === "accessor") && + !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?.name; + if (name === "in" || name === "instanceof") { + return true; + } + } + + // Flow variance sigil +/- requires semi if there's no + // "declare" or "static" keyword before it. + if ( + isClassProperty(nextNode) && + nextNode.variance && + !nextNode.static && + !nextNode.declare + ) { + return true; + } + + switch (nextNode.type) { + case "ClassProperty": + case "PropertyDefinition": + case "TSAbstractPropertyDefinition": + return nextNode.computed; + case "MethodDefinition": + case "TSAbstractMethodDefinition": + 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; + } + + /* c8 ignore next */ + return false; +} + export { printClass, printClassMethod, printClassProperty, printHardlineAfterHeritage, + printClassBody, }; diff --git a/src/language-js/print/statement.js b/src/language-js/print/statement.js index f6ea3d4ac..0aae47d0f 100644 --- a/src/language-js/print/statement.js +++ b/src/language-js/print/statement.js @@ -8,7 +8,6 @@ import { hasComment, CommentCheckFlags, isNextLineEmpty, - createTypeCheckFunction, } from "../utils/index.js"; import { shouldPrintParamsWithoutParens } from "./function.js"; @@ -17,13 +16,13 @@ import { shouldPrintParamsWithoutParens } from "./function.js"; * @typedef {import("../../common/ast-path.js")} AstPath */ -function printStatementSequence(path, options, print, property) { +function printStatementSequence(path, options, print) { const { node } = path; const parts = []; - const isClassBody = node.type === "ClassBody"; + const property = node.type === "SwitchCase" ? "consequent" : "body"; const lastStatement = getLastStatement(node[property]); - path.each(({ node, next }) => { + path.each(({ node }) => { // Skip printing EmptyStatement nodes to avoid leaving stray // semicolons lying around. if (node.type === "EmptyStatement") { @@ -36,7 +35,6 @@ function printStatementSequence(path, options, print, property) { // don't prepend the only JSX element in a program with semicolon if ( !options.semi && - !isClassBody && !isTheOnlyJsxElementInMarkdown(options, path) && statementNeedsASIProtection(path, options) ) { @@ -49,17 +47,6 @@ function printStatementSequence(path, options, print, property) { 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, next) - ) { - parts.push(";"); - } - if (node !== lastStatement) { parts.push(hardline); @@ -150,102 +137,4 @@ function expressionNeedsASIProtection(path, options) { ); } -function printBody(path, options, print) { - return printStatementSequence(path, options, print, "body"); -} - -function printSwitchCaseConsequent(path, options, print) { - return printStatementSequence(path, options, print, "consequent"); -} - -const isClassProperty = createTypeCheckFunction([ - "ClassProperty", - "PropertyDefinition", - "ClassPrivateProperty", - "ClassAccessorProperty", - "AccessorProperty", - "TSAbstractPropertyDefinition", - "TSAbstractAccessorProperty", -]); -/** - * @returns {boolean} - */ -function shouldPrintSemicolonAfterClassProperty(node, nextNode) { - const { type, name } = node.key; - if ( - !node.computed && - type === "Identifier" && - (name === "static" || - name === "get" || - name === "set" || - // TODO: Remove this https://github.com/microsoft/TypeScript/issues/51707 is fixed - name === "accessor") && - !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?.name; - if (name === "in" || name === "instanceof") { - return true; - } - } - - // Flow variance sigil +/- requires semi if there's no - // "declare" or "static" keyword before it. - if ( - isClassProperty(nextNode) && - nextNode.variance && - !nextNode.static && - !nextNode.declare - ) { - return true; - } - - switch (nextNode.type) { - case "ClassProperty": - case "PropertyDefinition": - case "TSAbstractPropertyDefinition": - 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; - } - - /* c8 ignore next */ - return false; -} - -export { printBody, printSwitchCaseConsequent }; +export { printStatementSequence }; diff --git a/src/language-js/printer-estree.js b/src/language-js/printer-estree.js index c135af94d..da598d746 100644 --- a/src/language-js/printer-estree.js +++ b/src/language-js/printer-estree.js @@ -69,6 +69,7 @@ import { printClass, printClassMethod, printClassProperty, + printClassBody, } from "./print/class.js"; import { printProperty } from "./print/property.js"; import { @@ -84,7 +85,7 @@ import { printAssignmentExpression, } from "./print/assignment.js"; import { printBinaryishExpression } from "./print/binaryish.js"; -import { printSwitchCaseConsequent } from "./print/statement.js"; +import { printStatementSequence } from "./print/statement.js"; import { printMemberExpression } from "./print/member.js"; import { printBlock, printBlockBody } from "./print/block.js"; import { printLiteral } from "./print/literal.js"; @@ -355,8 +356,9 @@ function printPathNoParens(path, options, print, args) { return "import"; case "BlockStatement": case "StaticBlock": - case "ClassBody": return printBlock(path, options, print); + case "ClassBody": + return printClassBody(path, options, print); case "ThrowStatement": return printThrowStatement(path, options, print); case "ReturnStatement": @@ -715,7 +717,7 @@ function printPathNoParens(path, options, print, args) { ); if (consequent.length > 0) { - const cons = printSwitchCaseConsequent(path, options, print); + const cons = printStatementSequence(path, options, print); parts.push( consequent.length === 1 && consequent[0].type === "BlockStatement"