From 19bdace85a8d0bc5ed3a4dec4071cb08c8d003f2 Mon Sep 17 00:00:00 2001 From: Chris Garrett Date: Sun, 14 Feb 2021 23:56:25 -0800 Subject: [PATCH] [FEAT] Extract the Handlebars parser (#1713) Extracts the parser to `@handlebars/parser`, where it can be shared between different implementations. This means that e.g. Glimmer/Ember will be able to iterate on new features without forcing Handlebars to adopt them immediately, and vice versa. All implementors will be able to absorb changes as it makes sense for them. --- .eslintignore | 1 - .gitignore | 3 +- .prettierignore | 1 - Gruntfile.js | 9 +- lib/handlebars.js | 13 +- lib/handlebars.runtime.js | 2 +- lib/handlebars/base.js | 2 +- lib/handlebars/compiler/base.js | 34 -- lib/handlebars/compiler/compiler.js | 2 +- lib/handlebars/compiler/helpers.js | 219 --------- .../compiler/javascript-compiler.js | 2 +- lib/handlebars/compiler/printer.js | 178 ------- lib/handlebars/compiler/visitor.js | 136 ------ lib/handlebars/compiler/whitespace-control.js | 234 --------- lib/handlebars/exception.js | 68 --- lib/handlebars/helpers/each.js | 2 +- lib/handlebars/helpers/helper-missing.js | 2 +- lib/handlebars/helpers/if.js | 2 +- lib/handlebars/helpers/with.js | 2 +- lib/handlebars/runtime.js | 2 +- lib/index.js | 6 +- nyc.config.js | 2 +- package-lock.json | 5 + package.json | 1 + spec/ast.js | 247 ---------- spec/env/runtime.js | 2 +- spec/parser.js | 455 ------------------ spec/visitor.js | 164 ------- src/handlebars.l | 126 ----- src/handlebars.yy | 166 ------- src/parser-prefix.js | 1 - src/parser-suffix.js | 1 - tasks/parser.js | 33 -- types/index.d.ts | 203 ++------ 34 files changed, 58 insertions(+), 2268 deletions(-) delete mode 100644 lib/handlebars/compiler/base.js delete mode 100644 lib/handlebars/compiler/helpers.js delete mode 100644 lib/handlebars/compiler/printer.js delete mode 100644 lib/handlebars/compiler/visitor.js delete mode 100644 lib/handlebars/compiler/whitespace-control.js delete mode 100644 lib/handlebars/exception.js delete mode 100644 spec/parser.js delete mode 100644 spec/visitor.js delete mode 100644 src/handlebars.l delete mode 100644 src/handlebars.yy delete mode 100644 src/parser-prefix.js delete mode 100644 src/parser-suffix.js delete mode 100644 tasks/parser.js 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;