File: Microsoft.NetCore.Analyzers\Runtime\UseOrdinalStringComparison.cs
Web Access
Project: src\sdk\src\Microsoft.CodeAnalysis.NetAnalyzers\src\Microsoft.CodeAnalysis.NetAnalyzers\Microsoft.CodeAnalysis.NetAnalyzers.csproj (Microsoft.CodeAnalysis.NetAnalyzers)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Immutable;
using System.Linq;
using Analyzer.Utilities;
using Analyzer.Utilities.Extensions;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.NetAnalyzers;
using Microsoft.CodeAnalysis.Operations;

namespace Microsoft.NetCore.Analyzers.Runtime
{
    using static MicrosoftNetCoreAnalyzersResources;

    public abstract class UseOrdinalStringComparisonAnalyzer : AbstractGlobalizationDiagnosticAnalyzer
    {
        internal const string RuleId = "CA1309";

        private static readonly LocalizableString s_localizableMessageAndTitle = CreateLocalizableResourceString(nameof(UseOrdinalStringComparisonTitle));

        internal static readonly DiagnosticDescriptor Rule = DiagnosticDescriptorHelper.Create(
            RuleId,
            s_localizableMessageAndTitle,
            s_localizableMessageAndTitle,
            DiagnosticCategory.Globalization,
            RuleLevel.IdeHidden_BulkConfigurable,
            description: CreateLocalizableResourceString(nameof(UseOrdinalStringComparisonDescription)),
            isPortedFxCopRule: true,
            isDataflowRule: false);

        internal const string CompareMethodName = "Compare";
        internal const string EqualsMethodName = "Equals";
        internal const string OrdinalText = "Ordinal";
        internal const string OrdinalIgnoreCaseText = "OrdinalIgnoreCase";
        internal const string StringComparisonTypeName = "System.StringComparison";
        internal const string IgnoreCaseText = "IgnoreCase";

        protected abstract Location GetMethodNameLocation(SyntaxNode invocationNode);

        public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = ImmutableArray.Create(Rule);

        protected override void InitializeWorker(CompilationStartAnalysisContext context)
        {
            INamedTypeSymbol? stringComparisonType = context.Compilation.GetOrCreateTypeByMetadataName(StringComparisonTypeName);
            if (stringComparisonType != null)
            {
                context.RegisterOperationAction(operationContext =>
                {
                    var operation = (IInvocationOperation)operationContext.Operation;
                    IMethodSymbol methodSymbol = operation.TargetMethod;
                    if (methodSymbol != null &&
                        methodSymbol.ContainingType.SpecialType == SpecialType.System_String &&
                        IsEqualsOrCompare(methodSymbol.Name))
                    {
                        if (!IsAcceptableOverload(methodSymbol, stringComparisonType))
                        {
                            // wrong overload
                            operationContext.ReportDiagnostic(Diagnostic.Create(Rule, GetMethodNameLocation(operation.Syntax)));
                        }
                        else
                        {
                            IArgumentOperation lastArgument = operation.Arguments.Last();
                            if (lastArgument.Value.Kind == OperationKind.FieldReference)
                            {
                                IFieldSymbol fieldSymbol = ((IFieldReferenceOperation)lastArgument.Value).Field;
                                if (fieldSymbol != null &&
                                    fieldSymbol.ContainingType.Equals(stringComparisonType) &&
                                    !IsOrdinalOrOrdinalIgnoreCase(fieldSymbol.Name))
                                {
                                    // right overload, wrong value
                                    operationContext.ReportDiagnostic(lastArgument.Syntax.CreateDiagnostic(Rule));
                                }
                            }
                        }
                    }
                },
                    OperationKind.Invocation);
            }
        }

        private static bool IsEqualsOrCompare(string methodName)
        {
            return string.Equals(methodName, EqualsMethodName, StringComparison.Ordinal) ||
                string.Equals(methodName, CompareMethodName, StringComparison.Ordinal);
        }

        private static bool IsAcceptableOverload(IMethodSymbol methodSymbol, INamedTypeSymbol stringComparisonType)
        {
            return methodSymbol.IsStatic
                ? IsAcceptableStaticOverload(methodSymbol, stringComparisonType)
                : IsAcceptableInstanceOverload(methodSymbol, stringComparisonType);
        }

        private static bool IsAcceptableInstanceOverload(IMethodSymbol methodSymbol, INamedTypeSymbol stringComparisonType)
        {
            if (string.Equals(methodSymbol.Name, EqualsMethodName, StringComparison.Ordinal))
            {
                switch (methodSymbol.Parameters.Length)
                {
                    case 1:
                        // the instance method .Equals(object) is acceptable
                        return methodSymbol.Parameters[0].Type.SpecialType == SpecialType.System_Object;
                    case 2:
                        // .Equals(string, System.StringComparison) is acceptable
                        return methodSymbol.Parameters[0].Type.SpecialType == SpecialType.System_String &&
                            methodSymbol.Parameters[1].Type.Equals(stringComparisonType);
                }
            }

            // all other overloads are unacceptable
            return false;
        }

        private static bool IsAcceptableStaticOverload(IMethodSymbol methodSymbol, INamedTypeSymbol stringComparisonType)
        {
            if (string.Equals(methodSymbol.Name, CompareMethodName, StringComparison.Ordinal))
            {
                switch (methodSymbol.Parameters.Length)
                {
                    case 3:
                        // (string, string, StringComparison) is acceptable
                        return methodSymbol.Parameters[0].Type.SpecialType == SpecialType.System_String &&
                            methodSymbol.Parameters[1].Type.SpecialType == SpecialType.System_String &&
                            methodSymbol.Parameters[2].Type.Equals(stringComparisonType);
                    case 6:
                        // (string, int, string, int, int, StringComparison) is acceptable
                        return methodSymbol.Parameters[0].Type.SpecialType == SpecialType.System_String &&
                            methodSymbol.Parameters[1].Type.SpecialType == SpecialType.System_Int32 &&
                            methodSymbol.Parameters[2].Type.SpecialType == SpecialType.System_String &&
                            methodSymbol.Parameters[3].Type.SpecialType == SpecialType.System_Int32 &&
                            methodSymbol.Parameters[4].Type.SpecialType == SpecialType.System_Int32 &&
                            methodSymbol.Parameters[5].Type.Equals(stringComparisonType);
                }
            }
            else if (string.Equals(methodSymbol.Name, EqualsMethodName, StringComparison.Ordinal))
            {
                switch (methodSymbol.Parameters.Length)
                {
                    case 2:
                        // (object, object) is acceptable
                        return methodSymbol.Parameters[0].Type.SpecialType == SpecialType.System_Object &&
                            methodSymbol.Parameters[1].Type.SpecialType == SpecialType.System_Object;
                    case 3:
                        // (string, string, StringComparison) is acceptable
                        return methodSymbol.Parameters[0].Type.SpecialType == SpecialType.System_String &&
                            methodSymbol.Parameters[1].Type.SpecialType == SpecialType.System_String &&
                            methodSymbol.Parameters[2].Type.Equals(stringComparisonType);
                }
            }

            // all other overloads are unacceptable
            return false;
        }

        private static bool IsOrdinalOrOrdinalIgnoreCase(string name)
        {
            return string.Equals(name, OrdinalText, StringComparison.Ordinal) ||
                string.Equals(name, OrdinalIgnoreCaseText, StringComparison.Ordinal);
        }
    }
}