Skip to content

Instantly share code, notes, and snippets.

@jbboehr
Last active April 27, 2020 22:53
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jbboehr/00b0c336ba81483712706d0db6a13f09 to your computer and use it in GitHub Desktop.
Save jbboehr/00b0c336ba81483712706d0db6a13f09 to your computer and use it in GitHub Desktop.
handlebars-spec-translator
var inputFile = process.argv[2] || process.argv[1];
var acorn = require('acorn');
var walk = require('acorn-walk');
var fs = require('fs');
var assert = require('assert');
var escodegen = require('escodegen');
var path = require('path');
var inputText = fs.readFileSync(inputFile).toString();
var ast = acorn.parse(inputText);
var entries = [];
class SkipError extends Error {}
function extractNode(node) {
return inputText.substring(node.start, node.end);
}
function handleEquals(node) {
var first = node.arguments[0];
if (first.type !== 'CallExpression' || first.callee.name !== 'astFor') {
throw new SkipError();
}
assert.strictEqual(node.arguments.length, 2);
assert.strictEqual(first.arguments.length, 1);
var second = node.arguments[1];
assert.strictEqual(second.type, 'Literal');
var templateNode = first.arguments[0];
if (templateNode.type !== "Literal") {
throw new SkipError('Only apply to literals');
}
var text = 'expectTemplate(' + escodegen.generate(templateNode) + ')'
+ '\n.toParseTo(' + escodegen.generate(second) + ')';
// assert.ok(node.arguments.length >= 3);
// var text = handleTemplateNode(node.arguments[0])
// + handleHashOrArrayNode(node.arguments[1], node)
// //+ handlePartialsNode(node.arguments[2])
// + handleMessageNode(node.arguments[4] || undefined)
// + handleExpectedNode(node.arguments[3]);
entries.push({
start: node.start,
end: node.end,
text: text,
});
}
function handleShouldThrowBody(node) {
assert.strictEqual(node.type, 'CallExpression');
assert.strictEqual(node.callee.name, 'astFor');
assert.strictEqual(node.arguments.length, 1);
return 'expectTemplate(' + escodegen.generate(node.arguments[0]) + ')';
}
function handleShouldThrow(node) {
var n = node.arguments.length;
assert.ok(n === 2 || n === 3);
var first = node.arguments[0];
var second = node.arguments[1];
var third = node.arguments[2];
assert.strictEqual(1, first.body.body.length)
assert.strictEqual(first.body.body[0].type, 'ExpressionStatement');
var text = handleShouldThrowBody(first.body.body[0].expression);
text += '.toParseError(' + [
escodegen.generate(second),
third ? escodegen.generate(third) : undefined
].filter(function (v) {
return !!v;
}).join(',') + ')';
entries.push({
start: node.start,
end: node.end,
text: text,
});
}
walk.simple(ast, {
CallExpression: function (node) {
try {
if (node.callee.name === "equals") {
handleEquals(node);
} else if (node.callee.name === "shouldThrow") {
handleShouldThrow(node);
}
} catch (e) {
if (e instanceof SkipError) {
console.warn('skipped', e.message, extractNode(node));
} else {
console.warn('incoming exception for:', extractNode(node));
throw e;
}
}
},
});
var outputText = '';
var last = 0;
var nReplaced = 0;
entries.sort(function (a, b) {
return a.start - b.start;
}).forEach(function (entry) {
outputText += inputText.substring(last, entry.start);
outputText += entry.text;
// outputText += '/*DEL \n' + inputText.substring(entry.start, entry.end) + '\n DEL*/';
last = entry.end;
nReplaced++;
});
outputText += inputText.substring(last);
process.stdout.write(outputText);
console.warn(path.basename(inputFile), 'replaced', nReplaced);
var inputFile = process.argv[2] || process.argv[1];
var acorn = require('acorn');
var walk = require('acorn-walk');
var fs = require('fs');
var assert = require('assert');
var escodegen = require('escodegen');
var path = require('path');
var inputText = fs.readFileSync(inputFile).toString();
var ast = acorn.parse(inputText);
var entries = [];
class SkipError extends Error {}
function extractNode(node) {
return inputText.substring(node.start, node.end);
}
function handleTemplateNode(node) {
if (['Literal', 'Identifier', 'BinaryExpression'].indexOf(node.type) !== -1) {
return 'expectTemplate(' + escodegen.generate(node) + ')';
} else {
assert.fail("Unhandled templateNode type: " + node.type);
}
}
function handleObject(node) {
var text = '';
var hashNode;
node.properties = node.properties.filter(function (child) {
if (child.key.name === 'hash') {
hashNode = child.value;
return false;
} else if (child.key.name === 'helpers') {
text += '\n.withHelpers(' + escodegen.generate(child.value) + ')';
return false;
} else if (child.key.name === 'partials') {
text += '\n.withPartials(' + escodegen.generate(child.value) + ')';
return false;
} else if (child.key.name === 'decorators') {
text += '\n.withDecorators(' + escodegen.generate(child.value) + ')';
return false;
}
return true;
});
var hashNodeText;
if (hashNode) {
hashNodeText = escodegen.generate(hashNode);
if (hashNodeText !== '{}') {
text += '\n.withInput(' + hashNodeText + ')';
}
var optionsText = escodegen.generate(node);
if (optionsText !== '{}') {
text += '\n.withCompileOptions(' + optionsText + ')';
}
} else {
hashNodeText = escodegen.generate(node);
if (hashNodeText !== '{}') {
text += '\n.withInput(' + hashNodeText + ')';
}
}
return text;
}
function handleOptions(node, hadHelpers, hadPartials, compileOptions) {
if (node.type === "Literal") {
compileOptions['compat'] = escodegen.generate(node);
return '';
} else if (node.type === "ObjectExpression") {
// fallthrough
} else {
assert.fail("Unhandled runtimeOptionsNode type: " + node.type);
}
var text = '';
node.properties = node.properties.filter(function (child) {
if (child.key.name === 'helpers') {
if (hadHelpers) {
throw new SkipError("can't handle two helpers keys");
}
text += '\n.withHelpers(' + escodegen.generate(child.value) + ')';
return false;
} else if (child.key.name === 'partials') {
if (hadPartials) {
throw new SkipError("can't handle two partials keys");
}
text += '\n.withPartials(' + escodegen.generate(child.value) + ')';
return false;
}
return true;
});
node.properties.forEach(function (child) {
compileOptions[child.key.name] = escodegen.generate(child.value);
});
return text;
}
function handleArray(node) {
var n = node.elements.length;
assert.ok(n >= 1);
var hadHelpers = false;
var hadPartials = false;
var text = '';
if (n >= 1 && node.elements[0]) {
var hashText = escodegen.generate(node.elements[0]);
if (hashText !== '{}') {
text += '\n.withInput(' + escodegen.generate(node.elements[0]) + ')';
}
} else {
text += '\n.withInput(undefined)';
}
if (n >= 2 && node.elements[1] && !(node.elements[1].type === 'Identifier' && node.elements[1].name === 'undefined') ) {
hadHelpers = true;
var helperText = escodegen.generate(node.elements[1]);
if (helperText !== '{}') {
text += '\n.withHelpers(' + helperText + ')';
}
}
if (n >= 3 && node.elements[2] && !(node.elements[2].type === 'Identifier' && node.elements[2].name === 'undefined')) {
hadPartials = true;
var partialText = escodegen.generate(node.elements[2]);
if (partialText !== '{}') {
text += '\n.withPartials(' + partialText + ')';
}
}
var compileOptions = {};
if (n >= 5) {
var compileOptionsText = escodegen.generate(node.elements[4]);
if (node.elements[4].type === 'Literal') {
compileOptions['data'] = compileOptionsText;
} else if (compileOptionsText) {
compileOptions['data'] = 'true';
}
text += '\n.withRuntimeOptions({ data: ' + escodegen.generate(node.elements[4]) + ' })';
}
if (n >= 4 && node.elements[3]) {
text += handleOptions(node.elements[3], hadHelpers, hadPartials, compileOptions);
}
if (compileOptions && Object.keys(compileOptions).length > 0) {
text += '\n.withCompileOptions({ ';
Object.keys(compileOptions).forEach(function (key) {
text += key + ': ' + compileOptions[key] + ',';
});
text = text.substring(0, text.length - 1);
text += ' })';
}
return text;
}
function handleHashOrArrayNode(node, parent) {
if (node.type === "Literal") {
return '\n.withInput(' + node.raw + ')';
} else if (node.type === "Identifier" && node.name === 'undefined') {
return '\n.withInput(undefined)';
} else if (node.type === 'Identifier') {
return '\n.withInput(' + escodegen.generate(node) + ')';
} else if (node.type === 'ObjectExpression') {
return handleObject(node);
} else if (node.type === 'ArrayExpression') {
return handleArray(node);
} else {
assert.fail("Unhandled hashOrArrayNode type: " + node.type);
}
}
function handleExpectedNode(node) {
if (['Literal', 'Identifier', 'BinaryExpression', 'CallExpression'].indexOf(node.type) !== -1) {
return '\n.toCompileTo(' + escodegen.generate(node) + ')';
} else {
assert.fail("Unhandled expectedNode type: " + node.type);
}
}
function handleMessageNode(node) {
if (!node) return '';
if (node.type === 'Literal') {
return '\n.withMessage(' + node.raw + ')';
} else if (node.type === 'Identifier') {
return '\n.withMessage(' + node.name + ')';
} else {
assert.fail("Unhandled messageNode type: " + node.type);
}
}
function handleShouldCompileTo(node) {
assert.ok(node.arguments.length >= 3);
var text = handleTemplateNode(node.arguments[0])
+ handleHashOrArrayNode(node.arguments[1], node)
+ handleMessageNode(node.arguments[3] || undefined)
+ handleExpectedNode(node.arguments[2]);
entries.push({
start: node.start,
end: node.end,
text: text,
});
}
function handleShouldCompileToWithPartials(node) {
assert.ok(node.arguments.length >= 3);
var text = handleTemplateNode(node.arguments[0])
+ handleHashOrArrayNode(node.arguments[1], node)
//+ handlePartialsNode(node.arguments[2])
+ handleMessageNode(node.arguments[4] || undefined)
+ handleExpectedNode(node.arguments[3]);
entries.push({
start: node.start,
end: node.end,
text: text,
});
}
walk.simple(ast, {
CallExpression: function (node) {
try {
if (node.callee.name === "shouldCompileTo") {
handleShouldCompileTo(node);
} else if (node.callee.name === 'shouldCompileToWithPartials') {
handleShouldCompileToWithPartials(node);
}
} catch (e) {
if (e instanceof SkipError) {
console.warn('skipped', e.message, extractNode(node));
} else {
console.warn('incoming exception for:', extractNode(node));
throw e;
}
}
},
});
var outputText = '';
var last = 0;
var nReplaced = 0;
entries.sort(function (a, b) {
return a.start - b.start;
}).forEach(function (entry) {
outputText += inputText.substring(last, entry.start);
outputText += entry.text;
// outputText += '/*DEL \n' + inputText.substring(entry.start, entry.end) + '\n DEL*/';
last = entry.end;
nReplaced++;
});
outputText += inputText.substring(last);
process.stdout.write(outputText);
console.warn(path.basename(inputFile), 'replaced', nReplaced);
{
"name": "handlebars-spec-translator",
"version": "0.1.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"acorn": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.1.tgz",
"integrity": "sha512-add7dgA5ppRPxCFJoAGfMDi7PIBXq1RtGo7BhbLaxwrXPOmw8gq48Y9ozT01hUKy9byMjlR20EJhu5zlkErEkg=="
},
"acorn-walk": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.1.1.tgz",
"integrity": "sha512-wdlPY2tm/9XBr7QkKlq0WQVgiuGTX6YWPyRyBviSoScBuLfTVQhvwg6wJ369GJ/1nPfTLMfnrFIfjqVg6d+jQQ=="
},
"deep-is": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz",
"integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ="
},
"escodegen": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.1.tgz",
"integrity": "sha512-Bmt7NcRySdIfNPfU2ZoXDrrXsG9ZjvDxcAlMfDUgRBjLOWTuIACXPBFJH7Z+cLb40JeQco5toikyc9t9P8E9SQ==",
"requires": {
"esprima": "^4.0.1",
"estraverse": "^4.2.0",
"esutils": "^2.0.2",
"optionator": "^0.8.1",
"source-map": "~0.6.1"
}
},
"esprima": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="
},
"estraverse": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz",
"integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw=="
},
"esutils": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
"integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="
},
"fast-levenshtein": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
"integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc="
},
"levn": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz",
"integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=",
"requires": {
"prelude-ls": "~1.1.2",
"type-check": "~0.3.2"
}
},
"optionator": {
"version": "0.8.3",
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz",
"integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==",
"requires": {
"deep-is": "~0.1.3",
"fast-levenshtein": "~2.0.6",
"levn": "~0.3.0",
"prelude-ls": "~1.1.2",
"type-check": "~0.3.2",
"word-wrap": "~1.2.3"
}
},
"prelude-ls": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
"integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ="
},
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"optional": true
},
"type-check": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz",
"integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=",
"requires": {
"prelude-ls": "~1.1.2"
}
},
"word-wrap": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
"integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ=="
}
}
}
{
"name": "handlebars-spec-translator",
"version": "0.1.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "GPL-2.0",
"dependencies": {
"acorn": "^7.1.1",
"acorn-walk": "^7.1.1",
"escodegen": "^1.14.1"
}
}
diff --git a/spec/env/common.js b/spec/env/common.js
index a122f4d..1827265 100644
--- a/spec/env/common.js
+++ b/spec/env/common.js
@@ -237,3 +237,21 @@ HandlebarsTestBench.prototype._combineRuntimeOptions = function() {
combinedRuntimeOptions.decorators = this.decorators;
return combinedRuntimeOptions;
};
+
+HandlebarsTestBench.prototype.toParseTo = function(expectedOutputAsString) {
+ expect(this._parseAndPrint()).to.equal(
+ expectedOutputAsString,
+ this.message
+ );
+};
+
+HandlebarsTestBench.prototype.toParseError = function(errorLike, errMsgMatcher) {
+ var self = this;
+ expect(function() {
+ self._parseAndPrint();
+ }).to.throw(errorLike, errMsgMatcher, this.message);
+};
+
+HandlebarsTestBench.prototype._parseAndPrint = function() {
+ return Handlebars.print(Handlebars.parse(this.templateAsString));
+};
diff --git a/spec/parser.js b/spec/parser.js
index 2aa7e39..626491a 100644
--- a/spec/parser.js
+++ b/spec/parser.js
@@ -1,432 +1,317 @@
-describe('parser', function() {
+describe('parser', function () {
if (!Handlebars.print) {
return;
}
- function astFor(template) {
- var ast = Handlebars.parse(template);
- return Handlebars.print(ast);
- }
-
- it('parses simple mustaches', function() {
- equals(astFor('{{123}}'), '{{ NUMBER{123} [] }}\n');
- equals(astFor('{{"foo"}}'), '{{ "foo" [] }}\n');
- equals(astFor('{{false}}'), '{{ BOOLEAN{false} [] }}\n');
- equals(astFor('{{true}}'), '{{ BOOLEAN{true} [] }}\n');
- equals(astFor('{{foo}}'), '{{ PATH:foo [] }}\n');
- equals(astFor('{{foo?}}'), '{{ PATH:foo? [] }}\n');
- equals(astFor('{{foo_}}'), '{{ PATH:foo_ [] }}\n');
- equals(astFor('{{foo-}}'), '{{ PATH:foo- [] }}\n');
- equals(astFor('{{foo:}}'), '{{ PATH:foo: [] }}\n');
+ it('parses simple mustaches', function () {
+ expectTemplate('{{123}}')
+ .toParseTo('{{ NUMBER{123} [] }}\n');
+ expectTemplate('{{"foo"}}')
+ .toParseTo('{{ "foo" [] }}\n');
+ expectTemplate('{{false}}')
+ .toParseTo('{{ BOOLEAN{false} [] }}\n');
+ expectTemplate('{{true}}')
+ .toParseTo('{{ BOOLEAN{true} [] }}\n');
+ expectTemplate('{{foo}}')
+ .toParseTo('{{ PATH:foo [] }}\n');
+ expectTemplate('{{foo?}}')
+ .toParseTo('{{ PATH:foo? [] }}\n');
+ expectTemplate('{{foo_}}')
+ .toParseTo('{{ PATH:foo_ [] }}\n');
+ expectTemplate('{{foo-}}')
+ .toParseTo('{{ PATH:foo- [] }}\n');
+ expectTemplate('{{foo:}}')
+ .toParseTo('{{ PATH:foo: [] }}\n');
});
- it('parses simple mustaches with data', function() {
- equals(astFor('{{@foo}}'), '{{ @PATH:foo [] }}\n');
+ it('parses simple mustaches with data', function () {
+ expectTemplate('{{@foo}}')
+ .toParseTo('{{ @PATH:foo [] }}\n');
});
- it('parses simple mustaches with data paths', function() {
- equals(astFor('{{@../foo}}'), '{{ @PATH:foo [] }}\n');
+ it('parses simple mustaches with data paths', function () {
+ expectTemplate('{{@../foo}}')
+ .toParseTo('{{ @PATH:foo [] }}\n');
});
- it('parses mustaches with paths', function() {
- equals(astFor('{{foo/bar}}'), '{{ PATH:foo/bar [] }}\n');
+ it('parses mustaches with paths', function () {
+ expectTemplate('{{foo/bar}}')
+ .toParseTo('{{ PATH:foo/bar [] }}\n');
});
- it('parses mustaches with this/foo', function() {
- equals(astFor('{{this/foo}}'), '{{ PATH:foo [] }}\n');
+ it('parses mustaches with this/foo', function () {
+ expectTemplate('{{this/foo}}')
+ .toParseTo('{{ PATH:foo [] }}\n');
});
- it('parses mustaches with - in a path', function() {
- equals(astFor('{{foo-bar}}'), '{{ PATH:foo-bar [] }}\n');
+ it('parses mustaches with - in a path', function () {
+ expectTemplate('{{foo-bar}}')
+ .toParseTo('{{ PATH:foo-bar [] }}\n');
});
- it('parses mustaches with escaped [] in a path', function() {
- equals(astFor('{{[foo[\\]]}}'), '{{ PATH:foo[] [] }}\n');
+ it('parses mustaches with escaped [] in a path', function () {
+ expectTemplate('{{[foo[\\]]}}')
+ .toParseTo('{{ PATH:foo[] [] }}\n');
});
- it('parses escaped \\\\ in path', function() {
- equals(astFor('{{[foo\\\\]}}'), '{{ PATH:foo\\ [] }}\n');
+ it('parses escaped \\\\ in path', function () {
+ expectTemplate('{{[foo\\\\]}}')
+ .toParseTo('{{ PATH:foo\\ [] }}\n');
});
- it('parses mustaches with parameters', function() {
- equals(astFor('{{foo bar}}'), '{{ PATH:foo [PATH:bar] }}\n');
+ it('parses mustaches with parameters', function () {
+ expectTemplate('{{foo bar}}')
+ .toParseTo('{{ PATH:foo [PATH:bar] }}\n');
});
- it('parses mustaches with string parameters', function() {
- equals(astFor('{{foo bar "baz" }}'), '{{ PATH:foo [PATH:bar, "baz"] }}\n');
+ it('parses mustaches with string parameters', function () {
+ expectTemplate('{{foo bar "baz" }}')
+ .toParseTo('{{ PATH:foo [PATH:bar, "baz"] }}\n');
});
- it('parses mustaches with NUMBER parameters', function() {
- equals(astFor('{{foo 1}}'), '{{ PATH:foo [NUMBER{1}] }}\n');
+ it('parses mustaches with NUMBER parameters', function () {
+ expectTemplate('{{foo 1}}')
+ .toParseTo('{{ PATH:foo [NUMBER{1}] }}\n');
});
- it('parses mustaches with BOOLEAN parameters', function() {
- equals(astFor('{{foo true}}'), '{{ PATH:foo [BOOLEAN{true}] }}\n');
- equals(astFor('{{foo false}}'), '{{ PATH:foo [BOOLEAN{false}] }}\n');
+ it('parses mustaches with BOOLEAN parameters', function () {
+ expectTemplate('{{foo true}}')
+ .toParseTo('{{ PATH:foo [BOOLEAN{true}] }}\n');
+ expectTemplate('{{foo false}}')
+ .toParseTo('{{ PATH:foo [BOOLEAN{false}] }}\n');
});
- it('parses mustaches with undefined and null paths', function() {
- equals(astFor('{{undefined}}'), '{{ UNDEFINED [] }}\n');
- equals(astFor('{{null}}'), '{{ NULL [] }}\n');
+ it('parses mustaches with undefined and null paths', function () {
+ expectTemplate('{{undefined}}')
+ .toParseTo('{{ UNDEFINED [] }}\n');
+ expectTemplate('{{null}}')
+ .toParseTo('{{ NULL [] }}\n');
});
- it('parses mustaches with undefined and null parameters', function() {
- equals(
- astFor('{{foo undefined null}}'),
- '{{ PATH:foo [UNDEFINED, NULL] }}\n'
- );
+ it('parses mustaches with undefined and null parameters', function () {
+ expectTemplate('{{foo undefined null}}')
+ .toParseTo('{{ PATH:foo [UNDEFINED, NULL] }}\n');
});
- it('parses mustaches with DATA parameters', function() {
- equals(astFor('{{foo @bar}}'), '{{ PATH:foo [@PATH:bar] }}\n');
+ it('parses mustaches with DATA parameters', function () {
+ expectTemplate('{{foo @bar}}')
+ .toParseTo('{{ PATH:foo [@PATH:bar] }}\n');
});
- it('parses mustaches with hash arguments', function() {
- equals(astFor('{{foo bar=baz}}'), '{{ PATH:foo [] HASH{bar=PATH:baz} }}\n');
- equals(astFor('{{foo bar=1}}'), '{{ PATH:foo [] HASH{bar=NUMBER{1}} }}\n');
- equals(
- astFor('{{foo bar=true}}'),
- '{{ PATH:foo [] HASH{bar=BOOLEAN{true}} }}\n'
- );
- equals(
- astFor('{{foo bar=false}}'),
- '{{ PATH:foo [] HASH{bar=BOOLEAN{false}} }}\n'
- );
- equals(
- astFor('{{foo bar=@baz}}'),
- '{{ PATH:foo [] HASH{bar=@PATH:baz} }}\n'
- );
+ it('parses mustaches with hash arguments', function () {
+ expectTemplate('{{foo bar=baz}}')
+ .toParseTo('{{ PATH:foo [] HASH{bar=PATH:baz} }}\n');
+ expectTemplate('{{foo bar=1}}')
+ .toParseTo('{{ PATH:foo [] HASH{bar=NUMBER{1}} }}\n');
+ expectTemplate('{{foo bar=true}}')
+ .toParseTo('{{ PATH:foo [] HASH{bar=BOOLEAN{true}} }}\n');
+ expectTemplate('{{foo bar=false}}')
+ .toParseTo('{{ PATH:foo [] HASH{bar=BOOLEAN{false}} }}\n');
+ expectTemplate('{{foo bar=@baz}}')
+ .toParseTo('{{ PATH:foo [] HASH{bar=@PATH:baz} }}\n');
- equals(
- astFor('{{foo bar=baz bat=bam}}'),
- '{{ PATH:foo [] HASH{bar=PATH:baz, bat=PATH:bam} }}\n'
- );
- equals(
- astFor('{{foo bar=baz bat="bam"}}'),
- '{{ PATH:foo [] HASH{bar=PATH:baz, bat="bam"} }}\n'
- );
+ expectTemplate('{{foo bar=baz bat=bam}}')
+ .toParseTo('{{ PATH:foo [] HASH{bar=PATH:baz, bat=PATH:bam} }}\n');
+ expectTemplate('{{foo bar=baz bat="bam"}}')
+ .toParseTo('{{ PATH:foo [] HASH{bar=PATH:baz, bat="bam"} }}\n');
- equals(astFor("{{foo bat='bam'}}"), '{{ PATH:foo [] HASH{bat="bam"} }}\n');
+ expectTemplate('{{foo bat=\'bam\'}}')
+ .toParseTo('{{ PATH:foo [] HASH{bat="bam"} }}\n');
- equals(
- astFor('{{foo omg bar=baz bat="bam"}}'),
- '{{ PATH:foo [PATH:omg] HASH{bar=PATH:baz, bat="bam"} }}\n'
- );
- equals(
- astFor('{{foo omg bar=baz bat="bam" baz=1}}'),
- '{{ PATH:foo [PATH:omg] HASH{bar=PATH:baz, bat="bam", baz=NUMBER{1}} }}\n'
- );
- equals(
- astFor('{{foo omg bar=baz bat="bam" baz=true}}'),
- '{{ PATH:foo [PATH:omg] HASH{bar=PATH:baz, bat="bam", baz=BOOLEAN{true}} }}\n'
- );
- equals(
- astFor('{{foo omg bar=baz bat="bam" baz=false}}'),
- '{{ PATH:foo [PATH:omg] HASH{bar=PATH:baz, bat="bam", baz=BOOLEAN{false}} }}\n'
- );
+ expectTemplate('{{foo omg bar=baz bat="bam"}}')
+ .toParseTo('{{ PATH:foo [PATH:omg] HASH{bar=PATH:baz, bat="bam"} }}\n');
+ expectTemplate('{{foo omg bar=baz bat="bam" baz=1}}')
+ .toParseTo('{{ PATH:foo [PATH:omg] HASH{bar=PATH:baz, bat="bam", baz=NUMBER{1}} }}\n');
+ expectTemplate('{{foo omg bar=baz bat="bam" baz=true}}')
+ .toParseTo('{{ PATH:foo [PATH:omg] HASH{bar=PATH:baz, bat="bam", baz=BOOLEAN{true}} }}\n');
+ expectTemplate('{{foo omg bar=baz bat="bam" baz=false}}')
+ .toParseTo('{{ PATH:foo [PATH:omg] HASH{bar=PATH:baz, bat="bam", baz=BOOLEAN{false}} }}\n');
});
- it('parses contents followed by a mustache', function() {
- equals(
- astFor('foo bar {{baz}}'),
- "CONTENT[ 'foo bar ' ]\n{{ PATH:baz [] }}\n"
- );
+ it('parses contents followed by a mustache', function () {
+ expectTemplate('foo bar {{baz}}')
+ .toParseTo('CONTENT[ \'foo bar \' ]\n{{ PATH:baz [] }}\n');
});
- it('parses a partial', function() {
- equals(astFor('{{> foo }}'), '{{> PARTIAL:foo }}\n');
- equals(astFor('{{> "foo" }}'), '{{> PARTIAL:foo }}\n');
- equals(astFor('{{> 1 }}'), '{{> PARTIAL:1 }}\n');
+ it('parses a partial', function () {
+ expectTemplate('{{> foo }}')
+ .toParseTo('{{> PARTIAL:foo }}\n');
+ expectTemplate('{{> "foo" }}')
+ .toParseTo('{{> PARTIAL:foo }}\n');
+ expectTemplate('{{> 1 }}')
+ .toParseTo('{{> PARTIAL:1 }}\n');
});
- it('parses a partial with context', function() {
- equals(astFor('{{> foo bar}}'), '{{> PARTIAL:foo PATH:bar }}\n');
+ it('parses a partial with context', function () {
+ expectTemplate('{{> foo bar}}')
+ .toParseTo('{{> PARTIAL:foo PATH:bar }}\n');
});
- it('parses a partial with hash', function() {
- equals(
- astFor('{{> foo bar=bat}}'),
- '{{> PARTIAL:foo HASH{bar=PATH:bat} }}\n'
- );
+ it('parses a partial with hash', function () {
+ expectTemplate('{{> foo bar=bat}}')
+ .toParseTo('{{> PARTIAL:foo HASH{bar=PATH:bat} }}\n');
});
- it('parses a partial with context and hash', function() {
- equals(
- astFor('{{> foo bar bat=baz}}'),
- '{{> PARTIAL:foo PATH:bar HASH{bat=PATH:baz} }}\n'
- );
+ it('parses a partial with context and hash', function () {
+ expectTemplate('{{> foo bar bat=baz}}')
+ .toParseTo('{{> PARTIAL:foo PATH:bar HASH{bat=PATH:baz} }}\n');
});
- it('parses a partial with a complex name', function() {
- equals(
- astFor('{{> shared/partial?.bar}}'),
- '{{> PARTIAL:shared/partial?.bar }}\n'
- );
+ it('parses a partial with a complex name', function () {
+ expectTemplate('{{> shared/partial?.bar}}')
+ .toParseTo('{{> PARTIAL:shared/partial?.bar }}\n');
});
- it('parsers partial blocks', function() {
- equals(
- astFor('{{#> foo}}bar{{/foo}}'),
- "{{> PARTIAL BLOCK:foo PROGRAM:\n CONTENT[ 'bar' ]\n }}\n"
- );
+ it('parsers partial blocks', function () {
+ expectTemplate('{{#> foo}}bar{{/foo}}')
+ .toParseTo('{{> PARTIAL BLOCK:foo PROGRAM:\n CONTENT[ \'bar\' ]\n }}\n');
});
- it('should handle parser block mismatch', function() {
- shouldThrow(
- function() {
- astFor('{{#> goodbyes}}{{/hellos}}');
- },
- Error,
- /goodbyes doesn't match hellos/
- );
+ it('should handle parser block mismatch', function () {
+ expectTemplate('{{#> goodbyes}}{{/hellos}}').toParseError(Error, /goodbyes doesn't match hellos/);
});
- it('parsers partial blocks with arguments', function() {
- equals(
- astFor('{{#> foo context hash=value}}bar{{/foo}}'),
- "{{> PARTIAL BLOCK:foo PATH:context HASH{hash=PATH:value} PROGRAM:\n CONTENT[ 'bar' ]\n }}\n"
- );
+ it('parsers partial blocks with arguments', function () {
+ expectTemplate('{{#> foo context hash=value}}bar{{/foo}}')
+ .toParseTo('{{> PARTIAL BLOCK:foo PATH:context HASH{hash=PATH:value} PROGRAM:\n CONTENT[ \'bar\' ]\n }}\n');
});
- it('parses a comment', function() {
- equals(
- astFor('{{! this is a comment }}'),
- "{{! ' this is a comment ' }}\n"
- );
+ it('parses a comment', function () {
+ expectTemplate('{{! this is a comment }}')
+ .toParseTo('{{! \' this is a comment \' }}\n');
});
- it('parses a multi-line comment', function() {
- equals(
- astFor('{{!\nthis is a multi-line comment\n}}'),
- "{{! '\nthis is a multi-line comment\n' }}\n"
- );
+ it('parses a multi-line comment', function () {
+ expectTemplate('{{!\nthis is a multi-line comment\n}}')
+ .toParseTo('{{! \'\nthis is a multi-line comment\n\' }}\n');
});
- it('parses an inverse section', function() {
- equals(
- astFor('{{#foo}} bar {{^}} baz {{/foo}}'),
- "BLOCK:\n PATH:foo []\n PROGRAM:\n CONTENT[ ' bar ' ]\n {{^}}\n CONTENT[ ' baz ' ]\n"
- );
+ it('parses an inverse section', function () {
+ expectTemplate('{{#foo}} bar {{^}} baz {{/foo}}')
+ .toParseTo('BLOCK:\n PATH:foo []\n PROGRAM:\n CONTENT[ \' bar \' ]\n {{^}}\n CONTENT[ \' baz \' ]\n');
});
- it('parses an inverse (else-style) section', function() {
- equals(
- astFor('{{#foo}} bar {{else}} baz {{/foo}}'),
- "BLOCK:\n PATH:foo []\n PROGRAM:\n CONTENT[ ' bar ' ]\n {{^}}\n CONTENT[ ' baz ' ]\n"
- );
+ it('parses an inverse (else-style) section', function () {
+ expectTemplate('{{#foo}} bar {{else}} baz {{/foo}}')
+ .toParseTo('BLOCK:\n PATH:foo []\n PROGRAM:\n CONTENT[ \' bar \' ]\n {{^}}\n CONTENT[ \' baz \' ]\n');
});
- it('parses multiple inverse sections', function() {
- equals(
- astFor('{{#foo}} bar {{else if bar}}{{else}} baz {{/foo}}'),
- "BLOCK:\n PATH:foo []\n PROGRAM:\n CONTENT[ ' bar ' ]\n {{^}}\n BLOCK:\n PATH:if [PATH:bar]\n PROGRAM:\n {{^}}\n CONTENT[ ' baz ' ]\n"
- );
+ it('parses multiple inverse sections', function () {
+ expectTemplate('{{#foo}} bar {{else if bar}}{{else}} baz {{/foo}}')
+ .toParseTo('BLOCK:\n PATH:foo []\n PROGRAM:\n CONTENT[ \' bar \' ]\n {{^}}\n BLOCK:\n PATH:if [PATH:bar]\n PROGRAM:\n {{^}}\n CONTENT[ \' baz \' ]\n');
});
- it('parses empty blocks', function() {
- equals(astFor('{{#foo}}{{/foo}}'), 'BLOCK:\n PATH:foo []\n PROGRAM:\n');
+ it('parses empty blocks', function () {
+ expectTemplate('{{#foo}}{{/foo}}')
+ .toParseTo('BLOCK:\n PATH:foo []\n PROGRAM:\n');
});
- it('parses empty blocks with empty inverse section', function() {
- equals(
- astFor('{{#foo}}{{^}}{{/foo}}'),
- 'BLOCK:\n PATH:foo []\n PROGRAM:\n {{^}}\n'
- );
+ it('parses empty blocks with empty inverse section', function () {
+ expectTemplate('{{#foo}}{{^}}{{/foo}}')
+ .toParseTo('BLOCK:\n PATH:foo []\n PROGRAM:\n {{^}}\n');
});
- it('parses empty blocks with empty inverse (else-style) section', function() {
- equals(
- astFor('{{#foo}}{{else}}{{/foo}}'),
- 'BLOCK:\n PATH:foo []\n PROGRAM:\n {{^}}\n'
- );
+ it('parses empty blocks with empty inverse (else-style) section', function () {
+ expectTemplate('{{#foo}}{{else}}{{/foo}}')
+ .toParseTo('BLOCK:\n PATH:foo []\n PROGRAM:\n {{^}}\n');
});
- it('parses non-empty blocks with empty inverse section', function() {
- equals(
- astFor('{{#foo}} bar {{^}}{{/foo}}'),
- "BLOCK:\n PATH:foo []\n PROGRAM:\n CONTENT[ ' bar ' ]\n {{^}}\n"
- );
+ it('parses non-empty blocks with empty inverse section', function () {
+ expectTemplate('{{#foo}} bar {{^}}{{/foo}}')
+ .toParseTo('BLOCK:\n PATH:foo []\n PROGRAM:\n CONTENT[ \' bar \' ]\n {{^}}\n');
});
- it('parses non-empty blocks with empty inverse (else-style) section', function() {
- equals(
- astFor('{{#foo}} bar {{else}}{{/foo}}'),
- "BLOCK:\n PATH:foo []\n PROGRAM:\n CONTENT[ ' bar ' ]\n {{^}}\n"
- );
+ it('parses non-empty blocks with empty inverse (else-style) section', function () {
+ expectTemplate('{{#foo}} bar {{else}}{{/foo}}')
+ .toParseTo('BLOCK:\n PATH:foo []\n PROGRAM:\n CONTENT[ \' bar \' ]\n {{^}}\n');
});
- it('parses empty blocks with non-empty inverse section', function() {
- equals(
- astFor('{{#foo}}{{^}} bar {{/foo}}'),
- "BLOCK:\n PATH:foo []\n PROGRAM:\n {{^}}\n CONTENT[ ' bar ' ]\n"
- );
+ it('parses empty blocks with non-empty inverse section', function () {
+ expectTemplate('{{#foo}}{{^}} bar {{/foo}}')
+ .toParseTo('BLOCK:\n PATH:foo []\n PROGRAM:\n {{^}}\n CONTENT[ \' bar \' ]\n');
});
- it('parses empty blocks with non-empty inverse (else-style) section', function() {
- equals(
- astFor('{{#foo}}{{else}} bar {{/foo}}'),
- "BLOCK:\n PATH:foo []\n PROGRAM:\n {{^}}\n CONTENT[ ' bar ' ]\n"
- );
+ it('parses empty blocks with non-empty inverse (else-style) section', function () {
+ expectTemplate('{{#foo}}{{else}} bar {{/foo}}')
+ .toParseTo('BLOCK:\n PATH:foo []\n PROGRAM:\n {{^}}\n CONTENT[ \' bar \' ]\n');
});
- it('parses a standalone inverse section', function() {
- equals(
- astFor('{{^foo}}bar{{/foo}}'),
- "BLOCK:\n PATH:foo []\n {{^}}\n CONTENT[ 'bar' ]\n"
- );
+ it('parses a standalone inverse section', function () {
+ expectTemplate('{{^foo}}bar{{/foo}}')
+ .toParseTo('BLOCK:\n PATH:foo []\n {{^}}\n CONTENT[ \'bar\' ]\n');
});
- it('throws on old inverse section', function() {
- shouldThrow(function() {
- astFor('{{else foo}}bar{{/foo}}');
- }, Error);
+ it('throws on old inverse section', function () {
+ expectTemplate('{{else foo}}bar{{/foo}}').toParseError(Error);
});
- it('parses block with block params', function() {
- equals(
- astFor('{{#foo as |bar baz|}}content{{/foo}}'),
- "BLOCK:\n PATH:foo []\n PROGRAM:\n BLOCK PARAMS: [ bar baz ]\n CONTENT[ 'content' ]\n"
- );
+ it('parses block with block params', function () {
+ expectTemplate('{{#foo as |bar baz|}}content{{/foo}}')
+ .toParseTo('BLOCK:\n PATH:foo []\n PROGRAM:\n BLOCK PARAMS: [ bar baz ]\n CONTENT[ \'content\' ]\n');
});
- it('parses inverse block with block params', function() {
- equals(
- astFor('{{^foo as |bar baz|}}content{{/foo}}'),
- "BLOCK:\n PATH:foo []\n {{^}}\n BLOCK PARAMS: [ bar baz ]\n CONTENT[ 'content' ]\n"
- );
+ it('parses inverse block with block params', function () {
+ expectTemplate('{{^foo as |bar baz|}}content{{/foo}}')
+ .toParseTo('BLOCK:\n PATH:foo []\n {{^}}\n BLOCK PARAMS: [ bar baz ]\n CONTENT[ \'content\' ]\n');
});
- it('parses chained inverse block with block params', function() {
- equals(
- astFor('{{#foo}}{{else foo as |bar baz|}}content{{/foo}}'),
- "BLOCK:\n PATH:foo []\n PROGRAM:\n {{^}}\n BLOCK:\n PATH:foo []\n PROGRAM:\n BLOCK PARAMS: [ bar baz ]\n CONTENT[ 'content' ]\n"
- );
+ it('parses chained inverse block with block params', function () {
+ expectTemplate('{{#foo}}{{else foo as |bar baz|}}content{{/foo}}')
+ .toParseTo('BLOCK:\n PATH:foo []\n PROGRAM:\n {{^}}\n BLOCK:\n PATH:foo []\n PROGRAM:\n BLOCK PARAMS: [ bar baz ]\n CONTENT[ \'content\' ]\n');
});
- it("raises if there's a Parse error", function() {
- shouldThrow(
- function() {
- astFor('foo{{^}}bar');
- },
- Error,
- /Parse error on line 1/
- );
- shouldThrow(
- function() {
- astFor('{{foo}');
- },
- Error,
- /Parse error on line 1/
- );
- shouldThrow(
- function() {
- astFor('{{foo &}}');
- },
- Error,
- /Parse error on line 1/
- );
- shouldThrow(
- function() {
- astFor('{{#goodbyes}}{{/hellos}}');
- },
- Error,
- /goodbyes doesn't match hellos/
- );
+ it("raises if there's a Parse error", function () {
+ expectTemplate('foo{{^}}bar').toParseError(Error, /Parse error on line 1/);
+ expectTemplate('{{foo}').toParseError(Error, /Parse error on line 1/);
+ expectTemplate('{{foo &}}').toParseError(Error, /Parse error on line 1/);
+ expectTemplate('{{#goodbyes}}{{/hellos}}').toParseError(Error, /goodbyes doesn't match hellos/);
- shouldThrow(
- function() {
- astFor('{{{{goodbyes}}}} {{{{/hellos}}}}');
- },
- Error,
- /goodbyes doesn't match hellos/
- );
+ expectTemplate('{{{{goodbyes}}}} {{{{/hellos}}}}').toParseError(Error, /goodbyes doesn't match hellos/);
});
- it('should handle invalid paths', function() {
- shouldThrow(
- function() {
- astFor('{{foo/../bar}}');
- },
- Error,
- /Invalid path: foo\/\.\. - 1:2/
- );
- shouldThrow(
- function() {
- astFor('{{foo/./bar}}');
- },
- Error,
- /Invalid path: foo\/\. - 1:2/
- );
- shouldThrow(
- function() {
- astFor('{{foo/this/bar}}');
- },
- Error,
- /Invalid path: foo\/this - 1:2/
- );
+ it('should handle invalid paths', function () {
+ expectTemplate('{{foo/../bar}}').toParseError(Error, /Invalid path: foo\/\.\. - 1:2/);
+ expectTemplate('{{foo/./bar}}').toParseError(Error, /Invalid path: foo\/\. - 1:2/);
+ expectTemplate('{{foo/this/bar}}').toParseError(Error, /Invalid path: foo\/this - 1:2/);
});
- it('knows how to report the correct line number in errors', function() {
- shouldThrow(
- function() {
- astFor('hello\nmy\n{{foo}');
- },
- Error,
- /Parse error on line 3/
- );
- shouldThrow(
- function() {
- astFor('hello\n\nmy\n\n{{foo}');
- },
- Error,
- /Parse error on line 5/
- );
+ it('knows how to report the correct line number in errors', function () {
+ expectTemplate('hello\nmy\n{{foo}').toParseError(Error, /Parse error on line 3/);
+ expectTemplate('hello\n\nmy\n\n{{foo}').toParseError(Error, /Parse error on line 5/);
});
- it('knows how to report the correct line number in errors when the first character is a newline', function() {
- shouldThrow(
- function() {
- astFor('\n\nhello\n\nmy\n\n{{foo}');
- },
- Error,
- /Parse error on line 7/
- );
+ it('knows how to report the correct line number in errors when the first character is a newline', function () {
+ expectTemplate('\n\nhello\n\nmy\n\n{{foo}').toParseError(Error, /Parse error on line 7/);
});
- describe('externally compiled AST', function() {
- it('can pass through an already-compiled AST', function() {
- equals(
- astFor({
- type: 'Program',
- body: [{ type: 'ContentStatement', value: 'Hello' }]
- }),
- "CONTENT[ 'Hello' ]\n"
- );
+ describe('externally compiled AST', function () {
+ it('can pass through an already-compiled AST', function () {
+ expectTemplate({
+ type: 'Program',
+ body: [{
+ type: 'ContentStatement',
+ value: 'Hello'
+ }]
+ })
+ .toParseTo('CONTENT[ \'Hello\' ]\n');
});
});
- describe('directives', function() {
- it('should parse block directives', function() {
- equals(
- astFor('{{#* foo}}{{/foo}}'),
- 'DIRECTIVE BLOCK:\n PATH:foo []\n PROGRAM:\n'
- );
+ describe('directives', function () {
+ it('should parse block directives', function () {
+ expectTemplate('{{#* foo}}{{/foo}}')
+ .toParseTo('DIRECTIVE BLOCK:\n PATH:foo []\n PROGRAM:\n');
});
- it('should parse directives', function() {
- equals(astFor('{{* foo}}'), '{{ DIRECTIVE PATH:foo [] }}\n');
+ it('should parse directives', function () {
+ expectTemplate('{{* foo}}')
+ .toParseTo('{{ DIRECTIVE PATH:foo [] }}\n');
});
- it('should fail if directives have inverse', function() {
- shouldThrow(
- function() {
- astFor('{{#* foo}}{{^}}{{/foo}}');
- },
- Error,
- /Unexpected inverse/
- );
+ it('should fail if directives have inverse', function () {
+ expectTemplate('{{#* foo}}{{^}}{{/foo}}').toParseError(Error, /Unexpected inverse/);
});
});
- it('GH1024 - should track program location properly', function() {
+ it('GH1024 - should track program location properly', function () {
var p = Handlebars.parse(
'\n' +
- ' {{#if foo}}\n' +
- ' {{bar}}\n' +
- ' {{else}} {{baz}}\n' +
- '\n' +
- ' {{/if}}\n' +
- ' '
+ ' {{#if foo}}\n' +
+ ' {{bar}}\n' +
+ ' {{else}} {{baz}}\n' +
+ '\n' +
+ ' {{/if}}\n' +
+ ' '
);
// We really need a deep equals but for now this should be stable...
#/usr/bin/env bash
src=${1:-.}
dest=${2:-./out}
mkdir -p $dest
for x in basic blocks builtins data helpers javascript-compiler partials regressions security strict string-params subexpressions track-ids utils whitespace-control; do
node convert.js $src/$x.js > tmp.js
mv tmp.js $dest/$x.js
done
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment