123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276openPpxlib(* Extract module path from a Longident *)letreclongident_to_string=function|Lidents->s|Ldot(lid,s)->longident_to_stringlid^"."^s|Lapply_->failwith"Lapply not supported"(* Build a Longident for Module.field *)letmake_field_accessmod_pathfield=matchmod_pathwith|Lidentm->Ldot(Lidentm,field)|Ldot_asl->Ldot(l,field)|Lapply_->failwith"Lapply not supported"(* Collect type variables from a type expression *)letreccollect_type_varstypacc=matchtyp.ptyp_descwith|Ptyp_varname->ifList.memnameaccthenaccelsename::acc|Ptyp_arrow(_,t1,t2)->collect_type_varst2(collect_type_varst1acc)|Ptyp_tupletypes->List.fold_left(funacct->collect_type_varstacc)acctypes|Ptyp_constr(_,types)->List.fold_left(funacct->collect_type_varstacc)acctypes|Ptyp_poly(_,t)->collect_type_varstacc|_->accletcomponent_mapper=object(self)inheritAst_traverse.mapassuper(* Transform JSX expressions *)method!expressionexpr=matchexpr.pexp_descwith(* Match JSX: Component.createElement(~prop=val, ~children=[...], ()) *)|Pexp_apply(fn,args)->(matchfn.pexp_descwith|Pexp_ident{txt=Ldot(module_path,"createElement");loc=_}whenList.exists(fun(lbl,_)->matchlblwith|Labelled"children"->true|_->false)args->(* This looks like JSX - transform it *)letloc=expr.pexp_locin(* Separate children from other props *)letchildren_expr=refNoneinletprops=List.filter_map(fun(lbl,e)->matchlblwith|Labelled"children"->(* Check if children is empty list [] or single-element list *)(matche.pexp_descwith|Pexp_construct({txt=Lident"[]";_},None)->()|Pexp_construct({txt=Lident"::";_},Sometuple)->(* Single or multiple children in a list *)(matchtuple.pexp_descwith|Pexp_tuple[child;{pexp_desc=Pexp_construct({txt=Lident"[]";_},None);_}]->(* Single child - extract it *)children_expr:=Some(self#expressionchild)|_->(* Multiple children - pass list directly *)children_expr:=Some(self#expressione))|_->children_expr:=Some(self#expressione));None|Labelledname->Some(name,self#expressione)|Optionalname->Some(name,self#expressione)|Nolabel->None)argsin(* Build the props record *)letfirst_field=make_field_accessmodule_path(matchpropswith|(name,_)::_->name|[]->"children")inletrecord_fields=(List.map(fun(name,value)->({txt=Lidentname;loc},value))props)@(match!children_exprwith|Somechildren->[({txt=Lident"children";loc},children)]|None->[])in(* If we have fields, create a record; otherwise pass unit *)ifList.lengthrecord_fields>0thenletrecord=Ast_builder.Default.pexp_record~loc((let(_name,value)=List.hdrecord_fieldsin({txt=first_field;loc},value))::(List.tlrecord_fields|>List.map(fun(name,value)->({txt=name.txt;loc},value))))NoneinAst_builder.Default.pexp_apply~locfn[(Nolabel,record)]else(* No props - pass unit *)Ast_builder.Default.pexp_apply~locfn[(Nolabel,Ast_builder.Default.pexp_construct~loc{txt=Lident"()";loc}None)]|_->super#expressionexpr)|_->super#expressionexprmethod!structure_itemitem=matchitem.pstr_descwith|Pstr_value(Nonrecursive,[binding])whenList.exists(funattr->String.equalattr.attr_name.txt"component")binding.pvb_attributes->(* Found a [@component] let make = ... *)letloc=item.pstr_locin(* Extract the function and its labeled arguments *)letrecextract_argsexpracc=matchexpr.pexp_descwith|Pexp_fun(Labelledlabel,_default,pat,body)->lettyp=matchpat.ppat_descwith|Ppat_constraint(_,t)->Somet|_->Noneinextract_argsbody((label,typ)::acc)|Pexp_fun(Optionallabel,_default,pat,body)->lettyp=matchpat.ppat_descwith|Ppat_constraint(_,t)->Somet|_->Noneinextract_argsbody((label,typ)::acc)|_->(List.revacc,expr)inletargs,body=extract_argsbinding.pvb_expr[]in(* Transform any JSX in the body *)letbody=self#expressionbodyinifList.lengthargs=0then(* No labeled args - just a simple make function, return as-is but remove attribute *)letnew_binding={bindingwithpvb_attributes=[];pvb_expr=self#expressionbinding.pvb_expr}in{itemwithpstr_desc=Pstr_value(Nonrecursive,[new_binding])}else(* Collect type variables from all argument types *)lettype_vars=List.fold_left(funacc(_,typ)->matchtypwith|Somet->collect_type_varstacc|None->acc)[]args|>List.rev(* Preserve order *)in(* Generate props record type with type parameters *)letprops_fields=List.map(fun(label,typ)->letfield_type=matchtypwith|Somet->t|None->Ast_builder.Default.ptyp_constr~loc{txt=Lident"string";loc}[]inAst_builder.Default.label_declaration~loc~name:{txt=label;loc}~mutable_:Immutable~type_:field_type)argsin(* Create type parameters for the props type *)lettype_params=List.map(funvar->(Ast_builder.Default.ptyp_var~locvar,(NoVariance,NoInjectivity)))type_varsinletprops_type=Ast_builder.Default.pstr_type~locNonrecursive[Ast_builder.Default.type_declaration~loc~name:{txt="props";loc}~params:type_params~cstrs:[]~private_:Public~kind:(Ptype_recordprops_fields)~manifest:None;]in(* Generate destructuring pattern for make function argument *)letdestructure_pat=Ast_builder.Default.ppat_record~loc(List.map(fun(label,_)->({txt=Lidentlabel;loc},Ast_builder.Default.ppat_var~loc{txt=label;loc}))args)Closedin(* Generate make function: let make = (props) => { let {a, b, ...} = props; body } *)letprops_pat=Ast_builder.Default.ppat_var~loc{txt="props";loc}inletprops_var=Ast_builder.Default.pexp_ident~loc{txt=Lident"props";loc}inletbody_with_destructure=Ast_builder.Default.pexp_let~locNonrecursive[Ast_builder.Default.value_binding~loc~pat:destructure_pat~expr:props_var]bodyinletmake_fun=Ast_builder.Default.pexp_fun~locNolabelNoneprops_patbody_with_destructureinletmake_binding=Ast_builder.Default.pstr_value~locNonrecursive[Ast_builder.Default.value_binding~loc~pat:(Ast_builder.Default.ppat_var~loc{txt="make";loc})~expr:make_fun]in(* Generate createElement: let createElement = (props) => Element.createElement(() => make(props)) *)letcreate_element_body=letmake_call=Ast_builder.Default.pexp_apply~loc(Ast_builder.Default.pexp_ident~loc{txt=Lident"make";loc})[(Nolabel,props_var)]inletthunk=Ast_builder.Default.pexp_fun~locNolabelNone(Ast_builder.Default.ppat_construct~loc{txt=Lident"()";loc}None)make_callinAst_builder.Default.pexp_apply~loc(Ast_builder.Default.pexp_ident~loc{txt=Ldot(Lident"Element","createElement");loc})[(Nolabel,thunk)]inletcreate_element_fun=Ast_builder.Default.pexp_fun~locNolabelNoneprops_patcreate_element_bodyinletcreate_element_binding=Ast_builder.Default.pstr_value~locNonrecursive[Ast_builder.Default.value_binding~loc~pat:(Ast_builder.Default.ppat_var~loc{txt="createElement";loc})~expr:create_element_fun]in(* Return: type props, let make, let createElement *)Ast_builder.Default.pstr_include~loc{pincl_mod=Ast_builder.Default.pmod_structure~loc[props_type;make_binding;create_element_binding];pincl_loc=loc;pincl_attributes=[];}|_->super#structure_itemitemendlet()=Driver.register_transformation"ppx_component"~impl:(funstr->component_mapper#structurestr)