File: Microsoft.CodeQuality.Analyzers\Maintainability\UseNameofInPlaceOfString.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.Collections.Immutable;
using System.Linq;
using Analyzer.Utilities;
using Analyzer.Utilities.Extensions;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Operations;

namespace Microsoft.CodeQuality.Analyzers.Maintainability
{
    using static MicrosoftCodeQualityAnalyzersResources;

    /// <summary>
    /// CA1507: <inheritdoc cref="UseNameOfInPlaceOfStringTitle"/>
    /// </summary>
    public abstract class UseNameofInPlaceOfStringAnalyzer : DiagnosticAnalyzer
    {
        internal const string RuleId = "CA1507";
        private const string ParamName = "paramName";
        private const string PropertyName = "propertyName";
        internal const string StringText = "StringText";

        internal static readonly DiagnosticDescriptor RuleWithSuggestion = DiagnosticDescriptorHelper.Create(
            RuleId,
            CreateLocalizableResourceString(nameof(UseNameOfInPlaceOfStringTitle)),
            CreateLocalizableResourceString(nameof(UseNameOfInPlaceOfStringMessage)),
            DiagnosticCategory.Maintainability,
            RuleLevel.IdeSuggestion,
            description: CreateLocalizableResourceString(nameof(UseNameOfInPlaceOfStringDescription)),
            isPortedFxCopRule: false,
            isDataflowRule: false);

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

        protected abstract bool IsApplicableToLanguageVersion(ParseOptions options);

        public override void Initialize(AnalysisContext context)
        {
            context.EnableConcurrentExecution();
            context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);

            context.RegisterOperationAction(AnalyzeArgument, OperationKind.Argument);
        }

        private void AnalyzeArgument(OperationAnalysisContext context)
        {
            if (!IsApplicableToLanguageVersion(context.Operation.Syntax.SyntaxTree.Options))
            {
                return;
            }

            var argument = (IArgumentOperation)context.Operation;
            if (argument.Parameter == null
                || argument.Value.Kind != OperationKind.Literal
                || argument.ArgumentKind != ArgumentKind.Explicit
                || argument.Value.Type?.SpecialType != SpecialType.System_String
                || !argument.Value.ConstantValue.HasValue
                || argument.Value.ConstantValue.Value is not string stringText)
            {
                return;
            }

            // argument.Parameter will not be null here. The only case for it to be null is an __arglist argument, for which
            // the argument type will not be of SpecialType.System_String.
            switch (argument.Parameter.Name)
            {
                case ParamName:
                    if (HasAnyParameterMatchInScope(context, stringText))
                    {
                        context.ReportDiagnostic(argument.Value.CreateDiagnostic(RuleWithSuggestion, stringText));
                    }

                    return;
                case PropertyName:
                    if (HasAnyPropertyMatchInScope(context, stringText))
                    {
                        context.ReportDiagnostic(argument.Value.CreateDiagnostic(RuleWithSuggestion, stringText));
                    }

                    return;
                default:
                    return;
            }
        }

        private static bool HasAnyPropertyMatchInScope(OperationAnalysisContext context, string stringText)
        {
            var containingType = context.ContainingSymbol.ContainingType;
            return containingType?.GetMembers(stringText).Any(m => m.Kind == SymbolKind.Property) == true;
        }

        private static bool HasAnyParameterMatchInScope(OperationAnalysisContext context, string stringText)
        {
            // get the parameters for the containing method
            foreach (var parameter in context.ContainingSymbol.GetParameters())
            {
                if (parameter.Name == stringText)
                {
                    return true;
                }
            }

            // and loop through the ancestors to find parameters of anonymous functions and local functions
            var parentOperation = context.Operation.Parent;
            while (parentOperation != null)
            {
                IMethodSymbol? methodSymbol = parentOperation switch
                {
                    IAnonymousFunctionOperation anonymousOperation => anonymousOperation.Symbol,
                    ILocalFunctionOperation localFunctionOperation => localFunctionOperation.Symbol,
                    _ => null
                };

                if (methodSymbol is not null)
                {
                    foreach (var methodParameter in methodSymbol.Parameters)
                    {
                        if (methodParameter.Name == stringText)
                        {
                            return true;
                        }
                    }
                }

                parentOperation = parentOperation.Parent;
            }

            return false;
        }
    }
}