Skip to content

Commit

Permalink
Add an option to allow flags with all bits set (#455)
Browse files Browse the repository at this point in the history
  • Loading branch information
meziantou authored Feb 26, 2023
1 parent e361aa8 commit 66ba2df
Show file tree
Hide file tree
Showing 3 changed files with 132 additions and 27 deletions.
20 changes: 20 additions & 0 deletions docs/Rules/MA0062.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,23 @@ public enum Color
Yellow = 4,
}
````

# Configuration

In the following case, `All` is not a power of 2 and not a combination of other values. However, this construct can be used to easily defined a value that contains all other flags.

````
[Flags]
public enum MyEnum
{
None = 0,
Option1 = 1,
All = ~None,
}
````

You can allow this pattern by adding the following configuration to the `.editorconfig` file:

````
MA0062.allow_all_bits_set_value = true
````
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Meziantou.Analyzer.Configurations;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;

Expand Down Expand Up @@ -45,56 +46,62 @@ private static void AnalyzeSymbol(SymbolAnalysisContext context)
var members = symbol.GetMembers()
.OfType<IFieldSymbol>()
.Where(member => member.ConstantValue != null)
.Select(member => (member, IsPowerOfTwo: IsPowerOfTwo(member.ConstantValue!)))
.ToList();
foreach (var member in members.Where(member => !member.IsPowerOfTwo))
.Select(member => (member, IsSingleBitSet: IsSingleBitSet(member.ConstantValue), IsZero: IsZero(member.ConstantValue)))
.ToArray();
foreach (var member in members)
{
var value = member.member.ConstantValue;
if (value != null)
if (member.IsSingleBitSet || member.IsZero)
continue;

if (IsAllBitsSet(member.member.ConstantValue) && context.Options.GetConfigurationValue(member.member, RuleIdentifiers.NonFlagsEnumsShouldNotBeMarkedWithFlagsAttribute + ".allow_all_bits_set_value", defaultValue: false))
continue;

var value = member.member.ConstantValue!;
foreach (var otherMember in members)
{
foreach (var powerOfTwo in members.Where(member => member.IsPowerOfTwo))
{
if (powerOfTwo.member.ConstantValue != null)
{
value = RemoveValue(value, powerOfTwo.member.ConstantValue);
}
}
if (!otherMember.IsSingleBitSet)
continue;

if (!IsZero(value))
if (otherMember.member.ConstantValue != null)
{
context.ReportDiagnostic(s_rule, symbol, member.member.Name);
return;
value = RemoveValue(value, otherMember.member.ConstantValue);
}
}

if (!IsZero(value))
{
context.ReportDiagnostic(s_rule, symbol, member.member.Name);
return;
}
}
}

private static bool IsPowerOfTwo(object o)
private static bool IsSingleBitSet(object? o)
{
// https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/enum
// The approved types for an enum are byte, sbyte, short, ushort, int, uint, long, or ulong.
return o switch
{
null => throw new ArgumentOutOfRangeException(nameof(o), "null is not a valid value"),
byte x => (x == 0) || ((x & (x - 1)) == 0),
sbyte x => (x == 0) || ((x & (x - 1)) == 0),
short x => (x == 0) || ((x & (x - 1)) == 0),
ushort x => (x == 0) || ((x & (x - 1)) == 0),
int x => (x == 0) || ((x & (x - 1)) == 0),
uint x => (x == 0) || ((x & (x - 1)) == 0),
long x => (x == 0) || ((x & (x - 1)) == 0),
ulong x => (x == 0) || ((x & (x - 1)) == 0),
null => false,
sbyte x => IsSingleBitSet((byte)x),
byte x => x > 0 && (x & (x - 1)) == 0,
short x => IsSingleBitSet((ushort)x),
ushort x => x > 0 && (x & (x - 1)) == 0,
int x => IsSingleBitSet((uint)x),
uint x => x > 0 && (x & (x - 1)) == 0,
long x => IsSingleBitSet((ulong)x),
ulong x => x > 0 && (x & (x - 1)) == 0,
_ => throw new ArgumentOutOfRangeException(nameof(o), $"Type {o.GetType().FullName} is not supported"),
};
}

private static bool IsZero(object o)
private static bool IsZero(object? o)
{
// https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/enum
// The approved types for an enum are byte, sbyte, short, ushort, int, uint, long, or ulong.
return o switch
{
null => throw new ArgumentOutOfRangeException(nameof(o), "null is not a valid value"),
null => false,
byte x => x == 0,
sbyte x => x == 0,
short x => x == 0,
Expand All @@ -107,6 +114,25 @@ private static bool IsZero(object o)
};
}

private static bool IsAllBitsSet(object? o)
{
// https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/enum
// The approved types for an enum are byte, sbyte, short, ushort, int, uint, long, or ulong.
return o switch
{
null => false,
sbyte x => x == -1,
byte x => x == 0xFF,
short x => x == -1,
ushort x => x == 0xFFFF,
int x => x == -1,
uint x => x == 0xFFFF_FFFF,
long x => x == -1,
ulong x => x == 0xFFFF_FFFF_FFFF_FFFF,
_ => throw new ArgumentOutOfRangeException(nameof(o), $"Type {o.GetType().FullName} is not supported"),
};
}

[SuppressMessage("Style", "IDE0004:Remove Unnecessary Cast", Justification = "Clearer")]
private static object RemoveValue(object o, object valueToRemove)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,4 +94,63 @@ await CreateProjectBuilder()
.WithSourceCode(sourceCode)
.ValidateAsync();
}

[Fact]
public async Task PowerOfTwo_NegativeValue_Sbyte()
{
var sourceCode = $$"""
[System.Flags]
enum Test : sbyte
{
None = 0,
Option01 = 1,
Option02 = 2,
Option03 = 4,
Option04 = 8,
Option05 = 16,
Option06 = 32,
Option07 = 64,
Option08 = -128,
All = ~None,
}
""";
await CreateProjectBuilder()
.WithSourceCode(sourceCode)
.ValidateAsync();
}

[Fact]
public async Task AllBitSet_WithoutConfiguration()
{
var sourceCode = $$"""
[System.Flags]
enum [||]Test
{
None = 0,
Option1 = 1,
All = ~None,
}
""";
await CreateProjectBuilder()
.WithSourceCode(sourceCode)
.ValidateAsync();
}

[Fact]
public async Task AllBitSet_WithConfiguration()
{
var sourceCode = """
[System.Flags]
enum Test
{
None = 0,
Option1 = 1,
All = ~None,
}
""";
await CreateProjectBuilder()
.WithSourceCode(sourceCode)
.AddAnalyzerConfiguration("MA0062.allow_all_bits_set_value", "true")
.ValidateAsync();
}
}

0 comments on commit 66ba2df

Please sign in to comment.