diff --git a/clang/lib/Format/Format.cpp b/clang/lib/Format/Format.cpp --- a/clang/lib/Format/Format.cpp +++ b/clang/lib/Format/Format.cpp @@ -2682,8 +2682,7 @@ namespace { const char CppIncludeRegexPattern[] = - R"(^[\t\ ]*#[\t\ ]*(import|include)[^"<]*(["<][^">]*[">]))"; - + R"(^[\t\ ]*[@#][\t\ ]*(import|include)([^"]*("[^"]+")|[^<]*(<[^>]+>)|[\t\ ]*([^;]+;)))"; } // anonymous namespace tooling::Replacements sortCppIncludes(const FormatStyle &Style, StringRef Code, @@ -2751,7 +2750,20 @@ bool MergeWithNextLine = Trimmed.endswith("\\"); if (!FormattingOff && !MergeWithNextLine) { if (IncludeRegex.match(Line, &Matches)) { - StringRef IncludeName = Matches[2]; + StringRef IncludeName; + for (int i = Matches.size() - 1; i > 0; i--) { + if (!Matches[i].empty()) { + IncludeName = Matches[i]; + break; + } + } + // This addresses https://github.com/llvm/llvm-project/issues/38995 + int WithSemicolon = false; + if (!IncludeName.startswith("\"") && !IncludeName.startswith("<") && + IncludeName.endswith(";")) { + WithSemicolon = true; + } + if (Line.contains("/*") && !Line.contains("*/")) { // #include with a start of a block comment, but without the end. // Need to keep all the lines until the end of the comment together. @@ -2764,8 +2776,10 @@ int Category = Categories.getIncludePriority( IncludeName, /*CheckMainHeader=*/!MainIncludeFound && FirstIncludeBlock); - int Priority = Categories.getSortIncludePriority( - IncludeName, !MainIncludeFound && FirstIncludeBlock); + int Priority = WithSemicolon ? INT_MAX + : Categories.getSortIncludePriority( + IncludeName, !MainIncludeFound && + FirstIncludeBlock); if (Category == 0) MainIncludeFound = true; IncludesInBlock.push_back( @@ -3033,6 +3047,10 @@ return Replace.getOffset() == UINT_MAX && Replace.getLength() == 1; } +inline StringRef trimInclude(StringRef IncludeName) { + return IncludeName.trim("\"<>;"); +} + // FIXME: insert empty lines between newly created blocks. tooling::Replacements fixCppIncludeInsertions(StringRef Code, const tooling::Replacements &Replaces, @@ -3066,7 +3084,7 @@ for (const auto &Header : HeadersToDelete) { tooling::Replacements Replaces = - Includes.remove(Header.trim("\"<>"), Header.startswith("<")); + Includes.remove(trimInclude(Header), Header.startswith("<")); for (const auto &R : Replaces) { auto Err = Result.add(R); if (Err) { @@ -3086,9 +3104,16 @@ assert(Matched && "Header insertion replacement must have replacement text " "'#include ...'"); (void)Matched; - auto IncludeName = Matches[2]; + StringRef IncludeName; + for (int i = Matches.size() - 1; i > 0; i--) { + if (!Matches[i].empty()) { + IncludeName = Matches[i]; + break; + } + } + // auto IncludeName = Matches[2]; auto Replace = - Includes.insert(IncludeName.trim("\"<>"), IncludeName.startswith("<")); + Includes.insert(trimInclude(IncludeName), IncludeName.startswith("<")); if (Replace) { auto Err = Result.add(*Replace); if (Err) { diff --git a/clang/lib/Tooling/Inclusions/HeaderIncludes.cpp b/clang/lib/Tooling/Inclusions/HeaderIncludes.cpp --- a/clang/lib/Tooling/Inclusions/HeaderIncludes.cpp +++ b/clang/lib/Tooling/Inclusions/HeaderIncludes.cpp @@ -170,11 +170,11 @@ } inline StringRef trimInclude(StringRef IncludeName) { - return IncludeName.trim("\"<>"); + return IncludeName.trim("\"<>;"); } const char IncludeRegexPattern[] = - R"(^[\t\ ]*#[\t\ ]*(import|include)[^"<]*(["<][^">]*[">]))"; + R"(^[\t\ ]*[@#][\t\ ]*(import|include)([^"]*("[^"]+")|[^<]*(<[^>]+>)|[\t\ ]*([^;]+;)))"; // The filename of Path excluding extension. // Used to match implementation with headers, this differs from sys::path::stem: @@ -290,10 +290,17 @@ for (auto Line : Lines) { NextLineOffset = std::min(Code.size(), Offset + Line.size() + 1); if (IncludeRegex.match(Line, &Matches)) { + StringRef IncludeName; + for (int i = Matches.size() - 1; i > 0; i--) { + if (!Matches[i].empty()) { + IncludeName = Matches[i]; + break; + } + } // If this is the last line without trailing newline, we need to make // sure we don't delete across the file boundary. addExistingInclude( - Include(Matches[2], + Include(IncludeName, tooling::Range( Offset, std::min(Line.size() + 1, Code.size() - Offset))), NextLineOffset); diff --git a/clang/unittests/Format/SortIncludesTest.cpp b/clang/unittests/Format/SortIncludesTest.cpp --- a/clang/unittests/Format/SortIncludesTest.cpp +++ b/clang/unittests/Format/SortIncludesTest.cpp @@ -458,6 +458,39 @@ "#include \"b.h\"\n")); } +TEST_F(SortIncludesTest, SupportAtImportLines) { + // Test from https://github.com/llvm/llvm-project/issues/38995 + EXPECT_EQ("#import \"a.h\"\n" + "#import \"b.h\"\n" + "#import \"c.h\"\n" + "#import \n" + "@import Foundation;\n", + sort("#import \"b.h\"\n" + "#import \"c.h\"\n" + "#import \n" + "@import Foundation;\n" + "#import \"a.h\"\n")); + + // Slightly more complicated test that shows sorting in each priorities still + // works. + EXPECT_EQ("#import \"a.h\"\n" + "#import \"b.h\"\n" + "#import \"c.h\"\n" + "#import \n" + "@import Base;\n" + "@import Foundation;\n" + "@import base;\n" + "@import foundation;\n", + sort("#import \"b.h\"\n" + "#import \"c.h\"\n" + "@import Base;\n" + "#import \n" + "@import foundation;\n" + "@import Foundation;\n" + "@import base;\n" + "#import \"a.h\"\n")); +} + TEST_F(SortIncludesTest, LeavesMainHeaderFirst) { Style.IncludeIsMainRegex = "([-_](test|unittest))?$"; EXPECT_EQ("#include \"llvm/a.h\"\n"