Is there a way to transfer data (with the exception of the property package) between the analyzer and the code provider in Roslyn?

With the new RC release, I was glad to see that now there is a real estate bag to allow advanced diagnostics to have additional data, the main use case of which, in my opinion, to be able to calculate the data in the analyzer is transferred to the code lock that listens to this specific diagnostics.

Now I understand that this property package allows you to store only string values. Although this may be useful, I still have to perform the same logic in my analyzer and my encoder, since I cannot just store this information and transmit it. Of course, I'm talking about more complex types, such as syntax nodes and characters.

For example, I created an analyzer that provided a specific set of directives usingin each file. The analyzer calculates which directives are missing and calls a diagnostic that notifies the user and the textual designation of the missing directives. The code fix provider would be pretty straightforward if I already had SyntaxNodeone that I have to implement (which I already have in my analyzer), but now I need to re-run most of the same logic in my encoder. why I ended up having a lot of code in my analyzer's public static helper methods)

Now this example has lost some of its relevance since the appearance of the property package, but I still consider it a valid precedent. I am particularly concerned about the only connection between the analyzer and the code lock at the location of the reported diagnostics. In my case, I can have several instances DiagnosticDescriptorthat could represent various potential problems arising from a particular β€œrule”, as defined by Diagnosticit Id(I have no idea if this is good practice in Roslyn code analysis, but it looks like an acceptable way work).

: , , (.. ) . "" , / , .

, ? , , Diagnostic, , , Diagnostic , SimpleCodeFix (argggghhhh)

+4
1

, , , , , , , , , . , , . / .

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");
+2

All Articles