@@ -5,7 +5,14 @@ import { cpus } from 'os';
55import * as path from 'path' ;
66import * as vscode from 'vscode' ;
77import { CancellationToken } from 'vscode-languageclient' ;
8- import { getMatchingPrefixes , parallelize , staggerProgress , toPosix } from './helpers' ;
8+ import { logger } from './extension' ;
9+ import {
10+ getMatchingPrefixes ,
11+ parallelize ,
12+ showErrorMessageWithOpenLogButton ,
13+ staggerProgress ,
14+ toPosix ,
15+ } from './helpers' ;
916
1017/**
1118 * Parsing GNATcoverage XML reports.
@@ -25,7 +32,8 @@ import { getMatchingPrefixes, parallelize, staggerProgress, toPosix } from './he
2532 * where we can give the XML paths that should always be parsed as lists, even
2633 * when one tag is encountered. However if no tags are encountered, the parser
2734 * simply doesn't create the corresponding property in the result object, and
28- * accessing the property yields `undefined`.
35+ * accessing the property yields `undefined`. For this reason all array
36+ * properties are typed as optional.
2937 *
3038 * Similarly, the parser is configured with a function
3139 * `attributeValueProcessor` allowing to convert attributes to more specific
@@ -55,7 +63,7 @@ type coverage_info_type = {
5563 traces : traces_type ;
5664} ;
5765type traces_type = {
58- trace : trace_type [ ] ;
66+ trace ? : trace_type [ ] ;
5967} ;
6068type trace_type = {
6169 '@_filename' : string ;
@@ -66,13 +74,13 @@ type trace_type = {
6674} ;
6775type trace_kind_type = 'binary' | 'source' ;
6876type coverage_summary_type = {
69- metric : metric_type [ ] ;
70- obligation_stats : obligation_stats_type [ ] ;
71- file : file_type [ ] ;
77+ metric ? : metric_type [ ] ;
78+ obligation_stats ? : obligation_stats_type [ ] ;
79+ file ? : file_type [ ] ;
7280} ;
7381type file_type = {
74- metric : metric_type [ ] ;
75- obligation_stats : obligation_stats_type [ ] ;
82+ metric ? : metric_type [ ] ;
83+ obligation_stats ? : obligation_stats_type [ ] ;
7684 '@_name' ?: string ;
7785} ;
7886type coverage_level_type =
@@ -85,7 +93,7 @@ type coverage_level_type =
8593
8694type sources_type = {
8795 source ?: source_type [ ] ;
88- 'xi:include' : xi_include_type [ ] ;
96+ 'xi:include' ? : xi_include_type [ ] ;
8997} ;
9098
9199type xi_include_type = {
@@ -96,13 +104,13 @@ type xi_include_type = {
96104export type source_type = {
97105 '@_file' : string ;
98106 '@_coverage_level' : coverage_level_type ;
99- scope_metric : scope_metric_type [ ] ;
100- src_mapping : src_mapping_type [ ] ;
107+ scope_metric ? : scope_metric_type [ ] ;
108+ src_mapping ? : src_mapping_type [ ] ;
101109} ;
102110type scope_metric_type = {
103- metric : metric_type [ ] ;
104- obligation_stats : obligation_stats_type [ ] ;
105- scope_metric : scope_metric_type [ ] ;
111+ metric ? : metric_type [ ] ;
112+ obligation_stats ? : obligation_stats_type [ ] ;
113+ scope_metric ? : scope_metric_type [ ] ;
106114 '@_scope_name' : string ;
107115 '@_scope_line' : number ;
108116} ;
@@ -124,18 +132,18 @@ type metric_kind_type =
124132 | 'exempted_undetermined_coverage'
125133 | 'exempted' ;
126134type obligation_stats_type = {
127- metric : metric_type [ ] ;
135+ metric ? : metric_type [ ] ;
128136 '@_kind' : string ;
129137} ;
130138export type src_mapping_type = {
131139 src : src_type ;
132- statement : statement_type [ ] | undefined ;
133- decision : decision_type [ ] | undefined ;
134- message : message_type [ ] | undefined ;
140+ statement ? : statement_type [ ] ;
141+ decision ? : decision_type [ ] ;
142+ message ? : message_type [ ] ;
135143 '@_coverage' : coverage_type ;
136144} ;
137145type src_type = {
138- line : line_type [ ] ;
146+ line ? : line_type [ ] ;
139147} ;
140148export type line_type = {
141149 '@_num' : number ;
@@ -192,7 +200,7 @@ export type coverage_type = (typeof coverage_type_values)[number];
192200
193201type decision_type = {
194202 src ?: src_type ;
195- condition : condition_type [ ] ;
203+ condition ? : condition_type [ ] ;
196204 '@_coverage' : coverage_type ;
197205 '@_id' : number ;
198206 '@_text' : string ;
@@ -357,7 +365,7 @@ export async function addCoverageData(run: vscode.TestRun, covDir: string) {
357365 title : 'Loading GNATcoverage report' ,
358366 } ,
359367 async ( progress , token ) => {
360- const array = data . coverage_report . coverage_summary ! . file ;
368+ const array = data . coverage_report . coverage_summary ? .file ?? [ ] ;
361369 let done : number = 0 ;
362370 let lastProgress = 0 ;
363371 const totalFiles = array . length ;
@@ -537,9 +545,22 @@ export async function addCoverageData(run: vscode.TestRun, covDir: string) {
537545 `Could not find the file in the workspace: ${ file [ '@_name' ] } ` ,
538546 ) ;
539547
540- const fileReportBasename = data . coverage_report . sources ! [ 'xi:include' ] . find (
548+ const fileReportBasename = data . coverage_report . sources ?. [
549+ 'xi:include'
550+ ] ?. find (
541551 ( inc ) => inc [ '@_href' ] == `${ path . posix . basename ( srcUri . path ) } .xml` ,
542- ) ! [ '@_href' ] ;
552+ ) ?. [ '@_href' ] ;
553+
554+ if ( ! fileReportBasename ) {
555+ const msg = `Malformed GNATcoverage report ${ indexPath } ` ;
556+ void showErrorMessageWithOpenLogButton ( msg ) ;
557+ logger . warn (
558+ `${ msg } : cannot find <xi:include> element for source file ` +
559+ `${ path . posix . basename ( srcUri . path ) } ` ,
560+ ) ;
561+ return undefined ;
562+ }
563+
543564 const fileReportPath = path . join ( covDir , fileReportBasename ) ;
544565
545566 const stmtStats = getStats ( file , 'Stmt' ) ?? { covered : 0 , total : 0 } ;
@@ -661,13 +682,14 @@ function getStats(
661682 file : file_type ,
662683 level : 'Stmt' | 'Decision' | 'MCDC' ,
663684) : vscode . TestCoverageCount | undefined {
664- const stats = file . obligation_stats . find ( ( s ) => s [ '@_kind' ] == level ) ;
685+ const stats = file . obligation_stats ? .find ( ( s ) => s [ '@_kind' ] == level ) ;
665686 if ( stats ) {
666687 const total =
667688 stats ?. metric ?. find ( ( m ) => m [ '@_kind' ] == 'total_obligations_of_relevance' ) ?. [
668689 '@_count'
669690 ] ?? 0 ;
670- const covered = stats ?. metric ?. find ( ( m ) => m [ '@_kind' ] == 'fully_covered' ) ! [ '@_count' ] ?? 0 ;
691+ const covered =
692+ stats ?. metric ?. find ( ( m ) => m [ '@_kind' ] == 'fully_covered' ) ?. [ '@_count' ] ?? 0 ;
671693 return { covered, total } ;
672694 } else {
673695 return undefined ;
@@ -678,16 +700,18 @@ export function convertSourceReport(
678700 data : source_type ,
679701 token ?: CancellationToken ,
680702) : vscode . StatementCoverage [ ] {
681- return data . src_mapping
682- . flatMap ( ( src_mapping ) => {
683- if ( token ?. isCancellationRequested ) {
684- throw new vscode . CancellationError ( ) ;
685- }
703+ return (
704+ data . src_mapping
705+ ?. flatMap ( ( src_mapping ) => {
706+ if ( token ?. isCancellationRequested ) {
707+ throw new vscode . CancellationError ( ) ;
708+ }
686709
687- return convertSrcMapping ( src_mapping ) ;
688- } )
689- . flat ( )
690- . filter ( ( v ) => ! ! v ) ;
710+ return convertSrcMapping ( src_mapping ) ;
711+ } )
712+ . flat ( )
713+ . filter ( ( v ) => ! ! v ) ?? [ ]
714+ ) ;
691715}
692716
693717export function convertSrcMapping ( src_mapping : src_mapping_type ) : vscode . StatementCoverage [ ] {
@@ -785,9 +809,9 @@ export function convertSrcMapping(src_mapping: src_mapping_type): vscode.Stateme
785809 /**
786810 * Add <condition> reports as more branches
787811 */
788- const conditions = decision . condition
789- ? .filter ( ( c ) => c [ '@_coverage' ] != '.' )
790- ? .flatMap ( ( condition ) => {
812+ const conditions : vscode . BranchCoverage [ ] = ( decision . condition ?? [ ] )
813+ . filter ( ( c ) => c [ '@_coverage' ] != '.' )
814+ . flatMap ( ( condition ) => {
791815 assert ( condition . src ) ;
792816 const mergedText = mergeText ( condition . src ) ;
793817 const messages = getMessages ( condition , src_mapping ) ;
@@ -799,7 +823,7 @@ export function convertSrcMapping(src_mapping: src_mapping_type): vscode.Stateme
799823 ( m ) =>
800824 new vscode . BranchCoverage (
801825 m [ '@_kind' ] == 'notice' ,
802- toRange ( condition . src ! ) ,
826+ condition . src ? toRange ( condition . src ) : undefined ,
803827
804828 `condition ${ m [ '@_kind' ] } : '${ mergedText } ' ${ m [ '@_message' ] } ` ,
805829 ) ,
@@ -845,7 +869,7 @@ function getMessages(item: { '@_id': number }, src_mapping: src_mapping_type) {
845869 * @returns the joined lines spanned by the src object
846870 */
847871function mergeText ( src : src_type | undefined ) : string {
848- return src ?. line . map ( ( l ) => l [ '@_src' ] . trim ( ) ) . join ( ' ' ) ?? '' ;
872+ return src ?. line ? .map ( ( l ) => l [ '@_src' ] . trim ( ) ) . join ( ' ' ) ?? '' ;
849873}
850874
851875/**
@@ -858,9 +882,9 @@ function toRange(src: src_type): vscode.Range {
858882 * The <src> object may contain multiple <line>s, so we need to compute the
859883 * region start and end based on the first and last lines.
860884 */
861- const firstLine = src . line . at ( 0 ) ;
885+ const firstLine = src . line ? .at ( 0 ) ;
862886 assert ( firstLine ) ;
863- const lastLine = src . line . at ( - 1 ) ;
887+ const lastLine = src . line ? .at ( - 1 ) ;
864888 assert ( lastLine ) ;
865889 const range = new vscode . Range (
866890 firstLine [ '@_num' ] - 1 ,
0 commit comments