diff --git a/README.md b/README.md index 05e89132..926b0169 100755 --- a/README.md +++ b/README.md @@ -187,6 +187,7 @@ If you are already using other analyzers, you can check [which rules are duplica |[MA0169](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0169.md)|Design|Use Equals method instead of operator|⚠️|✔️|❌| |[MA0170](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0170.md)|Design|Type cannot be used as an attribute argument|⚠️|❌|❌| |[MA0171](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0171.md)|Usage|Use pattern matching instead of inequality operators for discrete value|ℹ️|❌|✔️| +|[MA0172](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0172.md)|Usage|Both sides of the logical operation are identical|⚠️|❌|❌| diff --git a/docs/README.md b/docs/README.md index afc32fdc..2edf082c 100755 --- a/docs/README.md +++ b/docs/README.md @@ -171,6 +171,7 @@ |[MA0169](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0169.md)|Design|Use Equals method instead of operator|⚠️|✔️|❌| |[MA0170](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0170.md)|Design|Type cannot be used as an attribute argument|⚠️|❌|❌| |[MA0171](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0171.md)|Usage|Use pattern matching instead of inequality operators for discrete value|ℹ️|❌|✔️| +|[MA0172](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0172.md)|Usage|Both sides of the logical operation are identical|⚠️|❌|❌| |Id|Suppressed rule|Justification| |--|---------------|-------------| @@ -692,6 +693,9 @@ dotnet_diagnostic.MA0170.severity = none # MA0171: Use pattern matching instead of inequality operators for discrete value dotnet_diagnostic.MA0171.severity = none + +# MA0172: Both sides of the logical operation are identical +dotnet_diagnostic.MA0172.severity = none ``` # .editorconfig - all rules disabled @@ -1206,4 +1210,7 @@ dotnet_diagnostic.MA0170.severity = none # MA0171: Use pattern matching instead of inequality operators for discrete value dotnet_diagnostic.MA0171.severity = none + +# MA0172: Both sides of the logical operation are identical +dotnet_diagnostic.MA0172.severity = none ``` diff --git a/docs/Rules/MA0172.md b/docs/Rules/MA0172.md new file mode 100644 index 00000000..24c9bd0a --- /dev/null +++ b/docs/Rules/MA0172.md @@ -0,0 +1,8 @@ +# MA0172 - Both sides of the logical operation are identical + +This rule triggers when both sides of a logical operation (such as `&&`, `||`, `&`, `|`, `==`, `!=`, or pattern matching operations) are identical. This usually indicates a mistake or redundant code. + +```csharp +_ = x == x; // non-compliant +_ = x is true or true; // non-compliant +``` diff --git a/src/Meziantou.Analyzer.Pack/configuration/default.editorconfig b/src/Meziantou.Analyzer.Pack/configuration/default.editorconfig index ba221cee..bafccfa2 100644 --- a/src/Meziantou.Analyzer.Pack/configuration/default.editorconfig +++ b/src/Meziantou.Analyzer.Pack/configuration/default.editorconfig @@ -511,3 +511,6 @@ dotnet_diagnostic.MA0170.severity = none # MA0171: Use pattern matching instead of inequality operators for discrete value dotnet_diagnostic.MA0171.severity = none + +# MA0172: Both sides of the logical operation are identical +dotnet_diagnostic.MA0172.severity = none diff --git a/src/Meziantou.Analyzer.Pack/configuration/none.editorconfig b/src/Meziantou.Analyzer.Pack/configuration/none.editorconfig index ad31e62d..6dedb33a 100644 --- a/src/Meziantou.Analyzer.Pack/configuration/none.editorconfig +++ b/src/Meziantou.Analyzer.Pack/configuration/none.editorconfig @@ -511,3 +511,6 @@ dotnet_diagnostic.MA0170.severity = none # MA0171: Use pattern matching instead of inequality operators for discrete value dotnet_diagnostic.MA0171.severity = none + +# MA0172: Both sides of the logical operation are identical +dotnet_diagnostic.MA0172.severity = none diff --git a/src/Meziantou.Analyzer/RuleIdentifiers.cs b/src/Meziantou.Analyzer/RuleIdentifiers.cs index a48b05a6..509e8a70 100755 --- a/src/Meziantou.Analyzer/RuleIdentifiers.cs +++ b/src/Meziantou.Analyzer/RuleIdentifiers.cs @@ -174,6 +174,7 @@ internal static class RuleIdentifiers public const string UseEqualsMethodInsteadOfOperator = "MA0169"; public const string TypeCannotBeUsedInAnAttributeParameter = "MA0170"; public const string UsePatternMatchingInsteadOfHasvalue = "MA0171"; + public const string BothSideOfTheConditionAreIdentical = "MA0172"; public static string GetHelpUri(string identifier) { diff --git a/src/Meziantou.Analyzer/Rules/BothSideOfTheConditionAreIdenticalAnalyzer.cs b/src/Meziantou.Analyzer/Rules/BothSideOfTheConditionAreIdenticalAnalyzer.cs new file mode 100644 index 00000000..539b2f8e --- /dev/null +++ b/src/Meziantou.Analyzer/Rules/BothSideOfTheConditionAreIdenticalAnalyzer.cs @@ -0,0 +1,56 @@ +using System.Collections.Immutable; +using Meziantou.Analyzer.Internals; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Operations; + +namespace Meziantou.Analyzer.Rules; + +[DiagnosticAnalyzer(LanguageNames.CSharp)] +public class BothSideOfTheConditionAreIdenticalAnalyzer : DiagnosticAnalyzer +{ + private static readonly DiagnosticDescriptor Rule = new( + RuleIdentifiers.BothSideOfTheConditionAreIdentical, + title: "Both sides of the logical operation are identical", + messageFormat: "Both sides of the logical operation are identical", + RuleCategories.Usage, + DiagnosticSeverity.Warning, + isEnabledByDefault: false, + helpLinkUri: RuleIdentifiers.GetHelpUri(RuleIdentifiers.BothSideOfTheConditionAreIdentical)); + + public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); + + public override void Initialize(AnalysisContext context) + { + context.EnableConcurrentExecution(); + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + + + context.RegisterOperationAction(AnalyzeBinaryOperation, OperationKind.Binary); + context.RegisterOperationAction(AnalyzeBinaryPatternOperation, OperationKind.BinaryPattern); + } + + private void AnalyzeBinaryOperation(OperationAnalysisContext context) + { + var operation = (IBinaryOperation)context.Operation; + if (operation.OperatorKind is BinaryOperatorKind.ConditionalAnd or BinaryOperatorKind.ConditionalOr or BinaryOperatorKind.And or BinaryOperatorKind.Or or BinaryOperatorKind.Equals or BinaryOperatorKind.NotEquals) + { + if (operation.Type.IsBoolean() && operation.LeftOperand.Syntax.IsEquivalentTo(operation.RightOperand.Syntax, topLevel: false)) + { + context.ReportDiagnostic(Rule, operation); + } + } + } + + private void AnalyzeBinaryPatternOperation(OperationAnalysisContext context) + { + var operation = (IBinaryPatternOperation)context.Operation; + if (operation.OperatorKind is BinaryOperatorKind.And or BinaryOperatorKind.Or) + { + if (operation.LeftPattern.Syntax.IsEquivalentTo(operation.RightPattern.Syntax, topLevel: false)) + { + context.ReportDiagnostic(Rule, operation); + } + } + } +} diff --git a/tests/Meziantou.Analyzer.Test/Rules/BothSideOfTheConditionAreIdenticalAnalyzerTests.cs b/tests/Meziantou.Analyzer.Test/Rules/BothSideOfTheConditionAreIdenticalAnalyzerTests.cs new file mode 100644 index 00000000..1e1f8c05 --- /dev/null +++ b/tests/Meziantou.Analyzer.Test/Rules/BothSideOfTheConditionAreIdenticalAnalyzerTests.cs @@ -0,0 +1,60 @@ +using Meziantou.Analyzer.Rules; +using TestHelper; +using Xunit; + +namespace Meziantou.Analyzer.Test.Rules; + +public sealed class BothSideOfTheConditionAreIdenticalAnalyzerTests +{ + private static ProjectBuilder CreateProjectBuilder() + { + return new ProjectBuilder() + .WithOutputKind(Microsoft.CodeAnalysis.OutputKind.ConsoleApplication) + .WithAnalyzer(); + } + + [Theory] + [InlineData("a == b")] + [InlineData("a != b")] + [InlineData("a & b")] + [InlineData("a && b")] + [InlineData("a | b")] + [InlineData("a || b")] + [InlineData("a is false")] + [InlineData("a is true")] + [InlineData("a is false or true")] + [InlineData("a is false and not true")] + public async Task DifferentCode(string expression) + { + await CreateProjectBuilder() + .WithSourceCode($$""" + var a = false; + var b = false; + var c = 0; + _ = {{expression}}; + """) + .ValidateAsync(); + } + + [Theory] + [InlineData("[|a == a|]")] + [InlineData("[|a != a|]")] + [InlineData("[|a & a|]")] + [InlineData("[|a && a|]")] + [InlineData("[|a | a|]")] + [InlineData("[|a || a|]")] + [InlineData("a is [|true or true|]")] + [InlineData("a is [|true and true|]")] + public async Task SameCode(string expression) + { + await CreateProjectBuilder() + .WithSourceCode($$""" + var a = false; + var b = false; + var c = 0; + _ = {{expression}}; + """) + .ValidateAsync(); + } + +}