Merge pull request #932 from wycats/strict

Implement strict and assumeObject modes
This commit is contained in:
Kevin Decker
2014-12-31 19:28:05 -06:00
3 changed files with 172 additions and 7 deletions
+42 -7
View File
@@ -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) {
@@ -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]
@@ -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))'
@@ -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
View File
@@ -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++) {
+124
View File
@@ -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');
});
});
});