diff --git a/lib/handlebars/compiler/ast.js b/lib/handlebars/compiler/ast.js index 599dab8d2..1699569ba 100644 --- a/lib/handlebars/compiler/ast.js +++ b/lib/handlebars/compiler/ast.js @@ -1,130 +1,4 @@ let AST = { - Program: function(statements, blockParams, strip, locInfo) { - this.loc = locInfo; - this.type = 'Program'; - this.body = statements; - - this.blockParams = blockParams; - this.strip = strip; - }, - - MustacheStatement: function(path, params, hash, escaped, strip, locInfo) { - this.loc = locInfo; - this.type = 'MustacheStatement'; - - this.path = path; - this.params = params || []; - this.hash = hash; - this.escaped = escaped; - - this.strip = strip; - }, - - BlockStatement: function(path, params, hash, program, inverse, openStrip, inverseStrip, closeStrip, locInfo) { - this.loc = locInfo; - this.type = 'BlockStatement'; - - this.path = path; - this.params = params || []; - this.hash = hash; - this.program = program; - this.inverse = inverse; - - this.openStrip = openStrip; - this.inverseStrip = inverseStrip; - this.closeStrip = closeStrip; - }, - - PartialStatement: function(name, params, hash, strip, locInfo) { - this.loc = locInfo; - this.type = 'PartialStatement'; - - this.name = name; - this.params = params || []; - this.hash = hash; - - this.indent = ''; - this.strip = strip; - }, - - ContentStatement: function(string, locInfo) { - this.loc = locInfo; - this.type = 'ContentStatement'; - this.original = this.value = string; - }, - - CommentStatement: function(comment, strip, locInfo) { - this.loc = locInfo; - this.type = 'CommentStatement'; - this.value = comment; - - this.strip = strip; - }, - - SubExpression: function(path, params, hash, locInfo) { - this.loc = locInfo; - - this.type = 'SubExpression'; - this.path = path; - this.params = params || []; - this.hash = hash; - }, - - PathExpression: function(data, depth, parts, original, locInfo) { - this.loc = locInfo; - this.type = 'PathExpression'; - - this.data = data; - this.original = original; - this.parts = parts; - this.depth = depth; - }, - - StringLiteral: function(string, locInfo) { - this.loc = locInfo; - this.type = 'StringLiteral'; - this.original = - this.value = string; - }, - - NumberLiteral: function(number, locInfo) { - this.loc = locInfo; - this.type = 'NumberLiteral'; - this.original = - this.value = Number(number); - }, - - BooleanLiteral: function(bool, locInfo) { - this.loc = locInfo; - this.type = 'BooleanLiteral'; - this.original = - this.value = bool === 'true'; - }, - - UndefinedLiteral: function(locInfo) { - this.loc = locInfo; - this.type = 'UndefinedLiteral'; - this.original = this.value = undefined; - }, - - NullLiteral: function(locInfo) { - this.loc = locInfo; - this.type = 'NullLiteral'; - this.original = this.value = null; - }, - - Hash: function(pairs, locInfo) { - this.loc = locInfo; - this.type = 'Hash'; - this.pairs = pairs; - }, - HashPair: function(key, value, locInfo) { - this.loc = locInfo; - this.type = 'HashPair'; - this.key = key; - this.value = value; - }, - // Public API used to evaluate derived attributes regarding AST nodes helpers: { // a mustache is definitely a helper if: diff --git a/lib/handlebars/compiler/base.js b/lib/handlebars/compiler/base.js index 85c2997b4..c6871d399 100644 --- a/lib/handlebars/compiler/base.js +++ b/lib/handlebars/compiler/base.js @@ -1,5 +1,4 @@ import parser from './parser'; -import AST from './ast'; import WhitespaceControl from './whitespace-control'; import * as Helpers from './helpers'; import { extend } from '../utils'; @@ -7,7 +6,7 @@ import { extend } from '../utils'; export { parser }; let yy = {}; -extend(yy, Helpers, AST); +extend(yy, Helpers); export function parse(input, options) { // Just return if an already-compiled AST was passed in. diff --git a/lib/handlebars/compiler/compiler.js b/lib/handlebars/compiler/compiler.js index 59a425f47..c8db7c955 100644 --- a/lib/handlebars/compiler/compiler.js +++ b/lib/handlebars/compiler/compiler.js @@ -514,6 +514,13 @@ function transformLiteralToPath(sexpr) { let literal = sexpr.path; // Casting to string here to make false and 0 literal values play nicely with the rest // of the system. - sexpr.path = new AST.PathExpression(false, 0, [literal.original + ''], literal.original + '', literal.loc); + sexpr.path = { + type: 'PathExpression', + data: false, + depth: 0, + parts: [literal.original + ''], + original: literal.original + '', + loc: literal.loc + }; } } diff --git a/lib/handlebars/compiler/helpers.js b/lib/handlebars/compiler/helpers.js index f2edfa110..bf72034a0 100644 --- a/lib/handlebars/compiler/helpers.js +++ b/lib/handlebars/compiler/helpers.js @@ -32,8 +32,8 @@ export function stripComment(comment) { .replace(/-?-?~?\}\}$/, ''); } -export function preparePath(data, parts, locInfo) { - locInfo = this.locInfo(locInfo); +export function preparePath(data, parts, loc) { + loc = this.locInfo(loc); let original = data ? '@' : '', dig = [], @@ -49,7 +49,7 @@ export function preparePath(data, parts, locInfo) { if (!isLiteral && (part === '..' || part === '.' || part === 'this')) { if (dig.length > 0) { - throw new Exception('Invalid path: ' + original, {loc: locInfo}); + throw new Exception('Invalid path: ' + original, {loc}); } else if (part === '..') { depth++; depthString += '../'; @@ -59,7 +59,14 @@ export function preparePath(data, parts, locInfo) { } } - return new this.PathExpression(data, depth, dig, original, locInfo); + return { + type: 'PathExpression', + data, + depth, + parts: dig, + original, + loc + }; } export function prepareMustache(path, params, hash, open, strip, locInfo) { @@ -67,7 +74,15 @@ export function prepareMustache(path, params, hash, open, strip, locInfo) { let escapeFlag = open.charAt(3) || open.charAt(2), escaped = escapeFlag !== '{' && escapeFlag !== '&'; - return new this.MustacheStatement(path, params, hash, escaped, strip, this.locInfo(locInfo)); + return { + type: 'MustacheStatement', + path, + params, + hash, + escaped, + strip, + loc: this.locInfo(locInfo) + }; } export function prepareRawBlock(openRawBlock, contents, close, locInfo) { @@ -78,13 +93,24 @@ export function prepareRawBlock(openRawBlock, contents, close, locInfo) { } locInfo = this.locInfo(locInfo); - let program = new this.Program(contents, null, {}, locInfo); + let program = { + type: 'Program', + body: contents, + strip: {}, + loc: locInfo + }; - return new this.BlockStatement( - openRawBlock.path, openRawBlock.params, openRawBlock.hash, - program, undefined, - {}, {}, {}, - locInfo); + return { + type: 'BlockStatement', + path: openRawBlock.path, + params: openRawBlock.params, + hash: openRawBlock.hash, + program, + openStrip: {}, + inverseStrip: {}, + closeStrip: {}, + loc: locInfo + }; } export function prepareBlock(openBlock, program, inverseAndProgram, close, inverted, locInfo) { @@ -115,11 +141,18 @@ export function prepareBlock(openBlock, program, inverseAndProgram, close, inver program = inverted; } - return new this.BlockStatement( - openBlock.path, openBlock.params, openBlock.hash, - program, inverse, - openBlock.strip, inverseStrip, close && close.strip, - this.locInfo(locInfo)); + return { + type: 'BlockStatement', + path: openBlock.path, + params: openBlock.params, + hash: openBlock.hash, + program, + inverse, + openStrip: openBlock.strip, + inverseStrip, + closeStrip: close && close.strip, + loc: this.locInfo(locInfo) + }; } export function prepareProgram(statements, loc) { @@ -143,7 +176,12 @@ export function prepareProgram(statements, loc) { } } - return new this.Program(statements, null, {}, loc); + return { + type: 'Program', + body: statements, + strip: {}, + loc: loc + }; } diff --git a/lib/handlebars/compiler/visitor.js b/lib/handlebars/compiler/visitor.js index ba7b3760c..47f86e0d3 100644 --- a/lib/handlebars/compiler/visitor.js +++ b/lib/handlebars/compiler/visitor.js @@ -1,5 +1,4 @@ import Exception from '../exception'; -import AST from './ast'; function Visitor() { this.parents = []; @@ -14,7 +13,7 @@ Visitor.prototype = { let value = this.accept(node[name]); if (this.mutating) { // Hacky sanity check: - if (value && (!value.type || !AST[value.type])) { + if (value && typeof value.type !== 'string') { throw new Exception('Unexpected node type "' + value.type + '" found when accepting ' + name + ' on ' + node.type); } node[name] = value; diff --git a/spec/ast.js b/spec/ast.js index dc5410fa2..8f346d882 100644 --- a/spec/ast.js +++ b/spec/ast.js @@ -5,159 +5,46 @@ describe('ast', function() { var AST = Handlebars.AST; - var LOCATION_INFO = { - start: { - line: 1, - column: 1 - }, - end: { - line: 1, - column: 1 - } - }; - - function testLocationInfoStorage(node) { - equals(node.loc.start.line, 1); - equals(node.loc.start.column, 1); - equals(node.loc.end.line, 1); - equals(node.loc.end.column, 1); - } - - describe('MustacheStatement', function() { - it('should store args', function() { - var mustache = new AST.MustacheStatement({}, null, null, true, {}, LOCATION_INFO); - equals(mustache.type, 'MustacheStatement'); - equals(mustache.escaped, true); - testLocationInfoStorage(mustache); - }); - }); describe('BlockStatement', function() { it('should throw on mustache mismatch', function() { shouldThrow(function() { handlebarsEnv.parse('\n {{#foo}}{{/bar}}'); }, Handlebars.Exception, "foo doesn't match bar - 2:5"); }); - - it('stores location info', function() { - var mustacheNode = new AST.MustacheStatement([{ original: 'foo'}], null, null, false, {}); - var block = new AST.BlockStatement( - mustacheNode, - null, null, - {body: []}, - {body: []}, - {}, - {}, - {}, - LOCATION_INFO); - testLocationInfoStorage(block); - }); - }); - describe('PathExpression', function() { - it('stores location info', function() { - var idNode = new AST.PathExpression(false, 0, [], 'foo', LOCATION_INFO); - testLocationInfoStorage(idNode); - }); - }); - - describe('Hash', function() { - it('stores location info', function() { - var hash = new AST.Hash([], LOCATION_INFO); - testLocationInfoStorage(hash); - }); - }); - - describe('ContentStatement', function() { - it('stores location info', function() { - var content = new AST.ContentStatement('HI', LOCATION_INFO); - testLocationInfoStorage(content); - }); - }); - - describe('CommentStatement', function() { - it('stores location info', function() { - var comment = new AST.CommentStatement('HI', {}, LOCATION_INFO); - testLocationInfoStorage(comment); - }); - }); - - describe('NumberLiteral', function() { - it('stores location info', function() { - var integer = new AST.NumberLiteral('6', LOCATION_INFO); - testLocationInfoStorage(integer); - }); - }); - - describe('StringLiteral', function() { - it('stores location info', function() { - var string = new AST.StringLiteral('6', LOCATION_INFO); - testLocationInfoStorage(string); - }); - }); - - describe('BooleanLiteral', function() { - it('stores location info', function() { - var bool = new AST.BooleanLiteral('true', LOCATION_INFO); - testLocationInfoStorage(bool); - }); - }); - - describe('PartialStatement', function() { - it('provides default params', function() { - var pn = new AST.PartialStatement('so_partial', undefined, {}, {}, LOCATION_INFO); - equals(pn.params.length, 0); - }); - it('stores location info', function() { - var pn = new AST.PartialStatement('so_partial', [], {}, {}, LOCATION_INFO); - testLocationInfoStorage(pn); - }); - }); - - describe('Program', function() { - it('storing location info', function() { - var pn = new AST.Program([], null, {}, LOCATION_INFO); - testLocationInfoStorage(pn); - }); - }); - - describe('SubExpression', function() { - it('provides default params', function() { - var pn = new AST.SubExpression('path', undefined, {}, LOCATION_INFO); - equals(pn.params.length, 0); - }); }); describe('helpers', function() { describe('#helperExpression', function() { it('should handle mustache statements', function() { - equals(AST.helpers.helperExpression(new AST.MustacheStatement('foo', [], undefined, false, {}, LOCATION_INFO)), false); - equals(AST.helpers.helperExpression(new AST.MustacheStatement('foo', [1], undefined, false, {}, LOCATION_INFO)), true); - equals(AST.helpers.helperExpression(new AST.MustacheStatement('foo', [], {}, false, {}, LOCATION_INFO)), true); + equals(AST.helpers.helperExpression({type: 'MustacheStatement', params: [], hash: undefined}), false); + equals(AST.helpers.helperExpression({type: 'MustacheStatement', params: [1], hash: undefined}), true); + equals(AST.helpers.helperExpression({type: 'MustacheStatement', params: [], hash: {}}), true); }); it('should handle block statements', function() { - equals(AST.helpers.helperExpression(new AST.BlockStatement('foo', [], undefined, false, {}, LOCATION_INFO)), false); - equals(AST.helpers.helperExpression(new AST.BlockStatement('foo', [1], undefined, false, {}, LOCATION_INFO)), true); - equals(AST.helpers.helperExpression(new AST.BlockStatement('foo', [], {}, false, {}, LOCATION_INFO)), true); + equals(AST.helpers.helperExpression({type: 'BlockStatement', params: [], hash: undefined}), false); + equals(AST.helpers.helperExpression({type: 'BlockStatement', params: [1], hash: undefined}), true); + equals(AST.helpers.helperExpression({type: 'BlockStatement', params: [], hash: {}}), true); }); it('should handle subexpressions', function() { - equals(AST.helpers.helperExpression(new AST.SubExpression()), true); + equals(AST.helpers.helperExpression({type: 'SubExpression'}), true); }); it('should work with non-helper nodes', function() { - equals(AST.helpers.helperExpression(new AST.Program([], [], {}, LOCATION_INFO)), false); + equals(AST.helpers.helperExpression({type: 'Program'}), false); - equals(AST.helpers.helperExpression(new AST.PartialStatement()), false); - equals(AST.helpers.helperExpression(new AST.ContentStatement('a', LOCATION_INFO)), false); - equals(AST.helpers.helperExpression(new AST.CommentStatement('a', {}, LOCATION_INFO)), false); + equals(AST.helpers.helperExpression({type: 'PartialStatement'}), false); + equals(AST.helpers.helperExpression({type: 'ContentStatement'}), false); + equals(AST.helpers.helperExpression({type: 'CommentStatement'}), false); - equals(AST.helpers.helperExpression(new AST.PathExpression(false, 0, ['a'], 'a', LOCATION_INFO)), false); + equals(AST.helpers.helperExpression({type: 'PathExpression'}), false); - equals(AST.helpers.helperExpression(new AST.StringLiteral('a', LOCATION_INFO)), false); - equals(AST.helpers.helperExpression(new AST.NumberLiteral(1, LOCATION_INFO)), false); - equals(AST.helpers.helperExpression(new AST.BooleanLiteral(true, LOCATION_INFO)), false); - equals(AST.helpers.helperExpression(new AST.UndefinedLiteral(LOCATION_INFO)), false); - equals(AST.helpers.helperExpression(new AST.NullLiteral(LOCATION_INFO)), false); + equals(AST.helpers.helperExpression({type: 'StringLiteral'}), false); + equals(AST.helpers.helperExpression({type: 'NumberLiteral'}), false); + equals(AST.helpers.helperExpression({type: 'BooleanLiteral'}), false); + equals(AST.helpers.helperExpression({type: 'UndefinedLiteral'}), false); + equals(AST.helpers.helperExpression({type: 'NullLiteral'}), false); - equals(AST.helpers.helperExpression(new AST.Hash([], LOCATION_INFO)), false); - equals(AST.helpers.helperExpression(new AST.HashPair('foo', 'bar', LOCATION_INFO)), false); + equals(AST.helpers.helperExpression({type: 'Hash'}), false); + equals(AST.helpers.helperExpression({type: 'HashPair'}), false); }); }); }); diff --git a/spec/compiler.js b/spec/compiler.js index fe4b63a30..be1fb007d 100644 --- a/spec/compiler.js +++ b/spec/compiler.js @@ -39,7 +39,10 @@ describe('compiler', function() { }); it('can utilize AST instance', function() { - equal(Handlebars.compile(new Handlebars.AST.Program([ new Handlebars.AST.ContentStatement('Hello')], null, {}))(), 'Hello'); + equal(Handlebars.compile({ + type: 'Program', + body: [ {type: 'ContentStatement', value: 'Hello'}] + })(), 'Hello'); }); it('can pass through an empty string', function() { @@ -58,7 +61,10 @@ describe('compiler', function() { }); it('can utilize AST instance', function() { - equal(/return "Hello"/.test(Handlebars.precompile(new Handlebars.AST.Program([ new Handlebars.AST.ContentStatement('Hello')]), null, {})), true); + equal(/return "Hello"/.test(Handlebars.precompile({ + type: 'Program', + body: [ {type: 'ContentStatement', value: 'Hello'}] + })), true); }); it('can pass through an empty string', function() { diff --git a/spec/parser.js b/spec/parser.js index 424e2d178..82c32d05d 100644 --- a/spec/parser.js +++ b/spec/parser.js @@ -228,7 +228,10 @@ describe('parser', function() { describe('externally compiled AST', function() { it('can pass through an already-compiled AST', function() { - equals(astFor(new Handlebars.AST.Program([new Handlebars.AST.ContentStatement('Hello')], null)), 'CONTENT[ \'Hello\' ]\n'); + equals(astFor({ + type: 'Program', + body: [ {type: 'ContentStatement', value: 'Hello'}] + }), 'CONTENT[ \'Hello\' ]\n'); }); }); diff --git a/spec/visitor.js b/spec/visitor.js index 1f50d79c1..23113cf77 100644 --- a/spec/visitor.js +++ b/spec/visitor.js @@ -49,7 +49,7 @@ describe('Visitor', function() { visitor.mutating = true; visitor.StringLiteral = function(string) { - return new Handlebars.AST.NumberLiteral(42, string.locInfo); + return {type: 'NumberLiteral', value: 42, loc: string.loc}; }; var ast = Handlebars.parse('{{foo foo="foo"}}'); @@ -109,7 +109,7 @@ describe('Visitor', function() { visitor.mutating = true; visitor.StringLiteral = function(string) { - return new Handlebars.AST.NumberLiteral(42, string.locInfo); + return {type: 'NumberLiteral', value: 42, loc: string.locInfo}; }; var ast = Handlebars.parse('{{foo "foo"}}'); diff --git a/src/handlebars.yy b/src/handlebars.yy index ecc79afc6..a05b477a3 100644 --- a/src/handlebars.yy +++ b/src/handlebars.yy @@ -18,12 +18,24 @@ statement | rawBlock -> $1 | partial -> $1 | content -> $1 - | COMMENT -> new yy.CommentStatement(yy.stripComment($1), yy.stripFlags($1, $1), yy.locInfo(@$)) - ; + | COMMENT { + $$ = { + type: 'CommentStatement', + value: yy.stripComment($1), + strip: yy.stripFlags($1, $1), + loc: yy.locInfo(@$) + }; + }; content - : CONTENT -> new yy.ContentStatement($1, yy.locInfo(@$)) - ; + : CONTENT { + $$ = { + type: 'ContentStatement', + original: $1, + value: $1, + loc: yy.locInfo(@$) + }; + }; rawBlock : openRawBlock content+ END_RAW_BLOCK -> yy.prepareRawBlock($1, $2, $3, @$) @@ -77,7 +89,17 @@ mustache ; partial - : OPEN_PARTIAL partialName param* hash? CLOSE -> new yy.PartialStatement($2, $3, $4, yy.stripFlags($1, $5), yy.locInfo(@$)) + : OPEN_PARTIAL partialName param* hash? CLOSE { + $$ = { + type: 'PartialStatement', + name: $2, + params: $3, + hash: $4, + indent: '', + strip: yy.stripFlags($1, $5), + loc: yy.locInfo(@$) + }; + } ; param @@ -86,15 +108,22 @@ param ; sexpr - : OPEN_SEXPR helperName param* hash? CLOSE_SEXPR -> new yy.SubExpression($2, $3, $4, yy.locInfo(@$)) - ; + : OPEN_SEXPR helperName param* hash? CLOSE_SEXPR { + $$ = { + type: 'SubExpression', + path: $2, + params: $3, + hash: $4, + loc: yy.locInfo(@$) + }; + }; hash - : hashSegment+ -> new yy.Hash($1, yy.locInfo(@$)) + : hashSegment+ -> {type: 'Hash', pairs: $1, loc: yy.locInfo(@$)} ; hashSegment - : ID EQUALS param -> new yy.HashPair(yy.id($1), $3, yy.locInfo(@$)) + : ID EQUALS param -> {type: 'HashPair', key: yy.id($1), value: $3, loc: yy.locInfo(@$)} ; blockParams @@ -104,11 +133,11 @@ blockParams helperName : path -> $1 | dataName -> $1 - | STRING -> new yy.StringLiteral($1, yy.locInfo(@$)) - | NUMBER -> new yy.NumberLiteral($1, yy.locInfo(@$)) - | BOOLEAN -> new yy.BooleanLiteral($1, yy.locInfo(@$)) - | UNDEFINED -> new yy.UndefinedLiteral(yy.locInfo(@$)) - | NULL -> new yy.NullLiteral(yy.locInfo(@$)) + | STRING -> {type: 'StringLiteral', value: $1, original: $1, loc: yy.locInfo(@$)} + | NUMBER -> {type: 'NumberLiteral', value: Number($1), original: Number($1), loc: yy.locInfo(@$)} + | BOOLEAN -> {type: 'BooleanLiteral', value: $1 === 'true', original: $1 === 'true', loc: yy.locInfo(@$)} + | UNDEFINED -> {type: 'UndefinedLiteral', original: undefined, value: undefined, loc: yy.locInfo(@$)} + | NULL -> {type: 'NullLiteral', original: null, value: null, loc: yy.locInfo(@$)} ; partialName