From 320fe884621fc7d859f5f73ca66ea5f41a5bd4df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hu=C3=A1ng=20J=C3=B9nli=C3=A0ng?= Date: Mon, 13 Jun 2022 09:51:14 -0400 Subject: [PATCH] Improve `@babel/generator` typings (#14644) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * generator * refactor: merge {start,end}Terminatorless to printTerminatorless * inline buildYieldAwait * inline ExportDeclaration * also export Pos * let getPossibleRaw return string | void * Apply suggestions from code review Co-authored-by: Nicolò Ribaudo Co-authored-by: liuxingbaoyu <30521560+liuxingbaoyu@users.noreply.github.com> * address review comments * do not export internal printer method * simplify needsWhitespace Co-authored-by: Nicolò Ribaudo Co-authored-by: liuxingbaoyu <30521560+liuxingbaoyu@users.noreply.github.com> --- packages/babel-generator/src/buffer.ts | 23 +- .../babel-generator/src/generators/base.ts | 2 +- .../babel-generator/src/generators/classes.ts | 15 +- .../src/generators/expressions.ts | 44 ++-- .../babel-generator/src/generators/flow.ts | 31 ++- .../babel-generator/src/generators/jsx.ts | 2 +- .../babel-generator/src/generators/methods.ts | 79 ++++++- .../babel-generator/src/generators/modules.ts | 42 ++-- .../src/generators/statements.ts | 104 +++++---- .../src/generators/template-literals.ts | 2 +- .../babel-generator/src/generators/types.ts | 6 +- .../src/generators/typescript.ts | 68 +++--- packages/babel-generator/src/index.ts | 30 +-- packages/babel-generator/src/node/index.ts | 64 ++++-- .../babel-generator/src/node/parentheses.ts | 120 +++++++---- .../babel-generator/src/node/whitespace.ts | 34 ++- packages/babel-generator/src/printer.ts | 198 +++++++++++------- 17 files changed, 556 insertions(+), 308 deletions(-) diff --git a/packages/babel-generator/src/buffer.ts b/packages/babel-generator/src/buffer.ts index 9070b2997187..f097e4fe2070 100644 --- a/packages/babel-generator/src/buffer.ts +++ b/packages/babel-generator/src/buffer.ts @@ -1,12 +1,11 @@ import type SourceMap from "./source-map"; -import type * as t from "@babel/types"; import * as charcodes from "charcodes"; -type Pos = { +export type Pos = { line: number; column: number; }; -type Loc = { +export type Loc = { start?: Pos; end?: Pos; identifierName?: string; @@ -63,14 +62,18 @@ export default class Buffer { // Encoding the sourcemap is moderately CPU expensive. get map() { - return (result.map = map ? map.get() : null); + const resultMap = map ? map.get() : null; + result.map = resultMap; + return resultMap; }, set map(value) { Object.defineProperty(result, "map", { value, writable: true }); }, // Retrieving the raw mappings is very memory intensive. get rawMappings() { - return (result.rawMappings = map?.getRawMappings()); + const mappings = map?.getRawMappings(); + result.rawMappings = mappings; + return mappings; }, set rawMappings(value) { Object.defineProperty(result, "rawMappings", { value, writable: true }); @@ -241,7 +244,7 @@ export default class Buffer { * With this line, there will be one mapping range over "mod" and another * over "();", where previously it would have been a single mapping. */ - exactSource(loc: any, cb: () => void) { + exactSource(loc: Loc | undefined, cb: () => void) { this.source("start", loc); cb(); @@ -262,7 +265,7 @@ export default class Buffer { * will be given this position in the sourcemap. */ - source(prop: string, loc: t.SourceLocation): void { + source(prop: "start" | "end", loc: Loc | undefined): void { if (prop && !loc) return; // Since this is called extremely often, we re-use the same _sourcePosition @@ -274,7 +277,7 @@ export default class Buffer { * Call a callback with a specific source location and restore on completion. */ - withSource(prop: string, loc: t.SourceLocation, cb: () => void): void { + withSource(prop: "start" | "end", loc: Loc, cb: () => void): void { if (!this._map) return cb(); // Use the call stack to manage a stack of "source location" data because @@ -309,14 +312,14 @@ export default class Buffer { * sourcemap output, so that certain printers can be sure that the * "end" location that they set is actually treated as the end position. */ - _disallowPop(prop: string, loc: t.SourceLocation) { + _disallowPop(prop: "start" | "end", loc: Loc) { if (prop && !loc) return; this._disallowedPop = this._normalizePosition(prop, loc, SourcePos()); } _normalizePosition( - prop: string, + prop: "start" | "end", loc: Loc | undefined | null, targetObj: SourcePos, ) { diff --git a/packages/babel-generator/src/generators/base.ts b/packages/babel-generator/src/generators/base.ts index 30253db2e5f7..ae53b83d0fc1 100644 --- a/packages/babel-generator/src/generators/base.ts +++ b/packages/babel-generator/src/generators/base.ts @@ -58,7 +58,7 @@ const unescapedDoubleQuoteRE = /(?:^|[^\\])(?:\\\\)*"/; export function DirectiveLiteral(this: Printer, node: t.DirectiveLiteral) { const raw = this.getPossibleRaw(node); - if (!this.format.minified && raw != null) { + if (!this.format.minified && raw !== undefined) { this.token(raw); return; } diff --git a/packages/babel-generator/src/generators/classes.ts b/packages/babel-generator/src/generators/classes.ts index 77eea7ea2664..0212bd9d4737 100644 --- a/packages/babel-generator/src/generators/classes.ts +++ b/packages/babel-generator/src/generators/classes.ts @@ -9,7 +9,7 @@ import * as charCodes from "charcodes"; export function ClassDeclaration( this: Printer, node: t.ClassDeclaration, - parent: any, + parent: t.Node, ) { if ( !this.format.decoratorsBeforeExport || @@ -86,7 +86,7 @@ export function ClassProperty(this: Printer, node: t.ClassProperty) { // between member modifiers and the property key. this.source("end", node.key.loc); - this.tsPrintClassMemberModifiers(node, /* isField */ true); + this.tsPrintClassMemberModifiers(node); if (node.computed) { this.token("["); @@ -125,7 +125,8 @@ export function ClassAccessorProperty( // between member modifiers and the property key. this.source("end", node.key.loc); - this.tsPrintClassMemberModifiers(node, /* isField */ true); + // TS does not support class accessor property yet + this.tsPrintClassMemberModifiers(node); this.word("accessor"); this.printInnerComments(node); @@ -136,6 +137,7 @@ export function ClassAccessorProperty( this.print(node.key, node); this.token("]"); } else { + // Todo: Flow does not support class accessor property yet. this._variance(node); this.print(node.key, node); } @@ -190,12 +192,15 @@ export function ClassPrivateMethod(this: Printer, node: t.ClassPrivateMethod) { this.print(node.body, node); } -export function _classMethodHead(this: Printer, node) { +export function _classMethodHead( + this: Printer, + node: t.ClassMethod | t.ClassPrivateMethod | t.TSDeclareMethod, +) { this.printJoin(node.decorators, node); // catch up to method key, avoid line break // between member modifiers/method heads and the method key. this.source("end", node.key.loc); - this.tsPrintClassMemberModifiers(node, /* isField */ false); + this.tsPrintClassMemberModifiers(node); this._methodHead(node); } diff --git a/packages/babel-generator/src/generators/expressions.ts b/packages/babel-generator/src/generators/expressions.ts index 311346cd123c..c79bf17c9ad3 100644 --- a/packages/babel-generator/src/generators/expressions.ts +++ b/packages/babel-generator/src/generators/expressions.ts @@ -49,9 +49,7 @@ export function UpdateExpression(this: Printer, node: t.UpdateExpression) { this.token(node.operator); this.print(node.argument, node); } else { - this.startTerminatorless(true); - this.print(node.argument, node); - this.endTerminatorless(); + this.printTerminatorless(node.argument, node, true); this.token(node.operator); } } @@ -74,7 +72,7 @@ export function ConditionalExpression( export function NewExpression( this: Printer, node: t.NewExpression, - parent: any, + parent: t.Node, ) { this.word("new"); this.space(); @@ -116,7 +114,7 @@ export function Super(this: Printer) { function isDecoratorMemberExpression( node: t.Expression | t.V8IntrinsicIdentifier, -) { +): boolean { switch (node.type) { case "Identifier": return true; @@ -218,25 +216,27 @@ export function Import(this: Printer) { this.word("import"); } -function buildYieldAwait(keyword: string) { - return function (this: Printer, node: any) { - this.word(keyword); - - if (node.delegate) { - this.token("*"); - } +export function AwaitExpression(this: Printer, node: t.AwaitExpression) { + this.word("await"); - if (node.argument) { - this.space(); - const terminatorState = this.startTerminatorless(); - this.print(node.argument, node); - this.endTerminatorless(terminatorState); - } - }; + if (node.argument) { + this.space(); + this.printTerminatorless(node.argument, node, false); + } } -export const YieldExpression = buildYieldAwait("yield"); -export const AwaitExpression = buildYieldAwait("await"); +export function YieldExpression(this: Printer, node: t.YieldExpression) { + this.word("yield"); + + if (node.delegate) { + this.token("*"); + } + + if (node.argument) { + this.space(); + this.printTerminatorless(node.argument, node, false); + } +} export function EmptyStatement(this: Printer) { this.semicolon(true /* force */); @@ -265,7 +265,7 @@ export function AssignmentPattern(this: Printer, node: t.AssignmentPattern) { export function AssignmentExpression( this: Printer, node: t.AssignmentExpression, - parent: any, + parent: t.Node, ) { // Somewhere inside a for statement `init` node but doesn't usually // needs a paren except for `in` expressions: `for (a in b ? a : b;;)` diff --git a/packages/babel-generator/src/generators/flow.ts b/packages/babel-generator/src/generators/flow.ts index f2ca2a6700c5..2a7af18ac267 100644 --- a/packages/babel-generator/src/generators/flow.ts +++ b/packages/babel-generator/src/generators/flow.ts @@ -48,7 +48,7 @@ export function DeclareClass( export function DeclareFunction( this: Printer, node: t.DeclareFunction, - parent: any, + parent: t.Node, ) { if (!isDeclareExportDeclaration(parent)) { this.word("declare"); @@ -118,7 +118,7 @@ export function DeclareTypeAlias(this: Printer, node: t.DeclareTypeAlias) { export function DeclareOpaqueType( this: Printer, node: t.DeclareOpaqueType, - parent: any, + parent: t.Node, ) { if (!isDeclareExportDeclaration(parent)) { this.word("declare"); @@ -130,7 +130,7 @@ export function DeclareOpaqueType( export function DeclareVariable( this: Printer, node: t.DeclareVariable, - parent: any, + parent: t.Node, ) { if (!isDeclareExportDeclaration(parent)) { this.word("declare"); @@ -174,7 +174,7 @@ export function EnumDeclaration(this: Printer, node: t.EnumDeclaration) { } function enumExplicitType( - context: any, + context: Printer, name: string, hasExplicitType: boolean, ) { @@ -187,7 +187,7 @@ function enumExplicitType( context.space(); } -function enumBody(context: any, node: any) { +function enumBody(context: Printer, node: t.EnumBody) { const { members } = node; context.token("{"); context.indent(); @@ -236,7 +236,10 @@ export function EnumDefaultedMember( this.token(","); } -function enumInitializedMember(context: any, node: any) { +function enumInitializedMember( + context: Printer, + node: t.EnumBooleanMember | t.EnumNumberMember | t.EnumStringMember, +) { const { id, init } = node; context.print(id, node); context.space(); @@ -258,7 +261,10 @@ export function EnumStringMember(this: Printer, node: t.EnumStringMember) { enumInitializedMember(this, node); } -function FlowExportDeclaration(this: Printer, node: any) { +function FlowExportDeclaration( + this: Printer, + node: t.DeclareExportDeclaration, +) { if (node.declaration) { const declar = node.declaration; this.print(declar, node); @@ -384,7 +390,16 @@ export function _interfaceish( this.print(node.body, node); } -export function _variance(this: Printer, node) { +export function _variance( + this: Printer, + node: + | t.TypeParameter + | t.ObjectTypeIndexer + | t.ObjectTypeProperty + | t.ClassProperty + | t.ClassPrivateProperty + | t.ClassAccessorProperty, +) { if (node.variance) { if (node.variance.kind === "plus") { this.token("+"); diff --git a/packages/babel-generator/src/generators/jsx.ts b/packages/babel-generator/src/generators/jsx.ts index fb3fd8356b85..3919626f08cf 100644 --- a/packages/babel-generator/src/generators/jsx.ts +++ b/packages/babel-generator/src/generators/jsx.ts @@ -54,7 +54,7 @@ export function JSXSpreadChild(this: Printer, node: t.JSXSpreadChild) { export function JSXText(this: Printer, node: t.JSXText) { const raw = this.getPossibleRaw(node); - if (raw != null) { + if (raw !== undefined) { this.token(raw); } else { this.token(node.value); diff --git a/packages/babel-generator/src/generators/methods.ts b/packages/babel-generator/src/generators/methods.ts index 42c6d3d5b20f..5459d1d71796 100644 --- a/packages/babel-generator/src/generators/methods.ts +++ b/packages/babel-generator/src/generators/methods.ts @@ -2,7 +2,10 @@ import type Printer from "../printer"; import { isIdentifier } from "@babel/types"; import type * as t from "@babel/types"; -export function _params(this: Printer, node: any) { +export function _params( + this: Printer, + node: t.Function | t.TSDeclareMethod | t.TSDeclareFunction, +) { this.print(node.typeParameters, node); this.token("("); this._parameters(node.params, node); @@ -11,7 +14,17 @@ export function _params(this: Printer, node: any) { this.print(node.returnType, node); } -export function _parameters(this: Printer, parameters, parent) { +export function _parameters( + this: Printer, + parameters: t.Function["params"], + parent: + | t.Function + | t.TSIndexSignature + | t.TSDeclareMethod + | t.TSDeclareFunction + | t.TSFunctionType + | t.TSConstructorType, +) { for (let i = 0; i < parameters.length; i++) { this._param(parameters[i], parent); @@ -22,14 +35,40 @@ export function _parameters(this: Printer, parameters, parent) { } } -export function _param(this: Printer, parameter, parent?) { +export function _param( + this: Printer, + parameter: + | t.Function["params"][number] + | t.TSIndexSignature["parameters"][number] + | t.TSDeclareMethod["params"][number] + | t.TSDeclareFunction["params"][number] + | t.TSFunctionType["parameters"][number] + | t.TSConstructorType["parameters"][number], + parent?: + | t.Function + | t.TSIndexSignature + | t.TSDeclareMethod + | t.TSDeclareFunction + | t.TSFunctionType + | t.TSConstructorType, +) { this.printJoin(parameter.decorators, parameter); this.print(parameter, parent); - if (parameter.optional) this.token("?"); // TS / flow - this.print(parameter.typeAnnotation, parameter); // TS / flow + if ( + // @ts-expect-error optional is not in TSParameterProperty + parameter.optional + ) { + this.token("?"); // TS / flow + } + + this.print( + // @ts-expect-error typeAnnotation is not in TSParameterProperty + parameter.typeAnnotation, + parameter, + ); // TS / flow } -export function _methodHead(this: Printer, node: any) { +export function _methodHead(this: Printer, node: t.Method | t.TSDeclareMethod) { const kind = node.kind; const key = node.key; @@ -45,7 +84,11 @@ export function _methodHead(this: Printer, node: any) { this.space(); } - if (kind === "method" || kind === "init") { + if ( + kind === "method" || + // @ts-ignore Fixme: kind: "init" is not defined + kind === "init" + ) { if (node.generator) { this.token("*"); } @@ -59,7 +102,10 @@ export function _methodHead(this: Printer, node: any) { this.print(key, node); } - if (node.optional) { + if ( + // @ts-expect-error optional is not in ObjectMethod + node.optional + ) { // TS this.token("?"); } @@ -67,7 +113,13 @@ export function _methodHead(this: Printer, node: any) { this._params(node); } -export function _predicate(this: Printer, node: any) { +export function _predicate( + this: Printer, + node: + | t.FunctionDeclaration + | t.FunctionExpression + | t.ArrowFunctionExpression, +) { if (node.predicate) { if (!node.returnType) { this.token(":"); @@ -77,7 +129,10 @@ export function _predicate(this: Printer, node: any) { } } -export function _functionHead(this: Printer, node: any) { +export function _functionHead( + this: Printer, + node: t.FunctionDeclaration | t.FunctionExpression | t.TSDeclareFunction, +) { if (node.async) { this.word("async"); this.space(); @@ -92,7 +147,9 @@ export function _functionHead(this: Printer, node: any) { } this._params(node); - this._predicate(node); + if (node.type !== "TSDeclareFunction") { + this._predicate(node); + } } export function FunctionExpression(this: Printer, node: t.FunctionExpression) { diff --git a/packages/babel-generator/src/generators/modules.ts b/packages/babel-generator/src/generators/modules.ts index aeaec676f2ba..ccd6c382d6cf 100644 --- a/packages/babel-generator/src/generators/modules.ts +++ b/packages/babel-generator/src/generators/modules.ts @@ -98,28 +98,6 @@ export function ExportNamedDeclaration( this.word("export"); this.space(); - ExportDeclaration.apply(this, arguments); -} - -export function ExportDefaultDeclaration( - this: Printer, - node: t.ExportDefaultDeclaration, -) { - if ( - this.format.decoratorsBeforeExport && - isClassDeclaration(node.declaration) - ) { - this.printJoin(node.declaration.decorators, node); - } - - this.word("export"); - this.space(); - this.word("default"); - this.space(); - ExportDeclaration.apply(this, arguments); -} - -function ExportDeclaration(this: Printer, node: any) { if (node.declaration) { const declar = node.declaration; this.print(declar, node); @@ -173,6 +151,26 @@ function ExportDeclaration(this: Printer, node: any) { } } +export function ExportDefaultDeclaration( + this: Printer, + node: t.ExportDefaultDeclaration, +) { + if ( + this.format.decoratorsBeforeExport && + isClassDeclaration(node.declaration) + ) { + this.printJoin(node.declaration.decorators, node); + } + + this.word("export"); + this.space(); + this.word("default"); + this.space(); + const declar = node.declaration; + this.print(declar, node); + if (!isStatement(declar)) this.semicolon(); +} + export function ImportDeclaration(this: Printer, node: t.ImportDeclaration) { this.word("import"); this.space(); diff --git a/packages/babel-generator/src/generators/statements.ts b/packages/babel-generator/src/generators/statements.ts index 52b1eea6bf19..8b383817df76 100644 --- a/packages/babel-generator/src/generators/statements.ts +++ b/packages/babel-generator/src/generators/statements.ts @@ -50,9 +50,22 @@ export function IfStatement(this: Printer, node: t.IfStatement) { } // Recursively get the last statement. -function getLastStatement(statement) { - if (!isStatement(statement.body)) return statement; - return getLastStatement(statement.body); +function getLastStatement( + statement: Exclude, +): t.Statement { + if ( + !isStatement( + // @ts-ignore body is not in BreakStatement + statement.body, + ) + ) { + return statement; + } + + return getLastStatement( + // @ts-ignore body is not in BreakStatement + statement.body, + ); } export function ForStatement(this: Printer, node: t.ForStatement) { @@ -89,27 +102,26 @@ export function WhileStatement(this: Printer, node: t.WhileStatement) { this.printBlock(node); } -const buildForXStatement = function (op) { - return function (this: Printer, node: any) { - this.word("for"); - this.space(); - if (op === "of" && node.await) { - this.word("await"); - this.space(); - } - this.token("("); - this.print(node.left, node); - this.space(); - this.word(op); +function ForXStatement(this: Printer, node: t.ForXStatement) { + this.word("for"); + this.space(); + const isForOf = node.type === "ForOfStatement"; + if (isForOf && node.await) { + this.word("await"); this.space(); - this.print(node.right, node); - this.token(")"); - this.printBlock(node); - }; -}; + } + this.token("("); + this.print(node.left, node); + this.space(); + this.word(isForOf ? "of" : "in"); + this.space(); + this.print(node.right, node); + this.token(")"); + this.printBlock(node); +} -export const ForInStatement = buildForXStatement("in"); -export const ForOfStatement = buildForXStatement("of"); +export const ForInStatement = ForXStatement; +export const ForOfStatement = ForXStatement; export function DoWhileStatement(this: Printer, node: t.DoWhileStatement) { this.word("do"); @@ -124,27 +136,39 @@ export function DoWhileStatement(this: Printer, node: t.DoWhileStatement) { this.semicolon(); } -function buildLabelStatement(prefix, key = "label") { - return function (this: Printer, node: any) { - this.word(prefix); - - const label = node[key]; - if (label) { - this.space(); - const isLabel = key == "label"; - const terminatorState = this.startTerminatorless(isLabel); - this.print(label, node); - this.endTerminatorless(terminatorState); - } +function printStatementAfterKeyword( + printer: Printer, + node: t.Node, + parent: t.Node, + isLabel: boolean, +) { + if (node) { + printer.space(); + printer.printTerminatorless(node, parent, isLabel); + } - this.semicolon(); - }; + printer.semicolon(); } -export const ContinueStatement = buildLabelStatement("continue"); -export const ReturnStatement = buildLabelStatement("return", "argument"); -export const BreakStatement = buildLabelStatement("break"); -export const ThrowStatement = buildLabelStatement("throw", "argument"); +export function BreakStatement(this: Printer, node: t.ContinueStatement) { + this.word("break"); + printStatementAfterKeyword(this, node.label, node, true); +} + +export function ContinueStatement(this: Printer, node: t.ContinueStatement) { + this.word("continue"); + printStatementAfterKeyword(this, node.label, node, true); +} + +export function ReturnStatement(this: Printer, node: t.ReturnStatement) { + this.word("return"); + printStatementAfterKeyword(this, node.argument, node, false); +} + +export function ThrowStatement(this: Printer, node: t.ThrowStatement) { + this.word("throw"); + printStatementAfterKeyword(this, node.argument, node, false); +} export function LabeledStatement(this: Printer, node: t.LabeledStatement) { this.print(node.label, node); diff --git a/packages/babel-generator/src/generators/template-literals.ts b/packages/babel-generator/src/generators/template-literals.ts index 14e5a4e03b7f..48c9e5589ebb 100644 --- a/packages/babel-generator/src/generators/template-literals.ts +++ b/packages/babel-generator/src/generators/template-literals.ts @@ -13,7 +13,7 @@ export function TaggedTemplateExpression( export function TemplateElement( this: Printer, node: t.TemplateElement, - parent: any, + parent: t.TemplateLiteral, ) { const isFirst = parent.quasis[0] === node; const isLast = parent.quasis[parent.quasis.length - 1] === node; diff --git a/packages/babel-generator/src/generators/types.ts b/packages/babel-generator/src/generators/types.ts index 779df2d69f8c..552b81faade6 100644 --- a/packages/babel-generator/src/generators/types.ts +++ b/packages/babel-generator/src/generators/types.ts @@ -201,7 +201,7 @@ export function NumericLiteral(this: Printer, node: t.NumericLiteral) { export function StringLiteral(this: Printer, node: t.StringLiteral) { const raw = this.getPossibleRaw(node); - if (!this.format.minified && raw != null) { + if (!this.format.minified && raw !== undefined) { this.token(raw); return; } @@ -221,7 +221,7 @@ export function StringLiteral(this: Printer, node: t.StringLiteral) { export function BigIntLiteral(this: Printer, node: t.BigIntLiteral) { const raw = this.getPossibleRaw(node); - if (!this.format.minified && raw != null) { + if (!this.format.minified && raw !== undefined) { this.word(raw); return; } @@ -230,7 +230,7 @@ export function BigIntLiteral(this: Printer, node: t.BigIntLiteral) { export function DecimalLiteral(this: Printer, node: t.DecimalLiteral) { const raw = this.getPossibleRaw(node); - if (!this.format.minified && raw != null) { + if (!this.format.minified && raw !== undefined) { this.word(raw); return; } diff --git a/packages/babel-generator/src/generators/typescript.ts b/packages/babel-generator/src/generators/typescript.ts index 4aa982e8741d..9f0405b7b09f 100644 --- a/packages/babel-generator/src/generators/typescript.ts +++ b/packages/babel-generator/src/generators/typescript.ts @@ -131,7 +131,10 @@ export function TSPropertySignature( this.token(";"); } -export function tsPrintPropertyOrMethodName(this: Printer, node) { +export function tsPrintPropertyOrMethodName( + this: Printer, + node: t.TSPropertySignature | t.TSMethodSignature, +) { if (node.computed) { this.token("["); } @@ -232,12 +235,12 @@ export function TSConstructorType(this: Printer, node: t.TSConstructorType) { export function tsPrintFunctionOrConstructorType( this: Printer, - // todo: missing type FunctionOrConstructorType - node: any, + node: t.TSFunctionType | t.TSConstructorType, ) { const { typeParameters } = node; const parameters = process.env.BABEL_8_BREAKING - ? node.params + ? // @ts-expect-error Babel 8 AST shape + node.params : node.parameters; this.print(typeParameters, node); this.token("("); @@ -247,7 +250,8 @@ export function tsPrintFunctionOrConstructorType( this.token("=>"); this.space(); const returnType = process.env.BABEL_8_BREAKING - ? node.returnType + ? // @ts-expect-error Babel 8 AST shape + node.returnType : node.typeAnnotation; this.print(returnType.typeAnnotation, node); } @@ -287,26 +291,26 @@ export function TSTypeLiteral(this: Printer, node: t.TSTypeLiteral) { export function tsPrintTypeLiteralOrInterfaceBody( this: Printer, - members, - node, + members: t.TSTypeElement[], + node: t.TSType | t.TSInterfaceBody, ) { - this.tsPrintBraced(members, node); + tsPrintBraced(this, members, node); } -export function tsPrintBraced(this: Printer, members, node) { - this.token("{"); +function tsPrintBraced(printer: Printer, members: t.Node[], node: t.Node) { + printer.token("{"); if (members.length) { - this.indent(); - this.newline(); + printer.indent(); + printer.newline(); for (const member of members) { - this.print(member, node); + printer.print(member, node); //this.token(sep); - this.newline(); + printer.newline(); } - this.dedent(); - this.rightBrace(); + printer.dedent(); + printer.rightBrace(); } else { - this.token("}"); + printer.token("}"); } } @@ -340,15 +344,19 @@ export function TSNamedTupleMember(this: Printer, node: t.TSNamedTupleMember) { } export function TSUnionType(this: Printer, node: t.TSUnionType) { - this.tsPrintUnionOrIntersectionType(node, "|"); + tsPrintUnionOrIntersectionType(this, node, "|"); } export function TSIntersectionType(this: Printer, node: t.TSIntersectionType) { - this.tsPrintUnionOrIntersectionType(node, "&"); + tsPrintUnionOrIntersectionType(this, node, "&"); } -export function tsPrintUnionOrIntersectionType(this: Printer, node: any, sep) { - this.printJoin(node.types, node, { +function tsPrintUnionOrIntersectionType( + printer: Printer, + node: t.TSUnionType | t.TSIntersectionType, + sep: "|" | "&", +) { + printer.printJoin(node.types, node, { separator() { this.space(); this.token(sep); @@ -445,7 +453,7 @@ export function TSMappedType(this: Printer, node: t.TSMappedType) { this.token("}"); } -function tokenIfPlusMinus(self, tok) { +function tokenIfPlusMinus(self: Printer, tok: true | "+" | "-") { if (tok !== true) { self.token(tok); } @@ -550,7 +558,7 @@ export function TSEnumDeclaration(this: Printer, node: t.TSEnumDeclaration) { this.space(); this.print(id, node); this.space(); - this.tsPrintBraced(members, node); + tsPrintBraced(this, members, node); } export function TSEnumMember(this: Printer, node: t.TSEnumMember) { @@ -599,7 +607,7 @@ export function TSModuleDeclaration( } export function TSModuleBlock(this: Printer, node: t.TSModuleBlock) { - this.tsPrintBraced(node.body, node); + tsPrintBraced(this, node.body, node); } export function TSImportType(this: Printer, node: t.TSImportType) { @@ -690,7 +698,17 @@ export function tsPrintSignatureDeclarationBase(this: Printer, node: any) { this.print(returnType, node); } -export function tsPrintClassMemberModifiers(this: Printer, node: any, isField) { +export function tsPrintClassMemberModifiers( + this: Printer, + node: + | t.ClassProperty + | t.ClassAccessorProperty + | t.ClassMethod + | t.ClassPrivateMethod + | t.TSDeclareMethod, +) { + const isField = + node.type === "ClassAccessorProperty" || node.type === "ClassProperty"; if (isField && node.declare) { this.word("declare"); this.space(); diff --git a/packages/babel-generator/src/index.ts b/packages/babel-generator/src/index.ts index 67d299411bff..c6f609c7e831 100644 --- a/packages/babel-generator/src/index.ts +++ b/packages/babel-generator/src/index.ts @@ -3,6 +3,10 @@ import Printer from "./printer"; import type * as t from "@babel/types"; import type { Opts as jsescOptions } from "jsesc"; import type { Format } from "./printer"; +import type { + RecordAndTuplePluginOptions, + PipelineOperatorPluginOptions, +} from "@babel/parser"; import type { DecodedSourceMap, Mapping } from "@jridgewell/gen-mapping"; /** @@ -13,12 +17,8 @@ import type { DecodedSourceMap, Mapping } from "@jridgewell/gen-mapping"; class Generator extends Printer { constructor( ast: t.Node, - opts: { - sourceFileName?: string; - sourceMaps?: boolean; - sourceRoot?: string; - } = {}, - code, + opts: GeneratorOptions = {}, + code: string | { [filename: string]: string }, ) { const format = normalizeOptions(code, opts); const map = opts.sourceMaps ? new SourceMap(opts, code) : null; @@ -47,7 +47,10 @@ class Generator extends Printer { * - If `opts.compact = "auto"` and the code is over 500KB, `compact` will be set to `true`. */ -function normalizeOptions(code, opts): Format { +function normalizeOptions( + code: string | { [filename: string]: string }, + opts: GeneratorOptions, +): Format { const format: Format = { auxiliaryCommentBefore: opts.auxiliaryCommentBefore, auxiliaryCommentAfter: opts.auxiliaryCommentAfter, @@ -183,6 +186,7 @@ export interface GeneratorOptions { /** * Set to true to run jsesc with "json": true to print "\u00A9" vs. "©"; + * @deprecated use `jsescOptions: { json: true }` instead */ jsonCompatibleStrings?: boolean; @@ -200,24 +204,24 @@ export interface GeneratorOptions { /** * For use with the recordAndTuple token. */ - recordAndTupleSyntaxType?: "hash" | "bar"; + recordAndTupleSyntaxType?: RecordAndTuplePluginOptions["syntaxType"]; /** * For use with the Hack-style pipe operator. * Changes what token is used for pipe bodies’ topic references. */ - topicToken?: "^^" | "@@" | "^" | "%" | "#"; + topicToken?: PipelineOperatorPluginOptions["topicToken"]; } export interface GeneratorResult { code: string; map: { version: number; - sources: string[]; - names: string[]; + sources: readonly string[]; + names: readonly string[]; sourceRoot?: string; - sourcesContent?: string[]; + sourcesContent?: readonly string[]; mappings: string; - file: string; + file?: string; } | null; decodedMap: DecodedSourceMap | undefined; rawMappings: Mapping[] | undefined; diff --git a/packages/babel-generator/src/node/index.ts b/packages/babel-generator/src/node/index.ts index e6b7184f6dc4..09e2bbdc79cd 100644 --- a/packages/babel-generator/src/node/index.ts +++ b/packages/babel-generator/src/node/index.ts @@ -7,11 +7,28 @@ import { isMemberExpression, isNewExpression, } from "@babel/types"; - -function expandAliases(obj) { - const newObj = {}; - - function add(type, func) { +import type * as t from "@babel/types"; +import type { WhitespaceObject } from "./whitespace"; + +export type NodeHandlers = { + [K in string]?: ( + node: K extends t.Node["type"] ? Extract : t.Node, + // todo: + // node: K extends keyof typeof t + // ? Extract + // : t.Node, + parent: t.Node, + stack: t.Node[], + ) => R; +}; + +function expandAliases(obj: NodeHandlers) { + const newObj: NodeHandlers = {}; + + function add( + type: string, + func: (node: t.Node, parent: t.Node, stack: t.Node[]) => R, + ) { const fn = newObj[type]; newObj[type] = fn ? function (node, parent, stack) { @@ -42,12 +59,17 @@ const expandedParens = expandAliases(parens); const expandedWhitespaceNodes = expandAliases(whitespace.nodes); const expandedWhitespaceList = expandAliases(whitespace.list); -function find(obj, node, parent, printStack?) { +function find( + obj: NodeHandlers, + node: t.Node, + parent: t.Node, + printStack?: t.Node[], +): R | null { const fn = obj[node.type]; return fn ? fn(node, parent, printStack) : null; } -function isOrHasCallExpression(node) { +function isOrHasCallExpression(node: t.Node): boolean { if (isCallExpression(node)) { return true; } @@ -55,14 +77,22 @@ function isOrHasCallExpression(node) { return isMemberExpression(node) && isOrHasCallExpression(node.object); } -export function needsWhitespace(node, parent, type) { - if (!node) return 0; +export function needsWhitespace( + node: t.Node, + parent: t.Node, + type: "before" | "after", +): boolean { + if (!node) return false; if (isExpressionStatement(node)) { node = node.expression; } - let linesInfo = find(expandedWhitespaceNodes, node, parent); + let linesInfo: WhitespaceObject | null | boolean = find( + expandedWhitespaceNodes, + node, + parent, + ); if (!linesInfo) { const items = find(expandedWhitespaceList, node, parent); @@ -75,21 +105,25 @@ export function needsWhitespace(node, parent, type) { } if (typeof linesInfo === "object" && linesInfo !== null) { - return linesInfo[type] || 0; + return linesInfo[type] || false; } - return 0; + return false; } -export function needsWhitespaceBefore(node, parent) { +export function needsWhitespaceBefore(node: t.Node, parent: t.Node) { return needsWhitespace(node, parent, "before"); } -export function needsWhitespaceAfter(node, parent) { +export function needsWhitespaceAfter(node: t.Node, parent: t.Node) { return needsWhitespace(node, parent, "after"); } -export function needsParens(node, parent, printStack?) { +export function needsParens( + node: t.Node, + parent: t.Node, + printStack?: t.Node[], +) { if (!parent) return false; if (isNewExpression(parent) && parent.callee === node) { diff --git a/packages/babel-generator/src/node/parentheses.ts b/packages/babel-generator/src/node/parentheses.ts index df255d85c61a..89ad23b18479 100644 --- a/packages/babel-generator/src/node/parentheses.ts +++ b/packages/babel-generator/src/node/parentheses.ts @@ -7,7 +7,7 @@ import { isBinaryExpression, isUpdateExpression, isCallExpression, - isClassDeclaration, + isClass, isClassExpression, isConditional, isConditionalExpression, @@ -54,6 +54,7 @@ import type * as t from "@babel/types"; const PRECEDENCE = { "||": 0, "??": 0, + "|>": 0, "&&": 1, "|": 2, "^": 3, @@ -79,11 +80,12 @@ const PRECEDENCE = { "**": 10, }; -const isClassExtendsClause = (node: any, parent: any): boolean => - (isClassDeclaration(parent) || isClassExpression(parent)) && - parent.superClass === node; +const isClassExtendsClause = ( + node: t.Node, + parent: t.Node, +): parent is t.Class => isClass(parent, { superClass: node }); -const hasPostfixPart = (node: any, parent: any) => +const hasPostfixPart = (node: t.Node, parent: t.Node) => ((isMemberExpression(parent) || isOptionalMemberExpression(parent)) && parent.object === node) || ((isCallExpression(parent) || @@ -93,14 +95,17 @@ const hasPostfixPart = (node: any, parent: any) => (isTaggedTemplateExpression(parent) && parent.tag === node) || isTSNonNullExpression(parent); -export function NullableTypeAnnotation(node: any, parent: any): boolean { +export function NullableTypeAnnotation( + node: t.NullableTypeAnnotation, + parent: t.Node, +): boolean { return isArrayTypeAnnotation(parent); } export function FunctionTypeAnnotation( - node: any, - parent: any, - printStack: Array, + node: t.FunctionTypeAnnotation, + parent: t.Node, + printStack: Array, ): boolean { return ( // (() => A) | (() => B) @@ -116,14 +121,17 @@ export function FunctionTypeAnnotation( ); } -export function UpdateExpression(node: any, parent: any): boolean { +export function UpdateExpression( + node: t.UpdateExpression, + parent: t.Node, +): boolean { return hasPostfixPart(node, parent) || isClassExtendsClause(node, parent); } export function ObjectExpression( - node: any, - parent: any, - printStack: Array, + node: t.ObjectExpression, + parent: t.Node, + printStack: Array, ): boolean { return isFirstInContext(printStack, { expressionStatement: true, @@ -132,9 +140,9 @@ export function ObjectExpression( } export function DoExpression( - node: any, - parent: any, - printStack: Array, + node: t.DoExpression, + parent: t.Node, + printStack: Array, ): boolean { // `async do` can start an expression statement return ( @@ -142,7 +150,7 @@ export function DoExpression( ); } -export function Binary(node: any, parent: any): boolean { +export function Binary(node: t.BinaryExpression, parent: t.Node): boolean { if ( node.operator === "**" && isBinaryExpression(parent, { operator: "**" }) @@ -181,7 +189,10 @@ export function Binary(node: any, parent: any): boolean { } } -export function UnionTypeAnnotation(node: any, parent: any): boolean { +export function UnionTypeAnnotation( + node: t.UnionTypeAnnotation, + parent: t.Node, +): boolean { return ( isArrayTypeAnnotation(parent) || isNullableTypeAnnotation(parent) || @@ -192,7 +203,10 @@ export function UnionTypeAnnotation(node: any, parent: any): boolean { export { UnionTypeAnnotation as IntersectionTypeAnnotation }; -export function OptionalIndexedAccessType(node: any, parent: any): boolean { +export function OptionalIndexedAccessType( + node: t.OptionalIndexedAccessType, + parent: t.Node, +): boolean { return isIndexedAccessType(parent, { objectType: node }); } @@ -204,7 +218,7 @@ export function TSTypeAssertion() { return true; } -export function TSUnionType(node: any, parent: any): boolean { +export function TSUnionType(node: t.TSUnionType, parent: t.Node): boolean { return ( isTSArrayType(parent) || isTSOptionalType(parent) || @@ -216,7 +230,7 @@ export function TSUnionType(node: any, parent: any): boolean { export { TSUnionType as TSIntersectionType }; -export function TSInferType(node: any, parent: any): boolean { +export function TSInferType(node: t.TSInferType, parent: t.Node): boolean { return isTSArrayType(parent) || isTSOptionalType(parent); } @@ -233,7 +247,10 @@ export function TSInstantiationExpression( ); } -export function BinaryExpression(node: any, parent: any): boolean { +export function BinaryExpression( + node: t.BinaryExpression, + parent: t.Node, +): boolean { // let i = (1 in []); // for ((1 in []);;); return ( @@ -241,7 +258,10 @@ export function BinaryExpression(node: any, parent: any): boolean { ); } -export function SequenceExpression(node: any, parent: any): boolean { +export function SequenceExpression( + node: t.SequenceExpression, + parent: t.Node, +): boolean { if ( // Although parentheses wouldn"t hurt around sequence // expressions in the head of for loops, traditional style @@ -264,7 +284,10 @@ export function SequenceExpression(node: any, parent: any): boolean { return true; } -export function YieldExpression(node: any, parent: any): boolean { +export function YieldExpression( + node: t.YieldExpression, + parent: t.Node, +): boolean { return ( isBinary(parent) || isUnaryLike(parent) || @@ -278,9 +301,9 @@ export function YieldExpression(node: any, parent: any): boolean { export { YieldExpression as AwaitExpression }; export function ClassExpression( - node: any, - parent: any, - printStack: Array, + node: t.ClassExpression, + parent: t.Node, + printStack: Array, ): boolean { return isFirstInContext(printStack, { expressionStatement: true, @@ -288,7 +311,14 @@ export function ClassExpression( }); } -export function UnaryLike(node: any, parent: any): boolean { +export function UnaryLike( + node: + | t.UnaryLike + | t.ArrowFunctionExpression + | t.ConditionalExpression + | t.AssignmentExpression, + parent: t.Node, +): boolean { return ( hasPostfixPart(node, parent) || isBinaryExpression(parent, { operator: "**", left: node }) || @@ -297,9 +327,9 @@ export function UnaryLike(node: any, parent: any): boolean { } export function FunctionExpression( - node: any, - parent: any, - printStack: Array, + node: t.FunctionExpression, + parent: t.Node, + printStack: Array, ): boolean { return isFirstInContext(printStack, { expressionStatement: true, @@ -307,11 +337,20 @@ export function FunctionExpression( }); } -export function ArrowFunctionExpression(node: any, parent: any): boolean { +export function ArrowFunctionExpression( + node: t.ArrowFunctionExpression, + parent: t.Node, +): boolean { return isExportDeclaration(parent) || ConditionalExpression(node, parent); } -export function ConditionalExpression(node: any, parent?): boolean { +export function ConditionalExpression( + node: + | t.ConditionalExpression + | t.ArrowFunctionExpression + | t.AssignmentExpression, + parent?: t.Node, +): boolean { if ( isUnaryLike(parent) || isBinary(parent) || @@ -326,7 +365,10 @@ export function ConditionalExpression(node: any, parent?): boolean { return UnaryLike(node, parent); } -export function OptionalMemberExpression(node: any, parent: any): boolean { +export function OptionalMemberExpression( + node: t.OptionalMemberExpression, + parent: t.Node, +): boolean { return ( isCallExpression(parent, { callee: node }) || isMemberExpression(parent, { object: node }) @@ -335,7 +377,10 @@ export function OptionalMemberExpression(node: any, parent: any): boolean { export { OptionalMemberExpression as OptionalCallExpression }; -export function AssignmentExpression(node: any, parent: any): boolean { +export function AssignmentExpression( + node: t.AssignmentExpression, + parent: t.Node, +): boolean { if (isObjectPattern(node.left)) { return true; } else { @@ -343,7 +388,10 @@ export function AssignmentExpression(node: any, parent: any): boolean { } } -export function LogicalExpression(node: any, parent: any): boolean { +export function LogicalExpression( + node: t.LogicalExpression, + parent: t.Node, +): boolean { switch (node.operator) { case "||": if (!isLogicalExpression(parent)) return false; diff --git a/packages/babel-generator/src/node/whitespace.ts b/packages/babel-generator/src/node/whitespace.ts index c9a83a2207e0..8bd234b8045e 100644 --- a/packages/babel-generator/src/node/whitespace.ts +++ b/packages/babel-generator/src/node/whitespace.ts @@ -15,8 +15,10 @@ import { isStringLiteral, } from "@babel/types"; +import type { NodeHandlers } from "./index"; + import type * as t from "@babel/types"; -type WhitespaceObject = { +export type WhitespaceObject = { before?: boolean; after?: boolean; }; @@ -72,7 +74,7 @@ function isHelper(node: t.Node): boolean { } } -function isType(node) { +function isType(node: t.Node) { return ( isLiteral(node) || isObjectExpression(node) || @@ -86,16 +88,7 @@ function isType(node) { * Tests for node types that need whitespace. */ -export const nodes: { - [K in string]?: ( - node: K extends t.Node["type"] ? Extract : t.Node, - // todo: - // node: K extends keyof typeof t - // ? Extract - // : t.Node, - parent: t.Node, - ) => void; -} = { +export const nodes: NodeHandlers = { /** * Test if AssignmentExpression needs whitespace. */ @@ -221,7 +214,7 @@ nodes.ObjectProperty = nodes.ObjectMethod = function ( node: t.ObjectProperty | t.ObjectTypeProperty | t.ObjectMethod, - parent: any, + parent: t.ObjectExpression, ): WhitespaceObject | undefined | null { if (parent.properties[0] === node) { return { @@ -232,7 +225,7 @@ nodes.ObjectProperty = nodes.ObjectTypeCallProperty = function ( node: t.ObjectTypeCallProperty, - parent: any, + parent: t.ObjectTypeAnnotation, ): WhitespaceObject | undefined | null { if (parent.callProperties[0] === node && !parent.properties?.length) { return { @@ -243,7 +236,7 @@ nodes.ObjectTypeCallProperty = function ( nodes.ObjectTypeIndexer = function ( node: t.ObjectTypeIndexer, - parent: any, + parent: t.ObjectTypeAnnotation, ): WhitespaceObject | undefined | null { if ( parent.indexers[0] === node && @@ -258,7 +251,7 @@ nodes.ObjectTypeIndexer = function ( nodes.ObjectTypeInternalSlot = function ( node: t.ObjectTypeInternalSlot, - parent: any, + parent: t.ObjectTypeAnnotation, ): WhitespaceObject | undefined | null { if ( parent.internalSlots[0] === node && @@ -276,7 +269,7 @@ nodes.ObjectTypeInternalSlot = function ( * Returns lists from node types that need whitespace. */ -export const list = { +export const list: NodeHandlers = { /** * Return VariableDeclaration declarations init properties. */ @@ -314,16 +307,13 @@ export const list = { ["LabeledStatement", true], ["SwitchStatement", true], ["TryStatement", true], - ] as Array<[string, any]> + ] as const ).forEach(function ([type, amounts]) { - if (typeof amounts === "boolean") { - amounts = { after: amounts, before: amounts }; - } [type as string] .concat(FLIPPED_ALIAS_KEYS[type] || []) .forEach(function (type) { nodes[type] = function () { - return amounts; + return { after: amounts, before: amounts }; }; }); }); diff --git a/packages/babel-generator/src/printer.ts b/packages/babel-generator/src/printer.ts index 1bde04f31c63..62bf02c045a3 100644 --- a/packages/babel-generator/src/printer.ts +++ b/packages/babel-generator/src/printer.ts @@ -1,7 +1,13 @@ import Buffer from "./buffer"; +import type { Loc } from "./buffer"; import * as n from "./node"; import { isProgram, isFile, isEmptyStatement } from "@babel/types"; import type * as t from "@babel/types"; +import type { + RecordAndTuplePluginOptions, + PipelineOperatorPluginOptions, +} from "@babel/parser"; +import type { Opts as jsescOptions } from "jsesc"; import * as generatorFunctions from "./generators"; import type SourceMap from "./source-map"; @@ -30,16 +36,35 @@ export type Format = { base: number; }; decoratorsBeforeExport: boolean; - recordAndTupleSyntaxType: "bar" | "hash"; - jsescOption; - jsonCompatibleStrings?; + recordAndTupleSyntaxType: RecordAndTuplePluginOptions["syntaxType"]; + jsescOption: jsescOptions; + jsonCompatibleStrings?: boolean; /** * For use with the Hack-style pipe operator. * Changes what token is used for pipe bodies’ topic references. */ - topicToken?: "#"; + topicToken?: PipelineOperatorPluginOptions["topicToken"]; }; +interface AddNewlinesOptions { + addNewlines(leading: boolean, node: t.Node): number; +} + +interface PrintSequenceOptions extends Partial { + statement?: boolean; + indent?: boolean; +} + +interface PrintListOptions { + separator?: (this: Printer) => void; + statement?: boolean; + indent?: boolean; +} + +type PrintJoinOptions = PrintListOptions & + PrintSequenceOptions & { + iterator?: (node: t.Node, index: number) => void; + }; class Printer { constructor(format: Format, map: SourceMap) { this.format = format; @@ -53,14 +78,14 @@ class Printer { _printStack: Array = []; _indent: number = 0; _insideAux: boolean = false; - _parenPushNewlineState: any = null; + _parenPushNewlineState: { printed: boolean } | null = null; _noLineTerminator: boolean = false; _printAuxAfterOnNextUserNode: boolean = false; - _printedComments: WeakSet = new WeakSet(); + _printedComments = new WeakSet(); _endsWithInteger = false; _endsWithWord = false; - generate(ast) { + generate(ast: t.Node) { this.print(ast); this._maybeAddAuxComment(); @@ -229,19 +254,23 @@ class Printer { this._buf.removeTrailingNewline(); } - exactSource(loc: any, cb: () => void) { + exactSource(loc: Loc | undefined, cb: () => void) { this._catchUp("start", loc); this._buf.exactSource(loc, cb); } - source(prop: string, loc: any): void { + source(prop: "start" | "end", loc: Loc | undefined): void { this._catchUp(prop, loc); this._buf.source(prop, loc); } - withSource(prop: string, loc: any, cb: () => void): void { + withSource( + prop: "start" | "end", + loc: Loc | undefined, + cb: () => void, + ): void { this._catchUp(prop, loc); this._buf.withSource(prop, loc, cb); @@ -337,7 +366,7 @@ class Printer { parenPushNewlineState.printed = true; } - _catchUp(prop: string, loc: any) { + _catchUp(prop: "start" | "end", loc?: Loc) { if (!this.format.retainLines) return; // catch up to this nodes newline if we're behind @@ -359,55 +388,64 @@ class Printer { return this.format.indent.style.repeat(this._indent); } - /** - * Set some state that will be modified if a newline has been inserted before any - * non-space characters. - * - * This is to prevent breaking semantics for terminatorless separator nodes. eg: - * - * return foo; - * - * returns `foo`. But if we do: - * - * return - * foo; - * - * `undefined` will be returned and not `foo` due to the terminator. - */ - - startTerminatorless(isLabel: boolean = false) { + printTerminatorless(node: t.Node, parent: t.Node, isLabel: boolean) { + /** + * Set some state that will be modified if a newline has been inserted before any + * non-space characters. + * + * This is to prevent breaking semantics for terminatorless separator nodes. eg: + * + * return foo; + * + * returns `foo`. But if we do: + * + * return + * foo; + * + * `undefined` will be returned and not `foo` due to the terminator. + */ if (isLabel) { this._noLineTerminator = true; - return null; + this.print(node, parent); + this._noLineTerminator = false; } else { - return (this._parenPushNewlineState = { + const terminatorState = { printed: false, - }); - } - } - - /** - * Print an ending parentheses if a starting one has been printed. - */ - - endTerminatorless(state?: any) { - this._noLineTerminator = false; - if (state?.printed) { - this.dedent(); - this.newline(); - this.token(")"); + }; + this._parenPushNewlineState = terminatorState; + this.print(node, parent); + /** + * Print an ending parentheses if a starting one has been printed. + */ + if (terminatorState.printed) { + this.dedent(); + this.newline(); + this.token(")"); + } } } - print(node, parent?) { + print(node: t.Node | null, parent?: t.Node) { if (!node) return; const oldConcise = this.format.concise; - if (node._compact) { + if ( + // @ts-ignore document _compact AST properties + node._compact + ) { this.format.concise = true; } - const printMethod = this[node.type]; + const printMethod = + this[ + node.type as Exclude< + t.Node["type"], + // removed + | "Noop" + // renamed + | t.DeprecatedAliases["type"] + > + ]; if (!printMethod) { throw new ReferenceError( `unknown node of type ${JSON.stringify( @@ -451,7 +489,7 @@ class Printer { this._insideAux = oldInAux; } - _maybeAddAuxComment(enteredPositionlessNode?) { + _maybeAddAuxComment(enteredPositionlessNode?: boolean) { if (enteredPositionlessNode) this._printAuxBeforeComment(); if (!this._insideAux) this._printAuxAfterComment(); } @@ -482,7 +520,15 @@ class Printer { } } - getPossibleRaw(node) { + getPossibleRaw( + node: + | t.StringLiteral + | t.NumericLiteral + | t.BigIntLiteral + | t.DecimalLiteral + | t.DirectiveLiteral + | t.JSXText, + ): string | undefined { const extra = node.extra; if ( extra && @@ -490,16 +536,21 @@ class Printer { extra.rawValue != null && node.value === extra.rawValue ) { + // @ts-expect-error: The extra.raw of these AST node types must be a string return extra.raw; } } - printJoin(nodes: Array | undefined | null, parent: any, opts: any = {}) { + printJoin( + nodes: Array | undefined | null, + parent: t.Node, + opts: PrintJoinOptions = {}, + ) { if (!nodes?.length) return; if (opts.indent) this.indent(); - const newlineOpts = { + const newlineOpts: AddNewlinesOptions = { addNewlines: opts.addNewlines, }; @@ -525,14 +576,14 @@ class Printer { if (opts.indent) this.dedent(); } - printAndIndentOnComments(node, parent) { + printAndIndentOnComments(node: t.Node, parent: t.Node) { const indent = node.leadingComments && node.leadingComments.length > 0; if (indent) this.indent(); this.print(node, parent); if (indent) this.dedent(); } - printBlock(parent) { + printBlock(parent: Extract) { const node = parent.body; if (!isEmptyStatement(node)) { @@ -542,11 +593,11 @@ class Printer { this.print(node, parent); } - _printTrailingComments(node) { + _printTrailingComments(node: t.Node) { this._printComments(this._getComments(false, node)); } - _printLeadingComments(node) { + _printLeadingComments(node: t.Node) { this._printComments( this._getComments(true, node), // Don't add leading/trailing new lines to #__PURE__ annotations @@ -554,7 +605,7 @@ class Printer { ); } - printInnerComments(node, indent = true) { + printInnerComments(node: t.Node, indent = true) { if (!node.innerComments?.length) return; if (indent) this.indent(); this._printComments(node.innerComments); @@ -562,23 +613,15 @@ class Printer { } printSequence( - nodes, - parent, - opts: { - statement?: boolean; - indent?: boolean; - addNewlines?: Function; - } = {}, + nodes: t.Node[], + parent: t.Node, + opts: PrintSequenceOptions = {}, ) { opts.statement = true; return this.printJoin(nodes, parent, opts); } - printList( - items, - parent, - opts: { separator?: Function; indent?: boolean; statement?: boolean } = {}, - ) { + printList(items: t.Node[], parent: t.Node, opts: PrintListOptions = {}) { if (opts.separator == null) { opts.separator = commaSeparator; } @@ -586,7 +629,12 @@ class Printer { return this.printJoin(items, parent, opts); } - _printNewline(leading, node, parent, opts) { + _printNewline( + leading: boolean, + node: t.Node, + parent: t.Node, + opts: AddNewlinesOptions, + ) { // Fast path since 'this.newline' does nothing when not tracking lines. if (this.format.retainLines || this.format.compact) return; @@ -610,7 +658,7 @@ class Printer { this.newline(Math.min(2, lines)); } - _getComments(leading, node) { + _getComments(leading: boolean, node: t.Node) { // Note, we use a boolean flag here instead of passing in the attribute name as it is faster // because this is called extremely frequently. return ( @@ -618,11 +666,12 @@ class Printer { ); } - _printComment(comment, skipNewLines?: boolean) { + _printComment(comment: t.Comment, skipNewLines?: boolean) { if (!this.format.shouldPrintComment(comment.value)) return; // Some plugins use this to mark comments as removed using the AST-root 'comments' property, // where they can't manually mutate the AST node comment lists. + // @ts-ignore todo: which plugin? if (comment.ignore) return; if (this._printedComments.has(comment)) return; @@ -674,7 +723,10 @@ class Printer { if (printNewLines) this.newline(1); } - _printComments(comments?: Array, inlinePureAnnotation?: boolean) { + _printComments( + comments?: readonly t.Comment[], + inlinePureAnnotation?: boolean, + ) { if (!comments?.length) return; if ( @@ -694,7 +746,7 @@ class Printer { } } // todo(flow->ts): was Node - printAssertions(node) { + printAssertions(node: Extract) { if (node.assertions?.length) { this.space(); this.word("assert");