public override void Initialize(AnalysisContext analysisContext) { analysisContext.EnableConcurrentExecution(); analysisContext.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); analysisContext.RegisterCompilationAction(compilationContext => { if (compilationContext.Compilation.SyntaxTrees.FirstOrDefault() is not SyntaxTree tree) { return; } // Try read the additional file containing the code metrics configuration. if (!TryGetRuleIdToThresholdMap( compilationContext.Options.AdditionalFiles, compilationContext.CancellationToken, out AdditionalText? additionalTextOpt, out ImmutableDictionary <string, IReadOnlyList <(SymbolKind?, uint)> >?ruleIdToThresholdMap, out List <Diagnostic>?invalidFileDiagnostics) && invalidFileDiagnostics != null) { // Report any invalid additional file diagnostics. foreach (var diagnostic in invalidFileDiagnostics) { compilationContext.ReportDiagnostic(diagnostic); } return; } // Compute code metrics. // For the calculation of the inheritance tree, we are allowing specific exclusions: // - all types from System namespaces // - all types/namespaces provided by the user // so that the calculation isn't unfair. // For example inheriting from WPF/WinForms UserControl makes your class over the default threshold, yet there isn't anything you can do about it. var inheritanceExcludedTypes = compilationContext.Options.GetInheritanceExcludedSymbolNamesOption(CA1501Rule, tree, compilationContext.Compilation, defaultForcedValue: "N:System.*", compilationContext.CancellationToken); var metricsAnalysisContext = new CodeMetricsAnalysisContext(compilationContext.Compilation, compilationContext.CancellationToken, namedType => inheritanceExcludedTypes.Contains(namedType)); var computeTask = CodeAnalysisMetricData.ComputeAsync(metricsAnalysisContext); 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(t => !inheritanceExcludedTypes.Contains(t)).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)); } static 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(); } }
public override void Initialize(AnalysisContext analysisContext) { analysisContext.EnableConcurrentExecution(); analysisContext.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); analysisContext.RegisterCompilationAction(compilationContext => { if (compilationContext.Compilation.SyntaxTrees.FirstOrDefault() is not SyntaxTree tree) { return; } // Try read the additional file containing the code metrics configuration. if (!TryGetRuleIdToThresholdMap( compilationContext.Options.AdditionalFiles, compilationContext.CancellationToken, out AdditionalText? additionalTextOpt, out ImmutableDictionary <string, IReadOnlyList <(SymbolKind?, uint)> >?ruleIdToThresholdMap, out List <Diagnostic>?invalidFileDiagnostics) && invalidFileDiagnostics != null) { // Report any invalid additional file diagnostics. foreach (var diagnostic in invalidFileDiagnostics) { compilationContext.ReportDiagnostic(diagnostic); } return; } var metricsAnalysisContext = new CodeMetricsAnalysisContext(compilationContext.Compilation, compilationContext.CancellationToken, namedType => IsConfiguredToSkipFromInheritanceCount(namedType, compilationContext, tree)); var computeTask = CodeAnalysisMetricData.ComputeAsync(metricsAnalysisContext); 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(t => !IsConfiguredToSkipFromInheritanceCount(t, compilationContext, tree)).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)); } static bool isApplicableByDefault(string ruleId, SymbolKind symbolKind) { return(ruleId switch { CA1501RuleId => symbolKind == SymbolKind.NamedType, CA1502RuleId => symbolKind == SymbolKind.Method, CA1505RuleId => symbolKind switch { SymbolKind.NamedType or SymbolKind.Method or SymbolKind.Field or SymbolKind.Property or SymbolKind.Event => true, _ => false, }, CA1506RuleId => symbolKind switch { SymbolKind.NamedType or SymbolKind.Method or SymbolKind.Field or SymbolKind.Property or SymbolKind.Event => true, _ => false, },