diff --git a/lib/src/element_type.dart b/lib/src/element_type.dart index 24dbef15b4..5ca366d1f2 100644 --- a/lib/src/element_type.dart +++ b/lib/src/element_type.dart @@ -383,8 +383,10 @@ abstract class DefinedElementType extends ElementType { } /// Any callable ElementType will mix-in this class, whether anonymous or not. -abstract class CallableElementTypeMixin implements ElementType { - Iterable _typeArguments; +mixin CallableElementTypeMixin implements ElementType { + @override + // TODO(jcollins-g): remove after dart-lang/dartdoc#2648 is fixed. + String get linkedName; ModelElement get returnElement => returnType is DefinedElementType ? (returnType as DefinedElementType).modelElement @@ -400,50 +402,12 @@ abstract class CallableElementTypeMixin implements ElementType { @override FunctionType get type => _type; - // TODO(jcollins-g): Rewrite this and improve object model so this doesn't - // require type checking everywhere. - Iterable get typeArguments { - if (_typeArguments == null) { - Iterable dartTypeArguments; - if (returnedFrom is FunctionTypeElementType) { - if (type.typeFormals.isEmpty) { - dartTypeArguments = type.aliasArguments; - } else { - dartTypeArguments = type.typeFormals.map(_legacyTypeParameterType); - } - } else { - if (type.typeFormals.isEmpty) { - dartTypeArguments = type.aliasArguments; - } else if (returnedFrom != null && - returnedFrom.type.element is GenericFunctionTypeElement) { - _typeArguments = (returnedFrom as DefinedElementType).typeArguments; - } else { - dartTypeArguments = type.typeFormals.map(_legacyTypeParameterType); - } - } - if (dartTypeArguments != null) { - _typeArguments = dartTypeArguments - .map((f) => ElementType.from(f, library, packageGraph)) - .toList(); - } - } - return _typeArguments; - } - - /// Return the [TypeParameterType] with the legacy nullability for the given - /// type parameter [element]. - /// - /// TODO(scheglov): This method is a work around that fact that DartDoc - /// currently represents both type formals and uses of them as actual types, - /// as [TypeParameterType]s. This was not perfect, but worked before Null - /// safety. With Null safety, types have nullability suffixes, but type - /// formals should not. Eventually we should separate models for type formals - /// and types. - static TypeParameterType _legacyTypeParameterType( - TypeParameterElement element, - ) { - return element.instantiate(nullabilitySuffix: NullabilitySuffix.star); - } + Iterable _typeArguments; + Iterable get typeArguments => + _typeArguments ??= type.aliasArguments + ?.map((f) => ElementType.from(f, library, packageGraph)) + ?.toList() ?? + []; } /// A callable type that may or may not be backed by a declaration using the generic @@ -455,16 +419,8 @@ class CallableElementType extends ParameterizedElementType : super(t, library, packageGraph, element, returnedFrom); @override - String get linkedName { - if (_linkedName == null) { - if (name != null && name.isNotEmpty) { - _linkedName = super.linkedName; - } else { - _linkedName = _renderer.renderLinkedName(this); - } - } - return _linkedName; - } + String get name => + super.name != null && super.name.isNotEmpty ? super.name : 'Function'; @override ElementTypeRenderer get _renderer => diff --git a/lib/src/generator/templates.renderers.dart b/lib/src/generator/templates.renderers.dart index ed387b810a..8ab179ce3a 100644 --- a/lib/src/generator/templates.renderers.dart +++ b/lib/src/generator/templates.renderers.dart @@ -500,7 +500,26 @@ class _Renderer_CallableElementTypeMixin _propertyMapCache.putIfAbsent( CT_, () => { - ..._Renderer_Object.propertyMap(), + 'linkedName': Property( + getValue: (CT_ c) => c.linkedName, + 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), + nextProperty, [...remainingNames.skip(1)]); + }, + isNullValue: (CT_ c) => c.linkedName == null, + renderValue: + (CT_ c, RendererBase r, List ast) { + return _render_String(c.linkedName, ast, r.template, + parent: r); + }, + ), 'returnElement': Property( getValue: (CT_ c) => c.returnElement, renderVariable: @@ -6042,60 +6061,35 @@ String _render_FunctionTypedef( class _Renderer_FunctionTypedef extends RendererBase { static final Map _propertyMapCache = {}; - static Map> propertyMap< - CT_ extends FunctionTypedef>() => - _propertyMapCache.putIfAbsent( - CT_, - () => { - ..._Renderer_Typedef.propertyMap(), - 'aliasedType': Property( - getValue: (CT_ c) => c.aliasedType, - renderVariable: (CT_ c, Property self, - List remainingNames) => - self.renderSimpleVariable( - c, remainingNames, 'FunctionType'), - isNullValue: (CT_ c) => c.aliasedType == null, - renderValue: - (CT_ c, RendererBase r, List ast) { - return renderSimple(c.aliasedType, ast, r.template, - parent: r); - }, - ), - 'genericTypeParameters': Property( - getValue: (CT_ c) => c.genericTypeParameters, - renderVariable: (CT_ c, Property self, - List remainingNames) => - self.renderSimpleVariable( - c, remainingNames, 'List'), - renderIterable: - (CT_ c, RendererBase r, List ast) { - return c.genericTypeParameters.map( - (e) => renderSimple(e, ast, r.template, parent: r)); - }, - ), - 'modelType': Property( - getValue: (CT_ c) => c.modelType, - renderVariable: - (CT_ c, Property self, List remainingNames) { - if (remainingNames.isEmpty) { - return self.getValue(c).toString(); - } - var name = remainingNames.first; - var nextProperty = - _Renderer_CallableElementTypeMixin.propertyMap() - .getValue(name); - return nextProperty.renderVariable(self.getValue(c), - nextProperty, [...remainingNames.skip(1)]); - }, - isNullValue: (CT_ c) => c.modelType == null, - renderValue: - (CT_ c, RendererBase r, List ast) { - return _render_CallableElementTypeMixin( - c.modelType, ast, r.template, - parent: r); - }, - ), - }); + static Map> + propertyMap() => + _propertyMapCache.putIfAbsent( + CT_, + () => { + ..._Renderer_Typedef.propertyMap(), + 'modelType': Property( + getValue: (CT_ c) => c.modelType, + renderVariable: (CT_ c, Property self, + List remainingNames) { + if (remainingNames.isEmpty) { + return self.getValue(c).toString(); + } + var name = remainingNames.first; + var nextProperty = + _Renderer_CallableElementTypeMixin.propertyMap() + .getValue(name); + return nextProperty.renderVariable(self.getValue(c), + nextProperty, [...remainingNames.skip(1)]); + }, + isNullValue: (CT_ c) => c.modelType == null, + renderValue: (CT_ c, RendererBase r, + List ast) { + return _render_CallableElementTypeMixin( + c.modelType, ast, r.template, + parent: r); + }, + ), + }); _Renderer_FunctionTypedef( FunctionTypedef context, RendererBase parent, Template template) @@ -13914,18 +13908,6 @@ class _Renderer_Typedef extends RendererBase { parent: r); }, ), - 'genericTypeParameters': Property( - getValue: (CT_ c) => c.genericTypeParameters, - renderVariable: (CT_ c, Property self, - List remainingNames) => - self.renderSimpleVariable( - c, remainingNames, 'List'), - renderIterable: - (CT_ c, RendererBase r, List ast) { - return c.genericTypeParameters.map( - (e) => renderSimple(e, ast, r.template, parent: r)); - }, - ), 'href': Property( getValue: (CT_ c) => c.href, renderVariable: @@ -13971,6 +13953,27 @@ class _Renderer_Typedef extends RendererBase { return _render_String(c.kind, ast, r.template, parent: r); }, ), + 'linkedGenericParameters': Property( + getValue: (CT_ c) => c.linkedGenericParameters, + 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), + nextProperty, [...remainingNames.skip(1)]); + }, + isNullValue: (CT_ c) => c.linkedGenericParameters == null, + renderValue: + (CT_ c, RendererBase r, List ast) { + return _render_String( + c.linkedGenericParameters, ast, r.template, + parent: r); + }, + ), 'modelType': Property( getValue: (CT_ c) => c.modelType, renderVariable: diff --git a/lib/src/model/typedef.dart b/lib/src/model/typedef.dart index 6f02cf996d..8f4e8527e7 100644 --- a/lib/src/model/typedef.dart +++ b/lib/src/model/typedef.dart @@ -33,8 +33,9 @@ class Typedef extends ModelElement @override String get genericParameters => _renderer.renderGenericParameters(this); - List get genericTypeParameters => - element.typeParameters; + @override + String get linkedGenericParameters => + _renderer.renderLinkedGenericParameters(this); @override String get filePath => '${library.dirName}/$fileName'; @@ -87,21 +88,6 @@ class FunctionTypedef extends Typedef { TypeAliasElement element, Library library, PackageGraph packageGraph) : super(element, library, packageGraph); - @override - FunctionType get aliasedType => super.aliasedType; - - @override - List get genericTypeParameters { - var aliasedTypeElement = aliasedType.aliasElement; - if (aliasedTypeElement is FunctionTypedElement) { - return aliasedTypeElement.typeParameters; - } - if (aliasedType.typeFormals.isNotEmpty == true) { - return aliasedType.typeFormals; - } - return super.genericTypeParameters; - } - @override CallableElementTypeMixin get modelType => super.modelType; } diff --git a/lib/src/render/element_type_renderer.dart b/lib/src/render/element_type_renderer.dart index 94bcedb4a0..940661e8f9 100644 --- a/lib/src/render/element_type_renderer.dart +++ b/lib/src/render/element_type_renderer.dart @@ -141,6 +141,22 @@ class CallableElementTypeRendererHtml buf.write(elementType.returnType.linkedName); return wrapNullabilityParens(elementType, buf.toString()); } + + @override + String renderNameWithGenerics(CallableElementType elementType) { + var buf = StringBuffer(); + buf.write(elementType.name); + if (elementType.typeArguments != null) { + if (elementType.typeArguments.isNotEmpty && + !elementType.typeArguments.every((t) => t.name == 'dynamic')) { + buf.write('<'); + buf.writeAll( + elementType.typeArguments.map((t) => t.nameWithGenerics), ', '); + buf.write('>'); + } + } + return wrapNullability(elementType, buf.toString()); + } } // Markdown implementations @@ -255,4 +271,20 @@ class CallableElementTypeRendererMd buf.write(elementType.returnType.linkedName); return wrapNullabilityParens(elementType, buf.toString()); } + + @override + String renderNameWithGenerics(CallableElementType elementType) { + var buf = StringBuffer(); + buf.write(elementType.name); + if (elementType.typeArguments != null) { + if (elementType.typeArguments.isNotEmpty && + !elementType.typeArguments.every((t) => t.name == 'dynamic')) { + buf.write('<'); + buf.writeAll( + elementType.typeArguments.map((t) => t.nameWithGenerics), ', '); + buf.write('>'); + } + } + return wrapNullability(elementType, buf.toString()); + } } diff --git a/lib/src/render/type_parameters_renderer.dart b/lib/src/render/type_parameters_renderer.dart index d688962eba..26d947a103 100644 --- a/lib/src/render/type_parameters_renderer.dart +++ b/lib/src/render/type_parameters_renderer.dart @@ -5,6 +5,8 @@ import 'package:dartdoc/src/model/type_parameter.dart'; abstract class TypeParametersRenderer { + const TypeParametersRenderer(); + String renderGenericParameters(TypeParameters typeParameters); String renderLinkedGenericParameters(TypeParameters typeParameters); @@ -19,7 +21,10 @@ class TypeParametersRendererHtml implements TypeParametersRenderer { return ''; } var joined = typeParameters.typeParameters - .map((t) => t.name) + .map((t) => [ + ...t.annotations.map((a) => a.linkedNameWithParameters), + t.name + ].join(' ')) .join(', '); return '<$joined>'; } @@ -30,7 +35,10 @@ class TypeParametersRendererHtml implements TypeParametersRenderer { return ''; } var joined = typeParameters.typeParameters - .map((t) => t.linkedName) + .map((t) => [ + ...t.annotations.map((a) => a.linkedNameWithParameters), + t.linkedName + ].join(' ')) .join(', '); return '<$joined>'; } @@ -40,12 +48,19 @@ class TypeParametersRendererMd implements TypeParametersRenderer { const TypeParametersRendererMd(); @override - String renderGenericParameters(TypeParameters typeParameters) => - _compose(typeParameters.typeParameters, (t) => t.name); + String renderGenericParameters(TypeParameters typeParameters) => _compose( + typeParameters.typeParameters, + (t) => [...t.annotations.map((a) => a.linkedNameWithParameters), t.name] + .join(' ')); @override String renderLinkedGenericParameters(TypeParameters typeParameters) => - _compose(typeParameters.typeParameters, (t) => t.linkedName); + _compose( + typeParameters.typeParameters, + (t) => [ + ...t.annotations.map((a) => a.linkedNameWithParameters), + t.linkedName + ].join(' ')); String _compose(List typeParameters, String Function(TypeParameter) mapfn) { diff --git a/lib/src/render/typedef_renderer.dart b/lib/src/render/typedef_renderer.dart index f484d5094b..0a0e224716 100644 --- a/lib/src/render/typedef_renderer.dart +++ b/lib/src/render/typedef_renderer.dart @@ -2,6 +2,7 @@ // 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/src/model/type_parameter.dart'; import 'package:dartdoc/src/model/typedef.dart'; /// A renderer for a [Typedef]. @@ -10,43 +11,69 @@ abstract class TypedefRenderer { /// Render the the generic type parameters of the specified [typedef]. String renderGenericParameters(Typedef typedef); + + /// Render the the generic type parameters of the specified [typedef]. + String renderLinkedGenericParameters(Typedef typedef); } /// A HTML renderer for a [Typedef]. class TypedefRendererHtml extends TypedefRenderer { const TypedefRendererHtml(); - @override - String renderGenericParameters(Typedef typedef) { - final genericTypeParameters = typedef.genericTypeParameters; - if (genericTypeParameters.isEmpty) { + String _renderTypeParameters(Iterable typeParameters, + String Function(TypeParameter t) getName) { + if (typeParameters.isEmpty) { return ''; } final buffer = StringBuffer('<'); - buffer.writeAll(genericTypeParameters.map((t) => t.name), + buffer.writeAll( + typeParameters.map((t) => [ + ...t.annotations.map((a) => a.linkedNameWithParameters), + getName(t) + ].join(' ')), ', '); buffer.write('>'); return buffer.toString(); } + + @override + String renderGenericParameters(Typedef typedef) => + _renderTypeParameters(typedef.typeParameters, (t) => t.name); + + @override + String renderLinkedGenericParameters(Typedef typedef) => + _renderTypeParameters(typedef.typeParameters, (t) => t.linkedName); } /// A markdown renderer for a [Typedef]. class TypedefRendererMd extends TypedefRenderer { const TypedefRendererMd(); - @override - String renderGenericParameters(Typedef typedef) { - final genericTypeParameters = typedef.genericTypeParameters; - if (genericTypeParameters.isEmpty) { + String _renderTypeParameters(Iterable typeParameters, + String Function(TypeParameter t) getName) { + if (typeParameters.isEmpty) { return ''; } final buffer = StringBuffer('<{'); - buffer.writeAll(genericTypeParameters.map((t) => t.name), ', '); + buffer.writeAll( + typeParameters.map((t) => [ + ...t.annotations.map((a) => a.linkedNameWithParameters), + getName(t) + ].join(' ')), + ', '); buffer.write('}>'); return buffer.toString(); } + + @override + String renderGenericParameters(Typedef typedef) => + _renderTypeParameters(typedef.typeParameters, (t) => t.name); + + @override + String renderLinkedGenericParameters(Typedef typedef) => + _renderTypeParameters(typedef.typeParameters, (t) => t.linkedName); } diff --git a/lib/templates/html/_typedef.html b/lib/templates/html/_typedef.html index 754e7067b7..297c264bdc 100644 --- a/lib/templates/html/_typedef.html +++ b/lib/templates/html/_typedef.html @@ -1,6 +1,15 @@ {{#isCallable}} {{#asCallable}} - {{>callable}} +
+ {{{linkedName}}}{{{linkedGenericParameters}}} + = {{{ modelType.linkedName }}} + + {{>categorization}} +
+ + {{{ oneLineDoc }}} {{{ extendedDocLink }}} + {{>features}} + {{/asCallable}} {{/isCallable}} {{^isCallable}} diff --git a/lib/templates/html/_typedef_multiline.html b/lib/templates/html/_typedef_multiline.html index cb8e1c3853..ec44d551ad 100644 --- a/lib/templates/html/_typedef_multiline.html +++ b/lib/templates/html/_typedef_multiline.html @@ -1,6 +1,16 @@ {{#isCallable}} {{#asCallable}} - {{>callable_multiline}} + {{#hasAnnotations}} +
+
    + {{#annotations}} +
  1. {{{linkedNameWithParameters}}}
  2. + {{/annotations}} +
+
+ {{/hasAnnotations}} + {{#isConst}}const {{/isConst}}{{name}}{{{linkedGenericParameters}}} = + {{{ modelType.linkedName }}} {{/asCallable}} {{/isCallable}} {{^isCallable}} diff --git a/test/end2end/model_special_cases_test.dart b/test/end2end/model_special_cases_test.dart index ea4b3bcd76..5cfc9eb1f3 100644 --- a/test/end2end/model_special_cases_test.dart +++ b/test/end2end/model_special_cases_test.dart @@ -10,6 +10,7 @@ library dartdoc.model_special_cases_test; import 'dart:io'; +import 'package:analyzer/dart/element/type.dart'; import 'package:async/async.dart'; import 'package:dartdoc/src/model/model.dart'; import 'package:dartdoc/src/package_config_provider.dart'; @@ -132,6 +133,7 @@ void main() { group('generic metadata', () { Library genericMetadata; TopLevelVariable f; + Typedef F; Class C; Method mp, mn; @@ -139,12 +141,24 @@ void main() { genericMetadata = (await _testPackageGraphExperiments) .libraries .firstWhere((l) => l.name == 'generic_metadata'); + F = genericMetadata.typedefs.firstWhere((t) => t.name == 'F'); f = genericMetadata.properties.firstWhere((p) => p.name == 'f'); C = genericMetadata.classes.firstWhere((c) => c.name == 'C'); mp = C.instanceMethods.firstWhere((m) => m.name == 'mp'); mn = C.instanceMethods.firstWhere((m) => m.name == 'mn'); }); + test( + 'Verify annotations and their type arguments render on type parameters for typedefs', + () { + expect((F.aliasedType as FunctionType).typeFormals.first.metadata, + isNotEmpty); + expect((F.aliasedType as FunctionType).parameters.first.metadata, + isNotEmpty); + // TODO(jcollins-g): add rendering verification once we have data from + // analyzer. + }, skip: 'dart-lang/sdk#46064'); + test('Verify type arguments on annotations renders, including parameters', () { var ab0 = diff --git a/test/end2end/model_test.dart b/test/end2end/model_test.dart index 6887d0844f..60b529389e 100644 --- a/test/end2end/model_test.dart +++ b/test/end2end/model_test.dart @@ -114,7 +114,7 @@ void main() { void expectTypedefs(Typedef t, String modelTypeToString, Iterable genericParameters) { expect(t.modelType.toString(), equals(modelTypeToString)); - expect(t.genericTypeParameters.map((p) => p.toString()), + expect(t.element.typeParameters.map((p) => p.toString()), orderedEquals(genericParameters)); } @@ -3787,6 +3787,7 @@ String topLevelFunction(int param1, bool param2, Cool coolBeans, group('Typedef', () { FunctionTypedef processMessage; + FunctionTypedef oldgeneric; FunctionTypedef generic; FunctionTypedef aComplexTypedef; Class TypedefUsingClass; @@ -3794,6 +3795,8 @@ String topLevelFunction(int param1, bool param2, Cool coolBeans, setUpAll(() { processMessage = exLibrary.typedefs.firstWhere((t) => t.name == 'processMessage'); + oldgeneric = + fakeLibrary.typedefs.firstWhere((t) => t.name == 'GenericTypedef'); generic = fakeLibrary.typedefs.firstWhere((t) => t.name == 'NewGenericTypedef'); @@ -3861,6 +3864,20 @@ String topLevelFunction(int param1, bool param2, Cool coolBeans, 'List<S>')); }); + test('return type', () { + expect( + oldgeneric.modelType.linkedName, + isIn([ + 'T Function(T input)', + // Remove below option after analyzer 1.6.0. + 'Function(T) → T' + ])); + expect( + generic.modelType.linkedName, + equals( + 'List<S> Function<S>(T, int, bool)')); + }); + test('name with generics', () { expect( processMessage.nameWithGenerics, @@ -3879,7 +3896,7 @@ String topLevelFunction(int param1, bool param2, Cool coolBeans, expect(TypedefRendererHtml().renderGenericParameters(processMessage), equals('<T>')); expect(TypedefRendererHtml().renderGenericParameters(generic), - equals('<S>')); + equals('<T>')); }); }); diff --git a/testing/test_package_experiments/lib/generic_metadata.dart b/testing/test_package_experiments/lib/generic_metadata.dart index 1beef9f449..fe0dcbfa29 100644 --- a/testing/test_package_experiments/lib/generic_metadata.dart +++ b/testing/test_package_experiments/lib/generic_metadata.dart @@ -2,7 +2,8 @@ // 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. -/// Borrowed from the Dart SDK: tests/language/generic_metadata_test.dart +/// Borrowed from the Dart SDK: +/// tests/language/generic/generic_metadata_test.dart /// with modifications to remove cases dartdoc is not interested in. // Check that metadata constructor invocations can have type arguments.