From a9bd874c8664f121600e697ae1d812cbef266982 Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Wed, 26 Jun 2024 17:56:17 -0700 Subject: [PATCH 1/3] WIP use docImports --- lib/src/model/documentation_comment.dart | 29 ++++++++- lib/src/model/model_node.dart | 35 +++++++++-- lib/src/model/package_graph.dart | 76 ++++++++++++------------ test/documentation_comment_test.dart | 53 +++++++++++------ test/model_node_test.dart | 14 ++--- 5 files changed, 135 insertions(+), 72 deletions(-) diff --git a/lib/src/model/documentation_comment.dart b/lib/src/model/documentation_comment.dart index 1365373f26..3a16039dde 100644 --- a/lib/src/model/documentation_comment.dart +++ b/lib/src/model/documentation_comment.dart @@ -111,7 +111,7 @@ mixin DocumentationComment /// Process [documentationComment], performing various actions based on /// `{@}`-style directives, returning the processed result. @visibleForTesting - Future processComment(String documentationComment) async { + Future processComment(/*String documentationComment*/) async { var docs = stripComments(documentationComment); // Must evaluate tools first, in case they insert any other directives. docs = await _evaluateTools(docs); @@ -121,6 +121,7 @@ mixin DocumentationComment } String processCommentDirectives(String docs) { + docs = _stripDocImports(docs); // The vast, vast majority of doc comments have no directives. if (!docs.contains('{@')) { return docs; @@ -557,6 +558,30 @@ mixin DocumentationComment }); } + String _stripDocImports(String content) { + if (modelNode?.commentData case var commentData?) { + var buffer = StringBuffer(); + if (commentData.docImports.isEmpty) return content; + var firstDocImport = commentData.docImports.first; + print( + 'FIRST DOC COMMENT: ${firstDocImport.offset} for ${firstDocImport.length}: ' + '"${content.substring(0, firstDocImport.offset)}"'); + buffer.write(content.substring(0, firstDocImport.offset)); + var offset = firstDocImport.offset + firstDocImport.length; + for (var docImport in commentData.docImports.skip(1)) { + print('NEXT DOC COMMENT: ${docImport.offset} for ${docImport.length}: ' + '"${content.substring(0, docImport.offset)}"'); + buffer.write(content.substring(offset, docImport.offset)); + offset = docImport.offset + docImport.length; + } + // Write from the end of the last doc-import to the end of the comment. + buffer.write(content.substring(offset)); + return buffer.toString(); + } else { + return content; + } + } + /// Parse and remove {@inject-html ...} in API comments and store /// them in the index on the package, replacing them with a SHA1 hash of the /// contents, where the HTML will be re-injected after Markdown processing of @@ -793,7 +818,7 @@ mixin DocumentationComment assert(_rawDocs == null, 'reentrant calls to _buildDocumentation* not allowed'); // Do not use the sync method if we need to evaluate tools or templates. - var rawDocs = await processComment(documentationComment); + var rawDocs = await processComment(); return _rawDocs = buildDocumentationAddition(rawDocs); } diff --git a/lib/src/model/model_node.dart b/lib/src/model/model_node.dart index 50d65abd12..f75933870e 100644 --- a/lib/src/model/model_node.dart +++ b/lib/src/model/model_node.dart @@ -18,14 +18,14 @@ class ModelNode { final int _sourceEnd; final int _sourceOffset; - /// Data about each comment reference found in the doc comment of this node. - final Map? commentReferenceData; + /// Data about the doc comment of this node. + final CommentData? commentData; factory ModelNode( AstNode? sourceNode, Element element, AnalysisContext analysisContext, { - required Map? commentReferenceData, + CommentData? commentData, }) { if (sourceNode == null) { return ModelNode._(element, analysisContext, @@ -44,7 +44,7 @@ class ModelNode { analysisContext, sourceEnd: sourceNode.end, sourceOffset: sourceNode.offset, - commentReferenceData: commentReferenceData, + commentData: commentData, ); } } @@ -54,7 +54,7 @@ class ModelNode { this._analysisContext, { required int sourceEnd, required int sourceOffset, - this.commentReferenceData = const {}, + this.commentData, }) : _sourceEnd = sourceEnd, _sourceOffset = sourceOffset; @@ -79,10 +79,33 @@ class ModelNode { }(); } +/// Comment data from the syntax tree. +/// +/// Various comment data is not available on the analyzer's Element model, so we +/// store it in instances of this class after resolving libraries. +class CommentData { + final List docImports; + final Map references; + + CommentData({required this.docImports, required this.references}); +} + +/// doc-import data from the syntax tree. +/// +/// Comment doc-import data is not available on the analyzer's Element model, so +/// we store it in instances of this class after resolving libraries. +class CommentDocImportData { + final int offset; + + final int length; + + CommentDocImportData({required this.offset, required this.length}); +} + /// Comment reference data from the syntax tree. /// /// Comment reference data is not available on the analyzer's Element model, so -/// we store it after resolving libraries in instances of this class. +/// we store it in instances of this class after resolving libraries. class CommentReferenceData { final Element element; final String name; diff --git a/lib/src/model/package_graph.dart b/lib/src/model/package_graph.dart index b0d06033c7..0c3b0d501b 100644 --- a/lib/src/model/package_graph.dart +++ b/lib/src/model/package_graph.dart @@ -238,14 +238,17 @@ class PackageGraph with CommentReferable, Nameable { for (var directive in unit.directives.whereType()) { // There should be only one library directive. If there are more, there // is no harm in grabbing ModelNode for each. - var commentReferenceData = directive.documentationComment?.data; + var commentData = directive.documentationComment?.data; + directive.documentationComment?.docDirectives; + directive.documentationComment?.docImports; + directive.documentationComment?.hasNodoc; _modelNodes.putIfAbsent( resolvedLibrary.element, () => ModelNode( directive, resolvedLibrary.element, analysisContext, - commentReferenceData: commentReferenceData, + commentData: commentData, )); } @@ -287,50 +290,39 @@ class PackageGraph with CommentReferable, Nameable { } void _populateModelNodeFor(Declaration declaration) { - var commentReferenceData = declaration.documentationComment?.data; + var commentData = declaration.documentationComment?.data; + + void addModelNode(Declaration declaration) { + var element = declaration.declaredElement; + if (element == null) { + throw StateError("Expected '$declaration' to declare an element"); + } + _modelNodes.putIfAbsent( + element, + () => ModelNode( + declaration, + element, + analysisContext, + commentData: commentData, + ), + ); + } if (declaration is FieldDeclaration) { var fields = declaration.fields.variables; for (var field in fields) { - var element = field.declaredElement!; - _modelNodes.putIfAbsent( - element, - () => ModelNode( - field, - element, - analysisContext, - commentReferenceData: commentReferenceData, - ), - ); + addModelNode(field); } return; } if (declaration is TopLevelVariableDeclaration) { var fields = declaration.variables.variables; for (var field in fields) { - var element = field.declaredElement!; - _modelNodes.putIfAbsent( - element, - () => ModelNode( - field, - element, - analysisContext, - commentReferenceData: commentReferenceData, - ), - ); + addModelNode(field); } return; } - var element = declaration.declaredElement!; - _modelNodes.putIfAbsent( - element, - () => ModelNode( - declaration, - element, - analysisContext, - commentReferenceData: commentReferenceData, - ), - ); + addModelNode(declaration); } ModelNode? getModelNodeFor(Element element) => _modelNodes[element]; @@ -1073,10 +1065,16 @@ class InheritableElementsKey { extension on Comment { /// A mapping of all comment references to their various data. - Map get data { - if (references.isEmpty) return const {}; + CommentData get data { + var docImportsData = []; + for (var docImport in docImports) { + docImportsData.add( + CommentDocImportData( + offset: docImport.offset, length: docImport.import.length), + ); + } - var data = {}; + var referencesData = {}; for (var reference in references) { var commentReferable = reference.expression; String name; @@ -1096,8 +1094,8 @@ extension on Comment { continue; } - if (staticElement != null && !data.containsKey(name)) { - data[name] = CommentReferenceData( + if (staticElement != null && !referencesData.containsKey(name)) { + referencesData[name] = CommentReferenceData( staticElement, name, commentReferable.offset, @@ -1105,6 +1103,6 @@ extension on Comment { ); } } - return data; + return CommentData(docImports: docImportsData, references: referencesData); } } diff --git a/test/documentation_comment_test.dart b/test/documentation_comment_test.dart index cdad6b2be0..b50b991b65 100644 --- a/test/documentation_comment_test.dart +++ b/test/documentation_comment_test.dart @@ -28,14 +28,11 @@ class DocumentationCommentTest extends DartdocTestBase { late ModelElement libraryModel; void expectNoWarnings() { - expect(packageGraph.packageWarningCounter.hasWarnings, isFalse); expect(packageGraph.packageWarningCounter.countedWarnings, isEmpty); + expect(packageGraph.packageWarningCounter.hasWarnings, isFalse); } - @override - Future setUp() async { - await super.setUp(); - + Future writeLibraryWithComment(String comment) async { projectRoot = utils.writePackage( 'my_package', resourceProvider, packageConfigProvider); projectRoot @@ -50,8 +47,8 @@ class DocumentationCommentTest extends DartdocTestBase { .getChildAssumingFolder('lib') .getChildAssumingFile('a.dart') .writeAsStringSync(''' -/// Documentation comment. -int x = 1; +$comment +library; '''); var optionSet = DartdocOptionRoot.fromOptionGenerators( @@ -64,10 +61,11 @@ int x = 1; } void test_removesTripleSlashes() async { - var doc = await libraryModel.processComment(''' + await writeLibraryWithComment(''' /// Text. /// More text. '''); + var doc = await libraryModel.processComment(); expect(doc, equals(''' Text. @@ -75,10 +73,11 @@ More text.''')); } void test_removesSpaceAfterTripleSlashes() async { - var doc = await libraryModel.processComment(''' + await writeLibraryWithComment(''' /// Text. /// More text. '''); + var doc = await libraryModel.processComment(); // TODO(srawlins): Actually, the three spaces before 'More' is perhaps not // the best fit. Should it only be two, to match the indent from the first @@ -89,11 +88,12 @@ Text. } void test_leavesBlankLines() async { - var doc = await libraryModel.processComment(''' + await writeLibraryWithComment(''' /// Text. /// /// More text. '''); + var doc = await libraryModel.processComment(); expect(doc, equals(''' Text. @@ -101,14 +101,15 @@ Text. More text.''')); } - void test_processesAanimationDirective() async { - var doc = await libraryModel.processComment(''' + void test_processesAnimationDirective() async { + await writeLibraryWithComment(''' /// Text. /// /// {@animation 100 200 http://host/path/to/video.mp4 id=barHerderAnimation} /// /// End text. '''); + var doc = await libraryModel.processComment(); expectNoWarnings(); expect(doc, equals(''' @@ -153,39 +154,42 @@ End text.''')); } void test_rendersUnnamedAnimation() async { - var doc = await libraryModel.processComment(''' + await writeLibraryWithComment(''' /// First line. /// /// {@animation 100 200 http://host/path/to/video.mp4} '''); + var doc = await libraryModel.processComment(); expectNoWarnings(); expect(doc, contains('