Move renderer access behind a renderer factory (#2086)

Renderers are now accessed from a RendererFactory held by the package
graph. This allows for changing renderers based on the configuration
of DartDoc without models having to know anything about that.
diff --git a/lib/src/element_type.dart b/lib/src/element_type.dart
index e9f7139..fc0d5ef 100644
--- a/lib/src/element_type.dart
+++ b/lib/src/element_type.dart
@@ -132,8 +132,7 @@
   @override
   String get linkedName {
     if (_linkedName == null) {
-      _linkedName =
-          FunctionTypeElementTypeRendererHtml().renderLinkedName(this);
+      _linkedName = _renderer.renderLinkedName(this);
     }
     return _linkedName;
   }
@@ -146,8 +145,7 @@
   @override
   String get nameWithGenerics {
     if (_nameWithGenerics == null) {
-      _nameWithGenerics =
-          FunctionTypeElementTypeRendererHtml().renderNameWithGenerics(this);
+      _nameWithGenerics = _renderer.renderNameWithGenerics(this);
     }
     return _nameWithGenerics;
   }
@@ -162,6 +160,9 @@
 
   @override
   String get name => 'Function';
+
+  ElementTypeRenderer<FunctionTypeElementType> get _renderer =>
+      packageGraph.rendererFactory.functionTypeElementTypeRenderer;
 }
 
 class ParameterizedElementType extends DefinedElementType {
@@ -173,8 +174,7 @@
   @override
   String get linkedName {
     if (_linkedName == null) {
-      _linkedName =
-          ParameterizedElementTypeRendererHtml().renderLinkedName(this);
+      _linkedName = _renderer.renderLinkedName(this);
     }
     return _linkedName;
   }
@@ -183,11 +183,13 @@
   @override
   String get nameWithGenerics {
     if (_nameWithGenerics == null) {
-      _nameWithGenerics =
-          ParameterizedElementTypeRendererHtml().renderNameWithGenerics(this);
+      _nameWithGenerics = _renderer.renderNameWithGenerics(this);
     }
     return _nameWithGenerics;
   }
+
+  ElementTypeRenderer<ParameterizedElementType> get _renderer =>
+      packageGraph.rendererFactory.parameterizedElementTypeRenderer;
 }
 
 class TypeParameterElementType extends DefinedElementType {
@@ -362,12 +364,16 @@
   @override
   String get linkedName {
     if (_linkedName == null) {
-      _linkedName = CallableElementTypeRendererHtml().renderLinkedName(this);
+      _linkedName = _renderer.renderLinkedName(this);
     }
     return _linkedName;
   }
 
   String get superLinkedName => super.linkedName;
+
+  @override
+  ElementTypeRenderer<CallableElementType> get _renderer =>
+      packageGraph.rendererFactory.callableElementTypeRenderer;
 }
 
 /// Types backed by a [GenericTypeAliasElement] that may or may not be callable.
diff --git a/lib/src/model/category.dart b/lib/src/model/category.dart
index 8464d50..6c61162 100644
--- a/lib/src/model/category.dart
+++ b/lib/src/model/category.dart
@@ -124,13 +124,9 @@
   String get href =>
       isCanonical ? '${package.baseHref}topics/${name}-topic.html' : null;
 
-  String get categorization {
-    return CategoryRendererHtml().renderCategoryLabel(this);
-  }
+  String get categorization => _renderer.renderCategoryLabel(this);
 
-  String get linkedName {
-    return CategoryRendererHtml().renderLinkedName(this);
-  }
+  String get linkedName => _renderer.renderLinkedName(this);
 
   int _categoryIndex;
 
@@ -201,4 +197,7 @@
 
   @override
   Iterable<Typedef> get typedefs => _typedefs;
+
+  CategoryRenderer get _renderer =>
+      packageGraph.rendererFactory.categoryRenderer;
 }
diff --git a/lib/src/model/documentation.dart b/lib/src/model/documentation.dart
index b22fa09..8344fdd 100644
--- a/lib/src/model/documentation.dart
+++ b/lib/src/model/documentation.dart
@@ -53,7 +53,7 @@
     _hasExtendedDocs = parseResult.item2;
 
     Tuple2<String, String> renderResult =
-        DocumentationRendererHtml().render(parseResult.item1, processAllDocs);
+        _renderer.render(parseResult.item1, processAllDocs);
 
     if (processAllDocs) {
       _asHtml = renderResult.item1;
@@ -71,4 +71,7 @@
         MarkdownDocument.withElementLinkResolver(_element, commentRefs);
     return document.parseMarkdownText(text, processFullDocs);
   }
+
+  DocumentationRenderer get _renderer =>
+      _element.packageGraph.rendererFactory.documentationRenderer;
 }
diff --git a/lib/src/model/enum.dart b/lib/src/model/enum.dart
index a534a36..017aca1 100644
--- a/lib/src/model/enum.dart
+++ b/lib/src/model/enum.dart
@@ -44,7 +44,7 @@
       : super(element, library, packageGraph, getter, null);
 
   @override
-  String get constantValueBase => EnumFieldRendererHtml().renderValue(this);
+  String get constantValueBase => _fieldRenderer.renderValue(this);
 
   @override
   List<ModelElement> get documentationFrom {
@@ -104,4 +104,7 @@
 
   @override
   Inheritable get overriddenElement => null;
+
+  EnumFieldRenderer get _fieldRenderer =>
+      packageGraph.rendererFactory.enumFieldRenderer;
 }
diff --git a/lib/src/model/model_element.dart b/lib/src/model/model_element.dart
index 8bc6918..8d33b86 100644
--- a/lib/src/model/model_element.dart
+++ b/lib/src/model/model_element.dart
@@ -25,8 +25,8 @@
 import 'package:dartdoc/src/logging.dart';
 import 'package:dartdoc/src/model/model.dart';
 import 'package:dartdoc/src/model_utils.dart' as utils;
-import 'package:dartdoc/src/render/parameter_renderer.dart';
 import 'package:dartdoc/src/render/model_element_renderer.dart';
+import 'package:dartdoc/src/render/parameter_renderer.dart';
 import 'package:dartdoc/src/source_linker.dart';
 import 'package:dartdoc/src/tuple.dart';
 import 'package:dartdoc/src/utils.dart';
@@ -784,7 +784,7 @@
   /// does not exist.
   String get extendedDocLink {
     if (hasExtendedDocumentation) {
-      return ModelElementRendererHtml().renderExtendedDocLink(this);
+      return _modelElementRenderer.renderExtendedDocLink(this);
     }
     return '';
   }
@@ -916,18 +916,25 @@
     return _linkedName;
   }
 
-  String get linkedParams =>
-      ParameterRendererHtml().renderLinkedParams(parameters);
+  ModelElementRenderer get _modelElementRenderer =>
+      packageGraph.rendererFactory.modelElementRenderer;
+
+  ParameterRenderer get _parameterRenderer =>
+      packageGraph.rendererFactory.parameterRenderer;
+
+  ParameterRenderer get _parameterRendererDetailed =>
+      packageGraph.rendererFactory.parameterRendererDetailed;
+
+  String get linkedParams => _parameterRenderer.renderLinkedParams(parameters);
 
   String get linkedParamsLines =>
-      ParameterRendererHtmlList().renderLinkedParams(parameters).trim();
+      _parameterRendererDetailed.renderLinkedParams(parameters).trim();
 
   String get linkedParamsNoMetadata =>
-      ParameterRendererHtml(showMetadata: false).renderLinkedParams(parameters);
+      _parameterRenderer.renderLinkedParams(parameters, showMetadata: false);
 
-  String get linkedParamsNoMetadataOrNames =>
-      ParameterRendererHtml(showMetadata: false, showNames: false)
-          .renderLinkedParams(parameters);
+  String get linkedParamsNoMetadataOrNames => _parameterRenderer
+      .renderLinkedParams(parameters, showMetadata: false, showNames: false);
 
   ElementType get modelType {
     if (_modelType == null) {
@@ -1120,7 +1127,7 @@
       return htmlEscape.convert(name);
     }
 
-    return ModelElementRendererHtml().renderLinkedName(this);
+    return _modelElementRenderer.renderLinkedName(this);
   }
 
   /// Replace &#123;@example ...&#125; in API comments with the content of named file.
@@ -1351,8 +1358,7 @@
       final String youTubeId = url.group(url.groupCount);
       final String aspectRatio = (height / width * 100).toStringAsFixed(2);
 
-      return ModelElementRendererHtml()
-          .renderYoutubeUrl(youTubeId, aspectRatio);
+      return _modelElementRenderer.renderYoutubeUrl(youTubeId, aspectRatio);
     });
   }
 
@@ -1483,8 +1489,8 @@
                 'parameter)');
       }
 
-      return ModelElementRendererHtml()
-          .renderAnimation(uniqueId, width, height, movieUrl, overlayId);
+      return _modelElementRenderer.renderAnimation(
+          uniqueId, width, height, movieUrl, overlayId);
     });
   }
 
diff --git a/lib/src/model/package_builder.dart b/lib/src/model/package_builder.dart
index 8d82ca1..ea728b6 100644
--- a/lib/src/model/package_builder.dart
+++ b/lib/src/model/package_builder.dart
@@ -27,6 +27,7 @@
 import 'package:dartdoc/src/logging.dart';
 import 'package:dartdoc/src/model/model.dart';
 import 'package:dartdoc/src/package_meta.dart' show PackageMeta;
+import 'package:dartdoc/src/render/renderer_factory.dart';
 import 'package:dartdoc/src/special_elements.dart';
 import 'package:package_config/discovery.dart' as package_config;
 import 'package:path/path.dart' as path;
@@ -42,13 +43,16 @@
     if (config.topLevelPackageMeta.needsPubGet) {
       config.topLevelPackageMeta.runPubGet();
     }
+    // TODO(jdkoren): change factory for other formats based on config options
+    RendererFactory rendererFactory = HtmlRenderFactory();
 
     PackageGraph newGraph = PackageGraph.UninitializedPackageGraph(
         config,
         driver,
         await driver.currentSession.typeSystem,
         sdk,
-        hasEmbedderSdkFiles);
+        hasEmbedderSdkFiles,
+        rendererFactory);
     await getLibraries(newGraph);
     await newGraph.initializePackageGraph();
     return newGraph;
diff --git a/lib/src/model/package_graph.dart b/lib/src/model/package_graph.dart
index 864ea31..30d6808 100644
--- a/lib/src/model/package_graph.dart
+++ b/lib/src/model/package_graph.dart
@@ -19,13 +19,14 @@
 import 'package:dartdoc/src/model/model.dart';
 import 'package:dartdoc/src/model_utils.dart' as utils;
 import 'package:dartdoc/src/package_meta.dart' show PackageMeta;
+import 'package:dartdoc/src/render/renderer_factory.dart';
 import 'package:dartdoc/src/special_elements.dart';
 import 'package:dartdoc/src/tuple.dart';
 import 'package:dartdoc/src/warnings.dart';
 
 class PackageGraph {
-  PackageGraph.UninitializedPackageGraph(
-      this.config, this.driver, this.typeSystem, this.sdk, this.hasEmbedderSdk)
+  PackageGraph.UninitializedPackageGraph(this.config, this.driver,
+      this.typeSystem, this.sdk, this.hasEmbedderSdk, this.rendererFactory)
       : packageMeta = config.topLevelPackageMeta,
         session = driver.currentSession {
     _packageWarningCounter = PackageWarningCounter(this);
@@ -207,6 +208,9 @@
   /// Dartdoc's configuration flags.
   final DartdocOptionContext config;
 
+  /// Factory for renderers
+  final RendererFactory rendererFactory;
+
   Package _defaultPackage;
 
   Package get defaultPackage {
diff --git a/lib/src/model/type_parameter.dart b/lib/src/model/type_parameter.dart
index 158095d..550c20b 100644
--- a/lib/src/model/type_parameter.dart
+++ b/lib/src/model/type_parameter.dart
@@ -76,16 +76,17 @@
 
   bool get hasGenericParameters => typeParameters.isNotEmpty;
 
-  String get genericParameters {
-    return TypeParametersRendererHtml().renderGenericParameters(this);
-  }
+  String get genericParameters =>
+      _typeParametersRenderer.renderGenericParameters(this);
 
-  String get linkedGenericParameters {
-    return TypeParametersRendererHtml().renderLinkedGenericParameters(this);
-  }
+  String get linkedGenericParameters =>
+      _typeParametersRenderer.renderLinkedGenericParameters(this);
 
   @override
   DefinedElementType get modelType;
 
   List<TypeParameter> get typeParameters;
+
+  TypeParametersRenderer get _typeParametersRenderer =>
+      packageGraph.rendererFactory.typeParametersRenderer;
 }
diff --git a/lib/src/model/typedef.dart b/lib/src/model/typedef.dart
index 3a13447..b7e7424 100644
--- a/lib/src/model/typedef.dart
+++ b/lib/src/model/typedef.dart
@@ -21,8 +21,7 @@
   String get nameWithGenerics => '$name${super.genericParameters}';
 
   @override
-  String get genericParameters =>
-      TypedefRendererHtml().renderGenericParameters(this);
+  String get genericParameters => _renderer.renderGenericParameters(this);
 
   List<TypeParameterElement> get genericTypeParameters {
     if (element is GenericTypeAliasElement) {
@@ -59,4 +58,6 @@
   List<TypeParameter> get typeParameters => _typedef.typeParameters.map((f) {
         return ModelElement.from(f, library, packageGraph) as TypeParameter;
       }).toList();
+
+  TypedefRenderer get _renderer => packageGraph.rendererFactory.typedefRenderer;
 }
diff --git a/lib/src/render/element_type_renderer.dart b/lib/src/render/element_type_renderer.dart
index eb62710..cc4be7a 100644
--- a/lib/src/render/element_type_renderer.dart
+++ b/lib/src/render/element_type_renderer.dart
@@ -85,8 +85,8 @@
     StringBuffer buf = StringBuffer();
     buf.write(elementType.nameWithGenerics);
     buf.write('(');
-    buf.write(ParameterRendererHtml(showNames: false)
-        .renderLinkedParams(elementType.element.parameters)
+    buf.write(ParameterRendererHtml()
+        .renderLinkedParams(elementType.element.parameters, showNames: false)
         .trim());
     buf.write(') → ');
     buf.write(elementType.returnType.linkedName);
diff --git a/lib/src/render/parameter_renderer.dart b/lib/src/render/parameter_renderer.dart
index 4cf6bcd..3e7a895 100644
--- a/lib/src/render/parameter_renderer.dart
+++ b/lib/src/render/parameter_renderer.dart
@@ -8,8 +8,6 @@
 
 /// Render HTML in an extended vertical format using <ol> tag.
 class ParameterRendererHtmlList extends ParameterRendererHtml {
-  ParameterRendererHtmlList({bool showMetadata = true, bool showNames = true})
-      : super(showMetadata: showMetadata, showNames: showNames);
   @override
   String listItem(String listItem) => '<li>$listItem</li>\n';
   @override
@@ -21,12 +19,6 @@
 /// Render HTML suitable for a single, wrapped line.
 class ParameterRendererHtml extends ParameterRenderer {
   @override
-  final bool showMetadata;
-  @override
-  final bool showNames;
-  ParameterRendererHtml({this.showMetadata = true, this.showNames = true});
-
-  @override
   String listItem(String listItem) => '${listItem}<wbr>';
   @override
   String orderedList(String listItems) => listItems;
@@ -51,9 +43,6 @@
 }
 
 abstract class ParameterRenderer {
-  bool get showMetadata;
-  bool get showNames;
-
   String listItem(String item);
   String orderedList(String listItems);
   String annotation(String annotation);
@@ -64,28 +53,8 @@
   String typeName(String typeName);
   String required(String required);
 
-  String _linkedParameterSublist(List<Parameter> parameters, bool trailingComma,
-      {String thisOpenBracket = '', String thisCloseBracket = ''}) {
-    StringBuffer builder = StringBuffer();
-    parameters.forEach((p) {
-      String prefix = '';
-      String suffix = '';
-      if (identical(p, parameters.first)) {
-        prefix = thisOpenBracket;
-      }
-      if (identical(p, parameters.last)) {
-        suffix += thisCloseBracket;
-        if (trailingComma) suffix += ', ';
-      } else {
-        suffix += ', ';
-      }
-      builder.write(
-          listItem(parameter(prefix + renderParam(p) + suffix, p.htmlId)));
-    });
-    return builder.toString();
-  }
-
-  String renderLinkedParams(List<Parameter> parameters) {
+  String renderLinkedParams(List<Parameter> parameters,
+      {showMetadata = true, showNames = true}) {
     List<Parameter> positionalParams =
         parameters.where((Parameter p) => p.isRequiredPositional).toList();
     List<Parameter> optionalPositionalParams =
@@ -96,21 +65,55 @@
     String positional = '', optional = '', named = '';
     if (positionalParams.isNotEmpty) {
       positional = _linkedParameterSublist(positionalParams,
-          optionalPositionalParams.isNotEmpty || namedParams.isNotEmpty);
+          optionalPositionalParams.isNotEmpty || namedParams.isNotEmpty,
+          showMetadata: showMetadata, showNames: showNames);
     }
     if (optionalPositionalParams.isNotEmpty) {
       optional = _linkedParameterSublist(
           optionalPositionalParams, namedParams.isNotEmpty,
-          thisOpenBracket: '[', thisCloseBracket: ']');
+          openBracket: '[',
+          closeBracket: ']',
+          showMetadata: showMetadata,
+          showNames: showNames);
     }
     if (namedParams.isNotEmpty) {
       named = _linkedParameterSublist(namedParams, false,
-          thisOpenBracket: '{', thisCloseBracket: '}');
+          openBracket: '{',
+          closeBracket: '}',
+          showMetadata: showMetadata,
+          showNames: showNames);
     }
     return (orderedList(positional + optional + named));
   }
 
-  String renderParam(Parameter param) {
+  String _linkedParameterSublist(List<Parameter> parameters, bool trailingComma,
+      {String openBracket = '',
+      String closeBracket = '',
+      showMetadata = true,
+      showNames = true}) {
+    StringBuffer builder = StringBuffer();
+    parameters.forEach((p) {
+      String prefix = '';
+      String suffix = '';
+      if (identical(p, parameters.first)) {
+        prefix = openBracket;
+      }
+      if (identical(p, parameters.last)) {
+        suffix += closeBracket;
+        if (trailingComma) suffix += ', ';
+      } else {
+        suffix += ', ';
+      }
+      String renderedParam =
+          _renderParam(p, showMetadata: showMetadata, showNames: showNames);
+      builder.write(
+          listItem(parameter(prefix + renderedParam + suffix, p.htmlId)));
+    });
+    return builder.toString();
+  }
+
+  String _renderParam(Parameter param,
+      {showMetadata = true, showNames = true}) {
     StringBuffer buf = StringBuffer();
     ElementType paramModelType = param.modelType;
 
@@ -140,13 +143,16 @@
       }
       if (!paramModelType.isTypedef && paramModelType is DefinedElementType) {
         buf.write('(');
-        buf.write(renderLinkedParams(paramModelType.element.parameters));
+        buf.write(renderLinkedParams(paramModelType.element.parameters,
+            showMetadata: showMetadata, showNames: showNames));
         buf.write(')');
       }
       if (!paramModelType.isTypedef && paramModelType.type is FunctionType) {
         buf.write('(');
         buf.write(renderLinkedParams(
-            (paramModelType as UndefinedElementType).parameters));
+            (paramModelType as UndefinedElementType).parameters,
+            showMetadata: showMetadata,
+            showNames: showNames));
         buf.write(')');
       }
     } else if (param.modelType != null) {
diff --git a/lib/src/render/renderer_factory.dart b/lib/src/render/renderer_factory.dart
new file mode 100644
index 0000000..7144c3f
--- /dev/null
+++ b/lib/src/render/renderer_factory.dart
@@ -0,0 +1,82 @@
+// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:dartdoc/dartdoc.dart';
+import 'package:dartdoc/src/render/category_renderer.dart';
+import 'package:dartdoc/src/render/documentation_renderer.dart';
+import 'package:dartdoc/src/render/element_type_renderer.dart';
+import 'package:dartdoc/src/render/enum_field_renderer.dart';
+import 'package:dartdoc/src/render/model_element_renderer.dart';
+import 'package:dartdoc/src/render/parameter_renderer.dart';
+import 'package:dartdoc/src/render/type_parameters_renderer.dart';
+import 'package:dartdoc/src/render/typedef_renderer.dart';
+
+abstract class RendererFactory {
+  CategoryRenderer get categoryRenderer;
+
+  DocumentationRenderer get documentationRenderer;
+
+  ElementTypeRenderer<FunctionTypeElementType>
+      get functionTypeElementTypeRenderer;
+
+  ElementTypeRenderer<ParameterizedElementType>
+      get parameterizedElementTypeRenderer;
+
+  ElementTypeRenderer<CallableElementType> get callableElementTypeRenderer;
+
+  EnumFieldRenderer get enumFieldRenderer;
+
+  ModelElementRenderer get modelElementRenderer;
+
+  ParameterRenderer get parameterRenderer;
+
+  ParameterRenderer get parameterRendererDetailed;
+
+  TypeParametersRenderer get typeParametersRenderer;
+
+  TypedefRenderer get typedefRenderer;
+}
+
+class HtmlRenderFactory extends RendererFactory {
+  @override
+  CategoryRenderer get categoryRenderer => CategoryRendererHtml();
+
+  @override
+  DocumentationRenderer get documentationRenderer =>
+      DocumentationRendererHtml();
+
+  @override
+  ElementTypeRenderer<CallableElementType> get callableElementTypeRenderer =>
+      CallableElementTypeRendererHtml();
+
+  @override
+  ElementTypeRenderer<FunctionTypeElementType>
+      get functionTypeElementTypeRenderer =>
+          FunctionTypeElementTypeRendererHtml();
+
+  @override
+  ElementTypeRenderer<ParameterizedElementType>
+      get parameterizedElementTypeRenderer =>
+          ParameterizedElementTypeRendererHtml();
+
+  @override
+  EnumFieldRenderer get enumFieldRenderer => EnumFieldRendererHtml();
+
+  @override
+  ModelElementRenderer get modelElementRenderer => ModelElementRendererHtml();
+
+  @override
+  ParameterRenderer get parameterRenderer => ParameterRendererHtml();
+
+  @override
+  ParameterRenderer get parameterRendererDetailed =>
+      ParameterRendererHtmlList();
+
+  @override
+  TypeParametersRenderer get typeParametersRenderer =>
+      TypeParametersRendererHtml();
+
+  @override
+  TypedefRenderer get typedefRenderer => TypedefRendererHtml();
+}
diff --git a/test/model_test.dart b/test/model_test.dart
index 5f52a10..edae9ae 100644
--- a/test/model_test.dart
+++ b/test/model_test.dart
@@ -3270,8 +3270,8 @@
 
     test('a function requiring a Future<void> parameter', () {
       expect(
-          ParameterRendererHtml(showMetadata: true, showNames: true)
-              .renderLinkedParams(aVoidParameter.parameters),
+          ParameterRendererHtml().renderLinkedParams(aVoidParameter.parameters,
+              showMetadata: true, showNames: true),
           equals(
               '<span class="parameter" id="aVoidParameter-param-p1"><span class="type-annotation">Future<span class="signature">&lt;<wbr><span class="type-parameter">void</span>&gt;</span></span> <span class="parameter-name">p1</span></span><wbr>'));
     });