From 03cde7d8d0abc1a40e16b795bd09693f61b04155 Mon Sep 17 00:00:00 2001 From: Josef Pihrt Date: Sun, 27 Mar 2022 14:13:30 +0200 Subject: [PATCH 1/5] x --- docs/Configuration.md | 3 + .../UnnecessaryEnumFlagCodeFixProvider.cs | 59 +++++++++++ .../Analyzers.UnnecessaryEnumFlag.xml | 12 +++ .../Analysis/UnnecessaryEnumFlagAnalyzer.cs | 99 +++++++++++++++++++ .../CSharp/DiagnosticIdentifiers.Generated.cs | 1 + .../CSharp/DiagnosticRules.Generated.cs | 12 +++ .../RCS1255UnnecessaryEnumFlagTests.cs | 83 ++++++++++++++++ .../src/configurationFiles.generated.ts | 3 + 8 files changed, 272 insertions(+) create mode 100644 src/Analyzers.CodeFixes/CSharp/CodeFixes/UnnecessaryEnumFlagCodeFixProvider.cs create mode 100644 src/Analyzers/Analyzers.UnnecessaryEnumFlag.xml create mode 100644 src/Analyzers/CSharp/Analysis/UnnecessaryEnumFlagAnalyzer.cs create mode 100644 src/Tests/Analyzers.Tests/RCS1255UnnecessaryEnumFlagTests.cs diff --git a/docs/Configuration.md b/docs/Configuration.md index 4af59257ab..9391dcab1a 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 +# Unnecessary enum flag +dotnet_diagnostic.rcs1255.severity = suggestion + # Use pattern matching dotnet_diagnostic.rcs9001.severity = silent diff --git a/src/Analyzers.CodeFixes/CSharp/CodeFixes/UnnecessaryEnumFlagCodeFixProvider.cs b/src/Analyzers.CodeFixes/CSharp/CodeFixes/UnnecessaryEnumFlagCodeFixProvider.cs new file mode 100644 index 0000000000..a994564799 --- /dev/null +++ b/src/Analyzers.CodeFixes/CSharp/CodeFixes/UnnecessaryEnumFlagCodeFixProvider.cs @@ -0,0 +1,59 @@ +// 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 System.Composition; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Roslynator.CodeFixes; + +namespace Roslynator.CSharp.CodeFixes +{ + [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(UnnecessaryEnumFlagCodeFixProvider))] + [Shared] + public sealed class UnnecessaryEnumFlagCodeFixProvider : BaseCodeFixProvider + { + public override ImmutableArray FixableDiagnosticIds + { + get { return ImmutableArray.Create(DiagnosticIdentifiers.UnnecessaryEnumFlag); } + } + + public override async Task RegisterCodeFixesAsync(CodeFixContext context) + { + SyntaxNode root = await context.GetSyntaxRootAsync().ConfigureAwait(false); + + if (!TryFindFirstAncestorOrSelf(root, context.Span, out MemberAccessExpressionSyntax memberAccessExpression)) + return; + + Document document = context.Document; + Diagnostic diagnostic = context.Diagnostics[0]; + + switch (diagnostic.Id) + { + case DiagnosticIdentifiers.UnnecessaryEnumFlag: + { + CodeAction codeAction = CodeAction.Create( + "Remove unnecessary flag", + ct => + { + var bitwiseOr = (BinaryExpressionSyntax)memberAccessExpression.Parent; + + ExpressionSyntax newExpression = (bitwiseOr.Left == memberAccessExpression) + ? bitwiseOr.Right + : bitwiseOr.Left; + + newExpression = newExpression.WithTriviaFrom(bitwiseOr); + + return document.ReplaceNodeAsync(bitwiseOr, newExpression, ct); + }, + GetEquivalenceKey(diagnostic)); + + context.RegisterCodeFix(codeAction, diagnostic); + break; + } + } + } + } +} diff --git a/src/Analyzers/Analyzers.UnnecessaryEnumFlag.xml b/src/Analyzers/Analyzers.UnnecessaryEnumFlag.xml new file mode 100644 index 0000000000..fcd058e7bd --- /dev/null +++ b/src/Analyzers/Analyzers.UnnecessaryEnumFlag.xml @@ -0,0 +1,12 @@ + + + + RCS1255 + Codestin Search App + Enum flag '{0}' is already contained in flag '{1}'. + General + Info + true + true + + \ No newline at end of file diff --git a/src/Analyzers/CSharp/Analysis/UnnecessaryEnumFlagAnalyzer.cs b/src/Analyzers/CSharp/Analysis/UnnecessaryEnumFlagAnalyzer.cs new file mode 100644 index 0000000000..53c0493fa9 --- /dev/null +++ b/src/Analyzers/CSharp/Analysis/UnnecessaryEnumFlagAnalyzer.cs @@ -0,0 +1,99 @@ +// 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.Threading; +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 UnnecessaryEnumFlagAnalyzer : BaseDiagnosticAnalyzer + { + private static ImmutableArray _supportedDiagnostics; + + public override ImmutableArray SupportedDiagnostics + { + get + { + if (_supportedDiagnostics.IsDefault) + Immutable.InterlockedInitialize(ref _supportedDiagnostics, DiagnosticRules.UnnecessaryEnumFlag); + + return _supportedDiagnostics; + } + } + + public override void Initialize(AnalysisContext context) + { + base.Initialize(context); + + context.RegisterSyntaxNodeAction(c => AnalyzeBitwiseOrExpression(c), SyntaxKind.BitwiseOrExpression); + } + + private static void AnalyzeBitwiseOrExpression(SyntaxNodeAnalysisContext context) + { + var bitwiseAnd = (BinaryExpressionSyntax)context.Node; + + foreach (ExpressionSyntax expression in bitwiseAnd.AsChain()) + { + if (!expression.IsKind(SyntaxKind.SimpleMemberAccessExpression)) + return; + } + + SemanticModel semanticModel = context.SemanticModel; + CancellationToken cancellationToken = context.CancellationToken; + ITypeSymbol symbol = semanticModel.GetTypeSymbol(bitwiseAnd, cancellationToken); + + if (!symbol.HasAttribute(MetadataNames.System_FlagsAttribute)) + return; + + var enumSymbol = (INamedTypeSymbol)symbol; + var values = new List<(ExpressionSyntax, ulong)>(); + + foreach (ExpressionSyntax expression in bitwiseAnd.AsChain()) + { + Optional constantValueOpt = semanticModel.GetConstantValue(expression, cancellationToken); + + if (!constantValueOpt.HasValue) + continue; + + ulong value = SymbolUtility.GetEnumValueAsUInt64(constantValueOpt.Value, enumSymbol); + var addToValues = true; + + for (int i = values.Count - 1; i >= 0; i--) + { + (ExpressionSyntax expression2, ulong value2) = values[i]; + + if ((value & value2) != 0) + { + if (value <= value2) + { + ReportDiagnostic(expression, expression2); + addToValues = false; + } + else + { + ReportDiagnostic(expression2, expression); + values.RemoveAt(i); + } + } + } + + if (addToValues) + values.Add((expression, value)); + } + + void ReportDiagnostic(ExpressionSyntax expression, ExpressionSyntax expression2) + { + context.ReportDiagnostic( + DiagnosticRules.UnnecessaryEnumFlag, + expression, + context.SemanticModel.GetSymbol(expression, context.CancellationToken).Name, + context.SemanticModel.GetSymbol(expression2, context.CancellationToken).Name); + } + } + } +} diff --git a/src/Analyzers/CSharp/DiagnosticIdentifiers.Generated.cs b/src/Analyzers/CSharp/DiagnosticIdentifiers.Generated.cs index 7cb72d0735..2e374097d6 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 UnnecessaryEnumFlag = "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..6fbc6420bf 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 UnnecessaryEnumFlag = DiagnosticDescriptorFactory.Create( + id: DiagnosticIdentifiers.UnnecessaryEnumFlag, + title: "Unnecessary enum flag.", + messageFormat: "Enum flag '{0}' is already contained in flag '{1}'.", + category: DiagnosticCategories.Roslynator, + defaultSeverity: DiagnosticSeverity.Info, + isEnabledByDefault: true, + description: null, + helpLinkUri: DiagnosticIdentifiers.UnnecessaryEnumFlag, + customTags: WellKnownDiagnosticTags.Unnecessary); + } } \ No newline at end of file diff --git a/src/Tests/Analyzers.Tests/RCS1255UnnecessaryEnumFlagTests.cs b/src/Tests/Analyzers.Tests/RCS1255UnnecessaryEnumFlagTests.cs new file mode 100644 index 0000000000..3fc035148c --- /dev/null +++ b/src/Tests/Analyzers.Tests/RCS1255UnnecessaryEnumFlagTests.cs @@ -0,0 +1,83 @@ +// 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 RCS1255UnnecessaryEnumFlagTests : AbstractCSharpDiagnosticVerifier + { + public override DiagnosticDescriptor Descriptor { get; } = DiagnosticRules.UnnecessaryEnumFlag; + + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.UnnecessaryEnumFlag)] + public async Task Test() + { + await VerifyDiagnosticAndFixAsync(@" +using System.Globalization; + +class C +{ + void M() + { + var styles = NumberStyles.Integer | [|NumberStyles.AllowLeadingWhite|]; + } +} +", @" +using System.Globalization; + +class C +{ + void M() + { + var styles = NumberStyles.Integer; + } +} +"); + } + + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.UnnecessaryEnumFlag)] + public async Task Test2() + { + await VerifyDiagnosticAndFixAsync(@" +using System.Globalization; + +class C +{ + void M() + { + var styles = [|NumberStyles.AllowLeadingWhite|] | [|NumberStyles.AllowTrailingWhite|] | NumberStyles.Integer; + } +} +", @" +using System.Globalization; + +class C +{ + void M() + { + var styles = NumberStyles.Integer; + } +} +"); + } + + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.UnnecessaryEnumFlag)] + public async Task TestNoDiagnostic() + { + await VerifyNoDiagnosticAsync(@" +using System.Globalization; + +class C +{ + void M() + { + var styles = NumberStyles.AllowCurrencySymbol | NumberStyles.AllowDecimalPoint | NumberStyles.AllowExponent | NumberStyles.AllowHexSpecifier | NumberStyles.AllowLeadingSign | NumberStyles.AllowLeadingWhite | NumberStyles.AllowParentheses | NumberStyles.AllowThousands | NumberStyles.AllowTrailingSign | NumberStyles.AllowTrailingWhite; + } +} +"); + } + } +} diff --git a/src/VisualStudioCode/package/src/configurationFiles.generated.ts b/src/VisualStudioCode/package/src/configurationFiles.generated.ts index 0ad0b7053f..81c906e938 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 +# Unnecessary enum flag +#dotnet_diagnostic.rcs1255.severity = suggestion + # Use pattern matching #dotnet_diagnostic.rcs9001.severity = silent From c35927945c689342e5a9350613da4ddc6a23f8e1 Mon Sep 17 00:00:00 2001 From: Josef Pihrt Date: Sun, 16 Jul 2023 13:36:06 +0200 Subject: [PATCH 2/5] fixes --- .../UnnecessaryEnumFlagCodeFixProvider.cs | 81 ++++++------ .../Analysis/UnnecessaryEnumFlagAnalyzer.cs | 125 +++++++++--------- .../RCS1255UnnecessaryEnumFlagTests.cs | 37 +++--- 3 files changed, 120 insertions(+), 123 deletions(-) diff --git a/src/Analyzers.CodeFixes/CSharp/CodeFixes/UnnecessaryEnumFlagCodeFixProvider.cs b/src/Analyzers.CodeFixes/CSharp/CodeFixes/UnnecessaryEnumFlagCodeFixProvider.cs index a994564799..e289836def 100644 --- a/src/Analyzers.CodeFixes/CSharp/CodeFixes/UnnecessaryEnumFlagCodeFixProvider.cs +++ b/src/Analyzers.CodeFixes/CSharp/CodeFixes/UnnecessaryEnumFlagCodeFixProvider.cs @@ -9,51 +9,50 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Roslynator.CodeFixes; -namespace Roslynator.CSharp.CodeFixes +namespace Roslynator.CSharp.CodeFixes; + +[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(UnnecessaryEnumFlagCodeFixProvider))] +[Shared] +public sealed class UnnecessaryEnumFlagCodeFixProvider : BaseCodeFixProvider { - [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(UnnecessaryEnumFlagCodeFixProvider))] - [Shared] - public sealed class UnnecessaryEnumFlagCodeFixProvider : BaseCodeFixProvider + public override ImmutableArray FixableDiagnosticIds { - public override ImmutableArray FixableDiagnosticIds - { - get { return ImmutableArray.Create(DiagnosticIdentifiers.UnnecessaryEnumFlag); } - } + get { return ImmutableArray.Create(DiagnosticIdentifiers.UnnecessaryEnumFlag); } + } + + public override async Task RegisterCodeFixesAsync(CodeFixContext context) + { + SyntaxNode root = await context.GetSyntaxRootAsync().ConfigureAwait(false); + + if (!TryFindFirstAncestorOrSelf(root, context.Span, out MemberAccessExpressionSyntax memberAccessExpression)) + return; + + Document document = context.Document; + Diagnostic diagnostic = context.Diagnostics[0]; - public override async Task RegisterCodeFixesAsync(CodeFixContext context) + switch (diagnostic.Id) { - SyntaxNode root = await context.GetSyntaxRootAsync().ConfigureAwait(false); - - if (!TryFindFirstAncestorOrSelf(root, context.Span, out MemberAccessExpressionSyntax memberAccessExpression)) - return; - - Document document = context.Document; - Diagnostic diagnostic = context.Diagnostics[0]; - - switch (diagnostic.Id) - { - case DiagnosticIdentifiers.UnnecessaryEnumFlag: - { - CodeAction codeAction = CodeAction.Create( - "Remove unnecessary flag", - ct => - { - var bitwiseOr = (BinaryExpressionSyntax)memberAccessExpression.Parent; - - ExpressionSyntax newExpression = (bitwiseOr.Left == memberAccessExpression) - ? bitwiseOr.Right - : bitwiseOr.Left; - - newExpression = newExpression.WithTriviaFrom(bitwiseOr); - - return document.ReplaceNodeAsync(bitwiseOr, newExpression, ct); - }, - GetEquivalenceKey(diagnostic)); - - context.RegisterCodeFix(codeAction, diagnostic); - break; - } - } + case DiagnosticIdentifiers.UnnecessaryEnumFlag: + { + CodeAction codeAction = CodeAction.Create( + "Remove unnecessary flag", + ct => + { + var bitwiseOr = (BinaryExpressionSyntax)memberAccessExpression.Parent; + + ExpressionSyntax newExpression = (bitwiseOr.Left == memberAccessExpression) + ? bitwiseOr.Right + : bitwiseOr.Left; + + newExpression = newExpression.WithTriviaFrom(bitwiseOr); + + return document.ReplaceNodeAsync(bitwiseOr, newExpression, ct); + }, + GetEquivalenceKey(diagnostic)); + + context.RegisterCodeFix(codeAction, diagnostic); + break; + } } } } diff --git a/src/Analyzers/CSharp/Analysis/UnnecessaryEnumFlagAnalyzer.cs b/src/Analyzers/CSharp/Analysis/UnnecessaryEnumFlagAnalyzer.cs index 53c0493fa9..dab5da5f4f 100644 --- a/src/Analyzers/CSharp/Analysis/UnnecessaryEnumFlagAnalyzer.cs +++ b/src/Analyzers/CSharp/Analysis/UnnecessaryEnumFlagAnalyzer.cs @@ -8,92 +8,91 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; -namespace Roslynator.CSharp.Analysis +namespace Roslynator.CSharp.Analysis; + +[DiagnosticAnalyzer(LanguageNames.CSharp)] +public sealed class UnnecessaryEnumFlagAnalyzer : BaseDiagnosticAnalyzer { - [DiagnosticAnalyzer(LanguageNames.CSharp)] - public sealed class UnnecessaryEnumFlagAnalyzer : BaseDiagnosticAnalyzer - { - private static ImmutableArray _supportedDiagnostics; + private static ImmutableArray _supportedDiagnostics; - public override ImmutableArray SupportedDiagnostics + public override ImmutableArray SupportedDiagnostics + { + get { - get - { - if (_supportedDiagnostics.IsDefault) - Immutable.InterlockedInitialize(ref _supportedDiagnostics, DiagnosticRules.UnnecessaryEnumFlag); + if (_supportedDiagnostics.IsDefault) + Immutable.InterlockedInitialize(ref _supportedDiagnostics, DiagnosticRules.UnnecessaryEnumFlag); - return _supportedDiagnostics; - } + return _supportedDiagnostics; } + } - public override void Initialize(AnalysisContext context) - { - base.Initialize(context); + public override void Initialize(AnalysisContext context) + { + base.Initialize(context); - context.RegisterSyntaxNodeAction(c => AnalyzeBitwiseOrExpression(c), SyntaxKind.BitwiseOrExpression); - } + context.RegisterSyntaxNodeAction(c => AnalyzeBitwiseOrExpression(c), SyntaxKind.BitwiseOrExpression); + } + + private static void AnalyzeBitwiseOrExpression(SyntaxNodeAnalysisContext context) + { + var bitwiseAnd = (BinaryExpressionSyntax)context.Node; - private static void AnalyzeBitwiseOrExpression(SyntaxNodeAnalysisContext context) + foreach (ExpressionSyntax expression in bitwiseAnd.AsChain()) { - var bitwiseAnd = (BinaryExpressionSyntax)context.Node; + if (!expression.IsKind(SyntaxKind.SimpleMemberAccessExpression)) + return; + } - foreach (ExpressionSyntax expression in bitwiseAnd.AsChain()) - { - if (!expression.IsKind(SyntaxKind.SimpleMemberAccessExpression)) - return; - } + SemanticModel semanticModel = context.SemanticModel; + CancellationToken cancellationToken = context.CancellationToken; + ITypeSymbol symbol = semanticModel.GetTypeSymbol(bitwiseAnd, cancellationToken); - SemanticModel semanticModel = context.SemanticModel; - CancellationToken cancellationToken = context.CancellationToken; - ITypeSymbol symbol = semanticModel.GetTypeSymbol(bitwiseAnd, cancellationToken); + if (!symbol.HasAttribute(MetadataNames.System_FlagsAttribute)) + return; - if (!symbol.HasAttribute(MetadataNames.System_FlagsAttribute)) - return; + var enumSymbol = (INamedTypeSymbol)symbol; + var values = new List<(ExpressionSyntax, ulong)>(); - var enumSymbol = (INamedTypeSymbol)symbol; - var values = new List<(ExpressionSyntax, ulong)>(); + foreach (ExpressionSyntax expression in bitwiseAnd.AsChain()) + { + Optional constantValueOpt = semanticModel.GetConstantValue(expression, cancellationToken); - foreach (ExpressionSyntax expression in bitwiseAnd.AsChain()) - { - Optional constantValueOpt = semanticModel.GetConstantValue(expression, cancellationToken); + if (!constantValueOpt.HasValue) + continue; - if (!constantValueOpt.HasValue) - continue; + ulong value = SymbolUtility.GetEnumValueAsUInt64(constantValueOpt.Value, enumSymbol); + var addToValues = true; - ulong value = SymbolUtility.GetEnumValueAsUInt64(constantValueOpt.Value, enumSymbol); - var addToValues = true; + for (int i = values.Count - 1; i >= 0; i--) + { + (ExpressionSyntax expression2, ulong value2) = values[i]; - for (int i = values.Count - 1; i >= 0; i--) + if ((value & value2) != 0) { - (ExpressionSyntax expression2, ulong value2) = values[i]; - - if ((value & value2) != 0) + if (value <= value2) + { + ReportDiagnostic(expression, expression2); + addToValues = false; + } + else { - if (value <= value2) - { - ReportDiagnostic(expression, expression2); - addToValues = false; - } - else - { - ReportDiagnostic(expression2, expression); - values.RemoveAt(i); - } + ReportDiagnostic(expression2, expression); + values.RemoveAt(i); } } - - if (addToValues) - values.Add((expression, value)); } - void ReportDiagnostic(ExpressionSyntax expression, ExpressionSyntax expression2) - { - context.ReportDiagnostic( - DiagnosticRules.UnnecessaryEnumFlag, - expression, - context.SemanticModel.GetSymbol(expression, context.CancellationToken).Name, - context.SemanticModel.GetSymbol(expression2, context.CancellationToken).Name); - } + if (addToValues) + values.Add((expression, value)); + } + + void ReportDiagnostic(ExpressionSyntax expression, ExpressionSyntax expression2) + { + context.ReportDiagnostic( + DiagnosticRules.UnnecessaryEnumFlag, + expression, + context.SemanticModel.GetSymbol(expression, context.CancellationToken).Name, + context.SemanticModel.GetSymbol(expression2, context.CancellationToken).Name); } } } diff --git a/src/Tests/Analyzers.Tests/RCS1255UnnecessaryEnumFlagTests.cs b/src/Tests/Analyzers.Tests/RCS1255UnnecessaryEnumFlagTests.cs index 3fc035148c..ec24bd93be 100644 --- a/src/Tests/Analyzers.Tests/RCS1255UnnecessaryEnumFlagTests.cs +++ b/src/Tests/Analyzers.Tests/RCS1255UnnecessaryEnumFlagTests.cs @@ -6,16 +6,16 @@ using Roslynator.Testing.CSharp; using Xunit; -namespace Roslynator.CSharp.Analysis.Tests +namespace Roslynator.CSharp.Analysis.Tests; + +public class RCS1255UnnecessaryEnumFlagTests : AbstractCSharpDiagnosticVerifier { - public class RCS1255UnnecessaryEnumFlagTests : AbstractCSharpDiagnosticVerifier - { - public override DiagnosticDescriptor Descriptor { get; } = DiagnosticRules.UnnecessaryEnumFlag; + public override DiagnosticDescriptor Descriptor { get; } = DiagnosticRules.UnnecessaryEnumFlag; - [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.UnnecessaryEnumFlag)] - public async Task Test() - { - await VerifyDiagnosticAndFixAsync(@" + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.UnnecessaryEnumFlag)] + public async Task Test() + { + await VerifyDiagnosticAndFixAsync(@" using System.Globalization; class C @@ -36,12 +36,12 @@ void M() } } "); - } + } - [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.UnnecessaryEnumFlag)] - public async Task Test2() - { - await VerifyDiagnosticAndFixAsync(@" + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.UnnecessaryEnumFlag)] + public async Task Test2() + { + await VerifyDiagnosticAndFixAsync(@" using System.Globalization; class C @@ -62,12 +62,12 @@ void M() } } "); - } + } - [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.UnnecessaryEnumFlag)] - public async Task TestNoDiagnostic() - { - await VerifyNoDiagnosticAsync(@" + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.UnnecessaryEnumFlag)] + public async Task TestNoDiagnostic() + { + await VerifyNoDiagnosticAsync(@" using System.Globalization; class C @@ -78,6 +78,5 @@ void M() } } "); - } } } From 0995e44bead9eba85c548ff98dfd54d78d3d77c5 Mon Sep 17 00:00:00 2001 From: Josef Pihrt Date: Fri, 18 Aug 2023 21:06:06 +0200 Subject: [PATCH 3/5] update --- ChangeLog.md | 2 + .../Analysis/UnnecessaryEnumFlagAnalyzer.cs | 47 +++++++++---------- .../CSharp/DiagnosticIdentifiers.Generated.cs | 2 +- .../CSharp/DiagnosticRules.Generated.cs | 24 +++++----- ....xml => UnnecessaryEnumFlag.Analyzers.xml} | 3 +- ....cs => RCS1258UnnecessaryEnumFlagTests.cs} | 2 +- .../src/configurationFiles.generated.ts | 6 +-- tools/generate_code.ps1 | 2 + tools/generate_metadata.ps1 | 2 + 9 files changed, 46 insertions(+), 44 deletions(-) rename src/Analyzers/{Analyzers.UnnecessaryEnumFlag.xml => UnnecessaryEnumFlag.Analyzers.xml} (88%) rename src/Tests/Analyzers.Tests/{RCS1255UnnecessaryEnumFlagTests.cs => RCS1258UnnecessaryEnumFlagTests.cs} (96%) diff --git a/ChangeLog.md b/ChangeLog.md index bef300d235..f84476b66c 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 "Unnecessary enum flag" [RCS1258](https://github.com/JosefPihrt/Roslynator/blob/main/docs/analyzers/RCS1258.md) ([#886](https://github.com/JosefPihrt/Roslynator/pull/886)). + - Enabled by default. ### Fixed diff --git a/src/Analyzers/CSharp/Analysis/UnnecessaryEnumFlagAnalyzer.cs b/src/Analyzers/CSharp/Analysis/UnnecessaryEnumFlagAnalyzer.cs index dab5da5f4f..2ba984900e 100644 --- a/src/Analyzers/CSharp/Analysis/UnnecessaryEnumFlagAnalyzer.cs +++ b/src/Analyzers/CSharp/Analysis/UnnecessaryEnumFlagAnalyzer.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Collections.Immutable; -using System.Threading; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -43,9 +42,7 @@ private static void AnalyzeBitwiseOrExpression(SyntaxNodeAnalysisContext context return; } - SemanticModel semanticModel = context.SemanticModel; - CancellationToken cancellationToken = context.CancellationToken; - ITypeSymbol symbol = semanticModel.GetTypeSymbol(bitwiseAnd, cancellationToken); + ITypeSymbol symbol = context.SemanticModel.GetTypeSymbol(bitwiseAnd, context.CancellationToken); if (!symbol.HasAttribute(MetadataNames.System_FlagsAttribute)) return; @@ -55,35 +52,35 @@ private static void AnalyzeBitwiseOrExpression(SyntaxNodeAnalysisContext context foreach (ExpressionSyntax expression in bitwiseAnd.AsChain()) { - Optional constantValueOpt = semanticModel.GetConstantValue(expression, cancellationToken); + Optional constantValueOpt = context.SemanticModel.GetConstantValue(expression, context.CancellationToken); - if (!constantValueOpt.HasValue) - continue; - - ulong value = SymbolUtility.GetEnumValueAsUInt64(constantValueOpt.Value, enumSymbol); - var addToValues = true; - - for (int i = values.Count - 1; i >= 0; i--) + if (constantValueOpt.HasValue) { - (ExpressionSyntax expression2, ulong value2) = values[i]; + ulong value = SymbolUtility.GetEnumValueAsUInt64(constantValueOpt.Value, enumSymbol); + var addToValues = true; - if ((value & value2) != 0) + for (int i = values.Count - 1; i >= 0; i--) { - if (value <= value2) - { - ReportDiagnostic(expression, expression2); - addToValues = false; - } - else + (ExpressionSyntax expression2, ulong value2) = values[i]; + + if ((value & value2) != 0) { - ReportDiagnostic(expression2, expression); - values.RemoveAt(i); + if (value <= value2) + { + ReportDiagnostic(expression, expression2); + addToValues = false; + } + else + { + ReportDiagnostic(expression2, expression); + values.RemoveAt(i); + } } } - } - if (addToValues) - values.Add((expression, value)); + if (addToValues) + values.Add((expression, value)); + } } void ReportDiagnostic(ExpressionSyntax expression, ExpressionSyntax expression2) diff --git a/src/Analyzers/CSharp/DiagnosticIdentifiers.Generated.cs b/src/Analyzers/CSharp/DiagnosticIdentifiers.Generated.cs index 3fff9fa9fa..819eede556 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 UnnecessaryEnumFlag = "RCS1255"; public const string SimplifyArgumentNullCheck = "RCS1255"; public const string InvalidArgumentNullCheck = "RCS1256"; + public const string UnnecessaryEnumFlag = "RCS1258"; } } \ No newline at end of file diff --git a/src/Analyzers/CSharp/DiagnosticRules.Generated.cs b/src/Analyzers/CSharp/DiagnosticRules.Generated.cs index b083f51f8a..412efb6a06 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 UnnecessaryEnumFlag = DiagnosticDescriptorFactory.Create( - id: DiagnosticIdentifiers.UnnecessaryEnumFlag, - title: "Unnecessary enum flag.", - messageFormat: "Enum flag '{0}' is already contained in flag '{1}'.", - category: DiagnosticCategories.Roslynator, - defaultSeverity: DiagnosticSeverity.Info, - isEnabledByDefault: true, - description: null, - helpLinkUri: DiagnosticIdentifiers.UnnecessaryEnumFlag, - customTags: WellKnownDiagnosticTags.Unnecessary); - /// 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()); + /// RCS1258 + public static readonly DiagnosticDescriptor UnnecessaryEnumFlag = DiagnosticDescriptorFactory.Create( + id: DiagnosticIdentifiers.UnnecessaryEnumFlag, + title: "Unnecessary enum flag.", + messageFormat: "Enum flag '{0}' is already contained in flag '{1}'.", + category: DiagnosticCategories.Roslynator, + defaultSeverity: DiagnosticSeverity.Info, + isEnabledByDefault: true, + description: null, + helpLinkUri: DiagnosticIdentifiers.UnnecessaryEnumFlag, + customTags: WellKnownDiagnosticTags.Unnecessary); + } } \ No newline at end of file diff --git a/src/Analyzers/Analyzers.UnnecessaryEnumFlag.xml b/src/Analyzers/UnnecessaryEnumFlag.Analyzers.xml similarity index 88% rename from src/Analyzers/Analyzers.UnnecessaryEnumFlag.xml rename to src/Analyzers/UnnecessaryEnumFlag.Analyzers.xml index fcd058e7bd..7efe901c1a 100644 --- a/src/Analyzers/Analyzers.UnnecessaryEnumFlag.xml +++ b/src/Analyzers/UnnecessaryEnumFlag.Analyzers.xml @@ -1,10 +1,9 @@ - RCS1255 + RCS1258 Codestin Search App Enum flag '{0}' is already contained in flag '{1}'. - General Info true true diff --git a/src/Tests/Analyzers.Tests/RCS1255UnnecessaryEnumFlagTests.cs b/src/Tests/Analyzers.Tests/RCS1258UnnecessaryEnumFlagTests.cs similarity index 96% rename from src/Tests/Analyzers.Tests/RCS1255UnnecessaryEnumFlagTests.cs rename to src/Tests/Analyzers.Tests/RCS1258UnnecessaryEnumFlagTests.cs index ec24bd93be..8fc4b8c85c 100644 --- a/src/Tests/Analyzers.Tests/RCS1255UnnecessaryEnumFlagTests.cs +++ b/src/Tests/Analyzers.Tests/RCS1258UnnecessaryEnumFlagTests.cs @@ -8,7 +8,7 @@ namespace Roslynator.CSharp.Analysis.Tests; -public class RCS1255UnnecessaryEnumFlagTests : AbstractCSharpDiagnosticVerifier +public class RCS1258UnnecessaryEnumFlagTests : AbstractCSharpDiagnosticVerifier { public override DiagnosticDescriptor Descriptor { get; } = DiagnosticRules.UnnecessaryEnumFlag; diff --git a/src/VisualStudioCode/package/src/configurationFiles.generated.ts b/src/VisualStudioCode/package/src/configurationFiles.generated.ts index 25b4922ca5..e8480d26e7 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 -# Unnecessary enum flag -#dotnet_diagnostic.rcs1255.severity = suggestion - # Simplify argument null check #dotnet_diagnostic.rcs1255.severity = none # Invalid argument null check #dotnet_diagnostic.rcs1256.severity = suggestion +# Unnecessary enum flag +#dotnet_diagnostic.rcs1258.severity = suggestion + # Use pattern matching #dotnet_diagnostic.rcs9001.severity = silent 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" From 52746cf450b9fadc40fb364e5ef07e8ad2372c6d Mon Sep 17 00:00:00 2001 From: Josef Pihrt Date: Sat, 19 Aug 2023 19:11:27 +0200 Subject: [PATCH 4/5] update --- src/Analyzers.xml | 14 ++++++++++++++ src/Analyzers/UnnecessaryEnumFlag.Analyzers.xml | 11 ----------- 2 files changed, 14 insertions(+), 11 deletions(-) delete mode 100644 src/Analyzers/UnnecessaryEnumFlag.Analyzers.xml diff --git a/src/Analyzers.xml b/src/Analyzers.xml index 2c334157e6..99eaf79f86 100644 --- a/src/Analyzers.xml +++ b/src/Analyzers.xml @@ -7368,6 +7368,20 @@ void M() + + RCS1258 + Codestin Search App + Enum flag '{0}' is already contained in flag '{1}'. + Info + true + true + + + + + + + RCS9001 UsePatternMatching diff --git a/src/Analyzers/UnnecessaryEnumFlag.Analyzers.xml b/src/Analyzers/UnnecessaryEnumFlag.Analyzers.xml deleted file mode 100644 index 7efe901c1a..0000000000 --- a/src/Analyzers/UnnecessaryEnumFlag.Analyzers.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - RCS1258 - Codestin Search App - Enum flag '{0}' is already contained in flag '{1}'. - Info - true - true - - \ No newline at end of file From 33ef3ed2aec1a5bfc3891037a2e152d2493a8e7e Mon Sep 17 00:00:00 2001 From: Josef Pihrt Date: Sat, 19 Aug 2023 19:14:21 +0200 Subject: [PATCH 5/5] 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..fc070c8d47 --- /dev/null +++ b/docs/configuration.md @@ -0,0 +1 @@ +https://josefpihrt.github.io/docs/roslynator/configuration \ No newline at end of file