Skip to content

Commit 9b43ca0

Browse files
committed
fixes numerous issues with rendering doc comments
Closes #756 Closes #754 Closes #753 Closes #715 Closes #714 LGTM given by: @sethladd Squashed commit of the following: commit 6092023 Author: Seth Ladd <[email protected]> Date: Fri Jul 31 17:29:56 2015 -0700 removed unused import commit 37e4874 Author: Seth Ladd <[email protected]> Date: Fri Jul 31 17:22:34 2015 -0700 fixes numerous issues with rendering doc comments
1 parent dc30253 commit 9b43ca0

File tree

8 files changed

+185
-213
lines changed

8 files changed

+185
-213
lines changed

lib/markdown_processor.dart

Lines changed: 68 additions & 155 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import 'package:analyzer/src/generated/ast.dart';
99
import 'package:analyzer/src/generated/element.dart'
1010
show
1111
LibraryElement,
12+
Element,
1213
ConstructorElement,
1314
ClassMemberElement,
1415
PropertyAccessorElement;
@@ -27,44 +28,71 @@ final List<md.InlineSyntax> _markdown_syntaxes = [new _InlineCodeSyntax()];
2728
// We don't emit warnings currently: #572.
2829
const bool _emitWarning = false;
2930

30-
String renderMarkdownToHtml(String text, [ModelElement element]) {
31-
// TODO: `renderMarkdownToHtml` is never called with an element arg.
32-
// TODO(keertip): use this for the one liner.
33-
md.Node _linkResolver(String name) {
34-
NodeList<CommentReference> commentRefs = _getCommentRefs(element);
35-
if (commentRefs == null || commentRefs.isEmpty) {
36-
return new md.Text('[$name]');
31+
String _linkDocReference(String reference, ModelElement element,
32+
NodeList<CommentReference> commentRefs) {
33+
String link;
34+
// support for [new Constructor] and [new Class.namedCtr]
35+
var refs = reference.split(' ');
36+
if (refs.length == 2 && refs.first == 'new') {
37+
link = _getMatchingLink(refs[1], element, commentRefs, isConstructor: true);
38+
} else {
39+
link = _getMatchingLink(reference, element, commentRefs);
40+
}
41+
if (link != null && link.isNotEmpty) {
42+
return '<a href="$link">$reference</a>';
43+
} else {
44+
if (_emitWarning) {
45+
print(" warning: unresolved doc reference '$reference' (in $element)");
3746
}
38-
// support for [new Constructor] and [new Class.namedCtr]
39-
var link;
40-
var refs = name.split(' ');
41-
if (refs.length == 2 && refs.first == 'new') {
42-
link =
43-
_getMatchingLink(refs[1], element, commentRefs, isConstructor: true);
47+
return '<code>$reference</code>';
48+
}
49+
}
50+
51+
class Documentation {
52+
final ModelElement element;
53+
String _asHtml;
54+
String _asOneLiner;
55+
Document _asHtmlDocument;
56+
57+
Documentation(this.element) {
58+
_processDocsAsMarkdown();
59+
}
60+
61+
String get raw => this.element.documentation;
62+
63+
String get asHtml => _asHtml;
64+
65+
Document get asHtmlDocument => _asHtmlDocument;
66+
67+
String get asOneLiner => _asOneLiner;
68+
69+
void _processDocsAsMarkdown() {
70+
String tempHtml = renderMarkdownToHtml(raw, element);
71+
_asHtmlDocument = parse(tempHtml);
72+
_asHtmlDocument.querySelectorAll('script').forEach((s) => s.remove());
73+
_asHtmlDocument.querySelectorAll('code').forEach((e) {
74+
e.classes.addAll(['prettyprint', 'lang-dart']);
75+
});
76+
_asHtml = _asHtmlDocument.body.innerHtml;
77+
78+
if (_asHtmlDocument.body.children.isEmpty) {
79+
_asOneLiner = '';
4480
} else {
45-
link = _getMatchingLink(name, element, commentRefs);
81+
_asOneLiner = _asHtmlDocument.body.children.first.innerHtml;
4682
}
47-
if (link != null) {
48-
return new md.Text('<a href="$link">$name</a>');
49-
}
50-
return new md.Text('$name');
83+
}
84+
}
85+
86+
String renderMarkdownToHtml(String text, [ModelElement element]) {
87+
md.Node _linkResolver(String name) {
88+
NodeList<CommentReference> commentRefs = _getCommentRefs(element);
89+
return new md.Text(_linkDocReference(name, element, commentRefs));
5190
}
5291

5392
return md.markdownToHtml(text,
5493
inlineSyntaxes: _markdown_syntaxes, linkResolver: _linkResolver);
5594
}
5695

57-
String processDocsAsMarkdown(ModelElement element) {
58-
if (element == null || element.documentation == null) return '';
59-
String html = renderMarkdownToHtml(element.documentation, element);
60-
Document doc = parse(html);
61-
doc.querySelectorAll('script').forEach((s) => s.remove());
62-
doc.querySelectorAll('code').forEach((e) {
63-
e.classes.addAll(['prettyprint', 'lang-dart']);
64-
});
65-
return doc.body.innerHtml;
66-
}
67-
6896
class _InlineCodeSyntax extends md.InlineSyntax {
6997
_InlineCodeSyntax() : super(r'\[:\s?((?:.|\n)*?)\s?:\]');
7098

@@ -81,129 +109,6 @@ class _InlineCodeSyntax extends md.InlineSyntax {
81109

82110
const List<String> _oneLinerSkipTags = const ["code", "pre"];
83111

84-
String oneLinerWithoutReferences(String text) {
85-
if (text == null) return '';
86-
// Parse with Markdown, but only care about the first block or paragraph.
87-
Iterable<String> lines = text.replaceAll('\r\n', '\n').split('\n');
88-
md.Document document = new md.Document(inlineSyntaxes: _markdown_syntaxes);
89-
document.parseRefLinks(lines);
90-
List blocks = document.parseLines(lines);
91-
92-
while (blocks.isNotEmpty &&
93-
((blocks.first is md.Element &&
94-
_oneLinerSkipTags.contains(blocks.first.tag)) ||
95-
(blocks.first is md.Text && blocks.first.text.isEmpty))) {
96-
blocks.removeAt(0);
97-
}
98-
99-
if (blocks.isEmpty) return '';
100-
101-
String firstPara = new PlainTextRenderer().render([blocks.first]);
102-
return firstPara.trim();
103-
}
104-
105-
String oneLiner(ModelElement element) {
106-
if (element == null ||
107-
element.documentation == null ||
108-
element.documentation.isEmpty) return '';
109-
110-
return _resolveDocReferences(
111-
oneLinerWithoutReferences(element.documentation), element).trim();
112-
}
113-
114-
class PlainTextRenderer implements md.NodeVisitor {
115-
static final _BLOCK_TAGS =
116-
new RegExp('blockquote|h1|h2|h3|h4|h5|h6|hr|p|pre');
117-
118-
StringBuffer _buffer;
119-
120-
String render(List<md.Node> nodes) {
121-
_buffer = new StringBuffer();
122-
123-
for (final node in nodes) {
124-
node.accept(this);
125-
}
126-
127-
return _buffer.toString();
128-
}
129-
130-
@override
131-
void visitText(md.Text text) {
132-
_buffer.write(text.text);
133-
}
134-
135-
@override
136-
bool visitElementBefore(md.Element element) {
137-
// do nothing
138-
return true;
139-
}
140-
141-
@override
142-
void visitElementAfter(md.Element element) {
143-
// Hackish. Separate block-level elements with newlines.
144-
if (!_buffer.isEmpty && _BLOCK_TAGS.firstMatch(element.tag) != null) {
145-
_buffer.write('\n\n');
146-
}
147-
}
148-
}
149-
150-
String _replaceAllLinks(ModelElement element, String str,
151-
List<CommentReference> commentRefs, String findMatchingLink(
152-
String input, ModelElement element, List<CommentReference> commentRefs,
153-
{bool isConstructor})) {
154-
int lastWritten = 0;
155-
int index = str.indexOf(_leftChar);
156-
StringBuffer buf = new StringBuffer();
157-
158-
while (index != -1) {
159-
int end = str.indexOf(_rightChar, index + 1);
160-
if (end != -1) {
161-
if (index - lastWritten > 0) {
162-
buf.write(str.substring(lastWritten, index));
163-
}
164-
String codeRef = str.substring(index + _leftChar.length, end);
165-
if (codeRef != null) {
166-
var link;
167-
// support for [new Constructor] and [new Class.namedCtr]
168-
var refs = codeRef.split(' ');
169-
if (refs.length == 2 && refs.first == 'new') {
170-
link = findMatchingLink(refs[1], element, commentRefs,
171-
isConstructor: true);
172-
} else {
173-
link = findMatchingLink(codeRef, element, commentRefs);
174-
}
175-
if (link != null) {
176-
buf.write('<a href="$link">$codeRef</a>');
177-
} else {
178-
if (_emitWarning) {
179-
print(
180-
" warning: unresolved doc reference '$codeRef' (in $element)");
181-
}
182-
buf.write(codeRef);
183-
}
184-
}
185-
lastWritten = end + _rightChar.length;
186-
} else {
187-
break;
188-
}
189-
index = str.indexOf(_leftChar, end + 1);
190-
}
191-
if (lastWritten < str.length) {
192-
buf.write(str.substring(lastWritten, str.length));
193-
}
194-
return buf.toString();
195-
}
196-
197-
String _resolveDocReferences(String docsAfterMarkdown, ModelElement element) {
198-
NodeList<CommentReference> commentRefs = _getCommentRefs(element);
199-
if (commentRefs == null || commentRefs.isEmpty) {
200-
return docsAfterMarkdown;
201-
}
202-
203-
return _replaceAllLinks(
204-
element, docsAfterMarkdown, commentRefs, _getMatchingLink);
205-
}
206-
207112
NodeList<CommentReference> _getCommentRefs(ModelElement modelElement) {
208113
if (modelElement == null) return null;
209114
if (modelElement.documentation == null && modelElement.canOverride()) {
@@ -238,14 +143,17 @@ NodeList<CommentReference> _getCommentRefs(ModelElement modelElement) {
238143
return null;
239144
}
240145

146+
/// Returns null if element is a parameter.
241147
String _getMatchingLink(
242148
String codeRef, ModelElement element, List<CommentReference> commentRefs,
243149
{bool isConstructor: false}) {
244-
var refElement;
150+
if (commentRefs == null) return null;
151+
152+
Element refElement;
245153

246154
for (CommentReference ref in commentRefs) {
247155
if (ref.identifier.name == codeRef) {
248-
var isConstrElement = ref.identifier.staticElement is ConstructorElement;
156+
bool isConstrElement = ref.identifier.staticElement is ConstructorElement;
249157
if (isConstructor && isConstrElement ||
250158
!isConstructor && !isConstrElement) {
251159
refElement = ref.identifier.staticElement;
@@ -256,14 +164,19 @@ String _getMatchingLink(
256164

257165
if (refElement == null) return null;
258166

259-
var refLibrary;
167+
Library refLibrary;
260168
var e = refElement is ClassMemberElement ||
261169
refElement is PropertyAccessorElement
262170
? refElement.enclosingElement
263171
: refElement;
172+
173+
// If e is a ParameterElement, it's
174+
// never going to be in a library. So refLibrary is going to be null.
264175
refLibrary = element.package.libraries.firstWhere(
265176
(lib) => lib.hasInNamespace(e), orElse: () => null);
266177
if (refLibrary != null) {
178+
// Is there a way to pull this from a registry of known elements?
179+
// Seems like we're creating too many objects this way.
267180
return new ModelElement.from(refElement, refLibrary).href;
268181
}
269182
return null;

lib/src/html_generator.dart

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ library dartdoc.html_generator;
66

77
import 'dart:async' show Future;
88
import 'dart:io';
9-
import 'dart:profiler';
109

1110
import 'package:mustache4dart/mustache4dart.dart';
1211
import 'package:path/path.dart' as path;
@@ -21,8 +20,6 @@ import '../resource_loader.dart' as loader;
2120
typedef String TemplateRenderer(context,
2221
{bool assumeNullNonExistingProperty, bool errorOnMissingProperty});
2322

24-
final UserTag _HTML_GENERATE = new UserTag('HTML GENERATE');
25-
2623
// Generation order for libraries:
2724
// constants
2825
// typedefs
@@ -150,7 +147,6 @@ class HtmlGeneratorInstance {
150147
HtmlGeneratorInstance(this.url, this._templates, this.package, this.out);
151148

152149
Future generate() async {
153-
var previousTag = _HTML_GENERATE.makeCurrent();
154150
await _templates.init();
155151
if (!out.existsSync()) out.createSync();
156152
generatePackage();
@@ -214,8 +210,6 @@ class HtmlGeneratorInstance {
214210
//if (url != null) generateSiteMap();
215211

216212
await _copyResources();
217-
218-
previousTag.makeCurrent();
219213
}
220214

221215
void generatePackage() {

0 commit comments

Comments
 (0)