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));
            }
Пример #2
0
            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));
            }
Пример #3
0
        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);
            }
        }