From f6e1bba6b46631f95b2e10ae8a01ed98b5a5dfb3 Mon Sep 17 00:00:00 2001 From: Collin Alpert Date: Sun, 2 Jul 2023 11:27:00 +0200 Subject: [PATCH 1/3] Emit CA2251 when comparing string.Compare with 0 using Equals --- .../UseStringEqualsOverStringCompare.Fixer.cs | 54 +++++++++----- .../UseStringEqualsOverStringCompare.cs | 70 +++++++++++++------ .../UseStringEqualsOverStringCompareTests.cs | 36 ++++++++++ 3 files changed, 122 insertions(+), 38 deletions(-) diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseStringEqualsOverStringCompare.Fixer.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseStringEqualsOverStringCompare.Fixer.cs index 5d99759a9e..1a30c2c7d4 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseStringEqualsOverStringCompare.Fixer.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseStringEqualsOverStringCompare.Fixer.cs @@ -35,7 +35,8 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) var root = await document.GetSyntaxRootAsync(token).ConfigureAwait(false); var node = root.FindNode(context.Span, getInnermostNodeForTie: true); - if (semanticModel.GetOperation(node, token) is not IBinaryOperation violation) + var violation = semanticModel.GetOperation(node, token); + if (violation is not (IBinaryOperation or IInvocationOperation)) return; // Get the replacer that applies to the reported violation. @@ -50,9 +51,9 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) // Local functions - async Task CreateChangedDocument(CancellationToken token) + async Task CreateChangedDocument(CancellationToken cancellationToken) { - var editor = await DocumentEditor.CreateAsync(document, token).ConfigureAwait(false); + var editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false); var replacementNode = replacer.CreateReplacementExpression(violation, editor.Generator); editor.ReplaceNode(violation.Syntax, replacementNode); @@ -87,17 +88,17 @@ protected OperationReplacer(RequiredSymbols symbols) /// /// The at the location reported by the analyzer. /// True if the current applies to the specified violation. - public abstract bool IsMatch(IBinaryOperation violation); + public abstract bool IsMatch(IOperation violation); /// /// Creates a replacement node for a violation that the current applies to. /// Asserts if the current does not apply to the specified violation. /// /// The obtained at the location reported by the analyzer. - /// must return for this operation. + /// must return for this operation. /// /// - public abstract SyntaxNode CreateReplacementExpression(IBinaryOperation violation, SyntaxGenerator generator); + public abstract SyntaxNode CreateReplacementExpression(IOperation violation, SyntaxGenerator generator); protected SyntaxNode CreateEqualsMemberAccess(SyntaxGenerator generator) { @@ -105,20 +106,37 @@ protected SyntaxNode CreateEqualsMemberAccess(SyntaxGenerator generator) return generator.MemberAccessExpression(stringTypeExpression, nameof(string.Equals)); } - protected static IInvocationOperation GetInvocation(IBinaryOperation violation) + protected IInvocationOperation GetInvocation(IOperation violation) { - var result = UseStringEqualsOverStringCompare.GetInvocationFromEqualityCheckWithLiteralZero(violation); + var result = violation switch + { + IBinaryOperation b => UseStringEqualsOverStringCompare.GetInvocationFromEqualityCheckWithLiteralZero(b), + IInvocationOperation i => UseStringEqualsOverStringCompare.GetInvocationFromEqualsCheckWithLiteralZero(i, Symbols.IntEquals), + _ => throw new NotSupportedException() + }; RoslynDebug.Assert(result is not null); return result; } - protected static SyntaxNode InvertIfNotEquals(SyntaxNode stringEqualsInvocationExpression, IBinaryOperation equalsOrNotEqualsOperation, SyntaxGenerator generator) + protected static SyntaxNode InvertIfNotEquals(SyntaxNode stringEqualsInvocationExpression, IOperation equalsOrNotEqualsOperation, SyntaxGenerator generator) { - return equalsOrNotEqualsOperation.OperatorKind is BinaryOperatorKind.NotEquals ? - generator.LogicalNotExpression(stringEqualsInvocationExpression) : - stringEqualsInvocationExpression; + if (equalsOrNotEqualsOperation is IBinaryOperation b) + { + return b.OperatorKind is BinaryOperatorKind.NotEquals + ? generator.LogicalNotExpression(stringEqualsInvocationExpression) + : stringEqualsInvocationExpression; + } + + if (equalsOrNotEqualsOperation is IInvocationOperation i) + { + return i.Instance.Parent is IUnaryOperation { OperatorKind: UnaryOperatorKind.Not } + ? generator.LogicalNotExpression(stringEqualsInvocationExpression) + : stringEqualsInvocationExpression; + } + + throw new NotSupportedException(); } } @@ -131,9 +149,9 @@ public StringStringCaseReplacer(RequiredSymbols symbols) : base(symbols) { } - public override bool IsMatch(IBinaryOperation violation) => UseStringEqualsOverStringCompare.IsStringStringCase(violation, Symbols); + public override bool IsMatch(IOperation violation) => UseStringEqualsOverStringCompare.IsStringStringCase(violation, Symbols); - public override SyntaxNode CreateReplacementExpression(IBinaryOperation violation, SyntaxGenerator generator) + public override SyntaxNode CreateReplacementExpression(IOperation violation, SyntaxGenerator generator) { RoslynDebug.Assert(IsMatch(violation)); @@ -155,9 +173,9 @@ public StringStringBoolReplacer(RequiredSymbols symbols) : base(symbols) { } - public override bool IsMatch(IBinaryOperation violation) => UseStringEqualsOverStringCompare.IsStringStringBoolCase(violation, Symbols); + public override bool IsMatch(IOperation violation) => UseStringEqualsOverStringCompare.IsStringStringBoolCase(violation, Symbols); - public override SyntaxNode CreateReplacementExpression(IBinaryOperation violation, SyntaxGenerator generator) + public override SyntaxNode CreateReplacementExpression(IOperation violation, SyntaxGenerator generator) { RoslynDebug.Assert(IsMatch(violation)); @@ -197,9 +215,9 @@ public StringStringStringComparisonReplacer(RequiredSymbols symbols) : base(symbols) { } - public override bool IsMatch(IBinaryOperation violation) => UseStringEqualsOverStringCompare.IsStringStringStringComparisonCase(violation, Symbols); + public override bool IsMatch(IOperation violation) => UseStringEqualsOverStringCompare.IsStringStringStringComparisonCase(violation, Symbols); - public override SyntaxNode CreateReplacementExpression(IBinaryOperation violation, SyntaxGenerator generator) + public override SyntaxNode CreateReplacementExpression(IOperation violation, SyntaxGenerator generator) { RoslynDebug.Assert(IsMatch(violation)); diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseStringEqualsOverStringCompare.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseStringEqualsOverStringCompare.cs index 3dd5156b47..f4a76099b6 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseStringEqualsOverStringCompare.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseStringEqualsOverStringCompare.cs @@ -52,19 +52,18 @@ private static void OnCompilationStart(CompilationStartAnalysisContext context) { if (!RequiredSymbols.TryGetSymbols(context.Compilation, out var symbols)) return; - context.RegisterOperationAction(AnalyzeOperation, OperationKind.Binary); + context.RegisterOperationAction(AnalyzeOperation, OperationKind.Binary, OperationKind.Invocation); return; // Local functions void AnalyzeOperation(OperationAnalysisContext context) { - var operation = (IBinaryOperation)context.Operation; foreach (var selector in CaseSelectors) { - if (selector(operation, symbols)) + if (selector(context.Operation, symbols)) { - context.ReportDiagnostic(operation.CreateDiagnostic(Rule)); + context.ReportDiagnostic(context.Operation.CreateDiagnostic(Rule)); return; } } @@ -81,7 +80,8 @@ private RequiredSymbols( IMethodSymbol? compareStringStringBool, IMethodSymbol? compareStringStringStringComparison, IMethodSymbol? equalsStringString, - IMethodSymbol? equalsStringStringStringComparison) + IMethodSymbol? equalsStringStringStringComparison, + IMethodSymbol intEquals) { StringType = stringType; BoolType = boolType; @@ -91,6 +91,7 @@ private RequiredSymbols( CompareStringStringStringComparison = compareStringStringStringComparison; EqualsStringString = equalsStringString; EqualsStringStringStringComparison = equalsStringStringStringComparison; + IntEquals = intEquals; } public static bool TryGetSymbols(Compilation compilation, [NotNullWhen(true)] out RequiredSymbols? symbols) @@ -103,7 +104,8 @@ public static bool TryGetSymbols(Compilation compilation, [NotNullWhen(true)] ou if (stringType is null || boolType is null) return false; - if (!compilation.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemStringComparison, out var stringComparisonType)) + var typeProvider = WellKnownTypeProvider.GetOrCreate(compilation); + if (!typeProvider.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemStringComparison, out var stringComparisonType)) return false; var compareMethods = stringType.GetMembers(nameof(string.Compare)) @@ -118,6 +120,15 @@ public static bool TryGetSymbols(Compilation compilation, [NotNullWhen(true)] ou .Where(x => x.IsStatic); var equalsStringString = equalsMethods.GetFirstOrDefaultMemberWithParameterTypes(stringType, stringType); var equalsStringStringStringComparison = equalsMethods.GetFirstOrDefaultMemberWithParameterTypes(stringType, stringType, stringComparisonType); + var intType = typeProvider.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemInt32); + var intEquals = intType + ?.GetMembers(nameof(int.Equals)) + .OfType() + .FirstOrDefault(m => m.GetParameters() is [var param] && param.Type.Equals(intType, SymbolEqualityComparer.Default)); + if(intEquals is null) + { + return false; + } // Bail if we do not have at least one complete pair of Compare-Equals methods in the compilation. if ((compareStringString is null || equalsStringString is null) && @@ -130,7 +141,7 @@ public static bool TryGetSymbols(Compilation compilation, [NotNullWhen(true)] ou symbols = new RequiredSymbols( stringType, boolType, stringComparisonType, compareStringString, compareStringStringBool, compareStringStringStringComparison, - equalsStringString, equalsStringStringStringComparison); + equalsStringString, equalsStringStringStringComparison, intEquals); return true; } @@ -142,6 +153,7 @@ public static bool TryGetSymbols(Compilation compilation, [NotNullWhen(true)] ou public IMethodSymbol? CompareStringStringStringComparison { get; } public IMethodSymbol? EqualsStringString { get; } public IMethodSymbol? EqualsStringStringStringComparison { get; } + public IMethodSymbol IntEquals { get; } } /// @@ -156,9 +168,9 @@ public static bool TryGetSymbols(Compilation compilation, [NotNullWhen(true)] ou /// /// /// - internal static IInvocationOperation? GetInvocationFromEqualityCheckWithLiteralZero(IBinaryOperation binaryOperation) + internal static IInvocationOperation? GetInvocationFromEqualityCheckWithLiteralZero(IBinaryOperation? binaryOperation) { - if (binaryOperation.OperatorKind is not (BinaryOperatorKind.Equals or BinaryOperatorKind.NotEquals)) + if (binaryOperation?.OperatorKind is not (BinaryOperatorKind.Equals or BinaryOperatorKind.NotEquals)) return default; if (IsLiteralZero(binaryOperation.LeftOperand)) @@ -175,6 +187,21 @@ static bool IsLiteralZero(IOperation? operation) return operation is ILiteralOperation literal && literal.ConstantValue.Value is 0; } } + + internal static IInvocationOperation? GetInvocationFromEqualsCheckWithLiteralZero(IInvocationOperation? invocation, IMethodSymbol int32Equals) + { + if (!int32Equals.Equals(invocation?.TargetMethod.OriginalDefinition, SymbolEqualityComparer.Default)) + { + return default; + } + + if (invocation!.Arguments.FirstOrDefault()?.Value is ILiteralOperation { ConstantValue.Value: 0 }) + { + return invocation.Instance as IInvocationOperation; + } + + return default; + } /// /// Returns true if the specified : @@ -184,20 +211,21 @@ static bool IsLiteralZero(IOperation? operation) /// The other operand is any invocation of /// /// - /// + /// /// /// - internal static bool IsStringStringCase(IBinaryOperation binaryOperation, RequiredSymbols symbols) + internal static bool IsStringStringCase(IOperation operation, RequiredSymbols symbols) { // Don't report a diagnostic if either the string.Compare overload or the - // corrasponding string.Equals overload is missing. + // corresponding string.Equals overload is missing. if (symbols.CompareStringString is null || symbols.EqualsStringString is null) { return false; } - var invocation = GetInvocationFromEqualityCheckWithLiteralZero(binaryOperation); + var invocation = GetInvocationFromEqualityCheckWithLiteralZero(operation as IBinaryOperation) + ?? GetInvocationFromEqualsCheckWithLiteralZero(operation as IInvocationOperation, symbols.IntEquals); return invocation is not null && invocation.TargetMethod.Equals(symbols.CompareStringString, SymbolEqualityComparer.Default); @@ -215,7 +243,7 @@ internal static bool IsStringStringCase(IBinaryOperation binaryOperation, Requir /// /// /// - internal static bool IsStringStringBoolCase(IBinaryOperation binaryOperation, RequiredSymbols symbols) + internal static bool IsStringStringBoolCase(IOperation operation, RequiredSymbols symbols) { // Don't report a diagnostic if either the string.Compare overload or the // corrasponding string.Equals overload is missing. @@ -225,7 +253,8 @@ internal static bool IsStringStringBoolCase(IBinaryOperation binaryOperation, Re return false; } - var invocation = GetInvocationFromEqualityCheckWithLiteralZero(binaryOperation); + var invocation = GetInvocationFromEqualityCheckWithLiteralZero(operation as IBinaryOperation) + ?? GetInvocationFromEqualsCheckWithLiteralZero(operation as IInvocationOperation, symbols.IntEquals); // Only report a diagnostic if the 'ignoreCase' argument is a boolean literal. return invocation is not null && @@ -242,10 +271,10 @@ internal static bool IsStringStringBoolCase(IBinaryOperation binaryOperation, Re /// The other operand is any invocation of /// /// - /// + /// /// /// - internal static bool IsStringStringStringComparisonCase(IBinaryOperation binaryOperation, RequiredSymbols symbols) + internal static bool IsStringStringStringComparisonCase(IOperation operation, RequiredSymbols symbols) { // Don't report a diagnostic if either the string.Compare overload or the // corrasponding string.Equals overload is missing. @@ -255,7 +284,8 @@ internal static bool IsStringStringStringComparisonCase(IBinaryOperation binaryO return false; } - var invocation = GetInvocationFromEqualityCheckWithLiteralZero(binaryOperation); + var invocation = GetInvocationFromEqualityCheckWithLiteralZero(operation as IBinaryOperation) + ?? GetInvocationFromEqualsCheckWithLiteralZero(operation as IInvocationOperation, symbols.IntEquals); return invocation is not null && invocation.TargetMethod.Equals(symbols.CompareStringStringStringComparison, SymbolEqualityComparer.Default); @@ -263,9 +293,9 @@ internal static bool IsStringStringStringComparisonCase(IBinaryOperation binaryO // No IOperation instances are being stored here. #pragma warning disable RS1008 // Avoid storing per-compilation data into the fields of a diagnostic analyzer - private static readonly ImmutableArray> CaseSelectors = + private static readonly ImmutableArray> CaseSelectors = #pragma warning restore RS1008 // Avoid storing per-compilation data into the fields of a diagnostic analyzer - ImmutableArray.Create>( + ImmutableArray.Create>( IsStringStringCase, IsStringStringBoolCase, IsStringStringStringComparisonCase); diff --git a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/UseStringEqualsOverStringCompareTests.cs b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/UseStringEqualsOverStringCompareTests.cs index 7168403d1b..33097d7962 100644 --- a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/UseStringEqualsOverStringCompareTests.cs +++ b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/UseStringEqualsOverStringCompareTests.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.CodeAnalysis; +using Test.Utilities; using Xunit; using VerifyCS = Test.Utilities.CSharpCodeFixVerifier< @@ -43,6 +44,9 @@ private static readonly (string CompareCall, string EqualsCall)[] VB_ComparisonE public static IEnumerable CS_ComparisonLeftOfLiteralTestData { get; } = CS_ComparisonEqualityMethodCallPairs .Select(pair => new object[] { $"{pair.CompareCall} == 0", pair.EqualsCall }); + public static IEnumerable CS_EqualsComparisonLeftOfLiteralTestData { get; } = CS_ComparisonEqualityMethodCallPairs + .Select(pair => new object[] { $"[|{pair.CompareCall}.Equals(0)|]", pair.EqualsCall }); + public static IEnumerable VB_ComparisonLeftOfLiteralTestData { get; } = VB_ComparisonEqualityMethodPairs .Select(pair => new object[] { $"{pair.CompareCall} = 0", pair.EqualsCall }); @@ -55,6 +59,9 @@ private static readonly (string CompareCall, string EqualsCall)[] VB_ComparisonE public static IEnumerable CS_InvertedComparisonLeftOfLiteralTestData { get; } = CS_ComparisonEqualityMethodCallPairs .Select(pair => new object[] { $"{pair.CompareCall} != 0", $"!{pair.EqualsCall}" }); + public static IEnumerable CS_InvertedEqualsComparisonLeftOfLiteralTestData { get; } = CS_ComparisonEqualityMethodCallPairs + .Select(pair => new object[] { $"![|{pair.CompareCall}.Equals(0)|]", $"!{pair.EqualsCall}" }); + public static IEnumerable VB_InvertedComparisonLeftOfLiteralTestData { get; } = VB_ComparisonEqualityMethodPairs .Select(pair => new object[] { $"{pair.CompareCall} <> 0", $"Not {pair.EqualsCall}" }); @@ -121,6 +128,35 @@ public bool Huh(string x, string y) return VerifyCS.VerifyCodeFixAsync(testCode, VerifyCS.Diagnostic(Rule).WithLocation(0), fixedCode); } + [Theory, WorkItem(6609, "https://github.com/dotnet/roslyn-analyzers/issues/6609")] + [MemberData(nameof(CS_InvertedEqualsComparisonLeftOfLiteralTestData))] + [MemberData(nameof(CS_EqualsComparisonLeftOfLiteralTestData))] + public Task ComparisonWithEquals(string testExpression, string fixedExpression) + { + string testCode = $@" +using System; + +public class Testopolis +{{ + public bool Huh(string x, string y) + {{ + return {testExpression}; + }} +}}"; + string fixedCode = $@" +using System; + +public class Testopolis +{{ + public bool Huh(string x, string y) + {{ + return {fixedExpression}; + }} +}}"; + + return VerifyCS.VerifyCodeFixAsync(testCode, fixedCode); + } + [Theory] [MemberData(nameof(VB_ComparisonLeftOfLiteralTestData))] [MemberData(nameof(VB_ComparisonRightOfLiteralTestData))] From 8d735b8dee521284ad055ec8ab3f125bcf9a28fe Mon Sep 17 00:00:00 2001 From: Collin Alpert Date: Sun, 2 Jul 2023 11:36:12 +0200 Subject: [PATCH 2/3] Fix formatting. --- .../Runtime/UseStringEqualsOverStringCompare.Fixer.cs | 4 ++-- .../Runtime/UseStringEqualsOverStringCompare.cs | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseStringEqualsOverStringCompare.Fixer.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseStringEqualsOverStringCompare.Fixer.cs index 1a30c2c7d4..0c90159cec 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseStringEqualsOverStringCompare.Fixer.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseStringEqualsOverStringCompare.Fixer.cs @@ -128,10 +128,10 @@ protected static SyntaxNode InvertIfNotEquals(SyntaxNode stringEqualsInvocationE ? generator.LogicalNotExpression(stringEqualsInvocationExpression) : stringEqualsInvocationExpression; } - + if (equalsOrNotEqualsOperation is IInvocationOperation i) { - return i.Instance.Parent is IUnaryOperation { OperatorKind: UnaryOperatorKind.Not } + return i.Instance.Parent is IUnaryOperation { OperatorKind: UnaryOperatorKind.Not } ? generator.LogicalNotExpression(stringEqualsInvocationExpression) : stringEqualsInvocationExpression; } diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseStringEqualsOverStringCompare.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseStringEqualsOverStringCompare.cs index f4a76099b6..dcebeea530 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseStringEqualsOverStringCompare.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseStringEqualsOverStringCompare.cs @@ -125,7 +125,7 @@ public static bool TryGetSymbols(Compilation compilation, [NotNullWhen(true)] ou ?.GetMembers(nameof(int.Equals)) .OfType() .FirstOrDefault(m => m.GetParameters() is [var param] && param.Type.Equals(intType, SymbolEqualityComparer.Default)); - if(intEquals is null) + if (intEquals is null) { return false; } @@ -187,7 +187,7 @@ static bool IsLiteralZero(IOperation? operation) return operation is ILiteralOperation literal && literal.ConstantValue.Value is 0; } } - + internal static IInvocationOperation? GetInvocationFromEqualsCheckWithLiteralZero(IInvocationOperation? invocation, IMethodSymbol int32Equals) { if (!int32Equals.Equals(invocation?.TargetMethod.OriginalDefinition, SymbolEqualityComparer.Default)) @@ -240,13 +240,13 @@ internal static bool IsStringStringCase(IOperation operation, RequiredSymbols sy /// The ignoreCase argument is a boolean literal /// /// - /// + /// /// /// internal static bool IsStringStringBoolCase(IOperation operation, RequiredSymbols symbols) { // Don't report a diagnostic if either the string.Compare overload or the - // corrasponding string.Equals overload is missing. + // corresponding string.Equals overload is missing. if (symbols.CompareStringStringBool is null || symbols.EqualsStringStringStringComparison is null) { From 591d0b4387c215d1eafeb84a855150b31dbc651e Mon Sep 17 00:00:00 2001 From: Collin Alpert Date: Sun, 2 Jul 2023 11:56:24 +0200 Subject: [PATCH 3/3] Fix possible null reference. --- .../Runtime/UseStringEqualsOverStringCompare.Fixer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseStringEqualsOverStringCompare.Fixer.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseStringEqualsOverStringCompare.Fixer.cs index 0c90159cec..1043a0b349 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseStringEqualsOverStringCompare.Fixer.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseStringEqualsOverStringCompare.Fixer.cs @@ -131,7 +131,7 @@ protected static SyntaxNode InvertIfNotEquals(SyntaxNode stringEqualsInvocationE if (equalsOrNotEqualsOperation is IInvocationOperation i) { - return i.Instance.Parent is IUnaryOperation { OperatorKind: UnaryOperatorKind.Not } + return i.Instance?.Parent is IUnaryOperation { OperatorKind: UnaryOperatorKind.Not } ? generator.LogicalNotExpression(stringEqualsInvocationExpression) : stringEqualsInvocationExpression; }