找出在方法中使用哪些using指令

时间:2016-08-11 14:24:31

标签: c# roslyn roslyn-code-analysis

我如何知道在给定的SyntaxNode的后代中使用了哪些using指令。

请参阅以下示例: https://dotnetfiddle.net/mCzNST 在这里,我想知道在Class1中使用了哪些用法,但忽略了Class2中使用的用法。

1 个答案:

答案 0 :(得分:3)

您可以使用语义模型获取与语法节点相关的类型。类型信息知道相关的命名空间,您可以使用它来标识相关的使用。迭代所有节点时,您会收到方法的返回值以及变量,属性和字段的类型。如果将节点限制为特定类型(例如InvocationExpressionSyntax),则只能获得返回类型,变量类型等。

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不需要使用

对于第二种类型的使用,您应该考虑:

  • 虽然可以使用别名解析类型,但您也可以使用原始名称。使用上面的示例,您可以编写Statement字符串sample = Text.Empty;

对于第三种类型的使用,您没有该类型的标识符,因此您需要使用表达式语句查找调用。 请注意,在下面的解决方案中,使用静态MyNamespace.Classname将无法正确解析,因为我没有为方法的IdentifierNameSyntax提供TypeSymbol,因此无法解析该类型。

这可以解决两个问题:

  • 如果未解析类型,则在分析类MyNamespace.Class2时可能会丢失使用静态MyNamespace.Classname
  • 如果解析了类型,则在分析类MyNamespace.Classname时可能会出现使用静态MyNamespace.Classname的无效(因为类本身不需要类型名称。

考虑到这一点,我想出了解决方案。可能还有其他特殊情况要考虑,但我认为这是一个很好的起点:

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