@@ -30,43 +30,73 @@ namespace Microsoft.CodeAnalysis.CodeRefactorings;
30
30
internal sealed class CodeRefactoringService (
31
31
[ ImportMany ] IEnumerable < Lazy < CodeRefactoringProvider , CodeChangeProviderMetadata > > providers ) : ICodeRefactoringService
32
32
{
33
- private readonly Lazy < ImmutableDictionary < string , Lazy < ImmutableArray < CodeRefactoringProvider > > > > _lazyLanguageToProvidersMap = new Lazy < ImmutableDictionary < string , Lazy < ImmutableArray < CodeRefactoringProvider > > > > (
34
- ( ) =>
35
- ImmutableDictionary . CreateRange (
36
- DistributeLanguages ( providers )
37
- . GroupBy ( lz => lz . Metadata . Language )
38
- . Select ( grp => KeyValuePairUtil . Create (
39
- grp . Key ,
40
- new Lazy < ImmutableArray < CodeRefactoringProvider > > ( ( ) => [ .. ExtensionOrderer . Order ( grp ) . Select ( lz => lz . Value ) ] ) ) ) ) ) ;
33
+ private readonly Lazy < ImmutableDictionary < ProviderKey , Lazy < ImmutableArray < CodeRefactoringProvider > > > > _lazyLanguageDocumentToProvidersMap =
34
+ new Lazy < ImmutableDictionary < ProviderKey , Lazy < ImmutableArray < CodeRefactoringProvider > > > > ( ( ) =>
35
+ ImmutableDictionary . CreateRange (
36
+ DistributeLanguagesAndDocuments ( providers )
37
+ . GroupBy ( lz => new ProviderKey ( lz . Metadata . Language , lz . Metadata . DocumentKind , lz . Metadata . DocumentExtension ) )
38
+ . Select ( grp => KeyValuePairUtil . Create ( grp . Key ,
39
+ new Lazy < ImmutableArray < CodeRefactoringProvider > > ( ( ) => [ .. ExtensionOrderer . Order ( grp ) . Select ( lz => lz . Value ) ] ) ) ) ) ) ;
40
+
41
41
private readonly Lazy < ImmutableDictionary < CodeRefactoringProvider , CodeChangeProviderMetadata > > _lazyRefactoringToMetadataMap = new ( ( ) => providers . Where ( provider => provider . IsValueCreated ) . ToImmutableDictionary ( provider => provider . Value , provider => provider . Metadata ) ) ;
42
42
43
43
private ImmutableDictionary < CodeRefactoringProvider , FixAllProviderInfo ? > _fixAllProviderMap = ImmutableDictionary < CodeRefactoringProvider , FixAllProviderInfo ? > . Empty ;
44
44
45
- private static IEnumerable < Lazy < CodeRefactoringProvider , OrderableLanguageMetadata > > DistributeLanguages ( IEnumerable < Lazy < CodeRefactoringProvider , CodeChangeProviderMetadata > > providers )
45
+ private static IEnumerable < Lazy < CodeRefactoringProvider , OrderableLanguageDocumentMetadata > > DistributeLanguagesAndDocuments ( IEnumerable < Lazy < CodeRefactoringProvider , CodeChangeProviderMetadata > > providers )
46
46
{
47
47
foreach ( var provider in providers )
48
48
{
49
49
foreach ( var language in provider . Metadata . Languages )
50
50
{
51
- var orderable = new OrderableLanguageMetadata (
52
- provider . Metadata . Name , language , provider . Metadata . AfterTyped , provider . Metadata . BeforeTyped ) ;
53
- yield return new Lazy < CodeRefactoringProvider , OrderableLanguageMetadata > ( ( ) => provider . Value , orderable ) ;
51
+ foreach ( var documentKind in provider . Metadata . DocumentKinds )
52
+ {
53
+ // Document kinds come from ExportCodeRefactoringProviderAttribute which throws
54
+ // if values do not match enum TextDocumentKind. Here we'll throw too.
55
+ var kind = ( TextDocumentKind ) Enum . Parse ( typeof ( TextDocumentKind ) , documentKind , ignoreCase : true ) ;
56
+ var documentExtensions = provider . Metadata . DocumentExtensions . Where ( e => ! string . IsNullOrEmpty ( e ) ) ;
57
+ if ( ! documentExtensions . Any ( ) )
58
+ {
59
+ // Metadata without file extension applies to all files.
60
+ documentExtensions = [ "" ] ;
61
+ }
62
+
63
+ foreach ( var documentExtension in documentExtensions )
64
+ {
65
+ var orderable = new OrderableLanguageDocumentMetadata (
66
+ provider . Metadata . Name ?? "" , language , kind , documentExtension , provider . Metadata . AfterTyped , provider . Metadata . BeforeTyped ) ;
67
+ yield return new Lazy < CodeRefactoringProvider , OrderableLanguageDocumentMetadata > ( ( ) => provider . Value , orderable ) ;
68
+ }
69
+ }
54
70
}
55
71
}
56
72
}
57
73
58
- private ImmutableDictionary < string , Lazy < ImmutableArray < CodeRefactoringProvider > > > LanguageToProvidersMap
59
- => _lazyLanguageToProvidersMap . Value ;
74
+ private ImmutableDictionary < ProviderKey , Lazy < ImmutableArray < CodeRefactoringProvider > > > LanguageDocumentToProvidersMap
75
+ => _lazyLanguageDocumentToProvidersMap . Value ;
60
76
61
77
private ImmutableDictionary < CodeRefactoringProvider , CodeChangeProviderMetadata > RefactoringToMetadataMap
62
78
=> _lazyRefactoringToMetadataMap . Value ;
63
79
64
- private ConcatImmutableArray < CodeRefactoringProvider > GetProviders ( TextDocument document )
80
+ internal ConcatImmutableArray < CodeRefactoringProvider > GetProviders ( TextDocument document )
65
81
{
66
82
var allRefactorings = ImmutableArray < CodeRefactoringProvider > . Empty ;
67
- if ( LanguageToProvidersMap . TryGetValue ( document . Project . Language , out var lazyProviders ) )
83
+
84
+ // Include providers which apply to all extensions
85
+ var key = new ProviderKey ( document . Project . Language , document . Kind , "" ) ;
86
+ if ( LanguageDocumentToProvidersMap . TryGetValue ( key , out var lazyProviders ) )
87
+ {
88
+ allRefactorings = lazyProviders . Value ;
89
+ }
90
+
91
+ // Get providers for specific combination of language, doc kind and extension,
92
+ // e.g. (C#, AdditionalDocument, .xaml)
93
+ if ( FileNameUtilities . GetExtension ( document . FilePath ) is string documentExtension && documentExtension . Length > 0 )
68
94
{
69
- allRefactorings = ProjectCodeRefactoringProvider . FilterExtensions ( document , lazyProviders . Value , GetExtensionInfo ) ;
95
+ key = new ProviderKey ( document . Project . Language , document . Kind , documentExtension ) ;
96
+ if ( LanguageDocumentToProvidersMap . TryGetValue ( key , out lazyProviders ) )
97
+ {
98
+ allRefactorings = allRefactorings . Concat ( lazyProviders . Value ) ;
99
+ }
70
100
}
71
101
72
102
return allRefactorings . ConcatFast ( GetProjectRefactorings ( document ) ) ;
@@ -271,4 +301,24 @@ protected override bool TryGetExtensionsFromReference(AnalyzerReference referenc
271
301
return false ;
272
302
}
273
303
}
304
+
305
+ private record struct ProviderKey ( string Language , TextDocumentKind DocumentKind , string DocumentExtension ) : IEquatable < ProviderKey >
306
+ {
307
+ public bool Equals ( ProviderKey other )
308
+ {
309
+ // We create keys from two sources:
310
+ // * MEF's ExportCodeRefactoringProviderAttribute when building the map for available providers.
311
+ // * TextDocument when looking up the map for available providers.
312
+ // Text documents can point to files with different extensions, e.g. MyPage.xaml and MyControl.XAML.
313
+ // Thus we need case insensitive comparison for DocumentExtension.
314
+ return Language == other . Language &&
315
+ DocumentKind == other . DocumentKind &&
316
+ StringComparer . OrdinalIgnoreCase . Equals ( DocumentExtension , other . DocumentExtension ) ;
317
+ }
318
+
319
+ public override int GetHashCode ( )
320
+ {
321
+ return ( Language , DocumentKind , StringComparer . OrdinalIgnoreCase . GetHashCode ( DocumentExtension ) ) . GetHashCode ( ) ;
322
+ }
323
+ }
274
324
}
0 commit comments