diff --git a/nuget/Microsoft.CodeQuality.Analyzers/Microsoft.CodeQuality.Analyzers.props b/nuget/Microsoft.CodeQuality.Analyzers/Microsoft.CodeQuality.Analyzers.props index 2073c35758..a9919e74a9 100644 --- a/nuget/Microsoft.CodeQuality.Analyzers/Microsoft.CodeQuality.Analyzers.props +++ b/nuget/Microsoft.CodeQuality.Analyzers/Microsoft.CodeQuality.Analyzers.props @@ -37,6 +37,11 @@ -Microsoft.Design#CA1064; -Microsoft.Design#CA1065; + -Microsoft.Maintainability#CA1501; + -Microsoft.Maintainability#CA1502; + -Microsoft.Maintainability#CA1505; + -Microsoft.Maintainability#CA1506; + -Microsoft.Naming#CA1707; -Microsoft.Naming#CA1708; -Microsoft.Naming#CA1710; diff --git a/src/Microsoft.CodeQuality.Analyzers/Core/Maintainability/CodeMetricsAnalyzer.cs b/src/Microsoft.CodeQuality.Analyzers/Core/Maintainability/CodeMetricsAnalyzer.cs new file mode 100644 index 0000000000..a4c8648094 --- /dev/null +++ b/src/Microsoft.CodeQuality.Analyzers/Core/Maintainability/CodeMetricsAnalyzer.cs @@ -0,0 +1,487 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Analyzer.Utilities; +using Analyzer.Utilities.Extensions; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeMetrics; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Text; + +namespace Microsoft.CodeQuality.Analyzers.Maintainability.CodeMetrics +{ + /// + /// CA1501: Avoid excessive inheritance + /// CA1502: Avoid excessive complexity + /// CA1505: Avoid unmaintainable code + /// CA1506: Avoid excessive class coupling + /// + [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] + public sealed class CodeMetricsAnalyzer : DiagnosticAnalyzer + { + internal const string CA1501RuleId = "CA1501"; + internal const string CA1502RuleId = "CA1502"; + internal const string CA1505RuleId = "CA1505"; + internal const string CA1506RuleId = "CA1506"; + + /// + /// Configuration file to configure custom threshold values for supported code metrics. + /// For example, the below entry changes the maximum allowed inheritance depth from the default value of 5 to 10: + /// + /// # FORMAT: + /// # 'RuleId'(Optional 'SymbolKind'): 'Threshold' + /// + /// CA1501: 10 + /// See CA1508 unit tests for more examples. + /// + private const string CodeMetricsConfigurationFile = "CodeMetricsConfig.txt"; + + // New rule for invalid entries in CodeMetricsConfigurationFile. + internal const string CA1508RuleId = "CA1508"; + + private static readonly LocalizableString s_localizableTitleCA1501 = new LocalizableResourceString(nameof(MicrosoftMaintainabilityAnalyzersResources.AvoidExcessiveInheritanceTitle), MicrosoftMaintainabilityAnalyzersResources.ResourceManager, typeof(MicrosoftMaintainabilityAnalyzersResources)); + private static readonly LocalizableString s_localizableMessageCA1501 = new LocalizableResourceString(nameof(MicrosoftMaintainabilityAnalyzersResources.AvoidExcessiveInheritanceMessage), MicrosoftMaintainabilityAnalyzersResources.ResourceManager, typeof(MicrosoftMaintainabilityAnalyzersResources)); + private static readonly LocalizableString s_localizableDescriptionCA1501 = new LocalizableResourceString(nameof(MicrosoftMaintainabilityAnalyzersResources.AvoidExcessiveInheritanceDescription), MicrosoftMaintainabilityAnalyzersResources.ResourceManager, typeof(MicrosoftMaintainabilityAnalyzersResources)); + + private static readonly LocalizableString s_localizableTitleCA1502 = new LocalizableResourceString(nameof(MicrosoftMaintainabilityAnalyzersResources.AvoidExcessiveComplexityTitle), MicrosoftMaintainabilityAnalyzersResources.ResourceManager, typeof(MicrosoftMaintainabilityAnalyzersResources)); + private static readonly LocalizableString s_localizableMessageCA1502 = new LocalizableResourceString(nameof(MicrosoftMaintainabilityAnalyzersResources.AvoidExcessiveComplexityMessage), MicrosoftMaintainabilityAnalyzersResources.ResourceManager, typeof(MicrosoftMaintainabilityAnalyzersResources)); + private static readonly LocalizableString s_localizableDescriptionCA1502 = new LocalizableResourceString(nameof(MicrosoftMaintainabilityAnalyzersResources.AvoidExcessiveComplexityDescription), MicrosoftMaintainabilityAnalyzersResources.ResourceManager, typeof(MicrosoftMaintainabilityAnalyzersResources)); + + private static readonly LocalizableString s_localizableTitleCA1505 = new LocalizableResourceString(nameof(MicrosoftMaintainabilityAnalyzersResources.AvoidUnmantainableCodeTitle), MicrosoftMaintainabilityAnalyzersResources.ResourceManager, typeof(MicrosoftMaintainabilityAnalyzersResources)); + private static readonly LocalizableString s_localizableMessageCA1505 = new LocalizableResourceString(nameof(MicrosoftMaintainabilityAnalyzersResources.AvoidUnmantainableCodeMessage), MicrosoftMaintainabilityAnalyzersResources.ResourceManager, typeof(MicrosoftMaintainabilityAnalyzersResources)); + private static readonly LocalizableString s_localizableDescriptionCA1505 = new LocalizableResourceString(nameof(MicrosoftMaintainabilityAnalyzersResources.AvoidUnmantainableCodeDescription), MicrosoftMaintainabilityAnalyzersResources.ResourceManager, typeof(MicrosoftMaintainabilityAnalyzersResources)); + + private static readonly LocalizableString s_localizableTitleCA1506 = new LocalizableResourceString(nameof(MicrosoftMaintainabilityAnalyzersResources.AvoidExcessiveClassCouplingTitle), MicrosoftMaintainabilityAnalyzersResources.ResourceManager, typeof(MicrosoftMaintainabilityAnalyzersResources)); + private static readonly LocalizableString s_localizableMessageCA1506 = new LocalizableResourceString(nameof(MicrosoftMaintainabilityAnalyzersResources.AvoidExcessiveClassCouplingMessage), MicrosoftMaintainabilityAnalyzersResources.ResourceManager, typeof(MicrosoftMaintainabilityAnalyzersResources)); + private static readonly LocalizableString s_localizableDescriptionCA1506 = new LocalizableResourceString(nameof(MicrosoftMaintainabilityAnalyzersResources.AvoidExcessiveClassCouplingDescription), MicrosoftMaintainabilityAnalyzersResources.ResourceManager, typeof(MicrosoftMaintainabilityAnalyzersResources)); + + private static readonly LocalizableString s_localizableTitleCA1508 = new LocalizableResourceString(nameof(MicrosoftMaintainabilityAnalyzersResources.InvalidEntryInCodeMetricsConfigFileTitle), MicrosoftMaintainabilityAnalyzersResources.ResourceManager, typeof(MicrosoftMaintainabilityAnalyzersResources)); + private static readonly LocalizableString s_localizableMessageCA1508 = new LocalizableResourceString(nameof(MicrosoftMaintainabilityAnalyzersResources.InvalidEntryInCodeMetricsConfigFileMessage), MicrosoftMaintainabilityAnalyzersResources.ResourceManager, typeof(MicrosoftMaintainabilityAnalyzersResources)); + private static readonly LocalizableString s_localizableDescriptionCA1508 = new LocalizableResourceString(nameof(MicrosoftMaintainabilityAnalyzersResources.InvalidEntryInCodeMetricsConfigFileDescription), MicrosoftMaintainabilityAnalyzersResources.ResourceManager, typeof(MicrosoftMaintainabilityAnalyzersResources)); + + internal static DiagnosticDescriptor CA1501Rule = new DiagnosticDescriptor(CA1501RuleId, + s_localizableTitleCA1501, + s_localizableMessageCA1501, + DiagnosticCategory.Maintainability, + DiagnosticHelpers.DefaultDiagnosticSeverity, + isEnabledByDefault: false, + description: s_localizableDescriptionCA1501, + helpLinkUri: "https://docs.microsoft.com/en-us/visualstudio/code-quality/ca1501-avoid-excessive-inheritance", + customTags: WellKnownDiagnosticTags.Telemetry); + + internal static DiagnosticDescriptor CA1502Rule = new DiagnosticDescriptor(CA1502RuleId, + s_localizableTitleCA1502, + s_localizableMessageCA1502, + DiagnosticCategory.Maintainability, + DiagnosticHelpers.DefaultDiagnosticSeverity, + isEnabledByDefault: false, + description: s_localizableDescriptionCA1502, + helpLinkUri: "https://docs.microsoft.com/en-us/visualstudio/code-quality/ca1502-avoid-excessive-complexity", + customTags: WellKnownDiagnosticTags.Telemetry); + + internal static DiagnosticDescriptor CA1505Rule = new DiagnosticDescriptor(CA1505RuleId, + s_localizableTitleCA1505, + s_localizableMessageCA1505, + DiagnosticCategory.Maintainability, + DiagnosticHelpers.DefaultDiagnosticSeverity, + isEnabledByDefault: false, + description: s_localizableDescriptionCA1505, + helpLinkUri: "https://docs.microsoft.com/en-us/visualstudio/code-quality/ca1505-avoid-unmaintainable-code", + customTags: WellKnownDiagnosticTags.Telemetry); + + internal static DiagnosticDescriptor CA1506Rule = new DiagnosticDescriptor(CA1506RuleId, + s_localizableTitleCA1506, + s_localizableMessageCA1506, + DiagnosticCategory.Maintainability, + DiagnosticHelpers.DefaultDiagnosticSeverity, + isEnabledByDefault: false, + description: s_localizableDescriptionCA1506, + helpLinkUri: "https://docs.microsoft.com/en-us/visualstudio/code-quality/ca1506-avoid-excessive-class-coupling", + customTags: WellKnownDiagnosticTags.Telemetry); + + internal static DiagnosticDescriptor InvalidEntryInCodeMetricsConfigFileRule = new DiagnosticDescriptor(CA1508RuleId, + s_localizableTitleCA1508, + s_localizableMessageCA1508, + DiagnosticCategory.Maintainability, + DiagnosticHelpers.DefaultDiagnosticSeverity, + isEnabledByDefault: false, + description: s_localizableDescriptionCA1508, + helpLinkUri: null, // TODO: Add help link + customTags: WellKnownDiagnosticTags.Telemetry); + + public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(CA1501Rule, CA1502Rule, CA1505Rule, CA1506Rule, InvalidEntryInCodeMetricsConfigFileRule); + + public override void Initialize(AnalysisContext analysisContext) + { + analysisContext.EnableConcurrentExecution(); + analysisContext.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + + analysisContext.RegisterCompilationAction(compilationContext => + { + // Try read the additional file containing the code metrics configuration. + if (!TryGetRuleIdToThresholdMap( + compilationContext.Options.AdditionalFiles, + compilationContext.CancellationToken, + out AdditionalText additionalTextOpt, + out ImmutableDictionary> ruleIdToThresholdMap, + out List invalidFileDiagnostics) && + invalidFileDiagnostics != null) + { + // Report any invalid additional file diagnostics. + foreach (var diagnostic in invalidFileDiagnostics) + { + compilationContext.ReportDiagnostic(diagnostic); + } + + return; + } + + // Compute code metrics. + var computeTask = CodeAnalysisMetricData.ComputeAsync(compilationContext.Compilation, compilationContext.CancellationToken); + computeTask.Wait(compilationContext.CancellationToken); + + // Analyze code metrics tree and report diagnostics. + analyzeMetricsData(computeTask.Result); + + void analyzeMetricsData(CodeAnalysisMetricData codeAnalysisMetricData) + { + var symbol = codeAnalysisMetricData.Symbol; + + // CA1501: Avoid excessive inheritance + if (symbol.Kind == SymbolKind.NamedType && codeAnalysisMetricData.DepthOfInheritance.HasValue) + { + uint? inheritanceThreshold = getThreshold(CA1501RuleId, symbol.Kind); + if (inheritanceThreshold.HasValue && codeAnalysisMetricData.DepthOfInheritance.Value > inheritanceThreshold.Value) + { + // '{0}' has an object hierarchy '{1}' levels deep within the defining module. If possible, eliminate base classes within the hierarchy to decrease its hierarchy level below '{2}': '{3}' + var arg1 = symbol.Name; + var arg2 = codeAnalysisMetricData.DepthOfInheritance; + var arg3 = inheritanceThreshold + 1; + var arg4 = string.Join(", ", ((INamedTypeSymbol)symbol).GetBaseTypes().Select(t => t.Name)); + var diagnostic = symbol.CreateDiagnostic(CA1501Rule, arg1, arg2, arg3, arg4); + compilationContext.ReportDiagnostic(diagnostic); + } + } + + // CA1502: Avoid excessive complexity + uint? complexityThreshold = getThreshold(CA1502RuleId, symbol.Kind); + if (complexityThreshold.HasValue && codeAnalysisMetricData.CyclomaticComplexity > complexityThreshold.Value) + { + // '{0}' has a cyclomatic complexity of '{1}'. Rewrite or refactor the code to decrease its complexity below '{2}'. + var arg1 = symbol.Name; + var arg2 = codeAnalysisMetricData.CyclomaticComplexity; + var arg3 = complexityThreshold.Value + 1; + var diagnostic = symbol.CreateDiagnostic(CA1502Rule, arg1, arg2, arg3); + compilationContext.ReportDiagnostic(diagnostic); + } + + // CA1505: Avoid unmaintainable code + uint? maintainabilityIndexThreshold = getThreshold(CA1505RuleId, symbol.Kind); + if (maintainabilityIndexThreshold.HasValue && maintainabilityIndexThreshold.Value > codeAnalysisMetricData.MaintainabilityIndex) + { + // '{0}' has a maintainability index of '{1}'. Rewrite or refactor the code to increase its maintainability index (MI) above '{2}'. + var arg1 = symbol.Name; + var arg2 = codeAnalysisMetricData.MaintainabilityIndex; + var arg3 = maintainabilityIndexThreshold.Value - 1; + var diagnostic = symbol.CreateDiagnostic(CA1505Rule, arg1, arg2, arg3); + compilationContext.ReportDiagnostic(diagnostic); + } + + // CA1506: Avoid excessive class coupling + uint? classCouplingThreshold = getThreshold(CA1506RuleId, symbol.Kind); + if (classCouplingThreshold.HasValue && codeAnalysisMetricData.CoupledNamedTypes.Count > classCouplingThreshold.Value) + { + // '{0}' is coupled with '{1}' different types from '{2}' different namespaces. Rewrite or refactor the code to decrease its class coupling below '{3}'. + var arg1 = symbol.Name; + var arg2 = codeAnalysisMetricData.CoupledNamedTypes.Count; + var arg3 = GetDistinctContainingNamespacesCount(codeAnalysisMetricData.CoupledNamedTypes); + var arg4 = classCouplingThreshold.Value + 1; + var diagnostic = symbol.CreateDiagnostic(CA1506Rule, arg1, arg2, arg3, arg4); + compilationContext.ReportDiagnostic(diagnostic); + } + + foreach (var child in codeAnalysisMetricData.Children) + { + analyzeMetricsData(child); + } + } + + uint? getThreshold(string ruleId, SymbolKind symbolKind) + { + // Check if we have custom threshold value for the given ruleId and symbolKind. + if (ruleIdToThresholdMap != null && + ruleIdToThresholdMap.TryGetValue(ruleId, out IReadOnlyList<(SymbolKind? symbolKindOpt, uint threshold)> values)) + { + foreach ((SymbolKind? symbolKindOpt, uint threshold) in values) + { + if (symbolKindOpt.HasValue && symbolKindOpt.Value == symbolKind) + { + return threshold; + } + } + + if (values.Count == 1 && + values[0].symbolKindOpt == null && + isApplicableByDefault(ruleId, symbolKind)) + { + return values[0].threshold; + } + } + + return getDefaultThreshold(ruleId, symbolKind); + } + + bool isApplicableByDefault(string ruleId, SymbolKind symbolKind) + { + switch (ruleId) + { + case CA1501RuleId: + return symbolKind == SymbolKind.NamedType; + + case CA1502RuleId: + return symbolKind == SymbolKind.Method; + + case CA1505RuleId: + switch (symbolKind) + { + case SymbolKind.NamedType: + case SymbolKind.Method: + case SymbolKind.Field: + case SymbolKind.Property: + case SymbolKind.Event: + return true; + + default: + return false; + } + + case CA1506RuleId: + switch (symbolKind) + { + case SymbolKind.NamedType: + case SymbolKind.Method: + case SymbolKind.Field: + case SymbolKind.Property: + case SymbolKind.Event: + return true; + + default: + return false; + } + + default: + throw new NotImplementedException(); + } + } + + uint? getDefaultThreshold(string ruleId, SymbolKind symbolKind) + { + if (!isApplicableByDefault(ruleId, symbolKind)) + { + return null; + } + + // Compat: we match the default threshold values for old FxCop implementation. + switch (ruleId) + { + case CA1501RuleId: + return 5; + + case CA1502RuleId: + return 25; + + case CA1505RuleId: + return 10; + + case CA1506RuleId: + return symbolKind == SymbolKind.NamedType ? 95 : (uint)40; + + default: + throw new NotImplementedException(); + } + } + }); + } + + private static bool TryGetRuleIdToThresholdMap( + ImmutableArray additionalFiles, + CancellationToken cancellationToken, + out AdditionalText additionalText, + out ImmutableDictionary> ruleIdToThresholdMap, + out List invalidFileDiagnostics) + { + invalidFileDiagnostics = null; + ruleIdToThresholdMap = null; + + // Parse the additional file for code metrics configuration. + // Return false if there is no such additional file or it contains at least one invalid entry. + additionalText = TryGetCodeMetricsConfigurationFile(additionalFiles, cancellationToken); + return additionalText != null && + TryParseCodeMetricsConfigurationFile(additionalText, cancellationToken, out ruleIdToThresholdMap, out invalidFileDiagnostics); + } + + private static AdditionalText TryGetCodeMetricsConfigurationFile(ImmutableArray additionalFiles, CancellationToken cancellationToken) + { + StringComparer comparer = StringComparer.Ordinal; + foreach (AdditionalText textFile in additionalFiles) + { + cancellationToken.ThrowIfCancellationRequested(); + + string fileName = Path.GetFileName(textFile.Path); + if (comparer.Equals(fileName, CodeMetricsConfigurationFile)) + { + return textFile; + } + } + + return null; + } + + private static bool TryParseCodeMetricsConfigurationFile( + AdditionalText additionalText, + CancellationToken cancellationToken, + out ImmutableDictionary> ruleIdToThresholdMap, + out List invalidFileDiagnostics) + { + // Parse the additional file with Metric rule ID (which may contain an optional parenthesized SymbolKind suffix) and custom threshold. + // # FORMAT: + // # 'RuleId'(Optional 'SymbolKind'): 'Threshold' + + ruleIdToThresholdMap = null; + invalidFileDiagnostics = null; + + var builder = ImmutableDictionary.CreateBuilder>(StringComparer.OrdinalIgnoreCase); + var lines = additionalText.GetText(cancellationToken).Lines; + foreach (var line in lines) + { + var contents = line.ToString().Trim(); + if (contents.Length == 0 || contents.StartsWith("#", StringComparison.Ordinal)) + { + // Ignore empty lines and comments. + continue; + } + + var parts = contents.Split(':'); + for (int i = 0; i < parts.Length; i++) + { + parts[i] = parts[i].Trim(); + } + + var isInvalidLine = false; + string key = parts[0]; + if (parts.Length != 2 || // We require exactly one ':' separator in the line. + key.Any(char.IsWhiteSpace) || // We do not allow white spaces in rule name. + !uint.TryParse(parts[1], out uint threshold)) // Value must be a non-negative integral threshold. + { + isInvalidLine = true; + } + else + { + SymbolKind? symbolKindOpt = null; + string[] keyParts = key.Split('('); + switch (keyParts[0]) + { + case CA1501RuleId: + case CA1502RuleId: + case CA1505RuleId: + case CA1506RuleId: + break; + + default: + isInvalidLine = true; + break; + } + + if (!isInvalidLine && keyParts.Length > 1) + { + if (keyParts.Length > 2 || + keyParts[1].Length == 0 || + keyParts[1].Last() != ')') + { + isInvalidLine = true; + } + else + { + // Remove the trailing ')' + var symbolKindStr = keyParts[1].Substring(0, keyParts[1].Length - 1); + switch (symbolKindStr) + { + case "Assembly": + symbolKindOpt = SymbolKind.Assembly; + break; + case "Namespace": + symbolKindOpt = SymbolKind.Namespace; + break; + case "Type": + symbolKindOpt = SymbolKind.NamedType; + break; + case "Method": + symbolKindOpt = SymbolKind.Method; + break; + case "Field": + symbolKindOpt = SymbolKind.Field; + break; + case "Event": + symbolKindOpt = SymbolKind.Event; + break; + case "Property": + symbolKindOpt = SymbolKind.Property; + break; + + default: + isInvalidLine = true; + break; + } + } + } + + if (!isInvalidLine) + { + if (!builder.TryGetValue(keyParts[0], out var values)) + { + values = new List<(SymbolKind?, uint)>(); + builder.Add(keyParts[0], values); + } + + ((List<(SymbolKind?, uint)>)values).Add((symbolKindOpt, threshold)); + } + } + + if (isInvalidLine) + { + // Invalid entry '{0}' in code metrics rule specification file '{1}'. + string arg1 = contents; + string arg2 = Path.GetFileName(additionalText.Path); + LinePositionSpan linePositionSpan = lines.GetLinePositionSpan(line.Span); + Location location = Location.Create(additionalText.Path, line.Span, linePositionSpan); + invalidFileDiagnostics = invalidFileDiagnostics ?? new List(); + var diagnostic = Diagnostic.Create(InvalidEntryInCodeMetricsConfigFileRule, location, arg1, arg2); + invalidFileDiagnostics.Add(diagnostic); + } + } + + ruleIdToThresholdMap = builder.ToImmutable(); + return invalidFileDiagnostics == null; + } + + private static int GetDistinctContainingNamespacesCount(IEnumerable namedTypes) + { + var distinctNamespaces = new HashSet(); + foreach (var namedType in namedTypes) + { + if (namedType.ContainingNamespace != null) + { + distinctNamespaces.Add(namedType.ContainingNamespace); + } + } + + return distinctNamespaces.Count; + } + } +} \ No newline at end of file diff --git a/src/Microsoft.CodeQuality.Analyzers/Core/Maintainability/MicrosoftMaintainabilityAnalyzersResources.resx b/src/Microsoft.CodeQuality.Analyzers/Core/Maintainability/MicrosoftMaintainabilityAnalyzersResources.resx index 6bcd798231..3cbdc2a628 100644 --- a/src/Microsoft.CodeQuality.Analyzers/Core/Maintainability/MicrosoftMaintainabilityAnalyzersResources.resx +++ b/src/Microsoft.CodeQuality.Analyzers/Core/Maintainability/MicrosoftMaintainabilityAnalyzersResources.resx @@ -189,4 +189,54 @@ Use nameof to express symbol names + + This rule measures class coupling by counting the number of unique type references that a symbol contains. Symbols that have a high degree of class coupling can be difficult to maintain. It is a good practice to have types and methods that exhibit low coupling and high cohesion. To fix this violation, try to redesign the code to reduce the number of types to which it is coupled. + + + '{0}' is coupled with '{1}' different types from '{2}' different namespaces. Rewrite or refactor the code to decrease its class coupling below '{3}'. + + + Avoid excessive class coupling + + + Cyclomatic complexity measures the number of linearly independent paths through the method, which is determined by the number and complexity of conditional branches. A low cyclomatic complexity generally indicates a method that is easy to understand, test, and maintain. The cyclomatic complexity is calculated from a control flow graph of the method and is given as follows: + +cyclomatic complexity = the number of edges - the number of nodes + 1 + +where a node represents a logic branch point and an edge represents a line between nodes. + + + '{0}' has a cyclomatic complexity of '{1}'. Rewrite or refactor the code to decrease its complexity below '{2}'. + + + Avoid excessive complexity + + + Deeply nested type hierarchies can be difficult to follow, understand, and maintain. This rule limits analysis to hierarchies in the same module. To fix a violation of this rule, derive the type from a base type that is less deep in the inheritance hierarchy or eliminate some of the intermediate base types. + + + '{0}' has an object hierarchy '{1}' levels deep within the defining module. If possible, eliminate base classes within the hierarchy to decrease its hierarchy level below '{2}': '{3}' + + + Avoid excessive inheritance + + + The maintainability index is calculated by using the following metrics: lines of code, program volume, and cyclomatic complexity. Program volume is a measure of the difficulty of understanding of a symbol that is based on the number of operators and operands in the code. Cyclomatic complexity is a measure of the structural complexity of the type or method. +A low maintainability index indicates that code is probably difficult to maintain and would be a good candidate to redesign. + + + '{0}' has a maintainability index of '{1}'. Rewrite or refactor the code to increase its maintainability index (MI) above '{2}'. + + + Avoid unmaintainable code + + + Invalid entry in code metrics rule specification file + + + Invalid entry '{0}' in code metrics rule specification file '{1}' + + + Invalid entry in code metrics rule specification file + \ No newline at end of file diff --git a/src/Microsoft.CodeQuality.Analyzers/Core/Maintainability/xlf/MicrosoftMaintainabilityAnalyzersResources.cs.xlf b/src/Microsoft.CodeQuality.Analyzers/Core/Maintainability/xlf/MicrosoftMaintainabilityAnalyzersResources.cs.xlf index 48369ae37e..5c3321e98c 100644 --- a/src/Microsoft.CodeQuality.Analyzers/Core/Maintainability/xlf/MicrosoftMaintainabilityAnalyzersResources.cs.xlf +++ b/src/Microsoft.CodeQuality.Analyzers/Core/Maintainability/xlf/MicrosoftMaintainabilityAnalyzersResources.cs.xlf @@ -2,6 +2,91 @@ + + This rule measures class coupling by counting the number of unique type references that a symbol contains. Symbols that have a high degree of class coupling can be difficult to maintain. It is a good practice to have types and methods that exhibit low coupling and high cohesion. To fix this violation, try to redesign the code to reduce the number of types to which it is coupled. + This rule measures class coupling by counting the number of unique type references that a symbol contains. Symbols that have a high degree of class coupling can be difficult to maintain. It is a good practice to have types and methods that exhibit low coupling and high cohesion. To fix this violation, try to redesign the code to reduce the number of types to which it is coupled. + + + + '{0}' is coupled with '{1}' different types from '{2}' different namespaces. Rewrite or refactor the code to decrease its class coupling below '{3}'. + '{0}' is coupled with '{1}' different types from '{2}' different namespaces. Rewrite or refactor the code to decrease its class coupling below '{3}'. + + + + Avoid excessive class coupling + Avoid excessive class coupling + + + + Cyclomatic complexity measures the number of linearly independent paths through the method, which is determined by the number and complexity of conditional branches. A low cyclomatic complexity generally indicates a method that is easy to understand, test, and maintain. The cyclomatic complexity is calculated from a control flow graph of the method and is given as follows: + +cyclomatic complexity = the number of edges - the number of nodes + 1 + +where a node represents a logic branch point and an edge represents a line between nodes. + Cyclomatic complexity measures the number of linearly independent paths through the method, which is determined by the number and complexity of conditional branches. A low cyclomatic complexity generally indicates a method that is easy to understand, test, and maintain. The cyclomatic complexity is calculated from a control flow graph of the method and is given as follows: + +cyclomatic complexity = the number of edges - the number of nodes + 1 + +where a node represents a logic branch point and an edge represents a line between nodes. + + + + '{0}' has a cyclomatic complexity of '{1}'. Rewrite or refactor the code to decrease its complexity below '{2}'. + '{0}' has a cyclomatic complexity of '{1}'. Rewrite or refactor the code to decrease its complexity below '{2}'. + + + + Avoid excessive complexity + Avoid excessive complexity + + + + Deeply nested type hierarchies can be difficult to follow, understand, and maintain. This rule limits analysis to hierarchies in the same module. To fix a violation of this rule, derive the type from a base type that is less deep in the inheritance hierarchy or eliminate some of the intermediate base types. + Deeply nested type hierarchies can be difficult to follow, understand, and maintain. This rule limits analysis to hierarchies in the same module. To fix a violation of this rule, derive the type from a base type that is less deep in the inheritance hierarchy or eliminate some of the intermediate base types. + + + + '{0}' has an object hierarchy '{1}' levels deep within the defining module. If possible, eliminate base classes within the hierarchy to decrease its hierarchy level below '{2}': '{3}' + '{0}' has an object hierarchy '{1}' levels deep within the defining module. If possible, eliminate base classes within the hierarchy to decrease its hierarchy level below '{2}': '{3}' + + + + Avoid excessive inheritance + Avoid excessive inheritance + + + + The maintainability index is calculated by using the following metrics: lines of code, program volume, and cyclomatic complexity. Program volume is a measure of the difficulty of understanding of a symbol that is based on the number of operators and operands in the code. Cyclomatic complexity is a measure of the structural complexity of the type or method. +A low maintainability index indicates that code is probably difficult to maintain and would be a good candidate to redesign. + The maintainability index is calculated by using the following metrics: lines of code, program volume, and cyclomatic complexity. Program volume is a measure of the difficulty of understanding of a symbol that is based on the number of operators and operands in the code. Cyclomatic complexity is a measure of the structural complexity of the type or method. +A low maintainability index indicates that code is probably difficult to maintain and would be a good candidate to redesign. + + + + '{0}' has a maintainability index of '{1}'. Rewrite or refactor the code to increase its maintainability index (MI) above '{2}'. + '{0}' has a maintainability index of '{1}'. Rewrite or refactor the code to increase its maintainability index (MI) above '{2}'. + + + + Avoid unmaintainable code + Avoid unmaintainable code + + + + Invalid entry in code metrics rule specification file + Invalid entry in code metrics rule specification file + + + + Invalid entry '{0}' in code metrics rule specification file '{1}' + Invalid entry '{0}' in code metrics rule specification file '{1}' + + + + Invalid entry in code metrics rule specification file + Invalid entry in code metrics rule specification file + + Variable names should not match field names Názvy proměnných se nemají shodovat s názvy polí. diff --git a/src/Microsoft.CodeQuality.Analyzers/Core/Maintainability/xlf/MicrosoftMaintainabilityAnalyzersResources.de.xlf b/src/Microsoft.CodeQuality.Analyzers/Core/Maintainability/xlf/MicrosoftMaintainabilityAnalyzersResources.de.xlf index 09c083c419..e6e836bb59 100644 --- a/src/Microsoft.CodeQuality.Analyzers/Core/Maintainability/xlf/MicrosoftMaintainabilityAnalyzersResources.de.xlf +++ b/src/Microsoft.CodeQuality.Analyzers/Core/Maintainability/xlf/MicrosoftMaintainabilityAnalyzersResources.de.xlf @@ -2,6 +2,91 @@ + + This rule measures class coupling by counting the number of unique type references that a symbol contains. Symbols that have a high degree of class coupling can be difficult to maintain. It is a good practice to have types and methods that exhibit low coupling and high cohesion. To fix this violation, try to redesign the code to reduce the number of types to which it is coupled. + This rule measures class coupling by counting the number of unique type references that a symbol contains. Symbols that have a high degree of class coupling can be difficult to maintain. It is a good practice to have types and methods that exhibit low coupling and high cohesion. To fix this violation, try to redesign the code to reduce the number of types to which it is coupled. + + + + '{0}' is coupled with '{1}' different types from '{2}' different namespaces. Rewrite or refactor the code to decrease its class coupling below '{3}'. + '{0}' is coupled with '{1}' different types from '{2}' different namespaces. Rewrite or refactor the code to decrease its class coupling below '{3}'. + + + + Avoid excessive class coupling + Avoid excessive class coupling + + + + Cyclomatic complexity measures the number of linearly independent paths through the method, which is determined by the number and complexity of conditional branches. A low cyclomatic complexity generally indicates a method that is easy to understand, test, and maintain. The cyclomatic complexity is calculated from a control flow graph of the method and is given as follows: + +cyclomatic complexity = the number of edges - the number of nodes + 1 + +where a node represents a logic branch point and an edge represents a line between nodes. + Cyclomatic complexity measures the number of linearly independent paths through the method, which is determined by the number and complexity of conditional branches. A low cyclomatic complexity generally indicates a method that is easy to understand, test, and maintain. The cyclomatic complexity is calculated from a control flow graph of the method and is given as follows: + +cyclomatic complexity = the number of edges - the number of nodes + 1 + +where a node represents a logic branch point and an edge represents a line between nodes. + + + + '{0}' has a cyclomatic complexity of '{1}'. Rewrite or refactor the code to decrease its complexity below '{2}'. + '{0}' has a cyclomatic complexity of '{1}'. Rewrite or refactor the code to decrease its complexity below '{2}'. + + + + Avoid excessive complexity + Avoid excessive complexity + + + + Deeply nested type hierarchies can be difficult to follow, understand, and maintain. This rule limits analysis to hierarchies in the same module. To fix a violation of this rule, derive the type from a base type that is less deep in the inheritance hierarchy or eliminate some of the intermediate base types. + Deeply nested type hierarchies can be difficult to follow, understand, and maintain. This rule limits analysis to hierarchies in the same module. To fix a violation of this rule, derive the type from a base type that is less deep in the inheritance hierarchy or eliminate some of the intermediate base types. + + + + '{0}' has an object hierarchy '{1}' levels deep within the defining module. If possible, eliminate base classes within the hierarchy to decrease its hierarchy level below '{2}': '{3}' + '{0}' has an object hierarchy '{1}' levels deep within the defining module. If possible, eliminate base classes within the hierarchy to decrease its hierarchy level below '{2}': '{3}' + + + + Avoid excessive inheritance + Avoid excessive inheritance + + + + The maintainability index is calculated by using the following metrics: lines of code, program volume, and cyclomatic complexity. Program volume is a measure of the difficulty of understanding of a symbol that is based on the number of operators and operands in the code. Cyclomatic complexity is a measure of the structural complexity of the type or method. +A low maintainability index indicates that code is probably difficult to maintain and would be a good candidate to redesign. + The maintainability index is calculated by using the following metrics: lines of code, program volume, and cyclomatic complexity. Program volume is a measure of the difficulty of understanding of a symbol that is based on the number of operators and operands in the code. Cyclomatic complexity is a measure of the structural complexity of the type or method. +A low maintainability index indicates that code is probably difficult to maintain and would be a good candidate to redesign. + + + + '{0}' has a maintainability index of '{1}'. Rewrite or refactor the code to increase its maintainability index (MI) above '{2}'. + '{0}' has a maintainability index of '{1}'. Rewrite or refactor the code to increase its maintainability index (MI) above '{2}'. + + + + Avoid unmaintainable code + Avoid unmaintainable code + + + + Invalid entry in code metrics rule specification file + Invalid entry in code metrics rule specification file + + + + Invalid entry '{0}' in code metrics rule specification file '{1}' + Invalid entry '{0}' in code metrics rule specification file '{1}' + + + + Invalid entry in code metrics rule specification file + Invalid entry in code metrics rule specification file + + Variable names should not match field names Variablennamen dürfen nicht mit Feldnamen übereinstimmen diff --git a/src/Microsoft.CodeQuality.Analyzers/Core/Maintainability/xlf/MicrosoftMaintainabilityAnalyzersResources.es.xlf b/src/Microsoft.CodeQuality.Analyzers/Core/Maintainability/xlf/MicrosoftMaintainabilityAnalyzersResources.es.xlf index ebdcea76f5..1a5d4b8652 100644 --- a/src/Microsoft.CodeQuality.Analyzers/Core/Maintainability/xlf/MicrosoftMaintainabilityAnalyzersResources.es.xlf +++ b/src/Microsoft.CodeQuality.Analyzers/Core/Maintainability/xlf/MicrosoftMaintainabilityAnalyzersResources.es.xlf @@ -2,6 +2,91 @@ + + This rule measures class coupling by counting the number of unique type references that a symbol contains. Symbols that have a high degree of class coupling can be difficult to maintain. It is a good practice to have types and methods that exhibit low coupling and high cohesion. To fix this violation, try to redesign the code to reduce the number of types to which it is coupled. + This rule measures class coupling by counting the number of unique type references that a symbol contains. Symbols that have a high degree of class coupling can be difficult to maintain. It is a good practice to have types and methods that exhibit low coupling and high cohesion. To fix this violation, try to redesign the code to reduce the number of types to which it is coupled. + + + + '{0}' is coupled with '{1}' different types from '{2}' different namespaces. Rewrite or refactor the code to decrease its class coupling below '{3}'. + '{0}' is coupled with '{1}' different types from '{2}' different namespaces. Rewrite or refactor the code to decrease its class coupling below '{3}'. + + + + Avoid excessive class coupling + Avoid excessive class coupling + + + + Cyclomatic complexity measures the number of linearly independent paths through the method, which is determined by the number and complexity of conditional branches. A low cyclomatic complexity generally indicates a method that is easy to understand, test, and maintain. The cyclomatic complexity is calculated from a control flow graph of the method and is given as follows: + +cyclomatic complexity = the number of edges - the number of nodes + 1 + +where a node represents a logic branch point and an edge represents a line between nodes. + Cyclomatic complexity measures the number of linearly independent paths through the method, which is determined by the number and complexity of conditional branches. A low cyclomatic complexity generally indicates a method that is easy to understand, test, and maintain. The cyclomatic complexity is calculated from a control flow graph of the method and is given as follows: + +cyclomatic complexity = the number of edges - the number of nodes + 1 + +where a node represents a logic branch point and an edge represents a line between nodes. + + + + '{0}' has a cyclomatic complexity of '{1}'. Rewrite or refactor the code to decrease its complexity below '{2}'. + '{0}' has a cyclomatic complexity of '{1}'. Rewrite or refactor the code to decrease its complexity below '{2}'. + + + + Avoid excessive complexity + Avoid excessive complexity + + + + Deeply nested type hierarchies can be difficult to follow, understand, and maintain. This rule limits analysis to hierarchies in the same module. To fix a violation of this rule, derive the type from a base type that is less deep in the inheritance hierarchy or eliminate some of the intermediate base types. + Deeply nested type hierarchies can be difficult to follow, understand, and maintain. This rule limits analysis to hierarchies in the same module. To fix a violation of this rule, derive the type from a base type that is less deep in the inheritance hierarchy or eliminate some of the intermediate base types. + + + + '{0}' has an object hierarchy '{1}' levels deep within the defining module. If possible, eliminate base classes within the hierarchy to decrease its hierarchy level below '{2}': '{3}' + '{0}' has an object hierarchy '{1}' levels deep within the defining module. If possible, eliminate base classes within the hierarchy to decrease its hierarchy level below '{2}': '{3}' + + + + Avoid excessive inheritance + Avoid excessive inheritance + + + + The maintainability index is calculated by using the following metrics: lines of code, program volume, and cyclomatic complexity. Program volume is a measure of the difficulty of understanding of a symbol that is based on the number of operators and operands in the code. Cyclomatic complexity is a measure of the structural complexity of the type or method. +A low maintainability index indicates that code is probably difficult to maintain and would be a good candidate to redesign. + The maintainability index is calculated by using the following metrics: lines of code, program volume, and cyclomatic complexity. Program volume is a measure of the difficulty of understanding of a symbol that is based on the number of operators and operands in the code. Cyclomatic complexity is a measure of the structural complexity of the type or method. +A low maintainability index indicates that code is probably difficult to maintain and would be a good candidate to redesign. + + + + '{0}' has a maintainability index of '{1}'. Rewrite or refactor the code to increase its maintainability index (MI) above '{2}'. + '{0}' has a maintainability index of '{1}'. Rewrite or refactor the code to increase its maintainability index (MI) above '{2}'. + + + + Avoid unmaintainable code + Avoid unmaintainable code + + + + Invalid entry in code metrics rule specification file + Invalid entry in code metrics rule specification file + + + + Invalid entry '{0}' in code metrics rule specification file '{1}' + Invalid entry '{0}' in code metrics rule specification file '{1}' + + + + Invalid entry in code metrics rule specification file + Invalid entry in code metrics rule specification file + + Variable names should not match field names Los nombres de las variables no deben coincidir con los nombres de los campos diff --git a/src/Microsoft.CodeQuality.Analyzers/Core/Maintainability/xlf/MicrosoftMaintainabilityAnalyzersResources.fr.xlf b/src/Microsoft.CodeQuality.Analyzers/Core/Maintainability/xlf/MicrosoftMaintainabilityAnalyzersResources.fr.xlf index 3b504318b3..ede5c0c913 100644 --- a/src/Microsoft.CodeQuality.Analyzers/Core/Maintainability/xlf/MicrosoftMaintainabilityAnalyzersResources.fr.xlf +++ b/src/Microsoft.CodeQuality.Analyzers/Core/Maintainability/xlf/MicrosoftMaintainabilityAnalyzersResources.fr.xlf @@ -2,6 +2,91 @@ + + This rule measures class coupling by counting the number of unique type references that a symbol contains. Symbols that have a high degree of class coupling can be difficult to maintain. It is a good practice to have types and methods that exhibit low coupling and high cohesion. To fix this violation, try to redesign the code to reduce the number of types to which it is coupled. + This rule measures class coupling by counting the number of unique type references that a symbol contains. Symbols that have a high degree of class coupling can be difficult to maintain. It is a good practice to have types and methods that exhibit low coupling and high cohesion. To fix this violation, try to redesign the code to reduce the number of types to which it is coupled. + + + + '{0}' is coupled with '{1}' different types from '{2}' different namespaces. Rewrite or refactor the code to decrease its class coupling below '{3}'. + '{0}' is coupled with '{1}' different types from '{2}' different namespaces. Rewrite or refactor the code to decrease its class coupling below '{3}'. + + + + Avoid excessive class coupling + Avoid excessive class coupling + + + + Cyclomatic complexity measures the number of linearly independent paths through the method, which is determined by the number and complexity of conditional branches. A low cyclomatic complexity generally indicates a method that is easy to understand, test, and maintain. The cyclomatic complexity is calculated from a control flow graph of the method and is given as follows: + +cyclomatic complexity = the number of edges - the number of nodes + 1 + +where a node represents a logic branch point and an edge represents a line between nodes. + Cyclomatic complexity measures the number of linearly independent paths through the method, which is determined by the number and complexity of conditional branches. A low cyclomatic complexity generally indicates a method that is easy to understand, test, and maintain. The cyclomatic complexity is calculated from a control flow graph of the method and is given as follows: + +cyclomatic complexity = the number of edges - the number of nodes + 1 + +where a node represents a logic branch point and an edge represents a line between nodes. + + + + '{0}' has a cyclomatic complexity of '{1}'. Rewrite or refactor the code to decrease its complexity below '{2}'. + '{0}' has a cyclomatic complexity of '{1}'. Rewrite or refactor the code to decrease its complexity below '{2}'. + + + + Avoid excessive complexity + Avoid excessive complexity + + + + Deeply nested type hierarchies can be difficult to follow, understand, and maintain. This rule limits analysis to hierarchies in the same module. To fix a violation of this rule, derive the type from a base type that is less deep in the inheritance hierarchy or eliminate some of the intermediate base types. + Deeply nested type hierarchies can be difficult to follow, understand, and maintain. This rule limits analysis to hierarchies in the same module. To fix a violation of this rule, derive the type from a base type that is less deep in the inheritance hierarchy or eliminate some of the intermediate base types. + + + + '{0}' has an object hierarchy '{1}' levels deep within the defining module. If possible, eliminate base classes within the hierarchy to decrease its hierarchy level below '{2}': '{3}' + '{0}' has an object hierarchy '{1}' levels deep within the defining module. If possible, eliminate base classes within the hierarchy to decrease its hierarchy level below '{2}': '{3}' + + + + Avoid excessive inheritance + Avoid excessive inheritance + + + + The maintainability index is calculated by using the following metrics: lines of code, program volume, and cyclomatic complexity. Program volume is a measure of the difficulty of understanding of a symbol that is based on the number of operators and operands in the code. Cyclomatic complexity is a measure of the structural complexity of the type or method. +A low maintainability index indicates that code is probably difficult to maintain and would be a good candidate to redesign. + The maintainability index is calculated by using the following metrics: lines of code, program volume, and cyclomatic complexity. Program volume is a measure of the difficulty of understanding of a symbol that is based on the number of operators and operands in the code. Cyclomatic complexity is a measure of the structural complexity of the type or method. +A low maintainability index indicates that code is probably difficult to maintain and would be a good candidate to redesign. + + + + '{0}' has a maintainability index of '{1}'. Rewrite or refactor the code to increase its maintainability index (MI) above '{2}'. + '{0}' has a maintainability index of '{1}'. Rewrite or refactor the code to increase its maintainability index (MI) above '{2}'. + + + + Avoid unmaintainable code + Avoid unmaintainable code + + + + Invalid entry in code metrics rule specification file + Invalid entry in code metrics rule specification file + + + + Invalid entry '{0}' in code metrics rule specification file '{1}' + Invalid entry '{0}' in code metrics rule specification file '{1}' + + + + Invalid entry in code metrics rule specification file + Invalid entry in code metrics rule specification file + + Variable names should not match field names Les noms de variables ne doivent pas correspondre à des noms de champs diff --git a/src/Microsoft.CodeQuality.Analyzers/Core/Maintainability/xlf/MicrosoftMaintainabilityAnalyzersResources.it.xlf b/src/Microsoft.CodeQuality.Analyzers/Core/Maintainability/xlf/MicrosoftMaintainabilityAnalyzersResources.it.xlf index c83a9e071b..beacb0de2a 100644 --- a/src/Microsoft.CodeQuality.Analyzers/Core/Maintainability/xlf/MicrosoftMaintainabilityAnalyzersResources.it.xlf +++ b/src/Microsoft.CodeQuality.Analyzers/Core/Maintainability/xlf/MicrosoftMaintainabilityAnalyzersResources.it.xlf @@ -2,6 +2,91 @@ + + This rule measures class coupling by counting the number of unique type references that a symbol contains. Symbols that have a high degree of class coupling can be difficult to maintain. It is a good practice to have types and methods that exhibit low coupling and high cohesion. To fix this violation, try to redesign the code to reduce the number of types to which it is coupled. + This rule measures class coupling by counting the number of unique type references that a symbol contains. Symbols that have a high degree of class coupling can be difficult to maintain. It is a good practice to have types and methods that exhibit low coupling and high cohesion. To fix this violation, try to redesign the code to reduce the number of types to which it is coupled. + + + + '{0}' is coupled with '{1}' different types from '{2}' different namespaces. Rewrite or refactor the code to decrease its class coupling below '{3}'. + '{0}' is coupled with '{1}' different types from '{2}' different namespaces. Rewrite or refactor the code to decrease its class coupling below '{3}'. + + + + Avoid excessive class coupling + Avoid excessive class coupling + + + + Cyclomatic complexity measures the number of linearly independent paths through the method, which is determined by the number and complexity of conditional branches. A low cyclomatic complexity generally indicates a method that is easy to understand, test, and maintain. The cyclomatic complexity is calculated from a control flow graph of the method and is given as follows: + +cyclomatic complexity = the number of edges - the number of nodes + 1 + +where a node represents a logic branch point and an edge represents a line between nodes. + Cyclomatic complexity measures the number of linearly independent paths through the method, which is determined by the number and complexity of conditional branches. A low cyclomatic complexity generally indicates a method that is easy to understand, test, and maintain. The cyclomatic complexity is calculated from a control flow graph of the method and is given as follows: + +cyclomatic complexity = the number of edges - the number of nodes + 1 + +where a node represents a logic branch point and an edge represents a line between nodes. + + + + '{0}' has a cyclomatic complexity of '{1}'. Rewrite or refactor the code to decrease its complexity below '{2}'. + '{0}' has a cyclomatic complexity of '{1}'. Rewrite or refactor the code to decrease its complexity below '{2}'. + + + + Avoid excessive complexity + Avoid excessive complexity + + + + Deeply nested type hierarchies can be difficult to follow, understand, and maintain. This rule limits analysis to hierarchies in the same module. To fix a violation of this rule, derive the type from a base type that is less deep in the inheritance hierarchy or eliminate some of the intermediate base types. + Deeply nested type hierarchies can be difficult to follow, understand, and maintain. This rule limits analysis to hierarchies in the same module. To fix a violation of this rule, derive the type from a base type that is less deep in the inheritance hierarchy or eliminate some of the intermediate base types. + + + + '{0}' has an object hierarchy '{1}' levels deep within the defining module. If possible, eliminate base classes within the hierarchy to decrease its hierarchy level below '{2}': '{3}' + '{0}' has an object hierarchy '{1}' levels deep within the defining module. If possible, eliminate base classes within the hierarchy to decrease its hierarchy level below '{2}': '{3}' + + + + Avoid excessive inheritance + Avoid excessive inheritance + + + + The maintainability index is calculated by using the following metrics: lines of code, program volume, and cyclomatic complexity. Program volume is a measure of the difficulty of understanding of a symbol that is based on the number of operators and operands in the code. Cyclomatic complexity is a measure of the structural complexity of the type or method. +A low maintainability index indicates that code is probably difficult to maintain and would be a good candidate to redesign. + The maintainability index is calculated by using the following metrics: lines of code, program volume, and cyclomatic complexity. Program volume is a measure of the difficulty of understanding of a symbol that is based on the number of operators and operands in the code. Cyclomatic complexity is a measure of the structural complexity of the type or method. +A low maintainability index indicates that code is probably difficult to maintain and would be a good candidate to redesign. + + + + '{0}' has a maintainability index of '{1}'. Rewrite or refactor the code to increase its maintainability index (MI) above '{2}'. + '{0}' has a maintainability index of '{1}'. Rewrite or refactor the code to increase its maintainability index (MI) above '{2}'. + + + + Avoid unmaintainable code + Avoid unmaintainable code + + + + Invalid entry in code metrics rule specification file + Invalid entry in code metrics rule specification file + + + + Invalid entry '{0}' in code metrics rule specification file '{1}' + Invalid entry '{0}' in code metrics rule specification file '{1}' + + + + Invalid entry in code metrics rule specification file + Invalid entry in code metrics rule specification file + + Variable names should not match field names I nomi di variabile non devono corrispondere ai nomi di campo diff --git a/src/Microsoft.CodeQuality.Analyzers/Core/Maintainability/xlf/MicrosoftMaintainabilityAnalyzersResources.ja.xlf b/src/Microsoft.CodeQuality.Analyzers/Core/Maintainability/xlf/MicrosoftMaintainabilityAnalyzersResources.ja.xlf index 77a26f884b..a486b80f66 100644 --- a/src/Microsoft.CodeQuality.Analyzers/Core/Maintainability/xlf/MicrosoftMaintainabilityAnalyzersResources.ja.xlf +++ b/src/Microsoft.CodeQuality.Analyzers/Core/Maintainability/xlf/MicrosoftMaintainabilityAnalyzersResources.ja.xlf @@ -2,6 +2,91 @@ + + This rule measures class coupling by counting the number of unique type references that a symbol contains. Symbols that have a high degree of class coupling can be difficult to maintain. It is a good practice to have types and methods that exhibit low coupling and high cohesion. To fix this violation, try to redesign the code to reduce the number of types to which it is coupled. + This rule measures class coupling by counting the number of unique type references that a symbol contains. Symbols that have a high degree of class coupling can be difficult to maintain. It is a good practice to have types and methods that exhibit low coupling and high cohesion. To fix this violation, try to redesign the code to reduce the number of types to which it is coupled. + + + + '{0}' is coupled with '{1}' different types from '{2}' different namespaces. Rewrite or refactor the code to decrease its class coupling below '{3}'. + '{0}' is coupled with '{1}' different types from '{2}' different namespaces. Rewrite or refactor the code to decrease its class coupling below '{3}'. + + + + Avoid excessive class coupling + Avoid excessive class coupling + + + + Cyclomatic complexity measures the number of linearly independent paths through the method, which is determined by the number and complexity of conditional branches. A low cyclomatic complexity generally indicates a method that is easy to understand, test, and maintain. The cyclomatic complexity is calculated from a control flow graph of the method and is given as follows: + +cyclomatic complexity = the number of edges - the number of nodes + 1 + +where a node represents a logic branch point and an edge represents a line between nodes. + Cyclomatic complexity measures the number of linearly independent paths through the method, which is determined by the number and complexity of conditional branches. A low cyclomatic complexity generally indicates a method that is easy to understand, test, and maintain. The cyclomatic complexity is calculated from a control flow graph of the method and is given as follows: + +cyclomatic complexity = the number of edges - the number of nodes + 1 + +where a node represents a logic branch point and an edge represents a line between nodes. + + + + '{0}' has a cyclomatic complexity of '{1}'. Rewrite or refactor the code to decrease its complexity below '{2}'. + '{0}' has a cyclomatic complexity of '{1}'. Rewrite or refactor the code to decrease its complexity below '{2}'. + + + + Avoid excessive complexity + Avoid excessive complexity + + + + Deeply nested type hierarchies can be difficult to follow, understand, and maintain. This rule limits analysis to hierarchies in the same module. To fix a violation of this rule, derive the type from a base type that is less deep in the inheritance hierarchy or eliminate some of the intermediate base types. + Deeply nested type hierarchies can be difficult to follow, understand, and maintain. This rule limits analysis to hierarchies in the same module. To fix a violation of this rule, derive the type from a base type that is less deep in the inheritance hierarchy or eliminate some of the intermediate base types. + + + + '{0}' has an object hierarchy '{1}' levels deep within the defining module. If possible, eliminate base classes within the hierarchy to decrease its hierarchy level below '{2}': '{3}' + '{0}' has an object hierarchy '{1}' levels deep within the defining module. If possible, eliminate base classes within the hierarchy to decrease its hierarchy level below '{2}': '{3}' + + + + Avoid excessive inheritance + Avoid excessive inheritance + + + + The maintainability index is calculated by using the following metrics: lines of code, program volume, and cyclomatic complexity. Program volume is a measure of the difficulty of understanding of a symbol that is based on the number of operators and operands in the code. Cyclomatic complexity is a measure of the structural complexity of the type or method. +A low maintainability index indicates that code is probably difficult to maintain and would be a good candidate to redesign. + The maintainability index is calculated by using the following metrics: lines of code, program volume, and cyclomatic complexity. Program volume is a measure of the difficulty of understanding of a symbol that is based on the number of operators and operands in the code. Cyclomatic complexity is a measure of the structural complexity of the type or method. +A low maintainability index indicates that code is probably difficult to maintain and would be a good candidate to redesign. + + + + '{0}' has a maintainability index of '{1}'. Rewrite or refactor the code to increase its maintainability index (MI) above '{2}'. + '{0}' has a maintainability index of '{1}'. Rewrite or refactor the code to increase its maintainability index (MI) above '{2}'. + + + + Avoid unmaintainable code + Avoid unmaintainable code + + + + Invalid entry in code metrics rule specification file + Invalid entry in code metrics rule specification file + + + + Invalid entry '{0}' in code metrics rule specification file '{1}' + Invalid entry '{0}' in code metrics rule specification file '{1}' + + + + Invalid entry in code metrics rule specification file + Invalid entry in code metrics rule specification file + + Variable names should not match field names 変数名はフィールド名と同一にすることはできません diff --git a/src/Microsoft.CodeQuality.Analyzers/Core/Maintainability/xlf/MicrosoftMaintainabilityAnalyzersResources.ko.xlf b/src/Microsoft.CodeQuality.Analyzers/Core/Maintainability/xlf/MicrosoftMaintainabilityAnalyzersResources.ko.xlf index 120b084294..fc7c744c85 100644 --- a/src/Microsoft.CodeQuality.Analyzers/Core/Maintainability/xlf/MicrosoftMaintainabilityAnalyzersResources.ko.xlf +++ b/src/Microsoft.CodeQuality.Analyzers/Core/Maintainability/xlf/MicrosoftMaintainabilityAnalyzersResources.ko.xlf @@ -2,6 +2,91 @@ + + This rule measures class coupling by counting the number of unique type references that a symbol contains. Symbols that have a high degree of class coupling can be difficult to maintain. It is a good practice to have types and methods that exhibit low coupling and high cohesion. To fix this violation, try to redesign the code to reduce the number of types to which it is coupled. + This rule measures class coupling by counting the number of unique type references that a symbol contains. Symbols that have a high degree of class coupling can be difficult to maintain. It is a good practice to have types and methods that exhibit low coupling and high cohesion. To fix this violation, try to redesign the code to reduce the number of types to which it is coupled. + + + + '{0}' is coupled with '{1}' different types from '{2}' different namespaces. Rewrite or refactor the code to decrease its class coupling below '{3}'. + '{0}' is coupled with '{1}' different types from '{2}' different namespaces. Rewrite or refactor the code to decrease its class coupling below '{3}'. + + + + Avoid excessive class coupling + Avoid excessive class coupling + + + + Cyclomatic complexity measures the number of linearly independent paths through the method, which is determined by the number and complexity of conditional branches. A low cyclomatic complexity generally indicates a method that is easy to understand, test, and maintain. The cyclomatic complexity is calculated from a control flow graph of the method and is given as follows: + +cyclomatic complexity = the number of edges - the number of nodes + 1 + +where a node represents a logic branch point and an edge represents a line between nodes. + Cyclomatic complexity measures the number of linearly independent paths through the method, which is determined by the number and complexity of conditional branches. A low cyclomatic complexity generally indicates a method that is easy to understand, test, and maintain. The cyclomatic complexity is calculated from a control flow graph of the method and is given as follows: + +cyclomatic complexity = the number of edges - the number of nodes + 1 + +where a node represents a logic branch point and an edge represents a line between nodes. + + + + '{0}' has a cyclomatic complexity of '{1}'. Rewrite or refactor the code to decrease its complexity below '{2}'. + '{0}' has a cyclomatic complexity of '{1}'. Rewrite or refactor the code to decrease its complexity below '{2}'. + + + + Avoid excessive complexity + Avoid excessive complexity + + + + Deeply nested type hierarchies can be difficult to follow, understand, and maintain. This rule limits analysis to hierarchies in the same module. To fix a violation of this rule, derive the type from a base type that is less deep in the inheritance hierarchy or eliminate some of the intermediate base types. + Deeply nested type hierarchies can be difficult to follow, understand, and maintain. This rule limits analysis to hierarchies in the same module. To fix a violation of this rule, derive the type from a base type that is less deep in the inheritance hierarchy or eliminate some of the intermediate base types. + + + + '{0}' has an object hierarchy '{1}' levels deep within the defining module. If possible, eliminate base classes within the hierarchy to decrease its hierarchy level below '{2}': '{3}' + '{0}' has an object hierarchy '{1}' levels deep within the defining module. If possible, eliminate base classes within the hierarchy to decrease its hierarchy level below '{2}': '{3}' + + + + Avoid excessive inheritance + Avoid excessive inheritance + + + + The maintainability index is calculated by using the following metrics: lines of code, program volume, and cyclomatic complexity. Program volume is a measure of the difficulty of understanding of a symbol that is based on the number of operators and operands in the code. Cyclomatic complexity is a measure of the structural complexity of the type or method. +A low maintainability index indicates that code is probably difficult to maintain and would be a good candidate to redesign. + The maintainability index is calculated by using the following metrics: lines of code, program volume, and cyclomatic complexity. Program volume is a measure of the difficulty of understanding of a symbol that is based on the number of operators and operands in the code. Cyclomatic complexity is a measure of the structural complexity of the type or method. +A low maintainability index indicates that code is probably difficult to maintain and would be a good candidate to redesign. + + + + '{0}' has a maintainability index of '{1}'. Rewrite or refactor the code to increase its maintainability index (MI) above '{2}'. + '{0}' has a maintainability index of '{1}'. Rewrite or refactor the code to increase its maintainability index (MI) above '{2}'. + + + + Avoid unmaintainable code + Avoid unmaintainable code + + + + Invalid entry in code metrics rule specification file + Invalid entry in code metrics rule specification file + + + + Invalid entry '{0}' in code metrics rule specification file '{1}' + Invalid entry '{0}' in code metrics rule specification file '{1}' + + + + Invalid entry in code metrics rule specification file + Invalid entry in code metrics rule specification file + + Variable names should not match field names 변수 이름과 필드 이름이 일치하지 않아야 합니다. diff --git a/src/Microsoft.CodeQuality.Analyzers/Core/Maintainability/xlf/MicrosoftMaintainabilityAnalyzersResources.pl.xlf b/src/Microsoft.CodeQuality.Analyzers/Core/Maintainability/xlf/MicrosoftMaintainabilityAnalyzersResources.pl.xlf index bf38fd02e9..a9be4637dc 100644 --- a/src/Microsoft.CodeQuality.Analyzers/Core/Maintainability/xlf/MicrosoftMaintainabilityAnalyzersResources.pl.xlf +++ b/src/Microsoft.CodeQuality.Analyzers/Core/Maintainability/xlf/MicrosoftMaintainabilityAnalyzersResources.pl.xlf @@ -2,6 +2,91 @@ + + This rule measures class coupling by counting the number of unique type references that a symbol contains. Symbols that have a high degree of class coupling can be difficult to maintain. It is a good practice to have types and methods that exhibit low coupling and high cohesion. To fix this violation, try to redesign the code to reduce the number of types to which it is coupled. + This rule measures class coupling by counting the number of unique type references that a symbol contains. Symbols that have a high degree of class coupling can be difficult to maintain. It is a good practice to have types and methods that exhibit low coupling and high cohesion. To fix this violation, try to redesign the code to reduce the number of types to which it is coupled. + + + + '{0}' is coupled with '{1}' different types from '{2}' different namespaces. Rewrite or refactor the code to decrease its class coupling below '{3}'. + '{0}' is coupled with '{1}' different types from '{2}' different namespaces. Rewrite or refactor the code to decrease its class coupling below '{3}'. + + + + Avoid excessive class coupling + Avoid excessive class coupling + + + + Cyclomatic complexity measures the number of linearly independent paths through the method, which is determined by the number and complexity of conditional branches. A low cyclomatic complexity generally indicates a method that is easy to understand, test, and maintain. The cyclomatic complexity is calculated from a control flow graph of the method and is given as follows: + +cyclomatic complexity = the number of edges - the number of nodes + 1 + +where a node represents a logic branch point and an edge represents a line between nodes. + Cyclomatic complexity measures the number of linearly independent paths through the method, which is determined by the number and complexity of conditional branches. A low cyclomatic complexity generally indicates a method that is easy to understand, test, and maintain. The cyclomatic complexity is calculated from a control flow graph of the method and is given as follows: + +cyclomatic complexity = the number of edges - the number of nodes + 1 + +where a node represents a logic branch point and an edge represents a line between nodes. + + + + '{0}' has a cyclomatic complexity of '{1}'. Rewrite or refactor the code to decrease its complexity below '{2}'. + '{0}' has a cyclomatic complexity of '{1}'. Rewrite or refactor the code to decrease its complexity below '{2}'. + + + + Avoid excessive complexity + Avoid excessive complexity + + + + Deeply nested type hierarchies can be difficult to follow, understand, and maintain. This rule limits analysis to hierarchies in the same module. To fix a violation of this rule, derive the type from a base type that is less deep in the inheritance hierarchy or eliminate some of the intermediate base types. + Deeply nested type hierarchies can be difficult to follow, understand, and maintain. This rule limits analysis to hierarchies in the same module. To fix a violation of this rule, derive the type from a base type that is less deep in the inheritance hierarchy or eliminate some of the intermediate base types. + + + + '{0}' has an object hierarchy '{1}' levels deep within the defining module. If possible, eliminate base classes within the hierarchy to decrease its hierarchy level below '{2}': '{3}' + '{0}' has an object hierarchy '{1}' levels deep within the defining module. If possible, eliminate base classes within the hierarchy to decrease its hierarchy level below '{2}': '{3}' + + + + Avoid excessive inheritance + Avoid excessive inheritance + + + + The maintainability index is calculated by using the following metrics: lines of code, program volume, and cyclomatic complexity. Program volume is a measure of the difficulty of understanding of a symbol that is based on the number of operators and operands in the code. Cyclomatic complexity is a measure of the structural complexity of the type or method. +A low maintainability index indicates that code is probably difficult to maintain and would be a good candidate to redesign. + The maintainability index is calculated by using the following metrics: lines of code, program volume, and cyclomatic complexity. Program volume is a measure of the difficulty of understanding of a symbol that is based on the number of operators and operands in the code. Cyclomatic complexity is a measure of the structural complexity of the type or method. +A low maintainability index indicates that code is probably difficult to maintain and would be a good candidate to redesign. + + + + '{0}' has a maintainability index of '{1}'. Rewrite or refactor the code to increase its maintainability index (MI) above '{2}'. + '{0}' has a maintainability index of '{1}'. Rewrite or refactor the code to increase its maintainability index (MI) above '{2}'. + + + + Avoid unmaintainable code + Avoid unmaintainable code + + + + Invalid entry in code metrics rule specification file + Invalid entry in code metrics rule specification file + + + + Invalid entry '{0}' in code metrics rule specification file '{1}' + Invalid entry '{0}' in code metrics rule specification file '{1}' + + + + Invalid entry in code metrics rule specification file + Invalid entry in code metrics rule specification file + + Variable names should not match field names Nazwy zmiennych nie powinny być zgodne z nazwami pól diff --git a/src/Microsoft.CodeQuality.Analyzers/Core/Maintainability/xlf/MicrosoftMaintainabilityAnalyzersResources.pt-BR.xlf b/src/Microsoft.CodeQuality.Analyzers/Core/Maintainability/xlf/MicrosoftMaintainabilityAnalyzersResources.pt-BR.xlf index 657b43aaad..92ce4f14ca 100644 --- a/src/Microsoft.CodeQuality.Analyzers/Core/Maintainability/xlf/MicrosoftMaintainabilityAnalyzersResources.pt-BR.xlf +++ b/src/Microsoft.CodeQuality.Analyzers/Core/Maintainability/xlf/MicrosoftMaintainabilityAnalyzersResources.pt-BR.xlf @@ -2,6 +2,91 @@ + + This rule measures class coupling by counting the number of unique type references that a symbol contains. Symbols that have a high degree of class coupling can be difficult to maintain. It is a good practice to have types and methods that exhibit low coupling and high cohesion. To fix this violation, try to redesign the code to reduce the number of types to which it is coupled. + This rule measures class coupling by counting the number of unique type references that a symbol contains. Symbols that have a high degree of class coupling can be difficult to maintain. It is a good practice to have types and methods that exhibit low coupling and high cohesion. To fix this violation, try to redesign the code to reduce the number of types to which it is coupled. + + + + '{0}' is coupled with '{1}' different types from '{2}' different namespaces. Rewrite or refactor the code to decrease its class coupling below '{3}'. + '{0}' is coupled with '{1}' different types from '{2}' different namespaces. Rewrite or refactor the code to decrease its class coupling below '{3}'. + + + + Avoid excessive class coupling + Avoid excessive class coupling + + + + Cyclomatic complexity measures the number of linearly independent paths through the method, which is determined by the number and complexity of conditional branches. A low cyclomatic complexity generally indicates a method that is easy to understand, test, and maintain. The cyclomatic complexity is calculated from a control flow graph of the method and is given as follows: + +cyclomatic complexity = the number of edges - the number of nodes + 1 + +where a node represents a logic branch point and an edge represents a line between nodes. + Cyclomatic complexity measures the number of linearly independent paths through the method, which is determined by the number and complexity of conditional branches. A low cyclomatic complexity generally indicates a method that is easy to understand, test, and maintain. The cyclomatic complexity is calculated from a control flow graph of the method and is given as follows: + +cyclomatic complexity = the number of edges - the number of nodes + 1 + +where a node represents a logic branch point and an edge represents a line between nodes. + + + + '{0}' has a cyclomatic complexity of '{1}'. Rewrite or refactor the code to decrease its complexity below '{2}'. + '{0}' has a cyclomatic complexity of '{1}'. Rewrite or refactor the code to decrease its complexity below '{2}'. + + + + Avoid excessive complexity + Avoid excessive complexity + + + + Deeply nested type hierarchies can be difficult to follow, understand, and maintain. This rule limits analysis to hierarchies in the same module. To fix a violation of this rule, derive the type from a base type that is less deep in the inheritance hierarchy or eliminate some of the intermediate base types. + Deeply nested type hierarchies can be difficult to follow, understand, and maintain. This rule limits analysis to hierarchies in the same module. To fix a violation of this rule, derive the type from a base type that is less deep in the inheritance hierarchy or eliminate some of the intermediate base types. + + + + '{0}' has an object hierarchy '{1}' levels deep within the defining module. If possible, eliminate base classes within the hierarchy to decrease its hierarchy level below '{2}': '{3}' + '{0}' has an object hierarchy '{1}' levels deep within the defining module. If possible, eliminate base classes within the hierarchy to decrease its hierarchy level below '{2}': '{3}' + + + + Avoid excessive inheritance + Avoid excessive inheritance + + + + The maintainability index is calculated by using the following metrics: lines of code, program volume, and cyclomatic complexity. Program volume is a measure of the difficulty of understanding of a symbol that is based on the number of operators and operands in the code. Cyclomatic complexity is a measure of the structural complexity of the type or method. +A low maintainability index indicates that code is probably difficult to maintain and would be a good candidate to redesign. + The maintainability index is calculated by using the following metrics: lines of code, program volume, and cyclomatic complexity. Program volume is a measure of the difficulty of understanding of a symbol that is based on the number of operators and operands in the code. Cyclomatic complexity is a measure of the structural complexity of the type or method. +A low maintainability index indicates that code is probably difficult to maintain and would be a good candidate to redesign. + + + + '{0}' has a maintainability index of '{1}'. Rewrite or refactor the code to increase its maintainability index (MI) above '{2}'. + '{0}' has a maintainability index of '{1}'. Rewrite or refactor the code to increase its maintainability index (MI) above '{2}'. + + + + Avoid unmaintainable code + Avoid unmaintainable code + + + + Invalid entry in code metrics rule specification file + Invalid entry in code metrics rule specification file + + + + Invalid entry '{0}' in code metrics rule specification file '{1}' + Invalid entry '{0}' in code metrics rule specification file '{1}' + + + + Invalid entry in code metrics rule specification file + Invalid entry in code metrics rule specification file + + Variable names should not match field names Nomes de variáveis não devem corresponder a nomes de campos diff --git a/src/Microsoft.CodeQuality.Analyzers/Core/Maintainability/xlf/MicrosoftMaintainabilityAnalyzersResources.ru.xlf b/src/Microsoft.CodeQuality.Analyzers/Core/Maintainability/xlf/MicrosoftMaintainabilityAnalyzersResources.ru.xlf index 949e5971e7..599476e933 100644 --- a/src/Microsoft.CodeQuality.Analyzers/Core/Maintainability/xlf/MicrosoftMaintainabilityAnalyzersResources.ru.xlf +++ b/src/Microsoft.CodeQuality.Analyzers/Core/Maintainability/xlf/MicrosoftMaintainabilityAnalyzersResources.ru.xlf @@ -2,6 +2,91 @@ + + This rule measures class coupling by counting the number of unique type references that a symbol contains. Symbols that have a high degree of class coupling can be difficult to maintain. It is a good practice to have types and methods that exhibit low coupling and high cohesion. To fix this violation, try to redesign the code to reduce the number of types to which it is coupled. + This rule measures class coupling by counting the number of unique type references that a symbol contains. Symbols that have a high degree of class coupling can be difficult to maintain. It is a good practice to have types and methods that exhibit low coupling and high cohesion. To fix this violation, try to redesign the code to reduce the number of types to which it is coupled. + + + + '{0}' is coupled with '{1}' different types from '{2}' different namespaces. Rewrite or refactor the code to decrease its class coupling below '{3}'. + '{0}' is coupled with '{1}' different types from '{2}' different namespaces. Rewrite or refactor the code to decrease its class coupling below '{3}'. + + + + Avoid excessive class coupling + Avoid excessive class coupling + + + + Cyclomatic complexity measures the number of linearly independent paths through the method, which is determined by the number and complexity of conditional branches. A low cyclomatic complexity generally indicates a method that is easy to understand, test, and maintain. The cyclomatic complexity is calculated from a control flow graph of the method and is given as follows: + +cyclomatic complexity = the number of edges - the number of nodes + 1 + +where a node represents a logic branch point and an edge represents a line between nodes. + Cyclomatic complexity measures the number of linearly independent paths through the method, which is determined by the number and complexity of conditional branches. A low cyclomatic complexity generally indicates a method that is easy to understand, test, and maintain. The cyclomatic complexity is calculated from a control flow graph of the method and is given as follows: + +cyclomatic complexity = the number of edges - the number of nodes + 1 + +where a node represents a logic branch point and an edge represents a line between nodes. + + + + '{0}' has a cyclomatic complexity of '{1}'. Rewrite or refactor the code to decrease its complexity below '{2}'. + '{0}' has a cyclomatic complexity of '{1}'. Rewrite or refactor the code to decrease its complexity below '{2}'. + + + + Avoid excessive complexity + Avoid excessive complexity + + + + Deeply nested type hierarchies can be difficult to follow, understand, and maintain. This rule limits analysis to hierarchies in the same module. To fix a violation of this rule, derive the type from a base type that is less deep in the inheritance hierarchy or eliminate some of the intermediate base types. + Deeply nested type hierarchies can be difficult to follow, understand, and maintain. This rule limits analysis to hierarchies in the same module. To fix a violation of this rule, derive the type from a base type that is less deep in the inheritance hierarchy or eliminate some of the intermediate base types. + + + + '{0}' has an object hierarchy '{1}' levels deep within the defining module. If possible, eliminate base classes within the hierarchy to decrease its hierarchy level below '{2}': '{3}' + '{0}' has an object hierarchy '{1}' levels deep within the defining module. If possible, eliminate base classes within the hierarchy to decrease its hierarchy level below '{2}': '{3}' + + + + Avoid excessive inheritance + Avoid excessive inheritance + + + + The maintainability index is calculated by using the following metrics: lines of code, program volume, and cyclomatic complexity. Program volume is a measure of the difficulty of understanding of a symbol that is based on the number of operators and operands in the code. Cyclomatic complexity is a measure of the structural complexity of the type or method. +A low maintainability index indicates that code is probably difficult to maintain and would be a good candidate to redesign. + The maintainability index is calculated by using the following metrics: lines of code, program volume, and cyclomatic complexity. Program volume is a measure of the difficulty of understanding of a symbol that is based on the number of operators and operands in the code. Cyclomatic complexity is a measure of the structural complexity of the type or method. +A low maintainability index indicates that code is probably difficult to maintain and would be a good candidate to redesign. + + + + '{0}' has a maintainability index of '{1}'. Rewrite or refactor the code to increase its maintainability index (MI) above '{2}'. + '{0}' has a maintainability index of '{1}'. Rewrite or refactor the code to increase its maintainability index (MI) above '{2}'. + + + + Avoid unmaintainable code + Avoid unmaintainable code + + + + Invalid entry in code metrics rule specification file + Invalid entry in code metrics rule specification file + + + + Invalid entry '{0}' in code metrics rule specification file '{1}' + Invalid entry '{0}' in code metrics rule specification file '{1}' + + + + Invalid entry in code metrics rule specification file + Invalid entry in code metrics rule specification file + + Variable names should not match field names Имена переменных не должны совпадать с именами полей diff --git a/src/Microsoft.CodeQuality.Analyzers/Core/Maintainability/xlf/MicrosoftMaintainabilityAnalyzersResources.tr.xlf b/src/Microsoft.CodeQuality.Analyzers/Core/Maintainability/xlf/MicrosoftMaintainabilityAnalyzersResources.tr.xlf index 006cc5fc79..2939359047 100644 --- a/src/Microsoft.CodeQuality.Analyzers/Core/Maintainability/xlf/MicrosoftMaintainabilityAnalyzersResources.tr.xlf +++ b/src/Microsoft.CodeQuality.Analyzers/Core/Maintainability/xlf/MicrosoftMaintainabilityAnalyzersResources.tr.xlf @@ -2,6 +2,91 @@ + + This rule measures class coupling by counting the number of unique type references that a symbol contains. Symbols that have a high degree of class coupling can be difficult to maintain. It is a good practice to have types and methods that exhibit low coupling and high cohesion. To fix this violation, try to redesign the code to reduce the number of types to which it is coupled. + This rule measures class coupling by counting the number of unique type references that a symbol contains. Symbols that have a high degree of class coupling can be difficult to maintain. It is a good practice to have types and methods that exhibit low coupling and high cohesion. To fix this violation, try to redesign the code to reduce the number of types to which it is coupled. + + + + '{0}' is coupled with '{1}' different types from '{2}' different namespaces. Rewrite or refactor the code to decrease its class coupling below '{3}'. + '{0}' is coupled with '{1}' different types from '{2}' different namespaces. Rewrite or refactor the code to decrease its class coupling below '{3}'. + + + + Avoid excessive class coupling + Avoid excessive class coupling + + + + Cyclomatic complexity measures the number of linearly independent paths through the method, which is determined by the number and complexity of conditional branches. A low cyclomatic complexity generally indicates a method that is easy to understand, test, and maintain. The cyclomatic complexity is calculated from a control flow graph of the method and is given as follows: + +cyclomatic complexity = the number of edges - the number of nodes + 1 + +where a node represents a logic branch point and an edge represents a line between nodes. + Cyclomatic complexity measures the number of linearly independent paths through the method, which is determined by the number and complexity of conditional branches. A low cyclomatic complexity generally indicates a method that is easy to understand, test, and maintain. The cyclomatic complexity is calculated from a control flow graph of the method and is given as follows: + +cyclomatic complexity = the number of edges - the number of nodes + 1 + +where a node represents a logic branch point and an edge represents a line between nodes. + + + + '{0}' has a cyclomatic complexity of '{1}'. Rewrite or refactor the code to decrease its complexity below '{2}'. + '{0}' has a cyclomatic complexity of '{1}'. Rewrite or refactor the code to decrease its complexity below '{2}'. + + + + Avoid excessive complexity + Avoid excessive complexity + + + + Deeply nested type hierarchies can be difficult to follow, understand, and maintain. This rule limits analysis to hierarchies in the same module. To fix a violation of this rule, derive the type from a base type that is less deep in the inheritance hierarchy or eliminate some of the intermediate base types. + Deeply nested type hierarchies can be difficult to follow, understand, and maintain. This rule limits analysis to hierarchies in the same module. To fix a violation of this rule, derive the type from a base type that is less deep in the inheritance hierarchy or eliminate some of the intermediate base types. + + + + '{0}' has an object hierarchy '{1}' levels deep within the defining module. If possible, eliminate base classes within the hierarchy to decrease its hierarchy level below '{2}': '{3}' + '{0}' has an object hierarchy '{1}' levels deep within the defining module. If possible, eliminate base classes within the hierarchy to decrease its hierarchy level below '{2}': '{3}' + + + + Avoid excessive inheritance + Avoid excessive inheritance + + + + The maintainability index is calculated by using the following metrics: lines of code, program volume, and cyclomatic complexity. Program volume is a measure of the difficulty of understanding of a symbol that is based on the number of operators and operands in the code. Cyclomatic complexity is a measure of the structural complexity of the type or method. +A low maintainability index indicates that code is probably difficult to maintain and would be a good candidate to redesign. + The maintainability index is calculated by using the following metrics: lines of code, program volume, and cyclomatic complexity. Program volume is a measure of the difficulty of understanding of a symbol that is based on the number of operators and operands in the code. Cyclomatic complexity is a measure of the structural complexity of the type or method. +A low maintainability index indicates that code is probably difficult to maintain and would be a good candidate to redesign. + + + + '{0}' has a maintainability index of '{1}'. Rewrite or refactor the code to increase its maintainability index (MI) above '{2}'. + '{0}' has a maintainability index of '{1}'. Rewrite or refactor the code to increase its maintainability index (MI) above '{2}'. + + + + Avoid unmaintainable code + Avoid unmaintainable code + + + + Invalid entry in code metrics rule specification file + Invalid entry in code metrics rule specification file + + + + Invalid entry '{0}' in code metrics rule specification file '{1}' + Invalid entry '{0}' in code metrics rule specification file '{1}' + + + + Invalid entry in code metrics rule specification file + Invalid entry in code metrics rule specification file + + Variable names should not match field names Değişken adları alan adlarıyla eşleşmemelidir diff --git a/src/Microsoft.CodeQuality.Analyzers/Core/Maintainability/xlf/MicrosoftMaintainabilityAnalyzersResources.zh-Hans.xlf b/src/Microsoft.CodeQuality.Analyzers/Core/Maintainability/xlf/MicrosoftMaintainabilityAnalyzersResources.zh-Hans.xlf index 8c70ab4974..888f43f6be 100644 --- a/src/Microsoft.CodeQuality.Analyzers/Core/Maintainability/xlf/MicrosoftMaintainabilityAnalyzersResources.zh-Hans.xlf +++ b/src/Microsoft.CodeQuality.Analyzers/Core/Maintainability/xlf/MicrosoftMaintainabilityAnalyzersResources.zh-Hans.xlf @@ -2,6 +2,91 @@ + + This rule measures class coupling by counting the number of unique type references that a symbol contains. Symbols that have a high degree of class coupling can be difficult to maintain. It is a good practice to have types and methods that exhibit low coupling and high cohesion. To fix this violation, try to redesign the code to reduce the number of types to which it is coupled. + This rule measures class coupling by counting the number of unique type references that a symbol contains. Symbols that have a high degree of class coupling can be difficult to maintain. It is a good practice to have types and methods that exhibit low coupling and high cohesion. To fix this violation, try to redesign the code to reduce the number of types to which it is coupled. + + + + '{0}' is coupled with '{1}' different types from '{2}' different namespaces. Rewrite or refactor the code to decrease its class coupling below '{3}'. + '{0}' is coupled with '{1}' different types from '{2}' different namespaces. Rewrite or refactor the code to decrease its class coupling below '{3}'. + + + + Avoid excessive class coupling + Avoid excessive class coupling + + + + Cyclomatic complexity measures the number of linearly independent paths through the method, which is determined by the number and complexity of conditional branches. A low cyclomatic complexity generally indicates a method that is easy to understand, test, and maintain. The cyclomatic complexity is calculated from a control flow graph of the method and is given as follows: + +cyclomatic complexity = the number of edges - the number of nodes + 1 + +where a node represents a logic branch point and an edge represents a line between nodes. + Cyclomatic complexity measures the number of linearly independent paths through the method, which is determined by the number and complexity of conditional branches. A low cyclomatic complexity generally indicates a method that is easy to understand, test, and maintain. The cyclomatic complexity is calculated from a control flow graph of the method and is given as follows: + +cyclomatic complexity = the number of edges - the number of nodes + 1 + +where a node represents a logic branch point and an edge represents a line between nodes. + + + + '{0}' has a cyclomatic complexity of '{1}'. Rewrite or refactor the code to decrease its complexity below '{2}'. + '{0}' has a cyclomatic complexity of '{1}'. Rewrite or refactor the code to decrease its complexity below '{2}'. + + + + Avoid excessive complexity + Avoid excessive complexity + + + + Deeply nested type hierarchies can be difficult to follow, understand, and maintain. This rule limits analysis to hierarchies in the same module. To fix a violation of this rule, derive the type from a base type that is less deep in the inheritance hierarchy or eliminate some of the intermediate base types. + Deeply nested type hierarchies can be difficult to follow, understand, and maintain. This rule limits analysis to hierarchies in the same module. To fix a violation of this rule, derive the type from a base type that is less deep in the inheritance hierarchy or eliminate some of the intermediate base types. + + + + '{0}' has an object hierarchy '{1}' levels deep within the defining module. If possible, eliminate base classes within the hierarchy to decrease its hierarchy level below '{2}': '{3}' + '{0}' has an object hierarchy '{1}' levels deep within the defining module. If possible, eliminate base classes within the hierarchy to decrease its hierarchy level below '{2}': '{3}' + + + + Avoid excessive inheritance + Avoid excessive inheritance + + + + The maintainability index is calculated by using the following metrics: lines of code, program volume, and cyclomatic complexity. Program volume is a measure of the difficulty of understanding of a symbol that is based on the number of operators and operands in the code. Cyclomatic complexity is a measure of the structural complexity of the type or method. +A low maintainability index indicates that code is probably difficult to maintain and would be a good candidate to redesign. + The maintainability index is calculated by using the following metrics: lines of code, program volume, and cyclomatic complexity. Program volume is a measure of the difficulty of understanding of a symbol that is based on the number of operators and operands in the code. Cyclomatic complexity is a measure of the structural complexity of the type or method. +A low maintainability index indicates that code is probably difficult to maintain and would be a good candidate to redesign. + + + + '{0}' has a maintainability index of '{1}'. Rewrite or refactor the code to increase its maintainability index (MI) above '{2}'. + '{0}' has a maintainability index of '{1}'. Rewrite or refactor the code to increase its maintainability index (MI) above '{2}'. + + + + Avoid unmaintainable code + Avoid unmaintainable code + + + + Invalid entry in code metrics rule specification file + Invalid entry in code metrics rule specification file + + + + Invalid entry '{0}' in code metrics rule specification file '{1}' + Invalid entry '{0}' in code metrics rule specification file '{1}' + + + + Invalid entry in code metrics rule specification file + Invalid entry in code metrics rule specification file + + Variable names should not match field names 变量名不应与字段名相同 diff --git a/src/Microsoft.CodeQuality.Analyzers/Core/Maintainability/xlf/MicrosoftMaintainabilityAnalyzersResources.zh-Hant.xlf b/src/Microsoft.CodeQuality.Analyzers/Core/Maintainability/xlf/MicrosoftMaintainabilityAnalyzersResources.zh-Hant.xlf index bf9d4a9bb6..7a1769a000 100644 --- a/src/Microsoft.CodeQuality.Analyzers/Core/Maintainability/xlf/MicrosoftMaintainabilityAnalyzersResources.zh-Hant.xlf +++ b/src/Microsoft.CodeQuality.Analyzers/Core/Maintainability/xlf/MicrosoftMaintainabilityAnalyzersResources.zh-Hant.xlf @@ -2,6 +2,91 @@ + + This rule measures class coupling by counting the number of unique type references that a symbol contains. Symbols that have a high degree of class coupling can be difficult to maintain. It is a good practice to have types and methods that exhibit low coupling and high cohesion. To fix this violation, try to redesign the code to reduce the number of types to which it is coupled. + This rule measures class coupling by counting the number of unique type references that a symbol contains. Symbols that have a high degree of class coupling can be difficult to maintain. It is a good practice to have types and methods that exhibit low coupling and high cohesion. To fix this violation, try to redesign the code to reduce the number of types to which it is coupled. + + + + '{0}' is coupled with '{1}' different types from '{2}' different namespaces. Rewrite or refactor the code to decrease its class coupling below '{3}'. + '{0}' is coupled with '{1}' different types from '{2}' different namespaces. Rewrite or refactor the code to decrease its class coupling below '{3}'. + + + + Avoid excessive class coupling + Avoid excessive class coupling + + + + Cyclomatic complexity measures the number of linearly independent paths through the method, which is determined by the number and complexity of conditional branches. A low cyclomatic complexity generally indicates a method that is easy to understand, test, and maintain. The cyclomatic complexity is calculated from a control flow graph of the method and is given as follows: + +cyclomatic complexity = the number of edges - the number of nodes + 1 + +where a node represents a logic branch point and an edge represents a line between nodes. + Cyclomatic complexity measures the number of linearly independent paths through the method, which is determined by the number and complexity of conditional branches. A low cyclomatic complexity generally indicates a method that is easy to understand, test, and maintain. The cyclomatic complexity is calculated from a control flow graph of the method and is given as follows: + +cyclomatic complexity = the number of edges - the number of nodes + 1 + +where a node represents a logic branch point and an edge represents a line between nodes. + + + + '{0}' has a cyclomatic complexity of '{1}'. Rewrite or refactor the code to decrease its complexity below '{2}'. + '{0}' has a cyclomatic complexity of '{1}'. Rewrite or refactor the code to decrease its complexity below '{2}'. + + + + Avoid excessive complexity + Avoid excessive complexity + + + + Deeply nested type hierarchies can be difficult to follow, understand, and maintain. This rule limits analysis to hierarchies in the same module. To fix a violation of this rule, derive the type from a base type that is less deep in the inheritance hierarchy or eliminate some of the intermediate base types. + Deeply nested type hierarchies can be difficult to follow, understand, and maintain. This rule limits analysis to hierarchies in the same module. To fix a violation of this rule, derive the type from a base type that is less deep in the inheritance hierarchy or eliminate some of the intermediate base types. + + + + '{0}' has an object hierarchy '{1}' levels deep within the defining module. If possible, eliminate base classes within the hierarchy to decrease its hierarchy level below '{2}': '{3}' + '{0}' has an object hierarchy '{1}' levels deep within the defining module. If possible, eliminate base classes within the hierarchy to decrease its hierarchy level below '{2}': '{3}' + + + + Avoid excessive inheritance + Avoid excessive inheritance + + + + The maintainability index is calculated by using the following metrics: lines of code, program volume, and cyclomatic complexity. Program volume is a measure of the difficulty of understanding of a symbol that is based on the number of operators and operands in the code. Cyclomatic complexity is a measure of the structural complexity of the type or method. +A low maintainability index indicates that code is probably difficult to maintain and would be a good candidate to redesign. + The maintainability index is calculated by using the following metrics: lines of code, program volume, and cyclomatic complexity. Program volume is a measure of the difficulty of understanding of a symbol that is based on the number of operators and operands in the code. Cyclomatic complexity is a measure of the structural complexity of the type or method. +A low maintainability index indicates that code is probably difficult to maintain and would be a good candidate to redesign. + + + + '{0}' has a maintainability index of '{1}'. Rewrite or refactor the code to increase its maintainability index (MI) above '{2}'. + '{0}' has a maintainability index of '{1}'. Rewrite or refactor the code to increase its maintainability index (MI) above '{2}'. + + + + Avoid unmaintainable code + Avoid unmaintainable code + + + + Invalid entry in code metrics rule specification file + Invalid entry in code metrics rule specification file + + + + Invalid entry '{0}' in code metrics rule specification file '{1}' + Invalid entry '{0}' in code metrics rule specification file '{1}' + + + + Invalid entry in code metrics rule specification file + Invalid entry in code metrics rule specification file + + Variable names should not match field names 變數名稱不應與欄位名稱相符 diff --git a/src/Microsoft.CodeQuality.Analyzers/Microsoft.CodeQuality.Analyzers.md b/src/Microsoft.CodeQuality.Analyzers/Microsoft.CodeQuality.Analyzers.md index 581aed622f..aefb874a5f 100644 --- a/src/Microsoft.CodeQuality.Analyzers/Microsoft.CodeQuality.Analyzers.md +++ b/src/Microsoft.CodeQuality.Analyzers/Microsoft.CodeQuality.Analyzers.md @@ -490,6 +490,60 @@ Help: [Source](https://github.com/dotnet/roslyn-analyzers/blob/master/src/Micros Maintainability ---------------------------------- +### CA1501: Avoid excessive inheritance ### + +A type has deeply nested inheritance hierarchy. + +Category: Maintainability + +Severity: Warning + +Help: [https://docs.microsoft.com/en-us/visualstudio/code-quality/ca1501-avoid-excessive-inheritance](https://docs.microsoft.com/en-us/visualstudio/code-quality/ca1501-avoid-excessive-inheritance) + +### CA1502: Avoid excessive complexity ### + +A symbol has an excessive cyclomatic complexity. + +Category: Maintainability + +Severity: Warning + +Help: [https://docs.microsoft.com/en-us/visualstudio/code-quality/ca1502-avoid-excessive-complexity](https://docs.microsoft.com/en-us/visualstudio/code-quality/ca1502-avoid-excessive-complexity) + +### CA1505: Avoid unmaintainable code ### + +A symbol has a low maintainability index value. + +Category: Maintainability + +Severity: Warning + +Help: [https://docs.microsoft.com/en-us/visualstudio/code-quality/ca1505-avoid-unmaintainable-code](https://docs.microsoft.com/en-us/visualstudio/code-quality/ca1505-avoid-unmaintainable-code) + +### CA1506: Avoid excessive class coupling ### + +A symbol is coupled with many other types. + +Category: Maintainability + +Severity: Warning + +Help: [https://docs.microsoft.com/en-us/visualstudio/code-quality/ca1506-avoid-excessive-class-coupling](https://docs.microsoft.com/en-us/visualstudio/code-quality/ca1506-avoid-excessive-class-coupling) + +### CA1508: Invalid entry in code metrics rule specification file ### + +Invalid entry in code metrics rule specification file "CodeMetricsConfig.txt". Expected format example: + # FORMAT: + # 'RuleId'(Optional 'SymbolKind'): 'Threshold' + + CA1501: 10 + +Category: Maintainability + +Severity: Warning + +Help: [TBD] + ### CA1801: Review unused parameters ### A method signature includes a parameter that is not used in the method body. diff --git a/src/Microsoft.CodeQuality.Analyzers/UnitTests/Maintainability/CodeMetricsAnalyzerTests.cs b/src/Microsoft.CodeQuality.Analyzers/UnitTests/Maintainability/CodeMetricsAnalyzerTests.cs new file mode 100644 index 0000000000..f47a78a794 --- /dev/null +++ b/src/Microsoft.CodeQuality.Analyzers/UnitTests/Maintainability/CodeMetricsAnalyzerTests.cs @@ -0,0 +1,783 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Analyzer.Utilities; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.Diagnostics; +using Test.Utilities; +using Xunit; + +namespace Microsoft.CodeQuality.Analyzers.Maintainability.CodeMetrics.UnitTests +{ + public class CodeMetricsAnalyzerTests : CodeFixTestBase + { + #region CA1501: Avoid excessive inheritance + + [Fact] + public void CA1501_CSharp_VerifyDiagnostic() + { + var source = @" +class BaseClass { } +class FirstDerivedClass : BaseClass { } +class SecondDerivedClass : FirstDerivedClass { } +class ThirdDerivedClass : SecondDerivedClass { } +class FourthDerivedClass : ThirdDerivedClass { } + +// This class violates the rule. +class FifthDerivedClass : FourthDerivedClass { } +"; + DiagnosticResult[] expected = new[] { + // Test0.cs(9, 7): warning CA1501: 'FifthDerivedClass' has an object hierarchy '6' levels deep within the defining module. If possible, eliminate base classes within the hierarchy to decrease its hierarchy level below '6': 'FourthDerivedClass, ThirdDerivedClass, SecondDerivedClass, FirstDerivedClass, BaseClass, Object' + GetCSharpCA1501ExpectedDiagnostic(9, 7, "FifthDerivedClass", 6, 6, "FourthDerivedClass, ThirdDerivedClass, SecondDerivedClass, FirstDerivedClass, BaseClass, Object")}; + VerifyCSharp(source, expected); + } + + [Fact] + public void CA1501_Basic_VerifyDiagnostic() + { + var source = @" +Class BaseClass +End Class + +Class FirstDerivedClass + Inherits BaseClass +End Class + +Class SecondDerivedClass + Inherits FirstDerivedClass +End Class + +Class ThirdDerivedClass + Inherits SecondDerivedClass +End Class + +Class FourthDerivedClass + Inherits ThirdDerivedClass +End Class + +Class FifthDerivedClass + Inherits FourthDerivedClass +End Class +"; + DiagnosticResult[] expected = new[] { + // Test0.vb(21, 7): warning CA1501: 'FifthDerivedClass' has an object hierarchy '6' levels deep within the defining module. If possible, eliminate base classes within the hierarchy to decrease its hierarchy level below '6': 'FourthDerivedClass, ThirdDerivedClass, SecondDerivedClass, FirstDerivedClass, BaseClass, Object' + GetBasicCA1501ExpectedDiagnostic(21, 7, "FifthDerivedClass", 6, 6, "FourthDerivedClass, ThirdDerivedClass, SecondDerivedClass, FirstDerivedClass, BaseClass, Object")}; + VerifyBasic(source, expected); + } + + [Fact] + public void CA1501_Configuration_CSharp_VerifyDiagnostic() + { + var source = @" +class BaseClass { } +class FirstDerivedClass : BaseClass { } +"; + string additionalText = @" +# FORMAT: +# 'RuleId'(Optional 'SymbolKind'): 'Threshold' + +CA1501: 1 +"; + DiagnosticResult[] expected = new[] { + // Test0.cs(3, 7): warning CA1501: 'BaseClass' has an object hierarchy '2' levels deep within the defining module. If possible, eliminate base classes within the hierarchy to decrease its hierarchy level below '2': 'BaseClass, Object' + GetCSharpCA1501ExpectedDiagnostic(3, 7, "FirstDerivedClass", 2, 2, "BaseClass, Object")}; + VerifyCSharp(source, GetAdditionalFile(additionalText), expected); + } + + [Fact] + public void CA1501_Configuration_Basic_VerifyDiagnostic() + { + var source = @" +Class BaseClass +End Class + +Class FirstDerivedClass + Inherits BaseClass +End Class +"; + string additionalText = @" +# FORMAT: +# 'RuleId'(Optional 'SymbolKind'): 'Threshold' + +CA1501: 1 +"; + DiagnosticResult[] expected = new[] { + // Test0.vb(5, 7): warning CA1501: 'BaseClass' has an object hierarchy '2' levels deep within the defining module. If possible, eliminate base classes within the hierarchy to decrease its hierarchy level below '2': 'BaseClass, Object' + GetBasicCA1501ExpectedDiagnostic(5, 7, "FirstDerivedClass", 2, 2, "BaseClass, Object")}; + VerifyBasic(source, GetAdditionalFile(additionalText), expected); + } + + #endregion + + #region CA1502: Avoid excessive complexity + + [Fact] + public void CA1502_CSharp_VerifyDiagnostic() + { + var source = @" +class C +{ + void M(bool b) + { + // Default threshold = 25 + var x = b && b && b && b && b && b && b && + b && b && b && b && b && b && b && + b && b && b && b && b && b && b && + b && b && b && b && b && b && b; + } +} +"; + DiagnosticResult[] expected = new[] { + // Test0.cs(4,10): warning CA1502: 'M' has a cyclomatic complexity of '29'. Rewrite or refactor the code to decrease its complexity below '26'. + GetCSharpCA1502ExpectedDiagnostic(4, 10, "M", 28, 26)}; + VerifyCSharp(source, expected); + } + + [Fact] + public void CA1502_Basic_VerifyDiagnostic() + { + var source = @" +Class C + Private Sub M(ByVal b As Boolean) + Dim x = b AndAlso b AndAlso b AndAlso b AndAlso b AndAlso b AndAlso + b AndAlso b AndAlso b AndAlso b AndAlso b AndAlso b AndAlso b AndAlso + b AndAlso b AndAlso b AndAlso b AndAlso b AndAlso b AndAlso b AndAlso + b AndAlso b AndAlso b AndAlso b AndAlso b AndAlso b AndAlso b AndAlso b + End Sub +End Class +"; + DiagnosticResult[] expected = new[] { + // Test0.vb(3,17): warning CA1502: 'M' has a cyclomatic complexity of '28'. Rewrite or refactor the code to decrease its complexity below '26'. + GetBasicCA1502ExpectedDiagnostic(3, 17, "M", 28, 26)}; + VerifyBasic(source, expected); + } + + [Fact] + public void CA1502_Configuration_CSharp_VerifyDiagnostic() + { + var source = @" +class C +{ + void M1(bool b) + { + var x = b && b && b && b; + } + + void M2(bool b) + { + var x = b && b; + } +} +"; + + string additionalText = @" +# FORMAT: +# 'RuleId'(Optional 'SymbolKind'): 'Threshold' + +CA1502: 2 +"; + DiagnosticResult[] expected = new[] { + // Test0.cs(4,10): warning CA1502: 'M1' has a cyclomatic complexity of '4'. Rewrite or refactor the code to decrease its complexity below '3'. + GetCSharpCA1502ExpectedDiagnostic(4, 10, "M1", 4, 3)}; + VerifyCSharp(source, GetAdditionalFile(additionalText), expected); + } + + [Fact] + public void CA1502_Configuration_Basic_VerifyDiagnostic() + { + var source = @" +Class C + Private Sub M1(ByVal b As Boolean) + Dim x = b AndAlso b AndAlso b AndAlso b + End Sub + + Private Sub M2(ByVal b As Boolean) + Dim x = b AndAlso b + End Sub +End Class +"; + string additionalText = @" +# FORMAT: +# 'RuleId'(Optional 'SymbolKind'): 'Threshold' + +CA1502: 2 +"; + DiagnosticResult[] expected = new[] { + // Test0.vb(3,17): warning CA1502: 'M1' has a cyclomatic complexity of '4'. Rewrite or refactor the code to decrease its complexity below '3'. + GetBasicCA1502ExpectedDiagnostic(3, 17, "M1", 4, 3)}; + VerifyBasic(source, GetAdditionalFile(additionalText), expected); + } + + [Fact] + public void CA1502_SymbolBasedConfiguration_CSharp_VerifyDiagnostic() + { + var source = @" +class C +{ + void M1(bool b) + { + var x = b && b && b && b; + } + + void M2(bool b) + { + var x = b && b; + } +} +"; + + string additionalText = @" +# FORMAT: +# 'RuleId'(Optional 'SymbolKind'): 'Threshold' + +CA1502(Type): 4 +CA1502(Method): 2 +"; + DiagnosticResult[] expected = new[] { + // Test0.cs(2,7): warning CA1502: 'C' has a cyclomatic complexity of '6'. Rewrite or refactor the code to decrease its complexity below '5'. + GetCSharpCA1502ExpectedDiagnostic(2, 7, "C", 6, 5), + // Test0.cs(4,10): warning CA1502: 'M1' has a cyclomatic complexity of '4'. Rewrite or refactor the code to decrease its complexity below '3'. + GetCSharpCA1502ExpectedDiagnostic(4, 10, "M1", 4, 3)}; + VerifyCSharp(source, GetAdditionalFile(additionalText), expected); + } + + [Fact] + public void CA1502_SymbolBasedConfiguration_Basic_VerifyDiagnostic() + { + var source = @" +Class C + Private Sub M1(ByVal b As Boolean) + Dim x = b AndAlso b AndAlso b AndAlso b + End Sub + + Private Sub M2(ByVal b As Boolean) + Dim x = b AndAlso b + End Sub +End Class +"; + string additionalText = @" +# FORMAT: +# 'RuleId'(Optional 'SymbolKind'): 'Threshold' + +CA1502(Type): 4 +CA1502(Method): 2 +"; + DiagnosticResult[] expected = new[] { + // Test0.vb(2,7): warning CA1502: 'C' has a cyclomatic complexity of '6'. Rewrite or refactor the code to decrease its complexity below '5'. + GetBasicCA1502ExpectedDiagnostic(2, 7, "C", 6, 5), + // Test0.vb(3,17): warning CA1502: 'M1' has a cyclomatic complexity of '4'. Rewrite or refactor the code to decrease its complexity below '3'. + GetBasicCA1502ExpectedDiagnostic(3, 17, "M1", 4, 3)}; + VerifyBasic(source, GetAdditionalFile(additionalText), expected); + } + + #endregion + + #region CA1505: Avoid unmaintainable code + + [Fact] + public void CA1505_Configuration_CSharp_VerifyDiagnostic() + { + var source = @" +class C +{ + void M1(bool b) + { + var x = b && b && b && b; + } +} +"; + + string additionalText = @" +# FORMAT: +# 'RuleId'(Optional 'SymbolKind'): 'Threshold' + +CA1505: 95 +"; + DiagnosticResult[] expected = new[] { + // Test0.cs(2,7): warning CA1505: 'C' has a maintainability index of '91'. Rewrite or refactor the code to increase its maintainability index (MI) above '94'. + GetCSharpCA1505ExpectedDiagnostic(2, 7, "C", 91, 94), + // Test0.cs(4,10): warning CA1505: 'M1' has a maintainability index of '91'. Rewrite or refactor the code to increase its maintainability index (MI) above '94'. + GetCSharpCA1505ExpectedDiagnostic(4, 10, "M1", 91, 94)}; + VerifyCSharp(source, GetAdditionalFile(additionalText), expected); + } + + [Fact] + public void CA1505_Configuration_Basic_VerifyDiagnostic() + { + var source = @" +Class C + Private Sub M1(ByVal b As Boolean) + Dim x = b AndAlso b AndAlso b AndAlso b + End Sub +End Class +"; + + string additionalText = @" +# FORMAT: +# 'RuleId'(Optional 'SymbolKind'): 'Threshold' + +CA1505: 95 +"; + DiagnosticResult[] expected = new[] { + // Test0.vb(2,7): warning CA1505: 'C' has a maintainability index of '91'. Rewrite or refactor the code to increase its maintainability index (MI) above '94'. + GetBasicCA1505ExpectedDiagnostic(2, 7, "C", 91, 94), + // Test0.vb(3,17): warning CA1505: 'M1' has a maintainability index of '91'. Rewrite or refactor the code to increase its maintainability index (MI) above '94'. + GetBasicCA1505ExpectedDiagnostic(3, 17, "M1", 91, 94)}; + VerifyBasic(source, GetAdditionalFile(additionalText), expected); + } + + [Fact] + public void CA1505_SymbolBasedConfiguration_CSharp_VerifyDiagnostic() + { + var source = @" +class C +{ + void M1(bool b) + { + var x = b && b && b && b; + } +} +"; + + string additionalText = @" +# FORMAT: +# 'RuleId'(Optional 'SymbolKind'): 'Threshold' + +CA1505(Type): 95 +"; + DiagnosticResult[] expected = new[] { + // Test0.cs(2,7): warning CA1505: 'C' has a maintainability index of '91'. Rewrite or refactor the code to increase its maintainability index (MI) above '94'. + GetCSharpCA1505ExpectedDiagnostic(2, 7, "C", 91, 94)}; + VerifyCSharp(source, GetAdditionalFile(additionalText), expected); + } + + [Fact] + public void CA1505_SymbolBasedConfiguration_Basic_VerifyDiagnostic() + { + var source = @" +Class C + Private Sub M1(ByVal b As Boolean) + Dim x = b AndAlso b AndAlso b AndAlso b + End Sub +End Class +"; + + string additionalText = @" +# FORMAT: +# 'RuleId'(Optional 'SymbolKind'): 'Threshold' + +CA1505(Type): 95 +"; + DiagnosticResult[] expected = new[] { + // Test0.vb(2,7): warning CA1505: 'C' has a maintainability index of '91'. Rewrite or refactor the code to increase its maintainability index (MI) above '94'. + GetBasicCA1505ExpectedDiagnostic(2, 7, "C", 91, 94)}; + VerifyBasic(source, GetAdditionalFile(additionalText), expected); + } + + #endregion + + #region CA1506: Avoid excessive class coupling + + [Fact] + public void CA1506_Configuration_CSharp_VerifyDiagnostic() + { + var source = @" +class C +{ + void M1(C1 c1, C2 c2, C3 c3, N.C4 c4) + { + } +} + +class C1 { } +class C2 { } +class C3 { } +namespace N { class C4 { } } +"; + string additionalText = @" +# FORMAT: +# 'RuleId'(Optional 'SymbolKind'): 'Threshold' + +CA1506: 2 +"; + DiagnosticResult[] expected = new[] { + // Test0.cs(2,7): warning CA1506: 'C' is coupled with '4' different types from '2' different namespaces. Rewrite or refactor the code to decrease its class coupling below '3'. + GetCSharpCA1506ExpectedDiagnostic(2, 7, "C", 4, 2, 3), + // Test0.cs(4,10): warning CA1506: 'M1' is coupled with '4' different types from '2' different namespaces. Rewrite or refactor the code to decrease its class coupling below '3'. + GetCSharpCA1506ExpectedDiagnostic(4, 10, "M1", 4, 2, 3)}; + VerifyCSharp(source, GetAdditionalFile(additionalText), expected); + } + + [Fact] + public void CA1506_Configuration_Basic_VerifyDiagnostic() + { + var source = @" +Class C + Private Sub M1(c1 As C1, c2 As C2, c3 As C3, c4 As N.C4) + End Sub +End Class + +Class C1 +End Class + +Class C2 +End Class + +Class C3 +End Class + +Namespace N + Class C4 + End Class +End Namespace +"; + string additionalText = @" +# FORMAT: +# 'RuleId'(Optional 'SymbolKind'): 'Threshold' + +CA1506: 2 +"; + DiagnosticResult[] expected = new[] { + // Test0.vb(2,7): warning CA1506: 'C' is coupled with '4' different types from '2' different namespaces. Rewrite or refactor the code to decrease its class coupling below '3'. + GetBasicCA1506ExpectedDiagnostic(2, 7, "C", 4, 2, 3), + // Test0.vb(3,17): warning CA1506: 'M1' is coupled with '4' different types from '2' different namespaces. Rewrite or refactor the code to decrease its class coupling below '3'. + GetBasicCA1506ExpectedDiagnostic(3, 17, "M1", 4, 2, 3)}; + VerifyBasic(source, GetAdditionalFile(additionalText), expected); + } + + [Fact] + public void CA1506_SymbolBasedConfiguration_CSharp_VerifyDiagnostic() + { + var source = @" +class C +{ + void M1(C1 c1, C2 c2, C3 c3, N.C4 c4) + { + } +} + +class C1 { } +class C2 { } +class C3 { } +namespace N { class C4 { } } +"; + string additionalText = @" +# FORMAT: +# 'RuleId'(Optional 'SymbolKind'): 'Threshold' + +CA1506(Method): 2 +CA1506(Type): 10 +"; + DiagnosticResult[] expected = new[] { + // Test0.cs(4,10): warning CA1506: 'M1' is coupled with '4' different types from '2' different namespaces. Rewrite or refactor the code to decrease its class coupling below '3'. + GetCSharpCA1506ExpectedDiagnostic(4, 10, "M1", 4, 2, 3)}; + VerifyCSharp(source, GetAdditionalFile(additionalText), expected); + } + + [Fact] + public void CA1506_SymbolBasedConfiguration_Basic_VerifyDiagnostic() + { + var source = @" +Class C + Private Sub M1(c1 As C1, c2 As C2, c3 As C3, c4 As N.C4) + End Sub +End Class + +Class C1 +End Class + +Class C2 +End Class + +Class C3 +End Class + +Namespace N + Class C4 + End Class +End Namespace +"; + string additionalText = @" +# FORMAT: +# 'RuleId'(Optional 'SymbolKind'): 'Threshold' + +CA1506(Method): 2 +CA1506(Type): 10 +"; + DiagnosticResult[] expected = new[] { + // Test0.vb(3,17): warning CA1506: 'M1' is coupled with '4' different types from '2' different namespaces. Rewrite or refactor the code to decrease its class coupling below '3'. + GetBasicCA1506ExpectedDiagnostic(3, 17, "M1", 4, 2, 3)}; + VerifyBasic(source, GetAdditionalFile(additionalText), expected); + } + + #endregion + + #region CA1508 + + [Fact] + public void CA1508_VerifyDiagnostics() + { + var source = @""; + + string additionalText = @" +# FORMAT: +# 'RuleId'(Optional 'SymbolKind'): 'Threshold' + +# 1. Multiple colons +CA1501: 1 : 2 + +# 2. Whitespace in RuleId +CA 1501: 1 + +# 3. Invalid Code Metrics RuleId +CA1600: 1 + +# 4. Non-integral Threshold. +CA1501: None + +# 5. Not supported SymbolKind. +CA1501(Local): 1 + +# 6. Missing SymbolKind. +CA1501(: 1 + +# 7. Missing CloseParens after SymbolKind. +CA1501(Method: 1 + +# 8. Multiple SymbolKinds. +CA1501(Method)(Type): 1 + +# 9. Missing Threshold. +CA1501 +"; + DiagnosticResult[] expected = new[] { + // CodeMetricsConfig.txt(6,1): warning CA1508: Invalid entry 'CA1501: 1 : 2' in code metrics rule specification file 'CodeMetricsConfig.txt' + GetCA1508ExpectedDiagnostic(6, 1, "CA1501: 1 : 2", AdditionalFileName), + // CodeMetricsConfig.txt(9,1): warning CA1508: Invalid entry 'CA 1501: 1' in code metrics rule specification file 'CodeMetricsConfig.txt' + GetCA1508ExpectedDiagnostic(9, 1, "CA 1501: 1", AdditionalFileName), + // CodeMetricsConfig.txt(12,1): warning CA1508: Invalid entry 'CA1600: 1' in code metrics rule specification file 'CodeMetricsConfig.txt' + GetCA1508ExpectedDiagnostic(12, 1, "CA1600: 1", AdditionalFileName), + // CodeMetricsConfig.txt(15,1): warning CA1508: Invalid entry 'CA1501: None' in code metrics rule specification file 'CodeMetricsConfig.txt' + GetCA1508ExpectedDiagnostic(15, 1, "CA1501: None", AdditionalFileName), + // CodeMetricsConfig.txt(18,1): warning CA1508: Invalid entry 'CA1501(Local): 1' in code metrics rule specification file 'CodeMetricsConfig.txt' + GetCA1508ExpectedDiagnostic(18, 1, "CA1501(Local): 1", AdditionalFileName), + // CodeMetricsConfig.txt(21,1): warning CA1508: Invalid entry 'CA1501(: 1' in code metrics rule specification file 'CodeMetricsConfig.txt' + GetCA1508ExpectedDiagnostic(21, 1, "CA1501(: 1", AdditionalFileName), + // CodeMetricsConfig.txt(24,1): warning CA1508: Invalid entry 'CA1501(Method: 1' in code metrics rule specification file 'CodeMetricsConfig.txt' + GetCA1508ExpectedDiagnostic(24, 1, "CA1501(Method: 1", AdditionalFileName), + // CodeMetricsConfig.txt(27,1): warning CA1508: Invalid entry 'CA1501(Method)(Type): 1' in code metrics rule specification file 'CodeMetricsConfig.txt' + GetCA1508ExpectedDiagnostic(27, 1, "CA1501(Method)(Type): 1", AdditionalFileName), + // CodeMetricsConfig.txt(30,1): warning CA1508: Invalid entry 'CA1501' in code metrics rule specification file 'CodeMetricsConfig.txt' + GetCA1508ExpectedDiagnostic(30, 1, "CA1501", AdditionalFileName)}; + VerifyCSharp(source, GetAdditionalFile(additionalText), expected); + } + + [Fact] + public void CA1508_NoDiagnostics() + { + var source = @""; + + string additionalText = @" +# FORMAT: +# 'RuleId'(Optional 'SymbolKind'): 'Threshold' + +# 1. Duplicates are allowed +CA1501: 1 +CA1501: 2 + +# 2. Duplicate RuleId-SymbolKind pairs are allowed. +CA1501(Method): 1 +CA1501(Method): 1 + +# 3. All valid symbol kinds +CA1502(Assembly): 1 +CA1502(Namespace): 1 +CA1502(Type): 1 +CA1502(Method): 1 +CA1502(Field): 1 +CA1502(Property): 1 +CA1502(Event): 1 + +# 4. Whitespaces before and after the key-value pair are allowed. + CA1501: 1 + +# 5. Whitespaces before and after the colon are allowed. +CA1501 : 1 +"; + VerifyCSharp(source, GetAdditionalFile(additionalText)); + } + + [Fact] + public void CA1508_VerifyNoMetricDiagnostics() + { + // Ensure we don't report any code metric diagnostics when we have invalid entries in code metrics configuration file. + var source = @" +class BaseClass { } +class FirstDerivedClass : BaseClass { } +class SecondDerivedClass : FirstDerivedClass { } +class ThirdDerivedClass : SecondDerivedClass { } +class FourthDerivedClass : ThirdDerivedClass { } + +// This class violates the CA1501 rule for default threshold. +class FifthDerivedClass : FourthDerivedClass { }"; + + string additionalText = @" +# FORMAT: +# 'RuleId'(Optional 'SymbolKind'): 'Threshold' + +CA 1501: 10 +"; + DiagnosticResult[] expected = new[] { + // CodeMetricsConfig.txt(5,1): warning CA1508: Invalid entry 'CA 1501: 10' in code metrics rule specification file 'CodeMetricsConfig.txt' + GetCA1508ExpectedDiagnostic(5, 1, "CA 1501: 10", AdditionalFileName)}; + VerifyCSharp(source, GetAdditionalFile(additionalText), expected); + } + + #endregion + + #region Helpers + protected override CodeFixProvider GetCSharpCodeFixProvider() + { + return null; + } + + protected override CodeFixProvider GetBasicCodeFixProvider() + { + return null; + } + + protected override DiagnosticAnalyzer GetCSharpDiagnosticAnalyzer() + { + return new CodeMetricsAnalyzer(); + } + + protected override DiagnosticAnalyzer GetBasicDiagnosticAnalyzer() + { + return new CodeMetricsAnalyzer(); + } + + private static DiagnosticResult GetCSharpCA1501ExpectedDiagnostic(int line, int column, string symbolName, int metricValue, int threshold, string baseTypes) + { + return GetCA1501ExpectedDiagnostic(LanguageNames.CSharp, line, column, symbolName, metricValue, threshold, baseTypes); + } + + private static DiagnosticResult GetBasicCA1501ExpectedDiagnostic(int line, int column, string symbolName, int metricValue, int threshold, string baseTypes) + { + return GetCA1501ExpectedDiagnostic(LanguageNames.VisualBasic, line, column, symbolName, metricValue, threshold, baseTypes); + } + + private static DiagnosticResult GetCSharpCA1502ExpectedDiagnostic(int line, int column, string symbolName, int metricValue, int threshold) + { + return GetCA1502ExpectedDiagnostic(LanguageNames.CSharp, line, column, symbolName, metricValue, threshold); + } + + private static DiagnosticResult GetBasicCA1502ExpectedDiagnostic(int line, int column, string symbolName, int metricValue, int threshold) + { + return GetCA1502ExpectedDiagnostic(LanguageNames.VisualBasic, line, column, symbolName, metricValue, threshold); + } + + private static DiagnosticResult GetCSharpCA1505ExpectedDiagnostic(int line, int column, string symbolName, int metricValue, int threshold) + { + return GetCA1505ExpectedDiagnostic(LanguageNames.CSharp, line, column, symbolName, metricValue, threshold); + } + + private static DiagnosticResult GetBasicCA1505ExpectedDiagnostic(int line, int column, string symbolName, int metricValue, int threshold) + { + return GetCA1505ExpectedDiagnostic(LanguageNames.VisualBasic, line, column, symbolName, metricValue, threshold); + } + + private static DiagnosticResult GetCSharpCA1506ExpectedDiagnostic(int line, int column, string symbolName, int coupledTypesCount, int namespaceCount, int threshold) + { + return GetCA1506ExpectedDiagnostic(LanguageNames.CSharp, line, column, symbolName, coupledTypesCount, namespaceCount, threshold); + } + + private static DiagnosticResult GetBasicCA1506ExpectedDiagnostic(int line, int column, string symbolName, int coupledTypesCount, int namespaceCount, int threshold) + { + return GetCA1506ExpectedDiagnostic(LanguageNames.VisualBasic, line, column, symbolName, coupledTypesCount, namespaceCount, threshold); + } + + // '{0}' has an object hierarchy '{1}' levels deep within the defining module. If possible, eliminate base classes within the hierarchy to decrease its hierarchy level below '{2}': '{3}' + private static DiagnosticResult GetCA1501ExpectedDiagnostic(string language, int line, int column, string symbolName, int metricValue, int threshold, string baseTypes) + { + string fileName = language == LanguageNames.CSharp ? "Test0.cs" : "Test0.vb"; + return new DiagnosticResult + { + Id = CodeMetricsAnalyzer.CA1501RuleId, + Message = string.Format(MicrosoftMaintainabilityAnalyzersResources.AvoidExcessiveInheritanceMessage, symbolName, metricValue, threshold, baseTypes), + Severity = DiagnosticHelpers.DefaultDiagnosticSeverity, + Locations = new[] + { + new DiagnosticResultLocation(fileName, line, column) + } + }; + } + + // '{0}' has a cyclomatic complexity of '{1}'. Rewrite or refactor the code to decrease its complexity below '{2}'. + private static DiagnosticResult GetCA1502ExpectedDiagnostic(string language, int line, int column, string symbolName, int metricValue, int threshold) + { + string fileName = language == LanguageNames.CSharp ? "Test0.cs" : "Test0.vb"; + return new DiagnosticResult + { + Id = CodeMetricsAnalyzer.CA1502RuleId, + Message = string.Format(MicrosoftMaintainabilityAnalyzersResources.AvoidExcessiveComplexityMessage, symbolName, metricValue, threshold), + Severity = DiagnosticHelpers.DefaultDiagnosticSeverity, + Locations = new[] + { + new DiagnosticResultLocation(fileName, line, column) + } + }; + } + + // '{0}' has a maintainability index of '{1}'. Rewrite or refactor the code to increase its maintainability index (MI) above '{2}'. + private static DiagnosticResult GetCA1505ExpectedDiagnostic(string language, int line, int column, string symbolName, int metricValue, int threshold) + { + string fileName = language == LanguageNames.CSharp ? "Test0.cs" : "Test0.vb"; + return new DiagnosticResult + { + Id = CodeMetricsAnalyzer.CA1505RuleId, + Message = string.Format(MicrosoftMaintainabilityAnalyzersResources.AvoidUnmantainableCodeMessage, symbolName, metricValue, threshold), + Severity = DiagnosticHelpers.DefaultDiagnosticSeverity, + Locations = new[] + { + new DiagnosticResultLocation(fileName, line, column) + } + }; + } + + // '{0}' is coupled with '{1}' different types from '{2}' different namespaces. Rewrite or refactor the code to decrease its class coupling below '{3}'. + private static DiagnosticResult GetCA1506ExpectedDiagnostic(string language, int line, int column, string symbolName, int coupledTypesCount, int namespaceCount, int threshold) + { + string fileName = language == LanguageNames.CSharp ? "Test0.cs" : "Test0.vb"; + return new DiagnosticResult + { + Id = CodeMetricsAnalyzer.CA1506RuleId, + Message = string.Format(MicrosoftMaintainabilityAnalyzersResources.AvoidExcessiveClassCouplingMessage, symbolName, coupledTypesCount, namespaceCount, threshold), + Severity = DiagnosticHelpers.DefaultDiagnosticSeverity, + Locations = new[] + { + new DiagnosticResultLocation(fileName, line, column) + } + }; + } + + private static DiagnosticResult GetCA1508ExpectedDiagnostic(int line, int column, string entry, string additionalFile) + { + return new DiagnosticResult + { + Id = CodeMetricsAnalyzer.CA1508RuleId, + Message = string.Format(MicrosoftMaintainabilityAnalyzersResources.InvalidEntryInCodeMetricsConfigFileMessage, entry, additionalFile), + Severity = DiagnosticHelpers.DefaultDiagnosticSeverity, + Locations = new[] + { + new DiagnosticResultLocation(AdditionalFileName, line, column) + } + }; + } + + private const string AdditionalFileName = "CodeMetricsConfig.txt"; + private FileAndSource GetAdditionalFile(string source) + => new FileAndSource() { Source = source, FilePath = AdditionalFileName }; + + #endregion + } +} diff --git a/src/Microsoft.CodeQuality.Analyzers/UnitTests/Maintainability/CodeMetricsComputationTests.cs b/src/Microsoft.CodeQuality.Analyzers/UnitTests/Maintainability/CodeMetricsComputationTests.cs new file mode 100644 index 0000000000..212e71b180 --- /dev/null +++ b/src/Microsoft.CodeQuality.Analyzers/UnitTests/Maintainability/CodeMetricsComputationTests.cs @@ -0,0 +1,1796 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Threading; +using Test.Utilities.CodeMetrics; +using Xunit; + +namespace Microsoft.CodeAnalysis.CodeMetrics.UnitTests +{ + public class CodeMetricsComputationTests : CodeMetricsTestBase + { + protected override string GetMetricsDataString(Compilation compilation) + { + return CodeAnalysisMetricData.ComputeAsync(compilation, CancellationToken.None).Result.ToString(); + } + + [Fact] + public void EmptyCompilation() + { + var source = @""; + + var expectedMetricsText = @" +Assembly: (Lines: 0, MntIndex: 100, CycCxty: 0, DepthInherit: 0)"; + + VerifyCSharp(source, expectedMetricsText); + } + + [Fact] + public void EmptyNamespace() + { + var source = @" +namespace N { }"; + + var expectedMetricsText = @" +Assembly: (Lines: 0, MntIndex: 100, CycCxty: 0, DepthInherit: 0)"; + + VerifyCSharp(source, expectedMetricsText); + } + + [Fact] + public void EmptyNamespaces() + { + var source = @" +namespace N1 { } + +namespace N2 +{ +}"; + + var expectedMetricsText = @" +Assembly: (Lines: 0, MntIndex: 100, CycCxty: 0, DepthInherit: 0)"; + + VerifyCSharp(source, expectedMetricsText); + } + + [Fact] + public void TypeInNamespace() + { + var source = @" +namespace N1 +{ + class C { } +}"; + + var expectedMetricsText = @" +Assembly: (Lines: 4, MntIndex: 100, CycCxty: 1, DepthInherit: 1) + N1: (Lines: 4, MntIndex: 100, CycCxty: 1, DepthInherit: 1) + C: (Lines: 1, MntIndex: 100, CycCxty: 1, DepthInherit: 1) +"; + + VerifyCSharp(source, expectedMetricsText); + } + + [Fact] + public void TypeInGlobalNamespace() + { + var source = @" +class C +{ +}"; + + var expectedMetricsText = @" +Assembly: (Lines: 3, MntIndex: 100, CycCxty: 1, DepthInherit: 1) + C: (Lines: 3, MntIndex: 100, CycCxty: 1, DepthInherit: 1) +"; + + VerifyCSharp(source, expectedMetricsText); + } + + [Fact] + public void TypesInNamespaces() + { + var source = @" +namespace N1 { class C1 { } } + +namespace N2 +{ + class C2 { } + class C3 + { + } +} +"; + + var expectedMetricsText = @" +Assembly: (Lines: 10, MntIndex: 100, CycCxty: 3, DepthInherit: 1) + N1: (Lines: 2, MntIndex: 100, CycCxty: 1, DepthInherit: 1) + C1: (Lines: 1, MntIndex: 100, CycCxty: 1, DepthInherit: 1) + N2: (Lines: 8, MntIndex: 100, CycCxty: 2, DepthInherit: 1) + C2: (Lines: 1, MntIndex: 100, CycCxty: 1, DepthInherit: 1) + C3: (Lines: 3, MntIndex: 100, CycCxty: 1, DepthInherit: 1) +"; + + VerifyCSharp(source, expectedMetricsText); + } + + [Fact] + public void TypesInChildAndParentNamespaces() + { + var source = @" +namespace N1 { class C1 { } } + +namespace N1.N2 +{ + class C2 { } +} +"; + + var expectedMetricsText = @" +Assembly: (Lines: 7, MntIndex: 100, CycCxty: 2, DepthInherit: 1) + N1: (Lines: 2, MntIndex: 100, CycCxty: 1, DepthInherit: 1) + C1: (Lines: 1, MntIndex: 100, CycCxty: 1, DepthInherit: 1) + N2: (Lines: 5, MntIndex: 100, CycCxty: 1, DepthInherit: 1) + C2: (Lines: 1, MntIndex: 100, CycCxty: 1, DepthInherit: 1) +"; + + VerifyCSharp(source, expectedMetricsText); + } + + [Fact] + public void TypesInDifferentSourceFiles() + { + var source1 = @" +namespace N1 { class C1 { } } +"; + + var source2 = @" +namespace N2 +{ + class C2 { } + class C3 + { + } +} +"; + + var expectedMetricsText = @" +Assembly: (Lines: 10, MntIndex: 100, CycCxty: 3, DepthInherit: 1) + N1: (Lines: 2, MntIndex: 100, CycCxty: 1, DepthInherit: 1) + C1: (Lines: 1, MntIndex: 100, CycCxty: 1, DepthInherit: 1) + N2: (Lines: 8, MntIndex: 100, CycCxty: 2, DepthInherit: 1) + C2: (Lines: 1, MntIndex: 100, CycCxty: 1, DepthInherit: 1) + C3: (Lines: 3, MntIndex: 100, CycCxty: 1, DepthInherit: 1) +"; + + VerifyCSharp(new[] { source1, source2 }, expectedMetricsText); + } + + [Fact] + public void PartialTypeDeclarationsInSameSourceFile() + { + var source = @" +partial class C1 +{ + void M1(int x) + { + x = 0; + } +} + +partial class C1 +{ + void M2(int x) + { + x = 0; + } +} +"; + + var expectedMetricsText = @" +Assembly: (Lines: 16, MntIndex: 95, CycCxty: 2, DepthInherit: 1) + C1: (Lines: 16, MntIndex: 95, CycCxty: 2, DepthInherit: 1) + C1.M1(int): (Lines: 4, MntIndex: 97, CycCxty: 1) + C1.M2(int): (Lines: 4, MntIndex: 97, CycCxty: 1) +"; + + VerifyCSharp(source, expectedMetricsText); + } + + [Fact] + public void PartialTypeDeclarationsInDifferentSourceFiles() + { + var source1 = @" +partial class C1 +{ + void M1(int x) + { + x = 0; + } +} +"; + + var source2 = @" +partial class C1 +{ + void M2(int x) + { + x = 0; + } +} +"; + + var expectedMetricsText = @" +Assembly: (Lines: 16, MntIndex: 95, CycCxty: 2, DepthInherit: 1) + C1: (Lines: 16, MntIndex: 95, CycCxty: 2, DepthInherit: 1) + C1.M1(int): (Lines: 4, MntIndex: 97, CycCxty: 1) + C1.M2(int): (Lines: 4, MntIndex: 97, CycCxty: 1) +"; + + VerifyCSharp(new[] { source1, source2 }, expectedMetricsText); + } + + [Fact] + public void NestedType() + { + var source = @" +namespace N1 +{ + class C1 + { + class NestedType + { + void M1(int x) + { + x = 0; + } + } + } +} +"; + + var expectedMetricsText = @" +Assembly: (Lines: 14, MntIndex: 98, CycCxty: 2, DepthInherit: 1) + N1: (Lines: 14, MntIndex: 98, CycCxty: 2, DepthInherit: 1) + C1: (Lines: 10, MntIndex: 100, CycCxty: 1, DepthInherit: 1) + NestedType: (Lines: 7, MntIndex: 97, CycCxty: 1, DepthInherit: 1) + N1.C1.NestedType.M1(int): (Lines: 4, MntIndex: 97, CycCxty: 1) +"; + + VerifyCSharp(source, expectedMetricsText); + } + + [Fact] + public void GenericType() + { + var source = @" +namespace N1 +{ + class C { } +}"; + + var expectedMetricsText = @" +Assembly: (Lines: 4, MntIndex: 100, CycCxty: 1, DepthInherit: 1) + N1: (Lines: 4, MntIndex: 100, CycCxty: 1, DepthInherit: 1) + C: (Lines: 1, MntIndex: 100, CycCxty: 1, DepthInherit: 1) +"; + + VerifyCSharp(source, expectedMetricsText); + } + + [Fact] + public void NestedTypeInTopLevelType() + { + var source = @" +class C1 +{ + class NestedType + { + void M1(int x) + { + x = 0; + } + } +} +"; + + var expectedMetricsText = @" +Assembly: (Lines: 11, MntIndex: 98, CycCxty: 2, DepthInherit: 1) + C1: (Lines: 11, MntIndex: 100, CycCxty: 1, DepthInherit: 1) + NestedType: (Lines: 7, MntIndex: 97, CycCxty: 1, DepthInherit: 1) + C1.NestedType.M1(int): (Lines: 4, MntIndex: 97, CycCxty: 1) +"; + + VerifyCSharp(source, expectedMetricsText); + } + + [Fact] + public void TypesWithInheritance() + { + var source = @" +namespace N1 +{ + class C1 { } + + class C2 : C1 { } +}"; + + var expectedMetricsText = @" +Assembly: (Lines: 6, MntIndex: 100, CycCxty: 2, CoupledTypes: {N1.C1}, DepthInherit: 2) + N1: (Lines: 6, MntIndex: 100, CycCxty: 2, CoupledTypes: {N1.C1}, DepthInherit: 2) + C1: (Lines: 1, MntIndex: 100, CycCxty: 1, DepthInherit: 1) + C2: (Lines: 2, MntIndex: 100, CycCxty: 1, CoupledTypes: {N1.C1}, DepthInherit: 2) +"; + + VerifyCSharp(source, expectedMetricsText); + } + + [Fact] + public void TypeWithCouplingFromBaseType() + { + var source = @" +namespace N1 +{ + class C1 { } + + class C2 { } + + class C3 : C2 { } +}"; + + var expectedMetricsText = @" +Assembly: (Lines: 8, MntIndex: 100, CycCxty: 3, CoupledTypes: {N1.C1, N1.C2}, DepthInherit: 2) + N1: (Lines: 8, MntIndex: 100, CycCxty: 3, CoupledTypes: {N1.C1, N1.C2}, DepthInherit: 2) + C1: (Lines: 1, MntIndex: 100, CycCxty: 1, DepthInherit: 1) + C2: (Lines: 2, MntIndex: 100, CycCxty: 1, DepthInherit: 1) + C3: (Lines: 2, MntIndex: 100, CycCxty: 1, CoupledTypes: {N1.C1, N1.C2}, DepthInherit: 2) +"; + + VerifyCSharp(source, expectedMetricsText); + } + + [Fact] + public void TypeWithCouplingFromAttribute() + { + var source = @" +namespace N1 +{ + class C1: System.Attribute { } + + [C1] + class C2 { } +}"; + + var expectedMetricsText = @" +Assembly: (Lines: 7, MntIndex: 100, CycCxty: 2, CoupledTypes: {N1.C1, System.Attribute}, DepthInherit: 2) + N1: (Lines: 7, MntIndex: 100, CycCxty: 2, CoupledTypes: {N1.C1, System.Attribute}, DepthInherit: 2) + C1: (Lines: 1, MntIndex: 100, CycCxty: 1, CoupledTypes: {System.Attribute}, DepthInherit: 2) + C2: (Lines: 3, MntIndex: 100, CycCxty: 1, CoupledTypes: {N1.C1}, DepthInherit: 1) +"; + + VerifyCSharp(source, expectedMetricsText); + } + + [Fact] + public void SimpleEmptyMethod() + { + var source = @"class C { void M() { } }"; + + var expectedMetricsText = @" +Assembly: (Lines: 1, MntIndex: 100, CycCxty: 1, DepthInherit: 1) + C: (Lines: 1, MntIndex: 100, CycCxty: 1, DepthInherit: 1) + C.M(): (Lines: 1, MntIndex: 100, CycCxty: 1) +"; + + VerifyCSharp(source, expectedMetricsText); + } + + [Fact] + public void SimpleEmptyMethod2() + { + var source = @" +class C +{ + void M() + { + } +}"; + + var expectedMetricsText = @" +Assembly: (Lines: 6, MntIndex: 100, CycCxty: 1, DepthInherit: 1) + C: (Lines: 6, MntIndex: 100, CycCxty: 1, DepthInherit: 1) + C.M(): (Lines: 3, MntIndex: 100, CycCxty: 1) +"; + + VerifyCSharp(source, expectedMetricsText); + } + + [Fact] + public void MethodWithIgnoreableParametersAndReturnType() + { + var source = @" +class C +{ + int M(string s, object o) + { + return 0; + } +}"; + + var expectedMetricsText = @" +Assembly: (Lines: 7, MntIndex: 100, CycCxty: 1, DepthInherit: 1) + C: (Lines: 7, MntIndex: 100, CycCxty: 1, DepthInherit: 1) + C.M(string, object): (Lines: 4, MntIndex: 100, CycCxty: 1) +"; + + VerifyCSharp(source, expectedMetricsText); + } + + [Fact] + public void MethodWithParametersAndReturnType() + { + var source = @" +class C +{ + C1 M(C2 s, C3 o) + { + return null; + } +} + +class C1 { } +class C2 { } +class C3 { } +"; + + var expectedMetricsText = @" +Assembly: (Lines: 12, MntIndex: 100, CycCxty: 4, CoupledTypes: {C1, C2, C3}, DepthInherit: 1) + C: (Lines: 8, MntIndex: 100, CycCxty: 1, CoupledTypes: {C1, C2, C3}, DepthInherit: 1) + C.M(C2, C3): (Lines: 4, MntIndex: 100, CycCxty: 1, CoupledTypes: {C1, C2, C3}) + C1: (Lines: 2, MntIndex: 100, CycCxty: 1, DepthInherit: 1) + C2: (Lines: 1, MntIndex: 100, CycCxty: 1, DepthInherit: 1) + C3: (Lines: 1, MntIndex: 100, CycCxty: 1, DepthInherit: 1) +"; + + VerifyCSharp(source, expectedMetricsText); + } + + [Fact] + public void MethodWithParameterInitializers() + { + var source = @" +class C1 +{ + void M(int i = C2.MyConst) + { + } +} + +class C2 +{ + public const int MyConst = 0; +} +"; + + var expectedMetricsText = @" +Assembly: (Lines: 12, MntIndex: 93, CycCxty: 2, CoupledTypes: {C2}, DepthInherit: 1) + C1: (Lines: 7, MntIndex: 93, CycCxty: 1, CoupledTypes: {C2}, DepthInherit: 1) + C1.M(int): (Lines: 3, MntIndex: 93, CycCxty: 1, CoupledTypes: {C2}) + C2: (Lines: 5, MntIndex: 93, CycCxty: 1, DepthInherit: 1) + C2.MyConst: (Lines: 1, MntIndex: 93, CycCxty: 0) +"; + + VerifyCSharp(source, expectedMetricsText); + } + + [Fact] + public void MethodWithTypeReferencesInBody() + { + var source = @" +class C +{ + object M(C1 c) + { + C2 c2 = C4.MyC2; + return (C3)null; + } +} + +class C1 { } +class C2 { } +class C3 : C1 { } +class C4 { public static C2 MyC2 = null; } +"; + + var expectedMetricsText = @" +Assembly: (Lines: 14, MntIndex: 95, CycCxty: 5, CoupledTypes: {C1, C2, C3, C4}, DepthInherit: 2) + C: (Lines: 9, MntIndex: 86, CycCxty: 1, CoupledTypes: {C1, C2, C3, C4}, DepthInherit: 1) + C.M(C1): (Lines: 5, MntIndex: 86, CycCxty: 1, CoupledTypes: {C1, C2, C3, C4}) + C1: (Lines: 2, MntIndex: 100, CycCxty: 1, DepthInherit: 1) + C2: (Lines: 1, MntIndex: 100, CycCxty: 1, DepthInherit: 1) + C3: (Lines: 1, MntIndex: 100, CycCxty: 1, CoupledTypes: {C1}, DepthInherit: 2) + C4: (Lines: 1, MntIndex: 93, CycCxty: 1, CoupledTypes: {C2}, DepthInherit: 1) + C4.MyC2: (Lines: 1, MntIndex: 93, CycCxty: 0, CoupledTypes: {C2}) +"; + + VerifyCSharp(source, expectedMetricsText); + } + + [Fact] + public void MethodsWithDifferentTypeReferencesInBody() + { + var source = @" +class C +{ + object M1(C1 c) + { + return (I)null; + } + + object M2(C1 c) + { + return (C2)null; + } +} + +interface I { } +class C1: I { } +class C2 : C1 { } +"; + + var expectedMetricsText = @" +Assembly: (Lines: 17, MntIndex: 100, CycCxty: 5, CoupledTypes: {C1, C2, I}, DepthInherit: 2) + C: (Lines: 13, MntIndex: 100, CycCxty: 2, CoupledTypes: {C1, C2, I}, DepthInherit: 1) + C.M1(C1): (Lines: 4, MntIndex: 100, CycCxty: 1, CoupledTypes: {C1, I}) + C.M2(C1): (Lines: 5, MntIndex: 100, CycCxty: 1, CoupledTypes: {C1, C2}) + C1: (Lines: 1, MntIndex: 100, CycCxty: 1, CoupledTypes: {I}, DepthInherit: 1) + C2: (Lines: 1, MntIndex: 100, CycCxty: 1, CoupledTypes: {C1}, DepthInherit: 2) + I: (Lines: 2, MntIndex: 100, CycCxty: 1, DepthInherit: 0) +"; + + VerifyCSharp(source, expectedMetricsText); + } + + [Fact] + public void MethodWithTypeReferencesInAttributes() + { + var source = @" +class C1 +{ + [CAttr(C2.MyConst)] + [return: CAttr(C3.MyConst)] + void M([CAttr(C4.MyConst)]int p) + { + } +} + +class CAttr : System.Attribute { public CAttr(string s) { } } + +class C2 +{ + public const string MyConst = nameof(MyConst); +} + +class C3 +{ + public const string MyConst = nameof(MyConst); +} + +class C4 +{ + public const string MyConst = nameof(MyConst); +} +"; + + var expectedMetricsText = @" +Assembly: (Lines: 26, MntIndex: 94, CycCxty: 5, CoupledTypes: {C2, C3, C4, CAttr, System.Attribute}, DepthInherit: 2) + C1: (Lines: 9, MntIndex: 100, CycCxty: 1, CoupledTypes: {C2, C3, C4, CAttr}, DepthInherit: 1) + C1.M(int): (Lines: 5, MntIndex: 100, CycCxty: 1, CoupledTypes: {C2, C3, C4, CAttr}) + C2: (Lines: 5, MntIndex: 90, CycCxty: 1, DepthInherit: 1) + C2.MyConst: (Lines: 1, MntIndex: 90, CycCxty: 0) + C3: (Lines: 5, MntIndex: 90, CycCxty: 1, DepthInherit: 1) + C3.MyConst: (Lines: 1, MntIndex: 90, CycCxty: 0) + C4: (Lines: 5, MntIndex: 90, CycCxty: 1, DepthInherit: 1) + C4.MyConst: (Lines: 1, MntIndex: 90, CycCxty: 0) + CAttr: (Lines: 2, MntIndex: 100, CycCxty: 1, CoupledTypes: {System.Attribute}, DepthInherit: 2) + CAttr.CAttr(string): (Lines: 1, MntIndex: 100, CycCxty: 1) +"; + + VerifyCSharp(source, expectedMetricsText); + } + + [Fact] + public void FieldWithIgnoreableType() + { + var source = @" +public class C +{ + public int f; +}"; + + var expectedMetricsText = @" +Assembly: (Lines: 4, MntIndex: 100, CycCxty: 1, DepthInherit: 1) + C: (Lines: 4, MntIndex: 100, CycCxty: 1, DepthInherit: 1) + C.f: (Lines: 1, MntIndex: 100, CycCxty: 0) +"; + + VerifyCSharp(source, expectedMetricsText); + } + + [Fact] + public void FieldWithNamedType() + { + var source = @" +class C1 +{ + private C2 f = new C2(); +} + +class C2 { } +"; + + var expectedMetricsText = @" +Assembly: (Lines: 7, MntIndex: 96, CycCxty: 2, CoupledTypes: {C2}, DepthInherit: 1) + C1: (Lines: 5, MntIndex: 93, CycCxty: 1, CoupledTypes: {C2}, DepthInherit: 1) + C1.f: (Lines: 1, MntIndex: 93, CycCxty: 0, CoupledTypes: {C2}) + C2: (Lines: 2, MntIndex: 100, CycCxty: 1, DepthInherit: 1) +"; + + VerifyCSharp(source, expectedMetricsText); + } + + [Fact] + public void FieldWithInitializer() + { + var source = @" +class C1 +{ + private C2 f = new C2(); +} + +class C2 { } +"; + + var expectedMetricsText = @" +Assembly: (Lines: 7, MntIndex: 96, CycCxty: 2, CoupledTypes: {C2}, DepthInherit: 1) + C1: (Lines: 5, MntIndex: 93, CycCxty: 1, CoupledTypes: {C2}, DepthInherit: 1) + C1.f: (Lines: 1, MntIndex: 93, CycCxty: 0, CoupledTypes: {C2}) + C2: (Lines: 2, MntIndex: 100, CycCxty: 1, DepthInherit: 1) +"; + + VerifyCSharp(source, expectedMetricsText); + } + + [Fact] + public void FieldWithTypeReferencesInInitializer() + { + var source = @" +class C1 +{ + private C2 f = (C3)C4.MyC2; +} + +class C2 { } +class C3 : C2 { } +class C4 { public static C2 MyC2 = null; } +"; + + var expectedMetricsText = @" +Assembly: (Lines: 9, MntIndex: 96, CycCxty: 4, CoupledTypes: {C2, C3, C4}, DepthInherit: 2) + C1: (Lines: 5, MntIndex: 93, CycCxty: 1, CoupledTypes: {C2, C3, C4}, DepthInherit: 1) + C1.f: (Lines: 1, MntIndex: 93, CycCxty: 0, CoupledTypes: {C2, C3, C4}) + C2: (Lines: 2, MntIndex: 100, CycCxty: 1, DepthInherit: 1) + C3: (Lines: 1, MntIndex: 100, CycCxty: 1, CoupledTypes: {C2}, DepthInherit: 2) + C4: (Lines: 1, MntIndex: 93, CycCxty: 1, CoupledTypes: {C2}, DepthInherit: 1) + C4.MyC2: (Lines: 1, MntIndex: 93, CycCxty: 0, CoupledTypes: {C2}) +"; + + VerifyCSharp(source, expectedMetricsText); + } + + [Fact] + public void FieldAndMethodWithTypeReferencesInInitializer() + { + var source = @" +class C1 +{ + private C2 f = (C3)C4.MyC2; + + object M(C1 c) + { + C2 c2 = C4.MyC2; + return (C3)null; + } +} + +class C2 { } +class C3 : C2 { } +class C4 { public static C2 MyC2 = null; } +"; + + var expectedMetricsText = @" +Assembly: (Lines: 15, MntIndex: 95, CycCxty: 4, CoupledTypes: {C2, C3, C4}, DepthInherit: 2) + C1: (Lines: 11, MntIndex: 87, CycCxty: 1, CoupledTypes: {C2, C3, C4}, DepthInherit: 1) + C1.f: (Lines: 1, MntIndex: 93, CycCxty: 0, CoupledTypes: {C2, C3, C4}) + C1.M(C1): (Lines: 6, MntIndex: 86, CycCxty: 1, CoupledTypes: {C2, C3, C4}) + C2: (Lines: 2, MntIndex: 100, CycCxty: 1, DepthInherit: 1) + C3: (Lines: 1, MntIndex: 100, CycCxty: 1, CoupledTypes: {C2}, DepthInherit: 2) + C4: (Lines: 1, MntIndex: 93, CycCxty: 1, CoupledTypes: {C2}, DepthInherit: 1) + C4.MyC2: (Lines: 1, MntIndex: 93, CycCxty: 0, CoupledTypes: {C2}) +"; + + VerifyCSharp(source, expectedMetricsText); + } + + [Fact] + public void ManySimpleFieldsAndOneComplexMethod() + { + var source = @" +public class C1 +{ + public object f1 = null; + public object f2 = null; + public object f3 = null; + public object f4 = null; + public object f5 = null; + public object f6 = null; + public object f7 = null; + public object f8 = null; + public object f9 = null; + public object f10 = null; + public object f11 = null; + public object f12 = null; + public object f13 = null; + public object f14 = null; + public object f15 = null; + public object f16 = null; + public object f17 = null; + public object f18 = null; + public object f19 = null; + public object f20 = null; + + void MultipleLogicals(bool b1, bool b2, bool b3, bool b4, bool b5) + { + var x1 = b1 && b2 || b3; + var x2 = b1 && (b2 && b3 || b4); + var x3 = b3 && b4 || b5; + var x4 = b1 && (b2 && b3 || b4 && b5); + var x5 = b1 && (b2 && b3 || b4); + } +} +"; + + var expectedMetricsText = @" +Assembly: (Lines: 33, MntIndex: 77, CycCxty: 15, DepthInherit: 1) + C1: (Lines: 33, MntIndex: 77, CycCxty: 15, DepthInherit: 1) + C1.f1: (Lines: 1, MntIndex: 93, CycCxty: 0) + C1.f2: (Lines: 1, MntIndex: 93, CycCxty: 0) + C1.f3: (Lines: 1, MntIndex: 93, CycCxty: 0) + C1.f4: (Lines: 1, MntIndex: 93, CycCxty: 0) + C1.f5: (Lines: 1, MntIndex: 93, CycCxty: 0) + C1.f6: (Lines: 1, MntIndex: 93, CycCxty: 0) + C1.f7: (Lines: 1, MntIndex: 93, CycCxty: 0) + C1.f8: (Lines: 1, MntIndex: 93, CycCxty: 0) + C1.f9: (Lines: 1, MntIndex: 93, CycCxty: 0) + C1.f10: (Lines: 1, MntIndex: 93, CycCxty: 0) + C1.f11: (Lines: 1, MntIndex: 93, CycCxty: 0) + C1.f12: (Lines: 1, MntIndex: 93, CycCxty: 0) + C1.f13: (Lines: 1, MntIndex: 93, CycCxty: 0) + C1.f14: (Lines: 1, MntIndex: 93, CycCxty: 0) + C1.f15: (Lines: 1, MntIndex: 93, CycCxty: 0) + C1.f16: (Lines: 1, MntIndex: 93, CycCxty: 0) + C1.f17: (Lines: 1, MntIndex: 93, CycCxty: 0) + C1.f18: (Lines: 1, MntIndex: 93, CycCxty: 0) + C1.f19: (Lines: 1, MntIndex: 93, CycCxty: 0) + C1.f20: (Lines: 1, MntIndex: 93, CycCxty: 0) + C1.MultipleLogicals(bool, bool, bool, bool, bool): (Lines: 9, MntIndex: 67, CycCxty: 15) +"; + + VerifyCSharp(source, expectedMetricsText); + } + + [Fact] + public void MultipleFieldsWithDifferentTypeReferencesInBody() + { + var source = @" +class C +{ + private object f1 = (I)new C1(); + private object f2 = (C2)new C1(); +} + +interface I { } +class C1: I { } +class C2 : C1 { } +"; + + var expectedMetricsText = @" +Assembly: (Lines: 10, MntIndex: 98, CycCxty: 4, CoupledTypes: {C1, C2, I}, DepthInherit: 2) + C: (Lines: 6, MntIndex: 93, CycCxty: 1, CoupledTypes: {C1, C2, I}, DepthInherit: 1) + C.f1: (Lines: 1, MntIndex: 93, CycCxty: 0, CoupledTypes: {C1, I}) + C.f2: (Lines: 1, MntIndex: 93, CycCxty: 0, CoupledTypes: {C1, C2}) + C1: (Lines: 1, MntIndex: 100, CycCxty: 1, CoupledTypes: {I}, DepthInherit: 1) + C2: (Lines: 1, MntIndex: 100, CycCxty: 1, CoupledTypes: {C1}, DepthInherit: 2) + I: (Lines: 2, MntIndex: 100, CycCxty: 1, DepthInherit: 0) +"; + + VerifyCSharp(source, expectedMetricsText); + } + + [Fact] + public void MultipleFieldsInSameDeclaration() + { + var source = @" +class C +{ + private object f1 = (I)new C1(), f2 = (C2)new C1(); +} + +interface I { } +class C1: I { } +class C2 : C1 { } +"; + + var expectedMetricsText = @" +Assembly: (Lines: 9, MntIndex: 98, CycCxty: 4, CoupledTypes: {C1, C2, I}, DepthInherit: 2) + C: (Lines: 5, MntIndex: 93, CycCxty: 1, CoupledTypes: {C1, C2, I}, DepthInherit: 1) + C.f1: (Lines: 1, MntIndex: 93, CycCxty: 0, CoupledTypes: {C1, I}) + C.f2: (Lines: 1, MntIndex: 93, CycCxty: 0, CoupledTypes: {C1, C2}) + C1: (Lines: 1, MntIndex: 100, CycCxty: 1, CoupledTypes: {I}, DepthInherit: 1) + C2: (Lines: 1, MntIndex: 100, CycCxty: 1, CoupledTypes: {C1}, DepthInherit: 2) + I: (Lines: 2, MntIndex: 100, CycCxty: 1, DepthInherit: 0) +"; + + VerifyCSharp(source, expectedMetricsText); + } + + [Fact] + public void FieldWithTypeReferencesInAttribute() + { + var source = @" +public class C1 +{ + [System.Obsolete(C2.MyConst)] + public object f = null; +} + +public class C2 +{ + public const string MyConst = nameof(MyConst); +} +"; + + var expectedMetricsText = @" +Assembly: (Lines: 11, MntIndex: 90, CycCxty: 2, CoupledTypes: {C2, System.ObsoleteAttribute}, DepthInherit: 1) + C1: (Lines: 6, MntIndex: 90, CycCxty: 1, CoupledTypes: {C2, System.ObsoleteAttribute}, DepthInherit: 1) + C1.f: (Lines: 1, MntIndex: 91, CycCxty: 0, CoupledTypes: {C2, System.ObsoleteAttribute}) + C2: (Lines: 5, MntIndex: 90, CycCxty: 1, DepthInherit: 1) + C2.MyConst: (Lines: 1, MntIndex: 90, CycCxty: 0) +"; + + VerifyCSharp(source, expectedMetricsText); + } + + [Fact] + public void SimpleEmptyProperty() + { + var source = @"class C { int P { get; } }"; + + var expectedMetricsText = @" +Assembly: (Lines: 1, MntIndex: 100, CycCxty: 1, DepthInherit: 1) + C: (Lines: 1, MntIndex: 100, CycCxty: 1, DepthInherit: 1) + C.P: (Lines: 1, MntIndex: 100, CycCxty: 1) + C.P.get: (Lines: 1, MntIndex: 100, CycCxty: 1) +"; + + VerifyCSharp(source, expectedMetricsText); + } + + [Fact] + public void SimpleEmptyProperty2() + { + var source = @" +class C +{ + int P + { + get + { + return 0; + } + } +}"; + + var expectedMetricsText = @" +Assembly: (Lines: 10, MntIndex: 100, CycCxty: 1, DepthInherit: 1) + C: (Lines: 10, MntIndex: 100, CycCxty: 1, DepthInherit: 1) + C.P: (Lines: 7, MntIndex: 100, CycCxty: 1) + C.P.get: (Lines: 4, MntIndex: 100, CycCxty: 1) +"; + + VerifyCSharp(source, expectedMetricsText); + } + + [Fact] + public void SimpleEmptyProperty3() + { + var source = @"class C { int P { get; set; } }"; + + var expectedMetricsText = @" +Assembly: (Lines: 1, MntIndex: 100, CycCxty: 2, DepthInherit: 1) + C: (Lines: 1, MntIndex: 100, CycCxty: 2, DepthInherit: 1) + C.P: (Lines: 1, MntIndex: 100, CycCxty: 2) + C.P.get: (Lines: 1, MntIndex: 100, CycCxty: 1) + C.P.set: (Lines: 1, MntIndex: 100, CycCxty: 1) +"; + + VerifyCSharp(source, expectedMetricsText); + } + + [Fact] + public void SimpleEmptyProperty4() + { + var source = @" +class C +{ + int P + { + get + { + return 0; + } + set + { + } + } +}"; + + var expectedMetricsText = @" +Assembly: (Lines: 13, MntIndex: 100, CycCxty: 2, DepthInherit: 1) + C: (Lines: 13, MntIndex: 100, CycCxty: 2, DepthInherit: 1) + C.P: (Lines: 10, MntIndex: 100, CycCxty: 2) + C.P.get: (Lines: 4, MntIndex: 100, CycCxty: 1) + C.P.set: (Lines: 3, MntIndex: 100, CycCxty: 1) +"; + + VerifyCSharp(source, expectedMetricsText); + } + + [Fact] + public void PropertyWithIgnoreableParametersAndReturnType() + { + var source = @" +class C +{ + int this[object x] { get { return 0; } set { } } +}"; + + var expectedMetricsText = @" +Assembly: (Lines: 4, MntIndex: 100, CycCxty: 2, DepthInherit: 1) + C: (Lines: 4, MntIndex: 100, CycCxty: 2, DepthInherit: 1) + C.this[object]: (Lines: 1, MntIndex: 100, CycCxty: 2) + C.this[object].get: (Lines: 1, MntIndex: 100, CycCxty: 1) + C.this[object].set: (Lines: 1, MntIndex: 100, CycCxty: 1) +"; + + VerifyCSharp(source, expectedMetricsText); + } + + [Fact] + public void PropertyWithParametersAndReturnType() + { + var source = @" +class C +{ + C1 this[C2 x] { get { return null; } set { } } +} + +class C1 { } +class C2 { } +"; + + var expectedMetricsText = @" +Assembly: (Lines: 8, MntIndex: 100, CycCxty: 4, CoupledTypes: {C1, C2}, DepthInherit: 1) + C: (Lines: 5, MntIndex: 100, CycCxty: 2, CoupledTypes: {C1, C2}, DepthInherit: 1) + C.this[C2]: (Lines: 1, MntIndex: 100, CycCxty: 2, CoupledTypes: {C1, C2}) + C.this[C2].get: (Lines: 1, MntIndex: 100, CycCxty: 1, CoupledTypes: {C1, C2}) + C.this[C2].set: (Lines: 1, MntIndex: 100, CycCxty: 1, CoupledTypes: {C1, C2}) + C1: (Lines: 2, MntIndex: 100, CycCxty: 1, DepthInherit: 1) + C2: (Lines: 1, MntIndex: 100, CycCxty: 1, DepthInherit: 1) +"; + + VerifyCSharp(source, expectedMetricsText); + } + + [Fact] + public void PropertyWithParameterInitializers() + { + var source = @" +class C +{ +#pragma warning disable CS1066 + C1 this[int i = C2.MyConst] { get { return null; } set { } } +} + +class C1 { } + +class C2 +{ + public const int MyConst = 0; +} +"; + + var expectedMetricsText = @" +Assembly: (Lines: 13, MntIndex: 97, CycCxty: 4, CoupledTypes: {C1, C2}, DepthInherit: 1) + C: (Lines: 6, MntIndex: 100, CycCxty: 2, CoupledTypes: {C1, C2}, DepthInherit: 1) + C.this[int]: (Lines: 2, MntIndex: 100, CycCxty: 2, CoupledTypes: {C1, C2}) + C.this[int].get: (Lines: 1, MntIndex: 100, CycCxty: 1, CoupledTypes: {C1}) + C.this[int].set: (Lines: 1, MntIndex: 100, CycCxty: 1, CoupledTypes: {C1}) + C1: (Lines: 2, MntIndex: 100, CycCxty: 1, DepthInherit: 1) + C2: (Lines: 5, MntIndex: 93, CycCxty: 1, DepthInherit: 1) + C2.MyConst: (Lines: 1, MntIndex: 93, CycCxty: 0) +"; + + VerifyCSharp(source, expectedMetricsText); + } + + [Fact] + public void PropertyWithTypeReferencesInBody() + { + var source = @" +class C +{ + C3 P + { + get + { + C2 c2 = C4.MyC2; + return (C3)null; + } + set + { + object c2 = C4.MyC2; + c2 = (C3)null; + } + } +} + +class C1 { } +class C2 : C3 { } +class C3 : C1 { } +class C4 { public static C2 MyC2 = null; } +"; + + var expectedMetricsText = @" +Assembly: (Lines: 22, MntIndex: 95, CycCxty: 6, CoupledTypes: {C1, C2, C3, C4}, DepthInherit: 3) + C: (Lines: 17, MntIndex: 86, CycCxty: 2, CoupledTypes: {C2, C3, C4}, DepthInherit: 1) + C.P: (Lines: 13, MntIndex: 86, CycCxty: 2, CoupledTypes: {C2, C3, C4}) + C.P.get: (Lines: 5, MntIndex: 86, CycCxty: 1, CoupledTypes: {C2, C3, C4}) + C.P.set: (Lines: 5, MntIndex: 86, CycCxty: 1, CoupledTypes: {C2, C3, C4}) + C1: (Lines: 2, MntIndex: 100, CycCxty: 1, DepthInherit: 1) + C2: (Lines: 1, MntIndex: 100, CycCxty: 1, CoupledTypes: {C3}, DepthInherit: 3) + C3: (Lines: 1, MntIndex: 100, CycCxty: 1, CoupledTypes: {C1}, DepthInherit: 2) + C4: (Lines: 1, MntIndex: 93, CycCxty: 1, CoupledTypes: {C2}, DepthInherit: 1) + C4.MyC2: (Lines: 1, MntIndex: 93, CycCxty: 0, CoupledTypes: {C2}) +"; + + VerifyCSharp(source, expectedMetricsText); + } + + [Fact] + public void PropertiesWithDifferentTypeReferencesInBody() + { + var source = @" +class C +{ + object P + { + get + { + return (I)null; + } + set + { + value = (C2)null; + } + } +} + +interface I { } +class C1 : I { } +class C2 : C1 { } +"; + + var expectedMetricsText = @" +Assembly: (Lines: 19, MntIndex: 99, CycCxty: 5, CoupledTypes: {C1, C2, I}, DepthInherit: 2) + C: (Lines: 15, MntIndex: 98, CycCxty: 2, CoupledTypes: {C2, I}, DepthInherit: 1) + C.P: (Lines: 11, MntIndex: 98, CycCxty: 2, CoupledTypes: {C2, I}) + C.P.get: (Lines: 4, MntIndex: 100, CycCxty: 1, CoupledTypes: {I}) + C.P.set: (Lines: 4, MntIndex: 96, CycCxty: 1, CoupledTypes: {C2}) + C1: (Lines: 1, MntIndex: 100, CycCxty: 1, CoupledTypes: {I}, DepthInherit: 1) + C2: (Lines: 1, MntIndex: 100, CycCxty: 1, CoupledTypes: {C1}, DepthInherit: 2) + I: (Lines: 2, MntIndex: 100, CycCxty: 1, DepthInherit: 0) +"; + + VerifyCSharp(source, expectedMetricsText); + } + + [Fact] + public void PropertyWithTypeReferencesInAttribute() + { + var source = @" +class C1 +{ + [System.Obsolete(C2.MyConst)] + object P + { + get + { + return null; + } + set + { + } + } +} + +class C2 +{ + public const string MyConst = nameof(MyConst); +} +"; + + var expectedMetricsText = @" +Assembly: (Lines: 20, MntIndex: 95, CycCxty: 3, CoupledTypes: {C2, System.ObsoleteAttribute}, DepthInherit: 1) + C1: (Lines: 15, MntIndex: 100, CycCxty: 2, CoupledTypes: {C2, System.ObsoleteAttribute}, DepthInherit: 1) + C1.P: (Lines: 11, MntIndex: 100, CycCxty: 2, CoupledTypes: {C2, System.ObsoleteAttribute}) + C1.P.get: (Lines: 4, MntIndex: 100, CycCxty: 1) + C1.P.set: (Lines: 3, MntIndex: 100, CycCxty: 1) + C2: (Lines: 5, MntIndex: 90, CycCxty: 1, DepthInherit: 1) + C2.MyConst: (Lines: 1, MntIndex: 90, CycCxty: 0) +"; + + VerifyCSharp(source, expectedMetricsText); + } + + [Fact] + public void SimpleFieldLikeEvent() + { + var source = @" +using System; + +class C +{ + public delegate void SampleEventHandler(object sender, EventArgs e); + + public event SampleEventHandler SampleEvent = C2.MyHandler; +} + +class C2 +{ + public static void MyHandler(object sender, EventArgs e) { } +} +"; + + var expectedMetricsText = @" +Assembly: (Lines: 12, MntIndex: 100, CycCxty: 3, CoupledTypes: {C.SampleEventHandler, C2, System.EventArgs}, DepthInherit: 1) + C: (Lines: 7, MntIndex: 100, CycCxty: 1, CoupledTypes: {C.SampleEventHandler, C2}, DepthInherit: 1) + C.SampleEvent: (Lines: 1, MntIndex: 100, CycCxty: 0, CoupledTypes: {C.SampleEventHandler, C2}) + SampleEventHandler: (Lines: 1, MntIndex: 100, CycCxty: 1, CoupledTypes: {System.EventArgs}, DepthInherit: 1) + C2: (Lines: 5, MntIndex: 100, CycCxty: 1, CoupledTypes: {System.EventArgs}, DepthInherit: 1) + C2.MyHandler(object, System.EventArgs): (Lines: 1, MntIndex: 100, CycCxty: 1, CoupledTypes: {System.EventArgs}) +"; + + VerifyCSharp(source, expectedMetricsText); + } + + [Fact] + public void SimpleEventWithAccessors() + { + var source = @" +using System; + +class C +{ + public event EventHandler ExplicitEvent + { + add { C2.ExplicitEvent += value; } + remove { C3.ExplicitEvent -= value; } + } +} + +class C2 +{ + public static EventHandler ExplicitEvent; +} + +class C3 +{ + public static EventHandler ExplicitEvent; +} +"; + + var expectedMetricsText = @" +Assembly: (Lines: 19, MntIndex: 98, CycCxty: 4, CoupledTypes: {C2, C3, System.EventHandler}, DepthInherit: 1) + C: (Lines: 9, MntIndex: 94, CycCxty: 2, CoupledTypes: {C2, C3, System.EventHandler}, DepthInherit: 1) + C.ExplicitEvent: (Lines: 5, MntIndex: 94, CycCxty: 2, CoupledTypes: {C2, C3, System.EventHandler}) + C.ExplicitEvent.add: (Lines: 1, MntIndex: 94, CycCxty: 1, CoupledTypes: {C2, System.EventHandler}) + C.ExplicitEvent.remove: (Lines: 1, MntIndex: 94, CycCxty: 1, CoupledTypes: {C3, System.EventHandler}) + C2: (Lines: 5, MntIndex: 100, CycCxty: 1, CoupledTypes: {System.EventHandler}, DepthInherit: 1) + C2.ExplicitEvent: (Lines: 1, MntIndex: 100, CycCxty: 0, CoupledTypes: {System.EventHandler}) + C3: (Lines: 5, MntIndex: 100, CycCxty: 1, CoupledTypes: {System.EventHandler}, DepthInherit: 1) + C3.ExplicitEvent: (Lines: 1, MntIndex: 100, CycCxty: 0, CoupledTypes: {System.EventHandler}) +"; + + VerifyCSharp(source, expectedMetricsText); + } + + [Fact] + public void ConditionalLogic_IfStatement() + { + var source = @" +class C +{ + void SimpleIf(bool b) + { + if (b) + { + } + } + + void SimpleIfElse(bool b) + { + if (b) + { + } + else + { + } + } + + void NestedIf(bool b, bool b2) + { + if (b) + { + if (b2) + { + } + } + } + + void ElseIf(bool b, bool b2) + { + if (b) + { + } + else if (b2) + { + } + } + + void MultipleIfs(bool b, bool b2) + { + if (b) + { + } + + if (b2) + { + } + + if (b2) + { + } + + if (b2) + { + } + + if (b2) + { + } + } +} +"; + + var expectedMetricsText = @" +Assembly: (Lines: 63, MntIndex: 87, CycCxty: 16, DepthInherit: 1) + C: (Lines: 63, MntIndex: 87, CycCxty: 16, DepthInherit: 1) + C.SimpleIf(bool): (Lines: 6, MntIndex: 100, CycCxty: 2) + C.SimpleIfElse(bool): (Lines: 10, MntIndex: 100, CycCxty: 2) + C.NestedIf(bool, bool): (Lines: 10, MntIndex: 91, CycCxty: 3) + C.ElseIf(bool, bool): (Lines: 10, MntIndex: 97, CycCxty: 3) + C.MultipleIfs(bool, bool): (Lines: 23, MntIndex: 79, CycCxty: 6) +"; + + VerifyCSharp(source, expectedMetricsText); + } + + [Fact] + public void ConditionalLogic_ConditionalExpression() + { + var source = @" +class C +{ + void SimpleConditional(bool b) + { + var x = b ? true : false; + } + + void NestedConditional(bool b, bool b2, bool b3, bool b4) + { + var x = b ? true : b2 ? b3 : b4; + } + + void MultipleConditionals(bool b1, bool b2, bool b3, bool b4, bool b5) + { + var x1 = b1 ? true : false; + var x2 = b2 ? true : false; + var x3 = b3 ? true : false; + var x4 = b4 ? true : false; + var x5 = b5 ? true : false; + } +} +"; + + var expectedMetricsText = @" +Assembly: (Lines: 22, MntIndex: 81, CycCxty: 11, DepthInherit: 1) + C: (Lines: 22, MntIndex: 81, CycCxty: 11, DepthInherit: 1) + C.SimpleConditional(bool): (Lines: 4, MntIndex: 93, CycCxty: 2) + C.NestedConditional(bool, bool, bool, bool): (Lines: 5, MntIndex: 91, CycCxty: 3) + C.MultipleConditionals(bool, bool, bool, bool, bool): (Lines: 9, MntIndex: 70, CycCxty: 6) +"; + + VerifyCSharp(source, expectedMetricsText); + } + + [Fact] + public void ConditionalLogic_LogicalOperators() + { + var source = @" +class C +{ + void SimpleLogical(bool b1, bool b2, bool b3, bool b4) + { + var x = b1 && b2 || b3; + } + + void NestedLogical(bool b1, bool b2, bool b3, bool b4) + { + var x = b1 && (b2 && b3 || b4); + } + + void MultipleLogicals(bool b1, bool b2, bool b3, bool b4, bool b5) + { + var x1 = b1 && b2 || b3; + var x2 = b1 && (b2 && b3 || b4); + var x3 = b3 && b4 || b5; + var x4 = b1 && (b2 && b3 || b4 && b5); + var x5 = b1 && (b2 && b3 || b4); + } +} +"; + + var expectedMetricsText = @" +Assembly: (Lines: 22, MntIndex: 79, CycCxty: 22, DepthInherit: 1) + C: (Lines: 22, MntIndex: 79, CycCxty: 22, DepthInherit: 1) + C.SimpleLogical(bool, bool, bool, bool): (Lines: 4, MntIndex: 91, CycCxty: 3) + C.NestedLogical(bool, bool, bool, bool): (Lines: 5, MntIndex: 89, CycCxty: 4) + C.MultipleLogicals(bool, bool, bool, bool, bool): (Lines: 9, MntIndex: 67, CycCxty: 15) +"; + + VerifyCSharp(source, expectedMetricsText); + } + + [Fact] + public void ConditionalLogic_CoalesceAndConditionalAccess() + { + var source = @" +class C +{ + private readonly C2 c2 = null; + private readonly bool b = false; + + void SimpleCoalesce(bool? b) + { + var x = b ?? true; + } + + void SimpleConditionalAccess(C c) + { + var x = c?.b; + } + + void NestedCoalesce(bool? b, bool? b2) + { + var x = b ?? b2 ?? true; + } + + void NestedConditionalAccess(C c) + { + var x = c?.c2?.B; + } + + void MultipleCoalesceAndConditionalAccess(C c1, C c2, C c3, C c4, C c5) + { + var x1 = c1?.c2?.B; + var x2 = (c2 ?? c3)?.c2?.B; + var x3 = (c4.c2 ?? c5.c2)?.B; + var x4 = c1?.b ?? c2?.b ?? c3?.b ?? (c4 ?? c5).b; + } +} + +class C2 { public readonly bool B = false; } +"; + + var expectedMetricsText = @" +Assembly: (Lines: 36, MntIndex: 90, CycCxty: 26, CoupledTypes: {bool?, C2}, DepthInherit: 1) + C: (Lines: 34, MntIndex: 87, CycCxty: 25, CoupledTypes: {bool?, C2}, DepthInherit: 1) + C.c2: (Lines: 1, MntIndex: 93, CycCxty: 0, CoupledTypes: {C2}) + C.b: (Lines: 1, MntIndex: 93, CycCxty: 0) + C.SimpleCoalesce(bool?): (Lines: 5, MntIndex: 93, CycCxty: 2, CoupledTypes: {bool?}) + C.SimpleConditionalAccess(C): (Lines: 5, MntIndex: 93, CycCxty: 2, CoupledTypes: {bool?}) + C.NestedCoalesce(bool?, bool?): (Lines: 5, MntIndex: 92, CycCxty: 3, CoupledTypes: {bool?}) + C.NestedConditionalAccess(C): (Lines: 5, MntIndex: 91, CycCxty: 3, CoupledTypes: {bool?, C2}) + C.MultipleCoalesceAndConditionalAccess(C, C, C, C, C): (Lines: 8, MntIndex: 69, CycCxty: 15, CoupledTypes: {bool?, C2}) + C2: (Lines: 2, MntIndex: 93, CycCxty: 1, DepthInherit: 1) + C2.B: (Lines: 1, MntIndex: 93, CycCxty: 0) +"; + + VerifyCSharp(source, expectedMetricsText); + } + + [Fact] + public void ConditionalLogic_Loops() + { + var source = @" +class C +{ + void SimpleWhileLoop(bool b, int i) + { + while (b) + { + i++; + } + } + + void SimpleForLoop() + { + for (int i = 0; i < 10; i++) + { + System.Console.WriteLine(i); + } + } + + void SimpleForEachLoop(int[] a) + { + foreach (var i in a) + { + System.Console.WriteLine(i); + } + } + + void NestedLoops(bool b, int[] a) + { + while (b) + { + for (int i = 0; i < 10; i++) + { + System.Console.WriteLine(i); + } + + foreach (var i in a) + { + System.Console.WriteLine(i); + } + } + } + + void MultipleLoops(bool b, int[] a, int j) + { + while (b) + { + j++; + } + + for (int i = 0; i < 10; i++) + { + System.Console.WriteLine(i);; + } + + foreach (var i in a) + { + System.Console.WriteLine(i); + } + + while (b) + { + j++; + } + + for (int i = 0; i < 10; i++) + { + System.Console.WriteLine(i); + } + + foreach (var i in a) + { + System.Console.WriteLine(i); + } + } +} +"; + + var expectedMetricsText = @" +Assembly: (Lines: 76, MntIndex: 74, CycCxty: 17, CoupledTypes: {System.Collections.IEnumerable, System.Console}, DepthInherit: 1) + C: (Lines: 76, MntIndex: 74, CycCxty: 17, CoupledTypes: {System.Collections.IEnumerable, System.Console}, DepthInherit: 1) + C.SimpleWhileLoop(bool, int): (Lines: 7, MntIndex: 90, CycCxty: 2) + C.SimpleForLoop(): (Lines: 8, MntIndex: 84, CycCxty: 2, CoupledTypes: {System.Console}) + C.SimpleForEachLoop(int[]): (Lines: 8, MntIndex: 90, CycCxty: 2, CoupledTypes: {System.Collections.IEnumerable, System.Console}) + C.NestedLoops(bool, int[]): (Lines: 16, MntIndex: 73, CycCxty: 4, CoupledTypes: {System.Collections.IEnumerable, System.Console}) + C.MultipleLoops(bool, int[], int): (Lines: 33, MntIndex: 61, CycCxty: 7, CoupledTypes: {System.Collections.IEnumerable, System.Console}) +"; + + VerifyCSharp(source, expectedMetricsText); + } + + [Fact] + public void LocalDeclarationsAndArithmeticOperations() + { + var source = @" +class C1 +{ + void M() + { + int x = 0; + x++; + int y = 1; + int z = y * x * 1; + } +} +"; + + var expectedMetricsText = @" +Assembly: (Lines: 11, MntIndex: 76, CycCxty: 1, DepthInherit: 1) + C1: (Lines: 11, MntIndex: 76, CycCxty: 1, DepthInherit: 1) + C1.M(): (Lines: 7, MntIndex: 76, CycCxty: 1) +"; + + VerifyCSharp(source, expectedMetricsText); + } + + [Fact] + public void MethodWithLotOfArithmeticOperations() + { + var source = @" +class C1 +{ + void M( + int x1, int y1, + int x2, int y2, + int x3, int y3, + int x4, int y4, + int x5, int y5, + int x6, int y6, + int x7, int y7, + int x8, int y8, + int x9, int y9, + int x10, int y10) + { + if (true) + { + var u1 = y1 - x1 * 1; + var u2 = y2 + x2 / 1; + var u3 = y3 > x3 % 1; + var u4 = y4 < x4 * 1; + var u5 = y5 * x5 * 1; + var u6 = y6 * x6 * 1; + var u7 = y7 * x7 * 1; + var u8 = y8 * x8 * 1; + var u9 = y9 * x9 * 1; + var u10 = y10 * x10 * 1; + + var v1 = y1 - x1 * 1; + var v2 = y2 + x2 / 1; + var v3 = y3 > x3 % 1; + var v4 = y4 < x4 * 1; + var v5 = y5 * x5 * 1; + var v6 = y6 * x6 * 1; + var v7 = y7 * x7 * 1; + var v8 = y8 * x8 * 1; + var v9 = y9 * x9 * 1; + var v10 = y10 * x10 * 1; + + var w1 = y1 - x1 * 1; + var w2 = y2 + x2 / 1; + var w3 = y3 > x3 % 1; + var w4 = y4 < x4 * 1; + var w5 = y5 * x5 * 1; + var w6 = y6 * x6 * 1; + var w7 = y7 * x7 * 1; + var w8 = y8 * x8 * 1; + var w9 = y9 * x9 * 1; + var w10 = y10 * x10 * 1; + + var z1 = y1 - x1 * 1; + var z2 = y2 + x2 / 1; + var z3 = y3 > x3 % 1; + var z4 = y4 < x4 * 1; + var z5 = y5 * x5 * 1; + var z6 = y6 * x6 * 1; + var z7 = y7 * x7 * 1; + var z8 = y8 * x8 * 1; + var z9 = y9 * x9 * 1; + var z10 = y10 * x10 * 1; + } + + if (true) + { + var u1 = y1 - x1 * 1; + var u2 = y2 + x2 / 1; + var u3 = y3 > x3 % 1; + var u4 = y4 < x4 * 1; + var u5 = y5 * x5 * 1; + var u6 = y6 * x6 * 1; + var u7 = y7 * x7 * 1; + var u8 = y8 * x8 * 1; + var u9 = y9 * x9 * 1; + var u10 = y10 * x10 * 1; + + var v1 = y1 - x1 * 1; + var v2 = y2 + x2 / 1; + var v3 = y3 > x3 % 1; + var v4 = y4 < x4 * 1; + var v5 = y5 * x5 * 1; + var v6 = y6 * x6 * 1; + var v7 = y7 * x7 * 1; + var v8 = y8 * x8 * 1; + var v9 = y9 * x9 * 1; + var v10 = y10 * x10 * 1; + + var w1 = y1 - x1 * 1; + var w2 = y2 + x2 / 1; + var w3 = y3 > x3 % 1; + var w4 = y4 < x4 * 1; + var w5 = y5 * x5 * 1; + var w6 = y6 * x6 * 1; + var w7 = y7 * x7 * 1; + var w8 = y8 * x8 * 1; + var w9 = y9 * x9 * 1; + var w10 = y10 * x10 * 1; + + var z1 = y1 - x1 * 1; + var z2 = y2 + x2 / 1; + var z3 = y3 > x3 % 1; + var z4 = y4 < x4 * 1; + var z5 = y5 * x5 * 1; + var z6 = y6 * x6 * 1; + var z7 = y7 * x7 * 1; + var z8 = y8 * x8 * 1; + var z9 = y9 * x9 * 1; + var z10 = y10 * x10 * 1; + } + + if (true) + { + var u1 = y1 - x1 * 1; + var u2 = y2 + x2 / 1; + var u3 = y3 > x3 % 1; + var u4 = y4 < x4 * 1; + var u5 = y5 * x5 * 1; + var u6 = y6 * x6 * 1; + var u7 = y7 * x7 * 1; + var u8 = y8 * x8 * 1; + var u9 = y9 * x9 * 1; + var u10 = y10 * x10 * 1; + + var v1 = y1 - x1 * 1; + var v2 = y2 + x2 / 1; + var v3 = y3 > x3 % 1; + var v4 = y4 < x4 * 1; + var v5 = y5 * x5 * 1; + var v6 = y6 * x6 * 1; + var v7 = y7 * x7 * 1; + var v8 = y8 * x8 * 1; + var v9 = y9 * x9 * 1; + var v10 = y10 * x10 * 1; + + var w1 = y1 - x1 * 1; + var w2 = y2 + x2 / 1; + var w3 = y3 > x3 % 1; + var w4 = y4 < x4 * 1; + var w5 = y5 * x5 * 1; + var w6 = y6 * x6 * 1; + var w7 = y7 * x7 * 1; + var w8 = y8 * x8 * 1; + var w9 = y9 * x9 * 1; + var w10 = y10 * x10 * 1; + + var z1 = y1 - x1 * 1; + var z2 = y2 + x2 / 1; + var z3 = y3 > x3 % 1; + var z4 = y4 < x4 * 1; + var z5 = y5 * x5 * 1; + var z6 = y6 * x6 * 1; + var z7 = y7 * x7 * 1; + var z8 = y8 * x8 * 1; + var z9 = y9 * x9 * 1; + var z10 = y10 * x10 * 1; + } + } +} +"; + + var expectedMetricsText = @" +Assembly: (Lines: 157, MntIndex: 27, CycCxty: 4, DepthInherit: 1) + C1: (Lines: 157, MntIndex: 27, CycCxty: 4, DepthInherit: 1) + C1.M(int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int): (Lines: 153, MntIndex: 27, CycCxty: 4) +"; + + VerifyCSharp(source, expectedMetricsText); + } + + [Fact] + public void VisualBasicTest01() + { + var source = @" +Class C + Sub M(i As Integer) + Select Case i + Case 0 + Exit Select + Case 1 + Exit Select + Case Else + End Select + End Sub +End Class +"; + + var expectedMetricsText = @" +Assembly: (Lines: 12, MntIndex: 92, CycCxty: 4, DepthInherit: 1) + C: (Lines: 12, MntIndex: 92, CycCxty: 4, DepthInherit: 1) + Public Sub M(i As Integer): (Lines: 9, MntIndex: 92, CycCxty: 4) +"; + + VerifyBasic(source, expectedMetricsText); + } + + [Fact] + public void VisualBasicTest02() + { + var source = @" +Namespace N1 + Class C + Sub M(Of T)(i As Integer) + End Sub + + Class NestedClass(Of U) + Public Field0 As Integer = 0 + Public Field1, Field2 As New Integer + End Class + End Class +End Namespace + +Class TopLevel + Public ReadOnly Property P As Integer +End Class + +"; + + var expectedMetricsText = @" +Assembly: (Lines: 16, MntIndex: 100, CycCxty: 3, DepthInherit: 1) + TopLevel: (Lines: 4, MntIndex: 100, CycCxty: 1, DepthInherit: 1) + Public ReadOnly Property P As Integer: (Lines: 1, MntIndex: 100, CycCxty: 0) + N1: (Lines: 12, MntIndex: 100, CycCxty: 2, DepthInherit: 1) + C: (Lines: 9, MntIndex: 100, CycCxty: 1, DepthInherit: 1) + Public Sub M(Of T)(i As Integer): (Lines: 2, MntIndex: 100, CycCxty: 1) + NestedClass(Of U): (Lines: 5, MntIndex: 100, CycCxty: 1, DepthInherit: 1) + Public Field0 As Integer: (Lines: 1, MntIndex: 100, CycCxty: 0) + Public Field1 As Integer: (Lines: 1, MntIndex: 100, CycCxty: 0) + Public Field2 As Integer: (Lines: 1, MntIndex: 100, CycCxty: 0) +"; + + VerifyBasic(source, expectedMetricsText); + } + } +} diff --git a/src/Test.Utilities/CodeMetricsTestsBase.cs b/src/Test.Utilities/CodeMetricsTestsBase.cs new file mode 100644 index 0000000000..2bcd6880c5 --- /dev/null +++ b/src/Test.Utilities/CodeMetricsTestsBase.cs @@ -0,0 +1,85 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Linq; +using System.Threading; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Text; +using Xunit; + +namespace Test.Utilities.CodeMetrics +{ + public abstract class CodeMetricsTestBase + { + private static readonly MetadataReference s_corlibReference = MetadataReference.CreateFromFile(typeof(object).Assembly.Location); + private static readonly MetadataReference s_systemCoreReference = MetadataReference.CreateFromFile(typeof(Enumerable).Assembly.Location); + private static readonly CompilationOptions s_CSharpDefaultOptions = new Microsoft.CodeAnalysis.CSharp.CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary); + private static readonly CompilationOptions s_visualBasicDefaultOptions = new Microsoft.CodeAnalysis.VisualBasic.VisualBasicCompilationOptions(OutputKind.DynamicallyLinkedLibrary); + + internal static string DefaultFilePathPrefix = "Test"; + internal static string CSharpDefaultFileExt = "cs"; + internal static string VisualBasicDefaultExt = "vb"; + internal static string CSharpDefaultFilePath = DefaultFilePathPrefix + 0 + "." + CSharpDefaultFileExt; + internal static string VisualBasicDefaultFilePath = DefaultFilePathPrefix + 0 + "." + VisualBasicDefaultExt; + internal static string TestProjectName = "TestProject"; + + protected abstract string GetMetricsDataString(Compilation compilation); + + protected Project CreateProject(string source, string language = LanguageNames.CSharp) + { + return CreateProject(new[] { source }, language); + } + + protected Project CreateProject(string[] sources, string language = LanguageNames.CSharp) + { + string fileNamePrefix = DefaultFilePathPrefix; + string fileExt = language == LanguageNames.CSharp ? CSharpDefaultFileExt : VisualBasicDefaultExt; + var options = language == LanguageNames.CSharp ? s_CSharpDefaultOptions : s_visualBasicDefaultOptions; + + var projectId = ProjectId.CreateNewId(debugName: TestProjectName); + + var solution = new AdhocWorkspace().CurrentSolution + .AddProject(projectId, TestProjectName, TestProjectName, language) + .WithProjectCompilationOptions(projectId, options) + .AddMetadataReference(projectId, s_corlibReference) + .AddMetadataReference(projectId, s_systemCoreReference); + + int count = 0; + foreach (var source in sources) + { + var newFileName = fileNamePrefix + count + "." + fileExt; + var documentId = DocumentId.CreateNewId(projectId, debugName: newFileName); + solution = solution.AddDocument(documentId, newFileName, SourceText.From(source)); + count++; + } + + return solution.GetProject(projectId); + } + + protected void VerifyCSharp(string source, string expectedMetricsText, bool expectDiagnostics = false) + => Verify(new[] { source }, expectedMetricsText, expectDiagnostics, LanguageNames.CSharp); + + protected void VerifyCSharp(string[] sources, string expectedMetricsText, bool expectDiagnostics = false) + => Verify(sources, expectedMetricsText, expectDiagnostics, LanguageNames.CSharp); + + protected void VerifyBasic(string source, string expectedMetricsText, bool expectDiagnostics = false) + => Verify(new[] { source }, expectedMetricsText, expectDiagnostics, LanguageNames.VisualBasic); + + protected void VerifyBasic(string[] sources, string expectedMetricsText, bool expectDiagnostics = false) + => Verify(sources, expectedMetricsText, expectDiagnostics, LanguageNames.VisualBasic); + + private void Verify(string[] sources, string expectedMetricsText, bool expectDiagnostics, string language) + { + var project = CreateProject(sources, language); + var compilation = project.GetCompilationAsync(CancellationToken.None).Result; + var diagnostics = compilation.GetDiagnostics().Where(d => d.Severity == DiagnosticSeverity.Warning || d.Severity == DiagnosticSeverity.Error); + Assert.Equal(expectDiagnostics, diagnostics.Count() != 0); + + var actualMetricsText = GetMetricsDataString(compilation).Trim(); + expectedMetricsText = expectedMetricsText.Trim(); + if (actualMetricsText != expectedMetricsText) + { + Assert.False(false, $"Expected:\r\n{0}\r\n\r\nActual:\r\n{1}"); + } + } + } +} diff --git a/src/Utilities/Analyzer.Utilities.projitems b/src/Utilities/Analyzer.Utilities.projitems index 844f20598d..4512a48550 100644 --- a/src/Utilities/Analyzer.Utilities.projitems +++ b/src/Utilities/Analyzer.Utilities.projitems @@ -10,6 +10,17 @@ + + + + + + + + + + + diff --git a/src/Utilities/CodeMetrics/CodeAnalysisMetricData.AssemblyMetricData.cs b/src/Utilities/CodeMetrics/CodeAnalysisMetricData.AssemblyMetricData.cs new file mode 100644 index 0000000000..65dbdfb706 --- /dev/null +++ b/src/Utilities/CodeMetrics/CodeAnalysisMetricData.AssemblyMetricData.cs @@ -0,0 +1,103 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; + +namespace Microsoft.CodeAnalysis.CodeMetrics +{ + internal abstract partial class CodeAnalysisMetricData + { + private sealed class AssemblyMetricData : CodeAnalysisMetricData + { + private AssemblyMetricData( + IAssemblySymbol symbol, int maintainabilityIndex, + ImmutableHashSet coupledNamedTypes, + long linesOfCode, + int cyclomaticComplexity, + int? depthOfInheritance, + ImmutableArray children) + : base(symbol, maintainabilityIndex, ComputationalComplexityMetrics.Default, + coupledNamedTypes, linesOfCode, cyclomaticComplexity, depthOfInheritance, children) + { + } + + internal static async Task ComputeAsync(IAssemblySymbol assembly, SemanticModelProvider semanticModelProvider, CancellationToken cancellationToken) + { + var coupledTypesBuilder = ImmutableHashSet.CreateBuilder(); + long linesOfCode = 0; + int maintainabilityIndexTotal = 0; + int cyclomaticComplexity = 0; + int depthOfInheritance = 0; + int grandChildCount = 0; + + ImmutableArray children = await ComputeAsync(GetChildSymbols(assembly), semanticModelProvider, cancellationToken).ConfigureAwait(false); + foreach (CodeAnalysisMetricData child in children) + { + MetricsHelper.AddCoupledNamedTypes(coupledTypesBuilder, child.CoupledNamedTypes); + linesOfCode += child.LinesOfCode; + 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); + } + + private static ImmutableArray GetChildSymbols(IAssemblySymbol assembly) + { + // Compat: We only create child nodes for namespaces which have at least one type member. + var includeGlobalNamespace = false; + var namespacesWithTypeMember = new HashSet(); + + processNamespace(assembly.GlobalNamespace); + + var builder = ImmutableArray.CreateBuilder(); + + if (includeGlobalNamespace) + { + builder.Add(assembly.GlobalNamespace); + } + + foreach (INamespaceSymbol @namespace in namespacesWithTypeMember.OrderBy(ns => ns.ToDisplayString())) + { + builder.Add(@namespace); + } + + return builder.ToImmutable(); + + void processNamespace(INamespaceSymbol @namespace) + { + foreach (INamespaceOrTypeSymbol child in @namespace.GetMembers()) + { + if (child.Kind == SymbolKind.Namespace) + { + processNamespace((INamespaceSymbol)child); + } + else if(@namespace.IsGlobalNamespace) + { + includeGlobalNamespace = true; + } + else if (!child.IsImplicitlyDeclared) + { + namespacesWithTypeMember.Add(@namespace); + } + } + } + } + } + } +} diff --git a/src/Utilities/CodeMetrics/CodeAnalysisMetricData.EventMetricData.cs b/src/Utilities/CodeMetrics/CodeAnalysisMetricData.EventMetricData.cs new file mode 100644 index 0000000000..ad78f86cff --- /dev/null +++ b/src/Utilities/CodeMetrics/CodeAnalysisMetricData.EventMetricData.cs @@ -0,0 +1,75 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; + +namespace Microsoft.CodeAnalysis.CodeMetrics +{ + internal abstract partial class CodeAnalysisMetricData + { + private sealed class EventMetricData : CodeAnalysisMetricData + { + internal EventMetricData( + IEventSymbol symbol, + int maintainabilityIndex, + ComputationalComplexityMetrics computationalComplexityMetrics, + ImmutableHashSet coupledNamedTypes, + long linesOfCode, + int cyclomaticComplexity, + int? depthOfInheritance, + ImmutableArray children) + : base(symbol, maintainabilityIndex, computationalComplexityMetrics, coupledNamedTypes, + linesOfCode, cyclomaticComplexity, depthOfInheritance, children) + { + } + + internal async static Task ComputeAsync(IEventSymbol @event, SemanticModelProvider semanticModelProvider, CancellationToken cancellationToken) + { + var coupledTypesBuilder = ImmutableHashSet.CreateBuilder(); + ImmutableArray 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 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); + } + + private static IEnumerable GetAccessors(IEventSymbol @event) + { + if (@event.AddMethod != null) + { + yield return @event.AddMethod; + } + + if (@event.RemoveMethod != null) + { + yield return @event.RemoveMethod; + } + + if (@event.RaiseMethod != null) + { + yield return @event.RaiseMethod; + } + } + } + } +} diff --git a/src/Utilities/CodeMetrics/CodeAnalysisMetricData.FieldMetricData.cs b/src/Utilities/CodeMetrics/CodeAnalysisMetricData.FieldMetricData.cs new file mode 100644 index 0000000000..3c986e194a --- /dev/null +++ b/src/Utilities/CodeMetrics/CodeAnalysisMetricData.FieldMetricData.cs @@ -0,0 +1,52 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Immutable; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; + +namespace Microsoft.CodeAnalysis.CodeMetrics +{ + internal abstract partial class CodeAnalysisMetricData + { + private sealed class FieldMetricData : CodeAnalysisMetricData + { + internal FieldMetricData( + IFieldSymbol symbol, + int maintainabilityIndex, + ComputationalComplexityMetrics computationalComplexityMetrics, + ImmutableHashSet coupledNamedTypes, + long linesOfCode, + int cyclomaticComplexity, + int? depthOfInheritance) + : base(symbol, maintainabilityIndex, computationalComplexityMetrics, coupledNamedTypes, + linesOfCode, cyclomaticComplexity, depthOfInheritance, children: ImmutableArray.Empty) + { + } + + internal static async Task ComputeAsync(IFieldSymbol field, SemanticModelProvider semanticModelProvider, CancellationToken cancellationToken) + { + var coupledTypesBuilder = ImmutableHashSet.CreateBuilder(); + ImmutableArray 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); + } + + private static int CalculateMaintainabilityIndex(ComputationalComplexityMetrics computationalComplexityMetrics, int cyclomaticComplexity) + { + double computationalComplexityVolume = Math.Max(0.0, Math.Log(computationalComplexityMetrics.Volume)); //avoid Log(0) = -Infinity + double logEffectiveLinesOfCode = Math.Max(0.0, Math.Log(computationalComplexityMetrics.EffectiveLinesOfCode)); //avoid Log(0) = -Infinity + return MetricsHelper.NormalizeAndRoundMaintainabilityIndex(171 - 5.2 * computationalComplexityVolume - 0.23 * cyclomaticComplexity - 16.2 * logEffectiveLinesOfCode); + } + } + } +} diff --git a/src/Utilities/CodeMetrics/CodeAnalysisMetricData.MethodMetricData.cs b/src/Utilities/CodeMetrics/CodeAnalysisMetricData.MethodMetricData.cs new file mode 100644 index 0000000000..3a36e25e9a --- /dev/null +++ b/src/Utilities/CodeMetrics/CodeAnalysisMetricData.MethodMetricData.cs @@ -0,0 +1,62 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Immutable; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; + +namespace Microsoft.CodeAnalysis.CodeMetrics +{ + internal abstract partial class CodeAnalysisMetricData + { + private sealed class MethodMetricData : CodeAnalysisMetricData + { + internal MethodMetricData( + IMethodSymbol symbol, + int maintainabilityIndex, + ComputationalComplexityMetrics computationalComplexityMetrics, + ImmutableHashSet coupledNamedTypes, + long linesOfCode, + int cyclomaticComplexity, + int? depthOfInheritance) + : base(symbol, maintainabilityIndex, computationalComplexityMetrics, coupledNamedTypes, + linesOfCode, cyclomaticComplexity, depthOfInheritance, children: ImmutableArray.Empty) + { + } + + internal static async Task ComputeAsync(IMethodSymbol method, SemanticModelProvider semanticModelProvider, CancellationToken cancellationToken) + { + var coupledTypesBuilder = ImmutableHashSet.CreateBuilder(); + ImmutableArray 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); + } + + private static int CalculateMaintainabilityIndex(ComputationalComplexityMetrics computationalComplexityMetrics, int cyclomaticComplexity) + { + double computationalComplexityVolume = Math.Max(0.0, Math.Log(computationalComplexityMetrics.Volume)); //avoid Log(0) = -Infinity + double logEffectiveLinesOfCode = Math.Max(0.0, Math.Log(computationalComplexityMetrics.EffectiveLinesOfCode)); //avoid Log(0) = -Infinity + return MetricsHelper.NormalizeAndRoundMaintainabilityIndex(171 - 5.2 * computationalComplexityVolume - 0.23 * cyclomaticComplexity - 16.2 * logEffectiveLinesOfCode); + } + } + } +} diff --git a/src/Utilities/CodeMetrics/CodeAnalysisMetricData.NamedTypeMetricData.cs b/src/Utilities/CodeMetrics/CodeAnalysisMetricData.NamedTypeMetricData.cs new file mode 100644 index 0000000000..4f3edecf6d --- /dev/null +++ b/src/Utilities/CodeMetrics/CodeAnalysisMetricData.NamedTypeMetricData.cs @@ -0,0 +1,147 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Immutable; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; + +namespace Microsoft.CodeAnalysis.CodeMetrics +{ + internal abstract partial class CodeAnalysisMetricData + { + private sealed class NamedTypeMetricData : CodeAnalysisMetricData + { + internal NamedTypeMetricData( + INamedTypeSymbol symbol, + int maintainabilityIndex, + ComputationalComplexityMetrics computationalComplexityMetrics, + ImmutableHashSet coupledNamedTypes, + long linesOfCode, + int cyclomaticComplexity, + int? depthOfInheritance, + ImmutableArray children) + : base(symbol, maintainabilityIndex, computationalComplexityMetrics, + coupledNamedTypes, linesOfCode, cyclomaticComplexity, depthOfInheritance, children) + { + } + + internal static async Task ComputeAsync(INamedTypeSymbol namedType, SemanticModelProvider semanticModelProvider, CancellationToken cancellationToken) + { + var coupledTypesBuilder = ImmutableHashSet.CreateBuilder(); + ImmutableArray 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. + // Also filter out accessors as they are children of their associated symbols, for which we generate a separate node. + var members = namedType.GetMembers().Where(m => m.Kind != SymbolKind.NamedType && + (m.Kind != SymbolKind.Method || ((IMethodSymbol)m).AssociatedSymbol == null)); + ImmutableArray children = await ComputeAsync(members, semanticModelProvider, cancellationToken).ConfigureAwait(false); + + // Heuristic to prevent simple fields (no initializer or simple initializer) from skewing the complexity. + ImmutableHashSet 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 getFilteredFieldsForComplexity() + { + ImmutableHashSet.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 = builderOpt ?? ImmutableHashSet.CreateBuilder(); + builderOpt.Add((IFieldSymbol)fieldData.Symbol); + indexThreshold -= 4; + } + + return builderOpt?.ToImmutable() ?? ImmutableHashSet.Empty; + } + } + + private static int CalculateDepthOfInheritance(INamedTypeSymbol namedType) + { + switch (namedType.TypeKind) + { + case TypeKind.Class: + case TypeKind.Interface: + int depth = 0; + for (; namedType.BaseType != null; namedType = namedType.BaseType) + { + depth++; + } + 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 = computationalComplexityMetrics.EffectiveLinesOfCode / effectiveChildrenCount; + avgCyclomaticComplexity = 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); + } + } + } +} diff --git a/src/Utilities/CodeMetrics/CodeAnalysisMetricData.NamespaceMetricData.cs b/src/Utilities/CodeMetrics/CodeAnalysisMetricData.NamespaceMetricData.cs new file mode 100644 index 0000000000..f74c8c574f --- /dev/null +++ b/src/Utilities/CodeMetrics/CodeAnalysisMetricData.NamespaceMetricData.cs @@ -0,0 +1,90 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; + +namespace Microsoft.CodeAnalysis.CodeMetrics +{ + internal abstract partial class CodeAnalysisMetricData + { + private sealed class NamespaceMetricData : CodeAnalysisMetricData + { + internal NamespaceMetricData( + INamespaceSymbol symbol, + int maintainabilityIndex, + ImmutableHashSet coupledNamedTypes, + long linesOfCode, + int cyclomaticComplexity, + int? depthOfInheritance, + ImmutableArray children) + : base(symbol, maintainabilityIndex, ComputationalComplexityMetrics.Default, + coupledNamedTypes, linesOfCode, cyclomaticComplexity, depthOfInheritance, children) + { + } + + internal static async Task ComputeAsync(INamespaceSymbol @namespace, SemanticModelProvider semanticModelProvider, CancellationToken cancellationToken) + { + var coupledTypesBuilder = ImmutableHashSet.CreateBuilder(); + int maintainabilityIndexTotal = 0; + int cyclomaticComplexity = 0; + int depthOfInheritance = 0; + long childrenLinesOfCode = 0; + + ImmutableArray 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.LinesOfCode; + } + } + + 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); + } + + private static ImmutableArray GetChildSymbols(INamespaceSymbol @namespace) + { + // Compat: Create child nodes for types and nested types within the namespace. + // Child namespaces are directly child nodes of assembly. + var typesInNamespace = new HashSet(); + foreach (INamedTypeSymbol typeMember in @namespace.GetTypeMembers()) + { + processType(typeMember); + } + + var builder = ImmutableArray.CreateBuilder(); + foreach (INamedTypeSymbol namedType in typesInNamespace.OrderBy(t => t.ToDisplayString())) + { + builder.Add(namedType); + } + + return builder.ToImmutable(); + + void processType(INamedTypeSymbol namedType) + { + typesInNamespace.Add(namedType); + foreach (INamedTypeSymbol nestedType in namedType.GetTypeMembers()) + { + processType(nestedType); + } + } + } + } + } +} diff --git a/src/Utilities/CodeMetrics/CodeAnalysisMetricData.PropertyMetricData.cs b/src/Utilities/CodeMetrics/CodeAnalysisMetricData.PropertyMetricData.cs new file mode 100644 index 0000000000..0f459861d7 --- /dev/null +++ b/src/Utilities/CodeMetrics/CodeAnalysisMetricData.PropertyMetricData.cs @@ -0,0 +1,71 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; + +namespace Microsoft.CodeAnalysis.CodeMetrics +{ + internal abstract partial class CodeAnalysisMetricData + { + private sealed class PropertyMetricData : CodeAnalysisMetricData + { + internal PropertyMetricData( + IPropertySymbol symbol, + int maintainabilityIndex, + ComputationalComplexityMetrics computationalComplexityMetrics, + ImmutableHashSet coupledNamedTypes, + long linesOfCode, + int cyclomaticComplexity, + int? depthOfInheritance, + ImmutableArray children) + : base(symbol, maintainabilityIndex, computationalComplexityMetrics, coupledNamedTypes, + linesOfCode, cyclomaticComplexity, depthOfInheritance, children) + { + } + + internal static async Task ComputeAsync(IPropertySymbol property, SemanticModelProvider semanticModelProvider, CancellationToken cancellationToken) + { + var coupledTypesBuilder = ImmutableHashSet.CreateBuilder(); + ImmutableArray 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 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); + } + + private static IEnumerable GetAccessors(IPropertySymbol property) + { + if (property.GetMethod != null) + { + yield return property.GetMethod; + } + + if (property.SetMethod != null) + { + yield return property.SetMethod; + } + } + } + } +} diff --git a/src/Utilities/CodeMetrics/CodeAnalysisMetricData.cs b/src/Utilities/CodeMetrics/CodeAnalysisMetricData.cs new file mode 100644 index 0000000000..a46baea017 --- /dev/null +++ b/src/Utilities/CodeMetrics/CodeAnalysisMetricData.cs @@ -0,0 +1,213 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +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; +using Microsoft.CodeAnalysis; + +#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 +{ + internal abstract partial class CodeAnalysisMetricData + { + internal CodeAnalysisMetricData( + ISymbol symbol, + int maintainabilityIndex, + ComputationalComplexityMetrics computationalComplexityMetrics, + ImmutableHashSet coupledNamedTypes, + long linesOfCode, + int cyclomaticComplexity, + int? depthOfInheritance, + ImmutableArray children) + { + Debug.Assert(symbol != null); + Debug.Assert( + symbol.Kind == SymbolKind.Assembly || + symbol.Kind == SymbolKind.Namespace || + symbol.Kind == SymbolKind.NamedType || + symbol.Kind == SymbolKind.Method || + symbol.Kind == SymbolKind.Field || + symbol.Kind == SymbolKind.Event || + symbol.Kind == SymbolKind.Property); + Debug.Assert(depthOfInheritance.HasValue == (symbol.Kind == SymbolKind.Assembly || symbol.Kind == SymbolKind.Namespace || symbol.Kind == SymbolKind.NamedType)); + + Symbol = symbol; + MaintainabilityIndex = maintainabilityIndex; + ComputationalComplexityMetrics = computationalComplexityMetrics; + CoupledNamedTypes = coupledNamedTypes; + LinesOfCode = linesOfCode; + CyclomaticComplexity = cyclomaticComplexity; + DepthOfInheritance = depthOfInheritance; + Children = children; + } + + public ISymbol Symbol { get; } + + internal ComputationalComplexityMetrics ComputationalComplexityMetrics { get; } + + public int MaintainabilityIndex { get; } + + public ImmutableHashSet CoupledNamedTypes { get; } + + public long LinesOfCode { get; } + + public int CyclomaticComplexity { get; } + + public int? DepthOfInheritance { get; } + + public ImmutableArray Children { get; } + + 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.Substring(index + 1); + } + + break; + + default: + symbolName = Symbol.ToDisplayString(); + break; + } + + builder.Append($"{symbolName}: (Lines: {LinesOfCode}, MntIndex: {MaintainabilityIndex}, CycCxty: {CyclomaticComplexity}"); + if (CoupledNamedTypes.Count > 0) + { + 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}"); + } + } + } + } + + #region Core Compute Methods + public async static Task ComputeAsync(Project project, CancellationToken cancellationToken) + { + if (project == null) + { + throw new ArgumentNullException(nameof(project)); + } + + if (!project.SupportsCompilation) + { + throw new NotSupportedException("Project must support compilation."); + } + + var compilation = await project.GetCompilationAsync(cancellationToken).ConfigureAwait(false); + return await ComputeAsync(compilation.Assembly, compilation, cancellationToken).ConfigureAwait(false); + } + + public static Task ComputeAsync(Compilation compilation, CancellationToken cancellationToken) + { + if (compilation == null) + { + throw new ArgumentNullException(nameof(compilation)); + } + + return ComputeAsync(compilation.Assembly, compilation, cancellationToken); + } + + public static Task 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 async static Task 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> ComputeAsync(IEnumerable 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(); + + #endregion + } +} diff --git a/src/Utilities/CodeMetrics/ComputationalComplexityMetrics.cs b/src/Utilities/CodeMetrics/ComputationalComplexityMetrics.cs new file mode 100644 index 0000000000..eb9c12400e --- /dev/null +++ b/src/Utilities/CodeMetrics/ComputationalComplexityMetrics.cs @@ -0,0 +1,414 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Immutable; +using System.Diagnostics; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Operations; + +namespace Microsoft.CodeAnalysis.CodeMetrics +{ + /// + /// Calculates computational complexity metrics based on the number + /// of operators and operands found in the code. + /// + /// This metric is based off of the Halstead metric. + internal sealed class ComputationalComplexityMetrics + { + internal static readonly ComputationalComplexityMetrics Default = new ComputationalComplexityMetrics(0, 0, 0, 0, ImmutableHashSet.Empty, + ImmutableHashSet.Empty, ImmutableHashSet.Empty, ImmutableHashSet.Empty, ImmutableHashSet.Empty, ImmutableHashSet.Empty); + private static readonly object s_nullConstantPlaceholder = new object(); + + private readonly long _operatorUsageCounts; + private readonly long _symbolUsageCounts; + private readonly long _constantUsageCounts; + private readonly ImmutableHashSet _distinctOperatorKinds; + private readonly ImmutableHashSet _distinctBinaryOperatorKinds; + private readonly ImmutableHashSet _distinctUnaryOperatorKinds; + private readonly ImmutableHashSet _distinctCaseKinds; + private readonly ImmutableHashSet _distinctReferencedSymbols; + private readonly ImmutableHashSet _distinctReferencedConstants; + + private ComputationalComplexityMetrics( + long effectiveLinesOfCode, + long operatorUsageCounts, + long symbolUsageCounts, + long constantUsageCounts, + ImmutableHashSet distinctOperatorKinds, + ImmutableHashSet distinctBinaryOperatorKinds, + ImmutableHashSet distinctUnaryOperatorKinds, + ImmutableHashSet distinctCaseKinds, + ImmutableHashSet distinctReferencedSymbols, + ImmutableHashSet distinctReferencedConstants) + { + EffectiveLinesOfCode = effectiveLinesOfCode; + _operatorUsageCounts = operatorUsageCounts; + _symbolUsageCounts = symbolUsageCounts; + _constantUsageCounts = constantUsageCounts; + _distinctOperatorKinds = distinctOperatorKinds; + _distinctBinaryOperatorKinds = distinctBinaryOperatorKinds; + _distinctUnaryOperatorKinds = distinctUnaryOperatorKinds; + _distinctCaseKinds = distinctCaseKinds; + _distinctReferencedSymbols = distinctReferencedSymbols; + _distinctReferencedConstants = distinctReferencedConstants; + } + + private static ComputationalComplexityMetrics Create( + long effectiveLinesOfCode, + long operatorUsageCounts, + long symbolUsageCounts, + long constantUsageCounts, + ImmutableHashSet distinctOperatorKinds, + ImmutableHashSet distinctBinaryOperatorKinds, + ImmutableHashSet distinctUnaryOperatorKinds, + ImmutableHashSet distinctCaseKinds, + ImmutableHashSet distinctReferencedSymbols, + ImmutableHashSet distinctReferencedConstants) + { + if (effectiveLinesOfCode == 0 && operatorUsageCounts == 0 && symbolUsageCounts == 0 && constantUsageCounts == 0) + { + return Default; + } + + return new ComputationalComplexityMetrics(effectiveLinesOfCode, operatorUsageCounts, symbolUsageCounts, constantUsageCounts, + distinctOperatorKinds, distinctBinaryOperatorKinds, distinctUnaryOperatorKinds, distinctCaseKinds, distinctReferencedSymbols, distinctReferencedConstants); + } + + public static ComputationalComplexityMetrics Compute(IOperation operationBlock) + { + Debug.Assert(operationBlock != null); + + long effectiveLinesOfCode = 0; + long operatorUsageCounts = 0; + long symbolUsageCounts = 0; + long constantUsageCounts = 0; + ImmutableHashSet.Builder distinctOperatorKindsBuilder = null; + ImmutableHashSet.Builder distinctBinaryOperatorKindsBuilder = null; + ImmutableHashSet.Builder distinctUnaryOperatorKindsBuilder = null; + ImmutableHashSet.Builder distinctCaseKindsBuilder = null; + ImmutableHashSet.Builder distinctReferencedSymbolsBuilder = null; + ImmutableHashSet.Builder distinctReferencedConstantsBuilder = null; + foreach (var operation in operationBlock.Descendants()) + { + effectiveLinesOfCode += getEffectiveLinesOfCode(operation); + + if (operation.IsImplicit) + { + continue; + } + + if (operation.ConstantValue.HasValue) + { + constantUsageCounts++; + distinctReferencedConstantsBuilder = distinctReferencedConstantsBuilder ?? ImmutableHashSet.CreateBuilder(); + distinctReferencedConstantsBuilder.Add(operation.ConstantValue.Value ?? s_nullConstantPlaceholder); + continue; + } + + switch (operation.Kind) + { + // Symbol references. + case OperationKind.LocalReference: + countOperand(((ILocalReferenceOperation)operation).Local); + continue; + case OperationKind.ParameterReference: + countOperand(((IParameterReferenceOperation)operation).Parameter); + continue; + case OperationKind.FieldReference: + case OperationKind.MethodReference: + case OperationKind.PropertyReference: + case OperationKind.EventReference: + countOperator(operation); + countOperand(((IMemberReferenceOperation)operation).Member); + continue; + + // Symbol initializers. + case OperationKind.FieldInitializer: + foreach (var field in ((IFieldInitializerOperation)operation).InitializedFields) + { + countOperator(operation); + countOperand(field); + } + continue; + case OperationKind.PropertyInitializer: + foreach (var property in ((IPropertyInitializerOperation)operation).InitializedProperties) + { + countOperator(operation); + countOperand(property); + } + continue; + case OperationKind.ParameterInitializer: + countOperator(operation); + countOperand(((IParameterInitializerOperation)operation).Parameter); + continue; + case OperationKind.VariableInitializer: + countOperator(operation); + // We count the operand in the variable declarator. + continue; + case OperationKind.VariableDeclarator: + var variableDeclarator = (IVariableDeclaratorOperation)operation; + if (variableDeclarator.GetVariableInitializer() != null) + { + countOperand(variableDeclarator.Symbol); + } + continue; + + // Invocations and Object creations. + case OperationKind.Invocation: + countOperator(operation); + var invocation = (IInvocationOperation)operation; + if (!invocation.TargetMethod.ReturnsVoid) + { + countOperand(invocation.TargetMethod); + } + continue; + case OperationKind.ObjectCreation: + countOperator(operation); + countOperand(((IObjectCreationOperation)operation).Constructor); + continue; + case OperationKind.DelegateCreation: + case OperationKind.AnonymousObjectCreation: + case OperationKind.TypeParameterObjectCreation: + case OperationKind.DynamicObjectCreation: + case OperationKind.DynamicInvocation: + countOperator(operation); + continue; + + // Operators with special operator kinds. + case OperationKind.BinaryOperator: + countBinaryOperator(operation, ((IBinaryOperation)operation).OperatorKind); + continue; + case OperationKind.CompoundAssignment: + countBinaryOperator(operation, ((ICompoundAssignmentOperation)operation).OperatorKind); + continue; + // https://github.com/dotnet/roslyn-analyzers/issues/1742 + //case OperationKind.TupleBinaryOperator: + // countBinaryOperator(operation, ((ITupleBinaryOperation)operation).OperatorKind); + // continue; + case OperationKind.UnaryOperator: + countUnaryOperator(operation, ((IUnaryOperation)operation).OperatorKind); + continue; + case OperationKind.CaseClause: + var caseClauseOperation = (ICaseClauseOperation)operation; + distinctCaseKindsBuilder = distinctCaseKindsBuilder ?? ImmutableHashSet.CreateBuilder(); + distinctCaseKindsBuilder.Add(caseClauseOperation.CaseKind); + if (caseClauseOperation.CaseKind == CaseKind.Relational) + { + countBinaryOperator(operation, ((IRelationalCaseClauseOperation)operation).Relation); + } + else + { + countOperator(operation); + } + continue; + + // Other common operators. + case OperationKind.Increment: + case OperationKind.Decrement: + case OperationKind.SimpleAssignment: + case OperationKind.DeconstructionAssignment: + case OperationKind.EventAssignment: + case OperationKind.Coalesce: + case OperationKind.ConditionalAccess: + case OperationKind.Conversion: + case OperationKind.ArrayElementReference: + case OperationKind.Await: + case OperationKind.NameOf: + case OperationKind.SizeOf: + case OperationKind.TypeOf: + case OperationKind.AddressOf: + case OperationKind.MemberInitializer: + case OperationKind.IsType: + case OperationKind.IsPattern: + case OperationKind.Parenthesized: + countOperator(operation); + continue; + + // Following are considered operators for now, but we may want to revisit. + case OperationKind.ArrayCreation: + case OperationKind.ArrayInitializer: + case OperationKind.DynamicMemberReference: + case OperationKind.DynamicIndexerAccess: + case OperationKind.Tuple: + case OperationKind.Lock: + case OperationKind.Using: + case OperationKind.Throw: + case OperationKind.RaiseEvent: + case OperationKind.InterpolatedString: + countOperator(operation); + continue; + + // Return value. + case OperationKind.Return: + case OperationKind.YieldBreak: + case OperationKind.YieldReturn: + if (((IReturnOperation)operation).ReturnedValue != null) + { + countOperator(operation); + } + continue; + } + } + + return Create( + effectiveLinesOfCode, + operatorUsageCounts, + symbolUsageCounts, + constantUsageCounts, + distinctOperatorKindsBuilder != null ? distinctOperatorKindsBuilder.ToImmutable() : ImmutableHashSet.Empty, + distinctBinaryOperatorKindsBuilder != null ? distinctBinaryOperatorKindsBuilder.ToImmutable() : ImmutableHashSet.Empty, + distinctUnaryOperatorKindsBuilder != null ? distinctUnaryOperatorKindsBuilder.ToImmutable() : ImmutableHashSet.Empty, + distinctCaseKindsBuilder != null ? distinctCaseKindsBuilder.ToImmutable() : ImmutableHashSet.Empty, + distinctReferencedSymbolsBuilder != null ? distinctReferencedSymbolsBuilder.ToImmutable() : ImmutableHashSet.Empty, + distinctReferencedConstantsBuilder != null ? distinctReferencedConstantsBuilder.ToImmutable() : ImmutableHashSet.Empty); + + int getEffectiveLinesOfCode(IOperation operation) + { + if (operation.Parent != null) + { + switch (operation.Parent.Kind) + { + case OperationKind.Block: + return hasAnyExplicitExpression(operation) ? 1 : 0; + + case OperationKind.FieldInitializer: + case OperationKind.PropertyInitializer: + case OperationKind.ParameterInitializer: + // Count declaration and initialization. + return hasAnyExplicitExpression(operation) ? 2 : 0; + } + } + + return 0; + } + + bool hasAnyExplicitExpression(IOperation operation) + { + // Check if all descendants are either implicit or are explicit with no constant value or type, indicating it is not user written code. + return !operation.DescendantsAndSelf().All(o => o.IsImplicit || (!o.ConstantValue.HasValue && o.Type == null)); + } + + void countOperator(IOperation operation) + { + operatorUsageCounts++; + distinctOperatorKindsBuilder = distinctOperatorKindsBuilder ?? ImmutableHashSet.CreateBuilder(); + distinctOperatorKindsBuilder.Add(operation.Kind); + } + + void countOperand(ISymbol symbol) + { + symbolUsageCounts++; + distinctReferencedSymbolsBuilder = distinctReferencedSymbolsBuilder ?? ImmutableHashSet.CreateBuilder(); + distinctReferencedSymbolsBuilder.Add(symbol); + } + + void countBinaryOperator(IOperation operation, BinaryOperatorKind operatorKind) + { + countOperator(operation); + distinctBinaryOperatorKindsBuilder = distinctBinaryOperatorKindsBuilder ?? ImmutableHashSet.CreateBuilder(); + distinctBinaryOperatorKindsBuilder.Add(operatorKind); + } + + void countUnaryOperator(IOperation operation, UnaryOperatorKind operatorKind) + { + countOperator(operation); + distinctUnaryOperatorKindsBuilder = distinctUnaryOperatorKindsBuilder ?? ImmutableHashSet.CreateBuilder(); + distinctUnaryOperatorKindsBuilder.Add(operatorKind); + } + } + + public ComputationalComplexityMetrics Union(ComputationalComplexityMetrics other) + { + if (ReferenceEquals(this, Default)) + { + return other; + } + else if (ReferenceEquals(other, Default)) + { + return this; + } + + return new ComputationalComplexityMetrics( + effectiveLinesOfCode: EffectiveLinesOfCode + other.EffectiveLinesOfCode, + operatorUsageCounts: _operatorUsageCounts + other._operatorUsageCounts, + symbolUsageCounts: _symbolUsageCounts + other._symbolUsageCounts, + constantUsageCounts: _constantUsageCounts + other._constantUsageCounts, + distinctOperatorKinds: _distinctOperatorKinds.Union(other._distinctOperatorKinds), + distinctBinaryOperatorKinds: _distinctBinaryOperatorKinds.Union(other._distinctBinaryOperatorKinds), + distinctUnaryOperatorKinds: _distinctUnaryOperatorKinds.Union(other._distinctUnaryOperatorKinds), + distinctCaseKinds: _distinctCaseKinds.Union(other._distinctCaseKinds), + distinctReferencedSymbols: _distinctReferencedSymbols.Union(other._distinctReferencedSymbols), + distinctReferencedConstants: _distinctReferencedConstants.Union(other._distinctReferencedConstants)); + } + + public bool IsDefault => ReferenceEquals(this, Default); + + /// The number of unique operators found. + public long DistinctOperators //n1 + { + get + { + var count = _distinctBinaryOperatorKinds.Count; + if (_distinctBinaryOperatorKinds.Count > 1) + { + count += _distinctBinaryOperatorKinds.Count - 1; + } + if (_distinctUnaryOperatorKinds.Count > 1) + { + count += _distinctUnaryOperatorKinds.Count - 1; + } + if (_distinctCaseKinds.Count > 1) + { + count += _distinctCaseKinds.Count - 1; + } + + return count; + } + } + + /// The number of unique operands found. + public long DistinctOperands //n2 + { + get + { + return _distinctReferencedSymbols.Count + _distinctReferencedConstants.Count; + } + } + + /// The total number of operator usages found. + public long TotalOperators //N1 + { + get { return _operatorUsageCounts; } + } + + /// The total number of operand usages found. + public long TotalOperands //N2 + { + get + { + return _symbolUsageCounts + _constantUsageCounts; + } + } + + public long Vocabulary //n + { + // n = n1 + n2 + get { return DistinctOperators + DistinctOperands; } + } + + public long Length //N + { + // N = N1 + N2 + get { return TotalOperators + TotalOperands; } + } + + public double Volume //V + { + // V = N * Log2(n) + get { return (Length * Math.Max(0.0, Math.Log(Vocabulary, 2))); } + } + + public long EffectiveLinesOfCode { get; } + } +} diff --git a/src/Utilities/CodeMetrics/MetricsHelper.cs b/src/Utilities/CodeMetrics/MetricsHelper.cs new file mode 100644 index 0000000000..165359a695 --- /dev/null +++ b/src/Utilities/CodeMetrics/MetricsHelper.cs @@ -0,0 +1,309 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Operations; + +namespace Microsoft.CodeAnalysis.CodeMetrics +{ + internal static class MetricsHelper + { + internal static int GetAverageRoundedMetricValue(int total, int childrenCount) + { + Debug.Assert(childrenCount != 0); + return RoundMetricValue(total / childrenCount); + } + + private static int RoundMetricValue(double value) => (int)Math.Round(value, 0); + + internal static int NormalizeAndRoundMaintainabilityIndex(double maintIndex) + { + maintIndex = Math.Max(0.0, maintIndex); + return RoundMetricValue((maintIndex / 171.0) * 100.0); + } + + internal static void AddCoupledNamedTypes(ImmutableHashSet.Builder builder, IEnumerable coupledTypes) + { + foreach (var coupledType in coupledTypes) + { + AddCoupledNamedTypesCore(builder, coupledType); + } + } + + internal static void AddCoupledNamedTypes(ImmutableHashSet.Builder builder, params ITypeSymbol[] coupledTypes) + { + foreach (var coupledType in coupledTypes) + { + AddCoupledNamedTypesCore(builder, coupledType); + } + } + + internal static void AddCoupledNamedTypes(ImmutableHashSet.Builder builder, ImmutableArray parameters) + { + foreach (var parameter in parameters) + { + AddCoupledNamedTypesCore(builder, parameter.Type); + } + } + + internal static async Task GetLinesOfCodeAsync(ImmutableArray 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 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 && model.GetDeclaredSymbol(declSyntax.Parent, cancellationToken) == declaredSymbol) + { + declSyntax = declSyntax.Parent; + } + } + + return declSyntax; + } + + internal static async Task<(int cyclomaticComplexity, ComputationalComplexityMetrics computationalComplexityMetrics)> ComputeCoupledTypesAndComplexityExcludingMemberDeclsAsync( + ImmutableArray declarations, + ISymbol symbol, + ImmutableHashSet.Builder builder, + SemanticModelProvider semanticModelProvider, + CancellationToken cancellationToken) + { + int cyclomaticComplexity = 0; + ComputationalComplexityMetrics computationalComplexityMetrics = ComputationalComplexityMetrics.Default; + + var nodesToProcess = new Queue(); + + 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 && 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: + // https://github.com/dotnet/roslyn-analyzers/issues/1742 + //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 (!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); + } + } + } + else + { + // Enqueue child nodes for further processing. + foreach (var child in node.ChildNodes()) + { + nodesToProcess.Enqueue(child); + } + } + } while (nodesToProcess.Count != 0); + } + + return (cyclomaticComplexity, computationalComplexityMetrics); + + bool hasConditionalLogic(IOperation operation) + { + switch (operation.Kind) + { + case OperationKind.CaseClause: + case OperationKind.CatchClause: + case OperationKind.Coalesce: + case OperationKind.Conditional: + case OperationKind.ConditionalAccess: + case OperationKind.Loop: + return true; + + case OperationKind.BinaryOperator: + var binaryOperation = (IBinaryOperation)operation; + return binaryOperation.OperatorKind == BinaryOperatorKind.ConditionalAnd || + binaryOperation.OperatorKind == BinaryOperatorKind.ConditionalOr || + (binaryOperation.Type.SpecialType == SpecialType.System_Boolean && + (binaryOperation.OperatorKind == BinaryOperatorKind.Or || binaryOperation.OperatorKind == BinaryOperatorKind.And)); + + default: + return false; + } + } + } + + private static void AddCoupledNamedTypesCore(ImmutableHashSet.Builder builder, ITypeSymbol typeOpt) + { + if (typeOpt is INamedTypeSymbol usedType && + !isIgnoreableType(usedType) && + builder.Add(usedType)) + { + if (usedType.IsGenericType) + { + foreach (var type in usedType.TypeArguments) + { + AddCoupledNamedTypesCore(builder, type); + } + } + } + + // Compat + bool isIgnoreableType(INamedTypeSymbol namedType) + { + switch (namedType.SpecialType) + { + case SpecialType.System_Boolean: + case SpecialType.System_Byte: + case SpecialType.System_Char: + case SpecialType.System_Double: + case SpecialType.System_Int16: + case SpecialType.System_Int32: + case SpecialType.System_Int64: + case SpecialType.System_UInt16: + case SpecialType.System_UInt32: + case SpecialType.System_UInt64: + case SpecialType.System_IntPtr: + case SpecialType.System_UIntPtr: + case SpecialType.System_SByte: + case SpecialType.System_Single: + case SpecialType.System_String: + case SpecialType.System_Object: + case SpecialType.System_ValueType: + case SpecialType.System_Void: + return true; + + default: + return false; + } + } + } + + internal static void RemoveContainingTypes(ISymbol symbol, ImmutableHashSet.Builder coupledTypesBuilder) + { + var namedType = symbol as INamedTypeSymbol ?? symbol.ContainingType; + while (namedType != null) + { + coupledTypesBuilder.Remove(namedType); + namedType = namedType.ContainingType; + } + } + + internal static ImmutableArray GetParameters(this ISymbol member) + { + switch (member.Kind) + { + case SymbolKind.Method: + return ((IMethodSymbol)member).Parameters; + case SymbolKind.Property: + return ((IPropertySymbol)member).Parameters; + default: + return ImmutableArray.Empty; + } + } + } +} diff --git a/src/Utilities/CodeMetrics/SemanticModelProvider.cs b/src/Utilities/CodeMetrics/SemanticModelProvider.cs new file mode 100644 index 0000000000..afb131b324 --- /dev/null +++ b/src/Utilities/CodeMetrics/SemanticModelProvider.cs @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Concurrent; +using Microsoft.CodeAnalysis; + +namespace Microsoft.CodeAnalysis.CodeMetrics +{ + internal sealed class SemanticModelProvider + { + private readonly ConcurrentDictionary _semanticModelMap; + public SemanticModelProvider(Compilation compilation) + { + Compilation = compilation; + _semanticModelMap = new ConcurrentDictionary(); + } + + public Compilation Compilation { get; } + + public SemanticModel GetSemanticModel(SyntaxNode node) + => _semanticModelMap.GetOrAdd(node.SyntaxTree, tree => Compilation.GetSemanticModel(node.SyntaxTree)); + } +}