I have a situation where I need to modify a situation where a user writes this kind of code :
bool SomeMethod(object obj)
{
if(obj == null)
return false;
return true;
}
To the following code :
bool SomeMethod(object obj)
{
return obj == null;
}
Currently, I have built an analyzer that works. I'll put the code below. Basically, the analyzer looks for if statements and verifies if the only statement of the if is a return statement.Not only that, it verifies that also, the next statement inside the method's declaration is a return statement.
The code fix provider looks for the ifStatement's condition and creates a new return statement using that condition. What I'm trying to do after replacing the if statement by the return statement, is to remove the second return statement. At this, I'm failing without knowing how.
At first, when the node's been replaced, I create a new root, because they can't be modify like a string, it's about the same thing. I try to delete the node, but this instruction is being ignored for some reason. And I've debugged it, when I access the next node(ReturnStatement), it's not null.
I guess my question is basically, how can I build a code fix provider which can modify a node without being "linked" on it.
Here's the code for the analyzer
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using System.Collections.Immutable;
using System.Linq;
namespace RefactoringEssentials.CSharp.Diagnostics
{
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class RewriteIfReturnToReturnAnalyzer : DiagnosticAnalyzer
{
private static readonly DiagnosticDescriptor descriptor = new DiagnosticDescriptor(
CSharpDiagnosticIDs.RewriteIfReturnToReturnAnalyzerID,
GettextCatalog.GetString("Convert 'if...return' to 'return'"),
GettextCatalog.GetString("Convert to 'return' statement"),
DiagnosticAnalyzerCategories.Opportunities,
DiagnosticSeverity.Info,
isEnabledByDefault: true,
helpLinkUri: HelpLink.CreateFor(CSharpDiagnosticIDs.RewriteIfReturnToReturnAnalyzerID)
);
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(descriptor);
public override void Initialize(AnalysisContext context)
{
context.RegisterSyntaxNodeAction(
(nodeContext) =>
{
Diagnostic diagnostic;
if (TryGetDiagnostic(nodeContext, out diagnostic))
{
nodeContext.ReportDiagnostic(diagnostic);
}
}, SyntaxKind.IfStatement);
}
private static bool TryGetDiagnostic(SyntaxNodeAnalysisContext nodeContext, out Diagnostic diagnostic)
{
diagnostic = default(Diagnostic);
if (nodeContext.IsFromGeneratedCode())
return false;
var node = nodeContext.Node as IfStatementSyntax;
var methodBody = node?.Parent as BlockSyntax;
var ifStatementIndex = methodBody?.Statements.IndexOf(node);
if (node?.Statement is ReturnStatementSyntax &&
methodBody?.Statements.ElementAt(ifStatementIndex.Value + 1) is ReturnStatementSyntax)
{
diagnostic = Diagnostic.Create(descriptor, node.GetLocation());
return true;
}
return false;
}
}
}
Here's the code for the Code fix provider
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Formatting;
using System.Collections.Immutable;
using System.Linq;
using System.Threading.Tasks;
namespace RefactoringEssentials.CSharp.Diagnostics
{
[ExportCodeFixProvider(LanguageNames.CSharp), System.Composition.Shared]
public class RewriteIfReturnToReturnCodeFixProvider : CodeFixProvider
{
public override ImmutableArray<string> FixableDiagnosticIds
{
get
{
return ImmutableArray.Create(CSharpDiagnosticIDs.RewriteIfReturnToReturnAnalyzerID);
}
}
public override FixAllProvider GetFixAllProvider()
{
return WellKnownFixAllProviders.BatchFixer;
}
public async override Task RegisterCodeFixesAsync(CodeFixContext context)
{
var document = context.Document;
var cancellationToken = context.CancellationToken;
var span = context.Span;
var diagnostics = context.Diagnostics;
var root = await document.GetSyntaxRootAsync(cancellationToken);
var diagnostic = diagnostics.First();
var node = root.FindNode(context.Span);
if (node == null)
return;
context.RegisterCodeFix(
CodeActionFactory.Create(node.Span, diagnostic.Severity, "Convert to 'return' statement", token =>
{
var statementCondition = (node as IfStatementSyntax)?.Condition;
var newReturn = SyntaxFactory.ReturnStatement(SyntaxFactory.Token(SyntaxKind.ReturnKeyword),
statementCondition, SyntaxFactory.Token(SyntaxKind.SemicolonToken));
var newRoot = root.ReplaceNode(node as IfStatementSyntax, newReturn
.WithLeadingTrivia(node.GetLeadingTrivia())
.WithAdditionalAnnotations(Formatter.Annotation));
var block = node.Parent as BlockSyntax;
if (block == null)
return null;
//This code (starting from here) does not do what I'd like to do ...
var returnStatementAfterIfStatementIndex = block.Statements.IndexOf(node as IfStatementSyntax) + 1;
var returnStatementToBeEliminated = block.Statements.ElementAt(returnStatementAfterIfStatementIndex) as ReturnStatementSyntax;
var secondNewRoot = newRoot.RemoveNode(returnStatementToBeEliminated, SyntaxRemoveOptions.KeepNoTrivia);
return Task.FromResult(document.WithSyntaxRoot(secondNewRoot));
}), diagnostic);
}
}
}
And finally, this is my NUnit test :
[Test]
public void When_Retrurn_Statement_Corrected()
{
var input = @"
class TestClass
{
bool TestMethod (object obj)
{
$if (obj != null)
return true;$
return false;
}
}";
var output = @"
class TestClass
{
bool TestMethod (object obj)
{
return obj!= null;
}
}";
Analyze<RewriteIfReturnToReturnAnalyzer>(input, output);
}