diff --git a/src/language-js/print/flow.js b/src/language-js/print/flow.js index adefb649c..092665c34 100644 --- a/src/language-js/print/flow.js +++ b/src/language-js/print/flow.js @@ -1,7 +1,21 @@ "use strict"; +/** @typedef {import("../../document").Doc} Doc */ + const assert = require("assert"); -const { getParentExportDeclaration } = require("../utils"); +const { printDanglingComments } = require("../../main/comments"); +const { printString, printNumber } = require("../../common/util"); +const { + builders: { hardline, softline, group, indent }, +} = require("../../document"); +const { + getParentExportDeclaration, + isFunctionNotation, + isGetterOrSetter, + rawText, + shouldPrintComma, +} = require("../utils"); +const { locStart, locEnd } = require("../loc"); const { printClass } = require("./class"); const { printOpaqueType, @@ -21,10 +35,20 @@ const { printExportDeclaration, printExportAllDeclaration, } = require("./module"); +const { printArrayItems } = require("./array"); +const { printObject } = require("./object"); +const { printPropertyKey } = require("./property"); +const { + printOptionalToken, + printTypeAnnotation, + printRestSpread, +} = require("./misc"); function printFlow(path, options, print) { const node = path.getValue(); const semi = options.semi ? ";" : ""; + /** @type{Doc[]} */ + const parts = []; switch (node.type) { case "DeclareClass": return printFlowDeclaration(path, printClass(path, options, print)); @@ -96,6 +120,249 @@ function printFlow(path, options, print) { return printTypeParameter(path, options, print); case "TypeofTypeAnnotation": return ["typeof ", print("argument")]; + case "ExistsTypeAnnotation": + return "*"; + case "EmptyTypeAnnotation": + return "empty"; + case "MixedTypeAnnotation": + return "mixed"; + case "ArrayTypeAnnotation": + return [print("elementType"), "[]"]; + case "BooleanLiteralTypeAnnotation": + return String(node.value); + case "EnumDeclaration": + return ["enum ", print("id"), " ", print("body")]; + case "EnumBooleanBody": + case "EnumNumberBody": + case "EnumStringBody": + case "EnumSymbolBody": { + if (node.type === "EnumSymbolBody" || node.explicitType) { + let type = null; + switch (node.type) { + case "EnumBooleanBody": + type = "boolean"; + break; + case "EnumNumberBody": + type = "number"; + break; + case "EnumStringBody": + type = "string"; + break; + case "EnumSymbolBody": + type = "symbol"; + break; + } + parts.push("of ", type, " "); + } + if (node.members.length === 0 && !node.hasUnknownMembers) { + parts.push( + group(["{", printDanglingComments(path, options), softline, "}"]) + ); + } else { + const members = + node.members.length > 0 + ? [ + hardline, + printArrayItems(path, options, "members", print), + node.hasUnknownMembers || shouldPrintComma(options) ? "," : "", + ] + : []; + + parts.push( + group([ + "{", + indent([ + ...members, + ...(node.hasUnknownMembers ? [hardline, "..."] : []), + ]), + printDanglingComments(path, options, /* sameIndent */ true), + hardline, + "}", + ]) + ); + } + return parts; + } + case "EnumBooleanMember": + case "EnumNumberMember": + case "EnumStringMember": + return [ + print("id"), + " = ", + typeof node.init === "object" ? print("init") : String(node.init), + ]; + case "EnumDefaultedMember": + return print("id"); + case "FunctionTypeParam": { + const name = node.name + ? print("name") + : path.getParentNode().this === node + ? "this" + : ""; + return [ + name, + printOptionalToken(path), + name ? ": " : "", + print("typeAnnotation"), + ]; + } + + case "InterfaceDeclaration": + case "InterfaceTypeAnnotation": + return printInterface(path, options, print); + case "ClassImplements": + case "InterfaceExtends": + return [print("id"), print("typeParameters")]; + case "NullableTypeAnnotation": + return ["?", print("typeAnnotation")]; + case "Variance": { + const { kind } = node; + assert.ok(kind === "plus" || kind === "minus"); + return kind === "plus" ? "+" : "-"; + } + case "ObjectTypeCallProperty": + if (node.static) { + parts.push("static "); + } + + parts.push(print("value")); + + return parts; + case "ObjectTypeIndexer": { + return [ + node.variance ? print("variance") : "", + "[", + print("id"), + node.id ? ": " : "", + print("key"), + "]: ", + print("value"), + ]; + } + case "ObjectTypeProperty": { + let modifier = ""; + + if (node.proto) { + modifier = "proto "; + } else if (node.static) { + modifier = "static "; + } + + return [ + modifier, + isGetterOrSetter(node) ? node.kind + " " : "", + node.variance ? print("variance") : "", + printPropertyKey(path, options, print), + printOptionalToken(path), + isFunctionNotation(node) ? "" : ": ", + print("value"), + ]; + } + case "ObjectTypeAnnotation": + return printObject(path, options, print); + case "ObjectTypeInternalSlot": + return [ + node.static ? "static " : "", + "[[", + print("id"), + "]]", + printOptionalToken(path), + node.method ? "" : ": ", + print("value"), + ]; + // Same as `RestElement` + case "ObjectTypeSpreadProperty": + return printRestSpread(path, options, print); + case "QualifiedTypeIdentifier": + return [print("qualification"), ".", print("id")]; + case "StringLiteralTypeAnnotation": + return printString(rawText(node), options); + case "NumberLiteralTypeAnnotation": + assert.strictEqual(typeof node.value, "number"); + // fall through + case "BigIntLiteralTypeAnnotation": + if (node.extra) { + return printNumber(node.extra.raw); + } + return printNumber(node.raw); + case "TypeCastExpression": { + return [ + "(", + print("expression"), + printTypeAnnotation(path, options, print), + ")", + ]; + } + + case "TypeParameterDeclaration": + case "TypeParameterInstantiation": { + const printed = printTypeParameters(path, options, print, "params"); + + if (options.parser === "flow") { + const start = locStart(node); + const end = locEnd(node); + const commentStartIndex = options.originalText.lastIndexOf("/*", start); + const commentEndIndex = options.originalText.indexOf("*/", end); + if (commentStartIndex !== -1 && commentEndIndex !== -1) { + const comment = options.originalText + .slice(commentStartIndex + 2, commentEndIndex) + .trim(); + if ( + comment.startsWith("::") && + !comment.includes("/*") && + !comment.includes("*/") + ) { + return ["/*:: ", printed, " */"]; + } + } + } + + return printed; + } + + case "InferredPredicate": + return "%checks"; + // Unhandled types below. If encountered, nodes of these types should + // be either left alone or desugared into AST types that are fully + // supported by the pretty-printer. + case "DeclaredPredicate": + return ["%checks(", print("value"), ")"]; + case "AnyTypeAnnotation": + return "any"; + case "BooleanTypeAnnotation": + return "boolean"; + case "BigIntTypeAnnotation": + return "bigint"; + case "NullLiteralTypeAnnotation": + return "null"; + case "NumberTypeAnnotation": + return "number"; + case "SymbolTypeAnnotation": + return "symbol"; + case "StringTypeAnnotation": + return "string"; + case "VoidTypeAnnotation": + return "void"; + case "ThisTypeAnnotation": + return "this"; + // These types are unprintable because they serve as abstract + // supertypes for other (printable) types. + case "Node": + case "Printable": + case "SourceLocation": + case "Position": + case "Statement": + case "Function": + case "Pattern": + case "Expression": + case "Declaration": + case "Specifier": + case "NamedSpecifier": + case "Comment": + case "MemberTypeAnnotation": // Flow + case "Type": + /* istanbul ignore next */ + throw new Error("unprintable type: " + JSON.stringify(node.type)); } } diff --git a/src/language-js/print/misc.js b/src/language-js/print/misc.js index 417f55188..c4afd15a1 100644 --- a/src/language-js/print/misc.js +++ b/src/language-js/print/misc.js @@ -86,11 +86,16 @@ function adjustClause(node, clause, forceSpace) { return indent([line, clause]); } +function printRestSpread(path, options, print) { + return ["...", print("argument"), printTypeAnnotation(path, options, print)]; +} + module.exports = { printOptionalToken, printFunctionTypeParameters, printBindExpressionCallee, printTypeScriptModifiers, printTypeAnnotation, + printRestSpread, adjustClause, }; diff --git a/src/language-js/printer-estree.js b/src/language-js/printer-estree.js index b2fb41126..abd146f3e 100644 --- a/src/language-js/printer-estree.js +++ b/src/language-js/printer-estree.js @@ -2,12 +2,9 @@ /** @typedef {import("../document").Doc} Doc */ -/** @type {import("assert")} */ -const assert = require("assert"); - // TODO(azz): anything that imports from main shouldn't be in a `language-*` dir. const { printDanglingComments } = require("../main/comments"); -const { hasNewline, printString, printNumber } = require("../common/util"); +const { hasNewline } = require("../common/util"); const { builders: { join, line, hardline, softline, group, indent }, utils: { replaceNewlinesWithLiterallines }, @@ -23,15 +20,12 @@ const { hasFlowShorthandAnnotationComment, hasComment, CommentCheckFlags, - isFunctionNotation, - isGetterOrSetter, isTheOnlyJsxElementInMarkdown, isBlockComment, isLineComment, isNextLineEmpty, needsHardlineAfterDanglingComment, rawText, - shouldPrintComma, hasIgnoreComment, isCallExpression, isMemberExpression, @@ -51,6 +45,7 @@ const { printBindExpressionCallee, printTypeAnnotation, adjustClause, + printRestSpread, } = require("./print/misc"); const { printImportDeclaration, @@ -60,15 +55,14 @@ const { } = require("./print/module"); const { printTernary } = require("./print/ternary"); const { printTemplateLiteral } = require("./print/template-literal"); -const { printArray, printArrayItems } = require("./print/array"); +const { printArray } = require("./print/array"); const { printObject } = require("./print/object"); const { printClass, printClassMethod, printClassProperty, } = require("./print/class"); -const { printTypeParameters } = require("./print/type-parameters"); -const { printPropertyKey, printProperty } = require("./print/property"); +const { printProperty } = require("./print/property"); const { printFunctionDeclaration, printArrowFunctionExpression, @@ -77,7 +71,6 @@ const { printThrowStatement, } = require("./print/function"); const { printCallExpression } = require("./print/call-expression"); -const { printInterface } = require("./print/interface"); const { printVariableDeclarator, printAssignmentExpression, @@ -270,12 +263,7 @@ function printPathNoParens(path, options, print, args) { case "SpreadProperty": case "SpreadPropertyPattern": case "RestElement": - case "ObjectTypeSpreadProperty": - return [ - "...", - print("argument"), - printTypeAnnotation(path, options, print), - ]; + return printRestSpread(path, options, print); case "FunctionDeclaration": case "FunctionExpression": { let expandArg = false; @@ -355,20 +343,9 @@ function printPathNoParens(path, options, print, args) { case "OptionalCallExpression": case "CallExpression": return printCallExpression(path, options, print); - case "ObjectTypeInternalSlot": - return [ - node.static ? "static " : "", - "[[", - print("id"), - "]]", - printOptionalToken(path), - node.method ? "" : ": ", - print("value"), - ]; case "ObjectExpression": case "ObjectPattern": - case "ObjectTypeAnnotation": case "RecordExpression": return printObject(path, options, print); // Babel 6 @@ -755,236 +732,6 @@ function printPathNoParens(path, options, print, args) { return printTemplateLiteral(path, print, options); case "TaggedTemplateExpression": return [print("tag"), print("typeParameters"), print("quasi")]; - // These types are unprintable because they serve as abstract - // supertypes for other (printable) types. - case "Node": - case "Printable": - case "SourceLocation": - case "Position": - case "Statement": - case "Function": - case "Pattern": - case "Expression": - case "Declaration": - case "Specifier": - case "NamedSpecifier": - case "Comment": - case "MemberTypeAnnotation": // Flow - case "Type": - /* istanbul ignore next */ - throw new Error("unprintable type: " + JSON.stringify(node.type)); - case "ExistsTypeAnnotation": - return "*"; - case "EmptyTypeAnnotation": - return "empty"; - case "MixedTypeAnnotation": - return "mixed"; - case "ArrayTypeAnnotation": - return [print("elementType"), "[]"]; - case "BooleanLiteralTypeAnnotation": - return String(node.value); - - case "EnumDeclaration": - return ["enum ", print("id"), " ", print("body")]; - case "EnumBooleanBody": - case "EnumNumberBody": - case "EnumStringBody": - case "EnumSymbolBody": { - if (node.type === "EnumSymbolBody" || node.explicitType) { - let type = null; - switch (node.type) { - case "EnumBooleanBody": - type = "boolean"; - break; - case "EnumNumberBody": - type = "number"; - break; - case "EnumStringBody": - type = "string"; - break; - case "EnumSymbolBody": - type = "symbol"; - break; - } - parts.push("of ", type, " "); - } - if (node.members.length === 0 && !node.hasUnknownMembers) { - parts.push( - group(["{", printDanglingComments(path, options), softline, "}"]) - ); - } else { - const members = - node.members.length > 0 - ? [ - hardline, - printArrayItems(path, options, "members", print), - node.hasUnknownMembers || shouldPrintComma(options) ? "," : "", - ] - : []; - - parts.push( - group([ - "{", - indent([ - ...members, - ...(node.hasUnknownMembers ? [hardline, "..."] : []), - ]), - printDanglingComments(path, options, /* sameIndent */ true), - hardline, - "}", - ]) - ); - } - return parts; - } - case "EnumBooleanMember": - case "EnumNumberMember": - case "EnumStringMember": - return [ - print("id"), - " = ", - typeof node.init === "object" ? print("init") : String(node.init), - ]; - case "EnumDefaultedMember": - return print("id"); - case "FunctionTypeParam": { - const name = node.name - ? print("name") - : path.getParentNode().this === node - ? "this" - : ""; - return [ - name, - printOptionalToken(path), - name ? ": " : "", - print("typeAnnotation"), - ]; - } - - case "InterfaceDeclaration": - case "InterfaceTypeAnnotation": - return printInterface(path, options, print); - case "ClassImplements": - case "InterfaceExtends": - return [print("id"), print("typeParameters")]; - case "NullableTypeAnnotation": - return ["?", print("typeAnnotation")]; - case "Variance": { - const { kind } = node; - assert.ok(kind === "plus" || kind === "minus"); - return kind === "plus" ? "+" : "-"; - } - case "ObjectTypeCallProperty": - if (node.static) { - parts.push("static "); - } - - parts.push(print("value")); - - return parts; - case "ObjectTypeIndexer": { - return [ - node.variance ? print("variance") : "", - "[", - print("id"), - node.id ? ": " : "", - print("key"), - "]: ", - print("value"), - ]; - } - case "ObjectTypeProperty": { - let modifier = ""; - - if (node.proto) { - modifier = "proto "; - } else if (node.static) { - modifier = "static "; - } - - return [ - modifier, - isGetterOrSetter(node) ? node.kind + " " : "", - node.variance ? print("variance") : "", - printPropertyKey(path, options, print), - printOptionalToken(path), - isFunctionNotation(node) ? "" : ": ", - print("value"), - ]; - } - case "QualifiedTypeIdentifier": - return [print("qualification"), ".", print("id")]; - case "StringLiteralTypeAnnotation": - return printString(rawText(node), options); - case "NumberLiteralTypeAnnotation": - assert.strictEqual(typeof node.value, "number"); - // fall through - case "BigIntLiteralTypeAnnotation": - if (node.extra) { - return printNumber(node.extra.raw); - } - return printNumber(node.raw); - case "TypeCastExpression": { - return [ - "(", - print("expression"), - printTypeAnnotation(path, options, print), - ")", - ]; - } - - case "TypeParameterDeclaration": - case "TypeParameterInstantiation": { - const printed = printTypeParameters(path, options, print, "params"); - - if (options.parser === "flow") { - const start = locStart(node); - const end = locEnd(node); - const commentStartIndex = options.originalText.lastIndexOf("/*", start); - const commentEndIndex = options.originalText.indexOf("*/", end); - if (commentStartIndex !== -1 && commentEndIndex !== -1) { - const comment = options.originalText - .slice(commentStartIndex + 2, commentEndIndex) - .trim(); - if ( - comment.startsWith("::") && - !comment.includes("/*") && - !comment.includes("*/") - ) { - return ["/*:: ", printed, " */"]; - } - } - } - - return printed; - } - - case "InferredPredicate": - return "%checks"; - // Unhandled types below. If encountered, nodes of these types should - // be either left alone or desugared into AST types that are fully - // supported by the pretty-printer. - case "DeclaredPredicate": - return ["%checks(", print("value"), ")"]; - case "AnyTypeAnnotation": - return "any"; - case "BooleanTypeAnnotation": - return "boolean"; - case "BigIntTypeAnnotation": - return "bigint"; - case "NullLiteralTypeAnnotation": - return "null"; - case "NumberTypeAnnotation": - return "number"; - case "SymbolTypeAnnotation": - return "symbol"; - case "StringTypeAnnotation": - return "string"; - case "VoidTypeAnnotation": - return "void"; - case "ThisTypeAnnotation": - return "this"; - case "PrivateIdentifier": return ["#", print("name")]; case "PrivateName":