Skip to content

Commit

Permalink
Convert @babel/template from Flow to TS
Browse files Browse the repository at this point in the history
Co-authored-by: Bogdan Savluk <savluk.bogdan@gmail.com>
  • Loading branch information
nicolo-ribaudo and zxbodya committed Nov 6, 2020
1 parent 5b69956 commit c90459c
Show file tree
Hide file tree
Showing 10 changed files with 173 additions and 143 deletions.
36 changes: 36 additions & 0 deletions lib/babel-packages.js.flow
Original file line number Diff line number Diff line change
@@ -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,
};
}
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
// @flow
import type * as t from "@babel/types";

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

function makeStatementFormatter<T>(
fn: (Array<BabelNodeStatement>) => T,
fn: (statements: Array<t.Statement>) => T,
): Formatter<T> {
return {
// We need to prepend a ";" to force statement parsing so that
Expand All @@ -17,14 +17,14 @@ function makeStatementFormatter<T>(
// where the random semicolon came from.
code: str => `/* @babel/template */;\n${str}`,
validate: () => {},
unwrap: (ast: BabelNodeFile): T => {
unwrap: (ast: t.File): T => {
return fn(ast.program.body.slice(1));
},
};
}

export const smart: Formatter<
Array<BabelNodeStatement> | BabelNodeStatement,
Array<t.Statement> | t.Statement
> = makeStatementFormatter(body => {
if (body.length > 1) {
return body;
Expand All @@ -33,11 +33,11 @@ export const smart: Formatter<
}
});

export const statements: Formatter<
Array<BabelNodeStatement>,
> = makeStatementFormatter(body => body);
export const statements: Formatter<Array<t.Statement>> = makeStatementFormatter(
body => body,
);

export const statement: Formatter<BabelNodeStatement> = makeStatementFormatter(
export const statement: Formatter<t.Statement> = makeStatementFormatter(
body => {
// We do this validation when unwrapping since the replacement process
// could have added or removed statements.
Expand All @@ -52,23 +52,23 @@ export const statement: Formatter<BabelNodeStatement> = makeStatementFormatter(
},
);

export const expression: Formatter<BabelNodeExpression> = {
export const expression: Formatter<t.Expression> = {
code: str => `(\n${str}\n)`,
validate: ({ program }) => {
if (program.body.length > 1) {
throw new Error("Found multiple statements but wanted one");
}
// $FlowFixMe
// @ts-expect-error todo(flow->ts): consider adding assertion that body[0] indeed has expression statement
const expression = program.body[0].expression;
if (expression.start === 0) {
throw new Error("Parse result included parens.");
}
},
// $FlowFixMe
// @ts-expect-error todo(flow->ts): consider adding assertion that body[0] indeed has expression statement
unwrap: ast => ast.program.body[0].expression,
};

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

This file was deleted.

35 changes: 35 additions & 0 deletions packages/babel-template/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import * as formatters from "./formatters";
import createTemplateBuilder from "./builder";
import type * as t from "@babel/types";
//const a: string = 2;
export const smart = createTemplateBuilder<t.Statement | t.Statement[]>(
formatters.smart,
);
export const statement = createTemplateBuilder<t.Statement>(
formatters.statement,
);
export const statements = createTemplateBuilder<t.Statement[]>(
formatters.statements,
);
export const expression = createTemplateBuilder<t.Expression>(
formatters.expression,
);
export const program = createTemplateBuilder<t.Program>(formatters.program);

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

export default Object.assign(smart.bind(undefined) as DefaultTemplateBuilder, {
smart,
statement,
statements,
expression,
program,
ast: smart.ast,
});
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
// @flow

import type { Options as ParserOpts } from "@babel/parser/src/options";
import type { ParserOptions as ParserOpts } from "@babel/parser";

export type { ParserOpts };

Expand All @@ -15,8 +13,7 @@ export type PublicOpts = {
*
* This option can be used when using %%foo%% style placeholders.
*/
placeholderWhitelist?: ?Set<string>,

placeholderWhitelist?: Set<string>;
/**
* A pattern to search for when looking for Identifier and StringLiteral
* nodes that can be replaced.
Expand All @@ -28,30 +25,28 @@ export type PublicOpts = {
*
* This option can be used when using %%foo%% style placeholders.
*/
placeholderPattern?: ?(RegExp | false),

placeholderPattern?: RegExp | false;
/**
* 'true' to pass through comments from the template into the resulting AST,
* or 'false' to automatically discard comments. Defaults to 'false'.
*/
preserveComments?: ?boolean,

preserveComments?: boolean;
/**
* 'true' to use %%foo%% style placeholders, 'false' to use legacy placeholders
* described by placeholderPattern or placeholderWhitelist.
* When it is not set, it behaves as 'true' if there are syntactic placeholders,
* otherwise as 'false'.
*/
syntacticPlaceholders?: ?boolean,
syntacticPlaceholders?: boolean | null;
};

export type TemplateOpts = {|
parser: ParserOpts,
placeholderWhitelist: Set<string> | void,
placeholderPattern: RegExp | false | void,
preserveComments: boolean | void,
syntacticPlaceholders: boolean | void,
|};
export type TemplateOpts = {
parser: ParserOpts;
placeholderWhitelist?: Set<string>;
placeholderPattern?: RegExp | false;
preserveComments?: boolean;
syntacticPlaceholders?: boolean;
};

export function merge(a: TemplateOpts, b: TemplateOpts): TemplateOpts {
const {
Expand All @@ -73,7 +68,7 @@ export function merge(a: TemplateOpts, b: TemplateOpts): TemplateOpts {
};
}

export function validate(opts: mixed): TemplateOpts {
export function validate(opts: unknown): TemplateOpts {
if (opts != null && typeof opts !== "object") {
throw new Error("Unknown template options.");
}
Expand All @@ -84,7 +79,7 @@ export function validate(opts: mixed): TemplateOpts {
preserveComments,
syntacticPlaceholders,
...parser
} = opts || {};
} = opts || ({} as any);

if (placeholderWhitelist != null && !(placeholderWhitelist instanceof Set)) {
throw new Error(
Expand Down Expand Up @@ -137,19 +132,19 @@ export function validate(opts: mixed): TemplateOpts {
};
}

export type PublicReplacements = { [string]: mixed } | Array<mixed>;
export type TemplateReplacements = { [string]: mixed } | void;
export type PublicReplacements = { [x: string]: unknown } | Array<unknown>;
export type TemplateReplacements = { [x: string]: unknown } | void;

export function normalizeReplacements(
replacements: mixed,
replacements: unknown,
): TemplateReplacements {
if (Array.isArray(replacements)) {
return replacements.reduce((acc, replacement, i) => {
acc["$" + i] = replacement;
return acc;
}, {});
} else if (typeof replacements === "object" || replacements == null) {
return (replacements: any) || undefined;
return (replacements as any) || undefined;
}

throw new Error(
Expand Down

0 comments on commit c90459c

Please sign in to comment.