Skip to content

Commit 98ca81b

Browse files
committed
Add test; fix impl
1 parent 16ceec6 commit 98ca81b

File tree

4 files changed

+97
-28
lines changed

4 files changed

+97
-28
lines changed

lib/src/mustachio/renderer_base.dart

Lines changed: 50 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -180,29 +180,24 @@ abstract class RendererBase<T> {
180180
return context.toString();
181181
}
182182
var firstName = names.first;
183-
var property = getProperty(firstName);
184-
if (property != null) {
185-
var remainingNames = [...names.skip(1)];
186-
try {
187-
return property.renderVariable(context, property, remainingNames);
188-
} on PartialMustachioResolutionError catch (e) {
189-
// The error thrown by [Property.renderVariable] does not have all of
190-
// the names required for a decent error. We throw a new error here.
191-
throw MustachioResolutionError(node.keySpan.message(
192-
"Failed to resolve '${e.name}' on ${e.contextType} while resolving "
193-
'$remainingNames as a property chain on any types in the context '
194-
"chain: $contextChainString, after first resolving '$firstName' to "
195-
'a property on $T'));
183+
try {
184+
var property = getProperty(firstName);
185+
if (property != null) {
186+
var remainingNames = [...names.skip(1)];
187+
try {
188+
return property.renderVariable(context, property, remainingNames);
189+
} on PartialMustachioResolutionError catch (e) {
190+
// The error thrown by [Property.renderVariable] does not have all of
191+
// the names required for a decent error. We throw a new error here.
192+
throw MustachioResolutionError(node.keySpan.message(
193+
"Failed to resolve '${e.name}' on ${e.contextType} while "
194+
'resolving $remainingNames as a property chain on any types in '
195+
'the context chain: $contextChainString, after first resolving '
196+
"'$firstName' to a property on $T"));
197+
}
196198
}
197-
}
198-
199-
if (this is SimpleRenderer && _invisibleGetters.contains(names.first)) {
200-
var type = context.runtimeType;
201-
throw MustachioResolutionError(node.keySpan.message(
202-
'$firstName is a getter on $type, which is not visible to Mustache. '
203-
'To render $firstName on $type, make it visible to mustache; to '
204-
'render $firstName on a different type up in the context stack, '
205-
'perhaps provide $firstName via a different name.'));
199+
} on _MustachioResolutionErrorWithoutSpan catch (e) {
200+
throw MustachioResolutionError(node.keySpan.message(e.message));
206201
}
207202

208203
if (parent != null) {
@@ -302,20 +297,37 @@ class SimpleRenderer extends RendererBase<Object> {
302297
) : super(context, parent, template, invisibleGetters: invisibleGetters);
303298

304299
@override
305-
Property<Object> getProperty(String key) => null;
300+
Property<Object> getProperty(String key) {
301+
if (_invisibleGetters.contains(key)) {
302+
throw MustachioResolutionError(_failedKeyVisibilityMessage(key));
303+
} else {
304+
return null;
305+
}
306+
}
306307

307308
@override
308309
String getFields(Variable node) {
309310
var names = node.key;
310-
if (names.length == 1 && names.single == '.') {
311+
var firstName = node.key.first;
312+
if (names.length == 1 && firstName == '.') {
311313
return context.toString();
312-
}
313-
if (parent != null) {
314+
} else if (_invisibleGetters.contains(firstName)) {
315+
throw MustachioResolutionError(_failedKeyVisibilityMessage(firstName));
316+
} else if (parent != null) {
314317
return parent.getFields(node);
315318
} else {
316319
return 'null';
317320
}
318321
}
322+
323+
String _failedKeyVisibilityMessage(String name) {
324+
var type = context.runtimeType;
325+
return '[$name] is a getter on $type, which is not visible to Mustache. '
326+
'To render [$name] on $type, make it visible to Mustache via the '
327+
'`visibleTypes` parameter on `@Renderer`; to render [$name] on a '
328+
'different type up in the context stack, perhaps provide [$name] via '
329+
'a different name.';
330+
}
319331
}
320332

321333
/// An individual property of objects of type [T], including functions for
@@ -369,7 +381,7 @@ class Property<T> {
369381
class MustachioResolutionError extends Error {
370382
final String message;
371383

372-
MustachioResolutionError([this.message]);
384+
MustachioResolutionError(this.message);
373385

374386
@override
375387
String toString() => 'MustachioResolutionError: $message';
@@ -385,6 +397,17 @@ class PartialMustachioResolutionError extends Error {
385397
PartialMustachioResolutionError(this.name, this.contextType);
386398
}
387399

400+
/// A Mustachio resolution error which is thrown in a position where the AST
401+
/// node is not known.
402+
///
403+
/// This error should be caught and "re-thrown" as a [MustachioResolutionError]
404+
/// with a message derived from a [SourceSpan].
405+
class _MustachioResolutionErrorWithoutSpan extends Error {
406+
final String message;
407+
408+
_MustachioResolutionErrorWithoutSpan(this.message);
409+
}
410+
388411
extension MapExtensions<T> on Map<String, Property<T>> {
389412
Property<T> getValue(String name) {
390413
if (containsKey(name)) {

test/mustachio/foo.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ class Foo extends FooBase<Baz> {
1717
@override
1818
Baz baz;
1919
Property1 p1;
20+
int length;
2021
}
2122

2223
class Bar {

test/mustachio/foo.runtime_renderers.dart

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,18 @@ class Renderer_Foo extends RendererBase<Foo> {
215215
parent: r, getters: _invisibleGetters['int']));
216216
},
217217
),
218+
'length': Property(
219+
getValue: (CT_ c) => c.length,
220+
renderVariable: (CT_ c, Property<CT_> self,
221+
List<String> remainingNames) =>
222+
self.renderSimpleVariable(c, remainingNames, 'int'),
223+
isNullValue: (CT_ c) => c.length == null,
224+
renderValue:
225+
(CT_ c, RendererBase<CT_> r, List<MustachioNode> ast) {
226+
return renderSimple(c.length, ast, r.template,
227+
parent: r, getters: _invisibleGetters['int']);
228+
},
229+
),
218230
'p1': Property(
219231
getValue: (CT_ c) => c.p1,
220232
renderVariable:

test/mustachio/runtime_renderer_render_test.dart

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,13 @@ void main() {
2525

2626
test('property map contains all public getters', () {
2727
var propertyMap = Renderer_Foo.propertyMap();
28-
expect(propertyMap.keys, hasLength(7));
28+
expect(propertyMap.keys, hasLength(8));
2929
expect(propertyMap['b1'], isNotNull);
3030
expect(propertyMap['s1'], isNotNull);
3131
expect(propertyMap['l1'], isNotNull);
3232
expect(propertyMap['baz'], isNotNull);
3333
expect(propertyMap['p1'], isNotNull);
34+
expect(propertyMap['length'], isNotNull);
3435
expect(propertyMap['hashCode'], isNotNull);
3536
expect(propertyMap['runtimeType'], isNotNull);
3637
});
@@ -516,6 +517,38 @@ line 1, column 9 of ${fooTemplateFile.path}: Failed to resolve 's2' as a propert
516517
contains('Failed to resolve [length] property chain on String'))));
517518
});
518519

520+
test(
521+
'Renderer throws when a SimpleRenderer key would shadow a '
522+
'non-SimpleRenderer key in a variable', () async {
523+
var fooTemplateFile = getFile('/project/foo.mustache')
524+
..writeAsStringSync('Text {{#s1}} {{length}} {{/s1}}');
525+
var fooTemplate = await Template.parse(fooTemplateFile);
526+
var foo = Foo()..s1 = 'String';
527+
expect(
528+
() => renderFoo(foo, fooTemplate),
529+
throwsA(const TypeMatcher<MustachioResolutionError>().having(
530+
(e) => e.message,
531+
'message',
532+
contains('[length] is a getter on String, which is not visible to '
533+
'Mustache.'))));
534+
});
535+
536+
test(
537+
'Renderer throws when a SimpleRenderer key would shadow a '
538+
'non-SimpleRenderer key in a section', () async {
539+
var fooTemplateFile = getFile('/project/foo.mustache')
540+
..writeAsStringSync('Text {{#s1}} {{#length}}Inner{{/length}} {{/s1}}');
541+
var fooTemplate = await Template.parse(fooTemplateFile);
542+
var foo = Foo()..s1 = 'String';
543+
expect(
544+
() => renderFoo(foo, fooTemplate),
545+
throwsA(const TypeMatcher<MustachioResolutionError>().having(
546+
(e) => e.message,
547+
'message',
548+
contains('[length] is a getter on String, which is not visible to '
549+
'Mustache.'))));
550+
});
551+
519552
test('Template parser throws when it cannot read a template', () async {
520553
var barTemplateFile = getFile('/project/src/bar.mustache');
521554
expect(

0 commit comments

Comments
 (0)