build: ask262Debug

This commit is contained in:
2026-04-08 13:55:13 +05:30
parent 0b58f8483a
commit ebe36c3737
6 changed files with 395 additions and 38 deletions
+132 -37
View File
@@ -26,10 +26,11 @@ function getEnclosingConditionalExpression(path: NodePath) {
return null;
}
type NeededNames = 'Completion' | 'AbruptCompletion' | 'Assert' | 'Call' | 'IteratorClose' | 'AsyncIteratorClose' | 'Value' | 'skipDebugger';
type NeededNames = 'Completion' | 'AbruptCompletion' | 'Assert' | 'Call' | 'IteratorClose' | 'AsyncIteratorClose' | 'Value' | 'skipDebugger' | 'ask262Debug';
interface State extends PluginPass {
needed: Partial<Record<NeededNames, boolean>>;
fileRelativePath?: string;
}
interface Macro<R extends PublicReplacements = Record<string, Node | null>> {
@@ -98,38 +99,72 @@ export default ({ types: t, template }: typeof import('@babel/core')): PluginObj
`);
}
function addSectionFromComments(path: NodePath<t.FunctionDeclaration> | NodePath<t.VariableDeclaration> | NodePath<t.ExportNamedDeclaration>) {
if (path.node.leadingComments) {
for (const c of path.node.leadingComments) {
let name: string;
switch (path.type) {
case 'FunctionDeclaration':
name = path.node.id!.name;
break;
case 'ExportNamedDeclaration':
name = (path.node.declaration as t.FunctionDeclaration).id!.name;
break;
case 'VariableDeclaration':
name = (path.node.declarations[0].id as t.Identifier).name;
break;
default:
throw (path as NodePath).buildCodeFrameError('Internal error: Unsupported path to addSectionFromComments');
}
const lines = c.value.split('\n');
for (const line of lines) {
if (/#sec/.test(line)) {
const section = line.split(' ').find((l) => l.includes('#sec'))!;
const url = section.includes('https') ? section : `https://tc39.es/ecma262/${section}`;
const result = path.insertAfter(withSource(c, template.ast(`${name}.section = '${url}';`)));
if (path.node.trailingComments) {
result[result.length - 1].node.trailingComments = path.node.trailingComments;
path.node.trailingComments = null;
function createImportAsk262Debug() {
return template.ast(`
import { ask262Debug } from "#self";
`);
}
function addSectionFromComments(
path: NodePath<t.FunctionDeclaration> | NodePath<t.VariableDeclaration> | NodePath<t.ExportNamedDeclaration> | NodePath<t.ClassMethod> | NodePath<t.ObjectMethod>,
state: State,
getName: () => string,
getBody: () => t.BlockStatement | null,
insertSection: boolean,
) {
if (!path.node.leadingComments) return;
const sectionIds: string[] = [];
let url = '';
let firstComment: t.Comment | null = null;
for (const c of path.node.leadingComments) {
for (const line of c.value.split('\n')) {
const matches = line.match(/#(sec-[a-zA-Z0-9._%-]+)/g);
if (matches) {
sectionIds.push(...matches.map((m) => m.substring(1)));
if (!firstComment) {
firstComment = c;
}
if (!url) {
const section = line.split(' ').find((l) => l.includes('#sec'));
// Only capture external URLs (skip local/fragment references)
if (section?.startsWith('https://')) {
url = section;
}
return;
}
}
}
}
if (sectionIds.length === 0) return;
const name = getName();
// 1. Keep existing .section (backward compat) - uses first URL
// Only applies when caller explicitly allows it
if (name && url && firstComment && insertSection) {
const result = path.insertAfter(withSource(firstComment, template.ast(`${name}.section = '${url}';`)));
if (path.node.trailingComments) {
result[result.length - 1].node.trailingComments = path.node.trailingComments;
path.node.trailingComments = null;
}
}
// 2. Get function body via callback (node-type check done by caller)
const body = getBody();
// 3. Inject mark() at start of function body (only for block bodies)
if (body?.type === 'BlockStatement') {
const line = path.node.loc?.start.line ?? 0;
const sectionIdsStr = JSON.stringify(sectionIds);
const filePathStr = JSON.stringify(state.fileRelativePath);
const markCall = template.statement(
`ask262Debug.mark(${sectionIdsStr}, ${filePathStr}, ${line});`,
)();
body.body.unshift(markCall);
state.needed.ask262Debug = true;
}
}
@@ -254,8 +289,11 @@ export default ({ types: t, template }: typeof import('@babel/core')): PluginObj
return {
visitor: {
Program: {
enter(_path, state) {
enter(path, state) {
state.needed = {};
// Capture relative file path from state.filename (relative to src/)
const absolutePath = state.filename || '';
state.fileRelativePath = absolutePath.replace(/.*\/src\//, '') || 'unknown.mts';
},
exit(path, state) {
if (state.needed.skipDebugger) {
@@ -282,6 +320,9 @@ export default ({ types: t, template }: typeof import('@babel/core')): PluginObj
if (state.needed.Value) {
path.unshiftContainer('body', createImportValue());
}
if (state.needed.ask262Debug) {
path.unshiftContainer('body', createImportAsk262Debug());
}
},
},
CallExpression(path, state) {
@@ -438,17 +479,71 @@ export default ({ types: t, template }: typeof import('@babel/core')): PluginObj
}
}
},
FunctionDeclaration(path) {
addSectionFromComments(path);
FunctionDeclaration(path, state) {
addSectionFromComments(
path,
state,
() => path.node.id!.name,
() => path.node.body,
true,
);
},
VariableDeclaration(path) {
if (path.get('declarations.0.init').isArrowFunctionExpression() || path.get('declarations.0.init').isFunctionExpression()) {
addSectionFromComments(path);
VariableDeclaration(path, state) {
const init = path.get('declarations.0.init');
if (init.isFunctionExpression()) {
const id = path.node.declarations[0].id as t.Identifier;
addSectionFromComments(
path,
state,
() => id.name,
() => init.node.body,
true,
);
} else if (init.isArrowFunctionExpression()) {
const id = path.node.declarations[0].id as t.Identifier;
addSectionFromComments(
path,
state,
() => id.name,
() => (init.node.body.type === 'BlockStatement' ? init.node.body : null),
true,
);
}
},
ExportNamedDeclaration(path) {
if (path.get('declaration').isFunctionDeclaration()) {
addSectionFromComments(path);
ExportNamedDeclaration(path, state) {
const declaration = path.node.declaration;
if (declaration?.type === 'FunctionDeclaration') {
addSectionFromComments(
path,
state,
() => declaration.id!.name,
() => declaration.body,
true,
);
}
},
ClassMethod(path, state) {
const key = path.node.key;
if (key.type === 'Identifier') {
addSectionFromComments(
path,
state,
() => key.name,
() => path.node.body,
false,
);
}
},
ObjectMethod(path, state) {
const key = path.node.key;
if (key.type === 'Identifier') {
addSectionFromComments(
path,
state,
() => key.name,
() => path.node.body,
false,
);
}
},
},
+50
View File
@@ -0,0 +1,50 @@
#!/usr/bin/env node
/**
* Verification script for ask262Debug module.
* Tests that the module is properly exported and captures spec section marks.
*/
import {
ask262Debug, Agent, ManagedRealm, setSurroundingAgent,
} from '#self';
console.log('=== ask262Debug Verification ===\n');
// 1. Check export exists
console.log('1. Checking export...');
if (!ask262Debug) {
console.error('FAIL: ask262Debug not exported');
process.exit(1);
}
console.log(' ✓ ask262Debug exported\n');
// 2. Run code that hits known spec sections
console.log('2. Executing test code...');
const agent = new Agent();
setSurroundingAgent(agent);
const realm = new ManagedRealm();
realm.evaluateScript(`
// Array.prototype.every - should hit sec-array.prototype.every
[1, 2, 3].every(x => x > 0);
// Proxy creation - should hit sec-proxycreate or similar
new Proxy({}, {});
`);
// 3. Verify marks were captured
console.log('3. Checking marks...');
const marks = ask262Debug.marks;
console.log(` Captured ${marks.length} unique marks\n`);
if (marks.length === 0) {
console.error('FAIL: No marks captured');
process.exit(1);
}
// 4. Show sample marks
console.log('4. Sample marks:');
marks.slice(0, 5).forEach((m, i) => {
console.log(` [${i}] ${m.sectionIds.join(', ')} @ ${m.fileRelativePath}:${m.lineNumber}${m.important ? ' [important]' : ''}`);
});
console.log('=== All Checks Passed ===');