@@ -28,6 +28,12 @@ internal class CoberturaParser : ParserBase
2828 /// </summary>
2929 private static readonly Regex GenericClassRegex = new Regex ( "<.*>$" , RegexOptions . Compiled ) ;
3030
31+ /// <summary>
32+ /// Regex to analyze if a class name represents an async (generic) class.
33+ /// Format gets generated by 'dotnet test --collect "Code Coverage;Format=Cobertura"'.
34+ /// </summary>
35+ private static readonly Regex AsyncClassRegex = new Regex ( "^(?<ClassName>.+)\\ .<.*>.*__(?:.+(?<GenericTypes><.+>))?" , RegexOptions . Compiled ) ;
36+
3137 /// <summary>
3238 /// Regex to analyze if a method name belongs to a lamda expression.
3339 /// </summary>
@@ -36,7 +42,7 @@ internal class CoberturaParser : ParserBase
3642 /// <summary>
3743 /// Regex to analyze if a method name is generated by compiler.
3844 /// </summary>
39- private static readonly Regex CompilerGeneratedMethodNameRegex = new Regex ( @"(?<ClassName>.+)/ <(?<CompilerGeneratedName>.+)>.+__.+MoveNext\(\)$" , RegexOptions . Compiled ) ;
45+ private static readonly Regex CompilerGeneratedMethodNameRegex = new Regex ( @"(?<ClassName>.+)(/|\.) <(?<CompilerGeneratedName>.+)>.+__.+MoveNext\(\)$" , RegexOptions . Compiled ) ;
4046
4147 /// <summary>
4248 /// Regex to analyze the branch coverage of a line element.
@@ -127,18 +133,37 @@ private Assembly ProcessAssembly(XElement[] modules, string assemblyName)
127133 {
128134 string fullname = c . Attribute ( "name" ) . Value ;
129135 int nestedClassSeparatorIndex = fullname . IndexOf ( '/' ) ;
130- return nestedClassSeparatorIndex > - 1 ? fullname . Substring ( 0 , nestedClassSeparatorIndex ) : fullname ;
136+
137+ if ( nestedClassSeparatorIndex > - 1 )
138+ {
139+ string className = fullname . Substring ( 0 , nestedClassSeparatorIndex ) ;
140+ return Tuple . Create ( className , className ) ;
141+ }
142+
143+ if ( fullname . Contains ( "<" ) )
144+ {
145+ var match = AsyncClassRegex . Match ( fullname ) ;
146+
147+ if ( match . Success )
148+ {
149+ return Tuple . Create (
150+ match . Groups [ "ClassName" ] . Value ,
151+ match . Groups [ "ClassName" ] . Value + match . Groups [ "GenericTypes" ] . Value ) ;
152+ }
153+ }
154+
155+ return Tuple . Create ( fullname , fullname ) ;
131156 } )
132- . Where ( name => ! name . Contains ( "$" )
133- && ( ! name . Contains ( "<" ) || GenericClassRegex . IsMatch ( name ) ) )
157+ . Where ( c => ! c . Item1 . Contains ( "$" )
158+ && ( ! c . Item1 . Contains ( "<" ) || GenericClassRegex . IsMatch ( c . Item1 ) ) )
134159 . Distinct ( )
135- . Where ( c => this . ClassFilter . IsElementIncludedInReport ( c ) )
136- . OrderBy ( name => name )
160+ . Where ( c => this . ClassFilter . IsElementIncludedInReport ( c . Item1 ) )
161+ . OrderBy ( c => c . Item1 )
137162 . ToArray ( ) ;
138163
139164 var assembly = new Assembly ( assemblyName ) ;
140165
141- Parallel . ForEach ( classNames , className => this . ProcessClass ( modules , assembly , className ) ) ;
166+ Parallel . ForEach ( classNames , c => this . ProcessClass ( modules , assembly , c . Item1 , c . Item2 ) ) ;
142167
143168 return assembly ;
144169 }
@@ -149,15 +174,17 @@ private Assembly ProcessAssembly(XElement[] modules, string assemblyName)
149174 /// <param name="modules">The modules.</param>
150175 /// <param name="assembly">The assembly.</param>
151176 /// <param name="className">Name of the class.</param>
152- private void ProcessClass ( XElement [ ] modules , Assembly assembly , string className )
177+ /// <param name="classDisplayName">Diesplay name of the class.</param>
178+ private void ProcessClass ( XElement [ ] modules , Assembly assembly , string className , string classDisplayName )
153179 {
154180 var files = modules
155181 . Where ( m => m . Attribute ( "name" ) . Value . Equals ( assembly . Name ) )
156182 . Elements ( "classes" )
157183 . Elements ( "class" )
158184 . Where ( c => c . Attribute ( "name" ) . Value . Equals ( className )
159185 || c . Attribute ( "name" ) . Value . StartsWith ( className + "$" , StringComparison . Ordinal )
160- || c . Attribute ( "name" ) . Value . StartsWith ( className + "/" , StringComparison . Ordinal ) )
186+ || c . Attribute ( "name" ) . Value . StartsWith ( className + "/" , StringComparison . Ordinal )
187+ || c . Attribute ( "name" ) . Value . StartsWith ( className + "." , StringComparison . Ordinal ) )
161188 . Select ( c => c . Attribute ( "filename" ) . Value )
162189 . Distinct ( )
163190 . ToArray ( ) ;
@@ -169,11 +196,11 @@ private void ProcessClass(XElement[] modules, Assembly assembly, string classNam
169196 // If all files are removed by filters, then the whole class is omitted
170197 if ( ( files . Length == 0 && ! this . FileFilter . HasCustomFilters ) || filteredFiles . Length > 0 )
171198 {
172- var @class = new Class ( className , assembly ) ;
199+ var @class = new Class ( classDisplayName , assembly ) ;
173200
174201 foreach ( var file in filteredFiles )
175202 {
176- @class . AddFile ( ProcessFile ( modules , @class , file ) ) ;
203+ @class . AddFile ( ProcessFile ( modules , @class , className , file ) ) ;
177204 }
178205
179206 assembly . AddClass ( @class ) ;
@@ -185,18 +212,19 @@ private void ProcessClass(XElement[] modules, Assembly assembly, string classNam
185212 /// </summary>
186213 /// <param name="modules">The modules.</param>
187214 /// <param name="class">The class.</param>
215+ /// <param name="className">Name of the class.</param>
188216 /// <param name="filePath">The file path.</param>
189217 /// <returns>The <see cref="CodeFile"/>.</returns>
190- private static CodeFile ProcessFile ( XElement [ ] modules , Class @class , string filePath )
218+ private static CodeFile ProcessFile ( XElement [ ] modules , Class @class , string className , string filePath )
191219 {
192220 var classes = modules
193221 . Where ( m => m . Attribute ( "name" ) . Value . Equals ( @class . Assembly . Name ) )
194222 . Elements ( "classes" )
195223 . Elements ( "class" )
196- . Where ( c => c . Attribute ( "name" ) . Value . Equals ( @class . Name )
197- || c . Attribute ( "name" ) . Value . StartsWith ( @class . Name + "$" , StringComparison . Ordinal )
198- || c . Attribute ( "name" ) . Value . StartsWith ( @class . Name + "/" , StringComparison . Ordinal )
199- || c . Attribute ( "name" ) . Value . StartsWith ( @class . Name + "." , StringComparison . Ordinal ) )
224+ . Where ( c => c . Attribute ( "name" ) . Value . Equals ( className )
225+ || c . Attribute ( "name" ) . Value . StartsWith ( className + "$" , StringComparison . Ordinal )
226+ || c . Attribute ( "name" ) . Value . StartsWith ( className + "/" , StringComparison . Ordinal )
227+ || c . Attribute ( "name" ) . Value . StartsWith ( className + "." , StringComparison . Ordinal ) )
200228 . Where ( c => c . Attribute ( "filename" ) . Value . Equals ( filePath ) )
201229 . ToArray ( ) ;
202230
@@ -365,7 +393,7 @@ private static void SetCodeElements(CodeFile codeFile, IEnumerable<XElement> met
365393
366394 codeFile . AddCodeElement ( new CodeElement (
367395 methodName ,
368- CodeElementType . Method ,
396+ methodName . StartsWith ( "get_" ) || methodName . StartsWith ( "set_" ) ? CodeElementType . Property : CodeElementType . Method ,
369397 firstLine ,
370398 lastLine ,
371399 codeFile . CoverageQuota ( firstLine , lastLine ) ) ) ;
0 commit comments