Skip to content
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
58 changes: 36 additions & 22 deletions lib/src/model/inheriting_container.dart
Original file line number Diff line number Diff line change
Expand Up @@ -453,38 +453,52 @@ abstract class InheritingContainer extends Container {
}
}

for (var interface in directInterfaces) {
var interfaceElement = interface.modelElement;
void addFromSupertype(DefinedElementType supertype,
{required bool addSupertypes}) {
var superElement = supertype.modelElement;

/// Do not recurse if we can find an element here.
if (interfaceElement.canonicalModelElement != null) {
addInterfaceIfUnique(interface);
continue;
if (superElement.canonicalModelElement != null) {
if (addSupertypes) addInterfaceIfUnique(supertype);
return;
}
// Public types used to be unconditionally exposed here. However,
// if the packages are [DocumentLocation.missing] we generally treat types
// defined in them as actually defined in a documented package.
// That translates to them being defined here, but in 'src/' or similar,
// and so, are now always hidden.

// This type is not backed by a canonical Class; search
// the superchain and publicInterfaces of this interface to pretend
// as though the hidden class didn't exist and this class was declared
// directly referencing the canonical classes further up the chain.
if (interfaceElement is! InheritingContainer) {

// This type is not backed by a canonical Class; it is not documented.
// Search it's `superChain` and `publicInterfaces` to pretend that `this`
// container directly implements canonical classes further up the chain.

if (superElement is! InheritingContainer) {
assert(
false,
'Can not handle intermediate non-public interfaces created by '
'Cannot handle intermediate non-public interfaces created by '
"ModelElements that are not classes or mixins: '$fullyQualifiedName' "
"contains an interface '$interface', defined by '$interfaceElement'",
"contains a supertype '$supertype', defined by '$superElement'",
);
continue;
return;
}
var publicSuperChain = interfaceElement.superChain.wherePublic;
if (publicSuperChain.isNotEmpty) {
var publicSuperChain = superElement.superChain.wherePublic;
if (publicSuperChain.isNotEmpty && addSupertypes) {
addInterfaceIfUnique(publicSuperChain.first);
}
interfaceElement.publicInterfaces.forEach(addInterfaceIfUnique);
superElement.publicInterfaces.forEach(addInterfaceIfUnique);
}

for (var interface in directInterfaces) {
addFromSupertype(interface, addSupertypes: true);
}
for (var supertype in superChain) {
var interfaceElement = supertype.modelElement;

// Do not recurse if we can find an element here.
if (interfaceElement.canonicalModelElement != null) {
continue;
}
addFromSupertype(supertype, addSupertypes: false);
}
if (this case Class(:var mixedInTypes) || Enum(:var mixedInTypes)) {
for (var mixin in mixedInTypes) {
addFromSupertype(mixin, addSupertypes: false);
}
}
return interfaces;
}
Expand Down
77 changes: 69 additions & 8 deletions test/classes_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ class C {}
extension Ex on C {}
''');

var f = library.classes.named('C');
var c = library.classes.named('C');
expect(
f.potentiallyApplicableExtensionsSorted,
c.potentiallyApplicableExtensionsSorted,
contains(library.extensions.named('Ex')),
);
}
Expand All @@ -38,9 +38,9 @@ class C<T> {}
extension Ex on C<int> {}
''');

var f = library.classes.named('C');
var c = library.classes.named('C');
expect(
f.potentiallyApplicableExtensionsSorted,
c.potentiallyApplicableExtensionsSorted,
contains(library.extensions.named('Ex')),
);
}
Expand All @@ -52,9 +52,9 @@ class D extends C {}
extension Ex on C {}
''');

var f = library.classes.named('D');
var d = library.classes.named('D');
expect(
f.potentiallyApplicableExtensionsSorted,
d.potentiallyApplicableExtensionsSorted,
contains(library.extensions.named('Ex')),
);
}
Expand All @@ -66,9 +66,9 @@ class D<T> extends C<T> {}
extension Ex on C<int> {}
''');

var f = library.classes.named('D');
var d = library.classes.named('D');
expect(
f.potentiallyApplicableExtensionsSorted,
d.potentiallyApplicableExtensionsSorted,
contains(library.extensions.named('Ex')),
);
}
Expand Down Expand Up @@ -109,6 +109,67 @@ class C implements _B {}
expect(c.publicInterfaces.first.modelElement, library.classes.named('A'));
}

void test_publicInterfaces_indirectViaPrivate2() async {
var library = await bootPackageWithLibrary('''
class A {}
class _B implements A {}
class _C implements _B {}
class D implements _C {}
''');

var d = library.classes.named('D');
expect(d.publicInterfaces, hasLength(1));
expect(d.publicInterfaces.first.modelElement, library.classes.named('A'));
}

void test_publicInterfaces_indirectViaPrivateExtendedClass() async {
var library = await bootPackageWithLibrary('''
class A {}
class _B implements A {}
class C extends _B {}
''');

var c = library.classes.named('C');
expect(c.publicInterfaces, hasLength(1));
expect(c.publicInterfaces.first.modelElement, library.classes.named('A'));
}

void test_publicInterfaces_indirectViaPrivateExtendedClass2() async {
var library = await bootPackageWithLibrary('''
class A {}
class _B implements A {}
class _C extends _B {}
class D extends _C {}
''');

var c = library.classes.named('D');
expect(c.publicInterfaces, hasLength(1));
expect(c.publicInterfaces.first.modelElement, library.classes.named('A'));
}

void test_publicInterfaces_onlyExtendedClasses() async {
var library = await bootPackageWithLibrary('''
class A {}
class B extends A {}
class _C extends B {}
class D extends _C {}
''');

expect(library.classes.named('D').publicInterfaces, isEmpty);
}

void test_publicInterfaces_indirectViaPrivateMixedInMixin() async {
var library = await bootPackageWithLibrary('''
class A {}
mixin _M implements A {}
class C with _M {}
''');

var c = library.classes.named('C');
expect(c.publicInterfaces, hasLength(1));
expect(c.publicInterfaces.first.modelElement, library.classes.named('A'));
}

void test_publicInterfaces_indirectViaPrivate_multiply() async {
var library = await bootPackageWithLibrary('''
class A<T> {}
Expand Down
33 changes: 16 additions & 17 deletions test/src/utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -322,35 +322,34 @@ MatchingLinkResult referenceLookup(Warnable element, String codeRef) =>

/// Returns a matcher which compresses consecutive whitespace in [text] into a
/// single space.
Matcher matchesCompressed(String text) => matches(RegExp(text.replaceAll(
RegExp(r'\s\s+', multiLine: true),
' *',
)));

/// We can not use [ExperimentalFeature.releaseVersion] or even
/// [ExperimentalFeature.experimentalReleaseVersion] as these are set to `null`
/// even when partial analyzer implementations are available.
Matcher matchesCompressed(String text) => matches(RegExp(
text.replaceAll(RegExp(r'\s\s+', multiLine: true), r'\s*'),
));

// We can not use `ExperimentalFeature.releaseVersion` or even
// `ExperimentalFeature.experimentalReleaseVersion` as these are set to `null`
// even when partial analyzer implementations are available.
bool get namedArgumentsAnywhereAllowed =>
VersionRange(min: Version.parse('2.17.0-0'), includeMin: true)
.allows(platformVersion);

/// We can not use [ExperimentalFeature.releaseVersion] or even
/// [ExperimentalFeature.experimentalReleaseVersion] as these are set to `null`
/// even when partial analyzer implementations are available.
// We can not use `ExperimentalFeature.releaseVersion` or even
// `ExperimentalFeature.experimentalReleaseVersion` as these are set to `null`
// even when partial analyzer implementations are available.
bool get recordsAllowed =>
VersionRange(min: Version.parse('2.19.0-0'), includeMin: true)
.allows(platformVersion);

/// We can not use [ExperimentalFeature.releaseVersion] or even
/// [ExperimentalFeature.experimentalReleaseVersion] as these are set to `null`
/// even when partial analyzer implementations are available.
// We can not use `ExperimentalFeature.releaseVersion` or even
// `ExperimentalFeature.experimentalReleaseVersion` as these are set to `null`
// even when partial analyzer implementations are available.
bool get extensionTypesAllowed =>
VersionRange(min: Version.parse('3.2.0-0.0-dev'), includeMin: true)
.allows(platformVersion);

/// We can not use [ExperimentalFeature.releaseVersion] or even
/// [ExperimentalFeature.experimentalReleaseVersion] as these are set to `null`
/// even when partial analyzer implementations are available.
// We can not use `ExperimentalFeature.releaseVersion` or even
// `ExperimentalFeature.experimentalReleaseVersion` as these are set to `null`
// even when partial analyzer implementations are available.
bool get classModifiersAllowed =>
VersionRange(min: Version.parse('3.0.0-0.0-dev'), includeMin: true)
.allows(platformVersion);
Expand Down
51 changes: 39 additions & 12 deletions test/templates/class_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,36 @@ class Foo implements _Foo {}
''');
var baseLines = readLines(['lib', 'Base-class.html']);

baseLines.expectMainContentContainsAllInOrder([
matches('<dt>Implementers</dt>'),
matches('<dd><ul class="comma-separated clazz-relationships">'),
matches('<li><a href="../lib/Foo-class.html">Foo</a></li>'),
matches('</ul></dd>'),
]);
expect(
baseLines.join('\n'),
matchesCompressed('''
<dt>Implementers</dt>
<dd><ul class="comma-separated clazz-relationships">
<li><a href="../lib/Foo-class.html">Foo</a></li>
</ul></dd>
'''),
);
}

void test_implementers_class_extends2() async {
await createPackageWithLibrary('''
abstract class A {}
abstract class B extends A {}
class _C extends B {}
class D extends _C {}
''');
var baseLines = readLines(['lib', 'A-class.html']);

expect(
baseLines.join('\n'),
// D should not be found; it is indirect via B.
matchesCompressed('''
<dt>Implementers</dt>
<dd><ul class="comma-separated clazz-relationships">
<li><a href="../lib/B-class.html">B</a></li>
</ul></dd>
'''),
);
}

void test_implementers_class_implements_withGenericType() async {
Expand All @@ -61,12 +85,15 @@ class Foo<E> implements Base<E> {}
''');
var baseLines = readLines(['lib', 'Base-class.html']);

baseLines.expectMainContentContainsAllInOrder([
matches('<dt>Implementers</dt>'),
matches('<dd><ul class="comma-separated clazz-relationships">'),
matches('<li><a href="../lib/Foo-class.html">Foo</a></li>'),
matches('</ul></dd>'),
]);
expect(
baseLines.join('\n'),
matchesCompressed(r'''
<dt>Implementers</dt>
<dd><ul class="comma-separated clazz-relationships">
<li><a href="../lib/Foo-class.html">Foo</a></li>
</ul></dd>
'''),
);
}

void test_implementers_class_implements_withInstantiatedType() async {
Expand Down