From 4c22226c0a9bf29be30f2904f5932d2b46aaf193 Mon Sep 17 00:00:00 2001 From: Hugo Heuzard Date: Fri, 3 Feb 2023 10:38:06 +0100 Subject: [PATCH] Compiler: support for es6 import/export --- CHANGES.md | 1 + compiler/bin-jsoo_minify/jsoo_minify.ml | 3 +- compiler/lib/driver.ml | 2 +- compiler/lib/javascript.ml | 40 ++++ compiler/lib/javascript.mli | 40 ++++ compiler/lib/js_output.ml | 153 ++++++++++++- compiler/lib/js_parser.mly | 133 +++++++++++ compiler/lib/js_simpl.ml | 2 + compiler/lib/js_token.ml | 3 + compiler/lib/js_token.mli | 1 + compiler/lib/js_traverse.ml | 135 ++++++++++- compiler/lib/js_traverse.mli | 11 +- compiler/lib/parse_js.ml | 20 +- compiler/lib/stdlib.ml | 4 + compiler/tests-compiler/scopes.ml | 287 ++++++++++++++++++++++++ 15 files changed, 813 insertions(+), 22 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index ff967731bd..9c40b1c1ac 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,6 +5,7 @@ * Compiler: change control-flow compilation strategy (#1496) * Compiler: Dead code elimination of unused references (#2076) * Compiler: reduce memory consumption (#1516) +* Compiler: support for es6 import and export construct * Lib: add download attribute to anchor element * Misc: switch CI to OCaml 5.1 * Misc: preliminary support for OCaml 5.2 diff --git a/compiler/bin-jsoo_minify/jsoo_minify.ml b/compiler/bin-jsoo_minify/jsoo_minify.ml index 22ffdb132c..f607fa369b 100644 --- a/compiler/bin-jsoo_minify/jsoo_minify.ml +++ b/compiler/bin-jsoo_minify/jsoo_minify.ml @@ -81,7 +81,8 @@ let f { Cmd_arg.common; output_file; use_stdin; files } = let true_ () = true in let open Config in let passes : ((unit -> bool) * (unit -> Js_traverse.mapper)) list = - [ (Flag.shortvar, fun () -> (new Js_traverse.rename_variable :> Js_traverse.mapper)) + [ ( Flag.shortvar + , fun () -> (new Js_traverse.rename_variable ~esm:false :> Js_traverse.mapper) ) ; (true_, fun () -> new Js_traverse.simpl) ; (true_, fun () -> new Js_traverse.clean) ] diff --git a/compiler/lib/driver.ml b/compiler/lib/driver.ml index 9bbace68a8..4ce9d96884 100644 --- a/compiler/lib/driver.ml +++ b/compiler/lib/driver.ml @@ -575,7 +575,7 @@ if (typeof module === 'object' && module.exports) { if Config.Flag.shortvar () then ( let t5 = Timer.make () in - let js = (new Js_traverse.rename_variable)#program js in + let js = (new Js_traverse.rename_variable ~esm:false)#program js in if times () then Format.eprintf " shortten vars: %a@." Timer.print t5; js) else js diff --git a/compiler/lib/javascript.ml b/compiler/lib/javascript.ml index 756fdefdc5..256f3cc098 100644 --- a/compiler/lib/javascript.ml +++ b/compiler/lib/javascript.ml @@ -346,6 +346,8 @@ and statement = | Throw_statement of expression | Try_statement of block * (formal_parameter option * block) option * block option | Debugger_statement + | Import of import * Parse_info.t + | Export of export * Parse_info.t and ('left, 'right) either = | Left of 'left @@ -422,6 +424,44 @@ and function_body = statement_list and program = statement_list +and export = + | ExportVar of variable_declaration_kind * variable_declaration list + | ExportFun of ident * function_declaration + | ExportClass of ident * class_declaration + | ExportNames of (ident * Utf8_string.t) list + (* default *) + | ExportDefaultFun of ident * function_declaration + | ExportDefaultClass of ident * class_declaration + | ExportDefaultExpression of expression + (* from *) + | ExportFrom of + { kind : export_from_kind + ; from : Utf8_string.t + } + | CoverExportFrom of early_error + +and export_from_kind = + | Export_all of Utf8_string.t option + | Export_names of (Utf8_string.t * Utf8_string.t) list + +and import = + { from : Utf8_string.t + ; kind : import_kind + } + +and import_default = ident + +and import_kind = + | Namespace of import_default option * ident + (* import * as name from "fname" *) + (* import defaultname, * as name from "fname" *) + | Named of import_default option * (Utf8_string.t * ident) list + (* import { 'a' as a, ...} from "fname" *) + (* import defaultname, { 'a' as a, ...} from "fname" *) + | Default of import_default + (* import defaultname from "fname" *) + | SideEffect (* import "fname" *) + and program_with_annots = (statement_list * (Js_token.Annot.t * Parse_info.t) list) list let compare_ident t1 t2 = diff --git a/compiler/lib/javascript.mli b/compiler/lib/javascript.mli index 75cf608a77..69a87bea9d 100644 --- a/compiler/lib/javascript.mli +++ b/compiler/lib/javascript.mli @@ -266,6 +266,8 @@ and statement = | Throw_statement of expression | Try_statement of block * (formal_parameter option * block) option * block option | Debugger_statement + | Import of import * Parse_info.t + | Export of export * Parse_info.t and ('left, 'right) either = | Left of 'left @@ -342,6 +344,44 @@ and function_body = statement_list and program = statement_list +and export = + | ExportVar of variable_declaration_kind * variable_declaration list + | ExportFun of ident * function_declaration + | ExportClass of ident * class_declaration + | ExportNames of (ident * Utf8_string.t) list + (* default *) + | ExportDefaultFun of ident * function_declaration + | ExportDefaultClass of ident * class_declaration + | ExportDefaultExpression of expression + (* from *) + | ExportFrom of + { kind : export_from_kind + ; from : Utf8_string.t + } + | CoverExportFrom of early_error + +and export_from_kind = + | Export_all of Utf8_string.t option + | Export_names of (Utf8_string.t * Utf8_string.t) list + +and import = + { from : Utf8_string.t + ; kind : import_kind + } + +and import_default = ident + +and import_kind = + | Namespace of import_default option * ident + (* import * as name from "fname" *) + (* import defaultname, * as name from "fname" *) + | Named of import_default option * (Utf8_string.t * ident) list + (* import { 'a' as a, ...} from "fname" *) + (* import defaultname, { 'a' as a, ...} from "fname" *) + | Default of import_default + (* import defaultname from "fname" *) + | SideEffect (* import "fname" *) + and program_with_annots = (statement_list * (Js_token.Annot.t * Parse_info.t) list) list val compare_ident : ident -> ident -> int diff --git a/compiler/lib/js_output.ml b/compiler/lib/js_output.ml index 2db393ddc0..1898a8e01d 100644 --- a/compiler/lib/js_output.ml +++ b/compiler/lib/js_output.ml @@ -289,7 +289,9 @@ struct | Try_statement _ | Function_declaration _ | Class_declaration _ - | Debugger_statement -> false + | Debugger_statement + | Import _ + | Export _ -> false let starts_with ~obj ~funct ~let_identifier ~async_identifier l e = let rec traverse l e = @@ -368,6 +370,13 @@ struct Buffer.add_char b quote; PP.string f (Buffer.contents b) + let pp_string_lit f (Stdlib.Utf8_string.Utf8 s) = + let quote = best_string_quote s in + pp_string f ~quote s + + let pp_ident_or_string_lit f (Stdlib.Utf8_string.Utf8 s_lit as s) = + if is_ident s_lit then PP.string f s_lit else pp_string_lit f s + let rec comma_list f f_elt l = match l with | [] -> () @@ -523,9 +532,7 @@ struct then ( PP.string f ")"; PP.end_group f) - | EStr (Utf8 s) -> - let quote = best_string_quote s in - pp_string f ~quote s + | EStr x -> pp_string_lit f x | ETemplate l -> template f l | EBool b -> PP.string f (if b then "true" else "false") | ENum num -> @@ -833,9 +840,7 @@ struct and property_name f n = match n with | PNI (Utf8 s) -> PP.string f s - | PNS (Utf8 s) -> - let quote = best_string_quote s in - pp_string f ~quote s + | PNS s -> pp_string_lit f s | PNN v -> expression Expression f (ENum v) | PComputed e -> PP.string f "["; @@ -1409,6 +1414,140 @@ struct PP.string f "finally"; block f b); PP.end_group f + | Import ({ kind; from }, _loc) -> + PP.start_group f 0; + PP.string f "import"; + (match kind with + | SideEffect -> () + | Default i -> + PP.space f; + ident f i + | Namespace (def, i) -> + Option.iter def ~f:(fun def -> + PP.space f; + ident f def; + PP.string f ","); + PP.space f; + PP.string f "* as "; + ident f i + | Named (def, l) -> + Option.iter def ~f:(fun def -> + PP.space f; + ident f def; + PP.string f ","); + PP.space f; + PP.string f "{"; + PP.space f; + comma_list + f + (fun f (s, i) -> + if match i with + | S { name; _ } when Stdlib.Utf8_string.equal name s -> true + | _ -> false + then ident f i + else ( + pp_ident_or_string_lit f s; + PP.string f " as "; + ident f i)) + l; + PP.space f; + PP.string f "}"); + (match kind with + | SideEffect -> () + | _ -> + PP.space f; + PP.string f "from"); + PP.space f; + pp_string_lit f from; + PP.string f ";"; + PP.end_group f + | Export (e, _loc) -> + PP.start_group f 0; + PP.string f "export"; + (match e with + | ExportNames l -> + PP.space f; + PP.string f "{"; + PP.space f; + comma_list + f + (fun f (i, s) -> + if match i with + | S { name; _ } when Stdlib.Utf8_string.equal name s -> true + | _ -> false + then ident f i + else ( + ident f i; + PP.string f " as "; + pp_ident_or_string_lit f s)) + l; + PP.space f; + PP.string f "};" + | ExportFrom { kind; from } -> + PP.space f; + (match kind with + | Export_all None -> PP.string f "*" + | Export_all (Some s) -> + PP.string f "* as "; + pp_ident_or_string_lit f s + | Export_names l -> + PP.string f "{"; + PP.space f; + comma_list + f + (fun f (a, b) -> + if Stdlib.Utf8_string.equal a b + then pp_ident_or_string_lit f a + else ( + pp_ident_or_string_lit f a; + PP.string f " as "; + pp_ident_or_string_lit f b)) + l; + PP.space f; + PP.string f "}"); + PP.space f; + PP.string f "from"; + PP.space f; + pp_string_lit f from; + PP.string f ";" + | ExportDefaultExpression ((EFun _ | EClass _) as e) -> + PP.space f; + PP.string f "default"; + PP.space f; + expression Expression f e + | ExportDefaultExpression e -> + PP.space f; + PP.string f "default"; + PP.space f; + parenthesized_expression + ~last_semi + ~obj:true + ~funct:true + ~let_identifier:true + Expression + f + e + | ExportDefaultFun (id, decl) -> + PP.space f; + PP.string f "default"; + PP.space f; + statement f (Function_declaration (id, decl), loc) + | ExportDefaultClass (id, decl) -> + PP.space f; + PP.string f "default"; + PP.space f; + statement f (Class_declaration (id, decl), loc) + | ExportFun (id, decl) -> + PP.space f; + statement f (Function_declaration (id, decl), loc) + | ExportClass (id, decl) -> + PP.space f; + statement f (Class_declaration (id, decl), loc) + | ExportVar (k, l) -> + PP.space f; + variable_declaration_list k (not can_omit_semi) f l + | CoverExportFrom e -> early_error e); + PP.end_group f and statement_list f ?skip_last_semi b = match b with diff --git a/compiler/lib/js_parser.mly b/compiler/lib/js_parser.mly index 96bb64e399..ca7d03be67 100644 --- a/compiler/lib/js_parser.mly +++ b/compiler/lib/js_parser.mly @@ -67,6 +67,10 @@ let vartok pos tok = let utf8_s = Stdlib.Utf8_string.of_string_exn +let name_of_ident = function + | S { name; _} -> name + | V _ -> assert false + %} (*************************************************************************) @@ -120,6 +124,7 @@ T_PACKAGE T_DEBUGGER T_GET T_SET T_FROM +T_AS T_TARGET T_META (*-----------------------------------------*) @@ -249,6 +254,9 @@ program: module_item: | item { $symbolstartpos, $1 } + | import_decl { $symbolstartpos, $1 } + | export_decl { $symbolstartpos, $1 } + (*************************************************************************) (* statement *) @@ -271,6 +279,131 @@ decl: | class_decl { let i,f = $1 in Class_declaration (i,f), p $symbolstartpos } +(*************************************************************************) +(* Namespace *) +(*************************************************************************) +(*----------------------------*) +(* import *) +(*----------------------------*) + +import_decl: + | T_IMPORT kind=import_clause from=from_clause sc + { let pos = $symbolstartpos in + Import ({ from; kind }, pi pos), p pos } + | T_IMPORT from=module_specifier sc + { let pos = $symbolstartpos in + Import ({ from; kind = SideEffect }, pi pos), p pos } + +import_clause: + | import_default { Default $1 } + | import_default "," "*" T_AS id=binding_id { Namespace (Some $1, id) } + | "*" T_AS id=binding_id { Namespace (None, id) } + | import_default "," x=named_imports { Named (Some $1, x) } + | x=named_imports { Named (None, x) } + +import_default: binding_id { $1 } + +named_imports: + | "{" "}" { [] } + | "{" listc(import_specifier) "}" { $2 } + | "{" listc(import_specifier) "," "}" { $2 } + +(* also valid for export *) +from_clause: T_FROM module_specifier {$2 } + +import_specifier: + | binding_id { (name_of_ident $1, $1) } + | string_or_ident T_AS binding_id { + let (_,s,_) = $1 in + (s, $3) } + +%inline string_or_ident: + | T_STRING { `String, fst $1, $symbolstartpos } + | T_DEFAULT { `Ident, Stdlib.Utf8_string.of_string_exn "default", $symbolstartpos } + | id { `Ident, $1, $symbolstartpos } + +module_specifier: + | T_STRING { (fst $1) } + +(*----------------------------*) +(* export *) +(*----------------------------*) + +export_decl: + | T_EXPORT names=export_clause sc { + let exception Invalid of Lexing.position in + let k = + try + let names = + List.map (fun ((k, id,pos), (_,s,_)) -> + match k with + | `Ident -> (var (p pos) id, s) + | `String -> raise (Invalid pos)) + names + in + (ExportNames names) + with Invalid pos -> + CoverExportFrom (early_error (pi pos)) + in + let pos = $symbolstartpos in + Export (k, pi pos), p pos } + | T_EXPORT v=variable_stmt + { + let pos = $symbolstartpos in + let k = match v with + | Variable_statement (k,l) -> ExportVar (k, l) + | _ -> assert false + in + Export (k, pi pos), p pos } + | T_EXPORT d=decl + { let k = match d with + | Variable_statement (k,l),_ -> ExportVar (k,l) + | Function_declaration (id, decl),_ -> ExportFun (id,decl) + | Class_declaration (id, decl),_ -> ExportClass (id,decl) + | _ -> assert false + in + let pos = $symbolstartpos in + Export (k,pi pos), p pos } + (* in theory just func/gen/class, no lexical_decl *) + | T_EXPORT T_DEFAULT e=assignment_expr sc + { + let k = match e with + | EFun (Some id, decl) -> + ExportDefaultFun (id,decl) + | EClass (Some id, decl) -> + ExportDefaultClass (id, decl) + | e -> ExportDefaultExpression e + in + let pos = $symbolstartpos in + Export (k,pi pos), p pos } +| T_EXPORT "*" T_FROM from=module_specifier sc { + let kind = Export_all None in + let pos = $symbolstartpos in + Export (ExportFrom ({from; kind}),pi pos), p pos + } + | T_EXPORT "*" T_AS id=string_or_ident T_FROM from=module_specifier sc { + let (_,id,_) = id in + let kind = Export_all (Some id) in + let pos = $symbolstartpos in + Export (ExportFrom ({from; kind}), pi pos), p pos + } +| T_EXPORT names=export_clause T_FROM from=module_specifier sc { + let names = List.map (fun ((_,a,_), (_,b,_)) -> a, b) names in + let kind = Export_names names in + let pos = $symbolstartpos in + Export (ExportFrom ({from; kind}), pi pos), p pos + } + +export_specifier: + | string_or_ident { ($1, $1) } + | string_or_ident T_AS string_or_ident { ($1, $3) } + +export_clause: + | "{" "}" { [] } + | "{" listc(export_specifier) "}" { $2 } + | "{" listc(export_specifier) "," "}" { $2 } + + (*************************************************************************) (* Variable decl *) (*************************************************************************) diff --git a/compiler/lib/js_simpl.ml b/compiler/lib/js_simpl.ml index 24c78d7558..08eaf9e838 100644 --- a/compiler/lib/js_simpl.ml +++ b/compiler/lib/js_simpl.ml @@ -179,6 +179,8 @@ let rec depth = function | Try_statement (b, _, None) -> depth_block b + 1 | Try_statement (b, _, Some b2) -> max (depth_block b) (depth_block b2) + 1 | Debugger_statement -> 1 + | Import _ -> 1 + | Export _ -> 1 and depth_block b = List.fold_left b ~init:0 ~f:(fun acc (s, _) -> max acc (depth s)) diff --git a/compiler/lib/js_token.ml b/compiler/lib/js_token.ml index 4f0e56c0d5..e6a4a7b614 100644 --- a/compiler/lib/js_token.ml +++ b/compiler/lib/js_token.ml @@ -148,6 +148,7 @@ type t = | T_BACKQUOTE | T_DOLLARCURLY | T_ENCAPSED_STRING of string + | T_AS (* Extra tokens *) | T_ERROR of string | T_EOF @@ -303,6 +304,7 @@ let to_string = function | T_BACKQUOTE -> "`" | T_DOLLARCURLY -> "${" | T_ENCAPSED_STRING s -> s + | T_AS -> "as" let to_string_extra x = to_string x @@ -375,4 +377,5 @@ let is_keyword s = | "from" -> Some T_FROM | "target" -> Some T_TARGET | "meta" -> Some T_META + | "as" -> Some T_AS | _ -> None diff --git a/compiler/lib/js_token.mli b/compiler/lib/js_token.mli index 6c6a38e62f..2771555d80 100644 --- a/compiler/lib/js_token.mli +++ b/compiler/lib/js_token.mli @@ -147,6 +147,7 @@ type t = | T_BACKQUOTE | T_DOLLARCURLY | T_ENCAPSED_STRING of string + | T_AS (* Extra tokens *) | T_ERROR of string | T_EOF diff --git a/compiler/lib/js_traverse.ml b/compiler/lib/js_traverse.ml index 32c06b7398..075585f750 100644 --- a/compiler/lib/js_traverse.ml +++ b/compiler/lib/js_traverse.ml @@ -71,6 +71,10 @@ class type mapper = object method program : Javascript.program -> Javascript.program method function_body : statement_list -> statement_list + + method import : import -> import + + method export : export -> export end (* generic js ast walk/map *) @@ -187,6 +191,47 @@ class map : mapper = , match final with | None -> None | Some s -> Some (m#block s) ) + | Import (import, loc) -> Import (m#import import, loc) + | Export (export, loc) -> Export (m#export export, loc) + + method import { from; kind } = + let kind = + match kind with + | Namespace (iopt, i) -> Namespace (Option.map ~f:m#ident iopt, m#ident i) + | Named (iopt, l) -> + Named + (Option.map ~f:m#ident iopt, List.map ~f:(fun (s, id) -> s, m#ident id) l) + | Default import_default -> Default (m#ident import_default) + | SideEffect -> SideEffect + in + { from; kind } + + method export e = + match e with + | ExportVar (k, l) -> ( + match m#statement (Variable_statement (k, l)) with + | Variable_statement (k, l) -> ExportVar (k, l) + | _ -> assert false) + | ExportFun (id, f) -> ( + match m#statement (Function_declaration (id, f)) with + | Function_declaration (id, f) -> ExportFun (id, f) + | _ -> assert false) + | ExportClass (id, f) -> ( + match m#statement (Class_declaration (id, f)) with + | Class_declaration (id, f) -> ExportClass (id, f) + | _ -> assert false) + | ExportNames l -> ExportNames (List.map ~f:(fun (id, s) -> m#ident id, s) l) + | ExportDefaultFun (id, decl) -> ( + match m#statement (Function_declaration (id, decl)) with + | Function_declaration (id, decl) -> ExportDefaultFun (id, decl) + | _ -> assert false) + | ExportDefaultClass (id, decl) -> ( + match m#statement (Class_declaration (id, decl)) with + | Class_declaration (id, decl) -> ExportDefaultClass (id, decl) + | _ -> assert false) + | ExportDefaultExpression e -> ExportDefaultExpression (m#expression e) + | ExportFrom l -> ExportFrom l + | CoverExportFrom e -> CoverExportFrom (m#early_error e) method statement_o x = match x with @@ -340,6 +385,10 @@ class type iterator = object method program : Javascript.program -> unit method function_body : Javascript.statement_list -> unit + + method import : import -> unit + + method export : export -> unit end (* generic js ast iterator *) @@ -466,6 +515,31 @@ class iter : iterator = match final with | None -> () | Some s -> m#block s) + | Import (x, _loc) -> m#import x + | Export (x, _loc) -> m#export x + + method import { from = _; kind } = + match kind with + | Namespace (iopt, i) -> + Option.iter ~f:m#ident iopt; + m#ident i + | Named (iopt, l) -> + Option.iter ~f:m#ident iopt; + List.iter ~f:(fun (_, id) -> m#ident id) l + | Default import_default -> m#ident import_default + | SideEffect -> () + + method export e = + match e with + | ExportVar (k, l) -> m#statement (Variable_statement (k, l)) + | ExportFun (id, f) -> m#statement (Function_declaration (id, f)) + | ExportClass (id, f) -> m#statement (Class_declaration (id, f)) + | ExportNames l -> List.iter ~f:(fun (id, _) -> m#ident id) l + | ExportDefaultFun (id, decl) -> m#statement (Function_declaration (id, decl)) + | ExportDefaultClass (id, decl) -> m#statement (Class_declaration (id, decl)) + | ExportDefaultExpression e -> m#expression e + | ExportFrom { from = _; kind = _ } -> () + | CoverExportFrom e -> m#early_error e method statement_o x = match x with @@ -986,6 +1060,17 @@ class free = | Some f -> Some (m#block f) in Try_statement (b, w, f) + | Import ({ from = _; kind }, _) -> + (match kind with + | Namespace (iopt, i) -> + Option.iter ~f:m#def_local iopt; + m#def_local i + | Named (iopt, l) -> + Option.iter ~f:m#def_local iopt; + List.iter ~f:(fun (_, id) -> m#def_local id) l + | Default import_default -> m#def_local import_default + | SideEffect -> ()); + super#statement x | _ -> super#statement x method for_binding k x = @@ -1003,10 +1088,11 @@ class free = end type scope = + | Module | Lexical_block | Fun_block of ident option -class rename_variable = +class rename_variable ~esm = let declared scope params body = let declared_names = ref StringSet.empty in let decl_var x = @@ -1015,6 +1101,7 @@ class rename_variable = | _ -> () in (match scope with + | Module -> () | Lexical_block -> () | Fun_block None -> () | Fun_block (Some x) -> decl_var x); @@ -1032,13 +1119,14 @@ class rename_variable = method statement x = match scope, x with - | Fun_block _, Function_declaration (id, fd) -> + | (Fun_block _ | Module), Function_declaration (id, fd) -> decl_var id; self#fun_decl fd | Lexical_block, Function_declaration (_, fd) -> self#fun_decl fd - | (Lexical_block | Fun_block _), Class_declaration (id, cl_decl) -> + | (Fun_block _ | Module), Class_declaration (id, cl_decl) -> decl_var id; self#class_decl cl_decl + | Lexical_block, Class_declaration (_, cl_decl) -> self#class_decl cl_decl | _, For_statement (Right (((Const | Let) as k), l), _e1, _e2, (st, _loc)) -> let m = {} in List.iter ~f:(m#variable_declaration k) l; @@ -1056,13 +1144,35 @@ class rename_variable = List.iter l ~f:(fun (_, s) -> m#statements s); Option.iter def ~f:(fun l -> m#statements l); List.iter l' ~f:(fun (_, s) -> m#statements s) - | (Fun_block _ | Lexical_block), _ -> super#statement x + | _, Import ({ kind; from = _ }, _loc) -> ( + match kind with + | Namespace (iopt, i) -> + Option.iter ~f:decl_var iopt; + decl_var i + | Named (iopt, l) -> + Option.iter ~f:decl_var iopt; + List.iter ~f:(fun (_, id) -> decl_var id) l + | Default import_default -> decl_var import_default + | SideEffect -> ()) + | (Fun_block _ | Lexical_block | Module), _ -> super#statement x + + method export e = + match e with + | ExportVar (_k, _l) -> () + | ExportFun (_id, _f) -> () + | ExportClass (_id, _f) -> () + | ExportNames l -> List.iter ~f:(fun (id, _) -> self#ident id) l + | ExportDefaultFun (id, decl) -> self#statement (Function_declaration (id, decl)) + | ExportDefaultClass (id, decl) -> self#statement (Class_declaration (id, decl)) + | ExportDefaultExpression e -> self#expression e + | ExportFrom { from = _; kind = _ } -> () + | CoverExportFrom _ -> () method variable_declaration k l = if match scope, k with - | (Lexical_block | Fun_block _), (Let | Const) -> depth = 0 + | (Lexical_block | Fun_block _ | Module), (Let | Const) -> depth = 0 | Lexical_block, Var -> false - | Fun_block _, Var -> true + | (Fun_block _ | Module), Var -> true then let ids = bound_idents_of_variable_declaration l in List.iter ids ~f:decl_var @@ -1073,9 +1183,9 @@ class rename_variable = method for_binding k p = if match scope, k with - | (Lexical_block | Fun_block _), (Let | Const) -> depth = 0 + | (Lexical_block | Fun_block _ | Module), (Let | Const) -> depth = 0 | Lexical_block, Var -> false - | Fun_block _, Var -> true + | (Fun_block _ | Module), Var -> true then match p with | BindingIdent i -> decl_var i @@ -1123,8 +1233,13 @@ class rename_variable = k, m'#formal_parameter_list params, m'#function_body body, m#loc nid method program p = - let m' = m#update_state Lexical_block [] p in - m'#statements p + if esm + then + let m' = m#update_state Module [] p in + m'#statements p + else + let m' = m#update_state Lexical_block [] p in + m'#statements p method expression e = match e with diff --git a/compiler/lib/js_traverse.mli b/compiler/lib/js_traverse.mli index f931214e16..062402eced 100644 --- a/compiler/lib/js_traverse.mli +++ b/compiler/lib/js_traverse.mli @@ -64,6 +64,10 @@ class type mapper = object method program : program -> program method function_body : statement_list -> statement_list + + method import : import -> import + + method export : export -> export end class type iterator = object @@ -102,6 +106,10 @@ class type iterator = object method program : Javascript.program -> unit method function_body : Javascript.statement_list -> unit + + method import : import -> unit + + method export : export -> unit end class map : mapper @@ -152,10 +160,11 @@ end class free : freevar type scope = + | Module | Lexical_block | Fun_block of ident option -class rename_variable : object ('a) +class rename_variable : esm:bool -> object ('a) inherit mapper method update_state : scope -> Javascript.ident list -> Javascript.statement_list -> 'a diff --git a/compiler/lib/parse_js.ml b/compiler/lib/parse_js.ml index d45d0c2372..6bbe1e5451 100644 --- a/compiler/lib/parse_js.ml +++ b/compiler/lib/parse_js.ml @@ -514,10 +514,26 @@ let parse_aux the_parser (lexbuf : Lexer.t) = raise (Parsing_error (Parse_info.t_of_pos p)) let fail_early = - object - inherit Js_traverse.iter + object (m) + inherit Js_traverse.iter as super method early_error p = raise (Parsing_error p.loc) + + method statement s = + match s with + | Import (_, loc) -> raise (Parsing_error loc) + | Export (_, loc) -> raise (Parsing_error loc) + | _ -> super#statement s + + method program p = + List.iter p ~f:(fun ((p : Javascript.statement), _loc) -> + match p with + | Import _ -> super#statement p + | Export (e, _) -> ( + match e with + | CoverExportFrom e -> m#early_error e + | _ -> super#statement p) + | _ -> super#statement p) end let check_program p = List.iter p ~f:(function _, p -> fail_early#program [ p ]) diff --git a/compiler/lib/stdlib.ml b/compiler/lib/stdlib.ml index 4dc3322260..4a415f9a8a 100644 --- a/compiler/lib/stdlib.ml +++ b/compiler/lib/stdlib.ml @@ -988,6 +988,8 @@ module Utf8_string : sig val of_string_exn : string -> t val compare : t -> t -> int + + val equal : t -> t -> bool end = struct type t = Utf8 of string [@@ocaml.unboxed] @@ -997,6 +999,8 @@ end = struct else invalid_arg "Utf8_string.of_string: invalid utf8 string" let compare (Utf8 x) (Utf8 y) = String.compare x y + + let equal (Utf8 x) (Utf8 y) = String.equal x y end module Int = struct diff --git a/compiler/tests-compiler/scopes.ml b/compiler/tests-compiler/scopes.ml index 0780231362..9138418ca0 100644 --- a/compiler/tests-compiler/scopes.ml +++ b/compiler/tests-compiler/scopes.ml @@ -262,3 +262,290 @@ let%expect_test "let inside block" = 6: } 7: ()); 4 2 |}] + +let%expect_test "import" = + let test ?(module_ = false) js_prog = + let name = if module_ then "test.mjs" else "test.js" in + let js_file = js_prog |> Filetype.js_text_of_string |> Filetype.write_js ~name in + let js_min_file = + js_file + |> jsoo_minify + ~flags:[ "--enable"; "stable_var"; "--enable"; "shortvar" ] + ~pretty:true + in + print_file (Filetype.path_of_js_file js_min_file); + let js_min_file2 = + js_file + |> jsoo_minify + ~flags:[ "--enable"; "stable_var"; "--disable"; "shortvar" ] + ~pretty:true + in + print_file (Filetype.path_of_js_file js_min_file2); + check_javascript js_file |> print_endline + in + let t = test ~module_:true in + t {| +import defaultExport from "./module-name.mjs"; +|}; + [%expect + {| + $ cat "test.min.js" + 1: import v1 from "./module-name.mjs"; + $ cat "test.min.js" + 1: import defaultExport from "./module-name.mjs"; |}]; + t {| +import * as name from "./module-name.mjs"; +|}; + [%expect + {| + $ cat "test.min.js" + 1: import * as v1 from "./module-name.mjs"; + $ cat "test.min.js" + 1: import * as name from "./module-name.mjs"; |}]; + t {| +import { export1 } from "./module-name.mjs"; +|}; + [%expect + {| + $ cat "test.min.js" + 1: import { export1 as v1 } from "./module-name.mjs"; + $ cat "test.min.js" + 1: import { export1 } from "./module-name.mjs"; |}]; + t {| +import { export1 as alias1 } from "./module-name.mjs"; +|}; + [%expect + {| + $ cat "test.min.js" + 1: import { export1 as v1 } from "./module-name.mjs"; + $ cat "test.min.js" + 1: import { export1 as alias1 } from "./module-name.mjs"; |}]; + t {| +import { default as alias } from "module-name"; +|}; + [%expect + {| + $ cat "test.min.js" + 1: import { default as v1 } from "module-name"; + $ cat "test.min.js" + 1: import { default as alias } from "module-name"; |}]; + t {| +import { export1, export2 } from "./module-name.mjs"; +|}; + [%expect + {| + $ cat "test.min.js" + 1: import { export1 as v1, export2 as v2 } from "./module-name.mjs"; + $ cat "test.min.js" + 1: import { export1, export2 } from "./module-name.mjs"; |}]; + t {| +import { export1, export2 as alias2, /* … */ } from "./module-name.mjs"; +|}; + [%expect + {| + $ cat "test.min.js" + 1: import { export1 as v2, export2 as v1 } from "./module-name.mjs"; + $ cat "test.min.js" + 1: import { export1, export2 as alias2 } from "./module-name.mjs"; |}]; + t {| +import { "string name" as alias } from "./module-name.mjs"; +|}; + [%expect + {| + $ cat "test.min.js" + 1: import { "string name" as v1 } from "./module-name.mjs"; + $ cat "test.min.js" + 1: import { "string name" as alias } from "./module-name.mjs"; |}]; + t {| +import defaultExport, { export1, /* … */ } from "./module-name.mjs"; +|}; + [%expect + {| + $ cat "test.min.js" + 1: import v1, { export1 as v2 } from "./module-name.mjs"; + $ cat "test.min.js" + 1: import defaultExport, { export1 } from "./module-name.mjs"; |}]; + t {| +import defaultExport, * as name from "./module-name.mjs"; +|}; + [%expect + {| + $ cat "test.min.js" + 1: import v1, * as v2 from "./module-name.mjs"; + $ cat "test.min.js" + 1: import defaultExport, * as name from "./module-name.mjs"; |}]; + t {| +import "./module-name.mjs"; +|}; + [%expect + {| + $ cat "test.min.js" + 1: import "./module-name.mjs"; + $ cat "test.min.js" + 1: import "./module-name.mjs"; |}] + +let%expect_test "export" = + let test ?(module_ = false) js_prog = + try + let name = if module_ then "test.mjs" else "test.js" in + let js_file = js_prog |> Filetype.js_text_of_string |> Filetype.write_js ~name in + let js_min_file = + js_file + |> jsoo_minify + ~flags:[ "--enable"; "stable_var"; "--enable"; "shortvar" ] + ~pretty:true + in + print_file (Filetype.path_of_js_file js_min_file); + check_javascript js_file |> print_endline + with e -> print_endline (Printexc.to_string e) + in + let t = test ~module_:true in + t {| +var name1, nameN; +export { name1, /* …, */ nameN }; +|}; + [%expect + {| + $ cat "test.min.js" + 1: var name1, nameN; export { name1, nameN }; |}]; + t + {| +var variable1, variable2, nameN; +export { variable1 as name1, variable2 as name2, /* …, */ nameN }; +|}; + [%expect + {| + $ cat "test.min.js" + 1: var variable1, variable2, nameN; + 2: export { variable1 as name1, variable2 as name2, nameN }; |}]; + t {| +var variable1; +export { variable1 as "string name" }; +|}; + [%expect + {| + $ cat "test.min.js" + 1: var variable1; export { variable1 as "string name" }; |}]; + t {| +var name1 +export { name1 as default /*, … */ }; +|}; + [%expect {| + $ cat "test.min.js" + 1: var name1; export { name1 as default }; |}]; + t {| +export * from "module-name"; +|}; + [%expect {| + $ cat "test.min.js" + 1: export * from "module-name"; |}]; + t {| +export * as name1 from "module-name"; +|}; + [%expect {| + $ cat "test.min.js" + 1: export * as name1 from "module-name"; |}]; + t {| +export { name1, /* …, */ nameN } from "module-name"; +|}; + [%expect + {| + $ cat "test.min.js" + 1: export { name1, nameN } from "module-name"; |}]; + t + {| +export { import1 as name1, import2 as name2, /* …, */ nameN } from "module-name"; +|}; + [%expect + {| + $ cat "test.min.js" + 1: export { import1 as name1, import2 as name2, nameN } from "module-name"; |}]; + t {| +export { default, /* …, */ } from "module-name"; +|}; + [%expect {| + $ cat "test.min.js" + 1: export { default } from "module-name"; |}]; + t {| +export { default as name1 } from "module-name"; +|}; + [%expect + {| + $ cat "test.min.js" + 1: export { default as name1 } from "module-name"; |}]; + t {| +export default expression; +|}; + [%expect {| + $ cat "test.min.js" + 1: export default expression; |}]; + t {| +export default ({obj : 2}); +|}; + [%expect {| + $ cat "test.min.js" + 1: export default ({obj: 2}); |}]; + t {| +export default function functionName() { /* … */ } +|}; + [%expect + {| + $ cat "test.min.js" + 1: export default function functionName(){} |}]; + t {| +export default class ClassName { /* … */ }|}; + [%expect {| + $ cat "test.min.js" + 1: export default class ClassName{} |}]; + t {| +export default function* generatorFunctionName() { /* … */ }|}; + [%expect + {| + $ cat "test.min.js" + 1: export default function* generatorFunctionName(){} |}]; + t {| +export default function () { /* … */ }|}; + [%expect {| + $ cat "test.min.js" + 1: export default function(){} |}]; + t {| +export default class { /* … */ }|}; + [%expect {| + $ cat "test.min.js" + 1: export default class{} |}]; + t {| +export default function* () { /* … */ } +|}; + [%expect {| + $ cat "test.min.js" + 1: export default function*(){} |}]; + t {| export let name1, name2/*, … */; // also var |}; + [%expect {| + $ cat "test.min.js" + 1: export let name1, name2; |}]; + t {| export const name1 = 1, name2 = 2/*, … */; // also var, let |}; + [%expect {| + $ cat "test.min.js" + 1: export const name1 = 1, name2 = 2; |}]; + t {| export function functionName() { /* … */ } |}; + [%expect {| + $ cat "test.min.js" + 1: export function functionName(){} |}]; + t {| export class ClassName { /* … */ } |}; + [%expect {| + $ cat "test.min.js" + 1: export class ClassName{} |}]; + t {| export function* generatorFunctionName() { /* … */ } |}; + [%expect + {| + $ cat "test.min.js" + 1: export function* generatorFunctionName(){} |}]; + t {| export const { name1, name2: bar } = o; |}; + [%expect + {| + $ cat "test.min.js" + 1: export const {name1: name1, name2: bar} = o; |}]; + t {| export const [ name1, name2 ] = array; |}; + [%expect {| + $ cat "test.min.js" + 1: export const [name1, name2] = array; |}]