Convert @babel/template from Flow to TS (#12317)
Co-authored-by: Nicolò Ribaudo <nicolo.ribaudo@gmail.com>
This commit is contained in:
parent
f80478c06d
commit
089c200c8b
@ -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,12 +1,5 @@
|
|||||||
// @flow
|
import { merge, validate } from "./options";
|
||||||
|
import type { TemplateOpts, PublicOpts, PublicReplacements } from "./options";
|
||||||
import {
|
|
||||||
merge,
|
|
||||||
validate,
|
|
||||||
type TemplateOpts,
|
|
||||||
type PublicOpts,
|
|
||||||
type PublicReplacements,
|
|
||||||
} from "./options";
|
|
||||||
import type { Formatter } from "./formatters";
|
import type { Formatter } from "./formatters";
|
||||||
|
|
||||||
import stringTemplate from "./string";
|
import stringTemplate from "./string";
|
||||||
@ -14,20 +7,22 @@ import literalTemplate from "./literal";
|
|||||||
|
|
||||||
export type TemplateBuilder<T> = {
|
export type TemplateBuilder<T> = {
|
||||||
// Build a new builder, merging the given options with the previous ones.
|
// 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.
|
// 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.
|
// 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
|
// Allow users to explicitly create templates that produce ASTs, skipping
|
||||||
// the need for an intermediate function.
|
// the need for an intermediate function.
|
||||||
ast: {
|
ast: {
|
||||||
(tpl: string, opts: ?PublicOpts): T,
|
(tpl: string, opts?: PublicOpts): T;
|
||||||
(tpl: Array<string>, ...args: Array<mixed>): T,
|
(tpl: TemplateStringsArray, ...args: Array<any>): T;
|
||||||
},
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
// Prebuild the options that will be used when parsing a `.ast` template.
|
// Prebuild the options that will be used when parsing a `.ast` template.
|
||||||
@ -69,7 +64,7 @@ export default function createTemplateBuilder<T>(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
throw new Error(`Unexpected template param ${typeof tpl}`);
|
throw new Error(`Unexpected template param ${typeof tpl}`);
|
||||||
}: Function),
|
}) as TemplateBuilder<T>,
|
||||||
{
|
{
|
||||||
ast: (tpl, ...args) => {
|
ast: (tpl, ...args) => {
|
||||||
if (typeof tpl === "string") {
|
if (typeof tpl === "string") {
|
||||||
@ -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
|
// Since we lazy parse the template, we get the current stack so we have the
|
||||||
// original stack to append if it errors when parsing
|
// original stack to append if it errors when parsing
|
||||||
let rootStack = "";
|
let rootStack = "";
|
||||||
@ -1,75 +0,0 @@
|
|||||||
// @flow
|
|
||||||
|
|
||||||
export type Formatter<T> = {
|
|
||||||
code: string => string,
|
|
||||||
validate: BabelNodeFile => void,
|
|
||||||
unwrap: BabelNodeFile => T,
|
|
||||||
};
|
|
||||||
|
|
||||||
function makeStatementFormatter<T>(
|
|
||||||
fn: (Array<BabelNodeStatement>) => 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: BabelNodeFile): T => {
|
|
||||||
return fn(ast.program.body.slice(1));
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export const smart: Formatter<
|
|
||||||
Array<BabelNodeStatement> | BabelNodeStatement,
|
|
||||||
> = makeStatementFormatter(body => {
|
|
||||||
if (body.length > 1) {
|
|
||||||
return body;
|
|
||||||
} else {
|
|
||||||
return body[0];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
export const statements: Formatter<
|
|
||||||
Array<BabelNodeStatement>,
|
|
||||||
> = makeStatementFormatter(body => body);
|
|
||||||
|
|
||||||
export const statement: Formatter<BabelNodeStatement> = 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<BabelNodeExpression> = {
|
|
||||||
code: str => `(\n${str}\n)`,
|
|
||||||
validate: ({ program }) => {
|
|
||||||
if (program.body.length > 1) {
|
|
||||||
throw new Error("Found multiple statements but wanted one");
|
|
||||||
}
|
|
||||||
// $FlowFixMe
|
|
||||||
const expression = program.body[0].expression;
|
|
||||||
if (expression.start === 0) {
|
|
||||||
throw new Error("Parse result included parens.");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// $FlowFixMe
|
|
||||||
unwrap: ast => ast.program.body[0].expression,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const program: Formatter<BabelNodeProgram> = {
|
|
||||||
code: str => str,
|
|
||||||
validate: () => {},
|
|
||||||
unwrap: ast => ast.program,
|
|
||||||
};
|
|
||||||
70
packages/babel-template/src/formatters.ts
Normal file
70
packages/babel-template/src/formatters.ts
Normal file
@ -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,
|
||||||
|
};
|
||||||
@ -1,31 +0,0 @@
|
|||||||
// @flow
|
|
||||||
|
|
||||||
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,
|
|
||||||
ast: typeof smart.ast,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Object.assign(
|
|
||||||
((smart.bind(undefined): any): DefaultTemplateBuilder),
|
|
||||||
{
|
|
||||||
smart,
|
|
||||||
statement,
|
|
||||||
statements,
|
|
||||||
expression,
|
|
||||||
program,
|
|
||||||
ast: smart.ast,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
25
packages/babel-template/src/index.ts
Normal file
25
packages/babel-template/src/index.ts
Normal file
@ -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,7 +1,6 @@
|
|||||||
// @flow
|
|
||||||
|
|
||||||
import type { Formatter } from "./formatters";
|
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 parseAndBuildMetadata from "./parse";
|
||||||
import populatePlaceholders from "./populate";
|
import populatePlaceholders from "./populate";
|
||||||
|
|
||||||
@ -9,16 +8,16 @@ export default function literalTemplate<T>(
|
|||||||
formatter: Formatter<T>,
|
formatter: Formatter<T>,
|
||||||
tpl: Array<string>,
|
tpl: Array<string>,
|
||||||
opts: TemplateOpts,
|
opts: TemplateOpts,
|
||||||
): (Array<mixed>) => mixed => T {
|
): (_: Array<unknown>) => (_: unknown) => T {
|
||||||
const { metadata, names } = buildLiteralData(formatter, tpl, opts);
|
const { metadata, names } = buildLiteralData(formatter, tpl, opts);
|
||||||
|
|
||||||
return (arg: Array<mixed>) => {
|
return arg => {
|
||||||
const defaultReplacements = arg.reduce((acc, replacement, i) => {
|
const defaultReplacements: TemplateReplacements = {};
|
||||||
acc[names[i]] = replacement;
|
arg.forEach((replacement, i) => {
|
||||||
return acc;
|
defaultReplacements[names[i]] = replacement;
|
||||||
}, {});
|
});
|
||||||
|
|
||||||
return (arg: mixed) => {
|
return (arg: unknown) => {
|
||||||
const replacements = normalizeReplacements(arg);
|
const replacements = normalizeReplacements(arg);
|
||||||
|
|
||||||
if (replacements) {
|
if (replacements) {
|
||||||
@ -88,7 +87,7 @@ function buildLiteralData<T>(
|
|||||||
function buildTemplateCode(
|
function buildTemplateCode(
|
||||||
tpl: Array<string>,
|
tpl: Array<string>,
|
||||||
prefix: string,
|
prefix: string,
|
||||||
): { names: Array<string>, code: string } {
|
): { names: Array<string>; code: string } {
|
||||||
const names = [];
|
const names = [];
|
||||||
|
|
||||||
let code = tpl[0];
|
let code = tpl[0];
|
||||||
@ -1,6 +1,4 @@
|
|||||||
// @flow
|
import type { ParserOptions as ParserOpts } from "@babel/parser";
|
||||||
|
|
||||||
import type { Options as ParserOpts } from "@babel/parser/src/options";
|
|
||||||
|
|
||||||
export type { ParserOpts };
|
export type { ParserOpts };
|
||||||
|
|
||||||
@ -15,8 +13,7 @@ export type PublicOpts = {
|
|||||||
*
|
*
|
||||||
* This option can be used when using %%foo%% style placeholders.
|
* 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
|
* A pattern to search for when looking for Identifier and StringLiteral
|
||||||
* nodes that can be replaced.
|
* nodes that can be replaced.
|
||||||
@ -28,30 +25,28 @@ export type PublicOpts = {
|
|||||||
*
|
*
|
||||||
* This option can be used when using %%foo%% style placeholders.
|
* 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,
|
* 'true' to pass through comments from the template into the resulting AST,
|
||||||
* or 'false' to automatically discard comments. Defaults to 'false'.
|
* or 'false' to automatically discard comments. Defaults to 'false'.
|
||||||
*/
|
*/
|
||||||
preserveComments?: ?boolean,
|
preserveComments?: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 'true' to use %%foo%% style placeholders, 'false' to use legacy placeholders
|
* 'true' to use %%foo%% style placeholders, 'false' to use legacy placeholders
|
||||||
* described by placeholderPattern or placeholderWhitelist.
|
* described by placeholderPattern or placeholderWhitelist.
|
||||||
* When it is not set, it behaves as 'true' if there are syntactic placeholders,
|
* When it is not set, it behaves as 'true' if there are syntactic placeholders,
|
||||||
* otherwise as 'false'.
|
* otherwise as 'false'.
|
||||||
*/
|
*/
|
||||||
syntacticPlaceholders?: ?boolean,
|
syntacticPlaceholders?: boolean | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TemplateOpts = {|
|
export type TemplateOpts = {
|
||||||
parser: ParserOpts,
|
parser: ParserOpts;
|
||||||
placeholderWhitelist: Set<string> | void,
|
placeholderWhitelist?: Set<string>;
|
||||||
placeholderPattern: RegExp | false | void,
|
placeholderPattern?: RegExp | false;
|
||||||
preserveComments: boolean | void,
|
preserveComments?: boolean;
|
||||||
syntacticPlaceholders: boolean | void,
|
syntacticPlaceholders?: boolean;
|
||||||
|};
|
};
|
||||||
|
|
||||||
export function merge(a: TemplateOpts, b: TemplateOpts): TemplateOpts {
|
export function merge(a: TemplateOpts, b: TemplateOpts): TemplateOpts {
|
||||||
const {
|
const {
|
||||||
@ -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") {
|
if (opts != null && typeof opts !== "object") {
|
||||||
throw new Error("Unknown template options.");
|
throw new Error("Unknown template options.");
|
||||||
}
|
}
|
||||||
@ -84,7 +79,7 @@ export function validate(opts: mixed): TemplateOpts {
|
|||||||
preserveComments,
|
preserveComments,
|
||||||
syntacticPlaceholders,
|
syntacticPlaceholders,
|
||||||
...parser
|
...parser
|
||||||
} = opts || {};
|
} = opts || ({} as any);
|
||||||
|
|
||||||
if (placeholderWhitelist != null && !(placeholderWhitelist instanceof Set)) {
|
if (placeholderWhitelist != null && !(placeholderWhitelist instanceof Set)) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
@ -137,11 +132,11 @@ export function validate(opts: mixed): TemplateOpts {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export type PublicReplacements = { [string]: mixed } | Array<mixed>;
|
export type PublicReplacements = { [x: string]: unknown } | Array<unknown>;
|
||||||
export type TemplateReplacements = { [string]: mixed } | void;
|
export type TemplateReplacements = { [x: string]: unknown } | void;
|
||||||
|
|
||||||
export function normalizeReplacements(
|
export function normalizeReplacements(
|
||||||
replacements: mixed,
|
replacements: unknown,
|
||||||
): TemplateReplacements {
|
): TemplateReplacements {
|
||||||
if (Array.isArray(replacements)) {
|
if (Array.isArray(replacements)) {
|
||||||
return replacements.reduce((acc, replacement, i) => {
|
return replacements.reduce((acc, replacement, i) => {
|
||||||
@ -149,7 +144,7 @@ export function normalizeReplacements(
|
|||||||
return acc;
|
return acc;
|
||||||
}, {});
|
}, {});
|
||||||
} else if (typeof replacements === "object" || replacements == null) {
|
} else if (typeof replacements === "object" || replacements == null) {
|
||||||
return (replacements: any) || undefined;
|
return (replacements as any) || undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new Error(
|
throw new Error(
|
||||||
@ -1,4 +1,3 @@
|
|||||||
// @flow
|
|
||||||
import * as t from "@babel/types";
|
import * as t from "@babel/types";
|
||||||
import type { TraversalAncestors, TraversalHandler } from "@babel/types";
|
import type { TraversalAncestors, TraversalHandler } from "@babel/types";
|
||||||
import { parse } from "@babel/parser";
|
import { parse } from "@babel/parser";
|
||||||
@ -7,18 +6,18 @@ import type { TemplateOpts, ParserOpts } from "./options";
|
|||||||
import type { Formatter } from "./formatters";
|
import type { Formatter } from "./formatters";
|
||||||
|
|
||||||
export type Metadata = {
|
export type Metadata = {
|
||||||
ast: BabelNodeFile,
|
ast: t.File;
|
||||||
placeholders: Array<Placeholder>,
|
placeholders: Array<Placeholder>;
|
||||||
placeholderNames: Set<string>,
|
placeholderNames: Set<string>;
|
||||||
};
|
};
|
||||||
|
|
||||||
type PlaceholderType = "string" | "param" | "statement" | "other";
|
type PlaceholderType = "string" | "param" | "statement" | "other";
|
||||||
export type Placeholder = {|
|
export type Placeholder = {
|
||||||
name: string,
|
name: string;
|
||||||
resolve: BabelNodeFile => { parent: BabelNode, key: string, index?: number },
|
resolve: (a: t.File) => { parent: t.Node; key: string; index?: number };
|
||||||
type: PlaceholderType,
|
type: PlaceholderType;
|
||||||
isDuplicate: boolean,
|
isDuplicate: boolean;
|
||||||
|};
|
};
|
||||||
|
|
||||||
const PATTERN = /^[_$A-Z0-9]+$/;
|
const PATTERN = /^[_$A-Z0-9]+$/;
|
||||||
|
|
||||||
@ -44,15 +43,15 @@ export default function parseAndBuildMetadata<T>(
|
|||||||
|
|
||||||
const syntactic = {
|
const syntactic = {
|
||||||
placeholders: [],
|
placeholders: [],
|
||||||
placeholderNames: new Set(),
|
placeholderNames: new Set<string>(),
|
||||||
};
|
};
|
||||||
const legacy = {
|
const legacy = {
|
||||||
placeholders: [],
|
placeholders: [],
|
||||||
placeholderNames: new Set(),
|
placeholderNames: new Set<string>(),
|
||||||
};
|
};
|
||||||
const isLegacyRef = { value: undefined };
|
const isLegacyRef = { value: undefined };
|
||||||
|
|
||||||
t.traverse(ast, (placeholderVisitorHandler: TraversalHandler<*>), {
|
t.traverse(ast, placeholderVisitorHandler as TraversalHandler<any>, {
|
||||||
syntactic,
|
syntactic,
|
||||||
legacy,
|
legacy,
|
||||||
isLegacyRef,
|
isLegacyRef,
|
||||||
@ -68,11 +67,11 @@ export default function parseAndBuildMetadata<T>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
function placeholderVisitorHandler(
|
function placeholderVisitorHandler(
|
||||||
node: BabelNode,
|
node: t.Node,
|
||||||
ancestors: TraversalAncestors,
|
ancestors: TraversalAncestors,
|
||||||
state: MetadataState,
|
state: MetadataState,
|
||||||
) {
|
) {
|
||||||
let name;
|
let name: string;
|
||||||
|
|
||||||
if (t.isPlaceholder(node)) {
|
if (t.isPlaceholder(node)) {
|
||||||
if (state.syntacticPlaceholders === false) {
|
if (state.syntacticPlaceholders === false) {
|
||||||
@ -81,16 +80,16 @@ function placeholderVisitorHandler(
|
|||||||
"'.syntacticPlaceholders' is false.",
|
"'.syntacticPlaceholders' is false.",
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
name = ((node: any).name: BabelNodeIdentifier).name;
|
name = node.name.name;
|
||||||
state.isLegacyRef.value = false;
|
state.isLegacyRef.value = false;
|
||||||
}
|
}
|
||||||
} else if (state.isLegacyRef.value === false || state.syntacticPlaceholders) {
|
} else if (state.isLegacyRef.value === false || state.syntacticPlaceholders) {
|
||||||
return;
|
return;
|
||||||
} else if (t.isIdentifier(node) || t.isJSXIdentifier(node)) {
|
} else if (t.isIdentifier(node) || t.isJSXIdentifier(node)) {
|
||||||
name = ((node: any): BabelNodeIdentifier).name;
|
name = node.name;
|
||||||
state.isLegacyRef.value = true;
|
state.isLegacyRef.value = true;
|
||||||
} else if (t.isStringLiteral(node)) {
|
} else if (t.isStringLiteral(node)) {
|
||||||
name = ((node: any): BabelNodeStringLiteral).value;
|
name = node.value;
|
||||||
state.isLegacyRef.value = true;
|
state.isLegacyRef.value = true;
|
||||||
} else {
|
} else {
|
||||||
return;
|
return;
|
||||||
@ -156,15 +155,15 @@ function placeholderVisitorHandler(
|
|||||||
placeholderNames.add(name);
|
placeholderNames.add(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveAncestors(ast: BabelNodeFile, ancestors: TraversalAncestors) {
|
function resolveAncestors(ast: t.File, ancestors: TraversalAncestors) {
|
||||||
let parent: BabelNode = ast;
|
let parent: t.Node = ast;
|
||||||
for (let i = 0; i < ancestors.length - 1; i++) {
|
for (let i = 0; i < ancestors.length - 1; i++) {
|
||||||
const { key, index } = ancestors[i];
|
const { key, index } = ancestors[i];
|
||||||
|
|
||||||
if (index === undefined) {
|
if (index === undefined) {
|
||||||
parent = (parent: any)[key];
|
parent = (parent as any)[key];
|
||||||
} else {
|
} else {
|
||||||
parent = (parent: any)[key][index];
|
parent = (parent as any)[key][index];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -175,24 +174,26 @@ function resolveAncestors(ast: BabelNodeFile, ancestors: TraversalAncestors) {
|
|||||||
|
|
||||||
type MetadataState = {
|
type MetadataState = {
|
||||||
syntactic: {
|
syntactic: {
|
||||||
placeholders: Array<Placeholder>,
|
placeholders: Array<Placeholder>;
|
||||||
placeholderNames: Set<string>,
|
placeholderNames: Set<string>;
|
||||||
},
|
};
|
||||||
legacy: {
|
legacy: {
|
||||||
placeholders: Array<Placeholder>,
|
placeholders: Array<Placeholder>;
|
||||||
placeholderNames: Set<string>,
|
placeholderNames: Set<string>;
|
||||||
},
|
};
|
||||||
isLegacyRef: { value: boolean | void },
|
isLegacyRef: {
|
||||||
placeholderWhitelist: Set<string> | void,
|
value?: boolean;
|
||||||
placeholderPattern: RegExp | false | void,
|
};
|
||||||
syntacticPlaceholders: boolean | void,
|
placeholderWhitelist?: Set<string>;
|
||||||
|
placeholderPattern?: RegExp | false;
|
||||||
|
syntacticPlaceholders?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
function parseWithCodeFrame(
|
function parseWithCodeFrame(
|
||||||
code: string,
|
code: string,
|
||||||
parserOpts: ParserOpts,
|
parserOpts: ParserOpts,
|
||||||
syntacticPlaceholders?: boolean,
|
syntacticPlaceholders?: boolean,
|
||||||
): BabelNodeFile {
|
): t.File {
|
||||||
const plugins = (parserOpts.plugins || []).slice();
|
const plugins = (parserOpts.plugins || []).slice();
|
||||||
if (syntacticPlaceholders !== false) {
|
if (syntacticPlaceholders !== false) {
|
||||||
plugins.push("placeholders");
|
plugins.push("placeholders");
|
||||||
@ -207,7 +208,6 @@ function parseWithCodeFrame(
|
|||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// $FlowFixMe - The parser AST is not the same type as the babel-types type.
|
|
||||||
return parse(code, parserOpts);
|
return parse(code, parserOpts);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const loc = err.loc;
|
const loc = err.loc;
|
||||||
@ -1,4 +1,3 @@
|
|||||||
// @flow
|
|
||||||
import * as t from "@babel/types";
|
import * as t from "@babel/types";
|
||||||
|
|
||||||
import type { TemplateReplacements } from "./options";
|
import type { TemplateReplacements } from "./options";
|
||||||
@ -7,7 +6,7 @@ import type { Metadata, Placeholder } from "./parse";
|
|||||||
export default function populatePlaceholders(
|
export default function populatePlaceholders(
|
||||||
metadata: Metadata,
|
metadata: Metadata,
|
||||||
replacements: TemplateReplacements,
|
replacements: TemplateReplacements,
|
||||||
): BabelNodeFile {
|
): t.File {
|
||||||
const ast = t.cloneNode(metadata.ast);
|
const ast = t.cloneNode(metadata.ast);
|
||||||
|
|
||||||
if (replacements) {
|
if (replacements) {
|
||||||
@ -55,7 +54,7 @@ export default function populatePlaceholders(
|
|||||||
|
|
||||||
function applyReplacement(
|
function applyReplacement(
|
||||||
placeholder: Placeholder,
|
placeholder: Placeholder,
|
||||||
ast: BabelNodeFile,
|
ast: t.File,
|
||||||
replacement: any,
|
replacement: any,
|
||||||
) {
|
) {
|
||||||
// Track inserted nodes and clone them if they are inserted more than
|
// Track inserted nodes and clone them if they are inserted more than
|
||||||
@ -86,7 +85,7 @@ function applyReplacement(
|
|||||||
} else if (typeof replacement === "string") {
|
} else if (typeof replacement === "string") {
|
||||||
replacement = t.expressionStatement(t.identifier(replacement));
|
replacement = t.expressionStatement(t.identifier(replacement));
|
||||||
} else if (!t.isStatement(replacement)) {
|
} else if (!t.isStatement(replacement)) {
|
||||||
replacement = t.expressionStatement((replacement: any));
|
replacement = t.expressionStatement(replacement as any);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (replacement && !Array.isArray(replacement)) {
|
if (replacement && !Array.isArray(replacement)) {
|
||||||
@ -94,7 +93,7 @@ function applyReplacement(
|
|||||||
replacement = t.identifier(replacement);
|
replacement = t.identifier(replacement);
|
||||||
}
|
}
|
||||||
if (!t.isStatement(replacement)) {
|
if (!t.isStatement(replacement)) {
|
||||||
replacement = t.expressionStatement((replacement: any));
|
replacement = t.expressionStatement(replacement as any);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -116,9 +115,9 @@ function applyReplacement(
|
|||||||
if (index === undefined) {
|
if (index === undefined) {
|
||||||
t.validate(parent, key, replacement);
|
t.validate(parent, key, replacement);
|
||||||
|
|
||||||
(parent: any)[key] = replacement;
|
(parent as any)[key] = replacement;
|
||||||
} else {
|
} else {
|
||||||
const items: Array<BabelNode> = (parent: any)[key].slice();
|
const items: Array<t.Node> = (parent as any)[key].slice();
|
||||||
|
|
||||||
if (placeholder.type === "statement" || placeholder.type === "param") {
|
if (placeholder.type === "statement" || placeholder.type === "param") {
|
||||||
if (replacement == null) {
|
if (replacement == null) {
|
||||||
@ -133,6 +132,6 @@ function applyReplacement(
|
|||||||
}
|
}
|
||||||
|
|
||||||
t.validate(parent, key, items);
|
t.validate(parent, key, items);
|
||||||
(parent: any)[key] = items;
|
(parent as any)[key] = items;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,6 +1,6 @@
|
|||||||
// @flow
|
|
||||||
import type { Formatter } from "./formatters";
|
import type { Formatter } from "./formatters";
|
||||||
import { normalizeReplacements, type TemplateOpts } from "./options";
|
import type { TemplateOpts } from "./options";
|
||||||
|
import { normalizeReplacements } from "./options";
|
||||||
import parseAndBuildMetadata from "./parse";
|
import parseAndBuildMetadata from "./parse";
|
||||||
import populatePlaceholders from "./populate";
|
import populatePlaceholders from "./populate";
|
||||||
|
|
||||||
@ -8,12 +8,12 @@ export default function stringTemplate<T>(
|
|||||||
formatter: Formatter<T>,
|
formatter: Formatter<T>,
|
||||||
code: string,
|
code: string,
|
||||||
opts: TemplateOpts,
|
opts: TemplateOpts,
|
||||||
): mixed => T {
|
): (arg?: unknown) => T {
|
||||||
code = formatter.code(code);
|
code = formatter.code(code);
|
||||||
|
|
||||||
let metadata;
|
let metadata;
|
||||||
|
|
||||||
return (arg?: mixed) => {
|
return (arg?: unknown) => {
|
||||||
const replacements = normalizeReplacements(arg);
|
const replacements = normalizeReplacements(arg);
|
||||||
|
|
||||||
if (!metadata) metadata = parseAndBuildMetadata(formatter, code, opts);
|
if (!metadata) metadata = parseAndBuildMetadata(formatter, code, opts);
|
||||||
Loading…
x
Reference in New Issue
Block a user