【问题标题】:Is there a way to pass data (other than through the property bag) between an analyzer and a code fix provider in Roslyn?有没有办法在 Roslyn 的分析器和代码修复提供程序之间传递数据(除了通过属性包)?
【发布时间】:2015-07-17 00:51:42
【问题描述】:

在新的 RC 版本中,我很高兴看到现在有一​​个属性包可以让引发的诊断获得额外的数据,在我看来,它的一个主要用例是能够在分析器转移到代码修复器中,以侦听该特定诊断。

我现在意识到这个属性包只允许存储字符串值。虽然这可能很有用,但我仍然发现自己必须在我的分析器和代码修复程序中运行完全相同的逻辑,因为我没有能力只保留这些信息并传递它。我当然是在谈论更复杂的类型,例如语法节点和符号。

例如,我创建了一个分析器,该分析器强制在每个文件中存在一组特定的 using 指令。分析器计算缺少哪些指令并引发诊断,通知用户并以文本方式指示缺少的指令。如果我已经有我必须实现的SyntaxNodes(我的分析器中已经有),那么代码修复提供程序将非常简单,但我现在必须在我的代码修复程序中重新运行大部分相同的逻辑(这就是为什么我最终将大量代码放在我的分析器中的公共静态帮助方法中)

现在,自从引入属性包以来,这个示例失去了一些相关性,但我仍然认为它是一个有效的用例。我特别担心报告诊断位置中分析器和代码修复程序之间的唯一链接。就我而言,我可以有多个 DiagnosticDescriptor 实例,它们都可能代表源自特定“规则”的不同潜在问题,由 Diagnostic 及其 Id 定义(我不知道这是否是一个好习惯在 Roslyn 代码分析领域,但似乎是一种可接受的操作方式)。

底线是:对于相同的诊断 ID,我可能会根据情况在不同的位置(即在完全不同的语法元素上)提出诊断。因此,我失去了让提供的位置位于确定和/或相关语法元素上的“确定性”,并且修复诊断的后续逻辑消失了。

那么,有没有办法将数据从分析器传递到相关的代码修复提供程序?我还考虑过向下转换从 Diagnostic 派生的自定义类型的实例,但对我来说这似乎是一种代码味道,此外,Diagnostic 充满了我需要重新实现的抽象成员添加一个属性的目的,并且SimpleCodeFix被密封(argggghhhh)

【问题讨论】:

  • 您为什么认为重新计算修复数据不好?一般来说,您应该在诊断中做尽可能少的工作,因为这会在整个程序中发生,并尽可能多地将工作推迟到修复中,因为这只是偶尔发生,并且只有在用户请求时才会发生。跨度>
  • 我同意分析仪应该做最少的工作,但是如果特定分析仪需要进行处理以提供准确的分析并通过诊断报告特定问题已经为我提供了我需要的信息,我是否应该无法向代码修复程序提供此(在我看来,可能是高度相关的)信息?此外,我不会称它为 bad 重新计算两次相同的东西,但如果有机会,我肯定会避免这样做,你不同意吗?
  • API提供一种在诊断中存储数据其他字符串的方法的原因之一是,我们正在考虑分析仪未来可能不使用的潜在用途在与修复相同的进程中运行,诊断需要序列化。
  • 我不知道您将来会考虑在模型上使用这种类型。话虽如此,您似乎误解了我在最初问题中的意图:我无意将工作卸载到分析仪。我只是说,当试图找出应该提出诊断的 ifwhere 时,一定会获得一些信息。由于此信息与诊断相关,因此可以说它可能与代码修复程序相关。
  • 另一个强调我的观点的例子是验证链调用,如 X.Foo(...).Bar (...).DoSomething("Y") 必须具有参数传递给 DoSomething 是一个字符串,其值表示作为成员访问链的根的变量的名称(在这种情况下,“X”!=“Y”,因此分析器将标记诊断)。为了检测到应该报告诊断,我必须处理一些逻辑,例如使用“this.X”(在这种情况下,只有 X 应该以相等为目标)或仅以“DoSomething”方法为目标,等等。 .

标签: c# code-analysis roslyn


【解决方案1】:

由于 Kevin 提到没有真正的方法来完成我在本地尝试做的事情,因为诊断应该是可序列化的,这让我想到我可以通过序列化来模拟我想要的东西。我是 发布我想出的解决方案来解决问题。随意批评和/或强调一些潜在问题。

语法元素容器

public class SyntaxElementContainer<TKey> : Dictionary<string, string>
{
    private const string Separator = "...";
    private static readonly string DeserializationPattern = GetFormattedRange(@"(\d+)", @"(\d+)");

    private static string GetFormattedRange(string start, string end)
    {
        return $"{start}{Separator}{end}";
    }

    public SyntaxElementContainer()
    {
    }

    public SyntaxElementContainer(ImmutableDictionary<string, string> propertyBag)
        : base(propertyBag)
    {
    }

    public void Add(TKey nodeKey, SyntaxNode node)
    {
        Add(nodeKey.ToString(), SerializeSpan(node?.Span));
    }

    public void Add(TKey tokenKey, SyntaxToken token)
    {
        Add(tokenKey.ToString(), SerializeSpan(token.Span));
    }

    public void Add(TKey triviaKey, SyntaxTrivia trivia)
    {
        Add(triviaKey.ToString(), SerializeSpan(trivia.Span));
    }


    public TextSpan GetTextSpanFromKey(string syntaxElementKey)
    {
        var spanAsText = this[syntaxElementKey];
        return DeSerializeSpan(spanAsText);
    }

    public int GetTextSpanStartFromKey(string syntaxElementKey)
    {
        var span = GetTextSpanFromKey(syntaxElementKey);
        return span.Start;
    }

    private string SerializeSpan(TextSpan? span)
    {
        var actualSpan = span == null || span.Value.IsEmpty ? default(TextSpan) : span.Value; 
        return GetFormattedRange(actualSpan.Start.ToString(), actualSpan.End.ToString());
    }

    private TextSpan DeSerializeSpan(string spanAsText)
    {
        var match = Regex.Match(spanAsText, DeserializationPattern);
        if (match.Success)
        {
            var spanStartAsText = match.Groups[1].Captures[0].Value;
            var spanEndAsText = match.Groups[2].Captures[0].Value;

            return TextSpan.FromBounds(int.Parse(spanStartAsText), int.Parse(spanEndAsText));
        }

        return new TextSpan();
    }   
}

PropertyBagSyntaxInterpreter

public class PropertyBagSyntaxInterpreter<TKey>
{
    private readonly SyntaxNode _root;

    public SyntaxElementContainer<TKey> Container { get; }

    protected PropertyBagSyntaxInterpreter(ImmutableDictionary<string, string> propertyBag, SyntaxNode root)
    {
        _root = root;
        Container = new SyntaxElementContainer<TKey>(propertyBag);
    }

    public PropertyBagSyntaxInterpreter(Diagnostic diagnostic, SyntaxNode root)
        : this(diagnostic.Properties, root)
    {
    }

    public SyntaxNode GetNode(TKey nodeKey)
    {
        return _root.FindNode(Container.GetTextSpanFromKey(nodeKey.ToString()));
    }

    public TSyntaxType GetNodeAs<TSyntaxType>(TKey nodeKey) where TSyntaxType : SyntaxNode
    {
        return _root.FindNode(Container.GetTextSpanFromKey(nodeKey.ToString())) as TSyntaxType;
    }


    public SyntaxToken GetToken(TKey tokenKey)
    {

        return _root.FindToken(Container.GetTextSpanStartFromKey(tokenKey.ToString()));
    }

    public SyntaxTrivia GetTrivia(TKey triviaKey)
    {
        return _root.FindTrivia(Container.GetTextSpanStartFromKey(triviaKey.ToString()));
    }
}

用例(为简短起见进行了简化)

// In the analyzer
MethodDeclarationSyntax someMethodSyntax = ...
var container = new SyntaxElementContainer<string>
{
    {"TargetMethodKey", someMethodSyntax}
};

// In the code fixer
var bagInterpreter = new PropertyBagSyntaxInterpreter<string>(diagnostic, root);
var myMethod = bagInterpreter.GetNodeAs<MethodDeclarationSyntax>("TargetMethodKey");

【讨论】:

  • 我会在某处添加,为了对属性包进行诊断,您需要调用需要一个的重载。
猜你喜欢
  • 1970-01-01
  • 2014-02-27
  • 2015-03-31
  • 1970-01-01
  • 2019-10-09
  • 2020-12-31
  • 1970-01-01
  • 1970-01-01
  • 2018-12-02
相关资源
最近更新 更多