diff --git a/lib/src/generator/templates.runtime_renderers.dart b/lib/src/generator/templates.runtime_renderers.dart index f7fa4d9ad4..5abfcd2d64 100644 --- a/lib/src/generator/templates.runtime_renderers.dart +++ b/lib/src/generator/templates.runtime_renderers.dart @@ -3928,6 +3928,26 @@ class _Renderer_DocumentationComment parent: r); }, ), + 'documentationLocal': Property( + getValue: (CT_ c) => c.documentationLocal, + renderVariable: + (CT_ c, Property self, List 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 r, + List ast, StringSink sink) { + _render_String(c.documentationLocal, ast, r.template, sink, + parent: r); + }, + ), 'fullyQualifiedNameWithoutLibrary': Property( getValue: (CT_ c) => c.fullyQualifiedNameWithoutLibrary, renderVariable: @@ -3971,6 +3991,13 @@ class _Renderer_DocumentationComment getters: _invisibleGetters['ModelElementRenderer']); }, ), + 'needsPrecache': Property( + getValue: (CT_ c) => c.needsPrecache, + renderVariable: (CT_ c, Property self, + List remainingNames) => + self.renderSimpleVariable(c, remainingNames, 'bool'), + getBool: (CT_ c) => c.needsPrecache == true, + ), 'pathContext': Property( getValue: (CT_ c) => c.pathContext, renderVariable: (CT_ c, Property self, @@ -4229,6 +4256,7 @@ class _Renderer_Enum extends RendererBase { CT_, () => { ..._Renderer_InheritingContainer.propertyMap(), + ..._Renderer_TypeImplementing.propertyMap(), 'inheritanceChain': Property( getValue: (CT_ c) => c.inheritanceChain, renderVariable: (CT_ c, Property self, @@ -9481,26 +9509,6 @@ class _Renderer_ModelElement extends RendererBase { parent: r)); }, ), - 'documentationLocal': Property( - getValue: (CT_ c) => c.documentationLocal, - renderVariable: - (CT_ c, Property self, List 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 r, - List ast, StringSink sink) { - _render_String(c.documentationLocal, ast, r.template, sink, - parent: r); - }, - ), 'element': Property( getValue: (CT_ c) => c.element, renderVariable: (CT_ c, Property self, diff --git a/lib/src/model/documentation_comment.dart b/lib/src/model/documentation_comment.dart index 1422cc8f5b..405804dc3f 100644 --- a/lib/src/model/documentation_comment.dart +++ b/lib/src/model/documentation_comment.dart @@ -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'; @@ -23,9 +25,15 @@ final _basicToolPattern = RegExp( final _examplePattern = RegExp(r'{@example\s+([^}]+)}'); +final _macroRegExp = RegExp(r'{@macro\s+([^}]+)}'); + +final _htmlInjectRegExp = RegExp(r'([a-f0-9]+)'); + +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 { @@ -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); @@ -79,6 +87,7 @@ mixin DocumentationComment /// Process [documentationComment], performing various actions based on /// `{@}`-style directives, returning the processed result. + @visibleForTesting Future processComment(String documentationComment) async { var docs = stripComments(documentationComment); // Must evaluate tools first, in case they insert any other directives. @@ -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 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 _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 <[digest]> in API comments with + /// the contents of the HTML fragment earlier defined by the + /// {@inject-html} directive. The [digest] is a SHA1 of the contents + /// of the HTML fragment, automatically generated upon parsing the + /// {@inject-html} 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 + /// {@inject-html} + /// <p>[HTML contents!]</p> + /// {@endtemplate} + /// More comments + /// + /// and [_stripHtmlAndAddToIndex] will replace your HTML fragment with this: + /// + /// Some comments + /// <dartdoc-html>4cc02f877240bf69855b4c7291aba8a16e5acce0</dartdoc-html> + /// More comments + /// + /// Which will render in the final HTML output as: + /// + /// Some comments + /// <p>[HTML contents!]</p> + /// 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 {@macro ...} in API comments with the contents of the macro + /// + /// Syntax: + /// + /// {@macro NAME} + /// + /// Example: + /// + /// You define the template in any comment for a documentable entity like: + /// + /// {@template foo} + /// Foo contents! + /// {@endtemplate} + /// + /// and them somewhere use it like this: + /// + /// Some comments + /// {@macro foo} + /// 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; + }); + } } diff --git a/lib/src/model/model_element.dart b/lib/src/model/model_element.dart index 9d834dd151..6eaa00a4b6 100644 --- a/lib/src/model/model_element.dart +++ b/lib/src/model/model_element.dart @@ -34,20 +34,6 @@ import 'package:dartdoc/src/warnings.dart'; import 'package:meta/meta.dart'; import 'package:path/path.dart' as path show Context; -/// This doc may need to be processed in case it has a template or html -/// fragment. -final RegExp needsPrecacheRegExp = RegExp(r'{@(template|tool|inject-html)'); - -final _htmlInjectRegExp = RegExp(r'([a-f0-9]+)'); -@Deprecated('Public variable intended to be private; will be removed as early ' - 'as Dartdoc 1.0.0') -RegExp get htmlInjectRegExp => _htmlInjectRegExp; - -final _macroRegExp = RegExp(r'{@macro\s+([^}]+)}'); -@Deprecated('Public variable intended to be private; will be removed as early ' - 'as Dartdoc 1.0.0') -RegExp get macroRegExp => _macroRegExp; - // TODO(jcollins-g): Implement resolution per ECMA-408 4th edition, page 39 #22. /// Resolves this very rare case incorrectly by picking the closest element in /// the inheritance and interface chains from the analyzer. @@ -118,7 +104,6 @@ abstract class ModelElement extends Canonicalization final Member /*?*/ _originalMember; final Library /*?*/ _library; - String _rawDocs; Documentation __documentation; UnmodifiableListView _parameters; String _linkedName; @@ -546,59 +531,6 @@ abstract class ModelElement extends Canonicalization } } - 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 || - !needsPrecacheRegExp.hasMatch(documentationComment ?? '')); - 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 _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; - } - - /// 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(); - - /// Returns the docs, stripped of their leading comments syntax. - @override - String get documentation { - return _injectMacros( - documentationFrom.map((e) => e.documentationLocal).join('

')); - } - Library get definingLibrary { var library = packageGraph.findButDoNotCreateLibraryFor(element); if (library == null) { @@ -735,13 +667,19 @@ abstract class ModelElement extends Canonicalization return i.enclosingElement == i.canonicalEnclosingContainer; } - String _htmlDocumentation; + /// Returns the docs, stripped of their leading comments syntax. + @override + String get documentation { + return injectMacros( + documentationFrom.map((e) => e.documentationLocal).join('

')); + } + String _documentationAsHtml; @override String get documentationAsHtml { - if (_htmlDocumentation != null) return _htmlDocumentation; - _htmlDocumentation = _injectHtmlFragments(_documentation.asHtml); - return _htmlDocumentation; + if (_documentationAsHtml != null) return _documentationAsHtml; + _documentationAsHtml = injectHtmlFragments(_elementDocumentation.asHtml); + return _documentationAsHtml; } @override @@ -824,7 +762,7 @@ abstract class ModelElement extends Canonicalization @override bool get hasExtendedDocumentation => - href != null && _documentation.hasExtendedDocs; + href != null && _elementDocumentation.hasExtendedDocs; bool get hasParameters => parameters.isNotEmpty; @@ -929,7 +867,7 @@ abstract class ModelElement extends Canonicalization String get name => _name ??= element.name; @override - String get oneLineDoc => _documentation.asOneLiner; + String get oneLineDoc => _elementDocumentation.asOneLiner; Member get originalMember => _originalMember; @@ -1022,14 +960,7 @@ abstract class ModelElement extends Canonicalization @override String computeDocumentationComment() => element.documentationComment; - /// Unconditionally precache local documentation. - /// - /// Use only in factory for [PackageGraph]. - Future precacheLocalDocs() async { - _documentationLocal = await _buildDocumentationBase(); - } - - Documentation get _documentation { + Documentation get _elementDocumentation { if (__documentation != null) return __documentation; __documentation = Documentation.forElement(this); return __documentation; @@ -1087,87 +1018,4 @@ abstract class ModelElement extends Canonicalization return modelElementRenderer.renderLinkedName(this); } - - /// Replace <[digest]> in API comments with - /// the contents of the HTML fragment earlier defined by the - /// {@inject-html} directive. The [digest] is a SHA1 of the contents - /// of the HTML fragment, automatically generated upon parsing the - /// {@inject-html} 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 - /// {@inject-html} - /// <p>[HTML contents!]</p> - /// {@endtemplate} - /// More comments - /// - /// and [_stripHtmlAndAddToIndex] will replace your HTML fragment with this: - /// - /// Some comments - /// <dartdoc-html>4cc02f877240bf69855b4c7291aba8a16e5acce0</dartdoc-html> - /// More comments - /// - /// Which will render in the final HTML output as: - /// - /// Some comments - /// <p>[HTML contents!]</p> - /// 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 {@macro ...} in API comments with the contents of the macro - /// - /// Syntax: - /// - /// {@macro NAME} - /// - /// Example: - /// - /// You define the template in any comment for a documentable entity like: - /// - /// {@template foo} - /// Foo contents! - /// {@endtemplate} - /// - /// and them somewhere use it like this: - /// - /// Some comments - /// {@macro foo} - /// 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; - }); - } } diff --git a/lib/src/model/package_graph.dart b/lib/src/model/package_graph.dart index 07b26ef911..26210a2d7d 100644 --- a/lib/src/model/package_graph.dart +++ b/lib/src/model/package_graph.dart @@ -135,8 +135,7 @@ class PackageGraph with CommentReferable, Nameable { Iterable> precacheOneElement(ModelElement m) sync* { for (var d in m.documentationFrom.where((d) => d.documentationComment != null)) { - if (needsPrecacheRegExp.hasMatch(d.documentationComment) && - !precachedElements.contains(d)) { + if (d.needsPrecache && !precachedElements.contains(d)) { precachedElements.add(d); yield d.precacheLocalDocs(); logProgress(d.name);