analyzer: Parse '@nodoc' in a doc comment

Work towards https://github.com/dart-lang/sdk/issues/52705

Change-Id: Icaa0bcb0e58ca07250d135372dc88985b5f1f68c
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/323423
Commit-Queue: Samuel Rawlins <srawlins@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
This commit is contained in:
Sam Rawlins 2023-08-31 18:27:28 +00:00 committed by Commit Queue
parent 224a5c7d32
commit 4a9a0dcfdb
4 changed files with 102 additions and 12 deletions

View file

@ -3226,6 +3226,11 @@ abstract final class Comment implements AstNode {
@experimental
List<DocImport> get docImports;
/// Whether this comment has a line beginning with '@nodoc', indicating its
/// contents are not intended for publishing.
@experimental
bool get hasNodoc;
/// Return `true` if this is a block comment.
bool get isBlock;
@ -3277,6 +3282,9 @@ final class CommentImpl extends AstNodeImpl implements Comment {
@override
final List<DocImport> docImports;
@override
final bool hasNodoc;
/// Initialize a newly created comment. The list of [tokens] must contain at
/// least one token. The [_type] is the type of the comment. The list of
/// [references] can be empty if the comment does not contain any embedded
@ -3287,6 +3295,7 @@ final class CommentImpl extends AstNodeImpl implements Comment {
required List<CommentReferenceImpl> references,
required this.codeBlocks,
required this.docImports,
required this.hasNodoc,
}) : _type = type {
_references._initialize(this, references);
}

View file

@ -86,6 +86,7 @@ class DocCommentBuilder {
final List<CommentReferenceImpl> references = [];
final List<MdCodeBlock> codeBlocks = [];
final List<DocImport> docImports = [];
bool hasNodoc = false;
final Token startToken;
final _CharacterSequence characterSequence;
@ -117,6 +118,7 @@ class DocCommentBuilder {
references: references,
codeBlocks: codeBlocks,
docImports: docImports,
hasNodoc: hasNodoc,
);
}
@ -139,14 +141,16 @@ class DocCommentBuilder {
continue;
}
var fencedCodeBlockIndex = _fencedCodeBlockDelimiter(content);
if (fencedCodeBlockIndex > -1) {
_parseFencedCodeBlock(index: fencedCodeBlockIndex, content: content);
} else if (!_parseDocImport(
index: whitespaceEndIndex, content: content)) {
if (_parseFencedCodeBlock(content: content)) {
isPreviousLineEmpty = false;
} else if (_parseDocImport(index: whitespaceEndIndex, content: content)) {
isPreviousLineEmpty = false;
} else if (_parseNodoc(index: whitespaceEndIndex, content: content)) {
isPreviousLineEmpty = false;
} else {
_parseDocCommentLine(offset, content);
isPreviousLineEmpty = content.isEmpty;
}
isPreviousLineEmpty = content.isEmpty;
lineInfo = characterSequence.next();
}
}
@ -287,10 +291,13 @@ class DocCommentBuilder {
///
/// When this method returns, [characterSequence] is postioned at the closing
/// delimiter line (`.next()` must be called to move to the next line).
void _parseFencedCodeBlock({
required int index,
bool _parseFencedCodeBlock({
required String content,
}) {
var index = _fencedCodeBlockDelimiter(content);
if (index == -1) {
return false;
}
var tickCount = 0;
var length = content.length;
while (content.codeUnitAt(index) == 0x60 /* '`' */) {
@ -313,25 +320,22 @@ class DocCommentBuilder {
var lineInfo = characterSequence.next();
while (lineInfo != null) {
var (:offset, :content) = lineInfo;
fencedCodeBlockLines.add(
MdCodeBlockLine(offset: offset, length: content.length),
);
var fencedCodeBlockIndex =
_fencedCodeBlockDelimiter(content, minimumTickCount: tickCount);
if (fencedCodeBlockIndex > -1) {
// End the fenced code block.
break;
}
lineInfo = characterSequence.next();
}
// Non-terminating fenced code block.
codeBlocks.add(
MdCodeBlock(infoString: infoString, lines: fencedCodeBlockLines),
);
return true;
}
({int offset, String content})? _parseIndentedCodeBlock(String content) {
@ -368,6 +372,21 @@ class DocCommentBuilder {
return lineInfo;
}
/// Tries to parse a `@nodoc` doc directive at the beginning of a line of a
/// doc comment, returning whether this was successful.
bool _parseNodoc({required int index, required String content}) {
const nodocLength = '@nodoc'.length;
if (!content.startsWith('@nodoc', index)) {
return false;
}
if (content.length == index + nodocLength ||
content.codeUnitAt(index + nodocLength) == 0x20 /* ' ' */) {
hasNodoc = true;
return true;
}
return false;
}
/// Parses the [source] text, found at [offset] in a single comment reference.
///
/// Returns `null` if the text could not be parsed as a comment reference.

View file

@ -1168,6 +1168,65 @@ Comment
''');
}
test_nodoc_eol() {
final parseResult = parseStringWithErrors(r'''
/// Text.
///
/// @nodoc
class A {}
''');
parseResult.assertNoErrors();
final node = parseResult.findNode.comment('Text.');
assertParsedNodeText(node, r'''
Comment
tokens
/// Text.
///
/// @nodoc
hasNodoc: true
''');
}
test_nodoc_more() {
final parseResult = parseStringWithErrors(r'''
/// Text.
///
/// @nodocxx
class A {}
''');
parseResult.assertNoErrors();
final node = parseResult.findNode.comment('Text.');
assertParsedNodeText(node, r'''
Comment
tokens
/// Text.
///
/// @nodocxx
''');
}
test_nodoc_space() {
final parseResult = parseStringWithErrors(r'''
/// Text.
///
/// @nodoc This is not super public.
class A {}
''');
parseResult.assertNoErrors();
final node = parseResult.findNode.comment('Text.');
assertParsedNodeText(node, r'''
Comment
tokens
/// Text.
///
/// @nodoc This is not super public.
hasNodoc: true
''');
}
test_onlyWhitespace() {
final parseResult = parseStringWithErrors('''
///${" "}

View file

@ -269,6 +269,9 @@ class ResolvedAstPrinter extends ThrowingAstVisitor<void> {
}
});
}
if (node.hasNodoc) {
_sink.writelnWithIndent('hasNodoc: true');
}
});
}