diff --git a/lib/src/markdown_processor.dart b/lib/src/markdown_processor.dart index 5fab0eece2..e753506930 100644 --- a/lib/src/markdown_processor.dart +++ b/lib/src/markdown_processor.dart @@ -13,7 +13,6 @@ import 'package:dartdoc/src/element_type.dart'; import 'package:dartdoc/src/model/model.dart'; import 'package:dartdoc/src/tuple.dart'; import 'package:dartdoc/src/warnings.dart'; -import 'package:html/parser.dart' show parse; import 'package:markdown/markdown.dart' as md; const validHtmlTags = [ @@ -757,23 +756,23 @@ class _MarkdownCommentReference { } } -String _linkDocReference(String codeRef, Warnable warnable, +md.Node _makeLinkNode(String codeRef, Warnable warnable, List commentRefs) { - MatchingLinkResult result; - result = _getMatchingLinkElement(codeRef, warnable, commentRefs); + MatchingLinkResult result = + _getMatchingLinkElement(codeRef, warnable, commentRefs); + final textContent = htmlEscape.convert(codeRef); final ModelElement linkedElement = result.element; if (linkedElement != null) { - var classContent = ''; - if (linkedElement.isDeprecated) { - classContent = 'class="deprecated" '; - } - // This would be linkedElement.linkedName, but link bodies are slightly - // different for doc references. - if (linkedElement.href == null) { - return '${htmlEscape.convert(codeRef)}'; - } else { - return '${htmlEscape.convert(codeRef)}'; + if (linkedElement.href != null) { + var anchor = md.Element.text('a', textContent); + if (linkedElement.isDeprecated) { + anchor.attributes['class'] = 'deprecated'; + } + anchor.attributes['href'] = linkedElement.href; + return anchor; } + // else this would be linkedElement.linkedName, but link bodies are slightly + // different for doc references, so fall out. } else { if (result.warn) { // Avoid claiming documentation is inherited when it comes from the @@ -784,8 +783,9 @@ String _linkDocReference(String codeRef, Warnable warnable, ? null : warnable.documentationFrom); } - return '${htmlEscape.convert(codeRef)}'; } + + return md.Element.text('code', textContent); } // Maximum number of characters to display before a suspected generic. @@ -800,7 +800,7 @@ final RegExp allAfterLastNewline = RegExp(r'\n.*$', multiLine: true); // (like, [Apple]). @Hixie asked for a warning when there's something, that looks // like a non HTML tag (a generic?) outside of a `[]` block. // https://github.com/dart-lang/dartdoc/issues/1250#issuecomment-269257942 -void _showWarningsForGenericsOutsideSquareBracketsBlocks( +void showWarningsForGenericsOutsideSquareBracketsBlocks( String text, Warnable element) { List tagPositions = findFreeHangingGenericsPositions(text); if (tagPositions.isNotEmpty) { @@ -851,6 +851,21 @@ List findFreeHangingGenericsPositions(String string) { } class MarkdownDocument extends md.Document { + factory MarkdownDocument.withElementLinkResolver( + Canonicalization element, List commentRefs) { + md.Node linkResolver(String name, [String _]) { + if (name.isEmpty) { + return null; + } + return _makeLinkNode(name, element, commentRefs); + } + + return MarkdownDocument( + inlineSyntaxes: _markdown_syntaxes, + blockSyntaxes: _markdown_block_syntaxes, + linkResolver: linkResolver); + } + MarkdownDocument( {Iterable blockSyntaxes, Iterable inlineSyntaxes, @@ -864,43 +879,24 @@ class MarkdownDocument extends md.Document { linkResolver: linkResolver, imageLinkResolver: imageLinkResolver); - /// Returns a tuple of longHtml, shortHtml. longHtml is NULL if [processFullDocs] is true. - static Tuple2 _renderNodesToHtml( - List nodes, bool processFullDocs) { - var rawHtml = md.HtmlRenderer().render(nodes); - var asHtmlDocument = parse(rawHtml); - for (var s in asHtmlDocument.querySelectorAll('script')) { - s.remove(); - } - for (var pre in asHtmlDocument.querySelectorAll('pre')) { - if (pre.children.isNotEmpty && - pre.children.length != 1 && - pre.children.first.localName != 'code') { - continue; - } - - if (pre.children.isNotEmpty && pre.children.first.localName == 'code') { - var code = pre.children.first; - pre.classes - .addAll(code.classes.where((name) => name.startsWith('language-'))); + /// Returns a tuple of List and hasExtendedContent + Tuple2, bool> parseMarkdownText( + String text, bool processFullText) { + bool hasExtendedContent = false; + List lines = LineSplitter.split(text).toList(); + md.Node firstNode; + List nodes = []; + for (md.Node node + in IterableBlockParser(lines, this).parseLinesGenerator()) { + if (firstNode != null) { + hasExtendedContent = true; + if (!processFullText) break; } - - bool specifiesLanguage = pre.classes.isNotEmpty; - // Assume the user intended Dart if there are no other classes present. - if (!specifiesLanguage) pre.classes.add('language-dart'); - } - String asHtml; - String asOneLiner; - - if (processFullDocs) { - // `trim` fixes issue with line ending differences between mac and windows. - asHtml = asHtmlDocument.body.innerHtml?.trim(); + firstNode ??= node; + nodes.add(node); } - asOneLiner = asHtmlDocument.body.children.isEmpty - ? '' - : asHtmlDocument.body.children.first.innerHtml; - - return Tuple2(asHtml, asOneLiner); + _parseInlineContent(nodes); + return Tuple2(nodes, hasExtendedContent); } // From package:markdown/src/document.dart @@ -919,112 +915,6 @@ class MarkdownDocument extends md.Document { } } } - - /// Returns a tuple of longHtml, shortHtml (longHtml is NULL if !processFullDocs) - Tuple3 renderLinesToHtml( - List lines, bool processFullDocs) { - bool hasExtendedDocs = false; - md.Node firstNode; - List nodes = []; - for (md.Node node - in IterableBlockParser(lines, this).parseLinesGenerator()) { - if (firstNode != null) { - hasExtendedDocs = true; - if (!processFullDocs) break; - } - 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 = ''; - } - } - return Tuple3(longHtml, shortHtml, hasExtendedDocs); - } -} - -class Documentation { - final Canonicalization _element; - - Documentation.forElement(this._element); - - bool _hasExtendedDocs; - - bool get hasExtendedDocs { - if (_hasExtendedDocs == null) { - _renderHtmlForDartdoc(_element.isCanonical && _asHtml == null); - } - return _hasExtendedDocs; - } - - 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; - } - - List get commentRefs => _element.commentRefs; - - void _renderHtmlForDartdoc(bool processAllDocs) { - Tuple3 renderResults = - _renderMarkdownToHtml(processAllDocs); - if (processAllDocs) { - _asHtml = renderResults.item1; - } - 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 _renderMarkdownToHtml(bool processFullDocs) { - md.Node _linkResolver(String name, [String _]) { - if (name.isEmpty) { - return null; - } - return md.Text(_linkDocReference(name, _element, commentRefs)); - } - - String text = _element.documentation; - _showWarningsForGenericsOutsideSquareBracketsBlocks(text, _element); - MarkdownDocument document = MarkdownDocument( - inlineSyntaxes: _markdown_syntaxes, - blockSyntaxes: _markdown_block_syntaxes, - linkResolver: _linkResolver); - List lines = LineSplitter.split(text).toList(); - return document.renderLinesToHtml(lines, processFullDocs); - } } class _InlineCodeSyntax extends md.InlineSyntax { diff --git a/lib/src/model/documentable.dart b/lib/src/model/documentable.dart index 52addfc02f..49a6886297 100644 --- a/lib/src/model/documentable.dart +++ b/lib/src/model/documentable.dart @@ -3,7 +3,6 @@ // BSD-style license that can be found in the LICENSE file. import 'package:dartdoc/src/dartdoc_options.dart'; -import 'package:dartdoc/src/markdown_processor.dart'; import 'package:dartdoc/src/package_meta.dart'; import 'package:path/path.dart' as path; diff --git a/lib/src/model/documentation.dart b/lib/src/model/documentation.dart new file mode 100644 index 0000000000..b22fa09315 --- /dev/null +++ b/lib/src/model/documentation.dart @@ -0,0 +1,74 @@ +// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:dartdoc/src/markdown_processor.dart'; +import 'package:dartdoc/src/model/model.dart'; +import 'package:dartdoc/src/render/documentation_renderer.dart'; +import 'package:dartdoc/src/tuple.dart'; +import 'package:markdown/markdown.dart' as md; + +class Documentation { + final Canonicalization _element; + + Documentation.forElement(this._element); + + bool _hasExtendedDocs; + + bool get hasExtendedDocs { + if (_hasExtendedDocs == null) { + _renderDocumentation(_element.isCanonical && _asHtml == null); + } + return _hasExtendedDocs; + } + + String _asHtml; + + String get asHtml { + if (_asHtml == null) { + assert(_asOneLiner == null || _element.isCanonical); + _renderDocumentation(true); + } + return _asHtml; + } + + String _asOneLiner; + + String get asOneLiner { + if (_asOneLiner == null) { + assert(_asHtml == null); + _renderDocumentation(_element.isCanonical); + } + return _asOneLiner; + } + + List get commentRefs => _element.commentRefs; + + void _renderDocumentation(bool processAllDocs) { + Tuple2, bool> parseResult = + _parseDocumentation(processAllDocs); + if (_hasExtendedDocs != null) { + assert(_hasExtendedDocs == parseResult.item2); + } + _hasExtendedDocs = parseResult.item2; + + Tuple2 renderResult = + DocumentationRendererHtml().render(parseResult.item1, processAllDocs); + + if (processAllDocs) { + _asHtml = renderResult.item1; + } + if (_asOneLiner == null) { + _asOneLiner = renderResult.item2; + } + } + + /// Returns a tuple of List and hasExtendedDocs + Tuple2, bool> _parseDocumentation(bool processFullDocs) { + String text = _element.documentation; + showWarningsForGenericsOutsideSquareBracketsBlocks(text, _element); + MarkdownDocument document = + MarkdownDocument.withElementLinkResolver(_element, commentRefs); + return document.parseMarkdownText(text, processFullDocs); + } +} diff --git a/lib/src/model/model.dart b/lib/src/model/model.dart index d691c68575..1de8844c88 100644 --- a/lib/src/model/model.dart +++ b/lib/src/model/model.dart @@ -11,6 +11,7 @@ export 'constructor.dart'; export 'container.dart'; export 'container_member.dart'; export 'documentable.dart'; +export 'documentation.dart'; export 'dynamic.dart'; export 'enclosed_element.dart'; export 'enum.dart'; diff --git a/lib/src/model/model_element.dart b/lib/src/model/model_element.dart index e2e0f4f4c6..08835728f9 100644 --- a/lib/src/model/model_element.dart +++ b/lib/src/model/model_element.dart @@ -23,7 +23,6 @@ import 'package:crypto/crypto.dart'; import 'package:dartdoc/src/dartdoc_options.dart'; import 'package:dartdoc/src/element_type.dart'; import 'package:dartdoc/src/logging.dart'; -import 'package:dartdoc/src/markdown_processor.dart' show Documentation; import 'package:dartdoc/src/model/model.dart'; import 'package:dartdoc/src/model_utils.dart' as utils; import 'package:dartdoc/src/render/parameter_renderer.dart'; diff --git a/lib/src/model/package.dart b/lib/src/model/package.dart index 4cc18d6519..23b091280f 100644 --- a/lib/src/model/package.dart +++ b/lib/src/model/package.dart @@ -6,7 +6,6 @@ import 'dart:io'; import 'package:analyzer/dart/element/element.dart'; import 'package:dartdoc/src/dartdoc_options.dart'; -import 'package:dartdoc/src/markdown_processor.dart'; import 'package:dartdoc/src/model/model.dart'; import 'package:dartdoc/src/package_meta.dart'; import 'package:dartdoc/src/warnings.dart'; diff --git a/lib/src/render/documentation_renderer.dart b/lib/src/render/documentation_renderer.dart new file mode 100644 index 0000000000..44466ea426 --- /dev/null +++ b/lib/src/render/documentation_renderer.dart @@ -0,0 +1,51 @@ +// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:dartdoc/src/tuple.dart'; +import 'package:html/parser.dart' show parse; +import 'package:markdown/markdown.dart' as md; + +abstract class DocumentationRenderer { + Tuple2 render(List nodes, bool processFullDocs); +} + +class DocumentationRendererHtml extends DocumentationRenderer { + @override + Tuple2 render(List nodes, bool processFullDocs) { + var rawHtml = md.HtmlRenderer().render(nodes); + var asHtmlDocument = parse(rawHtml); + for (var s in asHtmlDocument.querySelectorAll('script')) { + s.remove(); + } + for (var pre in asHtmlDocument.querySelectorAll('pre')) { + if (pre.children.isNotEmpty && + pre.children.length != 1 && + pre.children.first.localName != 'code') { + continue; + } + + if (pre.children.isNotEmpty && pre.children.first.localName == 'code') { + var code = pre.children.first; + pre.classes + .addAll(code.classes.where((name) => name.startsWith('language-'))); + } + + bool specifiesLanguage = pre.classes.isNotEmpty; + // Assume the user intended Dart if there are no other classes present. + if (!specifiesLanguage) pre.classes.add('language-dart'); + } + String asHtml; + String asOneLiner; + + 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; + + return Tuple2(asHtml, asOneLiner); + } +}