diff --git a/src/language-js/print/class-body.js b/src/language-js/print/class-body.js index c653dff7b..db66ee7c0 100644 --- a/src/language-js/print/class-body.js +++ b/src/language-js/print/class-body.js @@ -10,6 +10,7 @@ import { printDanglingComments } from "../../main/comments/print.js"; import hasNewline from "../../utilities/has-newline.js"; import hasNewlineInRange from "../../utilities/has-newline-in-range.js"; import { locEnd, locStart } from "../loc.js"; +import { iterateClassMembersPath } from "../utilities/class-members.js"; import { CommentCheckFlags, getComments, @@ -40,7 +41,7 @@ function printClassBody(path, options, print) { isFlowTypeAnnotation && node.exact ? ["{|", "|}"] : "{}"; let firstMember; - iterateClassMembers(path, ({ node, next, isLast }) => { + iterateClassMembersPath(path, ({ node, next, isLast }) => { firstMember ??= node; parts.push(print()); @@ -172,59 +173,6 @@ function isClassBody(path) { ); } -function iterateClassMembers(path, iteratee) { - const { node } = path; - if (node.type === "ClassBody" || node.type === "TSInterfaceBody") { - path.each(iteratee, "body"); - return; - } - - if (node.type === "TSTypeLiteral") { - path.each(iteratee, "members"); - return; - } - - if (node.type === "RecordDeclarationBody") { - path.each(iteratee, "elements"); - return; - } - - if (node.type === "ObjectTypeAnnotation") { - // Unfortunately, things grouped together in the ast can be - // interleaved in the source code. So we need to reorder them before - // printing them. - const children = [ - "properties", - "indexers", - "callProperties", - "internalSlots", - ] - .flatMap((field) => - path.map( - ({ node, index }) => ({ - node, - loc: locStart(node), - selector: [field, index], - }), - field, - ), - ) - .sort((a, b) => a.loc - b.loc); - - for (const [index, { node, selector }] of children.entries()) { - path.call( - () => - iteratee({ - node, - next: children[index + 1]?.node, - isLast: index === children.length - 1, - }), - ...selector, - ); - } - } -} - function printClassMemberSemicolon(path, options) { const { parent } = path; diff --git a/src/language-js/print/class.js b/src/language-js/print/class.js index f7a47665a..612fde284 100644 --- a/src/language-js/print/class.js +++ b/src/language-js/print/class.js @@ -14,6 +14,7 @@ import { } from "../../main/comments/print.js"; import createGroupIdMapper from "../../utilities/create-group-id-mapper.js"; import isNonEmptyArray from "../../utilities/is-non-empty-array.js"; +import { isNonEmptyClassBody } from "../utilities/class-members.js"; import { CommentCheckFlags, hasComment } from "../utilities/comments.js"; import { createTypeCheckFunction } from "../utilities/create-type-check-function.js"; import { isMemberExpression } from "../utilities/node-types.js"; @@ -143,16 +144,6 @@ function printClass(path, options, print) { return parts; } -function isNonEmptyClassBody(node) { - return node.type === "ObjectTypeAnnotation" - ? ["properties", "indexers", "callProperties", "internalSlots"].some( - (property) => isNonEmptyArray(node[property]), - ) - : node.type === "RecordDeclarationBody" - ? isNonEmptyArray(node.elements) - : isNonEmptyArray(node.body); -} - function hasMultipleHeritage(node) { let count = node.superClass ? 1 : 0; for (const listName of ["extends", "mixins", "implements"]) { diff --git a/src/language-js/utilities/class-members.js b/src/language-js/utilities/class-members.js new file mode 100644 index 000000000..f402b1cc7 --- /dev/null +++ b/src/language-js/utilities/class-members.js @@ -0,0 +1,86 @@ +import isNonEmptyArray from "../../utilities/is-non-empty-array.js"; +import { locStart } from "../loc.js"; + +/** +@import AstPath from "../../common/ast-path.js"; +@import {Node, NodeMap} from "../types/estree.js"; +*/ + +const flowObjectTypeAnnotationChildrenProperties = [ + "properties", + "indexers", + "callProperties", + "internalSlots", +]; + +function iterateClassMembersPath(path, iteratee) { + const { node } = path; + if (node.type === "ClassBody" || node.type === "TSInterfaceBody") { + path.each(iteratee, "body"); + return; + } + + if (node.type === "TSTypeLiteral") { + path.each(iteratee, "members"); + return; + } + + if (node.type === "RecordDeclarationBody") { + path.each(iteratee, "elements"); + return; + } + + if (node.type === "ObjectTypeAnnotation") { + // Unfortunately, things grouped together in the ast can be + // interleaved in the source code. So we need to reorder them before + // printing them. + const children = flowObjectTypeAnnotationChildrenProperties + .flatMap((field) => + path.map( + ({ node, index }) => ({ + node, + loc: locStart(node), + selector: [field, index], + }), + field, + ), + ) + .sort((a, b) => a.loc - b.loc); + + for (const [index, { node, selector }] of children.entries()) { + path.call( + () => + iteratee({ + node, + next: children[index + 1]?.node, + isLast: index === children.length - 1, + }), + ...selector, + ); + } + } +} + +/** +@param { + | NodeMap["ClassBody"] + | NodeMap["TSInterfaceBody"] + | NodeMap["RecordDeclarationBody"] + | NodeMap["ObjectTypeAnnotation"] +} node +@returns {boolean} +*/ +function isNonEmptyClassBody(node) { + if (node.type === "ObjectTypeAnnotation") { + return flowObjectTypeAnnotationChildrenProperties.some((property) => + isNonEmptyArray(node[property]), + ); + } + + const members = + node.type === "RecordDeclarationBody" ? node.elements : node.body; + + return isNonEmptyArray(members); +} + +export { isNonEmptyClassBody, iterateClassMembersPath };