File: ExtractMethod\CSharpMethodExtractor.Analyzer.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.
 
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.ExtractMethod;
 
namespace Microsoft.CodeAnalysis.CSharp.ExtractMethod;
 
internal sealed partial class CSharpExtractMethodService
{
    internal sealed partial class CSharpMethodExtractor
    {
        private sealed class CSharpAnalyzer(SelectionResult selectionResult, bool localFunction, CancellationToken cancellationToken)
            : Analyzer(selectionResult, localFunction, cancellationToken)
        {
            protected override bool TreatOutAsRef
                => false;
 
            protected override bool IsInPrimaryConstructorBaseType()
                => this.SelectionResult.GetContainingScopeOf<PrimaryConstructorBaseTypeSyntax>() != null;
 
            protected override VariableInfo CreateFromSymbol(
                ISymbol symbol, ITypeSymbol type, VariableStyle style, bool variableDeclared)
            {
                return CreateFromSymbolCommon(symbol, type, style);
            }
 
            protected override ITypeSymbol? GetRangeVariableType(IRangeVariableSymbol symbol)
            {
                var info = this.SemanticModel.GetSpeculativeTypeInfo(SelectionResult.FinalSpan.Start, SyntaxFactory.ParseName(symbol.Name), SpeculativeBindingOption.BindAsExpression);
                if (info.Type is IErrorTypeSymbol)
                    return null;
 
                return info.Type == null || info.Type.SpecialType == SpecialType.System_Object
                    ? info.Type
                    : info.ConvertedType;
            }
 
            protected override bool ContainsReturnStatementInSelectedCode(ImmutableArray<SyntaxNode> exitPoints)
                => exitPoints.Any(n => n is ReturnStatementSyntax);
 
            protected override bool ReadOnlyFieldAllowed()
            {
                var scope = SelectionResult.GetContainingScopeOf<ConstructorDeclarationSyntax>();
                return scope == null;
            }
 
            protected override bool IsReadOutside(ISymbol symbol, HashSet<ISymbol> readOutsideMap)
            {
                if (!base.IsReadOutside(symbol, readOutsideMap))
                    return false;
 
                // Special case `using var v = ...` where the selection grabs the last statement that follows the local
                // declaration.  The compiler here considers the local variable 'read outside' since it makes it to the
                // implicit 'dispose' call that comes after the last statement.  However, as that implicit dispose would
                // move if we move the `using var v` entirely into the new method, then it's still safe to move as there's
                // no actual "explicit user read" that happens in the outer caller at all.
                if (!this.SelectionResult.IsExtractMethodOnExpression &&
                    symbol is ILocalSymbol { IsUsing: true, DeclaringSyntaxReferences: [var reference] } &&
                    reference.GetSyntax(this.CancellationToken) is VariableDeclaratorSyntax
                    {
                        Parent: VariableDeclarationSyntax
                        {
                            Parent: LocalDeclarationStatementSyntax
                            {
                                Parent: BlockSyntax { Statements: [.., var lastBlockStatement] },
                            },
                        }
                    })
                {
                    var lastStatement = this.SelectionResult.GetLastStatement();
                    if (lastStatement == lastBlockStatement)
                        return false;
                }
 
                return true;
            }
        }
    }
}