File: EnumsShouldHavePluralNames.cs
Web Access
Project: src\src\RoslynAnalyzers\Text.Analyzers\Core\Text.Analyzers.csproj (Text.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;
using System.Collections.Immutable;
using System.Linq;
using Analyzer.Utilities;
using Analyzer.Utilities.Extensions;
using Humanizer;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
 
namespace Text.Analyzers
{
    using static TextAnalyzersResources;
 
    /// <summary>
    /// CA1714: <inheritdoc cref="FlagsEnumsShouldHavePluralNamesTitle"/>
    /// CA1717: <inheritdoc cref="OnlyFlagsEnumsShouldHavePluralNamesTitle"/>
    /// </summary>
    [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)]
    public sealed class EnumsShouldHavePluralNamesAnalyzer : DiagnosticAnalyzer
    {
        internal const string RuleId_Plural = "CA1714";
 
        internal static readonly DiagnosticDescriptor Rule_CA1714 =
            DiagnosticDescriptorHelper.Create(
                RuleId_Plural,
                CreateLocalizableResourceString(nameof(FlagsEnumsShouldHavePluralNamesTitle)),
                CreateLocalizableResourceString(nameof(FlagsEnumsShouldHavePluralNamesMessage)),
                DiagnosticCategory.Naming,
                RuleLevel.Disabled,
                description: CreateLocalizableResourceString(nameof(FlagsEnumsShouldHavePluralNamesDescription)),
                isPortedFxCopRule: true,
                isDataflowRule: false);
 
        internal const string RuleId_NoPlural = "CA1717";
 
        internal static readonly DiagnosticDescriptor Rule_CA1717 =
            DiagnosticDescriptorHelper.Create(
                RuleId_NoPlural,
                CreateLocalizableResourceString(nameof(OnlyFlagsEnumsShouldHavePluralNamesTitle)),
                CreateLocalizableResourceString(nameof(OnlyFlagsEnumsShouldHavePluralNamesMessage)),
                DiagnosticCategory.Naming,
                RuleLevel.Disabled,
                description: CreateLocalizableResourceString(nameof(OnlyFlagsEnumsShouldHavePluralNamesDescription)),
                isPortedFxCopRule: true,
                isDataflowRule: false);
 
        public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = ImmutableArray.Create(Rule_CA1714, Rule_CA1717);
 
        public override void Initialize(AnalysisContext context)
        {
            context.EnableConcurrentExecution();
            context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
 
            context.RegisterCompilationStartAction(compilationContext =>
            {
                INamedTypeSymbol? flagsAttribute = compilationContext.Compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemFlagsAttribute);
                if (flagsAttribute == null)
                {
                    return;
                }
 
                compilationContext.RegisterSymbolAction(symbolContext => AnalyzeSymbol(symbolContext, flagsAttribute), SymbolKind.NamedType);
            });
        }
 
        private static void AnalyzeSymbol(SymbolAnalysisContext context, INamedTypeSymbol flagsAttribute)
        {
            var symbol = (INamedTypeSymbol)context.Symbol;
            if (symbol.TypeKind != TypeKind.Enum)
            {
                return;
            }
 
            var reportCA1714 = context.Options.MatchesConfiguredVisibility(Rule_CA1714, symbol, context.Compilation);
            var reportCA1717 = context.Options.MatchesConfiguredVisibility(Rule_CA1717, symbol, context.Compilation);
            if (!reportCA1714 && !reportCA1717)
            {
                return;
            }
 
            if (symbol.Name.EndsWith("i", StringComparison.OrdinalIgnoreCase) || symbol.Name.EndsWith("ae", StringComparison.OrdinalIgnoreCase))
            {
                // Skip words ending with 'i' and 'ae' to avoid flagging irregular plurals.
                // Humanizer does not recognize these as plurals, such as 'formulae', 'trophi', etc.
                return;
            }
 
            if (!symbol.Name.IsASCII())
            {
                // Skip non-ASCII names.
                return;
            }
 
            if (symbol.HasAnyAttribute(flagsAttribute))
            {
                if (reportCA1714 && !IsPlural(symbol.Name)) // Checking Rule CA1714
                {
                    context.ReportDiagnostic(symbol.CreateDiagnostic(Rule_CA1714, symbol.OriginalDefinition.Locations.First(), symbol.Name));
                }
            }
            else
            {
                if (reportCA1717 && IsPlural(symbol.Name)) // Checking Rule CA1717
                {
                    context.ReportDiagnostic(symbol.CreateDiagnostic(Rule_CA1717, symbol.OriginalDefinition.Locations.First(), symbol.Name));
                }
            }
        }
 
        private static bool IsPlural(string word)
            => word.Equals(word.Pluralize(inputIsKnownToBeSingular: false), StringComparison.OrdinalIgnoreCase);
    }
}