https://github.com/unterumarmung updated 
https://github.com/llvm/llvm-project/pull/180404

>From 5e6ce37d89246794a27b634fc24fcace5dac818f Mon Sep 17 00:00:00 2001
From: Daniil Dudkin <[email protected]>
Date: Sat, 28 Feb 2026 21:55:34 +0300
Subject: [PATCH 1/4] [clang-tidy][NFC] Add getCommentsInRange utility

Introduce getCommentsInRange in LexerUtils and refactor 
getTrailingCommentsInRange to reuse a shared comment collector with mode-based 
behavior. Add unit tests for all-comments behavior and preserve existing 
trailing-comment semantics.
---
 .../clang-tidy/utils/LexerUtils.cpp           |  27 +++-
 .../clang-tidy/utils/LexerUtils.h             |   5 +
 .../unittests/clang-tidy/LexerUtilsTest.cpp   | 119 ++++++++++++++++++
 3 files changed, 147 insertions(+), 4 deletions(-)

diff --git a/clang-tools-extra/clang-tidy/utils/LexerUtils.cpp 
b/clang-tools-extra/clang-tidy/utils/LexerUtils.cpp
index a9a8c7bbf4c89..4c36d4cacd1ec 100644
--- a/clang-tools-extra/clang-tidy/utils/LexerUtils.cpp
+++ b/clang-tools-extra/clang-tidy/utils/LexerUtils.cpp
@@ -100,9 +100,14 @@ bool rangeContainsExpansionsOrDirectives(SourceRange Range,
   return false;
 }
 
-std::vector<CommentToken>
-getTrailingCommentsInRange(CharSourceRange Range, const SourceManager &SM,
-                           const LangOptions &LangOpts) {
+namespace {
+enum class CommentCollectionMode { AllComments, TrailingComments };
+} // namespace
+
+static std::vector<CommentToken>
+collectCommentsInRange(CharSourceRange Range, const SourceManager &SM,
+                       const LangOptions &LangOpts,
+                       CommentCollectionMode Mode) {
   std::vector<CommentToken> Comments;
   if (Range.isInvalid())
     return Comments;
@@ -149,7 +154,7 @@ getTrailingCommentsInRange(CharSourceRange Range, const 
SourceManager &SM,
           Tok.getLocation(),
           StringRef(Buffer.begin() + CommentLoc.second, Tok.getLength()),
       });
-    } else {
+    } else if (Mode == CommentCollectionMode::TrailingComments) {
       // Clear comments found before the different token, e.g. comma. Callers
       // use this to retrieve only the contiguous comment block that directly
       // precedes a token of interest.
@@ -160,6 +165,20 @@ getTrailingCommentsInRange(CharSourceRange Range, const 
SourceManager &SM,
   return Comments;
 }
 
+std::vector<CommentToken> getCommentsInRange(CharSourceRange Range,
+                                             const SourceManager &SM,
+                                             const LangOptions &LangOpts) {
+  return collectCommentsInRange(Range, SM, LangOpts,
+                                CommentCollectionMode::AllComments);
+}
+
+std::vector<CommentToken>
+getTrailingCommentsInRange(CharSourceRange Range, const SourceManager &SM,
+                           const LangOptions &LangOpts) {
+  return collectCommentsInRange(Range, SM, LangOpts,
+                                CommentCollectionMode::TrailingComments);
+}
+
 std::optional<Token> getQualifyingToken(tok::TokenKind TK,
                                         CharSourceRange Range,
                                         const ASTContext &Context,
diff --git a/clang-tools-extra/clang-tidy/utils/LexerUtils.h 
b/clang-tools-extra/clang-tidy/utils/LexerUtils.h
index 38123ae14cff7..681fc6194d45c 100644
--- a/clang-tools-extra/clang-tidy/utils/LexerUtils.h
+++ b/clang-tools-extra/clang-tidy/utils/LexerUtils.h
@@ -120,6 +120,11 @@ struct CommentToken {
   StringRef Text;
 };
 
+/// Returns all comment tokens found in the given range.
+std::vector<CommentToken> getCommentsInRange(CharSourceRange Range,
+                                             const SourceManager &SM,
+                                             const LangOptions &LangOpts);
+
 /// Returns comment tokens found in the given range. If a non-comment token is
 /// encountered, clears previously collected comments and continues.
 std::vector<CommentToken>
diff --git a/clang-tools-extra/unittests/clang-tidy/LexerUtilsTest.cpp 
b/clang-tools-extra/unittests/clang-tidy/LexerUtilsTest.cpp
index 438a78b4694ee..0e0c3d60dedfe 100644
--- a/clang-tools-extra/unittests/clang-tidy/LexerUtilsTest.cpp
+++ b/clang-tools-extra/unittests/clang-tidy/LexerUtilsTest.cpp
@@ -43,6 +43,125 @@ static CharSourceRange rangeFromAnnotations(const 
llvm::Annotations &A,
 
 namespace {
 
+TEST(LexerUtilsTest, GetCommentsInRangeAdjacentComments) {
+  llvm::Annotations Code(R"cpp(
+void f() {
+  $range[[/*first*/ /*second*/]]
+  int x = 0;
+}
+)cpp");
+  std::unique_ptr<ASTUnit> AST = buildAST(Code.code());
+  ASSERT_TRUE(AST);
+  const ASTContext &Context = AST->getASTContext();
+  const SourceManager &SM = Context.getSourceManager();
+  const LangOptions &LangOpts = Context.getLangOpts();
+
+  const CharSourceRange Range =
+      rangeFromAnnotations(Code, SM, SM.getMainFileID(), "range");
+  const std::vector<utils::lexer::CommentToken> Comments =
+      utils::lexer::getCommentsInRange(Range, SM, LangOpts);
+  ASSERT_EQ(2u, Comments.size());
+  EXPECT_EQ("/*first*/", Comments[0].Text);
+  EXPECT_EQ("/*second*/", Comments[1].Text);
+  const StringRef CodeText = Code.code();
+  const size_t FirstOffset = CodeText.find("/*first*/");
+  ASSERT_NE(StringRef::npos, FirstOffset);
+  const size_t SecondOffset = CodeText.find("/*second*/");
+  ASSERT_NE(StringRef::npos, SecondOffset);
+  EXPECT_EQ(FirstOffset, SM.getFileOffset(Comments[0].Loc));
+  EXPECT_EQ(SecondOffset, SM.getFileOffset(Comments[1].Loc));
+}
+
+TEST(LexerUtilsTest, GetCommentsInRangeKeepsCommentsAcrossTokens) {
+  llvm::Annotations Code(R"cpp(
+void f() {
+  int x = ($range[[/*first*/ 0, /*second*/]] 1);
+}
+)cpp");
+  std::unique_ptr<ASTUnit> AST = buildAST(Code.code());
+  ASSERT_TRUE(AST);
+  const ASTContext &Context = AST->getASTContext();
+  const SourceManager &SM = Context.getSourceManager();
+  const LangOptions &LangOpts = Context.getLangOpts();
+
+  const CharSourceRange Range =
+      rangeFromAnnotations(Code, SM, SM.getMainFileID(), "range");
+  const std::vector<utils::lexer::CommentToken> Comments =
+      utils::lexer::getCommentsInRange(Range, SM, LangOpts);
+  ASSERT_EQ(2u, Comments.size());
+  EXPECT_EQ("/*first*/", Comments[0].Text);
+  EXPECT_EQ("/*second*/", Comments[1].Text);
+  const StringRef CodeText = Code.code();
+  const size_t FirstOffset = CodeText.find("/*first*/");
+  ASSERT_NE(StringRef::npos, FirstOffset);
+  const size_t SecondOffset = CodeText.find("/*second*/");
+  ASSERT_NE(StringRef::npos, SecondOffset);
+  EXPECT_EQ(FirstOffset, SM.getFileOffset(Comments[0].Loc));
+  EXPECT_EQ(SecondOffset, SM.getFileOffset(Comments[1].Loc));
+}
+
+TEST(LexerUtilsTest, GetCommentsInRangeLineComments) {
+  llvm::Annotations Code(R"cpp(
+void f() {
+  $range[[// first
+  // second
+  ]]
+  int x = 0;
+}
+)cpp");
+  std::unique_ptr<ASTUnit> AST = buildAST(Code.code());
+  ASSERT_TRUE(AST);
+  const ASTContext &Context = AST->getASTContext();
+  const SourceManager &SM = Context.getSourceManager();
+  const LangOptions &LangOpts = Context.getLangOpts();
+
+  const CharSourceRange Range =
+      rangeFromAnnotations(Code, SM, SM.getMainFileID(), "range");
+  const std::vector<utils::lexer::CommentToken> Comments =
+      utils::lexer::getCommentsInRange(Range, SM, LangOpts);
+  ASSERT_EQ(2u, Comments.size());
+  EXPECT_EQ("// first", Comments[0].Text);
+  EXPECT_EQ("// second", Comments[1].Text);
+  const StringRef CodeText = Code.code();
+  const size_t FirstOffset = CodeText.find("// first");
+  ASSERT_NE(StringRef::npos, FirstOffset);
+  const size_t SecondOffset = CodeText.find("// second");
+  ASSERT_NE(StringRef::npos, SecondOffset);
+  EXPECT_EQ(FirstOffset, SM.getFileOffset(Comments[0].Loc));
+  EXPECT_EQ(SecondOffset, SM.getFileOffset(Comments[1].Loc));
+}
+
+TEST(LexerUtilsTest, GetCommentsInRangeNoComments) {
+  llvm::Annotations Code(R"cpp(
+void f() {
+  int x = $range[[0 + 1]];
+}
+)cpp");
+  std::unique_ptr<ASTUnit> AST = buildAST(Code.code());
+  ASSERT_TRUE(AST);
+  const ASTContext &Context = AST->getASTContext();
+  const SourceManager &SM = Context.getSourceManager();
+  const LangOptions &LangOpts = Context.getLangOpts();
+
+  const CharSourceRange Range =
+      rangeFromAnnotations(Code, SM, SM.getMainFileID(), "range");
+  const std::vector<utils::lexer::CommentToken> Comments =
+      utils::lexer::getCommentsInRange(Range, SM, LangOpts);
+  EXPECT_TRUE(Comments.empty());
+}
+
+TEST(LexerUtilsTest, GetCommentsInRangeInvalidRange) {
+  std::unique_ptr<ASTUnit> AST = buildAST("int value = 0;");
+  ASSERT_TRUE(AST);
+  const ASTContext &Context = AST->getASTContext();
+  const SourceManager &SM = Context.getSourceManager();
+  const LangOptions &LangOpts = Context.getLangOpts();
+
+  const std::vector<utils::lexer::CommentToken> Comments =
+      utils::lexer::getCommentsInRange(CharSourceRange(), SM, LangOpts);
+  EXPECT_TRUE(Comments.empty());
+}
+
 TEST(LexerUtilsTest, GetTrailingCommentsInRangeAdjacentComments) {
   llvm::Annotations Code(R"cpp(
 void f() {

>From 36cd9ae4b1eed157b68dd9ce326b6fd7f48167f7 Mon Sep 17 00:00:00 2001
From: Daniil Dudkin <[email protected]>
Date: Sat, 28 Feb 2026 22:08:22 +0300
Subject: [PATCH 2/4] [clang-tidy][NFC] Add findTokenInRange and reuse it in
 ExplicitConstructorCheck

---
 .../google/ExplicitConstructorCheck.cpp       |  41 +---
 .../clang-tidy/utils/LexerUtils.cpp           |  53 +++++
 .../clang-tidy/utils/LexerUtils.h             |   8 +
 .../unittests/clang-tidy/LexerUtilsTest.cpp   | 206 ++++++++++++++++++
 4 files changed, 274 insertions(+), 34 deletions(-)

diff --git a/clang-tools-extra/clang-tidy/google/ExplicitConstructorCheck.cpp 
b/clang-tools-extra/clang-tidy/google/ExplicitConstructorCheck.cpp
index ac604b7b9f1b4..ba3af5762f27e 100644
--- a/clang-tools-extra/clang-tidy/google/ExplicitConstructorCheck.cpp
+++ b/clang-tools-extra/clang-tidy/google/ExplicitConstructorCheck.cpp
@@ -7,10 +7,10 @@
 
//===----------------------------------------------------------------------===//
 
 #include "ExplicitConstructorCheck.h"
+#include "../utils/LexerUtils.h"
 #include "clang/AST/ASTContext.h"
 #include "clang/ASTMatchers/ASTMatchFinder.h"
 #include "clang/ASTMatchers/ASTMatchers.h"
-#include "clang/Lex/Lexer.h"
 
 using namespace clang::ast_matchers;
 
@@ -31,32 +31,6 @@ void ExplicitConstructorCheck::registerMatchers(MatchFinder 
*Finder) {
       this);
 }
 
-// Looks for the token matching the predicate and returns the range of the 
found
-// token including trailing whitespace.
-static SourceRange findToken(const SourceManager &Sources,
-                             const LangOptions &LangOpts,
-                             SourceLocation StartLoc, SourceLocation EndLoc,
-                             bool (*Pred)(const Token &)) {
-  if (StartLoc.isMacroID() || EndLoc.isMacroID())
-    return {};
-  const FileID File = Sources.getFileID(Sources.getSpellingLoc(StartLoc));
-  const StringRef Buf = Sources.getBufferData(File);
-  const char *StartChar = Sources.getCharacterData(StartLoc);
-  Lexer Lex(StartLoc, LangOpts, StartChar, StartChar, Buf.end());
-  Lex.SetCommentRetentionState(true);
-  Token Tok;
-  do {
-    Lex.LexFromRawLexer(Tok);
-    if (Pred(Tok)) {
-      Token NextTok;
-      Lex.LexFromRawLexer(NextTok);
-      return {Tok.getLocation(), NextTok.getLocation()};
-    }
-  } while (Tok.isNot(tok::eof) && Tok.getLocation() < EndLoc);
-
-  return {};
-}
-
 static bool declIsStdInitializerList(const NamedDecl *D) {
   // First use the fast getName() method to avoid unnecessary calls to the
   // slow getQualifiedNameAsString().
@@ -113,9 +87,10 @@ void ExplicitConstructorCheck::check(const 
MatchFinder::MatchResult &Result) {
       return Tok.is(tok::raw_identifier) &&
              Tok.getRawIdentifier() == "explicit";
     };
-    const SourceRange ExplicitTokenRange =
-        findToken(*Result.SourceManager, getLangOpts(),
-                  Ctor->getOuterLocStart(), Ctor->getEndLoc(), IsKwExplicit);
+    const CharSourceRange ConstructorRange = CharSourceRange::getTokenRange(
+        Ctor->getOuterLocStart(), Ctor->getEndLoc());
+    const CharSourceRange ExplicitTokenRange = utils::lexer::findTokenInRange(
+        ConstructorRange, *Result.SourceManager, getLangOpts(), IsKwExplicit);
     StringRef ConstructorDescription;
     if (Ctor->isMoveConstructor())
       ConstructorDescription = "move";
@@ -127,10 +102,8 @@ void ExplicitConstructorCheck::check(const 
MatchFinder::MatchResult &Result) {
     auto Diag = diag(Ctor->getLocation(),
                      "%0 constructor should not be declared explicit")
                 << ConstructorDescription;
-    if (ExplicitTokenRange.isValid()) {
-      Diag << FixItHint::CreateRemoval(
-          CharSourceRange::getCharRange(ExplicitTokenRange));
-    }
+    if (ExplicitTokenRange.isValid())
+      Diag << FixItHint::CreateRemoval(ExplicitTokenRange);
     return;
   }
 
diff --git a/clang-tools-extra/clang-tidy/utils/LexerUtils.cpp 
b/clang-tools-extra/clang-tidy/utils/LexerUtils.cpp
index 4c36d4cacd1ec..75842542b63a1 100644
--- a/clang-tools-extra/clang-tidy/utils/LexerUtils.cpp
+++ b/clang-tools-extra/clang-tidy/utils/LexerUtils.cpp
@@ -179,6 +179,59 @@ getTrailingCommentsInRange(CharSourceRange Range, const 
SourceManager &SM,
                                 CommentCollectionMode::TrailingComments);
 }
 
+CharSourceRange findTokenInRange(CharSourceRange Range, const SourceManager 
&SM,
+                                 const LangOptions &LangOpts,
+                                 llvm::function_ref<bool(const Token &)> Pred) 
{
+  if (Range.isInvalid())
+    return {};
+
+  // Normalize to a file-based char range so raw lexing can operate on one
+  // contiguous buffer and reject unmappable (e.g. macro) ranges.
+  const CharSourceRange FileRange =
+      Lexer::makeFileCharRange(Range, SM, LangOpts);
+  if (FileRange.isInvalid())
+    return {};
+
+  const auto [BeginFID, BeginOffset] =
+      SM.getDecomposedLoc(FileRange.getBegin());
+  const auto [EndFID, EndOffset] = SM.getDecomposedLoc(FileRange.getEnd());
+  if (BeginFID != EndFID || BeginOffset > EndOffset)
+    return {};
+
+  bool Invalid = false;
+  const StringRef Buffer = SM.getBufferData(BeginFID, &Invalid);
+  if (Invalid)
+    return {};
+
+  const char *LexStart = Buffer.data() + BeginOffset;
+  // Re-lex raw tokens in the bounded file buffer while preserving comments so
+  // callers can match tokens regardless of interleaved comments.
+  Lexer TheLexer(SM.getLocForStartOfFile(BeginFID), LangOpts, Buffer.begin(),
+                 LexStart, Buffer.end());
+  TheLexer.SetCommentRetentionState(true);
+
+  while (true) {
+    Token Tok;
+    if (TheLexer.LexFromRawLexer(Tok))
+      return {};
+
+    if (Tok.is(tok::eof) || Tok.getLocation() == FileRange.getEnd() ||
+        SM.isBeforeInTranslationUnit(FileRange.getEnd(), Tok.getLocation()))
+      return {};
+
+    if (!Pred(Tok))
+      continue;
+
+    Token NextTok;
+    if (TheLexer.LexFromRawLexer(NextTok))
+      return {};
+    // Return a char range ending at the next token start so trailing trivia of
+    // the matched token is included (useful for fix-it removals).
+    return CharSourceRange::getCharRange(Tok.getLocation(),
+                                         NextTok.getLocation());
+  }
+}
+
 std::optional<Token> getQualifyingToken(tok::TokenKind TK,
                                         CharSourceRange Range,
                                         const ASTContext &Context,
diff --git a/clang-tools-extra/clang-tidy/utils/LexerUtils.h 
b/clang-tools-extra/clang-tidy/utils/LexerUtils.h
index 681fc6194d45c..f89ca919ac95a 100644
--- a/clang-tools-extra/clang-tidy/utils/LexerUtils.h
+++ b/clang-tools-extra/clang-tidy/utils/LexerUtils.h
@@ -12,6 +12,7 @@
 #include "clang/AST/ASTContext.h"
 #include "clang/Basic/TokenKinds.h"
 #include "clang/Lex/Lexer.h"
+#include "llvm/ADT/STLFunctionalExtras.h"
 #include <optional>
 #include <utility>
 #include <vector>
@@ -131,6 +132,13 @@ std::vector<CommentToken>
 getTrailingCommentsInRange(CharSourceRange Range, const SourceManager &SM,
                            const LangOptions &LangOpts);
 
+/// Returns the first token in \p Range matching \p Pred.
+/// The returned char range starts at the matched token and ends at the start
+/// of the next token. Returns invalid range if no token matches.
+CharSourceRange findTokenInRange(CharSourceRange Range, const SourceManager 
&SM,
+                                 const LangOptions &LangOpts,
+                                 llvm::function_ref<bool(const Token &)> Pred);
+
 /// Assuming that ``Range`` spans a CVR-qualified type, returns the
 /// token in ``Range`` that is responsible for the qualification. ``Range``
 /// must be valid with respect to ``SM``.  Returns ``std::nullopt`` if no
diff --git a/clang-tools-extra/unittests/clang-tidy/LexerUtilsTest.cpp 
b/clang-tools-extra/unittests/clang-tidy/LexerUtilsTest.cpp
index 0e0c3d60dedfe..9360e6fe9b14f 100644
--- a/clang-tools-extra/unittests/clang-tidy/LexerUtilsTest.cpp
+++ b/clang-tools-extra/unittests/clang-tidy/LexerUtilsTest.cpp
@@ -8,6 +8,7 @@
 
 #include "../clang-tidy/utils/LexerUtils.h"
 
+#include "clang/AST/DeclCXX.h"
 #include "clang/Basic/FileManager.h"
 #include "clang/Basic/SourceManager.h"
 #include "clang/Frontend/ASTUnit.h"
@@ -41,8 +42,213 @@ static CharSourceRange rangeFromAnnotations(const 
llvm::Annotations &A,
   return CharSourceRange::getCharRange(Begin, End);
 }
 
+static bool isRawIdentifierNamed(const Token &Tok, StringRef Name) {
+  return Tok.is(tok::raw_identifier) && Tok.getRawIdentifier() == Name;
+}
+
+static const CXXConstructorDecl *
+findFirstNonImplicitConstructor(const ASTContext &Context) {
+  for (const Decl *D : Context.getTranslationUnitDecl()->decls()) {
+    const auto *RD = dyn_cast<CXXRecordDecl>(D);
+    if (!RD)
+      continue;
+    for (const CXXConstructorDecl *Ctor : RD->ctors())
+      if (!Ctor->isImplicit())
+        return Ctor;
+  }
+  return nullptr;
+}
+
 namespace {
 
+TEST(LexerUtilsTest, FindTokenInRangeFindsMatch) {
+  llvm::Annotations Code(R"cpp(
+struct S {
+  $range[[explicit   ]] S(int);
+};
+)cpp");
+  std::unique_ptr<ASTUnit> AST = buildAST(Code.code());
+  ASSERT_TRUE(AST);
+  const ASTContext &Context = AST->getASTContext();
+  const SourceManager &SM = Context.getSourceManager();
+  const LangOptions &LangOpts = Context.getLangOpts();
+
+  const CharSourceRange SearchRange =
+      rangeFromAnnotations(Code, SM, SM.getMainFileID(), "range");
+  const CharSourceRange MatchedRange = utils::lexer::findTokenInRange(
+      SearchRange, SM, LangOpts,
+      [](const Token &Tok) { return isRawIdentifierNamed(Tok, "explicit"); });
+  ASSERT_TRUE(MatchedRange.isValid());
+
+  const StringRef CodeText = Code.code();
+  const size_t ExplicitOffset = CodeText.find("explicit");
+  ASSERT_NE(StringRef::npos, ExplicitOffset);
+  const size_t ConstructorOffset = CodeText.find("S(int)");
+  ASSERT_NE(StringRef::npos, ConstructorOffset);
+  EXPECT_EQ(ExplicitOffset, SM.getFileOffset(MatchedRange.getBegin()));
+  EXPECT_EQ(ConstructorOffset, SM.getFileOffset(MatchedRange.getEnd()));
+}
+
+TEST(LexerUtilsTest, FindTokenInRangeReturnsInvalidWhenNotFound) {
+  llvm::Annotations Code(R"cpp(
+struct S {
+  $range[[int x = 0;]]
+  S(int);
+};
+)cpp");
+  std::unique_ptr<ASTUnit> AST = buildAST(Code.code());
+  ASSERT_TRUE(AST);
+  const ASTContext &Context = AST->getASTContext();
+  const SourceManager &SM = Context.getSourceManager();
+  const LangOptions &LangOpts = Context.getLangOpts();
+
+  const CharSourceRange SearchRange =
+      rangeFromAnnotations(Code, SM, SM.getMainFileID(), "range");
+  const CharSourceRange MatchedRange = utils::lexer::findTokenInRange(
+      SearchRange, SM, LangOpts,
+      [](const Token &Tok) { return isRawIdentifierNamed(Tok, "explicit"); });
+  EXPECT_TRUE(MatchedRange.isInvalid());
+}
+
+TEST(LexerUtilsTest, FindTokenInRangeDoesNotMatchTokenAtEndBoundary) {
+  llvm::Annotations Code(R"cpp(
+struct S {
+  $range[[int x = 0; ]]explicit S(int);
+};
+)cpp");
+  std::unique_ptr<ASTUnit> AST = buildAST(Code.code());
+  ASSERT_TRUE(AST);
+  const ASTContext &Context = AST->getASTContext();
+  const SourceManager &SM = Context.getSourceManager();
+  const LangOptions &LangOpts = Context.getLangOpts();
+
+  const CharSourceRange SearchRange =
+      rangeFromAnnotations(Code, SM, SM.getMainFileID(), "range");
+  const CharSourceRange MatchedRange = utils::lexer::findTokenInRange(
+      SearchRange, SM, LangOpts,
+      [](const Token &Tok) { return isRawIdentifierNamed(Tok, "explicit"); });
+  EXPECT_TRUE(MatchedRange.isInvalid());
+}
+
+TEST(LexerUtilsTest, FindTokenInRangeReturnsInvalidWhenPredicateNeverMatches) {
+  llvm::Annotations Code(R"cpp(
+struct S {
+  $range[[explicit ]] S(int);
+};
+)cpp");
+  std::unique_ptr<ASTUnit> AST = buildAST(Code.code());
+  ASSERT_TRUE(AST);
+  const ASTContext &Context = AST->getASTContext();
+  const SourceManager &SM = Context.getSourceManager();
+  const LangOptions &LangOpts = Context.getLangOpts();
+
+  const CharSourceRange SearchRange =
+      rangeFromAnnotations(Code, SM, SM.getMainFileID(), "range");
+  const CharSourceRange MatchedRange = utils::lexer::findTokenInRange(
+      SearchRange, SM, LangOpts, [](const Token &) { return false; });
+  EXPECT_TRUE(MatchedRange.isInvalid());
+}
+
+TEST(LexerUtilsTest, FindTokenInRangeReturnsInvalidForInvalidRange) {
+  std::unique_ptr<ASTUnit> AST = buildAST("struct S { explicit S(int); };");
+  ASSERT_TRUE(AST);
+  const ASTContext &Context = AST->getASTContext();
+  const SourceManager &SM = Context.getSourceManager();
+  const LangOptions &LangOpts = Context.getLangOpts();
+
+  const CharSourceRange MatchedRange = utils::lexer::findTokenInRange(
+      CharSourceRange(), SM, LangOpts, [](const Token &) { return true; });
+  EXPECT_TRUE(MatchedRange.isInvalid());
+}
+
+TEST(LexerUtilsTest, FindTokenInRangeReturnsInvalidForReversedOffsets) {
+  llvm::Annotations Code(R"cpp(
+struct S {
+  $a^explicit S(int);$b^
+};
+)cpp");
+  std::unique_ptr<ASTUnit> AST = buildAST(Code.code());
+  ASSERT_TRUE(AST);
+  const ASTContext &Context = AST->getASTContext();
+  const SourceManager &SM = Context.getSourceManager();
+  const LangOptions &LangOpts = Context.getLangOpts();
+
+  const SourceLocation MainFileStart =
+      SM.getLocForStartOfFile(SM.getMainFileID());
+  const SourceLocation Begin = MainFileStart.getLocWithOffset(Code.point("b"));
+  const SourceLocation End = MainFileStart.getLocWithOffset(Code.point("a"));
+  ASSERT_TRUE(SM.isBeforeInTranslationUnit(End, Begin));
+
+  const CharSourceRange ReversedRange =
+      CharSourceRange::getCharRange(Begin, End);
+  const CharSourceRange MatchedRange = utils::lexer::findTokenInRange(
+      ReversedRange, SM, LangOpts,
+      [](const Token &Tok) { return isRawIdentifierNamed(Tok, "explicit"); });
+  EXPECT_TRUE(MatchedRange.isInvalid());
+}
+
+TEST(LexerUtilsTest, FindTokenInRangeReturnsInvalidWhenFileRangeIsInvalid) {
+  llvm::Annotations Code(R"cpp(
+#include "header.h"
+int $begin^main_var = 0;
+)cpp");
+  const FileContentMappings Mappings = {
+      {"header.h", "int header_var = 0;\n"},
+  };
+  std::unique_ptr<ASTUnit> AST = buildAST(Code.code(), Mappings);
+  ASSERT_TRUE(AST);
+  const ASTContext &Context = AST->getASTContext();
+  const SourceManager &SM = Context.getSourceManager();
+  const LangOptions &LangOpts = Context.getLangOpts();
+
+  const SourceLocation MainFileStart =
+      SM.getLocForStartOfFile(SM.getMainFileID());
+  const SourceLocation Begin =
+      MainFileStart.getLocWithOffset(Code.point("begin"));
+  ASSERT_TRUE(Begin.isFileID());
+
+  auto HeaderFile = AST->getFileManager().getOptionalFileRef("header.h");
+  ASSERT_TRUE(HeaderFile.has_value());
+  const FileID HeaderFID = SM.translateFile(*HeaderFile);
+  ASSERT_TRUE(HeaderFID.isValid());
+  const SourceLocation HeaderBegin = SM.getLocForStartOfFile(HeaderFID);
+  ASSERT_TRUE(HeaderBegin.isFileID());
+
+  const CharSourceRange SearchRange =
+      CharSourceRange::getCharRange(Begin, HeaderBegin);
+  const CharSourceRange FileRange =
+      Lexer::makeFileCharRange(SearchRange, SM, LangOpts);
+  EXPECT_TRUE(FileRange.isInvalid());
+
+  const CharSourceRange MatchedRange = utils::lexer::findTokenInRange(
+      SearchRange, SM, LangOpts, [](const Token &) { return true; });
+  EXPECT_TRUE(MatchedRange.isInvalid());
+}
+
+TEST(LexerUtilsTest, FindTokenInRangeReturnsInvalidForMacroRange) {
+  std::unique_ptr<ASTUnit> AST = buildAST(R"cpp(
+#define EXPLICIT explicit
+struct S {
+  EXPLICIT S(int);
+};
+)cpp");
+  ASSERT_TRUE(AST);
+  const ASTContext &Context = AST->getASTContext();
+  const SourceManager &SM = Context.getSourceManager();
+  const LangOptions &LangOpts = Context.getLangOpts();
+
+  const CXXConstructorDecl *Ctor = findFirstNonImplicitConstructor(Context);
+  ASSERT_NE(nullptr, Ctor);
+  ASSERT_TRUE(Ctor->getOuterLocStart().isMacroID());
+
+  const CharSourceRange SearchRange = CharSourceRange::getTokenRange(
+      Ctor->getOuterLocStart(), Ctor->getEndLoc());
+  const CharSourceRange MatchedRange = utils::lexer::findTokenInRange(
+      SearchRange, SM, LangOpts,
+      [](const Token &Tok) { return isRawIdentifierNamed(Tok, "explicit"); });
+  EXPECT_TRUE(MatchedRange.isInvalid());
+}
+
 TEST(LexerUtilsTest, GetCommentsInRangeAdjacentComments) {
   llvm::Annotations Code(R"cpp(
 void f() {

>From d67e1a1d24adc7a6af8775bd74c9ce0357a4b2fa Mon Sep 17 00:00:00 2001
From: Daniil Dudkin <[email protected]>
Date: Sun, 8 Feb 2026 14:16:43 +0300
Subject: [PATCH 3/4] [clang-tidy] Add redundant qualified alias check

Introduce `readability-redundant-qualified-alias` to flag identity type aliases
that repeat a qualified name and suggest using-declarations when safe. The
check is conservative: it skips macros, elaborated keywords, dependent types,
and templates, and it suppresses fix-its when comments appear between the
alias name and '='. `OnlyNamespaceScope` controls whether local/class scopes
are included (default false).
---
 .../clang-tidy/readability/CMakeLists.txt     |   1 +
 .../readability/ReadabilityTidyModule.cpp     |   3 +
 .../RedundantQualifiedAliasCheck.cpp          | 240 ++++++++++++++++++
 .../RedundantQualifiedAliasCheck.h            |  37 +++
 clang-tools-extra/docs/ReleaseNotes.rst       |   6 +
 .../docs/clang-tidy/checks/list.rst           |   1 +
 .../readability/redundant-qualified-alias.rst |  30 +++
 .../readability/redundant-qualified-alias.cpp | 201 +++++++++++++++
 8 files changed, 519 insertions(+)
 create mode 100644 
clang-tools-extra/clang-tidy/readability/RedundantQualifiedAliasCheck.cpp
 create mode 100644 
clang-tools-extra/clang-tidy/readability/RedundantQualifiedAliasCheck.h
 create mode 100644 
clang-tools-extra/docs/clang-tidy/checks/readability/redundant-qualified-alias.rst
 create mode 100644 
clang-tools-extra/test/clang-tidy/checkers/readability/redundant-qualified-alias.cpp

diff --git a/clang-tools-extra/clang-tidy/readability/CMakeLists.txt 
b/clang-tools-extra/clang-tidy/readability/CMakeLists.txt
index f1f3cde32feff..686e7c19d650b 100644
--- a/clang-tools-extra/clang-tidy/readability/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/readability/CMakeLists.txt
@@ -47,6 +47,7 @@ add_clang_library(clangTidyReadabilityModule STATIC
   RedundantMemberInitCheck.cpp
   RedundantParenthesesCheck.cpp
   RedundantPreprocessorCheck.cpp
+  RedundantQualifiedAliasCheck.cpp
   RedundantSmartptrGetCheck.cpp
   RedundantStringCStrCheck.cpp
   RedundantStringInitCheck.cpp
diff --git a/clang-tools-extra/clang-tidy/readability/ReadabilityTidyModule.cpp 
b/clang-tools-extra/clang-tidy/readability/ReadabilityTidyModule.cpp
index c582dc98eac6b..8e9e00b23c84a 100644
--- a/clang-tools-extra/clang-tidy/readability/ReadabilityTidyModule.cpp
+++ b/clang-tools-extra/clang-tidy/readability/ReadabilityTidyModule.cpp
@@ -49,6 +49,7 @@
 #include "RedundantMemberInitCheck.h"
 #include "RedundantParenthesesCheck.h"
 #include "RedundantPreprocessorCheck.h"
+#include "RedundantQualifiedAliasCheck.h"
 #include "RedundantSmartptrGetCheck.h"
 #include "RedundantStringCStrCheck.h"
 #include "RedundantStringInitCheck.h"
@@ -148,6 +149,8 @@ class ReadabilityModule : public ClangTidyModule {
         "readability-redundant-parentheses");
     CheckFactories.registerCheck<RedundantPreprocessorCheck>(
         "readability-redundant-preprocessor");
+    CheckFactories.registerCheck<RedundantQualifiedAliasCheck>(
+        "readability-redundant-qualified-alias");
     CheckFactories.registerCheck<RedundantTypenameCheck>(
         "readability-redundant-typename");
     CheckFactories.registerCheck<ReferenceToConstructedTemporaryCheck>(
diff --git 
a/clang-tools-extra/clang-tidy/readability/RedundantQualifiedAliasCheck.cpp 
b/clang-tools-extra/clang-tidy/readability/RedundantQualifiedAliasCheck.cpp
new file mode 100644
index 0000000000000..ea02dbc634658
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/readability/RedundantQualifiedAliasCheck.cpp
@@ -0,0 +1,240 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 "RedundantQualifiedAliasCheck.h"
+#include "../utils/LexerUtils.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/AST/Decl.h"
+#include "clang/AST/Stmt.h"
+#include "clang/AST/TypeLoc.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "clang/Basic/SourceManager.h"
+#include <cassert>
+#include <optional>
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::readability {
+
+namespace {
+
+struct NominalTypeLocInfo {
+  TypeLoc Loc;
+  bool HasQualifier = false;
+};
+
+} // namespace
+
+static bool hasMacroInRange(SourceRange Range, const SourceManager &SM,
+                            const LangOptions &LangOpts) {
+  if (Range.isInvalid())
+    return true;
+  return utils::lexer::rangeContainsExpansionsOrDirectives(Range, SM, 
LangOpts);
+}
+
+static std::optional<NominalTypeLocInfo> peelToNominalTypeLoc(TypeLoc TL) {
+  if (TL.isNull())
+    return std::nullopt;
+
+  if (const auto TypedefTL = TL.getAs<TypedefTypeLoc>()) {
+    // Avoid rewriting aliases that use an elaborated keyword
+    // (class/struct/enum).
+    if (TypedefTL.getElaboratedKeywordLoc().isValid())
+      return std::nullopt;
+    const bool HasQualifier =
+        
static_cast<bool>(TypedefTL.getQualifierLoc().getNestedNameSpecifier());
+    return NominalTypeLocInfo{TypedefTL, HasQualifier};
+  }
+
+  if (const auto TagTL = TL.getAs<TagTypeLoc>()) {
+    // Avoid rewriting aliases that use an elaborated keyword
+    // (class/struct/enum).
+    if (TagTL.getElaboratedKeywordLoc().isValid())
+      return std::nullopt;
+    const bool HasQualifier =
+        static_cast<bool>(TagTL.getQualifierLoc().getNestedNameSpecifier());
+    return NominalTypeLocInfo{TagTL, HasQualifier};
+  }
+
+  return std::nullopt;
+}
+
+static const NamedDecl *getNamedDeclFromNominalTypeLoc(TypeLoc TL) {
+  if (const auto TypedefTL = TL.getAs<TypedefTypeLoc>())
+    return TypedefTL.getTypePtr()->getDecl();
+  if (const auto TagTL = TL.getAs<TagTypeLoc>())
+    return TagTL.getDecl();
+  return nullptr;
+}
+
+static bool hasSameUnqualifiedName(const TypeAliasDecl *Alias,
+                                   const NamedDecl *Target) {
+  return Alias->getName() == Target->getName();
+}
+
+static bool isNamespaceLikeDeclContext(const DeclContext *DC) {
+  return isa<TranslationUnitDecl>(DC) || isa<NamespaceDecl>(DC);
+}
+
+static bool canUseUsingDeclarationForTarget(const TypeAliasDecl *Alias,
+                                            const NamedDecl *Target) {
+  const DeclContext *AliasContext = 
Alias->getDeclContext()->getRedeclContext();
+  const DeclContext *TargetContext =
+      Target->getDeclContext()->getRedeclContext();
+
+  const auto *AliasRecord = dyn_cast<CXXRecordDecl>(AliasContext);
+  if (!AliasRecord)
+    return isNamespaceLikeDeclContext(TargetContext);
+
+  const auto *TargetRecord = dyn_cast<CXXRecordDecl>(TargetContext);
+  return TargetRecord && AliasRecord->isDerivedFrom(TargetRecord);
+}
+
+static bool hasTrailingSyntaxAfterRhsType(TypeLoc TL, const SourceManager &SM,
+                                          const LangOptions &LangOpts) {
+  const SourceLocation TypeEndLoc = TL.getEndLoc();
+  if (TypeEndLoc.isInvalid())
+    return true;
+  if (TypeEndLoc.isMacroID())
+    return true;
+  const std::optional<Token> NextToken =
+      utils::lexer::findNextTokenSkippingComments(TypeEndLoc, SM, LangOpts);
+  return !NextToken || NextToken->isNot(tok::semi);
+}
+
+namespace {
+
+AST_MATCHER(TypeAliasDecl, isAliasTemplate) {
+  return Node.getDescribedAliasTemplate() != nullptr;
+}
+
+AST_MATCHER(TypeAliasDecl, hasAliasAttributes) {
+  if (Node.hasAttrs())
+    return true;
+  const TypeSourceInfo *TSI = Node.getTypeSourceInfo();
+  if (!TSI)
+    return false;
+  for (TypeLoc CurTL = TSI->getTypeLoc(); !CurTL.isNull();
+       CurTL = CurTL.getNextTypeLoc())
+    if (CurTL.getAs<AttributedTypeLoc>())
+      return true;
+  return false;
+}
+
+AST_MATCHER(TypeLoc, isNonDependentTypeLoc) {
+  return !Node.getType().isNull() && !Node.getType()->isDependentType();
+}
+
+AST_MATCHER(TypeLoc, isMacroFreeTypeLoc) {
+  const ASTContext &Context = Finder->getASTContext();
+  return !hasMacroInRange(Node.getSourceRange(), Context.getSourceManager(),
+                          Context.getLangOpts());
+}
+
+AST_MATCHER(TypeLoc, hasNoTrailingSyntaxAfterTypeLoc) {
+  const ASTContext &Context = Finder->getASTContext();
+  return !hasTrailingSyntaxAfterRhsType(Node, Context.getSourceManager(),
+                                        Context.getLangOpts());
+}
+
+AST_MATCHER(TypeAliasDecl, hasUsingDeclarationEquivalentTarget) {
+  const TypeSourceInfo *TSI = Node.getTypeSourceInfo();
+  if (!TSI)
+    return false;
+  const std::optional<NominalTypeLocInfo> NominalInfo =
+      peelToNominalTypeLoc(TSI->getTypeLoc());
+  if (!NominalInfo || !NominalInfo->HasQualifier)
+    return false;
+  const NamedDecl *Target = getNamedDeclFromNominalTypeLoc(NominalInfo->Loc);
+  return Target && hasSameUnqualifiedName(&Node, Target) &&
+         canUseUsingDeclarationForTarget(&Node, Target);
+}
+
+} // namespace
+
+RedundantQualifiedAliasCheck::RedundantQualifiedAliasCheck(
+    StringRef Name, ClangTidyContext *Context)
+    : ClangTidyCheck(Name, Context),
+      OnlyNamespaceScope(Options.get("OnlyNamespaceScope", false)) {}
+
+void RedundantQualifiedAliasCheck::storeOptions(
+    ClangTidyOptions::OptionMap &Opts) {
+  Options.store(Opts, "OnlyNamespaceScope", OnlyNamespaceScope);
+}
+
+void RedundantQualifiedAliasCheck::registerMatchers(MatchFinder *Finder) {
+  const auto ControlFlowInitStatementMatcher = stmt(
+      anyOf(mapAnyOf(ifStmt, switchStmt, cxxForRangeStmt)
+                .with(hasInitStatement(stmt(equalsBoundNode("initDeclStmt")))),
+            forStmt(hasLoopInit(stmt(equalsBoundNode("initDeclStmt"))))));
+
+  const auto AliasPreconditions =
+      allOf(unless(isAliasTemplate()), unless(isImplicit()),
+            unless(hasAliasAttributes()));
+  const auto InControlFlowInit =
+      allOf(hasParent(declStmt().bind("initDeclStmt")),
+            hasAncestor(ControlFlowInitStatementMatcher));
+  const auto RewriteableTypeLoc =
+      typeLoc(allOf(isNonDependentTypeLoc(), isMacroFreeTypeLoc(),
+                    hasNoTrailingSyntaxAfterTypeLoc()))
+          .bind("loc");
+
+  const auto RedundantQualifiedAliasMatcher = typeAliasDecl(
+      AliasPreconditions, unless(InControlFlowInit),
+      hasUsingDeclarationEquivalentTarget(), hasTypeLoc(RewriteableTypeLoc));
+
+  if (OnlyNamespaceScope) {
+    Finder->addMatcher(typeAliasDecl(RedundantQualifiedAliasMatcher,
+                                     
hasDeclContext(anyOf(translationUnitDecl(),
+                                                          namespaceDecl())))
+                           .bind("alias"),
+                       this);
+    return;
+  }
+  Finder->addMatcher(RedundantQualifiedAliasMatcher.bind("alias"), this);
+}
+
+void RedundantQualifiedAliasCheck::check(
+    const MatchFinder::MatchResult &Result) {
+  const auto *Alias = Result.Nodes.getNodeAs<TypeAliasDecl>("alias");
+  assert(Alias && "matcher must bind alias");
+  const auto *WrittenTLNode = Result.Nodes.getNodeAs<TypeLoc>("loc");
+  assert(WrittenTLNode && "matcher must bind loc");
+  const TypeLoc WrittenTL = *WrittenTLNode;
+
+  if (Alias->getLocation().isMacroID())
+    return;
+
+  const SourceManager &SM = *Result.SourceManager;
+  const LangOptions &LangOpts = getLangOpts();
+
+  const SourceLocation AliasLoc = Alias->getLocation();
+  const SourceLocation RhsBeginLoc = WrittenTL.getBeginLoc();
+  const CharSourceRange EqualRange = utils::lexer::findTokenInRange(
+      CharSourceRange::getCharRange(AliasLoc, RhsBeginLoc), SM, LangOpts,
+      [](const Token &Tok) { return Tok.is(tok::equal); });
+  if (EqualRange.isInvalid())
+    return;
+
+  auto Diag = diag(Alias->getLocation(),
+                   "type alias is redundant; use a using-declaration instead");
+
+  if (!utils::lexer::getCommentsInRange(
+           CharSourceRange::getCharRange(AliasLoc, EqualRange.getBegin()), SM,
+           LangOpts)
+           .empty()) {
+    // Suppress fix-it: avoid deleting comments between alias name and '='.
+    return;
+  }
+
+  Diag << FixItHint::CreateRemoval(Alias->getLocation())
+       << FixItHint::CreateRemoval(EqualRange.getBegin());
+}
+
+} // namespace clang::tidy::readability
diff --git 
a/clang-tools-extra/clang-tidy/readability/RedundantQualifiedAliasCheck.h 
b/clang-tools-extra/clang-tidy/readability/RedundantQualifiedAliasCheck.h
new file mode 100644
index 0000000000000..1084112dd13a7
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/readability/RedundantQualifiedAliasCheck.h
@@ -0,0 +1,37 @@
+//===----------------------------------------------------------------------===//
+//
+// 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_READABILITY_REDUNDANTQUALIFIEDALIASCHECK_H
+#define 
LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_REDUNDANTQUALIFIEDALIASCHECK_H
+
+#include "../ClangTidyCheck.h"
+
+namespace clang::tidy::readability {
+
+/// Finds identity type aliases to qualified names that can be expressed as
+/// using-declarations.
+///
+/// For the user-facing documentation see:
+/// 
https://clang.llvm.org/extra/clang-tidy/checks/readability/redundant-qualified-alias.html
+class RedundantQualifiedAliasCheck : public ClangTidyCheck {
+public:
+  RedundantQualifiedAliasCheck(StringRef Name, ClangTidyContext *Context);
+  bool isLanguageVersionSupported(const LangOptions &LangOpts) const override {
+    return LangOpts.CPlusPlus11;
+  }
+  void storeOptions(ClangTidyOptions::OptionMap &Opts) override;
+  void registerMatchers(ast_matchers::MatchFinder *Finder) override;
+  void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
+
+private:
+  const bool OnlyNamespaceScope;
+};
+
+} // namespace clang::tidy::readability
+
+#endif // 
LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_REDUNDANTQUALIFIEDALIASCHECK_H
diff --git a/clang-tools-extra/docs/ReleaseNotes.rst 
b/clang-tools-extra/docs/ReleaseNotes.rst
index 6bdc0ae7bdcc8..db6dda3c6645e 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -142,6 +142,12 @@ New checks
   Finds and removes redundant conversions from 
``std::[w|u8|u16|u32]string_view`` to
   ``std::[...]string`` in call expressions expecting ``std::[...]string_view``.
 
+- New :doc:`readability-redundant-qualified-alias
+  <clang-tidy/checks/readability/redundant-qualified-alias>` check.
+
+  Finds redundant identity type aliases that re-expose a qualified name and can
+  be replaced with a ``using`` declaration.
+
 - New :doc:`readability-trailing-comma
   <clang-tidy/checks/readability/trailing-comma>` check.
 
diff --git a/clang-tools-extra/docs/clang-tidy/checks/list.rst 
b/clang-tools-extra/docs/clang-tidy/checks/list.rst
index c475870ed7b31..ce1ef9e445877 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/list.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/list.rst
@@ -415,6 +415,7 @@ Clang-Tidy Checks
    :doc:`readability-redundant-member-init 
<readability/redundant-member-init>`, "Yes"
    :doc:`readability-redundant-parentheses 
<readability/redundant-parentheses>`, "Yes"
    :doc:`readability-redundant-preprocessor 
<readability/redundant-preprocessor>`,
+   :doc:`readability-redundant-qualified-alias 
<readability/redundant-qualified-alias>`, "Yes"
    :doc:`readability-redundant-smartptr-get 
<readability/redundant-smartptr-get>`, "Yes"
    :doc:`readability-redundant-string-cstr 
<readability/redundant-string-cstr>`, "Yes"
    :doc:`readability-redundant-string-init 
<readability/redundant-string-init>`, "Yes"
diff --git 
a/clang-tools-extra/docs/clang-tidy/checks/readability/redundant-qualified-alias.rst
 
b/clang-tools-extra/docs/clang-tidy/checks/readability/redundant-qualified-alias.rst
new file mode 100644
index 0000000000000..c85a2b45b387c
--- /dev/null
+++ 
b/clang-tools-extra/docs/clang-tidy/checks/readability/redundant-qualified-alias.rst
@@ -0,0 +1,30 @@
+.. title:: clang-tidy - readability-redundant-qualified-alias
+
+readability-redundant-qualified-alias
+=====================================
+
+Finds redundant identity type aliases that re-expose a qualified name and can
+be replaced with a ``using`` declaration.
+
+.. code-block:: c++
+
+  using CommentToken = clang::tidy::utils::lexer::CommentToken;
+
+  // becomes
+
+  using clang::tidy::utils::lexer::CommentToken;
+
+The check is conservative and only warns when the alias name exactly matches
+the unqualified name of a non-dependent, non-specialized named type written
+with a qualifier. It skips alias templates, dependent forms, elaborated
+keywords (``class``, ``struct``, ``enum``, ``typename``), and cases involving
+macros or comments between the alias name and ``=``.
+
+Options
+-------
+
+.. option:: OnlyNamespaceScope
+
+   When `true`, only consider aliases declared in a namespace or the
+   translation unit. When `false`, also consider aliases declared inside
+   classes, functions, and lambdas. Default is `false`.
diff --git 
a/clang-tools-extra/test/clang-tidy/checkers/readability/redundant-qualified-alias.cpp
 
b/clang-tools-extra/test/clang-tidy/checkers/readability/redundant-qualified-alias.cpp
new file mode 100644
index 0000000000000..692c3038b2276
--- /dev/null
+++ 
b/clang-tools-extra/test/clang-tidy/checkers/readability/redundant-qualified-alias.cpp
@@ -0,0 +1,201 @@
+// RUN: %check_clang_tidy -std=c++11-or-later %s 
readability-redundant-qualified-alias %t
+// RUN: %check_clang_tidy -check-suffix=NS -std=c++11-or-later %s 
readability-redundant-qualified-alias %t -- \
+// RUN:   -config='{CheckOptions: { 
readability-redundant-qualified-alias.OnlyNamespaceScope: true }}'
+// RUN: %check_clang_tidy -check-suffixes=,CXX23 -std=c++23-or-later %s 
readability-redundant-qualified-alias %t
+// RUN: %check_clang_tidy -check-suffixes=NS,NS-CXX23 -std=c++23-or-later %s 
readability-redundant-qualified-alias %t -- \
+// RUN:   -config='{CheckOptions: { 
readability-redundant-qualified-alias.OnlyNamespaceScope: true }}'
+
+namespace n1 {
+struct Foo {};
+struct Bar {};
+struct Attr {};
+enum PlainEnum { V0 };
+enum class ScopedEnum { V1 };
+struct Commented {};
+struct AfterType {};
+struct Elab {};
+struct MacroEq {};
+struct MacroType {};
+struct PtrType {};
+struct LocalType {};
+} // namespace n1
+
+namespace n2 {
+namespace n3 {
+struct Deep {};
+} // namespace n3
+} // namespace n2
+
+namespace td {
+typedef n1::Foo TypedefFoo;
+} // namespace td
+
+struct GlobalType {};
+struct Outer {
+  struct Inner {};
+};
+
+using Foo = n1::Foo;
+// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: type alias is redundant; use a 
using-declaration instead [readability-redundant-qualified-alias]
+// CHECK-MESSAGES-NS: :[[@LINE-2]]:7: warning: type alias is redundant; use a 
using-declaration instead [readability-redundant-qualified-alias]
+// CHECK-FIXES: using n1::Foo;
+
+using Bar = ::n1::Bar;
+// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: type alias is redundant; use a 
using-declaration instead [readability-redundant-qualified-alias]
+// CHECK-MESSAGES-NS: :[[@LINE-2]]:7: warning: type alias is redundant; use a 
using-declaration instead [readability-redundant-qualified-alias]
+// CHECK-FIXES: using ::n1::Bar;
+
+using Attr = n1::Attr __attribute__((aligned(8)));
+// CHECK-MESSAGES-NOT: warning: type alias is redundant; use a 
using-declaration instead
+// CHECK-MESSAGES-NS-NOT: warning: type alias is redundant; use a 
using-declaration instead
+
+using AliasDeprecated [[deprecated("alias attr")]] = n1::Foo;
+// CHECK-MESSAGES-NOT: warning: type alias is redundant; use a 
using-declaration instead
+// CHECK-MESSAGES-NS-NOT: warning: type alias is redundant; use a 
using-declaration instead
+
+using Deep = n2::n3::Deep;
+// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: type alias is redundant; use a 
using-declaration instead [readability-redundant-qualified-alias]
+// CHECK-MESSAGES-NS: :[[@LINE-2]]:7: warning: type alias is redundant; use a 
using-declaration instead [readability-redundant-qualified-alias]
+// CHECK-FIXES: using n2::n3::Deep;
+
+using TypedefFoo = td::TypedefFoo;
+// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: type alias is redundant; use a 
using-declaration instead [readability-redundant-qualified-alias]
+// CHECK-MESSAGES-NS: :[[@LINE-2]]:7: warning: type alias is redundant; use a 
using-declaration instead [readability-redundant-qualified-alias]
+// CHECK-FIXES: using td::TypedefFoo;
+
+using GlobalType = ::GlobalType;
+// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: type alias is redundant; use a 
using-declaration instead [readability-redundant-qualified-alias]
+// CHECK-MESSAGES-NS: :[[@LINE-2]]:7: warning: type alias is redundant; use a 
using-declaration instead [readability-redundant-qualified-alias]
+// CHECK-FIXES: using ::GlobalType;
+
+using PlainEnum = n1::PlainEnum;
+// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: type alias is redundant; use a 
using-declaration instead [readability-redundant-qualified-alias]
+// CHECK-MESSAGES-NS: :[[@LINE-2]]:7: warning: type alias is redundant; use a 
using-declaration instead [readability-redundant-qualified-alias]
+// CHECK-FIXES: using n1::PlainEnum;
+
+using ScopedEnum = n1::ScopedEnum;
+// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: type alias is redundant; use a 
using-declaration instead [readability-redundant-qualified-alias]
+// CHECK-MESSAGES-NS: :[[@LINE-2]]:7: warning: type alias is redundant; use a 
using-declaration instead [readability-redundant-qualified-alias]
+// CHECK-FIXES: using n1::ScopedEnum;
+
+using Inner = Outer::Inner;
+// CHECK-MESSAGES-NOT: warning: type alias is redundant; use a 
using-declaration instead
+// CHECK-MESSAGES-NS-NOT: warning: type alias is redundant; use a 
using-declaration instead
+
+using Builtin = int;
+// CHECK-MESSAGES-NOT: warning: type alias is redundant; use a 
using-declaration instead
+// CHECK-MESSAGES-NS-NOT: warning: type alias is redundant; use a 
using-declaration instead
+
+using PtrType = n1::PtrType *;
+// CHECK-MESSAGES-NOT: warning: type alias is redundant; use a 
using-declaration instead
+// CHECK-MESSAGES-NS-NOT: warning: type alias is redundant; use a 
using-declaration instead
+
+namespace templ {
+template <typename T>
+struct Vec {};
+} // namespace templ
+
+using Vec = templ::Vec<int>;
+// CHECK-MESSAGES-NOT: warning: type alias is redundant; use a 
using-declaration instead
+// CHECK-MESSAGES-NS-NOT: warning: type alias is redundant; use a 
using-declaration instead
+
+namespace templ_alias {
+template <typename T>
+using Foo = n1::Foo;
+// CHECK-MESSAGES-NOT: warning: type alias is redundant; use a 
using-declaration instead
+// CHECK-MESSAGES-NS-NOT: warning: type alias is redundant; use a 
using-declaration instead
+} // namespace templ_alias
+
+template <typename T>
+struct Dependent {
+  using X = typename T::X;
+  // CHECK-MESSAGES-NOT: warning: type alias is redundant; use a 
using-declaration instead
+  // CHECK-MESSAGES-NS-NOT: warning: type alias is redundant; use a 
using-declaration instead
+};
+
+using Elab = class n1::Elab;
+// CHECK-MESSAGES-NOT: warning: type alias is redundant; use a 
using-declaration instead
+// CHECK-MESSAGES-NS-NOT: warning: type alias is redundant; use a 
using-declaration instead
+
+using Commented /*comment*/ = n1::Commented;
+// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: type alias is redundant; use a 
using-declaration instead [readability-redundant-qualified-alias]
+// CHECK-MESSAGES-NS: :[[@LINE-2]]:7: warning: type alias is redundant; use a 
using-declaration instead [readability-redundant-qualified-alias]
+// CHECK-FIXES: using Commented /*comment*/ = n1::Commented;
+
+using AfterType = n1::AfterType /*rhs-comment*/;
+// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: type alias is redundant; use a 
using-declaration instead [readability-redundant-qualified-alias]
+// CHECK-MESSAGES-NS: :[[@LINE-2]]:7: warning: type alias is redundant; use a 
using-declaration instead [readability-redundant-qualified-alias]
+// CHECK-FIXES: using n1::AfterType /*rhs-comment*/;
+
+#define DECL_END ;
+using MacroDeclEnd = n1::MacroType DECL_END
+// CHECK-MESSAGES-NOT: warning: type alias is redundant; use a 
using-declaration instead
+// CHECK-MESSAGES-NS-NOT: warning: type alias is redundant; use a 
using-declaration instead
+
+#define ALIAS MacroType
+using ALIAS = n1::MacroType;
+// CHECK-MESSAGES-NOT: warning: type alias is redundant; use a 
using-declaration instead
+// CHECK-MESSAGES-NS-NOT: warning: type alias is redundant; use a 
using-declaration instead
+
+#define RHS n1::MacroType
+using MacroType = RHS;
+// CHECK-MESSAGES-NOT: warning: type alias is redundant; use a 
using-declaration instead
+// CHECK-MESSAGES-NS-NOT: warning: type alias is redundant; use a 
using-declaration instead
+
+#define EQ =
+using MacroEq EQ n1::MacroEq;
+// CHECK-MESSAGES-NOT: warning: type alias is redundant; use a 
using-declaration instead
+// CHECK-MESSAGES-NS-NOT: warning: type alias is redundant; use a 
using-declaration instead
+
+struct Base {
+  using T = n1::Foo;
+};
+
+struct Derived : Base {
+  using T = Base::T;
+  // CHECK-MESSAGES: :[[@LINE-1]]:9: warning: type alias is redundant; use a 
using-declaration instead [readability-redundant-qualified-alias]
+  // CHECK-MESSAGES-NS-NOT: warning: type alias is redundant; use a 
using-declaration instead
+  // CHECK-FIXES: using Base::T;
+};
+
+struct ClassScopeNamespaceAlias {
+  using Foo = n1::Foo;
+  // CHECK-MESSAGES-NOT: warning: type alias is redundant; use a 
using-declaration instead
+  // CHECK-MESSAGES-NS-NOT: warning: type alias is redundant; use a 
using-declaration instead
+};
+
+void local_scope() {
+  using LocalType = n1::LocalType;
+  // CHECK-MESSAGES: :[[@LINE-1]]:9: warning: type alias is redundant; use a 
using-declaration instead [readability-redundant-qualified-alias]
+  // CHECK-MESSAGES-NS-NOT: warning: type alias is redundant; use a 
using-declaration instead
+  // CHECK-FIXES: using n1::LocalType;
+}
+
+#if __cplusplus >= 202302L
+void cxx23_init_statement_scope(bool Cond) {
+  if (using Foo = n1::Foo; Cond) {
+  }
+  // CHECK-MESSAGES-CXX23-NOT: warning: type alias is redundant; use a 
using-declaration instead
+  // CHECK-MESSAGES-NS-CXX23-NOT: warning: type alias is redundant; use a 
using-declaration instead
+
+  switch (using Bar = ::n1::Bar; 0) {
+  default:
+    break;
+  }
+  // CHECK-MESSAGES-CXX23-NOT: warning: type alias is redundant; use a 
using-declaration instead
+  // CHECK-MESSAGES-NS-CXX23-NOT: warning: type alias is redundant; use a 
using-declaration instead
+
+  for (using Deep = n2::n3::Deep; Cond;) {
+    Cond = false;
+  }
+  // CHECK-MESSAGES-CXX23-NOT: warning: type alias is redundant; use a 
using-declaration instead
+  // CHECK-MESSAGES-NS-CXX23-NOT: warning: type alias is redundant; use a 
using-declaration instead
+
+  int Values[] = {0};
+  for (using GlobalType = ::GlobalType; int V : Values) {
+    (void)V;
+  }
+  // CHECK-MESSAGES-CXX23-NOT: warning: type alias is redundant; use a 
using-declaration instead
+  // CHECK-MESSAGES-NS-CXX23-NOT: warning: type alias is redundant; use a 
using-declaration instead
+}
+#endif

>From 44fe66a4791afd8bae1c978c02de7aa9682fcdac Mon Sep 17 00:00:00 2001
From: Daniil Dudkin <[email protected]>
Date: Sun, 1 Mar 2026 16:44:06 +0300
Subject: [PATCH 4/4] Fix review comments

---
 .../readability/RedundantQualifiedAliasCheck.cpp          | 8 --------
 .../checks/readability/redundant-qualified-alias.rst      | 2 +-
 .../checkers/readability/redundant-qualified-alias.cpp    | 6 ++++--
 3 files changed, 5 insertions(+), 11 deletions(-)

diff --git 
a/clang-tools-extra/clang-tidy/readability/RedundantQualifiedAliasCheck.cpp 
b/clang-tools-extra/clang-tidy/readability/RedundantQualifiedAliasCheck.cpp
index ea02dbc634658..2d570b6f5610d 100644
--- a/clang-tools-extra/clang-tidy/readability/RedundantQualifiedAliasCheck.cpp
+++ b/clang-tools-extra/clang-tidy/readability/RedundantQualifiedAliasCheck.cpp
@@ -225,14 +225,6 @@ void RedundantQualifiedAliasCheck::check(
   auto Diag = diag(Alias->getLocation(),
                    "type alias is redundant; use a using-declaration instead");
 
-  if (!utils::lexer::getCommentsInRange(
-           CharSourceRange::getCharRange(AliasLoc, EqualRange.getBegin()), SM,
-           LangOpts)
-           .empty()) {
-    // Suppress fix-it: avoid deleting comments between alias name and '='.
-    return;
-  }
-
   Diag << FixItHint::CreateRemoval(Alias->getLocation())
        << FixItHint::CreateRemoval(EqualRange.getBegin());
 }
diff --git 
a/clang-tools-extra/docs/clang-tidy/checks/readability/redundant-qualified-alias.rst
 
b/clang-tools-extra/docs/clang-tidy/checks/readability/redundant-qualified-alias.rst
index c85a2b45b387c..399f5aae4828f 100644
--- 
a/clang-tools-extra/docs/clang-tidy/checks/readability/redundant-qualified-alias.rst
+++ 
b/clang-tools-extra/docs/clang-tidy/checks/readability/redundant-qualified-alias.rst
@@ -18,7 +18,7 @@ The check is conservative and only warns when the alias name 
exactly matches
 the unqualified name of a non-dependent, non-specialized named type written
 with a qualifier. It skips alias templates, dependent forms, elaborated
 keywords (``class``, ``struct``, ``enum``, ``typename``), and cases involving
-macros or comments between the alias name and ``=``.
+macros.
 
 Options
 -------
diff --git 
a/clang-tools-extra/test/clang-tidy/checkers/readability/redundant-qualified-alias.cpp
 
b/clang-tools-extra/test/clang-tidy/checkers/readability/redundant-qualified-alias.cpp
index 692c3038b2276..6edcfe323743d 100644
--- 
a/clang-tools-extra/test/clang-tidy/checkers/readability/redundant-qualified-alias.cpp
+++ 
b/clang-tools-extra/test/clang-tidy/checkers/readability/redundant-qualified-alias.cpp
@@ -49,9 +49,11 @@ using Attr = n1::Attr __attribute__((aligned(8)));
 // CHECK-MESSAGES-NOT: warning: type alias is redundant; use a 
using-declaration instead
 // CHECK-MESSAGES-NS-NOT: warning: type alias is redundant; use a 
using-declaration instead
 
-using AliasDeprecated [[deprecated("alias attr")]] = n1::Foo;
+namespace alias_attr {
+using Foo [[deprecated("alias attr")]] = n1::Foo;
 // CHECK-MESSAGES-NOT: warning: type alias is redundant; use a 
using-declaration instead
 // CHECK-MESSAGES-NS-NOT: warning: type alias is redundant; use a 
using-declaration instead
+} // namespace alias_attr
 
 using Deep = n2::n3::Deep;
 // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: type alias is redundant; use a 
using-declaration instead [readability-redundant-qualified-alias]
@@ -120,7 +122,7 @@ using Elab = class n1::Elab;
 using Commented /*comment*/ = n1::Commented;
 // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: type alias is redundant; use a 
using-declaration instead [readability-redundant-qualified-alias]
 // CHECK-MESSAGES-NS: :[[@LINE-2]]:7: warning: type alias is redundant; use a 
using-declaration instead [readability-redundant-qualified-alias]
-// CHECK-FIXES: using Commented /*comment*/ = n1::Commented;
+// CHECK-FIXES: using{{[ ]+}}/*comment*/{{[ ]+}}n1::Commented;
 
 using AfterType = n1::AfterType /*rhs-comment*/;
 // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: type alias is redundant; use a 
using-declaration instead [readability-redundant-qualified-alias]

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

Reply via email to