Skip to content

Commit

Permalink
Fix handling of EOLs in removed members
Browse files Browse the repository at this point in the history
  • Loading branch information
Sergio0694 committed Dec 27, 2024
1 parent 7d5c559 commit 17bc997
Showing 1 changed file with 64 additions and 33 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Collections.Generic;
using System.Collections.Immutable;
using System.Composition;
using System.Diagnostics.CodeAnalysis;
Expand Down Expand Up @@ -217,6 +218,8 @@ private static async Task<Document> ConvertToPartialProperty(
syntaxEditor,
defaultValueExpression);

RemoveLeftoverLeadingEndOfLines([fieldDeclaration], syntaxEditor);

// Create the new document with the single change
return document.WithSyntaxRoot(syntaxEditor.GetChangedRoot());
}
Expand Down Expand Up @@ -305,39 +308,6 @@ private static void ConvertToPartialProperty(
])).WithTrailingTrivia(propertyDeclaration.AccessorList.GetTrailingTrivia()));
});

// Special handling for the leading trivia of members following the field declaration we are about to remove.
// There is an edge case that can happen when a type declaration is as follows:
//
// class ContainingType
// {
// public static readonly DependencyProperty NameProperty = ...;
//
// public void SomeOtherMember() { }
//
// public string? Name { ... }
// }
//
// In this case, just removing the target field for the dependency property being rewritten (that is, 'NameProperty')
// will cause an extra blank line to be left after the edits, right above the member immediately following the field.
// To work around this, we look for such a member and check its trivia, and then manually remove a leading blank line.
if (fieldDeclaration.Parent is TypeDeclarationSyntax fieldParentTypeDeclaration)
{
int fieldDeclarationIndex = fieldParentTypeDeclaration.Members.IndexOf(fieldDeclaration);

// Check whether there is a member immediatley following the field
if (fieldDeclarationIndex >= 0 && fieldDeclarationIndex < fieldParentTypeDeclaration.Members.Count - 1)
{
MemberDeclarationSyntax nextMember = fieldParentTypeDeclaration.Members[fieldDeclarationIndex + 1];
SyntaxTriviaList leadingTrivia = nextMember.GetLeadingTrivia();

// Check whether this member has a first leading trivia that's just a blank line: we want to remove this one
if (leadingTrivia.Count > 0 && leadingTrivia[0].IsKind(SyntaxKind.EndOfLineTrivia))
{
syntaxEditor.ReplaceNode(nextMember, (nextMember, _) => nextMember.WithLeadingTrivia(leadingTrivia.RemoveAt(0)));
}
}
}

// Also remove the field declaration (it'll be generated now)
syntaxEditor.RemoveNode(fieldDeclaration);

Expand All @@ -352,6 +322,58 @@ private static void ConvertToPartialProperty(
}
}

/// <summary>
/// Removes any leftover leading end of lines on remaining members following any removed fields.
/// </summary>
/// <param name="fieldDeclarations">The collection of all fields that have been removed.</param>
/// <param name="syntaxEditor">The <see cref="SyntaxEditor"/> instance to use.</param>
private static void RemoveLeftoverLeadingEndOfLines(IReadOnlyCollection<FieldDeclarationSyntax> fieldDeclarations, SyntaxEditor syntaxEditor)
{
foreach (FieldDeclarationSyntax fieldDeclaration in fieldDeclarations)
{
// Special handling for the leading trivia of members following the field declaration we are about to remove.
// There is an edge case that can happen when a type declaration is as follows:
//
// class ContainingType
// {
// public static readonly DependencyProperty NameProperty = ...;
//
// public void SomeOtherMember() { }
//
// public string? Name { ... }
// }
//
// In this case, just removing the target field for the dependency property being rewritten (that is, 'NameProperty')
// will cause an extra blank line to be left after the edits, right above the member immediately following the field.
// To work around this, we look for such a member and check its trivia, and then manually remove a leading blank line.
if (fieldDeclaration.Parent is TypeDeclarationSyntax fieldParentTypeDeclaration)
{
int fieldDeclarationIndex = fieldParentTypeDeclaration.Members.IndexOf(fieldDeclaration);

// Check whether there is a member immediatley following the field
if (fieldDeclarationIndex >= 0 && fieldDeclarationIndex < fieldParentTypeDeclaration.Members.Count - 1)
{
MemberDeclarationSyntax nextMember = fieldParentTypeDeclaration.Members[fieldDeclarationIndex + 1];

// It's especially important to skip members that have been rmeoved. This would otherwise fail when computing
// the final document. We only care about fixing trivia for members that will still be present after all edits.
if (fieldDeclarations.Contains(nextMember))
{
continue;
}

SyntaxTriviaList leadingTrivia = nextMember.GetLeadingTrivia();

// Check whether this member has a first leading trivia that's just a blank line: we want to remove this one
if (leadingTrivia.Count > 0 && leadingTrivia[0].IsKind(SyntaxKind.EndOfLineTrivia))
{
syntaxEditor.ReplaceNode(nextMember, (nextMember, _) => nextMember.WithLeadingTrivia(leadingTrivia.RemoveAt(0)));
}
}
}
}
}

/// <summary>
/// A custom <see cref="FixAllProvider"/> with the logic from <see cref="UsePartialPropertyForSemiAutoPropertyCodeFixer"/>.
/// </summary>
Expand Down Expand Up @@ -381,6 +403,10 @@ private sealed class FixAllProvider : DocumentBasedFixAllProvider
// Create an editor to perform all mutations (across all edits in the file)
SyntaxEditor syntaxEditor = new(root, fixAllContext.Solution.Services);

// Create the set to track all fields being removed, to adjust whitespaces
HashSet<FieldDeclarationSyntax> fieldDeclarations = [];

// Step 1: rewrite all properties and remove the fields
foreach (Diagnostic diagnostic in diagnostics)
{
// Get the current property declaration for the diagnostic
Expand All @@ -407,8 +433,13 @@ private sealed class FixAllProvider : DocumentBasedFixAllProvider
generatedDependencyPropertyAttributeList,
syntaxEditor,
defaultValue);

fieldDeclarations.Add(fieldDeclaration);
}

// Step 2: remove any leftover leading end of lines on members following fields that have been removed
RemoveLeftoverLeadingEndOfLines(fieldDeclarations, syntaxEditor);

return document.WithSyntaxRoot(syntaxEditor.GetChangedRoot());
}
}
Expand Down

0 comments on commit 17bc997

Please sign in to comment.