diff --git a/lib/src/html/html_generator_instance.dart b/lib/src/html/html_generator_instance.dart index 00a8cbe27a..037ec674a8 100644 --- a/lib/src/html/html_generator_instance.dart +++ b/lib/src/html/html_generator_instance.dart @@ -171,7 +171,7 @@ class HtmlGeneratorInstance { } } - for (var extension in filterNonPublic(lib.extensions)) { + for (var extension in filterNonDocumented(lib.extensions)) { generateExtension(_packageGraph, lib, extension); for (var constant in filterNonDocumented(extension.constants)) { diff --git a/lib/src/model.dart b/lib/src/model.dart index a215e14a9c..d5b6d623c7 100644 --- a/lib/src/model.dart +++ b/lib/src/model.dart @@ -2415,8 +2415,6 @@ class Library extends ModelElement with Categorization, TopLevelContainer { return _allOriginalModelElementNames; } - List get allClasses => _allClasses; - @override CharacterLocation get characterLocation { if (element.nameOffset == -1) { @@ -2430,19 +2428,30 @@ class Library extends ModelElement with Categorization, TopLevelContainer { CompilationUnitElement get compilationUnitElement => (element as LibraryElement).definingCompilationUnit; @override - Iterable get classes { - return _allClasses - .where((c) => !c.isErrorOrException) - .toList(growable: false); - } + Iterable get classes => allClasses.where((c) => !c.isErrorOrException); @override Iterable get extensions { - if (_extensions != null) return _extensions; - _extensions = _libraryElement.definingCompilationUnit.extensions - .map((e) => ModelElement.from(e, this, packageGraph) as Extension) - .toList(growable: false) - ..sort(byName); + if (_extensions == null) { + // De-dupe extensions coming from multiple exported libraries at once. + Set extensionElements = Set(); + extensionElements.addAll(_libraryElement.definingCompilationUnit.extensions); + for (CompilationUnitElement cu in _libraryElement.parts) { + extensionElements.addAll(cu.extensions); + } + for (LibraryElement le in _libraryElement.exportedLibraries) { + extensionElements.addAll(le.definingCompilationUnit.extensions + .where((t) => _exportedNamespace.definedNames.values.contains(t.name))); + } + + extensionElements.addAll(_exportedNamespace.definedNames.values + .whereType()); + + _extensions = extensionElements + .map((e) => ModelElement.from(e, this, packageGraph) as Extension) + .toList(growable: false) + ..sort(byName); + } return _extensions; } @@ -2628,10 +2637,10 @@ class Library extends ModelElement with Categorization, TopLevelContainer { @override List get exceptions { - return _allClasses - .where((c) => c.isErrorOrException) - .toList(growable: false) - ..sort(byName); + if (_exceptions == null) { + _exceptions = allClasses.where((c) => c.isErrorOrException).toList(growable: false); + } + return _exceptions; } @override @@ -2752,9 +2761,10 @@ class Library extends ModelElement with Categorization, TopLevelContainer { return _typedefs; } - List get _allClasses { + List get allClasses { if (_classes != null) return _classes; + // De-dupe classes coming from multiple exported libraries at once. Set types = Set(); types.addAll(_libraryElement.definingCompilationUnit.types); for (CompilationUnitElement cu in _libraryElement.parts) { @@ -2762,14 +2772,12 @@ class Library extends ModelElement with Categorization, TopLevelContainer { } for (LibraryElement le in _libraryElement.exportedLibraries) { types.addAll(le.definingCompilationUnit.types - .where((t) => _exportedNamespace.definedNames.values.contains(t.name)) - .toList()); + .where((t) => _exportedNamespace.definedNames.values.contains(t.name))); } types.addAll(_exportedNamespace.definedNames.values - .where((e) => e is ClassElement && !e.isMixin) - .cast() - .where((element) => !element.isEnum)); + .whereType() + .where((e) => !e.isMixin && !e.isEnum)); _classes = types .map((e) => ModelElement.from(e, this, packageGraph) as Class) @@ -2783,7 +2791,7 @@ class Library extends ModelElement with Categorization, TopLevelContainer { LibraryElement get _libraryElement => (element as LibraryElement); Class getClassByName(String name) { - return _allClasses.firstWhere((it) => it.name == name, orElse: () => null); + return allClasses.firstWhere((it) => it.name == name, orElse: () => null); } List _getVariables() { @@ -5026,7 +5034,7 @@ class PackageGraph { documentedPackages.toList().forEach((package) { package._libraries.sort((a, b) => compareNatural(a.name, b.name)); package._libraries.forEach((library) { - library._allClasses.forEach(_addToImplementors); + library.allClasses.forEach(_addToImplementors); }); }); _implementors.values.forEach((l) => l.sort()); diff --git a/test/model_test.dart b/test/model_test.dart index e441d08283..c2a076e218 100644 --- a/test/model_test.dart +++ b/test/model_test.dart @@ -2114,11 +2114,20 @@ void main() { group('Extension', () { Extension ext, fancyList; + Extension documentOnceReexportOne, documentOnceReexportTwo; + Library reexportOneLib, reexportTwoLib; Class extensionReferencer; Method doSomeStuff, doStuff, s; List extensions; setUpAll(() { + reexportOneLib = packageGraph.libraries + .firstWhere((lib) => lib.name == 'reexport_one'); + reexportTwoLib = packageGraph.libraries + .firstWhere((lib) => lib.name == 'reexport_two'); + documentOnceReexportOne = reexportOneLib.extensions.firstWhere((e) => e.name == 'DocumentThisExtensionOnce'); + documentOnceReexportTwo = reexportTwoLib.extensions.firstWhere((e) => e.name == 'DocumentThisExtensionOnce'); + ext = exLibrary.extensions.firstWhere((e) => e.name == 'AppleExtension'); extensionReferencer = exLibrary.classes.firstWhere((c) => c.name == 'ExtensionReferencer'); fancyList = exLibrary.extensions.firstWhere((e) => e.name == 'FancyList'); @@ -2129,6 +2138,12 @@ void main() { extensions = exLibrary.publicExtensions.toList(); }); + test('basic canonicalization for extensions', () { + expect(documentOnceReexportOne.isCanonical, isFalse); + expect(documentOnceReexportOne.href, equals(documentOnceReexportTwo.href)); + expect(documentOnceReexportTwo.isCanonical, isTrue); + }); + // TODO(jcollins-g): implement feature and update tests test('documentation links do not crash in base cases', () { packageGraph.packageWarningCounter.hasWarning(doStuff, PackageWarning.notImplemented, @@ -2143,6 +2158,7 @@ void main() { expect(extensionReferencer.documentationAsHtml, contains('_Shhh')); expect(extensionReferencer.documentationAsHtml, contains('FancyList')); expect(extensionReferencer.documentationAsHtml, contains('AnExtension.call')); + expect(extensionReferencer.documentationAsHtml, contains('DocumentThisExtensionOnce')); }); test('has a fully qualified name', () { diff --git a/testing/test_package/lib/example.dart b/testing/test_package/lib/example.dart index 8b233189ca..b1874b67ac 100644 --- a/testing/test_package/lib/example.dart +++ b/testing/test_package/lib/example.dart @@ -677,4 +677,5 @@ extension on Object { /// This class has nothing to do with [_Shhh], [FancyList], or [AnExtension.call], /// but should not crash because we referenced them. +/// We should be able to find [DocumentThisExtensionOnce], too. class ExtensionReferencer {} \ No newline at end of file diff --git a/testing/test_package/lib/reexport_two.dart b/testing/test_package/lib/reexport_two.dart index e3fddc2419..e922e8aed6 100644 --- a/testing/test_package/lib/reexport_two.dart +++ b/testing/test_package/lib/reexport_two.dart @@ -1,6 +1,7 @@ /// {@canonicalFor reexport.somelib.SomeClass} /// {@canonicalFor reexport.somelib.AUnicornClass} /// {@canonicalFor something.ThatDoesntExist} +/// {@canonicalFor reexport.somelib.DocumentThisExtensionOnce} /// {@category Unreal} library reexport_two; diff --git a/testing/test_package/lib/src/somelib.dart b/testing/test_package/lib/src/somelib.dart index c5243040c8..075745e6f5 100644 --- a/testing/test_package/lib/src/somelib.dart +++ b/testing/test_package/lib/src/somelib.dart @@ -7,3 +7,19 @@ class SomeOtherClass {} class YetAnotherClass {} class AUnicornClass {} + + +/// A private extension. +extension _Unseen on Object { + void doYouSeeMe() { } +} + +/// An extension without a name +extension on List { + void somethingNew() { } +} + +/// [_Unseen] is not seen, but [DocumentMe] is. +extension DocumentThisExtensionOnce on String { + String get reportOnString => '$this is wonderful'; +} \ No newline at end of file