来自参考位置的符号



我正在尝试使用 Roslyn 编写一些代码,以验证一些架构分层约束以帮助一些程序集整合。

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

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

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

这是一种耻辱,因为这似乎是尝试SemanticModelExtensions.GetSymbols()内部扩展方法使用的方法的限制因素。

我错过了一些直截了当的东西吗?我当然希望如此。

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

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

只允许在 X.Y.Z 命名空间中使用"X.Y.Z.Internal"类型。这更难。如果可以选择仅在VS内部工作,则可以使用FindReferencesAsync找到上述每种类型符号的所有引用。您所指的链接有很好的建议。您可以在此处获取封闭符号。然后,您需要查找方法/属性/字段是否位于适当命名的命名空间中。所以你必须在ContainingTypeContainingNamespace上向上走符号声明树。

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

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

以下解决方案涉及:

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

  • 使用FindNodeReferenceLocation转换为SyntaxNode

  • 根据检索到的SyntaxNode相对于其父节点的位置,进行一些调整,直到我们找到正确的SyntaxNode,该将从 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;
        }
    }
    

最新更新