@@ -34,14 +34,16 @@ namespace Microsoft.CodeAnalysis.ChangeNamespace;
34
34
/// <summary>
35
35
/// This intermediate class is used to hide method `TryGetReplacementReferenceSyntax` from <see cref="IChangeNamespaceService" />.
36
36
/// </summary>
37
- internal abstract class AbstractChangeNamespaceService : IChangeNamespaceService
37
+ internal abstract partial class AbstractChangeNamespaceService : IChangeNamespaceService
38
38
{
39
39
public abstract Task < bool > CanChangeNamespaceAsync ( Document document , SyntaxNode container , CancellationToken cancellationToken ) ;
40
40
41
41
public abstract Task < Solution > ChangeNamespaceAsync ( Document document , SyntaxNode container , string targetNamespace , CancellationToken cancellationToken ) ;
42
42
43
43
public abstract Task < Solution ? > TryChangeTopLevelNamespacesAsync ( Document document , string targetNamespace , CancellationToken cancellationToken ) ;
44
44
45
+ public abstract AbstractReducer NameReducer { get ; }
46
+
45
47
/// <summary>
46
48
/// Try to get a new node to replace given node, which is a reference to a top-level type declared inside the
47
49
/// 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
57
59
public abstract bool TryGetReplacementReferenceSyntax ( SyntaxNode reference , ImmutableArray < string > newNamespaceParts , ISyntaxFactsService syntaxFacts , [ NotNullWhen ( returnValue : true ) ] out SyntaxNode ? old , [ NotNullWhen ( returnValue : true ) ] out SyntaxNode ? @new ) ;
58
60
}
59
61
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 >
61
69
: AbstractChangeNamespaceService
62
- where TNamespaceDeclarationSyntax : SyntaxNode
63
70
where TCompilationUnitSyntax : SyntaxNode
64
71
where TMemberDeclarationSyntax : SyntaxNode
72
+ where TNamespaceDeclarationSyntax : TMemberDeclarationSyntax
73
+ where TNameSyntax : SyntaxNode
74
+ where TSimpleNameSyntax : TNameSyntax
75
+ where TCrefSyntax : SyntaxNode
65
76
{
66
77
private static readonly char [ ] s_dotSeparator = [ '.' ] ;
67
78
@@ -125,9 +136,7 @@ public override async Task<bool> CanChangeNamespaceAsync(Document document, Synt
125
136
var originalNamespaceDeclarations = await GetTopLevelNamespacesAsync ( document , cancellationToken ) . ConfigureAwait ( false ) ;
126
137
127
138
if ( originalNamespaceDeclarations . Length == 0 )
128
- {
129
139
return null ;
130
- }
131
140
132
141
var semanticModel = await document . GetRequiredSemanticModelAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
133
142
var originalNamespaceName = semanticModel . GetRequiredDeclaredSymbol ( originalNamespaceDeclarations . First ( ) , cancellationToken ) . ToDisplayString ( ) ;
@@ -139,12 +148,10 @@ public override async Task<bool> CanChangeNamespaceAsync(Document document, Synt
139
148
for ( var i = 0 ; i < originalNamespaceDeclarations . Length ; i ++ )
140
149
{
141
150
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.
142
153
if ( namespaceName != originalNamespaceName )
143
- {
144
- // Skip all namespaces that didn't match the original namespace name that
145
- // we were syncing.
146
154
continue ;
147
- }
148
155
149
156
syntaxRoot = await document . GetRequiredSyntaxRootAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
150
157
@@ -194,16 +201,12 @@ public override async Task<Solution> ChangeNamespaceAsync(
194
201
195
202
var containersFromAllDocuments = await GetValidContainersFromAllLinkedDocumentsAsync ( document , container , cancellationToken ) . ConfigureAwait ( false ) ;
196
203
if ( containersFromAllDocuments . IsDefault )
197
- {
198
204
return solution ;
199
- }
200
205
201
206
// No action required if declared namespace already matches target.
202
207
var declaredNamespace = GetDeclaredNamespace ( container ) ;
203
208
if ( syntaxFacts . StringComparer . Equals ( targetNamespace , declaredNamespace ) )
204
- {
205
209
return solution ;
206
- }
207
210
208
211
// Annotate the container nodes so we can still find and modify them after syntax tree has changed.
209
212
var annotatedSolution = await AnnotateContainersAsync ( solution , containersFromAllDocuments , cancellationToken ) . ConfigureAwait ( false ) ;
@@ -222,9 +225,8 @@ public override async Task<Solution> ChangeNamespaceAsync(
222
225
223
226
foreach ( var documentId in documentIds )
224
227
{
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 ) ;
228
230
solutionAfterNamespaceChange = newSolution ;
229
231
referenceDocuments . AddRange ( refDocumentIds ) ;
230
232
}
@@ -463,8 +465,8 @@ private static SyntaxNode CreateImport(SyntaxGenerator syntaxGenerator, string n
463
465
}
464
466
}
465
467
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 ) ;
468
470
var solutionWithChangedNamespace = documentWithNewNamespace . Project . Solution ;
469
471
470
472
var refLocationsInSolution = refLocationsInOtherDocuments
@@ -501,15 +503,6 @@ private static SyntaxNode CreateImport(SyntaxGenerator syntaxGenerator, string n
501
503
return ( solutionWithFixedReferences , refLocationGroups . SelectAsArray ( g => g . Key ) ) ;
502
504
}
503
505
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
-
513
506
private static async Task < ImmutableArray < LocationForAffectedSymbol > > FindReferenceLocationsForSymbolAsync (
514
507
Document document , ISymbol symbol , CancellationToken cancellationToken )
515
508
{
@@ -631,9 +624,49 @@ private async Task<Document> FixDeclarationDocumentAsync(
631
624
var services = documentWithAddedImports . Project . Solution . Services ;
632
625
root = Formatter . Format ( root , Formatter . Annotation , services , documentOptions . FormattingOptions , cancellationToken ) ;
633
626
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
+
635
634
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
+ }
637
670
}
638
671
639
672
private static async Task < Document > FixReferencingDocumentAsync (
@@ -651,9 +684,8 @@ private static async Task<Document> FixReferencingDocumentAsync(
651
684
652
685
var newNamespaceParts = GetNamespaceParts ( newNamespace ) ;
653
686
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 ) ;
657
689
658
690
var documentOptions = await document . GetCodeCleanupOptionsAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
659
691
@@ -666,10 +698,24 @@ await FixReferencesAsync(document, changeNamespaceService, addImportService, ref
666
698
cancellationToken ) . ConfigureAwait ( false ) ;
667
699
668
700
// 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 ) ;
671
703
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 ) ;
673
719
}
674
720
675
721
/// <summary>
@@ -708,9 +754,7 @@ await FixReferencesAsync(document, changeNamespaceService, addImportService, ref
708
754
// it will be handled properly because it is one of the reference to the type symbol. Otherwise, we don't
709
755
// attempt to make a potential fix, and user might end up with errors as a result.
710
756
if ( refLoc . ReferenceLocation . Alias != null )
711
- {
712
757
continue ;
713
- }
714
758
715
759
// Other documents in the solution might have changed after we calculated those ReferenceLocation,
716
760
// 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
743
787
}
744
788
745
789
foreach ( var container in containers )
746
- {
747
790
editor . TrackNode ( container ) ;
748
- }
749
791
750
792
var fixedDocument = editor . GetChangedDocument ( ) ;
751
793
root = await fixedDocument . GetRequiredSyntaxRootAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
@@ -858,7 +900,7 @@ private SyntaxNodeSpanStartComparer()
858
900
{
859
901
}
860
902
861
- public static SyntaxNodeSpanStartComparer Instance { get ; } = new SyntaxNodeSpanStartComparer ( ) ;
903
+ public static SyntaxNodeSpanStartComparer Instance { get ; } = new ( ) ;
862
904
863
905
public int Compare ( SyntaxNode ? x , SyntaxNode ? y )
864
906
{
0 commit comments