diff --git a/clang/lib/Format/UnwrappedLineParser.cpp b/clang/lib/Format/UnwrappedLineParser.cpp --- a/clang/lib/Format/UnwrappedLineParser.cpp +++ b/clang/lib/Format/UnwrappedLineParser.cpp @@ -13,6 +13,7 @@ //===----------------------------------------------------------------------===// #include "UnwrappedLineParser.h" +#include "FormatToken.h" #include "llvm/ADT/STLExtras.h" #include "llvm/Support/Debug.h" #include "llvm/Support/raw_ostream.h" @@ -1495,9 +1496,7 @@ if (FormatTok->Previous->isNot(tok::identifier)) return false; - // Try to parse the property accessor braces and contents: - // `{ get; set; } = new MyType(defaultValue);` - // ^^^^^^^^^^^^^ + // See if we are inside a property accessor. // // Record the current tokenPosition so that we can advance and // reset the current token. `Next` is not set yet so we need @@ -1505,7 +1504,11 @@ unsigned int StoredPosition = Tokens->getPosition(); FormatToken *Tok = Tokens->getNextToken(); + // A trivial property accessor is of the form: + // { [ACCESS_SPECIFIER] [get]; [ACCESS_SPECIFIER] [set] } + // Track these as they do not require line breaks to be introduced. bool HasGetOrSet = false; + bool IsTrivialPropertyAccessor = true; while (!eof()) { if (Tok->isOneOf(tok::semi, tok::kw_public, tok::kw_private, tok::kw_protected, Keywords.kw_internal, Keywords.kw_get, @@ -1515,10 +1518,9 @@ Tok = Tokens->getNextToken(); continue; } - if (Tok->is(tok::r_brace)) - break; - Tokens->setPosition(StoredPosition); - return false; + if (Tok->isNot(tok::r_brace)) + IsTrivialPropertyAccessor = false; + break; } if (!HasGetOrSet) { @@ -1526,33 +1528,51 @@ return false; } + // Try to parse the property accessor: + // https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/properties Tokens->setPosition(StoredPosition); - while (FormatTok->isNot(tok::r_brace)) { - nextToken(); - } - - // Try to parse (optional) assignment to default value: - // `{ get; set; } = new MyType(defaultValue);` - // ^^^^^^^^^^^^^^^^^^^^^^^^^^^ - // There may be some very complicated expressions inside default value - // assignment, the simple parse block below will not handle them. - // The parse block below would need extending to handle opening parens etc. - StoredPosition = Tokens->getPosition(); - Tok = Tokens->getNextToken(); - bool NextTokenIsEqual = Tok->is(tok::equal); - Tokens->setPosition(StoredPosition); - - if (NextTokenIsEqual) { - do { + nextToken(); + do { + switch (FormatTok->Tok.getKind()) { + case tok::r_brace: nextToken(); - if (FormatTok->is(tok::semi)) + if (FormatTok->is(tok::equal)) { + while (!eof() && FormatTok->isNot(tok::semi)) + nextToken(); + nextToken(); + } + addUnwrappedLine(); + return true; + case tok::l_brace: + ++Line->Level; + parseBlock(/*MustBeDeclaration=*/true); + addUnwrappedLine(); + --Line->Level; + break; + case tok::equal: + if (FormatTok->is(TT_JsFatArrow)) { + ++Line->Level; + do { + nextToken(); + } while (!eof() && FormatTok->isNot(tok::semi)); + nextToken(); + addUnwrappedLine(); + --Line->Level; break; - } while (!eof()); - } + } + nextToken(); + break; + default: + if (FormatTok->isOneOf(Keywords.kw_get, Keywords.kw_set) && + !IsTrivialPropertyAccessor) { + // Non-trivial get/set needs to be on its own line. + addUnwrappedLine(); + } + nextToken(); + } + } while (!eof()); - // Add an unwrapped line for the whole property accessor. - nextToken(); - addUnwrappedLine(); + // Unreachable for well-formed code (paired '{' and '}'). return true; } diff --git a/clang/unittests/Format/FormatTestCSharp.cpp b/clang/unittests/Format/FormatTestCSharp.cpp --- a/clang/unittests/Format/FormatTestCSharp.cpp +++ b/clang/unittests/Format/FormatTestCSharp.cpp @@ -613,6 +613,64 @@ set => _name = value; })", Style); + + // Examples taken from + // https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/properties + verifyFormat(R"( +// Expression body definitions +public class SaleItem { + public decimal Price { + get => _cost; + set => _cost = value; + } +})", + Style); + + verifyFormat(R"( +// Properties with backing fields +class TimePeriod { + public double Hours { + get { return _seconds / 3600; } + set { + if (value < 0 || value > 24) + throw new ArgumentOutOfRangeException( + $"{nameof(value)} must be between 0 and 24."); + _seconds = value * 3600; + } + } +})", + Style); + + verifyFormat(R"( +// Auto-implemented properties +public class SaleItem { + public decimal Price { get; set; } +})", + Style); + + // Add column limit to wrap long lines. + Style.ColumnLimit = 100; + + // Examples with assignment to default value. + verifyFormat(R"( +// Long assignment to default value +class MyClass { + public override VeryLongNamedTypeIndeed VeryLongNamedValue { get; set } = + VeryLongNamedTypeIndeed.Create(DefaultFirstArgument, DefaultSecondArgument, + DefaultThirdArgument); +})", + Style); + + verifyFormat(R"( +// Long assignment to default value with expression body +class MyClass { + public override VeryLongNamedTypeIndeed VeryLongNamedValue { + get => veryLongNamedField; + set => veryLongNamedField = value; + } = VeryLongNamedTypeIndeed.Create(DefaultFirstArgument, DefaultSecondArgument, + DefaultThirdArgument); +})", + Style); } TEST_F(FormatTestCSharp, CSharpSpaces) {