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,26 @@ ``ClassImpl.hpp`` would not have the main include file put on top before any other include. +**IncludeSortAlphabetically** (``bool``) + Specify if sorting should be done in an alphabetical and + case sensitive fashion. + + When ``false``, includes are sorted in an ASCIIbetical + fashion. + When ``true``, includes are sorted in an alphabetical + fashion with case used as a tie-breaker. + + .. code-block:: c++ + + false: true: + #include "A/B.h" vs. #include "A/B.h" + #include "A/b.h" #include "A/b.h" + #include "B/A.h" #include "a/b.h" + #include "B/a.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,26 @@ /// ``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 an alphabetical and + /// case sensitive fashion. + /// + /// When ``false``, includes are sorted in an ASCIIbetical + /// fashion. + /// When ``true``, includes are sorted in an alphabetical + /// fashion with case used as a tie-breaker. + /// + /// \code + /// false: true: + /// #include "A/B.h" vs. #include "A/B.h" + /// #include "A/b.h" #include "A/b.h" + /// #include "B/A.h" #include "a/b.h" + /// #include "B/a.h" #include "B/A.h" + /// #include "a/b.h" #include "B/a.h" + /// \endcode + /// + /// This option is off by default. + bool IncludeSortAlphabetically; }; } // 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("IncludeSortAlphabetically", + Style.IncludeStyle.IncludeSortAlphabetically); IO.mapOptional("IndentCaseLabels", Style.IndentCaseLabels); IO.mapOptional("IndentCaseBlocks", Style.IndentCaseBlocks); IO.mapOptional("IndentGotoLabels", Style.IndentGotoLabels); @@ -925,6 +927,7 @@ {".*", 1, 0, false}}; LLVMStyle.IncludeStyle.IncludeIsMainRegex = "(Test)?$"; LLVMStyle.IncludeStyle.IncludeBlocks = tooling::IncludeStyle::IBS_Preserve; + LLVMStyle.IncludeStyle.IncludeSortAlphabetically = false; LLVMStyle.IndentCaseLabels = false; LLVMStyle.IndentCaseBlocks = false; LLVMStyle.IndentGotoLabels = true; @@ -2179,10 +2182,23 @@ for (unsigned i = 0, e = Includes.size(); i != e; ++i) { Indices.push_back(i); } - llvm::stable_sort(Indices, [&](unsigned LHSI, unsigned RHSI) { - return std::tie(Includes[LHSI].Priority, Includes[LHSI].Filename) < - std::tie(Includes[RHSI].Priority, Includes[RHSI].Filename); - }); + + if (Style.IncludeStyle.IncludeSortAlphabetically) { + llvm::stable_sort(Indices, [&](unsigned LHSI, unsigned RHSI) { + 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); + }); + } else { + llvm::stable_sort(Indices, [&](unsigned LHSI, unsigned RHSI) { + return std::tie(Includes[LHSI].Priority, Includes[LHSI].Filename) < + std::tie(Includes[RHSI].Priority, Includes[RHSI].Filename); + }); + } + // The index of the include on which the cursor will be put after // sorting/deduplicating. unsigned CursorIndex; 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.IncludeSortAlphabetically, + "IncludeSortAlphabetically"); 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,49 @@ "a.cc")); } +TEST_F(SortIncludesTest, SupportOptionalAlphabeticalSorting) { + EXPECT_FALSE(Style.IncludeSortAlphabetically); + + Style.IncludeSortAlphabetically = 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))?$";