diff --git a/CHANGELOG.md b/CHANGELOG.md index 22a3b4d..6e7a032 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # CHANGELOG +# 0.1.10 + +- Add `@ppx_ts.toArray` feature + # 0.1.9 - Build the executable with static linking for Linux with musl diff --git a/README.md b/README.md index 47e1b93..1655fd6 100644 --- a/README.md +++ b/README.md @@ -121,6 +121,16 @@ type t_omit_name = { } ``` +#### `toArray` + +```rescript +@ppx_ts.toArray +type t = Name | Age + +// automatically generated +let t_toArray = ["Name", "Age"] +``` + ### Extension `%` #### `keyOf` diff --git a/package.json b/package.json index 2ed435f..72a6857 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@greenlabs/ppx-ts", - "version": "0.1.9", + "version": "0.1.10", "description": "ReScript PPX helps binding to typescript modules", "license": "MIT", "author": "Greenlabs Dev ", diff --git a/ppx_ts.opam b/ppx_ts.opam index c3c2363..0802f94 100644 --- a/ppx_ts.opam +++ b/ppx_ts.opam @@ -1,6 +1,6 @@ # This file is generated by dune, edit dune-project instead opam-version: "2.0" -version: "0.1.9" +version: "0.1.10" synopsis: "A PPX helps binding to typescript modules" description: "" maintainer: ["developer@greenlabs.co.kr"] diff --git a/rescript/__tests__/test.js b/rescript/__tests__/test.js index 4dc78f2..e456643 100644 --- a/rescript/__tests__/test.js +++ b/rescript/__tests__/test.js @@ -19,4 +19,19 @@ Jest.describe("keyOf", (function (param) { })); })); +Jest.describe("toArray", (function (param) { + Jest.test("check array length", (function (param) { + var length = View.t8_toArray.length; + return Jest.Expect.toEqual(2, Jest.Expect.expect(length)); + })); + Jest.test("The first item is 'Name'", (function (param) { + var first = View.t8_toArray[0]; + return Jest.Expect.toEqual("Name", Jest.Expect.expect(first)); + })); + return Jest.test("The second item is 'Age'", (function (param) { + var first = View.t8_toArray[1]; + return Jest.Expect.toEqual("Age", Jest.Expect.expect(first)); + })); + })); + /* Not a pure module */ diff --git a/rescript/__tests__/test.res b/rescript/__tests__/test.res index 84748b6..077ac0c 100644 --- a/rescript/__tests__/test.res +++ b/rescript/__tests__/test.res @@ -17,4 +17,21 @@ describe("keyOf", _ => { let nameT2InString = nameT2->View.t2_keyToString expect(nameT2InString) |> toEqual("name") }) -}) \ No newline at end of file +}) + +describe("toArray", _ => { + test("check array length", _ => { + let length = View.t8_toArray->Array.length + expect(length) |> toEqual(2) + }) + + test("The first item is 'Name'", _ => { + let first = View.t8_toArray->Array.unsafe_get(0) + expect(first) |> toEqual("Name") + }) + + test("The second item is 'Age'", _ => { + let first = View.t8_toArray->Array.unsafe_get(1) + expect(first) |> toEqual("Age") + }) +}) diff --git a/rescript/src/View.js b/rescript/src/View.js index 21addff..cc8600d 100644 --- a/rescript/src/View.js +++ b/rescript/src/View.js @@ -1,9 +1,11 @@ // Generated by ReScript, PLEASE EDIT WITH CARE 'use strict'; +var Curry = require("rescript/lib/js/curry.js"); var Spice = require("@greenlabs/ppx-spice/src/rescript/Spice.js"); var Js_dict = require("rescript/lib/js/js_dict.js"); var Js_json = require("rescript/lib/js/js_json.js"); +var Belt_Array = require("rescript/lib/js/belt_Array.js"); var Belt_Option = require("rescript/lib/js/belt_Option.js"); function err_encode(v) { @@ -78,6 +80,55 @@ function t_keyToString(key) { } } +function t1_encode(v) { + if (v) { + return ["LastName"]; + } else { + return ["FirstName"]; + } +} + +function t1_decode(v) { + var json_arr = Js_json.classify(v); + if (typeof json_arr === "number") { + return Spice.error(undefined, "Not a variant", v); + } + if (json_arr.TAG !== /* JSONArray */3) { + return Spice.error(undefined, "Not a variant", v); + } + var json_arr$1 = json_arr._0; + if (json_arr$1.length === 0) { + return Spice.error(undefined, "Expected variant, found empty array", v); + } + var tagged = json_arr$1.map(Js_json.classify); + var match = Belt_Array.getExn(tagged, 0); + if (typeof match !== "number" && match.TAG === /* JSONString */0) { + switch (match._0) { + case "FirstName" : + if (tagged.length !== 1) { + return Spice.error(undefined, "Invalid number of arguments to variant constructor", v); + } else { + return { + TAG: /* Ok */0, + _0: /* FirstName */0 + }; + } + case "LastName" : + if (tagged.length !== 1) { + return Spice.error(undefined, "Invalid number of arguments to variant constructor", v); + } else { + return { + TAG: /* Ok */0, + _0: /* LastName */1 + }; + } + default: + + } + } + return Spice.error(undefined, "Invalid variant constructor", Belt_Array.getExn(json_arr$1, 0)); +} + function t1_keyToString(key) { if (key) { return "lastName"; @@ -86,6 +137,68 @@ function t1_keyToString(key) { } } +function t2_encode(v) { + switch (v) { + case /* Name */0 : + return ["Name"]; + case /* Age */1 : + return ["Age"]; + case /* IsKorean */2 : + return ["IsKorean"]; + + } +} + +function t2_decode(v) { + var json_arr = Js_json.classify(v); + if (typeof json_arr === "number") { + return Spice.error(undefined, "Not a variant", v); + } + if (json_arr.TAG !== /* JSONArray */3) { + return Spice.error(undefined, "Not a variant", v); + } + var json_arr$1 = json_arr._0; + if (json_arr$1.length === 0) { + return Spice.error(undefined, "Expected variant, found empty array", v); + } + var tagged = json_arr$1.map(Js_json.classify); + var match = Belt_Array.getExn(tagged, 0); + if (typeof match !== "number" && match.TAG === /* JSONString */0) { + switch (match._0) { + case "Age" : + if (tagged.length !== 1) { + return Spice.error(undefined, "Invalid number of arguments to variant constructor", v); + } else { + return { + TAG: /* Ok */0, + _0: /* Age */1 + }; + } + case "IsKorean" : + if (tagged.length !== 1) { + return Spice.error(undefined, "Invalid number of arguments to variant constructor", v); + } else { + return { + TAG: /* Ok */0, + _0: /* IsKorean */2 + }; + } + case "Name" : + if (tagged.length !== 1) { + return Spice.error(undefined, "Invalid number of arguments to variant constructor", v); + } else { + return { + TAG: /* Ok */0, + _0: /* Name */0 + }; + } + default: + + } + } + return Spice.error(undefined, "Invalid variant constructor", Belt_Array.getExn(json_arr$1, 0)); +} + function t2_keyToString(key) { switch (key) { case /* Name */0 : @@ -98,24 +211,20 @@ function t2_keyToString(key) { } } -function t5_encode(v) { +function t3_encode(encoder_a, v) { return Js_dict.fromArray([ [ - "name", - err_encode(v.name) - ], - [ - "age", - err_encode(v.age) + "firstName", + Curry._1(encoder_a, v.firstName) ], [ - "isKorean", - err_encode(v.isKorean) + "lastName", + Curry._1(encoder_a, v.lastName) ] ]); } -function t5_decode(v) { +function t3_decode(decoder_a, v) { var dict = Js_json.classify(v); if (typeof dict === "number") { return Spice.error(undefined, "Not an object", v); @@ -124,70 +233,57 @@ function t5_decode(v) { return Spice.error(undefined, "Not an object", v); } var dict$1 = dict._0; - var name = err_decode(Belt_Option.getWithDefault(Js_dict.get(dict$1, "name"), null)); - if (name.TAG === /* Ok */0) { - var age = err_decode(Belt_Option.getWithDefault(Js_dict.get(dict$1, "age"), null)); - if (age.TAG === /* Ok */0) { - var isKorean = err_decode(Belt_Option.getWithDefault(Js_dict.get(dict$1, "isKorean"), null)); - if (isKorean.TAG === /* Ok */0) { - return { - TAG: /* Ok */0, - _0: { - name: name._0, - age: age._0, - isKorean: isKorean._0 - } - }; - } - var e = isKorean._0; + var firstName = Curry._1(decoder_a, Belt_Option.getWithDefault(Js_dict.get(dict$1, "firstName"), null)); + if (firstName.TAG === /* Ok */0) { + var lastName = Curry._1(decoder_a, Belt_Option.getWithDefault(Js_dict.get(dict$1, "lastName"), null)); + if (lastName.TAG === /* Ok */0) { return { - TAG: /* Error */1, + TAG: /* Ok */0, _0: { - path: ".isKorean" + e.path, - message: e.message, - value: e.value + firstName: firstName._0, + lastName: lastName._0 } }; } - var e$1 = age._0; + var e = lastName._0; return { TAG: /* Error */1, _0: { - path: ".age" + e$1.path, - message: e$1.message, - value: e$1.value + path: ".lastName" + e.path, + message: e.message, + value: e.value } }; } - var e$2 = name._0; + var e$1 = firstName._0; return { TAG: /* Error */1, _0: { - path: ".name" + e$2.path, - message: e$2.message, - value: e$2.value + path: ".firstName" + e$1.path, + message: e$1.message, + value: e$1.value } }; } -function t6_encode(v) { +function t4_encode(encoder_a, v) { return Js_dict.fromArray([ [ "name", - Spice.stringToJson(v.name) + Curry._1(encoder_a, v.name) ], [ "age", - Spice.stringToJson(v.age) + Curry._1(encoder_a, v.age) ], [ "isKorean", - Spice.boolToJson(v.isKorean) + Curry._1(encoder_a, v.isKorean) ] ]); } -function t6_decode(v) { +function t4_decode(decoder_a, v) { var dict = Js_json.classify(v); if (typeof dict === "number") { return Spice.error(undefined, "Not an object", v); @@ -196,11 +292,11 @@ function t6_decode(v) { return Spice.error(undefined, "Not an object", v); } var dict$1 = dict._0; - var name = Spice.stringFromJson(Belt_Option.getWithDefault(Js_dict.get(dict$1, "name"), null)); + var name = Curry._1(decoder_a, Belt_Option.getWithDefault(Js_dict.get(dict$1, "name"), null)); if (name.TAG === /* Ok */0) { - var age = Spice.stringFromJson(Belt_Option.getWithDefault(Js_dict.get(dict$1, "age"), null)); + var age = Curry._1(decoder_a, Belt_Option.getWithDefault(Js_dict.get(dict$1, "age"), null)); if (age.TAG === /* Ok */0) { - var isKorean = Spice.boolFromJson(Belt_Option.getWithDefault(Js_dict.get(dict$1, "isKorean"), null)); + var isKorean = Curry._1(decoder_a, Belt_Option.getWithDefault(Js_dict.get(dict$1, "isKorean"), null)); if (isKorean.TAG === /* Ok */0) { return { TAG: /* Ok */0, @@ -242,24 +338,24 @@ function t6_decode(v) { }; } -function t7_encode(v) { +function t5_encode(v) { return Js_dict.fromArray([ [ "name", - Spice.optionToJson(Spice.stringToJson, v.name) + err_encode(v.name) ], [ "age", - Spice.optionToJson(Spice.intToJson, v.age) + err_encode(v.age) ], [ "isKorean", - Spice.optionToJson(Spice.boolToJson, v.isKorean) + err_encode(v.isKorean) ] ]); } -function t7_decode(v) { +function t5_decode(v) { var dict = Js_json.classify(v); if (typeof dict === "number") { return Spice.error(undefined, "Not an object", v); @@ -268,11 +364,11 @@ function t7_decode(v) { return Spice.error(undefined, "Not an object", v); } var dict$1 = dict._0; - var name = Spice.optionFromJson(Spice.stringFromJson, Belt_Option.getWithDefault(Js_dict.get(dict$1, "name"), null)); + var name = err_decode(Belt_Option.getWithDefault(Js_dict.get(dict$1, "name"), null)); if (name.TAG === /* Ok */0) { - var age = Spice.optionFromJson(Spice.intFromJson, Belt_Option.getWithDefault(Js_dict.get(dict$1, "age"), null)); + var age = err_decode(Belt_Option.getWithDefault(Js_dict.get(dict$1, "age"), null)); if (age.TAG === /* Ok */0) { - var isKorean = Spice.optionFromJson(Spice.boolFromJson, Belt_Option.getWithDefault(Js_dict.get(dict$1, "isKorean"), null)); + var isKorean = err_decode(Belt_Option.getWithDefault(Js_dict.get(dict$1, "isKorean"), null)); if (isKorean.TAG === /* Ok */0) { return { TAG: /* Ok */0, @@ -314,14 +410,24 @@ function t7_decode(v) { }; } +var t8_toArray = [ + "Name", + "Age" +]; + exports.Err = Err; exports.t_keyToString = t_keyToString; +exports.t1_encode = t1_encode; +exports.t1_decode = t1_decode; exports.t1_keyToString = t1_keyToString; +exports.t2_encode = t2_encode; +exports.t2_decode = t2_decode; exports.t2_keyToString = t2_keyToString; +exports.t3_encode = t3_encode; +exports.t3_decode = t3_decode; +exports.t4_encode = t4_encode; +exports.t4_decode = t4_decode; exports.t5_encode = t5_encode; exports.t5_decode = t5_decode; -exports.t6_encode = t6_encode; -exports.t6_decode = t6_decode; -exports.t7_encode = t7_encode; -exports.t7_decode = t7_decode; +exports.t8_toArray = t8_toArray; /* No side effect */ diff --git a/rescript/src/View.res b/rescript/src/View.res index bbec6cf..96158c0 100644 --- a/rescript/src/View.res +++ b/rescript/src/View.res @@ -14,7 +14,7 @@ module Err: Err = { } } -@ppx_ts.keyOf +@ppx_ts.keyOf @ppx_ts.setType(string) type t = { name: string, age: int, @@ -31,7 +31,8 @@ type t3 = %ppx_ts.toGeneric(Err.err) type t4 = %ppx_ts.toGeneric(t) @spice type t5 = %ppx_ts.setType((t, Err.err)) -@spice type t6 = %ppx_ts.setTypeExceptBool((t, string)) -@spice -type t7 = %ppx_ts.partial(t) \ No newline at end of file +type t7 = %ppx_ts.partial(t) + +@ppx_ts.toArray +type t8 = Name(string) | Age(int) diff --git a/rescript/src/View.resi b/rescript/src/View.resi index 0002ef4..d9b4409 100644 --- a/rescript/src/View.resi +++ b/rescript/src/View.resi @@ -8,11 +8,11 @@ module type Err = { module Err: Err -@ppx_ts.keyOf +@ppx_ts.keyOf @ppx_ts.setType(string) type t = { name: string, age: int, - isKorean: bool + isKorean: bool, } @spice @@ -25,7 +25,8 @@ type t3 = %ppx_ts.toGeneric(Err.err) type t4 = %ppx_ts.toGeneric(t) @spice type t5 = %ppx_ts.setType((t, Err.err)) -@spice type t6 = %ppx_ts.setTypeExceptBool((t, string)) -@spice type t7 = %ppx_ts.partial(t) + +@ppx_ts.toArray +type t8 = Name(string) | Age(int) diff --git a/src/sig_to_array.ml b/src/sig_to_array.ml new file mode 100644 index 0000000..bb6bb60 --- /dev/null +++ b/src/sig_to_array.ml @@ -0,0 +1,29 @@ +open Ppxlib +open Parsetree +open Ast_helper +open Utils + +(* keyOf attribute mapper *) +let make_signature_items name loc manifest kind suffix = + match (manifest, kind) with + | None, Ptype_abstract -> fail loc "Can't handle the unspecified type" + | None, Ptype_variant _ -> + [ + Sig.value ~loc + (Val.mk + (mkloc (name ^ "_" ^ suffix) loc) + (Typ.constr (Utils.lid "array") + [ Typ.constr (Utils.lid "string") [] ])); + ] + | _ -> fail loc "This type is not handled by ppx_ts" + +(* keyOf extension mapper *) +let make_new_signature_item name loc manifest kind attributes = + match (manifest, kind) with + | None, Ptype_abstract -> fail loc "Can't handle the unspecified type" + | None, Ptype_record _ -> + Sig.value ~loc + (Val.mk ~loc ~attrs:attributes (mkloc name loc) + (Typ.constr (Utils.lid "array") + [ Typ.constr (Utils.lid "string") [] ])) + | _ -> fail loc "This type is not handled by ppx_ts" diff --git a/src/signature.ml b/src/signature.ml index 938074b..f0596e1 100644 --- a/src/signature.ml +++ b/src/signature.ml @@ -34,6 +34,9 @@ let map_type_decl decl = | Some (Omit (suffix, payload)) -> Sig_omit.make_signature_item type_name ptype_loc ptype_manifest ptype_kind suffix payload + | Some (ToArray (suffix, _)) -> + Sig_to_array.make_signature_items type_name ptype_loc + ptype_manifest ptype_kind suffix | None -> []) |> List.concat diff --git a/src/str_to_array.ml b/src/str_to_array.ml new file mode 100644 index 0000000..e27aaa2 --- /dev/null +++ b/src/str_to_array.ml @@ -0,0 +1,47 @@ +open Ppxlib +open Parsetree +open Ast_helper +open Utils + +(* toArray attribute mapper *) +let make_structure_items name loc manifest kind suffix = + match (manifest, kind) with + (* type t *) + | None, Ptype_abstract -> fail loc "Can't handle the unspecified type" + | None, Ptype_variant cds -> + let keys = + cds |> List.map (fun { pcd_name = { txt }; pcd_loc } -> (txt, pcd_loc)) + in + + [ + Str.value ~loc Nonrecursive + [ + Vb.mk + (Pat.var (mkloc (name ^ "_" ^ suffix) loc)) + (Exp.array + (keys + |> List.map (fun (key, loc) -> + Exp.constant (Pconst_string (key, loc, None))))); + ]; + ] + | _ -> fail loc "This type is not handled by @ppx_ts.toArray" + +(* toArray extension mapper *) +let make_new_structure_item name loc manifest kind attributes = + match (manifest, kind) with + (* type t *) + | None, Ptype_abstract -> fail loc "Can't handle the unspecified type" + | None, Ptype_variant cds -> + let keys = + cds |> List.map (fun { pcd_name = { txt }; pcd_loc } -> (txt, pcd_loc)) + in + Str.value ~loc Nonrecursive + [ + Vb.mk ~loc ~attrs:attributes + (Pat.var (mkloc name loc)) + (Exp.array + (keys + |> List.map (fun (key, loc) -> + Exp.constant (Pconst_string (key, loc, None))))); + ] + | _ -> fail loc "This type is not handled by %ppx_ts.toArray" diff --git a/src/structure.ml b/src/structure.ml index 175a167..90b3fe0 100644 --- a/src/structure.ml +++ b/src/structure.ml @@ -34,6 +34,9 @@ let map_type_decl decl = | Some (Omit (suffix, payload)) -> Str_omit.make_structure_item type_name ptype_loc ptype_manifest ptype_kind suffix payload + | Some (ToArray (suffix, _)) -> + Str_to_array.make_structure_items type_name ptype_loc + ptype_manifest ptype_kind suffix | None -> []) |> List.concat diff --git a/src/utils.ml b/src/utils.ml index 6f74f45..7158e54 100644 --- a/src/utils.ml +++ b/src/utils.ml @@ -11,6 +11,7 @@ type attribute_kind = | Partial of string * payload | Pick of string * payload | Omit of string * payload + | ToArray of string * payload type extension_kind = | KeyOf of string * string list * payload * attributes @@ -29,6 +30,7 @@ let suffix_partial = "partial" let suffix_pick = "pick" let suffix_omit = "omit" let suffix_key_to_string = "keyToString" +let suffix_to_array = "toArray" (* make attribute name to suffix string *) let mk_attr_with_suffix attr_name suffix = attr_name ^ "." ^ suffix @@ -70,6 +72,8 @@ let parse_attribute { attr_name = { Location.txt }; attr_payload } : Some (Pick (suffix_pick, attr_payload)) else if txt = mk_attr_with_suffix attribute_name suffix_omit then Some (Omit (suffix_omit, attr_payload)) + else if txt = mk_attr_with_suffix attribute_name suffix_to_array then + Some (ToArray (suffix_to_array, attr_payload)) else None let parse_extension { ptype_name; ptype_manifest; ptype_attributes } :