public async Task <ExtractClassOptions?> GetExtractClassOptionsAsync(Document document, INamedTypeSymbol selectedType, ISymbol?selectedMember) { var notificationService = document.Project.Solution.Workspace.Services.GetRequiredService <INotificationService>(); var membersInType = selectedType.GetMembers(). WhereAsArray(member => MemberAndDestinationValidator.IsMemberValid(member)); var memberViewModels = membersInType .SelectAsArray(member => new PullMemberUpSymbolViewModel(member, _glyphService) { // The member user selected will be checked at the beginning. IsChecked = SymbolEquivalenceComparer.Instance.Equals(selectedMember, member), MakeAbstract = false, IsMakeAbstractCheckable = !member.IsKind(SymbolKind.Field) && !member.IsAbstract, IsCheckable = true }); using var cancellationTokenSource = new CancellationTokenSource(); var memberToDependentsMap = SymbolDependentsBuilder.FindMemberToDependentsMap(membersInType, document.Project, cancellationTokenSource.Token); var conflictingTypeNames = selectedType.ContainingNamespace.GetAllTypes(cancellationTokenSource.Token).Select(t => t.Name); var candidateName = selectedType.Name + "Base"; var defaultTypeName = NameGenerator.GenerateUniqueName(candidateName, name => !conflictingTypeNames.Contains(name)); var containingNamespaceDisplay = selectedType.ContainingNamespace.IsGlobalNamespace ? string.Empty : selectedType.ContainingNamespace.ToDisplayString(); var generatedNameTypeParameterSuffix = ExtractTypeHelpers.GetTypeParameterSuffix(document, selectedType, membersInType); var viewModel = new ExtractClassViewModel( _waitIndicator, notificationService, memberViewModels, memberToDependentsMap, defaultTypeName, containingNamespaceDisplay, document.Project.Language, generatedNameTypeParameterSuffix, conflictingTypeNames.ToImmutableArray(), document.GetRequiredLanguageService <ISyntaxFactsService>()); await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(); var dialog = new ExtractClassDialog(viewModel); var result = dialog.ShowModal(); if (result.GetValueOrDefault()) { return(new ExtractClassOptions( viewModel.DestinationViewModel.FileName, viewModel.DestinationViewModel.TypeName, viewModel.DestinationViewModel.Destination == CommonControls.NewTypeDestination.CurrentFile, viewModel.MemberSelectionViewModel.CheckedMembers.SelectAsArray(m => new ExtractClassMemberAnalysisResult(m.Symbol, m.MakeAbstract)))); } return(null); }
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>()); } }
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) }); }