Skip to content

Commit 949fa35

Browse files
committed
#625 Improved support for local methods (Cobertura, dotCover, OpenCover, VisualStudio)
1 parent ae8c4fc commit 949fa35

13 files changed

+618
-383
lines changed

src/Readme.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ CHANGELOG
7070
5.1.26.0
7171

7272
* New: #595 Added new report type 'Html_BlueRed_Summary' to improve red-green colorblind accessibility
73+
* New: #625 Improved support for local methods (Cobertura, dotCover, OpenCover, VisualStudio)
7374
* New: #627 Removed PNG badges and replaced report type 'PngChart' with 'SvgChart'
7475
* Fix: #623 Improved Cobertura output (complexity metric)
7576

src/ReportGenerator.Core/Parser/CoberturaParser.cs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,11 @@ internal class CoberturaParser : ParserBase
4444
/// </summary>
4545
private static readonly Regex CompilerGeneratedMethodNameRegex = new Regex(@"(?<ClassName>.+)(/|\.)<(?<CompilerGeneratedName>.+)>.+__.+MoveNext\(\)$", RegexOptions.Compiled);
4646

47+
/// <summary>
48+
/// Regex to analyze if a method name is a nested method (a method nested within a method).
49+
/// </summary>
50+
private static readonly Regex LocalFunctionMethodNameRegex = new Regex(@"^.*(?<ParentMethodName><.+>).*__(?<NestedMethodName>[^\|]+)\|.*$", RegexOptions.Compiled);
51+
4752
/// <summary>
4853
/// Regex to analyze the branch coverage of a line element.
4954
/// </summary>
@@ -480,8 +485,16 @@ private static Dictionary<int, ICollection<Branch>> GetBranches(IEnumerable<XEle
480485
/// <returns>The method name.</returns>
481486
private static string ExtractMethodName(string methodName, string className)
482487
{
483-
// Quick check before expensive regex is called
484-
if (methodName.EndsWith("MoveNext()"))
488+
if (methodName.Contains("|") || className.Contains("|"))
489+
{
490+
Match match = LocalFunctionMethodNameRegex.Match(className + methodName);
491+
492+
if (match.Success)
493+
{
494+
methodName = match.Groups["NestedMethodName"].Value + "()";
495+
}
496+
}
497+
else if (methodName.EndsWith("MoveNext()"))
485498
{
486499
Match match = CompilerGeneratedMethodNameRegex.Match(className + methodName);
487500

src/ReportGenerator.Core/Parser/DotCoverParser.cs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,11 @@ internal class DotCoverParser : ParserBase
3232
/// </summary>
3333
private static readonly Regex CompilerGeneratedMethodNameRegex = new Regex(@"<(?<CompilerGeneratedName>.+)>.+__.+MoveNext\(\):.+$", RegexOptions.Compiled);
3434

35+
/// <summary>
36+
/// Regex to analyze if a method name is a nested method (a method nested within a method).
37+
/// </summary>
38+
private static readonly Regex LocalFunctionMethodNameRegex = new Regex(@"^.*(?<ParentMethodName><.+>).*__(?<NestedMethodName>[^\|]+)\|.+\((?<Arguments>.*)\):.+$", RegexOptions.Compiled);
39+
3540
/// <summary>
3641
/// Initializes a new instance of the <see cref="DotCoverParser" /> class.
3742
/// </summary>
@@ -278,8 +283,16 @@ private static void SetCodeElements(CodeFile codeFile, string fileId, IEnumerabl
278283
/// <returns>The method name.</returns>
279284
private static string ExtractMethodName(string typeName, string methodName)
280285
{
281-
// Quick check before expensive regex is called
282-
if (methodName.Contains("MoveNext()"))
286+
if (typeName.Contains("|") || methodName.Contains("|"))
287+
{
288+
Match match = LocalFunctionMethodNameRegex.Match(typeName + methodName);
289+
290+
if (match.Success)
291+
{
292+
return match.Groups["NestedMethodName"].Value + "(" + match.Groups["Arguments"].Value + ")";
293+
}
294+
}
295+
else if (methodName.Contains("MoveNext()"))
283296
{
284297
Match match = CompilerGeneratedMethodNameRegex.Match(typeName + methodName);
285298

src/ReportGenerator.Core/Parser/OpenCoverParser.cs

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,18 @@ internal class OpenCoverParser : ParserBase
3939
/// <summary>
4040
/// Regex to analyze if a method name belongs to a lamda expression.
4141
/// </summary>
42-
private static readonly Regex LambdaMethodNameRegex = new Regex("::<.+>.+__", RegexOptions.Compiled);
42+
private static readonly Regex LambdaMethodNameRegex = new Regex("::<.+>.+__[^\\|]+$", RegexOptions.Compiled);
4343

4444
/// <summary>
4545
/// Regex to analyze if a method name is generated by compiler.
4646
/// </summary>
4747
private static readonly Regex CompilerGeneratedMethodNameRegex = new Regex(@"<(?<CompilerGeneratedName>.+)>.+__.+::MoveNext\(\)$", RegexOptions.Compiled);
4848

49+
/// <summary>
50+
/// Regex to analyze if a method name is a nested method (a method nested within a method).
51+
/// </summary>
52+
private static readonly Regex LocalFunctionMethodNameRegex = new Regex(@"^.*(?<ParentMethodName><.+>).*__(?<NestedMethodName>[^\|]+)\|.+\((?<Arguments>.*)\).*$", RegexOptions.Compiled);
53+
4954
/// <summary>
5055
/// Regex to extract short method name.
5156
/// </summary>
@@ -586,8 +591,16 @@ private static string ExtractMethodName(string methodName)
586591
{
587592
if (!MethodNameMap.TryGetValue(methodName, out var fullName))
588593
{
589-
// Quick check before expensive regex is called
590-
if (methodName.EndsWith("::MoveNext()"))
594+
if (methodName.Contains("|"))
595+
{
596+
Match match = LocalFunctionMethodNameRegex.Match(methodName);
597+
598+
if (match.Success)
599+
{
600+
methodName = match.Groups["NestedMethodName"].Value + "(" + match.Groups["Arguments"].Value + ")";
601+
}
602+
}
603+
else if (methodName.EndsWith("::MoveNext()"))
591604
{
592605
Match match = CompilerGeneratedMethodNameRegex.Match(methodName);
593606

src/ReportGenerator.Core/Parser/VisualStudioParser.cs

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,18 @@ internal class VisualStudioParser : ParserBase
2626
/// <summary>
2727
/// Regex to analyze if a method name belongs to a lamda expression.
2828
/// </summary>
29-
private static readonly Regex LambdaMethodNameRegex = new Regex("<.+>.+__", RegexOptions.Compiled);
29+
private static readonly Regex LambdaMethodNameRegex = new Regex("<.+>.+__[^\\|]+$", RegexOptions.Compiled);
3030

3131
/// <summary>
3232
/// Regex to analyze if a method name is generated by compiler.
3333
/// </summary>
3434
private static readonly Regex CompilerGeneratedMethodNameRegex = new Regex(@"^.*<(?<CompilerGeneratedName>.+)>.+__.+!MoveNext\(\)!.+$", RegexOptions.Compiled);
3535

36+
/// <summary>
37+
/// Regex to analyze if a method name is a nested method (a method nested within a method).
38+
/// </summary>
39+
private static readonly Regex LocalFunctionMethodNameRegex = new Regex(@"^.*(?<ParentMethodName><.+>).*__(?<NestedMethodName>[^\|]+)\|.+\((?<Arguments>.*)\).*$", RegexOptions.Compiled);
40+
3641
/// <summary>
3742
/// Regex to extract short method name.
3843
/// </summary>
@@ -329,8 +334,16 @@ private static void SetCodeElements(CodeFile codeFile, IEnumerable<XElement> met
329334
/// <returns>The method name.</returns>
330335
private static string ExtractMethodName(string methodName, string methodKeyName)
331336
{
332-
// Quick check before expensive regex is called
333-
if (methodKeyName.Contains("MoveNext()"))
337+
if (methodKeyName.Contains("|"))
338+
{
339+
Match match = LocalFunctionMethodNameRegex.Match(methodKeyName);
340+
341+
if (match.Success)
342+
{
343+
methodName = match.Groups["NestedMethodName"].Value + "(" + match.Groups["Arguments"].Value + ")";
344+
}
345+
}
346+
else if (methodKeyName.Contains("MoveNext()"))
334347
{
335348
Match match = CompilerGeneratedMethodNameRegex.Match(methodKeyName);
336349

src/ReportGenerator.sln

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Reports", "Reports", "{8775
4444
Testprojects\FSharp\Reports\OpenCover.xml = Testprojects\FSharp\Reports\OpenCover.xml
4545
Testprojects\FSharp\Reports\VisualStudio2010.coveragexml = Testprojects\FSharp\Reports\VisualStudio2010.coveragexml
4646
Testprojects\FSharp\Reports\VisualStudio2013.coveragexml = Testprojects\FSharp\Reports\VisualStudio2013.coveragexml
47+
Testprojects\CSharp\Reports\VisualStudio2022.coveragexml = Testprojects\CSharp\Reports\VisualStudio2022.coveragexml
4748
EndProjectSection
4849
EndProject
4950
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Java", "Java", "{89813866-CE17-47E5-BCA3-4740F8624BA5}"

src/Testprojects/CSharp/Project_DotNetCore/Test/Program.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ public class Program
99
public static void Main(string[] args)
1010
{
1111
new TestClass().SampleFunction();
12+
new TestClass().ParentMethod();
13+
new TestClass().MethodWithLambda();
1214

1315
new TestClass2("Test").ExecutedMethod();
1416
new TestClass2("Test").SampleFunction("Munich");

src/Testprojects/CSharp/Project_DotNetCore/Test/TestClass.cs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Linq;
23

34
namespace Test
45
{
@@ -24,6 +25,27 @@ public void SampleFunction()
2425
}
2526
}
2627

28+
public void ParentMethod()
29+
{
30+
string resultFromLocalFunction = NestedLocalFunction("Hello");
31+
32+
Console.WriteLine(resultFromLocalFunction);
33+
34+
string NestedLocalFunction(string input)
35+
{
36+
return input + " world";
37+
}
38+
}
39+
40+
public void MethodWithLambda()
41+
{
42+
var chars = "abc".Where(c => c == 'a').ToArray();
43+
44+
var lambda = (char c) => c == 'a';
45+
46+
var chars2 = "abc".Where(lambda).ToArray();
47+
}
48+
2749
public class NestedClass
2850
{
2951
public void SampleFunction()

0 commit comments

Comments
 (0)