From 7a9846589dc0ecdc043be811718a371b7aed4615 Mon Sep 17 00:00:00 2001 From: Josef Pihrt Date: Sun, 13 Mar 2022 13:57:08 +0100 Subject: [PATCH 01/11] x --- docs/Configuration.md | 3 + .../CaseSwitchLabelCodeFixProvider.cs | 1 + .../CastExpressionCodeFixProvider.cs | 105 ++++++++ .../Analyzers.UseEnumFieldExplicitly.xml | 16 ++ .../CSharp/Analysis/ConfigureAwaitAnalyzer.cs | 1 + .../UseEnumFieldExplicitlyAnalyzer.cs | 97 ++++++++ .../CSharp/DiagnosticIdentifiers.Generated.cs | 1 + .../CSharp/DiagnosticRules.Generated.cs | 12 + ...1254NormalizeFormatOfEnumFlagValueTests.cs | 234 +++--------------- .../src/configurationFiles.generated.ts | 3 + 10 files changed, 270 insertions(+), 203 deletions(-) create mode 100644 src/Analyzers.CodeFixes/CSharp/CodeFixes/CastExpressionCodeFixProvider.cs create mode 100644 src/Analyzers/Analyzers.UseEnumFieldExplicitly.xml create mode 100644 src/Analyzers/CSharp/Analysis/UseEnumFieldExplicitlyAnalyzer.cs diff --git a/docs/Configuration.md b/docs/Configuration.md index 0e0c40ca9a..d442d6909b 100644 --- a/docs/Configuration.md +++ b/docs/Configuration.md @@ -933,6 +933,9 @@ dotnet_diagnostic.rcs1253.severity = none dotnet_diagnostic.rcs1254.severity = suggestion # Options: roslynator_enum_flag_value_style +# Use enum field explicitly +dotnet_diagnostic.rcs1255.severity = suggestion + # Use pattern matching dotnet_diagnostic.rcs9001.severity = silent diff --git a/src/Analyzers.CodeFixes/CSharp/CodeFixes/CaseSwitchLabelCodeFixProvider.cs b/src/Analyzers.CodeFixes/CSharp/CodeFixes/CaseSwitchLabelCodeFixProvider.cs index 60eed02e4f..0702086bcb 100644 --- a/src/Analyzers.CodeFixes/CSharp/CodeFixes/CaseSwitchLabelCodeFixProvider.cs +++ b/src/Analyzers.CodeFixes/CSharp/CodeFixes/CaseSwitchLabelCodeFixProvider.cs @@ -12,6 +12,7 @@ namespace Roslynator.CSharp.CodeFixes { + [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(CaseSwitchLabelCodeFixProvider))] [Shared] public sealed class CaseSwitchLabelCodeFixProvider : BaseCodeFixProvider diff --git a/src/Analyzers.CodeFixes/CSharp/CodeFixes/CastExpressionCodeFixProvider.cs b/src/Analyzers.CodeFixes/CSharp/CodeFixes/CastExpressionCodeFixProvider.cs new file mode 100644 index 0000000000..9ca1c441ad --- /dev/null +++ b/src/Analyzers.CodeFixes/CSharp/CodeFixes/CastExpressionCodeFixProvider.cs @@ -0,0 +1,105 @@ +// Copyright (c) Josef Pihrt and Contributors. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Composition; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Roslynator.CodeFixes; +using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; +using static Roslynator.CSharp.CSharpFactory; + +namespace Roslynator.CSharp.CodeFixes +{ + [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(CastExpressionCodeFixProvider))] + [Shared] + public sealed class CastExpressionCodeFixProvider : BaseCodeFixProvider + { + public override ImmutableArray FixableDiagnosticIds + { + get { return ImmutableArray.Create(DiagnosticIdentifiers.UseEnumFieldExplicitly); } + } + + public override async Task RegisterCodeFixesAsync(CodeFixContext context) + { + SyntaxNode root = await context.GetSyntaxRootAsync().ConfigureAwait(false); + + if (!TryFindFirstAncestorOrSelf(root, context.Span, out CastExpressionSyntax castExpression)) + return; + + Diagnostic diagnostic = context.Diagnostics[0]; + Document document = context.Document; + + CodeAction codeAction = CodeAction.Create( + "Use enum field explicitly", + ct => UseEnumFieldExplicitlyAsync(castExpression, document, ct), + GetEquivalenceKey(DiagnosticIdentifiers.UseEnumFieldExplicitly)); + + context.RegisterCodeFix(codeAction, diagnostic); + } + + private static async Task UseEnumFieldExplicitlyAsync( + CastExpressionSyntax castExpression, + Document document, + CancellationToken cancellationToken) + { + SemanticModel semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); + + Optional constantValueOpt = semanticModel.GetConstantValue(castExpression.Expression, cancellationToken); + + var enumSymbol = (INamedTypeSymbol)semanticModel.GetTypeSymbol(castExpression.Type, cancellationToken); + + if (enumSymbol.HasAttribute(MetadataNames.System_FlagsAttribute)) + { + ulong value = SymbolUtility.GetEnumValueAsUInt64(constantValueOpt.Value, enumSymbol); + + List flags = FlagsUtility.Instance.GetFlags(value).ToList(); + + List fields = EnumSymbolInfo.Create(enumSymbol).Fields + .Where(f => flags.Contains(f.Value)) + .OrderByDescending(f => f.Value) + .ToList(); + + ExpressionSyntax newExpression = CreateEnumFieldExpression(fields[0].Symbol); + + for (int i = 1; i < fields.Count; i++) + { + newExpression = BitwiseOrExpression( + CreateEnumFieldExpression(fields[i].Symbol), + newExpression); + } + + newExpression = newExpression.WithTriviaFrom(castExpression); + + return await document.ReplaceNodeAsync(castExpression, newExpression).ConfigureAwait(false); + } + else + { + IFieldSymbol symbol = enumSymbol + .GetMembers() + .OfType() + .First(fieldSymbol => + { + return fieldSymbol.HasConstantValue + && constantValueOpt.Value.Equals(fieldSymbol.ConstantValue); + }); + + ExpressionSyntax newExpression = CreateEnumFieldExpression(symbol).WithTriviaFrom(castExpression); + + return await document.ReplaceNodeAsync(castExpression, newExpression).ConfigureAwait(false); + } + + static MemberAccessExpressionSyntax CreateEnumFieldExpression(IFieldSymbol symbol) + { + return SimpleMemberAccessExpression( + symbol.Type.ToTypeSyntax().WithSimplifierAnnotation(), + IdentifierName(symbol.Name)); + } + } + } +} diff --git a/src/Analyzers/Analyzers.UseEnumFieldExplicitly.xml b/src/Analyzers/Analyzers.UseEnumFieldExplicitly.xml new file mode 100644 index 0000000000..db1fbddf9c --- /dev/null +++ b/src/Analyzers/Analyzers.UseEnumFieldExplicitly.xml @@ -0,0 +1,16 @@ + + + + RCS1255 + Use enum field explicitly + General + Info + true + + + + + + + + \ No newline at end of file diff --git a/src/Analyzers/CSharp/Analysis/ConfigureAwaitAnalyzer.cs b/src/Analyzers/CSharp/Analysis/ConfigureAwaitAnalyzer.cs index 7dc1228ae1..76ccb6b1ad 100644 --- a/src/Analyzers/CSharp/Analysis/ConfigureAwaitAnalyzer.cs +++ b/src/Analyzers/CSharp/Analysis/ConfigureAwaitAnalyzer.cs @@ -11,6 +11,7 @@ namespace Roslynator.CSharp.Analysis { + [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class ConfigureAwaitAnalyzer : BaseDiagnosticAnalyzer { diff --git a/src/Analyzers/CSharp/Analysis/UseEnumFieldExplicitlyAnalyzer.cs b/src/Analyzers/CSharp/Analysis/UseEnumFieldExplicitlyAnalyzer.cs new file mode 100644 index 0000000000..30481241ac --- /dev/null +++ b/src/Analyzers/CSharp/Analysis/UseEnumFieldExplicitlyAnalyzer.cs @@ -0,0 +1,97 @@ +// Copyright (c) Josef Pihrt and Contributors. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; + +namespace Roslynator.CSharp.Analysis +{ + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public sealed class UseEnumFieldExplicitlyAnalyzer : BaseDiagnosticAnalyzer + { + private static ImmutableArray _supportedDiagnostics; + + public override ImmutableArray SupportedDiagnostics + { + get + { + if (_supportedDiagnostics.IsDefault) + { + Immutable.InterlockedInitialize(ref _supportedDiagnostics, DiagnosticRules.UseEnumFieldExplicitly); + } + + return _supportedDiagnostics; + } + } + + public override void Initialize(AnalysisContext context) + { + base.Initialize(context); + + context.RegisterSyntaxNodeAction(c => AnalyzeCastExpression(c), SyntaxKind.CastExpression); + } + + private static void AnalyzeCastExpression(SyntaxNodeAnalysisContext context) + { + var castExpression = (CastExpressionSyntax)context.Node; + + ExpressionSyntax expression = castExpression.Expression; + + if (expression is not LiteralExpressionSyntax literalExpression) + return; + + string s = literalExpression.Token.Text; + + if (s.Length == 0) + return; + + if (!s.StartsWith("0x") + && !s.StartsWith("0X") + && !s.StartsWith("0b") + && !s.StartsWith("0B") + && !char.IsDigit(s[0])) + { + return; + } + + Optional constantValueOpt = context.SemanticModel.GetConstantValue(literalExpression, context.CancellationToken); + + if (!constantValueOpt.HasValue) + return; + + var enumSymbol = context.SemanticModel.GetTypeSymbol(castExpression.Type, context.CancellationToken) as INamedTypeSymbol; + + if (enumSymbol?.EnumUnderlyingType is null) + return; + + ulong value = SymbolUtility.GetEnumValueAsUInt64(constantValueOpt.Value, enumSymbol); + + foreach (ISymbol member in enumSymbol.GetMembers()) + { + if (member is IFieldSymbol fieldSymbol + && fieldSymbol.HasConstantValue + && value == SymbolUtility.GetEnumValueAsUInt64(fieldSymbol.ConstantValue, enumSymbol)) + { + context.ReportDiagnostic(DiagnosticRules.UseEnumFieldExplicitly, castExpression); + return; + } + } + + if (enumSymbol.HasAttribute(MetadataNames.System_FlagsAttribute) + && FlagsUtility.Instance.IsComposite(value)) + { + EnumSymbolInfo enumInfo = EnumSymbolInfo.Create(enumSymbol); + + foreach (ulong flag in FlagsUtility.Instance.GetFlags(value)) + { + if (!enumInfo.Contains(flag)) + return; + } + + context.ReportDiagnostic(DiagnosticRules.UseEnumFieldExplicitly, castExpression); + } + } + } +} diff --git a/src/Analyzers/CSharp/DiagnosticIdentifiers.Generated.cs b/src/Analyzers/CSharp/DiagnosticIdentifiers.Generated.cs index 7cb72d0735..26dbb38e7f 100644 --- a/src/Analyzers/CSharp/DiagnosticIdentifiers.Generated.cs +++ b/src/Analyzers/CSharp/DiagnosticIdentifiers.Generated.cs @@ -211,5 +211,6 @@ public static partial class DiagnosticIdentifiers public const string NormalizeUsageOfInfiniteLoop = "RCS1252"; public const string FormatDocumentationCommentSummary = "RCS1253"; public const string NormalizeFormatOfEnumFlagValue = "RCS1254"; + public const string UseEnumFieldExplicitly = "RCS1255"; } } \ No newline at end of file diff --git a/src/Analyzers/CSharp/DiagnosticRules.Generated.cs b/src/Analyzers/CSharp/DiagnosticRules.Generated.cs index 7693d8aa80..0b75e9ab0b 100644 --- a/src/Analyzers/CSharp/DiagnosticRules.Generated.cs +++ b/src/Analyzers/CSharp/DiagnosticRules.Generated.cs @@ -2497,5 +2497,17 @@ public static partial class DiagnosticRules helpLinkUri: DiagnosticIdentifiers.NormalizeFormatOfEnumFlagValue, customTags: Array.Empty()); + /// RCS1255 + public static readonly DiagnosticDescriptor UseEnumFieldExplicitly = DiagnosticDescriptorFactory.Create( + id: DiagnosticIdentifiers.UseEnumFieldExplicitly, + title: "Use enum field explicitly", + messageFormat: "Use enum field explicitly", + category: DiagnosticCategories.Roslynator, + defaultSeverity: DiagnosticSeverity.Info, + isEnabledByDefault: true, + description: null, + helpLinkUri: DiagnosticIdentifiers.UseEnumFieldExplicitly, + customTags: Array.Empty()); + } } \ No newline at end of file diff --git a/src/Tests/Analyzers.Tests/RCS1254NormalizeFormatOfEnumFlagValueTests.cs b/src/Tests/Analyzers.Tests/RCS1254NormalizeFormatOfEnumFlagValueTests.cs index b4e4b88b4e..e7da8336bf 100644 --- a/src/Tests/Analyzers.Tests/RCS1254NormalizeFormatOfEnumFlagValueTests.cs +++ b/src/Tests/Analyzers.Tests/RCS1254NormalizeFormatOfEnumFlagValueTests.cs @@ -8,232 +8,60 @@ namespace Roslynator.CSharp.Analysis.Tests { - public class RCS1254NormalizeFormatOfEnumFlagValueTests : AbstractCSharpDiagnosticVerifier + public class RCS1255UseEnumFieldExplicitlyTests : AbstractCSharpDiagnosticVerifier { - public override DiagnosticDescriptor Descriptor { get; } = DiagnosticRules.NormalizeFormatOfEnumFlagValue; + public override DiagnosticDescriptor Descriptor { get; } = DiagnosticRules.UseEnumFieldExplicitly; - [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.NormalizeFormatOfEnumFlagValue)] - public async Task Test_DecimalToShift() + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.UseEnumFieldExplicitly)] + public async Task Test() { await VerifyDiagnosticAndFixAsync(@" -using System; +using System.Text.RegularExpressions; -[Flags] -enum Foo +class C { - _ = 0, - A = 1, - B = [|2|], - AB = 3, - C = ([|4|]), -} -", @" -using System; - -[Flags] -enum Foo -{ - _ = 0, - A = 1, - B = 1 << 1, - AB = 3, - C = (1 << 2), -} -", options: Options.AddConfigOption(ConfigOptionKeys.EnumFlagValueStyle, ConfigOptionValues.EnumFlagValueStyle_ShiftOperator)); - } - - [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.NormalizeFormatOfEnumFlagValue)] - public async Task Test_ShiftToDecimal() - { - await VerifyDiagnosticAndFixAsync(@" -using System; - -[Flags] -enum Foo -{ - _ = 0, - A = 1, - B = [|1 << 1|], - AB = 3, - C = ([|1 << 2|]), -} -", @" -using System; - -[Flags] -enum Foo -{ - _ = 0, - A = 1, - B = 2, - AB = 3, - C = (4), -} -", options: Options.AddConfigOption(ConfigOptionKeys.EnumFlagValueStyle, ConfigOptionValues.EnumFlagValueStyle_DecimalNumber)); - } - - [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.NormalizeFormatOfEnumFlagValue)] - public async Task Test_HexadecimalToShift() - { - await VerifyDiagnosticAndFixAsync(@" -using System; - -[Flags] -enum Foo -{ - _ = 0, - A = 1, - B = [|0x2|], - C = [|0X4|], -} -", @" -using System; - -[Flags] -enum Foo -{ - _ = 0, - A = 1, - B = 1 << 1, - C = 1 << 2, -} -", options: Options.AddConfigOption(ConfigOptionKeys.EnumFlagValueStyle, ConfigOptionValues.EnumFlagValueStyle_ShiftOperator)); - } - - [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.NormalizeFormatOfEnumFlagValue)] - public async Task Test_HexadecimalToDecimal() - { - await VerifyDiagnosticAndFixAsync(@" -using System; - -[Flags] -enum Foo -{ - _ = 0, - A = 1, - B = [|0x2|], - C = [|0X4|], -} -", @" -using System; - -[Flags] -enum Foo -{ - _ = 0, - A = 1, - B = 2, - C = 4, -} -", options: Options.AddConfigOption(ConfigOptionKeys.EnumFlagValueStyle, ConfigOptionValues.EnumFlagValueStyle_DecimalNumber)); - } - - [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.NormalizeFormatOfEnumFlagValue)] - public async Task Test_BinaryToShift() - { - await VerifyDiagnosticAndFixAsync(@" -using System; - -[Flags] -enum Foo -{ - _ = 0, - A = 1, - B = [|0b10|], - C = [|0B100|], + void M() + { + var x = [|(RegexOptions)1|]; + } } ", @" -using System; +using System.Text.RegularExpressions; -[Flags] -enum Foo +class C { - _ = 0, - A = 1, - B = 1 << 1, - C = 1 << 2, + void M() + { + var x = RegexOptions.IgnoreCase; + } } -", options: Options.AddConfigOption(ConfigOptionKeys.EnumFlagValueStyle, ConfigOptionValues.EnumFlagValueStyle_ShiftOperator)); +"); } - [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.NormalizeFormatOfEnumFlagValue)] - public async Task Test_BinaryToDecimal() + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.UseEnumFieldExplicitly)] + public async Task Test_Flags() { await VerifyDiagnosticAndFixAsync(@" -using System; +using System.Text.RegularExpressions; -[Flags] -enum Foo +class C { - _ = 0, - A = 1, - B = [|0b10|], - C = [|0B100|], + void M() + { + var x = [|(RegexOptions)7|]; + } } ", @" -using System; +using System.Text.RegularExpressions; -[Flags] -enum Foo +class C { - _ = 0, - A = 1, - B = 2, - C = 4, -} -", options: Options.AddConfigOption(ConfigOptionKeys.EnumFlagValueStyle, ConfigOptionValues.EnumFlagValueStyle_DecimalNumber)); - } - - [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.NormalizeFormatOfEnumFlagValue)] - public async Task TestNoDiagnostic_WithoutFlags() - { - await VerifyNoDiagnosticAsync(@" -enum Foo -{ - _ = 0, - A = 1, - B = 2, - C = 4, -} -", options: Options.AddConfigOption(ConfigOptionKeys.EnumFlagValueStyle, ConfigOptionValues.EnumFlagValueStyle_ShiftOperator)); - } - - [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.NormalizeFormatOfEnumFlagValue)] - public async Task TestNoDiagnostic_BitShift() - { - await VerifyNoDiagnosticAsync(@" -using System; - -[Flags] -enum Foo -{ - _ = 0, - A = 1, - B = 1 << 1, + void M() + { + var x = RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.ExplicitCapture; + } } "); } - - [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.NormalizeFormatOfEnumFlagValue)] - public async Task TestNoDiagnostic_CombinedValue() - { - await VerifyNoDiagnosticAsync(@" -using System; - -[Flags] -enum Foo -{ - _ = 0, - A = 1, - B = 1 << 1, - C = 1 << 2, - D = 1 << 3, - AB = 3, - AC = 1 | 4, - AD = 0b1001, - X = int.MaxValue, -} -", options: Options.AddConfigOption(ConfigOptionKeys.EnumFlagValueStyle, ConfigOptionValues.EnumFlagValueStyle_ShiftOperator)); - } } } diff --git a/src/VisualStudioCode/package/src/configurationFiles.generated.ts b/src/VisualStudioCode/package/src/configurationFiles.generated.ts index 9b185615ee..ffd1a26f19 100644 --- a/src/VisualStudioCode/package/src/configurationFiles.generated.ts +++ b/src/VisualStudioCode/package/src/configurationFiles.generated.ts @@ -905,6 +905,9 @@ roslynator_analyzers.enabled_by_default = true|false #dotnet_diagnostic.rcs1254.severity = suggestion # Options: roslynator_enum_flag_value_style +# Use enum field explicitly +#dotnet_diagnostic.rcs1255.severity = suggestion + # Use pattern matching #dotnet_diagnostic.rcs9001.severity = silent From 39b44106f80f0b541d7ffab2d0e495c886fe98bd Mon Sep 17 00:00:00 2001 From: Josef Pihrt Date: Thu, 17 Nov 2022 11:59:45 +0100 Subject: [PATCH 02/11] fix format --- .../CSharp/CodeFixes/CaseSwitchLabelCodeFixProvider.cs | 1 - src/Analyzers/CSharp/Analysis/ConfigureAwaitAnalyzer.cs | 1 - 2 files changed, 2 deletions(-) diff --git a/src/Analyzers.CodeFixes/CSharp/CodeFixes/CaseSwitchLabelCodeFixProvider.cs b/src/Analyzers.CodeFixes/CSharp/CodeFixes/CaseSwitchLabelCodeFixProvider.cs index 0702086bcb..60eed02e4f 100644 --- a/src/Analyzers.CodeFixes/CSharp/CodeFixes/CaseSwitchLabelCodeFixProvider.cs +++ b/src/Analyzers.CodeFixes/CSharp/CodeFixes/CaseSwitchLabelCodeFixProvider.cs @@ -12,7 +12,6 @@ namespace Roslynator.CSharp.CodeFixes { - [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(CaseSwitchLabelCodeFixProvider))] [Shared] public sealed class CaseSwitchLabelCodeFixProvider : BaseCodeFixProvider diff --git a/src/Analyzers/CSharp/Analysis/ConfigureAwaitAnalyzer.cs b/src/Analyzers/CSharp/Analysis/ConfigureAwaitAnalyzer.cs index 76ccb6b1ad..7dc1228ae1 100644 --- a/src/Analyzers/CSharp/Analysis/ConfigureAwaitAnalyzer.cs +++ b/src/Analyzers/CSharp/Analysis/ConfigureAwaitAnalyzer.cs @@ -11,7 +11,6 @@ namespace Roslynator.CSharp.Analysis { - [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class ConfigureAwaitAnalyzer : BaseDiagnosticAnalyzer { From dcf48a8762c9a2175989978392f137e03e22e29f Mon Sep 17 00:00:00 2001 From: Josef Pihrt Date: Thu, 17 Nov 2022 12:01:48 +0100 Subject: [PATCH 03/11] Update tests --- ...1254NormalizeFormatOfEnumFlagValueTests.cs | 234 +++++++++++++++--- .../RCS1255UseEnumFieldExplicitlyTests.cs | 67 +++++ 2 files changed, 270 insertions(+), 31 deletions(-) create mode 100644 src/Tests/Analyzers.Tests/RCS1255UseEnumFieldExplicitlyTests.cs diff --git a/src/Tests/Analyzers.Tests/RCS1254NormalizeFormatOfEnumFlagValueTests.cs b/src/Tests/Analyzers.Tests/RCS1254NormalizeFormatOfEnumFlagValueTests.cs index e7da8336bf..b4e4b88b4e 100644 --- a/src/Tests/Analyzers.Tests/RCS1254NormalizeFormatOfEnumFlagValueTests.cs +++ b/src/Tests/Analyzers.Tests/RCS1254NormalizeFormatOfEnumFlagValueTests.cs @@ -8,60 +8,232 @@ namespace Roslynator.CSharp.Analysis.Tests { - public class RCS1255UseEnumFieldExplicitlyTests : AbstractCSharpDiagnosticVerifier + public class RCS1254NormalizeFormatOfEnumFlagValueTests : AbstractCSharpDiagnosticVerifier { - public override DiagnosticDescriptor Descriptor { get; } = DiagnosticRules.UseEnumFieldExplicitly; + public override DiagnosticDescriptor Descriptor { get; } = DiagnosticRules.NormalizeFormatOfEnumFlagValue; - [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.UseEnumFieldExplicitly)] - public async Task Test() + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.NormalizeFormatOfEnumFlagValue)] + public async Task Test_DecimalToShift() { await VerifyDiagnosticAndFixAsync(@" -using System.Text.RegularExpressions; +using System; -class C +[Flags] +enum Foo { - void M() - { - var x = [|(RegexOptions)1|]; - } + _ = 0, + A = 1, + B = [|2|], + AB = 3, + C = ([|4|]), } ", @" -using System.Text.RegularExpressions; +using System; -class C +[Flags] +enum Foo { - void M() - { - var x = RegexOptions.IgnoreCase; - } + _ = 0, + A = 1, + B = 1 << 1, + AB = 3, + C = (1 << 2), } -"); +", options: Options.AddConfigOption(ConfigOptionKeys.EnumFlagValueStyle, ConfigOptionValues.EnumFlagValueStyle_ShiftOperator)); } - [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.UseEnumFieldExplicitly)] - public async Task Test_Flags() + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.NormalizeFormatOfEnumFlagValue)] + public async Task Test_ShiftToDecimal() { await VerifyDiagnosticAndFixAsync(@" -using System.Text.RegularExpressions; +using System; -class C +[Flags] +enum Foo { - void M() - { - var x = [|(RegexOptions)7|]; - } + _ = 0, + A = 1, + B = [|1 << 1|], + AB = 3, + C = ([|1 << 2|]), } ", @" -using System.Text.RegularExpressions; +using System; -class C +[Flags] +enum Foo { - void M() - { - var x = RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.ExplicitCapture; - } + _ = 0, + A = 1, + B = 2, + AB = 3, + C = (4), +} +", options: Options.AddConfigOption(ConfigOptionKeys.EnumFlagValueStyle, ConfigOptionValues.EnumFlagValueStyle_DecimalNumber)); + } + + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.NormalizeFormatOfEnumFlagValue)] + public async Task Test_HexadecimalToShift() + { + await VerifyDiagnosticAndFixAsync(@" +using System; + +[Flags] +enum Foo +{ + _ = 0, + A = 1, + B = [|0x2|], + C = [|0X4|], +} +", @" +using System; + +[Flags] +enum Foo +{ + _ = 0, + A = 1, + B = 1 << 1, + C = 1 << 2, +} +", options: Options.AddConfigOption(ConfigOptionKeys.EnumFlagValueStyle, ConfigOptionValues.EnumFlagValueStyle_ShiftOperator)); + } + + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.NormalizeFormatOfEnumFlagValue)] + public async Task Test_HexadecimalToDecimal() + { + await VerifyDiagnosticAndFixAsync(@" +using System; + +[Flags] +enum Foo +{ + _ = 0, + A = 1, + B = [|0x2|], + C = [|0X4|], +} +", @" +using System; + +[Flags] +enum Foo +{ + _ = 0, + A = 1, + B = 2, + C = 4, +} +", options: Options.AddConfigOption(ConfigOptionKeys.EnumFlagValueStyle, ConfigOptionValues.EnumFlagValueStyle_DecimalNumber)); + } + + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.NormalizeFormatOfEnumFlagValue)] + public async Task Test_BinaryToShift() + { + await VerifyDiagnosticAndFixAsync(@" +using System; + +[Flags] +enum Foo +{ + _ = 0, + A = 1, + B = [|0b10|], + C = [|0B100|], +} +", @" +using System; + +[Flags] +enum Foo +{ + _ = 0, + A = 1, + B = 1 << 1, + C = 1 << 2, +} +", options: Options.AddConfigOption(ConfigOptionKeys.EnumFlagValueStyle, ConfigOptionValues.EnumFlagValueStyle_ShiftOperator)); + } + + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.NormalizeFormatOfEnumFlagValue)] + public async Task Test_BinaryToDecimal() + { + await VerifyDiagnosticAndFixAsync(@" +using System; + +[Flags] +enum Foo +{ + _ = 0, + A = 1, + B = [|0b10|], + C = [|0B100|], +} +", @" +using System; + +[Flags] +enum Foo +{ + _ = 0, + A = 1, + B = 2, + C = 4, +} +", options: Options.AddConfigOption(ConfigOptionKeys.EnumFlagValueStyle, ConfigOptionValues.EnumFlagValueStyle_DecimalNumber)); + } + + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.NormalizeFormatOfEnumFlagValue)] + public async Task TestNoDiagnostic_WithoutFlags() + { + await VerifyNoDiagnosticAsync(@" +enum Foo +{ + _ = 0, + A = 1, + B = 2, + C = 4, +} +", options: Options.AddConfigOption(ConfigOptionKeys.EnumFlagValueStyle, ConfigOptionValues.EnumFlagValueStyle_ShiftOperator)); + } + + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.NormalizeFormatOfEnumFlagValue)] + public async Task TestNoDiagnostic_BitShift() + { + await VerifyNoDiagnosticAsync(@" +using System; + +[Flags] +enum Foo +{ + _ = 0, + A = 1, + B = 1 << 1, } "); } + + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.NormalizeFormatOfEnumFlagValue)] + public async Task TestNoDiagnostic_CombinedValue() + { + await VerifyNoDiagnosticAsync(@" +using System; + +[Flags] +enum Foo +{ + _ = 0, + A = 1, + B = 1 << 1, + C = 1 << 2, + D = 1 << 3, + AB = 3, + AC = 1 | 4, + AD = 0b1001, + X = int.MaxValue, +} +", options: Options.AddConfigOption(ConfigOptionKeys.EnumFlagValueStyle, ConfigOptionValues.EnumFlagValueStyle_ShiftOperator)); + } } } diff --git a/src/Tests/Analyzers.Tests/RCS1255UseEnumFieldExplicitlyTests.cs b/src/Tests/Analyzers.Tests/RCS1255UseEnumFieldExplicitlyTests.cs new file mode 100644 index 0000000000..e7da8336bf --- /dev/null +++ b/src/Tests/Analyzers.Tests/RCS1255UseEnumFieldExplicitlyTests.cs @@ -0,0 +1,67 @@ +// Copyright (c) Josef Pihrt and Contributors. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Roslynator.CSharp.CodeFixes; +using Roslynator.Testing.CSharp; +using Xunit; + +namespace Roslynator.CSharp.Analysis.Tests +{ + public class RCS1255UseEnumFieldExplicitlyTests : AbstractCSharpDiagnosticVerifier + { + public override DiagnosticDescriptor Descriptor { get; } = DiagnosticRules.UseEnumFieldExplicitly; + + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.UseEnumFieldExplicitly)] + public async Task Test() + { + await VerifyDiagnosticAndFixAsync(@" +using System.Text.RegularExpressions; + +class C +{ + void M() + { + var x = [|(RegexOptions)1|]; + } +} +", @" +using System.Text.RegularExpressions; + +class C +{ + void M() + { + var x = RegexOptions.IgnoreCase; + } +} +"); + } + + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.UseEnumFieldExplicitly)] + public async Task Test_Flags() + { + await VerifyDiagnosticAndFixAsync(@" +using System.Text.RegularExpressions; + +class C +{ + void M() + { + var x = [|(RegexOptions)7|]; + } +} +", @" +using System.Text.RegularExpressions; + +class C +{ + void M() + { + var x = RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.ExplicitCapture; + } +} +"); + } + } +} From 68749852ea91e66e4450cbda2d3c168b942ae2fa Mon Sep 17 00:00:00 2001 From: Josef Pihrt Date: Thu, 17 Nov 2022 13:21:59 +0100 Subject: [PATCH 04/11] Add tests --- .../RCS1255UseEnumFieldExplicitlyTests.cs | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/src/Tests/Analyzers.Tests/RCS1255UseEnumFieldExplicitlyTests.cs b/src/Tests/Analyzers.Tests/RCS1255UseEnumFieldExplicitlyTests.cs index e7da8336bf..095792f87c 100644 --- a/src/Tests/Analyzers.Tests/RCS1255UseEnumFieldExplicitlyTests.cs +++ b/src/Tests/Analyzers.Tests/RCS1255UseEnumFieldExplicitlyTests.cs @@ -61,6 +61,46 @@ void M() var x = RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.ExplicitCapture; } } +"); + } + + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.UseEnumFieldExplicitly)] + public async Task TestNoDiagnostic_UndefinedValue() + { + await VerifyNoDiagnosticAsync(@" +using System.Text.RegularExpressions; + +class C +{ + void M() + { + var x = (Foo)17; + } +} + +[System.Flags] +enum Foo +{ + None = 0, + A = 1, + B = 2, + C = 4, + D = 8, +} +"); + } + + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.UseEnumFieldExplicitly)] + public async Task TestNoDiagnostic_FileAttributes() + { + await VerifyNoDiagnosticAsync(@" +class C +{ + void M() + { + var x = (System.IO.FileAttributes)0; + } +} "); } } From cf75bb88109fc2a65453d83ea336b90612598ac0 Mon Sep 17 00:00:00 2001 From: Josef Pihrt Date: Sun, 16 Jul 2023 13:32:23 +0200 Subject: [PATCH 05/11] fixes --- .../CastExpressionCodeFixProvider.cs | 133 +++++++++--------- .../UseEnumFieldExplicitlyAnalyzer.cs | 121 ++++++++-------- .../RCS1255UseEnumFieldExplicitlyTests.cs | 47 +++---- 3 files changed, 149 insertions(+), 152 deletions(-) diff --git a/src/Analyzers.CodeFixes/CSharp/CodeFixes/CastExpressionCodeFixProvider.cs b/src/Analyzers.CodeFixes/CSharp/CodeFixes/CastExpressionCodeFixProvider.cs index 9ca1c441ad..f5e29ab0e6 100644 --- a/src/Analyzers.CodeFixes/CSharp/CodeFixes/CastExpressionCodeFixProvider.cs +++ b/src/Analyzers.CodeFixes/CSharp/CodeFixes/CastExpressionCodeFixProvider.cs @@ -14,92 +14,91 @@ using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; using static Roslynator.CSharp.CSharpFactory; -namespace Roslynator.CSharp.CodeFixes +namespace Roslynator.CSharp.CodeFixes; + +[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(CastExpressionCodeFixProvider))] +[Shared] +public sealed class CastExpressionCodeFixProvider : BaseCodeFixProvider { - [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(CastExpressionCodeFixProvider))] - [Shared] - public sealed class CastExpressionCodeFixProvider : BaseCodeFixProvider + public override ImmutableArray FixableDiagnosticIds { - public override ImmutableArray FixableDiagnosticIds - { - get { return ImmutableArray.Create(DiagnosticIdentifiers.UseEnumFieldExplicitly); } - } + get { return ImmutableArray.Create(DiagnosticIdentifiers.UseEnumFieldExplicitly); } + } - public override async Task RegisterCodeFixesAsync(CodeFixContext context) - { - SyntaxNode root = await context.GetSyntaxRootAsync().ConfigureAwait(false); + public override async Task RegisterCodeFixesAsync(CodeFixContext context) + { + SyntaxNode root = await context.GetSyntaxRootAsync().ConfigureAwait(false); - if (!TryFindFirstAncestorOrSelf(root, context.Span, out CastExpressionSyntax castExpression)) - return; + if (!TryFindFirstAncestorOrSelf(root, context.Span, out CastExpressionSyntax castExpression)) + return; - Diagnostic diagnostic = context.Diagnostics[0]; - Document document = context.Document; + Diagnostic diagnostic = context.Diagnostics[0]; + Document document = context.Document; - CodeAction codeAction = CodeAction.Create( - "Use enum field explicitly", - ct => UseEnumFieldExplicitlyAsync(castExpression, document, ct), - GetEquivalenceKey(DiagnosticIdentifiers.UseEnumFieldExplicitly)); + CodeAction codeAction = CodeAction.Create( + "Use enum field explicitly", + ct => UseEnumFieldExplicitlyAsync(castExpression, document, ct), + GetEquivalenceKey(DiagnosticIdentifiers.UseEnumFieldExplicitly)); - context.RegisterCodeFix(codeAction, diagnostic); - } + context.RegisterCodeFix(codeAction, diagnostic); + } - private static async Task UseEnumFieldExplicitlyAsync( - CastExpressionSyntax castExpression, - Document document, - CancellationToken cancellationToken) - { - SemanticModel semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); + private static async Task UseEnumFieldExplicitlyAsync( + CastExpressionSyntax castExpression, + Document document, + CancellationToken cancellationToken) + { + SemanticModel semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); - Optional constantValueOpt = semanticModel.GetConstantValue(castExpression.Expression, cancellationToken); + Optional constantValueOpt = semanticModel.GetConstantValue(castExpression.Expression, cancellationToken); - var enumSymbol = (INamedTypeSymbol)semanticModel.GetTypeSymbol(castExpression.Type, cancellationToken); + var enumSymbol = (INamedTypeSymbol)semanticModel.GetTypeSymbol(castExpression.Type, cancellationToken); - if (enumSymbol.HasAttribute(MetadataNames.System_FlagsAttribute)) - { - ulong value = SymbolUtility.GetEnumValueAsUInt64(constantValueOpt.Value, enumSymbol); + if (enumSymbol.HasAttribute(MetadataNames.System_FlagsAttribute)) + { + ulong value = SymbolUtility.GetEnumValueAsUInt64(constantValueOpt.Value, enumSymbol); - List flags = FlagsUtility.Instance.GetFlags(value).ToList(); + List flags = FlagsUtility.Instance.GetFlags(value).ToList(); - List fields = EnumSymbolInfo.Create(enumSymbol).Fields - .Where(f => flags.Contains(f.Value)) - .OrderByDescending(f => f.Value) - .ToList(); + List fields = EnumSymbolInfo.Create(enumSymbol).Fields + .Where(f => flags.Contains(f.Value)) + .OrderByDescending(f => f.Value) + .ToList(); - ExpressionSyntax newExpression = CreateEnumFieldExpression(fields[0].Symbol); + ExpressionSyntax newExpression = CreateEnumFieldExpression(fields[0].Symbol); - for (int i = 1; i < fields.Count; i++) + for (int i = 1; i < fields.Count; i++) + { + newExpression = BitwiseOrExpression( + CreateEnumFieldExpression(fields[i].Symbol), + newExpression); + } + + newExpression = newExpression.WithTriviaFrom(castExpression); + + return await document.ReplaceNodeAsync(castExpression, newExpression).ConfigureAwait(false); + } + else + { + IFieldSymbol symbol = enumSymbol + .GetMembers() + .OfType() + .First(fieldSymbol => { - newExpression = BitwiseOrExpression( - CreateEnumFieldExpression(fields[i].Symbol), - newExpression); - } + return fieldSymbol.HasConstantValue + && constantValueOpt.Value.Equals(fieldSymbol.ConstantValue); + }); - newExpression = newExpression.WithTriviaFrom(castExpression); + ExpressionSyntax newExpression = CreateEnumFieldExpression(symbol).WithTriviaFrom(castExpression); - return await document.ReplaceNodeAsync(castExpression, newExpression).ConfigureAwait(false); - } - else - { - IFieldSymbol symbol = enumSymbol - .GetMembers() - .OfType() - .First(fieldSymbol => - { - return fieldSymbol.HasConstantValue - && constantValueOpt.Value.Equals(fieldSymbol.ConstantValue); - }); - - ExpressionSyntax newExpression = CreateEnumFieldExpression(symbol).WithTriviaFrom(castExpression); - - return await document.ReplaceNodeAsync(castExpression, newExpression).ConfigureAwait(false); - } + return await document.ReplaceNodeAsync(castExpression, newExpression).ConfigureAwait(false); + } - static MemberAccessExpressionSyntax CreateEnumFieldExpression(IFieldSymbol symbol) - { - return SimpleMemberAccessExpression( - symbol.Type.ToTypeSyntax().WithSimplifierAnnotation(), - IdentifierName(symbol.Name)); - } + static MemberAccessExpressionSyntax CreateEnumFieldExpression(IFieldSymbol symbol) + { + return SimpleMemberAccessExpression( + symbol.Type.ToTypeSyntax().WithSimplifierAnnotation(), + IdentifierName(symbol.Name)); } } } diff --git a/src/Analyzers/CSharp/Analysis/UseEnumFieldExplicitlyAnalyzer.cs b/src/Analyzers/CSharp/Analysis/UseEnumFieldExplicitlyAnalyzer.cs index 30481241ac..66b547af66 100644 --- a/src/Analyzers/CSharp/Analysis/UseEnumFieldExplicitlyAnalyzer.cs +++ b/src/Analyzers/CSharp/Analysis/UseEnumFieldExplicitlyAnalyzer.cs @@ -6,92 +6,91 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; -namespace Roslynator.CSharp.Analysis +namespace Roslynator.CSharp.Analysis; + +[DiagnosticAnalyzer(LanguageNames.CSharp)] +public sealed class UseEnumFieldExplicitlyAnalyzer : BaseDiagnosticAnalyzer { - [DiagnosticAnalyzer(LanguageNames.CSharp)] - public sealed class UseEnumFieldExplicitlyAnalyzer : BaseDiagnosticAnalyzer - { - private static ImmutableArray _supportedDiagnostics; + private static ImmutableArray _supportedDiagnostics; - public override ImmutableArray SupportedDiagnostics + public override ImmutableArray SupportedDiagnostics + { + get { - get + if (_supportedDiagnostics.IsDefault) { - if (_supportedDiagnostics.IsDefault) - { - Immutable.InterlockedInitialize(ref _supportedDiagnostics, DiagnosticRules.UseEnumFieldExplicitly); - } - - return _supportedDiagnostics; + Immutable.InterlockedInitialize(ref _supportedDiagnostics, DiagnosticRules.UseEnumFieldExplicitly); } + + return _supportedDiagnostics; } + } - public override void Initialize(AnalysisContext context) - { - base.Initialize(context); + public override void Initialize(AnalysisContext context) + { + base.Initialize(context); - context.RegisterSyntaxNodeAction(c => AnalyzeCastExpression(c), SyntaxKind.CastExpression); - } + context.RegisterSyntaxNodeAction(c => AnalyzeCastExpression(c), SyntaxKind.CastExpression); + } - private static void AnalyzeCastExpression(SyntaxNodeAnalysisContext context) - { - var castExpression = (CastExpressionSyntax)context.Node; + private static void AnalyzeCastExpression(SyntaxNodeAnalysisContext context) + { + var castExpression = (CastExpressionSyntax)context.Node; - ExpressionSyntax expression = castExpression.Expression; + ExpressionSyntax expression = castExpression.Expression; - if (expression is not LiteralExpressionSyntax literalExpression) - return; + if (expression is not LiteralExpressionSyntax literalExpression) + return; - string s = literalExpression.Token.Text; + string s = literalExpression.Token.Text; - if (s.Length == 0) - return; + if (s.Length == 0) + return; - if (!s.StartsWith("0x") - && !s.StartsWith("0X") - && !s.StartsWith("0b") - && !s.StartsWith("0B") - && !char.IsDigit(s[0])) - { - return; - } + if (!s.StartsWith("0x") + && !s.StartsWith("0X") + && !s.StartsWith("0b") + && !s.StartsWith("0B") + && !char.IsDigit(s[0])) + { + return; + } - Optional constantValueOpt = context.SemanticModel.GetConstantValue(literalExpression, context.CancellationToken); + Optional constantValueOpt = context.SemanticModel.GetConstantValue(literalExpression, context.CancellationToken); - if (!constantValueOpt.HasValue) - return; + if (!constantValueOpt.HasValue) + return; - var enumSymbol = context.SemanticModel.GetTypeSymbol(castExpression.Type, context.CancellationToken) as INamedTypeSymbol; + var enumSymbol = context.SemanticModel.GetTypeSymbol(castExpression.Type, context.CancellationToken) as INamedTypeSymbol; - if (enumSymbol?.EnumUnderlyingType is null) - return; + if (enumSymbol?.EnumUnderlyingType is null) + return; - ulong value = SymbolUtility.GetEnumValueAsUInt64(constantValueOpt.Value, enumSymbol); + ulong value = SymbolUtility.GetEnumValueAsUInt64(constantValueOpt.Value, enumSymbol); - foreach (ISymbol member in enumSymbol.GetMembers()) + foreach (ISymbol member in enumSymbol.GetMembers()) + { + if (member is IFieldSymbol fieldSymbol + && fieldSymbol.HasConstantValue + && value == SymbolUtility.GetEnumValueAsUInt64(fieldSymbol.ConstantValue, enumSymbol)) { - if (member is IFieldSymbol fieldSymbol - && fieldSymbol.HasConstantValue - && value == SymbolUtility.GetEnumValueAsUInt64(fieldSymbol.ConstantValue, enumSymbol)) - { - context.ReportDiagnostic(DiagnosticRules.UseEnumFieldExplicitly, castExpression); - return; - } + context.ReportDiagnostic(DiagnosticRules.UseEnumFieldExplicitly, castExpression); + return; } + } - if (enumSymbol.HasAttribute(MetadataNames.System_FlagsAttribute) - && FlagsUtility.Instance.IsComposite(value)) - { - EnumSymbolInfo enumInfo = EnumSymbolInfo.Create(enumSymbol); - - foreach (ulong flag in FlagsUtility.Instance.GetFlags(value)) - { - if (!enumInfo.Contains(flag)) - return; - } + if (enumSymbol.HasAttribute(MetadataNames.System_FlagsAttribute) + && FlagsUtility.Instance.IsComposite(value)) + { + EnumSymbolInfo enumInfo = EnumSymbolInfo.Create(enumSymbol); - context.ReportDiagnostic(DiagnosticRules.UseEnumFieldExplicitly, castExpression); + foreach (ulong flag in FlagsUtility.Instance.GetFlags(value)) + { + if (!enumInfo.Contains(flag)) + return; } + + context.ReportDiagnostic(DiagnosticRules.UseEnumFieldExplicitly, castExpression); } } } diff --git a/src/Tests/Analyzers.Tests/RCS1255UseEnumFieldExplicitlyTests.cs b/src/Tests/Analyzers.Tests/RCS1255UseEnumFieldExplicitlyTests.cs index 095792f87c..d3e243bfbe 100644 --- a/src/Tests/Analyzers.Tests/RCS1255UseEnumFieldExplicitlyTests.cs +++ b/src/Tests/Analyzers.Tests/RCS1255UseEnumFieldExplicitlyTests.cs @@ -6,16 +6,16 @@ using Roslynator.Testing.CSharp; using Xunit; -namespace Roslynator.CSharp.Analysis.Tests +namespace Roslynator.CSharp.Analysis.Tests; + +public class RCS1255UseEnumFieldExplicitlyTests : AbstractCSharpDiagnosticVerifier { - public class RCS1255UseEnumFieldExplicitlyTests : AbstractCSharpDiagnosticVerifier - { - public override DiagnosticDescriptor Descriptor { get; } = DiagnosticRules.UseEnumFieldExplicitly; + public override DiagnosticDescriptor Descriptor { get; } = DiagnosticRules.UseEnumFieldExplicitly; - [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.UseEnumFieldExplicitly)] - public async Task Test() - { - await VerifyDiagnosticAndFixAsync(@" + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.UseEnumFieldExplicitly)] + public async Task Test() + { + await VerifyDiagnosticAndFixAsync(@" using System.Text.RegularExpressions; class C @@ -36,12 +36,12 @@ void M() } } "); - } + } - [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.UseEnumFieldExplicitly)] - public async Task Test_Flags() - { - await VerifyDiagnosticAndFixAsync(@" + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.UseEnumFieldExplicitly)] + public async Task Test_Flags() + { + await VerifyDiagnosticAndFixAsync(@" using System.Text.RegularExpressions; class C @@ -62,12 +62,12 @@ void M() } } "); - } + } - [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.UseEnumFieldExplicitly)] - public async Task TestNoDiagnostic_UndefinedValue() - { - await VerifyNoDiagnosticAsync(@" + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.UseEnumFieldExplicitly)] + public async Task TestNoDiagnostic_UndefinedValue() + { + await VerifyNoDiagnosticAsync(@" using System.Text.RegularExpressions; class C @@ -88,12 +88,12 @@ enum Foo D = 8, } "); - } + } - [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.UseEnumFieldExplicitly)] - public async Task TestNoDiagnostic_FileAttributes() - { - await VerifyNoDiagnosticAsync(@" + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.UseEnumFieldExplicitly)] + public async Task TestNoDiagnostic_FileAttributes() + { + await VerifyNoDiagnosticAsync(@" class C { void M() @@ -102,6 +102,5 @@ void M() } } "); - } } } From 028463740c00c8539723f9ceceb794c147539ef1 Mon Sep 17 00:00:00 2001 From: Josef Pihrt Date: Fri, 18 Aug 2023 20:57:49 +0200 Subject: [PATCH 06/11] update --- ChangeLog.md | 2 ++ .../CSharp/CodeFixes/CastExpressionCodeFixProvider.cs | 4 ++-- ...ldExplicitly.xml => UseEnumFieldExplicitly.Analyzers.xml} | 5 ++--- ...licitlyTests.cs => RCS1257UseEnumFieldExplicitlyTests.cs} | 2 +- 4 files changed, 7 insertions(+), 6 deletions(-) rename src/Analyzers/{Analyzers.UseEnumFieldExplicitly.xml => UseEnumFieldExplicitly.Analyzers.xml} (80%) rename src/Tests/Analyzers.Tests/{RCS1255UseEnumFieldExplicitlyTests.cs => RCS1257UseEnumFieldExplicitlyTests.cs} (97%) diff --git a/ChangeLog.md b/ChangeLog.md index bef300d235..466ecbfeb4 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -11,6 +11,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add SECURITY.md ([#1147](https://github.com/josefpihrt/roslynator/pull/1147)) - Add custom FixAllProvider for [RCS1014](https://github.com/JosefPihrt/Roslynator/blob/main/docs/analyzers/RCS1014.md) ([#1070](https://github.com/JosefPihrt/Roslynator/pull/1070)). +- Add analyzer "Use enum field explicitly" ([RCS1257](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1257)) ([#889](https://github.com/josefpihrt/roslynator/pull/889)). + - Enabled by default. ### Fixed diff --git a/src/Analyzers.CodeFixes/CSharp/CodeFixes/CastExpressionCodeFixProvider.cs b/src/Analyzers.CodeFixes/CSharp/CodeFixes/CastExpressionCodeFixProvider.cs index f5e29ab0e6..5b27af3bf0 100644 --- a/src/Analyzers.CodeFixes/CSharp/CodeFixes/CastExpressionCodeFixProvider.cs +++ b/src/Analyzers.CodeFixes/CSharp/CodeFixes/CastExpressionCodeFixProvider.cs @@ -76,7 +76,7 @@ private static async Task UseEnumFieldExplicitlyAsync( newExpression = newExpression.WithTriviaFrom(castExpression); - return await document.ReplaceNodeAsync(castExpression, newExpression).ConfigureAwait(false); + return await document.ReplaceNodeAsync(castExpression, newExpression, cancellationToken).ConfigureAwait(false); } else { @@ -91,7 +91,7 @@ private static async Task UseEnumFieldExplicitlyAsync( ExpressionSyntax newExpression = CreateEnumFieldExpression(symbol).WithTriviaFrom(castExpression); - return await document.ReplaceNodeAsync(castExpression, newExpression).ConfigureAwait(false); + return await document.ReplaceNodeAsync(castExpression, newExpression, cancellationToken).ConfigureAwait(false); } static MemberAccessExpressionSyntax CreateEnumFieldExpression(IFieldSymbol symbol) diff --git a/src/Analyzers/Analyzers.UseEnumFieldExplicitly.xml b/src/Analyzers/UseEnumFieldExplicitly.Analyzers.xml similarity index 80% rename from src/Analyzers/Analyzers.UseEnumFieldExplicitly.xml rename to src/Analyzers/UseEnumFieldExplicitly.Analyzers.xml index db1fbddf9c..bd47c447f0 100644 --- a/src/Analyzers/Analyzers.UseEnumFieldExplicitly.xml +++ b/src/Analyzers/UseEnumFieldExplicitly.Analyzers.xml @@ -1,9 +1,8 @@ - RCS1255 - Use enum field explicitly - General + RCS1257 + Use enum field explicitly. Info true diff --git a/src/Tests/Analyzers.Tests/RCS1255UseEnumFieldExplicitlyTests.cs b/src/Tests/Analyzers.Tests/RCS1257UseEnumFieldExplicitlyTests.cs similarity index 97% rename from src/Tests/Analyzers.Tests/RCS1255UseEnumFieldExplicitlyTests.cs rename to src/Tests/Analyzers.Tests/RCS1257UseEnumFieldExplicitlyTests.cs index d3e243bfbe..c814d98e13 100644 --- a/src/Tests/Analyzers.Tests/RCS1255UseEnumFieldExplicitlyTests.cs +++ b/src/Tests/Analyzers.Tests/RCS1257UseEnumFieldExplicitlyTests.cs @@ -8,7 +8,7 @@ namespace Roslynator.CSharp.Analysis.Tests; -public class RCS1255UseEnumFieldExplicitlyTests : AbstractCSharpDiagnosticVerifier +public class RCS1257UseEnumFieldExplicitlyTests : AbstractCSharpDiagnosticVerifier { public override DiagnosticDescriptor Descriptor { get; } = DiagnosticRules.UseEnumFieldExplicitly; From e6345710714956be7025395d40fcab5e79c5a0b2 Mon Sep 17 00:00:00 2001 From: Josef Pihrt Date: Fri, 18 Aug 2023 21:08:30 +0200 Subject: [PATCH 07/11] update --- .../CSharp/DiagnosticIdentifiers.Generated.cs | 2 +- .../CSharp/DiagnosticRules.Generated.cs | 24 +++++++++---------- .../src/configurationFiles.generated.ts | 6 ++--- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/Analyzers/CSharp/DiagnosticIdentifiers.Generated.cs b/src/Analyzers/CSharp/DiagnosticIdentifiers.Generated.cs index 906da362d6..721968b654 100644 --- a/src/Analyzers/CSharp/DiagnosticIdentifiers.Generated.cs +++ b/src/Analyzers/CSharp/DiagnosticIdentifiers.Generated.cs @@ -211,8 +211,8 @@ public static partial class DiagnosticIdentifiers public const string NormalizeUsageOfInfiniteLoop = "RCS1252"; public const string FormatDocumentationCommentSummary = "RCS1253"; public const string NormalizeFormatOfEnumFlagValue = "RCS1254"; - public const string UseEnumFieldExplicitly = "RCS1255"; public const string SimplifyArgumentNullCheck = "RCS1255"; public const string InvalidArgumentNullCheck = "RCS1256"; + public const string UseEnumFieldExplicitly = "RCS1257"; } } \ No newline at end of file diff --git a/src/Analyzers/CSharp/DiagnosticRules.Generated.cs b/src/Analyzers/CSharp/DiagnosticRules.Generated.cs index 77b0e7522a..db03876f8f 100644 --- a/src/Analyzers/CSharp/DiagnosticRules.Generated.cs +++ b/src/Analyzers/CSharp/DiagnosticRules.Generated.cs @@ -2497,18 +2497,6 @@ public static partial class DiagnosticRules helpLinkUri: DiagnosticIdentifiers.NormalizeFormatOfEnumFlagValue, customTags: Array.Empty()); - /// RCS1255 - public static readonly DiagnosticDescriptor UseEnumFieldExplicitly = DiagnosticDescriptorFactory.Create( - id: DiagnosticIdentifiers.UseEnumFieldExplicitly, - title: "Use enum field explicitly", - messageFormat: "Use enum field explicitly", - category: DiagnosticCategories.Roslynator, - defaultSeverity: DiagnosticSeverity.Info, - isEnabledByDefault: true, - description: null, - helpLinkUri: DiagnosticIdentifiers.UseEnumFieldExplicitly, - customTags: Array.Empty()); - /// RCS1255 public static readonly DiagnosticDescriptor SimplifyArgumentNullCheck = DiagnosticDescriptorFactory.Create( id: DiagnosticIdentifiers.SimplifyArgumentNullCheck, @@ -2533,5 +2521,17 @@ public static partial class DiagnosticRules helpLinkUri: DiagnosticIdentifiers.InvalidArgumentNullCheck, customTags: Array.Empty()); + /// RCS1257 + public static readonly DiagnosticDescriptor UseEnumFieldExplicitly = DiagnosticDescriptorFactory.Create( + id: DiagnosticIdentifiers.UseEnumFieldExplicitly, + title: "Use enum field explicitly.", + messageFormat: "Use enum field explicitly.", + category: DiagnosticCategories.Roslynator, + defaultSeverity: DiagnosticSeverity.Info, + isEnabledByDefault: true, + description: null, + helpLinkUri: DiagnosticIdentifiers.UseEnumFieldExplicitly, + customTags: Array.Empty()); + } } \ No newline at end of file diff --git a/src/VisualStudioCode/package/src/configurationFiles.generated.ts b/src/VisualStudioCode/package/src/configurationFiles.generated.ts index cb57768d7f..ead0f0f981 100644 --- a/src/VisualStudioCode/package/src/configurationFiles.generated.ts +++ b/src/VisualStudioCode/package/src/configurationFiles.generated.ts @@ -900,15 +900,15 @@ roslynator_analyzers.enabled_by_default = true|false #dotnet_diagnostic.rcs1254.severity = suggestion # Options: roslynator_enum_flag_value_style -# Use enum field explicitly -#dotnet_diagnostic.rcs1255.severity = suggestion - # Simplify argument null check #dotnet_diagnostic.rcs1255.severity = none # Invalid argument null check #dotnet_diagnostic.rcs1256.severity = suggestion +# Use enum field explicitly +#dotnet_diagnostic.rcs1257.severity = suggestion + # Use pattern matching #dotnet_diagnostic.rcs9001.severity = silent From bb9cf2de4cdccf6faa2aefd65d0dd4433a98c31d Mon Sep 17 00:00:00 2001 From: Josef Pihrt Date: Sat, 19 Aug 2023 18:52:40 +0200 Subject: [PATCH 08/11] update --- src/Analyzers.xml | 12 ++++++++++++ .../UseEnumFieldExplicitly.Analyzers.xml | 15 --------------- 2 files changed, 12 insertions(+), 15 deletions(-) delete mode 100644 src/Analyzers/UseEnumFieldExplicitly.Analyzers.xml diff --git a/src/Analyzers.xml b/src/Analyzers.xml index 9ccef0e95c..2c334157e6 100644 --- a/src/Analyzers.xml +++ b/src/Analyzers.xml @@ -7356,6 +7356,18 @@ void M() - optional and its default value is `null`. + + RCS1257 + Use enum field explicitly. + Info + true + + + + + + + RCS9001 UsePatternMatching diff --git a/src/Analyzers/UseEnumFieldExplicitly.Analyzers.xml b/src/Analyzers/UseEnumFieldExplicitly.Analyzers.xml deleted file mode 100644 index bd47c447f0..0000000000 --- a/src/Analyzers/UseEnumFieldExplicitly.Analyzers.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - RCS1257 - Use enum field explicitly. - Info - true - - - - - - - - \ No newline at end of file From 6b53e1fa2617133e2a8021bb2455b08ee7db7c6c Mon Sep 17 00:00:00 2001 From: Josef Pihrt Date: Sat, 19 Aug 2023 18:53:59 +0200 Subject: [PATCH 09/11] update --- docs/configuration.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 docs/configuration.md diff --git a/docs/configuration.md b/docs/configuration.md new file mode 100644 index 0000000000..516ea4bc45 --- /dev/null +++ b/docs/configuration.md @@ -0,0 +1 @@ +# https://josefpihrt.github.io/docs/roslynator/configuration \ No newline at end of file From 89424c7a509c4f40d248142052593f457504fd74 Mon Sep 17 00:00:00 2001 From: Josef Pihrt Date: Sat, 19 Aug 2023 18:54:17 +0200 Subject: [PATCH 10/11] update --- docs/configuration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/configuration.md b/docs/configuration.md index 516ea4bc45..fc070c8d47 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -1 +1 @@ -# https://josefpihrt.github.io/docs/roslynator/configuration \ No newline at end of file +https://josefpihrt.github.io/docs/roslynator/configuration \ No newline at end of file From ef5bce2478ac6f309af798af11abd149d5cd2b0a Mon Sep 17 00:00:00 2001 From: Josef Pihrt Date: Sat, 19 Aug 2023 18:56:35 +0200 Subject: [PATCH 11/11] update --- src/Analyzers/CSharp/Analysis/UseEnumFieldExplicitlyAnalyzer.cs | 2 -- tools/generate_code.ps1 | 2 ++ tools/generate_metadata.ps1 | 2 ++ 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Analyzers/CSharp/Analysis/UseEnumFieldExplicitlyAnalyzer.cs b/src/Analyzers/CSharp/Analysis/UseEnumFieldExplicitlyAnalyzer.cs index 66b547af66..74b5d16f1b 100644 --- a/src/Analyzers/CSharp/Analysis/UseEnumFieldExplicitlyAnalyzer.cs +++ b/src/Analyzers/CSharp/Analysis/UseEnumFieldExplicitlyAnalyzer.cs @@ -18,9 +18,7 @@ public override ImmutableArray SupportedDiagnostics get { if (_supportedDiagnostics.IsDefault) - { Immutable.InterlockedInitialize(ref _supportedDiagnostics, DiagnosticRules.UseEnumFieldExplicitly); - } return _supportedDiagnostics; } diff --git a/tools/generate_code.ps1 b/tools/generate_code.ps1 index d184878ddf..e9481f040c 100644 --- a/tools/generate_code.ps1 +++ b/tools/generate_code.ps1 @@ -1,4 +1,6 @@ dotnet restore "../src/Tools/Tools.sln" --force dotnet build "../src/Tools/Tools.sln" --no-restore /p:Configuration=Release,Deterministic=true,TreatWarningsAsErrors=true,WarningsNotAsErrors="1591,RS1024,RS1025,RS1026" /m +if(!$?) { Read-Host; Exit } + dotnet "../src/Tools/CodeGenerator/bin/Release/net7.0/Roslynator.CodeGenerator.dll" "../src" diff --git a/tools/generate_metadata.ps1 b/tools/generate_metadata.ps1 index 092484adac..194c9dd414 100644 --- a/tools/generate_metadata.ps1 +++ b/tools/generate_metadata.ps1 @@ -1,4 +1,6 @@ dotnet restore "../src/Tools/Tools.sln" --force dotnet build "../src/Tools/Tools.sln" --no-restore /p:"Configuration=Release,Deterministic=true,TreatWarningsAsErrors=true,WarningsNotAsErrors=1591" /m +if(!$?) { Read-Host; Exit } + & "../src/Tools/MetadataGenerator/bin/Release/net7.0/Roslynator.MetadataGenerator" "../src" "build"