Skip to content

Refactor more documentation comment generation into DocumentationComment #2817

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
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
48 changes: 28 additions & 20 deletions lib/src/generator/templates.runtime_renderers.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3928,6 +3928,26 @@ class _Renderer_DocumentationComment
parent: r);
},
),
'documentationLocal': Property(
getValue: (CT_ c) => c.documentationLocal,
renderVariable:
(CT_ c, Property<CT_> self, List<String> remainingNames) {
if (remainingNames.isEmpty) {
return self.getValue(c).toString();
}
var name = remainingNames.first;
var nextProperty =
_Renderer_String.propertyMap().getValue(name);
return nextProperty.renderVariable(self.getValue(c),
nextProperty, [...remainingNames.skip(1)]);
},
isNullValue: (CT_ c) => c.documentationLocal == null,
renderValue: (CT_ c, RendererBase<CT_> r,
List<MustachioNode> ast, StringSink sink) {
_render_String(c.documentationLocal, ast, r.template, sink,
parent: r);
},
),
'fullyQualifiedNameWithoutLibrary': Property(
getValue: (CT_ c) => c.fullyQualifiedNameWithoutLibrary,
renderVariable:
Expand Down Expand Up @@ -3971,6 +3991,13 @@ class _Renderer_DocumentationComment
getters: _invisibleGetters['ModelElementRenderer']);
},
),
'needsPrecache': Property(
getValue: (CT_ c) => c.needsPrecache,
renderVariable: (CT_ c, Property<CT_> self,
List<String> remainingNames) =>
self.renderSimpleVariable(c, remainingNames, 'bool'),
getBool: (CT_ c) => c.needsPrecache == true,
),
'pathContext': Property(
getValue: (CT_ c) => c.pathContext,
renderVariable: (CT_ c, Property<CT_> self,
Expand Down Expand Up @@ -4229,6 +4256,7 @@ class _Renderer_Enum extends RendererBase<Enum> {
CT_,
() => {
..._Renderer_InheritingContainer.propertyMap<CT_>(),
..._Renderer_TypeImplementing.propertyMap<CT_>(),
'inheritanceChain': Property(
getValue: (CT_ c) => c.inheritanceChain,
renderVariable: (CT_ c, Property<CT_> self,
Expand Down Expand Up @@ -9481,26 +9509,6 @@ class _Renderer_ModelElement extends RendererBase<ModelElement> {
parent: r));
},
),
'documentationLocal': Property(
getValue: (CT_ c) => c.documentationLocal,
renderVariable:
(CT_ c, Property<CT_> self, List<String> remainingNames) {
if (remainingNames.isEmpty) {
return self.getValue(c).toString();
}
var name = remainingNames.first;
var nextProperty =
_Renderer_String.propertyMap().getValue(name);
return nextProperty.renderVariable(self.getValue(c),
nextProperty, [...remainingNames.skip(1)]);
},
isNullValue: (CT_ c) => c.documentationLocal == null,
renderValue: (CT_ c, RendererBase<CT_> r,
List<MustachioNode> ast, StringSink sink) {
_render_String(c.documentationLocal, ast, r.template, sink,
parent: r);
},
),
'element': Property(
getValue: (CT_ c) => c.element,
renderVariable: (CT_ c, Property<CT_> self,
Expand Down
156 changes: 153 additions & 3 deletions lib/src/model/documentation_comment.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import 'package:args/args.dart';
import 'package:crypto/crypto.dart' as crypto;
import 'package:dartdoc/src/model/model.dart';
import 'package:dartdoc/src/model/documentable.dart';
import 'package:dartdoc/src/model/locatable.dart';
import 'package:dartdoc/src/model/source_code_mixin.dart';
import 'package:dartdoc/src/render/model_element_renderer.dart';
import 'package:dartdoc/src/utils.dart';
import 'package:dartdoc/src/warnings.dart';
Expand All @@ -23,9 +25,15 @@ final _basicToolPattern = RegExp(

final _examplePattern = RegExp(r'{@example\s+([^}]+)}');

final _macroRegExp = RegExp(r'{@macro\s+([^}]+)}');

final _htmlInjectRegExp = RegExp(r'<dartdoc-html>([a-f0-9]+)</dartdoc-html>');

final RegExp _needsPrecacheRegExp = RegExp(r'{@(template|tool|inject-html)');

/// Features for processing directives in a documentation comment.
///
/// [processCommentWithoutTools] and [processComment] are the primary
/// [_processCommentWithoutTools] and [processComment] are the primary
/// entrypoints.
mixin DocumentationComment
on Documentable, Warnable, Locatable, SourceCodeMixin {
Expand Down Expand Up @@ -59,7 +67,7 @@ mixin DocumentationComment

/// Process a [documentationComment], performing various actions based on
/// `{@}`-style directives, except `{@tool}`, returning the processed result.
String processCommentWithoutTools(String documentationComment) {
String _processCommentWithoutTools(String documentationComment) {
var docs = stripComments(documentationComment);
if (!docs.contains('{@')) {
_analyzeCodeBlocks(docs);
Expand All @@ -79,6 +87,7 @@ mixin DocumentationComment

/// Process [documentationComment], performing various actions based on
/// `{@}`-style directives, returning the processed result.
@visibleForTesting
Future<String> processComment(String documentationComment) async {
var docs = stripComments(documentationComment);
// Must evaluate tools first, in case they insert any other directives.
Expand Down Expand Up @@ -707,4 +716,145 @@ mixin DocumentationComment
}
});
}

/// Returns the documentation for this literal element unless
/// [config.dropTextFrom] indicates it should not be returned. Macro
/// definitions are stripped, but macros themselves are not injected. This
/// is a two stage process to avoid ordering problems.
String _documentationLocal;

String get documentationLocal =>
_documentationLocal ??= _buildDocumentationLocal();

/// Unconditionally precache local documentation.
///
/// Use only in factory for [PackageGraph].
Future<void> precacheLocalDocs() async {
_documentationLocal = await _buildDocumentationBase();
}

bool _needsPrecache;
bool get needsPrecache => _needsPrecache ??=
_needsPrecacheRegExp.hasMatch(documentationComment ?? '');

String _rawDocs;

String _buildDocumentationLocal() => _buildDocumentationBaseSync();

/// Override this to add more features to the documentation builder in a
/// subclass.
String buildDocumentationAddition(String docs) => docs ?? '';

/// Separate from _buildDocumentationLocal for overriding.
String _buildDocumentationBaseSync() {
assert(_rawDocs == null,
'reentrant calls to _buildDocumentation* not allowed');
// Do not use the sync method if we need to evaluate tools or templates.
assert(!isCanonical || !needsPrecache);
if (config.dropTextFrom.contains(element.library.name)) {
_rawDocs = '';
} else {
_rawDocs = _processCommentWithoutTools(documentationComment ?? '');
}
_rawDocs = buildDocumentationAddition(_rawDocs);
return _rawDocs;
}

/// Separate from _buildDocumentationLocal for overriding. Can only be
/// used as part of [PackageGraph.setUpPackageGraph].
Future<String> _buildDocumentationBase() async {
assert(_rawDocs == null,
'reentrant calls to _buildDocumentation* not allowed');
// Do not use the sync method if we need to evaluate tools or templates.
if (config.dropTextFrom.contains(element.library.name)) {
_rawDocs = '';
} else {
_rawDocs = await processComment(documentationComment ?? '');
}
_rawDocs = buildDocumentationAddition(_rawDocs);
return _rawDocs;
}

/// Replace &lt;<dartdoc-html>[digest]</dartdoc-html>&gt; in API comments with
/// the contents of the HTML fragment earlier defined by the
/// &#123;@inject-html&#125; directive. The [digest] is a SHA1 of the contents
/// of the HTML fragment, automatically generated upon parsing the
/// &#123;@inject-html&#125; directive.
///
/// This markup is generated and inserted by [_stripHtmlAndAddToIndex] when it
/// removes the HTML fragment in preparation for markdown processing. It isn't
/// meant to be used at a user level.
///
/// Example:
///
/// You place the fragment in a dartdoc comment:
///
/// Some comments
/// &#123;@inject-html&#125;
/// &lt;p&gt;[HTML contents!]&lt;/p&gt;
/// &#123;@endtemplate&#125;
/// More comments
///
/// and [_stripHtmlAndAddToIndex] will replace your HTML fragment with this:
///
/// Some comments
/// &lt;dartdoc-html&gt;4cc02f877240bf69855b4c7291aba8a16e5acce0&lt;/dartdoc-html&gt;
/// More comments
///
/// Which will render in the final HTML output as:
///
/// Some comments
/// &lt;p&gt;[HTML contents!]&lt;/p&gt;
/// More comments
///
/// And the HTML fragment will not have been processed or changed by Markdown,
/// but just injected verbatim.
String injectHtmlFragments(String rawDocs) {
if (!config.injectHtml) return rawDocs;

return rawDocs.replaceAllMapped(_htmlInjectRegExp, (match) {
var fragment = packageGraph.getHtmlFragment(match[1]);
if (fragment == null) {
warn(PackageWarning.unknownHtmlFragment, message: match[1]);
}
return fragment;
});
}

/// Replace &#123;@macro ...&#125; in API comments with the contents of the macro
///
/// Syntax:
///
/// &#123;@macro NAME&#125;
///
/// Example:
///
/// You define the template in any comment for a documentable entity like:
///
/// &#123;@template foo&#125;
/// Foo contents!
/// &#123;@endtemplate&#125;
///
/// and them somewhere use it like this:
///
/// Some comments
/// &#123;@macro foo&#125;
/// More comments
///
/// Which will render
///
/// Some comments
/// Foo contents!
/// More comments
///
String injectMacros(String rawDocs) {
return rawDocs.replaceAllMapped(_macroRegExp, (match) {
var macro = packageGraph.getMacro(match[1]);
if (macro == null) {
warn(PackageWarning.unknownMacro, message: match[1]);
}
macro = processCommentDirectives(macro ?? '');
return macro;
});
}
}
Loading