summaryrefslogtreecommitdiffstats
path: root/lib/handlebars/compiler/javascript-compiler.js
diff options
context:
space:
mode:
authorkpdecker <kpdecker@gmail.com>2014-07-06 23:40:46 -0500
committerkpdecker <kpdecker@gmail.com>2014-07-06 23:41:15 -0500
commitb5a5c76ceb85fee36340e14b79306a436d32ff72 (patch)
tree08f036fce72b0e0f1c700383cdba1d13033334ec /lib/handlebars/compiler/javascript-compiler.js
parent4aad72d2230a6eaaf242130c57ad1efa4d701f3c (diff)
downloadhandlebars.js-b5a5c76ceb85fee36340e14b79306a436d32ff72.zip
handlebars.js-b5a5c76ceb85fee36340e14b79306a436d32ff72.tar.gz
handlebars.js-b5a5c76ceb85fee36340e14b79306a436d32ff72.tar.bz2
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
Diffstat (limited to 'lib/handlebars/compiler/javascript-compiler.js')
-rw-r--r--lib/handlebars/compiler/javascript-compiler.js116
1 files changed, 62 insertions, 54 deletions
diff --git a/lib/handlebars/compiler/javascript-compiler.js b/lib/handlebars/compiler/javascript-compiler.js
index 4d950d4..09309b5 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 {