File: MetaAnalyzers\PreferIsKindAnalyzer.cs
Web Access
Project: src\src\RoslynAnalyzers\Microsoft.CodeAnalysis.Analyzers\Core\Microsoft.CodeAnalysis.Analyzers.csproj (Microsoft.CodeAnalysis.Analyzers)
// 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.Generic;
using System.Collections.Immutable;
using Analyzer.Utilities;
using Analyzer.Utilities.Extensions;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Operations;
 
namespace Microsoft.CodeAnalysis.Analyzers.MetaAnalyzers
{
    using static CodeAnalysisDiagnosticsResources;
 
    /// <summary>
    /// RS1034: <inheritdoc cref="PreferIsKindTitle"/>
    /// </summary>
    [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)]
    public sealed class PreferIsKindAnalyzer : DiagnosticAnalyzer
    {
        internal static readonly DiagnosticDescriptor Rule = new(
            DiagnosticIds.PreferIsKindRuleId,
            CreateLocalizableResourceString(nameof(PreferIsKindTitle)),
            CreateLocalizableResourceString(nameof(PreferIsKindMessage)),
            DiagnosticCategory.MicrosoftCodeAnalysisPerformance,
            DiagnosticSeverity.Warning,
            isEnabledByDefault: true,
            description: CreateLocalizableResourceString(nameof(PreferIsKindDescription)),
            helpLinkUri: null,
            customTags: WellKnownDiagnosticTagsExtensions.Telemetry);
 
        public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = ImmutableArray.Create(Rule);
 
        public override void Initialize(AnalysisContext context)
        {
            context.EnableConcurrentExecution();
            context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
 
            context.RegisterCompilationStartAction(context =>
            {
                // Kind() methods
                //  Microsoft.CodeAnalysis.CSharp.CSharpExtensions.Kind
                //  Microsoft.CodeAnalysis.VisualBasic.VisualBasicExtensions.Kind
                //
                // IsKind() methods
                //  Microsoft.CodeAnalysis.CSharpExtensions.IsKind
                //  Microsoft.CodeAnalysis.VisualBasicExtensions.IsKind
 
                Dictionary<INamedTypeSymbol, INamedTypeSymbol> containingTypeMap = [];
                if (context.Compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.MicrosoftCodeAnalysisCSharpCSharpExtensions) is { } csharpKindExtensions
                    && context.Compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.MicrosoftCodeAnalysisCSharpExtensions) is { } csharpIsKindExtensions)
                {
                    containingTypeMap[csharpKindExtensions] = csharpIsKindExtensions;
                }
 
                if (context.Compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.MicrosoftCodeAnalysisVisualBasicVisualBasicExtensions) is { } vbKindExtensions
                    && context.Compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.MicrosoftCodeAnalysisVisualBasicExtensions) is { } vbIsKindExtensions)
                {
                    containingTypeMap[vbKindExtensions] = vbIsKindExtensions;
                }
 
                if (containingTypeMap.Count > 0)
                {
                    context.RegisterOperationAction(context => HandleBinaryOperation(context, containingTypeMap), OperationKind.Binary);
                }
            });
        }
 
        private static void HandleBinaryOperation(OperationAnalysisContext context, Dictionary<INamedTypeSymbol, INamedTypeSymbol> containingTypeMap)
        {
            var operation = (IBinaryOperation)context.Operation;
            if (operation.OperatorKind is not (BinaryOperatorKind.Equals or BinaryOperatorKind.NotEquals))
            {
                return;
            }
 
            var possibleInvocation = operation.LeftOperand.WalkDownConversion();
            if (possibleInvocation is IConditionalAccessOperation conditionalAccess)
            {
                // We don't currently report on Nullable<SyntaxToken>. If we'll report that in the future, the codefix must behave correctly.
                if (conditionalAccess.Operation.Type!.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T)
                {
                    return;
                }
 
                possibleInvocation = conditionalAccess.WhenNotNull;
            }
 
            if (possibleInvocation is IInvocationOperation { TargetMethod: { Name: "Kind", ContainingType: var containingType } }
                && containingTypeMap.TryGetValue(containingType, out _))
            {
                context.ReportDiagnostic(operation.LeftOperand.CreateDiagnostic(Rule));
            }
        }
    }
}