https://github.com/tcottin created 
https://github.com/llvm/llvm-project/pull/202121

This patch mainly fixes a bug with parsing of unknown doxygen commands in 
function parameter documentation.

To extract the parameter documentation from the function documentation, the 
whole function documentation is parsed first.
Then the documentation paragraph for the requested parameter is "converted" to 
a string and stored as the documentation for the parameter. The string is 
converted by visiting and dumping all chunks of the parsed paragraph.

When unknown doxygen commands are parsed (during the function documentation 
parsing step), they are registered in a `clang::comments::CommandTraits` object.
Visiting the unknown command requires to query the registered commands through 
the `clang::comments::CommandTraits` object to get the command name.

The bug was that the function documentation parsing and the visiting step used 
2 different `clang::comments::CommandTraits` objects. Hence the visiting step 
fails (array access out of bounds) when trying to retrieve the command names 
for unknown commands.

The patch moves the function documentation parsing step to the construction of 
the `SymbolDocCommentVisitor` which is also responsible for converting the 
parameter documentation paragraph to a string.
This way the same `clang::comments::CommandTraits` is used and the query for 
unknown command names is correct.

Additional fixes:

- correct some whitespace behaviour for doxygen inline commands
- add a new token kind for the clang comment parser to distinguish unknown 
"backslash" and "at" commands to correctly show them in the clangd hover info

Related issue: clangd/clangd#2671

>From d5e315d578a0b80bbc3aca76c93dc5bf0277314e Mon Sep 17 00:00:00 2001
From: Tim Cottin <[email protected]>
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@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`
+
+@brief brief doc  
+@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
+@unknown command starting a new line)",
+       R"( parameter doc with an
+@unknown command starting a new line)",
+       "a"},
+      {R"(@param a parameter doc with a
+@note command which is a new block command and therefore ends the parameter 
doc paragraph)",
+       R"( parameter doc with a
+)",
+       "a"},
+      {R"(Unrelated docs
+@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
+@unknown command starting a new line)",
+       R"( template type parameter doc with an
+@unknown command starting a new line)",
+       "a"},
+      {R"(@tparam a template type parameter doc with a
+@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
+@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@bbb @ccc\t@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"

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to