Nicolò Ribaudo c285d5409e
Add %%placeholders%% support to @babel/template (#9648)
This is the last step to make https://github.com/babel/babel/pull/9364 usable in Babel. I'm sorry for opening this PR so late, but I hope to get it in v7.4.

In this PR I added a new option to `@babel/template`, `syntacticPlaceholders: ?boolean`, which toggles between `%%foo%%` placeholders (when `true`) and `FOO` placeholders. If it isn't specified, Babel tries to be "smart" to avoid breaking backward compat: if `%%foo%%` is used `syntacticPlaceholders` defaults to `true`, otherwise to `false`.

0e58e252913efe84eba926cc9c9c19fb18d5c620 commit shows how some templates we used could be simplified by using this new placeholders syntax (we can't actually do it yet because we are importing `template` from `@babel/core` which could be an older version).

NOTE: Since I wanted to keep this PR as small as possible to make it easier to review, I didn't migrate `template.ast` to internally use the new syntax. It is an implementation detail, so it will be possible to change it in a patch release.
2019-03-18 21:23:10 +01:00

159 lines
4.3 KiB
JavaScript

// @flow
import type { Options as ParserOpts } from "@babel/parser/src/options";
export type { ParserOpts };
/**
* These are the options that 'babel-template' actually accepts and typechecks
* when called. All other options are passed through to the parser.
*/
export type PublicOpts = {
/**
* A set of placeholder names to automatically accept, ignoring the given
* pattern entirely.
*
* This option can be used when using %%foo%% style placeholders.
*/
placeholderWhitelist?: ?Set<string>,
/**
* A pattern to search for when looking for Identifier and StringLiteral
* nodes that can be replaced.
*
* 'false' will disable placeholder searching entirely, leaving only the
* 'placeholderWhitelist' value to find replacements.
*
* Defaults to /^[_$A-Z0-9]+$/.
*
* This option can be used when using %%foo%% style placeholders.
*/
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,
/**
* '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,
};
export type TemplateOpts = {|
parser: ParserOpts,
placeholderWhitelist: Set<string> | void,
placeholderPattern: RegExp | false | void,
preserveComments: boolean | void,
syntacticPlaceholders: boolean | void,
|};
export function merge(a: TemplateOpts, b: TemplateOpts): TemplateOpts {
const {
placeholderWhitelist = a.placeholderWhitelist,
placeholderPattern = a.placeholderPattern,
preserveComments = a.preserveComments,
syntacticPlaceholders = a.syntacticPlaceholders,
} = b;
return {
parser: {
...a.parser,
...b.parser,
},
placeholderWhitelist,
placeholderPattern,
preserveComments,
syntacticPlaceholders,
};
}
export function validate(opts: mixed): TemplateOpts {
if (opts != null && typeof opts !== "object") {
throw new Error("Unknown template options.");
}
const {
placeholderWhitelist,
placeholderPattern,
preserveComments,
syntacticPlaceholders,
...parser
} = opts || {};
if (placeholderWhitelist != null && !(placeholderWhitelist instanceof Set)) {
throw new Error(
"'.placeholderWhitelist' must be a Set, null, or undefined",
);
}
if (
placeholderPattern != null &&
!(placeholderPattern instanceof RegExp) &&
placeholderPattern !== false
) {
throw new Error(
"'.placeholderPattern' must be a RegExp, false, null, or undefined",
);
}
if (preserveComments != null && typeof preserveComments !== "boolean") {
throw new Error(
"'.preserveComments' must be a boolean, null, or undefined",
);
}
if (
syntacticPlaceholders != null &&
typeof syntacticPlaceholders !== "boolean"
) {
throw new Error(
"'.syntacticPlaceholders' must be a boolean, null, or undefined",
);
}
if (
syntacticPlaceholders === true &&
(placeholderWhitelist != null || placeholderPattern != null)
) {
throw new Error(
"'.placeholderWhitelist' and '.placeholderPattern' aren't compatible" +
" with '.syntacticPlaceholders: true'",
);
}
return {
parser,
placeholderWhitelist: placeholderWhitelist || undefined,
placeholderPattern:
placeholderPattern == null ? undefined : placeholderPattern,
preserveComments: preserveComments == null ? false : preserveComments,
syntacticPlaceholders:
syntacticPlaceholders == null ? undefined : syntacticPlaceholders,
};
}
export type PublicReplacements = { [string]: mixed } | Array<mixed>;
export type TemplateReplacements = { [string]: mixed } | void;
export function normalizeReplacements(
replacements: mixed,
): 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 || undefined;
}
throw new Error(
"Template replacements must be an array, object, null, or undefined",
);
}