Index: clang/docs/ClangFormat.rst =================================================================== --- clang/docs/ClangFormat.rst +++ clang/docs/ClangFormat.rst @@ -30,9 +30,9 @@ Clang-format options: - -assume-filename= - When reading from stdin, clang-format assumes this - filename to look for a style config file (with - -style=file) and to determine the language. + -assume-filename= - Override filename used to determine the language. + When reading from stdin, clang-format assumes this + filename to determine the language. -cursor= - The position of the cursor when invoking clang-format from an editor integration -dump-config - Dump configuration options to stdout and exit. Index: clang/docs/ClangFormatStyleOptions.rst =================================================================== --- clang/docs/ClangFormatStyleOptions.rst +++ clang/docs/ClangFormatStyleOptions.rst @@ -1510,6 +1510,26 @@ 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. +**IncludeIsMainSourceRegex** (``std::string``) + Specify a regular expression for files being formatted + that are allowed to be considered "main" in the + file-to-main-include mapping. + + By default, clang-format considers files as "main" only when they end + with: ``.c``, ``.cc``, ``.cpp``, ``.c++``, ``.cxx``, ``.m`` or ``.mm`` + extensions. + For these files a guessing of "main" include takes place + (to assign category 0, see above). This config option allows for + additional suffixes and extensions for files to be considered as "main". + + For example, if this option is configured to ``(Impl\.hpp)$``, + then a file ``ClassImpl.hpp`` is considered "main" (in addition to + ``Class.c``, ``Class.cc``, ``Class.cpp`` and so on) and "main + include file" logic will be executed (with *IncludeIsMainRegex* setting + also being respected in later phase). Without this option set, + ``ClassImpl.hpp`` would not have the main include file put on top + before any other include. + **IndentCaseLabels** (``bool``) Indent case labels one level from the switch statement. Index: clang/docs/ReleaseNotes.rst =================================================================== --- clang/docs/ReleaseNotes.rst +++ clang/docs/ReleaseNotes.rst @@ -192,7 +192,23 @@ clang-format ------------ -- ... +- Option *IncludeIsMainSourceRegex* added to allow for additional + suffixes and file extensions to be considered as a source file + for execution of logic that looks for "main *include* file" to put + it on top. + + By default, clang-format considers *source* files as "main" only when + they end with: ``.c``, ``.cc``, ``.cpp``, ``.c++``, ``.cxx``, + ``.m`` or ``.mm`` extensions. This config option allows to + extend this set of source files considered as "main". + + For example, if this option is configured to ``(Impl\.hpp)$``, + then a file ``ClassImpl.hpp`` is considered "main" (in addition to + ``Class.c``, ``Class.cc``, ``Class.cpp`` and so on) and "main + include file" logic will be executed (with *IncludeIsMainRegex* setting + also being respected in later phase). Without this option set, + ``ClassImpl.hpp`` would not have the main include file put on top + before any other include. libclang -------- Index: clang/include/clang/Tooling/Inclusions/IncludeStyle.h =================================================================== --- clang/include/clang/Tooling/Inclusions/IncludeStyle.h +++ clang/include/clang/Tooling/Inclusions/IncludeStyle.h @@ -109,6 +109,25 @@ /// 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; + + /// Specify a regular expression for files being formatted + /// that are allowed to be considered "main" in the + /// file-to-main-include mapping. + /// + /// By default, clang-format considers files as "main" only when they end + /// with: .c, .cc., .cpp, .c++, .cxx, .m or .mm extensions. + /// For these files a guessing of "main" include takes place + /// (to assign category 0, see above). This config option allows for + /// additional suffixes and extensions for files to be considered as "main". + /// + /// For example, if this option is configured to ``(Impl\.hpp)$``, + /// then a file ``ClassImpl.hpp`` is considered "main" (in addition to + /// ``Class.c``, ``Class.cc``, ``Class.cpp`` and so on) and "main + /// include file" logic will be executed (with *IncludeIsMainRegex* setting + /// also being respected in later phase). Without this option set, + /// ``ClassImpl.hpp`` would not have the main include file put on top + /// before any other include. + std::string IncludeIsMainSourceRegex; }; } // namespace tooling Index: clang/lib/Format/Format.cpp =================================================================== --- clang/lib/Format/Format.cpp +++ clang/lib/Format/Format.cpp @@ -452,6 +452,8 @@ IO.mapOptional("IncludeBlocks", Style.IncludeStyle.IncludeBlocks); IO.mapOptional("IncludeCategories", Style.IncludeStyle.IncludeCategories); IO.mapOptional("IncludeIsMainRegex", Style.IncludeStyle.IncludeIsMainRegex); + IO.mapOptional("IncludeIsMainSourceRegex", + Style.IncludeStyle.IncludeIsMainSourceRegex); IO.mapOptional("IndentCaseLabels", Style.IndentCaseLabels); IO.mapOptional("IndentGotoLabels", Style.IndentGotoLabels); IO.mapOptional("IndentPPDirectives", Style.IndentPPDirectives); Index: clang/lib/Tooling/Inclusions/HeaderIncludes.cpp =================================================================== --- clang/lib/Tooling/Inclusions/HeaderIncludes.cpp +++ clang/lib/Tooling/Inclusions/HeaderIncludes.cpp @@ -180,10 +180,11 @@ FileStem = llvm::sys::path::stem(FileName); for (const auto &Category : Style.IncludeCategories) CategoryRegexs.emplace_back(Category.Regex, llvm::Regex::IgnoreCase); + llvm::Regex MainFileRegex(Style.IncludeIsMainSourceRegex); IsMainFile = FileName.endswith(".c") || FileName.endswith(".cc") || FileName.endswith(".cpp") || FileName.endswith(".c++") || FileName.endswith(".cxx") || FileName.endswith(".m") || - FileName.endswith(".mm"); + FileName.endswith(".mm") || MainFileRegex.match(FileName); } int IncludeCategoryManager::getIncludePriority(StringRef IncludeName, Index: clang/tools/clang-format/ClangFormat.cpp =================================================================== --- clang/tools/clang-format/ClangFormat.cpp +++ clang/tools/clang-format/ClangFormat.cpp @@ -72,12 +72,12 @@ cl::init(clang::format::DefaultFallbackStyle), cl::cat(ClangFormatCategory)); -static cl::opt -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."), - cl::init(""), cl::cat(ClangFormatCategory)); +static cl::opt AssumeFileName( + "assume-filename", + cl::desc("Override filename used to determine the language.\n" + "When reading from stdin, clang-format assumes this\n" + "filename to determine the language."), + cl::init(""), cl::cat(ClangFormatCategory)); static cl::opt Inplace("i", cl::desc("Inplace edit s, if specified."), Index: clang/unittests/Format/FormatTest.cpp =================================================================== --- clang/unittests/Format/FormatTest.cpp +++ clang/unittests/Format/FormatTest.cpp @@ -12194,6 +12194,8 @@ IncludeStyle.IncludeCategories, ExpectedCategories); CHECK_PARSE("IncludeIsMainRegex: 'abc$'", IncludeStyle.IncludeIsMainRegex, "abc$"); + CHECK_PARSE("IncludeIsMainSourceRegex: 'abc$'", + IncludeStyle.IncludeIsMainSourceRegex, "abc$"); Style.RawStringFormats.clear(); std::vector ExpectedRawStringFormats = { Index: clang/unittests/Format/SortIncludesTest.cpp =================================================================== --- clang/unittests/Format/SortIncludesTest.cpp +++ clang/unittests/Format/SortIncludesTest.cpp @@ -395,6 +395,57 @@ "a.cc")); } +TEST_F(SortIncludesTest, LeavesMainHeaderFirstInAdditionalExtensions) { + Style.IncludeIsMainRegex = "([-_](test|unittest))?|(Impl)?$"; + 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_test.xxx")); + 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", + "aImpl.hpp")); + + // .cpp extension is considered "main" by default + 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", + "aImpl.cpp")); + 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", + "a_test.cpp")); + + // Allow additional filenames / extensions + Style.IncludeIsMainSourceRegex = "(Impl\\.hpp)|(\\.xxx)$"; + 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", + "a_test.xxx")); + 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", + "aImpl.hpp")); +} + TEST_F(SortIncludesTest, RecognizeMainHeaderInAllGroups) { Style.IncludeIsMainRegex = "([-_](test|unittest))?$"; Style.IncludeBlocks = tooling::IncludeStyle::IBS_Merge;