Skip to content

Commit 90e6ada

Browse files
committed
Merge branch 'master' into fix/canonical-score-lints
2 parents 459b56f + 174a198 commit 90e6ada

File tree

8 files changed

+125
-12
lines changed

8 files changed

+125
-12
lines changed

lib/src/model/class.dart

Lines changed: 42 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ class Class extends Container
4141
'Dartdoc 1.0.0')
4242
set supertype(DefinedElementType value) => _supertype = value;
4343

44-
final List<DefinedElementType> _interfaces;
44+
final List<DefinedElementType> _directInterfaces;
4545

4646
Class(ClassElement element, Library library, PackageGraph packageGraph)
4747
: _mixedInTypes = element.mixins
@@ -52,7 +52,7 @@ class Class extends Container
5252
_supertype = element.supertype?.element?.supertype == null
5353
? null
5454
: ElementType.from(element.supertype, library, packageGraph),
55-
_interfaces = element.interfaces
55+
_directInterfaces = element.interfaces
5656
.map<DefinedElementType>(
5757
(f) => ElementType.from(f, library, packageGraph))
5858
.toList(growable: false),
@@ -258,10 +258,46 @@ class Class extends Container
258258
Iterable<Field> get publicInheritedFields =>
259259
model_utils.filterNonPublic(inheritedFields);
260260

261-
List<DefinedElementType> get interfaces => _interfaces;
262-
263-
Iterable<DefinedElementType> get publicInterfaces =>
264-
model_utils.filterNonPublic(interfaces);
261+
/// Interfaces directly implemented by this class.
262+
List<DefinedElementType> get interfaces => _directInterfaces;
263+
264+
/// The public interfaces may include substitutions for intermediate
265+
/// private interfaces, and so unlike other public* methods, is not
266+
/// a strict subset of [interfaces].
267+
Iterable<DefinedElementType> get publicInterfaces sync* {
268+
for (var i in _directInterfaces) {
269+
/// Do not recurse if we can find an element here.
270+
if (i.element.canonicalModelElement != null) {
271+
yield i;
272+
continue;
273+
}
274+
// Public types used to be unconditionally exposed here. However,
275+
// if the packages are [DocumentLocation.missing] we generally treat types
276+
// defined in them as actually defined in a documented package.
277+
// That translates to them being defined here, but in 'src/' or similar,
278+
// and so, are now always hidden.
279+
280+
// This type is not backed by a canonical Class; search
281+
// the superchain and publicInterfaces of this interface to pretend
282+
// as though the hidden class didn't exist and this class was declared
283+
// directly referencing the canonical classes further up the chain.
284+
if (i.element is Class) {
285+
var hiddenClass = i.element as Class;
286+
if (hiddenClass.publicSuperChain.isNotEmpty) {
287+
yield hiddenClass.publicSuperChain.first;
288+
}
289+
yield* hiddenClass.publicInterfaces;
290+
} else {
291+
assert(
292+
false,
293+
'Can not handle intermediate non-public interfaces '
294+
'created by ModelElements that are not classes or mixins: '
295+
'${fullyQualifiedName} contains an interface {$i}, '
296+
'defined by ${i.element}');
297+
continue;
298+
}
299+
}
300+
}
265301

266302
bool get isAbstract => element.isAbstract;
267303

lib/src/model/package_graph.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -634,6 +634,9 @@ class PackageGraph {
634634
for (var type in clazz.interfaces) {
635635
checkAndAddClass(type.element, clazz);
636636
}
637+
for (var type in class_.publicInterfaces) {
638+
checkAndAddClass(type.element, class_);
639+
}
637640
}
638641

639642
classes.forEach(addImplementor);

lib/src/mustachio/renderer_base.dart

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -136,12 +136,18 @@ abstract class RendererBase<T> {
136136
/// The renderer of the parent context, if any, otherwise `null`.
137137
final RendererBase parent;
138138

139-
final Template template;
139+
/// The current template being rendered.
140+
///
141+
/// When rendering a partial, the [context] object, and hence the
142+
/// [RendererBase] does not change, only this current template.
143+
Template _template;
140144

141145
/// The output buffer into which [context] is rendered, using a template.
142146
final buffer = StringBuffer();
143147

144-
RendererBase(this.context, this.parent, this.template);
148+
RendererBase(this.context, this.parent, this._template);
149+
150+
Template get template => _template;
145151

146152
void write(String text) => buffer.write(text);
147153

@@ -245,7 +251,11 @@ abstract class RendererBase<T> {
245251
void partial(Partial node) {
246252
var key = node.key;
247253
var partialFile = template.partials[key];
248-
renderBlock(template.partialTemplates[partialFile].ast);
254+
var partialTemplate = template.partialTemplates[partialFile];
255+
var outerTemplate = _template;
256+
_template = partialTemplate;
257+
renderBlock(partialTemplate.ast);
258+
_template = outerTemplate;
249259
}
250260
}
251261

test/end2end/model_test.dart

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1313,6 +1313,30 @@ void main() {
13131313
});
13141314

13151315
group('Class edge cases', () {
1316+
// This is distinct from inheritance in the language and the analyzer
1317+
// implementation.
1318+
test(
1319+
'Implementor chain is correctly rewritten through intermediate private classes',
1320+
() {
1321+
var implementorsLibrary = packageGraph.publicLibraries
1322+
.firstWhere((l) => l.name == 'implementors');
1323+
var ImplementerOfDeclaredPrivateClasses = implementorsLibrary.classes
1324+
.firstWhere((c) => c.name == 'ImplementerOfDeclaredPrivateClasses');
1325+
var ImplementerOfThings = implementorsLibrary.classes
1326+
.firstWhere((c) => c.name == 'ImplementerOfThings');
1327+
var ImplementBase = implementorsLibrary.classes
1328+
.firstWhere((c) => c.name == 'ImplementBase');
1329+
1330+
expect(ImplementerOfThings.publicInterfaces.first.element,
1331+
equals(ImplementBase));
1332+
expect(ImplementerOfDeclaredPrivateClasses.publicInterfaces.first.element,
1333+
equals(ImplementBase));
1334+
1335+
expect(ImplementBase.publicImplementors,
1336+
contains(ImplementerOfDeclaredPrivateClasses));
1337+
expect(ImplementBase.publicImplementors, contains(ImplementerOfThings));
1338+
});
1339+
13161340
test('Overrides from intermediate abstract classes are picked up correctly',
13171341
() {
13181342
var IntermediateAbstractSubclass = fakeLibrary.allClasses
@@ -3410,8 +3434,8 @@ String topLevelFunction(int param1, bool param2, Cool coolBeans,
34103434
ImplementsFutureVoid.linkedName,
34113435
equals(
34123436
'<a href="${htmlBasePlaceholder}fake/ImplementsFutureVoid-class.html">ImplementsFutureVoid</a>'));
3413-
var FutureVoid = ImplementsFutureVoid.publicInterfaces
3414-
.firstWhere((c) => c.name == 'Future');
3437+
var FutureVoid =
3438+
ImplementsFutureVoid.interfaces.firstWhere((c) => c.name == 'Future');
34153439
expect(
34163440
FutureVoid.linkedName,
34173441
equals(

test/mustachio/renderer_test.dart

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,20 @@ void main() {
336336
expect(renderBar(bar, barTemplate), equals('Text Partial goodbye'));
337337
});
338338

339+
test('Renderer renders a partial which refers to other partials', () async {
340+
var barTemplateFile = getFile('/project/bar.mustache')
341+
..writeAsStringSync('Text, {{#foo}}{{>foo.mustache}}{{/foo}}');
342+
getFile('/project/foo.mustache')
343+
.writeAsStringSync('p1, {{#l1}}{{>foo_l1.mustache}}{{/l1}}');
344+
getFile('/project/foo_l1.mustache').writeAsStringSync('p2 {{.}}, ');
345+
var barTemplate = await Template.parse(barTemplateFile);
346+
var bar = Bar()
347+
..foo = (Foo()
348+
..s1 = 'hello'
349+
..l1 = [1, 2, 3]);
350+
expect(renderBar(bar, barTemplate), equals('Text, p1, p2 1, p2 2, p2 3, '));
351+
});
352+
339353
test('Renderer renders a partial with a heterogeneous context chain',
340354
() async {
341355
var barTemplateFile = getFile('/project/bar.mustache')

test/src/utils.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import 'package:dartdoc/src/package_meta.dart';
1414
/// The number of public libraries in testing/test_package, minus 2 for
1515
/// the excluded libraries listed in the initializers for _testPackageGraphMemo
1616
/// and minus 1 for the <nodoc> tag in the 'excluded' library.
17-
const int kTestPackagePublicLibraries = 17;
17+
const int kTestPackagePublicLibraries = 18;
1818

1919
final _resourceProvider = pubPackageMetaProvider.resourceProvider;
2020
final _pathContext = _resourceProvider.pathContext;
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
// A library useful to check implementer chain edge cases.
6+
library implementors;
7+
8+
import 'src/intermediate_implements.dart';
9+
10+
abstract class ImplementBase {}
11+
12+
abstract class _APrivateThingToImplement implements ImplementBase {}
13+
14+
abstract class ImplementerOfThings implements IntermediateImplementer {}
15+
16+
abstract class ImplementerOfDeclaredPrivateClasses implements _APrivateThingToImplement {}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
// A src-private library for intermediate classes to implement.
6+
library intermediate_implements;
7+
8+
import 'package:test_package/implementors.dart';
9+
10+
abstract class IntermediateImplementer implements ImplementBase {}

0 commit comments

Comments
 (0)