flow-api-translator 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,2507 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @flow strict-local
8
+ * @format
9
+ */
10
+
11
+ 'use strict';
12
+
13
+ import type {ObjectWithLoc} from 'hermes-estree';
14
+ import * as FlowESTree from 'hermes-estree';
15
+ import type {ScopeManager} from 'hermes-eslint';
16
+ import {cloneJSDocCommentsToNewNode as cloneJSDocCommentsToNewNodeOriginal} from 'hermes-transform';
17
+ import * as TSESTree from './utils/ts-estree-ast-types';
18
+ import {
19
+ translationError as translationErrorBase,
20
+ unexpectedTranslationError as unexpectedTranslationErrorBase,
21
+ } from './utils/ErrorUtils';
22
+ import {removeAtFlowFromDocblock} from './utils/DocblockUtils';
23
+
24
+ const cloneJSDocCommentsToNewNode =
25
+ // $FlowExpectedError[incompatible-cast] - trust me this re-type is 100% safe
26
+ (cloneJSDocCommentsToNewNodeOriginal: (mixed, mixed) => void);
27
+
28
+ const VALID_REACT_IMPORTS = new Set<string>(['React', 'react']);
29
+
30
+ export function flowDefToTSDef(
31
+ originalCode: string,
32
+ ast: FlowESTree.Program,
33
+ scopeManager: ScopeManager,
34
+ ): TSESTree.Program {
35
+ const tsBody: Array<TSESTree.ProgramStatement> = [];
36
+ const tsProgram: TSESTree.Program = {
37
+ type: 'Program',
38
+ body: tsBody,
39
+ sourceType: ast.sourceType,
40
+ docblock:
41
+ ast.docblock == null ? null : removeAtFlowFromDocblock(ast.docblock),
42
+ };
43
+
44
+ const transform = getTransforms(originalCode, scopeManager);
45
+
46
+ for (const node of ast.body) {
47
+ if (node.type in transform) {
48
+ const result: $FlowFixMe | Array<$FlowFixMe> = transform[
49
+ // $FlowExpectedError[prop-missing]
50
+ node.type
51
+ ](
52
+ // $FlowExpectedError[incompatible-type]
53
+ // $FlowExpectedError[prop-missing]
54
+ node,
55
+ );
56
+ tsBody.push(...(Array.isArray(result) ? result : [result]));
57
+ } else {
58
+ throw unexpectedTranslationErrorBase(
59
+ node,
60
+ `Unexpected node type ${node.type}`,
61
+ {code: originalCode},
62
+ );
63
+ }
64
+ }
65
+
66
+ return tsProgram;
67
+ }
68
+
69
+ const getTransforms = (code: string, scopeManager: ScopeManager) => {
70
+ function translationError(node: ObjectWithLoc, message: string) {
71
+ return translationErrorBase(node, message, {code});
72
+ }
73
+ function unexpectedTranslationError(node: ObjectWithLoc, message: string) {
74
+ return unexpectedTranslationErrorBase(node, message, {code});
75
+ }
76
+ function unsupportedTranslationError(node: ObjectWithLoc, thing: string) {
77
+ return translationError(
78
+ node,
79
+ `Unsupported feature: Translating "${thing}" is currently not supported.`,
80
+ );
81
+ }
82
+
83
+ const topScope = (() => {
84
+ const globalScope = scopeManager.globalScope;
85
+ if (
86
+ globalScope.childScopes.length > 0 &&
87
+ globalScope.childScopes[0].type === 'module'
88
+ ) {
89
+ return globalScope.childScopes[0];
90
+ }
91
+ return globalScope;
92
+ })();
93
+
94
+ function isReactImport(id: FlowESTree.Identifier): boolean {
95
+ let currentScope = scopeManager.acquire(id);
96
+
97
+ const variableDef = (() => {
98
+ while (currentScope != null) {
99
+ for (const variable of currentScope.variables) {
100
+ if (variable.defs.length && variable.name === id.name) {
101
+ return variable;
102
+ }
103
+ }
104
+ currentScope = currentScope.upper;
105
+ }
106
+ })();
107
+
108
+ // No variable found, it must be global. Using the `React` variable is enough in this case.
109
+ if (variableDef == null) {
110
+ return VALID_REACT_IMPORTS.has(id.name);
111
+ }
112
+
113
+ const def = variableDef.defs[0];
114
+ // Detect:
115
+ switch (def.type) {
116
+ // import React from 'react';
117
+ // import * as React from 'react';
118
+ case 'ImportBinding': {
119
+ if (
120
+ def.node.type === 'ImportDefaultSpecifier' ||
121
+ def.node.type === 'ImportNamespaceSpecifier'
122
+ ) {
123
+ return VALID_REACT_IMPORTS.has(def.parent.source.value);
124
+ }
125
+ return false;
126
+ }
127
+
128
+ // Globals
129
+ case 'ImplicitGlobalVariable': {
130
+ return VALID_REACT_IMPORTS.has(id.name);
131
+ }
132
+
133
+ // TODO Handle:
134
+ // const React = require('react');
135
+ // const Something = React;
136
+ }
137
+
138
+ return false;
139
+ }
140
+
141
+ const transform = {
142
+ AnyTypeAnnotation(
143
+ _node: FlowESTree.AnyTypeAnnotation,
144
+ ): TSESTree.TSAnyKeyword {
145
+ return {
146
+ type: 'TSAnyKeyword',
147
+ };
148
+ },
149
+ ArrayTypeAnnotation(
150
+ node: FlowESTree.ArrayTypeAnnotation,
151
+ ): TSESTree.TSArrayType {
152
+ return {
153
+ type: 'TSArrayType',
154
+ elementType: transform.TypeAnnotationType(node.elementType),
155
+ };
156
+ },
157
+ BigIntLiteral(node: FlowESTree.BigIntLiteral): TSESTree.BigIntLiteral {
158
+ return {
159
+ type: 'Literal',
160
+ bigint: node.bigint,
161
+ raw: node.raw,
162
+ value: node.value,
163
+ };
164
+ },
165
+ BigIntLiteralTypeAnnotation(
166
+ node: FlowESTree.BigIntLiteralTypeAnnotation,
167
+ ): TSESTree.TSLiteralType {
168
+ // technically hermes doesn't support this yet
169
+ // but future proofing amirite
170
+ const bigint =
171
+ // $FlowExpectedError[prop-missing]
172
+ node.bigint ??
173
+ node.raw
174
+ // estree spec is to not have a trailing `n` on this property
175
+ // https://github.com/estree/estree/blob/db962bb417a97effcfe9892f87fbb93c81a68584/es2020.md#bigintliteral
176
+ .replace(/n$/, '')
177
+ // `BigInt` doesn't accept numeric separator and `bigint` property should not include numeric separator
178
+ .replace(/_/, '');
179
+ return {
180
+ type: 'TSLiteralType',
181
+ literal: ({
182
+ type: 'Literal',
183
+ value: node.value,
184
+ raw: node.raw,
185
+ bigint,
186
+ }: TSESTree.BigIntLiteral),
187
+ };
188
+ },
189
+ BigIntTypeAnnotation(
190
+ _node: FlowESTree.BigIntTypeAnnotation,
191
+ ): TSESTree.TSBigIntKeyword {
192
+ return {
193
+ type: 'TSBigIntKeyword',
194
+ };
195
+ },
196
+ BooleanLiteral(node: FlowESTree.BooleanLiteral): TSESTree.BooleanLiteral {
197
+ return {
198
+ type: 'Literal',
199
+ raw: node.raw,
200
+ value: node.value,
201
+ };
202
+ },
203
+ BooleanLiteralTypeAnnotation(
204
+ node: FlowESTree.BooleanLiteralTypeAnnotation,
205
+ ): TSESTree.TSLiteralType {
206
+ return {
207
+ type: 'TSLiteralType',
208
+ literal: ({
209
+ type: 'Literal',
210
+ value: node.value,
211
+ raw: node.raw,
212
+ }: TSESTree.BooleanLiteral),
213
+ };
214
+ },
215
+ BooleanTypeAnnotation(
216
+ _node: FlowESTree.BooleanTypeAnnotation,
217
+ ): TSESTree.TSBooleanKeyword {
218
+ return {
219
+ type: 'TSBooleanKeyword',
220
+ };
221
+ },
222
+ ClassImplements(
223
+ node: FlowESTree.ClassImplements,
224
+ ): TSESTree.TSClassImplements {
225
+ return {
226
+ type: 'TSClassImplements',
227
+ expression: transform.Identifier(node.id, false),
228
+ typeParameters:
229
+ node.typeParameters == null
230
+ ? undefined
231
+ : transform.TypeParameterInstantiation(node.typeParameters),
232
+ };
233
+ },
234
+ DeclareClass(
235
+ node: FlowESTree.DeclareClass,
236
+ ): TSESTree.ClassDeclarationWithName {
237
+ const classMembers: Array<TSESTree.ClassElement> = [];
238
+ const transformedBody = transform.ObjectTypeAnnotation(node.body);
239
+ if (transformedBody.type !== 'TSTypeLiteral') {
240
+ throw translationError(
241
+ node.body,
242
+ 'Spreads in declare class are not allowed',
243
+ );
244
+ }
245
+ for (const member of transformedBody.members) {
246
+ // TS uses the real ClassDeclaration AST so we need to
247
+ // make the signatures real methods/properties
248
+ switch (member.type) {
249
+ case 'TSIndexSignature': {
250
+ classMembers.push(member);
251
+ break;
252
+ }
253
+
254
+ case 'TSMethodSignature': {
255
+ // flow just creates a method signature like any other for a constructor
256
+ // but the proper AST has `kind = 'constructor'`
257
+ const isConstructor = (() => {
258
+ if (member.computed === true) {
259
+ return false;
260
+ }
261
+
262
+ return (
263
+ (member.key.type === 'Identifier' &&
264
+ member.key.name === 'constructor') ||
265
+ (member.key.type === 'Literal' &&
266
+ member.key.value === 'constructor')
267
+ );
268
+ })();
269
+ if (isConstructor) {
270
+ const newNode: TSESTree.MethodDefinitionAmbiguous = {
271
+ type: 'MethodDefinition',
272
+ accessibility: undefined,
273
+ computed: false,
274
+ key: {
275
+ type: 'Identifier',
276
+ name: 'constructor',
277
+ },
278
+ kind: 'constructor',
279
+ optional: false,
280
+ override: false,
281
+ static: false,
282
+ value: {
283
+ type: 'TSEmptyBodyFunctionExpression',
284
+ async: false,
285
+ body: null,
286
+ declare: false,
287
+ expression: false,
288
+ generator: false,
289
+ id: null,
290
+ params: member.params,
291
+ // constructors explicitly have no return type
292
+ returnType: undefined,
293
+ typeParameters: member.typeParameters,
294
+ },
295
+ };
296
+ cloneJSDocCommentsToNewNode(member, newNode);
297
+ classMembers.push(newNode);
298
+ } else {
299
+ const newNode: TSESTree.MethodDefinitionAmbiguous = {
300
+ type: 'MethodDefinition',
301
+ accessibility: member.accessibility,
302
+ computed: member.computed ?? false,
303
+ key: member.key,
304
+ kind: member.kind,
305
+ optional: member.optional,
306
+ override: false,
307
+ static: member.static ?? false,
308
+ value: {
309
+ type: 'TSEmptyBodyFunctionExpression',
310
+ async: false,
311
+ body: null,
312
+ declare: false,
313
+ expression: false,
314
+ generator: false,
315
+ id: null,
316
+ params: member.params,
317
+ returnType: member.returnType,
318
+ typeParameters: member.typeParameters,
319
+ },
320
+ };
321
+ cloneJSDocCommentsToNewNode(member, newNode);
322
+ classMembers.push(newNode);
323
+ }
324
+ break;
325
+ }
326
+
327
+ case 'TSPropertySignature': {
328
+ const newNode: TSESTree.PropertyDefinitionAmbiguous = {
329
+ type: 'PropertyDefinition',
330
+ accessibility: member.accessibility,
331
+ computed: member.computed ?? false,
332
+ declare: false,
333
+ key: member.key,
334
+ optional: member.optional,
335
+ readonly: member.readonly,
336
+ static: member.static ?? false,
337
+ typeAnnotation: member.typeAnnotation,
338
+ value: null,
339
+ };
340
+ cloneJSDocCommentsToNewNode(member, newNode);
341
+ classMembers.push(newNode);
342
+ break;
343
+ }
344
+
345
+ case 'TSCallSignatureDeclaration': {
346
+ /*
347
+ TODO - callProperties
348
+ It's not valid to directly declare a call property on a class in TS
349
+ You can do it, but it's a big complication in the AST:
350
+ ```ts
351
+ declare Class {
352
+ // ...
353
+ }
354
+ interface ClassConstructor {
355
+ new (): Class;
356
+ // call sigs
357
+ (): Type;
358
+ }
359
+ ```
360
+ Let's put a pin in it for now and deal with it later if the need arises.
361
+ */
362
+ throw unsupportedTranslationError(
363
+ node.body.callProperties[0] ?? node.body,
364
+ 'call signatures on classes',
365
+ );
366
+ }
367
+
368
+ default:
369
+ throw unexpectedTranslationError(
370
+ node.body,
371
+ `Unexpected member type ${member.type}`,
372
+ );
373
+ }
374
+ }
375
+
376
+ const superClass = node.extends.length > 0 ? node.extends[0] : undefined;
377
+
378
+ return {
379
+ type: 'ClassDeclaration',
380
+ body: {
381
+ type: 'ClassBody',
382
+ body: classMembers,
383
+ },
384
+ declare: true,
385
+ id: transform.Identifier(node.id, false),
386
+ implements:
387
+ node.implements == null
388
+ ? undefined
389
+ : node.implements.map(transform.ClassImplements),
390
+ superClass:
391
+ superClass == null
392
+ ? null
393
+ : transform.Identifier(superClass.id, false),
394
+ superTypeParameters:
395
+ superClass?.typeParameters == null
396
+ ? undefined
397
+ : transform.TypeParameterInstantiation(superClass.typeParameters),
398
+ typeParameters:
399
+ node.typeParameters == null
400
+ ? undefined
401
+ : transform.TypeParameterDeclaration(node.typeParameters),
402
+ // TODO - mixins??
403
+ };
404
+ },
405
+ DeclareExportDeclaration(
406
+ node: FlowESTree.DeclareExportDeclaration,
407
+ ):
408
+ | TSESTree.ExportNamedDeclaration
409
+ | TSESTree.ExportDefaultDeclaration
410
+ | [
411
+ (
412
+ | TSESTree.VariableDeclaration
413
+ | TSESTree.ClassDeclaration
414
+ | TSESTree.TSDeclareFunction
415
+ ),
416
+ TSESTree.ExportDefaultDeclaration,
417
+ ] {
418
+ if (node.default === true) {
419
+ const declaration = node.declaration;
420
+
421
+ switch (declaration.type) {
422
+ // TS doesn't support direct default export for declare'd classes
423
+ case 'DeclareClass': {
424
+ const classDecl = transform.DeclareClass(declaration);
425
+ const name = declaration.id.name;
426
+ return [
427
+ classDecl,
428
+ {
429
+ type: 'ExportDefaultDeclaration',
430
+ declaration: {
431
+ type: 'Identifier',
432
+ name,
433
+ },
434
+ exportKind: 'value',
435
+ },
436
+ ];
437
+ }
438
+
439
+ // TS doesn't support direct default export for declare'd functions
440
+ case 'DeclareFunction': {
441
+ const functionDecl = transform.DeclareFunction(declaration);
442
+ const name = declaration.id.name;
443
+ return [
444
+ functionDecl,
445
+ {
446
+ type: 'ExportDefaultDeclaration',
447
+ declaration: {
448
+ type: 'Identifier',
449
+ name,
450
+ },
451
+ exportKind: 'value',
452
+ },
453
+ ];
454
+ }
455
+
456
+ // Flow's declare export default Identifier is ambiguous.
457
+ // the Identifier might reference a type, or it might reference a value
458
+ // - If it's a value, then that's all good, TS supports that.
459
+ // - If it's a type, that's a problem - TS only allows value variables to be exported
460
+ // so we need to create an intermediate variable to hold the type.
461
+
462
+ case 'GenericTypeAnnotation': {
463
+ const referencedId = declaration.id;
464
+
465
+ // QualifiedTypeIdentifiers are types so cannot be handled without the intermediate variable so
466
+ // only Identifiers can be handled here.
467
+ if (referencedId.type === 'Identifier') {
468
+ const exportedVar = topScope.set.get(referencedId.name);
469
+ if (exportedVar == null || exportedVar.defs.length !== 1) {
470
+ throw unexpectedTranslationError(
471
+ referencedId,
472
+ `Unable to find exported variable ${referencedId.name}`,
473
+ );
474
+ }
475
+
476
+ const def = exportedVar.defs[0];
477
+ switch (def.type) {
478
+ case 'ImportBinding': {
479
+ // `import type { Wut } from 'mod'; declare export default Wut;`
480
+ // `import { type Wut } from 'mod'; declare export default Wut;`
481
+ // these cases should be wrapped in a variable because they're exporting a type, not a value
482
+ const specifier = def.node;
483
+ if (
484
+ specifier.importKind === 'type' ||
485
+ specifier.parent.importKind === 'type'
486
+ ) {
487
+ // fallthrough to the "default" handling
488
+ break;
489
+ }
490
+
491
+ // intentional fallthrough to the "value" handling
492
+ }
493
+ case 'ClassName':
494
+ case 'Enum':
495
+ case 'FunctionName':
496
+ case 'ImplicitGlobalVariable':
497
+ case 'Variable':
498
+ // there's already a variable defined to hold the type
499
+ return {
500
+ type: 'ExportDefaultDeclaration',
501
+ declaration: {
502
+ type: 'Identifier',
503
+ name: referencedId.name,
504
+ },
505
+ exportKind: 'value',
506
+ };
507
+
508
+ case 'CatchClause':
509
+ case 'Parameter':
510
+ case 'TypeParameter':
511
+ throw translationError(
512
+ def.node,
513
+ `Unexpected variable def type: ${def.type}`,
514
+ );
515
+
516
+ case 'Type':
517
+ // fallthrough to the "default" handling
518
+ break;
519
+ }
520
+ }
521
+
522
+ // intentional fallthrough to the "default" handling
523
+ }
524
+
525
+ default: {
526
+ /*
527
+ flow allows syntax like
528
+ ```
529
+ declare export default TypeName;
530
+ ```
531
+ but TS does not, so we have to declare a temporary variable to
532
+ reference in the export declaration:
533
+ ```
534
+ declare const $$EXPORT_DEFAULT_DECLARATION$$: TypeName;
535
+ export default $$EXPORT_DEFAULT_DECLARATION$$;
536
+ ```
537
+ */
538
+ const SPECIFIER = '$$EXPORT_DEFAULT_DECLARATION$$';
539
+ return [
540
+ {
541
+ type: 'VariableDeclaration',
542
+ declarations: [
543
+ {
544
+ type: 'VariableDeclarator',
545
+ id: {
546
+ type: 'Identifier',
547
+ name: SPECIFIER,
548
+ typeAnnotation: {
549
+ type: 'TSTypeAnnotation',
550
+ typeAnnotation:
551
+ transform.TypeAnnotationType(declaration),
552
+ },
553
+ },
554
+ init: null,
555
+ },
556
+ ],
557
+ declare: true,
558
+ kind: 'const',
559
+ },
560
+ {
561
+ type: 'ExportDefaultDeclaration',
562
+ declaration: {
563
+ type: 'Identifier',
564
+ name: SPECIFIER,
565
+ },
566
+ exportKind: 'value',
567
+ },
568
+ ];
569
+ }
570
+ }
571
+ } else {
572
+ // eslint-disable-next-line eqeqeq
573
+ if (node.source === null) {
574
+ // eslint-disable-next-line eqeqeq
575
+ if (node.declaration === null) {
576
+ return ({
577
+ type: 'ExportNamedDeclaration',
578
+ // flow does not currently support assertions
579
+ assertions: [],
580
+ declaration: null,
581
+ // flow does not support declared type exports with specifiers
582
+ exportKind: 'value',
583
+ source: null,
584
+ specifiers: node.specifiers.map(transform.ExportSpecifier),
585
+ }: TSESTree.ExportNamedDeclarationWithoutSourceWithMultiple);
586
+ }
587
+
588
+ const {declaration, exportKind} = (() => {
589
+ switch (node.declaration.type) {
590
+ case 'DeclareClass':
591
+ return {
592
+ declaration: transform.DeclareClass(node.declaration),
593
+ exportKind: 'value',
594
+ };
595
+ case 'DeclareFunction':
596
+ return {
597
+ declaration: transform.DeclareFunction(node.declaration),
598
+ exportKind: 'value',
599
+ };
600
+ case 'DeclareInterface':
601
+ return {
602
+ declaration: transform.DeclareInterface(node.declaration),
603
+ exportKind: 'type',
604
+ };
605
+ case 'DeclareOpaqueType':
606
+ return {
607
+ declaration: transform.DeclareOpaqueType(node.declaration),
608
+ exportKind: 'type',
609
+ };
610
+ case 'DeclareVariable':
611
+ return {
612
+ declaration: transform.DeclareVariable(node.declaration),
613
+ exportKind: 'value',
614
+ };
615
+ }
616
+ })();
617
+
618
+ return ({
619
+ type: 'ExportNamedDeclaration',
620
+ // flow does not currently support assertions
621
+ assertions: [],
622
+ declaration,
623
+ exportKind,
624
+ source: null,
625
+ specifiers: [],
626
+ }: TSESTree.ExportNamedDeclarationWithoutSourceWithSingle);
627
+ } else {
628
+ return ({
629
+ type: 'ExportNamedDeclaration',
630
+ // flow does not currently support assertions
631
+ assertions: [],
632
+ declaration: null,
633
+ // flow does not support declared type exports with a source
634
+ exportKind: 'value',
635
+ source: transform.StringLiteral(node.source),
636
+ specifiers: node.specifiers.map(transform.ExportSpecifier),
637
+ }: TSESTree.ExportNamedDeclarationWithSource);
638
+ }
639
+ }
640
+ },
641
+ DeclareFunction(
642
+ node: FlowESTree.DeclareFunction,
643
+ ): TSESTree.TSDeclareFunction {
644
+ // the function information is stored as an annotation on the ID...
645
+ const id = transform.Identifier(node.id, false);
646
+ const functionInfo = transform.FunctionTypeAnnotation(
647
+ node.id.typeAnnotation.typeAnnotation,
648
+ );
649
+
650
+ return {
651
+ type: 'TSDeclareFunction',
652
+ async: false,
653
+ body: undefined,
654
+ declare: true,
655
+ expression: false,
656
+ generator: false,
657
+ id: {
658
+ type: 'Identifier',
659
+ name: id.name,
660
+ },
661
+ params: functionInfo.params,
662
+ returnType: functionInfo.returnType,
663
+ typeParameters: functionInfo.typeParameters,
664
+ };
665
+ },
666
+ DeclareInterface(
667
+ node: FlowESTree.DeclareInterface | FlowESTree.InterfaceDeclaration,
668
+ ): TSESTree.TSInterfaceDeclaration {
669
+ const transformedBody = transform.ObjectTypeAnnotation(node.body);
670
+ if (transformedBody.type !== 'TSTypeLiteral') {
671
+ throw translationError(
672
+ node.body,
673
+ 'Spreads in interfaces are not allowed',
674
+ );
675
+ }
676
+
677
+ return {
678
+ type: 'TSInterfaceDeclaration',
679
+ body: {
680
+ type: 'TSInterfaceBody',
681
+ body: transformedBody.members,
682
+ },
683
+ declare: node.type !== 'InterfaceDeclaration',
684
+ extends: node.extends.map(transform.InterfaceExtends),
685
+ id: transform.Identifier(node.id, false),
686
+ typeParameters:
687
+ node.typeParameters == null
688
+ ? undefined
689
+ : transform.TypeParameterDeclaration(node.typeParameters),
690
+ };
691
+ },
692
+ DeclareTypeAlias(
693
+ node: FlowESTree.DeclareTypeAlias | FlowESTree.TypeAlias,
694
+ ): TSESTree.TSTypeAliasDeclaration {
695
+ return {
696
+ type: 'TSTypeAliasDeclaration',
697
+ declare: node.type === 'DeclareTypeAlias',
698
+ id: transform.Identifier(node.id, false),
699
+ typeAnnotation: transform.TypeAnnotationType(node.right),
700
+ typeParameters:
701
+ node.typeParameters == null
702
+ ? undefined
703
+ : transform.TypeParameterDeclaration(node.typeParameters),
704
+ };
705
+ },
706
+ DeclareOpaqueType(
707
+ node: FlowESTree.DeclareOpaqueType | FlowESTree.OpaqueType,
708
+ ): TSESTree.TSTypeAliasDeclaration {
709
+ // TS doesn't currently have nominal types - https://github.com/Microsoft/Typescript/issues/202
710
+ // TODO - we could simulate this in a variety of ways
711
+ // Examples - https://basarat.gitbook.io/typescript/main-1/nominaltyping
712
+
713
+ return {
714
+ type: 'TSTypeAliasDeclaration',
715
+ declare: true,
716
+ id: transform.Identifier(node.id, false),
717
+ typeAnnotation:
718
+ node.supertype == null
719
+ ? {
720
+ type: 'TSUnknownKeyword',
721
+ }
722
+ : transform.TypeAnnotationType(node.supertype),
723
+ typeParameters:
724
+ node.typeParameters == null
725
+ ? undefined
726
+ : transform.TypeParameterDeclaration(node.typeParameters),
727
+ };
728
+ },
729
+ DeclareVariable(
730
+ node: FlowESTree.DeclareVariable,
731
+ ): TSESTree.VariableDeclaration {
732
+ return {
733
+ type: 'VariableDeclaration',
734
+ declare: true,
735
+ declarations: [
736
+ {
737
+ type: 'VariableDeclarator',
738
+ declare: true,
739
+ id: transform.Identifier(node.id, true),
740
+ init: null,
741
+ },
742
+ ],
743
+ kind: 'var',
744
+ };
745
+ },
746
+ EmptyTypeAnnotation(
747
+ node: FlowESTree.EmptyTypeAnnotation,
748
+ ): TSESTree.TypeNode {
749
+ // Flow's `empty` type doesn't map well to any types in TS.
750
+ // The closest is `never`, but `never` has a number of different semantics
751
+ // In reality no human code should ever directly use the `empty` type in flow
752
+ // So let's put a pin in it for now
753
+ throw unsupportedTranslationError(node, 'empty type');
754
+ },
755
+ EnumDeclaration(
756
+ node: FlowESTree.EnumDeclaration,
757
+ ): [TSESTree.TSEnumDeclaration, TSESTree.TSModuleDeclaration] {
758
+ const body = node.body;
759
+ if (body.type === 'EnumSymbolBody') {
760
+ /*
761
+ There's unfortunately no way for us to support this in a clean way.
762
+ We can get really close using this code:
763
+ ```
764
+ declare namespace SymbolEnum {
765
+ export const member1: unique symbol;
766
+ export type member1 = typeof member1;
767
+
768
+ export const member2: unique symbol;
769
+ export type member2 = typeof member2;
770
+ }
771
+ type SymbolEnum = typeof SymbolEnum[keyof typeof SymbolEnum];
772
+ ```
773
+
774
+ However as explained in https://github.com/microsoft/TypeScript/issues/43657:
775
+ "A unique symbol type is never transferred from one declaration to another through inference."
776
+ This intended behaviour in TS means that the usage of the fake-enum would look like this:
777
+ ```
778
+ const value: SymbolEnum.member1 = SymbolEnum.member1;
779
+ // ^^^^^^^^^^^^^^^^^^ required to force TS to retain the information
780
+ ```
781
+ Which is really clunky and shitty. It definitely works, but ofc it's not good.
782
+ We can go with this design if users are okay with it!
783
+
784
+ Considering how rarely used symbol enums are ATM, let's just put a pin in it for now.
785
+ */
786
+ throw unsupportedTranslationError(node, 'symbol enums');
787
+ }
788
+ if (body.type === 'EnumBooleanBody') {
789
+ /*
790
+ TODO - TS enums only allow strings or numbers as their values - not booleans.
791
+ This means we need a non-ts-enum representation of the enum.
792
+ We can support boolean enums using a construct like this:
793
+ ```ts
794
+ declare namespace BooleanEnum {
795
+ export const member1: true;
796
+ export type member1 = typeof member1;
797
+
798
+ export const member2: false;
799
+ export type member2 = typeof member1;
800
+ }
801
+ declare type BooleanEnum = boolean;
802
+ ```
803
+
804
+ But it's pretty clunky and ugly.
805
+ Considering how rarely used boolean enums are ATM, let's just put a pin in it for now.
806
+ */
807
+ throw unsupportedTranslationError(node, 'boolean enums');
808
+ }
809
+
810
+ const members: Array<TSESTree.TSEnumMemberNonComputedName> = [];
811
+ for (const member of body.members) {
812
+ switch (member.type) {
813
+ case 'EnumDefaultedMember': {
814
+ if (body.type === 'EnumNumberBody') {
815
+ // this should be impossible!
816
+ throw unexpectedTranslationError(
817
+ member,
818
+ 'Unexpected defaulted number enum member',
819
+ );
820
+ }
821
+ members.push({
822
+ type: 'TSEnumMember',
823
+ computed: false,
824
+ id: transform.Identifier(member.id, false),
825
+ initializer: ({
826
+ type: 'Literal',
827
+ raw: `"${member.id.name}"`,
828
+ value: member.id.name,
829
+ }: TSESTree.StringLiteral),
830
+ });
831
+ break;
832
+ }
833
+
834
+ case 'EnumNumberMember':
835
+ case 'EnumStringMember':
836
+ members.push({
837
+ type: 'TSEnumMember',
838
+ computed: false,
839
+ id: transform.Identifier(member.id, false),
840
+ initializer:
841
+ member.init.literalType === 'string'
842
+ ? transform.StringLiteral(member.init)
843
+ : transform.NumericLiteral(member.init),
844
+ });
845
+ }
846
+ }
847
+
848
+ const bodyRepresentationType =
849
+ body.type === 'EnumNumberBody'
850
+ ? {type: 'TSNumberKeyword'}
851
+ : {type: 'TSStringKeyword'};
852
+
853
+ const enumName = transform.Identifier(node.id, false);
854
+ return [
855
+ {
856
+ type: 'TSEnumDeclaration',
857
+ const: false,
858
+ declare: true,
859
+ id: enumName,
860
+ members,
861
+ },
862
+ // flow also exports `.cast`, `.isValid`, `.members` and `.getName` for enums
863
+ // we can use declaration merging to declare these functions on the enum:
864
+ /*
865
+ declare enum Foo {
866
+ A = 1,
867
+ B = 2,
868
+ }
869
+ declare namespace Foo {
870
+ export function cast(value: number | null | undefined): Foo;
871
+ export function isValid(value: number | null | undefined): value is Foo;
872
+ export function members(): IterableIterator<Foo>;
873
+ export function getName(value: Foo): string;
874
+ }
875
+ */
876
+ {
877
+ type: 'TSModuleDeclaration',
878
+ declare: true,
879
+ id: enumName,
880
+ body: {
881
+ type: 'TSModuleBlock',
882
+ body: [
883
+ // export function cast(value: number | null | undefined): Foo
884
+ {
885
+ type: 'ExportNamedDeclaration',
886
+ declaration: {
887
+ type: 'TSDeclareFunction',
888
+ id: {
889
+ type: 'Identifier',
890
+ name: 'cast',
891
+ },
892
+ generator: false,
893
+ expression: false,
894
+ async: false,
895
+ params: [
896
+ {
897
+ type: 'Identifier',
898
+ name: 'value',
899
+ typeAnnotation: {
900
+ type: 'TSTypeAnnotation',
901
+ typeAnnotation: {
902
+ type: 'TSUnionType',
903
+ types: [
904
+ bodyRepresentationType,
905
+ {
906
+ type: 'TSNullKeyword',
907
+ },
908
+ {
909
+ type: 'TSUndefinedKeyword',
910
+ },
911
+ ],
912
+ },
913
+ },
914
+ },
915
+ ],
916
+ returnType: {
917
+ type: 'TSTypeAnnotation',
918
+ typeAnnotation: {
919
+ type: 'TSTypeReference',
920
+ typeName: enumName,
921
+ },
922
+ },
923
+ },
924
+ specifiers: [],
925
+ source: null,
926
+ exportKind: 'value',
927
+ assertions: [],
928
+ },
929
+ // export function isValid(value: number | null | undefined): value is Foo;
930
+ {
931
+ type: 'ExportNamedDeclaration',
932
+ declaration: {
933
+ type: 'TSDeclareFunction',
934
+ id: {
935
+ type: 'Identifier',
936
+ name: 'isValid',
937
+ },
938
+ generator: false,
939
+ expression: false,
940
+ async: false,
941
+ params: [
942
+ {
943
+ type: 'Identifier',
944
+ name: 'value',
945
+ typeAnnotation: {
946
+ type: 'TSTypeAnnotation',
947
+ typeAnnotation: {
948
+ type: 'TSUnionType',
949
+ types: [
950
+ bodyRepresentationType,
951
+ {
952
+ type: 'TSNullKeyword',
953
+ },
954
+ {
955
+ type: 'TSUndefinedKeyword',
956
+ },
957
+ ],
958
+ },
959
+ },
960
+ },
961
+ ],
962
+ returnType: {
963
+ type: 'TSTypeAnnotation',
964
+ typeAnnotation: {
965
+ type: 'TSTypePredicate',
966
+ asserts: false,
967
+ parameterName: {
968
+ type: 'Identifier',
969
+ name: 'value',
970
+ },
971
+ typeAnnotation: {
972
+ type: 'TSTypeAnnotation',
973
+ typeAnnotation: {
974
+ type: 'TSTypeReference',
975
+ typeName: enumName,
976
+ },
977
+ },
978
+ },
979
+ },
980
+ },
981
+ specifiers: [],
982
+ source: null,
983
+ exportKind: 'value',
984
+ assertions: [],
985
+ },
986
+ // export function members(): IterableIterator<Foo>;
987
+ {
988
+ type: 'ExportNamedDeclaration',
989
+ declaration: {
990
+ type: 'TSDeclareFunction',
991
+ id: {
992
+ type: 'Identifier',
993
+ name: 'members',
994
+ },
995
+ generator: false,
996
+ expression: false,
997
+ async: false,
998
+ params: [],
999
+ returnType: {
1000
+ type: 'TSTypeAnnotation',
1001
+ typeAnnotation: {
1002
+ type: 'TSTypeReference',
1003
+ typeName: {
1004
+ type: 'Identifier',
1005
+ name: 'IterableIterator',
1006
+ },
1007
+ typeParameters: {
1008
+ type: 'TSTypeParameterInstantiation',
1009
+ params: [
1010
+ {
1011
+ type: 'TSTypeReference',
1012
+ typeName: enumName,
1013
+ },
1014
+ ],
1015
+ },
1016
+ },
1017
+ },
1018
+ },
1019
+ specifiers: [],
1020
+ source: null,
1021
+ exportKind: 'value',
1022
+ assertions: [],
1023
+ },
1024
+ // export function getName(value: Foo): string;
1025
+ {
1026
+ type: 'ExportNamedDeclaration',
1027
+ declaration: {
1028
+ type: 'TSDeclareFunction',
1029
+ id: {
1030
+ type: 'Identifier',
1031
+ name: 'getName',
1032
+ },
1033
+ generator: false,
1034
+ expression: false,
1035
+ async: false,
1036
+ params: [
1037
+ {
1038
+ type: 'Identifier',
1039
+ name: 'value',
1040
+ typeAnnotation: {
1041
+ type: 'TSTypeAnnotation',
1042
+ typeAnnotation: {
1043
+ type: 'TSTypeReference',
1044
+ typeName: enumName,
1045
+ },
1046
+ },
1047
+ },
1048
+ ],
1049
+ returnType: {
1050
+ type: 'TSTypeAnnotation',
1051
+ typeAnnotation: {
1052
+ type: 'TSStringKeyword',
1053
+ },
1054
+ },
1055
+ },
1056
+ specifiers: [],
1057
+ source: null,
1058
+ exportKind: 'value',
1059
+ assertions: [],
1060
+ },
1061
+ ],
1062
+ },
1063
+ },
1064
+ ];
1065
+ },
1066
+ DeclareModuleExports(
1067
+ node: FlowESTree.DeclareModuleExports,
1068
+ ): TSESTree.TypeNode {
1069
+ throw translationError(node, 'CommonJS exports are not supported.');
1070
+ },
1071
+ ExistsTypeAnnotation(
1072
+ node: FlowESTree.ExistsTypeAnnotation,
1073
+ ): TSESTree.TypeNode {
1074
+ // The existential type does not map to any types in TS
1075
+ // It's also super deprecated - so let's not ever worry
1076
+ throw unsupportedTranslationError(node, 'exestential type');
1077
+ },
1078
+ ExportNamedDeclaration(
1079
+ node: FlowESTree.ExportNamedDeclaration,
1080
+ ):
1081
+ | TSESTree.ExportNamedDeclarationAmbiguous
1082
+ | [TSESTree.ExportNamedDeclarationAmbiguous, ?TSESTree.Node] {
1083
+ if (node.source != null || node.specifiers.length > 0) {
1084
+ // can never have a declaration with a source
1085
+ return {
1086
+ type: 'ExportNamedDeclaration',
1087
+ // flow does not currently support import/export assertions
1088
+ assertions: [],
1089
+ declaration: null,
1090
+ exportKind: node.exportKind,
1091
+ source:
1092
+ node.source == null ? null : transform.StringLiteral(node.source),
1093
+ specifiers: node.specifiers.map(transform.ExportSpecifier),
1094
+ };
1095
+ }
1096
+
1097
+ const [exportedDeclaration, mergedDeclaration] = (() => {
1098
+ if (node.declaration == null) {
1099
+ return [null];
1100
+ }
1101
+
1102
+ switch (node.declaration.type) {
1103
+ case 'ClassDeclaration':
1104
+ case 'FunctionDeclaration':
1105
+ case 'VariableDeclaration':
1106
+ // These cases shouldn't happen in flow defs because they have their own special
1107
+ // AST node (DeclareClass, DeclareFunction, DeclareVariable)
1108
+ throw unexpectedTranslationError(
1109
+ node.declaration,
1110
+ `Unexpected named declaration found ${node.declaration.type}`,
1111
+ );
1112
+
1113
+ case 'EnumDeclaration':
1114
+ return transform.EnumDeclaration(node.declaration);
1115
+ case 'InterfaceDeclaration':
1116
+ return [transform.InterfaceDeclaration(node.declaration), null];
1117
+ case 'OpaqueType':
1118
+ return [transform.OpaqueType(node.declaration), null];
1119
+ case 'TypeAlias':
1120
+ return [transform.TypeAlias(node.declaration), null];
1121
+ }
1122
+ })();
1123
+
1124
+ const mainExport = {
1125
+ type: 'ExportNamedDeclaration',
1126
+ assertions: [],
1127
+ declaration: exportedDeclaration,
1128
+ exportKind: node.exportKind,
1129
+ source: null,
1130
+ specifiers: [],
1131
+ };
1132
+
1133
+ if (mergedDeclaration != null) {
1134
+ // for cases where there is a merged declaration, TS enforces BOTH are exported
1135
+ return [
1136
+ mainExport,
1137
+ {
1138
+ type: 'ExportNamedDeclaration',
1139
+ assertions: [],
1140
+ declaration: mergedDeclaration,
1141
+ exportKind: node.exportKind,
1142
+ source: null,
1143
+ specifiers: [],
1144
+ },
1145
+ ];
1146
+ }
1147
+
1148
+ return mainExport;
1149
+ },
1150
+ ExportSpecifier(
1151
+ node: FlowESTree.ExportSpecifier,
1152
+ ): TSESTree.ExportSpecifier {
1153
+ return {
1154
+ type: 'ExportSpecifier',
1155
+ exported: transform.Identifier(node.exported, false),
1156
+ local: transform.Identifier(node.local, false),
1157
+ // flow does not support inline exportKind for named exports
1158
+ exportKind: 'value',
1159
+ };
1160
+ },
1161
+ FunctionTypeAnnotation(
1162
+ node: FlowESTree.FunctionTypeAnnotation,
1163
+ ): TSESTree.TSFunctionType {
1164
+ const params = node.params.map(transform.FunctionTypeParam);
1165
+ if (node.this != null) {
1166
+ params.unshift({
1167
+ type: 'Identifier',
1168
+ name: 'this',
1169
+ typeAnnotation: {
1170
+ type: 'TSTypeAnnotation',
1171
+ typeAnnotation: transform.TypeAnnotationType(
1172
+ node.this.typeAnnotation,
1173
+ ),
1174
+ },
1175
+ });
1176
+ }
1177
+ if (node.rest != null) {
1178
+ const rest = node.rest;
1179
+ params.push({
1180
+ type: 'RestElement',
1181
+ argument:
1182
+ rest.name == null
1183
+ ? {
1184
+ type: 'Identifier',
1185
+ name: '$$REST$$',
1186
+ }
1187
+ : transform.Identifier(rest.name, false),
1188
+ typeAnnotation: {
1189
+ type: 'TSTypeAnnotation',
1190
+ typeAnnotation: transform.TypeAnnotationType(rest.typeAnnotation),
1191
+ },
1192
+ });
1193
+ }
1194
+
1195
+ return {
1196
+ type: 'TSFunctionType',
1197
+ params,
1198
+ returnType: {
1199
+ type: 'TSTypeAnnotation',
1200
+ typeAnnotation: transform.TypeAnnotationType(node.returnType),
1201
+ },
1202
+ typeParameters:
1203
+ node.typeParameters == null
1204
+ ? undefined
1205
+ : transform.TypeParameterDeclaration(node.typeParameters),
1206
+ };
1207
+ },
1208
+ FunctionTypeParam(
1209
+ node: FlowESTree.FunctionTypeParam,
1210
+ idx: number = 0,
1211
+ ): TSESTree.Parameter {
1212
+ return {
1213
+ type: 'Identifier',
1214
+ name: node.name == null ? `$$PARAM_${idx}$$` : node.name.name,
1215
+ typeAnnotation: {
1216
+ type: 'TSTypeAnnotation',
1217
+ typeAnnotation: transform.TypeAnnotationType(node.typeAnnotation),
1218
+ },
1219
+ optional: node.optional,
1220
+ };
1221
+ },
1222
+ GenericTypeAnnotation(
1223
+ node: FlowESTree.GenericTypeAnnotation,
1224
+ ): TSESTree.TypeNode {
1225
+ const [fullTypeName, baseId] = (() => {
1226
+ let names: Array<string> = [];
1227
+ let currentNode = node.id;
1228
+
1229
+ while (currentNode != null) {
1230
+ switch (currentNode.type) {
1231
+ case 'Identifier': {
1232
+ names.unshift(currentNode.name);
1233
+ return [names.join('.'), currentNode];
1234
+ }
1235
+ case 'QualifiedTypeIdentifier': {
1236
+ names.unshift(currentNode.id.name);
1237
+ currentNode = currentNode.qualification;
1238
+ break;
1239
+ }
1240
+ }
1241
+ }
1242
+
1243
+ throw translationError(
1244
+ node,
1245
+ `Invalid program state, types should only contain 'Identifier' and 'QualifiedTypeIdentifier' nodes.`,
1246
+ );
1247
+ })();
1248
+
1249
+ const assertHasExactlyNTypeParameters = (
1250
+ count: number,
1251
+ ): $ReadOnlyArray<TSESTree.TypeNode> => {
1252
+ if (node.typeParameters != null) {
1253
+ if (node.typeParameters.params.length !== count) {
1254
+ throw translationError(
1255
+ node,
1256
+ `Expected exactly ${count} type parameter${
1257
+ count > 1 ? 's' : ''
1258
+ } with \`${fullTypeName}\``,
1259
+ );
1260
+ }
1261
+
1262
+ const res = [];
1263
+ for (const param of node.typeParameters.params) {
1264
+ res.push(transform.TypeAnnotationType(param));
1265
+ }
1266
+ return res;
1267
+ }
1268
+
1269
+ if (count !== 0) {
1270
+ throw translationError(
1271
+ node,
1272
+ `Expected no type parameters with \`${fullTypeName}\``,
1273
+ );
1274
+ }
1275
+
1276
+ return [];
1277
+ };
1278
+
1279
+ switch (fullTypeName) {
1280
+ case '$Call':
1281
+ case '$ObjMap':
1282
+ case '$ObjMapConst':
1283
+ case '$ObjMapi':
1284
+ case '$TupleMap': {
1285
+ /*
1286
+ TODO - I don't think it's possible to make these types work in the generic case.
1287
+
1288
+ TS has no utility types that allow you to generically mimic this functionality.
1289
+ You really need intimiate knowledge of the user's intent in order to correctly
1290
+ transform the code.
1291
+
1292
+ For example the simple example for $Call from the flow docs:
1293
+ ```
1294
+ type ExtractPropType = <T>({prop: T}) => T;
1295
+ type Obj = {prop: number};
1296
+ type PropType = $Call<ExtractPropType, Obj>;
1297
+ // expected -- typeof PropType === number
1298
+ ```
1299
+
1300
+ The equivalent in TS would be:
1301
+ ```
1302
+ type ExtractPropType<T extends { prop: any }> = (arg: T) => T['prop'];
1303
+ type Obj = { prop: number };
1304
+ type PropType = ReturnType<ExtractPropType<Obj>>; // number
1305
+ ```
1306
+ */
1307
+ throw unsupportedTranslationError(node, fullTypeName);
1308
+ }
1309
+
1310
+ case '$Diff':
1311
+ case '$Rest': {
1312
+ // `$Diff<A, B>` => `Pick<A, Exclude<keyof A, keyof B>>`
1313
+ const params = assertHasExactlyNTypeParameters(2);
1314
+ return {
1315
+ type: 'TSTypeReference',
1316
+ typeName: {
1317
+ type: 'Identifier',
1318
+ name: 'Pick',
1319
+ },
1320
+ typeParameters: {
1321
+ type: 'TSTypeParameterInstantiation',
1322
+ params: [
1323
+ params[0],
1324
+ {
1325
+ type: 'TSTypeReference',
1326
+ typeName: {
1327
+ type: 'Identifier',
1328
+ name: 'Exclude',
1329
+ },
1330
+ typeParameters: {
1331
+ type: 'TSTypeParameterInstantiation',
1332
+ params: [
1333
+ {
1334
+ type: 'TSTypeOperator',
1335
+ operator: 'keyof',
1336
+ typeAnnotation: params[0],
1337
+ },
1338
+ {
1339
+ type: 'TSTypeOperator',
1340
+ operator: 'keyof',
1341
+ typeAnnotation: params[1],
1342
+ },
1343
+ ],
1344
+ },
1345
+ },
1346
+ ],
1347
+ },
1348
+ };
1349
+ }
1350
+
1351
+ case '$ElementType':
1352
+ case '$PropertyType': {
1353
+ // `$ElementType<T, K>` => `T[K]`
1354
+ const params = assertHasExactlyNTypeParameters(2);
1355
+ return {
1356
+ type: 'TSIndexedAccessType',
1357
+ objectType: params[0],
1358
+ indexType: params[1],
1359
+ };
1360
+ }
1361
+
1362
+ case '$Exact': {
1363
+ // `$Exact<T>` => `T`
1364
+ // TS has no concept of exact vs inexact types
1365
+ return assertHasExactlyNTypeParameters(1)[0];
1366
+ }
1367
+
1368
+ case '$Exports': {
1369
+ // `$Exports<'module'>` => `typeof import('module')`
1370
+ const moduleName = assertHasExactlyNTypeParameters(1)[0];
1371
+ if (
1372
+ moduleName.type !== 'TSLiteralType' ||
1373
+ moduleName.literal.type !== 'Literal' ||
1374
+ typeof moduleName.literal.value !== 'string'
1375
+ ) {
1376
+ throw translationError(
1377
+ node,
1378
+ '$Exports must have a string literal argument',
1379
+ );
1380
+ }
1381
+
1382
+ return {
1383
+ type: 'TSImportType',
1384
+ isTypeOf: true,
1385
+ parameter: moduleName,
1386
+ qualifier: null,
1387
+ typeParameters: null,
1388
+ };
1389
+ }
1390
+
1391
+ case '$FlowFixMe': {
1392
+ // `$FlowFixMe` => `any`
1393
+ return {
1394
+ type: 'TSAnyKeyword',
1395
+ };
1396
+ }
1397
+
1398
+ case '$KeyMirror': {
1399
+ // `$KeyMirror<T>` => `{[K in keyof T]: K}`
1400
+ return {
1401
+ type: 'TSMappedType',
1402
+ typeParameter: {
1403
+ type: 'TSTypeParameter',
1404
+ name: {
1405
+ type: 'Identifier',
1406
+ name: 'K',
1407
+ },
1408
+ constraint: {
1409
+ type: 'TSTypeOperator',
1410
+ operator: 'keyof',
1411
+ typeAnnotation: assertHasExactlyNTypeParameters(1)[0],
1412
+ },
1413
+ in: false,
1414
+ out: false,
1415
+ },
1416
+ nameType: null,
1417
+ typeAnnotation: {
1418
+ type: 'TSTypeReference',
1419
+ typeName: {
1420
+ type: 'Identifier',
1421
+ name: 'K',
1422
+ },
1423
+ },
1424
+ };
1425
+ }
1426
+
1427
+ case '$Keys': {
1428
+ // `$Keys<T>` => `keyof T`
1429
+ return {
1430
+ type: 'TSTypeOperator',
1431
+ operator: 'keyof',
1432
+ typeAnnotation: assertHasExactlyNTypeParameters(1)[0],
1433
+ };
1434
+ }
1435
+
1436
+ case '$NonMaybeType': {
1437
+ // `$NonMaybeType<T>` => `NonNullable<T>`
1438
+ // Not a great name because `NonNullable` also excludes `undefined`
1439
+ return {
1440
+ type: 'TSTypeReference',
1441
+ typeName: {
1442
+ type: 'Identifier',
1443
+ name: 'NonNullable',
1444
+ },
1445
+ typeParameters: {
1446
+ type: 'TSTypeParameterInstantiation',
1447
+ params: assertHasExactlyNTypeParameters(1),
1448
+ },
1449
+ };
1450
+ }
1451
+
1452
+ case '$ReadOnly': {
1453
+ // `$ReadOnly<T>` => `Readonly<T>`
1454
+ return {
1455
+ type: 'TSTypeReference',
1456
+ typeName: {
1457
+ type: 'Identifier',
1458
+ name: 'Readonly',
1459
+ },
1460
+ typeParameters: {
1461
+ type: 'TSTypeParameterInstantiation',
1462
+ params: assertHasExactlyNTypeParameters(1),
1463
+ },
1464
+ };
1465
+ }
1466
+
1467
+ case '$ReadOnlyArray': {
1468
+ // `$ReadOnlyArray<T>` => `ReadonlyArray<T>`
1469
+ //
1470
+ // we could also do => `readonly T[]`
1471
+ // TODO - maybe a config option?
1472
+ return {
1473
+ type: 'TSTypeReference',
1474
+ typeName: {
1475
+ type: 'Identifier',
1476
+ name: 'ReadonlyArray',
1477
+ },
1478
+ typeParameters: {
1479
+ type: 'TSTypeParameterInstantiation',
1480
+ params: assertHasExactlyNTypeParameters(1),
1481
+ },
1482
+ };
1483
+ }
1484
+
1485
+ case '$Shape':
1486
+ case '$Partial': {
1487
+ // `$Partial<T>` => `Partial<T>`
1488
+ return {
1489
+ type: 'TSTypeReference',
1490
+ typeName: {
1491
+ type: 'Identifier',
1492
+ name: 'Partial',
1493
+ },
1494
+ typeParameters: {
1495
+ type: 'TSTypeParameterInstantiation',
1496
+ params: assertHasExactlyNTypeParameters(1),
1497
+ },
1498
+ };
1499
+ }
1500
+
1501
+ case '$Subtype':
1502
+ case '$Supertype': {
1503
+ // These types are deprecated and shouldn't be used in any modern code
1504
+ // so let's not even bother trying to figure it out
1505
+ throw unsupportedTranslationError(node, fullTypeName);
1506
+ }
1507
+
1508
+ case '$Values': {
1509
+ // `$Values<T>` => `T[keyof T]`
1510
+ const transformedType = assertHasExactlyNTypeParameters(1)[0];
1511
+ return {
1512
+ type: 'TSIndexedAccessType',
1513
+ objectType: transformedType,
1514
+ indexType: {
1515
+ type: 'TSTypeOperator',
1516
+ operator: 'keyof',
1517
+ typeAnnotation: transformedType,
1518
+ },
1519
+ };
1520
+ }
1521
+
1522
+ case 'Class': {
1523
+ // `Class<T>` => `new (...args: any[]) => T`
1524
+ const param = assertHasExactlyNTypeParameters(1)[0];
1525
+ if (param.type !== 'TSTypeReference') {
1526
+ throw translationError(
1527
+ node,
1528
+ 'Expected a type reference within Class<T>',
1529
+ );
1530
+ }
1531
+
1532
+ return {
1533
+ type: 'TSConstructorType',
1534
+ abstract: false,
1535
+ params: [
1536
+ {
1537
+ type: 'RestElement',
1538
+ argument: {
1539
+ type: 'Identifier',
1540
+ name: 'args',
1541
+ },
1542
+ typeAnnotation: {
1543
+ type: 'TSTypeAnnotation',
1544
+ typeAnnotation: {
1545
+ type: 'TSArrayType',
1546
+ elementType: {
1547
+ type: 'TSAnyKeyword',
1548
+ },
1549
+ },
1550
+ },
1551
+ },
1552
+ ],
1553
+ returnType: {
1554
+ type: 'TSTypeAnnotation',
1555
+ typeAnnotation: param,
1556
+ },
1557
+ };
1558
+ }
1559
+ }
1560
+
1561
+ // React special conversion:
1562
+ if (isReactImport(baseId)) {
1563
+ switch (fullTypeName) {
1564
+ // React.Node -> React.ReactNode
1565
+ case 'React.Node': {
1566
+ assertHasExactlyNTypeParameters(0);
1567
+ return {
1568
+ type: 'TSTypeReference',
1569
+ typeName: {
1570
+ type: 'TSQualifiedName',
1571
+ left: transform.Identifier(baseId, false),
1572
+ right: {
1573
+ type: 'Identifier',
1574
+ name: `ReactNode`,
1575
+ },
1576
+ },
1577
+ typeParameters: undefined,
1578
+ };
1579
+ }
1580
+ // React.Element<typeof Component> -> React.ReactElement<typeof Component>
1581
+ case 'React.Element': {
1582
+ return {
1583
+ type: 'TSTypeReference',
1584
+ typeName: {
1585
+ type: 'TSQualifiedName',
1586
+ left: transform.Identifier(baseId, false),
1587
+ right: {
1588
+ type: 'Identifier',
1589
+ name: `ReactElement`,
1590
+ },
1591
+ },
1592
+ typeParameters: {
1593
+ type: 'TSTypeParameterInstantiation',
1594
+ params: assertHasExactlyNTypeParameters(1),
1595
+ },
1596
+ };
1597
+ }
1598
+ // React.MixedElement -> JSX.Element
1599
+ case 'React.MixedElement': {
1600
+ assertHasExactlyNTypeParameters(0);
1601
+ return {
1602
+ type: 'TSTypeReference',
1603
+ typeName: {
1604
+ type: 'TSQualifiedName',
1605
+ left: {
1606
+ type: 'Identifier',
1607
+ name: 'JSX',
1608
+ },
1609
+ right: {
1610
+ type: 'Identifier',
1611
+ name: 'Element',
1612
+ },
1613
+ },
1614
+ typeParameters: undefined,
1615
+ };
1616
+ }
1617
+ // React.AbstractComponent<Config> -> React.ForwardRefExoticComponent<Config>
1618
+ // React.AbstractComponent<Config, Instance> -> React.ForwardRefExoticComponent<Config & React.RefAttributes<Instance>>
1619
+ case 'React.AbstractComponent': {
1620
+ const typeParameters = node.typeParameters;
1621
+ if (typeParameters == null || typeParameters.params.length === 0) {
1622
+ throw translationError(
1623
+ node,
1624
+ `Expected at least 1 type parameter with \`${fullTypeName}\``,
1625
+ );
1626
+ }
1627
+ const params = typeParameters.params;
1628
+ if (params.length > 2) {
1629
+ throw translationError(
1630
+ node,
1631
+ `Expected at no more than 2 type parameters with \`${fullTypeName}\``,
1632
+ );
1633
+ }
1634
+
1635
+ let newTypeParam = transform.TypeAnnotationType(params[0]);
1636
+ if (params[1] != null) {
1637
+ newTypeParam = {
1638
+ type: 'TSIntersectionType',
1639
+ types: [
1640
+ newTypeParam,
1641
+ {
1642
+ type: 'TSTypeReference',
1643
+ typeName: {
1644
+ type: 'TSQualifiedName',
1645
+ left: {
1646
+ type: 'Identifier',
1647
+ name: 'React',
1648
+ },
1649
+ right: {
1650
+ type: 'Identifier',
1651
+ name: 'RefAttributes',
1652
+ },
1653
+ },
1654
+ typeParameters: {
1655
+ type: 'TSTypeParameterInstantiation',
1656
+ params: [transform.TypeAnnotationType(params[1])],
1657
+ },
1658
+ },
1659
+ ],
1660
+ };
1661
+ }
1662
+
1663
+ return {
1664
+ type: 'TSTypeReference',
1665
+ typeName: {
1666
+ type: 'TSQualifiedName',
1667
+ left: transform.Identifier(baseId, false),
1668
+ right: {
1669
+ type: 'Identifier',
1670
+ name: `ForwardRefExoticComponent`,
1671
+ },
1672
+ },
1673
+ typeParameters: {
1674
+ type: 'TSTypeParameterInstantiation',
1675
+ params: [newTypeParam],
1676
+ },
1677
+ };
1678
+ }
1679
+ }
1680
+ }
1681
+
1682
+ return {
1683
+ type: 'TSTypeReference',
1684
+ typeName:
1685
+ node.id.type === 'Identifier'
1686
+ ? transform.Identifier(node.id, false)
1687
+ : transform.QualifiedTypeIdentifier(node.id),
1688
+ typeParameters:
1689
+ node.typeParameters == null
1690
+ ? undefined
1691
+ : transform.TypeParameterInstantiation(node.typeParameters),
1692
+ };
1693
+ },
1694
+ Identifier(
1695
+ node: FlowESTree.Identifier,
1696
+ includeTypeAnnotation: boolean = true,
1697
+ ): TSESTree.Identifier {
1698
+ return {
1699
+ type: 'Identifier',
1700
+ name: node.name,
1701
+ ...(includeTypeAnnotation && node.typeAnnotation != null
1702
+ ? {
1703
+ typeAnnotation: transform.TypeAnnotation(node.typeAnnotation),
1704
+ }
1705
+ : {}),
1706
+ };
1707
+ },
1708
+ IndexedAccessType(
1709
+ node: FlowESTree.IndexedAccessType | FlowESTree.OptionalIndexedAccessType,
1710
+ ): TSESTree.TSIndexedAccessType {
1711
+ return {
1712
+ type: 'TSIndexedAccessType',
1713
+ objectType: transform.TypeAnnotationType(node.objectType),
1714
+ indexType: transform.TypeAnnotationType(node.indexType),
1715
+ };
1716
+ },
1717
+ InterfaceDeclaration(
1718
+ node: FlowESTree.InterfaceDeclaration,
1719
+ ): TSESTree.TSInterfaceDeclaration {
1720
+ return transform.DeclareInterface(node);
1721
+ },
1722
+ ImportAttribute(
1723
+ node: FlowESTree.ImportAttribute,
1724
+ ): TSESTree.ImportAttribute {
1725
+ return {
1726
+ type: 'ImportAttribute',
1727
+ key:
1728
+ node.key.type === 'Identifier'
1729
+ ? transform.Identifier(node.key)
1730
+ : transform.Literal(node.key),
1731
+ value: transform.Literal(node.value),
1732
+ };
1733
+ },
1734
+ ImportDeclaration(
1735
+ node: FlowESTree.ImportDeclaration,
1736
+ ): TSESTree.ImportDeclaration {
1737
+ if (node.importKind === 'typeof') {
1738
+ /*
1739
+ TODO - this is a complicated change to support because TS
1740
+ does not have typeof imports.
1741
+ Making it a `type` import would change the meaning!
1742
+ The only way to truly support this is to prefix all **usages** with `typeof T`.
1743
+ eg:
1744
+
1745
+ ```
1746
+ import typeof Foo from 'Foo';
1747
+ type T = Foo;
1748
+ ```
1749
+
1750
+ would become:
1751
+
1752
+ ```
1753
+ import type Foo from 'Foo';
1754
+ type T = typeof Foo;
1755
+ ```
1756
+
1757
+ This seems simple, but will actually be super complicated for us to do with
1758
+ our current translation architecture
1759
+ */
1760
+ throw unsupportedTranslationError(node, 'typeof imports');
1761
+ }
1762
+ const importKind = node.importKind;
1763
+
1764
+ return {
1765
+ type: 'ImportDeclaration',
1766
+ assertions: node.assertions.map(transform.ImportAttribute),
1767
+ importKind: importKind ?? 'value',
1768
+ source: transform.StringLiteral(node.source),
1769
+ specifiers: node.specifiers.map(spec => {
1770
+ switch (spec.type) {
1771
+ case 'ImportDefaultSpecifier':
1772
+ return {
1773
+ type: 'ImportDefaultSpecifier',
1774
+ local: transform.Identifier(spec.local, false),
1775
+ };
1776
+
1777
+ case 'ImportNamespaceSpecifier':
1778
+ return {
1779
+ type: 'ImportNamespaceSpecifier',
1780
+ local: transform.Identifier(spec.local, false),
1781
+ };
1782
+
1783
+ case 'ImportSpecifier':
1784
+ if (spec.importKind === 'typeof') {
1785
+ // see above
1786
+ throw unsupportedTranslationError(node, 'typeof imports');
1787
+ }
1788
+ return {
1789
+ type: 'ImportSpecifier',
1790
+ importKind: spec.importKind ?? 'value',
1791
+ imported: transform.Identifier(spec.imported, false),
1792
+ local: transform.Identifier(spec.local, false),
1793
+ };
1794
+ }
1795
+ }),
1796
+ };
1797
+ },
1798
+ InterfaceExtends(
1799
+ node: FlowESTree.InterfaceExtends,
1800
+ ): TSESTree.TSInterfaceHeritage {
1801
+ return {
1802
+ type: 'TSInterfaceHeritage',
1803
+ expression: transform.Identifier(node.id, false),
1804
+ typeParameters:
1805
+ node.typeParameters == null
1806
+ ? undefined
1807
+ : transform.TypeParameterInstantiation(node.typeParameters),
1808
+ };
1809
+ },
1810
+ InterfaceTypeAnnotation(
1811
+ node: FlowESTree.InterfaceTypeAnnotation,
1812
+ ): TSESTree.TypeNode {
1813
+ if (node.extends) {
1814
+ // type T = interface extends U, V { ... }
1815
+ // to
1816
+ // type T = U & V & { ... }
1817
+ return {
1818
+ type: 'TSIntersectionType',
1819
+ types: [
1820
+ ...node.extends.map(ex => ({
1821
+ type: 'TSTypeReference',
1822
+ typeName: transform.Identifier(ex.id, false),
1823
+ typeParameters:
1824
+ ex.typeParameters == null
1825
+ ? undefined
1826
+ : transform.TypeParameterInstantiation(ex.typeParameters),
1827
+ })),
1828
+ transform.ObjectTypeAnnotation(node.body),
1829
+ ],
1830
+ };
1831
+ }
1832
+
1833
+ return transform.ObjectTypeAnnotation(node.body);
1834
+ },
1835
+ IntersectionTypeAnnotation(
1836
+ node: FlowESTree.IntersectionTypeAnnotation,
1837
+ ): TSESTree.TSIntersectionType {
1838
+ return {
1839
+ type: 'TSIntersectionType',
1840
+ types: node.types.map(transform.TypeAnnotationType),
1841
+ };
1842
+ },
1843
+ Literal(node: FlowESTree.Literal): TSESTree.Literal {
1844
+ switch (node.literalType) {
1845
+ case 'bigint':
1846
+ return transform.BigIntLiteral(node);
1847
+ case 'boolean':
1848
+ return transform.BooleanLiteral(node);
1849
+ case 'null':
1850
+ return transform.NullLiteral(node);
1851
+ case 'numeric':
1852
+ return transform.NumericLiteral(node);
1853
+ case 'regexp':
1854
+ return transform.RegExpLiteral(node);
1855
+ case 'string':
1856
+ return transform.StringLiteral(node);
1857
+ }
1858
+ },
1859
+ MixedTypeAnnotation(
1860
+ _node: FlowESTree.MixedTypeAnnotation,
1861
+ ): TSESTree.TSUnknownKeyword {
1862
+ return {
1863
+ type: 'TSUnknownKeyword',
1864
+ };
1865
+ },
1866
+ NullLiteral(_node: FlowESTree.NullLiteral): TSESTree.NullLiteral {
1867
+ return {
1868
+ type: 'Literal',
1869
+ raw: 'null',
1870
+ value: null,
1871
+ };
1872
+ },
1873
+ NullLiteralTypeAnnotation(
1874
+ _node: FlowESTree.NullLiteralTypeAnnotation,
1875
+ ): TSESTree.TSNullKeyword {
1876
+ return {
1877
+ type: 'TSNullKeyword',
1878
+ };
1879
+ },
1880
+ NullableTypeAnnotation(
1881
+ node: FlowESTree.NullableTypeAnnotation,
1882
+ ): TSESTree.TSUnionType {
1883
+ // TS doesn't support the maybe type, so have to explicitly union in `null | undefined`
1884
+ // `?T` becomes `null | undefined | T`
1885
+ return {
1886
+ type: 'TSUnionType',
1887
+ types: [
1888
+ {
1889
+ type: 'TSNullKeyword',
1890
+ },
1891
+ {
1892
+ type: 'TSUndefinedKeyword',
1893
+ },
1894
+ transform.TypeAnnotationType(node.typeAnnotation),
1895
+ ],
1896
+ };
1897
+ },
1898
+ NumberLiteralTypeAnnotation(
1899
+ node: FlowESTree.NumberLiteralTypeAnnotation,
1900
+ ): TSESTree.TSLiteralType {
1901
+ return {
1902
+ type: 'TSLiteralType',
1903
+ literal: ({
1904
+ type: 'Literal',
1905
+ value: node.value,
1906
+ raw: node.raw,
1907
+ }: TSESTree.NumberLiteral),
1908
+ };
1909
+ },
1910
+ NumberTypeAnnotation(
1911
+ _node: FlowESTree.NumberTypeAnnotation,
1912
+ ): TSESTree.TSNumberKeyword {
1913
+ return {
1914
+ type: 'TSNumberKeyword',
1915
+ };
1916
+ },
1917
+ NumericLiteral(node: FlowESTree.NumericLiteral): TSESTree.NumberLiteral {
1918
+ return {
1919
+ type: 'Literal',
1920
+ raw: node.raw,
1921
+ value: node.value,
1922
+ };
1923
+ },
1924
+ ObjectTypeAnnotation(
1925
+ node: FlowESTree.ObjectTypeAnnotation,
1926
+ ): TSESTree.TSTypeLiteral | TSESTree.TSIntersectionType {
1927
+ // we want to preserve the source order of the members
1928
+ // unfortunately flow has unordered properties storing things
1929
+ // so store all elements with their start index and sort the
1930
+ // list afterward
1931
+ const members: Array<{start: number, node: TSESTree.TypeElement}> = [];
1932
+
1933
+ for (const callProp of node.callProperties) {
1934
+ members.push({
1935
+ start: callProp.range[0],
1936
+ node: transform.ObjectTypeCallProperty(callProp),
1937
+ });
1938
+ }
1939
+
1940
+ for (const indexer of node.indexers) {
1941
+ members.push({
1942
+ start: indexer.range[0],
1943
+ node: transform.ObjectTypeIndexer(indexer),
1944
+ });
1945
+ }
1946
+
1947
+ /*
1948
+ TODO - internalSlots
1949
+ I don't think there's anything analogous in TS.
1950
+ They're really rarely used (if ever) - so let's just ignore them for now
1951
+ */
1952
+ if (node.internalSlots.length > 0) {
1953
+ throw unsupportedTranslationError(
1954
+ node.internalSlots[0],
1955
+ 'internal slots',
1956
+ );
1957
+ }
1958
+
1959
+ if (!node.properties.find(FlowESTree.isObjectTypeSpreadProperty)) {
1960
+ for (const property of node.properties) {
1961
+ if (property.type === 'ObjectTypeSpreadProperty') {
1962
+ // this is imposible due to the above find condition
1963
+ // this check is purely to satisfy flow
1964
+ throw unexpectedTranslationError(property, 'Impossible state');
1965
+ }
1966
+
1967
+ members.push({
1968
+ start: property.range[0],
1969
+ node: transform.ObjectTypeProperty(property),
1970
+ });
1971
+ }
1972
+
1973
+ const tsBody = members
1974
+ .sort((a, b) => a.start - b.start)
1975
+ .map(({node}) => node);
1976
+
1977
+ return {
1978
+ type: 'TSTypeLiteral',
1979
+ members: tsBody,
1980
+ };
1981
+ } else {
1982
+ /*
1983
+ spreads are a complicate thing for us to handle, sadly.
1984
+ in flow type spreads are modelled after object spreads; meaning that for
1985
+ { ...A, ...B } - all properties in B will explicitly replace any properties
1986
+ in A of the same name.
1987
+ ```
1988
+ type T1 = { a: string };
1989
+ type T2 = { a: number };
1990
+ type T3 = { ...T1, ...T2 };
1991
+ type A = T3['a'] // === number
1992
+ ```
1993
+
1994
+ however in TS there are no object type spreads - you can only merge
1995
+ objects either via the intersection operator or via interface extends.
1996
+
1997
+ For an interface extends - `interface B extends A { ... }` - TS enforces
1998
+ that the properties of B are all covariantly related to the same named
1999
+ properties in A.
2000
+ So we can't use an interface extends.
2001
+
2002
+ For a type intersection - `type T = A & B;` - TS will (essentially) merge
2003
+ the types by intersecting each same named property in each type to calculate
2004
+ the resulting type. This has the effect of enforcing the same constraint
2005
+ as the interface case, however instead of an error it causes properties to
2006
+ become `never`:
2007
+ ```
2008
+ type T1 = { a: string };
2009
+ type T2 = { a: number };
2010
+ type T3 = T1 & T2;
2011
+ type A = T3['a'] // === string & number === never
2012
+ ```
2013
+
2014
+ So in order for us to model flow's spreads we have to explicitly omit the keys
2015
+ from the proceeding type that might clash. We can do this pretty easily using
2016
+ TS's utility types:
2017
+ ```
2018
+ type T1 = { a: string };
2019
+ type T2 = { a: number };
2020
+ type T3 = Omit<T1, keyof T2> & T2;
2021
+ type A = T3['a'] // === number
2022
+ ```
2023
+
2024
+ Unfortunately because we need to solve for the general case object type usage,
2025
+ it's going to be a bit ugly and complicated, sadly.
2026
+
2027
+ If we had access to type information we would be able to skip some ugliness by
2028
+ checking to see if there is any overlap and skipping the omit step if there isn't.
2029
+ But alas - we're working purely syntactically.
2030
+
2031
+ ```
2032
+ type T = { ...T1, b: string };
2033
+ // becomes
2034
+ type T = Omit<T1, keyof { b: string }> & { b: string };
2035
+ ```
2036
+ ```
2037
+ type T = { ...T1, ...T2, ...T3, b: string };
2038
+ // becomes
2039
+ type T =
2040
+ & Omit<T1, keyof (T2 | T3 | { b: string })>
2041
+ & Omit<T2, keyof (T3 | { b: string })>
2042
+ & Omit<T3, keyof { b: string }>
2043
+ & { b: string };
2044
+ ```
2045
+
2046
+ Note that because it's going to be super ugly and complicated - for now we're going to disallow:
2047
+ - spreads in the middle
2048
+ - spreads at the end
2049
+ - spreads of things that aren't "Identifiers"
2050
+ */
2051
+
2052
+ if (members.length > 0) {
2053
+ throw unsupportedTranslationError(
2054
+ node,
2055
+ 'object types with spreads, indexers and/or call properties at the same time',
2056
+ );
2057
+ }
2058
+
2059
+ const typesToIntersect = [];
2060
+ for (const property of node.properties) {
2061
+ if (property.type === 'ObjectTypeSpreadProperty') {
2062
+ if (members.length > 0) {
2063
+ throw unsupportedTranslationError(
2064
+ property,
2065
+ 'object types with spreads in the middle or at the end',
2066
+ );
2067
+ }
2068
+
2069
+ const spreadType = transform.TypeAnnotationType(property.argument);
2070
+ if (spreadType.type !== 'TSTypeReference') {
2071
+ throw unsupportedTranslationError(
2072
+ property,
2073
+ 'object types with complex spreads',
2074
+ );
2075
+ }
2076
+
2077
+ typesToIntersect.push(spreadType);
2078
+ } else {
2079
+ members.push({
2080
+ start: property.range[0],
2081
+ node: transform.ObjectTypeProperty(property),
2082
+ });
2083
+ }
2084
+ }
2085
+
2086
+ const tsBody = members
2087
+ .sort((a, b) => a.start - b.start)
2088
+ .map(({node}) => node);
2089
+ const objectType = {
2090
+ type: 'TSTypeLiteral',
2091
+ members: tsBody,
2092
+ };
2093
+
2094
+ const intersectionMembers: Array<TSESTree.TypeNode> = [];
2095
+ for (let i = 0; i < typesToIntersect.length; i += 1) {
2096
+ const currentType = typesToIntersect[i];
2097
+ const remainingTypes = typesToIntersect.slice(i + 1);
2098
+ intersectionMembers.push({
2099
+ type: 'TSTypeReference',
2100
+ typeName: {
2101
+ type: 'Identifier',
2102
+ name: 'Omit',
2103
+ },
2104
+ typeParameters: {
2105
+ type: 'TSTypeParameterInstantiation',
2106
+ params: [
2107
+ currentType,
2108
+ {
2109
+ type: 'TSTypeOperator',
2110
+ operator: 'keyof',
2111
+ typeAnnotation: {
2112
+ type: 'TSUnionType',
2113
+ types: [...remainingTypes, objectType],
2114
+ },
2115
+ },
2116
+ ],
2117
+ },
2118
+ });
2119
+ }
2120
+ intersectionMembers.push(objectType);
2121
+
2122
+ return {
2123
+ type: 'TSIntersectionType',
2124
+ types: intersectionMembers,
2125
+ };
2126
+ }
2127
+ },
2128
+ ObjectTypeCallProperty(
2129
+ node: FlowESTree.ObjectTypeCallProperty,
2130
+ ): TSESTree.TSCallSignatureDeclaration {
2131
+ // the info is stored on the "value"
2132
+ const func = transform.FunctionTypeAnnotation(node.value);
2133
+ return {
2134
+ type: 'TSCallSignatureDeclaration',
2135
+ params: func.params,
2136
+ returnType: func.returnType,
2137
+ typeParameters: func.typeParameters,
2138
+ };
2139
+ },
2140
+ ObjectTypeIndexer(
2141
+ node: FlowESTree.ObjectTypeIndexer,
2142
+ ): TSESTree.TSIndexSignature {
2143
+ return {
2144
+ type: 'TSIndexSignature',
2145
+ parameters: [
2146
+ {
2147
+ type: 'Identifier',
2148
+ name: node.id == null ? '$$Key$$' : node.id.name,
2149
+ typeAnnotation: {
2150
+ type: 'TSTypeAnnotation',
2151
+ typeAnnotation: transform.TypeAnnotationType(node.key),
2152
+ },
2153
+ },
2154
+ ],
2155
+ readonly: node.variance?.kind === 'plus',
2156
+ static: node.static,
2157
+ typeAnnotation: {
2158
+ type: 'TSTypeAnnotation',
2159
+ typeAnnotation: transform.TypeAnnotationType(node.value),
2160
+ },
2161
+ };
2162
+ },
2163
+ ObjectTypeProperty(
2164
+ node: FlowESTree.ObjectTypeProperty,
2165
+ ): TSESTree.TSPropertySignature | TSESTree.TSMethodSignature {
2166
+ const key =
2167
+ node.key.type === 'Identifier'
2168
+ ? transform.Identifier(node.key)
2169
+ : transform.StringLiteral(node.key);
2170
+
2171
+ if (node.method === true) {
2172
+ // flow has just one node for all object properties and relies upon the method flag
2173
+ // TS has separate nodes for methods and properties
2174
+ const func = transform.FunctionTypeAnnotation(node.value);
2175
+ return {
2176
+ type: 'TSMethodSignature',
2177
+ computed: false,
2178
+ key,
2179
+ kind: node.kind === 'init' ? 'method' : node.kind,
2180
+ optional: node.optional,
2181
+ params: func.params,
2182
+ returnType: func.returnType,
2183
+ static: node.static,
2184
+ typeParameters: func.typeParameters,
2185
+ };
2186
+ }
2187
+
2188
+ if (node.kind === 'get' || node.kind === 'set') {
2189
+ // flow treats getters/setters as true property signatures (method === false)
2190
+ // TS treats them as method signatures
2191
+ const func = transform.FunctionTypeAnnotation(node.value);
2192
+ return {
2193
+ type: 'TSMethodSignature',
2194
+ computed: false,
2195
+ key,
2196
+ kind: node.kind,
2197
+ optional: false,
2198
+ params: func.params,
2199
+ // TS setters must not have a return type
2200
+ returnType: node.kind === 'set' ? undefined : func.returnType,
2201
+ static: node.static,
2202
+ // TS accessors cannot have type parameters
2203
+ typeParameters: undefined,
2204
+ };
2205
+ }
2206
+
2207
+ return {
2208
+ type: 'TSPropertySignature',
2209
+ computed: false,
2210
+ key,
2211
+ optional: node.optional,
2212
+ readonly: node.variance?.kind === 'plus',
2213
+ static: node.static,
2214
+ typeAnnotation: {
2215
+ type: 'TSTypeAnnotation',
2216
+ typeAnnotation: transform.TypeAnnotationType(node.value),
2217
+ },
2218
+ };
2219
+ },
2220
+ OpaqueType(node: FlowESTree.OpaqueType): TSESTree.TSTypeAliasDeclaration {
2221
+ return transform.DeclareOpaqueType(node);
2222
+ },
2223
+ OptionalIndexedAccessType(
2224
+ node: FlowESTree.OptionalIndexedAccessType,
2225
+ ): TSESTree.TSIndexedAccessType {
2226
+ // Foo?.[A][B]
2227
+ // ^^^^^^^^ optional = true
2228
+ // ^^^^^^^^^^^ optional = false
2229
+ if (node.optional === false) {
2230
+ return transform.IndexedAccessType(node);
2231
+ }
2232
+
2233
+ // TS doesn't support optional index access so we have to wrap the object:
2234
+ // `T?.[K]` becomes `NonNullable<T>[K]`
2235
+ return {
2236
+ type: 'TSIndexedAccessType',
2237
+ objectType: {
2238
+ type: 'TSTypeReference',
2239
+ typeName: {
2240
+ type: 'Identifier',
2241
+ name: 'NonNullable',
2242
+ },
2243
+ typeParameters: {
2244
+ type: 'TSTypeParameterInstantiation',
2245
+ params: [transform.TypeAnnotationType(node.objectType)],
2246
+ },
2247
+ },
2248
+ indexType: transform.TypeAnnotationType(node.indexType),
2249
+ };
2250
+ },
2251
+ QualifiedTypeIdentifier(
2252
+ node: FlowESTree.QualifiedTypeIdentifier,
2253
+ ): TSESTree.TSQualifiedName {
2254
+ const qual = node.qualification;
2255
+
2256
+ return {
2257
+ type: 'TSQualifiedName',
2258
+ left:
2259
+ qual.type === 'Identifier'
2260
+ ? transform.Identifier(qual, false)
2261
+ : transform.QualifiedTypeIdentifier(qual),
2262
+ right: transform.Identifier(node.id, false),
2263
+ };
2264
+ },
2265
+ RegExpLiteral(node: FlowESTree.RegExpLiteral): TSESTree.RegExpLiteral {
2266
+ return {
2267
+ type: 'Literal',
2268
+ raw: node.raw,
2269
+ regex: {
2270
+ pattern: node.regex.pattern,
2271
+ flags: node.regex.pattern,
2272
+ },
2273
+ value: node.value,
2274
+ };
2275
+ },
2276
+ StringLiteral(node: FlowESTree.StringLiteral): TSESTree.StringLiteral {
2277
+ return {
2278
+ type: 'Literal',
2279
+ raw: node.raw,
2280
+ value: node.value,
2281
+ };
2282
+ },
2283
+ StringLiteralTypeAnnotation(
2284
+ node: FlowESTree.StringLiteralTypeAnnotation,
2285
+ ): TSESTree.TSLiteralType {
2286
+ return {
2287
+ type: 'TSLiteralType',
2288
+ literal: ({
2289
+ type: 'Literal',
2290
+ value: node.value,
2291
+ raw: node.raw,
2292
+ }: TSESTree.StringLiteral),
2293
+ };
2294
+ },
2295
+ StringTypeAnnotation(
2296
+ _node: FlowESTree.StringTypeAnnotation,
2297
+ ): TSESTree.TSStringKeyword {
2298
+ return {
2299
+ type: 'TSStringKeyword',
2300
+ };
2301
+ },
2302
+ SymbolTypeAnnotation(
2303
+ _node: FlowESTree.SymbolTypeAnnotation,
2304
+ ): TSESTree.TSSymbolKeyword {
2305
+ return {
2306
+ type: 'TSSymbolKeyword',
2307
+ };
2308
+ },
2309
+ ThisTypeAnnotation(
2310
+ _node: FlowESTree.ThisTypeAnnotation,
2311
+ ): TSESTree.TSThisType {
2312
+ return {
2313
+ type: 'TSThisType',
2314
+ };
2315
+ },
2316
+ TupleTypeAnnotation(
2317
+ node: FlowESTree.TupleTypeAnnotation,
2318
+ ): TSESTree.TSTupleType {
2319
+ return {
2320
+ type: 'TSTupleType',
2321
+ elementTypes: node.types.map(transform.TypeAnnotationType),
2322
+ };
2323
+ },
2324
+ TypeAlias(node: FlowESTree.TypeAlias): TSESTree.TSTypeAliasDeclaration {
2325
+ return transform.DeclareTypeAlias(node);
2326
+ },
2327
+ TypeAnnotation(node: FlowESTree.TypeAnnotation): TSESTree.TSTypeAnnotation {
2328
+ return {
2329
+ type: 'TSTypeAnnotation',
2330
+ typeAnnotation: transform.TypeAnnotationType(node.typeAnnotation),
2331
+ };
2332
+ },
2333
+ TypeAnnotationType(node: FlowESTree.TypeAnnotationType): TSESTree.TypeNode {
2334
+ switch (node.type) {
2335
+ case 'AnyTypeAnnotation':
2336
+ return transform.AnyTypeAnnotation(node);
2337
+ case 'ArrayTypeAnnotation':
2338
+ return transform.ArrayTypeAnnotation(node);
2339
+ case 'BigIntLiteralTypeAnnotation':
2340
+ return transform.BigIntLiteralTypeAnnotation(node);
2341
+ case 'BigIntTypeAnnotation':
2342
+ return transform.BigIntTypeAnnotation(node);
2343
+ case 'BooleanLiteralTypeAnnotation':
2344
+ return transform.BooleanLiteralTypeAnnotation(node);
2345
+ case 'BooleanTypeAnnotation':
2346
+ return transform.BooleanTypeAnnotation(node);
2347
+ case 'EmptyTypeAnnotation':
2348
+ return transform.EmptyTypeAnnotation(node);
2349
+ case 'ExistsTypeAnnotation':
2350
+ return transform.ExistsTypeAnnotation(node);
2351
+ case 'FunctionTypeAnnotation':
2352
+ return transform.FunctionTypeAnnotation(node);
2353
+ case 'GenericTypeAnnotation':
2354
+ return transform.GenericTypeAnnotation(node);
2355
+ case 'IndexedAccessType':
2356
+ return transform.IndexedAccessType(node);
2357
+ case 'InterfaceTypeAnnotation':
2358
+ return transform.InterfaceTypeAnnotation(node);
2359
+ case 'IntersectionTypeAnnotation':
2360
+ return transform.IntersectionTypeAnnotation(node);
2361
+ case 'MixedTypeAnnotation':
2362
+ return transform.MixedTypeAnnotation(node);
2363
+ case 'NullLiteralTypeAnnotation':
2364
+ return transform.NullLiteralTypeAnnotation(node);
2365
+ case 'NullableTypeAnnotation':
2366
+ return transform.NullableTypeAnnotation(node);
2367
+ case 'NumberLiteralTypeAnnotation':
2368
+ return transform.NumberLiteralTypeAnnotation(node);
2369
+ case 'NumberTypeAnnotation':
2370
+ return transform.NumberTypeAnnotation(node);
2371
+ case 'ObjectTypeAnnotation':
2372
+ return transform.ObjectTypeAnnotation(node);
2373
+ case 'OptionalIndexedAccessType':
2374
+ return transform.OptionalIndexedAccessType(node);
2375
+ case 'QualifiedTypeIdentifier':
2376
+ return transform.QualifiedTypeIdentifier(node);
2377
+ case 'StringLiteralTypeAnnotation':
2378
+ return transform.StringLiteralTypeAnnotation(node);
2379
+ case 'StringTypeAnnotation':
2380
+ return transform.StringTypeAnnotation(node);
2381
+ case 'SymbolTypeAnnotation':
2382
+ return transform.SymbolTypeAnnotation(node);
2383
+ case 'ThisTypeAnnotation':
2384
+ return transform.ThisTypeAnnotation(node);
2385
+ case 'TupleTypeAnnotation':
2386
+ return transform.TupleTypeAnnotation(node);
2387
+ case 'TypeofTypeAnnotation':
2388
+ return transform.TypeofTypeAnnotation(node);
2389
+ case 'UnionTypeAnnotation':
2390
+ return transform.UnionTypeAnnotation(node);
2391
+ case 'VoidTypeAnnotation':
2392
+ return transform.VoidTypeAnnotation(node);
2393
+ default:
2394
+ throw unexpectedTranslationError(node, `Unhandled type ${node.type}`);
2395
+ }
2396
+ },
2397
+ TypeofTypeAnnotation(
2398
+ node: FlowESTree.TypeofTypeAnnotation,
2399
+ ): TSESTree.TSTypeQuery {
2400
+ const argument = transform.TypeAnnotationType(node.argument);
2401
+ if (argument.type !== 'TSTypeReference') {
2402
+ throw unexpectedTranslationError(
2403
+ node,
2404
+ `Expected to find a type reference as the argument to the TypeofTypeAnnotation, but got ${node.argument.type}`,
2405
+ );
2406
+ }
2407
+
2408
+ return {
2409
+ type: 'TSTypeQuery',
2410
+ exprName: argument.typeName,
2411
+ typeParameters: argument.typeParameters,
2412
+ };
2413
+ },
2414
+ TypeParameter(node: FlowESTree.TypeParameter): TSESTree.TSTypeParameter {
2415
+ /*
2416
+ TODO - flow models variance as explicit syntax, but but TS resolves it automatically
2417
+ TS does have syntax for explicit variance, but you can introduce a TS error if the
2418
+ marked parameter isn't used in the location that TS expects them to be in.
2419
+
2420
+ To make it easier for now let's just rely on TS's auto-resolution.
2421
+
2422
+ ```
2423
+ const variance =
2424
+ new Set(
2425
+ node.variance == null
2426
+ ? // by default flow generics act invariantly
2427
+ ['in', 'out']
2428
+ : node.variance.kind === 'plus'
2429
+ ? // covariant
2430
+ ['out']
2431
+ : // contravariant
2432
+ ['in'],
2433
+ );
2434
+ ```
2435
+ */
2436
+ return {
2437
+ type: 'TSTypeParameter',
2438
+ name: {
2439
+ type: 'Identifier',
2440
+ name: node.name,
2441
+ },
2442
+ constraint:
2443
+ node.bound == null
2444
+ ? undefined
2445
+ : transform.TypeAnnotationType(node.bound.typeAnnotation),
2446
+ default:
2447
+ node.default == null
2448
+ ? undefined
2449
+ : transform.TypeAnnotationType(node.default),
2450
+ in: false,
2451
+ out: false,
2452
+ // in: variance.has('in'),
2453
+ // out: variance.has('out'),
2454
+ };
2455
+ },
2456
+ TypeParameterDeclaration(
2457
+ node: FlowESTree.TypeParameterDeclaration,
2458
+ ): TSESTree.TSTypeParameterDeclaration {
2459
+ return {
2460
+ type: 'TSTypeParameterDeclaration',
2461
+ params: node.params.map(transform.TypeParameter),
2462
+ };
2463
+ },
2464
+ TypeParameterInstantiation(
2465
+ node: FlowESTree.TypeParameterInstantiation,
2466
+ ): TSESTree.TSTypeParameterInstantiation {
2467
+ return {
2468
+ type: 'TSTypeParameterInstantiation',
2469
+ params: node.params.map(transform.TypeAnnotationType),
2470
+ };
2471
+ },
2472
+ UnionTypeAnnotation(
2473
+ node: FlowESTree.UnionTypeAnnotation,
2474
+ ): TSESTree.TSUnionType {
2475
+ return {
2476
+ type: 'TSUnionType',
2477
+ types: node.types.map(transform.TypeAnnotationType),
2478
+ };
2479
+ },
2480
+ VoidTypeAnnotation(
2481
+ _node: FlowESTree.VoidTypeAnnotation,
2482
+ ): TSESTree.TSVoidKeyword {
2483
+ return {
2484
+ type: 'TSVoidKeyword',
2485
+ };
2486
+ },
2487
+ };
2488
+
2489
+ // wrap each transform so that we automatically preserve jsdoc comments
2490
+ // this just saves us manually wiring up every single case
2491
+ for (const key of Object.keys(transform)) {
2492
+ const originalFn: $FlowFixMe = transform[key];
2493
+ // $FlowExpectedError[cannot-write]
2494
+ // $FlowExpectedError[missing-local-annot]
2495
+ transform[key] = (node, ...args) => {
2496
+ const result = originalFn(node, ...args);
2497
+ if (Array.isArray(result)) {
2498
+ cloneJSDocCommentsToNewNode(node, result[0]);
2499
+ } else {
2500
+ cloneJSDocCommentsToNewNode(node, result);
2501
+ }
2502
+ return result;
2503
+ };
2504
+ }
2505
+
2506
+ return transform;
2507
+ };