1+ import { SourceMapConsumer , SourceNode } from "source-map" ;
12import {
23 deepEqual ,
34 equal ,
45 fail ,
56 ok ,
67 throws ,
78} from "node:assert/strict" ;
8- import { SourceNode } from "source-map" ;
99import { fileURLToPath } from "node:url" ;
1010import fs from "node:fs/promises" ;
1111import path from "node:path" ;
1212
1313const INVALID = "\uffff" ;
14+ const START = "//#" ; // c8: THIS file is not mapped.
15+ const sourceMapRE = new RegExp ( `^${ START } \\s*sourceMappingURL\\s*=\\s*([^\r\n]+)` , "m" ) ;
16+ const startRuleRE = / ^ \s * p e g \$ r e s u l t = p e g \$ s t a r t R u l e F u n c t i o n \( \) ; / ;
1417let counter = 0 ;
1518
1619/**
@@ -258,6 +261,8 @@ function checkParserStarts(grammar, starts, modified, counts) {
258261 * original.
259262 * @prop {boolean } [noOriginal] Do not run tests on the original code, only
260263 * on the generated code.
264+ * @prop {boolean } [inputSourceMap] Read sourceMap information from the input
265+ * file. Use it to filter the generated sourceMap information.
261266 */
262267
263268/**
@@ -333,13 +338,47 @@ export async function testPeggy(grammarUrl, starts, opts) {
333338 ok ( grammarJs ) ;
334339 equal ( typeof grammarJs , "string" ) ;
335340
341+ /** @type {SourceMapConsumer|null } */
342+ let ism = null ;
343+ if ( opts ?. inputSourceMap ) {
344+ const m = grammarJs . match ( sourceMapRE ) ;
345+ if ( m ) {
346+ const map = m [ 1 ] . startsWith ( "data:" )
347+ ? await ( await fetch ( m [ 1 ] ) ) . json ( )
348+ : JSON . parse (
349+ await fs . readFile (
350+ path . resolve ( process . cwd ( ) , path . dirname ( grammarPath ) , m [ 1 ] ) ,
351+ "utf8"
352+ )
353+ ) ;
354+ ism = await new SourceMapConsumer ( map ) ;
355+ }
356+ }
357+
336358 // Approach: generate a new file next to the existing grammar file, with
337359 // test code injected just before the parser runs. Source map information
338360 // embedded in the new file will make coverage show up on the original file.
339361 const src = new SourceNode ( ) ;
340362 let lineNum = 1 ;
341363 for ( const line of grammarJs . split ( / (?< = \n ) / ) ) {
342- if ( / ^ \s * p e g \$ r e s u l t = p e g \$ s t a r t R u l e F u n c t i o n \( \) ; / . test ( line ) ) {
364+ let sn = null ;
365+ if ( ism ) {
366+ const pos = ism . originalPositionFor ( {
367+ line : lineNum ,
368+ column : 0 ,
369+ } ) ;
370+ if ( pos ) {
371+ sn = new SourceNode ( pos . line , pos . column , pos . source , line ) ;
372+ }
373+ }
374+ if ( ! sn ) {
375+ sn = new SourceNode ( lineNum , 0 , grammarPath , line ) ;
376+ }
377+ lineNum ++ ;
378+
379+ if ( sourceMapRE . test ( line ) ) {
380+ // No-op, never output sourcemapurl line.
381+ } else if ( startRuleRE . test ( line ) ) {
343382 src . add ( `\
344383 //#region Inserted by @peggyjs/coverage
345384 let replacements = Object.create(null);
@@ -440,7 +479,7 @@ export async function testPeggy(grammarUrl, starts, opts) {
440479 //#endregion
441480
442481` ) ;
443- src . add ( new SourceNode ( lineNum ++ , 0 , grammarPath , line ) ) ;
482+ src . add ( sn ) ;
444483 src . add ( `
445484 //#region Inserted by @peggyjs/coverage
446485 for (const [name, orig] of Object.entries(replacements)) {
@@ -450,7 +489,7 @@ export async function testPeggy(grammarUrl, starts, opts) {
450489 //#endregion
451490` ) ;
452491 } else {
453- src . add ( new SourceNode ( lineNum ++ , 0 , grammarPath , line ) ) ;
492+ src . add ( sn ) ;
454493 }
455494 }
456495
@@ -460,17 +499,15 @@ export async function testPeggy(grammarUrl, starts, opts) {
460499 let sm = starts . every ( s => ! s . options ?. peg$debugger ) ;
461500 if ( opts && Object . prototype . hasOwnProperty . call ( opts , "noMap" ) ) {
462501 sm = ! opts . noMap ;
463- } else {
464- if ( ! sm ) {
465- console . error ( "WARNING: sourcemap disabled due to peg$debugger" ) ;
466- }
502+ } else if ( ! sm ) {
503+ console . error ( "WARNING: sourcemap disabled due to peg$debugger" ) ;
467504 }
468- const start = "//#" ; // c8: THIS file is not mapped.
469505 if ( sm ) {
470506 code += `
471- ${ start } sourceMappingURL=data:application/json;charset=utf-8;base64,${ map }
507+ ${ START } sourceMappingURL=data:application/json;charset=utf-8;base64,${ map }
472508` ;
473509 }
510+ ism ?. destroy ( ) ;
474511 await fs . writeFile ( modifiedPath , code ) ;
475512 try {
476513 const agrammar = /** @type {Parser } */ (
0 commit comments