/// <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) }); }