Skip to content

Commit 255ef4c

Browse files
committed
Fix errors with strict lookup in compat mode
Fixes #2149, fixes #1741
1 parent b10cec2 commit 255ef4c

3 files changed

Lines changed: 88 additions & 7 deletions

File tree

lib/handlebars/compiler/javascript-compiler.js

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1175,20 +1175,34 @@ function strictLookup(requireTerminal, compiler, parts, startPartIndex, type) {
11751175
stack = compiler.nameLookup(stack, parts[i], type);
11761176
}
11771177

1178-
if (requireTerminal) {
1178+
if (!requireTerminal) {
1179+
return stack;
1180+
}
1181+
1182+
if (startPartIndex > len) {
1183+
// Compat mode already consumed all parts via depthedLookup; the stack
1184+
// holds the resolved value, not an object to look the property up on.
1185+
// Use container.strictLookup to traverse depths with a strict error on miss.
11791186
return [
1180-
compiler.aliasable('container.strict'),
1181-
'(',
1182-
stack,
1183-
', ',
1187+
compiler.aliasable('container.strictLookup'),
1188+
'(depths, ',
11841189
compiler.quotedString(parts[len]),
11851190
', ',
11861191
JSON.stringify(compiler.source.currentLocation),
11871192
' )',
11881193
];
1189-
} else {
1190-
return stack;
11911194
}
1195+
1196+
return [
1197+
compiler.aliasable('container.strict'),
1198+
'(',
1199+
stack,
1200+
', ',
1201+
compiler.quotedString(parts[len]),
1202+
', ',
1203+
JSON.stringify(compiler.source.currentLocation),
1204+
' )',
1205+
];
11921206
}
11931207

11941208
export default JavaScriptCompiler;

lib/handlebars/runtime.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,22 @@ export function template(templateSpec, env) {
116116
}
117117
return container.lookupProperty(obj, name);
118118
},
119+
strictLookup: function (depths, name, loc) {
120+
const len = depths.length;
121+
let depth;
122+
for (let i = 0; i < len; i++) {
123+
const d = depths[i];
124+
if (
125+
d &&
126+
(typeof d === 'object' || typeof d === 'function') &&
127+
name in d
128+
) {
129+
depth = d;
130+
break;
131+
}
132+
}
133+
return container.strict(depth, name, loc);
134+
},
119135
lookupProperty: function (parent, propertyName) {
120136
if (Utils.isMap(parent)) {
121137
return parent.get(propertyName);

spec/strict.js

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,57 @@ describe('strict', function () {
121121
});
122122
});
123123

124+
describe('strict and compat mode', function () {
125+
it('GH-1741: should render a simple variable', function () {
126+
expectTemplate('{{v}}')
127+
.withCompileOptions({ strict: true, compat: true })
128+
.withInput({ v: 'a' })
129+
.toCompileTo('a');
130+
});
131+
132+
it('GH-2149: should render correctly when block context is a boolean', function () {
133+
expectTemplate('{{#foo}}Hello {{bar}}{{/foo}}')
134+
.withCompileOptions({ strict: true, compat: true })
135+
.withInput({ foo: true, bar: 'World' })
136+
.toCompileTo('Hello World');
137+
});
138+
139+
it('GH-2149: should render correctly when looking up a property on each item', function () {
140+
expectTemplate('{{#each items}}{{name}}{{/each}}')
141+
.withCompileOptions({ strict: true, compat: true })
142+
.withInput({ items: [{ name: 'Hello' }] })
143+
.toCompileTo('Hello');
144+
});
145+
146+
it('should still throw when a property is missing at all depths', function () {
147+
expectTemplate('{{#each items}}{{name}}{{/each}}')
148+
.withCompileOptions({ strict: true, compat: true })
149+
.withInput({ items: [1, 2] })
150+
.toThrow(Exception, /"name" not defined in/);
151+
});
152+
153+
it('should still perform recursive lookup when a property is not in the current context', function () {
154+
expectTemplate('{{#each items}}{{name}}{{/each}}')
155+
.withCompileOptions({ strict: true, compat: true })
156+
.withInput({ name: 'root', items: [{}] })
157+
.toCompileTo('root');
158+
});
159+
160+
it('should still perform recursive lookup with a multi-part path not in context', function () {
161+
expectTemplate('{{#with child}}{{name.first}}{{/with}}')
162+
.withCompileOptions({ strict: true, compat: true })
163+
.withInput({ name: { first: 'root' }, child: { name: null } })
164+
.toCompileTo('root');
165+
});
166+
167+
it('should directly return an explicitly null property', function () {
168+
expectTemplate('{{#each items}}{{name}}{{/each}}')
169+
.withCompileOptions({ strict: true, compat: true })
170+
.withInput({ name: 'root', items: [{ name: null }] })
171+
.toCompileTo('');
172+
});
173+
});
174+
124175
describe('assume objects', function () {
125176
it('should ignore missing property', function () {
126177
expectTemplate('{{hello}}')

0 commit comments

Comments
 (0)