diff --git a/src/Meziantou.Analyzer.CodeFixers/Rules/UseEventArgsEmptyFixer.cs b/src/Meziantou.Analyzer.CodeFixers/Rules/UseEventArgsEmptyFixer.cs index 9fe0201d9..e9d82fd84 100644 --- a/src/Meziantou.Analyzer.CodeFixers/Rules/UseEventArgsEmptyFixer.cs +++ b/src/Meziantou.Analyzer.CodeFixers/Rules/UseEventArgsEmptyFixer.cs @@ -13,28 +13,29 @@ public override FixAllProvider GetFixAllProvider() public override async Task RegisterCodeFixesAsync(CodeFixContext context) { var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); - var nodeToFix = root?.FindNode(context.Span, getInnermostNodeForTie: true); - if (nodeToFix is null) + if (root?.FindNode(context.Span, getInnermostNodeForTie: true) is not ExpressionSyntax nodeToFix) + return; + + var semanticModel = await context.Document.GetSemanticModelAsync(context.CancellationToken).ConfigureAwait(false); + if (semanticModel?.Compilation.GetTypeByMetadataName("System.EventArgs") is not { } eventArgsSymbol) + return; + + // The parameter can be typed with a type parameter constrained to EventArgs: Invoke(this, null) cannot become Invoke(this, EventArgs.Empty) + if (semanticModel.GetTypeInfo(nodeToFix, context.CancellationToken).ConvertedType is { } convertedType && !semanticModel.Compilation.ClassifyConversion(eventArgsSymbol, convertedType).IsImplicit) return; context.RegisterCodeFix( CodeAction.Create( "Use EventArgs.Empty", - ct => Fix(context, nodeToFix, ct), + ct => Fix(context.Document, nodeToFix, eventArgsSymbol, ct), equivalenceKey: "Use EventArgs.Empty"), context.Diagnostics); } - private static async Task Fix(CodeFixContext context, SyntaxNode nodeToFix, CancellationToken cancellationToken) + private static async Task Fix(Document document, ExpressionSyntax nodeToFix, INamedTypeSymbol eventArgsSymbol, CancellationToken cancellationToken) { - var editor = await DocumentEditor.CreateAsync(context.Document, cancellationToken).ConfigureAwait(false); - var generator = editor.Generator; - - var typeSymbol = editor.SemanticModel.Compilation.GetTypeByMetadataName("System.EventArgs"); - if (typeSymbol is null) - return context.Document; - - var newExpression = generator.TypeMemberAccessExpression(typeSymbol, nameof(EventArgs.Empty)); + var editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false); + var newExpression = editor.Generator.TypeMemberAccessExpression(eventArgsSymbol, nameof(EventArgs.Empty)); editor.ReplaceNode(nodeToFix, newExpression); return editor.GetChangedDocument(); } diff --git a/tests/Meziantou.Analyzer.Test/Rules/EventsShouldHaveProperArgumentsAnalyzerTests.cs b/tests/Meziantou.Analyzer.Test/Rules/EventsShouldHaveProperArgumentsAnalyzerTests.cs index 9635b6346..6ef9c3381 100755 --- a/tests/Meziantou.Analyzer.Test/Rules/EventsShouldHaveProperArgumentsAnalyzerTests.cs +++ b/tests/Meziantou.Analyzer.Test/Rules/EventsShouldHaveProperArgumentsAnalyzerTests.cs @@ -405,4 +405,26 @@ void OnEvent() return test.RunAsync(); } + + [Fact] + public Task InvalidEventArgs_GenericEventArgs_NoCodeFix() + { + const string Source = """ + using System; + delegate void CustomEventHandler(object sender, TEventArgs e) where TEventArgs : EventArgs; + class Test where TEventArgs : EventArgs + { + public event CustomEventHandler MyEvent; + void OnEvent() + { + MyEvent?.Invoke(this, {|MA0093:null|}); + } + } + """; + var test = CreateEventArgsFixTest(); + test.TestCode = Source; + test.FixedCode = Source; + + return test.RunAsync(); + } }