Skip to content

Separate documentation parsing and rendering #2081

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

Merged
merged 1 commit into from
Dec 2, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
204 changes: 47 additions & 157 deletions lib/src/markdown_processor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
Expand Down Expand Up @@ -757,23 +756,23 @@ class _MarkdownCommentReference {
}
}

String _linkDocReference(String codeRef, Warnable warnable,
md.Node _makeLinkNode(String codeRef, Warnable warnable,
List<ModelCommentReference> 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 '<code>${htmlEscape.convert(codeRef)}</code>';
} else {
return '<a ${classContent}href="${linkedElement.href}">${htmlEscape.convert(codeRef)}</a>';
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
Expand All @@ -784,8 +783,9 @@ String _linkDocReference(String codeRef, Warnable warnable,
? null
: warnable.documentationFrom);
}
return '<code>${htmlEscape.convert(codeRef)}</code>';
}

return md.Element.text('code', textContent);
}

// Maximum number of characters to display before a suspected generic.
Expand All @@ -800,7 +800,7 @@ final RegExp allAfterLastNewline = RegExp(r'\n.*$', multiLine: true);
// (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.
// https://github.com/dart-lang/dartdoc/issues/1250#issuecomment-269257942
void _showWarningsForGenericsOutsideSquareBracketsBlocks(
void showWarningsForGenericsOutsideSquareBracketsBlocks(
String text, Warnable element) {
List<int> tagPositions = findFreeHangingGenericsPositions(text);
if (tagPositions.isNotEmpty) {
Expand Down Expand Up @@ -851,6 +851,21 @@ List<int> findFreeHangingGenericsPositions(String string) {
}

class MarkdownDocument extends md.Document {
factory MarkdownDocument.withElementLinkResolver(
Canonicalization element, List<ModelCommentReference> 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<md.BlockSyntax> blockSyntaxes,
Iterable<md.InlineSyntax> inlineSyntaxes,
Expand All @@ -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<String, String> _renderNodesToHtml(
List<md.Node> 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<md.Node> and hasExtendedContent
Tuple2<List<md.Node>, bool> parseMarkdownText(
String text, bool processFullText) {
bool hasExtendedContent = false;
List<String> lines = LineSplitter.split(text).toList();
md.Node firstNode;
List<md.Node> 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
Expand All @@ -919,112 +915,6 @@ class MarkdownDocument extends md.Document {
}
}
}

/// 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 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<String, String, bool>(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<ModelCommentReference> get commentRefs => _element.commentRefs;

void _renderHtmlForDartdoc(bool processAllDocs) {
Tuple3<String, String, bool> 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<String, String, bool> _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<String> lines = LineSplitter.split(text).toList();
return document.renderLinesToHtml(lines, processFullDocs);
}
}

class _InlineCodeSyntax extends md.InlineSyntax {
Expand Down
1 change: 0 additions & 1 deletion lib/src/model/documentable.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
74 changes: 74 additions & 0 deletions lib/src/model/documentation.dart
Original file line number Diff line number Diff line change
@@ -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<ModelCommentReference> get commentRefs => _element.commentRefs;

void _renderDocumentation(bool processAllDocs) {
Tuple2<List<md.Node>, bool> parseResult =
_parseDocumentation(processAllDocs);
if (_hasExtendedDocs != null) {
assert(_hasExtendedDocs == parseResult.item2);
}
_hasExtendedDocs = parseResult.item2;

Tuple2<String, String> renderResult =
DocumentationRendererHtml().render(parseResult.item1, processAllDocs);

if (processAllDocs) {
_asHtml = renderResult.item1;
}
if (_asOneLiner == null) {
_asOneLiner = renderResult.item2;
}
}

/// Returns a tuple of List<md.Node> and hasExtendedDocs
Tuple2<List<md.Node>, bool> _parseDocumentation(bool processFullDocs) {
String text = _element.documentation;
showWarningsForGenericsOutsideSquareBracketsBlocks(text, _element);
MarkdownDocument document =
MarkdownDocument.withElementLinkResolver(_element, commentRefs);
return document.parseMarkdownText(text, processFullDocs);
}
}
1 change: 1 addition & 0 deletions lib/src/model/model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
1 change: 0 additions & 1 deletion lib/src/model/model_element.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
1 change: 0 additions & 1 deletion lib/src/model/package.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
Loading