Skip to content

Commit 5718ce7

Browse files
Refactor cognitive metrics and analysis to include additional metrics and improve score calculations
- Updated `cognitive-metrics-settings.json` to disable several metrics by default, including `variableCount`, `propertyCallCount`, `loopCount`, `switchCount`, and `tryCatchCount`. - Enhanced `CognitiveCodeAnalyser` to utilize a `SemanticModel` for improved analysis, allowing for accurate counting of local variables, field accesses, and property accesses. - Introduced new methods for counting loop statements and local variables, and updated the `AnalyseClasses` method to incorporate these metrics. - Modified `ScoreCalculator` to resolve legacy metric aliases and ensure proper score calculations for new metrics. - Added tests to validate the counting of loop statements, local variables, and field/property accesses, as well as the correct mapping of scores. - Updated documentation to reflect changes in supported metric keys and their contributions to total scores.
1 parent 891802f commit 5718ce7

11 files changed

Lines changed: 433 additions & 58 deletions

File tree

CognitiveCodeAnalysis.Tests/src/CognitiveAnalysis/CognitiveAnalysisFacadeTests.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ public void DoWork() {
9393
Assert.That(metrics.MethodName, Is.EqualTo("DoWork"));
9494
Assert.That(metrics.FilePath, Is.Not.Null.And.Not.Empty);
9595
Assert.That(metrics.linesOfCode, Is.GreaterThan(0));
96+
Assert.That(metrics.ifCount, Is.EqualTo(1));
9697
Assert.That(metrics.cyclomaticComplexity, Is.GreaterThanOrEqualTo(2));
9798
Assert.That(metrics.Halstead, Is.Not.Null);
9899
}
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
/// <copyright company="Florian Krämer">
2+
/// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
/// </copyright>
4+
5+
using CognitiveCodeAnalysis.CognitiveAnalysis;
6+
using CognitiveCodeAnalysis.Configuration;
7+
8+
namespace CognitiveCodeAnalysis.Tests.CognitiveAnalysis;
9+
10+
public class CognitiveCodeAnalyserTests
11+
{
12+
private CognitiveCodeAnalyser _analyser;
13+
private CognitiveConfiguration _configuration;
14+
private TempFiles _tempFiles;
15+
16+
[SetUp]
17+
public void SetUp()
18+
{
19+
_analyser = new CognitiveCodeAnalyser();
20+
_configuration = new CognitiveConfiguration();
21+
_tempFiles = new TempFiles();
22+
}
23+
24+
[TearDown]
25+
public void TearDown()
26+
{
27+
_tempFiles.CleanUp();
28+
}
29+
30+
[Test]
31+
public void AnalyseFiles_CountsLoopAndSwitchStatements()
32+
{
33+
const string content = @"
34+
namespace X {
35+
public class Y {
36+
public void Run() {
37+
for (int i = 0; i < 1; i++) { }
38+
foreach (var item in new int[0]) { }
39+
while (false) { }
40+
do { } while (false);
41+
switch (1) {
42+
case 1: break;
43+
}
44+
}
45+
}
46+
}";
47+
string file = _tempFiles.CreateFileWithContent("Loops.cs", content);
48+
49+
var metrics = _analyser.AnalyseFiles([file], _configuration);
50+
51+
Assert.That(metrics.Count, Is.EqualTo(1));
52+
using (Assert.EnterMultipleScope())
53+
{
54+
Assert.That(metrics.First().loopCount, Is.EqualTo(4));
55+
Assert.That(metrics.First().switchCount, Is.EqualTo(1));
56+
}
57+
}
58+
59+
[Test]
60+
public void AnalyseFiles_CountsLocalVariables()
61+
{
62+
const string content = @"
63+
namespace X {
64+
public class Y {
65+
public void Run(int arg) {
66+
int a = 1;
67+
var b = 2;
68+
string c = ""x"";
69+
}
70+
}
71+
}";
72+
string file = _tempFiles.CreateFileWithContent("Locals.cs", content);
73+
74+
var metrics = _analyser.AnalyseFiles([file], _configuration);
75+
76+
Assert.That(metrics.First().localVariableCount, Is.EqualTo(3));
77+
}
78+
79+
[Test]
80+
public void AnalyseFiles_CountsFieldAndPropertyAccesses()
81+
{
82+
const string content = @"
83+
namespace X {
84+
public class Y {
85+
private int _field;
86+
public int Prop { get; set; }
87+
88+
public void Run() {
89+
_field = 1;
90+
Prop = 2;
91+
int local = _field + Prop;
92+
}
93+
}
94+
}";
95+
string file = _tempFiles.CreateFileWithContent("Members.cs", content);
96+
97+
var metrics = _analyser.AnalyseFiles([file], _configuration);
98+
99+
using (Assert.EnterMultipleScope())
100+
{
101+
Assert.That(metrics.First().fieldAccessCount, Is.EqualTo(2));
102+
Assert.That(metrics.First().propertyAccessCount, Is.EqualTo(2));
103+
}
104+
}
105+
}

CognitiveCodeAnalysis.Tests/src/CognitiveAnalysis/ScoreCalculatorTests.cs

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,99 @@ public void Setup()
1414
{
1515
}
1616

17+
[Test]
18+
public void CalculateScores_MapsLinesOfCodeToScoreAndTotalScore()
19+
{
20+
var metrics = new CognitiveMetrics(
21+
methodName: "TestMethod",
22+
className: "TestClass",
23+
filePath: "TestFile.cs",
24+
methodSignature: "TestMethod()",
25+
methodLineNumber: 10,
26+
linesOfCode: 85
27+
);
28+
29+
var configuration = new CognitiveConfiguration
30+
{
31+
Metrics = new Dictionary<string, MetricConfiguration>
32+
{
33+
{ "linesOfCode", new MetricConfiguration { Scale = 25.0, Threshold = 60, Enabled = true } },
34+
}
35+
};
36+
37+
new ScoreCalculator().CalculateScores(metrics, configuration);
38+
39+
using (Assert.EnterMultipleScope())
40+
{
41+
Assert.That(metrics.linesOfCodeScore, Is.EqualTo(Math.Log(2)));
42+
Assert.That(metrics.totalScore, Is.EqualTo(metrics.linesOfCodeScore));
43+
}
44+
}
45+
46+
[Test]
47+
public void CalculateScores_ResolvesLegacyMetricAliases()
48+
{
49+
var metrics = new CognitiveMetrics(
50+
methodName: "TestMethod",
51+
className: "TestClass",
52+
filePath: "TestFile.cs",
53+
methodSignature: "TestMethod()",
54+
methodLineNumber: 10,
55+
localVariableCount: 5,
56+
propertyAccessCount: 6
57+
);
58+
59+
var configuration = new CognitiveConfiguration
60+
{
61+
Metrics = new Dictionary<string, MetricConfiguration>
62+
{
63+
{ "variableCount", new MetricConfiguration { Scale = 5.0, Threshold = 4, Enabled = true } },
64+
{ "propertyCallCount", new MetricConfiguration { Scale = 15.0, Threshold = 4, Enabled = true } },
65+
}
66+
};
67+
68+
new ScoreCalculator().CalculateScores(metrics, configuration);
69+
70+
using (Assert.EnterMultipleScope())
71+
{
72+
Assert.That(metrics.localVariableScore, Is.EqualTo(Math.Log(1.2)));
73+
Assert.That(metrics.propertyAccessScore, Is.EqualTo(Math.Log(1 + 2.0 / 15.0)));
74+
Assert.That(metrics.totalScore, Is.EqualTo(metrics.localVariableScore + metrics.propertyAccessScore));
75+
}
76+
}
77+
78+
[Test]
79+
public void CalculateScores_DisabledMetricsDoNotContribute()
80+
{
81+
var metrics = new CognitiveMetrics(
82+
methodName: "TestMethod",
83+
className: "TestClass",
84+
filePath: "TestFile.cs",
85+
methodSignature: "TestMethod()",
86+
methodLineNumber: 10,
87+
localVariableCount: 10,
88+
fieldAccessCount: 10
89+
);
90+
91+
var configuration = new CognitiveConfiguration
92+
{
93+
Metrics = new Dictionary<string, MetricConfiguration>
94+
{
95+
{ "variableCount", new MetricConfiguration { Scale = 5.0, Threshold = 4, Enabled = false } },
96+
{ "fieldAccessCount", new MetricConfiguration { Scale = 15.0, Threshold = 4, Enabled = false } },
97+
}
98+
};
99+
100+
new ScoreCalculator().CalculateScores(metrics, configuration);
101+
102+
using (Assert.EnterMultipleScope())
103+
{
104+
Assert.That(metrics.localVariableScore, Is.EqualTo(0));
105+
Assert.That(metrics.fieldAccessScore, Is.EqualTo(0));
106+
Assert.That(metrics.totalScore, Is.EqualTo(0));
107+
}
108+
}
109+
17110
[Test]
18111
public void CalculateScoresWithValidMetric()
19112
{

CognitiveCodeAnalysis/cognitive-metrics-settings.json

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,17 @@
3030
"variableCount": {
3131
"threshold": 4,
3232
"scale": 5.0,
33-
"enabled": true
33+
"enabled": false
3434
},
3535
"propertyCallCount": {
3636
"threshold": 4,
3737
"scale": 15.0,
38-
"enabled": true
38+
"enabled": false
39+
},
40+
"fieldAccessCount": {
41+
"threshold": 4,
42+
"scale": 15.0,
43+
"enabled": false
3944
},
4045
"ifCount": {
4146
"threshold": 3,
@@ -51,6 +56,21 @@
5156
"threshold": 1,
5257
"scale": 1.0,
5358
"enabled": true
59+
},
60+
"loopCount": {
61+
"threshold": 2,
62+
"scale": 1.0,
63+
"enabled": false
64+
},
65+
"switchCount": {
66+
"threshold": 1,
67+
"scale": 1.0,
68+
"enabled": false
69+
},
70+
"tryCatchCount": {
71+
"threshold": 1,
72+
"scale": 1.0,
73+
"enabled": false
5474
}
5575
}
5676
}

0 commit comments

Comments
 (0)