/// <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="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)); } 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 => await MethodMetricData.ComputeAsync((IMethodSymbol)symbol, context).ConfigureAwait(false), SymbolKind.Property => await PropertyMetricData.ComputeAsync((IPropertySymbol)symbol, context).ConfigureAwait(false), SymbolKind.Field => await FieldMetricData.ComputeAsync((IFieldSymbol)symbol, context).ConfigureAwait(false), SymbolKind.Event => await EventMetricData.ComputeAsync((IEventSymbol)symbol, context).ConfigureAwait(false), _ => throw new NotSupportedException(), });
internal static async Task <long> GetLinesOfCodeAsync(ImmutableArray <SyntaxReference> declarations, ISymbol symbol, CodeMetricsAnalysisContext context) { long linesOfCode = 0; foreach (var decl in declarations) { SyntaxNode declSyntax = await GetTopmostSyntaxNodeForDeclarationAsync(decl, symbol, context).ConfigureAwait(false); // For namespace symbols, don't count lines of code for declarations of child namespaces. // For example, "namespace N1.N2 { }" is a declaration reference for N1, but the actual declaration is for N2. if (symbol.Kind == SymbolKind.Namespace) { var model = context.GetSemanticModel(declSyntax); if (!Equals(model.GetDeclaredSymbol(declSyntax, context.CancellationToken), symbol)) { continue; } } FileLinePositionSpan linePosition = declSyntax.SyntaxTree.GetLineSpan(declSyntax.FullSpan, context.CancellationToken); long delta = linePosition.EndLinePosition.Line - linePosition.StartLinePosition.Line; if (delta == 0) { // Declaration on a single line, we count it as a separate line. delta = 1; } else { // Ensure that we do not count the leading and trailing emtpy new lines. var additionalNewLines = Math.Max(0, GetNewlineCount(declSyntax.GetLeadingTrivia(), leading: true) + GetNewlineCount(declSyntax.GetTrailingTrivia(), leading: false) - 1); delta -= additionalNewLines; } linesOfCode += delta; } return(linesOfCode);
internal static async Task <MethodMetricData> ComputeAsync(IMethodSymbol method, CodeMetricsAnalysisContext context) { var wellKnownTypeProvider = WellKnownTypeProvider.GetOrCreate(context.Compilation); var coupledTypesBuilder = ImmutableHashSet.CreateBuilder <INamedTypeSymbol>(); ImmutableArray <SyntaxReference> declarations = method.DeclaringSyntaxReferences; long linesOfCode = await MetricsHelper.GetLinesOfCodeAsync(declarations, method, context).ConfigureAwait(false); (int cyclomaticComplexity, ComputationalComplexityMetrics computationalComplexityMetrics) = await MetricsHelper.ComputeCoupledTypesAndComplexityExcludingMemberDeclsAsync(declarations, method, coupledTypesBuilder, context).ConfigureAwait(false); MetricsHelper.AddCoupledNamedTypes(coupledTypesBuilder, wellKnownTypeProvider, method.Parameters); if (!method.ReturnsVoid) { MetricsHelper.AddCoupledNamedTypes(coupledTypesBuilder, wellKnownTypeProvider, method.ReturnType); } int?depthOfInheritance = null; int maintainabilityIndex = CalculateMaintainabilityIndex(computationalComplexityMetrics, cyclomaticComplexity); MetricsHelper.RemoveContainingTypes(method, coupledTypesBuilder); if (cyclomaticComplexity == 0) { // Empty method, such as auto-generated accessor. cyclomaticComplexity = 1; } return(new MethodMetricData(method, maintainabilityIndex, computationalComplexityMetrics, coupledTypesBuilder.ToImmutable(), linesOfCode, cyclomaticComplexity, depthOfInheritance)); }
internal static async Task <AssemblyMetricData> ComputeAsync(IAssemblySymbol assembly, CodeMetricsAnalysisContext context) { var coupledTypesBuilder = ImmutableHashSet.CreateBuilder <INamedTypeSymbol>(); long linesOfCode = 0; int maintainabilityIndexTotal = 0; int cyclomaticComplexity = 0; int depthOfInheritance = 0; int grandChildCount = 0; var wellKnownTypeProvider = WellKnownTypeProvider.GetOrCreate(context.Compilation); ImmutableArray <CodeAnalysisMetricData> children = await ComputeAsync(GetChildSymbols(assembly), context).ConfigureAwait(false); foreach (CodeAnalysisMetricData child in children) { MetricsHelper.AddCoupledNamedTypes(coupledTypesBuilder, wellKnownTypeProvider, child.CoupledNamedTypes); linesOfCode += child.SourceLines; cyclomaticComplexity += child.CyclomaticComplexity; depthOfInheritance = Math.Max(child.DepthOfInheritance.GetValueOrDefault(), depthOfInheritance); // Compat: Maintainability index of an assembly is computed based on the values of types, not namespace children. Debug.Assert(child.Symbol.Kind == SymbolKind.Namespace); Debug.Assert(!child.Children.IsEmpty); Debug.Assert(child.Children.All(grandChild => grandChild.Symbol.Kind == SymbolKind.NamedType)); maintainabilityIndexTotal += child.MaintainabilityIndex * child.Children.Length; grandChildCount += child.Children.Length; } int maintainabilityIndex = grandChildCount > 0 ? MetricsHelper.GetAverageRoundedMetricValue(maintainabilityIndexTotal, grandChildCount) : 100; return(new AssemblyMetricData(assembly, maintainabilityIndex, coupledTypesBuilder.ToImmutable(), linesOfCode, cyclomaticComplexity, depthOfInheritance, children)); }
internal static async Task <PropertyMetricData> ComputeAsync(IPropertySymbol property, CodeMetricsAnalysisContext context) { var wellKnownTypeProvider = WellKnownTypeProvider.GetOrCreate(context.Compilation); var coupledTypesBuilder = ImmutableHashSet.CreateBuilder <INamedTypeSymbol>(); ImmutableArray <SyntaxReference> declarations = property.DeclaringSyntaxReferences; long linesOfCode = await MetricsHelper.GetLinesOfCodeAsync(declarations, property, context).ConfigureAwait(false); (int cyclomaticComplexity, ComputationalComplexityMetrics computationalComplexityMetrics) = await MetricsHelper.ComputeCoupledTypesAndComplexityExcludingMemberDeclsAsync(declarations, property, coupledTypesBuilder, context).ConfigureAwait(false); MetricsHelper.AddCoupledNamedTypes(coupledTypesBuilder, wellKnownTypeProvider, property.Parameters); MetricsHelper.AddCoupledNamedTypes(coupledTypesBuilder, wellKnownTypeProvider, property.Type); ImmutableArray <CodeAnalysisMetricData> children = await ComputeAsync(GetAccessors(property), context).ConfigureAwait(false); int maintainabilityIndexTotal = 0; foreach (CodeAnalysisMetricData child in children) { MetricsHelper.AddCoupledNamedTypes(coupledTypesBuilder, wellKnownTypeProvider, child.CoupledNamedTypes); maintainabilityIndexTotal += child.MaintainabilityIndex; cyclomaticComplexity += child.CyclomaticComplexity; computationalComplexityMetrics = computationalComplexityMetrics.Union(child.ComputationalComplexityMetrics); } int?depthOfInheritance = null; int maintainabilityIndex = children.Length > 0 ? MetricsHelper.GetAverageRoundedMetricValue(maintainabilityIndexTotal, children.Length) : 100; MetricsHelper.RemoveContainingTypes(property, coupledTypesBuilder); return(new PropertyMetricData(property, maintainabilityIndex, computationalComplexityMetrics, coupledTypesBuilder.ToImmutable(), linesOfCode, cyclomaticComplexity, depthOfInheritance, children)); }
internal static async Task <NamedTypeMetricData> ComputeAsync(INamedTypeSymbol namedType, CodeMetricsAnalysisContext context) { var wellKnownTypeProvider = WellKnownTypeProvider.GetOrCreate(context.Compilation); var coupledTypesBuilder = ImmutableHashSet.CreateBuilder <INamedTypeSymbol>(); ImmutableArray <SyntaxReference> declarations = namedType.DeclaringSyntaxReferences; (int cyclomaticComplexity, ComputationalComplexityMetrics computationalComplexityMetrics) = await MetricsHelper.ComputeCoupledTypesAndComplexityExcludingMemberDeclsAsync(declarations, namedType, coupledTypesBuilder, context).ConfigureAwait(false); // 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 ImmutableArray <CodeAnalysisMetricData> children = await ComputeAsync(members, context).ConfigureAwait(false); // 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, 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 = await MetricsHelper.GetLinesOfCodeAsync(declarations, namedType, context).ConfigureAwait(false); 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); } }
internal static async Task <SyntaxNode> GetTopmostSyntaxNodeForDeclarationAsync(SyntaxReference declaration, ISymbol declaredSymbol, CodeMetricsAnalysisContext context) { var declSyntax = await declaration.GetSyntaxAsync(context.CancellationToken).ConfigureAwait(false); if (declSyntax.Language == LanguageNames.VisualBasic) { SemanticModel model = context.GetSemanticModel(declSyntax); while (declSyntax.Parent != null && Equals(model.GetDeclaredSymbol(declSyntax.Parent, context.CancellationToken), declaredSymbol)) { declSyntax = declSyntax.Parent; } } return(declSyntax); }
internal static async Task <long> GetLinesOfCodeAsync(ImmutableArray <SyntaxReference> declarations, ISymbol symbol, CodeMetricsAnalysisContext context) { long linesOfCode = 0; foreach (var decl in declarations) { SyntaxNode declSyntax = await GetTopmostSyntaxNodeForDeclarationAsync(decl, symbol, context).ConfigureAwait(false); // For namespace symbols, don't count lines of code for declarations of child namespaces. // For example, "namespace N1.N2 { }" is a declaration reference for N1, but the actual declaration is for N2. if (symbol.Kind == SymbolKind.Namespace) { var model = context.GetSemanticModel(declSyntax); if (model.GetDeclaredSymbol(declSyntax, context.CancellationToken) != (object)symbol) { continue; } } FileLinePositionSpan linePosition = declSyntax.SyntaxTree.GetLineSpan(declSyntax.FullSpan, context.CancellationToken); long delta = linePosition.EndLinePosition.Line - linePosition.StartLinePosition.Line; if (delta == 0) { // Declaration on a single line, we count it as a separate line. delta = 1; } linesOfCode += delta; } return(linesOfCode); }
internal static async Task <(int cyclomaticComplexity, ComputationalComplexityMetrics computationalComplexityMetrics)> ComputeCoupledTypesAndComplexityExcludingMemberDeclsAsync( ImmutableArray <SyntaxReference> declarations, ISymbol symbol, ImmutableHashSet <INamedTypeSymbol> .Builder builder, CodeMetricsAnalysisContext context) { int cyclomaticComplexity = 0; ComputationalComplexityMetrics computationalComplexityMetrics = ComputationalComplexityMetrics.Default; var nodesToProcess = new Queue <SyntaxNode>(); using var applicableAttributeNodes = PooledHashSet <SyntaxNode> .GetInstance(); var wellKnownTypeProvider = WellKnownTypeProvider.GetOrCreate(context.Compilation); foreach (var declaration in declarations) { SyntaxNode syntax = await GetTopmostSyntaxNodeForDeclarationAsync(declaration, symbol, context).ConfigureAwait(false); nodesToProcess.Enqueue(syntax); // Ensure we process parameter initializers and attributes. var parameters = GetParameters(symbol); foreach (var parameter in parameters) { var parameterSyntaxRef = parameter.DeclaringSyntaxReferences.FirstOrDefault(); if (parameterSyntaxRef != null) { var parameterSyntax = await parameterSyntaxRef.GetSyntaxAsync(context.CancellationToken).ConfigureAwait(false); nodesToProcess.Enqueue(parameterSyntax); } } var attributes = symbol.GetAttributes(); if (symbol is IMethodSymbol methodSymbol) { attributes = attributes.AddRange(methodSymbol.GetReturnTypeAttributes()); } foreach (var attribute in attributes) { if (attribute.ApplicationSyntaxReference != null && attribute.ApplicationSyntaxReference.SyntaxTree == declaration.SyntaxTree) { var attributeSyntax = await attribute.ApplicationSyntaxReference.GetSyntaxAsync(context.CancellationToken).ConfigureAwait(false); if (applicableAttributeNodes.Add(attributeSyntax)) { nodesToProcess.Enqueue(attributeSyntax); } } } do { var node = nodesToProcess.Dequeue(); var model = context.GetSemanticModel(node); if (!ReferenceEquals(node, syntax)) { var declaredSymbol = model.GetDeclaredSymbol(node, context.CancellationToken); if (declaredSymbol != null && !Equals(symbol, declaredSymbol) && declaredSymbol.Kind != SymbolKind.Parameter) { // Skip member declarations. continue; } } var typeInfo = model.GetTypeInfo(node, context.CancellationToken); AddCoupledNamedTypesCore(builder, typeInfo.Type, wellKnownTypeProvider); var operationBlock = model.GetOperation(node, context.CancellationToken); if (operationBlock != null && operationBlock.Parent == null) { switch (operationBlock.Kind) { case OperationKind.Block: case OperationKind.MethodBodyOperation: case OperationKind.ConstructorBodyOperation: cyclomaticComplexity += 1; break; case OperationKind.None: // Skip non-applicable attributes. if (!applicableAttributeNodes.Contains(node)) { continue; } break; } computationalComplexityMetrics = computationalComplexityMetrics.Union(ComputationalComplexityMetrics.Compute(operationBlock)); // Add used types within executable code in the operation tree. foreach (var operation in operationBlock.DescendantsAndSelf()) { #if LEGACY_CODE_METRICS_MODE // Legacy mode does not account for code within lambdas/local functions for code metrics. if (operation.IsWithinLambdaOrLocalFunction()) { continue; } #endif if (!operation.IsImplicit && hasConditionalLogic(operation)) { cyclomaticComplexity += 1; } AddCoupledNamedTypesCore(builder, operation.Type, wellKnownTypeProvider); // Handle static member accesses specially as there is no operation for static type off which the member is accessed. if (operation is IMemberReferenceOperation memberReference && memberReference.Member.IsStatic) { AddCoupledNamedTypesCore(builder, memberReference.Member.ContainingType, wellKnownTypeProvider); } else if (operation is IInvocationOperation invocation && (invocation.TargetMethod.IsStatic || invocation.TargetMethod.IsExtensionMethod)) { AddCoupledNamedTypesCore(builder, invocation.TargetMethod.ContainingType, wellKnownTypeProvider); } } }
internal static async Task <NamespaceMetricData> ComputeAsync(INamespaceSymbol @namespace, CodeMetricsAnalysisContext context) { var coupledTypesBuilder = ImmutableHashSet.CreateBuilder <INamedTypeSymbol>(); int maintainabilityIndexTotal = 0; int cyclomaticComplexity = 0; int depthOfInheritance = 0; long childrenLinesOfCode = 0; var wellKnownTypeProvider = WellKnownTypeProvider.GetOrCreate(context.Compilation); ImmutableArray <CodeAnalysisMetricData> children = await ComputeAsync(GetChildSymbols(@namespace), context).ConfigureAwait(false); foreach (CodeAnalysisMetricData child in children) { MetricsHelper.AddCoupledNamedTypes(coupledTypesBuilder, wellKnownTypeProvider, child.CoupledNamedTypes); maintainabilityIndexTotal += child.MaintainabilityIndex; cyclomaticComplexity += child.CyclomaticComplexity; depthOfInheritance = Math.Max(child.DepthOfInheritance.GetValueOrDefault(), depthOfInheritance); // Avoid double counting lines for nested types. if (child.Symbol.ContainingType == null) { childrenLinesOfCode += child.SourceLines; } } long linesOfCode = @namespace.IsImplicitlyDeclared ? childrenLinesOfCode : await MetricsHelper.GetLinesOfCodeAsync(@namespace.DeclaringSyntaxReferences, @namespace, context).ConfigureAwait(false); int maintainabilityIndex = children.Length > 0 ? MetricsHelper.GetAverageRoundedMetricValue(maintainabilityIndexTotal, children.Length) : 100; return(new NamespaceMetricData(@namespace, maintainabilityIndex, coupledTypesBuilder.ToImmutable(), linesOfCode, cyclomaticComplexity, depthOfInheritance, children)); }
internal static async Task <FieldMetricData> ComputeAsync(IFieldSymbol field, CodeMetricsAnalysisContext context) { var wellKnownTypeProvider = WellKnownTypeProvider.GetOrCreate(context.Compilation); var coupledTypesBuilder = ImmutableHashSet.CreateBuilder <INamedTypeSymbol>(); ImmutableArray <SyntaxReference> declarations = field.DeclaringSyntaxReferences; long linesOfCode = await MetricsHelper.GetLinesOfCodeAsync(declarations, field, context).ConfigureAwait(false); (int cyclomaticComplexity, ComputationalComplexityMetrics computationalComplexityMetrics) = await MetricsHelper.ComputeCoupledTypesAndComplexityExcludingMemberDeclsAsync(declarations, field, coupledTypesBuilder, context).ConfigureAwait(false); MetricsHelper.AddCoupledNamedTypes(coupledTypesBuilder, wellKnownTypeProvider, field.Type); int?depthOfInheritance = null; int maintainabilityIndex = CalculateMaintainabilityIndex(computationalComplexityMetrics, cyclomaticComplexity); MetricsHelper.RemoveContainingTypes(field, coupledTypesBuilder); return(new FieldMetricData(field, maintainabilityIndex, computationalComplexityMetrics, coupledTypesBuilder.ToImmutable(), linesOfCode, cyclomaticComplexity, depthOfInheritance)); }