Skip to content

Commit

Permalink
Implement CA2246 codefix
Browse files Browse the repository at this point in the history
  • Loading branch information
Youssef1313 committed Sep 30, 2020
1 parent 83a6c22 commit f7df990
Show file tree
Hide file tree
Showing 2 changed files with 190 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections.Immutable;
using System.Composition;
using System.Diagnostics.CodeAnalysis;
using System.Threading;
using System.Threading.Tasks;
using Analyzer.Utilities;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;

#nullable enable

namespace Microsoft.CodeQuality.Analyzers.QualityGuidelines
{
[ExportCodeFixProvider(LanguageNames.CSharp, Name = AssigningSymbolAndItsMemberInSameStatement.RuleId), Shared]
public sealed class AssigningSymbolAndItsMemberInSameStatementFixer : CodeFixProvider
{
public override ImmutableArray<string> FixableDiagnosticIds => ImmutableArray.Create(AssigningSymbolAndItsMemberInSameStatement.RuleId);

public override Task RegisterCodeFixesAsync(CodeFixContext context)
{
// For something like `a.x = a = b;`, we offer 3 code fixes:
// First:
// a = b;
// a.x = b;
// Second:
// a = b;
// a.x = a;
// Third: (Not currently implemented)
// var temp = a;
// a = b;
// temp.x = b;

var title = MicrosoftCodeQualityAnalyzersResources.AssigningSymbolAndItsMemberInSameStatementTitle;
context.RegisterCodeFix(new MyCodeAction(title,
async ct => await SplitAssignmentFirstOption(context.Document, context.Span, ct).ConfigureAwait(false),
equivalenceKey: title),
context.Diagnostics);
context.RegisterCodeFix(new MyCodeAction(title,
async ct => await SplitAssignmentSecondOption(context.Document, context.Span, ct).ConfigureAwait(false),
equivalenceKey: title),
context.Diagnostics);
return Task.CompletedTask;
}

private static async Task<Document> SplitAssignmentFirstOption(Document document, TextSpan span, CancellationToken cancellationToken)
{
// This method splits `a.x = a = b` to:
// a = b;
// a.x = b;
var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);

// a.x = a = b;
var parentAssignment = root.FindNode(span).Parent;

if (!TryGetAssignmentExpressionParts(parentAssignment, out _, out var right) ||
!TryGetAssignmentExpressionParts(right, out _, out var rightOfRight))
{
return document;
}

// a = b;
right = GetExpressionFromAssignment(right).WithTriviaFrom(parentAssignment.Parent);

// a.x = b;
var firstEqualsLastAssignment = GetExpressionFromAssignment(GetAssignmentWithRight(parentAssignment, rightOfRight));

root = root.ReplaceNode(parentAssignment.Parent, new[] { right, firstEqualsLastAssignment });
return document.WithSyntaxRoot(root);
}

private static async Task<Document> SplitAssignmentSecondOption(Document document, TextSpan span, CancellationToken cancellationToken)
{
// This method splits `a.x = a = b` to:
// a = b;
// a.x = a;
var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);

// a.x = a = b;
var parentAssignment = root.FindNode(span).Parent;

if (!TryGetAssignmentExpressionParts(parentAssignment, out _, out var right) ||
!TryGetAssignmentExpressionParts(right, out var leftOfRight, out _))
{
return document;
}

// a = b;
right = GetExpressionFromAssignment(right).WithTriviaFrom(parentAssignment.Parent);

// a.x = a;
var firstEqualsSecondAssignment = GetExpressionFromAssignment(GetAssignmentWithRight(parentAssignment, leftOfRight));

root = root.ReplaceNode(parentAssignment.Parent, new[] { right, firstEqualsSecondAssignment });
return document.WithSyntaxRoot(root);
}

private static SyntaxNode GetAssignmentWithRight(SyntaxNode assignmentExpression, SyntaxNode newRight)
{
return ((AssignmentExpressionSyntax)assignmentExpression).WithRight((ExpressionSyntax)newRight);
}

private static SyntaxNode GetExpressionFromAssignment(SyntaxNode assignmentExpression)
{
return SyntaxFactory.ExpressionStatement((AssignmentExpressionSyntax)assignmentExpression);
}

private static bool TryGetAssignmentExpressionParts(SyntaxNode assignmentExpression, [NotNullWhen(true)] out SyntaxNode? left, [NotNullWhen(true)] out SyntaxNode? right)
{
if (assignmentExpression is AssignmentExpressionSyntax assignment)
{
left = assignment.Left;
right = assignment.Right;
return true;
}
left = null;
right = null;
return false;
}

public override FixAllProvider GetFixAllProvider()
{
return WellKnownFixAllProviders.BatchFixer;
}

private class MyCodeAction : DocumentChangeAction
{
public MyCodeAction(string title, Func<CancellationToken, Task<Document>> createChangedDocument, string equivalenceKey)
: base(title, createChangedDocument, equivalenceKey)
{
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System.Threading.Tasks;
using Xunit;
using VerifyCS = Test.Utilities.CSharpCodeFixVerifier<
Microsoft.CodeQuality.Analyzers.QualityGuidelines.AssigningSymbolAndItsMemberInSameStatement,
Microsoft.CodeQuality.Analyzers.QualityGuidelines.AssigningSymbolAndItsMemberInSameStatementFixer>;

namespace Microsoft.CodeAnalysis.NetAnalyzers.UnitTests.Microsoft.CodeQuality.Analyzers.QualityGuidelines
{
public class AssigningSymbolAndItsMemberInSameStatementFixerTests
{
[Fact]
public async Task CA2246CSharpCodeFixTestSplitUnobviousAssignment()
{
await VerifyCS.VerifyCodeFixAsync(@"
public class C
{
public C Field;
}
public class Test
{
public void Method()
{
C a = new C();
C b = new C();
[|a.Field|] = a = b;
}
}
",
@"
public class C
{
public C Field;
}
public class Test
{
public void Method()
{
C a = new C();
C b = new C();
a = b;
a.Field = b;
}
}
");
}
}
}

0 comments on commit f7df990

Please sign in to comment.