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

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

    /// <summary>
    /// CA1069: <inheritdoc cref="EnumShouldNotHaveDuplicatedValuesTitle"/>
    /// </summary>
    [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)]
    public class EnumShouldNotHaveDuplicatedValues : DiagnosticAnalyzer
    {
        public const string RuleId = "CA1069";

        private static readonly LocalizableString s_localizableTitle = CreateLocalizableResourceString(nameof(EnumShouldNotHaveDuplicatedValuesTitle));

        internal static readonly DiagnosticDescriptor RuleDuplicatedValue = DiagnosticDescriptorHelper.Create(
            RuleId,
            s_localizableTitle,
            CreateLocalizableResourceString(nameof(EnumShouldNotHaveDuplicatedValuesMessageDuplicatedValue)),
            DiagnosticCategory.Design,
            RuleLevel.IdeSuggestion,
            description: null,
            isPortedFxCopRule: false,
            isDataflowRule: false);

        internal static readonly DiagnosticDescriptor RuleDuplicatedBitwiseValuePart = DiagnosticDescriptorHelper.Create(
            RuleId,
            s_localizableTitle,
            CreateLocalizableResourceString(nameof(EnumShouldNotHaveDuplicatedValuesMessageDuplicatedBitwiseValuePart)),
            DiagnosticCategory.Design,
            RuleLevel.IdeSuggestion,
            description: null,
            isPortedFxCopRule: false,
            isDataflowRule: false);

        public sealed override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = ImmutableArray.Create(RuleDuplicatedValue, RuleDuplicatedBitwiseValuePart);

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

            context.RegisterSymbolStartAction(visitEnumSymbol, SymbolKind.NamedType);

            void visitEnumSymbol(SymbolStartAnalysisContext context)
            {
                var enumSymbol = (INamedTypeSymbol)context.Symbol;
                if (enumSymbol.TypeKind != TypeKind.Enum)
                {
                    return;
                }

                // This dictionary is populated by this thread and then read concurrently.
                // https://learn.microsoft.com/dotnet/api/system.collections.generic.dictionary-2?view=net-5.0#thread-safety
                var membersByValue = PooledDictionary<object, IFieldSymbol>.GetInstance();
                var duplicates = PooledConcurrentSet<IFieldSymbol>.GetInstance(SymbolEqualityComparer.Default);
                foreach (var member in enumSymbol.GetMembers())
                {
                    if (member is not IFieldSymbol { IsImplicitlyDeclared: false, HasConstantValue: true } field ||
                        field.ConstantValue is null)
                    {
                        continue;
                    }

                    var constantValue = field.ConstantValue;
                    if (membersByValue.ContainsKey(constantValue))
                    {
                        // This field is a duplicate. We need to first check
                        // if its initializer is another field on this enum,
                        // and if not give a diagnostic on it.
                        var added = duplicates.Add(field);
                        Debug.Assert(added);
                    }
                    else
                    {
                        membersByValue[constantValue] = field;
                    }
                }

                context.RegisterOperationAction(visitFieldInitializer, OperationKind.FieldInitializer);
                context.RegisterSymbolEndAction(endVisitEnumSymbol);

                void visitFieldInitializer(OperationAnalysisContext context)
                {
                    var initializer = (IFieldInitializerOperation)context.Operation;
                    if (initializer.InitializedFields.Length != 1)
                    {
                        return;
                    }

                    var field = initializer.InitializedFields[0];
                    if (duplicates.Remove(field))
                    {
                        var duplicatedField = membersByValue[field.ConstantValue!];
                        if (initializer.Value is not IConversionOperation { Operand: IFieldReferenceOperation { Field: IFieldSymbol referencedField } }
                            || !SymbolEqualityComparer.Default.Equals(referencedField, duplicatedField))
                        {
                            context.ReportDiagnostic(field.CreateDiagnostic(RuleDuplicatedValue, field.Name, field.ConstantValue!, duplicatedField.Name));
                        }
                    }

                    // Check for duplicate usages of an enum field in an initializer consisting of '|' expressions
                    var referencedSymbols = PooledHashSet<IFieldSymbol>.GetInstance(SymbolEqualityComparer.Default);
                    var containingType = field.ContainingType;
                    visitInitializerValue(initializer.Value);
                    referencedSymbols.Free(context.CancellationToken);

                    void visitInitializerValue(IOperation operation)
                    {
                        switch (operation)
                        {
                            case IBinaryOperation { OperatorKind: not BinaryOperatorKind.Or }:
                                // only descend into '|' binary operators, not into '&', '+', ...
                                break;
                            case IFieldReferenceOperation { Field: var referencedField } fieldOperation:
                                if (SymbolEqualityComparer.Default.Equals(referencedField.ContainingType, containingType)
                                    && !referencedSymbols.Add(referencedField))
                                {
                                    context.ReportDiagnostic(fieldOperation.CreateDiagnostic(RuleDuplicatedBitwiseValuePart, referencedField.Name));
                                }

                                break;
                            default:
                                foreach (var childOperation in operation.Children)
                                {
                                    visitInitializerValue(childOperation);
                                }

                                break;
                        }
                    }
                }

                void endVisitEnumSymbol(SymbolAnalysisContext context)
                {
                    // visit any duplicates which didn't have an initializer
                    foreach (var field in duplicates)
                    {
                        var duplicatedField = membersByValue[field.ConstantValue!];
                        context.ReportDiagnostic(field.CreateDiagnostic(RuleDuplicatedValue, field.Name, field.ConstantValue!, duplicatedField.Name));
                    }

                    duplicates.Free(context.CancellationToken);
                    membersByValue.Free(context.CancellationToken);
                }
            }
        }
    }
}