Example #1
0
        private static async Task <Solution> GetSolutionWithBaseAddedAsync(
            Solution solution,
            AnnotatedSymbolMapping symbolMapping,
            INamedTypeSymbol newType,
            ImmutableArray <ExtractClassMemberAnalysisResult> memberAnalysisResults,
            CancellationToken cancellationToken
            )
        {
            var unformattedSolution = solution;
            var remainingResults    = new List <ExtractClassMemberAnalysisResult>(
                memberAnalysisResults
                );

            foreach (var documentId in symbolMapping.DocumentIdsToSymbolMap.Keys)
            {
                if (remainingResults.IsEmpty())
                {
                    // All results have been taken care of
                    break;
                }

                var document    = solution.GetRequiredDocument(documentId);
                var currentRoot = await document
                                  .GetRequiredSyntaxRootAsync(cancellationToken)
                                  .ConfigureAwait(false);

                var typeDeclaration = currentRoot
                                      .GetAnnotatedNodes(symbolMapping.TypeNodeAnnotation)
                                      .SingleOrDefault();
                if (typeDeclaration == null)
                {
                    continue;
                }

                var syntaxGenerator = SyntaxGenerator.GetGenerator(document);
                var typeReference   = syntaxGenerator.TypeExpression(newType);

                currentRoot = currentRoot.ReplaceNode(
                    typeDeclaration,
                    syntaxGenerator.AddBaseType(typeDeclaration, typeReference)
                    );

                unformattedSolution = document.WithSyntaxRoot(currentRoot).Project.Solution;
                // Only need to update on declaration of the type
                break;
            }

            return(unformattedSolution);
        }
        protected override async Task <IEnumerable <CodeActionOperation> > ComputeOperationsAsync(object options, CancellationToken cancellationToken)
        {
            if (options is ExtractClassOptions extractClassOptions)
            {
                // Map the symbols we're removing to annotations
                // so we can find them easily
                var codeGenerator = _document.GetRequiredLanguageService <ICodeGenerationService>();
                var symbolMapping = await AnnotatedSymbolMapping.CreateAsync(
                    extractClassOptions.MemberAnalysisResults.Select(m => m.Member),
                    _document.Project.Solution,
                    _selectedTypeDeclarationNode,
                    cancellationToken).ConfigureAwait(false);

                var namespaceService = _document.GetRequiredLanguageService <AbstractExtractInterfaceService>();

                // Create the symbol for the new type
                var newType = CodeGenerationSymbolFactory.CreateNamedTypeSymbol(
                    _selectedType.GetAttributes(),
                    _selectedType.DeclaredAccessibility,
                    _selectedType.GetSymbolModifiers(),
                    TypeKind.Class,
                    extractClassOptions.TypeName,
                    typeParameters: ExtractTypeHelpers.GetRequiredTypeParametersForMembers(_selectedType, extractClassOptions.MemberAnalysisResults.Select(m => m.Member)));

                var containingNamespaceDisplay = namespaceService.GetContainingNamespaceDisplay(
                    _selectedType,
                    _document.Project.CompilationOptions);

                // Add the new type to the solution. It can go in a new file or
                // be added to an existing. The returned document is always the document
                // containing the new type
                var(updatedDocument, typeAnnotation) = extractClassOptions.SameFile
                    ? await ExtractTypeHelpers.AddTypeToExistingFileAsync(
                    symbolMapping.AnnotatedSolution.GetRequiredDocument(_document.Id),
                    newType,
                    symbolMapping,
                    _fallbackOptions,
                    cancellationToken).ConfigureAwait(false)
                    : await ExtractTypeHelpers.AddTypeToNewFileAsync(
                    symbolMapping.AnnotatedSolution,
                    containingNamespaceDisplay,
                    extractClassOptions.FileName,
                    _document.Project.Id,
                    _document.Folders,
                    newType,
                    _document,
                    _fallbackOptions,
                    cancellationToken).ConfigureAwait(false);

                // Update the original type to have the new base
                var solutionWithUpdatedOriginalType = await GetSolutionWithBaseAddedAsync(
                    updatedDocument.Project.Solution,
                    symbolMapping,
                    newType,
                    extractClassOptions.MemberAnalysisResults,
                    cancellationToken).ConfigureAwait(false);

                // After all the changes, make sure we're using the most up to date symbol
                // as the destination for pulling members into
                var documentWithTypeDeclaration = solutionWithUpdatedOriginalType.GetRequiredDocument(updatedDocument.Id);
                var newTypeAfterEdits           = await GetNewTypeSymbolAsync(documentWithTypeDeclaration, typeAnnotation, cancellationToken).ConfigureAwait(false);

                // Use Members Puller to move the members to the new symbol
                var finalSolution = await PullMembersUpAsync(
                    solutionWithUpdatedOriginalType,
                    newTypeAfterEdits,
                    symbolMapping,
                    extractClassOptions.MemberAnalysisResults,
                    cancellationToken).ConfigureAwait(false);

                return(new[] { new ApplyChangesOperation(finalSolution) });
            }
            else
            {
                // If user click cancel button, options will be null and hit this branch
                return(SpecializedCollections.EmptyEnumerable <CodeActionOperation>());
            }
        }
        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));
        }