Skip to content

Commit

Permalink
#625 Improved support for local methods (Cobertura, dotCover, OpenCov…
Browse files Browse the repository at this point in the history
…er, VisualStudio)
  • Loading branch information
danielpalme committed Oct 5, 2023
1 parent ae8c4fc commit 949fa35
Show file tree
Hide file tree
Showing 13 changed files with 618 additions and 383 deletions.
1 change: 1 addition & 0 deletions src/Readme.txt
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ CHANGELOG
5.1.26.0

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

Expand Down
17 changes: 15 additions & 2 deletions src/ReportGenerator.Core/Parser/CoberturaParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ internal class CoberturaParser : ParserBase
/// </summary>
private static readonly Regex CompilerGeneratedMethodNameRegex = new Regex(@"(?<ClassName>.+)(/|\.)<(?<CompilerGeneratedName>.+)>.+__.+MoveNext\(\)$", RegexOptions.Compiled);

/// <summary>
/// Regex to analyze if a method name is a nested method (a method nested within a method).
/// </summary>
private static readonly Regex LocalFunctionMethodNameRegex = new Regex(@"^.*(?<ParentMethodName><.+>).*__(?<NestedMethodName>[^\|]+)\|.*$", RegexOptions.Compiled);

/// <summary>
/// Regex to analyze the branch coverage of a line element.
/// </summary>
Expand Down Expand Up @@ -480,8 +485,16 @@ private static Dictionary<int, ICollection<Branch>> GetBranches(IEnumerable<XEle
/// <returns>The method name.</returns>
private static string ExtractMethodName(string methodName, string className)
{
// Quick check before expensive regex is called
if (methodName.EndsWith("MoveNext()"))
if (methodName.Contains("|") || className.Contains("|"))
{
Match match = LocalFunctionMethodNameRegex.Match(className + methodName);

if (match.Success)
{
methodName = match.Groups["NestedMethodName"].Value + "()";
}
}
else if (methodName.EndsWith("MoveNext()"))
{
Match match = CompilerGeneratedMethodNameRegex.Match(className + methodName);

Expand Down
17 changes: 15 additions & 2 deletions src/ReportGenerator.Core/Parser/DotCoverParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ internal class DotCoverParser : ParserBase
/// </summary>
private static readonly Regex CompilerGeneratedMethodNameRegex = new Regex(@"<(?<CompilerGeneratedName>.+)>.+__.+MoveNext\(\):.+$", RegexOptions.Compiled);

/// <summary>
/// Regex to analyze if a method name is a nested method (a method nested within a method).
/// </summary>
private static readonly Regex LocalFunctionMethodNameRegex = new Regex(@"^.*(?<ParentMethodName><.+>).*__(?<NestedMethodName>[^\|]+)\|.+\((?<Arguments>.*)\):.+$", RegexOptions.Compiled);

/// <summary>
/// Initializes a new instance of the <see cref="DotCoverParser" /> class.
/// </summary>
Expand Down Expand Up @@ -278,8 +283,16 @@ private static void SetCodeElements(CodeFile codeFile, string fileId, IEnumerabl
/// <returns>The method name.</returns>
private static string ExtractMethodName(string typeName, string methodName)
{
// Quick check before expensive regex is called
if (methodName.Contains("MoveNext()"))
if (typeName.Contains("|") || methodName.Contains("|"))
{
Match match = LocalFunctionMethodNameRegex.Match(typeName + methodName);

if (match.Success)
{
return match.Groups["NestedMethodName"].Value + "(" + match.Groups["Arguments"].Value + ")";
}
}
else if (methodName.Contains("MoveNext()"))
{
Match match = CompilerGeneratedMethodNameRegex.Match(typeName + methodName);

Expand Down
19 changes: 16 additions & 3 deletions src/ReportGenerator.Core/Parser/OpenCoverParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,18 @@ internal class OpenCoverParser : ParserBase
/// <summary>
/// Regex to analyze if a method name belongs to a lamda expression.
/// </summary>
private static readonly Regex LambdaMethodNameRegex = new Regex("::<.+>.+__", RegexOptions.Compiled);
private static readonly Regex LambdaMethodNameRegex = new Regex("::<.+>.+__[^\\|]+$", RegexOptions.Compiled);

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

/// <summary>
/// Regex to analyze if a method name is a nested method (a method nested within a method).
/// </summary>
private static readonly Regex LocalFunctionMethodNameRegex = new Regex(@"^.*(?<ParentMethodName><.+>).*__(?<NestedMethodName>[^\|]+)\|.+\((?<Arguments>.*)\).*$", RegexOptions.Compiled);

/// <summary>
/// Regex to extract short method name.
/// </summary>
Expand Down Expand Up @@ -586,8 +591,16 @@ private static string ExtractMethodName(string methodName)
{
if (!MethodNameMap.TryGetValue(methodName, out var fullName))
{
// Quick check before expensive regex is called
if (methodName.EndsWith("::MoveNext()"))
if (methodName.Contains("|"))
{
Match match = LocalFunctionMethodNameRegex.Match(methodName);

if (match.Success)
{
methodName = match.Groups["NestedMethodName"].Value + "(" + match.Groups["Arguments"].Value + ")";
}
}
else if (methodName.EndsWith("::MoveNext()"))
{
Match match = CompilerGeneratedMethodNameRegex.Match(methodName);

Expand Down
19 changes: 16 additions & 3 deletions src/ReportGenerator.Core/Parser/VisualStudioParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,18 @@ internal class VisualStudioParser : ParserBase
/// <summary>
/// Regex to analyze if a method name belongs to a lamda expression.
/// </summary>
private static readonly Regex LambdaMethodNameRegex = new Regex("<.+>.+__", RegexOptions.Compiled);
private static readonly Regex LambdaMethodNameRegex = new Regex("<.+>.+__[^\\|]+$", RegexOptions.Compiled);

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

/// <summary>
/// Regex to analyze if a method name is a nested method (a method nested within a method).
/// </summary>
private static readonly Regex LocalFunctionMethodNameRegex = new Regex(@"^.*(?<ParentMethodName><.+>).*__(?<NestedMethodName>[^\|]+)\|.+\((?<Arguments>.*)\).*$", RegexOptions.Compiled);

/// <summary>
/// Regex to extract short method name.
/// </summary>
Expand Down Expand Up @@ -329,8 +334,16 @@ private static void SetCodeElements(CodeFile codeFile, IEnumerable<XElement> met
/// <returns>The method name.</returns>
private static string ExtractMethodName(string methodName, string methodKeyName)
{
// Quick check before expensive regex is called
if (methodKeyName.Contains("MoveNext()"))
if (methodKeyName.Contains("|"))
{
Match match = LocalFunctionMethodNameRegex.Match(methodKeyName);

if (match.Success)
{
methodName = match.Groups["NestedMethodName"].Value + "(" + match.Groups["Arguments"].Value + ")";
}
}
else if (methodKeyName.Contains("MoveNext()"))
{
Match match = CompilerGeneratedMethodNameRegex.Match(methodKeyName);

Expand Down
1 change: 1 addition & 0 deletions src/ReportGenerator.sln
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Reports", "Reports", "{8775
Testprojects\FSharp\Reports\OpenCover.xml = Testprojects\FSharp\Reports\OpenCover.xml
Testprojects\FSharp\Reports\VisualStudio2010.coveragexml = Testprojects\FSharp\Reports\VisualStudio2010.coveragexml
Testprojects\FSharp\Reports\VisualStudio2013.coveragexml = Testprojects\FSharp\Reports\VisualStudio2013.coveragexml
Testprojects\CSharp\Reports\VisualStudio2022.coveragexml = Testprojects\CSharp\Reports\VisualStudio2022.coveragexml
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Java", "Java", "{89813866-CE17-47E5-BCA3-4740F8624BA5}"
Expand Down
2 changes: 2 additions & 0 deletions src/Testprojects/CSharp/Project_DotNetCore/Test/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ public class Program
public static void Main(string[] args)
{
new TestClass().SampleFunction();
new TestClass().ParentMethod();
new TestClass().MethodWithLambda();

new TestClass2("Test").ExecutedMethod();
new TestClass2("Test").SampleFunction("Munich");
Expand Down
22 changes: 22 additions & 0 deletions src/Testprojects/CSharp/Project_DotNetCore/Test/TestClass.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Linq;

namespace Test
{
Expand All @@ -24,6 +25,27 @@ public void SampleFunction()
}
}

public void ParentMethod()
{
string resultFromLocalFunction = NestedLocalFunction("Hello");

Console.WriteLine(resultFromLocalFunction);

string NestedLocalFunction(string input)
{
return input + " world";
}
}

public void MethodWithLambda()
{
var chars = "abc".Where(c => c == 'a').ToArray();

var lambda = (char c) => c == 'a';

var chars2 = "abc".Where(lambda).ToArray();
}

public class NestedClass
{
public void SampleFunction()
Expand Down
Loading

0 comments on commit 949fa35

Please sign in to comment.