clang-format: Extend #include sorting functionality

Recognize the main module header as well as different #include categories.
This should now mimic the behavior of llvm/utils/sort_includes.py as
well as clang-tools-extra/clang-tidy/llvm/IncludeOrderCheck.cpp very
closely.

llvm-svn: 248782
This commit is contained in:
Daniel Jasper 2015-09-29 07:53:08 +00:00
parent 98b3ee50ff
commit 85c472dccd
4 changed files with 99 additions and 22 deletions

View File

@ -259,6 +259,21 @@ struct FormatStyle {
/// For example: BOOST_FOREACH.
std::vector<std::string> ForEachMacros;
/// \brief Regular expressions denoting the different #include categories used
/// for ordering #includes.
///
/// These regular expressions are matched against the filename of an include
/// (including the <> or "") in order. The value belonging to the first
/// matching regular expression is assigned and #includes are sorted first
/// according to increasing category number and then alphabetically within
/// each category.
///
/// If none of the regular expressions match, UINT_MAX is assigned as
/// category. The main header for a source file automatically gets category 0,
/// so that it is kept at the beginning of the #includes
/// (http://llvm.org/docs/CodingStandards.html#include-style).
std::vector<std::pair<std::string, unsigned>> IncludeCategories;
/// \brief Indent case labels one level from the switch statement.
///
/// When \c false, use the same indentation level as for the switch statement.

View File

@ -13,6 +13,7 @@
///
//===----------------------------------------------------------------------===//
#include "clang/Format/Format.h"
#include "ContinuationIndenter.h"
#include "TokenAnnotator.h"
#include "UnwrappedLineFormatter.h"
@ -21,7 +22,6 @@
#include "clang/Basic/Diagnostic.h"
#include "clang/Basic/DiagnosticOptions.h"
#include "clang/Basic/SourceManager.h"
#include "clang/Format/Format.h"
#include "clang/Lex/Lexer.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/Support/Allocator.h"
@ -375,6 +375,9 @@ FormatStyle getLLVMStyle() {
LLVMStyle.ForEachMacros.push_back("foreach");
LLVMStyle.ForEachMacros.push_back("Q_FOREACH");
LLVMStyle.ForEachMacros.push_back("BOOST_FOREACH");
LLVMStyle.IncludeCategories = {{"^\"(llvm|llvm-c|clang|clang-c)/", 2},
{"^(<|\"(gtest|isl|json)/)", 3},
{".*", 1}};
LLVMStyle.IndentCaseLabels = false;
LLVMStyle.IndentWrappedFunctionNames = false;
LLVMStyle.IndentWidth = 2;
@ -423,6 +426,7 @@ FormatStyle getGoogleStyle(FormatStyle::LanguageKind Language) {
GoogleStyle.AlwaysBreakTemplateDeclarations = true;
GoogleStyle.ConstructorInitializerAllOnOneLineOrOnePerLine = true;
GoogleStyle.DerivePointerAlignment = true;
GoogleStyle.IncludeCategories = {{"^<.*\\.h>", 1}, {"^<.*", 2}, {".*", 3}};
GoogleStyle.IndentCaseLabels = true;
GoogleStyle.KeepEmptyLinesAtTheStartOfBlocks = false;
GoogleStyle.ObjCSpaceAfterProperty = false;
@ -1575,7 +1579,7 @@ struct IncludeDirective {
StringRef Filename;
StringRef Text;
unsigned Offset;
bool IsAngled;
unsigned Category;
};
} // end anonymous namespace
@ -1605,7 +1609,8 @@ static void sortIncludes(const FormatStyle &Style,
for (unsigned i = 0, e = Includes.size(); i != e; ++i)
Indices.push_back(i);
std::sort(Indices.begin(), Indices.end(), [&](unsigned LHSI, unsigned RHSI) {
return Includes[LHSI].Filename < Includes[RHSI].Filename;
return std::tie(Includes[LHSI].Category, Includes[LHSI].Filename) <
std::tie(Includes[RHSI].Category, Includes[RHSI].Filename);
});
// If the #includes are out of order, we generate a single replacement fixing
@ -1642,22 +1647,49 @@ tooling::Replacements sortIncludes(const FormatStyle &Style, StringRef Code,
tooling::Replacements Replaces;
unsigned Prev = 0;
unsigned SearchFrom = 0;
llvm::Regex IncludeRegex(R"(^[\t\ ]*#[\t\ ]*include[^"<]*["<]([^">]*)([">]))");
llvm::Regex IncludeRegex(
R"(^[\t\ ]*#[\t\ ]*include[^"<]*(["<][^">]*[">]))");
SmallVector<StringRef, 4> Matches;
SmallVector<IncludeDirective, 16> IncludesInBlock;
// In compiled files, consider the first #include to be the main #include of
// the file if it is not a system #include. This ensures that the header
// doesn't have hidden dependencies
// (http://llvm.org/docs/CodingStandards.html#include-style).
//
// 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 LookForMainHeader = FileName.endswith(".c") ||
FileName.endswith(".cc") ||
FileName.endswith(".cpp")||
FileName.endswith(".c++")||
FileName.endswith(".cxx");
// Create pre-compiled regular expressions for the #include categories.
SmallVector<llvm::Regex, 4> CategoryRegexs;
for (const auto &IncludeBlock : Style.IncludeCategories)
CategoryRegexs.emplace_back(IncludeBlock.first);
for (;;) {
auto Pos = Code.find('\n', SearchFrom);
StringRef Line =
Code.substr(Prev, (Pos != StringRef::npos ? Pos : Code.size()) - Prev);
if (!Line.endswith("\\")) {
if (IncludeRegex.match(Line, &Matches)) {
bool IsAngled = Matches[2] == ">";
if (!IncludesInBlock.empty() &&
IsAngled != IncludesInBlock.back().IsAngled) {
sortIncludes(Style, IncludesInBlock, Ranges, FileName, Replaces);
IncludesInBlock.clear();
unsigned Category;
if (LookForMainHeader && !Matches[1].startswith("<")) {
Category = 0;
} else {
Category = UINT_MAX;
for (unsigned i = 0, e = CategoryRegexs.size(); i != e; ++i) {
if (CategoryRegexs[i].match(Matches[1])) {
Category = Style.IncludeCategories[i].second;
break;
}
}
}
IncludesInBlock.push_back({Matches[1], Line, Prev, Matches[2] == ">"});
LookForMainHeader = false;
IncludesInBlock.push_back({Matches[1], Line, Prev, Category});
} else if (!IncludesInBlock.empty()) {
sortIncludes(Style, IncludesInBlock, Ranges, FileName, Replaces);
IncludesInBlock.clear();

View File

@ -73,7 +73,7 @@ FallbackStyle("fallback-style",
cl::init("LLVM"), cl::cat(ClangFormatCategory));
static cl::opt<std::string>
AssumeFilename("assume-filename",
AssumeFileName("assume-filename",
cl::desc("When reading from stdin, clang-format assumes this\n"
"filename to look for a style config file (with\n"
"-style=file) and to determine the language."),
@ -239,13 +239,13 @@ static bool format(StringRef FileName) {
std::vector<tooling::Range> Ranges;
if (fillRanges(Code.get(), Ranges))
return true;
FormatStyle FormatStyle = getStyle(
Style, (FileName == "-") ? AssumeFilename : FileName, FallbackStyle);
StringRef AssumedFileName = (FileName == "-") ? AssumeFileName : FileName;
FormatStyle FormatStyle = getStyle(Style, AssumedFileName, FallbackStyle);
Replacements Replaces;
std::string ChangedCode;
if (SortIncludes) {
Replaces =
sortIncludes(FormatStyle, Code->getBuffer(), Ranges, FileName);
sortIncludes(FormatStyle, Code->getBuffer(), Ranges, AssumedFileName);
ChangedCode = tooling::applyAllReplacements(Code->getBuffer(), Replaces);
for (const auto &R : Replaces)
Ranges.push_back({R.getOffset(), R.getLength()});
@ -324,7 +324,7 @@ int main(int argc, const char **argv) {
if (DumpConfig) {
std::string Config =
clang::format::configurationAsText(clang::format::getStyle(
Style, FileNames.empty() ? AssumeFilename : FileNames[0],
Style, FileNames.empty() ? AssumeFileName : FileNames[0],
FallbackStyle));
llvm::outs() << Config << "\n";
return 0;

View File

@ -20,13 +20,15 @@ namespace {
class SortIncludesTest : public ::testing::Test {
protected:
std::string sort(llvm::StringRef Code) {
std::string sort(llvm::StringRef Code, StringRef FileName = "input.cpp") {
std::vector<tooling::Range> Ranges(1, tooling::Range(0, Code.size()));
std::string Sorted = applyAllReplacements(
Code, sortIncludes(getLLVMStyle(), Code, Ranges, "input.cpp"));
return applyAllReplacements(
Sorted, reformat(getLLVMStyle(), Sorted, Ranges, "input.cpp"));
std::string Sorted =
applyAllReplacements(Code, sortIncludes(Style, Code, Ranges, FileName));
return applyAllReplacements(Sorted,
reformat(Style, Sorted, Ranges, FileName));
}
FormatStyle Style = getLLVMStyle();
};
TEST_F(SortIncludesTest, BasicSorting) {
@ -76,13 +78,23 @@ TEST_F(SortIncludesTest, SortsLocallyInEachBlock) {
"#include \"c.h\"\n"
"\n"
"#include \"b.h\"\n",
sort("#include \"c.h\"\n"
"#include \"a.h\"\n"
sort("#include \"a.h\"\n"
"#include \"c.h\"\n"
"\n"
"#include \"b.h\"\n"));
}
TEST_F(SortIncludesTest, HandlesAngledIncludesAsSeparateBlocks) {
EXPECT_EQ("#include \"a.h\"\n"
"#include \"c.h\"\n"
"#include <b.h>\n"
"#include <d.h>\n",
sort("#include <d.h>\n"
"#include <b.h>\n"
"#include \"c.h\"\n"
"#include \"a.h\"\n"));
Style = getGoogleStyle(FormatStyle::LK_Cpp);
EXPECT_EQ("#include <b.h>\n"
"#include <d.h>\n"
"#include \"a.h\"\n"
@ -103,6 +115,24 @@ TEST_F(SortIncludesTest, HandlesMultilineIncludes) {
"#include \"b.h\"\n"));
}
TEST_F(SortIncludesTest, LeavesMainHeaderFirst) {
EXPECT_EQ("#include \"llvm/a.h\"\n"
"#include \"b.h\"\n"
"#include \"c.h\"\n",
sort("#include \"llvm/a.h\"\n"
"#include \"c.h\"\n"
"#include \"b.h\"\n"));
// Don't do this in headers.
EXPECT_EQ("#include \"b.h\"\n"
"#include \"c.h\"\n"
"#include \"llvm/a.h\"\n",
sort("#include \"llvm/a.h\"\n"
"#include \"c.h\"\n"
"#include \"b.h\"\n",
"some_header.h"));
}
} // end namespace
} // end namespace format
} // end namespace clang