File: GenerateType\AbstractGenerateTypeService.Editor.cs
Web Access
Project: src\src\Features\Core\Portable\Microsoft.CodeAnalysis.Features.csproj (Microsoft.CodeAnalysis.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.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeCleanup;
using Microsoft.CodeAnalysis.CodeGeneration;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.LanguageService;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;
using Microsoft.CodeAnalysis.Utilities;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.GenerateType;
 
internal abstract partial class AbstractGenerateTypeService<TService, TSimpleNameSyntax, TObjectCreationExpressionSyntax, TExpressionSyntax, TTypeDeclarationSyntax, TArgumentSyntax>
{
    protected abstract bool IsConversionImplicit(Compilation compilation, ITypeSymbol sourceType, ITypeSymbol targetType);
 
    private sealed partial class Editor
    {
        private readonly TService _service;
        private TargetProjectChangeInLanguage _targetProjectChangeInLanguage = TargetProjectChangeInLanguage.NoChange;
        private IGenerateTypeService _targetLanguageService;
 
        private readonly SemanticDocument _semanticDocument;
        private readonly State _state;
        private readonly bool _intoNamespace;
        private readonly bool _inNewFile;
        private readonly bool _fromDialog;
        private readonly GenerateTypeOptionsResult _generateTypeOptionsResult;
        private readonly CancellationToken _cancellationToken;
 
        public Editor(
            TService service,
            SemanticDocument document,
            State state,
            bool intoNamespace,
            bool inNewFile,
            CancellationToken cancellationToken)
        {
            _service = service;
            _semanticDocument = document;
            _state = state;
            _intoNamespace = intoNamespace;
            _inNewFile = inNewFile;
            _cancellationToken = cancellationToken;
        }
 
        public Editor(
            TService service,
            SemanticDocument document,
            State state,
            bool fromDialog,
            GenerateTypeOptionsResult generateTypeOptionsResult,
            CancellationToken cancellationToken)
        {
            // the document comes from the same snapshot as the project
            Contract.ThrowIfFalse(document.Project.Solution == generateTypeOptionsResult.Project.Solution);
 
            _service = service;
            _semanticDocument = document;
            _state = state;
            _fromDialog = fromDialog;
            _generateTypeOptionsResult = generateTypeOptionsResult;
            _cancellationToken = cancellationToken;
        }
 
        private enum TargetProjectChangeInLanguage
        {
            NoChange,
            CSharpToVisualBasic,
            VisualBasicToCSharp
        }
 
        public async Task<ImmutableArray<CodeActionOperation>> GetOperationsAsync()
        {
            // Check to see if it is from GFU Dialog
            if (!_fromDialog)
            {
                // Generate the actual type declaration.
                var namedType = await GenerateNamedTypeAsync().ConfigureAwait(false);
 
                if (_intoNamespace)
                {
                    if (_inNewFile)
                    {
                        // Generating into a new file is somewhat complicated.
                        var documentName = GetTypeName(_state) + _service.DefaultFileExtension;
 
                        return await GetGenerateInNewFileOperationsAsync(
                            namedType,
                            documentName,
                            folders: null,
                            areFoldersValidIdentifiers: true,
                            fullFilePath: null,
                            _semanticDocument.Project,
                            _semanticDocument.Project,
                            isDialog: false).ConfigureAwait(false);
                    }
                    else
                    {
                        return await GetGenerateIntoContainingNamespaceOperationsAsync(namedType).ConfigureAwait(false);
                    }
                }
                else
                {
                    return await GetGenerateIntoTypeOperationsAsync(namedType).ConfigureAwait(false);
                }
            }
            else
            {
                var namedType = await GenerateNamedTypeAsync(_generateTypeOptionsResult).ConfigureAwait(false);
 
                // Honor the options from the dialog
                // Check to see if the type is requested to be generated in cross language Project
                // e.g.: C# -> VB or VB -> C#
                if (_semanticDocument.Project.Language != _generateTypeOptionsResult.Project.Language)
                {
                    _targetProjectChangeInLanguage =
                        _generateTypeOptionsResult.Project.Language == LanguageNames.CSharp
                        ? TargetProjectChangeInLanguage.VisualBasicToCSharp
                        : TargetProjectChangeInLanguage.CSharpToVisualBasic;
 
                    // Get the cross language service
                    _targetLanguageService = _generateTypeOptionsResult.Project.Services.GetService<IGenerateTypeService>();
                }
 
                if (_generateTypeOptionsResult.IsNewFile)
                {
                    return await GetGenerateInNewFileOperationsAsync(
                        namedType,
                        _generateTypeOptionsResult.NewFileName,
                        _generateTypeOptionsResult.Folders,
                        _generateTypeOptionsResult.AreFoldersValidIdentifiers,
                        _generateTypeOptionsResult.FullFilePath,
                        _generateTypeOptionsResult.Project,
                        _semanticDocument.Project,
                        isDialog: true).ConfigureAwait(false);
                }
                else
                {
                    return await GetGenerateIntoExistingDocumentAsync(
                        namedType,
                        _semanticDocument.Project,
                        _generateTypeOptionsResult,
                        isDialog: true).ConfigureAwait(false);
                }
            }
        }
 
        private string GetNamespaceToGenerateInto()
        {
            var namespaceToGenerateInto = _state.NamespaceToGenerateInOpt.Trim();
            var rootNamespace = _service.GetRootNamespace(_semanticDocument.SemanticModel.Compilation.Options).Trim();
            if (!string.IsNullOrWhiteSpace(rootNamespace))
            {
                if (namespaceToGenerateInto == rootNamespace ||
                    namespaceToGenerateInto.StartsWith(rootNamespace + ".", StringComparison.Ordinal))
                {
                    namespaceToGenerateInto = namespaceToGenerateInto[rootNamespace.Length..];
                }
            }
 
            return namespaceToGenerateInto;
        }
 
        private string GetNamespaceToGenerateIntoForUsageWithNamespace(Project targetProject, Project triggeringProject)
        {
            var namespaceToGenerateInto = _state.NamespaceToGenerateInOpt.Trim();
 
            if (targetProject.Language == LanguageNames.CSharp ||
                targetProject == triggeringProject)
            {
                // If the target project is C# project then we don't have to make any modification to the namespace
                // or
                // This is a VB project generation into itself which requires no change as well
                return namespaceToGenerateInto;
            }
 
            // If the target Project is VB then we have to check if the RootNamespace of the VB project is the parent most namespace of the type being generated
            // True, Remove the RootNamespace
            // False, Add Global to the Namespace
            Debug.Assert(targetProject.Language == LanguageNames.VisualBasic);
            IGenerateTypeService targetLanguageService;
            if (_semanticDocument.Project.Language == LanguageNames.VisualBasic)
            {
                targetLanguageService = _service;
            }
            else
            {
                Debug.Assert(_targetLanguageService != null);
                targetLanguageService = _targetLanguageService;
            }
 
            var rootNamespace = targetLanguageService.GetRootNamespace(targetProject.CompilationOptions).Trim();
            if (!string.IsNullOrWhiteSpace(rootNamespace))
            {
                var rootNamespaceLength = CheckIfRootNamespacePresentInNamespace(namespaceToGenerateInto, rootNamespace);
                if (rootNamespaceLength > -1)
                {
                    // True, Remove the RootNamespace
                    namespaceToGenerateInto = namespaceToGenerateInto[rootNamespaceLength..];
                }
                else
                {
                    // False, Add Global to the Namespace
                    namespaceToGenerateInto = AddGlobalDotToTheNamespace(namespaceToGenerateInto);
                }
            }
            else
            {
                // False, Add Global to the Namespace
                namespaceToGenerateInto = AddGlobalDotToTheNamespace(namespaceToGenerateInto);
            }
 
            return namespaceToGenerateInto;
        }
 
        private static string AddGlobalDotToTheNamespace(string namespaceToBeGenerated)
            => "Global." + namespaceToBeGenerated;
 
        // Returns the length of the meaningful rootNamespace substring part of namespaceToGenerateInto
        private static int CheckIfRootNamespacePresentInNamespace(string namespaceToGenerateInto, string rootNamespace)
        {
            if (namespaceToGenerateInto == rootNamespace)
            {
                return rootNamespace.Length;
            }
 
            if (namespaceToGenerateInto.StartsWith(rootNamespace + ".", StringComparison.Ordinal))
            {
                return rootNamespace.Length + 1;
            }
 
            return -1;
        }
 
        private static void AddFoldersToNamespaceContainers(List<string> container, IList<string> folders)
        {
            // Add the folder as part of the namespace if there are not empty
            if (folders != null && folders.Count != 0)
            {
                // Remove the empty entries and replace the spaces in the folder name to '_'
                var refinedFolders = folders.Where(n => n != null && !n.IsEmpty()).Select(n => n.Replace(' ', '_')).ToArray();
                container.AddRange(refinedFolders);
            }
        }
 
        private async Task<ImmutableArray<CodeActionOperation>> GetGenerateInNewFileOperationsAsync(
            INamedTypeSymbol namedType,
            string documentName,
            IList<string> folders,
            bool areFoldersValidIdentifiers,
            string fullFilePath,
            Project projectToBeUpdated,
            Project triggeringProject,
            bool isDialog)
        {
            // First, we fork the solution with a new, empty, file in it.  
            var newDocumentId = DocumentId.CreateNewId(projectToBeUpdated.Id, debugName: documentName);
            var newSolution = projectToBeUpdated.Solution.AddDocument(newDocumentId, documentName, string.Empty, folders, fullFilePath);
 
            // Now we get the semantic model for that file we just added.  We do that to get the
            // root namespace in that new document, along with location for that new namespace.
            // That way, when we use the code gen service we can say "add this symbol to the
            // root namespace" and it will pick the one in the new file.
            var newDocument = newSolution.GetDocument(newDocumentId);
            var newSemanticModel = await newDocument.GetSemanticModelAsync(_cancellationToken).ConfigureAwait(false);
            var enclosingNamespace = newSemanticModel.GetEnclosingNamespace(0, _cancellationToken);
 
            var namespaceContainersAndUsings = GetNamespaceContainersAndAddUsingsOrImport(
                isDialog, folders, areFoldersValidIdentifiers, projectToBeUpdated, triggeringProject);
 
            var containers = namespaceContainersAndUsings.containers;
            var includeUsingsOrImports = namespaceContainersAndUsings.usingOrImport;
 
            var rootNamespaceOrType = namedType.GenerateRootNamespaceOrType(containers);
 
            // Now, actually ask the code gen service to add this namespace or type to the root
            // namespace in the new file.  This will properly generate the code, and add any
            // additional niceties like imports/usings.
            var codeGenResult = await CodeGenerator.AddNamespaceOrTypeDeclarationAsync(
                new CodeGenerationSolutionContext(
                    newSolution,
                    new CodeGenerationContext(newSemanticModel.SyntaxTree.GetLocation(new TextSpan()))),
                enclosingNamespace,
                rootNamespaceOrType,
                _cancellationToken).ConfigureAwait(false);
 
            // containers is determined to be
            // 1: folders -> if triggered from Dialog
            // 2: containers -> if triggered not from a Dialog but from QualifiedName
            // 3: triggering document folder structure -> if triggered not from a Dialog and a SimpleName
            var adjustedContainer = isDialog
                ? folders
                : _state.SimpleName != _state.NameOrMemberAccessExpression
                    ? containers.ToList()
                    : [.. _semanticDocument.Document.Folders];
 
            if (newDocument.Project.Language == _semanticDocument.Document.Project.Language)
            {
                var formattingService = newDocument.GetLanguageService<INewDocumentFormattingService>();
                if (formattingService is not null)
                {
                    var cleanupOptions = await codeGenResult.GetCodeCleanupOptionsAsync(_cancellationToken).ConfigureAwait(false);
                    codeGenResult = await formattingService.FormatNewDocumentAsync(codeGenResult, _semanticDocument.Document, cleanupOptions, _cancellationToken).ConfigureAwait(false);
                }
            }
 
            // Now, take the code that would be generated and actually create an edit that would
            // produce a document with that code in it.
            var newRoot = await codeGenResult.GetSyntaxRootAsync(_cancellationToken).ConfigureAwait(false);
 
            return await CreateAddDocumentAndUpdateUsingsOrImportsOperationsAsync(
                projectToBeUpdated,
                triggeringProject,
                documentName,
                newRoot,
                includeUsingsOrImports,
                adjustedContainer,
                SourceCodeKind.Regular,
                _cancellationToken).ConfigureAwait(false);
        }
 
        private async Task<ImmutableArray<CodeActionOperation>> CreateAddDocumentAndUpdateUsingsOrImportsOperationsAsync(
            Project projectToBeUpdated,
            Project triggeringProject,
            string documentName,
            SyntaxNode root,
            string includeUsingsOrImports,
            IList<string> containers,
            SourceCodeKind sourceCodeKind,
            CancellationToken cancellationToken)
        {
            // TODO(cyrusn): make sure documentId is unique.
            var documentId = DocumentId.CreateNewId(projectToBeUpdated.Id, documentName);
 
            var updatedSolution = projectToBeUpdated.Solution.AddDocument(
                DocumentInfo.Create(
                    documentId,
                    documentName,
                    containers,
                    sourceCodeKind));
 
            updatedSolution = updatedSolution.WithDocumentSyntaxRoot(documentId, root, PreservationMode.PreserveIdentity);
 
            // Update the Generating Document with a using if required
            if (includeUsingsOrImports != null)
            {
                updatedSolution = await _service.TryAddUsingsOrImportToDocumentAsync(
                    updatedSolution, modifiedRoot: null, _semanticDocument.Document, _state.SimpleName,
                    includeUsingsOrImports, cancellationToken).ConfigureAwait(false);
            }
 
            // Add reference of the updated project to the triggering Project if they are 2 different projects
            updatedSolution = AddProjectReference(projectToBeUpdated, triggeringProject, updatedSolution);
 
            return [new ApplyChangesOperation(updatedSolution), new OpenDocumentOperation(documentId)];
        }
 
        private static Solution AddProjectReference(Project projectToBeUpdated, Project triggeringProject, Solution updatedSolution)
        {
            if (projectToBeUpdated != triggeringProject)
            {
                if (!triggeringProject.ProjectReferences.Any(pr => pr.ProjectId == projectToBeUpdated.Id))
                {
                    updatedSolution = updatedSolution.AddProjectReference(triggeringProject.Id, new ProjectReference(projectToBeUpdated.Id));
                }
            }
 
            return updatedSolution;
        }
 
        private async Task<ImmutableArray<CodeActionOperation>> GetGenerateIntoContainingNamespaceOperationsAsync(INamedTypeSymbol namedType)
        {
            var enclosingNamespace = _semanticDocument.SemanticModel.GetEnclosingNamespace(
                _state.SimpleName.SpanStart, _cancellationToken);
 
            var solution = _semanticDocument.Project.Solution;
            var codeGenResult = await CodeGenerator.AddNamedTypeDeclarationAsync(
                new CodeGenerationSolutionContext(
                    solution,
                    new CodeGenerationContext(afterThisLocation: _semanticDocument.SyntaxTree.GetLocation(_state.SimpleName.Span))),
                enclosingNamespace,
                namedType,
                _cancellationToken).ConfigureAwait(false);
 
            return [new ApplyChangesOperation(codeGenResult.Project.Solution)];
        }
 
        private async Task<ImmutableArray<CodeActionOperation>> GetGenerateIntoExistingDocumentAsync(
            INamedTypeSymbol namedType,
            Project triggeringProject,
            GenerateTypeOptionsResult generateTypeOptionsResult,
            bool isDialog)
        {
            var root = await generateTypeOptionsResult.ExistingDocument.GetSyntaxRootAsync(_cancellationToken).ConfigureAwait(false);
            var folders = generateTypeOptionsResult.ExistingDocument.Folders;
 
            var namespaceContainersAndUsings = GetNamespaceContainersAndAddUsingsOrImport(isDialog, new List<string>(folders), generateTypeOptionsResult.AreFoldersValidIdentifiers, generateTypeOptionsResult.Project, triggeringProject);
 
            var containers = namespaceContainersAndUsings.containers;
            var includeUsingsOrImports = namespaceContainersAndUsings.usingOrImport;
 
            (INamespaceSymbol, INamespaceOrTypeSymbol, Location) enclosingNamespaceGeneratedTypeToAddAndLocation;
            if (_targetProjectChangeInLanguage == TargetProjectChangeInLanguage.NoChange)
            {
                enclosingNamespaceGeneratedTypeToAddAndLocation = await _service.GetOrGenerateEnclosingNamespaceSymbolAsync(
                    namedType,
                    containers,
                    generateTypeOptionsResult.ExistingDocument,
                    root,
                    _cancellationToken).ConfigureAwait(false);
            }
            else
            {
                enclosingNamespaceGeneratedTypeToAddAndLocation = await _targetLanguageService.GetOrGenerateEnclosingNamespaceSymbolAsync(
                    namedType,
                    containers,
                    generateTypeOptionsResult.ExistingDocument,
                    root,
                    _cancellationToken).ConfigureAwait(false);
            }
 
            var solution = _semanticDocument.Project.Solution;
            var codeGenResult = await CodeGenerator.AddNamespaceOrTypeDeclarationAsync(
                new CodeGenerationSolutionContext(
                    solution,
                    new CodeGenerationContext(afterThisLocation: enclosingNamespaceGeneratedTypeToAddAndLocation.Item3)),
                enclosingNamespaceGeneratedTypeToAddAndLocation.Item1,
                enclosingNamespaceGeneratedTypeToAddAndLocation.Item2,
                _cancellationToken).ConfigureAwait(false);
            var newRoot = await codeGenResult.GetSyntaxRootAsync(_cancellationToken).ConfigureAwait(false);
            var updatedSolution = solution.WithDocumentSyntaxRoot(generateTypeOptionsResult.ExistingDocument.Id, newRoot, PreservationMode.PreserveIdentity);
 
            // Update the Generating Document with a using if required
            if (includeUsingsOrImports != null)
            {
                updatedSolution = await _service.TryAddUsingsOrImportToDocumentAsync(
                                    updatedSolution,
                                    generateTypeOptionsResult.ExistingDocument.Id == _semanticDocument.Document.Id ? newRoot : null,
                                    _semanticDocument.Document,
                                    _state.SimpleName,
                                    includeUsingsOrImports,
                                    _cancellationToken).ConfigureAwait(false);
            }
 
            updatedSolution = AddProjectReference(generateTypeOptionsResult.Project, triggeringProject, updatedSolution);
 
            return [new ApplyChangesOperation(updatedSolution)];
        }
 
        private (string[] containers, string usingOrImport) GetNamespaceContainersAndAddUsingsOrImport(
            bool isDialog,
            IList<string> folders,
            bool areFoldersValidIdentifiers,
            Project targetProject,
            Project triggeringProject)
        {
            string includeUsingsOrImports = null;
            if (!areFoldersValidIdentifiers)
                folders = [];
 
            // Now actually create the symbol that we want to add to the root namespace.  The
            // symbol may either be a named type (if we're not generating into a namespace) or
            // it may be a namespace symbol.
            string[] containers = null;
            if (!isDialog)
            {
                // Not generated from the Dialog 
                containers = GetNamespaceToGenerateInto().Split(['.'], StringSplitOptions.RemoveEmptyEntries);
            }
            else if (!_service.IsSimpleName(_state.NameOrMemberAccessExpression))
            {
                // If the usage was with a namespace
                containers = GetNamespaceToGenerateIntoForUsageWithNamespace(targetProject, triggeringProject).Split(['.'], StringSplitOptions.RemoveEmptyEntries);
            }
            else
            {
                // Generated from the Dialog
                var containerList = new List<string>();
 
                var rootNamespaceOfTheProjectGeneratedInto =
                    _targetProjectChangeInLanguage == TargetProjectChangeInLanguage.NoChange
                        ? _service.GetRootNamespace(_generateTypeOptionsResult.Project.CompilationOptions).Trim()
                        : _targetLanguageService.GetRootNamespace(_generateTypeOptionsResult.Project.CompilationOptions).Trim();
 
                var defaultNamespace = _generateTypeOptionsResult.DefaultNamespace;
 
                // Case 1 : If the type is generated into the same C# project or
                // Case 2 : If the type is generated from a C# project to a C# Project
                // Case 3 : If the Type is generated from a VB Project to a C# Project
                // Using and Namespace will be the DefaultNamespace + Folder Structure
                if ((_semanticDocument.Project == _generateTypeOptionsResult.Project && _semanticDocument.Project.Language == LanguageNames.CSharp) ||
                    (_targetProjectChangeInLanguage == TargetProjectChangeInLanguage.NoChange && _generateTypeOptionsResult.Project.Language == LanguageNames.CSharp) ||
                    _targetProjectChangeInLanguage == TargetProjectChangeInLanguage.VisualBasicToCSharp)
                {
                    if (!string.IsNullOrWhiteSpace(defaultNamespace))
                    {
                        containerList.Add(defaultNamespace);
                    }
 
                    // Populate the ContainerList
                    AddFoldersToNamespaceContainers(containerList, folders);
 
                    containers = [.. containerList];
                    includeUsingsOrImports = string.Join(".", containers);
                }
 
                // Case 4 : If the type is generated into the same VB project or
                // Case 5 : If Type is generated from a VB Project to VB Project
                // Case 6 : If Type is generated from a C# Project to VB Project 
                // Namespace will be Folder Structure and Import will have the RootNamespace of the project generated into as part of the Imports
                if ((_semanticDocument.Project == _generateTypeOptionsResult.Project && _semanticDocument.Project.Language == LanguageNames.VisualBasic) ||
                    (_semanticDocument.Project != _generateTypeOptionsResult.Project && _targetProjectChangeInLanguage == TargetProjectChangeInLanguage.NoChange && _generateTypeOptionsResult.Project.Language == LanguageNames.VisualBasic) ||
                    _targetProjectChangeInLanguage == TargetProjectChangeInLanguage.CSharpToVisualBasic)
                {
                    // Populate the ContainerList
                    AddFoldersToNamespaceContainers(containerList, folders);
                    containers = [.. containerList];
                    includeUsingsOrImports = string.Join(".", containers);
                    if (!string.IsNullOrWhiteSpace(rootNamespaceOfTheProjectGeneratedInto))
                    {
                        includeUsingsOrImports = string.IsNullOrEmpty(includeUsingsOrImports)
                                                 ? rootNamespaceOfTheProjectGeneratedInto
                                                 : rootNamespaceOfTheProjectGeneratedInto + "." + includeUsingsOrImports;
                    }
                }
 
                Debug.Assert(includeUsingsOrImports != null);
            }
 
            return (containers, includeUsingsOrImports);
        }
 
        private async Task<ImmutableArray<CodeActionOperation>> GetGenerateIntoTypeOperationsAsync(INamedTypeSymbol namedType)
        {
            var solution = _semanticDocument.Project.Solution;
            var codeGenResult = await CodeGenerator.AddNamedTypeDeclarationAsync(
                new CodeGenerationSolutionContext(
                    solution,
                    new CodeGenerationContext(contextLocation: _state.SimpleName.GetLocation())),
                _state.TypeToGenerateInOpt,
                namedType,
                _cancellationToken)
                .ConfigureAwait(false);
 
            return [new ApplyChangesOperation(codeGenResult.Project.Solution)];
        }
 
        private ImmutableArray<ITypeSymbol> GetArgumentTypes(IList<TArgumentSyntax> argumentList)
        {
            var types = argumentList.Select(a => _service.DetermineArgumentType(_semanticDocument.SemanticModel, a, _cancellationToken));
            return types.SelectAsArray(FixType);
        }
 
        private ImmutableArray<TExpressionSyntax> GetArgumentExpressions(IList<TArgumentSyntax> argumentList)
        {
            var syntaxFacts = _semanticDocument.Document.GetRequiredLanguageService<ISyntaxFactsService>();
            return argumentList.SelectAsArray(a => (TExpressionSyntax)syntaxFacts.GetExpressionOfArgument(a));
        }
 
        private ITypeSymbol FixType(
            ITypeSymbol typeSymbol)
        {
            var compilation = _semanticDocument.SemanticModel.Compilation;
            return typeSymbol.RemoveUnnamedErrorTypes(compilation);
        }
 
        private async Task<bool> FindExistingOrCreateNewMemberAsync(
            ParameterName parameterName,
            ITypeSymbol parameterType,
            ImmutableDictionary<string, ISymbol>.Builder parameterToFieldMap,
            ImmutableDictionary<string, string>.Builder parameterToNewFieldMap)
        {
            // If the base types have an accessible field or property with the same name and
            // an acceptable type, then we should just defer to that.
            if (_state.BaseTypeOrInterfaceOpt != null)
            {
                var expectedFieldName = parameterName.NameBasedOnArgument;
                var members = from t in _state.BaseTypeOrInterfaceOpt.GetBaseTypesAndThis()
                              from m in t.GetMembers()
                              where m.Name.Equals(expectedFieldName, StringComparison.OrdinalIgnoreCase)
                              where IsSymbolAccessible(m)
                              where IsViableFieldOrProperty(parameterType, m)
                              select m;
 
                var membersArray = members.ToImmutableArray();
                var symbol = membersArray.FirstOrDefault(m => m.Name.Equals(expectedFieldName, StringComparison.Ordinal)) ?? membersArray.FirstOrDefault();
                if (symbol != null)
                {
                    parameterToFieldMap[parameterName.BestNameForParameter] = symbol;
                    return true;
                }
            }
 
            var fieldNamingRule = await _semanticDocument.Document.GetApplicableNamingRuleAsync(SymbolKind.Field, Accessibility.Private, _cancellationToken).ConfigureAwait(false);
            var nameToUse = fieldNamingRule.NamingStyle.MakeCompliant(parameterName.NameBasedOnArgument).First();
            parameterToNewFieldMap[parameterName.BestNameForParameter] = nameToUse;
            return false;
        }
 
        private bool IsViableFieldOrProperty(
            ITypeSymbol parameterType,
            ISymbol symbol)
        {
            if (symbol != null && !symbol.IsStatic && parameterType.Language == symbol.Language)
            {
                if (symbol is IFieldSymbol field)
                {
                    return
                        !field.IsReadOnly &&
                        _service.IsConversionImplicit(_semanticDocument.SemanticModel.Compilation, parameterType, field.Type);
                }
                else if (symbol is IPropertySymbol property)
                {
                    return
                        property.Parameters.Length == 0 &&
                        property.SetMethod != null &&
                        IsSymbolAccessible(property.SetMethod) &&
                        _service.IsConversionImplicit(_semanticDocument.SemanticModel.Compilation, parameterType, property.Type);
                }
            }
 
            return false;
        }
 
        private bool IsSymbolAccessible(ISymbol symbol)
        {
            // Public and protected constructors are accessible.  Internal constructors are
            // accessible if we have friend access.  We can't call the normal accessibility
            // checkers since they will think that a protected constructor isn't accessible
            // (since we don't have the destination type that would have access to them yet).
            switch (symbol.DeclaredAccessibility)
            {
                case Accessibility.ProtectedOrInternal:
                case Accessibility.Protected:
                case Accessibility.Public:
                    return true;
                case Accessibility.ProtectedAndInternal:
                case Accessibility.Internal:
                    // TODO: Code coverage
                    return _semanticDocument.SemanticModel.Compilation.Assembly.IsSameAssemblyOrHasFriendAccessTo(
                        symbol.ContainingAssembly);
 
                default:
                    return false;
            }
        }
    }
}