File: ExtractMethod\CSharpSelectionResult.cs
Web Access
Project: ..\..\..\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;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.LanguageService;
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 abstract partial class CSharpSelectionResult : SelectionResult
    {
        public static async Task<CSharpSelectionResult> CreateAsync(
            OperationStatus status,
            TextSpan originalSpan,
            TextSpan finalSpan,
            ExtractMethodOptions options,
            bool selectionInExpression,
            SemanticDocument document,
            SyntaxToken firstToken,
            SyntaxToken lastToken,
            CancellationToken cancellationToken)
        {
            Contract.ThrowIfNull(status);
            Contract.ThrowIfNull(document);
 
            var firstTokenAnnotation = new SyntaxAnnotation();
            var lastTokenAnnotation = new SyntaxAnnotation();
 
            var root = await document.Document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
            var newDocument = await SemanticDocument.CreateAsync(document.Document.WithSyntaxRoot(root.AddAnnotations(
                    new[]
                    {
                        Tuple.Create<SyntaxToken, SyntaxAnnotation>(firstToken, firstTokenAnnotation),
                        Tuple.Create<SyntaxToken, SyntaxAnnotation>(lastToken, lastTokenAnnotation)
                    })), cancellationToken).ConfigureAwait(false);
 
            if (selectionInExpression)
            {
                return new ExpressionResult(
                    status, originalSpan, finalSpan, options, selectionInExpression,
                    newDocument, firstTokenAnnotation, lastTokenAnnotation);
            }
            else
            {
                return new StatementResult(
                    status, originalSpan, finalSpan, options, selectionInExpression,
                    newDocument, firstTokenAnnotation, lastTokenAnnotation);
            }
        }
 
        protected CSharpSelectionResult(
            OperationStatus status,
            TextSpan originalSpan,
            TextSpan finalSpan,
            ExtractMethodOptions options,
            bool selectionInExpression,
            SemanticDocument document,
            SyntaxAnnotation firstTokenAnnotation,
            SyntaxAnnotation lastTokenAnnotation)
            : base(status, originalSpan, finalSpan, options, selectionInExpression,
                   document, firstTokenAnnotation, lastTokenAnnotation)
        {
        }
 
        protected override bool UnderAnonymousOrLocalMethod(SyntaxToken token, SyntaxToken firstToken, SyntaxToken lastToken)
        {
            var current = token.Parent;
            for (; current != null; current = current.Parent)
            {
                if (current is MemberDeclarationSyntax or
                    SimpleLambdaExpressionSyntax or
                    ParenthesizedLambdaExpressionSyntax or
                    AnonymousMethodExpressionSyntax or
                    LocalFunctionStatementSyntax)
                {
                    break;
                }
            }
 
            if (current is null or MemberDeclarationSyntax)
            {
                return false;
            }
 
            // make sure the selection contains the lambda
            return firstToken.SpanStart <= current.GetFirstToken().SpanStart &&
                   current.GetLastToken().Span.End <= lastToken.Span.End;
        }
 
        public StatementSyntax GetFirstStatement()
            => GetFirstStatement<StatementSyntax>();
 
        public StatementSyntax GetLastStatement()
            => GetLastStatement<StatementSyntax>();
 
        public StatementSyntax GetFirstStatementUnderContainer()
        {
            Contract.ThrowIfTrue(SelectionInExpression);
 
            var firstToken = GetFirstTokenInSelection();
            var statement = firstToken.Parent.GetStatementUnderContainer();
            Contract.ThrowIfNull(statement);
 
            return statement;
        }
 
        public StatementSyntax GetLastStatementUnderContainer()
        {
            Contract.ThrowIfTrue(SelectionInExpression);
 
            var lastToken = GetLastTokenInSelection();
            var statement = lastToken.Parent.GetStatementUnderContainer();
 
            Contract.ThrowIfNull(statement);
            var firstStatementUnderContainer = GetFirstStatementUnderContainer();
            Contract.ThrowIfFalse(CSharpSyntaxFacts.Instance.AreStatementsInSameContainer(statement, firstStatementUnderContainer));
 
            return statement;
        }
 
        public SyntaxNode GetInnermostStatementContainer()
        {
            Contract.ThrowIfFalse(SelectionInExpression);
            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;
            }
 
            Contract.ThrowIfFalse(last.IsParentKind(SyntaxKind.GlobalStatement));
            Contract.ThrowIfFalse(last.Parent.IsParentKind(SyntaxKind.CompilationUnit));
            return last.Parent.Parent;
        }
 
        public bool ShouldPutUnsafeModifier()
        {
            var token = GetFirstTokenInSelection();
            var ancestors = token.GetAncestors<SyntaxNode>();
 
            // if enclosing type contains unsafe keyword, we don't need to put it again
            if (ancestors.Where(a => SyntaxFacts.IsTypeDeclaration(a.Kind()))
                         .Cast<MemberDeclarationSyntax>()
                         .Any(m => m.GetModifiers().Any(SyntaxKind.UnsafeKeyword)))
            {
                return false;
            }
 
            return token.Parent.IsUnsafeContext();
        }
 
        public SyntaxKind UnderCheckedExpressionContext()
            => UnderCheckedContext<CheckedExpressionSyntax>();
 
        public SyntaxKind UnderCheckedStatementContext()
            => UnderCheckedContext<CheckedStatementSyntax>();
 
        private SyntaxKind UnderCheckedContext<T>() where T : SyntaxNode
        {
            var token = GetFirstTokenInSelection();
            var contextNode = token.Parent.GetAncestor<T>();
            if (contextNode == null)
            {
                return SyntaxKind.None;
            }
 
            return contextNode.Kind();
        }
    }
}