我有一种情况需要修改用户编写此类代码的情况:
bool SomeMethod(object obj)
{
if(obj == null)
return false;
return true;
}
以下代码:
bool SomeMethod(object obj)
{
return obj == null;
}
目前,我已经建立了一个可行的分析仪。我将把代码放在下面。基本上,分析器查找if语句并验证if的唯一语句是否是return语句。不仅如此,它还验证了方法声明中的下一个语句是return语句。
代码修复提供程序查找ifStatement的条件并使用该条件创建新的return语句。我在用return语句替换if语句后尝试做的是删除第二个return语句。在这一点上,我在不知道如何失败的情况下失败了。
首先,当节点被替换时,我创建了一个新的根,因为它们不能像字符串那样修改,它是关于同样的事情。我尝试删除该节点,但由于某种原因,该指令被忽略。我调试了它,当我访问下一个节点(ReturnStatement)时,它不是null。
我想我的问题基本上是,我如何构建一个代码修复提供程序,它可以修改节点而不需要“链接”它。
以下是分析器的代码
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;
}
}
}
以下是代码修复提供程序的代码
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);
}
}
}
最后,这是我的NUnit测试:
[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);
}
答案 0 :(得分:1)
我认为问题可能就在这一行:
var block = node.Parent as BlockSyntax;
您正在使用原始树中的node
,原始树中也有.Parent
(不已更新newReturn
)。
然后,它最终使用不再是最新的旧树计算returnStatementToBeEliminated
,因此当您调用var secondNewRoot = newRoot.RemoveNode(returnStatementToBeEliminated, SyntaxRemoveOptions.KeepNoTrivia);
时,没有任何反应,因为newRoot
不包含{{1} }}
因此,您基本上希望使用等效的returnStatementToBeEliminated
,而不是node.Parent
下的版本。我们用于此的低级工具称为SyntaxAnnotation
,它们具有在树编辑之间向前追踪的属性。您可以在进行任何编辑之前向newRoot
添加特定注释,然后进行编辑,然后让node.Parent
找到包含注释的节点。
您可以像这样手动跟踪节点,或者您可以使用SyntaxEditor
类,它将Annotations部分抽象为更简单的方法,如newRoot
({{1}中还有一些其他不错的功能您可能想要查看)。
答案 1 :(得分:0)
对于这个问题,我参考了以下帖子: How do I create a new root by adding and removing nodes retrieved from the old root?
这篇文章展示了这个名为DocumentEditor的类,它允许用户修改他想要的文档,即使它假设是不可变的。之前的问题是,删除节点后,如果我指的是与该节点有连接的东西,那么关系就会消失,我就可以填充了。 基本上,文档注释说这个类是#34;一个编辑器,用于更改文档的语法树。&#34; 在您完成修改该文档后,您需要创建一个新文档并将其作为代码修复提供程序中的任务返回。 为了解决我在代码修复提供程序中遇到的这个问题,我使用了以下代码:
context.RegisterCodeFix(CodeAction.Create("Convert to 'return' statement", async token =>
{
var editor = await DocumentEditor.CreateAsync(document, cancellationToken);
var statementCondition = (node as IfStatementSyntax)?.Condition;
var newReturn = SyntaxFactory.ReturnStatement(SyntaxFactory.Token(SyntaxKind.ReturnKeyword),
statementCondition, SyntaxFactory.Token(SyntaxKind.SemicolonToken));
editor.ReplaceNode(node as IfStatementSyntax, newReturn
.WithLeadingTrivia(node.GetLeadingTrivia())
.WithAdditionalAnnotations(Formatter.Annotation));
var block = node.Parent as BlockSyntax;
if (block == null)
return null;
var returnStatementAfterIfStatementIndex = block.Statements.IndexOf(node as IfStatementSyntax) + 1;
var returnStatementToBeEliminated = block.Statements.ElementAt(returnStatementAfterIfStatementIndex) as ReturnStatementSyntax;
editor.RemoveNode(returnStatementToBeEliminated);
var newDocument = editor.GetChangedDocument();
return newDocument;
}, string.Empty), diagnostic);
由于该课程,解决我的问题非常简单。