diff --git a/Src/CSharpier.Tests/DocPrinterTests.cs b/Src/CSharpier.Tests/DocPrinterTests.cs index 71468f889..229458bb0 100644 --- a/Src/CSharpier.Tests/DocPrinterTests.cs +++ b/Src/CSharpier.Tests/DocPrinterTests.cs @@ -724,6 +724,29 @@ public void Print_Should_Include_Single_NewLine_To_End_File(int instances, strin result.Should().Be($"1{endOfLine}"); } + [Test] + public void ConditionalGroup_Does_Not_Propagate_Breaks() + { + var doc = Doc.Group( + Doc.ConditionalGroup( + Doc.Concat("1", Doc.HardLine, "2"), + Doc.Concat("1", Doc.HardLine, "2") + ), + Doc.SoftLine, + "3" + ); + // this seems odd, but this is how conditional groups work + // I assume partly because if only one of the potential groups contains a hardline + // then you don't want to always break the conditional group + PrintedDocShouldBe( + doc, + """ + 1 + 23 + """ + ); + } + [Test] public void Scratch() { diff --git a/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/RawStringLiterals.test b/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/RawStringLiterals.test index ac6b168d0..2ebe6dc03 100644 --- a/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/RawStringLiterals.test +++ b/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/RawStringLiterals.test @@ -49,3 +49,39 @@ CallMethod( TrailingSpaceHere """ ); + +CallMethod( + """ + SomeRawString + """.CallMethod() +); + +CallMethod( + """ + SomeRawString + """.CallMethod().CallMethod() +); + +CallMethod( + """ + SomeRawString + """.CallMethod().CallMethod().CallMethod() +); + +CallMethod( + $""" + SomeRawString + """.CallMethod() +); + +CallMethod( + $""" + SomeRawString + """.CallMethod().CallMethod() +); + +CallMethod( + $""" + SomeRawString + """.CallMethod().CallMethod().CallMethod() +); diff --git a/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/StringLiterals.test b/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/StringLiterals.test index e4bfc259f..1cbe2b914 100644 --- a/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/StringLiterals.test +++ b/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/StringLiterals.test @@ -165,5 +165,23 @@ four } """ ); + + CallMethod( + @" +SomeVerbatimString +".CallMethod() + ); + + CallMethod( + @" +SomeVerbatimString +".CallMethod().CallMethod() + ); + + CallMethod( + @" +SomeVerbatimString +".CallMethod().CallMethod().CallMethod() + ); } } diff --git a/Src/CSharpier/DocPrinter/PropagateBreaks.cs b/Src/CSharpier/DocPrinter/PropagateBreaks.cs index 92e26ef5f..554f1df8b 100644 --- a/Src/CSharpier/DocPrinter/PropagateBreaks.cs +++ b/Src/CSharpier/DocPrinter/PropagateBreaks.cs @@ -51,12 +51,10 @@ bool OnEnter(Doc doc) { canSkipBreak = true; groupStack.Push(group); - if (alreadyVisitedSet.Contains(group)) + if (!alreadyVisitedSet.Add(group)) { return false; } - - alreadyVisitedSet.Add(group); } else if (doc is StringDoc { IsDirective: false }) { diff --git a/Src/CSharpier/SyntaxNodeExtensions.cs b/Src/CSharpier/SyntaxNodeExtensions.cs new file mode 100644 index 000000000..3cfc2fa97 --- /dev/null +++ b/Src/CSharpier/SyntaxNodeExtensions.cs @@ -0,0 +1,22 @@ +namespace CSharpier; + +internal static class SyntaxNodeExtensions +{ + public static bool HasParent(this SyntaxNode? node, Type theType) + { + while (true) + { + if (node?.Parent?.GetType() == theType) + { + return true; + } + + if (node is null) + { + return false; + } + + node = node.Parent; + } + } +} diff --git a/Src/CSharpier/SyntaxPrinter/SyntaxNodePrinters/InterpolatedStringExpression.cs b/Src/CSharpier/SyntaxPrinter/SyntaxNodePrinters/InterpolatedStringExpression.cs index 37bafffb6..bce5981fb 100644 --- a/Src/CSharpier/SyntaxPrinter/SyntaxNodePrinters/InterpolatedStringExpression.cs +++ b/Src/CSharpier/SyntaxPrinter/SyntaxNodePrinters/InterpolatedStringExpression.cs @@ -91,6 +91,6 @@ private static Doc RawString(InterpolatedStringExpressionSyntax node, Formatting contents.Add(lastLineIsIndented ? Doc.HardLineNoTrim : Doc.LiteralLine); contents.Add(Token.Print(node.StringEndToken, context)); - return Doc.IndentIf(node.Parent is not ArgumentSyntax, Doc.Concat(contents)); + return Doc.IndentIf(!node.HasParent(typeof(ArgumentSyntax)), Doc.Concat(contents)); } } diff --git a/Src/CSharpier/SyntaxPrinter/SyntaxNodePrinters/InvocationExpression.cs b/Src/CSharpier/SyntaxPrinter/SyntaxNodePrinters/InvocationExpression.cs index 1796b8916..1be7e30fb 100644 --- a/Src/CSharpier/SyntaxPrinter/SyntaxNodePrinters/InvocationExpression.cs +++ b/Src/CSharpier/SyntaxPrinter/SyntaxNodePrinters/InvocationExpression.cs @@ -56,7 +56,30 @@ or PostfixUnaryExpressionSyntax ) ); - if (forceOneLine) + if ( + forceOneLine + // this handles the case of a multiline string being part of an invocation chain + // conditional groups don't propagate breaks so we need to avoid the conditional group + || groups[0] + .Any(o => + o.Node + is LiteralExpressionSyntax + { + Token.RawKind: (int)SyntaxKind.MultiLineRawStringLiteralToken + } + or InterpolatedStringExpressionSyntax + { + StringStartToken.RawKind: (int) + SyntaxKind.InterpolatedMultiLineRawStringStartToken + } + || o.Node + is LiteralExpressionSyntax + { + Token.Text.Length: > 0 + } literalExpressionSyntax + && literalExpressionSyntax.Token.Text.Contains('\n') + ) + ) { return Doc.Group(oneLine); } diff --git a/Src/CSharpier/SyntaxPrinter/Token.cs b/Src/CSharpier/SyntaxPrinter/Token.cs index a5c05a70e..3262ecd8f 100644 --- a/Src/CSharpier/SyntaxPrinter/Token.cs +++ b/Src/CSharpier/SyntaxPrinter/Token.cs @@ -97,9 +97,9 @@ is InterpolatedStringExpressionSyntax contents.Add(linesIncludingQuotes[^1].TrimStart()); - docs.Add( - Doc.IndentIf(syntaxToken.Parent?.Parent is not ArgumentSyntax, Doc.Concat(contents)) - ); + var hasArgumentParent = syntaxToken.Parent.HasParent(typeof(ArgumentSyntax)); + + docs.Add(Doc.IndentIf(!hasArgumentParent, Doc.Concat(contents))); } else if ( syntaxToken.RawSyntaxKind()