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

>From fdac9c55eb7b4b15211a3500febfe170fe2e6ccc Mon Sep 17 00:00:00 2001
From: voyager-jhk <[email protected]>
Date: Tue, 9 Jun 2026 18:32:50 +0800
Subject: [PATCH] [clang-tidy] Support LineFilter in .clang-tidy configuration
 files

Previously, the `LineFilter` option was strictly read from the global 
command-line
arguments. This made it completely inaccessible to Language Servers like 
`clangd`,
which rely exclusively on `.clang-tidy` configuration files.

This patch adds `LineFilter` to the `ClangTidyOptions` struct, implements YAML
serialization, and defines merge semantics. The `DiagnosticConsumer` is updated
to fallback to the local configuration only if the global command-line filter 
is empty,
preserving the expected CLI-override behavior.

Fixes #59263
---
 .../ClangTidyDiagnosticConsumer.cpp           |  19 +---
 .../clang-tidy/ClangTidyOptions.cpp           |  19 ++++
 .../clang-tidy/ClangTidyOptions.h             |  10 ++
 clang-tools-extra/clangd/ParsedAST.cpp        | 107 +++++++++++-------
 .../clangd/unittests/DiagnosticsTests.cpp     |  19 ++++
 clang-tools-extra/docs/ReleaseNotes.md        |  21 ++--
 .../infrastructure/line-filter-config.cpp     |  18 +++
 .../clang-tidy/ClangTidyOptionsTest.cpp       |  15 +++
 8 files changed, 164 insertions(+), 64 deletions(-)
 create mode 100644 
clang-tools-extra/test/clang-tidy/infrastructure/line-filter-config.cpp

diff --git a/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.cpp 
b/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.cpp
index 48d8c76bd4db6..4f8bc0fdd7726 100644
--- a/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.cpp
+++ b/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.cpp
@@ -482,19 +482,12 @@ void ClangTidyDiagnosticConsumer::HandleDiagnostic(
 
 bool ClangTidyDiagnosticConsumer::passesLineFilter(StringRef FileName,
                                                    unsigned LineNumber) const {
-  if (Context.getGlobalOptions().LineFilter.empty())
-    return true;
-  for (const FileFilter &Filter : Context.getGlobalOptions().LineFilter) {
-    if (FileName.ends_with(Filter.Name)) {
-      if (Filter.LineRanges.empty())
-        return true;
-      return llvm::any_of(
-          Filter.LineRanges, [&](const FileFilter::LineRange &Range) {
-            return Range.first <= LineNumber && LineNumber <= Range.second;
-          });
-    }
-  }
-  return false;
+  const std::vector<FileFilter> *Filters =
+      &Context.getGlobalOptions().LineFilter;
+  if (Filters->empty() && Context.getOptions().LineFilter)
+    Filters = &*Context.getOptions().LineFilter;
+
+  return tidy::passesLineFilter(*Filters, FileName, LineNumber);
 }
 
 void ClangTidyDiagnosticConsumer::forwardDiagnostic(const Diagnostic &Info) {
diff --git a/clang-tools-extra/clang-tidy/ClangTidyOptions.cpp 
b/clang-tools-extra/clang-tidy/ClangTidyOptions.cpp
index 2ef23ede09972..582e276bbcf56 100644
--- a/clang-tools-extra/clang-tidy/ClangTidyOptions.cpp
+++ b/clang-tools-extra/clang-tidy/ClangTidyOptions.cpp
@@ -10,6 +10,7 @@
 #include "ClangTidyModule.h"
 #include "clang/Basic/DiagnosticIDs.h"
 #include "clang/Basic/LLVM.h"
+#include "llvm/ADT/STLExtras.h"
 #include "llvm/ADT/SmallString.h"
 #include "llvm/ADT/StringExtras.h"
 #include "llvm/Support/Debug.h"
@@ -243,6 +244,7 @@ template <> struct MappingTraits<ClangTidyOptions> {
     IO.mapOptional("UseColor", Options.UseColor);
     IO.mapOptional("SystemHeaders", Options.SystemHeaders);
     IO.mapOptional("CustomChecks", Options.CustomChecks);
+    IO.mapOptional("LineFilter", Options.LineFilter);
   }
 };
 
@@ -303,6 +305,7 @@ ClangTidyOptions &ClangTidyOptions::mergeWith(const 
ClangTidyOptions &Other,
   overrideValue(FormatStyle, Other.FormatStyle);
   overrideValue(User, Other.User);
   overrideValue(UseColor, Other.UseColor);
+  overrideValue(LineFilter, Other.LineFilter);
   mergeVectors(ExtraArgs, Other.ExtraArgs);
   mergeVectors(ExtraArgsBefore, Other.ExtraArgsBefore);
   mergeVectors(RemovedArgs, Other.RemovedArgs);
@@ -526,6 +529,22 @@ FileOptionsBaseProvider::tryReadConfigFile(StringRef 
Directory) {
   return std::nullopt;
 }
 
+bool passesLineFilter(llvm::ArrayRef<FileFilter> LineFilter,
+                      llvm::StringRef FileName, unsigned LineNumber) {
+  if (LineFilter.empty())
+    return true;
+  for (const FileFilter &Filter : LineFilter) {
+    if (!FileName.ends_with(Filter.Name))
+      continue;
+    if (Filter.LineRanges.empty())
+      return true;
+    return llvm::any_of(Filter.LineRanges, [LineNumber](const auto &Range) {
+      return Range.first <= LineNumber && LineNumber <= Range.second;
+    });
+  }
+  return false;
+}
+
 /// Parses -line-filter option and stores it to the \c Options.
 std::error_code parseLineFilter(StringRef LineFilter,
                                 clang::tidy::ClangTidyGlobalOptions &Options) {
diff --git a/clang-tools-extra/clang-tidy/ClangTidyOptions.h 
b/clang-tools-extra/clang-tidy/ClangTidyOptions.h
index 73fdbabd5bdba..65eb6032a87e7 100644
--- a/clang-tools-extra/clang-tidy/ClangTidyOptions.h
+++ b/clang-tools-extra/clang-tidy/ClangTidyOptions.h
@@ -10,6 +10,7 @@
 #define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CLANGTIDYOPTIONS_H
 
 #include "clang/Basic/DiagnosticIDs.h"
+#include "llvm/ADT/ArrayRef.h"
 #include "llvm/ADT/IntrusiveRefCntPtr.h"
 #include "llvm/ADT/SmallString.h"
 #include "llvm/ADT/StringMap.h"
@@ -70,6 +71,9 @@ struct ClangTidyOptions {
   /// Checks filter.
   std::optional<std::string> Checks;
 
+  /// Line filter.
+  std::optional<std::vector<FileFilter>> LineFilter;
+
   /// WarningsAsErrors filter.
   std::optional<std::string> WarningsAsErrors;
 
@@ -338,6 +342,12 @@ class FileOptionsProvider : public FileOptionsBaseProvider 
{
   std::vector<OptionsSource> getRawOptions(StringRef FileName) override;
 };
 
+/// Returns true if a diagnostic at \p LineNumber in \p FileName should be
+/// displayed according to \p LineFilter. An empty filter allows all
+/// diagnostics.
+bool passesLineFilter(llvm::ArrayRef<FileFilter> LineFilter,
+                      llvm::StringRef FileName, unsigned LineNumber);
+
 /// Parses LineFilter from JSON and stores it to the \p Options.
 std::error_code parseLineFilter(StringRef LineFilter,
                                 ClangTidyGlobalOptions &Options);
diff --git a/clang-tools-extra/clangd/ParsedAST.cpp 
b/clang-tools-extra/clangd/ParsedAST.cpp
index df56420cd7f24..1a1bc05b0632e 100644
--- a/clang-tools-extra/clangd/ParsedAST.cpp
+++ b/clang-tools-extra/clangd/ParsedAST.cpp
@@ -383,6 +383,23 @@ void applyWarningOptions(llvm::ArrayRef<std::string> 
ExtraArgs,
   }
 }
 
+bool passesTidyLineFilter(const tidy::ClangTidyOptions &Options,
+                          SourceLocation Loc, const SourceManager &SM) {
+  if (!Options.LineFilter)
+    return true;
+
+  if (!Loc.isValid())
+    return true;
+
+  FileID FID = SM.getDecomposedExpansionLoc(Loc).first;
+  OptionalFileEntryRef File = SM.getFileEntryRefForID(FID);
+  if (!File)
+    return true;
+
+  return tidy::passesLineFilter(*Options.LineFilter, File->getName(),
+                                SM.getExpansionLineNumber(Loc));
+}
+
 std::vector<Diag> getIncludeCleanerDiags(ParsedAST &AST, llvm::StringRef Code,
                                          const ThreadsafeFS &TFS) {
   auto &Cfg = Config::current();
@@ -605,50 +622,54 @@ ParsedAST::build(llvm::StringRef Filename, const 
ParseInputs &Inputs,
                                           SourceLocation());
     }
 
-    ASTDiags.setLevelAdjuster([&](DiagnosticsEngine::Level DiagLevel,
-                                  const clang::Diagnostic &Info) {
-      auto It = OverriddenSeverity.find(Info.getID());
-      if (It != OverriddenSeverity.end())
-        DiagLevel = It->second;
-
-      if (!CTChecks.empty()) {
-        std::string CheckName = CTContext->getCheckName(Info.getID());
-        bool IsClangTidyDiag = !CheckName.empty();
-        if (IsClangTidyDiag) {
-          if (Cfg.Diagnostics.Suppress.contains(CheckName))
-            return DiagnosticsEngine::Ignored;
-          // Check for suppression comment. Skip the check for diagnostics not
-          // in the main file, because we don't want that function to query the
-          // source buffer for preamble files. For the same reason, we ask
-          // shouldSuppressDiagnostic to avoid I/O.
-          // We let suppression comments take precedence over warning-as-error
-          // to match clang-tidy's behaviour.
-          bool IsInsideMainFile =
-              Info.hasSourceManager() &&
-              isInsideMainFile(Info.getLocation(), Info.getSourceManager());
-          SmallVector<tooling::Diagnostic, 1> TidySuppressedErrors;
-          if (IsInsideMainFile && CTContext->shouldSuppressDiagnostic(
-                                      DiagLevel, Info, TidySuppressedErrors,
-                                      /*AllowIO=*/false,
-                                      /*EnableNolintBlocks=*/true)) {
-            // FIXME: should we expose the suppression error (invalid use of
-            // NOLINT comments)?
-            return DiagnosticsEngine::Ignored;
-          }
-          if (!CTContext->getOptions().SystemHeaders.value_or(false) &&
-              Info.hasSourceManager() &&
-              Info.getSourceManager().isInSystemMacro(Info.getLocation()))
-            return DiagnosticsEngine::Ignored;
-
-          // Check for warning-as-error.
-          if (DiagLevel == DiagnosticsEngine::Warning &&
-              CTContext->treatAsError(CheckName)) {
-            return DiagnosticsEngine::Error;
+    ASTDiags.setLevelAdjuster(
+        [&](DiagnosticsEngine::Level DiagLevel, const clang::Diagnostic &Info) 
{
+          auto It = OverriddenSeverity.find(Info.getID());
+          if (It != OverriddenSeverity.end())
+            DiagLevel = It->second;
+
+          if (!CTChecks.empty()) {
+            std::string CheckName = CTContext->getCheckName(Info.getID());
+            bool IsClangTidyDiag = !CheckName.empty();
+            if (IsClangTidyDiag) {
+              if (Cfg.Diagnostics.Suppress.contains(CheckName))
+                return DiagnosticsEngine::Ignored;
+              if (Info.hasSourceManager() &&
+                  !passesTidyLineFilter(ClangTidyOpts, Info.getLocation(),
+                                        Info.getSourceManager()))
+                return DiagnosticsEngine::Ignored;
+              // Check for suppression comment. Skip the check for diagnostics
+              // not in the main file, because we don't want that function to
+              // query the source buffer for preamble files. For the same
+              // reason, we ask shouldSuppressDiagnostic to avoid I/O. We let
+              // suppression comments take precedence over warning-as-error to
+              // match clang-tidy's behaviour.
+              bool IsInsideMainFile =
+                  Info.hasSourceManager() &&
+                  isInsideMainFile(Info.getLocation(), 
Info.getSourceManager());
+              SmallVector<tooling::Diagnostic, 1> TidySuppressedErrors;
+              if (IsInsideMainFile && CTContext->shouldSuppressDiagnostic(
+                                          DiagLevel, Info, 
TidySuppressedErrors,
+                                          /*AllowIO=*/false,
+                                          /*EnableNolintBlocks=*/true)) {
+                // FIXME: should we expose the suppression error (invalid use 
of
+                // NOLINT comments)?
+                return DiagnosticsEngine::Ignored;
+              }
+              if (!CTContext->getOptions().SystemHeaders.value_or(false) &&
+                  Info.hasSourceManager() &&
+                  Info.getSourceManager().isInSystemMacro(Info.getLocation()))
+                return DiagnosticsEngine::Ignored;
+
+              // Check for warning-as-error.
+              if (DiagLevel == DiagnosticsEngine::Warning &&
+                  CTContext->treatAsError(CheckName)) {
+                return DiagnosticsEngine::Error;
+              }
+            }
           }
-        }
-      }
-      return DiagLevel;
-    });
+          return DiagLevel;
+        });
 
     // Add IncludeFixer which can recover diagnostics caused by missing 
includes
     // (e.g. incomplete type) and attach include insertion fixes to 
diagnostics.
diff --git a/clang-tools-extra/clangd/unittests/DiagnosticsTests.cpp 
b/clang-tools-extra/clangd/unittests/DiagnosticsTests.cpp
index 6d91ac1ef1e8e..b8703a8384bcc 100644
--- a/clang-tools-extra/clangd/unittests/DiagnosticsTests.cpp
+++ b/clang-tools-extra/clangd/unittests/DiagnosticsTests.cpp
@@ -2078,6 +2078,25 @@ TEST(Diagnostics, TidyDiagsArentAffectedFromWerror) {
                 diagSeverity(DiagnosticsEngine::Error)))));
 }
 
+TEST(Diagnostics, TidyLineFilter) {
+  Annotations Test(R"cpp(
+    $skip[[typedef int Skip]];
+    $keep[[typedef int Keep]];
+    $after[[typedef int After]];
+  )cpp");
+  TestTU TU = TestTU::withCode(Test.code());
+  unsigned KeepLine = Test.range("keep").start.line + 1;
+  TU.ClangTidyProvider = [KeepLine](tidy::ClangTidyOptions &Opts,
+                                    llvm::StringRef) {
+    Opts.Checks = "modernize-use-using";
+    Opts.LineFilter =
+        std::vector<tidy::FileFilter>{{"TestTU.cpp", {{KeepLine, KeepLine}}}};
+  };
+  EXPECT_THAT(TU.build().getDiagnostics(),
+              ifTidyChecks(ElementsAre(Diag(
+                  Test.range("keep"), "use 'using' instead of 'typedef'"))));
+}
+
 TEST(Diagnostics, DeprecatedDiagsAreHints) {
   ClangdDiagnosticOptions Opts;
   std::optional<clangd::Diagnostic> Diag;
diff --git a/clang-tools-extra/docs/ReleaseNotes.md 
b/clang-tools-extra/docs/ReleaseNotes.md
index 28da9da42d8ce..265773109c277 100644
--- a/clang-tools-extra/docs/ReleaseNotes.md
+++ b/clang-tools-extra/docs/ReleaseNotes.md
@@ -10,6 +10,7 @@ myst:
 % ReleaseNotes.md and ReleaseNotesTemplate.txt.
 
 {#extra-clang-tools-release-releasenotestitle}
+
 # Extra Clang Tools {{env.config.release}} {{ (('(In-Progress) ' if 
env.app.tags.has('PreRelease') else '') ~ 'Release Notes') }}
 
 ```{contents}
@@ -41,11 +42,12 @@ the latest release, please see the [Clang Web 
Site](https://clang.llvm.org) or
 the [LLVM Web Site](https://llvm.org).
 
 Note that if you are reading this file from a Git checkout or the
-main Clang web page, this document applies to the *next* release, not
+main Clang web page, this document applies to the _next_ release, not
 the current one. To see the release notes for a specific release, please
 see the [releases page](https://llvm.org/releases/).
 
 {#what-s-new-in-extra-clang-tools-release}
+
 ## What's New in Extra Clang Tools {{env.config.release}}?
 
 Some of the major new features and improvements to Extra Clang Tools are listed
@@ -90,10 +92,14 @@ infrastructure are described first, followed by 
tool-specific sections.
   `-std=cXX-or-earlier` values, mirroring the existing `-std=cXX-or-later`.
   New construct expands to the given standard and every earlier one.
 
+- Added support for specifying `LineFilter` in {program}`clang-tidy`
+  configuration files. This is particularly useful for tools such as
+  {program}`clangd` that consume `.clang-tidy` files directly.
+
 #### New checks
 
 - New {doc}`performance-expensive-value-or
-  <clang-tidy/checks/performance/expensive-value-or>` check.
+<clang-tidy/checks/performance/expensive-value-or>` check.
 
   Finds calls to `value_or` (and alternative spellings `valueOr`,
   `ValueOr`) on optional types where the return type is expensive to copy.
@@ -103,29 +109,29 @@ infrastructure are described first, followed by 
tool-specific sections.
 #### Changes in existing checks
 
 - Improved {doc}`cppcoreguidelines-pro-type-member-init
-  <clang-tidy/checks/cppcoreguidelines/pro-type-member-init>` check by treating
+<clang-tidy/checks/cppcoreguidelines/pro-type-member-init>` check by treating
   `std::array` the same as built-in arrays when `IgnoreArrays` option is 
enabled.
 
 - Improved {doc}`misc-redundant-expression
-  <clang-tidy/checks/misc/redundant-expression>` by fixing false positives in
+<clang-tidy/checks/misc/redundant-expression>` by fixing false positives in
   nested expressions involving different macros or a mix of macro and
   non-macro operands.
 
 - Improved {doc}`modernize-return-braced-init-list
-  <clang-tidy/checks/modernize/return-braced-init-list>` check to no longer
+<clang-tidy/checks/modernize/return-braced-init-list>` check to no longer
   rewrite the return value when the constructed type has a
   `std::initializer_list` constructor, as the braced form could select a
   different constructor.
 
 - Improved {doc}`readability-named-parameter
-  <clang-tidy/checks/readability/named-parameter>` check by ignoring
+<clang-tidy/checks/readability/named-parameter>` check by ignoring
   standard tag types (e.g. `std::in_place_t`, `std::allocator_arg_t`,
   `std::nothrow_t`, iterator tags, lock tags, etc.) that are used
   exclusively for overload resolution. Added the {option}`IgnoredTypes`
   option to allow customizing the set of ignored types.
 
 - Improved {doc}`readability-use-std-min-max
-  <clang-tidy/checks/readability/use-std-min-max>` check by fixing spurious
+<clang-tidy/checks/readability/use-std-min-max>` check by fixing spurious
   trailing semicolons and lost comments when the `if` body has no braces.
 
 #### Removed checks
@@ -141,4 +147,3 @@ infrastructure are described first, followed by 
tool-specific sections.
 ### Improvements to pp-trace
 
 ### Clang-tidy Visual Studio plugin
-
diff --git 
a/clang-tools-extra/test/clang-tidy/infrastructure/line-filter-config.cpp 
b/clang-tools-extra/test/clang-tidy/infrastructure/line-filter-config.cpp
new file mode 100644
index 0000000000000..5a79ba2f39012
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/infrastructure/line-filter-config.cpp
@@ -0,0 +1,18 @@
+// RUN: clang-tidy -checks='-*,modernize-use-using' -config="{LineFilter: 
[{name: 'line-filter-config.cpp', lines: [[8, 8]]}]}" %s -- 2>&1 | FileCheck 
--check-prefix=CONFIG %s
+// RUN: clang-tidy -checks='-*,modernize-use-using' -config="{LineFilter: 
[{name: 'line-filter-config.cpp', lines: [[8, 8]]}]}" -line-filter="[{name: 
'line-filter-config.cpp', lines: [[12, 12]]}]" %s -- 2>&1 | FileCheck 
--check-prefix=CLI %s
+
+typedef int BeforeLineFilter;
+// CONFIG-NOT: :[[@LINE-1]]:1: warning: use 'using' instead of 'typedef'
+// CLI-NOT: :[[@LINE-2]]:1: warning: use 'using' instead of 'typedef'
+
+typedef int ConfigWarn;
+// CONFIG: :[[@LINE-1]]:1: warning: use 'using' instead of 'typedef' 
[modernize-use-using]
+// CLI-NOT: :[[@LINE-2]]:1: warning: use 'using' instead of 'typedef'
+
+typedef int CliWarn;
+// CONFIG-NOT: :[[@LINE-1]]:1: warning: use 'using' instead of 'typedef'
+// CLI: :[[@LINE-2]]:1: warning: use 'using' instead of 'typedef' 
[modernize-use-using]
+
+typedef int AfterLineFilter;
+// CONFIG-NOT: :[[@LINE-1]]:1: warning: use 'using' instead of 'typedef'
+// CLI-NOT: :[[@LINE-2]]:1: warning: use 'using' instead of 'typedef'
diff --git a/clang-tools-extra/unittests/clang-tidy/ClangTidyOptionsTest.cpp 
b/clang-tools-extra/unittests/clang-tidy/ClangTidyOptionsTest.cpp
index 3f86f65c1ce65..78e3f7abd2d70 100644
--- a/clang-tools-extra/unittests/clang-tidy/ClangTidyOptionsTest.cpp
+++ b/clang-tools-extra/unittests/clang-tidy/ClangTidyOptionsTest.cpp
@@ -75,6 +75,21 @@ TEST(ParseLineFilter, ValidFilter) {
   EXPECT_EQ(1000u, Options.LineFilter[2].LineRanges[0].second);
 }
 
+TEST(ClangTidyOptions, PassesLineFilter) {
+  EXPECT_TRUE(passesLineFilter({}, "a.cpp", 1));
+
+  std::vector<FileFilter> Filters = {{"file.cpp", {{10, 12}, {20, 20}}}};
+  EXPECT_TRUE(passesLineFilter(Filters, "/path/file.cpp", 10));
+  EXPECT_TRUE(passesLineFilter(Filters, "/path/file.cpp", 12));
+  EXPECT_TRUE(passesLineFilter(Filters, "/path/file.cpp", 20));
+  EXPECT_FALSE(passesLineFilter(Filters, "/path/file.cpp", 13));
+  EXPECT_FALSE(passesLineFilter(Filters, "/path/other.cpp", 10));
+
+  Filters = {{"header.h", {}}};
+  EXPECT_TRUE(passesLineFilter(Filters, "/path/header.h", 999));
+  EXPECT_FALSE(passesLineFilter(Filters, "/path/file.cpp", 1));
+}
+
 TEST(ParseConfiguration, ValidConfiguration) {
   llvm::ErrorOr<ClangTidyOptions> Options =
       parseConfiguration(llvm::MemoryBufferRef(

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

Reply via email to