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)); } var semanticModelProvider = new SemanticModelProvider(compilation); return(ComputeAsync(symbol, semanticModelProvider, cancellationToken)); }
internal static async Task <NamedTypeMetricData> ComputeAsync(INamedTypeSymbol namedType, SemanticModelProvider semanticModelProvider, CancellationToken cancellationToken) { var coupledTypesBuilder = ImmutableHashSet.CreateBuilder <INamedTypeSymbol>(); ImmutableArray <SyntaxReference> declarations = namedType.DeclaringSyntaxReferences; (int cyclomaticComplexity, ComputationalComplexityMetrics computationalComplexityMetrics) = await MetricsHelper.ComputeCoupledTypesAndComplexityExcludingMemberDeclsAsync(declarations, namedType, coupledTypesBuilder, semanticModelProvider, cancellationToken).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 != SymbolKind.Field && m.Kind != SymbolKind.Property && m.Kind != 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, semanticModelProvider, cancellationToken).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, 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); long linesOfCode = await MetricsHelper.GetLinesOfCodeAsync(declarations, namedType, semanticModelProvider, cancellationToken).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?builderOpt = 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; } builderOpt ??= ImmutableHashSet.CreateBuilder <IFieldSymbol>(); builderOpt.Add((IFieldSymbol)fieldData.Symbol); indexThreshold -= 4; } return(builderOpt?.ToImmutable() ?? ImmutableHashSet <IFieldSymbol> .Empty); } }
internal static async Task <NamespaceMetricData> ComputeAsync(INamespaceSymbol @namespace, SemanticModelProvider semanticModelProvider, CancellationToken cancellationToken) { var coupledTypesBuilder = ImmutableHashSet.CreateBuilder <INamedTypeSymbol>(); int maintainabilityIndexTotal = 0; int cyclomaticComplexity = 0; int depthOfInheritance = 0; long childrenLinesOfCode = 0; ImmutableArray <CodeAnalysisMetricData> children = await ComputeAsync(GetChildSymbols(@namespace), semanticModelProvider, cancellationToken).ConfigureAwait(false); foreach (CodeAnalysisMetricData child in children) { MetricsHelper.AddCoupledNamedTypes(coupledTypesBuilder, child.CoupledNamedTypes); maintainabilityIndexTotal += child.MaintainabilityIndex; cyclomaticComplexity += child.CyclomaticComplexity; depthOfInheritance = Math.Max(child.DepthOfInheritance.Value, 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, semanticModelProvider, cancellationToken).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 <ImmutableArray <CodeAnalysisMetricData> > ComputeAsync(IEnumerable <ISymbol> children, SemanticModelProvider semanticModelProvider, CancellationToken cancellationToken) => (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 as INamespaceSymbol)?.IsGlobalNamespace == true #endif select Task.Run(() => ComputeAsync(child, semanticModelProvider, cancellationToken))).ConfigureAwait(false)).ToImmutableArray();
internal async static Task <CodeAnalysisMetricData> ComputeAsync(ISymbol symbol, SemanticModelProvider semanticModelProvider, CancellationToken cancellationToken) { switch (symbol.Kind) { case SymbolKind.Assembly: return(await AssemblyMetricData.ComputeAsync((IAssemblySymbol)symbol, semanticModelProvider, cancellationToken).ConfigureAwait(false)); case SymbolKind.Namespace: return(await NamespaceMetricData.ComputeAsync((INamespaceSymbol)symbol, semanticModelProvider, cancellationToken).ConfigureAwait(false)); case SymbolKind.NamedType: return(await NamedTypeMetricData.ComputeAsync((INamedTypeSymbol)symbol, semanticModelProvider, cancellationToken).ConfigureAwait(false)); case SymbolKind.Method: return(await MethodMetricData.ComputeAsync((IMethodSymbol)symbol, semanticModelProvider, cancellationToken).ConfigureAwait(false)); case SymbolKind.Property: return(await PropertyMetricData.ComputeAsync((IPropertySymbol)symbol, semanticModelProvider, cancellationToken).ConfigureAwait(false)); case SymbolKind.Field: return(await FieldMetricData.ComputeAsync((IFieldSymbol)symbol, semanticModelProvider, cancellationToken).ConfigureAwait(false)); case SymbolKind.Event: return(await EventMetricData.ComputeAsync((IEventSymbol)symbol, semanticModelProvider, cancellationToken).ConfigureAwait(false)); default: throw new NotSupportedException(); } }
internal static async Task <FieldMetricData> ComputeAsync(IFieldSymbol field, SemanticModelProvider semanticModelProvider, CancellationToken cancellationToken) { var coupledTypesBuilder = ImmutableHashSet.CreateBuilder <INamedTypeSymbol>(); ImmutableArray <SyntaxReference> declarations = field.DeclaringSyntaxReferences; long linesOfCode = await MetricsHelper.GetLinesOfCodeAsync(declarations, field, semanticModelProvider, cancellationToken).ConfigureAwait(false); (int cyclomaticComplexity, ComputationalComplexityMetrics computationalComplexityMetrics) = await MetricsHelper.ComputeCoupledTypesAndComplexityExcludingMemberDeclsAsync(declarations, field, coupledTypesBuilder, semanticModelProvider, cancellationToken).ConfigureAwait(false); MetricsHelper.AddCoupledNamedTypes(coupledTypesBuilder, 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)); }
internal async static Task <EventMetricData> ComputeAsync(IEventSymbol @event, SemanticModelProvider semanticModelProvider, CancellationToken cancellationToken) { var coupledTypesBuilder = ImmutableHashSet.CreateBuilder <INamedTypeSymbol>(); ImmutableArray <SyntaxReference> declarations = @event.DeclaringSyntaxReferences; long linesOfCode = await MetricsHelper.GetLinesOfCodeAsync(declarations, @event, semanticModelProvider, cancellationToken).ConfigureAwait(false); (int cyclomaticComplexity, ComputationalComplexityMetrics computationalComplexityMetrics) = await MetricsHelper.ComputeCoupledTypesAndComplexityExcludingMemberDeclsAsync(declarations, @event, coupledTypesBuilder, semanticModelProvider, cancellationToken).ConfigureAwait(false); MetricsHelper.AddCoupledNamedTypes(coupledTypesBuilder, @event.Type); ImmutableArray <CodeAnalysisMetricData> children = await ComputeAsync(GetAccessors(@event), semanticModelProvider, cancellationToken).ConfigureAwait(false); int maintainabilityIndexTotal = 0; foreach (CodeAnalysisMetricData child in children) { MetricsHelper.AddCoupledNamedTypes(coupledTypesBuilder, 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(@event, coupledTypesBuilder); return(new EventMetricData(@event, maintainabilityIndex, computationalComplexityMetrics, coupledTypesBuilder.ToImmutable(), linesOfCode, cyclomaticComplexity, depthOfInheritance, children)); }
internal static async Task <long> GetLinesOfCodeAsync(ImmutableArray <SyntaxReference> declarations, ISymbol symbol, SemanticModelProvider semanticModelProvider, CancellationToken cancellationToken) { long linesOfCode = 0; foreach (var decl in declarations) { SyntaxNode declSyntax = await GetTopmostSyntaxNodeForDeclarationAsync(decl, symbol, semanticModelProvider, cancellationToken).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 = semanticModelProvider.GetSemanticModel(declSyntax); if (model.GetDeclaredSymbol(declSyntax, cancellationToken) != (object)symbol) { continue; } } FileLinePositionSpan linePosition = declSyntax.SyntaxTree.GetLineSpan(declSyntax.FullSpan, 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 <SyntaxNode> GetTopmostSyntaxNodeForDeclarationAsync(SyntaxReference declaration, ISymbol declaredSymbol, SemanticModelProvider semanticModelProvider, CancellationToken cancellationToken) { var declSyntax = await declaration.GetSyntaxAsync(cancellationToken).ConfigureAwait(false); if (declSyntax.Language == LanguageNames.VisualBasic) { SemanticModel model = semanticModelProvider.GetSemanticModel(declSyntax); while (declSyntax.Parent != null && Equals(model.GetDeclaredSymbol(declSyntax.Parent, cancellationToken), declaredSymbol)) { declSyntax = declSyntax.Parent; } } return(declSyntax); }
internal static async Task <(int cyclomaticComplexity, ComputationalComplexityMetrics computationalComplexityMetrics)> ComputeCoupledTypesAndComplexityExcludingMemberDeclsAsync( ImmutableArray <SyntaxReference> declarations, ISymbol symbol, ImmutableHashSet <INamedTypeSymbol> .Builder builder, SemanticModelProvider semanticModelProvider, CancellationToken cancellationToken) { int cyclomaticComplexity = 0; ComputationalComplexityMetrics computationalComplexityMetrics = ComputationalComplexityMetrics.Default; var nodesToProcess = new Queue <SyntaxNode>(); foreach (var declaration in declarations) { SyntaxNode syntax = await GetTopmostSyntaxNodeForDeclarationAsync(declaration, symbol, semanticModelProvider, cancellationToken).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(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) { var attributeSyntax = await attribute.ApplicationSyntaxReference.GetSyntaxAsync(cancellationToken).ConfigureAwait(false); nodesToProcess.Enqueue(attributeSyntax); } } do { var node = nodesToProcess.Dequeue(); var model = semanticModelProvider.GetSemanticModel(node); if (!ReferenceEquals(node, syntax)) { var declaredSymbol = model.GetDeclaredSymbol(node, cancellationToken); if (declaredSymbol != null && !Equals(symbol, declaredSymbol) && declaredSymbol.Kind != SymbolKind.Parameter) { // Skip member declarations. continue; } } var typeInfo = model.GetTypeInfo(node, cancellationToken); AddCoupledNamedTypesCore(builder, typeInfo.Type); var operationBlock = model.GetOperation(node, cancellationToken); if (operationBlock != null && operationBlock.Parent == null) { switch (operationBlock.Kind) { case OperationKind.Block: case OperationKind.MethodBodyOperation: case OperationKind.ConstructorBodyOperation: cyclomaticComplexity += 1; 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); // 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); } else if (operation is IInvocationOperation invocation && (invocation.TargetMethod.IsStatic || invocation.TargetMethod.IsExtensionMethod)) { AddCoupledNamedTypesCore(builder, invocation.TargetMethod.ContainingType); } } }
internal async static Task <CodeAnalysisMetricData> ComputeAsync(ISymbol symbol, SemanticModelProvider semanticModelProvider, CancellationToken cancellationToken) { return(symbol.Kind switch { SymbolKind.Assembly => await AssemblyMetricData.ComputeAsync((IAssemblySymbol)symbol, semanticModelProvider, cancellationToken).ConfigureAwait(false), SymbolKind.Namespace => await NamespaceMetricData.ComputeAsync((INamespaceSymbol)symbol, semanticModelProvider, cancellationToken).ConfigureAwait(false), SymbolKind.NamedType => await NamedTypeMetricData.ComputeAsync((INamedTypeSymbol)symbol, semanticModelProvider, cancellationToken).ConfigureAwait(false), SymbolKind.Method => await MethodMetricData.ComputeAsync((IMethodSymbol)symbol, semanticModelProvider, cancellationToken).ConfigureAwait(false), SymbolKind.Property => await PropertyMetricData.ComputeAsync((IPropertySymbol)symbol, semanticModelProvider, cancellationToken).ConfigureAwait(false), SymbolKind.Field => await FieldMetricData.ComputeAsync((IFieldSymbol)symbol, semanticModelProvider, cancellationToken).ConfigureAwait(false), SymbolKind.Event => await EventMetricData.ComputeAsync((IEventSymbol)symbol, semanticModelProvider, cancellationToken).ConfigureAwait(false), _ => throw new NotSupportedException(), });
internal static async Task <AssemblyMetricData> ComputeAsync(IAssemblySymbol assembly, SemanticModelProvider semanticModelProvider, CancellationToken cancellationToken) { var coupledTypesBuilder = ImmutableHashSet.CreateBuilder <INamedTypeSymbol>(); long linesOfCode = 0; int maintainabilityIndexTotal = 0; int cyclomaticComplexity = 0; int depthOfInheritance = 0; int grandChildCount = 0; ImmutableArray <CodeAnalysisMetricData> children = await ComputeAsync(GetChildSymbols(assembly), semanticModelProvider, cancellationToken).ConfigureAwait(false); foreach (CodeAnalysisMetricData child in children) { MetricsHelper.AddCoupledNamedTypes(coupledTypesBuilder, child.CoupledNamedTypes); linesOfCode += child.SourceLines; cyclomaticComplexity += child.CyclomaticComplexity; depthOfInheritance = Math.Max(child.DepthOfInheritance.Value, 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.Length > 0); 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 <MethodMetricData> ComputeAsync(IMethodSymbol method, SemanticModelProvider semanticModelProvider, CancellationToken cancellationToken) { var coupledTypesBuilder = ImmutableHashSet.CreateBuilder <INamedTypeSymbol>(); ImmutableArray <SyntaxReference> declarations = method.DeclaringSyntaxReferences; long linesOfCode = await MetricsHelper.GetLinesOfCodeAsync(declarations, method, semanticModelProvider, cancellationToken).ConfigureAwait(false); (int cyclomaticComplexity, ComputationalComplexityMetrics computationalComplexityMetrics) = await MetricsHelper.ComputeCoupledTypesAndComplexityExcludingMemberDeclsAsync(declarations, method, coupledTypesBuilder, semanticModelProvider, cancellationToken).ConfigureAwait(false); MetricsHelper.AddCoupledNamedTypes(coupledTypesBuilder, method.Parameters); if (!method.ReturnsVoid) { MetricsHelper.AddCoupledNamedTypes(coupledTypesBuilder, 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 <ImmutableArray <CodeAnalysisMetricData> > ComputeAsync(IEnumerable <ISymbol> children, SemanticModelProvider semanticModelProvider, CancellationToken cancellationToken) => (await Task.WhenAll( from child in children where !child.IsImplicitlyDeclared || (child as INamespaceSymbol)?.IsGlobalNamespace == true select Task.Run(() => ComputeAsync(child, semanticModelProvider, cancellationToken))).ConfigureAwait(false)).ToImmutableArray();