Skip to content

Commit

Permalink
Support for enhanced parts (#3892)
Browse files Browse the repository at this point in the history
There isn't _much_ for dartdoc to be concerned with. Dartdoc basically must be
aware of an exports foud in a part, and there might be reasons to be concerned
with imports coming from a part. In particular, dartdoc used to skip resolving
all part files when searching for libraries, just as an optimization. Can't do
that any more.

This might not be a full implementation of enhanced parts support, but it sets
up the basics.
  • Loading branch information
srawlins authored Sep 30, 2024
1 parent c613f42 commit 76678f6
Show file tree
Hide file tree
Showing 3 changed files with 121 additions and 8 deletions.
30 changes: 23 additions & 7 deletions lib/src/model/package_builder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,9 @@ class PubPackageBuilder implements PackageBuilder {
continue;
}
newFiles.addFilesReferencedBy(resolvedLibrary.element);
for (var unit in resolvedLibrary.units) {
newFiles.addFilesReferencedBy(unit.declaredElement);
}
if (processedLibraries.contains(resolvedLibrary.element)) {
continue;
}
Expand Down Expand Up @@ -548,24 +551,37 @@ class DartDocResolvedLibrary {
extension on Set<String> {
/// Adds [element]'s path and all of its part files' paths to `this`, and
/// recursively adds the paths of all imported and exported libraries.
void addFilesReferencedBy(LibraryOrAugmentationElement? element) {
///
/// [element] must be a [LibraryElement] or [CompilationUnitElement].
void addFilesReferencedBy(Element? element) {
if (element == null) return;

var path = element.source?.fullName;
if (path == null) return;

if (add(path)) {
for (var import in element.libraryImports) {
var libraryImports = switch (element) {
LibraryElement(:var libraryImports) ||
CompilationUnitElement(:var libraryImports) =>
libraryImports,
_ => const <LibraryImportElement>[],
};
for (var import in libraryImports) {
addFilesReferencedBy(import.importedLibrary);
}
for (var export in element.libraryExports) {

var libraryExports = switch (element) {
LibraryElement(:var libraryExports) ||
CompilationUnitElement(:var libraryExports) =>
libraryExports,
_ => const <LibraryExportElement>[],
};
for (var export in libraryExports) {
addFilesReferencedBy(export.exportedLibrary);
}
if (element is LibraryElement) {
for (var part in element.parts
.map((e) => e.uri)
.whereType<DirectiveUriWithUnit>()) {
add(part.source.fullName);
for (var unit in element.units) {
addFilesReferencedBy(unit);
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion test/dartdoc_test_base.dart
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ abstract class DartdocTestBase {

String get sdkConstraint => '>=3.6.0 <4.0.0';

List<String> get experiments => ['wildcard-variables'];
List<String> get experiments => ['enhanced-parts', 'wildcard-variables'];

bool get skipUnreachableSdkLibraries => true;

Expand Down
97 changes: 97 additions & 0 deletions test/packages_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,26 @@
import 'package:analyzer/file_system/file_system.dart';
import 'package:analyzer/file_system/memory_file_system.dart';
import 'package:dartdoc/src/dartdoc_options.dart';
import 'package:dartdoc/src/model/class.dart';
import 'package:dartdoc/src/model/documentable.dart';
import 'package:dartdoc/src/model/kind.dart';
import 'package:dartdoc/src/package_config_provider.dart';
import 'package:dartdoc/src/package_meta.dart';
import 'package:dartdoc/src/special_elements.dart';
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';

import 'dartdoc_test_base.dart';
import 'src/test_descriptor_utils.dart' as d;
import 'src/utils.dart' as utils;

void main() {
defineReflectiveSuite(() {
defineReflectiveTests(PackagesTest);
});

// TODO(srawlins): Migrate to test_reflective_loader tests.

late MemoryResourceProvider resourceProvider;
late PackageMetaProvider packageMetaProvider;
late FakePackageConfigProvider packageConfigProvider;
Expand Down Expand Up @@ -103,6 +113,8 @@ int x;
projectPath, packageMetaProvider, packageConfigProvider);

expect(packageGraph.localPublicLibraries, hasLength(1));
var library = packageGraph.libraries.named('a');
expect(library.isDocumented, true);
});

test('has private libraries', () async {
Expand Down Expand Up @@ -489,3 +501,88 @@ int x;
});
});
}

@reflectiveTest
class PackagesTest extends DartdocTestBase {
@override
String get libraryName => 'lib';

void test_exportedElements() async {
var graph = await bootPackageFromFiles(
[
d.dir('lib', [
d.file('lib.dart', "export 'src/impl.dart';"),
d.dir('src', [
d.file('impl.dart', 'class C {}'),
]),
]),
],
);
var library = graph.libraries.named('lib');
expect(library.classes.single.name, 'C');
}

void test_exportedElements_indirectlyExported() async {
var graph = await bootPackageFromFiles(
[
d.dir('lib', [
d.file('lib.dart', "export 'src/impl.dart';"),
d.dir('src', [
d.file('impl.dart', "export 'impl2.dart';"),
d.file('impl2.dart', 'class C {}'),
]),
]),
],
);
var library = graph.libraries.named('lib');
expect(library.classes.single.name, 'C');
}

void test_exportedElements_fromPart() async {
var graph = await bootPackageFromFiles(
[
d.dir('lib', [
d.file('lib.dart', "part 'part.dart';"),
d.file('part.dart', '''
part of 'lib.dart';
export 'src/impl.dart';
'''),
d.dir('src', [
d.file('impl.dart', 'class C {}'),
]),
]),
],
);
var library = graph.libraries.named('lib');
expect(library.qualifiedName, 'lib');
expect(library.classes, isNotEmpty);
expect(library.classes.single,
isA<Class>().having((c) => c.name, 'name', 'C'));
}

void test_exportedElements_fromPartOfPart() async {
var graph = await bootPackageFromFiles(
[
d.dir('lib', [
d.file('lib.dart', "part 'part1.dart';"),
d.file('part1.dart', '''
part of 'lib.dart';
part 'part2.dart';
'''),
d.file('part2.dart', '''
part of 'part1.dart';
export 'src/impl.dart';
'''),
d.dir('src', [
d.file('impl.dart', 'class C {}'),
]),
]),
],
);
var library = graph.libraries.named('lib');
expect(library.qualifiedName, 'lib');
expect(library.classes, isNotEmpty);
expect(library.classes.single,
isA<Class>().having((c) => c.name, 'name', 'C'));
}
}

0 comments on commit 76678f6

Please sign in to comment.