// 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. #if HAS_IOPERATION using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Threading.Tasks; namespace Microsoft.CodeAnalysis.CodeMetrics { public abstract partial class CodeAnalysisMetricData { private sealed class NamedTypeMetricData : CodeAnalysisMetricData { internal NamedTypeMetricData( INamedTypeSymbol symbol, int maintainabilityIndex, ComputationalComplexityMetrics computationalComplexityMetrics, ImmutableHashSet<INamedTypeSymbol> coupledNamedTypes, long linesOfCode, int cyclomaticComplexity, int? depthOfInheritance, ImmutableArray<CodeAnalysisMetricData> children) : base(symbol, maintainabilityIndex, computationalComplexityMetrics, coupledNamedTypes, linesOfCode, cyclomaticComplexity, depthOfInheritance, children) { } internal static async Task<NamedTypeMetricData> ComputeAsync(INamedTypeSymbol namedType, CodeMetricsAnalysisContext context) { var members = GetMembers(namedType, context); ImmutableArray<CodeAnalysisMetricData> children = await ComputeAsync(members, context).ConfigureAwait(false); return ComputeFromChildren(namedType, children, context); } internal static NamedTypeMetricData ComputeSynchronously(INamedTypeSymbol namedType, CodeMetricsAnalysisContext context) { var members = GetMembers(namedType, context); ImmutableArray<CodeAnalysisMetricData> children = ComputeSynchronously(members, context); return ComputeFromChildren(namedType, children, context); } private static IEnumerable<ISymbol> GetMembers(INamedTypeSymbol namedType, CodeMetricsAnalysisContext context) { // Compat: Filter out nested types as they are children of most closest containing namespace. var members = namedType.GetMembers().Where(m => m.Kind != SymbolKind.NamedType); #if LEGACY_CODE_METRICS_MODE // Legacy mode skips metrics for field/property/event symbols, and explicitly includes accessors as methods. members = members.Where(m => m.Kind is not SymbolKind.Field and not SymbolKind.Property and not SymbolKind.Event); #else // Filter out accessors as they are children of their associated symbols, for which we generate a separate node. members = members.Where(m => m.Kind != SymbolKind.Method || ((IMethodSymbol)m).AssociatedSymbol == null); #endif return members; } private static NamedTypeMetricData ComputeFromChildren(INamedTypeSymbol namedType, ImmutableArray<CodeAnalysisMetricData> children, CodeMetricsAnalysisContext context) { var coupledTypesBuilder = ImmutableHashSet.CreateBuilder<INamedTypeSymbol>(); ImmutableArray<SyntaxReference> declarations = namedType.DeclaringSyntaxReferences; (int cyclomaticComplexity, ComputationalComplexityMetrics computationalComplexityMetrics) = MetricsHelper.ComputeCoupledTypesAndComplexityExcludingMemberDecls(declarations, namedType, coupledTypesBuilder, context); // Heuristic to prevent simple fields (no initializer or simple initializer) from skewing the complexity. ImmutableHashSet<IFieldSymbol> filteredFieldsForComplexity = getFilteredFieldsForComplexity(); int effectiveChildrenCountForComplexity = 0; int singleEffectiveChildMaintainabilityIndex = -1; foreach (CodeAnalysisMetricData child in children) { MetricsHelper.AddCoupledNamedTypes(coupledTypesBuilder, context.WellKnownTypeProvider, child.CoupledNamedTypes); if (child.Symbol.Kind != SymbolKind.Field || filteredFieldsForComplexity.Contains((IFieldSymbol)child.Symbol)) { singleEffectiveChildMaintainabilityIndex = effectiveChildrenCountForComplexity == 0 && computationalComplexityMetrics.IsDefault ? child.MaintainabilityIndex : -1; effectiveChildrenCountForComplexity++; cyclomaticComplexity += child.CyclomaticComplexity; computationalComplexityMetrics = computationalComplexityMetrics.Union(child.ComputationalComplexityMetrics); } } if (cyclomaticComplexity == 0 && !namedType.IsStatic) { // Empty named type, account for implicit constructor. cyclomaticComplexity = 1; } int depthOfInheritance = CalculateDepthOfInheritance(namedType, context.IsExcludedFromInheritanceCountFunc); long linesOfCode = MetricsHelper.GetLinesOfCode(declarations, namedType, context); int maintainabilityIndex = singleEffectiveChildMaintainabilityIndex != -1 ? singleEffectiveChildMaintainabilityIndex : CalculateMaintainabilityIndex(computationalComplexityMetrics, cyclomaticComplexity, effectiveChildrenCountForComplexity); MetricsHelper.RemoveContainingTypes(namedType, coupledTypesBuilder); return new NamedTypeMetricData(namedType, maintainabilityIndex, computationalComplexityMetrics, coupledTypesBuilder.ToImmutable(), linesOfCode, cyclomaticComplexity, depthOfInheritance, children); ImmutableHashSet<IFieldSymbol> getFilteredFieldsForComplexity() { ImmutableHashSet<IFieldSymbol>.Builder? builder = null; var orderedFieldDatas = children.Where(c => c.Symbol.Kind == SymbolKind.Field).OrderBy(c => c.MaintainabilityIndex); var indexThreshold = 99; foreach (CodeAnalysisMetricData fieldData in orderedFieldDatas) { if (fieldData.MaintainabilityIndex > indexThreshold) { break; } builder ??= ImmutableHashSet.CreateBuilder<IFieldSymbol>(); builder.Add((IFieldSymbol)fieldData.Symbol); indexThreshold -= 4; } return builder?.ToImmutable() ?? ImmutableHashSet<IFieldSymbol>.Empty; } } private static int CalculateDepthOfInheritance(INamedTypeSymbol namedType, Func<INamedTypeSymbol, bool> isExcludedFromInheritanceCount) { switch (namedType.TypeKind) { case TypeKind.Class: case TypeKind.Interface: int depth = 0; var parent = namedType.BaseType; while (parent != null && !isExcludedFromInheritanceCount(parent)) { depth++; parent = parent.BaseType; } return depth; case TypeKind.Struct: case TypeKind.Enum: case TypeKind.Delegate: // Compat: For structs, enums and delegates, we consider the depth to be 1. return 1; default: return 0; } } private static int CalculateMaintainabilityIndex( ComputationalComplexityMetrics computationalComplexityMetrics, int cyclomaticComplexity, int effectiveChildrenCount) { double avgComputationalComplexityVolume = 1.0; double avgEffectiveLinesOfCode = 0.0; double avgCyclomaticComplexity = 0.0; if (effectiveChildrenCount > 0) { avgComputationalComplexityVolume = computationalComplexityMetrics.Volume / effectiveChildrenCount; avgEffectiveLinesOfCode = (double)computationalComplexityMetrics.EffectiveLinesOfCode / effectiveChildrenCount; avgCyclomaticComplexity = (double)cyclomaticComplexity / effectiveChildrenCount; } double logAvgComputationalComplexityVolume = Math.Max(0.0, Math.Log(avgComputationalComplexityVolume)); //avoid Log(0) = -Infinity double logAvgLinesOfCode = Math.Max(0.0, Math.Log(avgEffectiveLinesOfCode)); //avoid Log(0) = -Infinity return MetricsHelper.NormalizeAndRoundMaintainabilityIndex(171 - 5.2 * logAvgComputationalComplexityVolume - 0.23 * avgCyclomaticComplexity - 16.2 * logAvgLinesOfCode); } } } } #endif |