diff --git a/clang/docs/ClangFormatStyleOptions.rst b/clang/docs/ClangFormatStyleOptions.rst index 8bc13e45bf2f5..50f20ea796a7f 100644 --- a/clang/docs/ClangFormatStyleOptions.rst +++ b/clang/docs/ClangFormatStyleOptions.rst @@ -3220,6 +3220,58 @@ the configuration (without a prefix: ``Auto``). +.. _EmptyLinesAfterIncludes: + +**EmptyLinesAfterIncludes** (``Unsigned``) :versionbadge:`clang-format 1` :ref:`¶ ` + Number of lines after includes. + If set, determines the number of lines to insert after includes. + Limited by MaxEmptyLinesToKeep. + Example: + EmptyLinesAfterIncludes = 1 + + .. code-block:: c++ + + #include + #include + + class Test {}; + + vs EmptyLinesAfterIncludes = 2 + + .. code-block:: c++ + + #include + #include + + + class Test {}; + +.. _EmptyLinesAfterTopLevelComment: + +**EmptyLinesAfterTopLevelComment** (``Unsigned``) :versionbadge:`clang-format 1` :ref:`¶ ` + Number of empty lines after top level comment. + If set, determines the number of empty lines to insert/keep after the top + level comment. Limited by MaxEmptyLinesToKeep. + Example: + EmptyLinesAfterTopLevelComment = 1 + + .. code-block:: c++ + + /* LICENSE TEXT */ + + #include + class Test {}; + + vs EmptyLinesAfterTopLevelComment = 2 + + .. code-block:: c++ + + /* License Text */ + + + #include + class Test {}; + .. _ExperimentalAutoDetectBinPacking: **ExperimentalAutoDetectBinPacking** (``Boolean``) :versionbadge:`clang-format 3.7` :ref:`¶ ` diff --git a/clang/include/clang/Format/Format.h b/clang/include/clang/Format/Format.h index 6fd7947bd2179..5db9ccd8d6776 100644 --- a/clang/include/clang/Format/Format.h +++ b/clang/include/clang/Format/Format.h @@ -2459,6 +2459,52 @@ struct FormatStyle { /// \version 12 EmptyLineBeforeAccessModifierStyle EmptyLineBeforeAccessModifier; + /// \brief Number of lines after includes. + /// If set, determines the number of lines to insert after includes. + /// Limited by MaxEmptyLinesToKeep. + /// Example: + /// EmptyLinesAfterIncludes = 1 + /// \code + /// #include + /// #include + /// + /// class Test {}; + /// + /// \endcode + /// vs EmptyLinesAfterIncludes = 2 + /// \code + /// #include + /// #include + /// + /// + /// class Test {}; + /// \endcode + /// \version 1 + std::optional EmptyLinesAfterIncludes; + + /// \brief Number of empty lines after top level comment. + /// If set, determines the number of empty lines to insert/keep after the top + /// level comment. Limited by MaxEmptyLinesToKeep. + /// Example: + /// EmptyLinesAfterTopLevelComment = 1 + /// \code + /// /* LICENSE TEXT */ + /// + /// #include + /// class Test {}; + /// + /// \endcode + /// vs EmptyLinesAfterTopLevelComment = 2 + /// \code + /// /* License Text */ + /// + /// + /// #include + /// class Test {}; + /// \endcode + /// \version 1 + std::optional EmptyLinesAfterTopLevelComment; + /// If ``true``, clang-format detects whether function calls and /// definitions are formatted with one parameter per line. /// @@ -4827,6 +4873,8 @@ struct FormatStyle { DerivePointerAlignment == R.DerivePointerAlignment && DisableFormat == R.DisableFormat && EmptyLineAfterAccessModifier == R.EmptyLineAfterAccessModifier && + EmptyLinesAfterIncludes == R.EmptyLinesAfterIncludes && + EmptyLinesAfterTopLevelComment == R.EmptyLinesAfterTopLevelComment && EmptyLineBeforeAccessModifier == R.EmptyLineBeforeAccessModifier && ExperimentalAutoDetectBinPacking == R.ExperimentalAutoDetectBinPacking && diff --git a/clang/lib/Format/CMakeLists.txt b/clang/lib/Format/CMakeLists.txt index 84a3c136f650a..64ad0dfb48f84 100644 --- a/clang/lib/Format/CMakeLists.txt +++ b/clang/lib/Format/CMakeLists.txt @@ -18,6 +18,7 @@ add_clang_library(clangFormat SortJavaScriptImports.cpp TokenAnalyzer.cpp TokenAnnotator.cpp + TopLevelCommentSeparator.cpp UnwrappedLineFormatter.cpp UnwrappedLineParser.cpp UsingDeclarationsSorter.cpp diff --git a/clang/lib/Format/Format.cpp b/clang/lib/Format/Format.cpp index 7c2f4dcf3d230..a4aff282c8b25 100644 --- a/clang/lib/Format/Format.cpp +++ b/clang/lib/Format/Format.cpp @@ -27,6 +27,7 @@ #include "SortJavaScriptImports.h" #include "TokenAnalyzer.h" #include "TokenAnnotator.h" +#include "TopLevelCommentSeparator.h" #include "UnwrappedLineFormatter.h" #include "UnwrappedLineParser.h" #include "UsingDeclarationsSorter.h" @@ -995,6 +996,9 @@ template <> struct MappingTraits { IO.mapOptional("DisableFormat", Style.DisableFormat); IO.mapOptional("EmptyLineAfterAccessModifier", Style.EmptyLineAfterAccessModifier); + IO.mapOptional("EmptyLinesAfterIncludes", Style.EmptyLinesAfterIncludes); + IO.mapOptional("EmptyLinesAfterTopLevelComment", + Style.EmptyLinesAfterTopLevelComment); IO.mapOptional("EmptyLineBeforeAccessModifier", Style.EmptyLineBeforeAccessModifier); IO.mapOptional("ExperimentalAutoDetectBinPacking", @@ -1035,6 +1039,8 @@ template <> struct MappingTraits { IO.mapOptional("MaxEmptyLinesToKeep", Style.MaxEmptyLinesToKeep); IO.mapOptional("NamespaceIndentation", Style.NamespaceIndentation); IO.mapOptional("NamespaceMacros", Style.NamespaceMacros); + IO.mapOptional("EmptyLinesAfterTopLevelComment", + Style.EmptyLinesAfterTopLevelComment); IO.mapOptional("ObjCBinPackProtocolList", Style.ObjCBinPackProtocolList); IO.mapOptional("ObjCBlockIndentWidth", Style.ObjCBlockIndentWidth); IO.mapOptional("ObjCBreakBeforeNestedBlockParam", @@ -1501,6 +1507,8 @@ FormatStyle getLLVMStyle(FormatStyle::LanguageKind Language) { LLVMStyle.DerivePointerAlignment = false; LLVMStyle.DisableFormat = false; LLVMStyle.EmptyLineAfterAccessModifier = FormatStyle::ELAAMS_Never; + LLVMStyle.EmptyLinesAfterIncludes = std::nullopt; + LLVMStyle.EmptyLinesAfterTopLevelComment = std::nullopt; LLVMStyle.EmptyLineBeforeAccessModifier = FormatStyle::ELBAMS_LogicalBlock; LLVMStyle.ExperimentalAutoDetectBinPacking = false; LLVMStyle.FixNamespaceComments = true; @@ -3713,6 +3721,12 @@ reformat(const FormatStyle &Style, StringRef Code, }); } + if (Style.EmptyLinesAfterTopLevelComment.has_value()) { + Passes.emplace_back([&](const Environment &Env) { + return TopLevelCommentSeparator(Env, Expanded).process(); + }); + } + if (Style.Language == FormatStyle::LK_ObjC && !Style.ObjCPropertyAttributeOrder.empty()) { Passes.emplace_back([&](const Environment &Env) { diff --git a/clang/lib/Format/TokenAnnotator.h b/clang/lib/Format/TokenAnnotator.h index 05a6daa87d803..06486799ec403 100644 --- a/clang/lib/Format/TokenAnnotator.h +++ b/clang/lib/Format/TokenAnnotator.h @@ -113,6 +113,14 @@ class AnnotatedLine { return First && First->is(tok::comment) && !First->getNextNonComment(); } + bool isInclude() const { + if (!First) + return false; + + const auto *NextToken = First->getNextNonComment(); + return First->is(tok::hash) && NextToken && NextToken->is(tok::pp_include); + } + /// \c true if this line starts with the given tokens in order, ignoring /// comments. template bool startsWith(Ts... Tokens) const { diff --git a/clang/lib/Format/TopLevelCommentSeparator.cpp b/clang/lib/Format/TopLevelCommentSeparator.cpp new file mode 100644 index 0000000000000..b55ddead48f5e --- /dev/null +++ b/clang/lib/Format/TopLevelCommentSeparator.cpp @@ -0,0 +1,70 @@ +//===--- TopLevelCommentSeparator.cpp ---------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// This file implements TopLevelCommentSeparator, a TokenAnalyzer that inserts +/// new lines or removes empty lines after the top level comment (i.e. comment +/// block at the top of the source file), usually license text or documentation. +/// +//===----------------------------------------------------------------------===// + +#include "TopLevelCommentSeparator.h" +#define DEBUG_TYPE "top-level-comment-separator" + +namespace clang { +namespace format { +std::pair TopLevelCommentSeparator::analyze( + TokenAnnotator &Annotator, SmallVectorImpl &AnnotatedLines, + FormatTokenLexer &Tokens) { + assert(Style.EmptyLinesAfterTopLevelComment.has_value()); + AffectedRangeMgr.computeAffectedLines(AnnotatedLines); + tooling::Replacements Result; + separateTopLevelComment(AnnotatedLines, Result, Tokens); + return {Result, 0}; +} + +void TopLevelCommentSeparator::separateTopLevelComment( + SmallVectorImpl &Lines, tooling::Replacements &Result, + FormatTokenLexer &Tokens) { + unsigned NewlineCount = std::min(Style.MaxEmptyLinesToKeep, + *Style.EmptyLinesAfterTopLevelComment) + + 1; + WhitespaceManager Whitespaces( + Env.getSourceManager(), Style, + Style.LineEnding > FormatStyle::LE_CRLF + ? WhitespaceManager::inputUsesCRLF( + Env.getSourceManager().getBufferData(Env.getFileID()), + Style.LineEnding == FormatStyle::LE_DeriveCRLF) + : Style.LineEnding == FormatStyle::LE_CRLF); + + bool InTopLevelComment = false; + for (unsigned I = 0; I < Lines.size(); ++I) { + const auto &CurrentLine = Lines[I]; + if (CurrentLine->isComment()) { + InTopLevelComment = true; + } else if (InTopLevelComment) { + // Do not handle EOF newlines. + if (!CurrentLine->First->is(tok::eof) && CurrentLine->Affected) { + Whitespaces.replaceWhitespace(*CurrentLine->First, NewlineCount, + CurrentLine->First->OriginalColumn, + CurrentLine->First->OriginalColumn); + } + break; + } + } + + for (const auto &R : Whitespaces.generateReplacements()) { + // The add method returns an Error instance which simulates program exit + // code through overloading boolean operator, thus false here indicates + // success. + if (Result.add(R)) + return; + } +} +} // namespace format +} // namespace clang diff --git a/clang/lib/Format/TopLevelCommentSeparator.h b/clang/lib/Format/TopLevelCommentSeparator.h new file mode 100644 index 0000000000000..2901942d8cf7d --- /dev/null +++ b/clang/lib/Format/TopLevelCommentSeparator.h @@ -0,0 +1,42 @@ +//===--- TopLevelCommentSeparator.h -----------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// This file declares TopLevelCommentSeparator, a TokenAnalyzer that inserts +/// new lines or removes empty lines after the top level comment (i.e. comment +/// block at the top of the source file), usually license text or documentation. +/// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_LIB_FORMAT_TOPLEVELCOMMENTSEPARATOR_H +#define LLVM_CLANG_LIB_FORMAT_TOPLEVELCOMMENTSEPARATOR_H + +#include "TokenAnalyzer.h" +#include "WhitespaceManager.h" + +namespace clang { +namespace format { +class TopLevelCommentSeparator : public TokenAnalyzer { +public: + TopLevelCommentSeparator(const Environment &Env, const FormatStyle &Style) + : TokenAnalyzer(Env, Style) {} + + std::pair + analyze(TokenAnnotator &Annotator, + SmallVectorImpl &AnnotatedLines, + FormatTokenLexer &Tokens) override; + +private: + void separateTopLevelComment(SmallVectorImpl &Lines, + tooling::Replacements &Result, + FormatTokenLexer &Tokens); +}; +} // namespace format +} // namespace clang + +#endif diff --git a/clang/lib/Format/UnwrappedLineFormatter.cpp b/clang/lib/Format/UnwrappedLineFormatter.cpp index adeb072434873..c512c919ef426 100644 --- a/clang/lib/Format/UnwrappedLineFormatter.cpp +++ b/clang/lib/Format/UnwrappedLineFormatter.cpp @@ -1535,6 +1535,12 @@ static auto computeNewlines(const AnnotatedLine &Line, } } + if (Style.EmptyLinesAfterIncludes.has_value() && !Line.InMacroBody && + PreviousLine && PreviousLine->isInclude() && !Line.isInclude()) { + Newlines = + 1 + std::min(Style.MaxEmptyLinesToKeep, *Style.EmptyLinesAfterIncludes); + } + return Newlines; } diff --git a/clang/unittests/Format/CMakeLists.txt b/clang/unittests/Format/CMakeLists.txt index 71f5886d946c8..c24f5fecffc1f 100644 --- a/clang/unittests/Format/CMakeLists.txt +++ b/clang/unittests/Format/CMakeLists.txt @@ -36,6 +36,7 @@ add_clang_unittest(FormatTests SortIncludesTest.cpp UsingDeclarationsSorterTest.cpp TokenAnnotatorTest.cpp + TopLevelCommentSeparatorTest.cpp ) clang_target_link_libraries(FormatTests diff --git a/clang/unittests/Format/ConfigParseTest.cpp b/clang/unittests/Format/ConfigParseTest.cpp index 172aaab5988ce..7c9221ad12c8d 100644 --- a/clang/unittests/Format/ConfigParseTest.cpp +++ b/clang/unittests/Format/ConfigParseTest.cpp @@ -1004,6 +1004,10 @@ TEST(ConfigParseTest, ParsesConfiguration) { FormatStyle::SDS_Leave); CHECK_PARSE("SeparateDefinitionBlocks: Never", SeparateDefinitionBlocks, FormatStyle::SDS_Never); + + CHECK_PARSE("EmptyLinesAfterIncludes: 2", EmptyLinesAfterIncludes, 2); + CHECK_PARSE("EmptyLinesAfterTopLevelComment: 2", + EmptyLinesAfterTopLevelComment, 2); } TEST(ConfigParseTest, ParsesConfigurationWithLanguages) { diff --git a/clang/unittests/Format/FormatTest.cpp b/clang/unittests/Format/FormatTest.cpp index c229d9bc56def..f83ef91dcfe2d 100644 --- a/clang/unittests/Format/FormatTest.cpp +++ b/clang/unittests/Format/FormatTest.cpp @@ -26846,6 +26846,26 @@ TEST_F(FormatTest, BreakAdjacentStringLiterals) { Style.BreakAdjacentStringLiterals = false; verifyFormat(Code, Style); } + +TEST_F(FormatTest, EmptyLinesAfterInclude) { + auto Style = getLLVMStyle(); + Style.EmptyLinesAfterIncludes = 2; + Style.MaxEmptyLinesToKeep = 2; + verifyFormat("#include \n\n\n" + "class Test {};", + Style); + + Style.EmptyLinesAfterIncludes = 1; + verifyFormat("#include \n\n" + "class Test {};", + Style); + + Style.EmptyLinesAfterIncludes = 2; + Style.MaxEmptyLinesToKeep = 1; + verifyFormat("#include \n\n" + "class Test {};", + Style); +} } // namespace } // namespace test } // namespace format diff --git a/clang/unittests/Format/TopLevelCommentSeparatorTest.cpp b/clang/unittests/Format/TopLevelCommentSeparatorTest.cpp new file mode 100644 index 0000000000000..6560819556457 --- /dev/null +++ b/clang/unittests/Format/TopLevelCommentSeparatorTest.cpp @@ -0,0 +1,112 @@ +//===- unittest/Format/TopLevelCommentSeparatorTest.cpp - Formatting unit tests +//-----===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "FormatTestBase.h" + +#define DEBUG_TYPE "format-test-comments" + +namespace clang { +namespace format { +namespace test { +namespace { + +class TopLevelCommentSeparatorTest : public FormatTestBase {}; + +TEST_F(TopLevelCommentSeparatorTest, CheckEmptyLines) { + FormatStyle Style = getDefaultStyle(); + Style.EmptyLinesAfterTopLevelComment = 2; + Style.MaxEmptyLinesToKeep = 2; + verifyFormat("// start license\n" + "// license text\n" + "// more license text\n" + "// end license\n\n\n" + "class Test {};", + Style); + + verifyFormat("// start license\n" + "// license text\n" + "// more license text\n" + "// end license\n\n\n" + "static int test = 10;", + Style); + + verifyFormat("// start license\n" + "// license text\n" + "// more license text\n" + "// end license\n\n\n" + "#include ", + Style); + + verifyFormat("// start license\n" + "// license text\n" + "// more license text\n" + "// end license", + Style); + + verifyFormat("/* top level comment */\n\n\n" + "#include \n" + "class Test {\n" + "public:\n" + " void test() {}\n" + "};\n" + "int main() {\n" + " Test test;\n" + " test.test();\n" + " return 0;\n" + "}", + Style); + + Style.EmptyLinesAfterTopLevelComment = 1; + verifyFormat("// start license\n" + "// license text\n" + "// more license text\n" + "// end license\n\n" + "class Test {};", + Style); + + verifyFormat("// start license\n" + "// license text\n" + "// more license text\n" + "// end license\n\n" + "#include ", + Style); + + verifyFormat("/* top level comment */\n\n" + "#include \n" + "class Test {};", + Style); +} + +TEST_F(TopLevelCommentSeparatorTest, LimitedByMaxEmptyLinesToKeep) { + FormatStyle Style = getDefaultStyle(); + Style.EmptyLinesAfterTopLevelComment = 2; + Style.MaxEmptyLinesToKeep = 1; + verifyFormat("// start license\n" + "// license text\n" + "// more license text\n" + "// end license\n\n" + "class Test {};", + Style); + + verifyFormat("// start license\n" + "// license text\n" + "// more license text\n" + "// end license\n\n" + "#include ", + Style); + + verifyFormat("/* top level comment */\n\n" + "#include \n" + "class Test {};", + Style); +} +} // namespace +} // namespace test +} // namespace format +} // namespace clang