diff --git a/README.md b/README.md
index 75a8470df..74e813b82 100755
--- a/README.md
+++ b/README.md
@@ -194,6 +194,7 @@ If you are already using other analyzers, you can check [which rules are duplica
|[MA0176](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0176.md)|Performance|Optimize guid creation|ℹ️|✔️|✔️|
|[MA0177](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0177.md)|Style|Use single-line XML comment syntax when possible|ℹ️|❌|✔️|
|[MA0178](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0178.md)|Design|Use TimeSpan.Zero instead of TimeSpan.FromXXX(0)|ℹ️|✔️|✔️|
+|[MA0179](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0179.md)|Performance|Use Attribute.IsDefined instead of GetCustomAttribute(s)|ℹ️|✔️|✔️|
diff --git a/docs/README.md b/docs/README.md
index ea087d645..2128a625f 100755
--- a/docs/README.md
+++ b/docs/README.md
@@ -178,6 +178,7 @@
|[MA0176](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0176.md)|Performance|Optimize guid creation|ℹ️|✔️|✔️|
|[MA0177](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0177.md)|Style|Use single-line XML comment syntax when possible|ℹ️|❌|✔️|
|[MA0178](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0178.md)|Design|Use TimeSpan.Zero instead of TimeSpan.FromXXX(0)|ℹ️|✔️|✔️|
+|[MA0179](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0179.md)|Performance|Use Attribute.IsDefined instead of GetCustomAttribute(s)|ℹ️|✔️|✔️|
|Id|Suppressed rule|Justification|
|--|---------------|-------------|
@@ -720,6 +721,9 @@ dotnet_diagnostic.MA0177.severity = none
# MA0178: Use TimeSpan.Zero instead of TimeSpan.FromXXX(0)
dotnet_diagnostic.MA0178.severity = suggestion
+
+# MA0179: Use Attribute.IsDefined instead of GetCustomAttribute(s)
+dotnet_diagnostic.MA0179.severity = suggestion
```
# .editorconfig - all rules disabled
@@ -1255,4 +1259,7 @@ dotnet_diagnostic.MA0177.severity = none
# MA0178: Use TimeSpan.Zero instead of TimeSpan.FromXXX(0)
dotnet_diagnostic.MA0178.severity = none
+
+# MA0179: Use Attribute.IsDefined instead of GetCustomAttribute(s)
+dotnet_diagnostic.MA0179.severity = none
```
diff --git a/docs/Rules/MA0179.md b/docs/Rules/MA0179.md
new file mode 100644
index 000000000..3f66faf49
--- /dev/null
+++ b/docs/Rules/MA0179.md
@@ -0,0 +1,42 @@
+# MA0179 - Use Attribute.IsDefined instead of GetCustomAttribute(s)
+
+Sources: [UseAttributeIsDefinedAnalyzer.cs](https://github.com/meziantou/Meziantou.Analyzer/blob/main/src/Meziantou.Analyzer/Rules/UseAttributeIsDefinedAnalyzer.cs), [UseAttributeIsDefinedFixer.cs](https://github.com/meziantou/Meziantou.Analyzer/blob/main/src/Meziantou.Analyzer.CodeFixers/Rules/UseAttributeIsDefinedFixer.cs)
+
+
+`Attribute.IsDefined` is more efficient than `GetCustomAttribute()` when you only need to check if an attribute exists on a member, type, assembly, or module. The method avoids allocating the attribute instance and is optimized for simple existence checks.
+
+````csharp
+using System;
+using System.Reflection;
+
+// non-compliant
+if (type.GetCustomAttribute() != null) { }
+if (type.GetCustomAttribute() == null) { }
+if (type.GetCustomAttribute() is null) { }
+if (type.GetCustomAttribute() is not null) { }
+if (member.GetCustomAttributes().Any()) { }
+if (member.GetCustomAttributes().Count() > 0) { }
+if (member.GetCustomAttributes().Length > 0) { }
+if (member.GetCustomAttributes().Length == 0) { }
+
+// compliant
+if (Attribute.IsDefined(type, typeof(ObsoleteAttribute))) { }
+if (!Attribute.IsDefined(type, typeof(ObsoleteAttribute))) { }
+if (!Attribute.IsDefined(type, typeof(ObsoleteAttribute))) { }
+if (Attribute.IsDefined(type, typeof(ObsoleteAttribute))) { }
+if (Attribute.IsDefined(member, typeof(ObsoleteAttribute))) { }
+if (Attribute.IsDefined(member, typeof(ObsoleteAttribute))) { }
+if (Attribute.IsDefined(member, typeof(ObsoleteAttribute))) { }
+if (!Attribute.IsDefined(member, typeof(ObsoleteAttribute))) { }
+
+// compliant - with predicate (not detected)
+if (member.GetCustomAttributes().Any(a => a.Message != null)) { }
+if (member.GetCustomAttributes().Count(a => a.Message != null) > 0) { }
+
+// compliant - accessing attribute properties
+var attr = type.GetCustomAttribute();
+if (attr != null)
+{
+ _ = attr.Message;
+}
+````
diff --git a/src/Meziantou.Analyzer.CodeFixers/Rules/UseAttributeIsDefinedFixer.cs b/src/Meziantou.Analyzer.CodeFixers/Rules/UseAttributeIsDefinedFixer.cs
new file mode 100644
index 000000000..b2b71fb09
--- /dev/null
+++ b/src/Meziantou.Analyzer.CodeFixers/Rules/UseAttributeIsDefinedFixer.cs
@@ -0,0 +1,319 @@
+using System.Collections.Immutable;
+using System.Composition;
+using Meziantou.Analyzer.Internals;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CodeActions;
+using Microsoft.CodeAnalysis.CodeFixes;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Microsoft.CodeAnalysis.Editing;
+using Microsoft.CodeAnalysis.Operations;
+
+namespace Meziantou.Analyzer.Rules;
+
+[ExportCodeFixProvider(LanguageNames.CSharp), Shared]
+public sealed class UseAttributeIsDefinedFixer : CodeFixProvider
+{
+ private const string EnumerableAnyMethodDocId = "M:System.Linq.Enumerable.Any``1(System.Collections.Generic.IEnumerable{``0})";
+ private const string EnumerableCountMethodDocId = "M:System.Linq.Enumerable.Count``1(System.Collections.Generic.IEnumerable{``0})";
+
+ public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(RuleIdentifiers.UseAttributeIsDefined);
+
+ public override FixAllProvider GetFixAllProvider()
+ {
+ return WellKnownFixAllProviders.BatchFixer;
+ }
+
+ 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)
+ return;
+
+ var title = "Use Attribute.IsDefined";
+ var codeAction = CodeAction.Create(
+ title,
+ ct => ReplaceWithAttributeIsDefined(context.Document, nodeToFix, ct),
+ equivalenceKey: title);
+
+ context.RegisterCodeFix(codeAction, context.Diagnostics);
+ }
+
+ private static async Task ReplaceWithAttributeIsDefined(Document document, SyntaxNode nodeToFix, CancellationToken cancellationToken)
+ {
+ var editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false);
+ var semanticModel = editor.SemanticModel;
+ var generator = editor.Generator;
+
+ var operation = semanticModel.GetOperation(nodeToFix, cancellationToken);
+ if (operation is null)
+ return document;
+
+ SyntaxNode? replacement = null;
+
+ if (operation is IBinaryOperation binaryOperation)
+ {
+ var negate = binaryOperation.OperatorKind == BinaryOperatorKind.Equals;
+ var invocation = GetGetCustomAttributeInvocation(binaryOperation.LeftOperand) ?? GetGetCustomAttributeInvocation(binaryOperation.RightOperand);
+ if (invocation is not null)
+ {
+ replacement = CreateAttributeIsDefinedInvocation(generator, semanticModel, invocation, negate);
+ }
+ else
+ {
+ // Check for GetCustomAttributes().Length comparisons
+ var lengthInvocation = GetGetCustomAttributesLengthInvocation(binaryOperation.LeftOperand, out var isLeftSide);
+ if (lengthInvocation is null)
+ {
+ lengthInvocation = GetGetCustomAttributesLengthInvocation(binaryOperation.RightOperand, out isLeftSide);
+ isLeftSide = !isLeftSide; // If found on right, flip the comparison perspective
+ }
+
+ if (lengthInvocation is not null)
+ {
+ var negateLength = ShouldNegateLengthComparison(binaryOperation, isLeftSide);
+ replacement = CreateAttributeIsDefinedInvocation(generator, semanticModel, lengthInvocation, negateLength);
+ }
+ else
+ {
+ // Check for GetCustomAttributes().Count() comparisons
+ var countInvocation = GetGetCustomAttributesCountInvocation(semanticModel, binaryOperation.LeftOperand, out var foundOnLeft);
+ var countIsOnLeft = foundOnLeft;
+ if (countInvocation is null)
+ {
+ countInvocation = GetGetCustomAttributesCountInvocation(semanticModel, binaryOperation.RightOperand, out var foundOnRight);
+ if (foundOnRight)
+ {
+ countIsOnLeft = false; // Count is on right side
+ }
+ }
+
+ if (countInvocation is not null)
+ {
+ var negateCount = ShouldNegateLengthComparison(binaryOperation, countIsOnLeft);
+ replacement = CreateAttributeIsDefinedInvocation(generator, semanticModel, countInvocation, negateCount);
+ }
+ }
+ }
+ }
+ else if (operation is IIsPatternOperation isPatternOperation)
+ {
+ var negate = isPatternOperation.Pattern is IConstantPatternOperation;
+ var invocation = GetGetCustomAttributeInvocation(isPatternOperation.Value);
+ if (invocation is not null)
+ {
+ replacement = CreateAttributeIsDefinedInvocation(generator, semanticModel, invocation, negate);
+ }
+ }
+ else if (operation is IInvocationOperation invocationOperation)
+ {
+ // Check if this is the Any(IEnumerable) method
+ var enumerableAnyMethod = DocumentationCommentId.GetFirstSymbolForDeclarationId(EnumerableAnyMethodDocId, semanticModel.Compilation) as IMethodSymbol;
+ if (enumerableAnyMethod is not null &&
+ SymbolEqualityComparer.Default.Equals(invocationOperation.TargetMethod.OriginalDefinition, enumerableAnyMethod) &&
+ invocationOperation.Arguments.Length == 1 &&
+ invocationOperation.Arguments[0].Value is IInvocationOperation getCustomAttributesInvocation)
+ {
+ replacement = CreateAttributeIsDefinedInvocation(generator, semanticModel, getCustomAttributesInvocation, negate: false);
+ }
+ }
+
+ if (replacement is not null)
+ {
+ editor.ReplaceNode(nodeToFix, replacement.WithTriviaFrom(nodeToFix));
+ return editor.GetChangedDocument();
+ }
+
+ return document;
+ }
+
+ private static IInvocationOperation? GetGetCustomAttributeInvocation(IOperation operation)
+ {
+ if (operation.UnwrapConversionOperations() is IInvocationOperation invocation &&
+ (invocation.TargetMethod.Name == "GetCustomAttribute" || invocation.TargetMethod.Name == "GetCustomAttributes"))
+ {
+ return invocation;
+ }
+
+ return null;
+ }
+
+ private static IInvocationOperation? GetGetCustomAttributesLengthInvocation(IOperation operation, out bool isFound)
+ {
+ isFound = false;
+ if (operation is IPropertyReferenceOperation propertyReference &&
+ propertyReference.Property.Name == "Length" &&
+ propertyReference.Instance is IInvocationOperation invocation &&
+ invocation.TargetMethod.Name == "GetCustomAttributes")
+ {
+ isFound = true;
+ return invocation;
+ }
+
+ return null;
+ }
+
+ private static IInvocationOperation? GetGetCustomAttributesCountInvocation(SemanticModel semanticModel, IOperation operation, out bool isFound)
+ {
+ isFound = false;
+ if (operation is not IInvocationOperation countInvocation)
+ return null;
+
+ // Check if this is the specific Count(IEnumerable) method
+ var enumerableCountMethod = DocumentationCommentId.GetFirstSymbolForDeclarationId(EnumerableCountMethodDocId, semanticModel.Compilation) as IMethodSymbol;
+ if (enumerableCountMethod is null ||
+ !SymbolEqualityComparer.Default.Equals(countInvocation.TargetMethod.OriginalDefinition, enumerableCountMethod))
+ return null;
+
+ if (countInvocation.Arguments.Length != 1)
+ return null;
+
+ if (countInvocation.Arguments[0].Value is not IInvocationOperation invocation)
+ return null;
+
+ if (invocation.TargetMethod.Name != "GetCustomAttributes")
+ return null;
+
+ isFound = true;
+ return invocation;
+ }
+
+ private static bool ShouldNegateLengthComparison(IBinaryOperation operation, bool lengthIsOnLeft)
+ {
+ var otherOperand = lengthIsOnLeft ? operation.RightOperand : operation.LeftOperand;
+ if (otherOperand.ConstantValue is not { HasValue: true, Value: int value })
+ return false;
+
+ // Determine if we should negate based on operator and compared value
+ // Patterns that mean "has attributes" -> IsDefined (no negation):
+ // length > 0, length >= 1, length != 0, 0 < length, 1 <= length, 0 != length
+ // Patterns that mean "no attributes" -> !IsDefined (negate):
+ // length == 0, length <= 0, length < 1, 0 == length, 0 >= length, 1 > length
+
+ if (lengthIsOnLeft)
+ {
+ return operation.OperatorKind switch
+ {
+ BinaryOperatorKind.Equals when value == 0 => true, // length == 0 -> !IsDefined
+ BinaryOperatorKind.NotEquals when value == 0 => false, // length != 0 -> IsDefined
+ BinaryOperatorKind.GreaterThan when value == 0 => false, // length > 0 -> IsDefined
+ BinaryOperatorKind.GreaterThanOrEqual when value == 1 => false, // length >= 1 -> IsDefined
+ BinaryOperatorKind.LessThan when value == 1 => true, // length < 1 -> !IsDefined
+ BinaryOperatorKind.LessThanOrEqual when value == 0 => true, // length <= 0 -> !IsDefined
+ _ => false,
+ };
+ }
+ else
+ {
+ // When length is on the right: reverse the operator logic
+ return operation.OperatorKind switch
+ {
+ BinaryOperatorKind.Equals when value == 0 => true, // 0 == length -> !IsDefined
+ BinaryOperatorKind.NotEquals when value == 0 => false, // 0 != length -> IsDefined
+ BinaryOperatorKind.LessThan when value == 0 => false, // 0 < length (length > 0) -> IsDefined
+ BinaryOperatorKind.LessThanOrEqual when value == 1 => false, // 1 <= length (length >= 1) -> IsDefined
+ BinaryOperatorKind.GreaterThan when value == 1 => true, // 1 > length (length < 1) -> !IsDefined
+ BinaryOperatorKind.GreaterThanOrEqual when value == 0 => true, // 0 >= length (length <= 0) -> !IsDefined
+ _ => false,
+ };
+ }
+ }
+
+ private static SyntaxNode CreateAttributeIsDefinedInvocation(SyntaxGenerator generator, SemanticModel semanticModel, IInvocationOperation invocation, bool negate)
+ {
+ var attributeTypeSymbol = semanticModel.Compilation.GetBestTypeByMetadataName("System.Attribute");
+ var systemTypeSymbol = semanticModel.Compilation.GetBestTypeByMetadataName("System.Type");
+ var attributeTypeSyntax = generator.TypeExpression(attributeTypeSymbol!);
+
+ var instance = invocation.Instance;
+ var instanceSyntax = instance?.Syntax;
+
+ // For extension methods, the instance is in the first argument
+ if (instanceSyntax is null && invocation.TargetMethod.IsExtensionMethod && invocation.Arguments.Length > 0)
+ {
+ instanceSyntax = invocation.Arguments[0].Syntax;
+ }
+ else if (instanceSyntax is null && invocation.TargetMethod.IsStatic && SymbolEqualityComparer.Default.Equals(invocation.TargetMethod.ContainingType, attributeTypeSymbol))
+ {
+ if (invocation.Arguments.Length > 0)
+ {
+ instanceSyntax = invocation.Arguments[0].Syntax;
+ }
+ }
+
+ var arguments = new List();
+ if (instanceSyntax is not null)
+ {
+ arguments.Add(instanceSyntax);
+ }
+
+ // Find Type argument
+ SyntaxNode? typeSyntax = null;
+ if (invocation.TargetMethod.IsGenericMethod)
+ {
+ var typeArg = invocation.TargetMethod.TypeArguments[0];
+ typeSyntax = generator.TypeOfExpression(generator.TypeExpression(typeArg));
+ }
+ else
+ {
+ foreach (var arg in invocation.Arguments)
+ {
+ // Skip instance argument for extension methods
+ if (invocation.TargetMethod.IsExtensionMethod && arg == invocation.Arguments[0])
+ continue;
+
+ // Skip instance argument for static Attribute methods
+ if (invocation.TargetMethod.IsStatic && SymbolEqualityComparer.Default.Equals(invocation.TargetMethod.ContainingType, attributeTypeSymbol) && arg == invocation.Arguments[0])
+ continue;
+
+ if (SymbolEqualityComparer.Default.Equals(arg.Parameter?.Type, systemTypeSymbol))
+ {
+ typeSyntax = arg.Syntax;
+ break;
+ }
+ }
+ }
+
+ if (typeSyntax is null)
+ {
+ typeSyntax = generator.TypeOfExpression(attributeTypeSyntax);
+ }
+ arguments.Add(typeSyntax);
+
+ // Find inherit argument
+ SyntaxNode? inheritSyntax = null;
+ foreach (var arg in invocation.Arguments)
+ {
+ // Skip instance argument for extension methods
+ if (invocation.TargetMethod.IsExtensionMethod && arg == invocation.Arguments[0])
+ continue;
+
+ // Skip instance argument for static Attribute methods
+ if (invocation.TargetMethod.IsStatic && SymbolEqualityComparer.Default.Equals(invocation.TargetMethod.ContainingType, attributeTypeSymbol) && arg == invocation.Arguments[0])
+ continue;
+
+ if (arg.Parameter?.Type.SpecialType == SpecialType.System_Boolean && arg.Parameter.Name == "inherit")
+ {
+ inheritSyntax = arg.Syntax;
+ break;
+ }
+ }
+
+ if (inheritSyntax is not null)
+ {
+ arguments.Add(inheritSyntax);
+ }
+
+ var isDefinedInvocation = generator.InvocationExpression(
+ generator.MemberAccessExpression(attributeTypeSyntax, "IsDefined"),
+ arguments);
+
+ if (negate)
+ {
+ return generator.LogicalNotExpression(isDefinedInvocation);
+ }
+
+ return isDefinedInvocation;
+ }
+}
diff --git a/src/Meziantou.Analyzer.Pack/configuration/default.editorconfig b/src/Meziantou.Analyzer.Pack/configuration/default.editorconfig
index a4247fabe..18284e80c 100644
--- a/src/Meziantou.Analyzer.Pack/configuration/default.editorconfig
+++ b/src/Meziantou.Analyzer.Pack/configuration/default.editorconfig
@@ -532,3 +532,6 @@ dotnet_diagnostic.MA0177.severity = none
# MA0178: Use TimeSpan.Zero instead of TimeSpan.FromXXX(0)
dotnet_diagnostic.MA0178.severity = suggestion
+
+# MA0179: Use Attribute.IsDefined instead of GetCustomAttribute(s)
+dotnet_diagnostic.MA0179.severity = suggestion
diff --git a/src/Meziantou.Analyzer.Pack/configuration/none.editorconfig b/src/Meziantou.Analyzer.Pack/configuration/none.editorconfig
index fd989423f..179fd62c2 100644
--- a/src/Meziantou.Analyzer.Pack/configuration/none.editorconfig
+++ b/src/Meziantou.Analyzer.Pack/configuration/none.editorconfig
@@ -532,3 +532,6 @@ dotnet_diagnostic.MA0177.severity = none
# MA0178: Use TimeSpan.Zero instead of TimeSpan.FromXXX(0)
dotnet_diagnostic.MA0178.severity = none
+
+# MA0179: Use Attribute.IsDefined instead of GetCustomAttribute(s)
+dotnet_diagnostic.MA0179.severity = none
diff --git a/src/Meziantou.Analyzer/RuleIdentifiers.cs b/src/Meziantou.Analyzer/RuleIdentifiers.cs
index 3bf49361a..3ce1f4f8c 100755
--- a/src/Meziantou.Analyzer/RuleIdentifiers.cs
+++ b/src/Meziantou.Analyzer/RuleIdentifiers.cs
@@ -179,6 +179,7 @@ internal static class RuleIdentifiers
public const string OptimizeGuidCreation = "MA0176";
public const string UseSingleLineXmlCommentSyntaxWhenPossible = "MA0177";
public const string UseTimeSpanZero = "MA0178";
+ public const string UseAttributeIsDefined = "MA0179";
public static string GetHelpUri(string identifier)
{
diff --git a/src/Meziantou.Analyzer/Rules/UseAttributeIsDefinedAnalyzer.cs b/src/Meziantou.Analyzer/Rules/UseAttributeIsDefinedAnalyzer.cs
new file mode 100644
index 000000000..6ab0edbbd
--- /dev/null
+++ b/src/Meziantou.Analyzer/Rules/UseAttributeIsDefinedAnalyzer.cs
@@ -0,0 +1,302 @@
+using System.Collections.Immutable;
+using Meziantou.Analyzer.Internals;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Diagnostics;
+using Microsoft.CodeAnalysis.Operations;
+
+namespace Meziantou.Analyzer.Rules;
+
+[DiagnosticAnalyzer(LanguageNames.CSharp)]
+public sealed class UseAttributeIsDefinedAnalyzer : DiagnosticAnalyzer
+{
+ private const string EnumerableAnyMethodDocId = "M:System.Linq.Enumerable.Any``1(System.Collections.Generic.IEnumerable{``0})";
+ private const string EnumerableCountMethodDocId = "M:System.Linq.Enumerable.Count``1(System.Collections.Generic.IEnumerable{``0})";
+
+ private static readonly DiagnosticDescriptor Rule = new(
+ RuleIdentifiers.UseAttributeIsDefined,
+ title: "Use Attribute.IsDefined instead of GetCustomAttribute(s)",
+ messageFormat: "Use 'Attribute.IsDefined' instead of '{0}'",
+ RuleCategories.Performance,
+ DiagnosticSeverity.Info,
+ isEnabledByDefault: true,
+ description: "Detects inefficient attribute existence checks that can be replaced with Attribute.IsDefined for better performance.",
+ helpLinkUri: RuleIdentifiers.GetHelpUri(RuleIdentifiers.UseAttributeIsDefined));
+
+ public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule);
+
+ public override void Initialize(AnalysisContext context)
+ {
+ context.EnableConcurrentExecution();
+ context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
+
+ context.RegisterCompilationStartAction(compilationContext =>
+ {
+ var analyzerContext = new AnalyzerContext(compilationContext.Compilation);
+ if (analyzerContext.IsValid)
+ {
+ compilationContext.RegisterOperationAction(analyzerContext.AnalyzeBinary, OperationKind.Binary);
+ compilationContext.RegisterOperationAction(analyzerContext.AnalyzeIsPattern, OperationKind.IsPattern);
+ compilationContext.RegisterOperationAction(analyzerContext.AnalyzeInvocation, OperationKind.Invocation);
+ }
+ });
+ }
+
+ private sealed class AnalyzerContext(Compilation compilation)
+ {
+ private readonly INamedTypeSymbol? _attributeSymbol = compilation.GetBestTypeByMetadataName("System.Attribute");
+ private readonly INamedTypeSymbol? _assemblySymbol = compilation.GetBestTypeByMetadataName("System.Reflection.Assembly");
+ private readonly INamedTypeSymbol? _moduleSymbol = compilation.GetBestTypeByMetadataName("System.Reflection.Module");
+ private readonly INamedTypeSymbol? _memberInfoSymbol = compilation.GetBestTypeByMetadataName("System.Reflection.MemberInfo");
+ private readonly INamedTypeSymbol? _parameterInfoSymbol = compilation.GetBestTypeByMetadataName("System.Reflection.ParameterInfo");
+ private readonly INamedTypeSymbol? _typeSymbol = compilation.GetBestTypeByMetadataName("System.Type");
+ private readonly INamedTypeSymbol? _customAttributeExtensionsSymbol = compilation.GetBestTypeByMetadataName("System.Reflection.CustomAttributeExtensions");
+ private readonly IMethodSymbol? _enumerableAnyMethod = DocumentationCommentId.GetFirstSymbolForDeclarationId(EnumerableAnyMethodDocId, compilation) as IMethodSymbol;
+ private readonly IMethodSymbol? _enumerableCountMethod = DocumentationCommentId.GetFirstSymbolForDeclarationId(EnumerableCountMethodDocId, compilation) as IMethodSymbol;
+
+ public bool IsValid => _attributeSymbol is not null;
+
+ public void AnalyzeBinary(OperationAnalysisContext context)
+ {
+ var operation = (IBinaryOperation)context.Operation;
+ if (operation.OperatorKind is not (BinaryOperatorKind.Equals or BinaryOperatorKind.NotEquals or BinaryOperatorKind.GreaterThan or BinaryOperatorKind.LessThan or BinaryOperatorKind.GreaterThanOrEqual or BinaryOperatorKind.LessThanOrEqual))
+ return;
+
+ if (IsGetCustomAttributeComparison(operation.LeftOperand, operation.RightOperand, out var invocation))
+ {
+ context.ReportDiagnostic(Rule, operation, invocation!.TargetMethod.Name);
+ return;
+ }
+
+ if (IsGetCustomAttributesLengthComparison(operation, operation.LeftOperand, operation.RightOperand, out _))
+ {
+ context.ReportDiagnostic(Rule, operation, "GetCustomAttributes().Length");
+ }
+ else if (IsGetCustomAttributesCountComparison(operation, operation.LeftOperand, operation.RightOperand, out _))
+ {
+ context.ReportDiagnostic(Rule, operation, "GetCustomAttributes().Count()");
+ }
+ }
+
+ public void AnalyzeIsPattern(OperationAnalysisContext context)
+ {
+ var operation = (IIsPatternOperation)context.Operation;
+
+ if (operation.Pattern is not (IConstantPatternOperation or INegatedPatternOperation))
+ return;
+
+ if (!IsGetCustomAttributeInvocation(operation.Value, out var invocation))
+ return;
+
+ context.ReportDiagnostic(Rule, operation, invocation!.TargetMethod.Name);
+ }
+
+ public void AnalyzeInvocation(OperationAnalysisContext context)
+ {
+ var operation = (IInvocationOperation)context.Operation;
+
+ // Check if this is the specific Any(IEnumerable) method
+ if (operation.TargetMethod.OriginalDefinition.IsEqualTo(_enumerableAnyMethod))
+ {
+ if (operation.Arguments.Length != 1)
+ return;
+
+ var instance = operation.Arguments[0].Value;
+ if (!IsGetCustomAttributesInvocation(instance, out _))
+ return;
+
+ context.ReportDiagnostic(Rule, operation, "GetCustomAttributes().Any()");
+ }
+ }
+
+ private bool IsGetCustomAttributeComparison(IOperation left, IOperation right, out IInvocationOperation? invocation)
+ {
+ if (right.IsNull())
+ return IsGetCustomAttributeInvocation(left, out invocation);
+
+ if (left.IsNull())
+ return IsGetCustomAttributeInvocation(right, out invocation);
+
+ invocation = null;
+ return false;
+ }
+
+ private bool IsGetCustomAttributesLengthComparison(IBinaryOperation binaryOp, IOperation left, IOperation right, out IInvocationOperation? invocation)
+ {
+ invocation = null;
+
+ if (left is not IPropertyReferenceOperation propertyReference)
+ return false;
+
+ if (propertyReference.Property.Name is not "Length")
+ return false;
+
+ if (propertyReference.Instance is null)
+ return false;
+
+ if (!IsGetCustomAttributesInvocation(propertyReference.Instance, out invocation))
+ return false;
+
+ // Only allow clear-cut patterns that unambiguously check for existence
+ if (right.ConstantValue is not { HasValue: true, Value: int value })
+ return false;
+
+ // Validate that the operator + value combination makes sense
+ return IsValidLengthComparisonPattern(binaryOp.OperatorKind, value, lengthIsOnLeft: true);
+ }
+
+ private static bool IsValidLengthComparisonPattern(BinaryOperatorKind operatorKind, int value, bool lengthIsOnLeft)
+ {
+ if (lengthIsOnLeft)
+ {
+ return (operatorKind, value) switch
+ {
+ (BinaryOperatorKind.Equals, 0) => true, // length == 0
+ (BinaryOperatorKind.NotEquals, 0) => true, // length != 0
+ (BinaryOperatorKind.GreaterThan, 0) => true, // length > 0
+ (BinaryOperatorKind.GreaterThanOrEqual, 1) => true, // length >= 1
+ (BinaryOperatorKind.LessThan, 1) => true, // length < 1
+ (BinaryOperatorKind.LessThanOrEqual, 0) => true, // length <= 0
+ _ => false,
+ };
+ }
+ else
+ {
+ return (operatorKind, value) switch
+ {
+ (BinaryOperatorKind.Equals, 0) => true, // 0 == length
+ (BinaryOperatorKind.NotEquals, 0) => true, // 0 != length
+ (BinaryOperatorKind.LessThan, 0) => true, // 0 < length (length > 0)
+ (BinaryOperatorKind.LessThanOrEqual, 1) => true, // 1 <= length (length >= 1)
+ (BinaryOperatorKind.GreaterThan, 1) => true, // 1 > length (length < 1)
+ (BinaryOperatorKind.GreaterThanOrEqual, 0) => true, // 0 >= length (length <= 0)
+ _ => false,
+ };
+ }
+ }
+
+ private bool IsGetCustomAttributesCountComparison(IBinaryOperation binaryOp, IOperation left, IOperation right, out IInvocationOperation? invocation)
+ {
+ invocation = null;
+
+ if (left is not IInvocationOperation countInvocation)
+ return false;
+
+ // Check if this is the specific Count(IEnumerable) method
+ if (_enumerableCountMethod is null ||
+ !SymbolEqualityComparer.Default.Equals(countInvocation.TargetMethod.OriginalDefinition, _enumerableCountMethod))
+ return false;
+
+ // Only detect Count() without predicate (1 argument = the collection itself)
+ if (countInvocation.Arguments.Length != 1)
+ return false;
+
+ var instance = countInvocation.Arguments[0].Value;
+ if (!IsGetCustomAttributesInvocation(instance, out invocation))
+ return false;
+
+ // Only allow clear-cut patterns that unambiguously check for existence
+ if (right.ConstantValue is not { HasValue: true, Value: int value })
+ return false;
+
+ // Use the same validation as Length (Count and Length have the same semantics)
+ return IsValidLengthComparisonPattern(binaryOp.OperatorKind, value, lengthIsOnLeft: true);
+ }
+
+ private bool IsGetCustomAttributeInvocation(IOperation operation, out IInvocationOperation? invocation)
+ {
+ invocation = operation.UnwrapConversionOperations() as IInvocationOperation;
+ if (invocation is null)
+ return false;
+
+ if (invocation.TargetMethod.Name != "GetCustomAttribute")
+ return false;
+
+ // For extension methods, the instance is in the first argument
+ var instance = invocation.Instance;
+ if (instance is null && invocation.TargetMethod.IsExtensionMethod && invocation.Arguments.Length > 0)
+ {
+ instance = invocation.Arguments[0].Value;
+ }
+ else if (instance is null && invocation.TargetMethod.IsStatic && SymbolEqualityComparer.Default.Equals(invocation.TargetMethod.ContainingType, _attributeSymbol))
+ {
+ if (invocation.Arguments.Length > 0)
+ {
+ instance = invocation.Arguments[0].Value;
+ }
+ }
+
+ if (instance is null)
+ return false;
+
+ return IsValidInstanceType(instance.Type);
+ }
+
+ private bool IsGetCustomAttributesInvocation(IOperation operation, out IInvocationOperation? invocation)
+ {
+ invocation = operation as IInvocationOperation;
+ if (invocation is null)
+ return false;
+
+ if (invocation.TargetMethod.Name != "GetCustomAttributes")
+ return false;
+
+ if (!IsMethodFromReflectionTypes(invocation.TargetMethod))
+ return false;
+
+ // For extension methods, the instance is in the first argument
+ var instance = invocation.Instance;
+ if (instance is null && invocation.TargetMethod.IsExtensionMethod && invocation.Arguments.Length > 0)
+ {
+ instance = invocation.Arguments[0].Value;
+ }
+ else if (instance is null && invocation.TargetMethod.IsStatic && SymbolEqualityComparer.Default.Equals(invocation.TargetMethod.ContainingType, _attributeSymbol))
+ {
+ if (invocation.Arguments.Length > 0)
+ {
+ instance = invocation.Arguments[0].Value;
+ }
+ }
+
+ if (instance is null)
+ return false;
+
+ return IsValidInstanceType(instance.Type);
+ }
+
+ private bool IsMethodFromReflectionTypes(IMethodSymbol method)
+ {
+ if (SymbolEqualityComparer.Default.Equals(method.ContainingType, _customAttributeExtensionsSymbol))
+ return true;
+
+ if (SymbolEqualityComparer.Default.Equals(method.ContainingType, _attributeSymbol))
+ return true;
+
+ // Check for extension methods on reflection types
+ if (method.Name is "GetCustomAttribute" or "GetCustomAttributes")
+ {
+ if (method.Parameters.Length > 0)
+ {
+ var firstParamType = method.Parameters[0].Type;
+ if (IsReflectionType(firstParamType) || IsParameterInfo(firstParamType))
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private bool IsParameterInfo(ITypeSymbol type) => type.IsEqualTo(_parameterInfoSymbol);
+ private bool IsReflectionType(ITypeSymbol type) => type.IsEqualToAny(_assemblySymbol, _moduleSymbol, _memberInfoSymbol, _typeSymbol);
+
+ private bool IsValidInstanceType(ITypeSymbol? type)
+ {
+ if (type is null)
+ return false;
+
+ return type.IsOrInheritFrom(_assemblySymbol) ||
+ type.IsOrInheritFrom(_moduleSymbol) ||
+ type.IsOrInheritFrom(_memberInfoSymbol) ||
+ type.IsOrInheritFrom(_typeSymbol);
+ }
+ }
+}
diff --git a/tests/Meziantou.Analyzer.Test/Rules/UseAttributeIsDefinedAnalyzerTests.cs b/tests/Meziantou.Analyzer.Test/Rules/UseAttributeIsDefinedAnalyzerTests.cs
new file mode 100644
index 000000000..49eee20db
--- /dev/null
+++ b/tests/Meziantou.Analyzer.Test/Rules/UseAttributeIsDefinedAnalyzerTests.cs
@@ -0,0 +1,658 @@
+using Meziantou.Analyzer.Rules;
+using TestHelper;
+
+namespace Meziantou.Analyzer.Test.Rules;
+
+public sealed class UseAttributeIsDefinedAnalyzerTests
+{
+ private static ProjectBuilder CreateProjectBuilder()
+ {
+ return new ProjectBuilder()
+ .WithAnalyzer()
+ .WithCodeFixProvider();
+ }
+
+ [Fact]
+ public async Task GetCustomAttribute_NotEqualNull_MemberInfo()
+ {
+ await CreateProjectBuilder()
+ .WithSourceCode("""
+using System;
+using System.Reflection;
+
+class TestClass
+{
+ void Test(MemberInfo member)
+ {
+ _ = [|member.GetCustomAttribute() != null|];
+ }
+}
+""")
+ .ShouldFixCodeWith("""
+using System;
+using System.Reflection;
+
+class TestClass
+{
+ void Test(MemberInfo member)
+ {
+ _ = Attribute.IsDefined(member, typeof(ObsoleteAttribute));
+ }
+}
+""")
+ .ValidateAsync();
+ }
+
+ [Fact]
+ public async Task GetCustomAttribute_EqualNull_MemberInfo()
+ {
+ await CreateProjectBuilder()
+ .WithSourceCode("""
+using System;
+using System.Reflection;
+
+class TestClass
+{
+ void Test(MemberInfo member)
+ {
+ _ = [|member.GetCustomAttribute() == null|];
+ }
+}
+""")
+ .ShouldFixCodeWith("""
+using System;
+using System.Reflection;
+
+class TestClass
+{
+ void Test(MemberInfo member)
+ {
+ _ = !Attribute.IsDefined(member, typeof(ObsoleteAttribute));
+ }
+}
+""")
+ .ValidateAsync();
+ }
+
+ [Fact]
+ public async Task GetCustomAttribute_IsNull_MemberInfo()
+ {
+ await CreateProjectBuilder()
+ .WithSourceCode("""
+using System;
+using System.Reflection;
+
+class TestClass
+{
+ void Test(MemberInfo member)
+ {
+ _ = [|member.GetCustomAttribute() is null|];
+ }
+}
+""")
+ .ShouldFixCodeWith("""
+using System;
+using System.Reflection;
+
+class TestClass
+{
+ void Test(MemberInfo member)
+ {
+ _ = !Attribute.IsDefined(member, typeof(ObsoleteAttribute));
+ }
+}
+""")
+ .ValidateAsync();
+ }
+
+ [Fact]
+ public async Task GetCustomAttribute_IsNotNull_MemberInfo()
+ {
+ await CreateProjectBuilder()
+ .WithSourceCode("""
+using System;
+using System.Reflection;
+
+class TestClass
+{
+ void Test(MemberInfo member)
+ {
+ _ = [|member.GetCustomAttribute() is not null|];
+ }
+}
+""")
+ .ShouldFixCodeWith("""
+using System;
+using System.Reflection;
+
+class TestClass
+{
+ void Test(MemberInfo member)
+ {
+ _ = Attribute.IsDefined(member, typeof(ObsoleteAttribute));
+ }
+}
+""")
+ .ValidateAsync();
+ }
+
+ [Fact]
+ public async Task GetCustomAttributes_Any_MemberInfo()
+ {
+ await CreateProjectBuilder()
+ .WithSourceCode("""
+using System;
+using System.Linq;
+using System.Reflection;
+
+class TestClass
+{
+ void Test(MemberInfo member)
+ {
+ _ = [|member.GetCustomAttributes().Any()|];
+ }
+}
+""")
+ .ShouldFixCodeWith("""
+using System;
+using System.Linq;
+using System.Reflection;
+
+class TestClass
+{
+ void Test(MemberInfo member)
+ {
+ _ = Attribute.IsDefined(member, typeof(ObsoleteAttribute));
+ }
+}
+""")
+ .ValidateAsync();
+ }
+
+ [Fact]
+ public async Task GetCustomAttribute_NotEqualNull_Type()
+ {
+ await CreateProjectBuilder()
+ .WithSourceCode("""
+using System;
+using System.Reflection;
+
+class TestClass
+{
+ void Test(Type type)
+ {
+ _ = [|type.GetCustomAttribute() != null|];
+ }
+}
+""")
+ .ShouldFixCodeWith("""
+using System;
+using System.Reflection;
+
+class TestClass
+{
+ void Test(Type type)
+ {
+ _ = Attribute.IsDefined(type, typeof(ObsoleteAttribute));
+ }
+}
+""")
+ .ValidateAsync();
+ }
+
+ [Fact]
+ public async Task GetCustomAttribute_NotEqualNull_Assembly()
+ {
+ await CreateProjectBuilder()
+ .WithSourceCode("""
+using System;
+using System.Reflection;
+
+class TestClass
+{
+ void Test(Assembly assembly)
+ {
+ _ = [|assembly.GetCustomAttribute() != null|];
+ }
+}
+""")
+ .ShouldFixCodeWith("""
+using System;
+using System.Reflection;
+
+class TestClass
+{
+ void Test(Assembly assembly)
+ {
+ _ = Attribute.IsDefined(assembly, typeof(ObsoleteAttribute));
+ }
+}
+""")
+ .ValidateAsync();
+ }
+
+ [Fact]
+ public async Task GetCustomAttribute_NotEqualNull_Module()
+ {
+ await CreateProjectBuilder()
+ .WithSourceCode("""
+using System;
+using System.Reflection;
+
+class TestClass
+{
+ void Test(Module module)
+ {
+ _ = [|module.GetCustomAttribute() != null|];
+ }
+}
+""")
+ .ShouldFixCodeWith("""
+using System;
+using System.Reflection;
+
+class TestClass
+{
+ void Test(Module module)
+ {
+ _ = Attribute.IsDefined(module, typeof(ObsoleteAttribute));
+ }
+}
+""")
+ .ValidateAsync();
+ }
+
+ [Fact]
+ public async Task GetCustomAttribute_WithInherit_NotEqualNull_MemberInfo()
+ {
+ await CreateProjectBuilder()
+ .WithSourceCode("""
+using System;
+using System.Reflection;
+
+class TestClass
+{
+ void Test(MemberInfo member)
+ {
+ _ = [|member.GetCustomAttribute(inherit: true) != null|];
+ }
+}
+""")
+ .ShouldFixCodeWith("""
+using System;
+using System.Reflection;
+
+class TestClass
+{
+ void Test(MemberInfo member)
+ {
+ _ = Attribute.IsDefined(member, typeof(ObsoleteAttribute), inherit: true);
+ }
+}
+""")
+ .ValidateAsync();
+ }
+
+ [Fact]
+ public async Task GetCustomAttributes_WithInherit_Any_MemberInfo()
+ {
+ await CreateProjectBuilder()
+ .WithSourceCode("""
+using System;
+using System.Linq;
+using System.Reflection;
+
+class TestClass
+{
+ void Test(MemberInfo member)
+ {
+ _ = [|member.GetCustomAttributes(inherit: true).Any()|];
+ }
+}
+""")
+ .ShouldFixCodeWith("""
+using System;
+using System.Linq;
+using System.Reflection;
+
+class TestClass
+{
+ void Test(MemberInfo member)
+ {
+ _ = Attribute.IsDefined(member, typeof(ObsoleteAttribute), inherit: true);
+ }
+}
+""")
+ .ValidateAsync();
+ }
+
+ [Fact]
+ public async Task GetCustomAttribute_UsedDirectly_ShouldNotReport()
+ {
+ await CreateProjectBuilder()
+ .WithSourceCode("""
+using System;
+using System.Reflection;
+
+class TestClass
+{
+ void Test(MemberInfo member)
+ {
+ var attr = member.GetCustomAttribute();
+ _ = attr.Message;
+ }
+}
+""")
+ .ValidateAsync();
+ }
+
+ [Fact]
+ public async Task GetCustomAttributes_WithPredicate_ShouldNotReport()
+ {
+ await CreateProjectBuilder()
+ .WithSourceCode("""
+using System;
+using System.Linq;
+using System.Reflection;
+
+class TestClass
+{
+ void Test(MemberInfo member)
+ {
+ _ = member.GetCustomAttributes().Any(a => a.Message != null);
+ }
+}
+""")
+ .ValidateAsync();
+ }
+
+ [Fact]
+ public async Task GetCustomAttributes_Any_WithTruePredicate_ShouldNotReport()
+ {
+ await CreateProjectBuilder()
+ .WithSourceCode("""
+using System;
+using System.Linq;
+using System.Reflection;
+
+class TestClass
+{
+ void Test(MemberInfo member)
+ {
+ _ = member.GetCustomAttributes().Any(attr => true);
+ }
+}
+""")
+ .ValidateAsync();
+ }
+
+ [Fact]
+ public async Task GetCustomAttributes_Count_ShouldReport()
+ {
+ await CreateProjectBuilder()
+ .WithSourceCode("""
+using System;
+using System.Linq;
+using System.Reflection;
+
+class TestClass
+{
+ void Test(MemberInfo member)
+ {
+ _ = [|member.GetCustomAttributes().Count() > 0|];
+ }
+}
+""")
+ .ShouldFixCodeWith("""
+using System;
+using System.Linq;
+using System.Reflection;
+
+class TestClass
+{
+ void Test(MemberInfo member)
+ {
+ _ = Attribute.IsDefined(member, typeof(ObsoleteAttribute));
+ }
+}
+""")
+ .ValidateAsync();
+ }
+
+ [Fact]
+ public async Task GetCustomAttributes_Count_WithPredicate_ShouldNotReport()
+ {
+ await CreateProjectBuilder()
+ .WithSourceCode("""
+using System;
+using System.Linq;
+using System.Reflection;
+
+class TestClass
+{
+ void Test(MemberInfo member)
+ {
+ _ = member.GetCustomAttributes().Count(a => a.Message != null) > 0;
+ }
+}
+""")
+ .ValidateAsync();
+ }
+
+ [Fact]
+ public async Task GetCustomAttribute_NullComparison_ReversedOrder()
+ {
+ await CreateProjectBuilder()
+ .WithSourceCode("""
+using System;
+using System.Reflection;
+
+class TestClass
+{
+ void Test(MemberInfo member)
+ {
+ _ = [|null != member.GetCustomAttribute()|];
+ }
+}
+""")
+ .ShouldFixCodeWith("""
+using System;
+using System.Reflection;
+
+class TestClass
+{
+ void Test(MemberInfo member)
+ {
+ _ = Attribute.IsDefined(member, typeof(ObsoleteAttribute));
+ }
+}
+""")
+ .ValidateAsync();
+ }
+
+ [Fact]
+ public async Task GetCustomAttributes_Length_GreaterThanZero()
+ {
+ await CreateProjectBuilder()
+ .WithSourceCode("""
+using System;
+using System.Reflection;
+
+class TestClass
+{
+ void Test(MemberInfo member)
+ {
+ _ = [|member.GetCustomAttributes(typeof(ObsoleteAttribute), false).Length > 0|];
+ }
+}
+""")
+ .ShouldFixCodeWith("""
+using System;
+using System.Reflection;
+
+class TestClass
+{
+ void Test(MemberInfo member)
+ {
+ _ = Attribute.IsDefined(member, typeof(ObsoleteAttribute), false);
+ }
+}
+""")
+ .ValidateAsync();
+ }
+
+ [Fact]
+ public async Task GetCustomAttributes_Length_NotEqualZero()
+ {
+ await CreateProjectBuilder()
+ .WithSourceCode("""
+using System;
+using System.Reflection;
+
+class TestClass
+{
+ void Test(MemberInfo member)
+ {
+ _ = [|member.GetCustomAttributes(typeof(ObsoleteAttribute), false).Length != 0|];
+ }
+}
+""")
+ .ShouldFixCodeWith("""
+using System;
+using System.Reflection;
+
+class TestClass
+{
+ void Test(MemberInfo member)
+ {
+ _ = Attribute.IsDefined(member, typeof(ObsoleteAttribute), false);
+ }
+}
+""")
+ .ValidateAsync();
+ }
+
+ [Fact]
+ public async Task GetCustomAttributes_Length_EqualZero()
+ {
+ await CreateProjectBuilder()
+ .WithSourceCode("""
+using System;
+using System.Reflection;
+
+class TestClass
+{
+ void Test(MemberInfo member)
+ {
+ _ = [|member.GetCustomAttributes(typeof(ObsoleteAttribute), false).Length == 0|];
+ }
+}
+""")
+ .ShouldFixCodeWith("""
+using System;
+using System.Reflection;
+
+class TestClass
+{
+ void Test(MemberInfo member)
+ {
+ _ = !Attribute.IsDefined(member, typeof(ObsoleteAttribute), false);
+ }
+}
+""")
+ .ValidateAsync();
+ }
+
+ [Fact]
+ public async Task GetCustomAttributes_Length_GreaterThanOrEqualOne()
+ {
+ await CreateProjectBuilder()
+ .WithSourceCode("""
+using System;
+using System.Reflection;
+
+class TestClass
+{
+ void Test(MemberInfo member)
+ {
+ _ = [|member.GetCustomAttributes(typeof(ObsoleteAttribute), false).Length >= 1|];
+ }
+}
+""")
+ .ShouldFixCodeWith("""
+using System;
+using System.Reflection;
+
+class TestClass
+{
+ void Test(MemberInfo member)
+ {
+ _ = Attribute.IsDefined(member, typeof(ObsoleteAttribute), false);
+ }
+}
+""")
+ .ValidateAsync();
+ }
+
+ [Fact]
+ public async Task Attribute_GetCustomAttributes_Length_GreaterThanZero()
+ {
+ await CreateProjectBuilder()
+ .WithSourceCode("""
+using System;
+using System.Reflection;
+
+class TestClass
+{
+ void Test(MemberInfo member)
+ {
+ _ = [|Attribute.GetCustomAttributes(member, typeof(ObsoleteAttribute)).Length > 0|];
+ }
+}
+""")
+ .ShouldFixCodeWith("""
+using System;
+using System.Reflection;
+
+class TestClass
+{
+ void Test(MemberInfo member)
+ {
+ _ = Attribute.IsDefined(member, typeof(ObsoleteAttribute));
+ }
+}
+""")
+ .ValidateAsync();
+ }
+
+ [Fact]
+ public async Task Attribute_GetCustomAttribute_NotEqualNull()
+ {
+ await CreateProjectBuilder()
+ .WithSourceCode("""
+using System;
+using System.Reflection;
+
+class TestClass
+{
+ void Test(MemberInfo member)
+ {
+ _ = [|Attribute.GetCustomAttribute(member, typeof(ObsoleteAttribute)) != null|];
+ }
+}
+""")
+ .ShouldFixCodeWith("""
+using System;
+using System.Reflection;
+
+class TestClass
+{
+ void Test(MemberInfo member)
+ {
+ _ = Attribute.IsDefined(member, typeof(ObsoleteAttribute));
+ }
+}
+""")
+ .ValidateAsync();
+ }
+}