diff --git a/lib/src/generator/templates.runtime_renderers.dart b/lib/src/generator/templates.runtime_renderers.dart index 803a73f0ea..3203d958e2 100644 --- a/lib/src/generator/templates.runtime_renderers.dart +++ b/lib/src/generator/templates.runtime_renderers.dart @@ -3571,6 +3571,38 @@ class _Renderer_Constructor 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); + }, + ), 'fullKind': Property( getValue: (CT_ c) => c.fullKind, renderVariable: ( diff --git a/lib/src/model/constructor.dart b/lib/src/model/constructor.dart index 343dabaec5..f8ce976335 100644 --- a/lib/src/model/constructor.dart +++ b/lib/src/model/constructor.dart @@ -2,8 +2,6 @@ // 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. -// ignore_for_file: analyzer_use_new_elements - import 'package:analyzer/dart/element/element.dart'; import 'package:analyzer/dart/element/element2.dart'; import 'package:analyzer/source/line_info.dart'; @@ -17,7 +15,8 @@ import 'package:dartdoc/src/model_utils.dart'; class Constructor extends ModelElement with ContainerMember, TypeParameters { @override - ConstructorElement get element => element2.asElement; + // ignore: analyzer_use_new_elements + ConstructorElement get element => element2.asElement; @override final ConstructorElement2 element2; @@ -26,7 +25,7 @@ class Constructor extends ModelElement with ContainerMember, TypeParameters { @override CharacterLocation? get characterLocation { - if (element.isSynthetic) { + if (element2.isSynthetic) { // Make warnings for a synthetic constructor refer to somewhere reasonable // since a synthetic constructor has no definition independent of the // parent class. @@ -39,11 +38,11 @@ class Constructor extends ModelElement with ContainerMember, TypeParameters { bool get isPublic { if (!super.isPublic) return false; if (element2.hasPrivateName) return false; - var class_ = element.enclosingElement3; + var class_ = element2.enclosingElement2; // Enums cannot be explicitly constructed or extended. - if (class_ is EnumElement) return false; - if (class_ is ClassElement) { - if (element.isFactory) return true; + if (class_ is EnumElement2) return false; + if (class_ is ClassElement2) { + if (element2.isFactory) return true; if (class_.isSealed || (class_.isAbstract && class_.isFinal) || (class_.isAbstract && class_.isInterface)) { @@ -62,7 +61,12 @@ class Constructor extends ModelElement with ContainerMember, TypeParameters { @override Container get enclosingElement => - getModelFor(element.enclosingElement3, library) as Container; + getModelFor2(element2.enclosingElement2, library) as Container; + + @override + String get fileName => + // TODO(srawlins): It would be great to use `new.html` as the file name. + isUnnamedConstructor ? '${enclosingElement.name}.html' : '$name.html'; @override String get aboveSidebarPath => enclosingElement.sidebarPath; @@ -77,39 +81,32 @@ class Constructor extends ModelElement with ContainerMember, TypeParameters { } @override - String get fullyQualifiedName { - if (isUnnamedConstructor) return super.fullyQualifiedName; - return '${library.name}.$name'; - } + String get fullyQualifiedName => '${library.name}.$name'; @override - bool get isConst => element.isConst; + bool get isConst => element2.isConst; - bool get isUnnamedConstructor => name == enclosingElement.name; + bool get isUnnamedConstructor => element2.name3 == 'new'; - bool get isFactory => element.isFactory; + bool get isFactory => element2.isFactory; @override Kind get kind => Kind.constructor; - late final Callable modelType = getTypeFor(element.type, library) as Callable; + late final Callable modelType = + getTypeFor(element2.type, library) as Callable; @override - String get name { - // TODO(jcollins-g): After the old lookup code is retired, rationalize - // [name] around the conventions used in referenceChildren and replace - // code there and elsewhere with simple references to the name. - var constructorName = element.name; - if (constructorName.isEmpty) { - return enclosingElement.name; - } - return '${enclosingElement.name}.$constructorName'; - } + String get name => + // TODO(jcollins-g): After the old lookup code is retired, rationalize + // [name] around the conventions used in referenceChildren and replace + // code there and elsewhere with simple references to the name. + '${enclosingElement.name}.${element2.name3}'; @override String get nameWithGenerics { - var constructorName = element.name; - if (constructorName.isEmpty) { + var constructorName = element2.name3!; + if (constructorName == 'new') { return '${enclosingElement.name}$genericParameters'; } return '${enclosingElement.name}$genericParameters.$constructorName'; @@ -117,7 +114,7 @@ class Constructor extends ModelElement with ContainerMember, TypeParameters { String? get shortName { if (name.contains('.')) { - return name.substring(element.enclosingElement3.name.length + 1); + return name.substring(element2.enclosingElement2.name3!.length + 1); } else { return name; } @@ -126,17 +123,17 @@ class Constructor extends ModelElement with ContainerMember, TypeParameters { @override late final Map referenceChildren = () { // Find the element that [parameter] is _really_ referring to. - Element? dereferenceParameter(ParameterElement? parameter) => + Element2? dereferenceParameter(FormalParameterElement? parameter) => switch (parameter) { - FieldFormalParameterElement() => parameter.field, - SuperFormalParameterElement() => - dereferenceParameter(parameter.superConstructorParameter), + FieldFormalParameterElement2() => parameter.field2, + SuperFormalParameterElement2() => + dereferenceParameter(parameter.superConstructorParameter2), _ => parameter }; var parameterElements = parameters.map((parameter) { - var element = dereferenceParameter(parameter.element); - return element == null ? parameter : getModelForElement(element); + var element = dereferenceParameter(parameter.element2); + return element == null ? parameter : getModelForElement2(element); }); return { for (var element in parameterElements) element.referenceName: element, @@ -146,5 +143,5 @@ class Constructor extends ModelElement with ContainerMember, TypeParameters { @override String get referenceName => - isUnnamedConstructor ? enclosingElement.name : element.name; + isUnnamedConstructor ? enclosingElement.name : element2.name3!; } diff --git a/lib/src/model/getter_setter_combo.dart b/lib/src/model/getter_setter_combo.dart index ace35bb0b3..0475c1d619 100644 --- a/lib/src/model/getter_setter_combo.dart +++ b/lib/src/model/getter_setter_combo.dart @@ -83,7 +83,6 @@ mixin GetterSetterCombo on ModelElement { String linkifyConstantValue(String original) { if (_constantInitializer is! InstanceCreationExpression) return original; - var constructorName = _constantInitializer.constructorName.toString(); var element = _constantInitializer.constructorName.element; if (element == null) return original; @@ -93,8 +92,11 @@ mixin GetterSetterCombo on ModelElement { // TODO(jcollins-g): this logic really should be integrated into // `Constructor`, but that's not trivial because of `linkedName`'s usage. - if (enclosingElement.name == target.name) { - return original.replaceAll(constructorName, target.linkedName); + if (target.isUnnamedConstructor) { + var parts = target.linkedNameParts; + // We don't want the `.new` representation of an unnamed constructor. + var linkedName = '${parts.tag}${enclosingElement.name}${parts.endTag}'; + return original.replaceAll(enclosingElement.name, linkedName); } return original.replaceAll('${enclosingElement.name}.${target.name}', '${enclosingElement.linkedName}.${target.linkedName}'); diff --git a/lib/src/model/inheriting_container.dart b/lib/src/model/inheriting_container.dart index 7cd552fc86..0f8c0ec797 100644 --- a/lib/src/model/inheriting_container.dart +++ b/lib/src/model/inheriting_container.dart @@ -28,7 +28,7 @@ mixin Constructable implements InheritingContainer { @override late final List publicConstructorsSorted = - constructors.wherePublic.toList(growable: false)..sort(); + constructors.wherePublic.toList(growable: false)..sort(byName); @override @visibleForOverriding diff --git a/lib/src/model/model_element.dart b/lib/src/model/model_element.dart index 2c7f3f8394..97e4075134 100644 --- a/lib/src/model/model_element.dart +++ b/lib/src/model/model_element.dart @@ -228,9 +228,9 @@ abstract class ModelElement if (e is ExecutableMember) { originalMember = e; e = e.baseElement; - } else if (e is FieldMember){ + } else if (e is FieldMember) { originalMember = e; - e = e.baseElement; + e = e.baseElement; } // Return the cached ModelElement if it exists. @@ -676,7 +676,14 @@ abstract class ModelElement @override Library get library => _library; + /// The name of this element, wrapped in an HTML link (an `` tag) if [href] + /// is non-`null`. late final String linkedName = () { + var parts = linkedNameParts; + return '${parts.tag}${parts.text}${parts.endTag}'; + }(); + + ({String tag, String text, String endTag}) get linkedNameParts { // If `name` is empty, we probably have the wrong Element association or // there's an analyzer issue. assert(name.isNotEmpty || @@ -689,12 +696,16 @@ abstract class ModelElement if (isPublicAndPackageDocumented) { warn(PackageWarning.noCanonicalFound); } - return htmlEscape.convert(name); + return (tag: '', text: htmlEscape.convert(name), endTag: ''); } var cssClass = isDeprecated ? ' class="deprecated"' : ''; - return '$displayName'; - }(); + return ( + tag: '', + text: displayName, + endTag: '' + ); + } ParameterRenderer get _parameterRenderer => const ParameterRendererHtml(); diff --git a/lib/src/model/nameable.dart b/lib/src/model/nameable.dart index a7fc9c4f70..423948fd59 100644 --- a/lib/src/model/nameable.dart +++ b/lib/src/model/nameable.dart @@ -12,6 +12,7 @@ import 'package:analyzer/src/utilities/extensions/element.dart'; import 'package:collection/collection.dart'; import 'package:dartdoc/src/element_type.dart'; import 'package:dartdoc/src/model/accessor.dart'; +import 'package:dartdoc/src/model/constructor.dart'; import 'package:dartdoc/src/model/container.dart'; import 'package:dartdoc/src/model/library.dart'; import 'package:dartdoc/src/model/model_element.dart'; @@ -121,7 +122,7 @@ mixin Nameable { enclosingContainer: enclosingContainer, ); - /// Returns the [ModelElement] for [element], instantiating it if needed. + /// Returns the [ModelElement] for [element], instantiating it if needed. /// /// A convenience method for [ModelElement.forPropertyInducingElement], see /// its documentation. @@ -155,6 +156,12 @@ int byName(Nameable a, Nameable b) { return compareAsciiLowerCaseNatural(a.displayName, b.displayName); } + if (a is Constructor && b is Constructor) { + var aName = a.name.replaceFirst('.new', ''); + var bName = b.name.replaceFirst('.new', ''); + return compareAsciiLowerCaseNatural(aName, bName); + } + var stringCompare = compareAsciiLowerCaseNatural(a.name, b.name); if (stringCompare != 0) { return stringCompare; diff --git a/test/constructors_test.dart b/test/constructors_test.dart index 81f744910b..2e2d74ce4f 100644 --- a/test/constructors_test.dart +++ b/test/constructors_test.dart @@ -27,9 +27,8 @@ abstract final class C { } '''); var c = library.classes.named('C').constructors.first; - expect(c.name, equals('C')); - // TODO(srawlins): This should be `constructors.C.new`. - expect(c.fullyQualifiedName, 'constructors.C.C'); + expect(c.name, equals('C.new')); + expect(c.fullyQualifiedName, 'constructors.C.new'); expect(c.isPublic, isTrue); expect(c.documentationAsHtml, '

Constructor.

'); } @@ -42,9 +41,8 @@ abstract final class C { } '''); var c = library.classes.named('C').constructors.first; - expect(c.name, equals('C')); - // TODO(srawlins): This should be `constructors.C.new`. - expect(c.fullyQualifiedName, 'constructors.C.C'); + expect(c.name, equals('C.new')); + expect(c.fullyQualifiedName, 'constructors.C.new'); expect(c.isPublic, isFalse); expect(c.documentationAsHtml, '

Constructor.

'); } @@ -57,9 +55,8 @@ abstract interface class C { } '''); var c = library.classes.named('C').constructors.first; - expect(c.name, equals('C')); - // TODO(srawlins): This should be `constructors.C.new`. - expect(c.fullyQualifiedName, 'constructors.C.C'); + expect(c.name, equals('C.new')); + expect(c.fullyQualifiedName, 'constructors.C.new'); expect(c.isPublic, isFalse); expect(c.documentationAsHtml, '

Constructor.

'); } @@ -86,7 +83,7 @@ class _C { } '''); var c = library.classes.named('_C').constructors.first; - expect(c.name, equals('_C')); + expect(c.name, equals('_C.new')); expect(c.isPublic, isFalse); expect(c.documentationAsHtml, '

Constructor.

'); } @@ -96,7 +93,7 @@ class _C { class C {} '''); var c = library.classes.named('C').constructors.first; - expect(c.name, equals('C')); + expect(c.name, equals('C.new')); expect(c.isPublic, isTrue); expect(c.documentationAsHtml, ''); } @@ -123,7 +120,7 @@ class C { } '''); var c = library.classes.named('C').constructors.first; - expect(c.name, equals('C')); + expect(c.name, equals('C.new')); expect(c.isPublic, isTrue); expect(c.documentationAsHtml, '

Constructor.

'); } @@ -136,9 +133,8 @@ class C { } '''); var c = library.classes.named('C').constructors.first; - expect(c.name, equals('C')); - // TODO(srawlins): This should be `constructors.C.new`. - expect(c.fullyQualifiedName, 'constructors.C.C'); + expect(c.name, equals('C.new')); + expect(c.fullyQualifiedName, 'constructors.C.new'); expect(c.isPublic, isTrue); expect(c.documentationAsHtml, '

Constructor.

'); } @@ -151,7 +147,7 @@ sealed class C { } '''); var c = library.classes.named('C').constructors.first; - expect(c.name, equals('C')); + expect(c.name, equals('C.new')); expect(c.isPublic, isFalse); expect(c.documentationAsHtml, '

Constructor.

'); } @@ -180,9 +176,8 @@ enum E { } '''); var e = library.enums.named('E').constructors.first; - expect(e.name, equals('E')); - // TODO(srawlins): This should be `constructors.E.new`. - expect(e.fullyQualifiedName, 'constructors.E.E'); + expect(e.name, equals('E.new')); + expect(e.fullyQualifiedName, 'constructors.E.new'); expect(e.isPublic, isFalse); expect(e.documentationAsHtml, '

Constructor.

'); } @@ -196,7 +191,6 @@ extension type ET(int it) { '''); var etNamed = library.extensionTypes.named('ET').constructors.named('ET.named'); - expect(etNamed.name, equals('ET.named')); expect(etNamed.fullyQualifiedName, 'constructors.ET.named'); expect(etNamed.isPublic, isTrue); expect(etNamed.documentationAsHtml, '

Constructor.

'); @@ -208,7 +202,6 @@ extension type ET.named(int it) {} '''); var etNamed = library.extensionTypes.named('ET').constructors.named('ET.named'); - expect(etNamed.name, equals('ET.named')); expect(etNamed.fullyQualifiedName, 'constructors.ET.named'); expect(etNamed.isPublic, isTrue); } @@ -217,10 +210,8 @@ extension type ET.named(int it) {} var library = await bootPackageWithLibrary(''' extension type ET(int it) {} '''); - var et = library.extensionTypes.named('ET').constructors.named('ET'); - expect(et.name, equals('ET')); - // TODO(srawlins): This should be `constructors.ET.new`. - expect(et.fullyQualifiedName, 'constructors.ET.ET'); + var et = library.extensionTypes.named('ET').constructors.named('ET.new'); + expect(et.fullyQualifiedName, 'constructors.ET.new'); expect(et.isPublic, isTrue); } @@ -231,10 +222,9 @@ extension type ET.named(int it) { ET(this.it); } '''); - var etNamed = library.extensionTypes.named('ET').constructors.named('ET'); - expect(etNamed.name, equals('ET')); - // TODO(srawlins): This should be `constructors.ET.new`. - expect(etNamed.fullyQualifiedName, 'constructors.ET.ET'); + var etNamed = + library.extensionTypes.named('ET').constructors.named('ET.new'); + expect(etNamed.fullyQualifiedName, 'constructors.ET.new'); expect(etNamed.isPublic, isTrue); expect(etNamed.documentationAsHtml, '

Constructor.

'); } diff --git a/test/end2end/model_test.dart b/test/end2end/model_test.dart index b8745b7f5c..b49f7f976f 100644 --- a/test/end2end/model_test.dart +++ b/test/end2end/model_test.dart @@ -2414,7 +2414,7 @@ void main() async { aNonDefaultConstructor = baseForDocComments.constructors .named('BaseForDocComments.aNonDefaultConstructor'); defaultConstructor = - baseForDocComments.constructors.named('BaseForDocComments'); + baseForDocComments.constructors.named('BaseForDocComments.new'); somethingShadowyParameter = defaultConstructor.parameters.named('somethingShadowy'); initializeMe = baseForDocComments.allFields.named('initializeMe'); @@ -2473,7 +2473,7 @@ void main() async { anotherConstructor = FactoryConstructorThings.constructors .named('FactoryConstructorThings.anotherConstructor'); factoryConstructorThingsDefault = FactoryConstructorThings.constructors - .named('FactoryConstructorThings'); + .named('FactoryConstructorThings.new'); aName = anotherName.parameters.named('aName'); anotherNameParameter = anotherName.parameters.named('anotherName'); @@ -3974,8 +3974,10 @@ String? topLevelFunction(int param1, bool param2, Cool coolBeans, }); test('substrings of the constant values type are not linked (#1535)', () { - expect(aName.constantValue, - 'const ExtendedShortName("hello there")'); + expect( + aName.constantValue, + 'const ExtendedShortName("hello there")', + ); }); test('constant field values are escaped', () { @@ -4040,10 +4042,10 @@ String? topLevelFunction(int param1, bool param2, Cool coolBeans, constCat = exLibrary.classes.named('ConstantCat'); constructorTester = fakeLibrary.classes.named('ConstructorTester'); constCatConstructor = constCat.constructors.first; - appleDefaultConstructor = apple.constructors.named('Apple'); + appleDefaultConstructor = apple.constructors.named('Apple.new'); appleConstructorFromString = apple.constructors.named('Apple.fromString'); constructorTesterDefault = - constructorTester.constructors.named('ConstructorTester'); + constructorTester.constructors.named('ConstructorTester.new'); constructorTesterFromSomething = constructorTester.constructors .named('ConstructorTester.fromSomething'); referToADefaultConstructor = @@ -4095,8 +4097,8 @@ String? topLevelFunction(int param1, bool param2, Cool coolBeans, test('has constructor', () { expect(appleDefaultConstructor, isNotNull); - expect(appleDefaultConstructor.name, equals('Apple')); - expect(appleDefaultConstructor.shortName, equals('Apple')); + expect(appleDefaultConstructor.name, equals('Apple.new')); + expect(appleDefaultConstructor.shortName, equals('new')); }); test('title has factory qualifier', () { diff --git a/test/parameters_test.dart b/test/parameters_test.dart index f43a6361e9..d9c3908403 100644 --- a/test/parameters_test.dart +++ b/test/parameters_test.dart @@ -251,7 +251,7 @@ class C { C(this.p); } '''); - var cConstructor = library.classes.named('C').constructors.named('C'); + var cConstructor = library.classes.named('C').constructors.named('C.new'); // There is no link, but also no wrong link or crash. expect(cConstructor.documentationAsHtml, '

Text p.

'); } @@ -264,7 +264,7 @@ class C { C(this._); } '''); - var cConstructor = library.classes.named('C').constructors.named('C'); + var cConstructor = library.classes.named('C').constructors.named('C.new'); // There is no link, but also no wrong link or crash. expect(cConstructor.documentationAsHtml, '

Text _.

'); } @@ -279,7 +279,7 @@ class D extends C { D(super._) {} } '''); - var dConstructor = library.classes.named('D').constructors.named('D'); + var dConstructor = library.classes.named('D').constructors.named('D.new'); // There is no link, but also no wrong link or crash. expect(dConstructor.documentationAsHtml, '

Text _.

'); } diff --git a/test/templates/class_test.dart b/test/templates/class_test.dart index 2e46a46a88..c70ef2bde0 100644 --- a/test/templates/class_test.dart +++ b/test/templates/class_test.dart @@ -218,7 +218,7 @@ class C { htmlLines.expectMainContentContainsAllInOrder([ matches('

Constructors

'), - matches('C'), + matches('C.new'), matches('An unnamed constructor.'), ]); } diff --git a/test/templates/extension_type_test.dart b/test/templates/extension_type_test.dart index 5c9a7cb381..4d50a24844 100644 --- a/test/templates/extension_type_test.dart +++ b/test/templates/extension_type_test.dart @@ -166,9 +166,8 @@ extension type One(int it) { htmlLines.expectMainContentContainsAllInOrder([ matches('

Constructors

'), - matches('One'), - matches('' - 'One.named'), + matches('One.new'), + matches('One.named'), matches('A named constructor.'), ]); }