diff --git a/lib/src/generator/generator_backend.dart b/lib/src/generator/generator_backend.dart index 43577ce99e..ffc54781ce 100644 --- a/lib/src/generator/generator_backend.dart +++ b/lib/src/generator/generator_backend.dart @@ -86,8 +86,7 @@ abstract class GeneratorBackend { ); } var e = data.self; - writer.write(filename, content, - element: e is Warnable ? e : null); + writer.write(filename, content, element: e is Warnable ? e : null); } /// Emits JSON describing the [categories] defined by the package. @@ -122,6 +121,11 @@ abstract class GeneratorBackend { var data = CategoryTemplateData(options, packageGraph, category); var content = templates.renderCategory(data); write(writer, category.filePath, data, content); + if (category.filePath != category.redirectFilePath) { + var redirectContent = templates.renderCategoryRedirect(data); + write(writer, category.redirectFilePath, data, redirectContent); + } + runtimeStats.incrementAccumulator('writtenCategoryFileCount'); } diff --git a/lib/src/generator/templates.aot_renderers_for_html.dart b/lib/src/generator/templates.aot_renderers_for_html.dart index 4f19ebb48b..9333c1b73c 100644 --- a/lib/src/generator/templates.aot_renderers_for_html.dart +++ b/lib/src/generator/templates.aot_renderers_for_html.dart @@ -234,6 +234,41 @@ String renderCategory(CategoryTemplateData context0) { return buffer.toString(); } +String renderCategoryRedirect(CategoryTemplateData context0) { + final buffer = StringBuffer(); + buffer.write(''' + + + + + + +

New URL

+ + +'''); + + return buffer.toString(); +} + String renderClass(ClassTemplateData context0) { final buffer = StringBuffer(); buffer.write(_renderClass_partial_head_0(context0)); diff --git a/lib/src/generator/templates.dart b/lib/src/generator/templates.dart index 4fcd71becb..eb624456af 100644 --- a/lib/src/generator/templates.dart +++ b/lib/src/generator/templates.dart @@ -12,6 +12,9 @@ @Renderer(#renderCategory, Context(), 'category', visibleTypes: _visibleTypes) +@Renderer(#renderCategoryRedirect, Context(), + 'category_redirect', + visibleTypes: _visibleTypes) @Renderer(#renderClass, Context(), 'class') @Renderer(#renderConstructor, Context(), 'constructor') @Renderer(#renderEnum, Context(), 'enum') @@ -92,6 +95,7 @@ const _visibleTypes = { /// The collection of [Template] objects. abstract class Templates { String renderCategory(CategoryTemplateData context); + String renderCategoryRedirect(CategoryTemplateData context); String renderClass(ClassTemplateData context); String renderConstructor(ConstructorTemplateData context); String renderEnum(EnumTemplateData context); @@ -141,6 +145,10 @@ class HtmlAotTemplates implements Templates { String renderCategory(CategoryTemplateData context) => aot_renderers_for_html.renderCategory(context); + @override + String renderCategoryRedirect(CategoryTemplateData context) => + aot_renderers_for_html.renderCategoryRedirect(context); + @override String renderClass(ClassTemplateData context) => aot_renderers_for_html.renderClass(context); @@ -222,6 +230,10 @@ class RuntimeTemplates implements Templates { String renderCategory(CategoryTemplateData context) => runtime_renderers.renderCategory(context, _categoryTemplate); + @override + String renderCategoryRedirect(CategoryTemplateData context) => + runtime_renderers.renderCategoryRedirect(context, _categoryTemplate); + @override String renderClass(ClassTemplateData context) => runtime_renderers.renderClass(context, _classTemplate); diff --git a/lib/src/generator/templates.runtime_renderers.dart b/lib/src/generator/templates.runtime_renderers.dart index cc9d11902d..bc01161928 100644 --- a/lib/src/generator/templates.runtime_renderers.dart +++ b/lib/src/generator/templates.runtime_renderers.dart @@ -1846,6 +1846,38 @@ class _Renderer_Category extends RendererBase { ); }, ), + 'fileName': Property( + getValue: (CT_ c) => c.fileName, + renderVariable: ( + CT_ c, + Property self, + List remainingNames, + ) { + if (remainingNames.isEmpty) { + return self.getValue(c).toString(); + } + var name = remainingNames.first; + var nextProperty = _Renderer_String.propertyMap().getValue( + name, + ); + return nextProperty.renderVariable( + self.getValue(c) as String, + nextProperty, + [...remainingNames.skip(1)], + ); + }, + + isNullValue: (CT_ c) => false, + + renderValue: ( + CT_ c, + RendererBase r, + List ast, + StringSink sink, + ) { + _render_String(c.fileName, ast, r.template, sink, parent: r); + }, + ), 'filePath': Property( getValue: (CT_ c) => c.filePath, renderVariable: ( @@ -2155,6 +2187,44 @@ class _Renderer_Category extends RendererBase { ); }, ), + 'redirectFilePath': Property( + getValue: (CT_ c) => c.redirectFilePath, + renderVariable: ( + CT_ c, + Property self, + List remainingNames, + ) { + if (remainingNames.isEmpty) { + return self.getValue(c).toString(); + } + var name = remainingNames.first; + var nextProperty = _Renderer_String.propertyMap().getValue( + name, + ); + return nextProperty.renderVariable( + self.getValue(c) as String, + nextProperty, + [...remainingNames.skip(1)], + ); + }, + + isNullValue: (CT_ c) => false, + + renderValue: ( + CT_ c, + RendererBase r, + List ast, + StringSink sink, + ) { + _render_String( + c.redirectFilePath, + ast, + r.template, + sink, + parent: r, + ); + }, + ), 'referenceChildren': Property( getValue: (CT_ c) => c.referenceChildren, renderVariable: @@ -2285,7 +2355,7 @@ class _Renderer_Category extends RendererBase { } } -String renderCategory(CategoryTemplateData context, Template template) { +String renderCategoryRedirect(CategoryTemplateData context, Template template) { var buffer = StringBuffer(); _render_CategoryTemplateData(context, template.ast, template, buffer); return buffer.toString(); @@ -2543,6 +2613,12 @@ class _Renderer_CategoryTemplateData } } +String renderCategory(CategoryTemplateData context, Template template) { + var buffer = StringBuffer(); + _render_CategoryTemplateData(context, template.ast, template, buffer); + return buffer.toString(); +} + void _render_Class( Class context, List ast, @@ -19948,7 +20024,7 @@ class _Renderer_Package extends RendererBase { } } -String renderIndex(PackageTemplateData context, Template template) { +String renderSearchPage(PackageTemplateData context, Template template) { var buffer = StringBuffer(); _render_PackageTemplateData(context, template.ast, template, buffer); return buffer.toString(); @@ -20305,13 +20381,13 @@ class _Renderer_PackageTemplateData extends RendererBase { } } -String renderError(PackageTemplateData context, Template template) { +String renderIndex(PackageTemplateData context, Template template) { var buffer = StringBuffer(); _render_PackageTemplateData(context, template.ast, template, buffer); return buffer.toString(); } -String renderSearchPage(PackageTemplateData context, Template template) { +String renderError(PackageTemplateData context, Template template) { var buffer = StringBuffer(); _render_PackageTemplateData(context, template.ast, template, buffer); return buffer.toString(); diff --git a/lib/src/model/category.dart b/lib/src/model/category.dart index 5008b3ef0e..225ff7532b 100644 --- a/lib/src/model/category.dart +++ b/lib/src/model/category.dart @@ -105,11 +105,17 @@ class Category late final bool isDocumented = documentedWhere != DocumentLocation.missing && documentationFile != null; - String get filePath { + String get fileName { assert(_name != null); - return 'topics/$_name-topic.html'; + return '$_name-topic.html'; } + String get filePath => 'topics/$fileName'; + + /// Prior to dartdoc 8.3.4 the `displayName` was used in the file path + /// for category pages. We now create a redirect file here instead. + String get redirectFilePath => 'topics/$name-topic.html'; + @override String? get href => isCanonical ? '${package.baseHref}$filePath' : null; diff --git a/lib/templates/category_redirect.html b/lib/templates/category_redirect.html new file mode 100644 index 0000000000..548f6676ea --- /dev/null +++ b/lib/templates/category_redirect.html @@ -0,0 +1,10 @@ + + + + + + + +

New URL

+ + diff --git a/test/end2end/model_test.dart b/test/end2end/model_test.dart index 331f2f9d56..0d1170d860 100644 --- a/test/end2end/model_test.dart +++ b/test/end2end/model_test.dart @@ -730,6 +730,11 @@ void main() async { }); }); + test('Verify redirectFilePath set', () { + var category = packageGraph.publicPackages.first.categories.first; + expect(category.redirectFilePath, 'topics/Superb-topic.html'); + }); + group('LibraryContainer', () { late final TestLibraryContainer topLevel; var sortOrderBasic = ['theFirst', 'second', 'fruit']; diff --git a/test/templates/category_test.dart b/test/templates/category_test.dart index 9b7255fd2b..29db4bb567 100644 --- a/test/templates/category_test.dart +++ b/test/templates/category_test.dart @@ -74,52 +74,53 @@ analyzer: dartdocOptions: ''' dartdoc: categories: - One: + cat1: markdown: one.md + displayName: One Documented: markdown: documented.md ''', libFiles: [ d.file('lib.dart', ''' /// A class. -/// {@category One} +/// {@category cat1} class C1 {} /// A constant. -/// {@category One} +/// {@category cat1} const c1 = 1; /// An enum. -/// {@category One} +/// {@category cat1} enum E1 { one, two } /// A function. -/// {@category One} +/// {@category cat1} void F1() {} /// A mixin. -/// {@category One} +/// {@category cat1} mixin M1 {} /// A property. -/// {@category One} +/// {@category cat1} var p1 = 1; /// A typedef. -/// {@category One} +/// {@category cat1} typedef T1 = void Function(); /// A typedef. -/// {@category One} +/// {@category cat1} // TODO(srawlins): Properly unit-test "typedef pointing to typedef". typedef T2 = T1; /// An extension. -/// {@category One} +/// {@category cat1} extension Ex on int {} /// An extension type. -/// {@category One} +/// {@category cat1} extension type ExType(int it) {} '''), d.file('other.dart', ''' @@ -136,7 +137,7 @@ library; await utils.writeDartdocResources(resourceProvider); await (await buildDartdoc()).generateDocs(); topicOneLines = resourceProvider - .getFile(path.join(packagePath, 'doc', 'topics', 'One-topic.html')) + .getFile(path.join(packagePath, 'doc', 'topics', 'cat1-topic.html')) .readAsStringSync() .split('\n'); indexPageLines = resourceProvider @@ -145,6 +146,14 @@ library; .split('\n'); }); + test('redirect category file created', () async { + final redirectContent = resourceProvider + .getFile(path.join(packagePath, 'doc', 'topics', 'One-topic.html')) + .readAsStringSync(); + + expect(redirectContent, contains('Classes'), + matches('Classes'), matches('C1'), ]), ); @@ -225,7 +234,7 @@ library; topicOneLines, containsAllInOrder([ matches('
Enums'), + matches('Enums'), matches('E1'), ]), ); @@ -236,7 +245,7 @@ library; topicOneLines, containsAllInOrder([ matches('
Mixins'), + matches('Mixins'), matches('M1'), ]), ); @@ -247,7 +256,7 @@ library; topicOneLines, containsAllInOrder([ matches('
Constants'), + matches('Constants'), matches('c1'), ]), ); @@ -258,7 +267,8 @@ library; topicOneLines, containsAllInOrder([ matches('
Properties'), + matches( + 'Properties'), matches('p1'), ]), ); @@ -269,7 +279,7 @@ library; topicOneLines, containsAllInOrder([ matches('
Functions'), + matches('Functions'), matches('F1'), ]), ); @@ -280,7 +290,7 @@ library; topicOneLines, containsAllInOrder([ matches('
Typedefs'), + matches('Typedefs'), matches('T1'), ]), ); @@ -291,7 +301,8 @@ library; topicOneLines, containsAllInOrder([ matches('
Extensions'), + matches( + 'Extensions'), matches('Ex'), ]), ); @@ -302,7 +313,7 @@ library; topicOneLines, containsAllInOrder([ matches('
' + matches('' 'Extension Types'), matches('ExType'), ]),