12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259openPpxlibopenAst_builder.DefaultmoduleList=ListLabelstypetarget=Native|Js(* Since ppxlib doesn't provide a way to get the submodules, we need to keep track of them manually *)letmode=refNativeletshared_folder_prefix=refNoneletrepo_url="https://github.com/ml-in-barcelona/server-reason-react"letissues_url=Printf.sprintf"%s/issues"repo_urlletmatch_substringstringsubstring=tryStr.search_forward(Str.regexp_stringsubstring)string0|>ignore;truewithNot_found->false(* There's no Ppxlib.pexp_list since isn't a parsetree constructor *)letpexp_list~locxs=List.fold_left(List.revxs)~init:[%expr[]]~f:(funxsx->letloc=x.pexp_locin[%expr[%ex]::[%exs]])exceptionErrorofexpressionletraise_errorf~locfmt=Printf.ksprintf(funmsg->letexpr=pexp_extension~loc(Location.error_extensionf~loc"%s"msg)inraise(Errorexpr))fmtletlongident~loctxt={txt=Lidenttxt;loc}letident~loctxt=pexp_ident~loc(longident~loctxt)letmake_string~locstr=Ast_helper.Exp.constant~loc(Ast_helper.Const.stringstr)letreact_dot_component="react.component"letreact_dot_async_dot_component="react.async.component"letreact_dot_client_dot_component="react.client.component"letreact_dot_server_dot_function="react.server.function"(* Helper method to look up the [@react.component] attribute *)lethasAttr{attr_name;_}comparable=attr_name.txt=comparablelethasAnyReactComponentAttribute{attr_name;_}=attr_name.txt=react_dot_component||attr_name.txt=react_dot_async_dot_component||attr_name.txt=react_dot_client_dot_component(* Helper method to filter out any attribute that isn't [@react.component] *)letnonReactAttributes{attr_name;_}=attr_name.txt<>react_dot_component&&attr_name.txt<>react_dot_async_dot_component&&attr_name.txt<>react_dot_client_dot_componentlethasAttrOnBinding{pvb_attributes}comparable=List.find_opt~f:(funattr->hasAttrattrcomparable)pvb_attributes<>NoneletisReactComponentBindingvb=hasAttrOnBindingvbreact_dot_componentletisReactAsyncComponentBindingvb=hasAttrOnBindingvbreact_dot_async_dot_componentletisReactClientComponentBindingvb=hasAttrOnBindingvbreact_dot_client_dot_componentletisReactServerFunctionBindingvb=hasAttrOnBindingvbreact_dot_server_dot_functionletisClientComponentBindingvalue_bindings=letfirst_binding=List.hdvalue_bindingsinisReactClientComponentBindingfirst_bindingletcontains_client_componentstructure=List.exists~f:(funstructure_item->matchstructure_item.pstr_descwith|Pstr_value(_,value_bindings)->List.exists~f:isReactClientComponentBindingvalue_bindings|_->false)structureletrecunwrap_childrenchildren=function|{pexp_desc=Pexp_construct({txt=Lident"[]";_},None);_}->List.revchildren|{pexp_desc=Pexp_construct({txt=Lident"::";_},Some{pexp_desc=Pexp_tuple[child;next];_});_}->unwrap_children(child::children)next|e->raise_errorf~loc:e.pexp_loc"jsx: children prop should be a list"letis_jsx=function{attr_name={txt="JSX";_};_}->true|_->falselethas_jsx_attrattrs=List.exists~f:is_jsxattrsletrewrite_component~loctagargschildren=letcomponent=pexp_ident~loctaginletprops=matchchildrenwith|None->args|Some[children]->(Labelled"children",children)::args|Somechildren->(Labelled"children",[%exprReact.list[%epexp_list~locchildren]])::argsinpexp_apply~loccomponentpropsletvalidate_prop~locidname=matchDomProps.findByJsxName~tag:idnamewith|Okp->p|Error`ElementNotFound->raise_errorf~loc"jsx: HTML tag '%s' doesn't exist.\nIf this isn't correct, please open an issue at %s"idissues_url|Error`AttributeNotFound->(matchDomProps.findClosestNamenamewith|None->raise_errorf~loc"jsx: prop '%s' isn't valid on a '%s' element.\nIf this isn't correct, please open an issue at %s."nameidissues_url|Somesuggestion->raise_errorf~loc"jsx: prop '%s' isn't valid on a '%s' element.\n\
Hint: Maybe you mean '%s'?\n\n\
If this isn't correct, please open an issue at %s."nameidsuggestionissues_url)letmake_prop~is_optional~propattribute_value=letloc=attribute_value.pexp_locinletopenDomPropsinmatch(prop,is_optional)with|Attribute{type_=DomProps.Action;name;jsxName},false->[%exprmatch([%eattribute_value]:[`Stringofstring|`Functionof'aRuntime.server_function])with|`Strings->Some(React.JSX.String([%eestring~locname],[%eestring~locjsxName],(s:string)))|`Functionf->Some(React.JSX.Action([%eestring~locname],[%eestring~locjsxName],(f:'aRuntime.server_function)))]|Attribute{type_=DomProps.Action;name;jsxName},true->[%exprmatch([%eattribute_value]:[`Stringofstring|`Functionof'aRuntime.server_function]option)with|None->None|Somev->Some(React.JSX.Action([%eestring~locname],[%eestring~locjsxName],v))]|Attribute{type_=DomProps.String;name;jsxName},false->[%exprSome(React.JSX.String([%eestring~locname],[%eestring~locjsxName],([%eattribute_value]:string)))]|Attribute{type_=DomProps.String;name;jsxName},true->[%exprmatch([%eattribute_value]:stringoption)with|None->None|Somev->Some(React.JSX.String([%eestring~locname],[%eestring~locjsxName],v))]|Attribute{type_=DomProps.Int;name;jsxName},false->[%exprSome(React.JSX.String([%eestring~locname],[%eestring~locjsxName],Stdlib.Int.to_string([%eattribute_value]:int)))]|Attribute{type_=DomProps.Int;name;jsxName},true->[%exprmatch([%eattribute_value]:intoption)with|None->None|Somev->Some(React.JSX.String([%eestring~locname],[%eestring~locjsxName],Stdlib.Int.to_stringv))]|Attribute{type_=DomProps.Bool;name;jsxName},false->[%exprSome(React.JSX.Bool([%eestring~locname],[%eestring~locjsxName],([%eattribute_value]:bool)))]|Attribute{type_=DomProps.Bool;name;jsxName},true->[%exprmatch([%eattribute_value]:booloption)with|None->None|Somev->Some(React.JSX.Bool([%eestring~locname],[%eestring~locjsxName],v))](* BooleanishString needs to transform bool into string *)|Attribute{type_=DomProps.BooleanishString;name;jsxName},false->[%exprSome(React.JSX.String([%eestring~locname],[%eestring~locjsxName],Stdlib.Bool.to_string([%eattribute_value]:bool)))]|Attribute{type_=DomProps.BooleanishString;name;jsxName},true->[%exprmatch([%eattribute_value]:booloption)with|None->None|Somev->Some(React.JSX.String([%eestring~locname],[%eestring~locjsxName],Stdlib.Bool.to_stringv))]|Attribute{type_=DomProps.Style;_},false->[%exprSome(React.JSX.Style([%eattribute_value]:ReactDOM.Style.t))]|Attribute{type_=DomProps.Style;_},true->[%exprmatch([%eattribute_value]:ReactDOM.Style.toption)withNone->None|Somev->Some(React.JSX.Stylev)]|Attribute{type_=DomProps.Ref;_},false->[%exprSome(React.JSX.Ref([%eattribute_value]:React.domRef))]|Attribute{type_=DomProps.Ref;_},true->[%exprmatch([%eattribute_value]:React.domRefoption)withNone->None|Somev->Some(React.JSX.Refv)]|Attribute{type_=DomProps.InnerHtml;_},false->[%exprSome(React.JSX.dangerouslyInnerHtml[%eattribute_value])]|Attribute{type_=DomProps.InnerHtml;_},true->[%exprmatch[%eattribute_value]withNone->None|Somev->Some(React.JSX.dangerouslyInnerHtmlv)]|Event{type_=Mouse;jsxName},false->[%exprSome(React.JSX.Event([%emake_string~locjsxName],React.JSX.Mouse([%eattribute_value]:React.Event.Mouse.t->unit)))]|Event{type_=Mouse;jsxName},true->[%exprmatch([%eattribute_value]:(React.Event.Mouse.t->unit)option)with|None->None|Somev->Some(React.JSX.Event([%emake_string~locjsxName],React.JSX.Mousev))]|Event{type_=Selection;jsxName},false->[%exprSome(React.JSX.Event([%emake_string~locjsxName],React.JSX.Selection([%eattribute_value]:React.Event.Mouse.t->unit)))]|Event{type_=Selection;jsxName},true->[%exprmatch([%eattribute_value]:(React.Event.Selection.t->unit)option)with|None->None|Somev->Some(React.JSX.Event([%emake_string~locjsxName],React.JSX.Selectionv))]|Event{type_=Touch;jsxName},false->[%exprSome(React.JSX.Event([%emake_string~locjsxName],React.JSX.Touch([%eattribute_value]:React.Event.Touch.t->unit)))]|Event{type_=Touch;jsxName},true->[%exprmatch([%eattribute_value]:(React.Event.Touch.t->unit)option)with|None->None|Somev->Some(React.JSX.Event([%emake_string~locjsxName],React.JSX.Touchv))]|Event{type_=UI;jsxName},false->[%exprSome(React.JSX.Event([%emake_string~locjsxName],React.JSX.UI([%eattribute_value]:React.Event.UI.t->unit)))]|Event{type_=UI;jsxName},true->[%exprmatch([%eattribute_value]:(React.Event.UI.t->unit)option)with|None->None|Somev->Some(React.JSX.Event([%emake_string~locjsxName],React.JSX.UIv))]|Event{type_=Wheel;jsxName},false->[%exprSome(React.JSX.Event([%emake_string~locjsxName],React.JSX.Wheel([%eattribute_value]:React.Event.Wheel.t->unit)))]|Event{type_=Wheel;jsxName},true->[%exprmatch([%eattribute_value]:(React.Event.Wheel.t->unit)option)with|None->None|Somev->Some(React.JSX.Event([%emake_string~locjsxName],React.JSX.Wheelv))]|Event{type_=Clipboard;jsxName},false->[%exprSome(React.JSX.Event([%emake_string~locjsxName],React.JSX.Clipboard([%eattribute_value]:React.Event.Clipboard.t->unit)))]|Event{type_=Clipboard;jsxName},true->[%exprmatch([%eattribute_value]:(React.Event.Clipboard.t->unit)option)with|None->None|Somev->Some(React.JSX.Event([%emake_string~locjsxName],React.JSX.Clipboardv))]|Event{type_=Composition;jsxName},false->[%exprSome(React.JSX.Event([%emake_string~locjsxName],React.JSX.Composition([%eattribute_value]:React.Event.Composition.t->unit)))]|Event{type_=Composition;jsxName},true->[%exprmatch([%eattribute_value]:(React.Event.Composition.t->unit)option)with|None->None|Somev->Some(React.JSX.Event([%emake_string~locjsxName],React.JSX.Compositionv))]|Event{type_=Keyboard;jsxName},false->[%exprSome(React.JSX.Event([%emake_string~locjsxName],React.JSX.Keyboard([%eattribute_value]:React.Event.Keyboard.t->unit)))]|Event{type_=Keyboard;jsxName},true->[%exprmatch([%eattribute_value]:(React.Event.Keyboard.t->unit)option)with|None->None|Somev->Some(React.JSX.Event([%emake_string~locjsxName],React.JSX.Keyboardv))]|Event{type_=Focus;jsxName},false->[%exprSome(React.JSX.Event([%emake_string~locjsxName],React.JSX.Focus([%eattribute_value]:React.Event.Focus.t->unit)))]|Event{type_=Focus;jsxName},true->[%exprmatch([%eattribute_value]:(React.Event.Focus.t->unit)option)with|None->None|Somev->Some(React.JSX.Event([%emake_string~locjsxName],React.JSX.Focusv))]|Event{type_=Form;jsxName},false->[%exprSome(React.JSX.Event([%emake_string~locjsxName],React.JSX.Form([%eattribute_value]:React.Event.Form.t->unit)))]|Event{type_=Form;jsxName},true->[%exprmatch([%eattribute_value]:(React.Event.Form.t->unit)option)with|None->None|Somev->Some(React.JSX.Event([%emake_string~locjsxName],React.JSX.Formv))]|Event{type_=Media;jsxName},false->[%exprSome(React.JSX.Event([%emake_string~locjsxName],React.JSX.Media([%eattribute_value]:React.Event.Media.t->unit)))]|Event{type_=Media;jsxName},true->[%exprmatch([%eattribute_value]:(React.Event.Media.t->unit)option)with|None->None|Somev->Some(React.JSX.Event([%emake_string~locjsxName],React.JSX.Mediav))]|Event{type_=Inline;jsxName},false->[%exprSome(React.JSX.Event([%emake_string~locjsxName],React.JSX.Inline([%eattribute_value]:string)))]|Event{type_=Inline;jsxName},true->[%exprmatch([%eattribute_value]:stringoption)with|None->None|Somev->Some(React.JSX.Event([%emake_string~locjsxName],React.JSX.Inlinev))]|Event{type_=Image;jsxName},false->[%exprSome(React.JSX.Event([%emake_string~locjsxName],React.JSX.Image([%eattribute_value]:(React.Event.Image.t->unit)option)))]|Event{type_=Image;jsxName},true->[%exprmatch([%eattribute_value]:(React.Event.Image.t->unit)option)with|None->None|Somev->Some(React.JSX.Event([%emake_string~locjsxName],React.JSX.Imagev))]|Event{type_=Animation;jsxName},false->[%exprSome(React.JSX.Event([%emake_string~locjsxName],React.JSX.Animation([%eattribute_value]:React.Event.Animation.t->unit)))]|Event{type_=Animation;jsxName},true->[%exprmatch([%eattribute_value]:(React.Event.Animation.t->unit)option)with|None->None|Somev->Some(React.JSX.Event([%emake_string~locjsxName],React.JSX.Animationv))]|Event{type_=Transition;jsxName},false->[%exprSome(React.JSX.Event([%emake_string~locjsxName],React.JSX.Transition([%eattribute_value]:React.Event.Transition.t->unit)))]|Event{type_=Transition;jsxName},true->[%exprmatch([%eattribute_value]:(React.Event.Transition.t->unit)option)with|None->None|Somev->Some(React.JSX.Event([%emake_string~locjsxName],React.JSX.Transitionv))]|Event{type_=Pointer;jsxName},false->[%exprSome(React.JSX.Event([%emake_string~locjsxName],React.JSX.Pointer([%eattribute_value]:React.Event.Pointer.t->unit)))]|Event{type_=Pointer;jsxName},true->[%exprmatch([%eattribute_value]:(React.Event.Pointer.t->unit)option)with|None->None|Somev->Some(React.JSX.Event([%emake_string~locjsxName],React.JSX.Pointerv))]|Event{type_=Drag;jsxName},false->[%exprSome(React.JSX.Event([%emake_string~locjsxName],React.JSX.Drag([%eattribute_value]:React.Event.Drag.t->unit)))]|Event{type_=Drag;jsxName},true->[%exprmatch([%eattribute_value]:(React.Event.Drag.t->unit)option)with|None->None|Somev->Some(React.JSX.Event([%emake_string~locjsxName],React.JSX.Dragv))]letis_optional=functionOptional_->true|_->falseletget_label=functionNolabel->""|Optionalname|Labelledname->namelettransform_labelled~loc~tag_name(prop_label,(runtime_value:expression))props=matchprop_labelwith|Nolabel->props|Optionalname|Labelledname->letis_optional=is_optionalprop_labelinletprop=validate_prop~loctag_namenameinletnew_prop=make_prop~is_optional~propruntime_valuein[%expr[%enew_prop]::[%eprops]]lettransform_lowercase_props~loc~tag_nameargs=matchargswith|[]->[%expr[]]|attrs->(letlist_of_attributes=attrs|>List.fold_right~f:(transform_labelled~loc~tag_name)~init:[%expr[]]inmatchlist_of_attributeswith|[%expr[]]->[%expr[]]|_->(* We need to filter attributes since optionals are represented as None *)[%exprStdlib.List.filter_mapStdlib.Fun.id[%elist_of_attributes]])letdefault_buffer_size=1024letgenerate_buffer_code~locparts=letbuf_var="__html_buf"inletbuf_ident=pexp_ident~loc{loc;txt=Lidentbuf_var}inletbuf_pat=ppat_var~loc{loc;txt=buf_var}inletbuffer_size_expr=eint~locdefault_buffer_sizeinletgenerate_part_codepart=matchpartwith|Static_analysis.Static_strs->lets_expr=estring~locsin[%exprBuffer.add_string[%ebuf_ident][%es_expr]]|Static_analysis.Dynamic_stringexpr->(* Dynamic string needs HTML escaping *)[%exprReactDOM.escape_to_buffer[%ebuf_ident][%eexpr]]|Static_analysis.Dynamic_intexpr->(* Int.to_string cannot produce escapable characters, skip escaping *)[%exprBuffer.add_string[%ebuf_ident](Int.to_string[%eexpr])]|Static_analysis.Dynamic_floatexpr->(* Float.to_string cannot produce escapable characters, skip escaping *)[%exprBuffer.add_string[%ebuf_ident](Float.to_string[%eexpr])]|Static_analysis.Dynamic_elementexpr->(* Full element needs runtime rendering to buffer *)[%exprReactDOM.write_to_buffer[%ebuf_ident][%eexpr]]inletops=List.map~f:generate_part_codepartsinletseq=List.fold_rightops~init:[%expr()]~f:(funopacc->[%expr[%eop];[%eacc]])in[%exprlet[%pbuf_pat]=Buffer.create[%ebuffer_size_expr]in[%eseq];React.DangerouslyInnerHtml(Buffer.contents[%ebuf_ident])]letrewrite_lowercase~loctag_nameargschildren=matchStatic_analysis.analyze_element~tag_name~attrs:args~childrenwith|Static_analysis.Fully_statichtml->lethtml_with_doctype=Static_analysis.maybe_add_doctypetag_namehtmlinlethtml_expr=estring~lochtml_with_doctypein[%exprReact.DangerouslyInnerHtml[%ehtml_expr]]|Static_analysis.Needs_string_concat_|Static_analysis.Needs_buffer_|Static_analysis.Cannot_optimize->(letdom_node_name=estring~loctag_nameinletkey=args|>List.find_opt~f:(fun(label,_)->get_labellabel="key")|>Option.map(fun(_,value)->value)inletprops=transform_lowercase_props~loc~tag_nameargsinmatch(key,children)with|Somekey,Somechildren->letchildrens=pexp_list~locchildrenin[%exprReact.createElementWithKey~key:[%ekey][%edom_node_name][%eprops][%echildrens]]|None,Somechildren->letchildrens=pexp_list~locchildrenin[%exprReact.createElement[%edom_node_name][%eprops][%echildrens]]|Somekey,None->[%exprReact.createElementWithKey~key:[%ekey][%edom_node_name][%eprops][]]|None,None->[%exprReact.createElement[%edom_node_name][%eprops][]])letsplit_argsargs=letchildren=ref(Location.none,[])inletrest=List.filter_mapargs~f:(function|Labelled"children",children_expression->letchildren'=unwrap_children[]children_expressioninchildren:=(children_expression.pexp_loc,children');None|arg_label,e->Some(arg_label,e))inletchildren_prop=match!childrenwith_loc,[]->None|_loc,children->Somechildrenin(children_prop,rest)letreverse_pexp_list~locexpr=letrecgoacc=function|[%expr[]]->acc|[%expr[%e?hd]::[%e?tl]]->go[%expr[%ehd]::[%eacc]]tl|expr->expringo[%expr[]]exprletlist_have_tailexpr=matchexprwith|Pexp_construct({txt=Lident"::";_},Some{pexp_desc=Pexp_tuple_;_})|Pexp_construct({txt=Lident"[]";_},None)->false|_->truelettransform_items_of_list~locchildren=letrecrun_mapperchildrenaccum=matchchildrenwith|[%expr[]]->reverse_pexp_list~locaccum|[%expr[%e?v]::[%e?acc]]whenlist_have_tailacc.pexp_desc->[%expr[%ev]]|[%expr[%e?v]::[%e?acc]]->run_mapperacc[%expr[%ev]::[%eaccum]]|notAList->notAListinrun_mapperchildren[%expr[]]letremove_warning_16_optional_argument_cannot_be_erased~loc=letopenAst_helperin{attr_name={txt="warning";loc};attr_payload=PStr[Str.eval(Exp.constant(Const.string"-16"))];attr_loc=loc;}letremove_warning_27_unused_var_strict~loc=letopenAst_helperin{attr_name={txt="warning";loc};attr_payload=PStr[Str.eval(Exp.constant(Const.string"-27"))];attr_loc=loc;}(* Finds the name of the variable the binding is assigned to, otherwise raises *)letget_function_namebinding=matchbindingwith|{pvb_pat={ppat_desc=Ppat_var{txt}}}->txt|_->raise_errorf~loc:binding.pvb_loc"react.component calls cannot be destructured."(* TODO: there are a few unsupported features inside of blocks - Pexp_letmodule , Pexp_letexception , Pexp_ifthenelse *)letadd_unit_at_the_last_argumentexpression=letloc=expression.pexp_locinlethas_final_unitparams=matchList.revparamswith|{pparam_desc=Pparam_val(Nolabel,_,{ppat_desc=Ppat_construct({txt=Lident"()"},_)|Ppat_any;_});_;}::_->true|_->falseinletunit_param={pparam_loc=loc;pparam_desc=Pparam_val(Nolabel,None,[%pat?()])}inletrecfind_innermost_function_and_add_unitexpression=matchexpression.pexp_descwith|Pexp_function(params,constraint_,Pfunction_bodyinner_body)->(matchinner_body.pexp_descwith|Pexp_function_->letmodified_inner=find_innermost_function_and_add_unitinner_bodyin{expressionwithpexp_desc=Pexp_function(params,constraint_,Pfunction_bodymodified_inner)}|_when(not(has_final_unitparams))&¶ms<>[]->{expressionwithpexp_attributes=remove_warning_16_optional_argument_cannot_be_erased~loc::expression.pexp_attributes;pexp_desc=Pexp_function(params@[unit_param],constraint_,Pfunction_bodyinner_body);}|_->expression)|Pexp_function_->expression|_->expressioninletrecinnerexpression=matchexpression.pexp_descwith|Pexp_function_->find_innermost_function_and_add_unitexpression(* let make = {let foo = bar in (~prop) => ...} *)|Pexp_let(recursive,vbs,internalExpression)->pexp_let~loc:expression.pexp_locrecursivevbs(innerinternalExpression)(* let make = React.forwardRef((~prop) => ...) *)|Pexp_apply(_,[(Nolabel,internalExpression)])->innerinternalExpression(* let make = React.memoCustomCompareProps((~prop) => ..., (prevPros, nextProps) => true) *)|Pexp_apply(_,[(Nolabel,internalExpression);((Nolabel,{pexp_desc=Pexp_function_;_})as_compareProps)])->innerinternalExpression|Pexp_sequence(wrapperExpression,internalExpression)->pexp_sequence~loc:expression.pexp_locwrapperExpression(innerinternalExpression)|_->expressionininnerexpressionlettransform_fun_body_expressionexprfn=letrecfind_innermost_body_and_transformexpr=matchexpr.pexp_descwith|Pexp_function(params,constraint_,Pfunction_bodyinner_body)->(matchinner_body.pexp_descwith|Pexp_function_->lettransformed_inner=find_innermost_body_and_transforminner_bodyin{exprwithpexp_desc=Pexp_function(params,constraint_,Pfunction_bodytransformed_inner)}|_->lettransformed_body=fninner_bodyin{exprwithpexp_desc=Pexp_function(params,constraint_,Pfunction_bodytransformed_body)})|_->fnexprinfind_innermost_body_and_transformexprlettransform_fun_argumentsexprfn=matchexpr.pexp_descwith|Pexp_function(params,constraint_,Pfunction_bodyexpression)->letnew_params=List.map~f:(funparam->matchparam.pparam_descwith|Pparam_val(label,def,patt)->{paramwithpparam_desc=Pparam_val(label,def,fnpatt)}|Pparam_newtype_->param)paramsin{exprwithpexp_desc=Pexp_function(new_params,constraint_,Pfunction_bodyexpression)}|_->exprlettransform_labelled_arguments_type(core_type:core_type)fn=letrecinnercore_type=matchcore_type.ptyp_descwith|Ptyp_arrow(label,core_type_1,core_type_2)->ptyp_arrow~loc:core_type.ptyp_loclabel(fncore_type_1)(innercore_type_2)|_->core_typeininnercore_typeletexpand_make_bindingbindingreact_element_variant_wrapping=letattributers=binding.pvb_attributes|>List.filter~f:nonReactAttributesinletloc=binding.pvb_locinletghost_loc={binding.pvb_locwithloc_ghost=true}inletbinding_with_unit=add_unit_at_the_last_argumentbinding.pvb_exprinletbinding_expr=transform_fun_body_expressionbinding_with_unitreact_element_variant_wrappingin(* Builds an AST node for the modified `make` function *)letname=ppat_var~loc:ghost_loc{txt=get_function_namebinding;loc=ghost_loc}inletkey_arg=Optional"key"inletdefault_value=(* default_value = None means there's no default *)Noneinletunderscore=ppat_var~loc:ghost_loc{txt="_";loc}inletcore_type=[%type:stringoption]inletkey_pattern=ppat_constraint~locunderscorecore_typein(* Append key argument since we want to allow users of this component to set key (and assign it to _ since it shouldn't be used) *)letfunction_body=pexp_fun~loc:ghost_lockey_argdefault_valuekey_patternbinding_exprin(* Since expand_make_binding is called on both native and js contexts, we need to keep the attributes *){(value_binding~loc:ghost_loc~pat:name~expr:function_body)withpvb_attributes=attributers}letget_argumentspvb_expr=letrecgoacc=function|Pexp_function(params,_,Pfunction_bodyexpr)->letargs=List.filter_map~f:(function|{pparam_desc=Pparam_val(label,default,patt);_}->Some(label,default,patt)|_->None)paramsingo(args@acc)expr.pexp_desc|_->accingo[]pvb_expr.pexp_descletmake_of_json~loc(core_type:core_type)prop=matchcore_typewith(* QUESTION: How do we handle especial types on props,
like `("someProp"), `List([React.element, string]).
We already support it, but not with the ppx.
Checkout the test_RSC_model.ml for more details. packages/reactDom/test/test_RSC_html.ml *)(* QUESTION: How can we handle optionals and others? Need a [@deriving rsc] for them? We currently encode None's as React.Model.Json `Null, should be enought *)|[%type:React.element]->[%expr([%eprop]:React.element)]|[%type:React.elementoption]->[%expr([%eprop]:React.elementoption)](* TODO: Add promise caching? When is it needed? *)(* | [%type: [%t? t] Js.Promise.t] ->
[%expr
let promise = [%e prop] in
let promise' = (Obj.magic promise : [%t t] Js.Promise.t Js.Dict.t) in
match Js.Dict.get promise' "__promise" with
| Some promise -> promise
| None ->
let promise =
Promise.(
let* json = (Obj.magic (Js.Promise.resolve promise) : Realm.Json.t Promise.t) in
let data = [%of_json: [%t t]] json in
return data)
in
Js.Dict.set promise' "__promise" promise;
promise] *)|[%type:[%t?t]Js.Promise.t]->[%expr([%eprop]:[%tt]Js.Promise.t)]|[%type:[%t?t]Runtime.server_function]->[%expr([%eprop]:[%tt]Runtime.server_function)]|[%type:[%t?inner_type]option]astype_->(matchinner_type.ptyp_descwith|Ptyp_arrow(_,_,_)->[%expr([%eprop]:[%ttype_])]|_->[%expr[%of_json:[%ttype_]][%eprop]])|type_->(matchtype_.ptyp_descwith|Ptyp_arrow(_,_,_)->[%expr([%eprop]:[%ttype_])]|_->[%expr[%of_json:[%ttype_]][%eprop]])letprops_of_model~loc(props:(arg_label*expressionoption*pattern)list):(longidentloc*expression)list=List.filter_map~f:(fun(arg_label,default,pattern)->matchpattern.ppat_descwith|Ppat_construct({txt=Lident"()";_},None)->None|Ppat_constraint(_,core_type)->(matcharg_labelwith|Nolabel->(* This error is raised by reason-react-ppx as well *)letloc=pattern.ppat_locinSome(longident~loc"error",[%expr[%ocaml.error"props need to be labelled arguments"]])|Labelledlabel|Optionallabel->letcore_type=matchdefaultwithSome_->[%type:[%tcore_type]option]|None->core_typeinletprop=[%exprprops##[%eident~loclabel]]inletvalue=make_of_json~loccore_typepropinSome(longident~loclabel,value))|_->letloc=pattern.ppat_locinletexpr=matcharg_labelwith|Nolabel->[%expr[%ocaml.error"server-reason-react: client components need type annotations"]]|Labelledlabel|Optionallabel->letmsg=Printf.sprintf"server-reason-react: client components need type annotations. Missing annotation for '%s'"labelinletmsg_expr=estring~locmsgin[%expr[%ocaml.error[%emsg_expr]]]inSome(longident~loc"error",expr))propsletreact_component_attribute~loc={attr_name={txt="react.component";loc};attr_payload=PStr[];attr_loc=loc}letmel_obj~locfields=matchfieldswith(* QUESTION: Maybe unit would work here best, for correctness? *)|[]->[%exprJs.Obj.empty()]|_->letrecord=pexp_record~locfieldsNoneinletstri=pstr_eval~locrecord[]in[%expr[%mel.obj[%%istri]]]letexpand_make_binding_to_clientbinding=letloc=binding.pvb_locinletghost_loc={binding.pvb_locwithloc_ghost=true}inletarguments=get_argumentsbinding.pvb_exprinletprops_as_object_with_decoders=mel_obj~loc(props_of_model~locarguments)inletmake_argument=[(Nolabel,props_as_object_with_decoders)]inletmake_call=pexp_apply~loc:ghost_loc[%exprmake]make_argumentinletname=ppat_var~loc:ghost_loc{txt="make_client";loc=ghost_loc}inletclient_single_argument=ppat_var~loc:ghost_loc{txt="props";loc}inletfunction_body=pexp_fun~loc:ghost_locNolabelNoneclient_single_argumentmake_callinvalue_binding~loc:ghost_loc~pat:name~expr:function_bodyletrecadd_unit_at_the_last_argument_in_core_typecore_type=matchcore_type.ptyp_descwith|Ptyp_arrow(arg_label,core_type_1,core_type_2)->{core_typewithptyp_desc=Ptyp_arrow(arg_label,core_type_1,add_unit_at_the_last_argument_in_core_typecore_type_2);}|Ptyp_constr_->letloc=core_type.ptyp_locin{core_typewithptyp_desc=Ptyp_arrow(Nolabel,[%type:unit],core_type)}|_->core_typeletrewrite_signature_itemsignature_item=(* Removes the [@react.component] from the AST *)matchsignature_itemwith|{psig_loc=_;psig_desc=Psig_value({pval_name={txt=_fnName};pval_attributes;pval_type}aspsig_desc);}aspsig->(letnew_ptyp_desc=matchpval_type.ptyp_descwith|Ptyp_arrow(arg_label,core_type_1,core_type_2)->letloc=pval_type.ptyp_locinletoriginal_core_type={pval_typewithptyp_desc=Ptyp_arrow(arg_label,core_type_1,core_type_2)}inletnew_core_type=add_unit_at_the_last_argument_in_core_typeoriginal_core_typeinPtyp_arrow(Optional"key",[%type:string],new_core_type)|ptyp_desc->ptyp_descinletnew_core_type={pval_typewithptyp_desc=new_ptyp_desc}inmatchList.filter~f:hasAnyReactComponentAttributepval_attributeswith|[]->signature_item|[_]->{psigwithpsig_desc=Psig_value{psig_descwithpval_type=new_core_type;pval_attributes=List.filter~f:nonReactAttributespval_attributes;};}|_->letloc=signature_item.psig_locin[%sigi:[%%ocaml.error"server-reason-react: there's seems to be an error in the signature of the component."]])|_->signature_itemletmake_to_json~loc(core_type:core_type)prop=matchcore_typewith|[%type:React.element]->[%exprReact.Model.Element([%eprop]:React.element)]|[%type:React.elementoption]->[%exprmatch[%eprop]withSomeprop->React.Model.Element(prop:React.element)|None->React.Model.Json`Null]|[%type:[%t?inner_type]Js.Promise.t]->letjson=[%expr[%to_json:[%tinner_type]]]in[%exprReact.Model.Promise([%eprop],[%ejson])]|[%type:[%t?inner_type]Js.Promise.toption]->letjson=[%expr[%to_json:[%tinner_type]]]in[%exprmatch[%eprop]with|Someprop->[%exprReact.Model.Promise([%eprop],[%ejson])]|None->React.Model.Json`Null]|{ptyp_desc=Ptyp_arrow(_,_,_)}->letloc=core_type.ptyp_locin[%expr[%ocaml.error"server-reason-react: you can't pass functions into client components. Functions aren't serialisable to JSON."]]|[%type:[%t?_]Runtime.server_function]->[%exprReact.Model.Function[%eprop]]|type_->letjson=[%expr[%to_json:[%ttype_]][%eprop]]in[%exprReact.Model.Json[%ejson]]letprops_to_model~loc(props:(arg_label*expressionoption*pattern)list)=List.fold_left~init:[%expr[]]~f:(funacc(arg_label,_default,pattern)->matchpattern.ppat_descwith|Ppat_construct({txt=Lident"()";_},None)->acc|Ppat_constraint(_,core_type)->(matcharg_labelwith|Nolabel->(* This error is raised by reason-react-ppx as well *)letloc=pattern.ppat_locin[%expr[%ocaml.error"props need to be labelled arguments"]::[%eacc]]|Labelledlabel|Optionallabel->letprop=ident~loclabelinletvalue=make_to_json~loccore_typepropinletname=estring~loclabelin[%expr([%ename],[%evalue])::[%eacc]])(* TODO: Add all ppat_desc possibilities *)|_->letloc=pattern.ppat_locinletexpr=matcharg_labelwith|Nolabel->[%expr[%ocaml.error"server-reason-react: client components need type annotations"]]|Labelledlabel|Optionallabel->letmsg=Printf.sprintf"server-reason-react: client components need type annotations. Missing annotation for '%s'"labelinletmsg_expr=estring~locmsgin[%expr[%ocaml.error[%emsg_expr]]]in[%expr[%eexpr]::[%eacc]])propsmoduleServerFunction=structletreclast_expr_to_fn~locexprfn=matchexpr.pexp_descwith|Pexp_constraint(expr,_)->last_expr_to_fn~locexprfn|Pexp_function(params,constraint_,Pfunction_bodyexpression)whenparams<>[]->(matchexpression.pexp_descwith|Pexp_function_->lettransformed_inner=last_expr_to_fn~locexpressionfnin{exprwithpexp_desc=Pexp_function(params,constraint_,Pfunction_bodytransformed_inner)}|_->{exprwithpexp_desc=Pexp_function(params,constraint_,Pfunction_bodyfn)})|_->fnletgenerate_id~locname=letfile_path=loc.loc_start.pos_fnameinletreplacement=matchshared_folder_prefix.contentswith|Somex->ifmatch_substringfile_pathxthenxelseraise_errorf~loc"Prefix doesn't match the file path. Provide a prefix that matches the file path."|None->raise_errorf~loc"Found a server.function without --shared-folder-prefix argument. Provide one."in(* We need to add a nasty hack here, since have different files for native and melange.Assume that the file structure is native/lib and js, and replace the name directly. This is supposed to be temporal, until dune implements https://github.com/ocaml/dune/issues/10630 *)letfile_path=Str.replace_first(Str.regexpreplacement)""file_pathinlethash=Printf.sprintf"%s_%s_%d"namefile_pathloc.loc_start.pos_lnum|>Hashtbl.hash|>string_of_intinhashletget_arg_details(arg:arg_label*expressionoption*pattern)=letarg_label,default,pattern=arginletloc=pattern.ppat_locinmatchpattern.ppat_descwith|Ppat_construct({txt=Lident"()";loc},None)->Ok(Nolabel,None,[%type:unit])|Ppat_constraint(pattern,core_type)->(letloc=pattern.ppat_locinletcore_type=matchdefaultwithSome_->[%type:[%tcore_type]option]|None->core_typeinmatchpattern.ppat_descwith|Ppat_var{txt=label;_}->Ok(arg_label,Somelabel,core_type)|_->Error(loc,"server-reason-react: server function arguments must have a name"))|_->Error(loc,"server-reason-react: server function arguments must have type annotations")letget_response_typeexpr=letrecauxexpracc=matchexpr.pexp_descwith|Pexp_function(_,Some(Pconstraintcore_type),Pfunction_bodybody)->auxbody(Somecore_type)|Pexp_function(_,_,Pfunction_bodybody)->auxbodyacc|Pexp_constraint(expr,core_type)->auxexpr(Somecore_type)|_->accinauxexprNoneletresponse_to_json~loccore_typeresponse=matchcore_typewith|Some[%type:[%t?core_type]Js.Promise.t]->letjson=[%expr[%to_json:[%tcore_type]][%eresponse]]in[%exprReact.Model.Json[%ejson]]|Some_->[%expr[%ocaml.error"server-reason-react: server functions must return a promise"]]|_->[%expr[%ocaml.error"server-reason-react: server functions must have a return type annotation (Js.Promise.t)"]]letmap_arguments_to_expressions~locargs=List.map~f:(funarg->matchargwith|Ok(arg_label,Somearg_name,_)->(arg_label,[%expr[%eevar~locarg_name]])|Ok(arg_label,_,[%type:unit])->(arg_label,[%expr()])|Ok_->(Nolabel,[%expr[%ocaml.error"server-reason-react: invalid argument, it must have a argument with name and type annotation"]])|Error(loc,msg)->(Nolabel,[%expr[%ocaml.error[%eestring~locmsg]]]))argsletencode_function_response~loc~response_expr~core_type=[%exprtry[%eresponse_expr]|>Lwt.map(funresponse->[%eresponse_to_json~loccore_type[%exprresponse]])withe->Lwt.faile]letdecode_arguments_vb~locargs_to_decode=args_to_decode|>List.mapi~f:(funi(_,label,core_type)->letstring_of_core_typex=letf=Format.str_formatterinAstlib.Pprintast.core_typefx;Format.flush_str_formatter()inletcore_type_string=string_of_core_typecore_typeinletof_json=make_of_json~loccore_type[%exprStdlib.Array.unsafe_getargs[%eeint~loci]]invalue_binding~loc~pat:[%pat?[%pppat_var~loc{txt=label;loc}]]~expr:[%exprtry[%eof_json]with_->Stdlib.raise(Invalid_argument(Stdlib.Printf.sprintf"server-reason-react: error on decoding argument '%s'. EXPECTED: %s, RECEIVED: %s"[%eestring~loclabel][%eestring~loccore_type_string](Stdlib.Array.unsafe_getargs[%eeint~loci]|>Yojson.Basic.to_string)))])letcreate_function_reference_registration~loc~id~function_name~args~core_type=letapply_args=map_arguments_to_expressions~locargsinletresponse_expr=pexp_apply~loc[%expr[%eevar~locfunction_name].call]apply_argsinletencoded_response_expr=encode_function_response~loc~response_expr~core_typeinletargs_to_decode=List.filter_map~f:(funarg->matchargwith|Ok(_,_,[%type:Js.FormData.t])->None|Ok(arg_label,Somearg_name,core_type)->Some(arg_label,arg_name,core_type)|Ok_->None|Error_->None)argsinletargs,formData=List.partition_map~f:(funarg->matchargwithOk(_,_,[%type:Js.FormData.t])->Rightarg|Ok_->Leftarg|Error_->Leftarg)argsinletbody_expr=matchargs_to_decodewith|[]->encoded_response_expr|args_to_decode->letdecoded_expr=decode_arguments_vb~locargs_to_decodeinpexp_let~locNonrecursivedecoded_exprencoded_response_exprinmatch(formData,args)with|[],_->[%striFunctionReferences.register[%eestring~locid](Body(funargs->[%ebody_expr]))]|[_],[]->[%striFunctionReferences.register[%eestring~locid](FormData(fun_formData->[%ebody_expr]))]|_,[]->[%stri[%ocaml.error"server-reason-react: server functions with form data must have at only one argument"]]|_->[%striFunctionReferences.register[%eestring~locid](FormData(funargsformData->[%ebody_expr]))]letcreate_server_function_record~locidexpression=[%expr{Runtime.id=[%eestring~locid];call=[%eexpression]}]letrewrite_native_function~vb~rec_flagstructure_item=letloc=structure_item.pstr_locinletfunction_name=get_function_namevbinletargs=get_argumentsvb.pvb_expr|>List.map~f:get_arg_details|>List.revinletbase_fn=vb.pvb_exprinletreturn_core_type=get_response_typebase_fninletid=generate_id~loc:vb.pvb_locfunction_nameinletserver_function_record_vb=value_binding~loc:vb.pvb_loc~pat:vb.pvb_pat~expr:(create_server_function_record~loc:vb.pvb_locidbase_fn)inletstri=[%striincludestruct[%%ipstr_value~locrec_flag[server_function_record_vb]][%%icreate_function_reference_registration~loc~id~function_name~args~core_type:return_core_type]end]instriletresponse_of_json~loccore_typeresponse=matchcore_typewith|Some[%type:[%t?core_type]Js.Promise.t]->[%expr[%of_json:[%tcore_type]][%eresponse]]|Some_->[%expr[%ocaml.error"server-reason-react: server functions must return a promise"]]|_->[%expr[%ocaml.error"server-reason-react: server functions must have a return type annotation (Js.Promise.t)"]]letcreate_client_function~loc~return_core_typeidargs=letdecode_response=response_of_json~locreturn_core_typeinletapply_args=map_arguments_to_expressions~locargs|>List.map~f:(fun(_,expr)->(Nolabel,expr))inletfn=[%exprletaction=ReactServerDOMEsbuild.createServerReference[%eestring~locid]in([%epexp_apply~loc[%expraction]apply_args][@u])|>Js.Promise.then_(funresponse->Js.Promise.resolve[%edecode_response[%exprresponse]])]infnletrewrite_client_function~nested_module_names~vb~rec_flagstructure_item=letloc=structure_item.pstr_locinletfunction_name=get_function_namevbinletargs=get_argumentsvb.pvb_expr|>List.map~f:get_arg_details|>List.revinletbase_fn=vb.pvb_exprinletreturn_core_type=get_response_typebase_fninletid=generate_id~loc:vb.pvb_locfunction_nameinletserver_function_record_vb=value_binding~loc:vb.pvb_loc~pat:vb.pvb_pat~expr:(create_server_function_record~loc:vb.pvb_locid(last_expr_to_fn~locbase_fn(create_client_function~loc~return_core_typeidargs)))inletloc=structure_item.pstr_locinletmodule_name=String.concat"."nested_module_namesinlet_,formData=List.partition_map~f:(funarg->matchargwithOk(_,_,[%type:Js.FormData.t])->Rightarg|Ok_->Leftarg|Error_->Leftarg)argsinletfunctionToCall=matchformDatawith[]->function_name|_->Printf.sprintf"%s.call"function_nameinletcomment=Printf.sprintf"// extract-server-function %s %s %s"idfunctionToCallmodule_nameinletraw=estring~loccommentinletextract_client_raw=[%stri[%%raw[%eraw]]]in[%striincludestruct[%%iextract_client_raw][%%ipstr_value~loc:structure_item.pstr_locrec_flag[server_function_record_vb]]end]endletrewrite_structure_item~nested_module_namesstructure_item=matchstructure_item.pstr_descwith(* external *)|Pstr_primitive({pval_name={txt=_fnName};pval_attributes;pval_type=_}as_value_description)->(matchList.filter~f:(funattr->hasAttrattrreact_dot_component||hasAttrattrreact_dot_async_dot_component)pval_attributeswith|[]->structure_item|_->letloc=structure_item.pstr_locin[%stri[%%ocaml.error"externals aren't supported on server-reason-react. externals are used to bind to React components defined \
in JavaScript, in the server, that doesn't make sense. If you need to render this on the server, \
implement a placeholder or an empty element"]])(* let make = ... *)|Pstr_value(rec_flag,value_bindings)whenisReactServerFunctionBinding(List.hdvalue_bindings)->letvb=List.hdvalue_bindingsinletloc=structure_item.pstr_locinifList.lengthvalue_bindings>1then[%stri[%%ocaml.error"server-reason-react: server functions don't support recursive bindings yet. If you need it, please open an \
issue on https://github.com/reasonml-community/server-reason-react/issues"]]elseServerFunction.rewrite_native_function~vb~rec_flagstructure_item|Pstr_value(rec_flag,value_bindings)->letmap_value_bindingvb=ifisReactClientComponentBindingvbthenexpand_make_bindingvb(funexpr->letloc=expr.pexp_locinletfileName=expr.pexp_loc.loc_start.pos_fnameinletreplacement=matchshared_folder_prefix.contentswith|Someprefix->ifmatch_substringfileNameprefixthenprefixelseraise_errorf~loc"Prefix doesn't match the file path. Provide a prefix that matches the file path."|None->raise_errorf~loc"Found a react.client.component without --shared-folder-prefix argument. Provide one."inletfile=fileName|>Str.replace_first(Str.regexpreplacement)""|>estring~locinletimport_module=matchnested_module_nameswith|[]->file|_->letsubmodule=estring~loc(String.concat"."nested_module_names)in[%exprPrintf.sprintf"%s#%s"[%efile][%esubmodule]]inletarguments=get_argumentsvb.pvb_exprin(* We transform the arguments from the value binding into React.client_props *)letprops=props_to_model~locargumentsin[%exprReact.Client_component{import_module=[%eimport_module];import_name="";props=[%eprops];client=React.Upper_case_component(Stdlib.__FUNCTION__,fun()->[%eexpr]);}])elseifisReactComponentBindingvbthenexpand_make_bindingvb(funexpr->letloc=expr.pexp_locin[%exprReact.Upper_case_component(Stdlib.__FUNCTION__,fun()->[%eexpr])])elseifisReactAsyncComponentBindingvbthenexpand_make_bindingvb(funexpr->letloc=expr.pexp_locin[%exprReact.Async_component(Stdlib.__FUNCTION__,fun()->[%eexpr])])elsevbinletbindings=List.map~f:map_value_bindingvalue_bindingsinpstr_value~loc:structure_item.pstr_locrec_flagbindings|_->structure_itemletrewrite_structure_item_for_js~nested_module_namesctxstructure_item=matchstructure_item.pstr_descwith(* external *)|Pstr_primitive({pval_name={txt=_fnName};pval_attributes;pval_type=_}as_value_description)->(matchList.filter~f:(funattr->hasAttrattrreact_dot_client_dot_component)pval_attributeswith|[]->structure_item|_->letloc=structure_item.pstr_locin[%stri[%%ocaml.error"server-reason-react: externals aren't supported on client components yet"]])|Pstr_value(rec_flag,value_bindings)whenisReactServerFunctionBinding(List.hdvalue_bindings)->letvb=List.hdvalue_bindingsinServerFunction.rewrite_client_function~nested_module_names~vb~rec_flagstructure_item(* let make = ... *)|Pstr_value(rec_flag,value_bindings)whenisClientComponentBindingvalue_bindings->letfirst_value_binding=List.hdvalue_bindingsinletmake_client=expand_make_binding_to_clientfirst_value_bindinginletmake_client_binding=pstr_value~loc:structure_item.pstr_locrec_flag[make_client]inletoriginal_value_binding={first_value_bindingwithpvb_attributes=[react_component_attribute~loc:first_value_binding.pvb_loc]}inletloc=structure_item.pstr_locinletcode_path=Expansion_context.Base.code_pathctxinletfileName=Code_path.file_pathcode_pathin(* We need to add a nasty hack here, since have different files for native and melange.Assume that the file structure is /native/shared/ and js, and replace the name directly. This is supposed to be temporal, until dune implements https://github.com/ocaml/dune/issues/10630 *)letreplacement=matchshared_folder_prefix.contentswith|Someprefix->ifmatch_substringfileNameprefixthenprefixelseraise_errorf~loc"Prefix doesn't match the file path. Provide a prefix that matches the file path."|None->raise_errorf~loc"Found a react.client.component without --shared-folder-prefix argument. Provide one."inletfileName=Str.replace_first(Str.regexpreplacement)""fileNameinletcomment=matchnested_module_nameswith|[]->estring~loc(Printf.sprintf"// extract-client %s"fileName)|_->estring~loc(Printf.sprintf"// extract-client %s %s"fileName(String.concat"."nested_module_names))in[%striincludestruct[%%i[%stri[%%raw[%ecomment]]]][%%ipstr_value~loc:structure_item.pstr_locrec_flag[original_value_binding]][%%imake_client_binding]end]|_->structure_itemletvalidate_tag_childrentagchildrenattributes:(unit,string)result=matchHtml.is_self_closing_tagtagwith|truewhenOption.fold~none:false~some:(funchildren->List.lengthchildren>0)children->Error(Printf.sprintf{|"%s" is a self-closing tag and must not have "children".\n|}tag)|truewhenList.exists~f:(fun(arg_label,_)->matcharg_labelwith|Labelled"dangerouslySetInnerHTML"|Optional"dangerouslySetInnerHTML"->true|_->false)attributes->Error(Printf.sprintf{|server-reason-react: "%s" is a self-closing tag and must not have "children".\n|}tag)|false->Ok()|true->Ok()lettraverse=object(_)inherit[Expansion_context.Base.t]Ast_traverse.map_with_contextassupervalmutablenested_module_names=[]method!module_bindingctxtmodule_binding=(matchmodule_binding.pmb_name.txtwith|None->()|Somename->nested_module_names<-nested_module_names@[name]);letmapped=super#module_bindingctxtmodule_bindinginletrecremove_lastl=matchlwith[]->[]|[_]->[]|hd::tl->hd::remove_lasttlinnested_module_names<-remove_lastnested_module_names;mappedmethod!structure_itemctxstructure_item=matchmode.contentswith|Native->rewrite_structure_item~nested_module_names(super#structure_itemctxstructure_item)|Js->rewrite_structure_item_for_js~nested_module_namesctx(super#structure_itemctxstructure_item)method!signature_itemctxsignature_item=matchmode.contentswith|Native->rewrite_signature_item(super#signature_itemctxsignature_item)|Js->super#signature_itemctxsignature_itemmethod!expressionctxexpr=letexpr=super#expressionctxexprinletattributes=expr.pexp_attributesinmatchmode.contentswith|Js->((* In the case of expressions, it's the only transformation that needs to be done for JS. This expansion from "styles" prop into "className" and "style" props is a feature by styled-ppx. The existence of this here, is because dune/ppxlib doesn't allow more than one preprocess_impl and even that, the combination of styled-ppx and server-reason-react.ppx doesn't compose properly. *)trymatchexpr.pexp_descwith|Pexp_apply(({pexp_desc=Pexp_ident_;pexp_loc=loc;_}astag),args)whenhas_jsx_attrexpr.pexp_attributes->letnew_args=Expand_styles_attribute.make~locargsin{(pexp_apply~loc(super#expressionctxtag)new_args)withpexp_attributes=attributes}|_->exprwithErrorerr->[%expr[%eerr]])|Native->(trymatchexpr.pexp_descwith|Pexp_apply(({pexp_desc=Pexp_ident_;pexp_loc=loc;_}astag),args)whenhas_jsx_attrexpr.pexp_attributes->(letchildren,rest_of_args=split_argsargsinmatchvalidate_tag_children(Pprintast.string_of_expressiontag)childrenrest_of_argswith|Errorerr->[%expr[%ocaml.error[%eestring~loc:expr.pexp_locerr]]]|Ok()->(matchtag.pexp_descwith(* div() [@JSX] *)|Pexp_ident{txt=Lidentname;loc=_name_loc}->(* This expansion from "styles" prop into "className" and "style" props is a feature by styled-ppx. The existence of this here, is because dune/ppxlib doesn't allow more than one preprocess_impl and even that, the combination of styled-ppx and server-reason-react.ppx doesn't compose properly. *)letnew_args=Expand_styles_attribute.make~locrest_of_argsinrewrite_lowercase~loc:expr.pexp_locnamenew_argschildren(* Reason adds `createElement` as default when an uppercase is found,
we change it back to make *)(* Foo.createElement() [@JSX] *)|Pexp_ident{txt=Ldot(modulePath,("createElement"|"make"));loc}->letid={loc;txt=Ldot(modulePath,"make")}inrewrite_component~loc:expr.pexp_locidrest_of_argschildren(* local_function() [@JSX] *)|Pexp_identid->rewrite_component~loc:expr.pexp_locidrest_of_argschildren|_->assertfalse))(* div() [@JSX] *)|Pexp_apply(tag,_props)whenhas_jsx_attrexpr.pexp_attributes->raise_errorf~loc:expr.pexp_loc"jsx: %s should be an identifier, not an expression"(Pprintast.string_of_expressiontag)(* <> </> is represented as a list in the Parsetree with [@JSX] *)|Pexp_construct({txt=Lident"::";loc},Some{pexp_desc=Pexp_tuple_;_})|Pexp_construct({txt=Lident"[]";loc},None)->(letjsx_attr,rest_attributes=List.partition~f:is_jsxexpr.pexp_attributesinmatch(jsx_attr,rest_attributes)with|[],_->expr|_,rest_attributes->letchildren=transform_items_of_list~locexprinletnew_expr=[%exprReact.fragment(React.list[%echildren])]in{new_exprwithpexp_attributes=rest_attributes})|_->exprwithErrorerr->[%expr[%eerr]])endlet()=Driver.add_arg"-melange"(Unit(fun()->mode:=Js))~doc:"preprocess for js build";Driver.add_arg"-shared-folder-prefix"(String(funstr->letcomponents=String.split_on_char'/'str|>List.filter~f:(funx->x<>"")inletprefix=String.concat"/"componentsinletprefix=ifprefix=""then""elseprefix^"/"inshared_folder_prefix:=Someprefix))~doc:"prefix of shared folder, used to replace the it in the file path";Ppxlib.Driver.V2.register_transformation"server-reason-react.ppx"~preprocess_impl:traverse#structure~preprocess_intf:traverse#signature