diff --git a/gen/function_source.c b/gen/function_source.c index e041b18..d4394f6 100644 Binary files a/gen/function_source.c and b/gen/function_source.c differ diff --git a/gen/hello.c b/gen/hello.c index 9eb2e04..821267c 100644 Binary files a/gen/hello.c and b/gen/hello.c differ diff --git a/quickjs.c b/quickjs.c index 2587a64..32ecd80 100644 --- a/quickjs.c +++ b/quickjs.c @@ -20250,34 +20250,6 @@ static void skip_shebang(const uint8_t **pp, const uint8_t *buf_end) } } -/* return true if 'input' contains the source of a module - (heuristic). 'input' must be a zero terminated. - - Heuristic: - - Skip comments - - Expect 'import' keyword not followed by '(' or '.' - - Expect 'export' keyword - - Expect 'await' keyword -*/ -/* input is pure ASCII or UTF-8 encoded source code */ -BOOL JS_DetectModule(const char *input, size_t input_len) -{ - const uint8_t *p = (const uint8_t *)input; - int tok; - - skip_shebang(&p, p + input_len); - switch(simple_next_token(&p, FALSE)) { - case TOK_IMPORT: - tok = simple_next_token(&p, FALSE); - return (tok != '.' && tok != '('); - case TOK_AWAIT: - case TOK_EXPORT: - return TRUE; - default: - return FALSE; - } -} - static inline int get_prev_opcode(JSFunctionDef *fd) { if (fd->last_opcode_pos < 0) return OP_invalid; @@ -26380,6 +26352,7 @@ static JSModuleDef *js_host_resolve_imported_module(JSContext *ctx, /* load the module */ if (!rt->module_loader_func) { /* XXX: use a syntax error ? */ + // XXX: update JS_DetectModule when you change this JS_ThrowReferenceError(ctx, "could not load module '%s'", cname); js_free(ctx, cname); @@ -54702,6 +54675,38 @@ static void _JS_AddIntrinsicCallSite(JSContext *ctx) countof(js_callsite_proto_funcs)); } +BOOL JS_DetectModule(const char *input, size_t input_len) +{ + JSRuntime *rt; + JSContext *ctx; + JSValue val; + BOOL is_module; + + is_module = TRUE; + rt = JS_NewRuntime(); + if (!rt) + return FALSE; + ctx = JS_NewContextRaw(rt); + if (!ctx) { + JS_FreeRuntime(rt); + return FALSE; + } + JS_AddIntrinsicRegExp(ctx); // otherwise regexp literals don't parse + val = __JS_EvalInternal(ctx, JS_UNDEFINED, input, input_len, "", + JS_EVAL_TYPE_MODULE|JS_EVAL_FLAG_COMPILE_ONLY, -1); + if (JS_IsException(val)) { + const char *msg = JS_ToCString(ctx, rt->current_exception); + // gruesome hack to recognize exceptions from import statements; + // necessary because we don't pass in a module loader + is_module = !!strstr(msg, "ReferenceError: could not load module"); + JS_FreeCString(ctx, msg); + } + JS_FreeValue(ctx, val); + JS_FreeContext(ctx); + JS_FreeRuntime(rt); + return is_module; +} + #undef malloc #undef free #undef realloc diff --git a/quickjs.h b/quickjs.h index e95d224..1e33150 100644 --- a/quickjs.h +++ b/quickjs.h @@ -693,6 +693,12 @@ JS_EXTERN JSValue JS_CallConstructor(JSContext *ctx, JSValue func_obj, JS_EXTERN JSValue JS_CallConstructor2(JSContext *ctx, JSValue func_obj, JSValue new_target, int argc, JSValue *argv); +/* Try to detect if the input is a module. Returns TRUE if parsing the input + * as a module produces no syntax errors. It's a naive approach that is not + * wholly infallible: non-strict classic scripts may _parse_ okay as a module + * but not _execute_ as one (different runtime semantics.) Use with caution. + * |input| can be either ASCII or UTF-8 encoded source code. + */ JS_EXTERN JS_BOOL JS_DetectModule(const char *input, size_t input_len); /* 'input' must be zero terminated i.e. input[input_len] = '\0'. */ JS_EXTERN JSValue JS_Eval(JSContext *ctx, const char *input, size_t input_len, diff --git a/tests/detect_module/0.js b/tests/detect_module/0.js new file mode 100644 index 0000000..18a9109 --- /dev/null +++ b/tests/detect_module/0.js @@ -0,0 +1 @@ +await undefined diff --git a/tests/detect_module/1.js b/tests/detect_module/1.js new file mode 100644 index 0000000..0212282 --- /dev/null +++ b/tests/detect_module/1.js @@ -0,0 +1,2 @@ +const p = Promise.resolve(42) +await p diff --git a/tests/detect_module/2.js b/tests/detect_module/2.js new file mode 100644 index 0000000..0b1d1f7 --- /dev/null +++ b/tests/detect_module/2.js @@ -0,0 +1 @@ +await = 42 // parsed as classic script diff --git a/tests/detect_module/3.js b/tests/detect_module/3.js new file mode 100644 index 0000000..5b175e9 --- /dev/null +++ b/tests/detect_module/3.js @@ -0,0 +1,8 @@ +/*--- +negative: + phase: parse + type: SyntaxError +---*/ +// the import statement makes it a module but `await = 42` is a SyntaxError +import * as _ from "dummy" +await = 42 diff --git a/tests/detect_module/4.js b/tests/detect_module/4.js new file mode 100644 index 0000000..3ee1df7 --- /dev/null +++ b/tests/detect_module/4.js @@ -0,0 +1,3 @@ +// imports should classify it as a module, even when not at the top +os.now() +import * as os from "os" diff --git a/tests/test_module_detect.js b/tests/test_module_detect.js deleted file mode 100644 index 7ce9d22..0000000 --- a/tests/test_module_detect.js +++ /dev/null @@ -1,5 +0,0 @@ -// This needs to be parsed as a module or will throw SyntaxError. -// - -await 0; -