diff --git a/README.md b/README.md
index 96dd5830f..6112f70d4 100755
--- a/README.md
+++ b/README.md
@@ -219,6 +219,7 @@ If you are already using other analyzers, you can check [which rules are duplica
|[MA0198](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0198.md)|Design|Specify cref for ambiguous inheritdoc on types|⚠️|✔️|✔️|
|[MA0199](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0199.md)|Design|Do not use inheritdoc on types without inheritance source|⚠️|✔️|❌|
|[MA0200](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0200.md)|Usage|Do not use empty property patterns with non-nullable value types|ℹ️|✔️|✔️|
+|[MA0201](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0201.md)|Usage|Do not use zero-valued enum flags in flag checks|⚠️|✔️|❌|
diff --git a/docs/README.md b/docs/README.md
index c62941ee2..e4320c0f1 100755
--- a/docs/README.md
+++ b/docs/README.md
@@ -199,6 +199,7 @@
|[MA0198](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0198.md)|Design|Specify cref for ambiguous inheritdoc on types|⚠️|✔️|✔️|
|[MA0199](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0199.md)|Design|Do not use inheritdoc on types without inheritance source|⚠️|✔️|❌|
|[MA0200](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0200.md)|Usage|Do not use empty property patterns with non-nullable value types|ℹ️|✔️|✔️|
+|[MA0201](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0201.md)|Usage|Do not use zero-valued enum flags in flag checks|⚠️|✔️|❌|
|Id|Suppressed rule|Justification|
|--|---------------|-------------|
@@ -811,6 +812,9 @@ dotnet_diagnostic.MA0199.severity = warning
# MA0200: Do not use empty property patterns with non-nullable value types
dotnet_diagnostic.MA0200.severity = suggestion
+
+# MA0201: Do not use zero-valued enum flags in flag checks
+dotnet_diagnostic.MA0201.severity = warning
```
# .editorconfig - all rules disabled
@@ -1409,4 +1413,7 @@ dotnet_diagnostic.MA0199.severity = none
# MA0200: Do not use empty property patterns with non-nullable value types
dotnet_diagnostic.MA0200.severity = none
+
+# MA0201: Do not use zero-valued enum flags in flag checks
+dotnet_diagnostic.MA0201.severity = none
```
diff --git a/docs/Rules/MA0192.md b/docs/Rules/MA0192.md
index a2202fa8e..cdffb96af 100644
--- a/docs/Rules/MA0192.md
+++ b/docs/Rules/MA0192.md
@@ -18,6 +18,8 @@ This rule reports patterns such as:
For comparisons against `0`, the enum member used in the bitwise `&` must be a single-bit value (for example `1`, `2`, `4`, `8`, ...). Combined values are ignored.
+Zero-valued enum members are not reported by this rule. They are covered by [MA0201](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0201.md).
+
## Non-compliant code
````csharp
diff --git a/docs/Rules/MA0201.md b/docs/Rules/MA0201.md
new file mode 100644
index 000000000..201423b34
--- /dev/null
+++ b/docs/Rules/MA0201.md
@@ -0,0 +1,46 @@
+# MA0201 - Do not use zero-valued enum flags in flag checks
+
+Source: [UseHasFlagMethodAnalyzer.cs](https://github.com/meziantou/Meziantou.Analyzer/blob/main/src/Meziantou.Analyzer/Rules/UseHasFlagMethodAnalyzer.cs)
+
+
+Using a zero-valued enum member (`None = 0`) as a flag to test always produces a constant result.
+
+This rule reports patterns such as:
+
+- `(value & MyEnum.None) == MyEnum.None` (always `true`)
+- `(value & MyEnum.None) != MyEnum.None` (always `false`)
+- `(value & MyEnum.None) is MyEnum.None` (always `true`)
+- `(value & MyEnum.None) is not MyEnum.None` (always `false`)
+- `value.HasFlag(MyEnum.None)` (always `true`)
+
+## Non-compliant code
+
+````csharp
+[System.Flags]
+enum MyEnum
+{
+ None = 0,
+ Flag1 = 1,
+}
+
+bool M(MyEnum value)
+{
+ return (value & MyEnum.None) == MyEnum.None;
+}
+````
+
+## Compliant code
+
+````csharp
+[System.Flags]
+enum MyEnum
+{
+ None = 0,
+ Flag1 = 1,
+}
+
+bool M(MyEnum value)
+{
+ return value.HasFlag(MyEnum.Flag1);
+}
+````
diff --git a/src/Meziantou.Analyzer.Pack/configuration/all-errors.editorconfig b/src/Meziantou.Analyzer.Pack/configuration/all-errors.editorconfig
index 0a0867114..65b502cbc 100644
--- a/src/Meziantou.Analyzer.Pack/configuration/all-errors.editorconfig
+++ b/src/Meziantou.Analyzer.Pack/configuration/all-errors.editorconfig
@@ -595,3 +595,6 @@ dotnet_diagnostic.MA0199.severity = error
# MA0200: Do not use empty property patterns with non-nullable value types
dotnet_diagnostic.MA0200.severity = error
+
+# MA0201: Do not use zero-valued enum flags in flag checks
+dotnet_diagnostic.MA0201.severity = error
diff --git a/src/Meziantou.Analyzer.Pack/configuration/all-suggestions.editorconfig b/src/Meziantou.Analyzer.Pack/configuration/all-suggestions.editorconfig
index 95de6e5a3..09aa7c3fe 100644
--- a/src/Meziantou.Analyzer.Pack/configuration/all-suggestions.editorconfig
+++ b/src/Meziantou.Analyzer.Pack/configuration/all-suggestions.editorconfig
@@ -595,3 +595,6 @@ dotnet_diagnostic.MA0199.severity = suggestion
# MA0200: Do not use empty property patterns with non-nullable value types
dotnet_diagnostic.MA0200.severity = suggestion
+
+# MA0201: Do not use zero-valued enum flags in flag checks
+dotnet_diagnostic.MA0201.severity = suggestion
diff --git a/src/Meziantou.Analyzer.Pack/configuration/all-warnings.editorconfig b/src/Meziantou.Analyzer.Pack/configuration/all-warnings.editorconfig
index 8661f041d..fea58ce76 100644
--- a/src/Meziantou.Analyzer.Pack/configuration/all-warnings.editorconfig
+++ b/src/Meziantou.Analyzer.Pack/configuration/all-warnings.editorconfig
@@ -595,3 +595,6 @@ dotnet_diagnostic.MA0199.severity = warning
# MA0200: Do not use empty property patterns with non-nullable value types
dotnet_diagnostic.MA0200.severity = warning
+
+# MA0201: Do not use zero-valued enum flags in flag checks
+dotnet_diagnostic.MA0201.severity = warning
diff --git a/src/Meziantou.Analyzer.Pack/configuration/default.editorconfig b/src/Meziantou.Analyzer.Pack/configuration/default.editorconfig
index b6bd05b52..b17b312ab 100644
--- a/src/Meziantou.Analyzer.Pack/configuration/default.editorconfig
+++ b/src/Meziantou.Analyzer.Pack/configuration/default.editorconfig
@@ -595,3 +595,6 @@ dotnet_diagnostic.MA0199.severity = warning
# MA0200: Do not use empty property patterns with non-nullable value types
dotnet_diagnostic.MA0200.severity = suggestion
+
+# MA0201: Do not use zero-valued enum flags in flag checks
+dotnet_diagnostic.MA0201.severity = warning
diff --git a/src/Meziantou.Analyzer.Pack/configuration/none.editorconfig b/src/Meziantou.Analyzer.Pack/configuration/none.editorconfig
index 9f50e81ff..32beb6a64 100644
--- a/src/Meziantou.Analyzer.Pack/configuration/none.editorconfig
+++ b/src/Meziantou.Analyzer.Pack/configuration/none.editorconfig
@@ -595,3 +595,6 @@ dotnet_diagnostic.MA0199.severity = none
# MA0200: Do not use empty property patterns with non-nullable value types
dotnet_diagnostic.MA0200.severity = none
+
+# MA0201: Do not use zero-valued enum flags in flag checks
+dotnet_diagnostic.MA0201.severity = none
diff --git a/src/Meziantou.Analyzer/RuleIdentifiers.cs b/src/Meziantou.Analyzer/RuleIdentifiers.cs
index 6f7317178..a33eca897 100755
--- a/src/Meziantou.Analyzer/RuleIdentifiers.cs
+++ b/src/Meziantou.Analyzer/RuleIdentifiers.cs
@@ -200,6 +200,7 @@ internal static class RuleIdentifiers
public const string InheritdocShouldNotBeAmbiguousOnTypes = "MA0198";
public const string InheritdocShouldHaveSourceOnTypes = "MA0199";
public const string DoNotUseEmptyPropertyPatternOnNonNullableValueType = "MA0200";
+ public const string DoNotUseZeroValuedEnumFlagsInFlagChecks = "MA0201";
public static string GetHelpUri(string identifier)
{
diff --git a/src/Meziantou.Analyzer/Rules/UseHasFlagMethodAnalyzer.cs b/src/Meziantou.Analyzer/Rules/UseHasFlagMethodAnalyzer.cs
index 850612796..a42e33dfd 100644
--- a/src/Meziantou.Analyzer/Rules/UseHasFlagMethodAnalyzer.cs
+++ b/src/Meziantou.Analyzer/Rules/UseHasFlagMethodAnalyzer.cs
@@ -9,7 +9,9 @@ namespace Meziantou.Analyzer.Rules;
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public sealed class UseHasFlagMethodAnalyzer : DiagnosticAnalyzer
{
- private static readonly DiagnosticDescriptor Rule = new(
+ private const string EnumHasFlagMethodDocumentationId = "M:System.Enum.HasFlag(System.Enum)";
+
+ private static readonly DiagnosticDescriptor UseHasFlagRule = new(
RuleIdentifiers.UseHasFlagMethod,
title: "Use HasFlag instead of bitwise checks",
messageFormat: "Use HasFlag instead of bitwise checks",
@@ -19,76 +21,249 @@ public sealed class UseHasFlagMethodAnalyzer : DiagnosticAnalyzer
description: "",
helpLinkUri: RuleIdentifiers.GetHelpUri(RuleIdentifiers.UseHasFlagMethod));
- public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule);
+ private static readonly DiagnosticDescriptor DoNotUseZeroValuedEnumFlagsInFlagChecksRule = new(
+ RuleIdentifiers.DoNotUseZeroValuedEnumFlagsInFlagChecks,
+ title: "Do not use zero-valued enum flags in flag checks",
+ messageFormat: "This flag check is always '{0}' because the checked flag value is 0",
+ RuleCategories.Usage,
+ DiagnosticSeverity.Warning,
+ isEnabledByDefault: true,
+ description: "",
+ helpLinkUri: RuleIdentifiers.GetHelpUri(RuleIdentifiers.DoNotUseZeroValuedEnumFlagsInFlagChecks));
+
+ public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(UseHasFlagRule, DoNotUseZeroValuedEnumFlagsInFlagChecksRule);
public override void Initialize(AnalysisContext context)
{
context.EnableConcurrentExecution();
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
- context.RegisterOperationAction(AnalyzeBinary, OperationKind.Binary);
- context.RegisterOperationAction(AnalyzeIsPattern, OperationKind.IsPattern);
+ context.RegisterCompilationStartAction(compilationContext =>
+ {
+ var hasFlagMethod = DocumentationCommentId.GetFirstSymbolForDeclarationId(EnumHasFlagMethodDocumentationId, compilationContext.Compilation) as IMethodSymbol;
+
+ compilationContext.RegisterOperationAction(AnalyzeBinary, OperationKind.Binary);
+ compilationContext.RegisterOperationAction(AnalyzeIsPattern, OperationKind.IsPattern);
+
+ if (hasFlagMethod is not null)
+ {
+ compilationContext.RegisterOperationAction(context => AnalyzeInvocation(context, hasFlagMethod), OperationKind.Invocation);
+ }
+ });
}
private static void AnalyzeBinary(OperationAnalysisContext context)
{
var operation = (IBinaryOperation)context.Operation;
- if (operation.OperatorKind is BinaryOperatorKind.Equals or BinaryOperatorKind.NotEquals &&
- TryGetHasFlagPattern(operation, out _))
+ if (operation.OperatorKind is not (BinaryOperatorKind.Equals or BinaryOperatorKind.NotEquals))
+ return;
+
+ if (TryGetZeroValuedFlagPattern(operation, out var isAlwaysTrue))
+ {
+ context.ReportDiagnostic(DoNotUseZeroValuedEnumFlagsInFlagChecksRule, operation, isAlwaysTrue ? "true" : "false");
+ return;
+ }
+
+ if (TryGetHasFlagPattern(operation, out _))
{
- context.ReportDiagnostic(Rule, operation);
+ context.ReportDiagnostic(UseHasFlagRule, operation);
}
}
private static void AnalyzeIsPattern(OperationAnalysisContext context)
{
var operation = (IIsPatternOperation)context.Operation;
+ if (TryGetZeroValuedFlagPattern(operation, out var isAlwaysTrue))
+ {
+ context.ReportDiagnostic(DoNotUseZeroValuedEnumFlagsInFlagChecksRule, operation, isAlwaysTrue ? "true" : "false");
+ return;
+ }
+
if (TryGetHasFlagPattern(operation, out _))
{
- context.ReportDiagnostic(Rule, operation);
+ context.ReportDiagnostic(UseHasFlagRule, operation);
}
}
- private static bool TryGetHasFlagPattern(IOperation operation, [NotNullWhen(true)] out HasFlagPattern? pattern)
+ private static void AnalyzeInvocation(OperationAnalysisContext context, IMethodSymbol hasFlagMethod)
+ {
+ var operation = (IInvocationOperation)context.Operation;
+ if (!IsZeroValuedHasFlagInvocation(operation, hasFlagMethod))
+ return;
+
+ context.ReportDiagnostic(DoNotUseZeroValuedEnumFlagsInFlagChecksRule, operation, "true");
+ }
+
+ private static bool TryGetZeroValuedFlagPattern(IOperation operation, out bool isAlwaysTrue)
{
if (operation is IBinaryOperation { OperatorKind: BinaryOperatorKind.Equals or BinaryOperatorKind.NotEquals } binaryOperation)
{
- pattern = GetFromBinaryComparison(binaryOperation);
- return pattern is not null;
+ if (TryGetZeroValuedFlagPattern(binaryOperation))
+ {
+ isAlwaysTrue = binaryOperation.OperatorKind is BinaryOperatorKind.Equals;
+ return true;
+ }
+ }
+ else if (operation is IIsPatternOperation
+ {
+ Value: IBinaryOperation { OperatorKind: BinaryOperatorKind.And } andOperation,
+ Pattern: var patternOperation,
+ } &&
+ TryGetComparedOperand(patternOperation, out var comparedOperand, out var negateResult) &&
+ IsZeroValuedFlagCheck(andOperation, comparedOperand))
+ {
+ isAlwaysTrue = !negateResult;
+ return true;
}
- if (operation is IIsPatternOperation
- {
- Value: IBinaryOperation { OperatorKind: BinaryOperatorKind.And } andOperation,
- Pattern: IPatternOperation patternOperation,
- })
+ isAlwaysTrue = false;
+ return false;
+ }
+
+ private static bool TryGetZeroValuedFlagPattern(IBinaryOperation operation)
+ {
+ var leftOperand = operation.LeftOperand.UnwrapImplicitConversionOperations();
+ var rightOperand = operation.RightOperand.UnwrapImplicitConversionOperations();
+ if (leftOperand is IBinaryOperation { OperatorKind: BinaryOperatorKind.And } leftBitwiseAnd &&
+ IsZeroValuedFlagCheck(leftBitwiseAnd, rightOperand))
{
- if (TryGetComparedOperand(patternOperation, out var comparedOperand))
- {
- pattern = GetFromBitwiseAnd(andOperation, comparedOperand);
- return pattern is not null;
- }
+ return true;
+ }
+
+ if (rightOperand is IBinaryOperation { OperatorKind: BinaryOperatorKind.And } rightBitwiseAnd &&
+ IsZeroValuedFlagCheck(rightBitwiseAnd, leftOperand))
+ {
+ return true;
+ }
+
+ return false;
+ }
+
+ private static bool IsZeroValuedFlagCheck(IBinaryOperation bitwiseAndOperation, IOperation comparedOperand)
+ {
+ var leftOperand = bitwiseAndOperation.LeftOperand.UnwrapImplicitConversionOperations();
+ var rightOperand = bitwiseAndOperation.RightOperand.UnwrapImplicitConversionOperations();
+ comparedOperand = comparedOperand.UnwrapImplicitConversionOperations();
+ if (TryGetZeroValuedEnumFlagType(rightOperand, comparedOperand, out var enumType) &&
+ IsValidPattern(leftOperand, enumType))
+ {
+ return true;
+ }
+
+ if (TryGetZeroValuedEnumFlagType(leftOperand, comparedOperand, out enumType) &&
+ IsValidPattern(rightOperand, enumType))
+ {
+ return true;
}
- pattern = null;
return false;
}
- private static bool TryGetComparedOperand(IPatternOperation patternOperation, [NotNullWhen(true)] out IOperation? comparedOperand)
+ private static bool TryGetZeroValuedEnumFlagType(IOperation potentialFlag, IOperation comparedOperand, out ITypeSymbol enumType)
+ {
+ potentialFlag = potentialFlag.UnwrapImplicitConversionOperations();
+ comparedOperand = comparedOperand.UnwrapImplicitConversionOperations();
+ if (potentialFlag is not IFieldReferenceOperation firstFieldReference ||
+ !firstFieldReference.Field.HasConstantValue ||
+ !firstFieldReference.Field.ContainingType.IsEnumeration() ||
+ !NumericHelpers.IsZero(firstFieldReference.Field.ConstantValue))
+ {
+ enumType = null!;
+ return false;
+ }
+
+ if (!IsComparedOperandZero(comparedOperand, firstFieldReference.Field.ContainingType))
+ {
+ enumType = null!;
+ return false;
+ }
+
+ enumType = firstFieldReference.Field.ContainingType;
+ return true;
+ }
+
+ private static bool IsComparedOperandZero(IOperation comparedOperand, ITypeSymbol enumType)
+ {
+ comparedOperand = comparedOperand.UnwrapImplicitConversionOperations();
+ if (comparedOperand is IFieldReferenceOperation comparedFieldReference &&
+ comparedFieldReference.Field.HasConstantValue &&
+ comparedFieldReference.Field.ContainingType.IsEqualTo(enumType) &&
+ NumericHelpers.IsZero(comparedFieldReference.Field.ConstantValue))
+ {
+ return true;
+ }
+
+ if (comparedOperand.IsConstantZero())
+ return true;
+
+ if (comparedOperand.Type?.IsEqualTo(enumType) is true &&
+ comparedOperand.ConstantValue.HasValue &&
+ NumericHelpers.IsZero(comparedOperand.ConstantValue.Value))
+ {
+ return true;
+ }
+
+ return false;
+ }
+
+ private static bool IsZeroValuedHasFlagInvocation(IInvocationOperation operation, IMethodSymbol hasFlagMethod)
+ {
+ if (!operation.TargetMethod.OriginalDefinition.IsEqualTo(hasFlagMethod))
+ return false;
+
+ if (operation.Arguments.Length is not 1 || operation.Instance is null)
+ return false;
+
+ var enumValueOperation = operation.Instance.UnwrapImplicitConversionOperations();
+ if (enumValueOperation.Type is null || !enumValueOperation.Type.IsEnumeration())
+ return false;
+
+ return IsComparedOperandZero(operation.Arguments[0].Value, enumValueOperation.Type);
+ }
+
+ private static bool TryGetComparedOperand(IPatternOperation patternOperation, [NotNullWhen(true)] out IOperation? comparedOperand, out bool negateResult)
{
if (patternOperation is IConstantPatternOperation { Value: not null } constantPattern)
{
comparedOperand = constantPattern.Value;
+ negateResult = false;
return true;
}
if (patternOperation is INegatedPatternOperation { Pattern: IConstantPatternOperation { Value: not null } negatedConstantPattern })
{
comparedOperand = negatedConstantPattern.Value;
+ negateResult = true;
return true;
}
comparedOperand = null;
+ negateResult = false;
+ 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,
+ })
+ {
+ if (TryGetComparedOperand(patternOperation, out var comparedOperand, out _))
+ {
+ pattern = GetFromBitwiseAnd(andOperation, comparedOperand);
+ return pattern is not null;
+ }
+ }
+
+ pattern = null;
return false;
}
@@ -146,7 +321,8 @@ private static bool TryGetEnumFlagReference(IOperation potentialFlag, IOperation
{
if (comparedOperand is IFieldReferenceOperation secondFieldReference &&
secondFieldReference.Field.HasConstantValue &&
- firstFieldReference.Field.IsEqualTo(secondFieldReference.Field))
+ firstFieldReference.Field.IsEqualTo(secondFieldReference.Field) &&
+ !NumericHelpers.IsZero(firstFieldReference.Field.ConstantValue))
{
flagOperation = secondFieldReference;
return true;
@@ -165,16 +341,24 @@ private static bool TryGetEnumFlagReference(IOperation potentialFlag, IOperation
private static bool IsValidPattern(IOperation enumValueOperation, IOperation flagOperation)
{
- if (enumValueOperation.Type is null || flagOperation.Type is null)
+ if (flagOperation.Type is null)
+ return false;
+
+ return IsValidPattern(enumValueOperation, flagOperation.Type);
+ }
+
+ private static bool IsValidPattern(IOperation enumValueOperation, ITypeSymbol flagType)
+ {
+ if (enumValueOperation.Type is null)
return false;
if (!enumValueOperation.Type.IsEnumeration())
return false;
- if (!flagOperation.Type.IsEnumeration())
+ if (!flagType.IsEnumeration())
return false;
- return enumValueOperation.Type.IsEqualTo(flagOperation.Type);
+ return enumValueOperation.Type.IsEqualTo(flagType);
}
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
index dbb48ff87..b1cbde114 100644
--- a/tests/Meziantou.Analyzer.Test/Rules/UseHasFlagMethodAnalyzerTests.cs
+++ b/tests/Meziantou.Analyzer.Test/Rules/UseHasFlagMethodAnalyzerTests.cs
@@ -1,5 +1,4 @@
using Meziantou.Analyzer.Rules;
-using Microsoft.CodeAnalysis;
using TestHelper;
namespace Meziantou.Analyzer.Test.Rules;
@@ -28,7 +27,7 @@ enum MyEnum
class Sample
{
- bool M(MyEnum value) => [|(value & MyEnum.Flag1) == MyEnum.Flag1|];
+ bool M(MyEnum value) => {|MA0192:(value & MyEnum.Flag1) == MyEnum.Flag1|};
}
""")
.ShouldFixCodeWith("""
@@ -62,7 +61,7 @@ enum MyEnum
class Sample
{
- bool M(MyEnum value) => [|(MyEnum.Flag1 & value) == MyEnum.Flag1|];
+ bool M(MyEnum value) => {|MA0192:(MyEnum.Flag1 & value) == MyEnum.Flag1|};
}
""")
.ShouldFixCodeWith("""
@@ -96,7 +95,7 @@ enum MyEnum
class Sample
{
- bool M(MyEnum value) => [|(value & MyEnum.Flag1) is MyEnum.Flag1|];
+ bool M(MyEnum value) => {|MA0192:(value & MyEnum.Flag1) is MyEnum.Flag1|};
}
""")
.ShouldFixCodeWith("""
@@ -131,7 +130,7 @@ enum MyEnum
class Sample
{
- bool M(MyEnum value) => [|(value & MyEnum.Flag1) != MyEnum.Flag1|];
+ bool M(MyEnum value) => {|MA0192:(value & MyEnum.Flag1) != MyEnum.Flag1|};
}
""")
.ShouldFixCodeWith("""
@@ -166,7 +165,7 @@ enum MyEnum
class Sample
{
- bool M(MyEnum value) => [|(value & MyEnum.Flag1) is not MyEnum.Flag1|];
+ bool M(MyEnum value) => {|MA0192:(value & MyEnum.Flag1) is not MyEnum.Flag1|};
}
""")
.ShouldFixCodeWith("""
@@ -201,7 +200,7 @@ enum MyEnum
class Sample
{
- bool M(MyEnum value) => [|(value & MyEnum.Flag1) == 0|];
+ bool M(MyEnum value) => {|MA0192:(value & MyEnum.Flag1) == 0|};
}
""")
.ShouldFixCodeWith("""
@@ -236,7 +235,7 @@ enum MyEnum
class Sample
{
- bool M(MyEnum value) => [|(value & MyEnum.Flag1) != 0|];
+ bool M(MyEnum value) => {|MA0192:(value & MyEnum.Flag1) != 0|};
}
""")
.ShouldFixCodeWith("""
@@ -271,7 +270,7 @@ enum MyEnum
class Sample
{
- bool M(MyEnum value) => [|(value & MyEnum.Flag1) is 0|];
+ bool M(MyEnum value) => {|MA0192:(value & MyEnum.Flag1) is 0|};
}
""")
.ShouldFixCodeWith("""
@@ -306,7 +305,7 @@ enum MyEnum
class Sample
{
- bool M(MyEnum value) => [|(value & MyEnum.Flag1) is not 0|];
+ bool M(MyEnum value) => {|MA0192:(value & MyEnum.Flag1) is not 0|};
}
""")
.ShouldFixCodeWith("""
@@ -326,6 +325,146 @@ class Sample
.ValidateAsync();
}
+ [Fact]
+ public async Task ZeroFlagEqualityCheck_ReportDiagnostic()
+ {
+ await CreateProjectBuilder()
+ .WithSourceCode("""
+ [System.Flags]
+ enum MyEnum
+ {
+ None = 0,
+ Flag1 = 1,
+ }
+
+ class Sample
+ {
+ bool M(MyEnum value) => {|MA0201:(value & MyEnum.None) == MyEnum.None|};
+ }
+ """)
+ .ValidateAsync();
+ }
+
+ [Fact]
+ public async Task ZeroLiteralEqualityCheck_ReportDiagnostic()
+ {
+ await CreateProjectBuilder()
+ .WithSourceCode("""
+ [System.Flags]
+ enum MyEnum
+ {
+ None = 0,
+ Flag1 = 1,
+ }
+
+ class Sample
+ {
+ bool M(MyEnum value) => {|MA0201:(value & MyEnum.None) == 0|};
+ }
+ """)
+ .ValidateAsync();
+ }
+
+ [Fact]
+ public async Task ZeroFlagNotEqualsCheck_ReportDiagnostic()
+ {
+ await CreateProjectBuilder()
+ .WithSourceCode("""
+ [System.Flags]
+ enum MyEnum
+ {
+ None = 0,
+ Flag1 = 1,
+ }
+
+ class Sample
+ {
+ bool M(MyEnum value) => {|MA0201:(value & MyEnum.None) != MyEnum.None|};
+ }
+ """)
+ .ValidateAsync();
+ }
+
+ [Fact]
+ public async Task ZeroFlagIsPatternCheck_ReportDiagnostic()
+ {
+ await CreateProjectBuilder()
+ .WithSourceCode("""
+ [System.Flags]
+ enum MyEnum
+ {
+ None = 0,
+ Flag1 = 1,
+ }
+
+ class Sample
+ {
+ bool M(MyEnum value) => {|MA0201:(value & MyEnum.None) is MyEnum.None|};
+ }
+ """)
+ .ValidateAsync();
+ }
+
+ [Fact]
+ public async Task ZeroFlagIsNotPatternCheck_ReportDiagnostic()
+ {
+ await CreateProjectBuilder()
+ .WithSourceCode("""
+ [System.Flags]
+ enum MyEnum
+ {
+ None = 0,
+ Flag1 = 1,
+ }
+
+ class Sample
+ {
+ bool M(MyEnum value) => {|MA0201:(value & MyEnum.None) is not MyEnum.None|};
+ }
+ """)
+ .ValidateAsync();
+ }
+
+ [Fact]
+ public async Task HasFlagZeroFlag_ReportDiagnostic()
+ {
+ await CreateProjectBuilder()
+ .WithSourceCode("""
+ [System.Flags]
+ enum MyEnum
+ {
+ None = 0,
+ Flag1 = 1,
+ }
+
+ class Sample
+ {
+ bool M(MyEnum value) => {|MA0201:value.HasFlag(MyEnum.None)|};
+ }
+ """)
+ .ValidateAsync();
+ }
+
+ [Fact]
+ public async Task HasFlagExplicitZeroCast_ReportDiagnostic()
+ {
+ await CreateProjectBuilder()
+ .WithSourceCode("""
+ [System.Flags]
+ enum MyEnum
+ {
+ None = 0,
+ Flag1 = 1,
+ }
+
+ class Sample
+ {
+ bool M(MyEnum value) => {|MA0201:value.HasFlag((MyEnum)0)|};
+ }
+ """)
+ .ValidateAsync();
+ }
+
[Fact]
public async Task DifferentFlag_NoDiagnostic()
{
@@ -347,6 +486,51 @@ class Sample
.ValidateAsync();
}
+ [Fact]
+ public async Task HasFlagsExtensionZeroFlag_NoDiagnostic()
+ {
+ await CreateProjectBuilder()
+ .WithSourceCode("""
+ [System.Flags]
+ enum MyEnum
+ {
+ None = 0,
+ Flag1 = 1,
+ }
+
+ static class MyEnumExtensions
+ {
+ public static bool HasFlags(this MyEnum value, MyEnum flags) => (value & flags) == flags;
+ }
+
+ class Sample
+ {
+ bool M(MyEnum value) => value.HasFlags(MyEnum.None);
+ }
+ """)
+ .ValidateAsync();
+ }
+
+ [Fact]
+ public async Task NonZeroHasFlag_NoDiagnostic()
+ {
+ await CreateProjectBuilder()
+ .WithSourceCode("""
+ [System.Flags]
+ enum MyEnum
+ {
+ None = 0,
+ Flag1 = 1,
+ }
+
+ class Sample
+ {
+ bool M(MyEnum value) => value.HasFlag(MyEnum.Flag1);
+ }
+ """)
+ .ValidateAsync();
+ }
+
[Fact]
public async Task CombinedFlag_NotEqualsZero_NoDiagnostic()
{
@@ -423,11 +607,4 @@ class Sample
.ValidateAsync();
}
- [Fact]
- public void Rule_SeverityAndDefault()
- {
- var rule = new UseHasFlagMethodAnalyzer().SupportedDiagnostics[0];
- Assert.Equal(DiagnosticSeverity.Info, rule.DefaultSeverity);
- Assert.False(rule.IsEnabledByDefault);
- }
}