Skip to content

Conversation

HerrCai0907
Copy link
Contributor

@HerrCai0907 HerrCai0907 commented Mar 18, 2025

summary

  • remove ERROR diag level, it can be done by warning as error
  • add cmake option to design enable / disable this feature
  • configuration format: I prefer that we should still use structured options

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
  2. all behavior including InheritFromParantConfig is same as other config.
  3. change configuration schema from KV structured to List structured to make extend new function easier.
  4. Split bind string and diag message to two field to give more freedom to design query.

example:

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
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

Copy link
Contributor Author

This stack of pull requests is managed by Graphite. Learn more about stacking.

@HerrCai0907 HerrCai0907 marked this pull request as ready for review March 18, 2025 13:56
@HerrCai0907 HerrCai0907 changed the title origin pr [clang-tidy] support query based custom check Mar 18, 2025
@llvmbot
Copy link
Member

llvmbot commented Mar 18, 2025

@llvm/pr-subscribers-clang-tidy

@llvm/pr-subscribers-clang-tools-extra

Author: Congcong Cai (HerrCai0907)

Changes

Patch is 31.84 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/131804.diff

25 Files Affected:

  • (modified) clang-tools-extra/clang-tidy/CMakeLists.txt (+2)
  • (modified) clang-tools-extra/clang-tidy/ClangTidy.cpp (+8-1)
  • (modified) clang-tools-extra/clang-tidy/ClangTidyForceLinker.h (+5)
  • (modified) clang-tools-extra/clang-tidy/ClangTidyModule.h (+2)
  • (modified) clang-tools-extra/clang-tidy/ClangTidyOptions.cpp (+53-3)
  • (modified) clang-tools-extra/clang-tidy/ClangTidyOptions.h (+15)
  • (added) clang-tools-extra/clang-tidy/custom/CMakeLists.txt (+20)
  • (added) clang-tools-extra/clang-tidy/custom/CustomTidyModule.cpp (+50)
  • (added) clang-tools-extra/clang-tidy/custom/QueryCheck.cpp (+102)
  • (added) clang-tools-extra/clang-tidy/custom/QueryCheck.h (+42)
  • (modified) clang-tools-extra/clang-tidy/tool/ClangTidyMain.cpp (+2)
  • (modified) clang-tools-extra/docs/ReleaseNotes.rst (+3)
  • (added) clang-tools-extra/docs/clang-tidy/QueryBasedCustomChecks.rst (+57)
  • (modified) clang-tools-extra/docs/clang-tidy/index.rst (+3)
  • (added) clang-tools-extra/test/clang-tidy/checkers/custom/Inputs/clang-tidy.yml (+22)
  • (added) clang-tools-extra/test/clang-tidy/checkers/custom/Inputs/incorrect-clang-tidy.yml (+10)
  • (added) clang-tools-extra/test/clang-tidy/checkers/custom/query-incorrect-query.cpp (+3)
  • (added) clang-tools-extra/test/clang-tidy/checkers/custom/query-partially-active-check.cpp (+5)
  • (added) clang-tools-extra/test/clang-tidy/checkers/custom/query.cpp (+7)
  • (added) clang-tools-extra/test/clang-tidy/infrastructure/Inputs/custom-query-check/append-clang-tidy.yml (+8)
  • (added) clang-tools-extra/test/clang-tidy/infrastructure/Inputs/custom-query-check/empty-clang-tidy.yml (+1)
  • (added) clang-tools-extra/test/clang-tidy/infrastructure/Inputs/custom-query-check/override-clang-tidy.yml (+11)
  • (added) clang-tools-extra/test/clang-tidy/infrastructure/Inputs/custom-query-check/root-clang-tidy.yml (+11)
  • (added) clang-tools-extra/test/clang-tidy/infrastructure/Inputs/custom-query-check/vfsoverlay.yaml (+44)
  • (added) clang-tools-extra/test/clang-tidy/infrastructure/custom-query-check.cpp (+45)
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..f2a69e01a32c5 100644
--- a/clang-tools-extra/clang-tidy/ClangTidy.cpp
+++ b/clang-tools-extra/clang-tidy/ClangTidy.cpp
@@ -57,6 +57,11 @@ LLVM_INSTANTIATE_REGISTRY(clang::tidy::ClangTidyModuleRegistry)
 
 namespace clang::tidy {
 
+namespace custom {
+extern void registerCustomChecks(ClangTidyOptions const &O,
+                                 ClangTidyCheckFactories &Factories);
+} // namespace custom
+
 namespace {
 #if CLANG_TIDY_ENABLE_STATIC_ANALYZER
 static const char *AnalyzerCheckNamePrefix = "clang-analyzer-";
@@ -346,6 +351,7 @@ ClangTidyASTConsumerFactory::ClangTidyASTConsumerFactory(
     IntrusiveRefCntPtr<llvm::vfs::OverlayFileSystem> OverlayFS)
     : Context(Context), OverlayFS(std::move(OverlayFS)),
       CheckFactories(new ClangTidyCheckFactories) {
+  custom::registerCustomChecks(Context.getOptions(), *CheckFactories);
   for (ClangTidyModuleRegistry::entry E : ClangTidyModuleRegistry::entries()) {
     std::unique_ptr<ClangTidyModule> Module = E.instantiate();
     Module->addCheckFactories(*CheckFactories);
@@ -416,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);
 
@@ -659,6 +665,7 @@ getAllChecksAndOptions(bool AllowEnablingAnalyzerAlphaCheckers) {
       std::make_unique<DefaultOptionsProvider>(ClangTidyGlobalOptions(), Opts),
       AllowEnablingAnalyzerAlphaCheckers);
   ClangTidyCheckFactories Factories;
+  custom::registerCustomChecks(Context.getOptions(), Factories);
   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/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/ClangTidyOptions.cpp b/clang-tools-extra/clang-tidy/ClangTidyOptions.cpp
index 8bac6f161fa05..acedbd8d41faa 100644
--- a/clang-tools-extra/clang-tidy/ClangTidyOptions.cpp
+++ b/clang-tools-extra/clang-tidy/ClangTidyOptions.cpp
@@ -8,12 +8,12 @@
 
 #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/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"
@@ -72,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;
@@ -126,6 +127,52 @@ 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, "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;
+};
+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;
@@ -181,6 +228,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);
   }
 };
 
@@ -249,6 +297,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..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,6 +131,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/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..e11a39f1a4ccf
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/custom/CustomTidyModule.cpp
@@ -0,0 +1,50 @@
+#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 <memory>
+
+namespace clang::tidy {
+namespace custom {
+
+class CustomModule : public ClangTidyModule {
+public:
+  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.
+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..c69e76918f7ed
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/custom/QueryCheck.cpp
@@ -0,0 +1,102 @@
+//===--- 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 {
+
+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);
+  }
+
+  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) {
+  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())
+    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
new file mode 100644
index 0000000000000..ded4cad4e3459
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/custom/QueryCheck.h
@@ -0,0 +1,42 @@
+//===--- 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 {
+
+/// 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{};
+  using DiagMaps =
+      llvm::DenseMap<DiagnosticIDs::Level,
+                     llvm::StringMap<llvm::SmallVector<std::string>>>;
+  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 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 val...
[truncated]

@HerrCai0907 HerrCai0907 force-pushed the users/ccc/clang-tidy/query-check branch from c6e485e to 64c45dc Compare March 18, 2025 14:05
@HerrCai0907
Copy link
Contributor Author

--dump-config will create incorrect YAML due to #131694.

HerrCai0907 and others added 2 commits March 19, 2025 09:58
Co-authored-by: Baranov Victor <bar.victor.2002@gmail.com>
@carlosgalvezp
Copy link
Contributor

Sorry for being late for the review. This is a major new feature of clang-tidy, so I believe it would be good to have an RFC before looking at the detailed implementation. What problem does it solve, why should clang-tidy solve it, what the API towards the user should be, etc. Should we do that?

@olologin
Copy link

olologin commented Mar 19, 2025

@carlosgalvezp Hi, I tried to explain everything here: #107680
@HerrCai0907 maybe you can reference that issue in the first post in this PR.

@HerrCai0907
Copy link
Contributor Author

ping @PiotrZSL @vbvictor @carlosgalvezp
I have updated the PR according to comment and updated the description, could you review it again?

Co-authored-by: Baranov Victor <bar.victor.2002@gmail.com>
@HerrCai0907 HerrCai0907 requested review from vbvictor and 5chmidti June 18, 2025 08:18
Copy link
Contributor

@vbvictor vbvictor left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mostly docs suggestions. I like this approach with config file, but don't have a strong opinion for now, so LGTM without explicit approval.

HerrCai0907 and others added 2 commits June 22, 2025 22:24
@HerrCai0907
Copy link
Contributor Author

ping @PiotrZSL @carlosgalvezp

@5chmidti
Copy link
Contributor

5chmidti commented Aug 7, 2025

I'll find some time for this check on the weekend

Copy link

github-actions bot commented Aug 30, 2025

✅ With the latest revision this PR passed the C/C++ code formatter.

@HerrCai0907 HerrCai0907 force-pushed the users/ccc/clang-tidy/query-check branch from a462def to a373c55 Compare August 30, 2025 09:46
@5chmidti
Copy link
Contributor

5chmidti commented Sep 1, 2025

I won't have access to my machine for 2 weeks, so I can't try it out in the coming days, sorry. I'll see if I can do at least some review on mobile, but my general thoughts are in discourse.

For now: I think the documentation should be extended to explain that the AST is not a fixed construct, and it will change between versions (e.g., I think there was a nested namespace change recently). Also, we should encourage users to think about upstreaming checks when it makes sense

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

10 participants