diff --git a/quickjs.c b/quickjs.c index 793a13e9f..9fa431161 100644 --- a/quickjs.c +++ b/quickjs.c @@ -29334,11 +29334,17 @@ static __exception int js_parse_export(JSParseState *s) if (token_is_pseudo_keyword(s, JS_ATOM_as)) { if (next_token(s)) goto fail; - if (!token_is_ident(s->token.val)) { - js_parse_error(s, "identifier expected"); + if (token_is_ident(s->token.val)) { + export_name = JS_DupAtom(ctx, s->token.u.ident.atom); + } else if (s->token.val == TOK_STRING) { + export_name = JS_ValueToAtom(ctx, s->token.u.str.str); + if (export_name == JS_ATOM_NULL) { + return -1; + } + } else { + js_parse_error(s, "identifier or string expected"); goto fail; } - export_name = JS_DupAtom(ctx, s->token.u.ident.atom); if (next_token(s)) { fail: JS_FreeAtom(ctx, local_name); @@ -29382,11 +29388,19 @@ static __exception int js_parse_export(JSParseState *s) /* export ns from */ if (next_token(s)) return -1; - if (!token_is_ident(s->token.val)) { - js_parse_error(s, "identifier expected"); + + if (token_is_ident(s->token.val)) { + export_name = JS_DupAtom(ctx, s->token.u.ident.atom); + } else if (s->token.val == TOK_STRING) { + export_name = JS_ValueToAtom(ctx, s->token.u.str.str); + if (export_name == JS_ATOM_NULL) { + return -1; + } + } else { + js_parse_error(s, "identifier or string expected"); return -1; } - export_name = JS_DupAtom(ctx, s->token.u.ident.atom); + if (next_token(s)) goto fail1; module_name = js_parse_from_clause(s); @@ -29560,11 +29574,17 @@ static __exception int js_parse_import(JSParseState *s) return -1; while (s->token.val != '}') { - if (!token_is_ident(s->token.val)) { - js_parse_error(s, "identifier expected"); + if (token_is_ident(s->token.val)) { + import_name = JS_DupAtom(ctx, s->token.u.ident.atom); + } else if (s->token.val == TOK_STRING) { + import_name = JS_ValueToAtom(ctx, s->token.u.str.str); + if (import_name == JS_ATOM_NULL) { + return -1; + } + } else { + js_parse_error(s, "identifier or string expected expected"); return -1; } - import_name = JS_DupAtom(ctx, s->token.u.ident.atom); local_name = JS_ATOM_NULL; if (next_token(s)) goto fail; diff --git a/tests/fixture_string_exports.js b/tests/fixture_string_exports.js new file mode 100644 index 000000000..f6439064a --- /dev/null +++ b/tests/fixture_string_exports.js @@ -0,0 +1,12 @@ +// ES2020 string export names test fixture +export const regularExport = "regular"; +const value1 = "value-1"; +const value2 = "value-2"; + +// String export names (ES2020) +export { value1 as "string-export-1" }; +export { value2 as "string-export-2" }; + +// Mixed: regular and string exports +const mixed = "mixed-value"; +export { mixed as normalName, mixed as "string-name" }; diff --git a/tests/test_string_exports.js b/tests/test_string_exports.js new file mode 100644 index 000000000..b03b19fc6 --- /dev/null +++ b/tests/test_string_exports.js @@ -0,0 +1,25 @@ +// Test ES2020 string export/import names +import { assert } from "./assert.js"; +import * as mod from "./fixture_string_exports.js"; + +// Test string import names +import { "string-export-1" as str1 } from "./fixture_string_exports.js"; +import { "string-export-2" as str2 } from "./fixture_string_exports.js"; +import { "string-name" as strMixed } from "./fixture_string_exports.js"; + +// Test regular imports still work +import { regularExport, normalName } from "./fixture_string_exports.js"; + +// Verify values +assert(str1 === "value-1"); +assert(str2 === "value-2"); +assert(strMixed === "mixed-value"); +assert(regularExport === "regular"); +assert(normalName === "mixed-value"); + +// Verify module namespace has string-named exports +assert(mod["string-export-1"] === "value-1"); +assert(mod["string-export-2"] === "value-2"); +assert(mod["string-name"] === "mixed-value"); +assert(mod.regularExport === "regular"); +assert(mod.normalName === "mixed-value");