Skip to content

Commit 2919928

Browse files
Merge pull request #72522 from CyrusNajmabadi/redundantOperators
2 parents 9be4c18 + 326db66 commit 2919928

File tree

5 files changed

+98
-82
lines changed

5 files changed

+98
-82
lines changed

src/Analyzers/CSharp/Tests/RemoveRedundantEquality/RemoveRedundantEqualityTests.cs

+22-8
Original file line numberDiff line numberDiff line change
@@ -44,16 +44,23 @@ public bool M1(bool x)
4444
[Fact]
4545
public async Task TestSimpleCaseForEqualsFalse_NoDiagnostics()
4646
{
47-
var code = """
47+
await VerifyCS.VerifyCodeFixAsync("""
4848
public class C
4949
{
5050
public bool M1(bool x)
5151
{
52-
return x == false;
52+
return x [|==|] false;
5353
}
5454
}
55-
""";
56-
await VerifyCS.VerifyAnalyzerAsync(code);
55+
""", """
56+
public class C
57+
{
58+
public bool M1(bool x)
59+
{
60+
return !x;
61+
}
62+
}
63+
""");
5764
}
5865

5966
[Fact]
@@ -83,16 +90,23 @@ public bool M1(bool x)
8390
[Fact]
8491
public async Task TestSimpleCaseForNotEqualsTrue_NoDiagnostics()
8592
{
86-
var code = """
93+
await VerifyCS.VerifyCodeFixAsync("""
8794
public class C
8895
{
8996
public bool M1(bool x)
9097
{
91-
return x != true;
98+
return x [|!=|] true;
9299
}
93100
}
94-
""";
95-
await VerifyCS.VerifyAnalyzerAsync(code);
101+
""", """
102+
public class C
103+
{
104+
public bool M1(bool x)
105+
{
106+
return !x;
107+
}
108+
}
109+
""");
96110
}
97111

98112
[Fact]

src/Analyzers/Core/Analyzers/RemoveRedundantEquality/AbstractRemoveRedundantEqualityDiagnosticAnalyzer.cs

+28-32
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,13 @@
1010

1111
namespace Microsoft.CodeAnalysis.RemoveRedundantEquality;
1212

13-
internal abstract class AbstractRemoveRedundantEqualityDiagnosticAnalyzer
14-
: AbstractBuiltInCodeStyleDiagnosticAnalyzer
13+
internal abstract class AbstractRemoveRedundantEqualityDiagnosticAnalyzer(ISyntaxFacts syntaxFacts)
14+
: AbstractBuiltInCodeStyleDiagnosticAnalyzer(
15+
IDEDiagnosticIds.RemoveRedundantEqualityDiagnosticId,
16+
EnforceOnBuildValues.RemoveRedundantEquality,
17+
option: null,
18+
new LocalizableResourceString(nameof(AnalyzersResources.Remove_redundant_equality), AnalyzersResources.ResourceManager, typeof(AnalyzersResources)))
1519
{
16-
private readonly ISyntaxFacts _syntaxFacts;
17-
18-
protected AbstractRemoveRedundantEqualityDiagnosticAnalyzer(ISyntaxFacts syntaxFacts)
19-
: base(IDEDiagnosticIds.RemoveRedundantEqualityDiagnosticId,
20-
EnforceOnBuildValues.RemoveRedundantEquality,
21-
option: null,
22-
new LocalizableResourceString(nameof(AnalyzersResources.Remove_redundant_equality), AnalyzersResources.ResourceManager, typeof(AnalyzersResources)))
23-
{
24-
_syntaxFacts = syntaxFacts;
25-
}
26-
2720
public override DiagnosticAnalyzerCategory GetAnalyzerCategory()
2821
=> DiagnosticAnalyzerCategory.SemanticSpanAnalysis;
2922

@@ -35,19 +28,15 @@ private void AnalyzeBinaryOperator(OperationAnalysisContext context)
3528
if (ShouldSkipAnalysis(context, notification: null))
3629
return;
3730

31+
// We shouldn't report diagnostic on overloaded operator as the behavior can change.
3832
var operation = (IBinaryOperation)context.Operation;
3933
if (operation.OperatorMethod is not null)
40-
{
41-
// We shouldn't report diagnostic on overloaded operator as the behavior can change.
4234
return;
43-
}
4435

4536
if (operation.OperatorKind is not (BinaryOperatorKind.Equals or BinaryOperatorKind.NotEquals))
46-
{
4737
return;
48-
}
4938

50-
if (!_syntaxFacts.IsBinaryExpression(operation.Syntax))
39+
if (!syntaxFacts.IsBinaryExpression(operation.Syntax))
5140
{
5241
return;
5342
}
@@ -56,9 +45,7 @@ private void AnalyzeBinaryOperator(OperationAnalysisContext context)
5645
var leftOperand = operation.LeftOperand;
5746

5847
if (rightOperand.Type is null || leftOperand.Type is null)
59-
{
6048
return;
61-
}
6249

6350
if (rightOperand.Type.SpecialType != SpecialType.System_Boolean ||
6451
leftOperand.Type.SpecialType != SpecialType.System_Boolean)
@@ -67,34 +54,43 @@ private void AnalyzeBinaryOperator(OperationAnalysisContext context)
6754
}
6855

6956
var isOperatorEquals = operation.OperatorKind == BinaryOperatorKind.Equals;
70-
_syntaxFacts.GetPartsOfBinaryExpression(operation.Syntax, out _, out var operatorToken, out _);
57+
syntaxFacts.GetPartsOfBinaryExpression(operation.Syntax, out _, out var operatorToken, out _);
58+
7159
var properties = ImmutableDictionary.CreateBuilder<string, string?>();
72-
if (TryGetLiteralValue(rightOperand) == isOperatorEquals)
60+
if (TryGetLiteralValue(rightOperand) is bool rightBool)
7361
{
7462
properties.Add(RedundantEqualityConstants.RedundantSide, RedundantEqualityConstants.Right);
63+
if (rightBool != isOperatorEquals)
64+
properties.Add(RedundantEqualityConstants.Negate, RedundantEqualityConstants.Negate);
7565
}
76-
else if (TryGetLiteralValue(leftOperand) == isOperatorEquals)
66+
else if (TryGetLiteralValue(leftOperand) is bool leftBool)
7767
{
7868
properties.Add(RedundantEqualityConstants.RedundantSide, RedundantEqualityConstants.Left);
69+
if (leftBool != isOperatorEquals)
70+
properties.Add(RedundantEqualityConstants.Negate, RedundantEqualityConstants.Negate);
7971
}
80-
81-
if (properties.Count == 1)
72+
else
8273
{
83-
context.ReportDiagnostic(Diagnostic.Create(Descriptor,
84-
operatorToken.GetLocation(),
85-
additionalLocations: [operation.Syntax.GetLocation()],
86-
properties: properties.ToImmutable()));
74+
return;
8775
}
8876

77+
context.ReportDiagnostic(Diagnostic.Create(Descriptor,
78+
operatorToken.GetLocation(),
79+
additionalLocations: [operation.Syntax.GetLocation()],
80+
properties: properties.ToImmutable()));
81+
8982
return;
9083

9184
static bool? TryGetLiteralValue(IOperation operand)
9285
{
9386
// Make sure we only simplify literals to avoid changing
9487
// something like the following example:
9588
// const bool Activated = true; ... if (state == Activated)
96-
if (operand.ConstantValue.HasValue && operand.Kind == OperationKind.Literal &&
97-
operand.ConstantValue.Value is bool constValue)
89+
if (operand is
90+
{
91+
Kind: OperationKind.Literal,
92+
ConstantValue: { HasValue: true, Value: bool constValue }
93+
})
9894
{
9995
return constValue;
10096
}

src/Analyzers/Core/Analyzers/RemoveRedundantEquality/RedundantEqualityConstants.cs

+1
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,5 @@ internal static class RedundantEqualityConstants
99
public const string RedundantSide = nameof(RedundantSide);
1010
public const string Left = nameof(Left);
1111
public const string Right = nameof(Right);
12+
public const string Negate = nameof(Negate);
1213
}

src/Analyzers/Core/CodeFixes/RemoveRedundantEquality/RemoveRedundantEqualityCodeFixProvider.cs

+29-34
Original file line numberDiff line numberDiff line change
@@ -21,54 +21,49 @@ namespace Microsoft.CodeAnalysis.RemoveRedundantEquality;
2121
[ExportCodeFixProvider(LanguageNames.CSharp, LanguageNames.VisualBasic, Name = PredefinedCodeFixProviderNames.RemoveRedundantEquality), Shared]
2222
[method: ImportingConstructor]
2323
[method: SuppressMessage("RoslynDiagnosticsReliability", "RS0033:Importing constructor should be [Obsolete]", Justification = "Used in test code: https://github.com/dotnet/roslyn/issues/42814")]
24-
internal sealed class RemoveRedundantEqualityCodeFixProvider() : SyntaxEditorBasedCodeFixProvider
24+
internal sealed class RemoveRedundantEqualityCodeFixProvider() : ForkingSyntaxEditorBasedCodeFixProvider<SyntaxNode>
2525
{
2626
public override ImmutableArray<string> FixableDiagnosticIds => [IDEDiagnosticIds.RemoveRedundantEqualityDiagnosticId];
2727

28-
public override Task RegisterCodeFixesAsync(CodeFixContext context)
29-
{
30-
foreach (var diagnostic in context.Diagnostics)
31-
{
32-
RegisterCodeFix(context, AnalyzersResources.Remove_redundant_equality, nameof(AnalyzersResources.Remove_redundant_equality), diagnostic);
33-
}
28+
protected override (string title, string equivalenceKey) GetTitleAndEquivalenceKey(CodeFixContext context)
29+
=> (AnalyzersResources.Remove_redundant_equality, nameof(AnalyzersResources.Remove_redundant_equality));
3430

35-
return Task.CompletedTask;
36-
}
37-
38-
protected override async Task FixAllAsync(Document document, ImmutableArray<Diagnostic> diagnostics, SyntaxEditor editor, CodeActionOptionsProvider fallbackOptions, CancellationToken cancellationToken)
31+
protected override async Task FixAsync(
32+
Document document,
33+
SyntaxEditor editor,
34+
CodeActionOptionsProvider fallbackOptions,
35+
SyntaxNode node,
36+
ImmutableDictionary<string, string?> properties,
37+
CancellationToken cancellationToken)
3938
{
4039
var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
40+
var generator = editor.Generator;
41+
var generatorInternal = document.GetRequiredLanguageService<SyntaxGeneratorInternal>();
42+
4143
var syntaxFacts = document.GetRequiredLanguageService<ISyntaxFactsService>();
42-
var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
44+
var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false);
4345

44-
foreach (var diagnostic in diagnostics)
46+
editor.ReplaceNode(node, WithElasticTrailingTrivia(RewriteNode()));
47+
48+
return;
49+
50+
SyntaxNode RewriteNode()
4551
{
46-
var node = root.FindNode(diagnostic.AdditionalLocations[0].SourceSpan, getInnermostNodeForTie: true);
52+
// This should happen only in error cases.
53+
if (!syntaxFacts.IsBinaryExpression(node))
54+
return node;
4755

48-
editor.ReplaceNode(node, (n, _) =>
49-
{
50-
if (!syntaxFacts.IsBinaryExpression(n))
51-
{
52-
// This should happen only in error cases.
53-
return n;
54-
}
56+
syntaxFacts.GetPartsOfBinaryExpression(node, out var left, out var right);
57+
var rewritten =
58+
properties[RedundantEqualityConstants.RedundantSide] == RedundantEqualityConstants.Right ? left :
59+
properties[RedundantEqualityConstants.RedundantSide] == RedundantEqualityConstants.Left ? right : node;
5560

56-
syntaxFacts.GetPartsOfBinaryExpression(n, out var left, out var right);
57-
if (diagnostic.Properties[RedundantEqualityConstants.RedundantSide] == RedundantEqualityConstants.Right)
58-
{
59-
return WithElasticTrailingTrivia(left);
60-
}
61-
else if (diagnostic.Properties[RedundantEqualityConstants.RedundantSide] == RedundantEqualityConstants.Left)
62-
{
63-
return WithElasticTrailingTrivia(right);
64-
}
61+
if (properties.ContainsKey(RedundantEqualityConstants.Negate))
62+
rewritten = generator.Negate(generatorInternal, rewritten, semanticModel, cancellationToken);
6563

66-
return n;
67-
});
64+
return rewritten;
6865
}
6966

70-
return;
71-
7267
static SyntaxNode WithElasticTrailingTrivia(SyntaxNode node)
7368
{
7469
return node.WithTrailingTrivia(node.GetTrailingTrivia().Select(SyntaxTriviaExtensions.AsElastic));

src/Analyzers/VisualBasic/Tests/RemoveRedundantEquality/RemoveRedundantEqualityTests.vb

+18-8
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,19 @@ End Module
2929

3030
<Fact>
3131
Public Async Function TestSimpleCaseForEqualsFalse_NoDiagnostics() As Task
32-
Dim code = "
32+
Await VerifyVB.VerifyCodeFixAsync("
3333
Public Module Module1
3434
Public Function M1(x As Boolean) As Boolean
35-
Return x = False
35+
Return x [|=|] False
3636
End Function
3737
End Module
38-
"
39-
Await VerifyVB.VerifyAnalyzerAsync(code)
38+
", "
39+
Public Module Module1
40+
Public Function M1(x As Boolean) As Boolean
41+
Return Not x
42+
End Function
43+
End Module
44+
")
4045
End Function
4146

4247
<Fact>
@@ -60,14 +65,19 @@ End Module
6065

6166
<Fact>
6267
Public Async Function TestSimpleCaseForNotEqualsTrue_NoDiagnostics() As Task
63-
Dim code = "
68+
Await VerifyVB.VerifyCodeFixAsync("
6469
Public Module Module1
6570
Public Function M1(x As Boolean) As Boolean
66-
Return x <> True
71+
Return x [|<>|] True
6772
End Function
6873
End Module
69-
"
70-
Await VerifyVB.VerifyAnalyzerAsync(code)
74+
", "
75+
Public Module Module1
76+
Public Function M1(x As Boolean) As Boolean
77+
Return Not x
78+
End Function
79+
End Module
80+
")
7181
End Function
7282

7383
<Fact>

0 commit comments

Comments
 (0)