Reimplement 'babel-template' with better caching and tagged literal utilities.
This commit is contained in:
@@ -1,34 +1,202 @@
|
||||
import generator from "../../babel-generator";
|
||||
import template from "../lib";
|
||||
import chai from "chai";
|
||||
import { expect } from "chai";
|
||||
import * as t from "babel-types";
|
||||
|
||||
const comments = "// Sum two numbers\nconst add = (a, b) => a + b;";
|
||||
|
||||
describe("templating", function() {
|
||||
describe("babel-template", function() {
|
||||
it("import statements are allowed by default", function() {
|
||||
chai
|
||||
.expect(function() {
|
||||
template("import foo from 'foo'")({});
|
||||
})
|
||||
.not.to.throw();
|
||||
expect(function() {
|
||||
template("import foo from 'foo'")({});
|
||||
}).not.to.throw();
|
||||
});
|
||||
|
||||
it("with statements are allowed with sourceType: script", function() {
|
||||
chai
|
||||
.expect(function() {
|
||||
template("with({}){}", { sourceType: "script" })({});
|
||||
})
|
||||
.not.to.throw();
|
||||
expect(function() {
|
||||
template("with({}){}", { sourceType: "script" })({});
|
||||
}).not.to.throw();
|
||||
});
|
||||
|
||||
it("should strip comments by default", function() {
|
||||
const code = "const add = (a, b) => a + b;";
|
||||
const output = template(comments)();
|
||||
chai.expect(generator(output).code).to.be.equal(code);
|
||||
expect(generator(output).code).to.be.equal(code);
|
||||
});
|
||||
|
||||
it("should preserve comments with a flag", function() {
|
||||
const output = template(comments, { preserveComments: true })();
|
||||
chai.expect(generator(output).code).to.be.equal(comments);
|
||||
expect(generator(output).code).to.be.equal(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).to.equal("IfStatement");
|
||||
expect(result.test.type).to.equal("BinaryExpression");
|
||||
expect(result.test.left).to.equal(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).to.equal("IfStatement");
|
||||
expect(result.test.type).to.equal("BinaryExpression");
|
||||
expect(result.test.left).to.equal(value);
|
||||
});
|
||||
|
||||
it("should handle replacing values with null to remove them", () => {
|
||||
const result = template(`
|
||||
callee(ARG);
|
||||
`)({ ARG: null });
|
||||
|
||||
expect(result.type).to.equal("ExpressionStatement");
|
||||
expect(result.expression.type).to.equal("CallExpression");
|
||||
expect(result.expression.arguments).to.eql([]);
|
||||
});
|
||||
|
||||
it("should handle replacing values that are string content", () => {
|
||||
const result = template(`
|
||||
("ARG");
|
||||
`)({ ARG: "some new content" });
|
||||
|
||||
expect(result.type).to.equal("ExpressionStatement");
|
||||
expect(result.expression.type).to.equal("StringLiteral");
|
||||
expect(result.expression.value).to.equal("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).to.equal("ExpressionStatement");
|
||||
expect(result[0].expression).to.equal(id);
|
||||
expect(result[1].type).to.equal("ExpressionStatement");
|
||||
expect(result[1].expression).not.to.equal(id);
|
||||
expect(result[1].expression).to.eql(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).to.equal("ExpressionStatement");
|
||||
expect(result.expression).to.equal(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).to.equal("ExpressionStatement");
|
||||
expect(result[0].expression).to.equal(id);
|
||||
expect(result[1].type).to.equal("ExpressionStatement");
|
||||
expect(result[1].expression.type).to.equal("Identifier");
|
||||
expect(result[1].expression.name).to.equal("ANOTHER_ID");
|
||||
});
|
||||
|
||||
it("should throw if unknown replacements are provided", () => {
|
||||
expect(() => {
|
||||
template(`
|
||||
ID;
|
||||
`)({ ID: t.identifier("someIdent"), ANOTHER_ID: null });
|
||||
}).to.throw(Error, 'Unknown substitution "ANOTHER_ID" given');
|
||||
});
|
||||
|
||||
it("should throw if placeholders are not given explicit values", () => {
|
||||
expect(() => {
|
||||
template(`
|
||||
ID;
|
||||
ANOTHER_ID;
|
||||
`)({ ID: t.identifier("someIdent") });
|
||||
}).to.throw(Error, 'No substitution given for "ANOTHER_ID"');
|
||||
});
|
||||
|
||||
it("should return the AST directly when using .ast", () => {
|
||||
const result = template.ast(`
|
||||
if ("some string value" === "") {}
|
||||
`);
|
||||
|
||||
expect(result.type).to.equal("IfStatement");
|
||||
expect(result.test.type).to.equal("BinaryExpression");
|
||||
expect(result.test.left.type).to.equal("StringLiteral");
|
||||
expect(result.test.left.value).to.equal("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).to.equal("IfStatement");
|
||||
expect(result.test.type).to.equal("BinaryExpression");
|
||||
expect(result.test.left).to.equal(value);
|
||||
});
|
||||
|
||||
it("should handle replacing values with null to remove them", () => {
|
||||
const result = template`
|
||||
callee(${null});
|
||||
`();
|
||||
|
||||
expect(result.type).to.equal("ExpressionStatement");
|
||||
expect(result.expression.type).to.equal("CallExpression");
|
||||
expect(result.expression.arguments).to.eql([]);
|
||||
});
|
||||
|
||||
it("should handle replacing values that are string content", () => {
|
||||
const result = template`
|
||||
("${"some new content"}");
|
||||
`();
|
||||
|
||||
expect(result.type).to.equal("ExpressionStatement");
|
||||
expect(result.expression.type).to.equal("StringLiteral");
|
||||
expect(result.expression.value).to.equal("some new content");
|
||||
});
|
||||
|
||||
it("should allow setting options by passing an object", () => {
|
||||
const result = template({ sourceType: "script" })`
|
||||
with({}){}
|
||||
`();
|
||||
|
||||
expect(result.type).to.equal("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).to.equal("IfStatement");
|
||||
expect(result.test.type).to.equal("BinaryExpression");
|
||||
expect(result.test.left).to.equal(value);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,183 +0,0 @@
|
||||
import generator from "../../babel-generator";
|
||||
import * as t from "@babel/types";
|
||||
import template from "../lib";
|
||||
import chai from "chai";
|
||||
const expect = chai.expect;
|
||||
|
||||
describe("tagged templating", () => {
|
||||
it("basic support", () => {
|
||||
const tpl = template`("stringLiteral")`;
|
||||
const result = tpl();
|
||||
|
||||
expect(result).to.be.ok;
|
||||
expect(t.isStringLiteral(result.expression)).to.be.true;
|
||||
});
|
||||
|
||||
describe("numeric interpolation", () => {
|
||||
it("single replacement", () => {
|
||||
const tpl = template`+${0}`;
|
||||
const node = t.numericLiteral(123);
|
||||
const result = tpl(node);
|
||||
|
||||
expect(result).to.be.ok;
|
||||
expect(t.isUnaryExpression(result.expression)).to.be.true;
|
||||
expect(result.expression.argument).to.equal(node);
|
||||
});
|
||||
|
||||
it("duplicate replacement", () => {
|
||||
const tpl = template`${0} + ${0}`;
|
||||
const node = t.numericLiteral(123);
|
||||
const result = tpl(node);
|
||||
|
||||
expect(result).to.be.ok;
|
||||
expect(t.isBinaryExpression(result.expression)).to.be.true;
|
||||
expect(result.expression.left).to.equal(node);
|
||||
expect(result.expression.right).to.equal(result.expression.left);
|
||||
});
|
||||
|
||||
it("multiple replacement", () => {
|
||||
const tpl = template`${0}.${1}(${2})`;
|
||||
const object = t.identifier("foo");
|
||||
const property = t.identifier("bar");
|
||||
const argument = t.numericLiteral(123);
|
||||
const result = tpl(object, property, argument);
|
||||
|
||||
expect(result).to.be.ok;
|
||||
expect(t.isCallExpression(result.expression)).to.be.true;
|
||||
|
||||
const { callee, arguments: args } = result.expression;
|
||||
expect(t.isMemberExpression(callee)).to.be.true;
|
||||
expect(callee.object).to.equal(object);
|
||||
expect(callee.property).to.equal(property);
|
||||
|
||||
expect(args).to.deep.equal([argument]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("string interpolation", () => {
|
||||
it("has expected internal representation", () => {
|
||||
const tpl = template`${"foo"}(${"b a r"})`;
|
||||
expect(generator(tpl()).code).to.equal(
|
||||
"$BABEL_TEMPLATE$$foo($BABEL_TEMPLATE$$bAR);",
|
||||
);
|
||||
});
|
||||
|
||||
it("simple replacement", () => {
|
||||
const tpl = template`${"foo"}(${"b a r"})`;
|
||||
const arg = {
|
||||
foo: t.identifier("baz"),
|
||||
"b a r": t.numericLiteral(123),
|
||||
};
|
||||
|
||||
const result = tpl(arg);
|
||||
|
||||
expect(result).to.be.ok;
|
||||
expect(t.isCallExpression(result.expression)).to.be.true;
|
||||
|
||||
const { callee, arguments: args } = result.expression;
|
||||
|
||||
expect(callee).to.equal(arg.foo);
|
||||
expect(args).to.deep.equal([arg["b a r"]]);
|
||||
});
|
||||
|
||||
it("does not conflict with similar identifiers", () => {
|
||||
const tpl = template`foo + ${"foo"}`;
|
||||
const arg = {
|
||||
foo: t.identifier("foo"),
|
||||
};
|
||||
|
||||
const result = tpl(arg);
|
||||
|
||||
expect(result).to.be.ok;
|
||||
expect(t.isBinaryExpression(result.expression)).to.be.true;
|
||||
|
||||
const { left, right } = result.expression;
|
||||
expect(left).to.not.equal(right);
|
||||
expect(t.isIdentifier(left, { name: "foo" })).to.be.true;
|
||||
|
||||
expect(right).to.equal(arg.foo);
|
||||
});
|
||||
|
||||
it("does not conflict when t.toIdentifier conflicts", () => {
|
||||
const tpl = template`${"fOO"} + ${"f o o"}`;
|
||||
const arg = {
|
||||
fOO: t.numericLiteral(123),
|
||||
"f o o": t.numericLiteral(321),
|
||||
};
|
||||
|
||||
const result = tpl(arg);
|
||||
|
||||
expect(result).to.be.ok;
|
||||
expect(t.isBinaryExpression(result.expression)).to.be.true;
|
||||
|
||||
const { left, right } = result.expression;
|
||||
expect(left).to.not.equal(right);
|
||||
|
||||
expect(left).to.equal(arg.fOO);
|
||||
expect(right).to.equal(arg["f o o"]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("mixed interpolation", () => {
|
||||
it("throws when 0 is used", () => {
|
||||
expect(() => template`${0} - ${"foo"}`).to.throw(
|
||||
"Template cannot have a '0' replacement and a named replacement at the same time",
|
||||
);
|
||||
});
|
||||
|
||||
it("works", () => {
|
||||
const tpl = template`${1}.${"prop"}`;
|
||||
const arg = {
|
||||
prop: t.identifier("prop"),
|
||||
};
|
||||
|
||||
const result = tpl(arg, t.thisExpression());
|
||||
|
||||
expect(result).to.be.ok;
|
||||
expect(t.isMemberExpression(result.expression)).to.be.true;
|
||||
|
||||
const { object, property } = result.expression;
|
||||
|
||||
expect(t.isThisExpression(object)).to.be.true;
|
||||
expect(property).to.equal(arg.prop);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Node interpolation", () => {
|
||||
it("works", () => {
|
||||
const node = t.identifier("foo");
|
||||
const tpl = template`${node}`;
|
||||
|
||||
const result = tpl();
|
||||
|
||||
expect(result).to.be.ok;
|
||||
expect(result.expression).to.equal(node);
|
||||
});
|
||||
});
|
||||
|
||||
describe("options", () => {
|
||||
it("works", () => {
|
||||
const remove = template({ preserveComments: false })`// comment\nid;`;
|
||||
const preserve = template({ preserveComments: true })`// comment\nid;`;
|
||||
|
||||
const removeResult = remove();
|
||||
const preserveResult = preserve();
|
||||
|
||||
expect(removeResult);
|
||||
expect(preserveResult).to.be.ok;
|
||||
|
||||
// it exists, it just resets to undefined
|
||||
expect(removeResult.leadingComments).to.be.undefined;
|
||||
|
||||
expect(Array.isArray(preserveResult.leadingComments)).to.be.true;
|
||||
expect(preserveResult.leadingComments[0]).to.have.property(
|
||||
"type",
|
||||
"CommentLine",
|
||||
);
|
||||
expect(preserveResult.leadingComments[0]).to.have.property(
|
||||
"value",
|
||||
" comment",
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user