File: Utilities\SyntaxNodeExtensions.cs
Web Access
Project: src\src\Analyzers\Microsoft.Analyzers.Extra\Microsoft.Analyzers.Extra.csproj (Microsoft.Analyzers.Extra)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
 
namespace Microsoft.Extensions.ExtraAnalyzers.Utilities;
 
/// <summary>
/// Class contains <see cref="SyntaxNode"/> extensions.
/// </summary>
internal static class SyntaxNodeExtensions
{
    /// <summary>
    /// Finds the closest ancestor by syntax kind.
    /// </summary>
    /// <param name="node">The start node.</param>
    /// <param name="kind">The kind to search by.</param>
    /// <returns>The found node or <see langword="null" />.</returns>
    public static SyntaxNode? GetFirstAncestorOfSyntaxKind(this SyntaxNode node, SyntaxKind kind)
    {
        var n = node.Parent;
        while (n != null && !n.IsKind(kind))
        {
            n = n.Parent;
        }
 
        return n;
    }
 
    /// <summary>
    /// Checks node is invocation expression with specified name.
    /// </summary>
    /// <param name="node">Node to check.</param>
    /// <param name="semanticModel">Semantic model.</param>
    /// <param name="expectedFullMethodNames">Expected full method names.</param>
    /// <returns>Check result.</returns>
    public static bool NodeHasSpecifiedMethod(
        this SyntaxNode? node,
        SemanticModel semanticModel,
        ICollection<string> expectedFullMethodNames)
    {
        if (node is InvocationExpressionSyntax invocationExpression)
        {
            var memberSymbol = semanticModel.GetSymbolInfo(invocationExpression.Expression).Symbol as IMethodSymbol;
            if (memberSymbol == null)
            {
                return false;
            }
 
            var result = false;
            if (memberSymbol.ReducedFrom != null)
            {
                var fullMethodName = memberSymbol.ReducedFrom.ToString();
                result = expectedFullMethodNames.Contains(fullMethodName);
            }
 
            if (!result)
            {
                var fullMethodName = memberSymbol.OriginalDefinition.ToString();
                return expectedFullMethodNames.Contains(fullMethodName);
            }
 
            return result;
        }
 
        return false;
    }
 
    /// <summary>
    /// Returns invocation expression name.
    /// </summary>
    /// <param name="invocationExpression">The invocation expression.</param>
    /// <returns>The expression syntax name.</returns>
    public static SimpleNameSyntax? GetExpressionName(this InvocationExpressionSyntax invocationExpression)
    {
        if (invocationExpression.Expression is MemberAccessExpressionSyntax memberExpression)
        {
            return memberExpression.Name;
        }
 
        if (invocationExpression.Expression is MemberBindingExpressionSyntax memberBindingExpression)
        {
            return memberBindingExpression.Name;
        }
 
        return null;
    }
 
    /// <summary>
    /// Looks for a invocation node in a tree with a specified root type.
    /// </summary>
    /// <param name="nodeToStart">Node to start traversing.</param>
    /// <param name="semanticModel">The semantic model.</param>
    /// <param name="expectedFullMethodNames">Expected full method names.</param>
    /// <param name="typesToStopTraversing">Root node types.</param>
    /// <returns>Found invocation node or <see langword="null" />.</returns>
    public static SyntaxNode? FindNodeInTreeUpToSpecifiedParentByMethodName(
        this SyntaxNode nodeToStart,
        SemanticModel semanticModel,
        ICollection<string> expectedFullMethodNames,
        ICollection<Type> typesToStopTraversing)
    {
        var currentNode = nodeToStart;
        do
        {
            var foundNode = currentNode.DescendantNodesAndSelf()
                .FirstOrDefault(n => n.NodeHasSpecifiedMethod(semanticModel, expectedFullMethodNames));
            if (foundNode != null)
            {
                return foundNode;
            }
 
            currentNode = currentNode.Parent;
        }
        while (currentNode != null && !typesToStopTraversing.Contains(currentNode.GetType()));
 
        return currentNode?
                .DescendantNodesAndSelf()
                .FirstOrDefault(n => n.NodeHasSpecifiedMethod(semanticModel, expectedFullMethodNames));
    }
 
    /// <summary>
    /// Checks <see cref="IdentifierNameSyntax"/> has expected name.
    /// </summary>
    /// <param name="expression">Expression syntax to check.</param>
    /// <param name="expectedName">Expected name.</param>
    /// <returns><see langword="true" /> if the identifier text is equal to expected name; otherwise, <see langword="false" />.</returns>
    public static bool IdentifierNameEquals(this ExpressionSyntax expression, string expectedName)
    {
        return expression is IdentifierNameSyntax id && id.Identifier.Text == expectedName;
    }
}