File: src\Workspaces\SharedUtilitiesAndExtensions\Workspace\Core\LanguageServices\MoveDeclarationNearReference\AbstractMoveDeclarationNearReferenceService.State.cs
Web Access
Project: src\src\CodeStyle\Core\CodeFixes\Microsoft.CodeAnalysis.CodeStyle.Fixes.csproj (Microsoft.CodeAnalysis.CodeStyle.Fixes)
// 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.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.FindSymbols;
using Microsoft.CodeAnalysis.LanguageService;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.MoveDeclarationNearReference;
 
internal abstract partial class AbstractMoveDeclarationNearReferenceService<
    TService,
    TStatementSyntax,
    TLocalDeclarationStatementSyntax,
    TVariableDeclaratorSyntax>
{
    private sealed class State
    {
        public TLocalDeclarationStatementSyntax DeclarationStatement { get; private set; }
        public TVariableDeclaratorSyntax VariableDeclarator { get; private set; }
        public ILocalSymbol LocalSymbol { get; private set; }
        public SyntaxNode OutermostBlock { get; private set; }
        public SyntaxNode InnermostBlock { get; private set; }
        public IReadOnlyList<SyntaxNode> OutermostBlockStatements { get; private set; }
        public IReadOnlyList<SyntaxNode> InnermostBlockStatements { get; private set; }
        public TStatementSyntax FirstStatementAffectedInInnermostBlock { get; private set; }
        public int IndexOfDeclarationStatementInInnermostBlock { get; private set; }
        public int IndexOfFirstStatementAffectedInInnermostBlock { get; private set; }
 
        internal static async Task<State> GenerateAsync(
            TService service,
            Document document,
            TLocalDeclarationStatementSyntax statement,
            CancellationToken cancellationToken)
        {
            var state = new State();
            if (!await state.TryInitializeAsync(service, document, statement, cancellationToken).ConfigureAwait(false))
            {
                return null;
            }
 
            return state;
        }
 
        private async Task<bool> TryInitializeAsync(
            TService service,
            Document document,
            TLocalDeclarationStatementSyntax node,
            CancellationToken cancellationToken)
        {
            var syntaxFacts = document.GetRequiredLanguageService<ISyntaxFactsService>();
            var blockFacts = document.GetRequiredLanguageService<IBlockFactsService>();
 
            DeclarationStatement = node;
 
            var variables = syntaxFacts.GetVariablesOfLocalDeclarationStatement(DeclarationStatement);
            if (variables.Count != 1)
            {
                return false;
            }
 
            VariableDeclarator = (TVariableDeclaratorSyntax)variables[0];
            if (!service.IsValidVariableDeclarator(VariableDeclarator))
            {
                return false;
            }
 
            OutermostBlock = blockFacts.GetStatementContainer(DeclarationStatement);
            if (!blockFacts.IsExecutableBlock(OutermostBlock))
            {
                return false;
            }
 
            var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
            LocalSymbol = (ILocalSymbol)semanticModel.GetDeclaredSymbol(
                service.GetVariableDeclaratorSymbolNode(VariableDeclarator), cancellationToken);
            if (LocalSymbol == null)
            {
                // This can happen in broken code, for example: "{ object x; object }"
                return false;
            }
 
            var findReferencesResult = await SymbolFinder.FindReferencesAsync(LocalSymbol, document.Project.Solution, cancellationToken).ConfigureAwait(false);
            var findReferencesList = findReferencesResult.ToList();
            if (findReferencesList.Count != 1)
            {
                return false;
            }
 
            var references = findReferencesList[0].Locations.ToList();
            if (references.Count == 0)
            {
                return false;
            }
 
            var syntaxRoot = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
            var referencingStatements =
                (from r in references
                 let token = syntaxRoot.FindToken(r.Location.SourceSpan.Start)
                 let statement = token.GetAncestor<TStatementSyntax>()
                 where statement != null
                 select statement).ToSet();
 
            if (referencingStatements.Count == 0)
            {
                return false;
            }
 
            InnermostBlock = blockFacts.FindInnermostCommonExecutableBlock(referencingStatements);
            if (InnermostBlock == null)
            {
                return false;
            }
 
            InnermostBlockStatements = blockFacts.GetExecutableBlockStatements(InnermostBlock);
            OutermostBlockStatements = blockFacts.GetExecutableBlockStatements(OutermostBlock);
 
            var allAffectedStatements = new HashSet<TStatementSyntax>(referencingStatements.SelectMany(
                expr => expr.GetAncestorsOrThis<TStatementSyntax>()));
            FirstStatementAffectedInInnermostBlock = InnermostBlockStatements.Cast<TStatementSyntax>().FirstOrDefault(allAffectedStatements.Contains);
 
            if (FirstStatementAffectedInInnermostBlock == null)
            {
                return false;
            }
 
            if (FirstStatementAffectedInInnermostBlock == DeclarationStatement)
            {
                return false;
            }
 
            IndexOfDeclarationStatementInInnermostBlock = InnermostBlockStatements.IndexOf(DeclarationStatement);
            IndexOfFirstStatementAffectedInInnermostBlock = InnermostBlockStatements.IndexOf(FirstStatementAffectedInInnermostBlock);
            if (IndexOfDeclarationStatementInInnermostBlock >= 0 &&
                IndexOfDeclarationStatementInInnermostBlock < IndexOfFirstStatementAffectedInInnermostBlock)
            {
                // Don't want to move a decl with initializer past other decls in order to move it to the first
                // affected statement.  If we do we can end up in the following situation: 
#if false
                int x = 0;
                int y = 0;
                Console.WriteLine(x + y);
#endif
                // Each of these declarations will want to 'move' down to the WriteLine
                // statement and we don't want to keep offering the refactoring.  Note: this
                // solution is overly aggressive.  Technically if 'y' weren't referenced in
                // Console.Writeline, then it might be a good idea to move the 'x'.  But this
                // gives good enough behavior most of the time.
 
                // Note that if the variable declaration has no initializer, then we still want to offer
                // the move as the closest reference will be an assignment to the variable
                // and we should be able to merge the declaration and assignment into a single
                // statement.
                // So, we also check if the variable declaration has an initializer below.
 
                if (syntaxFacts.GetInitializerOfVariableDeclarator(VariableDeclarator) != null &&
                    InDeclarationStatementGroup(IndexOfDeclarationStatementInInnermostBlock, IndexOfFirstStatementAffectedInInnermostBlock))
                {
                    return false;
                }
            }
 
            var previousToken = FirstStatementAffectedInInnermostBlock.GetFirstToken().GetPreviousToken();
            var affectedSpan = TextSpan.FromBounds(previousToken.SpanStart, FirstStatementAffectedInInnermostBlock.Span.End);
            if (semanticModel.SyntaxTree.OverlapsHiddenPosition(affectedSpan, cancellationToken))
            {
                return false;
            }
 
            return true;
        }
 
        private bool InDeclarationStatementGroup(
            int originalIndexInBlock, int firstStatementIndexAffectedInBlock)
        {
            for (var i = originalIndexInBlock; i < firstStatementIndexAffectedInBlock; i++)
            {
                if (InnermostBlockStatements[i] is not TLocalDeclarationStatementSyntax)
                {
                    return false;
                }
            }
 
            return true;
        }
    }
}