66library dartdoc.markdown_processor;
77
88import 'dart:convert' ;
9+ import 'dart:io' ;
910import 'dart:math' ;
1011
1112import 'package:analyzer/dart/ast/ast.dart' ;
1213import 'package:analyzer/dart/element/element.dart' ;
1314import 'package:analyzer/src/dart/element/member.dart' show Member;
1415import 'package:html/parser.dart' show parse;
1516import 'package:markdown/markdown.dart' as md;
17+ import 'package:tuple/tuple.dart' ;
1618
1719import '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.
135137final 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-
140139final 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.
156171ModelElement _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.
683686const maxPriorContext = 20 ;
684687// Maximum number of characters to display after the beginning of a suspected generic.
685688const 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
0 commit comments