【问题标题】:How to evaluate local variable/ parameter state with Roslyn如何使用 Roslyn 评估局部变量/参数状态
【发布时间】:2015-08-12 10:27:08
【问题描述】:

我的情况有点复杂。我必须为诸如仅分配但从未使用过的参数或从未使用过的局部变量等情况创建分析器/代码修复提供程序。

对于参数的情况,我要进行方法声明并查看参数列表以获取所有分析器。我正在检查方法中的赋值表达式,并过滤使用辅助方法分配的参数。

它变得模糊的地方是我不知道或不知道何时使用局部变量/参数。我已经浏览了符号,但他们无法告诉我使用/未使用的变量。我可以尝试通过在字符串中转换方法声明语法上下文并查找已分配的参数来查找在方法中提及变量名称的次数,但这只是一个糟糕的主意。

我真的被困住了,我希望任何有过这种情况经历的人为此提供一些帮助。

对于可能会问的人,我主要是在寻找分析器缺少的逻辑。我不知道代码修复提供程序将如何工作。如果您知道我能做什么,请随时将其包含在您的答案中!到目前为止,我认为可以从方法中删除未使用的局部变量,对于未使用的参数也是如此。我现在不确定。

更新

我现在正在尝试使用 DataFlow API,但目前它不适合我。这个线程最古老的答案给了我一个起点,但它实际上不起作用。

我想出了自己的方法:

private static bool IsLocalVariableBeingUsed(VariableDeclaratorSyntax variableDeclarator, SyntaxNodeAnalysisContext syntaxNode)
{
    var model = syntaxNode.SemanticModel.Compilation.GetSemanticModel(variableDeclarator.SyntaxTree);
    var methodBody = variableDeclarator.AncestorsAndSelf(false).OfType<MethodDeclarationSyntax>().First();
    var lastMethodNode = methodBody?.ChildNodes().LastOrDefault();
    if (lastMethodNode == null)
        return false;

    var readWrite = syntaxNode.SemanticModel.AnalyzeDataFlow(variableDeclarator, lastMethodNode); 
}

但这也行不通。使用 NUnit 进行测试时:

var input = @"
class TestClass {
    void TestMethod ()
    {
        int i;
    }
}";

当运行时到达 readWrite 或 result(来自最旧的答案)时,我收到以下消息:

System.ArgumentOutRangeException Index was out of range Must be non negative and lesser than the size of the collection"

但在此之前,在我的分析器中,当我尝试验证我的节点以确保它不为空并为数据流 API 创建适当的元素时,没有代码中断(不确定这是否是适当的术语)但在我无法进步的那一刻。

【问题讨论】:

标签: c# roslyn analyzer


【解决方案1】:

您可以通过DataFlowAnalysis API 查看大部分变量是否被使用(读/写)。我已经在my blog 上写了这个 API 的介绍。

我相信您的情况是,您正在寻找从未读取过的变量。

var tree = CSharpSyntaxTree.ParseText(@"
public class Sample
{
   public void Foo()
   {
        int unused = 0;
        int used = 1;
        System.Console.Write(used);
   }
}");

var Mscorlib = PortableExecutableReference.CreateFromAssembly(typeof(object).Assembly);
var compilation = CSharpCompilation.Create("MyCompilation",
    syntaxTrees: new[] { tree }, references: new[] { Mscorlib });
var model = compilation.GetSemanticModel(tree);

var methodBody = tree.GetRoot().DescendantNodes().OfType<MethodDeclarationSyntax>().Single().Body;
DataFlowAnalysis result = model.AnalyzeDataFlow(methodBody);

var variablesDeclared = result.VariablesDeclared;
var variablesRead = result.ReadInside.Union(result.ReadOutside);

var unused = variablesDeclared.Except(variablesRead);

foreach(var variable in unused)
{
    Console.WriteLine(variable);
}

【讨论】:

  • 那是一个很好的答案。我真的很感谢你这么快回答。我应该比现在更快地把你的答案作为答案
  • 其实,看看我更新后的说法,答案对我不起作用
【解决方案2】:

基于 JoshVarty 的回答,为了让它在诊断中发挥作用,我将为所有 MethodDeclaration 语法种类注册一个 SyntaxNodeAction,然后在正文中查找未使用的变量:

public override void Initialize(AnalysisContext context)
{
    context.RegisterSyntaxNodeAction(AnalyzeIt, SyntaxKind.MethodDeclaration);
}

private static void AnalyzeIt(SyntaxNodeAnalysisContext context)
{
    var method = context.Node as MethodDeclarationSyntax;

    var dataFlow = context.SemanticModel.AnalyzeDataFlow(method.Body);

    var variablesDeclared = dataFlow.VariablesDeclared;
    var variablesRead = dataFlow.ReadInside.Union(dataFlow.ReadOutside);
    var unused = variablesDeclared.Except(variablesRead);

    if (unused.Any())
    {
        foreach (var unusedVar in unused)
        {
            context.ReportDiagnostic(Diagnostic.Create(Rule, unusedVar.Locations.First()));
        }
    }
}

【讨论】:

  • 喜欢这个答案!如果我想标记“int i”而不是简单的 i 怎么办?
  • context.Node 获取令牌,然后访问其父代。从那里您可以计算出要用于诊断的跨度。
  • 等等,考虑一下。如果我访问作为方法声明的节点的父节点,那对我来说没有任何意义,不是吗?还是我错过了什么?
  • 你说得对,我应该更清楚一点,你需要从 usedVar 获取令牌,然后访问它的父级,这应该是整个变量声明。您可以从 context.Node.FindToken 方法获取令牌。
  • 我修好了,谢谢你的洞察力!我所做的是保持标记原样,但删除 variableDeclarator.Parent.Parent 这是一个 LocalDeclarationVariable
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-01-29
  • 1970-01-01
  • 2014-12-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多