[clang-tools-extra] d05b7f1 - [clang-tidy] support query based custom check (#131804)

via cfe-commits cfe-commits at lists.llvm.org
Tue Sep 16 19:10:12 PDT 2025


Author: Congcong Cai
Date: 2025-09-17T10:10:08+08:00
New Revision: d05b7f1bb3e16ce37c1d17cfb170440e09244ce1

URL: https://github.com/llvm/llvm-project/commit/d05b7f1bb3e16ce37c1d17cfb170440e09244ce1
DIFF: https://github.com/llvm/llvm-project/commit/d05b7f1bb3e16ce37c1d17cfb170440e09244ce1.diff

LOG: [clang-tidy] support query based custom check (#131804)

## summary

### Design for Compatibility

For new field design
1. we must make sure the required fields do not change in quiet long
time.
2. we should tolerant the unknown optional field (do not exist now) in
the future.

For large project integration (3rd party)
1. For config itself, since we can tolerant the unknown optional fields
and required fields should not change, the user can decide whether to
use custom check from third-party.
2.For clang-query, if there are some break change, since the query will
be parsed at check time, the user can disable the check and it will not
damage the whole clang-tidy

----

Inherit from #123734
RFC:
https://discourse.llvm.org/t/support-query-based-clang-tidy-external-check/85331

this patch introduce query based custom check by `CustomChecks` options.

The major improvement compared with #123734:
1. don't need to define config yaml file in command line
4. all behavior including `InheritFromParantConfig` is same as other
config.
6. change configuration schema from KV structured to List structured to
make extend new function easier.
7. Split bind string and diag message to two field to give more freedom
to design query.

example:
```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
```
```c++
int main(); // note: main function.

void bar() {
  main(); // warning: call to main function. [custom-call-main-function]
}
```

---

To make this PR don't do too much things that hard to review, here are
some possible features not included in this PR
- support language
- support traverse
- easier used template string in diagnostics message

---------

Co-authored-by: DeNiCoN <denicon1234 at gmail.com>
Co-authored-by: Baranov Victor <bar.victor.2002 at gmail.com>

Added: 
    clang-tools-extra/clang-tidy/custom/CMakeLists.txt
    clang-tools-extra/clang-tidy/custom/CustomTidyModule.cpp
    clang-tools-extra/clang-tidy/custom/QueryCheck.cpp
    clang-tools-extra/clang-tidy/custom/QueryCheck.h
    clang-tools-extra/docs/clang-tidy/QueryBasedCustomChecks.rst
    clang-tools-extra/test/clang-tidy/checkers/custom/Inputs/clang-tidy.yml
    clang-tools-extra/test/clang-tidy/checkers/custom/Inputs/incorrect-clang-tidy.yml
    clang-tools-extra/test/clang-tidy/checkers/custom/query-incorrect-query.cpp
    clang-tools-extra/test/clang-tidy/checkers/custom/query-partially-active-check.cpp
    clang-tools-extra/test/clang-tidy/checkers/custom/query.cpp
    clang-tools-extra/test/clang-tidy/infrastructure/Inputs/custom-query-check/append-clang-tidy.yml
    clang-tools-extra/test/clang-tidy/infrastructure/Inputs/custom-query-check/empty-clang-tidy.yml
    clang-tools-extra/test/clang-tidy/infrastructure/Inputs/custom-query-check/override-clang-tidy.yml
    clang-tools-extra/test/clang-tidy/infrastructure/Inputs/custom-query-check/root-clang-tidy.yml
    clang-tools-extra/test/clang-tidy/infrastructure/Inputs/custom-query-check/vfsoverlay.yaml
    clang-tools-extra/test/clang-tidy/infrastructure/custom-query-check-not-enable.cpp
    clang-tools-extra/test/clang-tidy/infrastructure/custom-query-check.cpp

Modified: 
    clang-tools-extra/CMakeLists.txt
    clang-tools-extra/clang-tidy/CMakeLists.txt
    clang-tools-extra/clang-tidy/ClangTidy.cpp
    clang-tools-extra/clang-tidy/ClangTidy.h
    clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.cpp
    clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.h
    clang-tools-extra/clang-tidy/ClangTidyForceLinker.h
    clang-tools-extra/clang-tidy/ClangTidyModule.h
    clang-tools-extra/clang-tidy/ClangTidyOptions.cpp
    clang-tools-extra/clang-tidy/ClangTidyOptions.h
    clang-tools-extra/clang-tidy/clang-tidy-config.h.cmake
    clang-tools-extra/clang-tidy/tool/ClangTidyMain.cpp
    clang-tools-extra/docs/ReleaseNotes.rst
    clang-tools-extra/docs/clang-tidy/Contributing.rst
    clang-tools-extra/docs/clang-tidy/index.rst
    clang-tools-extra/test/clang-tidy/check_clang_tidy.py
    clang-tools-extra/unittests/clang-tidy/CMakeLists.txt
    clang-tools-extra/unittests/clang-tidy/ClangTidyTest.h

Removed: 
    


################################################################################
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 93117cf1d6373..153356245cfd1 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)
@@ -101,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 4c36bbccf44d9..db3b9eac53b8f 100644
--- a/clang-tools-extra/clang-tidy/ClangTidy.cpp
+++ b/clang-tools-extra/clang-tidy/ClangTidy.cpp
@@ -53,6 +53,11 @@ LLVM_INSTANTIATE_REGISTRY(clang::tidy::ClangTidyModuleRegistry)
 
 namespace clang::tidy {
 
+namespace custom {
+extern void registerCustomChecks(const ClangTidyOptions &O,
+                                 ClangTidyCheckFactories &Factories);
+} // namespace custom
+
 namespace {
 #if CLANG_TIDY_ENABLE_STATIC_ANALYZER
 #define ANALYZER_CHECK_NAME_PREFIX "clang-analyzer-"
@@ -342,6 +347,10 @@ ClangTidyASTConsumerFactory::ClangTidyASTConsumerFactory(
     IntrusiveRefCntPtr<llvm::vfs::OverlayFileSystem> OverlayFS)
     : 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);
+#endif
   for (ClangTidyModuleRegistry::entry E : ClangTidyModuleRegistry::entries()) {
     std::unique_ptr<ClangTidyModule> Module = E.instantiate();
     Module->addCheckFactories(*CheckFactories);
@@ -411,7 +420,10 @@ ClangTidyASTConsumerFactory::createASTConsumer(
                         .getCurrentWorkingDirectory();
   if (WorkingDir)
     Context.setCurrentBuildDirectory(WorkingDir.get());
-
+#if CLANG_TIDY_ENABLE_QUERY_BASED_CUSTOM_CHECKS
+  if (Context.canExperimentalCustomChecks())
+    custom::registerCustomChecks(Context.getOptions(), *CheckFactories);
+#endif
   std::vector<std::unique_ptr<ClangTidyCheck>> Checks =
       CheckFactories->createChecksForLanguage(&Context);
 
@@ -497,13 +509,13 @@ 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 ExperimentalCustomChecks) {
   clang::tidy::ClangTidyContext Context(
       std::make_unique<DefaultOptionsProvider>(ClangTidyGlobalOptions(),
                                                Options),
-      AllowEnablingAnalyzerAlphaCheckers);
+      AllowEnablingAnalyzerAlphaCheckers, false, ExperimentalCustomChecks);
   ClangTidyASTConsumerFactory Factory(Context);
   return Factory.getCheckNames();
 }
@@ -524,11 +536,12 @@ void filterCheckOptions(ClangTidyOptions &Options,
 
 ClangTidyOptions::OptionMap
 getCheckOptions(const ClangTidyOptions &Options,
-                bool AllowEnablingAnalyzerAlphaCheckers) {
+                bool AllowEnablingAnalyzerAlphaCheckers,
+                bool ExperimentalCustomChecks) {
   clang::tidy::ClangTidyContext Context(
       std::make_unique<DefaultOptionsProvider>(ClangTidyGlobalOptions(),
                                                Options),
-      AllowEnablingAnalyzerAlphaCheckers);
+      AllowEnablingAnalyzerAlphaCheckers, false, ExperimentalCustomChecks);
   ClangTidyDiagnosticConsumer DiagConsumer(Context);
   auto DiagOpts = std::make_unique<DiagnosticOptions>();
   DiagnosticsEngine DE(llvm::makeIntrusiveRefCnt<DiagnosticIDs>(), *DiagOpts,
@@ -665,15 +678,19 @@ void exportReplacements(const llvm::StringRef MainFilePath,
   YAML << TUD;
 }
 
-ChecksAndOptions
-getAllChecksAndOptions(bool AllowEnablingAnalyzerAlphaCheckers) {
+ChecksAndOptions getAllChecksAndOptions(bool AllowEnablingAnalyzerAlphaCheckers,
+                                        bool ExperimentalCustomChecks) {
   ChecksAndOptions Result;
   ClangTidyOptions Opts;
   Opts.Checks = "*";
   clang::tidy::ClangTidyContext Context(
       std::make_unique<DefaultOptionsProvider>(ClangTidyGlobalOptions(), Opts),
-      AllowEnablingAnalyzerAlphaCheckers);
+      AllowEnablingAnalyzerAlphaCheckers, false, ExperimentalCustomChecks);
   ClangTidyCheckFactories Factories;
+#if CLANG_TIDY_ENABLE_QUERY_BASED_CUSTOM_CHECKS
+  if (ExperimentalCustomChecks)
+    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/ClangTidy.h b/clang-tools-extra/clang-tidy/ClangTidy.h
index 3d1d3ca0b1791..f4e6b7ef34ab0 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 ExperimentalCustomChecks);
 
 struct ChecksAndOptions {
   llvm::StringSet<> Checks;
   llvm::StringSet<> Options;
 };
 
-ChecksAndOptions
-getAllChecksAndOptions(bool AllowEnablingAnalyzerAlphaCheckers = true);
+ChecksAndOptions getAllChecksAndOptions(bool AllowEnablingAnalyzerAlphaCheckers,
+                                        bool ExperimentalCustomChecks);
 
 /// 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 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 d07f15a10555f..823c7b5626e97 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 ExperimentalCustomChecks)
     : OptionsProvider(std::move(OptionsProvider)),
-
       AllowEnablingAnalyzerAlphaCheckers(AllowEnablingAnalyzerAlphaCheckers),
-      EnableModuleHeadersParsing(EnableModuleHeadersParsing) {
+      EnableModuleHeadersParsing(EnableModuleHeadersParsing),
+      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 a854756d647c2..21ffd9de35c19 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 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.
@@ -210,6 +214,10 @@ class ClangTidyContext {
     return EnableModuleHeadersParsing;
   }
 
+  // whether experimental custom checks can be enabled.
+  // enabled with `--experimental-custom-checks`
+  bool canExperimentalCustomChecks() const { return ExperimentalCustomChecks; }
+
   void setSelfContainedDiags(bool Value) { SelfContainedDiags = Value; }
 
   bool areDiagsSelfContained() const { return SelfContainedDiags; }
@@ -258,6 +266,7 @@ class ClangTidyContext {
 
   bool AllowEnablingAnalyzerAlphaCheckers;
   bool EnableModuleHeadersParsing;
+  bool ExperimentalCustomChecks;
 
   bool SelfContainedDiags = false;
 

diff  --git a/clang-tools-extra/clang-tidy/ClangTidyForceLinker.h b/clang-tools-extra/clang-tidy/ClangTidyForceLinker.h
index adde9136ff1dd..cdf6ce2045a5d 100644
--- a/clang-tools-extra/clang-tidy/ClangTidyForceLinker.h
+++ b/clang-tools-extra/clang-tidy/ClangTidyForceLinker.h
@@ -54,6 +54,13 @@ 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;
 static int LLVM_ATTRIBUTE_UNUSED DarwinModuleAnchorDestination =

diff  --git a/clang-tools-extra/clang-tidy/ClangTidyModule.h b/clang-tools-extra/clang-tidy/ClangTidyModule.h
index 7407ab580d378..8d697c6261286 100644
--- a/clang-tools-extra/clang-tidy/ClangTidyModule.h
+++ b/clang-tools-extra/clang-tidy/ClangTidyModule.h
@@ -62,6 +62,8 @@ class ClangTidyCheckFactories {
                          });
   }
 
+  void eraseCheck(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/ClangTidyOptions.cpp b/clang-tools-extra/clang-tidy/ClangTidyOptions.cpp
index dfa3521a25513..b752a9beb0e34 100644
--- a/clang-tools-extra/clang-tidy/ClangTidyOptions.cpp
+++ b/clang-tools-extra/clang-tidy/ClangTidyOptions.cpp
@@ -8,8 +8,10 @@
 
 #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"
 #include "llvm/Support/Debug.h"
 #include "llvm/Support/ErrorOr.h"
 #include "llvm/Support/MemoryBufferRef.h"
@@ -129,6 +131,51 @@ 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 ScalarEnumerationTraits<clang::DiagnosticIDs::Level> {
+  static void enumeration(IO &IO, clang::DiagnosticIDs::Level &Level) {
+    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;
+};
+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);
+    IO.mapRequired("Diagnostic", V.Diags);
+  }
+};
+
 struct ChecksVariant {
   std::optional<std::string> AsString;
   std::optional<std::vector<std::string>> AsVector;
@@ -184,6 +231,7 @@ template <> struct MappingTraits<ClangTidyOptions> {
     IO.mapOptional("InheritParentConfig", Options.InheritParentConfig);
     IO.mapOptional("UseColor", Options.UseColor);
     IO.mapOptional("SystemHeaders", Options.SystemHeaders);
+    IO.mapOptional("CustomChecks", Options.CustomChecks);
   }
 };
 
@@ -245,7 +293,8 @@ 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(),

diff  --git a/clang-tools-extra/clang-tidy/ClangTidyOptions.h b/clang-tools-extra/clang-tidy/ClangTidyOptions.h
index 22a954d2ac645..2aae92f1d9eb3 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"
@@ -129,6 +130,19 @@ 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;
+    llvm::SmallVector<CustomCheckDiag> Diags;
+  };
+  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.

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
new file mode 100644
index 0000000000000..0b43387970903
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/custom/CMakeLists.txt
@@ -0,0 +1,22 @@
+if(CLANG_TIDY_ENABLE_QUERY_BASED_CUSTOM_CHECKS)
+  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
+  )
+endif()

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..6aea3e4de4c6d
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/custom/CustomTidyModule.cpp
@@ -0,0 +1,51 @@
+#include "../ClangTidy.h"
+#include "../ClangTidyModule.h"
+#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 <cassert>
+#include <memory>
+
+namespace clang::tidy {
+namespace custom {
+
+class CustomModule : public ClangTidyModule {
+public:
+  void addCheckFactories(ClangTidyCheckFactories &CheckFactories) override {}
+};
+
+// We need to register the checks more flexibly than builtin modules. The checks
+// will changed dynamically when switching to 
diff erent source file.
+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 (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};
+    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 CustomTidyModule 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; // 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
new file mode 100644
index 0000000000000..f83c138fbfaf5
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/custom/QueryCheck.cpp
@@ -0,0 +1,146 @@
+//===--- 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/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;
+
+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) {
+  SmallVector<ast_matchers::dynamic::DynTypedMatcher> 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 &MatchQuery = llvm::cast<query::MatchQuery>(*Q);
+      Matchers.push_back(MatchQuery.Matcher);
+      break;
+    }
+    case query::QK_Let: {
+      const auto &LetQuery = llvm::cast<query::LetQuery>(*Q);
+      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);
+      emitConfigurationDiag(Context, InvalidQuery.ErrStr, V.Name);
+      return {};
+    }
+    // FIXME: TODO
+    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: {
+      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 {};
+    }
+    }
+    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) {
+  for (const ast_matchers::dynamic::DynTypedMatcher &M : Matchers)
+    Finder->addDynamicMatcher(M, this);
+}
+
+void QueryCheck::check(const MatchFinder::MatchResult &Result) {
+  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;
+    const BindNameMapToDiagMessage &BindNameMap = DiagMapIt->second;
+    BindNameMapToDiagMessage::const_iterator BindNameMapIt =
+        BindNameMap.find(BindName);
+    if (BindNameMapIt == BindNameMap.end())
+      return;
+    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::Warning);
+  // place Note last, otherwise it will not be emitted
+  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
new file mode 100644
index 0000000000000..3dcdc518736c2
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/custom/QueryCheck.h
@@ -0,0 +1,41 @@
+//===--- 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 "clang/Basic/DiagnosticIDs.h"
+#include "llvm/ADT/DenseMap.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/StringMap.h"
+
+namespace clang::tidy::custom {
+
+/// Implement of Clang-Query based check.
+/// Not directly visible to users.
+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;
+  using BindNameMapToDiagMessage =
+      llvm::StringMap<llvm::SmallVector<std::string>>;
+  using DiagMaps =
+      llvm::DenseMap<DiagnosticIDs::Level, BindNameMapToDiagMessage>;
+  DiagMaps Diags;
+};
+
+} // namespace clang::tidy::custom
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CUSTOM_QUERYCHECK_H

diff  --git a/clang-tools-extra/clang-tidy/tool/ClangTidyMain.cpp b/clang-tools-extra/clang-tidy/tool/ClangTidyMain.cpp
index 35ea1b5714b84..a2fa0edbbbea3 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'.
@@ -344,6 +346,15 @@ all of the checks.
 )"),
                                    cl::init(false), cl::cat(ClangTidyCategory));
 
+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));
+
 namespace clang::tidy {
 
 static void printStats(const ClangTidyStats &Stats) {
@@ -631,7 +642,8 @@ int clangTidyMain(int argc, const char **argv) {
   ClangTidyOptions EffectiveOptions = OptionsProvider->getOptions(FilePath);
 
   std::vector<std::string> EnabledChecks =
-      getCheckNames(EffectiveOptions, AllowEnablingAnalyzerAlphaCheckers);
+      getCheckNames(EffectiveOptions, AllowEnablingAnalyzerAlphaCheckers,
+                    ExperimentalCustomChecks);
 
   if (ExplainConfig) {
     // FIXME: Show other ClangTidyOptions' fields, like ExtraArg.
@@ -663,7 +675,8 @@ int clangTidyMain(int argc, const char **argv) {
 
   if (DumpConfig) {
     EffectiveOptions.CheckOptions =
-        getCheckOptions(EffectiveOptions, AllowEnablingAnalyzerAlphaCheckers);
+        getCheckOptions(EffectiveOptions, AllowEnablingAnalyzerAlphaCheckers,
+                        ExperimentalCustomChecks);
     ClangTidyOptions OptionsToDump =
         ClangTidyOptions::getDefaults().merge(EffectiveOptions, 0);
     filterCheckOptions(OptionsToDump, EnabledChecks);
@@ -674,8 +687,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, ExperimentalCustomChecks);
     bool AnyInvalid = false;
     for (const auto &[Opts, Source] : RawOptions) {
       if (Opts.Checks)
@@ -712,9 +725,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, ExperimentalCustomChecks);
   std::vector<ClangTidyError> Errors =
       runClangTidy(Context, OptionsParser->getCompilations(), PathList, BaseFS,
                    FixNotes, EnableCheckProfile, ProfilePrefix, Quiet);

diff  --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst
index 3f403c42a168a..a4652a7a54858 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -127,6 +127,10 @@ Improvements to clang-tidy
   by default, greatly improving performance. This behavior is disabled if the
   `SystemHeaders` option is enabled.
 
+- :program:`clang-tidy` now supports query based custom checks by `CustomChecks`
+  configuration option.
+  :doc:`Query Based Custom Check Document <clang-tidy/QueryBasedCustomChecks>`
+
 - The :program:`run-clang-tidy.py` and :program:`clang-tidy-
diff .py` scripts
   now run checks in parallel by default using all available hardware threads.
   Both scripts display the number of threads being used in their output.

diff  --git a/clang-tools-extra/docs/clang-tidy/Contributing.rst b/clang-tools-extra/docs/clang-tidy/Contributing.rst
index ad12b2343d1e9..ad7f22381a3ca 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/docs/clang-tidy/QueryBasedCustomChecks.rst b/clang-tools-extra/docs/clang-tidy/QueryBasedCustomChecks.rst
new file mode 100644
index 0000000000000..f10d15412fe16
--- /dev/null
+++ b/clang-tools-extra/docs/clang-tidy/QueryBasedCustomChecks.rst
@@ -0,0 +1,82 @@
+====================================
+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 :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.
+
+Goals: easy to write, cross platform, multiple versions supported toolkit for
+custom clang-tidy rules.
+Non-Goals: complex checks, performance, fix-its, etc.
+
+Configuration
+=============
+
+`CustomChecks` is a list of custom checks. Each check must contain
+  - 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.
+    - Level: severity of the diagnostic, the possible values are `Note`, `Warning`.
+
+`CustomChecks` can be configured by `Checks` option in the configuration file.
+
+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]
+  }
+
+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>`_.

diff  --git a/clang-tools-extra/docs/clang-tidy/index.rst b/clang-tools-extra/docs/clang-tidy/index.rst
index 3abd6639c3025..bd2c40e948f34 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>
@@ -304,6 +305,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                 - List 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/check_clang_tidy.py b/clang-tools-extra/test/clang-tidy/check_clang_tidy.py
index 26f8cbaeb9f31..183b33f135be8 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",
+                "--experimental-custom-checks",
                 self.temp_file_name,
             ]
             + [

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..b4524e247feae
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/custom/Inputs/clang-tidy.yml
@@ -0,0 +1,22 @@
+CustomChecks:
+  - 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..b94ba32997029
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/custom/Inputs/incorrect-clang-tidy.yml
@@ -0,0 +1,25 @@
+CustomChecks:
+  - Name: test-let-bind-invalid-1
+    Query: |
+      let expr varDecl(isStaticStorageClass()).bind("vd")
+      match expr
+      set output print
+    Diagnostic:
+      - 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")
+      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
new file mode 100644
index 0000000000000..f9a73750b4c3e
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/custom/query-incorrect-query.cpp
@@ -0,0 +1,7 @@
+// RUN: %check_clang_tidy %s custom-* %t --config-file=%S/Inputs/incorrect-clang-tidy.yml
+
+// 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]

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]

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..5b25ec061ba63
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/infrastructure/Inputs/custom-query-check/append-clang-tidy.yml
@@ -0,0 +1,8 @@
+InheritParentConfig: true
+CustomChecks:
+  - 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..2e9a13e720f4e
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/infrastructure/Inputs/custom-query-check/empty-clang-tidy.yml
@@ -0,0 +1 @@
+InheritParentConfig: false

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..ec243b8396ea8
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/infrastructure/Inputs/custom-query-check/override-clang-tidy.yml
@@ -0,0 +1,11 @@
+CustomChecks:
+  - 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..861ed10be1a5f
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/infrastructure/Inputs/custom-query-check/root-clang-tidy.yml
@@ -0,0 +1,11 @@
+CustomChecks:
+  - 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-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
new file mode 100644
index 0000000000000..8d0bc2bed180d
--- /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 --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
+
+
+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: CustomChecks:
+// 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

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

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,


        


More information about the cfe-commits mailing list