[clang] [include-cleaner]Support #import directives in HeaderIncludes (PR #212582)

via cfe-commits cfe-commits at lists.llvm.org
Tue Jul 28 11:51:44 PDT 2026


https://github.com/dmaclach created https://github.com/llvm/llvm-project/pull/212582

This change updates HeaderIncludes to recognize #import directives in addition to #include when parsing existing headers. It also fixes up an existing issue parsing include/import statements that may have contained tabs.


>From e7e3f9cac7cd180ac8ce8a826603a507ff01e439 Mon Sep 17 00:00:00 2001
From: Dave MacLachlan <dmaclach at gmail.com>
Date: Tue, 28 Jul 2026 11:47:07 -0700
Subject: [PATCH] [include-cleaner]Support #import directives in HeaderIncludes

This change updates HeaderIncludes to recognize #import directives in addition to #include when parsing existing headers.
It also fixes up an existing issue parsing include/import statements that may have contained tabs.
---
 .../lib/Tooling/Inclusions/HeaderIncludes.cpp |  8 ++--
 .../unittests/Tooling/HeaderIncludesTest.cpp  | 41 +++++++++++++++++++
 2 files changed, 46 insertions(+), 3 deletions(-)

diff --git a/clang/lib/Tooling/Inclusions/HeaderIncludes.cpp b/clang/lib/Tooling/Inclusions/HeaderIncludes.cpp
index 34f21faa8e342..62752122db2ac 100644
--- a/clang/lib/Tooling/Inclusions/HeaderIncludes.cpp
+++ b/clang/lib/Tooling/Inclusions/HeaderIncludes.cpp
@@ -154,7 +154,7 @@ unsigned getMinHeaderInsertionOffset(StringRef FileName, StringRef Code,
 }
 
 // Check if a sequence of tokens is like
-//    "#include ("header.h" | <header.h>)".
+//    "#(include | import) ("header.h" | <header.h>)".
 // If it is, \p Tok will be the token after this directive; otherwise, it can be
 // any token after the given \p Tok (including \p Tok).
 bool checkAndConsumeInclusiveDirective(Lexer &Lex, Token &Tok) {
@@ -163,7 +163,9 @@ bool checkAndConsumeInclusiveDirective(Lexer &Lex, Token &Tok) {
     return true;
   };
   if (Tok.is(tok::hash) && !Lex.LexFromRawLexer(Tok) &&
-      Tok.is(tok::raw_identifier) && Tok.getRawIdentifier() == "include") {
+      Tok.is(tok::raw_identifier) &&
+      (Tok.getRawIdentifier() == "include" ||
+       Tok.getRawIdentifier() == "import")) {
     if (Lex.LexFromRawLexer(Tok))
       return false;
     if (Tok.is(tok::string_literal))
@@ -246,7 +248,7 @@ inline StringRef trimInclude(StringRef IncludeName) {
 }
 
 const char IncludeRegexPattern[] =
-    R"(^[\t\ ]*#[\t\ ]*(import|include)[^"<]*(["<][^">]*[">]))";
+    "^[\t ]*#[\t ]*(import|include)[^\"<]*([\"<][^\">]*[\">])";
 
 // The filename of Path excluding extension.
 // Used to match implementation with headers, this differs from sys::path::stem:
diff --git a/clang/unittests/Tooling/HeaderIncludesTest.cpp b/clang/unittests/Tooling/HeaderIncludesTest.cpp
index bc2ac3a97e97d..4c1848252df56 100644
--- a/clang/unittests/Tooling/HeaderIncludesTest.cpp
+++ b/clang/unittests/Tooling/HeaderIncludesTest.cpp
@@ -82,6 +82,47 @@ int x;)cpp";
   EXPECT_EQ("\nint x;", remove(Code, "<abc.h>"));
 }
 
+TEST_F(HeaderIncludesTest, DeleteMixedImportAndIncludeQuoted) {
+  std::string Code = R"cpp(
+#include "a.h"
+#import "a.h"
+int x;)cpp";
+  EXPECT_EQ("\nint x;", remove(Code, "\"a.h\""));
+}
+
+TEST_F(HeaderIncludesTest, ImportWithSpacesAndTabs) {
+  std::string Code = "int x;\n";
+  // The parser should detect these as existing imports if we had them,
+  // but here we are testing insertion/detection integration.
+  // Let's verify that a file with weird spacing is parsed correctly.
+  std::string CodeWithSpaces =
+      "#  import   \"a.h\"\n#\tinclude\t\"b.h\"\nint x;\n";
+
+  // Try inserting "a.h" again as import - should be blocked by the existing one
+  // if the regex captures it correctly.
+  EXPECT_EQ(CodeWithSpaces,
+            insert(CodeWithSpaces, "\"a.h\"", IncludeDirective::Import));
+
+  // Try inserting "b.h" again as include - should be blocked.
+  EXPECT_EQ(CodeWithSpaces,
+            insert(CodeWithSpaces, "\"b.h\"", IncludeDirective::Include));
+}
+
+TEST_F(HeaderIncludesTest, InsertIncludeWhenImportExists) {
+  std::string Code = "#import \"a.h\"\n";
+  std::string Expected = Code + "#include \"a.h\"\n";
+  // Currently, the logic allows inserting #include even if #import exists
+  // because the Directive differs. This test verifies this current behavior.
+  EXPECT_EQ(Expected, insert(Code, "\"a.h\"", IncludeDirective::Include));
+}
+
+TEST_F(HeaderIncludesTest, InsertImportWhenIncludeExists) {
+  std::string Code = "#include \"a.h\"\n";
+  std::string Expected = Code + "#import \"a.h\"\n";
+  // Similarly, allows inserting #import even if #include exists.
+  EXPECT_EQ(Expected, insert(Code, "\"a.h\"", IncludeDirective::Import));
+}
+
 TEST_F(HeaderIncludesTest, NoExistingIncludeWithDefine) {
   std::string Code = "#ifndef A_H\n"
                      "#define A_H\n"



More information about the cfe-commits mailing list