Use PackageWarning system for multiple file overwrite errors (#2111)
* Do not assert on multiple file crashes, use PackageWarning error instead.
* Clean up error messages
diff --git a/lib/dartdoc.dart b/lib/dartdoc.dart
index 0a8e252..2c10570 100644
--- a/lib/dartdoc.dart
+++ b/lib/dartdoc.dart
@@ -105,7 +105,7 @@
for (var generator in generators) {
await generator.generate(packageGraph, outputDir.path);
- writtenFiles.addAll(generator.writtenFiles.map(path.normalize));
+ writtenFiles.addAll(generator.writtenFiles.keys.map(path.normalize));
}
if (config.validateLinks && writtenFiles.isNotEmpty) {
validateLinks(packageGraph, outputDir.path);
@@ -140,7 +140,7 @@
dartdocResults.packageGraph.packageWarningCounter.errorCount;
if (errorCount > 0) {
throw DartdocFailure(
- "dartdoc encountered $errorCount} errors while processing.");
+ "dartdoc encountered $errorCount errors while processing.");
}
logInfo(
'Success! Docs generated into ${dartdocResults.outDir.absolute.path}');
diff --git a/lib/src/empty_generator.dart b/lib/src/empty_generator.dart
index 016f2d1..d879574 100644
--- a/lib/src/empty_generator.dart
+++ b/lib/src/empty_generator.dart
@@ -5,9 +5,10 @@
import 'package:dartdoc/src/generator.dart';
import 'package:dartdoc/src/model/model.dart';
import 'package:dartdoc/src/model_utils.dart';
+import 'package:dartdoc/src/warnings.dart';
/// A generator that does not generate files, but does traverse the [PackageGraph]
-/// and access [ModelElement.documetationAsHtml] for every element as though
+/// and access [ModelElement.documentationAsHtml] for every element as though
/// it were.
class EmptyGenerator extends Generator {
@override
@@ -36,5 +37,5 @@
Stream<void> get onFileCreated => _onFileCreated.stream;
@override
- Set<String> get writtenFiles => Set();
+ final Map<String, Warnable> writtenFiles = {};
}
diff --git a/lib/src/generator.dart b/lib/src/generator.dart
index 758659a..d062ae9 100644
--- a/lib/src/generator.dart
+++ b/lib/src/generator.dart
@@ -8,6 +8,7 @@
import 'dart:async' show Stream, Future;
import 'package:dartdoc/src/model/model.dart' show PackageGraph;
+import 'package:dartdoc/src/warnings.dart';
/// An abstract class that defines a generator that generates documentation for
/// a given package.
@@ -22,5 +23,5 @@
Stream<void> get onFileCreated;
/// Fetches all filenames written by this generator.
- Set<String> get writtenFiles;
+ Map<String, Warnable> get writtenFiles;
}
diff --git a/lib/src/html/html_generator.dart b/lib/src/html/html_generator.dart
index f346499..ca9b0c6 100644
--- a/lib/src/html/html_generator.dart
+++ b/lib/src/html/html_generator.dart
@@ -15,6 +15,7 @@
import 'package:dartdoc/src/html/template_data.dart';
import 'package:dartdoc/src/html/templates.dart';
import 'package:dartdoc/src/model/model.dart';
+import 'package:dartdoc/src/warnings.dart';
import 'package:path/path.dart' as path;
typedef Renderer = String Function(String input);
@@ -48,7 +49,7 @@
Stream<void> get onFileCreated => _onFileCreated.stream;
@override
- final Set<String> writtenFiles = Set<String>();
+ final Map<String, Warnable> writtenFiles = {};
static Future<HtmlGenerator> create(
{HtmlGeneratorOptions options,
@@ -83,14 +84,23 @@
assert(_instance == null);
var enabled = true;
- void write(String filePath, Object content, {bool allowOverwrite}) {
+ void write(String filePath, Object content,
+ {bool allowOverwrite, Warnable element}) {
allowOverwrite ??= false;
if (!enabled) {
throw StateError('`write` was called after `generate` completed.');
}
- // If you see this assert, we're probably being called to build non-canonical
- // docs somehow. Check data.self.isCanonical and callers for bugs.
- assert(allowOverwrite || !writtenFiles.contains(filePath));
+ if (!allowOverwrite) {
+ if (writtenFiles.containsKey(filePath)) {
+ assert(element != null,
+ 'Attempted overwrite of ${filePath} without corresponding element');
+ Warnable originalElement = writtenFiles[filePath];
+ Iterable<Warnable> referredFrom =
+ originalElement != null ? [originalElement] : null;
+ element?.warn(PackageWarning.duplicateFile,
+ message: filePath, referredFrom: referredFrom);
+ }
+ }
var file = File(path.join(outputDirectoryPath, filePath));
var parent = file.parent;
@@ -107,7 +117,7 @@
content, 'content', '`content` must be `String` or `List<int>`.');
}
_onFileCreated.add(file);
- writtenFiles.add(filePath);
+ writtenFiles[filePath] = element;
}
try {
diff --git a/lib/src/html/html_generator_instance.dart b/lib/src/html/html_generator_instance.dart
index b3adb6c..0ac5d7f 100644
--- a/lib/src/html/html_generator_instance.dart
+++ b/lib/src/html/html_generator_instance.dart
@@ -21,7 +21,7 @@
import 'package:path/path.dart' as path;
typedef FileWriter = void Function(String path, Object content,
- {bool allowOverwrite});
+ {bool allowOverwrite, Warnable element});
class HtmlGeneratorInstance {
final HtmlGeneratorOptions _options;
@@ -412,8 +412,8 @@
// Replaces '/' separators with proper separators for the platform.
String outFile = path.joinAll(filename.split('/'));
String content = template.renderString(data);
-
- _writer(outFile, content);
+ _writer(outFile, content,
+ element: data.self is Warnable ? data.self : null);
if (data.self is Indexable) _indexedElements.add(data.self as Indexable);
}
}
diff --git a/lib/src/model/package_graph.dart b/lib/src/model/package_graph.dart
index 1025e27..83043bb 100644
--- a/lib/src/model/package_graph.dart
+++ b/lib/src/model/package_graph.dart
@@ -405,6 +405,11 @@
case PackageWarning.unresolvedExport:
warningMessage = 'unresolved export uri: ${message}';
break;
+ case PackageWarning.duplicateFile:
+ warningMessage = 'failed to write file at: ${message}';
+ warnablePrefix = 'for symbol';
+ referredFromPrefix = 'conflicting with file already generated by';
+ break;
}
List<String> messageParts = [warningMessage];
diff --git a/lib/src/warnings.dart b/lib/src/warnings.dart
index 2d59d7b..e1941fb 100644
--- a/lib/src/warnings.dart
+++ b/lib/src/warnings.dart
@@ -193,6 +193,19 @@
"unresolved-export",
"An export refers to a URI that cannot be resolved.",
defaultWarningMode: PackageWarningMode.error),
+ PackageWarning.duplicateFile: PackageWarningDefinition(
+ PackageWarning.duplicateFile,
+ "duplicate-file",
+ "Dartdoc is trying to write to a duplicate filename based on the names of Dart symbols.",
+ longHelp: [
+ "Dartdoc generates a path and filename to write to for each symbol.",
+ "@@name@@ conflicts with another symbol in the generated path, and",
+ "therefore can not be written out. Changing the name, library name, or",
+ "class name (if appropriate) of one of the conflicting items can resolve",
+ "the conflict. Alternatively, use the @nodoc tag in one symbol's",
+ "documentation comments to hide it."
+ ],
+ defaultWarningMode: PackageWarningMode.error),
};
/// Something that package warnings can be called on. Optionally associated
@@ -222,6 +235,7 @@
unknownMacro,
unknownHtmlFragment,
brokenLink,
+ duplicateFile,
orphanedFile,
unknownFile,
missingFromSearchIndex,
diff --git a/test/html_generator_test.dart b/test/html_generator_test.dart
index cda62d7..6cfdee0 100644
--- a/test/html_generator_test.dart
+++ b/test/html_generator_test.dart
@@ -9,9 +9,13 @@
import 'package:dartdoc/src/html/html_generator.dart';
import 'package:dartdoc/src/html/templates.dart';
import 'package:dartdoc/src/html/resources.g.dart';
+import 'package:dartdoc/src/model/package_graph.dart';
+import 'package:dartdoc/src/warnings.dart';
import 'package:path/path.dart' as path;
import 'package:test/test.dart';
+import 'src/utils.dart' as utils;
+
void main() {
group('Templates', () {
Templates templates;
@@ -90,6 +94,37 @@
}
});
});
+
+ group('for a package that causes duplicate files', () {
+ HtmlGenerator generator;
+ PackageGraph packageGraph;
+ Directory tempOutput;
+
+ setUp(() async {
+ generator = await HtmlGenerator.create();
+ packageGraph = await utils
+ .bootBasicPackage(utils.testPackageDuplicateDir.path, []);
+ tempOutput = await Directory.systemTemp.createTemp('doc_test_temp');
+ });
+
+ tearDown(() {
+ if (tempOutput != null) {
+ tempOutput.deleteSync(recursive: true);
+ }
+ });
+
+ test('run generator and verify duplicate file error', () async {
+ await generator.generate(packageGraph, tempOutput.path);
+ expect(generator, isNotNull);
+ expect(tempOutput, isNotNull);
+ String expectedPath =
+ path.join('aDuplicate', 'aDuplicate-library.html');
+ expect(
+ packageGraph.localPublicLibraries,
+ anyElement((l) => packageGraph.packageWarningCounter
+ .hasWarning(l, PackageWarning.duplicateFile, expectedPath)));
+ }, timeout: Timeout.factor(2));
+ });
});
}
diff --git a/test/src/utils.dart b/test/src/utils.dart
index f387dcc..b5d8a79 100644
--- a/test/src/utils.dart
+++ b/test/src/utils.dart
@@ -59,6 +59,8 @@
final Directory testPackageBadDir = Directory('testing/test_package_bad');
final Directory testPackageDir = Directory('testing/test_package');
+final Directory testPackageDuplicateDir =
+ Directory('testing/test_package_duplicate');
final Directory testPackageExperimentsDir =
Directory('testing/test_package_experiments');
final Directory testPackageMinimumDir =
diff --git a/testing/test_package_duplicate/lib/first.dart b/testing/test_package_duplicate/lib/first.dart
new file mode 100644
index 0000000..d369bda
--- /dev/null
+++ b/testing/test_package_duplicate/lib/first.dart
@@ -0,0 +1 @@
+library aDuplicate;
diff --git a/testing/test_package_duplicate/lib/second.dart b/testing/test_package_duplicate/lib/second.dart
new file mode 100644
index 0000000..d369bda
--- /dev/null
+++ b/testing/test_package_duplicate/lib/second.dart
@@ -0,0 +1 @@
+library aDuplicate;
diff --git a/testing/test_package_duplicate/pubspec.yaml b/testing/test_package_duplicate/pubspec.yaml
new file mode 100644
index 0000000..f0f2729
--- /dev/null
+++ b/testing/test_package_duplicate/pubspec.yaml
@@ -0,0 +1 @@
+name: test_package_duplicate