// 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); } } } }