Skip to content

Commit 4259b0b

Browse files
authored
Parse and remove doc-imports from comment text (#3803)
1 parent 31833c3 commit 4259b0b

8 files changed

+413
-228
lines changed

lib/src/model/accessor.dart

+1-1
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ class Accessor extends ModelElement {
7272
}
7373
// TODO(srawlins): This doesn't seem right... the super value has delimiters
7474
// (like `///`), but this one doesn't?
75-
return stripComments(super.documentationComment);
75+
return stripCommentDelimiters(super.documentationComment);
7676
}();
7777

7878
/// If this is a getter, assume we want synthetic documentation.

lib/src/model/documentation_comment.dart

+33-5
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,10 @@ mixin DocumentationComment
9696
/// `{@}`-style directives (except tool directives), returning the processed
9797
/// result.
9898
String _processCommentWithoutTools(String documentationComment) {
99-
var docs = stripComments(documentationComment);
99+
// We must first strip the comment of directives like `@docImport`, since
100+
// the offsets are for the source text.
101+
var docs = _stripDocImports(documentationComment);
102+
docs = stripCommentDelimiters(docs);
100103
if (docs.contains('{@')) {
101104
docs = _injectYouTube(docs);
102105
docs = _injectAnimations(docs);
@@ -111,9 +114,13 @@ mixin DocumentationComment
111114
/// Process [documentationComment], performing various actions based on
112115
/// `{@}`-style directives, returning the processed result.
113116
@visibleForTesting
114-
Future<String> processComment(String documentationComment) async {
115-
var docs = stripComments(documentationComment);
116-
// Must evaluate tools first, in case they insert any other directives.
117+
Future<String> processComment() async {
118+
// We must first strip the comment of directives like `@docImport`, since
119+
// the offsets are for the source text.
120+
var docs = _stripDocImports(documentationComment);
121+
docs = stripCommentDelimiters(docs);
122+
// Then we evaluate tools, in case they insert any other directives that
123+
// would need to be processed by `processCommentDirectives`.
117124
docs = await _evaluateTools(docs);
118125
docs = processCommentDirectives(docs);
119126
_analyzeCodeBlocks(docs);
@@ -557,6 +564,27 @@ mixin DocumentationComment
557564
});
558565
}
559566

567+
String _stripDocImports(String content) {
568+
if (modelNode?.commentData case var commentData?) {
569+
var commentOffset = commentData.offset;
570+
var buffer = StringBuffer();
571+
if (commentData.docImports.isEmpty) return content;
572+
var firstDocImport = commentData.docImports.first;
573+
buffer.write(content.substring(0, firstDocImport.offset - commentOffset));
574+
var offset = firstDocImport.end - commentOffset;
575+
for (var docImport in commentData.docImports.skip(1)) {
576+
buffer
577+
.write(content.substring(offset, docImport.offset - commentOffset));
578+
offset = docImport.end - commentOffset;
579+
}
580+
// Write from the end of the last doc-import to the end of the comment.
581+
buffer.write(content.substring(offset));
582+
return buffer.toString();
583+
} else {
584+
return content;
585+
}
586+
}
587+
560588
/// Parse and remove &#123;@inject-html ...&#125; in API comments and store
561589
/// them in the index on the package, replacing them with a SHA1 hash of the
562590
/// contents, where the HTML will be re-injected after Markdown processing of
@@ -793,7 +821,7 @@ mixin DocumentationComment
793821
assert(_rawDocs == null,
794822
'reentrant calls to _buildDocumentation* not allowed');
795823
// Do not use the sync method if we need to evaluate tools or templates.
796-
var rawDocs = await processComment(documentationComment);
824+
var rawDocs = await processComment();
797825
return _rawDocs = buildDocumentationAddition(rawDocs);
798826
}
799827

lib/src/model/model_node.dart

+37-6
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,14 @@ class ModelNode {
1818
final int _sourceEnd;
1919
final int _sourceOffset;
2020

21-
/// Data about each comment reference found in the doc comment of this node.
22-
final Map<String, CommentReferenceData>? commentReferenceData;
21+
/// Data about the doc comment of this node.
22+
final CommentData? commentData;
2323

2424
factory ModelNode(
2525
AstNode? sourceNode,
2626
Element element,
2727
AnalysisContext analysisContext, {
28-
required Map<String, CommentReferenceData>? commentReferenceData,
28+
CommentData? commentData,
2929
}) {
3030
if (sourceNode == null) {
3131
return ModelNode._(element, analysisContext,
@@ -44,7 +44,7 @@ class ModelNode {
4444
analysisContext,
4545
sourceEnd: sourceNode.end,
4646
sourceOffset: sourceNode.offset,
47-
commentReferenceData: commentReferenceData,
47+
commentData: commentData,
4848
);
4949
}
5050
}
@@ -54,7 +54,7 @@ class ModelNode {
5454
this._analysisContext, {
5555
required int sourceEnd,
5656
required int sourceOffset,
57-
this.commentReferenceData = const {},
57+
this.commentData,
5858
}) : _sourceEnd = sourceEnd,
5959
_sourceOffset = sourceOffset;
6060

@@ -79,10 +79,41 @@ class ModelNode {
7979
}();
8080
}
8181

82+
/// Comment data from the syntax tree.
83+
///
84+
/// Various comment data is not available on the analyzer's Element model, so we
85+
/// store it in instances of this class after resolving libraries.
86+
class CommentData {
87+
/// The offset of this comment in the source text.
88+
final int offset;
89+
final List<CommentDocImportData> docImports;
90+
final Map<String, CommentReferenceData> references;
91+
92+
CommentData({
93+
required this.offset,
94+
required this.docImports,
95+
required this.references,
96+
});
97+
}
98+
99+
/// doc-import data from the syntax tree.
100+
///
101+
/// Comment doc-import data is not available on the analyzer's Element model, so
102+
/// we store it in instances of this class after resolving libraries.
103+
class CommentDocImportData {
104+
/// The offset of the doc import in the source text.
105+
final int offset;
106+
107+
/// The offset of the end of the doc import in the source text.
108+
final int end;
109+
110+
CommentDocImportData({required this.offset, required this.end});
111+
}
112+
82113
/// Comment reference data from the syntax tree.
83114
///
84115
/// Comment reference data is not available on the analyzer's Element model, so
85-
/// we store it after resolving libraries in instances of this class.
116+
/// we store it in instances of this class after resolving libraries.
86117
class CommentReferenceData {
87118
final Element element;
88119
final String name;

lib/src/model/package_graph.dart

+35-39
Original file line numberDiff line numberDiff line change
@@ -238,14 +238,14 @@ class PackageGraph with CommentReferable, Nameable {
238238
for (var directive in unit.directives.whereType<LibraryDirective>()) {
239239
// There should be only one library directive. If there are more, there
240240
// is no harm in grabbing ModelNode for each.
241-
var commentReferenceData = directive.documentationComment?.data;
241+
var commentData = directive.documentationComment?.data;
242242
_modelNodes.putIfAbsent(
243243
resolvedLibrary.element,
244244
() => ModelNode(
245245
directive,
246246
resolvedLibrary.element,
247247
analysisContext,
248-
commentReferenceData: commentReferenceData,
248+
commentData: commentData,
249249
));
250250
}
251251

@@ -287,50 +287,39 @@ class PackageGraph with CommentReferable, Nameable {
287287
}
288288

289289
void _populateModelNodeFor(Declaration declaration) {
290-
var commentReferenceData = declaration.documentationComment?.data;
290+
var commentData = declaration.documentationComment?.data;
291+
292+
void addModelNode(Declaration declaration) {
293+
var element = declaration.declaredElement;
294+
if (element == null) {
295+
throw StateError("Expected '$declaration' to declare an element");
296+
}
297+
_modelNodes.putIfAbsent(
298+
element,
299+
() => ModelNode(
300+
declaration,
301+
element,
302+
analysisContext,
303+
commentData: commentData,
304+
),
305+
);
306+
}
291307

292308
if (declaration is FieldDeclaration) {
293309
var fields = declaration.fields.variables;
294310
for (var field in fields) {
295-
var element = field.declaredElement!;
296-
_modelNodes.putIfAbsent(
297-
element,
298-
() => ModelNode(
299-
field,
300-
element,
301-
analysisContext,
302-
commentReferenceData: commentReferenceData,
303-
),
304-
);
311+
addModelNode(field);
305312
}
306313
return;
307314
}
308315
if (declaration is TopLevelVariableDeclaration) {
309316
var fields = declaration.variables.variables;
310317
for (var field in fields) {
311-
var element = field.declaredElement!;
312-
_modelNodes.putIfAbsent(
313-
element,
314-
() => ModelNode(
315-
field,
316-
element,
317-
analysisContext,
318-
commentReferenceData: commentReferenceData,
319-
),
320-
);
318+
addModelNode(field);
321319
}
322320
return;
323321
}
324-
var element = declaration.declaredElement!;
325-
_modelNodes.putIfAbsent(
326-
element,
327-
() => ModelNode(
328-
declaration,
329-
element,
330-
analysisContext,
331-
commentReferenceData: commentReferenceData,
332-
),
333-
);
322+
addModelNode(declaration);
334323
}
335324

336325
ModelNode? getModelNodeFor(Element element) => _modelNodes[element];
@@ -1073,10 +1062,16 @@ class InheritableElementsKey {
10731062

10741063
extension on Comment {
10751064
/// A mapping of all comment references to their various data.
1076-
Map<String, CommentReferenceData> get data {
1077-
if (references.isEmpty) return const {};
1065+
CommentData get data {
1066+
var docImportsData = <CommentDocImportData>[];
1067+
for (var docImport in docImports) {
1068+
docImportsData.add(
1069+
CommentDocImportData(
1070+
offset: docImport.offset, end: docImport.import.end),
1071+
);
1072+
}
10781073

1079-
var data = <String, CommentReferenceData>{};
1074+
var referencesData = <String, CommentReferenceData>{};
10801075
for (var reference in references) {
10811076
var commentReferable = reference.expression;
10821077
String name;
@@ -1096,15 +1091,16 @@ extension on Comment {
10961091
continue;
10971092
}
10981093

1099-
if (staticElement != null && !data.containsKey(name)) {
1100-
data[name] = CommentReferenceData(
1094+
if (staticElement != null && !referencesData.containsKey(name)) {
1095+
referencesData[name] = CommentReferenceData(
11011096
staticElement,
11021097
name,
11031098
commentReferable.offset,
11041099
commentReferable.length,
11051100
);
11061101
}
11071102
}
1108-
return data;
1103+
return CommentData(
1104+
offset: offset, docImports: docImportsData, references: referencesData);
11091105
}
11101106
}

lib/src/utils.dart

+2-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ Iterable<String> stripCommonWhitespace(String str) sync* {
3030
}
3131
}
3232

33-
String stripComments(String str) {
33+
/// Strips [str] of all comment delimiters.
34+
String stripCommentDelimiters(String str) {
3435
if (str.isEmpty) return '';
3536
final buf = StringBuffer();
3637

0 commit comments

Comments
 (0)