c# - 有没有办法在 Roslyn 的分析器和代码修复提供者之间传递数据(除了通过属性包)?

标签 c# code-analysis roslyn

随着新的 RC 版本的发布,我很高兴地看到现在有一​​个属性包允许 raised diagnostics 有额外的数据,在我看来,一个主要的用例是能够计算数据分析器转移到代码修复器中以监听该特定诊断。

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

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

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

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

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

最佳答案

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

SyntaxElementContainer

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()));
    }
}

Use case (simplified for shortness' sake)

// 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");

关于c# - 有没有办法在 Roslyn 的分析器和代码修复提供者之间传递数据(除了通过属性包)?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30084293/

相关文章:

c# - 如何自动加载 Roslyn 分析器(无代码修复)

c# - 从虚拟主机发送电子邮件而无需使用用户名和密码

c# - 从 .xls 和 .ods 文件中读取数据

c# - Linux 中的 MonoDevelop——终端命令

visual-studio-2010 - VS2010代码分析,有什么方法可以自动修复某些警告?

c++ - 哪个工具可以列出对 C 中特定变量的写入访问权限?

c# - 使用 MSBuildWorkspace 加载解决方案和项目时出现诊断错误

c# - 使用 Roslyn 访问和修改解决方案中的所有文档

c# - Untitesting/Moq 方法调用 webrequest

c# - 关于处理表单的代码分析警告