File: ExtractMethod\CSharpMethodExtractor.Analyzer.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.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.ExtractMethod;
using Microsoft.CodeAnalysis.Operations;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.CSharp.ExtractMethod
{
    internal partial class CSharpMethodExtractor : MethodExtractor
    {
        private class CSharpAnalyzer : Analyzer
        {
            private static readonly HashSet<int> s_nonNoisySyntaxKindSet = new HashSet<int>(new int[] { (int)SyntaxKind.WhitespaceTrivia, (int)SyntaxKind.EndOfLineTrivia });
 
            public static Task<AnalyzerResult> AnalyzeAsync(SelectionResult selectionResult, bool localFunction, CancellationToken cancellationToken)
            {
                var analyzer = new CSharpAnalyzer(selectionResult, localFunction, cancellationToken);
                return analyzer.AnalyzeAsync();
            }
 
            public CSharpAnalyzer(SelectionResult selectionResult, bool localFunction, CancellationToken cancellationToken)
                : base(selectionResult, localFunction, cancellationToken)
            {
            }
 
            protected override VariableInfo CreateFromSymbol(
                Compilation compilation,
                ISymbol symbol,
                ITypeSymbol type,
                VariableStyle style,
                bool variableDeclared)
            {
                return CreateFromSymbolCommon<LocalDeclarationStatementSyntax>(compilation, symbol, type, style, s_nonNoisySyntaxKindSet);
            }
 
            protected override int GetIndexOfVariableInfoToUseAsReturnValue(IList<VariableInfo> variableInfo)
            {
                var numberOfOutParameters = 0;
                var numberOfRefParameters = 0;
 
                var outSymbolIndex = -1;
                var refSymbolIndex = -1;
 
                for (var i = 0; i < variableInfo.Count; i++)
                {
                    var variable = variableInfo[i];
 
                    // there should be no-one set as return value yet
                    Contract.ThrowIfTrue(variable.UseAsReturnValue);
 
                    if (!variable.CanBeUsedAsReturnValue)
                    {
                        continue;
                    }
 
                    // check modifier
                    if (variable.ParameterModifier == ParameterBehavior.Ref)
                    {
                        numberOfRefParameters++;
                        refSymbolIndex = i;
                    }
                    else if (variable.ParameterModifier == ParameterBehavior.Out)
                    {
                        numberOfOutParameters++;
                        outSymbolIndex = i;
                    }
                }
 
                // if there is only one "out" or "ref", that will be converted to return statement.
                if (numberOfOutParameters == 1)
                {
                    return outSymbolIndex;
                }
 
                if (numberOfRefParameters == 1)
                {
                    return refSymbolIndex;
                }
 
                return -1;
            }
 
            protected override ITypeSymbol GetRangeVariableType(SemanticModel model, IRangeVariableSymbol symbol)
            {
                var info = model.GetSpeculativeTypeInfo(SelectionResult.FinalSpan.Start, SyntaxFactory.ParseName(symbol.Name), SpeculativeBindingOption.BindAsExpression);
                if (Microsoft.CodeAnalysis.Shared.Extensions.ISymbolExtensions.IsErrorType(info.Type))
                {
                    return null;
                }
 
                return info.Type == null || info.Type.SpecialType == Microsoft.CodeAnalysis.SpecialType.System_Object
                    ? info.Type
                    : info.ConvertedType;
            }
 
            protected override Tuple<SyntaxNode, SyntaxNode> GetFlowAnalysisNodeRange()
            {
                var csharpSelectionResult = SelectionResult as CSharpSelectionResult;
 
                var first = csharpSelectionResult.GetFirstStatement();
                var last = csharpSelectionResult.GetLastStatement();
 
                // single statement case
                if (first == last ||
                    first.Span.Contains(last.Span))
                {
                    return new Tuple<SyntaxNode, SyntaxNode>(first, first);
                }
 
                // multiple statement case
                var firstUnderContainer = csharpSelectionResult.GetFirstStatementUnderContainer();
                var lastUnderContainer = csharpSelectionResult.GetLastStatementUnderContainer();
                return new Tuple<SyntaxNode, SyntaxNode>(firstUnderContainer, lastUnderContainer);
            }
 
            protected override bool ContainsReturnStatementInSelectedCode(IEnumerable<SyntaxNode> jumpOutOfRegionStatements)
                => jumpOutOfRegionStatements.Where(n => n is ReturnStatementSyntax).Any();
 
            protected override bool ReadOnlyFieldAllowed()
            {
                var scope = SelectionResult.GetContainingScopeOf<ConstructorDeclarationSyntax>();
                return scope == null;
            }
 
            protected override ITypeSymbol GetSymbolType(SemanticModel semanticModel, ISymbol symbol)
            {
                var selectionOperation = semanticModel.GetOperation(SelectionResult.GetContainingScope());
 
                // Check if null is possibly assigned to the symbol. If it is, leave nullable annotation as is, otherwise
                // we can modify the annotation to be NotAnnotated to code that more likely matches the user's intent.
                if (selectionOperation is not null &&
                    NullableHelpers.IsSymbolAssignedPossiblyNullValue(semanticModel, selectionOperation, symbol) == false)
                {
                    return base.GetSymbolType(semanticModel, symbol).WithNullableAnnotation(NullableAnnotation.NotAnnotated);
                }
 
                return base.GetSymbolType(semanticModel, symbol);
            }
        }
    }
}