Skip to content

Commit e2b63ff

Browse files
authored
eliminate markdown generation when unneeded (#1446)
* intermediate stage more or less works, good for performance test * switch markdown back to normal * intermediate state: functional, tests pass * cleanup * dartfmt * Implement @devoncarew review comments * regen docs w/1.24.0-dev.6.5 rather than bleeding edge
1 parent 6baa5e6 commit e2b63ff

File tree

65 files changed

+958
-806
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

65 files changed

+958
-806
lines changed

lib/src/html/html_generator_instance.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ class HtmlGeneratorInstance implements HtmlOptions {
9696
generatePackage();
9797

9898
for (var lib in package.libraries) {
99+
//if (lib.name != 'flutter_test') continue;
99100
generateLibrary(package, lib);
100101

101102
for (var clazz in lib.allClasses) {

lib/src/markdown_processor.dart

Lines changed: 170 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,15 @@
66
library dartdoc.markdown_processor;
77

88
import 'dart:convert';
9+
import 'dart:io';
910
import 'dart:math';
1011

1112
import 'package:analyzer/dart/ast/ast.dart';
1213
import 'package:analyzer/dart/element/element.dart';
1314
import 'package:analyzer/src/dart/element/member.dart' show Member;
1415
import 'package:html/parser.dart' show parse;
1516
import 'package:markdown/markdown.dart' as md;
17+
import 'package:tuple/tuple.dart';
1618

1719
import 'model.dart';
1820

@@ -134,9 +136,6 @@ final RegExp isConstructor = new RegExp(r'^new[\s]+', multiLine: true);
134136
// Covers anything with leading digits/symbols, empty string, weird punctuation, spaces.
135137
final RegExp notARealDocReference = new RegExp(r'''(^[^\w]|^[\d]|[,"'/]|^$)''');
136138

137-
// We don't emit warnings currently: #572.
138-
const List<String> _oneLinerSkipTags = const ["code", "pre"];
139-
140139
final List<md.InlineSyntax> _markdown_syntaxes = [
141140
new _InlineCodeSyntax(),
142141
new _AutolinkWithoutScheme()
@@ -152,6 +151,22 @@ class MatchingLinkResult {
152151
MatchingLinkResult(this.element, this.label, {this.warn: true});
153152
}
154153

154+
class IterableBlockParser extends md.BlockParser {
155+
IterableBlockParser(lines, document) : super(lines, document);
156+
157+
Iterable<md.Node> parseLinesGenerator() sync* {
158+
while (!isDone) {
159+
for (var syntax in blockSyntaxes) {
160+
if (syntax.canParse(this)) {
161+
md.Node block = syntax.parse(this);
162+
if (block != null) yield (block);
163+
break;
164+
}
165+
}
166+
}
167+
}
168+
}
169+
155170
// Calculate a class hint for findCanonicalModelElementFor.
156171
ModelElement _getPreferredClass(ModelElement modelElement) {
157172
if (modelElement is EnclosedElement &&
@@ -667,23 +682,14 @@ String _linkDocReference(String codeRef, Documentable documentable,
667682
}
668683
}
669684

670-
String _renderMarkdownToHtml(Documentable element) {
671-
NodeList<CommentReference> commentRefs = _getCommentRefs(element);
672-
md.Node _linkResolver(String name) {
673-
return new md.Text(_linkDocReference(name, element, commentRefs));
674-
}
675-
676-
String text = element.documentation;
677-
_showWarningsForGenericsOutsideSquareBracketsBlocks(text, element);
678-
return md.markdownToHtml(text,
679-
inlineSyntaxes: _markdown_syntaxes, linkResolver: _linkResolver);
680-
}
681-
682685
// Maximum number of characters to display before a suspected generic.
683686
const maxPriorContext = 20;
684687
// Maximum number of characters to display after the beginning of a suspected generic.
685688
const maxPostContext = 30;
686689

690+
final RegExp allBeforeFirstNewline = new RegExp(r'^.*\n', multiLine: true);
691+
final RegExp allAfterLastNewline = new RegExp(r'\n.*$', multiLine: true);
692+
687693
// Generics should be wrapped into `[]` blocks, to avoid handling them as HTML tags
688694
// (like, [Apple<int>]). @Hixie asked for a warning when there's something, that looks
689695
// like a non HTML tag (a generic?) outside of a `[]` block.
@@ -697,10 +703,8 @@ void _showWarningsForGenericsOutsideSquareBracketsBlocks(
697703
"${text.substring(max(position - maxPriorContext, 0), position)}";
698704
String postContext =
699705
"${text.substring(position, min(position + maxPostContext, text.length))}";
700-
priorContext =
701-
priorContext.replaceAll(new RegExp(r'^.*\n', multiLine: true), '');
702-
postContext =
703-
postContext.replaceAll(new RegExp(r'\n.*$', multiLine: true), '');
706+
priorContext = priorContext.replaceAll(allBeforeFirstNewline, '');
707+
postContext = postContext.replaceAll(allAfterLastNewline, '');
704708
String errorMessage = "$priorContext$postContext";
705709
// TODO(jcollins-g): allow for more specific error location inside comments
706710
element.warn(PackageWarning.typeAsHtml, message: errorMessage);
@@ -740,19 +744,24 @@ List<int> findFreeHangingGenericsPositions(String string) {
740744
return results;
741745
}
742746

743-
class Documentation {
744-
final String raw;
745-
final String asHtml;
746-
final String asOneLiner;
747-
748-
factory Documentation.forElement(Documentable element) {
749-
String tempHtml = _renderMarkdownToHtml(element);
750-
return new Documentation._internal(element.documentation, tempHtml);
751-
}
752-
753-
Documentation._(this.raw, this.asHtml, this.asOneLiner);
754-
755-
factory Documentation._internal(String markdown, String rawHtml) {
747+
class MarkdownDocument extends md.Document {
748+
MarkdownDocument(
749+
{Iterable<md.BlockSyntax> blockSyntaxes,
750+
Iterable<md.InlineSyntax> inlineSyntaxes,
751+
md.ExtensionSet extensionSet,
752+
linkResolver,
753+
imageLinkResolver})
754+
: super(
755+
blockSyntaxes: blockSyntaxes,
756+
inlineSyntaxes: inlineSyntaxes,
757+
extensionSet: extensionSet,
758+
linkResolver: linkResolver,
759+
imageLinkResolver: imageLinkResolver);
760+
761+
/// Returns a tuple of longHtml, shortHtml. longHtml is NULL if [processFullDocs] is true.
762+
static Tuple2<String, String> _renderNodesToHtml(
763+
List<md.Node> nodes, bool processFullDocs) {
764+
var rawHtml = new md.HtmlRenderer().render(nodes);
756765
var asHtmlDocument = parse(rawHtml);
757766
for (var s in asHtmlDocument.querySelectorAll('script')) {
758767
s.remove();
@@ -775,16 +784,139 @@ class Documentation {
775784
// Assume the user intended Dart if there are no other classes present.
776785
if (!specifiesLanguage) pre.classes.add('language-dart');
777786
}
787+
String asHtml;
788+
String asOneLiner;
778789

779-
// `trim` fixes issue with line ending differences between mac and windows.
780-
var asHtml = asHtmlDocument.body.innerHtml?.trim();
781-
var asOneLiner = asHtmlDocument.body.children.isEmpty
790+
if (processFullDocs) {
791+
// `trim` fixes issue with line ending differences between mac and windows.
792+
asHtml = asHtmlDocument.body.innerHtml?.trim();
793+
}
794+
asOneLiner = asHtmlDocument.body.children.isEmpty
782795
? ''
783796
: asHtmlDocument.body.children.first.innerHtml;
784-
if (!asOneLiner.startsWith('<p>')) {
785-
asOneLiner = '<p>$asOneLiner</p>';
797+
798+
return new Tuple2(asHtml, asOneLiner);
799+
}
800+
801+
// From package:markdown/src/document.dart
802+
// TODO(jcollins-g): consider making this a public method in markdown package
803+
void _parseInlineContent(List<md.Node> nodes) {
804+
for (int i = 0; i < nodes.length; i++) {
805+
var node = nodes[i];
806+
if (node is md.UnparsedContent) {
807+
List<md.Node> inlineNodes =
808+
new md.InlineParser(node.textContent, this).parse();
809+
nodes.removeAt(i);
810+
nodes.insertAll(i, inlineNodes);
811+
i += inlineNodes.length - 1;
812+
} else if (node is md.Element && node.children != null) {
813+
_parseInlineContent(node.children);
814+
}
815+
}
816+
}
817+
818+
/// Returns a tuple of longHtml, shortHtml (longHtml is NULL if !processFullDocs)
819+
Tuple3<String, String, bool> renderLinesToHtml(
820+
List<String> lines, bool processFullDocs) {
821+
bool hasExtendedDocs = false;
822+
md.Node firstNode;
823+
List<md.Node> nodes = [];
824+
for (md.Node node
825+
in new IterableBlockParser(lines, this).parseLinesGenerator()) {
826+
if (firstNode != null) {
827+
hasExtendedDocs = true;
828+
if (!processFullDocs) break;
829+
}
830+
firstNode ??= node;
831+
nodes.add(node);
786832
}
787-
return new Documentation._(markdown, asHtml, asOneLiner);
833+
_parseInlineContent(nodes);
834+
835+
String shortHtml;
836+
String longHtml;
837+
if (processFullDocs) {
838+
Tuple2 htmls = _renderNodesToHtml(nodes, processFullDocs);
839+
longHtml = htmls.item1;
840+
shortHtml = htmls.item2;
841+
} else {
842+
if (firstNode != null) {
843+
Tuple2 htmls = _renderNodesToHtml([firstNode], processFullDocs);
844+
shortHtml = htmls.item2;
845+
} else {
846+
shortHtml = '';
847+
}
848+
}
849+
return new Tuple3<String, String, bool>(
850+
longHtml, shortHtml, hasExtendedDocs);
851+
}
852+
}
853+
854+
class Documentation {
855+
final Documentable _element;
856+
Documentation.forElement(this._element) {}
857+
858+
bool _hasExtendedDocs;
859+
bool get hasExtendedDocs {
860+
if (_hasExtendedDocs == null) {
861+
_renderHtmlForDartdoc(_element.isCanonical && _asHtml == null);
862+
}
863+
return _hasExtendedDocs;
864+
}
865+
866+
String _asHtml;
867+
String get asHtml {
868+
if (_asHtml == null) {
869+
assert(_asOneLiner == null || _element.isCanonical);
870+
_renderHtmlForDartdoc(true);
871+
}
872+
return _asHtml;
873+
}
874+
875+
String _asOneLiner;
876+
String get asOneLiner {
877+
if (_asOneLiner == null) {
878+
assert(_asHtml == null);
879+
_renderHtmlForDartdoc(_element.isCanonical);
880+
}
881+
return _asOneLiner;
882+
}
883+
884+
NodeList<CommentReference> _commentRefs;
885+
NodeList<CommentReference> get commentRefs {
886+
if (_commentRefs == null) _commentRefs = _getCommentRefs(_element);
887+
return _commentRefs;
888+
}
889+
890+
String get raw => _element.documentation;
891+
892+
void _renderHtmlForDartdoc(bool processAllDocs) {
893+
Tuple3<String, String, bool> renderResults =
894+
_renderMarkdownToHtml(processAllDocs);
895+
if (processAllDocs) {
896+
_asHtml = renderResults.item1;
897+
}
898+
if (_asOneLiner == null) {
899+
_asOneLiner = renderResults.item2;
900+
}
901+
if (_hasExtendedDocs != null) {
902+
assert(_hasExtendedDocs == renderResults.item3);
903+
}
904+
_hasExtendedDocs = renderResults.item3;
905+
}
906+
907+
/// Returns a tuple of longHtml, shortHtml, hasExtendedDocs
908+
/// (longHtml is NULL if !processFullDocs)
909+
Tuple3<String, String, bool> _renderMarkdownToHtml(bool processFullDocs) {
910+
md.Node _linkResolver(String name) {
911+
return new md.Text(_linkDocReference(name, _element, commentRefs));
912+
}
913+
914+
String text = _element.documentation;
915+
_showWarningsForGenericsOutsideSquareBracketsBlocks(text, _element);
916+
MarkdownDocument document = new MarkdownDocument(
917+
inlineSyntaxes: _markdown_syntaxes, linkResolver: _linkResolver);
918+
List<String> lines = text.replaceAll('\r\n', '\n').split('\n');
919+
return document.renderLinesToHtml(lines, processFullDocs);
788920
}
789921
}
790922

lib/src/model.dart

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,9 @@ class Accessor extends ModelElement
178178

179179
ModelElement get enclosingCombo => _enclosingCombo;
180180

181+
@override
182+
bool get isCanonical => enclosingCombo.isCanonical;
183+
181184
@override
182185
void warn(PackageWarning kind, {String message, Locatable referredFrom}) {
183186
if (enclosingCombo != null) {
@@ -903,6 +906,7 @@ abstract class Documentable implements Warnable {
903906
String get documentation;
904907
String get documentationAsHtml;
905908
bool get hasDocumentation;
909+
bool get hasExtendedDocumentation;
906910
String get oneLineDoc;
907911
Documentable get overriddenDocumentedElement;
908912
Package get package;
@@ -2092,6 +2096,10 @@ abstract class ModelElement implements Comparable, Nameable, Documentable {
20922096
bool get hasDocumentation =>
20932097
documentation != null && documentation.isNotEmpty;
20942098

2099+
@override
2100+
bool get hasExtendedDocumentation =>
2101+
href != null && _documentation.hasExtendedDocs;
2102+
20952103
bool get hasParameters => parameters.isNotEmpty;
20962104

20972105
/// If canonicalLibrary (or canonicalEnclosingElement, for Inheritable
@@ -2963,6 +2971,9 @@ class Package implements Nameable, Documentable {
29632971
@override
29642972
Documentable get documentationFrom => this;
29652973

2974+
@override
2975+
bool get hasExtendedDocumentation => documentation.isNotEmpty;
2976+
29662977
final Map<Element, Library> _elementToLibrary = {};
29672978
String _docsAsHtml;
29682979
final Map<String, String> _macros = {};

pubspec.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ packages:
138138
name: markdown
139139
url: "https://pub.dartlang.org"
140140
source: hosted
141-
version: "0.11.2"
141+
version: "0.11.3"
142142
matcher:
143143
description:
144144
name: matcher

test/model_test.dart

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ void main() {
179179
expect(
180180
fakeLibrary.oneLineDoc,
181181
equals(
182-
'<p>WOW FAKE PACKAGE IS <strong>BEST</strong> <a href="http://example.org">PACKAGE</a></p>'));
182+
'WOW FAKE PACKAGE IS <strong>BEST</strong> <a href="http://example.org">PACKAGE</a>'));
183183
});
184184

185185
test('has properties', () {
@@ -427,14 +427,22 @@ void main() {
427427
contains("['hello from dart']"));
428428
});
429429

430+
test('class without additional docs', () {
431+
expect(specialList.hasExtendedDocumentation, equals(false));
432+
});
433+
434+
test('class with additional docs', () {
435+
expect(Apple.hasExtendedDocumentation, equals(true));
436+
});
437+
430438
test('oneLine doc references in inherited methods should not have brackets',
431439
() {
432440
Method add =
433441
specialList.allInstanceMethods.firstWhere((m) => m.name == 'add');
434442
expect(
435443
add.oneLineDoc,
436444
equals(
437-
'<p>Adds <code>value</code> to the end of this list,\nextending the length by one.</p>'));
445+
'Adds <code>value</code> to the end of this list,\nextending the length by one.'));
438446
});
439447

440448
test(
@@ -515,7 +523,7 @@ void main() {
515523
expect(
516524
testingCodeSyntaxInOneLiners.oneLineDoc,
517525
equals(
518-
'<p>These are code syntaxes: <code>true</code> and <code>false</code></p>'));
526+
'These are code syntaxes: <code>true</code> and <code>false</code>'));
519527
});
520528

521529
test('doc comments to parameters are marked as code', () {

testing/test_package_docs/anonymous_library/anonymous_library-library.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ <h2>Functions</h2>
7777
</span>
7878
</dt>
7979
<dd>
80-
<p></p>
80+
8181

8282
</dd>
8383
</dl>

testing/test_package_docs/another_anonymous_lib/another_anonymous_lib-library.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ <h2>Functions</h2>
7777
</span>
7878
</dt>
7979
<dd>
80-
<p></p>
80+
8181

8282
</dd>
8383
</dl>

testing/test_package_docs/css/css-library.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ <h2>Properties</h2>
8080
</span>
8181
</dt>
8282
<dd>
83-
<p></p>
83+
8484
<div class="features">read / write</div>
8585
</dd>
8686
</dl>

0 commit comments

Comments
 (0)