https://github.com/voyager-jhk updated 
https://github.com/llvm/llvm-project/pull/216336

>From b831ee106b7b2a3f71dd2009c66bf8629e79dded Mon Sep 17 00:00:00 2001
From: voyager-jhk <[email protected]>
Date: Fri, 14 Aug 2026 22:37:24 +0800
Subject: [PATCH] [clangd] Enable query-based custom checks

Enable query-based custom checks in clangd behind a new
`Diagnostics.ClangTidy.ExperimentalCustomChecks` configuration option.

Custom checks are registered from the per-file clang-tidy options and remain
subject to clangd's `FastCheckFilter` policy.

Fixes #206696
---
 clang-tools-extra/clangd/Config.h             |  1 +
 clang-tools-extra/clangd/ConfigCompile.cpp    |  5 +++
 clang-tools-extra/clangd/ConfigFragment.h     |  4 ++
 clang-tools-extra/clangd/ConfigYAML.cpp       |  4 ++
 clang-tools-extra/clangd/ParsedAST.cpp        | 23 ++++++++--
 .../clangd/unittests/ConfigCompileTests.cpp   | 14 ++++++
 .../clangd/unittests/ConfigYAMLTests.cpp      | 15 +++++++
 .../clangd/unittests/DiagnosticsTests.cpp     | 43 +++++++++++++++++++
 clang-tools-extra/docs/ReleaseNotes.md        |  5 +++
 .../clang-tidy/QueryBasedCustomChecks.rst     | 19 ++++++++
 10 files changed, 130 insertions(+), 3 deletions(-)

diff --git a/clang-tools-extra/clangd/Config.h 
b/clang-tools-extra/clangd/Config.h
index a94a1727199bd..7365c59bb1b36 100644
--- a/clang-tools-extra/clangd/Config.h
+++ b/clang-tools-extra/clangd/Config.h
@@ -111,6 +111,7 @@ struct Config {
       std::string Checks;
       llvm::StringMap<std::string> CheckOptions;
       FastCheckPolicy FastCheckFilter = FastCheckPolicy::Strict;
+      bool ExperimentalCustomChecks = false;
     } ClangTidy;
 
     IncludesPolicy UnusedIncludes = IncludesPolicy::Strict;
diff --git a/clang-tools-extra/clangd/ConfigCompile.cpp 
b/clang-tools-extra/clangd/ConfigCompile.cpp
index 2b41949d6d05c..34394453ca8a4 100644
--- a/clang-tools-extra/clangd/ConfigCompile.cpp
+++ b/clang-tools-extra/clangd/ConfigCompile.cpp
@@ -624,6 +624,11 @@ struct FragmentCompiler {
         Out.Apply.push_back([Val](const Params &, Config &C) {
           C.Diagnostics.ClangTidy.FastCheckFilter = *Val;
         });
+    if (F.ExperimentalCustomChecks)
+      Out.Apply.push_back(
+          [Enabled = **F.ExperimentalCustomChecks](const Params &, Config &C) {
+            C.Diagnostics.ClangTidy.ExperimentalCustomChecks = Enabled;
+          });
   }
 
   void compile(Fragment::DiagnosticsBlock::IncludesBlock &&F) {
diff --git a/clang-tools-extra/clangd/ConfigFragment.h 
b/clang-tools-extra/clangd/ConfigFragment.h
index 7604fe4e24c97..560c8d0ed2cf3 100644
--- a/clang-tools-extra/clangd/ConfigFragment.h
+++ b/clang-tools-extra/clangd/ConfigFragment.h
@@ -297,6 +297,10 @@ struct Fragment {
       ///   Loose: Run checks unless they are known to be slow.
       ///   None: Run checks regardless of their speed.
       std::optional<Located<std::string>> FastCheckFilter;
+
+      /// Whether to enable experimental query-based custom checks configured
+      /// in .clang-tidy files.
+      std::optional<Located<bool>> ExperimentalCustomChecks;
     };
     ClangTidyBlock ClangTidy;
   };
diff --git a/clang-tools-extra/clangd/ConfigYAML.cpp 
b/clang-tools-extra/clangd/ConfigYAML.cpp
index 940c7b8bd2de1..ad1bd814f8405 100644
--- a/clang-tools-extra/clangd/ConfigYAML.cpp
+++ b/clang-tools-extra/clangd/ConfigYAML.cpp
@@ -174,6 +174,10 @@ class Parser {
       if (auto FastCheckFilter = scalarValue(N, "FastCheckFilter"))
         F.FastCheckFilter = *FastCheckFilter;
     });
+    Dict.handle("ExperimentalCustomChecks", [&](Node &N) {
+      if (auto Value = boolValue(N, "ExperimentalCustomChecks"))
+        F.ExperimentalCustomChecks = *Value;
+    });
     Dict.parse(N);
   }
 
diff --git a/clang-tools-extra/clangd/ParsedAST.cpp 
b/clang-tools-extra/clangd/ParsedAST.cpp
index df56420cd7f24..b498f8535abf6 100644
--- a/clang-tools-extra/clangd/ParsedAST.cpp
+++ b/clang-tools-extra/clangd/ParsedAST.cpp
@@ -7,6 +7,7 @@
 
//===----------------------------------------------------------------------===//
 
 #include "ParsedAST.h"
+#include "../clang-tidy/ClangTidy.h"
 #include "../clang-tidy/ClangTidyCheck.h"
 #include "../clang-tidy/ClangTidyDiagnosticConsumer.h"
 #include "../clang-tidy/ClangTidyModule.h"
@@ -69,6 +70,7 @@
 #include <cstddef>
 #include <iterator>
 #include <memory>
+#include <mutex>
 #include <optional>
 #include <string>
 #include <tuple>
@@ -574,15 +576,30 @@ ParsedAST::build(llvm::StringRef Filename, const 
ParseInputs &Inputs,
         E.instantiate()->addCheckFactories(*CTFactories);
       return CTFactories;
     }();
-    tidy::ClangTidyCheckFactories FastFactories = filterFastTidyChecks(
-        *AllCTFactories, Cfg.Diagnostics.ClangTidy.FastCheckFilter);
     CTContext.emplace(std::make_unique<tidy::DefaultOptionsProvider>(
-        tidy::ClangTidyGlobalOptions(), ClangTidyOpts));
+                          tidy::ClangTidyGlobalOptions(), ClangTidyOpts),
+                      /*AllowEnablingAnalyzerAlphaCheckers=*/false,
+                      /*EnableModuleHeadersParsing=*/false,
+                      Cfg.Diagnostics.ClangTidy.ExperimentalCustomChecks);
     // The lifetime of DiagnosticOptions is managed by \c Clang.
     CTContext->setDiagnosticsEngine(nullptr, &Clang->getDiagnostics());
     CTContext->setASTContext(&Clang->getASTContext());
     CTContext->setCurrentFile(Filename);
     CTContext->setSelfContainedDiags(true);
+    tidy::ClangTidyCheckFactories CTCheckFactories = *AllCTFactories;
+#if CLANG_TIDY_ENABLE_QUERY_BASED_CUSTOM_CHECKS
+    if (CTContext->canExperimentalCustomChecks() &&
+        tidy::custom::RegisterCustomChecks) {
+      // RegisterCustomChecks tracks names in process-wide mutable state.
+      // Serializing its use keeps concurrent AST builds independent.
+      static std::mutex CustomChecksMu;
+      std::lock_guard<std::mutex> Lock(CustomChecksMu);
+      tidy::custom::RegisterCustomChecks(CTContext->getOptions(),
+                                         CTCheckFactories);
+    }
+#endif
+    tidy::ClangTidyCheckFactories FastFactories = filterFastTidyChecks(
+        CTCheckFactories, Cfg.Diagnostics.ClangTidy.FastCheckFilter);
     CTChecks = FastFactories.createChecksForLanguage(&*CTContext);
     Preprocessor *PP = &Clang->getPreprocessor();
     for (const auto &Check : CTChecks) {
diff --git a/clang-tools-extra/clangd/unittests/ConfigCompileTests.cpp 
b/clang-tools-extra/clangd/unittests/ConfigCompileTests.cpp
index 5fecf32f8e5a5..6759afa79bbcd 100644
--- a/clang-tools-extra/clangd/unittests/ConfigCompileTests.cpp
+++ b/clang-tools-extra/clangd/unittests/ConfigCompileTests.cpp
@@ -333,6 +333,20 @@ TEST_F(ConfigCompileTests, Tidy) {
 #endif
 }
 
+TEST_F(ConfigCompileTests, TidyExperimentalCustomChecks) {
+  EXPECT_FALSE(Conf.Diagnostics.ClangTidy.ExperimentalCustomChecks);
+
+  Frag.Diagnostics.ClangTidy.ExperimentalCustomChecks = true;
+  EXPECT_TRUE(compileAndApply());
+  EXPECT_TRUE(Conf.Diagnostics.ClangTidy.ExperimentalCustomChecks);
+
+  Fragment Override;
+  Override.Diagnostics.ClangTidy.ExperimentalCustomChecks = false;
+  auto Compiled = std::move(Override).compile(Diags.callback());
+  EXPECT_TRUE(Compiled(Parm, Conf));
+  EXPECT_FALSE(Conf.Diagnostics.ClangTidy.ExperimentalCustomChecks);
+}
+
 TEST_F(ConfigCompileTests, TidyBadChecks) {
   auto &Tidy = Frag.Diagnostics.ClangTidy;
   Tidy.Add.emplace_back("unknown-check");
diff --git a/clang-tools-extra/clangd/unittests/ConfigYAMLTests.cpp 
b/clang-tools-extra/clangd/unittests/ConfigYAMLTests.cpp
index 264cb453b413c..753cd14eaa32c 100644
--- a/clang-tools-extra/clangd/unittests/ConfigYAMLTests.cpp
+++ b/clang-tools-extra/clangd/unittests/ConfigYAMLTests.cpp
@@ -320,6 +320,21 @@ TEST(ParseYAML, IncludesAnalyzeAngledIncludes) {
               llvm::ValueIs(val(true)));
 }
 
+TEST(ParseYAML, ClangTidyExperimentalCustomChecks) {
+  CapturedDiags Diags;
+  Annotations YAML(R"yaml(
+Diagnostics:
+  ClangTidy:
+    ExperimentalCustomChecks: true
+  )yaml");
+  auto Results =
+      Fragment::parseYAML(YAML.code(), "config.yaml", Diags.callback());
+  ASSERT_THAT(Diags.Diagnostics, IsEmpty());
+  ASSERT_EQ(Results.size(), 1u);
+  EXPECT_THAT(Results[0].Diagnostics.ClangTidy.ExperimentalCustomChecks,
+              llvm::ValueIs(val(true)));
+}
+
 TEST(ParseYAML, Style) {
   CapturedDiags Diags;
   Annotations YAML(R"yaml(
diff --git a/clang-tools-extra/clangd/unittests/DiagnosticsTests.cpp 
b/clang-tools-extra/clangd/unittests/DiagnosticsTests.cpp
index 6d91ac1ef1e8e..ed743f072822b 100644
--- a/clang-tools-extra/clangd/unittests/DiagnosticsTests.cpp
+++ b/clang-tools-extra/clangd/unittests/DiagnosticsTests.cpp
@@ -18,6 +18,7 @@
 #include "TestIndex.h"
 #include "TestTU.h"
 #include "TidyProvider.h"
+#include "clang-tidy-config.h"
 #include "index/MemIndex.h"
 #include "index/Ref.h"
 #include "index/Relation.h"
@@ -359,6 +360,48 @@ TEST(DiagnosticsTest, ClangTidy) {
                "function 'bar' is within a recursive call chain"))));
 }
 
+#if CLANGD_TIDY_CHECKS && CLANG_TIDY_ENABLE_QUERY_BASED_CUSTOM_CHECKS
+TEST(DiagnosticsTest, ClangTidyExperimentalCustomChecks) {
+  Annotations Test(R"cpp(
+    int x = $literal[[0]];
+  )cpp");
+  auto TU = TestTU::withCode(Test.code());
+  TU.ClangTidyProvider = [](tidy::ClangTidyOptions &Opts, llvm::StringRef) {
+    Opts.Checks = "-*,custom-integer-literal";
+    tidy::ClangTidyOptions::CustomCheckValue Check;
+    Check.Name = "integer-literal";
+    Check.Query = R"(match integerLiteral().bind("literal"))";
+    tidy::ClangTidyOptions::CustomCheckDiag Diagnostic;
+    Diagnostic.BindName = "literal";
+    Diagnostic.Message = "integer literal found";
+    Diagnostic.Level = DiagnosticIDs::Warning;
+    Check.Diags.push_back(std::move(Diagnostic));
+    Opts.CustomChecks.emplace();
+    Opts.CustomChecks->push_back(std::move(Check));
+  };
+
+  auto DiagnosticsFor = [&](bool Enabled, Config::FastCheckPolicy Policy) {
+    Config Cfg;
+    Cfg.Diagnostics.ClangTidy.ExperimentalCustomChecks = Enabled;
+    Cfg.Diagnostics.ClangTidy.FastCheckFilter = Policy;
+    WithContextValue WithCfg(Config::Key, std::move(Cfg));
+    auto AST = TU.build();
+    return std::vector<clangd::Diag>(AST.getDiagnostics());
+  };
+  auto CustomDiagnostic =
+      AllOf(Diag(Test.range("literal"), "integer literal found"),
+            diagSource(Diag::ClangTidy), diagName("custom-integer-literal"),
+            diagSeverity(DiagnosticsEngine::Warning));
+
+  EXPECT_THAT(DiagnosticsFor(false, Config::FastCheckPolicy::Loose), 
IsEmpty());
+  EXPECT_THAT(DiagnosticsFor(true, Config::FastCheckPolicy::Strict), 
IsEmpty());
+  EXPECT_THAT(DiagnosticsFor(true, Config::FastCheckPolicy::Loose),
+              ElementsAre(CustomDiagnostic));
+  EXPECT_THAT(DiagnosticsFor(true, Config::FastCheckPolicy::None),
+              ElementsAre(CustomDiagnostic));
+}
+#endif
+
 TEST(DiagnosticsTest, ClangTidyRedundantParenthesesFix) {
   Annotations Test(R"cpp(
     int func() {
diff --git a/clang-tools-extra/docs/ReleaseNotes.md 
b/clang-tools-extra/docs/ReleaseNotes.md
index 29de9aef9e4b6..75b9f6a4ef2e5 100644
--- a/clang-tools-extra/docs/ReleaseNotes.md
+++ b/clang-tools-extra/docs/ReleaseNotes.md
@@ -70,6 +70,11 @@ infrastructure are described first, followed by 
tool-specific sections.
 
 #### Diagnostics
 
+- Query-based custom clang-tidy checks can now be enabled with the
+  `Diagnostics.ClangTidy.ExperimentalCustomChecks` clangd configuration option.
+  Custom checks are subject to `FastCheckFilter`, and therefore require
+  `FastCheckFilter: Loose` or `None` to run.
+
 #### Semantic Highlighting
 
 #### Compile flags
diff --git a/clang-tools-extra/docs/clang-tidy/QueryBasedCustomChecks.rst 
b/clang-tools-extra/docs/clang-tidy/QueryBasedCustomChecks.rst
index 1d91fad975c7f..73dded0087bdc 100644
--- a/clang-tools-extra/docs/clang-tidy/QueryBasedCustomChecks.rst
+++ b/clang-tools-extra/docs/clang-tidy/QueryBasedCustomChecks.rst
@@ -63,6 +63,25 @@ Example
     main(); // warning: call to main function. [custom-call-main-function]
   }
 
+Using with clangd
+=================
+
+To enable query-based custom checks in clangd, add the following to the clangd
+configuration:
+
+.. code-block:: yaml
+
+  Diagnostics:
+    ClangTidy:
+      ExperimentalCustomChecks: true
+      FastCheckFilter: Loose
+
+The custom-check definitions and check selection remain part of the clang-tidy
+configuration. ``ExperimentalCustomChecks`` does not override clangd's
+``FastCheckFilter``. Because custom checks are not present in clangd's
+fast-check database, ``Strict`` excludes them, while ``Loose`` and ``None``
+allow them.
+
 Matters Need Attention
 ======================
 

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

Reply via email to