diff --git a/src/EditorFeatures/CSharp/CSharpEditorFeatures.csproj b/src/EditorFeatures/CSharp/CSharpEditorFeatures.csproj index 88fa74e11c498f1a6b10ab79fcde00828dfd1cfa..00583439c981a20e3b203674724d8fdc578cf236 100644 --- a/src/EditorFeatures/CSharp/CSharpEditorFeatures.csproj +++ b/src/EditorFeatures/CSharp/CSharpEditorFeatures.csproj @@ -201,6 +201,7 @@ + @@ -275,4 +276,4 @@ - + \ No newline at end of file diff --git a/src/EditorFeatures/CSharp/RenameTracking/CSharpRenameTrackingLanguageHeuristicsService.cs b/src/EditorFeatures/CSharp/RenameTracking/CSharpRenameTrackingLanguageHeuristicsService.cs new file mode 100644 index 0000000000000000000000000000000000000000..0fc209a227e50672c700093bb0674e8da50a3636 --- /dev/null +++ b/src/EditorFeatures/CSharp/RenameTracking/CSharpRenameTrackingLanguageHeuristicsService.cs @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Composition; +using Microsoft.CodeAnalysis.Editor.Implementation.RenameTracking; +using Microsoft.CodeAnalysis.Host.Mef; + +namespace Microsoft.CodeAnalysis.Editor.CSharp.RenameTracking +{ + [ExportLanguageService(typeof(IRenameTrackingLanguageHeuristicsService), LanguageNames.CSharp), Shared] + internal sealed class CSharpRenameTrackingLanguageHeuristicsService : IRenameTrackingLanguageHeuristicsService + { + public bool IsIdentifierValidForRenameTracking(string name) + { + return name != "var" && name != "dynamic"; + } + } +} diff --git a/src/EditorFeatures/Core/EditorFeatures.csproj b/src/EditorFeatures/Core/EditorFeatures.csproj index 2bac1c9d2fe560841f85c02edfe7550dcd9ee0d3..23598c0f06d7150e1b649eee9caf092e57a57a89 100644 --- a/src/EditorFeatures/Core/EditorFeatures.csproj +++ b/src/EditorFeatures/Core/EditorFeatures.csproj @@ -586,6 +586,7 @@ + diff --git a/src/EditorFeatures/Core/Implementation/RenameTracking/IRenameTrackingLanguageHeuristicsService.cs b/src/EditorFeatures/Core/Implementation/RenameTracking/IRenameTrackingLanguageHeuristicsService.cs new file mode 100644 index 0000000000000000000000000000000000000000..206234fc850836e68b40ecacd159afad2939ffe5 --- /dev/null +++ b/src/EditorFeatures/Core/Implementation/RenameTracking/IRenameTrackingLanguageHeuristicsService.cs @@ -0,0 +1,11 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.CodeAnalysis.Host; + +namespace Microsoft.CodeAnalysis.Editor.Implementation.RenameTracking +{ + internal interface IRenameTrackingLanguageHeuristicsService : ILanguageService + { + bool IsIdentifierValidForRenameTracking(string name); + } +} diff --git a/src/EditorFeatures/Core/Implementation/RenameTracking/RenameTrackingTaggerProvider.StateMachine.cs b/src/EditorFeatures/Core/Implementation/RenameTracking/RenameTrackingTaggerProvider.StateMachine.cs index 61076f3967a539bee0ef3d8365c65594776db43f..b227373a37eee9bf75afe4a1593312f582805618 100644 --- a/src/EditorFeatures/Core/Implementation/RenameTracking/RenameTrackingTaggerProvider.StateMachine.cs +++ b/src/EditorFeatures/Core/Implementation/RenameTracking/RenameTrackingTaggerProvider.StateMachine.cs @@ -253,8 +253,9 @@ public bool CanInvokeRename(out TrackingSession trackingSession, bool isSmartTag } ISyntaxFactsService syntaxFactsService; - return TryGetSyntaxFactsService(out syntaxFactsService) && - trackingSession.CanInvokeRename(syntaxFactsService, isSmartTagCheck, waitForResult, cancellationToken); + IRenameTrackingLanguageHeuristicsService languageHeuristicsService; + return TryGetSyntaxFactsService(out syntaxFactsService) && TryGetLanguageHeuristicsService(out languageHeuristicsService) && + trackingSession.CanInvokeRename(syntaxFactsService, languageHeuristicsService, isSmartTagCheck, waitForResult, cancellationToken); } internal async Task> GetDiagnostic(SyntaxTree tree, DiagnosticDescriptor diagnosticDescriptor, CancellationToken cancellationToken) @@ -332,6 +333,20 @@ private bool TryGetSyntaxFactsService(out ISyntaxFactsService syntaxFactsService return syntaxFactsService != null; } + private bool TryGetLanguageHeuristicsService(out IRenameTrackingLanguageHeuristicsService languageHeuristicsService) + { + // Can be called on a background thread + + languageHeuristicsService = null; + var document = _buffer.CurrentSnapshot.GetOpenDocumentInCurrentContextWithChanges(); + if (document != null) + { + languageHeuristicsService = document.Project.LanguageServices.GetService(); + } + + return languageHeuristicsService != null; + } + public void Connect() { AssertIsForeground(); diff --git a/src/EditorFeatures/Core/Implementation/RenameTracking/RenameTrackingTaggerProvider.TrackingSession.cs b/src/EditorFeatures/Core/Implementation/RenameTracking/RenameTrackingTaggerProvider.TrackingSession.cs index 4aa38af80dfca182507e186b5bdc23f6e451a95f..a12323635d4d2f62fc7288815046ef1c7c1f2d8e 100644 --- a/src/EditorFeatures/Core/Implementation/RenameTracking/RenameTrackingTaggerProvider.TrackingSession.cs +++ b/src/EditorFeatures/Core/Implementation/RenameTracking/RenameTrackingTaggerProvider.TrackingSession.cs @@ -159,7 +159,8 @@ private async Task DetermineIfRenamableIdentifierAsync(Sn return TriggerIdentifierKind.NotRenamable; } - if (syntaxFactsService.IsIdentifier(token)) + var languageHeuristicsService = document.Project.LanguageServices.GetService(); + if (syntaxFactsService.IsIdentifier(token) && languageHeuristicsService.IsIdentifierValidForRenameTracking(token.Text)) { var semanticModel = await document.GetSemanticModelForNodeAsync(token.Parent, _cancellationToken).ConfigureAwait(false); var semanticFacts = document.GetLanguageService(); @@ -218,7 +219,12 @@ private async Task DetermineIfRenamableSymbolAsync(ISymbo : TriggerIdentifierKind.RenamableReference; } - internal bool CanInvokeRename(ISyntaxFactsService syntaxFactsService, bool isSmartTagCheck, bool waitForResult, CancellationToken cancellationToken) + internal bool CanInvokeRename( + ISyntaxFactsService syntaxFactsService, + IRenameTrackingLanguageHeuristicsService languageHeuristicsService, + bool isSmartTagCheck, + bool waitForResult, + CancellationToken cancellationToken) { if (IsRenamableIdentifier(_isRenamableIdentifierTask, waitForResult, cancellationToken)) { @@ -227,7 +233,8 @@ internal bool CanInvokeRename(ISyntaxFactsService syntaxFactsService, bool isSma var comparison = isRenamingDeclaration || syntaxFactsService.IsCaseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase; if (!string.Equals(OriginalName, newName, comparison) && - syntaxFactsService.IsValidIdentifier(newName)) + syntaxFactsService.IsValidIdentifier(newName) && + languageHeuristicsService.IsIdentifierValidForRenameTracking(newName)) { // At this point, we want to allow renaming if the user invoked Ctrl+. explicitly, but we // want to avoid showing a smart tag if we're renaming a reference that binds to an existing diff --git a/src/EditorFeatures/Test/RenameTracking/RenameTrackingTaggerProviderTests.cs b/src/EditorFeatures/Test/RenameTracking/RenameTrackingTaggerProviderTests.cs index 980867e19ce73b3d38a97b991dea698d0f0c62d1..60f8b19c1141562baf8e4148ad2ebd90b640f62b 100644 --- a/src/EditorFeatures/Test/RenameTracking/RenameTrackingTaggerProviderTests.cs +++ b/src/EditorFeatures/Test/RenameTracking/RenameTrackingTaggerProviderTests.cs @@ -1090,5 +1090,109 @@ void M() state.AssertNoTag(); } } + + [Fact] + [WorkItem(2605, "https://github.com/dotnet/roslyn/issues/2605")] + [Trait(Traits.Feature, Traits.Features.RenameTracking)] + public void RenameTracking_CannotRenameToVarInCSharp() + { + var code = @" +class C +{ + void M() + { + C$$ c; + } +}"; + using (var state = new RenameTrackingTestState(code, LanguageNames.CSharp)) + { + state.EditorOperations.Backspace(); + state.EditorOperations.InsertText("va"); + + state.AssertTag("C", "va"); + Assert.NotEmpty(state.GetDocumentDiagnostics()); + + state.EditorOperations.InsertText("r"); + state.AssertNoTag(); + Assert.Empty(state.GetDocumentDiagnostics()); + + state.EditorOperations.InsertText("p"); + state.AssertTag("C", "varp"); + Assert.NotEmpty(state.GetDocumentDiagnostics()); + } + } + + [Fact] + [WorkItem(2605, "https://github.com/dotnet/roslyn/issues/2605")] + [Trait(Traits.Feature, Traits.Features.RenameTracking)] + public void RenameTracking_CannotRenameFromVarInCSharp() + { + var code = @" +class C +{ + void M() + { + var$$ c = new C(); + } +}"; + using (var state = new RenameTrackingTestState(code, LanguageNames.CSharp)) + { + state.EditorOperations.Backspace(); + state.AssertNoTag(); + Assert.Empty(state.GetDocumentDiagnostics()); + } + } + + [Fact] + [WorkItem(2605, "https://github.com/dotnet/roslyn/issues/2605")] + [Trait(Traits.Feature, Traits.Features.RenameTracking)] + public void RenameTracking_CanRenameToVarInVisualBasic() + { + var code = @" +Class C + Sub M() + Dim x as C$$ + End Sub +End Class"; + using (var state = new RenameTrackingTestState(code, LanguageNames.VisualBasic)) + { + state.EditorOperations.Backspace(); + state.EditorOperations.InsertText("var"); + + state.AssertTag("C", "var"); + Assert.NotEmpty(state.GetDocumentDiagnostics()); + } + } + + [Fact] + [WorkItem(2605, "https://github.com/dotnet/roslyn/issues/2605")] + [Trait(Traits.Feature, Traits.Features.RenameTracking)] + public void RenameTracking_CannotRenameToDynamicInCSharp() + { + var code = @" +class C +{ + void M() + { + C$$ c; + } +}"; + using (var state = new RenameTrackingTestState(code, LanguageNames.CSharp)) + { + state.EditorOperations.Backspace(); + state.EditorOperations.InsertText("dynami"); + + state.AssertTag("C", "dynami"); + Assert.NotEmpty(state.GetDocumentDiagnostics()); + + state.EditorOperations.InsertText("c"); + state.AssertNoTag(); + Assert.Empty(state.GetDocumentDiagnostics()); + + state.EditorOperations.InsertText("s"); + state.AssertTag("C", "dynamics"); + Assert.NotEmpty(state.GetDocumentDiagnostics()); + } + } } } diff --git a/src/EditorFeatures/VisualBasic/BasicEditorFeatures.vbproj b/src/EditorFeatures/VisualBasic/BasicEditorFeatures.vbproj index 486ca275d1efdbc6fb706058909b08b8bbf67f39..a5d1175ca8d9ac5401cbec18a6c55d10e37045ef 100644 --- a/src/EditorFeatures/VisualBasic/BasicEditorFeatures.vbproj +++ b/src/EditorFeatures/VisualBasic/BasicEditorFeatures.vbproj @@ -216,6 +216,7 @@ + diff --git a/src/EditorFeatures/VisualBasic/RenameTracking/BasicRenameTrackingLanguageHeuristicsService.vb b/src/EditorFeatures/VisualBasic/RenameTracking/BasicRenameTrackingLanguageHeuristicsService.vb new file mode 100644 index 0000000000000000000000000000000000000000..192f43dbd7bab32cd034034b4f5e6ef633e7f24e --- /dev/null +++ b/src/EditorFeatures/VisualBasic/RenameTracking/BasicRenameTrackingLanguageHeuristicsService.vb @@ -0,0 +1,16 @@ +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +Imports System.Composition +Imports Microsoft.CodeAnalysis.Editor.Implementation.RenameTracking +Imports Microsoft.CodeAnalysis.Host.Mef + +Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.RenameTracking + + Friend NotInheritable Class BasicRenameTrackingLanguageHeuristicsService + Implements IRenameTrackingLanguageHeuristicsService + + Public Function IsIdentifierValidForRenameTracking(name As String) As Boolean Implements IRenameTrackingLanguageHeuristicsService.IsIdentifierValidForRenameTracking + Return True + End Function + End Class +End Namespace