File: GenerateFromMembers\AbstractGenerateFromMembersCodeRefactoringProvider.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.
 
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeGeneration;
using Microsoft.CodeAnalysis.CodeRefactorings;
using Microsoft.CodeAnalysis.Diagnostics.Analyzers.NamingStyles;
using Microsoft.CodeAnalysis.LanguageService;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Shared.Naming;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.GenerateFromMembers;
 
internal abstract partial class AbstractGenerateFromMembersCodeRefactoringProvider : CodeRefactoringProvider
{
    protected AbstractGenerateFromMembersCodeRefactoringProvider()
    {
    }
 
    protected static async Task<SelectedMemberInfo?> GetSelectedMemberInfoAsync(
        Document document, TextSpan textSpan, bool allowPartialSelection, CancellationToken cancellationToken)
    {
        var syntaxFacts = document.GetRequiredLanguageService<ISyntaxFactsService>();
 
        var tree = await document.GetRequiredSyntaxTreeAsync(cancellationToken).ConfigureAwait(false);
        var selectedDeclarations = await syntaxFacts.GetSelectedFieldsAndPropertiesAsync(
            tree, textSpan, allowPartialSelection, cancellationToken).ConfigureAwait(false);
 
        if (selectedDeclarations.Length > 0)
        {
            var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false);
            var selectedMembers = selectedDeclarations.Select(
                d => semanticModel.GetDeclaredSymbol(d, cancellationToken)).WhereNotNull().ToImmutableArray();
            if (selectedMembers.Length > 0)
            {
                var containingType = selectedMembers.First().ContainingType;
                if (containingType != null)
                {
                    return new SelectedMemberInfo(containingType, selectedDeclarations, selectedMembers);
                }
            }
        }
 
        return null;
    }
 
    protected static bool IsReadableInstanceFieldOrProperty(ISymbol symbol)
        => !symbol.IsStatic && IsReadableFieldOrProperty(symbol);
 
    protected static bool IsWritableInstanceFieldOrProperty(ISymbol symbol)
        => !symbol.IsStatic && IsWritableFieldOrProperty(symbol);
 
    private static bool IsReadableFieldOrProperty(ISymbol symbol)
        => symbol switch
        {
            IFieldSymbol field => IsViableField(field),
            IPropertySymbol property => IsViableProperty(property) && !property.IsWriteOnly,
            _ => false,
        };
 
    private static bool IsWritableFieldOrProperty(ISymbol symbol)
        => symbol switch
        {
            // Can use non const fields and properties with setters in them.
            IFieldSymbol field => IsViableField(field) && !field.IsConst,
            IPropertySymbol property => IsViableProperty(property) && property.IsWritableInConstructor(),
            _ => false,
        };
 
    private static bool IsViableField(IFieldSymbol field)
        => field.AssociatedSymbol == null;
 
    private static bool IsViableProperty(IPropertySymbol property)
        => property.Parameters.IsEmpty;
 
    /// <summary>
    /// Returns an array of parameter symbols that correspond to selected member symbols.
    /// If a selected member symbol has an empty base identifier name, the parameter symbol will not be added.
    /// </summary>
    /// <param name="selectedMembers"></param>
    /// <param name="rules"></param>
    /// <returns></returns>
    protected static ImmutableArray<IParameterSymbol> DetermineParameters(
        ImmutableArray<ISymbol> selectedMembers, ImmutableArray<NamingRule> rules)
    {
        using var _ = ArrayBuilder<IParameterSymbol>.GetInstance(out var parameters);
 
        foreach (var symbol in selectedMembers)
        {
            var type = symbol.GetMemberType();
            if (type == null)
                continue;
 
            var identifierNameParts = IdentifierNameParts.CreateIdentifierNameParts(symbol, rules);
            if (identifierNameParts.BaseName == "")
            {
                continue;
            }
 
            var parameterNamingRule = rules.Where(rule => rule.SymbolSpecification.AppliesTo(SymbolKind.Parameter, Accessibility.NotApplicable)).First();
            var parameterName = parameterNamingRule.NamingStyle.MakeCompliant(identifierNameParts.BaseName).First();
 
            parameters.Add(CodeGenerationSymbolFactory.CreateParameterSymbol(
                attributes: default,
                refKind: RefKind.None,
                isParams: false,
                type: type,
                name: parameterName));
        }
 
        return parameters.ToImmutableAndClear();
    }
 
    protected static readonly SymbolDisplayFormat SimpleFormat =
        new(
            typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameOnly,
            genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeParameters,
            parameterOptions: SymbolDisplayParameterOptions.IncludeParamsRefOut | SymbolDisplayParameterOptions.IncludeType,
            miscellaneousOptions: SymbolDisplayMiscellaneousOptions.UseSpecialTypes);
}