91513cdbf3
- Updated local module loading to force TS syntax stripping for .ts/.tsx/.mts modules and use the matching Bun transpiler loader. - Extended TypeScript stripping to detect `import type`/`export type` syntax and rewired wrapping to strip TS syntax after final-expression extraction with TypeScript-aware parsing. - Added tests verifying type-only imports are handled correctly in evaluator modules and rewritten code no longer contains type-only import declarations.
141 lines
5.8 KiB
TypeScript
141 lines
5.8 KiB
TypeScript
import { describe, expect, it } from "bun:test";
|
|
|
|
import { rewriteImports, wrapCode } from "../../src/eval/js/context-manager";
|
|
|
|
// Test fixtures embed user-supplied `import(...)` syntax that the rewriter must
|
|
// transform. The strings are split so static-analysis heuristics don't read them
|
|
// as real imports in this file.
|
|
const IMPORT = "import";
|
|
const dyn = (rest: string) => `${IMPORT}${rest}`;
|
|
|
|
describe("rewriteImports", () => {
|
|
it("rewrites a top-level default import", () => {
|
|
const out = rewriteImports(`${IMPORT} foo from "bar";\nconsole.log(foo);`);
|
|
expect(out).toContain('await __omp_import__("bar")');
|
|
expect(out).not.toContain(`${IMPORT} foo from "bar"`);
|
|
});
|
|
|
|
it("rewrites destructured named imports with renames", () => {
|
|
const out = rewriteImports(`${IMPORT} { foo, bar as baz } from "pkg";`);
|
|
expect(out).toContain('await __omp_import__("pkg")');
|
|
expect(out).toContain("foo");
|
|
expect(out).toContain("bar: baz");
|
|
});
|
|
|
|
it("rewrites namespace imports", () => {
|
|
const out = rewriteImports(`${IMPORT} * as ns from "pkg";`);
|
|
expect(out).toContain('const ns = await __omp_import__("pkg")');
|
|
});
|
|
|
|
it("rewrites combined default + namespace", () => {
|
|
const out = rewriteImports(`${IMPORT} def, * as ns from "pkg";`);
|
|
expect(out).toContain('const ns = await __omp_import__("pkg")');
|
|
expect(out).toContain("const def = ns.default");
|
|
});
|
|
|
|
it("rewrites combined default + named", () => {
|
|
const out = rewriteImports(`${IMPORT} def, { foo, bar as baz } from "pkg";`);
|
|
expect(out).toContain('await __omp_import__("pkg")');
|
|
expect(out).toContain("default: def");
|
|
expect(out).toContain("bar: baz");
|
|
});
|
|
|
|
it("rewrites side-effect-only imports", () => {
|
|
const out = rewriteImports(`${IMPORT} "polyfill";`);
|
|
expect(out).toContain('await __omp_import__("polyfill")');
|
|
});
|
|
|
|
it("preserves import attributes via the dynamic import options bag", () => {
|
|
const out = rewriteImports(`${IMPORT} data from "./d.json" with { type: "json" };`);
|
|
expect(out).toContain('await __omp_import__("./d.json", { with: { type: "json" } })');
|
|
expect(out).toContain("const data =");
|
|
});
|
|
|
|
it("rewrites bare dynamic import() so its specifier resolves against the session cwd", () => {
|
|
const out = rewriteImports(`const m = await ${dyn('("./foo.ts")')};`);
|
|
expect(out).toContain('await __omp_import__("./foo.ts")');
|
|
expect(out).not.toContain(dyn('("./foo.ts")'));
|
|
});
|
|
|
|
it("rewrites dynamic import() with an options bag (passes options through unchanged)", () => {
|
|
const out = rewriteImports(`const m = await ${dyn('("./d.json", { with: { type: "json" } })')};`);
|
|
expect(out).toContain('__omp_import__("./d.json", { with: { type: "json" } })');
|
|
});
|
|
|
|
it("rewrites nested and chained dynamic import() calls", () => {
|
|
const out = rewriteImports(
|
|
`Promise.all([${dyn('("./a.ts")')}, ${dyn('("./b.ts")')}]).then(([a, b]) => a.run(b));`,
|
|
);
|
|
expect(out).toContain('__omp_import__("./a.ts")');
|
|
expect(out).toContain('__omp_import__("./b.ts")');
|
|
expect(out).not.toContain(dyn('("./a.ts")'));
|
|
});
|
|
|
|
it("rewrites dynamic import() with a non-literal specifier", () => {
|
|
const out = rewriteImports(`const m = await ${dyn("(spec)")};`);
|
|
expect(out).toContain("__omp_import__(spec)");
|
|
});
|
|
|
|
it("does not rewrite import statements embedded in template literals (the bug)", () => {
|
|
const code = ["const generated = `", `${IMPORT} { foo } from "./foo";`, "export const bar = foo + 1;", "`;"].join(
|
|
"\n",
|
|
);
|
|
const out = rewriteImports(code);
|
|
expect(out).toContain(`${IMPORT} { foo } from "./foo";`);
|
|
expect(out).toContain("export const bar = foo + 1;");
|
|
expect(out).not.toContain("await __omp_import__(");
|
|
});
|
|
|
|
it("does not rewrite import statements inside block comments", () => {
|
|
const code = `/*\n${IMPORT} foo from "bar";\n*/\nconst x = 1;`;
|
|
const out = rewriteImports(code);
|
|
expect(out).toContain(`${IMPORT} foo from "bar";`);
|
|
expect(out).not.toContain('await __omp_import__("bar")');
|
|
});
|
|
|
|
it("does not rewrite import statements inside double-quoted strings using line continuation", () => {
|
|
const code = `const code = "${IMPORT} foo from \\\n'bar'";\nconsole.log(code);`;
|
|
const out = rewriteImports(code);
|
|
expect(out).not.toContain("await __omp_import__");
|
|
});
|
|
|
|
it("rewrites real top-level imports while leaving template-embedded look-alikes alone", () => {
|
|
const code = [
|
|
`${IMPORT} a from "alpha";`,
|
|
"const code = `",
|
|
`${IMPORT} b from "beta";`,
|
|
"`;",
|
|
`${IMPORT} c from "gamma";`,
|
|
].join("\n");
|
|
const out = rewriteImports(code);
|
|
expect(out).toContain('await __omp_import__("alpha")');
|
|
expect(out).toContain('await __omp_import__("gamma")');
|
|
expect(out).not.toContain('await __omp_import__("beta")');
|
|
expect(out).toContain(`${IMPORT} b from "beta";`);
|
|
});
|
|
|
|
it("returns the input unchanged when there are no imports", () => {
|
|
const code = "const x = 1 + 2;\nreturn x;";
|
|
expect(rewriteImports(code)).toBe(code);
|
|
});
|
|
|
|
it("returns the input unchanged when the parser cannot make sense of the code", () => {
|
|
const code = `${IMPORT} { foo from broken syntax 'unterminated`;
|
|
// Should not throw; should fall through to the VM which will surface the syntax error.
|
|
expect(() => rewriteImports(code)).not.toThrow();
|
|
});
|
|
|
|
it("captures the final expression even when trailing empty statements follow", () => {
|
|
const wrapped = wrapCode("await Promise.resolve(1);;");
|
|
expect(wrapped.finalExpressionReturned).toBe(true);
|
|
expect(wrapped.source).toContain("__omp_set_final_expr__((await Promise.resolve(1)))");
|
|
});
|
|
|
|
it("strips type-only imports before rewriting imports and top-level return", () => {
|
|
const wrapped = wrapCode(`${IMPORT} type { Thing } from "./types";\nreturn 42;`);
|
|
expect(wrapped.finalExpressionReturned).toBe(true);
|
|
expect(wrapped.source).toContain("__omp_set_final_expr__(42)");
|
|
expect(wrapped.source).not.toContain(`${IMPORT} type`);
|
|
});
|
|
});
|