File: Microsoft.CodeQuality.Analyzers\ApiDesignGuidelines\IdentifiersShouldNotContainTypeNames.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 Analyzer.Utilities;
using Analyzer.Utilities.Extensions;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;

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

    /// <summary>
    /// CA1720: <inheritdoc cref="IdentifiersShouldNotContainTypeNamesTitle"/>
    /// Cause:
    /// The name of a parameter or a member contains a language-specific data type name.
    ///
    /// Description:
    /// Names of parameters and members are better used to communicate their meaning than
    /// to describe their type, which is expected to be provided by development tools. For names of members,
    /// if a data type name must be used, use a language-independent name instead of a language-specific one.
    /// </summary>
    [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)]
    public class IdentifiersShouldNotContainTypeNames : DiagnosticAnalyzer
    {
        internal const string RuleId = "CA1720";

        private static readonly ImmutableHashSet<string> s_typeNames =
            ImmutableHashSet.CreateRange(StringComparer.OrdinalIgnoreCase, new[]
            {
                "char",
                "wchar",
                "int8",
                "uint8",
                "short",
                "ushort",
                "int",
                "uint",
                "integer",
                "uinteger",
                "long",
                "ulong",
                "unsigned",
                "signed",
                "float",
                "float32",
                "float64",
                "int16",
                "int32",
                "int64",
                "uint16",
                "uint32",
                "uint64",
                "intptr",
                "uintptr",
                "ptr",
                "uptr",
                "pointer",
                "upointer",
                "single",
                "double",
                "decimal",
                "guid",
                "object",
                "string"
            });

        internal static readonly DiagnosticDescriptor Rule = DiagnosticDescriptorHelper.Create(
            RuleId,
            CreateLocalizableResourceString(nameof(IdentifiersShouldNotContainTypeNamesTitle)),
            CreateLocalizableResourceString(nameof(IdentifiersShouldNotContainTypeNamesMessage)),
            DiagnosticCategory.Naming,
            RuleLevel.IdeHidden_BulkConfigurable,
            description: CreateLocalizableResourceString(nameof(IdentifiersShouldNotContainTypeNamesDescription)),
            isPortedFxCopRule: true,
            isDataflowRule: false);

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

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

            context.RegisterCompilationStartAction(compilationStartAnalysisContext =>
            {
                // Analyze named types and fields.
                compilationStartAnalysisContext.RegisterSymbolAction(
                    symbolContext => AnalyzeSymbol(symbolContext.Symbol, symbolContext),
                    SymbolKind.NamedType,
                    SymbolKind.Field);

                // Analyze properties and methods, and their parameters.
                compilationStartAnalysisContext.RegisterSymbolAction(
                    symbolContext =>
                    {
                        // Although indexers aren't IMethodSymbols, their accessors are, and we can get their parameters from them
                        if (symbolContext.Symbol is IMethodSymbol method)
                        {
                            // If this method contains parameters with names violating this rule, we only want to flag them
                            // if this method is not overriding another or implementing an interface. Otherwise, changing the
                            // parameter names will violate CA1725 - Parameter names should match base declaration.
                            if (method.OverriddenMethod == null && !method.IsImplementationOfAnyInterfaceMember())
                            {
                                foreach (var param in method.Parameters)
                                {
                                    AnalyzeSymbol(param, symbolContext);
                                }
                            }
                        }

                        AnalyzeSymbol(symbolContext.Symbol, symbolContext);
                    },
                    SymbolKind.Property,
                    SymbolKind.Method);
            });
        }

        private static void AnalyzeSymbol(ISymbol symbol, SymbolAnalysisContext context)
        {
            // FxCop compat: only analyze externally visible symbols by default.
            if (!context.Options.MatchesConfiguredVisibility(Rule, symbol, context.Compilation))
            {
                return;
            }

            var identifier = symbol.Name;
            if (s_typeNames.Contains(identifier))
            {
                Diagnostic diagnostic = symbol.CreateDiagnostic(Rule, identifier);
                context.ReportDiagnostic(diagnostic);
            }
        }
    }
}