Symbol from ReferenceLocation?

℡╲_俬逩灬. 提交于 2019-12-11 23:25:38

问题


I'm trying to write some code using Roslyn that verifies some architectural layering constraints to aid some assembly consolidation.

i.e.: * An internal type, must be in an .Internal suffixed namespace. * A use of a 'X.Y.Z.Internal' type is only allowed from the X.Y.Z namespace.

The simplest looking approach to solve this problem is to find all internal types via enumeration as per FAQ(9) and then utilize SymbolFinder to find all of the references and examine the containing type at the reference sites. As mentioned in Get Symbol for ReferenceLocation, GetEnclosingSymbol() doesn't really return the most useful data for this purpose.

It looks like there might be a useful approach utilizing SymbolFinder.FindSymbolAtPosition, but sadly it appears that the ISyntaxFactsService and ISemanticModelFactsService are internal.

This is a shame, because that appears to be the limiting factor in trying the approach that the SemanticModelExtensions.GetSymbols() internal extension method uses.

Am I missing something straightforward? I sure hope so.


回答1:


SymbolFinder requires a Workspace, which you don't have when running inside MsBuild. So basically if you'd like to use the SymbolFinder you have to be inside Visual Studio and not in a let's say CI job.

An internal type, must be in an .Internal suffixed namespace. This can be easily solved by registering a symbol action on NamedTypes. in your callback you'll get an INamedSymbol, and you have to check if it's internal and if it's inside a properly named namespace or not. (Note you might need to think about what you do with nested types.)

A use of a 'X.Y.Z.Internal' type is only allowed from the X.Y.Z namespace. This is tougher. If working only inside VS is an option then you can find all references for each of the above type symbols by using the FindReferencesAsync. The link you're referring to has good suggestions. You can get the enclosing symbol as done here. And then you'll need to find if the method/property/field is in an appropriately named namespace or not. So you'' have to walk the symbol declaration tree upwards on ContainingType and ContainingNamespace.

I'm guessing that you'd like to run this analysis as part of a CI job, so this solution would not fit your need. In that case you can register a SyntaxNode action for all identifiers, get the underlying ISymbol and the enclosing symbol. And then you are at the same point as before. So you can check for the names. (This solution might be slow, you'll have to check if the performance is okay for your needs.)




回答2:


TL;DR: I think I've found a way that works, although I'm not sure if the performance will be ok or not.

The solution below involves:

  • calling GetReferenceSymbolLocationsAsync from inside the AddSymbolsAsync method from the ReferenceLocatoinExtensions.cs pattern that was linked to above.

  • using FindNode to transform the ReferenceLocation into a SyntaxNode

  • Depending on where the retrieved SyntaxNode is with respect to its parent nodes, do some adjustment until we find the correct SyntaxNode that will return a non-null value from SemanticModel.GetDeclaredSymbol()

  • Do some extra bookkeeping for purposes of being able to customize the rule at a higher level of the code. (The extra bookkeeping types are at the end.)

    private static async Task<List<ReferencedSymbolLocation>> GetReferenceSymbolLocationsAsync(
        Solution solution,
        SemanticModel semanticModel,
        ReferenceLocation reference,
        CancellationToken cancellationToken)
    {
        cancellationToken.ThrowIfCancellationRequested();
    
        var syntaxTree = semanticModel.SyntaxTree;
    
        var position = reference.Location.SourceSpan.Start;
        var result = new List<ReferencedSymbolLocation>();
    
        if (position >= syntaxTree.Length)
        {
            return result;
        }
    
        var root = await syntaxTree.GetRootAsync(cancellationToken).ConfigureAwait(false);
        var foundNode = root.FindNode(reference.Location.SourceSpan, false, getInnermostNodeForTie: false);
    
        // Try and keep track of what kind of syntactical context
        // we found the referenced symbol:
        ReferenceType discoveredType = ReferenceType.Other;
    
        for (var current = foundNode; current != null; current = current.Parent)
        {
            cancellationToken.ThrowIfCancellationRequested();
    
            if (current as BaseListSyntax != null)
            {
                discoveredType = ReferenceType.BaseClass;
                continue;
            }
            else if (current as ClassDeclarationSyntax != null)
            {
                if (discoveredType == ReferenceType.Other)
                {
                    discoveredType = ReferenceType.Class;
                }
                result.Add(CreateReferenceSymbolLocation(reference, semanticModel, current, discoveredType, cancellationToken));
                break;
            }
            else if (current as PropertyDeclarationSyntax != null)
            {
                result.Add(CreateReferenceSymbolLocation(reference, semanticModel, current, ReferenceType.Property, cancellationToken));
                break;
            }
            else if (current as ParameterSyntax != null)
            {
                // This covers method parameters and lambda parameters:
                result.Add(CreateReferenceSymbolLocation(reference, semanticModel, current, ReferenceType.Parameter, cancellationToken));
                break;
            }
            else if (current?.Parent as VariableDeclarationSyntax != null)
            {
                var grandparent = current?.Parent?.Parent;
                var parent = current.Parent as VariableDeclarationSyntax;
                if (grandparent as LocalDeclarationStatementSyntax != null)
                {
                    discoveredType = ReferenceType.LocalVariable;
                }
                // Ditto for field based things:
                else if (grandparent as BaseFieldDeclarationSyntax != null)
                {
                    if (grandparent as FieldDeclarationSyntax != null)
                    {
                        discoveredType = ReferenceType.Field;
                    }
                    else if (grandparent as EventFieldDeclarationSyntax != null)
                    {
                        discoveredType = ReferenceType.Event;
                    }
                }
                else if (grandparent as ForStatementSyntax != null)
                {
                    discoveredType = ReferenceType.ForVariable;
                }
                else if (grandparent as FixedStatementSyntax != null)
                {
                    discoveredType = ReferenceType.FixedVariable;
                }
                else if (grandparent as UsingStatementSyntax != null)
                {
                    discoveredType = ReferenceType.UsingVariable;
                }
                foreach (var variable in parent.Variables)
                {
                    result.Add(CreateReferenceSymbolLocation(reference, semanticModel, variable, discoveredType, cancellationToken));
                }
                break;
            }
            else if (current as InvocationExpressionSyntax != null)
            {
                discoveredType = ReferenceType.MethodInvocation;
                continue;
            }
            else if (current as ObjectCreationExpressionSyntax != null)
            {
                // This covers constructing a class directly 'new XYZ()'
                // and 'new Action<XYZ>()':
                discoveredType = ReferenceType.ObjectCreation;
                continue;
            }
            else if (current as MethodDeclarationSyntax != null)
            {
                result.Add(CreateReferenceSymbolLocation(reference, semanticModel, current, discoveredType, cancellationToken));
                break;
            }
        }
        return result;
    }
    
    private static ReferencedSymbolLocation CreateReferenceSymbolLocation(
        ReferenceLocation reference,
        SemanticModel semanticModel,
        SyntaxNode node,
        ReferenceType referenceType,
        CancellationToken cancellationToken)
    {
        return new ReferencedSymbolLocation(reference, semanticModel.GetDeclaredSymbol(node, cancellationToken), referenceType);
    }
    
    public enum ReferenceType
    {
        None = 0,
        /// <summary>
        /// Used for ReferenceSymbolLocations where the context of the reference
        /// isn't yet in this enumeration. ReferenceSymbolLocation.ReferencedSymbol will point at the
        /// declaration that contains the ReferenceLocation.
        /// </summary>
        Other,
        Class,
        BaseClass,
        Field,
        Property,
        Parameter,
        Event,
        LocalVariable,
        ForVariable,
        FixedVariable,
        UsingVariable,
        // The following are related to references found inside of statements:
        MethodInvocation,
        ObjectCreation,
    }
    
    public class ReferencedSymbolLocation
    {
        public ReferenceLocation ReferenceLocation { get; private set; }
        public ISymbol ReferencedSymbol { get; private set; }
        public ReferenceType ReferenceType { get; private set; }
    
        internal ReferencedSymbolLocation(ReferenceLocation location, ISymbol referencedSymbol, ReferenceType referenceType)
        {
            ReferenceLocation = location;
            ReferencedSymbol = referencedSymbol;
            ReferenceType = referenceType;
        }
    }
    


来源:https://stackoverflow.com/questions/35918908/symbol-from-referencelocation

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!