Stuart Cook 20664a430e
Permit %%placeholder%% in left-hand-side of a let declaration (#12725)
* Permit %%placeholder%% in left-hand-side of a let declaration

* Test that "let" followed by modulo is still treated as an identifier

* More tests for edge-case handling of "let" with placeholders enabled
2021-02-01 20:00:21 -05:00

422 lines
13 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);
});
it("should preserve comments with a flag", function () {
const output = template(comments, { preserveComments: true })();
expect(generator(output).code).toBe(comments);
});
it("should preserve comments with a flag when using .ast", function () {
const output1 = template.ast(comments, { preserveComments: true });
const output2 = template({ preserveComments: true }).ast(comments);
expect(generator(output1).code).toBe(comments);
expect(generator(output2).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 return assertions in ImportDeclaration when using .ast", () => {
const result = template.ast(
`import json from "./foo.json" assert { type: "json" };`,
{
plugins: ["importAssertions"],
},
);
expect(result.assertions[0].type).toBe("ImportAttribute");
});
it("should return assertions in ExportNamedDeclaration when using .ast", () => {
const result = template.ast(
`export { foo2 } from "foo.json" assert { type: "json" };`,
{
plugins: ["importAssertions"],
},
);
expect(result.assertions[0].type).toBe("ImportAttribute");
});
it("should return assertions in ExportDefaultDeclaration when using .ast", () => {
const result = template.ast(
`export foo2 from "foo.json" assert { type: "json" };`,
{
plugins: ["importAssertions", "exportDefaultFrom"],
},
);
expect(result.assertions[0].type).toBe("ImportAttribute");
});
it("should return assertions in ExportAllDeclaration when using .ast", () => {
const result = template.ast(
`export * from "foo.json" assert { type: "json" };`,
{
plugins: ["importAssertions"],
},
);
expect(result.assertions[0].type).toBe("ImportAttribute");
});
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(".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(/Unexpected token.*/);
});
it("disallow manually enabled placeholders", () => {
expect(() => {
template(`%%FOO%%`, {
syntacticPlaceholders: false,
plugins: ["placeholders"],
})({
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;"`);
});
it("allows v8intrinsics", () => {
const output = template(`%DebugPrint(1)`, {
syntacticPlaceholders: false,
plugins: ["v8intrinsic"],
})();
expect(generator(output).code).toMatchInlineSnapshot(
`"%DebugPrint(1);"`,
);
});
});
describe("undefined", () => {
it("allows placeholders", () => {
const output = template(`%%FOO%%`)({
FOO: t.numericLiteral(1),
});
expect(generator(output).code).toMatchInlineSnapshot(`"1;"`);
});
it("replaces identifiers", () => {
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;"`);
});
});
});
it("works in var declaration", () => {
const output = template("var %%LHS%% = %%RHS%%")({
LHS: t.identifier("x"),
RHS: t.numericLiteral(7),
});
expect(generator(output).code).toMatchInlineSnapshot(`"var x = 7;"`);
});
it("works in const declaration", () => {
const output = template("const %%LHS%% = %%RHS%%")({
LHS: t.identifier("x"),
RHS: t.numericLiteral(7),
});
expect(generator(output).code).toMatchInlineSnapshot(`"const x = 7;"`);
});
it("works in let declaration", () => {
const output = template("let %%LHS%% = %%RHS%%")({
LHS: t.identifier("x"),
RHS: t.numericLiteral(7),
});
expect(generator(output).code).toMatchInlineSnapshot(`"let x = 7;"`);
});
});
});