forked from rescript-lang/rescript
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathEmitType.ml
417 lines (386 loc) · 13.3 KB
/
EmitType.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
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
open GenTypeCommon
let fileHeader ~sourceFile =
let makeHeader ~lines =
match lines with
| [line] -> "/* " ^ line ^ " */\n"
| _ ->
"/** \n"
^ (lines |> List.map (fun line -> " * " ^ line) |> String.concat "\n")
^ "\n */\n"
in
makeHeader
~lines:["TypeScript file generated from " ^ sourceFile ^ " by genType."]
^ "/* eslint-disable import/first */\n\n"
let generatedFilesExtension ~(config : Config.t) =
match config.generatedFileExtension with
| Some s ->
(* from .foo.bar to .foo *)
Filename.remove_extension s
| None -> ".gen"
let outputFileSuffix ~(config : Config.t) =
match config.generatedFileExtension with
| Some s when Filename.extension s <> "" (* double extension *) -> s
| _ -> generatedFilesExtension ~config ^ ".tsx"
let generatedModuleExtension ~config = generatedFilesExtension ~config
let shimExtension = ".shim.ts"
let interfaceName ~(config : Config.t) name =
match config.exportInterfaces with
| true -> "I" ^ name
| false -> name
let typeAny = ident ~builtin:true "any"
let typeReactComponent ~propsType =
"React.ComponentType" |> ident ~builtin:true ~typeArgs:[propsType]
let typeReactContext ~type_ =
"React.Context" |> ident ~builtin:true ~typeArgs:[type_]
let typeReactElementTypeScript = ident ~builtin:true "JSX.Element"
let typeReactChildTypeScript = ident ~builtin:true "React.ReactNode"
let typeReactElement = typeReactElementTypeScript
let typeReactChild = typeReactChildTypeScript
let isTypeReactElement type_ = type_ == typeReactElement
let typeReactDOMReDomRef =
"React.Ref" |> ident ~builtin:true ~typeArgs:[unknown]
let typeReactEventMouseT = "MouseEvent" |> ident ~builtin:true
let reactRefCurrent = "current"
let typeReactRef ~type_ =
Object
( Open,
[
{
mutable_ = Mutable;
nameJS = reactRefCurrent;
nameRE = reactRefCurrent;
optional = Mandatory;
type_ = Null type_;
};
] )
let isTypeReactRef ~fields =
match fields with
| [{mutable_ = Mutable; nameJS; nameRE; optional = Mandatory}] ->
nameJS == reactRefCurrent && nameJS == nameRE
| _ -> false
let isTypeFunctionComponent ~fields type_ =
type_ |> isTypeReactElement && not (isTypeReactRef ~fields)
let rec renderType ~(config : Config.t) ?(indent = None) ~typeNameIsInterface
~inFunType type0 =
match type0 with
| Array (t, arrayKind) ->
let typeIsSimple =
match t with
| Ident _ | TypeVar _ -> true
| _ -> false
in
if typeIsSimple && arrayKind = Mutable then
(t |> renderType ~config ~indent ~typeNameIsInterface ~inFunType) ^ "[]"
else
let arrayName =
match arrayKind = Mutable with
| true -> "Array"
| false -> "ReadonlyArray"
in
arrayName ^ "<"
^ (t |> renderType ~config ~indent ~typeNameIsInterface ~inFunType)
^ ">"
| Function
{argTypes = [{aType = Object (closedFlag, fields)}]; retType; typeVars}
when retType |> isTypeFunctionComponent ~fields ->
let fields =
fields
|> List.map (fun field ->
{
field with
type_ =
field.type_
|> TypeVars.substitute ~f:(fun s ->
if typeVars |> List.mem s then Some typeAny else None);
})
in
let componentType =
typeReactComponent ~propsType:(Object (closedFlag, fields))
in
componentType |> renderType ~config ~indent ~typeNameIsInterface ~inFunType
| Function {argTypes; retType; typeVars} ->
renderFunType ~config ~indent ~inFunType ~typeNameIsInterface ~typeVars
argTypes retType
| GroupOfLabeledArgs fields | Object (_, fields) | Record fields ->
let indent1 = fields |> Indent.heuristicFields ~indent in
fields
|> renderFields ~config ~indent:indent1 ~inFunType ~typeNameIsInterface
| Ident {builtin; name; typeArgs} ->
let name = name |> sanitizeTypeName in
(match
(not builtin) && config.exportInterfaces && name |> typeNameIsInterface
with
| true -> name |> interfaceName ~config
| false -> name)
^ EmitText.genericsString
~typeVars:
(typeArgs
|> List.map
(renderType ~config ~indent ~typeNameIsInterface ~inFunType))
| Null type_ ->
"(null | "
^ (type_ |> renderType ~config ~indent ~typeNameIsInterface ~inFunType)
^ ")"
| Nullable type_ | Option type_ ->
let useParens x =
match type_ with
| Function _ | Variant _ -> EmitText.parens [x]
| _ -> x
in
"(null | undefined | "
^ useParens
(type_ |> renderType ~config ~indent ~typeNameIsInterface ~inFunType)
^ ")"
| Promise type_ ->
"Promise" ^ "<"
^ (type_ |> renderType ~config ~indent ~typeNameIsInterface ~inFunType)
^ ">"
| Tuple innerTypes ->
"["
^ (innerTypes
|> List.map (renderType ~config ~indent ~typeNameIsInterface ~inFunType)
|> String.concat ", ")
^ "]"
| TypeVar s -> s
| Variant {inherits; noPayloads; payloads; polymorphic; unboxed} ->
let inheritsRendered =
inherits
|> List.map (fun type_ ->
type_ |> renderType ~config ~indent ~typeNameIsInterface ~inFunType)
in
let noPayloadsRendered = noPayloads |> List.map labelJSToString in
let field ~name value =
{
mutable_ = Mutable;
nameJS = name;
nameRE = name;
optional = Mandatory;
type_ = TypeVar value;
}
in
let fields fields =
fields |> renderFields ~config ~indent ~inFunType ~typeNameIsInterface
in
let payloadsRendered =
payloads
|> List.map (fun {case; t = type_} ->
let typeRendered =
type_
|> renderType ~config ~indent ~typeNameIsInterface ~inFunType
in
match unboxed with
| true -> typeRendered
| false ->
[
case |> labelJSToString
|> field ~name:(Runtime.jsVariantTag ~polymorphic);
typeRendered
|> field ~name:(Runtime.jsVariantValue ~polymorphic);
]
|> fields)
in
let rendered = inheritsRendered @ noPayloadsRendered @ payloadsRendered in
let indent1 = rendered |> Indent.heuristicVariants ~indent in
(match indent1 = None with
| true -> ""
| false -> Indent.break ~indent:indent1 ^ " ")
^ (rendered
|> String.concat
((match indent1 = None with
| true -> " "
| false -> Indent.break ~indent:indent1)
^ "| "))
and renderField ~config ~indent ~typeNameIsInterface ~inFunType
{mutable_; nameJS = lbl; optional; type_} =
let optMarker =
match optional == Optional with
| true -> "?"
| false -> ""
in
let mutMarker =
match mutable_ = Immutable with
| true -> "readonly "
| false -> ""
in
let lbl =
match isJSSafePropertyName lbl with
| true -> lbl
| false -> EmitText.quotes lbl
in
Indent.break ~indent ^ mutMarker ^ lbl ^ optMarker ^ ": "
^ (type_ |> renderType ~config ~indent ~typeNameIsInterface ~inFunType)
and renderFields ~config ~indent ~inFunType ~typeNameIsInterface fields =
let indent1 = indent |> Indent.more in
let space =
match indent = None && fields <> [] with
| true -> " "
| false -> ""
in
let renderedFields =
fields
|> List.map
(renderField ~config ~indent:indent1 ~typeNameIsInterface ~inFunType)
in
("{" ^ space)
^ String.concat "; " renderedFields
^ Indent.break ~indent ^ space ^ "}"
and renderFunType ~config ~indent ~inFunType ~typeNameIsInterface ~typeVars
argTypes retType =
(match inFunType with
| true -> "("
| false -> "")
^ EmitText.genericsString ~typeVars
^ "("
^ String.concat ", "
(List.mapi
(fun i {aName; aType} ->
let parameterName =
(match aName = "" with
| true -> "_" ^ string_of_int (i + 1)
| false -> aName)
^ ":"
in
parameterName
^ (aType
|> renderType ~config ~indent ~typeNameIsInterface ~inFunType:true
))
argTypes)
^ ") => "
^ (retType |> renderType ~config ~indent ~typeNameIsInterface ~inFunType)
^
match inFunType with
| true -> ")"
| false -> ""
let typeToString ~config ~typeNameIsInterface type_ =
type_ |> renderType ~config ~typeNameIsInterface ~inFunType:false
let ofType ~config ?(typeNameIsInterface = fun _ -> false) ~type_ s =
s ^ ": " ^ (type_ |> typeToString ~config ~typeNameIsInterface)
let emitExportConst ~early ?(comment = "") ~config ?(docString = "") ~emitters
~name ~type_ ~typeNameIsInterface line =
(match comment = "" with
| true -> comment
| false -> "// " ^ comment ^ "\n")
^ docString ^ "export const "
^ (name |> ofType ~config ~typeNameIsInterface ~type_)
^ " = " ^ line
|> (match early with
| true -> Emitters.exportEarly
| false -> Emitters.export)
~emitters
let emitExportDefault ~emitters name =
"export default " ^ name ^ ";" |> Emitters.export ~emitters
let emitExportType ~(config : Config.t) ~emitters ~nameAs ~opaque ~type_
~typeNameIsInterface ~typeVars resolvedTypeName =
let typeParamsString = EmitText.genericsString ~typeVars in
let isInterface = resolvedTypeName |> typeNameIsInterface in
let resolvedTypeName =
match config.exportInterfaces && isInterface with
| true -> resolvedTypeName |> interfaceName ~config
| false -> resolvedTypeName
in
let exportNameAs =
match nameAs with
| None -> ""
| Some s ->
"\nexport type " ^ s ^ typeParamsString ^ " = " ^ resolvedTypeName
^ typeParamsString ^ ";"
in
if opaque then
(* Represent an opaque type as an absract class with a field called 'opaque'.
Any type parameters must occur in the type of opaque, so that different
instantiations are considered different types. *)
let typeOfOpaqueField =
match typeVars = [] with
| true -> "any"
| false -> typeVars |> String.concat " | "
in
"// tslint:disable-next-line:max-classes-per-file \n"
^ (match String.capitalize_ascii resolvedTypeName <> resolvedTypeName with
| true -> "// tslint:disable-next-line:class-name\n"
| false -> "")
^ "export abstract class " ^ resolvedTypeName ^ typeParamsString
^ " { protected opaque!: " ^ typeOfOpaqueField
^ " }; /* simulate opaque types */" ^ exportNameAs
|> Emitters.export ~emitters
else
(if isInterface && config.exportInterfaces then
"export interface " ^ resolvedTypeName ^ typeParamsString ^ " "
else
"// tslint:disable-next-line:interface-over-type-literal\n"
^ "export type " ^ resolvedTypeName ^ typeParamsString ^ " = ")
^ (match type_ with
| _ -> type_ |> typeToString ~config ~typeNameIsInterface)
^ ";" ^ exportNameAs
|> Emitters.export ~emitters
let emitImportValueAsEarly ~emitters ~name ~nameAs importPath =
"import "
^ (match nameAs with
| Some nameAs -> "{" ^ name ^ " as " ^ nameAs ^ "}"
| None -> name)
^ " from " ^ "'"
^ (importPath |> ImportPath.emit)
^ "';"
|> Emitters.requireEarly ~emitters
let emitRequire ~importedValueOrComponent ~early ~emitters ~(config : Config.t)
~moduleName importPath =
let commentBeforeRequire =
match importedValueOrComponent with
| true -> "// tslint:disable-next-line:no-var-requires\n"
| false -> "// @ts-ignore: Implicit any on import\n"
in
match config.module_ with
| ES6 when not importedValueOrComponent ->
let moduleNameString = ModuleName.toString moduleName in
(let es6ImportModule = moduleNameString ^ "__Es6Import" in
commentBeforeRequire ^ "import * as " ^ es6ImportModule ^ " from '"
^ (importPath |> ImportPath.emit)
^ "';\n" ^ "const " ^ moduleNameString ^ ": any = " ^ es6ImportModule ^ ";")
|> (match early with
| true -> Emitters.requireEarly
| false -> Emitters.require)
~emitters
| _ ->
commentBeforeRequire ^ "const "
^ ModuleName.toString moduleName
^ " = require('"
^ (importPath |> ImportPath.emit)
^ "');"
|> (match early with
| true -> Emitters.requireEarly
| false -> Emitters.require)
~emitters
let require ~early =
match early with
| true -> Emitters.requireEarly
| false -> Emitters.require
let emitImportReact ~emitters =
"import * as React from 'react';" |> require ~early:true ~emitters
let emitImportTypeAs ~emitters ~config ~typeName ~asTypeName
~typeNameIsInterface ~importPath =
let typeName = sanitizeTypeName typeName in
let asTypeName =
match asTypeName with
| None -> asTypeName
| Some s -> Some (sanitizeTypeName s)
in
let typeName, asTypeName =
match asTypeName with
| Some asName -> (
match asName |> typeNameIsInterface with
| true ->
( typeName |> interfaceName ~config,
Some (asName |> interfaceName ~config) )
| false -> (typeName, asTypeName))
| None -> (typeName, asTypeName)
in
let importPathString = importPath |> ImportPath.emit in
let importPrefix = "import type" in
importPrefix ^ " " ^ "{" ^ typeName
^ (match asTypeName with
| Some asT -> " as " ^ asT
| None -> "")
^ "} from '" ^ importPathString ^ "';"
|> Emitters.import ~emitters
let ofTypeAny ~config s = s |> ofType ~config ~type_:typeAny
let emitTypeCast ~config ~type_ ~typeNameIsInterface s =
s ^ " as " ^ (type_ |> typeToString ~config ~typeNameIsInterface)