diff --git a/clang/lib/Format/UnwrappedLineFormatter.cpp b/clang/lib/Format/UnwrappedLineFormatter.cpp --- a/clang/lib/Format/UnwrappedLineFormatter.cpp +++ b/clang/lib/Format/UnwrappedLineFormatter.cpp @@ -292,6 +292,13 @@ } } + // Try to merge a CSharp property declaration like `{ get; private set }`. + if (Style.isCSharp()) { + unsigned CSPA = tryMergeCSharpPropertyAccessor(I, E, Limit); + if (CSPA > 0) + return CSPA; + } + // Try to merge a function block with left brace unwrapped if (TheLine->Last->is(TT_FunctionLBrace) && TheLine->First != TheLine->Last) { @@ -421,6 +428,64 @@ return 0; } + // true for lines of the form [access-modifier] {get,set} [;] + bool isMergeablePropertyAccessor(const AnnotatedLine *Line) { + auto *Tok = Line->First; + if (!Tok) + return false; + + if (Tok->isOneOf(tok::kw_public, tok::kw_protected, tok::kw_private, + Keywords.kw_internal)) + Tok = Tok->Next; + + if (!Tok || (Tok->TokenText != "get" && Tok->TokenText != "set")) + return false; + + if (!Tok->Next || Tok->Next->is(tok::semi)) + return true; + + return false; + } + + unsigned tryMergeCSharpPropertyAccessor( + SmallVectorImpl::const_iterator I, + SmallVectorImpl::const_iterator E, unsigned /*Limit*/) { + + auto CurrentLine = I; + // Does line start with `{` + if (!(*CurrentLine)->Last || (*CurrentLine)->Last->isNot(TT_FunctionLBrace)) + return 0; + ++CurrentLine; + + unsigned MergedLines = 0; + bool HasGetOrSet = false; + while (CurrentLine != E) { + bool LineIsGetOrSet = isMergeablePropertyAccessor(*CurrentLine); + HasGetOrSet = HasGetOrSet || LineIsGetOrSet; + if (LineIsGetOrSet) { + ++CurrentLine; + ++MergedLines; + continue; + } + auto *Tok = (*CurrentLine)->First; + if (Tok && Tok->is(tok::r_brace)) { + ++CurrentLine; + ++MergedLines; + // See if the next line is a default value so that we can merge `{ get; + // set } = 0` + if (CurrentLine != E && (*CurrentLine)->First && + (*CurrentLine)->First->is(tok::equal)) { + ++MergedLines; + } + break; + } + // Not a '}' or a get/set line so do not merege lines. + return 0; + } + + return HasGetOrSet ? MergedLines : 0; + } + unsigned tryMergeSimplePPDirective(SmallVectorImpl::const_iterator I, SmallVectorImpl::const_iterator E, 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 @@ -240,18 +240,12 @@ verifyFormat("[TestMethod]\n" "public string Host\n" - "{\n" - " set;\n" - " get;\n" - "}"); + "{ set; get; }"); verifyFormat("[TestMethod(\"start\", HelpText = \"Starts the server " "listening on provided host\")]\n" "public string Host\n" - "{\n" - " set;\n" - " get;\n" - "}"); + "{ set; get; }"); verifyFormat( "[DllImport(\"Hello\", EntryPoint = \"hello_world\")]\n" @@ -554,5 +548,32 @@ Style); } +TEST_F(FormatTestCSharp, CSharpPropertyAccessors) { + FormatStyle Style = getGoogleStyle(FormatStyle::LK_CSharp); + + verifyFormat("int Value { get }", Style); + verifyFormat("int Value { get; }", Style); + verifyFormat("int Value { internal get; }", Style); + verifyFormat("int Value { get; } = 0", Style); + verifyFormat("int Value { set }", Style); + verifyFormat("int Value { set; }", Style); + verifyFormat("int Value { internal set; }", Style); + verifyFormat("int Value { set; } = 0", Style); + verifyFormat("int Value { get; set }", Style); + verifyFormat("int Value { set; get }", Style); + verifyFormat("int Value { get; private set; }", Style); + verifyFormat("int Value { get; set; }", Style); + verifyFormat("int Value { get; set; } = 0", Style); + verifyFormat("int Value { internal get; internal set; }", Style); + + // Do not wrap expression body definitions. + verifyFormat(R"(// +public string Name { + get => _name; + set => _name = value; +})", + Style); +} + } // namespace format } // end namespace clang