来自 ReferenceLocation 的符号?

Symbol from ReferenceLocation?

我正在尝试使用 Roslyn 编写一些代码来验证一些体系结构分层约束以帮助进行一些程序集整合。

即: * 内部类型,必须位于以 .Internal 为后缀的命名空间中。 * 仅允许在 X.Y.Z 命名空间中使用 'X.Y.Z.Internal' 类型。

解决此问题的最简单方法是根据 FAQ(9) 通过枚举查找所有内部类型,然后利用 SymbolFinder 查找所有引用并检查引用站点中的包含类型。如 Get Symbol for ReferenceLocation 中所述,GetEnclosingSymbol() 并不是真正 return 最有用的数据。

看起来可能有一种有用的方法利用 SymbolFinder.FindSymbolAtPosition,但遗憾的是,ISyntaxFactsService 和 ISemanticModelFactsService 似乎是内部的。

这很遗憾,因为这似乎是尝试 SemanticModelExtensions.GetSymbols() 内部扩展方法使用的方法的限制因素。

我是不是漏掉了什么直截了当的东西?我当然希望如此。

SymbolFinder 需要一个 Workspace,当 运行 在 MsBuild 中时没有。所以基本上如果你想使用 SymbolFinder 你必须在 Visual Studio 里面而不是在 CI 工作中。

内部类型,必须在 .Internal 后缀的命名空间中。 这可以通过在 NamedTypes 上注册一个符号动作来轻松解决。在你的回调中你会得到一个 INamedSymbol,你必须检查它是否是 internal 以及它是否在一个正确命名的命名空间内。 (请注意,您可能需要考虑如何处理嵌套类型。)

只能在 X.Y.Z 命名空间中使用 'X.Y.Z.Internal' 类型。 这更难。如果仅在 VS 内部工作是一个选项,那么您可以使用 FindReferencesAsync. The link you're referring to has good suggestions. You can get the enclosing symbol as done here 找到上述每个类型符号的所有引用。然后您需要查找 method/property/field 是否在适当命名的命名空间中。所以你必须在 ContainingTypeContainingNamespace.

上向上遍历符号声明树

我猜您希望 运行 此分析作为 CI 工作的一部分,因此此解决方案不符合您的需要。在这种情况下,您可以为所有标识符注册一个 SyntaxNode 操作,获取基础 ISymbol 和封闭符号。然后你和以前一样。所以你可以检查名称。 (此解决方案可能会很慢,您必须检查性能是否满足您的需求。)

TL;DR:我想我找到了一种可行的方法,尽管我不确定性能是否正常。

以下解决方案涉及:

  • 从上面链接的 ReferenceLocatoinExtensions.cs 模式的 AddSymbolsAsync 方法内部调用 GetReferenceSymbolLocationsAsync

  • 使用FindNodeReferenceLocation转换为SyntaxNode

  • 根据检索到的 SyntaxNode 相对于其父节点的位置,做一些调整,直到我们找到正确的 SyntaxNode 将 return 一个非- 来自 SemanticModel.GetDeclaredSymbol()

  • 的空值
  • 做一些额外的簿记,以便能够在更高级别的代码中自定义规则。 (额外的簿记类型在最后。)

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