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
diff --git a/lib/src/dartdoc_options.dart b/lib/src/dartdoc_options.dart
index d205153..e523843 100644
--- a/lib/src/dartdoc_options.dart
+++ b/lib/src/dartdoc_options.dart
@@ -32,6 +32,12 @@
const double _kDoubleVal = 0.0;
const bool _kBoolVal = true;
+/// Args are computed relative to the current directory at the time the
+/// program starts.
+final Directory directoryCurrent = Directory.current;
+final String directoryCurrentPath =
+ pathLib.canonicalize(Directory.current.path);
+
String resolveTildePath(String originalPath) {
if (originalPath == null || !originalPath.startsWith('~/')) {
return originalPath;
@@ -585,8 +591,8 @@
/// corresponding files or directories.
T valueAt(Directory dir);
- /// Calls [valueAt] with the current working directory.
- T valueAtCurrent() => valueAt(Directory.current);
+ /// Calls [valueAt] with the working directory at the start of the program.
+ T valueAtCurrent() => valueAt(directoryCurrent);
/// Calls [valueAt] on the directory this element is defined in.
T valueAtElement(Element element) => valueAt(new Directory(
@@ -945,7 +951,7 @@
return _valueAtFromFiles(dir) ?? defaultsTo;
}
- Map<String, T> __valueAtFromFiles = new Map();
+ final Map<String, T> __valueAtFromFiles = new Map();
// The value of this option from files will not change unless files are
// modified during execution (not allowed in Dartdoc).
T _valueAtFromFiles(Directory dir) {
@@ -1050,8 +1056,8 @@
_YamlFileData _yamlAtDirectory(Directory dir) {
List<String> canonicalPaths = [pathLib.canonicalize(dir.path)];
if (!_yamlAtCanonicalPathCache.containsKey(canonicalPaths.first)) {
- _YamlFileData yamlData = new _YamlFileData(
- new Map(), pathLib.canonicalize(Directory.current.path));
+ _YamlFileData yamlData =
+ new _YamlFileData(new Map(), directoryCurrentPath);
if (dir.existsSync()) {
File dartdocOptionsFile;
@@ -1126,7 +1132,7 @@
}
/// Generates an _OptionValueWithContext using the value of the argument from
- /// the [argParser] and the working directory from [Directory.current].
+ /// the [argParser] and the working directory from [directoryCurrent].
///
/// Throws [UnsupportedError] if [T] is not a supported type.
_OptionValueWithContext _valueAtFromArgsWithContext() {
@@ -1157,7 +1163,7 @@
} else {
throw UnsupportedError('Type ${T} is not supported');
}
- return new _OptionValueWithContext(retval, Directory.current.path);
+ return new _OptionValueWithContext(retval, directoryCurrentPath);
}
/// The name of this option as a command line argument.
@@ -1234,8 +1240,8 @@
/// the inputDir flag to determine the context.
DartdocOptionContext(this.optionSet, FileSystemEntity entity) {
if (entity == null) {
- String inputDir = optionSet['inputDir'].valueAt(Directory.current) ??
- Directory.current.path;
+ String inputDir = optionSet['inputDir'].valueAt(directoryCurrent) ??
+ directoryCurrentPath;
context = new Directory(inputDir);
} else {
context = new Directory(pathLib
@@ -1404,7 +1410,7 @@
new DartdocOptionArgOnly<bool>('injectHtml', false,
help: 'Allow the use of the {@inject-html} directive to inject raw '
'HTML into dartdoc output.'),
- new DartdocOptionArgOnly<String>('input', Directory.current.path,
+ new DartdocOptionArgOnly<String>('input', directoryCurrentPath,
isDir: true, help: 'Path to source directory', mustExist: true),
new DartdocOptionSyntheticOnly<String>('inputDir',
(DartdocSyntheticOption<String> option, Directory dir) {
diff --git a/lib/src/markdown_processor.dart b/lib/src/markdown_processor.dart
index d8a8cac..eb3eda1 100644
--- a/lib/src/markdown_processor.dart
+++ b/lib/src/markdown_processor.dart
@@ -198,8 +198,9 @@
// Try expensive not-scoped lookup.
if (refModelElement == null && element is ModelElement) {
Class preferredClass = _getPreferredClass(element);
- refModelElement =
- new _MarkdownCommentReference(codeRef, element, commentRefs, preferredClass).computeReferredElement();
+ refModelElement = new _MarkdownCommentReference(
+ codeRef, element, commentRefs, preferredClass)
+ .computeReferredElement();
}
// Did not find it anywhere.
@@ -269,32 +270,40 @@
return null;
}
-
/// Represents a single comment reference.
class _MarkdownCommentReference {
/// The code reference text.
final String codeRef;
+
/// The element containing the code reference.
final Warnable element;
+
/// A list of [CommentReference]s from the analyzer.
final List<CommentReference> commentRefs;
+
/// Disambiguate inheritance with this class.
final Class preferredClass;
+
/// Current results. Input/output of all _find and _reduce methods.
Set<ModelElement> results;
+
/// codeRef with any leading constructor string, stripped.
String codeRefChomped;
+
/// Library associated with this element.
Library library;
+
/// PackageGraph associated with this element.
PackageGraph packageGraph;
- _MarkdownCommentReference(this.codeRef, this.element, this.commentRefs, this.preferredClass) {
+ _MarkdownCommentReference(
+ this.codeRef, this.element, this.commentRefs, this.preferredClass) {
assert(element != null);
assert(element.packageGraph.allLibrariesAdded);
codeRefChomped = codeRef.replaceFirst(isConstructor, '');
- library = element is ModelElement ? (element as ModelElement).library : null;
+ library =
+ element is ModelElement ? (element as ModelElement).library : null;
packageGraph = library.packageGraph;
}
@@ -334,7 +343,8 @@
// This could conceivably be a reference to an enum member. They don't show up in allModelElements.
_findEnumReferences,
// Use the analyzer to resolve a comment reference.
- _findAnalyzerReferences]) {
+ _findAnalyzerReferences
+ ]) {
findMethod();
// Remove any "null" objects after each step of trying to add to results.
// TODO(jcollins-g): Eliminate all situations where nulls can be added
@@ -382,13 +392,20 @@
if (!results.every((r) => r is Parameter)) {
element.warn(PackageWarning.ambiguousDocReference,
message:
- "[$codeRef] => ${results.map((r) => "'${r.fullyQualifiedName}'").join(", ")}");
+ "[$codeRef] => ${results.map((r) => "'${r.fullyQualifiedName}'").join(", ")}");
}
result = results.first;
}
return result;
}
+ List<String> _codeRefParts;
+ List<String> get codeRefParts => _codeRefParts ??= codeRef.split('.');
+
+ List<String> _codeRefChompedParts;
+ List<String> get codeRefChompedParts =>
+ _codeRefChompedParts ??= codeRefChomped.split('.');
+
/// Returns true if this is a constructor we should consider due to its
/// name and the code reference, or if this isn't a constructor. False
/// otherwise.
@@ -397,7 +414,6 @@
if (modelElement is! Constructor) return true;
if (codeRef.contains(isConstructor)) return true;
Constructor aConstructor = modelElement;
- List<String> codeRefParts = codeRef.split('.');
if (codeRefParts.length > 1) {
// Pick the last two parts, in case a specific library was part of the
// codeRef.
@@ -434,18 +450,20 @@
void _reducePreferLibrariesInLocalImportExportGraph() {
if (results.any(
- (r) => library.packageImportedExportedLibraries.contains(r.library))) {
+ (r) => library.packageImportedExportedLibraries.contains(r.library))) {
results.removeWhere(
- (r) => !library.packageImportedExportedLibraries.contains(r.library));
+ (r) => !library.packageImportedExportedLibraries.contains(r.library));
}
}
void _reducePreferResultsAccessibleInSameLibrary() {
// TODO(jcollins-g): we could have saved ourselves some work by using the analyzer
// to search the namespace, somehow. Do that instead.
- if (element is ModelElement && results.any((r) => r.element.isAccessibleIn((element as ModelElement).library.element))) {
- results.removeWhere(
- (r) => !r.element.isAccessibleIn((element as ModelElement).library.element));
+ if (element is ModelElement &&
+ results.any((r) => r.element
+ .isAccessibleIn((element as ModelElement).library.element))) {
+ results.removeWhere((r) =>
+ !r.element.isAccessibleIn((element as ModelElement).library.element));
}
}
@@ -464,14 +482,14 @@
void _findTypeParameters() {
if (element is TypeParameters) {
results.addAll((element as TypeParameters).typeParameters.where((p) =>
- p.name == codeRefChomped || codeRefChomped.startsWith("${p.name}.")));
+ p.name == codeRefChomped || codeRefChomped.startsWith("${p.name}.")));
}
}
void _findParameters() {
if (element is ModelElement) {
results.addAll((element as ModelElement).allParameters.where((p) =>
- p.name == codeRefChomped || codeRefChomped.startsWith("${p.name}.")));
+ p.name == codeRefChomped || codeRefChomped.startsWith("${p.name}.")));
}
}
@@ -479,7 +497,8 @@
if (codeRef.contains(leadingIgnoreStuff)) {
String newCodeRef = codeRef.replaceFirst(leadingIgnoreStuff, '');
results.add(new _MarkdownCommentReference(
- newCodeRef, element, commentRefs, preferredClass).computeReferredElement());
+ newCodeRef, element, commentRefs, preferredClass)
+ .computeReferredElement());
}
}
@@ -487,7 +506,8 @@
if (codeRef.contains(trailingIgnoreStuff)) {
String newCodeRef = codeRef.replaceFirst(trailingIgnoreStuff, '');
results.add(new _MarkdownCommentReference(
- newCodeRef, element, commentRefs, preferredClass).computeReferredElement());
+ newCodeRef, element, commentRefs, preferredClass)
+ .computeReferredElement());
}
}
@@ -495,14 +515,14 @@
if (codeRef.startsWith(operatorPrefix)) {
String newCodeRef = codeRef.replaceFirst(operatorPrefix, '');
results.add(new _MarkdownCommentReference(
- newCodeRef, element, commentRefs, preferredClass).computeReferredElement());
+ newCodeRef, element, commentRefs, preferredClass)
+ .computeReferredElement());
}
}
void _findEnumReferences() {
// TODO(jcollins-g): Put enum members in allModelElements with useful hrefs without blowing up other assumptions about what that means.
// 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.
- List<String> codeRefChompedParts = codeRefChomped.split('.');
if (codeRefChompedParts.length >= 2) {
String maybeEnumName = codeRefChompedParts
.sublist(0, codeRefChompedParts.length - 1)
@@ -510,7 +530,7 @@
String maybeEnumMember = codeRefChompedParts.last;
if (packageGraph.findRefElementCache.containsKey(maybeEnumName)) {
for (final modelElement
- in packageGraph.findRefElementCache[maybeEnumName]) {
+ in packageGraph.findRefElementCache[maybeEnumName]) {
if (modelElement is Enum) {
if (modelElement.constants.any((e) => e.name == maybeEnumMember)) {
results.add(modelElement);
@@ -525,7 +545,7 @@
void _findGlobalWithinRefElementCache() {
if (packageGraph.findRefElementCache.containsKey(codeRefChomped)) {
for (final modelElement
- in packageGraph.findRefElementCache[codeRefChomped]) {
+ in packageGraph.findRefElementCache[codeRefChomped]) {
if (codeRefChomped == modelElement.fullyQualifiedNameWithoutLibrary ||
(modelElement is Library &&
codeRefChomped == modelElement.fullyQualifiedName)) {
@@ -552,10 +572,10 @@
// We now need the ref element cache to keep from repeatedly searching [Package.allModelElements].
// But if not, look for a fully qualified match. (That only makes sense
// if the codeRef might be qualified, and contains periods.)
- if (
- codeRefChomped.contains('.') &&
+ if (codeRefChomped.contains('.') &&
packageGraph.findRefElementCache.containsKey(codeRefChomped)) {
- for (final ModelElement modelElement in packageGraph.findRefElementCache[codeRefChomped]) {
+ for (final ModelElement modelElement
+ in packageGraph.findRefElementCache[codeRefChomped]) {
if (!_ConsiderIfConstructor(modelElement)) continue;
// For fully qualified matches, the original preferredClass passed
// might make no sense. Instead, use the enclosing class from the
@@ -576,10 +596,12 @@
List<Class> tryClasses = [preferredClass];
Class realClass = tryClasses.first;
if (element is Inheritable) {
- Inheritable overriddenElement = (element as Inheritable).overriddenElement;
+ Inheritable overriddenElement =
+ (element as Inheritable).overriddenElement;
while (overriddenElement != null) {
tryClasses.add(
- ((element as Inheritable).overriddenElement as EnclosedElement).enclosingElement);
+ ((element as Inheritable).overriddenElement as EnclosedElement)
+ .enclosingElement);
overriddenElement = overriddenElement.overriddenElement;
}
}
@@ -594,7 +616,7 @@
if (results.isEmpty && realClass != null) {
for (Class superClass
- in realClass.publicSuperChain.map((et) => et.element as Class)) {
+ in realClass.publicSuperChain.map((et) => et.element as Class)) {
if (!tryClasses.contains(superClass)) {
_getResultsForClass(superClass);
}
@@ -613,7 +635,8 @@
if (refModelElement is Accessor) {
refModelElement = (refModelElement as Accessor).enclosingCombo;
}
- refModelElement = refModelElement.canonicalModelElement ?? refModelElement;
+ refModelElement =
+ refModelElement.canonicalModelElement ?? refModelElement;
results.add(refModelElement);
}
}
@@ -626,13 +649,14 @@
if ((tryClass.modelType.typeArguments.map((e) => e.name))
.contains(codeRefChomped)) {
results.add((tryClass.modelType.typeArguments.firstWhere(
- (e) => e.name == codeRefChomped && e is DefinedElementType)
- as DefinedElementType)
+ (e) => e.name == codeRefChomped && e is DefinedElementType)
+ as DefinedElementType)
.element);
} else {
// People like to use 'this' in docrefs too.
if (codeRef == 'this') {
- results.add(packageGraph.findCanonicalModelElementFor(tryClass.element));
+ results
+ .add(packageGraph.findCanonicalModelElementFor(tryClass.element));
} else {
// TODO(jcollins-g): get rid of reimplementation of identifier resolution
// or integrate into ModelElement in a simpler way.
@@ -643,7 +667,6 @@
// TODO(jcollins-g): This makes our caller ~O(n^2) vs length of superChain.
// Fortunately superChains are short, but optimize this if it matters.
superChain.addAll(tryClass.superChain.map((t) => t.element as Class));
- List<String> codeRefParts = codeRefChomped.split('.');
for (final c in superChain) {
// TODO(jcollins-g): add a hash-map-enabled lookup function to Class?
for (final modelElement in c.allModelElements) {
@@ -667,7 +690,8 @@
// 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 (codeRefParts.first == c.name && codeRefParts.last == namePart) {
+ if (codeRefChompedParts.first == c.name &&
+ codeRefChompedParts.last == namePart) {
results.add(packageGraph.findCanonicalModelElementFor(
modelElement.element,
preferredClass: tryClass));
@@ -675,9 +699,10 @@
}
if (modelElement is Constructor) {
// Constructor names don't include the class, so we might miss them in the above search.
- if (codeRefParts.length > 1) {
- String codeRefClass = codeRefParts[codeRefParts.length - 2];
- String codeRefConstructor = codeRefParts.last;
+ if (codeRefChompedParts.length > 1) {
+ String codeRefClass =
+ codeRefChompedParts[codeRefChompedParts.length - 2];
+ String codeRefConstructor = codeRefChompedParts.last;
if (codeRefClass == c.name &&
codeRefConstructor ==
modelElement.fullyQualifiedName.split('.').last) {
diff --git a/lib/src/model.dart b/lib/src/model.dart
index 29bef4c..b8b7b63 100644
--- a/lib/src/model.dart
+++ b/lib/src/model.dart
@@ -99,6 +99,43 @@
final RegExp locationSplitter = new RegExp(r'(package:|[\\/;.])');
final RegExp substituteNameVersion = new RegExp(r'%([bnv])%');
+/// This doc may need to be processed in case it has a template or html
+/// fragment.
+final needsPrecacheRegExp = new RegExp(r'{@(template|tool|inject-html)');
+
+final templateRegExp = new RegExp(
+ r'[ ]*{@template\s+(.+?)}([\s\S]+?){@endtemplate}[ ]*\n?',
+ multiLine: true);
+final htmlRegExp = new RegExp(
+ r'[ ]*{@inject-html\s*}([\s\S]+?){@end-inject-html}[ ]*\n?',
+ multiLine: true);
+final htmlInjectRegExp =
+ new RegExp(r'<dartdoc-html>([a-f0-9]+)</dartdoc-html>');
+
+// Matches all tool directives (even some invalid ones). This is so
+// we can give good error messages if the directive is malformed, instead of
+// just silently emitting it as-is.
+final basicToolRegExp = new RegExp(
+ r'[ ]*{@tool\s+([^}]+)}\n?([\s\S]+?)\n?{@end-tool}[ ]*\n?',
+ multiLine: true);
+
+/// Regexp to take care of splitting arguments, and handling the quotes
+/// around arguments, if any.
+///
+/// Match group 1 is the "foo=" (or "--foo=") part of the option, if any.
+/// Match group 2 contains the quote character used (which is discarded).
+/// Match group 3 is a quoted arg, if any, without the quotes.
+/// Match group 4 is the unquoted arg, if any.
+final RegExp argMatcher = new RegExp(r'([a-zA-Z\-_0-9]+=)?' // option name
+ r'(?:' // Start a new non-capture group for the two possibilities.
+ r'''(["'])((?:\\{2})*|(?:.*?[^\\](?:\\{2})*))\2|''' // with quotes.
+ r'([^ ]+))'); // without quotes.
+
+final categoryRegexp = new RegExp(
+ r'[ ]*{@(api|category|subCategory|image|samples) (.+?)}[ ]*\n?',
+ multiLine: true);
+final macroRegExp = new RegExp(r'{@macro\s+([^}]+)}');
+
/// Mixin for subclasses of ModelElement representing Elements that can be
/// inherited from one class to another.
///
@@ -414,7 +451,7 @@
}
@override
- String get computeDocumentationComment {
+ String _computeDocumentationComment() {
if (isSynthetic) {
String docComment =
(element as PropertyAccessorElement).variable.documentationComment;
@@ -427,14 +464,13 @@
docComment.contains('@nodoc'))) ||
(isSetter &&
enclosingCombo.hasGetter &&
- enclosingCombo.getter.computeDocumentationComment !=
- docComment)) {
+ enclosingCombo.getter.documentationComment != docComment)) {
return stripComments(docComment);
} else {
return '';
}
}
- return stripComments(super.computeDocumentationComment);
+ return stripComments(super._computeDocumentationComment());
}
@override
@@ -1334,9 +1370,7 @@
Set<String> _categorySet = new Set();
Set<String> _subCategorySet = new Set();
_hasCategorization = false;
- final categoryRegexp = new RegExp(
- r'[ ]*{@(api|category|subCategory|image|samples) (.+?)}[ ]*\n?',
- multiLine: true);
+
rawDocs = rawDocs.replaceAllMapped(categoryRegexp, (match) {
_hasCategorization = true;
switch (match[1]) {
@@ -1768,7 +1802,7 @@
}
@override
- String get computeDocumentationComment {
+ String _computeDocumentationComment() {
String docs = getterSetterDocumentationComment;
if (docs.isEmpty) return _field.documentationComment;
return docs;
@@ -1942,8 +1976,7 @@
// doesn't yield the real elements for GetterSetterCombos.
if (!config.dropTextFrom
.contains(getter.documentationFrom.first.element.library.name)) {
- String docs =
- getter.documentationFrom.first.computeDocumentationComment;
+ String docs = getter.documentationFrom.first.documentationComment;
if (docs != null) buffer.write(docs);
}
}
@@ -1952,8 +1985,7 @@
assert(setter.documentationFrom.length == 1);
if (!config.dropTextFrom
.contains(setter.documentationFrom.first.element.library.name)) {
- String docs =
- setter.documentationFrom.first.computeDocumentationComment;
+ String docs = setter.documentationFrom.first.documentationComment;
if (docs != null) {
if (buffer.isNotEmpty) buffer.write('\n\n');
buffer.write(docs);
@@ -2534,14 +2566,17 @@
}
Map<String, Set<ModelElement>> _modelElementsNameMap;
+
/// Map of [fullyQualifiedNameWithoutLibrary] to all matching [ModelElement]s
/// in this library. Used for code reference lookups.
Map<String, Set<ModelElement>> get modelElementsNameMap {
if (_modelElementsNameMap == null) {
_modelElementsNameMap = new Map<String, Set<ModelElement>>();
allModelElements.forEach((ModelElement modelElement) {
- _modelElementsNameMap.putIfAbsent(modelElement.fullyQualifiedNameWithoutLibrary, () => new Set());
- _modelElementsNameMap[modelElement.fullyQualifiedNameWithoutLibrary].add(modelElement);
+ _modelElementsNameMap.putIfAbsent(
+ modelElement.fullyQualifiedNameWithoutLibrary, () => new Set());
+ _modelElementsNameMap[modelElement.fullyQualifiedNameWithoutLibrary]
+ .add(modelElement);
});
}
return _modelElementsNameMap;
@@ -3112,7 +3147,7 @@
!(enclosingElement as Class).isPublic) {
_isPublic = false;
} else {
- String docComment = computeDocumentationComment;
+ String docComment = documentationComment;
if (docComment == null) {
_isPublic = hasPublicName(element);
} else {
@@ -3226,7 +3261,7 @@
List<ModelElement> get computeDocumentationFrom {
List<ModelElement> docFrom;
- if (computeDocumentationComment == null &&
+ if (documentationComment == null &&
canOverride() &&
this is Inheritable &&
(this as Inheritable).overriddenElement != null) {
@@ -3252,7 +3287,7 @@
if (config.dropTextFrom.contains(element.library.name)) {
_rawDocs = '';
} else {
- _rawDocs = computeDocumentationComment ?? '';
+ _rawDocs = documentationComment ?? '';
_rawDocs = stripComments(_rawDocs) ?? '';
// Must evaluate tools first, in case they insert any other directives.
_rawDocs = _evaluateTools(_rawDocs);
@@ -3679,7 +3714,24 @@
extendedDebug: extendedDebug);
}
- String get computeDocumentationComment => element.documentationComment;
+ String _computeDocumentationComment() => element.documentationComment;
+
+ bool _documentationCommentComputed = false;
+ String _documentationComment;
+ String get documentationComment {
+ if (_documentationCommentComputed == false) {
+ _documentationComment = _computeDocumentationComment();
+ _documentationCommentComputed = true;
+ }
+ return _documentationComment;
+ }
+
+ /// Call this method to precache docs for this object if it might possibly
+ /// have a macro template or a tool definition.
+ void precacheLocalDocsIfNeeded() {
+ if (documentationComment != null &&
+ needsPrecacheRegExp.hasMatch(documentationComment)) documentationLocal;
+ }
Documentation get _documentation {
if (__documentation != null) return __documentation;
@@ -3963,13 +4015,6 @@
/// ## Content to send to tool.
/// 2018-09-18T21:15+00:00
String _evaluateTools(String rawDocs) {
- // Matches all tool directives (even some invalid ones). This is so
- // we can give good error messages if the directive is malformed, instead of
- // just silently emitting it as-is.
- final basicToolRegExp = new RegExp(
- r'[ ]*{@tool\s+([^}]+)}\n?([\s\S]+?)\n?{@end-tool}[ ]*\n?',
- multiLine: true);
-
var runner = new ToolRunner(config.tools, (String message) {
warn(PackageWarning.toolError, message: message);
});
@@ -4208,8 +4253,8 @@
/// but just injected verbatim.
String _injectHtmlFragments(String rawDocs) {
if (!config.injectHtml) return rawDocs;
- final macroRegExp = new RegExp(r'<dartdoc-html>([a-f0-9]+)</dartdoc-html>');
- return rawDocs.replaceAllMapped(macroRegExp, (match) {
+
+ return rawDocs.replaceAllMapped(htmlInjectRegExp, (match) {
String fragment = packageGraph.getHtmlFragment(match[1]);
if (fragment == null) {
warn(PackageWarning.unknownHtmlFragment, message: match[1]);
@@ -4245,7 +4290,6 @@
/// More comments
///
String _injectMacros(String rawDocs) {
- final macroRegExp = new RegExp(r'{@macro\s+([^}]+)}');
return rawDocs.replaceAllMapped(macroRegExp, (match) {
String macro = packageGraph.getMacro(match[1]);
if (macro == null) {
@@ -4265,9 +4309,6 @@
/// {@endtemplate}
///
String _stripMacroTemplatesAndAddToIndex(String rawDocs) {
- final templateRegExp = new RegExp(
- r'[ ]*{@template\s+(.+?)}([\s\S]+?){@endtemplate}[ ]*\n?',
- multiLine: true);
return rawDocs.replaceAllMapped(templateRegExp, (match) {
packageGraph._addMacro(match[1].trim(), match[2].trim());
return "{@macro ${match[1].trim()}}";
@@ -4287,10 +4328,7 @@
///
String _stripHtmlAndAddToIndex(String rawDocs) {
if (!config.injectHtml) return rawDocs;
- final templateRegExp = new RegExp(
- r'[ ]*{@inject-html\s*}([\s\S]+?){@end-inject-html}[ ]*\n?',
- multiLine: true);
- return rawDocs.replaceAllMapped(templateRegExp, (match) {
+ return rawDocs.replaceAllMapped(htmlRegExp, (match) {
String fragment = match[1];
String digest = sha1.convert(fragment.codeUnits).toString();
packageGraph._addHtmlFragment(digest, fragment);
@@ -4313,19 +4351,7 @@
/// value.
Iterable<String> _splitUpQuotedArgs(String argsAsString,
{bool convertToArgs = false}) {
- // Regexp to take care of splitting arguments, and handling the quotes
- // around arguments, if any.
- //
- // Match group 1 is the "foo=" (or "--foo=") part of the option, if any.
- // Match group 2 contains the quote character used (which is discarded).
- // Match group 3 is a quoted arg, if any, without the quotes.
- // Match group 4 is the unquoted arg, if any.
- final RegExp argMatcher = new RegExp(r'([a-zA-Z\-_0-9]+=)?' // option name
- r'(?:' // Start a new non-capture group for the two possibilities.
- r'''(["'])((?:\\{2})*|(?:.*?[^\\](?:\\{2})*))\2|''' // with quotes.
- r'([^ ]+))'); // without quotes.
final Iterable<Match> matches = argMatcher.allMatches(argsAsString);
-
// Remove quotes around args, and if convertToArgs is true, then for any
// args that look like assignments (start with valid option names followed
// by an equals sign), add a "--" in front so that they parse as options.
@@ -4634,7 +4660,7 @@
specialClasses = new SpecialClasses();
// Go through docs of every ModelElement in package to pre-build the macros
// index.
- allModelElements.forEach((m) => m.documentationLocal);
+ allModelElements.forEach((m) => m.precacheLocalDocsIfNeeded());
_localDocumentationBuilt = true;
// Scan all model elements to insure that interceptor and other special
@@ -6297,7 +6323,7 @@
Set<String> get features => super.features..addAll(comboFeatures);
@override
- String get computeDocumentationComment {
+ String _computeDocumentationComment() {
String docs = getterSetterDocumentationComment;
if (docs.isEmpty) return _variable.documentationComment;
return docs;
diff --git a/lib/src/special_elements.dart b/lib/src/special_elements.dart
index d599bc0..b2617a0 100644
--- a/lib/src/special_elements.dart
+++ b/lib/src/special_elements.dart
@@ -68,8 +68,8 @@
final Map<String, _SpecialClassDefinition> _specialClassDefinitions = {
'Object': new _SpecialClassDefinition(
SpecialClass.object, 'Object', 'dart.core', 'dart:core'),
- 'Interceptor': new _SpecialClassDefinition(SpecialClass.interceptor, 'Interceptor',
- '_interceptors', 'dart:_interceptors',
+ 'Interceptor': new _SpecialClassDefinition(SpecialClass.interceptor,
+ 'Interceptor', '_interceptors', 'dart:_interceptors',
required: false),
'pragma': new _SpecialClassDefinition(
SpecialClass.pragma, 'pragma', 'dart.core', 'dart:core',
diff --git a/lib/src/tool_runner.dart b/lib/src/tool_runner.dart
index 9653e16..aa79d4f 100644
--- a/lib/src/tool_runner.dart
+++ b/lib/src/tool_runner.dart
@@ -4,6 +4,7 @@
library dartdoc.tool_runner;
+import 'dart:async';
import 'dart:io';
import 'package:path/path.dart' as pathLib;
@@ -53,8 +54,14 @@
///
/// This will remove any temporary files created by the tool runner.
void dispose() {
- if (_temporaryDirectory != null && temporaryDirectory.existsSync())
- temporaryDirectory.deleteSync(recursive: true);
+ if (_temporaryDirectory != null) disposeAsync(_temporaryDirectory);
+ }
+
+ /// Avoid blocking on I/O for cleanups.
+ static Future<void> disposeAsync(Directory temporaryDirectory) async {
+ temporaryDirectory.exists().then((bool exists) {
+ if (exists) return temporaryDirectory.delete(recursive: true);
+ });
}
void _runSetup(
diff --git a/lib/src/utils.dart b/lib/src/utils.dart
index 3c53226..d732e50 100644
--- a/lib/src/utils.dart
+++ b/lib/src/utils.dart
@@ -5,8 +5,7 @@
final RegExp leadingWhiteSpace = new RegExp(r'^([ \t]*)[^ ]');
-String stripCommonWhitespace(String str) {
- StringBuffer buf = new StringBuffer();
+Iterable<String> stripCommonWhitespace(String str) sync* {
List<String> lines = str.split('\n');
int minimumSeen;
@@ -21,18 +20,13 @@
}
}
minimumSeen ??= 0;
- int lineno = 1;
for (String line in lines) {
if (line.length >= minimumSeen) {
- buf.write('${line.substring(minimumSeen)}\n');
+ yield '${line.substring(minimumSeen)}';
} else {
- if (lineno < lines.length) {
- buf.write('\n');
- }
+ yield '';
}
- ++lineno;
}
- return buf.toString();
}
String stripComments(String str) {
@@ -41,8 +35,7 @@
StringBuffer buf = new StringBuffer();
if (str.startsWith('///')) {
- str = stripCommonWhitespace(str);
- for (String line in str.split('\n')) {
+ for (String line in stripCommonWhitespace(str)) {
if (line.startsWith('/// ')) {
buf.write('${line.substring(4)}\n');
} else if (line.startsWith('///')) {
@@ -59,8 +52,7 @@
if (str.endsWith('*/')) {
str = str.substring(0, str.length - 2);
}
- str = stripCommonWhitespace(str);
- for (String line in str.split('\n')) {
+ for (String line in stripCommonWhitespace(str)) {
if (cStyle && line.startsWith('* ')) {
buf.write('${line.substring(2)}\n');
} else if (cStyle && line.startsWith('*')) {
diff --git a/test/compare_output_test.dart b/test/compare_output_test.dart
index d3572fd..82f93b3 100644
--- a/test/compare_output_test.dart
+++ b/test/compare_output_test.dart
@@ -67,7 +67,9 @@
'Top level package requires Flutter but FLUTTER_ROOT environment variable not set|test_package_flutter_plugin requires the Flutter SDK, version solving failed')));
expect(result.stderr, isNot(contains('asynchronous gap')));
expect(result.exitCode, isNot(0));
- }, skip: true /* TODO(gspencer): Re-enable as soon as Flutter's config is sane again. */ );
+ },
+ skip:
+ true /* TODO(gspencer): Re-enable as soon as Flutter's config is sane again. */);
test("Validate --version works", () async {
var args = <String>[dartdocBin, '--version'];
diff --git a/test/dartdoc_test.dart b/test/dartdoc_test.dart
index 1832de9..910254e 100644
--- a/test/dartdoc_test.dart
+++ b/test/dartdoc_test.dart
@@ -17,9 +17,10 @@
import 'src/utils.dart';
-class DartdocLoggingOptionContext extends DartdocGeneratorOptionContext with LoggingContext {
+class DartdocLoggingOptionContext extends DartdocGeneratorOptionContext
+ with LoggingContext {
DartdocLoggingOptionContext(DartdocOptionSet optionSet, Directory dir)
- : super(optionSet, dir);
+ : super(optionSet, dir);
}
void main() {
@@ -29,13 +30,17 @@
setUpAll(() async {
tempDir = Directory.systemTemp.createTempSync('dartdoc.test.');
outputParam = ['--output', tempDir.path];
- DartdocOptionSet optionSet = await DartdocOptionSet.fromOptionGenerators('dartdoc', [createLoggingOptions]);
+ DartdocOptionSet optionSet = await DartdocOptionSet.fromOptionGenerators(
+ 'dartdoc', [createLoggingOptions]);
optionSet.parseArguments([]);
- startLogging(new DartdocLoggingOptionContext(optionSet, Directory.current));
+ startLogging(
+ new DartdocLoggingOptionContext(optionSet, Directory.current));
});
tearDown(() async {
- tempDir.listSync().forEach((FileSystemEntity f) {f.deleteSync(recursive: true);});
+ tempDir.listSync().forEach((FileSystemEntity f) {
+ f.deleteSync(recursive: true);
+ });
});
Future<Dartdoc> buildDartdoc(
@@ -68,7 +73,8 @@
});
test('examplePathPrefix', () async {
- Class UseAnExampleHere = p.allCanonicalModelElements.whereType<Class>()
+ Class UseAnExampleHere = p.allCanonicalModelElements
+ .whereType<Class>()
.firstWhere((ModelElement c) => c.name == 'UseAnExampleHere');
expect(
UseAnExampleHere.documentationAsHtml,
@@ -77,7 +83,8 @@
});
test('includeExternal and showUndocumentedCategories', () async {
- Class Something = p.allCanonicalModelElements.whereType<Class>()
+ Class Something = p.allCanonicalModelElements
+ .whereType<Class>()
.firstWhere((ModelElement c) => c.name == 'Something');
expect(Something.isPublic, isTrue);
expect(Something.displayedCategories, isNotEmpty);
@@ -91,9 +98,9 @@
Iterable<String> unresolvedToolErrors = p
.packageWarningCounter.countedWarnings.values
.expand<String>((Set<Tuple2<PackageWarning, String>> s) => s
- .where((Tuple2<PackageWarning, String> t) =>
- t.item1 == PackageWarning.toolError)
- .map<String>((Tuple2<PackageWarning, String> t) => t.item2));
+ .where((Tuple2<PackageWarning, String> t) =>
+ t.item1 == PackageWarning.toolError)
+ .map<String>((Tuple2<PackageWarning, String> t) => t.item2));
expect(p.packageWarningCounter.errorCount, equals(1));
expect(unresolvedToolErrors.length, equals(1));
diff --git a/test/model_test.dart b/test/model_test.dart
index 38d0cc8..3745e51 100644
--- a/test/model_test.dart
+++ b/test/model_test.dart
@@ -148,9 +148,13 @@
});
test('can invoke a tool multiple times in one comment block', () {
RegExp envLine = RegExp(r'^Env: \{', multiLine: true);
- expect(envLine.allMatches(invokeToolMultipleSections.documentation).length, equals(2));
+ expect(
+ envLine.allMatches(invokeToolMultipleSections.documentation).length,
+ equals(2));
RegExp argLine = RegExp(r'^Args: \[', multiLine: true);
- expect(argLine.allMatches(invokeToolMultipleSections.documentation).length, equals(2));
+ expect(
+ argLine.allMatches(invokeToolMultipleSections.documentation).length,
+ equals(2));
expect(invokeToolMultipleSections.documentation,
contains('Invokes more than one tool in the same comment block.'));
expect(invokeToolMultipleSections.documentation,
@@ -204,11 +208,15 @@
});
test("can inject HTML from tool", () {
RegExp envLine = RegExp(r'^Env: \{', multiLine: true);
- expect(envLine.allMatches(injectHtmlFromTool.documentation).length, equals(2));
+ expect(envLine.allMatches(injectHtmlFromTool.documentation).length,
+ equals(2));
RegExp argLine = RegExp(r'^Args: \[', multiLine: true);
- expect(argLine.allMatches(injectHtmlFromTool.documentation).length, equals(2));
- expect(injectHtmlFromTool.documentation,
- contains('Invokes more than one tool in the same comment block, and injects HTML.'));
+ expect(argLine.allMatches(injectHtmlFromTool.documentation).length,
+ equals(2));
+ expect(
+ injectHtmlFromTool.documentation,
+ contains(
+ 'Invokes more than one tool in the same comment block, and injects HTML.'));
expect(injectHtmlFromTool.documentationAsHtml,
contains('<div class="title">Title</div>'));
expect(injectHtmlFromTool.documentationAsHtml,
@@ -746,12 +754,15 @@
.firstWhere((m) => m.name == 'withPrivateMacro');
withUndefinedMacro = dog.allInstanceMethods
.firstWhere((m) => m.name == 'withUndefinedMacro');
- MacrosFromAccessors = fakeLibrary.enums.firstWhere((e) => e.name == 'MacrosFromAccessors');
- macroReferencedHere = MacrosFromAccessors.publicConstants.firstWhere((e) => e.name == 'macroReferencedHere');
+ MacrosFromAccessors =
+ fakeLibrary.enums.firstWhere((e) => e.name == 'MacrosFromAccessors');
+ macroReferencedHere = MacrosFromAccessors.publicConstants
+ .firstWhere((e) => e.name == 'macroReferencedHere');
});
test("renders a macro defined within a enum", () {
- expect(macroReferencedHere.documentationAsHtml, contains('This is a macro defined in an Enum accessor.'));
+ expect(macroReferencedHere.documentationAsHtml,
+ contains('This is a macro defined in an Enum accessor.'));
});
test("renders a macro within the same comment where it's defined", () {
@@ -790,21 +801,29 @@
setUpAll(() {
documentationErrors = errorLibrary.classes
- .firstWhere((c) => c.name == 'DocumentationErrors');
+ .firstWhere((c) => c.name == 'DocumentationErrors')
+ ..documentation;
withInvalidNamedAnimation = documentationErrors.allInstanceMethods
- .firstWhere((m) => m.name == 'withInvalidNamedAnimation');
+ .firstWhere((m) => m.name == 'withInvalidNamedAnimation')
+ ..documentation;
withAnimationNonUnique = documentationErrors.allInstanceMethods
- .firstWhere((m) => m.name == 'withAnimationNonUnique');
+ .firstWhere((m) => m.name == 'withAnimationNonUnique')
+ ..documentation;
withAnimationNonUniqueDeprecated = documentationErrors.allInstanceMethods
- .firstWhere((m) => m.name == 'withAnimationNonUniqueDeprecated');
+ .firstWhere((m) => m.name == 'withAnimationNonUniqueDeprecated')
+ ..documentation;
withAnimationWrongParams = documentationErrors.allInstanceMethods
- .firstWhere((m) => m.name == 'withAnimationWrongParams');
+ .firstWhere((m) => m.name == 'withAnimationWrongParams')
+ ..documentation;
withAnimationBadWidth = documentationErrors.allInstanceMethods
- .firstWhere((m) => m.name == 'withAnimationBadWidth');
+ .firstWhere((m) => m.name == 'withAnimationBadWidth')
+ ..documentation;
withAnimationBadHeight = documentationErrors.allInstanceMethods
- .firstWhere((m) => m.name == 'withAnimationBadHeight');
+ .firstWhere((m) => m.name == 'withAnimationBadHeight')
+ ..documentation;
withAnimationUnknownArg = documentationErrors.allInstanceMethods
- .firstWhere((m) => m.name == 'withAnimationUnknownArg');
+ .firstWhere((m) => m.name == 'withAnimationUnknownArg')
+ ..documentation;
});
test("warns with invalidly-named animation within the method documentation",
@@ -1412,7 +1431,8 @@
});
test(('Verify mixin member is available in findRefElementCache'), () {
- expect(packageGraph.findRefElementCache['GenericMixin.mixinMember'], isNotEmpty);
+ expect(packageGraph.findRefElementCache['GenericMixin.mixinMember'],
+ isNotEmpty);
});
test(('Verify inheritance/mixin structure and type inference'), () {
@@ -2482,8 +2502,8 @@
});
test('Docs from inherited implicit accessors are preserved', () {
- expect(explicitGetterImplicitSetter.setter.computeDocumentationComment,
- isNot(''));
+ expect(
+ explicitGetterImplicitSetter.setter.documentationComment, isNot(''));
});
test('@nodoc on simple property works', () {
@@ -2506,7 +2526,7 @@
() {
expect(documentedPartialFieldInSubclassOnly.isPublic, isTrue);
expect(documentedPartialFieldInSubclassOnly.readOnly, isTrue);
- expect(documentedPartialFieldInSubclassOnly.computeDocumentationComment,
+ expect(documentedPartialFieldInSubclassOnly.documentationComment,
contains('This getter is documented'));
expect(
documentedPartialFieldInSubclassOnly.annotations
@@ -2517,7 +2537,7 @@
test('@nodoc overridden in subclass for getter works', () {
expect(explicitNonDocumentedInBaseClassGetter.isPublic, isTrue);
expect(explicitNonDocumentedInBaseClassGetter.hasPublicGetter, isTrue);
- expect(explicitNonDocumentedInBaseClassGetter.computeDocumentationComment,
+ expect(explicitNonDocumentedInBaseClassGetter.documentationComment,
contains('I should be documented'));
expect(explicitNonDocumentedInBaseClassGetter.readOnly, isTrue);
});
@@ -2804,14 +2824,14 @@
test('@nodoc on setter only works', () {
expect(nodocSetter.isPublic, isTrue);
expect(nodocSetter.readOnly, isTrue);
- expect(nodocSetter.computeDocumentationComment,
+ expect(nodocSetter.documentationComment,
equals('Getter docs should be shown.'));
});
test('@nodoc on getter only works', () {
expect(nodocGetter.isPublic, isTrue);
expect(nodocGetter.writeOnly, isTrue);
- expect(nodocGetter.computeDocumentationComment,
+ expect(nodocGetter.documentationComment,
equals('Setter docs should be shown.'));
});
diff --git a/test/src/utils.dart b/test/src/utils.dart
index 68fd79b..2002660 100644
--- a/test/src/utils.dart
+++ b/test/src/utils.dart
@@ -81,7 +81,8 @@
additionalArguments: additionalArguments);
testPackageGraphErrors = await bootBasicPackage(
- 'testing/test_package_doc_errors', ['css', 'code_in_comments', 'excluded'],
+ 'testing/test_package_doc_errors',
+ ['css', 'code_in_comments', 'excluded'],
additionalArguments: additionalArguments);
testPackageGraphSdk = await bootSdkPackage();
}
diff --git a/test/utils_test.dart b/test/utils_test.dart
index 59539c2..9ef899c 100644
--- a/test/utils_test.dart
+++ b/test/utils_test.dart
@@ -207,7 +207,7 @@
'2 spaces, one tab (same as 3 space)\n'
' \t4 spaces, one tab (preserve the tab)\n'
'3 space indent again\n';
- expect(stripCommonWhitespace(input), equals(output));
+ expect(stripCommonWhitespace(input).join('\n'), equals(output));
});
});
}
diff --git a/testing/test_package/bin/setup.dart b/testing/test_package/bin/setup.dart
index 67a6ca1..f67b442 100644
--- a/testing/test_package/bin/setup.dart
+++ b/testing/test_package/bin/setup.dart
@@ -10,7 +10,7 @@
void main(List<String> args) {
assert(args.isNotEmpty);
// Just touch the file given on the command line.
- File setupFile = new File(args[0])..createSync(recursive:true);
+ File setupFile = new File(args[0])..createSync(recursive: true);
setupFile.writeAsStringSync('setup');
exit(0);
}