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.
323 lines
9.9 KiB
JavaScript
323 lines
9.9 KiB
JavaScript
import generator from "../../babel-generator";
|
|
import template from "../lib";
|
|
import * as t from "@babel/types";
|
|
|
|
const comments = "// Sum two numbers\nconst add = (a, b) => a + b;";
|
|
|
|
describe("@babel/template", function() {
|
|
it("import statements are allowed by default", function() {
|
|
expect(function() {
|
|
template("import foo from 'foo'")({});
|
|
}).not.toThrow();
|
|
});
|
|
|
|
it("with statements are allowed with sourceType: script", function() {
|
|
expect(function() {
|
|
template("with({}){}", { sourceType: "script" })({});
|
|
}).not.toThrow();
|
|
});
|
|
|
|
it("should strip comments by default", function() {
|
|
const code = "const add = (a, b) => a + b;";
|
|
const output = template(comments)();
|
|
expect(generator(output).code).toBe(code);
|
|
});
|
|
|
|
it("should preserve comments with a flag", function() {
|
|
const output = template(comments, { preserveComments: true })();
|
|
expect(generator(output).code).toBe(comments);
|
|
});
|
|
|
|
describe("string-based", () => {
|
|
it("should handle replacing values from an object", () => {
|
|
const value = t.stringLiteral("some string value");
|
|
const result = template(`
|
|
if (SOME_VAR === "") {}
|
|
`)({
|
|
SOME_VAR: value,
|
|
});
|
|
|
|
expect(result.type).toBe("IfStatement");
|
|
expect(result.test.type).toBe("BinaryExpression");
|
|
expect(result.test.left).toBe(value);
|
|
});
|
|
|
|
it("should handle replacing values given an array", () => {
|
|
const value = t.stringLiteral("some string value");
|
|
const result = template(`
|
|
if ($0 === "") {}
|
|
`)([value]);
|
|
|
|
expect(result.type).toBe("IfStatement");
|
|
expect(result.test.type).toBe("BinaryExpression");
|
|
expect(result.test.left).toBe(value);
|
|
});
|
|
|
|
it("should handle replacing values with null to remove them", () => {
|
|
const result = template(`
|
|
callee(ARG);
|
|
`)({ ARG: null });
|
|
|
|
expect(result.type).toBe("ExpressionStatement");
|
|
expect(result.expression.type).toBe("CallExpression");
|
|
expect(result.expression.arguments).toEqual([]);
|
|
});
|
|
|
|
it("should handle replacing values that are string content", () => {
|
|
const result = template(`
|
|
("ARG");
|
|
`)({ ARG: "some new content" });
|
|
|
|
expect(result.type).toBe("ExpressionStatement");
|
|
expect(result.expression.type).toBe("StringLiteral");
|
|
expect(result.expression.value).toBe("some new content");
|
|
});
|
|
|
|
it("should automatically clone nodes if they are injected twice", () => {
|
|
const id = t.identifier("someIdent");
|
|
|
|
const result = template(`
|
|
ID;
|
|
ID;
|
|
`)({ ID: id });
|
|
|
|
expect(result[0].type).toBe("ExpressionStatement");
|
|
expect(result[0].expression).toBe(id);
|
|
expect(result[1].type).toBe("ExpressionStatement");
|
|
expect(result[1].expression).not.toBe(id);
|
|
expect(result[1].expression).toEqual(id);
|
|
});
|
|
|
|
it("should allow passing in a whitelist of replacement names", () => {
|
|
const id = t.identifier("someIdent");
|
|
const result = template(
|
|
`
|
|
some_id;
|
|
`,
|
|
{ placeholderWhitelist: new Set(["some_id"]) },
|
|
)({ some_id: id });
|
|
|
|
expect(result.type).toBe("ExpressionStatement");
|
|
expect(result.expression).toBe(id);
|
|
});
|
|
|
|
it("should allow passing in a RegExp to match replacement patterns", () => {
|
|
const id = t.identifier("someIdent");
|
|
const result = template(
|
|
`
|
|
ID;
|
|
ANOTHER_ID;
|
|
`,
|
|
{ placeholderPattern: /^ID$/ },
|
|
)({ ID: id });
|
|
|
|
expect(result[0].type).toBe("ExpressionStatement");
|
|
expect(result[0].expression).toBe(id);
|
|
expect(result[1].type).toBe("ExpressionStatement");
|
|
expect(result[1].expression.type).toBe("Identifier");
|
|
expect(result[1].expression.name).toBe("ANOTHER_ID");
|
|
});
|
|
|
|
it("should throw if unknown replacements are provided", () => {
|
|
expect(() => {
|
|
template(`
|
|
ID;
|
|
`)({ ID: t.identifier("someIdent"), ANOTHER_ID: null });
|
|
}).toThrow('Unknown substitution "ANOTHER_ID" given');
|
|
});
|
|
|
|
it("should throw if placeholders are not given explicit values", () => {
|
|
expect(() => {
|
|
template(`
|
|
ID;
|
|
ANOTHER_ID;
|
|
`)({ ID: t.identifier("someIdent") });
|
|
}).toThrow(
|
|
`Error: No substitution given for "ANOTHER_ID". If this is not meant to be a
|
|
placeholder you may want to consider passing one of the following options to @babel/template:
|
|
- { placeholderPattern: false, placeholderWhitelist: new Set(['ANOTHER_ID'])}
|
|
- { placeholderPattern: /^ANOTHER_ID$/ }`,
|
|
);
|
|
});
|
|
|
|
it("should return the AST directly when using .ast", () => {
|
|
const result = template.ast(`
|
|
if ("some string value" === "") {}
|
|
`);
|
|
|
|
expect(result.type).toBe("IfStatement");
|
|
expect(result.test.type).toBe("BinaryExpression");
|
|
expect(result.test.left.type).toBe("StringLiteral");
|
|
expect(result.test.left.value).toBe("some string value");
|
|
});
|
|
});
|
|
|
|
describe("literal-based", () => {
|
|
it("should handle replacing values from an object", () => {
|
|
const value = t.stringLiteral("some string value");
|
|
const result = template`
|
|
if (${value} === "") {}
|
|
`();
|
|
|
|
expect(result.type).toBe("IfStatement");
|
|
expect(result.test.type).toBe("BinaryExpression");
|
|
expect(result.test.left).toBe(value);
|
|
});
|
|
|
|
it("should handle replacing values with null to remove them", () => {
|
|
const result = template`
|
|
callee(${null});
|
|
`();
|
|
|
|
expect(result.type).toBe("ExpressionStatement");
|
|
expect(result.expression.type).toBe("CallExpression");
|
|
expect(result.expression.arguments).toEqual([]);
|
|
});
|
|
|
|
it("should handle replacing values that are string content", () => {
|
|
const result = template`
|
|
("${"some new content"}");
|
|
`();
|
|
|
|
expect(result.type).toBe("ExpressionStatement");
|
|
expect(result.expression.type).toBe("StringLiteral");
|
|
expect(result.expression.value).toBe("some new content");
|
|
});
|
|
|
|
it("should allow setting options by passing an object", () => {
|
|
const result = template({ sourceType: "script" })`
|
|
with({}){}
|
|
`();
|
|
|
|
expect(result.type).toBe("WithStatement");
|
|
});
|
|
|
|
it("should return the AST directly when using .ast", () => {
|
|
const value = t.stringLiteral("some string value");
|
|
const result = template.ast`
|
|
if (${value} === "") {}
|
|
`;
|
|
|
|
expect(result.type).toBe("IfStatement");
|
|
expect(result.test.type).toBe("BinaryExpression");
|
|
expect(result.test.left).toBe(value);
|
|
});
|
|
|
|
it("should replace JSX placeholder", () => {
|
|
const result = template.expression(
|
|
`
|
|
<TAG>{'content'}</TAG>
|
|
`,
|
|
{
|
|
plugins: ["jsx"],
|
|
},
|
|
)({
|
|
TAG: t.jsxIdentifier("div"),
|
|
});
|
|
|
|
expect(generator(result).code).toEqual("<div>{'content'}</div>");
|
|
});
|
|
});
|
|
|
|
describe.only(".syntacticPlaceholders", () => {
|
|
it("works in function body", () => {
|
|
const output = template(`function f() %%A%%`)({
|
|
A: t.blockStatement([]),
|
|
});
|
|
expect(generator(output).code).toMatchInlineSnapshot(`"function f() {}"`);
|
|
});
|
|
|
|
it("works in class body", () => {
|
|
const output = template(`class C %%A%%`)({
|
|
A: t.classBody([]),
|
|
});
|
|
expect(generator(output).code).toMatchInlineSnapshot(`"class C {}"`);
|
|
});
|
|
|
|
it("replaces lowercase names", () => {
|
|
const output = template(`%%foo%%`)({
|
|
foo: t.numericLiteral(1),
|
|
});
|
|
expect(generator(output).code).toMatchInlineSnapshot(`"1;"`);
|
|
});
|
|
|
|
it("pattern", () => {
|
|
expect(() => {
|
|
template(`%%A%% + %%B%%`, {
|
|
placeholderPattern: /B/,
|
|
})();
|
|
}).toThrow(/aren't compatible with '.syntacticPlaceholders: true'/);
|
|
});
|
|
|
|
it("whitelist", () => {
|
|
expect(() => {
|
|
template(`%%A%% + %%B%%`, {
|
|
placeholderPattern: false,
|
|
placeholderWhitelist: new Set(["B"]),
|
|
})();
|
|
}).toThrow(/aren't compatible with '.syntacticPlaceholders: true'/);
|
|
});
|
|
|
|
describe("option value", () => {
|
|
describe("true", () => {
|
|
it("allows placeholders", () => {
|
|
const output = template(`%%FOO%%`, { syntacticPlaceholders: true })({
|
|
FOO: t.numericLiteral(1),
|
|
});
|
|
expect(generator(output).code).toMatchInlineSnapshot(`"1;"`);
|
|
});
|
|
|
|
it("doesn't replace identifiers", () => {
|
|
expect(() => {
|
|
template(`FOO`, { syntacticPlaceholders: true })({
|
|
FOO: t.numericLiteral(1),
|
|
});
|
|
}).toThrow(/Unknown substitution/);
|
|
});
|
|
});
|
|
|
|
describe("false", () => {
|
|
it("disallow placeholders", () => {
|
|
expect(() => {
|
|
template(`%%FOO%%`, { syntacticPlaceholders: false })({
|
|
FOO: t.numericLiteral(1),
|
|
});
|
|
}).toThrow(/%%.*placeholders can't be used/);
|
|
});
|
|
|
|
it("replaces identifiers", () => {
|
|
const output = template(`FOO`, { syntacticPlaceholders: false })({
|
|
FOO: t.numericLiteral(1),
|
|
});
|
|
expect(generator(output).code).toMatchInlineSnapshot(`"1;"`);
|
|
});
|
|
});
|
|
|
|
describe("undefined", () => {
|
|
it("allows placeholders", () => {
|
|
const output = template(`%%FOO%%`)({
|
|
FOO: t.numericLiteral(1),
|
|
});
|
|
expect(generator(output).code).toMatchInlineSnapshot(`"1;"`);
|
|
});
|
|
|
|
it("replaces identifiers", () => {
|
|
expect(() => {
|
|
const output = template(`FOO`)({
|
|
FOO: t.numericLiteral(1),
|
|
});
|
|
expect(generator(output).code).toMatchInlineSnapshot(`"1;"`);
|
|
});
|
|
});
|
|
|
|
it("doesn't mix placeholder styles", () => {
|
|
const output = template(`FOO + %%FOO%%`)({
|
|
FOO: t.numericLiteral(1),
|
|
});
|
|
expect(generator(output).code).toMatchInlineSnapshot(`"FOO + 1;"`);
|
|
});
|
|
});
|
|
});
|
|
});
|
|
});
|