416 lines
14 KiB
TypeScript
416 lines
14 KiB
TypeScript
import { beforeAll, describe, expect, test } from "bun:test";
|
|||
|
|
import { bunEnv, bunExe, tempDirWithFiles } from "harness";
|
||
|
|
|
||
|
|
const exitCode0 = [
|
||
|
|
" ",
|
||
|
|
"\n",
|
||
|
|
"\t",
|
||
|
|
"\r\n",
|
||
|
|
" ",
|
||
|
|
"\n\n\n",
|
||
|
|
"export default '\\u{000C}'",
|
||
|
|
"export default '\\u{200B}'",
|
||
|
|
"export default '\\u{200C}'",
|
||
|
|
"export default '\\u{200D}'",
|
||
|
|
"export default '\\u{FEFF}'",
|
||
|
|
'"use strict";',
|
||
|
|
'"use strict";\n',
|
||
|
|
"'use strict';",
|
||
|
|
'"use strict";\n\n',
|
||
|
|
'"use asm";',
|
||
|
|
'"use asm";\n',
|
||
|
|
"'use strict'; 'use asm';",
|
||
|
|
'"use strict"; "use strict";',
|
||
|
|
"// empty comment",
|
||
|
|
"/* empty block comment */",
|
||
|
|
"/** JSDoc comment */",
|
||
|
|
"//\n//\n//",
|
||
|
|
"/* \n Multi\n line\n comment\n */",
|
||
|
|
"-->",
|
||
|
|
"// TODO: ",
|
||
|
|
"/** @type {number} */",
|
||
|
|
"const a = 123;",
|
||
|
|
"let a;",
|
||
|
|
"var a = undefined;",
|
||
|
|
"const a = null;",
|
||
|
|
"const [a] = [];",
|
||
|
|
"const {a} = {};",
|
||
|
|
"const [...a] = [];",
|
||
|
|
"const {...a} = {};",
|
||
|
|
"let [a = 1] = [];",
|
||
|
|
"let {a: b = 1} = {};",
|
||
|
|
"``",
|
||
|
|
"`template`",
|
||
|
|
"`template${123}`",
|
||
|
|
"`${null}`",
|
||
|
|
"`${'`'}`",
|
||
|
|
"`\n`",
|
||
|
|
"`\n`",
|
||
|
|
"({});",
|
||
|
|
"({ a: 1 });",
|
||
|
|
"({ ['computed']: 1 });",
|
||
|
|
"({ get x() { return 1; } });",
|
||
|
|
"({ __proto__: null });",
|
||
|
|
"({ get [1+2](){}, set [1+2](x){} });",
|
||
|
|
"({ a: 1, ...{b: 2} });",
|
||
|
|
"[];",
|
||
|
|
"[,];",
|
||
|
|
"[,,];",
|
||
|
|
"[1,];",
|
||
|
|
"[,...[1]];",
|
||
|
|
"[,,...[1]];",
|
||
|
|
"[[[[[]]]]]",
|
||
|
|
"[...[...[...[]]]]",
|
||
|
|
"[1,,2,,3];",
|
||
|
|
"Array(1_000_000).fill(1);",
|
||
|
|
"()=>{};",
|
||
|
|
"async()=>{};",
|
||
|
|
"(function(){}).bind(null);",
|
||
|
|
"()=>()=>()=>1;",
|
||
|
|
"function f(a=1,{b}={},...c){};",
|
||
|
|
"async function* f(){await 1; yield 2;};",
|
||
|
|
"(async()=>await 1)();",
|
||
|
|
"class A{}",
|
||
|
|
"class A extends null{}",
|
||
|
|
"class A{#private=1}",
|
||
|
|
"class A{static{}}",
|
||
|
|
"class A{get #a(){} set #a(x){}}",
|
||
|
|
"class A{*#gen(){yield this.#p}#p=1}",
|
||
|
|
"class A extends class B{}{};",
|
||
|
|
"export {};",
|
||
|
|
"export default 1;",
|
||
|
|
"export default function(){};",
|
||
|
|
"import.meta.url;",
|
||
|
|
"const pi = Math.PI;",
|
||
|
|
"const hello = 'world';",
|
||
|
|
"const star = '⭐';",
|
||
|
|
"'\\u0000\\uFFFF';",
|
||
|
|
"'\\x00\\xFF';",
|
||
|
|
"const \\u{61} = 1;",
|
||
|
|
"'\\0';",
|
||
|
|
"'\\v\\f\\b';",
|
||
|
|
"'\\\r\n';",
|
||
|
|
"/./;",
|
||
|
|
"/[]/;",
|
||
|
|
"/[^]/;",
|
||
|
|
"/\\u{0}/u;",
|
||
|
|
"/[\\u0000-\\u{10FFFF}]/u;",
|
||
|
|
"/./gimsuyd;",
|
||
|
|
"/\\p{Script=Latin}/u;",
|
||
|
|
"/(?<=a)b/;",
|
||
|
|
"/(?<!a)b/;",
|
||
|
|
"/\\k<name>/;",
|
||
|
|
"1n;",
|
||
|
|
"0n;",
|
||
|
|
"9007199254740991n;",
|
||
|
|
"0b1n;",
|
||
|
|
"0o7n;",
|
||
|
|
"0xFn;",
|
||
|
|
"1_2_3n;",
|
||
|
|
"BigInt(1);",
|
||
|
|
"-0n;",
|
||
|
|
"~0n;",
|
||
|
|
"0b1_0_1;",
|
||
|
|
"0o7_7_7;",
|
||
|
|
"0xF_F_F;",
|
||
|
|
"1_2_3_4;",
|
||
|
|
"try{}catch{}",
|
||
|
|
"try{}catch(e){}",
|
||
|
|
"try{}catch{}finally{}",
|
||
|
|
"try{}finally{}",
|
||
|
|
"try{throw 1}catch(e){}",
|
||
|
|
"try{throw new Error}catch{}",
|
||
|
|
"try{throw{}}catch({message}){}",
|
||
|
|
"try{throw null}catch(e){}",
|
||
|
|
"try{throw undefined}catch(e){}",
|
||
|
|
"try{throw function(){}}catch(e){}",
|
||
|
|
"function*g(){yield;}",
|
||
|
|
"function*g(){yield*g();}",
|
||
|
|
"async function*g(){yield;}",
|
||
|
|
"async function*g(){await 1;}",
|
||
|
|
"(async function*(){for await(x of y){}});",
|
||
|
|
"function*g(){try{yield}finally{}}",
|
||
|
|
"[...new Set];",
|
||
|
|
"for(x of[]){}",
|
||
|
|
"for await(x of[]){}",
|
||
|
|
"async function f(){for await(x of y){}}",
|
||
|
|
"void 0;",
|
||
|
|
"new (class{})();",
|
||
|
|
"(class extends null{});",
|
||
|
|
"(class{[Symbol.hasInstance](){}});",
|
||
|
|
"function*g(){yield yield yield;}",
|
||
|
|
"1..toString();",
|
||
|
|
"`${`${`${1}`}`}`",
|
||
|
|
"String.raw`\n`",
|
||
|
|
"String.raw`\\n`",
|
||
|
|
"`${`${function*(){yield 1}}`}`",
|
||
|
|
"`${class{}}${()=>{}}${/./}`",
|
||
|
|
"`${`${async()=>await 1}`}`",
|
||
|
|
"`${`${class{static{``}}}`}`",
|
||
|
|
"await import('bun');",
|
||
|
|
"await import('bun:ffi');",
|
||
|
|
// "await import(import.meta.path);" — the spec-compliant module loader treats
|
||
|
|
// a top-level await on its own module's load promise as an unsettleable TLA,
|
||
|
|
// so this snippet now blocks forever instead of completing synchronously.
|
||
|
|
"/(?:)/",
|
||
|
|
"/\\b\\B\\d\\D\\w\\W\\s\\S/",
|
||
|
|
"/\\cA\\cZ\\ca\\cz/",
|
||
|
|
"/\\p{General_Category=Letter}/u",
|
||
|
|
"/\\p{Script_Extensions=Latin}/u",
|
||
|
|
"/(?<=(?=a)b)c/",
|
||
|
|
"/(?<!(?!a)b)c/",
|
||
|
|
"/\\k<name>\\k<other>/",
|
||
|
|
"/\\u{10FFFF}\\u{0000}/u",
|
||
|
|
"/[\\p{ASCII}--\\p{Decimal_Number}]/v",
|
||
|
|
"const [{a: [b = 1] = []} = [{a: [2]}]] = [];",
|
||
|
|
"let {a: {b: {c = 1} = {}} = {}} = {};",
|
||
|
|
"const [a, , ...{0: b, length}] = [];",
|
||
|
|
"const {[class{}.prop]: x} = {};",
|
||
|
|
"const {[()=>{}]: x} = {};",
|
||
|
|
"let {[/./]: x} = {};",
|
||
|
|
"const {[class extends null{}]: x} = {};",
|
||
|
|
"const {[function*(){}]: x} = {};",
|
||
|
|
"function f([a] = [1], {b} = {b: 2}, ...c){}",
|
||
|
|
"(function({[key]: value} = {}){})",
|
||
|
|
"(({[this?.prop]: x} = {})=>{})",
|
||
|
|
"function f(a = () => b, b = 1){}",
|
||
|
|
"(a = class{}, b = new a)=>{}",
|
||
|
|
"function f(a = new.target){}",
|
||
|
|
"for await(const x of async function*(){yield 1}()){}",
|
||
|
|
"for(const x of function*(){yield 1}()){}",
|
||
|
|
"for(const {[key]: x} of []){}",
|
||
|
|
"for(let [{a = 1} = {}] of []){}",
|
||
|
|
"do{continue}while(false);",
|
||
|
|
"label:do{break label}while(false);",
|
||
|
|
"outer:for(;;)inner:break outer;",
|
||
|
|
"block:{break block;}",
|
||
|
|
"while(false)with(null);",
|
||
|
|
"switch(0){case 1:default:case 2:}",
|
||
|
|
"try{throw async()=>{}}catch(e){await e}",
|
||
|
|
"try{throw class{}}catch(e){new e}",
|
||
|
|
"try{throw{[Symbol.iterator]:()=>{}}}catch(e){}",
|
||
|
|
"try{throw Object.create(null)}catch(e){}",
|
||
|
|
"try{throw new Proxy({},{})}catch(e){}",
|
||
|
|
"try{throw new WeakMap}catch(e){}",
|
||
|
|
"try{throw new Int32Array}catch(e){}",
|
||
|
|
"try{throw new SharedArrayBuffer(0)}catch(e){}",
|
||
|
|
"try{throw new WebAssembly.Module(new Uint8Array)}catch(e){}",
|
||
|
|
"void void void 0;",
|
||
|
|
"typeof typeof typeof 0;",
|
||
|
|
"delete (delete (delete 0));",
|
||
|
|
"await (await (await 1));",
|
||
|
|
"--{a:1}.a;",
|
||
|
|
"(class{}).prototype.constructor;",
|
||
|
|
"(()=>{}).prototype?.call;",
|
||
|
|
"(async()=>{}).prototype?.call;",
|
||
|
|
"(function*(){}).prototype.next;",
|
||
|
|
"(async function*(){}).prototype.next;",
|
||
|
|
"this?.prop?.[key]?.();",
|
||
|
|
"0b0_1_0_1;",
|
||
|
|
"0B0_1_0_1;",
|
||
|
|
"0o0_1_2_3;",
|
||
|
|
"0O0_1_2_3;",
|
||
|
|
"0x0_1_2_3;",
|
||
|
|
"0X0_1_2_3;",
|
||
|
|
"1_2_3.4_5_6;",
|
||
|
|
"1_2_3e1_2_3;",
|
||
|
|
"1_2_3E1_2_3;",
|
||
|
|
".0_1_2_3;",
|
||
|
|
"0b1_0_1n;",
|
||
|
|
"0B1_0_1n;",
|
||
|
|
"0o1_2_3n;",
|
||
|
|
"0O1_2_3n;",
|
||
|
|
"0x1_2_3n;",
|
||
|
|
"0X1_2_3n;",
|
||
|
|
"1_2_3_4_5n;",
|
||
|
|
"BigInt(0b101);",
|
||
|
|
"BigInt(0o777);",
|
||
|
|
"BigInt(0xff);",
|
||
|
|
"for(const [,,...a] of [[]]){}",
|
||
|
|
"for(let {a: [b]} of []){}",
|
||
|
|
"for await(const [a = await 1] of []){}",
|
||
|
|
"for(const {[await 1]: x} of []){}",
|
||
|
|
"for(var {[class{}]: x} of []){}",
|
||
|
|
"for(let {[/./]: x} of []){}",
|
||
|
|
"for(const {[`${a}`]: x} of []){}",
|
||
|
|
"for await(const x of async function*(){yield*[1,2,3]}()){}",
|
||
|
|
"new class extends (await Promise.resolve(class{})){}",
|
||
|
|
"function*g(){yield yield*function*(){yield yield yield;}();}",
|
||
|
|
"async function*f(){yield await Promise.all([1,2,3])}",
|
||
|
|
"async function*f(){yield*[await 1,await 2]}",
|
||
|
|
"(async function(){await (async()=>1)()})();",
|
||
|
|
"async function*f(){for await(const x of async function*(){yield 1}())yield x}",
|
||
|
|
"async function f(){return await new Promise(r=>r(1))}",
|
||
|
|
"async function*f(){try{yield await 1}finally{await 2}}",
|
||
|
|
"(async()=>{for await(const x of[])for await(const y of[]){}})();",
|
||
|
|
"async function*f(){yield await(yield await(yield await 1))}",
|
||
|
|
"async function*f(){yield*async function*(){yield await 1}()}",
|
||
|
|
"export default await(async()=>1)();",
|
||
|
|
"function*g(){yield*{[Symbol.iterator]:function*(){yield 1}}}",
|
||
|
|
"function*g(){yield*{async*[Symbol.asyncIterator](){yield 1}}}",
|
||
|
|
"function*g(){yield*function*(){yield function*(){yield 1}}()}",
|
||
|
|
"function*g(){yield*{get[Symbol.iterator](){return function*(){yield 1}}}}",
|
||
|
|
"function*g(){yield*(class{static*[Symbol.iterator](){yield 1}});}",
|
||
|
|
"function*g(){yield*class{static*[Symbol.iterator](){yield 1}}}",
|
||
|
|
"function*g(){yield*{*[Symbol.iterator](){yield class{}}}}",
|
||
|
|
"function*g(){yield*{*[Symbol.iterator](){yield function(){}}}}",
|
||
|
|
"function*g(){yield*{*[Symbol.iterator](){yield async()=>{}}}}",
|
||
|
|
"function*g(){yield*{*[Symbol.iterator](){yield function*(){}}}}",
|
||
|
|
"({[function*(){yield 1}]:1});",
|
||
|
|
"({[async function*(){yield 1}]:1});",
|
||
|
|
"({[(()=>class{})()]:1});",
|
||
|
|
"({[new class{valueOf(){return 1}}]:2});",
|
||
|
|
"({[new class{[Symbol.toPrimitive](){return 1}}]:2});",
|
||
|
|
"({[new class{toString(){return '1'}}]:2});",
|
||
|
|
"({[new class{get [Symbol.toPrimitive](){return()=>1}}]:2});",
|
||
|
|
"({[new class{static{this.prototype.valueOf=()=>1}}]:2});",
|
||
|
|
"label:while(1)try{continue label}finally{break label}",
|
||
|
|
"new Proxy(class{},{construct(){return{}}});",
|
||
|
|
"new Proxy(function(){},{apply(){return{}}});",
|
||
|
|
"new Proxy({},{get(){return new Proxy({},{})}})",
|
||
|
|
"new Proxy([],{get(){return new Proxy({},{})}});",
|
||
|
|
"new Proxy(function(){},{construct(){return new Proxy({},{})}})",
|
||
|
|
"new Proxy(class{},{construct(){return new Proxy({},{})}})",
|
||
|
|
"new Proxy({},{get(){return function(){return new Proxy({},{})}}})",
|
||
|
|
"new Proxy(function(){},{apply(){return new Proxy(class{},{})}})",
|
||
|
|
"new Proxy(class{},{construct(){return new Proxy(function(){},{})}})",
|
||
|
|
"Reflect.get(new Proxy({},{}),'')",
|
||
|
|
"Reflect.set(new Proxy({},{}),'')",
|
||
|
|
"Reflect.has(new Proxy({},{}),'')",
|
||
|
|
"Reflect.deleteProperty(new Proxy({},{}),'')",
|
||
|
|
"Reflect.getOwnPropertyDescriptor(new Proxy({},{}),'')",
|
||
|
|
"Reflect.getPrototypeOf(new Proxy({},{}))",
|
||
|
|
"Reflect.setPrototypeOf(new Proxy({},{}),{})",
|
||
|
|
"Reflect.isExtensible(new Proxy({},{}))",
|
||
|
|
"Reflect.preventExtensions(new Proxy({},{}))",
|
||
|
|
"Promise.all([Promise.race([]),Promise.allSettled([])])",
|
||
|
|
"Promise.race([Promise.all([]),Promise.any([])])",
|
||
|
|
"Promise.allSettled([Promise.race([]),Promise.all([])])",
|
||
|
|
"Promise.any([Promise.allSettled([]),Promise.race([])])",
|
||
|
|
"Promise.resolve(Promise.reject().catch(()=>Promise.all([])))",
|
||
|
|
"new Function(`return function*(){${`yield${`yield${`yield`}`}`}}`)();",
|
||
|
|
"new Function(`return async()=>${`await${`await${`1`}`}`}`)();",
|
||
|
|
"new Function(`return class{${`*[Symbol.iterator](){${`yield 1`}}`}}`)();",
|
||
|
|
"new Function(`return class{${`async*[Symbol.asyncIterator](){${`yield 1`}}`}}`)();",
|
||
|
|
"new Function(`return class{${`get #a(){${`return 1`}}`}}`)();",
|
||
|
|
"new Function(`return class{${`set #a(v){${`this.#b=v`}}#b`}}`)();",
|
||
|
|
"new Function(`return class{${`#a;${`#b;${`#c`}`}`}}`)();",
|
||
|
|
"for await(let {...x} of async function*(){yield*[]}()){}",
|
||
|
|
"for await(const x of (async function*(){yield await 1})()){}",
|
||
|
|
"function f(){let a=b,b=1}",
|
||
|
|
"(function(){const[a=b,b=1]=[]})",
|
||
|
|
"(()=>{let{a=b,b=1}={}})",
|
||
|
|
];
|
||
|
|
|
||
|
|
// Originally each source was spawned twice (bun file + bun --eval), giving ~600
|
||
|
|
// cold process startups whose cost dominated the file's wall time under ASAN.
|
||
|
|
// The driver below parses and evaluates every fixture inside two subprocesses
|
||
|
|
// instead: one launched as a file entrypoint that imports each fixture from
|
||
|
|
// disk, and one launched via `--eval` that imports each fixture as a data: URL.
|
||
|
|
// Every source still passes through Bun's transpiler and module evaluation, and
|
||
|
|
// per-fixture failures are reported individually.
|
||
|
|
const driver = `
|
||
|
|
const count = Number(process.env.SYNTAX_FIXTURE_COUNT);
|
||
|
|
const mode = process.env.SYNTAX_FIXTURE_MODE;
|
||
|
|
let failures = 0;
|
||
|
|
for (let i = 0; i < count; i++) {
|
||
|
|
try {
|
||
|
|
if (mode === "data") {
|
||
|
|
const source = await Bun.file(\`./fixture-\${i}.js\`).text();
|
||
|
|
await import("data:text/javascript;base64," + Buffer.from(source).toString("base64"));
|
||
|
|
} else {
|
||
|
|
await import(\`./fixture-\${i}.js\`);
|
||
|
|
}
|
||
|
|
process.stdout.write(\`PASS \${i}\\n\`);
|
||
|
|
} catch (e) {
|
||
|
|
process.stdout.write(\`FAIL \${i} \${e?.message ?? e}\\n\`);
|
||
|
|
failures++;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
process.stdout.write(\`DONE \${count}\\n\`);
|
||
|
|
process.exitCode = failures === 0 ? 0 : 1;
|
||
|
|
`;
|
||
|
|
|
||
|
|
const fixturePath = tempDirWithFiles("syntax-fixture", {
|
||
|
|
"package.json": `{ "name": "test" }`,
|
||
|
|
"driver.mjs": driver,
|
||
|
|
...Object.fromEntries(exitCode0.map((source, i) => [`fixture-${i}.js`, source])),
|
||
|
|
});
|
||
|
|
|
||
|
|
type RunResult = {
|
||
|
|
results: Map<number, string>;
|
||
|
|
stdout: string;
|
||
|
|
stderr: string;
|
||
|
|
exitCode: number | null;
|
||
|
|
};
|
||
|
|
|
||
|
|
async function runDriver(cmd: string[], mode: string): Promise<RunResult> {
|
||
|
|
await using proc = Bun.spawn(cmd, {
|
||
|
|
env: { ...bunEnv, SYNTAX_FIXTURE_COUNT: String(exitCode0.length), SYNTAX_FIXTURE_MODE: mode },
|
||
|
|
cwd: fixturePath,
|
||
|
|
stdout: "pipe",
|
||
|
|
stderr: "pipe",
|
||
|
|
stdin: "ignore",
|
||
|
|
});
|
||
|
|
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
|
||
|
|
const results = new Map<number, string>();
|
||
|
|
for (const line of stdout.split("\n")) {
|
||
|
|
const m = /^(PASS|FAIL) (\d+)(?: (.*))?$/.exec(line);
|
||
|
|
if (m) results.set(Number(m[2]), m[1] === "PASS" ? "PASS" : `FAIL: ${m[3] ?? ""}`);
|
||
|
|
}
|
||
|
|
return { results, stdout, stderr, exitCode };
|
||
|
|
}
|
||
|
|
|
||
|
|
describe("exit code 0", () => {
|
||
|
|
let file: RunResult;
|
||
|
|
let data: RunResult;
|
||
|
|
|
||
|
|
beforeAll(async () => {
|
||
|
|
[file, data] = await Promise.all([
|
||
|
|
runDriver([bunExe(), "driver.mjs"], "file"),
|
||
|
|
runDriver([bunExe(), "--eval", driver], "data"),
|
||
|
|
]);
|
||
|
|
});
|
||
|
|
|
||
|
|
for (let i = 0; i < exitCode0.length; i++) {
|
||
|
|
const source = exitCode0[i];
|
||
|
|
|
||
|
|
test(`file #${i}: ${JSON.stringify(source)}`, () => {
|
||
|
|
const result = file.results.get(i);
|
||
|
|
if (result !== "PASS") {
|
||
|
|
// Surface stderr and exit code when the fixture failed or the driver crashed before reaching it.
|
||
|
|
expect({ result, stderr: file.stderr, exitCode: file.exitCode }).toEqual({
|
||
|
|
result: "PASS",
|
||
|
|
stderr: "",
|
||
|
|
exitCode: 0,
|
||
|
|
});
|
||
|
|
}
|
||
|
|
expect(result).toBe("PASS");
|
||
|
|
});
|
||
|
|
|
||
|
|
test(`eval #${i}: ${JSON.stringify(source)}`, () => {
|
||
|
|
const result = data.results.get(i);
|
||
|
|
if (result !== "PASS") {
|
||
|
|
expect({ result, stderr: data.stderr, exitCode: data.exitCode }).toEqual({
|
||
|
|
result: "PASS",
|
||
|
|
stderr: "",
|
||
|
|
exitCode: 0,
|
||
|
|
});
|
||
|
|
}
|
||
|
|
expect(result).toBe("PASS");
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
test("file driver: clean exit", () => {
|
||
|
|
expect(file.stderr).toBe("");
|
||
|
|
expect(file.stdout.trimEnd().split("\n").at(-1)).toBe(`DONE ${exitCode0.length}`);
|
||
|
|
expect(file.exitCode).toBe(0);
|
||
|
|
});
|
||
|
|
|
||
|
|
test("eval driver: clean exit", () => {
|
||
|
|
expect(data.stderr).toBe("");
|
||
|
|
expect(data.stdout.trimEnd().split("\n").at(-1)).toBe(`DONE ${exitCode0.length}`);
|
||
|
|
expect(data.exitCode).toBe(0);
|
||
|
|
});
|
||
|
|
});
|