77using System ;
88using System . Collections . Generic ;
99using System . Collections . Immutable ;
10- using System . Composition ;
1110using System . Linq ;
1211using System . Threading ;
1312using System . Threading . Tasks ;
@@ -97,33 +96,33 @@ public async Task TestGetFixesAsyncWithDuplicateDiagnostics()
9796 public async Task TestGetCodeFixWithExceptionInRegisterMethod ( )
9897 {
9998 await GetFirstDiagnosticWithFixAsync ( new ErrorCases . ExceptionInRegisterMethod ( ) ) ;
100- await GetAddedFixesAsync ( new ErrorCases . ExceptionInRegisterMethod ( ) ) ;
99+ await GetAddedFixesWithExceptionValidationAsync ( new ErrorCases . ExceptionInRegisterMethod ( ) ) ;
101100 }
102101
103102 [ Fact ]
104103 public async Task TestGetCodeFixWithExceptionInRegisterMethodAsync ( )
105104 {
106105 await GetFirstDiagnosticWithFixAsync ( new ErrorCases . ExceptionInRegisterMethodAsync ( ) ) ;
107- await GetAddedFixesAsync ( new ErrorCases . ExceptionInRegisterMethodAsync ( ) ) ;
106+ await GetAddedFixesWithExceptionValidationAsync ( new ErrorCases . ExceptionInRegisterMethodAsync ( ) ) ;
108107 }
109108
110109 [ Fact ]
111110 public async Task TestGetCodeFixWithExceptionInFixableDiagnosticIds ( )
112111 {
113112 await GetDefaultFixesAsync ( new ErrorCases . ExceptionInFixableDiagnosticIds ( ) ) ;
114- await GetAddedFixesAsync ( new ErrorCases . ExceptionInFixableDiagnosticIds ( ) ) ;
113+ await GetAddedFixesWithExceptionValidationAsync ( new ErrorCases . ExceptionInFixableDiagnosticIds ( ) ) ;
115114 }
116115
117116 [ Fact ( Skip = "https://github.com/dotnet/roslyn/issues/21533" ) ]
118117 public async Task TestGetCodeFixWithExceptionInFixableDiagnosticIds2 ( )
119118 {
120119 await GetDefaultFixesAsync ( new ErrorCases . ExceptionInFixableDiagnosticIds2 ( ) ) ;
121- await GetAddedFixesAsync ( new ErrorCases . ExceptionInFixableDiagnosticIds2 ( ) ) ;
120+ await GetAddedFixesWithExceptionValidationAsync ( new ErrorCases . ExceptionInFixableDiagnosticIds2 ( ) ) ;
122121 }
123122
124123 [ Fact ]
125124 public async Task TestGetCodeFixWithExceptionInGetFixAllProvider ( )
126- => await GetAddedFixesAsync ( new ErrorCases . ExceptionInGetFixAllProvider ( ) ) ;
125+ => await GetAddedFixesWithExceptionValidationAsync ( new ErrorCases . ExceptionInGetFixAllProvider ( ) ) ;
127126
128127 private async Task GetDefaultFixesAsync ( CodeFixProvider codefix )
129128 {
@@ -136,7 +135,10 @@ private async Task GetDefaultFixesAsync(CodeFixProvider codefix)
136135 Assert . True ( ( ( TestErrorLogger ) tuple . errorLogger ) . Messages . TryGetValue ( codefix . GetType ( ) . Name , out var message ) ) ;
137136 }
138137
139- private async Task GetAddedFixesAsync ( CodeFixProvider codefix )
138+ private Task < ImmutableArray < CodeFixCollection > > GetAddedFixesWithExceptionValidationAsync ( CodeFixProvider codefix )
139+ => GetAddedFixesAsync ( codefix , diagnosticAnalyzer : new MockAnalyzerReference . MockDiagnosticAnalyzer ( ) , exception : true ) ;
140+
141+ private async Task < ImmutableArray < CodeFixCollection > > GetAddedFixesAsync ( CodeFixProvider codefix , DiagnosticAnalyzer diagnosticAnalyzer , bool exception = false )
140142 {
141143 var tuple = ServiceSetup ( codefix ) ;
142144
@@ -145,13 +147,18 @@ private async Task GetAddedFixesAsync(CodeFixProvider codefix)
145147 GetDocumentAndExtensionManager ( tuple . analyzerService , workspace , out var document , out var extensionManager ) ;
146148 var incrementalAnalyzer = ( IIncrementalAnalyzerProvider ) tuple . analyzerService ;
147149 var analyzer = incrementalAnalyzer . CreateIncrementalAnalyzer ( workspace ) ;
148- var reference = new MockAnalyzerReference ( codefix ) ;
150+ var reference = new MockAnalyzerReference ( codefix , ImmutableArray . Create ( diagnosticAnalyzer ) ) ;
149151 var project = workspace . CurrentSolution . Projects . Single ( ) . AddAnalyzerReference ( reference ) ;
150152 document = project . Documents . Single ( ) ;
151153 var fixes = await tuple . codeFixService . GetFixesAsync ( document , TextSpan . FromBounds ( 0 , 0 ) , includeConfigurationFixes : true , cancellationToken : CancellationToken . None ) ;
152154
153- Assert . True ( extensionManager . IsDisabled ( codefix ) ) ;
154- Assert . False ( extensionManager . IsIgnored ( codefix ) ) ;
155+ if ( exception )
156+ {
157+ Assert . True ( extensionManager . IsDisabled ( codefix ) ) ;
158+ Assert . False ( extensionManager . IsIgnored ( codefix ) ) ;
159+ }
160+
161+ return fixes ;
155162 }
156163
157164 private async Task GetFirstDiagnosticWithFixAsync ( CodeFixProvider codefix )
@@ -527,5 +534,52 @@ public override Task RegisterCodeFixesAsync(CodeFixContext context)
527534 return Task . CompletedTask ;
528535 }
529536 }
537+
538+ [ Theory , WorkItem ( 44553 , "https://github.com/dotnet/roslyn/issues/44553" ) ]
539+ [ InlineData ( null ) ]
540+ [ InlineData ( "CodeFixProviderWithDuplicateEquivalenceKeyActions" ) ]
541+ public async Task TestRegisteredCodeActionsWithSameEquivalenceKey ( string ? equivalenceKey )
542+ {
543+ var diagnosticId = "ID1" ;
544+ var analyzer = new MockAnalyzerReference . MockDiagnosticAnalyzer ( ImmutableArray . Create ( diagnosticId ) ) ;
545+ var fixer = new CodeFixProviderWithDuplicateEquivalenceKeyActions ( diagnosticId , equivalenceKey ) ;
546+
547+ // Verify multiple code actions registered with same equivalence key are not de-duped.
548+ var fixes = ( await GetAddedFixesAsync ( fixer , analyzer ) ) . SelectMany ( fixCollection => fixCollection . Fixes ) . ToList ( ) ;
549+ Assert . Equal ( 2 , fixes . Count ) ;
550+ }
551+
552+ private sealed class CodeFixProviderWithDuplicateEquivalenceKeyActions : CodeFixProvider
553+ {
554+ private readonly string _diagnosticId ;
555+ private readonly string ? _equivalenceKey ;
556+
557+ public CodeFixProviderWithDuplicateEquivalenceKeyActions ( string diagnosticId , string ? equivalenceKey )
558+ {
559+ _diagnosticId = diagnosticId ;
560+ _equivalenceKey = equivalenceKey ;
561+ }
562+
563+ public override ImmutableArray < string > FixableDiagnosticIds => ImmutableArray . Create ( _diagnosticId ) ;
564+
565+ public override Task RegisterCodeFixesAsync ( CodeFixContext context )
566+ {
567+ // Register duplicate code actions with same equivalence key, but different title.
568+ RegisterCodeFix ( context , titleSuffix : "1" ) ;
569+ RegisterCodeFix ( context , titleSuffix : "2" ) ;
570+
571+ return Task . CompletedTask ;
572+ }
573+
574+ private void RegisterCodeFix ( CodeFixContext context , string titleSuffix )
575+ {
576+ context . RegisterCodeFix (
577+ CodeAction . Create (
578+ nameof ( CodeFixProviderWithDuplicateEquivalenceKeyActions ) + titleSuffix ,
579+ ct => Task . FromResult ( context . Document ) ,
580+ _equivalenceKey ) ,
581+ context . Diagnostics ) ;
582+ }
583+ }
530584 }
531585}
0 commit comments