From b9a3e8254db1088abddf4c490d6ab72bf303dd6b Mon Sep 17 00:00:00 2001 From: CyrusNajmabadi Date: Thu, 4 May 2017 19:38:30 -0700 Subject: [PATCH] Fix crash in AddUsing when changing casing of a typename. --- .../CSharpTest/AddUsing/AddUsingTests.cs | 49 +++++++++++++++++++ .../PackageReference.ParentCodeAction.cs | 3 +- .../AddImport/References/AssemblyReference.cs | 5 +- .../AddImport/References/Reference.cs | 30 ++++++------ .../AddImport/References/SymbolReference.cs | 17 ++++--- 5 files changed, 79 insertions(+), 25 deletions(-) diff --git a/src/EditorFeatures/CSharpTest/AddUsing/AddUsingTests.cs b/src/EditorFeatures/CSharpTest/AddUsing/AddUsingTests.cs index df00e9a3504..3c674d113ae 100644 --- a/src/EditorFeatures/CSharpTest/AddUsing/AddUsingTests.cs +++ b/src/EditorFeatures/CSharpTest/AddUsing/AddUsingTests.cs @@ -4554,5 +4554,54 @@ void M() } }"); } + + [WorkItem(19218, "https://github.com/dotnet/roslyn/issues/19218")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsAddImport)] + public async Task TestChangeCaseWithUsingsInNestedNamespace() + { + await TestInRegularAndScriptAsync( +@"namespace VS +{ + interface IVsStatusbar + { + } +} + +namespace Outer +{ + using System; + + class C + { + void M() + { + // Note: IVsStatusBar is cased incorrectly. + [|IVsStatusBar|] b; + } + } +} +", +@"namespace VS +{ + interface IVsStatusbar + { + } +} + +namespace Outer +{ + using System; + using VS; + + class C + { + void M() + { + IVsStatusbar b; + } + } +} +"); + } } } \ No newline at end of file diff --git a/src/Features/Core/Portable/AddImport/CodeActions/PackageReference.ParentCodeAction.cs b/src/Features/Core/Portable/AddImport/CodeActions/PackageReference.ParentCodeAction.cs index 87560df22c4..f7265a2c46e 100644 --- a/src/Features/Core/Portable/AddImport/CodeActions/PackageReference.ParentCodeAction.cs +++ b/src/Features/Core/Portable/AddImport/CodeActions/PackageReference.ParentCodeAction.cs @@ -107,7 +107,8 @@ private class ParentCodeAction : CodeAction.CodeActionWithNestedActions CancellationToken cancellationToken) { var oldDocument = document; - reference.ReplaceNameNode(ref node, ref document, cancellationToken); + (node, document) = await reference.ReplaceNameNodeAsync( + node, document, cancellationToken).ConfigureAwait(false); var newDocument = await reference.provider.AddImportAsync( node, reference.SearchResult.NameParts, document, placeSystemNamespaceFirst, cancellationToken).ConfigureAwait(false); diff --git a/src/Features/Core/Portable/AddImport/References/AssemblyReference.cs b/src/Features/Core/Portable/AddImport/References/AssemblyReference.cs index 07f8df29262..ba59aac0fad 100644 --- a/src/Features/Core/Portable/AddImport/References/AssemblyReference.cs +++ b/src/Features/Core/Portable/AddImport/References/AssemblyReference.cs @@ -105,9 +105,8 @@ protected override async Task> ComputeOperation var reference = service.GetReference(resolvedPath, MetadataReferenceProperties.Assembly); // First add the "using/import" directive in the code. - var node = _node; - var document = _document; - _reference.ReplaceNameNode(ref node, ref document, cancellationToken); + (SyntaxNode node, Document document) = await _reference.ReplaceNameNodeAsync( + _node, _document, cancellationToken).ConfigureAwait(false); var newDocument = await _reference.provider.AddImportAsync( node, _reference.SearchResult.NameParts, document, _placeSystemNamespaceFirst, cancellationToken).ConfigureAwait(false); diff --git a/src/Features/Core/Portable/AddImport/References/Reference.cs b/src/Features/Core/Portable/AddImport/References/Reference.cs index a36eb4646d3..a6e25150913 100644 --- a/src/Features/Core/Portable/AddImport/References/Reference.cs +++ b/src/Features/Core/Portable/AddImport/References/Reference.cs @@ -92,26 +92,28 @@ public override int GetHashCode() return Hash.CombineValues(this.SearchResult.NameParts); } - protected void ReplaceNameNode( - ref SyntaxNode contextNode, ref Document document, CancellationToken cancellationToken) + protected async Task<(SyntaxNode, Document)> ReplaceNameNodeAsync( + SyntaxNode contextNode, Document document, CancellationToken cancellationToken) { - if (!this.SearchResult.DesiredNameDiffersFromSourceName()) + if (this.SearchResult.DesiredNameDiffersFromSourceName()) { - return; - } + var identifier = SearchResult.NameNode.GetFirstToken(); + var generator = SyntaxGenerator.GetGenerator(document); + var newIdentifier = generator.IdentifierName(SearchResult.DesiredName).GetFirstToken().WithTriviaFrom(identifier); + var annotation = new SyntaxAnnotation(); + + var root = contextNode.SyntaxTree.GetRoot(cancellationToken); + root = root.ReplaceToken(identifier, newIdentifier.WithAdditionalAnnotations(annotation)); + document = document.WithSyntaxRoot(root); - var identifier = SearchResult.NameNode.GetFirstToken(); - var generator = SyntaxGenerator.GetGenerator(document); - var newIdentifier = generator.IdentifierName(SearchResult.DesiredName).GetFirstToken().WithTriviaFrom(identifier); - var annotation = new SyntaxAnnotation(); + var currentRoot = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + contextNode = currentRoot.GetAnnotatedTokens(annotation).First().Parent; + } - var root = contextNode.SyntaxTree.GetRoot(cancellationToken); - root = root.ReplaceToken(identifier, newIdentifier.WithAdditionalAnnotations(annotation)); - document = document.WithSyntaxRoot(root); - contextNode = root.GetAnnotatedTokens(annotation).First().Parent; + return (contextNode, document); } public abstract Task CreateCodeActionAsync(Document document, SyntaxNode node, bool placeSystemNamespaceFirst, CancellationToken cancellationToken); } } -} +} \ No newline at end of file diff --git a/src/Features/Core/Portable/AddImport/References/SymbolReference.cs b/src/Features/Core/Portable/AddImport/References/SymbolReference.cs index 4ad54cbb54f..9b27495bbc9 100644 --- a/src/Features/Core/Portable/AddImport/References/SymbolReference.cs +++ b/src/Features/Core/Portable/AddImport/References/SymbolReference.cs @@ -2,9 +2,11 @@ using System; using System.Collections.Immutable; +using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.FindSymbols; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CodeFixes.AddImport @@ -61,22 +63,23 @@ public override int GetHashCode() protected virtual Solution GetUpdatedSolution(Document newDocument) => newDocument.Project.Solution; - private Task UpdateDocumentAsync( + private async Task UpdateDocumentAsync( Document document, SyntaxNode contextNode, bool placeSystemNamespaceFirst, bool hasExistingImport, CancellationToken cancellationToken) { - ReplaceNameNode(ref contextNode, ref document, cancellationToken); - // Defer to the language to add the actual import/using. if (hasExistingImport) { - return Task.FromResult(document); + return document; } - return provider.AddImportAsync(contextNode, - this.SymbolResult.Symbol, document, - placeSystemNamespaceFirst, cancellationToken); + (var newContextNode, var newDocument) = await ReplaceNameNodeAsync( + contextNode, document, cancellationToken).ConfigureAwait(false); + + return await provider.AddImportAsync( + newContextNode, this.SymbolResult.Symbol, newDocument, + placeSystemNamespaceFirst, cancellationToken).ConfigureAwait(false); } public override async Task CreateCodeActionAsync( -- GitLab