Skip to content

Commit

Permalink
Merge pull request #932 from wycats/strict
Browse files Browse the repository at this point in the history
Implement strict and assumeObject modes
  • Loading branch information
kpdecker committed Jan 1, 2015
2 parents 4b2146b + 6650957 commit 96a5cf5
Show file tree
Hide file tree
Showing 3 changed files with 172 additions and 7 deletions.
49 changes: 42 additions & 7 deletions lib/handlebars/compiler/javascript-compiler.js
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,11 @@ JavaScriptCompiler.prototype = {

resolvePath: function(type, parts, i, falsy) {
/*jshint -W083 */
if (this.options.strict || this.options.assumeObjects) {
this.push(strictLookup(this.options.strict, this, parts, type));
return;
}

var len = parts.length;
for (; i < len; i++) {
this.replaceStack(function(current) {
Expand Down Expand Up @@ -572,11 +577,13 @@ JavaScriptCompiler.prototype = {
var helper = this.setupHelper(paramSize, name);
var simple = isSimple ? [helper.name, ' || '] : '';

this.push(
this.source.functionCall(
['('].concat(simple, nonHelper, ' || ', this.aliasable('helpers.helperMissing'), ')'),
'call',
helper.callParams));
var lookup = ['('].concat(simple, nonHelper);
if (!this.options.strict) {
lookup.push(' || ', this.aliasable('helpers.helperMissing'));
}
lookup.push(')');

this.push(this.source.functionCall(lookup, 'call', helper.callParams));
},

// [invokeKnownHelper]
Expand Down Expand Up @@ -613,9 +620,17 @@ JavaScriptCompiler.prototype = {

var helperName = this.lastHelper = this.nameLookup('helpers', name, 'helper');

var lookup = ['(', '(helper = ', helperName, ' || ', nonHelper, ')'];
if (!this.options.strict) {
lookup[0] = '(helper = ';
lookup.push(
' != null ? helper : ',
this.aliasable('helpers.helperMissing')
);
}

this.push([
'((helper = (helper = ', helperName, ' || ', nonHelper, ') != null ? helper : ',
this.aliasable('helpers.helperMissing'),
'(', lookup,
(helper.paramsInit ? ['),(', helper.paramsInit] : []), '),',
'(typeof helper === ', this.aliasable('"function"'), ' ? ',
this.source.functionCall('helper','call', helper.callParams), ' : helper))'
Expand Down Expand Up @@ -1023,4 +1038,24 @@ JavaScriptCompiler.isValidJavaScriptVariableName = function(name) {
return !JavaScriptCompiler.RESERVED_WORDS[name] && /^[a-zA-Z_$][0-9a-zA-Z_$]*$/.test(name);
};

function strictLookup(requireTerminal, compiler, parts, type) {
var stack = compiler.popStack();

var i = 0,
len = parts.length;
if (requireTerminal) {
len--;
}

for (; i < len; i++) {
stack = compiler.nameLookup(stack, parts[i], type);
}

if (requireTerminal) {
return [compiler.aliasable('this.strict'), '(', stack, ', ', compiler.quotedString(parts[i]), ')'];
} else {
return stack;
}
}

export default JavaScriptCompiler;
6 changes: 6 additions & 0 deletions lib/handlebars/runtime.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,12 @@ export function template(templateSpec, env) {

// Just add water
var container = {
strict: function(obj, name) {
if (!(name in obj)) {
throw new Exception('"' + name + '" not defined in ' + obj);
}
return obj[name];
},
lookup: function(depths, name) {
var len = depths.length;
for (var i = 0; i < len; i++) {
Expand Down
124 changes: 124 additions & 0 deletions spec/strict.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
/*global CompilerContext, Handlebars, shouldThrow */
var Exception = Handlebars.Exception;

describe('strict', function() {
describe('strict mode', function() {
it('should error on missing property lookup', function() {
shouldThrow(function() {
var template = CompilerContext.compile('{{hello}}', {strict: true});

template({});
}, Exception, /"hello" not defined in/);
});
it('should error on missing child', function() {
var template = CompilerContext.compile('{{hello.bar}}', {strict: true});
equals(template({hello: {bar: 'foo'}}), 'foo');

shouldThrow(function() {
template({hello: {}});
}, Exception, /"bar" not defined in/);
});
it('should handle explicit undefined', function() {
var template = CompilerContext.compile('{{hello.bar}}', {strict: true});

equals(template({hello: {bar: undefined}}), '');
});
it('should error on missing property lookup in known helpers mode', function() {
shouldThrow(function() {
var template = CompilerContext.compile('{{hello}}', {strict: true, knownHelpersOnly: true});

template({});
}, Exception, /"hello" not defined in/);
});
it('should error on missing context', function() {
shouldThrow(function() {
var template = CompilerContext.compile('{{hello}}', {strict: true});

template();
}, Error);
});

it('should error on missing data lookup', function() {
var template = CompilerContext.compile('{{@hello}}', {strict: true});
equals(template(undefined, {data: {hello: 'foo'}}), 'foo');

shouldThrow(function() {
template();
}, Error);
});

it('should not run helperMissing for helper calls', function() {
shouldThrow(function() {
var template = CompilerContext.compile('{{hello foo}}', {strict: true});

template({foo: true});
}, Exception, /"hello" not defined in/);

shouldThrow(function() {
var template = CompilerContext.compile('{{#hello foo}}{{/hello}}', {strict: true});

template({foo: true});
}, Exception, /"hello" not defined in/);
});
it('should throw on ambiguous blocks', function() {
shouldThrow(function() {
var template = CompilerContext.compile('{{#hello}}{{/hello}}', {strict: true});

template({});
}, Exception, /"hello" not defined in/);

shouldThrow(function() {
var template = CompilerContext.compile('{{^hello}}{{/hello}}', {strict: true});

template({});
}, Exception, /"hello" not defined in/);

shouldThrow(function() {
var template = CompilerContext.compile('{{#hello.bar}}{{/hello.bar}}', {strict: true});

template({hello: {}});
}, Exception, /"bar" not defined in/);
});
});

describe('assume objects', function() {
it('should ignore missing property', function() {
var template = CompilerContext.compile('{{hello}}', {assumeObjects: true});

equal(template({}), '');
});
it('should ignore missing child', function() {
var template = CompilerContext.compile('{{hello.bar}}', {assumeObjects: true});

equal(template({hello: {}}), '');
});
it('should error on missing object', function() {
shouldThrow(function() {
var template = CompilerContext.compile('{{hello.bar}}', {assumeObjects: true});

template({});
}, Error);
});
it('should error on missing context', function() {
shouldThrow(function() {
var template = CompilerContext.compile('{{hello}}', {assumeObjects: true});

template();
}, Error);
});

it('should error on missing data lookup', function() {
shouldThrow(function() {
var template = CompilerContext.compile('{{@hello.bar}}', {assumeObjects: true});

template();
}, Error);
});

it('should execute blockHelperMissing', function() {
var template = CompilerContext.compile('{{^hello}}foo{{/hello}}', {assumeObjects: true});

equals(template({}), 'foo');
});
});
});

0 comments on commit 96a5cf5

Please sign in to comment.