Source file jsx_helper.ml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
open Printf
open Asttypes
open Longident
open Parsetree
open Ast_helper
let make_loc (startpos, endpos) =
{
Location.loc_start = startpos;
Location.loc_end = endpos;
Location.loc_ghost = false;
}
let mkloc = Location.mkloc
let mkexp ~loc d = Exp.mk ~loc:(make_loc loc) d
let mkjsxexp ~loc:loc' e =
let e = mkexp ~loc:loc' e in
let loc = make_loc loc' in
let pexp_attributes = [ Attr.mk ~loc { txt = "JSX"; loc } (PStr []) ] in
{ e with pexp_attributes }
let rec equal_longindent a b =
match a, b with
| Longident.Lident a, Longident.Lident b -> String.equal a b
| Ldot (pa, a), Ldot (pb, b) ->
String.equal a.txt b.txt && equal_longindent pa.txt pb.txt
| Lapply _, _ | _, Lapply _ -> assert false
| _ -> false
let make_jsx_element ~raise ~loc:_ ~tag ~end_tag ~props ~children () =
let () =
match end_tag with
| None -> ()
| Some (end_tag, (_, end_loc_e)) ->
let eq =
match tag, end_tag with
| (`Module, _, s), (`Module, _, e) -> equal_longindent s e
| (`Value, _, s), (`Value, _, e) -> equal_longindent s e
| _ -> false
in
if not eq then
let _, (end_loc_s, _), _ = end_tag in
let end_loc = end_loc_s, end_loc_e in
let _, start_loc, tag = tag in
let tag = Longident.flatten tag |> String.concat "." in
raise
Syntaxerr.(
Error
(Unclosed
( make_loc start_loc,
sprintf "<%s>" tag,
make_loc end_loc,
sprintf "</%s>" tag )))
in
let tag =
match tag with
| `Value, loc, txt ->
mkexp ~loc (Pexp_ident { loc = make_loc loc; txt })
| `Module, loc, txt ->
let txt = Longident.Ldot (mkloc txt (make_loc loc), mkloc "createElement" (make_loc loc)) in
mkexp ~loc (Pexp_ident { loc = make_loc loc; txt })
in
let props =
let prop_exp ~loc name =
let id = mkloc (Lident name.txt) (make_loc loc) in
mkexp ~loc (Pexp_ident id)
in
List.map
(function
| loc, `Prop_punned name -> Labelled {txt=name.txt;loc = make_loc loc}, prop_exp ~loc name
| loc, `Prop_opt_punned name -> Optional {txt=name.txt;loc = make_loc loc}, prop_exp ~loc name
| _loc, `Prop (name, expr) -> Labelled name, expr
| _loc, `Prop_opt (name, expr) -> Optional name, expr)
props
in
let unit =
Exp.mk ~loc:Location.none
(Pexp_construct ({ txt = Lident "()"; loc = Location.none }, None))
in
let props = (Labelled {txt="children"; loc=children.pexp_loc}, children) :: props in
Pexp_apply (tag, (Nolabel, unit) :: props)
(** A [@JSX] application that can be printed with JSX syntax. *)
type element = {
tag : string;
tag_loc : Location.t;
props : (arg_label * expression) list;
children_loc : Location.t;
children : expression list;
}
(** Classify a [@JSX] application. JSX syntax can only express applications
of the exact shape produced by [make_jsx_element]: an identifier tag
applied to one unlabelled [()] argument, one [~children] argument that
is a list literal, and labelled or optional props. Hand-written [@JSX]
applications may have any other shape (see
ocaml-mlx/ocamlformat-mlx#12), in which case [None] is returned and the
application must be printed as a regular application. *)
let classify_element ~attrs e0 args =
let tag =
let rec ident_of = function
| Lident name -> Some name
| Ldot (id, name) ->
Option.map (fun path -> path ^ "." ^ name.txt) (ident_of id.txt)
| Lapply _ -> None
in
match e0.pexp_desc with
| Pexp_ident { txt = Lident name; loc } -> Some (name, loc)
| Pexp_ident { txt = Ldot (id, name); loc = _ } ->
Option.map
(fun path ->
let tag =
match name.txt with
| "createElement" -> path
| name -> path ^ "." ^ name
in
(tag, e0.pexp_loc))
(ident_of id.txt)
| _ -> None
in
let units, children, props =
List.fold_right
(fun arg (units, children, props) ->
match arg with
| ( Nolabel,
{ pexp_desc = Pexp_construct ({ txt = Lident "()"; _ }, None);
pexp_attributes = [];
_ } ) ->
(() :: units, children, props)
| ( Labelled { txt = "children"; _ },
{ pexp_desc = Pexp_list es; pexp_attributes = []; pexp_loc; _ } )
->
(units, (pexp_loc, es) :: children, props)
| ( Labelled { txt = "children"; _ },
{ pexp_desc = Pexp_construct ({ txt = Lident "[]"; _ }, None);
pexp_attributes = [];
pexp_loc;
_ } ) ->
(units, (pexp_loc, []) :: children, props)
| arg -> (units, children, arg :: props))
args ([], [], [])
in
let is_prop = function
| Labelled { txt = "children"; _ }, _ ->
false
| (Labelled _ | Optional _), _ -> true
| Nolabel, _ -> false
in
match (attrs, tag, units, children) with
| ( [ { attr_name = { txt = "JSX"; _ }; attr_payload = PStr []; _ } ],
Some (tag, tag_loc),
[ () ],
[ (children_loc, children) ] )
when List.for_all is_prop props ->
Some { tag; tag_loc; props; children_loc; children }
| _ -> None