查找方法中使用了哪些 using 指令
Find which using directives are used in method
我如何知道在给定 SyntaxNode 的后代中使用了哪些 using 指令。
请看下面的例子:
https://dotnetfiddle.net/mCzNST
这里我想知道Class1中使用了哪些usings,忽略了Class2中使用的usings
您可以使用语义模型获取与语法节点相关的类型。类型信息知道相关的命名空间,您可以使用它来识别相关的使用。当遍历所有节点时,您会收到方法的 return 值以及变量、属性和字段的类型。如果将节点限制为特定类型(例如 InvocationExpressionSyntax),则只能获得 return 类型、变量类型等
private static IEnumerable<string> FindUsedUsings(SemanticModel model, SyntaxNode node)
{
var resultList = new List<string>();
foreach (var identifier in node.DescendantNodesAndSelf())
{
var typeInfo = model.GetTypeInfo(identifier);
if (typeInfo.Type != null)
{
resultList.Add(typeInfo.Type.ContainingNamespace.ToDisplayString());
}
}
return resultList.Distinct().ToList();
}
但是,如果您希望获得用法,则必须识别所有已声明的类型。我写了一个(不完整的)解决方案来确定三种类型的必需用法:
using System;
using Text = System.String;
using static System.Console;
您可以使用不同的逻辑来识别每种类型的使用。
对于第一种使用方式,您应该考虑:
- var 不需要使用
- PredefinedTypes (string, int) 不需要使用
- 动态不需要使用
- QualifiedTypeNames 不需要使用
对于第二种使用方式,您应该考虑:
- 虽然可以使用别名解析类型,但您也可以使用原始名称。使用上面的示例,您可以编写语句字符串 sample = Text.Empty;
对于第三种类型的使用,您没有该类型的标识符,因此您需要使用表达式语句查找调用。
请注意,在下面的解决方案中,using static MyNamespace.Classname 将无法正确解析,因为我没有为该方法的 IdentifierNameSyntax 提供 TypeSymbol,因此我无法解析类型。
这可以解决两个问题:
- 如果类型未解析,则在分析 class MyNamespace.Class2
时可能会缺少 using static MyNamespace.Classname
- 如果解析了类型,则在分析 class MyNamespace.Classname 时可能会出现无效的 using static MyNamespace.Classname (因为 class 中不需要类型名称本身。
考虑到这一点,这是我提出的解决方案。可能还有其他特殊情况需要考虑,但我认为这是一个很好的起点:
private static IEnumerable<string> FindUsedUsings(SemanticModel model,
SyntaxNode node, SyntaxNode root)
{
var aliasResolution = new Dictionary<string, string>();
var usings = root.DescendantNodes().OfType<UsingDirectiveSyntax>();
foreach (var curr in usings)
{
var nameEquals = curr.DescendantNodes().
OfType<NameEqualsSyntax>().FirstOrDefault();
if (nameEquals != null)
{
var qualifiedName =
curr.DescendantNodes().OfType<QualifiedNameSyntax>().
FirstOrDefault()?.ToFullString();
if (qualifiedName != null)
{
aliasResolution.Add(nameEquals.Name.Identifier.Text, qualifiedName);
}
}
}
var currentNamespace = node.Ancestors().
OfType<NamespaceDeclarationSyntax>().FirstOrDefault();
var namespacename = currentNamespace?.Name.ToString();
if (namespacename == null)
{
// Empty namespace
namespacename = string.Empty;
}
var resultList = new List<string>();
foreach (var identifier in node.DescendantNodesAndSelf().OfType<TypeSyntax>())
{
if (identifier is PredefinedTypeSyntax || identifier.IsVar)
{
// No usings required for predefined types or var...
// [string, int, char, var, etc. do not need usings]
continue;
}
// If an alias is defined use it prioritized
if (GetUsingFromAlias(model, identifier, aliasResolution, resultList))
{
continue;
}
// If a type is provided, try to resolve it
if (GetUsingFromType(model, identifier, namespacename, resultList))
{
continue;
}
// If no type is provided check if the expression
// corresponds to a static member call
GetUsingFromStatic(model, identifier, resultList);
}
return resultList.Distinct().ToList();
}
private static void GetUsingFromStatic(SemanticModel model, TypeSyntax identifier,
List<string> resultList)
{
var symbol = model.GetSymbolInfo(identifier).Symbol;
// If the symbol (field, property, method call) can be resolved,
// is static and has a containing type
if (symbol != null && symbol.IsStatic && symbol.ContainingType != null)
{
var memberAccess = identifier.Parent as ExpressionSyntax;
if (memberAccess != null)
{
bool hasCallingType = false;
var children = memberAccess.ChildNodes();
foreach (var childNode in children)
{
// If the Expression has a Type
// (that is, if the expression is called from an identifyable source)
// no using static is required
var typeInfo = model.GetSymbolInfo(childNode).Symbol as INamedTypeSymbol;
if (typeInfo != null)
{
hasCallingType = true;
break;
}
}
// if the type-Info is missing a static using is required
if (!hasCallingType)
{
// type three using: using static [QualifiedType]
resultList.Add($"static {symbol.ContainingType}");
}
}
}
}
private static bool GetUsingFromType(SemanticModel model, TypeSyntax identifier,
string namespacename, List<string> resultList)
{
var typeInfo = model.GetSymbolInfo(identifier).Symbol as INamedTypeSymbol;
// dynamic is not required and not provided as an INamedTypeSymbol
if (typeInfo != null)
{
if (identifier is QualifiedNameSyntax
|| identifier.Parent is QualifiedNameSyntax)
{
// Qualified namespaces can be ignored...
return true;
}
var containingNamespace = typeInfo.ContainingNamespace.ToDisplayString();
// The current namespace need not be referenced
if (namespacename == containingNamespace)
{
return true;
}
// Type one using: using [Namespace];
resultList.Add(typeInfo.ContainingNamespace.ToDisplayString());
return true;
}
return false;
}
private static bool GetUsingFromAlias(SemanticModel model, TypeSyntax identifier,
Dictionary<string, string> aliasResolution, List<string> resultList)
{
var aliasInfo = model.GetAliasInfo(identifier);
// If the identifier is an alias
if (aliasInfo != null)
{
// check if it can be resolved
if (aliasResolution.ContainsKey(aliasInfo.Name))
{
// Type two using: using [Alias] = [QualifiedType];
resultList.Add($"{aliasInfo.Name} = {aliasResolution[aliasInfo.Name]}");
return true;
}
}
return false;
}
我如何知道在给定 SyntaxNode 的后代中使用了哪些 using 指令。
请看下面的例子: https://dotnetfiddle.net/mCzNST 这里我想知道Class1中使用了哪些usings,忽略了Class2中使用的usings
您可以使用语义模型获取与语法节点相关的类型。类型信息知道相关的命名空间,您可以使用它来识别相关的使用。当遍历所有节点时,您会收到方法的 return 值以及变量、属性和字段的类型。如果将节点限制为特定类型(例如 InvocationExpressionSyntax),则只能获得 return 类型、变量类型等
private static IEnumerable<string> FindUsedUsings(SemanticModel model, SyntaxNode node)
{
var resultList = new List<string>();
foreach (var identifier in node.DescendantNodesAndSelf())
{
var typeInfo = model.GetTypeInfo(identifier);
if (typeInfo.Type != null)
{
resultList.Add(typeInfo.Type.ContainingNamespace.ToDisplayString());
}
}
return resultList.Distinct().ToList();
}
但是,如果您希望获得用法,则必须识别所有已声明的类型。我写了一个(不完整的)解决方案来确定三种类型的必需用法:
using System;
using Text = System.String;
using static System.Console;
您可以使用不同的逻辑来识别每种类型的使用。
对于第一种使用方式,您应该考虑:
- var 不需要使用
- PredefinedTypes (string, int) 不需要使用
- 动态不需要使用
- QualifiedTypeNames 不需要使用
对于第二种使用方式,您应该考虑:
- 虽然可以使用别名解析类型,但您也可以使用原始名称。使用上面的示例,您可以编写语句字符串 sample = Text.Empty;
对于第三种类型的使用,您没有该类型的标识符,因此您需要使用表达式语句查找调用。 请注意,在下面的解决方案中,using static MyNamespace.Classname 将无法正确解析,因为我没有为该方法的 IdentifierNameSyntax 提供 TypeSymbol,因此我无法解析类型。
这可以解决两个问题:
- 如果类型未解析,则在分析 class MyNamespace.Class2 时可能会缺少 using static MyNamespace.Classname
- 如果解析了类型,则在分析 class MyNamespace.Classname 时可能会出现无效的 using static MyNamespace.Classname (因为 class 中不需要类型名称本身。
考虑到这一点,这是我提出的解决方案。可能还有其他特殊情况需要考虑,但我认为这是一个很好的起点:
private static IEnumerable<string> FindUsedUsings(SemanticModel model,
SyntaxNode node, SyntaxNode root)
{
var aliasResolution = new Dictionary<string, string>();
var usings = root.DescendantNodes().OfType<UsingDirectiveSyntax>();
foreach (var curr in usings)
{
var nameEquals = curr.DescendantNodes().
OfType<NameEqualsSyntax>().FirstOrDefault();
if (nameEquals != null)
{
var qualifiedName =
curr.DescendantNodes().OfType<QualifiedNameSyntax>().
FirstOrDefault()?.ToFullString();
if (qualifiedName != null)
{
aliasResolution.Add(nameEquals.Name.Identifier.Text, qualifiedName);
}
}
}
var currentNamespace = node.Ancestors().
OfType<NamespaceDeclarationSyntax>().FirstOrDefault();
var namespacename = currentNamespace?.Name.ToString();
if (namespacename == null)
{
// Empty namespace
namespacename = string.Empty;
}
var resultList = new List<string>();
foreach (var identifier in node.DescendantNodesAndSelf().OfType<TypeSyntax>())
{
if (identifier is PredefinedTypeSyntax || identifier.IsVar)
{
// No usings required for predefined types or var...
// [string, int, char, var, etc. do not need usings]
continue;
}
// If an alias is defined use it prioritized
if (GetUsingFromAlias(model, identifier, aliasResolution, resultList))
{
continue;
}
// If a type is provided, try to resolve it
if (GetUsingFromType(model, identifier, namespacename, resultList))
{
continue;
}
// If no type is provided check if the expression
// corresponds to a static member call
GetUsingFromStatic(model, identifier, resultList);
}
return resultList.Distinct().ToList();
}
private static void GetUsingFromStatic(SemanticModel model, TypeSyntax identifier,
List<string> resultList)
{
var symbol = model.GetSymbolInfo(identifier).Symbol;
// If the symbol (field, property, method call) can be resolved,
// is static and has a containing type
if (symbol != null && symbol.IsStatic && symbol.ContainingType != null)
{
var memberAccess = identifier.Parent as ExpressionSyntax;
if (memberAccess != null)
{
bool hasCallingType = false;
var children = memberAccess.ChildNodes();
foreach (var childNode in children)
{
// If the Expression has a Type
// (that is, if the expression is called from an identifyable source)
// no using static is required
var typeInfo = model.GetSymbolInfo(childNode).Symbol as INamedTypeSymbol;
if (typeInfo != null)
{
hasCallingType = true;
break;
}
}
// if the type-Info is missing a static using is required
if (!hasCallingType)
{
// type three using: using static [QualifiedType]
resultList.Add($"static {symbol.ContainingType}");
}
}
}
}
private static bool GetUsingFromType(SemanticModel model, TypeSyntax identifier,
string namespacename, List<string> resultList)
{
var typeInfo = model.GetSymbolInfo(identifier).Symbol as INamedTypeSymbol;
// dynamic is not required and not provided as an INamedTypeSymbol
if (typeInfo != null)
{
if (identifier is QualifiedNameSyntax
|| identifier.Parent is QualifiedNameSyntax)
{
// Qualified namespaces can be ignored...
return true;
}
var containingNamespace = typeInfo.ContainingNamespace.ToDisplayString();
// The current namespace need not be referenced
if (namespacename == containingNamespace)
{
return true;
}
// Type one using: using [Namespace];
resultList.Add(typeInfo.ContainingNamespace.ToDisplayString());
return true;
}
return false;
}
private static bool GetUsingFromAlias(SemanticModel model, TypeSyntax identifier,
Dictionary<string, string> aliasResolution, List<string> resultList)
{
var aliasInfo = model.GetAliasInfo(identifier);
// If the identifier is an alias
if (aliasInfo != null)
{
// check if it can be resolved
if (aliasResolution.ContainsKey(aliasInfo.Name))
{
// Type two using: using [Alias] = [QualifiedType];
resultList.Add($"{aliasInfo.Name} = {aliasResolution[aliasInfo.Name]}");
return true;
}
}
return false;
}