/
githubmirror
/
roslyn
Обзор
Документация
Войти
/
githubmirror
/
roslyn
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/VisualStudio/Core/Def/MoveStaticMembers/VisualStudioMoveStaticMembersOptionsService.cs
196 строк
8 KB
Cyrus Najmabadi
Remove unnecessary nullable directives
25 мар 2025, 21:58
25 мар 2025, 21:58
2a3f854
Код
Авторство
О чём код?
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Composition; using System.IO; using System.Linq; using System.Threading; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.LanguageService; using Microsoft.CodeAnalysis.MoveStaticMembers; using Microsoft.CodeAnalysis.PullMemberUp; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.Utilities; using Microsoft.VisualStudio.Language.Intellisense; using Microsoft.VisualStudio.LanguageServices.Implementation.PullMemberUp; using Microsoft.VisualStudio.LanguageServices.Implementation.Utilities; using Microsoft.VisualStudio.Utilities; using Roslyn.Utilities; namespace Microsoft.VisualStudio.LanguageServices.Implementation.MoveStaticMembers; [ExportWorkspaceService(typeof(IMoveStaticMembersOptionsService), ServiceLayer.Host), Shared] [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] internal sealed class VisualStudioMoveStaticMembersOptionsService( IGlyphService glyphService, IUIThreadOperationExecutor uiThreadOperationExecutor) : IMoveStaticMembersOptionsService { private readonly IGlyphService _glyphService = glyphService; private readonly IUIThreadOperationExecutor _uiThreadOperationExecutor = uiThreadOperationExecutor; private const int HistorySize = 3; public readonly LinkedList<INamedTypeSymbol> History = new(); public MoveStaticMembersOptions GetMoveMembersToTypeOptions(Document document, INamedTypeSymbol selectedType, ImmutableArray<ISymbol> selectedNodeSymbols) { var viewModel = GetViewModel(document, selectedType, selectedNodeSymbols, History, _glyphService, _uiThreadOperationExecutor); var dialog = new MoveStaticMembersDialog(viewModel); var result = dialog.ShowModal(); if (result.GetValueOrDefault()) { UpdateHistory(viewModel); return GenerateOptions(document.Project.Language, viewModel, result.GetValueOrDefault()); } return MoveStaticMembersOptions.Cancelled; } // internal for testing purposes internal static MoveStaticMembersOptions GenerateOptions(string language, MoveStaticMembersDialogViewModel viewModel, bool dialogResult) { if (dialogResult) { // if the destination name contains extra namespaces, we want the last one as that is the real type name var typeName = viewModel.DestinationName.FullyQualifiedTypeName.Split('.').Last(); var newFileName = Path.ChangeExtension(typeName, language == LanguageNames.CSharp ? ".cs" : ".vb"); var selectedMembers = viewModel.MemberSelectionViewModel.CheckedMembers.SelectAsArray(vm => vm.Symbol); if (viewModel.DestinationName.IsNew) { return new MoveStaticMembersOptions( newFileName, viewModel.DestinationName.FullyQualifiedTypeName, selectedMembers); } RoslynDebug.AssertNotNull(viewModel.DestinationName.NamedType); return new MoveStaticMembersOptions( viewModel.DestinationName.NamedType, selectedMembers); } return MoveStaticMembersOptions.Cancelled; } // internal for testing purposes, get the view model internal static MoveStaticMembersDialogViewModel GetViewModel( Document document, INamedTypeSymbol selectedType, ImmutableArray<ISymbol> selectedNodeSymbols, LinkedList<INamedTypeSymbol> history, 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(s) user selected will be checked at the beginning. IsChecked = selectedNodeSymbols.Any(SymbolEquivalenceComparer.Instance.Equals, member), }); using var cancellationTokenSource = new CancellationTokenSource(); var memberToDependentsMap = SymbolDependentsBuilder.FindMemberToDependentsMap(membersInType, document.Project, cancellationTokenSource.Token); var existingTypes = selectedType.ContainingNamespace.GetAllTypes(cancellationTokenSource.Token).ToImmutableArray(); var existingTypeNames = existingTypes.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); var availableTypeNames = MakeTypeNameItems( selectedType.ContainingNamespace, selectedType, document, history, cancellationTokenSource.Token); return new MoveStaticMembersDialogViewModel( selectMembersViewModel, defaultTypeName, availableTypeNames, containingNamespaceDisplay, document.GetRequiredLanguageService<ISyntaxFactsService>()); } private void UpdateHistory(MoveStaticMembersDialogViewModel viewModel) { if (viewModel.DestinationName.IsNew || viewModel.DestinationName.IsFromHistory) { // if we create a new destination or already have it in the history, // we don't need to update our history list. return; } History.AddFirst(viewModel.DestinationName.NamedType!); if (History.Count > HistorySize) { History.RemoveLast(); } } private static string GetFile(Location loc) => loc.SourceTree!.FilePath; /// <summary> /// Construct all the type names declared in the project, /// </summary> private static ImmutableArray<TypeNameItem> MakeTypeNameItems( INamespaceSymbol currentNamespace, INamedTypeSymbol currentType, Document currentDocument, LinkedList<INamedTypeSymbol> history, CancellationToken cancellationToken) { return currentNamespace.GetAllTypes(cancellationToken) // Remove non-static types only when the current type is static .Where(destinationType => IsValidTypeToMoveBetween(destinationType, currentType) && (destinationType.IsStaticType() || !currentType.IsStaticType())) .SelectMany(t => { // for partially declared classes, we may want multiple entries for a single type. // filter to those actually in a real file, and that is not our current location. return t.Locations .Where(l => l.IsInSource && (currentType.Name != t.Name || GetFile(l) != currentDocument.FilePath)) .Select(l => new TypeNameItem( history.Contains(t), GetFile(l), t)); }) .ToImmutableArrayOrEmpty() .Sort(comparison: TypeNameItem.CompareTo); } private static bool IsValidTypeToMoveBetween(INamedTypeSymbol destinationType, INamedTypeSymbol sourceType) { // Can only moved to named typed that can actually contain members. if (destinationType.TypeKind is not (TypeKind.Class or TypeKind.Interface or TypeKind.Module or TypeKind.Struct)) return false; // Very unlikely to be moving from a non-interface to an interface. Filter out for now. if (sourceType.TypeKind != TypeKind.Interface && destinationType.TypeKind == TypeKind.Interface) return false; return true; } }