Fix errors with strict lookup in compat mode (#2150)
Fixes #2149, fixes #1741
This commit is contained in:
@@ -1175,7 +1175,24 @@ function strictLookup(requireTerminal, compiler, parts, startPartIndex, type) {
|
|||||||
stack = compiler.nameLookup(stack, parts[i], type);
|
stack = compiler.nameLookup(stack, parts[i], type);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (requireTerminal) {
|
if (!requireTerminal) {
|
||||||
|
return stack;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (startPartIndex > len) {
|
||||||
|
// Compat mode already consumed all parts via depthedLookup; the stack
|
||||||
|
// holds the resolved value, not an object to look the property up on.
|
||||||
|
// Use container.strictLookup to traverse depths with a strict error on miss.
|
||||||
|
return [
|
||||||
|
compiler.aliasable('container.strictLookup'),
|
||||||
|
'(depths, ',
|
||||||
|
compiler.quotedString(parts[len]),
|
||||||
|
', ',
|
||||||
|
JSON.stringify(compiler.source.currentLocation),
|
||||||
|
' )',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
compiler.aliasable('container.strict'),
|
compiler.aliasable('container.strict'),
|
||||||
'(',
|
'(',
|
||||||
@@ -1186,9 +1203,6 @@ function strictLookup(requireTerminal, compiler, parts, startPartIndex, type) {
|
|||||||
JSON.stringify(compiler.source.currentLocation),
|
JSON.stringify(compiler.source.currentLocation),
|
||||||
' )',
|
' )',
|
||||||
];
|
];
|
||||||
} else {
|
|
||||||
return stack;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default JavaScriptCompiler;
|
export default JavaScriptCompiler;
|
||||||
|
|||||||
@@ -116,6 +116,22 @@ export function template(templateSpec, env) {
|
|||||||
}
|
}
|
||||||
return container.lookupProperty(obj, name);
|
return container.lookupProperty(obj, name);
|
||||||
},
|
},
|
||||||
|
strictLookup: function (depths, name, loc) {
|
||||||
|
const len = depths.length;
|
||||||
|
let depth;
|
||||||
|
for (let i = 0; i < len; i++) {
|
||||||
|
const d = depths[i];
|
||||||
|
if (
|
||||||
|
d &&
|
||||||
|
(typeof d === 'object' || typeof d === 'function') &&
|
||||||
|
name in d
|
||||||
|
) {
|
||||||
|
depth = d;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return container.strict(depth, name, loc);
|
||||||
|
},
|
||||||
lookupProperty: function (parent, propertyName) {
|
lookupProperty: function (parent, propertyName) {
|
||||||
if (Utils.isMap(parent)) {
|
if (Utils.isMap(parent)) {
|
||||||
return parent.get(propertyName);
|
return parent.get(propertyName);
|
||||||
|
|||||||
@@ -121,6 +121,63 @@ describe('strict', function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('strict and compat mode', function () {
|
||||||
|
it('GH-1741: should render a simple variable', function () {
|
||||||
|
expectTemplate('{{v}}')
|
||||||
|
.withCompileOptions({ strict: true, compat: true })
|
||||||
|
.withInput({ v: 'a' })
|
||||||
|
.toCompileTo('a');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw for a missing variable', function () {
|
||||||
|
expectTemplate('{{v}}')
|
||||||
|
.withCompileOptions({ strict: true, compat: true })
|
||||||
|
.toThrow(Exception, /"v" not defined in/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('GH-2149: should render correctly when block context is a boolean', function () {
|
||||||
|
expectTemplate('{{#foo}}Hello {{bar}}{{/foo}}')
|
||||||
|
.withCompileOptions({ strict: true, compat: true })
|
||||||
|
.withInput({ foo: true, bar: 'World' })
|
||||||
|
.toCompileTo('Hello World');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('GH-2149: should render correctly when looking up a property on each item', function () {
|
||||||
|
expectTemplate('{{#each items}}{{name}}{{/each}}')
|
||||||
|
.withCompileOptions({ strict: true, compat: true })
|
||||||
|
.withInput({ items: [{ name: 'Hello' }] })
|
||||||
|
.toCompileTo('Hello');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should still throw when a property is missing at all depths', function () {
|
||||||
|
expectTemplate('{{#each items}}{{name}}{{/each}}')
|
||||||
|
.withCompileOptions({ strict: true, compat: true })
|
||||||
|
.withInput({ items: [1, 2] })
|
||||||
|
.toThrow(Exception, /"name" not defined in/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should still perform recursive lookup when a property is not in the current context', function () {
|
||||||
|
expectTemplate('{{#each items}}{{name}}{{/each}}')
|
||||||
|
.withCompileOptions({ strict: true, compat: true })
|
||||||
|
.withInput({ name: 'root', items: [{}] })
|
||||||
|
.toCompileTo('root');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should still perform recursive lookup with a multi-part path not in context', function () {
|
||||||
|
expectTemplate('{{#with child}}{{name.first}}{{/with}}')
|
||||||
|
.withCompileOptions({ strict: true, compat: true })
|
||||||
|
.withInput({ name: { first: 'root' }, child: { name: null } })
|
||||||
|
.toCompileTo('root');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should directly return an explicitly null property', function () {
|
||||||
|
expectTemplate('{{#each items}}{{name}}{{/each}}')
|
||||||
|
.withCompileOptions({ strict: true, compat: true })
|
||||||
|
.withInput({ name: 'root', items: [{ name: null }] })
|
||||||
|
.toCompileTo('');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('assume objects', function () {
|
describe('assume objects', function () {
|
||||||
it('should ignore missing property', function () {
|
it('should ignore missing property', function () {
|
||||||
expectTemplate('{{hello}}')
|
expectTemplate('{{hello}}')
|
||||||
|
|||||||
Reference in New Issue
Block a user