@@ -147,7 +147,14 @@ abstract class RendererBase<T> {
147147 /// The output buffer into which [context] is rendered, using a template.
148148 final buffer = StringBuffer ();
149149
150- RendererBase (this .context, this .parent, this ._template);
150+ final Set <String > _invisibleGetters;
151+
152+ RendererBase (
153+ this .context,
154+ this .parent,
155+ this ._template, {
156+ Set <String > invisibleGetters = const {},
157+ }) : _invisibleGetters = invisibleGetters;
151158
152159 Template get template => _template;
153160
@@ -172,21 +179,28 @@ abstract class RendererBase<T> {
172179 if (names.length == 1 && names.single == '.' ) {
173180 return context.toString ();
174181 }
175- var property = getProperty (names.first);
176- if (property != null ) {
177- var remainingNames = [...names.skip (1 )];
178- try {
179- return property.renderVariable (context, property, remainingNames);
180- } on PartialMustachioResolutionError catch (e) {
181- // The error thrown by [Property.renderVariable] does not have all of
182- // the names required for a decent error. We throw a new error here.
183- throw MustachioResolutionError (node.keySpan.message (
184- "Failed to resolve '${e .name }' on ${e .contextType } while resolving "
185- '$remainingNames as a property chain on any types in the context '
186- "chain: $contextChainString , after first resolving '${names .first }' "
187- 'to a property on $T ' ));
182+ var firstName = names.first;
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+ }
188198 }
189- } else if (parent != null ) {
199+ } on _MustachioResolutionErrorWithoutSpan catch (e) {
200+ throw MustachioResolutionError (node.keySpan.message (e.message));
201+ }
202+
203+ if (parent != null ) {
190204 return parent.getFields (node);
191205 } else {
192206 throw MustachioResolutionError (node.keySpan.message (
@@ -268,31 +282,52 @@ abstract class RendererBase<T> {
268282}
269283
270284String renderSimple (Object context, List <MustachioNode > ast, Template template,
271- {RendererBase parent}) {
272- var renderer = SimpleRenderer (context, parent, template);
285+ {@required RendererBase parent, Set < String > getters }) {
286+ var renderer = SimpleRenderer (context, parent, template, getters );
273287 renderer.renderBlock (ast);
274288 return renderer.buffer.toString ();
275289}
276290
277291class SimpleRenderer extends RendererBase <Object > {
278- SimpleRenderer (Object context, RendererBase <Object > parent, Template template)
279- : super (context, parent, template);
292+ SimpleRenderer (
293+ Object context,
294+ RendererBase <Object > parent,
295+ Template template,
296+ Set <String > invisibleGetters,
297+ ) : super (context, parent, template, invisibleGetters: invisibleGetters);
280298
281299 @override
282- 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+ }
283307
284308 @override
285309 String getFields (Variable node) {
286310 var names = node.key;
287- if (names.length == 1 && names.single == '.' ) {
311+ var firstName = node.key.first;
312+ if (names.length == 1 && firstName == '.' ) {
288313 return context.toString ();
289- }
290- if (parent != null ) {
314+ } else if (_invisibleGetters.contains (firstName)) {
315+ throw MustachioResolutionError (_failedKeyVisibilityMessage (firstName));
316+ } else if (parent != null ) {
291317 return parent.getFields (node);
292318 } else {
293319 return 'null' ;
294320 }
295321 }
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+ }
296331}
297332
298333/// An individual property of objects of type [T] , including functions for
@@ -346,7 +381,7 @@ class Property<T> {
346381class MustachioResolutionError extends Error {
347382 final String message;
348383
349- MustachioResolutionError ([ this .message] );
384+ MustachioResolutionError (this .message);
350385
351386 @override
352387 String toString () => 'MustachioResolutionError: $message ' ;
@@ -362,6 +397,17 @@ class PartialMustachioResolutionError extends Error {
362397 PartialMustachioResolutionError (this .name, this .contextType);
363398}
364399
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+
365411extension MapExtensions <T > on Map <String , Property <T >> {
366412 Property <T > getValue (String name) {
367413 if (containsKey (name)) {
0 commit comments