首页 文章

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

提问于
浏览
4

随着新的RC发布,我很高兴看到现在有一个属性包允许提升诊断有额外的数据,在我看来,其主要用例是能够在分析仪中运行数据计算进入代码修复程序,监听该特定诊断 .

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

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

现在,这个例子自从引入属性包以来失去了一些相关性,但我仍然认为它是一个有效的用例 . 我特别担心分析器和代码修复器之间在报告的诊断位置之间的唯一联系 . 就我而言,我可以有多个 DiagnosticDescriptor 情况下,可能都代表从特定"Rule"而产生不同的潜在问题,由 Diagnostic 及其 Id 定义的(我不知道,如果这是在罗斯林代码分析领域中的良好做法,但似乎是一种可接受的操作方式) .

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

那么,有没有办法将数据从分析器传递给相关的代码修复提供程序?我也想过向下转型,从 Diagnostic 派生的自定义类型的实例,但它似乎是一个代码味道给我,而且, Diagnostic 充满,我需要添加一个唯一的目的,重新实现抽象成员属性和 SimpleCodeFix 是密封的(argggghhhh)

1 回答

  • 2

    由于凯文提到没有真正的方法可以完成我本来想做的事情,因为诊断预计是可序列化的,它让我觉得我可以通过序列化来模仿我想要的东西 . 我发布了解决问题的解决方案 . 随意批评和/或强调一些潜在的问题 .

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

    用例(为简洁而简化)

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

相关问题