diff --git a/README.md b/README.md
index 077c58fb..fbb2a806 100755
--- a/README.md
+++ b/README.md
@@ -206,6 +206,7 @@ If you are already using other analyzers, you can check [which rules are duplica
|[MA0189](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0189.md)|Design|Use InlineArray instead of fixed-size buffers|ℹ️|✔️|✔️|
|[MA0190](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0190.md)|Design|Use partial property instead of partial method for GeneratedRegex|ℹ️|✔️|✔️|
|[MA0191](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0191.md)|Design|Do not use the null-forgiving operator|⚠️|❌|❌|
+|[MA0192](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0192.md)|Usage|Use HasFlag instead of bitwise checks|ℹ️|❌|✔️|
diff --git a/docs/README.md b/docs/README.md
index 08d5ae0c..e0fd8a43 100755
--- a/docs/README.md
+++ b/docs/README.md
@@ -190,6 +190,7 @@
|[MA0189](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0189.md)|Design|Use InlineArray instead of fixed-size buffers|ℹ️|✔️|✔️|
|[MA0190](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0190.md)|Design|Use partial property instead of partial method for GeneratedRegex|ℹ️|✔️|✔️|
|[MA0191](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0191.md)|Design|Do not use the null-forgiving operator|⚠️|❌|❌|
+|[MA0192](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0192.md)|Usage|Use HasFlag instead of bitwise checks|ℹ️|❌|✔️|
|Id|Suppressed rule|Justification|
|--|---------------|-------------|
@@ -775,6 +776,9 @@ dotnet_diagnostic.MA0190.severity = suggestion
# MA0191: Do not use the null-forgiving operator
dotnet_diagnostic.MA0191.severity = none
+
+# MA0192: Use HasFlag instead of bitwise checks
+dotnet_diagnostic.MA0192.severity = none
```
# .editorconfig - all rules disabled
@@ -1346,4 +1350,7 @@ dotnet_diagnostic.MA0190.severity = none
# MA0191: Do not use the null-forgiving operator
dotnet_diagnostic.MA0191.severity = none
+
+# MA0192: Use HasFlag instead of bitwise checks
+dotnet_diagnostic.MA0192.severity = none
```
diff --git a/docs/Rules/MA0192.md b/docs/Rules/MA0192.md
new file mode 100644
index 00000000..31475d86
--- /dev/null
+++ b/docs/Rules/MA0192.md
@@ -0,0 +1,55 @@
+# MA0192 - Use HasFlag instead of bitwise checks
+
+Sources: [UseHasFlagMethodAnalyzer.cs](https://github.com/meziantou/Meziantou.Analyzer/blob/main/src/Meziantou.Analyzer/Rules/UseHasFlagMethodAnalyzer.cs), [UseHasFlagMethodFixer.cs](https://github.com/meziantou/Meziantou.Analyzer/blob/main/src/Meziantou.Analyzer.CodeFixers/Rules/UseHasFlagMethodFixer.cs)
+
+
+Use `Enum.HasFlag` instead of manually checking bits using equality or `is` patterns.
+
+This rule reports patterns such as:
+
+- `(value & MyEnum.Flag1) == MyEnum.Flag1`
+- `(value & MyEnum.Flag1) != MyEnum.Flag1`
+- `(value & MyEnum.Flag1) is MyEnum.Flag1`
+- `(value & MyEnum.Flag1) is not MyEnum.Flag1`
+
+## Non-compliant code
+
+````csharp
+[System.Flags]
+enum MyEnum
+{
+ None = 0,
+ Flag1 = 1,
+ Flag2 = 2,
+}
+
+bool M(MyEnum value)
+{
+ return (value & MyEnum.Flag1) == MyEnum.Flag1;
+}
+````
+
+## Compliant code
+
+````csharp
+[System.Flags]
+enum MyEnum
+{
+ None = 0,
+ Flag1 = 1,
+ Flag2 = 2,
+}
+
+bool M(MyEnum value)
+{
+ return value.HasFlag(MyEnum.Flag1);
+}
+````
+
+## Configuration
+
+This rule is disabled by default. To enable it, add the following to your `.editorconfig` file:
+
+````editorconfig
+dotnet_diagnostic.MA0192.severity = suggestion
+````
diff --git a/src/Meziantou.Analyzer.CodeFixers/Rules/UseHasFlagMethodFixer.cs b/src/Meziantou.Analyzer.CodeFixers/Rules/UseHasFlagMethodFixer.cs
new file mode 100644
index 00000000..74893384
--- /dev/null
+++ b/src/Meziantou.Analyzer.CodeFixers/Rules/UseHasFlagMethodFixer.cs
@@ -0,0 +1,244 @@
+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.Formatting;
+using Microsoft.CodeAnalysis.Operations;
+using Microsoft.CodeAnalysis.Text;
+using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
+
+namespace Meziantou.Analyzer.Rules;
+
+[ExportCodeFixProvider(LanguageNames.CSharp), Shared]
+public sealed class UseHasFlagMethodFixer : CodeFixProvider
+{
+ public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(RuleIdentifiers.UseHasFlagMethod);
+
+ public override FixAllProvider GetFixAllProvider() => 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 semanticModel = await context.Document.GetSemanticModelAsync(context.CancellationToken).ConfigureAwait(false);
+ if (semanticModel is null)
+ return;
+
+ if (!TryGetHasFlagPattern(semanticModel, nodeToFix, context.CancellationToken, out var pattern))
+ return;
+
+ const string Title = "Use HasFlag";
+ context.RegisterCodeFix(
+ CodeAction.Create(Title, ct => UseHasFlag(context.Document, pattern.OperationSpan, ct), equivalenceKey: Title),
+ context.Diagnostics);
+ }
+
+ private static async Task UseHasFlag(Document document, TextSpan operationSpan, CancellationToken cancellationToken)
+ {
+ var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
+ if (root is null)
+ return document;
+
+ var nodeToFix = root.FindNode(operationSpan, getInnermostNodeForTie: true);
+
+ var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
+ if (semanticModel is null)
+ return document;
+
+ if (!TryGetHasFlagPattern(semanticModel, nodeToFix, cancellationToken, out var pattern))
+ return document;
+
+ var editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false);
+ var target = AddParenthesesIfNeeded(pattern.EnumValueExpression.WithoutTrivia());
+
+ var replacementNode = InvocationExpression(
+ MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, target, IdentifierName(nameof(Enum.HasFlag))),
+ ArgumentList(SingletonSeparatedList(Argument(pattern.FlagExpression.WithoutTrivia()))));
+
+ ExpressionSyntax updatedNode = replacementNode;
+ if (pattern.Negate)
+ {
+ updatedNode = PrefixUnaryExpression(SyntaxKind.LogicalNotExpression, updatedNode);
+ }
+
+ editor.ReplaceNode(pattern.OperationExpression, updatedNode.WithTriviaFrom(pattern.OperationExpression).WithAdditionalAnnotations(Formatter.Annotation));
+ return editor.GetChangedDocument();
+ }
+
+ private static ExpressionSyntax AddParenthesesIfNeeded(ExpressionSyntax expression)
+ {
+ return expression switch
+ {
+ IdentifierNameSyntax => expression,
+ GenericNameSyntax => expression,
+ ThisExpressionSyntax => expression,
+ BaseExpressionSyntax => expression,
+ MemberAccessExpressionSyntax => expression,
+ InvocationExpressionSyntax => expression,
+ ElementAccessExpressionSyntax => expression,
+ ParenthesizedExpressionSyntax => expression,
+ _ => expression.Parentheses(),
+ };
+ }
+
+ private static bool TryGetHasFlagPattern(SemanticModel semanticModel, SyntaxNode node, CancellationToken cancellationToken, [NotNullWhen(true)] out HasFlagPattern? pattern)
+ {
+ foreach (var candidateNode in node.AncestorsAndSelf())
+ {
+ var operation = semanticModel.GetOperation(candidateNode, cancellationToken);
+ if (operation is null)
+ continue;
+
+ if (TryGetHasFlagPattern(operation, out pattern))
+ return true;
+ }
+
+ pattern = null;
+ return false;
+ }
+
+ private static bool TryGetHasFlagPattern(IOperation operation, [NotNullWhen(true)] out HasFlagPattern? pattern)
+ {
+ if (operation is IBinaryOperation { OperatorKind: BinaryOperatorKind.Equals or BinaryOperatorKind.NotEquals } binaryOperation)
+ {
+ pattern = GetFromBinaryComparison(binaryOperation);
+ return pattern is not null;
+ }
+
+ if (operation is IIsPatternOperation
+ {
+ Value: IBinaryOperation { OperatorKind: BinaryOperatorKind.And } andOperation,
+ Pattern: IPatternOperation patternOperation,
+ Syntax: ExpressionSyntax operationExpression,
+ })
+ {
+ if (TryGetComparedOperand(patternOperation, out var comparedOperand, out var negate))
+ {
+ pattern = GetFromBitwiseAnd(andOperation, comparedOperand, operationExpression, negate);
+ return pattern is not null;
+ }
+ }
+
+ pattern = null;
+ return false;
+ }
+
+ private static bool TryGetComparedOperand(IPatternOperation patternOperation, [NotNullWhen(true)] out IOperation? comparedOperand, out bool negate)
+ {
+ if (patternOperation is IConstantPatternOperation { Value: not null } constantPattern)
+ {
+ comparedOperand = constantPattern.Value;
+ negate = false;
+ return true;
+ }
+
+ if (patternOperation is INegatedPatternOperation { Pattern: IConstantPatternOperation { Value: not null } constantPattern2 })
+ {
+ comparedOperand = constantPattern2.Value;
+ negate = true;
+ return true;
+ }
+
+ comparedOperand = null;
+ negate = false;
+ return false;
+ }
+
+ private static HasFlagPattern? GetFromBinaryComparison(IBinaryOperation operation)
+ {
+ var leftOperand = operation.LeftOperand.UnwrapImplicitConversionOperations();
+ var rightOperand = operation.RightOperand.UnwrapImplicitConversionOperations();
+ var negate = operation.OperatorKind is BinaryOperatorKind.NotEquals;
+
+ if (operation.Syntax is not ExpressionSyntax operationExpression)
+ return null;
+
+ if (leftOperand is IBinaryOperation { OperatorKind: BinaryOperatorKind.And } leftBitwiseAnd)
+ {
+ var pattern = GetFromBitwiseAnd(leftBitwiseAnd, rightOperand, operationExpression, negate);
+ if (pattern is not null)
+ return pattern;
+ }
+
+ if (rightOperand is IBinaryOperation { OperatorKind: BinaryOperatorKind.And } rightBitwiseAnd)
+ {
+ var pattern = GetFromBitwiseAnd(rightBitwiseAnd, leftOperand, operationExpression, negate);
+ if (pattern is not null)
+ return pattern;
+ }
+
+ return null;
+ }
+
+ private static HasFlagPattern? GetFromBitwiseAnd(IBinaryOperation bitwiseAndOperation, IOperation comparedOperand, ExpressionSyntax operationExpression, bool negate)
+ {
+ var leftOperand = bitwiseAndOperation.LeftOperand.UnwrapImplicitConversionOperations();
+ var rightOperand = bitwiseAndOperation.RightOperand.UnwrapImplicitConversionOperations();
+ comparedOperand = comparedOperand.UnwrapImplicitConversionOperations();
+
+ if (TryGetEnumFlagReference(rightOperand, comparedOperand, out var flagOperation) &&
+ IsValidPattern(leftOperand, flagOperation) &&
+ leftOperand.Syntax is ExpressionSyntax enumValueExpression &&
+ flagOperation.Syntax is ExpressionSyntax flagExpression)
+ {
+ return new(operationExpression, enumValueExpression, flagExpression, negate);
+ }
+
+ if (TryGetEnumFlagReference(leftOperand, comparedOperand, out flagOperation) &&
+ IsValidPattern(rightOperand, flagOperation) &&
+ rightOperand.Syntax is ExpressionSyntax enumValueExpression2 &&
+ flagOperation.Syntax is ExpressionSyntax flagExpression2)
+ {
+ return new(operationExpression, enumValueExpression2, flagExpression2, negate);
+ }
+
+ return null;
+ }
+
+ private static bool TryGetEnumFlagReference(IOperation potentialFlag, IOperation comparedOperand, [NotNullWhen(true)] out IFieldReferenceOperation? flagOperation)
+ {
+ potentialFlag = potentialFlag.UnwrapImplicitConversionOperations();
+ comparedOperand = comparedOperand.UnwrapImplicitConversionOperations();
+
+ if (potentialFlag is IFieldReferenceOperation firstFieldReference &&
+ comparedOperand is IFieldReferenceOperation secondFieldReference &&
+ firstFieldReference.Field.HasConstantValue &&
+ secondFieldReference.Field.HasConstantValue &&
+ firstFieldReference.Field.IsEqualTo(secondFieldReference.Field) &&
+ firstFieldReference.Field.ContainingType.IsEnumeration())
+ {
+ flagOperation = secondFieldReference;
+ return true;
+ }
+
+ flagOperation = null;
+ return false;
+ }
+
+ private static bool IsValidPattern(IOperation enumValueOperation, IOperation flagOperation)
+ {
+ if (enumValueOperation.Type is null || flagOperation.Type is null)
+ return false;
+
+ if (!enumValueOperation.Type.IsEnumeration())
+ return false;
+
+ if (!flagOperation.Type.IsEnumeration())
+ return false;
+
+ return enumValueOperation.Type.IsEqualTo(flagOperation.Type);
+ }
+
+ private sealed record HasFlagPattern(ExpressionSyntax OperationExpression, ExpressionSyntax EnumValueExpression, ExpressionSyntax FlagExpression, bool Negate)
+ {
+ public TextSpan OperationSpan => OperationExpression.Span;
+ }
+}
diff --git a/src/Meziantou.Analyzer.Pack/configuration/default.editorconfig b/src/Meziantou.Analyzer.Pack/configuration/default.editorconfig
index c1f0232b..22060bd0 100644
--- a/src/Meziantou.Analyzer.Pack/configuration/default.editorconfig
+++ b/src/Meziantou.Analyzer.Pack/configuration/default.editorconfig
@@ -568,3 +568,6 @@ dotnet_diagnostic.MA0190.severity = suggestion
# MA0191: Do not use the null-forgiving operator
dotnet_diagnostic.MA0191.severity = none
+
+# MA0192: Use HasFlag instead of bitwise checks
+dotnet_diagnostic.MA0192.severity = none
diff --git a/src/Meziantou.Analyzer.Pack/configuration/none.editorconfig b/src/Meziantou.Analyzer.Pack/configuration/none.editorconfig
index 3cc8e7a3..27bd6318 100644
--- a/src/Meziantou.Analyzer.Pack/configuration/none.editorconfig
+++ b/src/Meziantou.Analyzer.Pack/configuration/none.editorconfig
@@ -568,3 +568,6 @@ dotnet_diagnostic.MA0190.severity = none
# MA0191: Do not use the null-forgiving operator
dotnet_diagnostic.MA0191.severity = none
+
+# MA0192: Use HasFlag instead of bitwise checks
+dotnet_diagnostic.MA0192.severity = none
diff --git a/src/Meziantou.Analyzer/RuleIdentifiers.cs b/src/Meziantou.Analyzer/RuleIdentifiers.cs
index 7d244575..f94bd899 100755
--- a/src/Meziantou.Analyzer/RuleIdentifiers.cs
+++ b/src/Meziantou.Analyzer/RuleIdentifiers.cs
@@ -191,6 +191,7 @@ internal static class RuleIdentifiers
public const string UseInlineArrayInsteadOfFixedBuffer = "MA0189";
public const string UsePartialPropertyInsteadOfPartialMethodForGeneratedRegex = "MA0190";
public const string DoNotUseNullForgiveness = "MA0191";
+ public const string UseHasFlagMethod = "MA0192";
public static string GetHelpUri(string identifier)
{
diff --git a/src/Meziantou.Analyzer/Rules/UseHasFlagMethodAnalyzer.cs b/src/Meziantou.Analyzer/Rules/UseHasFlagMethodAnalyzer.cs
new file mode 100644
index 00000000..a59a983b
--- /dev/null
+++ b/src/Meziantou.Analyzer/Rules/UseHasFlagMethodAnalyzer.cs
@@ -0,0 +1,173 @@
+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 UseHasFlagMethodAnalyzer : DiagnosticAnalyzer
+{
+ private static readonly DiagnosticDescriptor Rule = new(
+ RuleIdentifiers.UseHasFlagMethod,
+ title: "Use HasFlag instead of bitwise checks",
+ messageFormat: "Use HasFlag instead of bitwise checks",
+ RuleCategories.Usage,
+ DiagnosticSeverity.Info,
+ isEnabledByDefault: false,
+ description: "",
+ helpLinkUri: RuleIdentifiers.GetHelpUri(RuleIdentifiers.UseHasFlagMethod));
+
+ public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule);
+
+ public override void Initialize(AnalysisContext context)
+ {
+ context.EnableConcurrentExecution();
+ context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
+
+ context.RegisterOperationAction(AnalyzeBinary, OperationKind.Binary);
+ context.RegisterOperationAction(AnalyzeIsPattern, OperationKind.IsPattern);
+ }
+
+ private static void AnalyzeBinary(OperationAnalysisContext context)
+ {
+ var operation = (IBinaryOperation)context.Operation;
+ if (operation.OperatorKind is BinaryOperatorKind.Equals or BinaryOperatorKind.NotEquals &&
+ TryGetHasFlagPattern(operation, out _))
+ {
+ context.ReportDiagnostic(Rule, operation);
+ }
+ }
+
+ private static void AnalyzeIsPattern(OperationAnalysisContext context)
+ {
+ var operation = (IIsPatternOperation)context.Operation;
+ if (TryGetHasFlagPattern(operation, out _))
+ {
+ context.ReportDiagnostic(Rule, operation);
+ }
+ }
+
+ private static bool TryGetHasFlagPattern(IOperation operation, [NotNullWhen(true)] out HasFlagPattern? pattern)
+ {
+ if (operation is IBinaryOperation { OperatorKind: BinaryOperatorKind.Equals or BinaryOperatorKind.NotEquals } binaryOperation)
+ {
+ pattern = GetFromBinaryComparison(binaryOperation);
+ return pattern is not null;
+ }
+
+ if (operation is IIsPatternOperation
+ {
+ Value: IBinaryOperation { OperatorKind: BinaryOperatorKind.And } andOperation,
+ Pattern: IPatternOperation patternOperation,
+ })
+ {
+ if (TryGetComparedOperand(patternOperation, out var comparedOperand))
+ {
+ pattern = GetFromBitwiseAnd(andOperation, comparedOperand);
+ return pattern is not null;
+ }
+ }
+
+ pattern = null;
+ return false;
+ }
+
+ private static bool TryGetComparedOperand(IPatternOperation patternOperation, [NotNullWhen(true)] out IOperation? comparedOperand)
+ {
+ if (patternOperation is IConstantPatternOperation { Value: not null } constantPattern)
+ {
+ comparedOperand = constantPattern.Value;
+ return true;
+ }
+
+ if (patternOperation is INegatedPatternOperation { Pattern: IConstantPatternOperation { Value: not null } negatedConstantPattern })
+ {
+ comparedOperand = negatedConstantPattern.Value;
+ return true;
+ }
+
+ comparedOperand = null;
+ return false;
+ }
+
+ private static HasFlagPattern? GetFromBinaryComparison(IBinaryOperation operation)
+ {
+ var leftOperand = operation.LeftOperand.UnwrapImplicitConversionOperations();
+ var rightOperand = operation.RightOperand.UnwrapImplicitConversionOperations();
+
+ if (leftOperand is IBinaryOperation { OperatorKind: BinaryOperatorKind.And } leftBitwiseAnd)
+ {
+ var pattern = GetFromBitwiseAnd(leftBitwiseAnd, rightOperand);
+ if (pattern is not null)
+ return pattern;
+ }
+
+ if (rightOperand is IBinaryOperation { OperatorKind: BinaryOperatorKind.And } rightBitwiseAnd)
+ {
+ var pattern = GetFromBitwiseAnd(rightBitwiseAnd, leftOperand);
+ if (pattern is not null)
+ return pattern;
+ }
+
+ return null;
+ }
+
+ private static HasFlagPattern? GetFromBitwiseAnd(IBinaryOperation bitwiseAndOperation, IOperation comparedOperand)
+ {
+ var leftOperand = bitwiseAndOperation.LeftOperand.UnwrapImplicitConversionOperations();
+ var rightOperand = bitwiseAndOperation.RightOperand.UnwrapImplicitConversionOperations();
+ comparedOperand = comparedOperand.UnwrapImplicitConversionOperations();
+
+ if (TryGetEnumFlagReference(rightOperand, comparedOperand, out var flagOperation) &&
+ IsValidPattern(leftOperand, flagOperation))
+ {
+ return new(leftOperand, flagOperation);
+ }
+
+ if (TryGetEnumFlagReference(leftOperand, comparedOperand, out flagOperation) &&
+ IsValidPattern(rightOperand, flagOperation))
+ {
+ return new(rightOperand, flagOperation);
+ }
+
+ return null;
+ }
+
+ private static bool TryGetEnumFlagReference(IOperation potentialFlag, IOperation comparedOperand, [NotNullWhen(true)] out IFieldReferenceOperation? flagOperation)
+ {
+ potentialFlag = potentialFlag.UnwrapImplicitConversionOperations();
+ comparedOperand = comparedOperand.UnwrapImplicitConversionOperations();
+
+ if (potentialFlag is IFieldReferenceOperation firstFieldReference &&
+ comparedOperand is IFieldReferenceOperation secondFieldReference &&
+ firstFieldReference.Field.HasConstantValue &&
+ secondFieldReference.Field.HasConstantValue &&
+ firstFieldReference.Field.IsEqualTo(secondFieldReference.Field) &&
+ firstFieldReference.Field.ContainingType.IsEnumeration())
+ {
+ flagOperation = secondFieldReference;
+ return true;
+ }
+
+ flagOperation = null;
+ return false;
+ }
+
+ private static bool IsValidPattern(IOperation enumValueOperation, IOperation flagOperation)
+ {
+ if (enumValueOperation.Type is null || flagOperation.Type is null)
+ return false;
+
+ if (!enumValueOperation.Type.IsEnumeration())
+ return false;
+
+ if (!flagOperation.Type.IsEnumeration())
+ return false;
+
+ return enumValueOperation.Type.IsEqualTo(flagOperation.Type);
+ }
+
+ private sealed record HasFlagPattern(IOperation EnumValueOperation, IOperation FlagOperation);
+}
diff --git a/tests/Meziantou.Analyzer.Test/Rules/UseHasFlagMethodAnalyzerTests.cs b/tests/Meziantou.Analyzer.Test/Rules/UseHasFlagMethodAnalyzerTests.cs
new file mode 100644
index 00000000..1c025384
--- /dev/null
+++ b/tests/Meziantou.Analyzer.Test/Rules/UseHasFlagMethodAnalyzerTests.cs
@@ -0,0 +1,271 @@
+using Meziantou.Analyzer.Rules;
+using Microsoft.CodeAnalysis;
+using TestHelper;
+
+namespace Meziantou.Analyzer.Test.Rules;
+
+public sealed class UseHasFlagMethodAnalyzerTests
+{
+ private static ProjectBuilder CreateProjectBuilder()
+ {
+ return new ProjectBuilder()
+ .WithAnalyzer()
+ .WithCodeFixProvider();
+ }
+
+ [Fact]
+ public async Task EqualityCheck_ReportDiagnostic()
+ {
+ await CreateProjectBuilder()
+ .WithSourceCode("""
+ [System.Flags]
+ enum MyEnum
+ {
+ None = 0,
+ Flag1 = 1,
+ Flag2 = 2,
+ }
+
+ class Sample
+ {
+ bool M(MyEnum value) => [|(value & MyEnum.Flag1) == MyEnum.Flag1|];
+ }
+ """)
+ .ShouldFixCodeWith("""
+ [System.Flags]
+ enum MyEnum
+ {
+ None = 0,
+ Flag1 = 1,
+ Flag2 = 2,
+ }
+
+ class Sample
+ {
+ bool M(MyEnum value) => value.HasFlag(MyEnum.Flag1);
+ }
+ """)
+ .ValidateAsync();
+ }
+
+ [Fact]
+ public async Task EqualityCheck_ReversedAndOperands_ReportDiagnostic()
+ {
+ await CreateProjectBuilder()
+ .WithSourceCode("""
+ [System.Flags]
+ enum MyEnum
+ {
+ None = 0,
+ Flag1 = 1,
+ }
+
+ class Sample
+ {
+ bool M(MyEnum value) => [|(MyEnum.Flag1 & value) == MyEnum.Flag1|];
+ }
+ """)
+ .ShouldFixCodeWith("""
+ [System.Flags]
+ enum MyEnum
+ {
+ None = 0,
+ Flag1 = 1,
+ }
+
+ class Sample
+ {
+ bool M(MyEnum value) => value.HasFlag(MyEnum.Flag1);
+ }
+ """)
+ .ValidateAsync();
+ }
+
+ [Fact]
+ public async Task IsPatternCheck_ReportDiagnostic()
+ {
+ await CreateProjectBuilder()
+ .WithSourceCode("""
+ [System.Flags]
+ enum MyEnum
+ {
+ None = 0,
+ Flag1 = 1,
+ Flag2 = 2,
+ }
+
+ class Sample
+ {
+ bool M(MyEnum value) => [|(value & MyEnum.Flag1) is MyEnum.Flag1|];
+ }
+ """)
+ .ShouldFixCodeWith("""
+ [System.Flags]
+ enum MyEnum
+ {
+ None = 0,
+ Flag1 = 1,
+ Flag2 = 2,
+ }
+
+ class Sample
+ {
+ bool M(MyEnum value) => value.HasFlag(MyEnum.Flag1);
+ }
+ """)
+ .ValidateAsync();
+ }
+
+ [Fact]
+ public async Task NotEqualsCheck_ReportDiagnostic()
+ {
+ await CreateProjectBuilder()
+ .WithSourceCode("""
+ [System.Flags]
+ enum MyEnum
+ {
+ None = 0,
+ Flag1 = 1,
+ Flag2 = 2,
+ }
+
+ class Sample
+ {
+ bool M(MyEnum value) => [|(value & MyEnum.Flag1) != MyEnum.Flag1|];
+ }
+ """)
+ .ShouldFixCodeWith("""
+ [System.Flags]
+ enum MyEnum
+ {
+ None = 0,
+ Flag1 = 1,
+ Flag2 = 2,
+ }
+
+ class Sample
+ {
+ bool M(MyEnum value) => !value.HasFlag(MyEnum.Flag1);
+ }
+ """)
+ .ValidateAsync();
+ }
+
+ [Fact]
+ public async Task IsNotPatternCheck_ReportDiagnostic()
+ {
+ await CreateProjectBuilder()
+ .WithSourceCode("""
+ [System.Flags]
+ enum MyEnum
+ {
+ None = 0,
+ Flag1 = 1,
+ Flag2 = 2,
+ }
+
+ class Sample
+ {
+ bool M(MyEnum value) => [|(value & MyEnum.Flag1) is not MyEnum.Flag1|];
+ }
+ """)
+ .ShouldFixCodeWith("""
+ [System.Flags]
+ enum MyEnum
+ {
+ None = 0,
+ Flag1 = 1,
+ Flag2 = 2,
+ }
+
+ class Sample
+ {
+ bool M(MyEnum value) => !value.HasFlag(MyEnum.Flag1);
+ }
+ """)
+ .ValidateAsync();
+ }
+
+ [Fact]
+ public async Task DifferentFlag_NoDiagnostic()
+ {
+ await CreateProjectBuilder()
+ .WithSourceCode("""
+ [System.Flags]
+ enum MyEnum
+ {
+ None = 0,
+ Flag1 = 1,
+ Flag2 = 2,
+ }
+
+ class Sample
+ {
+ bool M(MyEnum value) => (value & MyEnum.Flag1) == MyEnum.Flag2;
+ }
+ """)
+ .ValidateAsync();
+ }
+
+ [Fact]
+ public async Task DifferentFlag_IsNotPattern_NoDiagnostic()
+ {
+ await CreateProjectBuilder()
+ .WithSourceCode("""
+ [System.Flags]
+ enum MyEnum
+ {
+ None = 0,
+ Flag1 = 1,
+ Flag2 = 2,
+ }
+
+ class Sample
+ {
+ bool M(MyEnum value) => (value & MyEnum.Flag1) is not MyEnum.Flag2;
+ }
+ """)
+ .ValidateAsync();
+ }
+
+ [Fact]
+ public async Task IntegerBitwiseCheck_NoDiagnostic()
+ {
+ await CreateProjectBuilder()
+ .WithSourceCode("""
+ class Sample
+ {
+ bool M(int value) => (value & 1) == 1;
+ }
+ """)
+ .ValidateAsync();
+ }
+
+ [Fact]
+ public async Task NullableEnum_NoDiagnostic()
+ {
+ await CreateProjectBuilder()
+ .WithSourceCode("""
+ [System.Flags]
+ enum MyEnum
+ {
+ None = 0,
+ Flag1 = 1,
+ }
+
+ class Sample
+ {
+ bool M(MyEnum? value) => (value & MyEnum.Flag1) == MyEnum.Flag1;
+ }
+ """)
+ .ValidateAsync();
+ }
+
+ [Fact]
+ public void Rule_SeverityAndDefault()
+ {
+ var rule = new UseHasFlagMethodAnalyzer().SupportedDiagnostics[0];
+ Assert.Equal(DiagnosticSeverity.Info, rule.DefaultSeverity);
+ Assert.False(rule.IsEnabledByDefault);
+ }
+}