diff --git a/src/EditorFeatures/CSharp/UseAutoProperty/UseAutoPropertyCodeFixProvider.cs b/src/EditorFeatures/CSharp/UseAutoProperty/UseAutoPropertyCodeFixProvider.cs index c8bd22c9a6350e877d111ccf0696465f56416f50..d1d0e4c9a700c766bfed93daad8a4756b3a05eb4 100644 --- a/src/EditorFeatures/CSharp/UseAutoProperty/UseAutoPropertyCodeFixProvider.cs +++ b/src/EditorFeatures/CSharp/UseAutoProperty/UseAutoPropertyCodeFixProvider.cs @@ -60,13 +60,18 @@ private async Task ProcessResult(CodeFixContext context, Diagnostic di var propertySymbol = (IPropertySymbol)propertySemanticModel.GetDeclaredSymbol(property); Debug.Assert(fieldDocument.Project == propertyDocument.Project); - var compilation = await fieldDocument.Project.GetCompilationAsync(cancellationToken).ConfigureAwait(false); + var project = fieldDocument.Project; + var compilation = await project.GetCompilationAsync(cancellationToken).ConfigureAwait(false); - // First, 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 solution = context.Document.Project.Solution; - var updatedSolution = await Renamer.RenameSymbolAsync(solution, - fieldSymbol, propertySymbol.Name, solution.Workspace.Options, + var fieldLocations = await Renamer.GetRenameLocationsAsync(solution, fieldSymbol, solution.Workspace.Options, cancellationToken).ConfigureAwait(false); + + // First, create the updated property we want to replace the old property with + var updatedProperty = UpdateProperty(project, fieldSymbol, propertySymbol, property, fieldLocations, cancellationToken); + + // 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), cancellationToken).ConfigureAwait(false); @@ -90,8 +95,6 @@ private async Task ProcessResult(CodeFixContext context, Diagnostic di var fieldDeclaration = (FieldDeclarationSyntax)declarator.Parent.Parent; var nodeToRemove = fieldDeclaration.Declaration.Variables.Count > 1 ? declarator : (SyntaxNode)fieldDeclaration; - var updatedProperty = UpdateProperty(property); - const SyntaxRemoveOptions options = SyntaxRemoveOptions.KeepUnbalancedDirectives | SyntaxRemoveOptions.AddElasticMarker; if (fieldDocument == propertyDocument) { @@ -149,9 +152,86 @@ private async Task ProcessResult(CodeFixContext context, Diagnostic di return null; } - private PropertyDeclarationSyntax UpdateProperty(PropertyDeclarationSyntax propertyDeclaration) + private PropertyDeclarationSyntax UpdateProperty( + Project project, IFieldSymbol fieldSymbol, IPropertySymbol propertySymbol, PropertyDeclarationSyntax propertyDeclaration, + RenameLocations fieldRenameLocations, CancellationToken cancellationToken) + { + var updatedProperty = propertyDeclaration.WithAccessorList(UpdateAccessorList(propertyDeclaration.AccessorList)); + + // We may need to add a setter if the field is written to outside of the constructor + // of it's class. + if (AddSetterIfNecessary(fieldSymbol, propertyDeclaration, fieldRenameLocations, cancellationToken)) + { + var accessor = SyntaxFactory.AccessorDeclaration(SyntaxKind.SetAccessorDeclaration) + .WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)); + var generator = SyntaxGenerator.GetGenerator(project); + + if (fieldSymbol.DeclaredAccessibility != propertySymbol.DeclaredAccessibility) + { + accessor = (AccessorDeclarationSyntax)generator.WithAccessibility(accessor, fieldSymbol.DeclaredAccessibility); + } + + updatedProperty = updatedProperty.AddAccessorListAccessors(accessor); + } + + return updatedProperty; + } + + private bool AddSetterIfNecessary( + IFieldSymbol fieldSymbol, + PropertyDeclarationSyntax propertyDeclaration, + RenameLocations fieldRenameLocations, + CancellationToken cancellationToken) + { + if (propertyDeclaration.AccessorList.Accessors.Any(SyntaxKind.SetAccessorDeclaration)) + { + // No need to add an setter if we already have one. + return false; + } + + // If the original field was written to outside of a constructor (or the property + // we're converting), then we'll need to add a setter to the property we're creating. + var containingTypeNodes = fieldSymbol.ContainingType.DeclaringSyntaxReferences.Select(s => s.GetSyntax(cancellationToken)).ToImmutableArray(); + + return fieldRenameLocations.Locations.Any(loc => NeedsSetter(loc, containingTypeNodes, propertyDeclaration, cancellationToken)); + } + + private bool NeedsSetter( + RenameLocation location, + ImmutableArray containingTypeNodes, + PropertyDeclarationSyntax propertyDeclaration, + CancellationToken cancellationToken) { - return propertyDeclaration.WithAccessorList(UpdateAccessorList(propertyDeclaration.AccessorList)); + if (!location.IsWrittenTo) + { + // We don't need a setter if we're not writing to this field. + return false; + } + + var node = location.Location.FindToken(cancellationToken).Parent; + while (node != null) + { + if (node == propertyDeclaration) + { + // We don't need a setter if we're a reference in the property we're replacing. + return false; + } + + if (node.IsKind(SyntaxKind.ConstructorDeclaration)) + { + // If we're written to in a constructor in the field's class, we don't need + // a setter. + if (containingTypeNodes.Contains(node.Parent)) + { + return false; + } + } + + node = node.Parent; + } + + // We do need a setter + return true; } private AccessorListSyntax UpdateAccessorList(AccessorListSyntax accessorList) diff --git a/src/EditorFeatures/CSharpTest/Diagnostics/UseAutoProperty/UseAutoPropertyTests.cs b/src/EditorFeatures/CSharpTest/Diagnostics/UseAutoProperty/UseAutoPropertyTests.cs index a3d04b7e0a377fcea34f30eb3eea5f4023ef11bc..3f986fdcef8716eee5717fa125745f8750f45bc2 100644 --- a/src/EditorFeatures/CSharpTest/Diagnostics/UseAutoProperty/UseAutoPropertyTests.cs +++ b/src/EditorFeatures/CSharpTest/Diagnostics/UseAutoProperty/UseAutoPropertyTests.cs @@ -191,5 +191,29 @@ public void TestUpdateReferencesConflictResolution() @"class Class { [|int i|]; int P { get { return i; } } public Class(int P) { i = 1; } }", @"class Class { int P { get; } public Class(int P) { this.P = 1; } }"); } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseAutoProperty)] + public void TestWriteInConstructor() + { + Test( +@"class Class { [|int i|]; int P { get { return i; } } public Class() { i = 1; } }", +@"class Class { int P { get; } public Class() { P = 1; } }"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseAutoProperty)] + public void TestWriteInNotInConstructor1() + { + Test( +@"class Class { [|int i|]; int P { get { return i; } } public Foo() { i = 1; } }", +@"class Class { int P { get; set; } public Foo() { P = 1; } }"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseAutoProperty)] + public void TestWriteInNotInConstructor2() + { + Test( +@"class Class { [|int i|]; public int P { get { return i; } } public Foo() { i = 1; } }", +@"class Class { public int P { get; private set; } public Foo() { P = 1; } }"); + } } } diff --git a/src/EditorFeatures/Core/Implementation/InlineRename/AbstractEditorInlineRenameService.InlineRenameLocationSet.cs b/src/EditorFeatures/Core/Implementation/InlineRename/AbstractEditorInlineRenameService.InlineRenameLocationSet.cs index 7b74b36d1db062a35befbf580380a402a425d69c..508c19a90d7c8a3d17b42ef997f8b4640905c571 100644 --- a/src/EditorFeatures/Core/Implementation/InlineRename/AbstractEditorInlineRenameService.InlineRenameLocationSet.cs +++ b/src/EditorFeatures/Core/Implementation/InlineRename/AbstractEditorInlineRenameService.InlineRenameLocationSet.cs @@ -14,12 +14,12 @@ internal abstract partial class AbstractEditorInlineRenameService { private class InlineRenameLocationSet : IInlineRenameLocationSet { - private readonly RenameLocationSet _renameLocationSet; + private readonly RenameLocations _renameLocationSet; private readonly SymbolInlineRenameInfo _renameInfo; public IList Locations { get; } - public InlineRenameLocationSet(SymbolInlineRenameInfo renameInfo, RenameLocationSet renameLocationSet) + public InlineRenameLocationSet(SymbolInlineRenameInfo renameInfo, RenameLocations renameLocationSet) { _renameInfo = renameInfo; _renameLocationSet = renameLocationSet; diff --git a/src/EditorFeatures/Core/Implementation/InlineRename/AbstractEditorInlineRenameService.SymbolRenameInfo.cs b/src/EditorFeatures/Core/Implementation/InlineRename/AbstractEditorInlineRenameService.SymbolRenameInfo.cs index bfc8d6331dc44d5ec99227e04d7e0954528b3359..9b2d364fdd5658f56ebfca127aa0b32f7d27773a 100644 --- a/src/EditorFeatures/Core/Implementation/InlineRename/AbstractEditorInlineRenameService.SymbolRenameInfo.cs +++ b/src/EditorFeatures/Core/Implementation/InlineRename/AbstractEditorInlineRenameService.SymbolRenameInfo.cs @@ -31,7 +31,7 @@ private partial class SymbolInlineRenameInfo : IInlineRenameInfo private readonly Document _document; private readonly IEnumerable _refactorNotifyServices; - private Task _underlyingFindRenameLocationsTask; + private Task _underlyingFindRenameLocationsTask; /// /// Whether or not we shortened the trigger span (say because we were renaming an attribute, @@ -61,7 +61,7 @@ private partial class SymbolInlineRenameInfo : IInlineRenameInfo _document = document; this.RenameSymbol = renameSymbol; - this.HasOverloads = RenameLocationSet.GetOverloadedSymbols(this.RenameSymbol).Any(); + this.HasOverloads = RenameLocations.GetOverloadedSymbols(this.RenameSymbol).Any(); this.ForceRenameOverloads = forceRenameOverloads; _isRenamingAttributePrefix = CanRenameAttributePrefix(document, triggerSpan, cancellationToken); @@ -208,14 +208,14 @@ public string GetFinalSymbolName(string replacementText) public Task FindRenameLocationsAsync(OptionSet optionSet, CancellationToken cancellationToken) { - Task renameTask; + Task renameTask; lock (_gate) { if (_underlyingFindRenameLocationsTask == null) { // If this is the first call, then just start finding the initial set of rename // locations. - _underlyingFindRenameLocationsTask = RenameLocationSet.FindAsync( + _underlyingFindRenameLocationsTask = RenameLocations.FindAsync( this.RenameSymbol, _document.Project.Solution, optionSet, cancellationToken); renameTask = _underlyingFindRenameLocationsTask; @@ -234,7 +234,7 @@ public Task FindRenameLocationsAsync(OptionSet optionS return GetLocationSet(renameTask, optionSet, cancellationToken); } - private async Task GetLocationSet(Task renameTask, OptionSet optionSet, CancellationToken cancellationToken) + private async Task GetLocationSet(Task renameTask, OptionSet optionSet, CancellationToken cancellationToken) { var locationSet = await renameTask.ConfigureAwait(false); if (optionSet != null) diff --git a/src/EditorFeatures/Core/Implementation/InlineRename/AbstractEditorInlineRenameService.cs b/src/EditorFeatures/Core/Implementation/InlineRename/AbstractEditorInlineRenameService.cs index 4362b117bcacd84aaa41f2a7f7d8be88f13553e8..8c4792a2592d03f354d424ee8982422e29648ebb 100644 --- a/src/EditorFeatures/Core/Implementation/InlineRename/AbstractEditorInlineRenameService.cs +++ b/src/EditorFeatures/Core/Implementation/InlineRename/AbstractEditorInlineRenameService.cs @@ -97,7 +97,7 @@ private IInlineRenameInfo GetRenameInfo(Document document, int position, Cancell } } - var symbol = RenameLocationSet.ReferenceProcessing.GetRenamableSymbolAsync(document, triggerToken.SpanStart, cancellationToken: cancellationToken).WaitAndGetResult(cancellationToken); + var symbol = RenameLocations.ReferenceProcessing.GetRenamableSymbolAsync(document, triggerToken.SpanStart, cancellationToken: cancellationToken).WaitAndGetResult(cancellationToken); if (symbol == null) { return new FailureInlineRenameInfo(EditorFeaturesResources.YouCannotRenameThisElement); diff --git a/src/EditorFeatures/Test2/Rename/RenameEngineResult.vb b/src/EditorFeatures/Test2/Rename/RenameEngineResult.vb index 45ff37c2fd74f7af714467a9b2b79cc28e986657..0069d092eeddf7b25cb08ab835351c0d7f6a31ae 100644 --- a/src/EditorFeatures/Test2/Rename/RenameEngineResult.vb +++ b/src/EditorFeatures/Test2/Rename/RenameEngineResult.vb @@ -68,7 +68,7 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.Rename Dim document = workspace.CurrentSolution.GetDocument(cursorDocument.Id) - Dim symbol = RenameLocationSet.ReferenceProcessing.GetRenamableSymbolAsync(document, cursorPosition, CancellationToken.None).Result + Dim symbol = RenameLocations.ReferenceProcessing.GetRenamableSymbolAsync(document, cursorPosition, CancellationToken.None).Result If symbol Is Nothing Then AssertEx.Fail("The symbol touching the $$ could not be found.") @@ -82,7 +82,7 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.Rename Next End If - Dim locations = RenameLocationSet.FindAsync(symbol, workspace.CurrentSolution, optionSet, CancellationToken.None).Result + 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, CancellationToken.None).Result diff --git a/src/Workspaces/CSharp/Portable/Rename/CSharpRenameRewriterLanguageService.cs b/src/Workspaces/CSharp/Portable/Rename/CSharpRenameRewriterLanguageService.cs index 7d6c20e23561e0949e2ce947c62e94affd15d095..24a6ec296ede9b6f034f725b642e6f7f8d474f42 100644 --- a/src/Workspaces/CSharp/Portable/Rename/CSharpRenameRewriterLanguageService.cs +++ b/src/Workspaces/CSharp/Portable/Rename/CSharpRenameRewriterLanguageService.cs @@ -614,7 +614,7 @@ private SyntaxToken RenameToken(SyntaxToken oldToken, SyntaxToken newToken, stri private SyntaxToken RenameInStringLiteral(SyntaxToken oldToken, SyntaxToken newToken, Func createNewStringLiteral) { var originalString = newToken.ToString(); - string replacedString = RenameLocationSet.ReferenceProcessing.ReplaceMatchingSubStrings(originalString, _originalText, _replacementText); + string replacedString = RenameLocations.ReferenceProcessing.ReplaceMatchingSubStrings(originalString, _originalText, _replacementText); if (replacedString != originalString) { var oldSpan = oldToken.Span; @@ -642,7 +642,7 @@ private SyntaxToken RenameInTrivia(SyntaxToken token, IEnumerable private SyntaxTrivia RenameInCommentTrivia(SyntaxTrivia trivia) { var originalString = trivia.ToString(); - string replacedString = RenameLocationSet.ReferenceProcessing.ReplaceMatchingSubStrings(originalString, _originalText, _replacementText); + string replacedString = RenameLocations.ReferenceProcessing.ReplaceMatchingSubStrings(originalString, _originalText, _replacementText); if (replacedString != originalString) { var oldSpan = trivia.Span; @@ -819,7 +819,7 @@ private SyntaxToken RenameWithinToken(SyntaxToken oldToken, SyntaxToken newToken var properties = new List(); foreach (var referencedSymbol in referencedSymbols) { - var property = await RenameLocationSet.ReferenceProcessing.GetPropertyFromAccessorOrAnOverride( + var property = await RenameLocations.ReferenceProcessing.GetPropertyFromAccessorOrAnOverride( referencedSymbol, baseSolution, cancellationToken).ConfigureAwait(false); if (property != null) { diff --git a/src/Workspaces/Core/Portable/Editing/SyntaxGenerator.cs b/src/Workspaces/Core/Portable/Editing/SyntaxGenerator.cs index b9a33f37498533abb279bc05ac43d7cb2a910c24..bf02622dbed1d74d47b17e488268ee7aa17dd3e7 100644 --- a/src/Workspaces/Core/Portable/Editing/SyntaxGenerator.cs +++ b/src/Workspaces/Core/Portable/Editing/SyntaxGenerator.cs @@ -36,7 +36,15 @@ public static SyntaxGenerator GetGenerator(Workspace workspace, string language) /// public static SyntaxGenerator GetGenerator(Document document) { - return document.Project.LanguageServices.GetService(); + return GetGenerator(document.Project); + } + + /// + /// Gets the for the language corresponding to the project. + /// + public static SyntaxGenerator GetGenerator(Project project) + { + return project.LanguageServices.GetService(); } #region Declarations diff --git a/src/Workspaces/Core/Portable/PublicAPI.Unshipped.txt b/src/Workspaces/Core/Portable/PublicAPI.Unshipped.txt index e0bf51467203e5649cdb02b2756bb3a82c496cf2..27131ef92201afc1f4c7a0917a247ded91f07fb6 100644 --- a/src/Workspaces/Core/Portable/PublicAPI.Unshipped.txt +++ b/src/Workspaces/Core/Portable/PublicAPI.Unshipped.txt @@ -1,4 +1,5 @@ Microsoft.CodeAnalysis.Editing.SyntaxEditor.RemoveNode(Microsoft.CodeAnalysis.SyntaxNode node, Microsoft.CodeAnalysis.SyntaxRemoveOptions options) -> void Microsoft.CodeAnalysis.Project.IsSubmission.get -> bool Microsoft.CodeAnalysis.Workspace.UpdateReferencesAfterAdd() -> void +static Microsoft.CodeAnalysis.Editing.SyntaxGenerator.GetGenerator(Microsoft.CodeAnalysis.Project project) -> Microsoft.CodeAnalysis.Editing.SyntaxGenerator virtual Microsoft.CodeAnalysis.Editing.SyntaxGenerator.RemoveNode(Microsoft.CodeAnalysis.SyntaxNode root, Microsoft.CodeAnalysis.SyntaxNode node, Microsoft.CodeAnalysis.SyntaxRemoveOptions options) -> Microsoft.CodeAnalysis.SyntaxNode \ No newline at end of file diff --git a/src/Workspaces/Core/Portable/Rename/ConflictEngine/ConflictResolver.Session.cs b/src/Workspaces/Core/Portable/Rename/ConflictEngine/ConflictResolver.Session.cs index 13ae0291d5ce50271345fdd7fa6bc7a70ccff3aa..e6991ed1a3fef3128cf41c01fe58bc4495f60c37 100644 --- a/src/Workspaces/Core/Portable/Rename/ConflictEngine/ConflictResolver.Session.cs +++ b/src/Workspaces/Core/Portable/Rename/ConflictEngine/ConflictResolver.Session.cs @@ -28,7 +28,7 @@ internal static partial class ConflictResolver private class Session { // Set of All Locations that will be renamed (does not include non-reference locations that need to be checked for conflicts) - private readonly RenameLocationSet _renameLocationSet; + private readonly RenameLocations _renameLocationSet; // Rename Symbol's Source Location private readonly Location _renameSymbolDeclarationLocation; @@ -52,7 +52,7 @@ private class Session private bool _documentOfRenameSymbolHasBeenRenamed; public Session( - RenameLocationSet renameLocationSet, + RenameLocations renameLocationSet, Location renameSymbolDeclarationLocation, string originalText, string replacementText, diff --git a/src/Workspaces/Core/Portable/Rename/ConflictEngine/ConflictResolver.cs b/src/Workspaces/Core/Portable/Rename/ConflictEngine/ConflictResolver.cs index 07c8cbda5f9bbcc5529104759a43502838e535de..344534ca49718846fdcf04fbe14e77f7f9d33bed 100644 --- a/src/Workspaces/Core/Portable/Rename/ConflictEngine/ConflictResolver.cs +++ b/src/Workspaces/Core/Portable/Rename/ConflictEngine/ConflictResolver.cs @@ -45,7 +45,7 @@ internal static partial class ConflictResolver /// The cancellation token. /// A conflict resolution containing the new solution. public static Task ResolveConflictsAsync( - RenameLocationSet renameLocationSet, + RenameLocations renameLocationSet, string originalText, string replacementText, OptionSet optionSet, diff --git a/src/Workspaces/Core/Portable/Rename/RenameLocation.ReferenceProcessing.cs b/src/Workspaces/Core/Portable/Rename/RenameLocation.ReferenceProcessing.cs index c4f9e6e93b0cf0eb4fce8968ef571d5b531f54ff..bc66ed860218db053c4f9592b231bbbff9bdb2b1 100644 --- a/src/Workspaces/Core/Portable/Rename/RenameLocation.ReferenceProcessing.cs +++ b/src/Workspaces/Core/Portable/Rename/RenameLocation.ReferenceProcessing.cs @@ -20,7 +20,7 @@ namespace Microsoft.CodeAnalysis.Rename /// A helper class that contains some of the methods and filters that must be used when /// processing the raw results from the FindReferences API. /// - internal sealed partial class RenameLocationSet + internal sealed partial class RenameLocations { internal static class ReferenceProcessing { @@ -354,7 +354,8 @@ internal static async Task> GetRenamableReferenceLoc { if (location.Alias.Name == referencedSymbol.Name) { - results.Add(new RenameLocation(location.Location, location.Document.Id, isCandidateLocation: location.IsCandidateLocation, isRenamableAliasUsage: true)); + results.Add(new RenameLocation(location.Location, location.Document.Id, + isCandidateLocation: location.IsCandidateLocation, isRenamableAliasUsage: true, isWrittenTo: location.IsWrittenTo)); // We also need to add the location of the alias itself var aliasLocation = location.Alias.Locations.Single(); @@ -367,6 +368,7 @@ internal static async Task> GetRenamableReferenceLoc results.Add(new RenameLocation( location.Location, location.Document.Id, + isWrittenTo: location.IsWrittenTo, isCandidateLocation: location.IsCandidateLocation, isMethodGroupReference: location.IsCandidateLocation && location.CandidateReason == CandidateReason.MemberGroup, isRenamableAccessor: await IsPropertyAccessorOrAnOverride(referencedSymbol, solution, cancellationToken).ConfigureAwait(false))); diff --git a/src/Workspaces/Core/Portable/Rename/RenameLocation.cs b/src/Workspaces/Core/Portable/Rename/RenameLocation.cs index 7d2ece56c7d57fea7a01acd7f57b05b2d6960342..aeb1599e2450b0c7a91ff524e6059ff235812b43 100644 --- a/src/Workspaces/Core/Portable/Rename/RenameLocation.cs +++ b/src/Workspaces/Core/Portable/Rename/RenameLocation.cs @@ -14,6 +14,7 @@ internal struct RenameLocation : IEquatable public readonly bool IsRenamableAliasUsage; public readonly bool IsRenamableAccessor; public readonly TextSpan ContainingLocationForStringOrComment; + public readonly bool IsWrittenTo; public bool IsRenameInStringOrComment { get { return ContainingLocationForStringOrComment != default(TextSpan); } } @@ -26,26 +27,25 @@ internal struct RenameLocation : IEquatable bool isMethodGroupReference = false, bool isRenamableAliasUsage = false, bool isRenamableAccessor = false, + bool isWrittenTo = false, TextSpan containingLocationForStringOrComment = default(TextSpan)) { - this.Location = location; - this.DocumentId = documentId; - this.IsCandidateLocation = isCandidateLocation; - this.IsMethodGroupReference = isMethodGroupReference; - this.IsRenamableAliasUsage = isRenamableAliasUsage; - this.IsRenamableAccessor = isRenamableAccessor; - this.ContainingLocationForStringOrComment = containingLocationForStringOrComment; + Location = location; + DocumentId = documentId; + IsCandidateLocation = isCandidateLocation; + IsMethodGroupReference = isMethodGroupReference; + IsRenamableAliasUsage = isRenamableAliasUsage; + IsRenamableAccessor = isRenamableAccessor; + IsWrittenTo = isWrittenTo; + ContainingLocationForStringOrComment = containingLocationForStringOrComment; } public RenameLocation(ReferenceLocation referenceLocation, DocumentId documentId) + : this(referenceLocation.Location, documentId, + isCandidateLocation: referenceLocation.IsCandidateLocation && referenceLocation.CandidateReason != CandidateReason.LateBound, + isMethodGroupReference: referenceLocation.IsCandidateLocation && referenceLocation.CandidateReason == CandidateReason.MemberGroup, + isWrittenTo: referenceLocation.IsWrittenTo) { - this.Location = referenceLocation.Location; - this.DocumentId = documentId; - this.IsCandidateLocation = referenceLocation.IsCandidateLocation && referenceLocation.CandidateReason != CandidateReason.LateBound; - this.IsMethodGroupReference = referenceLocation.IsCandidateLocation && referenceLocation.CandidateReason == CandidateReason.MemberGroup; - this.IsRenamableAliasUsage = false; - this.IsRenamableAccessor = false; - this.ContainingLocationForStringOrComment = default(TextSpan); } public bool Equals(RenameLocation other) diff --git a/src/Workspaces/Core/Portable/Rename/RenameLocationSet.cs b/src/Workspaces/Core/Portable/Rename/RenameLocations.cs similarity index 83% rename from src/Workspaces/Core/Portable/Rename/RenameLocationSet.cs rename to src/Workspaces/Core/Portable/Rename/RenameLocations.cs index f26ba601d679fa23a2b6a8b8543f104d983959c2..55af0a243be2dbb77dc48de9736742266a9100ff 100644 --- a/src/Workspaces/Core/Portable/Rename/RenameLocationSet.cs +++ b/src/Workspaces/Core/Portable/Rename/RenameLocations.cs @@ -14,10 +14,10 @@ namespace Microsoft.CodeAnalysis.Rename { /// - /// Holds the ILocations of a symbol that should be renamed, along with the symbol and Solution + /// Holds the Locations of a symbol that should be renamed, along with the symbol and Solution /// for the set. /// - internal sealed partial class RenameLocationSet + internal sealed partial class RenameLocations { private class SearchResult { @@ -37,26 +37,27 @@ public SearchResult(ISet locations, IEnumerable _overloadsResult; private readonly IEnumerable _stringsResult; private readonly IEnumerable _commentsResult; - public RenameLocationSet(ISet locations, ISymbol symbol, Solution solution, IEnumerable referencedSymbols, IEnumerable implicitLocations) + internal RenameLocations(ISet locations, ISymbol symbol, Solution solution, IEnumerable referencedSymbols, IEnumerable implicitLocations, OptionSet options) { _symbol = symbol; _solution = solution; _mergedResult = new SearchResult(locations, implicitLocations, referencedSymbols); + Options = options; } - private RenameLocationSet(ISymbol symbol, Solution solution, OptionSet optionSet, SearchResult originalSymbolResult, List overloadsResult, IEnumerable stringsResult, IEnumerable commentsResult) + private RenameLocations(ISymbol symbol, Solution solution, OptionSet options, SearchResult originalSymbolResult, List overloadsResult, IEnumerable stringsResult, IEnumerable commentsResult) { _symbol = symbol; _solution = solution; - _optionSet = optionSet; + Options = options; _originalSymbolResult = originalSymbolResult; _overloadsResult = overloadsResult; _stringsResult = stringsResult; @@ -66,18 +67,18 @@ private RenameLocationSet(ISymbol symbol, Solution solution, OptionSet optionSet var mergedReferencedSymbols = new List(); var mergedImplicitLocations = new List(); - if (optionSet.GetOption(RenameOptions.RenameInStrings)) + if (options.GetOption(RenameOptions.RenameInStrings)) { mergedLocations.AddRange(stringsResult); } - if (optionSet.GetOption(RenameOptions.RenameInComments)) + if (options.GetOption(RenameOptions.RenameInComments)) { mergedLocations.AddRange(commentsResult); } - var renameMethodGroupReferences = optionSet.GetOption(RenameOptions.RenameOverloads) || !GetOverloadedSymbols(symbol).Any(); - var overloadsToMerge = (optionSet.GetOption(RenameOptions.RenameOverloads) ? overloadsResult : null) ?? SpecializedCollections.EmptyEnumerable(); + var renameMethodGroupReferences = options.GetOption(RenameOptions.RenameOverloads) || !GetOverloadedSymbols(symbol).Any(); + var overloadsToMerge = (options.GetOption(RenameOptions.RenameOverloads) ? overloadsResult : null) ?? SpecializedCollections.EmptyEnumerable(); foreach (var result in overloadsToMerge.Concat(originalSymbolResult)) { mergedLocations.AddRange(renameMethodGroupReferences @@ -100,22 +101,22 @@ private RenameLocationSet(ISymbol symbol, Solution solution, OptionSet optionSet /// /// Find the locations that need to be renamed. /// - public static async Task FindAsync(ISymbol symbol, Solution solution, OptionSet optionSet, CancellationToken cancellationToken) + internal static async Task FindAsync(ISymbol symbol, Solution solution, OptionSet optionSet, CancellationToken cancellationToken) { Contract.ThrowIfNull(symbol); using (Logger.LogBlock(FunctionId.Rename_AllRenameLocations, cancellationToken)) { symbol = await ReferenceProcessing.FindDefinitionSymbolAsync(symbol, solution, cancellationToken).ConfigureAwait(false); var originalSymbolResult = await AddLocationsReferenceSymbolsAsync(symbol, solution, cancellationToken).ConfigureAwait(false); - var intermediateResult = new RenameLocationSet(symbol, solution, optionSet, originalSymbolResult, overloadsResult: null, stringsResult: null, commentsResult: null); + var intermediateResult = new RenameLocations(symbol, solution, optionSet, originalSymbolResult, overloadsResult: null, stringsResult: null, commentsResult: null); return await intermediateResult.FindWithUpdatedOptionsAsync(optionSet, cancellationToken).ConfigureAwait(false); } } - public async Task FindWithUpdatedOptionsAsync(OptionSet optionSet, CancellationToken cancellationToken) + internal async Task FindWithUpdatedOptionsAsync(OptionSet optionSet, CancellationToken cancellationToken) { - Contract.ThrowIfNull(_optionSet, "FindWithUpdatedOptionsAsync can only be called on a result of FindAsync"); + Contract.ThrowIfNull(Options, "FindWithUpdatedOptionsAsync can only be called on a result of FindAsync"); using (Logger.LogBlock(FunctionId.Rename_AllRenameLocations, cancellationToken)) { var overloadsResult = _overloadsResult ?? (optionSet.GetOption(RenameOptions.RenameOverloads) @@ -130,7 +131,7 @@ public async Task FindWithUpdatedOptionsAsync(OptionSet optio optionSet.GetOption(RenameOptions.RenameInComments) && _commentsResult == null, cancellationToken).ConfigureAwait(false); - return new RenameLocationSet(_symbol, _solution, optionSet, _originalSymbolResult, + return new RenameLocations(_symbol, _solution, optionSet, _originalSymbolResult, _overloadsResult ?? overloadsResult, _stringsResult ?? stringsAndComments.Item1, _commentsResult ?? stringsAndComments.Item2); @@ -191,4 +192,4 @@ internal static IEnumerable GetOverloadedSymbols(ISymbol symbol) return new SearchResult(locations, implicitLocations, referencedSymbols); } } -} +} \ No newline at end of file diff --git a/src/Workspaces/Core/Portable/Rename/Renamer.cs b/src/Workspaces/Core/Portable/Rename/Renamer.cs index c0253989b59685a6a7059fcd630c58928cbb2577..a3cc596f43a75cc9ccaeac562c1d320b9fc6a110 100644 --- a/src/Workspaces/Core/Portable/Rename/Renamer.cs +++ b/src/Workspaces/Core/Portable/Rename/Renamer.cs @@ -18,14 +18,7 @@ public static Task RenameSymbolAsync(Solution solution, ISymbol symbol return RenameSymbolAsync(solution, symbol, newName, optionSet, filter: null, cancellationToken: cancellationToken); } - internal static async Task RenameSymbolAsync( - Solution solution, - ISymbol symbol, - string newName, - OptionSet optionSet, - Func filter, - Func, bool?> hasConflict = null, - CancellationToken cancellationToken = default(CancellationToken)) + internal static Task GetRenameLocationsAsync(Solution solution, ISymbol symbol, OptionSet options, CancellationToken cancellationToken) { if (solution == null) { @@ -37,27 +30,66 @@ public static Task RenameSymbolAsync(Solution solution, ISymbol symbol throw new ArgumentNullException(nameof(symbol)); } + cancellationToken.ThrowIfCancellationRequested(); + + options = options ?? solution.Workspace.Options; + return RenameLocations.FindAsync(symbol, solution, options, cancellationToken); + } + + internal static async Task RenameAsync( + RenameLocations locations, + string newName, + Func filter = null, + Func, bool?> hasConflict = null, + CancellationToken cancellationToken = default(CancellationToken)) + { if (string.IsNullOrEmpty(newName)) { - throw new ArgumentException("newName"); + throw new ArgumentException(nameof(newName)); } cancellationToken.ThrowIfCancellationRequested(); - optionSet = optionSet ?? solution.Workspace.Options; - var renameLocationSet = await RenameLocationSet.FindAsync(symbol, solution, optionSet, cancellationToken).ConfigureAwait(false); + var symbol = locations.Symbol; if (filter != null) { - renameLocationSet = new RenameLocationSet( - renameLocationSet.Locations.Where(loc => filter(loc.Location)).ToSet(), - renameLocationSet.Symbol, renameLocationSet.Solution, - renameLocationSet.ReferencedSymbols, renameLocationSet.ImplicitLocations); + locations = new RenameLocations( + locations.Locations.Where(loc => filter(loc.Location)).ToSet(), + symbol, locations.Solution, + locations.ReferencedSymbols, locations.ImplicitLocations, + locations.Options); } var conflictResolution = await ConflictResolver.ResolveConflictsAsync( - renameLocationSet, symbol.Name, newName, optionSet, hasConflict, cancellationToken).ConfigureAwait(false); + locations, symbol.Name, newName, locations.Options, hasConflict, cancellationToken).ConfigureAwait(false); return conflictResolution.NewSolution; } + + internal static async Task RenameSymbolAsync( + Solution solution, + ISymbol symbol, + string newName, + OptionSet options, + Func filter, + Func, bool?> hasConflict = null, + CancellationToken cancellationToken = default(CancellationToken)) + { + if (solution == null) + { + throw new ArgumentNullException(nameof(solution)); + } + + if (symbol == null) + { + throw new ArgumentNullException(nameof(symbol)); + } + + cancellationToken.ThrowIfCancellationRequested(); + + 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); + } } -} +} \ No newline at end of file diff --git a/src/Workspaces/Core/Portable/Workspaces.csproj b/src/Workspaces/Core/Portable/Workspaces.csproj index 0a927534a852c194968a8143f34c522a2ad38627..fce6b5d4ea5e490ebfd83507e35c564f0da30793 100644 --- a/src/Workspaces/Core/Portable/Workspaces.csproj +++ b/src/Workspaces/Core/Portable/Workspaces.csproj @@ -649,7 +649,7 @@ - + diff --git a/src/Workspaces/VisualBasic/Portable/Rename/VisualBasicRenameRewriterLanguageService.vb b/src/Workspaces/VisualBasic/Portable/Rename/VisualBasicRenameRewriterLanguageService.vb index 882ed6e240c74047ad73282b96a9331c80e7d461..6000f12806b645ee83f4db560e93f467860d37bf 100644 --- a/src/Workspaces/VisualBasic/Portable/Rename/VisualBasicRenameRewriterLanguageService.vb +++ b/src/Workspaces/VisualBasic/Portable/Rename/VisualBasicRenameRewriterLanguageService.vb @@ -545,7 +545,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Rename Private Function RenameInStringLiteral(oldToken As SyntaxToken, newToken As SyntaxToken, createNewStringLiteral As Func(Of SyntaxTriviaList, String, String, SyntaxTriviaList, SyntaxToken)) As SyntaxToken Dim originalString = newToken.ToString() - Dim replacedString As String = RenameLocationSet.ReferenceProcessing.ReplaceMatchingSubStrings(originalString, _originalText, _replacementText) + Dim replacedString As String = RenameLocations.ReferenceProcessing.ReplaceMatchingSubStrings(originalString, _originalText, _replacementText) If replacedString <> originalString Then Dim oldSpan = oldToken.Span newToken = createNewStringLiteral(newToken.LeadingTrivia, replacedString, replacedString, newToken.TrailingTrivia) @@ -558,7 +558,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Rename Private Function RenameInCommentTrivia(trivia As SyntaxTrivia) As SyntaxTrivia Dim originalString = trivia.ToString() - Dim replacedString As String = RenameLocationSet.ReferenceProcessing.ReplaceMatchingSubStrings(originalString, _originalText, _replacementText) + Dim replacedString As String = RenameLocations.ReferenceProcessing.ReplaceMatchingSubStrings(originalString, _originalText, _replacementText) If replacedString <> originalString Then Dim oldSpan = trivia.Span Dim newTrivia = SyntaxFactory.CommentTrivia(replacedString)