@ 261027.7

261020.1 · 1d7e0126f3e601103d93586a6f872144f2789dad

chore: self-maintain Node.js features

reading declarations

Changes to test/nodejs.test.js. All files

test/nodejs.test.js

@@ -1,12 +1,39 @@ const assert = require("node:assert");+const fs = require("node:fs");+const path = require("node:path");+const repl = require("node:repl");+const { PassThrough } = require("node:stream"); const { test } = require("node:test"); const {+	findTopLevelAwaits, 	getFirstExpression, 	isRecoverableError, 	isValidSyntax,+	tokenize, 	transformModuleSyntax, } = require("../dist/internal.js"); +const AsyncFunction = Object.getPrototypeOf(async () => {}).constructor;++function validatedImport(specifier, requiredExports) {+	const checks = requiredExports+		.map(+			(name) =>+				`if (!(${JSON.stringify(name)} in m)) throw new SyntaxError(${JSON.stringify(+					`The requested module '${specifier}' does not provide an export named '${name}'`,+				)}); `,+		)+		.join("");+	return `__nodeREPLDynamicImport(${JSON.stringify(specifier)}).then((m) => { ${checks}return m; })`;+}++function execute(source, namespace) {+	return new AsyncFunction(+		"__nodeREPLDynamicImport",+		transformModuleSyntax(source).code,+	)(async () => namespace);+}+ test("should transform module syntax for the Node.js REPL", () => { 	const result = transformModuleSyntax( 		'import { readFile } from "node:fs/promises";\nreadFile("file");',@@ -17,6 +44,125 @@ test("should transform module syntax for the Node.js REPL", () => { 	assert.match(result.code, /readFile/); }); +test("should declare imported bindings instead of rewriting references", () => {+	assert.deepStrictEqual(transformModuleSyntax('import fs from "node:fs";'), {+		code: `const { default: fs } = await ${validatedImport("node:fs", ["default"])};`,+		hadModuleSyntax: true,+	});+	assert.deepStrictEqual(+		transformModuleSyntax('import { readFile as rf } from "node:fs";\nrf();'),+		{+			code: `const { readFile: rf } = await ${validatedImport("node:fs", ["readFile"])};\nrf();`,+			hadModuleSyntax: true,+		},+	);+	assert.deepStrictEqual(+		transformModuleSyntax('import def, * as ns from "mod";\ndef; ns;'),+		{+			code: `const ns = await ${validatedImport("mod", ["default"])}; const def = ns.default;\ndef; ns;`,+			hadModuleSyntax: true,+		},+	);+	assert.deepStrictEqual(+		transformModuleSyntax('import { "a-b" as c } from "mod";\nc;'),+		{+			code: `const { "a-b": c } = await ${validatedImport("mod", ["a-b"])};\nc;`,+			hadModuleSyntax: true,+		},+	);+});++test("should retain imported bindings across REPL inputs", async () => {+	const server = repl.start({+		input: new PassThrough(),+		output: new PassThrough(),+		terminal: false,+		prompt: "",+	});+	const namespace = { default: 7, value: 11, "a-b": 13 };+	server.context.__nodeREPLDynamicImport = async () => namespace;+	// Arrays come from the REPL's vm context, so copy them into this realm+	// before comparing prototypes with deepStrictEqual.+	const evaluate = (source) =>+		new Promise((resolve, reject) => {+			server.eval(+				`${transformModuleSyntax(source).code}\n`,+				server.context,+				"repl-import-test",+				(error, value) =>+					error+						? reject(error)+						: resolve(Array.isArray(value) ? [...value] : value),+			);+		});+	try {+		await evaluate('import first from "m";');+		assert.strictEqual(await evaluate("first"), 7);+		await evaluate('import { value as second, "a-b" as third } from "m";');+		assert.deepStrictEqual(+			await evaluate("[first, second, third]"),+			[7, 11, 13],+		);+		await evaluate('import another, * as ns from "m";');+		assert.deepStrictEqual(await evaluate("[another, ns.value]"), [7, 11]);+		namespace.value = 17;+		assert.deepStrictEqual(await evaluate("[second, ns.value]"), [11, 17]);+	} finally {+		server.close();+	}+});++test("should preserve bare-call this and lexical shadowing", async () => {+	// A bare call of a destructured binding must not receive the namespace+	// object as `this`, unlike the former `__nodeREPLImport0.call()` rewrite.+	const namespace = {+		call() {+			return this === namespace;+		},+		value: 1,+	};+	const { code } = transformModuleSyntax(+		'import { call, value } from "m";\nconst observed = [call(), (() => { const value = 2; return value; })(), value];',+	);+	const observed = await new AsyncFunction(+		"__nodeREPLDynamicImport",+		`${code}\nreturn observed;`,+	)(async () => namespace);+	assert.deepStrictEqual(observed, [false, 2, 1]);+});++test("should let JavaScript enforce const assignment semantics", async () => {+	for (const assignment of [+		"value = 2",+		"value++",+		"({ value } = { value: 2 })",+	]) {+		await assert.rejects(+			execute(`import { value } from "m";\n${assignment};`, { value: 1 }),+			/Assignment to constant variable\./,+		);+	}+});++test("should keep missing-export validation and handle special export names", async () => {+	await assert.rejects(+		execute('import missing from "m";', {}),+		/does not provide an export named 'default'/,+	);+	await assert.rejects(+		execute('import { missing } from "m";', {}),+		/does not provide an export named 'missing'/,+	);+	const { code } = transformModuleSyntax(+		'import { "a-b" as value, __proto__ as proto } from "m";',+	);+	const result = await new AsyncFunction(+		"__nodeREPLDynamicImport",+		`${code}\nreturn [value, proto];`,+	)(async () => ({ "a-b": 1, ["__proto__"]: 2 }));+	assert.deepStrictEqual(result, [1, 2]);+});+ test("should find the first expression at an error column", () => { 	assert.strictEqual( 		getFirstExpression("a(); assert.ok(value); b()", 13),@@ -33,3 +179,41 @@ test("should detect recoverable syntax errors", () => { 	assert.strictEqual(isRecoverableError("function foo() {"), true); 	assert.strictEqual(isRecoverableError("const value: number = 1"), false); });++test("should tokenize input for syntax highlighting", () => {+	assert.deepStrictEqual(tokenize("const x = 1; // done"), [+		{ kind: "keyword", start: 0, end: 5 },+		{ kind: "identifier", start: 6, end: 7 },+		{ kind: "punctuator", start: 8, end: 9 },+		{ kind: "number", start: 10, end: 11 },+		{ kind: "punctuator", start: 11, end: 12 },+		{ kind: "comment", start: 13, end: 20 },+	]);+});++test("should find top-level awaits", () => {+	assert.deepStrictEqual(+		findTopLevelAwaits("await 1;\nasync function f() { await 2; }\nawait 3;"),+		[+			{ line: 1, column: 0 },+			{ line: 3, column: 0 },+		],+	);+});++const fixturesDir = path.join(__dirname, "fixtures", "nodejs");+for (const [suite, api] of [+	["tokenize", tokenize],+	["awaits", findTopLevelAwaits],+	["recoverable", isRecoverableError],+]) {+	const dir = path.join(fixturesDir, suite);+	for (const file of fs.readdirSync(dir)) {+		const cases = JSON.parse(fs.readFileSync(path.join(dir, file), "utf8"));+		for (const { source, expected } of cases) {+			test(`${suite}/${file}: ${JSON.stringify(source)}`, () => {+				assert.deepStrictEqual(api(source), expected);+			});+		}+	}+}