Skip to content

Commit d2bf724

Browse files
authored
Minimize number of tool execution runs (#1898)
* canonicalFor must be declared literally in the library * Add tests for minimizing tool execution * dartfmt * Workaround problem with mixins on 2.1.0 stable * Review comments
1 parent 10f30e8 commit d2bf724

File tree

5 files changed

+88
-20
lines changed

5 files changed

+88
-20
lines changed

lib/src/model.dart

Lines changed: 27 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1300,7 +1300,7 @@ abstract class Documentable extends Nameable {
13001300
abstract class Categorization implements ModelElement {
13011301
@override
13021302
String _buildDocumentationAddition(String rawDocs) =>
1303-
_stripAndSetDartdocCategories(rawDocs);
1303+
_stripAndSetDartdocCategories(rawDocs ??= '');
13041304

13051305
/// Parse {@category ...} and related information in API comments, stripping
13061306
/// out that information from the given comments and returning the stripped
@@ -2263,7 +2263,7 @@ class Library extends ModelElement with Categorization, TopLevelContainer {
22632263
Set<String> get canonicalFor {
22642264
if (_canonicalFor == null) {
22652265
// TODO(jcollins-g): restructure to avoid using side effects.
2266-
documentation;
2266+
_buildDocumentationAddition(documentationComment);
22672267
}
22682268
return _canonicalFor;
22692269
}
@@ -2273,14 +2273,14 @@ class Library extends ModelElement with Categorization, TopLevelContainer {
22732273
///
22742274
/// Example:
22752275
/// {@canonicalFor libname.ClassName}
2276-
String _setCanonicalFor(String rawDocs) {
2277-
if (_canonicalFor == null) {
2278-
_canonicalFor = new Set();
2279-
}
2276+
@override
2277+
String _buildDocumentationAddition(String rawDocs) {
2278+
rawDocs = super._buildDocumentationAddition(rawDocs);
2279+
Set<String> newCanonicalFor = new Set();
22802280
Set<String> notFoundInAllModelElements = new Set();
22812281
final canonicalRegExp = new RegExp(r'{@canonicalFor\s([^}]+)}');
22822282
rawDocs = rawDocs.replaceAllMapped(canonicalRegExp, (Match match) {
2283-
canonicalFor.add(match.group(1));
2283+
newCanonicalFor.add(match.group(1));
22842284
notFoundInAllModelElements.add(match.group(1));
22852285
return '';
22862286
});
@@ -2290,16 +2290,12 @@ class Library extends ModelElement with Categorization, TopLevelContainer {
22902290
for (String notFound in notFoundInAllModelElements) {
22912291
warn(PackageWarning.ignoredCanonicalFor, message: notFound);
22922292
}
2293-
return rawDocs;
2294-
}
2295-
2296-
String _libraryDocs;
2297-
@override
2298-
String get documentation {
2299-
if (_libraryDocs == null) {
2300-
_libraryDocs = _setCanonicalFor(super.documentation);
2293+
// TODO(jcollins-g): warn if a macro/tool _does_ generate an unexpected
2294+
// canonicalFor?
2295+
if (_canonicalFor == null) {
2296+
_canonicalFor = newCanonicalFor;
23012297
}
2302-
return _libraryDocs;
2298+
return rawDocs;
23032299
}
23042300

23052301
/// Libraries are not enclosed by anything.
@@ -3250,13 +3246,14 @@ abstract class ModelElement extends Canonicalization
32503246

32513247
/// Override this to add more features to the documentation builder in a
32523248
/// subclass.
3253-
String _buildDocumentationAddition(String docs) => docs;
3249+
String _buildDocumentationAddition(String docs) => docs ??= '';
32543250

32553251
/// Separate from _buildDocumentationLocal for overriding.
32563252
String _buildDocumentationBaseSync() {
3257-
assert(_rawDocs == null);
3253+
assert(_rawDocs == null, 'reentrant calls to _buildDocumentation* not allowed');
32583254
// Do not use the sync method if we need to evaluate tools or templates.
3259-
assert(packageGraph._localDocumentationBuilt);
3255+
assert(!isCanonical ||
3256+
!needsPrecacheRegExp.hasMatch(documentationComment ?? ''));
32603257
if (config.dropTextFrom.contains(element.library.name)) {
32613258
_rawDocs = '';
32623259
} else {
@@ -3273,7 +3270,7 @@ abstract class ModelElement extends Canonicalization
32733270
/// Separate from _buildDocumentationLocal for overriding. Can only be
32743271
/// used as part of [PackageGraph.setUpPackageGraph].
32753272
Future<String> _buildDocumentationBase() async {
3276-
assert(_rawDocs == null);
3273+
assert(_rawDocs == null, 'reentrant calls to _buildDocumentation* not allowed');
32773274
// Do not use the sync method if we need to evaluate tools or templates.
32783275
if (config.dropTextFrom.contains(element.library.name)) {
32793276
_rawDocs = '';
@@ -4037,6 +4034,8 @@ abstract class ModelElement extends Canonicalization
40374034
'LIBRARY_NAME': library?.fullyQualifiedName,
40384035
'ELEMENT_NAME': fullyQualifiedNameWithoutLibrary,
40394036
'INVOCATION_INDEX': invocationIndex.toString(),
4037+
'PACKAGE_INVOCATION_INDEX':
4038+
(package.toolInvocationIndex++).toString(),
40404039
}..removeWhere((key, value) => value == null));
40414040
});
40424041
} else {
@@ -4688,6 +4687,11 @@ class PackageGraph {
46884687
/// Generate a list of futures for any docs that actually require precaching.
46894688
Iterable<Future> precacheLocalDocs() sync* {
46904689
for (ModelElement m in allModelElements) {
4690+
// Skip if there is a canonicalModelElement somewhere else we can run this
4691+
// for. Not the same as allCanonicalModelElements since we need to run
4692+
// for any ModelElement that might not have a canonical ModelElement,
4693+
// too.
4694+
if (m.canonicalModelElement != null && !m.isCanonical) continue;
46914695
if (m.documentationComment != null &&
46924696
needsPrecacheRegExp.hasMatch(m.documentationComment)) {
46934697
yield m._precacheLocalDocs();
@@ -5800,6 +5804,9 @@ class Package extends LibraryContainer
58005804
@override
58015805
Library get canonicalLibrary => null;
58025806

5807+
/// Number of times we have invoked a tool for this package.
5808+
int toolInvocationIndex = 0;
5809+
58035810
/// Pieces of the location split by [locationSplitter] (removing package: and
58045811
/// slashes).
58055812
@override

test/model_test.dart

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,20 +84,57 @@ void main() {
8484

8585
group('Tools', () {
8686
Class toolUser;
87+
Class _NonCanonicalToolUser, CanonicalToolUser, PrivateLibraryToolUser;
8788
Method invokeTool;
8889
Method invokeToolNoInput;
8990
Method invokeToolMultipleSections;
91+
Method invokeToolNonCanonical, invokeToolNonCanonicalSubclass;
92+
Method invokeToolPrivateLibrary, invokeToolPrivateLibraryOriginal;
9093

9194
setUpAll(() {
95+
_NonCanonicalToolUser = fakeLibrary.allClasses
96+
.firstWhere((c) => c.name == '_NonCanonicalToolUser');
97+
CanonicalToolUser = fakeLibrary.allClasses
98+
.firstWhere((c) => c.name == 'CanonicalToolUser');
99+
PrivateLibraryToolUser = fakeLibrary.allClasses
100+
.firstWhere((c) => c.name == 'PrivateLibraryToolUser');
92101
toolUser = exLibrary.classes.firstWhere((c) => c.name == 'ToolUser');
93102
invokeTool =
94103
toolUser.allInstanceMethods.firstWhere((m) => m.name == 'invokeTool');
104+
invokeToolNonCanonical = _NonCanonicalToolUser.allInstanceMethods
105+
.firstWhere((m) => m.name == 'invokeToolNonCanonical');
106+
invokeToolNonCanonicalSubclass = CanonicalToolUser.allInstanceMethods
107+
.firstWhere((m) => m.name == 'invokeToolNonCanonical');
95108
invokeToolNoInput = toolUser.allInstanceMethods
96109
.firstWhere((m) => m.name == 'invokeToolNoInput');
97110
invokeToolMultipleSections = toolUser.allInstanceMethods
98111
.firstWhere((m) => m.name == 'invokeToolMultipleSections');
112+
invokeToolPrivateLibrary = PrivateLibraryToolUser.allInstanceMethods
113+
.firstWhere((m) => m.name == 'invokeToolPrivateLibrary');
114+
invokeToolPrivateLibraryOriginal =
115+
(invokeToolPrivateLibrary.definingEnclosingElement as Class)
116+
.allInstanceMethods
117+
.firstWhere((m) => m.name == 'invokeToolPrivateLibrary');
99118
packageGraph.allLocalModelElements.forEach((m) => m.documentation);
100119
});
120+
121+
test('does _not_ invoke a tool multiple times unnecessarily', () {
122+
RegExp packageInvocationIndexRegexp =
123+
new RegExp(r'PACKAGE_INVOCATION_INDEX: (\d+)');
124+
expect(invokeToolNonCanonical.isCanonical, isFalse);
125+
expect(invokeToolNonCanonicalSubclass.isCanonical, isTrue);
126+
expect(
127+
packageInvocationIndexRegexp
128+
.firstMatch(invokeToolNonCanonical.documentation)
129+
.group(1),
130+
equals(packageInvocationIndexRegexp
131+
.firstMatch(invokeToolNonCanonicalSubclass.documentation)
132+
.group(1)));
133+
expect(invokeToolPrivateLibrary.documentation, isNot(contains('{@tool')));
134+
expect(
135+
invokeToolPrivateLibraryOriginal.documentation, contains('{@tool'));
136+
});
137+
101138
test('can invoke a tool and pass args and environment', () {
102139
expect(invokeTool.documentation, contains('--file=<INPUT_FILE>'));
103140
expect(

testing/test_package/bin/drill.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ void main(List<String> argList) {
3636
'LIBRARY_NAME',
3737
'ELEMENT_NAME',
3838
'INVOCATION_INDEX',
39+
'PACKAGE_INVOCATION_INDEX',
3940
]);
4041
Map<String, String> env = <String, String>{}..addAll(Platform.environment);
4142
env.removeWhere((String key, String value) => !variableNames.contains(key));

testing/test_package/lib/fake.dart

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ import 'two_exports.dart' show BaseClass;
6060
export 'package:test_package_imported/categoryExporting.dart'
6161
show IAmAClassWithCategories;
6262

63+
export 'src/tool.dart';
64+
6365
// Explicitly export ourselves, because why not.
6466
// ignore: uri_does_not_exist
6567
export 'package:test_package/fake.dart';
@@ -1004,3 +1006,14 @@ abstract class MIEEMixin<K, V> implements MIEEThing<K, V> {
10041006
abstract class MIEEThing<K, V> {
10051007
void operator []=(K key, V value);
10061008
}
1009+
1010+
abstract class _NonCanonicalToolUser {
1011+
/// Invokes a tool without the $INPUT token or args.
1012+
///
1013+
/// {@tool drill}
1014+
/// Some text in the drill that references [noInvokeTool].
1015+
/// {@end-tool}
1016+
void invokeToolNonCanonical();
1017+
}
1018+
1019+
abstract class CanonicalToolUser extends _NonCanonicalToolUser {}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
library test_package.tool;
2+
3+
abstract class PrivateLibraryToolUser {
4+
/// Invokes a tool from a private library.
5+
///
6+
/// {@tool drill}
7+
/// Some text in the drill.
8+
/// {@end-tool}
9+
void invokeToolPrivateLibrary();
10+
}

0 commit comments

Comments
 (0)