Index: clang/lib/Format/FormatTokenLexer.h =================================================================== --- clang/lib/Format/FormatTokenLexer.h +++ clang/lib/Format/FormatTokenLexer.h @@ -54,6 +54,7 @@ bool tryMergeCSharpNullConditionals(); bool tryMergeCSharpDoubleQuestion(); bool tryTransformCSharpForEach(); + bool tryMergeCSharpAttributeAndTarget(); bool tryMergeTokens(ArrayRef<tok::TokenKind> Kinds, TokenType NewType); Index: clang/lib/Format/FormatTokenLexer.cpp =================================================================== --- clang/lib/Format/FormatTokenLexer.cpp +++ clang/lib/Format/FormatTokenLexer.cpp @@ -76,6 +76,8 @@ return; if (Style.isCSharp()) { + if (tryMergeCSharpAttributeAndTarget()) + return; if (tryMergeCSharpKeywordVariables()) return; if (tryMergeCSharpStringLiteral()) @@ -275,6 +277,39 @@ return true; } +bool FormatTokenLexer::tryMergeCSharpAttributeAndTarget() { + // Treat '[assembly:' and '[field:' as tokens in their own right. + if (Tokens.size() < 3) + return false; + + auto &SquareBracket = *(Tokens.end() - 3); + auto &Target = *(Tokens.end() - 2); + auto &Colon = *(Tokens.end() - 1); + + if (!SquareBracket->Tok.is(tok::l_square)) + return false; + + // Valid C# attribute targets: + // https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/attributes/#attribute-targets + if (!(Target->TokenText == "assembly" || Target->TokenText == "module" || + Target->TokenText == "field" || Target->TokenText == "event" || + Target->TokenText == "method" || Target->TokenText == "param" || + Target->TokenText == "property" || Target->TokenText == "return" || + Target->TokenText == "type")) + return false; + + if (!Colon->Tok.is(tok::colon)) + return false; + + SquareBracket->TokenText = + StringRef(SquareBracket->TokenText.begin(), + Colon->TokenText.end() - SquareBracket->TokenText.begin()); + SquareBracket->ColumnWidth += (Target->ColumnWidth + Colon->ColumnWidth); + Tokens.erase(Tokens.end() - 2); + Tokens.erase(Tokens.end() - 1); + return true; +} + bool FormatTokenLexer::tryMergeCSharpDoubleQuestion() { if (Tokens.size() < 2) return false; Index: clang/unittests/Format/FormatTestCSharp.cpp =================================================================== --- clang/unittests/Format/FormatTestCSharp.cpp +++ clang/unittests/Format/FormatTestCSharp.cpp @@ -233,6 +233,15 @@ "[DllImport(\"Hello\", EntryPoint = \"hello_world\")]\n" "// The const char* returned by hello_world must not be deleted.\n" "private static extern IntPtr HelloFromCpp();)"); + + // Unwrappable lines go on a line of their own. + // 'target:' is not treated as a label. + // Modify Style to enforce a column limit. + FormatStyle Style = getGoogleStyle(FormatStyle::LK_CSharp); + Style.ColumnLimit = 10; + verifyFormat(R"([assembly:InternalsVisibleTo( + "SomeAssembly, PublicKey=SomePublicKeyThatExceedsTheColumnLimit")])", + Style); } TEST_F(FormatTestCSharp, CSharpUsing) {