diff --git a/clang/docs/ClangFormatStyleOptions.rst b/clang/docs/ClangFormatStyleOptions.rst --- a/clang/docs/ClangFormatStyleOptions.rst +++ b/clang/docs/ClangFormatStyleOptions.rst @@ -2036,6 +2036,33 @@ ``ClassImpl.hpp`` would not have the main include file put on top before any other include. +**IncludeSortCaseAware** (``bool``) + Specify if sorting should be done in a case aware fashion. + + By default, clang-format sorts includes in an ASCIIbetical + fashion. For instance: + + .. code-block:: c++ + + #include "B/a.h" into #include "A/B.h" + #include "B/A.h" #include "A/b.h" + #include "A/B.h" #include "B/A.h" + #include "a/b.h" #include "B/a.h" + #include "A/b.h" #include "a/b.h" + + This option will cause includes to be sorted alphabetically with case + used as a tie-breaker. For instance: + + .. code-block:: c++ + + #include "B/a.h" into #include "A/B.h" + #include "B/A.h" #include "A/b.h" + #include "A/B.h" #include "a/b.h" + #include "a/b.h" #include "B/A.h" + #include "A/b.h" #include "B/a.h" + + This option is off by default. + **IndentCaseBlocks** (``bool``) Indent case label blocks one level from the case label. diff --git a/clang/include/clang/Tooling/Inclusions/IncludeStyle.h b/clang/include/clang/Tooling/Inclusions/IncludeStyle.h --- a/clang/include/clang/Tooling/Inclusions/IncludeStyle.h +++ b/clang/include/clang/Tooling/Inclusions/IncludeStyle.h @@ -147,6 +147,33 @@ /// ``ClassImpl.hpp`` would not have the main include file put on top /// before any other include. std::string IncludeIsMainSourceRegex; + + /// Specify if sorting should be done in a case aware fashion. + /// + /// By default, clang-format sorts includes in an ASCIIbetical + /// fashion. For instance: + /// + /// \code + /// #include "B/a.h" into #include "A/B.h" + /// #include "B/A.h" #include "A/b.h" + /// #include "A/B.h" #include "B/A.h" + /// #include "a/b.h" #include "B/a.h" + /// #include "A/b.h" #include "a/b.h" + /// \endcode + /// + /// This option will cause includes to be sorted alphabetically with case + /// used as a tie-breaker. For instance: + /// + /// \code + /// #include "B/a.h" into #include "A/B.h" + /// #include "B/A.h" #include "A/b.h" + /// #include "A/B.h" #include "a/b.h" + /// #include "a/b.h" #include "B/A.h" + /// #include "A/b.h" #include "B/a.h" + /// \endcode + /// + /// This option is off by default. + bool IncludeSortCaseAware; }; } // namespace tooling diff --git a/clang/lib/Format/Format.cpp b/clang/lib/Format/Format.cpp --- a/clang/lib/Format/Format.cpp +++ b/clang/lib/Format/Format.cpp @@ -556,6 +556,8 @@ IO.mapOptional("IncludeIsMainRegex", Style.IncludeStyle.IncludeIsMainRegex); IO.mapOptional("IncludeIsMainSourceRegex", Style.IncludeStyle.IncludeIsMainSourceRegex); + IO.mapOptional("IncludeSortCaseAware", + Style.IncludeStyle.IncludeSortCaseAware); IO.mapOptional("IndentCaseLabels", Style.IndentCaseLabels); IO.mapOptional("IndentCaseBlocks", Style.IndentCaseBlocks); IO.mapOptional("IndentGotoLabels", Style.IndentGotoLabels); @@ -2180,6 +2182,14 @@ Indices.push_back(i); } llvm::stable_sort(Indices, [&](unsigned LHSI, unsigned RHSI) { + if (Style.IncludeStyle.IncludeSortCaseAware) { + const auto LHSFilenameLower = Includes[LHSI].Filename.lower(); + const auto RHSFilenameLower = Includes[RHSI].Filename.lower(); + return std::tie(Includes[LHSI].Priority, LHSFilenameLower, + Includes[LHSI].Filename) < + std::tie(Includes[RHSI].Priority, RHSFilenameLower, + Includes[RHSI].Filename); + } return std::tie(Includes[LHSI].Priority, Includes[LHSI].Filename) < std::tie(Includes[RHSI].Priority, Includes[RHSI].Filename); }); diff --git a/clang/unittests/Format/FormatTest.cpp b/clang/unittests/Format/FormatTest.cpp --- a/clang/unittests/Format/FormatTest.cpp +++ b/clang/unittests/Format/FormatTest.cpp @@ -14395,6 +14395,8 @@ CHECK_PARSE_BOOL(DeriveLineEnding); CHECK_PARSE_BOOL(DerivePointerAlignment); CHECK_PARSE_BOOL_FIELD(DerivePointerAlignment, "DerivePointerBinding"); + CHECK_PARSE_BOOL_FIELD(IncludeStyle.IncludeSortCaseAware, + "IncludeSortCaseAware"); CHECK_PARSE_BOOL(DisableFormat); CHECK_PARSE_BOOL(IndentCaseLabels); CHECK_PARSE_BOOL(IndentCaseBlocks); diff --git a/clang/unittests/Format/SortIncludesTest.cpp b/clang/unittests/Format/SortIncludesTest.cpp --- a/clang/unittests/Format/SortIncludesTest.cpp +++ b/clang/unittests/Format/SortIncludesTest.cpp @@ -598,6 +598,47 @@ "a.cc")); } +TEST_F(SortIncludesTest, SupportOptionalCaseAwareSorting) { + Style.IncludeSortCaseAware = true; + + EXPECT_EQ("#include \"A/B.h\"\n" + "#include \"A/b.h\"\n" + "#include \"a/b.h\"\n" + "#include \"B/A.h\"\n" + "#include \"B/a.h\"\n", + sort("#include \"B/a.h\"\n" + "#include \"B/A.h\"\n" + "#include \"A/B.h\"\n" + "#include \"a/b.h\"\n" + "#include \"A/b.h\"\n", + "a.h")); + + Style.IncludeBlocks = clang::tooling::IncludeStyle::IBS_Regroup; + Style.IncludeCategories = { + {"^\"", 1, 0, false}, {"^<.*\\.h>$", 2, 0, false}, {"^<", 3, 0, false}}; + + StringRef UnsortedCode = "#include \"qt.h\"\n" + "#include \n" + "#include \n" + "#include \n" + "#include \n" + "#include \"vlib.h\"\n" + "#include \"Vlib.h\"\n" + "#include \"AST.h\"\n"; + + EXPECT_EQ("#include \"AST.h\"\n" + "#include \"qt.h\"\n" + "#include \"Vlib.h\"\n" + "#include \"vlib.h\"\n" + "\n" + "#include \n" + "#include \n" + "\n" + "#include \n" + "#include \n", + sort(UnsortedCode)); +} + TEST_F(SortIncludesTest, SupportCaseInsensitiveMatching) { // Setup an regex for main includes so we can cover those as well. Style.IncludeIsMainRegex = "([-_](test|unittest))?$";