aboutsummaryrefslogtreecommitdiff
path: root/test/bun.js/transpiler.test.js
diff options
context:
space:
mode:
Diffstat (limited to 'test/bun.js/transpiler.test.js')
-rw-r--r--test/bun.js/transpiler.test.js1675
1 files changed, 1675 insertions, 0 deletions
diff --git a/test/bun.js/transpiler.test.js b/test/bun.js/transpiler.test.js
new file mode 100644
index 000000000..f8da4c18c
--- /dev/null
+++ b/test/bun.js/transpiler.test.js
@@ -0,0 +1,1675 @@
+import { expect, it, describe } from "bun:test";
+
+describe("Bun.Transpiler", () => {
+ describe("exports.replace", () => {
+ const transpiler = new Bun.Transpiler({
+ exports: {
+ replace: {
+ // export var foo = function() { }
+ // =>
+ // export var foo = "bar";
+ foo: "bar",
+
+ // export const getStaticProps = /* code */
+ // =>
+ // export var __N_SSG = true;
+ getStaticProps: ["__N_SSG", true],
+ getStaticPaths: ["__N_SSG", true],
+ // export function getStaticProps(ctx) { /* code */ }
+ // =>
+ // export var __N_SSP = true;
+ getServerSideProps: ["__N_SSP", true],
+ },
+
+ // Explicitly remove the top-level export, even if it is in use by
+ // another part of the file
+ eliminate: ["loader", "localVarToRemove"],
+ },
+ /* only per-file for now, so this isn't good yet */
+ treeShaking: true,
+
+ // remove non-bare unused exports, even if they may have side effects
+ // Consistent with tsc & esbuild, this is enabled by default for TypeScript files
+ // this flag lets you enable it for JavaScript files
+ // this already existed, just wasn't exposed in the API
+ trimUnusedImports: true,
+ });
+
+ it("a deletes dead exports and any imports only referenced in dead regions", () => {
+ const out = transpiler.transformSync(`
+ import {getUserById} from './my-database';
+
+ export async function getStaticProps(ctx){
+ return { props: { user: await getUserById(ctx.params.id) } };
+ }
+
+ export default function MyComponent({user}) {
+ getStaticProps();
+ return <div id='user'>{user.name}</div>;
+ }
+ `);
+ });
+
+ it("deletes dead exports and any imports only referenced in dead regions", () => {
+ const output = transpiler.transformSync(`
+ import deadFS from 'fs';
+ import liveFS from 'fs';
+
+ export var deleteMe = 100;
+
+ export function loader() {
+ deadFS.readFileSync("/etc/passwd");
+ liveFS.readFileSync("/etc/passwd");
+ }
+
+ export function action() {
+ require("foo");
+ liveFS.readFileSync("/etc/passwd")
+ deleteMe = 101;
+ }
+
+ export function baz() {
+ require("bar");
+ }
+ `);
+ expect(output.includes("loader")).toBe(false);
+ expect(output.includes("react")).toBe(false);
+ expect(output.includes("action")).toBe(true);
+ expect(output.includes("deadFS")).toBe(false);
+ expect(output.includes("liveFS")).toBe(true);
+ });
+
+ it("supports replacing exports", () => {
+ const output = transpiler.transformSync(`
+ import deadFS from 'fs';
+ import anotherDeadFS from 'fs';
+ import liveFS from 'fs';
+
+ export var localVarToRemove = deadFS.readFileSync("/etc/passwd");
+ export var localVarToReplace = 1;
+
+ var getStaticProps = function () {
+ deadFS.readFileSync("/etc/passwd")
+ };
+
+ export {getStaticProps}
+
+ export function baz() {
+ liveFS.readFileSync("/etc/passwd");
+ require("bar");
+ }
+ `);
+ expect(output.includes("loader")).toBe(false);
+ expect(output.includes("react")).toBe(false);
+ expect(output.includes("deadFS")).toBe(false);
+ expect(output.includes("default")).toBe(false);
+ expect(output.includes("anotherDeadFS")).toBe(false);
+ expect(output.includes("liveFS")).toBe(true);
+ expect(output.includes("__N_SSG")).toBe(true);
+ expect(output.includes("localVarToReplace")).toBe(true);
+ expect(output.includes("localVarToRemove")).toBe(false);
+ });
+ });
+
+ const transpiler = new Bun.Transpiler({
+ loader: "tsx",
+ define: {
+ "process.env.NODE_ENV": JSON.stringify("development"),
+ user_undefined: "undefined",
+ },
+ macro: {
+ react: {
+ bacon: `${import.meta.dir}/macro-check.js`,
+ },
+ },
+ platform: "browser",
+ });
+ const bunTranspiler = new Bun.Transpiler({
+ loader: "tsx",
+ define: {
+ "process.env.NODE_ENV": JSON.stringify("development"),
+ user_undefined: "undefined",
+ },
+ platform: "bun",
+ macro: {
+ inline: {
+ whatDidIPass: `${import.meta.dir}/inline.macro.js`,
+ },
+ react: {
+ bacon: `${import.meta.dir}/macro-check.js`,
+ },
+ },
+ });
+
+ const code = `import { useParams } from "remix";
+ import type { LoaderFunction, ActionFunction } from "remix";
+ import { type xx } from 'mod';
+ import { type xx as yy } from 'mod';
+ import { type 'xx' as yy } from 'mod';
+ import { type if as yy } from 'mod';
+ import React, { type ReactNode, Component as Romponent, Component } from 'react';
+
+
+ export const loader: LoaderFunction = async ({
+ params
+ }) => {
+ console.log(params.postId);
+ };
+
+ export const action: ActionFunction = async ({
+ params
+ }) => {
+ console.log(params.postId);
+ };
+
+ export default function PostRoute() {
+ const params = useParams();
+ console.log(params.postId);
+ }
+
+
+
+
+
+ `;
+
+ it("JSX", () => {
+ var bun = new Bun.Transpiler({
+ loader: "jsx",
+ define: {
+ "process.env.NODE_ENV": JSON.stringify("development"),
+ },
+ });
+ expect(bun.transformSync("export var foo = <div foo />")).toBe(
+ `export var foo = jsx("div", {
+ foo: true
+}, undefined, false, undefined, this);
+`
+ );
+ expect(bun.transformSync("export var foo = <div foo={foo} />")).toBe(
+ `export var foo = jsx("div", {
+ foo
+}, undefined, false, undefined, this);
+`
+ );
+ expect(bun.transformSync("export var foo = <div {...foo} />")).toBe(
+ `export var foo = jsx("div", {
+ ...foo
+}, undefined, false, undefined, this);
+`
+ );
+
+ expect(bun.transformSync("export var hi = <div {foo} />")).toBe(
+ `export var hi = jsx("div", {
+ foo
+}, undefined, false, undefined, this);
+`
+ );
+ expect(bun.transformSync("export var hi = <div {foo.bar.baz} />")).toBe(
+ `export var hi = jsx("div", {
+ baz: foo.bar.baz
+}, undefined, false, undefined, this);
+`
+ );
+ expect(bun.transformSync("export var hi = <div {foo?.bar?.baz} />")).toBe(
+ `export var hi = jsx("div", {
+ baz: foo?.bar?.baz
+}, undefined, false, undefined, this);
+`
+ );
+ expect(
+ bun.transformSync("export var hi = <div {foo['baz'].bar?.baz} />")
+ ).toBe(
+ `export var hi = jsx("div", {
+ baz: foo["baz"].bar?.baz
+}, undefined, false, undefined, this);
+`
+ );
+
+ // cursed
+ expect(
+ bun.transformSync(
+ "export var hi = <div {foo[{name: () => true}.name].hi} />"
+ )
+ ).toBe(
+ `export var hi = jsx("div", {
+ hi: foo[{ name: () => true }.name].hi
+}, undefined, false, undefined, this);
+`
+ );
+ expect(
+ bun.transformSync("export var hi = <Foo {process.env.NODE_ENV} />")
+ ).toBe(
+ `export var hi = jsx(Foo, {
+ NODE_ENV: "development"
+}, undefined, false, undefined, this);
+`
+ );
+
+ expect(
+ bun.transformSync("export var hi = <div {foo['baz'].bar?.baz} />")
+ ).toBe(
+ `export var hi = jsx("div", {
+ baz: foo["baz"].bar?.baz
+}, undefined, false, undefined, this);
+`
+ );
+ try {
+ bun.transformSync("export var hi = <div {foo}={foo}= />");
+ throw new Error("Expected error");
+ } catch (e) {
+ expect(e.errors[0].message.includes('Expected ">"')).toBe(true);
+ }
+
+ expect(
+ bun.transformSync("export var hi = <div {Foo}><Foo></Foo></div>")
+ ).toBe(
+ `export var hi = jsx("div", {
+ Foo,
+ children: jsx(Foo, {}, undefined, false, undefined, this)
+}, undefined, false, undefined, this);
+`
+ );
+ expect(
+ bun.transformSync("export var hi = <div {Foo}><Foo></Foo></div>")
+ ).toBe(
+ `export var hi = jsx("div", {
+ Foo,
+ children: jsx(Foo, {}, undefined, false, undefined, this)
+}, undefined, false, undefined, this);
+`
+ );
+
+ expect(bun.transformSync("export var hi = <div>{123}}</div>").trim()).toBe(
+ `export var hi = jsx("div", {
+ children: [
+ 123,
+ "}"
+ ]
+}, undefined, true, undefined, this);
+ `.trim()
+ );
+ });
+
+ describe("inline JSX", () => {
+ const inliner = new Bun.Transpiler({
+ loader: "tsx",
+ define: {
+ "process.env.NODE_ENV": JSON.stringify("production"),
+ user_undefined: "undefined",
+ },
+ platform: "bun",
+ jsxOptimizationInline: true,
+ treeShaking: false,
+ });
+
+ it("inlines static JSX into object literals", () => {
+ expect(
+ inliner
+ .transformSync(
+ `
+export var hi = <div>{123}</div>
+export var hiWithKey = <div key="hey">{123}</div>
+export var hiWithRef = <div ref={foo}>{123}</div>
+
+export var ComponentThatChecksDefaultProps = <Hello></Hello>
+export var ComponentThatChecksDefaultPropsAndHasChildren = <Hello>my child</Hello>
+export var ComponentThatHasSpreadCausesDeopt = <Hello {...spread} />
+
+`.trim()
+ )
+ .trim()
+ ).toBe(
+ `var $$typeof = Symbol.for("react.element");
+export var hi = {
+ $$typeof,
+ type: "div",
+ key: null,
+ ref: null,
+ props: {
+ children: 123
+ },
+ _owner: null
+};
+export var hiWithKey = {
+ $$typeof,
+ type: "div",
+ key: "hey",
+ ref: null,
+ props: {
+ children: 123
+ },
+ _owner: null
+};
+export var hiWithRef = jsx("div", {
+ ref: foo,
+ children: 123
+});
+export var ComponentThatChecksDefaultProps = {
+ $$typeof,
+ type: Hello,
+ key: null,
+ ref: null,
+ props: Hello.defaultProps || {},
+ _owner: null
+};
+export var ComponentThatChecksDefaultPropsAndHasChildren = {
+ $$typeof,
+ type: Hello,
+ key: null,
+ ref: null,
+ props: __merge({
+ children: "my child"
+ }, Hello.defaultProps),
+ _owner: null
+};
+export var ComponentThatHasSpreadCausesDeopt = jsx(Hello, {
+ ...spread
+});
+`.trim()
+ );
+ });
+ });
+
+ it("require with a dynamic non-string expression", () => {
+ var nodeTranspiler = new Bun.Transpiler({ platform: "node" });
+ expect(nodeTranspiler.transformSync("require('hi' + bar)")).toBe(
+ 'require("hi" + bar);\n'
+ );
+ });
+
+ it("CommonJS", () => {
+ var nodeTranspiler = new Bun.Transpiler({ platform: "node" });
+ expect(nodeTranspiler.transformSync("module.require('hi' + 123)")).toBe(
+ 'require("hi" + 123);\n'
+ );
+
+ expect(
+ nodeTranspiler.transformSync("module.require(1 ? 'foo' : 'bar')")
+ ).toBe('require("foo");\n');
+ expect(nodeTranspiler.transformSync("require(1 ? 'foo' : 'bar')")).toBe(
+ 'require("foo");\n'
+ );
+
+ expect(
+ nodeTranspiler.transformSync("module.require(unknown ? 'foo' : 'bar')")
+ ).toBe('unknown ? require("foo") : require("bar");\n');
+ });
+
+ describe("regressions", () => {
+ it("unexpected super", () => {
+ const input = `
+ 'use strict';
+
+ const ErrorReportingMixinBase = require('./mixin-base');
+ const PositionTrackingPreprocessorMixin = require('../position-tracking/preprocessor-mixin');
+ const Mixin = require('../../utils/mixin');
+
+ class ErrorReportingPreprocessorMixin extends ErrorReportingMixinBase {
+ constructor(preprocessor, opts) {
+ super(preprocessor, opts);
+
+ this.posTracker = Mixin.install(preprocessor, PositionTrackingPreprocessorMixin);
+ this.lastErrOffset = -1;
+ }
+
+ _reportError(code) {
+ //NOTE: avoid reporting error twice on advance/retreat
+ if (this.lastErrOffset !== this.posTracker.offset) {
+ this.lastErrOffset = this.posTracker.offset;
+ super._reportError(code);
+ }
+ }
+ }
+
+ module.exports = ErrorReportingPreprocessorMixin;
+
+
+`;
+ expect(transpiler.transformSync(input, "js").length > 0).toBe(true);
+ });
+ });
+
+ describe("scanImports", () => {
+ it("reports import paths, excluding types", () => {
+ const imports = transpiler.scanImports(code, "tsx");
+ expect(imports.filter(({ path }) => path === "remix")).toHaveLength(1);
+ expect(imports.filter(({ path }) => path === "mod")).toHaveLength(0);
+ expect(imports.filter(({ path }) => path === "react")).toHaveLength(1);
+ expect(imports).toHaveLength(2);
+ });
+ });
+
+ const parsed = (
+ code,
+ trim = true,
+ autoExport = false,
+ transpiler_ = transpiler
+ ) => {
+ if (autoExport) {
+ code = "export default (" + code + ")";
+ }
+
+ var out = transpiler_.transformSync(code, "js");
+ if (autoExport && out.startsWith("export default ")) {
+ out = out.substring("export default ".length);
+ }
+
+ if (trim) {
+ out = out.trim();
+
+ if (out.endsWith(";")) {
+ out = out.substring(0, out.length - 1);
+ }
+
+ return out.trim();
+ }
+
+ return out;
+ };
+
+ const expectPrinted = (code, out) => {
+ expect(parsed(code, true, true)).toBe(out);
+ };
+
+ const expectPrinted_ = (code, out) => {
+ expect(parsed(code, !out.endsWith(";\n"), false)).toBe(out);
+ };
+
+ const expectBunPrinted_ = (code, out) => {
+ expect(parsed(code, !out.endsWith(";\n"), false, bunTranspiler)).toBe(out);
+ };
+
+ const expectParseError = (code, message) => {
+ try {
+ parsed(code, false, false);
+ } catch (er) {
+ var err = er;
+ if (er instanceof AggregateError) {
+ err = err.errors[0];
+ }
+
+ expect(er.message).toBe(message);
+
+ return;
+ }
+
+ throw new Error("Expected parse error for code\n\t" + code);
+ };
+ const ts = {
+ parsed: (code, trim = true, autoExport = false) => {
+ if (autoExport) {
+ code = "export default (" + code + ")";
+ }
+
+ var out = transpiler.transformSync(code, "ts");
+ if (autoExport && out.startsWith("export default ")) {
+ out = out.substring("export default ".length);
+ }
+
+ if (trim) {
+ out = out.trim();
+
+ if (out.endsWith(";")) {
+ out = out.substring(0, out.length - 1);
+ }
+
+ return out.trim();
+ }
+
+ return out;
+ },
+
+ expectPrinted: (code, out) => {
+ expect(ts.parsed(code, true, true)).toBe(out);
+ },
+
+ expectPrinted_: (code, out) => {
+ expect(ts.parsed(code, !out.endsWith(";\n"), false)).toBe(out);
+ },
+
+ expectParseError: (code, message) => {
+ try {
+ ts.parsed(code, false, false);
+ } catch (er) {
+ var err = er;
+ if (er instanceof AggregateError) {
+ err = err.errors[0];
+ }
+
+ expect(er.message).toBe(message);
+
+ return;
+ }
+
+ throw new Error("Expected parse error for code\n\t" + code);
+ },
+ };
+
+ describe("parser", () => {
+ it("arrays", () => {
+ expectPrinted("[]", "[]");
+ expectPrinted("[,]", "[,]");
+ expectPrinted("[1]", "[1]");
+ expectPrinted("[1,]", "[1]");
+ expectPrinted("[,1]", "[, 1]");
+ expectPrinted("[1,2]", "[1, 2]");
+ expectPrinted("[,1,2]", "[, 1, 2]");
+ expectPrinted("[1,,2]", "[1, , 2]");
+ expectPrinted("[1,2,]", "[1, 2]");
+ expectPrinted("[1,2,,]", "[1, 2, ,]");
+ });
+
+ it("exponentiation", () => {
+ expectPrinted("(delete x) ** 0", "(delete x) ** 0");
+ expectPrinted("(delete x.prop) ** 0", "(delete x.prop) ** 0");
+ expectPrinted("(delete x[0]) ** 0", "(delete x[0]) ** 0");
+
+ expectPrinted("(delete x?.prop) ** 0", "(delete x?.prop) ** 0");
+
+ expectPrinted("(void x) ** 0", "(void x) ** 0");
+ expectPrinted("(typeof x) ** 0", "(typeof x) ** 0");
+ expectPrinted("(+x) ** 0", "(+x) ** 0");
+ expectPrinted("(-x) ** 0", "(-x) ** 0");
+ expectPrinted("(~x) ** 0", "(~x) ** 0");
+ expectPrinted("(!x) ** 0", "(!x) ** 0");
+ expectPrinted("(await x) ** 0", "(await x) ** 0");
+ expectPrinted("(await -x) ** 0", "(await -x) ** 0");
+
+ expectPrinted("--x ** 2", "--x ** 2");
+ expectPrinted("++x ** 2", "++x ** 2");
+ expectPrinted("x-- ** 2", "x-- ** 2");
+ expectPrinted("x++ ** 2", "x++ ** 2");
+
+ expectPrinted("(-x) ** 2", "(-x) ** 2");
+ expectPrinted("(+x) ** 2", "(+x) ** 2");
+ expectPrinted("(~x) ** 2", "(~x) ** 2");
+ expectPrinted("(!x) ** 2", "(!x) ** 2");
+ expectPrinted("(-1) ** 2", "(-1) ** 2");
+ expectPrinted("(+1) ** 2", "1 ** 2");
+ expectPrinted("(~1) ** 2", "(~1) ** 2");
+ expectPrinted("(!1) ** 2", "false ** 2");
+ expectPrinted("(void x) ** 2", "(void x) ** 2");
+ expectPrinted("(delete x) ** 2", "(delete x) ** 2");
+ expectPrinted("(typeof x) ** 2", "(typeof x) ** 2");
+ expectPrinted("undefined ** 2", "undefined ** 2");
+
+ expectParseError("-x ** 2", "Unexpected **");
+ expectParseError("+x ** 2", "Unexpected **");
+ expectParseError("~x ** 2", "Unexpected **");
+ expectParseError("!x ** 2", "Unexpected **");
+ expectParseError("void x ** 2", "Unexpected **");
+ expectParseError("delete x ** 2", "Unexpected **");
+ expectParseError("typeof x ** 2", "Unexpected **");
+
+ expectParseError("-x.y() ** 2", "Unexpected **");
+ expectParseError("+x.y() ** 2", "Unexpected **");
+ expectParseError("~x.y() ** 2", "Unexpected **");
+ expectParseError("!x.y() ** 2", "Unexpected **");
+ expectParseError("void x.y() ** 2", "Unexpected **");
+ expectParseError("delete x.y() ** 2", "Unexpected **");
+ expectParseError("typeof x.y() ** 2", "Unexpected **");
+
+ expectParseError("delete x ** 0", "Unexpected **");
+ expectParseError("delete x.prop ** 0", "Unexpected **");
+ expectParseError("delete x[0] ** 0", "Unexpected **");
+ expectParseError("delete x?.prop ** 0", "Unexpected **");
+ expectParseError("void x ** 0", "Unexpected **");
+ expectParseError("typeof x ** 0", "Unexpected **");
+ expectParseError("+x ** 0", "Unexpected **");
+ expectParseError("-x ** 0", "Unexpected **");
+ expectParseError("~x ** 0", "Unexpected **");
+ expectParseError("!x ** 0", "Unexpected **");
+ expectParseError("await x ** 0", "Unexpected **");
+ expectParseError("await -x ** 0", "Unexpected **");
+ });
+
+ it("await", () => {
+ expectPrinted("await x", "await x");
+ expectPrinted("await +x", "await +x");
+ expectPrinted("await -x", "await -x");
+ expectPrinted("await ~x", "await ~x");
+ expectPrinted("await !x", "await !x");
+ expectPrinted("await --x", "await --x");
+ expectPrinted("await ++x", "await ++x");
+ expectPrinted("await x--", "await x--");
+ expectPrinted("await x++", "await x++");
+ expectPrinted("await void x", "await void x");
+ expectPrinted("await typeof x", "await typeof x");
+ expectPrinted("await (x * y)", "await (x * y)");
+ expectPrinted("await (x ** y)", "await (x ** y)");
+
+ expectPrinted_(
+ "async function f() { await delete x }",
+ "async function f() {\n await delete x;\n}"
+ );
+
+ // expectParseError(
+ // "await delete x",
+ // "Delete of a bare identifier cannot be used in an ECMAScript module"
+ // );
+ });
+
+ it("import assert", () => {
+ expectPrinted_(
+ `import json from "./foo.json" assert { type: "json" };`,
+ `import json from "./foo.json"`
+ );
+ expectPrinted_(
+ `import json from "./foo.json";`,
+ `import json from "./foo.json"`
+ );
+ expectPrinted_(
+ `import("./foo.json", { type: "json" });`,
+ `import("./foo.json")`
+ );
+ });
+
+ it("import with unicode escape", () => {
+ expectPrinted_(
+ `import { name } from 'mod\\u1011';`,
+ `import {name} from "mod\\u1011"`
+ );
+ });
+
+ it("fold string addition", () => {
+ expectPrinted_(
+ `export const foo = "a" + "b";`,
+ `export const foo = "ab"`
+ );
+ expectPrinted_(
+ `export const foo = "F" + "0" + "F" + "0123456789" + "ABCDEF" + "0123456789ABCDEFF0123456789ABCDEF00" + "b";`,
+ `export const foo = "F0F0123456789ABCDEF0123456789ABCDEFF0123456789ABCDEF00b"`
+ );
+ expectPrinted_(
+ `export const foo = "a" + 1 + "b";`,
+ `export const foo = "a" + 1 + "b"`
+ );
+ expectPrinted_(
+ `export const foo = "a" + "b" + 1 + "b";`,
+ `export const foo = "ab" + 1 + "b"`
+ );
+ expectPrinted_(
+ `export const foo = "a" + "b" + 1 + "b" + "c";`,
+ `export const foo = "ab" + 1 + "bc"`
+ );
+ });
+
+ it("numeric constants", () => {
+ expectBunPrinted_("export const foo = 1 + 2", "export const foo = 3");
+ expectBunPrinted_("export const foo = 1 - 2", "export const foo = -1");
+ expectBunPrinted_("export const foo = 1 * 2", "export const foo = 2");
+ });
+
+ it("pass objects to macros", () => {
+ var object = {
+ helloooooooo: {
+ message: [12345],
+ },
+ };
+
+ const output = bunTranspiler.transformSync(
+ `
+ import {whatDidIPass} from 'inline';
+
+ export function foo() {
+ return whatDidIPass();
+ }
+ `,
+ object
+ );
+ expect(output).toBe(`export function foo() {
+ return {
+ helloooooooo: {
+ message: [
+ 12345
+ ]
+ }
+ };
+}
+`);
+ });
+
+ it("rewrite string to length", () => {
+ expectPrinted_(
+ `export const foo = "a".length + "b".length;`,
+ `export const foo = 1 + 1`
+ );
+ expectBunPrinted_(
+ `export const foo = "a".length + "b".length;`,
+ `export const foo = 2`
+ );
+ });
+
+ describe("Bun.js", () => {
+ it("require -> import.meta.require", () => {
+ expectBunPrinted_(
+ `export const foo = require('bar.node')`,
+ `export const foo = import.meta.require("bar.node")`
+ );
+ });
+
+ it("require.resolve -> import.meta.resolveSync", () => {
+ expectBunPrinted_(
+ `export const foo = require.resolve('bar.node')`,
+ `export const foo = import.meta.resolveSync("bar.node")`
+ );
+ });
+
+ it('require.resolve(path, {paths: ["blah"]}) -> import.meta.resolveSync', () => {
+ expectBunPrinted_(
+ `export const foo = require.resolve('bar.node', {paths: ["blah"]})`,
+ `export const foo = import.meta.resolveSync("bar.node", { paths: ["blah"] })`
+ );
+ });
+ });
+
+ describe("Browsers", () => {
+ it('require.resolve("my-module") -> "/resolved/my-module"', () => {
+ // the module resolver & linker doesn't run with Bun.Transpiler
+ // so in this test, it becomes the same path string
+ expectPrinted_(
+ `export const foo = require.resolve('my-module')`,
+ `export const foo = "my-module"`
+ );
+ });
+ });
+
+ it("define", () => {
+ expectPrinted_(
+ `export default typeof user_undefined === 'undefined';`,
+ `export default true`
+ );
+ expectPrinted_(
+ `export default typeof user_undefined !== 'undefined';`,
+ `export default false`
+ );
+
+ expectPrinted_(
+ `export default typeof user_undefined !== 'undefined';`,
+ `export default false`
+ );
+ expectPrinted_(`export default !user_undefined;`, `export default true`);
+ });
+
+ it("decls", () => {
+ // expectParseError("var x = 0", "");
+ // expectParseError("let x = 0", "");
+ // expectParseError("const x = 0", "");
+ // expectParseError("for (var x = 0;;) ;", "");
+ // expectParseError("for (let x = 0;;) ;", "");
+ // expectParseError("for (const x = 0;;) ;", "");
+
+ // expectParseError("for (var x in y) ;", "");
+ // expectParseError("for (let x in y) ;", "");
+ // expectParseError("for (const x in y) ;", "");
+ // expectParseError("for (var x of y) ;", "");
+ // expectParseError("for (let x of y) ;", "");
+ // expectParseError("for (const x of y) ;", "");
+
+ // expectParseError("var x", "");
+ // expectParseError("let x", "");
+ expectParseError("const x", 'The constant "x" must be initialized');
+ expectParseError("const {}", "This constant must be initialized");
+ expectParseError("const []", "This constant must be initialized");
+ // expectParseError("for (var x;;) ;", "");
+ // expectParseError("for (let x;;) ;", "");
+ expectParseError(
+ "for (const x;;) ;",
+ 'The constant "x" must be initialized'
+ );
+ expectParseError(
+ "for (const {};;) ;",
+ "This constant must be initialized"
+ );
+ expectParseError(
+ "for (const [];;) ;",
+ "This constant must be initialized"
+ );
+
+ // Make sure bindings are visited during parsing
+ expectPrinted_("var {[x]: y} = {}", "var { [x]: y } = {}");
+ expectPrinted_("var {...x} = {}", "var { ...x } = {}");
+
+ // Test destructuring patterns
+ expectPrinted_("var [...x] = []", "var [...x] = []");
+ expectPrinted_("var {...x} = {}", "var { ...x } = {}");
+
+ expectPrinted_(
+ "export var foo = ([...x] = []) => {}",
+ "export var foo = ([...x] = []) => {\n}"
+ );
+
+ expectPrinted_(
+ "export var foo = ({...x} = {}) => {}",
+ "export var foo = ({ ...x } = {}) => {\n}"
+ );
+
+ expectParseError("var [...x,] = []", 'Unexpected "," after rest pattern');
+ expectParseError("var {...x,} = {}", 'Unexpected "," after rest pattern');
+ expectParseError(
+ "export default function() { return ([...x,] = []) => {} }",
+ "Unexpected trailing comma after rest element"
+ );
+ expectParseError(
+ "({...x,} = {}) => {}",
+ "Unexpected trailing comma after rest element"
+ );
+
+ expectPrinted_("[b, ...c] = d", "[b, ...c] = d");
+ expectPrinted_("([b, ...c] = d)", "[b, ...c] = d");
+ expectPrinted_("({b, ...c} = d)", "({ b, ...c } = d)");
+ expectPrinted_("({a = b} = c)", "({ a = b } = c)");
+ expectPrinted_("({a: b = c} = d)", "({ a: b = c } = d)");
+ expectPrinted_("({a: b.c} = d)", "({ a: b.c } = d)");
+ expectPrinted_("[a = {}] = b", "[a = {}] = b");
+ expectPrinted_("[[...a, b].x] = c", "[[...a, b].x] = c");
+ expectPrinted_("[{...a, b}.x] = c", "[{ ...a, b }.x] = c");
+ expectPrinted_("({x: [...a, b].x} = c)", "({ x: [...a, b].x } = c)");
+ expectPrinted_("({x: {...a, b}.x} = c)", "({ x: { ...a, b }.x } = c)");
+ expectPrinted_("[x = [...a, b]] = c", "[x = [...a, b]] = c");
+ expectPrinted_("[x = {...a, b}] = c", "[x = { ...a, b }] = c");
+ expectPrinted_("({x = [...a, b]} = c)", "({ x = [...a, b] } = c)");
+ expectPrinted_("({x = {...a, b}} = c)", "({ x = { ...a, b } } = c)");
+
+ expectPrinted_("(x = y)", "x = y");
+ expectPrinted_("([] = [])", "[] = []");
+ expectPrinted_("({} = {})", "({} = {})");
+ expectPrinted_("([[]] = [[]])", "[[]] = [[]]");
+ expectPrinted_("({x: {}} = {x: {}})", "({ x: {} } = { x: {} })");
+ expectPrinted_("(x) = y", "x = y");
+ expectParseError("([]) = []", "Invalid assignment target");
+ expectParseError("({}) = {}", "Invalid assignment target");
+ expectParseError("[([])] = [[]]", "Invalid assignment target");
+ expectParseError("({x: ({})} = {x: {}})", "Invalid assignment target");
+ expectParseError(
+ "(([]) = []) => {}",
+ "Unexpected parentheses in binding pattern"
+ );
+ expectParseError(
+ "(({}) = {}) => {}",
+ "Unexpected parentheses in binding pattern"
+ );
+ expectParseError("function f(([]) = []) {}", "Parse error");
+ expectParseError(
+ "function f(({}) = {}) {}",
+ "Parse error"
+ // 'Expected identifier but found "("\n'
+ );
+
+ expectPrinted_("for (x in y) ;", "for (x in y) {\n}");
+ expectPrinted_("for ([] in y) ;", "for ([] in y) {\n}");
+ expectPrinted_("for ({} in y) ;", "for ({} in y) {\n}");
+ expectPrinted_("for ((x) in y) ;", "for (x in y) {\n}");
+ expectParseError("for (([]) in y) ;", "Invalid assignment target");
+ expectParseError("for (({}) in y) ;", "Invalid assignment target");
+
+ expectPrinted_("for (x of y) ;", "for (x of y) {\n}");
+ expectPrinted_("for ([] of y) ;", "for ([] of y) {\n}");
+ expectPrinted_("for ({} of y) ;", "for ({} of y) {\n}");
+ expectPrinted_("for ((x) of y) ;", "for (x of y) {\n}");
+ expectParseError("for (([]) of y) ;", "Invalid assignment target");
+ expectParseError("for (({}) of y) ;", "Invalid assignment target");
+
+ expectParseError("[[...a, b]] = c", 'Unexpected "," after rest pattern');
+ expectParseError("[{...a, b}] = c", 'Unexpected "," after rest pattern');
+ expectParseError(
+ "({x: [...a, b]} = c)",
+ 'Unexpected "," after rest pattern'
+ );
+ expectParseError(
+ "({x: {...a, b}} = c)",
+ 'Unexpected "," after rest pattern'
+ );
+ expectParseError("[b, ...c,] = d", 'Unexpected "," after rest pattern');
+ expectParseError("([b, ...c,] = d)", 'Unexpected "," after rest pattern');
+ expectParseError("({b, ...c,} = d)", 'Unexpected "," after rest pattern');
+ expectParseError("({a = b})", 'Unexpected "="');
+ expectParseError("({x = {a = b}} = c)", 'Unexpected "="');
+ expectParseError("[a = {b = c}] = d", 'Unexpected "="');
+
+ expectPrinted_(
+ "for ([{a = {}}] in b) {}",
+ "for ([{ a = {} }] in b) {\n}"
+ );
+ expectPrinted_(
+ "for ([{a = {}}] of b) {}",
+ "for ([{ a = {} }] of b) {\n}"
+ );
+ expectPrinted_("for ({a = {}} in b) {}", "for ({ a = {} } in b) {\n}");
+ expectPrinted_("for ({a = {}} of b) {}", "for ({ a = {} } of b) {\n}");
+
+ expectParseError("({a = {}} in b)", 'Unexpected "="');
+ expectParseError("[{a = {}}]\nof()", 'Unexpected "="');
+ expectParseError(
+ "for ([...a, b] in c) {}",
+ 'Unexpected "," after rest pattern'
+ );
+ expectParseError(
+ "for ([...a, b] of c) {}",
+ 'Unexpected "," after rest pattern'
+ );
+ });
+
+ it("regexp", () => {
+ expectPrinted("/x/g", "/x/g");
+ expectPrinted("/x/i", "/x/i");
+ expectPrinted("/x/m", "/x/m");
+ expectPrinted("/x/s", "/x/s");
+ expectPrinted("/x/u", "/x/u");
+ expectPrinted("/x/y", "/x/y");
+ expectPrinted("/gimme/g", "/gimme/g");
+ expectPrinted("/gimgim/g", "/gimgim/g");
+
+ expectParseError(
+ "/x/msuygig",
+ 'Duplicate flag "g" in regular expression'
+ );
+ });
+
+ it("identifier escapes", () => {
+ expectPrinted_("var _\u0076\u0061\u0072", "var _var");
+ expectParseError(
+ "var \u0076\u0061\u0072",
+ 'Expected identifier but found "\u0076\u0061\u0072"'
+ );
+ expectParseError(
+ "\\u0076\\u0061\\u0072 foo",
+ "Unexpected \\u0076\\u0061\\u0072"
+ );
+
+ expectPrinted_("foo._\u0076\u0061\u0072", "foo._var");
+ expectPrinted_("foo.\u0076\u0061\u0072", "foo.var");
+
+ // expectParseError("\u200Ca", 'Unexpected "\\u200c"');
+ // expectParseError("\u200Da", 'Unexpected "\\u200d"');
+ });
+ });
+
+ it("private identifiers", () => {
+ expectParseError("#foo", "Unexpected #foo");
+ expectParseError("#foo in this", "Unexpected #foo");
+ expectParseError("this.#foo", 'Expected identifier but found "#foo"');
+ expectParseError("this?.#foo", 'Expected identifier but found "#foo"');
+ expectParseError("({ #foo: 1 })", 'Expected identifier but found "#foo"');
+ expectParseError(
+ "class Foo { x = { #foo: 1 } }",
+ 'Expected identifier but found "#foo"'
+ );
+ expectParseError("class Foo { x = #foo }", 'Expected "in" but found "}"');
+ expectParseError(
+ "class Foo { #foo; foo() { delete this.#foo } }",
+ 'Deleting the private name "#foo" is forbidden'
+ );
+ expectParseError(
+ "class Foo { #foo; foo() { delete this?.#foo } }",
+ 'Deleting the private name "#foo" is forbidden'
+ );
+ expectParseError(
+ "class Foo extends Bar { #foo; foo() { super.#foo } }",
+ 'Expected identifier but found "#foo"'
+ );
+ expectParseError(
+ "class Foo { #foo = () => { for (#foo in this) ; } }",
+ "Unexpected #foo"
+ );
+ expectParseError(
+ "class Foo { #foo = () => { for (x = #foo in this) ; } }",
+ "Unexpected #foo"
+ );
+ expectPrinted_("class Foo { #foo }", "class Foo {\n #foo;\n}");
+ expectPrinted_("class Foo { #foo = 1 }", "class Foo {\n #foo = 1;\n}");
+ expectPrinted_(
+ "class Foo { #foo = #foo in this }",
+ "class Foo {\n #foo = #foo in this;\n}"
+ );
+ expectPrinted_(
+ "class Foo { #foo = #foo in (#bar in this); #bar }",
+ "class Foo {\n #foo = #foo in (#bar in this);\n #bar;\n}"
+ );
+ expectPrinted_(
+ "class Foo { #foo() {} }",
+ "class Foo {\n #foo() {\n }\n}"
+ );
+ expectPrinted_(
+ "class Foo { get #foo() {} }",
+ "class Foo {\n get #foo() {\n }\n}"
+ );
+ expectPrinted_(
+ "class Foo { set #foo(x) {} }",
+ "class Foo {\n set #foo(x) {\n }\n}"
+ );
+ expectPrinted_(
+ "class Foo { static #foo }",
+ "class Foo {\n static #foo;\n}"
+ );
+ expectPrinted_(
+ "class Foo { static #foo = 1 }",
+ "class Foo {\n static #foo = 1;\n}"
+ );
+ expectPrinted_(
+ "class Foo { static #foo() {} }",
+ "class Foo {\n static #foo() {\n }\n}"
+ );
+ expectPrinted_(
+ "class Foo { static get #foo() {} }",
+ "class Foo {\n static get #foo() {\n }\n}"
+ );
+ expectPrinted_(
+ "class Foo { static set #foo(x) {} }",
+ "class Foo {\n static set #foo(x) {\n }\n}"
+ );
+
+ expectParseError(
+ "class Foo { #foo = #foo in #bar in this; #bar }",
+ "Unexpected #bar"
+ );
+
+ expectParseError(
+ "class Foo { #constructor }",
+ 'Invalid field name "#constructor"'
+ );
+ expectParseError(
+ "class Foo { #constructor() {} }",
+ 'Invalid method name "#constructor"'
+ );
+ expectParseError(
+ "class Foo { static #constructor }",
+ 'Invalid field name "#constructor"'
+ );
+ expectParseError(
+ "class Foo { static #constructor() {} }",
+ 'Invalid method name "#constructor"'
+ );
+ expectParseError(
+ "class Foo { #\\u0063onstructor }",
+ 'Invalid field name "#constructor"'
+ );
+ expectParseError(
+ "class Foo { #\\u0063onstructor() {} }",
+ 'Invalid method name "#constructor"'
+ );
+ expectParseError(
+ "class Foo { static #\\u0063onstructor }",
+ 'Invalid field name "#constructor"'
+ );
+ expectParseError(
+ "class Foo { static #\\u0063onstructor() {} }",
+ 'Invalid method name "#constructor"'
+ );
+ const errorText = '"#foo" has already been declared';
+ expectParseError("class Foo { #foo; #foo }", errorText);
+ expectParseError("class Foo { #foo; static #foo }", errorText);
+ expectParseError("class Foo { static #foo; #foo }", errorText);
+ expectParseError("class Foo { #foo; #foo() {} }", errorText);
+ expectParseError("class Foo { #foo; get #foo() {} }", errorText);
+ expectParseError("class Foo { #foo; set #foo(x) {} }", errorText);
+ expectParseError("class Foo { #foo() {} #foo }", errorText);
+ expectParseError("class Foo { get #foo() {} #foo }", errorText);
+ expectParseError("class Foo { set #foo(x) {} #foo }", errorText);
+ expectParseError("class Foo { get #foo() {} get #foo() {} }", errorText);
+ expectParseError("class Foo { set #foo(x) {} set #foo(x) {} }", errorText);
+ expectParseError(
+ "class Foo { get #foo() {} set #foo(x) {} #foo }",
+ errorText
+ );
+ expectParseError(
+ "class Foo { set #foo(x) {} get #foo() {} #foo }",
+ errorText
+ );
+
+ expectPrinted_(
+ "class Foo { get #foo() {} set #foo(x) { this.#foo } }",
+ "class Foo {\n get #foo() {\n }\n set #foo(x) {\n this.#foo;\n }\n}"
+ );
+ expectPrinted_(
+ "class Foo { set #foo(x) { this.#foo } get #foo() {} }",
+ "class Foo {\n set #foo(x) {\n this.#foo;\n }\n get #foo() {\n }\n}"
+ );
+ expectPrinted_(
+ "class Foo { #foo } class Bar { #foo }",
+ "class Foo {\n #foo;\n}\n\nclass Bar {\n #foo;\n}"
+ );
+ expectPrinted_(
+ "class Foo { foo = this.#foo; #foo }",
+ "class Foo {\n foo = this.#foo;\n #foo;\n}"
+ );
+ expectPrinted_(
+ "class Foo { foo = this?.#foo; #foo }",
+ "class Foo {\n foo = this?.#foo;\n #foo;\n}"
+ );
+ expectParseError(
+ "class Foo { #foo } class Bar { foo = this.#foo }",
+ 'Private name "#foo" must be declared in an enclosing class'
+ );
+ expectParseError(
+ "class Foo { #foo } class Bar { foo = this?.#foo }",
+ 'Private name "#foo" must be declared in an enclosing class'
+ );
+ expectParseError(
+ "class Foo { #foo } class Bar { foo = #foo in this }",
+ 'Private name "#foo" must be declared in an enclosing class'
+ );
+
+ expectPrinted_(
+ `class Foo {
+ #if
+ #im() { return this.#im(this.#if) }
+ static #sf
+ static #sm() { return this.#sm(this.#sf) }
+ foo() {
+ return class {
+ #inner() {
+ return [this.#im, this?.#inner, this?.x.#if]
+ }
+ }
+ }
+}
+`,
+ `class Foo {
+ #if;
+ #im() {
+ return this.#im(this.#if);
+ }
+ static #sf;
+ static #sm() {
+ return this.#sm(this.#sf);
+ }
+ foo() {
+ return class {
+ #inner() {
+ return [this.#im, this?.#inner, this?.x.#if];
+ }
+ };
+ }
+}`
+ );
+ });
+
+ it("type only exports", () => {
+ let { expectPrinted_, expectParseError } = ts;
+ expectPrinted_("export type {foo, bar as baz} from 'bar'", "");
+ expectPrinted_("export type {foo, bar as baz}", "");
+ expectPrinted_("export type {foo} from 'bar'; x", "x");
+ expectPrinted_("export type {foo} from 'bar'\nx", "x");
+ expectPrinted_("export type {default} from 'bar'", "");
+ expectPrinted_(
+ "export { type } from 'mod'; type",
+ 'export { type } from "mod";\ntype'
+ );
+ expectPrinted_(
+ "export { type, as } from 'mod'",
+ 'export { type, as } from "mod"'
+ );
+ expectPrinted_(
+ "export { x, type foo } from 'mod'; x",
+ 'export { x } from "mod";\nx'
+ );
+ expectPrinted_(
+ "export { x, type as } from 'mod'; x",
+ 'export { x } from "mod";\nx'
+ );
+ expectPrinted_(
+ "export { x, type foo as bar } from 'mod'; x",
+ 'export { x } from "mod";\nx'
+ );
+ expectPrinted_(
+ "export { x, type foo as as } from 'mod'; x",
+ 'export { x } from "mod";\nx'
+ );
+ expectPrinted_(
+ "export { type as as } from 'mod'; as",
+ 'export { type as as } from "mod";\nas'
+ );
+ expectPrinted_(
+ "export { type as foo } from 'mod'; foo",
+ 'export { type as foo } from "mod";\nfoo'
+ );
+ expectPrinted_(
+ "export { type as type } from 'mod'; type",
+ 'export { type } from "mod";\ntype'
+ );
+ expectPrinted_(
+ "export { x, type as as foo } from 'mod'; x",
+ 'export { x } from "mod";\nx'
+ );
+ expectPrinted_(
+ "export { x, type as as as } from 'mod'; x",
+ 'export { x } from "mod";\nx'
+ );
+ expectPrinted_(
+ "export { x, type type as as } from 'mod'; x",
+ 'export { x } from "mod";\nx'
+ );
+ expectPrinted_(
+ "export { x, \\u0074ype y }; let x, y",
+ "export { x };\nlet x, y"
+ );
+ expectPrinted_(
+ "export { x, \\u0074ype y } from 'mod'",
+ 'export { x } from "mod"'
+ );
+ expectPrinted_(
+ "export { x, type if } from 'mod'",
+ 'export { x } from "mod"'
+ );
+ expectPrinted_("export { x, type y as if }; let x", "export { x };\nlet x");
+ expectPrinted_("export { type x };", "");
+ });
+
+ it("delete + optional chain", () => {
+ expectPrinted_("delete foo.bar.baz", "delete foo.bar.baz");
+ expectPrinted_("delete foo?.bar.baz", "delete foo?.bar.baz");
+ expectPrinted_("delete foo?.bar?.baz", "delete foo?.bar?.baz");
+ });
+
+ it("useDefineForConst TypeScript class initialization", () => {
+ var { expectPrinted_ } = ts;
+ expectPrinted_(
+ `
+class Foo {
+ constructor(public x: string = "hey") {}
+ bar: number;
+}
+`.trim(),
+ `
+class Foo {
+ x;
+ constructor(x = "hey") {
+ this.x = x;
+ }
+ bar;
+}
+`.trim()
+ );
+ });
+
+ it("class static blocks", () => {
+ expectPrinted_(
+ "class Foo { static {} }",
+ "class Foo {\n static {\n }\n}"
+ );
+ expectPrinted_(
+ "class Foo { static {} x = 1 }",
+ "class Foo {\n static {\n }\n x = 1;\n}"
+ );
+ expectPrinted_(
+ "class Foo { static { this.foo() } }",
+ "class Foo {\n static {\n this.foo();\n }\n}"
+ );
+
+ expectParseError(
+ "class Foo { static { yield } }",
+ '"yield" is a reserved word and cannot be used in strict mode'
+ );
+ expectParseError(
+ "class Foo { static { await } }",
+ 'The keyword "await" cannot be used here'
+ );
+ expectParseError(
+ "class Foo { static { return } }",
+ "A return statement cannot be used here"
+ );
+ expectParseError(
+ "class Foo { static { break } }",
+ 'Cannot use "break" here'
+ );
+ expectParseError(
+ "class Foo { static { continue } }",
+ 'Cannot use "continue" here'
+ );
+ expectParseError(
+ "x: { class Foo { static { break x } } }",
+ 'There is no containing label named "x"'
+ );
+ expectParseError(
+ "x: { class Foo { static { continue x } } }",
+ 'There is no containing label named "x"'
+ );
+
+ expectParseError(
+ "class Foo { get #x() { this.#x = 1 } }",
+ 'Writing to getter-only property "#x" will throw'
+ );
+ expectParseError(
+ "class Foo { get #x() { this.#x += 1 } }",
+ 'Writing to getter-only property "#x" will throw'
+ );
+ expectParseError(
+ "class Foo { set #x(x) { this.#x } }",
+ 'Reading from setter-only property "#x" will throw'
+ );
+ expectParseError(
+ "class Foo { set #x(x) { this.#x += 1 } }",
+ 'Reading from setter-only property "#x" will throw'
+ );
+
+ // Writing to method warnings
+ expectParseError(
+ "class Foo { #x() { this.#x = 1 } }",
+ 'Writing to read-only method "#x" will throw'
+ );
+ expectParseError(
+ "class Foo { #x() { this.#x += 1 } }",
+ 'Writing to read-only method "#x" will throw'
+ );
+ });
+
+ describe("simplification", () => {
+ it("unary operator", () => {
+ expectPrinted("a = !(b, c)", "a = (b , !c)");
+ });
+
+ it("constant folding", () => {
+ expectPrinted("1 && 2", "2");
+ expectPrinted("1 || 2", "1");
+ expectPrinted("0 && 1", "0");
+ expectPrinted("0 || 1", "1");
+
+ expectPrinted("null ?? 1", "1");
+ expectPrinted("undefined ?? 1", "1");
+ expectPrinted("0 ?? 1", "0");
+ expectPrinted("false ?? 1", "false");
+ expectPrinted('"" ?? 1', '""');
+
+ expectPrinted("typeof undefined", '"undefined"');
+ expectPrinted("typeof null", '"object"');
+ expectPrinted("typeof false", '"boolean"');
+ expectPrinted("typeof true", '"boolean"');
+ expectPrinted("typeof 123", '"number"');
+ expectPrinted("typeof 123n", '"bigint"');
+ expectPrinted("typeof 'abc'", '"string"');
+ expectPrinted("typeof function() {}", '"function"');
+ expectPrinted("typeof (() => {})", '"function"');
+ expectPrinted("typeof {}", "typeof {}");
+ expectPrinted("typeof []", "typeof []");
+
+ expectPrinted("undefined === undefined", "true");
+ expectPrinted("undefined !== undefined", "false");
+ expectPrinted("undefined == undefined", "true");
+ expectPrinted("undefined != undefined", "false");
+
+ expectPrinted("null === null", "true");
+ expectPrinted("null !== null", "false");
+ expectPrinted("null == null", "true");
+ expectPrinted("null != null", "false");
+
+ expectPrinted("undefined === null", "undefined === null");
+ expectPrinted("undefined !== null", "undefined !== null");
+ expectPrinted("undefined == null", "undefined == null");
+ expectPrinted("undefined != null", "undefined != null");
+
+ expectPrinted("true === true", "true");
+ expectPrinted("true === false", "false");
+ expectPrinted("true !== true", "false");
+ expectPrinted("true !== false", "true");
+ expectPrinted("true == true", "true");
+ expectPrinted("true == false", "false");
+ expectPrinted("true != true", "false");
+ expectPrinted("true != false", "true");
+
+ expectPrinted("1 === 1", "true");
+ expectPrinted("1 === 2", "false");
+ expectPrinted("1 === '1'", '1 === "1"');
+ expectPrinted("1 == 1", "true");
+ expectPrinted("1 == 2", "false");
+ expectPrinted("1 == '1'", '1 == "1"');
+
+ expectPrinted("1 !== 1", "false");
+ expectPrinted("1 !== 2", "true");
+ expectPrinted("1 !== '1'", '1 !== "1"');
+ expectPrinted("1 != 1", "false");
+ expectPrinted("1 != 2", "true");
+ expectPrinted("1 != '1'", '1 != "1"');
+
+ expectPrinted("'a' === '\\x61'", "true");
+ expectPrinted("'a' === '\\x62'", "false");
+ expectPrinted("'a' === 'abc'", "false");
+ expectPrinted("'a' !== '\\x61'", "false");
+ expectPrinted("'a' !== '\\x62'", "true");
+ expectPrinted("'a' !== 'abc'", "true");
+ expectPrinted("'a' == '\\x61'", "true");
+ expectPrinted("'a' == '\\x62'", "false");
+ expectPrinted("'a' == 'abc'", "false");
+ expectPrinted("'a' != '\\x61'", "false");
+ expectPrinted("'a' != '\\x62'", "true");
+ expectPrinted("'a' != 'abc'", "true");
+
+ expectPrinted("'a' + 'b'", '"ab"');
+ expectPrinted("'a' + 'bc'", '"abc"');
+ expectPrinted("'ab' + 'c'", '"abc"');
+ expectPrinted("x + 'a' + 'b'", 'x + "ab"');
+ expectPrinted("x + 'a' + 'bc'", 'x + "abc"');
+ expectPrinted("x + 'ab' + 'c'", 'x + "abc"');
+ expectPrinted("'a' + 1", '"a" + 1');
+ expectPrinted("x * 'a' + 'b'", 'x * "a" + "b"');
+
+ expectPrinted("'string' + `template`", `"stringtemplate"`);
+
+ expectPrinted("`template` + 'string'", "`templatestring`");
+
+ // TODO: string template simplification
+ // expectPrinted("'string' + `a${foo}b`", "`stringa${foo}b`");
+ // expectPrinted("'string' + tag`template`", '"string" + tag`template`;');
+ // expectPrinted("`a${foo}b` + 'string'", "`a${foo}bstring`");
+ // expectPrinted("tag`template` + 'string'", 'tag`template` + "string"');
+ // expectPrinted("`template` + `a${foo}b`", "`templatea${foo}b`");
+ // expectPrinted("`a${foo}b` + `template`", "`a${foo}btemplate`");
+ // expectPrinted("`a${foo}b` + `x${bar}y`", "`a${foo}bx${bar}y`");
+ // expectPrinted(
+ // "`a${i}${j}bb` + `xxx${bar}yyyy`",
+ // "`a${i}${j}bbxxx${bar}yyyy`"
+ // );
+ // expectPrinted(
+ // "`a${foo}bb` + `xxx${i}${j}yyyy`",
+ // "`a${foo}bbxxx${i}${j}yyyy`"
+ // );
+ // expectPrinted(
+ // "`template` + tag`template2`",
+ // "`template` + tag`template2`"
+ // );
+ // expectPrinted(
+ // "tag`template` + `template2`",
+ // "tag`template` + `template2`"
+ // );
+
+ expectPrinted("123", "123");
+ expectPrinted("123 .toString()", "123 .toString()");
+ expectPrinted("-123", "-123");
+ expectPrinted("(-123).toString()", "(-123).toString()");
+ expectPrinted("-0", "-0");
+ expectPrinted("(-0).toString()", "(-0).toString()");
+ expectPrinted("-0 === 0", "true");
+
+ expectPrinted("NaN", "NaN");
+ expectPrinted("NaN.toString()", "NaN.toString()");
+ expectPrinted("NaN === NaN", "false");
+
+ expectPrinted("Infinity", "Infinity");
+ expectPrinted("Infinity.toString()", "Infinity.toString()");
+ expectPrinted("(-Infinity).toString()", "(-Infinity).toString()");
+ expectPrinted("Infinity === Infinity", "true");
+ expectPrinted("Infinity === -Infinity", "false");
+
+ expectPrinted("123n === 1_2_3n", "true");
+ });
+ describe("type coercions", () => {
+ const dead = `
+ if ("") {
+ TEST_FAIL
+ }
+
+ if (false) {
+ TEST_FAIL
+ }
+
+ if (0) {
+ TEST_FAIL
+ }
+
+ if (void 0) {
+ TEST_FAIL
+ }
+
+ if (null) {
+ TEST_FAIL
+ }
+
+ var should_be_true = typeof "" === "string" || false
+ var should_be_false = typeof "" !== "string" && TEST_FAIL;
+ var should_be_false_2 = typeof true === "string" && TEST_FAIL;
+ var should_be_false_3 = typeof false === "string" && TEST_FAIL;
+ var should_be_false_4 = typeof 123n === "string" && TEST_FAIL;
+ var should_be_false_5 = typeof function(){} === "string" && TEST_FAIL;
+ var should_be_kept = typeof globalThis.BACON === "string" && TEST_OK;
+ var should_be_kept_1 = typeof TEST_OK === "string";
+
+ var should_be_kept_2 = TEST_OK ?? true;
+ var should_be_kept_4 = { "TEST_OK": true } ?? TEST_FAIL;
+ var should_be_false_6 = false ?? TEST_FAIL;
+ var should_be_true_7 = true ?? TEST_FAIL;
+ `;
+ const out = transpiler.transformSync(dead);
+
+ for (let line of out.split("\n")) {
+ it(line, () => {
+ if (line.includes("should_be_kept")) {
+ expect(line.includes("TEST_OK")).toBe(true);
+ }
+
+ if (line.includes("should_be_false")) {
+ if (!line.includes("= false"))
+ throw new Error(`Expected false in "${line}"`);
+ expect(line.includes("= false")).toBe(true);
+ }
+
+ if (line.includes("TEST_FAIL")) {
+ throw new Error(`"${line}"\n\tshould not contain TEST_FAIL`);
+ }
+ });
+ }
+ });
+ });
+
+ describe("scan", () => {
+ it("reports all export names", () => {
+ const { imports, exports } = transpiler.scan(code);
+
+ expect(exports[0]).toBe("action");
+ expect(exports[2]).toBe("loader");
+ expect(exports[1]).toBe("default");
+ expect(exports).toHaveLength(3);
+
+ expect(imports.filter(({ path }) => path === "remix")).toHaveLength(1);
+ expect(imports.filter(({ path }) => path === "mod")).toHaveLength(0);
+ expect(imports.filter(({ path }) => path === "react")).toHaveLength(1);
+ expect(imports).toHaveLength(2);
+ });
+ });
+
+ describe("transform", () => {
+ it("supports macros", async () => {
+ const out = await transpiler.transform(`
+ import {keepSecondArgument} from 'macro:${
+ import.meta.dir
+ }/macro-check.js';
+
+ export default keepSecondArgument("Test failed", "Test passed");
+ export function otherNamesStillWork() {}
+ `);
+ expect(out.includes("Test failed")).toBe(false);
+ expect(out.includes("Test passed")).toBe(true);
+
+ // ensure both the import and the macro function call are removed
+ expect(out.includes("keepSecondArgument")).toBe(false);
+ expect(out.includes("otherNamesStillWork")).toBe(true);
+ });
+
+ it("sync supports macros", () => {
+ const out = transpiler.transformSync(`
+ import {keepSecondArgument} from 'macro:${
+ import.meta.dir
+ }/macro-check.js';
+
+ export default keepSecondArgument("Test failed", "Test passed");
+ export function otherNamesStillWork() {
+
+ }
+ `);
+ expect(out.includes("Test failed")).toBe(false);
+ expect(out.includes("Test passed")).toBe(true);
+
+ expect(out.includes("keepSecondArgument")).toBe(false);
+ expect(out.includes("otherNamesStillWork")).toBe(true);
+ });
+
+ const importLines = [
+ "import {createElement, bacon} from 'react';",
+ "import {bacon, createElement} from 'react';",
+ ];
+ describe("sync supports macros remap", () => {
+ for (let importLine of importLines) {
+ it(importLine, () => {
+ var thisCode = `
+ ${importLine}
+
+ export default bacon("Test failed", "Test passed");
+ export function otherNamesStillWork() {
+ return createElement("div");
+ }
+
+ `;
+ var out = transpiler.transformSync(thisCode);
+ try {
+ expect(out.includes("Test failed")).toBe(false);
+ expect(out.includes("Test passed")).toBe(true);
+
+ expect(out.includes("bacon")).toBe(false);
+ expect(out.includes("createElement")).toBe(true);
+ } catch (e) {
+ console.log("Failing code:\n\n" + out + "\n");
+ throw e;
+ }
+ });
+ }
+ });
+
+ it("macro remap removes import statement if its the only used one", () => {
+ const out = transpiler.transformSync(`
+ import {bacon} from 'react';
+
+ export default bacon("Test failed", "Test passed");
+ `);
+
+ expect(out.includes("Test failed")).toBe(false);
+ expect(out.includes("Test passed")).toBe(true);
+
+ expect(out.includes("bacon")).toBe(false);
+ expect(out.includes("import")).toBe(false);
+ });
+
+ it("removes types", () => {
+ expect(code.includes("mod")).toBe(true);
+ expect(code.includes("xx")).toBe(true);
+ expect(code.includes("ActionFunction")).toBe(true);
+ expect(code.includes("LoaderFunction")).toBe(true);
+ expect(code.includes("ReactNode")).toBe(true);
+ expect(code.includes("React")).toBe(true);
+ expect(code.includes("Component")).toBe(true);
+ const out = transpiler.transformSync(code);
+
+ expect(out.includes("ActionFunction")).toBe(false);
+ expect(out.includes("LoaderFunction")).toBe(false);
+ expect(out.includes("mod")).toBe(false);
+ expect(out.includes("xx")).toBe(false);
+ expect(out.includes("ReactNode")).toBe(false);
+ const { exports } = transpiler.scan(out);
+ exports.sort();
+
+ expect(exports[0]).toBe("action");
+ expect(exports[2]).toBe("loader");
+ expect(exports[1]).toBe("default");
+ expect(exports).toHaveLength(3);
+ });
+ });
+});