diff --git a/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs b/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs index 48407168797af..323192e035ea1 100644 --- a/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs +++ b/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs @@ -930,7 +930,7 @@ private void CompileMethod( var lazySemanticModel = body == null ? null : new Lazy(() => { var syntax = body.Syntax; - var semanticModel = (CSharpSemanticModel)_compilation.GetSemanticModel(syntax.SyntaxTree); + var semanticModel = (CSharpSemanticModel)AnalyzerDriver.GetOrCreateCachedSemanticModel(syntax.SyntaxTree, _compilation, _cancellationToken); var memberModel = semanticModel.GetMemberModel(syntax); if (memberModel != null) { diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerDriver.CompilationData.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerDriver.CompilationData.cs index d731489469334..f8264e43fc0e3 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerDriver.CompilationData.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerDriver.CompilationData.cs @@ -48,7 +48,6 @@ public SemanticModel GetOrCreateCachedSemanticModel(SyntaxTree tree, Compilation } } - model = compilation.GetSemanticModel(tree); // Invoke GetDiagnostics to populate the compilation's CompilationEvent queue. diff --git a/src/Compilers/VisualBasic/Portable/Compilation/MethodCompiler.vb b/src/Compilers/VisualBasic/Portable/Compilation/MethodCompiler.vb index afd0ef2952e80..8662de9af1ed3 100644 --- a/src/Compilers/VisualBasic/Portable/Compilation/MethodCompiler.vb +++ b/src/Compilers/VisualBasic/Portable/Compilation/MethodCompiler.vb @@ -1186,7 +1186,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic Dim lazySemanticModel = New Lazy(Of SemanticModel)( Function() Dim syntax = block.Syntax - Dim semanticModel = CType(compilation.GetSemanticModel(syntax.SyntaxTree), SyntaxTreeSemanticModel) + Dim semanticModel = DirectCast(AnalyzerDriver.GetOrCreateCachedSemanticModel(syntax.SyntaxTree, compilation, _cancellationToken), SyntaxTreeSemanticModel) Dim memberModel = CType(semanticModel.GetMemberSemanticModel(syntax), MethodBodySemanticModel) If memberModel IsNot Nothing Then memberModel.CacheBoundNodes(block, syntax) diff --git a/src/EditorFeatures/CSharp/CSharpEditorFeatures.csproj b/src/EditorFeatures/CSharp/CSharpEditorFeatures.csproj index bbd7ae4020f2b..1e1e4b93128d7 100644 --- a/src/EditorFeatures/CSharp/CSharpEditorFeatures.csproj +++ b/src/EditorFeatures/CSharp/CSharpEditorFeatures.csproj @@ -212,8 +212,9 @@ - - + + + diff --git a/src/EditorFeatures/CSharp/UseAutoProperty/UseAutoPropertyAnalyzer.cs b/src/EditorFeatures/CSharp/UseAutoProperty/CSharpUseAutoPropertyAnalyzer.cs similarity index 72% rename from src/EditorFeatures/CSharp/UseAutoProperty/UseAutoPropertyAnalyzer.cs rename to src/EditorFeatures/CSharp/UseAutoProperty/CSharpUseAutoPropertyAnalyzer.cs index 870526221bc41..580bf6f35a5b0 100644 --- a/src/EditorFeatures/CSharp/UseAutoProperty/UseAutoPropertyAnalyzer.cs +++ b/src/EditorFeatures/CSharp/UseAutoProperty/CSharpUseAutoPropertyAnalyzer.cs @@ -13,9 +13,9 @@ namespace Microsoft.CodeAnalysis.Editor.CSharp.UseAutoProperty { // https://github.com/dotnet/roslyn/issues/5408 - //[Export] - //[DiagnosticAnalyzer(LanguageNames.CSharp)] - internal class UseAutoPropertyAnalyzer : AbstractUseAutoPropertyAnalyzer + [Export] + [DiagnosticAnalyzer(LanguageNames.CSharp)] + internal class CSharpUseAutoPropertyAnalyzer : AbstractUseAutoPropertyAnalyzer { protected override bool SupportsReadOnlyProperties(Compilation compilation) { @@ -27,44 +27,11 @@ protected override bool SupportsPropertyInitializer(Compilation compilation) return ((CSharpCompilation)compilation).LanguageVersion >= LanguageVersion.CSharp6; } - protected override void RegisterIneligibleFieldsAction(CompilationStartAnalysisContext context, ConcurrentBag ineligibleFields) - { - context.RegisterSyntaxNodeAction(snac => AnalyzeArgument(ineligibleFields, snac), SyntaxKind.Argument); - } - protected override ExpressionSyntax GetFieldInitializer(VariableDeclaratorSyntax variable, CancellationToken cancellationToken) { return variable.Initializer?.Value; } - private void AnalyzeArgument(ConcurrentBag ineligibleFields, SyntaxNodeAnalysisContext context) - { - // An argument will disqualify a field if that field is used in a ref/out position. - // We can't change such field references to be property references in C#. - var argument = (ArgumentSyntax)context.Node; - if (argument.RefOrOutKeyword.Kind() == SyntaxKind.None) - { - return; - } - - var cancellationToken = context.CancellationToken; - var symbolInfo = context.SemanticModel.GetSymbolInfo(argument.Expression, cancellationToken); - AddIneligibleField(symbolInfo.Symbol, ineligibleFields); - foreach (var symbol in symbolInfo.CandidateSymbols) - { - AddIneligibleField(symbol, ineligibleFields); - } - } - - private static void AddIneligibleField(ISymbol symbol, ConcurrentBag ineligibleFields) - { - var field = symbol as IFieldSymbol; - if (field != null) - { - ineligibleFields.Add(field); - } - } - private bool CheckExpressionSyntactically(ExpressionSyntax expression) { if (expression.IsKind(SyntaxKind.SimpleMemberAccessExpression)) diff --git a/src/EditorFeatures/CSharp/UseAutoProperty/UseAutoPropertyCodeFixProvider.cs b/src/EditorFeatures/CSharp/UseAutoProperty/CSharpUseAutoPropertyCodeFixProvider.cs similarity index 93% rename from src/EditorFeatures/CSharp/UseAutoProperty/UseAutoPropertyCodeFixProvider.cs rename to src/EditorFeatures/CSharp/UseAutoProperty/CSharpUseAutoPropertyCodeFixProvider.cs index 2ec14df4dc029..7296242e05265 100644 --- a/src/EditorFeatures/CSharp/UseAutoProperty/UseAutoPropertyCodeFixProvider.cs +++ b/src/EditorFeatures/CSharp/UseAutoProperty/CSharpUseAutoPropertyCodeFixProvider.cs @@ -13,8 +13,8 @@ namespace Microsoft.CodeAnalysis.Editor.CSharp.UseAutoProperty { [Shared] - [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(UseAutoPropertyCodeFixProvider))] - internal class UseAutoPropertyCodeFixProvider : AbstractUseAutoPropertyCodeFixProvider + [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(CSharpUseAutoPropertyCodeFixProvider))] + internal class CSharpUseAutoPropertyCodeFixProvider : AbstractUseAutoPropertyCodeFixProvider { protected override SyntaxNode GetNodeToRemove(VariableDeclaratorSyntax declarator) { diff --git a/src/EditorFeatures/CSharp/UseAutoProperty/CSharpUseAutoPropertyService.cs b/src/EditorFeatures/CSharp/UseAutoProperty/CSharpUseAutoPropertyService.cs new file mode 100644 index 0000000000000..aad3fc70b2fb5 --- /dev/null +++ b/src/EditorFeatures/CSharp/UseAutoProperty/CSharpUseAutoPropertyService.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Composition; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Extensions; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.UseAutoProperty; + +namespace Microsoft.CodeAnalysis.Editor.CSharp.UseAutoProperty +{ + [ExportLanguageService(typeof(IUseAutoPropertyService), LanguageNames.CSharp), Shared] + internal class CSharpUseAutoPropertyService : IUseAutoPropertyService + { + public SyntaxToken OnTokenRenamed(SyntaxToken oldToken, SyntaxToken newToken) + { + var parent = oldToken.Parent as ExpressionSyntax; + if (parent != null) + { + if (parent.IsRightSideOfDot()) + { + parent = parent.Parent as ExpressionSyntax; + } + + if (parent != null && + parent.Parent.IsKind(SyntaxKind.Argument) && + ((ArgumentSyntax)parent.Parent).RefOrOutKeyword.Kind() != SyntaxKind.None) + { + // We accessed the field in a ref/out location. Add a conflict annotation so the + // usre knows there's a problem here. + return newToken.WithAdditionalAnnotations(ConflictAnnotation.Create(CSharpFeaturesResources.ConflictsDetected)); + } + } + + return newToken; + } + } +} \ No newline at end of file diff --git a/src/EditorFeatures/CSharpTest/Diagnostics/UseAutoProperty/UseAutoPropertyTests.cs b/src/EditorFeatures/CSharpTest/Diagnostics/UseAutoProperty/UseAutoPropertyTests.cs index 7c80a2ac67f71..3adacb93f88c5 100644 --- a/src/EditorFeatures/CSharpTest/Diagnostics/UseAutoProperty/UseAutoPropertyTests.cs +++ b/src/EditorFeatures/CSharpTest/Diagnostics/UseAutoProperty/UseAutoPropertyTests.cs @@ -15,7 +15,7 @@ public class UseAutoPropertyTests : AbstractCSharpDiagnosticProviderBasedUserDia internal override Tuple CreateDiagnosticProviderAndFixer(Workspace workspace) { return Tuple.Create( - new UseAutoPropertyAnalyzer(), new UseAutoPropertyCodeFixProvider()); + new CSharpUseAutoPropertyAnalyzer(), new CSharpUseAutoPropertyCodeFixProvider()); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseAutoProperty)] @@ -142,22 +142,25 @@ public void TestFieldAndPropertyHaveDifferentStaticInstance() [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseAutoProperty)] public void TestFieldUseInRefArgument1() { - TestMissing( -@"class Class { [|int i|]; int P { get { return i; } } void M(ref int x) { M(ref i); } }"); + Test( +@"class Class { [|int i|]; int P { get { return i; } } void M(ref int x) { M(ref i); } }", +@"class Class { int P { get; set; } void M(ref int x) { M(ref {|Conflict:P|}); } }"); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseAutoProperty)] public void TestFieldUseInRefArgument2() { - TestMissing( -@"class Class { [|int i|]; int P { get { return i; } } void M(ref int x) { M(ref this.i); } }"); + Test( +@"class Class { [|int i|]; int P { get { return i; } } void M(ref int x) { M(ref this.i); } }", +@"class Class { int P { get; set; } void M(ref int x) { M(ref this.{|Conflict:P|}); } }"); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseAutoProperty)] public void TestFieldUseInOutArgument() { - TestMissing( -@"class Class { [|int i|]; int P { get { return i; } } void M(out x) { M(out i); } }"); + Test( +@"class Class { [|int i|]; int P { get { return i; } } void M(out x) { M(out i); } }", +@"class Class { int P { get; set; } void M(out x) { M(out {|Conflict:P|}); } }"); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseAutoProperty)] diff --git a/src/EditorFeatures/Core/Implementation/InlineRename/AbstractEditorInlineRenameService.InlineRenameLocationSet.cs b/src/EditorFeatures/Core/Implementation/InlineRename/AbstractEditorInlineRenameService.InlineRenameLocationSet.cs index 508c19a90d7c8..df891ec191efd 100644 --- a/src/EditorFeatures/Core/Implementation/InlineRename/AbstractEditorInlineRenameService.InlineRenameLocationSet.cs +++ b/src/EditorFeatures/Core/Implementation/InlineRename/AbstractEditorInlineRenameService.InlineRenameLocationSet.cs @@ -36,7 +36,7 @@ public async Task GetReplacementsAsync(string repl { var conflicts = await ConflictResolver.ResolveConflictsAsync( _renameLocationSet, _renameLocationSet.Symbol.Name, - _renameInfo.GetFinalSymbolName(replacementText), optionSet, hasConflict: null, cancellationToken: cancellationToken).ConfigureAwait(false); + _renameInfo.GetFinalSymbolName(replacementText), optionSet, callbacks: null, cancellationToken: cancellationToken).ConfigureAwait(false); return new InlineRenameReplacementInfo(conflicts); } diff --git a/src/EditorFeatures/Test2/Diagnostics/UseAutoProperty/UseAutoPropertyTests.vb b/src/EditorFeatures/Test2/Diagnostics/UseAutoProperty/UseAutoPropertyTests.vb index d282b035d87a5..f86670ca2117f 100644 --- a/src/EditorFeatures/Test2/Diagnostics/UseAutoProperty/UseAutoPropertyTests.vb +++ b/src/EditorFeatures/Test2/Diagnostics/UseAutoProperty/UseAutoPropertyTests.vb @@ -2,6 +2,8 @@ Imports Microsoft.CodeAnalysis.CodeFixes Imports Microsoft.CodeAnalysis.Diagnostics +Imports Microsoft.CodeAnalysis.Editor.CSharp.UseAutoProperty +Imports Microsoft.CodeAnalysis.Editor.VisualBasic.UseAutoProperty Namespace Microsoft.CodeAnalysis.Editor.UnitTests.Diagnostics.UseAutoProperty Public Class UseAutoPropertyTests @@ -9,9 +11,9 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.Diagnostics.UseAutoProperty Friend Overrides Function CreateDiagnosticProviderAndFixer(workspace As Workspace, language As String) As Tuple(Of DiagnosticAnalyzer, CodeFixProvider) If language = LanguageNames.CSharp Then - Return New Tuple(Of DiagnosticAnalyzer, CodeFixProvider)(New CSharp.UseAutoProperty.UseAutoPropertyAnalyzer(), New CSharp.UseAutoProperty.UseAutoPropertyCodeFixProvider()) + Return New Tuple(Of DiagnosticAnalyzer, CodeFixProvider)(New CSharpUseAutoPropertyAnalyzer(), New CSharpUseAutoPropertyCodeFixProvider()) ElseIf language = LanguageNames.VisualBasic - Return New Tuple(Of DiagnosticAnalyzer, CodeFixProvider)(New VisualBasic.UseAutoProperty.UseAutoPropertyAnalyzer(), New VisualBasic.UseAutoProperty.UseAutoPropertyCodeFixProvider()) + Return New Tuple(Of DiagnosticAnalyzer, CodeFixProvider)(New VisualBasicUseAutoPropertyAnalyzer(), New VisualBasicUseAutoPropertyCodeFixProvider()) Else Throw New Exception() End If diff --git a/src/EditorFeatures/Test2/Rename/RenameEngineResult.vb b/src/EditorFeatures/Test2/Rename/RenameEngineResult.vb index c5cd70073e0e9..c2e754ad5b324 100644 --- a/src/EditorFeatures/Test2/Rename/RenameEngineResult.vb +++ b/src/EditorFeatures/Test2/Rename/RenameEngineResult.vb @@ -85,7 +85,7 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.Rename Dim locations = RenameLocations.FindAsync(symbol, workspace.CurrentSolution, optionSet, CancellationToken.None).Result Dim originalName = symbol.Name.Split("."c).Last() - Dim result = ConflictResolver.ResolveConflictsAsync(locations, originalName, renameTo, optionSet, hasConflict:=Nothing, cancellationToken:=CancellationToken.None).Result + Dim result = ConflictResolver.ResolveConflictsAsync(locations, originalName, renameTo, optionSet, callbacks:=Nothing, cancellationToken:=CancellationToken.None).Result engineResult = New RenameEngineResult(workspace, result, renameTo) engineResult.AssertUnlabeledSpansRenamedAndHaveNoConflicts() diff --git a/src/EditorFeatures/VisualBasic/BasicEditorFeatures.vbproj b/src/EditorFeatures/VisualBasic/BasicEditorFeatures.vbproj index 594cb30d03fd6..99053015872d8 100644 --- a/src/EditorFeatures/VisualBasic/BasicEditorFeatures.vbproj +++ b/src/EditorFeatures/VisualBasic/BasicEditorFeatures.vbproj @@ -236,8 +236,8 @@ - - + + diff --git a/src/EditorFeatures/VisualBasic/UseAutoProperty/UseAutoPropertyAnalyzer.vb b/src/EditorFeatures/VisualBasic/UseAutoProperty/VisualBasicUseAutoPropertyAnalyzer.vb similarity index 94% rename from src/EditorFeatures/VisualBasic/UseAutoProperty/UseAutoPropertyAnalyzer.vb rename to src/EditorFeatures/VisualBasic/UseAutoProperty/VisualBasicUseAutoPropertyAnalyzer.vb index 67b7f83819987..13360dd171aa9 100644 --- a/src/EditorFeatures/VisualBasic/UseAutoProperty/UseAutoPropertyAnalyzer.vb +++ b/src/EditorFeatures/VisualBasic/UseAutoProperty/VisualBasicUseAutoPropertyAnalyzer.vb @@ -9,9 +9,9 @@ Imports Microsoft.CodeAnalysis.VisualBasic.Syntax Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UseAutoProperty ' https://github.com/dotnet/roslyn/issues/5408 - ' - ' - Friend Class UseAutoPropertyAnalyzer + + + Friend Class VisualBasicUseAutoPropertyAnalyzer Inherits AbstractUseAutoPropertyAnalyzer(Of PropertyBlockSyntax, FieldDeclarationSyntax, ModifiedIdentifierSyntax, ExpressionSyntax) Private ReadOnly semanticFacts As New VisualBasicSemanticFactsService() @@ -24,12 +24,6 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UseAutoProperty Return DirectCast(compilation, VisualBasicCompilation).LanguageVersion >= LanguageVersion.VisualBasic10 End Function - Protected Overrides Sub RegisterIneligibleFieldsAction(context As CompilationStartAnalysisContext, ineligibleFields As ConcurrentBag(Of IFieldSymbol)) - ' There are no syntactic constructs that make a field ineligible to be replaced with - ' a property. In C# you can't use a property in a ref/out position. But that restriction - ' doesn't apply to VB. - End Sub - Protected Overrides Function GetFieldInitializer(variable As ModifiedIdentifierSyntax, cancellationToken As CancellationToken) As ExpressionSyntax Dim declarator = TryCast(variable.Parent, VariableDeclaratorSyntax) Return declarator?.Initializer?.Value diff --git a/src/EditorFeatures/VisualBasic/UseAutoProperty/UseAutoPropertyCodeFixProvider.vb b/src/EditorFeatures/VisualBasic/UseAutoProperty/VisualBasicUseAutoPropertyCodeFixProvider.vb similarity index 96% rename from src/EditorFeatures/VisualBasic/UseAutoProperty/UseAutoPropertyCodeFixProvider.vb rename to src/EditorFeatures/VisualBasic/UseAutoProperty/VisualBasicUseAutoPropertyCodeFixProvider.vb index 99e0485e924a2..0ca94f7540ea6 100644 --- a/src/EditorFeatures/VisualBasic/UseAutoProperty/UseAutoPropertyCodeFixProvider.vb +++ b/src/EditorFeatures/VisualBasic/UseAutoProperty/VisualBasicUseAutoPropertyCodeFixProvider.vb @@ -10,8 +10,8 @@ Imports Microsoft.CodeAnalysis.VisualBasic.Syntax Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UseAutoProperty <[Shared]> - - Friend Class UseAutoPropertyCodeFixProvider + + Friend Class VisualBasicUseAutoPropertyCodeFixProvider Inherits AbstractUseAutoPropertyCodeFixProvider(Of PropertyBlockSyntax, FieldDeclarationSyntax, ModifiedIdentifierSyntax, ConstructorBlockSyntax, ExpressionSyntax) Protected Overrides Function GetNodeToRemove(identifier As ModifiedIdentifierSyntax) As SyntaxNode diff --git a/src/EditorFeatures/VisualBasicTest/Diagnostics/UseAutoProperty/UseAutoPropertyTests.vb b/src/EditorFeatures/VisualBasicTest/Diagnostics/UseAutoProperty/UseAutoPropertyTests.vb index e69175b1537a5..be7bb861faa47 100644 --- a/src/EditorFeatures/VisualBasicTest/Diagnostics/UseAutoProperty/UseAutoPropertyTests.vb +++ b/src/EditorFeatures/VisualBasicTest/Diagnostics/UseAutoProperty/UseAutoPropertyTests.vb @@ -9,7 +9,7 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.Diagnostics.UseAut Inherits AbstractVisualBasicDiagnosticProviderBasedUserDiagnosticTest Friend Overrides Function CreateDiagnosticProviderAndFixer(workspace As Workspace) As Tuple(Of DiagnosticAnalyzer, CodeFixProvider) - Return Tuple.Create(Of DiagnosticAnalyzer, CodeFixProvider)(New UseAutoPropertyAnalyzer(), New UseAutoPropertyCodeFixProvider()) + Return Tuple.Create(Of DiagnosticAnalyzer, CodeFixProvider)(New VisualBasicUseAutoPropertyAnalyzer(), New VisualBasicUseAutoPropertyCodeFixProvider()) End Function diff --git a/src/Features/Core/Portable/EncapsulateField/AbstractEncapsulateFieldService.cs b/src/Features/Core/Portable/EncapsulateField/AbstractEncapsulateFieldService.cs index 259808080498b..7b7661736e120 100644 --- a/src/Features/Core/Portable/EncapsulateField/AbstractEncapsulateFieldService.cs +++ b/src/Features/Core/Portable/EncapsulateField/AbstractEncapsulateFieldService.cs @@ -239,7 +239,8 @@ private async Task UpdateReferencesAsync( if (finalFieldName != field.Name && constructorSyntaxes.Count > 0) { solution = await Renamer.RenameSymbolAsync(solution, field, finalFieldName, solution.Workspace.Options, - location => constructorSyntaxes.Any(c => c.Span.IntersectsWith(location.SourceSpan)), cancellationToken: cancellationToken).ConfigureAwait(false); + new RenameCallbacks(location => constructorSyntaxes.Any(c => c.Span.IntersectsWith(location.SourceSpan))), + cancellationToken).ConfigureAwait(false); document = solution.GetDocument(document.Id); var compilation = await document.Project.GetCompilationAsync(cancellationToken).ConfigureAwait(false); @@ -249,7 +250,8 @@ private async Task UpdateReferencesAsync( // Outside the constructor we want to rename references to the field to final property name. return await Renamer.RenameSymbolAsync(solution, field, generatedPropertyName, solution.Workspace.Options, - location => !constructorSyntaxes.Any(c => c.Span.IntersectsWith(location.SourceSpan)), cancellationToken: cancellationToken).ConfigureAwait(false); + new RenameCallbacks(location => !constructorSyntaxes.Any(c => c.Span.IntersectsWith(location.SourceSpan))), + cancellationToken).ConfigureAwait(false); } else { diff --git a/src/Features/Core/Portable/Features.csproj b/src/Features/Core/Portable/Features.csproj index 4dd63caeaeeca..02824896fd707 100644 --- a/src/Features/Core/Portable/Features.csproj +++ b/src/Features/Core/Portable/Features.csproj @@ -531,6 +531,7 @@ + diff --git a/src/Features/Core/Portable/UseAutoProperty/AbstractUseAutoPropertyAnalyzer.cs b/src/Features/Core/Portable/UseAutoProperty/AbstractUseAutoPropertyAnalyzer.cs index f732aebfc630c..667090d1dae01 100644 --- a/src/Features/Core/Portable/UseAutoProperty/AbstractUseAutoPropertyAnalyzer.cs +++ b/src/Features/Core/Portable/UseAutoProperty/AbstractUseAutoPropertyAnalyzer.cs @@ -28,7 +28,6 @@ internal abstract class AbstractUseAutoPropertyAnalyzer SupportedDiagnostics => ImmutableArray.Create(Descriptor, FadedTokenDescriptor); - protected abstract void RegisterIneligibleFieldsAction(CompilationStartAnalysisContext context, ConcurrentBag ineligibleFields); protected abstract bool SupportsReadOnlyProperties(Compilation compilation); protected abstract bool SupportsPropertyInitializer(Compilation compilation); protected abstract TExpression GetFieldInitializer(TVariableDeclarator variable, CancellationToken cancellationToken); @@ -38,19 +37,10 @@ internal abstract class AbstractUseAutoPropertyAnalyzer - { - var analysisResults = new ConcurrentBag(); - var ineligibleFields = new ConcurrentBag(); - - csac.RegisterSymbolAction(sac => AnalyzeProperty(analysisResults, sac), SymbolKind.Property); - RegisterIneligibleFieldsAction(csac, ineligibleFields); - - csac.RegisterCompilationEndAction(cac => Process(analysisResults, ineligibleFields, cac)); - }); + context.RegisterSymbolAction(AnalyzeProperty, SymbolKind.Property); } - private void AnalyzeProperty(ConcurrentBag analysisResults, SymbolAnalysisContext symbolContext) + private void AnalyzeProperty(SymbolAnalysisContext symbolContext) { var property = (IPropertySymbol)symbolContext.Symbol; if (property.IsIndexer) @@ -114,31 +104,31 @@ private void AnalyzeProperty(ConcurrentBag analysisResults, Symb return; } - if (!containingType.Equals(getterField.ContainingType)) + // Don't want to remove constants. + if (getterField.IsConst) { - // Field and property have to be in the same type. return; } - // Property and field have to agree on type. - if (!property.Type.Equals(getterField.Type)) + // Field and property should match in static-ness + if (getterField.IsStatic != property.IsStatic) { return; } - // Don't want to remove constants. - if (getterField.IsConst) + if (!containingType.Equals(getterField.ContainingType)) { + // Field and property have to be in the same type. return; } - if (getterField.DeclaringSyntaxReferences.Length != 1) + // Property and field have to agree on type. + if (!property.Type.Equals(getterField.Type)) { return; } - // Field and property should match in static-ness - if (getterField.IsStatic != property.IsStatic) + if (getterField.DeclaringSyntaxReferences.Length != 1) { return; } @@ -182,23 +172,27 @@ private void AnalyzeProperty(ConcurrentBag analysisResults, Symb } // Looks like a viable property/field to convert into an auto property. - analysisResults.Add(new AnalysisResult(property, getterField, propertyDeclaration, fieldDeclaration, variableDeclarator, - property.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat))); + var result = new AnalysisResult(property, getterField, propertyDeclaration, fieldDeclaration, variableDeclarator, + property.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)); + ProcessResult(result, symbolContext); } private IFieldSymbol GetSetterField( SemanticModel semanticModel, ISymbol containingType, IMethodSymbol setMethod, CancellationToken cancellationToken) { - return CheckFieldAccessExpression(semanticModel, GetSetterExpression(setMethod, semanticModel, cancellationToken)); + cancellationToken.ThrowIfCancellationRequested(); + return CheckFieldAccessExpression(semanticModel, GetSetterExpression(setMethod, semanticModel, cancellationToken), cancellationToken); } private IFieldSymbol GetGetterField(SemanticModel semanticModel, IMethodSymbol getMethod, CancellationToken cancellationToken) { - return CheckFieldAccessExpression(semanticModel, GetGetterExpression(getMethod, cancellationToken)); + cancellationToken.ThrowIfCancellationRequested(); + return CheckFieldAccessExpression(semanticModel, GetGetterExpression(getMethod, cancellationToken), cancellationToken); } - private IFieldSymbol CheckFieldAccessExpression(SemanticModel semanticModel, TExpression expression) + private IFieldSymbol CheckFieldAccessExpression(SemanticModel semanticModel, TExpression expression, CancellationToken cancellationToken) { + cancellationToken.ThrowIfCancellationRequested(); if (expression == null) { return null; @@ -219,25 +213,7 @@ private IFieldSymbol CheckFieldAccessExpression(SemanticModel semanticModel, TEx return field; } - private void Process( - ConcurrentBag analysisResults, - ConcurrentBag ineligibleFields, - CompilationAnalysisContext compilationContext) - { - var ineligibleFieldsSet = new HashSet(ineligibleFields); - foreach (var result in analysisResults) - { - var field = result.Field; - if (ineligibleFieldsSet.Contains(field)) - { - continue; - } - - Process(result, compilationContext); - } - } - - private void Process(AnalysisResult result, CompilationAnalysisContext compilationContext) + private void ProcessResult(AnalysisResult result, SymbolAnalysisContext compilationContext) { // Check if there are additional reasons we think this field might be ineligible for // replacing with an auto prop. diff --git a/src/Features/Core/Portable/UseAutoProperty/AbstractUseAutoPropertyCodeFixProvider.cs b/src/Features/Core/Portable/UseAutoProperty/AbstractUseAutoPropertyCodeFixProvider.cs index 5554c7cafc419..f5061e56a364d 100644 --- a/src/Features/Core/Portable/UseAutoProperty/AbstractUseAutoPropertyCodeFixProvider.cs +++ b/src/Features/Core/Portable/UseAutoProperty/AbstractUseAutoPropertyCodeFixProvider.cs @@ -82,8 +82,10 @@ private async Task ProcessResult(CodeFixContext context, Diagnostic di // Now, rename all usages of the field to point at the property. Except don't actually // rename the field itself. We want to be able to find it again post rename. var updatedSolution = await Renamer.RenameAsync(fieldLocations, propertySymbol.Name, - location => !location.SourceSpan.IntersectsWith(declaratorLocation.SourceSpan), - symbols => HasConflict(symbols, propertySymbol, compilation, cancellationToken), + new RenameCallbacks( + location => !location.SourceSpan.IntersectsWith(declaratorLocation.SourceSpan), + symbols => HasConflict(symbols, propertySymbol, compilation, cancellationToken), + (doc, oldToken, newToken) => OnTokenRenamed(doc, oldToken, newToken)), cancellationToken).ConfigureAwait(false); solution = updatedSolution; @@ -134,6 +136,14 @@ private async Task ProcessResult(CodeFixContext context, Diagnostic di } } + private SyntaxToken OnTokenRenamed(Document document, SyntaxToken oldToken, SyntaxToken newToken) + { + var service = document.GetLanguageService(); + return service == null + ? newToken + : service.OnTokenRenamed(oldToken, newToken); + } + private static bool IsWrittenToOutsideOfConstructorOrProperty( IFieldSymbol field, RenameLocations renameLocations, TPropertyDeclaration propertyDeclaration, CancellationToken cancellationToken) { diff --git a/src/Features/Core/Portable/UseAutoProperty/IUseAutoPropertyService.cs b/src/Features/Core/Portable/UseAutoProperty/IUseAutoPropertyService.cs new file mode 100644 index 0000000000000..4df93c770b386 --- /dev/null +++ b/src/Features/Core/Portable/UseAutoProperty/IUseAutoPropertyService.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Host; + +namespace Microsoft.CodeAnalysis.UseAutoProperty +{ + interface IUseAutoPropertyService : ILanguageService + { + SyntaxToken OnTokenRenamed(SyntaxToken oldToken, SyntaxToken newToken); + } +} diff --git a/src/Workspaces/CSharp/Portable/Rename/CSharpRenameRewriterLanguageService.cs b/src/Workspaces/CSharp/Portable/Rename/CSharpRenameRewriterLanguageService.cs index 2450a5f61077b..8f34e8c1a473a 100644 --- a/src/Workspaces/CSharp/Portable/Rename/CSharpRenameRewriterLanguageService.cs +++ b/src/Workspaces/CSharp/Portable/Rename/CSharpRenameRewriterLanguageService.cs @@ -41,7 +41,7 @@ public SyntaxNode AnnotateAndRename(RenameRewriterParameters parameters) private class RenameRewriter : CSharpSyntaxRewriter { - private readonly DocumentId _documentId; + private readonly Document _document; private readonly RenameAnnotation _renameRenamableSymbolDeclaration; private readonly Solution _solution; private readonly string _replacementText; @@ -66,6 +66,7 @@ private class RenameRewriter : CSharpSyntaxRewriter private readonly ISemanticFactsService _semanticFactsService; private readonly HashSet _annotatedIdentifierTokens = new HashSet(); private readonly HashSet _invocationExpressionsNeedingConflictChecks = new HashSet(); + private readonly Func _onTokenRenamed; private readonly AnnotationTable _renameAnnotations; @@ -89,7 +90,7 @@ private void AddModifiedSpan(TextSpan oldSpan, TextSpan newSpan) if (!_isProcessingComplexifiedSpans) { - _renameSpansTracker.AddModifiedSpan(_documentId, oldSpan, newSpan); + _renameSpansTracker.AddModifiedSpan(_document.Id, oldSpan, newSpan); } else { @@ -100,7 +101,7 @@ private void AddModifiedSpan(TextSpan oldSpan, TextSpan newSpan) public RenameRewriter(RenameRewriterParameters parameters) : base(visitIntoStructuredTrivia: true) { - _documentId = parameters.Document.Id; + _document = parameters.Document; _renameRenamableSymbolDeclaration = parameters.RenamedSymbolDeclarationAnnotation; _solution = parameters.OriginalSolution; _replacementText = parameters.ReplacementText; @@ -117,6 +118,7 @@ public RenameRewriter(RenameRewriterParameters parameters) _isRenamingInComments = parameters.OptionSet.GetOption(RenameOptions.RenameInComments); _stringAndCommentTextSpans = parameters.StringAndCommentTextSpans; _renameAnnotations = parameters.RenameAnnotations; + _onTokenRenamed = parameters.Callbacks?.OnTokenRenamed; _aliasSymbol = _renamedSymbol as IAliasSymbol; _renamableDeclarationLocation = _renamedSymbol.Locations.FirstOrDefault(loc => loc.IsInSource && loc.SourceTree == _semanticModel.SyntaxTree); @@ -262,7 +264,7 @@ private SyntaxNode Complexify(SyntaxNode originalNode, SyntaxNode newNode) newNode = newNode.WithoutAnnotations(annotation); newNode = _renameAnnotations.WithAdditionalAnnotations(newNode, new RenameNodeSimplificationAnnotation() { OriginalTextSpan = oldSpan }); - _renameSpansTracker.AddComplexifiedSpan(_documentId, oldSpan, new TextSpan(oldSpan.Start, newSpan.Length), _modifiedSubSpans); + _renameSpansTracker.AddComplexifiedSpan(_document.Id, oldSpan, new TextSpan(oldSpan.Start, newSpan.Length), _modifiedSubSpans); _modifiedSubSpans = null; _isProcessingComplexifiedSpans = false; @@ -311,7 +313,8 @@ private bool IsExpandWithinMultiLineLambda(SyntaxNode node) return true; } - private async Task RenameAndAnnotateAsync(SyntaxToken token, SyntaxToken newToken, bool isRenameLocation, bool isOldText) + private async Task RenameAndAnnotateAsync( + SyntaxToken token, SyntaxToken newToken, bool isRenameLocation, bool isOldText) { try { @@ -622,6 +625,7 @@ private SyntaxToken RenameToken(SyntaxToken oldToken, SyntaxToken newToken, stri } } + newToken = _onTokenRenamed?.Invoke(_document, oldToken, newToken) ?? newToken; return newToken; } diff --git a/src/Workspaces/Core/Portable/Rename/ConflictEngine/ConflictResolver.Session.cs b/src/Workspaces/Core/Portable/Rename/ConflictEngine/ConflictResolver.Session.cs index e6991ed1a3fef..a517e920416ed 100644 --- a/src/Workspaces/Core/Portable/Rename/ConflictEngine/ConflictResolver.Session.cs +++ b/src/Workspaces/Core/Portable/Rename/ConflictEngine/ConflictResolver.Session.cs @@ -36,7 +36,7 @@ private class Session private readonly string _originalText; private readonly string _replacementText; private readonly OptionSet _optionSet; - private readonly Func, bool?> _hasConflictCallback; + private readonly RenameCallbacks _callbacks; private readonly CancellationToken _cancellationToken; private readonly RenameAnnotation _renamedSymbolDeclarationAnnotation; @@ -57,7 +57,7 @@ public Session( string originalText, string replacementText, OptionSet optionSet, - Func, bool?> newSymbolsAreValid, + RenameCallbacks callbacks, CancellationToken cancellationToken) { _renameLocationSet = renameLocationSet; @@ -65,7 +65,7 @@ public Session( _originalText = originalText; _replacementText = replacementText; _optionSet = optionSet; - _hasConflictCallback = newSymbolsAreValid; + _callbacks = callbacks; _cancellationToken = cancellationToken; _renamedSymbolDeclarationAnnotation = new RenameAnnotation(); @@ -266,7 +266,7 @@ private void DebugVerifyNoErrors(ConflictResolution conflictResolution, IEnumera // fixed them because of rename). Also, don't bother checking if a custom // callback was provided. The caller might be ok with a rename that introduces // errors. - if (!documentIdErrorStateLookup[documentId] && _hasConflictCallback == null) + if (!documentIdErrorStateLookup[documentId] && _callbacks?.HasConflict == null) { conflictResolution.NewSolution.GetDocument(documentId).VerifyNoErrorsAsync("Rename introduced errors in error-free code", _cancellationToken, ignoreErrorCodes).Wait(_cancellationToken); } @@ -355,7 +355,7 @@ private async Task IdentifyConflictsAsync( // the spans would have been modified and so we need to adjust the old position // to the new position for which we use the renameSpanTracker, which was tracking // & mapping the old span -> new span during rename - hasConflict = _hasConflictCallback?.Invoke(newReferencedSymbols) ?? + hasConflict = _callbacks?.HasConflict?.Invoke(newReferencedSymbols) ?? await CheckForConflictAsync(conflictResolution, renamedSymbolInNewSolution, newDocument, conflictAnnotation, newReferencedSymbols).ConfigureAwait(false); } @@ -766,6 +766,7 @@ private async Task AnnotateAndRename_WorkerAsync( renameSpansTracker, _optionSet, _renameAnnotations, + _callbacks, _cancellationToken); var renameRewriterLanguageService = document.Project.LanguageServices.GetService(); diff --git a/src/Workspaces/Core/Portable/Rename/ConflictEngine/ConflictResolver.cs b/src/Workspaces/Core/Portable/Rename/ConflictEngine/ConflictResolver.cs index 344534ca49718..88d2fc4a18ae2 100644 --- a/src/Workspaces/Core/Portable/Rename/ConflictEngine/ConflictResolver.cs +++ b/src/Workspaces/Core/Portable/Rename/ConflictEngine/ConflictResolver.cs @@ -38,10 +38,7 @@ internal static partial class ConflictResolver /// The original name of the identifier. /// The new name of the identifier /// The option for rename - /// Called after renaming references. Can be used by callers to - /// indicate if the new symbols that the reference binds to should be considered to be ok or - /// are in conflict. 'true' means they are conflicts. 'false' means they are not conflicts. - /// 'null' means that the default conflict check should be used. + /// Callbacks called during the renaming process. /// The cancellation token. /// A conflict resolution containing the new solution. public static Task ResolveConflictsAsync( @@ -49,7 +46,7 @@ public static Task ResolveConflictsAsync( string originalText, string replacementText, OptionSet optionSet, - Func, bool?> hasConflict, + RenameCallbacks callbacks, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); @@ -62,7 +59,7 @@ public static Task ResolveConflictsAsync( throw new ArgumentException(string.Format(WorkspacesResources.RenameSymbolIsNotFromSource, renameLocationSet.Symbol.Name)); } - var session = new Session(renameLocationSet, renameSymbolDeclarationLocation, originalText, replacementText, optionSet, hasConflict, cancellationToken); + var session = new Session(renameLocationSet, renameSymbolDeclarationLocation, originalText, replacementText, optionSet, callbacks, cancellationToken); return session.ResolveConflictsAsync(); } diff --git a/src/Workspaces/Core/Portable/Rename/RenameRewriterParameters.cs b/src/Workspaces/Core/Portable/Rename/RenameRewriterParameters.cs index f9a49ced445ea..a6c5cbc40c7cb 100644 --- a/src/Workspaces/Core/Portable/Rename/RenameRewriterParameters.cs +++ b/src/Workspaces/Core/Portable/Rename/RenameRewriterParameters.cs @@ -30,6 +30,7 @@ internal class RenameRewriterParameters internal readonly Document Document; internal readonly SemanticModel SemanticModel; internal readonly AnnotationTable RenameAnnotations; + internal readonly RenameCallbacks Callbacks; public RenameRewriterParameters( RenameAnnotation renamedSymbolDeclarationAnnotation, @@ -48,6 +49,7 @@ public RenameRewriterParameters( RenamedSpansTracker renameSpansTracker, OptionSet optionSet, AnnotationTable renameAnnotations, + RenameCallbacks callbacks, CancellationToken cancellationToken) { this.RenamedSymbolDeclarationAnnotation = renamedSymbolDeclarationAnnotation; @@ -68,6 +70,7 @@ public RenameRewriterParameters( this.RenameSpansTracker = renameSpansTracker; this.OptionSet = optionSet; this.RenameAnnotations = renameAnnotations; + this.Callbacks = callbacks; } } } diff --git a/src/Workspaces/Core/Portable/Rename/Renamer.cs b/src/Workspaces/Core/Portable/Rename/Renamer.cs index a3cc596f43a75..f2325f271e811 100644 --- a/src/Workspaces/Core/Portable/Rename/Renamer.cs +++ b/src/Workspaces/Core/Portable/Rename/Renamer.cs @@ -15,7 +15,7 @@ public static class Renamer { public static Task RenameSymbolAsync(Solution solution, ISymbol symbol, string newName, OptionSet optionSet, CancellationToken cancellationToken = default(CancellationToken)) { - return RenameSymbolAsync(solution, symbol, newName, optionSet, filter: null, cancellationToken: cancellationToken); + return RenameSymbolAsync(solution, symbol, newName, optionSet, callbacks: null, cancellationToken: cancellationToken); } internal static Task GetRenameLocationsAsync(Solution solution, ISymbol symbol, OptionSet options, CancellationToken cancellationToken) @@ -39,8 +39,7 @@ internal static Task GetRenameLocationsAsync(Solution solution, internal static async Task RenameAsync( RenameLocations locations, string newName, - Func filter = null, - Func, bool?> hasConflict = null, + RenameCallbacks callbacks, CancellationToken cancellationToken = default(CancellationToken)) { if (string.IsNullOrEmpty(newName)) @@ -51,17 +50,17 @@ internal static async Task RenameAsync( cancellationToken.ThrowIfCancellationRequested(); var symbol = locations.Symbol; - if (filter != null) + if (callbacks?.Filter != null) { locations = new RenameLocations( - locations.Locations.Where(loc => filter(loc.Location)).ToSet(), + locations.Locations.Where(loc => callbacks.Filter(loc.Location)).ToSet(), symbol, locations.Solution, locations.ReferencedSymbols, locations.ImplicitLocations, locations.Options); } var conflictResolution = await ConflictResolver.ResolveConflictsAsync( - locations, symbol.Name, newName, locations.Options, hasConflict, cancellationToken).ConfigureAwait(false); + locations, symbol.Name, newName, locations.Options, callbacks, cancellationToken).ConfigureAwait(false); return conflictResolution.NewSolution; } @@ -71,8 +70,7 @@ internal static async Task RenameSymbolAsync( ISymbol symbol, string newName, OptionSet options, - Func filter, - Func, bool?> hasConflict = null, + RenameCallbacks callbacks, CancellationToken cancellationToken = default(CancellationToken)) { if (solution == null) @@ -89,7 +87,31 @@ internal static async Task RenameSymbolAsync( options = options ?? solution.Workspace.Options; var renameLocations = await GetRenameLocationsAsync(solution, symbol, options, cancellationToken).ConfigureAwait(false); - return await RenameAsync(renameLocations, newName, filter, hasConflict, cancellationToken).ConfigureAwait(false); + return await RenameAsync(renameLocations, newName, callbacks, cancellationToken).ConfigureAwait(false); + } + } + + internal class RenameCallbacks + { + public readonly Func Filter; + public readonly Func, bool?> HasConflict; + public readonly Func OnTokenRenamed; + + /// Called on rename locations to determine if they should be renamed or not. + /// Called after renaming references. Can be used by callers to + /// indicate if the new symbols that the reference binds to should be considered to be ok or + /// are in conflict. 'true' means they are conflicts. 'false' means they are not conflicts. + /// 'null' means that the default conflict check should be used. + /// Called after a token is actually renamed. Can be used by callers to + /// Further manipulate the result (for example, by adding additional annotations to the new token). + public RenameCallbacks( + Func filter = null, + Func, bool?> hasConflict = null, + Func onTokenRenamed = null) + { + Filter = filter; + HasConflict = hasConflict; + OnTokenRenamed = onTokenRenamed; } } } \ No newline at end of file