Skip to content

Commit f5aaae5

Browse files
authored
Add markdown renderers and renderer factory (#2152)
* Implement renderers for markdown * Remove unnecessary strikethroughs in templates * Add some tests Only enough to mirror the few tests we have that exercise the existing renderers. In the future, we should have a more comprehensive suite that covers both html and md. * dartfmt * Call renderLinkedName directly from renderCategoryLabel This avoids calling back to Category, which immediately calls the renderer again anyway, except that during a test the second call went to an html renderer because the packageGraph was holding a HtmlRendererFactory.
1 parent 7479aa8 commit f5aaae5

18 files changed

+292
-9
lines changed

lib/src/model/package_builder.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,8 @@ class PackageBuilder {
4242
if (config.topLevelPackageMeta.needsPubGet) {
4343
config.topLevelPackageMeta.runPubGet();
4444
}
45-
// TODO(jdkoren): change factory for other formats based on config options
46-
RendererFactory rendererFactory = HtmlRenderFactory();
45+
46+
RendererFactory rendererFactory = RendererFactory.forFormat(config.format);
4747

4848
PackageGraph newGraph = PackageGraph.UninitializedPackageGraph(
4949
config, driver, sdk, hasEmbedderSdkFiles, rendererFactory);

lib/src/render/category_renderer.dart

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ class CategoryRendererHtml extends CategoryRenderer {
3131

3232
StringBuffer buf = StringBuffer();
3333
buf.write('<span class="${spanClasses.join(' ')}" title="$spanTitle">');
34-
buf.write(category.linkedName);
34+
buf.write(renderLinkedName(category));
3535
buf.write('</span>');
3636
return buf.toString();
3737
}
@@ -46,3 +46,17 @@ class CategoryRendererHtml extends CategoryRenderer {
4646
}
4747
}
4848
}
49+
50+
class CategoryRendererMd extends CategoryRenderer {
51+
@override
52+
String renderCategoryLabel(Category category) => renderLinkedName(category);
53+
54+
@override
55+
String renderLinkedName(Category category) {
56+
String name = category.name;
57+
if (category.isDocumented) {
58+
return '[$name](${category.href})';
59+
}
60+
return name;
61+
}
62+
}

lib/src/render/element_type_renderer.dart

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ abstract class ElementTypeRenderer<T extends ElementType> {
1111
String renderNameWithGenerics(T elementType) => '';
1212
}
1313

14+
// Html implementations
15+
1416
class FunctionTypeElementTypeRendererHtml
1517
extends ElementTypeRenderer<FunctionTypeElementType> {
1618
@override
@@ -93,3 +95,83 @@ class CallableElementTypeRendererHtml
9395
return buf.toString();
9496
}
9597
}
98+
99+
// Markdown implementations
100+
101+
class FunctionTypeElementTypeRendererMd
102+
extends ElementTypeRenderer<FunctionTypeElementType> {
103+
@override
104+
String renderLinkedName(FunctionTypeElementType elementType) {
105+
StringBuffer buf = StringBuffer();
106+
buf.write('${elementType.returnType.linkedName} ');
107+
buf.write('${elementType.nameWithGenerics}');
108+
buf.write('(');
109+
buf.write(ParameterRendererMd().renderLinkedParams(elementType.parameters));
110+
buf.write(')');
111+
return buf.toString();
112+
}
113+
114+
@override
115+
String renderNameWithGenerics(FunctionTypeElementType elementType) {
116+
StringBuffer buf = StringBuffer();
117+
buf.write(elementType.name);
118+
if (elementType.typeFormals.isNotEmpty) {
119+
if (!elementType.typeFormals.every((t) => t.name == 'dynamic')) {
120+
buf.write('<');
121+
buf.writeAll(elementType.typeFormals.map((t) => t.name), ', ');
122+
buf.write('>');
123+
}
124+
}
125+
return buf.toString();
126+
}
127+
}
128+
129+
class ParameterizedElementTypeRendererMd
130+
extends ElementTypeRenderer<ParameterizedElementType> {
131+
@override
132+
String renderLinkedName(ParameterizedElementType elementType) {
133+
StringBuffer buf = StringBuffer();
134+
buf.write(elementType.element.linkedName);
135+
if (elementType.typeArguments.isNotEmpty &&
136+
!elementType.typeArguments.every((t) => t.name == 'dynamic')) {
137+
buf.write('<');
138+
buf.writeAll(elementType.typeArguments.map((t) => t.linkedName), ', ');
139+
buf.write('>');
140+
}
141+
return buf.toString();
142+
}
143+
144+
@override
145+
String renderNameWithGenerics(ParameterizedElementType elementType) {
146+
StringBuffer buf = StringBuffer();
147+
buf.write(elementType.element.name);
148+
if (elementType.typeArguments.isNotEmpty &&
149+
!elementType.typeArguments.every((t) => t.name == 'dynamic')) {
150+
buf.write('<');
151+
buf.writeAll(
152+
elementType.typeArguments.map((t) => t.nameWithGenerics), ', ');
153+
buf.write('>');
154+
}
155+
return buf.toString();
156+
}
157+
}
158+
159+
class CallableElementTypeRendererMd
160+
extends ElementTypeRenderer<CallableElementType> {
161+
@override
162+
String renderLinkedName(CallableElementType elementType) {
163+
if (elementType.name != null && elementType.name.isNotEmpty) {
164+
return elementType.superLinkedName;
165+
}
166+
167+
StringBuffer buf = StringBuffer();
168+
buf.write(elementType.nameWithGenerics);
169+
buf.write('(');
170+
buf.write(ParameterRendererMd()
171+
.renderLinkedParams(elementType.element.parameters, showNames: false)
172+
.trim());
173+
buf.write(') → ');
174+
buf.write(elementType.returnType.linkedName);
175+
return buf.toString();
176+
}
177+
}

lib/src/render/enum_field_renderer.dart

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,14 @@ class EnumFieldRendererHtml extends EnumFieldRenderer {
1818
}
1919
}
2020
}
21+
22+
class EnumFieldRendererMd extends EnumFieldRenderer {
23+
@override
24+
String renderValue(EnumField field) {
25+
if (field.name == 'values') {
26+
return 'const List<${field.enclosingElement.name}>';
27+
} else {
28+
return 'const ${field.enclosingElement.name}(${field.index})';
29+
}
30+
}
31+
}

lib/src/render/model_element_renderer.dart

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,3 +95,17 @@ class ModelElementRendererHtml extends ModelElementRenderer {
9595
'''; // Must end at start of line, or following inline text will be indented.
9696
}
9797
}
98+
99+
class ModelElementRendererMd extends ModelElementRendererHtml {
100+
@override
101+
String renderLinkedName(ModelElement modelElement) {
102+
if (modelElement.isDeprecated) {
103+
return '[~~${modelElement.name}~~](${modelElement.href})';
104+
}
105+
return '[${modelElement.name}](${modelElement.href})';
106+
}
107+
108+
@override
109+
String renderExtendedDocLink(ModelElement modelElement) =>
110+
'[...](${modelElement.href})';
111+
}

lib/src/render/parameter_renderer.dart

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,35 @@ class ParameterRendererHtml extends ParameterRenderer {
4242
String required(String required) => '<span>$required</span>';
4343
}
4444

45+
class ParameterRendererMd extends ParameterRenderer {
46+
@override
47+
String annotation(String annotation) => annotation;
48+
49+
@override
50+
String covariant(String covariant) => covariant;
51+
52+
@override
53+
String defaultValue(String defaultValue) => defaultValue;
54+
55+
@override
56+
String listItem(String item) => item;
57+
58+
@override
59+
String orderedList(String listItems) => listItems;
60+
61+
@override
62+
String parameter(String parameter, String id) => parameter;
63+
64+
@override
65+
String parameterName(String parameterName) => parameterName;
66+
67+
@override
68+
String required(String required) => required;
69+
70+
@override
71+
String typeName(String typeName) => typeName;
72+
}
73+
4574
abstract class ParameterRenderer {
4675
String listItem(String item);
4776
String orderedList(String listItems);

lib/src/render/renderer_factory.dart

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,17 @@ import 'package:dartdoc/src/render/type_parameters_renderer.dart';
1414
import 'package:dartdoc/src/render/typedef_renderer.dart';
1515

1616
abstract class RendererFactory {
17+
static RendererFactory forFormat(String format) {
18+
switch (format) {
19+
case 'html':
20+
return HtmlRenderFactory();
21+
case 'md':
22+
return MdRenderFactory();
23+
default:
24+
throw ArgumentError('Unsupported format: $format');
25+
}
26+
}
27+
1728
TemplateRenderer get templateRenderer;
1829

1930
CategoryRenderer get categoryRenderer;
@@ -86,3 +97,50 @@ class HtmlRenderFactory extends RendererFactory {
8697
@override
8798
TypedefRenderer get typedefRenderer => TypedefRendererHtml();
8899
}
100+
101+
class MdRenderFactory extends RendererFactory {
102+
@override
103+
TemplateRenderer get templateRenderer => MdTemplateRenderer();
104+
105+
@override
106+
CategoryRenderer get categoryRenderer => CategoryRendererMd();
107+
108+
// We render documentation as HTML for now.
109+
// TODO(jdkoren): explore using documentation directly in the output file.
110+
@override
111+
DocumentationRenderer get documentationRenderer =>
112+
DocumentationRendererHtml();
113+
114+
@override
115+
ElementTypeRenderer<CallableElementType> get callableElementTypeRenderer =>
116+
CallableElementTypeRendererMd();
117+
118+
@override
119+
ElementTypeRenderer<FunctionTypeElementType>
120+
get functionTypeElementTypeRenderer =>
121+
FunctionTypeElementTypeRendererMd();
122+
123+
@override
124+
ElementTypeRenderer<ParameterizedElementType>
125+
get parameterizedElementTypeRenderer =>
126+
ParameterizedElementTypeRendererMd();
127+
128+
@override
129+
EnumFieldRenderer get enumFieldRenderer => EnumFieldRendererMd();
130+
131+
@override
132+
ModelElementRenderer get modelElementRenderer => ModelElementRendererMd();
133+
134+
@override
135+
ParameterRenderer get parameterRenderer => ParameterRendererMd();
136+
137+
@override
138+
ParameterRenderer get parameterRendererDetailed => parameterRenderer;
139+
140+
@override
141+
TypeParametersRenderer get typeParametersRenderer =>
142+
TypeParametersRendererMd();
143+
144+
@override
145+
TypedefRenderer get typedefRenderer => TypedefRendererMd();
146+
}

lib/src/render/template_renderer.dart

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,14 @@ class HtmlTemplateRenderer implements TemplateRenderer {
1616
}
1717
}
1818
}
19+
20+
class MdTemplateRenderer implements TemplateRenderer {
21+
@override
22+
String composeLayoutTitle(String name, String kind, bool isDeprecated) {
23+
if (isDeprecated) {
24+
return '~~${name}~~ ${kind}';
25+
} else {
26+
return '${name} ${kind}';
27+
}
28+
}
29+
}

lib/src/render/type_parameters_renderer.dart

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import 'package:dartdoc/src/model/type_parameter.dart';
66

77
abstract class TypeParametersRenderer {
88
String renderGenericParameters(TypeParameters typeParameters);
9+
910
String renderLinkedGenericParameters(TypeParameters typeParameters);
1011
}
1112

@@ -32,3 +33,22 @@ class TypeParametersRendererHtml extends TypeParametersRenderer {
3233
return '<span class="signature">&lt;<wbr><span class="type-parameter">${joined}</span>&gt;</span>';
3334
}
3435
}
36+
37+
class TypeParametersRendererMd extends TypeParametersRenderer {
38+
@override
39+
String renderGenericParameters(TypeParameters typeParameters) =>
40+
_compose(typeParameters.typeParameters, (t) => t.name);
41+
42+
@override
43+
String renderLinkedGenericParameters(TypeParameters typeParameters) =>
44+
_compose(typeParameters.typeParameters, (t) => t.linkedName);
45+
46+
String _compose(List<TypeParameter> typeParameters,
47+
String Function(TypeParameter) mapfn) {
48+
if (typeParameters.isEmpty) {
49+
return '';
50+
}
51+
var joined = typeParameters.map(mapfn).join(', ');
52+
return '<${joined}>';
53+
}
54+
}

lib/src/render/typedef_renderer.dart

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,14 @@ class TypedefRendererHtml extends TypedefRenderer {
2020
return '&lt;<wbr><span class="type-parameter">${joined}</span>&gt;';
2121
}
2222
}
23+
24+
class TypedefRendererMd extends TypedefRenderer {
25+
@override
26+
String renderGenericParameters(Typedef typedef) {
27+
if (typedef.genericTypeParameters.isEmpty) {
28+
return '';
29+
}
30+
var joined = typedef.genericTypeParameters.map((t) => t.name).join(', ');
31+
return '<{$joined}>';
32+
}
33+
}

lib/templates/md/_callable.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
{{#isDeprecated}}~~{{/isDeprecated}}{{{linkedName}}}{{{linkedGenericParameters}}}({{{ linkedParamsNoMetadata }}}) {{{ linkedReturnType }}} {{>categorization}}{{#isDeprecated}}~~{{/isDeprecated}}
1+
{{{linkedName}}}{{{linkedGenericParameters}}}({{{ linkedParamsNoMetadata }}}) {{{ linkedReturnType }}} {{>categorization}}
22
: {{{ oneLineDoc }}} {{{ extendedDocLink }}}
33
{{>features}}

lib/templates/md/_class.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
{{#isDeprecated}}~~{{/isDeprecated}}{{{linkedName}}}{{{linkedGenericParameters}}} {{>categorization}}{{#isDeprecated}}~~{{/isDeprecated}}
1+
{{{linkedName}}}{{{linkedGenericParameters}}} {{>categorization}}
22
: {{{ oneLineDoc }}} {{{ extendedDocLink }}}

lib/templates/md/_constant.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
{{#isDeprecated}}~~{{/isDeprecated}}{{{ linkedName }}} const {{{ linkedReturnType }}} {{>categorization}}{{#isDeprecated}}~~{{/isDeprecated}}
1+
{{{ linkedName }}} const {{{ linkedReturnType }}} {{>categorization}}
22
: {{{ oneLineDoc }}} {{{ extendedDocLink }}}
33
{{>features}}

lib/templates/md/_extension.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
{{#isDeprecated}}~~{{/isDeprecated}}{{{linkedName}}} {{>categorization}}{{#isDeprecated}}~~{{/isDeprecated}}
1+
{{{linkedName}}} {{>categorization}}
22
: {{{ oneLineDoc }}} {{{ extendedDocLink }}}

lib/templates/md/_mixin.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
{{#isDeprecated}}~~{{/isDeprecated}}{{{linkedName}}}{{{linkedGenericParameters}}} {{>categorization}}{{#isDeprecated}}~~{{/isDeprecated}}
1+
{{{linkedName}}}{{{linkedGenericParameters}}} {{>categorization}}
22
: {{{ oneLineDoc }}} {{{ extendedDocLink }}}

lib/templates/md/constructor.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
{{/hasAnnotations}}
1515

1616
{{#isConst}}const{{/isConst}}
17-
{{#isDeprecated}}~~{{/isDeprecated}}{{{nameWithGenerics}}}({{#hasParameters}}{{{linkedParamsLines}}}{{/hasParameters}}){{#isDeprecated}}~~{{/isDeprecated}}
17+
{{{nameWithGenerics}}}({{#hasParameters}}{{{linkedParamsLines}}}{{/hasParameters}})
1818

1919
{{>documentation}}
2020

test/model_test.dart

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -373,6 +373,7 @@ void main() {
373373
equals(utils.kTestPackagePublicLibraries - 5));
374374
});
375375

376+
// TODO consider moving these to a separate suite
376377
test('CategoryRendererHtml renders category label', () {
377378
Category category = packageGraph.publicPackages.first.categories.first;
378379
CategoryRendererHtml renderer = CategoryRendererHtml();
@@ -388,6 +389,20 @@ void main() {
388389
expect(renderer.renderLinkedName(category),
389390
'<a href="${HTMLBASE_PLACEHOLDER}topics/Superb-topic.html">Superb</a>');
390391
});
392+
393+
test('CategoryRendererMd renders category label', () {
394+
Category category = packageGraph.publicPackages.first.categories.first;
395+
CategoryRendererMd renderer = CategoryRendererMd();
396+
expect(renderer.renderCategoryLabel(category),
397+
'[Superb](${HTMLBASE_PLACEHOLDER}topics/Superb-topic.html)');
398+
});
399+
400+
test('CategoryRendererMd renders linkedName', () {
401+
Category category = packageGraph.publicPackages.first.categories.first;
402+
CategoryRendererMd renderer = CategoryRendererMd();
403+
expect(renderer.renderLinkedName(category),
404+
'[Superb](${HTMLBASE_PLACEHOLDER}topics/Superb-topic.html)');
405+
});
391406
});
392407

393408
group('LibraryContainer', () {

0 commit comments

Comments
 (0)