Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions clang/docs/ClangFormatStyleOptions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3220,6 +3220,25 @@ the configuration (without a prefix: ``Auto``).



.. _EmptyLinesAfterIncludes:

**EmptyLinesAfterIncludes** (``Unsigned``) :versionbadge:`clang-format 18` :ref:`<EmptyLinesAfterIncludes>`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

18 will be correct today, but it won't be tomorrow (as they branch I think), I doubt we are going to land this in 18

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where do I have to sign up for that knowledge? Back with the mailing lists I got that information, but for this new stuff I'm kind of too old.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've always looked at the "Upcoming Release" over at https://llvm.org/

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

18 is now wrong it should be 20

Number of lines after each include area. An include area is
a list of consecutive include statements. The include area may be
composed of multiple include blocks.
Limited by MaxEmptyLinesToKeep.
Example:

.. code-block:: c++


EmptyLinesAfterIncludes: 1 vs. EmptyLinesAfterIncludes: 2
#include <string> #include <string>
#include <map> #include <map>

class Test {};
class Test {};

.. _ExperimentalAutoDetectBinPacking:

**ExperimentalAutoDetectBinPacking** (``Boolean``) :versionbadge:`clang-format 3.7` :ref:`<ExperimentalAutoDetectBinPacking>`
Expand Down
1 change: 1 addition & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1260,6 +1260,7 @@ clang-format
- Add ``.clang-format-ignore`` files.
- Add ``AlignFunctionPointers`` sub-option for ``AlignConsecutiveDeclarations``.
- Add ``SkipMacroDefinitionBody`` option.
- Add ``EmptyLinesAfterIncludes`` option.

libclang
--------
Expand Down
18 changes: 18 additions & 0 deletions clang/include/clang/Format/Format.h
Original file line number Diff line number Diff line change
Expand Up @@ -2459,6 +2459,23 @@ struct FormatStyle {
/// \version 12
EmptyLineBeforeAccessModifierStyle EmptyLineBeforeAccessModifier;

/// \brief Number of lines after each include area. An include area is
/// a list of consecutive include statements. The include area may be
/// composed of multiple include blocks.
/// Limited by MaxEmptyLinesToKeep.
/// Example:
/// \code
///
/// EmptyLinesAfterIncludes: 1 vs. EmptyLinesAfterIncludes: 2
/// #include <string> #include <string>
/// #include <map> #include <map>
///
/// class Test {};
/// class Test {};
/// \endcode
/// \version 18
std::optional<unsigned> EmptyLinesAfterIncludes;

/// If ``true``, clang-format detects whether function calls and
/// definitions are formatted with one parameter per line.
///
Expand Down Expand Up @@ -4831,6 +4848,7 @@ struct FormatStyle {
DerivePointerAlignment == R.DerivePointerAlignment &&
DisableFormat == R.DisableFormat &&
EmptyLineAfterAccessModifier == R.EmptyLineAfterAccessModifier &&
EmptyLinesAfterIncludes == R.EmptyLinesAfterIncludes &&
EmptyLineBeforeAccessModifier == R.EmptyLineBeforeAccessModifier &&
Comment on lines +4851 to 4852
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
EmptyLinesAfterIncludes == R.EmptyLinesAfterIncludes &&
EmptyLineBeforeAccessModifier == R.EmptyLineBeforeAccessModifier &&
EmptyLineBeforeAccessModifier == R.EmptyLineBeforeAccessModifier &&
EmptyLinesAfterIncludes == R.EmptyLinesAfterIncludes &&

ExperimentalAutoDetectBinPacking ==
R.ExperimentalAutoDetectBinPacking &&
Expand Down
1 change: 1 addition & 0 deletions clang/lib/Format/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ add_clang_library(clangFormat
Format.cpp
FormatToken.cpp
FormatTokenLexer.cpp
IncludesSeparator.cpp
IntegerLiteralSeparatorFixer.cpp
MacroCallReconstructor.cpp
MacroExpander.cpp
Expand Down
9 changes: 9 additions & 0 deletions clang/lib/Format/Format.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#include "FormatInternal.h"
#include "FormatToken.h"
#include "FormatTokenLexer.h"
#include "IncludesSeparator.h"
#include "IntegerLiteralSeparatorFixer.h"
#include "NamespaceEndCommentsFixer.h"
#include "ObjCPropertyAttributeOrderFixer.h"
Expand Down Expand Up @@ -995,6 +996,7 @@ template <> struct MappingTraits<FormatStyle> {
IO.mapOptional("DisableFormat", Style.DisableFormat);
IO.mapOptional("EmptyLineAfterAccessModifier",
Style.EmptyLineAfterAccessModifier);
IO.mapOptional("EmptyLinesAfterIncludes", Style.EmptyLinesAfterIncludes);
IO.mapOptional("EmptyLineBeforeAccessModifier",
Style.EmptyLineBeforeAccessModifier);
IO.mapOptional("ExperimentalAutoDetectBinPacking",
Expand Down Expand Up @@ -1502,6 +1504,7 @@ FormatStyle getLLVMStyle(FormatStyle::LanguageKind Language) {
LLVMStyle.DerivePointerAlignment = false;
LLVMStyle.DisableFormat = false;
LLVMStyle.EmptyLineAfterAccessModifier = FormatStyle::ELAAMS_Never;
LLVMStyle.EmptyLinesAfterIncludes = std::nullopt;
LLVMStyle.EmptyLineBeforeAccessModifier = FormatStyle::ELBAMS_LogicalBlock;
LLVMStyle.ExperimentalAutoDetectBinPacking = false;
LLVMStyle.FixNamespaceComments = true;
Expand Down Expand Up @@ -3715,6 +3718,12 @@ reformat(const FormatStyle &Style, StringRef Code,
});
}

if (Style.EmptyLinesAfterIncludes.has_value()) {
Passes.emplace_back([&](const Environment &Env) {
return IncludesSeparator(Env, Expanded).process();
});
}

if (Style.Language == FormatStyle::LK_ObjC &&
!Style.ObjCPropertyAttributeOrder.empty()) {
Passes.emplace_back([&](const Environment &Env) {
Expand Down
159 changes: 159 additions & 0 deletions clang/lib/Format/IncludesSeparator.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
//===--- IncludesSeparator.cpp ---------------------------*- C++ -*-===//
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this have to be done in a separate pass? Couldn't it be done in the normal formatting?

Copy link
Contributor Author

@seranu seranu Jan 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Initially, I tried implementing it in UnwrappedLineFormatter but ran into problems while handling conditional compilation statements, where lookahead is needed. I also looked at Format.cpp:sortIncludes function, but doesn't seem any better. If you have suggestions, I'm happy to look into them.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe you could work something out with the line type?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this have to be done in a separate pass? Couldn't it be done in the normal formatting?

+1 to that, we shouldn't really need a seperate pass

//
// 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 IncludesSeparator, a TokenAnalyzer that inserts
/// new lines or removes empty lines after an include area.
/// An includes area is a list of consecutive include statements.
///
//===----------------------------------------------------------------------===//

#include "IncludesSeparator.h"
#include "TokenAnnotator.h"
#define DEBUG_TYPE "includes-separator"

namespace {
bool isConditionalCompilationStart(const clang::format::AnnotatedLine &Line) {
if (!Line.First)
return false;
const auto *NextToken = Line.First->getNextNonComment();
return Line.First->is(clang::tok::hash) && NextToken &&
NextToken->isOneOf(clang::tok::pp_if, clang::tok::pp_ifdef,
clang::tok::pp_ifndef, clang::tok::pp_defined);
}

bool isConditionalCompilationEnd(const clang::format::AnnotatedLine &Line) {
if (!Line.First)
return false;
const auto *NextToken = Line.First->getNextNonComment();
return Line.First->is(clang::tok::hash) && NextToken &&
NextToken->is(clang::tok::pp_endif);
}

bool isConditionalCompilationStatement(
const clang::format::AnnotatedLine &Line) {
if (!Line.First)
return false;
const auto *NextToken = Line.First->getNextNonComment();
return Line.First->is(clang::tok::hash) && NextToken &&
NextToken->isOneOf(clang::tok::pp_if, clang::tok::pp_ifdef,
clang::tok::pp_ifndef, clang::tok::pp_elif,
clang::tok::pp_elifdef, clang::tok::pp_elifndef,
clang::tok::pp_else, clang::tok::pp_defined,
clang::tok::pp_endif);
}

bool isCCOnlyWithIncludes(
const llvm::SmallVectorImpl<clang::format::AnnotatedLine *> &Lines,
unsigned StartIdx) {
int CCLevel = 0;
for (unsigned I = StartIdx; I < Lines.size(); ++I) {
const auto &CurrentLine = *Lines[I];
if (isConditionalCompilationStart(CurrentLine))
CCLevel++;

if (isConditionalCompilationEnd(CurrentLine))
CCLevel--;

if (CCLevel == 0)
break;

if (!(CurrentLine.isInclude() ||
isConditionalCompilationStatement(CurrentLine))) {
return false;
}
}
return true;
}

unsigned getEndOfCCBlock(
const llvm::SmallVectorImpl<clang::format::AnnotatedLine *> &Lines,
unsigned StartIdx) {
int CCLevel = 0;
unsigned I = StartIdx;
for (; I < Lines.size(); ++I) {
const auto &CurrentLine = *Lines[I];
if (isConditionalCompilationStart(CurrentLine))
CCLevel++;

if (isConditionalCompilationEnd(CurrentLine))
CCLevel--;

if (CCLevel == 0)
break;
}
return I;
}
} // namespace

namespace clang {
namespace format {
std::pair<tooling::Replacements, unsigned>
IncludesSeparator::analyze(TokenAnnotator &Annotator,
SmallVectorImpl<AnnotatedLine *> &AnnotatedLines,
FormatTokenLexer &Tokens) {
assert(Style.EmptyLinesAfterIncludes.has_value());
AffectedRangeMgr.computeAffectedLines(AnnotatedLines);
tooling::Replacements Result;
separateIncludes(AnnotatedLines, Result, Tokens);
return {Result, 0};
}

void IncludesSeparator::separateIncludes(
SmallVectorImpl<AnnotatedLine *> &Lines, tooling::Replacements &Result,
FormatTokenLexer &Tokens) {
const unsigned NewlineCount =
std::min(Style.MaxEmptyLinesToKeep, *Style.EmptyLinesAfterIncludes) + 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 InIncludeArea = false;
for (unsigned I = 0; I < Lines.size(); ++I) {
const auto &CurrentLine = *Lines[I];

if (InIncludeArea) {
if (CurrentLine.isInclude())
continue;

if (isConditionalCompilationStart(CurrentLine)) {
const bool CCWithOnlyIncludes = isCCOnlyWithIncludes(Lines, I);
I = getEndOfCCBlock(Lines, I);

// Conditional compilation blocks that only contain
// include statements are considered part of the include area.
if (CCWithOnlyIncludes)
continue;
}

if (!CurrentLine.First->is(tok::eof) && CurrentLine.Affected) {
Whitespaces.replaceWhitespace(*CurrentLine.First, NewlineCount,
CurrentLine.First->OriginalColumn,
CurrentLine.First->OriginalColumn);
}
InIncludeArea = false;
} else {
if (CurrentLine.isInclude())
InIncludeArea = true;
}
}

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
42 changes: 42 additions & 0 deletions clang/lib/Format/IncludesSeparator.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
//===--- IncludesSeparator.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 IncludesSeparator, a TokenAnalyzer that inserts
/// new lines or removes empty lines after an includes area.
/// An includes area is a list of consecutive include statements.
///
//===----------------------------------------------------------------------===//

#ifndef LLVM_CLANG_LIB_FORMAT_INCLUDESSEPARATOR_H
#define LLVM_CLANG_LIB_FORMAT_INCLUDESSEPARATOR_H

#include "TokenAnalyzer.h"
#include "WhitespaceManager.h"

namespace clang {
namespace format {
class IncludesSeparator : public TokenAnalyzer {
public:
IncludesSeparator(const Environment &Env, const FormatStyle &Style)
: TokenAnalyzer(Env, Style) {}

std::pair<tooling::Replacements, unsigned>
analyze(TokenAnnotator &Annotator,
SmallVectorImpl<AnnotatedLine *> &AnnotatedLines,
FormatTokenLexer &Tokens) override;

private:
void separateIncludes(SmallVectorImpl<AnnotatedLine *> &Lines,
tooling::Replacements &Result,
FormatTokenLexer &Tokens);
};
} // namespace format
} // namespace clang

#endif
8 changes: 8 additions & 0 deletions clang/lib/Format/TokenAnnotator.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 <typename... Ts> bool startsWith(Ts... Tokens) const {
Expand Down
2 changes: 2 additions & 0 deletions clang/unittests/Format/ConfigParseTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1005,6 +1005,8 @@ TEST(ConfigParseTest, ParsesConfiguration) {
FormatStyle::SDS_Leave);
CHECK_PARSE("SeparateDefinitionBlocks: Never", SeparateDefinitionBlocks,
FormatStyle::SDS_Never);

CHECK_PARSE("EmptyLinesAfterIncludes: 2", EmptyLinesAfterIncludes, 2);
}

TEST(ConfigParseTest, ParsesConfigurationWithLanguages) {
Expand Down
Loading