File: ExtractMethod\CSharpSelectionResult.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;
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.ExtractMethod;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.CSharp.ExtractMethod;
 
internal sealed partial class CSharpExtractMethodService
{
    internal abstract partial class CSharpSelectionResult(
        SemanticDocument document,
        SelectionType selectionType,
        TextSpan finalSpan)
        : SelectionResult(
            document, selectionType, finalSpan)
    {
        public static async Task<CSharpSelectionResult> CreateAsync(
            SemanticDocument document,
            FinalSelectionInfo selectionInfo,
            CancellationToken cancellationToken)
        {
            Contract.ThrowIfNull(document);
 
            var root = await document.Document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
            var newDocument = await SemanticDocument.CreateAsync(document.Document.WithSyntaxRoot(AddAnnotations(
                root,
                [
                    (selectionInfo.FirstTokenInFinalSpan, s_firstTokenAnnotation),
                    (selectionInfo.LastTokenInFinalSpan, s_lastTokenAnnotation)
                ])), cancellationToken).ConfigureAwait(false);
 
            var selectionType = selectionInfo.GetSelectionType();
            var finalSpan = selectionInfo.FinalSpan;
 
            return selectionType == SelectionType.Expression
                ? new ExpressionResult(newDocument, selectionType, finalSpan)
                : new StatementResult(newDocument, selectionType, finalSpan);
        }
 
        protected override OperationStatus ValidateLanguageSpecificRules(CancellationToken cancellationToken)
        {
            // Nothing language specific for C#.
            return OperationStatus.SucceededStatus;
        }
 
        protected override SyntaxNode GetNodeForDataFlowAnalysis()
        {
            var node = base.GetNodeForDataFlowAnalysis();
 
            // If we're returning a value by ref we actually want to do the analysis on the underlying expression.
            return node is RefExpressionSyntax refExpression
                ? refExpression.Expression
                : node;
        }
 
        public override StatementSyntax GetFirstStatementUnderContainer()
        {
            Contract.ThrowIfTrue(IsExtractMethodOnExpression);
 
            var firstToken = GetFirstTokenInSelection();
            var statement = firstToken.Parent.GetStatementUnderContainer();
            Contract.ThrowIfNull(statement);
 
            return statement;
        }
 
        public override StatementSyntax GetLastStatementUnderContainer()
        {
            Contract.ThrowIfTrue(IsExtractMethodOnExpression);
 
            var lastToken = GetLastTokenInSelection();
            var statement = lastToken.Parent.GetStatementUnderContainer();
 
            return statement;
        }
 
        public SyntaxNode GetInnermostStatementContainer()
        {
            Contract.ThrowIfFalse(IsExtractMethodOnExpression);
            var containingScope = GetContainingScope();
            var statements = containingScope.GetAncestorsOrThis<StatementSyntax>();
            StatementSyntax last = null;
 
            foreach (var statement in statements)
            {
                if (statement.IsStatementContainerNode())
                    return statement;
 
                last = statement;
            }
 
            // expression bodied member case
            var expressionBodiedMember = GetContainingScopeOf<ArrowExpressionClauseSyntax>();
            if (expressionBodiedMember != null)
            {
                // the class/struct declaration is the innermost statement container, since the 
                // member does not have a block body
                return GetContainingScopeOf<TypeDeclarationSyntax>();
            }
 
            // constructor initializer case
            var constructorInitializer = GetContainingScopeOf<ConstructorInitializerSyntax>();
            if (constructorInitializer != null)
                return constructorInitializer.Parent;
 
            // field initializer case
            var field = GetContainingScopeOf<FieldDeclarationSyntax>();
            if (field != null)
                return field.Parent;
 
            var primaryConstructorBaseType = GetContainingScopeOf<PrimaryConstructorBaseTypeSyntax>();
            if (primaryConstructorBaseType != null)
                return primaryConstructorBaseType.Parent;
 
            Contract.ThrowIfFalse(last.IsParentKind(SyntaxKind.GlobalStatement));
            Contract.ThrowIfFalse(last.Parent.IsParentKind(SyntaxKind.CompilationUnit));
            return last.Parent.Parent;
        }
 
        public override bool ContainsUnsupportedExitPointsStatements(ImmutableArray<SyntaxNode> exitPoints)
            => exitPoints.Any(n => n is not (BreakStatementSyntax or ContinueStatementSyntax or ReturnStatementSyntax));
 
        public override ImmutableArray<StatementSyntax> GetOuterReturnStatements(SyntaxNode commonRoot, ImmutableArray<SyntaxNode> exitPoints)
            => exitPoints.OfType<ReturnStatementSyntax>().ToImmutableArray().CastArray<StatementSyntax>();
 
        public override bool IsFinalSpanSemanticallyValidSpan(
            ImmutableArray<StatementSyntax> returnStatements, CancellationToken cancellationToken)
        {
            // Once we've gotten this far, everything is valid for us to return.  Only VB has special additional logic
            // it needs to apply at this point.
            return true;
        }
    }
}