Beispiel #1
0
        /// <summary>
        /// Finds references, refactors them, then moves the selected members to the destination.
        /// Used when the destination type/file already exists.
        /// </summary>
        /// <param name="selectedMembers">selected member symbols</param>
        /// <param name="oldMemberNodes">nodes corresponding to those symbols in the old solution, should have been annotated</param>
        /// <param name="oldSolution">solution without any members moved/refactored</param>
        /// <param name="newType">the type to move to, should be inserted into a document already</param>
        /// <param name="typeArgIndices">generic type arg indices to keep when refactoring generic class access to the new type. Empty if not relevant</param>
        /// <param name="sourceDocId">Id of the document where the mebers are being moved from</param>
        /// <returns>The solution with references refactored and members moved to the newType</returns>
        private async Task <Solution> RefactorAndMoveAsync(
            ImmutableArray <ISymbol> selectedMembers,
            ImmutableArray <SyntaxNode> oldMemberNodes,
            Solution oldSolution,
            INamedTypeSymbol newType,
            ImmutableArray <int> typeArgIndices,
            DocumentId sourceDocId,
            DocumentId newTypeDocId,
            CancellationToken cancellationToken)
        {
            // annotate our new type, in case our refactoring changes it
            var newTypeDoc = await oldSolution.GetRequiredDocumentAsync(newTypeDocId, cancellationToken : cancellationToken).ConfigureAwait(false);

            var newTypeRoot = await newTypeDoc.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false);

            var newTypeNode = newType.DeclaringSyntaxReferences
                              .SelectAsArray(sRef => sRef.GetSyntax(cancellationToken))
                              .First(node => newTypeRoot.Contains(node));

            newTypeRoot = newTypeRoot.TrackNodes(newTypeNode);
            oldSolution = newTypeDoc.WithSyntaxRoot(newTypeRoot).Project.Solution;

            // refactor references across the entire solution
            var memberReferenceLocations = await FindMemberReferencesAsync(selectedMembers, oldSolution, cancellationToken).ConfigureAwait(false);

            var projectToLocations          = memberReferenceLocations.ToLookup(loc => loc.location.Document.Project.Id);
            var solutionWithFixedReferences = await RefactorReferencesAsync(projectToLocations, oldSolution, newType, typeArgIndices, cancellationToken).ConfigureAwait(false);

            var sourceDoc = solutionWithFixedReferences.GetRequiredDocument(sourceDocId);

            // get back tracked nodes from our changes
            var root = await sourceDoc.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false);

            var semanticModel = await sourceDoc.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false);

            var members = oldMemberNodes
                          .Select(node => root.GetCurrentNode(node))
                          .WhereNotNull()
                          .SelectAsArray(node => (semanticModel.GetDeclaredSymbol(node, cancellationToken), false));

            newTypeDoc  = solutionWithFixedReferences.GetRequiredDocument(newTypeDoc.Id);
            newTypeRoot = await newTypeDoc.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false);

            var newTypeSemanticModel = await newTypeDoc.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false);

            newType = (INamedTypeSymbol)newTypeSemanticModel.GetRequiredDeclaredSymbol(newTypeRoot.GetCurrentNode(newTypeNode) !, cancellationToken);

            var pullMembersUpOptions = PullMembersUpOptionsBuilder.BuildPullMembersUpOptions(newType, members);

            return(await MembersPuller.PullMembersUpAsync(sourceDoc, pullMembersUpOptions, _fallbackOptions, cancellationToken).ConfigureAwait(false));
        }
        private async Task <Solution> PullMembersUpAsync(
            Solution solution,
            INamedTypeSymbol newType,
            AnnotatedSymbolMapping symbolMapping,
            ImmutableArray <ExtractClassMemberAnalysisResult> memberAnalysisResults,
            CancellationToken cancellationToken)
        {
            using var _ = ArrayBuilder <(ISymbol member, bool makeAbstract)> .GetInstance(out var pullMembersBuilder);

            using var _1 = ArrayBuilder <ExtractClassMemberAnalysisResult> .GetInstance(memberAnalysisResults.Length, out var remainingResults);

            remainingResults.AddRange(memberAnalysisResults);

            // For each document in the symbol mappings, we want to find the annotated nodes
            // of the members and get the current symbol that represents those after
            // any changes we made before members get pulled up into the base class.
            // We only need to worry about symbols that are actually being moved, so we track
            // the symbols from the member analysis.
            foreach (var(documentId, symbols) in symbolMapping.DocumentIdsToSymbolMap)
            {
                if (remainingResults.Count == 0)
                {
                    // All symbols have been taken care of
                    break;
                }

                var document      = solution.GetRequiredDocument(documentId);
                var syntaxFacts   = document.GetRequiredLanguageService <ISyntaxFactsService>();
                var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false);

                var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false);

                using var _2 = ArrayBuilder <ExtractClassMemberAnalysisResult> .GetInstance(remainingResults.Count, out var resultsToRemove);

                // Out of the remaining members that we need to move, does this
                // document contain the definition for that symbol? If so, add it to the builder
                // and remove it from the symbols we're looking for
                var memberAnalysisForDocumentSymbols = remainingResults.Where(analysis => symbols.Contains(analysis.Member));

                foreach (var memberAnalysis in memberAnalysisForDocumentSymbols)
                {
                    var annotation = symbolMapping.SymbolToDeclarationAnnotationMap[memberAnalysis.Member];

                    var nodeOrToken = root.GetAnnotatedNodesAndTokens(annotation).SingleOrDefault();
                    var node        = nodeOrToken.IsNode
                        ? nodeOrToken.AsNode()
                        : nodeOrToken.AsToken().Parent;

                    // If the node is null then the symbol mapping was wrong about
                    // the document containing the symbol.
                    RoslynDebug.AssertNotNull(node);

                    var currentSymbol = semanticModel.GetDeclaredSymbol(node, cancellationToken);

                    // If currentSymbol is null then no symbol is declared at the node and
                    // symbol mapping state is not right.
                    RoslynDebug.AssertNotNull(currentSymbol);

                    pullMembersBuilder.Add((currentSymbol, memberAnalysis.MakeAbstract));
                    resultsToRemove.Add(memberAnalysis);
                }

                // Remove the symbols we found in this document from the list
                // that we are looking for
                foreach (var resultToRemove in resultsToRemove)
                {
                    remainingResults.Remove(resultToRemove);
                }
            }

            // If we didn't find all of the symbols then something went really wrong
            Contract.ThrowIfFalse(remainingResults.Count == 0);

            var pullMemberUpOptions     = PullMembersUpOptionsBuilder.BuildPullMembersUpOptions(newType, pullMembersBuilder.ToImmutable());
            var updatedOriginalDocument = solution.GetRequiredDocument(_document.Id);

            return(await MembersPuller.PullMembersUpAsync(updatedOriginalDocument, pullMemberUpOptions, _fallbackOptions, cancellationToken).ConfigureAwait(false));
        }
        protected override async Task <IEnumerable <CodeActionOperation> > ComputeOperationsAsync(object options, CancellationToken cancellationToken)
        {
            if (options is not MoveStaticMembersOptions moveOptions || moveOptions.IsCancelled)
            {
                return(SpecializedCollections.EmptyEnumerable <CodeActionOperation>());
            }

            // Find the original doc root
            var syntaxFacts = _document.GetRequiredLanguageService <ISyntaxFactsService>();
            var root        = await _document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false);

            // add annotations to the symbols that we selected so we can find them later to pull up
            // These symbols should all have (singular) definitions, but in the case that we can't find
            // any location, we just won't move that particular symbol
            var memberNodes = moveOptions.SelectedMembers
                              .Select(symbol => symbol.Locations.FirstOrDefault())
                              .WhereNotNull()
                              .SelectAsArray(loc => loc.FindNode(cancellationToken));

            root = root.TrackNodes(memberNodes);
            var sourceDoc = _document.WithSyntaxRoot(root);

            var typeParameters = ExtractTypeHelpers.GetRequiredTypeParametersForMembers(_selectedType, moveOptions.SelectedMembers);
            // which indices of the old type params should we keep for a new class reference, used for refactoring usages
            var typeArgIndices = Enumerable.Range(0, _selectedType.TypeParameters.Length)
                                 .Where(i => typeParameters.Contains(_selectedType.TypeParameters[i]))
                                 .ToImmutableArrayOrEmpty();

            // even though we can move members here, we will move them by calling PullMembersUp
            var newType = CodeGenerationSymbolFactory.CreateNamedTypeSymbol(
                ImmutableArray.Create <AttributeData>(),
                Accessibility.NotApplicable,
                DeclarationModifiers.Static,
                GetNewTypeKind(_selectedType),
                moveOptions.TypeName,
                typeParameters: typeParameters);

            var(newDoc, annotation) = await ExtractTypeHelpers.AddTypeToNewFileAsync(
                sourceDoc.Project.Solution,
                moveOptions.NamespaceDisplay,
                moveOptions.FileName,
                _document.Project.Id,
                _document.Folders,
                newType,
                _document,
                _fallbackOptions,
                cancellationToken).ConfigureAwait(false);

            // get back type declaration in the newly created file
            var destRoot = await newDoc.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false);

            var destSemanticModel = await newDoc.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false);

            newType = (INamedTypeSymbol)destSemanticModel.GetRequiredDeclaredSymbol(destRoot.GetAnnotatedNodes(annotation).Single(), cancellationToken);

            // refactor references across the entire solution
            var memberReferenceLocations = await FindMemberReferencesAsync(moveOptions.SelectedMembers, newDoc.Project.Solution, cancellationToken).ConfigureAwait(false);

            var projectToLocations          = memberReferenceLocations.ToLookup(loc => loc.location.Document.Project.Id);
            var solutionWithFixedReferences = await RefactorReferencesAsync(projectToLocations, newDoc.Project.Solution, newType, typeArgIndices, cancellationToken).ConfigureAwait(false);

            sourceDoc = solutionWithFixedReferences.GetRequiredDocument(sourceDoc.Id);

            // get back nodes from our changes
            root = await sourceDoc.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false);

            var semanticModel = await sourceDoc.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false);

            var members = memberNodes
                          .Select(node => root.GetCurrentNode(node))
                          .WhereNotNull()
                          .SelectAsArray(node => (semanticModel.GetDeclaredSymbol(node, cancellationToken), false));

            var pullMembersUpOptions = PullMembersUpOptionsBuilder.BuildPullMembersUpOptions(newType, members);
            var movedSolution        = await MembersPuller.PullMembersUpAsync(sourceDoc, pullMembersUpOptions, _fallbackOptions, cancellationToken).ConfigureAwait(false);

            return(new CodeActionOperation[] { new ApplyChangesOperation(movedSolution) });
        }