https://github.com/Anthony-Gaudino created https://github.com/llvm/llvm-project/pull/225404
## Summary A comment block containing a section-divider line was reported as documentation for the next declaration. In practice this meant the title of a banner was shown on hover, e.g.: ```cpp // Per-frame pump // ======================================================================== void update(); // hover showed "Per-frame pump\n===..." ``` `getDeclComment` already filtered comments consisting solely of special characters (`looksLikeDocComment`), but a divider combined with a title line passed the filter and the title was shown as documentation. ## What this changes - `looksLikeDocComment` (`CodeCompletionStrings.cpp`) now also rejects any comment containing a divider line: a line that, trimmed, is a run of 10+ repetitions of a single character. The check runs on the formatted text, so it applies regardless of comment style (`//`, `///`, `/* ... */`). - The length threshold keeps short runs (markdown `---` rules and similar adornments) working as documentation; a regression test pins this boundary. This affects hover and code-completion documentation, which share `getDeclComment`. ## Testing - New cases in `TEST(Hover, Structured)`: titled `===` divider, same with a blank line before the declaration, `---` divider, `/* ### */` block banner, `+++` divider, and a short-run negative control. - Verified the divider cases fail without the fix (hover showed the banner title) and pass with it. - Full `ClangdTests` suite passes (1434 tests). ## Related - Related to clangd/clangd#974 (divider banners matched across blank lines). From 9738df685a6e7d9932298bcb08fd148dd4aaabbc Mon Sep 17 00:00:00 2001 From: Anthony Gaudino <[email protected]> Date: Tue, 22 Sep 2026 14:44:33 +0100 Subject: [PATCH] [clangd] Ignore section-divider comments in hover documentation Comments containing a divider line (a run of 10+ identical characters, e.g. // ===... or /* ###... */ banners) are never documentation, even when combined with a title line. Previously the title of such a block was reported as documentation for the next declaration. Related to clangd/clangd#974. --- .../clangd/CodeCompletionStrings.cpp | 28 ++++- .../clangd/unittests/HoverTests.cpp | 100 ++++++++++++++++++ 2 files changed, 127 insertions(+), 1 deletion(-) diff --git a/clang-tools-extra/clangd/CodeCompletionStrings.cpp b/clang-tools-extra/clangd/CodeCompletionStrings.cpp index dc86be60a876f..61c86f7b1d82c 100644 --- a/clang-tools-extra/clangd/CodeCompletionStrings.cpp +++ b/clang-tools-extra/clangd/CodeCompletionStrings.cpp @@ -54,13 +54,39 @@ void appendOptionalChunk(const CodeCompletionString &CCS, std::string *Out) { } } +/// A divider line is a long run of a single repeated character, as used in +/// section banners. These are never documentation, even when combined with +/// a title line, as in: +/// // Per-frame pump +/// // ==================================================================== +/// The length threshold keeps short runs (e.g. markdown `---` rules or RST +/// adornments) working as documentation. +bool isDividerLine(llvm::StringRef Line) { + constexpr unsigned MinDividerLength = 10; + Line = Line.trim(" \t\r\n"); + if (Line.size() < MinDividerLength) + return false; + return Line.find_first_not_of(Line.front()) == llvm::StringRef::npos; +} + bool looksLikeDocComment(llvm::StringRef CommentText) { // We don't report comments that only contain "special" chars. // This avoids reporting various delimiters, like: // ================= // ----------------- // ***************** - return CommentText.find_first_not_of("/*-= \t\r\n") != llvm::StringRef::npos; + if (CommentText.find_first_not_of("/*-= \t\r\n") == llvm::StringRef::npos) + return false; + // Nor comments containing a section-divider line. Without this, the title + // of a divider block is reported as documentation for the next declaration. + llvm::StringRef Rest = CommentText; + while (!Rest.empty()) { + const auto Split = Rest.split('\n'); + if (isDividerLine(Split.first)) + return false; + Rest = Split.second; + } + return true; } // Determine whether the completion string should be patched diff --git a/clang-tools-extra/clangd/unittests/HoverTests.cpp b/clang-tools-extra/clangd/unittests/HoverTests.cpp index 15b03e6bb6ece..0c2d667a45589 100644 --- a/clang-tools-extra/clangd/unittests/HoverTests.cpp +++ b/clang-tools-extra/clangd/unittests/HoverTests.cpp @@ -57,6 +57,106 @@ TEST(Hover, Structured) { HI.Type = "void ()"; HI.Parameters.emplace(); }}, + // A section divider with a title is not documentation. + {R"cpp( + // Per-frame pump + // ======================================================================== + void [[fo^o]]() {} + )cpp", + [](HoverInfo &HI) { + HI.NamespaceScope = ""; + HI.Name = "foo"; + HI.Kind = index::SymbolKind::Function; + HI.Documentation = ""; + HI.Definition = "void foo()"; + HI.ReturnType = "void"; + HI.Type = "void ()"; + HI.Parameters.emplace(); + }}, + // Same, with a blank line between the divider and the declaration. + {R"cpp( + // Per-frame pump + // ======================================================================== + + void [[fo^o]]() {} + )cpp", + [](HoverInfo &HI) { + HI.NamespaceScope = ""; + HI.Name = "foo"; + HI.Kind = index::SymbolKind::Function; + HI.Documentation = ""; + HI.Definition = "void foo()"; + HI.ReturnType = "void"; + HI.Type = "void ()"; + HI.Parameters.emplace(); + }}, + // Other divider styles are not documentation either. + {R"cpp( + // Appearance + // ------------------------------------------------------------------------ + void [[fo^o]]() {} + )cpp", + [](HoverInfo &HI) { + HI.NamespaceScope = ""; + HI.Name = "foo"; + HI.Kind = index::SymbolKind::Function; + HI.Documentation = ""; + HI.Definition = "void foo()"; + HI.ReturnType = "void"; + HI.Type = "void ()"; + HI.Parameters.emplace(); + }}, + // Block-comment banners are not documentation either. + {R"cpp( + /* + ########################################################################## + Private + ########################################################################## + */ + void [[fo^o]]() {} + )cpp", + [](HoverInfo &HI) { + HI.NamespaceScope = ""; + HI.Name = "foo"; + HI.Kind = index::SymbolKind::Function; + HI.Documentation = ""; + HI.Definition = "void foo()"; + HI.ReturnType = "void"; + HI.Type = "void ()"; + HI.Parameters.emplace(); + }}, + // Plus-run dividers are not documentation either. + {R"cpp( + // Helpers + // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + void [[fo^o]]() {} + )cpp", + [](HoverInfo &HI) { + HI.NamespaceScope = ""; + HI.Name = "foo"; + HI.Kind = index::SymbolKind::Function; + HI.Documentation = ""; + HI.Definition = "void foo()"; + HI.ReturnType = "void"; + HI.Type = "void ()"; + HI.Parameters.emplace(); + }}, + // Short runs (e.g. markdown rules) are still documentation. + {R"cpp( + // Best foo ever. + // --- + void [[fo^o]]() {} + )cpp", + [](HoverInfo &HI) { + HI.NamespaceScope = ""; + HI.Name = "foo"; + HI.Kind = index::SymbolKind::Function; + HI.Documentation = "Best foo ever.\n---"; + HI.Definition = "void foo()"; + HI.ReturnType = "void"; + HI.Type = "void ()"; + HI.Parameters.emplace(); + }}, {R"cpp( // Best foo ever. void [[fo^o]](auto x) {} _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
