Skip to content

Commit 030345e

Browse files
committed
add preliminary Code Metrics implementation status and unit-tests, see #403
1 parent 5bb807c commit 030345e

File tree

7 files changed

+410
-1
lines changed

7 files changed

+410
-1
lines changed
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using System.Threading.Tasks;
6+
7+
namespace Rubberduck.Navigation.CodeMetrics
8+
{
9+
public struct CodeMetricsResult
10+
{
11+
public CodeMetricsResult(int lines, int cyclomaticComplexity, int nesting)
12+
: this(lines, cyclomaticComplexity, nesting, Enumerable.Empty<CodeMetricsResult>())
13+
{
14+
}
15+
16+
public CodeMetricsResult(int lines, int cyclomaticComplexity, int nesting, IEnumerable<CodeMetricsResult> childScopeResults)
17+
{
18+
var childScopeMetric =
19+
childScopeResults.Aggregate(new CodeMetricsResult(), (r1, r2) => new CodeMetricsResult(r1.Lines + r2.Lines, r1.CyclomaticComplexity + r2.CyclomaticComplexity, Math.Max(r1.MaximumNesting, r2.MaximumNesting)));
20+
Lines = lines + childScopeMetric.Lines;
21+
CyclomaticComplexity = cyclomaticComplexity + childScopeMetric.CyclomaticComplexity;
22+
MaximumNesting = Math.Max(nesting, childScopeMetric.MaximumNesting);
23+
}
24+
25+
// possibly refer to a selection?
26+
public int Lines { get; private set; }
27+
public int CyclomaticComplexity { get; private set; }
28+
public int MaximumNesting { get; private set; }
29+
30+
}
31+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
using Rubberduck.Parsing.VBA;
2+
using System;
3+
using System.Collections.Generic;
4+
using System.Linq;
5+
using System.Text;
6+
using System.Threading;
7+
using System.Threading.Tasks;
8+
9+
namespace Rubberduck.Navigation.CodeMetrics
10+
{
11+
public interface ICodeMetricsAnalyst
12+
{
13+
Task<CodeMetricsResult> GetResult(RubberduckParserState state, CancellationToken token);
14+
}
15+
}
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using System.Threading;
6+
using System.Threading.Tasks;
7+
using Rubberduck.Parsing.VBA;
8+
using Antlr4.Runtime.Tree;
9+
using Antlr4.Runtime;
10+
using Rubberduck.Parsing.Grammar;
11+
using Rubberduck.VBEditor;
12+
using Antlr4.Runtime.Misc;
13+
14+
namespace Rubberduck.Navigation.CodeMetrics
15+
{
16+
public class ParseTreeMetricsAnalyst : ICodeMetricsAnalyst
17+
{
18+
public async Task<CodeMetricsResult> GetResult(RubberduckParserState state, CancellationToken token)
19+
{
20+
if (state == null || !state.AllUserDeclarations.Any())
21+
{
22+
return new CodeMetricsResult();
23+
}
24+
return await Task.Run(() =>
25+
{
26+
var trees = state.ParseTrees;
27+
var results = new List<CodeMetricsResult>();
28+
29+
foreach (var moduleTree in trees)
30+
{
31+
if (token.IsCancellationRequested)
32+
{
33+
return new CodeMetricsResult();
34+
}
35+
// FIXME rewrite as visitor. That should make subtrees easier and allow us to expand metrics
36+
var cmListener = new CodeMetricsListener(moduleTree.Key);
37+
ParseTreeWalker.Default.Walk(cmListener, moduleTree.Value);
38+
results.Add(cmListener.GetMetricsResult());
39+
}
40+
return new CodeMetricsResult(0, 0, 0, results);
41+
});
42+
}
43+
44+
private class CodeMetricsListener : VBAParserBaseListener
45+
{
46+
private QualifiedModuleName qmn;
47+
private List<CodeMetricsResult> results = new List<CodeMetricsResult>();
48+
49+
public CodeMetricsListener(QualifiedModuleName qmn)
50+
{
51+
this.qmn = qmn;
52+
}
53+
54+
public override void EnterEndOfLine([NotNull] VBAParser.EndOfLineContext context)
55+
{
56+
results.Add(new CodeMetricsResult(1, 0, 0));
57+
}
58+
59+
public override void EnterIfStmt([NotNull] VBAParser.IfStmtContext context)
60+
{
61+
// one additional path beside the default
62+
results.Add(new CodeMetricsResult(0, 1, 0));
63+
}
64+
65+
public override void EnterElseIfBlock([NotNull] VBAParser.ElseIfBlockContext context)
66+
{
67+
// one additonal path beside the default
68+
results.Add(new CodeMetricsResult(0, 1, 0));
69+
}
70+
71+
public override void EnterForEachStmt([NotNull] VBAParser.ForEachStmtContext context)
72+
{
73+
// one additional path
74+
results.Add(new CodeMetricsResult(0, 1, 0));
75+
}
76+
77+
public override void EnterForNextStmt([NotNull] VBAParser.ForNextStmtContext context)
78+
{
79+
results.Add(new CodeMetricsResult(0, 1, 0));
80+
}
81+
82+
public override void EnterCaseClause([NotNull] VBAParser.CaseClauseContext context)
83+
{
84+
results.Add(new CodeMetricsResult(0, 1, 0));
85+
}
86+
87+
public override void EnterSubStmt([NotNull] VBAParser.SubStmtContext context)
88+
{
89+
// this is the default path through the sub
90+
results.Add(new CodeMetricsResult(0, 1, 0));
91+
}
92+
93+
public override void EnterFunctionStmt([NotNull] VBAParser.FunctionStmtContext context)
94+
{
95+
// this is the default path through the function
96+
results.Add(new CodeMetricsResult(0, 1, 0));
97+
}
98+
99+
public override void EnterBlockStmt([NotNull] VBAParser.BlockStmtContext context)
100+
{
101+
var ws = context.whiteSpace();
102+
// FIXME divide by indent size and assume we're indented?
103+
// FIXME LINE_CONTINUATION might interfere here
104+
//results.Add(new CodeMetricsResult(0, 0, ws.ChildCount / 4));
105+
}
106+
107+
// FIXME also check if we need to do something about `mandatoryLineContinuation`?
108+
109+
internal CodeMetricsResult GetMetricsResult()
110+
{
111+
return new CodeMetricsResult(0, 0, 0, results);
112+
}
113+
}
114+
}
115+
}

RetailCoder.VBE/Rubberduck.csproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,9 @@
344344
<Compile Include="Common\WindowsOperatingSystem.cs" />
345345
<Compile Include="Common\UndocumentedAttribute.cs" />
346346
<Compile Include="Navigation\CodeExplorer\ICodeExplorerDeclarationViewModel.cs" />
347+
<Compile Include="Navigation\CodeMetrics\CodeMetricsResult.cs" />
348+
<Compile Include="Navigation\CodeMetrics\ICodeMetricsAnalyst.cs" />
349+
<Compile Include="Navigation\CodeMetrics\ParseTreeMetricsAnalyst.cs" />
347350
<Compile Include="Navigation\Folders\FolderHelper.cs" />
348351
<Compile Include="Refactorings\EncapsulateField\PropertyGenerator.cs" />
349352
<Compile Include="Refactorings\ExtractMethod\ExtractedMethod.cs" />

RubberduckTests/Mocks/MockParser.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,14 @@ public static RubberduckParserState ParseString(string inputCode, out QualifiedM
2929
var parser = Create(vbe.Object);
3030

3131
parser.Parse(new CancellationTokenSource());
32-
if (parser.State.Status == ParserState.Error) { Assert.Inconclusive("Parser Error"); }
32+
if (parser.State.Status == ParserState.Error) {
33+
SyntaxErrorException ex = null;
34+
foreach (var exception in parser.State.ModuleExceptions)
35+
{
36+
ex = exception.Item2;
37+
}
38+
Assert.Inconclusive("Parser Error: {0}", ex?.Message ?? "None?");
39+
}
3340
return parser.State;
3441

3542
}

RubberduckTests/RubberduckTests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,7 @@
180180
<Compile Include="QuickFixes\QuickFixBaseTests.cs" />
181181
<Compile Include="SmartIndenter\VerticalSpacingTests.cs" />
182182
<Compile Include="SourceControl\SynchrounouslyConstructedDeclarationFinderFactory.cs" />
183+
<Compile Include="Stats\ParseTreeMetricsAnalystTests.cs" />
183184
<Compile Include="Symbols\AccessibilityCheckTests.cs" />
184185
<Compile Include="Symbols\ClassModuleDeclarationTests.cs" />
185186
<Compile Include="Symbols\CommandBarControlCaptionGuardTests.cs" />

0 commit comments

Comments
 (0)