Determine file extensions on a per-package basis (#2134)

* Extract file extensions to a new renderer

* Determine file type based on documentation location

* Use config instead of a new renderer
diff --git a/lib/src/dartdoc_options.dart b/lib/src/dartdoc_options.dart
index 7385131..76d8757 100644
--- a/lib/src/dartdoc_options.dart
+++ b/lib/src/dartdoc_options.dart
@@ -1421,6 +1421,9 @@
   bool isPackageExcluded(String name) =>
       excludePackages.any((pattern) => name == pattern);
 
+  /// Output format, e.g. 'html', 'md'
+  String get format => optionSet['format'].valueAt(context);
+
   // TODO(jdkoren): temporary while we confirm href base behavior doesn't break important clients
   bool get useBaseHref => optionSet['useBaseHref'].valueAt(context);
 }
@@ -1633,6 +1636,8 @@
             'pages, and please file an issue on Github.',
         negatable: false,
         hide: true),
+    // TODO(jdkoren): Unhide when we have good support for another format.
+    DartdocOptionArgOnly<String>('format', 'html', hide: true),
     // TODO(jcollins-g): refactor so there is a single static "create" for
     // each DartdocOptionContext that traverses the inheritance tree itself.
   ]
diff --git a/lib/src/generator.dart b/lib/src/generator.dart
index 1904d3b..eda5ce6 100644
--- a/lib/src/generator.dart
+++ b/lib/src/generator.dart
@@ -56,9 +56,6 @@
 
   String get templatesDir => optionSet['templatesDir'].valueAt(context);
 
-  /// Output format, e.g. 'html', 'md'
-  String get format => optionSet['format'].valueAt(context);
-
   // TODO(jdkoren): duplicated temporarily so that GeneratorContext is enough for configuration.
   bool get useBaseHref => optionSet['useBaseHref'].valueAt(context);
 }
@@ -141,7 +138,5 @@
             'they must begin with an underscore, and references to them must '
             'omit the leading underscore (e.g. use {{>foo}} to reference the '
             'partial template named _foo).'),
-    // TODO(jdkoren): Unhide when we have good support for another format.
-    DartdocOptionArgOnly<String>('format', 'html', hide: true),
   ];
 }
diff --git a/lib/src/model/category.dart b/lib/src/model/category.dart
index 69bae80..5c4992e 100644
--- a/lib/src/model/category.dart
+++ b/lib/src/model/category.dart
@@ -120,14 +120,16 @@
   @override
   String get fullyQualifiedName => name;
 
-  String get filePath => 'topics/${name}-topic.html';
+  String get fileType => package.fileType;
+
+  String get filePath => 'topics/$name-topic.$fileType';
 
   @override
   String get href => isCanonical ? '${package.baseHref}$filePath' : null;
 
-  String get categorization => _renderer.renderCategoryLabel(this);
+  String get categorization => _categoryRenderer.renderCategoryLabel(this);
 
-  String get linkedName => _renderer.renderLinkedName(this);
+  String get linkedName => _categoryRenderer.renderLinkedName(this);
 
   int _categoryIndex;
 
@@ -199,6 +201,6 @@
   @override
   Iterable<Typedef> get typedefs => _typedefs;
 
-  CategoryRenderer get _renderer =>
+  CategoryRenderer get _categoryRenderer =>
       packageGraph.rendererFactory.categoryRenderer;
 }
diff --git a/lib/src/model/class.dart b/lib/src/model/class.dart
index cf9d1d5..dbe1cf8 100644
--- a/lib/src/model/class.dart
+++ b/lib/src/model/class.dart
@@ -184,7 +184,7 @@
   ModelElement get enclosingElement => library;
 
   @override
-  String get fileName => "${name}-class.html";
+  String get fileName => '$name-class.$fileType';
 
   @override
   String get filePath => '${library.dirName}/$fileName';
diff --git a/lib/src/model/field.dart b/lib/src/model/field.dart
index 838f8a3..c6d6dd6 100644
--- a/lib/src/model/field.dart
+++ b/lib/src/model/field.dart
@@ -156,7 +156,7 @@
   FieldElement get field => (element as FieldElement);
 
   @override
-  String get fileName => isConst ? '$name-constant.html' : '$name.html';
+  String get fileName => '${isConst ? '$name-constant' : name}.$fileType';
 
   String _sourceCode;
 
diff --git a/lib/src/model/library.dart b/lib/src/model/library.dart
index 50c540b..44f23bc 100644
--- a/lib/src/model/library.dart
+++ b/lib/src/model/library.dart
@@ -375,7 +375,7 @@
   }
 
   @override
-  String get fileName => '$dirName-library.html';
+  String get fileName => '$dirName-library.$fileType';
 
   @override
   String get filePath => '${library.dirName}/$fileName';
diff --git a/lib/src/model/mixin.dart b/lib/src/model/mixin.dart
index c9fe4f7..4c18868 100644
--- a/lib/src/model/mixin.dart
+++ b/lib/src/model/mixin.dart
@@ -64,7 +64,7 @@
   bool get hasModifiers => super.hasModifiers || hasPublicSuperclassConstraints;
 
   @override
-  String get fileName => "${name}-mixin.html";
+  String get fileName => '$name-mixin.$fileType';
 
   @override
   String get kind => 'mixin';
diff --git a/lib/src/model/model_element.dart b/lib/src/model/model_element.dart
index b352c43..1f009bb 100644
--- a/lib/src/model/model_element.dart
+++ b/lib/src/model/model_element.dart
@@ -787,7 +787,9 @@
     return '';
   }
 
-  String get fileName => "${name}.html";
+  String get fileName => '$name.$fileType';
+
+  String get fileType => package.fileType;
 
   String get filePath;
 
diff --git a/lib/src/model/operator.dart b/lib/src/model/operator.dart
index ee10f01..f7b4145 100644
--- a/lib/src/model/operator.dart
+++ b/lib/src/model/operator.dart
@@ -42,10 +42,9 @@
   String get fileName {
     var actualName = super.name;
     if (friendlyNames.containsKey(actualName)) {
-      return "operator_${friendlyNames[actualName]}.html";
-    } else {
-      return '$actualName.html';
+      actualName = "operator_${friendlyNames[actualName]}";
     }
+    return '$actualName.$fileType';
   }
 
   @override
diff --git a/lib/src/model/package.dart b/lib/src/model/package.dart
index b712e92..762d37e 100644
--- a/lib/src/model/package.dart
+++ b/lib/src/model/package.dart
@@ -181,7 +181,19 @@
   @override
   String get enclosingName => packageGraph.defaultPackageName;
 
-  String get filePath => 'index.html';
+  String get filePath => 'index.$fileType';
+
+  String get fileType {
+    // TODO(jdkoren): Provide a way to determine file type of a remote package's
+    // docs. Perhaps make this configurable through dartdoc options.
+    // In theory, a remote package could be documented in any supported format.
+    // In practice, devs depend on Dart, Flutter, and/or packages fetched
+    // from pub.dev, and we know that all of those use html docs.
+    if (package.documentedWhere == DocumentLocation.remote) {
+      return 'html';
+    }
+    return config.format;
+  }
 
   @override
   String get fullyQualifiedName => 'package:$name';
diff --git a/lib/src/model/top_level_variable.dart b/lib/src/model/top_level_variable.dart
index 7375640..edca645 100644
--- a/lib/src/model/top_level_variable.dart
+++ b/lib/src/model/top_level_variable.dart
@@ -85,7 +85,7 @@
   }
 
   @override
-  String get fileName => isConst ? '$name-constant.html' : '$name.html';
+  String get fileName => '${isConst ? '$name-constant' : name}.$fileType';
 
   @override
   DefinedElementType get modelType => super.modelType;
diff --git a/lib/src/render/file_type_renderer.dart b/lib/src/render/file_type_renderer.dart
new file mode 100644
index 0000000..7f6bbc5
--- /dev/null
+++ b/lib/src/render/file_type_renderer.dart
@@ -0,0 +1,12 @@
+// Copyright (c) 2020, 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.
+
+abstract class FileTypeRenderer {
+  String get fileType;
+}
+
+class HtmlFileTypeRenderer extends FileTypeRenderer {
+  @override
+  String get fileType => 'html';
+}