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 <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 <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 <(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>();
            var applicableAttributeNodes = PooledHashSet <SyntaxNode> .GetInstance();

            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 &&
                        attribute.ApplicationSyntaxReference.SyntaxTree == declaration.SyntaxTree)
                    {
                        var attributeSyntax = await attribute.ApplicationSyntaxReference.GetSyntaxAsync(cancellationToken).ConfigureAwait(false);

                        if (applicableAttributeNodes.Add(attributeSyntax))
                        {
                            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;

                        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);

                            // 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 static async Task <PropertyMetricData> ComputeAsync(IPropertySymbol property, SemanticModelProvider semanticModelProvider, CancellationToken cancellationToken)
            {
                var coupledTypesBuilder = ImmutableHashSet.CreateBuilder <INamedTypeSymbol>();
                ImmutableArray <SyntaxReference> declarations = property.DeclaringSyntaxReferences;
                long linesOfCode = await MetricsHelper.GetLinesOfCodeAsync(declarations, property, semanticModelProvider, cancellationToken).ConfigureAwait(false);

                (int cyclomaticComplexity, ComputationalComplexityMetrics computationalComplexityMetrics) =
                    await MetricsHelper.ComputeCoupledTypesAndComplexityExcludingMemberDeclsAsync(declarations, property, coupledTypesBuilder, semanticModelProvider, cancellationToken).ConfigureAwait(false);

                MetricsHelper.AddCoupledNamedTypes(coupledTypesBuilder, property.Parameters);
                MetricsHelper.AddCoupledNamedTypes(coupledTypesBuilder, property.Type);

                ImmutableArray <CodeAnalysisMetricData> children = await ComputeAsync(GetAccessors(property), 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(property, coupledTypesBuilder);

                return(new PropertyMetricData(property, maintainabilityIndex, computationalComplexityMetrics,
                                              coupledTypesBuilder.ToImmutable(), linesOfCode, cyclomaticComplexity, depthOfInheritance, children));
            }
            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 <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 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 System.Threading.Tasks.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.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.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));
            }
Пример #10
0
            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.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, 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 static async 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();
            }
            ;
        }