Decouple href and from a model's filename (#2096)

* Consolidate filename correction in html_generator_instance

* Decouple file path from a model's href

* Don't check canonical elements for filepaths

* Use filePath when constructing href for consistency
diff --git a/lib/src/html/html_generator_instance.dart b/lib/src/html/html_generator_instance.dart
index b892da4..b3adb6c 100644
--- a/lib/src/html/html_generator_instance.dart
+++ b/lib/src/html/html_generator_instance.dart
@@ -279,7 +279,7 @@
         PackageTemplateData(_options, packageGraph, _templateHelper, package);
     logInfo('documenting ${package.name}');
 
-    _build('index.html', _templates.indexTemplate, data);
+    _build(package.filePath, _templates.indexTemplate, data);
     _build('__404error.html', _templates.errorTemplate, data);
   }
 
@@ -289,8 +289,7 @@
     TemplateData data =
         CategoryTemplateData(_options, packageGraph, _templateHelper, category);
 
-    _build(path.joinAll(category.href.split('/')), _templates.categoryTemplate,
-        data);
+    _build(category.filePath, _templates.categoryTemplate, data);
   }
 
   void generateLibrary(PackageGraph packageGraph, Library lib) {
@@ -302,28 +301,26 @@
     TemplateData data =
         LibraryTemplateData(_options, packageGraph, _templateHelper, lib);
 
-    _build(path.join(lib.dirName, '${lib.fileName}'),
-        _templates.libraryTemplate, data);
+    _build(lib.filePath, _templates.libraryTemplate, data);
   }
 
   void generateClass(PackageGraph packageGraph, Library lib, Class clazz) {
     TemplateData data =
         ClassTemplateData(_options, packageGraph, _templateHelper, lib, clazz);
-    _build(path.joinAll(clazz.href.split('/')), _templates.classTemplate, data);
+    _build(clazz.filePath, _templates.classTemplate, data);
   }
 
   void generateExtension(
-      PackageGraph packageGraph, Library lib, Extension ext) {
+      PackageGraph packageGraph, Library lib, Extension extension) {
     TemplateData data = ExtensionTemplateData(
-        _options, packageGraph, _templateHelper, lib, ext);
-    _build(
-        path.joinAll(ext.href.split('/')), _templates.extensionTemplate, data);
+        _options, packageGraph, _templateHelper, lib, extension);
+    _build(extension.filePath, _templates.extensionTemplate, data);
   }
 
   void generateMixins(PackageGraph packageGraph, Library lib, Mixin mixin) {
     TemplateData data =
         MixinTemplateData(_options, packageGraph, _templateHelper, lib, mixin);
-    _build(path.joinAll(mixin.href.split('/')), _templates.mixinTemplate, data);
+    _build(mixin.filePath, _templates.mixinTemplate, data);
   }
 
   void generateConstructor(PackageGraph packageGraph, Library lib, Class clazz,
@@ -331,15 +328,14 @@
     TemplateData data = ConstructorTemplateData(
         _options, packageGraph, _templateHelper, lib, clazz, constructor);
 
-    _build(path.joinAll(constructor.href.split('/')),
-        _templates.constructorTemplate, data);
+    _build(constructor.filePath, _templates.constructorTemplate, data);
   }
 
   void generateEnum(PackageGraph packageGraph, Library lib, Enum eNum) {
     TemplateData data =
         EnumTemplateData(_options, packageGraph, _templateHelper, lib, eNum);
 
-    _build(path.joinAll(eNum.href.split('/')), _templates.enumTemplate, data);
+    _build(eNum.filePath, _templates.enumTemplate, data);
   }
 
   void generateFunction(
@@ -347,8 +343,7 @@
     TemplateData data = FunctionTemplateData(
         _options, packageGraph, _templateHelper, lib, function);
 
-    _build(path.joinAll(function.href.split('/')), _templates.functionTemplate,
-        data);
+    _build(function.filePath, _templates.functionTemplate, data);
   }
 
   void generateMethod(
@@ -356,8 +351,7 @@
     TemplateData data = MethodTemplateData(
         _options, packageGraph, _templateHelper, lib, clazz, method);
 
-    _build(
-        path.joinAll(method.href.split('/')), _templates.methodTemplate, data);
+    _build(method.filePath, _templates.methodTemplate, data);
   }
 
   void generateConstant(
@@ -365,8 +359,7 @@
     TemplateData data = ConstantTemplateData(
         _options, packageGraph, _templateHelper, lib, clazz, property);
 
-    _build(path.joinAll(property.href.split('/')), _templates.constantTemplate,
-        data);
+    _build(property.filePath, _templates.constantTemplate, data);
   }
 
   void generateProperty(
@@ -374,8 +367,7 @@
     TemplateData data = PropertyTemplateData(
         _options, packageGraph, _templateHelper, lib, clazz, property);
 
-    _build(path.joinAll(property.href.split('/')), _templates.propertyTemplate,
-        data);
+    _build(property.filePath, _templates.propertyTemplate, data);
   }
 
   void generateTopLevelProperty(
@@ -383,8 +375,7 @@
     TemplateData data = TopLevelPropertyTemplateData(
         _options, packageGraph, _templateHelper, lib, property);
 
-    _build(path.joinAll(property.href.split('/')),
-        _templates.topLevelPropertyTemplate, data);
+    _build(property.filePath, _templates.topLevelPropertyTemplate, data);
   }
 
   void generateTopLevelConstant(
@@ -392,8 +383,7 @@
     TemplateData data = TopLevelConstTemplateData(
         _options, packageGraph, _templateHelper, lib, property);
 
-    _build(path.joinAll(property.href.split('/')),
-        _templates.topLevelConstantTemplate, data);
+    _build(property.filePath, _templates.topLevelConstantTemplate, data);
   }
 
   void generateTypeDef(
@@ -401,8 +391,7 @@
     TemplateData data = TypedefTemplateData(
         _options, packageGraph, _templateHelper, lib, typeDef);
 
-    _build(path.joinAll(typeDef.href.split('/')), _templates.typeDefTemplate,
-        data);
+    _build(typeDef.filePath, _templates.typeDefTemplate, data);
   }
 
   // TODO: change this to use resource_loader
@@ -420,9 +409,11 @@
   }
 
   void _build(String filename, Template template, TemplateData data) {
+    // Replaces '/' separators with proper separators for the platform.
+    String outFile = path.joinAll(filename.split('/'));
     String content = template.renderString(data);
 
-    _writer(filename, content);
+    _writer(outFile, content);
     if (data.self is Indexable) _indexedElements.add(data.self as Indexable);
   }
 }
diff --git a/lib/src/model/accessor.dart b/lib/src/model/accessor.dart
index 8b08f4b..cf539b7 100644
--- a/lib/src/model/accessor.dart
+++ b/lib/src/model/accessor.dart
@@ -90,6 +90,9 @@
   }
 
   @override
+  String get filePath => enclosingCombo.filePath;
+
+  @override
   bool get isCanonical => enclosingCombo.isCanonical;
 
   @override
diff --git a/lib/src/model/category.dart b/lib/src/model/category.dart
index 6c61162..69bae80 100644
--- a/lib/src/model/category.dart
+++ b/lib/src/model/category.dart
@@ -120,9 +120,10 @@
   @override
   String get fullyQualifiedName => name;
 
+  String get filePath => 'topics/${name}-topic.html';
+
   @override
-  String get href =>
-      isCanonical ? '${package.baseHref}topics/${name}-topic.html' : null;
+  String get href => isCanonical ? '${package.baseHref}$filePath' : null;
 
   String get categorization => _renderer.renderCategoryLabel(this);
 
diff --git a/lib/src/model/class.dart b/lib/src/model/class.dart
index 69ba5b9..a647d29 100644
--- a/lib/src/model/class.dart
+++ b/lib/src/model/class.dart
@@ -201,6 +201,9 @@
   @override
   String get fileName => "${name}-class.html";
 
+  @override
+  String get filePath => '${library.dirName}/$fileName';
+
   String get fullkind {
     if (isAbstract) return 'abstract $kind';
     return kind;
@@ -249,7 +252,7 @@
     }
     assert(canonicalLibrary != null);
     assert(canonicalLibrary == library);
-    return '${package.baseHref}${library.dirName}/$fileName';
+    return '${package.baseHref}$filePath';
   }
 
   /// Returns all the implementors of this class.
diff --git a/lib/src/model/constructor.dart b/lib/src/model/constructor.dart
index 781417b..ba06314 100644
--- a/lib/src/model/constructor.dart
+++ b/lib/src/model/constructor.dart
@@ -34,6 +34,10 @@
   ModelElement get enclosingElement =>
       ModelElement.from(_constructor.enclosingElement, library, packageGraph);
 
+  @override
+  String get filePath =>
+      '${enclosingElement.library.dirName}/${enclosingElement.name}/$fileName';
+
   String get fullKind {
     if (isConst) return 'const $kind';
     if (isFactory) return 'factory $kind';
@@ -53,7 +57,7 @@
     }
     assert(canonicalLibrary != null);
     assert(canonicalLibrary == library);
-    return '${package.baseHref}${enclosingElement.library.dirName}/${enclosingElement.name}/$name.html';
+    return '${package.baseHref}$filePath';
   }
 
   @override
diff --git a/lib/src/model/dynamic.dart b/lib/src/model/dynamic.dart
index f8a9636..ea7dc30 100644
--- a/lib/src/model/dynamic.dart
+++ b/lib/src/model/dynamic.dart
@@ -27,4 +27,7 @@
 
   @override
   String get linkedName => 'dynamic';
+
+  @override
+  String get filePath => null;
 }
diff --git a/lib/src/model/extension.dart b/lib/src/model/extension.dart
index 018bcae..dd9bd06 100644
--- a/lib/src/model/extension.dart
+++ b/lib/src/model/extension.dart
@@ -144,12 +144,15 @@
   }
 
   @override
+  String get filePath => '${library.dirName}/$fileName';
+
+  @override
   String get href {
     if (!identical(canonicalModelElement, this)) {
       return canonicalModelElement?.href;
     }
     assert(canonicalLibrary != null);
     assert(canonicalLibrary == library);
-    return '${package.baseHref}${library.dirName}/$fileName';
+    return '${package.baseHref}$filePath';
   }
 }
diff --git a/lib/src/model/field.dart b/lib/src/model/field.dart
index a1b7440..838f8a3 100644
--- a/lib/src/model/field.dart
+++ b/lib/src/model/field.dart
@@ -65,6 +65,10 @@
   }
 
   @override
+  String get filePath =>
+      '${enclosingElement.library.dirName}/${enclosingElement.name}/$fileName';
+
+  @override
   String get href {
     if (!identical(canonicalModelElement, this)) {
       return canonicalModelElement?.href;
@@ -72,7 +76,7 @@
     assert(canonicalLibrary != null);
     assert(canonicalEnclosingContainer == enclosingElement);
     assert(canonicalLibrary == library);
-    return '${package.baseHref}${enclosingElement.library.dirName}/${enclosingElement.name}/$fileName';
+    return '${package.baseHref}$filePath';
   }
 
   @override
diff --git a/lib/src/model/library.dart b/lib/src/model/library.dart
index 7b511ca..50c540b 100644
--- a/lib/src/model/library.dart
+++ b/lib/src/model/library.dart
@@ -377,6 +377,9 @@
   @override
   String get fileName => '$dirName-library.html';
 
+  @override
+  String get filePath => '${library.dirName}/$fileName';
+
   List<ModelFunction> _functions;
 
   @override
@@ -396,7 +399,7 @@
     if (!identical(canonicalModelElement, this)) {
       return canonicalModelElement?.href;
     }
-    return '${package.baseHref}${library.dirName}/$fileName';
+    return '${package.baseHref}$filePath';
   }
 
   InheritanceManager3 _inheritanceManager;
diff --git a/lib/src/model/method.dart b/lib/src/model/method.dart
index 1156ae2..6466938 100644
--- a/lib/src/model/method.dart
+++ b/lib/src/model/method.dart
@@ -58,6 +58,10 @@
     return _enclosingContainer;
   }
 
+  @override
+  String get filePath =>
+      '${enclosingElement.library.dirName}/${enclosingElement.name}/$fileName';
+
   String get fullkind {
     if (_method.isAbstract) return 'abstract $kind';
     return kind;
@@ -71,7 +75,7 @@
     assert(!(canonicalLibrary == null || canonicalEnclosingContainer == null));
     assert(canonicalLibrary == library);
     assert(canonicalEnclosingContainer == enclosingElement);
-    return '${package.baseHref}${enclosingElement.library.dirName}/${enclosingElement.name}/${fileName}';
+    return '${package.baseHref}$filePath';
   }
 
   @override
diff --git a/lib/src/model/model_element.dart b/lib/src/model/model_element.dart
index b1b4a59..234bd75 100644
--- a/lib/src/model/model_element.dart
+++ b/lib/src/model/model_element.dart
@@ -790,6 +790,8 @@
 
   String get fileName => "${name}.html";
 
+  String get filePath;
+
   /// Returns the fully qualified name.
   ///
   /// For example: libraryName.className.methodName
diff --git a/lib/src/model/model_function.dart b/lib/src/model/model_function.dart
index da9f85c..4e11225 100644
--- a/lib/src/model/model_function.dart
+++ b/lib/src/model/model_function.dart
@@ -57,13 +57,16 @@
   ModelElement get enclosingElement => library;
 
   @override
+  String get filePath => '${library.dirName}/$fileName';
+
+  @override
   String get href {
     if (!identical(canonicalModelElement, this)) {
       return canonicalModelElement?.href;
     }
     assert(canonicalLibrary != null);
     assert(canonicalLibrary == library);
-    return '${package.baseHref}${library.dirName}/$fileName';
+    return '${package.baseHref}$filePath';
   }
 
   @override
diff --git a/lib/src/model/package.dart b/lib/src/model/package.dart
index 23b0912..7771d3e 100644
--- a/lib/src/model/package.dart
+++ b/lib/src/model/package.dart
@@ -170,6 +170,8 @@
   @override
   String get enclosingName => packageGraph.defaultPackageName;
 
+  String get filePath => 'index.html';
+
   @override
   String get fullyQualifiedName => 'package:$name';
 
@@ -211,7 +213,7 @@
   }
 
   @override
-  String get href => '${baseHref}index.html';
+  String get href => '$baseHref$filePath';
 
   @override
   String get location => path.toUri(packageMeta.resolvedDir).toString();
diff --git a/lib/src/model/parameter.dart b/lib/src/model/parameter.dart
index 19c88d7..52b6e86 100644
--- a/lib/src/model/parameter.dart
+++ b/lib/src/model/parameter.dart
@@ -28,6 +28,11 @@
   }
 
   @override
+  String get filePath {
+    throw StateError('filePath not implemented for parameters');
+  }
+
+  @override
   String get href {
     throw StateError('href not implemented for parameters');
   }
diff --git a/lib/src/model/top_level_variable.dart b/lib/src/model/top_level_variable.dart
index 3b53961..7375640 100644
--- a/lib/src/model/top_level_variable.dart
+++ b/lib/src/model/top_level_variable.dart
@@ -45,13 +45,16 @@
   ModelElement get enclosingElement => library;
 
   @override
+  String get filePath => '${library.dirName}/$fileName';
+
+  @override
   String get href {
     if (!identical(canonicalModelElement, this)) {
       return canonicalModelElement?.href;
     }
     assert(canonicalLibrary != null);
     assert(canonicalLibrary == library);
-    return '${package.baseHref}${library.dirName}/$fileName';
+    return '${package.baseHref}$filePath';
   }
 
   @override
diff --git a/lib/src/model/type_parameter.dart b/lib/src/model/type_parameter.dart
index 550c20b..71aac03 100644
--- a/lib/src/model/type_parameter.dart
+++ b/lib/src/model/type_parameter.dart
@@ -18,13 +18,17 @@
       : null;
 
   @override
+  String get filePath =>
+      '${enclosingElement.library.dirName}/${enclosingElement.name}/$name';
+
+  @override
   String get href {
     if (!identical(canonicalModelElement, this)) {
       return canonicalModelElement?.href;
     }
     assert(canonicalLibrary != null);
     assert(canonicalLibrary == library);
-    return '${package.baseHref}${enclosingElement.library.dirName}/${enclosingElement.name}/$name';
+    return '${package.baseHref}$filePath';
   }
 
   @override
diff --git a/lib/src/model/typedef.dart b/lib/src/model/typedef.dart
index b7e7424..760c249 100644
--- a/lib/src/model/typedef.dart
+++ b/lib/src/model/typedef.dart
@@ -31,13 +31,16 @@
   }
 
   @override
+  String get filePath => '${library.dirName}/$fileName';
+
+  @override
   String get href {
     if (!identical(canonicalModelElement, this)) {
       return canonicalModelElement?.href;
     }
     assert(canonicalLibrary != null);
     assert(canonicalLibrary == library);
-    return '${package.baseHref}${library.dirName}/$fileName';
+    return '${package.baseHref}$filePath';
   }
 
   // Food for mustache.