Prefer package-local canonicalization and warn on reexported private API across packages (#1684)
diff --git a/lib/src/model.dart b/lib/src/model.dart
index 78c5a26..3e4c982 100644
--- a/lib/src/model.dart
+++ b/lib/src/model.dart
@@ -2941,6 +2941,21 @@
if (topLevelElement == lookup) return true;
return false;
}).toList();
+
+ // Avoid claiming canonicalization for elements outside of this element's
+ // defining package.
+ // TODO(jcollins-g): Make the else block unconditional.
+ if (!candidateLibraries.isEmpty &&
+ !candidateLibraries
+ .any((l) => l.package == definingLibrary.package)) {
+ warn(PackageWarning.reexportedPrivateApiAcrossPackages,
+ message: definingLibrary.package.fullyQualifiedName,
+ referredFrom: candidateLibraries);
+ } else {
+ candidateLibraries
+ .removeWhere((l) => l.package != definingLibrary.package);
+ }
+
// Start with our top-level element.
ModelElement warnable =
new ModelElement.fromElement(topLevelElement, packageGraph);
@@ -2972,7 +2987,8 @@
} else {
_canonicalLibrary = definingLibrary;
}
- if (this is Inheritable) {
+ // Only pretend when not linking to remote packages.
+ if (this is Inheritable && !config.linkToRemote) {
if ((this as Inheritable).isInherited &&
_canonicalLibrary == null &&
packageGraph.publicLibraries.contains(library)) {
@@ -4054,8 +4070,9 @@
case PackageWarning.noCanonicalFound:
// Fix these warnings by adding libraries with --include, or by using
// --auto-include-dependencies.
- // TODO(jcollins-g): add a dartdoc flag to enable external website linking for non-canonical elements, using .packages for versioning
- // TODO(jcollins-g): support documenting multiple packages at once and linking between them
+ // TODO(jcollins-g): pipeline references through linkedName for error
+ // messages and warn for non-public canonicalization
+ // errors.
warningMessage =
"no canonical library found for ${warnableName}, not linking";
break;
@@ -4081,6 +4098,10 @@
warningMessage =
"--package-order gives invalid package name: '${message}'";
break;
+ case PackageWarning.reexportedPrivateApiAcrossPackages:
+ warningMessage =
+ "private API of ${message} is reexported by libraries in other packages: ";
+ break;
case PackageWarning.unresolvedDocReference:
warningMessage = "unresolved doc reference [${message}]";
if (referredFrom == null) {
diff --git a/lib/src/warnings.dart b/lib/src/warnings.dart
index 58589ed..2e89a2b 100644
--- a/lib/src/warnings.dart
+++ b/lib/src/warnings.dart
@@ -54,6 +54,10 @@
PackageWarning.packageOrderGivesMissingPackageName,
"category-order-gives-missing-package-name",
"The category-order flag on the command line was given the name of a nonexistent package"),
+ PackageWarning.reexportedPrivateApiAcrossPackages: const PackageWarningHelpText(
+ PackageWarning.reexportedPrivateApiAcrossPackages,
+ "reexported-private-api-across-packages",
+ "One or more libraries reexports private API members from outside its own package"),
PackageWarning.unresolvedDocReference: const PackageWarningHelpText(
PackageWarning.unresolvedDocReference,
"unresolved-doc-reference",
@@ -113,6 +117,7 @@
noCanonicalFound,
noLibraryLevelDocs,
packageOrderGivesMissingPackageName,
+ reexportedPrivateApiAcrossPackages,
unresolvedDocReference,
unknownMacro,
brokenLink,
diff --git a/tool/grind.dart b/tool/grind.dart
index 195c58c..abec203 100644
--- a/tool/grind.dart
+++ b/tool/grind.dart
@@ -33,6 +33,10 @@
}
}
+/// The pub cache inherited by grinder.
+final String defaultPubCache =
+ Platform.environment['PUB_CACHE'] ?? resolveTildePath('~/.pub-cache');
+
/// Run no more than the number of processors available in parallel.
final MultiFutureTracker testFutures = new MultiFutureTracker(
Platform.environment.containsKey('TRAVIS')
@@ -183,11 +187,11 @@
class WarningsCollection {
final String tempDir;
- final Map<String, int> _warningKeyCounts;
+ final Map<String, int> warningKeyCounts;
final String branch;
final String pubCachePath;
WarningsCollection(this.tempDir, this.pubCachePath, this.branch)
- : this._warningKeyCounts = new Map() {}
+ : this.warningKeyCounts = new Map() {}
static const String kPubCachePathReplacement = '_xxxPubDirectoryxxx_';
static const String kTempDirReplacement = '_xxxTempDirectoryxxx_';
@@ -208,8 +212,8 @@
void add(String text) {
String key = _toKey(text);
- _warningKeyCounts.putIfAbsent(key, () => 0);
- _warningKeyCounts[key]++;
+ warningKeyCounts.putIfAbsent(key, () => 0);
+ warningKeyCounts[key]++;
}
/// Output formatter for comparing warnings. [this] is the original.
@@ -220,19 +224,19 @@
Set<String> onlyCurrent = new Set();
Set<String> identical = new Set();
Set<String> allKeys = new Set.from([]
- ..addAll(_warningKeyCounts.keys)
- ..addAll(current._warningKeyCounts.keys));
+ ..addAll(warningKeyCounts.keys)
+ ..addAll(current.warningKeyCounts.keys));
for (String key in allKeys) {
- if (_warningKeyCounts.containsKey(key) &&
- !current._warningKeyCounts.containsKey(key)) {
+ if (warningKeyCounts.containsKey(key) &&
+ !current.warningKeyCounts.containsKey(key)) {
onlyOriginal.add(key);
- } else if (!_warningKeyCounts.containsKey(key) &&
- current._warningKeyCounts.containsKey(key)) {
+ } else if (!warningKeyCounts.containsKey(key) &&
+ current.warningKeyCounts.containsKey(key)) {
onlyCurrent.add(key);
- } else if (_warningKeyCounts.containsKey(key) &&
- current._warningKeyCounts.containsKey(key) &&
- _warningKeyCounts[key] != current._warningKeyCounts[key]) {
+ } else if (warningKeyCounts.containsKey(key) &&
+ current.warningKeyCounts.containsKey(key) &&
+ warningKeyCounts[key] != current.warningKeyCounts[key]) {
quantityChangedOuts.add(key);
} else {
identical.add(key);
@@ -253,7 +257,7 @@
printBuffer.writeln('*** $title : Identical warning quantity changed');
for (String key in quantityChangedOuts) {
printBuffer.writeln(
- "* Appeared ${_warningKeyCounts[key]} times in $branch, ${current._warningKeyCounts[key]} in ${current.branch}:");
+ "* Appeared ${warningKeyCounts[key]} times in $branch, ${current.warningKeyCounts[key]} in ${current.branch}:");
printBuffer.writeln(current._fromKey(key));
}
}
@@ -813,26 +817,38 @@
pluginPackageDocsDir.path, 8005, 'serve-dartdoc-flutter-plugin-docs');
}
-@Task('Build docs for a package that requires flutter with remote linking')
-buildDartdocFlutterPluginDocs() async {
+Future<WarningsCollection> _buildDartdocFlutterPluginDocs() async {
FlutterRepo flutterRepo = await FlutterRepo.fromExistingFlutterRepo(
await cleanFlutterRepo, 'docs-flutter-plugin');
- await flutterRepo.launcher.runStreamed(
- Platform.resolvedExecutable,
- [
- '--checked',
- pathLib.join(Directory.current.path, 'bin', 'dartdoc.dart'),
- '--link-to-remote',
- '--output',
- pluginPackageDocsDir.path
- ],
- workingDirectory: pluginPackage.path);
+ return jsonMessageIterableToWarnings(
+ await flutterRepo.launcher.runStreamed(
+ Platform.resolvedExecutable,
+ [
+ '--checked',
+ pathLib.join(Directory.current.path, 'bin', 'dartdoc.dart'),
+ '--json',
+ '--link-to-remote',
+ '--output',
+ pluginPackageDocsDir.path
+ ],
+ workingDirectory: pluginPackage.path),
+ pluginPackageDocsDir.path,
+ defaultPubCache,
+ 'HEAD');
+}
+
+@Task('Build docs for a package that requires flutter with remote linking')
+buildDartdocFlutterPluginDocs() async {
+ await _buildDartdocFlutterPluginDocs();
}
@Task('Verify docs for a package that requires flutter with remote linking')
-@Depends(buildDartdocFlutterPluginDocs)
testDartdocFlutterPlugin() async {
+ WarningsCollection warnings = await _buildDartdocFlutterPluginDocs();
+ if (!warnings.warningKeyCounts.isEmpty) {
+ fail('No warnings should exist in : ${warnings.warningKeyCounts}');
+ }
// Verify that links to Dart SDK and Flutter SDK go to the flutter site.
expectFileContains(
pathLib.join(