Skip to content
This repository was archived by the owner on Jun 15, 2023. It is now read-only.

Commit 523ff1e

Browse files
committed
1 parent d153447 commit 523ff1e

16 files changed

+1176
-27
lines changed

.depend

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
src/reactjs_jsx_ppx.cmx : src/reactjs_jsx_ppx.cmi
2+
src/reactjs_jsx_ppx.cmi :
13
src/res_ast_conversion.cmx : src/res_ast_conversion.cmi
24
src/res_ast_conversion.cmi :
35
src/res_ast_debugger.cmx : src/res_driver.cmx src/res_doc.cmx \
@@ -6,7 +8,7 @@ src/res_ast_debugger.cmi : src/res_driver.cmi
68
src/res_character_codes.cmx :
79
src/res_cli.cmx : src/res_driver_reason_binary.cmx \
810
src/res_driver_ml_parser.cmx src/res_driver_binary.cmx src/res_driver.cmx \
9-
src/res_ast_debugger.cmx
11+
src/res_ast_debugger.cmx src/reactjs_jsx_ppx.cmx
1012
src/res_comment.cmx : src/res_comment.cmi
1113
src/res_comment.cmi :
1214
src/res_comments_table.cmx : src/res_parsetree_viewer.cmx src/res_doc.cmx \
@@ -45,7 +47,7 @@ src/res_minibuffer.cmi :
4547
src/res_multi_printer.cmx : src/res_printer.cmx src/res_io.cmx \
4648
src/res_driver_reason_binary.cmx src/res_driver_ml_parser.cmx \
4749
src/res_driver.cmx src/res_diagnostics.cmx src/res_ast_conversion.cmx \
48-
src/res_multi_printer.cmi
50+
src/reactjs_jsx_ppx.cmx src/res_multi_printer.cmi
4951
src/res_multi_printer.cmi :
5052
src/res_outcome_printer.cmx : src/res_token.cmx src/res_doc.cmx \
5153
src/res_outcome_printer.cmi

Makefile

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ depend:
1111
$(OCAMLDEP) -native -I tests -I src src/*.ml src/*.mli tests/*.ml tests/*.mli > .depend
1212

1313
API_FILES = \
14+
src/reactjs_jsx_ppx.cmx\
1415
src/res_io.cmx\
1516
src/res_minibuffer.cmx\
1617
src/res_doc.cmx\
@@ -49,6 +50,8 @@ lib/rescript.exe: $(CLI_FILES)
4950

5051
build-native: lib/refmt.exe lib/rescript.exe depend
5152

53+
build-ppx: src/reactjs_jsx_ppx.cmx
54+
5255
bootstrap: build-native
5356
ocaml unix.cma ./scripts/bspack.ml -bs-main Res_cli -I src -o ./lib/rescript.ml
5457
./lib/rescript.exe -parse ml -print ns ./lib/Rescript.ml > ./lib/Rescript2.ml
@@ -94,4 +97,4 @@ clean:
9497
rm -rf lib/rescript.exe
9598
rm -rf lib/test.exe
9699
git clean -dfx src
97-
.PHONY: clean test roundtrip-test termination dce exception reanalyze bootstrap build-native
100+
.PHONY: clean test roundtrip-test termination dce exception reanalyze bootstrap build-native build-ppx

src/reactjs_jsx_ppx.ml

Lines changed: 880 additions & 0 deletions
Large diffs are not rendered by default.

src/reactjs_jsx_ppx.mli

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
(*
2+
This is the module that handles turning Reason JSX' agnostic function call into
3+
a ReasonReact-specific function call. Aka, this is a macro, using OCaml's ppx
4+
facilities; https://whitequark.org/blog/2014/04/16/a-guide-to-extension-
5+
points-in-ocaml/
6+
You wouldn't use this file directly; it's used by ReScript's
7+
bsconfig.json. Specifically, there's a field called `react-jsx` inside the
8+
field `reason`, which enables this ppx through some internal call in bsb
9+
*)
10+
11+
(*
12+
There are two different transforms that can be selected in this file (v2 and v3):
13+
v2:
14+
transform `[@JSX] div(~props1=a, ~props2=b, ~children=[foo, bar], ())` into
15+
`ReactDOMRe.createElement("div", ~props={"props1": 1, "props2": b}, [|foo,
16+
bar|])`.
17+
transform `[@JSX] div(~props1=a, ~props2=b, ~children=foo, ())` into
18+
`ReactDOMRe.createElementVariadic("div", ~props={"props1": 1, "props2": b}, foo)`.
19+
transform the upper-cased case
20+
`[@JSX] Foo.createElement(~key=a, ~ref=b, ~foo=bar, ~children=[], ())` into
21+
`ReasonReact.element(~key=a, ~ref=b, Foo.make(~foo=bar, [||]))`
22+
transform `[@JSX] [foo]` into
23+
`ReactDOMRe.createElement(ReasonReact.fragment, [|foo|])`
24+
v3:
25+
transform `[@JSX] div(~props1=a, ~props2=b, ~children=[foo, bar], ())` into
26+
`ReactDOMRe.createDOMElementVariadic("div", ReactDOMRe.domProps(~props1=1, ~props2=b), [|foo, bar|])`.
27+
transform the upper-cased case
28+
`[@JSX] Foo.createElement(~key=a, ~ref=b, ~foo=bar, ~children=[], ())` into
29+
`React.createElement(Foo.make, Foo.makeProps(~key=a, ~ref=b, ~foo=bar, ()))`
30+
transform the upper-cased case
31+
`[@JSX] Foo.createElement(~foo=bar, ~children=[foo, bar], ())` into
32+
`React.createElementVariadic(Foo.make, Foo.makeProps(~foo=bar, ~children=React.null, ()), [|foo, bar|])`
33+
transform `[@JSX] [foo]` into
34+
`ReactDOMRe.createElement(ReasonReact.fragment, [|foo|])`
35+
*)
36+
37+
val rewrite_implementation : Parsetree.structure -> Parsetree.structure
38+
39+
val rewrite_signature : Parsetree.signature -> Parsetree.signature

src/res_cli.ml

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -207,11 +207,12 @@ module CliArgProcessor = struct
207207
let processInterface =
208208
isInterface || len > 0 && (String.get [@doesNotRaise]) filename (len - 1) = 'i'
209209
in
210-
let parsingEngine =
210+
let parsingEngine, react =
211211
match origin with
212-
| "reasonBinary" -> Parser Res_driver_reason_binary.parsingEngine
213-
| "ml" | "ocaml" -> Parser Res_driver_ml_parser.parsingEngine
214-
| _ -> Parser Res_driver.parsingEngine
212+
| "reasonBinary" -> Parser Res_driver_reason_binary.parsingEngine, false
213+
| "ml" | "ocaml" -> Parser Res_driver_ml_parser.parsingEngine, false
214+
| "reactJsx" -> Parser Res_driver.parsingEngine, true
215+
| _ -> Parser Res_driver.parsingEngine, false
215216
in
216217
let printEngine =
217218
match target with
@@ -237,40 +238,50 @@ module CliArgProcessor = struct
237238
~source:parseResult.source
238239
~filename:parseResult.filename
239240
parseResult.diagnostics;
240-
if recover || not parseResult.invalid then
241+
if react then
242+
exit 1
243+
else if recover then
241244
printEngine.printInterface
242245
~width ~filename ~comments:parseResult.comments parseResult.parsetree
243246
else ()
244247
end
245248
else
249+
let parsetree =
250+
if react then Reactjs_jsx_ppx.rewrite_signature parseResult.parsetree else parseResult.parsetree
251+
in
246252
printEngine.printInterface
247-
~width ~filename ~comments:parseResult.comments parseResult.parsetree
253+
~width ~filename ~comments:parseResult.comments parsetree
248254
else
249255
let parseResult = backend.parseImplementation ~forPrinter ~filename in
250256
if parseResult.invalid then begin
251257
backend.stringOfDiagnostics
252258
~source:parseResult.source
253259
~filename:parseResult.filename
254260
parseResult.diagnostics;
255-
if recover || not parseResult.invalid then
261+
if react then
262+
exit 1
263+
else if recover then
256264
printEngine.printImplementation
257265
~width ~filename ~comments:parseResult.comments parseResult.parsetree
258266
else ()
259267
end
260268
else
269+
let parsetree =
270+
if react then Reactjs_jsx_ppx.rewrite_implementation parseResult.parsetree else parseResult.parsetree
271+
in
261272
printEngine.printImplementation
262-
~width ~filename ~comments:parseResult.comments parseResult.parsetree
273+
~width ~filename ~comments:parseResult.comments parsetree
263274
with
264275
| Failure txt ->
265276
prerr_string txt;
266277
prerr_newline();
267278
exit 1
268279
| _ -> exit 1
269-
[@@raises exit]
280+
[@@raises Invalid_argument, exit]
270281
end
271282

272283

273-
let [@raises exit] () =
284+
let [@raises Invalid_argument, exit] () =
274285
if not !Sys.interactive then begin
275286
ResClflags.parse ();
276287
match !ResClflags.files with

src/res_multi_printer.ml

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,40 @@ let printMl ~isInterface ~filename =
5151
~comments:parseResult.comments
5252
parseResult.parsetree
5353

54+
(* print files using React JSX ppx to res syntax *)
55+
let printJsx ~isInterface ~filename =
56+
if isInterface then
57+
let parseResult =
58+
Res_driver.parsingEngine.parseInterface ~forPrinter:true ~filename
59+
in
60+
if parseResult.invalid then
61+
begin
62+
Res_diagnostics.printReport parseResult.diagnostics parseResult.source;
63+
exit 1
64+
end
65+
else
66+
let jsxPass = Reactjs_jsx_ppx.rewrite_signature parseResult.parsetree in
67+
Res_printer.printInterface
68+
~width:defaultPrintWidth
69+
~comments:parseResult.comments
70+
jsxPass
71+
else
72+
let parseResult =
73+
Res_driver.parsingEngine.parseImplementation ~forPrinter:true ~filename
74+
in
75+
if parseResult.invalid then
76+
begin
77+
Res_diagnostics.printReport parseResult.diagnostics parseResult.source;
78+
exit 1
79+
end
80+
else
81+
let jsxPass = Reactjs_jsx_ppx.rewrite_implementation parseResult.parsetree in
82+
Res_printer.printImplementation
83+
~width:defaultPrintWidth
84+
~comments:parseResult.comments
85+
jsxPass
86+
[@@raises Invalid_argument, Failure, exit]
87+
5488
(* How does printing Reason to Res work?
5589
* -> open a tempfile
5690
* -> write the source code found in "filename" into the tempfile
@@ -122,7 +156,8 @@ let print language ~input =
122156
len > 0 && String.unsafe_get input (len - 1) = 'i'
123157
in
124158
match language with
159+
| `jsx -> printJsx ~isInterface ~filename:input
125160
| `res -> printRes ~isInterface ~filename:input
126161
| `ml -> printMl ~isInterface ~filename:input
127162
| `refmt path -> printReason ~refmtPath:path ~isInterface ~filename:input
128-
[@@raises Sys_error, exit]
163+
[@@raises Invalid_argument, Failure, Sys_error, exit]

src/res_multi_printer.mli

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
(* Interface to print source code from different languages to res.
22
* Takes a filename called "input" and returns the corresponding formatted res syntax *)
3-
val print: [`ml | `res | `refmt of string (* path to refmt *)] -> input: string -> string
3+
val print: [`jsx | `ml | `res | `refmt of string (* path to refmt *)] -> input: string -> string

tests/api/resReactJsx.res

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// test React JSX file
2+
3+
@react.component
4+
let make = (~msg) => {
5+
<div> {msg->React.string} </div>
6+
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`commentAtTop.react.res 1`] = `
4+
"@bs.obj
5+
external makeProps: (
6+
~msg: 'msg,
7+
~key: string=?,
8+
unit,
9+
) => {
10+
\\"msg\\": 'msg,
11+
// test React JSX file
12+
} = \\"\\"
13+
14+
let make =
15+
(@warning(\\"-16\\") ~msg) => {
16+
ReactDOMRe.createDOMElementVariadic(\\"div\\", [{msg->React.string}])
17+
}
18+
let make = {
19+
let \\\\\\"CommentAtTop.react\\" = (\\\\\\"Props\\": {\\"msg\\": 'msg}) =>
20+
make(~msg=\\\\\\"Props\\"[\\"msg\\"])
21+
\\\\\\"CommentAtTop.react\\"
22+
}
23+
"
24+
`;
25+
26+
exports[`externalWithCustomName.react.res.fixme 1`] = `""`;
27+
28+
exports[`innerModule.react.res 1`] = `
29+
"module Bar = {
30+
@bs.obj
31+
external makeProps: (
32+
~a: 'a,
33+
~b: 'b,
34+
~key: string=?,
35+
unit,
36+
) => {\\"a\\": 'a, \\"b\\": 'b} = \\"\\"
37+
let make =
38+
(@warning(\\"-16\\") ~a, @warning(\\"-16\\") ~b, _) => {
39+
Js.log(\\"This function should be named \`Test$Bar\`\\")
40+
ReactDOMRe.createDOMElementVariadic(\\"div\\", [])
41+
}
42+
let make = {
43+
let \\\\\\"InnerModule.react$Bar\\" = (\\\\\\"Props\\": {\\"a\\": 'a, \\"b\\": 'b}) =>
44+
make(~b=\\\\\\"Props\\"[\\"b\\"], ~a=\\\\\\"Props\\"[\\"a\\"], ())
45+
\\\\\\"InnerModule.react$Bar\\"
46+
}
47+
@bs.obj
48+
external componentProps: (
49+
~a: 'a,
50+
~b: 'b,
51+
~key: string=?,
52+
unit,
53+
) => {\\"a\\": 'a, \\"b\\": 'b} = \\"\\"
54+
55+
let component =
56+
(@warning(\\"-16\\") ~a, @warning(\\"-16\\") ~b, _) => {
57+
Js.log(\\"This function should be named \`Test$Bar$component\`\\")
58+
ReactDOMRe.createDOMElementVariadic(\\"div\\", [])
59+
}
60+
let component = {
61+
let \\\\\\"InnerModule.react$Bar$component\\" = (\\\\\\"Props\\": {\\"a\\": 'a, \\"b\\": 'b}) =>
62+
component(~b=\\\\\\"Props\\"[\\"b\\"], ~a=\\\\\\"Props\\"[\\"a\\"], ())
63+
\\\\\\"InnerModule.react$Bar$component\\"
64+
}
65+
}
66+
"
67+
`;
68+
69+
exports[`topLevel.react.res 1`] = `
70+
"@bs.obj
71+
external makeProps: (
72+
~a: 'a,
73+
~b: 'b,
74+
~key: string=?,
75+
unit,
76+
) => {\\"a\\": 'a, \\"b\\": 'b} = \\"\\"
77+
let make =
78+
(@warning(\\"-16\\") ~a, @warning(\\"-16\\") ~b, _) => {
79+
Js.log(\\"This function should be named 'TopLevel.react'\\")
80+
ReactDOMRe.createDOMElementVariadic(\\"div\\", [])
81+
}
82+
let make = {
83+
let \\\\\\"TopLevel.react\\" = (\\\\\\"Props\\": {\\"a\\": 'a, \\"b\\": 'b}) =>
84+
make(~b=\\\\\\"Props\\"[\\"b\\"], ~a=\\\\\\"Props\\"[\\"a\\"], ())
85+
\\\\\\"TopLevel.react\\"
86+
}
87+
"
88+
`;
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// test React JSX file
2+
3+
@react.component
4+
let make = (~msg) => {
5+
<div> {msg->React.string} </div>
6+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
module Foo = {
2+
@react.component @bs.module("Foo")
3+
external component: (~a: int, ~b: string, _) => React.element = "component"
4+
}
5+
6+
let t = <Foo.component a=1 b={"1"} />
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
module Bar = {
2+
@react.component
3+
let make = (~a, ~b, _) => {
4+
Js.log(
5+
"This function should be named `Test$Bar`",
6+
)
7+
<div />
8+
}
9+
@react.component
10+
let component = (~a, ~b, _) => {
11+
Js.log(
12+
"This function should be named `Test$Bar$component`",
13+
)
14+
<div />
15+
}
16+
}

tests/printer/reactJsx/render.spec.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
runPrinter(__dirname, false, false)
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
@react.component
2+
let make = (~a, ~b, _) => {
3+
Js.log("This function should be named 'TopLevel.react'")
4+
<div />
5+
}

tests/res_test.ml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,31 @@ let y: float
126126
|}
127127
)
128128

129+
(* test printing of React jsx file *)
130+
let () =
131+
let filename = "./tests/api/resReactJsx.res" in
132+
let prettySource = Res_multi_printer.print `jsx ~input:filename in
133+
assert (
134+
prettySource = {|@bs.obj
135+
external makeProps: (
136+
~msg: 'msg,
137+
~key: string=?,
138+
unit,
139+
) => {
140+
"msg": 'msg,
141+
// test React JSX file
142+
} = ""
143+
144+
let make =
145+
(@warning("-16") ~msg) => {
146+
ReactDOMRe.createDOMElementVariadic("div", [{msg->React.string}])
147+
}
148+
let make = {
149+
let \"ResReactJsx" = (\"Props": {"msg": 'msg}) => make(~msg=\"Props"["msg"])
150+
\"ResReactJsx"
151+
}
152+
|})
153+
129154
let () = print_endline "✅ multi printer api tests"
130155

131156
module OutcomePrinterTests = struct

0 commit comments

Comments
 (0)