Skip to content

Commit 7385888

Browse files
Refactor cognitive metrics and reporting for improved clarity and functionality
- Renamed metrics in `cognitive-metrics-settings.json` for consistency, changing `variableCount` to `localVariableCount` and `propertyCallCount` to `propertyAccessCount`. - Updated `CognitiveCodeAnalyser` to support asynchronous file reading based on target framework. - Removed legacy metric alias resolution in `ScoreCalculator` to simplify score calculations. - Enhanced `CognitiveCiSeverity` to utilize `StringBuilder` for message formatting, improving performance and readability. - Introduced `CognitiveReportTableFormat` for structured report generation, including new Markdown report format. - Added comprehensive metrics handling in `CsvReport`, `HtmlReport`, and `MarkdownReport` to ensure consistent output across formats. - Implemented new tests to validate the changes in metrics reporting and ensure accurate output across all report types.
2 parents b5fdf51 + 7996f47 commit 7385888

29 files changed

Lines changed: 1299 additions & 253 deletions

CognitiveCodeAnalysis.Tests/CognitiveCodeAnalysis.Tests.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,8 @@
4343
<Compile Remove="fixtures\**\*.cs" />
4444
</ItemGroup>
4545

46+
<ItemGroup>
47+
<None Include="fixtures\reports\golden\**\*" CopyToOutputDirectory="PreserveNewest" />
48+
</ItemGroup>
49+
4650
</Project>
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
FilePath,ClassName,MethodName,MethodSignature,LineNumber,TotalScore,LinesOfCode,IfCount,IfScore,ElseCount,ElseScore,LoopCount,LoopScore,SwitchCount,SwitchScore,TryCatchCount,TryCatchScore,ArgumentCount,ArgumentScore,NestingLevels,NestingScore,ReturnCount,ReturnScore,LocalVariableCount,LocalVariableScore,FieldAccessCount,FieldAccessScore,PropertyAccessCount,PropertyAccessScore
2+
src/Demo.cs,Demo,Alpha,void Alpha(),10,8.500,25,2,0.400,1,0.200,1,0.150,0,0.000,1,0.100,3,0.300,2,0.250,1,0.100,4,0.200,2,0.100,1,0.050
3+
src/Demo.cs,Demo,Beta,void Beta(),42,1.200,8,0,0.000,0,0.000,0,0.000,1,0.100,0,0.000,1,0.050,0,0.000,0,0.000,1,0.050,0,0.000,2,0.100
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
::warning file=src/Demo.cs,line=10,title=cognitive/method-complexity score 8.500::Cognitive complexity score 8.500 for Demo.Alpha (threshold 5.000). lines=25(0.500); if=2(0.400); else=1(0.200); loop=1(0.150); switch=0(0.000); try-catch=1(0.100); args=3(0.300); nesting=2(0.250); returns=1(0.100); locals=4(0.200); fields=2(0.100); props=1(0.050)
2+
::notice file=src/Demo.cs,line=42,title=cognitive/method-complexity score 1.200::Cognitive complexity score 1.200 for Demo.Beta (threshold 5.000). lines=8(0.100); if=0(0.000); else=0(0.000); loop=0(0.000); switch=1(0.100); try-catch=0(0.000); args=1(0.050); nesting=0(0.000); returns=0(0.000); locals=1(0.050); fields=0(0.000); props=2(0.100)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[{"type":"issue","checkName":"cognitive/method-complexity","description":"Cognitive complexity score 8.500 for Demo.Alpha (threshold 5.000). lines=25(0.500); if=2(0.400); else=1(0.200); loop=1(0.150); switch=0(0.000); try-catch=1(0.100); args=3(0.300); nesting=2(0.250); returns=1(0.100); locals=4(0.200); fields=2(0.100); props=1(0.050)","categories":["Complexity"],"severity":"minor","fingerprint":"f316cb2ef4533faa315430642a1103e09a52984c10459123b777ed92631aa520","location":{"path":"src/Demo.cs","lines":{"begin":10,"end":10}}},{"type":"issue","checkName":"cognitive/method-complexity","description":"Cognitive complexity score 1.200 for Demo.Beta (threshold 5.000). lines=8(0.100); if=0(0.000); else=0(0.000); loop=0(0.000); switch=1(0.100); try-catch=0(0.000); args=1(0.050); nesting=0(0.000); returns=0(0.000); locals=1(0.050); fields=0(0.000); props=2(0.100)","categories":["Complexity"],"severity":"info","fingerprint":"a0a7fde9d1aba758c1bdffdf7c9fd837a03b60a485035b2ce4d9951ec908c70e","location":{"path":"src/Demo.cs","lines":{"begin":42,"end":42}}}]
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6+
<title>Cognitive Code Analysis Report</title>
7+
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
8+
<style>
9+
body { font-size: 0.9rem; }
10+
.table { font-size: 0.9rem; }
11+
.score-green { color: #28a745; font-weight: bold; }
12+
.score-yellow { color: #ffc107; font-weight: bold; }
13+
.score-red { color: #dc3545; font-weight: bold; }
14+
.class-header { margin-top: 2rem; margin-bottom: 1rem; }
15+
.report-class-section:first-of-type .class-header { margin-top: 1rem; }
16+
.report-filter-bar { max-width: 32rem; }
17+
.delta-up { color: #dc3545; font-weight: bold; }
18+
.delta-down { color: #28a745; font-weight: bold; }
19+
</style>
20+
</head>
21+
<body>
22+
<div class="container-fluid mt-4">
23+
<h1 class="mb-4">Cognitive Code Analysis Report</h1>
24+
<div class="mb-4 report-filter-bar">
25+
<label for="report-filter" class="form-label">Filter by class or file path</label>
26+
<input type="search" id="report-filter" class="form-control" placeholder="e.g. controller" autocomplete="off">
27+
<div class="form-text">Case-insensitive match on class name or full file path. Empty shows all sections.</div>
28+
</div>
29+
<div class="report-class-section" data-class-name="Demo" data-file-path="src/Demo.cs">
30+
<div class="class-header">
31+
<h3 class="text-primary">Class: Demo</h3>
32+
<p class="text-muted">File: src/Demo.cs</p>
33+
<p class="text-muted">Coupling: In=2, Out=1, Stability=0.333</p>
34+
</div>
35+
<div class="table-responsive">
36+
<table class="table table-striped table-bordered">
37+
<thead class="table-dark">
38+
<tr>
39+
<th>Method</th>
40+
<th>Score</th>
41+
<th>Lines</th>
42+
<th>Ifs</th>
43+
<th>Else</th>
44+
<th>Loops</th>
45+
<th>Switch</th>
46+
<th>Try/Catch</th>
47+
<th>Arguments</th>
48+
<th>Nesting</th>
49+
<th>Returns</th>
50+
<th>Locals</th>
51+
<th>Fields</th>
52+
<th>Props</th>
53+
</tr>
54+
</thead>
55+
<tbody>
56+
<tr>
57+
<td>L10 Alpha</td>
58+
<td><span class="score-red">8.500</span></td>
59+
<td>25 (0.500)</td>
60+
<td>2 (0.400)</td>
61+
<td>1 (0.200)</td>
62+
<td>1 (0.150)</td>
63+
<td>0 (0.000)</td>
64+
<td>1 (0.100)</td>
65+
<td>3 (0.300)</td>
66+
<td>2 (0.250)</td>
67+
<td>1 (0.100)</td>
68+
<td>4 (0.200)</td>
69+
<td>2 (0.100)</td>
70+
<td>1 (0.050)</td>
71+
</tr>
72+
<tr>
73+
<td>L42 Beta</td>
74+
<td><span class="score-red">1.200</span></td>
75+
<td>8 (0.100)</td>
76+
<td>0 (0.000)</td>
77+
<td>0 (0.000)</td>
78+
<td>0 (0.000)</td>
79+
<td>1 (0.100)</td>
80+
<td>0 (0.000)</td>
81+
<td>1 (0.050)</td>
82+
<td>0 (0.000)</td>
83+
<td>0 (0.000)</td>
84+
<td>1 (0.050)</td>
85+
<td>0 (0.000)</td>
86+
<td>2 (0.100)</td>
87+
</tr>
88+
</tbody>
89+
</table>
90+
</div>
91+
</div>
92+
</div>
93+
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
94+
<script>
95+
(function(){
96+
var input=document.getElementById('report-filter');
97+
if(!input)return;
98+
var sections=document.querySelectorAll('.report-class-section');
99+
function apply(){
100+
var q=input.value.trim().toLowerCase();
101+
sections.forEach(function(sec){
102+
if(!q){ sec.classList.remove('d-none'); return; }
103+
var cn=(sec.getAttribute('data-class-name')||'').toLowerCase();
104+
var fp=(sec.getAttribute('data-file-path')||'').toLowerCase();
105+
var show=cn.indexOf(q)!==-1||fp.indexOf(q)!==-1;
106+
sec.classList.toggle('d-none',!show);
107+
});
108+
}
109+
input.addEventListener('input',apply);
110+
})();
111+
</script>
112+
</body>
113+
</html>
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6+
<title>Cognitive Code Analysis Report</title>
7+
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
8+
<style>
9+
body { font-size: 0.9rem; }
10+
.table { font-size: 0.9rem; }
11+
.score-green { color: #28a745; font-weight: bold; }
12+
.score-yellow { color: #ffc107; font-weight: bold; }
13+
.score-red { color: #dc3545; font-weight: bold; }
14+
.class-header { margin-top: 2rem; margin-bottom: 1rem; }
15+
.report-class-section:first-of-type .class-header { margin-top: 1rem; }
16+
.report-filter-bar { max-width: 32rem; }
17+
.delta-up { color: #dc3545; font-weight: bold; }
18+
.delta-down { color: #28a745; font-weight: bold; }
19+
</style>
20+
</head>
21+
<body>
22+
<div class="container-fluid mt-4">
23+
<h1 class="mb-4">Cognitive Code Analysis Report</h1>
24+
<div class="mb-4 report-filter-bar">
25+
<label for="report-filter" class="form-label">Filter by class or file path</label>
26+
<input type="search" id="report-filter" class="form-control" placeholder="e.g. controller" autocomplete="off">
27+
<div class="form-text">Case-insensitive match on class name or full file path. Empty shows all sections.</div>
28+
</div>
29+
<div class="report-class-section" data-class-name="Demo" data-file-path="src/Demo.cs">
30+
<div class="class-header">
31+
<h3 class="text-primary">Class: Demo</h3>
32+
<p class="text-success">Method: void Alpha()</p>
33+
<p class="text-muted">File: src/Demo.cs</p>
34+
</div>
35+
<div class="table-responsive">
36+
<table class="table table-striped table-bordered">
37+
<thead class="table-dark">
38+
<tr>
39+
<th>Method</th>
40+
<th>Score</th>
41+
<th>Lines</th>
42+
<th>Ifs</th>
43+
<th>Else</th>
44+
<th>Loops</th>
45+
<th>Switch</th>
46+
<th>Try/Catch</th>
47+
<th>Arguments</th>
48+
<th>Nesting</th>
49+
<th>Returns</th>
50+
<th>Locals</th>
51+
<th>Fields</th>
52+
<th>Props</th>
53+
</tr>
54+
</thead>
55+
<tbody>
56+
<tr>
57+
<td>L10 Alpha</td>
58+
<td><span class="score-red">8.500</span></td>
59+
<td>25 (0.500)</td>
60+
<td>2 (0.400)</td>
61+
<td>1 (0.200)</td>
62+
<td>1 (0.150)</td>
63+
<td>0 (0.000)</td>
64+
<td>1 (0.100)</td>
65+
<td>3 (0.300)</td>
66+
<td>2 (0.250)</td>
67+
<td>1 (0.100)</td>
68+
<td>4 (0.200)</td>
69+
<td>2 (0.100)</td>
70+
<td>1 (0.050)</td>
71+
</tr>
72+
</tbody>
73+
</table>
74+
</div>
75+
</div>
76+
<div class="report-class-section" data-class-name="Demo" data-file-path="src/Demo.cs">
77+
<div class="class-header">
78+
<h3 class="text-primary">Class: Demo</h3>
79+
<p class="text-success">Method: void Beta()</p>
80+
<p class="text-muted">File: src/Demo.cs</p>
81+
</div>
82+
<div class="table-responsive">
83+
<table class="table table-striped table-bordered">
84+
<thead class="table-dark">
85+
<tr>
86+
<th>Method</th>
87+
<th>Score</th>
88+
<th>Lines</th>
89+
<th>Ifs</th>
90+
<th>Else</th>
91+
<th>Loops</th>
92+
<th>Switch</th>
93+
<th>Try/Catch</th>
94+
<th>Arguments</th>
95+
<th>Nesting</th>
96+
<th>Returns</th>
97+
<th>Locals</th>
98+
<th>Fields</th>
99+
<th>Props</th>
100+
</tr>
101+
</thead>
102+
<tbody>
103+
<tr>
104+
<td>L42 Beta</td>
105+
<td><span class="score-red">1.200</span></td>
106+
<td>8 (0.100)</td>
107+
<td>0 (0.000)</td>
108+
<td>0 (0.000)</td>
109+
<td>0 (0.000)</td>
110+
<td>1 (0.100)</td>
111+
<td>0 (0.000)</td>
112+
<td>1 (0.050)</td>
113+
<td>0 (0.000)</td>
114+
<td>0 (0.000)</td>
115+
<td>1 (0.050)</td>
116+
<td>0 (0.000)</td>
117+
<td>2 (0.100)</td>
118+
</tr>
119+
</tbody>
120+
</table>
121+
</div>
122+
</div>
123+
</div>
124+
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
125+
<script>
126+
(function(){
127+
var input=document.getElementById('report-filter');
128+
if(!input)return;
129+
var sections=document.querySelectorAll('.report-class-section');
130+
function apply(){
131+
var q=input.value.trim().toLowerCase();
132+
sections.forEach(function(sec){
133+
if(!q){ sec.classList.remove('d-none'); return; }
134+
var cn=(sec.getAttribute('data-class-name')||'').toLowerCase();
135+
var fp=(sec.getAttribute('data-file-path')||'').toLowerCase();
136+
var show=cn.indexOf(q)!==-1||fp.indexOf(q)!==-1;
137+
sec.classList.toggle('d-none',!show);
138+
});
139+
}
140+
input.addEventListener('input',apply);
141+
})();
142+
</script>
143+
</body>
144+
</html>
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Cognitive Code Analysis Report
2+
3+
### Class: Demo
4+
5+
**File:** `src/Demo.cs`
6+
7+
**Coupling:** In=2, Out=1, Stability=0.333
8+
9+
| Method | Score | Lines | Ifs | Else | Loops | Switch | Try/Catch | Arguments | Nesting | Returns | Locals | Fields | Props |
10+
| ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
11+
| L10 Alpha | 8.500 | 25 | 2 (0.400) | 1 (0.200) | 1 (0.150) | 0 (0.000) | 1 (0.100) | 3 (0.300) | 2 (0.250) | 1 (0.100) | 4 (0.200) | 2 (0.100) | 1 (0.050) |
12+
| L42 Beta | 1.200 | 8 | 0 (0.000) | 0 (0.000) | 0 (0.000) | 1 (0.100) | 0 (0.000) | 1 (0.050) | 0 (0.000) | 0 (0.000) | 1 (0.050) | 0 (0.000) | 2 (0.100) |
13+
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Cognitive Code Analysis Report
2+
3+
### Class: Demo
4+
5+
**Method:** `void Alpha()`
6+
7+
**File:** `src/Demo.cs`
8+
9+
| Method | Score | Lines | Ifs | Else | Loops | Switch | Try/Catch | Arguments | Nesting | Returns | Locals | Fields | Props |
10+
| ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
11+
| L10 Alpha | 8.500 | 25 | 2 (0.400) | 1 (0.200) | 1 (0.150) | 0 (0.000) | 1 (0.100) | 3 (0.300) | 2 (0.250) | 1 (0.100) | 4 (0.200) | 2 (0.100) | 1 (0.050) |
12+
13+
### Class: Demo
14+
15+
**Method:** `void Beta()`
16+
17+
**File:** `src/Demo.cs`
18+
19+
| Method | Score | Lines | Ifs | Else | Loops | Switch | Try/Catch | Arguments | Nesting | Returns | Locals | Fields | Props |
20+
| ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
21+
| L42 Beta | 1.200 | 8 | 0 (0.000) | 0 (0.000) | 0 (0.000) | 1 (0.100) | 0 (0.000) | 1 (0.050) | 0 (0.000) | 0 (0.000) | 1 (0.050) | 0 (0.000) | 2 (0.100) |
22+
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"$schema":"https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json","version": "GOLDEN_VERSION","runs":[{"tool":{"driver":{"name":"CognitiveCodeAnalysis","version": "GOLDEN_VERSION","rules":[{"id":"cognitive/method-complexity","name":"Method cognitive complexity","shortDescription":{"text":"Cognitive complexity score for a C# method."},"fullDescription":{"text":"Aggregated structural and churn-based complexity metrics per method."}}]}},"results":[{"ruleId":"cognitive/method-complexity","level":"warning","message":{"text":"Cognitive complexity score 8.500 for Demo.Alpha (threshold 5.000). lines=25(0.500); if=2(0.400); else=1(0.200); loop=1(0.150); switch=0(0.000); try-catch=1(0.100); args=3(0.300); nesting=2(0.250); returns=1(0.100); locals=4(0.200); fields=2(0.100); props=1(0.050)"},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"src/Demo.cs"},"region":{"startLine":10,"endLine":10}}}]},{"ruleId":"cognitive/method-complexity","level":"note","message":{"text":"Cognitive complexity score 1.200 for Demo.Beta (threshold 5.000). lines=8(0.100); if=0(0.000); else=0(0.000); loop=0(0.000); switch=1(0.100); try-catch=0(0.000); args=1(0.050); nesting=0(0.000); returns=0(0.000); locals=1(0.050); fields=0(0.000); props=2(0.100)"},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"src/Demo.cs"},"region":{"startLine":42,"endLine":42}}}]}]}]}

0 commit comments

Comments
 (0)