Skip to content

Commit 515e8cf

Browse files
authored
Merge pull request #18 from hildjj/sourcemap
Support input sourceMaps
2 parents 2fd424a + 8215fcb commit 515e8cf

File tree

5 files changed

+178
-73
lines changed

5 files changed

+178
-73
lines changed

lib/index.d.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
* original.
88
* @prop {boolean} [noOriginal] Do not run tests on the original code, only
99
* on the generated code.
10+
* @prop {boolean} [inputSourceMap] Read sourceMap information from the input
11+
* file. Use it to filter the generated sourceMap information.
1012
*/
1113
/**
1214
* Test the basic functionality of a Peggy grammar, to make coverage easier.
@@ -113,4 +115,9 @@ export type TestPeggyOptions = {
113115
* on the generated code.
114116
*/
115117
noOriginal?: boolean | undefined;
118+
/**
119+
* Read sourceMap information from the input
120+
* file. Use it to filter the generated sourceMap information.
121+
*/
122+
inputSourceMap?: boolean | undefined;
116123
};

lib/index.js

Lines changed: 47 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,19 @@
1+
import { SourceMapConsumer, SourceNode } from "source-map";
12
import {
23
deepEqual,
34
equal,
45
fail,
56
ok,
67
throws,
78
} from "node:assert/strict";
8-
import { SourceNode } from "source-map";
99
import { fileURLToPath } from "node:url";
1010
import fs from "node:fs/promises";
1111
import path from "node:path";
1212

1313
const 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*peg\$result = peg\$startRuleFunction\(\);/;
1417
let 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*peg\$result = peg\$startRuleFunction\(\);/.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} */ (

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,10 @@
3737
},
3838
"devDependencies": {
3939
"@peggyjs/eslint-config": "6.0.0",
40-
"@types/node": "22.15.3",
40+
"@types/node": "22.15.14",
4141
"c8": "10.1.3",
4242
"eslint": "9.26.0",
43-
"typedoc": "0.28.3",
43+
"typedoc": "0.28.4",
4444
"typescript": "5.8.3"
4545
},
4646
"packageManager": "[email protected]",

0 commit comments

Comments
 (0)