| File: src\roslyn\src\Analyzers\Core\Analyzers\UseSystemHashCode\UseSystemHashCodeDiagnosticAnalyzer.cs | Web Access |
| Project: src\roslyn\src\CodeStyle\Core\Analyzers\Microsoft.CodeAnalysis.CodeStyle.csproj (Microsoft.CodeAnalysis.CodeStyle) |
// 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)); } }