diff --git a/src/Microsoft.CodeQuality.Analyzers/Core/ApiDesignGuidelines/IdentifiersShouldHaveCorrectSuffix.cs b/src/Microsoft.CodeQuality.Analyzers/Core/ApiDesignGuidelines/IdentifiersShouldHaveCorrectSuffix.cs index 11d959783e..15d41e7d91 100644 --- a/src/Microsoft.CodeQuality.Analyzers/Core/ApiDesignGuidelines/IdentifiersShouldHaveCorrectSuffix.cs +++ b/src/Microsoft.CodeQuality.Analyzers/Core/ApiDesignGuidelines/IdentifiersShouldHaveCorrectSuffix.cs @@ -147,16 +147,23 @@ private static void AnalyzeCompilationStart(CompilationStartAnalysisContext cont } , SymbolKind.NamedType); - context.RegisterSymbolAction((saContext) => + var eventArgsType = WellKnownTypes.EventArgs(context.Compilation); + if (eventArgsType != null) { - const string eventHandlerString = "EventHandler"; - var eventSymbol = saContext.Symbol as IEventSymbol; - if (!eventSymbol.Type.Name.EndsWith(eventHandlerString, StringComparison.Ordinal)) + context.RegisterSymbolAction((saContext) => { - saContext.ReportDiagnostic(eventSymbol.CreateDiagnostic(DefaultRule, eventSymbol.Type.Name, eventHandlerString)); - } - }, - SymbolKind.Event); + const string eventHandlerString = "EventHandler"; + var eventSymbol = (IEventSymbol)saContext.Symbol; + if (!eventSymbol.Type.Name.EndsWith(eventHandlerString, StringComparison.Ordinal) && + eventSymbol.Type.IsInSource() && + eventSymbol.Type.TypeKind == TypeKind.Delegate && + ((INamedTypeSymbol)eventSymbol.Type).DelegateInvokeMethod?.HasEventHandlerSignature(eventArgsType) == true) + { + saContext.ReportDiagnostic(eventSymbol.CreateDiagnostic(DefaultRule, eventSymbol.Type.Name, eventHandlerString)); + } + }, + SymbolKind.Event); + } } } } diff --git a/src/Microsoft.CodeQuality.Analyzers/UnitTests/ApiDesignGuidelines/IdentifiersShouldHaveCorrectSuffixTests.cs b/src/Microsoft.CodeQuality.Analyzers/UnitTests/ApiDesignGuidelines/IdentifiersShouldHaveCorrectSuffixTests.cs index a6d2f0a2ea..72be63b886 100644 --- a/src/Microsoft.CodeQuality.Analyzers/UnitTests/ApiDesignGuidelines/IdentifiersShouldHaveCorrectSuffixTests.cs +++ b/src/Microsoft.CodeQuality.Analyzers/UnitTests/ApiDesignGuidelines/IdentifiersShouldHaveCorrectSuffixTests.cs @@ -1079,6 +1079,31 @@ Inherits Attribute End Class"); } + [Fact, WorkItem(1822, "https://github.com/dotnet/roslyn-analyzers/issues/1822")] + public void CA1710_SystemAction_CSharp() + { + VerifyCSharp(@" +using System; + +public class C +{ + public event Action MyEvent; +}"); + } + + [Fact, WorkItem(1822, "https://github.com/dotnet/roslyn-analyzers/issues/1822")] + public void CA1710_CustomDelegate_CSharp() + { + VerifyCSharp(@" +using System; + +public class C +{ + public delegate void MyDelegate(int param); + public event MyDelegate MyEvent; +}"); + } + private static DiagnosticResult GetCA1710BasicResultAt(int line, int column, string symbolName, string replacementName, bool isSpecial = false) { return GetBasicResultAt( diff --git a/src/Utilities/Extensions/IMethodSymbolExtensions.cs b/src/Utilities/Extensions/IMethodSymbolExtensions.cs index 26533aba9b..bd66bfb61d 100644 --- a/src/Utilities/Extensions/IMethodSymbolExtensions.cs +++ b/src/Utilities/Extensions/IMethodSymbolExtensions.cs @@ -382,5 +382,16 @@ public static int GetParameterIndex(this IMethodSymbol methodSymbol, IParameterS throw new ArgumentException("Invalid paramater", nameof(parameterSymbol)); } + + /// + /// Returns true for void returning methods with two parameters, where + /// the first parameter is of type and the second + /// parameter inherits from or equals type. + /// + public static bool HasEventHandlerSignature(this IMethodSymbol method, INamedTypeSymbol eventArgsType) + => eventArgsType != null && + method.Parameters.Length == 2 && + method.Parameters[0].Type.SpecialType == SpecialType.System_Object && + method.Parameters[1].Type.DerivesFrom(eventArgsType, baseTypesOnly: true); } } diff --git a/src/Utilities/Extensions/ISymbolExtensions.cs b/src/Utilities/Extensions/ISymbolExtensions.cs index 8d29dd2313..acb79ff834 100644 --- a/src/Utilities/Extensions/ISymbolExtensions.cs +++ b/src/Utilities/Extensions/ISymbolExtensions.cs @@ -552,5 +552,13 @@ public static bool HasAttribute(this ISymbol symbol, INamedTypeSymbol attribute) { return symbol.GetAttributes().Any(attr => attr.AttributeClass.Equals(attribute)); } + + /// + /// Indicates if a symbol has at least one location in source. + /// + public static bool IsInSource(this ISymbol symbol) + { + return symbol.Locations.Any(l => l.IsInSource); + } } }