Index: include/clang/Format/Format.h =================================================================== --- include/clang/Format/Format.h +++ include/clang/Format/Format.h @@ -144,6 +144,14 @@ /// \endcode bool AllowAllParametersOfDeclarationOnNextLine; + /// \brief If ``true``, clang-format removes semicolon at the end of namespace. + /// \code + /// true: false: + /// namespace a { vs. namespace a { + /// } }; + /// \endcode + bool AllowSemicolonAfterNamespace; + /// \brief Allows contracting simple braced statements to a single line. /// /// E.g., this allows ``if (a) { return; }`` to be put on a single line. @@ -334,6 +342,21 @@ /// \endcode bool BinPackArguments; + /// \brief If ``true``, consecutive namespace declarations will 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 + bool BinPackNamespaces; + /// \brief If ``false``, a function declaration's or function definition's /// parameters will either all be on the same line or will have one line each. /// \code @@ -1352,6 +1375,7 @@ AlignTrailingComments == R.AlignTrailingComments && AllowAllParametersOfDeclarationOnNextLine == R.AllowAllParametersOfDeclarationOnNextLine && + AllowSemicolonAfterNamespace == R.AllowSemicolonAfterNamespace && AllowShortBlocksOnASingleLine == R.AllowShortBlocksOnASingleLine && AllowShortCaseLabelsOnASingleLine == R.AllowShortCaseLabelsOnASingleLine && @@ -1366,6 +1390,7 @@ AlwaysBreakTemplateDeclarations == R.AlwaysBreakTemplateDeclarations && BinPackArguments == R.BinPackArguments && + BinPackNamespaces == R.BinPackNamespaces && BinPackParameters == R.BinPackParameters && BreakBeforeBinaryOperators == R.BreakBeforeBinaryOperators && BreakBeforeBraces == R.BreakBeforeBraces && Index: lib/Format/Format.cpp =================================================================== --- lib/Format/Format.cpp +++ lib/Format/Format.cpp @@ -252,6 +252,7 @@ IO.mapOptional("AlignTrailingComments", Style.AlignTrailingComments); IO.mapOptional("AllowAllParametersOfDeclarationOnNextLine", Style.AllowAllParametersOfDeclarationOnNextLine); + IO.mapOptional("AllowSemicolonAfterNamespace", Style.AllowSemicolonAfterNamespace); IO.mapOptional("AllowShortBlocksOnASingleLine", Style.AllowShortBlocksOnASingleLine); IO.mapOptional("AllowShortCaseLabelsOnASingleLine", @@ -284,6 +285,7 @@ IO.mapOptional("AlwaysBreakTemplateDeclarations", Style.AlwaysBreakTemplateDeclarations); IO.mapOptional("BinPackArguments", Style.BinPackArguments); + IO.mapOptional("BinPackNamespaces", Style.BinPackNamespaces); IO.mapOptional("BinPackParameters", Style.BinPackParameters); IO.mapOptional("BraceWrapping", Style.BraceWrapping); IO.mapOptional("BreakBeforeBinaryOperators", @@ -505,6 +507,7 @@ LLVMStyle.AlignConsecutiveAssignments = false; LLVMStyle.AlignConsecutiveDeclarations = false; LLVMStyle.AllowAllParametersOfDeclarationOnNextLine = true; + LLVMStyle.AllowSemicolonAfterNamespace = true; LLVMStyle.AllowShortFunctionsOnASingleLine = FormatStyle::SFS_All; LLVMStyle.AllowShortBlocksOnASingleLine = false; LLVMStyle.AllowShortCaseLabelsOnASingleLine = false; @@ -514,8 +517,9 @@ LLVMStyle.AlwaysBreakAfterDefinitionReturnType = FormatStyle::DRTBS_None; LLVMStyle.AlwaysBreakBeforeMultilineStrings = false; LLVMStyle.AlwaysBreakTemplateDeclarations = false; - LLVMStyle.BinPackParameters = true; LLVMStyle.BinPackArguments = true; + LLVMStyle.BinPackNamespaces = false; + LLVMStyle.BinPackParameters = true; LLVMStyle.BreakBeforeBinaryOperators = FormatStyle::BOS_None; LLVMStyle.BreakBeforeTernaryOperators = true; LLVMStyle.BreakBeforeBraces = FormatStyle::BS_Attach; Index: lib/Format/NamespaceEndCommentsFixer.cpp =================================================================== --- lib/Format/NamespaceEndCommentsFixer.cpp +++ lib/Format/NamespaceEndCommentsFixer.cpp @@ -107,6 +107,36 @@ << llvm::toString(std::move(Err)) << "\n"; } } + +void removeTrailingSemicolon(const FormatToken *SemiColonTok, + const SourceManager &SourceMgr, + tooling::Replacements *Fixes) { + assert(SemiColonTok->is(tok::semi)); + auto Range = CharSourceRange::getCharRange(SemiColonTok->Tok.getLocation(), + SemiColonTok->Tok.getEndLoc()); + auto Err = Fixes->add(tooling::Replacement(SourceMgr, Range, StringRef())); + if (Err) { + llvm::errs() << "Error while removing trailing semicolon at end of namespace: " + << 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 NULL; + size_t StartLineIndex = line->MatchingOpeningBlockLineIndex; + if (StartLineIndex == UnwrappedLine::kInvalidIndex) + return NULL; + 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 NULL; + return NamespaceTok; +} } // namespace NamespaceEndCommentsFixer::NamespaceEndCommentsFixer(const Environment &Env, @@ -120,20 +150,12 @@ AffectedRangeMgr.computeAffectedLines(AnnotatedLines.begin(), AnnotatedLines.end()); tooling::Replacements Fixes; + std::string AllNamespaceNames = ""; + size_t StartLineIndex = SIZE_MAX; 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) @@ -144,7 +166,25 @@ // comments to the semicolon tokens. if (RBraceTok->Next && RBraceTok->Next->is(tok::semi)) { EndCommentPrevTok = RBraceTok->Next; + if (!Style.AllowSemicolonAfterNamespace) + removeTrailingSemicolon(RBraceTok->Next, SourceMgr, &Fixes); + } + if (StartLineIndex == SIZE_MAX) + StartLineIndex = EndLine->MatchingOpeningBlockLineIndex; + std::string NamespaceName = computeName(NamespaceTok); + bool CombineWithNext = I + 1 < E && + Style.BinPackNamespaces && + getNamespaceToken(AnnotatedLines[I + 1], AnnotatedLines) && + !AnnotatedLines[I + 1]->First->Finalized; + if (CombineWithNext) { + if (hasEndComment(EndCommentPrevTok) && validEndComment(EndCommentPrevTok, NamespaceName)) + // remove end comment, it will be merged in next one + updateEndComment(EndCommentPrevTok, std::string(), SourceMgr, &Fixes); + AllNamespaceNames = "::" + NamespaceName + AllNamespaceNames; + continue; } + NamespaceName += std::move(AllNamespaceNames); + 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 +196,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 @@ -127,12 +127,33 @@ unsigned Indent = 0; }; +bool isNamespaceToken(const FormatToken *NamespaceTok) { + // 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 false; + return true; +} + +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()); + const FormatToken *NamespaceTok = AnnotatedLines[StartLineIndex]->First; + return isNamespaceToken(NamespaceTok); +} + class LineJoiner { public: LineJoiner(const FormatStyle &Style, const AdditionalKeywords &Keywords, const SmallVectorImpl &Lines) : Style(Style), Keywords(Keywords), End(Lines.end()), - Next(Lines.begin()) {} + Next(Lines.begin()), AnnotatedLines(Lines) {} /// \brief Returns the next line, merging multiple lines into one if possible. const AnnotatedLine *getNextMergedLine(bool DryRun, @@ -195,6 +216,28 @@ (Style.AllowShortFunctionsOnASingleLine == FormatStyle::SFS_Inline && TheLine->Level != 0); + if (Style.BinPackNamespaces) { + if (isNamespaceToken(TheLine->First)) { + int i = 0; + while (I + 1 + i != E && isNamespaceToken(I[i + 1]->First) && + I[i + 1]->Last->TotalLength < Limit) { + Limit -= I[i + 1]->Last->TotalLength; + i++; + } + return i; + } + + if (isEndOfNamespace(TheLine, AnnotatedLines)) { + int i = 0; + while (I + 1 + i != E && isEndOfNamespace(I[i + 1], AnnotatedLines)) { + // No space between consecutive braces + I[i + 1]->First->SpacesRequiredBefore = !I[i]->Last->is(tok::r_brace); + i++; + } + return i; + } + } + if (TheLine->Last->is(TT_FunctionLBrace) && TheLine->First != TheLine->Last) { return MergeShortFunctions ? tryMergeSimpleBlock(I, E, Limit) : 0; @@ -449,6 +492,7 @@ const SmallVectorImpl::const_iterator End; SmallVectorImpl::const_iterator Next; + const SmallVectorImpl &AnnotatedLines; }; static void markFinalized(FormatToken *Tok) { Index: unittests/Format/FormatTest.cpp =================================================================== --- unittests/Format/FormatTest.cpp +++ unittests/Format/FormatTest.cpp @@ -1306,6 +1306,41 @@ "} // namespace in\n" "} // namespace out", Style)); + + FormatStyle LLVMWithBinPackNamespaces = getLLVMStyle(); + LLVMWithBinPackNamespaces.BinPackNamespaces = true; + + EXPECT_EQ("namespace out { namespace in {\n" + "}} // namespace out::in", + format("namespace out {\n" + "namespace in {\n" + "} // namespace in\n" + "} // namespace out", + LLVMWithBinPackNamespaces)); + + EXPECT_EQ("namespace out { namespace in1 {\n" + "} // namespace in1\n" + "namespace in2 {\n" + "}} // namespace out::in2", + format("namespace out {\n" + "namespace in1 {\n" + "} // namespace in1\n" + "namespace in2 {\n" + "} // namespace in2\n" + "} // namespace out", + LLVMWithBinPackNamespaces)); + + 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", + LLVMWithBinPackNamespaces)); } TEST_F(FormatTest, FormatsExternC) { verifyFormat("extern \"C\" {\nint a;"); } @@ -8670,12 +8705,14 @@ CHECK_PARSE_BOOL(AlignConsecutiveAssignments); CHECK_PARSE_BOOL(AlignConsecutiveDeclarations); CHECK_PARSE_BOOL(AllowAllParametersOfDeclarationOnNextLine); + CHECK_PARSE_BOOL(AllowSemicolonAfterNamespace); CHECK_PARSE_BOOL(AllowShortBlocksOnASingleLine); CHECK_PARSE_BOOL(AllowShortCaseLabelsOnASingleLine); CHECK_PARSE_BOOL(AllowShortIfStatementsOnASingleLine); CHECK_PARSE_BOOL(AllowShortLoopsOnASingleLine); CHECK_PARSE_BOOL(AlwaysBreakTemplateDeclarations); CHECK_PARSE_BOOL(BinPackArguments); + CHECK_PARSE_BOOL(BinPackNamespaces); CHECK_PARSE_BOOL(BinPackParameters); CHECK_PARSE_BOOL(BreakAfterJavaFieldAnnotations); CHECK_PARSE_BOOL(BreakBeforeTernaryOperators); Index: unittests/Format/NamespaceEndCommentsFixerTest.cpp =================================================================== --- unittests/Format/NamespaceEndCommentsFixerTest.cpp +++ unittests/Format/NamespaceEndCommentsFixerTest.cpp @@ -214,6 +214,51 @@ "// unrelated")); } +TEST_F(NamespaceEndCommentsFixerTest, RemoveSemicolon) { + FormatStyle LLVMStyleWithoutSemicolon = getLLVMStyle(); + LLVMStyleWithoutSemicolon.AllowSemicolonAfterNamespace = false; + + EXPECT_EQ("namespace {\n" + " int i;\n" + " int j;\n" + "}// namespace", + fixNamespaceEndComments("namespace {\n" + " int i;\n" + " int j;\n" + "};", + LLVMStyleWithoutSemicolon)); + EXPECT_EQ("namespace A {\n" + " int i;\n" + " int j;\n" + "}// namespace A", + fixNamespaceEndComments("namespace A {\n" + " int i;\n" + " int j;\n" + "};", + LLVMStyleWithoutSemicolon)); + EXPECT_EQ("namespace A {\n" + " int i;\n" + " int j;\n" + "}// namespace A\n" + "// unrelated", + fixNamespaceEndComments("namespace A {\n" + " int i;\n" + " int j;\n" + "};\n" + "// unrelated", + LLVMStyleWithoutSemicolon)); + // case without semicolon is not affected + EXPECT_EQ("namespace A {\n" + " int i;\n" + " int j;\n" + "}// namespace A", + fixNamespaceEndComments("namespace A {\n" + " int i;\n" + " int j;\n" + "}", + LLVMStyleWithoutSemicolon)); +} + TEST_F(NamespaceEndCommentsFixerTest, AddsNewlineIfNeeded) { EXPECT_EQ("namespace A {\n" " int i;\n"