[clang-tools-extra] [llvm] reapply "[clang-tidy] support query based custom check" (PR #159547)

Congcong Cai via llvm-commits llvm-commits at lists.llvm.org
Thu Sep 18 03:19:58 PDT 2025


https://github.com/HerrCai0907 created https://github.com/llvm/llvm-project/pull/159547

reapply #131804 and #159289
Fixed cmake link issue. 

>From a686695e6691fcb824b2055c3dfa8f0fd3727355 Mon Sep 17 00:00:00 2001
From: DeNiCoN <denicon1234 at gmail.com>
Date: Mon, 17 Mar 2025 08:04:32 +0000
Subject: [PATCH 01/25] origin pr

---
 clang-tools-extra/clang-tidy/CMakeLists.txt   |  2 +
 .../clang-tidy/ClangQueryCheck.cpp            | 30 +++++++
 .../clang-tidy/ClangQueryCheck.h              | 43 ++++++++++
 clang-tools-extra/clang-tidy/ClangTidy.cpp    |  8 ++
 .../clang-tidy/ClangTidyOptions.cpp           | 84 +++++++++++++++++++
 .../clang-tidy/ClangTidyOptions.h             |  8 ++
 .../clang-tidy/tool/ClangTidyMain.cpp         | 15 +++-
 clang-tools-extra/docs/clang-tidy/index.rst   | 12 +++
 .../infrastructure/query-checks.cpp           | 53 ++++++++++++
 9 files changed, 254 insertions(+), 1 deletion(-)
 create mode 100644 clang-tools-extra/clang-tidy/ClangQueryCheck.cpp
 create mode 100644 clang-tools-extra/clang-tidy/ClangQueryCheck.h
 create mode 100644 clang-tools-extra/test/clang-tidy/infrastructure/query-checks.cpp

diff --git a/clang-tools-extra/clang-tidy/CMakeLists.txt b/clang-tools-extra/clang-tidy/CMakeLists.txt
index 93117cf1d6373..76585b012d174 100644
--- a/clang-tools-extra/clang-tidy/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/CMakeLists.txt
@@ -11,6 +11,7 @@ include_directories(BEFORE ${CMAKE_CURRENT_BINARY_DIR})
 add_clang_library(clangTidy STATIC
   ClangTidy.cpp
   ClangTidyCheck.cpp
+  ClangQueryCheck.cpp
   ClangTidyModule.cpp
   ClangTidyDiagnosticConsumer.cpp
   ClangTidyOptions.cpp
@@ -38,6 +39,7 @@ clang_target_link_libraries(clangTidy
   clangSerialization
   clangTooling
   clangToolingCore
+  clangQuery
   )
 
 if(CLANG_TIDY_ENABLE_STATIC_ANALYZER)
diff --git a/clang-tools-extra/clang-tidy/ClangQueryCheck.cpp b/clang-tools-extra/clang-tidy/ClangQueryCheck.cpp
new file mode 100644
index 0000000000000..a9f46116f7089
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/ClangQueryCheck.cpp
@@ -0,0 +1,30 @@
+//===--- ClangQueryCheck.cpp - clang-tidy ----------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "ClangQueryCheck.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::misc {
+
+void ClangQueryCheck::registerMatchers(MatchFinder *Finder) {
+  for (const auto &Matcher : Matchers) {
+    bool Ok = Finder->addDynamicMatcher(Matcher, this);
+    assert(Ok && "Expected to get top level matcher from query parser");
+  }
+}
+
+void ClangQueryCheck::check(const MatchFinder::MatchResult &Result) {
+  auto Map = Result.Nodes.getMap();
+  for (const auto &[k, v] : Map) {
+    diag(v.getSourceRange().getBegin(), k) << v.getSourceRange();
+  }
+}
+
+} // namespace clang::tidy::misc
diff --git a/clang-tools-extra/clang-tidy/ClangQueryCheck.h b/clang-tools-extra/clang-tidy/ClangQueryCheck.h
new file mode 100644
index 0000000000000..3c3c702972068
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/ClangQueryCheck.h
@@ -0,0 +1,43 @@
+//===--- ClangQueryCheck.h - clang-tidy --------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CLANGQUERYCHECK_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CLANGQUERYCHECK_H
+
+#include "ClangTidyCheck.h"
+#include "clang/ASTMatchers/Dynamic/VariantValue.h"
+#include <vector>
+
+namespace clang::query {
+class QuerySession;
+} // namespace clang::query
+
+namespace clang::tidy::misc {
+
+/// A check that matches a given matchers printing their binds as warnings
+class ClangQueryCheck : public ClangTidyCheck {
+  using MatcherVec = std::vector<ast_matchers::dynamic::DynTypedMatcher>;
+
+public:
+  ClangQueryCheck(StringRef Name, ClangTidyContext *Context,
+                  MatcherVec Matchers)
+      : ClangTidyCheck(Name, Context), Matchers(std::move(Matchers)) {}
+
+  void registerMatchers(ast_matchers::MatchFinder *Finder) override;
+  void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
+  bool isLanguageVersionSupported(const LangOptions &LangOpts) const override {
+    return LangOpts.CPlusPlus;
+  }
+
+private:
+  MatcherVec Matchers;
+};
+
+} // namespace clang::tidy::misc
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CLANGQUERYCHECK_H
diff --git a/clang-tools-extra/clang-tidy/ClangTidy.cpp b/clang-tools-extra/clang-tidy/ClangTidy.cpp
index d99847a82d168..be969c49d3e21 100644
--- a/clang-tools-extra/clang-tidy/ClangTidy.cpp
+++ b/clang-tools-extra/clang-tidy/ClangTidy.cpp
@@ -15,6 +15,7 @@
 //===----------------------------------------------------------------------===//
 
 #include "ClangTidy.h"
+#include "ClangQueryCheck.h"
 #include "ClangTidyCheck.h"
 #include "ClangTidyDiagnosticConsumer.h"
 #include "ClangTidyModuleRegistry.h"
@@ -350,6 +351,13 @@ ClangTidyASTConsumerFactory::ClangTidyASTConsumerFactory(
     std::unique_ptr<ClangTidyModule> Module = E.instantiate();
     Module->addCheckFactories(*CheckFactories);
   }
+
+  for (const auto &[k, v] : Context.getOptions().ClangQueryChecks) {
+    CheckFactories->registerCheckFactory(k, [v](StringRef Name,
+                                                ClangTidyContext *Context) {
+      return std::make_unique<misc::ClangQueryCheck>(Name, Context, v.Matchers);
+    });
+  }
 }
 
 #if CLANG_TIDY_ENABLE_STATIC_ANALYZER
diff --git a/clang-tools-extra/clang-tidy/ClangTidyOptions.cpp b/clang-tools-extra/clang-tidy/ClangTidyOptions.cpp
index 8bac6f161fa05..0d6edb88a1d60 100644
--- a/clang-tools-extra/clang-tidy/ClangTidyOptions.cpp
+++ b/clang-tools-extra/clang-tidy/ClangTidyOptions.cpp
@@ -7,6 +7,8 @@
 //===----------------------------------------------------------------------===//
 
 #include "ClangTidyOptions.h"
+#include "../clang-query/Query.h"
+#include "../clang-query/QueryParser.h"
 #include "ClangTidyModuleRegistry.h"
 #include "clang/Basic/LLVM.h"
 #include "llvm/ADT/SmallString.h"
@@ -126,6 +128,83 @@ void yamlize(IO &IO, ClangTidyOptions::OptionMap &Val, bool,
   }
 }
 
+std::vector<clang::ast_matchers::dynamic::DynTypedMatcher>
+processQuerySource(IO &IO, StringRef SourceRef,
+                   clang::query::QuerySession &QS) {
+  namespace query = clang::query;
+  std::vector<clang::ast_matchers::dynamic::DynTypedMatcher> Matchers;
+
+  while (!SourceRef.empty()) {
+    query::QueryRef Q = query::QueryParser::parse(SourceRef, QS);
+    switch (Q->Kind) {
+    case query::QK_Match: {
+      const auto &MatchQuerry = llvm::cast<query::MatchQuery>(*Q);
+      Matchers.push_back(MatchQuerry.Matcher);
+      break;
+    }
+    case query::QK_Let: {
+      const auto &LetQuerry = llvm::cast<query::LetQuery>(*Q);
+      LetQuerry.run(llvm::errs(), QS);
+      break;
+    }
+    case query::QK_Invalid: {
+      const auto &InvalidQuerry = llvm::cast<query::InvalidQuery>(*Q);
+      for (const auto &Line : llvm::split(InvalidQuerry.ErrStr, "\n")) {
+        IO.setError(Line);
+      }
+      break;
+    }
+    // FIXME FileQuerry should also be supported, but what to do with relative
+    // paths?
+    case query::QK_File:
+    case query::QK_DisableOutputKind:
+    case query::QK_EnableOutputKind:
+    case query::QK_SetOutputKind:
+    case query::QK_SetTraversalKind:
+    case query::QK_Help:
+    case query::QK_NoOp:
+    case query::QK_Quit:
+    case query::QK_SetBool: {
+      IO.setError("unsupported querry kind");
+    }
+    }
+    SourceRef = Q->RemainingContent;
+  }
+
+  return Matchers;
+}
+
+template <>
+void yamlize(IO &IO, ClangTidyOptions::QueryCheckMap &Val, bool,
+             EmptyContext &Ctx) {
+  IO.beginMapping();
+  if (IO.outputting()) {
+    for (auto &[k, v] : Val) {
+      IO.mapRequired(k.data(), v);
+    }
+  } else {
+    for (StringRef Key : IO.keys()) {
+      IO.mapRequired(Key.data(), Val[Key]);
+    }
+  }
+  IO.endMapping();
+}
+
+template <>
+void yamlize(IO &IO, ClangTidyOptions::QueryCheckValue &Val, bool,
+             EmptyContext &Ctx) {
+  if (IO.outputting()) {
+    StringRef SourceRef = Val.Source;
+    IO.blockScalarString(SourceRef);
+  } else {
+    StringRef SourceRef;
+    IO.blockScalarString(SourceRef);
+    Val.Source = SourceRef;
+    clang::query::QuerySession QS({});
+    Val.Matchers = processQuerySource(IO, SourceRef, QS);
+  }
+}
+
 struct ChecksVariant {
   std::optional<std::string> AsString;
   std::optional<std::vector<std::string>> AsVector;
@@ -181,6 +260,7 @@ template <> struct MappingTraits<ClangTidyOptions> {
     IO.mapOptional("InheritParentConfig", Options.InheritParentConfig);
     IO.mapOptional("UseColor", Options.UseColor);
     IO.mapOptional("SystemHeaders", Options.SystemHeaders);
+    IO.mapOptional("ClangQueryChecks", Options.ClangQueryChecks);
   }
 };
 
@@ -249,6 +329,10 @@ ClangTidyOptions &ClangTidyOptions::mergeWith(const ClangTidyOptions &Other,
         ClangTidyValue(KeyValue.getValue().Value,
                        KeyValue.getValue().Priority + Order));
   }
+
+  for (const auto &KeyValue : Other.ClangQueryChecks) {
+    ClangQueryChecks.insert_or_assign(KeyValue.getKey(), KeyValue.getValue());
+  }
   return *this;
 }
 
diff --git a/clang-tools-extra/clang-tidy/ClangTidyOptions.h b/clang-tools-extra/clang-tidy/ClangTidyOptions.h
index dd78c570d25d9..3a015ed5163cd 100644
--- a/clang-tools-extra/clang-tidy/ClangTidyOptions.h
+++ b/clang-tools-extra/clang-tidy/ClangTidyOptions.h
@@ -9,6 +9,7 @@
 #ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CLANGTIDYOPTIONS_H
 #define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CLANGTIDYOPTIONS_H
 
+#include "clang/ASTMatchers/Dynamic/VariantValue.h"
 #include "llvm/ADT/IntrusiveRefCntPtr.h"
 #include "llvm/ADT/SmallString.h"
 #include "llvm/ADT/StringMap.h"
@@ -126,8 +127,15 @@ struct ClangTidyOptions {
   using StringPair = std::pair<std::string, std::string>;
   using OptionMap = llvm::StringMap<ClangTidyValue>;
 
+  struct QueryCheckValue {
+    std::string Source;
+    std::vector<ast_matchers::dynamic::DynTypedMatcher> Matchers;
+  };
+  using QueryCheckMap = llvm::StringMap<QueryCheckValue>;
+
   /// Key-value mapping used to store check-specific options.
   OptionMap CheckOptions;
+  QueryCheckMap ClangQueryChecks;
 
   using ArgList = std::vector<std::string>;
 
diff --git a/clang-tools-extra/clang-tidy/tool/ClangTidyMain.cpp b/clang-tools-extra/clang-tidy/tool/ClangTidyMain.cpp
index fa8887e4639b4..c60e6fcb8c5fa 100644
--- a/clang-tools-extra/clang-tidy/tool/ClangTidyMain.cpp
+++ b/clang-tools-extra/clang-tidy/tool/ClangTidyMain.cpp
@@ -60,6 +60,18 @@ Configuration files:
   Checks                       - Same as '--checks'. Additionally, the list of
                                  globs can be specified as a list instead of a
                                  string.
+  ClangQueryChecks             - List of key-value pairs. Key specifies a name
+                                  of the new check and value specifies a list
+                                  of matchers in the form of clang-query
+                                  syntax. Example:
+                                    ClangQueryChecks:
+                                      custom-check: |
+                                        let matcher varDecl(
+                                          hasTypeLoc(
+                                            typeLoc().bind("Custom message")
+                                          )
+                                        )
+                                        match matcher
   ExcludeHeaderFilterRegex     - Same as '--exclude-header-filter'.
   ExtraArgs                    - Same as '--extra-arg'.
   ExtraArgsBefore              - Same as '--extra-arg-before'.
@@ -483,7 +495,8 @@ static StringRef closest(StringRef Value, const StringSet<> &Allowed) {
   return Closest;
 }
 
-static constexpr StringLiteral VerifyConfigWarningEnd = " [-verify-config]\n";
+static constexpr llvm::StringLiteral VerifyConfigWarningEnd =
+    " [-verify-config]\n";
 
 static bool verifyChecks(const StringSet<> &AllChecks, StringRef CheckGlob,
                          StringRef Source) {
diff --git a/clang-tools-extra/docs/clang-tidy/index.rst b/clang-tools-extra/docs/clang-tidy/index.rst
index b7a366e874130..7ad35aceb094f 100644
--- a/clang-tools-extra/docs/clang-tidy/index.rst
+++ b/clang-tools-extra/docs/clang-tidy/index.rst
@@ -292,6 +292,18 @@ An overview of all the command-line options:
     Checks                       - Same as '--checks'. Additionally, the list of
                                    globs can be specified as a list instead of a
                                    string.
+    ClangQueryChecks             - List of key-value pairs. Key specifies a name
+                                   of the new check and value specifies a list
+                                   of matchers in the form of clang-query
+                                   syntax. Example:
+                                     ClangQueryChecks:
+                                       custom-check: |
+                                         let matcher varDecl(
+                                           hasTypeLoc(
+                                             typeLoc().bind("Custom message")
+                                           )
+                                         )
+                                         match matcher
     ExcludeHeaderFilterRegex     - Same as '--exclude-header-filter'.
     ExtraArgs                    - Same as '--extra-arg'.
     ExtraArgsBefore              - Same as '--extra-arg-before'.
diff --git a/clang-tools-extra/test/clang-tidy/infrastructure/query-checks.cpp b/clang-tools-extra/test/clang-tidy/infrastructure/query-checks.cpp
new file mode 100644
index 0000000000000..e84516a6977b7
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/infrastructure/query-checks.cpp
@@ -0,0 +1,53 @@
+// DEFINE: %{custom-call-yaml} = custom-call: 'm callExpr().bind(\"Custom message\")'
+//
+// DEFINE: %{custom-let-call-yaml} = custom-let-call: \"         \
+// DEFINE:     let expr varDecl(                                 \
+// DEFINE:       hasType(asString(\\\"long long\\\")),           \
+// DEFINE:       hasTypeLoc(typeLoc().bind(\\\"Let message\\\")) \
+// DEFINE:     ) \n                                              \
+// DEFINE:     match expr\"
+//
+// DEFINE: %{full-config} = "{ClangQueryChecks: {%{custom-call-yaml},%{custom-let-call-yaml}}}"
+
+//Check single match expression
+// RUN: clang-tidy %s -checks='-*, custom-*'                  \
+// RUN:   -config="{ClangQueryChecks: {%{custom-call-yaml}}}" \
+// RUN:   -- | FileCheck %s -check-prefix=CHECK-CUSTOM-CALL
+
+void a() {
+}
+
+// CHECK-CUSTOM-CALL: warning: Custom message [custom-call]
+// CHECK-CUSTOM-CALL-NEXT: a();{{$}}
+void b() {
+    a();
+}
+
+//Check let with match expression
+// RUN: clang-tidy %s -checks='-*, custom-*'                      \
+// RUN:   -config="{ClangQueryChecks: {%{custom-let-call-yaml}}}" \
+// RUN:   -- | FileCheck %s -check-prefix=CHECK-CUSTOM-LET
+void c() {
+    // CHECK-CUSTOM-LET: warning: Let message [custom-let-call]
+    // CHECK-CUSTOM-LET-NEXT: long long test_long_long = 0;{{$}}
+    long long test_long_long_nolint = 0; //NOLINT(custom-let-call)
+    long long test_long_long = 0;
+}
+
+//Check multiple checks in one config
+// RUN: clang-tidy %s -checks='-*, custom-*' \
+// RUN:   -config=%{full-config}             \
+// RUN:   -- | FileCheck %s -check-prefixes=CHECK-CUSTOM-CALL,CHECK-CUSTOM-LET
+
+//Check multiple checks in one config but only one enabled
+// RUN: clang-tidy %s -checks='-*, custom-call' \
+// RUN:   -config=%{full-config}                \
+// RUN:   -- | FileCheck %s -check-prefixes=CHECK-CUSTOM-CALL --implicit-check-not warning:
+
+//Check config dump
+// RUN: clang-tidy -dump-config -checks='-*, custom-*' \
+// RUN:   -config=%{full-config}                       \
+// RUN:   -- | FileCheck %s -check-prefix=CHECK-CONFIG
+// CHECK-CONFIG: ClangQueryChecks:
+// CHECK-CONFIG-DAG: custom-let-call:
+// CHECK-CONFIG-DAG: custom-call:  |{{$[[:space:]]}} m callExpr().bind("Custom message")

>From 421b26b2a83810573cb9642f8ed2417b42a85efa Mon Sep 17 00:00:00 2001
From: Congcong Cai <congcongcai0907 at 163.com>
Date: Mon, 17 Mar 2025 08:11:43 +0000
Subject: [PATCH 02/25] Revert "origin pr"

This reverts commit a686695e6691fcb824b2055c3dfa8f0fd3727355.
---
 clang-tools-extra/clang-tidy/CMakeLists.txt   |  2 -
 .../clang-tidy/ClangQueryCheck.cpp            | 30 -------
 .../clang-tidy/ClangQueryCheck.h              | 43 ----------
 clang-tools-extra/clang-tidy/ClangTidy.cpp    |  8 --
 .../clang-tidy/ClangTidyOptions.cpp           | 84 -------------------
 .../clang-tidy/ClangTidyOptions.h             |  8 --
 .../clang-tidy/tool/ClangTidyMain.cpp         | 15 +---
 clang-tools-extra/docs/clang-tidy/index.rst   | 12 ---
 .../infrastructure/query-checks.cpp           | 53 ------------
 9 files changed, 1 insertion(+), 254 deletions(-)
 delete mode 100644 clang-tools-extra/clang-tidy/ClangQueryCheck.cpp
 delete mode 100644 clang-tools-extra/clang-tidy/ClangQueryCheck.h
 delete mode 100644 clang-tools-extra/test/clang-tidy/infrastructure/query-checks.cpp

diff --git a/clang-tools-extra/clang-tidy/CMakeLists.txt b/clang-tools-extra/clang-tidy/CMakeLists.txt
index 76585b012d174..93117cf1d6373 100644
--- a/clang-tools-extra/clang-tidy/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/CMakeLists.txt
@@ -11,7 +11,6 @@ include_directories(BEFORE ${CMAKE_CURRENT_BINARY_DIR})
 add_clang_library(clangTidy STATIC
   ClangTidy.cpp
   ClangTidyCheck.cpp
-  ClangQueryCheck.cpp
   ClangTidyModule.cpp
   ClangTidyDiagnosticConsumer.cpp
   ClangTidyOptions.cpp
@@ -39,7 +38,6 @@ clang_target_link_libraries(clangTidy
   clangSerialization
   clangTooling
   clangToolingCore
-  clangQuery
   )
 
 if(CLANG_TIDY_ENABLE_STATIC_ANALYZER)
diff --git a/clang-tools-extra/clang-tidy/ClangQueryCheck.cpp b/clang-tools-extra/clang-tidy/ClangQueryCheck.cpp
deleted file mode 100644
index a9f46116f7089..0000000000000
--- a/clang-tools-extra/clang-tidy/ClangQueryCheck.cpp
+++ /dev/null
@@ -1,30 +0,0 @@
-//===--- ClangQueryCheck.cpp - clang-tidy ----------------------------===//
-//
-// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
-// See https://llvm.org/LICENSE.txt for license information.
-// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-//
-//===----------------------------------------------------------------------===//
-
-#include "ClangQueryCheck.h"
-#include "clang/ASTMatchers/ASTMatchFinder.h"
-
-using namespace clang::ast_matchers;
-
-namespace clang::tidy::misc {
-
-void ClangQueryCheck::registerMatchers(MatchFinder *Finder) {
-  for (const auto &Matcher : Matchers) {
-    bool Ok = Finder->addDynamicMatcher(Matcher, this);
-    assert(Ok && "Expected to get top level matcher from query parser");
-  }
-}
-
-void ClangQueryCheck::check(const MatchFinder::MatchResult &Result) {
-  auto Map = Result.Nodes.getMap();
-  for (const auto &[k, v] : Map) {
-    diag(v.getSourceRange().getBegin(), k) << v.getSourceRange();
-  }
-}
-
-} // namespace clang::tidy::misc
diff --git a/clang-tools-extra/clang-tidy/ClangQueryCheck.h b/clang-tools-extra/clang-tidy/ClangQueryCheck.h
deleted file mode 100644
index 3c3c702972068..0000000000000
--- a/clang-tools-extra/clang-tidy/ClangQueryCheck.h
+++ /dev/null
@@ -1,43 +0,0 @@
-//===--- ClangQueryCheck.h - clang-tidy --------------------*- C++ -*-===//
-//
-// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
-// See https://llvm.org/LICENSE.txt for license information.
-// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-//
-//===----------------------------------------------------------------------===//
-
-#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CLANGQUERYCHECK_H
-#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CLANGQUERYCHECK_H
-
-#include "ClangTidyCheck.h"
-#include "clang/ASTMatchers/Dynamic/VariantValue.h"
-#include <vector>
-
-namespace clang::query {
-class QuerySession;
-} // namespace clang::query
-
-namespace clang::tidy::misc {
-
-/// A check that matches a given matchers printing their binds as warnings
-class ClangQueryCheck : public ClangTidyCheck {
-  using MatcherVec = std::vector<ast_matchers::dynamic::DynTypedMatcher>;
-
-public:
-  ClangQueryCheck(StringRef Name, ClangTidyContext *Context,
-                  MatcherVec Matchers)
-      : ClangTidyCheck(Name, Context), Matchers(std::move(Matchers)) {}
-
-  void registerMatchers(ast_matchers::MatchFinder *Finder) override;
-  void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
-  bool isLanguageVersionSupported(const LangOptions &LangOpts) const override {
-    return LangOpts.CPlusPlus;
-  }
-
-private:
-  MatcherVec Matchers;
-};
-
-} // namespace clang::tidy::misc
-
-#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CLANGQUERYCHECK_H
diff --git a/clang-tools-extra/clang-tidy/ClangTidy.cpp b/clang-tools-extra/clang-tidy/ClangTidy.cpp
index be969c49d3e21..d99847a82d168 100644
--- a/clang-tools-extra/clang-tidy/ClangTidy.cpp
+++ b/clang-tools-extra/clang-tidy/ClangTidy.cpp
@@ -15,7 +15,6 @@
 //===----------------------------------------------------------------------===//
 
 #include "ClangTidy.h"
-#include "ClangQueryCheck.h"
 #include "ClangTidyCheck.h"
 #include "ClangTidyDiagnosticConsumer.h"
 #include "ClangTidyModuleRegistry.h"
@@ -351,13 +350,6 @@ ClangTidyASTConsumerFactory::ClangTidyASTConsumerFactory(
     std::unique_ptr<ClangTidyModule> Module = E.instantiate();
     Module->addCheckFactories(*CheckFactories);
   }
-
-  for (const auto &[k, v] : Context.getOptions().ClangQueryChecks) {
-    CheckFactories->registerCheckFactory(k, [v](StringRef Name,
-                                                ClangTidyContext *Context) {
-      return std::make_unique<misc::ClangQueryCheck>(Name, Context, v.Matchers);
-    });
-  }
 }
 
 #if CLANG_TIDY_ENABLE_STATIC_ANALYZER
diff --git a/clang-tools-extra/clang-tidy/ClangTidyOptions.cpp b/clang-tools-extra/clang-tidy/ClangTidyOptions.cpp
index 0d6edb88a1d60..8bac6f161fa05 100644
--- a/clang-tools-extra/clang-tidy/ClangTidyOptions.cpp
+++ b/clang-tools-extra/clang-tidy/ClangTidyOptions.cpp
@@ -7,8 +7,6 @@
 //===----------------------------------------------------------------------===//
 
 #include "ClangTidyOptions.h"
-#include "../clang-query/Query.h"
-#include "../clang-query/QueryParser.h"
 #include "ClangTidyModuleRegistry.h"
 #include "clang/Basic/LLVM.h"
 #include "llvm/ADT/SmallString.h"
@@ -128,83 +126,6 @@ void yamlize(IO &IO, ClangTidyOptions::OptionMap &Val, bool,
   }
 }
 
-std::vector<clang::ast_matchers::dynamic::DynTypedMatcher>
-processQuerySource(IO &IO, StringRef SourceRef,
-                   clang::query::QuerySession &QS) {
-  namespace query = clang::query;
-  std::vector<clang::ast_matchers::dynamic::DynTypedMatcher> Matchers;
-
-  while (!SourceRef.empty()) {
-    query::QueryRef Q = query::QueryParser::parse(SourceRef, QS);
-    switch (Q->Kind) {
-    case query::QK_Match: {
-      const auto &MatchQuerry = llvm::cast<query::MatchQuery>(*Q);
-      Matchers.push_back(MatchQuerry.Matcher);
-      break;
-    }
-    case query::QK_Let: {
-      const auto &LetQuerry = llvm::cast<query::LetQuery>(*Q);
-      LetQuerry.run(llvm::errs(), QS);
-      break;
-    }
-    case query::QK_Invalid: {
-      const auto &InvalidQuerry = llvm::cast<query::InvalidQuery>(*Q);
-      for (const auto &Line : llvm::split(InvalidQuerry.ErrStr, "\n")) {
-        IO.setError(Line);
-      }
-      break;
-    }
-    // FIXME FileQuerry should also be supported, but what to do with relative
-    // paths?
-    case query::QK_File:
-    case query::QK_DisableOutputKind:
-    case query::QK_EnableOutputKind:
-    case query::QK_SetOutputKind:
-    case query::QK_SetTraversalKind:
-    case query::QK_Help:
-    case query::QK_NoOp:
-    case query::QK_Quit:
-    case query::QK_SetBool: {
-      IO.setError("unsupported querry kind");
-    }
-    }
-    SourceRef = Q->RemainingContent;
-  }
-
-  return Matchers;
-}
-
-template <>
-void yamlize(IO &IO, ClangTidyOptions::QueryCheckMap &Val, bool,
-             EmptyContext &Ctx) {
-  IO.beginMapping();
-  if (IO.outputting()) {
-    for (auto &[k, v] : Val) {
-      IO.mapRequired(k.data(), v);
-    }
-  } else {
-    for (StringRef Key : IO.keys()) {
-      IO.mapRequired(Key.data(), Val[Key]);
-    }
-  }
-  IO.endMapping();
-}
-
-template <>
-void yamlize(IO &IO, ClangTidyOptions::QueryCheckValue &Val, bool,
-             EmptyContext &Ctx) {
-  if (IO.outputting()) {
-    StringRef SourceRef = Val.Source;
-    IO.blockScalarString(SourceRef);
-  } else {
-    StringRef SourceRef;
-    IO.blockScalarString(SourceRef);
-    Val.Source = SourceRef;
-    clang::query::QuerySession QS({});
-    Val.Matchers = processQuerySource(IO, SourceRef, QS);
-  }
-}
-
 struct ChecksVariant {
   std::optional<std::string> AsString;
   std::optional<std::vector<std::string>> AsVector;
@@ -260,7 +181,6 @@ template <> struct MappingTraits<ClangTidyOptions> {
     IO.mapOptional("InheritParentConfig", Options.InheritParentConfig);
     IO.mapOptional("UseColor", Options.UseColor);
     IO.mapOptional("SystemHeaders", Options.SystemHeaders);
-    IO.mapOptional("ClangQueryChecks", Options.ClangQueryChecks);
   }
 };
 
@@ -329,10 +249,6 @@ ClangTidyOptions &ClangTidyOptions::mergeWith(const ClangTidyOptions &Other,
         ClangTidyValue(KeyValue.getValue().Value,
                        KeyValue.getValue().Priority + Order));
   }
-
-  for (const auto &KeyValue : Other.ClangQueryChecks) {
-    ClangQueryChecks.insert_or_assign(KeyValue.getKey(), KeyValue.getValue());
-  }
   return *this;
 }
 
diff --git a/clang-tools-extra/clang-tidy/ClangTidyOptions.h b/clang-tools-extra/clang-tidy/ClangTidyOptions.h
index 3a015ed5163cd..dd78c570d25d9 100644
--- a/clang-tools-extra/clang-tidy/ClangTidyOptions.h
+++ b/clang-tools-extra/clang-tidy/ClangTidyOptions.h
@@ -9,7 +9,6 @@
 #ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CLANGTIDYOPTIONS_H
 #define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CLANGTIDYOPTIONS_H
 
-#include "clang/ASTMatchers/Dynamic/VariantValue.h"
 #include "llvm/ADT/IntrusiveRefCntPtr.h"
 #include "llvm/ADT/SmallString.h"
 #include "llvm/ADT/StringMap.h"
@@ -127,15 +126,8 @@ struct ClangTidyOptions {
   using StringPair = std::pair<std::string, std::string>;
   using OptionMap = llvm::StringMap<ClangTidyValue>;
 
-  struct QueryCheckValue {
-    std::string Source;
-    std::vector<ast_matchers::dynamic::DynTypedMatcher> Matchers;
-  };
-  using QueryCheckMap = llvm::StringMap<QueryCheckValue>;
-
   /// Key-value mapping used to store check-specific options.
   OptionMap CheckOptions;
-  QueryCheckMap ClangQueryChecks;
 
   using ArgList = std::vector<std::string>;
 
diff --git a/clang-tools-extra/clang-tidy/tool/ClangTidyMain.cpp b/clang-tools-extra/clang-tidy/tool/ClangTidyMain.cpp
index c60e6fcb8c5fa..fa8887e4639b4 100644
--- a/clang-tools-extra/clang-tidy/tool/ClangTidyMain.cpp
+++ b/clang-tools-extra/clang-tidy/tool/ClangTidyMain.cpp
@@ -60,18 +60,6 @@ Configuration files:
   Checks                       - Same as '--checks'. Additionally, the list of
                                  globs can be specified as a list instead of a
                                  string.
-  ClangQueryChecks             - List of key-value pairs. Key specifies a name
-                                  of the new check and value specifies a list
-                                  of matchers in the form of clang-query
-                                  syntax. Example:
-                                    ClangQueryChecks:
-                                      custom-check: |
-                                        let matcher varDecl(
-                                          hasTypeLoc(
-                                            typeLoc().bind("Custom message")
-                                          )
-                                        )
-                                        match matcher
   ExcludeHeaderFilterRegex     - Same as '--exclude-header-filter'.
   ExtraArgs                    - Same as '--extra-arg'.
   ExtraArgsBefore              - Same as '--extra-arg-before'.
@@ -495,8 +483,7 @@ static StringRef closest(StringRef Value, const StringSet<> &Allowed) {
   return Closest;
 }
 
-static constexpr llvm::StringLiteral VerifyConfigWarningEnd =
-    " [-verify-config]\n";
+static constexpr StringLiteral VerifyConfigWarningEnd = " [-verify-config]\n";
 
 static bool verifyChecks(const StringSet<> &AllChecks, StringRef CheckGlob,
                          StringRef Source) {
diff --git a/clang-tools-extra/docs/clang-tidy/index.rst b/clang-tools-extra/docs/clang-tidy/index.rst
index 7ad35aceb094f..b7a366e874130 100644
--- a/clang-tools-extra/docs/clang-tidy/index.rst
+++ b/clang-tools-extra/docs/clang-tidy/index.rst
@@ -292,18 +292,6 @@ An overview of all the command-line options:
     Checks                       - Same as '--checks'. Additionally, the list of
                                    globs can be specified as a list instead of a
                                    string.
-    ClangQueryChecks             - List of key-value pairs. Key specifies a name
-                                   of the new check and value specifies a list
-                                   of matchers in the form of clang-query
-                                   syntax. Example:
-                                     ClangQueryChecks:
-                                       custom-check: |
-                                         let matcher varDecl(
-                                           hasTypeLoc(
-                                             typeLoc().bind("Custom message")
-                                           )
-                                         )
-                                         match matcher
     ExcludeHeaderFilterRegex     - Same as '--exclude-header-filter'.
     ExtraArgs                    - Same as '--extra-arg'.
     ExtraArgsBefore              - Same as '--extra-arg-before'.
diff --git a/clang-tools-extra/test/clang-tidy/infrastructure/query-checks.cpp b/clang-tools-extra/test/clang-tidy/infrastructure/query-checks.cpp
deleted file mode 100644
index e84516a6977b7..0000000000000
--- a/clang-tools-extra/test/clang-tidy/infrastructure/query-checks.cpp
+++ /dev/null
@@ -1,53 +0,0 @@
-// DEFINE: %{custom-call-yaml} = custom-call: 'm callExpr().bind(\"Custom message\")'
-//
-// DEFINE: %{custom-let-call-yaml} = custom-let-call: \"         \
-// DEFINE:     let expr varDecl(                                 \
-// DEFINE:       hasType(asString(\\\"long long\\\")),           \
-// DEFINE:       hasTypeLoc(typeLoc().bind(\\\"Let message\\\")) \
-// DEFINE:     ) \n                                              \
-// DEFINE:     match expr\"
-//
-// DEFINE: %{full-config} = "{ClangQueryChecks: {%{custom-call-yaml},%{custom-let-call-yaml}}}"
-
-//Check single match expression
-// RUN: clang-tidy %s -checks='-*, custom-*'                  \
-// RUN:   -config="{ClangQueryChecks: {%{custom-call-yaml}}}" \
-// RUN:   -- | FileCheck %s -check-prefix=CHECK-CUSTOM-CALL
-
-void a() {
-}
-
-// CHECK-CUSTOM-CALL: warning: Custom message [custom-call]
-// CHECK-CUSTOM-CALL-NEXT: a();{{$}}
-void b() {
-    a();
-}
-
-//Check let with match expression
-// RUN: clang-tidy %s -checks='-*, custom-*'                      \
-// RUN:   -config="{ClangQueryChecks: {%{custom-let-call-yaml}}}" \
-// RUN:   -- | FileCheck %s -check-prefix=CHECK-CUSTOM-LET
-void c() {
-    // CHECK-CUSTOM-LET: warning: Let message [custom-let-call]
-    // CHECK-CUSTOM-LET-NEXT: long long test_long_long = 0;{{$}}
-    long long test_long_long_nolint = 0; //NOLINT(custom-let-call)
-    long long test_long_long = 0;
-}
-
-//Check multiple checks in one config
-// RUN: clang-tidy %s -checks='-*, custom-*' \
-// RUN:   -config=%{full-config}             \
-// RUN:   -- | FileCheck %s -check-prefixes=CHECK-CUSTOM-CALL,CHECK-CUSTOM-LET
-
-//Check multiple checks in one config but only one enabled
-// RUN: clang-tidy %s -checks='-*, custom-call' \
-// RUN:   -config=%{full-config}                \
-// RUN:   -- | FileCheck %s -check-prefixes=CHECK-CUSTOM-CALL --implicit-check-not warning:
-
-//Check config dump
-// RUN: clang-tidy -dump-config -checks='-*, custom-*' \
-// RUN:   -config=%{full-config}                       \
-// RUN:   -- | FileCheck %s -check-prefix=CHECK-CONFIG
-// CHECK-CONFIG: ClangQueryChecks:
-// CHECK-CONFIG-DAG: custom-let-call:
-// CHECK-CONFIG-DAG: custom-call:  |{{$[[:space:]]}} m callExpr().bind("Custom message")

>From 6fa8f7ee1188eae6bac1605b3c85d51c90601d9d Mon Sep 17 00:00:00 2001
From: Congcong Cai <congcongcai0907 at 163.com>
Date: Mon, 17 Mar 2025 10:01:49 +0000
Subject: [PATCH 03/25] impl option

---
 .../clang-tidy/ClangTidyOptions.cpp           | 34 +++++++++++++++++--
 .../clang-tidy/ClangTidyOptions.h             |  8 +++++
 2 files changed, 40 insertions(+), 2 deletions(-)

diff --git a/clang-tools-extra/clang-tidy/ClangTidyOptions.cpp b/clang-tools-extra/clang-tidy/ClangTidyOptions.cpp
index 8bac6f161fa05..f8b387e222168 100644
--- a/clang-tools-extra/clang-tidy/ClangTidyOptions.cpp
+++ b/clang-tools-extra/clang-tidy/ClangTidyOptions.cpp
@@ -10,10 +10,9 @@
 #include "ClangTidyModuleRegistry.h"
 #include "clang/Basic/LLVM.h"
 #include "llvm/ADT/SmallString.h"
+#include "llvm/ADT/StringExtras.h"
 #include "llvm/Support/Debug.h"
-#include "llvm/Support/Errc.h"
 #include "llvm/Support/ErrorOr.h"
-#include "llvm/Support/FileSystem.h"
 #include "llvm/Support/MemoryBufferRef.h"
 #include "llvm/Support/Path.h"
 #include "llvm/Support/YAMLTraits.h"
@@ -126,6 +125,34 @@ void yamlize(IO &IO, ClangTidyOptions::OptionMap &Val, bool,
   }
 }
 
+namespace {
+struct MultiLineString {
+  std::string &S;
+};
+} // namespace
+
+template <> struct BlockScalarTraits<MultiLineString> {
+  static void output(const MultiLineString &S, void *Ctxt, raw_ostream &OS) {
+    OS << S.S;
+  }
+
+  static StringRef input(StringRef Str, void *Ctxt, MultiLineString &S) {
+    S.S = Str;
+    return "";
+  }
+};
+
+template <> struct SequenceElementTraits<ClangTidyOptions::CustomCheckValue> {
+  static const bool flow = false;
+};
+template <> struct MappingTraits<ClangTidyOptions::CustomCheckValue> {
+  static void mapping(IO &IO, ClangTidyOptions::CustomCheckValue &V) {
+    IO.mapRequired("Name", V.Name);
+    MultiLineString MLS{V.Query};
+    IO.mapRequired("Query", MLS);
+  }
+};
+
 struct ChecksVariant {
   std::optional<std::string> AsString;
   std::optional<std::vector<std::string>> AsVector;
@@ -181,6 +208,7 @@ template <> struct MappingTraits<ClangTidyOptions> {
     IO.mapOptional("InheritParentConfig", Options.InheritParentConfig);
     IO.mapOptional("UseColor", Options.UseColor);
     IO.mapOptional("SystemHeaders", Options.SystemHeaders);
+    IO.mapOptional("CustomeChecks", Options.CustomChecks);
   }
 };
 
@@ -249,6 +277,8 @@ ClangTidyOptions &ClangTidyOptions::mergeWith(const ClangTidyOptions &Other,
         ClangTidyValue(KeyValue.getValue().Value,
                        KeyValue.getValue().Priority + Order));
   }
+  mergeVectors(CustomChecks, Other.CustomChecks);
+
   return *this;
 }
 
diff --git a/clang-tools-extra/clang-tidy/ClangTidyOptions.h b/clang-tools-extra/clang-tidy/ClangTidyOptions.h
index dd78c570d25d9..9547f94adbde6 100644
--- a/clang-tools-extra/clang-tidy/ClangTidyOptions.h
+++ b/clang-tools-extra/clang-tidy/ClangTidyOptions.h
@@ -129,6 +129,14 @@ struct ClangTidyOptions {
   /// Key-value mapping used to store check-specific options.
   OptionMap CheckOptions;
 
+  struct CustomCheckValue {
+    std::string Name;
+    std::string Query;
+    // FIXME: extend more features here (e.g. isLanguageVersionSupported, Level)
+  };
+  using CustomCheckValueList = llvm::SmallVector<CustomCheckValue>;
+  std::optional<CustomCheckValueList> CustomChecks;
+
   using ArgList = std::vector<std::string>;
 
   /// Add extra compilation arguments to the end of the list.

>From 6ab7149f0877767bc1d8db7fc561d5a87886e821 Mon Sep 17 00:00:00 2001
From: Congcong Cai <congcongcai0907 at 163.com>
Date: Mon, 17 Mar 2025 11:14:30 +0000
Subject: [PATCH 04/25] impl

---
 clang-tools-extra/clang-tidy/CMakeLists.txt   |  2 +
 clang-tools-extra/clang-tidy/ClangTidy.cpp    |  6 ++
 .../clang-tidy/ClangTidyForceLinker.h         |  5 ++
 .../clang-tidy/custom/CMakeLists.txt          | 20 ++++++
 .../clang-tidy/custom/CustomTidyModule.cpp    | 43 ++++++++++++
 .../clang-tidy/custom/QueryCheck.cpp          | 70 +++++++++++++++++++
 .../clang-tidy/custom/QueryCheck.h            | 35 ++++++++++
 7 files changed, 181 insertions(+)
 create mode 100644 clang-tools-extra/clang-tidy/custom/CMakeLists.txt
 create mode 100644 clang-tools-extra/clang-tidy/custom/CustomTidyModule.cpp
 create mode 100644 clang-tools-extra/clang-tidy/custom/QueryCheck.cpp
 create mode 100644 clang-tools-extra/clang-tidy/custom/QueryCheck.h

diff --git a/clang-tools-extra/clang-tidy/CMakeLists.txt b/clang-tools-extra/clang-tidy/CMakeLists.txt
index 93117cf1d6373..90efd2ef1f451 100644
--- a/clang-tools-extra/clang-tidy/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/CMakeLists.txt
@@ -58,6 +58,7 @@ add_subdirectory(bugprone)
 add_subdirectory(cert)
 add_subdirectory(concurrency)
 add_subdirectory(cppcoreguidelines)
+add_subdirectory(custom)
 add_subdirectory(darwin)
 add_subdirectory(fuchsia)
 add_subdirectory(google)
@@ -85,6 +86,7 @@ set(ALL_CLANG_TIDY_CHECKS
   clangTidyCERTModule
   clangTidyConcurrencyModule
   clangTidyCppCoreGuidelinesModule
+  clangTidyCustomModule
   clangTidyDarwinModule
   clangTidyFuchsiaModule
   clangTidyGoogleModule
diff --git a/clang-tools-extra/clang-tidy/ClangTidy.cpp b/clang-tools-extra/clang-tidy/ClangTidy.cpp
index d99847a82d168..25950319d72c6 100644
--- a/clang-tools-extra/clang-tidy/ClangTidy.cpp
+++ b/clang-tools-extra/clang-tidy/ClangTidy.cpp
@@ -57,6 +57,10 @@ LLVM_INSTANTIATE_REGISTRY(clang::tidy::ClangTidyModuleRegistry)
 
 namespace clang::tidy {
 
+namespace custom {
+extern void setOptions(ClangTidyOptions const &O);
+} // namespace custom
+
 namespace {
 #if CLANG_TIDY_ENABLE_STATIC_ANALYZER
 static const char *AnalyzerCheckNamePrefix = "clang-analyzer-";
@@ -346,6 +350,7 @@ ClangTidyASTConsumerFactory::ClangTidyASTConsumerFactory(
     IntrusiveRefCntPtr<llvm::vfs::OverlayFileSystem> OverlayFS)
     : Context(Context), OverlayFS(std::move(OverlayFS)),
       CheckFactories(new ClangTidyCheckFactories) {
+  custom::setOptions(Context.getOptions());
   for (ClangTidyModuleRegistry::entry E : ClangTidyModuleRegistry::entries()) {
     std::unique_ptr<ClangTidyModule> Module = E.instantiate();
     Module->addCheckFactories(*CheckFactories);
@@ -659,6 +664,7 @@ getAllChecksAndOptions(bool AllowEnablingAnalyzerAlphaCheckers) {
       std::make_unique<DefaultOptionsProvider>(ClangTidyGlobalOptions(), Opts),
       AllowEnablingAnalyzerAlphaCheckers);
   ClangTidyCheckFactories Factories;
+  custom::setOptions(Context.getOptions());
   for (const ClangTidyModuleRegistry::entry &Module :
        ClangTidyModuleRegistry::entries()) {
     Module.instantiate()->addCheckFactories(Factories);
diff --git a/clang-tools-extra/clang-tidy/ClangTidyForceLinker.h b/clang-tools-extra/clang-tidy/ClangTidyForceLinker.h
index adde9136ff1dd..50ac6e138df18 100644
--- a/clang-tools-extra/clang-tidy/ClangTidyForceLinker.h
+++ b/clang-tools-extra/clang-tidy/ClangTidyForceLinker.h
@@ -54,6 +54,11 @@ extern volatile int CppCoreGuidelinesModuleAnchorSource;
 static int LLVM_ATTRIBUTE_UNUSED CppCoreGuidelinesModuleAnchorDestination =
     CppCoreGuidelinesModuleAnchorSource;
 
+// This anchor is used to force the linker to link the CustomModule.
+extern volatile int CustomModuleAnchorSource;
+static int LLVM_ATTRIBUTE_UNUSED CustomModuleAnchorDestination =
+    CustomModuleAnchorSource;
+
 // This anchor is used to force the linker to link the DarwinModule.
 extern volatile int DarwinModuleAnchorSource;
 static int LLVM_ATTRIBUTE_UNUSED DarwinModuleAnchorDestination =
diff --git a/clang-tools-extra/clang-tidy/custom/CMakeLists.txt b/clang-tools-extra/clang-tidy/custom/CMakeLists.txt
new file mode 100644
index 0000000000000..40387b73b5253
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/custom/CMakeLists.txt
@@ -0,0 +1,20 @@
+set(LLVM_LINK_COMPONENTS
+  support
+  )
+
+add_clang_library(clangTidyCustomModule STATIC
+  CustomTidyModule.cpp
+  QueryCheck.cpp
+
+  LINK_LIBS
+  clangTidy
+  clangTidyUtils
+
+  DEPENDS
+  ClangDriverOptions
+  )
+
+clang_target_link_libraries(clangTidyCustomModule
+  PRIVATE
+  clangQuery
+  )
diff --git a/clang-tools-extra/clang-tidy/custom/CustomTidyModule.cpp b/clang-tools-extra/clang-tidy/custom/CustomTidyModule.cpp
new file mode 100644
index 0000000000000..23970c5a1e003
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/custom/CustomTidyModule.cpp
@@ -0,0 +1,43 @@
+#include "../ClangTidy.h"
+#include "../ClangTidyModule.h"
+#include "../ClangTidyModuleRegistry.h"
+#include "../ClangTidyOptions.h"
+#include "QueryCheck.h"
+#include <memory>
+
+namespace clang::tidy {
+namespace custom {
+
+// FIXME: could be clearer to add parameter of addCheckFactories to pass
+// Options?
+static ClangTidyOptions const *Options = nullptr;
+extern void setOptions(ClangTidyOptions const &O) { Options = &O; }
+
+class CustomModule : public ClangTidyModule {
+public:
+  void addCheckFactories(ClangTidyCheckFactories &CheckFactories) override {
+    if (Options == nullptr || !Options->CustomChecks.has_value() ||
+        Options->CustomChecks->empty())
+      return;
+    for (const ClangTidyOptions::CustomCheckValue &V :
+         Options->CustomChecks.value()) {
+      CheckFactories.registerCheckFactory(
+          "custom-" + V.Name,
+          [&V](llvm::StringRef Name, ClangTidyContext *Context) {
+            return std::make_unique<custom::QueryCheck>(Name, V, Context);
+          });
+    }
+  }
+};
+
+} // namespace custom
+
+// Register the AlteraTidyModule using this statically initialized variable.
+static ClangTidyModuleRegistry::Add<custom::CustomModule>
+    X("custom-module", "Adds custom query lint checks.");
+
+// This anchor is used to force the linker to link in the generated object file
+// and thus register the AlteraModule.
+volatile int CustomModuleAnchorSource = 0;
+
+} // namespace clang::tidy
diff --git a/clang-tools-extra/clang-tidy/custom/QueryCheck.cpp b/clang-tools-extra/clang-tidy/custom/QueryCheck.cpp
new file mode 100644
index 0000000000000..b72382bb20daa
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/custom/QueryCheck.cpp
@@ -0,0 +1,70 @@
+//===--- QueryCheck.cpp - clang-tidy --------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "QueryCheck.h"
+#include "../../clang-query/Query.h"
+#include "../../clang-query/QueryParser.h"
+#include "clang/ASTMatchers/Dynamic/VariantValue.h"
+#include "llvm/ADT/StringRef.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::custom {
+
+QueryCheck::QueryCheck(llvm::StringRef Name,
+                       const ClangTidyOptions::CustomCheckValue &V,
+                       ClangTidyContext *Context)
+    : ClangTidyCheck(Name, Context), Matchers{} {
+  clang::query::QuerySession QS({});
+  llvm::StringRef QueryStringRef{V.Query};
+  while (!QueryStringRef.empty()) {
+    query::QueryRef Q = query::QueryParser::parse(QueryStringRef, QS);
+    switch (Q->Kind) {
+    case query::QK_Match: {
+      const auto &MatchQuerry = llvm::cast<query::MatchQuery>(*Q);
+      Matchers.push_back(MatchQuerry.Matcher);
+      break;
+    }
+    case query::QK_Let: {
+      const auto &LetQuerry = llvm::cast<query::LetQuery>(*Q);
+      LetQuerry.run(llvm::errs(), QS);
+      break;
+    }
+    case query::QK_Invalid: {
+      const auto &InvalidQuerry = llvm::cast<query::InvalidQuery>(*Q);
+      Context->configurationDiag(InvalidQuerry.ErrStr);
+      break;
+    }
+    // FIXME: TODO
+    case query::QK_File:
+    case query::QK_DisableOutputKind:
+    case query::QK_EnableOutputKind:
+    case query::QK_SetOutputKind:
+    case query::QK_SetTraversalKind:
+    case query::QK_Help:
+    case query::QK_NoOp:
+    case query::QK_Quit:
+    case query::QK_SetBool: {
+      Context->configurationDiag("unsupported querry kind");
+    }
+    }
+    QueryStringRef = Q->RemainingContent;
+  }
+}
+
+void QueryCheck::registerMatchers(MatchFinder *Finder) {
+  for (const ast_matchers::dynamic::DynTypedMatcher &M : Matchers)
+    Finder->addDynamicMatcher(M, this);
+}
+
+void QueryCheck::check(const MatchFinder::MatchResult &Result) {
+  for (auto &[Name, Node] : Result.Nodes.getMap())
+    diag(Node.getSourceRange().getBegin(), Name) << Node.getSourceRange();
+}
+
+} // namespace clang::tidy::custom
diff --git a/clang-tools-extra/clang-tidy/custom/QueryCheck.h b/clang-tools-extra/clang-tidy/custom/QueryCheck.h
new file mode 100644
index 0000000000000..bb5501a1e5235
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/custom/QueryCheck.h
@@ -0,0 +1,35 @@
+//===--- QueryCheck.h - clang-tidy ------------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CUSTOM_QUERYCHECK_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CUSTOM_QUERYCHECK_H
+
+#include "../ClangTidyCheck.h"
+#include "clang/ASTMatchers/Dynamic/VariantValue.h"
+#include "llvm/ADT/SmallVector.h"
+
+namespace clang::tidy::custom {
+
+/// FIXME: Write a short description.
+///
+/// For the user-facing documentation see:
+/// http://clang.llvm.org/extra/clang-tidy/checks/custom/query.html
+class QueryCheck : public ClangTidyCheck {
+public:
+  QueryCheck(llvm::StringRef Name, const ClangTidyOptions::CustomCheckValue &V,
+             ClangTidyContext *Context);
+  void registerMatchers(ast_matchers::MatchFinder *Finder) override;
+  void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
+
+private:
+  llvm::SmallVector<ast_matchers::dynamic::DynTypedMatcher> Matchers{};
+};
+
+} // namespace clang::tidy::custom
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CUSTOM_QUERYCHECK_H

>From db6b3150c82d80245e46cd4a2e9e9bddf043d9ee Mon Sep 17 00:00:00 2001
From: Congcong Cai <congcongcai0907 at 163.com>
Date: Mon, 17 Mar 2025 11:54:50 +0000
Subject: [PATCH 05/25] extend func

---
 .../clang-tidy/ClangTidyOptions.cpp           | 24 +++++++++++++++++--
 .../clang-tidy/ClangTidyOptions.h             |  9 ++++++-
 .../clang-tidy/custom/CustomTidyModule.cpp    |  1 +
 .../clang-tidy/custom/QueryCheck.cpp          | 14 +++++++++--
 .../clang-tidy/custom/QueryCheck.h            |  6 +++++
 5 files changed, 49 insertions(+), 5 deletions(-)

diff --git a/clang-tools-extra/clang-tidy/ClangTidyOptions.cpp b/clang-tools-extra/clang-tidy/ClangTidyOptions.cpp
index f8b387e222168..b21b85ca38f06 100644
--- a/clang-tools-extra/clang-tidy/ClangTidyOptions.cpp
+++ b/clang-tools-extra/clang-tidy/ClangTidyOptions.cpp
@@ -8,6 +8,7 @@
 
 #include "ClangTidyOptions.h"
 #include "ClangTidyModuleRegistry.h"
+#include "clang/Basic/DiagnosticIDs.h"
 #include "clang/Basic/LLVM.h"
 #include "llvm/ADT/SmallString.h"
 #include "llvm/ADT/StringExtras.h"
@@ -71,7 +72,8 @@ struct NOptionMap {
   NOptionMap(IO &, const ClangTidyOptions::OptionMap &OptionMap) {
     Options.reserve(OptionMap.size());
     for (const auto &KeyValue : OptionMap)
-      Options.emplace_back(std::string(KeyValue.getKey()), KeyValue.getValue().Value);
+      Options.emplace_back(std::string(KeyValue.getKey()),
+                           KeyValue.getValue().Value);
   }
   ClangTidyOptions::OptionMap denormalize(IO &) {
     ClangTidyOptions::OptionMap Map;
@@ -135,13 +137,30 @@ template <> struct BlockScalarTraits<MultiLineString> {
   static void output(const MultiLineString &S, void *Ctxt, raw_ostream &OS) {
     OS << S.S;
   }
-
   static StringRef input(StringRef Str, void *Ctxt, MultiLineString &S) {
     S.S = Str;
     return "";
   }
 };
 
+template <> struct ScalarEnumerationTraits<clang::DiagnosticIDs::Level> {
+  static void enumeration(IO &IO, clang::DiagnosticIDs::Level &Level) {
+    IO.enumCase(Level, "Error", clang::DiagnosticIDs::Level::Error);
+    IO.enumCase(Level, "Warning", clang::DiagnosticIDs::Level::Warning);
+    IO.enumCase(Level, "Note", clang::DiagnosticIDs::Level::Note);
+  }
+};
+template <> struct SequenceElementTraits<ClangTidyOptions::CustomCheckDiag> {
+  static const bool flow = false;
+};
+template <> struct MappingTraits<ClangTidyOptions::CustomCheckDiag> {
+  static void mapping(IO &IO, ClangTidyOptions::CustomCheckDiag &D) {
+    IO.mapRequired("BindName", D.BindName);
+    MultiLineString MLS{D.Message};
+    IO.mapRequired("Message", MLS);
+    IO.mapOptional("Level", D.Level);
+  }
+};
 template <> struct SequenceElementTraits<ClangTidyOptions::CustomCheckValue> {
   static const bool flow = false;
 };
@@ -150,6 +169,7 @@ template <> struct MappingTraits<ClangTidyOptions::CustomCheckValue> {
     IO.mapRequired("Name", V.Name);
     MultiLineString MLS{V.Query};
     IO.mapRequired("Query", MLS);
+    IO.mapRequired("Diagnostic", V.Diags);
   }
 };
 
diff --git a/clang-tools-extra/clang-tidy/ClangTidyOptions.h b/clang-tools-extra/clang-tidy/ClangTidyOptions.h
index 9547f94adbde6..2a64ee8fdf7ea 100644
--- a/clang-tools-extra/clang-tidy/ClangTidyOptions.h
+++ b/clang-tools-extra/clang-tidy/ClangTidyOptions.h
@@ -9,6 +9,7 @@
 #ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CLANGTIDYOPTIONS_H
 #define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CLANGTIDYOPTIONS_H
 
+#include "clang/Basic/DiagnosticIDs.h"
 #include "llvm/ADT/IntrusiveRefCntPtr.h"
 #include "llvm/ADT/SmallString.h"
 #include "llvm/ADT/StringMap.h"
@@ -17,6 +18,7 @@
 #include "llvm/Support/MemoryBufferRef.h"
 #include "llvm/Support/VirtualFileSystem.h"
 #include <functional>
+#include <map>
 #include <optional>
 #include <string>
 #include <system_error>
@@ -129,10 +131,15 @@ struct ClangTidyOptions {
   /// Key-value mapping used to store check-specific options.
   OptionMap CheckOptions;
 
+  struct CustomCheckDiag {
+    std::string BindName;
+    std::string Message;
+    std::optional<DiagnosticIDs::Level> Level;
+  };
   struct CustomCheckValue {
     std::string Name;
     std::string Query;
-    // FIXME: extend more features here (e.g. isLanguageVersionSupported, Level)
+    llvm::SmallVector<CustomCheckDiag> Diags;
   };
   using CustomCheckValueList = llvm::SmallVector<CustomCheckValue>;
   std::optional<CustomCheckValueList> CustomChecks;
diff --git a/clang-tools-extra/clang-tidy/custom/CustomTidyModule.cpp b/clang-tools-extra/clang-tidy/custom/CustomTidyModule.cpp
index 23970c5a1e003..d7178b81b4c4e 100644
--- a/clang-tools-extra/clang-tidy/custom/CustomTidyModule.cpp
+++ b/clang-tools-extra/clang-tidy/custom/CustomTidyModule.cpp
@@ -22,6 +22,7 @@ class CustomModule : public ClangTidyModule {
     for (const ClangTidyOptions::CustomCheckValue &V :
          Options->CustomChecks.value()) {
       CheckFactories.registerCheckFactory(
+          // add custom- prefix to avoid conflicts with builtin checks
           "custom-" + V.Name,
           [&V](llvm::StringRef Name, ClangTidyContext *Context) {
             return std::make_unique<custom::QueryCheck>(Name, V, Context);
diff --git a/clang-tools-extra/clang-tidy/custom/QueryCheck.cpp b/clang-tools-extra/clang-tidy/custom/QueryCheck.cpp
index b72382bb20daa..61db01ce2a5c2 100644
--- a/clang-tools-extra/clang-tidy/custom/QueryCheck.cpp
+++ b/clang-tools-extra/clang-tidy/custom/QueryCheck.cpp
@@ -10,6 +10,8 @@
 #include "../../clang-query/Query.h"
 #include "../../clang-query/QueryParser.h"
 #include "clang/ASTMatchers/Dynamic/VariantValue.h"
+#include "clang/Basic/DiagnosticIDs.h"
+#include "llvm/ADT/SmallVector.h"
 #include "llvm/ADT/StringRef.h"
 
 using namespace clang::ast_matchers;
@@ -19,7 +21,13 @@ namespace clang::tidy::custom {
 QueryCheck::QueryCheck(llvm::StringRef Name,
                        const ClangTidyOptions::CustomCheckValue &V,
                        ClangTidyContext *Context)
-    : ClangTidyCheck(Name, Context), Matchers{} {
+    : ClangTidyCheck(Name, Context) {
+  for (const ClangTidyOptions::CustomCheckDiag &D : V.Diags) {
+    auto It = Diags.try_emplace(D.BindName, llvm::SmallVector<Diag>{}).first;
+    It->second.emplace_back(
+        Diag{D.Message, D.Level.value_or(DiagnosticIDs::Warning)});
+  }
+
   clang::query::QuerySession QS({});
   llvm::StringRef QueryStringRef{V.Query};
   while (!QueryStringRef.empty()) {
@@ -64,7 +72,9 @@ void QueryCheck::registerMatchers(MatchFinder *Finder) {
 
 void QueryCheck::check(const MatchFinder::MatchResult &Result) {
   for (auto &[Name, Node] : Result.Nodes.getMap())
-    diag(Node.getSourceRange().getBegin(), Name) << Node.getSourceRange();
+    if (Diags.contains(Name))
+      for (const Diag &D : Diags[Name])
+        diag(D.Message, D.Level) << Node.getSourceRange();
 }
 
 } // namespace clang::tidy::custom
diff --git a/clang-tools-extra/clang-tidy/custom/QueryCheck.h b/clang-tools-extra/clang-tidy/custom/QueryCheck.h
index bb5501a1e5235..f46a20185a195 100644
--- a/clang-tools-extra/clang-tidy/custom/QueryCheck.h
+++ b/clang-tools-extra/clang-tidy/custom/QueryCheck.h
@@ -12,6 +12,7 @@
 #include "../ClangTidyCheck.h"
 #include "clang/ASTMatchers/Dynamic/VariantValue.h"
 #include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/StringMap.h"
 
 namespace clang::tidy::custom {
 
@@ -28,6 +29,11 @@ class QueryCheck : public ClangTidyCheck {
 
 private:
   llvm::SmallVector<ast_matchers::dynamic::DynTypedMatcher> Matchers{};
+  struct Diag {
+    std::string Message;
+    DiagnosticIDs::Level Level;
+  };
+  llvm::StringMap<llvm::SmallVector<Diag>> Diags{};
 };
 
 } // namespace clang::tidy::custom

>From dba8a8ca84999e82a5300b7547d96c283e2ff993 Mon Sep 17 00:00:00 2001
From: Congcong Cai <congcongcai0907 at 163.com>
Date: Mon, 17 Mar 2025 21:59:10 +0000
Subject: [PATCH 06/25] wip

---
 clang-tools-extra/clang-tidy/ClangTidy.cpp    |  9 ++--
 .../clang-tidy/ClangTidyModule.h              |  2 +
 .../clang-tidy/custom/CustomTidyModule.cpp    | 44 +++++++++++--------
 .../clang-tidy/custom/QueryCheck.cpp          | 36 ++++++++++++---
 .../clang-tidy/custom/QueryCheck.h            | 11 ++---
 .../checkers/custom/Inputs/clang-tidy.yml     | 22 ++++++++++
 .../custom/Inputs/incorrect-clang-tidy.yml    | 10 +++++
 .../checkers/custom/query-incorrect-query.cpp |  3 ++
 .../custom/query-partially-active-check.cpp   |  5 +++
 .../test/clang-tidy/checkers/custom/query.cpp |  7 +++
 10 files changed, 114 insertions(+), 35 deletions(-)
 create mode 100644 clang-tools-extra/test/clang-tidy/checkers/custom/Inputs/clang-tidy.yml
 create mode 100644 clang-tools-extra/test/clang-tidy/checkers/custom/Inputs/incorrect-clang-tidy.yml
 create mode 100644 clang-tools-extra/test/clang-tidy/checkers/custom/query-incorrect-query.cpp
 create mode 100644 clang-tools-extra/test/clang-tidy/checkers/custom/query-partially-active-check.cpp
 create mode 100644 clang-tools-extra/test/clang-tidy/checkers/custom/query.cpp

diff --git a/clang-tools-extra/clang-tidy/ClangTidy.cpp b/clang-tools-extra/clang-tidy/ClangTidy.cpp
index 25950319d72c6..f2a69e01a32c5 100644
--- a/clang-tools-extra/clang-tidy/ClangTidy.cpp
+++ b/clang-tools-extra/clang-tidy/ClangTidy.cpp
@@ -58,7 +58,8 @@ LLVM_INSTANTIATE_REGISTRY(clang::tidy::ClangTidyModuleRegistry)
 namespace clang::tidy {
 
 namespace custom {
-extern void setOptions(ClangTidyOptions const &O);
+extern void registerCustomChecks(ClangTidyOptions const &O,
+                                 ClangTidyCheckFactories &Factories);
 } // namespace custom
 
 namespace {
@@ -350,7 +351,7 @@ ClangTidyASTConsumerFactory::ClangTidyASTConsumerFactory(
     IntrusiveRefCntPtr<llvm::vfs::OverlayFileSystem> OverlayFS)
     : Context(Context), OverlayFS(std::move(OverlayFS)),
       CheckFactories(new ClangTidyCheckFactories) {
-  custom::setOptions(Context.getOptions());
+  custom::registerCustomChecks(Context.getOptions(), *CheckFactories);
   for (ClangTidyModuleRegistry::entry E : ClangTidyModuleRegistry::entries()) {
     std::unique_ptr<ClangTidyModule> Module = E.instantiate();
     Module->addCheckFactories(*CheckFactories);
@@ -421,7 +422,7 @@ ClangTidyASTConsumerFactory::createASTConsumer(
                         .getCurrentWorkingDirectory();
   if (WorkingDir)
     Context.setCurrentBuildDirectory(WorkingDir.get());
-
+  custom::registerCustomChecks(Context.getOptions(), *CheckFactories);
   std::vector<std::unique_ptr<ClangTidyCheck>> Checks =
       CheckFactories->createChecksForLanguage(&Context);
 
@@ -664,7 +665,7 @@ getAllChecksAndOptions(bool AllowEnablingAnalyzerAlphaCheckers) {
       std::make_unique<DefaultOptionsProvider>(ClangTidyGlobalOptions(), Opts),
       AllowEnablingAnalyzerAlphaCheckers);
   ClangTidyCheckFactories Factories;
-  custom::setOptions(Context.getOptions());
+  custom::registerCustomChecks(Context.getOptions(), Factories);
   for (const ClangTidyModuleRegistry::entry &Module :
        ClangTidyModuleRegistry::entries()) {
     Module.instantiate()->addCheckFactories(Factories);
diff --git a/clang-tools-extra/clang-tidy/ClangTidyModule.h b/clang-tools-extra/clang-tidy/ClangTidyModule.h
index 28f54331755a7..6f0b2bf32a291 100644
--- a/clang-tools-extra/clang-tidy/ClangTidyModule.h
+++ b/clang-tools-extra/clang-tidy/ClangTidyModule.h
@@ -62,6 +62,8 @@ class ClangTidyCheckFactories {
                          });
   }
 
+  void erase(llvm::StringRef CheckName) { Factories.erase(CheckName); }
+
   /// Create instances of checks that are enabled.
   std::vector<std::unique_ptr<ClangTidyCheck>>
   createChecks(ClangTidyContext *Context) const;
diff --git a/clang-tools-extra/clang-tidy/custom/CustomTidyModule.cpp b/clang-tools-extra/clang-tidy/custom/CustomTidyModule.cpp
index d7178b81b4c4e..e11a39f1a4ccf 100644
--- a/clang-tools-extra/clang-tidy/custom/CustomTidyModule.cpp
+++ b/clang-tools-extra/clang-tidy/custom/CustomTidyModule.cpp
@@ -3,34 +3,40 @@
 #include "../ClangTidyModuleRegistry.h"
 #include "../ClangTidyOptions.h"
 #include "QueryCheck.h"
+#include "llvm/ADT/SmallSet.h"
+#include "llvm/ADT/SmallString.h"
+#include "llvm/ADT/StringRef.h"
 #include <memory>
 
 namespace clang::tidy {
 namespace custom {
 
-// FIXME: could be clearer to add parameter of addCheckFactories to pass
-// Options?
-static ClangTidyOptions const *Options = nullptr;
-extern void setOptions(ClangTidyOptions const &O) { Options = &O; }
-
 class CustomModule : public ClangTidyModule {
 public:
-  void addCheckFactories(ClangTidyCheckFactories &CheckFactories) override {
-    if (Options == nullptr || !Options->CustomChecks.has_value() ||
-        Options->CustomChecks->empty())
-      return;
-    for (const ClangTidyOptions::CustomCheckValue &V :
-         Options->CustomChecks.value()) {
-      CheckFactories.registerCheckFactory(
-          // add custom- prefix to avoid conflicts with builtin checks
-          "custom-" + V.Name,
-          [&V](llvm::StringRef Name, ClangTidyContext *Context) {
-            return std::make_unique<custom::QueryCheck>(Name, V, Context);
-          });
-    }
-  }
+  void addCheckFactories(ClangTidyCheckFactories &CheckFactories) override {}
 };
 
+// FIXME: could be clearer to add parameter of addCheckFactories to pass
+// Options?
+extern void registerCustomChecks(ClangTidyOptions const &Options,
+                                 ClangTidyCheckFactories &Factories) {
+  static llvm::SmallSet<llvm::SmallString<32>, 8> CustomCheckNames{};
+  if (!Options.CustomChecks.has_value() || Options.CustomChecks->empty())
+    return;
+  for (llvm::SmallString<32> const &Name : CustomCheckNames)
+    Factories.erase(Name);
+  for (const ClangTidyOptions::CustomCheckValue &V :
+       Options.CustomChecks.value()) {
+    llvm::SmallString<32> Name = llvm::StringRef{"custom-" + V.Name};
+    Factories.registerCheckFactory(
+        // add custom- prefix to avoid conflicts with builtin checks
+        Name, [&V](llvm::StringRef Name, ClangTidyContext *Context) {
+          return std::make_unique<custom::QueryCheck>(Name, V, Context);
+        });
+    CustomCheckNames.insert(std::move(Name));
+  }
+}
+
 } // namespace custom
 
 // Register the AlteraTidyModule using this statically initialized variable.
diff --git a/clang-tools-extra/clang-tidy/custom/QueryCheck.cpp b/clang-tools-extra/clang-tidy/custom/QueryCheck.cpp
index 61db01ce2a5c2..c69e76918f7ed 100644
--- a/clang-tools-extra/clang-tidy/custom/QueryCheck.cpp
+++ b/clang-tools-extra/clang-tidy/custom/QueryCheck.cpp
@@ -9,10 +9,12 @@
 #include "QueryCheck.h"
 #include "../../clang-query/Query.h"
 #include "../../clang-query/QueryParser.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
 #include "clang/ASTMatchers/Dynamic/VariantValue.h"
 #include "clang/Basic/DiagnosticIDs.h"
 #include "llvm/ADT/SmallVector.h"
 #include "llvm/ADT/StringRef.h"
+#include <string>
 
 using namespace clang::ast_matchers;
 
@@ -23,9 +25,16 @@ QueryCheck::QueryCheck(llvm::StringRef Name,
                        ClangTidyContext *Context)
     : ClangTidyCheck(Name, Context) {
   for (const ClangTidyOptions::CustomCheckDiag &D : V.Diags) {
-    auto It = Diags.try_emplace(D.BindName, llvm::SmallVector<Diag>{}).first;
-    It->second.emplace_back(
-        Diag{D.Message, D.Level.value_or(DiagnosticIDs::Warning)});
+    auto DiagnosticIdIt =
+        Diags
+            .try_emplace(D.Level.value_or(DiagnosticIDs::Warning),
+                         llvm::StringMap<llvm::SmallVector<std::string>>{})
+            .first;
+    auto DiagMessageIt =
+        DiagnosticIdIt->getSecond()
+            .try_emplace(D.BindName, llvm::SmallVector<std::string>{})
+            .first;
+    DiagMessageIt->second.emplace_back(D.Message);
   }
 
   clang::query::QuerySession QS({});
@@ -71,10 +80,23 @@ void QueryCheck::registerMatchers(MatchFinder *Finder) {
 }
 
 void QueryCheck::check(const MatchFinder::MatchResult &Result) {
+  auto Emit = [this](DiagMaps const &DiagMaps, std::string const &BindName,
+                     DynTypedNode const &Node, DiagnosticIDs::Level Level) {
+    if (!DiagMaps.contains(Level))
+      return;
+    auto &DiagMap = DiagMaps.at(Level);
+    if (!DiagMap.contains(BindName))
+      return;
+    for (const std::string &Message : DiagMap.at(BindName)) {
+      diag(Node.getSourceRange().getBegin(), Message, Level);
+    }
+  };
+  for (auto &[Name, Node] : Result.Nodes.getMap())
+    Emit(Diags, Name, Node, DiagnosticIDs::Error);
   for (auto &[Name, Node] : Result.Nodes.getMap())
-    if (Diags.contains(Name))
-      for (const Diag &D : Diags[Name])
-        diag(D.Message, D.Level) << Node.getSourceRange();
+    Emit(Diags, Name, Node, DiagnosticIDs::Warning);
+  // place Note last, otherwise it will not be emitted
+  for (auto &[Name, Node] : Result.Nodes.getMap())
+    Emit(Diags, Name, Node, DiagnosticIDs::Note);
 }
-
 } // namespace clang::tidy::custom
diff --git a/clang-tools-extra/clang-tidy/custom/QueryCheck.h b/clang-tools-extra/clang-tidy/custom/QueryCheck.h
index f46a20185a195..ded4cad4e3459 100644
--- a/clang-tools-extra/clang-tidy/custom/QueryCheck.h
+++ b/clang-tools-extra/clang-tidy/custom/QueryCheck.h
@@ -11,6 +11,8 @@
 
 #include "../ClangTidyCheck.h"
 #include "clang/ASTMatchers/Dynamic/VariantValue.h"
+#include "clang/Basic/DiagnosticIDs.h"
+#include "llvm/ADT/DenseMap.h"
 #include "llvm/ADT/SmallVector.h"
 #include "llvm/ADT/StringMap.h"
 
@@ -29,11 +31,10 @@ class QueryCheck : public ClangTidyCheck {
 
 private:
   llvm::SmallVector<ast_matchers::dynamic::DynTypedMatcher> Matchers{};
-  struct Diag {
-    std::string Message;
-    DiagnosticIDs::Level Level;
-  };
-  llvm::StringMap<llvm::SmallVector<Diag>> Diags{};
+  using DiagMaps =
+      llvm::DenseMap<DiagnosticIDs::Level,
+                     llvm::StringMap<llvm::SmallVector<std::string>>>;
+  DiagMaps Diags;
 };
 
 } // namespace clang::tidy::custom
diff --git a/clang-tools-extra/test/clang-tidy/checkers/custom/Inputs/clang-tidy.yml b/clang-tools-extra/test/clang-tidy/checkers/custom/Inputs/clang-tidy.yml
new file mode 100644
index 0000000000000..0af974515aa30
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/custom/Inputs/clang-tidy.yml
@@ -0,0 +1,22 @@
+CustomeChecks:
+  - Name: test-diag-level
+    Query: |
+      match varDecl(
+          hasType(asString("long")),
+          hasTypeLoc(typeLoc().bind("long"))
+      ).bind("decl")
+    Diagnostic:
+      - BindName: long
+        Message: use 'int' instead of 'long'
+        Level: Warning
+      - BindName: decl
+        Message: declaration of 'long'
+        Level: Note
+  - Name: test-let-bind
+    Query: |
+      let expr varDecl(isStaticStorageClass()).bind("vd")
+      match expr
+    Diagnostic:
+      - BindName: vd
+        Message: find static variable
+        Level: Warning
diff --git a/clang-tools-extra/test/clang-tidy/checkers/custom/Inputs/incorrect-clang-tidy.yml b/clang-tools-extra/test/clang-tidy/checkers/custom/Inputs/incorrect-clang-tidy.yml
new file mode 100644
index 0000000000000..9596d8e4b03a7
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/custom/Inputs/incorrect-clang-tidy.yml
@@ -0,0 +1,10 @@
+CustomeChecks:
+  - Name: test-let-bind
+    Query: |
+      let expr varDecl(isStaticStorageClass()).bind("vd")
+      match expr
+      set output print
+    Diagnostic:
+      - BindName: vd
+        Message: find static variable
+        Level: Warning
diff --git a/clang-tools-extra/test/clang-tidy/checkers/custom/query-incorrect-query.cpp b/clang-tools-extra/test/clang-tidy/checkers/custom/query-incorrect-query.cpp
new file mode 100644
index 0000000000000..cfc2753ca9be7
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/custom/query-incorrect-query.cpp
@@ -0,0 +1,3 @@
+// RUN: %check_clang_tidy %s custom-* %t --config-file=%S/Inputs/incorrect-clang-tidy.yml
+
+// CHECK-MESSAGES: warning: unsupported querry kind [clang-tidy-config]
diff --git a/clang-tools-extra/test/clang-tidy/checkers/custom/query-partially-active-check.cpp b/clang-tools-extra/test/clang-tidy/checkers/custom/query-partially-active-check.cpp
new file mode 100644
index 0000000000000..962313ddb587b
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/custom/query-partially-active-check.cpp
@@ -0,0 +1,5 @@
+// RUN: %check_clang_tidy %s custom-test-let-bind %t --config-file=%S/Inputs/clang-tidy.yml
+
+extern long E;
+static int S;
+// CHECK-MESSAGES: [[@LINE-1]]:1: warning: find static variable [custom-test-let-bind]
diff --git a/clang-tools-extra/test/clang-tidy/checkers/custom/query.cpp b/clang-tools-extra/test/clang-tidy/checkers/custom/query.cpp
new file mode 100644
index 0000000000000..e9a27301bd611
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/custom/query.cpp
@@ -0,0 +1,7 @@
+// RUN: %check_clang_tidy %s custom-* %t --config-file=%S/Inputs/clang-tidy.yml
+
+extern long E;
+// CHECK-MESSAGES: [[@LINE-1]]:8: warning: use 'int' instead of 'long' [custom-test-diag-level]
+// CHECK-MESSAGES: [[@LINE-2]]:1: note: declaration of 'long'
+static int S;
+// CHECK-MESSAGES: [[@LINE-1]]:1: warning: find static variable [custom-test-let-bind]

>From 0df6f7eee3093ccf1f77320367379155e65e4629 Mon Sep 17 00:00:00 2001
From: Congcong Cai <congcongcai0907 at 163.com>
Date: Mon, 17 Mar 2025 22:41:40 +0000
Subject: [PATCH 07/25] test

---
 .../custom-query-check/append-clang-tidy.yml  |  8 ++++
 .../custom-query-check/empty-clang-tidy.yml   |  1 +
 .../override-clang-tidy.yml                   | 11 +++++
 .../custom-query-check/root-clang-tidy.yml    | 11 +++++
 .../Inputs/custom-query-check/vfsoverlay.yaml | 44 ++++++++++++++++++
 .../infrastructure/custom-query-check.cpp     | 45 +++++++++++++++++++
 6 files changed, 120 insertions(+)
 create mode 100644 clang-tools-extra/test/clang-tidy/infrastructure/Inputs/custom-query-check/append-clang-tidy.yml
 create mode 100644 clang-tools-extra/test/clang-tidy/infrastructure/Inputs/custom-query-check/empty-clang-tidy.yml
 create mode 100644 clang-tools-extra/test/clang-tidy/infrastructure/Inputs/custom-query-check/override-clang-tidy.yml
 create mode 100644 clang-tools-extra/test/clang-tidy/infrastructure/Inputs/custom-query-check/root-clang-tidy.yml
 create mode 100644 clang-tools-extra/test/clang-tidy/infrastructure/Inputs/custom-query-check/vfsoverlay.yaml
 create mode 100644 clang-tools-extra/test/clang-tidy/infrastructure/custom-query-check.cpp

diff --git a/clang-tools-extra/test/clang-tidy/infrastructure/Inputs/custom-query-check/append-clang-tidy.yml b/clang-tools-extra/test/clang-tidy/infrastructure/Inputs/custom-query-check/append-clang-tidy.yml
new file mode 100644
index 0000000000000..5d816eec99f00
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/infrastructure/Inputs/custom-query-check/append-clang-tidy.yml
@@ -0,0 +1,8 @@
+InheritParentConfig: true
+CustomeChecks:
+  - Name: function-decl
+    Query: match functionDecl().bind("func")
+    Diagnostic:
+      - BindName: func
+        Message: find function decl
+        Level: Warning
diff --git a/clang-tools-extra/test/clang-tidy/infrastructure/Inputs/custom-query-check/empty-clang-tidy.yml b/clang-tools-extra/test/clang-tidy/infrastructure/Inputs/custom-query-check/empty-clang-tidy.yml
new file mode 100644
index 0000000000000..1f16f2a33b3ce
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/infrastructure/Inputs/custom-query-check/empty-clang-tidy.yml
@@ -0,0 +1 @@
+InheritParentConfig: false
\ No newline at end of file
diff --git a/clang-tools-extra/test/clang-tidy/infrastructure/Inputs/custom-query-check/override-clang-tidy.yml b/clang-tools-extra/test/clang-tidy/infrastructure/Inputs/custom-query-check/override-clang-tidy.yml
new file mode 100644
index 0000000000000..0ed6a68ff2af3
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/infrastructure/Inputs/custom-query-check/override-clang-tidy.yml
@@ -0,0 +1,11 @@
+CustomeChecks:
+  - Name: avoid-long-type
+    Query: |
+      match varDecl(
+          hasType(asString("long")),
+          hasTypeLoc(typeLoc().bind("long"))
+      )
+    Diagnostic:
+      - BindName: long
+        Message: use 'int' instead of 'long' override
+        Level: Warning
diff --git a/clang-tools-extra/test/clang-tidy/infrastructure/Inputs/custom-query-check/root-clang-tidy.yml b/clang-tools-extra/test/clang-tidy/infrastructure/Inputs/custom-query-check/root-clang-tidy.yml
new file mode 100644
index 0000000000000..9c06a91c56811
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/infrastructure/Inputs/custom-query-check/root-clang-tidy.yml
@@ -0,0 +1,11 @@
+CustomeChecks:
+  - Name: avoid-long-type
+    Query: |
+      match varDecl(
+          hasType(asString("long")),
+          hasTypeLoc(typeLoc().bind("long"))
+      )
+    Diagnostic:
+      - BindName: long
+        Message: use 'int' instead of 'long'
+        Level: Warning
diff --git a/clang-tools-extra/test/clang-tidy/infrastructure/Inputs/custom-query-check/vfsoverlay.yaml b/clang-tools-extra/test/clang-tidy/infrastructure/Inputs/custom-query-check/vfsoverlay.yaml
new file mode 100644
index 0000000000000..2b507f60092c3
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/infrastructure/Inputs/custom-query-check/vfsoverlay.yaml
@@ -0,0 +1,44 @@
+version: 0
+roots:
+  - name: OUT_DIR
+    type: directory
+    contents:
+      - name: .clang-tidy
+        type: file
+        external-contents: INPUT_DIR/root-clang-tidy.yml
+      - name: main.cpp
+        type: file
+        external-contents: MAIN_FILE
+      - name: subdir
+        type: directory
+        contents:
+          - name: main.cpp
+            type: file
+            external-contents: MAIN_FILE
+      - name: subdir-override
+        type: directory
+        contents:
+          - name: main.cpp
+            type: file
+            external-contents: MAIN_FILE
+          - name: .clang-tidy
+            type: file
+            external-contents: INPUT_DIR/override-clang-tidy.yml
+      - name: subdir-empty
+        type: directory
+        contents:
+          - name: main.cpp
+            type: file
+            external-contents: MAIN_FILE
+          - name: .clang-tidy
+            type: file
+            external-contents: INPUT_DIR/empty-clang-tidy.yml
+      - name: subdir-append
+        type: directory
+        contents:
+          - name: main.cpp
+            type: file
+            external-contents: MAIN_FILE
+          - name: .clang-tidy
+            type: file
+            external-contents: INPUT_DIR/append-clang-tidy.yml
diff --git a/clang-tools-extra/test/clang-tidy/infrastructure/custom-query-check.cpp b/clang-tools-extra/test/clang-tidy/infrastructure/custom-query-check.cpp
new file mode 100644
index 0000000000000..b91c1726b3e54
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/infrastructure/custom-query-check.cpp
@@ -0,0 +1,45 @@
+// RUN: sed -e "s:INPUT_DIR:%S/Inputs/custom-query-check:g" -e "s:OUT_DIR:%t:g" -e "s:MAIN_FILE:%s:g" %S/Inputs/custom-query-check/vfsoverlay.yaml > %t.yaml
+// RUN: clang-tidy %t/main.cpp -checks='-*,custom-*' -vfsoverlay %t.yaml | FileCheck %s --check-prefix=CHECK-SAME-DIR
+// RUN: clang-tidy %t/subdir/main.cpp -checks='-*,custom-*' -vfsoverlay %t.yaml | FileCheck %s --check-prefix=CHECK-SUB-DIR-BASE
+// RUN: clang-tidy %t/subdir-override/main.cpp -checks='-*,custom-*' -vfsoverlay %t.yaml | FileCheck %s --check-prefix=CHECK-SUB-DIR-OVERRIDE
+// RUN: clang-tidy %t/subdir-empty/main.cpp -checks='-*,custom-*' -vfsoverlay %t.yaml --allow-no-checks | FileCheck %s --check-prefix=CHECK-SUB-DIR-EMPTY
+// RUN: clang-tidy %t/subdir-append/main.cpp -checks='-*,custom-*' -vfsoverlay %t.yaml | FileCheck %s --check-prefix=CHECK-SUB-DIR-APPEND
+// RUN: clang-tidy %t/subdir-append/main.cpp -checks='-*,custom-*' -vfsoverlay %t.yaml --list-checks | FileCheck %s --check-prefix=LIST-CHECK
+// RUN: clang-tidy %t/subdir-append/main.cpp -checks='-*,custom-*' -vfsoverlay %t.yaml --dump-config | FileCheck %s --check-prefix=DUMP-CONFIG
+// REQUIRES: shell
+
+
+long V;
+// CHECK-SAME-DIR: [[@LINE-1]]:1: warning: use 'int' instead of 'long' [custom-avoid-long-type]
+// CHECK-SUB-DIR-BASE: [[@LINE-2]]:1: warning: use 'int' instead of 'long' [custom-avoid-long-type]
+// CHECK-SUB-DIR-OVERRIDE: [[@LINE-3]]:1: warning: use 'int' instead of 'long' override [custom-avoid-long-type]
+// CHECK-SUB-DIR-EMPTY: No checks enabled.
+// CHECK-SUB-DIR-APPEND: [[@LINE-5]]:1: warning: use 'int' instead of 'long' [custom-avoid-long-type]
+
+void f();
+// CHECK-SUB-DIR-APPEND: [[@LINE-1]]:1: warning: find function decl [custom-function-decl]
+
+// LIST-CHECK: Enabled checks:
+// LIST-CHECK:   custom-avoid-long-type
+// LIST-CHECK:   custom-function-decl
+
+// DUMP-CONFIG: CustomeChecks:
+// DUMP-CONFIG: - Name: avoid-long-type
+// DUMP-CONFIG:   Query: |
+// DUMP-CONFIG:     match varDecl(
+// DUMP-CONFIG:       hasType(asString("long")),
+// DUMP-CONFIG:       hasTypeLoc(typeLoc().bind("long"))
+// DUMP-CONFIG:     )
+// DUMP-CONFIG:   Diagnostic:
+// DUMP-CONFIG:    - BindName: long
+// DUMP-CONFIG:      Message: |
+// DUMP-CONFIG:           use 'int' instead of 'long'
+// DUMP-CONFIG:      Level:           Warning
+// DUMP-CONFIG: - Name: function-decl
+// DUMP-CONFIG:   Query: |
+// DUMP-CONFIG:     match functionDecl().bind("func")
+// DUMP-CONFIG:   Diagnostic:
+// DUMP-CONFIG:    - BindName: func
+// DUMP-CONFIG:      Message: |
+// DUMP-CONFIG:        find function decl
+// DUMP-CONFIG:   Level: Warning

>From 5bb2c6c287186cf2427a325fe481aa3e7757a0ac Mon Sep 17 00:00:00 2001
From: Congcong Cai <congcongcai0907 at 163.com>
Date: Tue, 18 Mar 2025 13:55:22 +0000
Subject: [PATCH 08/25] doc

---
 .../clang-tidy/ClangTidyOptions.cpp           |  2 +-
 .../clang-tidy/tool/ClangTidyMain.cpp         |  2 +
 clang-tools-extra/docs/ReleaseNotes.rst       |  3 +
 .../clang-tidy/QueryBasedCustomChecks.rst     | 57 +++++++++++++++++++
 clang-tools-extra/docs/clang-tidy/index.rst   |  3 +
 .../checkers/custom/Inputs/clang-tidy.yml     |  2 +-
 .../custom/Inputs/incorrect-clang-tidy.yml    |  2 +-
 .../custom-query-check/append-clang-tidy.yml  |  2 +-
 .../override-clang-tidy.yml                   |  2 +-
 .../custom-query-check/root-clang-tidy.yml    |  2 +-
 .../infrastructure/custom-query-check.cpp     |  2 +-
 11 files changed, 72 insertions(+), 7 deletions(-)
 create mode 100644 clang-tools-extra/docs/clang-tidy/QueryBasedCustomChecks.rst

diff --git a/clang-tools-extra/clang-tidy/ClangTidyOptions.cpp b/clang-tools-extra/clang-tidy/ClangTidyOptions.cpp
index b21b85ca38f06..acedbd8d41faa 100644
--- a/clang-tools-extra/clang-tidy/ClangTidyOptions.cpp
+++ b/clang-tools-extra/clang-tidy/ClangTidyOptions.cpp
@@ -228,7 +228,7 @@ template <> struct MappingTraits<ClangTidyOptions> {
     IO.mapOptional("InheritParentConfig", Options.InheritParentConfig);
     IO.mapOptional("UseColor", Options.UseColor);
     IO.mapOptional("SystemHeaders", Options.SystemHeaders);
-    IO.mapOptional("CustomeChecks", Options.CustomChecks);
+    IO.mapOptional("CustomChecks", Options.CustomChecks);
   }
 };
 
diff --git a/clang-tools-extra/clang-tidy/tool/ClangTidyMain.cpp b/clang-tools-extra/clang-tidy/tool/ClangTidyMain.cpp
index fa8887e4639b4..5784b05d2281d 100644
--- a/clang-tools-extra/clang-tidy/tool/ClangTidyMain.cpp
+++ b/clang-tools-extra/clang-tidy/tool/ClangTidyMain.cpp
@@ -60,6 +60,8 @@ Configuration files:
   Checks                       - Same as '--checks'. Additionally, the list of
                                  globs can be specified as a list instead of a
                                  string.
+  CustomChecks                 - Array of user defined checks based on
+                                 clang-query syntax.
   ExcludeHeaderFilterRegex     - Same as '--exclude-header-filter'.
   ExtraArgs                    - Same as '--extra-arg'.
   ExtraArgsBefore              - Same as '--extra-arg-before'.
diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst
index 2252efb498c2c..6d22f83f2248b 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -96,6 +96,9 @@ Improvements to clang-tidy
   `SystemHeaders` option is enabled.
   Note: this may lead to false negatives; downstream users may need to adjust
   their checks to preserve existing behavior.
+- :program:`clang-tidy` now supports query based custom checks by `CustomChecks`
+  configuration option.
+  :doc:`Query Based Custom Check Document <clang-tidy/QueryBasedCustomChecks>`
 
 New checks
 ^^^^^^^^^^
diff --git a/clang-tools-extra/docs/clang-tidy/QueryBasedCustomChecks.rst b/clang-tools-extra/docs/clang-tidy/QueryBasedCustomChecks.rst
new file mode 100644
index 0000000000000..6efd8fe6797df
--- /dev/null
+++ b/clang-tools-extra/docs/clang-tidy/QueryBasedCustomChecks.rst
@@ -0,0 +1,57 @@
+====================================
+Query Based Custom Clang-Tidy Checks
+====================================
+
+Introduction
+============
+
+This page provides examples of how to add query based custom checks for
+:program:`clang-tidy`.
+
+Custom checks are based on clang-query syntax. Every custom checks will be
+registered in `custom` module to avoid name conflict. They can be enabled or
+disabled by the checks option like the builtin checks.
+
+Custom checks support inheritance from parent configurations like other
+configuration items.
+
+Configuration
+=============
+
+`CustomChecks` is a list of custom checks. Each check must contain
+  - Name: check name can been used in `-checks` option.
+  - Query: query string
+  - Diagnostic: list of diagnostics to be reported.
+     - BindName: name of the node to be bound in `Query`.
+     - Message: message to be reported.
+     - Level: severity of the diagnostic, the possible values are `Note`, `Warning`, `Error`.
+
+Example
+=======
+
+.. code-block:: yaml
+
+  Checks: -*,custom-call-main-function
+  CustomChecks:
+    - Name: call-main-function
+      Query: |
+          match callExpr(
+            callee(
+              functionDecl(isMain()).bind("fn")
+            )
+          ).bind("callee")
+      Diagnostic:
+        - BindName: fn
+          Message: main function.
+          Level: Note
+        - BindName: callee
+          Message: call to main function.
+          Level: Warning
+
+.. code-block:: c++
+
+  int main(); // note: main function.
+
+  void bar() {
+    main(); // warning: call to main function. [custom-call-main-function]
+  }
diff --git a/clang-tools-extra/docs/clang-tidy/index.rst b/clang-tools-extra/docs/clang-tidy/index.rst
index b7a366e874130..83b3a4d485df5 100644
--- a/clang-tools-extra/docs/clang-tidy/index.rst
+++ b/clang-tools-extra/docs/clang-tidy/index.rst
@@ -10,6 +10,7 @@ See also:
    :maxdepth: 1
 
    List of Clang-Tidy Checks <checks/list>
+   Query Based Custom Clang-Tidy Checks <QueryBasedCustomChecks>
    Clang-tidy IDE/Editor Integrations <Integrations>
    Getting Involved <Contributing>
    External Clang-Tidy Examples <ExternalClang-TidyExamples>
@@ -292,6 +293,8 @@ An overview of all the command-line options:
     Checks                       - Same as '--checks'. Additionally, the list of
                                    globs can be specified as a list instead of a
                                    string.
+    CustomChecks                 - Array of user defined checks based on
+                                   clang-query syntax.
     ExcludeHeaderFilterRegex     - Same as '--exclude-header-filter'.
     ExtraArgs                    - Same as '--extra-arg'.
     ExtraArgsBefore              - Same as '--extra-arg-before'.
diff --git a/clang-tools-extra/test/clang-tidy/checkers/custom/Inputs/clang-tidy.yml b/clang-tools-extra/test/clang-tidy/checkers/custom/Inputs/clang-tidy.yml
index 0af974515aa30..b4524e247feae 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/custom/Inputs/clang-tidy.yml
+++ b/clang-tools-extra/test/clang-tidy/checkers/custom/Inputs/clang-tidy.yml
@@ -1,4 +1,4 @@
-CustomeChecks:
+CustomChecks:
   - Name: test-diag-level
     Query: |
       match varDecl(
diff --git a/clang-tools-extra/test/clang-tidy/checkers/custom/Inputs/incorrect-clang-tidy.yml b/clang-tools-extra/test/clang-tidy/checkers/custom/Inputs/incorrect-clang-tidy.yml
index 9596d8e4b03a7..4cad7364c1297 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/custom/Inputs/incorrect-clang-tidy.yml
+++ b/clang-tools-extra/test/clang-tidy/checkers/custom/Inputs/incorrect-clang-tidy.yml
@@ -1,4 +1,4 @@
-CustomeChecks:
+CustomChecks:
   - Name: test-let-bind
     Query: |
       let expr varDecl(isStaticStorageClass()).bind("vd")
diff --git a/clang-tools-extra/test/clang-tidy/infrastructure/Inputs/custom-query-check/append-clang-tidy.yml b/clang-tools-extra/test/clang-tidy/infrastructure/Inputs/custom-query-check/append-clang-tidy.yml
index 5d816eec99f00..5b25ec061ba63 100644
--- a/clang-tools-extra/test/clang-tidy/infrastructure/Inputs/custom-query-check/append-clang-tidy.yml
+++ b/clang-tools-extra/test/clang-tidy/infrastructure/Inputs/custom-query-check/append-clang-tidy.yml
@@ -1,5 +1,5 @@
 InheritParentConfig: true
-CustomeChecks:
+CustomChecks:
   - Name: function-decl
     Query: match functionDecl().bind("func")
     Diagnostic:
diff --git a/clang-tools-extra/test/clang-tidy/infrastructure/Inputs/custom-query-check/override-clang-tidy.yml b/clang-tools-extra/test/clang-tidy/infrastructure/Inputs/custom-query-check/override-clang-tidy.yml
index 0ed6a68ff2af3..ec243b8396ea8 100644
--- a/clang-tools-extra/test/clang-tidy/infrastructure/Inputs/custom-query-check/override-clang-tidy.yml
+++ b/clang-tools-extra/test/clang-tidy/infrastructure/Inputs/custom-query-check/override-clang-tidy.yml
@@ -1,4 +1,4 @@
-CustomeChecks:
+CustomChecks:
   - Name: avoid-long-type
     Query: |
       match varDecl(
diff --git a/clang-tools-extra/test/clang-tidy/infrastructure/Inputs/custom-query-check/root-clang-tidy.yml b/clang-tools-extra/test/clang-tidy/infrastructure/Inputs/custom-query-check/root-clang-tidy.yml
index 9c06a91c56811..861ed10be1a5f 100644
--- a/clang-tools-extra/test/clang-tidy/infrastructure/Inputs/custom-query-check/root-clang-tidy.yml
+++ b/clang-tools-extra/test/clang-tidy/infrastructure/Inputs/custom-query-check/root-clang-tidy.yml
@@ -1,4 +1,4 @@
-CustomeChecks:
+CustomChecks:
   - Name: avoid-long-type
     Query: |
       match varDecl(
diff --git a/clang-tools-extra/test/clang-tidy/infrastructure/custom-query-check.cpp b/clang-tools-extra/test/clang-tidy/infrastructure/custom-query-check.cpp
index b91c1726b3e54..13834660385d1 100644
--- a/clang-tools-extra/test/clang-tidy/infrastructure/custom-query-check.cpp
+++ b/clang-tools-extra/test/clang-tidy/infrastructure/custom-query-check.cpp
@@ -23,7 +23,7 @@ void f();
 // LIST-CHECK:   custom-avoid-long-type
 // LIST-CHECK:   custom-function-decl
 
-// DUMP-CONFIG: CustomeChecks:
+// DUMP-CONFIG: CustomChecks:
 // DUMP-CONFIG: - Name: avoid-long-type
 // DUMP-CONFIG:   Query: |
 // DUMP-CONFIG:     match varDecl(

>From 64c45dc6033be5eeb906ccf53561f64f4aac8067 Mon Sep 17 00:00:00 2001
From: Congcong Cai <congcongcai0907 at 163.com>
Date: Tue, 18 Mar 2025 14:04:39 +0000
Subject: [PATCH 09/25] fix comment

---
 clang-tools-extra/clang-tidy/ClangTidyOptions.cpp        | 5 ++---
 clang-tools-extra/clang-tidy/custom/CustomTidyModule.cpp | 4 ++--
 2 files changed, 4 insertions(+), 5 deletions(-)

diff --git a/clang-tools-extra/clang-tidy/ClangTidyOptions.cpp b/clang-tools-extra/clang-tidy/ClangTidyOptions.cpp
index acedbd8d41faa..dd06e502b338c 100644
--- a/clang-tools-extra/clang-tidy/ClangTidyOptions.cpp
+++ b/clang-tools-extra/clang-tidy/ClangTidyOptions.cpp
@@ -290,15 +290,14 @@ ClangTidyOptions &ClangTidyOptions::mergeWith(const ClangTidyOptions &Other,
   overrideValue(UseColor, Other.UseColor);
   mergeVectors(ExtraArgs, Other.ExtraArgs);
   mergeVectors(ExtraArgsBefore, Other.ExtraArgsBefore);
-
+  // FIXME: how to handle duplicate names check?
+  mergeVectors(CustomChecks, Other.CustomChecks);
   for (const auto &KeyValue : Other.CheckOptions) {
     CheckOptions.insert_or_assign(
         KeyValue.getKey(),
         ClangTidyValue(KeyValue.getValue().Value,
                        KeyValue.getValue().Priority + Order));
   }
-  mergeVectors(CustomChecks, Other.CustomChecks);
-
   return *this;
 }
 
diff --git a/clang-tools-extra/clang-tidy/custom/CustomTidyModule.cpp b/clang-tools-extra/clang-tidy/custom/CustomTidyModule.cpp
index e11a39f1a4ccf..6150634fbc4bc 100644
--- a/clang-tools-extra/clang-tidy/custom/CustomTidyModule.cpp
+++ b/clang-tools-extra/clang-tidy/custom/CustomTidyModule.cpp
@@ -16,8 +16,8 @@ class CustomModule : public ClangTidyModule {
   void addCheckFactories(ClangTidyCheckFactories &CheckFactories) override {}
 };
 
-// FIXME: could be clearer to add parameter of addCheckFactories to pass
-// Options?
+// We need to register the checks more flexibly than builtin modules. The checks
+// will changed dynamically when switching to different source file.
 extern void registerCustomChecks(ClangTidyOptions const &Options,
                                  ClangTidyCheckFactories &Factories) {
   static llvm::SmallSet<llvm::SmallString<32>, 8> CustomCheckNames{};

>From b6505940716dcdc1992157b4463e005639e0ecf8 Mon Sep 17 00:00:00 2001
From: Congcong Cai <congcongcai0907 at 163.com>
Date: Wed, 19 Mar 2025 09:58:10 +0800
Subject: [PATCH 10/25] Update
 clang-tools-extra/clang-tidy/custom/CustomTidyModule.cpp

Co-authored-by: Baranov Victor <bar.victor.2002 at gmail.com>
---
 clang-tools-extra/clang-tidy/custom/CustomTidyModule.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/clang-tools-extra/clang-tidy/custom/CustomTidyModule.cpp b/clang-tools-extra/clang-tidy/custom/CustomTidyModule.cpp
index 6150634fbc4bc..3fe94d8e5e941 100644
--- a/clang-tools-extra/clang-tidy/custom/CustomTidyModule.cpp
+++ b/clang-tools-extra/clang-tidy/custom/CustomTidyModule.cpp
@@ -39,7 +39,7 @@ extern void registerCustomChecks(ClangTidyOptions const &Options,
 
 } // namespace custom
 
-// Register the AlteraTidyModule using this statically initialized variable.
+// Register the CustomTidyModule using this statically initialized variable.
 static ClangTidyModuleRegistry::Add<custom::CustomModule>
     X("custom-module", "Adds custom query lint checks.");
 

>From b56c07c359936b91a2951b8aaf577298573037fd Mon Sep 17 00:00:00 2001
From: Congcong Cai <congcongcai0907 at 163.com>
Date: Wed, 19 Mar 2025 10:10:34 +0800
Subject: [PATCH 11/25] fix review

---
 clang-tools-extra/clang-tidy/custom/QueryCheck.cpp          | 6 +++---
 clang-tools-extra/clang-tidy/custom/QueryCheck.h            | 6 ++----
 clang-tools-extra/clang-tidy/tool/ClangTidyMain.cpp         | 2 +-
 .../docs/clang-tidy/QueryBasedCustomChecks.rst              | 6 +++---
 clang-tools-extra/docs/clang-tidy/index.rst                 | 2 +-
 .../Inputs/custom-query-check/empty-clang-tidy.yml          | 2 +-
 6 files changed, 11 insertions(+), 13 deletions(-)

diff --git a/clang-tools-extra/clang-tidy/custom/QueryCheck.cpp b/clang-tools-extra/clang-tidy/custom/QueryCheck.cpp
index c69e76918f7ed..dc698d18be505 100644
--- a/clang-tools-extra/clang-tidy/custom/QueryCheck.cpp
+++ b/clang-tools-extra/clang-tidy/custom/QueryCheck.cpp
@@ -91,12 +91,12 @@ void QueryCheck::check(const MatchFinder::MatchResult &Result) {
       diag(Node.getSourceRange().getBegin(), Message, Level);
     }
   };
-  for (auto &[Name, Node] : Result.Nodes.getMap())
+  for (const auto &[Name, Node] : Result.Nodes.getMap())
     Emit(Diags, Name, Node, DiagnosticIDs::Error);
-  for (auto &[Name, Node] : Result.Nodes.getMap())
+  for (const auto &[Name, Node] : Result.Nodes.getMap())
     Emit(Diags, Name, Node, DiagnosticIDs::Warning);
   // place Note last, otherwise it will not be emitted
-  for (auto &[Name, Node] : Result.Nodes.getMap())
+  for (const auto &[Name, Node] : Result.Nodes.getMap())
     Emit(Diags, Name, Node, DiagnosticIDs::Note);
 }
 } // namespace clang::tidy::custom
diff --git a/clang-tools-extra/clang-tidy/custom/QueryCheck.h b/clang-tools-extra/clang-tidy/custom/QueryCheck.h
index ded4cad4e3459..1fd1d0ccb9ea0 100644
--- a/clang-tools-extra/clang-tidy/custom/QueryCheck.h
+++ b/clang-tools-extra/clang-tidy/custom/QueryCheck.h
@@ -18,10 +18,8 @@
 
 namespace clang::tidy::custom {
 
-/// FIXME: Write a short description.
-///
-/// For the user-facing documentation see:
-/// http://clang.llvm.org/extra/clang-tidy/checks/custom/query.html
+/// Implement of Clang-Query based check.
+/// Not directly visible to users.
 class QueryCheck : public ClangTidyCheck {
 public:
   QueryCheck(llvm::StringRef Name, const ClangTidyOptions::CustomCheckValue &V,
diff --git a/clang-tools-extra/clang-tidy/tool/ClangTidyMain.cpp b/clang-tools-extra/clang-tidy/tool/ClangTidyMain.cpp
index 5784b05d2281d..0c5a7ff518653 100644
--- a/clang-tools-extra/clang-tidy/tool/ClangTidyMain.cpp
+++ b/clang-tools-extra/clang-tidy/tool/ClangTidyMain.cpp
@@ -61,7 +61,7 @@ Configuration files:
                                  globs can be specified as a list instead of a
                                  string.
   CustomChecks                 - Array of user defined checks based on
-                                 clang-query syntax.
+                                 Clang-Query syntax.
   ExcludeHeaderFilterRegex     - Same as '--exclude-header-filter'.
   ExtraArgs                    - Same as '--extra-arg'.
   ExtraArgsBefore              - Same as '--extra-arg-before'.
diff --git a/clang-tools-extra/docs/clang-tidy/QueryBasedCustomChecks.rst b/clang-tools-extra/docs/clang-tidy/QueryBasedCustomChecks.rst
index 6efd8fe6797df..a41837a73548e 100644
--- a/clang-tools-extra/docs/clang-tidy/QueryBasedCustomChecks.rst
+++ b/clang-tools-extra/docs/clang-tidy/QueryBasedCustomChecks.rst
@@ -8,9 +8,9 @@ Introduction
 This page provides examples of how to add query based custom checks for
 :program:`clang-tidy`.
 
-Custom checks are based on clang-query syntax. Every custom checks will be
-registered in `custom` module to avoid name conflict. They can be enabled or
-disabled by the checks option like the builtin checks.
+Custom checks are based on :program:`clang-query` syntax. Every custom checks
+will be registered in `custom` module to avoid name conflict. They can be
+enabled or disabled by the checks option like the built-in checks.
 
 Custom checks support inheritance from parent configurations like other
 configuration items.
diff --git a/clang-tools-extra/docs/clang-tidy/index.rst b/clang-tools-extra/docs/clang-tidy/index.rst
index 83b3a4d485df5..70f67582b1f5b 100644
--- a/clang-tools-extra/docs/clang-tidy/index.rst
+++ b/clang-tools-extra/docs/clang-tidy/index.rst
@@ -294,7 +294,7 @@ An overview of all the command-line options:
                                    globs can be specified as a list instead of a
                                    string.
     CustomChecks                 - Array of user defined checks based on
-                                   clang-query syntax.
+                                   Clang-Query syntax.
     ExcludeHeaderFilterRegex     - Same as '--exclude-header-filter'.
     ExtraArgs                    - Same as '--extra-arg'.
     ExtraArgsBefore              - Same as '--extra-arg-before'.
diff --git a/clang-tools-extra/test/clang-tidy/infrastructure/Inputs/custom-query-check/empty-clang-tidy.yml b/clang-tools-extra/test/clang-tidy/infrastructure/Inputs/custom-query-check/empty-clang-tidy.yml
index 1f16f2a33b3ce..2e9a13e720f4e 100644
--- a/clang-tools-extra/test/clang-tidy/infrastructure/Inputs/custom-query-check/empty-clang-tidy.yml
+++ b/clang-tools-extra/test/clang-tidy/infrastructure/Inputs/custom-query-check/empty-clang-tidy.yml
@@ -1 +1 @@
-InheritParentConfig: false
\ No newline at end of file
+InheritParentConfig: false

>From b2cecb8cb74544e222067f3df94eafdb44657653 Mon Sep 17 00:00:00 2001
From: Congcong Cai <congcongcai0907 at 163.com>
Date: Sun, 25 May 2025 22:24:04 +0800
Subject: [PATCH 12/25] add CLANG_TIDY_ENABLE_QUERY_BASED_CUSTOM_CHECKS cmake
 config

---
 clang-tools-extra/CMakeLists.txt              |  2 ++
 clang-tools-extra/clang-tidy/CMakeLists.txt   |  5 +++-
 clang-tools-extra/clang-tidy/ClangTidy.cpp    |  6 ++++
 .../clang-tidy/ClangTidyForceLinker.h         |  2 ++
 .../clang-tidy/clang-tidy-config.h.cmake      |  1 +
 .../clang-tidy/custom/CMakeLists.txt          | 28 ++++++++++---------
 .../docs/clang-tidy/Contributing.rst          |  3 ++
 .../unittests/clang-tidy/CMakeLists.txt       |  1 +
 8 files changed, 34 insertions(+), 14 deletions(-)

diff --git a/clang-tools-extra/CMakeLists.txt b/clang-tools-extra/CMakeLists.txt
index 6b6f2b1ca2276..87050db4e0e75 100644
--- a/clang-tools-extra/CMakeLists.txt
+++ b/clang-tools-extra/CMakeLists.txt
@@ -5,6 +5,8 @@ include(GNUInstallDirs)
 
 option(CLANG_TIDY_ENABLE_STATIC_ANALYZER
   "Include static analyzer checks in clang-tidy" ON)
+option(CLANG_TIDY_ENABLE_QUERY_BASED_CUSTOM_CHECKS
+  "Enable query-based custom checks in clang-tidy" ON)
 
 if(CLANG_INCLUDE_TESTS)
   umbrella_lit_testsuite_begin(check-clang-tools)
diff --git a/clang-tools-extra/clang-tidy/CMakeLists.txt b/clang-tools-extra/clang-tidy/CMakeLists.txt
index 90efd2ef1f451..153356245cfd1 100644
--- a/clang-tools-extra/clang-tidy/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/CMakeLists.txt
@@ -86,7 +86,6 @@ set(ALL_CLANG_TIDY_CHECKS
   clangTidyCERTModule
   clangTidyConcurrencyModule
   clangTidyCppCoreGuidelinesModule
-  clangTidyCustomModule
   clangTidyDarwinModule
   clangTidyFuchsiaModule
   clangTidyGoogleModule
@@ -103,6 +102,10 @@ set(ALL_CLANG_TIDY_CHECKS
   clangTidyReadabilityModule
   clangTidyZirconModule
   )
+
+if(CLANG_TIDY_ENABLE_QUERY_BASED_CUSTOM_CHECKS)
+  list(APPEND ALL_CLANG_TIDY_CHECKS clangTidyCustomModule)
+endif()
 if(CLANG_TIDY_ENABLE_STATIC_ANALYZER)
   list(APPEND ALL_CLANG_TIDY_CHECKS clangTidyMPIModule)
 endif()
diff --git a/clang-tools-extra/clang-tidy/ClangTidy.cpp b/clang-tools-extra/clang-tidy/ClangTidy.cpp
index 0c6e18b26ad59..00030b733042a 100644
--- a/clang-tools-extra/clang-tidy/ClangTidy.cpp
+++ b/clang-tools-extra/clang-tidy/ClangTidy.cpp
@@ -350,7 +350,9 @@ ClangTidyASTConsumerFactory::ClangTidyASTConsumerFactory(
     IntrusiveRefCntPtr<llvm::vfs::OverlayFileSystem> OverlayFS)
     : Context(Context), OverlayFS(std::move(OverlayFS)),
       CheckFactories(new ClangTidyCheckFactories) {
+#if CLANG_TIDY_ENABLE_QUERY_BASED_CUSTOM_CHECKS
   custom::registerCustomChecks(Context.getOptions(), *CheckFactories);
+#endif
   for (ClangTidyModuleRegistry::entry E : ClangTidyModuleRegistry::entries()) {
     std::unique_ptr<ClangTidyModule> Module = E.instantiate();
     Module->addCheckFactories(*CheckFactories);
@@ -421,7 +423,9 @@ ClangTidyASTConsumerFactory::createASTConsumer(
                         .getCurrentWorkingDirectory();
   if (WorkingDir)
     Context.setCurrentBuildDirectory(WorkingDir.get());
+#if CLANG_TIDY_ENABLE_QUERY_BASED_CUSTOM_CHECKS
   custom::registerCustomChecks(Context.getOptions(), *CheckFactories);
+#endif
   std::vector<std::unique_ptr<ClangTidyCheck>> Checks =
       CheckFactories->createChecksForLanguage(&Context);
 
@@ -661,7 +665,9 @@ getAllChecksAndOptions(bool AllowEnablingAnalyzerAlphaCheckers) {
       std::make_unique<DefaultOptionsProvider>(ClangTidyGlobalOptions(), Opts),
       AllowEnablingAnalyzerAlphaCheckers);
   ClangTidyCheckFactories Factories;
+#if CLANG_TIDY_ENABLE_QUERY_BASED_CUSTOM_CHECKS
   custom::registerCustomChecks(Context.getOptions(), Factories);
+#endif
   for (const ClangTidyModuleRegistry::entry &Module :
        ClangTidyModuleRegistry::entries()) {
     Module.instantiate()->addCheckFactories(Factories);
diff --git a/clang-tools-extra/clang-tidy/ClangTidyForceLinker.h b/clang-tools-extra/clang-tidy/ClangTidyForceLinker.h
index 50ac6e138df18..cdf6ce2045a5d 100644
--- a/clang-tools-extra/clang-tidy/ClangTidyForceLinker.h
+++ b/clang-tools-extra/clang-tidy/ClangTidyForceLinker.h
@@ -54,10 +54,12 @@ extern volatile int CppCoreGuidelinesModuleAnchorSource;
 static int LLVM_ATTRIBUTE_UNUSED CppCoreGuidelinesModuleAnchorDestination =
     CppCoreGuidelinesModuleAnchorSource;
 
+#if CLANG_TIDY_ENABLE_QUERY_BASED_CUSTOM_CHECKS
 // This anchor is used to force the linker to link the CustomModule.
 extern volatile int CustomModuleAnchorSource;
 static int LLVM_ATTRIBUTE_UNUSED CustomModuleAnchorDestination =
     CustomModuleAnchorSource;
+#endif
 
 // This anchor is used to force the linker to link the DarwinModule.
 extern volatile int DarwinModuleAnchorSource;
diff --git a/clang-tools-extra/clang-tidy/clang-tidy-config.h.cmake b/clang-tools-extra/clang-tidy/clang-tidy-config.h.cmake
index f4d1a4b38004b..400e89ea60b33 100644
--- a/clang-tools-extra/clang-tidy/clang-tidy-config.h.cmake
+++ b/clang-tools-extra/clang-tidy/clang-tidy-config.h.cmake
@@ -6,5 +6,6 @@
 #define CLANG_TIDY_CONFIG_H
 
 #cmakedefine01 CLANG_TIDY_ENABLE_STATIC_ANALYZER
+#cmakedefine01 CLANG_TIDY_ENABLE_QUERY_BASED_CUSTOM_CHECKS
 
 #endif
diff --git a/clang-tools-extra/clang-tidy/custom/CMakeLists.txt b/clang-tools-extra/clang-tidy/custom/CMakeLists.txt
index 40387b73b5253..0b43387970903 100644
--- a/clang-tools-extra/clang-tidy/custom/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/custom/CMakeLists.txt
@@ -1,20 +1,22 @@
-set(LLVM_LINK_COMPONENTS
-  support
+if(CLANG_TIDY_ENABLE_QUERY_BASED_CUSTOM_CHECKS)
+  set(LLVM_LINK_COMPONENTS
+    support
   )
 
-add_clang_library(clangTidyCustomModule STATIC
-  CustomTidyModule.cpp
-  QueryCheck.cpp
+  add_clang_library(clangTidyCustomModule STATIC
+    CustomTidyModule.cpp
+    QueryCheck.cpp
 
-  LINK_LIBS
-  clangTidy
-  clangTidyUtils
+    LINK_LIBS
+    clangTidy
+    clangTidyUtils
 
-  DEPENDS
-  ClangDriverOptions
+    DEPENDS
+    ClangDriverOptions
   )
 
-clang_target_link_libraries(clangTidyCustomModule
-  PRIVATE
-  clangQuery
+  clang_target_link_libraries(clangTidyCustomModule
+    PRIVATE
+    clangQuery
   )
+endif()
diff --git a/clang-tools-extra/docs/clang-tidy/Contributing.rst b/clang-tools-extra/docs/clang-tidy/Contributing.rst
index 9611c655886f2..49621ea1bed04 100644
--- a/clang-tools-extra/docs/clang-tidy/Contributing.rst
+++ b/clang-tools-extra/docs/clang-tidy/Contributing.rst
@@ -33,6 +33,9 @@ If CMake is configured with ``CLANG_TIDY_ENABLE_STATIC_ANALYZER=NO``,
 :program:`clang-tidy` will not be built with support for the
 ``clang-analyzer-*`` checks or the ``mpi-*`` checks.
 
+If CMake is configured with ``CLANG_TIDY_ENABLE_QUERY_BASED_CUSTOM_CHECKS=NO``,
+:program:`clang-tidy` will not be built with support for query based checks. 
+
 
 .. _AST Matchers: https://clang.llvm.org/docs/LibASTMatchers.html
 .. _PPCallbacks: https://clang.llvm.org/doxygen/classclang_1_1PPCallbacks.html
diff --git a/clang-tools-extra/unittests/clang-tidy/CMakeLists.txt b/clang-tools-extra/unittests/clang-tidy/CMakeLists.txt
index 3304924d39757..a3f20f5c1359f 100644
--- a/clang-tools-extra/unittests/clang-tidy/CMakeLists.txt
+++ b/clang-tools-extra/unittests/clang-tidy/CMakeLists.txt
@@ -54,6 +54,7 @@ target_link_libraries(ClangTidyTests
   PRIVATE
   clangTidy
   clangTidyAndroidModule
+  clangTidyCustomModule
   clangTidyGoogleModule
   clangTidyMiscModule
   clangTidyLLVMModule

>From 8be412cdd2ccdf7a173706d9f909cecceb9d925c Mon Sep 17 00:00:00 2001
From: Congcong Cai <congcongcai0907 at 163.com>
Date: Fri, 13 Jun 2025 15:16:47 +0800
Subject: [PATCH 13/25] remove ERROR level

---
 clang-tools-extra/clang-tidy/ClangTidyOptions.cpp    |  1 -
 clang-tools-extra/clang-tidy/custom/QueryCheck.cpp   | 12 ++++++------
 .../docs/clang-tidy/QueryBasedCustomChecks.rst       |  6 +++---
 3 files changed, 9 insertions(+), 10 deletions(-)

diff --git a/clang-tools-extra/clang-tidy/ClangTidyOptions.cpp b/clang-tools-extra/clang-tidy/ClangTidyOptions.cpp
index 84f1108fcf311..646a3a0a08c8b 100644
--- a/clang-tools-extra/clang-tidy/ClangTidyOptions.cpp
+++ b/clang-tools-extra/clang-tidy/ClangTidyOptions.cpp
@@ -145,7 +145,6 @@ template <> struct BlockScalarTraits<MultiLineString> {
 
 template <> struct ScalarEnumerationTraits<clang::DiagnosticIDs::Level> {
   static void enumeration(IO &IO, clang::DiagnosticIDs::Level &Level) {
-    IO.enumCase(Level, "Error", clang::DiagnosticIDs::Level::Error);
     IO.enumCase(Level, "Warning", clang::DiagnosticIDs::Level::Warning);
     IO.enumCase(Level, "Note", clang::DiagnosticIDs::Level::Note);
   }
diff --git a/clang-tools-extra/clang-tidy/custom/QueryCheck.cpp b/clang-tools-extra/clang-tidy/custom/QueryCheck.cpp
index dc698d18be505..aca0d99c5331d 100644
--- a/clang-tools-extra/clang-tidy/custom/QueryCheck.cpp
+++ b/clang-tools-extra/clang-tidy/custom/QueryCheck.cpp
@@ -43,18 +43,18 @@ QueryCheck::QueryCheck(llvm::StringRef Name,
     query::QueryRef Q = query::QueryParser::parse(QueryStringRef, QS);
     switch (Q->Kind) {
     case query::QK_Match: {
-      const auto &MatchQuerry = llvm::cast<query::MatchQuery>(*Q);
-      Matchers.push_back(MatchQuerry.Matcher);
+      const auto &MatchQuery = llvm::cast<query::MatchQuery>(*Q);
+      Matchers.push_back(MatchQuery.Matcher);
       break;
     }
     case query::QK_Let: {
-      const auto &LetQuerry = llvm::cast<query::LetQuery>(*Q);
-      LetQuerry.run(llvm::errs(), QS);
+      const auto &LetQuery = llvm::cast<query::LetQuery>(*Q);
+      LetQuery.run(llvm::errs(), QS);
       break;
     }
     case query::QK_Invalid: {
-      const auto &InvalidQuerry = llvm::cast<query::InvalidQuery>(*Q);
-      Context->configurationDiag(InvalidQuerry.ErrStr);
+      const auto &InvalidQuery = llvm::cast<query::InvalidQuery>(*Q);
+      Context->configurationDiag(InvalidQuery.ErrStr);
       break;
     }
     // FIXME: TODO
diff --git a/clang-tools-extra/docs/clang-tidy/QueryBasedCustomChecks.rst b/clang-tools-extra/docs/clang-tidy/QueryBasedCustomChecks.rst
index a41837a73548e..aa2cede866c25 100644
--- a/clang-tools-extra/docs/clang-tidy/QueryBasedCustomChecks.rst
+++ b/clang-tools-extra/docs/clang-tidy/QueryBasedCustomChecks.rst
@@ -22,9 +22,9 @@ Configuration
   - Name: check name can been used in `-checks` option.
   - Query: query string
   - Diagnostic: list of diagnostics to be reported.
-     - BindName: name of the node to be bound in `Query`.
-     - Message: message to be reported.
-     - Level: severity of the diagnostic, the possible values are `Note`, `Warning`, `Error`.
+    - BindName: name of the node to be bound in `Query`.
+    - Message: message to be reported.
+    - Level: severity of the diagnostic, the possible values are `Note`, `Warning`.
 
 Example
 =======

>From 1367f0b30ac228307a3ec75885c9fb5f45b5c096 Mon Sep 17 00:00:00 2001
From: Congcong Cai <congcongcai0907 at 163.com>
Date: Fri, 13 Jun 2025 15:23:13 +0800
Subject: [PATCH 14/25] fix typo

---
 clang-tools-extra/clang-tidy/custom/QueryCheck.cpp              | 2 +-
 .../test/clang-tidy/checkers/custom/query-incorrect-query.cpp   | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/clang-tools-extra/clang-tidy/custom/QueryCheck.cpp b/clang-tools-extra/clang-tidy/custom/QueryCheck.cpp
index aca0d99c5331d..7821db3e2b5c0 100644
--- a/clang-tools-extra/clang-tidy/custom/QueryCheck.cpp
+++ b/clang-tools-extra/clang-tidy/custom/QueryCheck.cpp
@@ -67,7 +67,7 @@ QueryCheck::QueryCheck(llvm::StringRef Name,
     case query::QK_NoOp:
     case query::QK_Quit:
     case query::QK_SetBool: {
-      Context->configurationDiag("unsupported querry kind");
+      Context->configurationDiag("unsupported query kind");
     }
     }
     QueryStringRef = Q->RemainingContent;
diff --git a/clang-tools-extra/test/clang-tidy/checkers/custom/query-incorrect-query.cpp b/clang-tools-extra/test/clang-tidy/checkers/custom/query-incorrect-query.cpp
index cfc2753ca9be7..0101c6d260741 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/custom/query-incorrect-query.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/custom/query-incorrect-query.cpp
@@ -1,3 +1,3 @@
 // RUN: %check_clang_tidy %s custom-* %t --config-file=%S/Inputs/incorrect-clang-tidy.yml
 
-// CHECK-MESSAGES: warning: unsupported querry kind [clang-tidy-config]
+// CHECK-MESSAGES: warning: unsupported query kind [clang-tidy-config]

>From 595e0a670f72e7c687d95d20255255efdac87b55 Mon Sep 17 00:00:00 2001
From: Congcong Cai <congcongcai0907 at 163.com>
Date: Fri, 13 Jun 2025 16:07:03 +0800
Subject: [PATCH 15/25] test, doc

---
 .../clang-tidy/custom/QueryCheck.cpp          | 49 +++++++++++--------
 .../clang-tidy/QueryBasedCustomChecks.rst     |  6 +++
 .../custom/Inputs/incorrect-clang-tidy.yml    | 10 +++-
 .../checkers/custom/query-incorrect-query.cpp |  7 ++-
 4 files changed, 49 insertions(+), 23 deletions(-)

diff --git a/clang-tools-extra/clang-tidy/custom/QueryCheck.cpp b/clang-tools-extra/clang-tidy/custom/QueryCheck.cpp
index 7821db3e2b5c0..b7cf3493427b2 100644
--- a/clang-tools-extra/clang-tidy/custom/QueryCheck.cpp
+++ b/clang-tools-extra/clang-tidy/custom/QueryCheck.cpp
@@ -20,23 +20,10 @@ using namespace clang::ast_matchers;
 
 namespace clang::tidy::custom {
 
-QueryCheck::QueryCheck(llvm::StringRef Name,
-                       const ClangTidyOptions::CustomCheckValue &V,
-                       ClangTidyContext *Context)
-    : ClangTidyCheck(Name, Context) {
-  for (const ClangTidyOptions::CustomCheckDiag &D : V.Diags) {
-    auto DiagnosticIdIt =
-        Diags
-            .try_emplace(D.Level.value_or(DiagnosticIDs::Warning),
-                         llvm::StringMap<llvm::SmallVector<std::string>>{})
-            .first;
-    auto DiagMessageIt =
-        DiagnosticIdIt->getSecond()
-            .try_emplace(D.BindName, llvm::SmallVector<std::string>{})
-            .first;
-    DiagMessageIt->second.emplace_back(D.Message);
-  }
-
+static SmallVector<ast_matchers::dynamic::DynTypedMatcher>
+parseQuery(const ClangTidyOptions::CustomCheckValue &V,
+           ClangTidyContext *Context) {
+  SmallVector<ast_matchers::dynamic::DynTypedMatcher> Matchers{};
   clang::query::QuerySession QS({});
   llvm::StringRef QueryStringRef{V.Query};
   while (!QueryStringRef.empty()) {
@@ -54,8 +41,8 @@ QueryCheck::QueryCheck(llvm::StringRef Name,
     }
     case query::QK_Invalid: {
       const auto &InvalidQuery = llvm::cast<query::InvalidQuery>(*Q);
-      Context->configurationDiag(InvalidQuery.ErrStr);
-      break;
+      Context->configurationDiag(InvalidQuery.ErrStr, DiagnosticIDs::Error);
+      return {};
     }
     // FIXME: TODO
     case query::QK_File:
@@ -67,11 +54,33 @@ QueryCheck::QueryCheck(llvm::StringRef Name,
     case query::QK_NoOp:
     case query::QK_Quit:
     case query::QK_SetBool: {
-      Context->configurationDiag("unsupported query kind");
+      Context->configurationDiag("unsupported query kind",
+                                 DiagnosticIDs::Error);
+      return {};
     }
     }
     QueryStringRef = Q->RemainingContent;
   }
+  return Matchers;
+}
+
+QueryCheck::QueryCheck(llvm::StringRef Name,
+                       const ClangTidyOptions::CustomCheckValue &V,
+                       ClangTidyContext *Context)
+    : ClangTidyCheck(Name, Context) {
+  for (const ClangTidyOptions::CustomCheckDiag &D : V.Diags) {
+    auto DiagnosticIdIt =
+        Diags
+            .try_emplace(D.Level.value_or(DiagnosticIDs::Warning),
+                         llvm::StringMap<llvm::SmallVector<std::string>>{})
+            .first;
+    auto DiagMessageIt =
+        DiagnosticIdIt->getSecond()
+            .try_emplace(D.BindName, llvm::SmallVector<std::string>{})
+            .first;
+    DiagMessageIt->second.emplace_back(D.Message);
+  }
+  Matchers = parseQuery(V, Context);
 }
 
 void QueryCheck::registerMatchers(MatchFinder *Finder) {
diff --git a/clang-tools-extra/docs/clang-tidy/QueryBasedCustomChecks.rst b/clang-tools-extra/docs/clang-tidy/QueryBasedCustomChecks.rst
index aa2cede866c25..7a72081489195 100644
--- a/clang-tools-extra/docs/clang-tidy/QueryBasedCustomChecks.rst
+++ b/clang-tools-extra/docs/clang-tidy/QueryBasedCustomChecks.rst
@@ -15,6 +15,10 @@ enabled or disabled by the checks option like the built-in checks.
 Custom checks support inheritance from parent configurations like other
 configuration items.
 
+Goal: easy to write, cross platform, multiple versions supported toolkit for
+custom clang-tidy rules.
+Non-Goal: complex checks, performance, fix-its, etc.
+
 Configuration
 =============
 
@@ -26,6 +30,8 @@ Configuration
     - Message: message to be reported.
     - Level: severity of the diagnostic, the possible values are `Note`, `Warning`.
 
+CustomChecks can be configured by `Checks` option in the configuration file.
+
 Example
 =======
 
diff --git a/clang-tools-extra/test/clang-tidy/checkers/custom/Inputs/incorrect-clang-tidy.yml b/clang-tools-extra/test/clang-tidy/checkers/custom/Inputs/incorrect-clang-tidy.yml
index 4cad7364c1297..e5dfb5643a048 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/custom/Inputs/incorrect-clang-tidy.yml
+++ b/clang-tools-extra/test/clang-tidy/checkers/custom/Inputs/incorrect-clang-tidy.yml
@@ -1,5 +1,5 @@
 CustomChecks:
-  - Name: test-let-bind
+  - Name: test-let-bind-invalid
     Query: |
       let expr varDecl(isStaticStorageClass()).bind("vd")
       match expr
@@ -8,3 +8,11 @@ CustomChecks:
       - BindName: vd
         Message: find static variable
         Level: Warning
+  - Name: test-let-bind-valid
+    Query: |
+      let expr varDecl(isStaticStorageClass()).bind("vd")
+      match expr
+    Diagnostic:
+      - BindName: vd
+        Message: find static variable
+        Level: Warning
diff --git a/clang-tools-extra/test/clang-tidy/checkers/custom/query-incorrect-query.cpp b/clang-tools-extra/test/clang-tidy/checkers/custom/query-incorrect-query.cpp
index 0101c6d260741..643ee5a80d176 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/custom/query-incorrect-query.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/custom/query-incorrect-query.cpp
@@ -1,3 +1,6 @@
-// RUN: %check_clang_tidy %s custom-* %t --config-file=%S/Inputs/incorrect-clang-tidy.yml
+// RUN: not %check_clang_tidy %s custom-* %t --config-file=%S/Inputs/incorrect-clang-tidy.yml
 
-// CHECK-MESSAGES: warning: unsupported query kind [clang-tidy-config]
+// CHECK-MESSAGES: error: unsupported query kind [clang-tidy-config]
+
+static int S;
+// CHECK-MESSAGES: [[@LINE-1]]:1: warning: find static variable [custom-test-let-bind-valid]

>From 190ad52cfba90f6c348d6582f1b0cedcfe6d7667 Mon Sep 17 00:00:00 2001
From: Congcong Cai <congcongcai0907 at 163.com>
Date: Fri, 13 Jun 2025 16:41:57 +0800
Subject: [PATCH 16/25] typo

---
 clang-tools-extra/docs/clang-tidy/QueryBasedCustomChecks.rst | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/clang-tools-extra/docs/clang-tidy/QueryBasedCustomChecks.rst b/clang-tools-extra/docs/clang-tidy/QueryBasedCustomChecks.rst
index 7a72081489195..32e169ead7748 100644
--- a/clang-tools-extra/docs/clang-tidy/QueryBasedCustomChecks.rst
+++ b/clang-tools-extra/docs/clang-tidy/QueryBasedCustomChecks.rst
@@ -30,7 +30,7 @@ Configuration
     - Message: message to be reported.
     - Level: severity of the diagnostic, the possible values are `Note`, `Warning`.
 
-CustomChecks can be configured by `Checks` option in the configuration file.
+`CustomChecks` can be configured by `Checks` option in the configuration file.
 
 Example
 =======

>From 5500699951f4238acbe14d2a0a99682627033792 Mon Sep 17 00:00:00 2001
From: Congcong Cai <congcongcai0907 at 163.com>
Date: Mon, 16 Jun 2025 17:53:14 +0800
Subject: [PATCH 17/25] fix review

---
 clang-tools-extra/clang-tidy/ClangTidy.cpp    |  2 +-
 .../clang-tidy/ClangTidyModule.h              |  2 +-
 .../clang-tidy/ClangTidyOptions.h             |  1 -
 .../clang-tidy/custom/CustomTidyModule.cpp    |  8 +-
 .../clang-tidy/custom/QueryCheck.cpp          | 75 ++++++++++++++-----
 .../clang-tidy/custom/QueryCheck.h            |  5 +-
 clang-tools-extra/docs/ReleaseNotes.rst       |  5 --
 .../custom/Inputs/incorrect-clang-tidy.yml    |  9 ++-
 .../checkers/custom/query-incorrect-query.cpp |  5 +-
 9 files changed, 75 insertions(+), 37 deletions(-)

diff --git a/clang-tools-extra/clang-tidy/ClangTidy.cpp b/clang-tools-extra/clang-tidy/ClangTidy.cpp
index 690aae117d287..95726337df91f 100644
--- a/clang-tools-extra/clang-tidy/ClangTidy.cpp
+++ b/clang-tools-extra/clang-tidy/ClangTidy.cpp
@@ -54,7 +54,7 @@ LLVM_INSTANTIATE_REGISTRY(clang::tidy::ClangTidyModuleRegistry)
 namespace clang::tidy {
 
 namespace custom {
-extern void registerCustomChecks(ClangTidyOptions const &O,
+extern void registerCustomChecks(const ClangTidyOptions &O,
                                  ClangTidyCheckFactories &Factories);
 } // namespace custom
 
diff --git a/clang-tools-extra/clang-tidy/ClangTidyModule.h b/clang-tools-extra/clang-tidy/ClangTidyModule.h
index 6f0b2bf32a291..d818ea926a567 100644
--- a/clang-tools-extra/clang-tidy/ClangTidyModule.h
+++ b/clang-tools-extra/clang-tidy/ClangTidyModule.h
@@ -62,7 +62,7 @@ class ClangTidyCheckFactories {
                          });
   }
 
-  void erase(llvm::StringRef CheckName) { Factories.erase(CheckName); }
+  void eraseCheck(llvm::StringRef CheckName) { Factories.erase(CheckName); }
 
   /// Create instances of checks that are enabled.
   std::vector<std::unique_ptr<ClangTidyCheck>>
diff --git a/clang-tools-extra/clang-tidy/ClangTidyOptions.h b/clang-tools-extra/clang-tidy/ClangTidyOptions.h
index a114738fbd493..0f52925a7c765 100644
--- a/clang-tools-extra/clang-tidy/ClangTidyOptions.h
+++ b/clang-tools-extra/clang-tidy/ClangTidyOptions.h
@@ -18,7 +18,6 @@
 #include "llvm/Support/MemoryBufferRef.h"
 #include "llvm/Support/VirtualFileSystem.h"
 #include <functional>
-#include <map>
 #include <optional>
 #include <string>
 #include <system_error>
diff --git a/clang-tools-extra/clang-tidy/custom/CustomTidyModule.cpp b/clang-tools-extra/clang-tidy/custom/CustomTidyModule.cpp
index 3fe94d8e5e941..4ec9501dd4c2c 100644
--- a/clang-tools-extra/clang-tidy/custom/CustomTidyModule.cpp
+++ b/clang-tools-extra/clang-tidy/custom/CustomTidyModule.cpp
@@ -18,13 +18,13 @@ class CustomModule : public ClangTidyModule {
 
 // We need to register the checks more flexibly than builtin modules. The checks
 // will changed dynamically when switching to different source file.
-extern void registerCustomChecks(ClangTidyOptions const &Options,
+extern void registerCustomChecks(const ClangTidyOptions &Options,
                                  ClangTidyCheckFactories &Factories) {
   static llvm::SmallSet<llvm::SmallString<32>, 8> CustomCheckNames{};
   if (!Options.CustomChecks.has_value() || Options.CustomChecks->empty())
     return;
-  for (llvm::SmallString<32> const &Name : CustomCheckNames)
-    Factories.erase(Name);
+  for (const llvm::SmallString<32> &Name : CustomCheckNames)
+    Factories.eraseCheck(Name);
   for (const ClangTidyOptions::CustomCheckValue &V :
        Options.CustomChecks.value()) {
     llvm::SmallString<32> Name = llvm::StringRef{"custom-" + V.Name};
@@ -45,6 +45,6 @@ static ClangTidyModuleRegistry::Add<custom::CustomModule>
 
 // This anchor is used to force the linker to link in the generated object file
 // and thus register the AlteraModule.
-volatile int CustomModuleAnchorSource = 0;
+volatile int CustomModuleAnchorSource = 0; // NOLINT (misc-use-internal-linkage)
 
 } // namespace clang::tidy
diff --git a/clang-tools-extra/clang-tidy/custom/QueryCheck.cpp b/clang-tools-extra/clang-tidy/custom/QueryCheck.cpp
index b7cf3493427b2..f83c138fbfaf5 100644
--- a/clang-tools-extra/clang-tidy/custom/QueryCheck.cpp
+++ b/clang-tools-extra/clang-tidy/custom/QueryCheck.cpp
@@ -20,6 +20,12 @@ using namespace clang::ast_matchers;
 
 namespace clang::tidy::custom {
 
+static void emitConfigurationDiag(ClangTidyContext *Context, StringRef Message,
+                                  StringRef CheckName) {
+  Context->configurationDiag("%0 in '%1'", DiagnosticIDs::Warning)
+      << Message << CheckName;
+}
+
 static SmallVector<ast_matchers::dynamic::DynTypedMatcher>
 parseQuery(const ClangTidyOptions::CustomCheckValue &V,
            ClangTidyContext *Context) {
@@ -39,23 +45,52 @@ parseQuery(const ClangTidyOptions::CustomCheckValue &V,
       LetQuery.run(llvm::errs(), QS);
       break;
     }
+    case query::QK_NoOp: {
+      const auto &NoOpQuery = llvm::cast<query::NoOpQuery>(*Q);
+      NoOpQuery.run(llvm::errs(), QS);
+      break;
+    }
     case query::QK_Invalid: {
       const auto &InvalidQuery = llvm::cast<query::InvalidQuery>(*Q);
-      Context->configurationDiag(InvalidQuery.ErrStr, DiagnosticIDs::Error);
+      emitConfigurationDiag(Context, InvalidQuery.ErrStr, V.Name);
       return {};
     }
     // FIXME: TODO
-    case query::QK_File:
-    case query::QK_DisableOutputKind:
-    case query::QK_EnableOutputKind:
-    case query::QK_SetOutputKind:
-    case query::QK_SetTraversalKind:
-    case query::QK_Help:
-    case query::QK_NoOp:
-    case query::QK_Quit:
+    case query::QK_File: {
+      emitConfigurationDiag(Context, "unsupported query kind 'File'", V.Name);
+      return {};
+    }
+    case query::QK_DisableOutputKind: {
+      emitConfigurationDiag(
+          Context, "unsupported query kind 'DisableOutputKind'", V.Name);
+      return {};
+    }
+    case query::QK_EnableOutputKind: {
+      emitConfigurationDiag(
+          Context, "unsupported query kind 'EnableOutputKind'", V.Name);
+      return {};
+    }
+    case query::QK_SetOutputKind: {
+      emitConfigurationDiag(Context, "unsupported query kind 'SetOutputKind'",
+                            V.Name);
+      return {};
+    }
+    case query::QK_SetTraversalKind: {
+      emitConfigurationDiag(
+          Context, "unsupported query kind 'SetTraversalKind'", V.Name);
+      return {};
+    }
     case query::QK_SetBool: {
-      Context->configurationDiag("unsupported query kind",
-                                 DiagnosticIDs::Error);
+      emitConfigurationDiag(Context, "unsupported query kind 'SetBool'",
+                            V.Name);
+      return {};
+    }
+    case query::QK_Help: {
+      emitConfigurationDiag(Context, "unsupported query kind 'Help'", V.Name);
+      return {};
+    }
+    case query::QK_Quit: {
+      emitConfigurationDiag(Context, "unsupported query kind 'Quit'", V.Name);
       return {};
     }
     }
@@ -89,19 +124,19 @@ void QueryCheck::registerMatchers(MatchFinder *Finder) {
 }
 
 void QueryCheck::check(const MatchFinder::MatchResult &Result) {
-  auto Emit = [this](DiagMaps const &DiagMaps, std::string const &BindName,
-                     DynTypedNode const &Node, DiagnosticIDs::Level Level) {
-    if (!DiagMaps.contains(Level))
+  auto Emit = [this](const DiagMaps &DiagMaps, const std::string &BindName,
+                     const DynTypedNode &Node, DiagnosticIDs::Level Level) {
+    DiagMaps::const_iterator DiagMapIt = DiagMaps.find(Level);
+    if (DiagMapIt == DiagMaps.end())
       return;
-    auto &DiagMap = DiagMaps.at(Level);
-    if (!DiagMap.contains(BindName))
+    const BindNameMapToDiagMessage &BindNameMap = DiagMapIt->second;
+    BindNameMapToDiagMessage::const_iterator BindNameMapIt =
+        BindNameMap.find(BindName);
+    if (BindNameMapIt == BindNameMap.end())
       return;
-    for (const std::string &Message : DiagMap.at(BindName)) {
+    for (const std::string &Message : BindNameMapIt->second)
       diag(Node.getSourceRange().getBegin(), Message, Level);
-    }
   };
-  for (const auto &[Name, Node] : Result.Nodes.getMap())
-    Emit(Diags, Name, Node, DiagnosticIDs::Error);
   for (const auto &[Name, Node] : Result.Nodes.getMap())
     Emit(Diags, Name, Node, DiagnosticIDs::Warning);
   // place Note last, otherwise it will not be emitted
diff --git a/clang-tools-extra/clang-tidy/custom/QueryCheck.h b/clang-tools-extra/clang-tidy/custom/QueryCheck.h
index 1fd1d0ccb9ea0..4bc0638361895 100644
--- a/clang-tools-extra/clang-tidy/custom/QueryCheck.h
+++ b/clang-tools-extra/clang-tidy/custom/QueryCheck.h
@@ -29,9 +29,10 @@ class QueryCheck : public ClangTidyCheck {
 
 private:
   llvm::SmallVector<ast_matchers::dynamic::DynTypedMatcher> Matchers{};
+  using BindNameMapToDiagMessage =
+      llvm::StringMap<llvm::SmallVector<std::string>>;
   using DiagMaps =
-      llvm::DenseMap<DiagnosticIDs::Level,
-                     llvm::StringMap<llvm::SmallVector<std::string>>>;
+      llvm::DenseMap<DiagnosticIDs::Level, BindNameMapToDiagMessage>;
   DiagMaps Diags;
 };
 
diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst
index 19854ea96611e..c9dade560b7bc 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -104,11 +104,6 @@ Improvements to clang-tidy
   clauses. Added a ``--match-partial-fixes`` option to keep previous behavior on
   specific tests. This may break tests for users with custom out-of-tree checks
   who use :program:`check_clang_tidy.py` as-is.
-- :program:`clang-tidy` no longer processes declarations from system headers
-  by default, greatly improving performance. This behavior is disabled if the
-  `SystemHeaders` option is enabled.
-  Note: this may lead to false negatives; downstream users may need to adjust
-  their checks to preserve existing behavior.
 - :program:`clang-tidy` now supports query based custom checks by `CustomChecks`
   configuration option.
   :doc:`Query Based Custom Check Document <clang-tidy/QueryBasedCustomChecks>`
diff --git a/clang-tools-extra/test/clang-tidy/checkers/custom/Inputs/incorrect-clang-tidy.yml b/clang-tools-extra/test/clang-tidy/checkers/custom/Inputs/incorrect-clang-tidy.yml
index e5dfb5643a048..b94ba32997029 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/custom/Inputs/incorrect-clang-tidy.yml
+++ b/clang-tools-extra/test/clang-tidy/checkers/custom/Inputs/incorrect-clang-tidy.yml
@@ -1,5 +1,5 @@
 CustomChecks:
-  - Name: test-let-bind-invalid
+  - Name: test-let-bind-invalid-1
     Query: |
       let expr varDecl(isStaticStorageClass()).bind("vd")
       match expr
@@ -8,6 +8,13 @@ CustomChecks:
       - BindName: vd
         Message: find static variable
         Level: Warning
+  - Name: test-let-bind-invalid-2
+    Query: |
+      match varDeclInvalid(isStaticStorageClass()).bind("vd")
+    Diagnostic:
+      - BindName: vd
+        Message: find static variable
+        Level: Warning
   - Name: test-let-bind-valid
     Query: |
       let expr varDecl(isStaticStorageClass()).bind("vd")
diff --git a/clang-tools-extra/test/clang-tidy/checkers/custom/query-incorrect-query.cpp b/clang-tools-extra/test/clang-tidy/checkers/custom/query-incorrect-query.cpp
index 643ee5a80d176..f9a73750b4c3e 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/custom/query-incorrect-query.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/custom/query-incorrect-query.cpp
@@ -1,6 +1,7 @@
-// RUN: not %check_clang_tidy %s custom-* %t --config-file=%S/Inputs/incorrect-clang-tidy.yml
+// RUN: %check_clang_tidy %s custom-* %t --config-file=%S/Inputs/incorrect-clang-tidy.yml
 
-// CHECK-MESSAGES: error: unsupported query kind [clang-tidy-config]
+// CHECK-MESSAGES: warning: 1:1: Matcher not found: varDeclInvalid in 'test-let-bind-invalid-2' [clang-tidy-config]
+// CHECK-MESSAGES: warning: unsupported query kind 'SetOutputKind' in 'test-let-bind-invalid-1' [clang-tidy-config]
 
 static int S;
 // CHECK-MESSAGES: [[@LINE-1]]:1: warning: find static variable [custom-test-let-bind-valid]

>From 4ce48a5441dcd81f59d0c64c9dd113a0e348cf65 Mon Sep 17 00:00:00 2001
From: Congcong Cai <congcongcai0907 at 163.com>
Date: Mon, 16 Jun 2025 17:56:03 +0800
Subject: [PATCH 18/25] Apply suggestions from code review

Co-authored-by: Baranov Victor <bar.victor.2002 at gmail.com>
---
 clang-tools-extra/clang-tidy/custom/QueryCheck.h | 2 +-
 clang-tools-extra/docs/ReleaseNotes.rst          | 2 ++
 2 files changed, 3 insertions(+), 1 deletion(-)

diff --git a/clang-tools-extra/clang-tidy/custom/QueryCheck.h b/clang-tools-extra/clang-tidy/custom/QueryCheck.h
index 4bc0638361895..3dcdc518736c2 100644
--- a/clang-tools-extra/clang-tidy/custom/QueryCheck.h
+++ b/clang-tools-extra/clang-tidy/custom/QueryCheck.h
@@ -28,7 +28,7 @@ class QueryCheck : public ClangTidyCheck {
   void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
 
 private:
-  llvm::SmallVector<ast_matchers::dynamic::DynTypedMatcher> Matchers{};
+  llvm::SmallVector<ast_matchers::dynamic::DynTypedMatcher> Matchers;
   using BindNameMapToDiagMessage =
       llvm::StringMap<llvm::SmallVector<std::string>>;
   using DiagMaps =
diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst
index c9dade560b7bc..0fad932ea27cb 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -104,9 +104,11 @@ Improvements to clang-tidy
   clauses. Added a ``--match-partial-fixes`` option to keep previous behavior on
   specific tests. This may break tests for users with custom out-of-tree checks
   who use :program:`check_clang_tidy.py` as-is.
+
 - :program:`clang-tidy` now supports query based custom checks by `CustomChecks`
   configuration option.
   :doc:`Query Based Custom Check Document <clang-tidy/QueryBasedCustomChecks>`
+  
 - Improved :program:`clang-tidy-diff.py` script. Add the `-warnings-as-errors`
   argument to treat warnings as errors.
 

>From fd15b055d86d913cd130d6df85d45dad61dac70a Mon Sep 17 00:00:00 2001
From: Congcong Cai <congcongcai0907 at 163.com>
Date: Sun, 22 Jun 2025 22:24:54 +0800
Subject: [PATCH 19/25] Apply suggestions from code review

Co-authored-by: Baranov Victor <bar.victor.2002 at gmail.com>
---
 .../docs/clang-tidy/QueryBasedCustomChecks.rst            | 8 ++++----
 clang-tools-extra/docs/clang-tidy/index.rst               | 2 +-
 2 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/clang-tools-extra/docs/clang-tidy/QueryBasedCustomChecks.rst b/clang-tools-extra/docs/clang-tidy/QueryBasedCustomChecks.rst
index 32e169ead7748..0f8bcf1ec84be 100644
--- a/clang-tools-extra/docs/clang-tidy/QueryBasedCustomChecks.rst
+++ b/clang-tools-extra/docs/clang-tidy/QueryBasedCustomChecks.rst
@@ -15,16 +15,16 @@ enabled or disabled by the checks option like the built-in checks.
 Custom checks support inheritance from parent configurations like other
 configuration items.
 
-Goal: easy to write, cross platform, multiple versions supported toolkit for
+Goals: easy to write, cross platform, multiple versions supported toolkit for
 custom clang-tidy rules.
-Non-Goal: complex checks, performance, fix-its, etc.
+Non-Goals: complex checks, performance, fix-its, etc.
 
 Configuration
 =============
 
 `CustomChecks` is a list of custom checks. Each check must contain
-  - Name: check name can been used in `-checks` option.
-  - Query: query string
+  - Name: check name can be used in `-checks` option.
+  - Query: `clang-query` string
   - Diagnostic: list of diagnostics to be reported.
     - BindName: name of the node to be bound in `Query`.
     - Message: message to be reported.
diff --git a/clang-tools-extra/docs/clang-tidy/index.rst b/clang-tools-extra/docs/clang-tidy/index.rst
index 70f67582b1f5b..b218e0034825c 100644
--- a/clang-tools-extra/docs/clang-tidy/index.rst
+++ b/clang-tools-extra/docs/clang-tidy/index.rst
@@ -293,7 +293,7 @@ An overview of all the command-line options:
     Checks                       - Same as '--checks'. Additionally, the list of
                                    globs can be specified as a list instead of a
                                    string.
-    CustomChecks                 - Array of user defined checks based on
+    CustomChecks                 - List of user defined checks based on
                                    Clang-Query syntax.
     ExcludeHeaderFilterRegex     - Same as '--exclude-header-filter'.
     ExtraArgs                    - Same as '--extra-arg'.

>From b534fe9cc4af2250819d5194e2b4d9bf05a0c0bf Mon Sep 17 00:00:00 2001
From: Congcong Cai <congcongcai0907 at 163.com>
Date: Sat, 30 Aug 2025 17:39:14 +0800
Subject: [PATCH 20/25] add --enable-experimental-custom-checks

---
 clang-tools-extra/clang-tidy/ClangTidy.cpp    | 31 ++++++++++++-------
 clang-tools-extra/clang-tidy/ClangTidy.h      | 10 +++---
 .../ClangTidyDiagnosticConsumer.cpp           |  7 +++--
 .../clang-tidy/ClangTidyDiagnosticConsumer.h  | 15 +++++++--
 .../clang-tidy/custom/CustomTidyModule.cpp    |  1 +
 .../clang-tidy/tool/ClangTidyMain.cpp         | 26 +++++++++++-----
 .../clang-tidy/QueryBasedCustomChecks.rst     |  6 ++++
 .../test/clang-tidy/check_clang_tidy.py       |  1 +
 .../custom-query-check-not-enable.cpp         | 14 +++++++++
 .../infrastructure/custom-query-check.cpp     | 14 ++++-----
 .../unittests/clang-tidy/ClangTidyTest.h      |  3 +-
 11 files changed, 92 insertions(+), 36 deletions(-)
 create mode 100644 clang-tools-extra/test/clang-tidy/infrastructure/custom-query-check-not-enable.cpp

diff --git a/clang-tools-extra/clang-tidy/ClangTidy.cpp b/clang-tools-extra/clang-tidy/ClangTidy.cpp
index 793d0824cb883..92ef3bdf075e0 100644
--- a/clang-tools-extra/clang-tidy/ClangTidy.cpp
+++ b/clang-tools-extra/clang-tidy/ClangTidy.cpp
@@ -348,7 +348,8 @@ ClangTidyASTConsumerFactory::ClangTidyASTConsumerFactory(
     : Context(Context), OverlayFS(std::move(OverlayFS)),
       CheckFactories(new ClangTidyCheckFactories) {
 #if CLANG_TIDY_ENABLE_QUERY_BASED_CUSTOM_CHECKS
-  custom::registerCustomChecks(Context.getOptions(), *CheckFactories);
+  if (Context.canEnableExperimentalCustomChecks())
+    custom::registerCustomChecks(Context.getOptions(), *CheckFactories);
 #endif
   for (ClangTidyModuleRegistry::entry E : ClangTidyModuleRegistry::entries()) {
     std::unique_ptr<ClangTidyModule> Module = E.instantiate();
@@ -420,7 +421,8 @@ ClangTidyASTConsumerFactory::createASTConsumer(
   if (WorkingDir)
     Context.setCurrentBuildDirectory(WorkingDir.get());
 #if CLANG_TIDY_ENABLE_QUERY_BASED_CUSTOM_CHECKS
-  custom::registerCustomChecks(Context.getOptions(), *CheckFactories);
+  if (Context.canEnableExperimentalCustomChecks())
+    custom::registerCustomChecks(Context.getOptions(), *CheckFactories);
 #endif
   std::vector<std::unique_ptr<ClangTidyCheck>> Checks =
       CheckFactories->createChecksForLanguage(&Context);
@@ -507,13 +509,14 @@ ClangTidyOptions::OptionMap ClangTidyASTConsumerFactory::getCheckOptions() {
   return Options;
 }
 
-std::vector<std::string>
-getCheckNames(const ClangTidyOptions &Options,
-              bool AllowEnablingAnalyzerAlphaCheckers) {
+std::vector<std::string> getCheckNames(const ClangTidyOptions &Options,
+                                       bool AllowEnablingAnalyzerAlphaCheckers,
+                                       bool EnableExperimentalCustomChecks) {
   clang::tidy::ClangTidyContext Context(
       std::make_unique<DefaultOptionsProvider>(ClangTidyGlobalOptions(),
                                                Options),
-      AllowEnablingAnalyzerAlphaCheckers);
+      AllowEnablingAnalyzerAlphaCheckers, false,
+      EnableExperimentalCustomChecks);
   ClangTidyASTConsumerFactory Factory(Context);
   return Factory.getCheckNames();
 }
@@ -534,11 +537,13 @@ void filterCheckOptions(ClangTidyOptions &Options,
 
 ClangTidyOptions::OptionMap
 getCheckOptions(const ClangTidyOptions &Options,
-                bool AllowEnablingAnalyzerAlphaCheckers) {
+                bool AllowEnablingAnalyzerAlphaCheckers,
+                bool EnableExperimentalCustomChecks) {
   clang::tidy::ClangTidyContext Context(
       std::make_unique<DefaultOptionsProvider>(ClangTidyGlobalOptions(),
                                                Options),
-      AllowEnablingAnalyzerAlphaCheckers);
+      AllowEnablingAnalyzerAlphaCheckers, false,
+      EnableExperimentalCustomChecks);
   ClangTidyDiagnosticConsumer DiagConsumer(Context);
   auto DiagOpts = std::make_unique<DiagnosticOptions>();
   DiagnosticsEngine DE(llvm::makeIntrusiveRefCnt<DiagnosticIDs>(), *DiagOpts,
@@ -675,17 +680,19 @@ void exportReplacements(const llvm::StringRef MainFilePath,
   YAML << TUD;
 }
 
-ChecksAndOptions
-getAllChecksAndOptions(bool AllowEnablingAnalyzerAlphaCheckers) {
+ChecksAndOptions getAllChecksAndOptions(bool AllowEnablingAnalyzerAlphaCheckers,
+                                        bool EnableExperimentalCustomChecks) {
   ChecksAndOptions Result;
   ClangTidyOptions Opts;
   Opts.Checks = "*";
   clang::tidy::ClangTidyContext Context(
       std::make_unique<DefaultOptionsProvider>(ClangTidyGlobalOptions(), Opts),
-      AllowEnablingAnalyzerAlphaCheckers);
+      AllowEnablingAnalyzerAlphaCheckers, false,
+      EnableExperimentalCustomChecks);
   ClangTidyCheckFactories Factories;
 #if CLANG_TIDY_ENABLE_QUERY_BASED_CUSTOM_CHECKS
-  custom::registerCustomChecks(Context.getOptions(), Factories);
+  if (EnableExperimentalCustomChecks)
+    custom::registerCustomChecks(Context.getOptions(), Factories);
 #endif
   for (const ClangTidyModuleRegistry::entry &Module :
        ClangTidyModuleRegistry::entries()) {
diff --git a/clang-tools-extra/clang-tidy/ClangTidy.h b/clang-tools-extra/clang-tidy/ClangTidy.h
index d37d68ec0a5b9..921b5adc95700 100644
--- a/clang-tools-extra/clang-tidy/ClangTidy.h
+++ b/clang-tools-extra/clang-tidy/ClangTidy.h
@@ -56,15 +56,16 @@ class ClangTidyASTConsumerFactory {
 /// Fills the list of check names that are enabled when the provided
 /// filters are applied.
 std::vector<std::string> getCheckNames(const ClangTidyOptions &Options,
-                                       bool AllowEnablingAnalyzerAlphaCheckers);
+                                       bool AllowEnablingAnalyzerAlphaCheckers,
+                                       bool EnableExperimentalCustomChecks);
 
 struct ChecksAndOptions {
   llvm::StringSet<> Checks;
   llvm::StringSet<> Options;
 };
 
-ChecksAndOptions
-getAllChecksAndOptions(bool AllowEnablingAnalyzerAlphaCheckers = true);
+ChecksAndOptions getAllChecksAndOptions(bool AllowEnablingAnalyzerAlphaCheckers,
+                                        bool EnableExperimentalCustomChecks);
 
 /// Returns the effective check-specific options.
 ///
@@ -74,7 +75,8 @@ getAllChecksAndOptions(bool AllowEnablingAnalyzerAlphaCheckers = true);
 /// Options.
 ClangTidyOptions::OptionMap
 getCheckOptions(const ClangTidyOptions &Options,
-                bool AllowEnablingAnalyzerAlphaCheckers);
+                bool AllowEnablingAnalyzerAlphaCheckers,
+                bool EnableExperimentalCustomChecks);
 
 /// Filters CheckOptions in \p Options to only include options specified in
 /// the \p EnabledChecks which is a sorted vector.
diff --git a/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.cpp b/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.cpp
index fac6e0418d163..2644592f6d5d9 100644
--- a/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.cpp
+++ b/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.cpp
@@ -160,11 +160,12 @@ ClangTidyError::ClangTidyError(StringRef CheckName,
 
 ClangTidyContext::ClangTidyContext(
     std::unique_ptr<ClangTidyOptionsProvider> OptionsProvider,
-    bool AllowEnablingAnalyzerAlphaCheckers, bool EnableModuleHeadersParsing)
+    bool AllowEnablingAnalyzerAlphaCheckers, bool EnableModuleHeadersParsing,
+    bool EnableExperimentalCustomChecks)
     : OptionsProvider(std::move(OptionsProvider)),
-
       AllowEnablingAnalyzerAlphaCheckers(AllowEnablingAnalyzerAlphaCheckers),
-      EnableModuleHeadersParsing(EnableModuleHeadersParsing) {
+      EnableModuleHeadersParsing(EnableModuleHeadersParsing),
+      EnableExperimentalCustomChecks(EnableExperimentalCustomChecks) {
   // Before the first translation unit we can get errors related to command-line
   // parsing, use dummy string for the file name in this case.
   setCurrentFile("dummy");
diff --git a/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.h b/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.h
index 6e7cb7bb10e57..e0909c05e0389 100644
--- a/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.h
+++ b/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.h
@@ -19,6 +19,7 @@
 #include "llvm/ADT/StringSet.h"
 #include "llvm/Support/Regex.h"
 #include <optional>
+#include <utility>
 
 namespace clang {
 
@@ -68,10 +69,13 @@ struct ClangTidyStats {
 /// \endcode
 class ClangTidyContext {
 public:
+  ClangTidyContext(std::unique_ptr<ClangTidyOptionsProvider> OptionsProvider)
+      : ClangTidyContext(std::move(OptionsProvider), false, false, false) {}
   /// Initializes \c ClangTidyContext instance.
   ClangTidyContext(std::unique_ptr<ClangTidyOptionsProvider> OptionsProvider,
-                   bool AllowEnablingAnalyzerAlphaCheckers = false,
-                   bool EnableModuleHeadersParsing = false);
+                   bool AllowEnablingAnalyzerAlphaCheckers,
+                   bool EnableModuleHeadersParsing,
+                   bool EnableExperimentalCustomChecks);
   /// Sets the DiagnosticsEngine that diag() will emit diagnostics to.
   // FIXME: this is required initialization, and should be a constructor param.
   // Fix the context -> diag engine -> consumer -> context initialization cycle.
@@ -210,6 +214,12 @@ class ClangTidyContext {
     return EnableModuleHeadersParsing;
   }
 
+  // whether experimental custom checks can be enabled.
+  // enabled with `--enable-experimental-custom-checks`
+  bool canEnableExperimentalCustomChecks() const {
+    return EnableExperimentalCustomChecks;
+  }
+
   void setSelfContainedDiags(bool Value) { SelfContainedDiags = Value; }
 
   bool areDiagsSelfContained() const { return SelfContainedDiags; }
@@ -258,6 +268,7 @@ class ClangTidyContext {
 
   bool AllowEnablingAnalyzerAlphaCheckers;
   bool EnableModuleHeadersParsing;
+  bool EnableExperimentalCustomChecks;
 
   bool SelfContainedDiags = false;
 
diff --git a/clang-tools-extra/clang-tidy/custom/CustomTidyModule.cpp b/clang-tools-extra/clang-tidy/custom/CustomTidyModule.cpp
index 4ec9501dd4c2c..6aea3e4de4c6d 100644
--- a/clang-tools-extra/clang-tidy/custom/CustomTidyModule.cpp
+++ b/clang-tools-extra/clang-tidy/custom/CustomTidyModule.cpp
@@ -6,6 +6,7 @@
 #include "llvm/ADT/SmallSet.h"
 #include "llvm/ADT/SmallString.h"
 #include "llvm/ADT/StringRef.h"
+#include <cassert>
 #include <memory>
 
 namespace clang::tidy {
diff --git a/clang-tools-extra/clang-tidy/tool/ClangTidyMain.cpp b/clang-tools-extra/clang-tidy/tool/ClangTidyMain.cpp
index dc60e6099cdb2..ae4f3ef38f911 100644
--- a/clang-tools-extra/clang-tidy/tool/ClangTidyMain.cpp
+++ b/clang-tools-extra/clang-tidy/tool/ClangTidyMain.cpp
@@ -346,6 +346,16 @@ all of the checks.
 )"),
                                    cl::init(false), cl::cat(ClangTidyCategory));
 
+static cl::opt<bool>
+    EnableExperimentalCustomChecks("enable-experimental-custom-checks", desc(R"(
+Enable experimental clang-query based
+custom checks.
+see https://clang.llvm.org/extra/clang-tidy/QueryBasedCustomChecks.html.
+Note this function is still under development
+and the API is subject to change.
+)"),
+                                   cl::init(false), cl::cat(ClangTidyCategory));
+
 namespace clang::tidy {
 
 static void printStats(const ClangTidyStats &Stats) {
@@ -633,7 +643,8 @@ int clangTidyMain(int argc, const char **argv) {
   ClangTidyOptions EffectiveOptions = OptionsProvider->getOptions(FilePath);
 
   std::vector<std::string> EnabledChecks =
-      getCheckNames(EffectiveOptions, AllowEnablingAnalyzerAlphaCheckers);
+      getCheckNames(EffectiveOptions, AllowEnablingAnalyzerAlphaCheckers,
+                    EnableExperimentalCustomChecks);
 
   if (ExplainConfig) {
     // FIXME: Show other ClangTidyOptions' fields, like ExtraArg.
@@ -665,7 +676,8 @@ int clangTidyMain(int argc, const char **argv) {
 
   if (DumpConfig) {
     EffectiveOptions.CheckOptions =
-        getCheckOptions(EffectiveOptions, AllowEnablingAnalyzerAlphaCheckers);
+        getCheckOptions(EffectiveOptions, AllowEnablingAnalyzerAlphaCheckers,
+                        EnableExperimentalCustomChecks);
     ClangTidyOptions OptionsToDump =
         ClangTidyOptions::getDefaults().merge(EffectiveOptions, 0);
     filterCheckOptions(OptionsToDump, EnabledChecks);
@@ -676,8 +688,8 @@ int clangTidyMain(int argc, const char **argv) {
   if (VerifyConfig) {
     std::vector<ClangTidyOptionsProvider::OptionsSource> RawOptions =
         OptionsProvider->getRawOptions(FileName);
-    ChecksAndOptions Valid =
-        getAllChecksAndOptions(AllowEnablingAnalyzerAlphaCheckers);
+    ChecksAndOptions Valid = getAllChecksAndOptions(
+        AllowEnablingAnalyzerAlphaCheckers, EnableExperimentalCustomChecks);
     bool AnyInvalid = false;
     for (const auto &[Opts, Source] : RawOptions) {
       if (Opts.Checks)
@@ -714,9 +726,9 @@ int clangTidyMain(int argc, const char **argv) {
   llvm::InitializeAllTargetMCs();
   llvm::InitializeAllAsmParsers();
 
-  ClangTidyContext Context(std::move(OwningOptionsProvider),
-                           AllowEnablingAnalyzerAlphaCheckers,
-                           EnableModuleHeadersParsing);
+  ClangTidyContext Context(
+      std::move(OwningOptionsProvider), AllowEnablingAnalyzerAlphaCheckers,
+      EnableModuleHeadersParsing, EnableExperimentalCustomChecks);
   std::vector<ClangTidyError> Errors =
       runClangTidy(Context, OptionsParser->getCompilations(), PathList, BaseFS,
                    FixNotes, EnableCheckProfile, ProfilePrefix, Quiet);
diff --git a/clang-tools-extra/docs/clang-tidy/QueryBasedCustomChecks.rst b/clang-tools-extra/docs/clang-tidy/QueryBasedCustomChecks.rst
index 0f8bcf1ec84be..acedb7557b71a 100644
--- a/clang-tools-extra/docs/clang-tidy/QueryBasedCustomChecks.rst
+++ b/clang-tools-extra/docs/clang-tidy/QueryBasedCustomChecks.rst
@@ -35,6 +35,12 @@ Configuration
 Example
 =======
 
+Note: Since this feature is currently in the development stage. The API may
+change in the future. It needs to be explicitly enabled by
+`--enable-experimental-custom-checks`.
+
+We also welcome suggestions in the link https://discourse.llvm.org/t/support-query-based-clang-tidy-external-check/85331.
+
 .. code-block:: yaml
 
   Checks: -*,custom-call-main-function
diff --git a/clang-tools-extra/test/clang-tidy/check_clang_tidy.py b/clang-tools-extra/test/clang-tidy/check_clang_tidy.py
index d80a28044ea0a..f3f0bc58b0d45 100755
--- a/clang-tools-extra/test/clang-tidy/check_clang_tidy.py
+++ b/clang-tools-extra/test/clang-tidy/check_clang_tidy.py
@@ -209,6 +209,7 @@ def run_clang_tidy(self) -> str:
         args = (
             [
                 "clang-tidy",
+                "--enable-experimental-custom-checks",
                 self.temp_file_name,
             ]
             + [
diff --git a/clang-tools-extra/test/clang-tidy/infrastructure/custom-query-check-not-enable.cpp b/clang-tools-extra/test/clang-tidy/infrastructure/custom-query-check-not-enable.cpp
new file mode 100644
index 0000000000000..dc160fbbbcd9e
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/infrastructure/custom-query-check-not-enable.cpp
@@ -0,0 +1,14 @@
+// RUN: sed -e "s:INPUT_DIR:%S/Inputs/custom-query-check:g" -e "s:OUT_DIR:%t:g" -e "s:MAIN_FILE:%s:g" %S/Inputs/custom-query-check/vfsoverlay.yaml > %t.yaml
+// RUN: clang-tidy --allow-no-checks %t/main.cpp -checks='-*,custom-*' -vfsoverlay %t.yaml | FileCheck %s --check-prefix=CHECK
+// RUN: clang-tidy --allow-no-checks %t/main.cpp -checks='-*,custom-*' -vfsoverlay %t.yaml --list-checks | FileCheck %s --check-prefix=LIST-CHECK
+// REQUIRES: shell
+
+
+long V;
+// CHECK: No checks enabled.
+
+void f();
+// CHECK-SUB-DIR-APPEND: [[@LINE-1]]:1: warning: find function decl [custom-function-decl]
+
+// LIST-CHECK: Enabled checks:
+// LIST-CHECK-EMPTY:
diff --git a/clang-tools-extra/test/clang-tidy/infrastructure/custom-query-check.cpp b/clang-tools-extra/test/clang-tidy/infrastructure/custom-query-check.cpp
index 13834660385d1..1d23349afa1fc 100644
--- a/clang-tools-extra/test/clang-tidy/infrastructure/custom-query-check.cpp
+++ b/clang-tools-extra/test/clang-tidy/infrastructure/custom-query-check.cpp
@@ -1,11 +1,11 @@
 // RUN: sed -e "s:INPUT_DIR:%S/Inputs/custom-query-check:g" -e "s:OUT_DIR:%t:g" -e "s:MAIN_FILE:%s:g" %S/Inputs/custom-query-check/vfsoverlay.yaml > %t.yaml
-// RUN: clang-tidy %t/main.cpp -checks='-*,custom-*' -vfsoverlay %t.yaml | FileCheck %s --check-prefix=CHECK-SAME-DIR
-// RUN: clang-tidy %t/subdir/main.cpp -checks='-*,custom-*' -vfsoverlay %t.yaml | FileCheck %s --check-prefix=CHECK-SUB-DIR-BASE
-// RUN: clang-tidy %t/subdir-override/main.cpp -checks='-*,custom-*' -vfsoverlay %t.yaml | FileCheck %s --check-prefix=CHECK-SUB-DIR-OVERRIDE
-// RUN: clang-tidy %t/subdir-empty/main.cpp -checks='-*,custom-*' -vfsoverlay %t.yaml --allow-no-checks | FileCheck %s --check-prefix=CHECK-SUB-DIR-EMPTY
-// RUN: clang-tidy %t/subdir-append/main.cpp -checks='-*,custom-*' -vfsoverlay %t.yaml | FileCheck %s --check-prefix=CHECK-SUB-DIR-APPEND
-// RUN: clang-tidy %t/subdir-append/main.cpp -checks='-*,custom-*' -vfsoverlay %t.yaml --list-checks | FileCheck %s --check-prefix=LIST-CHECK
-// RUN: clang-tidy %t/subdir-append/main.cpp -checks='-*,custom-*' -vfsoverlay %t.yaml --dump-config | FileCheck %s --check-prefix=DUMP-CONFIG
+// RUN: clang-tidy --enable-experimental-custom-checks %t/main.cpp -checks='-*,custom-*' -vfsoverlay %t.yaml | FileCheck %s --check-prefix=CHECK-SAME-DIR
+// RUN: clang-tidy --enable-experimental-custom-checks %t/subdir/main.cpp -checks='-*,custom-*' -vfsoverlay %t.yaml | FileCheck %s --check-prefix=CHECK-SUB-DIR-BASE
+// RUN: clang-tidy --enable-experimental-custom-checks %t/subdir-override/main.cpp -checks='-*,custom-*' -vfsoverlay %t.yaml | FileCheck %s --check-prefix=CHECK-SUB-DIR-OVERRIDE
+// RUN: clang-tidy --enable-experimental-custom-checks %t/subdir-empty/main.cpp -checks='-*,custom-*' -vfsoverlay %t.yaml --allow-no-checks | FileCheck %s --check-prefix=CHECK-SUB-DIR-EMPTY
+// RUN: clang-tidy --enable-experimental-custom-checks %t/subdir-append/main.cpp -checks='-*,custom-*' -vfsoverlay %t.yaml | FileCheck %s --check-prefix=CHECK-SUB-DIR-APPEND
+// RUN: clang-tidy --enable-experimental-custom-checks %t/subdir-append/main.cpp -checks='-*,custom-*' -vfsoverlay %t.yaml --list-checks | FileCheck %s --check-prefix=LIST-CHECK
+// RUN: clang-tidy --enable-experimental-custom-checks %t/subdir-append/main.cpp -checks='-*,custom-*' -vfsoverlay %t.yaml --dump-config | FileCheck %s --check-prefix=DUMP-CONFIG
 // REQUIRES: shell
 
 
diff --git a/clang-tools-extra/unittests/clang-tidy/ClangTidyTest.h b/clang-tools-extra/unittests/clang-tidy/ClangTidyTest.h
index 89f0f9f2a1f19..195d6a6c60918 100644
--- a/clang-tools-extra/unittests/clang-tidy/ClangTidyTest.h
+++ b/clang-tools-extra/unittests/clang-tidy/ClangTidyTest.h
@@ -94,7 +94,8 @@ runCheckOnCode(StringRef Code, std::vector<ClangTidyError> *Errors = nullptr,
   ClangTidyOptions Options = ExtraOptions;
   Options.Checks = "*";
   ClangTidyContext Context(std::make_unique<DefaultOptionsProvider>(
-      ClangTidyGlobalOptions(), Options));
+                               ClangTidyGlobalOptions(), Options),
+                           false, false, false);
   ClangTidyDiagnosticConsumer DiagConsumer(Context);
   auto DiagOpts = std::make_unique<DiagnosticOptions>();
   DiagnosticsEngine DE(DiagnosticIDs::create(), *DiagOpts, &DiagConsumer,

>From a373c55ff7a0a06597d900614a2c3b73ad382e46 Mon Sep 17 00:00:00 2001
From: Congcong Cai <congcongcai0907 at 163.com>
Date: Sat, 30 Aug 2025 17:41:58 +0800
Subject: [PATCH 21/25] rename

---
 clang-tools-extra/clang-tidy/ClangTidy.cpp     | 18 +++++++++---------
 clang-tools-extra/clang-tidy/ClangTidy.h       |  6 +++---
 .../clang-tidy/ClangTidyDiagnosticConsumer.cpp |  4 ++--
 .../clang-tidy/ClangTidyDiagnosticConsumer.h   | 10 +++++-----
 .../clang-tidy/tool/ClangTidyMain.cpp          | 12 +++++-------
 .../docs/clang-tidy/QueryBasedCustomChecks.rst |  2 +-
 .../test/clang-tidy/check_clang_tidy.py        |  2 +-
 .../infrastructure/custom-query-check.cpp      | 14 +++++++-------
 8 files changed, 33 insertions(+), 35 deletions(-)

diff --git a/clang-tools-extra/clang-tidy/ClangTidy.cpp b/clang-tools-extra/clang-tidy/ClangTidy.cpp
index 92ef3bdf075e0..cee5c15968e64 100644
--- a/clang-tools-extra/clang-tidy/ClangTidy.cpp
+++ b/clang-tools-extra/clang-tidy/ClangTidy.cpp
@@ -348,7 +348,7 @@ ClangTidyASTConsumerFactory::ClangTidyASTConsumerFactory(
     : Context(Context), OverlayFS(std::move(OverlayFS)),
       CheckFactories(new ClangTidyCheckFactories) {
 #if CLANG_TIDY_ENABLE_QUERY_BASED_CUSTOM_CHECKS
-  if (Context.canEnableExperimentalCustomChecks())
+  if (Context.canExperimentalCustomChecks())
     custom::registerCustomChecks(Context.getOptions(), *CheckFactories);
 #endif
   for (ClangTidyModuleRegistry::entry E : ClangTidyModuleRegistry::entries()) {
@@ -421,7 +421,7 @@ ClangTidyASTConsumerFactory::createASTConsumer(
   if (WorkingDir)
     Context.setCurrentBuildDirectory(WorkingDir.get());
 #if CLANG_TIDY_ENABLE_QUERY_BASED_CUSTOM_CHECKS
-  if (Context.canEnableExperimentalCustomChecks())
+  if (Context.canExperimentalCustomChecks())
     custom::registerCustomChecks(Context.getOptions(), *CheckFactories);
 #endif
   std::vector<std::unique_ptr<ClangTidyCheck>> Checks =
@@ -511,12 +511,12 @@ ClangTidyOptions::OptionMap ClangTidyASTConsumerFactory::getCheckOptions() {
 
 std::vector<std::string> getCheckNames(const ClangTidyOptions &Options,
                                        bool AllowEnablingAnalyzerAlphaCheckers,
-                                       bool EnableExperimentalCustomChecks) {
+                                       bool ExperimentalCustomChecks) {
   clang::tidy::ClangTidyContext Context(
       std::make_unique<DefaultOptionsProvider>(ClangTidyGlobalOptions(),
                                                Options),
       AllowEnablingAnalyzerAlphaCheckers, false,
-      EnableExperimentalCustomChecks);
+      ExperimentalCustomChecks);
   ClangTidyASTConsumerFactory Factory(Context);
   return Factory.getCheckNames();
 }
@@ -538,12 +538,12 @@ void filterCheckOptions(ClangTidyOptions &Options,
 ClangTidyOptions::OptionMap
 getCheckOptions(const ClangTidyOptions &Options,
                 bool AllowEnablingAnalyzerAlphaCheckers,
-                bool EnableExperimentalCustomChecks) {
+                bool ExperimentalCustomChecks) {
   clang::tidy::ClangTidyContext Context(
       std::make_unique<DefaultOptionsProvider>(ClangTidyGlobalOptions(),
                                                Options),
       AllowEnablingAnalyzerAlphaCheckers, false,
-      EnableExperimentalCustomChecks);
+      ExperimentalCustomChecks);
   ClangTidyDiagnosticConsumer DiagConsumer(Context);
   auto DiagOpts = std::make_unique<DiagnosticOptions>();
   DiagnosticsEngine DE(llvm::makeIntrusiveRefCnt<DiagnosticIDs>(), *DiagOpts,
@@ -681,17 +681,17 @@ void exportReplacements(const llvm::StringRef MainFilePath,
 }
 
 ChecksAndOptions getAllChecksAndOptions(bool AllowEnablingAnalyzerAlphaCheckers,
-                                        bool EnableExperimentalCustomChecks) {
+                                        bool ExperimentalCustomChecks) {
   ChecksAndOptions Result;
   ClangTidyOptions Opts;
   Opts.Checks = "*";
   clang::tidy::ClangTidyContext Context(
       std::make_unique<DefaultOptionsProvider>(ClangTidyGlobalOptions(), Opts),
       AllowEnablingAnalyzerAlphaCheckers, false,
-      EnableExperimentalCustomChecks);
+      ExperimentalCustomChecks);
   ClangTidyCheckFactories Factories;
 #if CLANG_TIDY_ENABLE_QUERY_BASED_CUSTOM_CHECKS
-  if (EnableExperimentalCustomChecks)
+  if (ExperimentalCustomChecks)
     custom::registerCustomChecks(Context.getOptions(), Factories);
 #endif
   for (const ClangTidyModuleRegistry::entry &Module :
diff --git a/clang-tools-extra/clang-tidy/ClangTidy.h b/clang-tools-extra/clang-tidy/ClangTidy.h
index 921b5adc95700..7e852b65b658b 100644
--- a/clang-tools-extra/clang-tidy/ClangTidy.h
+++ b/clang-tools-extra/clang-tidy/ClangTidy.h
@@ -57,7 +57,7 @@ class ClangTidyASTConsumerFactory {
 /// filters are applied.
 std::vector<std::string> getCheckNames(const ClangTidyOptions &Options,
                                        bool AllowEnablingAnalyzerAlphaCheckers,
-                                       bool EnableExperimentalCustomChecks);
+                                       bool ExperimentalCustomChecks);
 
 struct ChecksAndOptions {
   llvm::StringSet<> Checks;
@@ -65,7 +65,7 @@ struct ChecksAndOptions {
 };
 
 ChecksAndOptions getAllChecksAndOptions(bool AllowEnablingAnalyzerAlphaCheckers,
-                                        bool EnableExperimentalCustomChecks);
+                                        bool ExperimentalCustomChecks);
 
 /// Returns the effective check-specific options.
 ///
@@ -76,7 +76,7 @@ ChecksAndOptions getAllChecksAndOptions(bool AllowEnablingAnalyzerAlphaCheckers,
 ClangTidyOptions::OptionMap
 getCheckOptions(const ClangTidyOptions &Options,
                 bool AllowEnablingAnalyzerAlphaCheckers,
-                bool EnableExperimentalCustomChecks);
+                bool ExperimentalCustomChecks);
 
 /// Filters CheckOptions in \p Options to only include options specified in
 /// the \p EnabledChecks which is a sorted vector.
diff --git a/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.cpp b/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.cpp
index 2644592f6d5d9..54c1e593e14db 100644
--- a/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.cpp
+++ b/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.cpp
@@ -161,11 +161,11 @@ ClangTidyError::ClangTidyError(StringRef CheckName,
 ClangTidyContext::ClangTidyContext(
     std::unique_ptr<ClangTidyOptionsProvider> OptionsProvider,
     bool AllowEnablingAnalyzerAlphaCheckers, bool EnableModuleHeadersParsing,
-    bool EnableExperimentalCustomChecks)
+    bool ExperimentalCustomChecks)
     : OptionsProvider(std::move(OptionsProvider)),
       AllowEnablingAnalyzerAlphaCheckers(AllowEnablingAnalyzerAlphaCheckers),
       EnableModuleHeadersParsing(EnableModuleHeadersParsing),
-      EnableExperimentalCustomChecks(EnableExperimentalCustomChecks) {
+      ExperimentalCustomChecks(ExperimentalCustomChecks) {
   // Before the first translation unit we can get errors related to command-line
   // parsing, use dummy string for the file name in this case.
   setCurrentFile("dummy");
diff --git a/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.h b/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.h
index e0909c05e0389..cbded96e800e3 100644
--- a/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.h
+++ b/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.h
@@ -75,7 +75,7 @@ class ClangTidyContext {
   ClangTidyContext(std::unique_ptr<ClangTidyOptionsProvider> OptionsProvider,
                    bool AllowEnablingAnalyzerAlphaCheckers,
                    bool EnableModuleHeadersParsing,
-                   bool EnableExperimentalCustomChecks);
+                   bool ExperimentalCustomChecks);
   /// Sets the DiagnosticsEngine that diag() will emit diagnostics to.
   // FIXME: this is required initialization, and should be a constructor param.
   // Fix the context -> diag engine -> consumer -> context initialization cycle.
@@ -215,9 +215,9 @@ class ClangTidyContext {
   }
 
   // whether experimental custom checks can be enabled.
-  // enabled with `--enable-experimental-custom-checks`
-  bool canEnableExperimentalCustomChecks() const {
-    return EnableExperimentalCustomChecks;
+  // enabled with `--experimental-custom-checks`
+  bool canExperimentalCustomChecks() const {
+    return ExperimentalCustomChecks;
   }
 
   void setSelfContainedDiags(bool Value) { SelfContainedDiags = Value; }
@@ -268,7 +268,7 @@ class ClangTidyContext {
 
   bool AllowEnablingAnalyzerAlphaCheckers;
   bool EnableModuleHeadersParsing;
-  bool EnableExperimentalCustomChecks;
+  bool ExperimentalCustomChecks;
 
   bool SelfContainedDiags = false;
 
diff --git a/clang-tools-extra/clang-tidy/tool/ClangTidyMain.cpp b/clang-tools-extra/clang-tidy/tool/ClangTidyMain.cpp
index ae4f3ef38f911..5b5caed2e5d7e 100644
--- a/clang-tools-extra/clang-tidy/tool/ClangTidyMain.cpp
+++ b/clang-tools-extra/clang-tidy/tool/ClangTidyMain.cpp
@@ -347,12 +347,10 @@ all of the checks.
                                    cl::init(false), cl::cat(ClangTidyCategory));
 
 static cl::opt<bool>
-    EnableExperimentalCustomChecks("enable-experimental-custom-checks", desc(R"(
+    ExperimentalCustomChecks("experimental-custom-checks", desc(R"(
 Enable experimental clang-query based
 custom checks.
 see https://clang.llvm.org/extra/clang-tidy/QueryBasedCustomChecks.html.
-Note this function is still under development
-and the API is subject to change.
 )"),
                                    cl::init(false), cl::cat(ClangTidyCategory));
 
@@ -644,7 +642,7 @@ int clangTidyMain(int argc, const char **argv) {
 
   std::vector<std::string> EnabledChecks =
       getCheckNames(EffectiveOptions, AllowEnablingAnalyzerAlphaCheckers,
-                    EnableExperimentalCustomChecks);
+                    ExperimentalCustomChecks);
 
   if (ExplainConfig) {
     // FIXME: Show other ClangTidyOptions' fields, like ExtraArg.
@@ -677,7 +675,7 @@ int clangTidyMain(int argc, const char **argv) {
   if (DumpConfig) {
     EffectiveOptions.CheckOptions =
         getCheckOptions(EffectiveOptions, AllowEnablingAnalyzerAlphaCheckers,
-                        EnableExperimentalCustomChecks);
+                        ExperimentalCustomChecks);
     ClangTidyOptions OptionsToDump =
         ClangTidyOptions::getDefaults().merge(EffectiveOptions, 0);
     filterCheckOptions(OptionsToDump, EnabledChecks);
@@ -689,7 +687,7 @@ int clangTidyMain(int argc, const char **argv) {
     std::vector<ClangTidyOptionsProvider::OptionsSource> RawOptions =
         OptionsProvider->getRawOptions(FileName);
     ChecksAndOptions Valid = getAllChecksAndOptions(
-        AllowEnablingAnalyzerAlphaCheckers, EnableExperimentalCustomChecks);
+        AllowEnablingAnalyzerAlphaCheckers, ExperimentalCustomChecks);
     bool AnyInvalid = false;
     for (const auto &[Opts, Source] : RawOptions) {
       if (Opts.Checks)
@@ -728,7 +726,7 @@ int clangTidyMain(int argc, const char **argv) {
 
   ClangTidyContext Context(
       std::move(OwningOptionsProvider), AllowEnablingAnalyzerAlphaCheckers,
-      EnableModuleHeadersParsing, EnableExperimentalCustomChecks);
+      EnableModuleHeadersParsing, ExperimentalCustomChecks);
   std::vector<ClangTidyError> Errors =
       runClangTidy(Context, OptionsParser->getCompilations(), PathList, BaseFS,
                    FixNotes, EnableCheckProfile, ProfilePrefix, Quiet);
diff --git a/clang-tools-extra/docs/clang-tidy/QueryBasedCustomChecks.rst b/clang-tools-extra/docs/clang-tidy/QueryBasedCustomChecks.rst
index acedb7557b71a..45ce87e26e8e3 100644
--- a/clang-tools-extra/docs/clang-tidy/QueryBasedCustomChecks.rst
+++ b/clang-tools-extra/docs/clang-tidy/QueryBasedCustomChecks.rst
@@ -37,7 +37,7 @@ Example
 
 Note: Since this feature is currently in the development stage. The API may
 change in the future. It needs to be explicitly enabled by
-`--enable-experimental-custom-checks`.
+`--experimental-custom-checks`.
 
 We also welcome suggestions in the link https://discourse.llvm.org/t/support-query-based-clang-tidy-external-check/85331.
 
diff --git a/clang-tools-extra/test/clang-tidy/check_clang_tidy.py b/clang-tools-extra/test/clang-tidy/check_clang_tidy.py
index f3f0bc58b0d45..719c38c90f6be 100755
--- a/clang-tools-extra/test/clang-tidy/check_clang_tidy.py
+++ b/clang-tools-extra/test/clang-tidy/check_clang_tidy.py
@@ -209,7 +209,7 @@ def run_clang_tidy(self) -> str:
         args = (
             [
                 "clang-tidy",
-                "--enable-experimental-custom-checks",
+                "--experimental-custom-checks",
                 self.temp_file_name,
             ]
             + [
diff --git a/clang-tools-extra/test/clang-tidy/infrastructure/custom-query-check.cpp b/clang-tools-extra/test/clang-tidy/infrastructure/custom-query-check.cpp
index 1d23349afa1fc..8d0bc2bed180d 100644
--- a/clang-tools-extra/test/clang-tidy/infrastructure/custom-query-check.cpp
+++ b/clang-tools-extra/test/clang-tidy/infrastructure/custom-query-check.cpp
@@ -1,11 +1,11 @@
 // RUN: sed -e "s:INPUT_DIR:%S/Inputs/custom-query-check:g" -e "s:OUT_DIR:%t:g" -e "s:MAIN_FILE:%s:g" %S/Inputs/custom-query-check/vfsoverlay.yaml > %t.yaml
-// RUN: clang-tidy --enable-experimental-custom-checks %t/main.cpp -checks='-*,custom-*' -vfsoverlay %t.yaml | FileCheck %s --check-prefix=CHECK-SAME-DIR
-// RUN: clang-tidy --enable-experimental-custom-checks %t/subdir/main.cpp -checks='-*,custom-*' -vfsoverlay %t.yaml | FileCheck %s --check-prefix=CHECK-SUB-DIR-BASE
-// RUN: clang-tidy --enable-experimental-custom-checks %t/subdir-override/main.cpp -checks='-*,custom-*' -vfsoverlay %t.yaml | FileCheck %s --check-prefix=CHECK-SUB-DIR-OVERRIDE
-// RUN: clang-tidy --enable-experimental-custom-checks %t/subdir-empty/main.cpp -checks='-*,custom-*' -vfsoverlay %t.yaml --allow-no-checks | FileCheck %s --check-prefix=CHECK-SUB-DIR-EMPTY
-// RUN: clang-tidy --enable-experimental-custom-checks %t/subdir-append/main.cpp -checks='-*,custom-*' -vfsoverlay %t.yaml | FileCheck %s --check-prefix=CHECK-SUB-DIR-APPEND
-// RUN: clang-tidy --enable-experimental-custom-checks %t/subdir-append/main.cpp -checks='-*,custom-*' -vfsoverlay %t.yaml --list-checks | FileCheck %s --check-prefix=LIST-CHECK
-// RUN: clang-tidy --enable-experimental-custom-checks %t/subdir-append/main.cpp -checks='-*,custom-*' -vfsoverlay %t.yaml --dump-config | FileCheck %s --check-prefix=DUMP-CONFIG
+// RUN: clang-tidy --experimental-custom-checks %t/main.cpp -checks='-*,custom-*' -vfsoverlay %t.yaml | FileCheck %s --check-prefix=CHECK-SAME-DIR
+// RUN: clang-tidy --experimental-custom-checks %t/subdir/main.cpp -checks='-*,custom-*' -vfsoverlay %t.yaml | FileCheck %s --check-prefix=CHECK-SUB-DIR-BASE
+// RUN: clang-tidy --experimental-custom-checks %t/subdir-override/main.cpp -checks='-*,custom-*' -vfsoverlay %t.yaml | FileCheck %s --check-prefix=CHECK-SUB-DIR-OVERRIDE
+// RUN: clang-tidy --experimental-custom-checks %t/subdir-empty/main.cpp -checks='-*,custom-*' -vfsoverlay %t.yaml --allow-no-checks | FileCheck %s --check-prefix=CHECK-SUB-DIR-EMPTY
+// RUN: clang-tidy --experimental-custom-checks %t/subdir-append/main.cpp -checks='-*,custom-*' -vfsoverlay %t.yaml | FileCheck %s --check-prefix=CHECK-SUB-DIR-APPEND
+// RUN: clang-tidy --experimental-custom-checks %t/subdir-append/main.cpp -checks='-*,custom-*' -vfsoverlay %t.yaml --list-checks | FileCheck %s --check-prefix=LIST-CHECK
+// RUN: clang-tidy --experimental-custom-checks %t/subdir-append/main.cpp -checks='-*,custom-*' -vfsoverlay %t.yaml --dump-config | FileCheck %s --check-prefix=DUMP-CONFIG
 // REQUIRES: shell
 
 

>From 0a99d0c12ed6f42b8e52a903a44cf181a0f4462e Mon Sep 17 00:00:00 2001
From: Congcong Cai <congcongcai0907 at 163.com>
Date: Sat, 30 Aug 2025 17:53:28 +0800
Subject: [PATCH 22/25] format

---
 clang-tools-extra/clang-tidy/ClangTidy.cpp               | 9 +++------
 .../clang-tidy/ClangTidyDiagnosticConsumer.h             | 4 +---
 clang-tools-extra/clang-tidy/tool/ClangTidyMain.cpp      | 7 ++++---
 3 files changed, 8 insertions(+), 12 deletions(-)

diff --git a/clang-tools-extra/clang-tidy/ClangTidy.cpp b/clang-tools-extra/clang-tidy/ClangTidy.cpp
index cee5c15968e64..2451d4eb94ba5 100644
--- a/clang-tools-extra/clang-tidy/ClangTidy.cpp
+++ b/clang-tools-extra/clang-tidy/ClangTidy.cpp
@@ -515,8 +515,7 @@ std::vector<std::string> getCheckNames(const ClangTidyOptions &Options,
   clang::tidy::ClangTidyContext Context(
       std::make_unique<DefaultOptionsProvider>(ClangTidyGlobalOptions(),
                                                Options),
-      AllowEnablingAnalyzerAlphaCheckers, false,
-      ExperimentalCustomChecks);
+      AllowEnablingAnalyzerAlphaCheckers, false, ExperimentalCustomChecks);
   ClangTidyASTConsumerFactory Factory(Context);
   return Factory.getCheckNames();
 }
@@ -542,8 +541,7 @@ getCheckOptions(const ClangTidyOptions &Options,
   clang::tidy::ClangTidyContext Context(
       std::make_unique<DefaultOptionsProvider>(ClangTidyGlobalOptions(),
                                                Options),
-      AllowEnablingAnalyzerAlphaCheckers, false,
-      ExperimentalCustomChecks);
+      AllowEnablingAnalyzerAlphaCheckers, false, ExperimentalCustomChecks);
   ClangTidyDiagnosticConsumer DiagConsumer(Context);
   auto DiagOpts = std::make_unique<DiagnosticOptions>();
   DiagnosticsEngine DE(llvm::makeIntrusiveRefCnt<DiagnosticIDs>(), *DiagOpts,
@@ -687,8 +685,7 @@ ChecksAndOptions getAllChecksAndOptions(bool AllowEnablingAnalyzerAlphaCheckers,
   Opts.Checks = "*";
   clang::tidy::ClangTidyContext Context(
       std::make_unique<DefaultOptionsProvider>(ClangTidyGlobalOptions(), Opts),
-      AllowEnablingAnalyzerAlphaCheckers, false,
-      ExperimentalCustomChecks);
+      AllowEnablingAnalyzerAlphaCheckers, false, ExperimentalCustomChecks);
   ClangTidyCheckFactories Factories;
 #if CLANG_TIDY_ENABLE_QUERY_BASED_CUSTOM_CHECKS
   if (ExperimentalCustomChecks)
diff --git a/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.h b/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.h
index cbded96e800e3..fd346669bc9a0 100644
--- a/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.h
+++ b/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.h
@@ -216,9 +216,7 @@ class ClangTidyContext {
 
   // whether experimental custom checks can be enabled.
   // enabled with `--experimental-custom-checks`
-  bool canExperimentalCustomChecks() const {
-    return ExperimentalCustomChecks;
-  }
+  bool canExperimentalCustomChecks() const { return ExperimentalCustomChecks; }
 
   void setSelfContainedDiags(bool Value) { SelfContainedDiags = Value; }
 
diff --git a/clang-tools-extra/clang-tidy/tool/ClangTidyMain.cpp b/clang-tools-extra/clang-tidy/tool/ClangTidyMain.cpp
index 5b5caed2e5d7e..2913f24985a1e 100644
--- a/clang-tools-extra/clang-tidy/tool/ClangTidyMain.cpp
+++ b/clang-tools-extra/clang-tidy/tool/ClangTidyMain.cpp
@@ -346,13 +346,14 @@ all of the checks.
 )"),
                                    cl::init(false), cl::cat(ClangTidyCategory));
 
-static cl::opt<bool>
-    ExperimentalCustomChecks("experimental-custom-checks", desc(R"(
+static cl::opt<bool> ExperimentalCustomChecks("experimental-custom-checks",
+                                              desc(R"(
 Enable experimental clang-query based
 custom checks.
 see https://clang.llvm.org/extra/clang-tidy/QueryBasedCustomChecks.html.
 )"),
-                                   cl::init(false), cl::cat(ClangTidyCategory));
+                                              cl::init(false),
+                                              cl::cat(ClangTidyCategory));
 
 namespace clang::tidy {
 

>From 2a3e0f1c26970563cae44a57936234ab4b103742 Mon Sep 17 00:00:00 2001
From: Congcong Cai <congcongcai0907 at 163.com>
Date: Thu, 4 Sep 2025 22:14:36 +0800
Subject: [PATCH 23/25] doc

---
 .../clang-tidy/QueryBasedCustomChecks.rst     | 25 ++++++++++++++-----
 1 file changed, 19 insertions(+), 6 deletions(-)

diff --git a/clang-tools-extra/docs/clang-tidy/QueryBasedCustomChecks.rst b/clang-tools-extra/docs/clang-tidy/QueryBasedCustomChecks.rst
index 45ce87e26e8e3..f10d15412fe16 100644
--- a/clang-tools-extra/docs/clang-tidy/QueryBasedCustomChecks.rst
+++ b/clang-tools-extra/docs/clang-tidy/QueryBasedCustomChecks.rst
@@ -35,12 +35,6 @@ Configuration
 Example
 =======
 
-Note: Since this feature is currently in the development stage. The API may
-change in the future. It needs to be explicitly enabled by
-`--experimental-custom-checks`.
-
-We also welcome suggestions in the link https://discourse.llvm.org/t/support-query-based-clang-tidy-external-check/85331.
-
 .. code-block:: yaml
 
   Checks: -*,custom-call-main-function
@@ -67,3 +61,22 @@ We also welcome suggestions in the link https://discourse.llvm.org/t/support-que
   void bar() {
     main(); // warning: call to main function. [custom-call-main-function]
   }
+
+Matters Need Attention
+======================
+
+This feature needs to be explicitly enabled by `--experimental-custom-checks`
+because it is currently in the experimental stage. Welcome to submit any
+suggestions in the `link <https://discourse.llvm.org/t/support-query-based-clang-tidy-external-check/85331>`_.
+
+During the experimental stage, the required configuration structure of this
+feature may be changed in the future. Future changes will be as
+forward-compatible as possible, but this is not a guarantee.
+
+In subsequent versions, including non-experimental stage, the query statements
+will change at any time. The essence of :program:`clang-query` is to parse the
+query string and dynamically generate the corresponding AST matcher.
+Therefore, its functionality is entirely dependent on the functions provided by
+the AST matcher library.
+The ast matcher will change along with the changes in the clang AST.
+Please refer to `ast matcher reference <https://clang.llvm.org/docs/LibASTMatchersReference.html>`_.

>From 296eea3070bb8db57beb7a384feb0883d4353e37 Mon Sep 17 00:00:00 2001
From: Congcong Cai <congcongcai0907 at 163.com>
Date: Thu, 18 Sep 2025 18:13:33 +0800
Subject: [PATCH 24/25] fix link issue

---
 clang-tools-extra/clang-tidy/ClangTidy.cpp     | 18 ++++++++++--------
 clang-tools-extra/clang-tidy/ClangTidy.h       |  4 ++++
 .../clang-tidy/custom/CustomTidyModule.cpp     |  8 ++++++++
 3 files changed, 22 insertions(+), 8 deletions(-)

diff --git a/clang-tools-extra/clang-tidy/ClangTidy.cpp b/clang-tools-extra/clang-tidy/ClangTidy.cpp
index db3b9eac53b8f..e100f1412b066 100644
--- a/clang-tools-extra/clang-tidy/ClangTidy.cpp
+++ b/clang-tools-extra/clang-tidy/ClangTidy.cpp
@@ -53,10 +53,12 @@ LLVM_INSTANTIATE_REGISTRY(clang::tidy::ClangTidyModuleRegistry)
 
 namespace clang::tidy {
 
+#if CLANG_TIDY_ENABLE_QUERY_BASED_CUSTOM_CHECKS
 namespace custom {
-extern void registerCustomChecks(const ClangTidyOptions &O,
-                                 ClangTidyCheckFactories &Factories);
+void (*RegisterCustomChecks)(const ClangTidyOptions &O,
+                             ClangTidyCheckFactories &Factories) = nullptr;
 } // namespace custom
+#endif
 
 namespace {
 #if CLANG_TIDY_ENABLE_STATIC_ANALYZER
@@ -348,8 +350,8 @@ ClangTidyASTConsumerFactory::ClangTidyASTConsumerFactory(
     : Context(Context), OverlayFS(std::move(OverlayFS)),
       CheckFactories(new ClangTidyCheckFactories) {
 #if CLANG_TIDY_ENABLE_QUERY_BASED_CUSTOM_CHECKS
-  if (Context.canExperimentalCustomChecks())
-    custom::registerCustomChecks(Context.getOptions(), *CheckFactories);
+  if (Context.canExperimentalCustomChecks() && custom::RegisterCustomChecks)
+    custom::RegisterCustomChecks(Context.getOptions(), *CheckFactories);
 #endif
   for (ClangTidyModuleRegistry::entry E : ClangTidyModuleRegistry::entries()) {
     std::unique_ptr<ClangTidyModule> Module = E.instantiate();
@@ -421,8 +423,8 @@ ClangTidyASTConsumerFactory::createASTConsumer(
   if (WorkingDir)
     Context.setCurrentBuildDirectory(WorkingDir.get());
 #if CLANG_TIDY_ENABLE_QUERY_BASED_CUSTOM_CHECKS
-  if (Context.canExperimentalCustomChecks())
-    custom::registerCustomChecks(Context.getOptions(), *CheckFactories);
+  if (Context.canExperimentalCustomChecks() && custom::RegisterCustomChecks)
+    custom::RegisterCustomChecks(Context.getOptions(), *CheckFactories);
 #endif
   std::vector<std::unique_ptr<ClangTidyCheck>> Checks =
       CheckFactories->createChecksForLanguage(&Context);
@@ -688,8 +690,8 @@ ChecksAndOptions getAllChecksAndOptions(bool AllowEnablingAnalyzerAlphaCheckers,
       AllowEnablingAnalyzerAlphaCheckers, false, ExperimentalCustomChecks);
   ClangTidyCheckFactories Factories;
 #if CLANG_TIDY_ENABLE_QUERY_BASED_CUSTOM_CHECKS
-  if (ExperimentalCustomChecks)
-    custom::registerCustomChecks(Context.getOptions(), Factories);
+  if (ExperimentalCustomChecks && custom::RegisterCustomChecks)
+    custom::RegisterCustomChecks(Context.getOptions(), Factories);
 #endif
   for (const ClangTidyModuleRegistry::entry &Module :
        ClangTidyModuleRegistry::entries()) {
diff --git a/clang-tools-extra/clang-tidy/ClangTidy.h b/clang-tools-extra/clang-tidy/ClangTidy.h
index f4e6b7ef34ab0..e238f2357caeb 100644
--- a/clang-tools-extra/clang-tidy/ClangTidy.h
+++ b/clang-tools-extra/clang-tidy/ClangTidy.h
@@ -127,6 +127,10 @@ void exportReplacements(StringRef MainFilePath,
                         const std::vector<ClangTidyError> &Errors,
                         raw_ostream &OS);
 
+namespace custom {
+extern void (*RegisterCustomChecks)(const ClangTidyOptions &O,
+                                    ClangTidyCheckFactories &Factories);
+} // namespace custom
 } // end namespace tidy
 } // end namespace clang
 
diff --git a/clang-tools-extra/clang-tidy/custom/CustomTidyModule.cpp b/clang-tools-extra/clang-tidy/custom/CustomTidyModule.cpp
index 6aea3e4de4c6d..94d76e0dfbb6b 100644
--- a/clang-tools-extra/clang-tidy/custom/CustomTidyModule.cpp
+++ b/clang-tools-extra/clang-tidy/custom/CustomTidyModule.cpp
@@ -38,6 +38,14 @@ extern void registerCustomChecks(const ClangTidyOptions &Options,
   }
 }
 
+struct CustomChecksRegisterInitializer {
+  CustomChecksRegisterInitializer() noexcept {
+    RegisterCustomChecks = &custom::registerCustomChecks;
+  }
+};
+
+static CustomChecksRegisterInitializer Init{};
+
 } // namespace custom
 
 // Register the CustomTidyModule using this statically initialized variable.

>From 0263db748f3283cc1cb7d2bfd433efe29b0cfd93 Mon Sep 17 00:00:00 2001
From: Congcong Cai <congcongcai0907 at 163.com>
Date: Thu, 18 Sep 2025 18:18:26 +0800
Subject: [PATCH 25/25] Revert "Revert "[clang-tidy] Fix bazel build after
 #131804 (lazy style)." (#159382)"

This reverts commit 2bf62e767119369e90670ca5bdd73a54193041e2.
---
 .../clang-tools-extra/clang-tidy/BUILD.bazel  | 19 +++++++++++--------
 1 file changed, 11 insertions(+), 8 deletions(-)

diff --git a/utils/bazel/llvm-project-overlay/clang-tools-extra/clang-tidy/BUILD.bazel b/utils/bazel/llvm-project-overlay/clang-tools-extra/clang-tidy/BUILD.bazel
index f779be14ee468..6a8e8b4485313 100644
--- a/utils/bazel/llvm-project-overlay/clang-tools-extra/clang-tidy/BUILD.bazel
+++ b/utils/bazel/llvm-project-overlay/clang-tools-extra/clang-tidy/BUILD.bazel
@@ -33,14 +33,17 @@ config_setting(
 expand_template(
     name = "config",
     out = "clang-tidy-config.h",
-    substitutions = select({
-        ":static_analyzer_enabled": {
-            "#cmakedefine01 CLANG_TIDY_ENABLE_STATIC_ANALYZER": "#define CLANG_TIDY_ENABLE_STATIC_ANALYZER 1",
-        },
-        "//conditions:default": {
-            "#cmakedefine01 CLANG_TIDY_ENABLE_STATIC_ANALYZER": "#define CLANG_TIDY_ENABLE_STATIC_ANALYZER 0",
-        },
-    }),
+    substitutions =
+        {
+            "#cmakedefine01 CLANG_TIDY_ENABLE_QUERY_BASED_CUSTOM_CHECKS": "#define CLANG_TIDY_ENABLE_QUERY_BASED_CUSTOM_CHECKS 0",
+        } | select({
+            ":static_analyzer_enabled": {
+                "#cmakedefine01 CLANG_TIDY_ENABLE_STATIC_ANALYZER": "#define CLANG_TIDY_ENABLE_STATIC_ANALYZER 1",
+            },
+            "//conditions:default": {
+                "#cmakedefine01 CLANG_TIDY_ENABLE_STATIC_ANALYZER": "#define CLANG_TIDY_ENABLE_STATIC_ANALYZER 0",
+            },
+        }),
     template = "clang-tidy-config.h.cmake",
     visibility = ["//visibility:private"],
 )



More information about the llvm-commits mailing list