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

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

    /// <summary>
    /// CA2244: <inheritdoc cref="AvoidDuplicateElementInitializationTitle"/>
    /// </summary>
#pragma warning disable RS1004 // Recommend adding language support to diagnostic analyzer - Construct impossible in VB.NET
    [DiagnosticAnalyzer(LanguageNames.CSharp)]
#pragma warning restore RS1004 // Recommend adding language support to diagnostic analyzer
    public sealed class AvoidDuplicateElementInitialization : DiagnosticAnalyzer
    {
        internal const string RuleId = "CA2244";

        internal static readonly DiagnosticDescriptor Rule = DiagnosticDescriptorHelper.Create(
            RuleId,
            CreateLocalizableResourceString(nameof(AvoidDuplicateElementInitializationTitle)),
            CreateLocalizableResourceString(nameof(AvoidDuplicateElementInitializationMessage)),
            DiagnosticCategory.Usage,
            RuleLevel.IdeSuggestion,
            description: CreateLocalizableResourceString(nameof(AvoidDuplicateElementInitializationDescription)),
            isPortedFxCopRule: false,
            isDataflowRule: false);

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

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

            context.RegisterOperationAction(AnalyzeOperation, OperationKind.ObjectCreation);
        }

        private static void AnalyzeOperation(OperationAnalysisContext context)
        {
            var objectInitializer = (IObjectCreationOperation)context.Operation;
            if (objectInitializer.Initializer == null ||
                objectInitializer.Initializer.Initializers.Length <= 1)
            {
                return;
            }

            var initializedElementIndexes = new HashSet<ImmutableArray<object>>(ConstantArgumentEqualityComparer.Instance);

            for (int i = objectInitializer.Initializer.Initializers.Length - 1; i >= 0; i--)
            {
                if (objectInitializer.Initializer.Initializers[i] is ISimpleAssignmentOperation assignment &&
                    assignment.Target is IPropertyReferenceOperation propertyReference &&
                    !propertyReference.Arguments.IsEmpty)
                {
                    var values = GetConstantArgumentValues(propertyReference.Arguments);
                    if (!values.IsEmpty && !initializedElementIndexes.Add(values))
                    {
                        var indexesText = string.Join(", ", values.Select(value => value?.ToString() ?? "null"));
                        context.ReportDiagnostic(
                            Diagnostic.Create(Rule, propertyReference.Syntax.GetLocation(),
                                additionalLocations: ImmutableArray.Create(assignment.Syntax.GetLocation()),
                                messageArgs: indexesText));
                    }
                }
            }
        }

        /// <summary>
        /// Gets the argument values in parameter order, filling in defaults if necessary, if all
        /// arguments are constants. Otherwise, returns null.
        /// </summary>
        private static ImmutableArray<object> GetConstantArgumentValues(ImmutableArray<IArgumentOperation> arguments)
        {
            var result = new object[arguments.Length];
            foreach (var argument in arguments)
            {
                var parameter = argument.Parameter;
                if (parameter == null ||
                    parameter.Ordinal >= result.Length ||
                    !argument.Value.ConstantValue.HasValue ||
                    argument.Value.ConstantValue.Value is not { } value)
                {
                    return ImmutableArray<object>.Empty;
                }

                result[parameter.Ordinal] = value;
            }

            return result.ToImmutableArray();
        }

        private sealed class ConstantArgumentEqualityComparer : IEqualityComparer<ImmutableArray<object>>
        {
            public static readonly ConstantArgumentEqualityComparer Instance = new();

            private readonly EqualityComparer<object> _objectComparer = EqualityComparer<object>.Default;

            private ConstantArgumentEqualityComparer() { }

            bool IEqualityComparer<ImmutableArray<object>>.Equals(ImmutableArray<object> x, ImmutableArray<object> y)
            {
                if (x == y)
                {
                    return true;
                }

                return x != null && y != null && x.SequenceEqual(y, _objectComparer);
            }

            int IEqualityComparer<ImmutableArray<object>>.GetHashCode(ImmutableArray<object> obj)
            {
                int hash = 0;
                if (obj != null)
                {
                    foreach (var item in obj)
                    {
                        hash = unchecked((hash * (int)0xA5555529) + _objectComparer.GetHashCode(item));
                    }
                }

                return hash;
            }
        }
    }
}