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); }
private async Task <ExtractClassWithDialogCodeAction?> TryGetMemberActionAsync( CodeRefactoringContext context, IExtractClassOptionsService optionsService ) { var selectedMemberNode = await GetSelectedNodeAsync(context).ConfigureAwait(false); if (selectedMemberNode is null) { return(null); } var(document, span, cancellationToken) = context; var semanticModel = await document .GetRequiredSemanticModelAsync(cancellationToken) .ConfigureAwait(false); var selectedMember = semanticModel.GetDeclaredSymbol( selectedMemberNode, cancellationToken ); if (selectedMember is null || selectedMember.ContainingType is null) { return(null); } // Use same logic as pull members up for determining if a selected member // is valid to be moved into a base if (!MemberAndDestinationValidator.IsMemberValid(selectedMember)) { return(null); } var containingType = selectedMember.ContainingType; // Can't extract to a new type if there's already a base. Maybe // in the future we could inject a new type inbetween base and // current if (containingType.BaseType?.SpecialType != SpecialType.System_Object) { return(null); } var syntaxFacts = document.GetRequiredLanguageService <ISyntaxFactsService>(); var containingTypeDeclarationNode = selectedMemberNode.FirstAncestorOrSelf <SyntaxNode>( syntaxFacts.IsTypeDeclaration ); return(new ExtractClassWithDialogCodeAction( document, span, optionsService, containingType, containingTypeDeclarationNode !, selectedMember )); }
private async Task <ExtractClassWithDialogCodeAction?> TryGetMemberActionAsync(CodeRefactoringContext context, IExtractClassOptionsService optionsService) { var selectedMemberNodes = await GetSelectedNodesAsync(context).ConfigureAwait(false); if (selectedMemberNodes.IsEmpty) { return(null); } var(document, span, cancellationToken) = context; var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); var memberNodeSymbolPairs = selectedMemberNodes .SelectAsArray(m => (node: m, symbol: semanticModel.GetRequiredDeclaredSymbol(m, cancellationToken))) // Use same logic as pull members up for determining if a selected member // is valid to be moved into a base .WhereAsArray(pair => MemberAndDestinationValidator.IsMemberValid(pair.symbol)); if (memberNodeSymbolPairs.IsEmpty) { return(null); } var selectedMembers = memberNodeSymbolPairs.SelectAsArray(pair => pair.symbol); var containingType = selectedMembers.First().ContainingType; Contract.ThrowIfNull(containingType); // Treat the entire nodes' span as the span of interest here. That way if the user's location is closer to // a refactoring with a narrower span (for example, a span just on the name/parameters of a member, then it // will take precedence over us). var memberSpan = TextSpan.FromBounds( memberNodeSymbolPairs.First().node.FullSpan.Start, memberNodeSymbolPairs.Last().node.FullSpan.End); // Can't extract to a new type if there's already a base. Maybe // in the future we could inject a new type inbetween base and // current if (containingType.BaseType?.SpecialType != SpecialType.System_Object) { return(null); } var syntaxFacts = document.GetRequiredLanguageService <ISyntaxFactsService>(); var containingTypeDeclarationNode = selectedMemberNodes.First().FirstAncestorOrSelf <SyntaxNode>(syntaxFacts.IsTypeDeclaration); Contract.ThrowIfNull(containingTypeDeclarationNode); if (selectedMemberNodes.Any(m => m.FirstAncestorOrSelf <SyntaxNode>(syntaxFacts.IsTypeDeclaration) != containingTypeDeclarationNode)) { return(null); } return(new ExtractClassWithDialogCodeAction( document, memberSpan, optionsService, containingType, containingTypeDeclarationNode, context.Options, selectedMembers)); }
public PullMembersUpOptions GetPullMemberUpOptions( Document document, ISymbol selectedMember ) { var membersInType = selectedMember.ContainingType .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 baseTypeRootViewModel = BaseTypeTreeNodeViewModel.CreateBaseTypeTree( _glyphService, document.Project.Solution, selectedMember.ContainingType, cancellationTokenSource.Token ); var memberToDependentsMap = SymbolDependentsBuilder.FindMemberToDependentsMap( membersInType, document.Project, cancellationTokenSource.Token ); var viewModel = new PullMemberUpDialogViewModel( _waitIndicator, memberViewModels, baseTypeRootViewModel, memberToDependentsMap ); var dialog = new PullMemberUpDialog(viewModel); var result = dialog.ShowModal(); // Dialog has finshed its work, cancel finding dependents task. cancellationTokenSource.Cancel(); if (result.GetValueOrDefault()) { return(dialog.ViewModel.CreatePullMemberUpOptions()); } else { return(null); } }
public override async Task ComputeRefactoringsAsync(CodeRefactoringContext context) { var(document, span, cancellationToken) = context; var service = document.Project.Solution.Services.GetService <IMoveStaticMembersOptionsService>(); if (service == null) { return; } var selectedMemberNodes = await GetSelectedNodesAsync(context).ConfigureAwait(false); if (selectedMemberNodes.IsEmpty) { return; } var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); if (semanticModel == null) { return; } var memberNodeSymbolPairs = selectedMemberNodes .SelectAsArray(m => (node: m, symbol: semanticModel.GetRequiredDeclaredSymbol(m, cancellationToken))) // Use same logic as pull members up for determining if a selected member // is valid to be moved into a base .WhereAsArray(pair => MemberAndDestinationValidator.IsMemberValid(pair.symbol) && pair.symbol.IsStatic); if (memberNodeSymbolPairs.IsEmpty) { return; } var selectedMembers = memberNodeSymbolPairs.SelectAsArray(pair => pair.symbol); var containingType = selectedMembers.First().ContainingType; Contract.ThrowIfNull(containingType); if (selectedMembers.Any(m => !m.ContainingType.Equals(containingType))) { return; } // we want to use a span which covers all the selected viable member nodes, so that more specific nodes have priority var memberSpan = TextSpan.FromBounds( memberNodeSymbolPairs.First().node.FullSpan.Start, memberNodeSymbolPairs.Last().node.FullSpan.End); var action = new MoveStaticMembersWithDialogCodeAction(document, service, containingType, context.Options, selectedMembers); context.RegisterRefactoring(action, memberSpan); }
public override async Task ComputeRefactoringsAsync(CodeRefactoringContext context) { // Currently support to pull field, method, event, property and indexer up, // constructor, operator and finalizer are excluded. var document = context.Document; var cancellationToken = context.CancellationToken; var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var selectedMemberNode = await GetMatchedNodeAsync(document, context.Span, cancellationToken).ConfigureAwait(false); if (selectedMemberNode == null) { return; } var selectedMember = semanticModel.GetDeclaredSymbol(selectedMemberNode); if (selectedMember == null || selectedMember.ContainingType == null) { return; } if (!MemberAndDestinationValidator.IsMemberValid(selectedMember)) { return; } if (!IsSelectionValid(context.Span, selectedMemberNode)) { return; } var allDestinations = FindAllValidDestinations( selectedMember, document.Project.Solution, cancellationToken); if (allDestinations.Length == 0) { return; } var allActions = allDestinations.Select(destination => MembersPuller.TryComputeCodeAction(document, selectedMember, destination)) .WhereNotNull().Concat(new PullMemberUpWithDialogCodeAction(document, selectedMember, _service)) .ToImmutableArray(); var nestedCodeAction = new CodeActionWithNestedActions( string.Format(FeaturesResources.Pull_0_up, selectedMember.ToNameDisplayString()), allActions, isInlinable: true); context.RegisterRefactoring(nestedCodeAction); }
private ImmutableArray <INamedTypeSymbol> FindAllValidDestinations( ISymbol selectedMember, Solution solution, CancellationToken cancellationToken) { var containingType = selectedMember.ContainingType; var allDestinations = selectedMember.IsKind(SymbolKind.Field) ? containingType.GetBaseTypes().ToImmutableArray() : containingType.AllInterfaces.Concat(containingType.GetBaseTypes()).ToImmutableArray(); return(allDestinations.WhereAsArray(destination => MemberAndDestinationValidator.IsDestinationValid(solution, destination, cancellationToken))); }
public static BaseTypeTreeNodeViewModel CreateBaseTypeTree( IGlyphService glyphService, Solution solution, INamedTypeSymbol root, CancellationToken cancellationToken ) { var rootTreeNode = new BaseTypeTreeNodeViewModel(root, glyphService) { IsChecked = false, IsExpanded = true }; var queue = new Queue <BaseTypeTreeNodeViewModel>(); queue.Enqueue(rootTreeNode); while (queue.Any()) { var currentTreeNode = queue.Dequeue(); var currentTypeSymbol = currentTreeNode.Symbol; currentTreeNode.BaseTypeNodes = currentTypeSymbol.Interfaces .Concat(currentTypeSymbol.BaseType) .Where( baseType => baseType != null && MemberAndDestinationValidator.IsDestinationValid( solution, baseType, cancellationToken ) ) .OrderBy(baseType => baseType.ToDisplayString()) .Select( baseType => new BaseTypeTreeNodeViewModel(baseType, glyphService) { IsChecked = false, IsExpanded = true } ) .ToImmutableArray(); foreach (var node in currentTreeNode.BaseTypeNodes) { queue.Enqueue(node); } } return(rootTreeNode); }
public override async Task ComputeRefactoringsAsync(CodeRefactoringContext context) { var(document, span, cancellationToken) = context; var service = document.Project.Solution.Workspace.Services.GetService <IMoveStaticMembersOptionsService>(); if (service == null) { return; } var memberDeclaration = await GetSelectedNodeAsync(context).ConfigureAwait(false); if (memberDeclaration == null) { return; } var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); if (semanticModel == null) { return; } var selectedType = semanticModel.GetEnclosingNamedType(span.Start, cancellationToken); if (selectedType == null) { return; } var selectedMembers = selectedType.GetMembers() .WhereAsArray(m => m.IsStatic && MemberAndDestinationValidator.IsMemberValid(m) && m.DeclaringSyntaxReferences.Any(sr => memberDeclaration.FullSpan.Contains(sr.Span))); if (selectedMembers.IsEmpty) { return; } var syntaxFacts = document.GetRequiredLanguageService <ISyntaxFactsService>(); var action = new MoveStaticMembersWithDialogCodeAction(document, span, service, selectedType, context.Options, selectedMember: selectedMembers[0]); context.RegisterRefactoring(action, selectedMembers[0].DeclaringSyntaxReferences[0].Span); }
private async Task <ExtractClassWithDialogCodeAction?> TryGetMemberActionAsync(CodeRefactoringContext context, IExtractClassOptionsService optionsService) { var selectedMemberNode = await GetSelectedNodeAsync(context).ConfigureAwait(false); if (selectedMemberNode is null) { return(null); } var(document, span, cancellationToken) = context; var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); var selectedMember = semanticModel.GetDeclaredSymbol(selectedMemberNode, cancellationToken); if (selectedMember is null || selectedMember.ContainingType is null) { return(null); } // Use same logic as pull members up for determining if a selected member // is valid to be moved into a base if (!MemberAndDestinationValidator.IsMemberValid(selectedMember)) { return(null); } var containingType = selectedMember.ContainingType; // Can't extract to a new type if there's already a base. Maybe // in the future we could inject a new type inbetween base and // current if (containingType.BaseType?.SpecialType != SpecialType.System_Object) { return(null); } var syntaxFacts = document.GetRequiredLanguageService <ISyntaxFactsService>(); var containingTypeDeclarationNode = selectedMemberNode.FirstAncestorOrSelf <SyntaxNode>(syntaxFacts.IsTypeDeclaration); Contract.ThrowIfNull(containingTypeDeclarationNode); // Treat the entire node's span as the span of interest here. That way if the user's location is closer to // a refactoring with a narrower span (for example, a span just on the name/parameters of a member, then it // will take precedence over us). return(new ExtractClassWithDialogCodeAction( document, selectedMemberNode.Span, optionsService, containingType, containingTypeDeclarationNode, context.Options, selectedMember)); }
// internal for testing purposes, get the view model internal static MoveStaticMembersDialogViewModel GetViewModel( Document document, INamedTypeSymbol selectedType, ISymbol?selectedNodeSymbol, IGlyphService?glyphService, IUIThreadOperationExecutor uiThreadOperationExecutor) { var membersInType = selectedType.GetMembers(). WhereAsArray(member => MemberAndDestinationValidator.IsMemberValid(member) && member.IsStatic); var memberViewModels = membersInType .SelectAsArray(member => new SymbolViewModel <ISymbol>(member, glyphService) { // The member user selected will be checked at the beginning. IsChecked = SymbolEquivalenceComparer.Instance.Equals(selectedNodeSymbol, member), }); using var cancellationTokenSource = new CancellationTokenSource(); var memberToDependentsMap = SymbolDependentsBuilder.FindMemberToDependentsMap(membersInType, document.Project, cancellationTokenSource.Token); var existingTypeNames = selectedType.ContainingNamespace.GetAllTypes(cancellationTokenSource.Token).SelectAsArray(t => t.ToDisplayString()); var candidateName = selectedType.Name + "Helpers"; var defaultTypeName = NameGenerator.GenerateUniqueName(candidateName, name => !existingTypeNames.Contains(name)); var containingNamespaceDisplay = selectedType.ContainingNamespace.IsGlobalNamespace ? string.Empty : selectedType.ContainingNamespace.ToDisplayString(); var selectMembersViewModel = new StaticMemberSelectionViewModel( uiThreadOperationExecutor, memberViewModels, memberToDependentsMap); return(new MoveStaticMembersDialogViewModel(selectMembersViewModel, defaultTypeName, existingTypeNames, containingNamespaceDisplay, document.GetRequiredLanguageService <ISyntaxFactsService>())); }
public override async Task ComputeRefactoringsAsync(CodeRefactoringContext context) { var(document, span, cancellationToken) = context; var service = document.Project.Solution.Workspace.Services.GetService <IMoveStaticMembersOptionsService>(); if (service == null) { return; } var memberDeclaration = await GetSelectedNodeAsync(context).ConfigureAwait(false); if (memberDeclaration == null) { return; } var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); if (semanticModel == null) { return; } var selectedType = semanticModel.GetEnclosingNamedType(span.Start, cancellationToken); if (selectedType == null) { return; } var selectedMembers = selectedType.GetMembers() .WhereAsArray(m => m.IsStatic && MemberAndDestinationValidator.IsMemberValid(m) && m.DeclaringSyntaxReferences.Any(static (sr, memberDeclaration) => memberDeclaration.FullSpan.Contains(sr.Span), memberDeclaration));