@@ -43,6 +43,15 @@ const { Parser: AcornParser } =
4343const { simple : acornWalkSimple } =
4444 require ( 'internal/deps/acorn/acorn-walk/dist/walk' ) ;
4545
46+ const kStatementTypes = [
47+ 'ExpressionStatement' , 'ReturnStatement' , 'ThrowStatement' ,
48+ 'IfStatement' , 'WhileStatement' , 'DoWhileStatement' ,
49+ 'ForStatement' , 'ForInStatement' , 'ForOfStatement' ,
50+ 'SwitchStatement' , 'TryStatement' , 'BreakStatement' ,
51+ 'ContinueStatement' , 'VariableDeclaration' , 'LabeledStatement' ,
52+ 'WithStatement' , 'DebuggerStatement' ,
53+ ] ;
54+
4655const kCoverageFileRegex = / ^ c o v e r a g e - ( \d + ) - ( \d { 13 } ) - ( \d + ) \. j s o n $ / ;
4756const kIgnoreRegex = / \/ \* n o d e : c o v e r a g e i g n o r e n e x t (?< count > \d + ) ? \* \/ / ;
4857const kLineEndingRegex = / \r ? \n $ / u;
@@ -88,34 +97,52 @@ class TestCoverage {
8897 }
8998
9099 const statements = [ ] ;
100+
101+ // Build a visitor with one handler per concrete statement type.
102+ // acorn-walk does not fire a generic "Statement" visitor for concrete
103+ // node types, so each type must be registered individually.
104+ const visitor = { __proto__ : null } ;
105+ const handler = ( node ) => {
106+ ArrayPrototypePush ( statements , {
107+ __proto__ : null ,
108+ startOffset : node . start ,
109+ endOffset : node . end ,
110+ count : 0 ,
111+ } ) ;
112+ } ;
113+ for ( let i = 0 ; i < kStatementTypes . length ; ++ i ) {
114+ visitor [ kStatementTypes [ i ] ] = handler ;
115+ }
116+
117+ // Try parsing as a module first, fall back to script for files that
118+ // use CommonJS syntax incompatible with module mode.
119+ let ast ;
91120 try {
92- const ast = AcornParser . parse ( source , {
121+ ast = AcornParser . parse ( source , {
93122 __proto__ : null ,
94123 ecmaVersion : 'latest' ,
95124 sourceType : 'module' ,
96125 allowReturnOutsideFunction : true ,
97126 allowImportExportEverywhere : true ,
98127 allowAwaitOutsideFunction : true ,
99128 } ) ;
100-
101- acornWalkSimple ( ast , {
102- Statement ( node ) {
103- if ( node . type === 'BlockStatement' ) {
104- return ;
105- }
106- ArrayPrototypePush ( statements , {
107- __proto__ : null ,
108- startOffset : node . start ,
109- endOffset : node . end ,
110- count : 0 ,
111- } ) ;
112- } ,
113- } ) ;
114129 } catch {
115- this . #sourceStatements. set ( fileUrl , null ) ;
116- return null ;
130+ try {
131+ ast = AcornParser . parse ( source , {
132+ __proto__ : null ,
133+ ecmaVersion : 'latest' ,
134+ sourceType : 'script' ,
135+ allowReturnOutsideFunction : true ,
136+ allowAwaitOutsideFunction : true ,
137+ } ) ;
138+ } catch {
139+ this . #sourceStatements. set ( fileUrl , null ) ;
140+ return null ;
141+ }
117142 }
118143
144+ acornWalkSimple ( ast , visitor ) ;
145+
119146 this . #sourceStatements. set ( fileUrl , statements ) ;
120147 return statements ;
121148 }
@@ -228,7 +255,16 @@ class TestCoverage {
228255 const functionReports = [ ] ;
229256 const branchReports = [ ] ;
230257
231- const lines = this . getLines ( url ) ;
258+ // Read source once and pass to both getLines and getStatements to
259+ // avoid double disk I/O for the same file.
260+ let source ;
261+ try {
262+ source = readFileSync ( fileURLToPath ( url ) , 'utf8' ) ;
263+ } catch {
264+ continue ;
265+ }
266+
267+ const lines = this . getLines ( url , source ) ;
232268 if ( ! lines ) {
233269 continue ;
234270 }
@@ -298,7 +334,8 @@ class TestCoverage {
298334 }
299335
300336 // Compute statement coverage by mapping V8 ranges to AST statements.
301- const statements = this . getStatements ( url ) ;
337+ // Pass the source already read above to avoid double disk I/O.
338+ const statements = this . getStatements ( url , source ) ;
302339 let totalStatements = 0 ;
303340 let statementsCovered = 0 ;
304341 const statementReports = [ ] ;
@@ -307,8 +344,7 @@ class TestCoverage {
307344 for ( let j = 0 ; j < statements . length ; ++ j ) {
308345 const stmt = statements [ j ] ;
309346 let bestCount = 0 ;
310- let bestSize = Infinity ;
311- let found = false ;
347+ let bestRange = null ;
312348
313349 for ( let fi = 0 ; fi < functions . length ; ++ fi ) {
314350 const { ranges } = functions [ fi ] ;
@@ -317,16 +353,16 @@ class TestCoverage {
317353 if ( range . startOffset <= stmt . startOffset &&
318354 range . endOffset >= stmt . endOffset ) {
319355 const size = range . endOffset - range . startOffset ;
320- if ( ! found || size < bestSize ) {
356+ if ( bestRange === null ||
357+ size < ( bestRange . endOffset - bestRange . startOffset ) ) {
321358 bestCount = range . count ;
322- bestSize = size ;
359+ bestRange = range ;
323360 }
324- found = true ;
325361 }
326362 }
327363 }
328364
329- stmt . count = found ? bestCount : 0 ;
365+ stmt . count = bestRange !== null ? bestCount : 0 ;
330366
331367 const stmtLine = findLineForOffset ( stmt . startOffset , lines ) ;
332368 const isIgnored = stmtLine != null && stmtLine . ignore ;
0 commit comments