diff --git a/src/EditorFeatures/CSharpTest/MoveToNamespace/MoveToNamespaceTests.cs b/src/EditorFeatures/CSharpTest/MoveToNamespace/MoveToNamespaceTests.cs new file mode 100644 index 0000000000000000000000000000000000000000..559c7e6c411fa27fdfea16ee70f9bd9f241cdc0f --- /dev/null +++ b/src/EditorFeatures/CSharpTest/MoveToNamespace/MoveToNamespaceTests.cs @@ -0,0 +1,225 @@ +// Copyright(c) Microsoft.All Rights Reserved.Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Test.Utilities; +using Microsoft.CodeAnalysis.Test.Utilities.MoveToNamespace; +using Roslyn.Test.Utilities; +using Xunit; + +namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.MoveToNamespace +{ + public class MoveToNamespaceTests : AbstractMoveToNamespaceTests + { + [WpfFact, Trait(Traits.Feature, Traits.Features.MoveToNamespace)] + public async Task MoveToNamespace_CaretOnNamespaceName() + { + var markup = @" +using System; + +namespace A$$ +{ + class MyClass + { + void Method() { } + } +}"; + + var expectedMarkup = @" +using System; + +namespace B +{ + class MyClass + { + void Method() { } + } +}"; + await TestMoveToNamespaceCommandCSharpAsync( + markup, + expectedSuccess: true, + expectedNamespace: "B", + expectedMarkup: expectedMarkup); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.MoveToNamespace)] + public async Task MoveToNamespace_CaretOnNamespaceKeyword() + { + var markup = @" +using System; + +namespace$$ A +{ + class MyClass + { + void Method() { } + } +}"; + + var expectedMarkup = @" +using System; + +namespace B +{ + class MyClass + { + void Method() { } + } +}"; + await TestMoveToNamespaceCommandCSharpAsync( + markup, + expectedSuccess: true, + expectedNamespace: "B", + expectedMarkup: expectedMarkup); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.MoveToNamespace)] + public async Task MoveToNamespace_CaretOnNamespaceNameMultipleDeclarations() + { + var markup = @" +using System; + +namespace A$$ +{ + class MyClass + { + void Method() { } + } + + class MyOtherClass + { + void Method() { } + } +}"; + + var expectedMarkup = @" +using System; + +namespace B +{ + class MyClass + { + void Method() { } + } + + class MyOtherClass + { + void Method() { } + } +}"; + await TestMoveToNamespaceCommandCSharpAsync( + markup, + expectedSuccess: true, + expectedNamespace: "B", + expectedMarkup: expectedMarkup); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.MoveToNamespace)] + public async Task MoveToNamespace_CaretOnTypeDeclaration() + { + var markup = @" +using System; +namespace A +{ + class MyClass$$ + { + void Method() {} + } +}"; + await TestMoveToNamespaceCommandCSharpAsync( + markup, + expectedSuccess: false); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.MoveToNamespace)] + public async Task MoveToNamespace_WithVariousSymbols() + { + var markup = @" +using System; + +namespace A$$ +{ + public delegate void MyDelegate(); + + public enum MyEnum + { + One, + Two, + Three + } + + public struct MyStruct + { } + + public interface MyInterface + { } + + class MyClass + { + void Method() { } + } + + class MyOtherClass + { + void Method() { } + } +}"; + + var expectedMarkup = @" +using System; + +namespace B +{ + public delegate void MyDelegate(); + + public enum MyEnum + { + One, + Two, + Three + } + + public struct MyStruct + { } + + public interface MyInterface + { } + + class MyClass + { + void Method() { } + } + + class MyOtherClass + { + void Method() { } + } +}"; + await TestMoveToNamespaceCommandCSharpAsync( + markup, + expectedSuccess: true, + expectedNamespace: "B", + expectedMarkup: expectedMarkup); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.MoveToNamespace)] + public async Task MoveToNamespace_NestedNamespace() + { + var markup = @" +using System; + +namespace A$$ +{ + namespace C + { + class MyClass + { + void Method() { } + } + } +}"; + await TestMoveToNamespaceCommandCSharpAsync( + markup, + expectedSuccess: false); + } + } +} diff --git a/src/EditorFeatures/TestUtilities/MoveToNamespace/AbstractMoveToNamespaceTests.cs b/src/EditorFeatures/TestUtilities/MoveToNamespace/AbstractMoveToNamespaceTests.cs new file mode 100644 index 0000000000000000000000000000000000000000..239a60e0978eff9827bd59301992daf3128cb2e2 --- /dev/null +++ b/src/EditorFeatures/TestUtilities/MoveToNamespace/AbstractMoveToNamespaceTests.cs @@ -0,0 +1,146 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.AddImports; +using Microsoft.CodeAnalysis.CSharp.ChangeNamespace; +using Microsoft.CodeAnalysis.CSharp.MoveToNamespace; +using Microsoft.CodeAnalysis.CSharp.RemoveUnnecessaryImports; +using Microsoft.CodeAnalysis.Editor.UnitTests; +using Microsoft.CodeAnalysis.Editor.UnitTests.Extensions; +using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces; +using Microsoft.CodeAnalysis.MoveToNamespace; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.VisualStudio.Composition; +using Xunit; +using static Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions.AbstractCodeActionOrUserDiagnosticTest; + +namespace Microsoft.CodeAnalysis.Test.Utilities.MoveToNamespace +{ + [UseExportProvider] + public abstract class AbstractMoveToNamespaceTests + { + private static readonly IExportProviderFactory CSharpExportProviderFactory = + ExportProviderCache.GetOrCreateExportProviderFactory( + TestExportProvider.MinimumCatalogWithCSharpAndVisualBasic + .WithPart(typeof(TestMoveToNamespaceOptionsService)) + .WithPart(typeof(CSharpMoveToNamespaceService)) + .WithPart(typeof(CSharpChangeNamespaceService)) + .WithPart(typeof(Experiments.DefaultExperimentationService)) + .WithPart(typeof(CSharpAddImportsService)) + .WithPart(typeof(CSharpRemoveUnnecessaryImportsService))); + + public static Task TestMoveToNamespaceCommandCSharpAsync( + string markup, + bool expectedSuccess, + string expectedNamespace = null, + string expectedMarkup = null) + { + return TestMoveToNamespaceCommandAsync( + markup, + expectedSuccess, + LanguageNames.CSharp, + expectedNamespace: expectedNamespace, + expectedMarkup: expectedMarkup); + } + + public static Task TestMoveToNamespaceCommandVisualBasicAsync( + string markup, + bool expectedSuccess, + string expectedNamespace = null, + string expectedMarkup = null) + { + return TestMoveToNamespaceCommandAsync( + markup, + expectedSuccess, + LanguageNames.VisualBasic, + expectedNamespace: expectedNamespace, + expectedMarkup: expectedMarkup); + } + + + public static async Task TestMoveToNamespaceCommandAsync( + string markup, + bool expectedSuccess, + string languageName, + string expectedNamespace = null, + string expectedMarkup = null, + CompilationOptions compilationOptions = null) + { + expectedNamespace = expectedNamespace ?? string.Empty; + var parameters = new TestParameters(); + using (var workspace = CreateWorkspace(markup, languageName, compilationOptions, parameters)) + { + var testDocument = workspace.Documents.Single(d => d.CursorPosition.HasValue); + var document = workspace.CurrentSolution.GetDocument(testDocument.Id); + + var result = await MoveViaCommandAsync(testDocument, document, expectedNamespace); + + if (expectedSuccess) + { + Assert.True(result.Succeeded); + Assert.NotNull(result.UpdatedSolution); + Assert.NotNull(result.UpdatedDocumentId); + + if (expectedMarkup != null) + { + var updatedDocument = result.UpdatedSolution.GetDocument(result.UpdatedDocumentId); + var updatedText = await updatedDocument.GetTextAsync().ConfigureAwait(false); + Assert.Equal(expectedMarkup, updatedText.ToString()); + } + } + else + { + Assert.False(result.Succeeded); + } + } + } + + private static TestWorkspace CreateWorkspace( + string markup, + string languageName, + CompilationOptions compilationOptions, + TestParameters parameters) + { + var exportProviderFactory = GetExportProviderFactory(languageName); + var exportProvider = exportProviderFactory.CreateExportProvider(); + + var workspace = languageName == LanguageNames.CSharp + ? TestWorkspace.CreateCSharp(markup, exportProvider: exportProvider, compilationOptions: compilationOptions as CSharpCompilationOptions) + : TestWorkspace.CreateVisualBasic(markup, exportProvider: exportProvider, compilationOptions: compilationOptions); + + workspace.ApplyOptions(parameters.options); + + return workspace; + } + + private static IExportProviderFactory GetExportProviderFactory(string languageName) + { + return languageName == LanguageNames.CSharp + ? CSharpExportProviderFactory + : throw new InvalidOperationException("VB is not currently supported"); + } + + private static async Task MoveViaCommandAsync( + TestHostDocument testDocument, + Document document, + string newNamespace) + { + var cancellationToken = CancellationToken.None; + var moveToNamespaceService = document.GetLanguageService(); + + var analysisResult = await moveToNamespaceService.AnalyzeTypeAtPositionAsync( + document, + testDocument.CursorPosition.Value, + cancellationToken: cancellationToken).ConfigureAwait(false); + + return await moveToNamespaceService.MoveToNamespaceAsync( + analysisResult, + newNamespace, + cancellationToken: cancellationToken).ConfigureAwait(false); + } + } +} diff --git a/src/EditorFeatures/TestUtilities/MoveToNamespace/TestMoveToNamespaceOptionsService.cs b/src/EditorFeatures/TestUtilities/MoveToNamespace/TestMoveToNamespaceOptionsService.cs new file mode 100644 index 0000000000000000000000000000000000000000..601e1a5132a085e5fe02963056ca14d04c083817 --- /dev/null +++ b/src/EditorFeatures/TestUtilities/MoveToNamespace/TestMoveToNamespaceOptionsService.cs @@ -0,0 +1,33 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +extern alias WORKSPACES; + +using System; +using System.ComponentModel.Composition; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.LanguageServices; +using Microsoft.CodeAnalysis.MoveToNamespace; +using Microsoft.CodeAnalysis.Notification; +using WORKSPACES::Microsoft.CodeAnalysis.Host.Mef; + +namespace Microsoft.CodeAnalysis.Test.Utilities.MoveToNamespace +{ + [Export(typeof(IMoveToNamespaceOptionsService))] + [PartNotDiscoverable] + class TestMoveToNamespaceOptionsService : IMoveToNamespaceOptionsService + { + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public TestMoveToNamespaceOptionsService() + { + } + + public Task GetChangeNamespaceOptionsAsync(ISyntaxFactsService syntaxFactsService, INotificationService notificationService, string defaultNamespace, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/Features/CSharp/Portable/MoveToNamespace/CSharpMoveToNamespaceService.cs b/src/Features/CSharp/Portable/MoveToNamespace/CSharpMoveToNamespaceService.cs new file mode 100644 index 0000000000000000000000000000000000000000..f063d877f7295a25408ab598f377a92e102514be --- /dev/null +++ b/src/Features/CSharp/Portable/MoveToNamespace/CSharpMoveToNamespaceService.cs @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Composition; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.MoveToNamespace; + +namespace Microsoft.CodeAnalysis.CSharp.MoveToNamespace +{ + [ExportLanguageService(typeof(AbstractMoveToNamespaceService), LanguageNames.CSharp), Shared] + internal class CSharpMoveToNamespaceService : + AbstractMoveToNamespaceService + { + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public CSharpMoveToNamespaceService(IMoveToNamespaceOptionsService moveToNamespaceOptionsService) + : base(moveToNamespaceOptionsService) + { + } + } +} diff --git a/src/Features/Core/Portable/CodeRefactorings/PredefinedCodeRefactoringProviderNames.cs b/src/Features/Core/Portable/CodeRefactorings/PredefinedCodeRefactoringProviderNames.cs index e707f03e150328a4fb7e63bfa566a553a803c06f..1d06ba94c0bc359ea970b77f4eceb0f9efc481db 100644 --- a/src/Features/Core/Portable/CodeRefactorings/PredefinedCodeRefactoringProviderNames.cs +++ b/src/Features/Core/Portable/CodeRefactorings/PredefinedCodeRefactoringProviderNames.cs @@ -28,6 +28,7 @@ internal static class PredefinedCodeRefactoringProviderNames public const string MergeConsecutiveIfStatements = "Merge Consecutive If Statements Code Action Provider"; public const string MergeNestedIfStatements = "Merge Nested If Statements Code Action Provider"; public const string MoveDeclarationNearReference = "Move Declaration Near Reference Code Action Provider"; + public const string MoveToNamespace = "Move To Namespace Code Action Provider"; public const string MoveTypeToFile = "Move Type To File Code Action Provider"; public const string PullMemberUp = "Pull Member Up Code Action Provider"; public const string ReplaceDocCommentTextWithTag = "Replace Documentation Comment Text With Tag Code Action Provider"; diff --git a/src/Features/Core/Portable/FeaturesResources.Designer.cs b/src/Features/Core/Portable/FeaturesResources.Designer.cs index d4d86c2862e2efd1b2ae6832cd1967a455610c76..036747852249758a0387e47f46775db17a051527 100644 --- a/src/Features/Core/Portable/FeaturesResources.Designer.cs +++ b/src/Features/Core/Portable/FeaturesResources.Designer.cs @@ -2624,6 +2624,15 @@ internal class FeaturesResources { } } + /// + /// Looks up a localized string similar to Move to namespace.... + /// + internal static string Move_to_namespace { + get { + return ResourceManager.GetString("Move_to_namespace", resourceCulture); + } + } + /// /// Looks up a localized string similar to Move type to {0}. /// diff --git a/src/Features/Core/Portable/FeaturesResources.resx b/src/Features/Core/Portable/FeaturesResources.resx index 8b235dc060efb226a87cfd1909801be025344af1..52227cb5509c6e191630fdffa4acfc6261b0cca9 100644 --- a/src/Features/Core/Portable/FeaturesResources.resx +++ b/src/Features/Core/Portable/FeaturesResources.resx @@ -1457,6 +1457,9 @@ This version used in: {2} Move file to project root folder + + Move to namespace... + Change to global namespace diff --git a/src/Features/Core/Portable/MoveToNamespace/AbstractMoveToNamespaceService.cs b/src/Features/Core/Portable/MoveToNamespace/AbstractMoveToNamespaceService.cs new file mode 100644 index 0000000000000000000000000000000000000000..24a3fa5fdb1341a8bd9a454d28e9f31086ee2245 --- /dev/null +++ b/src/Features/Core/Portable/MoveToNamespace/AbstractMoveToNamespaceService.cs @@ -0,0 +1,123 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Immutable; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.ChangeNamespace; +using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.LanguageServices; +using Microsoft.CodeAnalysis.Notification; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.MoveToNamespace +{ + internal abstract class AbstractMoveToNamespaceService : ILanguageService + { + internal abstract Task> GetCodeActionsAsync(Document document, TextSpan span, CancellationToken cancellationToken); + internal abstract Task AnalyzeTypeAtPositionAsync(Document document, int position, CancellationToken cancellationToken); + public abstract Task MoveToNamespaceAsync(MoveToNamespaceAnalysisResult analysisResult, string targetNamespace, CancellationToken cancellationToken); + public abstract MoveToNamespaceOptionsResult GetOptions(Document document, string defaultNamespace, CancellationToken cancellationToken); + } + + internal abstract class AbstractMoveToNamespaceService + : AbstractMoveToNamespaceService + { + private IMoveToNamespaceOptionsService _moveToNamespaceOptionsService; + + public AbstractMoveToNamespaceService(IMoveToNamespaceOptionsService moveToNamespaceOptionsService) + { + _moveToNamespaceOptionsService = moveToNamespaceOptionsService; + } + + internal override async Task> GetCodeActionsAsync( + Document document, + TextSpan span, + CancellationToken cancellationToken) + { + var typeAnalysisResult = await AnalyzeTypeAtPositionAsync(document, span.Start, cancellationToken).ConfigureAwait(false); + + return typeAnalysisResult.CanPerform + ? ImmutableArray.Create(new MoveToNamespaceCodeAction(this, typeAnalysisResult)) + : ImmutableArray.Empty; + } + + internal override async Task AnalyzeTypeAtPositionAsync( + Document document, + int position, + CancellationToken cancellationToken) + { + var root = await document.GetSyntaxRootAsync().ConfigureAwait(false); + var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); + + var token = root.FindToken(position); + var node = token.Parent; + + var symbolInfo = semanticModel.GetSymbolInfo(node, cancellationToken: cancellationToken); + var symbol = symbolInfo.Symbol; + + if (symbol is INamespaceSymbol namespaceSymbol) + { + node = node.FirstAncestorOrSelf(a => a is TNamespaceDeclarationSyntax); + } + + if (node is TNamespaceDeclarationSyntax declarationSyntax) + { + if (ContainsNamespaceDeclaration(node)) + { + return new MoveToNamespaceAnalysisResult("Container contains nested namespace declaration"); + } + + return new MoveToNamespaceAnalysisResult(document, node, declarationSyntax.GetTypeDisplayName()); + } + + return new MoveToNamespaceAnalysisResult("Not a valid position"); + } + + private bool ContainsNamespaceDeclaration(SyntaxNode node) + => node.DescendantNodes(n => n is TCompilationSyntax || n is TNamespaceDeclarationSyntax) + .OfType().Any(); + + public override async Task MoveToNamespaceAsync( + MoveToNamespaceAnalysisResult analysisResult, + string targetNamespace, + CancellationToken cancellationToken) + { + if (!analysisResult.CanPerform) + { + return MoveToNamespaceResult.Failed; + } + + var changeNamespaceService = analysisResult.Document.GetLanguageService(); + if (changeNamespaceService == null) + { + return MoveToNamespaceResult.Failed; + } + + var changedSolution = await changeNamespaceService.ChangeNamespaceAsync( + analysisResult.Document, + analysisResult.Container, + targetNamespace, + cancellationToken).ConfigureAwait(false); + + return new MoveToNamespaceResult(changedSolution, analysisResult.Document.Id); + } + + public override MoveToNamespaceOptionsResult GetOptions( + Document document, + string defaultNamespace, + CancellationToken cancellationToken) + { + var syntaxFactsService = document.GetLanguageService(); + var notificationService = document.Project.Solution.Workspace.Services.GetService(); + + return _moveToNamespaceOptionsService.GetChangeNamespaceOptionsAsync( + syntaxFactsService, + notificationService, + defaultNamespace, + cancellationToken).WaitAndGetResult(cancellationToken); + } + } +} diff --git a/src/Features/Core/Portable/MoveToNamespace/IMoveToNamespaceOptionsService.cs b/src/Features/Core/Portable/MoveToNamespace/IMoveToNamespaceOptionsService.cs new file mode 100644 index 0000000000000000000000000000000000000000..f0d3647768b8fddd87fe1245c80e8ac1ab7334df --- /dev/null +++ b/src/Features/Core/Portable/MoveToNamespace/IMoveToNamespaceOptionsService.cs @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.LanguageServices; +using Microsoft.CodeAnalysis.Notification; + +namespace Microsoft.CodeAnalysis.MoveToNamespace +{ + internal interface IMoveToNamespaceOptionsService : IWorkspaceService + { + Task GetChangeNamespaceOptionsAsync( + ISyntaxFactsService syntaxFactsService, + INotificationService notificationService, + string defaultNamespace, + CancellationToken cancellationToken); + } +} diff --git a/src/Features/Core/Portable/MoveToNamespace/MoveToNamespaceAnalysisResult.cs b/src/Features/Core/Portable/MoveToNamespace/MoveToNamespaceAnalysisResult.cs new file mode 100644 index 0000000000000000000000000000000000000000..548871f20acf76da655f4830e865f4c431f3392c --- /dev/null +++ b/src/Features/Core/Portable/MoveToNamespace/MoveToNamespaceAnalysisResult.cs @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Text; + +namespace Microsoft.CodeAnalysis.MoveToNamespace +{ + internal class MoveToNamespaceAnalysisResult + { + public bool CanPerform { get; } + public Document Document { get; } + public string ErrorMessage { get; } + public SyntaxNode Container { get; } + public string OriginalNamespace { get; } + + public MoveToNamespaceAnalysisResult( + Document document, + SyntaxNode container, + string originalNamespace) + { + CanPerform = true; + Document = document; + Container = container; + OriginalNamespace = originalNamespace; + } + + public MoveToNamespaceAnalysisResult(string errorMessage) + { + CanPerform = false; + ErrorMessage = errorMessage; + } + } +} diff --git a/src/Features/Core/Portable/MoveToNamespace/MoveToNamespaceCodeAction.cs b/src/Features/Core/Portable/MoveToNamespace/MoveToNamespaceCodeAction.cs new file mode 100644 index 0000000000000000000000000000000000000000..0ab67374c8b2ac997c22eadfc5bd78a5130acc7b --- /dev/null +++ b/src/Features/Core/Portable/MoveToNamespace/MoveToNamespaceCodeAction.cs @@ -0,0 +1,54 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CodeActions; + +namespace Microsoft.CodeAnalysis.MoveToNamespace +{ + internal class MoveToNamespaceCodeAction : CodeActionWithOptions + { + private readonly AbstractMoveToNamespaceService _changeNamespaceService; + private readonly MoveToNamespaceAnalysisResult _moveToNamespaceAnalysisResult; + + public override string Title => FeaturesResources.Move_to_namespace; + + public MoveToNamespaceCodeAction(AbstractMoveToNamespaceService changeNamespaceService, MoveToNamespaceAnalysisResult analysisResult) + { + _changeNamespaceService = changeNamespaceService; + _moveToNamespaceAnalysisResult = analysisResult; + } + + public override object GetOptions(CancellationToken cancellationToken) + { + return _changeNamespaceService.GetOptions( + _moveToNamespaceAnalysisResult.Document, + _moveToNamespaceAnalysisResult.OriginalNamespace, + cancellationToken); + } + + protected override async Task> ComputeOperationsAsync(object options, CancellationToken cancellationToken) + { + IEnumerable operations = null; + + if (options is MoveToNamespaceOptionsResult moveToNamespaceOptions && !moveToNamespaceOptions.IsCancelled) + { + var moveToNamespaceResult = await _changeNamespaceService.MoveToNamespaceAsync( + _moveToNamespaceAnalysisResult, + moveToNamespaceOptions.Namespace, + cancellationToken).ConfigureAwait(false); + + if (moveToNamespaceResult.Succeeded) + { + operations = new CodeActionOperation[] + { + new ApplyChangesOperation(moveToNamespaceResult.UpdatedSolution) + }; + } + } + + return operations; + } + } +} diff --git a/src/Features/Core/Portable/MoveToNamespace/MoveToNamespaceCodeActionProvider.cs b/src/Features/Core/Portable/MoveToNamespace/MoveToNamespaceCodeActionProvider.cs new file mode 100644 index 0000000000000000000000000000000000000000..52f7da29f4d68bab4347c5893f73c9ffe7156c36 --- /dev/null +++ b/src/Features/Core/Portable/MoveToNamespace/MoveToNamespaceCodeActionProvider.cs @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Composition; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CodeRefactorings; +using Microsoft.CodeAnalysis.Shared.Extensions; + +namespace Microsoft.CodeAnalysis.MoveToNamespace +{ + [ExportCodeRefactoringProvider(LanguageNames.CSharp, Name = PredefinedCodeRefactoringProviderNames.MoveToNamespace), Shared] + internal class MoveToNamespaceCodeActionProvider : CodeRefactoringProvider + { + public override async Task ComputeRefactoringsAsync(CodeRefactoringContext context) + { + var service = context.Document.GetLanguageService(); + var actions = await service.GetCodeActionsAsync(context.Document, context.Span, context.CancellationToken).ConfigureAwait(false); + context.RegisterRefactorings(actions); + } + } +} diff --git a/src/Features/Core/Portable/MoveToNamespace/MoveToNamespaceOptionsResult.cs b/src/Features/Core/Portable/MoveToNamespace/MoveToNamespaceOptionsResult.cs new file mode 100644 index 0000000000000000000000000000000000000000..a654927ce4c50fecd4830856000a82356b06dcbe --- /dev/null +++ b/src/Features/Core/Portable/MoveToNamespace/MoveToNamespaceOptionsResult.cs @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Immutable; + +namespace Microsoft.CodeAnalysis.MoveToNamespace +{ + internal class MoveToNamespaceOptionsResult + { + public static readonly MoveToNamespaceOptionsResult Cancelled = new MoveToNamespaceOptionsResult(isCancelled: true); + + public bool IsCancelled { get; } + public string Namespace { get; } + + public MoveToNamespaceOptionsResult(bool isCancelled) + { + IsCancelled = true; + } + + public MoveToNamespaceOptionsResult(string @namespace) + { + Namespace = @namespace; + } + } +} diff --git a/src/Features/Core/Portable/MoveToNamespace/MoveToNamespaceResult.cs b/src/Features/Core/Portable/MoveToNamespace/MoveToNamespaceResult.cs new file mode 100644 index 0000000000000000000000000000000000000000..9089a91f47500ee37fc0a9f444bccbf161d40719 --- /dev/null +++ b/src/Features/Core/Portable/MoveToNamespace/MoveToNamespaceResult.cs @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.CodeAnalysis.MoveToNamespace +{ + internal class MoveToNamespaceResult + { + public static MoveToNamespaceResult Failed = new MoveToNamespaceResult(succeeded: false); + + public bool Succeeded { get; } + public Solution UpdatedSolution { get; } + public DocumentId UpdatedDocumentId { get; } + + public MoveToNamespaceResult(Solution solution, DocumentId updatedDocumentId) + { + UpdatedSolution = solution; + UpdatedDocumentId = updatedDocumentId; + Succeeded = true; + } + + private MoveToNamespaceResult(bool succeeded) + { + Succeeded = succeeded; + } + } +} diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf index 1e37a709778ba2f07849a5a834ef4d6d4cd55acd..8359ac8bf35610b452a910e213874fcabd4d2500 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf @@ -212,6 +212,11 @@ Přesunout soubor do kořenové složky projektu + + Move to namespace... + Move to namespace... + + Private member '{0}' can be removed as the value assigned to it is never read. Soukromý člen {0} se může odebrat, jak jeho přiřazená hodnota se nikdy nečte. diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf index f930afd7411a4d3c01a90844190d0e81dbd67a5c..7651088cc12006092c97e4eabb37ec4616691e69 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf @@ -212,6 +212,11 @@ Datei in den Stammordner des Projekts verschieben + + Move to namespace... + Move to namespace... + + Private member '{0}' can be removed as the value assigned to it is never read. Der private Member "{0}" kann entfernt werden, da der zugewiesene Wert nie gelesen wird. diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf index 7dcba2a02e39cb334c6265df3051d34e0fdadafd..f2eda71dc102ab38959aa0bc14ab15c69f0cfbfd 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf @@ -212,6 +212,11 @@ Mover el archivo a la carpeta raíz del proyecto + + Move to namespace... + Move to namespace... + + Private member '{0}' can be removed as the value assigned to it is never read. Un miembro privado "{0}" puede retirarse porque el valor asignado a él no se lee nunca. diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf index f01bbd3b0c1968ce7c1ac42edfd33043d4bdce26..fd0fcbfedf989a347c8c2174cae7003ccaba4e4a 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf @@ -212,6 +212,11 @@ Déplacer le fichier dans le dossier racine du projet + + Move to namespace... + Move to namespace... + + Private member '{0}' can be removed as the value assigned to it is never read. Vous pouvez supprimer le membre privé '{0}', car la valeur qui lui est attribuée n'est jamais lue. diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf index 899d62dfe52a531bd24d540b12b6a6df83971c80..10d20905960372af3a1ca9e31e5d4e14b3f2776f 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf @@ -212,6 +212,11 @@ Sposta il file nella cartella radice del progetto + + Move to namespace... + Move to namespace... + + Private member '{0}' can be removed as the value assigned to it is never read. È possibile rimuovere il membro privato '{0}' perché il valore assegnato ad esso non viene mai letto. diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf index b0a698594f4650316f510af7c46d283fa03fab77..e5317b1231f13597359dc307d9e375b43f7b239e 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf @@ -212,6 +212,11 @@ ファイルをプロジェクト ルート フォルダーに移動します + + Move to namespace... + Move to namespace... + + Private member '{0}' can be removed as the value assigned to it is never read. 割り当てられている値が読み取られることがないようにプライベートメンバー '{0}' を削除できます。 diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf index 3c52d4081147f0debfb25568723754908d044ef3..a90fb97b914cd36c106afabdb67c141867cfb510 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf @@ -212,6 +212,11 @@ 파일을 프로젝트 루트 폴더로 이동 + + Move to namespace... + Move to namespace... + + Private member '{0}' can be removed as the value assigned to it is never read. Private 멤버 '{0}'에 할당된 값을 읽을 수 없으므로 이 멤버를 제거할 수 있습니다. diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf index 5a808972a17f2a9cca0429ab28d40aacde3d2d31..158eae37a60b3d3d9aca4d262ca045bb857c1321 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf @@ -212,6 +212,11 @@ Przenieś plik do folderu głównego projektu + + Move to namespace... + Move to namespace... + + Private member '{0}' can be removed as the value assigned to it is never read. Prywatną składową „{0}” można usunąć, ponieważ przypisana do niej wartość nie jest nigdy odczytywana. diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf index f5a248a624651247965a75051d2a2ebd8b77fb50..e99186238cafeed7277b746a196b14b63bb75fe8 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf @@ -212,6 +212,11 @@ Mover o arquivo para a pasta raiz do projeto + + Move to namespace... + Move to namespace... + + Private member '{0}' can be removed as the value assigned to it is never read. O membro particular '{0}' pode ser removido pois o valor atribuído a ele nunca é lido. diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf index 2cda232be56b7871449c169e1feb0327d2b52f52..1543d3f1116352487110e1a0301aeb0f5ce71f1a 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf @@ -212,6 +212,11 @@ Переместить файл в корневую папку проекта + + Move to namespace... + Move to namespace... + + Private member '{0}' can be removed as the value assigned to it is never read. Закрытый член «{0}» может быть удален как значение, присвоенное него никогда не читал. diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf index 83eb09066ef6bd2d0770158929b3ec6e968290e3..4e5eaf5b9d8aa5ccb960ebf6b6cc1761064fde4e 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf @@ -212,6 +212,11 @@ Dosyayı proje kök klasörüne taşı + + Move to namespace... + Move to namespace... + + Private member '{0}' can be removed as the value assigned to it is never read. Kendisine atanmış değeri hiç okurken özel üye '{0}'-ebilmek var olmak çıkarmak. diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf index 3a1aadd49a7f187346fb3905176aacb8e7a94ccb..e6996ca59e4d1cab4273205df888975779128bd8 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf @@ -212,6 +212,11 @@ 将文件移动到项目根文件夹 + + Move to namespace... + Move to namespace... + + Private member '{0}' can be removed as the value assigned to it is never read. 可删除私有成员“{0}”,因为永不会读取分配给它的值。 diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf index 9be4328ef39fd2061eb19631f916521dc715d934..9dcfd2e74a44b42cd182208edae51f288da8039a 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf @@ -212,6 +212,11 @@ 將檔案移到專案根資料夾 + + Move to namespace... + Move to namespace... + + Private member '{0}' can be removed as the value assigned to it is never read. 因為永遠不會讀取指派給私用成員 '{0}' 的值,所以可移除該成員。 diff --git a/src/Test/Utilities/Portable/Traits/Traits.cs b/src/Test/Utilities/Portable/Traits/Traits.cs index 1726c951f31f231101a2c0900a14902bbfb668d3..b5da3351f74003707e76ff862fa96f59069b1fbf 100644 --- a/src/Test/Utilities/Portable/Traits/Traits.cs +++ b/src/Test/Utilities/Portable/Traits/Traits.cs @@ -215,6 +215,7 @@ public static class Features public const string LinkedFileDiffMerging = nameof(LinkedFileDiffMerging); public const string MetadataAsSource = nameof(MetadataAsSource); public const string MSBuildWorkspace = nameof(MSBuildWorkspace); + public const string MoveToNamespace = nameof(MoveToNamespace); public const string NamingStyle = nameof(NamingStyle); public const string NavigableSymbols = nameof(NavigableSymbols); public const string NavigateTo = nameof(NavigateTo); diff --git a/src/VisualStudio/Core/Def/Implementation/MoveToNamespace/MoveToNamespaceDialog.xaml b/src/VisualStudio/Core/Def/Implementation/MoveToNamespace/MoveToNamespaceDialog.xaml new file mode 100644 index 0000000000000000000000000000000000000000..19866d45f772d37192839061386edc79fc726b0c --- /dev/null +++ b/src/VisualStudio/Core/Def/Implementation/MoveToNamespace/MoveToNamespaceDialog.xaml @@ -0,0 +1,70 @@ + + + + 0, 5, 0, 2 + 2 + + + + + + + + + + + +