Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions c/interface.c
Original file line number Diff line number Diff line change
Expand Up @@ -1098,8 +1098,10 @@ MaybeAsync(JSValue *) QTS_Eval(JSContext *ctx, BorrowedHeapChar *js_code, size_t
// Error - nothing more to do.
JS_IsException(eval_result)
// Non-module eval - return the result
|| (evalFlags & JS_EVAL_TYPE_MODULE) == 0) {
QTS_DEBUG("QTS_Eval: non-module or exception")
|| (evalFlags & JS_EVAL_TYPE_MODULE) == 0
// Compiled code has not been evaluated; return it without accessing module.
|| (evalFlags & JS_EVAL_FLAG_COMPILE_ONLY) != 0) {
QTS_DEBUG("QTS_Eval: non-module, compile-only, or exception")
return jsvalue_to_heap(eval_result);
}

Expand All @@ -1114,7 +1116,6 @@ MaybeAsync(JSValue *) QTS_Eval(JSContext *ctx, BorrowedHeapChar *js_code, size_t
// quickjs@2024-01-14 evaluating module
// produced a promise
(state == JS_PROMISE_FULFILLED)
// quickjs in compile mode
// quickjs-ng before rebasing on quickjs@2024-01-14
// not a promise.
|| (state == -1)) {
Expand Down
51 changes: 51 additions & 0 deletions packages/quickjs-emscripten/src/quickjs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -541,6 +541,34 @@ function contextTests(getContext: GetTestContext, options: ContextTestOptions =
})

describe(".evalCode", () => {
describe("compileOnly", () => {
for (const type of ["global", "module"] as const) {
it(`${type}: returns disposable compiled code without executing it`, () => {
vm.unwrapResult(vm.evalCode("globalThis.compileOnlyRuns = 0")).dispose()
const code =
type === "module"
? "globalThis.compileOnlyRuns++; export const value = 42"
: "globalThis.compileOnlyRuns++; 42"
const compiled = vm.unwrapResult(
vm.evalCode(code, "compiled.js", { type, compileOnly: true }),
)
// Compiled code is bytecode, not a JS value, so it has no JS type.
assert.equal(vm.typeof(compiled), "unknown")
compiled.dispose()
assert.equal(vm.getProp(vm.global, "compileOnlyRuns").consume(vm.getNumber), 0)
})
}

it("module: preserves syntax errors", () => {
const result = vm.evalCode("export const =", "invalid.js", {
type: "module",
compileOnly: true,
})
assert(result.error, "Compilation should return a syntax error")
assert.equal(result.error.consume(vm.dump).name, "SyntaxError")
})
})

it("on success: returns { value: success }", () => {
const value = vm.unwrapResult(vm.evalCode(`["this", "should", "work"].join(' ')`))
assert.equal(vm.getString(value), "this should work")
Expand Down Expand Up @@ -1268,6 +1296,29 @@ function asyncContextTests(
})

describe("module loading", () => {
it("compileOnly: returns disposable compiled code without executing it", async () => {
vm.unwrapResult(vm.evalCode("globalThis.compileOnlyRuns = 0")).dispose()
const result = await vm.evalCodeAsync(
"globalThis.compileOnlyRuns++; export const value = 42",
"compiled.js",
{ type: "module", compileOnly: true },
)
const compiled = vm.unwrapResult(result)
// Compiled code is bytecode, not a JS value, so it has no JS type.
assert.equal(vm.typeof(compiled), "unknown")
compiled.dispose()
assert.equal(vm.getProp(vm.global, "compileOnlyRuns").consume(vm.getNumber), 0)
})

it("compileOnly: preserves syntax errors", async () => {
const result = await vm.evalCodeAsync("export const =", "invalid.js", {
type: "module",
compileOnly: true,
})
assert(result.error, "Compilation should return a syntax error")
assert.equal(result.error.consume(vm.dump).name, "SyntaxError")
})

it("supports async module loading", async () => {
async function testBody() {
let moduleLoaderCalls = 0
Expand Down