-
Notifications
You must be signed in to change notification settings - Fork 132
eliminate markdown generation when unneeded #1446
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 6 commits
6b2b175
c715dd0
912d77b
799b741
4abf156
63e7579
b4b4c55
c430b4e
f6f42f3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,13 +6,15 @@ | |
| library dartdoc.markdown_processor; | ||
|
|
||
| import 'dart:convert'; | ||
| import 'dart:io'; | ||
| import 'dart:math'; | ||
|
|
||
| import 'package:analyzer/dart/ast/ast.dart'; | ||
| import 'package:analyzer/dart/element/element.dart'; | ||
| import 'package:analyzer/src/dart/element/member.dart' show Member; | ||
| import 'package:html/parser.dart' show parse; | ||
| import 'package:markdown/markdown.dart' as md; | ||
| import 'package:tuple/tuple.dart'; | ||
|
|
||
| import 'model.dart'; | ||
|
|
||
|
|
@@ -134,9 +136,6 @@ final RegExp isConstructor = new RegExp(r'^new[\s]+', multiLine: true); | |
| // Covers anything with leading digits/symbols, empty string, weird punctuation, spaces. | ||
| final RegExp notARealDocReference = new RegExp(r'''(^[^\w]|^[\d]|[,"'/]|^$)'''); | ||
|
|
||
| // We don't emit warnings currently: #572. | ||
| const List<String> _oneLinerSkipTags = const ["code", "pre"]; | ||
|
|
||
| final List<md.InlineSyntax> _markdown_syntaxes = [ | ||
| new _InlineCodeSyntax(), | ||
| new _AutolinkWithoutScheme() | ||
|
|
@@ -152,6 +151,22 @@ class MatchingLinkResult { | |
| MatchingLinkResult(this.element, this.label, {this.warn: true}); | ||
| } | ||
|
|
||
| class IterableBlockParser extends md.BlockParser { | ||
| IterableBlockParser(lines, document) : super(lines, document); | ||
|
|
||
| Iterable<md.Node> parseLinesGenerator() sync* { | ||
| while (!isDone) { | ||
| for (var syntax in blockSyntaxes) { | ||
| if (syntax.canParse(this)) { | ||
| md.Node block = syntax.parse(this); | ||
| if (block != null) yield (block); | ||
| break; | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // Calculate a class hint for findCanonicalModelElementFor. | ||
| ModelElement _getPreferredClass(ModelElement modelElement) { | ||
| if (modelElement is EnclosedElement && | ||
|
|
@@ -667,23 +682,14 @@ String _linkDocReference(String codeRef, Documentable documentable, | |
| } | ||
| } | ||
|
|
||
| String _renderMarkdownToHtml(Documentable element) { | ||
| NodeList<CommentReference> commentRefs = _getCommentRefs(element); | ||
| md.Node _linkResolver(String name) { | ||
| return new md.Text(_linkDocReference(name, element, commentRefs)); | ||
| } | ||
|
|
||
| String text = element.documentation; | ||
| _showWarningsForGenericsOutsideSquareBracketsBlocks(text, element); | ||
| return md.markdownToHtml(text, | ||
| inlineSyntaxes: _markdown_syntaxes, linkResolver: _linkResolver); | ||
| } | ||
|
|
||
| // Maximum number of characters to display before a suspected generic. | ||
| const maxPriorContext = 20; | ||
| // Maximum number of characters to display after the beginning of a suspected generic. | ||
| const maxPostContext = 30; | ||
|
|
||
| final RegExp allBeforeFirstNewline = new RegExp(r'^.*\n', multiLine: true); | ||
| final RegExp allAfterLastNewline = new RegExp(r'\n.*$', multiLine: true); | ||
|
|
||
| // Generics should be wrapped into `[]` blocks, to avoid handling them as HTML tags | ||
| // (like, [Apple<int>]). @Hixie asked for a warning when there's something, that looks | ||
| // like a non HTML tag (a generic?) outside of a `[]` block. | ||
|
|
@@ -697,10 +703,8 @@ void _showWarningsForGenericsOutsideSquareBracketsBlocks( | |
| "${text.substring(max(position - maxPriorContext, 0), position)}"; | ||
| String postContext = | ||
| "${text.substring(position, min(position + maxPostContext, text.length))}"; | ||
| priorContext = | ||
| priorContext.replaceAll(new RegExp(r'^.*\n', multiLine: true), ''); | ||
| postContext = | ||
| postContext.replaceAll(new RegExp(r'\n.*$', multiLine: true), ''); | ||
| priorContext = priorContext.replaceAll(allBeforeFirstNewline, ''); | ||
| postContext = postContext.replaceAll(allAfterLastNewline, ''); | ||
| String errorMessage = "$priorContext$postContext"; | ||
| // TODO(jcollins-g): allow for more specific error location inside comments | ||
| element.warn(PackageWarning.typeAsHtml, message: errorMessage); | ||
|
|
@@ -740,19 +744,24 @@ List<int> findFreeHangingGenericsPositions(String string) { | |
| return results; | ||
| } | ||
|
|
||
| class Documentation { | ||
| final String raw; | ||
| final String asHtml; | ||
| final String asOneLiner; | ||
|
|
||
| factory Documentation.forElement(Documentable element) { | ||
| String tempHtml = _renderMarkdownToHtml(element); | ||
| return new Documentation._internal(element.documentation, tempHtml); | ||
| } | ||
|
|
||
| Documentation._(this.raw, this.asHtml, this.asOneLiner); | ||
|
|
||
| factory Documentation._internal(String markdown, String rawHtml) { | ||
| class MarkdownDocument extends md.Document { | ||
| MarkdownDocument( | ||
| {Iterable<md.BlockSyntax> blockSyntaxes, | ||
| Iterable<md.InlineSyntax> inlineSyntaxes, | ||
| md.ExtensionSet extensionSet, | ||
| linkResolver, | ||
| imageLinkResolver}) | ||
| : super( | ||
| blockSyntaxes: blockSyntaxes, | ||
| inlineSyntaxes: inlineSyntaxes, | ||
| extensionSet: extensionSet, | ||
| linkResolver: linkResolver, | ||
| imageLinkResolver: imageLinkResolver); | ||
|
|
||
| /// Returns a tuple of longHtml, shortHtml. longHtml is NULL if [processFullDocs] is true. | ||
| static Tuple2<String, String> _renderNodesToHtml( | ||
| List<md.Node> nodes, bool processFullDocs) { | ||
| var rawHtml = new md.HtmlRenderer().render(nodes); | ||
| var asHtmlDocument = parse(rawHtml); | ||
| for (var s in asHtmlDocument.querySelectorAll('script')) { | ||
| s.remove(); | ||
|
|
@@ -775,17 +784,146 @@ class Documentation { | |
| // Assume the user intended Dart if there are no other classes present. | ||
| if (!specifiesLanguage) pre.classes.add('language-dart'); | ||
| } | ||
| String asHtml; | ||
| String asOneLiner; | ||
|
|
||
| // `trim` fixes issue with line ending differences between mac and windows. | ||
| var asHtml = asHtmlDocument.body.innerHtml?.trim(); | ||
| var asOneLiner = asHtmlDocument.body.children.isEmpty | ||
| if (processFullDocs) { | ||
| // `trim` fixes issue with line ending differences between mac and windows. | ||
| asHtml = asHtmlDocument.body.innerHtml?.trim(); | ||
| } | ||
| asOneLiner = asHtmlDocument.body.children.isEmpty | ||
| ? '' | ||
| : asHtmlDocument.body.children.first.innerHtml; | ||
| if (!asOneLiner.startsWith('<p>')) { | ||
| asOneLiner = '<p>$asOneLiner</p>'; | ||
|
|
||
| return new Tuple2(asHtml, asOneLiner); | ||
| } | ||
|
|
||
| // From package:markdown/src/document.dart | ||
| // TODO(jcollins-g): consider making this a public method in markdown package | ||
| void _parseInlineContent(List<md.Node> nodes) { | ||
| for (int i = 0; i < nodes.length; i++) { | ||
| var node = nodes[i]; | ||
| if (node is md.UnparsedContent) { | ||
| List<md.Node> inlineNodes = | ||
| new md.InlineParser(node.textContent, this).parse(); | ||
| nodes.removeAt(i); | ||
| nodes.insertAll(i, inlineNodes); | ||
| i += inlineNodes.length - 1; | ||
| } else if (node is md.Element && node.children != null) { | ||
| _parseInlineContent(node.children); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /// Returns a tuple of longHtml, shortHtml (longHtml is NULL if !processFullDocs) | ||
| Tuple3<String, String, bool> renderLinesToHtml( | ||
| List<String> lines, bool processFullDocs) { | ||
| bool hasExtendedDocs = false; | ||
| md.Node firstNode; | ||
| List<md.Node> nodes = []; | ||
| for (md.Node node | ||
| in new IterableBlockParser(lines, this).parseLinesGenerator()) { | ||
| if (firstNode != null) { | ||
| hasExtendedDocs = true; | ||
| if (!processFullDocs) break; | ||
| } | ||
| if (firstNode == null) { | ||
| firstNode = node; | ||
| } | ||
| nodes.add(node); | ||
| } | ||
| _parseInlineContent(nodes); | ||
|
|
||
| String shortHtml; | ||
| String longHtml; | ||
| if (processFullDocs) { | ||
| Tuple2 htmls = _renderNodesToHtml(nodes, processFullDocs); | ||
| longHtml = htmls.item1; | ||
| shortHtml = htmls.item2; | ||
| } else { | ||
| if (firstNode != null) { | ||
| Tuple2 htmls = _renderNodesToHtml([firstNode], processFullDocs); | ||
| shortHtml = htmls.item2; | ||
| } else { | ||
| shortHtml = ''; | ||
| } | ||
| } | ||
| if (!shortHtml.startsWith('<p>')) { | ||
|
||
| shortHtml = '<p>$shortHtml</p>'; | ||
| } | ||
| return new Tuple3<String, String, bool>( | ||
| longHtml, shortHtml, hasExtendedDocs); | ||
| } | ||
| } | ||
|
|
||
| class Documentation { | ||
| bool _hasExtendedDocs; | ||
| bool get hasExtendedDocs { | ||
| if (_hasExtendedDocs == null) { | ||
| _renderHtmlForDartdoc(_element.isCanonical && _asHtml == null); | ||
| } | ||
| return _hasExtendedDocs; | ||
| } | ||
|
|
||
| String get raw => _element.documentation; | ||
|
|
||
| String _asHtml; | ||
| String get asHtml { | ||
| if (_asHtml == null) { | ||
| assert(_asOneLiner == null || _element.isCanonical); | ||
| _renderHtmlForDartdoc(true); | ||
| } | ||
| return _asHtml; | ||
| } | ||
|
|
||
| String _asOneLiner; | ||
| String get asOneLiner { | ||
| if (_asOneLiner == null) { | ||
| assert(_asHtml == null); | ||
| _renderHtmlForDartdoc(_element.isCanonical); | ||
| } | ||
| return _asOneLiner; | ||
| } | ||
|
|
||
| NodeList<CommentReference> _commentRefs; | ||
| NodeList<CommentReference> get commentRefs { | ||
| if (_commentRefs == null) _commentRefs = _getCommentRefs(_element); | ||
| return _commentRefs; | ||
| } | ||
|
|
||
| final Documentable _element; | ||
|
|
||
| void _renderHtmlForDartdoc(bool processAllDocs) { | ||
| Tuple3<String, String, bool> renderResults = | ||
| _renderMarkdownToHtml(processAllDocs); | ||
| if (processAllDocs) { | ||
| _asHtml = renderResults.item1; | ||
| } | ||
| return new Documentation._(markdown, asHtml, asOneLiner); | ||
| if (_asOneLiner == null) { | ||
| _asOneLiner = renderResults.item2; | ||
| } | ||
| if (_hasExtendedDocs != null) { | ||
| assert(_hasExtendedDocs == renderResults.item3); | ||
| } | ||
| _hasExtendedDocs = renderResults.item3; | ||
| } | ||
|
|
||
| /// Returns a tuple of longHtml, shortHtml, hasExtendedDocs | ||
| /// (longHtml is NULL if !processFullDocs) | ||
| Tuple3<String, String, bool> _renderMarkdownToHtml(bool processFullDocs) { | ||
| md.Node _linkResolver(String name) { | ||
| return new md.Text(_linkDocReference(name, _element, commentRefs)); | ||
| } | ||
|
|
||
| String text = _element.documentation; | ||
| _showWarningsForGenericsOutsideSquareBracketsBlocks(text, _element); | ||
| MarkdownDocument document = new MarkdownDocument( | ||
| inlineSyntaxes: _markdown_syntaxes, linkResolver: _linkResolver); | ||
| List<String> lines = text.replaceAll('\r\n', '\n').split('\n'); | ||
| return document.renderLinesToHtml(lines, processFullDocs); | ||
| } | ||
|
|
||
| Documentation.forElement(this._element) {} | ||
|
||
| } | ||
|
|
||
| class _InlineCodeSyntax extends md.InlineSyntax { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -138,7 +138,7 @@ packages: | |
| name: markdown | ||
| url: "https://pub.dartlang.org" | ||
| source: hosted | ||
| version: "0.11.2" | ||
| version: "0.11.3" | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just want to confirm, that we're not using new API from
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Correct, not using any new APIs. |
||
| matcher: | ||
| description: | ||
| name: matcher | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could be
firstNode ??= nodeThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done