Index: include/clang/Format/Format.h =================================================================== --- include/clang/Format/Format.h +++ include/clang/Format/Format.h @@ -760,6 +760,29 @@ /// \endcode bool BreakBeforeInheritanceComma; + /// \brief If ``true``, consecutive namespace declarations will be on the same + /// line. If ``false``, each namespace is declared on a new line. + /// \code + /// true: + /// namespace Foo { namespace Bar { + /// }} + /// + /// false: + /// namespace Foo { + /// namespace Bar { + /// } + /// } + /// \endcode + /// + /// If it does not fit on a single line, the overflowing namespaces get + /// wrapped: + /// \code + /// namespace Foo { namespace Bar { + /// namespace Extra { + /// }}} + /// \endcode + bool CompactNamespaces; + /// \brief If the constructor initializers don't fit on a line, put each /// initializer on its own line. /// \code @@ -1390,6 +1413,7 @@ BreakBeforeBinaryOperators == R.BreakBeforeBinaryOperators && BreakBeforeBraces == R.BreakBeforeBraces && BreakBeforeTernaryOperators == R.BreakBeforeTernaryOperators && + CompactNamespaces == R.CompactNamespaces && BreakConstructorInitializersBeforeComma == R.BreakConstructorInitializersBeforeComma && BreakAfterJavaFieldAnnotations == R.BreakAfterJavaFieldAnnotations && Index: lib/Format/Format.cpp =================================================================== --- lib/Format/Format.cpp +++ lib/Format/Format.cpp @@ -299,20 +299,21 @@ IO.mapOptional("BinPackArguments", Style.BinPackArguments); IO.mapOptional("BinPackParameters", Style.BinPackParameters); IO.mapOptional("BraceWrapping", Style.BraceWrapping); + IO.mapOptional("BreakAfterJavaFieldAnnotations", + Style.BreakAfterJavaFieldAnnotations); IO.mapOptional("BreakBeforeBinaryOperators", Style.BreakBeforeBinaryOperators); IO.mapOptional("BreakBeforeBraces", Style.BreakBeforeBraces); + IO.mapOptional("BreakBeforeInheritanceComma", + Style.BreakBeforeInheritanceComma); IO.mapOptional("BreakBeforeTernaryOperators", Style.BreakBeforeTernaryOperators); IO.mapOptional("BreakConstructorInitializersBeforeComma", Style.BreakConstructorInitializersBeforeComma); - IO.mapOptional("BreakAfterJavaFieldAnnotations", - Style.BreakAfterJavaFieldAnnotations); IO.mapOptional("BreakStringLiterals", Style.BreakStringLiterals); IO.mapOptional("ColumnLimit", Style.ColumnLimit); IO.mapOptional("CommentPragmas", Style.CommentPragmas); - IO.mapOptional("BreakBeforeInheritanceComma", - Style.BreakBeforeInheritanceComma); + IO.mapOptional("CompactNamespaces", Style.CompactNamespaces); IO.mapOptional("ConstructorInitializerAllOnOneLineOrOnePerLine", Style.ConstructorInitializerAllOnOneLineOrOnePerLine); IO.mapOptional("ConstructorInitializerIndentWidth", @@ -529,8 +530,8 @@ LLVMStyle.AlwaysBreakAfterDefinitionReturnType = FormatStyle::DRTBS_None; LLVMStyle.AlwaysBreakBeforeMultilineStrings = false; LLVMStyle.AlwaysBreakTemplateDeclarations = false; - LLVMStyle.BinPackParameters = true; LLVMStyle.BinPackArguments = true; + LLVMStyle.BinPackParameters = true; LLVMStyle.BreakBeforeBinaryOperators = FormatStyle::BOS_None; LLVMStyle.BreakBeforeTernaryOperators = true; LLVMStyle.BreakBeforeBraces = FormatStyle::BS_Attach; @@ -542,6 +543,7 @@ LLVMStyle.BreakStringLiterals = true; LLVMStyle.ColumnLimit = 80; LLVMStyle.CommentPragmas = "^ IWYU pragma:"; + LLVMStyle.CompactNamespaces = false; LLVMStyle.ConstructorInitializerAllOnOneLineOrOnePerLine = false; LLVMStyle.ConstructorInitializerIndentWidth = 4; LLVMStyle.ContinuationIndentWidth = 4; Index: lib/Format/NamespaceEndCommentsFixer.cpp =================================================================== --- lib/Format/NamespaceEndCommentsFixer.cpp +++ lib/Format/NamespaceEndCommentsFixer.cpp @@ -107,6 +107,24 @@ << llvm::toString(std::move(Err)) << "\n"; } } + +const FormatToken * +getNamespaceToken(const AnnotatedLine *line, + const SmallVectorImpl &AnnotatedLines) { + if (!line->Affected || line->InPPDirective || !line->startsWith(tok::r_brace)) + return nullptr; + size_t StartLineIndex = line->MatchingOpeningBlockLineIndex; + if (StartLineIndex == UnwrappedLine::kInvalidIndex) + return nullptr; + assert(StartLineIndex < AnnotatedLines.size()); + const FormatToken *NamespaceTok = AnnotatedLines[StartLineIndex]->First; + // Detect "(inline)? namespace" in the beginning of a line. + if (NamespaceTok->is(tok::kw_inline)) + NamespaceTok = NamespaceTok->getNextNonComment(); + if (!NamespaceTok || NamespaceTok->isNot(tok::kw_namespace)) + return nullptr; + return NamespaceTok; +} } // namespace NamespaceEndCommentsFixer::NamespaceEndCommentsFixer(const Environment &Env, @@ -120,20 +138,14 @@ AffectedRangeMgr.computeAffectedLines(AnnotatedLines.begin(), AnnotatedLines.end()); tooling::Replacements Fixes; + std::string AllNamespaceNames = ""; + size_t StartLineIndex = SIZE_MAX; + unsigned int CompactedNamespacesCount = 0; for (size_t I = 0, E = AnnotatedLines.size(); I != E; ++I) { - if (!AnnotatedLines[I]->Affected || AnnotatedLines[I]->InPPDirective || - !AnnotatedLines[I]->startsWith(tok::r_brace)) - continue; const AnnotatedLine *EndLine = AnnotatedLines[I]; - size_t StartLineIndex = EndLine->MatchingOpeningBlockLineIndex; - if (StartLineIndex == UnwrappedLine::kInvalidIndex) - continue; - assert(StartLineIndex < E); - const FormatToken *NamespaceTok = AnnotatedLines[StartLineIndex]->First; - // Detect "(inline)? namespace" in the beginning of a line. - if (NamespaceTok->is(tok::kw_inline)) - NamespaceTok = NamespaceTok->getNextNonComment(); - if (!NamespaceTok || NamespaceTok->isNot(tok::kw_namespace)) + const FormatToken *NamespaceTok = + getNamespaceToken(EndLine, AnnotatedLines); + if (!NamespaceTok) continue; FormatToken *RBraceTok = EndLine->First; if (RBraceTok->Finalized) @@ -145,6 +157,27 @@ if (RBraceTok->Next && RBraceTok->Next->is(tok::semi)) { EndCommentPrevTok = RBraceTok->Next; } + if (StartLineIndex == SIZE_MAX) + StartLineIndex = EndLine->MatchingOpeningBlockLineIndex; + std::string NamespaceName = computeName(NamespaceTok); + if (Style.CompactNamespaces) { + if ((I + 1 < E) && + getNamespaceToken(AnnotatedLines[I + 1], AnnotatedLines) && + StartLineIndex - CompactedNamespacesCount - 1 == + AnnotatedLines[I + 1]->MatchingOpeningBlockLineIndex && + !AnnotatedLines[I + 1]->First->Finalized) { + if (hasEndComment(EndCommentPrevTok)) { + // remove end comment, it will be merged in next one + updateEndComment(EndCommentPrevTok, std::string(), SourceMgr, &Fixes); + } + CompactedNamespacesCount++; + AllNamespaceNames = "::" + NamespaceName + AllNamespaceNames; + continue; + } + NamespaceName += std::move(AllNamespaceNames); + CompactedNamespacesCount = 0; + AllNamespaceNames = std::string(); + } // The next token in the token stream after the place where the end comment // token must be. This is either the next token on the current line or the // first token on the next line. @@ -156,17 +189,16 @@ bool AddNewline = EndCommentNextTok && EndCommentNextTok->NewlinesBefore == 0 && EndCommentNextTok->isNot(tok::eof); - const std::string NamespaceName = computeName(NamespaceTok); const std::string EndCommentText = computeEndCommentText(NamespaceName, AddNewline); if (!hasEndComment(EndCommentPrevTok)) { bool isShort = I - StartLineIndex <= kShortNamespaceMaxLines + 1; if (!isShort) addEndComment(EndCommentPrevTok, EndCommentText, SourceMgr, &Fixes); - continue; - } - if (!validEndComment(EndCommentPrevTok, NamespaceName)) + } else if (!validEndComment(EndCommentPrevTok, NamespaceName)) { updateEndComment(EndCommentPrevTok, EndCommentText, SourceMgr, &Fixes); + } + StartLineIndex = SIZE_MAX; } return Fixes; } Index: lib/Format/UnwrappedLineFormatter.cpp =================================================================== --- lib/Format/UnwrappedLineFormatter.cpp +++ lib/Format/UnwrappedLineFormatter.cpp @@ -66,6 +66,13 @@ Indent += Offset; } + /// \brief Update the indent state given that \p Line indent should be + /// skipped. + void skipLine(const AnnotatedLine &Line) { + while (IndentForLevel.size() <= Line.Level) + IndentForLevel.push_back(Indent); + } + /// \brief Update the level indent to adapt to the given \p Line. /// /// When a line is not formatted, we move the subsequent lines on the same @@ -127,12 +134,31 @@ unsigned Indent = 0; }; +bool isNamespaceDeclaration(const AnnotatedLine *Line) { + const FormatToken *NamespaceTok = Line->First; + // Detect "(inline)? namespace" in the beginning of a line. + if (NamespaceTok->is(tok::kw_inline)) + NamespaceTok = NamespaceTok->getNextNonComment(); + return NamespaceTok && NamespaceTok->is(tok::kw_namespace); +} + +bool isEndOfNamespace(const AnnotatedLine *Line, + const SmallVectorImpl &AnnotatedLines) { + if (!Line->startsWith(tok::r_brace)) + return false; + size_t StartLineIndex = Line->MatchingOpeningBlockLineIndex; + if (StartLineIndex == UnwrappedLine::kInvalidIndex) + return false; + assert(StartLineIndex < AnnotatedLines.size()); + return isNamespaceDeclaration(AnnotatedLines[StartLineIndex]); +} + class LineJoiner { public: LineJoiner(const FormatStyle &Style, const AdditionalKeywords &Keywords, const SmallVectorImpl &Lines) - : Style(Style), Keywords(Keywords), End(Lines.end()), - Next(Lines.begin()) {} + : Style(Style), Keywords(Keywords), End(Lines.end()), Next(Lines.begin()), + AnnotatedLines(Lines) {} /// \brief Returns the next line, merging multiple lines into one if possible. const AnnotatedLine *getNextMergedLine(bool DryRun, @@ -142,7 +168,7 @@ const AnnotatedLine *Current = *Next; IndentTracker.nextLine(*Current); unsigned MergedLines = - tryFitMultipleLinesInOne(IndentTracker.getIndent(), Next, End); + tryFitMultipleLinesInOne(IndentTracker, Next, End); if (MergedLines > 0 && Style.ColumnLimit == 0) // Disallow line merging if there is a break at the start of one of the // input lines. @@ -159,9 +185,11 @@ private: /// \brief Calculates how many lines can be merged into 1 starting at \p I. unsigned - tryFitMultipleLinesInOne(unsigned Indent, + tryFitMultipleLinesInOne(LevelIndentTracker &IndentTracker, SmallVectorImpl::const_iterator I, SmallVectorImpl::const_iterator E) { + const unsigned Indent = IndentTracker.getIndent(); + // Can't join the last line with anything. if (I + 1 == E) return 0; @@ -195,6 +223,38 @@ (Style.AllowShortFunctionsOnASingleLine == FormatStyle::SFS_Inline && TheLine->Level != 0); + if (Style.CompactNamespaces) { + if (isNamespaceDeclaration(TheLine)) { + int i = 0; + unsigned closingLine = TheLine->MatchingOpeningBlockLineIndex - 1; + for (; I + 1 + i != E && isNamespaceDeclaration(I[i + 1]) && + closingLine == I[i + 1]->MatchingOpeningBlockLineIndex && + I[i + 1]->Last->TotalLength < Limit; + i++, closingLine--) { + // No extra indent for compacted namespaces + IndentTracker.skipLine(*I[i + 1]); + + Limit -= I[i + 1]->Last->TotalLength; + } + return i; + } + + if (isEndOfNamespace(TheLine, AnnotatedLines)) { + int i = 0; + unsigned openingLine = TheLine->MatchingOpeningBlockLineIndex - 1; + for (; I + 1 + i != E && isEndOfNamespace(I[i + 1], AnnotatedLines) && + openingLine == I[i + 1]->MatchingOpeningBlockLineIndex; + i++, openingLine--) { + // No space between consecutive braces + I[i + 1]->First->SpacesRequiredBefore = !I[i]->Last->is(tok::r_brace); + + // Indent like the outer-most namespace + IndentTracker.nextLine(*I[i + 1]); + } + return i; + } + } + if (TheLine->Last->is(TT_FunctionLBrace) && TheLine->First != TheLine->Last) { return MergeShortFunctions ? tryMergeSimpleBlock(I, E, Limit) : 0; @@ -449,6 +509,7 @@ const SmallVectorImpl::const_iterator End; SmallVectorImpl::const_iterator Next; + const SmallVectorImpl &AnnotatedLines; }; static void markFinalized(FormatToken *Tok) { Index: lib/Format/UnwrappedLineParser.cpp =================================================================== --- lib/Format/UnwrappedLineParser.cpp +++ lib/Format/UnwrappedLineParser.cpp @@ -470,6 +470,11 @@ nextToken(); Line->Level = InitialLevel; Line->MatchingOpeningBlockLineIndex = OpeningLineIndex; + if (OpeningLineIndex != UnwrappedLine::kInvalidIndex) { + // Update the opening line to add the forward reference as well + (*CurrentLines)[OpeningLineIndex].MatchingOpeningBlockLineIndex = + CurrentLines->size() - 1; + } } static bool isGoogScope(const UnwrappedLine &Line) { Index: unittests/Format/FormatTest.cpp =================================================================== --- unittests/Format/FormatTest.cpp +++ unittests/Format/FormatTest.cpp @@ -1309,6 +1309,141 @@ Style)); } +TEST_F(FormatTest, FormatsCompactNamespaces) { + FormatStyle Style = getLLVMStyle(); + Style.CompactNamespaces = true; + + verifyFormat("namespace A { namespace B {\n" + "}} // namespace A::B", + Style); + + EXPECT_EQ("namespace out { namespace in {\n" + "}} // namespace out::in", + format("namespace out {\n" + "namespace in {\n" + "} // namespace in\n" + "} // namespace out", + Style)); + + // Only namespaces which have both consecutive opening and end get compacted + EXPECT_EQ("namespace out {\n" + "namespace in1 {\n" + "} // namespace in1\n" + "namespace in2 {\n" + "} // namespace in2\n" + "} // namespace out", + format("namespace out {\n" + "namespace in1 {\n" + "} // namespace in1\n" + "namespace in2 {\n" + "} // namespace in2\n" + "} // namespace out", + Style)); + + EXPECT_EQ("namespace out {\n" + "int i;\n" + "namespace in {\n" + "int j;\n" + "} // namespace in\n" + "int k;\n" + "} // namespace out", + format("namespace out { int i;\n" + "namespace in { int j; } // namespace in\n" + "int k; } // namespace out", + Style)); + + EXPECT_EQ("namespace A { namespace B { namespace C {\n" + "}}} // namespace A::B::C\n", + format("namespace A { namespace B {\n" + "namespace C {\n" + "}} // namespace B::C\n" + "} // namespace A\n", + Style)); + + Style.ColumnLimit = 40; + EXPECT_EQ("namespace aaaaaaaaaa {\n" + "namespace bbbbbbbbbb {\n" + "}} // namespace aaaaaaaaaa::bbbbbbbbbb", + format("namespace aaaaaaaaaa {\n" + "namespace bbbbbbbbbb {\n" + "} // namespace bbbbbbbbbb\n" + "} // namespace aaaaaaaaaa", + Style)); + + EXPECT_EQ("namespace aaaaaa { namespace bbbbbb {\n" + "namespace cccccc {\n" + "}}} // namespace aaaaaa::bbbbbb::cccccc", + format("namespace aaaaaa {\n" + "namespace bbbbbb {\n" + "namespace cccccc {\n" + "} // namespace cccccc\n" + "} // namespace bbbbbb\n" + "} // namespace aaaaaa", + Style)); + Style.ColumnLimit = 80; + + // Extra semicolon after 'inner' closing brace prevents merging + EXPECT_EQ("namespace out { namespace in {\n" + "}; } // namespace out::in", + format("namespace out {\n" + "namespace in {\n" + "}; // namespace in\n" + "} // namespace out", + Style)); + + // Extra semicolon after 'outer' closing brace is conserved + EXPECT_EQ("namespace out { namespace in {\n" + "}}; // namespace out::in", + format("namespace out {\n" + "namespace in {\n" + "} // namespace in\n" + "}; // namespace out", + Style)); + + Style.NamespaceIndentation = FormatStyle::NI_All; + EXPECT_EQ("namespace out { namespace in {\n" + " int i;\n" + "}} // namespace out::in", + format("namespace out {\n" + "namespace in {\n" + "int i;\n" + "} // namespace in\n" + "} // namespace out", + Style)); + EXPECT_EQ("namespace out { namespace mid {\n" + " namespace in {\n" + " int j;\n" + " } // namespace in\n" + " int k;\n" + "}} // namespace out::mid", + format("namespace out { namespace mid {\n" + "namespace in { int j; } // namespace in\n" + "int k; }} // namespace out::mid", + Style)); + + Style.NamespaceIndentation = FormatStyle::NI_Inner; + EXPECT_EQ("namespace out { namespace in {\n" + " int i;\n" + "}} // namespace out::in", + format("namespace out {\n" + "namespace in {\n" + "int i;\n" + "} // namespace in\n" + "} // namespace out", + Style)); + EXPECT_EQ("namespace out { namespace mid { namespace in {\n" + " int i;\n" + "}}} // namespace out::mid::in", + format("namespace out {\n" + "namespace mid {\n" + "namespace in {\n" + "int i;\n" + "} // namespace in\n" + "} // namespace mid\n" + "} // namespace out", + Style)); +} + TEST_F(FormatTest, FormatsExternC) { verifyFormat("extern \"C\" {\nint a;"); } TEST_F(FormatTest, FormatsInlineASM) { @@ -8768,6 +8903,7 @@ CHECK_PARSE_BOOL(BreakConstructorInitializersBeforeComma); CHECK_PARSE_BOOL(BreakStringLiterals); CHECK_PARSE_BOOL(BreakBeforeInheritanceComma) + CHECK_PARSE_BOOL(CompactNamespaces); CHECK_PARSE_BOOL(ConstructorInitializerAllOnOneLineOrOnePerLine); CHECK_PARSE_BOOL(DerivePointerAlignment); CHECK_PARSE_BOOL_FIELD(DerivePointerAlignment, "DerivePointerBinding"); Index: unittests/Format/NamespaceEndCommentsFixerTest.cpp =================================================================== --- unittests/Format/NamespaceEndCommentsFixerTest.cpp +++ unittests/Format/NamespaceEndCommentsFixerTest.cpp @@ -185,6 +185,41 @@ "}\n" "}")); + // Add comment for namespaces which will be 'compacted' + FormatStyle CompactNamespacesStyle = getLLVMStyle(); + CompactNamespacesStyle.CompactNamespaces = true; + EXPECT_EQ("namespace out { namespace in {\n" + "int i;\n" + "int j;\n" + "}}// namespace out::in", + fixNamespaceEndComments("namespace out { namespace in {\n" + "int i;\n" + "int j;\n" + "}}", + CompactNamespacesStyle)); + EXPECT_EQ("namespace out {\n" + "namespace in {\n" + "int i;\n" + "int j;\n" + "}\n" + "}// namespace out::in", + fixNamespaceEndComments("namespace out {\n" + "namespace in {\n" + "int i;\n" + "int j;\n" + "}\n" + "}", + CompactNamespacesStyle)); + EXPECT_EQ("namespace out { namespace in {\n" + "int i;\n" + "int j;\n" + "};}// namespace out::in", + fixNamespaceEndComments("namespace out { namespace in {\n" + "int i;\n" + "int j;\n" + "};}", + CompactNamespacesStyle)); + // Adds an end comment after a semicolon. EXPECT_EQ("namespace {\n" " int i;\n" @@ -388,6 +423,27 @@ fixNamespaceEndComments("namespace A {} // namespace")); EXPECT_EQ("namespace A {}; // namespace A", fixNamespaceEndComments("namespace A {}; // namespace")); + + // Update invalid comments for compacted namespaces. + FormatStyle CompactNamespacesStyle = getLLVMStyle(); + CompactNamespacesStyle.CompactNamespaces = true; + EXPECT_EQ("namespace out { namespace in {\n" + "}} // namespace out::in", + fixNamespaceEndComments("namespace out { namespace in {\n" + "}} // namespace out", + CompactNamespacesStyle)); + EXPECT_EQ("namespace out { namespace in {\n" + "}} // namespace out::in", + fixNamespaceEndComments("namespace out { namespace in {\n" + "}} // namespace in", + CompactNamespacesStyle)); + EXPECT_EQ("namespace out { namespace in {\n" + "}\n" + "} // namespace out::in", + fixNamespaceEndComments("namespace out { namespace in {\n" + "}// banamespace in\n" + "} // namespace out", + CompactNamespacesStyle)); } TEST_F(NamespaceEndCommentsFixerTest, UpdatesInvalidEndBlockComment) {