diff options
-rw-r--r-- | lib/handlebars/base.js | 3 | ||||
-rw-r--r-- | lib/handlebars/compiler/javascript-compiler.js | 6 | ||||
-rw-r--r-- | lib/handlebars/runtime.js | 1 | ||||
-rw-r--r-- | lib/handlebars/utils.js | 4 | ||||
-rw-r--r-- | lib/index.js | 1 | ||||
-rw-r--r-- | spec/blocks.js | 13 | ||||
-rw-r--r-- | spec/compiler.js | 70 | ||||
-rw-r--r-- | spec/env/common.js | 2 | ||||
-rw-r--r-- | spec/helpers.js | 43 | ||||
-rw-r--r-- | spec/parser.js | 2 | ||||
-rw-r--r-- | spec/partials.js | 3 | ||||
-rw-r--r-- | spec/precompiler.js | 45 | ||||
-rw-r--r-- | spec/regressions.js | 16 | ||||
-rw-r--r-- | spec/utils.js | 16 | ||||
-rw-r--r-- | src/handlebars.l | 5 |
15 files changed, 186 insertions, 44 deletions
diff --git a/lib/handlebars/base.js b/lib/handlebars/base.js index 022417b..4eac7e9 100644 --- a/lib/handlebars/base.js +++ b/lib/handlebars/base.js @@ -67,7 +67,8 @@ function registerDefaultHelpers(instance) { }); instance.registerHelper('blockHelperMissing', function(context, options) { - var inverse = options.inverse || function() {}, fn = options.fn; + var inverse = options.inverse, + fn = options.fn; if (isFunction(context)) { context = context.call(this); } diff --git a/lib/handlebars/compiler/javascript-compiler.js b/lib/handlebars/compiler/javascript-compiler.js index ef02cc7..d52f2a0 100644 --- a/lib/handlebars/compiler/javascript-compiler.js +++ b/lib/handlebars/compiler/javascript-compiler.js @@ -99,6 +99,7 @@ JavaScriptCompiler.prototype = { // Flush any trailing content that might be pending. this.pushSource(''); + /* istanbul ignore next */ if (this.stackSlot || this.inlineStack.length || this.compileStack.length) { throw new Exception('Compile completed with content left on stack'); } @@ -551,10 +552,6 @@ JavaScriptCompiler.prototype = { var helper = this.setupHelper(paramSize, name); var lookup = (isSimple ? helper.name + ' || ' : '') + nonHelper + ' || helperMissing'; - if (helper.paramsInit) { - lookup += ',' + helper.paramsInit; - } - this.push('((' + lookup + ').call(' + helper.callParams + '))'); // Always flush subexpressions. This is both to prevent the compounding size issue that @@ -849,6 +846,7 @@ JavaScriptCompiler.prototype = { return item.value; } else { if (!inline) { + /* istanbul ignore next */ if (!this.stackSlot) { throw new Exception('Invalid stack pop'); } diff --git a/lib/handlebars/runtime.js b/lib/handlebars/runtime.js index bfdb3b4..d351918 100644 --- a/lib/handlebars/runtime.js +++ b/lib/handlebars/runtime.js @@ -23,6 +23,7 @@ export function checkRevision(compilerInfo) { // TODO: Remove this line and break up compilePartial export function template(templateSpec, env) { + /* istanbul ignore next */ if (!env) { throw new Exception("No environment passed to template"); } diff --git a/lib/handlebars/utils.js b/lib/handlebars/utils.js index f2f1a54..087183e 100644 --- a/lib/handlebars/utils.js +++ b/lib/handlebars/utils.js @@ -14,7 +14,7 @@ var badChars = /[&<>"'`]/g; var possible = /[&<>"'`]/; function escapeChar(chr) { - return escape[chr] || "&"; + return escape[chr]; } export function extend(obj /* , ...source */) { @@ -37,6 +37,7 @@ var isFunction = function(value) { return typeof value === 'function'; }; // fallback for older versions of Chrome and Safari +/* istanbul ignore next */ if (isFunction(/x/)) { isFunction = function(value) { return typeof value === 'function' && toString.call(value) === '[object Function]'; @@ -44,6 +45,7 @@ if (isFunction(/x/)) { } export var isFunction; +/* istanbul ignore next */ export var isArray = Array.isArray || function(value) { return (value && typeof value === 'object') ? toString.call(value) === '[object Array]' : false; }; diff --git a/lib/index.js b/lib/index.js index e150524..790aab7 100644 --- a/lib/index.js +++ b/lib/index.js @@ -14,6 +14,7 @@ handlebars.print = printer.print; module.exports = handlebars; // Publish a Node.js require() handler for .handlebars and .hbs files +/* istanbul ignore else */ if (typeof require !== 'undefined' && require.extensions) { var extension = function(module, filename) { var fs = require("fs"); diff --git a/spec/blocks.js b/spec/blocks.js index a13cce2..061947a 100644 --- a/spec/blocks.js +++ b/spec/blocks.js @@ -39,6 +39,13 @@ describe('blocks', function() { "Templates can access variables in contexts up the stack with relative path syntax"); }); + it('multiple blocks with complex lookup', function() { + var string = '{{#goodbyes}}{{../name}}{{../name}}{{/goodbyes}}'; + var hash = {name: 'Alan', goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}]}; + + shouldCompileTo(string, hash, 'AlanAlanAlanAlanAlanAlan'); + }); + it("block with complex lookup using nested context", function() { var string = "{{#goodbyes}}{{text}} cruel {{foo/../name}}! {{/goodbyes}}"; @@ -48,10 +55,10 @@ describe('blocks', function() { }); it("block with deep nested complex lookup", function() { - var string = "{{#outer}}Goodbye {{#inner}}cruel {{../../omg}}{{/inner}}{{/outer}}"; - var hash = {omg: "OMG!", outer: [{ inner: [{ text: "goodbye" }] }] }; + var string = "{{#outer}}Goodbye {{#inner}}cruel {{../sibling}} {{../../omg}}{{/inner}}{{/outer}}"; + var hash = {omg: "OMG!", outer: [{ sibling: 'sad', inner: [{ text: "goodbye" }] }] }; - shouldCompileTo(string, hash, "Goodbye cruel OMG!"); + shouldCompileTo(string, hash, "Goodbye cruel sad OMG!"); }); describe('inverted sections', function() { diff --git a/spec/compiler.js b/spec/compiler.js new file mode 100644 index 0000000..fa7635e --- /dev/null +++ b/spec/compiler.js @@ -0,0 +1,70 @@ +/*global Handlebars, shouldThrow */ + +describe('compiler', function() { + if (!Handlebars.compile) { + return; + } + + describe('#equals', function() { + function compile(string) { + var ast = Handlebars.parse(string); + return new Handlebars.Compiler().compile(ast, {}); + } + + it('should treat as equal', function() { + equal(compile('foo').equals(compile('foo')), true); + equal(compile('{{foo}}').equals(compile('{{foo}}')), true); + equal(compile('{{foo.bar}}').equals(compile('{{foo.bar}}')), true); + equal(compile('{{foo.bar baz "foo" true false bat=1}}').equals(compile('{{foo.bar baz "foo" true false bat=1}}')), true); + equal(compile('{{foo.bar (baz bat=1)}}').equals(compile('{{foo.bar (baz bat=1)}}')), true); + equal(compile('{{#foo}} {{/foo}}').equals(compile('{{#foo}} {{/foo}}')), true); + }); + it('should treat as not equal', function() { + equal(compile('foo').equals(compile('bar')), false); + equal(compile('{{foo}}').equals(compile('{{bar}}')), false); + equal(compile('{{foo.bar}}').equals(compile('{{bar.bar}}')), false); + equal(compile('{{foo.bar baz bat=1}}').equals(compile('{{foo.bar bar bat=1}}')), false); + equal(compile('{{foo.bar (baz bat=1)}}').equals(compile('{{foo.bar (bar bat=1)}}')), false); + equal(compile('{{#foo}} {{/foo}}').equals(compile('{{#bar}} {{/bar}}')), false); + equal(compile('{{#foo}} {{/foo}}').equals(compile('{{#foo}} {{foo}}{{/foo}}')), false); + }); + }); + + describe('#compile', function() { + it('should fail with invalid input', function() { + shouldThrow(function() { + Handlebars.compile(null); + }, Error, 'You must pass a string or Handlebars AST to Handlebars.compile. You passed null'); + shouldThrow(function() { + Handlebars.compile({}); + }, Error, 'You must pass a string or Handlebars AST to Handlebars.compile. You passed [object Object]'); + }); + + it('can utilize AST instance', function() { + equal(Handlebars.compile(new Handlebars.AST.ProgramNode(true, [ new Handlebars.AST.ContentNode("Hello")]))(), 'Hello'); + }); + + it("can pass through an empty string", function() { + equal(Handlebars.compile('')(), ''); + }); + }); + + describe('#precompile', function() { + it('should fail with invalid input', function() { + shouldThrow(function() { + Handlebars.precompile(null); + }, Error, 'You must pass a string or Handlebars AST to Handlebars.precompile. You passed null'); + shouldThrow(function() { + Handlebars.precompile({}); + }, Error, 'You must pass a string or Handlebars AST to Handlebars.precompile. You passed [object Object]'); + }); + + it('can utilize AST instance', function() { + equal(/return "Hello"/.test(Handlebars.precompile(new Handlebars.AST.ProgramNode(true, [ new Handlebars.AST.ContentNode("Hello")]))), true); + }); + + it("can pass through an empty string", function() { + equal(/return ""/.test(Handlebars.precompile('')), true); + }); + }); +}); diff --git a/spec/env/common.js b/spec/env/common.js index a603fc0..e551837 100644 --- a/spec/env/common.js +++ b/spec/env/common.js @@ -44,7 +44,7 @@ global.shouldThrow = function(callback, type, msg) { throw new Error('Type failure'); } if (msg && !(msg.test ? msg.test(err.message) : msg === err.message)) { - throw new Error('Message failure'); + equal(msg, err.message); } } if (failed) { diff --git a/spec/helpers.js b/spec/helpers.js index d686b64..e3b5863 100644 --- a/spec/helpers.js +++ b/spec/helpers.js @@ -208,20 +208,41 @@ describe('helpers', function() { }); }); - it("Multiple global helper registration", function() { - var helpers = handlebarsEnv.helpers; - handlebarsEnv.helpers = {}; + describe('registration', function() { + it('unregisters', function() { + var helpers = handlebarsEnv.helpers; + handlebarsEnv.helpers = {}; - handlebarsEnv.registerHelper({ - 'if': helpers['if'], - world: function() { return "world!"; }, - test_helper: function() { return 'found it!'; } + handlebarsEnv.registerHelper('foo', function() { + return 'fail'; + }); + handlebarsEnv.unregisterHelper('foo'); + equals(handlebarsEnv.helpers.foo, undefined); }); - shouldCompileTo( - "{{test_helper}} {{#if cruel}}Goodbye {{cruel}} {{world}}!{{/if}}", - [{cruel: "cruel"}], - "found it! Goodbye cruel world!!"); + it('allows multiple globals', function() { + var helpers = handlebarsEnv.helpers; + handlebarsEnv.helpers = {}; + + handlebarsEnv.registerHelper({ + 'if': helpers['if'], + world: function() { return "world!"; }, + test_helper: function() { return 'found it!'; } + }); + + shouldCompileTo( + "{{test_helper}} {{#if cruel}}Goodbye {{cruel}} {{world}}!{{/if}}", + [{cruel: "cruel"}], + "found it! Goodbye cruel world!!"); + }); + it('fails with multiple and args', function() { + shouldThrow(function() { + handlebarsEnv.registerHelper({ + world: function() { return "world!"; }, + test_helper: function() { return 'found it!'; } + }, {}); + }, Error, 'Arg not supported with multiple helpers'); + }); }); it("decimal number literals work", function() { diff --git a/spec/parser.js b/spec/parser.js index ff12cc4..076ce36 100644 --- a/spec/parser.js +++ b/spec/parser.js @@ -1,4 +1,4 @@ -/*global Handlebars */ +/*global Handlebars, shouldThrow */ describe('parser', function() { if (!Handlebars.print) { return; diff --git a/spec/partials.js b/spec/partials.js index 208d038..cc464b9 100644 --- a/spec/partials.js +++ b/spec/partials.js @@ -91,6 +91,9 @@ describe('partials', function() { var dude = "{{name}}"; var hash = {name:"Jeepers", another_dude:"Creepers"}; shouldCompileToWithPartials(string, [hash, {}, {'shared/dude':dude}], true, "Dudes: Jeepers Creepers", "Partials can use globals or passed"); + + handlebarsEnv.unregisterPartial('global_test'); + equals(handlebarsEnv.partials.global_test, undefined); }); it("Multiple partial registration", function() { diff --git a/spec/precompiler.js b/spec/precompiler.js index 7cd1ffd..57fc280 100644 --- a/spec/precompiler.js +++ b/spec/precompiler.js @@ -10,9 +10,12 @@ describe('precompiler', function() { Precompiler = require('../lib/precompiler'); var log, - logFunction; + logFunction, + + precompile; beforeEach(function() { + precompile = Handlebars.precompile; logFunction = console.log; log = ''; console.log = function() { @@ -20,6 +23,7 @@ describe('precompiler', function() { }; }); afterEach(function() { + Handlebars.precompile = precompile; console.log = logFunction; }); @@ -37,4 +41,43 @@ describe('precompiler', function() { Precompiler.cli({templates: ['foo']}); }, Handlebars.Exception, 'Unable to open template file "foo"'); }); + it('should throw when combining simple and minimized', function() { + shouldThrow(function() { + Precompiler.cli({templates: [__dirname], simple: true, min: true}); + }, Handlebars.Exception, 'Unable to minimze simple output'); + }); + it('should throw when combining simple and multiple templates', function() { + shouldThrow(function() { + Precompiler.cli({templates: [__dirname + '/artifacts/empty.handlebars', __dirname + '/artifacts/empty.handlebars'], simple: true}); + }, Handlebars.Exception, 'Unable to output multiple templates in simple mode'); + }); + it('should throw when combining simple and directories', function() { + shouldThrow(function() { + Precompiler.cli({templates: [__dirname], simple: true}); + }, Handlebars.Exception, 'Unable to output multiple templates in simple mode'); + }); + it('should enumerate directories by extension', function() { + Precompiler.cli({templates: [__dirname + '/artifacts'], extension: 'hbs'}); + equal(/'example_2'/.test(log), true); + log = ''; + + Precompiler.cli({templates: [__dirname + '/artifacts'], extension: 'handlebars'}); + equal(/'empty'/.test(log), true); + equal(/'example_1'/.test(log), true); + }); + it('should output simple templates', function() { + Handlebars.precompile = function() { return 'simple'; }; + Precompiler.cli({templates: [__dirname + '/artifacts/empty.handlebars'], simple: true, extension: 'handlebars'}); + equal(log, 'simple\n'); + }); + it('should output amd templates', function() { + Handlebars.precompile = function() { return 'amd'; }; + Precompiler.cli({templates: [__dirname + '/artifacts/empty.handlebars'], amd: true, extension: 'handlebars'}); + equal(/template\(amd\)/.test(log), true); + }); + it('should output commonjs templates', function() { + Handlebars.precompile = function() { return 'commonjs'; }; + Precompiler.cli({templates: [__dirname + '/artifacts/empty.handlebars'], commonjs: true, extension: 'handlebars'}); + equal(/template\(commonjs\)/.test(log), true); + }); }); diff --git a/spec/regressions.js b/spec/regressions.js index dd4eedd..d4598cc 100644 --- a/spec/regressions.js +++ b/spec/regressions.js @@ -130,12 +130,6 @@ describe('Regressions', function() { shouldCompileTo(string, data, "Hello Chris. You have just won $10000! Well, $6000, after taxes.", "the hello world mustache example works"); }); - it("Passing falsy values to Handlebars.compile throws an error", function() { - shouldThrow(function() { - CompilerContext.compile(null); - }, Error, 'You must pass a string or Handlebars AST to Handlebars.precompile. You passed null'); - }); - it('GH-731: zero context rendering', function() { shouldCompileTo('{{#foo}} This is {{bar}} ~ {{/foo}}', {foo: 0, bar: 'OK'}, ' This is ~ '); }); @@ -143,14 +137,4 @@ describe('Regressions', function() { it('GH-820: zero pathed rendering', function() { shouldCompileTo('{{foo.bar}}', {foo: 0}, ''); }); - - if (Handlebars.AST) { - it("can pass through an already-compiled AST via compile/precompile", function() { - equal(Handlebars.compile(new Handlebars.AST.ProgramNode(true, [ new Handlebars.AST.ContentNode("Hello")]))(), 'Hello'); - }); - - it("can pass through an empty string", function() { - equal(Handlebars.compile('')(), ''); - }); - } }); diff --git a/spec/utils.js b/spec/utils.js index 390ad05..ea7d782 100644 --- a/spec/utils.js +++ b/spec/utils.js @@ -56,4 +56,20 @@ describe('utils', function() { equals(Handlebars.Utils.isEmpty({bar: 1}), false); }); }); + + describe('#extend', function() { + it('should ignore prototype values', function() { + function A() { + this.a = 1; + } + A.prototype.b = 4; + + var b = {b: 2}; + + Handlebars.Utils.extend(b, new A()); + + equals(b.a, 1); + equals(b.b, 2); + }); + }); }); diff --git a/src/handlebars.l b/src/handlebars.l index 0a531d5..57ba12a 100644 --- a/src/handlebars.l +++ b/src/handlebars.l @@ -67,11 +67,6 @@ ID [^\s!"#%-,\.\/;->@\[-\^`\{-~]+/{LOOKAHEAD} this.begin('raw'); return 'CLOSE_RAW_BLOCK'; } -<mu>"{{{{"[^\x00]*"}}}}" { - yytext = yytext.substr(4, yyleng-8); - this.popState(); - return 'RAW_BLOCK'; - } <mu>"{{"{LEFT_STRIP}?">" return 'OPEN_PARTIAL'; <mu>"{{"{LEFT_STRIP}?"#" return 'OPEN_BLOCK'; <mu>"{{"{LEFT_STRIP}?"/" return 'OPEN_ENDBLOCK'; |