diff --git a/src/Microsoft.CodeAnalysis.Analyzers/Core/MetaAnalyzers/CompareSymbolsCorrectlyAnalyzer.cs b/src/Microsoft.CodeAnalysis.Analyzers/Core/MetaAnalyzers/CompareSymbolsCorrectlyAnalyzer.cs index a1bc0630a3..6ecc1217d5 100644 --- a/src/Microsoft.CodeAnalysis.Analyzers/Core/MetaAnalyzers/CompareSymbolsCorrectlyAnalyzer.cs +++ b/src/Microsoft.CodeAnalysis.Analyzers/Core/MetaAnalyzers/CompareSymbolsCorrectlyAnalyzer.cs @@ -1,8 +1,10 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System.Linq; using System.Collections.Immutable; using Analyzer.Utilities; using Analyzer.Utilities.Extensions; +using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Operations; @@ -16,6 +18,8 @@ public class CompareSymbolsCorrectlyAnalyzer : DiagnosticAnalyzer private static readonly LocalizableString s_localizableDescription = new LocalizableResourceString(nameof(CodeAnalysisDiagnosticsResources.CompareSymbolsCorrectlyDescription), CodeAnalysisDiagnosticsResources.ResourceManager, typeof(CodeAnalysisDiagnosticsResources)); private static readonly string s_symbolTypeFullName = typeof(ISymbol).FullName; + private const string s_symbolEqualsName = nameof(ISymbol.Equals); + public const string SymbolEqualityComparerName = "Microsoft.CodeAnalysis.SymbolEqualityComparer"; public static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor( DiagnosticIds.CompareSymbolsCorrectlyRuleId, @@ -43,11 +47,29 @@ public override void Initialize(AnalysisContext context) return; } - context.RegisterOperationAction(context => HandleBinaryOperator(in context, symbolType), OperationKind.BinaryOperator); + // Check that the EqualityComparer exists and can be used, otherwise the Roslyn version + // being used it too low to need the change for method references + var operatorsToHandle = UseSymbolEqualityComparer(context.Compilation) ? + new[] { OperationKind.BinaryOperator, OperationKind.Invocation } : + new[] { OperationKind.BinaryOperator }; + + context.RegisterOperationAction(context => HandleOperation(in context, symbolType), operatorsToHandle); }); } - private void HandleBinaryOperator(in OperationAnalysisContext context, INamedTypeSymbol symbolType) + private void HandleOperation(in OperationAnalysisContext context, INamedTypeSymbol symbolType) + { + if (context.Operation is IBinaryOperation) + { + HandleBinaryOperator(in context, symbolType); + } + else if (context.Operation is IInvocationOperation) + { + HandleInvocationOperation(in context, symbolType); + } + } + + private static void HandleBinaryOperator(in OperationAnalysisContext context, INamedTypeSymbol symbolType) { var binary = (IBinaryOperation)context.Operation; if (binary.OperatorKind != BinaryOperatorKind.Equals && binary.OperatorKind != BinaryOperatorKind.NotEquals) @@ -90,19 +112,32 @@ private void HandleBinaryOperator(in OperationAnalysisContext context, INamedTyp context.ReportDiagnostic(binary.Syntax.GetLocation().CreateDiagnostic(Rule)); } - private static bool IsSymbolType(IOperation operation, INamedTypeSymbol symbolType) + private static void HandleInvocationOperation(in OperationAnalysisContext context, INamedTypeSymbol symbolType) { - if (operation.Type is object) + var invocationOperation = (IInvocationOperation)context.Operation; + var method = invocationOperation.TargetMethod; + if (method.Name != s_symbolEqualsName) { - if (operation.Type.Equals(symbolType)) - { - return true; - } + return; + } - if (operation.Type.AllInterfaces.Contains(symbolType)) - { - return true; - } + if (invocationOperation.Instance != null && !IsSymbolType(invocationOperation.Instance, symbolType)) + { + return; + } + + var parameters = invocationOperation.Arguments; + if (parameters.All(p => IsSymbolType(p.Value, symbolType))) + { + context.ReportDiagnostic(invocationOperation.Syntax.GetLocation().CreateDiagnostic(Rule)); + } + } + + private static bool IsSymbolType(IOperation operation, INamedTypeSymbol symbolType) + { + if (operation.Type is object && IsSymbolType(operation.Type, symbolType)) + { + return true; } if (operation is IConversionOperation conversion) @@ -113,6 +148,26 @@ private static bool IsSymbolType(IOperation operation, INamedTypeSymbol symbolTy return false; } + private static bool IsSymbolType(ITypeSymbol typeSymbol, INamedTypeSymbol symbolType) + { + if (typeSymbol == null) + { + return false; + } + + if (typeSymbol.Equals(symbolType)) + { + return true; + } + + if (typeSymbol.AllInterfaces.Contains(symbolType)) + { + return true; + } + + return false; + } + private static bool IsSymbolClassType(IOperation operation) { if (operation.Type is object) @@ -146,5 +201,8 @@ private static bool IsExplicitCastToObject(IOperation operation) return conversion.Type?.SpecialType == SpecialType.System_Object; } + + public static bool UseSymbolEqualityComparer(Compilation compilation) + => compilation.GetTypeByMetadataName(SymbolEqualityComparerName) is object; } } diff --git a/src/Microsoft.CodeAnalysis.Analyzers/Core/MetaAnalyzers/Fixers/CompareSymbolsCorrectlyFix.cs b/src/Microsoft.CodeAnalysis.Analyzers/Core/MetaAnalyzers/Fixers/CompareSymbolsCorrectlyFix.cs index 368670b9d2..986faaba52 100644 --- a/src/Microsoft.CodeAnalysis.Analyzers/Core/MetaAnalyzers/Fixers/CompareSymbolsCorrectlyFix.cs +++ b/src/Microsoft.CodeAnalysis.Analyzers/Core/MetaAnalyzers/Fixers/CompareSymbolsCorrectlyFix.cs @@ -2,6 +2,7 @@ using System.Collections.Immutable; using System.Composition; +using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeActions; @@ -41,7 +42,19 @@ private async Task ConvertToEqualsAsync(Document document, TextSpan so var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var expression = root.FindNode(sourceSpan, getInnermostNodeForTie: true); - if (!(semanticModel.GetOperation(expression, cancellationToken) is IBinaryOperation operation)) + var rawOperation = semanticModel.GetOperation(expression, cancellationToken); + + return rawOperation switch + { + IBinaryOperation binaryOperation => await ConvertToEqualsAsync(document, semanticModel, binaryOperation, cancellationToken).ConfigureAwait(false), + IInvocationOperation invocationOperation => await EnsureEqualsCorrectAsync(document, semanticModel, invocationOperation, cancellationToken).ConfigureAwait(false), + _ => document + }; + } + + private static async Task EnsureEqualsCorrectAsync(Document document, SemanticModel semanticModel, IInvocationOperation invocationOperation, CancellationToken cancellationToken) + { + if (!CompareSymbolsCorrectlyAnalyzer.UseSymbolEqualityComparer(semanticModel.Compilation)) { return document; } @@ -49,13 +62,48 @@ private async Task ConvertToEqualsAsync(Document document, TextSpan so var editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false); var generator = editor.Generator; - var replacement = generator.InvocationExpression( - generator.MemberAccessExpression( - generator.TypeExpression(semanticModel.Compilation.GetSpecialType(SpecialType.System_Object)), - nameof(object.Equals)), - operation.LeftOperand.Syntax.WithoutLeadingTrivia(), - operation.RightOperand.Syntax.WithoutTrailingTrivia()); - if (operation.OperatorKind == BinaryOperatorKind.NotEquals) + if (invocationOperation.Instance is null) + { + var replacement = generator.InvocationExpression( + GetEqualityComparerDefaultEquals(generator), + invocationOperation.Arguments.Select(argument => argument.Syntax).ToImmutableArray()); + + editor.ReplaceNode(invocationOperation.Syntax, replacement.WithTriviaFrom(invocationOperation.Syntax)); + } + else + { + var replacement = generator.AddParameters(invocationOperation.Syntax, new[] { GetEqualityComparerDefault(generator) }); + editor.ReplaceNode(invocationOperation.Syntax, replacement.WithTriviaFrom(invocationOperation.Syntax)); + } + + return editor.GetChangedDocument(); + } + + private static async Task ConvertToEqualsAsync(Document document, SemanticModel semanticModel, IBinaryOperation binaryOperation, CancellationToken cancellationToken) + { + + var expression = binaryOperation.Syntax; + var editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false); + var generator = editor.Generator; + + var replacement = CompareSymbolsCorrectlyAnalyzer.UseSymbolEqualityComparer(semanticModel.Compilation) switch + { + true => + generator.InvocationExpression( + GetEqualityComparerDefaultEquals(generator), + binaryOperation.LeftOperand.Syntax.WithoutLeadingTrivia(), + binaryOperation.RightOperand.Syntax.WithoutTrailingTrivia()), + + false => + generator.InvocationExpression( + generator.MemberAccessExpression( + generator.TypeExpression(semanticModel.Compilation.GetSpecialType(SpecialType.System_Object)), + nameof(object.Equals)), + binaryOperation.LeftOperand.Syntax.WithoutLeadingTrivia(), + binaryOperation.RightOperand.Syntax.WithoutTrailingTrivia()) + }; + + if (binaryOperation.OperatorKind == BinaryOperatorKind.NotEquals) { replacement = generator.LogicalNotExpression(replacement); } @@ -63,5 +111,13 @@ private async Task ConvertToEqualsAsync(Document document, TextSpan so editor.ReplaceNode(expression, replacement.WithTriviaFrom(expression)); return editor.GetChangedDocument(); } + + private static SyntaxNode GetEqualityComparerDefaultEquals(SyntaxGenerator generator) + => generator.MemberAccessExpression( + GetEqualityComparerDefault(generator), + nameof(object.Equals)); + + private static SyntaxNode GetEqualityComparerDefault(SyntaxGenerator generator) + => generator.MemberAccessExpression(generator.DottedName(CompareSymbolsCorrectlyAnalyzer.SymbolEqualityComparerName), "Default"); } } diff --git a/src/Microsoft.CodeAnalysis.Analyzers/Core/xlf/CodeAnalysisDiagnosticsResources.cs.xlf b/src/Microsoft.CodeAnalysis.Analyzers/Core/xlf/CodeAnalysisDiagnosticsResources.cs.xlf index fb5879538f..ef3250a8c2 100644 --- a/src/Microsoft.CodeAnalysis.Analyzers/Core/xlf/CodeAnalysisDiagnosticsResources.cs.xlf +++ b/src/Microsoft.CodeAnalysis.Analyzers/Core/xlf/CodeAnalysisDiagnosticsResources.cs.xlf @@ -9,7 +9,7 @@ Symbols should be compared for equality, not identity. - Symboly by se měly porovnat podle rovnosti, ne podle identity. + Symboly by se měly porovnat podle rovnosti, ne podle identity. diff --git a/src/Microsoft.CodeAnalysis.Analyzers/Core/xlf/CodeAnalysisDiagnosticsResources.de.xlf b/src/Microsoft.CodeAnalysis.Analyzers/Core/xlf/CodeAnalysisDiagnosticsResources.de.xlf index 847d030d6f..6d6dd42622 100644 --- a/src/Microsoft.CodeAnalysis.Analyzers/Core/xlf/CodeAnalysisDiagnosticsResources.de.xlf +++ b/src/Microsoft.CodeAnalysis.Analyzers/Core/xlf/CodeAnalysisDiagnosticsResources.de.xlf @@ -9,7 +9,7 @@ Symbols should be compared for equality, not identity. - Symbole sollten hinsichtlich Gleichheit verglichen werden, nicht hinsichtlich Identität. + Symbole sollten hinsichtlich Gleichheit verglichen werden, nicht hinsichtlich Identität. diff --git a/src/Microsoft.CodeAnalysis.Analyzers/Core/xlf/CodeAnalysisDiagnosticsResources.es.xlf b/src/Microsoft.CodeAnalysis.Analyzers/Core/xlf/CodeAnalysisDiagnosticsResources.es.xlf index ae11a9f18e..e3de66dd20 100644 --- a/src/Microsoft.CodeAnalysis.Analyzers/Core/xlf/CodeAnalysisDiagnosticsResources.es.xlf +++ b/src/Microsoft.CodeAnalysis.Analyzers/Core/xlf/CodeAnalysisDiagnosticsResources.es.xlf @@ -9,7 +9,7 @@ Symbols should be compared for equality, not identity. - Solo se debe realizar una comparación de igualdad de los símbolos, no de identidad. + Solo se debe realizar una comparación de igualdad de los símbolos, no de identidad. diff --git a/src/Microsoft.CodeAnalysis.Analyzers/Core/xlf/CodeAnalysisDiagnosticsResources.fr.xlf b/src/Microsoft.CodeAnalysis.Analyzers/Core/xlf/CodeAnalysisDiagnosticsResources.fr.xlf index 0340cd7ae4..3f5697d10a 100644 --- a/src/Microsoft.CodeAnalysis.Analyzers/Core/xlf/CodeAnalysisDiagnosticsResources.fr.xlf +++ b/src/Microsoft.CodeAnalysis.Analyzers/Core/xlf/CodeAnalysisDiagnosticsResources.fr.xlf @@ -9,7 +9,7 @@ Symbols should be compared for equality, not identity. - La comparaison des symboles doit porter sur l'égalité, pas sur l'identité. + La comparaison des symboles doit porter sur l'égalité, pas sur l'identité. diff --git a/src/Microsoft.CodeAnalysis.Analyzers/Core/xlf/CodeAnalysisDiagnosticsResources.it.xlf b/src/Microsoft.CodeAnalysis.Analyzers/Core/xlf/CodeAnalysisDiagnosticsResources.it.xlf index b925c2d15f..c0b4a56043 100644 --- a/src/Microsoft.CodeAnalysis.Analyzers/Core/xlf/CodeAnalysisDiagnosticsResources.it.xlf +++ b/src/Microsoft.CodeAnalysis.Analyzers/Core/xlf/CodeAnalysisDiagnosticsResources.it.xlf @@ -9,7 +9,7 @@ Symbols should be compared for equality, not identity. - È necessario confrontare i simboli per verificarne l'uguaglianza, non l'identità. + È necessario confrontare i simboli per verificarne l'uguaglianza, non l'identità. diff --git a/src/Microsoft.CodeAnalysis.Analyzers/Core/xlf/CodeAnalysisDiagnosticsResources.ja.xlf b/src/Microsoft.CodeAnalysis.Analyzers/Core/xlf/CodeAnalysisDiagnosticsResources.ja.xlf index 6e4a4c023f..995fc1c9c9 100644 --- a/src/Microsoft.CodeAnalysis.Analyzers/Core/xlf/CodeAnalysisDiagnosticsResources.ja.xlf +++ b/src/Microsoft.CodeAnalysis.Analyzers/Core/xlf/CodeAnalysisDiagnosticsResources.ja.xlf @@ -9,7 +9,7 @@ Symbols should be compared for equality, not identity. - シンボルは、ID ではなく等値で比較する必要があります。 + シンボルは、ID ではなく等値で比較する必要があります。 diff --git a/src/Microsoft.CodeAnalysis.Analyzers/Core/xlf/CodeAnalysisDiagnosticsResources.ko.xlf b/src/Microsoft.CodeAnalysis.Analyzers/Core/xlf/CodeAnalysisDiagnosticsResources.ko.xlf index 6e4762e995..4604041ed6 100644 --- a/src/Microsoft.CodeAnalysis.Analyzers/Core/xlf/CodeAnalysisDiagnosticsResources.ko.xlf +++ b/src/Microsoft.CodeAnalysis.Analyzers/Core/xlf/CodeAnalysisDiagnosticsResources.ko.xlf @@ -9,7 +9,7 @@ Symbols should be compared for equality, not identity. - 기호는 ID가 아니라 같은지 비교해야 합니다. + 기호는 ID가 아니라 같은지 비교해야 합니다. diff --git a/src/Microsoft.CodeAnalysis.Analyzers/Core/xlf/CodeAnalysisDiagnosticsResources.pl.xlf b/src/Microsoft.CodeAnalysis.Analyzers/Core/xlf/CodeAnalysisDiagnosticsResources.pl.xlf index e04acaf2d2..41f3d2894b 100644 --- a/src/Microsoft.CodeAnalysis.Analyzers/Core/xlf/CodeAnalysisDiagnosticsResources.pl.xlf +++ b/src/Microsoft.CodeAnalysis.Analyzers/Core/xlf/CodeAnalysisDiagnosticsResources.pl.xlf @@ -9,7 +9,7 @@ Symbols should be compared for equality, not identity. - Symbole powinny być porównywane pod kątem równości, a nie tożsamości. + Symbole powinny być porównywane pod kątem równości, a nie tożsamości. diff --git a/src/Microsoft.CodeAnalysis.Analyzers/Core/xlf/CodeAnalysisDiagnosticsResources.pt-BR.xlf b/src/Microsoft.CodeAnalysis.Analyzers/Core/xlf/CodeAnalysisDiagnosticsResources.pt-BR.xlf index cf0563d019..ebb54c6299 100644 --- a/src/Microsoft.CodeAnalysis.Analyzers/Core/xlf/CodeAnalysisDiagnosticsResources.pt-BR.xlf +++ b/src/Microsoft.CodeAnalysis.Analyzers/Core/xlf/CodeAnalysisDiagnosticsResources.pt-BR.xlf @@ -9,7 +9,7 @@ Symbols should be compared for equality, not identity. - Os símbolos devem ser comparados quanto à igualdade, não à identidade. + Os símbolos devem ser comparados quanto à igualdade, não à identidade. diff --git a/src/Microsoft.CodeAnalysis.Analyzers/Core/xlf/CodeAnalysisDiagnosticsResources.ru.xlf b/src/Microsoft.CodeAnalysis.Analyzers/Core/xlf/CodeAnalysisDiagnosticsResources.ru.xlf index 6f5ea23740..e753e2d126 100644 --- a/src/Microsoft.CodeAnalysis.Analyzers/Core/xlf/CodeAnalysisDiagnosticsResources.ru.xlf +++ b/src/Microsoft.CodeAnalysis.Analyzers/Core/xlf/CodeAnalysisDiagnosticsResources.ru.xlf @@ -9,7 +9,7 @@ Symbols should be compared for equality, not identity. - Символы необходимо сравнивать на равенство, а не на тождественность. + Символы необходимо сравнивать на равенство, а не на тождественность. diff --git a/src/Microsoft.CodeAnalysis.Analyzers/Core/xlf/CodeAnalysisDiagnosticsResources.tr.xlf b/src/Microsoft.CodeAnalysis.Analyzers/Core/xlf/CodeAnalysisDiagnosticsResources.tr.xlf index 52bf4512cf..fe2fda20de 100644 --- a/src/Microsoft.CodeAnalysis.Analyzers/Core/xlf/CodeAnalysisDiagnosticsResources.tr.xlf +++ b/src/Microsoft.CodeAnalysis.Analyzers/Core/xlf/CodeAnalysisDiagnosticsResources.tr.xlf @@ -9,7 +9,7 @@ Symbols should be compared for equality, not identity. - Semboller kimlik değil eşitlik için karşılaştırılmalıdır. + Semboller kimlik değil eşitlik için karşılaştırılmalıdır. diff --git a/src/Microsoft.CodeAnalysis.Analyzers/Core/xlf/CodeAnalysisDiagnosticsResources.zh-Hans.xlf b/src/Microsoft.CodeAnalysis.Analyzers/Core/xlf/CodeAnalysisDiagnosticsResources.zh-Hans.xlf index 467e677bfb..568f89eff0 100644 --- a/src/Microsoft.CodeAnalysis.Analyzers/Core/xlf/CodeAnalysisDiagnosticsResources.zh-Hans.xlf +++ b/src/Microsoft.CodeAnalysis.Analyzers/Core/xlf/CodeAnalysisDiagnosticsResources.zh-Hans.xlf @@ -9,7 +9,7 @@ Symbols should be compared for equality, not identity. - 符号应比较相等性,而不是标识。 + 符号应比较相等性,而不是标识。 diff --git a/src/Microsoft.CodeAnalysis.Analyzers/Core/xlf/CodeAnalysisDiagnosticsResources.zh-Hant.xlf b/src/Microsoft.CodeAnalysis.Analyzers/Core/xlf/CodeAnalysisDiagnosticsResources.zh-Hant.xlf index 87741bf033..3addadd2f1 100644 --- a/src/Microsoft.CodeAnalysis.Analyzers/Core/xlf/CodeAnalysisDiagnosticsResources.zh-Hant.xlf +++ b/src/Microsoft.CodeAnalysis.Analyzers/Core/xlf/CodeAnalysisDiagnosticsResources.zh-Hant.xlf @@ -9,7 +9,7 @@ Symbols should be compared for equality, not identity. - 應比較符號是否相等,而非比較身分識別。 + 應比較符號是否相等,而非比較身分識別。 diff --git a/src/Microsoft.CodeAnalysis.Analyzers/UnitTests/MetaAnalyzers/CompareSymbolsCorrectlyTests.cs b/src/Microsoft.CodeAnalysis.Analyzers/UnitTests/MetaAnalyzers/CompareSymbolsCorrectlyTests.cs index 95d110012a..cd236a79a4 100644 --- a/src/Microsoft.CodeAnalysis.Analyzers/UnitTests/MetaAnalyzers/CompareSymbolsCorrectlyTests.cs +++ b/src/Microsoft.CodeAnalysis.Analyzers/UnitTests/MetaAnalyzers/CompareSymbolsCorrectlyTests.cs @@ -145,6 +145,57 @@ End Operator End Class "; + private const string SymbolEqualityComparerStubVisualBasic = +@" +Imports System.Collections.Generic + +Namespace Microsoft + Namespace CodeAnalysis + Public Class SymbolEqualityComparer + Implements IEqualityComparer(Of ISymbol) + + Public Shared ReadOnly [Default] As SymbolEqualityComparer = New SymbolEqualityComparer() + + Private Sub New() + End Sub + + Public Function Equals(x As ISymbol, y As ISymbol) As Boolean Implements IEqualityComparer(Of ISymbol).Equals + Throw New System.NotImplementedException() + End Function + + Public Function GetHashCode(obj As ISymbol) As Integer Implements IEqualityComparer(Of ISymbol).GetHashCode + Throw New System.NotImplementedException() + End Function + End Class + End Namespace +End Namespace"; + + private const string SymbolEqualityComparerStubCSharp = +@" +using System.Collections.Generic; + +namespace Microsoft.CodeAnalysis +{ + public class SymbolEqualityComparer : IEqualityComparer + { + public static readonly SymbolEqualityComparer Default = new SymbolEqualityComparer(); + + private SymbolEqualityComparer() + { + } + + public bool Equals(ISymbol x, ISymbol y) + { + throw new System.NotImplementedException(); + } + + public int GetHashCode(ISymbol obj) + { + throw new System.NotImplementedException(); + } + } +}"; + [Theory] [InlineData(nameof(ISymbol))] [InlineData(nameof(INamedTypeSymbol))] @@ -162,11 +213,38 @@ class TestClass {{ using Microsoft.CodeAnalysis; class TestClass {{ bool Method({symbolType} x, {symbolType} y) {{ - return Equals(x, y); + return SymbolEqualityComparer.Default.Equals(x, y); }} }} "; + await new VerifyCS.Test + { + TestState = { Sources = { source, SymbolEqualityComparerStubCSharp } }, + FixedState = { Sources = { fixedSource, SymbolEqualityComparerStubCSharp } }, + }.RunAsync(); + } + [Theory] + [InlineData(nameof(ISymbol))] + [InlineData(nameof(INamedTypeSymbol))] + public async Task CompareTwoSymbolsEquals_NoComparer_CSharp(string symbolType) + { + var source = $@" +using Microsoft.CodeAnalysis; +class TestClass {{ + bool Method({symbolType} x, {symbolType} y) {{ + return [|x == y|]; + }} +}} +"; + var fixedSource = $@" +using Microsoft.CodeAnalysis; +class TestClass {{ + bool Method({symbolType} x, {symbolType} y) {{ + return Equals(x, y); + }} +}} +"; await VerifyCS.VerifyCodeFixAsync(source, fixedSource); } @@ -228,6 +306,35 @@ class TestClass {{ "; var fixedSource = $@" using Microsoft.CodeAnalysis; +class TestClass {{ + bool Method(Symbol x, {symbolType} y) {{ + return SymbolEqualityComparer.Default.Equals(x, y); + }} +}} +"; + + await new VerifyCS.Test + { + TestState = { Sources = { source, MinimalSymbolImplementationCSharp, SymbolEqualityComparerStubCSharp } }, + FixedState = { Sources = { fixedSource, MinimalSymbolImplementationCSharp, SymbolEqualityComparerStubCSharp } }, + }.RunAsync(); + } + + [Theory] + [InlineData(nameof(ISymbol))] + [InlineData(nameof(INamedTypeSymbol))] + public async Task CompareSymbolImplementationWithInterface_NoComparer_CSharp(string symbolType) + { + var source = $@" +using Microsoft.CodeAnalysis; +class TestClass {{ + bool Method(Symbol x, {symbolType} y) {{ + return [|x == y|]; + }} +}} +"; + var fixedSource = $@" +using Microsoft.CodeAnalysis; class TestClass {{ bool Method(Symbol x, {symbolType} y) {{ return Equals(x, y); @@ -297,6 +404,35 @@ End Class "; var fixedSource = $@" Imports Microsoft.CodeAnalysis +Class TestClass + Function Method(x As {symbolType}, y As {symbolType}) As Boolean + Return SymbolEqualityComparer.Default.Equals(x, y) + End Function +End Class +"; + + await new VerifyVB.Test + { + TestState = { Sources = { source, SymbolEqualityComparerStubVisualBasic } }, + FixedState = { Sources = { fixedSource, SymbolEqualityComparerStubVisualBasic } }, + }.RunAsync(); + } + + [Theory] + [InlineData(nameof(ISymbol))] + [InlineData(nameof(INamedTypeSymbol))] + public async Task CompareTwoSymbolsEquals_NoComparer_VisualBasic(string symbolType) + { + var source = $@" +Imports Microsoft.CodeAnalysis +Class TestClass + Function Method(x As {symbolType}, y As {symbolType}) As Boolean + Return [|x Is y|] + End Function +End Class +"; + var fixedSource = $@" +Imports Microsoft.CodeAnalysis Class TestClass Function Method(x As {symbolType}, y As {symbolType}) As Boolean Return Equals(x, y) @@ -372,5 +508,35 @@ End Class await VerifyVB.VerifyAnalyzerAsync(source); } + + + [Theory] + [InlineData(nameof(ISymbol))] + [InlineData(nameof(INamedTypeSymbol))] + public async Task CompareSymbolImplementationWithInterface_EqualsComparison_CSharp(string symbolType) + { + var source = $@" +using Microsoft.CodeAnalysis; +class TestClass {{ + bool Method(ISymbol x, {symbolType} y) {{ + return [|Equals(x, y)|]; + }} +}} +"; + var fixedSource = $@" +using Microsoft.CodeAnalysis; +class TestClass {{ + bool Method(ISymbol x, {symbolType} y) {{ + return SymbolEqualityComparer.Default.Equals(x, y); + }} +}} +"; + + await new VerifyCS.Test + { + TestState = { Sources = { source, SymbolEqualityComparerStubCSharp } }, + FixedState = { Sources = { fixedSource, SymbolEqualityComparerStubCSharp } }, + }.RunAsync(); + } } }