我正在尝试使用 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
找到上述每种类型符号的所有引用。您所指的链接有很好的建议。您可以在此处获取封闭符号。然后,您需要查找方法/属性/字段是否位于适当命名的命名空间中。所以你必须在ContainingType
和ContainingNamespace
上向上走符号声明树。
我猜您想将此分析作为 CI 作业的一部分运行,因此此解决方案不符合您的需求。在这种情况下,您可以为所有标识符注册SyntaxNode
操作,获取基础ISymbol
和封闭符号。然后你和以前一样。因此,您可以检查名称。(此解决方案可能很慢,您必须检查性能是否适合您的需求。
TL;DR:我想我已经找到了一种有效的方法,尽管我不确定性能是否正常。
以下解决方案涉及:
-
从上面链接到的
ReferenceLocatoinExtensions.cs
模式从AddSymbolsAsync
方法内部调用GetReferenceSymbolLocationsAsync
。 -
使用
FindNode
将ReferenceLocation
转换为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; } }