| File: src\roslyn\src\Analyzers\Core\Analyzers\UseIsNullCheck\AbstractUseIsNullForReferenceEqualsDiagnosticAnalyzer.cs | Web Access |
| Project: src\roslyn\src\CodeStyle\Core\Analyzers\Microsoft.CodeAnalysis.CodeStyle.csproj (Microsoft.CodeAnalysis.CodeStyle) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System.Collections.Immutable; using System.Linq; using System.Threading; using Microsoft.CodeAnalysis.CodeStyle; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.LanguageService; namespace Microsoft.CodeAnalysis.UseIsNullCheck; internal abstract class AbstractUseIsNullCheckForReferenceEqualsDiagnosticAnalyzer< TLanguageKindEnum> : AbstractBuiltInCodeStyleDiagnosticAnalyzer where TLanguageKindEnum : struct { protected AbstractUseIsNullCheckForReferenceEqualsDiagnosticAnalyzer(LocalizableString title) : base(IDEDiagnosticIds.UseIsNullCheckDiagnosticId, EnforceOnBuildValues.UseIsNullCheck, CodeStyleOptions2.PreferIsNullCheckOverReferenceEqualityMethod, title, new LocalizableResourceString(nameof(AnalyzersResources.Null_check_can_be_simplified), AnalyzersResources.ResourceManager, typeof(AnalyzersResources))) { } public override DiagnosticAnalyzerCategory GetAnalyzerCategory() => DiagnosticAnalyzerCategory.SemanticSpanAnalysis; protected override void InitializeWorker(AnalysisContext context) => context.RegisterCompilationStartAction(context => { var objectType = context.Compilation.GetSpecialType(SpecialType.System_Object); if (objectType != null && IsLanguageVersionSupported(context.Compilation)) { var referenceEqualsMethod = objectType.GetMembers(nameof(ReferenceEquals)) .OfType<IMethodSymbol>() .FirstOrDefault(m => m.DeclaredAccessibility == Accessibility.Public && m.Parameters.Length == 2); if (referenceEqualsMethod != null) { var syntaxKinds = GetSyntaxFacts().SyntaxKinds; var unconstraintedGenericSupported = IsUnconstrainedGenericSupported(context.Compilation); context.RegisterSyntaxNodeAction( c => AnalyzeSyntax(c, referenceEqualsMethod, unconstraintedGenericSupported), syntaxKinds.Convert<TLanguageKindEnum>(syntaxKinds.InvocationExpression)); } } }); protected abstract bool IsLanguageVersionSupported(Compilation compilation); protected abstract bool IsUnconstrainedGenericSupported(Compilation compilation); protected abstract ISyntaxFacts GetSyntaxFacts(); private void AnalyzeSyntax(SyntaxNodeAnalysisContext context, IMethodSymbol referenceEqualsMethod, bool unconstraintedGenericSupported) { var cancellationToken = context.CancellationToken; var semanticModel = context.SemanticModel; var option = context.GetAnalyzerOptions().PreferIsNullCheckOverReferenceEqualityMethod; if (!option.Value || ShouldSkipAnalysis(context, option.Notification)) { return; } var invocation = context.Node; var syntaxFacts = GetSyntaxFacts(); var expression = syntaxFacts.GetExpressionOfInvocationExpression(invocation); var nameNode = syntaxFacts.IsIdentifierName(expression) ? expression : syntaxFacts.IsSimpleMemberAccessExpression(expression) ? syntaxFacts.GetNameOfMemberAccessExpression(expression) : null; if (!syntaxFacts.IsIdentifierName(nameNode)) { return; } syntaxFacts.GetNameAndArityOfSimpleName(nameNode, out var name, out _); if (!syntaxFacts.StringComparer.Equals(name, nameof(ReferenceEquals))) { return; } var arguments = syntaxFacts.GetArgumentsOfInvocationExpression(invocation); if (arguments.Count != 2) { return; } if (!MatchesPattern(syntaxFacts, arguments[0], arguments[1]) && !MatchesPattern(syntaxFacts, arguments[1], arguments[0])) { return; } var symbol = semanticModel.GetSymbolInfo(invocation, cancellationToken).Symbol; if (!referenceEqualsMethod.Equals(symbol)) { return; } var properties = ImmutableDictionary<string, string?>.Empty.Add( UseIsNullConstants.Kind, UseIsNullConstants.ReferenceEqualsKey); var genericParameterSymbol = GetGenericParameterSymbol(syntaxFacts, semanticModel, arguments[0], arguments[1], cancellationToken); if (genericParameterSymbol != null) { if (genericParameterSymbol.IsValueType) { // 'is null' would generate error CS0403: Cannot convert null to type parameter 'T' because it could be a non-nullable value type. Consider using 'default(T)' instead. // '== null' would generate error CS0019: Operator '==' cannot be applied to operands of type 'T' and '<null>' // 'Is Nothing' would generate error BC30020: 'Is' operator does not accept operands of type 'T'. Operands must be reference or nullable types. return; } // HasReferenceTypeConstraint returns false for base type constraint. // IsReferenceType returns true. if (!genericParameterSymbol.IsReferenceType && !unconstraintedGenericSupported) { // Needs special casing for C# as long as // 'is null' over unconstrained generic is implemented in C# 8. properties = properties.Add(UseIsNullConstants.UnconstrainedGeneric, ""); } } var additionalLocations = ImmutableArray.Create(invocation.GetLocation()); var negated = syntaxFacts.IsLogicalNotExpression(invocation.Parent); if (negated) { properties = properties.Add(UseIsNullConstants.Negated, ""); } context.ReportDiagnostic( DiagnosticHelper.Create( Descriptor, nameNode.GetLocation(), option.Notification, context.Options, additionalLocations, properties)); } private static ITypeParameterSymbol? GetGenericParameterSymbol(ISyntaxFacts syntaxFacts, SemanticModel semanticModel, SyntaxNode node1, SyntaxNode node2, CancellationToken cancellationToken) { var valueNode = syntaxFacts.IsNullLiteralExpression(syntaxFacts.GetExpressionOfArgument(node1)) ? node2 : node1; var argumentExpression = syntaxFacts.GetExpressionOfArgument(valueNode); if (argumentExpression != null) { var parameterType = semanticModel.GetTypeInfo(argumentExpression, cancellationToken).Type; return parameterType as ITypeParameterSymbol; } return null; } private static bool MatchesPattern(ISyntaxFacts syntaxFacts, SyntaxNode node1, SyntaxNode node2) => syntaxFacts.IsNullLiteralExpression(syntaxFacts.GetExpressionOfArgument(node1)) && !syntaxFacts.IsNullLiteralExpression(syntaxFacts.GetExpressionOfArgument(node2)); }