clang-format: Make include sorting's main include detection configurable.

This patch adds a regular expression to configure suffixes of an
included file to check whether it is the "main" include of the current
file. Previously, clang-format has allowed arbitrary suffixes on the
formatted file, which is still the case when no IncludeMainRegex is
specified.

llvm-svn: 263943
This commit is contained in:
Daniel Jasper 2016-03-21 14:11:27 +00:00
parent 4aeab5fbf2
commit 9c8ff3551a
5 changed files with 73 additions and 3 deletions

View File

@ -519,6 +519,19 @@ the configuration (without a prefix: ``Auto``).
- Regex: '.\*'
Priority: 1
**IncludeIsMainRegex** (``std::string``)
Specify a regular expression of suffixes that are allowed in the
file-to-main-include mapping.
When guessing whether a #include is the "main" include (to assign
category 0, see above), use this regex of allowed suffixes to the header
stem. A partial match is done, so that:
- "" means "arbitrary suffix"
- "$" means "no suffix"
For example, if configured to "(_test)?$", then a header a.h would be seen
as the "main" include in both a.cc and a_test.cc.
**IndentCaseLabels** (``bool``)
Indent case labels one level from the switch statement.
@ -532,6 +545,22 @@ the configuration (without a prefix: ``Auto``).
Indent if a function definition or declaration is wrapped after the
type.
**JavaScriptQuotes** (``JavaScriptQuoteStyle``)
The JavaScriptQuoteStyle to use for JavaScript strings.
Possible values:
* ``JSQS_Leave`` (in configuration: ``Leave``)
Leave string quotes as they are.
* ``JSQS_Single`` (in configuration: ``Single``)
Always use single quotes.
* ``JSQS_Double`` (in configuration: ``Double``)
Always use double quotes.
**KeepEmptyLinesAtTheStartOfBlocks** (``bool``)
If true, empty lines at the start of blocks are kept.

View File

@ -401,6 +401,19 @@ struct FormatStyle {
/// \endcode
std::vector<IncludeCategory> IncludeCategories;
/// \brief Specify a regular expression of suffixes that are allowed in the
/// file-to-main-include mapping.
///
/// When guessing whether a #include is the "main" include (to assign
/// category 0, see above), use this regex of allowed suffixes to the header
/// stem. A partial match is done, so that:
/// - "" means "arbitrary suffix"
/// - "$" means "no suffix"
///
/// For example, if configured to "(_test)?$", then a header a.h would be seen
/// as the "main" include in both a.cc and a_test.cc.
std::string IncludeIsMainRegex;
/// \brief Indent case labels one level from the switch statement.
///
/// When ``false``, use the same indentation level as for the switch statement.

View File

@ -300,6 +300,7 @@ template <> struct MappingTraits<FormatStyle> {
Style.ExperimentalAutoDetectBinPacking);
IO.mapOptional("ForEachMacros", Style.ForEachMacros);
IO.mapOptional("IncludeCategories", Style.IncludeCategories);
IO.mapOptional("IncludeIsMainRegex", Style.IncludeIsMainRegex);
IO.mapOptional("IndentCaseLabels", Style.IndentCaseLabels);
IO.mapOptional("IndentWidth", Style.IndentWidth);
IO.mapOptional("IndentWrappedFunctionNames",
@ -517,6 +518,7 @@ FormatStyle getLLVMStyle() {
LLVMStyle.IncludeCategories = {{"^\"(llvm|llvm-c|clang|clang-c)/", 2},
{"^(<|\"(gtest|isl|json)/)", 3},
{".*", 1}};
LLVMStyle.IncludeIsMainRegex = "$";
LLVMStyle.IndentCaseLabels = false;
LLVMStyle.IndentWrappedFunctionNames = false;
LLVMStyle.IndentWidth = 2;
@ -569,6 +571,7 @@ FormatStyle getGoogleStyle(FormatStyle::LanguageKind Language) {
GoogleStyle.ConstructorInitializerAllOnOneLineOrOnePerLine = true;
GoogleStyle.DerivePointerAlignment = true;
GoogleStyle.IncludeCategories = {{"^<.*\\.h>", 1}, {"^<.*", 2}, {".*", 3}};
GoogleStyle.IncludeIsMainRegex = "([-_](test|unittest))?$";
GoogleStyle.IndentCaseLabels = true;
GoogleStyle.KeepEmptyLinesAtTheStartOfBlocks = false;
GoogleStyle.ObjCSpaceAfterProperty = false;
@ -1961,8 +1964,12 @@ tooling::Replacements sortIncludes(const FormatStyle &Style, StringRef Code,
StringRef HeaderStem =
llvm::sys::path::stem(IncludeName.drop_front(1).drop_back(1));
if (FileStem.startswith(HeaderStem)) {
Category = 0;
MainIncludeFound = true;
llvm::Regex MainIncludeRegex(
(HeaderStem + Style.IncludeIsMainRegex).str());
if (MainIncludeRegex.match(FileStem)) {
Category = 0;
MainIncludeFound = true;
}
}
}
IncludesInBlock.push_back({IncludeName, Line, Prev, Category});

View File

@ -9941,6 +9941,7 @@ TEST_F(FormatTest, ParsesConfiguration) {
SpacesBeforeTrailingComments, 1234u);
CHECK_PARSE("IndentWidth: 32", IndentWidth, 32u);
CHECK_PARSE("ContinuationIndentWidth: 11", ContinuationIndentWidth, 11u);
CHECK_PARSE("CommentPragmas: '// abc$'", CommentPragmas, "// abc$");
Style.PointerAlignment = FormatStyle::PAS_Middle;
CHECK_PARSE("PointerAlignment: Left", PointerAlignment,
@ -10099,6 +10100,7 @@ TEST_F(FormatTest, ParsesConfiguration) {
" - Regex: .*\n"
" Priority: 1",
IncludeCategories, ExpectedCategories);
CHECK_PARSE("IncludeIsMainRegex: 'abc$'", IncludeIsMainRegex, "abc$");
}
TEST_F(FormatTest, ParsesConfigurationWithLanguages) {

View File

@ -175,6 +175,7 @@ TEST_F(SortIncludesTest, HandlesMultilineIncludes) {
}
TEST_F(SortIncludesTest, LeavesMainHeaderFirst) {
Style.IncludeIsMainRegex = "([-_](test|unittest))?$";
EXPECT_EQ("#include \"llvm/a.h\"\n"
"#include \"b.h\"\n"
"#include \"c.h\"\n",
@ -188,7 +189,7 @@ TEST_F(SortIncludesTest, LeavesMainHeaderFirst) {
sort("#include \"llvm/a.h\"\n"
"#include \"c.h\"\n"
"#include \"b.h\"\n",
"a_main.cc"));
"a_test.cc"));
EXPECT_EQ("#include \"llvm/input.h\"\n"
"#include \"b.h\"\n"
"#include \"c.h\"\n",
@ -197,6 +198,24 @@ TEST_F(SortIncludesTest, LeavesMainHeaderFirst) {
"#include \"b.h\"\n",
"input.mm"));
// Don't allow prefixes.
EXPECT_EQ("#include \"b.h\"\n"
"#include \"c.h\"\n"
"#include \"llvm/not_a.h\"\n",
sort("#include \"llvm/not_a.h\"\n"
"#include \"c.h\"\n"
"#include \"b.h\"\n",
"a.cc"));
// Don't do this for _main and other suffixes.
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",
"a_main.cc"));
// Don't do this in headers.
EXPECT_EQ("#include \"b.h\"\n"
"#include \"c.h\"\n"