Skip to content

Commit

Permalink
Convert @babel/template from Flow to TS (#12317)
Browse files Browse the repository at this point in the history
Co-authored-by: Nicolò Ribaudo <nicolo.ribaudo@gmail.com>
  • Loading branch information
zxbodya and nicolo-ribaudo committed Nov 9, 2020
1 parent f80478c commit 089c200
Show file tree
Hide file tree
Showing 11 changed files with 218 additions and 203 deletions.
36 changes: 36 additions & 0 deletions lib/babel-packages.js.flow
@@ -0,0 +1,36 @@
declare module "@babel/template" {
declare type PublicOpts = {
placeholderWhitelist?: ?Set<string>,
placeholderPattern?: ?(RegExp | false),
preserveComments?: ?boolean,
syntacticPlaceholders?: ?boolean,
};

declare type PublicReplacements = { [string]: mixed } | Array<mixed>;

declare type TemplateBuilder<T> = {
ast: {
(tpl: string, opts: ?PublicOpts): T,
(tpl: Array<string>, ...args: Array<mixed>): T,
},
(opts: PublicOpts): TemplateBuilder<T>,
(tpl: string, opts: ?PublicOpts): (?PublicReplacements) => T,
(tpl: Array<string>, ...args: Array<mixed>): (?PublicReplacements) => T,
};

declare type Smart = TemplateBuilder<
Array<BabelNodeStatement> | BabelNodeStatement
>;
declare type Statement = TemplateBuilder<BabelNodeStatement>;
declare type Statements = TemplateBuilder<Array<BabelNodeStatement>>;
declare type Expression = TemplateBuilder<BabelNodeExpression>;
declare type Program = TemplateBuilder<BabelNodeProgram>;

declare export default Smart & {
smart: Smart,
statement: Statement,
statements: Statements,
expression: Expression,
program: Program,
};
}
@@ -1,33 +1,28 @@
// @flow

import {
merge,
validate,
type TemplateOpts,
type PublicOpts,
type PublicReplacements,
} from "./options";
import { merge, validate } from "./options";
import type { TemplateOpts, PublicOpts, PublicReplacements } from "./options";
import type { Formatter } from "./formatters";

import stringTemplate from "./string";
import literalTemplate from "./literal";

export type TemplateBuilder<T> = {
// Build a new builder, merging the given options with the previous ones.
(opts: PublicOpts): TemplateBuilder<T>,
(opts: PublicOpts): TemplateBuilder<T>;

// Building from a string produces an AST builder function by default.
(tpl: string, opts: ?PublicOpts): (?PublicReplacements) => T,
(tpl: string, opts?: PublicOpts): (replacements?: PublicReplacements) => T;

// Building from a template literal produces an AST builder function by default.
(tpl: Array<string>, ...args: Array<mixed>): (?PublicReplacements) => T,
(tpl: TemplateStringsArray, ...args: Array<any>): (
replacements?: PublicReplacements,
) => T;

// Allow users to explicitly create templates that produce ASTs, skipping
// the need for an intermediate function.
ast: {
(tpl: string, opts: ?PublicOpts): T,
(tpl: Array<string>, ...args: Array<mixed>): T,
},
(tpl: string, opts?: PublicOpts): T;
(tpl: TemplateStringsArray, ...args: Array<any>): T;
};
};

// Prebuild the options that will be used when parsing a `.ast` template.
Expand Down Expand Up @@ -69,7 +64,7 @@ export default function createTemplateBuilder<T>(
);
}
throw new Error(`Unexpected template param ${typeof tpl}`);
}: Function),
}) as TemplateBuilder<T>,
{
ast: (tpl, ...args) => {
if (typeof tpl === "string") {
Expand Down Expand Up @@ -98,7 +93,9 @@ export default function createTemplateBuilder<T>(
);
}

function extendedTrace<Arg, Result>(fn: Arg => Result): Arg => Result {
function extendedTrace<Arg, Result>(
fn: (_: Arg) => Result,
): (_: Arg) => Result {
// Since we lazy parse the template, we get the current stack so we have the
// original stack to append if it errors when parsing
let rootStack = "";
Expand Down
75 changes: 0 additions & 75 deletions packages/babel-template/src/formatters.js

This file was deleted.

70 changes: 70 additions & 0 deletions packages/babel-template/src/formatters.ts
@@ -0,0 +1,70 @@
import * as t from "@babel/types";

export type Formatter<T> = {
code: (source: string) => string;
validate: (ast: t.File) => void;
unwrap: (ast: t.File) => T;
};

function makeStatementFormatter<T>(
fn: (statements: Array<t.Statement>) => T,
): Formatter<T> {
return {
// We need to prepend a ";" to force statement parsing so that
// ExpressionStatement strings won't be parsed as directives.
// Alongside that, we also prepend a comment so that when a syntax error
// is encountered, the user will be less likely to get confused about
// where the random semicolon came from.
code: str => `/* @babel/template */;\n${str}`,
validate: () => {},
unwrap: (ast: t.File): T => {
return fn(ast.program.body.slice(1));
},
};
}

export const smart = makeStatementFormatter(body => {
if (body.length > 1) {
return body;
} else {
return body[0];
}
});

export const statements = makeStatementFormatter(body => body);

export const statement = makeStatementFormatter(body => {
// We do this validation when unwrapping since the replacement process
// could have added or removed statements.
if (body.length === 0) {
throw new Error("Found nothing to return.");
}
if (body.length > 1) {
throw new Error("Found multiple statements but wanted one");
}

return body[0];
});

export const expression: Formatter<t.Expression> = {
code: str => `(\n${str}\n)`,
validate: ast => {
if (ast.program.body.length > 1) {
throw new Error("Found multiple statements but wanted one");
}
if (expression.unwrap(ast).start === 0) {
throw new Error("Parse result included parens.");
}
},
unwrap: ({ program }) => {
const [stmt] = program.body;
t.assertExpressionStatement(stmt);
return stmt.expression;
},
};

export const program: Formatter<t.Program> = {
code: str => str,
validate: () => {},
unwrap: ast => ast.program,
};
31 changes: 0 additions & 31 deletions packages/babel-template/src/index.js

This file was deleted.

25 changes: 25 additions & 0 deletions packages/babel-template/src/index.ts
@@ -0,0 +1,25 @@
import * as formatters from "./formatters";
import createTemplateBuilder from "./builder";

export const smart = createTemplateBuilder(formatters.smart);
export const statement = createTemplateBuilder(formatters.statement);
export const statements = createTemplateBuilder(formatters.statements);
export const expression = createTemplateBuilder(formatters.expression);
export const program = createTemplateBuilder(formatters.program);

type DefaultTemplateBuilder = typeof smart & {
smart: typeof smart;
statement: typeof statement;
statements: typeof statements;
expression: typeof expression;
program: typeof program;
};

export default Object.assign(smart.bind(undefined) as DefaultTemplateBuilder, {
smart,
statement,
statements,
expression,
program,
ast: smart.ast,
});
@@ -1,24 +1,23 @@
// @flow

import type { Formatter } from "./formatters";
import { normalizeReplacements, type TemplateOpts } from "./options";
import type { TemplateReplacements, TemplateOpts } from "./options";
import { normalizeReplacements } from "./options";
import parseAndBuildMetadata from "./parse";
import populatePlaceholders from "./populate";

export default function literalTemplate<T>(
formatter: Formatter<T>,
tpl: Array<string>,
opts: TemplateOpts,
): (Array<mixed>) => mixed => T {
): (_: Array<unknown>) => (_: unknown) => T {
const { metadata, names } = buildLiteralData(formatter, tpl, opts);

return (arg: Array<mixed>) => {
const defaultReplacements = arg.reduce((acc, replacement, i) => {
acc[names[i]] = replacement;
return acc;
}, {});
return arg => {
const defaultReplacements: TemplateReplacements = {};
arg.forEach((replacement, i) => {
defaultReplacements[names[i]] = replacement;
});

return (arg: mixed) => {
return (arg: unknown) => {
const replacements = normalizeReplacements(arg);

if (replacements) {
Expand Down Expand Up @@ -88,7 +87,7 @@ function buildLiteralData<T>(
function buildTemplateCode(
tpl: Array<string>,
prefix: string,
): { names: Array<string>, code: string } {
): { names: Array<string>; code: string } {
const names = [];

let code = tpl[0];
Expand Down

0 comments on commit 089c200

Please sign in to comment.