aboutsummaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to 'test')
-rw-r--r--test/bun.js/transpiler.test.js429
1 files changed, 429 insertions, 0 deletions
diff --git a/test/bun.js/transpiler.test.js b/test/bun.js/transpiler.test.js
index 76356d90f..2be937d78 100644
--- a/test/bun.js/transpiler.test.js
+++ b/test/bun.js/transpiler.test.js
@@ -1680,6 +1680,435 @@ class Foo {
expectPrinted("a = !(b, c)", "a = (b , !c)");
});
+ it("substitution", () => {
+ var transpiler = new Bun.Transpiler({
+ inline: true,
+ platform: "bun",
+ allowBunRuntime: false,
+ });
+ function check(input, output) {
+ expect(
+ transpiler
+ .transformSync("export function hello() {\n" + input + "\n}")
+ .trim()
+ .replaceAll(/^ /gm, ""),
+ ).toBe(
+ "export function hello() {\n" +
+ output +
+ "\n}".replaceAll(/^ /gm, ""),
+ );
+ }
+ check("var x = 1; return x", "var x = 1;\nreturn x;");
+ check("let x = 1; return x", "return 1;");
+ check("const x = 1; return x", "return 1;");
+
+ check("let x = 1; if (false) x++; return x", "return 1;");
+ // TODO: comma operator
+ // check("let x = 1; if (true) x++; return x", "let x = 1;\nreturn x++, x;");
+ check("let x = 1; return x + x", "let x = 1;\nreturn x + x;");
+
+ // Can substitute into normal unary operators
+ check("let x = 1; return +x", "return 1;");
+ check("let x = 1; return -x", "return -1;");
+ check("let x = 1; return !x", "return false;");
+ check("let x = 1; return ~x", "return ~1;");
+ // TODO: remove needless return undefined;
+ // check("let x = 1; return void x", "let x = 1;");
+
+ // esbuild does this:
+ // check("let x = 1; return typeof x", "return typeof 1;");
+ // we do:
+ check("let x = 1; return typeof x", 'return "number";');
+
+ // Check substituting a side-effect free value into normal binary operators
+ // esbuild does this:
+ // check("let x = 1; return x + 2", "return 1 + 2;");
+ // we do:
+ check("let x = 1; return x + 2", "return 3;");
+ check("let x = 1; return 2 + x", "return 3;");
+ check("let x = 1; return x + arg0", "return 1 + arg0;");
+ // check("let x = 1; return arg0 + x", "return arg0 + 1;");
+ check("let x = 1; return x + fn()", "return 1 + fn();");
+ check("let x = 1; return fn() + x", "let x = 1;\nreturn fn() + x;");
+ check("let x = 1; return x + undef", "return 1 + undef;");
+ check("let x = 1; return undef + x", "let x = 1;\nreturn undef + x;");
+
+ // Check substituting a value with side-effects into normal binary operators
+ check("let x = fn(); return x + 2", "return fn() + 2;");
+ check("let x = fn(); return 2 + x", "return 2 + fn();");
+ check("let x = fn(); return x + arg0", "return fn() + arg0;");
+ check("let x = fn(); return arg0 + x", "let x = fn();\nreturn arg0 + x;");
+ check("let x = fn(); return x + fn2()", "return fn() + fn2();");
+ check(
+ "let x = fn(); return fn2() + x",
+ "let x = fn();\nreturn fn2() + x;",
+ );
+ check("let x = fn(); return x + undef", "return fn() + undef;");
+ check(
+ "let x = fn(); return undef + x",
+ "let x = fn();\nreturn undef + x;",
+ );
+
+ // Cannot substitute into mutating unary operators
+ check("let x = 1; ++x", "let x = 1;\n++x;");
+ check("let x = 1; --x", "let x = 1;\n--x;");
+ check("let x = 1; x++", "let x = 1;\nx++;");
+ check("let x = 1; x--", "let x = 1;\nx--;");
+ check("let x = 1; delete x", "let x = 1;\ndelete x;");
+
+ // Cannot substitute into mutating binary operators
+ check("let x = 1; x = 2", "let x = 1;\nx = 2;");
+ check("let x = 1; x += 2", "let x = 1;\nx += 2;");
+ check("let x = 1; x ||= 2", "let x = 1;\nx ||= 2;");
+
+ // Can substitute past mutating binary operators when the left operand has no side effects
+ // check("let x = 1; arg0 = x", "arg0 = 1;");
+ // check("let x = 1; arg0 += x", "arg0 += 1;");
+ // check("let x = 1; arg0 ||= x", "arg0 ||= 1;");
+ // check("let x = fn(); arg0 = x", "arg0 = fn();");
+ // check("let x = fn(); arg0 += x", "let x = fn();\narg0 += x;");
+ // check("let x = fn(); arg0 ||= x", "let x = fn();\narg0 ||= x;");
+
+ // Cannot substitute past mutating binary operators when the left operand has side effects
+ check("let x = 1; y.z = x", "let x = 1;\ny.z = x;");
+ check("let x = 1; y.z += x", "let x = 1;\ny.z += x;");
+ check("let x = 1; y.z ||= x", "let x = 1;\ny.z ||= x;");
+ check("let x = fn(); y.z = x", "let x = fn();\ny.z = x;");
+ check("let x = fn(); y.z += x", "let x = fn();\ny.z += x;");
+ check("let x = fn(); y.z ||= x", "let x = fn();\ny.z ||= x;");
+
+ // TODO:
+ // Can substitute code without side effects into branches
+ // check("let x = arg0; return x ? y : z;", "return arg0 ? y : z;");
+ // check("let x = arg0; return arg1 ? x : y;", "return arg1 ? arg0 : y;");
+ // check("let x = arg0; return arg1 ? y : x;", "return arg1 ? y : arg0;");
+ // check("let x = arg0; return x || y;", "return arg0 || y;");
+ // check("let x = arg0; return x && y;", "return arg0 && y;");
+ // check("let x = arg0; return x ?? y;", "return arg0 ?? y;");
+ // check("let x = arg0; return arg1 || x;", "return arg1 || arg0;");
+ // check("let x = arg0; return arg1 && x;", "return arg1 && arg0;");
+ // check("let x = arg0; return arg1 ?? x;", "return arg1 ?? arg0;");
+
+ // Can substitute code without side effects into branches past an expression with side effects
+ // check(
+ // "let x = arg0; return y ? x : z;",
+ // "let x = arg0;\nreturn y ? x : z;",
+ // );
+ // check(
+ // "let x = arg0; return y ? z : x;",
+ // "let x = arg0;\nreturn y ? z : x;",
+ // );
+ // check("let x = arg0; return (arg1 ? 1 : 2) ? x : 3;", "return arg0;");
+ // check(
+ // "let x = arg0; return (arg1 ? 1 : 2) ? 3 : x;",
+ // "let x = arg0;\nreturn 3;",
+ // );
+ // check(
+ // "let x = arg0; return (arg1 ? y : 1) ? x : 2;",
+ // "let x = arg0;\nreturn !arg1 || y ? x : 2;",
+ // );
+ // check(
+ // "let x = arg0; return (arg1 ? 1 : y) ? x : 2;",
+ // "let x = arg0;\nreturn arg1 || y ? x : 2;",
+ // );
+ // check(
+ // "let x = arg0; return (arg1 ? y : 1) ? 2 : x;",
+ // "let x = arg0;\nreturn !arg1 || y ? 2 : x;",
+ // );
+ // check(
+ // "let x = arg0; return (arg1 ? 1 : y) ? 2 : x;",
+ // "let x = arg0;\nreturn arg1 || y ? 2 : x;",
+ // );
+ // check("let x = arg0; return y || x;", "let x = arg0;\nreturn y || x;");
+ // check("let x = arg0; return y && x;", "let x = arg0;\nreturn y && x;");
+ // check("let x = arg0; return y ?? x;", "let x = arg0;\nreturn y ?? x;");
+
+ // Cannot substitute code with side effects into branches
+ check("let x = fn(); return x ? arg0 : y;", "return fn() ? arg0 : y;");
+ check(
+ "let x = fn(); return arg0 ? x : y;",
+ "let x = fn();\nreturn arg0 ? x : y;",
+ );
+ check(
+ "let x = fn(); return arg0 ? y : x;",
+ "let x = fn();\nreturn arg0 ? y : x;",
+ );
+ check("let x = fn(); return x || arg0;", "return fn() || arg0;");
+ check("let x = fn(); return x && arg0;", "return fn() && arg0;");
+ check("let x = fn(); return x ?? arg0;", "return fn() ?? arg0;");
+ check(
+ "let x = fn(); return arg0 || x;",
+ "let x = fn();\nreturn arg0 || x;",
+ );
+ check(
+ "let x = fn(); return arg0 && x;",
+ "let x = fn();\nreturn arg0 && x;",
+ );
+ check(
+ "let x = fn(); return arg0 ?? x;",
+ "let x = fn();\nreturn arg0 ?? x;",
+ );
+
+ // Test chaining
+ check(
+ "let x = fn(); let y = x[prop]; let z = y.val; throw z",
+ "throw fn()[prop].val;",
+ );
+ check(
+ "let x = fn(), y = x[prop], z = y.val; throw z",
+ "throw fn()[prop].val;",
+ );
+
+ // Can substitute an initializer with side effects
+ check("let x = 0; let y = ++x; return y", "let x = 0;\nreturn ++x;");
+
+ // Can substitute an initializer without side effects past an expression without side effects
+ check(
+ "let x = 0; let y = x; return [x, y]",
+ "let x = 0;\nreturn [x, x];",
+ );
+
+ // TODO: merge s_local
+ // Cannot substitute an initializer with side effects past an expression without side effects
+ // check(
+ // "let x = 0; let y = ++x; return [x, y]",
+ // "let x = 0, y = ++x;\nreturn [x, y];",
+ // );
+
+ // Cannot substitute an initializer without side effects past an expression with side effects
+ // TODO: merge s_local
+ // check(
+ // "let x = 0; let y = {valueOf() { x = 1 }}; let z = x; return [y == 1, z]",
+ // "let x = 0, y = { valueOf() {\n x = 1;\n} }, z = x;\nreturn [y == 1, z];",
+ // );
+
+ // Cannot inline past a spread operator, since that evaluates code
+ check("let x = arg0; return [...x];", "return [...arg0];");
+ check("let x = arg0; return [x, ...arg1];", "return [arg0, ...arg1];");
+ check(
+ "let x = arg0; return [...arg1, x];",
+ "let x = arg0;\nreturn [...arg1, x];",
+ );
+ // TODO: preserve call here
+ // check("let x = arg0; return arg1(...x);", "return arg1(...arg0);");
+ // check(
+ // "let x = arg0; return arg1(x, ...arg1);",
+ // "return arg1(arg0, ...arg1);",
+ // );
+ check(
+ "let x = arg0; return arg1(...arg1, x);",
+ "let x = arg0;\nreturn arg1(...arg1, x);",
+ );
+
+ // Test various statement kinds
+ // TODO:
+ // check("let x = arg0; arg1(x);", "arg1(arg0);");
+
+ check("let x = arg0; throw x;", "throw arg0;");
+ check("let x = arg0; return x;", "return arg0;");
+ check("let x = arg0; if (x) return 1;", "if (arg0)\n return 1;");
+ check(
+ "let x = arg0; switch (x) { case 0: return 1; }",
+ "switch (arg0) {\n case 0:\n return 1;\n}",
+ );
+ check(
+ "let x = arg0; let y = x; return y + y;",
+ "let y = arg0;\nreturn y + y;",
+ );
+
+ // Loops must not be substituted into because they evaluate multiple times
+ check(
+ "let x = arg0; do {} while (x);",
+ "let x = arg0;\ndo\n ;\nwhile (x);",
+ );
+
+ // TODO: convert while(x) to for (;x;)
+ check(
+ "let x = arg0; while (x) return 1;",
+ "let x = arg0;\nwhile (x)\n return 1;",
+ // "let x = arg0;\nfor (; x; )\n return 1;",
+ );
+ check(
+ "let x = arg0; for (; x; ) return 1;",
+ "let x = arg0;\nfor (;x; )\n return 1;",
+ );
+
+ // Can substitute an expression without side effects into a branch due to optional chaining
+ // TODO:
+ // check("let x = arg0; return arg1?.[x];", "return arg1?.[arg0];");
+ // check("let x = arg0; return arg1?.(x);", "return arg1?.(arg0);");
+
+ // Cannot substitute an expression with side effects into a branch due to optional chaining,
+ // since that would change the expression with side effects from being unconditionally
+ // evaluated to being conditionally evaluated, which is a behavior change
+ check(
+ "let x = fn(); return arg1?.[x];",
+ "let x = fn();\nreturn arg1?.[x];",
+ );
+ check(
+ "let x = fn(); return arg1?.(x);",
+ "let x = fn();\nreturn arg1?.(x);",
+ );
+
+ // Can substitute an expression past an optional chaining operation, since it has side effects
+ check(
+ "let x = arg0; return arg1?.a === x;",
+ "let x = arg0;\nreturn arg1?.a === x;",
+ );
+ check(
+ "let x = arg0; return arg1?.[0] === x;",
+ "let x = arg0;\nreturn arg1?.[0] === x;",
+ );
+ check(
+ "let x = arg0; return arg1?.(0) === x;",
+ "let x = arg0;\nreturn arg1?.(0) === x;",
+ );
+ check(
+ "let x = arg0; return arg1?.a[x];",
+ "let x = arg0;\nreturn arg1?.a[x];",
+ );
+ check(
+ "let x = arg0; return arg1?.a(x);",
+ "let x = arg0;\nreturn arg1?.a(x);",
+ );
+ // TODO:
+ // check(
+ // "let x = arg0; return arg1?.[a][x];",
+ // "let x = arg0;\nreturn arg1?.[a][x];",
+ // );
+ check(
+ "let x = arg0; return arg1?.[a](x);",
+ "let x = arg0;\nreturn (arg1?.[a])(x);",
+ );
+ check(
+ "let x = arg0; return arg1?.(a)[x];",
+ "let x = arg0;\nreturn (arg1?.(a))[x];",
+ );
+ check(
+ "let x = arg0; return arg1?.(a)(x);",
+ "let x = arg0;\nreturn (arg1?.(a))(x);",
+ );
+
+ // Can substitute into an object as long as there are no side effects
+ // beforehand. Note that computed properties must call "toString()" which
+ // can have side effects.
+ check("let x = arg0; return {x};", "return { x: arg0 };");
+ check(
+ "let x = arg0; return {x: y, y: x};",
+ "let x = arg0;\nreturn { x: y, y: x };",
+ );
+ // TODO:
+ // check(
+ // "let x = arg0; return {x: arg1, y: x};",
+ // "return { x: arg1, y: arg0 };",
+ // );
+ check("let x = arg0; return {[x]: 0};", "return { [arg0]: 0 };");
+ check(
+ "let x = arg0; return {[y]: x};",
+ "let x = arg0;\nreturn { [y]: x };",
+ );
+ check(
+ "let x = arg0; return {[arg1]: x};",
+ "let x = arg0;\nreturn { [arg1]: x };",
+ );
+ // TODO:
+ // check(
+ // "let x = arg0; return {y() {}, x};",
+ // "return { y() {\n}, x: arg0 };",
+ // );
+ check(
+ "let x = arg0; return {[y]() {}, x};",
+ "let x = arg0;\nreturn { [y]() {\n}, x };",
+ );
+ check("let x = arg0; return {...x};", "return { ...arg0 };");
+ check("let x = arg0; return {...x, y};", "return { ...arg0, y };");
+ check("let x = arg0; return {x, ...y};", "return { x: arg0, ...y };");
+ check(
+ "let x = arg0; return {...y, x};",
+ "let x = arg0;\nreturn { ...y, x };",
+ );
+
+ // TODO:
+ // Check substitutions into template literals
+ // check("let x = arg0; return `a${x}b${y}c`;", "return `a${arg0}b${y}c`;");
+ // check(
+ // "let x = arg0; return `a${y}b${x}c`;",
+ // "let x = arg0;\nreturn `a${y}b${x}c`;",
+ // );
+ // check(
+ // "let x = arg0; return `a${arg1}b${x}c`;",
+ // "return `a${arg1}b${arg0}c`;",
+ // );
+ // check("let x = arg0; return x`y`;", "return arg0`y`;");
+ // check(
+ // "let x = arg0; return y`a${x}b`;",
+ // "let x = arg0;\nreturn y`a${x}b`;",
+ // );
+ // check("let x = arg0; return arg1`a${x}b`;", "return arg1`a${arg0}b`;");
+ // check("let x = 'x'; return `a${x}b`;", "return `axb`;");
+
+ // Check substitutions into import expressions
+ // TODO:
+ // check("let x = arg0; return import(x);", "return import(arg0);");
+ // check(
+ // "let x = arg0; return [import(y), x];",
+ // "let x = arg0;\nreturn [import(y), x];",
+ // );
+ // check(
+ // "let x = arg0; return [import(arg1), x];",
+ // "return [import(arg1), arg0];",
+ // );
+
+ // Check substitutions into await expressions
+ check(
+ "return async () => { let x = arg0; await x; };",
+ "return async () => {\n await arg0;\n};",
+ );
+
+ // TODO: combine with comma operator
+ // check(
+ // "return async () => { let x = arg0; await y; return x; };",
+ // "return async () => {\n let x = arg0;\n return await y, x;\n};",
+ // );
+ // check(
+ // "return async () => { let x = arg0; await arg1; return x; };",
+ // "return async () => {\n let x = arg0;\n return await arg1, x;\n};",
+ // );
+
+ // Check substitutions into yield expressions
+ check(
+ "return function* () { let x = arg0; yield x; };",
+ "return function* () {\n yield arg0;\n};",
+ );
+ // TODO: combine with comma operator
+ // check(
+ // "return function* () { let x = arg0; yield; return x; };",
+ // "return function* () {\n let x = arg0;\n yield ; \n return x;\n};",
+ // );
+ // check(
+ // "return function* () { let x = arg0; yield y; return x; };",
+ // "return function* () {\n let x = arg0;\n return yield y, x;\n};",
+ // );
+ // check(
+ // "return function* () { let x = arg0; yield arg1; return x; };",
+ // "return function* () {\n let x = arg0;\n return yield arg1, x;\n};",
+ // );
+
+ // Cannot substitute into call targets when it would change "this"
+ check("let x = arg0; x()", "arg0();");
+ // check("let x = arg0; (0, x)()", "arg0();");
+ check("let x = arg0.foo; x.bar()", "arg0.foo.bar();");
+ check("let x = arg0.foo; x[bar]()", "arg0.foo[bar]();");
+ check("let x = arg0.foo; x()", "let x = arg0.foo;\nx();");
+ check("let x = arg0[foo]; x()", "let x = arg0[foo];\nx();");
+ check("let x = arg0?.foo; x()", "let x = arg0?.foo;\nx();");
+ check("let x = arg0?.[foo]; x()", "let x = arg0?.[foo];\nx();");
+ // check("let x = arg0.foo; (0, x)()", "let x = arg0.foo;\nx();");
+ // check("let x = arg0[foo]; (0, x)()", "let x = arg0[foo];\nx();");
+ // check("let x = arg0?.foo; (0, x)()", "let x = arg0?.foo;\nx();");
+ // check("let x = arg0?.[foo]; (0, x)()", "let x = arg0?.[foo];\nx();");
+ });
+
it("constant folding", () => {
expectPrinted("1 && 2", "2");
expectPrinted("1 || 2", "1");