// 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.Diagnostics; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; #pragma warning disable CS3001 // Some types from Roslyn are not CLS-Compliant #pragma warning disable CS3003 // Some types from Roslyn are not CLS-Compliant namespace Microsoft.CodeAnalysis.CodeMetrics { /// <summary> /// Code analysis metrics data. /// See https://learn.microsoft.com/visualstudio/code-quality/code-metrics-values for more details /// </summary> public abstract partial class CodeAnalysisMetricData { internal CodeAnalysisMetricData( ISymbol symbol, int maintainabilityIndex, ComputationalComplexityMetrics computationalComplexityMetrics, ImmutableHashSet<INamedTypeSymbol> coupledNamedTypes, long linesOfCode, int cyclomaticComplexity, int? depthOfInheritance, ImmutableArray<CodeAnalysisMetricData> children) { Debug.Assert( symbol.Kind is SymbolKind.Assembly or SymbolKind.Namespace or SymbolKind.NamedType or SymbolKind.Method or SymbolKind.Field or SymbolKind.Event or SymbolKind.Property); Debug.Assert(depthOfInheritance.HasValue == (symbol.Kind == SymbolKind.Assembly || symbol.Kind == SymbolKind.Namespace || symbol.Kind == SymbolKind.NamedType)); var executableLines = !computationalComplexityMetrics.IsDefault ? computationalComplexityMetrics.ExecutableLines : children.Sum(c => c.ExecutableLines); Symbol = symbol; MaintainabilityIndex = maintainabilityIndex; ComputationalComplexityMetrics = computationalComplexityMetrics; CoupledNamedTypes = coupledNamedTypes; SourceLines = linesOfCode; ExecutableLines = executableLines; CyclomaticComplexity = cyclomaticComplexity; DepthOfInheritance = depthOfInheritance; Children = children; } /// <summary> /// Symbol corresponding to the metric data. /// </summary> public ISymbol Symbol { get; } internal ComputationalComplexityMetrics ComputationalComplexityMetrics { get; } /// <summary> /// Indicates an index value between 0 and 100 that represents the relative ease of maintaining the code. /// A high value means better maintainability. /// </summary> public int MaintainabilityIndex { get; } /// <summary> /// Indicates the coupling to unique named types through parameters, local variables, return types, method calls, /// generic or template instantiations, base classes, interface implementations, fields defined on external types, and attribute decoration. /// Good software design dictates that types and methods should have high cohesion and low coupling. /// High coupling indicates a design that is difficult to reuse and maintain because of its many interdependencies on other types. /// </summary> public ImmutableHashSet<INamedTypeSymbol> CoupledNamedTypes { get; } /// <summary> /// Indicates the exact number of lines in source code file. /// </summary> public long SourceLines { get; } /// <summary> /// Indicates the approximate number of executable statements/lines in code. /// The count is based on the executable <see cref="IOperation"/>s in code and is therefore not the exact number of lines in the source code file. /// A high count might indicate that a type or method is trying to do too much work and should be split up. /// It might also indicate that the type or method might be hard to maintain. /// </summary> public long ExecutableLines { get; } /// <summary> /// Measures the structural complexity of the code. /// It is created by calculating the number of different code paths in the flow of the program. /// A program that has complex control flow requires more tests to achieve good code coverage and is less maintainable. /// </summary> public int CyclomaticComplexity { get; } /// <summary> /// Indicates the number of different classes that inherit from one another, all the way back to the base class. /// Depth of Inheritance is similar to class coupling in that a change in a base class can affect any of its inherited classes. /// The higher this number, the deeper the inheritance and the higher the potential for base class modifications to result in a breaking change. /// For Depth of Inheritance, a low value is good and a high value is bad. /// </summary> public int? DepthOfInheritance { get; } /// <summary> /// Array of code metrics data for symbolic children of <see cref="Symbol"/>, if any. /// </summary> public ImmutableArray<CodeAnalysisMetricData> Children { get; } /// <summary> /// Computes string representation of metrics data. /// </summary> public sealed override string ToString() { var builder = new StringBuilder(); string symbolName; switch (Symbol.Kind) { case SymbolKind.Assembly: symbolName = "Assembly"; break; case SymbolKind.Namespace: // Skip explicit display for global namespace. if (((INamespaceSymbol)Symbol).IsGlobalNamespace) { appendChildren(indent: string.Empty); return builder.ToString(); } symbolName = Symbol.Name; break; case SymbolKind.NamedType: symbolName = Symbol.ToDisplayString(); var index = symbolName.LastIndexOf(".", StringComparison.OrdinalIgnoreCase); if (index >= 0 && index < symbolName.Length) { symbolName = symbolName[(index + 1)..]; } break; default: symbolName = Symbol.ToDisplayString(); break; } builder.Append($"{symbolName}: (Lines: {SourceLines}, ExecutableLines: {ExecutableLines}, MntIndex: {MaintainabilityIndex}, CycCxty: {CyclomaticComplexity}"); if (!CoupledNamedTypes.IsEmpty) { var coupledNamedTypesStr = string.Join(", ", CoupledNamedTypes.Select(t => t.ToDisplayString()).OrderBy(n => n)); builder.Append($", CoupledTypes: {{{coupledNamedTypesStr}}}"); } if (DepthOfInheritance.HasValue) { builder.Append($", DepthInherit: {DepthOfInheritance}"); } builder.Append(')'); appendChildren(indent: " "); return builder.ToString(); void appendChildren(string indent) { foreach (var child in Children) { foreach (var line in child.ToString().Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries)) { builder.AppendLine(); builder.Append($"{indent}{line}"); } } } } /// <summary> /// Computes <see cref="CodeAnalysisMetricData"/> for the given <paramref name="compilation"/>. /// </summary> [Obsolete("Use ComputeAsync(CodeMetricsAnalysisContext) instead.")] public static Task<CodeAnalysisMetricData> ComputeAsync(Compilation compilation, CancellationToken cancellationToken) { if (compilation == null) { throw new ArgumentNullException(nameof(compilation)); } return ComputeAsync(compilation.Assembly, new CodeMetricsAnalysisContext(compilation, cancellationToken)); } /// <summary> /// Computes <see cref="CodeAnalysisMetricData"/> for the given <paramref name="context"/>. /// </summary> public static Task<CodeAnalysisMetricData> ComputeAsync(CodeMetricsAnalysisContext context) { if (context == null) { throw new ArgumentNullException(nameof(context)); } return ComputeAsync(context.Compilation.Assembly, context); } /// <summary> /// Computes <see cref="CodeAnalysisMetricData"/> for the given <paramref name="context"/>. /// </summary> public static CodeAnalysisMetricData ComputeSynchronously(CodeMetricsAnalysisContext context) { if (context == null) { throw new ArgumentNullException(nameof(context)); } return ComputeSynchronously(context.Compilation.Assembly, context); } /// <summary> /// Computes <see cref="CodeAnalysisMetricData"/> for the given <paramref name="symbol"/> from the given <paramref name="compilation"/>. /// </summary> [Obsolete("Use ComputeAsync(ISymbol, CodeMetricsAnalysisContext) instead.")] public static Task<CodeAnalysisMetricData> ComputeAsync(ISymbol symbol, Compilation compilation, CancellationToken cancellationToken) { if (symbol == null) { throw new ArgumentNullException(nameof(symbol)); } if (compilation == null) { throw new ArgumentNullException(nameof(compilation)); } return ComputeAsync(symbol, new CodeMetricsAnalysisContext(compilation, cancellationToken)); } /// <summary> /// Computes <see cref="CodeAnalysisMetricData"/> for the given <paramref name="symbol"/> from the given <paramref name="context"/>. /// </summary> public static Task<CodeAnalysisMetricData> ComputeAsync(ISymbol symbol, CodeMetricsAnalysisContext context) { if (symbol == null) { throw new ArgumentNullException(nameof(symbol)); } if (context == null) { throw new ArgumentNullException(nameof(context)); } if (context.CancellationToken.IsCancellationRequested) { return Task.FromCanceled<CodeAnalysisMetricData>(context.CancellationToken); } return ComputeAsync(symbol, context); static async Task<CodeAnalysisMetricData> ComputeAsync(ISymbol symbol, CodeMetricsAnalysisContext context) { return symbol.Kind switch { SymbolKind.Assembly => await AssemblyMetricData.ComputeAsync((IAssemblySymbol)symbol, context).ConfigureAwait(false), SymbolKind.Namespace => await NamespaceMetricData.ComputeAsync((INamespaceSymbol)symbol, context).ConfigureAwait(false), SymbolKind.NamedType => await NamedTypeMetricData.ComputeAsync((INamedTypeSymbol)symbol, context).ConfigureAwait(false), SymbolKind.Method => MethodMetricData.Compute((IMethodSymbol)symbol, context), SymbolKind.Property => PropertyMetricData.Compute((IPropertySymbol)symbol, context), SymbolKind.Field => FieldMetricData.Compute((IFieldSymbol)symbol, context), SymbolKind.Event => EventMetricData.Compute((IEventSymbol)symbol, context), _ => throw new NotSupportedException(), }; } } /// <summary> /// Computes <see cref="CodeAnalysisMetricData"/> for the given <paramref name="symbol"/> from the given <paramref name="context"/>. /// </summary> public static CodeAnalysisMetricData ComputeSynchronously(ISymbol symbol, CodeMetricsAnalysisContext context) { if (symbol == null) { throw new ArgumentNullException(nameof(symbol)); } if (context == null) { throw new ArgumentNullException(nameof(context)); } context.CancellationToken.ThrowIfCancellationRequested(); return symbol.Kind switch { SymbolKind.Assembly => AssemblyMetricData.ComputeSynchronously((IAssemblySymbol)symbol, context), SymbolKind.Namespace => NamespaceMetricData.ComputeSynchronously((INamespaceSymbol)symbol, context), SymbolKind.NamedType => NamedTypeMetricData.ComputeSynchronously((INamedTypeSymbol)symbol, context), SymbolKind.Method => MethodMetricData.Compute((IMethodSymbol)symbol, context), SymbolKind.Property => PropertyMetricData.Compute((IPropertySymbol)symbol, context), SymbolKind.Field => FieldMetricData.Compute((IFieldSymbol)symbol, context), SymbolKind.Event => EventMetricData.Compute((IEventSymbol)symbol, context), _ => throw new NotSupportedException(), }; } internal static async Task<ImmutableArray<CodeAnalysisMetricData>> ComputeAsync(IEnumerable<ISymbol> children, CodeMetricsAnalysisContext context) => (await Task.WhenAll( from child in children #if !LEGACY_CODE_METRICS_MODE // Skip implicitly declared symbols, such as default constructor, for non-legacy mode. where !child.IsImplicitlyDeclared || child is INamespaceSymbol { IsGlobalNamespace: true } #endif select Task.Run(() => ComputeAsync(child, context))).ConfigureAwait(false)).ToImmutableArray(); internal static ImmutableArray<CodeAnalysisMetricData> ComputeSynchronously(IEnumerable<ISymbol> children, CodeMetricsAnalysisContext context) => (from child in children #if !LEGACY_CODE_METRICS_MODE // Skip implicitly declared symbols, such as default constructor, for non-legacy mode. where !child.IsImplicitlyDeclared || child is INamespaceSymbol { IsGlobalNamespace: true } #endif select ComputeSynchronously(child, context)).ToImmutableArray(); } } #endif |