[clang] [clang-tools-extra] [clangd] Fix unknown doxygen command parsing in parameter documentation (PR #202121)
via cfe-commits
cfe-commits at lists.llvm.org
Sun Jun 14 00:18:40 PDT 2026
https://github.com/tcottin updated https://github.com/llvm/llvm-project/pull/202121
>From d5e315d578a0b80bbc3aca76c93dc5bf0277314e Mon Sep 17 00:00:00 2001
From: Tim Cottin <timcottin at gmx.de>
Date: Sun, 7 Jun 2026 10:16:21 +0000
Subject: [PATCH] fix unknown doxygen command parsing
---
.../clangd/CodeCompletionStrings.cpp | 7 +-
.../clangd/SymbolDocumentation.cpp | 18 ++--
.../clangd/SymbolDocumentation.h | 11 --
clang-tools-extra/clangd/support/Markup.cpp | 12 +++
.../unittests/CodeCompletionStringsTests.cpp | 54 ++++++++++
.../clangd/unittests/HoverTests.cpp | 66 ++++++++++++
.../unittests/SymbolDocumentationTests.cpp | 100 ++++++++++++++++++
clang/include/clang/AST/Comment.h | 9 --
clang/include/clang/AST/CommentLexer.h | 13 ++-
clang/include/clang/AST/CommentSema.h | 6 +-
clang/lib/AST/CommentLexer.cpp | 6 +-
clang/lib/AST/CommentParser.cpp | 19 ++--
clang/lib/AST/CommentSema.cpp | 20 ++--
clang/unittests/AST/CommentLexer.cpp | 40 ++++++-
14 files changed, 321 insertions(+), 60 deletions(-)
diff --git a/clang-tools-extra/clangd/CodeCompletionStrings.cpp b/clang-tools-extra/clangd/CodeCompletionStrings.cpp
index 9c4241b54057a..dc86be60a876f 100644
--- a/clang-tools-extra/clangd/CodeCompletionStrings.cpp
+++ b/clang-tools-extra/clangd/CodeCompletionStrings.cpp
@@ -127,11 +127,12 @@ std::string getDeclComment(const ASTContext &Ctx, const NamedDecl &Decl) {
// not write them into PCH, because they are racy and slow to load.
assert(!Ctx.getSourceManager().isLoadedSourceLocation(RC->getBeginLoc()));
- comments::FullComment *FC = RC->parse(Ctx, /*PP=*/nullptr, ND);
- if (!FC)
+ std::string DeclDoc =
+ RC->getFormattedText(Ctx.getSourceManager(), Ctx.getDiagnostics());
+ if (!looksLikeDocComment(DeclDoc))
return "";
- SymbolDocCommentVisitor V(FC, Ctx.getLangOpts().CommentOpts);
+ SymbolDocCommentVisitor V(DeclDoc, Ctx.getLangOpts().CommentOpts);
std::string RawDoc;
llvm::raw_string_ostream OS(RawDoc);
diff --git a/clang-tools-extra/clangd/SymbolDocumentation.cpp b/clang-tools-extra/clangd/SymbolDocumentation.cpp
index a50d7a565b1bc..c54f752f04196 100644
--- a/clang-tools-extra/clangd/SymbolDocumentation.cpp
+++ b/clang-tools-extra/clangd/SymbolDocumentation.cpp
@@ -34,9 +34,10 @@ void commandToMarkup(markup::Paragraph &Out, StringRef Command,
comments::CommandMarkerKind CommandMarker,
StringRef Args) {
Out.appendBoldText(commandMarkerAsString(CommandMarker) + Command.str());
- Out.appendSpace();
- if (!Args.empty())
+ if (!Args.empty()) {
+ Out.appendSpace();
Out.appendCode(Args.str());
+ }
}
template <typename T> std::string getArgText(const T *Command) {
@@ -108,6 +109,7 @@ class ParagraphToMarkupDocument
commandToMarkup(Out, C->getCommandName(Traits), C->getCommandMarker(),
"");
+ LastChunkEndsWithNewline = false;
}
}
@@ -159,7 +161,11 @@ class ParagraphToString
}
}
- void visitTextComment(const comments::TextComment *C) { Out << C->getText(); }
+ void visitTextComment(const comments::TextComment *C) {
+ Out << C->getText();
+ if (C->hasTrailingNewline())
+ Out << "\n";
+ }
void visitInlineCommandComment(const comments::InlineCommandComment *C) {
Out << commandMarkerAsString(C->getCommandMarker());
@@ -167,7 +173,6 @@ class ParagraphToString
std::string ArgText = getArgText(C);
if (!ArgText.empty())
Out << " " << ArgText;
- Out << " ";
}
void visitHTMLStartTagComment(const comments::HTMLStartTagComment *STC) {
@@ -251,10 +256,7 @@ class BlockCommentToMarkupDocument
commandToMarkup(P, B->getCommandName(Traits), B->getCommandMarker(),
ArgText);
if (B->getParagraph() && !B->getParagraph()->isWhitespace()) {
- // For commands with arguments, the paragraph starts after the first
- // space. Therefore we need to append a space manually in this case.
- if (!ArgText.empty())
- P.appendSpace();
+ P.appendSpace();
ParagraphToMarkupDocument(P, Traits).visit(B->getParagraph());
}
}
diff --git a/clang-tools-extra/clangd/SymbolDocumentation.h b/clang-tools-extra/clangd/SymbolDocumentation.h
index 88c7ade633516..95787ae6c92b9 100644
--- a/clang-tools-extra/clangd/SymbolDocumentation.h
+++ b/clang-tools-extra/clangd/SymbolDocumentation.h
@@ -31,17 +31,6 @@ namespace clangd {
class SymbolDocCommentVisitor
: public comments::ConstCommentVisitor<SymbolDocCommentVisitor> {
public:
- SymbolDocCommentVisitor(comments::FullComment *FC,
- const CommentOptions &CommentOpts)
- : Traits(Allocator, CommentOpts), Allocator() {
- if (!FC)
- return;
-
- for (auto *Block : FC->getBlocks()) {
- visit(Block);
- }
- }
-
SymbolDocCommentVisitor(llvm::StringRef Documentation,
const CommentOptions &CommentOpts)
: Traits(Allocator, CommentOpts), Allocator() {
diff --git a/clang-tools-extra/clangd/support/Markup.cpp b/clang-tools-extra/clangd/support/Markup.cpp
index 9ba993a04709c..8ecaa823eb280 100644
--- a/clang-tools-extra/clangd/support/Markup.cpp
+++ b/clang-tools-extra/clangd/support/Markup.cpp
@@ -537,16 +537,28 @@ void Paragraph::renderEscapedMarkdown(llvm::raw_ostream &OS) const {
void Paragraph::renderMarkdown(llvm::raw_ostream &OS) const {
bool NeedsSpace = false;
bool HasChunks = false;
+ bool TextEndsWithNewline = false;
std::string ParagraphText;
ParagraphText.reserve(EstimatedStringSize);
llvm::raw_string_ostream ParagraphTextOS(ParagraphText);
for (auto &C : Chunks) {
+
+ if (TextEndsWithNewline) {
+ ParagraphTextOS << "\n";
+ TextEndsWithNewline = false;
+ }
+
if (C.SpaceBefore || NeedsSpace)
ParagraphTextOS << " ";
+
switch (C.Kind) {
case ChunkKind::PlainText:
ParagraphTextOS << renderText(C.Contents, !HasChunks,
/*EscapeMarkdown=*/false);
+ // renderText removes trailing newlines, but in case there are additional
+ // chunks to process, we need to keep track of the trailing newline and
+ // add it in the next iteration.
+ TextEndsWithNewline = llvm::StringRef(C.Contents).ends_with("\n");
break;
case ChunkKind::InlineCode:
ParagraphTextOS << renderInlineBlock(C.Contents);
diff --git a/clang-tools-extra/clangd/unittests/CodeCompletionStringsTests.cpp b/clang-tools-extra/clangd/unittests/CodeCompletionStringsTests.cpp
index de5f533d31645..6b8b003a4fe18 100644
--- a/clang-tools-extra/clangd/unittests/CodeCompletionStringsTests.cpp
+++ b/clang-tools-extra/clangd/unittests/CodeCompletionStringsTests.cpp
@@ -7,6 +7,7 @@
//===----------------------------------------------------------------------===//
#include "CodeCompletionStrings.h"
+#include "Config.h"
#include "TestTU.h"
#include "clang/Sema/CodeCompleteConsumer.h"
#include "gmock/gmock.h"
@@ -69,6 +70,59 @@ TEST_F(CompletionStringTest, GetDeclCommentBadUTF8) {
getDeclComment(AST.getASTContext(), findDecl(AST, "X")));
}
+TEST_F(CompletionStringTest, GetDeclCommentForParam) {
+ auto TU =
+ TestTU::withCode("/** @param a this is param a */\nvoid func(int a);");
+ auto AST = TU.build();
+ Config Cfg;
+ Cfg.Documentation.CommentFormat = Config::CommentFormatPolicy::Doxygen;
+ WithContextValue WithCfg(Config::Key, std::move(Cfg));
+ const auto &FD = llvm::cast<FunctionDecl>(findDecl(AST, "func"));
+ EXPECT_EQ(FD.getNumParams(), 1UL);
+ EXPECT_EQ("this is param a",
+ getDeclComment(AST.getASTContext(), *FD.parameters()[0]));
+}
+
+TEST_F(CompletionStringTest, GetDeclCommentForMultipleParams) {
+ auto TU = TestTU::withCode("/** @param a this is param a\n * @param b this "
+ "is param b\n */\nvoid func(int a, int b);");
+ auto AST = TU.build();
+ Config Cfg;
+ Cfg.Documentation.CommentFormat = Config::CommentFormatPolicy::Doxygen;
+ WithContextValue WithCfg(Config::Key, std::move(Cfg));
+ const auto &FD = llvm::cast<FunctionDecl>(findDecl(AST, "func"));
+ EXPECT_EQ(FD.getNumParams(), 2UL);
+ EXPECT_EQ("this is param a",
+ getDeclComment(AST.getASTContext(), *FD.parameters()[0]));
+ EXPECT_EQ("this is param b",
+ getDeclComment(AST.getASTContext(), *FD.parameters()[1]));
+}
+
+TEST_F(CompletionStringTest, GetDeclCommentForUndocumentedParam) {
+ auto TU =
+ TestTU::withCode("/** @param c this is param c */\nvoid func(int a);");
+ auto AST = TU.build();
+ Config Cfg;
+ Cfg.Documentation.CommentFormat = Config::CommentFormatPolicy::Doxygen;
+ WithContextValue WithCfg(Config::Key, std::move(Cfg));
+ const auto &FD = llvm::cast<FunctionDecl>(findDecl(AST, "func"));
+ EXPECT_EQ(FD.getNumParams(), 1UL);
+ EXPECT_EQ("", getDeclComment(AST.getASTContext(), *FD.parameters()[0]));
+}
+
+TEST_F(CompletionStringTest, GetDeclCommentForParamWithUnknownDoxygenCommand) {
+ auto TU = TestTU::withCode("/** @param a this is param a and an\n * @unknown "
+ "command\n */\nvoid func(int a);");
+ auto AST = TU.build();
+ Config Cfg;
+ Cfg.Documentation.CommentFormat = Config::CommentFormatPolicy::Doxygen;
+ WithContextValue WithCfg(Config::Key, std::move(Cfg));
+ const auto &FD = llvm::cast<FunctionDecl>(findDecl(AST, "func"));
+ EXPECT_EQ(FD.getNumParams(), 1UL);
+ EXPECT_EQ("this is param a and an\n at unknown command",
+ getDeclComment(AST.getASTContext(), *FD.parameters()[0]));
+}
+
TEST_F(CompletionStringTest, MultipleAnnotations) {
Builder.AddAnnotation("Ano1");
Builder.AddAnnotation("Ano2");
diff --git a/clang-tools-extra/clangd/unittests/HoverTests.cpp b/clang-tools-extra/clangd/unittests/HoverTests.cpp
index 7b168b0bdca60..476494cdd3e6d 100644
--- a/clang-tools-extra/clangd/unittests/HoverTests.cpp
+++ b/clang-tools-extra/clangd/unittests/HoverTests.cpp
@@ -4431,6 +4431,56 @@ brief doc
### Details
longer doc)"},
+ {[](HoverInfo &HI) {
+ HI.Kind = index::SymbolKind::Function;
+ HI.Documentation = "@brief brief doc\n"
+ "@unknown command is treated as an inline command";
+ HI.Definition = "int foo(int a)";
+ HI.ReturnType = "int";
+ HI.Name = "foo";
+ HI.Parameters.emplace();
+ HI.Parameters->emplace_back();
+ HI.Parameters->back().Type = "int";
+ HI.Parameters->back().Name = "a";
+ },
+ R"(### function `foo`
+
+---
+→ `int`
+
+Parameters:
+
+- `int a`
+
+ at brief brief doc
+ at unknown command is treated as an inline command
+
+---
+```cpp
+int foo(int a)
+```)",
+ R"(### function
+
+---
+```cpp
+int foo(int a)
+```
+
+---
+### Brief
+
+brief doc
+**@unknown** command is treated as an inline command
+
+---
+### Parameters
+
+- `int a`
+
+---
+### Returns
+
+`int`)"},
};
for (const auto &C : Cases) {
@@ -5203,6 +5253,22 @@ TEST(Hover, FunctionParameters) {
"### param\n\n---\n```cpp\n// In foo\nint b\n```\n\n---\nthis is "
"\\<b>doc\\</b> \\<html-tag attribute/> \\<another-html-tag "
"attribute=\"value\">for\\</another-html-tag> `b`\n\n---\nType: `int`"},
+ {R"cpp(/// Function doc
+ /// @param a the next command is an
+ /// @unknown command.
+ void foo(int [[^a]]);
+ )cpp",
+ [](HoverInfo &HI) {
+ HI.Name = "a";
+ HI.Kind = index::SymbolKind::Parameter;
+ HI.NamespaceScope = "";
+ HI.LocalScope = "foo::";
+ HI.Type = "int";
+ HI.Definition = "int a";
+ HI.Documentation = "the next command is an\n @unknown command.";
+ },
+ "### param\n\n---\n```cpp\n// In foo\nint a\n```\n\n---\nthe next "
+ "command is an\n**@unknown** command.\n\n---\nType: `int`"},
};
// Create a tiny index, so tests above can verify documentation is fetched.
diff --git a/clang-tools-extra/clangd/unittests/SymbolDocumentationTests.cpp b/clang-tools-extra/clangd/unittests/SymbolDocumentationTests.cpp
index 676f7dfc74483..d140ab00a175f 100644
--- a/clang-tools-extra/clangd/unittests/SymbolDocumentationTests.cpp
+++ b/clang-tools-extra/clangd/unittests/SymbolDocumentationTests.cpp
@@ -10,6 +10,7 @@
#include "support/Markup.h"
#include "clang/Basic/CommentOptions.h"
#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/raw_ostream.h"
#include "gtest/gtest.h"
namespace clang {
@@ -732,5 +733,104 @@ line
}
}
+TEST(SymbolDocumentation, ParameterDocToString) {
+ CommentOptions CommentOpts;
+
+ struct Case {
+ llvm::StringRef Documentation;
+ llvm::StringRef ExpectedOutputString;
+ llvm::StringRef ParameterName;
+ } Cases[] = {
+ {"This documentation does not contain parameter docs", "", "a"},
+ {"@param a this is a parameter", "", "not_exists"},
+ {"@param a this is a parameter", " this is a parameter", "a"},
+ {R"(@param a parameter doc with an \p inline command)",
+ R"( parameter doc with an \p inline command)", "a"},
+ {R"(@param a parameter doc with an \unknown command)",
+ R"( parameter doc with an \unknown command)", "a"},
+ {"@param a parameter doc with an @unknown command",
+ " parameter doc with an @unknown command", "a"},
+ {R"(@param a parameter doc with
+multiple lines)",
+ R"( parameter doc with
+multiple lines)",
+ "a"},
+ {R"(@param a parameter doc with an
+ at unknown command starting a new line)",
+ R"( parameter doc with an
+ at unknown command starting a new line)",
+ "a"},
+ {R"(@param a parameter doc with a
+ at note command which is a new block command and therefore ends the parameter doc paragraph)",
+ R"( parameter doc with a
+)",
+ "a"},
+ {R"(Unrelated docs
+ at param a parameter doc
+
+New paragraph with unrelated docs)",
+ " parameter doc", "a"},
+ };
+ for (const auto &C : Cases) {
+ std::string Result;
+ llvm::raw_string_ostream OS(Result);
+ SymbolDocCommentVisitor SymbolDoc(C.Documentation, CommentOpts);
+
+ SymbolDoc.parameterDocToString(C.ParameterName, OS);
+
+ EXPECT_EQ(Result, C.ExpectedOutputString);
+ }
+}
+
+TEST(SymbolDocumentation, TemplateParameterDocToString) {
+ CommentOptions CommentOpts;
+
+ struct Case {
+ llvm::StringRef Documentation;
+ llvm::StringRef ExpectedOutputString;
+ llvm::StringRef TemplateParameterName;
+ } Cases[] = {
+ {"This documentation does not contain parameter docs", "", "a"},
+ {"@tparam a this is a template type parameter", "", "not_exists"},
+ {"@tparam a this is a template type parameter",
+ " this is a template type parameter", "a"},
+ {R"(@tparam a template type parameter doc with an \p inline command)",
+ R"( template type parameter doc with an \p inline command)", "a"},
+ {R"(@tparam a template type parameter doc with an \unknown command)",
+ R"( template type parameter doc with an \unknown command)", "a"},
+ {"@tparam a template type parameter doc with an @unknown command",
+ " template type parameter doc with an @unknown command", "a"},
+ {R"(@tparam a template type parameter doc with
+multiple lines)",
+ R"( template type parameter doc with
+multiple lines)",
+ "a"},
+ {R"(@tparam a template type parameter doc with an
+ at unknown command starting a new line)",
+ R"( template type parameter doc with an
+ at unknown command starting a new line)",
+ "a"},
+ {R"(@tparam a template type parameter doc with a
+ at note command which is a new block command and therefore ends the template type parameter doc paragraph)",
+ R"( template type parameter doc with a
+)",
+ "a"},
+ {R"(Unrelated docs
+ at tparam a template type parameter doc
+
+New paragraph with unrelated docs)",
+ " template type parameter doc", "a"},
+ };
+ for (const auto &C : Cases) {
+ std::string Result;
+ llvm::raw_string_ostream OS(Result);
+ SymbolDocCommentVisitor SymbolDoc(C.Documentation, CommentOpts);
+
+ SymbolDoc.templateTypeParmDocToString(C.TemplateParameterName, OS);
+
+ EXPECT_EQ(Result, C.ExpectedOutputString);
+ }
+}
+
} // namespace clangd
} // namespace clang
diff --git a/clang/include/clang/AST/Comment.h b/clang/include/clang/AST/Comment.h
index 9ea86089373d5..84e5444675ae1 100644
--- a/clang/include/clang/AST/Comment.h
+++ b/clang/include/clang/AST/Comment.h
@@ -344,15 +344,6 @@ class InlineCommandComment : public InlineContentComment {
ArrayRef<Argument> Args;
public:
- InlineCommandComment(SourceLocation LocBegin, SourceLocation LocEnd,
- unsigned CommandID, InlineCommandRenderKind RK,
- ArrayRef<Argument> Args)
- : InlineContentComment(CommentKind::InlineCommandComment, LocBegin,
- LocEnd),
- Args(Args) {
- InlineCommandCommentBits.RenderKind = llvm::to_underlying(RK);
- InlineCommandCommentBits.CommandID = CommandID;
- }
InlineCommandComment(SourceLocation LocBegin, SourceLocation LocEnd,
unsigned CommandID, InlineCommandRenderKind RK,
CommandMarkerKind CommandMarker, ArrayRef<Argument> Args)
diff --git a/clang/include/clang/AST/CommentLexer.h b/clang/include/clang/AST/CommentLexer.h
index 9aa1681cb2c5c..194a31cb7b934 100644
--- a/clang/include/clang/AST/CommentLexer.h
+++ b/clang/include/clang/AST/CommentLexer.h
@@ -33,9 +33,12 @@ enum TokenKind {
eof,
newline,
text,
- unknown_command, // Command that does not have an ID.
- backslash_command, // Command with an ID, that used backslash marker.
- at_command, // Command with an ID, that used 'at' marker.
+ unknown_backslash_command, // Command that does not have an ID, that used
+ // backslash marker.
+ unknown_at_command, // Command that does not have an ID, that used 'at'
+ // marker.
+ backslash_command, // Command with an ID, that used backslash marker.
+ at_command, // Command with an ID, that used 'at' marker.
verbatim_block_begin,
verbatim_block_line,
verbatim_block_end,
@@ -107,12 +110,12 @@ class Token {
}
StringRef getUnknownCommandName() const LLVM_READONLY {
- assert(is(tok::unknown_command));
+ assert(is(tok::unknown_backslash_command) || is(tok::unknown_at_command));
return StringRef(TextPtr, IntVal);
}
void setUnknownCommandName(StringRef Name) {
- assert(is(tok::unknown_command));
+ assert(is(tok::unknown_backslash_command) || is(tok::unknown_at_command));
TextPtr = Name.data();
IntVal = Name.size();
}
diff --git a/clang/include/clang/AST/CommentSema.h b/clang/include/clang/AST/CommentSema.h
index 8dc6e50763dc5..3a7eda6538cbe 100644
--- a/clang/include/clang/AST/CommentSema.h
+++ b/clang/include/clang/AST/CommentSema.h
@@ -136,11 +136,13 @@ class Sema {
InlineContentComment *actOnUnknownCommand(SourceLocation LocBegin,
SourceLocation LocEnd,
- StringRef CommandName);
+ StringRef CommandName,
+ CommandMarkerKind CommandMarker);
InlineContentComment *actOnUnknownCommand(SourceLocation LocBegin,
SourceLocation LocEnd,
- unsigned CommandID);
+ unsigned CommandID,
+ CommandMarkerKind CommandMarker);
TextComment *actOnText(SourceLocation LocBegin,
SourceLocation LocEnd,
diff --git a/clang/lib/AST/CommentLexer.cpp b/clang/lib/AST/CommentLexer.cpp
index a0903d0903dd8..a8ac08c90c630 100644
--- a/clang/lib/AST/CommentLexer.cpp
+++ b/clang/lib/AST/CommentLexer.cpp
@@ -7,6 +7,7 @@
//===----------------------------------------------------------------------===//
#include "clang/AST/CommentLexer.h"
+#include "clang/AST/Comment.h"
#include "clang/AST/CommentCommandTraits.h"
#include "clang/Basic/CharInfo.h"
#include "clang/Basic/DiagnosticComment.h"
@@ -420,7 +421,10 @@ void Lexer::lexCommentText(Token &T) {
<< FullRange << CommandName << CorrectedName
<< FixItHint::CreateReplacement(CommandRange, CorrectedName);
} else {
- formTokenWithChars(T, TokenPtr, tok::unknown_command);
+ formTokenWithChars(T, TokenPtr,
+ CommandKind == tok::backslash_command
+ ? tok::unknown_backslash_command
+ : tok::unknown_at_command);
T.setUnknownCommandName(CommandName);
Diag(T.getLocation(), diag::warn_unknown_comment_command_name)
<< SourceRange(T.getLocation(), T.getEndLocation());
diff --git a/clang/lib/AST/CommentParser.cpp b/clang/lib/AST/CommentParser.cpp
index 68f18cfb5173e..87f8c870919d1 100644
--- a/clang/lib/AST/CommentParser.cpp
+++ b/clang/lib/AST/CommentParser.cpp
@@ -726,10 +726,12 @@ BlockContentComment *Parser::parseParagraphOrBlockCommand() {
case tok::eof:
break; // Block content or EOF ahead, finish this parapgaph.
- case tok::unknown_command:
- Content.push_back(S.actOnUnknownCommand(Tok.getLocation(),
- Tok.getEndLocation(),
- Tok.getUnknownCommandName()));
+ case tok::unknown_backslash_command:
+ case tok::unknown_at_command:
+ Content.push_back(S.actOnUnknownCommand(
+ Tok.getLocation(), Tok.getEndLocation(), Tok.getUnknownCommandName(),
+ Tok.getKind() == tok::unknown_backslash_command ? CMK_Backslash
+ : CMK_At));
consumeToken();
continue;
@@ -751,9 +753,9 @@ BlockContentComment *Parser::parseParagraphOrBlockCommand() {
continue;
}
if (Info->IsUnknownCommand) {
- Content.push_back(S.actOnUnknownCommand(Tok.getLocation(),
- Tok.getEndLocation(),
- Info->getID()));
+ Content.push_back(S.actOnUnknownCommand(
+ Tok.getLocation(), Tok.getEndLocation(), Info->getID(),
+ Tok.getKind() == tok::backslash_command ? CMK_Backslash : CMK_At));
consumeToken();
continue;
}
@@ -892,7 +894,8 @@ VerbatimLineComment *Parser::parseVerbatimLine() {
BlockContentComment *Parser::parseBlockContent() {
switch (Tok.getKind()) {
case tok::text:
- case tok::unknown_command:
+ case tok::unknown_backslash_command:
+ case tok::unknown_at_command:
case tok::backslash_command:
case tok::at_command:
case tok::html_start_tag:
diff --git a/clang/lib/AST/CommentSema.cpp b/clang/lib/AST/CommentSema.cpp
index e74c7cb5ce605..bc7884f1ffd52 100644
--- a/clang/lib/AST/CommentSema.cpp
+++ b/clang/lib/AST/CommentSema.cpp
@@ -376,19 +376,21 @@ Sema::actOnInlineCommand(SourceLocation CommandLocBegin,
getInlineCommandRenderKind(CommandName), CommandMarker, Args);
}
-InlineContentComment *Sema::actOnUnknownCommand(SourceLocation LocBegin,
- SourceLocation LocEnd,
- StringRef CommandName) {
+InlineContentComment *
+Sema::actOnUnknownCommand(SourceLocation LocBegin, SourceLocation LocEnd,
+ StringRef CommandName,
+ CommandMarkerKind CommandMarker) {
unsigned CommandID = Traits.registerUnknownCommand(CommandName)->getID();
- return actOnUnknownCommand(LocBegin, LocEnd, CommandID);
+ return actOnUnknownCommand(LocBegin, LocEnd, CommandID, CommandMarker);
}
-InlineContentComment *Sema::actOnUnknownCommand(SourceLocation LocBegin,
- SourceLocation LocEnd,
- unsigned CommandID) {
+InlineContentComment *
+Sema::actOnUnknownCommand(SourceLocation LocBegin, SourceLocation LocEnd,
+ unsigned CommandID, CommandMarkerKind CommandMarker) {
ArrayRef<InlineCommandComment::Argument> Args;
- return new (Allocator) InlineCommandComment(
- LocBegin, LocEnd, CommandID, InlineCommandRenderKind::Normal, Args);
+ return new (Allocator) InlineCommandComment(LocBegin, LocEnd, CommandID,
+ InlineCommandRenderKind::Normal,
+ CommandMarker, Args);
}
TextComment *Sema::actOnText(SourceLocation LocBegin,
diff --git a/clang/unittests/AST/CommentLexer.cpp b/clang/unittests/AST/CommentLexer.cpp
index 99f469173964e..1d016cee2877b 100644
--- a/clang/unittests/AST/CommentLexer.cpp
+++ b/clang/unittests/AST/CommentLexer.cpp
@@ -447,22 +447,22 @@ TEST_F(CommentLexerTest, DoxygenCommand9) {
ASSERT_EQ(tok::text, Toks[0].getKind());
ASSERT_EQ(StringRef(" "), Toks[0].getText());
- ASSERT_EQ(tok::unknown_command, Toks[1].getKind());
+ ASSERT_EQ(tok::unknown_backslash_command, Toks[1].getKind());
ASSERT_EQ(StringRef("aaa"), Toks[1].getUnknownCommandName());
- ASSERT_EQ(tok::unknown_command, Toks[2].getKind());
+ ASSERT_EQ(tok::unknown_backslash_command, Toks[2].getKind());
ASSERT_EQ(StringRef("bbb"), Toks[2].getUnknownCommandName());
ASSERT_EQ(tok::text, Toks[3].getKind());
ASSERT_EQ(StringRef(" "), Toks[3].getText());
- ASSERT_EQ(tok::unknown_command, Toks[4].getKind());
+ ASSERT_EQ(tok::unknown_backslash_command, Toks[4].getKind());
ASSERT_EQ(StringRef("ccc"), Toks[4].getUnknownCommandName());
ASSERT_EQ(tok::text, Toks[5].getKind());
ASSERT_EQ(StringRef("\t"), Toks[5].getText());
- ASSERT_EQ(tok::unknown_command, Toks[6].getKind());
+ ASSERT_EQ(tok::unknown_backslash_command, Toks[6].getKind());
ASSERT_EQ(StringRef("ddd"), Toks[6].getUnknownCommandName());
ASSERT_EQ(tok::newline, Toks[7].getKind());
@@ -485,6 +485,38 @@ TEST_F(CommentLexerTest, DoxygenCommand10) {
ASSERT_EQ(tok::newline, Toks[2].getKind());
}
+TEST_F(CommentLexerTest, DoxygenCommand11) {
+ const char *Source = "/// @aaa at bbb @ccc\t at ddd\n";
+ std::vector<Token> Toks;
+
+ lexString(Source, Toks);
+
+ ASSERT_EQ(8U, Toks.size());
+
+ ASSERT_EQ(tok::text, Toks[0].getKind());
+ ASSERT_EQ(StringRef(" "), Toks[0].getText());
+
+ ASSERT_EQ(tok::unknown_at_command, Toks[1].getKind());
+ ASSERT_EQ(StringRef("aaa"), Toks[1].getUnknownCommandName());
+
+ ASSERT_EQ(tok::unknown_at_command, Toks[2].getKind());
+ ASSERT_EQ(StringRef("bbb"), Toks[2].getUnknownCommandName());
+
+ ASSERT_EQ(tok::text, Toks[3].getKind());
+ ASSERT_EQ(StringRef(" "), Toks[3].getText());
+
+ ASSERT_EQ(tok::unknown_at_command, Toks[4].getKind());
+ ASSERT_EQ(StringRef("ccc"), Toks[4].getUnknownCommandName());
+
+ ASSERT_EQ(tok::text, Toks[5].getKind());
+ ASSERT_EQ(StringRef("\t"), Toks[5].getText());
+
+ ASSERT_EQ(tok::unknown_at_command, Toks[6].getKind());
+ ASSERT_EQ(StringRef("ddd"), Toks[6].getUnknownCommandName());
+
+ ASSERT_EQ(tok::newline, Toks[7].getKind());
+}
+
TEST_F(CommentLexerTest, RegisterCustomBlockCommand) {
const char *Source =
"/// \\NewBlockCommand Aaa.\n"
More information about the cfe-commits
mailing list