diff --git a/.eslintignore b/.eslintignore index 714d84019..424ad0b0e 100644 --- a/.eslintignore +++ b/.eslintignore @@ -12,7 +12,6 @@ node_modules .nyc_output # Generated files -lib/handlebars/compiler/parser.js /coverage/ /dist/ /integration-testing/*/dist/ diff --git a/.gitignore b/.gitignore index 97e44f885..7bccd17e3 100644 --- a/.gitignore +++ b/.gitignore @@ -13,8 +13,7 @@ node_modules .nyc_output # Generated files -lib/handlebars/compiler/parser.js /coverage/ /dist/ /integration-testing/*/dist/ -/spec/tmp/* \ No newline at end of file +/spec/tmp/* diff --git a/.prettierignore b/.prettierignore index 724620669..f160a7f51 100644 --- a/.prettierignore +++ b/.prettierignore @@ -12,7 +12,6 @@ node_modules .nyc_output # Generated files -lib/handlebars/compiler/parser.js /coverage/ /dist/ /integration-testing/*/dist/ diff --git a/Gruntfile.js b/Gruntfile.js index 8b9538320..deacdc432 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -3,12 +3,7 @@ module.exports = function(grunt) { grunt.initConfig({ pkg: grunt.file.readJSON('package.json'), - clean: [ - 'tmp', - 'dist', - 'lib/handlebars/compiler/parser.js', - 'integration-testing/**/node_modules' - ], + clean: ['tmp', 'dist', 'integration-testing/**/node_modules'], copy: { dist: { @@ -198,7 +193,7 @@ module.exports = function(grunt) { this.registerTask( 'build', 'Builds a distributable version of the current project', - ['parser', 'node', 'globals'] + ['node', 'globals'] ); this.registerTask('node', ['babel:cjs']); diff --git a/lib/handlebars.js b/lib/handlebars.js index c4f8fc781..64a288c35 100644 --- a/lib/handlebars.js +++ b/lib/handlebars.js @@ -1,15 +1,16 @@ +import { + parser as Parser, + parse, + parseWithoutProcessing, + Visitor +} from '@handlebars/parser'; + import runtime from './handlebars.runtime'; // Compiler imports import AST from './handlebars/compiler/ast'; -import { - parser as Parser, - parse, - parseWithoutProcessing -} from './handlebars/compiler/base'; import { Compiler, compile, precompile } from './handlebars/compiler/compiler'; import JavaScriptCompiler from './handlebars/compiler/javascript-compiler'; -import Visitor from './handlebars/compiler/visitor'; import noConflict from './handlebars/no-conflict'; diff --git a/lib/handlebars.runtime.js b/lib/handlebars.runtime.js index 3d05b5448..6b6270b2f 100644 --- a/lib/handlebars.runtime.js +++ b/lib/handlebars.runtime.js @@ -1,9 +1,9 @@ +import { Exception } from '@handlebars/parser'; import * as base from './handlebars/base'; // Each of these augment the Handlebars object. No need to setup here. // (This is done to easily share code between commonjs and browse envs) import SafeString from './handlebars/safe-string'; -import Exception from './handlebars/exception'; import * as Utils from './handlebars/utils'; import * as runtime from './handlebars/runtime'; diff --git a/lib/handlebars/base.js b/lib/handlebars/base.js index 753c9a466..de4f18d43 100644 --- a/lib/handlebars/base.js +++ b/lib/handlebars/base.js @@ -1,5 +1,5 @@ +import { Exception } from '@handlebars/parser'; import { createFrame, extend, toString } from './utils'; -import Exception from './exception'; import { registerDefaultHelpers } from './helpers'; import { registerDefaultDecorators } from './decorators'; import logger from './logger'; diff --git a/lib/handlebars/compiler/base.js b/lib/handlebars/compiler/base.js deleted file mode 100644 index 1dd5af1a4..000000000 --- a/lib/handlebars/compiler/base.js +++ /dev/null @@ -1,34 +0,0 @@ -import parser from './parser'; -import WhitespaceControl from './whitespace-control'; -import * as Helpers from './helpers'; -import { extend } from '../utils'; - -export { parser }; - -let yy = {}; -extend(yy, Helpers); - -export function parseWithoutProcessing(input, options) { - // Just return if an already-compiled AST was passed in. - if (input.type === 'Program') { - return input; - } - - parser.yy = yy; - - // Altering the shared object here, but this is ok as parser is a sync operation - yy.locInfo = function(locInfo) { - return new yy.SourceLocation(options && options.srcName, locInfo); - }; - - let ast = parser.parse(input); - - return ast; -} - -export function parse(input, options) { - let ast = parseWithoutProcessing(input, options); - let strip = new WhitespaceControl(options); - - return strip.accept(ast); -} diff --git a/lib/handlebars/compiler/compiler.js b/lib/handlebars/compiler/compiler.js index d759dd4d3..83a13ab51 100644 --- a/lib/handlebars/compiler/compiler.js +++ b/lib/handlebars/compiler/compiler.js @@ -1,6 +1,6 @@ /* eslint-disable new-cap */ -import Exception from '../exception'; +import { Exception } from '@handlebars/parser'; import { isArray, indexOf, extend } from '../utils'; import AST from './ast'; diff --git a/lib/handlebars/compiler/helpers.js b/lib/handlebars/compiler/helpers.js deleted file mode 100644 index 27033a1d5..000000000 --- a/lib/handlebars/compiler/helpers.js +++ /dev/null @@ -1,219 +0,0 @@ -import Exception from '../exception'; - -function validateClose(open, close) { - close = close.path ? close.path.original : close; - - if (open.path.original !== close) { - let errorNode = { loc: open.path.loc }; - - throw new Exception( - open.path.original + " doesn't match " + close, - errorNode - ); - } -} - -export function SourceLocation(source, locInfo) { - this.source = source; - this.start = { - line: locInfo.first_line, - column: locInfo.first_column - }; - this.end = { - line: locInfo.last_line, - column: locInfo.last_column - }; -} - -export function id(token) { - if (/^\[.*\]$/.test(token)) { - return token.substring(1, token.length - 1); - } else { - return token; - } -} - -export function stripFlags(open, close) { - return { - open: open.charAt(2) === '~', - close: close.charAt(close.length - 3) === '~' - }; -} - -export function stripComment(comment) { - return comment.replace(/^\{\{~?!-?-?/, '').replace(/-?-?~?\}\}$/, ''); -} - -export function preparePath(data, parts, loc) { - loc = this.locInfo(loc); - - let original = data ? '@' : '', - dig = [], - depth = 0; - - for (let i = 0, l = parts.length; i < l; i++) { - let part = parts[i].part, - // If we have [] syntax then we do not treat path references as operators, - // i.e. foo.[this] resolves to approximately context.foo['this'] - isLiteral = parts[i].original !== part; - original += (parts[i].separator || '') + part; - - if (!isLiteral && (part === '..' || part === '.' || part === 'this')) { - if (dig.length > 0) { - throw new Exception('Invalid path: ' + original, { loc }); - } else if (part === '..') { - depth++; - } - } else { - dig.push(part); - } - } - - return { - type: 'PathExpression', - data, - depth, - parts: dig, - original, - loc - }; -} - -export function prepareMustache(path, params, hash, open, strip, locInfo) { - // Must use charAt to support IE pre-10 - let escapeFlag = open.charAt(3) || open.charAt(2), - escaped = escapeFlag !== '{' && escapeFlag !== '&'; - - let decorator = /\*/.test(open); - return { - type: decorator ? 'Decorator' : 'MustacheStatement', - path, - params, - hash, - escaped, - strip, - loc: this.locInfo(locInfo) - }; -} - -export function prepareRawBlock(openRawBlock, contents, close, locInfo) { - validateClose(openRawBlock, close); - - locInfo = this.locInfo(locInfo); - let program = { - type: 'Program', - body: contents, - strip: {}, - loc: 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 -) { - if (close && close.path) { - validateClose(openBlock, close); - } - - let decorator = /\*/.test(openBlock.open); - - program.blockParams = openBlock.blockParams; - - let inverse, inverseStrip; - - if (inverseAndProgram) { - if (decorator) { - throw new Exception( - 'Unexpected inverse block on decorator', - inverseAndProgram - ); - } - - if (inverseAndProgram.chain) { - inverseAndProgram.program.body[0].closeStrip = close.strip; - } - - inverseStrip = inverseAndProgram.strip; - inverse = inverseAndProgram.program; - } - - if (inverted) { - inverted = inverse; - inverse = program; - program = inverted; - } - - return { - type: decorator ? 'DecoratorBlock' : '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) { - if (!loc && statements.length) { - const firstLoc = statements[0].loc, - lastLoc = statements[statements.length - 1].loc; - - /* istanbul ignore else */ - if (firstLoc && lastLoc) { - loc = { - source: firstLoc.source, - start: { - line: firstLoc.start.line, - column: firstLoc.start.column - }, - end: { - line: lastLoc.end.line, - column: lastLoc.end.column - } - }; - } - } - - return { - type: 'Program', - body: statements, - strip: {}, - loc: loc - }; -} - -export function preparePartialBlock(open, program, close, locInfo) { - validateClose(open, close); - - return { - type: 'PartialBlockStatement', - name: open.path, - params: open.params, - hash: open.hash, - program, - openStrip: open.strip, - closeStrip: close && close.strip, - loc: this.locInfo(locInfo) - }; -} diff --git a/lib/handlebars/compiler/javascript-compiler.js b/lib/handlebars/compiler/javascript-compiler.js index 7b48e9a3f..47c498e12 100644 --- a/lib/handlebars/compiler/javascript-compiler.js +++ b/lib/handlebars/compiler/javascript-compiler.js @@ -1,5 +1,5 @@ +import { Exception } from '@handlebars/parser'; import { COMPILER_REVISION, REVISION_CHANGES } from '../base'; -import Exception from '../exception'; import { isArray } from '../utils'; import CodeGen from './code-gen'; diff --git a/lib/handlebars/compiler/printer.js b/lib/handlebars/compiler/printer.js deleted file mode 100644 index e087806cd..000000000 --- a/lib/handlebars/compiler/printer.js +++ /dev/null @@ -1,178 +0,0 @@ -/* eslint-disable new-cap */ -import Visitor from './visitor'; - -export function print(ast) { - return new PrintVisitor().accept(ast); -} - -export function PrintVisitor() { - this.padding = 0; -} - -PrintVisitor.prototype = new Visitor(); - -PrintVisitor.prototype.pad = function(string) { - let out = ''; - - for (let i = 0, l = this.padding; i < l; i++) { - out += ' '; - } - - out += string + '\n'; - return out; -}; - -PrintVisitor.prototype.Program = function(program) { - let out = '', - body = program.body, - i, - l; - - if (program.blockParams) { - let blockParams = 'BLOCK PARAMS: ['; - for (i = 0, l = program.blockParams.length; i < l; i++) { - blockParams += ' ' + program.blockParams[i]; - } - blockParams += ' ]'; - out += this.pad(blockParams); - } - - for (i = 0, l = body.length; i < l; i++) { - out += this.accept(body[i]); - } - - this.padding--; - - return out; -}; - -PrintVisitor.prototype.MustacheStatement = function(mustache) { - return this.pad('{{ ' + this.SubExpression(mustache) + ' }}'); -}; -PrintVisitor.prototype.Decorator = function(mustache) { - return this.pad('{{ DIRECTIVE ' + this.SubExpression(mustache) + ' }}'); -}; - -PrintVisitor.prototype.BlockStatement = PrintVisitor.prototype.DecoratorBlock = function( - block -) { - let out = ''; - - out += this.pad( - (block.type === 'DecoratorBlock' ? 'DIRECTIVE ' : '') + 'BLOCK:' - ); - this.padding++; - out += this.pad(this.SubExpression(block)); - if (block.program) { - out += this.pad('PROGRAM:'); - this.padding++; - out += this.accept(block.program); - this.padding--; - } - if (block.inverse) { - if (block.program) { - this.padding++; - } - out += this.pad('{{^}}'); - this.padding++; - out += this.accept(block.inverse); - this.padding--; - if (block.program) { - this.padding--; - } - } - this.padding--; - - return out; -}; - -PrintVisitor.prototype.PartialStatement = function(partial) { - let content = 'PARTIAL:' + partial.name.original; - if (partial.params[0]) { - content += ' ' + this.accept(partial.params[0]); - } - if (partial.hash) { - content += ' ' + this.accept(partial.hash); - } - return this.pad('{{> ' + content + ' }}'); -}; -PrintVisitor.prototype.PartialBlockStatement = function(partial) { - let content = 'PARTIAL BLOCK:' + partial.name.original; - if (partial.params[0]) { - content += ' ' + this.accept(partial.params[0]); - } - if (partial.hash) { - content += ' ' + this.accept(partial.hash); - } - - content += ' ' + this.pad('PROGRAM:'); - this.padding++; - content += this.accept(partial.program); - this.padding--; - - return this.pad('{{> ' + content + ' }}'); -}; - -PrintVisitor.prototype.ContentStatement = function(content) { - return this.pad("CONTENT[ '" + content.value + "' ]"); -}; - -PrintVisitor.prototype.CommentStatement = function(comment) { - return this.pad("{{! '" + comment.value + "' }}"); -}; - -PrintVisitor.prototype.SubExpression = function(sexpr) { - let params = sexpr.params, - paramStrings = [], - hash; - - for (let i = 0, l = params.length; i < l; i++) { - paramStrings.push(this.accept(params[i])); - } - - params = '[' + paramStrings.join(', ') + ']'; - - hash = sexpr.hash ? ' ' + this.accept(sexpr.hash) : ''; - - return this.accept(sexpr.path) + ' ' + params + hash; -}; - -PrintVisitor.prototype.PathExpression = function(id) { - let path = id.parts.join('/'); - return (id.data ? '@' : '') + 'PATH:' + path; -}; - -PrintVisitor.prototype.StringLiteral = function(string) { - return '"' + string.value + '"'; -}; - -PrintVisitor.prototype.NumberLiteral = function(number) { - return 'NUMBER{' + number.value + '}'; -}; - -PrintVisitor.prototype.BooleanLiteral = function(bool) { - return 'BOOLEAN{' + bool.value + '}'; -}; - -PrintVisitor.prototype.UndefinedLiteral = function() { - return 'UNDEFINED'; -}; - -PrintVisitor.prototype.NullLiteral = function() { - return 'NULL'; -}; - -PrintVisitor.prototype.Hash = function(hash) { - let pairs = hash.pairs, - joinedPairs = []; - - for (let i = 0, l = pairs.length; i < l; i++) { - joinedPairs.push(this.accept(pairs[i])); - } - - return 'HASH{' + joinedPairs.join(', ') + '}'; -}; -PrintVisitor.prototype.HashPair = function(pair) { - return pair.key + '=' + this.accept(pair.value); -}; -/* eslint-enable new-cap */ diff --git a/lib/handlebars/compiler/visitor.js b/lib/handlebars/compiler/visitor.js deleted file mode 100644 index 76bb01f12..000000000 --- a/lib/handlebars/compiler/visitor.js +++ /dev/null @@ -1,136 +0,0 @@ -import Exception from '../exception'; - -function Visitor() { - this.parents = []; -} - -Visitor.prototype = { - constructor: Visitor, - mutating: false, - - // Visits a given value. If mutating, will replace the value if necessary. - acceptKey: function(node, name) { - let value = this.accept(node[name]); - if (this.mutating) { - // Hacky sanity check: This may have a few false positives for type for the helper - // methods but will generally do the right thing without a lot of overhead. - if (value && !Visitor.prototype[value.type]) { - throw new Exception( - 'Unexpected node type "' + - value.type + - '" found when accepting ' + - name + - ' on ' + - node.type - ); - } - node[name] = value; - } - }, - - // Performs an accept operation with added sanity check to ensure - // required keys are not removed. - acceptRequired: function(node, name) { - this.acceptKey(node, name); - - if (!node[name]) { - throw new Exception(node.type + ' requires ' + name); - } - }, - - // Traverses a given array. If mutating, empty respnses will be removed - // for child elements. - acceptArray: function(array) { - for (let i = 0, l = array.length; i < l; i++) { - this.acceptKey(array, i); - - if (!array[i]) { - array.splice(i, 1); - i--; - l--; - } - } - }, - - accept: function(object) { - if (!object) { - return; - } - - /* istanbul ignore next: Sanity code */ - if (!this[object.type]) { - throw new Exception('Unknown type: ' + object.type, object); - } - - if (this.current) { - this.parents.unshift(this.current); - } - this.current = object; - - let ret = this[object.type](object); - - this.current = this.parents.shift(); - - if (!this.mutating || ret) { - return ret; - } else if (ret !== false) { - return object; - } - }, - - Program: function(program) { - this.acceptArray(program.body); - }, - - MustacheStatement: visitSubExpression, - Decorator: visitSubExpression, - - BlockStatement: visitBlock, - DecoratorBlock: visitBlock, - - PartialStatement: visitPartial, - PartialBlockStatement: function(partial) { - visitPartial.call(this, partial); - - this.acceptKey(partial, 'program'); - }, - - ContentStatement: function(/* content */) {}, - CommentStatement: function(/* comment */) {}, - - SubExpression: visitSubExpression, - - PathExpression: function(/* path */) {}, - - StringLiteral: function(/* string */) {}, - NumberLiteral: function(/* number */) {}, - BooleanLiteral: function(/* bool */) {}, - UndefinedLiteral: function(/* literal */) {}, - NullLiteral: function(/* literal */) {}, - - Hash: function(hash) { - this.acceptArray(hash.pairs); - }, - HashPair: function(pair) { - this.acceptRequired(pair, 'value'); - } -}; - -function visitSubExpression(mustache) { - this.acceptRequired(mustache, 'path'); - this.acceptArray(mustache.params); - this.acceptKey(mustache, 'hash'); -} -function visitBlock(block) { - visitSubExpression.call(this, block); - - this.acceptKey(block, 'program'); - this.acceptKey(block, 'inverse'); -} -function visitPartial(partial) { - this.acceptRequired(partial, 'name'); - this.acceptArray(partial.params); - this.acceptKey(partial, 'hash'); -} - -export default Visitor; diff --git a/lib/handlebars/compiler/whitespace-control.js b/lib/handlebars/compiler/whitespace-control.js deleted file mode 100644 index c437bf013..000000000 --- a/lib/handlebars/compiler/whitespace-control.js +++ /dev/null @@ -1,234 +0,0 @@ -import Visitor from './visitor'; - -function WhitespaceControl(options = {}) { - this.options = options; -} -WhitespaceControl.prototype = new Visitor(); - -WhitespaceControl.prototype.Program = function(program) { - const doStandalone = !this.options.ignoreStandalone; - - let isRoot = !this.isRootSeen; - this.isRootSeen = true; - - let body = program.body; - for (let i = 0, l = body.length; i < l; i++) { - let current = body[i], - strip = this.accept(current); - - if (!strip) { - continue; - } - - let _isPrevWhitespace = isPrevWhitespace(body, i, isRoot), - _isNextWhitespace = isNextWhitespace(body, i, isRoot), - openStandalone = strip.openStandalone && _isPrevWhitespace, - closeStandalone = strip.closeStandalone && _isNextWhitespace, - inlineStandalone = - strip.inlineStandalone && _isPrevWhitespace && _isNextWhitespace; - - if (strip.close) { - omitRight(body, i, true); - } - if (strip.open) { - omitLeft(body, i, true); - } - - if (doStandalone && inlineStandalone) { - omitRight(body, i); - - if (omitLeft(body, i)) { - // If we are on a standalone node, save the indent info for partials - if (current.type === 'PartialStatement') { - // Pull out the whitespace from the final line - current.indent = /([ \t]+$)/.exec(body[i - 1].original)[1]; - } - } - } - if (doStandalone && openStandalone) { - omitRight((current.program || current.inverse).body); - - // Strip out the previous content node if it's whitespace only - omitLeft(body, i); - } - if (doStandalone && closeStandalone) { - // Always strip the next node - omitRight(body, i); - - omitLeft((current.inverse || current.program).body); - } - } - - return program; -}; - -WhitespaceControl.prototype.BlockStatement = WhitespaceControl.prototype.DecoratorBlock = WhitespaceControl.prototype.PartialBlockStatement = function( - block -) { - this.accept(block.program); - this.accept(block.inverse); - - // Find the inverse program that is involed with whitespace stripping. - let program = block.program || block.inverse, - inverse = block.program && block.inverse, - firstInverse = inverse, - lastInverse = inverse; - - if (inverse && inverse.chained) { - firstInverse = inverse.body[0].program; - - // Walk the inverse chain to find the last inverse that is actually in the chain. - while (lastInverse.chained) { - lastInverse = lastInverse.body[lastInverse.body.length - 1].program; - } - } - - let strip = { - open: block.openStrip.open, - close: block.closeStrip.close, - - // Determine the standalone candiacy. Basically flag our content as being possibly standalone - // so our parent can determine if we actually are standalone - openStandalone: isNextWhitespace(program.body), - closeStandalone: isPrevWhitespace((firstInverse || program).body) - }; - - if (block.openStrip.close) { - omitRight(program.body, null, true); - } - - if (inverse) { - let inverseStrip = block.inverseStrip; - - if (inverseStrip.open) { - omitLeft(program.body, null, true); - } - - if (inverseStrip.close) { - omitRight(firstInverse.body, null, true); - } - if (block.closeStrip.open) { - omitLeft(lastInverse.body, null, true); - } - - // Find standalone else statments - if ( - !this.options.ignoreStandalone && - isPrevWhitespace(program.body) && - isNextWhitespace(firstInverse.body) - ) { - omitLeft(program.body); - omitRight(firstInverse.body); - } - } else if (block.closeStrip.open) { - omitLeft(program.body, null, true); - } - - return strip; -}; - -WhitespaceControl.prototype.Decorator = WhitespaceControl.prototype.MustacheStatement = function( - mustache -) { - return mustache.strip; -}; - -WhitespaceControl.prototype.PartialStatement = WhitespaceControl.prototype.CommentStatement = function( - node -) { - /* istanbul ignore next */ - let strip = node.strip || {}; - return { - inlineStandalone: true, - open: strip.open, - close: strip.close - }; -}; - -function isPrevWhitespace(body, i, isRoot) { - if (i === undefined) { - i = body.length; - } - - // Nodes that end with newlines are considered whitespace (but are special - // cased for strip operations) - let prev = body[i - 1], - sibling = body[i - 2]; - if (!prev) { - return isRoot; - } - - if (prev.type === 'ContentStatement') { - return (sibling || !isRoot ? /\r?\n\s*?$/ : /(^|\r?\n)\s*?$/).test( - prev.original - ); - } -} -function isNextWhitespace(body, i, isRoot) { - if (i === undefined) { - i = -1; - } - - let next = body[i + 1], - sibling = body[i + 2]; - if (!next) { - return isRoot; - } - - if (next.type === 'ContentStatement') { - return (sibling || !isRoot ? /^\s*?\r?\n/ : /^\s*?(\r?\n|$)/).test( - next.original - ); - } -} - -// Marks the node to the right of the position as omitted. -// I.e. {{foo}}' ' will mark the ' ' node as omitted. -// -// If i is undefined, then the first child will be marked as such. -// -// If multiple is truthy then all whitespace will be stripped out until non-whitespace -// content is met. -function omitRight(body, i, multiple) { - let current = body[i == null ? 0 : i + 1]; - if ( - !current || - current.type !== 'ContentStatement' || - (!multiple && current.rightStripped) - ) { - return; - } - - let original = current.value; - current.value = current.value.replace( - multiple ? /^\s+/ : /^[ \t]*\r?\n?/, - '' - ); - current.rightStripped = current.value !== original; -} - -// Marks the node to the left of the position as omitted. -// I.e. ' '{{foo}} will mark the ' ' node as omitted. -// -// If i is undefined then the last child will be marked as such. -// -// If multiple is truthy then all whitespace will be stripped out until non-whitespace -// content is met. -function omitLeft(body, i, multiple) { - let current = body[i == null ? body.length - 1 : i - 1]; - if ( - !current || - current.type !== 'ContentStatement' || - (!multiple && current.leftStripped) - ) { - return; - } - - // We omit the last node if it's whitespace only and not preceded by a non-content node. - let original = current.value; - current.value = current.value.replace(multiple ? /\s+$/ : /[ \t]+$/, ''); - current.leftStripped = current.value !== original; - return current.leftStripped; -} - -export default WhitespaceControl; diff --git a/lib/handlebars/exception.js b/lib/handlebars/exception.js deleted file mode 100644 index b2fdc00bc..000000000 --- a/lib/handlebars/exception.js +++ /dev/null @@ -1,68 +0,0 @@ -const errorProps = [ - 'description', - 'fileName', - 'lineNumber', - 'endLineNumber', - 'message', - 'name', - 'number', - 'stack' -]; - -function Exception(message, node) { - let loc = node && node.loc, - line, - endLineNumber, - column, - endColumn; - - if (loc) { - line = loc.start.line; - endLineNumber = loc.end.line; - column = loc.start.column; - endColumn = loc.end.column; - - message += ' - ' + line + ':' + column; - } - - let tmp = Error.prototype.constructor.call(this, message); - - // Unfortunately errors are not enumerable in Chrome (at least), so `for prop in tmp` doesn't work. - for (let idx = 0; idx < errorProps.length; idx++) { - this[errorProps[idx]] = tmp[errorProps[idx]]; - } - - /* istanbul ignore else */ - if (Error.captureStackTrace) { - Error.captureStackTrace(this, Exception); - } - - try { - if (loc) { - this.lineNumber = line; - this.endLineNumber = endLineNumber; - - // Work around issue under safari where we can't directly set the column value - /* istanbul ignore next */ - if (Object.defineProperty) { - Object.defineProperty(this, 'column', { - value: column, - enumerable: true - }); - Object.defineProperty(this, 'endColumn', { - value: endColumn, - enumerable: true - }); - } else { - this.column = column; - this.endColumn = endColumn; - } - } - } catch (nop) { - /* Ignore if the browser is very particular */ - } -} - -Exception.prototype = new Error(); - -export default Exception; diff --git a/lib/handlebars/helpers/each.js b/lib/handlebars/helpers/each.js index 25651f453..33d19190d 100644 --- a/lib/handlebars/helpers/each.js +++ b/lib/handlebars/helpers/each.js @@ -1,5 +1,5 @@ +import { Exception } from '@handlebars/parser'; import { createFrame, isArray, isFunction } from '../utils'; -import Exception from '../exception'; export default function(instance) { instance.registerHelper('each', function(context, options) { diff --git a/lib/handlebars/helpers/helper-missing.js b/lib/handlebars/helpers/helper-missing.js index f8869e9c9..6daf44253 100644 --- a/lib/handlebars/helpers/helper-missing.js +++ b/lib/handlebars/helpers/helper-missing.js @@ -1,4 +1,4 @@ -import Exception from '../exception'; +import { Exception } from '@handlebars/parser'; export default function(instance) { instance.registerHelper('helperMissing', function(/* [args, ]options */) { diff --git a/lib/handlebars/helpers/if.js b/lib/handlebars/helpers/if.js index 55241ecb7..db06df262 100644 --- a/lib/handlebars/helpers/if.js +++ b/lib/handlebars/helpers/if.js @@ -1,5 +1,5 @@ +import { Exception } from '@handlebars/parser'; import { isEmpty, isFunction } from '../utils'; -import Exception from '../exception'; export default function(instance) { instance.registerHelper('if', function(conditional, options) { diff --git a/lib/handlebars/helpers/with.js b/lib/handlebars/helpers/with.js index 3394b7ae6..63559be5c 100644 --- a/lib/handlebars/helpers/with.js +++ b/lib/handlebars/helpers/with.js @@ -1,5 +1,5 @@ +import { Exception } from '@handlebars/parser'; import { isEmpty, isFunction } from '../utils'; -import Exception from '../exception'; export default function(instance) { instance.registerHelper('with', function(context, options) { diff --git a/lib/handlebars/runtime.js b/lib/handlebars/runtime.js index cb3ff3d04..eb31bfa52 100644 --- a/lib/handlebars/runtime.js +++ b/lib/handlebars/runtime.js @@ -1,5 +1,5 @@ +import { Exception } from '@handlebars/parser'; import * as Utils from './utils'; -import Exception from './exception'; import { COMPILER_REVISION, createFrame, diff --git a/lib/index.js b/lib/index.js index 0383c02f7..06547f1db 100644 --- a/lib/index.js +++ b/lib/index.js @@ -6,9 +6,9 @@ var handlebars = require('../dist/cjs/handlebars')['default']; -var printer = require('../dist/cjs/handlebars/compiler/printer'); -handlebars.PrintVisitor = printer.PrintVisitor; -handlebars.print = printer.print; +var parser = require('@handlebars/parser'); +handlebars.PrintVisitor = parser.PrintVisitor; +handlebars.print = parser.print; module.exports = handlebars; diff --git a/nyc.config.js b/nyc.config.js index f516eb2b4..3f52f089e 100644 --- a/nyc.config.js +++ b/nyc.config.js @@ -4,6 +4,6 @@ module.exports = { lines: 100, functions: 100, statements: 100, - exclude: ['**/spec/**', '**/handlebars/compiler/parser.js'], + exclude: ['**/spec/**'], reporter: 'html' }; diff --git a/package-lock.json b/package-lock.json index 699777bdf..835fffde9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -166,6 +166,11 @@ } } }, + "@handlebars/parser": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@handlebars/parser/-/parser-1.1.0.tgz", + "integrity": "sha512-rR7tJoSwJ2eooOpYGxGGW95sLq6GXUaS1UtWvN7pei6n2/okYvCGld9vsUTvkl2migxbkszsycwtMf/GEc1k1A==" + }, "@knappi/grunt-saucelabs": { "version": "9.0.2", "resolved": "https://registry.npmjs.org/@knappi/grunt-saucelabs/-/grunt-saucelabs-9.0.2.tgz", diff --git a/package.json b/package.json index 296074952..5e2d1033c 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "node": ">=10" }, "dependencies": { + "@handlebars/parser": "^1.1.0", "neo-async": "^2.6.0", "source-map": "^0.6.1", "yargs": "^15.3.1" diff --git a/spec/ast.js b/spec/ast.js index 1f4146ae0..d31752048 100644 --- a/spec/ast.js +++ b/spec/ast.js @@ -181,251 +181,4 @@ describe('ast', function() { testColumns(chainInverseNode.inverse.body[0].inverse, 10, 11, 8, 0); }); }); - - describe('whitespace control', function() { - describe('parse', function() { - it('mustache', function() { - var ast = Handlebars.parse(' {{~comment~}} '); - - equals(ast.body[0].value, ''); - equals(ast.body[2].value, ''); - }); - - it('block statements', function() { - var ast = Handlebars.parse(' {{# comment~}} \nfoo\n {{~/comment}}'); - - equals(ast.body[0].value, ''); - equals(ast.body[1].program.body[0].value, 'foo'); - }); - }); - - describe('parseWithoutProcessing', function() { - it('mustache', function() { - var ast = Handlebars.parseWithoutProcessing(' {{~comment~}} '); - - equals(ast.body[0].value, ' '); - equals(ast.body[2].value, ' '); - }); - - it('block statements', function() { - var ast = Handlebars.parseWithoutProcessing( - ' {{# comment~}} \nfoo\n {{~/comment}}' - ); - - equals(ast.body[0].value, ' '); - equals(ast.body[1].program.body[0].value, ' \nfoo\n '); - }); - }); - }); - - describe('standalone flags', function() { - describe('mustache', function() { - it('does not mark mustaches as standalone', function() { - var ast = Handlebars.parse(' {{comment}} '); - equals(!!ast.body[0].value, true); - equals(!!ast.body[2].value, true); - }); - }); - describe('blocks - parseWithoutProcessing', function() { - it('block mustaches', function() { - var ast = Handlebars.parseWithoutProcessing( - ' {{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} ' - ), - block = ast.body[1]; - - equals(ast.body[0].value, ' '); - - equals(block.program.body[0].value, ' \nfoo\n '); - equals(block.inverse.body[0].value, ' \n bar \n '); - - equals(ast.body[2].value, ' '); - }); - it('initial block mustaches', function() { - var ast = Handlebars.parseWithoutProcessing( - '{{# comment}} \nfoo\n {{/comment}}' - ), - block = ast.body[0]; - - equals(block.program.body[0].value, ' \nfoo\n '); - }); - it('mustaches with children', function() { - var ast = Handlebars.parseWithoutProcessing( - '{{# comment}} \n{{foo}}\n {{/comment}}' - ), - block = ast.body[0]; - - equals(block.program.body[0].value, ' \n'); - equals(block.program.body[1].path.original, 'foo'); - equals(block.program.body[2].value, '\n '); - }); - it('nested block mustaches', function() { - var ast = Handlebars.parseWithoutProcessing( - '{{#foo}} \n{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} \n{{/foo}}' - ), - body = ast.body[0].program.body, - block = body[1]; - - equals(body[0].value, ' \n'); - - equals(block.program.body[0].value, ' \nfoo\n '); - equals(block.inverse.body[0].value, ' \n bar \n '); - }); - it('column 0 block mustaches', function() { - var ast = Handlebars.parseWithoutProcessing( - 'test\n{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} ' - ), - block = ast.body[1]; - - equals(ast.body[0].omit, undefined); - - equals(block.program.body[0].value, ' \nfoo\n '); - equals(block.inverse.body[0].value, ' \n bar \n '); - - equals(ast.body[2].value, ' '); - }); - }); - describe('blocks', function() { - it('marks block mustaches as standalone', function() { - var ast = Handlebars.parse( - ' {{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} ' - ), - block = ast.body[1]; - - equals(ast.body[0].value, ''); - - equals(block.program.body[0].value, 'foo\n'); - equals(block.inverse.body[0].value, ' bar \n'); - - equals(ast.body[2].value, ''); - }); - it('marks initial block mustaches as standalone', function() { - var ast = Handlebars.parse('{{# comment}} \nfoo\n {{/comment}}'), - block = ast.body[0]; - - equals(block.program.body[0].value, 'foo\n'); - }); - it('marks mustaches with children as standalone', function() { - var ast = Handlebars.parse('{{# comment}} \n{{foo}}\n {{/comment}}'), - block = ast.body[0]; - - equals(block.program.body[0].value, ''); - equals(block.program.body[1].path.original, 'foo'); - equals(block.program.body[2].value, '\n'); - }); - it('marks nested block mustaches as standalone', function() { - var ast = Handlebars.parse( - '{{#foo}} \n{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} \n{{/foo}}' - ), - body = ast.body[0].program.body, - block = body[1]; - - equals(body[0].value, ''); - - equals(block.program.body[0].value, 'foo\n'); - equals(block.inverse.body[0].value, ' bar \n'); - - equals(body[0].value, ''); - }); - it('does not mark nested block mustaches as standalone', function() { - var ast = Handlebars.parse( - '{{#foo}} {{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} {{/foo}}' - ), - body = ast.body[0].program.body, - block = body[1]; - - equals(body[0].omit, undefined); - - equals(block.program.body[0].value, ' \nfoo\n'); - equals(block.inverse.body[0].value, ' bar \n '); - - equals(body[0].omit, undefined); - }); - it('does not mark nested initial block mustaches as standalone', function() { - var ast = Handlebars.parse( - '{{#foo}}{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}}{{/foo}}' - ), - body = ast.body[0].program.body, - block = body[0]; - - equals(block.program.body[0].value, ' \nfoo\n'); - equals(block.inverse.body[0].value, ' bar \n '); - - equals(body[0].omit, undefined); - }); - - it('marks column 0 block mustaches as standalone', function() { - var ast = Handlebars.parse( - 'test\n{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} ' - ), - block = ast.body[1]; - - equals(ast.body[0].omit, undefined); - - equals(block.program.body[0].value, 'foo\n'); - equals(block.inverse.body[0].value, ' bar \n'); - - equals(ast.body[2].value, ''); - }); - }); - describe('partials - parseWithoutProcessing', function() { - it('simple partial', function() { - var ast = Handlebars.parseWithoutProcessing('{{> partial }} '); - equals(ast.body[1].value, ' '); - }); - it('indented partial', function() { - var ast = Handlebars.parseWithoutProcessing(' {{> partial }} '); - equals(ast.body[0].value, ' '); - equals(ast.body[1].indent, ''); - equals(ast.body[2].value, ' '); - }); - }); - describe('partials', function() { - it('marks partial as standalone', function() { - var ast = Handlebars.parse('{{> partial }} '); - equals(ast.body[1].value, ''); - }); - it('marks indented partial as standalone', function() { - var ast = Handlebars.parse(' {{> partial }} '); - equals(ast.body[0].value, ''); - equals(ast.body[1].indent, ' '); - equals(ast.body[2].value, ''); - }); - it('marks those around content as not standalone', function() { - var ast = Handlebars.parse('a{{> partial }}'); - equals(ast.body[0].omit, undefined); - - ast = Handlebars.parse('{{> partial }}a'); - equals(ast.body[1].omit, undefined); - }); - }); - describe('comments - parseWithoutProcessing', function() { - it('simple comment', function() { - var ast = Handlebars.parseWithoutProcessing('{{! comment }} '); - equals(ast.body[1].value, ' '); - }); - it('indented comment', function() { - var ast = Handlebars.parseWithoutProcessing(' {{! comment }} '); - equals(ast.body[0].value, ' '); - equals(ast.body[2].value, ' '); - }); - }); - describe('comments', function() { - it('marks comment as standalone', function() { - var ast = Handlebars.parse('{{! comment }} '); - equals(ast.body[1].value, ''); - }); - it('marks indented comment as standalone', function() { - var ast = Handlebars.parse(' {{! comment }} '); - equals(ast.body[0].value, ''); - equals(ast.body[2].value, ''); - }); - it('marks those around content as not standalone', function() { - var ast = Handlebars.parse('a{{! comment }}'); - equals(ast.body[0].omit, undefined); - - ast = Handlebars.parse('{{! comment }}a'); - equals(ast.body[1].omit, undefined); - }); - }); - }); }); diff --git a/spec/env/runtime.js b/spec/env/runtime.js index 99b5bf785..2d5007a2e 100644 --- a/spec/env/runtime.js +++ b/spec/env/runtime.js @@ -22,7 +22,7 @@ vm.runInThisContext( filename ); -var parse = require('../../dist/cjs/handlebars/compiler/base').parse; +var parse = require('@handlebars/parser').parse; var compiler = require('../../dist/cjs/handlebars/compiler/compiler'); var JavaScriptCompiler = require('../../dist/cjs/handlebars/compiler/javascript-compiler'); diff --git a/spec/parser.js b/spec/parser.js deleted file mode 100644 index 2aa7e39ff..000000000 --- a/spec/parser.js +++ /dev/null @@ -1,455 +0,0 @@ -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 with data', function() { - equals(astFor('{{@foo}}'), '{{ @PATH:foo [] }}\n'); - }); - - it('parses simple mustaches with data paths', function() { - equals(astFor('{{@../foo}}'), '{{ @PATH:foo [] }}\n'); - }); - - it('parses mustaches with paths', function() { - equals(astFor('{{foo/bar}}'), '{{ PATH:foo/bar [] }}\n'); - }); - - it('parses mustaches with this/foo', function() { - equals(astFor('{{this/foo}}'), '{{ PATH:foo [] }}\n'); - }); - - it('parses mustaches with - in a path', function() { - equals(astFor('{{foo-bar}}'), '{{ PATH:foo-bar [] }}\n'); - }); - it('parses mustaches with escaped [] in a path', function() { - equals(astFor('{{[foo[\\]]}}'), '{{ PATH:foo[] [] }}\n'); - }); - it('parses escaped \\\\ in path', function() { - equals(astFor('{{[foo\\\\]}}'), '{{ PATH:foo\\ [] }}\n'); - }); - - it('parses mustaches with parameters', function() { - equals(astFor('{{foo bar}}'), '{{ 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 NUMBER parameters', function() { - equals(astFor('{{foo 1}}'), '{{ 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 undefined and null paths', function() { - equals(astFor('{{undefined}}'), '{{ UNDEFINED [] }}\n'); - equals(astFor('{{null}}'), '{{ 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 DATA parameters', function() { - equals(astFor('{{foo @bar}}'), '{{ 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' - ); - - 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' - ); - - equals(astFor("{{foo bat='bam'}}"), '{{ 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' - ); - }); - - it('parses contents followed by a mustache', function() { - equals( - astFor('foo bar {{baz}}'), - "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 with context', function() { - equals(astFor('{{> foo bar}}'), '{{> 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 context and hash', function() { - equals( - astFor('{{> foo bar bat=baz}}'), - '{{> 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('parsers partial blocks', function() { - equals( - astFor('{{#> foo}}bar{{/foo}}'), - "{{> 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('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('parses a comment', function() { - equals( - astFor('{{! this is a comment }}'), - "{{! ' 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 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 (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 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 empty blocks', function() { - equals(astFor('{{#foo}}{{/foo}}'), '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 (else-style) section', function() { - equals( - astFor('{{#foo}}{{else}}{{/foo}}'), - '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 (else-style) section', function() { - equals( - astFor('{{#foo}} bar {{else}}{{/foo}}'), - "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 (else-style) section', function() { - equals( - astFor('{{#foo}}{{else}} bar {{/foo}}'), - "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('throws on old inverse section', function() { - shouldThrow(function() { - astFor('{{else foo}}bar{{/foo}}'); - }, 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 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 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("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/ - ); - - shouldThrow( - function() { - astFor('{{{{goodbyes}}}} {{{{/hellos}}}}'); - }, - 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('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 when the first character is a newline', function() { - shouldThrow( - function() { - astFor('\n\nhello\n\nmy\n\n{{foo}'); - }, - 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('directives', function() { - it('should parse block directives', function() { - equals( - astFor('{{#* foo}}{{/foo}}'), - 'DIRECTIVE BLOCK:\n PATH:foo []\n PROGRAM:\n' - ); - }); - it('should parse directives', function() { - equals(astFor('{{* foo}}'), '{{ DIRECTIVE PATH:foo [] }}\n'); - }); - it('should fail if directives have inverse', function() { - shouldThrow( - function() { - astFor('{{#* foo}}{{^}}{{/foo}}'); - }, - Error, - /Unexpected inverse/ - ); - }); - }); - - it('GH1024 - should track program location properly', function() { - var p = Handlebars.parse( - '\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... - equals( - JSON.stringify(p.loc), - JSON.stringify({ - start: { line: 1, column: 0 }, - end: { line: 7, column: 4 } - }) - ); - equals( - JSON.stringify(p.body[1].program.loc), - JSON.stringify({ - start: { line: 2, column: 13 }, - end: { line: 4, column: 7 } - }) - ); - equals( - JSON.stringify(p.body[1].inverse.loc), - JSON.stringify({ - start: { line: 4, column: 15 }, - end: { line: 6, column: 5 } - }) - ); - }); -}); diff --git a/spec/visitor.js b/spec/visitor.js deleted file mode 100644 index 2bec356fe..000000000 --- a/spec/visitor.js +++ /dev/null @@ -1,164 +0,0 @@ -describe('Visitor', function() { - if (!Handlebars.Visitor || !Handlebars.print) { - return; - } - - it('should provide coverage', function() { - // Simply run the thing and make sure it does not fail and that all of the - // stub methods are executed - var visitor = new Handlebars.Visitor(); - visitor.accept( - Handlebars.parse( - '{{foo}}{{#foo (bar 1 "1" true undefined null) foo=@data}}{{!comment}}{{> bar }} {{/foo}}' - ) - ); - visitor.accept(Handlebars.parse('{{#> bar }} {{/bar}}')); - visitor.accept(Handlebars.parse('{{#* bar }} {{/bar}}')); - visitor.accept(Handlebars.parse('{{* bar }}')); - }); - - it('should traverse to stubs', function() { - var visitor = new Handlebars.Visitor(); - - visitor.StringLiteral = function(string) { - equal(string.value, '2'); - }; - visitor.NumberLiteral = function(number) { - equal(number.value, 1); - }; - visitor.BooleanLiteral = function(bool) { - equal(bool.value, true); - - equal(this.parents.length, 3); - equal(this.parents[0].type, 'SubExpression'); - equal(this.parents[1].type, 'BlockStatement'); - equal(this.parents[2].type, 'Program'); - }; - visitor.PathExpression = function(id) { - equal(/(foo\.)?bar$/.test(id.original), true); - }; - visitor.ContentStatement = function(content) { - equal(content.value, ' '); - }; - visitor.CommentStatement = function(comment) { - equal(comment.value, 'comment'); - }; - - visitor.accept( - Handlebars.parse( - '{{#foo.bar (foo.bar 1 "2" true) foo=@foo.bar}}{{!comment}}{{> bar }} {{/foo.bar}}' - ) - ); - }); - - describe('mutating', function() { - describe('fields', function() { - it('should replace value', function() { - var visitor = new Handlebars.Visitor(); - - visitor.mutating = true; - visitor.StringLiteral = function(string) { - return { type: 'NumberLiteral', value: 42, loc: string.loc }; - }; - - var ast = Handlebars.parse('{{foo foo="foo"}}'); - visitor.accept(ast); - equals( - Handlebars.print(ast), - '{{ PATH:foo [] HASH{foo=NUMBER{42}} }}\n' - ); - }); - it('should treat undefined resonse as identity', function() { - var visitor = new Handlebars.Visitor(); - visitor.mutating = true; - - var ast = Handlebars.parse('{{foo foo=42}}'); - visitor.accept(ast); - equals( - Handlebars.print(ast), - '{{ PATH:foo [] HASH{foo=NUMBER{42}} }}\n' - ); - }); - it('should remove false responses', function() { - var visitor = new Handlebars.Visitor(); - - visitor.mutating = true; - visitor.Hash = function() { - return false; - }; - - var ast = Handlebars.parse('{{foo foo=42}}'); - visitor.accept(ast); - equals(Handlebars.print(ast), '{{ PATH:foo [] }}\n'); - }); - it('should throw when removing required values', function() { - shouldThrow( - function() { - var visitor = new Handlebars.Visitor(); - - visitor.mutating = true; - visitor.PathExpression = function() { - return false; - }; - - var ast = Handlebars.parse('{{foo 42}}'); - visitor.accept(ast); - }, - Handlebars.Exception, - 'MustacheStatement requires path' - ); - }); - it('should throw when returning non-node responses', function() { - shouldThrow( - function() { - var visitor = new Handlebars.Visitor(); - - visitor.mutating = true; - visitor.PathExpression = function() { - return {}; - }; - - var ast = Handlebars.parse('{{foo 42}}'); - visitor.accept(ast); - }, - Handlebars.Exception, - 'Unexpected node type "undefined" found when accepting path on MustacheStatement' - ); - }); - }); - describe('arrays', function() { - it('should replace value', function() { - var visitor = new Handlebars.Visitor(); - - visitor.mutating = true; - visitor.StringLiteral = function(string) { - return { type: 'NumberLiteral', value: 42, loc: string.locInfo }; - }; - - var ast = Handlebars.parse('{{foo "foo"}}'); - visitor.accept(ast); - equals(Handlebars.print(ast), '{{ PATH:foo [NUMBER{42}] }}\n'); - }); - it('should treat undefined resonse as identity', function() { - var visitor = new Handlebars.Visitor(); - visitor.mutating = true; - - var ast = Handlebars.parse('{{foo 42}}'); - visitor.accept(ast); - equals(Handlebars.print(ast), '{{ PATH:foo [NUMBER{42}] }}\n'); - }); - it('should remove false responses', function() { - var visitor = new Handlebars.Visitor(); - - visitor.mutating = true; - visitor.NumberLiteral = function() { - return false; - }; - - var ast = Handlebars.parse('{{foo 42}}'); - visitor.accept(ast); - equals(Handlebars.print(ast), '{{ PATH:foo [] }}\n'); - }); - }); - }); -}); diff --git a/src/handlebars.l b/src/handlebars.l deleted file mode 100644 index fbf208b48..000000000 --- a/src/handlebars.l +++ /dev/null @@ -1,126 +0,0 @@ - -%x mu emu com raw - -%{ - -function strip(start, end) { - return yytext = yytext.substring(start, yyleng - end + start); -} - -%} - -LEFT_STRIP "~" -RIGHT_STRIP "~" - -LOOKAHEAD [=~}\s\/.)|] -LITERAL_LOOKAHEAD [~}\s)] - -/* -ID is the inverse of control characters. -Control characters ranges: - [\s] Whitespace - [!"#%-,\./] !, ", #, %, &, ', (, ), *, +, ,, ., /, Exceptions in range: $, - - [;->@] ;, <, =, >, @, Exceptions in range: :, ? - [\[-\^`] [, \, ], ^, `, Exceptions in range: _ - [\{-~] {, |, }, ~ -*/ -ID [^\s!"#%-,\.\/;->@\[-\^`\{-~]+/{LOOKAHEAD} - -%% - -[^\x00]*?/("{{") { - if(yytext.slice(-2) === "\\\\") { - strip(0,1); - this.begin("mu"); - } else if(yytext.slice(-1) === "\\") { - strip(0,1); - this.begin("emu"); - } else { - this.begin("mu"); - } - if(yytext) return 'CONTENT'; - } - -[^\x00]+ return 'CONTENT'; - -// marks CONTENT up to the next mustache or escaped mustache -[^\x00]{2,}?/("{{"|"\\{{"|"\\\\{{"|<>) { - this.popState(); - return 'CONTENT'; - } - -// nested raw block will create stacked 'raw' condition -"{{{{"/[^/] this.begin('raw'); return 'CONTENT'; -"{{{{/"[^\s!"#%-,\.\/;->@\[-\^`\{-~]+/[=}\s\/.]"}}}}" { - this.popState(); - // Should be using `this.topState()` below, but it currently - // returns the second top instead of the first top. Opened an - // issue about it at https://github.com/zaach/jison/issues/291 - if (this.conditionStack[this.conditionStack.length-1] === 'raw') { - return 'CONTENT'; - } else { - strip(5, 9); - return 'END_RAW_BLOCK'; - } - } -[^\x00]+?/("{{{{") { return 'CONTENT'; } - -[\s\S]*?"--"{RIGHT_STRIP}?"}}" { - this.popState(); - return 'COMMENT'; -} - -"(" return 'OPEN_SEXPR'; -")" return 'CLOSE_SEXPR'; - -"{{{{" { return 'OPEN_RAW_BLOCK'; } -"}}}}" { - this.popState(); - this.begin('raw'); - return 'CLOSE_RAW_BLOCK'; - } -"{{"{LEFT_STRIP}?">" return 'OPEN_PARTIAL'; -"{{"{LEFT_STRIP}?"#>" return 'OPEN_PARTIAL_BLOCK'; -"{{"{LEFT_STRIP}?"#""*"? return 'OPEN_BLOCK'; -"{{"{LEFT_STRIP}?"/" return 'OPEN_ENDBLOCK'; -"{{"{LEFT_STRIP}?"^"\s*{RIGHT_STRIP}?"}}" this.popState(); return 'INVERSE'; -"{{"{LEFT_STRIP}?\s*"else"\s*{RIGHT_STRIP}?"}}" this.popState(); return 'INVERSE'; -"{{"{LEFT_STRIP}?"^" return 'OPEN_INVERSE'; -"{{"{LEFT_STRIP}?\s*"else" return 'OPEN_INVERSE_CHAIN'; -"{{"{LEFT_STRIP}?"{" return 'OPEN_UNESCAPED'; -"{{"{LEFT_STRIP}?"&" return 'OPEN'; -"{{"{LEFT_STRIP}?"!--" { - this.unput(yytext); - this.popState(); - this.begin('com'); -} -"{{"{LEFT_STRIP}?"!"[\s\S]*?"}}" { - this.popState(); - return 'COMMENT'; -} -"{{"{LEFT_STRIP}?"*"? return 'OPEN'; - -"=" return 'EQUALS'; -".." return 'ID'; -"."/{LOOKAHEAD} return 'ID'; -[\/.] return 'SEP'; -\s+ // ignore whitespace -"}"{RIGHT_STRIP}?"}}" this.popState(); return 'CLOSE_UNESCAPED'; -{RIGHT_STRIP}?"}}" this.popState(); return 'CLOSE'; -'"'("\\"["]|[^"])*'"' yytext = strip(1,2).replace(/\\"/g,'"'); return 'STRING'; -"'"("\\"[']|[^'])*"'" yytext = strip(1,2).replace(/\\'/g,"'"); return 'STRING'; -"@" return 'DATA'; -"true"/{LITERAL_LOOKAHEAD} return 'BOOLEAN'; -"false"/{LITERAL_LOOKAHEAD} return 'BOOLEAN'; -"undefined"/{LITERAL_LOOKAHEAD} return 'UNDEFINED'; -"null"/{LITERAL_LOOKAHEAD} return 'NULL'; -\-?[0-9]+(?:\.[0-9]+)?/{LITERAL_LOOKAHEAD} return 'NUMBER'; -"as"\s+"|" return 'OPEN_BLOCK_PARAMS'; -"|" return 'CLOSE_BLOCK_PARAMS'; - -{ID} return 'ID'; - -'['('\\]'|[^\]])*']' yytext = yytext.replace(/\\([\\\]])/g,'$1'); return 'ID'; -. return 'INVALID'; - -<> return 'EOF'; diff --git a/src/handlebars.yy b/src/handlebars.yy deleted file mode 100644 index cab04c61a..000000000 --- a/src/handlebars.yy +++ /dev/null @@ -1,166 +0,0 @@ -%start root - -%ebnf - -%% - -root - : program EOF { return $1; } - ; - -program - : statement* -> yy.prepareProgram($1) - ; - -statement - : mustache -> $1 - | block -> $1 - | rawBlock -> $1 - | partial -> $1 - | partialBlock -> $1 - | content -> $1 - | COMMENT { - $$ = { - type: 'CommentStatement', - value: yy.stripComment($1), - strip: yy.stripFlags($1, $1), - loc: yy.locInfo(@$) - }; - }; - -content - : CONTENT { - $$ = { - type: 'ContentStatement', - original: $1, - value: $1, - loc: yy.locInfo(@$) - }; - }; - -rawBlock - : openRawBlock content* END_RAW_BLOCK -> yy.prepareRawBlock($1, $2, $3, @$) - ; - -openRawBlock - : OPEN_RAW_BLOCK helperName param* hash? CLOSE_RAW_BLOCK -> { path: $2, params: $3, hash: $4 } - ; - -block - : openBlock program inverseChain? closeBlock -> yy.prepareBlock($1, $2, $3, $4, false, @$) - | openInverse program inverseAndProgram? closeBlock -> yy.prepareBlock($1, $2, $3, $4, true, @$) - ; - -openBlock - : OPEN_BLOCK helperName param* hash? blockParams? CLOSE -> { open: $1, path: $2, params: $3, hash: $4, blockParams: $5, strip: yy.stripFlags($1, $6) } - ; - -openInverse - : OPEN_INVERSE helperName param* hash? blockParams? CLOSE -> { path: $2, params: $3, hash: $4, blockParams: $5, strip: yy.stripFlags($1, $6) } - ; - -openInverseChain - : OPEN_INVERSE_CHAIN helperName param* hash? blockParams? CLOSE -> { path: $2, params: $3, hash: $4, blockParams: $5, strip: yy.stripFlags($1, $6) } - ; - -inverseAndProgram - : INVERSE program -> { strip: yy.stripFlags($1, $1), program: $2 } - ; - -inverseChain - : openInverseChain program inverseChain? { - var inverse = yy.prepareBlock($1, $2, $3, $3, false, @$), - program = yy.prepareProgram([inverse], $2.loc); - program.chained = true; - - $$ = { strip: $1.strip, program: program, chain: true }; - } - | inverseAndProgram -> $1 - ; - -closeBlock - : OPEN_ENDBLOCK helperName CLOSE -> {path: $2, strip: yy.stripFlags($1, $3)} - ; - -mustache - // Parsing out the '&' escape token at AST level saves ~500 bytes after min due to the removal of one parser node. - // This also allows for handler unification as all mustache node instances can utilize the same handler - : OPEN helperName param* hash? CLOSE -> yy.prepareMustache($2, $3, $4, $1, yy.stripFlags($1, $5), @$) - | OPEN_UNESCAPED helperName param* hash? CLOSE_UNESCAPED -> yy.prepareMustache($2, $3, $4, $1, yy.stripFlags($1, $5), @$) - ; - -partial - : OPEN_PARTIAL partialName param* hash? CLOSE { - $$ = { - type: 'PartialStatement', - name: $2, - params: $3, - hash: $4, - indent: '', - strip: yy.stripFlags($1, $5), - loc: yy.locInfo(@$) - }; - } - ; -partialBlock - : openPartialBlock program closeBlock -> yy.preparePartialBlock($1, $2, $3, @$) - ; -openPartialBlock - : OPEN_PARTIAL_BLOCK partialName param* hash? CLOSE -> { path: $2, params: $3, hash: $4, strip: yy.stripFlags($1, $5) } - ; - -param - : helperName -> $1 - | sexpr -> $1 - ; - -sexpr - : OPEN_SEXPR helperName param* hash? CLOSE_SEXPR { - $$ = { - type: 'SubExpression', - path: $2, - params: $3, - hash: $4, - loc: yy.locInfo(@$) - }; - }; - -hash - : hashSegment+ -> {type: 'Hash', pairs: $1, loc: yy.locInfo(@$)} - ; - -hashSegment - : ID EQUALS param -> {type: 'HashPair', key: yy.id($1), value: $3, loc: yy.locInfo(@$)} - ; - -blockParams - : OPEN_BLOCK_PARAMS ID+ CLOSE_BLOCK_PARAMS -> yy.id($2) - ; - -helperName - : path -> $1 - | dataName -> $1 - | 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 - : helperName -> $1 - | sexpr -> $1 - ; - -dataName - : DATA pathSegments -> yy.preparePath(true, $2, @$) - ; - -path - : pathSegments -> yy.preparePath(false, $1, @$) - ; - -pathSegments - : pathSegments SEP ID { $1.push({part: yy.id($3), original: $3, separator: $2}); $$ = $1; } - | ID -> [{part: yy.id($1), original: $1}] - ; diff --git a/src/parser-prefix.js b/src/parser-prefix.js deleted file mode 100644 index d9ed04116..000000000 --- a/src/parser-prefix.js +++ /dev/null @@ -1 +0,0 @@ -// File ignored in coverage tests via setting in .istanbul.yml diff --git a/src/parser-suffix.js b/src/parser-suffix.js deleted file mode 100644 index 6e4aa20d6..000000000 --- a/src/parser-suffix.js +++ /dev/null @@ -1 +0,0 @@ -export default handlebars; diff --git a/tasks/parser.js b/tasks/parser.js deleted file mode 100644 index 252b8c267..000000000 --- a/tasks/parser.js +++ /dev/null @@ -1,33 +0,0 @@ -const { execFileWithInheritedOutput } = require('./util/exec-file'); -const { createRegisterAsyncTaskFn } = require('./util/async-grunt-task'); - -const OUTPUT_FILE = 'lib/handlebars/compiler/parser.js'; - -module.exports = function(grunt) { - const registerAsyncTask = createRegisterAsyncTaskFn(grunt); - - registerAsyncTask('parser', async () => { - await runJison(); - combineWithPrefixAndSuffix(); - grunt.log.writeln(`Parser "${OUTPUT_FILE}" created.`); - }); - - async function runJison() { - await execFileWithInheritedOutput('jison', [ - '-m', - 'js', - 'src/handlebars.yy', - 'src/handlebars.l' - ]); - } - - function combineWithPrefixAndSuffix() { - const combinedParserSourceCode = - grunt.file.read('src/parser-prefix.js') + - grunt.file.read('handlebars.js') + - grunt.file.read('src/parser-suffix.js'); - - grunt.file.write(OUTPUT_FILE, combinedParserSourceCode); - grunt.file.delete('handlebars.js'); - } -}; diff --git a/types/index.d.ts b/types/index.d.ts index 3f2f8b792..4275c50f4 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -13,6 +13,12 @@ * https://github.com/DefinitelyTyped/DefinitelyTyped/commits/1ce60bdc07f10e0b076778c6c953271c072bc894/types/handlebars/index.d.ts */ // TypeScript Version: 2.3 +import { + parse, + parseWithoutProcessing, + ParseOptions, + AST +} from '@handlebars/parser'; declare namespace Handlebars { export interface TemplateDelegate { @@ -50,10 +56,7 @@ declare namespace Handlebars { [key: string]: HelperDelegate; } - export interface ParseOptions { - srcName?: string; - ignoreStandalone?: boolean; - } + export { parse, parseWithoutProcessing, ParseOptions }; export function registerHelper(name: string, fn: HelperDelegate): void; export function registerHelper(name: HelperDeclareSpec): void; @@ -71,8 +74,7 @@ declare namespace Handlebars { export function createFrame(object: any): any; export function blockParams(obj: any[], ids: any[]): any[]; export function log(level: number, obj: any): void; - export function parse(input: string, options?: ParseOptions): hbs.AST.Program; - export function parseWithoutProcessing(input: string, options?: ParseOptions): hbs.AST.Program; + export function compile(input: any, options?: CompileOptions): HandlebarsTemplateDelegate; export function precompile(input: any, options?: PrecompileOptions): TemplateSpecification; export function template(precompilation: TemplateSpecification): HandlebarsTemplateDelegate; @@ -127,7 +129,7 @@ declare namespace Handlebars { export const helpers: hbs.AST.helpers; } - interface ICompiler { + export interface ICompiler { accept(node: hbs.AST.Node): void; Program(program: hbs.AST.Program): void; BlockStatement(block: hbs.AST.BlockStatement): void; @@ -191,25 +193,25 @@ declare namespace Handlebars { /** * Implement this interface on your MVW/MVVM/MVC views such as Backbone.View **/ -interface HandlebarsTemplatable { +export interface HandlebarsTemplatable { template: HandlebarsTemplateDelegate; } // NOTE: for backward compatibility of this typing -type HandlebarsTemplateDelegate = Handlebars.TemplateDelegate; +export type HandlebarsTemplateDelegate = Handlebars.TemplateDelegate; -interface HandlebarsTemplates { +export interface HandlebarsTemplates { [index: string]: HandlebarsTemplateDelegate; } -interface TemplateSpecification { +export interface TemplateSpecification { } // for backward compatibility of this typing -type RuntimeOptions = Handlebars.RuntimeOptions; +export type RuntimeOptions = Handlebars.RuntimeOptions; -interface CompileOptions { +export interface CompileOptions { data?: boolean; compat?: boolean; knownHelpers?: KnownHelpers; @@ -222,11 +224,11 @@ interface CompileOptions { explicitPartialContext?: boolean; } -type KnownHelpers = { +export type KnownHelpers = { [name in BuiltinHelperName | CustomHelperName]: boolean; }; -type BuiltinHelperName = +export type BuiltinHelperName = "helperMissing"| "blockHelperMissing"| "each"| @@ -236,21 +238,23 @@ type BuiltinHelperName = "log"| "lookup"; -type CustomHelperName = string; +export type CustomHelperName = string; -interface PrecompileOptions extends CompileOptions { +export interface PrecompileOptions extends CompileOptions { srcName?: string; destName?: string; } -declare namespace hbs { +export namespace hbs { // for backward compatibility of this typing - type SafeString = Handlebars.SafeString; + export type SafeString = Handlebars.SafeString; + + export type Utils = typeof Handlebars.Utils; - type Utils = typeof Handlebars.Utils; + export { AST } } -interface Logger { +export interface Logger { DEBUG: number; INFO: number; WARN: number; @@ -262,161 +266,10 @@ interface Logger { log(level: number, obj: string): void; } -type CompilerInfo = [number/* revision */, string /* versions */]; - -declare namespace hbs { - namespace AST { - interface Node { - type: string; - loc: SourceLocation; - } - - interface SourceLocation { - source: string; - start: Position; - end: Position; - } - - interface Position { - line: number; - column: number; - } - - interface Program extends Node { - body: Statement[]; - blockParams: string[]; - } - - interface Statement extends Node {} - - interface MustacheStatement extends Statement { - type: 'MustacheStatement'; - path: PathExpression | Literal; - params: Expression[]; - hash: Hash; - escaped: boolean; - strip: StripFlags; - } - - interface Decorator extends MustacheStatement { } - - interface BlockStatement extends Statement { - type: 'BlockStatement'; - path: PathExpression; - params: Expression[]; - hash: Hash; - program: Program; - inverse: Program; - openStrip: StripFlags; - inverseStrip: StripFlags; - closeStrip: StripFlags; - } - - interface DecoratorBlock extends BlockStatement { } - - interface PartialStatement extends Statement { - type: 'PartialStatement'; - name: PathExpression | SubExpression; - params: Expression[]; - hash: Hash; - indent: string; - strip: StripFlags; - } - - interface PartialBlockStatement extends Statement { - type: 'PartialBlockStatement'; - name: PathExpression | SubExpression; - params: Expression[]; - hash: Hash; - program: Program; - openStrip: StripFlags; - closeStrip: StripFlags; - } - - interface ContentStatement extends Statement { - type: 'ContentStatement'; - value: string; - original: StripFlags; - } - - interface CommentStatement extends Statement { - type: 'CommentStatement'; - value: string; - strip: StripFlags; - } - - interface Expression extends Node {} - - interface SubExpression extends Expression { - type: 'SubExpression'; - path: PathExpression; - params: Expression[]; - hash: Hash; - } - - interface PathExpression extends Expression { - type: 'PathExpression'; - data: boolean; - depth: number; - parts: string[]; - original: string; - } - - interface Literal extends Expression {} - interface StringLiteral extends Literal { - type: 'StringLiteral'; - value: string; - original: string; - } - - interface BooleanLiteral extends Literal { - type: 'BooleanLiteral'; - value: boolean; - original: boolean; - } - - interface NumberLiteral extends Literal { - type: 'NumberLiteral'; - value: number; - original: number; - } - - interface UndefinedLiteral extends Literal { - type: 'UndefinedLiteral'; - } - - interface NullLiteral extends Literal { - type: 'NullLiteral'; - } - - interface Hash extends Node { - type: 'Hash'; - pairs: HashPair[]; - } - - interface HashPair extends Node { - type: 'HashPair'; - key: string; - value: Expression; - } - - interface StripFlags { - open: boolean; - close: boolean; - } - - interface helpers { - helperExpression(node: Node): boolean; - scopeId(path: PathExpression): boolean; - simpleId(path: PathExpression): boolean; - } - } -} - -declare module "handlebars" { - export = Handlebars; -} +export type CompilerInfo = [number/* revision */, string /* versions */]; declare module "handlebars/runtime" { export = Handlebars; } + +export default Handlebars;