Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using Microsoft.CodeAnalysis.CSharp.Shared.Extensions;
using Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions;
using Microsoft.CodeAnalysis.Editor.UnitTests.Diagnostics.NamingStyles;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.ImplementType;
using Microsoft.CodeAnalysis.Test.Utilities;
using Microsoft.CodeAnalysis.Testing;
Expand Down Expand Up @@ -11900,4 +11901,27 @@ class C1 : I1
ReferenceAssemblies = ReferenceAssemblies.Net.Net80,
LanguageVersion = LanguageVersionExtensions.CSharpNext,
}.RunAsync();

[Fact]
public Task TestImplementIDisposable_DisposePattern_LF_EndOfLine()
=> new VerifyCS.Test
{
TestCode = """
using System;
class C : {|CS0535:IDisposable|}{|CS1513:|}{|CS1514:|}
""".Replace("\r\n", "\n"),
FixedCode = $$"""
using System;
class C : IDisposable
{
private bool disposedValue;

{{DisposePattern("protected virtual ", "C", "public void ")}}
}
""".Replace("\r\n", "\n"),
CodeActionIndex = 1,
ReferenceAssemblies = ReferenceAssemblies.Net.Net80,
LanguageVersion = LanguageVersionExtensions.CSharpNext,
Options = { { FormattingOptions2.NewLine, "\n" } },
}.RunAsync();
}
Original file line number Diff line number Diff line change
Expand Up @@ -281,31 +281,34 @@ static IEnumerable<TextSpan> EnumerateAnnotatedSpans(SyntaxNode node, SyntaxAnno
var (firstToken, lastToken) = nodeOrToken.AsNode(out var childNode)
? (childNode.GetFirstToken(includeZeroWidth: true), childNode.GetLastToken(includeZeroWidth: true))
: (nodeOrToken.AsToken(), nodeOrToken.AsToken());
yield return GetSpan(firstToken, lastToken);
yield return GetSpanIncludingPreviousAndNextTokens(firstToken, lastToken);
}
}
}

internal static TextSpan GetSpan(SyntaxToken firstToken, SyntaxToken lastToken)
/// <summary>
/// Attempt to get a span that encompassed these tokens, but is expanded to go from the normal start of the
/// token that precedes them to the normal end of the token that follows. If there is no token that precedes
/// or follows, then we expand to consume at least the full span of the <paramref name="firstToken"/> and
/// <paramref name="lastToken"/> so that we at least will try to format any trivia on them.
/// </summary>
internal static TextSpan GetSpanIncludingPreviousAndNextTokens(SyntaxToken firstToken, SyntaxToken lastToken)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the thing we're fixing is that we injected a final } into the file with an elastic-cr-lf (this is good).

However, the formatting engine wasn't ever fixing up that elastic-cr-lf because the code below effectively said "only format up to the exact end of the }, no further". This was happening because we normally try to find the 'next' token after the last touched token and format through that. But in this case, there is no next token (it's the last token in the file). So the fix is in that case to consider up through the full end of the token (which includes the trailing trivia) so we will update that properly.

{
var previousToken = firstToken.GetPreviousToken();
var nextToken = lastToken.GetNextToken();

if (previousToken.RawKind != 0)
{
firstToken = previousToken;
}
var start = previousToken.RawKind != 0
? previousToken.SpanStart
: firstToken.FullSpan.Start;

if (nextToken.RawKind != 0)
{
lastToken = nextToken;
}
var nextToken = lastToken.GetNextToken();
var end = nextToken.RawKind != 0
? nextToken.Span.End
: lastToken.FullSpan.End;

return TextSpan.FromBounds(firstToken.SpanStart, lastToken.Span.End);
return TextSpan.FromBounds(start, end);
}

internal static TextSpan GetElasticSpan(SyntaxToken token)
=> GetSpan(token, token);
=> GetSpanIncludingPreviousAndNextTokens(token, token);

private static IEnumerable<TextSpan> AggregateSpans(IEnumerable<TextSpan> spans)
{
Expand Down
Loading