Skip to content

Commit 2b18453

Browse files
committed
Support for parsing headers in Doxygen \par commands
1 parent ffc9a30 commit 2b18453

File tree

7 files changed

+228
-8
lines changed

7 files changed

+228
-8
lines changed

clang/include/clang/AST/CommentCommandTraits.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,10 @@ struct CommandInfo {
8888
LLVM_PREFERRED_TYPE(bool)
8989
unsigned IsHeaderfileCommand : 1;
9090

91+
/// True if this is a \\par command.
92+
LLVM_PREFERRED_TYPE(bool)
93+
unsigned IsParCommand : 1;
94+
9195
/// True if we don't want to warn about this command being passed an empty
9296
/// paragraph. Meaningful only for block commands.
9397
LLVM_PREFERRED_TYPE(bool)

clang/include/clang/AST/CommentCommands.td

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ class Command<string name> {
1818
bit IsThrowsCommand = 0;
1919
bit IsDeprecatedCommand = 0;
2020
bit IsHeaderfileCommand = 0;
21+
bit IsParCommand = 0;
2122

2223
bit IsEmptyParagraphAllowed = 0;
2324

@@ -156,7 +157,7 @@ def Date : BlockCommand<"date">;
156157
def Invariant : BlockCommand<"invariant">;
157158
def Li : BlockCommand<"li">;
158159
def Note : BlockCommand<"note">;
159-
def Par : BlockCommand<"par">;
160+
def Par : BlockCommand<"par"> { let IsParCommand = 1; let NumArgs = 1; }
160161
def Post : BlockCommand<"post">;
161162
def Pre : BlockCommand<"pre">;
162163
def Remark : BlockCommand<"remark">;

clang/include/clang/AST/CommentParser.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,9 @@ class Parser {
100100
ArrayRef<Comment::Argument>
101101
parseCommandArgs(TextTokenRetokenizer &Retokenizer, unsigned NumArgs);
102102

103+
ArrayRef<Comment::Argument>
104+
parseParCommandArgs(TextTokenRetokenizer &Retokenizer, unsigned NumArgs);
105+
103106
BlockCommandComment *parseBlockCommand();
104107
InlineCommandComment *parseInlineCommand();
105108

@@ -118,4 +121,3 @@ class Parser {
118121
} // end namespace clang
119122

120123
#endif
121-

clang/lib/AST/CommentParser.cpp

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,63 @@ class TextTokenRetokenizer {
149149
addToken();
150150
}
151151

152+
/// Check if this line starts with @par or \par
153+
bool startsWithParCommand() {
154+
unsigned Offset = 1;
155+
156+
/// Skip all whitespace characters at the beginning.
157+
/// This needs to backtrack because Pos has already advanced past the
158+
/// actual \par or @par command by the time this function is called.
159+
while (isWhitespace(*(Pos.BufferPtr - Offset)))
160+
Offset++;
161+
162+
/// Check if next four characters are \par or @par
163+
llvm::StringRef LineStart(Pos.BufferPtr - 5, 4);
164+
return LineStart.starts_with("\\par") || LineStart.starts_with("@par");
165+
}
166+
167+
/// Extract a par command argument-header.
168+
bool lexParHeading(Token &Tok) {
169+
if (isEnd())
170+
return false;
171+
172+
Position SavedPos = Pos;
173+
174+
consumeWhitespace();
175+
SmallString<32> WordText;
176+
const char *WordBegin = Pos.BufferPtr;
177+
SourceLocation Loc = getSourceLocation();
178+
179+
if (!startsWithParCommand())
180+
return false;
181+
182+
// Read until the end of this token, which is effectively the end of the
183+
// line This gets us the content of the par header, if there is one.
184+
while (!isEnd()) {
185+
WordText.push_back(peek());
186+
if (Pos.BufferPtr + 1 == Pos.BufferEnd) {
187+
consumeChar();
188+
break;
189+
} else {
190+
consumeChar();
191+
}
192+
}
193+
194+
const unsigned Length = WordText.size();
195+
if (Length == 0) {
196+
Pos = SavedPos;
197+
return false;
198+
}
199+
200+
char *TextPtr = Allocator.Allocate<char>(Length + 1);
201+
202+
memcpy(TextPtr, WordText.c_str(), Length + 1);
203+
StringRef Text = StringRef(TextPtr, Length);
204+
205+
formTokenWithChars(Tok, Loc, WordBegin, Length, Text);
206+
return true;
207+
}
208+
152209
/// Extract a word -- sequence of non-whitespace characters.
153210
bool lexWord(Token &Tok) {
154211
if (isEnd())
@@ -304,6 +361,23 @@ Parser::parseCommandArgs(TextTokenRetokenizer &Retokenizer, unsigned NumArgs) {
304361
return llvm::ArrayRef(Args, ParsedArgs);
305362
}
306363

364+
ArrayRef<Comment::Argument>
365+
Parser::parseParCommandArgs(TextTokenRetokenizer &Retokenizer,
366+
unsigned NumArgs) {
367+
auto *Args = new (Allocator.Allocate<Comment::Argument>(NumArgs))
368+
Comment::Argument[NumArgs];
369+
unsigned ParsedArgs = 0;
370+
Token Arg;
371+
372+
while (ParsedArgs < NumArgs && Retokenizer.lexParHeading(Arg)) {
373+
Args[ParsedArgs] = Comment::Argument{
374+
SourceRange(Arg.getLocation(), Arg.getEndLocation()), Arg.getText()};
375+
ParsedArgs++;
376+
}
377+
378+
return llvm::ArrayRef(Args, ParsedArgs);
379+
}
380+
307381
BlockCommandComment *Parser::parseBlockCommand() {
308382
assert(Tok.is(tok::backslash_command) || Tok.is(tok::at_command));
309383

@@ -356,6 +430,9 @@ BlockCommandComment *Parser::parseBlockCommand() {
356430
parseParamCommandArgs(PC, Retokenizer);
357431
else if (TPC)
358432
parseTParamCommandArgs(TPC, Retokenizer);
433+
else if (Info->IsParCommand)
434+
S.actOnBlockCommandArgs(BC,
435+
parseParCommandArgs(Retokenizer, Info->NumArgs));
359436
else
360437
S.actOnBlockCommandArgs(BC, parseCommandArgs(Retokenizer, Info->NumArgs));
361438

clang/test/Index/comment-misc-tags.m

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -91,18 +91,16 @@ @interface IOCommandGate
9191

9292
struct Test {int filler;};
9393

94-
// CHECK: (CXComment_BlockCommand CommandName=[par]
94+
// CHECK: (CXComment_BlockCommand CommandName=[par] Arg[0]=User defined paragraph:
9595
// CHECK-NEXT: (CXComment_Paragraph
96-
// CHECK-NEXT: (CXComment_Text Text=[ User defined paragraph:] HasTrailingNewline)
9796
// CHECK-NEXT: (CXComment_Text Text=[ Contents of the paragraph.])))
9897
// CHECK: (CXComment_BlockCommand CommandName=[par]
9998
// CHECK-NEXT: (CXComment_Paragraph
100-
// CHECK-NEXT: (CXComment_Text Text=[ New paragraph under the same heading.])))
99+
// CHECK-NEXT: (CXComment_Text Text=[New paragraph under the same heading.])))
101100
// CHECK: (CXComment_BlockCommand CommandName=[note]
102101
// CHECK-NEXT: (CXComment_Paragraph
103102
// CHECK-NEXT: (CXComment_Text Text=[ This note consists of two paragraphs.] HasTrailingNewline)
104103
// CHECK-NEXT: (CXComment_Text Text=[ This is the first paragraph.])))
105104
// CHECK: (CXComment_BlockCommand CommandName=[par]
106105
// CHECK-NEXT: (CXComment_Paragraph
107-
// CHECK-NEXT: (CXComment_Text Text=[ And this is the second paragraph.])))
108-
106+
// CHECK-NEXT: (CXComment_Text Text=[And this is the second paragraph.])))

clang/unittests/AST/CommentParser.cpp

Lines changed: 138 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1427,8 +1427,145 @@ TEST_F(CommentParserTest, Deprecated) {
14271427
}
14281428
}
14291429

1430+
TEST_F(CommentParserTest, ParCommandHasArg1) {
1431+
const char *Sources[] = {
1432+
"/// @par Paragraph header:", "/// @par Paragraph header:\n",
1433+
"/// @par Paragraph header:\r\n", "/// @par Paragraph header:\n\r",
1434+
"/** @par Paragraph header:*/",
1435+
};
1436+
1437+
for (size_t i = 0, e = std::size(Sources); i != e; i++) {
1438+
FullComment *FC = parseString(Sources[i]);
1439+
ASSERT_TRUE(HasChildCount(FC, 2));
1440+
1441+
ASSERT_TRUE(HasParagraphCommentAt(FC, 0, " "));
1442+
{
1443+
BlockCommandComment *BCC;
1444+
ParagraphComment *PC;
1445+
ASSERT_TRUE(HasBlockCommandAt(FC, Traits, 1, BCC, "par", PC));
1446+
ASSERT_TRUE(HasChildCount(PC, 0));
1447+
ASSERT_TRUE(BCC->getNumArgs() == 1);
1448+
ASSERT_TRUE(BCC->getArgText(0) == "Paragraph header:");
1449+
}
1450+
}
1451+
}
1452+
1453+
TEST_F(CommentParserTest, ParCommandHasArg2) {
1454+
const char *Sources[] = {
1455+
"/// @par Paragraph header: ", "/// @par Paragraph header: \n",
1456+
"/// @par Paragraph header: \r\n", "/// @par Paragraph header: \n\r",
1457+
"/** @par Paragraph header: */",
1458+
};
1459+
1460+
for (size_t i = 0, e = std::size(Sources); i != e; i++) {
1461+
FullComment *FC = parseString(Sources[i]);
1462+
ASSERT_TRUE(HasChildCount(FC, 2));
1463+
1464+
ASSERT_TRUE(HasParagraphCommentAt(FC, 0, " "));
1465+
{
1466+
BlockCommandComment *BCC;
1467+
ParagraphComment *PC;
1468+
ASSERT_TRUE(HasBlockCommandAt(FC, Traits, 1, BCC, "par", PC));
1469+
ASSERT_TRUE(HasChildCount(PC, 0));
1470+
ASSERT_TRUE(BCC->getNumArgs() == 1);
1471+
ASSERT_TRUE(BCC->getArgText(0) == "Paragraph header: ");
1472+
}
1473+
}
1474+
}
1475+
1476+
TEST_F(CommentParserTest, ParCommandHasArg3) {
1477+
const char *Sources[] = {
1478+
("/// @par Paragraph header:\n"
1479+
"/// Paragraph body"),
1480+
("/// @par Paragraph header:\r\n"
1481+
"/// Paragraph body"),
1482+
("/// @par Paragraph header:\n\r"
1483+
"/// Paragraph body"),
1484+
};
1485+
1486+
for (size_t i = 0, e = std::size(Sources); i != e; i++) {
1487+
FullComment *FC = parseString(Sources[i]);
1488+
ASSERT_TRUE(HasChildCount(FC, 2));
1489+
1490+
ASSERT_TRUE(HasParagraphCommentAt(FC, 0, " "));
1491+
{
1492+
BlockCommandComment *BCC;
1493+
ParagraphComment *PC;
1494+
TextComment *TC;
1495+
ASSERT_TRUE(HasBlockCommandAt(FC, Traits, 1, BCC, "par", PC));
1496+
ASSERT_TRUE(HasChildCount(PC, 1));
1497+
ASSERT_TRUE(BCC->getNumArgs() == 1);
1498+
ASSERT_TRUE(BCC->getArgText(0) == "Paragraph header:");
1499+
ASSERT_TRUE(GetChildAt(PC, 0, TC));
1500+
ASSERT_TRUE(TC->getText() == " Paragraph body");
1501+
}
1502+
}
1503+
}
1504+
1505+
TEST_F(CommentParserTest, ParCommandHasArg4) {
1506+
const char *Sources[] = {
1507+
("/// @par Paragraph header:\n"
1508+
"/// Paragraph body1\n"
1509+
"/// Paragraph body2"),
1510+
("/// @par Paragraph header:\r\n"
1511+
"/// Paragraph body1\n"
1512+
"/// Paragraph body2"),
1513+
("/// @par Paragraph header:\n\r"
1514+
"/// Paragraph body1\n"
1515+
"/// Paragraph body2"),
1516+
};
1517+
1518+
for (size_t i = 0, e = std::size(Sources); i != e; i++) {
1519+
FullComment *FC = parseString(Sources[i]);
1520+
ASSERT_TRUE(HasChildCount(FC, 2));
1521+
1522+
ASSERT_TRUE(HasParagraphCommentAt(FC, 0, " "));
1523+
{
1524+
BlockCommandComment *BCC;
1525+
ParagraphComment *PC;
1526+
TextComment *TC;
1527+
ASSERT_TRUE(HasBlockCommandAt(FC, Traits, 1, BCC, "par", PC));
1528+
ASSERT_TRUE(HasChildCount(PC, 2));
1529+
ASSERT_TRUE(BCC->getNumArgs() == 1);
1530+
ASSERT_TRUE(BCC->getArgText(0) == "Paragraph header:");
1531+
ASSERT_TRUE(GetChildAt(PC, 0, TC));
1532+
ASSERT_TRUE(TC->getText() == " Paragraph body1");
1533+
ASSERT_TRUE(GetChildAt(PC, 1, TC));
1534+
ASSERT_TRUE(TC->getText() == " Paragraph body2");
1535+
}
1536+
}
1537+
}
1538+
1539+
TEST_F(CommentParserTest, ParCommandHasArg5) {
1540+
const char *Sources[] = {
1541+
("/// @par \n"
1542+
"/// Paragraphs with no text before newline have no heading"),
1543+
("/// @par \r\n"
1544+
"/// Paragraphs with no text before newline have no heading"),
1545+
("/// @par \n\r"
1546+
"/// Paragraphs with no text before newline have no heading"),
1547+
};
1548+
1549+
for (size_t i = 0, e = std::size(Sources); i != e; i++) {
1550+
FullComment *FC = parseString(Sources[i]);
1551+
ASSERT_TRUE(HasChildCount(FC, 2));
1552+
1553+
ASSERT_TRUE(HasParagraphCommentAt(FC, 0, " "));
1554+
{
1555+
BlockCommandComment *BCC;
1556+
ParagraphComment *PC;
1557+
TextComment *TC;
1558+
ASSERT_TRUE(HasBlockCommandAt(FC, Traits, 1, BCC, "par", PC));
1559+
ASSERT_TRUE(HasChildCount(PC, 1));
1560+
ASSERT_TRUE(BCC->getNumArgs() == 0);
1561+
ASSERT_TRUE(GetChildAt(PC, 0, TC));
1562+
ASSERT_TRUE(TC->getText() ==
1563+
"Paragraphs with no text before newline have no heading");
1564+
}
1565+
}
1566+
}
1567+
14301568
} // unnamed namespace
14311569

14321570
} // end namespace comments
14331571
} // end namespace clang
1434-

clang/utils/TableGen/ClangCommentCommandInfoEmitter.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ void clang::EmitClangCommentCommandInfo(RecordKeeper &Records,
4444
<< Tag.getValueAsBit("IsThrowsCommand") << ", "
4545
<< Tag.getValueAsBit("IsDeprecatedCommand") << ", "
4646
<< Tag.getValueAsBit("IsHeaderfileCommand") << ", "
47+
<< Tag.getValueAsBit("IsParCommand") << ", "
4748
<< Tag.getValueAsBit("IsEmptyParagraphAllowed") << ", "
4849
<< Tag.getValueAsBit("IsVerbatimBlockCommand") << ", "
4950
<< Tag.getValueAsBit("IsVerbatimBlockEndCommand") << ", "

0 commit comments

Comments
 (0)