@@ -34,14 +34,16 @@ namespace Microsoft.CodeAnalysis.ChangeNamespace;
3434/// <summary>
3535/// This intermediate class is used to hide method `TryGetReplacementReferenceSyntax` from <see cref="IChangeNamespaceService" />.
3636/// </summary>
37- internal abstract class AbstractChangeNamespaceService : IChangeNamespaceService
37+ internal abstract partial class AbstractChangeNamespaceService : IChangeNamespaceService
3838{
3939 public abstract Task < bool > CanChangeNamespaceAsync ( Document document , SyntaxNode container , CancellationToken cancellationToken ) ;
4040
4141 public abstract Task < Solution > ChangeNamespaceAsync ( Document document , SyntaxNode container , string targetNamespace , CancellationToken cancellationToken ) ;
4242
4343 public abstract Task < Solution ? > TryChangeTopLevelNamespacesAsync ( Document document , string targetNamespace , CancellationToken cancellationToken ) ;
4444
45+ public abstract AbstractReducer NameReducer { get ; }
46+
4547 /// <summary>
4648 /// Try to get a new node to replace given node, which is a reference to a top-level type declared inside the
4749 /// namespace to be changed. If this reference is the right side of a qualified name, the new node returned would
@@ -57,11 +59,20 @@ internal abstract class AbstractChangeNamespaceService : IChangeNamespaceService
5759 public abstract bool TryGetReplacementReferenceSyntax ( SyntaxNode reference , ImmutableArray < string > newNamespaceParts , ISyntaxFactsService syntaxFacts , [ NotNullWhen ( returnValue : true ) ] out SyntaxNode ? old , [ NotNullWhen ( returnValue : true ) ] out SyntaxNode ? @new ) ;
5860}
5961
60- internal abstract class AbstractChangeNamespaceService < TNamespaceDeclarationSyntax , TCompilationUnitSyntax , TMemberDeclarationSyntax >
62+ internal abstract partial class AbstractChangeNamespaceService <
63+ TCompilationUnitSyntax ,
64+ TMemberDeclarationSyntax ,
65+ TNamespaceDeclarationSyntax ,
66+ TNameSyntax ,
67+ TSimpleNameSyntax ,
68+ TCrefSyntax >
6169 : AbstractChangeNamespaceService
62- where TNamespaceDeclarationSyntax : SyntaxNode
6370 where TCompilationUnitSyntax : SyntaxNode
6471 where TMemberDeclarationSyntax : SyntaxNode
72+ where TNamespaceDeclarationSyntax : TMemberDeclarationSyntax
73+ where TNameSyntax : SyntaxNode
74+ where TSimpleNameSyntax : TNameSyntax
75+ where TCrefSyntax : SyntaxNode
6576{
6677 private static readonly char [ ] s_dotSeparator = [ '.' ] ;
6778
@@ -125,9 +136,7 @@ public override async Task<bool> CanChangeNamespaceAsync(Document document, Synt
125136 var originalNamespaceDeclarations = await GetTopLevelNamespacesAsync ( document , cancellationToken ) . ConfigureAwait ( false ) ;
126137
127138 if ( originalNamespaceDeclarations . Length == 0 )
128- {
129139 return null ;
130- }
131140
132141 var semanticModel = await document . GetRequiredSemanticModelAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
133142 var originalNamespaceName = semanticModel . GetRequiredDeclaredSymbol ( originalNamespaceDeclarations . First ( ) , cancellationToken ) . ToDisplayString ( ) ;
@@ -139,12 +148,10 @@ public override async Task<bool> CanChangeNamespaceAsync(Document document, Synt
139148 for ( var i = 0 ; i < originalNamespaceDeclarations . Length ; i ++ )
140149 {
141150 var namespaceName = semanticModel . GetRequiredDeclaredSymbol ( originalNamespaceDeclarations [ i ] , cancellationToken ) . ToDisplayString ( ) ;
151+
152+ // Skip all namespaces that didn't match the original namespace name that we were syncing.
142153 if ( namespaceName != originalNamespaceName )
143- {
144- // Skip all namespaces that didn't match the original namespace name that
145- // we were syncing.
146154 continue ;
147- }
148155
149156 syntaxRoot = await document . GetRequiredSyntaxRootAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
150157
@@ -194,16 +201,12 @@ public override async Task<Solution> ChangeNamespaceAsync(
194201
195202 var containersFromAllDocuments = await GetValidContainersFromAllLinkedDocumentsAsync ( document , container , cancellationToken ) . ConfigureAwait ( false ) ;
196203 if ( containersFromAllDocuments . IsDefault )
197- {
198204 return solution ;
199- }
200205
201206 // No action required if declared namespace already matches target.
202207 var declaredNamespace = GetDeclaredNamespace ( container ) ;
203208 if ( syntaxFacts . StringComparer . Equals ( targetNamespace , declaredNamespace ) )
204- {
205209 return solution ;
206- }
207210
208211 // Annotate the container nodes so we can still find and modify them after syntax tree has changed.
209212 var annotatedSolution = await AnnotateContainersAsync ( solution , containersFromAllDocuments , cancellationToken ) . ConfigureAwait ( false ) ;
@@ -222,9 +225,8 @@ public override async Task<Solution> ChangeNamespaceAsync(
222225
223226 foreach ( var documentId in documentIds )
224227 {
225- var ( newSolution , refDocumentIds ) =
226- await ChangeNamespaceInSingleDocumentAsync ( solutionAfterNamespaceChange , documentId , declaredNamespace , targetNamespace , cancellationToken )
227- . ConfigureAwait ( false ) ;
228+ var ( newSolution , refDocumentIds ) = await ChangeNamespaceInSingleDocumentAsync (
229+ solutionAfterNamespaceChange , documentId , declaredNamespace , targetNamespace , cancellationToken ) . ConfigureAwait ( false ) ;
228230 solutionAfterNamespaceChange = newSolution ;
229231 referenceDocuments . AddRange ( refDocumentIds ) ;
230232 }
@@ -463,8 +465,8 @@ private static SyntaxNode CreateImport(SyntaxGenerator syntaxGenerator, string n
463465 }
464466 }
465467
466- var documentWithNewNamespace = await FixDeclarationDocumentAsync ( document , refLocationsInCurrentDocument , oldNamespace , newNamespace , cancellationToken )
467- . ConfigureAwait ( false ) ;
468+ var documentWithNewNamespace = await FixDeclarationDocumentAsync (
469+ document , refLocationsInCurrentDocument , oldNamespace , newNamespace , cancellationToken ) . ConfigureAwait ( false ) ;
468470 var solutionWithChangedNamespace = documentWithNewNamespace . Project . Solution ;
469471
470472 var refLocationsInSolution = refLocationsInOtherDocuments
@@ -501,15 +503,6 @@ private static SyntaxNode CreateImport(SyntaxGenerator syntaxGenerator, string n
501503 return ( solutionWithFixedReferences , refLocationGroups . SelectAsArray ( g => g . Key ) ) ;
502504 }
503505
504- private readonly struct LocationForAffectedSymbol ( ReferenceLocation location , bool isReferenceToExtensionMethod )
505- {
506- public ReferenceLocation ReferenceLocation { get ; } = location ;
507-
508- public bool IsReferenceToExtensionMethod { get ; } = isReferenceToExtensionMethod ;
509-
510- public Document Document => ReferenceLocation . Document ;
511- }
512-
513506 private static async Task < ImmutableArray < LocationForAffectedSymbol > > FindReferenceLocationsForSymbolAsync (
514507 Document document , ISymbol symbol , CancellationToken cancellationToken )
515508 {
@@ -631,9 +624,49 @@ private async Task<Document> FixDeclarationDocumentAsync(
631624 var services = documentWithAddedImports . Project . Solution . Services ;
632625 root = Formatter . Format ( root , Formatter . Annotation , services , documentOptions . FormattingOptions , cancellationToken ) ;
633626
634- root = root . WithAdditionalAnnotations ( Simplifier . Annotation ) ;
627+ using var _ = PooledHashSet < string > . GetInstance ( out var allNamespaceNameParts ) ;
628+ allNamespaceNameParts . AddRange ( oldNamespaceParts ) ;
629+ allNamespaceNameParts . AddRange ( newNamespaceParts ) ;
630+
631+ var syntaxFacts = document . GetRequiredLanguageService < ISyntaxFactsService > ( ) ;
632+ root = AddSimplifierAnnotationToPotentialReferences ( syntaxFacts , root , allNamespaceNameParts ) ;
633+
635634 var formattedDocument = documentWithAddedImports . WithSyntaxRoot ( root ) ;
636- return await Simplifier . ReduceAsync ( formattedDocument , documentOptions . SimplifierOptions , cancellationToken ) . ConfigureAwait ( false ) ;
635+ return await SimplifyTypeNamesAsync ( formattedDocument , documentOptions , cancellationToken ) . ConfigureAwait ( false ) ;
636+ }
637+
638+ private static SyntaxNode AddSimplifierAnnotationToPotentialReferences (
639+ ISyntaxFactsService syntaxFacts , SyntaxNode root , HashSet < string > allNamespaceNameParts )
640+ {
641+ // Find all identifiers in this tree that use at least one of the namespace names of either the old or new
642+ // namespace. Mark those as needing potential complexification/simplification to preserve meaning.
643+ //
644+ // Note: we could go further here and actually bind these nodes to make sure they are actually references
645+ // to one of the namespaces in question. But that doesn't seem super necessary as the chance that these names
646+ // are actually to something else *and* they would reduce without issue seems very low. This can be revisited
647+ // if we get feedback on this.
648+
649+ using var _ = PooledHashSet < SyntaxNode > . GetInstance ( out var namesToUpdate ) ;
650+ foreach ( var descendent in root . DescendantNodes ( descendIntoTrivia : true ) )
651+ {
652+ if ( descendent is TSimpleNameSyntax simpleName &&
653+ allNamespaceNameParts . Contains ( syntaxFacts . GetIdentifierOfSimpleName ( simpleName ) . ValueText ) )
654+ {
655+ namesToUpdate . Add ( GetHighestNameOrCref ( simpleName ) ) ;
656+ }
657+ }
658+
659+ return root . ReplaceNodes (
660+ namesToUpdate ,
661+ ( _ , current ) => current . WithAdditionalAnnotations ( Simplifier . Annotation ) ) ;
662+
663+ static SyntaxNode GetHighestNameOrCref ( TNameSyntax name )
664+ {
665+ while ( name . Parent is TNameSyntax parentName )
666+ name = parentName ;
667+
668+ return name . Parent is TCrefSyntax ? name . Parent : name ;
669+ }
637670 }
638671
639672 private static async Task < Document > FixReferencingDocumentAsync (
@@ -651,9 +684,8 @@ private static async Task<Document> FixReferencingDocumentAsync(
651684
652685 var newNamespaceParts = GetNamespaceParts ( newNamespace ) ;
653686
654- var ( documentWithRefFixed , containers ) =
655- await FixReferencesAsync ( document , changeNamespaceService , addImportService , refLocations , newNamespaceParts , cancellationToken )
656- . ConfigureAwait ( false ) ;
687+ var ( documentWithRefFixed , containers ) = await FixReferencesAsync (
688+ document , changeNamespaceService , addImportService , refLocations , newNamespaceParts , cancellationToken ) . ConfigureAwait ( false ) ;
657689
658690 var documentOptions = await document . GetCodeCleanupOptionsAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
659691
@@ -666,10 +698,24 @@ await FixReferencesAsync(document, changeNamespaceService, addImportService, ref
666698 cancellationToken ) . ConfigureAwait ( false ) ;
667699
668700 // Need to invoke formatter explicitly since we are doing the diff merge ourselves.
669- var formattedDocument = await Formatter . FormatAsync ( documentWithAdditionalImports , Formatter . Annotation , documentOptions . FormattingOptions , cancellationToken )
670- . ConfigureAwait ( false ) ;
701+ var formattedDocument = await Formatter . FormatAsync (
702+ documentWithAdditionalImports , Formatter . Annotation , documentOptions . FormattingOptions , cancellationToken ) . ConfigureAwait ( false ) ;
671703
672- return await Simplifier . ReduceAsync ( formattedDocument , documentOptions . SimplifierOptions , cancellationToken ) . ConfigureAwait ( false ) ;
704+ return await SimplifyTypeNamesAsync ( formattedDocument , documentOptions , cancellationToken ) . ConfigureAwait ( false ) ;
705+ }
706+
707+ private static async Task < Document > SimplifyTypeNamesAsync (
708+ Document document , CodeCleanupOptions documentOptions , CancellationToken cancellationToken )
709+ {
710+ var changeNamespaceService = document . GetRequiredLanguageService < IChangeNamespaceService > ( ) ;
711+ var text = await document . GetTextAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
712+ var service = document . GetRequiredLanguageService < ISimplificationService > ( ) ;
713+ return await service . ReduceAsync (
714+ document ,
715+ [ new TextSpan ( 0 , text . Length ) ] ,
716+ documentOptions . SimplifierOptions ,
717+ [ changeNamespaceService . NameReducer ] ,
718+ cancellationToken ) . ConfigureAwait ( false ) ;
673719 }
674720
675721 /// <summary>
@@ -708,9 +754,7 @@ await FixReferencesAsync(document, changeNamespaceService, addImportService, ref
708754 // it will be handled properly because it is one of the reference to the type symbol. Otherwise, we don't
709755 // attempt to make a potential fix, and user might end up with errors as a result.
710756 if ( refLoc . ReferenceLocation . Alias != null )
711- {
712757 continue ;
713- }
714758
715759 // Other documents in the solution might have changed after we calculated those ReferenceLocation,
716760 // so we can't trust anything to be still up-to-date except their spans.
@@ -743,9 +787,7 @@ await FixReferencesAsync(document, changeNamespaceService, addImportService, ref
743787 }
744788
745789 foreach ( var container in containers )
746- {
747790 editor . TrackNode ( container ) ;
748- }
749791
750792 var fixedDocument = editor . GetChangedDocument ( ) ;
751793 root = await fixedDocument . GetRequiredSyntaxRootAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
@@ -858,7 +900,7 @@ private SyntaxNodeSpanStartComparer()
858900 {
859901 }
860902
861- public static SyntaxNodeSpanStartComparer Instance { get ; } = new SyntaxNodeSpanStartComparer ( ) ;
903+ public static SyntaxNodeSpanStartComparer Instance { get ; } = new ( ) ;
862904
863905 public int Compare ( SyntaxNode ? x , SyntaxNode ? y )
864906 {
0 commit comments