Skip to content

Support displaying record types #3233

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Oct 28, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 27 additions & 5 deletions lib/src/element_type.dart
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ abstract class ElementType extends Privacy

factory ElementType._from(
DartType f, Library library, PackageGraph packageGraph) {
if (f is RecordType) {
return RecordElementType(f, library, packageGraph);
}
var fElement = DartTypeExtension(f).element;
if (fElement == null ||
fElement.kind == ElementKind.DYNAMIC ||
Expand All @@ -55,10 +58,10 @@ abstract class ElementType extends Privacy
}
// [DefinedElementType]s.
var element = packageGraph.modelBuilder.fromElement(fElement);
// [TypeAliasElement.aliasElement] has different implications.
// In that case it is an actual type alias of some kind (generic
// or otherwise. Here however aliasElement signals that this is a
// type referring to an alias.
// `TypeAliasElement.alias.element` has different implications.
// In that case it is an actual type alias of some kind (generic or
// otherwise). Here however `alias.element` signals that this is a type
// referring to an alias.
if (f is! TypeAliasElement && f.alias?.element != null) {
return AliasedElementType(
f as ParameterizedType, library, packageGraph, element);
Expand All @@ -69,7 +72,7 @@ abstract class ElementType extends Privacy
var isGenericTypeAlias = f.alias?.element != null && f is! InterfaceType;
if (f is FunctionType) {
assert(f is ParameterizedType);
// This is an indication we have an extremely out of date analyzer....
// This is an indication we have an extremely out of date analyzer.
assert(!isGenericTypeAlias, 'should never occur: out of date analyzer?');
// And finally, delete this case and its associated class
// after https://dart-review.googlesource.com/c/sdk/+/201520
Expand Down Expand Up @@ -175,6 +178,25 @@ class FunctionTypeElementType extends UndefinedElementType
packageGraph.rendererFactory.functionTypeElementTypeRenderer;
}

/// A [RecordType] which does not have an underpinning Element.
class RecordElementType extends UndefinedElementType with Rendered {
RecordElementType(RecordType super.f, super.library, super.packageGraph);

@override
String get name => 'Record';

@override
ElementTypeRenderer get _renderer =>
packageGraph.rendererFactory.recordElementTypeRenderer;

List<RecordTypeField> get positionalFields => type.positionalFields;

List<RecordTypeField> get namedFields => type.namedFields;

@override
RecordType get type => super.type as RecordType;
}

class AliasedFunctionTypeElementType extends FunctionTypeElementType
with Aliased {
AliasedFunctionTypeElementType(super.f, super.library, super.packageGraph) {
Expand Down
43 changes: 43 additions & 0 deletions lib/src/render/element_type_renderer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import 'package:dartdoc/src/element_type.dart';
import 'package:dartdoc/src/render/parameter_renderer.dart';
import 'package:dartdoc/src/render/record_type_field_renderer.dart';

abstract class ElementTypeRenderer<T extends ElementType> {
const ElementTypeRenderer();
Expand Down Expand Up @@ -115,6 +116,28 @@ class ParameterizedElementTypeRendererHtml
);
}

class RecordElementTypeRendererHtml
extends ElementTypeRendererHtml<RecordElementType> {
const RecordElementTypeRendererHtml();

@override
String renderLinkedName(RecordElementType elementType) {
var buffer = StringBuffer()
..write(elementType.nameWithGenerics)
..write('(')
..write(const RecordTypeFieldListHtmlRenderer()
.renderLinkedFields(elementType)
.trim())
..write(')');
return wrapNullabilityParens(elementType, buffer.toString());
}

@override
String renderNameWithGenerics(RecordElementType elementType) {
return '${elementType.name}${elementType.nullabilitySuffix}';
}
}

class AliasedFunctionTypeElementTypeRendererHtml
extends ElementTypeRendererHtml<AliasedFunctionTypeElementType> {
const AliasedFunctionTypeElementTypeRendererHtml();
Expand Down Expand Up @@ -277,6 +300,26 @@ class ParameterizedElementTypeRendererMd
);
}

class RecordElementTypeRendererMd
extends ElementTypeRendererMd<RecordElementType> {
const RecordElementTypeRendererMd();

@override
String renderLinkedName(RecordElementType elementType) {
var buffer = StringBuffer()
..write('(')
..write(
const RecordTypeFieldListMdRenderer().renderLinkedFields(elementType))
..write(')');
return wrapNullabilityParens(elementType, buffer.toString());
}

@override
String renderNameWithGenerics(RecordElementType elementType) {
return '${elementType.name}${elementType.nullabilitySuffix}';
}
}

class AliasedElementTypeRendererMd
extends ElementTypeRendererMd<AliasedElementType> {
const AliasedElementTypeRendererMd();
Expand Down
119 changes: 119 additions & 0 deletions lib/src/render/record_type_field_renderer.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
// Copyright (c) 2022, 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:analyzer/dart/element/type.dart';
import 'package:collection/collection.dart';
import 'package:dartdoc/src/element_type.dart';

/// Render HTML suitable for a single, wrapped line.
class RecordTypeFieldListHtmlRenderer extends _RecordTypeFieldListRenderer {
const RecordTypeFieldListHtmlRenderer();

@override
String listItem(String item) => item;
@override
String orderedList(String listItems) => listItems;
@override
String annotation(String name) => '<span>$name</span>';

@override
String field(String name) => '<span class="field">$name</span>';
@override
String fieldName(String name) => '<span class="field-name">$name</span>';
@override
String typeName(String name) => '<span class="type-annotation">$name</span>';
}

class RecordTypeFieldListMdRenderer extends _RecordTypeFieldListRenderer {
const RecordTypeFieldListMdRenderer();

@override
String annotation(String name) => name;

@override
String listItem(String item) => item;

@override
String orderedList(String listItems) => listItems;

@override
String field(String name) => name;

@override
String fieldName(String name) => name;

@override
String typeName(String name) => name;
}

abstract class _RecordTypeFieldListRenderer {
const _RecordTypeFieldListRenderer();

String listItem(String item);
String orderedList(String listItems);
String annotation(String name);
String field(String name);
String fieldName(String name);
String typeName(String name);

String renderLinkedFields(RecordElementType recordElementType) {
final buffer = StringBuffer();

void renderLinkedFieldSublist(
List<RecordTypeField> fields, {
required bool trailingComma,
String openBracket = '',
String closeBracket = '',
}) {
fields.forEachIndexed((index, field) {
var prefix = '';
var suffix = '';
if (identical(field, fields.first)) {
prefix = openBracket;
}
if (identical(field, fields.last)) {
suffix += closeBracket;
if (trailingComma) suffix += ', ';
} else {
suffix += ', ';
}

var fieldBuffer = StringBuffer();
fieldBuffer.write(prefix);
var modelType = recordElementType.modelBuilder
.typeFrom(field.type, recordElementType.library);
var linkedTypeName = typeName(modelType.linkedName);
if (linkedTypeName.isNotEmpty) {
fieldBuffer.write(linkedTypeName);
fieldBuffer.write(' ');
}
var name = field is RecordTypeNamedField
? field.name
: _fieldName(field, index);
fieldBuffer.write(fieldName(name));
fieldBuffer.write(suffix);

buffer.write(listItem(this.field(fieldBuffer.toString())));
});
}

if (recordElementType.positionalFields.isNotEmpty) {
renderLinkedFieldSublist(
recordElementType.positionalFields,
trailingComma: recordElementType.namedFields.isNotEmpty,
);
}
if (recordElementType.namedFields.isNotEmpty) {
renderLinkedFieldSublist(
recordElementType.namedFields,
trailingComma: false,
openBracket: '{',
closeBracket: '}',
);
}
return orderedList(buffer.toString());
}

String _fieldName(RecordTypeField field, int index) => '\$$index';
}
10 changes: 10 additions & 0 deletions lib/src/render/renderer_factory.dart
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ abstract class RendererFactory {
ElementTypeRenderer<ParameterizedElementType>
get parameterizedElementTypeRenderer;

ElementTypeRenderer<RecordElementType> get recordElementTypeRenderer;

ElementTypeRenderer<AliasedElementType> get aliasedElementTypeRenderer;

ElementTypeRenderer<AliasedFunctionTypeElementType>
Expand Down Expand Up @@ -98,6 +100,10 @@ class HtmlRenderFactory extends RendererFactory {
get parameterizedElementTypeRenderer =>
const ParameterizedElementTypeRendererHtml();

@override
ElementTypeRenderer<RecordElementType> get recordElementTypeRenderer =>
const RecordElementTypeRendererHtml();

@override
ElementTypeRenderer<AliasedElementType> get aliasedElementTypeRenderer =>
const AliasedElementTypeRendererHtml();
Expand Down Expand Up @@ -168,6 +174,10 @@ class MdRenderFactory extends RendererFactory {
get parameterizedElementTypeRenderer =>
const ParameterizedElementTypeRendererMd();

@override
ElementTypeRenderer<RecordElementType> get recordElementTypeRenderer =>
const RecordElementTypeRendererMd();

@override
ElementTypeRenderer<AliasedElementType> get aliasedElementTypeRenderer =>
const AliasedElementTypeRendererMd();
Expand Down
Loading