File: Microsoft.NetCore.Analyzers\Runtime\CSharpDetectPreviewFeatureAnalyzer.cs
Web Access
Project: ..\..\..\src\Microsoft.CodeAnalysis.NetAnalyzers\src\Microsoft.CodeAnalysis.CSharp.NetAnalyzers\Microsoft.CodeAnalysis.CSharp.NetAnalyzers.csproj (Microsoft.CodeAnalysis.CSharp.NetAnalyzers)
// Copyright (c) Microsoft.  All Rights Reserved.  Licensed under the MIT license.  See License.txt in the project root for license information.
 
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.NetCore.Analyzers.Runtime;
 
namespace Microsoft.NetCore.CSharp.Analyzers.Runtime
{
    [DiagnosticAnalyzer(LanguageNames.CSharp)]
    public class CSharpDetectPreviewFeatureAnalyzer : DetectPreviewFeatureAnalyzer
    {
        protected override SyntaxNode? GetPreviewSyntaxNodeForFieldsOrEvents(ISymbol fieldOrEventSymbol, ISymbol previewSymbol)
        {
            ImmutableArray<SyntaxReference> fieldOrEventReferences = fieldOrEventSymbol.DeclaringSyntaxReferences;
 
            foreach (SyntaxReference? fieldOrEventReference in fieldOrEventReferences)
            {
                SyntaxNode? definition = fieldOrEventReference.GetSyntax();
 
                while (definition is VariableDeclaratorSyntax)
                {
                    definition = definition.Parent;
                }
 
                if (definition is VariableDeclarationSyntax fieldDeclaration)
                {
                    TypeSyntax parameterType = fieldDeclaration.Type;
                    parameterType = GetElementTypeForNullableAndArrayTypeNodes(parameterType);
 
                    if (IsIdentifierNameSyntax(parameterType, previewSymbol))
                    {
                        return parameterType;
                    }
                    else if (parameterType is GenericNameSyntax genericName)
                    {
                        if (TryMatchGenericSyntaxNodeWithGivenSymbol(genericName, previewSymbol, out SyntaxNode? previewNode))
                        {
                            return previewNode;
                        }
                    }
                }
            }
 
            return null;
        }
 
        private static TypeSyntax GetElementTypeForNullableAndArrayTypeNodes(TypeSyntax parameterType)
        {
            while (parameterType is NullableTypeSyntax nullable)
            {
                parameterType = nullable.ElementType;
            }
 
            while (parameterType is ArrayTypeSyntax arrayType)
            {
                parameterType = arrayType.ElementType;
            }
 
            return parameterType;
        }
 
        protected override SyntaxNode? GetPreviewParameterSyntaxNodeForMethod(IMethodSymbol methodSymbol, ISymbol parameterSymbol)
        {
            ImmutableArray<SyntaxReference> methodSymbolDeclaringReferences = methodSymbol.DeclaringSyntaxReferences;
 
            foreach (SyntaxReference? syntaxReference in methodSymbolDeclaringReferences)
            {
                SyntaxNode methodDefinition = syntaxReference.GetSyntax();
                if (methodDefinition is MethodDeclarationSyntax methodDeclaration)
                {
                    ParameterListSyntax? parameters = methodDeclaration.ParameterList;
                    foreach (ParameterSyntax? parameter in parameters.Parameters)
                    {
                        TypeSyntax parameterType = parameter.Type!;
                        parameterType = GetElementTypeForNullableAndArrayTypeNodes(parameterType);
 
                        if (IsIdentifierNameSyntax(parameterType, parameterSymbol))
                        {
                            return parameterType;
                        }
                        else if (parameterType is GenericNameSyntax genericName)
                        {
                            if (TryMatchGenericSyntaxNodeWithGivenSymbol(genericName, parameterSymbol, out SyntaxNode? previewNode))
                            {
                                return previewNode;
                            }
                        }
                    }
                }
            }
 
            return null;
        }
 
        // Handles both generic and non-generic return types
        protected override SyntaxNode? GetPreviewReturnTypeSyntaxNodeForMethodOrProperty(ISymbol methodOrPropertySymbol, ISymbol previewReturnTypeSymbol)
        {
            ImmutableArray<SyntaxReference> methodOrPropertySymbolDeclaringReferences = methodOrPropertySymbol.DeclaringSyntaxReferences;
 
            foreach (SyntaxReference? syntaxReference in methodOrPropertySymbolDeclaringReferences)
            {
                SyntaxNode methodOrPropertyDefinition = syntaxReference.GetSyntax();
                if (methodOrPropertyDefinition is PropertyDeclarationSyntax propertyDeclaration)
                {
                    TypeSyntax returnType = propertyDeclaration.Type;
                    returnType = GetElementTypeForNullableAndArrayTypeNodes(returnType);
                    if (IsIdentifierNameSyntax(returnType, previewReturnTypeSymbol))
                    {
                        return returnType;
                    }
                    else if (returnType is GenericNameSyntax genericName)
                    {
                        if (TryMatchGenericSyntaxNodeWithGivenSymbol(genericName, previewReturnTypeSymbol, out SyntaxNode? previewNode))
                        {
                            return previewNode;
                        }
                    }
                }
                else if (methodOrPropertyDefinition is MethodDeclarationSyntax methodDeclaration)
                {
                    TypeSyntax returnType = methodDeclaration.ReturnType;
                    returnType = GetElementTypeForNullableAndArrayTypeNodes(returnType);
                    if (IsIdentifierNameSyntax(returnType, previewReturnTypeSymbol))
                    {
                        return returnType;
                    }
                    else if (returnType is GenericNameSyntax genericName)
                    {
                        if (TryMatchGenericSyntaxNodeWithGivenSymbol(genericName, previewReturnTypeSymbol, out SyntaxNode? previewNode))
                        {
                            return previewNode;
                        }
                    }
                }
            }
 
            return null;
        }
 
        private static bool TryMatchGenericSyntaxNodeWithGivenSymbol(GenericNameSyntax genericName, ISymbol previewReturnTypeSymbol, [NotNullWhen(true)] out SyntaxNode? syntaxNode)
        {
            if (IsSyntaxToken(genericName.Identifier, previewReturnTypeSymbol))
            {
                syntaxNode = genericName;
                return true;
            }
 
            TypeArgumentListSyntax typeArgumentList = genericName.TypeArgumentList;
            foreach (TypeSyntax typeArgument in typeArgumentList.Arguments)
            {
                TypeSyntax typeArgumentElementType = GetElementTypeForNullableAndArrayTypeNodes(typeArgument);
                if (typeArgumentElementType is GenericNameSyntax innerGenericName)
                {
                    if (TryMatchGenericSyntaxNodeWithGivenSymbol(innerGenericName, previewReturnTypeSymbol, out syntaxNode))
                    {
                        return true;
                    }
                }
 
                if (IsIdentifierNameSyntax(typeArgumentElementType, previewReturnTypeSymbol))
                {
                    syntaxNode = typeArgumentElementType;
                    return true;
                }
            }
 
            syntaxNode = null;
            return false;
        }
 
        protected override SyntaxNode? GetConstraintSyntaxNodeForTypeConstrainedByPreviewTypes(ISymbol typeOrMethodSymbol, ISymbol previewInterfaceConstraintSymbol)
        {
            ImmutableArray<SyntaxReference> typeSymbolDeclaringReferences = typeOrMethodSymbol.DeclaringSyntaxReferences;
 
            foreach (SyntaxReference? syntaxReference in typeSymbolDeclaringReferences)
            {
                SyntaxNode typeOrMethodDefinition = syntaxReference.GetSyntax();
                if (typeOrMethodDefinition is TypeDeclarationSyntax typeDeclaration)
                {
                    // For ex: class A<T> where T : IFoo, new() // where IFoo is preview
                    SyntaxList<TypeParameterConstraintClauseSyntax> constraintClauses = typeDeclaration.ConstraintClauses;
                    if (TryGetConstraintClauseNode(constraintClauses, previewInterfaceConstraintSymbol, out SyntaxNode? ret))
                    {
                        return ret;
                    }
                }
                else if (typeOrMethodDefinition is MethodDeclarationSyntax methodDeclaration)
                {
                    SyntaxList<TypeParameterConstraintClauseSyntax> constraintClauses = methodDeclaration.ConstraintClauses;
                    if (TryGetConstraintClauseNode(constraintClauses, previewInterfaceConstraintSymbol, out SyntaxNode? ret))
                    {
                        return ret;
                    }
                }
            }
 
            return null;
        }
 
        private static bool TryGetConstraintClauseNode(SyntaxList<TypeParameterConstraintClauseSyntax> constraintClauses, ISymbol previewInterfaceConstraintSymbol, [NotNullWhen(true)] out SyntaxNode? syntaxNode)
        {
            foreach (TypeParameterConstraintClauseSyntax constraintClause in constraintClauses)
            {
                SeparatedSyntaxList<TypeParameterConstraintSyntax> constraints = constraintClause.Constraints;
                foreach (TypeParameterConstraintSyntax? constraint in constraints)
                {
                    if (constraint is TypeConstraintSyntax typeConstraintSyntax)
                    {
                        TypeSyntax typeConstraintSyntaxType = typeConstraintSyntax.Type;
                        typeConstraintSyntaxType = GetElementTypeForNullableAndArrayTypeNodes(typeConstraintSyntaxType);
                        if (typeConstraintSyntaxType is GenericNameSyntax generic)
                        {
                            if (TryMatchGenericSyntaxNodeWithGivenSymbol(generic, previewInterfaceConstraintSymbol, out SyntaxNode? previewConstraint))
                            {
                                syntaxNode = previewConstraint;
                                return true;
                            }
                        }
 
                        if (IsIdentifierNameSyntax(typeConstraintSyntaxType, previewInterfaceConstraintSymbol))
                        {
                            syntaxNode = constraint;
                            return true;
                        }
                    }
                }
            }
 
            syntaxNode = null;
            return false;
        }
 
        protected override SyntaxNode? GetPreviewInterfaceNodeForTypeImplementingPreviewInterface(ISymbol typeSymbol, ISymbol previewInterfaceSymbol)
        {
            SyntaxNode? ret = null;
            ImmutableArray<SyntaxReference> typeSymbolDeclaringReferences = typeSymbol.DeclaringSyntaxReferences;
 
            foreach (SyntaxReference? syntaxReference in typeSymbolDeclaringReferences)
            {
                SyntaxNode typeSymbolDefinition = syntaxReference.GetSyntax();
                if (typeSymbolDefinition is TypeDeclarationSyntax { BaseList.Types: var baseListTypes })
                {
                    if (TryGetPreviewInterfaceNodeForTypeImplementingPreviewInterface(baseListTypes, previewInterfaceSymbol, out ret))
                    {
                        return ret;
                    }
                }
            }
 
            return ret;
        }
 
        private static bool TryGetPreviewInterfaceNodeForTypeImplementingPreviewInterface(SeparatedSyntaxList<BaseTypeSyntax> baseListTypes, ISymbol previewInterfaceSymbol, out SyntaxNode? previewInterfaceNode)
        {
            foreach (BaseTypeSyntax baseTypeSyntax in baseListTypes)
            {
                if (baseTypeSyntax is BaseTypeSyntax simpleBaseTypeSyntax)
                {
                    TypeSyntax type = simpleBaseTypeSyntax.Type;
                    if (type is IdentifierNameSyntax identifier && IsSyntaxToken(identifier.Identifier, previewInterfaceSymbol))
                    {
                        previewInterfaceNode = simpleBaseTypeSyntax;
                        return true;
                    }
 
                    if (type is GenericNameSyntax generic)
                    {
                        if (TryMatchGenericSyntaxNodeWithGivenSymbol(generic, previewInterfaceSymbol, out SyntaxNode? previewConstraint))
                        {
                            previewInterfaceNode = previewConstraint;
                            return true;
                        }
                    }
                }
            }
 
            previewInterfaceNode = null;
            return false;
        }
 
        private static bool IsSyntaxToken(SyntaxToken identifier, ISymbol previewInterfaceSymbol) => identifier.ValueText == previewInterfaceSymbol.Name;
 
        private static bool IsIdentifierNameSyntax(TypeSyntax identifier, ISymbol previewInterfaceSymbol) => identifier is IdentifierNameSyntax identifierName && IsSyntaxToken(identifierName.Identifier, previewInterfaceSymbol) ||
          identifier is NullableTypeSyntax nullable && IsIdentifierNameSyntax(nullable.ElementType, previewInterfaceSymbol);
 
        protected override SyntaxNode? GetPreviewImplementsClauseSyntaxNodeForMethodOrProperty(ISymbol methodOrPropertySymbol, ISymbol previewSymbol)
        {
            throw new System.NotImplementedException();
        }
    }
}