File: GenerateType\CSharpGenerateTypeService.cs
Web Access
Project: src\src\Features\CSharp\Portable\Microsoft.CodeAnalysis.CSharp.Features.csproj (Microsoft.CodeAnalysis.CSharp.Features)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
#nullable disable
 
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Composition;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.AddImport;
using Microsoft.CodeAnalysis.CodeGeneration;
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.GenerateType;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.LanguageService;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;
using Microsoft.CodeAnalysis.Utilities;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.CSharp.GenerateType;
 
[ExportLanguageService(typeof(IGenerateTypeService), LanguageNames.CSharp), Shared]
[method: ImportingConstructor]
[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
internal sealed class CSharpGenerateTypeService() :
    AbstractGenerateTypeService<CSharpGenerateTypeService, SimpleNameSyntax, ObjectCreationExpressionSyntax, ExpressionSyntax, TypeDeclarationSyntax, ArgumentSyntax>
{
    protected override string DefaultFileExtension => ".cs";
 
    protected override ExpressionSyntax GetLeftSideOfDot(SimpleNameSyntax simpleName)
        => simpleName.GetLeftSideOfDot();
 
    protected override bool IsInCatchDeclaration(ExpressionSyntax expression)
        => expression.IsParentKind(SyntaxKind.CatchDeclaration);
 
    protected override bool IsArrayElementType(ExpressionSyntax expression)
    {
        return expression.IsParentKind(SyntaxKind.ArrayType) &&
            expression.Parent.IsParentKind(SyntaxKind.ArrayCreationExpression);
    }
 
    protected override bool IsInValueTypeConstraintContext(
        SemanticModel semanticModel,
        ExpressionSyntax expression,
        CancellationToken cancellationToken)
    {
        if (expression is TypeSyntax typeSyntax && expression.Parent is TypeArgumentListSyntax typeArgumentList)
        {
            var symbolInfo = semanticModel.GetSymbolInfo(typeArgumentList.Parent, cancellationToken);
            var symbol = symbolInfo.GetAnySymbol();
            if (symbol.IsConstructor())
            {
                symbol = symbol.ContainingType;
            }
 
            var parameterIndex = typeArgumentList.Arguments.IndexOf(typeSyntax);
            if (symbol is INamedTypeSymbol type)
            {
                type = type.OriginalDefinition;
                var typeParameter = parameterIndex < type.TypeParameters.Length ? type.TypeParameters[parameterIndex] : null;
                return typeParameter != null && typeParameter.HasValueTypeConstraint;
            }
 
            if (symbol is IMethodSymbol method)
            {
                method = method.OriginalDefinition;
                var typeParameter = parameterIndex < method.TypeParameters.Length ? method.TypeParameters[parameterIndex] : null;
                return typeParameter != null && typeParameter.HasValueTypeConstraint;
            }
        }
 
        return false;
    }
 
    protected override bool IsInInterfaceList(ExpressionSyntax expression)
    {
        if (expression is TypeSyntax { Parent: BaseTypeSyntax { Parent: BaseListSyntax baseList } baseType } &&
            baseType.Type == expression)
        {
            // If it's after the first item, then it's definitely an interface.
            if (baseList.Types[0] != expression.Parent)
                return true;
 
            // If it's in the base list of an interface or struct, then it's definitely an
            // interface.
            return baseList.Parent.Kind() is
                SyntaxKind.InterfaceDeclaration or
                SyntaxKind.StructDeclaration or
                SyntaxKind.RecordStructDeclaration;
        }
 
        if (expression is TypeSyntax { Parent: TypeConstraintSyntax { Parent: TypeParameterConstraintClauseSyntax constraintClause } typeConstraint })
        {
            var index = constraintClause.Constraints.IndexOf(typeConstraint);
 
            // If it's after the first item, then it's definitely an interface.
            return index > 0;
        }
 
        return false;
    }
 
    protected override bool TryGetNameParts(ExpressionSyntax expression, out IList<string> nameParts)
        => expression.TryGetNameParts(out nameParts);
 
    protected override bool TryInitializeState(
        SemanticDocument document,
        SimpleNameSyntax simpleName,
        CancellationToken cancellationToken,
        out GenerateTypeServiceStateOptions generateTypeServiceStateOptions)
    {
        generateTypeServiceStateOptions = new GenerateTypeServiceStateOptions();
 
        if (simpleName.IsVar)
            return false;
 
        if (SyntaxFacts.IsAliasQualifier(simpleName))
            return false;
 
        // Never offer if we're in a using directive, unless its a static using.  The feeling here is that it's highly
        // unlikely that this would be a location where a user would be wanting to generate
        // something.  They're really just trying to reference something that exists but
        // isn't available for some reason (i.e. a missing reference).
        var usingDirectiveSyntax = simpleName.GetAncestorOrThis<UsingDirectiveSyntax>();
        if (usingDirectiveSyntax != null && usingDirectiveSyntax.StaticKeyword.Kind() != SyntaxKind.StaticKeyword)
            return false;
 
        ExpressionSyntax nameOrMemberAccessExpression = null;
        if (simpleName.IsRightSideOfDot())
        {
            // This simplename comes from the cref
            if (simpleName.IsParentKind(SyntaxKind.NameMemberCref))
                return false;
 
            nameOrMemberAccessExpression = generateTypeServiceStateOptions.NameOrMemberAccessExpression = (ExpressionSyntax)simpleName.Parent;
 
            // If we're on the right side of a dot, then the left side better be a name (and
            // not an arbitrary expression).
            var leftSideExpression = simpleName.GetLeftSideOfDot();
            if (leftSideExpression.Kind() is not (
                    SyntaxKind.QualifiedName or
                    SyntaxKind.IdentifierName or
                    SyntaxKind.AliasQualifiedName or
                    SyntaxKind.GenericName or
                    SyntaxKind.SimpleMemberAccessExpression))
            {
                return false;
            }
        }
        else
        {
            nameOrMemberAccessExpression = generateTypeServiceStateOptions.NameOrMemberAccessExpression = simpleName;
        }
 
        // BUG(5712): Don't offer generate type in an enum's base list.
        if (nameOrMemberAccessExpression.Parent is BaseTypeSyntax baseType &&
            nameOrMemberAccessExpression.Parent.IsParentKind(SyntaxKind.BaseList) &&
            baseType.Type == nameOrMemberAccessExpression &&
            nameOrMemberAccessExpression.Parent.Parent.IsParentKind(SyntaxKind.EnumDeclaration))
        {
            return false;
        }
 
        // If we can guarantee it's a type only context, great.  Otherwise, we may not want to
        // provide this here.
        var semanticModel = document.SemanticModel;
        if (!SyntaxFacts.IsInNamespaceOrTypeContext(nameOrMemberAccessExpression))
        {
            // Don't offer Generate Type in an expression context *unless* we're on the left
            // side of a dot.  In that case the user might be making a type that they're
            // accessing a static off of.
            var syntaxTree = semanticModel.SyntaxTree;
            var start = nameOrMemberAccessExpression.SpanStart;
            var tokenOnLeftOfStart = syntaxTree.FindTokenOnLeftOfPosition(start, cancellationToken);
            var isExpressionContext = syntaxTree.IsExpressionContext(start, tokenOnLeftOfStart, attributes: true, cancellationToken: cancellationToken, semanticModel: semanticModel);
            var isStatementContext = syntaxTree.IsStatementContext(start, tokenOnLeftOfStart, cancellationToken);
            var isExpressionOrStatementContext = isExpressionContext || isStatementContext;
 
            // Delegate Type Creation is not allowed in Non Type Namespace Context
            generateTypeServiceStateOptions.IsDelegateAllowed = false;
 
            if (!isExpressionOrStatementContext)
                return false;
 
            if (!simpleName.IsLeftSideOfDot() &&
                !simpleName.IsInsideNameOfExpression(semanticModel, cancellationToken))
            {
                if (nameOrMemberAccessExpression == null || !nameOrMemberAccessExpression.IsKind(SyntaxKind.SimpleMemberAccessExpression) || !simpleName.IsRightSideOfDot())
                    return false;
 
                var leftSymbol = semanticModel.GetSymbolInfo(((MemberAccessExpressionSyntax)nameOrMemberAccessExpression).Expression, cancellationToken).Symbol;
                var token = simpleName.GetLastToken().GetNextToken();
 
                // We let only the Namespace to be left of the Dot
                if (leftSymbol == null ||
                    !leftSymbol.IsKind(SymbolKind.Namespace) ||
                    !token.IsKind(SyntaxKind.DotToken))
                {
                    return false;
                }
                else
                {
                    generateTypeServiceStateOptions.IsMembersWithModule = true;
                    generateTypeServiceStateOptions.IsTypeGeneratedIntoNamespaceFromMemberAccess = true;
                }
            }
 
            // Global Namespace 
            if (!generateTypeServiceStateOptions.IsTypeGeneratedIntoNamespaceFromMemberAccess &&
                !SyntaxFacts.IsInNamespaceOrTypeContext(simpleName))
            {
                var token = simpleName.GetLastToken().GetNextToken();
                if (token.IsKind(SyntaxKind.DotToken) &&
                        simpleName.Parent == token.Parent)
                {
                    generateTypeServiceStateOptions.IsMembersWithModule = true;
                    generateTypeServiceStateOptions.IsTypeGeneratedIntoNamespaceFromMemberAccess = true;
                }
            }
        }
 
        var fieldDeclaration = simpleName.GetAncestor<FieldDeclarationSyntax>();
        if (fieldDeclaration != null &&
            fieldDeclaration.Parent is CompilationUnitSyntax &&
            document.Document.SourceCodeKind == SourceCodeKind.Regular)
        {
            return false;
        }
 
        // Check to see if Module could be an option in the Type Generation in Cross Language Generation
        var nextToken = simpleName.GetLastToken().GetNextToken();
        if (simpleName.IsLeftSideOfDot() ||
            nextToken.IsKind(SyntaxKind.DotToken))
        {
            if (simpleName.IsRightSideOfDot())
            {
                if (simpleName.Parent is QualifiedNameSyntax parent)
                {
                    var leftSymbol = semanticModel.GetSymbolInfo(parent.Left, cancellationToken).Symbol;
                    if (leftSymbol != null && leftSymbol.IsKind(SymbolKind.Namespace))
                        generateTypeServiceStateOptions.IsMembersWithModule = true;
                }
            }
        }
 
        if (SyntaxFacts.IsInNamespaceOrTypeContext(nameOrMemberAccessExpression))
        {
            if (nextToken.IsKind(SyntaxKind.DotToken))
            {
                // In Namespace or Type Context we cannot have Interface, Enum, Delegate as part of the Left Expression of a QualifiedName
                generateTypeServiceStateOptions.IsDelegateAllowed = false;
                generateTypeServiceStateOptions.IsInterfaceOrEnumNotAllowedInTypeContext = true;
                generateTypeServiceStateOptions.IsMembersWithModule = true;
            }
 
            // case: class Goo<T> where T: MyType
            if (nameOrMemberAccessExpression.GetAncestors<TypeConstraintSyntax>().Any())
            {
                generateTypeServiceStateOptions.IsClassInterfaceTypes = true;
                return true;
            }
 
            // Events
            if (nameOrMemberAccessExpression.GetAncestors<EventFieldDeclarationSyntax>().Any() ||
                nameOrMemberAccessExpression.GetAncestors<EventDeclarationSyntax>().Any())
            {
                // Case : event goo name11
                // Only Delegate
                if (simpleName.Parent is not null and not QualifiedNameSyntax)
                {
                    generateTypeServiceStateOptions.IsDelegateOnly = true;
                    return true;
                }
 
                // Case : event SomeSymbol.goo name11
                if (nameOrMemberAccessExpression is QualifiedNameSyntax)
                {
                    // Only Namespace, Class, Struct and Module are allowed to contain Delegate
                    // Case : event Something.Mytype.<Delegate> Identifier
                    if (nextToken.IsKind(SyntaxKind.DotToken))
                    {
                        if (nameOrMemberAccessExpression.Parent is not null and QualifiedNameSyntax)
                        {
                            return true;
                        }
 
                        throw ExceptionUtilities.Unreachable();
                    }
                    else
                    {
                        // Case : event Something.<Delegate> Identifier
                        generateTypeServiceStateOptions.IsDelegateOnly = true;
                        return true;
                    }
                }
            }
        }
        else
        {
            // MemberAccessExpression
            if ((nameOrMemberAccessExpression.IsKind(SyntaxKind.SimpleMemberAccessExpression) || (nameOrMemberAccessExpression.Parent != null && nameOrMemberAccessExpression.IsParentKind(SyntaxKind.SimpleMemberAccessExpression)))
                && nameOrMemberAccessExpression.IsLeftSideOfDot())
            {
                // Check to see if the expression is part of Invocation Expression
                ExpressionSyntax outerMostMemberAccessExpression = null;
                if (nameOrMemberAccessExpression.IsKind(SyntaxKind.SimpleMemberAccessExpression))
                {
                    outerMostMemberAccessExpression = nameOrMemberAccessExpression;
                }
                else
                {
                    Debug.Assert(nameOrMemberAccessExpression.IsParentKind(SyntaxKind.SimpleMemberAccessExpression));
                    outerMostMemberAccessExpression = (ExpressionSyntax)nameOrMemberAccessExpression.Parent;
                }
 
                outerMostMemberAccessExpression = outerMostMemberAccessExpression.GetAncestorsOrThis<ExpressionSyntax>().SkipWhile(n => n != null && n.IsKind(SyntaxKind.SimpleMemberAccessExpression)).FirstOrDefault();
                if (outerMostMemberAccessExpression is not null and InvocationExpressionSyntax)
                {
                    generateTypeServiceStateOptions.IsEnumNotAllowed = true;
                }
            }
        }
 
        // Cases:
        // // 1 - Function Address
        // var s2 = new MyD2(goo);
 
        // // 2 - Delegate
        // MyD1 d = null;
        // var s1 = new MyD2(d);
 
        // // 3 - Action
        // Action action1 = null;
        // var s3 = new MyD2(action1);
 
        // // 4 - Func
        // Func<int> lambda = () => { return 0; };
        // var s4 = new MyD3(lambda);
 
        if (nameOrMemberAccessExpression.Parent is ObjectCreationExpressionSyntax objectCreationExpressionOpt)
        {
            generateTypeServiceStateOptions.ObjectCreationExpressionOpt = objectCreationExpressionOpt;
 
            // Enum and Interface not Allowed in Object Creation Expression
            generateTypeServiceStateOptions.IsInterfaceOrEnumNotAllowedInTypeContext = true;
 
            if (objectCreationExpressionOpt.ArgumentList != null)
            {
                if (objectCreationExpressionOpt.ArgumentList.CloseParenToken.IsMissing)
                    return false;
 
                // Get the Method symbol for the Delegate to be created
                if (generateTypeServiceStateOptions.IsDelegateAllowed &&
                    objectCreationExpressionOpt.ArgumentList.Arguments is [{ Expression: (kind: not SyntaxKind.DeclarationExpression) expression }])
                {
                    generateTypeServiceStateOptions.DelegateCreationMethodSymbol = GetMethodSymbolIfPresent(semanticModel, expression, cancellationToken);
                }
                else
                {
                    generateTypeServiceStateOptions.IsDelegateAllowed = false;
                }
            }
 
            var initializer = objectCreationExpressionOpt.Initializer;
            AddPropertiesToInitialize(generateTypeServiceStateOptions, initializer);
        }
 
        // Check for `NewType t = new() { ... }` and initialize properties if present 
        if (nameOrMemberAccessExpression.Parent is VariableDeclarationSyntax
            {
                Variables: [{ Initializer.Value: ImplicitObjectCreationExpressionSyntax { Initializer: { } implicitInitializer } }, ..]
            })
        {
            AddPropertiesToInitialize(generateTypeServiceStateOptions, implicitInitializer);
        }
 
        if (generateTypeServiceStateOptions.IsDelegateAllowed)
        {
            // MyD1 z1 = goo;
            if (nameOrMemberAccessExpression.Parent is VariableDeclarationSyntax variableDeclaration)
            {
                var firstVarDeclWithInitializer = variableDeclaration.Variables.FirstOrDefault(var => var.Initializer != null && var.Initializer.Value != null);
                if (firstVarDeclWithInitializer != null)
                    generateTypeServiceStateOptions.DelegateCreationMethodSymbol = GetMethodSymbolIfPresent(semanticModel, firstVarDeclWithInitializer.Initializer.Value, cancellationToken);
            }
 
            // var w1 = (MyD1)goo;
            if (nameOrMemberAccessExpression.Parent is CastExpressionSyntax { Expression: not null } castExpression)
                generateTypeServiceStateOptions.DelegateCreationMethodSymbol = GetMethodSymbolIfPresent(semanticModel, castExpression.Expression, cancellationToken);
        }
 
        return true;
    }
 
    private static void AddPropertiesToInitialize(GenerateTypeServiceStateOptions generateTypeServiceStateOptions, InitializerExpressionSyntax initializer)
    {
        if (initializer != null)
        {
            foreach (var expression in initializer.Expressions)
            {
                if (expression is not AssignmentExpressionSyntax simpleAssignmentExpression)
                    continue;
 
                if (simpleAssignmentExpression.Left is not SimpleNameSyntax name)
                    continue;
 
                generateTypeServiceStateOptions.PropertiesToGenerate.Add(name);
            }
        }
    }
 
    private static IMethodSymbol GetMethodSymbolIfPresent(SemanticModel semanticModel, ExpressionSyntax expression, CancellationToken cancellationToken)
    {
        if (expression == null)
        {
            return null;
        }
 
        var memberGroup = semanticModel.GetMemberGroup(expression, cancellationToken);
        if (memberGroup.Length != 0)
        {
            return memberGroup.ElementAt(0).IsKind(SymbolKind.Method) ? (IMethodSymbol)memberGroup.ElementAt(0) : null;
        }
 
        var expressionType = semanticModel.GetTypeInfo(expression, cancellationToken).Type;
        if (expressionType.IsDelegateType())
        {
            return ((INamedTypeSymbol)expressionType).DelegateInvokeMethod;
        }
 
        var expressionSymbol = semanticModel.GetSymbolInfo(expression, cancellationToken).Symbol;
        if (expressionSymbol.IsKind(SymbolKind.Method))
        {
            return (IMethodSymbol)expressionSymbol;
        }
 
        return null;
    }
 
    private static Accessibility DetermineAccessibilityConstraint(
        State state,
        SemanticModel semanticModel,
        CancellationToken cancellationToken)
    {
        return semanticModel.DetermineAccessibilityConstraint(
            state.NameOrMemberAccessExpression as TypeSyntax, cancellationToken);
    }
 
    private static bool AllContainingTypesArePublicOrProtected(
        State state,
        SemanticModel semanticModel,
        CancellationToken cancellationToken)
    {
        return semanticModel.AllContainingTypesArePublicOrProtected(
            state.NameOrMemberAccessExpression as TypeSyntax, cancellationToken);
    }
 
    protected override ImmutableArray<ITypeParameterSymbol> GetTypeParameters(
        State state,
        SemanticModel semanticModel,
        CancellationToken cancellationToken)
    {
        if (state.SimpleName is GenericNameSyntax)
        {
            var genericName = (GenericNameSyntax)state.SimpleName;
            var typeArguments = state.SimpleName.Arity == genericName.TypeArgumentList.Arguments.Count
                ? genericName.TypeArgumentList.Arguments.OfType<SyntaxNode>().ToList()
                : Enumerable.Repeat<SyntaxNode>(null, state.SimpleName.Arity);
            return GetTypeParameters(state, semanticModel, typeArguments, cancellationToken);
        }
 
        return [];
    }
 
    protected override bool TryGetArgumentList(ObjectCreationExpressionSyntax objectCreationExpression, out IList<ArgumentSyntax> argumentList)
    {
        if (objectCreationExpression != null && objectCreationExpression.ArgumentList != null)
        {
            argumentList = objectCreationExpression.ArgumentList.Arguments.ToList();
            return true;
        }
 
        argumentList = null;
        return false;
    }
 
    protected override IList<ParameterName> GenerateParameterNames(
        SemanticModel semanticModel, IList<ArgumentSyntax> arguments, CancellationToken cancellationToken)
    {
        return semanticModel.GenerateParameterNames(arguments, reservedNames: null, cancellationToken: cancellationToken);
    }
 
    public override string GetRootNamespace(CompilationOptions options)
        => string.Empty;
 
    protected override bool IsInVariableTypeContext(ExpressionSyntax expression)
        => false;
 
    protected override INamedTypeSymbol DetermineTypeToGenerateIn(SemanticModel semanticModel, SimpleNameSyntax simpleName, CancellationToken cancellationToken)
        => semanticModel.GetEnclosingNamedType(simpleName.SpanStart, cancellationToken);
 
    protected override Accessibility GetAccessibility(State state, SemanticModel semanticModel, bool intoNamespace, CancellationToken cancellationToken)
    {
        var accessibility = DetermineDefaultAccessibility(state, semanticModel, intoNamespace, cancellationToken);
        if (!state.IsTypeGeneratedIntoNamespaceFromMemberAccess)
        {
            var accessibilityConstraint = DetermineAccessibilityConstraint(state, semanticModel, cancellationToken);
 
            if (accessibilityConstraint is Accessibility.Public or
                Accessibility.Internal)
            {
                accessibility = accessibilityConstraint;
            }
            else if (accessibilityConstraint is Accessibility.Protected or
                     Accessibility.ProtectedOrInternal)
            {
                // If nested type is declared in public type then we should generate public type instead of internal
                accessibility = AllContainingTypesArePublicOrProtected(state, semanticModel, cancellationToken)
                    ? Accessibility.Public
                    : Accessibility.Internal;
            }
        }
 
        return accessibility;
    }
 
    protected override ITypeSymbol DetermineArgumentType(SemanticModel semanticModel, ArgumentSyntax argument, CancellationToken cancellationToken)
        => argument.DetermineParameterType(semanticModel, cancellationToken);
 
    protected override bool IsConversionImplicit(Compilation compilation, ITypeSymbol sourceType, ITypeSymbol targetType)
        => compilation.ClassifyConversion(sourceType, targetType).IsImplicit;
 
    public override async Task<(INamespaceSymbol, INamespaceOrTypeSymbol, Location)> GetOrGenerateEnclosingNamespaceSymbolAsync(
        INamedTypeSymbol namedTypeSymbol, string[] containers, Document selectedDocument, SyntaxNode selectedDocumentRoot, CancellationToken cancellationToken)
    {
        var compilationUnit = (CompilationUnitSyntax)selectedDocumentRoot;
        var semanticModel = await selectedDocument.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
        if (containers.Length != 0)
        {
            // Search the NS declaration in the root
            var containerList = new List<string>(containers);
            var enclosingNamespace = FindNamespaceInMemberDeclarations(compilationUnit.Members, indexDone: 0, containerList);
            if (enclosingNamespace != null)
            {
                var enclosingNamespaceSymbol = semanticModel.GetSymbolInfo(enclosingNamespace.Name, cancellationToken);
                if (enclosingNamespaceSymbol.Symbol is INamespaceSymbol namespaceSymbol)
                    return (namespaceSymbol, namedTypeSymbol, enclosingNamespace.GetLastToken().GetLocation());
            }
        }
 
        var globalNamespace = semanticModel.GetEnclosingNamespace(0, cancellationToken);
        var rootNamespaceOrType = namedTypeSymbol.GenerateRootNamespaceOrType(containers);
        var lastMember = compilationUnit.Members.LastOrDefault();
        var afterThisLocation = lastMember != null
            ? semanticModel.SyntaxTree.GetLocation(new TextSpan(lastMember.Span.End, 0))
            : semanticModel.SyntaxTree.GetLocation(new TextSpan());
 
        return (globalNamespace, rootNamespaceOrType, afterThisLocation);
    }
 
    private BaseNamespaceDeclarationSyntax FindNamespaceInMemberDeclarations(SyntaxList<MemberDeclarationSyntax> members, int indexDone, List<string> containers)
    {
        foreach (var member in members)
        {
            if (member is BaseNamespaceDeclarationSyntax namespaceDeclaration)
            {
                var found = FindNamespaceInNamespace(namespaceDeclaration, indexDone, containers);
                if (found != null)
                    return found;
            }
        }
 
        return null;
    }
 
    private BaseNamespaceDeclarationSyntax FindNamespaceInNamespace(BaseNamespaceDeclarationSyntax namespaceDecl, int indexDone, List<string> containers)
    {
        if (namespaceDecl.Name is AliasQualifiedNameSyntax)
            return null;
 
        var namespaceContainers = new List<string>();
        GetNamespaceContainers(namespaceDecl.Name, namespaceContainers);
 
        if (namespaceContainers.Count + indexDone > containers.Count ||
            !IdentifierMatches(indexDone, namespaceContainers, containers))
        {
            return null;
        }
 
        indexDone += namespaceContainers.Count;
        if (indexDone == containers.Count)
            return namespaceDecl;
 
        return FindNamespaceInMemberDeclarations(namespaceDecl.Members, indexDone, containers);
    }
 
    private static bool IdentifierMatches(int indexDone, List<string> namespaceContainers, List<string> containers)
    {
        for (var i = 0; i < namespaceContainers.Count; ++i)
        {
            if (namespaceContainers[i] != containers[indexDone + i])
            {
                return false;
            }
        }
 
        return true;
    }
 
    private static void GetNamespaceContainers(NameSyntax name, List<string> namespaceContainers)
    {
        if (name is QualifiedNameSyntax qualifiedName)
        {
            GetNamespaceContainers(qualifiedName.Left, namespaceContainers);
            namespaceContainers.Add(qualifiedName.Right.Identifier.ValueText);
        }
        else
        {
            Debug.Assert(name is SimpleNameSyntax);
            namespaceContainers.Add(((SimpleNameSyntax)name).Identifier.ValueText);
        }
    }
 
    internal override bool TryGetBaseList(ExpressionSyntax expression, out TypeKindOptions typeKindValue)
    {
        typeKindValue = TypeKindOptions.AllOptions;
 
        if (expression == null)
        {
            return false;
        }
 
        if (expression.Parent is BaseTypeSyntax { Parent: BaseListSyntax baseList })
        {
            if (baseList.Parent.Kind() is SyntaxKind.InterfaceDeclaration or SyntaxKind.StructDeclaration or SyntaxKind.RecordStructDeclaration)
            {
                typeKindValue = TypeKindOptions.Interface;
                return true;
            }
 
            typeKindValue = TypeKindOptions.BaseList;
            return true;
        }
 
        return false;
    }
 
    internal override bool IsPublicOnlyAccessibility(ExpressionSyntax expression, Project project)
    {
        if (expression == null)
            return false;
 
        var node = expression as SyntaxNode;
        SyntaxNode previousNode = null;
 
        while (node != null)
        {
            // Types in BaseList, Type Constraint or Member Types cannot be of more restricted accessibility than the declaring type
            if (node is BaseListSyntax or TypeParameterConstraintClauseSyntax &&
                node.Parent != null &&
                node.Parent is TypeDeclarationSyntax)
            {
                if (node.Parent is TypeDeclarationSyntax typeDecl)
                {
                    // The Type Decl which contains the BaseList needs to contain Public
                    return typeDecl.GetModifiers().Any(SyntaxKind.PublicKeyword) && IsAllContainingTypeDeclsPublic(typeDecl);
                }
 
                throw ExceptionUtilities.Unreachable();
            }
 
            if (node is EventDeclarationSyntax or EventFieldDeclarationSyntax &&
                node.Parent != null &&
                node.Parent is TypeDeclarationSyntax)
            {
                // Make sure the GFU is not inside the Accessors
                // Make sure that Event Declaration themselves are Public in the first place
                return previousNode is not AccessorListSyntax &&
                    node.GetModifiers().Any(SyntaxKind.PublicKeyword) &&
                    IsAllContainingTypeDeclsPublic(node);
            }
 
            previousNode = node;
            node = node.Parent;
        }
 
        return false;
    }
 
    private static bool IsAllContainingTypeDeclsPublic(SyntaxNode node)
    {
        // Make sure that all the containing Type Declarations are also Public
        var containingTypeDeclarations = node.GetAncestors<TypeDeclarationSyntax>();
        return !containingTypeDeclarations.Any()
            || containingTypeDeclarations.All(typedecl => typedecl.GetModifiers().Any(SyntaxKind.PublicKeyword));
    }
 
    internal override bool IsGenericName(SimpleNameSyntax simpleName)
        => simpleName is GenericNameSyntax;
 
    internal override bool IsSimpleName(ExpressionSyntax expression)
        => expression is SimpleNameSyntax;
 
    internal override async Task<Solution> TryAddUsingsOrImportToDocumentAsync(
        Solution updatedSolution, SyntaxNode modifiedRoot, Document document, SimpleNameSyntax simpleName, string includeUsingsOrImports, CancellationToken cancellationToken)
    {
        // Nothing to include
        if (string.IsNullOrWhiteSpace(includeUsingsOrImports))
        {
            return updatedSolution;
        }
 
        SyntaxNode root = null;
        if (modifiedRoot == null)
        {
            root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
        }
        else
        {
            root = modifiedRoot;
        }
 
        if (root is CompilationUnitSyntax compilationRoot)
        {
            var usingDirective = SyntaxFactory.UsingDirective(SyntaxFactory.ParseName(includeUsingsOrImports));
 
            // Check if the usings is already present
            if (compilationRoot.Usings.Where(n => n != null && n.Alias == null)
                                      .Select(n => n.Name.ToString())
                                      .Any(n => n.Equals(includeUsingsOrImports)))
            {
                return updatedSolution;
            }
 
            // Check if the GFU is triggered from the namespace same as the usings namespace
            if (await IsWithinTheImportingNamespaceAsync(document, simpleName.SpanStart, includeUsingsOrImports, cancellationToken).ConfigureAwait(false))
            {
                return updatedSolution;
            }
 
            var addImportOptions = await document.GetAddImportPlacementOptionsAsync(cancellationToken).ConfigureAwait(false);
            var addedCompilationRoot = compilationRoot.AddUsingDirectives([usingDirective], addImportOptions.PlaceSystemNamespaceFirst, Formatter.Annotation);
            updatedSolution = updatedSolution.WithDocumentSyntaxRoot(document.Id, addedCompilationRoot, PreservationMode.PreserveIdentity);
        }
 
        return updatedSolution;
    }
 
    private static ITypeSymbol GetPropertyType(
        SimpleNameSyntax propertyName,
        SemanticModel semanticModel,
        ITypeInferenceService typeInference,
        CancellationToken cancellationToken)
    {
        if (propertyName.Parent is AssignmentExpressionSyntax parentAssignment)
        {
            return typeInference.InferType(
                semanticModel, parentAssignment.Left, objectAsDefault: true, cancellationToken: cancellationToken);
        }
 
        if (propertyName.Parent is IsPatternExpressionSyntax isPatternExpression)
        {
            return typeInference.InferType(
                semanticModel, isPatternExpression.Expression, objectAsDefault: true, cancellationToken: cancellationToken);
        }
 
        return null;
    }
 
    private static IPropertySymbol CreatePropertySymbol(
        SimpleNameSyntax propertyName, ITypeSymbol propertyType)
    {
        return CodeGenerationSymbolFactory.CreatePropertySymbol(
            attributes: [],
            accessibility: Accessibility.Public,
            modifiers: new DeclarationModifiers(),
            explicitInterfaceImplementations: default,
            name: propertyName.Identifier.ValueText,
            type: propertyType,
            refKind: RefKind.None,
            parameters: default,
            getMethod: s_accessor,
            setMethod: s_accessor,
            isIndexer: false);
    }
 
    private static readonly IMethodSymbol s_accessor = CodeGenerationSymbolFactory.CreateAccessorSymbol(
        attributes: default,
        accessibility: Accessibility.Public,
        statements: default);
 
    internal override bool TryGenerateProperty(
        SimpleNameSyntax propertyName,
        SemanticModel semanticModel,
        ITypeInferenceService typeInference,
        CancellationToken cancellationToken,
        out IPropertySymbol property)
    {
        var propertyType = GetPropertyType(propertyName, semanticModel, typeInference, cancellationToken);
        if (propertyType is null or IErrorTypeSymbol)
        {
            property = CreatePropertySymbol(propertyName, semanticModel.Compilation.ObjectType);
            return property != null;
        }
 
        property = CreatePropertySymbol(propertyName, propertyType);
        return property != null;
    }
}