File: GenerateConstructorFromMembers\AbstractGenerateConstructorFromMembersCodeRefactoringProvider.ConstructorDelegatingCodeAction.cs
Web Access
Project: ..\..\..\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.
 
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.AddImport;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeGeneration;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.GenerateConstructorFromMembers
{
    internal abstract partial class AbstractGenerateConstructorFromMembersCodeRefactoringProvider
    {
        private sealed class ConstructorDelegatingCodeAction : CodeAction
        {
            private readonly AbstractGenerateConstructorFromMembersCodeRefactoringProvider _service;
            private readonly Document _document;
            private readonly State _state;
            private readonly bool _addNullChecks;
            private readonly CleanCodeGenerationOptionsProvider _fallbackOptions;
 
            public ConstructorDelegatingCodeAction(
                AbstractGenerateConstructorFromMembersCodeRefactoringProvider service,
                Document document,
                State state,
                bool addNullChecks,
                CleanCodeGenerationOptionsProvider fallbackOptions)
            {
                _service = service;
                _document = document;
                _state = state;
                _addNullChecks = addNullChecks;
                _fallbackOptions = fallbackOptions;
            }
 
            protected override async Task<Document> GetChangedDocumentAsync(CancellationToken cancellationToken)
            {
                // First, see if there are any constructors that would take the first 'n' arguments
                // we've provided.  If so, delegate to those, and then create a field for any
                // remaining arguments.  Try to match from largest to smallest.
                //
                // Otherwise, just generate a normal constructor that assigns any provided
                // parameters into fields.
                var project = _document.Project;
                var languageServices = project.Solution.Services.GetLanguageServices(_state.ContainingType.Language);
 
                var semanticModel = await _document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false);
                var factory = languageServices.GetRequiredService<SyntaxGenerator>();
                var codeGenerationService = languageServices.GetRequiredService<ICodeGenerationService>();
 
                Contract.ThrowIfNull(_state.DelegatedConstructor);
                var thisConstructorArguments = factory.CreateArguments(
                    _state.Parameters.Take(_state.DelegatedConstructor.Parameters.Length).ToImmutableArray());
 
                using var _1 = ArrayBuilder<SyntaxNode>.GetInstance(out var nullCheckStatements);
                using var _2 = ArrayBuilder<SyntaxNode>.GetInstance(out var assignStatements);
 
                var useThrowExpressions = await _service.PrefersThrowExpressionAsync(_document, _fallbackOptions, cancellationToken).ConfigureAwait(false);
 
                for (var i = _state.DelegatedConstructor.Parameters.Length; i < _state.Parameters.Length; i++)
                {
                    var symbolName = _state.SelectedMembers[i].Name;
                    var parameter = _state.Parameters[i];
 
                    var fieldAccess = factory.MemberAccessExpression(
                        factory.ThisExpression(),
                        factory.IdentifierName(symbolName));
 
                    factory.AddAssignmentStatements(
                        semanticModel, parameter, fieldAccess,
                        _addNullChecks, useThrowExpressions,
                        nullCheckStatements, assignStatements);
                }
 
                var syntaxTree = await _document.GetRequiredSyntaxTreeAsync(cancellationToken).ConfigureAwait(false);
 
                // If the user has selected a set of members (i.e. TextSpan is not empty), then we will
                // choose the right location (i.e. null) to insert the constructor.  However, if they're 
                // just invoking the feature manually at a specific location, then we'll insert the 
                // members at that specific place in the class/struct.
                var afterThisLocation = _state.TextSpan.IsEmpty
                    ? syntaxTree.GetLocation(_state.TextSpan)
                    : null;
 
                var statements = nullCheckStatements.ToImmutable().Concat(assignStatements.ToImmutable());
                var result = await codeGenerationService.AddMethodAsync(
                    new CodeGenerationSolutionContext(
                        _document.Project.Solution,
                        new CodeGenerationContext(
                            contextLocation: syntaxTree.GetLocation(_state.TextSpan),
                            afterThisLocation: afterThisLocation),
                        _fallbackOptions),
                    _state.ContainingType,
                    CodeGenerationSymbolFactory.CreateConstructorSymbol(
                        attributes: default,
                        accessibility: _state.ContainingType.IsAbstractClass() ? Accessibility.Protected : Accessibility.Public,
                        modifiers: new DeclarationModifiers(),
                        typeName: _state.ContainingType.Name,
                        parameters: _state.Parameters,
                        statements: statements,
                        thisConstructorArguments: thisConstructorArguments),
                    cancellationToken: cancellationToken).ConfigureAwait(false);
 
                return await AddNavigationAnnotationAsync(result, cancellationToken).ConfigureAwait(false);
            }
 
            public override string Title
            {
                get
                {
                    var parameters = _state.Parameters.Select(p => _service.ToDisplayString(p, SimpleFormat));
                    var parameterString = string.Join(", ", parameters);
 
                    return string.Format(FeaturesResources.Generate_delegating_constructor_0_1,
                        _state.ContainingType.Name, parameterString);
                }
            }
        }
    }
}