Skip to content

Commit a8d14b0

Browse files
authored
Mustachio: Add getProperty(), and end-to-end Renderer test (#2442)
Mustachio: Add property getters to Property, and end-to-end Renderer test This includes some unorthodox tests: I've added a package in `testing/mustachio`, complete with a pubspec.yaml and a build.yaml. These are required in order to test the runtime behavior of generated code; in order to make assertions on the generated code (not its String content, but its runtime properties), it must be generated with public classes (the default is private). In order to pass a configuration to the Builder, I need a build.yaml separate from dartdoc's. In order to import `testing/mustachio/lib/foo.renderers.dart`, and resolve that library's reference to `package:testing_mustachio/foo.dart`, I need to add `testing_mustachio` to `dev_dependencies`. In order to reference the builders from the testing package, the builder code needed to move from `tool/mustachio/` to `lib/src/mustachio/`. The generated code now always references the library that generated it, on the assumption that it needs types from that file. This is not necessarily correct, but is a decent approximation until we start using code_builder. RendererBase now needs to know about the renderer of the parent context type, so it gets a new field. Property and each renderer's propertyMap gets corrected code; the runtime tests revealed some runtime type bugs.
1 parent ffa4004 commit a8d14b0

File tree

10 files changed

+893
-300
lines changed

10 files changed

+893
-300
lines changed

build.yaml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,26 @@ targets:
1616
sources:
1717
- tool/builder.dart
1818
- tool/mustachio/builder.dart
19+
testing:
20+
sources:
21+
exclude:
22+
- tool/builder.dart
23+
- tool/mustachio/builder.dart
24+
builders:
25+
dartdoc|mustachio_builder:
26+
enabled: true
27+
generate_for:
28+
- test/mustachio/foo.dart
29+
options:
30+
rendererClassesArePublic: true
31+
build_test:test_bootstrap:
32+
enabled: false
33+
build_version:build_version:
34+
enabled: false
1935

2036
$default:
37+
dependencies:
38+
- :testing
2139
sources:
2240
exclude:
2341
- tool/builder.dart

lib/src/generator/templates.renderers.dart

Lines changed: 415 additions & 265 deletions
Large diffs are not rendered by default.

lib/src/mustachio/renderer_base.dart

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,62 @@ abstract class RendererBase<T> {
1010
/// The context object which this renderer can render.
1111
final T context;
1212

13+
/// The renderer of the parent context, if any, otherwise `null`.
14+
final RendererBase parent;
15+
1316
/// The output buffer into which [context] is rendered, using a template.
1417
final buffer = StringBuffer();
1518

16-
RendererBase(this.context);
19+
RendererBase(this.context, this.parent);
1720

1821
void write(String text) => buffer.write(text);
1922

23+
/// Returns the [Property] on this renderer named [name].
24+
///
25+
/// If no property named [name] exists for this renderer, `null` is returned.
26+
Property getProperty(String key);
27+
28+
/// Resolves [names] into one or more field accesses, returning the result as
29+
/// a String.
30+
///
31+
/// [names] may have multiple dot-separate names, and [names] may not be a
32+
/// valid property of _this_ context type, in which the [parent] renderer is
33+
/// referenced.
34+
String getFields(List<String> names) {
35+
if (names.length == 1 && names.single == '.') {
36+
return context.toString();
37+
}
38+
var property = getProperty(names.first);
39+
if (property != null) {
40+
var value = property.getValue(context);
41+
for (var name in names.skip(1)) {
42+
if (property.getProperties().containsKey(name)) {
43+
property = property.getProperties()[name];
44+
value = property.getValue(value);
45+
} else {
46+
throw MustachioResolutionError(
47+
'Failed to resolve ${names.join('.')} as a property '
48+
'on ${context.runtimeType}');
49+
}
50+
}
51+
return value.toString();
52+
} else if (parent != null) {
53+
return parent.getFields(names);
54+
} else {
55+
throw MustachioResolutionError(
56+
'Failed to resolve ${names.first} as a property '
57+
'on any types in the current context');
58+
}
59+
}
60+
2061
/// Renders a block of Mustache template, the [ast], into [buffer].
2162
void renderBlock(List<MustachioNode> ast) {
2263
for (var node in ast) {
2364
if (node is Text) {
2465
write(node.content);
2566
} else if (node is Variable) {
26-
// TODO(srawlins): Implement.
67+
var content = getFields(node.key);
68+
write(content);
2769
} else if (node is Section) {
2870
section(node);
2971
} else if (node is Partial) {
@@ -60,3 +102,10 @@ class Property<T> {
60102

61103
Property({@required this.getValue, this.getProperties, this.getBool});
62104
}
105+
106+
/// An error indicating that a renderer failed to resolve a key.
107+
class MustachioResolutionError extends Error {
108+
String message;
109+
110+
MustachioResolutionError(this.message);
111+
}

test/mustachio/builder_test.dart

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ class Bar {}
9090
expect(
9191
generatedContent,
9292
contains(
93-
'String _render_FooBase(FooBase context, List<MustachioNode> ast)'));
93+
'String _render_FooBase(FooBase context, List<MustachioNode> ast,'));
9494
// The renderer class for Foo
9595
expect(generatedContent,
9696
contains('class _Renderer_FooBase extends RendererBase<FooBase>'));
@@ -101,7 +101,7 @@ class Bar {}
101101
expect(
102102
generatedContent,
103103
contains(
104-
'String _render_Object(Object context, List<MustachioNode> ast) {'));
104+
'String _render_Object(Object context, List<MustachioNode> ast,'));
105105
// The renderer class for Object
106106
expect(generatedContent,
107107
contains('class _Renderer_Object extends RendererBase<Object> {'));
@@ -120,13 +120,13 @@ class Bar {}
120120

121121
test('with a property map', () {
122122
expect(generatedContent,
123-
contains('static Map<String, Property<Foo>> propertyMap() => {'));
123+
contains('static Map<String, Property> propertyMap() => {'));
124124
});
125125

126126
test('with a property map with a String property', () {
127127
expect(generatedContent, contains('''
128128
's1': Property(
129-
getValue: (Foo c) => c.s1,
129+
getValue: (Object c) => (c as Foo).s1,
130130
getProperties: _Renderer_String.propertyMap,
131131
),
132132
'''));
@@ -139,9 +139,9 @@ class Bar {}
139139
test('with a property map with a bool property', () {
140140
expect(generatedContent, contains('''
141141
'b1': Property(
142-
getValue: (Foo c) => c.b1,
142+
getValue: (Object c) => (c as Foo).b1,
143143
getProperties: _Renderer_bool.propertyMap,
144-
getBool: (Foo c) => c.b1 == true,
144+
getBool: (Object c) => (c as Foo).b1 == true,
145145
),
146146
'''));
147147
});
@@ -191,7 +191,7 @@ import 'package:mustachio/annotations.dart';
191191
expect(
192192
generatedContent,
193193
contains(
194-
'String renderFoo<T>(Foo<T> context, List<MustachioNode> ast)'));
194+
'String renderFoo<T>(Foo<T> context, List<MustachioNode> ast,'));
195195
});
196196

197197
test('with a generic supertype type argument', () async {

test/mustachio/foo.dart

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
@Renderer(#renderFoo, Context<Foo>())
2+
library dartdoc.testing.foo;
3+
4+
import 'package:dartdoc/src/mustachio/annotations.dart';
5+
6+
class Foo {
7+
String s1 = 's1';
8+
bool b1 = true;
9+
}

0 commit comments

Comments
 (0)