public async Task <IInlineRenameLocationSet> FindRenameLocationsAsync(OptionSet?optionSet, CancellationToken cancellationToken) { var solution = _document.Project.Solution; var locations = await Renamer.FindRenameLocationsAsync( solution, this.RenameSymbol, RenameOptionSet.From(solution, optionSet), cancellationToken).ConfigureAwait(false); return(new InlineRenameLocationSet(this, locations)); }
private async Task <IInlineRenameLocationSet> GetLocationSetAsync(Task <RenameLocations> renameTask, OptionSet optionSet, CancellationToken cancellationToken) { var locationSet = await renameTask.ConfigureAwait(false); if (optionSet != null) { locationSet = await locationSet.FindWithUpdatedOptionsAsync( RenameOptionSet.From(_document.Project.Solution, optionSet), cancellationToken).ConfigureAwait(false); } return(new InlineRenameLocationSet(this, locationSet)); }
private static async Task <Solution> RenameAsync( Solution solution, IFieldSymbol field, string finalName, Func <Location, bool> filter, CancellationToken cancellationToken) { var initialLocations = await Renamer.FindRenameLocationsAsync( solution, field, RenameOptionSet.From(solution), cancellationToken).ConfigureAwait(false); var resolution = await initialLocations.Filter(filter).ResolveConflictsAsync( finalName, nonConflictSymbols: null, cancellationToken).ConfigureAwait(false); Contract.ThrowIfTrue(resolution.ErrorMessage != null); return(resolution.NewSolution); }
private async Task <Solution> ProcessResultAsync(CodeFixContext context, Diagnostic diagnostic, CancellationToken cancellationToken) { var locations = diagnostic.AdditionalLocations; var propertyLocation = locations[0]; var declaratorLocation = locations[1]; var solution = context.Document.Project.Solution; var declarator = (TVariableDeclarator)declaratorLocation.FindNode(cancellationToken); var fieldDocument = solution.GetRequiredDocument(declarator.SyntaxTree); var fieldSemanticModel = await fieldDocument.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); var fieldSymbol = (IFieldSymbol)fieldSemanticModel.GetDeclaredSymbol(declarator, cancellationToken); var property = GetPropertyDeclaration(propertyLocation.FindNode(cancellationToken)); var propertyDocument = solution.GetRequiredDocument(property.SyntaxTree); var propertySemanticModel = await propertyDocument.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); var propertySymbol = (IPropertySymbol)propertySemanticModel.GetDeclaredSymbol(property, cancellationToken); Debug.Assert(fieldDocument.Project == propertyDocument.Project); var project = fieldDocument.Project; var compilation = await project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false); var fieldLocations = await Renamer.FindRenameLocationsAsync( solution, fieldSymbol, RenameOptionSet.From(solution), cancellationToken).ConfigureAwait(false); // First, create the updated property we want to replace the old property with var isWrittenToOutsideOfConstructor = IsWrittenToOutsideOfConstructorOrProperty(fieldSymbol, fieldLocations, property, cancellationToken); var updatedProperty = await UpdatePropertyAsync( propertyDocument, compilation, fieldSymbol, propertySymbol, property, isWrittenToOutsideOfConstructor, cancellationToken).ConfigureAwait(false); // Note: rename will try to update all the references in linked files as well. However, // this can lead to some very bad behavior as we will change the references in linked files // but only remove the field and update the property in a single document. So, you can // end in the state where you do this in one of the linked file: // // int Prop { get { return this.field; } } => int Prop { get { return this.Prop } } // // But in the main file we'll replace: // // int Prop { get { return this.field; } } => int Prop { get; } // // The workspace will see these as two irreconcilable edits. To avoid this, we disallow // any edits to the other links for the files containing the field and property. i.e. // rename will only be allowed to edit the exact same doc we're removing the field from // and the exact doc we're updating the property in. It can't touch the other linked // files for those docs. (It can of course touch any other documents unrelated to the // docs that the field and prop are declared in). var linkedFiles = new HashSet <DocumentId>(); linkedFiles.AddRange(fieldDocument.GetLinkedDocumentIds()); linkedFiles.AddRange(propertyDocument.GetLinkedDocumentIds()); var canEdit = new Dictionary <SyntaxTree, bool>(); // 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. // // We're asking the rename API to update a bunch of references to an existing field to the same name as an // existing property. Rename will often flag this situation as an unresolvable conflict because the new // name won't bind to the field anymore. // // To address this, we let rename know that there is no conflict if the new symbol it resolves to is the // same as the property we're trying to get the references pointing to. var filteredLocations = fieldLocations.Filter( location => location.SourceTree != null && !location.IntersectsWith(declaratorLocation) && CanEditDocument(solution, location.SourceTree, linkedFiles, canEdit)); var resolution = await filteredLocations.ResolveConflictsAsync( propertySymbol.Name, nonConflictSymbols : ImmutableHashSet.Create <ISymbol>(propertySymbol), cancellationToken).ConfigureAwait(false); Contract.ThrowIfTrue(resolution.ErrorMessage != null); solution = resolution.NewSolution; // Now find the field and property again post rename. fieldDocument = solution.GetRequiredDocument(fieldDocument.Id); propertyDocument = solution.GetRequiredDocument(propertyDocument.Id); Debug.Assert(fieldDocument.Project == propertyDocument.Project); compilation = await fieldDocument.Project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false); fieldSymbol = (IFieldSymbol?)fieldSymbol.GetSymbolKey(cancellationToken).Resolve(compilation, cancellationToken: cancellationToken).Symbol; propertySymbol = (IPropertySymbol?)propertySymbol.GetSymbolKey(cancellationToken).Resolve(compilation, cancellationToken: cancellationToken).Symbol; Contract.ThrowIfTrue(fieldSymbol == null || propertySymbol == null); declarator = (TVariableDeclarator)await fieldSymbol.DeclaringSyntaxReferences[0].GetSyntaxAsync(cancellationToken).ConfigureAwait(false); property = GetPropertyDeclaration(await propertySymbol.DeclaringSyntaxReferences[0].GetSyntaxAsync(cancellationToken).ConfigureAwait(false)); var nodeToRemove = GetNodeToRemove(declarator); // If we have a situation where the property is the second member in a type, and it // would become the first, then remove any leading blank lines from it so we don't have // random blanks above it that used to space it from the field that was there. // // The reason we do this special processing is that the first member of a type tends to // be special wrt leading trivia. i.e. users do not normally put blank lines before the // first member. And so, when a type now becomes the first member, we want to follow the // user's common pattern here. // // In all other code cases, i.e.when there are multiple fields above, or the field is // below the property, then the property isn't now becoming "the first member", and as // such, it doesn't want this special behavior about it's leading blank lines. i.e. if // the user has: // // class C // { // int i; // int j; // // int Prop => j; // } // // Then if we remove 'j' (or even 'i'), then 'Prop' would stay the non-first member, and // would definitely want to keep that blank line above it. // // In essence, the blank line above the property exists for separation from what's above // it. As long as something is above it, we keep the separation. However, if the // property becomes the first member in the type, the separation is now inappropriate // because there's nothing to actually separate it from. if (fieldDocument == propertyDocument) { var syntaxFacts = fieldDocument.GetRequiredLanguageService <ISyntaxFactsService>(); if (WillRemoveFirstFieldInTypeDirectlyAboveProperty(syntaxFacts, property, nodeToRemove) && syntaxFacts.GetLeadingBlankLines(nodeToRemove).Length == 0) { updatedProperty = syntaxFacts.GetNodeWithoutLeadingBlankLines(updatedProperty); } } var syntaxRemoveOptions = CreateSyntaxRemoveOptions(nodeToRemove); if (fieldDocument == propertyDocument) { // Same file. Have to do this in a slightly complicated fashion. var declaratorTreeRoot = await fieldDocument.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var editor = new SyntaxEditor(declaratorTreeRoot, fieldDocument.Project.Solution.Workspace); editor.ReplaceNode(property, updatedProperty); editor.RemoveNode(nodeToRemove, syntaxRemoveOptions); var newRoot = editor.GetChangedRoot(); newRoot = await FormatAsync(newRoot, fieldDocument, cancellationToken).ConfigureAwait(false); return(solution.WithDocumentSyntaxRoot(fieldDocument.Id, newRoot)); } else { // In different files. Just update both files. var fieldTreeRoot = await fieldDocument.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var propertyTreeRoot = await propertyDocument.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var newFieldTreeRoot = fieldTreeRoot.RemoveNode(nodeToRemove, syntaxRemoveOptions); Contract.ThrowIfNull(newFieldTreeRoot); var newPropertyTreeRoot = propertyTreeRoot.ReplaceNode(property, updatedProperty); newFieldTreeRoot = await FormatAsync(newFieldTreeRoot, fieldDocument, cancellationToken).ConfigureAwait(false); newPropertyTreeRoot = await FormatAsync(newPropertyTreeRoot, propertyDocument, cancellationToken).ConfigureAwait(false); var updatedSolution = solution.WithDocumentSyntaxRoot(fieldDocument.Id, newFieldTreeRoot); updatedSolution = updatedSolution.WithDocumentSyntaxRoot(propertyDocument.Id, newPropertyTreeRoot); return(updatedSolution); } }