Merge pull request #932 from wycats/strict
Implement strict and assumeObject modes
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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
@@ -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');
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user