diff --git a/src/Analyzers/CSharp/Tests/NamingStyles/NamingStylesTests.cs b/src/Analyzers/CSharp/Tests/NamingStyles/NamingStylesTests.cs index d970743029292..6f3e8939e11e5 100644 --- a/src/Analyzers/CSharp/Tests/NamingStyles/NamingStylesTests.cs +++ b/src/Analyzers/CSharp/Tests/NamingStyles/NamingStylesTests.cs @@ -12,8 +12,9 @@ using Microsoft.CodeAnalysis.CSharp.Diagnostics.NamingStyles; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Editor.UnitTests.Diagnostics.NamingStyles; -using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces; using Microsoft.CodeAnalysis.Test.Utilities; +using Microsoft.CodeAnalysis.Test.Utilities.Utilities; +using Microsoft.CodeAnalysis.Text; using Roslyn.Test.Utilities; using Xunit; using Xunit.Abstractions; @@ -32,9 +33,6 @@ public NamingStylesTests(ITestOutputHelper logger) internal override (DiagnosticAnalyzer, CodeFixProvider) CreateDiagnosticProviderAndFixer(Workspace workspace) => (new CSharpNamingStyleDiagnosticAnalyzer(), new NamingStyleCodeFixProvider()); - protected override TestComposition GetComposition() - => base.GetComposition().AddParts(typeof(TestSymbolRenamedCodeActionOperationFactoryWorkspaceService)); - [Fact, Trait(Traits.Feature, Traits.Features.NamingStyle)] public async Task TestPascalCaseClass_CorrectName() { @@ -1221,11 +1219,7 @@ internal interface ", new TestParameters(options: s_options.InterfaceNamesStartWithI)); } -#if CODE_STYLE - [Fact(Skip = "https://github.com/dotnet/roslyn/issues/42218")] -#else [Fact] -#endif [Trait(Traits.Feature, Traits.Features.NamingStyle)] [WorkItem(16562, "https://github.com/dotnet/roslyn/issues/16562")] public async Task TestRefactorNotify() @@ -1237,21 +1231,30 @@ public async Task TestRefactorNotify() var (_, action) = await GetCodeActionsAsync(workspace, testParameters); var previewOperations = await action.GetPreviewOperationsAsync(CancellationToken.None); - Assert.Empty(previewOperations.OfType()); + Assert.Equal(1, previewOperations.Length); var commitOperations = await action.GetOperationsAsync(CancellationToken.None); - Assert.Equal(2, commitOperations.Length); + Assert.Equal(1, commitOperations.Length); + + var symbolRenamedOperation = commitOperations[0]; + + var solutionPair = await TestActionAsync( + workspace, + @"public class C { }", + action, + ImmutableArray.Empty, + ImmutableArray.Empty, + ImmutableArray.Empty, + ImmutableArray.Empty, + testParameters); - var symbolRenamedOperation = (TestSymbolRenamedCodeActionOperationFactoryWorkspaceService.Operation)commitOperations[1]; - Assert.Equal("c", symbolRenamedOperation._symbol.Name); - Assert.Equal("C", symbolRenamedOperation._newName); + var oldSolution = solutionPair.Item1; + var newSolution = solutionPair.Item2; + + await RenameHelpers.AssertRenameAnnotationsAsync(oldSolution, newSolution, RenameHelpers.MakeSymbolPairs("c", "C")); } -#if CODE_STYLE - [Fact(Skip = "https://github.com/dotnet/roslyn/issues/42218")] -#else [Fact] -#endif [Trait(Traits.Feature, Traits.Features.NamingStyle)] [WorkItem(38513, "https://github.com/dotnet/roslyn/issues/38513")] public async Task TestRefactorNotifyInterfaceNamesStartWithI() @@ -1263,21 +1266,28 @@ public async Task TestRefactorNotifyInterfaceNamesStartWithI() var (_, action) = await GetCodeActionsAsync(workspace, testParameters); var previewOperations = await action.GetPreviewOperationsAsync(CancellationToken.None); - Assert.Empty(previewOperations.OfType()); + Assert.Equal(1, previewOperations.Length); var commitOperations = await action.GetOperationsAsync(CancellationToken.None); - Assert.Equal(2, commitOperations.Length); + Assert.Equal(1, commitOperations.Length); + + var solutionPair = await TestActionAsync( + workspace, + @"public interface ITest { }", + action, + ImmutableArray.Empty, + ImmutableArray.Empty, + ImmutableArray.Empty, + ImmutableArray.Empty, + testParameters); + + var oldSolution = solutionPair.Item1; + var newSolution = solutionPair.Item2; - var symbolRenamedOperation = (TestSymbolRenamedCodeActionOperationFactoryWorkspaceService.Operation)commitOperations[1]; - Assert.Equal("test", symbolRenamedOperation._symbol.Name); - Assert.Equal("ITest", symbolRenamedOperation._newName); + await RenameHelpers.AssertRenameAnnotationsAsync(oldSolution, newSolution, RenameHelpers.MakeSymbolPairs("test", "ITest")); } -#if CODE_STYLE - [Fact(Skip = "https://github.com/dotnet/roslyn/issues/42218")] -#else [Fact] -#endif [Trait(Traits.Feature, Traits.Features.NamingStyle)] [WorkItem(38513, "https://github.com/dotnet/roslyn/issues/38513")] public async Task TestRefactorNotifyTypeParameterNamesStartWithT() @@ -1286,20 +1296,37 @@ public async Task TestRefactorNotifyTypeParameterNamesStartWithT() { void DoOtherThing<[|arg|]>() { } }"; + + var expectedMarkup = @"public class A +{ + void DoOtherThing() { } +}"; + var testParameters = new TestParameters(options: s_options.TypeParameterNamesStartWithT); using var workspace = CreateWorkspaceFromOptions(markup, testParameters); var (_, action) = await GetCodeActionsAsync(workspace, testParameters); var previewOperations = await action.GetPreviewOperationsAsync(CancellationToken.None); - Assert.Empty(previewOperations.OfType()); + Assert.Equal(1, previewOperations.Length); var commitOperations = await action.GetOperationsAsync(CancellationToken.None); - Assert.Equal(2, commitOperations.Length); - - var symbolRenamedOperation = (TestSymbolRenamedCodeActionOperationFactoryWorkspaceService.Operation)commitOperations[1]; - Assert.Equal("arg", symbolRenamedOperation._symbol.Name); - Assert.Equal("TArg", symbolRenamedOperation._newName); + Assert.Equal(1, commitOperations.Length); + + var solutionPair = await TestActionAsync( + workspace, + expectedMarkup, + action, + ImmutableArray.Empty, + ImmutableArray.Empty, + ImmutableArray.Empty, + ImmutableArray.Empty, + testParameters); + + var oldSolution = solutionPair.Item1; + var newSolution = solutionPair.Item2; + + await RenameHelpers.AssertRenameAnnotationsAsync(oldSolution, newSolution, RenameHelpers.MakeSymbolPairs("arg", "TArg")); } [Fact, Trait(Traits.Feature, Traits.Features.NamingStyle)] diff --git a/src/Analyzers/Core/CodeFixes/NamingStyle/NamingStyleCodeFixProvider.cs b/src/Analyzers/Core/CodeFixes/NamingStyle/NamingStyleCodeFixProvider.cs index 4dc7601951e2b..eab5753f78e43 100644 --- a/src/Analyzers/Core/CodeFixes/NamingStyle/NamingStyleCodeFixProvider.cs +++ b/src/Analyzers/Core/CodeFixes/NamingStyle/NamingStyleCodeFixProvider.cs @@ -21,16 +21,10 @@ using Microsoft.CodeAnalysis.Shared.Extensions; using Roslyn.Utilities; -#if !CODE_STYLE // https://github.com/dotnet/roslyn/issues/42218 removing dependency on WorkspaceServices. -using Microsoft.CodeAnalysis.CodeActions.WorkspaceServices; -#endif - namespace Microsoft.CodeAnalysis.CodeFixes.NamingStyles { -#if !CODE_STYLE // https://github.com/dotnet/roslyn/issues/42218 tracks enabling this fixer in CodeStyle layer. [ExportCodeFixProvider(LanguageNames.CSharp, LanguageNames.VisualBasic, Name = PredefinedCodeFixProviderNames.ApplyNamingStyle), Shared] -#endif internal class NamingStyleCodeFixProvider : CodeFixProvider { [ImportingConstructor] @@ -88,11 +82,6 @@ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) { context.RegisterCodeFix( new FixNameCodeAction( -#if !CODE_STYLE - document.Project.Solution, - symbol, - fixedName, -#endif string.Format(CodeFixesResources.Fix_Name_Violation_colon_0, fixedName), c => FixAsync(document, symbol, fixedName, c), equivalenceKey: nameof(NamingStyleCodeFixProvider)), @@ -111,31 +100,15 @@ await document.GetOptionsAsync(cancellationToken).ConfigureAwait(false), private class FixNameCodeAction : CodeAction { -#if !CODE_STYLE - private readonly Solution _startingSolution; - private readonly ISymbol _symbol; - private readonly string _newName; -#endif - private readonly string _title; private readonly Func> _createChangedSolutionAsync; private readonly string _equivalenceKey; public FixNameCodeAction( -#if !CODE_STYLE - Solution startingSolution, - ISymbol symbol, - string newName, -#endif string title, Func> createChangedSolutionAsync, string equivalenceKey) { -#if !CODE_STYLE - _startingSolution = startingSolution; - _symbol = symbol; - _newName = newName; -#endif _title = title; _createChangedSolutionAsync = createChangedSolutionAsync; _equivalenceKey = equivalenceKey; @@ -151,16 +124,7 @@ protected override async Task> ComputeOperation var newSolution = await _createChangedSolutionAsync(cancellationToken).ConfigureAwait(false); var codeAction = new ApplyChangesOperation(newSolution); -#if CODE_STYLE // https://github.com/dotnet/roslyn/issues/42218 tracks removing this conditional code. return SpecializedCollections.SingletonEnumerable(codeAction); -#else - var factory = _startingSolution.Workspace.Services.GetRequiredService(); - return new CodeActionOperation[] - { - codeAction, - factory.CreateSymbolRenamedOperation(_symbol, _newName, _startingSolution, newSolution) - }.AsEnumerable(); -#endif } public override string Title => _title; diff --git a/src/Analyzers/VisualBasic/Tests/NamingStyles/NamingStylesTests.vb b/src/Analyzers/VisualBasic/Tests/NamingStyles/NamingStylesTests.vb index 20f3460bbb0ae..635476cd38dcd 100644 --- a/src/Analyzers/VisualBasic/Tests/NamingStyles/NamingStylesTests.vb +++ b/src/Analyzers/VisualBasic/Tests/NamingStyles/NamingStylesTests.vb @@ -19,10 +19,6 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.Diagnostics.Naming Return (New VisualBasicNamingStyleDiagnosticAnalyzer(), New NamingStyleCodeFixProvider()) End Function - Protected Overrides Function GetComposition() As TestComposition - Return MyBase.GetComposition().AddParts(GetType(TestSymbolRenamedCodeActionOperationFactoryWorkspaceService)) - End Function - ' TODO: everything else apart from locals diff --git a/src/EditorFeatures/CSharpTest/MoveToNamespace/MoveToNamespaceTests.cs b/src/EditorFeatures/CSharpTest/MoveToNamespace/MoveToNamespaceTests.cs index a37667cc1b7f3..35fae17f9b28d 100644 --- a/src/EditorFeatures/CSharpTest/MoveToNamespace/MoveToNamespaceTests.cs +++ b/src/EditorFeatures/CSharpTest/MoveToNamespace/MoveToNamespaceTests.cs @@ -25,9 +25,7 @@ public class MoveToNamespaceTests : AbstractMoveToNamespaceTests { private static readonly TestComposition s_compositionWithoutOptions = FeaturesTestCompositions.Features .AddExcludedPartTypes(typeof(IDiagnosticUpdateSourceRegistrationService)) - .AddParts( - typeof(MockDiagnosticUpdateSourceRegistrationService), - typeof(TestSymbolRenamedCodeActionOperationFactoryWorkspaceService)); + .AddParts(typeof(MockDiagnosticUpdateSourceRegistrationService)); private static readonly TestComposition s_composition = s_compositionWithoutOptions.AddParts( typeof(TestMoveToNamespaceOptionsService)); diff --git a/src/EditorFeatures/CSharpTest/Rename/CSharpRenamerTests.cs b/src/EditorFeatures/CSharpTest/Rename/CSharpRenamerTests.cs index 53ed7819261d4..415c5ce34cd28 100644 --- a/src/EditorFeatures/CSharpTest/Rename/CSharpRenamerTests.cs +++ b/src/EditorFeatures/CSharpTest/Rename/CSharpRenamerTests.cs @@ -5,6 +5,7 @@ #nullable disable using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Test.Utilities.Utilities; using Roslyn.Test.Utilities; using Xunit; @@ -17,9 +18,8 @@ public class CSharpRenamerTests : RenamerTests [Fact] public Task CSharp_TestEmptyDocument() => TestRenameDocument( - "", - "", - newDocumentName: "NewDocumentName"); + MakeSingleDocumentWithInfoArray(""), + MakeSingleDocumentWithInfoArray("", "NewDocumentName")); [Fact] public Task CSharp_TestNullDocumentName() @@ -37,34 +37,30 @@ public Task CSharp_RenameDocument_NoRenameType() [Fact] public Task CSharp_RenameDocument_RenameType() => TestRenameDocument( - @"class OriginalName {}", - @"class NewDocumentName {}", - documentName: "OriginalName.cs", - newDocumentName: "NewDocumentName.cs"); + MakeSingleDocumentWithInfoArray(@"class OriginalName {}", "OriginalName.cs"), + MakeSingleDocumentWithInfoArray(@"class NewDocumentName {}", "NewDocumentName.cs"), + RenameHelpers.MakeSymbolPairs("OriginalName", "NewDocumentName")); [Fact] public Task CSharp_RenameDocument_RenameType_CaseInsensitive() => TestRenameDocument( - @"class OriginalName {}", - @"class NewDocumentName {}", - documentName: "originalName.cs", - newDocumentName: "NewDocumentName.cs"); + MakeSingleDocumentWithInfoArray(@"class OriginalName {}", "originalName.cs"), + MakeSingleDocumentWithInfoArray(@"class NewDocumentName {}", "NewDocumentName.cs"), + RenameHelpers.MakeSymbolPairs("OriginalName", "NewDocumentName")); [Fact] public Task CSharp_RenameDocument_RenameInterface() => TestRenameDocument( - @"interface IInterface {}", - @"interface IInterface2 {}", - documentName: "IInterface.cs", - newDocumentName: "IInterface2.cs"); + MakeSingleDocumentWithInfoArray(@"interface IInterface {}", "IInterface.cs"), + MakeSingleDocumentWithInfoArray(@"interface IInterface2 {}", "IInterface2.cs"), + RenameHelpers.MakeSymbolPairs("IInterface", "IInterface2")); [Fact] public Task CSharp_RenameDocument_RenameEnum() => TestRenameDocument( - @"enum MyEnum {}", - @"enum MyEnum2 {}", - documentName: "MyEnum.cs", - newDocumentName: "MyEnum2.cs"); + MakeSingleDocumentWithInfoArray(@"enum MyEnum {}", "MyEnum.cs"), + MakeSingleDocumentWithInfoArray(@"enum MyEnum2 {}", "MyEnum2.cs"), + RenameHelpers.MakeSymbolPairs("MyEnum", "MyEnum2")); [Fact] public Task CSharp_RenameDocument_RenamePartialClass() @@ -131,7 +127,7 @@ class Other } }; - return TestRenameDocument(originalDocuments, expectedDocuments); + return TestRenameDocument(originalDocuments, expectedDocuments, RenameHelpers.MakeSymbolPairs("Test.C", "Test.C2")); } [Fact] @@ -144,30 +140,43 @@ class C } }", documentPath: @"Test\Path\Document.cs", - documentName: @"Document.cs"); + newDocumentPath: @"Test\Path\Document.cs"); [Fact] public Task CSharp_RenameDocument_RenameNamespace() - => TestRenameDocument( + { + + var originalDocuments = MakeSingleDocumentWithInfoArray( @"namespace Test.Path { class C { } }", + path: @"Test\Path\Document.cs", + name: "Document.cs"); + + var newDocuments = MakeSingleDocumentWithInfoArray( @"namespace Test.Path.After.Test { class C { } }", - documentPath: @"Test\Path\Document.cs", - documentName: @"Document.cs", - newDocumentPath: @"Test\Path\After\Test\Document.cs"); + path: @"Test\Path\After\Test\Document.cs", + name: "Document.cs"); + + return TestRenameDocument( + originalDocuments, + newDocuments, + RenameHelpers.MakeSymbolPairs("Test.Path.C", "Test.Path.After.Test.C")); + } [Fact] public Task CSharp_RenameDocument_RenameMultipleNamespaces() - => TestRenameDocument( + { + + var originalDocuments = MakeSingleDocumentWithInfoArray( @"namespace Test.Path { class C @@ -181,6 +190,10 @@ class C2 { } }", + path: @"Test\Path\Document.cs", + name: "Document.cs"); + + var newDocuments = MakeSingleDocumentWithInfoArray( @"namespace Test.Path.After.Test { class C @@ -194,13 +207,19 @@ class C2 { } }", - documentPath: @"Test\Path\Document.cs", - documentName: @"Document.cs", - newDocumentPath: @"Test\Path\After\Test\Document.cs"); + path: @"Test\Path\After\Test\Document.cs", + name: "Document.cs"); + + return TestRenameDocument( + originalDocuments, + newDocuments, + RenameHelpers.MakeSymbolPairs("Test.Path.C", "Test.Path.After.Test.C", "Test.Path.C2", "Test.Path.After.Test.C2")); + } [Fact] public Task CSharp_RenameDocument_RenameMultipleNamespaces2() - => TestRenameDocument( + { + var originalDocuments = MakeSingleDocumentWithInfoArray( @"namespace Test.Path { class C @@ -221,6 +240,10 @@ class C3 { } }", + name: "Document.cs", + path: @"Test\Path\Document.cs"); + + var newDocuments = MakeSingleDocumentWithInfoArray( @"namespace Test.Path.After.Test { class C @@ -241,13 +264,19 @@ class C3 { } }", - documentPath: @"Test\Path\Document.cs", - documentName: @"Document.cs", - newDocumentPath: @"Test\Path\After\Test\Document.cs"); + name: "Document.cs", + path: @"Test\Path\After\Test\Document.cs"); + + return TestRenameDocument( + originalDocuments, + newDocuments, + RenameHelpers.MakeSymbolPairs("Test.Path.C", "Test.Path.After.Test.C", "Test.Path.C2", "Test.Path.After.Test.C2")); + } [Fact] public Task CSharp_RenameDocument_RenameMultipleNamespaces3() - => TestRenameDocument( + { + var originalDocuments = MakeSingleDocumentWithInfoArray( @"namespace Test.Path { class C @@ -268,6 +297,10 @@ class C3 { } }", + name: "Document.cs", + path: @"Test\Path\Document.cs"); + + var newDocuments = MakeSingleDocumentWithInfoArray( @"namespace Test.Path.After.Test { class C @@ -288,13 +321,19 @@ class C3 { } }", - documentPath: @"Test\Path\Document.cs", - documentName: @"Document.cs", - newDocumentPath: @"Test\Path\After\Test\Document.cs"); + name: "Document.cs", + path: @"Test\Path\After\Test\Document.cs"); + + return TestRenameDocument( + originalDocuments, + newDocuments, + RenameHelpers.MakeSymbolPairs("Test.Path.C", "Test.Path.After.Test.C", "Test.Path.C3", "Test.Path.After.Test.C3")); + } [Fact] public Task CSharp_RenameDocument_RenameMultipleNamespaces_Nested() -=> TestRenameDocument( + { + var originalDocuments = MakeSingleDocumentWithInfoArray( @"namespace Test.Path { class C @@ -311,6 +350,10 @@ class C2 } } }", + path: @"Test\Path\Document.cs", + name: "Document.cs"); + + var newDocuments = MakeSingleDocumentWithInfoArray( @"namespace Test.Path.After.Test { class C @@ -327,48 +370,71 @@ class C2 } } }", -documentPath: @"Test\Path\Document.cs", -documentName: @"Document.cs", -newDocumentPath: @"Test\Path\After\Test\Document.cs"); + name: @"Document.cs", + path: @"Test\Path\After\Test\Document.cs"); + + return TestRenameDocument( + originalDocuments, + newDocuments, + RenameHelpers.MakeSymbolPairs("Test.Path.C", "Test.Path.After.Test.C")); + } [Fact] public Task CSharp_RenameDocument_RenameNamespace2() - => TestRenameDocument( -@"namespace Test.Path + { + var originalDocuments = MakeSingleDocumentWithInfoArray(@"namespace Test.Path { class C { } }", + path: @"Test\Path\Document.cs", + name: "Document.cs"); + + var newDocuments = MakeSingleDocumentWithInfoArray( @"namespace Test { class C { } }", - documentPath: @"Test\Path\Document.cs", - documentName: @"Document.cs", - newDocumentPath: @"Test\Document.cs"); + name: @"Document.cs", + path: @"Test\Document.cs"); + + return TestRenameDocument( + originalDocuments, + newDocuments, + RenameHelpers.MakeSymbolPairs("Test.Path.C", "Test.C")); + } [Fact] public Task CSharp_RenameDocument_RenameNamespaceAndClass() - => TestRenameDocument( + { + var originalDocuments = MakeSingleDocumentWithInfoArray( @"namespace Test.Path { class C { } }", + path: @"Test\Path\C.cs", + name: "C.cs"); + + var newDocuments = MakeSingleDocumentWithInfoArray( @"namespace Test { class C2 { } }", - documentPath: @"Test\Path\C2.cs", - documentName: @"C.cs", - newDocumentName: @"C2", - newDocumentPath: @"Test\C2.cs"); + path: @"Test\C2.cs", + name: @"C2.cs"); + + return TestRenameDocument( + originalDocuments, + newDocuments, + RenameHelpers.MakeSymbolPairs("Test.Path.C", "Test.C2")); + } [Fact] [WorkItem(46580, "https://github.com/dotnet/roslyn/issues/46580")] diff --git a/src/EditorFeatures/Core.Cocoa/InlineRename/AbstractEditorInlineRenameService.SymbolRenameInfo.cs b/src/EditorFeatures/Core.Cocoa/InlineRename/AbstractEditorInlineRenameService.SymbolRenameInfo.cs index 14c2e7caf7028..a1010f2848449 100644 --- a/src/EditorFeatures/Core.Cocoa/InlineRename/AbstractEditorInlineRenameService.SymbolRenameInfo.cs +++ b/src/EditorFeatures/Core.Cocoa/InlineRename/AbstractEditorInlineRenameService.SymbolRenameInfo.cs @@ -32,7 +32,6 @@ private partial class SymbolInlineRenameInfo : IInlineRenameInfoWithFileRename private const string AttributeSuffix = "Attribute"; private readonly Document _document; - private readonly IEnumerable _refactorNotifyServices; /// /// Whether or not we shortened the trigger span (say because we were renaming an attribute, @@ -54,7 +53,6 @@ private partial class SymbolInlineRenameInfo : IInlineRenameInfoWithFileRename public ISymbol RenameSymbol { get; } public SymbolInlineRenameInfo( - IEnumerable refactorNotifyServices, Document document, TextSpan triggerSpan, string triggerText, @@ -65,7 +63,6 @@ public SymbolInlineRenameInfo( { this.CanRename = true; - _refactorNotifyServices = refactorNotifyServices; _document = document; this.RenameSymbol = renameSymbol; @@ -194,18 +191,6 @@ public async Task FindRenameLocationsAsync(OptionSet? return new InlineRenameLocationSet(this, locations); } - public bool TryOnBeforeGlobalSymbolRenamed(Workspace workspace, IEnumerable changedDocumentIDs, string replacementText) - { - return _refactorNotifyServices.TryOnBeforeGlobalSymbolRenamed(workspace, changedDocumentIDs, RenameSymbol, - this.GetFinalSymbolName(replacementText), throwOnFailure: false); - } - - public bool TryOnAfterGlobalSymbolRenamed(Workspace workspace, IEnumerable changedDocumentIDs, string replacementText) - { - return _refactorNotifyServices.TryOnAfterGlobalSymbolRenamed(workspace, changedDocumentIDs, RenameSymbol, - this.GetFinalSymbolName(replacementText), throwOnFailure: false); - } - public InlineRenameFileRenameInfo GetFileRenameInfo() { if (RenameSymbol.Kind == SymbolKind.NamedType && diff --git a/src/EditorFeatures/Core.Cocoa/InlineRename/AbstractEditorInlineRenameService.cs b/src/EditorFeatures/Core.Cocoa/InlineRename/AbstractEditorInlineRenameService.cs index 8981bde5068d7..542db01be9666 100644 --- a/src/EditorFeatures/Core.Cocoa/InlineRename/AbstractEditorInlineRenameService.cs +++ b/src/EditorFeatures/Core.Cocoa/InlineRename/AbstractEditorInlineRenameService.cs @@ -20,11 +20,6 @@ namespace Microsoft.CodeAnalysis.Editor.Implementation.InlineRename { internal abstract partial class AbstractEditorInlineRenameService : IEditorInlineRenameService { - private readonly IEnumerable _refactorNotifyServices; - - protected AbstractEditorInlineRenameService(IEnumerable refactorNotifyServices) - => _refactorNotifyServices = refactorNotifyServices; - public async Task GetRenameInfoAsync(Document document, int position, CancellationToken cancellationToken) { var triggerToken = await GetTriggerTokenAsync(document, position, cancellationToken).ConfigureAwait(false); @@ -34,11 +29,10 @@ public async Task GetRenameInfoAsync(Document document, int p return new FailureInlineRenameInfo(EditorFeaturesResources.You_must_rename_an_identifier); } - return await GetRenameInfoAsync(_refactorNotifyServices, document, triggerToken, cancellationToken).ConfigureAwait(false); + return await GetRenameInfoAsync(document, triggerToken, cancellationToken).ConfigureAwait(false); } internal static async Task GetRenameInfoAsync( - IEnumerable refactorNotifyServices, Document document, SyntaxToken triggerToken, CancellationToken cancellationToken) { var syntaxFactsService = document.GetRequiredLanguageService(); @@ -201,7 +195,7 @@ internal static async Task GetRenameInfoAsync( var triggerText = sourceText.ToString(triggerToken.Span); return new SymbolInlineRenameInfo( - refactorNotifyServices, document, triggerToken.Span, triggerText, + document, triggerToken.Span, triggerText, symbol, forceRenameOverloads, documentSpans.ToImmutableAndFree(), cancellationToken); } diff --git a/src/EditorFeatures/Core.Cocoa/InlineRename/CSharpEditorInlineRenameService.cs b/src/EditorFeatures/Core.Cocoa/InlineRename/CSharpEditorInlineRenameService.cs index 0a6c12b757a91..7802aa80692b7 100644 --- a/src/EditorFeatures/Core.Cocoa/InlineRename/CSharpEditorInlineRenameService.cs +++ b/src/EditorFeatures/Core.Cocoa/InlineRename/CSharpEditorInlineRenameService.cs @@ -3,7 +3,6 @@ // See the LICENSE file in the project root for more information. using System; -using System.Collections.Generic; using System.Composition; using Microsoft.CodeAnalysis.Editor.Implementation.InlineRename; using Microsoft.CodeAnalysis.Host.Mef; @@ -15,8 +14,7 @@ internal class CSharpEditorInlineRenameService : AbstractEditorInlineRenameServi { [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public CSharpEditorInlineRenameService( - [ImportMany] IEnumerable refactorNotifyServices) : base(refactorNotifyServices) + public CSharpEditorInlineRenameService() { } } diff --git a/src/EditorFeatures/Core.Cocoa/InlineRename/InlineRenameSession.cs b/src/EditorFeatures/Core.Cocoa/InlineRename/InlineRenameSession.cs index 6de6b353fcaf1..ff671d14557d2 100644 --- a/src/EditorFeatures/Core.Cocoa/InlineRename/InlineRenameSession.cs +++ b/src/EditorFeatures/Core.Cocoa/InlineRename/InlineRenameSession.cs @@ -780,16 +780,6 @@ private void ApplyRename(Solution newSolution, IWaitContext waitContext) var changes = _baseSolution.GetChanges(newSolution); var changedDocumentIDs = changes.GetProjectChanges().SelectMany(c => c.GetChangedDocuments()).ToList(); - if (!_renameInfo.TryOnBeforeGlobalSymbolRenamed(_workspace, changedDocumentIDs, this.ReplacementText)) - { - var notificationService = _workspace.Services.GetService(); - notificationService.SendNotification( - EditorFeaturesResources.Rename_operation_was_cancelled_or_is_not_valid, - EditorFeaturesResources.Rename_Symbol, - NotificationSeverity.Error); - return; - } - using var undoTransaction = _workspace.OpenGlobalUndoTransaction(EditorFeaturesResources.Inline_Rename); var finalSolution = newSolution.Workspace.CurrentSolution; foreach (var id in changedDocumentIDs) @@ -829,15 +819,6 @@ private void ApplyRename(Solution newSolution, IWaitContext waitContext) .SelectMany(c => c.GetChangedDocuments().Concat(c.GetAddedDocuments())) .ToList(); - if (!_renameInfo.TryOnAfterGlobalSymbolRenamed(_workspace, finalChangedIds, this.ReplacementText)) - { - var notificationService = _workspace.Services.GetService(); - notificationService.SendNotification( - EditorFeaturesResources.Rename_operation_was_not_properly_completed_Some_file_might_not_have_been_updated, - EditorFeaturesResources.Rename_Symbol, - NotificationSeverity.Information); - } - undoTransaction.Commit(); } } diff --git a/src/EditorFeatures/Core.Cocoa/InlineRename/VisualBasicEditorInlineRenameService.cs b/src/EditorFeatures/Core.Cocoa/InlineRename/VisualBasicEditorInlineRenameService.cs index 11dabf5cbf5be..c08d28f5961d4 100644 --- a/src/EditorFeatures/Core.Cocoa/InlineRename/VisualBasicEditorInlineRenameService.cs +++ b/src/EditorFeatures/Core.Cocoa/InlineRename/VisualBasicEditorInlineRenameService.cs @@ -3,7 +3,6 @@ // See the LICENSE file in the project root for more information. using System; -using System.Collections.Generic; using System.Composition; using Microsoft.CodeAnalysis.Editor.Implementation.InlineRename; using Microsoft.CodeAnalysis.Host.Mef; @@ -15,8 +14,7 @@ internal class VisualBasicEditorInlineRenameService : AbstractEditorInlineRename { [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public VisualBasicEditorInlineRenameService( - [ImportMany] IEnumerable refactorNotifyServices) : base(refactorNotifyServices) + public VisualBasicEditorInlineRenameService() { } } diff --git a/src/EditorFeatures/Core.Wpf/InlineRename/AbstractEditorInlineRenameService.FailureInlineRenameInfo.cs b/src/EditorFeatures/Core.Wpf/InlineRename/AbstractEditorInlineRenameService.FailureInlineRenameInfo.cs index 5be670ca869ec..aefb7b2ef05ec 100644 --- a/src/EditorFeatures/Core.Wpf/InlineRename/AbstractEditorInlineRenameService.FailureInlineRenameInfo.cs +++ b/src/EditorFeatures/Core.Wpf/InlineRename/AbstractEditorInlineRenameService.FailureInlineRenameInfo.cs @@ -45,10 +45,6 @@ public FailureInlineRenameInfo(string localizedErrorMessage) public TextSpan? GetConflictEditSpan(InlineRenameLocation location, string triggerText, string replacementText, CancellationToken cancellationToken) => null; public Task FindRenameLocationsAsync(OptionSet optionSet, CancellationToken cancellationToken) => Task.FromResult(null); - - public bool TryOnAfterGlobalSymbolRenamed(Workspace workspace, IEnumerable changedDocumentIDs, string replacementText) => false; - - public bool TryOnBeforeGlobalSymbolRenamed(Workspace workspace, IEnumerable changedDocumentIDs, string replacementText) => false; } } } diff --git a/src/EditorFeatures/Core.Wpf/InlineRename/AbstractEditorInlineRenameService.SymbolRenameInfo.cs b/src/EditorFeatures/Core.Wpf/InlineRename/AbstractEditorInlineRenameService.SymbolRenameInfo.cs index 3b57149607560..4147598ea45f7 100644 --- a/src/EditorFeatures/Core.Wpf/InlineRename/AbstractEditorInlineRenameService.SymbolRenameInfo.cs +++ b/src/EditorFeatures/Core.Wpf/InlineRename/AbstractEditorInlineRenameService.SymbolRenameInfo.cs @@ -5,12 +5,10 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; -using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.LanguageServices; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Rename; @@ -31,7 +29,6 @@ private partial class SymbolInlineRenameInfo : IInlineRenameInfoWithFileRename private const string AttributeSuffix = "Attribute"; private readonly Document _document; - private readonly IEnumerable _refactorNotifyServices; /// /// Whether or not we shortened the trigger span (say because we were renaming an attribute, @@ -53,7 +50,6 @@ private partial class SymbolInlineRenameInfo : IInlineRenameInfoWithFileRename public ISymbol RenameSymbol { get; } public SymbolInlineRenameInfo( - IEnumerable refactorNotifyServices, Document document, TextSpan triggerSpan, string triggerText, @@ -64,7 +60,6 @@ public SymbolInlineRenameInfo( { this.CanRename = true; - _refactorNotifyServices = refactorNotifyServices; _document = document; this.RenameSymbol = renameSymbol; @@ -193,18 +188,6 @@ public async Task FindRenameLocationsAsync(OptionSet? return new InlineRenameLocationSet(this, locations); } - public bool TryOnBeforeGlobalSymbolRenamed(Workspace workspace, IEnumerable changedDocumentIDs, string replacementText) - { - return _refactorNotifyServices.TryOnBeforeGlobalSymbolRenamed(workspace, changedDocumentIDs, RenameSymbol, - this.GetFinalSymbolName(replacementText), throwOnFailure: false); - } - - public bool TryOnAfterGlobalSymbolRenamed(Workspace workspace, IEnumerable changedDocumentIDs, string replacementText) - { - return _refactorNotifyServices.TryOnAfterGlobalSymbolRenamed(workspace, changedDocumentIDs, RenameSymbol, - this.GetFinalSymbolName(replacementText), throwOnFailure: false); - } - public InlineRenameFileRenameInfo GetFileRenameInfo() { if (RenameSymbol.Kind == SymbolKind.NamedType && diff --git a/src/EditorFeatures/Core.Wpf/InlineRename/AbstractEditorInlineRenameService.cs b/src/EditorFeatures/Core.Wpf/InlineRename/AbstractEditorInlineRenameService.cs index 36fbc84b64cb4..0e2b8a270896f 100644 --- a/src/EditorFeatures/Core.Wpf/InlineRename/AbstractEditorInlineRenameService.cs +++ b/src/EditorFeatures/Core.Wpf/InlineRename/AbstractEditorInlineRenameService.cs @@ -18,11 +18,6 @@ namespace Microsoft.CodeAnalysis.Editor.Implementation.InlineRename { internal abstract partial class AbstractEditorInlineRenameService : IEditorInlineRenameService { - private readonly IEnumerable _refactorNotifyServices; - - protected AbstractEditorInlineRenameService(IEnumerable refactorNotifyServices) - => _refactorNotifyServices = refactorNotifyServices; - public async Task GetRenameInfoAsync(Document document, int position, CancellationToken cancellationToken) { var triggerToken = await GetTriggerTokenAsync(document, position, cancellationToken).ConfigureAwait(false); @@ -32,11 +27,10 @@ public async Task GetRenameInfoAsync(Document document, int p return new FailureInlineRenameInfo(EditorFeaturesResources.You_must_rename_an_identifier); } - return await GetRenameInfoAsync(_refactorNotifyServices, document, triggerToken, cancellationToken).ConfigureAwait(false); + return await GetRenameInfoAsync(document, triggerToken, cancellationToken).ConfigureAwait(false); } internal static async Task GetRenameInfoAsync( - IEnumerable refactorNotifyServices, Document document, SyntaxToken triggerToken, CancellationToken cancellationToken) { var syntaxFactsService = document.GetRequiredLanguageService(); @@ -199,7 +193,7 @@ internal static async Task GetRenameInfoAsync( var triggerText = sourceText.ToString(triggerToken.Span); return new SymbolInlineRenameInfo( - refactorNotifyServices, document, triggerToken.Span, triggerText, + document, triggerToken.Span, triggerText, symbol, forceRenameOverloads, documentSpans.ToImmutableAndFree(), cancellationToken); } diff --git a/src/EditorFeatures/Core.Wpf/InlineRename/CSharpEditorInlineRenameService.cs b/src/EditorFeatures/Core.Wpf/InlineRename/CSharpEditorInlineRenameService.cs index 3f9cf0f276c13..7492b0302ed97 100644 --- a/src/EditorFeatures/Core.Wpf/InlineRename/CSharpEditorInlineRenameService.cs +++ b/src/EditorFeatures/Core.Wpf/InlineRename/CSharpEditorInlineRenameService.cs @@ -5,7 +5,6 @@ #nullable disable using System; -using System.Collections.Generic; using System.Composition; using Microsoft.CodeAnalysis.Editor.Implementation.InlineRename; using Microsoft.CodeAnalysis.Host.Mef; @@ -17,8 +16,7 @@ internal class CSharpEditorInlineRenameService : AbstractEditorInlineRenameServi { [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public CSharpEditorInlineRenameService( - [ImportMany] IEnumerable refactorNotifyServices) : base(refactorNotifyServices) + public CSharpEditorInlineRenameService() { } } diff --git a/src/EditorFeatures/Core.Wpf/InlineRename/InlineRenameService.cs b/src/EditorFeatures/Core.Wpf/InlineRename/InlineRenameService.cs index 30e76741e0545..0112981b512ee 100644 --- a/src/EditorFeatures/Core.Wpf/InlineRename/InlineRenameService.cs +++ b/src/EditorFeatures/Core.Wpf/InlineRename/InlineRenameService.cs @@ -28,7 +28,6 @@ internal class InlineRenameService : IInlineRenameService private readonly IWaitIndicator _waitIndicator; private readonly ITextBufferAssociatedViewService _textBufferAssociatedViewService; private readonly IAsynchronousOperationListener _asyncListener; - private readonly IEnumerable _refactorNotifyServices; private readonly ITextBufferFactoryService _textBufferFactoryService; private readonly IFeatureServiceFactory _featureServiceFactory; private InlineRenameSession? _activeRenameSession; @@ -41,7 +40,6 @@ public InlineRenameService( ITextBufferAssociatedViewService textBufferAssociatedViewService, ITextBufferFactoryService textBufferFactoryService, IFeatureServiceFactory featureServiceFactory, - [ImportMany] IEnumerable refactorNotifyServices, IAsynchronousOperationListenerProvider listenerProvider) { _threadingContext = threadingContext; @@ -49,7 +47,6 @@ public InlineRenameService( _textBufferAssociatedViewService = textBufferAssociatedViewService; _textBufferFactoryService = textBufferFactoryService; _featureServiceFactory = featureServiceFactory; - _refactorNotifyServices = refactorNotifyServices; _asyncListener = listenerProvider.GetListener(FeatureAttribute.Rename); } @@ -90,7 +87,6 @@ public InlineRenameSessionInfo StartInlineSession( _textBufferAssociatedViewService, _textBufferFactoryService, _featureServiceFactory, - _refactorNotifyServices, _asyncListener); return new InlineRenameSessionInfo(ActiveSession); diff --git a/src/EditorFeatures/Core.Wpf/InlineRename/InlineRenameSession.cs b/src/EditorFeatures/Core.Wpf/InlineRename/InlineRenameSession.cs index 7e509d00e0e43..082325f4acfee 100644 --- a/src/EditorFeatures/Core.Wpf/InlineRename/InlineRenameSession.cs +++ b/src/EditorFeatures/Core.Wpf/InlineRename/InlineRenameSession.cs @@ -20,7 +20,6 @@ using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Internal.Log; -using Microsoft.CodeAnalysis.Notification; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Rename; using Microsoft.CodeAnalysis.Shared.Extensions; @@ -43,7 +42,6 @@ internal partial class InlineRenameSession : ForegroundThreadAffinitizedObject, private readonly ITextBufferFactoryService _textBufferFactoryService; private readonly IFeatureService _featureService; private readonly IFeatureDisableToken _completionDisabledToken; - private readonly IEnumerable _refactorNotifyServices; private readonly IDebuggingWorkspaceService _debuggingWorkspaceService; private readonly IAsynchronousOperationListener _asyncListener; private readonly Solution _baseSolution; @@ -120,7 +118,6 @@ public InlineRenameSession( ITextBufferAssociatedViewService textBufferAssociatedViewService, ITextBufferFactoryService textBufferFactoryService, IFeatureServiceFactory featureServiceFactory, - IEnumerable refactorNotifyServices, IAsynchronousOperationListener asyncListener) : base(threadingContext, assertIsForeground: true) { @@ -148,7 +145,6 @@ public InlineRenameSession( _renameService = renameService; _waitIndicator = waitIndicator; - _refactorNotifyServices = refactorNotifyServices; _asyncListener = asyncListener; _triggerView = textBufferAssociatedViewService.GetAssociatedTextViews(triggerSpan.Snapshot.TextBuffer).FirstOrDefault(v => v.HasAggregateFocus) ?? textBufferAssociatedViewService.GetAssociatedTextViews(triggerSpan.Snapshot.TextBuffer).First(); @@ -783,16 +779,6 @@ private void ApplyRename(Solution newSolution, IWaitContext waitContext) var changes = _baseSolution.GetChanges(newSolution); var changedDocumentIDs = changes.GetProjectChanges().SelectMany(c => c.GetChangedDocuments()).ToList(); - if (!_renameInfo.TryOnBeforeGlobalSymbolRenamed(_workspace, changedDocumentIDs, this.ReplacementText)) - { - var notificationService = _workspace.Services.GetService(); - notificationService.SendNotification( - EditorFeaturesResources.Rename_operation_was_cancelled_or_is_not_valid, - EditorFeaturesResources.Rename_Symbol, - NotificationSeverity.Error); - return; - } - using var undoTransaction = _workspace.OpenGlobalUndoTransaction(EditorFeaturesResources.Inline_Rename); var finalSolution = newSolution.Workspace.CurrentSolution; foreach (var id in changedDocumentIDs) @@ -821,26 +807,6 @@ private void ApplyRename(Solution newSolution, IWaitContext waitContext) if (_workspace.TryApplyChanges(finalSolution)) { - // Since rename can apply file changes as well, and those file - // changes can generate new document ids, include added documents - // as well as changed documents. This also ensures that any document - // that was removed is not included - var finalChanges = _workspace.CurrentSolution.GetChanges(_baseSolution); - - var finalChangedIds = finalChanges - .GetProjectChanges() - .SelectMany(c => c.GetChangedDocuments().Concat(c.GetAddedDocuments())) - .ToList(); - - if (!_renameInfo.TryOnAfterGlobalSymbolRenamed(_workspace, finalChangedIds, this.ReplacementText)) - { - var notificationService = _workspace.Services.GetService(); - notificationService.SendNotification( - EditorFeaturesResources.Rename_operation_was_not_properly_completed_Some_file_might_not_have_been_updated, - EditorFeaturesResources.Rename_Symbol, - NotificationSeverity.Information); - } - undoTransaction.Commit(); } } diff --git a/src/EditorFeatures/Core.Wpf/InlineRename/VisualBasicEditorInlineRenameService.cs b/src/EditorFeatures/Core.Wpf/InlineRename/VisualBasicEditorInlineRenameService.cs index 7bb12ae04b9b4..a82ae93668027 100644 --- a/src/EditorFeatures/Core.Wpf/InlineRename/VisualBasicEditorInlineRenameService.cs +++ b/src/EditorFeatures/Core.Wpf/InlineRename/VisualBasicEditorInlineRenameService.cs @@ -5,7 +5,6 @@ #nullable disable using System; -using System.Collections.Generic; using System.Composition; using Microsoft.CodeAnalysis.Editor.Implementation.InlineRename; using Microsoft.CodeAnalysis.Host.Mef; @@ -17,8 +16,7 @@ internal class VisualBasicEditorInlineRenameService : AbstractEditorInlineRename { [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public VisualBasicEditorInlineRenameService( - [ImportMany] IEnumerable refactorNotifyServices) : base(refactorNotifyServices) + public VisualBasicEditorInlineRenameService() { } } diff --git a/src/EditorFeatures/Core/IRefactorNotifyService.cs b/src/EditorFeatures/Core/IRefactorNotifyService.cs index 841083a864d00..0568caa21ed5b 100644 --- a/src/EditorFeatures/Core/IRefactorNotifyService.cs +++ b/src/EditorFeatures/Core/IRefactorNotifyService.cs @@ -12,6 +12,10 @@ namespace Microsoft.CodeAnalysis.Editor /// Allows editors to listen to refactoring events and take appropriate action. For example, /// when VS knows about a symbol rename, it asks the Xaml language service to update xaml files /// + /// + /// In general code changes shouldn't worry about triggering notifications, it's handled by the workspace + /// on + /// internal interface IRefactorNotifyService { /// diff --git a/src/EditorFeatures/Core/Implementation/InlineRename/IEditorInlineRenameService.cs b/src/EditorFeatures/Core/Implementation/InlineRename/IEditorInlineRenameService.cs index 00e0deafb732b..84c7c110aa491 100644 --- a/src/EditorFeatures/Core/Implementation/InlineRename/IEditorInlineRenameService.cs +++ b/src/EditorFeatures/Core/Implementation/InlineRename/IEditorInlineRenameService.cs @@ -226,18 +226,6 @@ internal interface IInlineRenameInfo /// locations to rename, as well as any time the rename options are changed by the user. /// Task FindRenameLocationsAsync(OptionSet optionSet, CancellationToken cancellationToken); - - /// - /// Called before the rename is applied to the specified documents in the workspace. Return - /// if rename should proceed, or if it should be canceled. - /// - bool TryOnBeforeGlobalSymbolRenamed(Workspace workspace, IEnumerable changedDocumentIDs, string replacementText); - - /// - /// Called after the rename is applied to the specified documents in the workspace. Return - /// if this operation succeeded, or if it failed. - /// - bool TryOnAfterGlobalSymbolRenamed(Workspace workspace, IEnumerable changedDocumentIDs, string replacementText); } internal interface IInlineRenameInfoWithFileRename : IInlineRenameInfo diff --git a/src/EditorFeatures/Core/Implementation/RenameTracking/RenameTrackingCodeRefactoringProvider.cs b/src/EditorFeatures/Core/Implementation/RenameTracking/RenameTrackingCodeRefactoringProvider.cs index 038eeac35ab4d..0965729785949 100644 --- a/src/EditorFeatures/Core/Implementation/RenameTracking/RenameTrackingCodeRefactoringProvider.cs +++ b/src/EditorFeatures/Core/Implementation/RenameTracking/RenameTrackingCodeRefactoringProvider.cs @@ -2,7 +2,6 @@ // 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.Collections.Generic; using System.Composition; using System.Diagnostics.CodeAnalysis; using System.Threading.Tasks; @@ -16,16 +15,13 @@ namespace Microsoft.CodeAnalysis.Editor.Implementation.RenameTracking internal class RenameTrackingCodeRefactoringProvider : CodeRefactoringProvider { private readonly ITextUndoHistoryRegistry _undoHistoryRegistry; - private readonly IEnumerable _refactorNotifyServices; [ImportingConstructor] [SuppressMessage("RoslynDiagnosticsReliability", "RS0033:Importing constructor should be [Obsolete]", Justification = "Used in test code: https://github.com/dotnet/roslyn/issues/42814")] public RenameTrackingCodeRefactoringProvider( - ITextUndoHistoryRegistry undoHistoryRegistry, - [ImportMany] IEnumerable refactorNotifyServices) + ITextUndoHistoryRegistry undoHistoryRegistry) { _undoHistoryRegistry = undoHistoryRegistry; - _refactorNotifyServices = refactorNotifyServices; } public override Task ComputeRefactoringsAsync(CodeRefactoringContext context) @@ -33,7 +29,7 @@ public override Task ComputeRefactoringsAsync(CodeRefactoringContext context) var (document, span, cancellationToken) = context; var (action, renameSpan) = RenameTrackingTaggerProvider.TryGetCodeAction( - document, span, _refactorNotifyServices, _undoHistoryRegistry, cancellationToken); + document, span, _undoHistoryRegistry, cancellationToken); if (action != null) context.RegisterRefactoring(action, renameSpan); diff --git a/src/EditorFeatures/Core/Implementation/RenameTracking/RenameTrackingTaggerProvider.RenameTrackingCodeAction.cs b/src/EditorFeatures/Core/Implementation/RenameTracking/RenameTrackingTaggerProvider.RenameTrackingCodeAction.cs index de3fe05254fd7..6cf313436553f 100644 --- a/src/EditorFeatures/Core/Implementation/RenameTracking/RenameTrackingTaggerProvider.RenameTrackingCodeAction.cs +++ b/src/EditorFeatures/Core/Implementation/RenameTracking/RenameTrackingTaggerProvider.RenameTrackingCodeAction.cs @@ -22,15 +22,13 @@ private class RenameTrackingCodeAction : CodeAction { private readonly string _title; private readonly Document _document; - private readonly IEnumerable _refactorNotifyServices; private readonly ITextUndoHistoryRegistry _undoHistoryRegistry; private RenameTrackingCommitter _renameTrackingCommitter; - public RenameTrackingCodeAction(Document document, string title, IEnumerable refactorNotifyServices, ITextUndoHistoryRegistry undoHistoryRegistry) + public RenameTrackingCodeAction(Document document, string title, ITextUndoHistoryRegistry undoHistoryRegistry) { _document = document; _title = title; - _refactorNotifyServices = refactorNotifyServices; _undoHistoryRegistry = undoHistoryRegistry; } @@ -85,9 +83,7 @@ private bool TryInitializeRenameTrackingCommitter(CancellationToken cancellation } var snapshotSpan = stateMachine.TrackingSession.TrackingSpan.GetSpan(stateMachine.Buffer.CurrentSnapshot); - var newName = snapshotSpan.GetText(); - var displayText = string.Format(EditorFeaturesResources.Rename_0_to_1, stateMachine.TrackingSession.OriginalName, newName); - _renameTrackingCommitter = new RenameTrackingCommitter(stateMachine, snapshotSpan, _refactorNotifyServices, _undoHistoryRegistry, displayText); + _renameTrackingCommitter = new RenameTrackingCommitter(stateMachine, snapshotSpan, _undoHistoryRegistry); return true; } } @@ -95,7 +91,7 @@ private bool TryInitializeRenameTrackingCommitter(CancellationToken cancellation return false; } - private sealed class RenameTrackingCommitterOperation : CodeActionOperation + private class RenameTrackingCommitterOperation : RenameTrackingOperation { private readonly RenameTrackingCommitter _committer; @@ -104,6 +100,12 @@ public RenameTrackingCommitterOperation(RenameTrackingCommitter committer) public override void Apply(Workspace workspace, CancellationToken cancellationToken) => _committer.Commit(cancellationToken); + + public override async Task<(Solution originalSolution, Solution newSolution)> GetChangedSolutionAsync(CancellationToken cancellationToken) + { + var solutionSet = await _committer.RenameSymbolAsync(cancellationToken).ConfigureAwait(false); + return (solutionSet.OriginalSolution, solutionSet.RenamedSolution); + } } } } diff --git a/src/EditorFeatures/Core/Implementation/RenameTracking/RenameTrackingTaggerProvider.RenameTrackingCommitter.cs b/src/EditorFeatures/Core/Implementation/RenameTracking/RenameTrackingTaggerProvider.RenameTrackingCommitter.cs index 2d048440c3e99..29323871ebc71 100644 --- a/src/EditorFeatures/Core/Implementation/RenameTracking/RenameTrackingTaggerProvider.RenameTrackingCommitter.cs +++ b/src/EditorFeatures/Core/Implementation/RenameTracking/RenameTrackingTaggerProvider.RenameTrackingCommitter.cs @@ -2,18 +2,13 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - -using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Editor.Undo; using Microsoft.CodeAnalysis.LanguageServices; -using Microsoft.CodeAnalysis.Notification; using Microsoft.CodeAnalysis.Rename; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; @@ -29,24 +24,18 @@ private class RenameTrackingCommitter : ForegroundThreadAffinitizedObject { private readonly StateMachine _stateMachine; private readonly SnapshotSpan _snapshotSpan; - private readonly IEnumerable _refactorNotifyServices; private readonly ITextUndoHistoryRegistry _undoHistoryRegistry; - private readonly string _displayText; private readonly AsyncLazy _renameSymbolResultGetter; public RenameTrackingCommitter( StateMachine stateMachine, SnapshotSpan snapshotSpan, - IEnumerable refactorNotifyServices, - ITextUndoHistoryRegistry undoHistoryRegistry, - string displayText) + ITextUndoHistoryRegistry undoHistoryRegistry) : base(stateMachine.ThreadingContext) { _stateMachine = stateMachine; _snapshotSpan = snapshotSpan; - _refactorNotifyServices = refactorNotifyServices; _undoHistoryRegistry = undoHistoryRegistry; - _displayText = displayText; _renameSymbolResultGetter = new AsyncLazy(c => RenameSymbolWorkerAsync(c), cacheResult: true); } @@ -121,6 +110,11 @@ private bool ApplyChangesToWorkspace(CancellationToken cancellationToken) var renameTrackingSolutionSet = RenameSymbolAsync(cancellationToken).WaitAndGetResult(cancellationToken); var document = _snapshotSpan.Snapshot.GetOpenDocumentInCurrentContextWithChanges(); + if (document is null) + { + return false; + } + var newName = _snapshotSpan.GetText(); var workspace = document.Project.Solution.Workspace; @@ -137,19 +131,6 @@ private bool ApplyChangesToWorkspace(CancellationToken cancellationToken) var trackingSessionId = _stateMachine.StoreCurrentTrackingSessionAndGenerateId(); UpdateWorkspaceForResetOfTypedIdentifier(workspace, renameTrackingSolutionSet.OriginalSolution, trackingSessionId); - // Now that the solution is back in its original state, notify third parties about - // the coming rename operation. - if (!_refactorNotifyServices.TryOnBeforeGlobalSymbolRenamed(workspace, changedDocuments, renameTrackingSolutionSet.Symbol, newName, throwOnFailure: false)) - { - var notificationService = workspace.Services.GetService(); - notificationService.SendNotification( - EditorFeaturesResources.Rename_operation_was_cancelled_or_is_not_valid, - EditorFeaturesResources.Rename_Symbol, - NotificationSeverity.Error); - - return true; - } - // move all changes to final solution based on the workspace's current solution, since the current solution // got updated when we reset it above. var finalSolution = workspace.CurrentSolution; @@ -157,7 +138,7 @@ private bool ApplyChangesToWorkspace(CancellationToken cancellationToken) { // because changes have already been made to the workspace (UpdateWorkspaceForResetOfTypedIdentifier() above), // these calls can't be cancelled and must be allowed to complete. - var root = renameTrackingSolutionSet.RenamedSolution.GetDocument(docId).GetSyntaxRootSynchronously(CancellationToken.None); + var root = renameTrackingSolutionSet.RenamedSolution.GetRequiredDocument(docId).GetRequiredSyntaxRootSynchronously(CancellationToken.None); finalSolution = finalSolution.WithDocumentSyntaxRoot(docId, root); } @@ -165,19 +146,15 @@ private bool ApplyChangesToWorkspace(CancellationToken cancellationToken) UpdateWorkspaceForGlobalIdentifierRename( workspace, finalSolution, - _displayText, - changedDocuments, - renameTrackingSolutionSet.Symbol, newName, trackingSessionId); - RenameTrackingDismisser.DismissRenameTracking(workspace, changedDocuments); return true; } private Solution CreateSolutionWithOriginalName(Document document, CancellationToken cancellationToken) { - var syntaxTree = document.GetSyntaxTreeSynchronously(cancellationToken); + var syntaxTree = document.GetRequiredSyntaxTreeSynchronously(cancellationToken); var fullText = syntaxTree.GetText(cancellationToken); var textChange = new TextChange(new TextSpan(_snapshotSpan.Start, _snapshotSpan.Length), _stateMachine.TrackingSession.OriginalName); @@ -199,17 +176,21 @@ private Solution CreateSolutionWithOriginalName(Document document, CancellationT return solution; } - private async Task TryGetSymbolAsync(Solution solutionWithOriginalName, DocumentId documentId, CancellationToken cancellationToken) + private async Task TryGetSymbolAsync(Solution solutionWithOriginalName, DocumentId documentId, CancellationToken cancellationToken) { - var documentWithOriginalName = solutionWithOriginalName.GetDocument(documentId); - var syntaxTreeWithOriginalName = await documentWithOriginalName.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); + var documentWithOriginalName = solutionWithOriginalName.GetRequiredDocument(documentId); + var syntaxTreeWithOriginalName = await documentWithOriginalName.GetRequiredSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); - var syntaxFacts = documentWithOriginalName.GetLanguageService(); + var syntaxFacts = documentWithOriginalName.GetRequiredLanguageService(); var semanticFacts = documentWithOriginalName.GetLanguageService(); var semanticModel = await documentWithOriginalName.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); var token = await syntaxTreeWithOriginalName.GetTouchingWordAsync(_snapshotSpan.Start, syntaxFacts, cancellationToken).ConfigureAwait(false); var tokenRenameInfo = RenameUtilities.GetTokenRenameInfo(semanticFacts, semanticModel, token, cancellationToken); + if (tokenRenameInfo is null) + { + return null; + } return tokenRenameInfo.HasSymbols ? tokenRenameInfo.Symbols.First() : null; } @@ -243,9 +224,6 @@ private void UpdateWorkspaceForGlobalIdentifierRename( Workspace workspace, Solution newSolution, string undoName, - IEnumerable changedDocuments, - ISymbol symbol, - string newName, int trackingSessionId) { AssertIsForeground(); @@ -266,15 +244,6 @@ private void UpdateWorkspaceForGlobalIdentifierRename( Contract.Fail("Rename Tracking could not update solution."); } - if (!_refactorNotifyServices.TryOnAfterGlobalSymbolRenamed(workspace, changedDocuments, symbol, newName, throwOnFailure: false)) - { - var notificationService = workspace.Services.GetService(); - notificationService.SendNotification( - EditorFeaturesResources.Rename_operation_was_not_properly_completed_Some_file_might_not_have_been_updated, - EditorFeaturesResources.Rename_Symbol, - NotificationSeverity.Information); - } - // Never resume tracking session on redo var undoPrimitiveAfter = new UndoPrimitive(_stateMachine.Buffer, trackingSessionId, shouldRestoreStateOnUndo: false); localUndoTransaction.AddUndo(undoPrimitiveAfter); diff --git a/src/EditorFeatures/Core/Implementation/RenameTracking/RenameTrackingTaggerProvider.RenameTrackingOperation.cs b/src/EditorFeatures/Core/Implementation/RenameTracking/RenameTrackingTaggerProvider.RenameTrackingOperation.cs new file mode 100644 index 0000000000000..4fcae1cba3105 --- /dev/null +++ b/src/EditorFeatures/Core/Implementation/RenameTracking/RenameTrackingTaggerProvider.RenameTrackingOperation.cs @@ -0,0 +1,21 @@ +// 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.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CodeActions; + +namespace Microsoft.CodeAnalysis.Editor.Implementation.RenameTracking +{ + internal sealed partial class RenameTrackingTaggerProvider + { + /// + /// This class exists for test usage without fully exposing the private classes of RenameTrackingTaggerProvider + /// + internal abstract class RenameTrackingOperation : CodeActionOperation + { + public abstract Task<(Solution originalSolution, Solution newSolution)> GetChangedSolutionAsync(CancellationToken cancellationToken); + } + } +} diff --git a/src/EditorFeatures/Core/Implementation/RenameTracking/RenameTrackingTaggerProvider.StateMachine.cs b/src/EditorFeatures/Core/Implementation/RenameTracking/RenameTrackingTaggerProvider.StateMachine.cs index 5f15c01a7d742..f065b9c73c574 100644 --- a/src/EditorFeatures/Core/Implementation/RenameTracking/RenameTrackingTaggerProvider.StateMachine.cs +++ b/src/EditorFeatures/Core/Implementation/RenameTracking/RenameTrackingTaggerProvider.StateMachine.cs @@ -284,7 +284,6 @@ public bool CanInvokeRename( internal (CodeAction action, TextSpan renameSpan) TryGetCodeAction( Document document, SourceText text, TextSpan userSpan, - IEnumerable refactorNotifyServices, ITextUndoHistoryRegistry undoHistoryRegistry, CancellationToken cancellationToken) { @@ -311,7 +310,7 @@ public bool CanInvokeRename( snapshotSpan.GetText()); return (new RenameTrackingCodeAction( - document, title, refactorNotifyServices, undoHistoryRegistry), + document, title, undoHistoryRegistry), snapshotSpan.Span.ToTextSpan()); } } diff --git a/src/EditorFeatures/Core/Implementation/RenameTracking/RenameTrackingTaggerProvider.cs b/src/EditorFeatures/Core/Implementation/RenameTracking/RenameTrackingTaggerProvider.cs index 43ca512dd0487..41c7de5b0cc0e 100644 --- a/src/EditorFeatures/Core/Implementation/RenameTracking/RenameTrackingTaggerProvider.cs +++ b/src/EditorFeatures/Core/Implementation/RenameTracking/RenameTrackingTaggerProvider.cs @@ -105,7 +105,6 @@ internal static bool ResetRenameTrackingStateWorker(Workspace workspace, Documen public static (CodeAction action, TextSpan renameSpan) TryGetCodeAction( Document document, TextSpan textSpan, - IEnumerable refactorNotifyServices, ITextUndoHistoryRegistry undoHistoryRegistry, CancellationToken cancellationToken) { @@ -119,7 +118,7 @@ public static (CodeAction action, TextSpan renameSpan) TryGetCodeAction( stateMachine.CanInvokeRename(out _, cancellationToken: cancellationToken)) { return stateMachine.TryGetCodeAction( - document, text, textSpan, refactorNotifyServices, undoHistoryRegistry, cancellationToken); + document, text, textSpan, undoHistoryRegistry, cancellationToken); } } diff --git a/src/EditorFeatures/Core/Shared/Extensions/IRefactorNotifyServiceExtensions.cs b/src/EditorFeatures/Core/Shared/Extensions/IRefactorNotifyServiceExtensions.cs index de47365091597..71f02c3d8cf15 100644 --- a/src/EditorFeatures/Core/Shared/Extensions/IRefactorNotifyServiceExtensions.cs +++ b/src/EditorFeatures/Core/Shared/Extensions/IRefactorNotifyServiceExtensions.cs @@ -2,48 +2,123 @@ // 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.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Editor.Shared.Utilities; +using Microsoft.CodeAnalysis.ErrorReporting; +using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Rename; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Editor.Shared.Extensions { internal static class IRefactorNotifyServiceExtensions { - public static bool TryOnBeforeGlobalSymbolRenamed( - this IEnumerable refactorNotifyServices, + /// + /// Calls all IRefactorNotifyService implementations TryOnBeforeGlobalSymbolRenamed, and if it succeds calls + /// TryOnAfterGlobalSymbolRenamed. All calls are made on the UI thread, the + /// is used to ensure this behavior. + /// + public static void TryNotifyChangesSynchronously( + this IEnumerable> refactorNotifyServices, Workspace workspace, - IEnumerable changedDocuments, - ISymbol symbol, - string newName, - bool throwOnFailure) + Solution newSolution, + Solution oldSolution, + IThreadingContext threadContext, + CancellationToken cancellationToken = default) { - foreach (var refactorNotifyService in refactorNotifyServices) + try { - if (!refactorNotifyService.TryOnBeforeGlobalSymbolRenamed(workspace, changedDocuments, symbol, newName, throwOnFailure)) - { - return false; - } + var refactorNotifyTask = refactorNotifyServices.TryNotifyChangesAsync(workspace, newSolution, oldSolution, threadContext, cancellationToken); + refactorNotifyTask.Wait(cancellationToken); + } + catch (Exception e) when (FatalError.ReportAndCatch(e)) + { + // No reason to fail because notify fails, but we want to track failure to see if there's something we're doing wrong. This results + // in a potentially bad user experience, but not complete broken and not worth crashing. } - - return true; } - public static bool TryOnAfterGlobalSymbolRenamed( - this IEnumerable refactorNotifyServices, + private static async Task TryNotifyChangesAsync( + this IEnumerable> refactorNotifyServices, Workspace workspace, - IEnumerable changedDocuments, - ISymbol symbol, - string newName, - bool throwOnFailure) + Solution newSolution, + Solution oldSolution, + IThreadingContext threadContext, + CancellationToken cancellationToken) { - foreach (var refactorNotifyService in refactorNotifyServices) + var projectChanges = newSolution.GetChanges(oldSolution).GetProjectChanges().ToImmutableArray(); + var changedDocumentIds = projectChanges.SelectMany(pd => pd.GetChangedDocuments(onlyGetDocumentsWithTextChanges: true)).ToImmutableArray(); + var _ = PooledDictionary.GetInstance(out var changedSymbols); + + foreach (var documentId in changedDocumentIds) + { + var newDocument = newSolution.GetRequiredDocument(documentId); + var oldDocument = oldSolution.GetDocument(documentId); + + if (oldDocument is null) + { + continue; + } + + var newSemanticModel = await newDocument.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); + var oldSemanticModel = await oldDocument.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); + + var renamedNodes = await GatherAnnotatedNodesAsync(newDocument, cancellationToken).ConfigureAwait(false); + + foreach (var node in renamedNodes) + { + foreach (var annotation in node.GetAnnotations(RenameSymbolAnnotation.RenameSymbolKind)) + { + var oldSymbol = annotation.ResolveSymbol(oldSemanticModel.Compilation); + Contract.ThrowIfNull(oldSymbol); + + var newSymbol = newSemanticModel.GetDeclaredSymbol(node, cancellationToken); + Contract.ThrowIfNull(newSymbol); + + changedSymbols.Add(oldSymbol, newSymbol); + } + } + } + + // TryOn{Before, After}GlobalSymbolRenamed requires calls from the foreground thread. + if (threadContext.HasMainThread) { - if (!refactorNotifyService.TryOnAfterGlobalSymbolRenamed(workspace, changedDocuments, symbol, newName, throwOnFailure)) + await threadContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + } + + foreach (var (oldSymbol, newSymbol) in changedSymbols) + { + foreach (var refactorNotifyService in refactorNotifyServices) { - return false; + if (refactorNotifyService.Value.TryOnBeforeGlobalSymbolRenamed(workspace, changedDocumentIds, oldSymbol, newSymbol.Name, false)) + { + refactorNotifyService.Value.TryOnAfterGlobalSymbolRenamed(workspace, changedDocumentIds, oldSymbol, newSymbol.Name, false); + } } } + } + + private static async Task> GatherAnnotatedNodesAsync(Document document, CancellationToken cancellationToken) + { + if (!document.SupportsSyntaxTree) + { + return ImmutableArray.Create(); + } + + var changedSymbols = ImmutableArray.CreateBuilder(); + + var syntaxRoot = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + var symbolRenameNodes = syntaxRoot.GetAnnotatedNodes(RenameSymbolAnnotation.RenameSymbolKind); + + changedSymbols.AddRange(symbolRenameNodes); - return true; + return changedSymbols.ToImmutable(); } } } diff --git a/src/EditorFeatures/DiagnosticsTestUtilities/MoveToNamespace/AbstractMoveToNamespaceTests.cs b/src/EditorFeatures/DiagnosticsTestUtilities/MoveToNamespace/AbstractMoveToNamespaceTests.cs index ca68a3cb41ace..f1af3760153a6 100644 --- a/src/EditorFeatures/DiagnosticsTestUtilities/MoveToNamespace/AbstractMoveToNamespaceTests.cs +++ b/src/EditorFeatures/DiagnosticsTestUtilities/MoveToNamespace/AbstractMoveToNamespaceTests.cs @@ -13,6 +13,8 @@ using Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions; using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces; using Microsoft.CodeAnalysis.MoveToNamespace; +using Microsoft.CodeAnalysis.Test.Utilities.Utilities; +using Microsoft.CodeAnalysis.Text; using Xunit; namespace Microsoft.CodeAnalysis.Test.Utilities.MoveToNamespace @@ -48,42 +50,25 @@ public async Task TestMoveToNamespaceAsync( testState.TestInvocationDocument.SelectedSpans.Single(), CancellationToken.None); - var operationTasks = actions - .Cast() - .Select(action => action.GetOperationsAsync(action.GetOptions(CancellationToken.None), CancellationToken.None)); - - foreach (var task in operationTasks) + foreach (var action in actions) { - var operations = await task; - if (optionCancelled || string.IsNullOrEmpty(targetNamespace)) { - Assert.Empty(operations); + Assert.Empty(await action.GetOperationsAsync(CancellationToken.None)); } else { - Assert.NotEmpty(operations); - var renamedCodeActionsOperations = operations - .Where(operation => operation is TestSymbolRenamedCodeActionOperationFactoryWorkspaceService.Operation) - .Cast() - .ToImmutableArray(); - - Assert.NotEmpty(renamedCodeActionsOperations); - Assert.NotNull(expectedSymbolChanges); + Assert.NotNull(expectedMarkup); - var checkedCodeActions = new HashSet(renamedCodeActionsOperations.Length); - foreach (var kvp in expectedSymbolChanges) - { - var originalName = kvp.Key; - var newName = kvp.Value; + var solutionPair = ApplyOperationsAndGetSolution( + workspace, + await action.GetOperationsAsync(CancellationToken.None)); - var codeAction = renamedCodeActionsOperations.FirstOrDefault(a => a._symbol.ToDisplayString() == originalName); - Assert.Equal(newName, codeAction?._newName); - Assert.False(checkedCodeActions.Contains(codeAction)); + var oldSolution = solutionPair.Item1; + var newSolution = solutionPair.Item2; - checkedCodeActions.Add(codeAction); - } + await RenameHelpers.AssertRenameAnnotationsAsync(oldSolution, newSolution, expectedSymbolChanges); } } diff --git a/src/EditorFeatures/Test/RenameTracking/RenameTrackingTaggerProviderTests.cs b/src/EditorFeatures/Test/RenameTracking/RenameTrackingTaggerProviderTests.cs index 15ec33a5c0d7c..67486035c6aae 100644 --- a/src/EditorFeatures/Test/RenameTracking/RenameTrackingTaggerProviderTests.cs +++ b/src/EditorFeatures/Test/RenameTracking/RenameTrackingTaggerProviderTests.cs @@ -731,84 +731,8 @@ public Cat() }"; using var state = RenameTrackingTestState.Create(code, LanguageNames.CSharp); state.EditorOperations.InsertText("s"); - await state.AssertTag("Cat", "Cats", invokeAction: true); - Assert.Equal(1, state.RefactorNotifyService.OnBeforeSymbolRenamedCount); - Assert.Equal(1, state.RefactorNotifyService.OnAfterSymbolRenamedCount); - - var expectedCode = @" -class Cats -{ - public Cats() - { - } -}"; - Assert.Equal(expectedCode, state.HostDocument.GetTextBuffer().CurrentSnapshot.GetText()); - - state.AssertNoNotificationMessage(); - await state.AssertNoTag(); - } - - [WpfFact] - [Trait(Traits.Feature, Traits.Features.RenameTracking)] - public async Task RenameTrackingHonorsThirdPartyRequestsForCancellationBeforeRename() - { - var code = @" -class Cat$$ -{ - public Cat() - { - } -}"; - using var state = RenameTrackingTestState.Create(code, LanguageNames.CSharp, onBeforeGlobalSymbolRenamedReturnValue: false); - state.EditorOperations.InsertText("s"); - await state.AssertTag("Cat", "Cats", invokeAction: true); - Assert.Equal(1, state.RefactorNotifyService.OnBeforeSymbolRenamedCount); - - // Make sure the rename didn't proceed - Assert.Equal(0, state.RefactorNotifyService.OnAfterSymbolRenamedCount); - await state.AssertNoTag(); - - var expectedCode = @" -class Cat -{ - public Cat() - { - } -}"; - Assert.Equal(expectedCode, state.HostDocument.GetTextBuffer().CurrentSnapshot.GetText()); - - state.AssertNotificationMessage(); - } - [WpfFact] - [Trait(Traits.Feature, Traits.Features.RenameTracking)] - public async Task RenameTrackingAlertsAboutThirdPartyRequestsForCancellationAfterRename() - { - var code = @" -class Cat$$ -{ - public Cat() - { - } -}"; - using var state = RenameTrackingTestState.Create(code, LanguageNames.CSharp, onAfterGlobalSymbolRenamedReturnValue: false); - state.EditorOperations.InsertText("s"); - await state.AssertTag("Cat", "Cats", invokeAction: true); - - Assert.Equal(1, state.RefactorNotifyService.OnBeforeSymbolRenamedCount); - Assert.Equal(1, state.RefactorNotifyService.OnAfterSymbolRenamedCount); - state.AssertNotificationMessage(); - - // Make sure the rename completed - var expectedCode = @" -class Cats -{ - public Cats() - { - } -}"; - Assert.Equal(expectedCode, state.HostDocument.GetTextBuffer().CurrentSnapshot.GetText()); - await state.AssertNoTag(); + await state.AssertRenameAnnotationAsync("Cat", "Cats"); } [WpfFact, WorkItem(530469, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/530469")] diff --git a/src/EditorFeatures/Test/RenameTracking/RenameTrackingTestState.cs b/src/EditorFeatures/Test/RenameTracking/RenameTrackingTestState.cs index 853e9afd779a1..33d309d48c052 100644 --- a/src/EditorFeatures/Test/RenameTracking/RenameTrackingTestState.cs +++ b/src/EditorFeatures/Test/RenameTracking/RenameTrackingTestState.cs @@ -21,6 +21,7 @@ using Microsoft.CodeAnalysis.Notification; using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.Shared.Utilities; +using Microsoft.CodeAnalysis.Test.Utilities.Utilities; using Microsoft.CodeAnalysis.Text; using Microsoft.CodeAnalysis.Text.Shared.Extensions; using Microsoft.VisualStudio.Composition; @@ -49,37 +50,28 @@ internal sealed class RenameTrackingTestState : IDisposable private readonly IEditorOperations _editorOperations; public IEditorOperations EditorOperations { get { return _editorOperations; } } - private readonly MockRefactorNotifyService _mockRefactorNotifyService; - public MockRefactorNotifyService RefactorNotifyService { get { return _mockRefactorNotifyService; } } - private readonly RenameTrackingCodeRefactoringProvider _codeRefactoringProvider; private readonly RenameTrackingCancellationCommandHandler _commandHandler = new RenameTrackingCancellationCommandHandler(); public static RenameTrackingTestState Create( string markup, - string languageName, - bool onBeforeGlobalSymbolRenamedReturnValue = true, - bool onAfterGlobalSymbolRenamedReturnValue = true) + string languageName) { var workspace = CreateTestWorkspace(markup, languageName); - return new RenameTrackingTestState(workspace, languageName, onBeforeGlobalSymbolRenamedReturnValue, onAfterGlobalSymbolRenamedReturnValue); + return new RenameTrackingTestState(workspace, languageName); } public static RenameTrackingTestState CreateFromWorkspaceXml( string workspaceXml, - string languageName, - bool onBeforeGlobalSymbolRenamedReturnValue = true, - bool onAfterGlobalSymbolRenamedReturnValue = true) + string languageName) { var workspace = CreateTestWorkspace(workspaceXml); - return new RenameTrackingTestState(workspace, languageName, onBeforeGlobalSymbolRenamedReturnValue, onAfterGlobalSymbolRenamedReturnValue); + return new RenameTrackingTestState(workspace, languageName); } public RenameTrackingTestState( TestWorkspace workspace, - string languageName, - bool onBeforeGlobalSymbolRenamedReturnValue = true, - bool onAfterGlobalSymbolRenamedReturnValue = true) + string languageName) { this.Workspace = workspace; @@ -88,11 +80,6 @@ public RenameTrackingTestState( _view.Caret.MoveTo(new SnapshotPoint(_view.TextSnapshot, _hostDocument.CursorPosition.Value)); _editorOperations = Workspace.GetService().GetEditorOperations(_view); _historyRegistry = Workspace.ExportProvider.GetExport().Value; - _mockRefactorNotifyService = new MockRefactorNotifyService - { - OnBeforeSymbolRenamedReturnValue = onBeforeGlobalSymbolRenamedReturnValue, - OnAfterSymbolRenamedReturnValue = onAfterGlobalSymbolRenamedReturnValue - }; // Mock the action taken by the workspace INotificationService var notificationService = (INotificationServiceCallback)Workspace.Services.GetRequiredService(); @@ -111,8 +98,7 @@ public RenameTrackingTestState( languageName == LanguageNames.VisualBasic) { _codeRefactoringProvider = new RenameTrackingCodeRefactoringProvider( - _historyRegistry, - SpecializedCollections.SingletonEnumerable(_mockRefactorNotifyService)); + _historyRegistry); } else { @@ -165,6 +151,21 @@ public async Task AssertNoTag() Assert.Equal(0, tags.Count()); } + public async Task AssertRenameAnnotationAsync(string oldName, string newName) + { + await WaitForAsyncOperationsAsync(); + + var codeAction = await TryGetCodeActionAsync(); + var operations = (await codeAction.GetOperationsAsync(CancellationToken.None)).ToArray(); + Assert.Equal(1, operations.Length); + + var applyChangesOperation = operations[0] as RenameTrackingTaggerProvider.RenameTrackingOperation; + Assert.NotNull(applyChangesOperation); + + var solutionPair = await applyChangesOperation.GetChangedSolutionAsync(CancellationToken.None); + await RenameHelpers.AssertRenameAnnotationsAsync(solutionPair.originalSolution, solutionPair.newSolution, RenameHelpers.MakeSymbolPairs(oldName, newName)); + } + /// If the current caret position will be used. public async Task TryGetCodeActionAsync(TextSpan? textSpan = null) { diff --git a/src/EditorFeatures/TestUtilities/Rename/RenamerTests.cs b/src/EditorFeatures/TestUtilities/Rename/RenamerTests.cs index 95a8fd566269e..52e4005f90581 100644 --- a/src/EditorFeatures/TestUtilities/Rename/RenamerTests.cs +++ b/src/EditorFeatures/TestUtilities/Rename/RenamerTests.cs @@ -5,10 +5,12 @@ #nullable disable using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Test.Utilities; +using Microsoft.CodeAnalysis.Test.Utilities.Utilities; using Microsoft.CodeAnalysis.Text; using Roslyn.Test.Utilities; using Xunit; @@ -34,6 +36,7 @@ protected struct DocumentWithInfo protected async Task TestRenameDocument( DocumentWithInfo[] startDocuments, DocumentWithInfo[] endDocuments, + ImmutableDictionary renamedSymbols = null, string[] expectedErrors = null) { using var workspace = new AdhocWorkspace(); @@ -65,6 +68,8 @@ protected async Task TestRenameDocument( documentIdToDocumentInfoMap.Add((documentId, endDocuments[i])); } + var originalSolution = solution; + foreach (var (documentId, endDocument) in documentIdToDocumentInfoMap) { var document = solution.GetDocument(documentId); @@ -98,6 +103,11 @@ protected async Task TestRenameDocument( AssertEx.EqualOrDiff(endDocument.Text, (await updatedDocument.GetTextAsync()).ToString()); Assert.Equal(0, remainingErrors.Count); } + + if (renamedSymbols is not null) + { + await RenameHelpers.AssertRenameAnnotationsAsync(originalSolution, solution, renamedSymbols, CancellationToken.None); + } } private static string[] GetDocumentFolders(string filePath) @@ -116,51 +126,16 @@ private static string[] GetDocumentFolders(string filePath) return splitPath.Take(splitPath.Length - 1).ToArray(); } - protected Task TestRenameDocument(string startText, string expectedText, string newDocumentName = null, string newDocumentPath = null, string documentName = null, string documentPath = null, string[] expectedErrors = null) - { - var defaultDocumentName = documentName ?? DefaultDocumentName; - var defaultDocumentPath = documentPath ?? s_defaultDocumentPath; - - var startDocuments = new[] - { - new DocumentWithInfo() - { - Text = startText, - DocumentName = defaultDocumentName, - DocumentFilePath = defaultDocumentPath - } - }; - - var endDocuments = new[] - { - new DocumentWithInfo() - { - Text = expectedText, - DocumentName = newDocumentName, - DocumentFilePath = newDocumentPath - } - }; - - return TestRenameDocument(startDocuments, endDocuments, expectedErrors); - } - protected async Task TestEmptyActionSet(string startText, string newDocumentName = null, string newDocumentPath = null, string documentName = null, string documentPath = null) { - var defaultDocumentName = documentName ?? DefaultDocumentName; - var defaultDocumentPath = documentPath ?? s_defaultDocumentPath; - var startDocuments = new[] { - new DocumentWithInfo() - { - Text = startText, - DocumentName = defaultDocumentName, - DocumentFilePath = defaultDocumentPath - } + MakeDocumentWithInfo(startText, documentName, documentPath) }; var endDocuments = new[] { + // Respect null values for final documents to test API correctly new DocumentWithInfo() { Text = startText, @@ -234,5 +209,17 @@ protected async Task TestRenameMappedFile(string startText, string documentName, var documentRenameResult = await Rename.Renamer.RenameDocumentAsync(document, newDocumentName, GetDocumentFolders(s_defaultDocumentPath)); Assert.Empty(documentRenameResult.ApplicableActions); } + protected static DocumentWithInfo MakeDocumentWithInfo(string text, string name = null, string path = null) + { + return new DocumentWithInfo() + { + Text = text, + DocumentName = name ?? DefaultDocumentName, + DocumentFilePath = path ?? s_defaultDocumentPath + }; + } + + protected static DocumentWithInfo[] MakeSingleDocumentWithInfoArray(string text, string name = null, string path = null) + => new[] { MakeDocumentWithInfo(text, name, path) }; } } diff --git a/src/EditorFeatures/TestUtilities/RenameTracking/MockRefactorNotifyService.cs b/src/EditorFeatures/TestUtilities/RenameTracking/MockRefactorNotifyService.cs deleted file mode 100644 index 778f369aba5b2..0000000000000 --- a/src/EditorFeatures/TestUtilities/RenameTracking/MockRefactorNotifyService.cs +++ /dev/null @@ -1,47 +0,0 @@ -// 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. - -#nullable disable - -using System.Collections.Generic; -using System.Runtime.InteropServices; - -namespace Microsoft.CodeAnalysis.Editor.UnitTests.RenameTracking -{ - public sealed class MockRefactorNotifyService : IRefactorNotifyService - { - private int _onBeforeSymbolRenamedCount = 0; - private int _onAfterSymbolRenamedCount = 0; - - public int OnBeforeSymbolRenamedCount { get { return _onBeforeSymbolRenamedCount; } } - public int OnAfterSymbolRenamedCount { get { return _onAfterSymbolRenamedCount; } } - - public bool OnBeforeSymbolRenamedReturnValue { get; set; } - public bool OnAfterSymbolRenamedReturnValue { get; set; } - - public bool TryOnBeforeGlobalSymbolRenamed(Workspace workspace, IEnumerable changedDocumentIDs, ISymbol symbol, string newName, bool throwOnFailure) - { - _onBeforeSymbolRenamedCount++; - - if (throwOnFailure && !OnBeforeSymbolRenamedReturnValue) - { - Marshal.ThrowExceptionForHR(unchecked((int)0x80004004)); // E_ABORT - } - - return OnBeforeSymbolRenamedReturnValue; - } - - public bool TryOnAfterGlobalSymbolRenamed(Workspace workspace, IEnumerable changedDocumentIDs, ISymbol symbol, string newName, bool throwOnFailure) - { - _onAfterSymbolRenamedCount++; - - if (throwOnFailure && !OnAfterSymbolRenamedReturnValue) - { - Marshal.ThrowExceptionForHR(unchecked((int)0x80004004)); // E_ABORT - } - - return OnAfterSymbolRenamedReturnValue; - } - } -} diff --git a/src/EditorFeatures/TestUtilities/Workspaces/TestSymbolRenamedCodeActionOperationFactoryWorkspaceService.cs b/src/EditorFeatures/TestUtilities/Workspaces/TestSymbolRenamedCodeActionOperationFactoryWorkspaceService.cs deleted file mode 100644 index fa48f115b4ccb..0000000000000 --- a/src/EditorFeatures/TestUtilities/Workspaces/TestSymbolRenamedCodeActionOperationFactoryWorkspaceService.cs +++ /dev/null @@ -1,43 +0,0 @@ -// 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. - -#nullable disable - -using System; -using System.Composition; -using Microsoft.CodeAnalysis.CodeActions; -using Microsoft.CodeAnalysis.CodeActions.WorkspaceServices; -using Microsoft.CodeAnalysis.Host.Mef; - -namespace Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces -{ - [ExportWorkspaceService(typeof(ISymbolRenamedCodeActionOperationFactoryWorkspaceService), ServiceLayer.Test), Shared, PartNotDiscoverable] - public class TestSymbolRenamedCodeActionOperationFactoryWorkspaceService : ISymbolRenamedCodeActionOperationFactoryWorkspaceService - { - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public TestSymbolRenamedCodeActionOperationFactoryWorkspaceService() - { - } - - public CodeActionOperation CreateSymbolRenamedOperation(ISymbol symbol, string newName, Solution startingSolution, Solution updatedSolution) - => new Operation(symbol, newName, startingSolution, updatedSolution); - - public class Operation : CodeActionOperation - { - public ISymbol _symbol; - public string _newName; - public Solution _startingSolution; - public Solution _updatedSolution; - - public Operation(ISymbol symbol, string newName, Solution startingSolution, Solution updatedSolution) - { - _symbol = symbol; - _newName = newName; - _startingSolution = startingSolution; - _updatedSolution = updatedSolution; - } - } - } -} diff --git a/src/EditorFeatures/TestUtilities/Workspaces/TestWorkspace.cs b/src/EditorFeatures/TestUtilities/Workspaces/TestWorkspace.cs index 88994577e4e7b..b1c4cb70b727e 100644 --- a/src/EditorFeatures/TestUtilities/Workspaces/TestWorkspace.cs +++ b/src/EditorFeatures/TestUtilities/Workspaces/TestWorkspace.cs @@ -17,6 +17,7 @@ using Microsoft.CodeAnalysis.MetadataAsSource; using Microsoft.CodeAnalysis.Notification; using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.Shared.Utilities; using Microsoft.CodeAnalysis.Test.Utilities; using Microsoft.CodeAnalysis.Text; using Microsoft.CodeAnalysis.Text.Shared.Extensions; @@ -50,6 +51,7 @@ public partial class TestWorkspace : Workspace private readonly BackgroundCompiler _backgroundCompiler; private readonly BackgroundParser _backgroundParser; private readonly IMetadataAsSourceFileService _metadataAsSourceFileService; + private readonly IEnumerable> _refactorNotifyServices; private readonly Dictionary _createdTextBuffers = new Dictionary(); @@ -95,6 +97,8 @@ public TestWorkspace(ExportProvider? exportProvider = null, TestComposition? com _metadataAsSourceFileService = ExportProvider.GetExportedValues().FirstOrDefault(); + _refactorNotifyServices = ExportProvider.GetExports(); + RegisterDocumentOptionProviders(ExportProvider.GetExports()); } @@ -263,6 +267,28 @@ public TServiceInterface GetService(string contentType, strin return values.Single(value => value.Metadata.Name == name && value.Metadata.ContentTypes.Contains(contentType)).Value; } + internal override bool TryApplyChanges( + Solution newSolution, + IProgressTracker progressTracker) + { + var currentSolution = CurrentSolution; + if (base.TryApplyChanges(newSolution, progressTracker)) + { + // Tests are not required to have a threading context in the exports, so + // check if one is available. RefactorNotify tests require that one is available + var threadingContextExports = ExportProvider.GetExports(); + var threadingContext = threadingContextExports.SingleOrDefault(); + if (threadingContext?.Value is not null) + { + _refactorNotifyServices.TryNotifyChangesSynchronously(this, newSolution, currentSolution, threadingContext.Value); + } + + return true; + } + + return false; + } + public override bool CanApplyChange(ApplyChangesKind feature) { switch (feature) diff --git a/src/Features/CSharp/Portable/CodeRefactorings/SyncNamespace/CSharpChangeNamespaceService.cs b/src/Features/CSharp/Portable/CodeRefactorings/SyncNamespace/CSharpChangeNamespaceService.cs index a052df2a27606..6198876c94ea4 100644 --- a/src/Features/CSharp/Portable/CodeRefactorings/SyncNamespace/CSharpChangeNamespaceService.cs +++ b/src/Features/CSharp/Portable/CodeRefactorings/SyncNamespace/CSharpChangeNamespaceService.cs @@ -13,6 +13,7 @@ using Microsoft.CodeAnalysis.ChangeNamespace; using Microsoft.CodeAnalysis.CSharp.CodeGeneration; using Microsoft.CodeAnalysis.CSharp.Extensions; +using Microsoft.CodeAnalysis.CSharp.LanguageServices; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.LanguageServices; diff --git a/src/Features/Core/Portable/CodeRefactorings/SyncNamespace/AbstractChangeNamespaceService.cs b/src/Features/Core/Portable/CodeRefactorings/SyncNamespace/AbstractChangeNamespaceService.cs index 6c5a881344ae1..701190ed6ce07 100644 --- a/src/Features/Core/Portable/CodeRefactorings/SyncNamespace/AbstractChangeNamespaceService.cs +++ b/src/Features/Core/Portable/CodeRefactorings/SyncNamespace/AbstractChangeNamespaceService.cs @@ -19,6 +19,7 @@ using Microsoft.CodeAnalysis.LanguageServices; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.RemoveUnnecessaryImports; +using Microsoft.CodeAnalysis.Rename; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Simplification; using Microsoft.CodeAnalysis.Text; @@ -617,7 +618,7 @@ private async Task FixDeclarationDocumentAsync( allowInHiddenRegions, cancellationToken).ConfigureAwait(false); - var root = await documentWithAddedImports.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + var root = await GetRenameAnnotatedRootAsync(documentWithAddedImports, cancellationToken).ConfigureAwait(false); root = ChangeNamespaceDeclaration((TCompilationUnitSyntax)root, oldNamespaceParts, newNamespaceParts) .WithAdditionalAnnotations(Formatter.Annotation); @@ -630,6 +631,34 @@ private async Task FixDeclarationDocumentAsync( return await Simplifier.ReduceAsync(formattedDocument, optionSet, cancellationToken).ConfigureAwait(false); } + /// + /// Annotated all of the members that will be "renamed" as part of changing the namespace they are in + /// + private static async Task GetRenameAnnotatedRootAsync(Document document, CancellationToken cancellationToken) + { + var documentEditor = await DocumentEditor.CreateAsync(document).ConfigureAwait(false); + var root = documentEditor.OriginalRoot; + var semanticModel = documentEditor.SemanticModel; + var syntaxFactsService = document.GetRequiredLanguageService(); + + var container = root.GetAnnotatedNodes(ContainerAnnotation).Single(); + + var members = container is TCompilationUnitSyntax + ? syntaxFactsService.GetMembersOfCompilationUnit(container) + : syntaxFactsService.GetMembersOfNamespaceDeclaration(container); + + foreach (var member in members) + { + var declaredSymbol = semanticModel.GetDeclaredSymbol(member, cancellationToken); + if (RenameSymbolAnnotation.TryAnnotateNode(member, declaredSymbol, out var annotatedNode)) + { + documentEditor.ReplaceNode(member, annotatedNode); + } + } + + return documentEditor.GetChangedRoot(); + } + private static async Task FixReferencingDocumentAsync( Document document, IEnumerable refLocations, diff --git a/src/Features/Core/Portable/CodeRefactorings/WorkspaceServices/ISymbolRenamedCodeActionOperationFactoryWorkspaceService.cs b/src/Features/Core/Portable/CodeRefactorings/WorkspaceServices/ISymbolRenamedCodeActionOperationFactoryWorkspaceService.cs deleted file mode 100644 index 4932c9771d20d..0000000000000 --- a/src/Features/Core/Portable/CodeRefactorings/WorkspaceServices/ISymbolRenamedCodeActionOperationFactoryWorkspaceService.cs +++ /dev/null @@ -1,15 +0,0 @@ -// 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. - -#nullable disable - -using Microsoft.CodeAnalysis.Host; - -namespace Microsoft.CodeAnalysis.CodeActions.WorkspaceServices -{ - internal interface ISymbolRenamedCodeActionOperationFactoryWorkspaceService : IWorkspaceService - { - CodeActionOperation CreateSymbolRenamedOperation(ISymbol symbol, string newName, Solution startingSolution, Solution updatedSolution); - } -} diff --git a/src/Features/Core/Portable/MoveToNamespace/AbstractMoveToNamespaceCodeAction.cs b/src/Features/Core/Portable/MoveToNamespace/AbstractMoveToNamespaceCodeAction.cs index 00534fe23c5bc..5283a9fe22b75 100644 --- a/src/Features/Core/Portable/MoveToNamespace/AbstractMoveToNamespaceCodeAction.cs +++ b/src/Features/Core/Portable/MoveToNamespace/AbstractMoveToNamespaceCodeAction.cs @@ -46,41 +46,13 @@ protected override async Task> ComputeOperation if (moveToNamespaceResult.Succeeded) { - return CreateRenameOperations(moveToNamespaceResult); + return SpecializedCollections.SingletonEnumerable(new ApplyChangesOperation(moveToNamespaceResult.UpdatedSolution)); } } return SpecializedCollections.EmptyEnumerable(); } - private static ImmutableArray CreateRenameOperations(MoveToNamespaceResult moveToNamespaceResult) - { - Debug.Assert(moveToNamespaceResult.Succeeded); - - using var _ = PooledObjects.ArrayBuilder.GetInstance(out var operations); - operations.Add(new ApplyChangesOperation(moveToNamespaceResult.UpdatedSolution)); - - var symbolRenameCodeActionOperationFactory = moveToNamespaceResult.UpdatedSolution.Workspace.Services.GetService(); - - // It's possible we're not in a host context providing this service, in which case - // just provide a code action that won't notify of the symbol rename. - // Without the symbol rename operation, code generators (like WPF) may not - // know to regenerate code correctly. - if (symbolRenameCodeActionOperationFactory != null) - { - foreach (var (newName, symbol) in moveToNamespaceResult.NewNameOriginalSymbolMapping) - { - operations.Add(symbolRenameCodeActionOperationFactory.CreateSymbolRenamedOperation( - symbol, - newName, - moveToNamespaceResult.OriginalSolution, - moveToNamespaceResult.UpdatedSolution)); - } - } - - return operations.ToImmutable(); - } - public static AbstractMoveToNamespaceCodeAction Generate(IMoveToNamespaceService changeNamespaceService, MoveToNamespaceAnalysisResult analysisResult) => analysisResult.Container switch { diff --git a/src/VisualStudio/CSharp/Impl/CodeModel/CSharpCodeModelService.cs b/src/VisualStudio/CSharp/Impl/CodeModel/CSharpCodeModelService.cs index 550213c64477c..b37b67faab00c 100644 --- a/src/VisualStudio/CSharp/Impl/CodeModel/CSharpCodeModelService.cs +++ b/src/VisualStudio/CSharp/Impl/CodeModel/CSharpCodeModelService.cs @@ -39,11 +39,9 @@ internal partial class CSharpCodeModelService : AbstractCodeModelService { internal CSharpCodeModelService( HostLanguageServices languageServiceProvider, - IEditorOptionsFactoryService editorOptionsFactoryService, - IEnumerable refactorNotifyServices) + IEditorOptionsFactoryService editorOptionsFactoryService) : base(languageServiceProvider, editorOptionsFactoryService, - refactorNotifyServices, BlankLineInGeneratedMethodFormattingRule.Instance, EndRegionFormattingRule.Instance) { diff --git a/src/VisualStudio/CSharp/Impl/CodeModel/CSharpCodeModelServiceFactory.cs b/src/VisualStudio/CSharp/Impl/CodeModel/CSharpCodeModelServiceFactory.cs index 32301f6bc65ed..0e63817ac1cd1 100644 --- a/src/VisualStudio/CSharp/Impl/CodeModel/CSharpCodeModelServiceFactory.cs +++ b/src/VisualStudio/CSharp/Impl/CodeModel/CSharpCodeModelServiceFactory.cs @@ -20,7 +20,6 @@ namespace Microsoft.VisualStudio.LanguageServices.CSharp.CodeModel internal partial class CSharpCodeModelServiceFactory : ILanguageServiceFactory { private readonly IEditorOptionsFactoryService _editorOptionsFactoryService; - private readonly IEnumerable _refactorNotifyServices; [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] @@ -29,10 +28,9 @@ public CSharpCodeModelServiceFactory( [ImportMany] IEnumerable refactorNotifyServices) { _editorOptionsFactoryService = editorOptionsFactoryService; - _refactorNotifyServices = refactorNotifyServices; } public ILanguageService CreateLanguageService(HostLanguageServices provider) - => new CSharpCodeModelService(provider, _editorOptionsFactoryService, _refactorNotifyServices); + => new CSharpCodeModelService(provider, _editorOptionsFactoryService); } } diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioWorkspaceImpl.cs b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioWorkspaceImpl.cs index 9d11d30fd3ab4..15b1603f3231a 100644 --- a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioWorkspaceImpl.cs +++ b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioWorkspaceImpl.cs @@ -15,12 +15,15 @@ using EnvDTE; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Editor; +using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Rename; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.Shared.Utilities; @@ -106,11 +109,13 @@ internal abstract partial class VisualStudioWorkspaceImpl : VisualStudioWorkspac private readonly Lazy _projectCodeModelFactory; private readonly IEnumerable> _documentOptionsProviderFactories; - private bool _documentOptionsProvidersInitialized = false; + private bool _documentOptionsProvidersInitialized; private readonly Lazy _lazyExternalErrorDiagnosticUpdateSource; private bool _isExternalErrorDiagnosticUpdateSourceSubscribedToSolutionBuildEvents; + private readonly IEnumerable> _refactorNotifyServices; + public VisualStudioWorkspaceImpl(ExportProvider exportProvider, IAsyncServiceProvider asyncServiceProvider) : base(VisualStudioMefHostServices.Create(exportProvider)) { @@ -146,6 +151,8 @@ public VisualStudioWorkspaceImpl(ExportProvider exportProvider, IAsyncServicePro exportProvider.GetExportedValue(), exportProvider.GetExportedValue(), _threadingContext), isThreadSafe: true); + + _refactorNotifyServices = exportProvider.GetExports(); } internal ExternalErrorDiagnosticUpdateSource ExternalErrorDiagnosticUpdateSource => _lazyExternalErrorDiagnosticUpdateSource.Value; @@ -266,6 +273,7 @@ internal VisualStudioProjectTracker ProjectTracker } } + [SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "The DocumentId should be contained inside a VisualStudioWorkspaceImpl even if member data is not accessed")] internal ContainedDocument? TryGetContainedDocument(DocumentId documentId) { // TODO: move everybody off of this instance method and replace them with calls to @@ -356,7 +364,13 @@ internal override bool TryApplyChanges( this.EnsureEditableDocuments(changedDocs); } - return base.TryApplyChanges(newSolution, progressTracker); + if (base.TryApplyChanges(newSolution, progressTracker)) + { + _refactorNotifyServices.TryNotifyChangesSynchronously(this, newSolution, currentSolution, _foregroundObject.ThreadingContext); + return true; + } + + return false; bool CanApplyChange(DocumentId documentId) { @@ -433,35 +447,32 @@ private void AddTextBufferCloneServiceToBuffer(object sender, TextBufferCreatedE => e.TextBuffer.Properties.AddProperty(typeof(ITextBufferCloneService), _textBufferCloneService); public override bool CanApplyChange(ApplyChangesKind feature) - { - switch (feature) - { - case ApplyChangesKind.AddDocument: - case ApplyChangesKind.RemoveDocument: - case ApplyChangesKind.ChangeDocument: - case ApplyChangesKind.AddMetadataReference: - case ApplyChangesKind.RemoveMetadataReference: - case ApplyChangesKind.AddProjectReference: - case ApplyChangesKind.RemoveProjectReference: - case ApplyChangesKind.AddAnalyzerReference: - case ApplyChangesKind.RemoveAnalyzerReference: - case ApplyChangesKind.AddAdditionalDocument: - case ApplyChangesKind.RemoveAdditionalDocument: - case ApplyChangesKind.ChangeAdditionalDocument: - case ApplyChangesKind.ChangeCompilationOptions: - case ApplyChangesKind.ChangeParseOptions: - case ApplyChangesKind.ChangeDocumentInfo: - case ApplyChangesKind.AddAnalyzerConfigDocument: - case ApplyChangesKind.RemoveAnalyzerConfigDocument: - case ApplyChangesKind.ChangeAnalyzerConfigDocument: - case ApplyChangesKind.AddSolutionAnalyzerReference: - case ApplyChangesKind.RemoveSolutionAnalyzerReference: - return true; - - default: - return false; - } - } + => feature switch + { + ApplyChangesKind.AddDocument or + ApplyChangesKind.RemoveDocument or + ApplyChangesKind.ChangeDocument or + ApplyChangesKind.AddMetadataReference or + ApplyChangesKind.RemoveMetadataReference or + ApplyChangesKind.AddProjectReference or + ApplyChangesKind.RemoveProjectReference or + ApplyChangesKind.AddAnalyzerReference or + ApplyChangesKind.RemoveAnalyzerReference or + ApplyChangesKind.AddAdditionalDocument or + ApplyChangesKind.RemoveAdditionalDocument or + ApplyChangesKind.ChangeAdditionalDocument or + ApplyChangesKind.ChangeCompilationOptions or + ApplyChangesKind.ChangeParseOptions or + ApplyChangesKind.ChangeDocumentInfo or + ApplyChangesKind.AddAnalyzerConfigDocument or + ApplyChangesKind.RemoveAnalyzerConfigDocument or + ApplyChangesKind.ChangeAnalyzerConfigDocument or + ApplyChangesKind.AddSolutionAnalyzerReference or + ApplyChangesKind.RemoveSolutionAnalyzerReference + => true, + + _ => false + }; private bool TryGetProjectData(ProjectId projectId, [NotNullWhen(returnValue: true)] out IVsHierarchy? hierarchy, [NotNullWhen(returnValue: true)] out EnvDTE.Project? project) { @@ -503,7 +514,7 @@ internal bool TryAddReferenceToProject(ProjectId projectId, string assemblyName) return true; } - private string? GetAnalyzerPath(AnalyzerReference analyzerReference) + private static string? GetAnalyzerPath(AnalyzerReference analyzerReference) => analyzerReference.FullPath; protected override void ApplyCompilationOptionsChanged(ProjectId projectId, CompilationOptions options) @@ -584,7 +595,7 @@ protected override void ApplyAnalyzerReferenceRemoved(ProjectId projectId, Analy } } - private string? GetMetadataPath(MetadataReference metadataReference) + private static string? GetMetadataPath(MetadataReference metadataReference) { if (metadataReference is PortableExecutableReference fileMetadata) { @@ -663,7 +674,9 @@ internal override void ApplyMappedFileChanges(SolutionChanges solutionChanges) // Instead, we invoke this in JTF run which will mitigate deadlocks when the ConfigureAwait(true) // tries to switch back to the main thread in the LSP client. // Link to LSP client bug for ConfigureAwait(true) - https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1216657 +#pragma warning disable VSTHRD102 // Implement internal logic asynchronously var mappedChanges = _threadingContext.JoinableTaskFactory.Run(() => GetMappedTextChanges(solutionChanges)); +#pragma warning restore VSTHRD102 // Implement internal logic asynchronously // Group the mapped text changes by file, then apply all mapped text changes for the file. foreach (var changesForFile in mappedChanges) @@ -678,7 +691,7 @@ internal override void ApplyMappedFileChanges(SolutionChanges solutionChanges) return; - async Task> GetMappedTextChanges(SolutionChanges solutionChanges) + static async Task> GetMappedTextChanges(SolutionChanges solutionChanges) { var filePathToMappedTextChanges = new MultiDictionary(); foreach (var projectChanges in solutionChanges.GetProjectChanges()) @@ -713,7 +726,7 @@ internal override void ApplyMappedFileChanges(SolutionChanges solutionChanges) return filePathToMappedTextChanges; } - bool ShouldApplyChangesToMappedDocuments(CodeAnalysis.Document document, [NotNullWhen(true)] out ISpanMappingService? spanMappingService) + static bool ShouldApplyChangesToMappedDocuments(CodeAnalysis.Document document, [NotNullWhen(true)] out ISpanMappingService? spanMappingService) { spanMappingService = document.Services.GetService(); // Only consider files that are mapped and that we are unable to apply changes to. @@ -850,7 +863,7 @@ private void AddDocumentCore(DocumentInfo info, SourceText initialText, TextDocu } } - private bool IsWebsite(EnvDTE.Project project) + private static bool IsWebsite(EnvDTE.Project project) => project.Kind == VsWebSite.PrjKind.prjKindVenusProject; private IEnumerable FilterFolderForProjectType(EnvDTE.Project project, IEnumerable folders) @@ -1253,7 +1266,7 @@ protected override void ApplyDocumentInfoChanged(DocumentId documentId, Document /// The currently supports only a subset of /// changes. /// - private void FailIfDocumentInfoChangesNotSupported(CodeAnalysis.Document document, DocumentInfo updatedInfo) + private static void FailIfDocumentInfoChangesNotSupported(CodeAnalysis.Document document, DocumentInfo updatedInfo) { if (document.SourceCodeKind != updatedInfo.SourceCodeKind) { @@ -1772,19 +1785,19 @@ private void ConvertProjectReferencesToMetadataReferences_NoLock(ProjectId proje // Update ConvertedProjectReferences in place to avoid duplicate list allocations for (var i = 0; i < referenceInfo.ConvertedProjectReferences.Count; i++) { - var convertedReference = referenceInfo.ConvertedProjectReferences[i]; + var (path, projectReference) = referenceInfo.ConvertedProjectReferences[i]; - if (string.Equals(convertedReference.path, outputPath, StringComparison.OrdinalIgnoreCase) && - convertedReference.projectReference.ProjectId == projectId) + if (string.Equals(path, outputPath, StringComparison.OrdinalIgnoreCase) && + projectReference.ProjectId == projectId) { var metadataReference = FileWatchedReferenceFactory.CreateReferenceAndStartWatchingFile( - convertedReference.path, + path, new MetadataReferenceProperties( - aliases: convertedReference.projectReference.Aliases, - embedInteropTypes: convertedReference.projectReference.EmbedInteropTypes)); + aliases: projectReference.Aliases, + embedInteropTypes: projectReference.EmbedInteropTypes)); - modifiedSolution = modifiedSolution.RemoveProjectReference(projectIdToRetarget, convertedReference.projectReference) + modifiedSolution = modifiedSolution.RemoveProjectReference(projectIdToRetarget, projectReference) .AddMetadataReference(projectIdToRetarget, metadataReference); projectIdsChanged.Add(projectIdToRetarget); diff --git a/src/VisualStudio/Core/Def/Implementation/Venus/ContainedLanguage.IVsContainedLanguageCodeSupport.cs b/src/VisualStudio/Core/Def/Implementation/Venus/ContainedLanguage.IVsContainedLanguageCodeSupport.cs index 9ed3201dc7475..c14e73ac40085 100644 --- a/src/VisualStudio/Core/Def/Implementation/Venus/ContainedLanguage.IVsContainedLanguageCodeSupport.cs +++ b/src/VisualStudio/Core/Def/Implementation/Venus/ContainedLanguage.IVsContainedLanguageCodeSupport.cs @@ -188,9 +188,7 @@ public int OnRenamed(ContainedLanguageRenameType clrt, string bstrOldID, string allowCancel: false, action: c => { - var refactorNotifyServices = this.ComponentModel.DefaultExportProvider.GetExportedValues(); - - if (!ContainedLanguageCodeSupport.TryRenameElement(GetThisDocument(), clrt, bstrOldID, bstrNewID, refactorNotifyServices, c.CancellationToken)) + if (!ContainedLanguageCodeSupport.TryRenameElement(GetThisDocument(), clrt, bstrOldID, bstrNewID, c.CancellationToken)) { result = s_CONTAINEDLANGUAGE_CANNOTFINDITEM; } diff --git a/src/VisualStudio/Core/Def/Implementation/Venus/ContainedLanguageCodeSupport.cs b/src/VisualStudio/Core/Def/Implementation/Venus/ContainedLanguageCodeSupport.cs index f8569f44fe72d..9a7fcf5b952c5 100644 --- a/src/VisualStudio/Core/Def/Implementation/Venus/ContainedLanguageCodeSupport.cs +++ b/src/VisualStudio/Core/Def/Implementation/Venus/ContainedLanguageCodeSupport.cs @@ -313,7 +313,6 @@ public static bool TryRenameElement( ContainedLanguageRenameType clrt, string oldFullyQualifiedName, string newFullyQualifiedName, - IEnumerable refactorNotifyServices, CancellationToken cancellationToken) { var symbol = FindSymbol(document, clrt, oldFullyQualifiedName, cancellationToken); @@ -332,19 +331,11 @@ public static bool TryRenameElement( var undoTitle = string.Format(EditorFeaturesResources.Rename_0_to_1, symbol.Name, newName); using (var workspaceUndoTransaction = workspace.OpenGlobalUndoTransaction(undoTitle)) { - // Notify third parties about the coming rename operation on the workspace, and let - // any exceptions propagate through - refactorNotifyServices.TryOnBeforeGlobalSymbolRenamed(workspace, changedDocuments, symbol, newName, throwOnFailure: true); - if (!workspace.TryApplyChanges(newSolution)) { Exceptions.ThrowEFail(); } - // Notify third parties about the completed rename operation on the workspace, and - // let any exceptions propagate through - refactorNotifyServices.TryOnAfterGlobalSymbolRenamed(workspace, changedDocuments, symbol, newName, throwOnFailure: true); - workspaceUndoTransaction.Commit(); } diff --git a/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioSymbolRenamedCodeActionOperationFactoryWorkspaceService.cs b/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioSymbolRenamedCodeActionOperationFactoryWorkspaceService.cs deleted file mode 100644 index 38c71239183ad..0000000000000 --- a/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioSymbolRenamedCodeActionOperationFactoryWorkspaceService.cs +++ /dev/null @@ -1,87 +0,0 @@ -// 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. - -#nullable disable - -using System; -using System.Collections.Generic; -using System.Composition; -using System.Linq; -using System.Threading; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CodeActions; -using Microsoft.CodeAnalysis.CodeActions.WorkspaceServices; -using Microsoft.CodeAnalysis.Editor; -using Microsoft.CodeAnalysis.Host.Mef; - -namespace Microsoft.VisualStudio.LanguageServices.Implementation -{ - using Workspace = Microsoft.CodeAnalysis.Workspace; - - [ExportWorkspaceService(typeof(ISymbolRenamedCodeActionOperationFactoryWorkspaceService), ServiceLayer.Host), Shared] - internal sealed class VisualStudioSymbolRenamedCodeActionOperationFactoryWorkspaceService : ISymbolRenamedCodeActionOperationFactoryWorkspaceService - { - private readonly IEnumerable _refactorNotifyServices; - - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public VisualStudioSymbolRenamedCodeActionOperationFactoryWorkspaceService( - [ImportMany] IEnumerable refactorNotifyServices) - { - _refactorNotifyServices = refactorNotifyServices; - } - - public CodeActionOperation CreateSymbolRenamedOperation(ISymbol symbol, string newName, Solution startingSolution, Solution updatedSolution) - { - return new RenameSymbolOperation( - _refactorNotifyServices, - symbol ?? throw new ArgumentNullException(nameof(symbol)), - newName ?? throw new ArgumentNullException(nameof(newName)), - startingSolution ?? throw new ArgumentNullException(nameof(startingSolution)), - updatedSolution ?? throw new ArgumentNullException(nameof(updatedSolution))); - } - - private class RenameSymbolOperation : CodeActionOperation - { - private readonly IEnumerable _refactorNotifyServices; - private readonly ISymbol _symbol; - private readonly string _newName; - private readonly Solution _startingSolution; - private readonly Solution _updatedSolution; - - public RenameSymbolOperation( - IEnumerable refactorNotifyServices, - ISymbol symbol, - string newName, - Solution startingSolution, - Solution updatedSolution) - { - _refactorNotifyServices = refactorNotifyServices; - _symbol = symbol; - _newName = newName; - _startingSolution = startingSolution; - _updatedSolution = updatedSolution; - } - - public override void Apply(Workspace workspace, CancellationToken cancellationToken = default) - { - var updatedDocumentIds = _updatedSolution.GetChanges(_startingSolution).GetProjectChanges().SelectMany(p => p.GetChangedDocuments()); - - foreach (var refactorNotifyService in _refactorNotifyServices) - { - // If something goes wrong and some language service rejects the rename, we - // can't really do anything about it because we're potentially in the middle of - // some unknown set of CodeActionOperations. This is a best effort approach. - - if (refactorNotifyService.TryOnBeforeGlobalSymbolRenamed(workspace, updatedDocumentIds, _symbol, _newName, throwOnFailure: false)) - { - refactorNotifyService.TryOnAfterGlobalSymbolRenamed(workspace, updatedDocumentIds, _symbol, _newName, throwOnFailure: false); - } - } - } - - public override string Title => string.Format(EditorFeaturesResources.Rename_0_to_1, _symbol.Name, _newName); - } - } -} diff --git a/src/VisualStudio/Core/Impl/CodeModel/AbstractCodeModelService.cs b/src/VisualStudio/Core/Impl/CodeModel/AbstractCodeModelService.cs index 45cee0f0db766..407f413ea4dc4 100644 --- a/src/VisualStudio/Core/Impl/CodeModel/AbstractCodeModelService.cs +++ b/src/VisualStudio/Core/Impl/CodeModel/AbstractCodeModelService.cs @@ -50,7 +50,6 @@ internal abstract partial class AbstractCodeModelService : ICodeModelService private readonly AbstractNodeNameGenerator _nodeNameGenerator; private readonly AbstractNodeLocator _nodeLocator; private readonly AbstractCodeModelEventCollector _eventCollector; - private readonly IEnumerable _refactorNotifyServices; private readonly AbstractFormattingRule _lineAdjustmentFormattingRule; private readonly AbstractFormattingRule _endRegionFormattingRule; @@ -58,7 +57,6 @@ internal abstract partial class AbstractCodeModelService : ICodeModelService protected AbstractCodeModelService( HostLanguageServices languageServiceProvider, IEditorOptionsFactoryService editorOptionsFactoryService, - IEnumerable refactorNotifyServices, AbstractFormattingRule lineAdjustmentFormattingRule, AbstractFormattingRule endRegionFormattingRule) { @@ -70,7 +68,6 @@ protected AbstractCodeModelService( _editorOptionsFactoryService = editorOptionsFactoryService; _lineAdjustmentFormattingRule = lineAdjustmentFormattingRule; _endRegionFormattingRule = endRegionFormattingRule; - _refactorNotifyServices = refactorNotifyServices; _nodeNameGenerator = CreateNodeNameGenerator(); _nodeLocator = CreateNodeLocator(); _eventCollector = CreateCodeModelEventCollector(); @@ -492,18 +489,12 @@ public void Rename(ISymbol symbol, string newName, Workspace workspace, ProjectC var newSolution = Renamer.RenameSymbolAsync(oldSolution, symbol, newName, oldSolution.Options).WaitAndGetResult_CodeModel(CancellationToken.None); var changedDocuments = newSolution.GetChangedDocuments(oldSolution); - // Notify third parties of the coming rename operation and let exceptions propagate out - _refactorNotifyServices.TryOnBeforeGlobalSymbolRenamed(workspace, changedDocuments, symbol, newName, throwOnFailure: true); - // Update the workspace. if (!workspace.TryApplyChanges(newSolution)) { throw Exceptions.ThrowEFail(); } - // Notify third parties of the completed rename operation and let exceptions propagate out - _refactorNotifyServices.TryOnAfterGlobalSymbolRenamed(workspace, changedDocuments, symbol, newName, throwOnFailure: true); - RenameTrackingDismisser.DismissRenameTracking(workspace, changedDocuments); // Update the node keys. diff --git a/src/VisualStudio/Core/Test/Venus/CSharpContainedLanguageSupportTests.vb b/src/VisualStudio/Core/Test/Venus/CSharpContainedLanguageSupportTests.vb index 71a9482094bae..9d0eee94de90b 100644 --- a/src/VisualStudio/Core/Test/Venus/CSharpContainedLanguageSupportTests.vb +++ b/src/VisualStudio/Core/Test/Venus/CSharpContainedLanguageSupportTests.vb @@ -833,7 +833,6 @@ public partial class _Default clrt:=ContainedLanguageRenameType.CLRT_CLASSMEMBER, oldFullyQualifiedName:="_Default.Page_Load", newFullyQualifiedName:="_Default.Page_Load1", - refactorNotifyServices:=SpecializedCollections.EmptyEnumerable(Of IRefactorNotifyService), cancellationToken:=Nothing) Assert.True(renameSucceeded) @@ -860,7 +859,6 @@ public partial class _Default clrt:=ContainedLanguageRenameType.CLRT_CLASSMEMBER, oldFullyQualifiedName:="_Default.Fictional", newFullyQualifiedName:="_Default.Fictional1", - refactorNotifyServices:=SpecializedCollections.EmptyEnumerable(Of IRefactorNotifyService), cancellationToken:=Nothing) Assert.False(renameSucceeded) @@ -878,7 +876,6 @@ public partial class _Default clrt:=ContainedLanguageRenameType.CLRT_CLASS, oldFullyQualifiedName:="Goo", newFullyQualifiedName:="Bar", - refactorNotifyServices:=SpecializedCollections.EmptyEnumerable(Of IRefactorNotifyService), cancellationToken:=Nothing) Assert.True(renameSucceeded) @@ -896,7 +893,6 @@ public partial class _Default clrt:=ContainedLanguageRenameType.CLRT_NAMESPACE, oldFullyQualifiedName:="Goo", newFullyQualifiedName:="Bar", - refactorNotifyServices:=SpecializedCollections.EmptyEnumerable(Of IRefactorNotifyService), cancellationToken:=Nothing) Assert.True(renameSucceeded) @@ -929,7 +925,6 @@ public class _Default clrt:=ContainedLanguageRenameType.CLRT_CLASSMEMBER, oldFullyQualifiedName:="_Default.button", newFullyQualifiedName:="_Default.button1", - refactorNotifyServices:=SpecializedCollections.EmptyEnumerable(Of IRefactorNotifyService), cancellationToken:=Nothing) Assert.True(renameSucceeded) diff --git a/src/VisualStudio/Core/Test/Venus/VisualBasicContainedLanguageSupportTests.vb b/src/VisualStudio/Core/Test/Venus/VisualBasicContainedLanguageSupportTests.vb index 81b309c25bd08..25bf7d927ca87 100644 --- a/src/VisualStudio/Core/Test/Venus/VisualBasicContainedLanguageSupportTests.vb +++ b/src/VisualStudio/Core/Test/Venus/VisualBasicContainedLanguageSupportTests.vb @@ -869,7 +869,6 @@ End Class.Value clrt:=ContainedLanguageRenameType.CLRT_CLASSMEMBER, oldFullyQualifiedName:="_Default.Page_Load", newFullyQualifiedName:="_Default.Page_Load1", - refactorNotifyServices:=SpecializedCollections.EmptyEnumerable(Of IRefactorNotifyService), cancellationToken:=Nothing) Assert.True(renameSucceeded) @@ -894,7 +893,6 @@ End Class.Value clrt:=ContainedLanguageRenameType.CLRT_CLASSMEMBER, oldFullyQualifiedName:="_Default.Fictional", newFullyQualifiedName:="_Default.Fictional1", - refactorNotifyServices:=SpecializedCollections.EmptyEnumerable(Of IRefactorNotifyService), cancellationToken:=Nothing) Assert.False(renameSucceeded) @@ -914,7 +912,6 @@ End Class.Value clrt:=ContainedLanguageRenameType.CLRT_CLASS, oldFullyQualifiedName:="Goo", newFullyQualifiedName:="Bar", - refactorNotifyServices:=SpecializedCollections.EmptyEnumerable(Of IRefactorNotifyService), cancellationToken:=Nothing) Assert.True(renameSucceeded) @@ -933,7 +930,6 @@ End Namespace.Value clrt:=ContainedLanguageRenameType.CLRT_NAMESPACE, oldFullyQualifiedName:="Goo", newFullyQualifiedName:="Bar", - refactorNotifyServices:=SpecializedCollections.EmptyEnumerable(Of IRefactorNotifyService), cancellationToken:=Nothing) Assert.True(renameSucceeded) @@ -963,7 +959,6 @@ End Class.Value clrt:=ContainedLanguageRenameType.CLRT_CLASSMEMBER, oldFullyQualifiedName:="_Default.button", newFullyQualifiedName:="_Default.button1", - refactorNotifyServices:=SpecializedCollections.EmptyEnumerable(Of IRefactorNotifyService), cancellationToken:=Nothing) Assert.True(renameSucceeded) diff --git a/src/VisualStudio/VisualBasic/Impl/CodeModel/VisualBasicCodeModelService.vb b/src/VisualStudio/VisualBasic/Impl/CodeModel/VisualBasicCodeModelService.vb index 825ed808387ac..31077972b098d 100644 --- a/src/VisualStudio/VisualBasic/Impl/CodeModel/VisualBasicCodeModelService.vb +++ b/src/VisualStudio/VisualBasic/Impl/CodeModel/VisualBasicCodeModelService.vb @@ -33,11 +33,10 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.CodeModel Private ReadOnly _commitBufferManagerFactory As CommitBufferManagerFactory - Friend Sub New(provider As HostLanguageServices, editorOptionsFactoryService As IEditorOptionsFactoryService, refactorNotifyServices As IEnumerable(Of IRefactorNotifyService), commitBufferManagerFactory As CommitBufferManagerFactory) + Friend Sub New(provider As HostLanguageServices, editorOptionsFactoryService As IEditorOptionsFactoryService, commitBufferManagerFactory As CommitBufferManagerFactory) MyBase.New( provider, editorOptionsFactoryService, - refactorNotifyServices, LineAdjustmentFormattingRule.Instance, EndRegionFormattingRule.Instance) diff --git a/src/VisualStudio/VisualBasic/Impl/CodeModel/VisualBasicCodeModelServiceFactory.vb b/src/VisualStudio/VisualBasic/Impl/CodeModel/VisualBasicCodeModelServiceFactory.vb index e8b8e9cfe625d..3151179a83285 100644 --- a/src/VisualStudio/VisualBasic/Impl/CodeModel/VisualBasicCodeModelServiceFactory.vb +++ b/src/VisualStudio/VisualBasic/Impl/CodeModel/VisualBasicCodeModelServiceFactory.vb @@ -17,21 +17,18 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.CodeModel Implements ILanguageServiceFactory Private ReadOnly _editorOptionsFactoryService As IEditorOptionsFactoryService - Private ReadOnly _refactorNotifyServices As IEnumerable(Of IRefactorNotifyService) Private ReadOnly _commitBufferManagerFactory As CommitBufferManagerFactory Public Sub New(editorOptionsFactoryService As IEditorOptionsFactoryService, - refactorNotifyServices As IEnumerable(Of IRefactorNotifyService), commitBufferManagerFactory As CommitBufferManagerFactory) Me._editorOptionsFactoryService = editorOptionsFactoryService - Me._refactorNotifyServices = refactorNotifyServices Me._commitBufferManagerFactory = commitBufferManagerFactory End Sub Public Function CreateLanguageService(provider As HostLanguageServices) As ILanguageService Implements ILanguageServiceFactory.CreateLanguageService - Return New VisualBasicCodeModelService(provider, Me._editorOptionsFactoryService, _refactorNotifyServices, _commitBufferManagerFactory) + Return New VisualBasicCodeModelService(provider, Me._editorOptionsFactoryService, _commitBufferManagerFactory) End Function End Class End Namespace diff --git a/src/Workspaces/Core/Portable/Rename/ConflictEngine/ConflictResolver.Session.cs b/src/Workspaces/Core/Portable/Rename/ConflictEngine/ConflictResolver.Session.cs index af0f45213f2ab..bac1f6ddc249c 100644 --- a/src/Workspaces/Core/Portable/Rename/ConflictEngine/ConflictResolver.Session.cs +++ b/src/Workspaces/Core/Portable/Rename/ConflictEngine/ConflictResolver.Session.cs @@ -10,6 +10,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Editing; using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.FindSymbols; using Microsoft.CodeAnalysis.Formatting; @@ -768,11 +769,39 @@ private async Task AnnotateAndRename_WorkerAsync( { try { + var solutionWithRenameSymbolAnnotations = originalSolution; + + if (RenameSymbolAnnotation.ShouldAnnotateSymbol(_renameLocationSet.Symbol)) + { + // Add the RenameSymbolAnnotation to declarations that will change + foreach (var syntaxReferenceGroup in _renameLocationSet.Symbol.DeclaringSyntaxReferences.GroupBy(r => r.SyntaxTree)) + { + var syntaxTree = syntaxReferenceGroup.Key; + var syntaxReferences = syntaxReferenceGroup.AsArray(); + + var document = solutionWithRenameSymbolAnnotations.GetRequiredDocument(syntaxTree); + var documentEditor = await DocumentEditor.CreateAsync(document, _cancellationToken).ConfigureAwait(false); + + foreach (var syntaxReference in syntaxReferences) + { + var node = await syntaxReference.GetSyntaxAsync(_cancellationToken).ConfigureAwait(false); + + if (RenameSymbolAnnotation.TryAnnotateNode(node, _renameLocationSet.Symbol, out var annotatedNode)) + { + documentEditor.ReplaceNode(node, annotatedNode); + } + } + + var annotatedDocument = documentEditor.GetChangedDocument(); + solutionWithRenameSymbolAnnotations = annotatedDocument.Project.Solution; + } + } + foreach (var documentId in documentIdsToRename.ToList()) { _cancellationToken.ThrowIfCancellationRequested(); - var document = originalSolution.GetRequiredDocument(documentId); + var document = solutionWithRenameSymbolAnnotations.GetRequiredDocument(documentId); var semanticModel = await document.GetRequiredSemanticModelAsync(_cancellationToken).ConfigureAwait(false); var originalSyntaxRoot = await semanticModel.SyntaxTree.GetRootAsync(_cancellationToken).ConfigureAwait(false); @@ -806,7 +835,7 @@ private async Task AnnotateAndRename_WorkerAsync( allTextSpansInSingleSourceTree, stringAndCommentTextSpansInSingleSourceTree, conflictLocationSpans, - originalSolution, + solutionWithRenameSymbolAnnotations, _renameLocationSet.Symbol, replacementTextValid, renameSpansTracker, diff --git a/src/Workspaces/Core/Portable/Rename/RenameSymbolAnnotation.cs b/src/Workspaces/Core/Portable/Rename/RenameSymbolAnnotation.cs new file mode 100644 index 0000000000000..3a2644f82e9ac --- /dev/null +++ b/src/Workspaces/Core/Portable/Rename/RenameSymbolAnnotation.cs @@ -0,0 +1,83 @@ +// 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.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Roslyn.Utilities; + +#nullable enable + +namespace Microsoft.CodeAnalysis.Rename +{ + /// + /// Provides a way to produce and consume annotations for rename + /// that contain the previous symbol serialized as a . + /// This annotations is used by + /// in some cases to notify the workspace host of refactorings. + /// + /// + /// This annotation is applied to the declaring syntax of a symbol that has been renamed. + /// When Workspace.TryApplyChanges happens in Visual Studio, we raise rename events for that symbol. + /// + internal static class RenameSymbolAnnotation + { + public const string RenameSymbolKind = nameof(RenameSymbolAnnotation); + + /// + /// Returns true if there are no existing rename annotations + /// and the symbol is a candidate for annotation. Out parameter + /// is the node with the annotation on it + /// + public static bool TryAnnotateNode(SyntaxNode syntaxNode, ISymbol? symbol, [NotNullWhen(returnValue: true)] out SyntaxNode? annotatedNode) + { + annotatedNode = null; + if (!ShouldAnnotateSymbol(symbol)) + { + return false; + } + + // If the node is already annotated, assume the original annotation is + // more correct for representing the original symbol + if (syntaxNode.GetAnnotations(RenameSymbolKind).Any()) + { + return false; + } + + var annotation = new SyntaxAnnotation(RenameSymbolKind, SerializeData(symbol)); + annotatedNode = syntaxNode.WithAdditionalAnnotations(annotation); + return true; + } + + public static bool ShouldAnnotateSymbol([NotNullWhen(returnValue: true)] ISymbol? symbol) + => symbol is null + ? false + : symbol.DeclaringSyntaxReferences.Any(); + + public static ISymbol? ResolveSymbol(this SyntaxAnnotation annotation, Compilation oldCompilation) + { + if (annotation.Kind != RenameSymbolKind) + { + throw new InvalidOperationException($"'{annotation}' is not of kind {RenameSymbolKind}"); + } + + if (string.IsNullOrEmpty(annotation.Data)) + { + throw new InvalidOperationException($"'{annotation}' has no data"); + } + + var oldSymbolKey = SymbolKey.ResolveString(annotation.Data, oldCompilation); + + return oldSymbolKey.Symbol; + } + + private static string SerializeData(ISymbol symbol) + => symbol.GetSymbolKey().ToString(); + } +} diff --git a/src/Workspaces/Core/Portable/Workspace/Workspace.cs b/src/Workspaces/Core/Portable/Workspace/Workspace.cs index 239a174cc3bf6..6e32fd9caf2b8 100644 --- a/src/Workspaces/Core/Portable/Workspace/Workspace.cs +++ b/src/Workspaces/Core/Portable/Workspace/Workspace.cs @@ -1927,7 +1927,6 @@ protected virtual void ApplyAnalyzerConfigDocumentTextChanged(DocumentId id, Sou Debug.Assert(CanApplyChange(ApplyChangesKind.ChangeAnalyzerConfigDocument)); this.OnAnalyzerConfigDocumentTextLoaderChanged(id, TextLoader.From(TextAndVersion.Create(text, VersionStamp.Create()))); } - #endregion #region Checks and Asserts diff --git a/src/Workspaces/CoreTestUtilities/RenameHelpers.cs b/src/Workspaces/CoreTestUtilities/RenameHelpers.cs new file mode 100644 index 0000000000000..d675541ca8dda --- /dev/null +++ b/src/Workspaces/CoreTestUtilities/RenameHelpers.cs @@ -0,0 +1,76 @@ +// 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.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Rename; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Roslyn.Utilities; +using Xunit; + +namespace Microsoft.CodeAnalysis.Test.Utilities.Utilities +{ + public static class RenameHelpers + { + public static async Task AssertRenameAnnotationsAsync(Solution originalSolution, Solution newSolution, IReadOnlyDictionary symbolPairs, CancellationToken cancellationToken = default) + { + var allDocuments = newSolution.Projects.SelectMany(p => p.Documents); + var remainingSymbols = symbolPairs.ToHashSet(); + + foreach (var document in allDocuments) + { + var root = await document.GetRequiredSyntaxRootAsync(cancellationToken); + var annotatedNodesAndTokens = root.GetAnnotatedNodesAndTokens(RenameSymbolAnnotation.RenameSymbolKind); + + var annotatedNodes = annotatedNodesAndTokens.Select(nodeOrToken => nodeOrToken.IsNode ? nodeOrToken.AsNode() : nodeOrToken.AsToken().Parent); + + var originalDocument = originalSolution.GetRequiredDocument(document.Id); + var originalSemanticModel = await originalDocument.GetRequiredSemanticModelAsync(cancellationToken); + var originalCompilation = originalSemanticModel.Compilation; + + var newSemanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken); + + foreach (var node in annotatedNodes) + { + var annotation = node!.GetAnnotations(RenameSymbolAnnotation.RenameSymbolKind).Single(); + var originalSymbol = RenameSymbolAnnotation.ResolveSymbol(annotation, originalCompilation); + var newSymbol = newSemanticModel.GetDeclaredSymbol(node, cancellationToken); + + Assert.NotNull(originalSymbol); + Assert.NotNull(newSymbol); + + var pair = (originalSymbol!.ToDisplayString(), newSymbol!.ToDisplayString()); + + var isRemoved = remainingSymbols.Remove(pair.ToKeyValuePair()); + if (!isRemoved) + { + Assert.True(symbolPairs.Contains(pair.ToKeyValuePair()), $"Rename annotation for {pair} not expected"); + } + } + } + + Assert.Empty(remainingSymbols); + } + + public static ImmutableDictionary MakeSymbolPairs(params (string, string)[] pairs) + => pairs.ToImmutableDictionary(p => p.Item1, p => p.Item2); + + public static ImmutableDictionary MakeSymbolPairs(params string[] items) + { + Assert.Equal(0, items.Length % 2); + + var tuples = new List<(string, string)>(items.Length / 2); + + for (var i = 0; i < items.Length; i += 2) + { + tuples.Add((items[i], items[i + 1])); + } + + return MakeSymbolPairs(tuples.ToArray()); + } + } +}