[clang-tools-extra] [clangd] Enable query-based custom checks (PR #216336)
Peiqi Li via cfe-commits
cfe-commits at lists.llvm.org
Fri Aug 14 07:54:57 PDT 2026
https://github.com/voyager-jhk created https://github.com/llvm/llvm-project/pull/216336
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
Assisted by GPT-5.6
>From 7cd17d82b4e229a125ddd55c65735e734042b4b5 Mon Sep 17 00:00:00 2001
From: voyager-jhk <voyager.lpq at gmail.com>
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 | 27 +++++++-----
.../clang-tidy/QueryBasedCustomChecks.rst | 19 ++++++++
10 files changed, 142 insertions(+), 13 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..d4e3935629b06 100644
--- a/clang-tools-extra/docs/ReleaseNotes.md
+++ b/clang-tools-extra/docs/ReleaseNotes.md
@@ -10,6 +10,7 @@ myst:
% ReleaseNotes.md and ReleaseNotesTemplate.txt.
{#extra-clang-tools-release-releasenotestitle}
+
# Extra Clang Tools {{env.config.release}} {{ (('(In-Progress) ' if env.app.tags.has('PreRelease') else '') ~ 'Release Notes') }}
```{contents}
@@ -41,11 +42,12 @@ the latest release, please see the [Clang Web Site](https://clang.llvm.org) or
the [LLVM Web Site](https://llvm.org).
Note that if you are reading this file from a Git checkout or the
-main Clang web page, this document applies to the *next* release, not
+main Clang web page, this document applies to the _next_ release, not
the current one. To see the release notes for a specific release, please
see the [releases page](https://llvm.org/releases/).
{#what-s-new-in-extra-clang-tools-release}
+
## What's New in Extra Clang Tools {{env.config.release}}?
Some of the major new features and improvements to Extra Clang Tools are listed
@@ -58,7 +60,7 @@ infrastructure are described first, followed by tool-specific sections.
- The deprecated `zircon` clang-tidy module has been removed. Users of
`zircon-temporary-objects` should migrate to {doc}`fuchsia-temporary-objects
- <clang-tidy/checks/fuchsia/temporary-objects>`.
+<clang-tidy/checks/fuchsia/temporary-objects>`.
- In 22nd release, The `clang-tidy/ClangTidyModuleRegistry.h` header was deprecated.
All of the symbols it used to define were moved into `clang-tidy/ClangTidyModule.h`.
@@ -70,6 +72,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
@@ -101,7 +108,7 @@ infrastructure are described first, followed by tool-specific sections.
#### New checks
- New {doc}`performance-expensive-value-or
- <clang-tidy/checks/performance/expensive-value-or>` check.
+<clang-tidy/checks/performance/expensive-value-or>` check.
Finds calls to `value_or` (and alternative spellings `valueOr`,
`ValueOr`) on optional types where the return type is expensive to copy.
@@ -111,37 +118,37 @@ infrastructure are described first, followed by tool-specific sections.
#### Changes in existing checks
- Fixed a crash in {doc}`bugprone-misplaced-operator-in-strlen-in-alloc
- <clang-tidy/checks/bugprone/misplaced-operator-in-strlen-in-alloc>` when
+<clang-tidy/checks/bugprone/misplaced-operator-in-strlen-in-alloc>` when
checking an array new expression without a size expression.
- Fixed a crash in {doc}`bugprone-std-namespace-modification
- <clang-tidy/checks/bugprone/std-namespace-modification>` when checking
+<clang-tidy/checks/bugprone/std-namespace-modification>` when checking
lambda closure types used as template arguments.
- Improved {doc}`cppcoreguidelines-pro-type-member-init
- <clang-tidy/checks/cppcoreguidelines/pro-type-member-init>` check by treating
+<clang-tidy/checks/cppcoreguidelines/pro-type-member-init>` check by treating
`std::array` the same as built-in arrays when `IgnoreArrays` option is enabled.
- Improved {doc}`misc-redundant-expression
- <clang-tidy/checks/misc/redundant-expression>` by fixing false positives in
+<clang-tidy/checks/misc/redundant-expression>` by fixing false positives in
nested expressions involving different macros or a mix of macro and
non-macro operands.
- Improved {doc}`modernize-return-braced-init-list
- <clang-tidy/checks/modernize/return-braced-init-list>` check to no longer
+<clang-tidy/checks/modernize/return-braced-init-list>` check to no longer
rewrite the return value when the constructed type has a
`std::initializer_list` constructor, as the braced form could select a
different constructor.
- Improved {doc}`readability-named-parameter
- <clang-tidy/checks/readability/named-parameter>` check by ignoring
+<clang-tidy/checks/readability/named-parameter>` check by ignoring
standard tag types (e.g. `std::in_place_t`, `std::allocator_arg_t`,
`std::nothrow_t`, iterator tags, lock tags, etc.) that are used
exclusively for overload resolution. Added the {option}`IgnoredTypes`
option to allow customizing the set of ignored types.
- Improved {doc}`readability-use-std-min-max
- <clang-tidy/checks/readability/use-std-min-max>` check by fixing spurious
+<clang-tidy/checks/readability/use-std-min-max>` check by fixing spurious
trailing semicolons and lost comments when the `if` body has no braces.
#### Removed checks
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
======================
More information about the cfe-commits
mailing list