ioeric created this revision.
ioeric added reviewers: djasper, klimek.
ioeric added subscribers: bkramer, cfe-commits.
Herald added a subscriber: klimek.
When a replacement's offset is set to UINT_MAX or -1U, it is treated as
a header insertion replacement by cleanupAroundReplacements(). The new #include
directive is then inserted into the correct block.
http://reviews.llvm.org/D20734
Files:
include/clang/Format/Format.h
lib/Format/Format.cpp
unittests/Format/CleanupTest.cpp
Index: unittests/Format/CleanupTest.cpp
===================================================================
--- unittests/Format/CleanupTest.cpp
+++ unittests/Format/CleanupTest.cpp
@@ -281,6 +281,294 @@
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, InsertAfterMainHeader) {
+ std::string Code = "#include \"fix.h\"\n"
+ "\n"
+ "int main() {}";
+ std::string Expected = "#include \"fix.h\"\n"
+ "#include <a>\n"
+ "\n"
+ "int main() {}";
+ Context.createInMemoryFile("fix.cpp", Code);
+ tooling::Replacements Replaces;
+ Replaces.insert(tooling::Replacement("fix.cpp", -1U, 0, "#include <a>"));
+ 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 <memory>\n"
+ "\n"
+ "int main() {}";
+ std::string Expected = "#include \"z.h\"\n"
+ "#include <memory>\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 <memory>\n"
+ "\n"
+ "int main() {}";
+ std::string Expected = "#include <memory>\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 <memory>\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 <memory>\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 <memory>\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 <memory>\n"
+ "#include <list>\n";
+ Context.createInMemoryFile("fix.cpp", Code);
+ tooling::Replacements Replaces;
+ Replaces.insert(tooling::Replacement("fix.cpp", -1U, 0, "#include <list>"));
+ 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 <vector>\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 <vector>"));
+ 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 <vector>\n"
+ "\n"
+ "#include \"y/a.h\"\n"
+ "#include \"z/b.h\"\n";
+ std::string Expected = "#include \"x/fix.h\"\n"
+ "\n"
+ "#include <vector>\n"
+ "#include <list>\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 <list>"));
+ 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 <list>\n"
+ "#include <vector>\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 <vector>"));
+ Replaces.insert(
+ tooling::Replacement("fix.cpp", -1U, 0, "#include <list>"));
+ 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 <list>\n"
+ "#include <vector>\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 <vector>"));
+ Replaces.insert(
+ tooling::Replacement("fix.cpp", -1U, 0, "#include <list>"));
+ 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 <list>\n"
+ "#include <vector>\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 <vector>"));
+ Replaces.insert(
+ tooling::Replacement("fix.cpp", -1U, 0, "#include <list>"));
+ 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 <vector>\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 <vector>"));
+ 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
Index: lib/Format/Format.cpp
===================================================================
--- lib/Format/Format.cpp
+++ lib/Format/Format.cpp
@@ -1402,6 +1402,170 @@
return processReplacements(Reformat, Code, SortedReplaces, Style);
}
+namespace {
+
+// Returns -1 if \p IncludeName does not match any category pattern.
+// FIXME: llvm::Regex::match is not a const function, so we can't pass
+// CategoryRegexs as const reference.
+int getIncludePriority(const FormatStyle &Style,
+ SmallVector<llvm::Regex, 4> &CategoryRegexs,
+ StringRef IncludeName) {
+ for (unsigned i = 0, e = CategoryRegexs.size(); i != e; ++i) {
+ if (CategoryRegexs[i].match(IncludeName)) {
+ return Style.IncludeCategories[i].Priority;
+ }
+ }
+ return -1;
+}
+
+// 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(
+ R"(^[\t\ ]*#[\t\ ]*include[\t\ ]*(["<][^">]*[">]))");
+ llvm::Regex DefineRegex(R"(^[\t\ ]*#[\t\ ]*define[\t\ ]*[^\\]*$)");
+ SmallVector<StringRef, 4> Matches;
+ // Create pre-compiled regular expressions for the #include categories.
+ SmallVector<llvm::Regex, 4> CategoryRegexs;
+ for (const auto &Category : Style.IncludeCategories)
+ CategoryRegexs.emplace_back(Category.Regex);
+
+ StringRef FileName = Replaces.begin()->getFilePath();
+ StringRef FileStem = llvm::sys::path::stem(FileName);
+ bool IsMainFile = FileName.endswith(".c") || FileName.endswith(".cc") ||
+ FileName.endswith(".cpp") || FileName.endswith(".c++") ||
+ FileName.endswith(".cxx") || FileName.endswith(".m") ||
+ FileName.endswith(".mm");
+ 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. Add an
+ // extra category (priority 0) for main include.
+ std::vector<int> CategoryEndOffsets(MaxPriority + 1, -1);
+ bool MainIncludeFound = false;
+ int FirstIncludeOffset = -1;
+ int Offset = 0;
+ int AfterFirstDefine = 0;
+ SmallVector<StringRef, 32> Lines;
+ Code.split(Lines, '\n');
+ for (auto Line : Lines) {
+ if (IncludeRegex.match(Line, &Matches)) {
+ StringRef IncludeName = Matches[1];
+ int Category = getIncludePriority(Style, CategoryRegexs, IncludeName);
+ if (IsMainFile && !MainIncludeFound && Category > 0 &&
+ FirstIncludeOffset < 0 && 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;
+ }
+ }
+ }
+ if (Category < 0) {
+ llvm::errs() << "#include " << IncludeName
+ << " does not belong to any include category.\n";
+ } else {
+ CategoryEndOffsets[Category] = Offset + Line.size() + 1;
+ }
+ if (FirstIncludeOffset < 0)
+ FirstIncludeOffset = Offset;
+ }
+ Offset += Line.size() + 1;
+ if (AfterFirstDefine == 0 && DefineRegex.match(Line))
+ AfterFirstDefine = Offset;
+ }
+
+ tooling::Replacements Results;
+ bool AddNewLineBefore;
+ 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[1];
+ int Category = getIncludePriority(Style, CategoryRegexs, IncludeName);
+ if (Category < 0) {
+ llvm::errs() << IncludeDirective
+ << " does not belong to any include category.\n";
+ continue;
+ }
+ // Append the new #include after the corresponding category if the category
+ // exists. Otherwise, append after the last category that has higher
+ // priority than the current category. If there is no category, insert after
+ // the first #define directive or top of the file is there is no #define
+ // directive.
+ // FIXME: skip comment section if there is no existing #include and #define.
+ AddNewLineBefore = false;
+ Offset = CategoryEndOffsets[Category];
+ if (Offset < 0) {
+ for (int P = Category - 1; P >= 0; P--) {
+ if (CategoryEndOffsets[P] >= 0) {
+ Offset = CategoryEndOffsets[P];
+ break;
+ }
+ }
+ if (Offset < 0) {
+ if (AfterFirstDefine > 0) {
+ Offset = AfterFirstDefine;
+ AddNewLineBefore = true;
+ } else {
+ Offset = 0;
+ }
+ }
+ }
+ std::string NewInclude =
+ (AddNewLineBefore && !IncludeDirective.startswith("\n"))
+ ? ("\n" + IncludeDirective).str()
+ : IncludeDirective.str();
+ if (!StringRef(NewInclude).endswith("\n"))
+ NewInclude.append("\n");
+ 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) {
+ // FIXME: support other languages.
+ 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 +1576,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: 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);
_______________________________________________
cfe-commits mailing list
[email protected]
http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits