Skip to content

Commit 47dba01

Browse files
authored
Performance improvements for documentation generation (#1837)
* Split out documentation comment computation and make it cached * Precache local documentation only if it is possible a macro template is defined there * Directory.current is expensive, also get rid of excessive split/joins for strings * Minor tweaks * dartfmt * Stable doesn't like Future<void> * Fix import for stable
1 parent 10b9f6b commit 47dba01

File tree

12 files changed

+236
-150
lines changed

12 files changed

+236
-150
lines changed

lib/src/dartdoc_options.dart

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,12 @@ const int _kIntVal = 0;
3232
const double _kDoubleVal = 0.0;
3333
const bool _kBoolVal = true;
3434

35+
/// Args are computed relative to the current directory at the time the
36+
/// program starts.
37+
final Directory directoryCurrent = Directory.current;
38+
final String directoryCurrentPath =
39+
pathLib.canonicalize(Directory.current.path);
40+
3541
String resolveTildePath(String originalPath) {
3642
if (originalPath == null || !originalPath.startsWith('~/')) {
3743
return originalPath;
@@ -585,8 +591,8 @@ abstract class DartdocOption<T> {
585591
/// corresponding files or directories.
586592
T valueAt(Directory dir);
587593

588-
/// Calls [valueAt] with the current working directory.
589-
T valueAtCurrent() => valueAt(Directory.current);
594+
/// Calls [valueAt] with the working directory at the start of the program.
595+
T valueAtCurrent() => valueAt(directoryCurrent);
590596

591597
/// Calls [valueAt] on the directory this element is defined in.
592598
T valueAtElement(Element element) => valueAt(new Directory(
@@ -945,7 +951,7 @@ abstract class _DartdocFileOption<T> implements DartdocOption<T> {
945951
return _valueAtFromFiles(dir) ?? defaultsTo;
946952
}
947953

948-
Map<String, T> __valueAtFromFiles = new Map();
954+
final Map<String, T> __valueAtFromFiles = new Map();
949955
// The value of this option from files will not change unless files are
950956
// modified during execution (not allowed in Dartdoc).
951957
T _valueAtFromFiles(Directory dir) {
@@ -1050,8 +1056,8 @@ abstract class _DartdocFileOption<T> implements DartdocOption<T> {
10501056
_YamlFileData _yamlAtDirectory(Directory dir) {
10511057
List<String> canonicalPaths = [pathLib.canonicalize(dir.path)];
10521058
if (!_yamlAtCanonicalPathCache.containsKey(canonicalPaths.first)) {
1053-
_YamlFileData yamlData = new _YamlFileData(
1054-
new Map(), pathLib.canonicalize(Directory.current.path));
1059+
_YamlFileData yamlData =
1060+
new _YamlFileData(new Map(), directoryCurrentPath);
10551061
if (dir.existsSync()) {
10561062
File dartdocOptionsFile;
10571063

@@ -1126,7 +1132,7 @@ abstract class _DartdocArgOption<T> implements DartdocOption<T> {
11261132
}
11271133

11281134
/// Generates an _OptionValueWithContext using the value of the argument from
1129-
/// the [argParser] and the working directory from [Directory.current].
1135+
/// the [argParser] and the working directory from [directoryCurrent].
11301136
///
11311137
/// Throws [UnsupportedError] if [T] is not a supported type.
11321138
_OptionValueWithContext _valueAtFromArgsWithContext() {
@@ -1157,7 +1163,7 @@ abstract class _DartdocArgOption<T> implements DartdocOption<T> {
11571163
} else {
11581164
throw UnsupportedError('Type ${T} is not supported');
11591165
}
1160-
return new _OptionValueWithContext(retval, Directory.current.path);
1166+
return new _OptionValueWithContext(retval, directoryCurrentPath);
11611167
}
11621168

11631169
/// The name of this option as a command line argument.
@@ -1234,8 +1240,8 @@ class DartdocOptionContext {
12341240
/// the inputDir flag to determine the context.
12351241
DartdocOptionContext(this.optionSet, FileSystemEntity entity) {
12361242
if (entity == null) {
1237-
String inputDir = optionSet['inputDir'].valueAt(Directory.current) ??
1238-
Directory.current.path;
1243+
String inputDir = optionSet['inputDir'].valueAt(directoryCurrent) ??
1244+
directoryCurrentPath;
12391245
context = new Directory(inputDir);
12401246
} else {
12411247
context = new Directory(pathLib
@@ -1404,7 +1410,7 @@ Future<List<DartdocOption>> createDartdocOptions() async {
14041410
new DartdocOptionArgOnly<bool>('injectHtml', false,
14051411
help: 'Allow the use of the {@inject-html} directive to inject raw '
14061412
'HTML into dartdoc output.'),
1407-
new DartdocOptionArgOnly<String>('input', Directory.current.path,
1413+
new DartdocOptionArgOnly<String>('input', directoryCurrentPath,
14081414
isDir: true, help: 'Path to source directory', mustExist: true),
14091415
new DartdocOptionSyntheticOnly<String>('inputDir',
14101416
(DartdocSyntheticOption<String> option, Directory dir) {

lib/src/markdown_processor.dart

Lines changed: 61 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -198,8 +198,9 @@ MatchingLinkResult _getMatchingLinkElement(
198198
// Try expensive not-scoped lookup.
199199
if (refModelElement == null && element is ModelElement) {
200200
Class preferredClass = _getPreferredClass(element);
201-
refModelElement =
202-
new _MarkdownCommentReference(codeRef, element, commentRefs, preferredClass).computeReferredElement();
201+
refModelElement = new _MarkdownCommentReference(
202+
codeRef, element, commentRefs, preferredClass)
203+
.computeReferredElement();
203204
}
204205

205206
// Did not find it anywhere.
@@ -269,32 +270,40 @@ Element _getRefElementFromCommentRefs(
269270
return null;
270271
}
271272

272-
273273
/// Represents a single comment reference.
274274
class _MarkdownCommentReference {
275275
/// The code reference text.
276276
final String codeRef;
277+
277278
/// The element containing the code reference.
278279
final Warnable element;
280+
279281
/// A list of [CommentReference]s from the analyzer.
280282
final List<CommentReference> commentRefs;
283+
281284
/// Disambiguate inheritance with this class.
282285
final Class preferredClass;
286+
283287
/// Current results. Input/output of all _find and _reduce methods.
284288
Set<ModelElement> results;
289+
285290
/// codeRef with any leading constructor string, stripped.
286291
String codeRefChomped;
292+
287293
/// Library associated with this element.
288294
Library library;
295+
289296
/// PackageGraph associated with this element.
290297
PackageGraph packageGraph;
291298

292-
_MarkdownCommentReference(this.codeRef, this.element, this.commentRefs, this.preferredClass) {
299+
_MarkdownCommentReference(
300+
this.codeRef, this.element, this.commentRefs, this.preferredClass) {
293301
assert(element != null);
294302
assert(element.packageGraph.allLibrariesAdded);
295303

296304
codeRefChomped = codeRef.replaceFirst(isConstructor, '');
297-
library = element is ModelElement ? (element as ModelElement).library : null;
305+
library =
306+
element is ModelElement ? (element as ModelElement).library : null;
298307
packageGraph = library.packageGraph;
299308
}
300309

@@ -334,7 +343,8 @@ class _MarkdownCommentReference {
334343
// This could conceivably be a reference to an enum member. They don't show up in allModelElements.
335344
_findEnumReferences,
336345
// Use the analyzer to resolve a comment reference.
337-
_findAnalyzerReferences]) {
346+
_findAnalyzerReferences
347+
]) {
338348
findMethod();
339349
// Remove any "null" objects after each step of trying to add to results.
340350
// TODO(jcollins-g): Eliminate all situations where nulls can be added
@@ -382,13 +392,20 @@ class _MarkdownCommentReference {
382392
if (!results.every((r) => r is Parameter)) {
383393
element.warn(PackageWarning.ambiguousDocReference,
384394
message:
385-
"[$codeRef] => ${results.map((r) => "'${r.fullyQualifiedName}'").join(", ")}");
395+
"[$codeRef] => ${results.map((r) => "'${r.fullyQualifiedName}'").join(", ")}");
386396
}
387397
result = results.first;
388398
}
389399
return result;
390400
}
391401

402+
List<String> _codeRefParts;
403+
List<String> get codeRefParts => _codeRefParts ??= codeRef.split('.');
404+
405+
List<String> _codeRefChompedParts;
406+
List<String> get codeRefChompedParts =>
407+
_codeRefChompedParts ??= codeRefChomped.split('.');
408+
392409
/// Returns true if this is a constructor we should consider due to its
393410
/// name and the code reference, or if this isn't a constructor. False
394411
/// otherwise.
@@ -397,7 +414,6 @@ class _MarkdownCommentReference {
397414
if (modelElement is! Constructor) return true;
398415
if (codeRef.contains(isConstructor)) return true;
399416
Constructor aConstructor = modelElement;
400-
List<String> codeRefParts = codeRef.split('.');
401417
if (codeRefParts.length > 1) {
402418
// Pick the last two parts, in case a specific library was part of the
403419
// codeRef.
@@ -434,18 +450,20 @@ class _MarkdownCommentReference {
434450

435451
void _reducePreferLibrariesInLocalImportExportGraph() {
436452
if (results.any(
437-
(r) => library.packageImportedExportedLibraries.contains(r.library))) {
453+
(r) => library.packageImportedExportedLibraries.contains(r.library))) {
438454
results.removeWhere(
439-
(r) => !library.packageImportedExportedLibraries.contains(r.library));
455+
(r) => !library.packageImportedExportedLibraries.contains(r.library));
440456
}
441457
}
442458

443459
void _reducePreferResultsAccessibleInSameLibrary() {
444460
// TODO(jcollins-g): we could have saved ourselves some work by using the analyzer
445461
// to search the namespace, somehow. Do that instead.
446-
if (element is ModelElement && results.any((r) => r.element.isAccessibleIn((element as ModelElement).library.element))) {
447-
results.removeWhere(
448-
(r) => !r.element.isAccessibleIn((element as ModelElement).library.element));
462+
if (element is ModelElement &&
463+
results.any((r) => r.element
464+
.isAccessibleIn((element as ModelElement).library.element))) {
465+
results.removeWhere((r) =>
466+
!r.element.isAccessibleIn((element as ModelElement).library.element));
449467
}
450468
}
451469

@@ -464,53 +482,55 @@ class _MarkdownCommentReference {
464482
void _findTypeParameters() {
465483
if (element is TypeParameters) {
466484
results.addAll((element as TypeParameters).typeParameters.where((p) =>
467-
p.name == codeRefChomped || codeRefChomped.startsWith("${p.name}.")));
485+
p.name == codeRefChomped || codeRefChomped.startsWith("${p.name}.")));
468486
}
469487
}
470488

471489
void _findParameters() {
472490
if (element is ModelElement) {
473491
results.addAll((element as ModelElement).allParameters.where((p) =>
474-
p.name == codeRefChomped || codeRefChomped.startsWith("${p.name}.")));
492+
p.name == codeRefChomped || codeRefChomped.startsWith("${p.name}.")));
475493
}
476494
}
477495

478496
void _findWithoutLeadingIgnoreStuff() {
479497
if (codeRef.contains(leadingIgnoreStuff)) {
480498
String newCodeRef = codeRef.replaceFirst(leadingIgnoreStuff, '');
481499
results.add(new _MarkdownCommentReference(
482-
newCodeRef, element, commentRefs, preferredClass).computeReferredElement());
500+
newCodeRef, element, commentRefs, preferredClass)
501+
.computeReferredElement());
483502
}
484503
}
485504

486505
void _findWithoutTrailingIgnoreStuff() {
487506
if (codeRef.contains(trailingIgnoreStuff)) {
488507
String newCodeRef = codeRef.replaceFirst(trailingIgnoreStuff, '');
489508
results.add(new _MarkdownCommentReference(
490-
newCodeRef, element, commentRefs, preferredClass).computeReferredElement());
509+
newCodeRef, element, commentRefs, preferredClass)
510+
.computeReferredElement());
491511
}
492512
}
493513

494514
void _findWithoutOperatorPrefix() {
495515
if (codeRef.startsWith(operatorPrefix)) {
496516
String newCodeRef = codeRef.replaceFirst(operatorPrefix, '');
497517
results.add(new _MarkdownCommentReference(
498-
newCodeRef, element, commentRefs, preferredClass).computeReferredElement());
518+
newCodeRef, element, commentRefs, preferredClass)
519+
.computeReferredElement());
499520
}
500521
}
501522

502523
void _findEnumReferences() {
503524
// TODO(jcollins-g): Put enum members in allModelElements with useful hrefs without blowing up other assumptions about what that means.
504525
// TODO(jcollins-g): This doesn't provide good warnings if an enum and class have the same name in different libraries in the same package. Fix that.
505-
List<String> codeRefChompedParts = codeRefChomped.split('.');
506526
if (codeRefChompedParts.length >= 2) {
507527
String maybeEnumName = codeRefChompedParts
508528
.sublist(0, codeRefChompedParts.length - 1)
509529
.join('.');
510530
String maybeEnumMember = codeRefChompedParts.last;
511531
if (packageGraph.findRefElementCache.containsKey(maybeEnumName)) {
512532
for (final modelElement
513-
in packageGraph.findRefElementCache[maybeEnumName]) {
533+
in packageGraph.findRefElementCache[maybeEnumName]) {
514534
if (modelElement is Enum) {
515535
if (modelElement.constants.any((e) => e.name == maybeEnumMember)) {
516536
results.add(modelElement);
@@ -525,7 +545,7 @@ class _MarkdownCommentReference {
525545
void _findGlobalWithinRefElementCache() {
526546
if (packageGraph.findRefElementCache.containsKey(codeRefChomped)) {
527547
for (final modelElement
528-
in packageGraph.findRefElementCache[codeRefChomped]) {
548+
in packageGraph.findRefElementCache[codeRefChomped]) {
529549
if (codeRefChomped == modelElement.fullyQualifiedNameWithoutLibrary ||
530550
(modelElement is Library &&
531551
codeRefChomped == modelElement.fullyQualifiedName)) {
@@ -552,10 +572,10 @@ class _MarkdownCommentReference {
552572
// We now need the ref element cache to keep from repeatedly searching [Package.allModelElements].
553573
// But if not, look for a fully qualified match. (That only makes sense
554574
// if the codeRef might be qualified, and contains periods.)
555-
if (
556-
codeRefChomped.contains('.') &&
575+
if (codeRefChomped.contains('.') &&
557576
packageGraph.findRefElementCache.containsKey(codeRefChomped)) {
558-
for (final ModelElement modelElement in packageGraph.findRefElementCache[codeRefChomped]) {
577+
for (final ModelElement modelElement
578+
in packageGraph.findRefElementCache[codeRefChomped]) {
559579
if (!_ConsiderIfConstructor(modelElement)) continue;
560580
// For fully qualified matches, the original preferredClass passed
561581
// might make no sense. Instead, use the enclosing class from the
@@ -576,10 +596,12 @@ class _MarkdownCommentReference {
576596
List<Class> tryClasses = [preferredClass];
577597
Class realClass = tryClasses.first;
578598
if (element is Inheritable) {
579-
Inheritable overriddenElement = (element as Inheritable).overriddenElement;
599+
Inheritable overriddenElement =
600+
(element as Inheritable).overriddenElement;
580601
while (overriddenElement != null) {
581602
tryClasses.add(
582-
((element as Inheritable).overriddenElement as EnclosedElement).enclosingElement);
603+
((element as Inheritable).overriddenElement as EnclosedElement)
604+
.enclosingElement);
583605
overriddenElement = overriddenElement.overriddenElement;
584606
}
585607
}
@@ -594,7 +616,7 @@ class _MarkdownCommentReference {
594616

595617
if (results.isEmpty && realClass != null) {
596618
for (Class superClass
597-
in realClass.publicSuperChain.map((et) => et.element as Class)) {
619+
in realClass.publicSuperChain.map((et) => et.element as Class)) {
598620
if (!tryClasses.contains(superClass)) {
599621
_getResultsForClass(superClass);
600622
}
@@ -613,7 +635,8 @@ class _MarkdownCommentReference {
613635
if (refModelElement is Accessor) {
614636
refModelElement = (refModelElement as Accessor).enclosingCombo;
615637
}
616-
refModelElement = refModelElement.canonicalModelElement ?? refModelElement;
638+
refModelElement =
639+
refModelElement.canonicalModelElement ?? refModelElement;
617640
results.add(refModelElement);
618641
}
619642
}
@@ -626,13 +649,14 @@ class _MarkdownCommentReference {
626649
if ((tryClass.modelType.typeArguments.map((e) => e.name))
627650
.contains(codeRefChomped)) {
628651
results.add((tryClass.modelType.typeArguments.firstWhere(
629-
(e) => e.name == codeRefChomped && e is DefinedElementType)
630-
as DefinedElementType)
652+
(e) => e.name == codeRefChomped && e is DefinedElementType)
653+
as DefinedElementType)
631654
.element);
632655
} else {
633656
// People like to use 'this' in docrefs too.
634657
if (codeRef == 'this') {
635-
results.add(packageGraph.findCanonicalModelElementFor(tryClass.element));
658+
results
659+
.add(packageGraph.findCanonicalModelElementFor(tryClass.element));
636660
} else {
637661
// TODO(jcollins-g): get rid of reimplementation of identifier resolution
638662
// or integrate into ModelElement in a simpler way.
@@ -643,7 +667,6 @@ class _MarkdownCommentReference {
643667
// TODO(jcollins-g): This makes our caller ~O(n^2) vs length of superChain.
644668
// Fortunately superChains are short, but optimize this if it matters.
645669
superChain.addAll(tryClass.superChain.map((t) => t.element as Class));
646-
List<String> codeRefParts = codeRefChomped.split('.');
647670
for (final c in superChain) {
648671
// TODO(jcollins-g): add a hash-map-enabled lookup function to Class?
649672
for (final modelElement in c.allModelElements) {
@@ -667,17 +690,19 @@ class _MarkdownCommentReference {
667690
// TODO(jcollins-g): Fix partial qualifications in _findRefElementInLibrary so it can tell
668691
// when it is referenced from a non-documented element?
669692
// TODO(jcollins-g): We could probably check this early.
670-
if (codeRefParts.first == c.name && codeRefParts.last == namePart) {
693+
if (codeRefChompedParts.first == c.name &&
694+
codeRefChompedParts.last == namePart) {
671695
results.add(packageGraph.findCanonicalModelElementFor(
672696
modelElement.element,
673697
preferredClass: tryClass));
674698
continue;
675699
}
676700
if (modelElement is Constructor) {
677701
// Constructor names don't include the class, so we might miss them in the above search.
678-
if (codeRefParts.length > 1) {
679-
String codeRefClass = codeRefParts[codeRefParts.length - 2];
680-
String codeRefConstructor = codeRefParts.last;
702+
if (codeRefChompedParts.length > 1) {
703+
String codeRefClass =
704+
codeRefChompedParts[codeRefChompedParts.length - 2];
705+
String codeRefConstructor = codeRefChompedParts.last;
681706
if (codeRefClass == c.name &&
682707
codeRefConstructor ==
683708
modelElement.fullyQualifiedName.split('.').last) {

0 commit comments

Comments
 (0)