From 1e3b04bbc8ef30c63301e3b96c6e84928919ca63 Mon Sep 17 00:00:00 2001 From: Jonathan Koren Date: Fri, 14 Feb 2020 12:34:57 -0800 Subject: [PATCH 1/5] Implement renderers for markdown --- lib/src/model/package_builder.dart | 4 +- lib/src/render/category_renderer.dart | 14 ++++ lib/src/render/element_type_renderer.dart | 83 ++++++++++++++++++++ lib/src/render/enum_field_renderer.dart | 11 +++ lib/src/render/model_element_renderer.dart | 14 ++++ lib/src/render/parameter_renderer.dart | 29 +++++++ lib/src/render/renderer_factory.dart | 59 ++++++++++++++ lib/src/render/template_renderer.dart | 11 +++ lib/src/render/type_parameters_renderer.dart | 20 +++++ lib/src/render/typedef_renderer.dart | 13 +++ 10 files changed, 256 insertions(+), 2 deletions(-) diff --git a/lib/src/model/package_builder.dart b/lib/src/model/package_builder.dart index 572faedaad..e3da440664 100644 --- a/lib/src/model/package_builder.dart +++ b/lib/src/model/package_builder.dart @@ -42,8 +42,8 @@ class PackageBuilder { if (config.topLevelPackageMeta.needsPubGet) { config.topLevelPackageMeta.runPubGet(); } - // TODO(jdkoren): change factory for other formats based on config options - RendererFactory rendererFactory = HtmlRenderFactory(); + + RendererFactory rendererFactory = RendererFactory.forFormat(config.format); PackageGraph newGraph = PackageGraph.UninitializedPackageGraph( config, driver, sdk, hasEmbedderSdkFiles, rendererFactory); diff --git a/lib/src/render/category_renderer.dart b/lib/src/render/category_renderer.dart index 7b0b803c9e..be36cf92e6 100644 --- a/lib/src/render/category_renderer.dart +++ b/lib/src/render/category_renderer.dart @@ -46,3 +46,17 @@ class CategoryRendererHtml extends CategoryRenderer { } } } + +class CategoryRendererMd extends CategoryRenderer { + @override + String renderCategoryLabel(Category category) => category.linkedName; + + @override + String renderLinkedName(Category category) { + String name = category.name; + if (category.isDocumented) { + return '[$name](${category.href})'; + } + return name; + } +} diff --git a/lib/src/render/element_type_renderer.dart b/lib/src/render/element_type_renderer.dart index cc4be7a4fb..e81cdc0910 100644 --- a/lib/src/render/element_type_renderer.dart +++ b/lib/src/render/element_type_renderer.dart @@ -11,6 +11,8 @@ abstract class ElementTypeRenderer { String renderNameWithGenerics(T elementType) => ''; } +// Html implementations + class FunctionTypeElementTypeRendererHtml extends ElementTypeRenderer { @override @@ -93,3 +95,84 @@ class CallableElementTypeRendererHtml return buf.toString(); } } + +// Markdown implementations + +class FunctionTypeElementTypeRendererMd + extends ElementTypeRenderer { + @override + String renderLinkedName(FunctionTypeElementType elementType) { + StringBuffer buf = StringBuffer(); + buf.write('${elementType.returnType.linkedName} '); + buf.write('${elementType.nameWithGenerics}'); + buf.write('('); + buf.write( + ParameterRendererMd().renderLinkedParams(elementType.parameters)); + buf.write(')'); + return buf.toString(); + } + + @override + String renderNameWithGenerics(FunctionTypeElementType elementType) { + StringBuffer buf = StringBuffer(); + buf.write(elementType.name); + if (elementType.typeFormals.isNotEmpty) { + if (!elementType.typeFormals.every((t) => t.name == 'dynamic')) { + buf.write('<'); + buf.writeAll(elementType.typeFormals.map((t) => t.name), ', '); + buf.write('>'); + } + } + return buf.toString(); + } +} + +class ParameterizedElementTypeRendererMd + extends ElementTypeRenderer { + @override + String renderLinkedName(ParameterizedElementType elementType) { + StringBuffer buf = StringBuffer(); + buf.write(elementType.element.linkedName); + if (elementType.typeArguments.isNotEmpty && + !elementType.typeArguments.every((t) => t.name == 'dynamic')) { + buf.write('<'); + buf.writeAll(elementType.typeArguments.map((t) => t.linkedName), ', '); + buf.write('>'); + } + return buf.toString(); + } + + @override + String renderNameWithGenerics(ParameterizedElementType elementType) { + StringBuffer buf = StringBuffer(); + buf.write(elementType.element.name); + if (elementType.typeArguments.isNotEmpty && + !elementType.typeArguments.every((t) => t.name == 'dynamic')) { + buf.write('<'); + buf.writeAll( + elementType.typeArguments.map((t) => t.nameWithGenerics), ', '); + buf.write('>'); + } + return buf.toString(); + } +} + +class CallableElementTypeRendererMd + extends ElementTypeRenderer { + @override + String renderLinkedName(CallableElementType elementType) { + if (elementType.name != null && elementType.name.isNotEmpty) { + return elementType.superLinkedName; + } + + StringBuffer buf = StringBuffer(); + buf.write(elementType.nameWithGenerics); + buf.write('('); + buf.write(ParameterRendererMd() + .renderLinkedParams(elementType.element.parameters, showNames: false) + .trim()); + buf.write(') → '); + buf.write(elementType.returnType.linkedName); + return buf.toString(); + } +} diff --git a/lib/src/render/enum_field_renderer.dart b/lib/src/render/enum_field_renderer.dart index c9096b0b26..4a635909e7 100644 --- a/lib/src/render/enum_field_renderer.dart +++ b/lib/src/render/enum_field_renderer.dart @@ -18,3 +18,14 @@ class EnumFieldRendererHtml extends EnumFieldRenderer { } } } + +class EnumFieldRendererMd extends EnumFieldRenderer { + @override + String renderValue(EnumField field) { + if (field.name == 'values') { + return 'const List<${field.enclosingElement.name}>'; + } else { + return 'const ${field.enclosingElement.name}(${field.index})'; + } + } +} diff --git a/lib/src/render/model_element_renderer.dart b/lib/src/render/model_element_renderer.dart index 9d274d4fe4..d7755d4851 100644 --- a/lib/src/render/model_element_renderer.dart +++ b/lib/src/render/model_element_renderer.dart @@ -95,3 +95,17 @@ class ModelElementRendererHtml extends ModelElementRenderer { '''; // Must end at start of line, or following inline text will be indented. } } + +class ModelElementRendererMd extends ModelElementRendererHtml { + @override + String renderLinkedName(ModelElement modelElement) { + if (modelElement.isDeprecated) { + return '[~~${modelElement.name}~~](${modelElement.href})'; + } + return '[${modelElement.name}](${modelElement.href})'; + } + + @override + String renderExtendedDocLink(ModelElement modelElement) => + '[...](${modelElement.href})'; +} diff --git a/lib/src/render/parameter_renderer.dart b/lib/src/render/parameter_renderer.dart index 3e7a895c3a..fab430d259 100644 --- a/lib/src/render/parameter_renderer.dart +++ b/lib/src/render/parameter_renderer.dart @@ -42,6 +42,35 @@ class ParameterRendererHtml extends ParameterRenderer { String required(String required) => '$required'; } +class ParameterRendererMd extends ParameterRenderer { + @override + String annotation(String annotation) => annotation; + + @override + String covariant(String covariant) => covariant; + + @override + String defaultValue(String defaultValue) => defaultValue; + + @override + String listItem(String item) => item; + + @override + String orderedList(String listItems) => listItems; + + @override + String parameter(String parameter, String id) => parameter; + + @override + String parameterName(String parameterName) => parameterName; + + @override + String required(String required) => required; + + @override + String typeName(String typeName) => typeName; +} + abstract class ParameterRenderer { String listItem(String item); String orderedList(String listItems); diff --git a/lib/src/render/renderer_factory.dart b/lib/src/render/renderer_factory.dart index f8787228a8..01c32c54e8 100644 --- a/lib/src/render/renderer_factory.dart +++ b/lib/src/render/renderer_factory.dart @@ -14,6 +14,18 @@ import 'package:dartdoc/src/render/type_parameters_renderer.dart'; import 'package:dartdoc/src/render/typedef_renderer.dart'; abstract class RendererFactory { + + static RendererFactory forFormat(String format) { + switch (format) { + case 'html': + return HtmlRenderFactory(); + case 'md': + return MdRenderFactory(); + default: + throw ArgumentError('Unsupported format: $format'); + } + } + TemplateRenderer get templateRenderer; CategoryRenderer get categoryRenderer; @@ -86,3 +98,50 @@ class HtmlRenderFactory extends RendererFactory { @override TypedefRenderer get typedefRenderer => TypedefRendererHtml(); } + +class MdRenderFactory extends RendererFactory { + @override + TemplateRenderer get templateRenderer => MdTemplateRenderer(); + + @override + CategoryRenderer get categoryRenderer => CategoryRendererMd(); + + // We render documentation as HTML for now. + // TODO(jdkoren): explore using documentation directly in the output file. + @override + DocumentationRenderer get documentationRenderer => + DocumentationRendererHtml(); + + @override + ElementTypeRenderer get callableElementTypeRenderer => + CallableElementTypeRendererMd(); + + @override + ElementTypeRenderer + get functionTypeElementTypeRenderer => + FunctionTypeElementTypeRendererMd(); + + @override + ElementTypeRenderer + get parameterizedElementTypeRenderer => + ParameterizedElementTypeRendererMd(); + + @override + EnumFieldRenderer get enumFieldRenderer => EnumFieldRendererMd(); + + @override + ModelElementRenderer get modelElementRenderer => ModelElementRendererMd(); + + @override + ParameterRenderer get parameterRenderer => ParameterRendererMd(); + + @override + ParameterRenderer get parameterRendererDetailed => parameterRenderer; + + @override + TypeParametersRenderer get typeParametersRenderer => + TypeParametersRendererMd(); + + @override + TypedefRenderer get typedefRenderer => TypedefRendererMd(); +} diff --git a/lib/src/render/template_renderer.dart b/lib/src/render/template_renderer.dart index 3e0ed15391..e18ab4ac41 100644 --- a/lib/src/render/template_renderer.dart +++ b/lib/src/render/template_renderer.dart @@ -16,3 +16,14 @@ class HtmlTemplateRenderer implements TemplateRenderer { } } } + +class MdTemplateRenderer implements TemplateRenderer { + @override + String composeLayoutTitle(String name, String kind, bool isDeprecated) { + if (isDeprecated) { + return '~~${name}~~ ${kind}'; + } else { + return '${name} ${kind}'; + } + } +} diff --git a/lib/src/render/type_parameters_renderer.dart b/lib/src/render/type_parameters_renderer.dart index 879ca0ffaa..09ac2eeda0 100644 --- a/lib/src/render/type_parameters_renderer.dart +++ b/lib/src/render/type_parameters_renderer.dart @@ -6,6 +6,7 @@ import 'package:dartdoc/src/model/type_parameter.dart'; abstract class TypeParametersRenderer { String renderGenericParameters(TypeParameters typeParameters); + String renderLinkedGenericParameters(TypeParameters typeParameters); } @@ -32,3 +33,22 @@ class TypeParametersRendererHtml extends TypeParametersRenderer { return '<${joined}>'; } } + +class TypeParametersRendererMd extends TypeParametersRenderer { + @override + String renderGenericParameters(TypeParameters typeParameters) => + _compose(typeParameters.typeParameters, (t) => t.name); + + @override + String renderLinkedGenericParameters(TypeParameters typeParameters) => + _compose(typeParameters.typeParameters, (t) => t.linkedName); + + String _compose(List typeParameters, + String Function(TypeParameter) mapfn) { + if (typeParameters.isEmpty) { + return ''; + } + var joined = typeParameters.map(mapfn).join(', '); + return '<${joined}>'; + } +} diff --git a/lib/src/render/typedef_renderer.dart b/lib/src/render/typedef_renderer.dart index 385225b87b..e1571a11ae 100644 --- a/lib/src/render/typedef_renderer.dart +++ b/lib/src/render/typedef_renderer.dart @@ -20,3 +20,16 @@ class TypedefRendererHtml extends TypedefRenderer { return '<${joined}>'; } } + +class TypedefRendererMd extends TypedefRenderer { + @override + String renderGenericParameters(Typedef typedef) { + if (typedef.genericTypeParameters.isEmpty) { + return ''; + } + var joined = typedef.genericTypeParameters + .map((t) => t.name) + .join(', '); + return '<{$joined}>'; + } +} From bc983d9831e62a177838fd7e952863a901a66bbc Mon Sep 17 00:00:00 2001 From: Jonathan Koren Date: Fri, 14 Feb 2020 14:07:51 -0800 Subject: [PATCH 2/5] Remove unnecessary strikethroughs in templates --- lib/templates/md/_callable.md | 2 +- lib/templates/md/_class.md | 2 +- lib/templates/md/_constant.md | 2 +- lib/templates/md/_extension.md | 2 +- lib/templates/md/_mixin.md | 2 +- lib/templates/md/constructor.md | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/templates/md/_callable.md b/lib/templates/md/_callable.md index 49d5fdb5f3..9cc868478b 100644 --- a/lib/templates/md/_callable.md +++ b/lib/templates/md/_callable.md @@ -1,3 +1,3 @@ -{{#isDeprecated}}~~{{/isDeprecated}}{{{linkedName}}}{{{linkedGenericParameters}}}({{{ linkedParamsNoMetadata }}}) {{{ linkedReturnType }}} {{>categorization}}{{#isDeprecated}}~~{{/isDeprecated}} +{{{linkedName}}}{{{linkedGenericParameters}}}({{{ linkedParamsNoMetadata }}}) {{{ linkedReturnType }}} {{>categorization}} : {{{ oneLineDoc }}} {{{ extendedDocLink }}} {{>features}} diff --git a/lib/templates/md/_class.md b/lib/templates/md/_class.md index 90721f1898..7659fa0e6e 100644 --- a/lib/templates/md/_class.md +++ b/lib/templates/md/_class.md @@ -1,2 +1,2 @@ -{{#isDeprecated}}~~{{/isDeprecated}}{{{linkedName}}}{{{linkedGenericParameters}}} {{>categorization}}{{#isDeprecated}}~~{{/isDeprecated}} +{{{linkedName}}}{{{linkedGenericParameters}}} {{>categorization}} : {{{ oneLineDoc }}} {{{ extendedDocLink }}} diff --git a/lib/templates/md/_constant.md b/lib/templates/md/_constant.md index 54d040ba0d..6367e59957 100644 --- a/lib/templates/md/_constant.md +++ b/lib/templates/md/_constant.md @@ -1,3 +1,3 @@ -{{#isDeprecated}}~~{{/isDeprecated}}{{{ linkedName }}} const {{{ linkedReturnType }}} {{>categorization}}{{#isDeprecated}}~~{{/isDeprecated}} +{{{ linkedName }}} const {{{ linkedReturnType }}} {{>categorization}} : {{{ oneLineDoc }}} {{{ extendedDocLink }}} {{>features}} diff --git a/lib/templates/md/_extension.md b/lib/templates/md/_extension.md index 097235ead6..84737c4d56 100644 --- a/lib/templates/md/_extension.md +++ b/lib/templates/md/_extension.md @@ -1,2 +1,2 @@ -{{#isDeprecated}}~~{{/isDeprecated}}{{{linkedName}}} {{>categorization}}{{#isDeprecated}}~~{{/isDeprecated}} +{{{linkedName}}} {{>categorization}} : {{{ oneLineDoc }}} {{{ extendedDocLink }}} diff --git a/lib/templates/md/_mixin.md b/lib/templates/md/_mixin.md index 90721f1898..7659fa0e6e 100644 --- a/lib/templates/md/_mixin.md +++ b/lib/templates/md/_mixin.md @@ -1,2 +1,2 @@ -{{#isDeprecated}}~~{{/isDeprecated}}{{{linkedName}}}{{{linkedGenericParameters}}} {{>categorization}}{{#isDeprecated}}~~{{/isDeprecated}} +{{{linkedName}}}{{{linkedGenericParameters}}} {{>categorization}} : {{{ oneLineDoc }}} {{{ extendedDocLink }}} diff --git a/lib/templates/md/constructor.md b/lib/templates/md/constructor.md index bd50ad17a1..ce897718a4 100644 --- a/lib/templates/md/constructor.md +++ b/lib/templates/md/constructor.md @@ -14,7 +14,7 @@ {{/hasAnnotations}} {{#isConst}}const{{/isConst}} -{{#isDeprecated}}~~{{/isDeprecated}}{{{nameWithGenerics}}}({{#hasParameters}}{{{linkedParamsLines}}}{{/hasParameters}}){{#isDeprecated}}~~{{/isDeprecated}} +{{{nameWithGenerics}}}({{#hasParameters}}{{{linkedParamsLines}}}{{/hasParameters}}) {{>documentation}} From 399dd96eaceafd86a920d191c60d0ded724fae71 Mon Sep 17 00:00:00 2001 From: Jonathan Koren Date: Fri, 14 Feb 2020 15:42:34 -0800 Subject: [PATCH 3/5] 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. --- test/model_test.dart | 16 ++++++++++++++++ .../template_renderer_test.dart} | 18 ++++++++++++++++++ 2 files changed, 34 insertions(+) rename test/{html/html_template_renderer_test.dart => render/template_renderer_test.dart} (63%) diff --git a/test/model_test.dart b/test/model_test.dart index 5f4efdef7b..e57b91b154 100644 --- a/test/model_test.dart +++ b/test/model_test.dart @@ -373,6 +373,7 @@ void main() { equals(utils.kTestPackagePublicLibraries - 5)); }); + // TODO consider moving these to a separate suite test('CategoryRendererHtml renders category label', () { Category category = packageGraph.publicPackages.first.categories.first; CategoryRendererHtml renderer = CategoryRendererHtml(); @@ -388,6 +389,21 @@ void main() { expect(renderer.renderLinkedName(category), 'Superb'); }); + + test('CategoryRendererMd renders category label', () { + Category category = packageGraph.publicPackages.first.categories.first; + CategoryRendererMd renderer = CategoryRendererMd(); + expect( + renderer.renderCategoryLabel(category), + '[Superb](${HTMLBASE_PLACEHOLDER}topics/Superb-topic.html)'); + }); + + test('CategoryRendererMd renders linkedName', () { + Category category = packageGraph.publicPackages.first.categories.first; + CategoryRendererMd renderer = CategoryRendererMd(); + expect(renderer.renderLinkedName(category), + '[Superb](${HTMLBASE_PLACEHOLDER}topics/Superb-topic.html)'); + }); }); group('LibraryContainer', () { diff --git a/test/html/html_template_renderer_test.dart b/test/render/template_renderer_test.dart similarity index 63% rename from test/html/html_template_renderer_test.dart rename to test/render/template_renderer_test.dart index e0611b9b4d..577a3b4a4a 100644 --- a/test/html/html_template_renderer_test.dart +++ b/test/render/template_renderer_test.dart @@ -23,4 +23,22 @@ void main() { expect(test, equals('Banana Fruit')); }); }); + + group('MdTemplateRenderer', () { + MdTemplateRenderer renderer; + + setUpAll(() { + renderer = MdTemplateRenderer(); + }); + + test('composeLayoutTitle', () { + String test = renderer.composeLayoutTitle('Banana', 'Fruit', false); + expect(test, equals('Banana Fruit')); + }); + + test('composeLayoutTitle deprecated', () { + String test = renderer.composeLayoutTitle('Banana', 'Fruit', true); + expect(test, equals('~~Banana~~ Fruit')); + }); + }); } From 7e96cf8b6a78011e59269768f84098311320739e Mon Sep 17 00:00:00 2001 From: Jonathan Koren Date: Wed, 26 Feb 2020 10:46:10 -0800 Subject: [PATCH 4/5] dartfmt --- lib/src/render/element_type_renderer.dart | 3 +-- lib/src/render/renderer_factory.dart | 9 ++++----- lib/src/render/typedef_renderer.dart | 4 +--- test/model_test.dart | 3 +-- 4 files changed, 7 insertions(+), 12 deletions(-) diff --git a/lib/src/render/element_type_renderer.dart b/lib/src/render/element_type_renderer.dart index e81cdc0910..af03ac7be9 100644 --- a/lib/src/render/element_type_renderer.dart +++ b/lib/src/render/element_type_renderer.dart @@ -106,8 +106,7 @@ class FunctionTypeElementTypeRendererMd buf.write('${elementType.returnType.linkedName} '); buf.write('${elementType.nameWithGenerics}'); buf.write('('); - buf.write( - ParameterRendererMd().renderLinkedParams(elementType.parameters)); + buf.write(ParameterRendererMd().renderLinkedParams(elementType.parameters)); buf.write(')'); return buf.toString(); } diff --git a/lib/src/render/renderer_factory.dart b/lib/src/render/renderer_factory.dart index 01c32c54e8..eaead50507 100644 --- a/lib/src/render/renderer_factory.dart +++ b/lib/src/render/renderer_factory.dart @@ -14,7 +14,6 @@ import 'package:dartdoc/src/render/type_parameters_renderer.dart'; import 'package:dartdoc/src/render/typedef_renderer.dart'; abstract class RendererFactory { - static RendererFactory forFormat(String format) { switch (format) { case 'html': @@ -118,13 +117,13 @@ class MdRenderFactory extends RendererFactory { @override ElementTypeRenderer - get functionTypeElementTypeRenderer => - FunctionTypeElementTypeRendererMd(); + get functionTypeElementTypeRenderer => + FunctionTypeElementTypeRendererMd(); @override ElementTypeRenderer - get parameterizedElementTypeRenderer => - ParameterizedElementTypeRendererMd(); + get parameterizedElementTypeRenderer => + ParameterizedElementTypeRendererMd(); @override EnumFieldRenderer get enumFieldRenderer => EnumFieldRendererMd(); diff --git a/lib/src/render/typedef_renderer.dart b/lib/src/render/typedef_renderer.dart index e1571a11ae..b79e3b3091 100644 --- a/lib/src/render/typedef_renderer.dart +++ b/lib/src/render/typedef_renderer.dart @@ -27,9 +27,7 @@ class TypedefRendererMd extends TypedefRenderer { if (typedef.genericTypeParameters.isEmpty) { return ''; } - var joined = typedef.genericTypeParameters - .map((t) => t.name) - .join(', '); + var joined = typedef.genericTypeParameters.map((t) => t.name).join(', '); return '<{$joined}>'; } } diff --git a/test/model_test.dart b/test/model_test.dart index e57b91b154..9fc8dbda34 100644 --- a/test/model_test.dart +++ b/test/model_test.dart @@ -393,8 +393,7 @@ void main() { test('CategoryRendererMd renders category label', () { Category category = packageGraph.publicPackages.first.categories.first; CategoryRendererMd renderer = CategoryRendererMd(); - expect( - renderer.renderCategoryLabel(category), + expect(renderer.renderCategoryLabel(category), '[Superb](${HTMLBASE_PLACEHOLDER}topics/Superb-topic.html)'); }); From aa6ac2eca69f92e6d43a2a9079f1aa1611ef18cd Mon Sep 17 00:00:00 2001 From: Jonathan Koren Date: Fri, 28 Feb 2020 10:20:17 -0800 Subject: [PATCH 5/5] 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. --- lib/src/render/category_renderer.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/render/category_renderer.dart b/lib/src/render/category_renderer.dart index be36cf92e6..d8e5f0598d 100644 --- a/lib/src/render/category_renderer.dart +++ b/lib/src/render/category_renderer.dart @@ -31,7 +31,7 @@ class CategoryRendererHtml extends CategoryRenderer { StringBuffer buf = StringBuffer(); buf.write(''); - buf.write(category.linkedName); + buf.write(renderLinkedName(category)); buf.write(''); return buf.toString(); } @@ -49,7 +49,7 @@ class CategoryRendererHtml extends CategoryRenderer { class CategoryRendererMd extends CategoryRenderer { @override - String renderCategoryLabel(Category category) => category.linkedName; + String renderCategoryLabel(Category category) => renderLinkedName(category); @override String renderLinkedName(Category category) {