File: ExtractMethod\CSharpMethodExtractor.CSharpCodeGenerator.ExpressionCodeGenerator.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.Collections.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CSharp.CodeGeneration;
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.ExtractMethod;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.CSharp.ExtractMethod;
 
internal sealed partial class CSharpMethodExtractor
{
    private abstract partial class CSharpCodeGenerator
    {
        private sealed class ExpressionCodeGenerator(
            CSharpSelectionResult selectionResult,
            AnalyzerResult analyzerResult,
            CSharpCodeGenerationOptions options,
            bool localFunction) : CSharpCodeGenerator(selectionResult, analyzerResult, options, localFunction)
        {
            protected override SyntaxToken CreateMethodName()
            {
                var methodName = GenerateMethodNameFromUserPreference();
 
                var containingScope = this.SelectionResult.GetContainingScope();
 
                methodName = GetMethodNameBasedOnExpression(methodName, containingScope);
 
                var semanticModel = SemanticDocument.SemanticModel;
                var nameGenerator = new UniqueNameGenerator(semanticModel);
                return SyntaxFactory.Identifier(nameGenerator.CreateUniqueMethodName(containingScope, methodName));
            }
 
            private static string GetMethodNameBasedOnExpression(string methodName, SyntaxNode expression)
            {
                if (expression.Parent != null &&
                    expression.Parent.Kind() == SyntaxKind.EqualsValueClause &&
                    expression.Parent.Parent != null &&
                    expression.Parent.Parent.Kind() == SyntaxKind.VariableDeclarator)
                {
                    var name = ((VariableDeclaratorSyntax)expression.Parent.Parent).Identifier.ValueText;
                    return (name != null && name.Length > 0) ? MakeMethodName("Get", name, methodName.Equals(NewMethodCamelCaseStr)) : methodName;
                }
 
                if (expression is MemberAccessExpressionSyntax memberAccess)
                {
                    expression = memberAccess.Name;
                }
 
                if (expression is NameSyntax)
                {
                    SimpleNameSyntax unqualifiedName;
 
                    switch (expression.Kind())
                    {
                        case SyntaxKind.IdentifierName:
                        case SyntaxKind.GenericName:
                            unqualifiedName = (SimpleNameSyntax)expression;
                            break;
                        case SyntaxKind.QualifiedName:
                            unqualifiedName = ((QualifiedNameSyntax)expression).Right;
                            break;
                        case SyntaxKind.AliasQualifiedName:
                            unqualifiedName = ((AliasQualifiedNameSyntax)expression).Name;
                            break;
                        default:
                            throw new System.NotSupportedException("Unexpected name kind: " + expression.Kind().ToString());
                    }
 
                    var unqualifiedNameIdentifierValueText = unqualifiedName.Identifier.ValueText;
                    return (unqualifiedNameIdentifierValueText != null && unqualifiedNameIdentifierValueText.Length > 0) ?
                        MakeMethodName("Get", unqualifiedNameIdentifierValueText, methodName.Equals(NewMethodCamelCaseStr)) : methodName;
                }
 
                return methodName;
            }
 
            protected override ImmutableArray<StatementSyntax> GetInitialStatementsForMethodDefinitions()
            {
                Contract.ThrowIfFalse(this.SelectionResult.SelectionInExpression);
 
                // special case for array initializer
                var returnType = AnalyzerResult.ReturnType;
                var containingScope = this.SelectionResult.GetContainingScope();
 
                ExpressionSyntax expression;
                if (returnType.TypeKind == TypeKind.Array && containingScope is InitializerExpressionSyntax)
                {
                    var typeSyntax = returnType.GenerateTypeSyntax();
 
                    expression = SyntaxFactory.ArrayCreationExpression(typeSyntax as ArrayTypeSyntax, containingScope as InitializerExpressionSyntax);
                }
                else
                {
                    expression = containingScope as ExpressionSyntax;
                }
 
                if (AnalyzerResult.HasReturnType)
                {
                    return [SyntaxFactory.ReturnStatement(
                            WrapInCheckedExpressionIfNeeded(expression))];
                }
                else
                {
                    return [SyntaxFactory.ExpressionStatement(
                            WrapInCheckedExpressionIfNeeded(expression))];
                }
            }
 
            private ExpressionSyntax WrapInCheckedExpressionIfNeeded(ExpressionSyntax expression)
            {
                var kind = this.SelectionResult.UnderCheckedExpressionContext();
                if (kind == SyntaxKind.None)
                {
                    return expression;
                }
 
                return SyntaxFactory.CheckedExpression(kind, expression);
            }
 
            protected override SyntaxNode GetFirstStatementOrInitializerSelectedAtCallSite()
            {
                var scope = (SyntaxNode)this.SelectionResult.GetContainingScopeOf<StatementSyntax>();
                scope ??= this.SelectionResult.GetContainingScopeOf<FieldDeclarationSyntax>();
 
                scope ??= this.SelectionResult.GetContainingScopeOf<ConstructorInitializerSyntax>();
 
                // This is similar to FieldDeclaration case but we only want to do this 
                // if the member has an expression body.
                scope ??= this.SelectionResult.GetContainingScopeOf<ArrowExpressionClauseSyntax>().Parent;
 
                return scope;
            }
 
            protected override SyntaxNode GetLastStatementOrInitializerSelectedAtCallSite()
                => GetFirstStatementOrInitializerSelectedAtCallSite();
 
            protected override async Task<SyntaxNode> GetStatementOrInitializerContainingInvocationToExtractedMethodAsync(CancellationToken cancellationToken)
            {
                var enclosingStatement = GetFirstStatementOrInitializerSelectedAtCallSite();
 
                var callSignature = CreateCallSignature().WithAdditionalAnnotations(CallSiteAnnotation);
 
                var sourceNode = this.SelectionResult.GetContainingScope();
                Contract.ThrowIfTrue(
                    sourceNode.Parent is MemberAccessExpressionSyntax memberAccessExpression && memberAccessExpression.Name == sourceNode,
                    "invalid scope. given scope is not an expression");
 
                // To lower the chances that replacing sourceNode with callSignature will break the user's
                // code, we make the enclosing statement semantically explicit. This ends up being a little
                // bit more work because we need to annotate the sourceNode so that we can get back to it
                // after rewriting the enclosing statement.
                var updatedDocument = SemanticDocument.Document;
                var sourceNodeAnnotation = new SyntaxAnnotation();
                var enclosingStatementAnnotation = new SyntaxAnnotation();
                var newEnclosingStatement = enclosingStatement
                    .ReplaceNode(sourceNode, sourceNode.WithAdditionalAnnotations(sourceNodeAnnotation))
                    .WithAdditionalAnnotations(enclosingStatementAnnotation);
 
                updatedDocument = await updatedDocument.ReplaceNodeAsync(enclosingStatement, newEnclosingStatement, cancellationToken).ConfigureAwait(false);
 
                var updatedRoot = await updatedDocument.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
                newEnclosingStatement = updatedRoot.GetAnnotatedNodesAndTokens(enclosingStatementAnnotation).Single().AsNode();
 
                // because of the complexification we cannot guarantee that there is only one annotation.
                // however complexification of names is prepended, so the last annotation should be the original one.
                sourceNode = updatedRoot.GetAnnotatedNodesAndTokens(sourceNodeAnnotation).Last().AsNode();
 
                return newEnclosingStatement.ReplaceNode(sourceNode, callSignature);
            }
        }
    }
}