Index: include/clang/Format/Format.h =================================================================== --- include/clang/Format/Format.h +++ include/clang/Format/Format.h @@ -773,6 +773,9 @@ /// \brief Returns the replacements corresponding to applying \p Replaces and /// cleaning up the code after that. +/// This also inserts a C++ #include directive into the correct block if the +/// replacement corresponding to the header insertion has offset UINT_MAX (i.e. +/// -1U). tooling::Replacements cleanupAroundReplacements(StringRef Code, const tooling::Replacements &Replaces, const FormatStyle &Style); Index: lib/Format/Format.cpp =================================================================== --- lib/Format/Format.cpp +++ lib/Format/Format.cpp @@ -1262,6 +1262,66 @@ result.size(), result)); } +namespace { + +// This class manages priorities of #include categories and calculates +// priorities for headers. +class IncludeCategoryManager { +public: + IncludeCategoryManager(const FormatStyle &Style, StringRef FileName) + : Style(Style), FileName(FileName) { + FileStem = llvm::sys::path::stem(FileName); + for (const auto &Category : Style.IncludeCategories) + CategoryRegexs.emplace_back(Category.Regex); + IsMainFile = FileName.endswith(".c") || FileName.endswith(".cc") || + FileName.endswith(".cpp") || FileName.endswith(".c++") || + FileName.endswith(".cxx") || FileName.endswith(".m") || + FileName.endswith(".mm"); + } + + // Returns the priority of the category which \p IncludeName belongs to, and + // if \p CheckMainHeader is true and \p IncldueName is a + // main header, returns 0. Otherwise, returns INT_MAX if \p IncludeName does + // not match any category pattern. + int getIncludePriority(StringRef IncludeName, bool CheckMainHeader) { + int Ret = INT_MAX; + for (unsigned I = 0, E = CategoryRegexs.size(); I != E; ++I) + if (CategoryRegexs[I].match(IncludeName)) { + Ret = Style.IncludeCategories[I].Priority; + break; + } + if (CheckMainHeader && IsMainFile && Ret > 0 && isMainHeader(IncludeName)) + Ret = 0; + return Ret; + } + +private: + bool isMainHeader(StringRef IncludeName) const { + if (!IncludeName.startswith("\"")) + return false; + StringRef HeaderStem = + llvm::sys::path::stem(IncludeName.drop_front(1).drop_back(1)); + if (FileStem.startswith(HeaderStem)) { + llvm::Regex MainIncludeRegex( + (HeaderStem + Style.IncludeIsMainRegex).str()); + if (MainIncludeRegex.match(FileStem)) + return true; + } + return false; + } + + const FormatStyle &Style; + bool IsMainFile; + StringRef FileName; + StringRef FileStem; + SmallVector CategoryRegexs; +}; + +const char IncludeRegexPattern[] = + R"(^[\t\ ]*#[\t\ ]*(import|include)[^"<]*(["<][^">]*[">]))"; + +} // anonymous namespace + tooling::Replacements sortCppIncludes(const FormatStyle &Style, StringRef Code, ArrayRef Ranges, StringRef FileName, @@ -1269,8 +1329,7 @@ unsigned *Cursor) { unsigned Prev = 0; unsigned SearchFrom = 0; - llvm::Regex IncludeRegex( - R"(^[\t\ ]*#[\t\ ]*(import|include)[^"<]*(["<][^">]*[">]))"); + llvm::Regex IncludeRegex(IncludeRegexPattern); SmallVector Matches; SmallVector IncludesInBlock; @@ -1281,19 +1340,9 @@ // // FIXME: Do some sanity checking, e.g. edit distance of the base name, to fix // cases where the first #include is unlikely to be the main header. - bool IsSource = FileName.endswith(".c") || FileName.endswith(".cc") || - FileName.endswith(".cpp") || FileName.endswith(".c++") || - FileName.endswith(".cxx") || FileName.endswith(".m") || - FileName.endswith(".mm"); - StringRef FileStem = llvm::sys::path::stem(FileName); + IncludeCategoryManager Categories(Style, FileName); bool FirstIncludeBlock = true; bool MainIncludeFound = false; - - // Create pre-compiled regular expressions for the #include categories. - SmallVector CategoryRegexs; - for (const auto &Category : Style.IncludeCategories) - CategoryRegexs.emplace_back(Category.Regex); - bool FormattingOff = false; for (;;) { @@ -1310,26 +1359,11 @@ if (!FormattingOff && !Line.endswith("\\")) { if (IncludeRegex.match(Line, &Matches)) { StringRef IncludeName = Matches[2]; - int Category = INT_MAX; - for (unsigned i = 0, e = CategoryRegexs.size(); i != e; ++i) { - if (CategoryRegexs[i].match(IncludeName)) { - Category = Style.IncludeCategories[i].Priority; - break; - } - } - if (IsSource && !MainIncludeFound && Category > 0 && - FirstIncludeBlock && IncludeName.startswith("\"")) { - StringRef HeaderStem = - llvm::sys::path::stem(IncludeName.drop_front(1).drop_back(1)); - if (FileStem.startswith(HeaderStem)) { - llvm::Regex MainIncludeRegex( - (HeaderStem + Style.IncludeIsMainRegex).str()); - if (MainIncludeRegex.match(FileStem)) { - Category = 0; - MainIncludeFound = true; - } - } - } + int Category = Categories.getIncludePriority( + IncludeName, + /*CheckMainHeader=*/!MainIncludeFound && FirstIncludeBlock); + if (Category == 0) + MainIncludeFound = true; IncludesInBlock.push_back({IncludeName, Line, Prev, Category}); } else if (!IncludesInBlock.empty()) { sortCppIncludes(Style, IncludesInBlock, Ranges, FileName, Replaces, @@ -1402,6 +1436,139 @@ return processReplacements(Reformat, Code, SortedReplaces, Style); } +namespace { + +// FIXME: do not insert headers into conditional #include blocks, e.g. #includes +// surrounded by compile condition "#if...". +// FIXME: do not insert existing headers. +// FIXME: insert empty lines between newly created blocks. +tooling::Replacements +fixCppIncludeInsertions(StringRef Code, const tooling::Replacements &Replaces, + const FormatStyle &Style) { + if (Replaces.empty()) + return tooling::Replacements(); + + llvm::Regex IncludeRegex(IncludeRegexPattern); + llvm::Regex DefineRegex(R"(^[\t\ ]*#[\t\ ]*define[\t\ ]*[^\\]*$)"); + SmallVector Matches; + + StringRef FileName = Replaces.begin()->getFilePath(); + IncludeCategoryManager Categories(Style, FileName); + int MaxPriority = 0; + for (const auto &Category : Style.IncludeCategories) + if (Category.Priority > MaxPriority) + MaxPriority = Category.Priority; + + // Record the offset of the end of the last include in each category. + std::map CategoryEndOffsets; + // All possible priorities. + // Add 0 for main header and INT_MAX for headers that are not in any category. + std::vector Priorities = {0, INT_MAX}; + for (const auto &Category : Style.IncludeCategories) + Priorities.push_back(Category.Priority); + // Sort and deduplicate priorities. + std::sort(Priorities.begin(), Priorities.end()); + Priorities.erase(std::unique(Priorities.begin(), Priorities.end()), + Priorities.end()); + int FirstIncludeOffset = -1; + int Offset = 0; + int AfterHeaderGuard = 0; + SmallVector Lines; + Code.split(Lines, '\n'); + for (auto Line : Lines) { + if (IncludeRegex.match(Line, &Matches)) { + StringRef IncludeName = Matches[2]; + int Category = Categories.getIncludePriority( + IncludeName, /*CheckMainHeader=*/FirstIncludeOffset < 0); + CategoryEndOffsets[Category] = Offset + Line.size() + 1; + if (FirstIncludeOffset < 0) + FirstIncludeOffset = Offset; + } + Offset += Line.size() + 1; + // FIXME: make header guard matching more strict, e.g. consider #ifndef. + if (AfterHeaderGuard == 0 && DefineRegex.match(Line)) + AfterHeaderGuard = Offset; + } + + // Set EndOffset of each category that is not set yet to be the end of the + // last category with higher priority. If there is no category with higher + // priority, then we look for the next block with lower category; if such + // block exists, then the EndOffset is set to the beginning of this category. + // Otherwise, we can say there is no existing header and set the EndOffset to + // be the end of header guard or the top of the code. + // To simplify this, we treat the category with the highest priority + // specially. If its EndOffset has not been set, we set its EndOffset to be + // the begin of the first #include if there is existing #include. Otherwise, + // we set its EndOffset to be AfterHeaderGuard. With this setup, the category + // with the highest priority has been intialized so that all other categories + // can find a category with higher priority and initialized EndOffset. + // FIXME: skip comment section in the beginning of the code if there is no + // existing #include and #define. + tooling::Replacements Results; + int Highest = Priorities[0]; + if (CategoryEndOffsets.find(Highest) == CategoryEndOffsets.end()) { + if (FirstIncludeOffset >= 0) { + CategoryEndOffsets[Highest] = FirstIncludeOffset; + } else { + // Insert a new line after header guard. + // FIXME: since the new line and the end offset are both set to + // AfterHeaderGuard, the order of these two insertions can not be + // guaranteed (the new line is inserted after the new #include). So we + // replace the new line at the end of the #define directive with two empty + // lines to walk around this issue. + if (AfterHeaderGuard > 0) + Results.insert( + tooling::Replacement(FileName, AfterHeaderGuard-1, 1, "\n\n")); + CategoryEndOffsets[Highest] = AfterHeaderGuard; + } + } + for (int I = 1, E = Priorities.size(); I != E; I++) + if (CategoryEndOffsets.find(Priorities[I]) == CategoryEndOffsets.end()) + CategoryEndOffsets[Priorities[I]] = CategoryEndOffsets[Priorities[I - 1]]; + + for (const auto &R : Replaces) { + auto IncludeDirective = R.getReplacementText(); + if (!IncludeRegex.match(IncludeDirective, &Matches)) { + llvm::errs() << IncludeDirective + << " is not a valid #include directive.\n"; + continue; + } + auto IncludeName = Matches[2]; + int Category = + Categories.getIncludePriority(IncludeName, /*CheckMainHeader=*/false); + Offset = CategoryEndOffsets[Category]; + std::string NewInclude = + (!IncludeDirective.endswith("\n")) + ? (IncludeDirective + "\n").str() + : IncludeDirective.str(); + Results.insert(tooling::Replacement(FileName, Offset, 0, NewInclude)); + } + return Results; +} + +// Insert #include directives into the correct blocks. +tooling::Replacements fixHeaderInsertions(StringRef Code, + const tooling::Replacements &Replaces, + const FormatStyle &Style) { + if (Style.Language != FormatStyle::LanguageKind::LK_Cpp) + return Replaces; + tooling::Replacements HeaderInsertionReplaces; + tooling::Replacements NewReplaces; + for (const auto &R : Replaces) { + if (R.getOffset() == -1U) + HeaderInsertionReplaces.insert(R); + else + NewReplaces.insert(R); + } + HeaderInsertionReplaces = + fixCppIncludeInsertions(Code, HeaderInsertionReplaces, Style); + NewReplaces.insert(HeaderInsertionReplaces.begin(), + HeaderInsertionReplaces.end()); + return NewReplaces; +} + +} // anonymous namespace + tooling::Replacements cleanupAroundReplacements(StringRef Code, const tooling::Replacements &Replaces, const FormatStyle &Style) { @@ -1412,7 +1579,10 @@ StringRef FileName) -> tooling::Replacements { return cleanup(Style, Code, Ranges, FileName); }; - return processReplacements(Cleanup, Code, Replaces, Style); + // Make header insertion replacements insert new headers into correct blocks. + tooling::Replacements NewReplaces = + fixHeaderInsertions(Code, Replaces, Style); + return processReplacements(Cleanup, Code, NewReplaces, Style); } tooling::Replacements reformat(const FormatStyle &Style, SourceManager &SM, Index: unittests/Format/CleanupTest.cpp =================================================================== --- unittests/Format/CleanupTest.cpp +++ unittests/Format/CleanupTest.cpp @@ -281,6 +281,323 @@ EXPECT_EQ(Expected, applyAllReplacements(Code, FinalReplaces)); } +TEST_F(CleanUpReplacementsTest, NoExistingIncludeWithoutDefine) { + std::string Code = "int main() {}"; + std::string Expected = "#include \"a.h\"\n" + "int main() {}"; + Context.createInMemoryFile("fix.cpp", Code); + tooling::Replacements Replaces; + Replaces.insert(tooling::Replacement("fix.cpp", -1U, 0, "#include \"a.h\"")); + format::FormatStyle Style = format::getLLVMStyle(); + auto NewReplaces = cleanupAroundReplacements(Code, Replaces, Style); + EXPECT_EQ(Expected, applyAllReplacements(Code, NewReplaces)); +} + +TEST_F(CleanUpReplacementsTest, NoExistingIncludeWithDefine) { + std::string Code = "#ifndef __A_H__\n" + "#define __A_H__\n" + "class A {};\n" + "#define MMM 123\n" + "#endif"; + std::string Expected = "#ifndef __A_H__\n" + "#define __A_H__\n" + "\n" + "#include \"b.h\"\n" + "class A {};\n" + "#define MMM 123\n" + "#endif"; + + Context.createInMemoryFile("fix.cpp", Code); + tooling::Replacements Replaces; + Replaces.insert(tooling::Replacement("fix.cpp", -1U, 0, "#include \"b.h\"")); + format::FormatStyle Style = format::getLLVMStyle(); + auto NewReplaces = cleanupAroundReplacements(Code, Replaces, Style); + EXPECT_EQ(Expected, applyAllReplacements(Code, NewReplaces)); +} + +TEST_F(CleanUpReplacementsTest, InsertBeforeCategoryWithLowerPriority) { + std::string Code = "#ifndef __A_H__\n" + "#define __A_H__\n" + "\n" + "\n" + "\n" + "#include \n" + "class A {};\n" + "#define MMM 123\n" + "#endif"; + std::string Expected = "#ifndef __A_H__\n" + "#define __A_H__\n" + "\n" + "\n" + "\n" + "#include \"a.h\"\n" + "#include \n" + "class A {};\n" + "#define MMM 123\n" + "#endif"; + + Context.createInMemoryFile("fix.cpp", Code); + tooling::Replacements Replaces; + Replaces.insert(tooling::Replacement("fix.cpp", -1U, 0, "#include \"a.h\"")); + format::FormatStyle Style = format::getLLVMStyle(); + auto NewReplaces = cleanupAroundReplacements(Code, Replaces, Style); + EXPECT_EQ(Expected, applyAllReplacements(Code, NewReplaces)); +} + +TEST_F(CleanUpReplacementsTest, InsertAfterMainHeader) { + std::string Code = "#include \"fix.h\"\n" + "\n" + "int main() {}"; + std::string Expected = "#include \"fix.h\"\n" + "#include \n" + "\n" + "int main() {}"; + Context.createInMemoryFile("fix.cpp", Code); + tooling::Replacements Replaces; + Replaces.insert(tooling::Replacement("fix.cpp", -1U, 0, "#include ")); + format::FormatStyle Style = + format::getGoogleStyle(format::FormatStyle::LanguageKind::LK_Cpp); + auto NewReplaces = cleanupAroundReplacements(Code, Replaces, Style); + EXPECT_EQ(Expected, applyAllReplacements(Code, NewReplaces)); +} + +TEST_F(CleanUpReplacementsTest, InsertBeforeSystemHeaderLLVM) { + std::string Code = "#include \n" + "\n" + "int main() {}"; + std::string Expected = "#include \"z.h\"\n" + "#include \n" + "\n" + "int main() {}"; + Context.createInMemoryFile("fix.cpp", Code); + tooling::Replacements Replaces; + Replaces.insert(tooling::Replacement("fix.cpp", -1U, 0, "#include \"z.h\"")); + format::FormatStyle Style = format::getLLVMStyle(); + auto NewReplaces = cleanupAroundReplacements(Code, Replaces, Style); + EXPECT_EQ(Expected, applyAllReplacements(Code, NewReplaces)); +} + +TEST_F(CleanUpReplacementsTest, InsertAfterSystemHeaderGoogle) { + std::string Code = "#include \n" + "\n" + "int main() {}"; + std::string Expected = "#include \n" + "#include \"z.h\"\n" + "\n" + "int main() {}"; + Context.createInMemoryFile("fix.cpp", Code); + tooling::Replacements Replaces; + Replaces.insert(tooling::Replacement("fix.cpp", -1U, 0, "#include \"z.h\"")); + format::FormatStyle Style = + format::getGoogleStyle(format::FormatStyle::LanguageKind::LK_Cpp); + auto NewReplaces = cleanupAroundReplacements(Code, Replaces, Style); + EXPECT_EQ(Expected, applyAllReplacements(Code, NewReplaces)); +} + +TEST_F(CleanUpReplacementsTest, InsertOneIncludeLLVMStyle) { + std::string Code = "#include \"x/fix.h\"\n" + "#include \"a.h\"\n" + "#include \"b.h\"\n" + "#include \"clang/Format/Format.h\"\n" + "#include \n"; + std::string Expected = "#include \"x/fix.h\"\n" + "#include \"a.h\"\n" + "#include \"b.h\"\n" + "#include \"d.h\"\n" + "#include \"clang/Format/Format.h\"\n" + "#include \"llvm/x/y.h\"\n" + "#include \n"; + Context.createInMemoryFile("fix.cpp", Code); + tooling::Replacements Replaces; + Replaces.insert(tooling::Replacement("fix.cpp", -1U, 0, "#include \"d.h\"")); + Replaces.insert( + tooling::Replacement("fix.cpp", -1U, 0, "#include \"llvm/x/y.h\"")); + format::FormatStyle Style = format::getLLVMStyle(); + auto NewReplaces = cleanupAroundReplacements(Code, Replaces, Style); + EXPECT_EQ(Expected, applyAllReplacements(Code, NewReplaces)); +} + +TEST_F(CleanUpReplacementsTest, InsertMultipleIncludesLLVMStyle) { + std::string Code = "#include \"x/fix.h\"\n" + "#include \"a.h\"\n" + "#include \"b.h\"\n" + "#include \"clang/Format/Format.h\"\n" + "#include \n"; + std::string Expected = "#include \"x/fix.h\"\n" + "#include \"a.h\"\n" + "#include \"b.h\"\n" + "#include \"new/new.h\"\n" + "#include \"clang/Format/Format.h\"\n" + "#include \n" + "#include \n"; + Context.createInMemoryFile("fix.cpp", Code); + tooling::Replacements Replaces; + Replaces.insert(tooling::Replacement("fix.cpp", -1U, 0, "#include ")); + Replaces.insert( + tooling::Replacement("fix.cpp", -1U, 0, "#include \"new/new.h\"")); + format::FormatStyle Style = format::getLLVMStyle(); + auto NewReplaces = cleanupAroundReplacements(Code, Replaces, Style); + EXPECT_EQ(Expected, applyAllReplacements(Code, NewReplaces)); +} + +TEST_F(CleanUpReplacementsTest, InsertNewSystemIncludeGoogleStyle) { + std::string Code = "#include \"x/fix.h\"\n" + "\n" + "#include \"y/a.h\"\n" + "#include \"z/b.h\"\n"; + // FIXME: inserting after the empty line following the main header might be + // prefered. + std::string Expected = "#include \"x/fix.h\"\n" + "#include \n" + "\n" + "#include \"y/a.h\"\n" + "#include \"z/b.h\"\n"; + Context.createInMemoryFile("x/fix.cpp", Code); + tooling::Replacements Replaces; + Replaces.insert( + tooling::Replacement("fix.cpp", -1U, 0, "#include ")); + format::FormatStyle Style = + format::getGoogleStyle(format::FormatStyle::LanguageKind::LK_Cpp); + auto NewReplaces = cleanupAroundReplacements(Code, Replaces, Style); + EXPECT_EQ(Expected, applyAllReplacements(Code, NewReplaces)); +} + +TEST_F(CleanUpReplacementsTest, InsertMultipleIncludesGoogleStyle) { + std::string Code = "#include \"x/fix.h\"\n" + "\n" + "#include \n" + "\n" + "#include \"y/a.h\"\n" + "#include \"z/b.h\"\n"; + std::string Expected = "#include \"x/fix.h\"\n" + "\n" + "#include \n" + "#include \n" + "\n" + "#include \"y/a.h\"\n" + "#include \"z/b.h\"\n" + "#include \"x/x.h\"\n"; + Context.createInMemoryFile("x/fix.cpp", Code); + tooling::Replacements Replaces; + Replaces.insert( + tooling::Replacement("fix.cpp", -1U, 0, "#include ")); + Replaces.insert( + tooling::Replacement("fix.cpp", -1U, 0, "#include \"x/x.h\"")); + format::FormatStyle Style = + format::getGoogleStyle(format::FormatStyle::LanguageKind::LK_Cpp); + auto NewReplaces = cleanupAroundReplacements(Code, Replaces, Style); + EXPECT_EQ(Expected, applyAllReplacements(Code, NewReplaces)); +} + +TEST_F(CleanUpReplacementsTest, InsertMultipleNewHeadersAndSortLLVM) { + std::string Code = "\nint x;"; + std::string Expected = "#include \"fix.h\"\n" + "#include \"a.h\"\n" + "#include \"b.h\"\n" + "#include \"c.h\"\n" + "#include \n" + "#include \n" + "\nint x;"; + Context.createInMemoryFile("fix.cpp", Code); + tooling::Replacements Replaces; + Replaces.insert( + tooling::Replacement("fix.cpp", -1U, 0, "#include \"a.h\"")); + Replaces.insert( + tooling::Replacement("fix.cpp", -1U, 0, "#include \"c.h\"")); + Replaces.insert( + tooling::Replacement("fix.cpp", -1U, 0, "#include \"b.h\"")); + Replaces.insert( + tooling::Replacement("fix.cpp", -1U, 0, "#include ")); + Replaces.insert( + tooling::Replacement("fix.cpp", -1U, 0, "#include ")); + Replaces.insert( + tooling::Replacement("fix.cpp", -1U, 0, "#include \"fix.h\"")); + format::FormatStyle Style = format::getLLVMStyle(); + auto NewReplaces = cleanupAroundReplacements(Code, Replaces, Style); + EXPECT_EQ(Expected, applyAllReplacements( + Code, formatReplacements(Code, NewReplaces, Style))); +} + +TEST_F(CleanUpReplacementsTest, InsertMultipleNewHeadersAndSortGoogle) { + std::string Code = "\nint x;"; + std::string Expected = "#include \"fix.h\"\n" + "#include \n" + "#include \n" + "#include \"a.h\"\n" + "#include \"b.h\"\n" + "#include \"c.h\"\n" + "\nint x;"; + Context.createInMemoryFile("fix.cpp", Code); + tooling::Replacements Replaces; + Replaces.insert( + tooling::Replacement("fix.cpp", -1U, 0, "#include \"a.h\"")); + Replaces.insert( + tooling::Replacement("fix.cpp", -1U, 0, "#include \"c.h\"")); + Replaces.insert( + tooling::Replacement("fix.cpp", -1U, 0, "#include \"b.h\"")); + Replaces.insert( + tooling::Replacement("fix.cpp", -1U, 0, "#include ")); + Replaces.insert( + tooling::Replacement("fix.cpp", -1U, 0, "#include ")); + Replaces.insert( + tooling::Replacement("fix.cpp", -1U, 0, "#include \"fix.h\"")); + format::FormatStyle Style = + format::getGoogleStyle(format::FormatStyle::LanguageKind::LK_Cpp); + auto NewReplaces = cleanupAroundReplacements(Code, Replaces, Style); + EXPECT_EQ(Expected, applyAllReplacements( + Code, formatReplacements(Code, NewReplaces, Style))); +} + +TEST_F(CleanUpReplacementsTest, FormatCorrectLineWhenHeadersAreInserted) { + std::string Code = "\n" + "int a;\n" + "int a;\n" + "int a;"; + + std::string Expected = "#include \"x.h\"\n" + "#include \"y.h\"\n" + "#include \"clang/x/x.h\"\n" + "#include \n" + "#include \n" + "\n" + "int a;\n" + "int b;\n" + "int a;"; + FileID ID = Context.createInMemoryFile("fix.cpp", Code); + tooling::Replacements Replaces; + Replaces.insert(tooling::Replacement(Context.Sources, + Context.getLocation(ID, 3, 8), 1, "b")); + Replaces.insert(tooling::Replacement("fix.cpp", -1U, 0, "#include ")); + Replaces.insert( + tooling::Replacement("fix.cpp", -1U, 0, "#include ")); + Replaces.insert( + tooling::Replacement("fix.cpp", -1U, 0, "#include \"clang/x/x.h\"")); + Replaces.insert( + tooling::Replacement("fix.cpp", -1U, 0, "#include \"y.h\"")); + Replaces.insert( + tooling::Replacement("fix.cpp", -1U, 0, "#include \"x.h\"")); + format::FormatStyle Style = format::getLLVMStyle(); + auto NewReplaces = cleanupAroundReplacements(Code, Replaces, Style); + EXPECT_EQ(Expected, applyAllReplacements( + Code, formatReplacements(Code, NewReplaces, Style))); +} + +TEST_F(CleanUpReplacementsTest, NotConfusedByDefine) { + std::string Code = "void f() {}\n" + "#define A \\\n" + " int i;"; + std::string Expected = "#include \n" + "void f() {}\n" + "#define A \\\n" + " int i;"; + Context.createInMemoryFile("fix.cpp", Code); + tooling::Replacements Replaces; + Replaces.insert(tooling::Replacement("fix.cpp", -1U, 0, "#include ")); + format::FormatStyle Style = format::getLLVMStyle(); + auto NewReplaces = cleanupAroundReplacements(Code, Replaces, Style); + EXPECT_EQ(Expected, applyAllReplacements(Code, NewReplaces)); +} + } // end namespace } // end namespace format } // end namespace clang