File: src\Analyzers\Core\Analyzers\UseSystemHashCode\UseSystemHashCodeDiagnosticAnalyzer.cs
Web Access
Project: src\src\Features\Core\Portable\Microsoft.CodeAnalysis.Features.csproj (Microsoft.CodeAnalysis.Features)
// 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.Collections.Immutable;
using System.Diagnostics;
using Microsoft.CodeAnalysis.CodeStyle;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Shared.Utilities;
 
namespace Microsoft.CodeAnalysis.UseSystemHashCode;
 
[DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)]
internal sealed class UseSystemHashCodeDiagnosticAnalyzer : AbstractBuiltInCodeStyleDiagnosticAnalyzer
{
    public UseSystemHashCodeDiagnosticAnalyzer()
        : base(IDEDiagnosticIds.UseSystemHashCode,
               EnforceOnBuildValues.UseSystemHashCode,
               option: null,
               new LocalizableResourceString(nameof(AnalyzersResources.Use_System_HashCode), AnalyzersResources.ResourceManager, typeof(AnalyzersResources)),
               new LocalizableResourceString(nameof(AnalyzersResources.GetHashCode_implementation_can_be_simplified), AnalyzersResources.ResourceManager, typeof(AnalyzersResources)))
    {
    }
 
    public override DiagnosticAnalyzerCategory GetAnalyzerCategory()
        => DiagnosticAnalyzerCategory.SemanticSpanAnalysis;
 
    protected override void InitializeWorker(AnalysisContext context)
    {
        context.RegisterCompilationStartAction(c =>
        {
            // var hashCodeType = c.Compilation.GetTypeByMetadataName("System.HashCode");
            if (HashCodeAnalyzer.TryGetAnalyzer(c.Compilation, out var analyzer))
            {
                c.RegisterOperationBlockAction(ctx => AnalyzeOperationBlock(analyzer, ctx));
            }
        });
    }
 
    private void AnalyzeOperationBlock(HashCodeAnalyzer analyzer, OperationBlockAnalysisContext context)
    {
        if (context.OperationBlocks.Length != 1)
            return;
 
        var owningSymbol = context.OwningSymbol;
        var diagnosticLocation = owningSymbol.Locations[0];
        if (!context.ShouldAnalyzeSpan(diagnosticLocation.SourceSpan))
            return;
 
        var operation = context.OperationBlocks[0];
        var (accessesBase, hashedMembers, statements) = analyzer.GetHashedMembers(owningSymbol, operation);
        var elementCount = (accessesBase ? 1 : 0) + (hashedMembers.IsDefaultOrEmpty ? 0 : hashedMembers.Length);
 
        // No members to call into HashCode.Combine with.  Don't offer anything here.
        if (elementCount == 0)
            return;
 
        // Just one member to call into HashCode.Combine. Only offer this if we have multiple statements that we can
        // reduce to a single statement.  It's not worth it to offer to replace:
        //
        //      `return x.GetHashCode();` with `return HashCode.Combine(x);`
        //
        // But it is work it to offer to replace:
        //
        //      `return (a, b).GetHashCode();` with `return HashCode.Combine(a, b);`
        if (elementCount == 1 && statements.Length < 2)
            return;
 
        // We've got multiple members to hash, or multiple statements that can be reduced at this point.
        Debug.Assert(elementCount >= 2 || statements.Length >= 2);
 
        var option = context.Options.GetAnalyzerOptions(operation.Syntax.SyntaxTree).PreferSystemHashCode;
        if (!option.Value || ShouldSkipAnalysis(context, option.Notification))
            return;
 
        var cancellationToken = context.CancellationToken;
        var operationLocation = operation.Syntax.GetLocation();
        var declarationLocation = context.OwningSymbol.DeclaringSyntaxReferences[0].GetSyntax(cancellationToken).GetLocation();
        context.ReportDiagnostic(DiagnosticHelper.Create(
            Descriptor,
            diagnosticLocation,
            option.Notification,
            context.Options,
            [operationLocation, declarationLocation],
            ImmutableDictionary<string, string?>.Empty));
    }
}