From 75a4f0d9317709e00f5c84b6b9d8cc4241f26d84 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Fri, 15 Feb 2013 20:22:11 -0600 Subject: [PATCH 01/17] Negative number literal support Fixes #422 --- dist/handlebars.js | 2 +- spec/qunit_spec.js | 10 ++++++++++ spec/tokenizer_spec.rb | 4 ++++ src/handlebars.l | 2 +- 4 files changed, 16 insertions(+), 2 deletions(-) diff --git a/dist/handlebars.js b/dist/handlebars.js index 26d920778..8f3433af7 100644 --- a/dist/handlebars.js +++ b/dist/handlebars.js @@ -635,7 +635,7 @@ case 32: return 5; break; } }; -lexer.rules = [/^(?:[^\x00]*?(?=(\{\{)))/,/^(?:[^\x00]+)/,/^(?:[^\x00]{2,}?(?=(\{\{|$)))/,/^(?:[\s\S]*?--\}\})/,/^(?:\{\{>)/,/^(?:\{\{#)/,/^(?:\{\{\/)/,/^(?:\{\{\^)/,/^(?:\{\{\s*else\b)/,/^(?:\{\{\{)/,/^(?:\{\{&)/,/^(?:\{\{!--)/,/^(?:\{\{![\s\S]*?\}\})/,/^(?:\{\{)/,/^(?:=)/,/^(?:\.(?=[} ]))/,/^(?:\.\.)/,/^(?:[\/.])/,/^(?:\s+)/,/^(?:\}\}\})/,/^(?:\}\})/,/^(?:"(\\["]|[^"])*")/,/^(?:'(\\[']|[^'])*')/,/^(?:@[a-zA-Z]+)/,/^(?:true(?=[}\s]))/,/^(?:false(?=[}\s]))/,/^(?:[0-9]+(?=[}\s]))/,/^(?:[a-zA-Z0-9_$-]+(?=[=}\s\/.]))/,/^(?:\[[^\]]*\])/,/^(?:.)/,/^(?:\s+)/,/^(?:[a-zA-Z0-9_$-/]+)/,/^(?:$)/]; +lexer.rules = [/^(?:[^\x00]*?(?=(\{\{)))/,/^(?:[^\x00]+)/,/^(?:[^\x00]{2,}?(?=(\{\{|$)))/,/^(?:[\s\S]*?--\}\})/,/^(?:\{\{>)/,/^(?:\{\{#)/,/^(?:\{\{\/)/,/^(?:\{\{\^)/,/^(?:\{\{\s*else\b)/,/^(?:\{\{\{)/,/^(?:\{\{&)/,/^(?:\{\{!--)/,/^(?:\{\{![\s\S]*?\}\})/,/^(?:\{\{)/,/^(?:=)/,/^(?:\.(?=[} ]))/,/^(?:\.\.)/,/^(?:[\/.])/,/^(?:\s+)/,/^(?:\}\}\})/,/^(?:\}\})/,/^(?:"(\\["]|[^"])*")/,/^(?:'(\\[']|[^'])*')/,/^(?:@[a-zA-Z]+)/,/^(?:true(?=[}\s]))/,/^(?:false(?=[}\s]))/,/^(?:-?[0-9]+(?=[}\s]))/,/^(?:[a-zA-Z0-9_$-]+(?=[=}\s\/.]))/,/^(?:\[[^\]]*\])/,/^(?:.)/,/^(?:\s+)/,/^(?:[a-zA-Z0-9_$-/]+)/,/^(?:$)/]; lexer.conditions = {"mu":{"rules":[4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,32],"inclusive":false},"emu":{"rules":[2],"inclusive":false},"com":{"rules":[3],"inclusive":false},"par":{"rules":[30,31],"inclusive":false},"INITIAL":{"rules":[0,1,32],"inclusive":true}}; return lexer;})() parser.lexer = lexer; diff --git a/spec/qunit_spec.js b/spec/qunit_spec.js index fa15c3c05..5953bcacf 100644 --- a/spec/qunit_spec.js +++ b/spec/qunit_spec.js @@ -571,6 +571,16 @@ test("simple literals work", function() { }}; shouldCompileTo(string, [hash, helpers], "Message: Hello world 12 times: true false", "template with a simple String literal"); }); +test("negative number literals work", function() { + var string = 'Message: {{hello -12}}'; + var hash = {}; + var helpers = {hello: function(times) { + if(typeof times !== 'number') { times = "NaN"; } + return "Hello " + times + " times"; + }}; + shouldCompileTo(string, [hash, helpers], "Message: Hello -12 times", "template with a negative integer literal"); +}); + test("using a quote in the middle of a parameter raises an error", function() { shouldThrow(function() { diff --git a/spec/tokenizer_spec.rb b/spec/tokenizer_spec.rb index cb7f6eb09..9e189280a 100644 --- a/spec/tokenizer_spec.rb +++ b/spec/tokenizer_spec.rb @@ -235,6 +235,10 @@ def tokenize(string) result = tokenize(%|{{ foo 1 }}|) result.should match_tokens(%w(OPEN ID INTEGER CLOSE)) result[2].should be_token("INTEGER", "1") + + result = tokenize(%|{{ foo -1 }}|) + result.should match_tokens(%w(OPEN ID INTEGER CLOSE)) + result[2].should be_token("INTEGER", "-1") end it "tokenizes booleans" do diff --git a/src/handlebars.l b/src/handlebars.l index 04c7c4c2b..b32e39c64 100644 --- a/src/handlebars.l +++ b/src/handlebars.l @@ -42,7 +42,7 @@ "@"[a-zA-Z]+ { yytext = yytext.substr(1); return 'DATA'; } "true"/[}\s] { return 'BOOLEAN'; } "false"/[}\s] { return 'BOOLEAN'; } -[0-9]+/[}\s] { return 'INTEGER'; } +\-?[0-9]+/[}\s] { return 'INTEGER'; } [a-zA-Z0-9_$-]+/[=}\s\/.] { return 'ID'; } '['[^\]]*']' { yytext = yytext.substr(1, yyleng-2); return 'ID'; } . { return 'INVALID'; } From 261a2deb51ce5dffdbe374fa2a231c9bea108025 Mon Sep 17 00:00:00 2001 From: Ryunosuke SATO Date: Sat, 16 Feb 2013 20:14:38 +0900 Subject: [PATCH 02/17] Fix method name in error message - compile -> precompile --- lib/handlebars/compiler/compiler.js | 2 +- spec/qunit_spec.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/handlebars/compiler/compiler.js b/lib/handlebars/compiler/compiler.js index 84008043c..0260a4124 100644 --- a/lib/handlebars/compiler/compiler.js +++ b/lib/handlebars/compiler/compiler.js @@ -1229,7 +1229,7 @@ Handlebars.JavaScriptCompiler = function() {}; Handlebars.precompile = function(input, options) { if (!input || (typeof input !== 'string' && input.constructor !== Handlebars.AST.ProgramNode)) { - throw new Handlebars.Exception("You must pass a string or Handlebars AST to Handlebars.compile. You passed " + input); + throw new Handlebars.Exception("You must pass a string or Handlebars AST to Handlebars.precompile. You passed " + input); } options = options || {}; diff --git a/spec/qunit_spec.js b/spec/qunit_spec.js index 5953bcacf..db7f956f7 100644 --- a/spec/qunit_spec.js +++ b/spec/qunit_spec.js @@ -1364,7 +1364,7 @@ test("bug reported by @fat where lambdas weren't being properly resolved", funct test("Passing falsy values to Handlebars.compile throws an error", function() { shouldThrow(function() { CompilerContext.compile(null); - }, "You must pass a string or Handlebars AST to Handlebars.compile. You passed null"); + }, "You must pass a string or Handlebars AST to Handlebars.precompile. You passed null"); }); test('GH-408: Multiple loops fail', function() { From 8a05dcb70e27705c954ad586b314dea73a283452 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sat, 16 Feb 2013 10:47:04 -0600 Subject: [PATCH 03/17] Remove unused scope function --- lib/handlebars/compiler/ast.js | 199 ++++++++++++++++----------------- 1 file changed, 98 insertions(+), 101 deletions(-) diff --git a/lib/handlebars/compiler/ast.js b/lib/handlebars/compiler/ast.js index 1ba1fcf73..850c60554 100644 --- a/lib/handlebars/compiler/ast.js +++ b/lib/handlebars/compiler/ast.js @@ -1,134 +1,131 @@ exports.attach = function(Handlebars) { // BEGIN(BROWSER) -(function() { +Handlebars.AST = {}; - Handlebars.AST = {}; - - Handlebars.AST.ProgramNode = function(statements, inverse) { - this.type = "program"; - this.statements = statements; - if(inverse) { this.inverse = new Handlebars.AST.ProgramNode(inverse); } - }; +Handlebars.AST.ProgramNode = function(statements, inverse) { + this.type = "program"; + this.statements = statements; + if(inverse) { this.inverse = new Handlebars.AST.ProgramNode(inverse); } +}; - Handlebars.AST.MustacheNode = function(rawParams, hash, unescaped) { - this.type = "mustache"; - this.escaped = !unescaped; - this.hash = hash; +Handlebars.AST.MustacheNode = function(rawParams, hash, unescaped) { + this.type = "mustache"; + this.escaped = !unescaped; + this.hash = hash; - var id = this.id = rawParams[0]; - var params = this.params = rawParams.slice(1); + var id = this.id = rawParams[0]; + var params = this.params = rawParams.slice(1); - // a mustache is an eligible helper if: - // * its id is simple (a single part, not `this` or `..`) - var eligibleHelper = this.eligibleHelper = id.isSimple; + // a mustache is an eligible helper if: + // * its id is simple (a single part, not `this` or `..`) + var eligibleHelper = this.eligibleHelper = id.isSimple; - // a mustache is definitely a helper if: - // * it is an eligible helper, and - // * it has at least one parameter or hash segment - this.isHelper = eligibleHelper && (params.length || hash); + // a mustache is definitely a helper if: + // * it is an eligible helper, and + // * it has at least one parameter or hash segment + this.isHelper = eligibleHelper && (params.length || hash); - // if a mustache is an eligible helper but not a definite - // helper, it is ambiguous, and will be resolved in a later - // pass or at runtime. - }; + // if a mustache is an eligible helper but not a definite + // helper, it is ambiguous, and will be resolved in a later + // pass or at runtime. +}; - Handlebars.AST.PartialNode = function(partialName, context) { - this.type = "partial"; - this.partialName = partialName; - this.context = context; - }; +Handlebars.AST.PartialNode = function(partialName, context) { + this.type = "partial"; + this.partialName = partialName; + this.context = context; +}; - Handlebars.AST.BlockNode = function(mustache, program, inverse, close) { - var verifyMatch = function(open, close) { - if(open.original !== close.original) { - throw new Handlebars.Exception(open.original + " doesn't match " + close.original); - } - }; - - verifyMatch(mustache.id, close); - this.type = "block"; - this.mustache = mustache; - this.program = program; - this.inverse = inverse; - - if (this.inverse && !this.program) { - this.isInverse = true; +Handlebars.AST.BlockNode = function(mustache, program, inverse, close) { + var verifyMatch = function(open, close) { + if(open.original !== close.original) { + throw new Handlebars.Exception(open.original + " doesn't match " + close.original); } }; - Handlebars.AST.ContentNode = function(string) { - this.type = "content"; - this.string = string; - }; + verifyMatch(mustache.id, close); + this.type = "block"; + this.mustache = mustache; + this.program = program; + this.inverse = inverse; - Handlebars.AST.HashNode = function(pairs) { - this.type = "hash"; - this.pairs = pairs; - }; + if (this.inverse && !this.program) { + this.isInverse = true; + } +}; + +Handlebars.AST.ContentNode = function(string) { + this.type = "content"; + this.string = string; +}; + +Handlebars.AST.HashNode = function(pairs) { + this.type = "hash"; + this.pairs = pairs; +}; - Handlebars.AST.IdNode = function(parts) { - this.type = "ID"; - this.original = parts.join("."); +Handlebars.AST.IdNode = function(parts) { + this.type = "ID"; + this.original = parts.join("."); - var dig = [], depth = 0; + var dig = [], depth = 0; - for(var i=0,l=parts.length; i 0) { throw new Handlebars.Exception("Invalid path: " + this.original); } - else if (part === "..") { depth++; } - else { this.isScoped = true; } - } - else { dig.push(part); } + if (part === ".." || part === "." || part === "this") { + if (dig.length > 0) { throw new Handlebars.Exception("Invalid path: " + this.original); } + else if (part === "..") { depth++; } + else { this.isScoped = true; } } + else { dig.push(part); } + } - this.parts = dig; - this.string = dig.join('.'); - this.depth = depth; + this.parts = dig; + this.string = dig.join('.'); + this.depth = depth; - // an ID is simple if it only has one part, and that part is not - // `..` or `this`. - this.isSimple = parts.length === 1 && !this.isScoped && depth === 0; + // an ID is simple if it only has one part, and that part is not + // `..` or `this`. + this.isSimple = parts.length === 1 && !this.isScoped && depth === 0; - this.stringModeValue = this.string; - }; + this.stringModeValue = this.string; +}; - Handlebars.AST.PartialNameNode = function(name) { - this.type = "PARTIAL_NAME"; - this.name = name; - }; +Handlebars.AST.PartialNameNode = function(name) { + this.type = "PARTIAL_NAME"; + this.name = name; +}; - Handlebars.AST.DataNode = function(id) { - this.type = "DATA"; - this.id = id; - }; +Handlebars.AST.DataNode = function(id) { + this.type = "DATA"; + this.id = id; +}; - Handlebars.AST.StringNode = function(string) { - this.type = "STRING"; - this.string = string; - this.stringModeValue = string; - }; +Handlebars.AST.StringNode = function(string) { + this.type = "STRING"; + this.string = string; + this.stringModeValue = string; +}; - Handlebars.AST.IntegerNode = function(integer) { - this.type = "INTEGER"; - this.integer = integer; - this.stringModeValue = Number(integer); - }; +Handlebars.AST.IntegerNode = function(integer) { + this.type = "INTEGER"; + this.integer = integer; + this.stringModeValue = Number(integer); +}; - Handlebars.AST.BooleanNode = function(bool) { - this.type = "BOOLEAN"; - this.bool = bool; - this.stringModeValue = bool === "true"; - }; +Handlebars.AST.BooleanNode = function(bool) { + this.type = "BOOLEAN"; + this.bool = bool; + this.stringModeValue = bool === "true"; +}; - Handlebars.AST.CommentNode = function(comment) { - this.type = "comment"; - this.comment = comment; - }; +Handlebars.AST.CommentNode = function(comment) { + this.type = "comment"; + this.comment = comment; +}; -})(); // END(BROWSER) return Handlebars; From e2481cd66e445129e45056af3a0c77b0eeb41ccb Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sat, 16 Feb 2013 10:47:42 -0600 Subject: [PATCH 04/17] Use PARSER_CONTEXT for tokenizer tests --- spec/spec_helper.rb | 7 +++++++ spec/tokenizer_spec.rb | 7 ++----- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index cf738012e..74a5d211f 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -93,6 +93,13 @@ def self.js_load(context, file) end end + PARSER_CONTEXT = V8::Context.new + PARSER_CONTEXT.instance_eval do |context| + Handlebars::Spec.load_helpers(context); + + Handlebars::Spec.js_load(context, 'lib/handlebars/compiler/parser.js'); + end + COMPILE_CONTEXT = V8::Context.new COMPILE_CONTEXT.instance_eval do |context| Handlebars::Spec.load_helpers(context); diff --git a/spec/tokenizer_spec.rb b/spec/tokenizer_spec.rb index 9e189280a..0240e6310 100644 --- a/spec/tokenizer_spec.rb +++ b/spec/tokenizer_spec.rb @@ -2,12 +2,9 @@ require "timeout" describe "Tokenizer" do - let(:parser) { @context["handlebars"] } - let(:lexer) { @context["handlebars"]["lexer"] } + let(:parser) { Handlebars::Spec::PARSER_CONTEXT["handlebars"] } + let(:lexer) { Handlebars::Spec::PARSER_CONTEXT["handlebars"]["lexer"] } - before(:all) do - @compiles = true - end Token = Struct.new(:name, :text) def tokenize(string) From 1f76b065fc75a1d5f4f90413e071225dbf218063 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sat, 16 Feb 2013 13:07:39 -0600 Subject: [PATCH 05/17] browser-prefix/suffix templates --- Rakefile | 4 ++-- lib/handlebars/base.js | 6 +----- lib/handlebars/browser-prefix.js | 3 +++ lib/handlebars/browser-suffix.js | 1 + 4 files changed, 7 insertions(+), 7 deletions(-) create mode 100644 lib/handlebars/browser-prefix.js create mode 100644 lib/handlebars/browser-suffix.js diff --git a/Rakefile b/Rakefile index 9ecf913d4..e9c01227c 100644 --- a/Rakefile +++ b/Rakefile @@ -45,11 +45,11 @@ def remove_exports(string) match ? match[1] : string end -minimal_deps = %w(base compiler/parser compiler/base compiler/ast utils compiler/compiler runtime).map do |file| +minimal_deps = %w(browser-prefix base compiler/parser compiler/base compiler/ast utils compiler/compiler runtime browser-suffix).map do |file| "lib/handlebars/#{file}.js" end -runtime_deps = %w(base utils runtime).map do |file| +runtime_deps = %w(browser-prefix base utils runtime browser-suffix).map do |file| "lib/handlebars/#{file}.js" end diff --git a/lib/handlebars/base.js b/lib/handlebars/base.js index 1a25b5cc8..a5fbeed48 100644 --- a/lib/handlebars/base.js +++ b/lib/handlebars/base.js @@ -2,11 +2,9 @@ module.exports.create = function() { -// BEGIN(BROWSER) - var Handlebars = {}; -(function(Handlebars) { +// BEGIN(BROWSER) Handlebars.VERSION = "1.0.0-rc.3"; Handlebars.COMPILER_REVISION = 2; @@ -147,8 +145,6 @@ Handlebars.registerHelper('log', function(context, options) { Handlebars.log(level, context); }); -}(Handlebars)); - // END(BROWSER) return Handlebars; diff --git a/lib/handlebars/browser-prefix.js b/lib/handlebars/browser-prefix.js new file mode 100644 index 000000000..138467d68 --- /dev/null +++ b/lib/handlebars/browser-prefix.js @@ -0,0 +1,3 @@ +var Handlebars = {}; + +(function(Handlebars, undefined) { diff --git a/lib/handlebars/browser-suffix.js b/lib/handlebars/browser-suffix.js new file mode 100644 index 000000000..36c52c091 --- /dev/null +++ b/lib/handlebars/browser-suffix.js @@ -0,0 +1 @@ +})(Handlebars); From 16bfebbf499710a41e7d737185d9842fe87ee9b9 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sat, 16 Feb 2013 13:07:56 -0600 Subject: [PATCH 06/17] Use dist output in spec helper --- spec/spec_helper.rb | 21 +++------------------ 1 file changed, 3 insertions(+), 18 deletions(-) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 74a5d211f..e79415ad2 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -76,9 +76,7 @@ def self.js_load(context, file) CONTEXT.instance_eval do |context| Handlebars::Spec.load_helpers(context); - Handlebars::Spec.js_load(context, 'lib/handlebars/base.js'); - Handlebars::Spec.js_load(context, 'lib/handlebars/utils.js'); - Handlebars::Spec.js_load(context, 'lib/handlebars/runtime.js'); + Handlebars::Spec.js_load(context, 'dist/handlebars.js'); context["CompilerContext"] = {} CompilerContext = context["CompilerContext"] @@ -104,14 +102,9 @@ def self.js_load(context, file) COMPILE_CONTEXT.instance_eval do |context| Handlebars::Spec.load_helpers(context); - Handlebars::Spec.js_load(context, 'lib/handlebars/base.js'); - Handlebars::Spec.js_load(context, 'lib/handlebars/utils.js'); - Handlebars::Spec.js_load(context, 'lib/handlebars/compiler/parser.js'); - Handlebars::Spec.js_load(context, 'lib/handlebars/compiler/base.js'); - Handlebars::Spec.js_load(context, 'lib/handlebars/compiler/ast.js'); + Handlebars::Spec.js_load(context, 'dist/handlebars.js'); Handlebars::Spec.js_load(context, 'lib/handlebars/compiler/visitor.js'); Handlebars::Spec.js_load(context, 'lib/handlebars/compiler/printer.js'); - Handlebars::Spec.js_load(context, 'lib/handlebars/compiler/compiler.js'); context["Handlebars"]["logger"]["level"] = ENV["DEBUG_JS"] ? context["Handlebars"]["logger"][ENV["DEBUG_JS"]] : 4 @@ -128,15 +121,7 @@ def self.js_load(context, file) FULL_CONTEXT.instance_eval do |context| Handlebars::Spec.load_helpers(context); - Handlebars::Spec.js_load(context, 'lib/handlebars/base.js'); - Handlebars::Spec.js_load(context, 'lib/handlebars/utils.js'); - Handlebars::Spec.js_load(context, 'lib/handlebars/compiler/parser.js'); - Handlebars::Spec.js_load(context, 'lib/handlebars/compiler/base.js'); - Handlebars::Spec.js_load(context, 'lib/handlebars/compiler/ast.js'); - Handlebars::Spec.js_load(context, 'lib/handlebars/compiler/visitor.js'); - Handlebars::Spec.js_load(context, 'lib/handlebars/compiler/printer.js'); - Handlebars::Spec.js_load(context, 'lib/handlebars/compiler/compiler.js'); - Handlebars::Spec.js_load(context, 'lib/handlebars/runtime.js'); + Handlebars::Spec.js_load(context, 'dist/handlebars.js'); context["Handlebars"]["logger"]["level"] = ENV["DEBUG_JS"] ? context["Handlebars"]["logger"][ENV["DEBUG_JS"]] : 4 From 3e86bb0f64b866ab15eda7cda6eed83ba44b2f74 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sat, 16 Feb 2013 13:08:46 -0600 Subject: [PATCH 07/17] Remove unnecessary child scopes --- lib/handlebars/compiler/compiler.js | 2083 +++++++++++++-------------- lib/handlebars/utils.js | 68 +- 2 files changed, 1073 insertions(+), 1078 deletions(-) diff --git a/lib/handlebars/compiler/compiler.js b/lib/handlebars/compiler/compiler.js index 84008043c..04bf5cd81 100644 --- a/lib/handlebars/compiler/compiler.js +++ b/lib/handlebars/compiler/compiler.js @@ -7,1225 +7,1222 @@ compilerbase.attach(Handlebars); // BEGIN(BROWSER) /*jshint eqnull:true*/ -Handlebars.Compiler = function() {}; -Handlebars.JavaScriptCompiler = function() {}; +var Compiler = Handlebars.Compiler = function() {}; +var JavaScriptCompiler = Handlebars.JavaScriptCompiler = function() {}; -(function(Compiler, JavaScriptCompiler) { - // the foundHelper register will disambiguate helper lookup from finding a - // function in a context. This is necessary for mustache compatibility, which - // requires that context functions in blocks are evaluated by blockHelperMissing, - // and then proceed as if the resulting value was provided to blockHelperMissing. +// the foundHelper register will disambiguate helper lookup from finding a +// function in a context. This is necessary for mustache compatibility, which +// requires that context functions in blocks are evaluated by blockHelperMissing, +// and then proceed as if the resulting value was provided to blockHelperMissing. - Compiler.prototype = { - compiler: Compiler, +Compiler.prototype = { + compiler: Compiler, - disassemble: function() { - var opcodes = this.opcodes, opcode, out = [], params, param; + disassemble: function() { + var opcodes = this.opcodes, opcode, out = [], params, param; - for (var i=0, l=opcodes.length; i 0) { - this.source[1] = this.source[1] + ", " + locals.join(", "); - } + createFunctionContext: function(asObject) { + var locals = this.stackVars.concat(this.registers.list); - // Generate minimizer alias mappings - if (!this.isChild) { - for (var alias in this.context.aliases) { - this.source[1] = this.source[1] + ', ' + alias + '=' + this.context.aliases[alias]; - } - } + if(locals.length > 0) { + this.source[1] = this.source[1] + ", " + locals.join(", "); + } - if (this.source[1]) { - this.source[1] = "var " + this.source[1].substring(2) + ";"; + // Generate minimizer alias mappings + if (!this.isChild) { + for (var alias in this.context.aliases) { + this.source[1] = this.source[1] + ', ' + alias + '=' + this.context.aliases[alias]; } + } - // Merge children - if (!this.isChild) { - this.source[1] += '\n' + this.context.programs.join('\n') + '\n'; - } + if (this.source[1]) { + this.source[1] = "var " + this.source[1].substring(2) + ";"; + } - if (!this.environment.isSimple) { - this.source.push("return buffer;"); - } + // Merge children + if (!this.isChild) { + this.source[1] += '\n' + this.context.programs.join('\n') + '\n'; + } - var params = this.isChild ? ["depth0", "data"] : ["Handlebars", "depth0", "helpers", "partials", "data"]; + if (!this.environment.isSimple) { + this.source.push("return buffer;"); + } - for(var i=0, l=this.environment.depths.list.length; i this.stackVars.length) { this.stackVars.push("stack" + this.stackSlot); } - return this.topStackName(); - }, - topStackName: function() { - return "stack" + this.stackSlot; - }, - flushInline: function() { - var inlineStack = this.inlineStack; - if (inlineStack.length) { - this.inlineStack = []; - for (var i = 0, len = inlineStack.length; i < len; i++) { - var entry = inlineStack[i]; - if (entry instanceof Literal) { - this.compileStack.push(entry); - } else { - this.pushStack(entry); - } - } + this.push('(' + prefix + item + ')'); + } else { + // Prevent modification of the context depth variable. Through replaceStack + if (!/^stack/.test(stack)) { + stack = this.nextStack(); } - }, - isInline: function() { - return this.inlineStack.length; - }, - - popStack: function(wrapped) { - var inline = this.isInline(), - item = (inline ? this.inlineStack : this.compileStack).pop(); - if (!wrapped && (item instanceof Literal)) { - return item.value; - } else { - if (!inline) { - this.stackSlot--; + this.source.push(stack + " = (" + prefix + item + ");"); + } + return stack; + }, + + nextStack: function() { + return this.pushStack(); + }, + + incrStack: function() { + this.stackSlot++; + if(this.stackSlot > this.stackVars.length) { this.stackVars.push("stack" + this.stackSlot); } + return this.topStackName(); + }, + topStackName: function() { + return "stack" + this.stackSlot; + }, + flushInline: function() { + var inlineStack = this.inlineStack; + if (inlineStack.length) { + this.inlineStack = []; + for (var i = 0, len = inlineStack.length; i < len; i++) { + var entry = inlineStack[i]; + if (entry instanceof Literal) { + this.compileStack.push(entry); + } else { + this.pushStack(entry); } - return item; } - }, - - topStack: function(wrapped) { - var stack = (this.isInline() ? this.inlineStack : this.compileStack), - item = stack[stack.length - 1]; - - if (!wrapped && (item instanceof Literal)) { - return item.value; - } else { - return item; + } + }, + isInline: function() { + return this.inlineStack.length; + }, + + popStack: function(wrapped) { + var inline = this.isInline(), + item = (inline ? this.inlineStack : this.compileStack).pop(); + + if (!wrapped && (item instanceof Literal)) { + return item.value; + } else { + if (!inline) { + this.stackSlot--; } - }, - - quotedString: function(str) { - return '"' + str - .replace(/\\/g, '\\\\') - .replace(/"/g, '\\"') - .replace(/\n/g, '\\n') - .replace(/\r/g, '\\r') + '"'; - }, - - setupHelper: function(paramSize, name, missingParams) { - var params = []; - this.setupParams(paramSize, params, missingParams); - var foundHelper = this.nameLookup('helpers', name, 'helper'); - - return { - params: params, - name: foundHelper, - callParams: ["depth0"].concat(params).join(", "), - helperMissingParams: missingParams && ["depth0", this.quotedString(name)].concat(params).join(", ") - }; - }, - - // the params and contexts arguments are passed in arrays - // to fill in - setupParams: function(paramSize, params, useRegister) { - var options = [], contexts = [], types = [], param, inverse, program; - - options.push("hash:" + this.popStack()); - - inverse = this.popStack(); - program = this.popStack(); - - // Avoid setting fn and inverse if neither are set. This allows - // helpers to do a check for `if (options.fn)` - if (program || inverse) { - if (!program) { - this.context.aliases.self = "this"; - program = "self.noop"; - } + return item; + } + }, - if (!inverse) { - this.context.aliases.self = "this"; - inverse = "self.noop"; - } + topStack: function(wrapped) { + var stack = (this.isInline() ? this.inlineStack : this.compileStack), + item = stack[stack.length - 1]; - options.push("inverse:" + inverse); - options.push("fn:" + program); + if (!wrapped && (item instanceof Literal)) { + return item.value; + } else { + return item; + } + }, + + quotedString: function(str) { + return '"' + str + .replace(/\\/g, '\\\\') + .replace(/"/g, '\\"') + .replace(/\n/g, '\\n') + .replace(/\r/g, '\\r') + '"'; + }, + + setupHelper: function(paramSize, name, missingParams) { + var params = []; + this.setupParams(paramSize, params, missingParams); + var foundHelper = this.nameLookup('helpers', name, 'helper'); + + return { + params: params, + name: foundHelper, + callParams: ["depth0"].concat(params).join(", "), + helperMissingParams: missingParams && ["depth0", this.quotedString(name)].concat(params).join(", ") + }; + }, + + // the params and contexts arguments are passed in arrays + // to fill in + setupParams: function(paramSize, params, useRegister) { + var options = [], contexts = [], types = [], param, inverse, program; + + options.push("hash:" + this.popStack()); + + inverse = this.popStack(); + program = this.popStack(); + + // Avoid setting fn and inverse if neither are set. This allows + // helpers to do a check for `if (options.fn)` + if (program || inverse) { + if (!program) { + this.context.aliases.self = "this"; + program = "self.noop"; } - for(var i=0; i": ">", - '"': """, - "'": "'", - "`": "`" - }; +var escape = { + "&": "&", + "<": "<", + ">": ">", + '"': """, + "'": "'", + "`": "`" +}; - var badChars = /[&<>"'`]/g; - var possible = /[&<>"'`]/; +var badChars = /[&<>"'`]/g; +var possible = /[&<>"'`]/; - var escapeChar = function(chr) { - return escape[chr] || "&"; - }; +var escapeChar = function(chr) { + return escape[chr] || "&"; +}; - Handlebars.Utils = { - escapeExpression: function(string) { - // don't escape SafeStrings, since they're already safe - if (string instanceof Handlebars.SafeString) { - return string.toString(); - } else if (string == null || string === false) { - return ""; - } +Handlebars.Utils = { + escapeExpression: function(string) { + // don't escape SafeStrings, since they're already safe + if (string instanceof Handlebars.SafeString) { + return string.toString(); + } else if (string == null || string === false) { + return ""; + } - if(!possible.test(string)) { return string; } - return string.replace(badChars, escapeChar); - }, + if(!possible.test(string)) { return string; } + return string.replace(badChars, escapeChar); + }, - isEmpty: function(value) { - if (!value && value !== 0) { - return true; - } else if(Object.prototype.toString.call(value) === "[object Array]" && value.length === 0) { - return true; - } else { - return false; - } + isEmpty: function(value) { + if (!value && value !== 0) { + return true; + } else if(toString.call(value) === "[object Array]" && value.length === 0) { + return true; + } else { + return false; } - }; -})(); + } +}; // END(BROWSER) From da5cde5d1f5d4e158449ebf8ed223a52433d4013 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sat, 16 Feb 2013 13:36:34 -0600 Subject: [PATCH 08/17] Move Handlerbars.print into printer file --- lib/handlebars/compiler/base.js | 3 --- lib/handlebars/compiler/printer.js | 4 ++++ 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/handlebars/compiler/base.js b/lib/handlebars/compiler/base.js index 8ff1101d5..759445154 100644 --- a/lib/handlebars/compiler/base.js +++ b/lib/handlebars/compiler/base.js @@ -15,9 +15,6 @@ Handlebars.parse = function(input) { return Handlebars.Parser.parse(input); }; -Handlebars.print = function(ast) { - return new Handlebars.PrintVisitor().accept(ast); -}; // END(BROWSER) return Handlebars; diff --git a/lib/handlebars/compiler/printer.js b/lib/handlebars/compiler/printer.js index 1d96a91f8..780770e01 100644 --- a/lib/handlebars/compiler/printer.js +++ b/lib/handlebars/compiler/printer.js @@ -2,6 +2,10 @@ exports.attach = function(Handlebars) { // BEGIN(BROWSER) +Handlebars.print = function(ast) { + return new Handlebars.PrintVisitor().accept(ast); +}; + Handlebars.PrintVisitor = function() { this.padding = 0; }; Handlebars.PrintVisitor.prototype = new Handlebars.Visitor(); From 30623ce90372d53e1b856f4d8bf561750353d7ba Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sat, 16 Feb 2013 13:55:25 -0600 Subject: [PATCH 09/17] Add explicit SafeString property test --- spec/qunit_spec.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/spec/qunit_spec.js b/spec/qunit_spec.js index 5953bcacf..02bf124d8 100644 --- a/spec/qunit_spec.js +++ b/spec/qunit_spec.js @@ -629,6 +629,12 @@ test("constructing a safestring from a string and checking its type", function() equal(safe, "testing 1, 2, 3", "SafeString is equivalent to its underlying string"); }); +test("it should not escape SafeString properties", function() { + var name = new Handlebars.SafeString("Sean O'Malley"); + + shouldCompileTo('{{name}}', [{ name: name }], "Sean O'Malley"); +}); + suite("helperMissing"); test("if a context is not found, helperMissing is used", function() { From 10453d1ef8b259ac0af4febb3beb37d636d4f2d7 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sat, 16 Feb 2013 13:56:19 -0600 Subject: [PATCH 10/17] Use local vars --- lib/handlebars/compiler/compiler.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/handlebars/compiler/compiler.js b/lib/handlebars/compiler/compiler.js index 04bf5cd81..b57386ef1 100644 --- a/lib/handlebars/compiler/compiler.js +++ b/lib/handlebars/compiler/compiler.js @@ -1234,8 +1234,8 @@ Handlebars.precompile = function(input, options) { options.data = true; } var ast = Handlebars.parse(input); - var environment = new Handlebars.Compiler().compile(ast, options); - return new Handlebars.JavaScriptCompiler().compile(environment, options); + var environment = new Compiler().compile(ast, options); + return new JavaScriptCompiler().compile(environment, options); }; Handlebars.compile = function(input, options) { @@ -1250,8 +1250,8 @@ Handlebars.compile = function(input, options) { var compiled; function compile() { var ast = Handlebars.parse(input); - var environment = new Handlebars.Compiler().compile(ast, options); - var templateSpec = new Handlebars.JavaScriptCompiler().compile(environment, options, undefined, true); + var environment = new Compiler().compile(ast, options); + var templateSpec = new JavaScriptCompiler().compile(environment, options, undefined, true); return Handlebars.template(templateSpec); } From 1f00504d257872d5f3096f4d67b710a7f4600f39 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sat, 16 Feb 2013 16:02:08 -0600 Subject: [PATCH 11/17] Rebuild --- dist/handlebars.js | 2297 ++++++++++++++++++------------------ dist/handlebars.runtime.js | 88 +- 2 files changed, 1188 insertions(+), 1197 deletions(-) diff --git a/dist/handlebars.js b/dist/handlebars.js index 8f3433af7..4ce69df79 100644 --- a/dist/handlebars.js +++ b/dist/handlebars.js @@ -22,11 +22,12 @@ THE SOFTWARE. */ -// lib/handlebars/base.js - +// lib/handlebars/browser-prefix.js var Handlebars = {}; -(function(Handlebars) { +(function(Handlebars, undefined) { +; +// lib/handlebars/base.js Handlebars.VERSION = "1.0.0-rc.3"; Handlebars.COMPILER_REVISION = 2; @@ -166,8 +167,6 @@ Handlebars.registerHelper('log', function(context, options) { var level = options.data && options.data.level != null ? parseInt(options.data.level, 10) : 1; Handlebars.log(level, context); }); - -}(Handlebars)); ; // lib/handlebars/compiler/parser.js /* Jison generated parser */ @@ -654,139 +653,133 @@ Handlebars.parse = function(input) { Handlebars.Parser.yy = Handlebars.AST; return Handlebars.Parser.parse(input); }; - -Handlebars.print = function(ast) { - return new Handlebars.PrintVisitor().accept(ast); -};; +; // lib/handlebars/compiler/ast.js -(function() { +Handlebars.AST = {}; - Handlebars.AST = {}; +Handlebars.AST.ProgramNode = function(statements, inverse) { + this.type = "program"; + this.statements = statements; + if(inverse) { this.inverse = new Handlebars.AST.ProgramNode(inverse); } +}; - Handlebars.AST.ProgramNode = function(statements, inverse) { - this.type = "program"; - this.statements = statements; - if(inverse) { this.inverse = new Handlebars.AST.ProgramNode(inverse); } - }; +Handlebars.AST.MustacheNode = function(rawParams, hash, unescaped) { + this.type = "mustache"; + this.escaped = !unescaped; + this.hash = hash; - Handlebars.AST.MustacheNode = function(rawParams, hash, unescaped) { - this.type = "mustache"; - this.escaped = !unescaped; - this.hash = hash; + var id = this.id = rawParams[0]; + var params = this.params = rawParams.slice(1); - var id = this.id = rawParams[0]; - var params = this.params = rawParams.slice(1); + // a mustache is an eligible helper if: + // * its id is simple (a single part, not `this` or `..`) + var eligibleHelper = this.eligibleHelper = id.isSimple; - // a mustache is an eligible helper if: - // * its id is simple (a single part, not `this` or `..`) - var eligibleHelper = this.eligibleHelper = id.isSimple; + // a mustache is definitely a helper if: + // * it is an eligible helper, and + // * it has at least one parameter or hash segment + this.isHelper = eligibleHelper && (params.length || hash); - // a mustache is definitely a helper if: - // * it is an eligible helper, and - // * it has at least one parameter or hash segment - this.isHelper = eligibleHelper && (params.length || hash); + // if a mustache is an eligible helper but not a definite + // helper, it is ambiguous, and will be resolved in a later + // pass or at runtime. +}; - // if a mustache is an eligible helper but not a definite - // helper, it is ambiguous, and will be resolved in a later - // pass or at runtime. - }; +Handlebars.AST.PartialNode = function(partialName, context) { + this.type = "partial"; + this.partialName = partialName; + this.context = context; +}; - Handlebars.AST.PartialNode = function(partialName, context) { - this.type = "partial"; - this.partialName = partialName; - this.context = context; +Handlebars.AST.BlockNode = function(mustache, program, inverse, close) { + var verifyMatch = function(open, close) { + if(open.original !== close.original) { + throw new Handlebars.Exception(open.original + " doesn't match " + close.original); + } }; - Handlebars.AST.BlockNode = function(mustache, program, inverse, close) { - var verifyMatch = function(open, close) { - if(open.original !== close.original) { - throw new Handlebars.Exception(open.original + " doesn't match " + close.original); - } - }; + verifyMatch(mustache.id, close); + this.type = "block"; + this.mustache = mustache; + this.program = program; + this.inverse = inverse; - verifyMatch(mustache.id, close); - this.type = "block"; - this.mustache = mustache; - this.program = program; - this.inverse = inverse; - - if (this.inverse && !this.program) { - this.isInverse = true; - } - }; + if (this.inverse && !this.program) { + this.isInverse = true; + } +}; - Handlebars.AST.ContentNode = function(string) { - this.type = "content"; - this.string = string; - }; +Handlebars.AST.ContentNode = function(string) { + this.type = "content"; + this.string = string; +}; - Handlebars.AST.HashNode = function(pairs) { - this.type = "hash"; - this.pairs = pairs; - }; +Handlebars.AST.HashNode = function(pairs) { + this.type = "hash"; + this.pairs = pairs; +}; - Handlebars.AST.IdNode = function(parts) { - this.type = "ID"; - this.original = parts.join("."); +Handlebars.AST.IdNode = function(parts) { + this.type = "ID"; + this.original = parts.join("."); - var dig = [], depth = 0; + var dig = [], depth = 0; - for(var i=0,l=parts.length; i 0) { throw new Handlebars.Exception("Invalid path: " + this.original); } - else if (part === "..") { depth++; } - else { this.isScoped = true; } - } - else { dig.push(part); } + if (part === ".." || part === "." || part === "this") { + if (dig.length > 0) { throw new Handlebars.Exception("Invalid path: " + this.original); } + else if (part === "..") { depth++; } + else { this.isScoped = true; } } + else { dig.push(part); } + } - this.parts = dig; - this.string = dig.join('.'); - this.depth = depth; - - // an ID is simple if it only has one part, and that part is not - // `..` or `this`. - this.isSimple = parts.length === 1 && !this.isScoped && depth === 0; + this.parts = dig; + this.string = dig.join('.'); + this.depth = depth; - this.stringModeValue = this.string; - }; + // an ID is simple if it only has one part, and that part is not + // `..` or `this`. + this.isSimple = parts.length === 1 && !this.isScoped && depth === 0; - Handlebars.AST.PartialNameNode = function(name) { - this.type = "PARTIAL_NAME"; - this.name = name; - }; + this.stringModeValue = this.string; +}; - Handlebars.AST.DataNode = function(id) { - this.type = "DATA"; - this.id = id; - }; +Handlebars.AST.PartialNameNode = function(name) { + this.type = "PARTIAL_NAME"; + this.name = name; +}; - Handlebars.AST.StringNode = function(string) { - this.type = "STRING"; - this.string = string; - this.stringModeValue = string; - }; +Handlebars.AST.DataNode = function(id) { + this.type = "DATA"; + this.id = id; +}; - Handlebars.AST.IntegerNode = function(integer) { - this.type = "INTEGER"; - this.integer = integer; - this.stringModeValue = Number(integer); - }; +Handlebars.AST.StringNode = function(string) { + this.type = "STRING"; + this.string = string; + this.stringModeValue = string; +}; - Handlebars.AST.BooleanNode = function(bool) { - this.type = "BOOLEAN"; - this.bool = bool; - this.stringModeValue = bool === "true"; - }; +Handlebars.AST.IntegerNode = function(integer) { + this.type = "INTEGER"; + this.integer = integer; + this.stringModeValue = Number(integer); +}; - Handlebars.AST.CommentNode = function(comment) { - this.type = "comment"; - this.comment = comment; - }; +Handlebars.AST.BooleanNode = function(bool) { + this.type = "BOOLEAN"; + this.bool = bool; + this.stringModeValue = bool === "true"; +}; -})();; +Handlebars.AST.CommentNode = function(comment) { + this.type = "comment"; + this.comment = comment; +}; +; // lib/handlebars/utils.js var errorProps = ['description', 'fileName', 'lineNumber', 'message', 'name', 'number', 'stack']; @@ -809,1274 +802,1269 @@ Handlebars.SafeString.prototype.toString = function() { return this.string.toString(); }; -(function() { - var escape = { - "&": "&", - "<": "<", - ">": ">", - '"': """, - "'": "'", - "`": "`" - }; +var escape = { + "&": "&", + "<": "<", + ">": ">", + '"': """, + "'": "'", + "`": "`" +}; - var badChars = /[&<>"'`]/g; - var possible = /[&<>"'`]/; +var badChars = /[&<>"'`]/g; +var possible = /[&<>"'`]/; - var escapeChar = function(chr) { - return escape[chr] || "&"; - }; +var escapeChar = function(chr) { + return escape[chr] || "&"; +}; - Handlebars.Utils = { - escapeExpression: function(string) { - // don't escape SafeStrings, since they're already safe - if (string instanceof Handlebars.SafeString) { - return string.toString(); - } else if (string == null || string === false) { - return ""; - } +Handlebars.Utils = { + escapeExpression: function(string) { + // don't escape SafeStrings, since they're already safe + if (string instanceof Handlebars.SafeString) { + return string.toString(); + } else if (string == null || string === false) { + return ""; + } - if(!possible.test(string)) { return string; } - return string.replace(badChars, escapeChar); - }, + if(!possible.test(string)) { return string; } + return string.replace(badChars, escapeChar); + }, - isEmpty: function(value) { - if (!value && value !== 0) { - return true; - } else if(Object.prototype.toString.call(value) === "[object Array]" && value.length === 0) { - return true; - } else { - return false; - } + isEmpty: function(value) { + if (!value && value !== 0) { + return true; + } else if(toString.call(value) === "[object Array]" && value.length === 0) { + return true; + } else { + return false; } - }; -})(); + } +}; ; // lib/handlebars/compiler/compiler.js /*jshint eqnull:true*/ -Handlebars.Compiler = function() {}; -Handlebars.JavaScriptCompiler = function() {}; +var Compiler = Handlebars.Compiler = function() {}; +var JavaScriptCompiler = Handlebars.JavaScriptCompiler = function() {}; -(function(Compiler, JavaScriptCompiler) { - // the foundHelper register will disambiguate helper lookup from finding a - // function in a context. This is necessary for mustache compatibility, which - // requires that context functions in blocks are evaluated by blockHelperMissing, - // and then proceed as if the resulting value was provided to blockHelperMissing. +// the foundHelper register will disambiguate helper lookup from finding a +// function in a context. This is necessary for mustache compatibility, which +// requires that context functions in blocks are evaluated by blockHelperMissing, +// and then proceed as if the resulting value was provided to blockHelperMissing. - Compiler.prototype = { - compiler: Compiler, +Compiler.prototype = { + compiler: Compiler, - disassemble: function() { - var opcodes = this.opcodes, opcode, out = [], params, param; + disassemble: function() { + var opcodes = this.opcodes, opcode, out = [], params, param; - for (var i=0, l=opcodes.length; i 0) { - this.source[1] = this.source[1] + ", " + locals.join(", "); - } + preamble: function() { + var out = []; - // Generate minimizer alias mappings - if (!this.isChild) { - for (var alias in this.context.aliases) { - this.source[1] = this.source[1] + ', ' + alias + '=' + this.context.aliases[alias]; - } - } + if (!this.isChild) { + var namespace = this.namespace; + var copies = "helpers = helpers || " + namespace + ".helpers;"; + if (this.environment.usePartial) { copies = copies + " partials = partials || " + namespace + ".partials;"; } + if (this.options.data) { copies = copies + " data = data || {};"; } + out.push(copies); + } else { + out.push(''); + } - if (this.source[1]) { - this.source[1] = "var " + this.source[1].substring(2) + ";"; - } + if (!this.environment.isSimple) { + out.push(", buffer = " + this.initializeBuffer()); + } else { + out.push(""); + } - // Merge children - if (!this.isChild) { - this.source[1] += '\n' + this.context.programs.join('\n') + '\n'; - } + // track the last context pushed into place to allow skipping the + // getContext opcode when it would be a noop + this.lastContext = 0; + this.source = out; + }, - if (!this.environment.isSimple) { - this.source.push("return buffer;"); - } + createFunctionContext: function(asObject) { + var locals = this.stackVars.concat(this.registers.list); - var params = this.isChild ? ["depth0", "data"] : ["Handlebars", "depth0", "helpers", "partials", "data"]; + if(locals.length > 0) { + this.source[1] = this.source[1] + ", " + locals.join(", "); + } - for(var i=0, l=this.environment.depths.list.length; i this.stackVars.length) { this.stackVars.push("stack" + this.stackSlot); } - return this.topStackName(); - }, - topStackName: function() { - return "stack" + this.stackSlot; - }, - flushInline: function() { - var inlineStack = this.inlineStack; - if (inlineStack.length) { - this.inlineStack = []; - for (var i = 0, len = inlineStack.length; i < len; i++) { - var entry = inlineStack[i]; - if (entry instanceof Literal) { - this.compileStack.push(entry); - } else { - this.pushStack(entry); - } - } + this.push('(' + prefix + item + ')'); + } else { + // Prevent modification of the context depth variable. Through replaceStack + if (!/^stack/.test(stack)) { + stack = this.nextStack(); } - }, - isInline: function() { - return this.inlineStack.length; - }, - popStack: function(wrapped) { - var inline = this.isInline(), - item = (inline ? this.inlineStack : this.compileStack).pop(); + this.source.push(stack + " = (" + prefix + item + ");"); + } + return stack; + }, + + nextStack: function() { + return this.pushStack(); + }, - if (!wrapped && (item instanceof Literal)) { - return item.value; - } else { - if (!inline) { - this.stackSlot--; + incrStack: function() { + this.stackSlot++; + if(this.stackSlot > this.stackVars.length) { this.stackVars.push("stack" + this.stackSlot); } + return this.topStackName(); + }, + topStackName: function() { + return "stack" + this.stackSlot; + }, + flushInline: function() { + var inlineStack = this.inlineStack; + if (inlineStack.length) { + this.inlineStack = []; + for (var i = 0, len = inlineStack.length; i < len; i++) { + var entry = inlineStack[i]; + if (entry instanceof Literal) { + this.compileStack.push(entry); + } else { + this.pushStack(entry); } - return item; } - }, + } + }, + isInline: function() { + return this.inlineStack.length; + }, - topStack: function(wrapped) { - var stack = (this.isInline() ? this.inlineStack : this.compileStack), - item = stack[stack.length - 1]; + popStack: function(wrapped) { + var inline = this.isInline(), + item = (inline ? this.inlineStack : this.compileStack).pop(); - if (!wrapped && (item instanceof Literal)) { - return item.value; - } else { - return item; + if (!wrapped && (item instanceof Literal)) { + return item.value; + } else { + if (!inline) { + this.stackSlot--; } - }, + return item; + } + }, - quotedString: function(str) { - return '"' + str - .replace(/\\/g, '\\\\') - .replace(/"/g, '\\"') - .replace(/\n/g, '\\n') - .replace(/\r/g, '\\r') + '"'; - }, + topStack: function(wrapped) { + var stack = (this.isInline() ? this.inlineStack : this.compileStack), + item = stack[stack.length - 1]; - setupHelper: function(paramSize, name, missingParams) { - var params = []; - this.setupParams(paramSize, params, missingParams); - var foundHelper = this.nameLookup('helpers', name, 'helper'); + if (!wrapped && (item instanceof Literal)) { + return item.value; + } else { + return item; + } + }, - return { - params: params, - name: foundHelper, - callParams: ["depth0"].concat(params).join(", "), - helperMissingParams: missingParams && ["depth0", this.quotedString(name)].concat(params).join(", ") - }; - }, + quotedString: function(str) { + return '"' + str + .replace(/\\/g, '\\\\') + .replace(/"/g, '\\"') + .replace(/\n/g, '\\n') + .replace(/\r/g, '\\r') + '"'; + }, - // the params and contexts arguments are passed in arrays - // to fill in - setupParams: function(paramSize, params, useRegister) { - var options = [], contexts = [], types = [], param, inverse, program; + setupHelper: function(paramSize, name, missingParams) { + var params = []; + this.setupParams(paramSize, params, missingParams); + var foundHelper = this.nameLookup('helpers', name, 'helper'); - options.push("hash:" + this.popStack()); + return { + params: params, + name: foundHelper, + callParams: ["depth0"].concat(params).join(", "), + helperMissingParams: missingParams && ["depth0", this.quotedString(name)].concat(params).join(", ") + }; + }, - inverse = this.popStack(); - program = this.popStack(); + // the params and contexts arguments are passed in arrays + // to fill in + setupParams: function(paramSize, params, useRegister) { + var options = [], contexts = [], types = [], param, inverse, program; - // Avoid setting fn and inverse if neither are set. This allows - // helpers to do a check for `if (options.fn)` - if (program || inverse) { - if (!program) { - this.context.aliases.self = "this"; - program = "self.noop"; - } + options.push("hash:" + this.popStack()); - if (!inverse) { - this.context.aliases.self = "this"; - inverse = "self.noop"; - } + inverse = this.popStack(); + program = this.popStack(); - options.push("inverse:" + inverse); - options.push("fn:" + program); + // Avoid setting fn and inverse if neither are set. This allows + // helpers to do a check for `if (options.fn)` + if (program || inverse) { + if (!program) { + this.context.aliases.self = "this"; + program = "self.noop"; } - for(var i=0; i": ">", - '"': """, - "'": "'", - "`": "`" - }; - - var badChars = /[&<>"'`]/g; - var possible = /[&<>"'`]/; - - var escapeChar = function(chr) { - return escape[chr] || "&"; - }; - - Handlebars.Utils = { - escapeExpression: function(string) { - // don't escape SafeStrings, since they're already safe - if (string instanceof Handlebars.SafeString) { - return string.toString(); - } else if (string == null || string === false) { - return ""; - } +var escape = { + "&": "&", + "<": "<", + ">": ">", + '"': """, + "'": "'", + "`": "`" +}; - if(!possible.test(string)) { return string; } - return string.replace(badChars, escapeChar); - }, - - isEmpty: function(value) { - if (!value && value !== 0) { - return true; - } else if(Object.prototype.toString.call(value) === "[object Array]" && value.length === 0) { - return true; - } else { - return false; - } +var badChars = /[&<>"'`]/g; +var possible = /[&<>"'`]/; + +var escapeChar = function(chr) { + return escape[chr] || "&"; +}; + +Handlebars.Utils = { + escapeExpression: function(string) { + // don't escape SafeStrings, since they're already safe + if (string instanceof Handlebars.SafeString) { + return string.toString(); + } else if (string == null || string === false) { + return ""; } - }; -})(); + + if(!possible.test(string)) { return string; } + return string.replace(badChars, escapeChar); + }, + + isEmpty: function(value) { + if (!value && value !== 0) { + return true; + } else if(toString.call(value) === "[object Array]" && value.length === 0) { + return true; + } else { + return false; + } + } +}; ; // lib/handlebars/runtime.js @@ -318,3 +315,6 @@ Handlebars.VM = { Handlebars.template = Handlebars.VM.template; ; +// lib/handlebars/browser-suffix.js +})(Handlebars); +; From d6f146f8a5b1d4386363a1566ef78db7214ab1ed Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sat, 16 Feb 2013 16:18:17 -0600 Subject: [PATCH 12/17] Fix #428 nested if else rendering The program equality checker was not taking children into account when determining equality, causing breakages under similar cases. --- dist/handlebars.js | 11 +++++++++++ lib/handlebars/compiler/compiler.js | 11 +++++++++++ spec/qunit_spec.js | 13 +++++++++++++ 3 files changed, 35 insertions(+) diff --git a/dist/handlebars.js b/dist/handlebars.js index 4ce69df79..3cfb57d4c 100644 --- a/dist/handlebars.js +++ b/dist/handlebars.js @@ -897,6 +897,17 @@ Compiler.prototype = { } } } + + len = this.children.length; + if (other.children.length !== len) { + return false; + } + for (i = 0; i < len; i++) { + if (!this.children[i].equals(other.children[i])) { + return false; + } + } + return true; }, diff --git a/lib/handlebars/compiler/compiler.js b/lib/handlebars/compiler/compiler.js index 2ab1b7303..196ba18bb 100644 --- a/lib/handlebars/compiler/compiler.js +++ b/lib/handlebars/compiler/compiler.js @@ -59,6 +59,17 @@ Compiler.prototype = { } } } + + len = this.children.length; + if (other.children.length !== len) { + return false; + } + for (i = 0; i < len; i++) { + if (!this.children[i].equals(other.children[i])) { + return false; + } + } + return true; }, diff --git a/spec/qunit_spec.js b/spec/qunit_spec.js index dadccef78..f12e44cd4 100644 --- a/spec/qunit_spec.js +++ b/spec/qunit_spec.js @@ -1384,3 +1384,16 @@ test('GH-408: Multiple loops fail', function() { var result = template(context); equals(result, "John DoeJane DoeJohn DoeJane DoeJohn DoeJane Doe", 'It should output multiple times'); }); + +test('GS-428: Nested if else rendering', function() { + var succeedingTemplate = '{{#inverse}} {{#blk}} Unexpected {{/blk}} {{else}} {{#blk}} Expected {{/blk}} {{/inverse}}'; + var failingTemplate = '{{#inverse}} {{#blk}} Unexpected {{/blk}} {{else}} {{#blk}} Expected {{/blk}} {{/inverse}}'; + + var helpers = { + blk: function(block) { return block.fn(''); }, + inverse: function(block) { return block.inverse(''); } + }; + + shouldCompileTo(succeedingTemplate, [{}, helpers], ' Expected '); + shouldCompileTo(failingTemplate, [{}, helpers], ' Expected '); +}); From c95b3d6fc5d250bfb8983969688d420774ce7480 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sat, 16 Feb 2013 16:18:48 -0600 Subject: [PATCH 13/17] Create new options object for unless nesting Fixes #343 --- dist/handlebars.js | 6 +----- dist/handlebars.runtime.js | 6 +----- lib/handlebars/base.js | 6 +----- 3 files changed, 3 insertions(+), 15 deletions(-) diff --git a/dist/handlebars.js b/dist/handlebars.js index 3cfb57d4c..39afa6419 100644 --- a/dist/handlebars.js +++ b/dist/handlebars.js @@ -152,11 +152,7 @@ Handlebars.registerHelper('if', function(context, options) { }); Handlebars.registerHelper('unless', function(context, options) { - var fn = options.fn, inverse = options.inverse; - options.fn = inverse; - options.inverse = fn; - - return Handlebars.helpers['if'].call(this, context, options); + return Handlebars.helpers['if'].call(this, context, {fn: options.inverse, inverse: options.fn}); }); Handlebars.registerHelper('with', function(context, options) { diff --git a/dist/handlebars.runtime.js b/dist/handlebars.runtime.js index c604a21e1..a55b30fd4 100644 --- a/dist/handlebars.runtime.js +++ b/dist/handlebars.runtime.js @@ -152,11 +152,7 @@ Handlebars.registerHelper('if', function(context, options) { }); Handlebars.registerHelper('unless', function(context, options) { - var fn = options.fn, inverse = options.inverse; - options.fn = inverse; - options.inverse = fn; - - return Handlebars.helpers['if'].call(this, context, options); + return Handlebars.helpers['if'].call(this, context, {fn: options.inverse, inverse: options.fn}); }); Handlebars.registerHelper('with', function(context, options) { diff --git a/lib/handlebars/base.js b/lib/handlebars/base.js index a5fbeed48..dcbd95889 100644 --- a/lib/handlebars/base.js +++ b/lib/handlebars/base.js @@ -129,11 +129,7 @@ Handlebars.registerHelper('if', function(context, options) { }); Handlebars.registerHelper('unless', function(context, options) { - var fn = options.fn, inverse = options.inverse; - options.fn = inverse; - options.inverse = fn; - - return Handlebars.helpers['if'].call(this, context, options); + return Handlebars.helpers['if'].call(this, context, {fn: options.inverse, inverse: options.fn}); }); Handlebars.registerHelper('with', function(context, options) { From ac59d6d0600c8a9eff8e94bb99f57f6a424db9f0 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Sun, 17 Feb 2013 04:34:04 -0600 Subject: [PATCH 14/17] Always rebuild dist files --- Rakefile | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Rakefile b/Rakefile index e9c01227c..6fe5d849e 100644 --- a/Rakefile +++ b/Rakefile @@ -81,8 +81,14 @@ file "dist/handlebars.runtime.js" => runtime_deps do |task| build_for_task(task) end -task :build => [:compile, "dist/handlebars.js"] -task :runtime => [:compile, "dist/handlebars.runtime.js"] +task :build => [:compile] do |task| + # Since the tests are dependent on this always rebuild. + Rake::Task["dist/handlebars.js"].execute +end +task :runtime => [:compile] do |task| + # Since the tests are dependent on this always rebuild. + Rake::Task["dist/handlebars.runtime.js"].execute +end desc "build the build and runtime version of handlebars" task :release => [:build, :runtime] From 519156138dd03fb99125ce8ec9e3b34c3934c8f1 Mon Sep 17 00:00:00 2001 From: machty Date: Wed, 20 Feb 2013 00:04:39 -0500 Subject: [PATCH 15/17] Added handlebars-source gemspec for sharing with 3rd party precompilation libs --- handlebars-source.gemspec | 21 +++++++++++++++++++++ lib/handlebars/source.rb | 11 +++++++++++ 2 files changed, 32 insertions(+) create mode 100644 handlebars-source.gemspec create mode 100644 lib/handlebars/source.rb diff --git a/handlebars-source.gemspec b/handlebars-source.gemspec new file mode 100644 index 000000000..514e2b1e4 --- /dev/null +++ b/handlebars-source.gemspec @@ -0,0 +1,21 @@ +# -*- encoding: utf-8 -*- +require 'json' + +package = JSON.parse(File.read('package.json')) + +Gem::Specification.new do |gem| + gem.name = "handlebars-source" + gem.authors = ["Yehuda Katz"] + gem.email = ["wycats@gmail.com"] + gem.date = Time.now.strftime("%Y-%m-%d") + gem.description = %q{Handlebars.js source code wrapper for (pre)compilation gems.} + gem.summary = %q{Handlebars.js source code wrapper} + gem.homepage = "https://github.com/wycats/handlebars.js/" + gem.version = package["version"] + + gem.files = [ + 'dist/handlebars.js', + 'dist/handlebars.runtime.js', + 'lib/handlebars/source.rb' + ] +end diff --git a/lib/handlebars/source.rb b/lib/handlebars/source.rb new file mode 100644 index 000000000..f576885ae --- /dev/null +++ b/lib/handlebars/source.rb @@ -0,0 +1,11 @@ +module Handlebars + module Source + def self.bundled_path + File.expand_path("../../../dist/handlebars.js", __FILE__) + end + + def self.runtime_bundled_path + File.expand_path("../../../dist/handlebars.runtime.js", __FILE__) + end + end +end From 4bfb137e8127eeb265374dc0070d98880e02e775 Mon Sep 17 00:00:00 2001 From: Kevin Decker Date: Tue, 26 Feb 2013 17:46:19 -0600 Subject: [PATCH 16/17] Use master build for travis badge --- README.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.markdown b/README.markdown index 56042f4d3..0b338e829 100644 --- a/README.markdown +++ b/README.markdown @@ -1,4 +1,4 @@ -[![Build Status](https://secure.travis-ci.org/wycats/handlebars.js.png)](http://travis-ci.org/wycats/handlebars.js) +[![Build Status](https://travis-ci.org/wycats/handlebars.js.png?branch=master)](https://travis-ci.org/wycats/handlebars.js) Handlebars.js ============= From 948231a31d7d6786f1ea0a293a378b20928f6f2b Mon Sep 17 00:00:00 2001 From: kpdecker Date: Wed, 27 Feb 2013 07:52:34 -0600 Subject: [PATCH 17/17] 1.0.10 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7d9071643..7dda35743 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "handlebars", "description": "Extension of the Mustache logicless template language", - "version": "1.0.9", + "version": "1.0.10", "homepage": "http://www.handlebarsjs.com/", "keywords": [ "handlebars mustache template html"