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 <[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