Browse Source

[js] ES6-class generation support (#7806)

Dan Korostelev 6 years ago
parent
commit
406d3fb2bf
5 changed files with 470 additions and 33 deletions
  1. 231 0
      src/filters/ES6Ctors.ml
  2. 198 26
      src/generators/genjs.ml
  3. 26 1
      std/js/_std/Type.hx
  4. 2 2
      tests/runci/targets/Js.hx
  5. 13 4
      tests/unit/src/RunSauceLabs.hx

+ 231 - 0
src/filters/ES6Ctors.ml

@@ -0,0 +1,231 @@
+(*
+	The Haxe Compiler
+	Copyright (C) 2005-2019  Haxe Foundation
+
+	This program is free software; you can redistribute it and/or
+	modify it under the terms of the GNU General Public License
+	as published by the Free Software Foundation; either version 2
+	of the License, or (at your option) any later version.
+
+	This program is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+	GNU General Public License for more details.
+
+	You should have received a copy of the GNU General Public License
+	along with this program; if not, write to the Free Software
+	Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ *)
+open Common
+open Globals
+open Type
+open Texpr.Builder
+
+(* name of the method to which the constructor is extracted *)
+let ctor_method_name = "_hx_constructor"
+
+(* name of the static bool flag to skip constructor body execution *)
+let skip_ctor_flag_name = "_hx_skip_constructor"
+
+(* replace super(a,b,c) with super._hx_constructor(a,b,c) *)
+let rec replace_super_call e =
+	match e.eexpr with
+	| TCall ({ eexpr = TConst TSuper } as e_super, args) ->
+		let e_super_hxctor = { e_super with eexpr = TField (e_super, FDynamic ctor_method_name) } in
+		{ e with eexpr = TCall (e_super_hxctor, args) }
+	| _ ->
+		map_expr replace_super_call e
+
+exception Accessed_this of texpr
+
+(* return whether given expression has `this` access before calling `super` *)
+let has_this_before_super e = 
+	let rec loop e =
+		match e.eexpr with 
+		| TCall ({ eexpr = TConst TSuper }, args) ->
+			List.iter loop args;
+			raise Exit
+		| TConst TThis ->
+			raise (Accessed_this e)
+		| _ ->
+			Type.iter loop e
+	in
+	try
+		(loop e; None)
+	with
+		| Exit -> None
+		| Accessed_this e -> Some e
+
+
+(*
+	the filter works in two passes:
+	- mark classes whether they need to support constructor skipping and/or they skip parent's constructor
+	- change the constructors of marked classes (extract body into method and/or add skipping super calls)
+*)
+let rewrite_ctors com =
+	(* we mark classes that need changing by storing them in these two maps *)
+	let needs_ctor_skipping, does_ctor_skipping, inject_super =
+		let l = List.length com.types in
+		Hashtbl.create l, Hashtbl.create l, Hashtbl.create l
+	in
+
+	(*
+		we're using a reference to the root of the inheritance chain so we can easily
+		generate RootClass._hx_skip_constructor expressions
+	*)
+	let mark_does_ctor_skipping cl cl_super p_this_access =
+		let rec mark_needs_ctor_skipping cl = 
+			(* for non haxe-generated extern classes we can't generate any valid code, so just fail *)
+			if cl.cl_extern && not (Meta.has Meta.HxGen cl.cl_meta) then begin
+				abort "Must call `super()` constructor before accessing `this` in classes derived from an extern class with constructor" p_this_access;
+			end;
+			try
+				Hashtbl.find needs_ctor_skipping cl.cl_path
+			with Not_found ->
+				let root =
+					match cl.cl_super with
+					| Some ({ cl_constructor = Some _ } as cl_super,_) -> mark_needs_ctor_skipping cl_super
+					| _ -> cl
+				in
+				Hashtbl.add needs_ctor_skipping cl.cl_path root;
+				root
+		in
+		let root_cl = mark_needs_ctor_skipping cl_super in
+		Hashtbl.add does_ctor_skipping cl.cl_path root_cl;
+	in
+
+	let e_empty_super_call = (* super() *)
+		let e_super = mk (TConst TSuper) t_dynamic null_pos in
+		mk (TCall (e_super,[])) com.basic.tvoid null_pos
+	in
+
+	let activated = ref false in
+	let mark t =
+		match t with
+		| TClassDecl ({ cl_constructor = Some { cf_expr = Some { eexpr = TFunction tf } }; cl_super = Some (cl_super,_) } as cl) ->
+			if Type.has_constructor cl_super then begin
+				(* if parent class has a constructor, check for `this` accesses before calling `super()` *)
+				let this_before_super = has_this_before_super tf.tf_expr in
+				Option.may (fun e_this_access ->
+					activated := true;
+					mark_does_ctor_skipping cl cl_super e_this_access.epos
+				) this_before_super
+			end else begin
+				(* if there was no ctor in the parent class, we still gotta call `super` *)
+				Hashtbl.add inject_super cl.cl_path cl; 
+			end
+		| _ -> ()
+	in
+	List.iter mark com.types;
+
+	if !activated then begin
+		(* just some helper common exprs *)
+		let e_false = (make_bool com.basic false null_pos) in 
+		let e_true = (make_bool com.basic true null_pos) in
+		let e_hx_ctor = (* this._hx_constructor *)
+			let ethis = mk (TConst TThis) t_dynamic null_pos  in
+			mk (TField (ethis, FDynamic ctor_method_name)) t_dynamic null_pos
+		in
+
+		let change t =
+			match t with
+			| TClassDecl ({ cl_constructor = Some ({ cf_expr = Some ({ eexpr = TFunction tf_ctor } as ctor_expr) } as cf_ctor) } as cl) ->
+				let does_ctor_skipping = try Some (Hashtbl.find does_ctor_skipping cl.cl_path) with Not_found -> None in
+
+				let add_hx_ctor_method () =
+					let cf_fun_ctor = mk_field ctor_method_name cf_ctor.cf_type cf_ctor.cf_pos null_pos in
+					cf_fun_ctor.cf_expr <- Some (replace_super_call ctor_expr);
+					cf_fun_ctor.cf_kind <- Method MethNormal;
+					cl.cl_ordered_fields <- cf_fun_ctor :: cl.cl_ordered_fields;
+					cl.cl_fields <- PMap.add cf_fun_ctor.cf_name cf_fun_ctor cl.cl_fields;
+				in
+
+				let make_hx_ctor_call e_skip_flag = (* this._hx_constructor(a,b,c) *)
+					let hxctor_call_args = List.map (fun (v,_) -> make_local v null_pos) tf_ctor.tf_args in
+					let hx_ctor_call = mk (TCall (e_hx_ctor, hxctor_call_args)) com.basic.tvoid null_pos in
+					if does_ctor_skipping <> None then
+						mk (TBlock [
+							mk (TBinop (OpAssign, e_skip_flag, e_true)) com.basic.tbool null_pos;
+							e_empty_super_call;
+							mk (TBinop (OpAssign, e_skip_flag, e_false)) com.basic.tbool null_pos;
+							hx_ctor_call
+						]) com.basic.tvoid null_pos
+					else
+						hx_ctor_call
+				in
+
+				let make_skip_flag root_cl = (* TopClass._hx_skip_constructor *)
+					let e_top = mk (TTypeExpr (TClassDecl root_cl)) t_dynamic null_pos in
+					mk (TField (e_top, FDynamic skip_ctor_flag_name)) com.basic.tbool null_pos
+				in
+
+				(match (try Some (Hashtbl.find needs_ctor_skipping cl.cl_path) with Not_found -> None) with
+				| Some root ->
+					add_hx_ctor_method ();
+					
+					if does_ctor_skipping = None && cl != root then
+						(* for intermediate classes that support skipping but don't do skipping themselves, we can just remove the constructor altogether,
+						because the skipping logic is implemented in the parent constructor, and the actual constructor body is moved into _hx_constructor *)
+						cf_ctor.cf_expr <- None
+					else begin
+						let e_skip = 
+							let e_return = (mk (TReturn None) t_dynamic null_pos) in
+							if cl.cl_super = None || (Hashtbl.mem inject_super cl.cl_path)  then
+								(* just `return` *)
+								e_return
+							else
+								(* `{ super(); return; }` *)
+								mk (TBlock [
+									e_empty_super_call;
+									e_return;
+								]) com.basic.tvoid null_pos
+						in
+
+						let e_skip_flag = make_skip_flag root in
+
+						let e_ctor_replaced = { tf_ctor.tf_expr with
+							eexpr = TBlock [
+								mk (TIf (mk_parent e_skip_flag, e_skip, None)) com.basic.tvoid null_pos;
+								make_hx_ctor_call e_skip_flag
+							]
+						} in
+						
+						cf_ctor.cf_expr <- Some { ctor_expr with eexpr = TFunction { tf_ctor with tf_expr = e_ctor_replaced } };
+					end;
+
+					if cl == root then begin
+						let cf_skip_ctor = mk_field skip_ctor_flag_name com.basic.tbool null_pos null_pos in
+						cf_skip_ctor.cf_expr <- Some e_false;
+						cl.cl_ordered_statics <- cf_skip_ctor :: cl.cl_ordered_statics;
+						cl.cl_statics <- PMap.add cf_skip_ctor.cf_name cf_skip_ctor cl.cl_statics;
+					end
+				| None ->
+					(match does_ctor_skipping with
+					| Some root ->
+
+						add_hx_ctor_method ();
+
+						let e_skip_flag = make_skip_flag root in
+
+						let e_ctor_replaced = { tf_ctor.tf_expr with
+							eexpr = TBlock [
+								make_hx_ctor_call e_skip_flag
+							]
+						} in
+						cf_ctor.cf_expr <- Some { ctor_expr with eexpr = TFunction { tf_ctor with tf_expr = e_ctor_replaced } };
+
+					| None -> ())
+				)
+			| _ ->
+				()
+		in
+		List.iter change com.types
+	end;
+
+	Hashtbl.iter (fun _ cl ->
+		match cl with
+		| { cl_constructor = Some ({ cf_expr = Some ({ eexpr = TFunction tf } as e_ctor) } as cf_ctor); cl_super = Some (cl_super,_) } ->
+			cl.cl_constructor <- Some { cf_ctor with cf_expr = Some { e_ctor with eexpr = TFunction { tf with tf_expr = { tf.tf_expr with eexpr = TBlock [e_empty_super_call; tf.tf_expr] } } } };
+		| _ ->
+			assert false
+	) inject_super;

+ 198 - 26
src/generators/genjs.ml

@@ -48,6 +48,7 @@ type ctx = {
 	smap : sourcemap option;
 	js_modern : bool;
 	js_flatten : bool;
+	has_resolveClass : bool;
 	es_version : int;
 	mutable current : tclass;
 	mutable statics : (tclass * string * texpr) list;
@@ -350,14 +351,14 @@ let gen_constant ctx p = function
 	| TBool b -> spr ctx (if b then "true" else "false")
 	| TNull -> spr ctx "null"
 	| TThis -> spr ctx (this ctx)
-	| TSuper -> assert false
+	| TSuper -> assert (ctx.es_version >= 6); spr ctx "super"
 
 let print_deprecation_message com msg p =
 	com.warning msg p
 
 let rec gen_call ctx e el in_value =
 	match e.eexpr , el with
-	| TConst TSuper , params ->
+	| TConst TSuper , params when ctx.es_version < 6 ->
 		(match ctx.current.cl_super with
 		| None -> abort "Missing api.setCurrentClass" e.epos
 		| Some (c,_) ->
@@ -365,7 +366,7 @@ let rec gen_call ctx e el in_value =
 			List.iter (fun p -> print ctx ","; gen_value ctx p) params;
 			spr ctx ")";
 		);
-	| TField ({ eexpr = TConst TSuper },f) , params ->
+	| TField ({ eexpr = TConst TSuper },f) , params when ctx.es_version < 6 ->
 		(match ctx.current.cl_super with
 		| None -> abort "Missing api.setCurrentClass" e.epos
 		| Some (c,_) ->
@@ -588,18 +589,7 @@ and gen_expr ctx e =
 		newline ctx;
 		print ctx "}";
 	| TFunction f ->
-		let old = ctx.in_value, ctx.in_loop in
-		ctx.in_value <- None;
-		ctx.in_loop <- false;
-		let args = List.map (fun (v,_) ->
-			check_var_declaration v;
-			ident v.v_name
-		) f.tf_args in
-		print ctx "function(%s) " (String.concat "," args);
-		gen_expr ctx (fun_block ctx f e.epos);
-		ctx.in_value <- fst old;
-		ctx.in_loop <- snd old;
-		ctx.separator <- true
+		gen_function ctx f e.epos
 	| TCall (e,el) ->
 		gen_call ctx e el false
 	| TArrayDecl el ->
@@ -753,6 +743,20 @@ and gen_expr ctx e =
 	);
 	clear_mapping ()
 
+and gen_function ?(keyword="function") ctx f pos =
+	let old = ctx.in_value, ctx.in_loop in
+	ctx.in_value <- None;
+	ctx.in_loop <- false;
+	let args = List.map (fun (v,_) ->
+		check_var_declaration v;
+		ident v.v_name
+	) f.tf_args in
+	print ctx "%s(%s) " keyword (String.concat "," args);
+	gen_expr ctx (fun_block ctx f pos);
+	ctx.in_value <- fst old;
+	ctx.in_loop <- snd old;
+	ctx.separator <- true
+
 and gen_block_element ?(after=false) ctx e =
 	match e.eexpr with
 	| TBlock el ->
@@ -1056,19 +1060,13 @@ let generate_class___name__ ctx c =
 		newline ctx;
 	end
 
-let generate_class ctx c =
-	ctx.current <- c;
-	ctx.id_counter <- 0;
-	(match c.cl_path with
-	| [],"Function" -> abort "This class redefine a native one" c.cl_pos
-	| _ -> ());
+let generate_class_es3 ctx c =
 	let p = s_path ctx c.cl_path in
-	let hxClasses = has_feature ctx "Type.resolveClass" in
 	if ctx.js_flatten then
 		print ctx "var "
 	else
 		generate_package_create ctx c.cl_path;
-	if ctx.js_modern || not hxClasses then
+	if ctx.js_modern || not ctx.has_resolveClass then
 		print ctx "%s = " p
 	else
 		print ctx "%s = $hxClasses[\"%s\"] = " p (dot_path c.cl_path);
@@ -1083,7 +1081,7 @@ let generate_class ctx c =
 			| _ -> (print ctx "function() { }"); ctx.separator <- true)
 	);
 	newline ctx;
-	if ctx.js_modern && hxClasses then begin
+	if ctx.js_modern && ctx.has_resolveClass then begin
 		print ctx "$hxClasses[\"%s\"] = %s" (dot_path c.cl_path) p;
 		newline ctx;
 	end;
@@ -1150,6 +1148,176 @@ let generate_class ctx c =
 	end;
 	flush ctx
 
+let generate_class_es6 ctx c =
+	let p = s_path ctx c.cl_path in
+
+	let cls_name =
+		if not ctx.js_flatten && (fst c.cl_path) <> [] then begin
+			generate_package_create ctx c.cl_path;
+			print ctx "%s = " p;
+			Path.flat_path c.cl_path
+		end else
+			p
+	in
+	print ctx "class %s" cls_name;
+
+	Option.may (fun (csup,_) ->
+		let psup = ctx.type_accessor (TClassDecl csup) in
+		print ctx " extends %s" psup
+	) c.cl_super;
+
+	spr ctx " {";
+	let close_block = open_block ctx in
+
+	(match c.cl_constructor with
+	| Some { cf_expr = Some ({ eexpr = TFunction f; epos = p }) } ->
+		newline ctx;
+		gen_function ~keyword:"constructor" ctx f p;
+		ctx.separator <- false
+	| _ -> ());
+
+	List.iter (fun cf ->
+		match cf.cf_kind, cf.cf_expr with
+		| Method _, Some { eexpr = TFunction f; epos = pos } ->
+			check_field_name c cf;
+			newline ctx;
+			gen_function ~keyword:cf.cf_name ctx f pos;
+			ctx.separator <- false
+		| _ -> ()
+	) c.cl_ordered_fields;
+	
+	let exposed_static_methods = ref [] in
+	let nonmethod_statics =
+		List.filter (fun cf ->
+			match cf.cf_kind, cf.cf_expr with
+			| Method _, Some { eexpr = TFunction f; epos = pos } ->
+				check_field_name c cf;
+				newline ctx;
+				gen_function ~keyword:("static " ^ cf.cf_name) ctx f pos;
+				ctx.separator <- false;
+
+				(match get_exposed ctx ((dot_path c.cl_path) ^ (static_field c cf.cf_name)) cf.cf_meta with
+				| [s] -> exposed_static_methods := (s,cf.cf_name) :: !exposed_static_methods;
+				| _ -> ());
+
+				false
+			| _ -> true
+		) c.cl_ordered_statics
+	in
+
+	close_block ();
+	newline ctx;
+	spr ctx "}";
+	newline ctx;
+
+	List.iter (fun (path,name) -> 
+		print ctx "$hx_exports%s = %s.%s;" (path_to_brackets path) p name;
+		newline ctx
+	) !exposed_static_methods;
+
+	List.iter (gen_class_static_field ctx c) nonmethod_statics;
+
+	let expose = (match get_exposed ctx (dot_path c.cl_path) c.cl_meta with [s] -> "$hx_exports" ^ (path_to_brackets s) | _ -> "") in
+	if expose <> "" || ctx.has_resolveClass then begin
+		if ctx.has_resolveClass then begin
+			print ctx "$hxClasses[\"%s\"] = " (dot_path c.cl_path)
+		end;
+		if expose <> "" then begin
+			print ctx "%s = " expose
+		end;
+		spr ctx p;
+		newline ctx;
+	end;
+
+	generate_class___name__ ctx c;
+
+	(match c.cl_implements with
+	| [] -> ()
+	| l ->
+		print ctx "%s.__interfaces__ = [%s]" p (String.concat "," (List.map (fun (i,_) -> ctx.type_accessor (TClassDecl i)) l));
+		newline ctx;
+	);
+
+	let has_property_reflection =
+		(has_feature ctx "Reflect.getProperty") || (has_feature ctx "Reflect.setProperty")
+	in
+	let gen_props props =
+		String.concat "," (List.map (fun (p,v) -> p ^": \""^v^"\"") props)
+	in
+
+	if has_property_reflection then begin
+		(match Codegen.get_properties nonmethod_statics with
+		| [] -> ()
+		| props ->
+			print ctx "%s.__properties__ = {%s}" p (gen_props props);
+			ctx.separator <- true;
+			newline ctx);
+	end;
+
+	(match c.cl_super with
+	| Some (csup,_) ->
+		if has_feature ctx "js.Boot.__instanceof" || has_feature ctx "Type.getSuperClass" then begin
+			let psup = ctx.type_accessor (TClassDecl csup) in
+			print ctx "%s.__super__ = %s" p psup;
+			newline ctx
+		end
+	| None -> ());
+
+	if has_feature ctx "Type.getInstanceFields" then begin
+		let rec loop c acc =
+			let fields = List.filter (fun cf -> cf.cf_name <> ES6Ctors.ctor_method_name && is_physical_field cf && not (List.memq cf c.cl_overrides)) c.cl_ordered_fields in
+			let acc = acc @ List.map (fun cf -> "\"" ^ cf.cf_name ^ "\"") fields in
+			match c.cl_super with
+			| None -> acc
+			| Some (csup,_) -> loop csup acc
+		in
+		match loop c [] with
+		| [] -> ()
+		| fl ->
+			print ctx "%s.__instanceFields__ = [%s];" p (String.concat "," fl);
+			newline ctx
+	end;
+
+	let has_class = has_feature ctx "js.Boot.getClass" && (c.cl_super <> None || c.cl_ordered_fields <> [] || c.cl_constructor <> None) in
+	let props_to_generate = if has_property_reflection then Codegen.get_properties c.cl_ordered_fields else [] in
+
+	if has_class || props_to_generate <> [] then begin
+		print ctx "Object.assign(%s.prototype, {" p;
+		let bend = open_block ctx in
+
+		if has_class then begin
+			newprop ctx;
+			print ctx "__class__: %s" p
+		end;
+
+		if props_to_generate <> [] then begin
+			newprop ctx;
+			match c.cl_super with
+			| Some (csup, _) when Codegen.has_properties csup ->
+				let psup = ctx.type_accessor (TClassDecl csup) in
+				print ctx "__properties__: Object.assign({}, %s.prototype.__properties__, {%s})" psup (gen_props props_to_generate)
+			| _ -> 
+				print ctx "__properties__: {%s}" (gen_props props_to_generate)
+		end;
+
+		bend();
+		print ctx "\n})";
+		newline ctx
+	end;
+
+	flush ctx
+
+let generate_class ctx c =
+	ctx.current <- c;
+	ctx.id_counter <- 0;
+	(match c.cl_path with
+	| [],"Function" -> abort "This class redefine a native one" c.cl_pos
+	| _ -> ());
+	if ctx.es_version >= 6 then
+		generate_class_es6 ctx c
+	else
+		generate_class_es3 ctx c
+
 let generate_enum ctx e =
 	let p = s_path ctx e.e_path in
 	let dotp = dot_path e.e_path in
@@ -1318,6 +1486,7 @@ let alloc_ctx com =
 		smap = smap;
 		js_modern = not (Common.defined com Define.JsClassic);
 		js_flatten = not (Common.defined com Define.JsUnflatten);
+		has_resolveClass = Common.has_feature com "Type.resolveClass";
 		es_version = (try int_of_string (Common.defined_value com Define.JsEs) with _ -> 0);
 		statics = [];
 		inits = [];
@@ -1360,6 +1529,9 @@ let generate com =
 
 	setup_kwds (if ctx.es_version >= 5 then es5kwds else es3kwds);
 
+	if ctx.es_version >= 6 then
+		ES6Ctors.rewrite_ctors com;
+
 	let exposed = List.concat (List.map (fun t ->
 		match t with
 			| TClassDecl c ->
@@ -1486,7 +1658,7 @@ let generate com =
 
 	(* TODO: fix $estr *)
 	let vars = [] in
-	let vars = (if has_feature ctx "Type.resolveClass" || (not enums_as_objects && has_feature ctx "Type.resolveEnum") then ("$hxClasses = " ^ (if ctx.js_modern then "{}" else "$hxClasses || {}")) :: vars else vars) in
+	let vars = (if ctx.has_resolveClass || (not enums_as_objects && has_feature ctx "Type.resolveEnum") then ("$hxClasses = " ^ (if ctx.js_modern then "{}" else "$hxClasses || {}")) :: vars else vars) in
 	let vars = if has_feature ctx "has_enum"
 		then ("$estr = function() { return " ^ (ctx.type_accessor (TClassDecl { null_class with cl_path = ["js"],"Boot" })) ^ ".__string_rec(this,''); }") :: vars
 		else vars in
@@ -1504,7 +1676,7 @@ let generate com =
 		ctx.separator <- true;
 		newline ctx
 	);
-	if List.exists (function TClassDecl { cl_extern = false; cl_super = Some _ } -> true | _ -> false) com.types then begin
+	if ctx.es_version < 6 && List.exists (function TClassDecl { cl_extern = false; cl_super = Some _ } -> true | _ -> false) com.types then begin
 		let extend_code =
 			"function $extend(from, fields) {\n" ^
 			(

+ 26 - 1
std/js/_std/Type.hx

@@ -49,7 +49,7 @@ enum ValueType {
 	}
 
 	public static inline function getSuperClass( c : Class<Dynamic> ) : Class<Dynamic> {
-		return (cast c).__super__;
+		return untyped __define_feature__("Type.getSuperClass", c.__super__);
 	}
 
 
@@ -157,6 +157,30 @@ enum ValueType {
 		return createEnum(e,c,params);
 	}
 
+	#if (js_es >= 6)
+	public static function getInstanceFields( c : Class<Dynamic> ) : Array<String> {
+		var fields:Null<Array<String>> = (cast c).__instanceFields__;
+		return if (fields == null) [] else fields.copy();
+	}
+
+	public static function getClassFields( c : Class<Dynamic> ) : Array<String> {
+		var a = js.Object.getOwnPropertyNames(cast c);
+		a.remove("__id__");
+		a.remove("hx__closures__");
+		a.remove("__name__");
+		a.remove("__interfaces__");
+		a.remove("__properties__");
+		a.remove("__instanceFields__");
+		a.remove("__super__");
+		a.remove("__meta__");
+		a.remove("prototype");
+		a.remove("name");
+		a.remove("length");
+		return a;
+	}
+
+	#else
+
 	public static function getInstanceFields( c : Class<Dynamic> ) : Array<String> {
 		var a = [];
 		untyped __js__("for(var i in c.prototype) a.push(i)");
@@ -175,6 +199,7 @@ enum ValueType {
 		a.remove("prototype");
 		return a;
 	}
+	#end
 
 	public static inline function getEnumConstructs( e : Enum<Dynamic> ) : Array<String> {
 		return ((cast e).__constructs__ : Array<String>).copy();

+ 2 - 2
tests/runci/targets/Js.hx

@@ -28,12 +28,12 @@ class Js {
 		getJSDependencies();
 
 		var jsOutputs = [
-			for (es3 in       [[], ["-D", "js-es=3"]])
+			for (es_ver in    [[], ["-D", "js-es=3"], ["-D", "js-es=6"]])
 			for (unflatten in [[], ["-D", "js-unflatten"]])
 			for (classic in   [[], ["-D", "js-classic"]])
 			for (enums_as_objects in [[], ["-D", "js-enums-as-arrays"]])
 			{
-				var extras = args.concat(es3).concat(unflatten).concat(classic).concat(enums_as_objects);
+				var extras = args.concat(es_ver).concat(unflatten).concat(classic).concat(enums_as_objects);
 
 				runCommand("haxe", ["compile-js.hxml"].concat(extras));
 

+ 13 - 4
tests/unit/src/RunSauceLabs.hx

@@ -44,6 +44,13 @@ class RunSauceLabs {
 			!(b.browserName == "internet explorer" && Std.parseInt(b.version) <= 8);
 	}
 
+	static function isEs6(b:Dynamic):Bool {
+		return switch b.browserName {
+			case "internet explorer" | "safari": false;
+			case _: true;
+		}
+	}
+
 	static function main():Void {
 		var serveDomain = "localhost";
 		var servePort = "2000";
@@ -180,10 +187,12 @@ class RunSauceLabs {
 			}
 
 			var browserSuccess = true;
-			var urls = if (!isEs5(caps)) {
-				urls.filter(function(url:String) return url.indexOf("js-es=3") != -1);
-			} else {
-				urls;
+			var urls = urls; // localize captured var
+			if (!isEs5(caps)) {
+				urls = urls.filter(url -> url.indexOf(StringTools.urlEncode("js-es=3")) != -1);
+			}
+			if (!isEs6(caps)) {
+				urls = urls.filter(url -> url.indexOf(StringTools.urlEncode("js-es=6")) == -1);
 			}
 
 			return browser