Merge commit '6e92d2345e8231a44556ce7d416451f5f3e6c398' as 'engine262'

This commit is contained in:
2026-03-27 10:07:29 +05:30
433 changed files with 90478 additions and 0 deletions
+239
View File
@@ -0,0 +1,239 @@
#!/usr/bin/env node
import { start } from 'node:repl';
import { readFileSync } from 'node:fs';
import { resolve } from 'node:path';
import { format as _format, inspect as _inspect, parseArgs } from 'node:util';
// let's try if the following message on old node causes test failure on test262.fyi
// "ExperimentalWarning: Importing JSON modules is an experimental feature and might change at any time"
// import packageJson from '../../package.json' with { type: 'json' };
import { createRequire } from 'node:module';
import { createConsole } from '../inspector/utils.mts';
import type { NodeWebsocketInspector } from './inspector.mts';
import { loadImportedModule } from './module.mts';
import {
setSurroundingAgent, FEATURES, inspect, Value, Completion, AbruptCompletion,
type Arguments,
evalQ,
Agent,
ManagedRealm,
skipDebugger,
type ValueCompletion,
createTest262Intrinsics,
surroundingAgent,
ThrowCompletion,
ValueOfNormalCompletion,
ScriptEvaluation,
type PlainEvaluator,
} from '#self';
const packageJson = createRequire(import.meta.url)('../../package.json');
const help = `
engine262 v${packageJson.version}
Usage:
engine262 [options]
engine262 [options] [input file]
engine262 [input file]
Options:
-h, --help Show help (this screen)
-m, --module Evaluate contents of input-file as a module.
-e, --eval Evaluate the given string.
--features=... A comma separated list of features.
--features=all Enable all features.
--list-features List available features.
--no-test262 Do not expose $ and $262 for test262.
--no-inspector Do not attach an inspector.
--no-preview Do not enable preview in the inspector.
`;
const argv = parseArgs({
args: process.argv.slice(2),
allowPositionals: true,
allowNegative: true,
strict: true,
options: {
'help': { type: 'boolean', short: 'h' },
'eval': { type: 'string', short: 'e' },
'module': { type: 'boolean', short: 'm' },
'features': { type: 'string' },
'list-features': { type: 'boolean' },
'inspector': { type: 'boolean' },
'test262': { type: 'boolean', default: true },
// hidden options
'preview-debug': { type: 'boolean' },
},
});
if (argv.values.help) {
process.stdout.write(help);
process.exit(0);
} else if (argv.values['list-features']) {
let nameLength = 0;
let flagLength = 0;
FEATURES.forEach((f) => {
if (f.name.length > nameLength) {
nameLength = f.name.length;
}
if (f.flag.length > flagLength) {
flagLength = f.flag.length;
}
});
const log = (f: string, n: string, u: string) => {
process.stdout.write(`${f.padEnd(flagLength, ' ')} ${n.padEnd(nameLength, ' ')} ${u}\n`);
};
log('flag', 'name', 'url');
log('----', '----', '---');
FEATURES.forEach((f) => {
log(f.flag, f.name, f.url);
});
process.exit(0);
}
let features: string[];
if (argv.values.features === 'all') {
features = FEATURES.map((f) => f.flag);
} else if (argv.values.features) {
features = argv.values.features.split(',');
} else {
features = [];
}
const agent = new Agent({
features,
supportedImportAttributes: ['type'],
loadImportedModule,
});
setSurroundingAgent(agent);
const realm = new ManagedRealm({ resolverCache: new Map(), name: 'repl', specifier: process.cwd() });
// Define console.log
{
const format = (function* format(args: Arguments): PlainEvaluator<string> {
const str = [];
for (const arg of args.values()) {
// TODO: inspect should return a PlainEvaluator so debugger can hook in.
str.push(inspect(arg));
}
return str.join(' ');
});
createConsole(realm, {
* log(args) {
process.stdout.write(`${yield* format(args)}\n`);
},
* error(args) {
process.stderr.write(`${yield* format(args)}\n`);
},
* debug(args) {
process.stderr.write(`${yield* format(args)}\n`);
},
});
}
if (argv.values.test262) {
createTest262Intrinsics(realm, argv.values.test262);
}
let inspector: NodeWebsocketInspector | undefined;
if (argv.values.inspector !== false) {
let has_ws = false;
try {
await import('ws');
has_ws = true;
} catch {
if (argv.values.inspector === true) {
process.stderr.write('--inspector requires the "ws" package to be installed.\n');
process.exit(1);
}
}
if (has_ws) {
const { NodeWebsocketInspector } = await import('./inspector.mts');
inspector = await NodeWebsocketInspector.new();
inspector.attachAgent(surroundingAgent, [realm]);
inspector.preference.previewDebug = argv.values['preview-debug'] || false;
}
}
function oneShotEval(source: string, filename: string) {
realm.scope(() => {
const completion = evalQ((Q) => {
if (argv.values.module || filename.endsWith('.mjs')) {
const module = Q(realm.compileModule(source, { specifier: filename }));
realm.HostDefined.resolverCache?.set(filename, module);
const load = Q(module.LoadRequestedModules());
if (load.PromiseState === 'rejected') {
Q(ThrowCompletion(load.PromiseResult!));
} else if (load.PromiseState === 'pending') {
throw new Error('Internal error: .LoadRequestedModules() returned a pending promise');
}
Q(module.Link());
const evaluate = Q(skipDebugger(module.Evaluate()));
if (evaluate.PromiseState === 'rejected') {
Q(ThrowCompletion(evaluate.PromiseResult!));
}
} else {
Q(realm.evaluateScript(source, { specifier: filename }));
}
});
if (completion instanceof AbruptCompletion) {
const inspected = inspect(completion);
process.stderr.write(`${inspected}\n`);
process.exit(1);
}
});
inspector?.stop();
}
if (argv.positionals[0]) {
const source = readFileSync(argv.positionals[0], 'utf8');
oneShotEval(source, resolve(argv.positionals[0]));
} else if (!process.stdin.isTTY) {
process.stdin.setEncoding('utf8');
let source = '';
process.stdin.on('data', (data) => {
source += data;
});
process.stdin.once('end', () => {
oneShotEval(source, process.cwd());
});
} else if (argv.values.eval) {
oneShotEval(argv.values.eval, process.cwd());
} else {
process.stdout.write(`${packageJson.name} v${String(packageJson.version).replace('0.0.1-', '')}
Type ".help" for more information. Please report bugs to ${packageJson.bugs.url}
`);
const server = start({
prompt: '> ',
eval: (cmd, _context, _filename, callback) => {
try {
const script = realm.compileScript(cmd, {});
if (script instanceof ThrowCompletion) {
callback(null, script);
return;
}
let c;
surroundingAgent.evaluate(ScriptEvaluation(ValueOfNormalCompletion(script)), (completion) => {
c = completion;
callback(null, completion);
});
if (!c) {
surroundingAgent.resumeEvaluate();
}
} catch (e) {
callback(e as Error, null);
}
},
preview: false,
writer: (o) => realm.scope(() => {
if (o instanceof Value || o instanceof Completion) {
return inspect(o as Value | ValueCompletion);
}
return _inspect(o);
}),
});
server.on('exit', () => inspector?.stop());
}
+58
View File
@@ -0,0 +1,58 @@
/* eslint-disable no-console */
import {
Agent, inspect, ManagedRealm, NormalCompletion, setSurroundingAgent, ThrowCompletion, type Arguments, type PlainEvaluator,
} from '#self';
import { createConsole } from '#self/inspector';
// Agent is the running environment.
const agent = new Agent({
});
// Only one agent can be active at a time.
setSurroundingAgent(agent);
// A Realm is a separate global environment.
// In Web browsers, each iframe has its own Realm and they may interact with each other.
const realm = new ManagedRealm({ resolverCache: new Map(), name: 'My Realm', specifier: process.cwd() });
// Define console.log
{
const format = (function* format(args: Arguments): PlainEvaluator<string> {
const str = [];
for (const arg of args.values()) {
str.push(inspect(arg));
}
return str.join(' ');
});
createConsole(realm, {
* log(args) {
process.stdout.write(`${yield* format(args)}\n`);
},
* error(args) {
process.stderr.write(`${yield* format(args)}\n`);
},
* debug(args) {
process.stderr.write(`${yield* format(args)}\n`);
},
* default(method, args) {
process.stdout.write(`[console.${method}] ${yield* format(args)}\n`);
},
});
}
// Do not forget to use realm.scope when running code.
realm.scope(() => {
// Run ECMAScript code in the Realm.
realm.evaluateScript(`
console.log('Hello from engine262!');
console.log('2 + 2 =', 2 + 2);
`, { specifier: 'example.mts' });
const result = realm.evaluateScript(`
throw new Error('This is an example error');
`, { specifier: 'example.mts' });
if (result instanceof NormalCompletion) {
console.log('No Error');
} else if (result instanceof ThrowCompletion) {
console.error('Caught error from evaluated script:', inspect(result.Value));
}
});
+124
View File
@@ -0,0 +1,124 @@
import http from 'node:http';
import https from 'node:https';
import { WebSocketServer } from 'ws';
import packageJson from '../../package.json' with { type: 'json' };
// Note: typescript will not copy json files, so it will not appear in the lib directory
// eslint-disable-next-line import/no-useless-path-segments
import protocol from '../../lib-src/inspector/js_protocol.json' with { type: 'json' };
import { Inspector } from '../inspector/index.mts';
const ANSI = {
reset: '\u001b[0m',
red: '\u001b[31m',
green: '\u001b[32m',
yellow: '\u001b[33m',
blue: '\u001b[34m',
};
export class NodeWebsocketInspector extends Inspector {
_server: http.Server | https.Server;
_ws: WebSocketServer;
isDebug = false;
protected override send(data: object): void {
const s = JSON.stringify(data);
this._ws.clients.forEach((ws) => {
ws.send(s);
});
}
protected constructor(server: http.Server | https.Server, isDebug: boolean) {
super();
this._server = server;
const ws = new WebSocketServer({ server });
this._ws = ws;
ws.on('connection', (ws) => {
const send = (obj: unknown) => {
const s = JSON.stringify(obj);
ws.send(s);
};
const sendEvent = Object.create(new Proxy({}, {
get: (_, key: string) => {
const f = (params: Record<string, unknown>) => {
send({ method: key, params });
};
Object.defineProperty(sendEvent, key, { value: key });
return f;
},
}));
ws.on('message', (data: string) => {
const { id, method, params } = JSON.parse(data);
if (isDebug) {
process.stdout.write(`${ANSI.green}${method}${ANSI.reset}: ${JSON.stringify(params)}\n`);
}
this.onMessage(id, method, params);
});
});
}
static inspectorHTTPServer(req: http.IncomingMessage, res: http.ServerResponse<http.IncomingMessage>) {
if (req.method !== 'GET') {
res.writeHead(405);
res.end();
return;
}
const json = (obj: unknown) => {
const s = JSON.stringify(obj);
res.writeHead(200, {
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(s),
});
res.end(s);
};
switch (req.url) {
case '/json':
case '/json/list':
json([{
description: `${packageJson.name} instance`,
devtoolsFrontendUrl: 'chrome-devtools://devtools/bundled/js_app.html?experiments=true&v8only=true&ws=localhost:9229/',
devtoolsFrontendUrlCompat: 'chrome-devtools://devtools/bundled/inspector.html?experiments=true&v8only=true&ws=localhost:9229/',
faviconUrl: 'https://avatars0.githubusercontent.com/u/51185628',
id: 'inspector.0',
title: 'engine262',
type: 'node',
url: `file://${process.cwd()}`,
webSocketDebuggerUrl: 'ws://localhost:9229/',
}]);
break;
case '/json/version':
json({
'Browser': `${packageJson.name}/v${packageJson.version}`,
'Protocol-Version': `${protocol.version.major}.${protocol.version.minor}`,
});
break;
case '/json/protocol':
json(protocol);
break;
default:
res.writeHead(404);
res.end();
break;
}
}
static new(port = 9229, host = '127.0.0.1', isDebug = !!process.env.DEBUG) {
const server = http.createServer(NodeWebsocketInspector.inspectorHTTPServer);
const inspector = new NodeWebsocketInspector(server, isDebug);
return new Promise<NodeWebsocketInspector>((resolve) => {
server.listen(port, host, () => {
resolve(inspector);
});
});
}
stop() {
this._server.close();
this._ws.close();
}
}
+74
View File
@@ -0,0 +1,74 @@
import { readFile, readFileSync } from 'node:fs';
import path from 'node:path';
import {
evalQ, ManagedRealm, Realm, Throw, ThrowCompletion, type AgentHostDefined,
} from '#self';
export function createLoadImportedModule(getCache = (realm: ManagedRealm) => realm.HostDefined.resolverCache) {
const validateType = (attributes: Map<string, string>, finish: (completion: ThrowCompletion) => void) => {
const type = attributes.get('type');
if (type && type !== 'json') {
finish(Throw('TypeError', 'UnsupportedModuleType', type));
return false;
}
return true;
};
const parseModule = (realm: ManagedRealm, resolved: string, attributes: Map<string, string>, source: string) => (attributes.get('type') === 'json' || resolved.endsWith('.json')
? realm.createJSONModule(resolved, source)
: realm.compileModule(source, { specifier: resolved }));
const loadImportedModuleSyncOrAsync = (
readFile: (path: string, callback: (err: NodeJS.ErrnoException | null, data: string) => void) => void,
...[referrer, specifier, attributes, _hostDefined, finish]: Parameters<NonNullable<AgentHostDefined['loadImportedModule']>>
) => {
const realm = (referrer instanceof Realm ? referrer : referrer.Realm) as ManagedRealm;
const cache = getCache(realm);
if (!referrer.HostDefined.specifier) {
finish(Throw('SyntaxError', 'CouldNotResolveModule', specifier));
return;
}
if (!validateType(attributes, finish)) {
return;
}
evalQ(async (Q) => {
const base = path.dirname(referrer.HostDefined.specifier!);
const resolved = path.resolve(base, specifier);
if (cache?.has(resolved)) {
finish(cache.get(resolved)!);
return;
}
try {
readFile(resolved, (err, data) => {
if (err) {
finish(Throw('SyntaxError', 'CouldNotResolveModule', specifier));
return;
}
const m = Q(parseModule(realm, resolved, attributes, data));
cache?.set(resolved, m);
finish(m);
});
} catch (error) {
finish(Throw('SyntaxError', 'CouldNotResolveModule', specifier));
}
});
};
const loadImportedModule: NonNullable<AgentHostDefined['loadImportedModule']> = loadImportedModuleSyncOrAsync.bind(null, (path, callback) => {
readFile(path, 'utf8', callback);
});
const loadImportedModuleSync: NonNullable<AgentHostDefined['loadImportedModule']> = loadImportedModuleSyncOrAsync.bind(null, (path, callback) => {
try {
const data = readFileSync(path, 'utf8');
callback(null, data);
} catch (error) {
callback(error as NodeJS.ErrnoException, '');
}
});
return { loadImportedModule, loadImportedModuleSync };
}
export const { loadImportedModule, loadImportedModuleSync } = createLoadImportedModule();
+21
View File
@@ -0,0 +1,21 @@
{
"extends": "../../tsconfig.base.json",
"references": [{ "path": "../inspector" }, { "path": "../../src" }],
"compilerOptions": {
"incremental": true,
"declarationDir": "../../lib/node",
"tsBuildInfoFile": "../../lib/node/.tsbuildinfo",
"erasableSyntaxOnly": true,
"rewriteRelativeImportExtensions": true,
"rootDir": "./",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"outDir": "../../lib/node/"
},
"include": [
"./example.mts",
"./bin.mts",
"./inspector.mts",
"./module.mts"
]
}