[clang-tools-extra] 3c88abe - [clang-tidy][readability-identifier-length] Add a line count threshold (#185319)

via cfe-commits cfe-commits at lists.llvm.org
Tue Apr 21 00:58:25 PDT 2026


Author: Alex Dutka
Date: 2026-04-21T15:58:20+08:00
New Revision: 3c88abe3206bb944566ff4b62aa4b9874327f37d

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

LOG: [clang-tidy][readability-identifier-length] Add a line count threshold (#185319)

This PR implements the feature described in #185318

A new parameter named `LineCountThreshold` is added to the
`readability-identifier-length` check, which controls how many lines of
code must separate the the last use of a variable from its declaration
for the check to warn. For backwards-compatibility, the default value
for this parameter is set to 0.

Increasing the threshold to 1 allows for short names in one-liners (for
example: `std::transform(..., [](auto i){ return i*i; });`), and in the
general case with `LineCountThreshold = N` a variable is allowed to have
a shorter name than otherwise required if it is never used again after
`N` lines (including its declaration line).

This feature is implemented ~using a secondary `MatchFinder`~ by calling
the `utils::decl_ref_expr::allDeclRefExprs` helper function for each
variable with a short name. For performance reasons, the new piece of
code is short-circuited if `LineCountThreshold` is set to 0 (the default
value).

Added: 
    clang-tools-extra/test/clang-tidy/checkers/readability/identifier-length-line-count-threshold.cpp

Modified: 
    clang-tools-extra/clang-tidy/readability/IdentifierLengthCheck.cpp
    clang-tools-extra/clang-tidy/readability/IdentifierLengthCheck.h
    clang-tools-extra/docs/ReleaseNotes.rst
    clang-tools-extra/docs/clang-tidy/checks/readability/identifier-length.rst

Removed: 
    


################################################################################
diff  --git a/clang-tools-extra/clang-tidy/readability/IdentifierLengthCheck.cpp b/clang-tools-extra/clang-tidy/readability/IdentifierLengthCheck.cpp
index a6204de16224d..7182d424c0273 100644
--- a/clang-tools-extra/clang-tidy/readability/IdentifierLengthCheck.cpp
+++ b/clang-tools-extra/clang-tidy/readability/IdentifierLengthCheck.cpp
@@ -7,6 +7,7 @@
 //===----------------------------------------------------------------------===//
 
 #include "IdentifierLengthCheck.h"
+#include "../utils/DeclRefExprUtils.h"
 #include "clang/ASTMatchers/ASTMatchFinder.h"
 
 using namespace clang::ast_matchers;
@@ -21,6 +22,7 @@ const char DefaultIgnoredLoopCounterNames[] = "^[ijk_]$";
 const char DefaultIgnoredVariableNames[] = "";
 const char DefaultIgnoredExceptionVariableNames[] = "^[e]$";
 const char DefaultIgnoredParameterNames[] = "^[n]$";
+const unsigned DefaultLineCountThreshold = 0;
 
 const char ErrorMessage[] =
     "%select{variable|exception variable|loop variable|"
@@ -49,7 +51,9 @@ IdentifierLengthCheck::IdentifierLengthCheck(StringRef Name,
       IgnoredExceptionVariableNames(IgnoredExceptionVariableNamesInput),
       IgnoredParameterNamesInput(
           Options.get("IgnoredParameterNames", DefaultIgnoredParameterNames)),
-      IgnoredParameterNames(IgnoredParameterNamesInput) {}
+      IgnoredParameterNames(IgnoredParameterNamesInput),
+      LineCountThreshold(
+          Options.get("LineCountThreshold", DefaultLineCountThreshold)) {}
 
 void IdentifierLengthCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
   Options.store(Opts, "MinimumVariableNameLength", MinimumVariableNameLength);
@@ -62,6 +66,7 @@ void IdentifierLengthCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
   Options.store(Opts, "IgnoredExceptionVariableNames",
                 IgnoredExceptionVariableNamesInput);
   Options.store(Opts, "IgnoredParameterNames", IgnoredParameterNamesInput);
+  Options.store(Opts, "LineCountThreshold", LineCountThreshold);
 }
 
 void IdentifierLengthCheck::registerMatchers(MatchFinder *Finder) {
@@ -85,6 +90,39 @@ void IdentifierLengthCheck::registerMatchers(MatchFinder *Finder) {
         this);
 }
 
+static std::optional<unsigned> countLinesToLastUse(const VarDecl *Var,
+                                                   const SourceManager *SrcMgr,
+                                                   ASTContext *Ctx) {
+  const auto *ParentScope = llvm::dyn_cast<FunctionDecl>(Var->getDeclContext());
+  if (ParentScope == nullptr)
+    return std::nullopt;
+
+  auto AllRefs =
+      utils::decl_ref_expr::allDeclRefExprs(*Var, *ParentScope, *Ctx);
+
+  const unsigned DeclLine = SrcMgr->getSpellingLineNumber(Var->getLocation());
+  const unsigned LastUseLine = std::transform_reduce(
+      AllRefs.begin(), AllRefs.end(), DeclLine,
+      [](unsigned Lhs, unsigned Rhs) -> unsigned { return std::max(Lhs, Rhs); },
+      [&](const DeclRefExpr *RefToVar) -> unsigned {
+        return SrcMgr->getSpellingLineNumber(RefToVar->getLocation());
+      });
+
+  return LastUseLine - DeclLine + 1;
+}
+
+static bool isShortLived(const VarDecl *Var, const SourceManager *SrcMgr,
+                         ASTContext *Ctx, unsigned LineCountThreshold) {
+  if (LineCountThreshold == 0)
+    return false;
+
+  std::optional<unsigned> LineCount = countLinesToLastUse(Var, SrcMgr, Ctx);
+  if (LineCount && LineCount.value() <= LineCountThreshold)
+    return true;
+
+  return false;
+}
+
 void IdentifierLengthCheck::check(const MatchFinder::MatchResult &Result) {
   const auto *StandaloneVar = Result.Nodes.getNodeAs<VarDecl>("standaloneVar");
   if (StandaloneVar) {
@@ -97,6 +135,10 @@ void IdentifierLengthCheck::check(const MatchFinder::MatchResult &Result) {
         IgnoredVariableNames.match(VarName))
       return;
 
+    if (isShortLived(StandaloneVar, Result.SourceManager, Result.Context,
+                     LineCountThreshold))
+      return;
+
     diag(StandaloneVar->getLocation(), ErrorMessage)
         << 0 << StandaloneVar << MinimumVariableNameLength;
   }
@@ -111,6 +153,10 @@ void IdentifierLengthCheck::check(const MatchFinder::MatchResult &Result) {
         IgnoredExceptionVariableNames.match(VarName))
       return;
 
+    if (isShortLived(ExceptionVarName, Result.SourceManager, Result.Context,
+                     LineCountThreshold))
+      return;
+
     diag(ExceptionVarName->getLocation(), ErrorMessage)
         << 1 << ExceptionVarName << MinimumExceptionNameLength;
   }
@@ -126,6 +172,10 @@ void IdentifierLengthCheck::check(const MatchFinder::MatchResult &Result) {
         IgnoredLoopCounterNames.match(VarName))
       return;
 
+    if (isShortLived(LoopVar, Result.SourceManager, Result.Context,
+                     LineCountThreshold))
+      return;
+
     diag(LoopVar->getLocation(), ErrorMessage)
         << 2 << LoopVar << MinimumLoopCounterNameLength;
   }
@@ -141,6 +191,10 @@ void IdentifierLengthCheck::check(const MatchFinder::MatchResult &Result) {
         IgnoredParameterNames.match(VarName))
       return;
 
+    if (isShortLived(ParamVar, Result.SourceManager, Result.Context,
+                     LineCountThreshold))
+      return;
+
     diag(ParamVar->getLocation(), ErrorMessage)
         << 3 << ParamVar << MinimumParameterNameLength;
   }

diff  --git a/clang-tools-extra/clang-tidy/readability/IdentifierLengthCheck.h b/clang-tools-extra/clang-tidy/readability/IdentifierLengthCheck.h
index 3adaf50bc57a1..2a59788260f8c 100644
--- a/clang-tools-extra/clang-tidy/readability/IdentifierLengthCheck.h
+++ b/clang-tools-extra/clang-tidy/readability/IdentifierLengthCheck.h
@@ -42,6 +42,8 @@ class IdentifierLengthCheck : public ClangTidyCheck {
 
   std::string IgnoredParameterNamesInput;
   llvm::Regex IgnoredParameterNames;
+
+  const unsigned LineCountThreshold;
 };
 
 } // namespace clang::tidy::readability

diff  --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst
index 95ed0061d654c..777ff064c08b7 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -498,6 +498,11 @@ Changes in existing checks
   now uses separate note diagnostics for each uninitialized enumerator, making
   it easier to see which specific enumerators need explicit initialization.
 
+- Improved :doc:`readability-identifier-length
+  <clang-tidy/checks/readability/identifier-length>` check by adding a new
+  option, named `LineCountThreshold`, to silence warnings for short-lived
+  variables, based on distance between declaration and last use.
+
 - Improved :doc:`readability-identifier-naming
   <clang-tidy/checks/readability/identifier-naming>` check:
 

diff  --git a/clang-tools-extra/docs/clang-tidy/checks/readability/identifier-length.rst b/clang-tools-extra/docs/clang-tidy/checks/readability/identifier-length.rst
index c4d39a28a4cb8..fce9592e699fb 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/readability/identifier-length.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/readability/identifier-length.rst
@@ -18,6 +18,7 @@ The following options are described below:
  - :option:`MinimumLoopCounterNameLength`, :option:`IgnoredLoopCounterNames`
  - :option:`MinimumExceptionNameLength`,
    :option:`IgnoredExceptionVariableNames`
+ - :option:`LineCountThreshold`
 
 .. option:: MinimumVariableNameLength
 
@@ -121,3 +122,20 @@ The following options are described below:
       catch (const std::exception& e) {
           // ...
       }
+
+.. option:: LineCountThreshold
+
+    Defines the minimum number of lines required between declaration and last
+    use for a diagnostic to be issued. The default value for this option is 0,
+    which corresponds to all variables being flagged. This option only affects
+    the behavior regarding local variables: a warning is always issued when a
+    global variable has a short name, because globals can potentially be used
+    across multiple files.
+
+    .. code-block:: c++
+
+      // In this example, a warning will be issued if LineCountThreshold < N
+      int a = 0;      // First line (declaration line)
+      a = 1;          // Second line
+                      // ...
+      last_use_of(a); // N-th line

diff  --git a/clang-tools-extra/test/clang-tidy/checkers/readability/identifier-length-line-count-threshold.cpp b/clang-tools-extra/test/clang-tidy/checkers/readability/identifier-length-line-count-threshold.cpp
new file mode 100644
index 0000000000000..17e75053eabfd
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/readability/identifier-length-line-count-threshold.cpp
@@ -0,0 +1,85 @@
+// RUN: %check_clang_tidy %s readability-identifier-length %t \
+// RUN: -config='{CheckOptions: \
+// RUN:  {readability-identifier-length.LineCountThreshold: 3}}' \
+// RUN: -- -fexceptions
+
+struct myexcept {
+  int val;
+};
+
+template<typename... Ts>
+void doIt(Ts...);
+
+#define MY_MACRO(arg) doIt(arg, arg)
+
+int g = 0;
+// CHECK-MESSAGES: :[[@LINE-1]]:5: warning: variable name 'g' is too short, expected at least 3 characters [readability-identifier-length]
+
+void shouldWarn(int z)
+// CHECK-MESSAGES: :[[@LINE-1]]:21: warning: parameter name 'z' is too short, expected at least 3 characters [readability-identifier-length]
+{
+  int i = 5;
+  // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: variable name 'i' is too short, expected at least 3 characters [readability-identifier-length]
+  ++i;
+
+  for (int m = 0; m < 5; ++m)
+  // CHECK-MESSAGES: :[[@LINE-1]]:12: warning: loop variable name 'm' is too short, expected at least 2 characters [readability-identifier-length]
+  {
+    doIt(i);
+    doIt(m);
+  }
+
+  try {
+    doIt(z);
+  } catch (const myexcept &x)
+  // CHECK-MESSAGES: :[[@LINE-1]]:28: warning: exception variable name 'x' is too short, expected at least 2 characters [readability-identifier-length]
+  {
+    doIt(x);
+  }
+
+  int a = 0;
+  // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: variable name 'a' is too short, expected at least 3 characters [readability-identifier-length]
+  ++a;
+  MY_MACRO(a);
+
+  int b = 0;
+  // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: variable name 'b' is too short, expected at least 3 characters [readability-identifier-length]
+  [&](){
+    doIt(b);
+  }();
+
+  int c = 0;
+  // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: variable name 'c' is too short, expected at least 3 characters [readability-identifier-length]
+  [=](){
+    doIt(c);
+  }();
+}
+
+void shouldNotWarn(int m)
+{
+  doIt(m);
+
+  int v = 5;
+  ++v;
+  doIt(v);
+
+  for (int a = 0; a < 42; ++a)
+  {
+      doIt(a);
+  }
+
+  try {
+    doIt();
+  } catch (const myexcept &x) {
+    doIt(x);
+  }
+
+  int a = 0;
+  MY_MACRO(a);
+
+  int b = 0;
+  [&](){ doIt(b); }();
+
+  int c = 0;
+  [=](){ doIt(c); }();
+}


        


More information about the cfe-commits mailing list