From 2e7a3bd70223c0e532a24290c7d817b951354541 Mon Sep 17 00:00:00 2001
From: Joel Wietelmann <jwietelmann@gmail.com>
Date: Sat, 10 May 2014 14:07:15 -0500
Subject: [PATCH 01/91] Turning CLI precompiler into a wrapper around a module.

---
 bin/handlebars                | 316 ++++++++++------------------------
 lib/handlebars/precompiler.js | 140 +++++++++++++++
 2 files changed, 228 insertions(+), 228 deletions(-)
 create mode 100644 lib/handlebars/precompiler.js

diff --git a/bin/handlebars b/bin/handlebars
index 8794faa60..86b885370 100755
--- a/bin/handlebars
+++ b/bin/handlebars
@@ -1,235 +1,95 @@
 #!/usr/bin/env node
 
 var optimist = require('optimist')
-    .usage('Precompile handlebar templates.\nUsage: $0 template...', {
-      'f': {
-        'type': 'string',
-        'description': 'Output File',
-        'alias': 'output'
-      },
-      'a': {
-        'type': 'boolean',
-        'description': 'Exports amd style (require.js)',
-        'alias': 'amd'
-      },
-      'c': {
-        'type': 'string',
-        'description': 'Exports CommonJS style, path to Handlebars module',
-        'alias': 'commonjs',
-        'default': null
-      },
-      'h': {
-        'type': 'string',
-        'description': 'Path to handlebar.js (only valid for amd-style)',
-        'alias': 'handlebarPath',
-		    'default': ''
-      },
-      'k': {
-        'type': 'string',
-        'description': 'Known helpers',
-        'alias': 'known'
-      },
-      'o': {
-        'type': 'boolean',
-        'description': 'Known helpers only',
-        'alias': 'knownOnly'
-      },
-      'm': {
-        'type': 'boolean',
-        'description': 'Minimize output',
-        'alias': 'min'
-      },
-      'n': {
-        'type': 'string',
-        'description': 'Template namespace',
-        'alias': 'namespace',
-        'default': 'Handlebars.templates'
-      },
-      's': {
-        'type': 'boolean',
-        'description': 'Output template function only.',
-        'alias': 'simple'
-      },
-      'r': {
-        'type': 'string',
-        'description': 'Template root. Base value that will be stripped from template names.',
-        'alias': 'root'
-      },
-      'p' : {
-        'type': 'boolean',
-        'description': 'Compiling a partial template',
-        'alias': 'partial'
-      },
-      'd' : {
-        'type': 'boolean',
-        'description': 'Include data when compiling',
-        'alias': 'data'
-      },
-      'e': {
-        'type': 'string',
-        'description': 'Template extension.',
-        'alias': 'extension',
-        'default': 'handlebars'
-      },
-      'b': {
-        'type': 'boolean',
-        'description': 'Removes the BOM (Byte Order Mark) from the beginning of the templates.',
-        'alias': 'bom'
-      },
-      'v': {
-        'type': 'boolean',
-        'description': 'Prints the current compiler version',
-        'alias': 'version'
-      }
-    })
-
-    .check(function(argv) {
-      if (argv.version) {
-        return;
-      }
-
-      var template = [0];
-      if (!argv._.length) {
-        throw 'Must define at least one template or directory.';
-      }
-
-      argv._.forEach(function(template) {
-        try {
-          fs.statSync(template);
-        } catch (err) {
-          throw 'Unable to open template file "' + template + '"';
-        }
-      });
-    })
-    .check(function(argv) {
-      if (argv.simple && argv.min) {
-        throw 'Unable to minimze simple output';
-      }
-      if (argv.simple && (argv._.length !== 1 || fs.statSync(argv._[0]).isDirectory())) {
-        throw 'Unable to output multiple templates in simple mode';
-      }
-    });
-
-var fs = require('fs'),
-    handlebars = require('../lib'),
-    basename = require('path').basename,
-    uglify = require('uglify-js');
-
-var argv = optimist.argv,
-    template = argv._[0];
-
-if (argv.version) {
-  return console.log(handlebars.VERSION);
-}
-
-// Convert the known list into a hash
-var known = {};
-if (argv.known && !Array.isArray(argv.known)) {
-  argv.known = [argv.known];
-}
-if (argv.known) {
-  for (var i = 0, len = argv.known.length; i < len; i++) {
-    known[argv.known[i]] = true;
-  }
-}
-
-// Build file extension pattern
-var extension = argv.extension.replace(/[\\^$*+?.():=!|{}\-\[\]]/g, function(arg) { return '\\' + arg; });
-extension = new RegExp('\\.' + extension + '$');
-
-var output = [];
-if (!argv.simple) {
-  if (argv.amd) {
-    output.push('define([\'' + argv.handlebarPath + 'handlebars.runtime\'], function(Handlebars) {\n  Handlebars = Handlebars["default"];');
-  } else if (argv.commonjs) {
-    output.push('var Handlebars = require("' + argv.commonjs + '");');
-  } else {
-    output.push('(function() {\n');
-  }
-  output.push('  var template = Handlebars.template, templates = ');
-  output.push(argv.namespace);
-  output.push(' = ');
-  output.push(argv.namespace);
-  output.push(' || {};\n');
-}
-function processTemplate(template, root, explicit) {
-  var path = template,
-      stat = fs.statSync(path);
-  if (stat.isDirectory()) {
-    fs.readdirSync(template).map(function(file) {
-      var path = template + '/' + file;
-
-      if (extension.test(path) || fs.statSync(path).isDirectory()) {
-        processTemplate(path, root || template);
-      }
-    });
-  } else if (explicit || extension.test(path)) {
-    var data = fs.readFileSync(path, 'utf8');
-
-    if (argv.bom && data.indexOf('\uFEFF') === 0) {
-      data = data.substring(1);
-    }
-
-    var options = {
-      knownHelpers: known,
-      knownHelpersOnly: argv.o
-    };
-
-    if (argv.data) {
-      options.data = true;
-    }
-
-    // Clean the template name
-    if (!root) {
-      template = basename(template);
-    } else if (template.indexOf(root) === 0) {
-      template = template.substring(root.length+1);
+  .usage('Precompile handlebar templates.\nUsage: $0 template...', {
+    'f': {
+      'type': 'string',
+      'description': 'Output File',
+      'alias': 'output'
+    },
+    'a': {
+      'type': 'boolean',
+      'description': 'Exports amd style (require.js)',
+      'alias': 'amd'
+    },
+    'c': {
+      'type': 'string',
+      'description': 'Exports CommonJS style, path to Handlebars module',
+      'alias': 'commonjs',
+      'default': null
+    },
+    'h': {
+      'type': 'string',
+      'description': 'Path to handlebar.js (only valid for amd-style)',
+      'alias': 'handlebarPath',
+	    'default': ''
+    },
+    'k': {
+      'type': 'string',
+      'description': 'Known helpers',
+      'alias': 'known'
+    },
+    'o': {
+      'type': 'boolean',
+      'description': 'Known helpers only',
+      'alias': 'knownOnly'
+    },
+    'm': {
+      'type': 'boolean',
+      'description': 'Minimize output',
+      'alias': 'min'
+    },
+    'n': {
+      'type': 'string',
+      'description': 'Template namespace',
+      'alias': 'namespace',
+      'default': 'Handlebars.templates'
+    },
+    's': {
+      'type': 'boolean',
+      'description': 'Output template function only.',
+      'alias': 'simple'
+    },
+    'r': {
+      'type': 'string',
+      'description': 'Template root. Base value that will be stripped from template names.',
+      'alias': 'root'
+    },
+    'p' : {
+      'type': 'boolean',
+      'description': 'Compiling a partial template',
+      'alias': 'partial'
+    },
+    'd' : {
+      'type': 'boolean',
+      'description': 'Include data when compiling',
+      'alias': 'data'
+    },
+    'e': {
+      'type': 'string',
+      'description': 'Template extension.',
+      'alias': 'extension',
+      'default': 'handlebars'
+    },
+    'b': {
+      'type': 'boolean',
+      'description': 'Removes the BOM (Byte Order Mark) from the beginning of the templates.',
+      'alias': 'bom'
+    },
+    'v': {
+      'type': 'boolean',
+      'description': 'Prints the current compiler version',
+      'alias': 'version'
     }
-    template = template.replace(extension, '');
+  })
 
-    if (argv.simple) {
-      output.push(handlebars.precompile(data, options) + '\n');
-    } else if (argv.partial) {
-      if(argv.amd && (argv._.length == 1 && !fs.statSync(argv._[0]).isDirectory())) {
-        output.push('return ');
-      }
-      output.push('Handlebars.partials[\'' + template + '\'] = template(' + handlebars.precompile(data, options) + ');\n');
-    } else {
-      if(argv.amd && (argv._.length == 1 && !fs.statSync(argv._[0]).isDirectory())) {
-        output.push('return ');
-      }
-      output.push('templates[\'' + template + '\'] = template(' + handlebars.precompile(data, options) + ');\n');
+  .check(function(argv) {
+    if (argv.version) {
+      return;
     }
-  }
-}
-
-argv._.forEach(function(template) {
-  processTemplate(template, argv.root, true);
-});
-
-// Output the content
-if (!argv.simple) {
-  if (argv.amd) {
-    if(argv._.length > 1 || (argv._.length == 1 && fs.statSync(argv._[0]).isDirectory())) {
-      if(argv.partial){
-        output.push('return Handlebars.partials;\n');
-      } else {
-        output.push('return templates;\n');
-      }
-    }
-    output.push('});');
-  } else if (!argv.commonjs) {
-    output.push('})();');
-  }
-}
-output = output.join('');
-
-if (argv.min) {
-  output = uglify.minify(output, {fromString: true}).code;
-}
+  });
 
-if (argv.output) {
-  fs.writeFileSync(argv.output, output, 'utf8');
-} else {
-  console.log(output);
-}
+var argv = optimist.argv;
+argv.templates = argv._;
+delete argv._;
+return require('../lib/handlebars/precompiler')(argv);
\ No newline at end of file
diff --git a/lib/handlebars/precompiler.js b/lib/handlebars/precompiler.js
new file mode 100644
index 000000000..5dcd32ac6
--- /dev/null
+++ b/lib/handlebars/precompiler.js
@@ -0,0 +1,140 @@
+
+var fs = require('fs'),
+    handlebars = require('../../lib'),
+    basename = require('path').basename,
+    uglify = require('uglify-js');
+
+module.exports = function(opts) {
+
+  var template = [0];
+  if (!opts.templates.length) {
+    throw 'Must define at least one template or directory.';
+  }
+
+  opts.templates.forEach(function(template) {
+    try {
+      fs.statSync(template);
+    } catch (err) {
+      throw 'Unable to open template file "' + template + '"';
+    }
+  });
+
+  if (opts.simple && opts.min) {
+    throw 'Unable to minimze simple output';
+  }
+  if (opts.simple && (opts.templates.length !== 1 || fs.statSync(opts.templates[0]).isDirectory())) {
+    throw 'Unable to output multiple templates in simple mode';
+  }
+
+  // Convert the known list into a hash
+  var known = {};
+  if (opts.known && !Array.isArray(opts.known)) {
+    opts.known = [opts.known];
+  }
+  if (opts.known) {
+    for (var i = 0, len = opts.known.length; i < len; i++) {
+      known[opts.known[i]] = true;
+    }
+  }
+
+  // Build file extension pattern
+  var extension = opts.extension.replace(/[\\^$*+?.():=!|{}\-\[\]]/g, function(arg) { return '\\' + arg; });
+  extension = new RegExp('\\.' + extension + '$');
+
+  var output = [];
+  if (!opts.simple) {
+    if (opts.amd) {
+      output.push('define([\'' + opts.handlebarPath + 'handlebars.runtime\'], function(Handlebars) {\n  Handlebars = Handlebars["default"];');
+    } else if (opts.commonjs) {
+      output.push('var Handlebars = require("' + opts.commonjs + '");');
+    } else {
+      output.push('(function() {\n');
+    }
+    output.push('  var template = Handlebars.template, templates = ');
+    output.push(opts.namespace);
+    output.push(' = ');
+    output.push(opts.namespace);
+    output.push(' || {};\n');
+  }
+  function processTemplate(template, root, explicit) {
+    var path = template,
+        stat = fs.statSync(path);
+    if (stat.isDirectory()) {
+      fs.readdirSync(template).map(function(file) {
+        var path = template + '/' + file;
+
+        if (extension.test(path) || fs.statSync(path).isDirectory()) {
+          processTemplate(path, root || template);
+        }
+      });
+    } else if (explicit || extension.test(path)) {
+      var data = fs.readFileSync(path, 'utf8');
+
+      if (opts.bom && data.indexOf('\uFEFF') === 0) {
+        data = data.substring(1);
+      }
+
+      var options = {
+        knownHelpers: known,
+        knownHelpersOnly: opts.o
+      };
+
+      if (opts.data) {
+        options.data = true;
+      }
+
+      // Clean the template name
+      if (!root) {
+        template = basename(template);
+      } else if (template.indexOf(root) === 0) {
+        template = template.substring(root.length+1);
+      }
+      template = template.replace(extension, '');
+
+      if (opts.simple) {
+        output.push(handlebars.precompile(data, options) + '\n');
+      } else if (opts.partial) {
+        if(opts.amd && (opts.templates.length == 1 && !fs.statSync(opts.templates[0]).isDirectory())) {
+          output.push('return ');
+        }
+        output.push('Handlebars.partials[\'' + template + '\'] = template(' + handlebars.precompile(data, options) + ');\n');
+      } else {
+        if(opts.amd && (opts.templates.length == 1 && !fs.statSync(opts.templates[0]).isDirectory())) {
+          output.push('return ');
+        }
+        output.push('templates[\'' + template + '\'] = template(' + handlebars.precompile(data, options) + ');\n');
+      }
+    }
+  }
+
+  opts.templates.forEach(function(template) {
+    processTemplate(template, opts.root, true);
+  });
+
+  // Output the content
+  if (!opts.simple) {
+    if (opts.amd) {
+      if(opts.templates.length > 1 || (opts.templates.length == 1 && fs.statSync(opts.templates[0]).isDirectory())) {
+        if(opts.partial){
+          output.push('return Handlebars.partials;\n');
+        } else {
+          output.push('return templates;\n');
+        }
+      }
+      output.push('});');
+    } else if (!opts.commonjs) {
+      output.push('})();');
+    }
+  }
+  output = output.join('');
+
+  if (opts.min) {
+    output = uglify.minify(output, {fromString: true}).code;
+  }
+
+  if (opts.output) {
+    fs.writeFileSync(opts.output, output, 'utf8');
+  } else {
+    console.log(output);
+  }
+};
\ No newline at end of file

From e143849f8ae745101783eb7801a012914e510213 Mon Sep 17 00:00:00 2001
From: Joel Wietelmann <jwietelmann@gmail.com>
Date: Sat, 10 May 2014 14:10:05 -0500
Subject: [PATCH 02/91] Knocking the indent back to what it was to make for a
 less scary-looking pull request

---
 bin/handlebars | 172 ++++++++++++++++++++++++-------------------------
 1 file changed, 86 insertions(+), 86 deletions(-)

diff --git a/bin/handlebars b/bin/handlebars
index 86b885370..b400aaa56 100755
--- a/bin/handlebars
+++ b/bin/handlebars
@@ -1,93 +1,93 @@
 #!/usr/bin/env node
 
 var optimist = require('optimist')
-  .usage('Precompile handlebar templates.\nUsage: $0 template...', {
-    'f': {
-      'type': 'string',
-      'description': 'Output File',
-      'alias': 'output'
-    },
-    'a': {
-      'type': 'boolean',
-      'description': 'Exports amd style (require.js)',
-      'alias': 'amd'
-    },
-    'c': {
-      'type': 'string',
-      'description': 'Exports CommonJS style, path to Handlebars module',
-      'alias': 'commonjs',
-      'default': null
-    },
-    'h': {
-      'type': 'string',
-      'description': 'Path to handlebar.js (only valid for amd-style)',
-      'alias': 'handlebarPath',
-	    'default': ''
-    },
-    'k': {
-      'type': 'string',
-      'description': 'Known helpers',
-      'alias': 'known'
-    },
-    'o': {
-      'type': 'boolean',
-      'description': 'Known helpers only',
-      'alias': 'knownOnly'
-    },
-    'm': {
-      'type': 'boolean',
-      'description': 'Minimize output',
-      'alias': 'min'
-    },
-    'n': {
-      'type': 'string',
-      'description': 'Template namespace',
-      'alias': 'namespace',
-      'default': 'Handlebars.templates'
-    },
-    's': {
-      'type': 'boolean',
-      'description': 'Output template function only.',
-      'alias': 'simple'
-    },
-    'r': {
-      'type': 'string',
-      'description': 'Template root. Base value that will be stripped from template names.',
-      'alias': 'root'
-    },
-    'p' : {
-      'type': 'boolean',
-      'description': 'Compiling a partial template',
-      'alias': 'partial'
-    },
-    'd' : {
-      'type': 'boolean',
-      'description': 'Include data when compiling',
-      'alias': 'data'
-    },
-    'e': {
-      'type': 'string',
-      'description': 'Template extension.',
-      'alias': 'extension',
-      'default': 'handlebars'
-    },
-    'b': {
-      'type': 'boolean',
-      'description': 'Removes the BOM (Byte Order Mark) from the beginning of the templates.',
-      'alias': 'bom'
-    },
-    'v': {
-      'type': 'boolean',
-      'description': 'Prints the current compiler version',
-      'alias': 'version'
-    }
-  })
+    .usage('Precompile handlebar templates.\nUsage: $0 template...', {
+      'f': {
+        'type': 'string',
+        'description': 'Output File',
+        'alias': 'output'
+      },
+      'a': {
+        'type': 'boolean',
+        'description': 'Exports amd style (require.js)',
+        'alias': 'amd'
+      },
+      'c': {
+        'type': 'string',
+        'description': 'Exports CommonJS style, path to Handlebars module',
+        'alias': 'commonjs',
+        'default': null
+      },
+      'h': {
+        'type': 'string',
+        'description': 'Path to handlebar.js (only valid for amd-style)',
+        'alias': 'handlebarPath',
+  	    'default': ''
+      },
+      'k': {
+        'type': 'string',
+        'description': 'Known helpers',
+        'alias': 'known'
+      },
+      'o': {
+        'type': 'boolean',
+        'description': 'Known helpers only',
+        'alias': 'knownOnly'
+      },
+      'm': {
+        'type': 'boolean',
+        'description': 'Minimize output',
+        'alias': 'min'
+      },
+      'n': {
+        'type': 'string',
+        'description': 'Template namespace',
+        'alias': 'namespace',
+        'default': 'Handlebars.templates'
+      },
+      's': {
+        'type': 'boolean',
+        'description': 'Output template function only.',
+        'alias': 'simple'
+      },
+      'r': {
+        'type': 'string',
+        'description': 'Template root. Base value that will be stripped from template names.',
+        'alias': 'root'
+      },
+      'p' : {
+        'type': 'boolean',
+        'description': 'Compiling a partial template',
+        'alias': 'partial'
+      },
+      'd' : {
+        'type': 'boolean',
+        'description': 'Include data when compiling',
+        'alias': 'data'
+      },
+      'e': {
+        'type': 'string',
+        'description': 'Template extension.',
+        'alias': 'extension',
+        'default': 'handlebars'
+      },
+      'b': {
+        'type': 'boolean',
+        'description': 'Removes the BOM (Byte Order Mark) from the beginning of the templates.',
+        'alias': 'bom'
+      },
+      'v': {
+        'type': 'boolean',
+        'description': 'Prints the current compiler version',
+        'alias': 'version'
+      }
+    })
 
-  .check(function(argv) {
-    if (argv.version) {
-      return;
-    }
-  });
+    .check(function(argv) {
+      if (argv.version) {
+        return;
+      }
+    });
 
 var argv = optimist.argv;
 argv.templates = argv._;

From a14c689fa6adc51faf3dbb33357ef2a5f59a0a53 Mon Sep 17 00:00:00 2001
From: Joel Wietelmann <jwietelmann@gmail.com>
Date: Sat, 10 May 2014 14:11:07 -0500
Subject: [PATCH 03/91] Stupid line ending

---
 lib/handlebars/precompiler.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lib/handlebars/precompiler.js b/lib/handlebars/precompiler.js
index 5dcd32ac6..19a5e4cd5 100644
--- a/lib/handlebars/precompiler.js
+++ b/lib/handlebars/precompiler.js
@@ -137,4 +137,4 @@ module.exports = function(opts) {
   } else {
     console.log(output);
   }
-};
\ No newline at end of file
+};

From 3cdf14d2949665a282ea562138b97c8d9f829988 Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Tue, 27 May 2014 09:32:29 -0400
Subject: [PATCH 04/91] Add test case for Issue #800
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This isn’t failing in master but this is a useful test to prevent regressions.
---
 spec/subexpressions.js | 18 ++++++++++++++++++
 1 file changed, 18 insertions(+)

diff --git a/spec/subexpressions.js b/spec/subexpressions.js
index 5d11473bc..0ecdbb9d2 100644
--- a/spec/subexpressions.js
+++ b/spec/subexpressions.js
@@ -59,6 +59,24 @@ describe('subexpressions', function() {
     shouldCompileTo(string, [context, helpers], "val is true");
   });
 
+  it('GH-800 : Complex subexpressions', function() {
+    var context  = {a: 'a', b:'b', c:{c:'c'}, d:'d', e: {e: 'e'}};
+    var helpers  = {
+      dash: function(a, b) {
+        return a + "-" + b;
+      },
+      concat: function(a, b) {
+        return a + b;
+      }
+    };
+
+    shouldCompileTo('{{dash "abc" (concat a b)}}', [context, helpers], 'abc-ab');
+    shouldCompileTo('{{dash d (concat a b)}}', [context, helpers], 'd-ab');
+    shouldCompileTo('{{dash c.c (concat a b)}}', [context, helpers], 'c-ab');
+    shouldCompileTo('{{dash (concat a b) c.c}}', [context, helpers], 'ab-c');
+    shouldCompileTo('{{dash (concat a e.e) c.c}}', [context, helpers], 'ae-c');
+  });
+
   it("provides each nested helper invocation its own options hash", function() {
     var string = '{{equal (equal true true) true}}';
 

From 7172d167ddf9cbf1e8a0aae48970da42107e9e59 Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Tue, 27 May 2014 10:54:22 -0400
Subject: [PATCH 05/91] Provide clear throw on {{#each}}

Fixes #773
---
 lib/handlebars/base.js | 4 +---
 spec/builtins.js       | 9 +++++----
 2 files changed, 6 insertions(+), 7 deletions(-)

diff --git a/lib/handlebars/base.js b/lib/handlebars/base.js
index 3cb432352..9b910c7f1 100644
--- a/lib/handlebars/base.js
+++ b/lib/handlebars/base.js
@@ -97,10 +97,8 @@ function registerDefaultHelpers(instance) {
   });
 
   instance.registerHelper('each', function(context, options) {
-    // Allow for {{#each}}
     if (!options) {
-      options = context;
-      context = this;
+      throw new Exception('Must pass iterator to #each');
     }
 
     var fn = options.fn, inverse = options.inverse;
diff --git a/spec/builtins.js b/spec/builtins.js
index bbe494e08..a28f40003 100644
--- a/spec/builtins.js
+++ b/spec/builtins.js
@@ -1,4 +1,4 @@
-/*global CompilerContext, shouldCompileTo, compileWithPartials, handlebarsEnv */
+/*global CompilerContext, shouldCompileTo, shouldThrow, compileWithPartials, handlebarsEnv */
 describe('builtin helpers', function() {
   describe('#if', function() {
     it("if", function() {
@@ -182,9 +182,10 @@ describe('builtin helpers', function() {
     });
 
     it("each on implicit context", function() {
-      var string   = "{{#each}}{{text}}! {{/each}}cruel world!";
-      var hash     = [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}];
-      shouldCompileTo(string, [hash], "goodbye! Goodbye! GOODBYE! cruel world!");
+      shouldThrow(function() {
+        var template = CompilerContext.compile("{{#each}}{{text}}! {{/each}}cruel world!");
+        template({});
+      }, handlebarsEnv.Exception, 'Must pass iterator to #each');
     });
   });
 

From b79e31e5a52b73b445ee27c5272a0ad213f26ae8 Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Sat, 28 Jun 2014 18:44:11 -0700
Subject: [PATCH 06/91] Remove disassemble log statement

Fixes #772
---
 lib/handlebars/compiler/javascript-compiler.js | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/lib/handlebars/compiler/javascript-compiler.js b/lib/handlebars/compiler/javascript-compiler.js
index 8bb5da699..4b9db9293 100644
--- a/lib/handlebars/compiler/javascript-compiler.js
+++ b/lib/handlebars/compiler/javascript-compiler.js
@@ -1,4 +1,4 @@
-import { COMPILER_REVISION, REVISION_CHANGES, log } from "../base";
+import { COMPILER_REVISION, REVISION_CHANGES } from "../base";
 import Exception from "../exception";
 
 function Literal(value) {
@@ -62,8 +62,6 @@ JavaScriptCompiler.prototype = {
     this.trackIds = this.options.trackIds;
     this.precompile = !asObject;
 
-    log('debug', this.environment.disassemble() + "\n\n");
-
     this.name = this.environment.name;
     this.isChild = !!context;
     this.context = context || {

From 7890c7dc891b264bb28b477ffd3b2e6053f4b1d4 Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Sat, 5 Jul 2014 12:21:39 -0500
Subject: [PATCH 07/91] Add basic FAQ

---
 FAQ.md          | 22 ++++++++++++++++++++++
 README.markdown |  4 ++--
 2 files changed, 24 insertions(+), 2 deletions(-)
 create mode 100644 FAQ.md

diff --git a/FAQ.md b/FAQ.md
new file mode 100644
index 000000000..35cbee95f
--- /dev/null
+++ b/FAQ.md
@@ -0,0 +1,22 @@
+# Frequently Asked Questions
+
+1. How can I file a bug report:
+  Please don't hesitate to let us know if you find something wrong! In general we are going to ask for an example of the problem failing, which can be as simple as a jsfiddle/jsbin/etc. We've put together a jsfiddle [template](http://jsfiddle.net/9D88g/11/) to ease this. (We will keep this link up to date as new releases occur, so feel free to check back here)
+
+1. Why is it slower when compiling?
+  The Handlebars compiler must parse the template and construct a JavaScript program which can then be run. Under some environments such as older mobile devices this can have a performance impact which can be avoided by precompiling. Generally it's recommended that precompilation and the runtime library be used on all clients.
+
+1. Why doesn't this work with Content Security Policy restrictions?
+  Handlebars generates a dynamic function for each template which can cause issues with pages that have enabled Content Policy. It's recommended that templates are precompiled or the `unsafe-eval` policy is enabled for sites that must generate dynamic templates at runtime.
+
+1. How can I include script tags in my template?
+  If loading the template via an inlined `<script type="text/x-handlebars">` tag then you may need to break up the script tag with an empty comment to avoid browser parser errors:
+
+```
+  <script type="text/x-handlebars">
+    foo
+    <scr{{!}}ipt src="bar"></scr{{!}}ipt>
+  </script>
+```
+
+  It's generally recommended that templates are served through external, precompiled, files, which do not suffer from this issue.
diff --git a/README.markdown b/README.markdown
index a675c0062..87b17aa7d 100644
--- a/README.markdown
+++ b/README.markdown
@@ -455,7 +455,7 @@ gem build handlebars-source.gemspec
 gem push handlebars-source-*.gem
 ```
 
-After this point the handlebars site needs to be updated to point to the new version numbers. The jsfiddle bitly link should be updated to point to the most recent distribution.
+After this point the handlebars site needs to be updated to point to the new version numbers. The jsfiddle link should be updated to point to the most recent distribution for all instances in our documentation.
 
 License
 -------
@@ -466,4 +466,4 @@ Handlebars.js is released under the MIT license.
 [generator-release]: https://github.com/walmartlabs/generator-release
 [pull-request]: https://github.com/wycats/handlebars.js/pull/new/master
 [issue]: https://github.com/wycats/handlebars.js/issues/new
-[jsfiddle]: http://l.kde.cc/hbs-bug
+[jsfiddle]: http://jsfiddle.net/9D88g/11/

From efb17b80a634e5fdfb06edaee2c9d94da6d0e0b7 Mon Sep 17 00:00:00 2001
From: Kevin Decker <kpdecker@gmail.com>
Date: Sat, 5 Jul 2014 12:22:28 -0500
Subject: [PATCH 08/91] Update FAQ styling

---
 FAQ.md | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/FAQ.md b/FAQ.md
index 35cbee95f..ac520426e 100644
--- a/FAQ.md
+++ b/FAQ.md
@@ -12,11 +12,11 @@
 1. How can I include script tags in my template?
   If loading the template via an inlined `<script type="text/x-handlebars">` tag then you may need to break up the script tag with an empty comment to avoid browser parser errors:
 
-```
+  ```
   <script type="text/x-handlebars">
     foo
     <scr{{!}}ipt src="bar"></scr{{!}}ipt>
   </script>
-```
+  ```
 
   It's generally recommended that templates are served through external, precompiled, files, which do not suffer from this issue.

From 059532295d022c775f6c3e6636a9d6ffce8da728 Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Sat, 5 Jul 2014 14:17:11 -0500
Subject: [PATCH 09/91] Move precompiler to safer location

Attempts to avoid some of the Node vs. ES6 confusion between the different styles within the application.

Also adds some simple tests in addition to the integration test.
---
 bin/handlebars                      |  2 +-
 lib/{handlebars => }/precompiler.js | 22 +++++++++-------
 spec/precompiler.js                 | 40 +++++++++++++++++++++++++++++
 3 files changed, 54 insertions(+), 10 deletions(-)
 rename lib/{handlebars => }/precompiler.js (84%)
 create mode 100644 spec/precompiler.js

diff --git a/bin/handlebars b/bin/handlebars
index b400aaa56..4ea329661 100755
--- a/bin/handlebars
+++ b/bin/handlebars
@@ -92,4 +92,4 @@ var optimist = require('optimist')
 var argv = optimist.argv;
 argv.templates = argv._;
 delete argv._;
-return require('../lib/handlebars/precompiler')(argv);
\ No newline at end of file
+return require('../lib/precompiler').cli(argv);
diff --git a/lib/handlebars/precompiler.js b/lib/precompiler.js
similarity index 84%
rename from lib/handlebars/precompiler.js
rename to lib/precompiler.js
index 19a5e4cd5..bae923c60 100644
--- a/lib/handlebars/precompiler.js
+++ b/lib/precompiler.js
@@ -1,29 +1,33 @@
 
 var fs = require('fs'),
-    handlebars = require('../../lib'),
+    Handlebars = require('./index'),
     basename = require('path').basename,
     uglify = require('uglify-js');
 
-module.exports = function(opts) {
+module.exports.cli = function(opts) {
+  if (opts.version) {
+    console.log(Handlebars.VERSION);
+    return;
+  }
 
   var template = [0];
   if (!opts.templates.length) {
-    throw 'Must define at least one template or directory.';
+    throw new Handlebars.Exception('Must define at least one template or directory.');
   }
 
   opts.templates.forEach(function(template) {
     try {
       fs.statSync(template);
     } catch (err) {
-      throw 'Unable to open template file "' + template + '"';
+      throw new Handlebars.Exception('Unable to open template file "' + template + '"');
     }
   });
 
   if (opts.simple && opts.min) {
-    throw 'Unable to minimze simple output';
+    throw new Handlebars.Exception('Unable to minimze simple output');
   }
   if (opts.simple && (opts.templates.length !== 1 || fs.statSync(opts.templates[0]).isDirectory())) {
-    throw 'Unable to output multiple templates in simple mode';
+    throw new Handlebars.Exception('Unable to output multiple templates in simple mode');
   }
 
   // Convert the known list into a hash
@@ -92,17 +96,17 @@ module.exports = function(opts) {
       template = template.replace(extension, '');
 
       if (opts.simple) {
-        output.push(handlebars.precompile(data, options) + '\n');
+        output.push(Handlebars.precompile(data, options) + '\n');
       } else if (opts.partial) {
         if(opts.amd && (opts.templates.length == 1 && !fs.statSync(opts.templates[0]).isDirectory())) {
           output.push('return ');
         }
-        output.push('Handlebars.partials[\'' + template + '\'] = template(' + handlebars.precompile(data, options) + ');\n');
+        output.push('Handlebars.partials[\'' + template + '\'] = template(' + Handlebars.precompile(data, options) + ');\n');
       } else {
         if(opts.amd && (opts.templates.length == 1 && !fs.statSync(opts.templates[0]).isDirectory())) {
           output.push('return ');
         }
-        output.push('templates[\'' + template + '\'] = template(' + handlebars.precompile(data, options) + ');\n');
+        output.push('templates[\'' + template + '\'] = template(' + Handlebars.precompile(data, options) + ');\n');
       }
     }
   }
diff --git a/spec/precompiler.js b/spec/precompiler.js
new file mode 100644
index 000000000..10e2a2ee0
--- /dev/null
+++ b/spec/precompiler.js
@@ -0,0 +1,40 @@
+/*global shouldThrow */
+
+// NOP Under non-node environments
+if (typeof process === 'undefined') {
+  return;
+}
+
+var Handlebars = require('../lib'),
+    Precompiler = require('../lib/precompiler');
+
+describe('precompiler', function() {
+  var log,
+      logFunction;
+
+  beforeEach(function() {
+    logFunction = console.log;
+    log = '';
+    console.log = function() {
+      log += Array.prototype.join.call(arguments, '');
+    };
+  });
+  afterEach(function() {
+    console.log = logFunction;
+  });
+
+  it('should output version', function() {
+    Precompiler.cli({templates: [], version: true});
+    equals(log, Handlebars.VERSION);
+  });
+  it('should throw if lacking templates', function() {
+    shouldThrow(function() {
+      Precompiler.cli({templates: []});
+    }, Handlebars.Exception, 'Must define at least one template or directory.');
+  });
+  it('should throw on missing template', function() {
+    shouldThrow(function() {
+      Precompiler.cli({templates: ['foo']});
+    }, Handlebars.Exception, 'Unable to open template file "foo"');
+  });
+});

From 058e7495f9ae22738919292ae0486531cd5b8b3e Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Sat, 5 Jul 2014 14:45:13 -0500
Subject: [PATCH 10/91] Add precompiler version FAQ entry.

Resolves #789
---
 FAQ.md | 17 ++++++++++++++++-
 1 file changed, 16 insertions(+), 1 deletion(-)

diff --git a/FAQ.md b/FAQ.md
index ac520426e..bd4f6aa38 100644
--- a/FAQ.md
+++ b/FAQ.md
@@ -12,7 +12,7 @@
 1. How can I include script tags in my template?
   If loading the template via an inlined `<script type="text/x-handlebars">` tag then you may need to break up the script tag with an empty comment to avoid browser parser errors:
 
-  ```
+  ```html
   <script type="text/x-handlebars">
     foo
     <scr{{!}}ipt src="bar"></scr{{!}}ipt>
@@ -20,3 +20,18 @@
   ```
 
   It's generally recommended that templates are served through external, precompiled, files, which do not suffer from this issue.
+
+1. Why are my precompiled scripts throwing exceptions?
+  When using the precompiler, it's important that a supporting version of the Handlebars runtime be loaded on the target page. In version 1.x there were rudimentary checks to compare the version but these did not always work. This is fixed under 2.x but the version checking does not work between these two versions. If you see unexpected errors such as `undefined is not a function` or similar, please verify that the same version is being used for both the precompiler and the client. This can be checked via:
+
+  ```sh
+  handlebars --version
+  ```
+  If using the integrated precompiler and
+
+  ```javascript
+  console.log(Handlebars.VERSION);
+  ```
+  On the client side.
+
+  Should these match, please file an issue with us, per our [issue filing guidelines](https://github.com/wycats/handlebars.js/blob/master/README.markdown#reporting-issues).

From 054627adb0441fbd4622ab3bd6c5d94e63dc4088 Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Sat, 5 Jul 2014 15:03:18 -0500
Subject: [PATCH 11/91] Move precompiler test filter into describe

This allows us to play nicely with in-browser tests
---
 spec/precompiler.js | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)

diff --git a/spec/precompiler.js b/spec/precompiler.js
index 10e2a2ee0..7cd1ffd0b 100644
--- a/spec/precompiler.js
+++ b/spec/precompiler.js
@@ -1,14 +1,14 @@
 /*global shouldThrow */
 
-// NOP Under non-node environments
-if (typeof process === 'undefined') {
-  return;
-}
+describe('precompiler', function() {
+  // NOP Under non-node environments
+  if (typeof process === 'undefined') {
+    return;
+  }
 
-var Handlebars = require('../lib'),
-    Precompiler = require('../lib/precompiler');
+  var Handlebars = require('../lib'),
+      Precompiler = require('../lib/precompiler');
 
-describe('precompiler', function() {
   var log,
       logFunction;
 

From 704961b37840a439d6576a0d8f0f595838ab0963 Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Sun, 6 Jul 2014 11:37:24 -0500
Subject: [PATCH 12/91] Add additional paths to path throughput test

---
 bench/templates/paths.js | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/bench/templates/paths.js b/bench/templates/paths.js
index 0a426dd83..d84e06152 100644
--- a/bench/templates/paths.js
+++ b/bench/templates/paths.js
@@ -1,7 +1,7 @@
 module.exports = {
-  context: { person: { name: "Larry", age: 45 } },
-  handlebars: "{{person.name}}{{person.age}}{{person.foo}}{{animal.age}}",
-  dust: "{person.name}{person.age}{person.foo}{animal.age}",
-  eco: "<%= @person.name %><%= @person.age %><%= @person.foo %><% if @animal: %><%= @animal.age %><% end %>",
-  mustache: "{{person.name}}{{person.age}}{{person.foo}}{{animal.age}}"
+  context: { person: { name: {bar: {baz: "Larry"}}, age: 45 } },
+  handlebars: "{{person.name.bar.baz}}{{person.age}}{{person.foo}}{{animal.age}}",
+  dust: "{person.name.bar.baz}{person.age}{person.foo}{animal.age}",
+  eco: "<%= @person.name.bar.baz %><%= @person.age %><%= @person.foo %><% if @animal: %><%= @animal.age %><% end %>",
+  mustache: "{{person.name.bar.baz}}{{person.age}}{{person.foo}}{{animal.age}}"
 };

From 4aad72d2230a6eaaf242130c57ad1efa4d701f3c Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Sun, 6 Jul 2014 12:53:01 -0500
Subject: [PATCH 13/91] Move lambda resolution to runtime

This has a very positive impact on precompiled output size, particularly for known-helpers cases, and little or no impact on the throughput numbers.
---
 lib/handlebars/compiler/javascript-compiler.js | 6 ++----
 lib/handlebars/runtime.js                      | 4 ++++
 2 files changed, 6 insertions(+), 4 deletions(-)

diff --git a/lib/handlebars/compiler/javascript-compiler.js b/lib/handlebars/compiler/javascript-compiler.js
index 4b9db9293..4d950d461 100644
--- a/lib/handlebars/compiler/javascript-compiler.js
+++ b/lib/handlebars/compiler/javascript-compiler.js
@@ -377,11 +377,9 @@ JavaScriptCompiler.prototype = {
   // If the `value` is a lambda, replace it on the stack by
   // the return value of the lambda
   resolvePossibleLambda: function() {
-    this.aliases.functionType = '"function"';
+    this.aliases.lambda = 'this.lambda';
 
-    this.replaceStack(function(current) {
-      return "typeof " + current + " === functionType ? " + current + ".apply(depth0) : " + current;
-    });
+    this.push('lambda(' + this.popStack() + ', depth0)');
   },
 
   // [lookup]
diff --git a/lib/handlebars/runtime.js b/lib/handlebars/runtime.js
index d43f465e3..b9fc77d7d 100644
--- a/lib/handlebars/runtime.js
+++ b/lib/handlebars/runtime.js
@@ -50,6 +50,10 @@ export function template(templateSpec, env) {
 
   // Just add water
   var container = {
+    lambda: function(current, context) {
+      return typeof current === 'function' ? current.call(context) : current;
+    },
+
     escapeExpression: Utils.escapeExpression,
     invokePartial: invokePartialWrapper,
 

From b5a5c76ceb85fee36340e14b79306a436d32ff72 Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Sun, 6 Jul 2014 23:40:46 -0500
Subject: [PATCH 14/91] Rework lookup null protector logic

- Move the lookup null protection out of `nameLookup` and into that contexts that are aware of the needs for falsy vs. not displayed values.
- Optimize lookup for nested path operations

Fixes #731
---
 lib/handlebars/compiler/compiler.js           |  15 +--
 .../compiler/javascript-compiler.js           | 116 ++++++++++--------
 2 files changed, 67 insertions(+), 64 deletions(-)

diff --git a/lib/handlebars/compiler/compiler.js b/lib/handlebars/compiler/compiler.js
index e5f2280ac..31176c9a3 100644
--- a/lib/handlebars/compiler/compiler.js
+++ b/lib/handlebars/compiler/compiler.js
@@ -275,6 +275,8 @@ Compiler.prototype = {
     } else if (this.options.knownHelpersOnly) {
       throw new Exception("You specified knownHelpersOnly, but used the unknown helper " + name, sexpr);
     } else {
+      id.falsy = true;
+
       this.ID(id);
       this.opcode('invokeHelper', params.length, id.original, sexpr.isRoot);
     }
@@ -298,23 +300,16 @@ Compiler.prototype = {
 
     var name = id.parts[0];
     if (!name) {
+      // Context reference, i.e. `{{foo .}}` or `{{foo ..}}`
       this.opcode('pushContext');
     } else {
-      this.opcode('lookupOnContext', id.parts[0]);
-    }
-
-    for(var i=1, l=id.parts.length; i<l; i++) {
-      this.opcode('lookup', id.parts[i]);
+      this.opcode('lookupOnContext', id.parts, id.falsy);
     }
   },
 
   DATA: function(data) {
     this.options.data = true;
-    this.opcode('lookupData', data.id.depth);
-    var parts = data.id.parts;
-    for(var i=0, l=parts.length; i<l; i++) {
-      this.opcode('lookup', parts[i]);
-    }
+    this.opcode('lookupData', data.id.depth, data.id.parts);
   },
 
   STRING: function(string) {
diff --git a/lib/handlebars/compiler/javascript-compiler.js b/lib/handlebars/compiler/javascript-compiler.js
index 4d950d461..09309b5a5 100644
--- a/lib/handlebars/compiler/javascript-compiler.js
+++ b/lib/handlebars/compiler/javascript-compiler.js
@@ -11,22 +11,10 @@ JavaScriptCompiler.prototype = {
   // PUBLIC API: You can override these methods in a subclass to provide
   // alternative compiled forms for name lookup and buffering semantics
   nameLookup: function(parent, name /* , type*/) {
-    var wrap,
-        ret;
-    if (parent.indexOf('depth') === 0) {
-      wrap = true;
-    }
-
     if (JavaScriptCompiler.isValidJavaScriptVariableName(name)) {
-      ret = parent + "." + name;
-    } else {
-      ret = parent + "['" + name + "']";
-    }
-
-    if (wrap) {
-      return '(' + parent + ' && ' + ret + ')';
+      return parent + "." + name;
     } else {
-      return ret;
+      return parent + "['" + name + "']";
     }
   },
 
@@ -343,20 +331,7 @@ JavaScriptCompiler.prototype = {
   //
   // Set the value of the `lastContext` compiler value to the depth
   getContext: function(depth) {
-    if(this.lastContext !== depth) {
-      this.lastContext = depth;
-    }
-  },
-
-  // [lookupOnContext]
-  //
-  // On stack, before: ...
-  // On stack, after: currentContext[name], ...
-  //
-  // Looks up the value of `name` on the current context and pushes
-  // it onto the stack.
-  lookupOnContext: function(name) {
-    this.push(this.nameLookup('depth' + this.lastContext, name, 'context'));
+    this.lastContext = depth;
   },
 
   // [pushContext]
@@ -369,30 +344,35 @@ JavaScriptCompiler.prototype = {
     this.pushStackLiteral('depth' + this.lastContext);
   },
 
-  // [resolvePossibleLambda]
-  //
-  // On stack, before: value, ...
-  // On stack, after: resolved value, ...
-  //
-  // If the `value` is a lambda, replace it on the stack by
-  // the return value of the lambda
-  resolvePossibleLambda: function() {
-    this.aliases.lambda = 'this.lambda';
-
-    this.push('lambda(' + this.popStack() + ', depth0)');
-  },
-
-  // [lookup]
+  // [lookupOnContext]
   //
-  // On stack, before: value, ...
-  // On stack, after: value[name], ...
+  // On stack, before: ...
+  // On stack, after: currentContext[name], ...
   //
-  // Replace the value on the stack with the result of looking
-  // up `name` on `value`
-  lookup: function(name) {
-    this.replaceStack(function(current) {
-      return current + " == null || " + current + " === false ? " + current + " : " + this.nameLookup(current, name, 'context');
-    });
+  // Looks up the value of `name` on the current context and pushes
+  // it onto the stack.
+  lookupOnContext: function(parts, falsy) {
+    /*jshint -W083 */
+    this.pushContext();
+    if (!parts) {
+      return;
+    }
+
+    var len = parts.length;
+    for (var i = 0; i < len; i++) {
+      this.replaceStack(function(current) {
+        var lookup = this.nameLookup(current, parts[i], 'context');
+        // We want to ensure that zero and false are handled properly for the first element
+        // of non-chained elements, if the context (falsy flag) needs to have the special
+        // handling for these values.
+        if (!falsy && !i && len === 1) {
+          return ' != null ? ' + lookup + ' : ' + current;
+        } else {
+          // Otherwise we can use generic falsy handling
+          return ' && ' + lookup;
+        }
+      }, true);
+    }
   },
 
   // [lookupData]
@@ -401,12 +381,36 @@ JavaScriptCompiler.prototype = {
   // On stack, after: data, ...
   //
   // Push the data lookup operator
-  lookupData: function(depth) {
+  lookupData: function(depth, parts) {
+    /*jshint -W083 */
     if (!depth) {
       this.pushStackLiteral('data');
     } else {
       this.pushStackLiteral('this.data(data, ' + depth + ')');
     }
+    if (!parts) {
+      return;
+    }
+
+    var len = parts.length;
+    for (var i = 0; i < len; i++) {
+      this.replaceStack(function(current) {
+        return ' && ' + this.nameLookup(current, parts[i], 'data');
+      }, true);
+    }
+  },
+
+  // [resolvePossibleLambda]
+  //
+  // On stack, before: value, ...
+  // On stack, after: resolved value, ...
+  //
+  // If the `value` is a lambda, replace it on the stack by
+  // the return value of the lambda
+  resolvePossibleLambda: function() {
+    this.aliases.lambda = 'this.lambda';
+
+    this.push('lambda(' + this.popStack() + ', depth0)');
   },
 
   // [pushStringParam]
@@ -577,7 +581,7 @@ JavaScriptCompiler.prototype = {
     var helper = this.setupHelper(0, name, helperCall);
 
     var helperName = this.lastHelper = this.nameLookup('helpers', name, 'helper');
-    var nonHelper = this.nameLookup('depth' + this.lastContext, name, 'context');
+    var nonHelper = '(depth' + this.lastContext + ' && ' + this.nameLookup('depth' + this.lastContext, name, 'context') + ')';
 
     this.push(
       '((helper = ' + helperName + ' || ' + nonHelper
@@ -737,7 +741,7 @@ JavaScriptCompiler.prototype = {
     return stack;
   },
 
-  replaceStack: function(callback) {
+  replaceStack: function(callback, chain) {
     var prefix = '',
         inline = this.isInline(),
         stack,
@@ -753,12 +757,16 @@ JavaScriptCompiler.prototype = {
         // Literals do not need to be inlined
         stack = top.value;
         usedLiteral = true;
+
+        if (chain) {
+          prefix = stack;
+        }
       } else {
         // Get or create the current stack name for use by the inline
         createdStack = !this.stackSlot;
         var name = !createdStack ? this.topStackName() : this.incrStack();
 
-        prefix = '(' + this.push(name) + ' = ' + top + '),';
+        prefix = '(' + this.push(name) + ' = ' + top + (chain ? ')' : '),');
         stack = this.topStack();
       }
     } else {

From 107aa4a66ada6fd18e9a890c434f382bae2da998 Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Mon, 7 Jul 2014 00:01:17 -0500
Subject: [PATCH 15/91] Add test case for GH-731

---
 spec/regressions.js | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/spec/regressions.js b/spec/regressions.js
index 7412cd339..214a14235 100644
--- a/spec/regressions.js
+++ b/spec/regressions.js
@@ -118,6 +118,10 @@ describe('Regressions', function() {
     }, 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  ~ ');
+  });
+
   if (Handlebars.AST) {
     it("can pass through an already-compiled AST via compile/precompile", function() {
       equal(Handlebars.compile(new Handlebars.AST.ProgramNode([ new Handlebars.AST.ContentNode("Hello")]))(), 'Hello');

From c90cfe247cf1108f94fd97b4e93a42f4a49d3516 Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Mon, 7 Jul 2014 00:20:02 -0500
Subject: [PATCH 16/91] Use ID lookup for ambiguous helper evaluation

Final change for #731
---
 lib/handlebars/compiler/compiler.js            | 2 ++
 lib/handlebars/compiler/javascript-compiler.js | 3 ++-
 2 files changed, 4 insertions(+), 1 deletion(-)

diff --git a/lib/handlebars/compiler/compiler.js b/lib/handlebars/compiler/compiler.js
index 31176c9a3..fbbb390b5 100644
--- a/lib/handlebars/compiler/compiler.js
+++ b/lib/handlebars/compiler/compiler.js
@@ -245,6 +245,8 @@ Compiler.prototype = {
     this.opcode('pushProgram', program);
     this.opcode('pushProgram', inverse);
 
+    this.ID(id);
+
     this.opcode('invokeAmbiguous', name, isBlock);
   },
 
diff --git a/lib/handlebars/compiler/javascript-compiler.js b/lib/handlebars/compiler/javascript-compiler.js
index 09309b5a5..b2baad467 100644
--- a/lib/handlebars/compiler/javascript-compiler.js
+++ b/lib/handlebars/compiler/javascript-compiler.js
@@ -577,11 +577,12 @@ JavaScriptCompiler.prototype = {
     this.aliases.functionType = '"function"';
     this.useRegister('helper');
 
+    var nonHelper = this.popStack();
+
     this.emptyHash();
     var helper = this.setupHelper(0, name, helperCall);
 
     var helperName = this.lastHelper = this.nameLookup('helpers', name, 'helper');
-    var nonHelper = '(depth' + this.lastContext + ' && ' + this.nameLookup('depth' + this.lastContext, name, 'context') + ')';
 
     this.push(
       '((helper = ' + helperName + ' || ' + nonHelper

From 1fb7b51ee6bb8f4a2e395821a4648333a699a982 Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Sat, 12 Jul 2014 11:43:45 -0500
Subject: [PATCH 17/91] Fix rendering of paths that resolve to zero

Fixes #820
---
 lib/handlebars/compiler/javascript-compiler.js | 2 +-
 spec/regressions.js                            | 4 ++++
 2 files changed, 5 insertions(+), 1 deletion(-)

diff --git a/lib/handlebars/compiler/javascript-compiler.js b/lib/handlebars/compiler/javascript-compiler.js
index b2baad467..2d4b11569 100644
--- a/lib/handlebars/compiler/javascript-compiler.js
+++ b/lib/handlebars/compiler/javascript-compiler.js
@@ -369,7 +369,7 @@ JavaScriptCompiler.prototype = {
           return ' != null ? ' + lookup + ' : ' + current;
         } else {
           // Otherwise we can use generic falsy handling
-          return ' && ' + lookup;
+          return ' != null && ' + lookup;
         }
       }, true);
     }
diff --git a/spec/regressions.js b/spec/regressions.js
index 214a14235..c633a212d 100644
--- a/spec/regressions.js
+++ b/spec/regressions.js
@@ -122,6 +122,10 @@ describe('Regressions', function() {
     shouldCompileTo('{{#foo}} This is {{bar}} ~ {{/foo}}', {foo: 0, bar: 'OK'}, ' This is  ~ ');
   });
 
+  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([ new Handlebars.AST.ContentNode("Hello")]))(), 'Hello');

From 271106d43fae96fc1287898568d000b871f19084 Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Sat, 12 Jul 2014 12:50:54 -0500
Subject: [PATCH 18/91] Do not lookup pathed helpers on the helper stack

Fixes #764
---
 lib/handlebars/compiler/compiler.js            |  2 +-
 lib/handlebars/compiler/javascript-compiler.js |  9 ++++-----
 spec/helpers.js                                | 16 ++++++++++++++++
 3 files changed, 21 insertions(+), 6 deletions(-)

diff --git a/lib/handlebars/compiler/compiler.js b/lib/handlebars/compiler/compiler.js
index fbbb390b5..3e83a62e6 100644
--- a/lib/handlebars/compiler/compiler.js
+++ b/lib/handlebars/compiler/compiler.js
@@ -280,7 +280,7 @@ Compiler.prototype = {
       id.falsy = true;
 
       this.ID(id);
-      this.opcode('invokeHelper', params.length, id.original, sexpr.isRoot);
+      this.opcode('invokeHelper', params.length, id.original, id.isSimple, sexpr.isRoot);
     }
   },
 
diff --git a/lib/handlebars/compiler/javascript-compiler.js b/lib/handlebars/compiler/javascript-compiler.js
index 2d4b11569..c4f3c2758 100644
--- a/lib/handlebars/compiler/javascript-compiler.js
+++ b/lib/handlebars/compiler/javascript-compiler.js
@@ -369,7 +369,7 @@ JavaScriptCompiler.prototype = {
           return ' != null ? ' + lookup + ' : ' + current;
         } else {
           // Otherwise we can use generic falsy handling
-          return ' != null && ' + lookup;
+          return (falsy ? ' && ' : ' != null && ') + lookup;
         }
       }, true);
     }
@@ -527,19 +527,18 @@ JavaScriptCompiler.prototype = {
   // and pushes the helper's return value onto the stack.
   //
   // If the helper is not found, `helperMissing` is called.
-  invokeHelper: function(paramSize, name, isRoot) {
+  invokeHelper: function(paramSize, name, isSimple, isRoot) {
     this.aliases.helperMissing = 'helpers.helperMissing';
-    this.useRegister('helper');
 
     var nonHelper = this.popStack();
     var helper = this.setupHelper(paramSize, name);
 
-    var lookup = 'helper = ' + helper.name + ' || ' + nonHelper + ' || helperMissing';
+    var lookup = (isSimple ? helper.name + ' || ' : '') + nonHelper + ' || helperMissing';
     if (helper.paramsInit) {
       lookup += ',' + helper.paramsInit;
     }
 
-    this.push('(' + lookup + ',helper.call(' + helper.callParams + '))');
+    this.push('((' + lookup + ').call(' + helper.callParams + '))');
 
     // Always flush subexpressions. This is both to prevent the compounding size issue that
     // occurs when the code has to be duplicated for inlining and also to prevent errors
diff --git a/spec/helpers.js b/spec/helpers.js
index 6ba368e4d..d686b6461 100644
--- a/spec/helpers.js
+++ b/spec/helpers.js
@@ -158,6 +158,22 @@ describe('helpers', function() {
     shouldCompileTo(messageString, [rootMessage, { list: list }], "<p>Nobody&#x27;s here</p>", "the context of an inverse is the parent of the block");
   });
 
+  it('pathed lambas with parameters', function() {
+    var hash = {
+      helper: function() {
+        return 'winning';
+      }
+    };
+    hash.hash = hash;
+    var helpers = {
+      './helper': function() {
+        return 'fail';
+      }
+    };
+    shouldCompileTo('{{./helper 1}}', [hash, helpers], 'winning');
+    shouldCompileTo('{{hash/helper 1}}', [hash, helpers], 'winning');
+  });
+
   describe("helpers hash", function() {
     it("providing a helpers hash", function() {
       shouldCompileTo("Goodbye {{cruel}} {{world}}!", [{cruel: "cruel"}, {world: function() { return "world"; }}], "Goodbye cruel world!",

From 2bdfd2a31d029355cc1aca6349d2b318720c1e81 Mon Sep 17 00:00:00 2001
From: Dan Manges <dan@fractalsurfing.com>
Date: Wed, 16 Jul 2014 19:37:01 +0000
Subject: [PATCH 19/91] Support inverse sections on the with helper

---
 lib/handlebars/base.js | 2 ++
 spec/builtins.js       | 4 ++++
 2 files changed, 6 insertions(+)

diff --git a/lib/handlebars/base.js b/lib/handlebars/base.js
index 9b910c7f1..022417b41 100644
--- a/lib/handlebars/base.js
+++ b/lib/handlebars/base.js
@@ -185,6 +185,8 @@ function registerDefaultHelpers(instance) {
       }
 
       return fn(context, options);
+    } else {
+      return options.inverse(this);
     }
   });
 
diff --git a/spec/builtins.js b/spec/builtins.js
index a28f40003..77f37012e 100644
--- a/spec/builtins.js
+++ b/spec/builtins.js
@@ -44,6 +44,10 @@ describe('builtin helpers', function() {
       var string = "{{#with person}}{{first}} {{last}}{{/with}}";
       shouldCompileTo(string, {person: function() { return {first: "Alan", last: "Johnson"};}}, "Alan Johnson");
     });
+    it("with with else", function() {
+      var string = "{{#with person}}Person is present{{else}}Person is not present{{/with}}";
+      shouldCompileTo(string, {}, "Person is not present");
+    });
   });
 
   describe('#each', function() {

From e11c1beb720fac57f2413e916028a959d3c520b7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Martin=20Mu=C3=B1oz?= <im.mmun@gmail.com>
Date: Tue, 29 Jul 2014 02:22:35 -0400
Subject: [PATCH 20/91] Lock mocha dependency to ~1.20.0

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index b8b3b3bca..ce1239872 100644
--- a/package.json
+++ b/package.json
@@ -46,7 +46,7 @@
     "es6-module-packager": "1.x",
     "jison": "~0.3.0",
     "keen.io": "0.0.3",
-    "mocha": "*",
+    "mocha": "~1.20.0",
     "mustache": "~0.7.2",
     "semver": "~2.1.0",
     "underscore": "~1.5.1"

From eee2c4d4f29e233280907bc89a32556de66fe783 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Martin=20Mu=C3=B1oz?= <im.mmun@gmail.com>
Date: Mon, 28 Jul 2014 18:56:15 -0400
Subject: [PATCH 21/91] Refactor blocks, programs and inverses

---
 lib/handlebars/compiler/ast.js     | 50 +++---------------------
 lib/handlebars/compiler/base.js    | 11 +++++-
 lib/handlebars/compiler/helpers.js | 41 ++++++++++++++++++++
 spec/ast.js                        | 47 +++++++---------------
 spec/parser.js                     |  4 +-
 spec/tokenizer.js                  |  8 ++--
 src/handlebars.l                   |  2 +
 src/handlebars.yy                  | 62 +++++++++++++-----------------
 8 files changed, 103 insertions(+), 122 deletions(-)
 create mode 100644 lib/handlebars/compiler/helpers.js

diff --git a/lib/handlebars/compiler/ast.js b/lib/handlebars/compiler/ast.js
index e388e54e1..5a3057fdb 100644
--- a/lib/handlebars/compiler/ast.js
+++ b/lib/handlebars/compiler/ast.js
@@ -1,6 +1,6 @@
 import Exception from "../exception";
 
-function LocationInfo(locInfo){
+function LocationInfo(locInfo) {
   locInfo = locInfo || {};
   this.firstLine   = locInfo.first_line;
   this.firstColumn = locInfo.first_column;
@@ -9,38 +9,11 @@ function LocationInfo(locInfo){
 }
 
 var AST = {
-  ProgramNode: function(statements, inverseStrip, inverse, locInfo) {
-    var inverseLocationInfo, firstInverseNode;
-    if (arguments.length === 3) {
-      locInfo = inverse;
-      inverse = null;
-    } else if (arguments.length === 2) {
-      locInfo = inverseStrip;
-      inverseStrip = null;
-    }
-
+  ProgramNode: function(statements, strip, locInfo) {
     LocationInfo.call(this, locInfo);
     this.type = "program";
     this.statements = statements;
-    this.strip = {};
-
-    if(inverse) {
-      firstInverseNode = inverse[0];
-      if (firstInverseNode) {
-        inverseLocationInfo = {
-          first_line: firstInverseNode.firstLine,
-          last_line: firstInverseNode.lastLine,
-          last_column: firstInverseNode.lastColumn,
-          first_column: firstInverseNode.firstColumn
-        };
-        this.inverse = new AST.ProgramNode(inverse, inverseStrip, inverseLocationInfo);
-      } else {
-        this.inverse = new AST.ProgramNode(inverse, inverseStrip);
-      }
-      this.strip.right = inverseStrip.left;
-    } else if (inverseStrip) {
-      this.strip.left = inverseStrip.right;
-    }
+    this.strip = strip;
   },
 
   MustacheNode: function(rawParams, hash, open, strip, locInfo) {
@@ -106,25 +79,14 @@ var AST = {
     this.strip = strip;
   },
 
-  BlockNode: function(mustache, program, inverse, close, locInfo) {
+  BlockNode: function(mustache, program, inverse, strip, locInfo) {
     LocationInfo.call(this, locInfo);
 
-    if(mustache.sexpr.id.original !== close.path.original) {
-      throw new Exception(mustache.sexpr.id.original + " doesn't match " + close.path.original, this);
-    }
-
     this.type = 'block';
     this.mustache = mustache;
     this.program  = program;
     this.inverse  = inverse;
-
-    this.strip = {
-      left: mustache.strip.left,
-      right: close.strip.right
-    };
-
-    (program || inverse).strip.left = mustache.strip.right;
-    (inverse || program).strip.right = close.strip.left;
+    this.strip = strip;
 
     if (inverse && !program) {
       this.isInverse = true;
@@ -142,7 +104,7 @@ var AST = {
 
     this.type = 'block';
     this.mustache = mustache;
-    this.program = new AST.ProgramNode([content], locInfo);
+    this.program = new AST.ProgramNode([content], {}, locInfo);
   },
 
   ContentNode: function(string, locInfo) {
diff --git a/lib/handlebars/compiler/base.js b/lib/handlebars/compiler/base.js
index 722f09af5..7ab184362 100644
--- a/lib/handlebars/compiler/base.js
+++ b/lib/handlebars/compiler/base.js
@@ -1,12 +1,19 @@
 import parser from "./parser";
 import AST from "./ast";
+import { stripFlags, prepareBlock } from "./helpers";
 
 export { parser };
 
 export function parse(input) {
   // Just return if an already-compile AST was passed in.
-  if(input.constructor === AST.ProgramNode) { return input; }
+  if (input.constructor === AST.ProgramNode) { return input; }
+
+  for (var key in AST) {
+    parser.yy[key] = AST[key];
+  }
+
+  parser.yy.stripFlags = stripFlags;
+  parser.yy.prepareBlock = prepareBlock;
 
-  parser.yy = AST;
   return parser.parse(input);
 }
diff --git a/lib/handlebars/compiler/helpers.js b/lib/handlebars/compiler/helpers.js
new file mode 100644
index 000000000..1a2bd262d
--- /dev/null
+++ b/lib/handlebars/compiler/helpers.js
@@ -0,0 +1,41 @@
+import Exception from "../exception";
+import AST from "./ast";
+
+export function stripFlags(open, close) {
+  return {
+    left: open.charAt(2) === '~',
+    right: close.charAt(close.length-3) === '~'
+  };
+}
+
+export function prepareBlock(mustache, program, inverseAndProgram, close, inverted, locInfo) {
+  if (mustache.sexpr.id.original !== close.path.original) {
+    throw new Exception(mustache.sexpr.id.original + " doesn't match " + close.path.original, mustache);
+  }
+
+  var inverse, strip;
+
+  strip = {
+    left: mustache.strip.left,
+    right: close.strip.right
+  };
+
+  if (inverseAndProgram) {
+    inverse = inverseAndProgram.program;
+    var inverseStrip = inverseAndProgram.strip;
+
+    program.strip.left = mustache.strip.right;
+    program.strip.right = inverseStrip.left;
+    inverse.strip.left = inverseStrip.right;
+    inverse.strip.right = close.strip.left;
+  } else {
+    program.strip.left = mustache.strip.right;
+    program.strip.right = close.strip.left;
+  }
+
+  if (inverted) {
+    return new AST.BlockNode(mustache, inverse, program, strip, locInfo);
+  } else {
+    return new AST.BlockNode(mustache, program, inverse, strip, locInfo);
+  }
+}
diff --git a/spec/ast.js b/spec/ast.js
index 17a982c41..f703054d1 100644
--- a/spec/ast.js
+++ b/spec/ast.js
@@ -68,17 +68,9 @@ describe('ast', function() {
     });
   });
   describe('BlockNode', function() {
-    it('should throw on mustache mismatch (old sexpr-less version)', function() {
-      shouldThrow(function() {
-        var mustacheNode = new handlebarsEnv.AST.MustacheNode([{ original: 'foo'}], null, '{{', {});
-        new handlebarsEnv.AST.BlockNode(mustacheNode, {}, {}, {path: {original: 'bar'}});
-      }, Handlebars.Exception, "foo doesn't match bar");
-    });
     it('should throw on mustache mismatch', function() {
       shouldThrow(function() {
-        var sexprNode = new handlebarsEnv.AST.SexprNode([{ original: 'foo'}], null);
-        var mustacheNode = new handlebarsEnv.AST.MustacheNode(sexprNode, null, '{{', {});
-        new handlebarsEnv.AST.BlockNode(mustacheNode, {}, {}, {path: {original: 'bar'}}, {first_line: 2, first_column: 2});
+        handlebarsEnv.parse("\n  {{#foo}}{{/bar}}")
       }, Handlebars.Exception, "foo doesn't match bar - 2:2");
     });
 
@@ -197,32 +189,12 @@ describe('ast', function() {
       testLocationInfoStorage(pn);
     });
   });
+
   describe("ProgramNode", function(){
 
-    describe("storing location info", function(){
-      it("stores when `inverse` argument isn't passed", function(){
-        var pn = new handlebarsEnv.AST.ProgramNode([], LOCATION_INFO);
-        testLocationInfoStorage(pn);
-      });
-
-      it("stores when `inverse` or `stripInverse` arguments passed", function(){
-        var pn = new handlebarsEnv.AST.ProgramNode([], {strip: {}}, undefined, LOCATION_INFO);
-        testLocationInfoStorage(pn);
-
-        var clone = {
-          strip: {},
-          firstLine: 0,
-          lastLine: 0,
-          firstColumn: 0,
-          lastColumn: 0
-        };
-        pn = new handlebarsEnv.AST.ProgramNode([], {strip: {}}, [ clone ], LOCATION_INFO);
-        testLocationInfoStorage(pn);
-
-        // Assert that the newly created ProgramNode has the same location
-        // information as the inverse
-        testLocationInfoStorage(pn.inverse);
-      });
+    it("storing location info", function(){
+      var pn = new handlebarsEnv.AST.ProgramNode([], {}, LOCATION_INFO);
+      testLocationInfoStorage(pn);
     });
   });
 
@@ -265,11 +237,18 @@ describe('ast', function() {
        testColumns(blockHelperNode, 3, 7, 8, 23);
      });
 
+     it('correctly records the line numbers the program of a block helper', function(){
+       var blockHelperNode = statements[5],
+           program = blockHelperNode.program;
+
+       testColumns(program, 3, 5, 8, 5);
+     });
+
      it('correctly records the line numbers of an inverse of a block helper', function(){
        var blockHelperNode = statements[5],
            inverse = blockHelperNode.inverse;
 
-       testColumns(inverse, 5, 6, 13, 0);
+       testColumns(inverse, 5, 7, 5, 0);
      });
   });
 });
diff --git a/spec/parser.js b/spec/parser.js
index ebde17196..34f6a1dc5 100644
--- a/spec/parser.js
+++ b/spec/parser.js
@@ -121,11 +121,11 @@ describe('parser', function() {
   });
 
   it('parses empty blocks with empty inverse section', function() {
-    equals(ast_for("{{#foo}}{{^}}{{/foo}}"), "BLOCK:\n  {{ ID:foo [] }}\n  PROGRAM:\n");
+    equals(ast_for("{{#foo}}{{^}}{{/foo}}"), "BLOCK:\n  {{ ID:foo [] }}\n  PROGRAM:\n  {{^}}\n");
   });
 
   it('parses empty blocks with empty inverse (else-style) section', function() {
-    equals(ast_for("{{#foo}}{{else}}{{/foo}}"), "BLOCK:\n  {{ ID:foo [] }}\n  PROGRAM:\n");
+    equals(ast_for("{{#foo}}{{else}}{{/foo}}"), "BLOCK:\n  {{ ID:foo [] }}\n  PROGRAM:\n  {{^}}\n");
   });
 
   it('parses non-empty blocks with empty inverse section', function() {
diff --git a/spec/tokenizer.js b/spec/tokenizer.js
index 36a632b8a..0f0dc0b88 100644
--- a/spec/tokenizer.js
+++ b/spec/tokenizer.js
@@ -237,10 +237,10 @@ describe('Tokenizer', function() {
     shouldMatchTokens(result, ['OPEN_BLOCK', 'ID', 'CLOSE', 'CONTENT', 'OPEN_ENDBLOCK', 'ID', 'CLOSE']);
   });
 
-  it('tokenizes inverse sections as "OPEN_INVERSE CLOSE"', function() {
-    shouldMatchTokens(tokenize("{{^}}"), ['OPEN_INVERSE', 'CLOSE']);
-    shouldMatchTokens(tokenize("{{else}}"), ['OPEN_INVERSE', 'CLOSE']);
-    shouldMatchTokens(tokenize("{{ else }}"), ['OPEN_INVERSE', 'CLOSE']);
+  it('tokenizes inverse sections as "INVERSE"', function() {
+    shouldMatchTokens(tokenize("{{^}}"), ['INVERSE']);
+    shouldMatchTokens(tokenize("{{else}}"), ['INVERSE']);
+    shouldMatchTokens(tokenize("{{ else }}"), ['INVERSE']);
   });
 
   it('tokenizes inverse sections with ID as "OPEN_INVERSE ID CLOSE"', function() {
diff --git a/src/handlebars.l b/src/handlebars.l
index cafdd72c7..006f2c71c 100644
--- a/src/handlebars.l
+++ b/src/handlebars.l
@@ -75,6 +75,8 @@ ID    [^\s!"#%-,\.\/;->@\[-\^`\{-~]+/{LOOKAHEAD}
 <mu>"{{"{LEFT_STRIP}?">"         return 'OPEN_PARTIAL';
 <mu>"{{"{LEFT_STRIP}?"#"         return 'OPEN_BLOCK';
 <mu>"{{"{LEFT_STRIP}?"/"         return 'OPEN_ENDBLOCK';
+<mu>"{{"{LEFT_STRIP}?"^"\s*{RIGHT_STRIP}?"}}"        this.popState(); return 'INVERSE';
+<mu>"{{"{LEFT_STRIP}?\s*"else"\s*{RIGHT_STRIP}?"}}"  this.popState(); return 'INVERSE';
 <mu>"{{"{LEFT_STRIP}?"^"         return 'OPEN_INVERSE';
 <mu>"{{"{LEFT_STRIP}?\s*"else"   return 'OPEN_INVERSE';
 <mu>"{{"{LEFT_STRIP}?"{"         return 'OPEN_UNESCAPED';
diff --git a/src/handlebars.yy b/src/handlebars.yy
index 51796ec6d..112c1adfb 100644
--- a/src/handlebars.yy
+++ b/src/handlebars.yy
@@ -2,78 +2,68 @@
 
 %ebnf
 
-%{
-
-function stripFlags(open, close) {
-  return {
-    left: open.charAt(2) === '~',
-    right: close.charAt(0) === '~' || close.charAt(1) === '~'
-  };
-}
-
-%}
-
 %%
 
 root
-  : statements EOF { return new yy.ProgramNode($1, @$); }
-  | EOF { return new yy.ProgramNode([], @$); }
+  : program EOF { return $1; }
   ;
 
 program
-  : simpleInverse statements -> new yy.ProgramNode([], $1, $2, @$)
-  | statements simpleInverse statements -> new yy.ProgramNode($1, $2, $3, @$)
-  | statements simpleInverse -> new yy.ProgramNode($1, $2, [], @$)
-  | statements -> new yy.ProgramNode($1, @$)
-  | simpleInverse -> new yy.ProgramNode([], @$)
-  | "" -> new yy.ProgramNode([], @$)
-  ;
-
-statements
-  : statement -> [$1]
-  | statements statement { $1.push($2); $$ = $1; }
+  : statement* -> new yy.ProgramNode($1, {}, @$)
   ;
 
 statement
-  : openRawBlock CONTENT END_RAW_BLOCK -> new yy.RawBlockNode($1, $2, $3, @$)
-  | openInverse program closeBlock -> new yy.BlockNode($1, $2.inverse, $2, $3, @$)
-  | openBlock program closeBlock -> new yy.BlockNode($1, $2, $2.inverse, $3, @$)
-  | mustache -> $1
+  : mustache -> $1
+  | block -> $1
+  | rawBlock -> $1
   | partial -> $1
   | CONTENT -> new yy.ContentNode($1, @$)
   | COMMENT -> new yy.CommentNode($1, @$)
   ;
 
+rawBlock
+  : openRawBlock CONTENT END_RAW_BLOCK -> new yy.RawBlockNode($1, $2, $3, @$)
+  ;
+
 openRawBlock
   : OPEN_RAW_BLOCK sexpr CLOSE_RAW_BLOCK -> new yy.MustacheNode($2, null, '', '', @$)
   ;
 
+block
+  : openBlock program inverseAndProgram? closeBlock -> yy.prepareBlock($1, $2, $3, $4, false, @$)
+  | openInverse program inverseAndProgram? closeBlock -> yy.prepareBlock($1, $2, $3, $4, true, @$)
+  ;
+
 openBlock
-  : OPEN_BLOCK sexpr CLOSE -> new yy.MustacheNode($2, null, $1, stripFlags($1, $3), @$)
+  : OPEN_BLOCK sexpr CLOSE -> new yy.MustacheNode($2, null, $1, yy.stripFlags($1, $3), @$)
   ;
 
 openInverse
-  : OPEN_INVERSE sexpr CLOSE -> new yy.MustacheNode($2, null, $1, stripFlags($1, $3), @$)
+  : OPEN_INVERSE sexpr CLOSE -> new yy.MustacheNode($2, null, $1, yy.stripFlags($1, $3), @$)
+  ;
+
+inverseAndProgram
+  : INVERSE program -> { strip: yy.stripFlags($1, $1), program: $2 }
   ;
 
 closeBlock
-  : OPEN_ENDBLOCK path CLOSE -> {path: $2, strip: stripFlags($1, $3)}
+  : OPEN_ENDBLOCK path 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 sexpr CLOSE -> new yy.MustacheNode($2, null, $1, stripFlags($1, $3), @$)
-  | OPEN_UNESCAPED sexpr CLOSE_UNESCAPED -> new yy.MustacheNode($2, null, $1, stripFlags($1, $3), @$)
+  : OPEN sexpr CLOSE -> new yy.MustacheNode($2, null, $1, yy.stripFlags($1, $3), @$)
+  | OPEN_UNESCAPED sexpr CLOSE_UNESCAPED -> new yy.MustacheNode($2, null, $1, yy.stripFlags($1, $3), @$)
   ;
 
 partial
-  : OPEN_PARTIAL partialName param hash? CLOSE -> new yy.PartialNode($2, $3, $4, stripFlags($1, $5), @$)
-  | OPEN_PARTIAL partialName hash? CLOSE -> new yy.PartialNode($2, undefined, $3, stripFlags($1, $4), @$)
+  : OPEN_PARTIAL partialName param hash? CLOSE -> new yy.PartialNode($2, $3, $4, yy.stripFlags($1, $5), @$)
+  | OPEN_PARTIAL partialName hash? CLOSE -> new yy.PartialNode($2, undefined, $3, yy.stripFlags($1, $4), @$)
   ;
 
 simpleInverse
-  : OPEN_INVERSE CLOSE -> stripFlags($1, $2)
+  : INVERSE -> yy.stripFlags($1, $1)
   ;
 
 sexpr

From 11f85089ccd2a6628f7699dfccab97f990cd2d75 Mon Sep 17 00:00:00 2001
From: Thaddee Tyl <thaddee.tyl@gmail.com>
Date: Fri, 8 Aug 2014 17:23:38 +0200
Subject: [PATCH 22/91] Readme: Use an SVG badge.

Using the up-to-date badge design.
---
 README.markdown | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/README.markdown b/README.markdown
index 87b17aa7d..45f630346 100644
--- a/README.markdown
+++ b/README.markdown
@@ -1,4 +1,4 @@
-[![Travis Build Status](https://travis-ci.org/wycats/handlebars.js.png?branch=master)](https://travis-ci.org/wycats/handlebars.js)
+[![Travis Build Status](https://img.shields.io/travis/wycats/handlebars.js/master.svg)](https://travis-ci.org/wycats/handlebars.js)
 [![Selenium Test Status](https://saucelabs.com/buildstatus/handlebars)](https://saucelabs.com/u/handlebars)
 
 Handlebars.js

From 905f99b18c7bb6bfe1cf1db3aee5e1e34c431372 Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Mon, 11 Aug 2014 19:43:29 -0500
Subject: [PATCH 23/91] Minor test cleanup

---
 spec/blocks.js     | 2 +-
 spec/env/common.js | 4 ++--
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/spec/blocks.js b/spec/blocks.js
index 8f7c242fa..718d31681 100644
--- a/spec/blocks.js
+++ b/spec/blocks.js
@@ -1,4 +1,4 @@
-/*global CompilerContext, shouldCompileTo */
+/*global CompilerContext, shouldCompileTo, shouldThrow */
 describe('blocks', function() {
   it("array", function() {
     var string   = "{{#goodbyes}}{{text}}! {{/goodbyes}}cruel {{world}}!";
diff --git a/spec/env/common.js b/spec/env/common.js
index 53bf977da..45687ba0b 100644
--- a/spec/env/common.js
+++ b/spec/env/common.js
@@ -5,7 +5,7 @@ global.shouldCompileTo = function(string, hashOrArray, expected, message) {
 global.shouldCompileToWithPartials = function(string, hashOrArray, partials, expected, message) {
   var result = compileWithPartials(string, hashOrArray, partials);
   if (result !== expected) {
-    throw new Error("'" + expected + "' should === '" + result + "': " + message);
+    throw new Error("'" + result + "' should === '" + expected + "': " + message);
   }
 };
 
@@ -25,7 +25,7 @@ global.compileWithPartials = function(string, hashOrArray, partials) {
 
 global.equals = global.equal = function(a, b, msg) {
   if (a !== b) {
-    throw new Error("'" + b + "' should === '" + a + "'" + (msg ? ": " + msg : ''));
+    throw new Error("'" + a + "' should === '" + b + "'" + (msg ? ": " + msg : ''));
   }
 };
 

From 248ad1acaece4178f8273e4a8e5a330306878fed Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Mon, 11 Aug 2014 20:01:06 -0500
Subject: [PATCH 24/91] Fix type in README

Fixes #843
---
 README.markdown | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/README.markdown b/README.markdown
index 87b17aa7d..b9c44e017 100644
--- a/README.markdown
+++ b/README.markdown
@@ -291,9 +291,8 @@ name sans the extension. These templates may be executed in the same
 manner as templates.
 
 If using the simple mode the precompiler will generate a single
-javascript method. To execute this method it must be passed to the using
-the `Handlebars.template` method and the resulting object may be as
-normal.
+javascript method. To execute this method it must be passed to
+the `Handlebars.template` method and the resulting object may be used as normal.
 
 ### Optimizations
 

From 63c5eb7a82802b2e955c6e8423000c9db3fec1a7 Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Sat, 12 Jul 2014 13:06:32 -0500
Subject: [PATCH 25/91] Remove unused JSCompiler.register method

---
 lib/handlebars/compiler/javascript-compiler.js | 5 -----
 1 file changed, 5 deletions(-)

diff --git a/lib/handlebars/compiler/javascript-compiler.js b/lib/handlebars/compiler/javascript-compiler.js
index c4f3c2758..bc9d3c18c 100644
--- a/lib/handlebars/compiler/javascript-compiler.js
+++ b/lib/handlebars/compiler/javascript-compiler.js
@@ -703,11 +703,6 @@ JavaScriptCompiler.prototype = {
     return (depths.length === 0 ? 'this.program(' : 'this.programWithDepth(') + programParams.join(', ') + ')';
   },
 
-  register: function(name, val) {
-    this.useRegister(name);
-    this.pushSource(name + " = " + val + ";");
-  },
-
   useRegister: function(name) {
     if(!this.registers[name]) {
       this.registers[name] = true;

From f4af8e06b68be0a46ffbefe139b1ce8bd25660c8 Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Sat, 12 Jul 2014 13:48:06 -0500
Subject: [PATCH 26/91] Allow compat flag in test specs

---
 spec/env/common.js | 9 +++++++--
 1 file changed, 7 insertions(+), 2 deletions(-)

diff --git a/spec/env/common.js b/spec/env/common.js
index 45687ba0b..03eef93e2 100644
--- a/spec/env/common.js
+++ b/spec/env/common.js
@@ -1,3 +1,4 @@
+/*global CompilerContext, compileWithPartials, shouldCompileToWithPartials */
 global.shouldCompileTo = function(string, hashOrArray, expected, message) {
   shouldCompileToWithPartials(string, hashOrArray, false, expected, message);
 };
@@ -10,15 +11,19 @@ global.shouldCompileToWithPartials = function(string, hashOrArray, partials, exp
 };
 
 global.compileWithPartials = function(string, hashOrArray, partials) {
-  var template = CompilerContext[partials ? 'compileWithPartial' : 'compile'](string), ary;
+  var template,
+      ary,
+      options;
   if(Object.prototype.toString.call(hashOrArray) === "[object Array]") {
     ary = [];
     ary.push(hashOrArray[0]);
-    ary.push({ helpers: hashOrArray[1], partials: hashOrArray[2] });
+    ary.push({ helpers: hashOrArray[1], partials: hashOrArray[2], compat: hashOrArray[3] });
+    options = {compat: hashOrArray[3]};
   } else {
     ary = [hashOrArray];
   }
 
+  template = CompilerContext[partials ? 'compileWithPartial' : 'compile'](string, options);
   return template.apply(this, ary);
 };
 

From 6efcaaf8d10b76af2646cbf72200172d42f6772a Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Tue, 12 Aug 2014 14:32:19 -0500
Subject: [PATCH 27/91] Update to latest saucelabs lib

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index ce1239872..ea8ca56b3 100644
--- a/package.json
+++ b/package.json
@@ -42,7 +42,7 @@
     "grunt-contrib-requirejs": "~0.4.1",
     "grunt-contrib-uglify": "~0.2.2",
     "grunt-contrib-watch": "~0.5.3",
-    "grunt-saucelabs": "~5.0.1",
+    "grunt-saucelabs": "8.x",
     "es6-module-packager": "1.x",
     "jison": "~0.3.0",
     "keen.io": "0.0.3",

From 4a4dcf16578f28bff42d755da5ece94188f75ba5 Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Sat, 12 Jul 2014 18:50:12 -0500
Subject: [PATCH 28/91] Parse context sections by line

---
 spec/ast.js      | 10 +++++-----
 src/handlebars.l |  4 ++--
 2 files changed, 7 insertions(+), 7 deletions(-)

diff --git a/spec/ast.js b/spec/ast.js
index 17a982c41..c05e45c28 100644
--- a/spec/ast.js
+++ b/spec/ast.js
@@ -251,22 +251,22 @@ describe('ast', function() {
     });
 
     it('gets line numbers correct when newlines appear', function(){
-      var secondContentNode = statements[2];
-      testColumns(secondContentNode, 1, 2, 21, 8);
+      testColumns(statements[2], 1, 2, 21, 0);
+      testColumns(statements[3], 2, 2, 0, 8);
     });
 
     it('gets MustacheNode line numbers correct across newlines', function(){
-      var secondMustacheNode = statements[3];
+      var secondMustacheNode = statements[4];
       testColumns(secondMustacheNode, 2, 2, 8, 22);
      });
 
      it('gets the block helper information correct', function(){
-       var blockHelperNode = statements[5];
+       var blockHelperNode = statements[7];
        testColumns(blockHelperNode, 3, 7, 8, 23);
      });
 
      it('correctly records the line numbers of an inverse of a block helper', function(){
-       var blockHelperNode = statements[5],
+       var blockHelperNode = statements[7],
            inverse = blockHelperNode.inverse;
 
        testColumns(inverse, 5, 6, 13, 0);
diff --git a/src/handlebars.l b/src/handlebars.l
index cafdd72c7..0a531d5e1 100644
--- a/src/handlebars.l
+++ b/src/handlebars.l
@@ -28,7 +28,7 @@ ID    [^\s!"#%-,\.\/;->@\[-\^`\{-~]+/{LOOKAHEAD}
 
 %%
 
-[^\x00]*?/("{{")                 {
+[^\x00\n]*?\n?/("{{")           {
                                    if(yytext.slice(-2) === "\\\\") {
                                      strip(0,1);
                                      this.begin("mu");
@@ -41,7 +41,7 @@ ID    [^\s!"#%-,\.\/;->@\[-\^`\{-~]+/{LOOKAHEAD}
                                    if(yytext) return 'CONTENT';
                                  }
 
-[^\x00]+                         return 'CONTENT';
+([^\x00\n]+\n?|\n)               return 'CONTENT';
 
 // marks CONTENT up to the next mustache or escaped mustache
 <emu>[^\x00]{2,}?/("{{"|"\\{{"|"\\\\{{"|<<EOF>>) {

From 60701790099d3c4b30c32d498b05c46fa323df30 Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Sun, 13 Jul 2014 07:24:52 -0500
Subject: [PATCH 29/91] Track root status in ProgramNode constructor

---
 lib/handlebars/compiler/ast.js | 12 ++++++------
 spec/ast.js                    |  8 ++++----
 spec/parser.js                 |  2 +-
 spec/regressions.js            |  2 +-
 src/handlebars.yy              | 16 ++++++++--------
 5 files changed, 20 insertions(+), 20 deletions(-)

diff --git a/lib/handlebars/compiler/ast.js b/lib/handlebars/compiler/ast.js
index e388e54e1..74f276e6a 100644
--- a/lib/handlebars/compiler/ast.js
+++ b/lib/handlebars/compiler/ast.js
@@ -9,12 +9,12 @@ function LocationInfo(locInfo){
 }
 
 var AST = {
-  ProgramNode: function(statements, inverseStrip, inverse, locInfo) {
+  ProgramNode: function(isRoot, statements, inverseStrip, inverse, locInfo) {
     var inverseLocationInfo, firstInverseNode;
-    if (arguments.length === 3) {
+    if (arguments.length === 4) {
       locInfo = inverse;
       inverse = null;
-    } else if (arguments.length === 2) {
+    } else if (arguments.length === 3) {
       locInfo = inverseStrip;
       inverseStrip = null;
     }
@@ -33,9 +33,9 @@ var AST = {
           last_column: firstInverseNode.lastColumn,
           first_column: firstInverseNode.firstColumn
         };
-        this.inverse = new AST.ProgramNode(inverse, inverseStrip, inverseLocationInfo);
+        this.inverse = new AST.ProgramNode(isRoot, inverse, inverseStrip, inverseLocationInfo);
       } else {
-        this.inverse = new AST.ProgramNode(inverse, inverseStrip);
+        this.inverse = new AST.ProgramNode(isRoot, inverse, inverseStrip);
       }
       this.strip.right = inverseStrip.left;
     } else if (inverseStrip) {
@@ -142,7 +142,7 @@ var AST = {
 
     this.type = 'block';
     this.mustache = mustache;
-    this.program = new AST.ProgramNode([content], locInfo);
+    this.program = new AST.ProgramNode(false, [content], locInfo);
   },
 
   ContentNode: function(string, locInfo) {
diff --git a/spec/ast.js b/spec/ast.js
index c05e45c28..cad4b3054 100644
--- a/spec/ast.js
+++ b/spec/ast.js
@@ -86,7 +86,7 @@ describe('ast', function() {
       var sexprNode = new handlebarsEnv.AST.SexprNode([{ original: 'foo'}], null);
       var mustacheNode = new handlebarsEnv.AST.MustacheNode(sexprNode, null, '{{', {});
       var block = new handlebarsEnv.AST.BlockNode(mustacheNode,
-                                                  {strip: {}}, {strip: {}},
+                                                  {statements: [], strip: {}}, {statements: [], strip: {}},
                                                   {
                                                     strip: {},
                                                     path: {original: 'foo'}
@@ -201,12 +201,12 @@ describe('ast', function() {
 
     describe("storing location info", function(){
       it("stores when `inverse` argument isn't passed", function(){
-        var pn = new handlebarsEnv.AST.ProgramNode([], LOCATION_INFO);
+        var pn = new handlebarsEnv.AST.ProgramNode(false, [], LOCATION_INFO);
         testLocationInfoStorage(pn);
       });
 
       it("stores when `inverse` or `stripInverse` arguments passed", function(){
-        var pn = new handlebarsEnv.AST.ProgramNode([], {strip: {}}, undefined, LOCATION_INFO);
+        var pn = new handlebarsEnv.AST.ProgramNode(false, [], {strip: {}}, undefined, LOCATION_INFO);
         testLocationInfoStorage(pn);
 
         var clone = {
@@ -216,7 +216,7 @@ describe('ast', function() {
           firstColumn: 0,
           lastColumn: 0
         };
-        pn = new handlebarsEnv.AST.ProgramNode([], {strip: {}}, [ clone ], LOCATION_INFO);
+        pn = new handlebarsEnv.AST.ProgramNode(false, [], {strip: {}}, [ clone ], LOCATION_INFO);
         testLocationInfoStorage(pn);
 
         // Assert that the newly created ProgramNode has the same location
diff --git a/spec/parser.js b/spec/parser.js
index ebde17196..ff12cc4ac 100644
--- a/spec/parser.js
+++ b/spec/parser.js
@@ -184,7 +184,7 @@ describe('parser', function() {
 
   describe('externally compiled AST', function() {
     it('can pass through an already-compiled AST', function() {
-      equals(ast_for(new Handlebars.AST.ProgramNode([ new Handlebars.AST.ContentNode("Hello")])), "CONTENT[ \'Hello\' ]\n");
+      equals(ast_for(new Handlebars.AST.ProgramNode(false, [ new Handlebars.AST.ContentNode("Hello")])), "CONTENT[ \'Hello\' ]\n");
     });
   });
 });
diff --git a/spec/regressions.js b/spec/regressions.js
index c633a212d..6a7990ce0 100644
--- a/spec/regressions.js
+++ b/spec/regressions.js
@@ -128,7 +128,7 @@ describe('Regressions', function() {
 
   if (Handlebars.AST) {
     it("can pass through an already-compiled AST via compile/precompile", function() {
-      equal(Handlebars.compile(new Handlebars.AST.ProgramNode([ new Handlebars.AST.ContentNode("Hello")]))(), 'Hello');
+      equal(Handlebars.compile(new Handlebars.AST.ProgramNode(true, [ new Handlebars.AST.ContentNode("Hello")]))(), 'Hello');
     });
 
     it("can pass through an empty string", function() {
diff --git a/src/handlebars.yy b/src/handlebars.yy
index 51796ec6d..fa69f735b 100644
--- a/src/handlebars.yy
+++ b/src/handlebars.yy
@@ -16,17 +16,17 @@ function stripFlags(open, close) {
 %%
 
 root
-  : statements EOF { return new yy.ProgramNode($1, @$); }
-  | EOF { return new yy.ProgramNode([], @$); }
+  : statements EOF { return new yy.ProgramNode(true, $1, @$); }
+  | EOF { return new yy.ProgramNode(true, [], @$); }
   ;
 
 program
-  : simpleInverse statements -> new yy.ProgramNode([], $1, $2, @$)
-  | statements simpleInverse statements -> new yy.ProgramNode($1, $2, $3, @$)
-  | statements simpleInverse -> new yy.ProgramNode($1, $2, [], @$)
-  | statements -> new yy.ProgramNode($1, @$)
-  | simpleInverse -> new yy.ProgramNode([], @$)
-  | "" -> new yy.ProgramNode([], @$)
+  : simpleInverse statements -> new yy.ProgramNode(false, [], $1, $2, @$)
+  | statements simpleInverse statements -> new yy.ProgramNode(false, $1, $2, $3, @$)
+  | statements simpleInverse -> new yy.ProgramNode(false, $1, $2, [], @$)
+  | statements -> new yy.ProgramNode(false, $1, @$)
+  | simpleInverse -> new yy.ProgramNode(false, [], @$)
+  | "" -> new yy.ProgramNode(false, [], @$)
   ;
 
 statements

From 50c657f138d01c90d2dee19c7c15ca81bb0ee165 Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Sun, 13 Jul 2014 08:25:28 -0500
Subject: [PATCH 30/91] Flag standalone nodes in the AST

---
 lib/handlebars/compiler/ast.js | 141 ++++++++++++++++++++++++++++++-
 spec/ast.js                    | 148 +++++++++++++++++++++++++++++++++
 2 files changed, 286 insertions(+), 3 deletions(-)

diff --git a/lib/handlebars/compiler/ast.js b/lib/handlebars/compiler/ast.js
index 74f276e6a..ae5ea63fd 100644
--- a/lib/handlebars/compiler/ast.js
+++ b/lib/handlebars/compiler/ast.js
@@ -41,6 +41,9 @@ var AST = {
     } else if (inverseStrip) {
       this.strip.left = inverseStrip.right;
     }
+
+    // Scan all children to complete the standalone analysis
+    checkStandalone(this, isRoot, statements);
   },
 
   MustacheNode: function(rawParams, hash, open, strip, locInfo) {
@@ -104,6 +107,8 @@ var AST = {
     this.context      = context;
     this.hash = hash;
     this.strip = strip;
+
+    this.strip.inlineStandalone = true;
   },
 
   BlockNode: function(mustache, program, inverse, close, locInfo) {
@@ -118,13 +123,31 @@ var AST = {
     this.program  = program;
     this.inverse  = inverse;
 
+    var firstChild = program || inverse,
+        lastChild = inverse || program;
+
     this.strip = {
       left: mustache.strip.left,
-      right: close.strip.right
+      right: close.strip.right,
+
+      // Determine the standalone candiacy. Basically flag our content as being possibly standalone
+      // so our parent can determine if we actually are standalone
+      openStandalone: isNextWhitespace(firstChild),
+      closeStandalone: isPrevWhitespace(lastChild)
     };
 
-    (program || inverse).strip.left = mustache.strip.right;
-    (inverse || program).strip.right = close.strip.left;
+    // Calculate stripping for any else statements
+    firstChild.strip.left = mustache.strip.right;
+    lastChild.strip.right = close.strip.left;
+
+    // Find standalone else statments
+    if (program && inverse
+        && isPrevWhitespace(program)
+        && isNextWhitespace(inverse)) {
+
+      omitLeft(program);
+      omitRight(inverse);
+    }
 
     if (inverse && !program) {
       this.isInverse = true;
@@ -238,9 +261,121 @@ var AST = {
     LocationInfo.call(this, locInfo);
     this.type = "comment";
     this.comment = comment;
+
+    this.strip = {
+      inlineStandalone: true
+    };
   }
 };
 
+
+function checkStandalone(program, isRoot, statements) {
+  for (var i = 0, l = statements.length; i < l; i++) {
+    var current = statements[i],
+        strip = current.strip;
+
+    if (!strip) {
+      continue;
+    }
+
+    var _isPrevWhitespace = isPrevWhitespace(program, i, isRoot, current.type === 'partial'),
+        _isNextWhitespace = isNextWhitespace(program, i, isRoot);
+    strip.openStandalone = strip.openStandalone && _isPrevWhitespace;
+    strip.closeStandalone = strip.closeStandalone && _isNextWhitespace;
+    strip.inlineStandalone = strip.inlineStandalone && _isPrevWhitespace && _isNextWhitespace;
+
+    if (strip.inlineStandalone) {
+      omitRight(program, i);
+
+      if (omitLeft(program, i)) {
+        // If we are on a standalone node, save the indent info for partials
+        if (current.type === 'partial') {
+          current.indent = statements[i-1].string;
+        }
+      }
+    }
+    if (strip.openStandalone) {
+      omitRight(current.program || current.inverse);
+
+      // Strip out the previous content node if it's whitespace only
+      omitLeft(program, i);
+    }
+    if (strip.closeStandalone) {
+      // Always strip the next node
+      omitRight(program, i);
+
+      omitLeft(current.inverse || current.program);
+    }
+  }
+}
+function isPrevWhitespace(parent, i, isRoot, disallowIndent) {
+  var statements = parent.statements;
+  if (i === undefined) {
+    i = statements.length;
+  }
+
+  // Nodes that end with newlines are considered whitespace (but are special
+  // cased for strip operations)
+  var prev = statements[i-1];
+  if (prev && /\n$/.test(prev.string)) {
+    return true;
+  }
+
+  return checkWhitespace(isRoot, prev, statements[i-2]);
+}
+function isNextWhitespace(parent, i, isRoot) {
+  var statements = parent.statements;
+  if (i === undefined) {
+    i = -1;
+  }
+
+  return checkWhitespace(isRoot, statements[i+1], statements[i+2]);
+}
+function checkWhitespace(isRoot, next1, next2, disallowIndent) {
+  if (!next1) {
+    return isRoot;
+  } else if (next1.type === 'content') {
+    // Check if the previous node is empty or whitespace only
+    if (disallowIndent ? !next1.string : /^[\s]*$/.test(next1.string)) {
+      if (next2) {
+        return next2.type === 'content' || /\n$/.test(next1.string);
+      } else {
+        return isRoot || (next1.string.indexOf('\n') >= 0);
+      }
+    }
+  }
+}
+
+// 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.
+function omitRight(program, i) {
+  var first = program.statements[i == null ? 0 : i + 1];
+  if (first) {
+    first.omit = true;
+  }
+}
+
+// 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.
+function omitLeft(program, i) {
+  var statements = program.statements;
+  if (i === undefined) {
+    i = statements.length;
+  }
+
+  var last = statements[i-1],
+      prev = statements[i-2];
+
+  // We omit the last node if it's whitespace only and not preceeded by a non-content node.
+  if (last && /^[\s]*$/.test(last.string) && (!prev || prev.type === 'content')) {
+    return last.omit = true;
+  }
+}
+
 // Must be exported as an object rather than the root of the module as the jison lexer
 // most modify the object to operate properly.
 export default AST;
diff --git a/spec/ast.js b/spec/ast.js
index cad4b3054..316d4a38b 100644
--- a/spec/ast.js
+++ b/spec/ast.js
@@ -272,5 +272,153 @@ describe('ast', function() {
        testColumns(inverse, 5, 6, 13, 0);
      });
   });
+
+  describe('standalone flags', function(){
+    describe('mustache', function() {
+      it('does not mark mustaches as standalone', function() {
+        var ast = Handlebars.parse('  {{comment}} ');
+        equals(ast.statements[0].omit, undefined);
+        equals(ast.statements[2].omit, undefined);
+      });
+    });
+    describe('blocks', function() {
+      it('marks block mustaches as standalone', function() {
+        var ast = Handlebars.parse(' {{# comment}} \nfoo\n {{else}} \n  bar \n  {{/comment}} '),
+            block = ast.statements[1];
+
+        equals(ast.statements[0].omit, true);
+
+        equals(block.program.statements[0].omit, true);
+        equals(block.program.statements[1].string, 'foo\n');
+        equals(block.program.statements[2].omit, true);
+
+        equals(block.inverse.statements[0].omit, true);
+        equals(block.inverse.statements[1].string, '  bar \n');
+        equals(block.inverse.statements[2].omit, true);
+
+        equals(ast.statements[2].omit, true);
+      });
+      it('marks initial block mustaches as standalone', function() {
+        var ast = Handlebars.parse('{{# comment}} \nfoo\n {{/comment}}'),
+            block = ast.statements[0];
+
+        equals(block.program.statements[0].omit, true);
+        equals(block.program.statements[1].string, 'foo\n');
+        equals(block.program.statements[2].omit, true);
+      });
+      it('marks mustaches with children as standalone', function() {
+        var ast = Handlebars.parse('{{# comment}} \n{{foo}}\n {{/comment}}'),
+            block = ast.statements[0];
+
+        equals(block.program.statements[0].omit, true);
+        equals(block.program.statements[1].id.original, 'foo');
+        equals(block.program.statements[2].omit, undefined);
+        equals(block.program.statements[3].omit, true);
+      });
+      it('marks nested block mustaches as standalone', function() {
+        var ast = Handlebars.parse('{{#foo}} \n{{# comment}} \nfoo\n {{else}} \n  bar \n  {{/comment}} \n{{/foo}}'),
+            statements = ast.statements[0].program.statements,
+            block = statements[1];
+
+        equals(statements[0].omit, true);
+
+        equals(block.program.statements[0].omit, true);
+        equals(block.program.statements[1].string, 'foo\n');
+        equals(block.program.statements[2].omit, true);
+
+        equals(block.inverse.statements[0].omit, true);
+        equals(block.inverse.statements[1].string, '  bar \n');
+        equals(block.inverse.statements[2].omit, true);
+
+        equals(statements[0].omit, true);
+      });
+      it('does not mark nested block mustaches as standalone', function() {
+        var ast = Handlebars.parse('{{#foo}} {{# comment}} \nfoo\n {{else}} \n  bar \n  {{/comment}} {{/foo}}'),
+            statements = ast.statements[0].program.statements,
+            block = statements[1];
+
+        equals(statements[0].omit, undefined);
+
+        equals(block.program.statements[0].omit, undefined);
+        equals(block.program.statements[1].string, 'foo\n');
+        equals(block.program.statements[2].omit, true);
+
+        equals(block.inverse.statements[0].omit, true);
+        equals(block.inverse.statements[1].string, '  bar \n');
+        equals(block.inverse.statements[2].omit, undefined);
+
+        equals(statements[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}}'),
+            statements = ast.statements[0].program.statements,
+            block = statements[0];
+
+        equals(block.program.statements[0].omit, undefined);
+        equals(block.program.statements[1].string, 'foo\n');
+        equals(block.program.statements[2].omit, true);
+
+        equals(block.inverse.statements[0].omit, true);
+        equals(block.inverse.statements[1].string, '  bar \n');
+        equals(block.inverse.statements[2].omit, undefined);
+
+        equals(statements[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.statements[1];
+
+        equals(ast.statements[0].omit, undefined);
+
+        equals(block.program.statements[0].omit, true);
+        equals(block.program.statements[1].string, 'foo\n');
+        equals(block.program.statements[2].omit, true);
+
+        equals(block.inverse.statements[0].omit, true);
+        equals(block.inverse.statements[1].string, '  bar \n');
+        equals(block.inverse.statements[2].omit, true);
+
+        equals(ast.statements[2].omit, true);
+      });
+    });
+    describe('partials', function() {
+      it('marks partial as standalone', function() {
+        var ast = Handlebars.parse('{{> partial }} ');
+        equals(ast.statements[1].omit, true);
+      });
+      it('marks indented partial as standalone', function() {
+        var ast = Handlebars.parse('  {{> partial }} ');
+        equals(ast.statements[0].omit, true);
+        equals(ast.statements[1].indent, '  ');
+        equals(ast.statements[2].omit, true);
+      });
+      it('marks those around content as not standalone', function() {
+        var ast = Handlebars.parse('a{{> partial }}');
+        equals(ast.statements[0].omit, undefined);
+
+        ast = Handlebars.parse('{{> partial }}a');
+        equals(ast.statements[1].omit, undefined);
+      });
+    });
+    describe('comments', function() {
+      it('marks comment as standalone', function() {
+        var ast = Handlebars.parse('{{! comment }} ');
+        equals(ast.statements[1].omit, true);
+      });
+      it('marks indented comment as standalone', function() {
+        var ast = Handlebars.parse('  {{! comment }} ');
+        equals(ast.statements[0].omit, true);
+        equals(ast.statements[2].omit, true);
+      });
+      it('marks those around content as not standalone', function() {
+        var ast = Handlebars.parse('a{{! comment }}');
+        equals(ast.statements[0].omit, undefined);
+
+        ast = Handlebars.parse('{{! comment }}a');
+        equals(ast.statements[1].omit, undefined);
+      });
+    });
+  });
 });
 

From 2a47d66ee9fdf467588f68de48a521ed2a1f59e1 Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Tue, 12 Aug 2014 14:07:13 -0500
Subject: [PATCH 31/91] Drop omitted content tags from generated output

---
 lib/handlebars/compiler/compiler.js |  4 +++-
 spec/blocks.js                      | 11 +++++++++++
 spec/regressions.js                 | 22 ++++++++++++++++++++--
 3 files changed, 34 insertions(+), 3 deletions(-)

diff --git a/lib/handlebars/compiler/compiler.js b/lib/handlebars/compiler/compiler.js
index 3e83a62e6..f823daa6b 100644
--- a/lib/handlebars/compiler/compiler.js
+++ b/lib/handlebars/compiler/compiler.js
@@ -222,7 +222,9 @@ Compiler.prototype = {
   },
 
   content: function(content) {
-    this.opcode('appendContent', content.string);
+    if (!content.omit) {
+      this.opcode('appendContent', content.string);
+    }
   },
 
   mustache: function(mustache) {
diff --git a/spec/blocks.js b/spec/blocks.js
index 718d31681..c57a67eee 100644
--- a/spec/blocks.js
+++ b/spec/blocks.js
@@ -83,4 +83,15 @@ describe('blocks', function() {
         "No people");
     });
   });
+
+  describe('standalone sections', function() {
+    it('block standalone else sections', function() {
+      shouldCompileTo('{{#people}}\n{{name}}\n{{^}}\n{{none}}\n{{/people}}\n', {none: 'No people'},
+        'No people\n');
+      shouldCompileTo('{{#none}}\n{{.}}\n{{^}}\n{{none}}\n{{/none}}\n', {none: 'No people'},
+        'No people\n');
+      shouldCompileTo('\n{{#people}}\n{{name}}\n{{^}}\n{{none}}\n{{/people}}\n', {none: 'No people'},
+        'No people\n');
+    });
+  });
 });
diff --git a/spec/regressions.js b/spec/regressions.js
index 6a7990ce0..dd4eedde1 100644
--- a/spec/regressions.js
+++ b/spec/regressions.js
@@ -24,7 +24,19 @@ describe('Regressions', function() {
   });
 
   it("bug reported by @fat where lambdas weren't being properly resolved", function() {
-    var string = "<strong>This is a slightly more complicated {{thing}}.</strong>.\n{{! Just ignore this business. }}\nCheck this out:\n{{#hasThings}}\n<ul>\n{{#things}}\n<li class={{className}}>{{word}}</li>\n{{/things}}</ul>.\n{{/hasThings}}\n{{^hasThings}}\n\n<small>Nothing to check out...</small>\n{{/hasThings}}";
+    var string = '<strong>This is a slightly more complicated {{thing}}.</strong>.\n'
+        + '{{! Just ignore this business. }}\n'
+        + 'Check this out:\n'
+        + '{{#hasThings}}\n'
+        + '<ul>\n'
+        + '{{#things}}\n'
+        + '<li class={{className}}>{{word}}</li>\n'
+        + '{{/things}}</ul>.\n'
+        + '{{/hasThings}}\n'
+        + '{{^hasThings}}\n'
+        + '\n'
+        + '<small>Nothing to check out...</small>\n'
+        + '{{/hasThings}}';
     var data = {
       thing: function() {
         return "blah";
@@ -39,7 +51,13 @@ describe('Regressions', function() {
       }
     };
 
-    var output = "<strong>This is a slightly more complicated blah.</strong>.\n\nCheck this out:\n\n<ul>\n\n<li class=one>@fat</li>\n\n<li class=two>@dhg</li>\n\n<li class=three>@sayrer</li>\n</ul>.\n\n";
+    var output = '<strong>This is a slightly more complicated blah.</strong>.\n'
+        + 'Check this out:\n'
+        + '<ul>\n'
+        + '<li class=one>@fat</li>\n'
+        + '<li class=two>@dhg</li>\n'
+        + '<li class=three>@sayrer</li>\n'
+        + '</ul>.\n';
     shouldCompileTo(string, data, output);
   });
 

From e93d5166723c9ea7da3cca3e6b2ba524a19e768e Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Tue, 12 Aug 2014 14:08:12 -0500
Subject: [PATCH 32/91] Render indent for standalone partials

---
 lib/handlebars/compiler/compiler.js           |  2 +-
 .../compiler/javascript-compiler.js           |  4 ++--
 lib/handlebars/runtime.js                     | 21 +++++++++++++++----
 3 files changed, 20 insertions(+), 7 deletions(-)

diff --git a/lib/handlebars/compiler/compiler.js b/lib/handlebars/compiler/compiler.js
index f823daa6b..131cb981b 100644
--- a/lib/handlebars/compiler/compiler.js
+++ b/lib/handlebars/compiler/compiler.js
@@ -217,7 +217,7 @@ Compiler.prototype = {
       this.opcode('push', 'depth0');
     }
 
-    this.opcode('invokePartial', partialName.name);
+    this.opcode('invokePartial', partialName.name, partial.indent || '');
     this.opcode('append');
   },
 
diff --git a/lib/handlebars/compiler/javascript-compiler.js b/lib/handlebars/compiler/javascript-compiler.js
index bc9d3c18c..c92cf58ad 100644
--- a/lib/handlebars/compiler/javascript-compiler.js
+++ b/lib/handlebars/compiler/javascript-compiler.js
@@ -596,8 +596,8 @@ JavaScriptCompiler.prototype = {
   //
   // This operation pops off a context, invokes a partial with that context,
   // and pushes the result of the invocation back.
-  invokePartial: function(name) {
-    var params = [this.nameLookup('partials', name, 'partial'), "'" + name + "'", this.popStack(), this.popStack(), "helpers", "partials"];
+  invokePartial: function(name, indent) {
+    var params = [this.nameLookup('partials', name, 'partial'), "'" + indent + "'", "'" + name + "'", this.popStack(), this.popStack(), "helpers", "partials"];
 
     if (this.options.data) {
       params.push("data");
diff --git a/lib/handlebars/runtime.js b/lib/handlebars/runtime.js
index b9fc77d7d..9b61afe2f 100644
--- a/lib/handlebars/runtime.js
+++ b/lib/handlebars/runtime.js
@@ -31,18 +31,31 @@ export function template(templateSpec, env) {
   // for external users to override these as psuedo-supported APIs.
   env.VM.checkRevision(templateSpec.compiler);
 
-  var invokePartialWrapper = function(partial, name, context, hash, helpers, partials, data) {
+  var invokePartialWrapper = function(partial, indent, name, context, hash, helpers, partials, data) {
     if (hash) {
       context = Utils.extend({}, context, hash);
     }
 
     var result = env.VM.invokePartial.call(this, partial, name, context, helpers, partials, data);
-    if (result != null) { return result; }
 
-    if (env.compile) {
+    if (result == null && env.compile) {
       var options = { helpers: helpers, partials: partials, data: data };
       partials[name] = env.compile(partial, { data: data !== undefined }, env);
-      return partials[name](context, options);
+      result = partials[name](context, options);
+    }
+    if (result != null) {
+      if (indent) {
+        var lines = result.split('\n');
+        for (var i = 0, l = lines.length; i < l; i++) {
+          if (!lines[i] && i + 1 === l) {
+            break;
+          }
+
+          lines[i] = indent + lines[i];
+        }
+        result = lines.join('\n');
+      }
+      return result;
     } else {
       throw new Exception("The partial " + name + " could not be compiled when running in runtime-only mode");
     }

From f2a2914d4fb38095105c963086425ba75121ae7b Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Tue, 12 Aug 2014 15:38:50 -0500
Subject: [PATCH 33/91] Add additional tests for standalone partials

---
 spec/partials.js | 20 +++++++++++++++++++-
 1 file changed, 19 insertions(+), 1 deletion(-)

diff --git a/spec/partials.js b/spec/partials.js
index 732436af0..816fd1e82 100644
--- a/spec/partials.js
+++ b/spec/partials.js
@@ -1,4 +1,4 @@
-/*global CompilerContext, shouldCompileTo, shouldCompileToWithPartials */
+/*global CompilerContext, Handlebars, handlebarsEnv, shouldCompileTo, shouldCompileToWithPartials, shouldThrow */
 describe('partials', function() {
   it("basic partials", function() {
     var string = "Dudes: {{#dudes}}{{> dude}}{{/dudes}}";
@@ -137,4 +137,22 @@ describe('partials', function() {
     var partial = "";
     var hash = {dudes: [{name: "Yehuda", url: "http://yehuda"}, {name: "Alan", url: "http://alan"}]};
     shouldCompileToWithPartials(string, [hash, {}, {dude: partial}], true, "Dudes: ");  });
+
+  describe('standalone partials', function() {
+    it("indented partials", function() {
+      var string = "Dudes:\n{{#dudes}}\n  {{>dude}}\n{{/dudes}}";
+      var dude = "{{name}}\n";
+      var hash = {dudes: [{name: "Yehuda", url: "http://yehuda"}, {name: "Alan", url: "http://alan"}]};
+      shouldCompileToWithPartials(string, [hash, {}, {dude: dude}], true,
+            "Dudes:\n  Yehuda\n  Alan\n");
+    });
+    it("nested indented partials", function() {
+      var string = "Dudes:\n{{#dudes}}\n  {{>dude}}\n{{/dudes}}";
+      var dude = "{{name}}\n {{> url}}";
+      var url = "{{url}}!\n";
+      var hash = {dudes: [{name: "Yehuda", url: "http://yehuda"}, {name: "Alan", url: "http://alan"}]};
+      shouldCompileToWithPartials(string, [hash, {}, {dude: dude, url: url}], true,
+            "Dudes:\n  Yehuda\n   http://yehuda!\n  Alan\n   http://alan!\n");
+    });
+  });
 });

From f6dc5ad7163b8b7c51d9816afa39741f47313602 Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Wed, 13 Aug 2014 11:41:27 -0500
Subject: [PATCH 34/91] Create contextName helper for context lookup

---
 lib/handlebars/compiler/compiler.js            |  3 ++-
 lib/handlebars/compiler/javascript-compiler.js | 17 ++++++++++-------
 2 files changed, 12 insertions(+), 8 deletions(-)

diff --git a/lib/handlebars/compiler/compiler.js b/lib/handlebars/compiler/compiler.js
index 131cb981b..48575d4d9 100644
--- a/lib/handlebars/compiler/compiler.js
+++ b/lib/handlebars/compiler/compiler.js
@@ -214,7 +214,8 @@ Compiler.prototype = {
     if (partial.context) {
       this.accept(partial.context);
     } else {
-      this.opcode('push', 'depth0');
+      this.opcode('getContext', 0);
+      this.opcode('pushContext');
     }
 
     this.opcode('invokePartial', partialName.name, partial.indent || '');
diff --git a/lib/handlebars/compiler/javascript-compiler.js b/lib/handlebars/compiler/javascript-compiler.js
index c92cf58ad..0472dbb1a 100644
--- a/lib/handlebars/compiler/javascript-compiler.js
+++ b/lib/handlebars/compiler/javascript-compiler.js
@@ -230,7 +230,7 @@ JavaScriptCompiler.prototype = {
   blockValue: function(name) {
     this.aliases.blockHelperMissing = 'helpers.blockHelperMissing';
 
-    var params = ["depth0"];
+    var params = [this.contextName(0)];
     this.setupParams(name, 0, params);
 
     this.replaceStack(function(current) {
@@ -249,7 +249,7 @@ JavaScriptCompiler.prototype = {
     this.aliases.blockHelperMissing = 'helpers.blockHelperMissing';
 
     // We're being a bit cheeky and reusing the options value from the prior exec
-    var params = ["depth0"];
+    var params = [this.contextName(0)];
     this.setupParams('', 0, params, true);
 
     this.flushInline();
@@ -341,7 +341,7 @@ JavaScriptCompiler.prototype = {
   //
   // Pushes the value of the current context onto the stack.
   pushContext: function() {
-    this.pushStackLiteral('depth' + this.lastContext);
+    this.pushStackLiteral(this.contextName(this.lastContext));
   },
 
   // [lookupOnContext]
@@ -410,7 +410,7 @@ JavaScriptCompiler.prototype = {
   resolvePossibleLambda: function() {
     this.aliases.lambda = 'this.lambda';
 
-    this.push('lambda(' + this.popStack() + ', depth0)');
+    this.push('lambda(' + this.popStack() + ', ' + this.contextName(0) + ')');
   },
 
   // [pushStringParam]
@@ -422,8 +422,7 @@ JavaScriptCompiler.prototype = {
   // provides the string value of a parameter along with its
   // depth rather than resolving it immediately.
   pushStringParam: function(string, type) {
-    this.pushStackLiteral('depth' + this.lastContext);
-
+    this.pushContext();
     this.pushString(type);
 
     // If it's a subexpression, the string result
@@ -847,6 +846,10 @@ JavaScriptCompiler.prototype = {
     }
   },
 
+  contextName: function(context) {
+    return 'depth' + context;
+  },
+
   quotedString: function(str) {
     return '"' + str
       .replace(/\\/g, '\\\\')
@@ -878,7 +881,7 @@ JavaScriptCompiler.prototype = {
       params: params,
       paramsInit: paramsInit,
       name: foundHelper,
-      callParams: ["depth0"].concat(params).join(", ")
+      callParams: [this.contextName(0)].concat(params).join(", ")
     };
   },
 

From b695aaec7c493ae992bfc1560e5153a9eddaa385 Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Wed, 13 Aug 2014 14:11:53 -0500
Subject: [PATCH 35/91] Use depths array rather than passing array args

Approximately doubles the throughput performance of depthed templates and clears the way for doing recursive path lookups in pathed mode.
---
 .../compiler/javascript-compiler.js           | 29 +++++++++-----
 lib/handlebars/runtime.js                     | 40 ++++++++-----------
 2 files changed, 36 insertions(+), 33 deletions(-)

diff --git a/lib/handlebars/compiler/javascript-compiler.js b/lib/handlebars/compiler/javascript-compiler.js
index 0472dbb1a..f1b6d3639 100644
--- a/lib/handlebars/compiler/javascript-compiler.js
+++ b/lib/handlebars/compiler/javascript-compiler.js
@@ -69,6 +69,8 @@ JavaScriptCompiler.prototype = {
 
     this.compileChildren(environment, options);
 
+    this.useDepths = this.useDepths || environment.depths.list.length;
+
     var opcodes = environment.opcodes,
         opcode,
         i,
@@ -115,6 +117,9 @@ JavaScriptCompiler.prototype = {
       if (this.options.data) {
         ret.useData = true;
       }
+      if (this.useDepths) {
+        ret.depths = true;
+      }
 
       if (!asObject) {
         ret.compiler = JSON.stringify(ret.compiler);
@@ -151,8 +156,8 @@ JavaScriptCompiler.prototype = {
 
     var params = ["depth0", "helpers", "partials", "data"];
 
-    for(var i=0, l=this.environment.depths.list.length; i<l; i++) {
-      params.push("depth" + this.environment.depths.list[i]);
+    if (this.useDepths) {
+      params.push('depths');
     }
 
     // Perform a second pass over the output to merge content when possible
@@ -668,6 +673,8 @@ JavaScriptCompiler.prototype = {
         child.name = 'program' + index;
         this.context.programs[index] = compiler.compile(child, options, this.context, !this.precompile);
         this.context.environments[index] = child;
+
+        this.useDepths = this.useDepths || compiler.useDepths;
       } else {
         child.index = index;
         child.name = 'program' + index;
@@ -689,17 +696,17 @@ JavaScriptCompiler.prototype = {
     }
 
     var child = this.environment.children[guid],
-        depths = child.depths.list, depth;
+        depths = child.depths.list,
+        useDepths = this.useDepths,
+        depth;
 
     var programParams = [child.index, 'data'];
 
-    for(var i=0, l = depths.length; i<l; i++) {
-      depth = depths[i];
-
-      programParams.push('depth' + (depth - 1));
+    if (useDepths) {
+      programParams.push('depths');
     }
 
-    return (depths.length === 0 ? 'this.program(' : 'this.programWithDepth(') + programParams.join(', ') + ')';
+    return 'this.program(' + programParams.join(', ') + ')';
   },
 
   useRegister: function(name) {
@@ -847,7 +854,11 @@ JavaScriptCompiler.prototype = {
   },
 
   contextName: function(context) {
-    return 'depth' + context;
+    if (this.useDepths && context) {
+      return 'depths[' + context + ']';
+    } else {
+      return 'depth' + context;
+    }
   },
 
   quotedString: function(str) {
diff --git a/lib/handlebars/runtime.js b/lib/handlebars/runtime.js
index 9b61afe2f..cbd539882 100644
--- a/lib/handlebars/runtime.js
+++ b/lib/handlebars/runtime.js
@@ -75,17 +75,16 @@ export function template(templateSpec, env) {
     },
 
     programs: [],
-    program: function(i, data) {
+    program: function(i, data, depths) {
       var programWrapper = this.programs[i],
           fn = this.fn(i);
-      if(data) {
-        programWrapper = program(this, i, fn, data);
+      if (data || depths) {
+        programWrapper = program(this, i, fn, data, depths);
       } else if (!programWrapper) {
         programWrapper = this.programs[i] = program(this, i, fn);
       }
       return programWrapper;
     },
-    programWithDepth: env.VM.programWithDepth,
 
     data: function(data, depth) {
       while (data && depth--) {
@@ -117,7 +116,12 @@ export function template(templateSpec, env) {
     if (!options.partial && templateSpec.useData) {
       data = initData(context, data);
     }
-    return templateSpec.main.call(container, context, container.helpers, container.partials, data);
+    var depths;
+    if (templateSpec.depths) {
+      depths = [context];
+    }
+
+    return templateSpec.main.call(container, context, container.helpers, container.partials, data, depths);
   };
 
   ret._setup = function(options) {
@@ -134,35 +138,23 @@ export function template(templateSpec, env) {
   };
 
   ret._child = function(i) {
+    if (templateSpec.depth) {
+      throw new Exception('_child can not be used with depthed methods');
+    }
+
     return container.programWithDepth(i);
   };
   return ret;
 }
 
-export function programWithDepth(i, data /*, $depth */) {
-  /*jshint -W040 */
-  var args = Array.prototype.slice.call(arguments, 2),
-      container = this,
-      fn = container.fn(i);
-
-  var prog = function(context, options) {
-    options = options || {};
-
-    return fn.apply(container, [context, container.helpers, container.partials, options.data || data].concat(args));
-  };
-  prog.program = i;
-  prog.depth = args.length;
-  return prog;
-}
-
-export function program(container, i, fn, data) {
+export function program(container, i, fn, data, depths) {
   var prog = function(context, options) {
     options = options || {};
 
-    return fn.call(container, context, container.helpers, container.partials, options.data || data);
+    return fn.call(container, context, container.helpers, container.partials, options.data || data, [context].concat(depths));
   };
   prog.program = i;
-  prog.depth = 0;
+  prog.depth = depths ? depths.length : 0;
   return prog;
 }
 

From 625b00e1dafccc8653bf4cb4dc3dac55f6b3b020 Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Wed, 13 Aug 2014 14:51:33 -0500
Subject: [PATCH 36/91] Avoid depth construction overhead for non-depthed

---
 lib/handlebars/runtime.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lib/handlebars/runtime.js b/lib/handlebars/runtime.js
index cbd539882..530dbd2f8 100644
--- a/lib/handlebars/runtime.js
+++ b/lib/handlebars/runtime.js
@@ -151,7 +151,7 @@ export function program(container, i, fn, data, depths) {
   var prog = function(context, options) {
     options = options || {};
 
-    return fn.call(container, context, container.helpers, container.partials, options.data || data, [context].concat(depths));
+    return fn.call(container, context, container.helpers, container.partials, options.data || data, depths && [context].concat(depths));
   };
   prog.program = i;
   prog.depth = depths ? depths.length : 0;

From c98613b711a70a82a960c20c00d8d92d5eed1024 Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Wed, 13 Aug 2014 20:46:07 -0500
Subject: [PATCH 37/91] Implement recursive field lookup in compat mode

Provides the mustache behavior of recursive lookup without the use of depthed paths.

Note that this does incur a fairly dramatic performance penalty for depthed queries.
---
 bench/throughput.js                           |  8 +++++++
 lib/handlebars/compiler/compiler.js           |  8 ++++++-
 .../compiler/javascript-compiler.js           | 24 +++++++++++++------
 lib/handlebars/runtime.js                     |  8 +++++++
 spec/blocks.js                                | 21 ++++++++++++++++
 spec/env/common.js                            |  2 +-
 6 files changed, 62 insertions(+), 9 deletions(-)

diff --git a/bench/throughput.js b/bench/throughput.js
index 308446a09..d27a94d31 100644
--- a/bench/throughput.js
+++ b/bench/throughput.js
@@ -28,11 +28,13 @@ function makeSuite(bench, name, template, handlebarsOnly) {
       partials = template.partials,
 
       handlebarsOut,
+      compatOut,
       dustOut,
       ecoOut,
       mustacheOut;
 
   var handlebar = Handlebars.compile(template.handlebars, {data: false}),
+      compat =  Handlebars.compile(template.handlebars, {data: false, compat: true}),
       options = {helpers: template.helpers};
   _.each(template.partials && template.partials.handlebars, function(partial, name) {
     Handlebars.registerPartial(name, Handlebars.compile(partial, {data: false}));
@@ -43,6 +45,11 @@ function makeSuite(bench, name, template, handlebarsOnly) {
     handlebar(context, options);
   });
 
+  compatOut = compat(context, options);
+  bench("compat", function() {
+    compat(context, options);
+  });
+
   if (handlebarsOnly) {
     return;
   }
@@ -107,6 +114,7 @@ function makeSuite(bench, name, template, handlebarsOnly) {
     }
   }
 
+  compare(compatOut, 'compat');
   compare(dustOut, 'dust');
   compare(ecoOut, 'eco');
   compare(mustacheOut, 'mustache');
diff --git a/lib/handlebars/compiler/compiler.js b/lib/handlebars/compiler/compiler.js
index 48575d4d9..ffddec432 100644
--- a/lib/handlebars/compiler/compiler.js
+++ b/lib/handlebars/compiler/compiler.js
@@ -308,7 +308,7 @@ Compiler.prototype = {
       // Context reference, i.e. `{{foo .}}` or `{{foo ..}}`
       this.opcode('pushContext');
     } else {
-      this.opcode('lookupOnContext', id.parts, id.falsy);
+      this.opcode('lookupOnContext', id.parts, id.falsy, id.isScoped);
     }
   },
 
@@ -424,6 +424,9 @@ export function precompile(input, options, env) {
   if (!('data' in options)) {
     options.data = true;
   }
+  if (options.compat) {
+    options.useDepths = true;
+  }
 
   var ast = env.parse(input);
   var environment = new env.Compiler().compile(ast, options);
@@ -440,6 +443,9 @@ export function compile(input, options, env) {
   if (!('data' in options)) {
     options.data = true;
   }
+  if (options.compat) {
+    options.useDepths = true;
+  }
 
   var compiled;
 
diff --git a/lib/handlebars/compiler/javascript-compiler.js b/lib/handlebars/compiler/javascript-compiler.js
index f1b6d3639..b9b858dbd 100644
--- a/lib/handlebars/compiler/javascript-compiler.js
+++ b/lib/handlebars/compiler/javascript-compiler.js
@@ -17,6 +17,11 @@ JavaScriptCompiler.prototype = {
       return parent + "['" + name + "']";
     }
   },
+  depthedLookup: function(name) {
+    this.aliases.lookup = 'this.lookup';
+
+    return 'lookup(depths, "' + name + '")';
+  },
 
   compilerInfo: function() {
     var revision = COMPILER_REVISION,
@@ -69,7 +74,7 @@ JavaScriptCompiler.prototype = {
 
     this.compileChildren(environment, options);
 
-    this.useDepths = this.useDepths || environment.depths.list.length;
+    this.useDepths = this.useDepths || environment.depths.list.length || this.options.compat;
 
     var opcodes = environment.opcodes,
         opcode,
@@ -356,15 +361,20 @@ JavaScriptCompiler.prototype = {
   //
   // Looks up the value of `name` on the current context and pushes
   // it onto the stack.
-  lookupOnContext: function(parts, falsy) {
+  lookupOnContext: function(parts, falsy, scoped) {
     /*jshint -W083 */
-    this.pushContext();
-    if (!parts) {
-      return;
+    var i = 0,
+        len = parts.length;
+
+    if (!scoped && this.isChild && this.options.compat && !this.lastContext) {
+      // The depthed query is expected to handle the undefined logic for the root level that
+      // is implemented below, so we evaluate that directly in compat mode
+      this.pushStackLiteral(this.depthedLookup(parts[i++]));
+    } else {
+      this.pushContext();
     }
 
-    var len = parts.length;
-    for (var i = 0; i < len; i++) {
+    for (; i < len; i++) {
       this.replaceStack(function(current) {
         var lookup = this.nameLookup(current, parts[i], 'context');
         // We want to ensure that zero and false are handled properly for the first element
diff --git a/lib/handlebars/runtime.js b/lib/handlebars/runtime.js
index 530dbd2f8..ec54f532f 100644
--- a/lib/handlebars/runtime.js
+++ b/lib/handlebars/runtime.js
@@ -63,6 +63,14 @@ export function template(templateSpec, env) {
 
   // Just add water
   var container = {
+    lookup: function(depths, name) {
+      var len = depths.length;
+      for (var i = 0; i < len; i++) {
+        if (depths[i] && depths[i][name] != null) {
+          return depths[i][name];
+        }
+      }
+    },
     lambda: function(current, context) {
       return typeof current === 'function' ? current.call(context) : current;
     },
diff --git a/spec/blocks.js b/spec/blocks.js
index c57a67eee..a13cce2a4 100644
--- a/spec/blocks.js
+++ b/spec/blocks.js
@@ -94,4 +94,25 @@ describe('blocks', function() {
         'No people\n');
     });
   });
+
+  describe('compat mode', function() {
+    it("block with deep recursive lookup lookup", function() {
+      var string = "{{#outer}}Goodbye {{#inner}}cruel {{omg}}{{/inner}}{{/outer}}";
+      var hash = {omg: "OMG!", outer: [{ inner: [{ text: "goodbye" }] }] };
+
+      shouldCompileTo(string, [hash, undefined, undefined, true], "Goodbye cruel OMG!");
+    });
+    it("block with deep recursive pathed lookup", function() {
+      var string = "{{#outer}}Goodbye {{#inner}}cruel {{omg.yes}}{{/inner}}{{/outer}}";
+      var hash = {omg: {yes: "OMG!"}, outer: [{ inner: [{ yes: 'no', text: "goodbye" }] }] };
+
+      shouldCompileTo(string, [hash, undefined, undefined, true], "Goodbye cruel OMG!");
+    });
+    it("block with missed recursive lookup", function() {
+      var string = "{{#outer}}Goodbye {{#inner}}cruel {{omg.yes}}{{/inner}}{{/outer}}";
+      var hash = {omg: {no: "OMG!"}, outer: [{ inner: [{ yes: 'no', text: "goodbye" }] }] };
+
+      shouldCompileTo(string, [hash, undefined, undefined, true], "Goodbye cruel ");
+    });
+  });
 });
diff --git a/spec/env/common.js b/spec/env/common.js
index 03eef93e2..a603fc0ef 100644
--- a/spec/env/common.js
+++ b/spec/env/common.js
@@ -17,7 +17,7 @@ global.compileWithPartials = function(string, hashOrArray, partials) {
   if(Object.prototype.toString.call(hashOrArray) === "[object Array]") {
     ary = [];
     ary.push(hashOrArray[0]);
-    ary.push({ helpers: hashOrArray[1], partials: hashOrArray[2], compat: hashOrArray[3] });
+    ary.push({ helpers: hashOrArray[1], partials: hashOrArray[2] });
     options = {compat: hashOrArray[3]};
   } else {
     ary = [hashOrArray];

From 0edce6e1d1555cfbe0c55908261fc304a145f1a1 Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Wed, 13 Aug 2014 22:33:14 -0500
Subject: [PATCH 38/91] Inherit compat flag for partials

---
 lib/handlebars/compiler/javascript-compiler.js | 5 ++++-
 lib/handlebars/runtime.js                      | 4 ++--
 spec/partials.js                               | 9 +++++++++
 3 files changed, 15 insertions(+), 3 deletions(-)

diff --git a/lib/handlebars/compiler/javascript-compiler.js b/lib/handlebars/compiler/javascript-compiler.js
index b9b858dbd..a8544d577 100644
--- a/lib/handlebars/compiler/javascript-compiler.js
+++ b/lib/handlebars/compiler/javascript-compiler.js
@@ -123,7 +123,10 @@ JavaScriptCompiler.prototype = {
         ret.useData = true;
       }
       if (this.useDepths) {
-        ret.depths = true;
+        ret.useDepths = true;
+      }
+      if (this.options.compat) {
+        ret.compat = true;
       }
 
       if (!asObject) {
diff --git a/lib/handlebars/runtime.js b/lib/handlebars/runtime.js
index ec54f532f..de6ea1b81 100644
--- a/lib/handlebars/runtime.js
+++ b/lib/handlebars/runtime.js
@@ -40,7 +40,7 @@ export function template(templateSpec, env) {
 
     if (result == null && env.compile) {
       var options = { helpers: helpers, partials: partials, data: data };
-      partials[name] = env.compile(partial, { data: data !== undefined }, env);
+      partials[name] = env.compile(partial, { data: data !== undefined, compat: templateSpec.compat }, env);
       result = partials[name](context, options);
     }
     if (result != null) {
@@ -125,7 +125,7 @@ export function template(templateSpec, env) {
       data = initData(context, data);
     }
     var depths;
-    if (templateSpec.depths) {
+    if (templateSpec.useDepths) {
       depths = [context];
     }
 
diff --git a/spec/partials.js b/spec/partials.js
index 816fd1e82..9c8fdbc1e 100644
--- a/spec/partials.js
+++ b/spec/partials.js
@@ -155,4 +155,13 @@ describe('partials', function() {
             "Dudes:\n  Yehuda\n   http://yehuda!\n  Alan\n   http://alan!\n");
     });
   });
+
+  describe('compat mode', function() {
+    it('partials inherit compat', function() {
+      var string = 'Dudes: {{> dude}}';
+      var partial = '{{#dudes}}{{name}} ({{url}}) {{root}} {{/dudes}}';
+      var hash = {root: 'yes', dudes: [{name: 'Yehuda', url: 'http://yehuda'}, {name: 'Alan', url: 'http://alan'}]};
+      shouldCompileToWithPartials(string, [hash, {}, {dude: partial}, true], true, 'Dudes: Yehuda (http://yehuda) yes Alan (http://alan) yes ');
+    });
+  });
 });

From 9f8110fe1505c6398aaebb1fff9a2ea2a8f99010 Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Thu, 14 Aug 2014 00:28:57 -0500
Subject: [PATCH 39/91] Implement partial recursive lookup

---
 lib/handlebars/compiler/javascript-compiler.js |  5 ++++-
 lib/handlebars/runtime.js                      | 12 ++++++------
 spec/partials.js                               |  6 ++++++
 3 files changed, 16 insertions(+), 7 deletions(-)

diff --git a/lib/handlebars/compiler/javascript-compiler.js b/lib/handlebars/compiler/javascript-compiler.js
index a8544d577..ef02cc76d 100644
--- a/lib/handlebars/compiler/javascript-compiler.js
+++ b/lib/handlebars/compiler/javascript-compiler.js
@@ -369,7 +369,7 @@ JavaScriptCompiler.prototype = {
     var i = 0,
         len = parts.length;
 
-    if (!scoped && this.isChild && this.options.compat && !this.lastContext) {
+    if (!scoped && this.options.compat && !this.lastContext) {
       // The depthed query is expected to handle the undefined logic for the root level that
       // is implemented below, so we evaluate that directly in compat mode
       this.pushStackLiteral(this.depthedLookup(parts[i++]));
@@ -619,6 +619,9 @@ JavaScriptCompiler.prototype = {
     if (this.options.data) {
       params.push("data");
     }
+    if (this.options.compat) {
+      params.push('depths');
+    }
 
     this.push("this.invokePartial(" + params.join(", ") + ")");
   },
diff --git a/lib/handlebars/runtime.js b/lib/handlebars/runtime.js
index de6ea1b81..bfdb3b400 100644
--- a/lib/handlebars/runtime.js
+++ b/lib/handlebars/runtime.js
@@ -31,15 +31,15 @@ export function template(templateSpec, env) {
   // for external users to override these as psuedo-supported APIs.
   env.VM.checkRevision(templateSpec.compiler);
 
-  var invokePartialWrapper = function(partial, indent, name, context, hash, helpers, partials, data) {
+  var invokePartialWrapper = function(partial, indent, name, context, hash, helpers, partials, data, depths) {
     if (hash) {
       context = Utils.extend({}, context, hash);
     }
 
-    var result = env.VM.invokePartial.call(this, partial, name, context, helpers, partials, data);
+    var result = env.VM.invokePartial.call(this, partial, name, context, helpers, partials, data, depths);
 
     if (result == null && env.compile) {
-      var options = { helpers: helpers, partials: partials, data: data };
+      var options = { helpers: helpers, partials: partials, data: data, depths: depths };
       partials[name] = env.compile(partial, { data: data !== undefined, compat: templateSpec.compat }, env);
       result = partials[name](context, options);
     }
@@ -126,7 +126,7 @@ export function template(templateSpec, env) {
     }
     var depths;
     if (templateSpec.useDepths) {
-      depths = [context];
+      depths = options.depths ? [context].concat(options.depths) : [context];
     }
 
     return templateSpec.main.call(container, context, container.helpers, container.partials, data, depths);
@@ -166,8 +166,8 @@ export function program(container, i, fn, data, depths) {
   return prog;
 }
 
-export function invokePartial(partial, name, context, helpers, partials, data) {
-  var options = { partial: true, helpers: helpers, partials: partials, data: data };
+export function invokePartial(partial, name, context, helpers, partials, data, depths) {
+  var options = { partial: true, helpers: helpers, partials: partials, data: data, depths: depths };
 
   if(partial === undefined) {
     throw new Exception("The partial " + name + " could not be found");
diff --git a/spec/partials.js b/spec/partials.js
index 9c8fdbc1e..208d0387f 100644
--- a/spec/partials.js
+++ b/spec/partials.js
@@ -157,6 +157,12 @@ describe('partials', function() {
   });
 
   describe('compat mode', function() {
+    it('partials can access parents', function() {
+      var string = 'Dudes: {{#dudes}}{{> dude}}{{/dudes}}';
+      var partial = '{{name}} ({{url}}) {{root}} ';
+      var hash = {root: 'yes', dudes: [{name: 'Yehuda', url: 'http://yehuda'}, {name: 'Alan', url: 'http://alan'}]};
+      shouldCompileToWithPartials(string, [hash, {}, {dude: partial}, true], true, 'Dudes: Yehuda (http://yehuda) yes Alan (http://alan) yes ');
+    });
     it('partials inherit compat', function() {
       var string = 'Dudes: {{> dude}}';
       var partial = '{{#dudes}}{{name}} ({{url}}) {{root}} {{/dudes}}';

From 59607aa9ab4f93683b372777fa03cb07ba5b7db7 Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Wed, 13 Aug 2014 21:27:18 -0500
Subject: [PATCH 40/91] Add mustache spec to tests

---
 .gitmodules   |  3 +++
 spec/mustache |  1 +
 spec/spec.js  | 49 +++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 53 insertions(+)
 create mode 100644 .gitmodules
 create mode 160000 spec/mustache
 create mode 100644 spec/spec.js

diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 000000000..9414214c8
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "spec/mustache"]
+	path = spec/mustache
+	url = git@github.com:mustache/spec.git
diff --git a/spec/mustache b/spec/mustache
new file mode 160000
index 000000000..72233f3ff
--- /dev/null
+++ b/spec/mustache
@@ -0,0 +1 @@
+Subproject commit 72233f3ffda9e33915fd3022d0a9ebbcce265acd
diff --git a/spec/spec.js b/spec/spec.js
new file mode 100644
index 000000000..2dd2bd8f0
--- /dev/null
+++ b/spec/spec.js
@@ -0,0 +1,49 @@
+describe('spec', function() {
+  // NOP Under non-node environments
+  if (typeof process === 'undefined') {
+    return;
+  }
+
+  var _ = require('underscore'),
+      Handlebars = require('../lib'),
+      fs = require('fs');
+
+  var specDir =__dirname + '/mustache/specs/';
+  var specs = _.filter(fs.readdirSync(specDir), function(name) {
+    return /.*\.json$/.test(name);
+  });
+
+  _.each(specs, function(name) {
+    var spec = require(specDir + name);
+    _.each(spec.tests, function(test) {
+      // Our lambda implementation knowingly deviates from the optional Mustace lambda spec
+      // We also do not support alternative delimeters
+      if (name === '~lambdas.json'
+
+          // We also choose to throw if paritals are not found
+          || (name === 'partials.json' && test.name === 'Failed Lookup')
+
+          // We nest the entire response from partials, not just the literals
+          || (name === 'partials.json' && test.name === 'Standalone Indentation')
+
+          || /\{\{\=/.test(test.template)
+          || _.any(test.partials, function(partial) { return /\{\{\=/.test(partial); })) {
+        it.skip(name + ' - ' + test.name);
+        return;
+      }
+
+      var data = _.clone(test.data);
+      if (data.lambda) {
+        // Blergh
+        data.lambda = eval('(' + data.lambda.js + ')');
+      }
+      it(name + ' - ' + test.name, function() {
+        if (test.partials) {
+          shouldCompileToWithPartials(test.template, [data, {}, test.partials, true], true, test.expected, test.desc + ' "' + test.template + '"');
+        } else {
+          shouldCompileTo(test.template, [data, {}, {}, true], test.expected, test.desc + ' "' + test.template + '"');
+        }
+      });
+    });
+  });
+});

From 1bd8d38c3c3402767584d40bef49988f101a7aa9 Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Thu, 14 Aug 2014 00:43:46 -0500
Subject: [PATCH 41/91] Update compatibility documentation

Provides an official statement regarding the issues discussed in #425
---
 README.markdown | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/README.markdown b/README.markdown
index 482062900..866661eef 100644
--- a/README.markdown
+++ b/README.markdown
@@ -338,6 +338,14 @@ rewritten Handlebars (current version) is faster than the old version,
 and we will have some benchmarks in the near future.
 
 
+Mustache Compatibilty
+---------------------
+
+Handlebars deviates from the Mustache spec in a few key ways:
+- Alternative delimeters are not supported
+- Recrusive value lookup is not enabled by default. The compile time `compat` flag must be set to enable this functionality. Users should note that there is a performance cost for enabling this flag. The exact cost varies by template, but it's recommended that performance sensitive operations should avoid this mode and instead opt for explicit path references.
+- The optional Mustache-style lambdas are not supported. Instead Handlebars provides it's own lambda resolution that follows the behaviors of helpers.
+
 Building
 --------
 

From 9ab31e1fc6c94e5c13f9e6a10e23b1c1ed79c504 Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Thu, 14 Aug 2014 01:11:55 -0500
Subject: [PATCH 42/91] Add istanbul code coverage

---
 .gitignore    |  1 +
 .istanbul.yml |  2 ++
 package.json  |  3 ++-
 tasks/test.js | 13 ++++++++++++-
 4 files changed, 17 insertions(+), 2 deletions(-)
 create mode 100644 .istanbul.yml

diff --git a/.gitignore b/.gitignore
index 0e32d042b..3c6d099f1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,6 +4,7 @@ vendor
 lib/handlebars/compiler/parser.js
 /dist/
 /tmp/
+/coverage/
 node_modules
 *.sublime-project
 *.sublime-workspace
diff --git a/.istanbul.yml b/.istanbul.yml
new file mode 100644
index 000000000..e6911f190
--- /dev/null
+++ b/.istanbul.yml
@@ -0,0 +1,2 @@
+instrumentation:
+    excludes: ['**/spec/**']
diff --git a/package.json b/package.json
index ea8ca56b3..8e69350da 100644
--- a/package.json
+++ b/package.json
@@ -32,6 +32,7 @@
     "benchmark": "~1.0",
     "dustjs-linkedin": "~2.0.2",
     "eco": "~1.1.0-rc-3",
+    "es6-module-packager": "1.x",
     "grunt": "~0.4.1",
     "grunt-cli": "~0.1.10",
     "grunt-contrib-clean": "~0.4.1",
@@ -43,7 +44,7 @@
     "grunt-contrib-uglify": "~0.2.2",
     "grunt-contrib-watch": "~0.5.3",
     "grunt-saucelabs": "8.x",
-    "es6-module-packager": "1.x",
+    "istanbul": "^0.3.0",
     "jison": "~0.3.0",
     "keen.io": "0.0.3",
     "mocha": "~1.20.0",
diff --git a/tasks/test.js b/tasks/test.js
index 664af60d8..ad8a911d3 100644
--- a/tasks/test.js
+++ b/tasks/test.js
@@ -29,5 +29,16 @@ module.exports = function(grunt) {
       done();
     });
   });
-  grunt.registerTask('test', ['test:bin', 'test:mocha']);
+  grunt.registerTask('test:cov', function() {
+    var done = this.async();
+
+    var runner = childProcess.fork('node_modules/.bin/istanbul', ['cover', '--', './spec/env/runner.js'], {stdio: 'inherit'});
+    runner.on('close', function(code) {
+      if (code != 0) {
+        grunt.fatal(code + ' tests failed');
+      }
+      done();
+    });
+  });
+  grunt.registerTask('test', ['test:bin', 'test:cov']);
 };

From cb22ee5681b1eb1f89ee675651c018b77dd1524d Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Thu, 14 Aug 2014 12:25:35 -0500
Subject: [PATCH 43/91] Increase test coverage a touch

---
 lib/handlebars/base.js                        |  3 +-
 .../compiler/javascript-compiler.js           |  6 +-
 lib/handlebars/runtime.js                     |  1 +
 lib/handlebars/utils.js                       |  4 +-
 lib/index.js                                  |  1 +
 spec/blocks.js                                | 13 +++-
 spec/compiler.js                              | 70 +++++++++++++++++++
 spec/env/common.js                            |  2 +-
 spec/helpers.js                               | 43 +++++++++---
 spec/parser.js                                |  2 +-
 spec/partials.js                              |  3 +
 spec/precompiler.js                           | 45 +++++++++++-
 spec/regressions.js                           | 16 -----
 spec/utils.js                                 | 16 +++++
 src/handlebars.l                              |  5 --
 15 files changed, 186 insertions(+), 44 deletions(-)
 create mode 100644 spec/compiler.js

diff --git a/lib/handlebars/base.js b/lib/handlebars/base.js
index 022417b41..4eac7e94c 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 ef02cc76d..d52f2a0e7 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 bfdb3b400..d3519187c 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 f2f1a5470..087183e54 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] || "&amp;";
+  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 e150524bd..790aab73a 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 a13cce2a4..061947a18 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 000000000..fa7635eab
--- /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 a603fc0ef..e551837db 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 d686b6461..e3b586349 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 ff12cc4ac..076ce36d6 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 208d0387f..cc464b95c 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 7cd1ffd0b..57fc280e6 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 dd4eedde1..d4598cc71 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 390ad0533..ea7d78256 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 0a531d5e1..57ba12a4c 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';

From 4d0e3c98ebec60d70202300128fb73e027eed3be Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Thu, 14 Aug 2014 12:25:58 -0500
Subject: [PATCH 44/91] Drop unused registerHelper inverse parameter

---
 lib/handlebars/base.js | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/lib/handlebars/base.js b/lib/handlebars/base.js
index 4eac7e94c..8f068e1b9 100644
--- a/lib/handlebars/base.js
+++ b/lib/handlebars/base.js
@@ -30,12 +30,11 @@ HandlebarsEnvironment.prototype = {
   logger: logger,
   log: log,
 
-  registerHelper: function(name, fn, inverse) {
+  registerHelper: function(name, fn) {
     if (toString.call(name) === objectType) {
-      if (inverse || fn) { throw new Exception('Arg not supported with multiple helpers'); }
+      if (fn) { throw new Exception('Arg not supported with multiple helpers'); }
       Utils.extend(this.helpers, name);
     } else {
-      if (inverse) { fn.not = inverse; }
       this.helpers[name] = fn;
     }
   },

From 3cea85b2b0d1478dbeb19c82f7781385268db6a2 Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Thu, 14 Aug 2014 12:27:02 -0500
Subject: [PATCH 45/91] Drop unused DECLARE opcode

---
 lib/handlebars/compiler/compiler.js            | 4 ----
 lib/handlebars/compiler/javascript-compiler.js | 6 +-----
 2 files changed, 1 insertion(+), 9 deletions(-)

diff --git a/lib/handlebars/compiler/compiler.js b/lib/handlebars/compiler/compiler.js
index ffddec432..120589828 100644
--- a/lib/handlebars/compiler/compiler.js
+++ b/lib/handlebars/compiler/compiler.js
@@ -336,10 +336,6 @@ Compiler.prototype = {
     this.opcodes.push({ opcode: name, args: [].slice.call(arguments, 1) });
   },
 
-  declare: function(name, value) {
-    this.opcodes.push({ opcode: 'DECLARE', name: name, value: value });
-  },
-
   addDepth: function(depth) {
     if(depth === 0) { return; }
 
diff --git a/lib/handlebars/compiler/javascript-compiler.js b/lib/handlebars/compiler/javascript-compiler.js
index d52f2a0e7..00402ba74 100644
--- a/lib/handlebars/compiler/javascript-compiler.js
+++ b/lib/handlebars/compiler/javascript-compiler.js
@@ -84,11 +84,7 @@ JavaScriptCompiler.prototype = {
     for (i = 0, l = opcodes.length; i < l; i++) {
       opcode = opcodes[i];
 
-      if(opcode.opcode === 'DECLARE') {
-        this[opcode.name] = opcode.value;
-      } else {
-        this[opcode.opcode].apply(this, opcode.args);
-      }
+      this[opcode.opcode].apply(this, opcode.args);
 
       // Reset the stripNext flag if it was not set by this operation.
       if (opcode.opcode !== this.stripNext) {

From b94656a31fe783b5b4c29eab09e7e334e5113036 Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Thu, 14 Aug 2014 12:27:23 -0500
Subject: [PATCH 46/91] Drop unused Compiler.disassemble method

---
 lib/handlebars/compiler/compiler.js | 24 ------------------------
 1 file changed, 24 deletions(-)

diff --git a/lib/handlebars/compiler/compiler.js b/lib/handlebars/compiler/compiler.js
index 120589828..8cabbddc2 100644
--- a/lib/handlebars/compiler/compiler.js
+++ b/lib/handlebars/compiler/compiler.js
@@ -10,30 +10,6 @@ export function Compiler() {}
 Compiler.prototype = {
   compiler: Compiler,
 
-  disassemble: function() {
-    var opcodes = this.opcodes, opcode, out = [], params, param;
-
-    for (var i=0, l=opcodes.length; i<l; i++) {
-      opcode = opcodes[i];
-
-      if (opcode.opcode === 'DECLARE') {
-        out.push("DECLARE " + opcode.name + "=" + opcode.value);
-      } else {
-        params = [];
-        for (var j=0; j<opcode.args.length; j++) {
-          param = opcode.args[j];
-          if (typeof param === "string") {
-            param = "\"" + param.replace("\n", "\\n") + "\"";
-          }
-          params.push(param);
-        }
-        out.push(opcode.opcode + " " + params.join(" "));
-      }
-    }
-
-    return out.join("\n");
-  },
-
   equals: function(other) {
     var len = this.opcodes.length;
     if (other.opcodes.length !== len) {

From 9e3f824bf57ed7f049314853782f61d258f3c7df Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Thu, 14 Aug 2014 12:27:39 -0500
Subject: [PATCH 47/91] Fix compiler program de-duping

---
 lib/handlebars/compiler/compiler.js | 28 +++++++++++++++++++---------
 1 file changed, 19 insertions(+), 9 deletions(-)

diff --git a/lib/handlebars/compiler/compiler.js b/lib/handlebars/compiler/compiler.js
index 8cabbddc2..a4bd09a57 100644
--- a/lib/handlebars/compiler/compiler.js
+++ b/lib/handlebars/compiler/compiler.js
@@ -1,4 +1,5 @@
 import Exception from "../exception";
+import {isArray} from "../utils";
 
 export function Compiler() {}
 
@@ -19,20 +20,14 @@ Compiler.prototype = {
     for (var i = 0; i < len; i++) {
       var opcode = this.opcodes[i],
           otherOpcode = other.opcodes[i];
-      if (opcode.opcode !== otherOpcode.opcode || opcode.args.length !== otherOpcode.args.length) {
+      if (opcode.opcode !== otherOpcode.opcode || !argEquals(opcode.args, otherOpcode.args)) {
         return false;
       }
-      for (var j = 0; j < opcode.args.length; j++) {
-        if (opcode.args[j] !== otherOpcode.args[j]) {
-          return false;
-        }
-      }
     }
 
+    // We know that length is the same between the two arrays because they are directly tied
+    // to the opcode behavior above.
     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;
@@ -449,3 +444,18 @@ export function compile(input, options, env) {
   };
   return ret;
 }
+
+function argEquals(a, b) {
+  if (a === b) {
+    return true;
+  }
+
+  if (isArray(a) && isArray(b) && a.length === b.length) {
+    for (var i = 0; i < a.length; i++) {
+      if (!argEquals(a[i], b[i])) {
+        return false;
+      }
+    }
+    return true;
+  }
+}

From 85b67d7f3d6aa134be942aa1099b0938a6d36060 Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Thu, 14 Aug 2014 12:31:01 -0500
Subject: [PATCH 48/91] Use public url for spec submodule

---
 .gitmodules | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.gitmodules b/.gitmodules
index 9414214c8..1739275a8 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,3 +1,3 @@
 [submodule "spec/mustache"]
 	path = spec/mustache
-	url = git@github.com:mustache/spec.git
+	url = git://github.com/mustache/spec.git

From c71ca34fd30a8ed04349b92564a577ba193da2ee Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Thu, 14 Aug 2014 22:00:59 -0500
Subject: [PATCH 49/91] Add FAQ entry for .default export

Fixes #824
---
 FAQ.md | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/FAQ.md b/FAQ.md
index bd4f6aa38..4e8b85625 100644
--- a/FAQ.md
+++ b/FAQ.md
@@ -35,3 +35,6 @@
   On the client side.
 
   Should these match, please file an issue with us, per our [issue filing guidelines](https://github.com/wycats/handlebars.js/blob/master/README.markdown#reporting-issues).
+
+1. Why doesn't IE like the `default` name in the AMD module?
+  Some browsers such as particular versions of IE treat `default` as a reserved word in JavaScript source files. To safely use this you need to reference this via the `Handlebars['default']` lookup method. This is an unfortunate side effect of the shims necessary to backport the handlebars ES6 code to all current browsers.

From 6f22ec1e01264f443820a8f89b8a7e2edcf89abf Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Thu, 14 Aug 2014 23:10:17 -0500
Subject: [PATCH 50/91] Remove nested function eval in blockHelperMissing

This only worked when a function returned a function, which was then evaluated. This seems like unexpected behavior so this was dropped.
---
 lib/handlebars/base.js | 2 --
 1 file changed, 2 deletions(-)

diff --git a/lib/handlebars/base.js b/lib/handlebars/base.js
index 8f068e1b9..f39241f82 100644
--- a/lib/handlebars/base.js
+++ b/lib/handlebars/base.js
@@ -69,8 +69,6 @@ function registerDefaultHelpers(instance) {
     var inverse = options.inverse,
         fn = options.fn;
 
-    if (isFunction(context)) { context = context.call(this); }
-
     if(context === true) {
       return fn(this);
     } else if(context === false || context == null) {

From 9f07a3495551f26020a49c90e33150b9fa6abf73 Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Fri, 15 Aug 2014 00:05:37 -0500
Subject: [PATCH 51/91] Additional test coverage

---
 lib/handlebars/base.js |   2 +-
 spec/blocks.js         |   5 ++
 spec/builtins.js       | 102 ++++++++++++++++++++++++++++++++++++-----
 spec/env/common.js     |   4 ++
 spec/partials.js       |  12 ++++-
 5 files changed, 111 insertions(+), 14 deletions(-)

diff --git a/lib/handlebars/base.js b/lib/handlebars/base.js
index f39241f82..e11f03265 100644
--- a/lib/handlebars/base.js
+++ b/lib/handlebars/base.js
@@ -219,7 +219,7 @@ export var logger = {
   }
 };
 
-export function log(level, obj) { logger.log(level, obj); }
+export var log = logger.log;
 
 export var createFrame = function(object) {
   var frame = Utils.extend({}, object);
diff --git a/spec/blocks.js b/spec/blocks.js
index 061947a18..9a5cb40d4 100644
--- a/spec/blocks.js
+++ b/spec/blocks.js
@@ -8,7 +8,12 @@ describe('blocks', function() {
 
     shouldCompileTo(string, {goodbyes: [], world: "world"}, "cruel world!",
                     "Arrays ignore the contents when empty");
+  });
 
+  it('array without data', function() {
+    var string   = '{{#goodbyes}}{{text}}{{/goodbyes}} {{#goodbyes}}{{text}}{{/goodbyes}}';
+    var hash     = {goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}], world: 'world'};
+    shouldCompileTo(string, [hash,,,,false], 'goodbyeGoodbyeGOODBYE goodbyeGoodbyeGOODBYE');
   });
 
   it("array with @index", function() {
diff --git a/spec/builtins.js b/spec/builtins.js
index 77f37012e..d2fa88f5e 100644
--- a/spec/builtins.js
+++ b/spec/builtins.js
@@ -66,9 +66,29 @@ describe('builtin helpers', function() {
                       "each with array argument ignores the contents when empty");
     });
 
+    it('each without data', function() {
+      var string   = '{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!';
+      var hash     = {goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}], world: 'world'};
+      shouldCompileTo(string, [hash,,,,false], 'goodbye! Goodbye! GOODBYE! cruel world!');
+
+      hash = {goodbyes: 'cruel', world: 'world'};
+      shouldCompileTo('{{#each .}}{{.}}{{/each}}', [hash,,,,false], 'cruelworld');
+    });
+
+    it('each without context', function() {
+      var string   = '{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!';
+      shouldCompileTo(string, [,,,,], 'cruel !');
+    });
+
     it("each with an object and @key", function() {
       var string   = "{{#each goodbyes}}{{@key}}. {{text}}! {{/each}}cruel {{world}}!";
-      var hash     = {goodbyes: {"<b>#1</b>": {text: "goodbye"}, 2: {text: "GOODBYE"}}, world: "world"};
+
+      function Clazz() {
+        this['<b>#1</b>'] = {text: 'goodbye'};
+        this[2] = {text: 'GOODBYE'};
+      }
+      Clazz.prototype.foo = 'fail';
+      var hash     = {goodbyes: new Clazz(), world: 'world'};
 
       // Object property iteration order is undefined according to ECMA spec,
       // so we need to check both possible orders
@@ -193,19 +213,77 @@ describe('builtin helpers', function() {
     });
   });
 
-  it("#log", function() {
-    var string = "{{log blah}}";
-    var hash   = { blah: "whee" };
+  describe("#log", function() {
+    var info,
+        error;
+    beforeEach(function() {
+      info = console.info;
+      error = console.error;
+    });
+    afterEach(function() {
+      console.info = info;
+      console.error = error;
+    });
+
+    it('should call logger at default level', function() {
+      var string = "{{log blah}}";
+      var hash   = { blah: "whee" };
 
-    var levelArg, logArg;
-    handlebarsEnv.log = function(level, arg){
-      levelArg = level;
-      logArg = arg;
-    };
+      var levelArg, logArg;
+      handlebarsEnv.log = function(level, arg){
+        levelArg = level;
+        logArg = arg;
+      };
 
-    shouldCompileTo(string, hash, "", "log should not display");
-    equals(1, levelArg, "should call log with 1");
-    equals("whee", logArg, "should call log with 'whee'");
+      shouldCompileTo(string, hash, "", "log should not display");
+      equals(1, levelArg, "should call log with 1");
+      equals("whee", logArg, "should call log with 'whee'");
+    });
+    it('should call logger at data level', function() {
+      var string = "{{log blah}}";
+      var hash   = { blah: "whee" };
+
+      var levelArg, logArg;
+      handlebarsEnv.log = function(level, arg){
+        levelArg = level;
+        logArg = arg;
+      };
+
+      shouldCompileTo(string, [hash,,,,{level: '03'}], "");
+      equals(3, levelArg);
+      equals("whee", logArg);
+    });
+    it('should not output to console', function() {
+      var string = "{{log blah}}";
+      var hash   = { blah: "whee" };
+
+      console.info = function() {
+        throw new Error();
+      };
+
+      shouldCompileTo(string, hash, "", "log should not display");
+    });
+    it('should log at data level', function() {
+      var string = "{{log blah}}";
+      var hash   = { blah: "whee" };
+      var called;
+
+      console.error = function(log) {
+        equals("whee", log);
+        called = true;
+      };
+
+      shouldCompileTo(string, [hash,,,,{level: '03'}], "");
+      equals(true, called);
+    });
+    it('should handle missing logger', function() {
+      var string = "{{log blah}}";
+      var hash   = { blah: "whee" };
+
+      console.error = undefined;
+
+      shouldCompileTo(string, [hash,,,,{level: '03'}], "");
+    });
   });
 
 
diff --git a/spec/env/common.js b/spec/env/common.js
index e551837db..92cc61123 100644
--- a/spec/env/common.js
+++ b/spec/env/common.js
@@ -19,6 +19,10 @@ global.compileWithPartials = function(string, hashOrArray, partials) {
     ary.push(hashOrArray[0]);
     ary.push({ helpers: hashOrArray[1], partials: hashOrArray[2] });
     options = {compat: hashOrArray[3]};
+    if (hashOrArray[4] != null) {
+      options.data = !!hashOrArray[4];
+      ary[1].data = hashOrArray[4];
+    }
   } else {
     ary = [hashOrArray];
   }
diff --git a/spec/partials.js b/spec/partials.js
index cc464b95c..d401d2eac 100644
--- a/spec/partials.js
+++ b/spec/partials.js
@@ -139,7 +139,17 @@ describe('partials', function() {
     var string = "Dudes: {{#dudes}}{{> dude}}{{/dudes}}";
     var partial = "";
     var hash = {dudes: [{name: "Yehuda", url: "http://yehuda"}, {name: "Alan", url: "http://alan"}]};
-    shouldCompileToWithPartials(string, [hash, {}, {dude: partial}], true, "Dudes: ");  });
+    shouldCompileToWithPartials(string, [hash, {}, {dude: partial}], true, "Dudes: ");
+  });
+
+  it("throw on missing partial", function() {
+    var compile = handlebarsEnv.compile;
+    handlebarsEnv.compile = undefined;
+    shouldThrow(function() {
+      shouldCompileTo('{{> dude}}', [{}, {}, {dude: 'fail'}], '');
+    }, Error, /The partial dude could not be compiled/);
+    handlebarsEnv.compile = compile;
+  });
 
   describe('standalone partials', function() {
     it("indented partials", function() {

From 084e8fe1b77f4e9a327ce745aa7bb58c812e3510 Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Fri, 15 Aug 2014 00:06:06 -0500
Subject: [PATCH 52/91] Provide better error on unexpected template

Fixes #806
---
 lib/handlebars/runtime.js |  4 ++++
 spec/runtime.js           | 36 ++++++++++++++++++++++++++++++++++++
 2 files changed, 40 insertions(+)
 create mode 100644 spec/runtime.js

diff --git a/lib/handlebars/runtime.js b/lib/handlebars/runtime.js
index d3519187c..0780fbf6d 100644
--- a/lib/handlebars/runtime.js
+++ b/lib/handlebars/runtime.js
@@ -27,6 +27,9 @@ export function template(templateSpec, env) {
   if (!env) {
     throw new Exception("No environment passed to template");
   }
+  if (!templateSpec || !templateSpec.main) {
+    throw new Exception('Unknown template object: ' + typeof templateSpec);
+  }
 
   // Note: Using env.VM references rather than local var references throughout this section to allow
   // for external users to override these as psuedo-supported APIs.
@@ -151,6 +154,7 @@ export function template(templateSpec, env) {
       throw new Exception('_child can not be used with depthed methods');
     }
 
+    // TODO : Fix this
     return container.programWithDepth(i);
   };
   return ret;
diff --git a/spec/runtime.js b/spec/runtime.js
new file mode 100644
index 000000000..4431a6877
--- /dev/null
+++ b/spec/runtime.js
@@ -0,0 +1,36 @@
+/*globals Handlebars, shouldThrow */
+
+describe('runtime', function() {
+  describe('#template', function() {
+    it('should throw on invalid templates', function() {
+      shouldThrow(function() {
+        Handlebars.template({});
+      }, Error, 'Unknown template object: object');
+      shouldThrow(function() {
+        Handlebars.template();
+      }, Error, 'Unknown template object: undefined');
+      shouldThrow(function() {
+        Handlebars.template('');
+      }, Error, 'Unknown template object: string');
+    });
+    it('should throw on version mismatch', function() {
+      shouldThrow(function() {
+        Handlebars.template({
+          main: true,
+          compiler: [Handlebars.COMPILER_REVISION + 1]
+        });
+      }, Error, /Template was precompiled with a newer version of Handlebars than the current runtime/);
+      shouldThrow(function() {
+        Handlebars.template({
+          main: true,
+          compiler: [Handlebars.COMPILER_REVISION - 1]
+        });
+      }, Error, /Template was precompiled with an older version of Handlebars than the current runtime/);
+      shouldThrow(function() {
+        Handlebars.template({
+          main: true
+        });
+      }, Error, /Template was precompiled with an older version of Handlebars than the current runtime/);
+    });
+  });
+});

From d05245b5f5ff71ead88075b20939c2c481166643 Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Fri, 15 Aug 2014 00:37:57 -0500
Subject: [PATCH 53/91] Make depthed lookup call non-literal

Prevents duplicate calls from being made.
---
 lib/handlebars/compiler/javascript-compiler.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lib/handlebars/compiler/javascript-compiler.js b/lib/handlebars/compiler/javascript-compiler.js
index 00402ba74..9fdef1d88 100644
--- a/lib/handlebars/compiler/javascript-compiler.js
+++ b/lib/handlebars/compiler/javascript-compiler.js
@@ -369,7 +369,7 @@ JavaScriptCompiler.prototype = {
     if (!scoped && this.options.compat && !this.lastContext) {
       // The depthed query is expected to handle the undefined logic for the root level that
       // is implemented below, so we evaluate that directly in compat mode
-      this.pushStackLiteral(this.depthedLookup(parts[i++]));
+      this.push(this.depthedLookup(parts[i++]));
     } else {
       this.pushContext();
     }

From cc5de5df3c6d5ecd6d3d63d6162e487459494e2f Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Fri, 15 Aug 2014 00:38:23 -0500
Subject: [PATCH 54/91] Fix compat partial exec without data

---
 lib/handlebars/compiler/javascript-compiler.js |  2 ++
 spec/partials.js                               | 18 ++++++++++++------
 2 files changed, 14 insertions(+), 6 deletions(-)

diff --git a/lib/handlebars/compiler/javascript-compiler.js b/lib/handlebars/compiler/javascript-compiler.js
index 9fdef1d88..7638fc79e 100644
--- a/lib/handlebars/compiler/javascript-compiler.js
+++ b/lib/handlebars/compiler/javascript-compiler.js
@@ -611,6 +611,8 @@ JavaScriptCompiler.prototype = {
 
     if (this.options.data) {
       params.push("data");
+    } else if (this.options.compat) {
+      params.push('undefined');
     }
     if (this.options.compat) {
       params.push('depths');
diff --git a/spec/partials.js b/spec/partials.js
index d401d2eac..20187f81d 100644
--- a/spec/partials.js
+++ b/spec/partials.js
@@ -1,11 +1,11 @@
 /*global CompilerContext, Handlebars, handlebarsEnv, shouldCompileTo, shouldCompileToWithPartials, shouldThrow */
 describe('partials', function() {
-  it("basic partials", function() {
-    var string = "Dudes: {{#dudes}}{{> dude}}{{/dudes}}";
-    var partial = "{{name}} ({{url}}) ";
-    var hash = {dudes: [{name: "Yehuda", url: "http://yehuda"}, {name: "Alan", url: "http://alan"}]};
-    shouldCompileToWithPartials(string, [hash, {}, {dude: partial}], true, "Dudes: Yehuda (http://yehuda) Alan (http://alan) ",
-                    "Basic partials output based on current context.");
+  it('basic partials', function() {
+    var string = 'Dudes: {{#dudes}}{{> dude}}{{/dudes}}';
+    var partial = '{{name}} ({{url}}) ';
+    var hash = {dudes: [{name: 'Yehuda', url: 'http://yehuda'}, {name: 'Alan', url: 'http://alan'}]};
+    shouldCompileToWithPartials(string, [hash, {}, {dude: partial}], true, 'Dudes: Yehuda (http://yehuda) Alan (http://alan) ');
+    shouldCompileToWithPartials(string, [hash, {}, {dude: partial},,false], true, 'Dudes: Yehuda (http://yehuda) Alan (http://alan) ');
   });
 
   it("partials with context", function() {
@@ -176,6 +176,12 @@ describe('partials', function() {
       var hash = {root: 'yes', dudes: [{name: 'Yehuda', url: 'http://yehuda'}, {name: 'Alan', url: 'http://alan'}]};
       shouldCompileToWithPartials(string, [hash, {}, {dude: partial}, true], true, 'Dudes: Yehuda (http://yehuda) yes Alan (http://alan) yes ');
     });
+    it('partials can access parents without data', function() {
+      var string = 'Dudes: {{#dudes}}{{> dude}}{{/dudes}}';
+      var partial = '{{name}} ({{url}}) {{root}} ';
+      var hash = {root: 'yes', dudes: [{name: 'Yehuda', url: 'http://yehuda'}, {name: 'Alan', url: 'http://alan'}]};
+      shouldCompileToWithPartials(string, [hash, {}, {dude: partial}, true, false], true, 'Dudes: Yehuda (http://yehuda) yes Alan (http://alan) yes ');
+    });
     it('partials inherit compat', function() {
       var string = 'Dudes: {{> dude}}';
       var partial = '{{#dudes}}{{name}} ({{url}}) {{root}} {{/dudes}}';

From f2b5519e76e769842bea8cc92142a4cd792d3921 Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Fri, 15 Aug 2014 00:40:07 -0500
Subject: [PATCH 55/91] Strip dead code from javascript compiler

---
 lib/handlebars/compiler/javascript-compiler.js | 11 ++---------
 1 file changed, 2 insertions(+), 9 deletions(-)

diff --git a/lib/handlebars/compiler/javascript-compiler.js b/lib/handlebars/compiler/javascript-compiler.js
index 7638fc79e..d9c23ee3f 100644
--- a/lib/handlebars/compiler/javascript-compiler.js
+++ b/lib/handlebars/compiler/javascript-compiler.js
@@ -403,9 +403,6 @@ JavaScriptCompiler.prototype = {
     } else {
       this.pushStackLiteral('this.data(data, ' + depth + ')');
     }
-    if (!parts) {
-      return;
-    }
 
     var len = parts.length;
     for (var i = 0; i < len; i++) {
@@ -702,10 +699,6 @@ JavaScriptCompiler.prototype = {
   },
 
   programExpression: function(guid) {
-    if(guid == null) {
-      return 'this.noop';
-    }
-
     var child = this.environment.children[guid],
         depths = child.depths.list,
         useDepths = this.useDepths,
@@ -854,11 +847,11 @@ JavaScriptCompiler.prototype = {
     }
   },
 
-  topStack: function(wrapped) {
+  topStack: function() {
     var stack = (this.isInline() ? this.inlineStack : this.compileStack),
         item = stack[stack.length - 1];
 
-    if (!wrapped && (item instanceof Literal)) {
+    if (item instanceof Literal) {
       return item.value;
     } else {
       return item;

From 2f14fa57e55d3089bc71aab3e08dc36a293cbc57 Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Fri, 15 Aug 2014 01:02:26 -0500
Subject: [PATCH 56/91] Fix/simplify tests under sauce

---
 Gruntfile.js     | 4 +---
 spec/builtins.js | 4 ++++
 2 files changed, 5 insertions(+), 3 deletions(-)

diff --git a/Gruntfile.js b/Gruntfile.js
index 4e12ef734..6cbef6eb7 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -138,13 +138,11 @@ module.exports = function(grunt) {
           browsers: [
             {browserName: 'chrome'},
             {browserName: 'firefox'},
-            {browserName: 'firefox', version: '3.6'},
             {browserName: 'safari', version: 7, platform: 'OS X 10.9'},
             {browserName: 'safari', version: 6, platform: 'OS X 10.8'},
             {browserName: 'internet explorer', version: 11, platform: 'Windows 8.1'},
             {browserName: 'internet explorer', version: 10, platform: 'Windows 8'},
-            {browserName: 'internet explorer', version: 9, platform: 'Windows 7'},
-            {browserName: 'internet explorer', version: 6, platform: 'XP'}
+            {browserName: 'internet explorer', version: 9, platform: 'Windows 7'}
           ]
         }
       }
diff --git a/spec/builtins.js b/spec/builtins.js
index d2fa88f5e..2cd6bacfc 100644
--- a/spec/builtins.js
+++ b/spec/builtins.js
@@ -214,6 +214,10 @@ describe('builtin helpers', function() {
   });
 
   describe("#log", function() {
+    if (typeof console === 'undefined') {
+      return;
+    }
+
     var info,
         error;
     beforeEach(function() {

From ae44e071d82b535071a3750eb53785460e2f6a3c Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Mon, 11 Aug 2014 20:09:39 -0500
Subject: [PATCH 57/91] Test case for #837

---
 spec/regressions.js | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/spec/regressions.js b/spec/regressions.js
index d4598cc71..11207fce3 100644
--- a/spec/regressions.js
+++ b/spec/regressions.js
@@ -137,4 +137,12 @@ describe('Regressions', function() {
   it('GH-820: zero pathed rendering', function() {
     shouldCompileTo('{{foo.bar}}', {foo: 0}, '');
   });
+
+  it('GH-837: undefined values for helpers', function() {
+    var helpers = {
+      str: function(value) { return value + ''; }
+    };
+
+    shouldCompileTo('{{str bar.baz}}', [{}, helpers], 'undefined');
+  });
 });

From 3ce105ae894b328b4da5a4cf5ad80026aee42489 Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Fri, 15 Aug 2014 00:56:29 -0500
Subject: [PATCH 58/91] Fix undefined handling for pathed lookups

Fixes #837
---
 lib/handlebars/compiler/javascript-compiler.js | 7 +++----
 1 file changed, 3 insertions(+), 4 deletions(-)

diff --git a/lib/handlebars/compiler/javascript-compiler.js b/lib/handlebars/compiler/javascript-compiler.js
index d9c23ee3f..a6930f088 100644
--- a/lib/handlebars/compiler/javascript-compiler.js
+++ b/lib/handlebars/compiler/javascript-compiler.js
@@ -377,10 +377,9 @@ JavaScriptCompiler.prototype = {
     for (; i < len; i++) {
       this.replaceStack(function(current) {
         var lookup = this.nameLookup(current, parts[i], 'context');
-        // We want to ensure that zero and false are handled properly for the first element
-        // of non-chained elements, if the context (falsy flag) needs to have the special
-        // handling for these values.
-        if (!falsy && !i && len === 1) {
+        // We want to ensure that zero and false are handled properly if the context (falsy flag)
+        // needs to have the special handling for these values.
+        if (!falsy) {
           return ' != null ? ' + lookup + ' : ' + current;
         } else {
           // Otherwise we can use generic falsy handling

From f4b8c5260c203d6ab7631fa394696f5c7a442586 Mon Sep 17 00:00:00 2001
From: Marko Zabcic <gm.marko@gmail.com>
Date: Wed, 19 Mar 2014 14:41:40 +0100
Subject: [PATCH 59/91] added test multiple subexpressions in a hash with
 context

---
 spec/subexpressions.js | 24 ++++++++++++++++++++++++
 1 file changed, 24 insertions(+)

diff --git a/spec/subexpressions.js b/spec/subexpressions.js
index 0ecdbb9d2..5c9fdfc30 100644
--- a/spec/subexpressions.js
+++ b/spec/subexpressions.js
@@ -139,6 +139,30 @@ describe('subexpressions', function() {
     shouldCompileTo(string, [{}, helpers], '<input aria-label="Name" placeholder="Example User" />');
   });
 
+  it("multiple subexpressions in a hash with context", function() {
+    var string = '{{input aria-label=(t item.field) placeholder=(t item.placeholder)}}';
+
+    var context = {
+      item: {
+        field: "Name",
+        placeholder: "Example User"
+      } 
+    };
+
+    var helpers = {
+      input: function(options) {
+        var hash        = options.hash;
+        var ariaLabel   = Handlebars.Utils.escapeExpression(hash['aria-label']);
+        var placeholder = Handlebars.Utils.escapeExpression(hash.placeholder);
+        return new Handlebars.SafeString('<input aria-label="' + ariaLabel + '" placeholder="' + placeholder + '" />');
+      },
+      t: function(defaultString) {
+        return new Handlebars.SafeString(defaultString);
+      }
+    }
+    shouldCompileTo(string, [context, helpers], '<input aria-label="Name" placeholder="Example User" />');
+  });  
+
   it("in string params mode,", function() {
     var template = CompilerContext.compile('{{snog (blorg foo x=y) yeah a=b}}', {stringParams: true});
 

From 19ce9812965d1dcd15b93cf4e993f958d147179b Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Sat, 23 Aug 2014 07:36:01 -0500
Subject: [PATCH 60/91] Do not flush subexpressions

They are no longer duplicated with the new helper calling pattern and this also introduced stack corruption issues due to improper value lookups.

Fixes #767
Fixes #768
---
 lib/handlebars/compiler/javascript-compiler.js | 7 -------
 1 file changed, 7 deletions(-)

diff --git a/lib/handlebars/compiler/javascript-compiler.js b/lib/handlebars/compiler/javascript-compiler.js
index a6930f088..7a4d98790 100644
--- a/lib/handlebars/compiler/javascript-compiler.js
+++ b/lib/handlebars/compiler/javascript-compiler.js
@@ -545,13 +545,6 @@ JavaScriptCompiler.prototype = {
 
     var lookup = (isSimple ? helper.name + ' || ' : '') + nonHelper + ' || helperMissing';
     this.push('((' + lookup + ').call(' + helper.callParams + '))');
-
-    // Always flush subexpressions. This is both to prevent the compounding size issue that
-    // occurs when the code has to be duplicated for inlining and also to prevent errors
-    // due to the incorrect options object being passed due to the shared register.
-    if (!isRoot) {
-      this.flushInline();
-    }
   },
 
   // [invokeKnownHelper]

From c3fde1c01c7f51dafdc2886296fbe3d7471db0a1 Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Sat, 23 Aug 2014 07:42:24 -0500
Subject: [PATCH 61/91] Remove unnecessary stack variable from blockHelper

---
 lib/handlebars/compiler/javascript-compiler.js | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/lib/handlebars/compiler/javascript-compiler.js b/lib/handlebars/compiler/javascript-compiler.js
index 7a4d98790..c02e0232a 100644
--- a/lib/handlebars/compiler/javascript-compiler.js
+++ b/lib/handlebars/compiler/javascript-compiler.js
@@ -243,10 +243,10 @@ JavaScriptCompiler.prototype = {
     var params = [this.contextName(0)];
     this.setupParams(name, 0, params);
 
-    this.replaceStack(function(current) {
-      params.splice(1, 0, current);
-      return "blockHelperMissing.call(" + params.join(", ") + ")";
-    });
+    var blockName = this.popStack();
+    params.splice(1, 0, blockName);
+
+    this.push('blockHelperMissing.call(' + params.join(', ') + ')');
   },
 
   // [ambiguousBlockValue]

From 3531e041174509c6c3c69417b1714dcda6bccb3e Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Sat, 23 Aug 2014 07:43:09 -0500
Subject: [PATCH 62/91] Optimize replaceStack for inline methods

Only use case was with inline input so most of this code was unnecessary.
---
 .../compiler/javascript-compiler.js           | 64 +++++++------------
 1 file changed, 24 insertions(+), 40 deletions(-)

diff --git a/lib/handlebars/compiler/javascript-compiler.js b/lib/handlebars/compiler/javascript-compiler.js
index c02e0232a..25d45d192 100644
--- a/lib/handlebars/compiler/javascript-compiler.js
+++ b/lib/handlebars/compiler/javascript-compiler.js
@@ -731,9 +731,7 @@ JavaScriptCompiler.prototype = {
     this.flushInline();
 
     var stack = this.incrStack();
-    if (item) {
-      this.pushSource(stack + " = " + item + ";");
-    }
+    this.pushSource(stack + " = " + item + ";");
     this.compileStack.push(stack);
     return stack;
   },
@@ -745,54 +743,40 @@ JavaScriptCompiler.prototype = {
         createdStack,
         usedLiteral;
 
-    // If we are currently inline then we want to merge the inline statement into the
-    // replacement statement via ','
-    if (inline) {
-      var top = this.popStack(true);
+    /* istanbul ignore next */
+    if (!this.isInline()) {
+      throw new Exception('replaceStack on non-inline');
+    }
 
-      if (top instanceof Literal) {
-        // Literals do not need to be inlined
-        stack = top.value;
-        usedLiteral = true;
+    // We want to merge the inline statement into the replacement statement via ','
+    var top = this.popStack(true);
 
-        if (chain) {
-          prefix = stack;
-        }
-      } else {
-        // Get or create the current stack name for use by the inline
-        createdStack = !this.stackSlot;
-        var name = !createdStack ? this.topStackName() : this.incrStack();
+    if (top instanceof Literal) {
+      // Literals do not need to be inlined
+      stack = top.value;
+      usedLiteral = true;
 
-        prefix = '(' + this.push(name) + ' = ' + top + (chain ? ')' : '),');
-        stack = this.topStack();
+      if (chain) {
+        prefix = stack;
       }
     } else {
+      // Get or create the current stack name for use by the inline
+      createdStack = !this.stackSlot;
+      var name = !createdStack ? this.topStackName() : this.incrStack();
+
+      prefix = '(' + this.push(name) + ' = ' + top + (chain ? ')' : '),');
       stack = this.topStack();
     }
 
     var item = callback.call(this, stack);
 
-    if (inline) {
-      if (!usedLiteral) {
-        this.popStack();
-      }
-      if (createdStack) {
-        this.stackSlot--;
-      }
-      this.push('(' + prefix + item + ')');
-    } else {
-      // Prevent modification of the context depth variable. Through replaceStack
-      if (!/^stack/.test(stack)) {
-        stack = this.nextStack();
-      }
-
-      this.pushSource(stack + " = (" + prefix + item + ");");
+    if (!usedLiteral) {
+      this.popStack();
     }
-    return stack;
-  },
-
-  nextStack: function() {
-    return this.pushStack();
+    if (createdStack) {
+      this.stackSlot--;
+    }
+    this.push('(' + prefix + item + ')');
   },
 
   incrStack: function() {

From 0528b9164488edc6031510997eb9dab4b64e4ea2 Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Sat, 23 Aug 2014 16:33:14 -0500
Subject: [PATCH 63/91] Add original value tracking to ContentNode

---
 lib/handlebars/compiler/ast.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lib/handlebars/compiler/ast.js b/lib/handlebars/compiler/ast.js
index 5a47e2b59..66533a5bf 100644
--- a/lib/handlebars/compiler/ast.js
+++ b/lib/handlebars/compiler/ast.js
@@ -112,7 +112,7 @@ var AST = {
   ContentNode: function(string, locInfo) {
     LocationInfo.call(this, locInfo);
     this.type = "content";
-    this.string = string;
+    this.original = this.string = string;
   },
 
   HashNode: function(pairs, locInfo) {

From ccd803ff15e98186a82988a67d9aeff411612af4 Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Sat, 23 Aug 2014 16:37:30 -0500
Subject: [PATCH 64/91] Replace string value rather than add omit flag

---
 lib/handlebars/compiler/compiler.js |  2 +-
 lib/handlebars/compiler/helpers.js  |  5 ++-
 spec/ast.js                         | 68 ++++++++++++++---------------
 3 files changed, 38 insertions(+), 37 deletions(-)

diff --git a/lib/handlebars/compiler/compiler.js b/lib/handlebars/compiler/compiler.js
index a4bd09a57..c7fdf3d3e 100644
--- a/lib/handlebars/compiler/compiler.js
+++ b/lib/handlebars/compiler/compiler.js
@@ -194,7 +194,7 @@ Compiler.prototype = {
   },
 
   content: function(content) {
-    if (!content.omit) {
+    if (content.string) {
       this.opcode('appendContent', content.string);
     }
   },
diff --git a/lib/handlebars/compiler/helpers.js b/lib/handlebars/compiler/helpers.js
index 5d8fec177..8ba7b407b 100644
--- a/lib/handlebars/compiler/helpers.js
+++ b/lib/handlebars/compiler/helpers.js
@@ -140,7 +140,7 @@ function checkWhitespace(isRoot, next1, next2, disallowIndent) {
 function omitRight(statements, i) {
   var first = statements[i == null ? 0 : i + 1];
   if (first) {
-    first.omit = true;
+    first.string = '';
   }
 }
 
@@ -158,6 +158,7 @@ function omitLeft(statements, i) {
 
   // We omit the last node if it's whitespace only and not preceeded by a non-content node.
   if (last && /^[\s]*$/.test(last.string) && (!prev || prev.type === 'content')) {
-    return last.omit = true;
+    last.string = '';
+    return true;
   }
 }
diff --git a/spec/ast.js b/spec/ast.js
index cba148b14..20988eb1b 100644
--- a/spec/ast.js
+++ b/spec/ast.js
@@ -70,7 +70,7 @@ describe('ast', function() {
   describe('BlockNode', function() {
     it('should throw on mustache mismatch', function() {
       shouldThrow(function() {
-        handlebarsEnv.parse("\n  {{#foo}}{{/bar}}")
+        handlebarsEnv.parse("\n  {{#foo}}{{/bar}}");
       }, Handlebars.Exception, "foo doesn't match bar - 2:2");
     });
 
@@ -255,8 +255,8 @@ describe('ast', function() {
     describe('mustache', function() {
       it('does not mark mustaches as standalone', function() {
         var ast = Handlebars.parse('  {{comment}} ');
-        equals(ast.statements[0].omit, undefined);
-        equals(ast.statements[2].omit, undefined);
+        equals(!!ast.statements[0].string, true);
+        equals(!!ast.statements[2].string, true);
       });
     });
     describe('blocks', function() {
@@ -264,51 +264,51 @@ describe('ast', function() {
         var ast = Handlebars.parse(' {{# comment}} \nfoo\n {{else}} \n  bar \n  {{/comment}} '),
             block = ast.statements[1];
 
-        equals(ast.statements[0].omit, true);
+        equals(ast.statements[0].string, '');
 
-        equals(block.program.statements[0].omit, true);
+        equals(block.program.statements[0].string, '');
         equals(block.program.statements[1].string, 'foo\n');
-        equals(block.program.statements[2].omit, true);
+        equals(block.program.statements[2].string, '');
 
-        equals(block.inverse.statements[0].omit, true);
+        equals(block.inverse.statements[0].string, '');
         equals(block.inverse.statements[1].string, '  bar \n');
-        equals(block.inverse.statements[2].omit, true);
+        equals(block.inverse.statements[2].string, '');
 
-        equals(ast.statements[2].omit, true);
+        equals(ast.statements[2].string, '');
       });
       it('marks initial block mustaches as standalone', function() {
         var ast = Handlebars.parse('{{# comment}} \nfoo\n {{/comment}}'),
             block = ast.statements[0];
 
-        equals(block.program.statements[0].omit, true);
+        equals(block.program.statements[0].string, '');
         equals(block.program.statements[1].string, 'foo\n');
-        equals(block.program.statements[2].omit, true);
+        equals(block.program.statements[2].string, '');
       });
       it('marks mustaches with children as standalone', function() {
         var ast = Handlebars.parse('{{# comment}} \n{{foo}}\n {{/comment}}'),
             block = ast.statements[0];
 
-        equals(block.program.statements[0].omit, true);
+        equals(block.program.statements[0].string, '');
         equals(block.program.statements[1].id.original, 'foo');
         equals(block.program.statements[2].omit, undefined);
-        equals(block.program.statements[3].omit, true);
+        equals(block.program.statements[3].string, '');
       });
       it('marks nested block mustaches as standalone', function() {
         var ast = Handlebars.parse('{{#foo}} \n{{# comment}} \nfoo\n {{else}} \n  bar \n  {{/comment}} \n{{/foo}}'),
             statements = ast.statements[0].program.statements,
             block = statements[1];
 
-        equals(statements[0].omit, true);
+        equals(statements[0].string, '');
 
-        equals(block.program.statements[0].omit, true);
+        equals(block.program.statements[0].string, '');
         equals(block.program.statements[1].string, 'foo\n');
-        equals(block.program.statements[2].omit, true);
+        equals(block.program.statements[2].string, '');
 
-        equals(block.inverse.statements[0].omit, true);
+        equals(block.inverse.statements[0].string, '');
         equals(block.inverse.statements[1].string, '  bar \n');
-        equals(block.inverse.statements[2].omit, true);
+        equals(block.inverse.statements[2].string, '');
 
-        equals(statements[0].omit, true);
+        equals(statements[0].string, '');
       });
       it('does not mark nested block mustaches as standalone', function() {
         var ast = Handlebars.parse('{{#foo}} {{# comment}} \nfoo\n {{else}} \n  bar \n  {{/comment}} {{/foo}}'),
@@ -319,9 +319,9 @@ describe('ast', function() {
 
         equals(block.program.statements[0].omit, undefined);
         equals(block.program.statements[1].string, 'foo\n');
-        equals(block.program.statements[2].omit, true);
+        equals(block.program.statements[2].string, '');
 
-        equals(block.inverse.statements[0].omit, true);
+        equals(block.inverse.statements[0].string, '');
         equals(block.inverse.statements[1].string, '  bar \n');
         equals(block.inverse.statements[2].omit, undefined);
 
@@ -334,9 +334,9 @@ describe('ast', function() {
 
         equals(block.program.statements[0].omit, undefined);
         equals(block.program.statements[1].string, 'foo\n');
-        equals(block.program.statements[2].omit, true);
+        equals(block.program.statements[2].string, '');
 
-        equals(block.inverse.statements[0].omit, true);
+        equals(block.inverse.statements[0].string, '');
         equals(block.inverse.statements[1].string, '  bar \n');
         equals(block.inverse.statements[2].omit, undefined);
 
@@ -349,27 +349,27 @@ describe('ast', function() {
 
         equals(ast.statements[0].omit, undefined);
 
-        equals(block.program.statements[0].omit, true);
+        equals(block.program.statements[0].string, '');
         equals(block.program.statements[1].string, 'foo\n');
-        equals(block.program.statements[2].omit, true);
+        equals(block.program.statements[2].string, '');
 
-        equals(block.inverse.statements[0].omit, true);
+        equals(block.inverse.statements[0].string, '');
         equals(block.inverse.statements[1].string, '  bar \n');
-        equals(block.inverse.statements[2].omit, true);
+        equals(block.inverse.statements[2].string, '');
 
-        equals(ast.statements[2].omit, true);
+        equals(ast.statements[2].string, '');
       });
     });
     describe('partials', function() {
       it('marks partial as standalone', function() {
         var ast = Handlebars.parse('{{> partial }} ');
-        equals(ast.statements[1].omit, true);
+        equals(ast.statements[1].string, '');
       });
       it('marks indented partial as standalone', function() {
         var ast = Handlebars.parse('  {{> partial }} ');
-        equals(ast.statements[0].omit, true);
+        equals(ast.statements[0].string, '');
         equals(ast.statements[1].indent, '  ');
-        equals(ast.statements[2].omit, true);
+        equals(ast.statements[2].string, '');
       });
       it('marks those around content as not standalone', function() {
         var ast = Handlebars.parse('a{{> partial }}');
@@ -382,12 +382,12 @@ describe('ast', function() {
     describe('comments', function() {
       it('marks comment as standalone', function() {
         var ast = Handlebars.parse('{{! comment }} ');
-        equals(ast.statements[1].omit, true);
+        equals(ast.statements[1].string, '');
       });
       it('marks indented comment as standalone', function() {
         var ast = Handlebars.parse('  {{! comment }} ');
-        equals(ast.statements[0].omit, true);
-        equals(ast.statements[2].omit, true);
+        equals(ast.statements[0].string, '');
+        equals(ast.statements[2].string, '');
       });
       it('marks those around content as not standalone', function() {
         var ast = Handlebars.parse('a{{! comment }}');

From 84d646bb5d5a8bb01bfb9465ee1078161f069742 Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Sat, 23 Aug 2014 17:44:20 -0500
Subject: [PATCH 65/91] Move strip processing into AST helper logic

We already have to track these behaviors for the standalone parsing and rather than having two whitespace pruning implementations this moves all of the behavior into one place.

Fixes #852
---
 lib/handlebars/compiler/compiler.js           | 14 +---
 lib/handlebars/compiler/helpers.js            | 84 +++++++++++++++----
 .../compiler/javascript-compiler.js           | 22 -----
 spec/whitespace-control.js                    | 14 ++++
 4 files changed, 81 insertions(+), 53 deletions(-)

diff --git a/lib/handlebars/compiler/compiler.js b/lib/handlebars/compiler/compiler.js
index c7fdf3d3e..29718d08e 100644
--- a/lib/handlebars/compiler/compiler.js
+++ b/lib/handlebars/compiler/compiler.js
@@ -69,19 +69,7 @@ Compiler.prototype = {
   },
 
   accept: function(node) {
-    var strip = node.strip || {},
-        ret;
-    if (strip.left) {
-      this.opcode('strip');
-    }
-
-    ret = this[node.type](node);
-
-    if (strip.right) {
-      this.opcode('strip');
-    }
-
-    return ret;
+    return this[node.type](node);
   },
 
   program: function(program) {
diff --git a/lib/handlebars/compiler/helpers.js b/lib/handlebars/compiler/helpers.js
index 8ba7b407b..c93b81147 100644
--- a/lib/handlebars/compiler/helpers.js
+++ b/lib/handlebars/compiler/helpers.js
@@ -26,13 +26,22 @@ export function prepareBlock(mustache, program, inverseAndProgram, close, invert
     closeStandalone: isPrevWhitespace((inverse || program).statements)
   };
 
+  if (mustache.strip.right) {
+    omitRight(program.statements, null, true);
+  }
+
   if (inverse) {
     var inverseStrip = inverseAndProgram.strip;
 
-    program.strip.left = mustache.strip.right;
-    program.strip.right = inverseStrip.left;
-    inverse.strip.left = inverseStrip.right;
-    inverse.strip.right = close.strip.left;
+    if (inverseStrip.left) {
+      omitLeft(program.statements, null, true);
+    }
+    if (inverseStrip.right) {
+      omitRight(inverse.statements, null, true);
+    }
+    if (close.strip.left) {
+      omitLeft(inverse.statements, null, true);
+    }
 
     // Find standalone else statments
     if (isPrevWhitespace(program.statements)
@@ -42,8 +51,9 @@ export function prepareBlock(mustache, program, inverseAndProgram, close, invert
       omitRight(inverse.statements);
     }
   } else {
-    program.strip.left = mustache.strip.right;
-    program.strip.right = close.strip.left;
+    if (close.strip.left) {
+      omitLeft(program.statements, null, true);
+    }
   }
 
   if (inverted) {
@@ -70,6 +80,13 @@ export function prepareProgram(statements, isRoot) {
         closeStandalone = strip.closeStandalone && _isNextWhitespace,
         inlineStandalone = strip.inlineStandalone && _isPrevWhitespace && _isNextWhitespace;
 
+    if (strip.right) {
+      omitRight(statements, i, true);
+    }
+    if (strip.left) {
+      omitLeft(statements, i, true);
+    }
+
     if (inlineStandalone) {
       omitRight(statements, i);
 
@@ -137,10 +154,25 @@ function checkWhitespace(isRoot, next1, next2, disallowIndent) {
 // I.e. {{foo}}' ' will mark the ' ' node as omitted.
 //
 // If i is undefined, then the first child will be marked as such.
-function omitRight(statements, i) {
-  var first = statements[i == null ? 0 : i + 1];
-  if (first) {
-    first.string = '';
+//
+// If mulitple is truthy then all whitespace will be stripped out until non-whitespace
+// content is met.
+function omitRight(statements, i, multiple) {
+  i = i == null ? 0 : i + 1;
+
+  var current = statements[i];
+  while (current) {
+    if (current.type !== 'content') {
+      return;
+    }
+
+    current.string = current.string.replace(/^[\s]+/, '');
+
+    if (multiple && !current.string) {
+      current = statements[++i];
+    } else {
+      return;
+    }
   }
 }
 
@@ -148,17 +180,33 @@ function omitRight(statements, i) {
 // I.e. ' '{{foo}} will mark the ' ' node as omitted.
 //
 // If i is undefined then the last child will be marked as such.
-function omitLeft(statements, i) {
-  if (i === undefined) {
+//
+// If mulitple is truthy then all whitespace will be stripped out until non-whitespace
+// content is met.
+function omitLeft(statements, i, multiple) {
+  if (i == null) {
     i = statements.length;
   }
 
-  var last = statements[i-1],
-      prev = statements[i-2];
+  var current = statements[--i],
+      prev = statements[i-1];
 
-  // We omit the last node if it's whitespace only and not preceeded by a non-content node.
-  if (last && /^[\s]*$/.test(last.string) && (!prev || prev.type === 'content')) {
-    last.string = '';
-    return true;
+  while (current) {
+    if (current.type !== 'content') {
+      return;
+    }
+
+    // We omit the last node if it's whitespace only and not preceeded by a non-content node.
+    if (multiple || (/^[\s]*$/.test(current.string) && (!prev || prev.type === 'content'))) {
+      current.string = current.string.replace(/[\s]+$/, '');
+
+      if (multiple && !current.string) {
+        current = statements[--i];
+      } else {
+        return true;
+      }
+    } else {
+      return;
+    }
   }
 }
diff --git a/lib/handlebars/compiler/javascript-compiler.js b/lib/handlebars/compiler/javascript-compiler.js
index 25d45d192..fcd58fdcc 100644
--- a/lib/handlebars/compiler/javascript-compiler.js
+++ b/lib/handlebars/compiler/javascript-compiler.js
@@ -85,11 +85,6 @@ JavaScriptCompiler.prototype = {
       opcode = opcodes[i];
 
       this[opcode.opcode].apply(this, opcode.args);
-
-      // Reset the stripNext flag if it was not set by this operation.
-      if (opcode.opcode !== this.stripNext) {
-        this.stripNext = false;
-      }
     }
 
     // Flush any trailing content that might be pending.
@@ -280,27 +275,10 @@ JavaScriptCompiler.prototype = {
     if (this.pendingContent) {
       content = this.pendingContent + content;
     }
-    if (this.stripNext) {
-      content = content.replace(/^\s+/, '');
-    }
 
     this.pendingContent = content;
   },
 
-  // [strip]
-  //
-  // On stack, before: ...
-  // On stack, after: ...
-  //
-  // Removes any trailing whitespace from the prior content node and flags
-  // the next operation for stripping if it is a content node.
-  strip: function() {
-    if (this.pendingContent) {
-      this.pendingContent = this.pendingContent.replace(/\s+$/, '');
-    }
-    this.stripNext = 'strip';
-  },
-
   // [append]
   //
   // On stack, before: value, ...
diff --git a/spec/whitespace-control.js b/spec/whitespace-control.js
index 2088ed8b7..86364292e 100644
--- a/spec/whitespace-control.js
+++ b/spec/whitespace-control.js
@@ -1,3 +1,5 @@
+/*global shouldCompileTo, shouldCompileToWithPartials */
+
 describe('whitespace control', function() {
   it('should strip whitespace around mustache calls', function() {
     var hash = {foo: 'bar<'};
@@ -8,6 +10,8 @@ describe('whitespace control', function() {
 
     shouldCompileTo(' {{~&foo~}} ', hash, 'bar<');
     shouldCompileTo(' {{~{foo}~}} ', hash, 'bar<');
+
+    shouldCompileTo('1\n{{foo~}} \n\n 23\n{{bar}}4', {}, '1\n23\n4');
   });
 
   describe('blocks', function() {
@@ -18,6 +22,9 @@ describe('whitespace control', function() {
       shouldCompileTo(' {{#if foo~}} bar {{/if~}} ', hash, ' bar ');
       shouldCompileTo(' {{~#if foo}} bar {{~/if}} ', hash, ' bar ');
       shouldCompileTo(' {{#if foo}} bar {{/if}} ', hash, '  bar  ');
+
+      shouldCompileTo(' \n\n{{~#if foo~}} \n\nbar \n\n{{~/if~}}\n\n ', hash, 'bar');
+      shouldCompileTo(' a\n\n{{~#if foo~}} \n\nbar \n\n{{~/if~}}\n\na ', hash, ' abara ');
     });
     it('should strip whitespace around inverse block calls', function() {
       var hash = {};
@@ -26,6 +33,8 @@ describe('whitespace control', function() {
       shouldCompileTo(' {{^if foo~}} bar {{/if~}} ', hash, ' bar ');
       shouldCompileTo(' {{~^if foo}} bar {{~/if}} ', hash, ' bar ');
       shouldCompileTo(' {{^if foo}} bar {{/if}} ', hash, '  bar  ');
+
+      shouldCompileTo(' \n\n{{~^if foo~}} \n\nbar \n\n{{~/if~}}\n\n ', hash, 'bar');
     });
     it('should strip whitespace around complex block calls', function() {
       var hash = {foo: 'bar<'};
@@ -37,6 +46,9 @@ describe('whitespace control', function() {
 
       shouldCompileTo('{{#if foo~}} bar {{~else~}} baz {{~/if}}', hash, 'bar');
 
+      shouldCompileTo('\n\n{{~#if foo~}} \n\nbar \n\n{{~^~}} \n\nbaz \n\n{{~/if~}}\n\n', hash, 'bar');
+      shouldCompileTo('\n\n{{~#if foo~}} \n\n{{{foo}}} \n\n{{~^~}} \n\nbaz \n\n{{~/if~}}\n\n', hash, 'bar<');
+
       hash = {};
 
       shouldCompileTo('{{#if foo~}} bar {{~^~}} baz {{~/if}}', hash, 'baz');
@@ -45,6 +57,8 @@ describe('whitespace control', function() {
       shouldCompileTo('{{#if foo~}} bar {{~^}} baz {{/if}}', hash, ' baz ');
 
       shouldCompileTo('{{#if foo~}} bar {{~else~}} baz {{~/if}}', hash, 'baz');
+
+      shouldCompileTo('\n\n{{~#if foo~}} \n\nbar \n\n{{~^~}} \n\nbaz \n\n{{~/if~}}\n\n', hash, 'baz');
     });
   });
 

From cde008b49f3cc64711a23c107fa53ad612954aef Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Sat, 23 Aug 2014 17:44:45 -0500
Subject: [PATCH 66/91] Cleanup from code coverage report

---
 lib/handlebars/compiler/helpers.js            |  4 +--
 .../compiler/javascript-compiler.js           | 18 +++++------
 lib/precompiler.js                            |  6 ++--
 spec/precompiler.js                           | 30 +++++++++++++++++++
 src/parser-prefix.js                          |  1 +
 5 files changed, 43 insertions(+), 16 deletions(-)

diff --git a/lib/handlebars/compiler/helpers.js b/lib/handlebars/compiler/helpers.js
index c93b81147..dd322998a 100644
--- a/lib/handlebars/compiler/helpers.js
+++ b/lib/handlebars/compiler/helpers.js
@@ -135,12 +135,12 @@ function isNextWhitespace(statements, i, isRoot) {
 
   return checkWhitespace(isRoot, statements[i+1], statements[i+2]);
 }
-function checkWhitespace(isRoot, next1, next2, disallowIndent) {
+function checkWhitespace(isRoot, next1, next2) {
   if (!next1) {
     return isRoot;
   } else if (next1.type === 'content') {
     // Check if the previous node is empty or whitespace only
-    if (disallowIndent ? !next1.string : /^[\s]*$/.test(next1.string)) {
+    if (/^[\s]*$/.test(next1.string)) {
       if (next2) {
         return next2.type === 'content' || /\n$/.test(next1.string);
       } else {
diff --git a/lib/handlebars/compiler/javascript-compiler.js b/lib/handlebars/compiler/javascript-compiler.js
index fcd58fdcc..e53b4f243 100644
--- a/lib/handlebars/compiler/javascript-compiler.js
+++ b/lib/handlebars/compiler/javascript-compiler.js
@@ -50,7 +50,7 @@ JavaScriptCompiler.prototype = {
 
   compile: function(environment, options, context, asObject) {
     this.environment = environment;
-    this.options = options || {};
+    this.options = options;
     this.stringParams = this.options.stringParams;
     this.trackIds = this.options.trackIds;
     this.precompile = !asObject;
@@ -361,9 +361,9 @@ JavaScriptCompiler.prototype = {
           return ' != null ? ' + lookup + ' : ' + current;
         } else {
           // Otherwise we can use generic falsy handling
-          return (falsy ? ' && ' : ' != null && ') + lookup;
+          return ' && ' + lookup;
         }
-      }, true);
+      });
     }
   },
 
@@ -385,7 +385,7 @@ JavaScriptCompiler.prototype = {
     for (var i = 0; i < len; i++) {
       this.replaceStack(function(current) {
         return ' && ' + this.nameLookup(current, parts[i], 'data');
-      }, true);
+      });
     }
   },
 
@@ -714,7 +714,7 @@ JavaScriptCompiler.prototype = {
     return stack;
   },
 
-  replaceStack: function(callback, chain) {
+  replaceStack: function(callback) {
     var prefix = '',
         inline = this.isInline(),
         stack,
@@ -731,18 +731,14 @@ JavaScriptCompiler.prototype = {
 
     if (top instanceof Literal) {
       // Literals do not need to be inlined
-      stack = top.value;
+      prefix = stack = top.value;
       usedLiteral = true;
-
-      if (chain) {
-        prefix = stack;
-      }
     } else {
       // Get or create the current stack name for use by the inline
       createdStack = !this.stackSlot;
       var name = !createdStack ? this.topStackName() : this.incrStack();
 
-      prefix = '(' + this.push(name) + ' = ' + top + (chain ? ')' : '),');
+      prefix = '(' + this.push(name) + ' = ' + top + ')';
       stack = this.topStack();
     }
 
diff --git a/lib/precompiler.js b/lib/precompiler.js
index bae923c60..676402226 100644
--- a/lib/precompiler.js
+++ b/lib/precompiler.js
@@ -60,7 +60,7 @@ module.exports.cli = function(opts) {
     output.push(opts.namespace);
     output.push(' || {};\n');
   }
-  function processTemplate(template, root, explicit) {
+  function processTemplate(template, root) {
     var path = template,
         stat = fs.statSync(path);
     if (stat.isDirectory()) {
@@ -71,7 +71,7 @@ module.exports.cli = function(opts) {
           processTemplate(path, root || template);
         }
       });
-    } else if (explicit || extension.test(path)) {
+    } else {
       var data = fs.readFileSync(path, 'utf8');
 
       if (opts.bom && data.indexOf('\uFEFF') === 0) {
@@ -112,7 +112,7 @@ module.exports.cli = function(opts) {
   }
 
   opts.templates.forEach(function(template) {
-    processTemplate(template, opts.root, true);
+    processTemplate(template, opts.root);
   });
 
   // Output the content
diff --git a/spec/precompiler.js b/spec/precompiler.js
index 57fc280e6..3b66353a6 100644
--- a/spec/precompiler.js
+++ b/spec/precompiler.js
@@ -75,9 +75,39 @@ describe('precompiler', function() {
     Precompiler.cli({templates: [__dirname + '/artifacts/empty.handlebars'], amd: true, extension: 'handlebars'});
     equal(/template\(amd\)/.test(log), true);
   });
+  it('should output multiple amd', function() {
+    Handlebars.precompile = function() { return 'amd'; };
+    Precompiler.cli({templates: [__dirname + '/artifacts'], amd: true, extension: 'handlebars'});
+    equal(/return templates/.test(log), true);
+    equal(/template\(amd\)/.test(log), true);
+  });
+  it('should output amd partials', function() {
+    Handlebars.precompile = function() { return 'amd'; };
+    Precompiler.cli({templates: [__dirname + '/artifacts/empty.handlebars'], amd: true, partial: true, extension: 'handlebars'});
+    equal(/return Handlebars\.partials\['empty'\]/.test(log), true);
+    equal(/template\(amd\)/.test(log), true);
+  });
+  it('should output multiple amd partials', function() {
+    Handlebars.precompile = function() { return 'amd'; };
+    Precompiler.cli({templates: [__dirname + '/artifacts'], amd: true, partial: true, extension: 'handlebars'});
+    equal(/return Handlebars\.partials\[/.test(log), false);
+    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);
   });
+
+  it('should set data flag', function() {
+    Handlebars.precompile = function(data, options) { equal(options.data, true); return 'simple'; };
+    Precompiler.cli({templates: [__dirname + '/artifacts/empty.handlebars'], simple: true, extension: 'handlebars', data: true});
+    equal(log, 'simple\n');
+  });
+
+  it('should set known helpers', function() {
+    Handlebars.precompile = function(data, options) { equal(options.knownHelpers.foo, true); return 'simple'; };
+    Precompiler.cli({templates: [__dirname + '/artifacts/empty.handlebars'], simple: true, extension: 'handlebars', known: 'foo'});
+    equal(log, 'simple\n');
+  });
 });
diff --git a/src/parser-prefix.js b/src/parser-prefix.js
index 685b6ec3b..a4ba487c6 100644
--- a/src/parser-prefix.js
+++ b/src/parser-prefix.js
@@ -1 +1,2 @@
 /* jshint ignore:start */
+/* istanbul ignore next */

From 0c7c37df6aa31a3e70f9eecadcaaa25928921c94 Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Sat, 23 Aug 2014 18:26:35 -0500
Subject: [PATCH 67/91] Restore helperMissing for ambiguous statements

Fixes #318 (regression)
Partial fix for #783
---
 lib/handlebars/compiler/javascript-compiler.js |  3 ++-
 spec/helpers.js                                | 15 +++++++++++++++
 2 files changed, 17 insertions(+), 1 deletion(-)

diff --git a/lib/handlebars/compiler/javascript-compiler.js b/lib/handlebars/compiler/javascript-compiler.js
index e53b4f243..c59fd7005 100644
--- a/lib/handlebars/compiler/javascript-compiler.js
+++ b/lib/handlebars/compiler/javascript-compiler.js
@@ -551,6 +551,7 @@ JavaScriptCompiler.prototype = {
   // `knownHelpersOnly` flags at compile-time.
   invokeAmbiguous: function(name, helperCall) {
     this.aliases.functionType = '"function"';
+    this.aliases.helperMissing = 'helpers.helperMissing';
     this.useRegister('helper');
 
     var nonHelper = this.popStack();
@@ -561,7 +562,7 @@ JavaScriptCompiler.prototype = {
     var helperName = this.lastHelper = this.nameLookup('helpers', name, 'helper');
 
     this.push(
-      '((helper = ' + helperName + ' || ' + nonHelper
+      '((helper = (helper = ' + helperName + ' || ' + nonHelper + ') != null ? helper : helperMissing'
         + (helper.paramsInit ? '),(' + helper.paramsInit : '') + '),'
       + '(typeof helper === functionType ? helper.call(' + helper.callParams + ') : helper))');
   },
diff --git a/spec/helpers.js b/spec/helpers.js
index e3b586349..e604e91c8 100644
--- a/spec/helpers.js
+++ b/spec/helpers.js
@@ -440,6 +440,21 @@ describe('helpers', function() {
 
       shouldCompileTo(string, [context, helpers], "Hello <a>world</a>");
     });
+
+    it("if a value is not found, custom helperMissing is used", function() {
+      var string = "{{hello}} {{link_to}}";
+      var context = { hello: "Hello", world: "world" };
+
+      var helpers = {
+        helperMissing: function(options) {
+          if(options.name === "link_to") {
+            return new Handlebars.SafeString("<a>winning</a>");
+          }
+        }
+      };
+
+      shouldCompileTo(string, [context, helpers], "Hello <a>winning</a>");
+    });
   });
 
   describe("knownHelpers", function() {

From 2b3fdf7ea80bb7af8e6317c2ce30a4ce3236e7f6 Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Sat, 23 Aug 2014 18:52:30 -0500
Subject: [PATCH 68/91] Additional test coverage cleanup

Also fixes the template._child implementation which broke with the depthed work.
---
 lib/handlebars/compiler/printer.js |  6 ++----
 lib/handlebars/runtime.js          |  7 +++----
 spec/precompiler.js                | 19 ++++++++++++++++++-
 spec/runtime.js                    | 20 ++++++++++++++++++++
 4 files changed, 43 insertions(+), 9 deletions(-)

diff --git a/lib/handlebars/compiler/printer.js b/lib/handlebars/compiler/printer.js
index be66d73d3..7654245c5 100644
--- a/lib/handlebars/compiler/printer.js
+++ b/lib/handlebars/compiler/printer.js
@@ -10,16 +10,14 @@ export function PrintVisitor() {
 
 PrintVisitor.prototype = new Visitor();
 
-PrintVisitor.prototype.pad = function(string, newline) {
+PrintVisitor.prototype.pad = function(string) {
   var out = "";
 
   for(var i=0,l=this.padding; i<l; i++) {
     out = out + "  ";
   }
 
-  out = out + string;
-
-  if(newline !== false) { out = out + "\n"; }
+  out = out + string + "\n";
   return out;
 };
 
diff --git a/lib/handlebars/runtime.js b/lib/handlebars/runtime.js
index 0780fbf6d..eb942d559 100644
--- a/lib/handlebars/runtime.js
+++ b/lib/handlebars/runtime.js
@@ -150,12 +150,11 @@ export function template(templateSpec, env) {
   };
 
   ret._child = function(i) {
-    if (templateSpec.depth) {
-      throw new Exception('_child can not be used with depthed methods');
+    if (templateSpec.useDepths) {
+      throw new Exception('_child can not be used with depthed templates');
     }
 
-    // TODO : Fix this
-    return container.programWithDepth(i);
+    return program(container, i, templateSpec[i]);
   };
   return ret;
 }
diff --git a/spec/precompiler.js b/spec/precompiler.js
index 3b66353a6..0310681cd 100644
--- a/spec/precompiler.js
+++ b/spec/precompiler.js
@@ -1,5 +1,7 @@
 /*global shouldThrow */
 
+var uglify = require('uglify-js');
+
 describe('precompiler', function() {
   // NOP Under non-node environments
   if (typeof process === 'undefined') {
@@ -12,10 +14,13 @@ describe('precompiler', function() {
   var log,
       logFunction,
 
-      precompile;
+      precompile,
+      minify;
 
   beforeEach(function() {
     precompile = Handlebars.precompile;
+    minify = uglify.minify;
+
     logFunction = console.log;
     log = '';
     console.log = function() {
@@ -24,6 +29,7 @@ describe('precompiler', function() {
   });
   afterEach(function() {
     Handlebars.precompile = precompile;
+    uglify.minify = minify;
     console.log = logFunction;
   });
 
@@ -65,6 +71,10 @@ describe('precompiler', function() {
     equal(/'empty'/.test(log), true);
     equal(/'example_1'/.test(log), true);
   });
+  it('should protect from regexp patterns', function() {
+    Precompiler.cli({templates: [__dirname + '/artifacts'], extension: 'hb(s'});
+    // Success is not throwing
+  });
   it('should output simple templates', function() {
     Handlebars.precompile = function() { return 'simple'; };
     Precompiler.cli({templates: [__dirname + '/artifacts/empty.handlebars'], simple: true, extension: 'handlebars'});
@@ -110,4 +120,11 @@ describe('precompiler', function() {
     Precompiler.cli({templates: [__dirname + '/artifacts/empty.handlebars'], simple: true, extension: 'handlebars', known: 'foo'});
     equal(log, 'simple\n');
   });
+
+  it('should output minimized templates', function() {
+    Handlebars.precompile = function() { return 'amd'; };
+    uglify.minify = function() { return {code: 'min'}; };
+    Precompiler.cli({templates: [__dirname + '/artifacts/empty.handlebars'], min: true, extension: 'handlebars'});
+    equal(log, 'min');
+  });
 });
diff --git a/spec/runtime.js b/spec/runtime.js
index 4431a6877..dc6e5126e 100644
--- a/spec/runtime.js
+++ b/spec/runtime.js
@@ -33,4 +33,24 @@ describe('runtime', function() {
       }, Error, /Template was precompiled with an older version of Handlebars than the current runtime/);
     });
   });
+
+  describe('#child', function() {
+    if (!Handlebars.compile) {
+      return;
+    }
+
+    it('should throw for depthed methods', function() {
+      shouldThrow(function() {
+        var template = Handlebars.compile('{{#foo}}{{../bar}}{{/foo}}');
+        template._setup({});
+        template._setup({});
+        template._child(1);
+      }, Error, '_child can not be used with depthed templates');
+    });
+    it('should expose child template', function() {
+      var template = Handlebars.compile('{{#foo}}bar{{/foo}}');
+      equal(template._child(1)(), 'bar');
+      equal(template._child(1)(), 'bar');
+    });
+  });
 });

From e5aeca648aeffb890d64f5fd4ba8e20a3fadef48 Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Sun, 24 Aug 2014 15:28:22 -0500
Subject: [PATCH 69/91] Create CONTRIBUTING.md

Cleans up some of the process focused content in the README and also adds nice message to github issue tracker, etc.
---
 CONTRIBUTING.md | 74 +++++++++++++++++++++++++++++++++++++++++++++++++
 README.markdown | 73 +-----------------------------------------------
 2 files changed, 75 insertions(+), 72 deletions(-)
 create mode 100644 CONTRIBUTING.md

diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 000000000..84d1dee3b
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,74 @@
+# How to Contribute
+
+## Reporting Issues
+
+Should you run into other issues with the project, please file an [issue][issue]. When filing issues a repo case running against the latest version of the code is appreciated. A [jsfiddle template][jsfiddle] is available for this purpose. As new versions are released this link will be updated to point to a fiddle template with the latest version.
+
+Failing test pull requests are welcomed for issues that can easily be reproduced and this also helps ensure that your issue won't regress in the future once it's fixed.
+
+## Pull Requests
+
+We also accept [pull requests][pull-request]!
+
+Generally we like to see pull requests that
+- Maintain the existing code style
+- Are focused on a single change (i.e. avoid large refactoring or style adjustments in untouched code if not the primary goal of the pull request)
+- Have [good commit messages](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html)
+- Have tests
+- Don't significantly decrease the current code coverage (see coverage/lcov-report/index.html)
+
+## Building
+
+To build Handlebars.js you'll need a few things installed.
+
+* Node.js
+* [Grunt](http://gruntjs.com/getting-started)
+
+Project dependencies may be installed via `npm install`.
+
+To build Handlebars.js from scratch, you'll want to run `grunt`
+in the root of the project. That will build Handlebars and output the
+results to the dist/ folder. To re-run tests, run `grunt test` or `npm test`.
+You can also run our set of benchmarks with `grunt bench`.
+
+The `grunt dev` implements watching for tests and allows for in browser testing at `http://localhost:9999/spec/`.
+
+If you notice any problems, please report them to the GitHub issue tracker at
+[http://github.com/wycats/handlebars.js/issues](http://github.com/wycats/handlebars.js/issues).
+
+## Ember testing
+
+The current ember distribution should be tested as part of the handlebars release process. This requires building the `handlebars-source` gem locally and then executing the ember test script.
+
+```sh
+grunt build release
+export HANDLEBARS_PATH=`pwd`
+
+cd $emberRepoDir
+bundle exec rake clean
+bundle exec rake test
+```
+
+## Releasing
+
+Handlebars utilizes the [release yeoman generator][generator-release] to perform most release tasks.
+
+A full release may be completed with the following:
+
+```
+yo release
+npm publish
+yo release:publish cdnjs handlebars.js dist/cdnjs/
+yo release:publish components handlebars.js dist/components/
+
+cd dist/components/
+gem build handlebars-source.gemspec
+gem push handlebars-source-*.gem
+```
+
+After this point the handlebars site needs to be updated to point to the new version numbers. The jsfiddle link should be updated to point to the most recent distribution for all instances in our documentation.
+
+[generator-release]: https://github.com/walmartlabs/generator-release
+[pull-request]: https://github.com/wycats/handlebars.js/pull/new/master
+[issue]: https://github.com/wycats/handlebars.js/issues/new
+[jsfiddle]: http://jsfiddle.net/9D88g/11/
diff --git a/README.markdown b/README.markdown
index 866661eef..f96c9818d 100644
--- a/README.markdown
+++ b/README.markdown
@@ -346,12 +346,6 @@ Handlebars deviates from the Mustache spec in a few key ways:
 - Recrusive value lookup is not enabled by default. The compile time `compat` flag must be set to enable this functionality. Users should note that there is a performance cost for enabling this flag. The exact cost varies by template, but it's recommended that performance sensitive operations should avoid this mode and instead opt for explicit path references.
 - The optional Mustache-style lambdas are not supported. Instead Handlebars provides it's own lambda resolution that follows the behaviors of helpers.
 
-Building
---------
-
-To build handlebars, just run `grunt build`, and the build will output to the `dist` directory.
-
-
 Upgrading
 ---------
 
@@ -359,16 +353,9 @@ See [release-notes.md](https://github.com/wycats/handlebars.js/blob/master/relea
 
 Known Issues
 ------------
-* Runtime/precompiler mismatches: Often result in errors like "can not find method match of object" or similar. Please verify the version of the runtime and the version used to precompile templates if odd issues occur after upgrading one component or another.
-* Handlebars.js can be cryptic when there's an error while rendering.
-* Using a variable, helper, or partial named `class` causes errors in IE browsers. (Instead, use `className`)
 
-Reporting Issues
-----------------
+See [FAQ.md](https://github.com/wycats/handlebars.js/blob/master/FAQ.md) for known issues and common pitfalls.
 
-Should you run into other issues with the project, please file an [issue][issue]. When filing issues a repo case running against the latest version of the code is appreciated. A [jsfiddle template][jsfiddle] is available for this purpose. As new versions are released the bitly link will be updated to point to a fiddle template with the latest version.
-
-We also accept [pull requests][pull-request]!
 
 Handlebars in the Wild
 ----------------------
@@ -409,68 +396,10 @@ External Resources
 
 Have a project using Handlebars? Send us a [pull request][pull-request]!
 
-Helping Out
------------
-
-To build Handlebars.js you'll need a few things installed.
-
-* Node.js
-* [Grunt](http://gruntjs.com/getting-started)
-
-Project dependencies may be installed via `npm install`.
-
-To build Handlebars.js from scratch, you'll want to run `grunt`
-in the root of the project. That will build Handlebars and output the
-results to the dist/ folder. To re-run tests, run `grunt test` or `npm test`.
-You can also run our set of benchmarks with `grunt bench`.
-
-The `grunt dev` implements watching for tests and allows for in browser testing at `http://localhost:9999/spec/`.
-
-If you notice any problems, please report them to the GitHub issue tracker at
-[http://github.com/wycats/handlebars.js/issues](http://github.com/wycats/handlebars.js/issues).
-Feel free to contact commondream or wycats through GitHub with any other
-questions or feature requests. To submit changes fork the project and
-send a pull request.
-
-### Ember testing
-
-The current ember distribution should be tested as part of the handlebars release process. This requires building the `handlebars-source` gem locally and then executing the ember test script.
-
-```sh
-grunt build release
-export HANDLEBARS_PATH=`pwd`
-
-cd $emberRepoDir
-bundle exec rake clean
-bundle exec rake test
-```
-
-### Releasing
-
-Handlebars utilizes the [release yeoman generator][generator-release] to perform most release tasks.
-
-A full release may be completed with the following:
-
-```
-yo release
-npm publish
-yo release:publish cdnjs handlebars.js dist/cdnjs/
-yo release:publish components handlebars.js dist/components/
-
-cd dist/components/
-gem build handlebars-source.gemspec
-gem push handlebars-source-*.gem
-```
-
-After this point the handlebars site needs to be updated to point to the new version numbers. The jsfiddle link should be updated to point to the most recent distribution for all instances in our documentation.
-
 License
 -------
 Handlebars.js is released under the MIT license.
 
 [bower-repo]: https://github.com/components/handlebars.js
 [builds-page]: http://builds.handlebarsjs.com.s3.amazonaws.com/bucket-listing.html?sort=lastmod&sortdir=desc
-[generator-release]: https://github.com/walmartlabs/generator-release
 [pull-request]: https://github.com/wycats/handlebars.js/pull/new/master
-[issue]: https://github.com/wycats/handlebars.js/issues/new
-[jsfiddle]: http://jsfiddle.net/9D88g/11/

From 3e6bf8b0f61ed0840c17f50cd67bc0e500cb9df3 Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Sun, 24 Aug 2014 15:45:10 -0500
Subject: [PATCH 70/91] Link to current performance tests in readme

---
 README.markdown | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/README.markdown b/README.markdown
index f96c9818d..4f8ccb73c 100644
--- a/README.markdown
+++ b/README.markdown
@@ -335,8 +335,7 @@ does have some big performance advantages. Justin Marney, a.k.a.
 [gotascii](http://github.com/gotascii), confirmed that with an
 [independent test](http://sorescode.com/2010/09/12/benchmarks.html). The
 rewritten Handlebars (current version) is faster than the old version,
-and we will have some benchmarks in the near future.
-
+with many [performance tests](https://travis-ci.org/wycats/handlebars.js/builds/33392182#L538) being 5 to 7 times faster than the Mustache equivalent.
 
 Mustache Compatibilty
 ---------------------

From 4ce474d3e4c0f3a9a9ae1600129a20680ba0df48 Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Sun, 24 Aug 2014 15:56:27 -0500
Subject: [PATCH 71/91] Update issue filing guidelines

---
 CONTRIBUTING.md | 6 ++++--
 FAQ.md          | 4 ++--
 2 files changed, 6 insertions(+), 4 deletions(-)

diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 84d1dee3b..e986cca3a 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -2,9 +2,11 @@
 
 ## Reporting Issues
 
-Should you run into other issues with the project, please file an [issue][issue]. When filing issues a repo case running against the latest version of the code is appreciated. A [jsfiddle template][jsfiddle] is available for this purpose. As new versions are released this link will be updated to point to a fiddle template with the latest version.
+Please see our [FAQ](https://github.com/wycats/handlebars.js/blob/master/FAQ.md) for common issues that people run into.
 
-Failing test pull requests are welcomed for issues that can easily be reproduced and this also helps ensure that your issue won't regress in the future once it's fixed.
+Should you run into other issues with the project, please don't hesitate to let us know by filing an [issue][issue]! In general we are going to ask for an example of the problem failing, which can be as simple as a jsfiddle/jsbin/etc. We've put together a jsfiddle [template][jsfiddle] to ease this. (We will keep this link up to date as new releases occur, so feel free to check back here)
+
+Pull requests containing only failing thats demonstrating the issue are welcomed and this also helps ensure that your issue won't regress in the future once it's fixed.
 
 ## Pull Requests
 
diff --git a/FAQ.md b/FAQ.md
index 4e8b85625..8068b8405 100644
--- a/FAQ.md
+++ b/FAQ.md
@@ -1,7 +1,7 @@
 # Frequently Asked Questions
 
 1. How can I file a bug report:
-  Please don't hesitate to let us know if you find something wrong! In general we are going to ask for an example of the problem failing, which can be as simple as a jsfiddle/jsbin/etc. We've put together a jsfiddle [template](http://jsfiddle.net/9D88g/11/) to ease this. (We will keep this link up to date as new releases occur, so feel free to check back here)
+  See our guidelines on [reporting issues](https://github.com/wycats/handlebars.js/blob/master/CONTRIBUTING.md#reporting-issues).
 
 1. Why is it slower when compiling?
   The Handlebars compiler must parse the template and construct a JavaScript program which can then be run. Under some environments such as older mobile devices this can have a performance impact which can be avoided by precompiling. Generally it's recommended that precompilation and the runtime library be used on all clients.
@@ -34,7 +34,7 @@
   ```
   On the client side.
 
-  Should these match, please file an issue with us, per our [issue filing guidelines](https://github.com/wycats/handlebars.js/blob/master/README.markdown#reporting-issues).
+  Should these match, please file an issue with us, per our [issue filing guidelines](https://github.com/wycats/handlebars.js/blob/master/CONTRIBUTING.md#reporting-issues).
 
 1. Why doesn't IE like the `default` name in the AMD module?
   Some browsers such as particular versions of IE treat `default` as a reserved word in JavaScript source files. To safely use this you need to reference this via the `Handlebars['default']` lookup method. This is an unfortunate side effect of the shims necessary to backport the handlebars ES6 code to all current browsers.

From 477a26913a66593c1384e83af866a71347336ce2 Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Sun, 24 Aug 2014 18:16:11 -0500
Subject: [PATCH 72/91] Optimize compiler opcode call

---
 lib/handlebars/compiler/compiler.js | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/lib/handlebars/compiler/compiler.js b/lib/handlebars/compiler/compiler.js
index 29718d08e..673cde39f 100644
--- a/lib/handlebars/compiler/compiler.js
+++ b/lib/handlebars/compiler/compiler.js
@@ -1,6 +1,8 @@
 import Exception from "../exception";
 import {isArray} from "../utils";
 
+var slice = [].slice;
+
 export function Compiler() {}
 
 // the foundHelper register will disambiguate helper lookup from finding a
@@ -292,7 +294,7 @@ Compiler.prototype = {
 
   // HELPERS
   opcode: function(name) {
-    this.opcodes.push({ opcode: name, args: [].slice.call(arguments, 1) });
+    this.opcodes.push({ opcode: name, args: slice.call(arguments, 1) });
   },
 
   addDepth: function(depth) {

From 501a640330187f179955c92cb5624c76c1963f81 Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Mon, 25 Aug 2014 00:32:57 -0500
Subject: [PATCH 73/91] Allow passing depths to _child

---
 lib/handlebars/compiler/compiler.js |  4 ++--
 lib/handlebars/runtime.js           |  8 ++++----
 spec/runtime.js                     | 11 +++++++++--
 3 files changed, 15 insertions(+), 8 deletions(-)

diff --git a/lib/handlebars/compiler/compiler.js b/lib/handlebars/compiler/compiler.js
index 673cde39f..eafee7490 100644
--- a/lib/handlebars/compiler/compiler.js
+++ b/lib/handlebars/compiler/compiler.js
@@ -426,11 +426,11 @@ export function compile(input, options, env) {
     }
     return compiled._setup(options);
   };
-  ret._child = function(i) {
+  ret._child = function(i, data, depths) {
     if (!compiled) {
       compiled = compileInput();
     }
-    return compiled._child(i);
+    return compiled._child(i, data, depths);
   };
   return ret;
 }
diff --git a/lib/handlebars/runtime.js b/lib/handlebars/runtime.js
index eb942d559..05f34fe4c 100644
--- a/lib/handlebars/runtime.js
+++ b/lib/handlebars/runtime.js
@@ -149,12 +149,12 @@ export function template(templateSpec, env) {
     }
   };
 
-  ret._child = function(i) {
-    if (templateSpec.useDepths) {
-      throw new Exception('_child can not be used with depthed templates');
+  ret._child = function(i, data, depths) {
+    if (templateSpec.useDepths && !depths) {
+      throw new Exception('must pass parent depths');
     }
 
-    return program(container, i, templateSpec[i]);
+    return program(container, i, templateSpec[i], data, depths);
   };
   return ret;
 }
diff --git a/spec/runtime.js b/spec/runtime.js
index dc6e5126e..d33dd747c 100644
--- a/spec/runtime.js
+++ b/spec/runtime.js
@@ -39,18 +39,25 @@ describe('runtime', function() {
       return;
     }
 
-    it('should throw for depthed methods', function() {
+    it('should throw for depthed methods without depths', function() {
       shouldThrow(function() {
         var template = Handlebars.compile('{{#foo}}{{../bar}}{{/foo}}');
+        // Calling twice to hit the non-compiled case.
         template._setup({});
         template._setup({});
         template._child(1);
-      }, Error, '_child can not be used with depthed templates');
+      }, Error, 'must pass parent depths');
     });
     it('should expose child template', function() {
       var template = Handlebars.compile('{{#foo}}bar{{/foo}}');
+        // Calling twice to hit the non-compiled case.
       equal(template._child(1)(), 'bar');
       equal(template._child(1)(), 'bar');
     });
+    it('should render depthed content', function() {
+      var template = Handlebars.compile('{{#foo}}{{../bar}}{{/foo}}');
+        // Calling twice to hit the non-compiled case.
+      equal(template._child(1, undefined, [{bar: 'baz'}])(), 'baz');
+    });
   });
 });

From c9886fdc0fc3d291cd19190e53687739125be34f Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Mon, 25 Aug 2014 00:33:46 -0500
Subject: [PATCH 74/91] FAQ tweaks

---
 FAQ.md | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/FAQ.md b/FAQ.md
index 8068b8405..748f68a68 100644
--- a/FAQ.md
+++ b/FAQ.md
@@ -3,11 +3,14 @@
 1. How can I file a bug report:
   See our guidelines on [reporting issues](https://github.com/wycats/handlebars.js/blob/master/CONTRIBUTING.md#reporting-issues).
 
+1. Why isn't my Mustache template working?
+  Handlebars deviates from Mustache slightly on a few behaviors. These variations are documented in our [readme](https://github.com/wycats/handlebars.js#differences-between-handlebarsjs-and-mustache).
+
 1. Why is it slower when compiling?
   The Handlebars compiler must parse the template and construct a JavaScript program which can then be run. Under some environments such as older mobile devices this can have a performance impact which can be avoided by precompiling. Generally it's recommended that precompilation and the runtime library be used on all clients.
 
 1. Why doesn't this work with Content Security Policy restrictions?
-  Handlebars generates a dynamic function for each template which can cause issues with pages that have enabled Content Policy. It's recommended that templates are precompiled or the `unsafe-eval` policy is enabled for sites that must generate dynamic templates at runtime.
+  When not using the precompiler, Handlebars generates a dynamic function for each template which can cause issues with pages that have enabled Content Policy. It's recommended that templates are precompiled or the `unsafe-eval` policy is enabled for sites that must generate dynamic templates at runtime.
 
 1. How can I include script tags in my template?
   If loading the template via an inlined `<script type="text/x-handlebars">` tag then you may need to break up the script tag with an empty comment to avoid browser parser errors:
@@ -34,6 +37,8 @@
   ```
   On the client side.
 
+  We include the built client libraries in the npm package for those who want to be certain that they are using the same client libraries as the compiler.
+
   Should these match, please file an issue with us, per our [issue filing guidelines](https://github.com/wycats/handlebars.js/blob/master/CONTRIBUTING.md#reporting-issues).
 
 1. Why doesn't IE like the `default` name in the AMD module?

From 31f6fd7131e2d725666cf86c6042d40fa91d604e Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Mon, 25 Aug 2014 00:34:05 -0500
Subject: [PATCH 75/91] Merge mustache compatibility sections

---
 README.markdown | 15 ++++++++-------
 1 file changed, 8 insertions(+), 7 deletions(-)

diff --git a/README.markdown b/README.markdown
index 4f8ccb73c..77552860d 100644
--- a/README.markdown
+++ b/README.markdown
@@ -252,6 +252,14 @@ You can also use real html comments if you want them to end up in the output.
 ```
 
 
+### Compatibility
+
+There are a few Mustache behaviors that Handlebars does not implement.
+- Handlebars deviates from Mustache slightly in that it does not perform recursive lookup by default. The compile time `compat` flag must be set to enable this functionality. Users should note that there is a performance cost for enabling this flag. The exact cost varies by template, but it's recommended that performance sensitive operations should avoid this mode and instead opt for explicit path references.
+- The optional Mustache-style lambdas are not supported. Instead Handlebars provides it's own lambda resolution that follows the behaviors of helpers.
+- Alternative delimeters are not supported.
+
+
 Precompiling Templates
 ----------------------
 
@@ -337,13 +345,6 @@ does have some big performance advantages. Justin Marney, a.k.a.
 rewritten Handlebars (current version) is faster than the old version,
 with many [performance tests](https://travis-ci.org/wycats/handlebars.js/builds/33392182#L538) being 5 to 7 times faster than the Mustache equivalent.
 
-Mustache Compatibilty
----------------------
-
-Handlebars deviates from the Mustache spec in a few key ways:
-- Alternative delimeters are not supported
-- Recrusive value lookup is not enabled by default. The compile time `compat` flag must be set to enable this functionality. Users should note that there is a performance cost for enabling this flag. The exact cost varies by template, but it's recommended that performance sensitive operations should avoid this mode and instead opt for explicit path references.
-- The optional Mustache-style lambdas are not supported. Instead Handlebars provides it's own lambda resolution that follows the behaviors of helpers.
 
 Upgrading
 ---------

From 980c38cebf9a12f80acbbc1fd97120c63f495e6d Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Mon, 25 Aug 2014 00:34:19 -0500
Subject: [PATCH 76/91] Expose escapeExpression on the root object

---
 lib/handlebars.runtime.js | 1 +
 1 file changed, 1 insertion(+)

diff --git a/lib/handlebars.runtime.js b/lib/handlebars.runtime.js
index e77aae000..a05812bad 100644
--- a/lib/handlebars.runtime.js
+++ b/lib/handlebars.runtime.js
@@ -16,6 +16,7 @@ var create = function() {
   hb.SafeString = SafeString;
   hb.Exception = Exception;
   hb.Utils = Utils;
+  hb.escapeExpression = Utils.escapeExpression;
 
   hb.VM = runtime;
   hb.template = function(spec) {

From dc9a2ed7eb97ecda0dab800c8e776cbf6a4d4555 Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Mon, 25 Aug 2014 13:27:28 -0500
Subject: [PATCH 77/91] Refactor content blocks to ignore lines

We can simplify our previous standalone determination logic by merging content blocks again.

Fixes #854
---
 lib/handlebars/compiler/helpers.js | 88 +++++++++++-------------------
 spec/ast.js                        | 63 ++++++---------------
 spec/blocks.js                     |  5 +-
 src/handlebars.l                   |  4 +-
 4 files changed, 54 insertions(+), 106 deletions(-)

diff --git a/lib/handlebars/compiler/helpers.js b/lib/handlebars/compiler/helpers.js
index dd322998a..758c740d5 100644
--- a/lib/handlebars/compiler/helpers.js
+++ b/lib/handlebars/compiler/helpers.js
@@ -93,7 +93,7 @@ export function prepareProgram(statements, isRoot) {
       if (omitLeft(statements, i)) {
         // If we are on a standalone node, save the indent info for partials
         if (current.type === 'partial') {
-          current.indent = statements[i-1].original;
+          current.indent = (/([ \t]+$)/).exec(statements[i-1].original) ? RegExp.$1 : '';
         }
       }
     }
@@ -114,39 +114,36 @@ export function prepareProgram(statements, isRoot) {
   return statements;
 }
 
-function isPrevWhitespace(statements, i, isRoot, disallowIndent) {
+function isPrevWhitespace(statements, i, isRoot) {
   if (i === undefined) {
     i = statements.length;
   }
 
   // Nodes that end with newlines are considered whitespace (but are special
   // cased for strip operations)
-  var prev = statements[i-1];
-  if (prev && /\n$/.test(prev.string)) {
-    return true;
+  var prev = statements[i-1],
+      sibling = statements[i-2];
+  if (!prev) {
+    return isRoot;
   }
 
-  return checkWhitespace(isRoot, prev, statements[i-2]);
+  if (prev.type === 'content') {
+    return (sibling || !isRoot ? (/\r?\n\s*?$/) : (/(^|\r?\n)\s*?$/)).test(prev.original);
+  }
 }
 function isNextWhitespace(statements, i, isRoot) {
   if (i === undefined) {
     i = -1;
   }
 
-  return checkWhitespace(isRoot, statements[i+1], statements[i+2]);
-}
-function checkWhitespace(isRoot, next1, next2) {
-  if (!next1) {
+  var next = statements[i+1],
+      sibling = statements[i+2];
+  if (!next) {
     return isRoot;
-  } else if (next1.type === 'content') {
-    // Check if the previous node is empty or whitespace only
-    if (/^[\s]*$/.test(next1.string)) {
-      if (next2) {
-        return next2.type === 'content' || /\n$/.test(next1.string);
-      } else {
-        return isRoot || (next1.string.indexOf('\n') >= 0);
-      }
-    }
+  }
+
+  if (next.type === 'content') {
+    return (sibling || !isRoot ? (/^\s*?\r?\n/) : (/^\s*?(\r?\n|$)/)).test(next.original);
   }
 }
 
@@ -158,22 +155,14 @@ function checkWhitespace(isRoot, next1, next2) {
 // If mulitple is truthy then all whitespace will be stripped out until non-whitespace
 // content is met.
 function omitRight(statements, i, multiple) {
-  i = i == null ? 0 : i + 1;
-
-  var current = statements[i];
-  while (current) {
-    if (current.type !== 'content') {
-      return;
-    }
-
-    current.string = current.string.replace(/^[\s]+/, '');
-
-    if (multiple && !current.string) {
-      current = statements[++i];
-    } else {
-      return;
-    }
+  var current = statements[i == null ? 0 : i + 1];
+  if (!current || current.type !== 'content' || (!multiple && current.rightStripped)) {
+    return;
   }
+
+  var original = current.string;
+  current.string = current.string.replace(multiple ? (/^\s+/) : (/^[ \t]*\r?\n?/), '');
+  current.rightStripped = current.string !== original;
 }
 
 // Marks the node to the left of the position as omitted.
@@ -184,29 +173,14 @@ function omitRight(statements, i, multiple) {
 // If mulitple is truthy then all whitespace will be stripped out until non-whitespace
 // content is met.
 function omitLeft(statements, i, multiple) {
-  if (i == null) {
-    i = statements.length;
+  var current = statements[i == null ? statements.length - 1 : i - 1];
+  if (!current || current.type !== 'content' || (!multiple && current.leftStripped)) {
+    return;
   }
 
-  var current = statements[--i],
-      prev = statements[i-1];
-
-  while (current) {
-    if (current.type !== 'content') {
-      return;
-    }
-
-    // We omit the last node if it's whitespace only and not preceeded by a non-content node.
-    if (multiple || (/^[\s]*$/.test(current.string) && (!prev || prev.type === 'content'))) {
-      current.string = current.string.replace(/[\s]+$/, '');
-
-      if (multiple && !current.string) {
-        current = statements[--i];
-      } else {
-        return true;
-      }
-    } else {
-      return;
-    }
-  }
+  // We omit the last node if it's whitespace only and not preceeded by a non-content node.
+  var original = current.string;
+  current.string = current.string.replace(multiple ? (/\s+$/) : (/[ \t]+$/), '');
+  current.leftStripped = current.string !== original;
+  return current.leftStripped;
 }
diff --git a/spec/ast.js b/spec/ast.js
index 20988eb1b..c28e876e8 100644
--- a/spec/ast.js
+++ b/spec/ast.js
@@ -222,29 +222,28 @@ describe('ast', function() {
     });
 
     it('gets line numbers correct when newlines appear', function(){
-      testColumns(statements[2], 1, 2, 21, 0);
-      testColumns(statements[3], 2, 2, 0, 8);
+      testColumns(statements[2], 1, 2, 21, 8);
     });
 
     it('gets MustacheNode line numbers correct across newlines', function(){
-      var secondMustacheNode = statements[4];
+      var secondMustacheNode = statements[3];
       testColumns(secondMustacheNode, 2, 2, 8, 22);
      });
 
      it('gets the block helper information correct', function(){
-       var blockHelperNode = statements[7];
+       var blockHelperNode = statements[5];
        testColumns(blockHelperNode, 3, 7, 8, 23);
      });
 
      it('correctly records the line numbers the program of a block helper', function(){
-       var blockHelperNode = statements[7],
+       var blockHelperNode = statements[5],
            program = blockHelperNode.program;
 
        testColumns(program, 3, 5, 8, 5);
      });
 
      it('correctly records the line numbers of an inverse of a block helper', function(){
-       var blockHelperNode = statements[7],
+       var blockHelperNode = statements[5],
            inverse = blockHelperNode.inverse;
 
        testColumns(inverse, 5, 7, 5, 0);
@@ -266,13 +265,8 @@ describe('ast', function() {
 
         equals(ast.statements[0].string, '');
 
-        equals(block.program.statements[0].string, '');
-        equals(block.program.statements[1].string, 'foo\n');
-        equals(block.program.statements[2].string, '');
-
-        equals(block.inverse.statements[0].string, '');
-        equals(block.inverse.statements[1].string, '  bar \n');
-        equals(block.inverse.statements[2].string, '');
+        equals(block.program.statements[0].string, 'foo\n');
+        equals(block.inverse.statements[0].string, '  bar \n');
 
         equals(ast.statements[2].string, '');
       });
@@ -280,9 +274,7 @@ describe('ast', function() {
         var ast = Handlebars.parse('{{# comment}} \nfoo\n {{/comment}}'),
             block = ast.statements[0];
 
-        equals(block.program.statements[0].string, '');
-        equals(block.program.statements[1].string, 'foo\n');
-        equals(block.program.statements[2].string, '');
+        equals(block.program.statements[0].string, 'foo\n');
       });
       it('marks mustaches with children as standalone', function() {
         var ast = Handlebars.parse('{{# comment}} \n{{foo}}\n {{/comment}}'),
@@ -290,8 +282,7 @@ describe('ast', function() {
 
         equals(block.program.statements[0].string, '');
         equals(block.program.statements[1].id.original, 'foo');
-        equals(block.program.statements[2].omit, undefined);
-        equals(block.program.statements[3].string, '');
+        equals(block.program.statements[2].string, '\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}}'),
@@ -300,13 +291,8 @@ describe('ast', function() {
 
         equals(statements[0].string, '');
 
-        equals(block.program.statements[0].string, '');
-        equals(block.program.statements[1].string, 'foo\n');
-        equals(block.program.statements[2].string, '');
-
-        equals(block.inverse.statements[0].string, '');
-        equals(block.inverse.statements[1].string, '  bar \n');
-        equals(block.inverse.statements[2].string, '');
+        equals(block.program.statements[0].string, 'foo\n');
+        equals(block.inverse.statements[0].string, '  bar \n');
 
         equals(statements[0].string, '');
       });
@@ -317,13 +303,8 @@ describe('ast', function() {
 
         equals(statements[0].omit, undefined);
 
-        equals(block.program.statements[0].omit, undefined);
-        equals(block.program.statements[1].string, 'foo\n');
-        equals(block.program.statements[2].string, '');
-
-        equals(block.inverse.statements[0].string, '');
-        equals(block.inverse.statements[1].string, '  bar \n');
-        equals(block.inverse.statements[2].omit, undefined);
+        equals(block.program.statements[0].string, ' \nfoo\n');
+        equals(block.inverse.statements[0].string, '  bar \n  ');
 
         equals(statements[0].omit, undefined);
       });
@@ -332,13 +313,8 @@ describe('ast', function() {
             statements = ast.statements[0].program.statements,
             block = statements[0];
 
-        equals(block.program.statements[0].omit, undefined);
-        equals(block.program.statements[1].string, 'foo\n');
-        equals(block.program.statements[2].string, '');
-
-        equals(block.inverse.statements[0].string, '');
-        equals(block.inverse.statements[1].string, '  bar \n');
-        equals(block.inverse.statements[2].omit, undefined);
+        equals(block.program.statements[0].string, ' \nfoo\n');
+        equals(block.inverse.statements[0].string, '  bar \n  ');
 
         equals(statements[0].omit, undefined);
       });
@@ -349,13 +325,8 @@ describe('ast', function() {
 
         equals(ast.statements[0].omit, undefined);
 
-        equals(block.program.statements[0].string, '');
-        equals(block.program.statements[1].string, 'foo\n');
-        equals(block.program.statements[2].string, '');
-
-        equals(block.inverse.statements[0].string, '');
-        equals(block.inverse.statements[1].string, '  bar \n');
-        equals(block.inverse.statements[2].string, '');
+        equals(block.program.statements[0].string, 'foo\n');
+        equals(block.inverse.statements[0].string, '  bar \n');
 
         equals(ast.statements[2].string, '');
       });
diff --git a/spec/blocks.js b/spec/blocks.js
index 9a5cb40d4..a172970d8 100644
--- a/spec/blocks.js
+++ b/spec/blocks.js
@@ -102,9 +102,12 @@ describe('blocks', function() {
         'No people\n');
       shouldCompileTo('{{#none}}\n{{.}}\n{{^}}\n{{none}}\n{{/none}}\n', {none: 'No people'},
         'No people\n');
-      shouldCompileTo('\n{{#people}}\n{{name}}\n{{^}}\n{{none}}\n{{/people}}\n', {none: 'No people'},
+      shouldCompileTo('{{#people}}\n{{name}}\n{{^}}\n{{none}}\n{{/people}}\n', {none: 'No people'},
         'No people\n');
     });
+    it('should handle nesting', function() {
+      shouldCompileTo('{{#data}}\n{{#if true}}\n{{.}}\n{{/if}}\n{{/data}}\nOK.', {data: [1, 3, 5]}, '1\n3\n5\nOK.');
+    });
   });
 
   describe('compat mode', function() {
diff --git a/src/handlebars.l b/src/handlebars.l
index f775cc4df..0f420e7ee 100644
--- a/src/handlebars.l
+++ b/src/handlebars.l
@@ -28,7 +28,7 @@ ID    [^\s!"#%-,\.\/;->@\[-\^`\{-~]+/{LOOKAHEAD}
 
 %%
 
-[^\x00\n]*?\n?/("{{")           {
+[^\x00]*?/("{{")                {
                                    if(yytext.slice(-2) === "\\\\") {
                                      strip(0,1);
                                      this.begin("mu");
@@ -41,7 +41,7 @@ ID    [^\s!"#%-,\.\/;->@\[-\^`\{-~]+/{LOOKAHEAD}
                                    if(yytext) return 'CONTENT';
                                  }
 
-([^\x00\n]+\n?|\n)               return 'CONTENT';
+[^\x00]+                         return 'CONTENT';
 
 // marks CONTENT up to the next mustache or escaped mustache
 <emu>[^\x00]{2,}?/("{{"|"\\{{"|"\\\\{{"|<<EOF>>) {

From 64f422b57fd44c96e9e90f989080bfe9984093ab Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Mon, 25 Aug 2014 13:49:20 -0500
Subject: [PATCH 78/91] Rev runtime version

---
 lib/handlebars/base.js     | 5 +++--
 spec/expected/empty.amd.js | 2 +-
 2 files changed, 4 insertions(+), 3 deletions(-)

diff --git a/lib/handlebars/base.js b/lib/handlebars/base.js
index e11f03265..2527fade2 100644
--- a/lib/handlebars/base.js
+++ b/lib/handlebars/base.js
@@ -2,14 +2,15 @@ module Utils from "./utils";
 import Exception from "./exception";
 
 export var VERSION = "2.0.0-alpha.4";
-export var COMPILER_REVISION = 5;
+export var COMPILER_REVISION = 6;
 
 export var REVISION_CHANGES = {
   1: '<= 1.0.rc.2', // 1.0.rc.2 is actually rev2 but doesn't report it
   2: '== 1.0.0-rc.3',
   3: '== 1.0.0-rc.4',
   4: '== 1.x.x',
-  5: '>= 2.0.0'
+  5: '>= 2.0.0-alpha.x',
+  6: '>= 2.0.0-beta.1'
 };
 
 var isArray = Utils.isArray,
diff --git a/spec/expected/empty.amd.js b/spec/expected/empty.amd.js
index e9616c344..1cf3298eb 100644
--- a/spec/expected/empty.amd.js
+++ b/spec/expected/empty.amd.js
@@ -1,6 +1,6 @@
 define(['handlebars.runtime'], function(Handlebars) {
   Handlebars = Handlebars["default"];  var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
-return templates['empty'] = template({"compiler":[5,">= 2.0.0"],"main":function(depth0,helpers,partials,data) {
+return templates['empty'] = template({"compiler":[6,">= 2.0.0-beta.1"],"main":function(depth0,helpers,partials,data) {
   return "";
 },"useData":true});
 });

From 36f00db0737ddffecfe13a6551c1cf7dcfeeb621 Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Mon, 25 Aug 2014 21:13:31 -0500
Subject: [PATCH 79/91] Fix compiler version tag

---
 lib/handlebars/base.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lib/handlebars/base.js b/lib/handlebars/base.js
index 2527fade2..2dfca0235 100644
--- a/lib/handlebars/base.js
+++ b/lib/handlebars/base.js
@@ -9,7 +9,7 @@ export var REVISION_CHANGES = {
   2: '== 1.0.0-rc.3',
   3: '== 1.0.0-rc.4',
   4: '== 1.x.x',
-  5: '>= 2.0.0-alpha.x',
+  5: '== 2.0.0-alpha.x',
   6: '>= 2.0.0-beta.1'
 };
 

From 24d63b69665d01407a02a2837f814f8c3310365c Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Mon, 25 Aug 2014 21:14:56 -0500
Subject: [PATCH 80/91] Update Ember testing instructions

---
 CONTRIBUTING.md | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index e986cca3a..75aa9f553 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -43,12 +43,13 @@ If you notice any problems, please report them to the GitHub issue tracker at
 The current ember distribution should be tested as part of the handlebars release process. This requires building the `handlebars-source` gem locally and then executing the ember test script.
 
 ```sh
+npm link
 grunt build release
-export HANDLEBARS_PATH=`pwd`
+cp dist/*.js $emberRepoDir/bower_components/handlebars/
 
 cd $emberRepoDir
-bundle exec rake clean
-bundle exec rake test
+npm link handlebars
+npm test
 ```
 
 ## Releasing

From 84342b62154921e3f83201e7624114a4cd51f26e Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Mon, 25 Aug 2014 21:15:26 -0500
Subject: [PATCH 81/91] Add template isTop flag

---
 lib/handlebars/runtime.js | 1 +
 1 file changed, 1 insertion(+)

diff --git a/lib/handlebars/runtime.js b/lib/handlebars/runtime.js
index 05f34fe4c..ca85d8478 100644
--- a/lib/handlebars/runtime.js
+++ b/lib/handlebars/runtime.js
@@ -135,6 +135,7 @@ export function template(templateSpec, env) {
 
     return templateSpec.main.call(container, context, container.helpers, container.partials, data, depths);
   };
+  ret.isTop = true;
 
   ret._setup = function(options) {
     if (!options.partial) {

From b41459055a77805922549a4c31c6d403a38d111d Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Mon, 25 Aug 2014 22:04:11 -0500
Subject: [PATCH 82/91] Expose default compatibility wrapper for umd build

---
 lib/handlebars.js         | 2 ++
 lib/handlebars.runtime.js | 2 ++
 2 files changed, 4 insertions(+)

diff --git a/lib/handlebars.js b/lib/handlebars.js
index ffa9c7ab2..039ab3e17 100644
--- a/lib/handlebars.js
+++ b/lib/handlebars.js
@@ -30,4 +30,6 @@ var create = function() {
 Handlebars = create();
 Handlebars.create = create;
 
+Handlebars['default'] = Handlebars;
+
 export default Handlebars;
diff --git a/lib/handlebars.runtime.js b/lib/handlebars.runtime.js
index a05812bad..bc07714f2 100644
--- a/lib/handlebars.runtime.js
+++ b/lib/handlebars.runtime.js
@@ -29,4 +29,6 @@ var create = function() {
 var Handlebars = create();
 Handlebars.create = create;
 
+Handlebars['default'] = Handlebars;
+
 export default Handlebars;

From c98147b31f12d97d219abaa699ef3bae32bbeb5e Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Mon, 25 Aug 2014 22:05:38 -0500
Subject: [PATCH 83/91] Move uglify include past conditional

---
 spec/precompiler.js | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/spec/precompiler.js b/spec/precompiler.js
index 0310681cd..bd19fbf85 100644
--- a/spec/precompiler.js
+++ b/spec/precompiler.js
@@ -1,6 +1,5 @@
 /*global shouldThrow */
 
-var uglify = require('uglify-js');
 
 describe('precompiler', function() {
   // NOP Under non-node environments
@@ -9,7 +8,8 @@ describe('precompiler', function() {
   }
 
   var Handlebars = require('../lib'),
-      Precompiler = require('../lib/precompiler');
+      Precompiler = require('../lib/precompiler'),
+      uglify = require('uglify-js');
 
   var log,
       logFunction,

From 09d3c74c7ffadef2db1260b50f08bc1d59ec3ed6 Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Mon, 25 Aug 2014 22:06:15 -0500
Subject: [PATCH 84/91] Convert build to expose UMD from the default files

---
 Gruntfile.js          |  13 ++++-
 spec/amd-runtime.html |  89 ++++++++++++++++++++++++++++++++++
 spec/umd-runtime.html |  89 ++++++++++++++++++++++++++++++++++
 spec/umd.html         | 109 ++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 299 insertions(+), 1 deletion(-)
 create mode 100644 spec/amd-runtime.html
 create mode 100644 spec/umd-runtime.html
 create mode 100644 spec/umd.html

diff --git a/Gruntfile.js b/Gruntfile.js
index 6cbef6eb7..09e478768 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -43,7 +43,7 @@ module.exports = function(grunt) {
 
     packager: {
       global: {
-        type: 'global',
+        type: 'umd',
         export: 'Handlebars',
         files: [{
           cwd: 'lib/',
@@ -145,6 +145,17 @@ module.exports = function(grunt) {
             {browserName: 'internet explorer', version: 9, platform: 'Windows 7'}
           ]
         }
+      },
+      sanity: {
+        options: {
+          build: process.env.TRAVIS_JOB_ID,
+          urls: ['http://localhost:9999/spec/umd.html?headless=true', 'http://localhost:9999/spec/amd-runtime?headless=true', 'http://localhost:9999/spec/umd-runtime.html?headless=true'],
+          detailedError: true,
+          concurrency: 2,
+          browsers: [
+            {browserName: 'chrome'}
+          ]
+        }
       }
     },
 
diff --git a/spec/amd-runtime.html b/spec/amd-runtime.html
new file mode 100644
index 000000000..19bccdbf5
--- /dev/null
+++ b/spec/amd-runtime.html
@@ -0,0 +1,89 @@
+<html>
+  <head>
+    <title>Mocha</title>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+
+    <link rel="stylesheet" href="/node_modules/mocha/mocha.css" />
+    <style>
+      .headless .suite > h1,
+      .headless .test.pass {
+        display: none;
+      }
+    </style>
+
+    <script>
+      // Show only errors in "headless", non-interactive mode.
+      if (/headless=true/.test(location.href)) {
+        document.documentElement.className = 'headless';
+      }
+    </script>
+    <script src="/node_modules/mocha/mocha.js"></script>
+    <script>
+      mocha.setup('bdd');
+    </script>
+
+    <script src="/spec/env/json2.js"></script>
+    <script src="/spec/env/require.js"></script>
+
+    <script src="/spec/env/common.js"></script>
+    <script>
+      requirejs.config({
+        paths: {
+          'handlebars.runtime': '/dist/handlebars.runtime.amd'
+        }
+      });
+    </script>
+    <script>
+      onload = function(){
+        require(['handlebars.runtime'], function(Handlebars) {
+          describe('runtime', function() {
+            it('should load', function() {
+              equal(!!Handlebars['default'].template, true);
+              equal(!!Handlebars['default'].VERSION, true);
+            });
+          });
+
+          mocha.globals(['mochaResults'])
+          // The test harness leaks under FF. We should have decent global leak coverage from other tests
+          if (!navigator.userAgent.match(/Firefox\/([\d.]+)/)) {
+            mocha.checkLeaks();
+          }
+          var runner = mocha.run();
+
+          //Reporting for saucelabs
+          var failedTests = [];
+          runner.on('end', function(){
+            window.mochaResults = runner.stats;
+            window.mochaResults.reports = failedTests;
+          });
+
+          runner.on('fail', logFailure);
+
+          function logFailure(test, err){
+
+            var flattenTitles = function(test){
+              var titles = [];
+              while (test.parent.title){
+                titles.push(test.parent.title);
+                test = test.parent;
+              }
+              return titles.reverse();
+            };
+
+            failedTests.push({
+              name: test.title,
+              result: false,
+              message: err.message,
+              stack: err.stack,
+              titles: flattenTitles(test)
+            });
+          }
+        });
+      };
+    </script>
+  </head>
+  <body>
+    <div id="mocha"></div>
+  </body>
+</html>
diff --git a/spec/umd-runtime.html b/spec/umd-runtime.html
new file mode 100644
index 000000000..90b91448a
--- /dev/null
+++ b/spec/umd-runtime.html
@@ -0,0 +1,89 @@
+<html>
+  <head>
+    <title>Mocha</title>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+
+    <link rel="stylesheet" href="/node_modules/mocha/mocha.css" />
+    <style>
+      .headless .suite > h1,
+      .headless .test.pass {
+        display: none;
+      }
+    </style>
+
+    <script>
+      // Show only errors in "headless", non-interactive mode.
+      if (/headless=true/.test(location.href)) {
+        document.documentElement.className = 'headless';
+      }
+    </script>
+    <script src="/node_modules/mocha/mocha.js"></script>
+    <script>
+      mocha.setup('bdd');
+    </script>
+
+    <script src="/spec/env/json2.js"></script>
+    <script src="/spec/env/require.js"></script>
+
+    <script src="/spec/env/common.js"></script>
+    <script>
+      requirejs.config({
+        paths: {
+          'handlebars.runtime': '/dist/handlebars.runtime'
+        }
+      });
+    </script>
+    <script>
+      onload = function(){
+        require(['handlebars.runtime'], function(Handlebars) {
+          describe('runtime', function() {
+            it('should load', function() {
+              equal(!!Handlebars.template, true);
+              equal(!!Handlebars.VERSION, true);
+            });
+          });
+
+          mocha.globals(['mochaResults'])
+          // The test harness leaks under FF. We should have decent global leak coverage from other tests
+          if (!navigator.userAgent.match(/Firefox\/([\d.]+)/)) {
+            mocha.checkLeaks();
+          }
+          var runner = mocha.run();
+
+          //Reporting for saucelabs
+          var failedTests = [];
+          runner.on('end', function(){
+            window.mochaResults = runner.stats;
+            window.mochaResults.reports = failedTests;
+          });
+
+          runner.on('fail', logFailure);
+
+          function logFailure(test, err){
+
+            var flattenTitles = function(test){
+              var titles = [];
+              while (test.parent.title){
+                titles.push(test.parent.title);
+                test = test.parent;
+              }
+              return titles.reverse();
+            };
+
+            failedTests.push({
+              name: test.title,
+              result: false,
+              message: err.message,
+              stack: err.stack,
+              titles: flattenTitles(test)
+            });
+          }
+        });
+      };
+    </script>
+  </head>
+  <body>
+    <div id="mocha"></div>
+  </body>
+</html>
diff --git a/spec/umd.html b/spec/umd.html
new file mode 100644
index 000000000..5f3863fc8
--- /dev/null
+++ b/spec/umd.html
@@ -0,0 +1,109 @@
+<html>
+  <head>
+    <title>Mocha</title>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+
+    <link rel="stylesheet" href="/node_modules/mocha/mocha.css" />
+    <style>
+      .headless .suite > h1,
+      .headless .test.pass {
+        display: none;
+      }
+    </style>
+
+    <script>
+      // Show only errors in "headless", non-interactive mode.
+      if (/headless=true/.test(location.href)) {
+        document.documentElement.className = 'headless';
+      }
+    </script>
+    <script src="/node_modules/mocha/mocha.js"></script>
+    <script>
+      mocha.setup('bdd');
+    </script>
+
+    <script src="/spec/env/json2.js"></script>
+    <script src="/spec/env/require.js"></script>
+
+    <script src="/spec/env/common.js"></script>
+    <script>
+      requirejs.config({
+        paths: {
+          handlebars: '/dist/handlebars',
+          tests: '/tmp/tests'
+        }
+      });
+
+      var CompilerContext = {
+        compile: function(template, options) {
+          var templateSpec = handlebarsEnv.precompile(template, options);
+          return handlebarsEnv.template(safeEval(templateSpec));
+        },
+        compileWithPartial: function(template, options) {
+          return handlebarsEnv.compile(template, options);
+        }
+      };
+
+      function safeEval(templateSpec) {
+        try {
+          var ret;
+          eval('ret = ' + templateSpec);
+          return ret;
+        } catch (err) {
+          console.error(templateSpec);
+          throw err;
+        }
+      }
+    </script>
+    <script>
+      onload = function(){
+
+        require(['handlebars'], function(Handlebars) {
+          window.Handlebars = Handlebars;
+
+          require(['tests'], function() {
+            mocha.globals(['mochaResults'])
+            // The test harness leaks under FF. We should have decent global leak coverage from other tests
+            if (!navigator.userAgent.match(/Firefox\/([\d.]+)/)) {
+              mocha.checkLeaks();
+            }
+            var runner = mocha.run();
+
+            //Reporting for saucelabs
+            var failedTests = [];
+            runner.on('end', function(){
+              window.mochaResults = runner.stats;
+              window.mochaResults.reports = failedTests;
+            });
+
+            runner.on('fail', logFailure);
+
+            function logFailure(test, err){
+
+              var flattenTitles = function(test){
+                var titles = [];
+                while (test.parent.title){
+                  titles.push(test.parent.title);
+                  test = test.parent;
+                }
+                return titles.reverse();
+              };
+
+              failedTests.push({
+                name: test.title,
+                result: false,
+                message: err.message,
+                stack: err.stack,
+                titles: flattenTitles(test)
+              });
+            };
+          });
+        });
+      };
+    </script>
+  </head>
+  <body>
+    <div id="mocha"></div>
+  </body>
+</html>

From dfca676d63d8e99985bf787e5f8998def19fd0e3 Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Mon, 25 Aug 2014 22:39:01 -0500
Subject: [PATCH 85/91] Fix test path

---
 Gruntfile.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Gruntfile.js b/Gruntfile.js
index 09e478768..b73974059 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -149,7 +149,7 @@ module.exports = function(grunt) {
       sanity: {
         options: {
           build: process.env.TRAVIS_JOB_ID,
-          urls: ['http://localhost:9999/spec/umd.html?headless=true', 'http://localhost:9999/spec/amd-runtime?headless=true', 'http://localhost:9999/spec/umd-runtime.html?headless=true'],
+          urls: ['http://localhost:9999/spec/umd.html?headless=true', 'http://localhost:9999/spec/amd-runtime.html?headless=true', 'http://localhost:9999/spec/umd-runtime.html?headless=true'],
           detailedError: true,
           concurrency: 2,
           browsers: [

From ca1486b960af514fc2816351c6a4599a8c1ae79e Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Mon, 25 Aug 2014 22:39:17 -0500
Subject: [PATCH 86/91] Prune unused code

---
 lib/handlebars/base.js                         | 2 +-
 lib/handlebars/compiler/ast.js                 | 2 --
 lib/handlebars/compiler/compiler.js            | 2 +-
 lib/handlebars/compiler/javascript-compiler.js | 2 +-
 lib/handlebars/runtime.js                      | 4 +---
 5 files changed, 4 insertions(+), 8 deletions(-)

diff --git a/lib/handlebars/base.js b/lib/handlebars/base.js
index 2dfca0235..0bc2948e4 100644
--- a/lib/handlebars/base.js
+++ b/lib/handlebars/base.js
@@ -194,7 +194,7 @@ function registerDefaultHelpers(instance) {
     instance.log(level, context);
   });
 
-  instance.registerHelper('lookup', function(obj, field, options) {
+  instance.registerHelper('lookup', function(obj, field) {
     return obj && obj[field];
   });
 }
diff --git a/lib/handlebars/compiler/ast.js b/lib/handlebars/compiler/ast.js
index 66533a5bf..49bdc3337 100644
--- a/lib/handlebars/compiler/ast.js
+++ b/lib/handlebars/compiler/ast.js
@@ -37,8 +37,6 @@ var AST = {
       this.sexpr = new AST.SexprNode(rawParams, hash);
     }
 
-    this.sexpr.isRoot = true;
-
     // Support old AST API that stored this info in MustacheNode
     this.id = this.sexpr.id;
     this.params = this.sexpr.params;
diff --git a/lib/handlebars/compiler/compiler.js b/lib/handlebars/compiler/compiler.js
index eafee7490..1aba34b4e 100644
--- a/lib/handlebars/compiler/compiler.js
+++ b/lib/handlebars/compiler/compiler.js
@@ -244,7 +244,7 @@ Compiler.prototype = {
       id.falsy = true;
 
       this.ID(id);
-      this.opcode('invokeHelper', params.length, id.original, id.isSimple, sexpr.isRoot);
+      this.opcode('invokeHelper', params.length, id.original, id.isSimple);
     }
   },
 
diff --git a/lib/handlebars/compiler/javascript-compiler.js b/lib/handlebars/compiler/javascript-compiler.js
index c59fd7005..19066d34f 100644
--- a/lib/handlebars/compiler/javascript-compiler.js
+++ b/lib/handlebars/compiler/javascript-compiler.js
@@ -515,7 +515,7 @@ JavaScriptCompiler.prototype = {
   // and pushes the helper's return value onto the stack.
   //
   // If the helper is not found, `helperMissing` is called.
-  invokeHelper: function(paramSize, name, isSimple, isRoot) {
+  invokeHelper: function(paramSize, name, isSimple) {
     this.aliases.helperMissing = 'helpers.helperMissing';
 
     var nonHelper = this.popStack();
diff --git a/lib/handlebars/runtime.js b/lib/handlebars/runtime.js
index ca85d8478..59701757c 100644
--- a/lib/handlebars/runtime.js
+++ b/lib/handlebars/runtime.js
@@ -120,9 +120,7 @@ export function template(templateSpec, env) {
 
   var ret = function(context, options) {
     options = options || {};
-    var helpers,
-        partials,
-        data = options.data;
+    var data = options.data;
 
     ret._setup(options);
     if (!options.partial && templateSpec.useData) {

From 3c869866c8b1c78e646b6f3ecb11dc108499cb6d Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Mon, 25 Aug 2014 22:55:05 -0500
Subject: [PATCH 87/91] Update FAQ with comment on UMD vs. AMD build

Fixes #796
---
 FAQ.md | 9 ++++++++-
 1 file changed, 8 insertions(+), 1 deletion(-)

diff --git a/FAQ.md b/FAQ.md
index 748f68a68..3b1cbd80d 100644
--- a/FAQ.md
+++ b/FAQ.md
@@ -42,4 +42,11 @@
   Should these match, please file an issue with us, per our [issue filing guidelines](https://github.com/wycats/handlebars.js/blob/master/CONTRIBUTING.md#reporting-issues).
 
 1. Why doesn't IE like the `default` name in the AMD module?
-  Some browsers such as particular versions of IE treat `default` as a reserved word in JavaScript source files. To safely use this you need to reference this via the `Handlebars['default']` lookup method. This is an unfortunate side effect of the shims necessary to backport the handlebars ES6 code to all current browsers.
+  Some browsers such as particular versions of IE treat `default` as a reserved word in JavaScript source files. To safely use this you need to reference this via the `Handlebars['default']` lookup method. This is an unfortunate side effect of the shims necessary to backport the Handlebars ES6 code to all current browsers.
+
+1. How do I load the runtime library when using AMD?
+  There are two options for loading under AMD environments. The first is to use the `handlebars.runtime.amd.js` file. This may require a [path mapping](https://github.com/wycats/handlebars.js/blob/master/spec/amd-runtime.html#L31) as well as access via the `default` field.
+
+  The other option is to load the `handlebars.runtime.js` UMD build, which might not require path configuration and exposes the library as both the module root and the `default` field for compatibility. 
+
+  If not using ES6 transpilers or accessing submodules in the build the former option should be sufficent for most use cases.

From 4f01f650dc2458343fcb40147803b30ba9a724de Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Mon, 25 Aug 2014 23:35:43 -0500
Subject: [PATCH 88/91] =?UTF-8?q?Render=20false=20literal=20as=20=E2=80=9C?=
 =?UTF-8?q?false=E2=80=9D?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Fixes #827
---
 lib/handlebars/compiler/javascript-compiler.js | 2 +-
 lib/handlebars/utils.js                        | 4 +++-
 spec/basic.js                                  | 8 ++++++++
 spec/utils.js                                  | 2 +-
 4 files changed, 13 insertions(+), 3 deletions(-)

diff --git a/lib/handlebars/compiler/javascript-compiler.js b/lib/handlebars/compiler/javascript-compiler.js
index 19066d34f..d41cacd73 100644
--- a/lib/handlebars/compiler/javascript-compiler.js
+++ b/lib/handlebars/compiler/javascript-compiler.js
@@ -293,7 +293,7 @@ JavaScriptCompiler.prototype = {
     // when we examine local
     this.flushInline();
     var local = this.popStack();
-    this.pushSource("if(" + local + " || " + local + " === 0) { " + this.appendToBuffer(local) + " }");
+    this.pushSource('if (' + local + ' != null) { ' + this.appendToBuffer(local) + ' }');
     if (this.environment.isSimple) {
       this.pushSource("else { " + this.appendToBuffer("''") + " }");
     }
diff --git a/lib/handlebars/utils.js b/lib/handlebars/utils.js
index 087183e54..f38b7afb2 100644
--- a/lib/handlebars/utils.js
+++ b/lib/handlebars/utils.js
@@ -55,8 +55,10 @@ export function escapeExpression(string) {
   // don't escape SafeStrings, since they're already safe
   if (string instanceof SafeString) {
     return string.toString();
-  } else if (!string && string !== 0) {
+  } else if (string == null) {
     return "";
+  } else if (!string) {
+    return string + '';
   }
 
   // Force a string conversion as this will be done by the append regardless and
diff --git a/spec/basic.js b/spec/basic.js
index 8aa54b98c..8a9c116cf 100644
--- a/spec/basic.js
+++ b/spec/basic.js
@@ -50,6 +50,14 @@ describe("basic context", function() {
     shouldCompileTo("num: {{.}}", 0, "num: 0");
     shouldCompileTo("num: {{num1/num2}}", {num1: {num2: 0}}, "num: 0");
   });
+  it('false', function() {
+    shouldCompileTo('val1: {{val1}}, val2: {{val2}}', {val1: false, val2: new Boolean(false)}, 'val1: false, val2: false');
+    shouldCompileTo('val: {{.}}', false, 'val: false');
+    shouldCompileTo('val: {{val1/val2}}', {val1: {val2: false}}, 'val: false');
+
+    shouldCompileTo('val1: {{{val1}}}, val2: {{{val2}}}', {val1: false, val2: new Boolean(false)}, 'val1: false, val2: false');
+    shouldCompileTo('val: {{{val1/val2}}}', {val1: {val2: false}}, 'val: false');
+  });
 
   it("newlines", function() {
       shouldCompileTo("Alan's\nTest", {}, "Alan's\nTest");
diff --git a/spec/utils.js b/spec/utils.js
index ea7d78256..0216c8de8 100644
--- a/spec/utils.js
+++ b/spec/utils.js
@@ -30,8 +30,8 @@ describe('utils', function() {
       equals(Handlebars.Utils.escapeExpression(''), '');
       equals(Handlebars.Utils.escapeExpression(undefined), '');
       equals(Handlebars.Utils.escapeExpression(null), '');
-      equals(Handlebars.Utils.escapeExpression(false), '');
 
+      equals(Handlebars.Utils.escapeExpression(false), 'false');
       equals(Handlebars.Utils.escapeExpression(0), '0');
     });
     it('should handle empty objects', function() {

From c5acea75aabb9f8238a35b53ac23f1bdb0c51f07 Mon Sep 17 00:00:00 2001
From: Kevin Decker <kpdecker@gmail.com>
Date: Mon, 25 Aug 2014 23:37:40 -0500
Subject: [PATCH 89/91] Update FAQ.md

---
 FAQ.md | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/FAQ.md b/FAQ.md
index 3b1cbd80d..0bd0997d6 100644
--- a/FAQ.md
+++ b/FAQ.md
@@ -1,18 +1,23 @@
 # Frequently Asked Questions
 
 1. How can I file a bug report:
+
   See our guidelines on [reporting issues](https://github.com/wycats/handlebars.js/blob/master/CONTRIBUTING.md#reporting-issues).
 
 1. Why isn't my Mustache template working?
+
   Handlebars deviates from Mustache slightly on a few behaviors. These variations are documented in our [readme](https://github.com/wycats/handlebars.js#differences-between-handlebarsjs-and-mustache).
 
 1. Why is it slower when compiling?
+
   The Handlebars compiler must parse the template and construct a JavaScript program which can then be run. Under some environments such as older mobile devices this can have a performance impact which can be avoided by precompiling. Generally it's recommended that precompilation and the runtime library be used on all clients.
 
 1. Why doesn't this work with Content Security Policy restrictions?
+
   When not using the precompiler, Handlebars generates a dynamic function for each template which can cause issues with pages that have enabled Content Policy. It's recommended that templates are precompiled or the `unsafe-eval` policy is enabled for sites that must generate dynamic templates at runtime.
 
 1. How can I include script tags in my template?
+
   If loading the template via an inlined `<script type="text/x-handlebars">` tag then you may need to break up the script tag with an empty comment to avoid browser parser errors:
 
   ```html
@@ -25,6 +30,7 @@
   It's generally recommended that templates are served through external, precompiled, files, which do not suffer from this issue.
 
 1. Why are my precompiled scripts throwing exceptions?
+
   When using the precompiler, it's important that a supporting version of the Handlebars runtime be loaded on the target page. In version 1.x there were rudimentary checks to compare the version but these did not always work. This is fixed under 2.x but the version checking does not work between these two versions. If you see unexpected errors such as `undefined is not a function` or similar, please verify that the same version is being used for both the precompiler and the client. This can be checked via:
 
   ```sh
@@ -42,9 +48,11 @@
   Should these match, please file an issue with us, per our [issue filing guidelines](https://github.com/wycats/handlebars.js/blob/master/CONTRIBUTING.md#reporting-issues).
 
 1. Why doesn't IE like the `default` name in the AMD module?
+
   Some browsers such as particular versions of IE treat `default` as a reserved word in JavaScript source files. To safely use this you need to reference this via the `Handlebars['default']` lookup method. This is an unfortunate side effect of the shims necessary to backport the Handlebars ES6 code to all current browsers.
 
 1. How do I load the runtime library when using AMD?
+
   There are two options for loading under AMD environments. The first is to use the `handlebars.runtime.amd.js` file. This may require a [path mapping](https://github.com/wycats/handlebars.js/blob/master/spec/amd-runtime.html#L31) as well as access via the `default` field.
 
   The other option is to load the `handlebars.runtime.js` UMD build, which might not require path configuration and exposes the library as both the module root and the `default` field for compatibility. 

From d33b362d97a0856e308ff5c4a4c5814409358dc9 Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Tue, 26 Aug 2014 18:27:11 -0500
Subject: [PATCH 90/91] Update release notes

---
 release-notes.md | 45 ++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 44 insertions(+), 1 deletion(-)

diff --git a/release-notes.md b/release-notes.md
index 981231e17..48cf7cf0d 100644
--- a/release-notes.md
+++ b/release-notes.md
@@ -2,7 +2,50 @@
 
 ## Development
 
-[Commits](https://github.com/wycats/handlebars.js/compare/v2.0.0-alpha.4...master)
+[Commits](https://github.com/wycats/handlebars.js/compare/v2.0.0-beta.1...master)
+
+## v2.0.0-beta.1 - August 26th, 2014
+- [#787](https://github.com/wycats/handlebars.js/pull/787) - Remove whitespace surrounding standalone statements ([@kpdecker](https://api.github.com/users/kpdecker))
+- [#827](https://github.com/wycats/handlebars.js/issues/827) - Render false literal as “false” ([@scoot557](https://api.github.com/users/scoot557))
+- [#767](https://github.com/wycats/handlebars.js/issues/767) - Subexpressions bug with hash and context ([@evensoul](https://api.github.com/users/evensoul))
+- Changes to 0/undefined handling
+  - [#731](https://github.com/wycats/handlebars.js/pull/731) - Strange behavior for {{#foo}} {{bar}} {{/foo}} when foo is 0 ([@kpdecker](https://api.github.com/users/kpdecker))
+  - [#820](https://github.com/wycats/handlebars.js/issues/820) - strange behavior for {{foo.bar}} when foo is 0 or null or false ([@zordius](https://api.github.com/users/zordius))
+  - [#837](https://github.com/wycats/handlebars.js/issues/837) - Strange input for custom helper ( foo.bar == false when foo is undefined ) ([@zordius](https://api.github.com/users/zordius))
+- [#819](https://github.com/wycats/handlebars.js/pull/819) - Implement recursive field lookup ([@kpdecker](https://api.github.com/users/kpdecker))
+- [#764](https://github.com/wycats/handlebars.js/issues/764) - This reference not working for helpers ([@kpdecker](https://api.github.com/users/kpdecker))
+- [#773](https://github.com/wycats/handlebars.js/issues/773) - Implicit parameters in {{#each}} introduces a peculiarity in helpers calling convention  ([@Bertrand](https://api.github.com/users/Bertrand))
+- [#783](https://github.com/wycats/handlebars.js/issues/783) - helperMissing and consistency for different expression types ([@ErisDS](https://api.github.com/users/ErisDS))
+- [#795](https://github.com/wycats/handlebars.js/pull/795) - Turn the precompile script into a wrapper around a module. ([@jwietelmann](https://api.github.com/users/jwietelmann))
+- [#823](https://github.com/wycats/handlebars.js/pull/823) - Support inverse sections on the with helper ([@dan-manges](https://api.github.com/users/dan-manges))
+- [#834](https://github.com/wycats/handlebars.js/pull/834) - Refactor blocks, programs and inverses ([@mmun](https://api.github.com/users/mmun))
+- [#852](https://github.com/wycats/handlebars.js/issues/852) - {{foo~}} space control behavior is different from older version ([@zordius](https://api.github.com/users/zordius))
+- [#835](https://github.com/wycats/handlebars.js/issues/835) - Templates overwritten if file is loaded twice
+
+- Expose escapeExpression on the root object - 980c38c
+- Remove nested function eval in blockHelperMissing - 6f22ec1
+- Fix compiler program de-duping - 9e3f824
+
+Compatibility notes:
+- The default build now outputs a generic UMD wrapper. This should be transparent change but may cause issues in some environments.
+- Runtime compatibility breaks in both directions. Ensure that both compiler and client are upgraded to 2.0.0-beta.1 or higher at the same time.
+  - `programWithDepth` has been removed an instead an array of context values is passed to fields needing depth lookups.
+- `false` values are now printed to output rather than silently dropped
+- Lines containing only block statements and whitespace are now removed. This matches the Mustache spec but may cause issues with code that expects whitespace to exist but would not otherwise.
+- Partials that are standalone will now indent their rendered content
+- `AST.ProgramNode`'s signature has changed. 
+- Numerious methods/features removed from psuedo-API classes
+  - `JavaScriptCompiler.register`
+  - `JavaScriptCompiler.replaceStack` no longer supports non-inline replace
+  - `Compiler.disassemble`
+  - `DECLARE` opcode
+  - `strip` opcode
+  - `lookup` opcode
+  - Content nodes may have their `string` values mutated over time. `original` field provides the unmodified value.
+- Removed unused `Handlebars.registerHelper` `inverse` parameter
+- `each` helper requires iterator parameter
+
+[Commits](https://github.com/wycats/handlebars.js/compare/v2.0.0-alpha.4...v2.0.0-beta.1)
 
 ## v2.0.0-alpha.4 - May 19th, 2014
 - Expose setup wrappers for compiled templates - 3638874

From cee52ea68f85baf6812cbc6d303595a88b4ea4f8 Mon Sep 17 00:00:00 2001
From: kpdecker <kpdecker@gmail.com>
Date: Tue, 26 Aug 2014 18:27:23 -0500
Subject: [PATCH 91/91] v2.0.0-beta.1

---
 components/bower.json           | 2 +-
 components/handlebars.js.nuspec | 2 +-
 lib/handlebars/base.js          | 2 +-
 package.json                    | 2 +-
 4 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/components/bower.json b/components/bower.json
index efcfbf584..d91be56b0 100644
--- a/components/bower.json
+++ b/components/bower.json
@@ -1,6 +1,6 @@
 {
   "name": "handlebars",
-  "version": "2.0.0-alpha.4",
+  "version": "2.0.0-beta.1",
   "main": "handlebars.js",
   "dependencies": {}
 }
diff --git a/components/handlebars.js.nuspec b/components/handlebars.js.nuspec
index 7cf788646..8e676c7ab 100644
--- a/components/handlebars.js.nuspec
+++ b/components/handlebars.js.nuspec
@@ -2,7 +2,7 @@
 <package>
 	<metadata>
 		<id>handlebars.js</id>
-		<version>2.0.0-alpha.4</version>
+		<version>2.0.0-beta.1</version>
 		<authors>handlebars.js Authors</authors>
 		<licenseUrl>https://github.com/wycats/handlebars.js/blob/master/LICENSE</licenseUrl>
 		<projectUrl>https://github.com/wycats/handlebars.js/</projectUrl>
diff --git a/lib/handlebars/base.js b/lib/handlebars/base.js
index 0bc2948e4..9ecf6f4f1 100644
--- a/lib/handlebars/base.js
+++ b/lib/handlebars/base.js
@@ -1,7 +1,7 @@
 module Utils from "./utils";
 import Exception from "./exception";
 
-export var VERSION = "2.0.0-alpha.4";
+export var VERSION = "2.0.0-beta.1";
 export var COMPILER_REVISION = 6;
 
 export var REVISION_CHANGES = {
diff --git a/package.json b/package.json
index 8e69350da..1759b1c60 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
 {
   "name": "handlebars",
   "barename": "handlebars",
-  "version": "2.0.0-alpha.4",
+  "version": "2.0.0-beta.1",
   "description": "Handlebars provides the power necessary to let you build semantic templates effectively with no frustration",
   "homepage": "http://www.handlebarsjs.com/",
   "keywords": [
