Breakdown comment reference handling further and tweak performance (#1863)
* Add option for asyncStackTraces
* Added lookup helper, but doesn't seem to actually help much
* More breakdown of the comment reference handling
* dartfmt
diff --git a/bin/dartdoc.dart b/bin/dartdoc.dart
index 216f6dd..903b331 100644
--- a/bin/dartdoc.dart
+++ b/bin/dartdoc.dart
@@ -18,12 +18,16 @@
DartdocProgramOptionContext(DartdocOptionSet optionSet, Directory dir)
: super(optionSet, dir);
+ bool get asyncStackTraces => optionSet['asyncStackTraces'].valueAt(context);
bool get help => optionSet['help'].valueAt(context);
bool get version => optionSet['version'].valueAt(context);
}
Future<List<DartdocOption>> createDartdocProgramOptions() async {
return <DartdocOption>[
+ new DartdocOptionArgOnly<bool>('asyncStackTraces', false,
+ help: 'Display coordinated asynchronous stack traces (slow)',
+ negatable: true),
new DartdocOptionArgOnly<bool>('help', false,
abbr: 'h', help: 'Show command help.', negatable: false),
new DartdocOptionArgOnly<bool>('version', false,
@@ -88,7 +92,7 @@
stderr.writeln('\nGeneration failed: ${e}\n${chain.terse}');
exit(255);
}
- });
+ }, when: config.asyncStackTraces);
} finally {
// Clear out any cached tool snapshots.
SnapshotCache.instance.dispose();
diff --git a/lib/src/io_utils.dart b/lib/src/io_utils.dart
index 6318dcf..79fc42a 100644
--- a/lib/src/io_utils.dart
+++ b/lib/src/io_utils.dart
@@ -6,7 +6,6 @@
library dartdoc.io_utils;
import 'dart:async';
-import 'dart:collection';
import 'dart:convert';
import 'dart:io';
@@ -89,27 +88,28 @@
/// Approximate maximum number of simultaneous active Futures.
final int parallel;
- final Queue<Future<T>> _queue = new Queue();
+ final Set<Future<T>> _trackedFutures = new Set();
MultiFutureTracker(this.parallel);
/// Wait until fewer or equal to this many Futures are outstanding.
Future<void> _waitUntil(int max) async {
- while (_queue.length > max) {
- await Future.any(_queue);
+ while (_trackedFutures.length > max) {
+ await Future.any(_trackedFutures);
}
}
/// Generates a [Future] from the given closure and adds it to the queue,
- /// once the queue is sufficiently empty.
+ /// once the queue is sufficiently empty. The returned future completes
+ /// when the generated [Future] has been added to the queue.
Future<void> addFutureFromClosure(Future<T> Function() closure) async {
- while (_queue.length > parallel - 1) {
- await Future.any(_queue);
+ while (_trackedFutures.length > parallel - 1) {
+ await Future.any(_trackedFutures);
}
Future future = closure();
- _queue.add(future);
+ _trackedFutures.add(future);
// ignore: unawaited_futures
- future.then((f) => _queue.remove(future));
+ future.then((f) => _trackedFutures.remove(future));
}
/// Wait until all futures added so far have completed.
diff --git a/lib/src/markdown_processor.dart b/lib/src/markdown_processor.dart
index b5706ee..56b4116 100644
--- a/lib/src/markdown_processor.dart
+++ b/lib/src/markdown_processor.dart
@@ -15,6 +15,7 @@
import 'package:dartdoc/src/warnings.dart';
import 'package:html/parser.dart' show parse;
import 'package:markdown/markdown.dart' as md;
+import 'package:quiver/iterables.dart' as quiverIterables;
const validHtmlTags = const [
"a",
@@ -547,8 +548,7 @@
if (codeRefChomped == modelElement.fullyQualifiedNameWithoutLibrary ||
(modelElement is Library &&
codeRefChomped == modelElement.fullyQualifiedName)) {
- results.add(
- packageGraph.findCanonicalModelElementFor(modelElement.element));
+ _addCanonicalResult(modelElement, null);
}
}
}
@@ -559,9 +559,7 @@
if (library.modelElementsNameMap.containsKey(codeRefChomped)) {
for (final modelElement in library.modelElementsNameMap[codeRefChomped]) {
if (!_ConsiderIfConstructor(modelElement)) continue;
- results.add(packageGraph.findCanonicalModelElementFor(
- modelElement.element,
- preferredClass: preferredClass));
+ _addCanonicalResult(modelElement, preferredClass);
}
}
}
@@ -579,11 +577,11 @@
// might make no sense. Instead, use the enclosing class from the
// element in [packageGraph.findRefElementCache], because that element's
// enclosing class will be preferred from [codeRefChomped]'s perspective.
- results.add(packageGraph.findCanonicalModelElementFor(
- modelElement.element,
- preferredClass: modelElement.enclosingElement is Class
+ _addCanonicalResult(
+ modelElement,
+ modelElement.enclosingElement is Class
? modelElement.enclosingElement
- : null));
+ : null);
}
}
}
@@ -639,6 +637,12 @@
}
}
+ // Add a result, but make it canonical.
+ void _addCanonicalResult(ModelElement modelElement, Class tryClass) {
+ results.add(packageGraph.findCanonicalModelElementFor(modelElement.element,
+ preferredClass: tryClass));
+ }
+
/// _getResultsForClass assumes codeRefChomped might be a member of tryClass (inherited or not)
/// and will add to [results]
void _getResultsForClass(Class tryClass) {
@@ -653,8 +657,7 @@
} else {
// People like to use 'this' in docrefs too.
if (codeRef == 'this') {
- results
- .add(packageGraph.findCanonicalModelElementFor(tryClass.element));
+ _addCanonicalResult(tryClass, null);
} else {
// TODO(jcollins-g): get rid of reimplementation of identifier resolution
// or integrate into ModelElement in a simpler way.
@@ -666,62 +669,42 @@
// Fortunately superChains are short, but optimize this if it matters.
superChain.addAll(tryClass.superChain.map((t) => t.element as Class));
for (final c in superChain) {
- // TODO(jcollins-g): add a hash-map-enabled lookup function to Class?
- for (final modelElement in c.allModelElements) {
- if (!_ConsiderIfConstructor(modelElement)) continue;
- String namePart = modelElement.fullyQualifiedName.split('.').last;
- if (modelElement is Accessor) {
- // TODO(jcollins-g): Individual classes should be responsible for
- // this name comparison munging.
- namePart = namePart.split('=').first;
- }
- // TODO(jcollins-g): fix operators so we can use 'name' here or similar.
- if (codeRefChomped == namePart) {
- results.add(packageGraph.findCanonicalModelElementFor(
- modelElement.element,
- preferredClass: tryClass));
- continue;
- }
- // Handle non-documented class documentation being imported into a
- // documented class when it refers to itself (with help from caller's
- // iteration on tryClasses).
- // TODO(jcollins-g): Fix partial qualifications in _findRefElementInLibrary so it can tell
- // when it is referenced from a non-documented element?
- // TODO(jcollins-g): We could probably check this early.
- if (codeRefChompedParts.first == c.name &&
- codeRefChompedParts.last == namePart) {
- results.add(packageGraph.findCanonicalModelElementFor(
- modelElement.element,
- preferredClass: tryClass));
- continue;
- }
- if (modelElement is Constructor) {
- // Constructor names don't include the class, so we might miss them in the above search.
- if (codeRefChompedParts.length > 1) {
- String codeRefClass =
- codeRefChompedParts[codeRefChompedParts.length - 2];
- String codeRefConstructor = codeRefChompedParts.last;
- if (codeRefClass == c.name &&
- codeRefConstructor ==
- modelElement.fullyQualifiedName.split('.').last) {
- results.add(packageGraph.findCanonicalModelElementFor(
- modelElement.element,
- preferredClass: tryClass));
- continue;
- }
- }
- }
- }
- results.remove(null);
+ _getResultsForSuperChainElement(c, tryClass);
if (results.isNotEmpty) break;
- if (c.fullyQualifiedNameWithoutLibrary == codeRefChomped) {
- results.add(c);
- break;
- }
}
}
}
}
+
+ /// Get any possible results for this class in the superChain. Returns
+ /// true if we found something.
+ void _getResultsForSuperChainElement(Class c, Class tryClass) {
+ Iterable<ModelElement> membersToCheck;
+ membersToCheck = (c.allModelElementsByNamePart[codeRefChomped] ?? [])
+ .where((m) => _ConsiderIfConstructor(m));
+ for (final ModelElement modelElement in membersToCheck) {
+ // [thing], a member of this class
+ _addCanonicalResult(modelElement, tryClass);
+ }
+ membersToCheck = (c.allModelElementsByNamePart[codeRefChompedParts.last] ??
+ <ModelElement>[])
+ .where((m) => _ConsiderIfConstructor(m));
+ if (codeRefChompedParts.first == c.name) {
+ // [Foo...thing], a member of this class (possibly a parameter).
+ membersToCheck.map((m) => _addCanonicalResult(m, tryClass));
+ } else if (codeRefChompedParts.length > 1 &&
+ codeRefChompedParts[codeRefChompedParts.length - 2] == c.name) {
+ // [....Foo.thing], a member of this class partially specified.
+ membersToCheck
+ .whereType<Constructor>()
+ .map((m) => _addCanonicalResult(m, tryClass));
+ }
+ results.remove(null);
+ if (results.isNotEmpty) return;
+ if (c.fullyQualifiedNameWithoutLibrary == codeRefChomped) {
+ results.add(c);
+ }
+ }
}
String _linkDocReference(String codeRef, Warnable warnable,
diff --git a/lib/src/model.dart b/lib/src/model.dart
index b9d7f61..55262c1 100644
--- a/lib/src/model.dart
+++ b/lib/src/model.dart
@@ -531,6 +531,14 @@
@override
String get kind => 'accessor';
+ @override
+ String get namePart {
+ if (_namePart == null) {
+ _namePart = super.namePart.split('=').first;
+ }
+ return _namePart;
+ }
+
PropertyAccessorElement get _accessor => (element as PropertyAccessorElement);
}
@@ -684,6 +692,21 @@
return _allElements;
}
+ Map<String, List<ModelElement>> _allModelElementsByNamePart;
+
+ /// Helper for [_MarkdownCommentReference._getResultsForClass].
+ Map<String, List<ModelElement>> get allModelElementsByNamePart {
+ if (_allModelElementsByNamePart == null) {
+ _allModelElementsByNamePart = {};
+ for (ModelElement me in allModelElements) {
+ _allModelElementsByNamePart.update(
+ me.namePart, (List<ModelElement> v) => v..add(me),
+ ifAbsent: () => <ModelElement>[me]);
+ }
+ }
+ return _allModelElementsByNamePart;
+ }
+
/// This class might be canonical for elements it does not contain.
/// See [Inheritable.canonicalEnclosingElement].
bool contains(Element element) => allElements.containsKey(element);
@@ -4621,6 +4644,7 @@
/// Something that has a name.
abstract class Nameable {
String get name;
+ String get fullyQualifiedName => name;
Set<String> _namePieces;
Set<String> get namePieces {
@@ -4630,11 +4654,22 @@
}
return _namePieces;
}
+
+ String _namePart;
+
+ /// Utility getter/cache for [_MarkdownCommentReference._getResultsForClass].
+ String get namePart {
+ // TODO(jcollins-g): This should really be the same as 'name', but isn't
+ // because of accessors and operators.
+ if (_namePart == null) {
+ _namePart = fullyQualifiedName.split('.').last;
+ }
+ return _namePart;
+ }
}
/// Something able to be indexed.
abstract class Indexable implements Nameable {
- String get fullyQualifiedName => name;
String get href;
String get kind;
int get overriddenDepth => 0;
@@ -6568,8 +6603,6 @@
PerformanceLog log = new PerformanceLog(null);
AnalysisDriverScheduler scheduler = new AnalysisDriverScheduler(log);
AnalysisOptionsImpl options = new AnalysisOptionsImpl();
- options.enableSuperMixins = true;
- options.previewDart2 = true;
// TODO(jcollins-g): Make use of currently not existing API for managing
// many AnalysisDrivers