123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330openPpxlibopenAst_builder.DefaultmoduleList=ListLabelsletrepo_url="https://github.com/davesnx/html_of_jsx"letissues_url="https://github.com/davesnx/html_of_jsx/issues"(* There's no pexp_list on Ppxlib since is not a constructor of the Parsetree *)letpexp_list~locxs=List.fold_left(List.revxs)~init:[%expr[]]~f:(funxsx->[%expr[%ex]::[%exs]])exceptionErrorofexpressionletraise_errorf~locfmt=letopenAst_builder.DefaultinPrintf.ksprintf(funmsg->letexpr=pexp_extension~loc(Location.error_extensionf~loc"[html_of_jsx] %s"msg)inraise(Errorexpr))fmtletcollect_propsvisitargs=letrecgoprops=function|[]->(None,props)|[(Nolabel,arg)]->(Some(visitarg),props)|(Nolabel,prop)::_->letloc=prop.pexp_locinraise_errorf~loc"an argument without a label could only be the last one"|(proplab,prop)::xs->go((proplab,visitprop)::props)xsingo[]argsletrecunwrap_children~fchildren=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~f(fchild::children)next|e->raise_errorf~loc:e.pexp_loc"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",[%expr[%epexp_list~locchildren]])::argsinpexp_apply~loccomponentpropsletvalidate_attr~locidname=matchHtml.findByNameidnamewith|Okp->p|Error`ElementNotFound->raise_errorf~loc{|HTML tag '%s' doesn't exist.
If this is not correct, please open an issue at %s|}idissues_url|Error(`AttributeNotFoundsuggestion)->letsuggestion=matchsuggestionwith|Somesuggestion->Printf.sprintf"Hint: Maybe you mean '%s'?\n"suggestion|None->""inraise_errorf~loc{|The attribute '%s' is not valid on a '%s' element.
%s
If this is not correct, please open an issue at %s.|}nameidsuggestionissues_urlletadd_attribute_type_constraint~loc~is_optional(type_:Html_attributes.kind)value=match(type_,is_optional)with|String,true->[%expr([%evalue]:stringoption)]|String,false->[%expr([%evalue]:string)]|Int,false->[%expr([%evalue]:int)]|Int,true->[%expr([%evalue]:intoption)]|Bool,false->[%expr([%evalue]:bool)]|Bool,true->[%expr([%evalue]:booloption)]|BooleanishString,false->[%expr([%evalue]:bool)]|BooleanishString,true->[%expr([%evalue]:booloption)](* We treat `Style` as string *)|Style,false->[%expr([%evalue]:string)]|Style,true->[%expr([%evalue]:stringoption)]letmake_attribute~loc~is_optional~propattribute_nameattribute_value=letopenHtml_attributesinmatch(prop,is_optional)with|Rich_attribute{type_=String;_},false|Attribute{type_=String;_},false->[%exprSome([%eattribute_name],`String[%eattribute_value])]|Rich_attribute{type_=String;_},true|Attribute{type_=String;_},true->[%exprStdlib.Option.map(funv->([%eattribute_name],`Stringv))[%eattribute_value]]|Rich_attribute{type_=Int;_},false|Attribute{type_=Int;_},false->[%exprSome([%eattribute_name],`Int[%eattribute_value])]|Rich_attribute{type_=Int;_},true|Attribute{type_=Int;_},true->[%exprStdlib.Option.map(funv->([%eattribute_name],`Intv))[%eattribute_value]]|Rich_attribute{type_=Bool;_},false|Attribute{type_=Bool;_},false->[%exprSome([%eattribute_name],`Bool[%eattribute_value])]|Rich_attribute{type_=Bool;_},true|Attribute{type_=Bool;_},true->[%exprStdlib.Option.map(funv->([%eattribute_name],`Boolv))[%eattribute_value]](* BooleanishString needs to transform bool into string *)|Rich_attribute{type_=BooleanishString;_},false|Attribute{type_=BooleanishString;_},false->[%exprSome([%eattribute_name],`String(Bool.to_string[%eattribute_value]))]|Rich_attribute{type_=BooleanishString;_},true|Attribute{type_=BooleanishString;_},true->[%exprStdlib.Option.map(funv->([%eattribute_name],`Stringv))(Bool.to_string[%eattribute_value])]|Rich_attribute{type_=Style;_},false|Attribute{type_=Style;_},false->[%exprSome("style",`String[%eattribute_value])]|Rich_attribute{type_=Style;_},true|Attribute{type_=Style;_},true->[%exprStdlib.Option.map(funv->("style",`Stringv))[%eattribute_value]]|Event_,false->[%exprSome([%eattribute_name],`String[%eattribute_value])]|Event_,true->[%exprStdlib.Option.map(funv->([%eattribute_name],`Stringv))[%eattribute_value]]letis_optional=functionOptional_->true|_->falselettransform_labelled~loc:_parentLoc~tag_nameprops(prop_label,value)=letloc=props.pexp_locinmatchprop_labelwith|Nolabel->props|Optionalname|Labelledname->letis_optional=is_optionalprop_labelinletattribute=validate_attr~loctag_namenameinletattribute_type=matchattributewith|Rich_attribute{type_;_}->type_|Attribute{type_;_}->type_|Event_->Stringinletattribute_name=Html.getNameattributeinletattribute_name_expr=estring~locattribute_nameinletattribute_value=add_attribute_type_constraint~loc~is_optionalattribute_typevalueinletattribute_final=make_attribute~loc~is_optional~prop:attributeattribute_name_exprattribute_valuein[%expr[%eattribute_final]::[%eprops]]lettransform_attributes~loc~tag_nameattrs=letattrs=List.revattrs|>List.fold_left~f:(transform_labelled~loc~tag_name)~init:[%expr[]]inmatchattrswith|[%expr[]]->[%expr[]]|attrs->(* We need to filter attributes since optionals are represented as None *)[%exprStdlib.List.filter_mapStdlib.Fun.id[%eattrs]]letrewrite_node~loctag_nameargschildren=letdom_node_name=estring~loctag_nameinletattributes=transform_attributes~loc~tag_nameargsinmatchchildrenwith|Somechildren->letchildrens=pexp_list~locchildrenin[%exprJSX.node[%edom_node_name][%eattributes][%echildrens]]|None->[%exprJSX.node[%edom_node_name][%eattributes][]]letsplit_args~mapperargs=letchildren=ref(Location.none,[])inletrest=List.filter_mapargs~f:(function|Labelled"children",children_expression->letchildren'=unwrap_children[]~f:(fune->letexpression=matche.pexp_descwith|Pexp_constant(Pconst_string_)->letloc=e.pexp_locin[%exprJSX.string[%ee]]|_->einmapperexpression)children_expressioninchildren:=(children_expression.pexp_loc,children');None|arg_label,expression->Some(arg_label,mapperexpression))inletchildren_prop=match!childrenwith_,[]->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~loc~mapperchildren=letrecrun_mapperchildrenaccum=matchchildrenwith|[%expr[]]->reverse_pexp_list~locaccum|[%expr[%e?v]::[%e?acc]]whenlist_have_tailacc.pexp_desc->[%expr[%emapper#expressionv]]|[%expr[%e?v]::[%e?acc]]->run_mapperacc[%expr[%emapper#expressionv]::[%eaccum]]|notAList->mapper#expressionnotAListinrun_mapperchildren[%expr[]]letrewrite_jsx=object(self)inheritAst_traverse.mapassupermethod!expressionexpr=trymatchexpr.pexp_descwith|Pexp_apply(({pexp_desc=Pexp_ident_;_}astag),args)whenhas_jsx_attrexpr.pexp_attributes->(letchildren,rest_of_args=split_args~mapper:self#expressionargsinmatchtag.pexp_descwith(* div() [@JSX] *)|Pexp_ident{txt=Lidentname;loc=name_loc}whenHtml.is_html_elementname||Html.is_svg_elementname->rewrite_node~loc:name_locnamerest_of_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:tag.pexp_locidrest_of_argschildren(* local_function() [@JSX] *)|Pexp_identid->rewrite_component~loc:tag.pexp_locidrest_of_argschildren|_->assertfalse)(* div() [@JSX] *)|Pexp_apply(_tag,_props)whenhas_jsx_attrexpr.pexp_attributes->raise_errorf~loc:expr.pexp_loc"tag should be an identifier"(* <> </> 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|[],_->super#expressionexpr|_,_rest_attributes->letchildren=transform_items_of_list~loc~mapper:selfexprin[%exprJSX.list[%echildren]])|_->super#expressionexprwithErrorerr->[%expr[%eerr]]endlet()=letdriver_args=[("Enable htmx attributes in HTML and SVG elements","-htmx",Arg.Unit(fun()->Extra_attributes.setHtmx));("Enable react attributes in HTML and SVG elements","-react",Arg.Unit(fun()->Extra_attributes.setReact));(* ( "-custom",
Arg.String (fun file -> Static_attributes.extra_properties := Some file),
"FILE Load inferred types from cmo file." ); *)]inList.iter~f:(fun(doc,key,spec)->Driver.add_argkeyspec~doc)driver_args;Driver.register_transformation"html_of_jsx.ppx"~preprocess_impl:rewrite_jsx#structure