diff --git a/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/InterpolatedStringExpressions.test b/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/InterpolatedStringExpressions.test index 010431fa9..e00c2d846 100644 --- a/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/InterpolatedStringExpressions.test +++ b/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/InterpolatedStringExpressions.test @@ -25,4 +25,36 @@ public class ClassName var s3 = $"x {1:d}"; var trailingComment = $"{someValue /* Comment shouldn't cause new line */}"; } + + void RawString() + { + var multiLineRawInterpolated = $""" + this has the wrong indentation but because the interpolation contains new lines we leave it + {(someValue ? + One : Two) + } + """; + + var multiLineRawInterpolated = $""" + This is a long message. + It has several lines. + Some are indented + more than others. + Some should start at the first column. + Some have "quoted text" in them. + """; + + var multiLineRawInterpolated = $""" + {one} + And {two} And + Just Text + {three} + """; + + var multiLineRawInterpolated = $""" + {one} + And {two} And + Just Text + """; + } } diff --git a/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/RawStringLiterals.test b/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/RawStringLiterals.test index f3a2d3072..ac6b168d0 100644 --- a/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/RawStringLiterals.test +++ b/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/RawStringLiterals.test @@ -25,3 +25,27 @@ var whatAboutWhiteSpace = """ That last line is six """; + +var whatAboutWhiteSpace = $""" + Four Spaces + + That last line is six + """; + +CallMethod( + """ + SomeString + """ +); + +CallMethod( + """ + TrailingSpaceHere + """ +); + +CallMethod( + $""" + TrailingSpaceHere + """ +); diff --git a/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/RawStringLiterals_MovesIndent.expected.test b/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/RawStringLiterals_MovesIndent.expected.test index f6b89a909..18761cce7 100644 --- a/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/RawStringLiterals_MovesIndent.expected.test +++ b/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/RawStringLiterals_MovesIndent.expected.test @@ -1,3 +1,7 @@ var someString = """ Indent based on previous line """; + +var someString = $""" + Indent based on previous line + """; diff --git a/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/RawStringLiterals_MovesIndent.test b/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/RawStringLiterals_MovesIndent.test index 3a3f7ee31..e320aa852 100644 --- a/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/RawStringLiterals_MovesIndent.test +++ b/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/RawStringLiterals_MovesIndent.test @@ -1,3 +1,7 @@ var someString = """ Indent based on previous line """; + +var someString = $""" + Indent based on previous line + """; diff --git a/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/RawStringLiterals_TrimExtra.expected.test b/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/RawStringLiterals_TrimExtra.expected.test index 71d25e199..07e6836bf 100644 --- a/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/RawStringLiterals_TrimExtra.expected.test +++ b/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/RawStringLiterals_TrimExtra.expected.test @@ -3,3 +3,9 @@ var whatAboutWhiteSpace = """ That last line is only two """; + +var whatAboutWhiteSpace = $""" + Four Spaces + + That last line is only two + """; diff --git a/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/RawStringLiterals_TrimExtra.test b/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/RawStringLiterals_TrimExtra.test index a901b3c05..fb92e1f59 100644 --- a/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/RawStringLiterals_TrimExtra.test +++ b/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/RawStringLiterals_TrimExtra.test @@ -3,3 +3,9 @@ var whatAboutWhiteSpace = """ That last line is only two """; + +var whatAboutWhiteSpace = $""" + Four Spaces + + That last line is only two + """; diff --git a/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/StringLiterals.test b/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/StringLiterals.test index 464dcee32..e4bfc259f 100644 --- a/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/StringLiterals.test +++ b/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/StringLiterals.test @@ -99,15 +99,6 @@ four", Some have "quoted text" in them. """; - var multiLineRawInterpolated = $""" - This is a long message. - It has several lines. - Some are indented - more than others. - Some should start at the first column. - Some have "quoted text" in them. - """; - var shortRaw = """Short Raw String"""; var shortRawInterpolated = $"""Short Raw String"""; var longRaw = diff --git a/Src/CSharpier.Tests/SyntaxNodeComparerTests.cs b/Src/CSharpier.Tests/SyntaxNodeComparerTests.cs index 9535f8222..65fe49539 100644 --- a/Src/CSharpier.Tests/SyntaxNodeComparerTests.cs +++ b/Src/CSharpier.Tests/SyntaxNodeComparerTests.cs @@ -497,6 +497,67 @@ public void Usings_With_Directives_Pass_Validation(string content) result.Should().BeEmpty(); } + [Test] + public void RawStringLiterals_Work_With_Moving_Indentation() + { + var left = """ +public class ClassName +{ + public void MethodName() + { + CallMethod( + \"\"\" + SomeString + \"\"\" + ); + } +} +"""; + + var right = """ +public class ClassName +{ + public void MethodName() + { + CallMethod( + \"\"\" + SomeString + \"\"\" + ); + } +} +"""; + + var result = CompareSource(left, right); + + result.Should().BeEmpty(); + } + + [Test] + public void RawStringLiterals_Work_With_Moving_Indentation_2() + { + var left = """" + CallMethod(CallMethod( + """ + SomeString + """, someValue)); + """"; + var right = """" + CallMethod( + CallMethod( + """ + SomeString + """, + someValue + ) + ); + """"; + + var result = CompareSource(left, right); + + result.Should().BeEmpty(); + } + private static void ResultShouldBe(string result, string be) { if (Environment.GetEnvironmentVariable("NormalizeLineEndings") != null) diff --git a/Src/CSharpier/SyntaxNodeComparer.cs b/Src/CSharpier/SyntaxNodeComparer.cs index eb0151b10..76205716f 100644 --- a/Src/CSharpier/SyntaxNodeComparer.cs +++ b/Src/CSharpier/SyntaxNodeComparer.cs @@ -233,7 +233,7 @@ formattedNode is FileScopedNamespaceDeclarationSyntax fsnd // this validation will fail unless we also get them consistent here // adding a semi-complicated if check to determine when to do the string replacement // did not appear to have any performance benefits - if (originalToken.Text.Replace("\r", "") != formattedToken.Text.Replace("\r", "")) + if (originalToken.ValueText.Replace("\r", "") != formattedToken.ValueText.Replace("\r", "")) { return NotEqual( originalToken.RawSyntaxKind() == SyntaxKind.None diff --git a/Src/CSharpier/SyntaxPrinter/SyntaxNodePrinters/InterpolatedStringExpression.cs b/Src/CSharpier/SyntaxPrinter/SyntaxNodePrinters/InterpolatedStringExpression.cs index 943be4084..ddb248afa 100644 --- a/Src/CSharpier/SyntaxPrinter/SyntaxNodePrinters/InterpolatedStringExpression.cs +++ b/Src/CSharpier/SyntaxPrinter/SyntaxNodePrinters/InterpolatedStringExpression.cs @@ -2,6 +2,8 @@ namespace CSharpier.SyntaxPrinter.SyntaxNodePrinters; internal static class InterpolatedStringExpression { + internal static readonly string[] lineSeparators = new[] { "\r\n", "\r", "\n" }; + public static Doc Print(InterpolatedStringExpressionSyntax node, FormattingContext context) { // if any of the expressions in the interpolation contain a newline then don't force this flat @@ -15,6 +17,14 @@ public static Doc Print(InterpolatedStringExpressionSyntax node, FormattingConte ); } + if ( + node.StringStartToken.RawSyntaxKind() + == SyntaxKind.InterpolatedMultiLineRawStringStartToken + ) + { + return RawString(node, context); + } + var docs = new List { Token.PrintWithoutLeadingTrivia(node.StringStartToken, context) @@ -29,4 +39,58 @@ public static Doc Print(InterpolatedStringExpressionSyntax node, FormattingConte Doc.ForceFlat(docs) ); } + + private static Doc RawString(InterpolatedStringExpressionSyntax node, FormattingContext context) + { + var lastLineIsIndented = + node.StringEndToken.Text.Replace("\r", string.Empty).Replace("\n", string.Empty)[0] + is '\t' + or ' '; + + var contents = new List + { + Token.Print(node.StringStartToken, context), + lastLineIsIndented ? Doc.HardLineNoTrim : Doc.LiteralLine + }; + foreach (var content in node.Contents) + { + if (content is InterpolationSyntax interpolationSyntax) + { + contents.Add(Interpolation.Print(interpolationSyntax, context)); + } + else if (content is InterpolatedStringTextSyntax textSyntax) + { + if (textSyntax.TextToken.ValueText == string.Empty) + { + continue; + } + + var lines = textSyntax + .TextToken + .ValueText + .Split(lineSeparators, StringSplitOptions.None); + for (var index = 0; index < lines.Length; index++) + { + var line = lines[index]; + contents.Add(line); + if (index == lines.Length - 1) + { + continue; + } + contents.Add( + lastLineIsIndented + ? string.IsNullOrEmpty(line) + ? Doc.HardLine + : Doc.HardLineNoTrim + : Doc.LiteralLine + ); + } + } + } + + 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)); + } } diff --git a/Src/CSharpier/SyntaxPrinter/Token.cs b/Src/CSharpier/SyntaxPrinter/Token.cs index 75e94f248..012fb4f55 100644 --- a/Src/CSharpier/SyntaxPrinter/Token.cs +++ b/Src/CSharpier/SyntaxPrinter/Token.cs @@ -24,6 +24,8 @@ public static Doc PrintWithSuffix( return PrintSyntaxToken(syntaxToken, context, suffixDoc, skipLeadingTrivia); } + internal static readonly string[] lineSeparators = new[] { "\r\n", "\r", "\n" }; + private static Doc PrintSyntaxToken( SyntaxToken syntaxToken, FormattingContext context, @@ -69,38 +71,42 @@ is InterpolatedStringExpressionSyntax } else if (syntaxToken.RawSyntaxKind() is SyntaxKind.MultiLineRawStringLiteralToken) { - var contents = new List(); - var lines = syntaxToken.Text.Replace("\r", string.Empty).Split('\n'); - var currentIndentation = lines[^1].CalculateCurrentLeadingIndentation( - context.IndentSize - ); - if (currentIndentation == 0) - { - contents.Add(Doc.Join(Doc.LiteralLine, lines.Select(o => new StringDoc(o)))); - } - else + var linesIncludingQuotes = syntaxToken + .Text + .Split(lineSeparators, StringSplitOptions.None); + var lastLineIsIndented = linesIncludingQuotes[^1][0] is '\t' or ' '; + var contents = new List { - foreach (var line in lines) - { - var indentation = line.CalculateCurrentLeadingIndentation(context.IndentSize); - var numberOfSpacesToAddOrRemove = indentation - currentIndentation; - var modifiedLine = - numberOfSpacesToAddOrRemove > 0 - ? context.UseTabs - ? new string('\t', numberOfSpacesToAddOrRemove / context.IndentSize) - : new string(' ', numberOfSpacesToAddOrRemove) - : string.Empty; - modifiedLine += line.TrimStart(); - contents.Add(modifiedLine); - contents.Add( - numberOfSpacesToAddOrRemove > 0 ? Doc.HardLineNoTrim : Doc.HardLine - ); - } + linesIncludingQuotes[0], + lastLineIsIndented ? Doc.HardLineNoTrim : Doc.LiteralLine + }; - contents.RemoveAt(contents.Count - 1); + var lines = syntaxToken.ValueText.Split(lineSeparators, StringSplitOptions.None); + foreach (var line in lines) + { + contents.Add(line); + contents.Add( + lastLineIsIndented + ? string.IsNullOrEmpty(line) + ? Doc.HardLine + : Doc.HardLineNoTrim + : Doc.LiteralLine + ); } - docs.Add(Doc.Indent(contents)); + contents.Add(linesIncludingQuotes[^1].TrimStart()); + + docs.Add( + Doc.IndentIf(syntaxToken.Parent?.Parent is not ArgumentSyntax, Doc.Concat(contents)) + ); + } + else if ( + syntaxToken.RawSyntaxKind() + is SyntaxKind.InterpolatedMultiLineRawStringStartToken + or SyntaxKind.InterpolatedRawStringEndToken + ) + { + docs.Add(syntaxToken.Text.Trim()); } else {