flow-api-translator 0.10.1 → 0.11.1

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.
@@ -13,25 +13,44 @@
13
13
  import type {ObjectWithLoc} from 'hermes-estree';
14
14
  import * as FlowESTree from 'hermes-estree';
15
15
  import type {ScopeManager} from 'hermes-eslint';
16
- import {cloneJSDocCommentsToNewNode as cloneJSDocCommentsToNewNodeOriginal} from 'hermes-transform';
16
+ import {
17
+ cloneJSDocCommentsToNewNode as cloneJSDocCommentsToNewNodeOriginal,
18
+ makeCommentOwnLine as makeCommentOwnLineOriginal,
19
+ } from 'hermes-transform';
17
20
  import * as TSESTree from './utils/ts-estree-ast-types';
18
21
  import {
22
+ buildCodeFrame,
19
23
  translationError as translationErrorBase,
20
24
  unexpectedTranslationError as unexpectedTranslationErrorBase,
21
25
  } from './utils/ErrorUtils';
22
26
  import {removeAtFlowFromDocblock} from './utils/DocblockUtils';
27
+ import type {TranslationOptions} from './utils/TranslationUtils';
28
+ import {EOL} from 'os';
29
+
30
+ type DeclarationOrUnsupported<T> = T | TSESTree.TSTypeAliasDeclaration;
23
31
 
24
32
  const cloneJSDocCommentsToNewNode =
25
33
  // $FlowExpectedError[incompatible-cast] - trust me this re-type is 100% safe
26
34
  (cloneJSDocCommentsToNewNodeOriginal: (mixed, mixed) => void);
27
35
 
36
+ const makeCommentOwnLine =
37
+ // $FlowExpectedError[incompatible-cast] - trust me this re-type is 100% safe
38
+ (makeCommentOwnLineOriginal: (string, mixed) => string);
39
+
28
40
  const VALID_REACT_IMPORTS = new Set<string>(['React', 'react']);
29
41
 
42
+ function isValidReactImportOrGlobal(id: FlowESTree.Identifier): boolean {
43
+ return VALID_REACT_IMPORTS.has(id.name) || id.name.startsWith('React$');
44
+ }
45
+
46
+ let shouldAddReactImport: boolean | null = null;
47
+
30
48
  export function flowDefToTSDef(
31
49
  originalCode: string,
32
50
  ast: FlowESTree.Program,
33
51
  scopeManager: ScopeManager,
34
- ): TSESTree.Program {
52
+ opts: TranslationOptions,
53
+ ): [TSESTree.Program, string] {
35
54
  const tsBody: Array<TSESTree.ProgramStatement> = [];
36
55
  const tsProgram: TSESTree.Program = {
37
56
  type: 'Program',
@@ -41,7 +60,9 @@ export function flowDefToTSDef(
41
60
  ast.docblock == null ? null : removeAtFlowFromDocblock(ast.docblock),
42
61
  };
43
62
 
44
- const transform = getTransforms(originalCode, scopeManager);
63
+ shouldAddReactImport = null;
64
+
65
+ const [transform, code] = getTransforms(originalCode, scopeManager, opts);
45
66
 
46
67
  for (const node of ast.body) {
47
68
  if (node.type in transform) {
@@ -58,26 +79,117 @@ export function flowDefToTSDef(
58
79
  throw unexpectedTranslationErrorBase(
59
80
  node,
60
81
  `Unexpected node type ${node.type}`,
61
- {code: originalCode},
82
+ {code},
62
83
  );
63
84
  }
64
85
  }
65
86
 
66
- return tsProgram;
87
+ if (shouldAddReactImport === true) {
88
+ tsBody.unshift({
89
+ type: 'ImportDeclaration',
90
+ assertions: [],
91
+ source: {
92
+ type: 'Literal',
93
+ value: 'react',
94
+ raw: "'react'",
95
+ },
96
+ specifiers: [
97
+ {
98
+ type: 'ImportNamespaceSpecifier',
99
+ local: {
100
+ type: 'Identifier',
101
+ name: 'React',
102
+ },
103
+ },
104
+ ],
105
+ importKind: 'value',
106
+ });
107
+ }
108
+
109
+ return [tsProgram, code];
67
110
  }
68
111
 
69
- const getTransforms = (code: string, scopeManager: ScopeManager) => {
112
+ const getTransforms = (
113
+ originalCode: string,
114
+ scopeManager: ScopeManager,
115
+ opts: TranslationOptions,
116
+ ) => {
117
+ let code = originalCode;
70
118
  function translationError(node: ObjectWithLoc, message: string) {
71
119
  return translationErrorBase(node, message, {code});
72
120
  }
73
121
  function unexpectedTranslationError(node: ObjectWithLoc, message: string) {
74
122
  return unexpectedTranslationErrorBase(node, message, {code});
75
123
  }
76
- function unsupportedTranslationError(node: ObjectWithLoc, thing: string) {
77
- return translationError(
78
- node,
79
- `Unsupported feature: Translating "${thing}" is currently not supported.`,
80
- );
124
+ function unsupportedFeatureMessage(thing: string) {
125
+ return `Unsupported feature: Translating "${thing}" is currently not supported.`;
126
+ }
127
+ function buildCodeFrameForComment(node: ObjectWithLoc, message: string) {
128
+ return buildCodeFrame(node, message, code, false);
129
+ }
130
+ function addErrorComment(node: TSESTree.Node, message: string): void {
131
+ const comment = {
132
+ type: 'Block',
133
+ value: `*${EOL} * ${message.replace(
134
+ new RegExp(EOL, 'g'),
135
+ `${EOL} * `,
136
+ )}${EOL}*`,
137
+ leading: true,
138
+ printed: false,
139
+ };
140
+
141
+ code = makeCommentOwnLine(code, comment);
142
+
143
+ // $FlowExpectedError[prop-missing]
144
+ // $FlowExpectedError[cannot-write]
145
+ node.comments ??= [];
146
+ // $FlowExpectedError[prop-missing]
147
+ // $FlowExpectedError[incompatible-cast]
148
+ (node.comments: Array<TSESTree.Comment>).push(comment);
149
+ }
150
+ function unsupportedAnnotation(
151
+ node: ObjectWithLoc,
152
+ thing: string,
153
+ ): TSESTree.TSAnyKeyword {
154
+ const message = unsupportedFeatureMessage(thing);
155
+ if (opts.recoverFromErrors) {
156
+ const codeFrame = buildCodeFrameForComment(node, message);
157
+ const newNode = {
158
+ type: 'TSAnyKeyword',
159
+ };
160
+ addErrorComment(newNode, codeFrame);
161
+ return newNode;
162
+ }
163
+
164
+ throw translationError(node, message);
165
+ }
166
+ function unsupportedDeclaration(
167
+ node: ObjectWithLoc,
168
+ thing: string,
169
+ id: FlowESTree.Identifier,
170
+ declare: boolean = false,
171
+ typeParameters: FlowESTree.TypeParameterDeclaration | null = null,
172
+ ): TSESTree.TSTypeAliasDeclaration {
173
+ const message = unsupportedFeatureMessage(thing);
174
+ if (opts.recoverFromErrors) {
175
+ const codeFrame = buildCodeFrameForComment(node, message);
176
+ const newNode = {
177
+ type: 'TSTypeAliasDeclaration',
178
+ declare,
179
+ id: transform.Identifier(id, false),
180
+ typeAnnotation: {
181
+ type: 'TSAnyKeyword',
182
+ },
183
+ typeParameters:
184
+ typeParameters == null
185
+ ? undefined
186
+ : transform.TypeParameterDeclaration(typeParameters),
187
+ };
188
+ addErrorComment(newNode, codeFrame);
189
+ return newNode;
190
+ }
191
+
192
+ throw translationError(node, message);
81
193
  }
82
194
 
83
195
  const topScope = (() => {
@@ -92,7 +204,20 @@ const getTransforms = (code: string, scopeManager: ScopeManager) => {
92
204
  })();
93
205
 
94
206
  function isReactImport(id: FlowESTree.Identifier): boolean {
95
- let currentScope = scopeManager.acquire(id);
207
+ let currentScope = (() => {
208
+ let scope = null;
209
+ let node: FlowESTree.ESNode = id;
210
+ while (!scope && node) {
211
+ scope = scopeManager.acquire(node, true);
212
+ node = node.parent;
213
+ }
214
+
215
+ return scope;
216
+ })();
217
+
218
+ if (currentScope == null) {
219
+ throw new Error('unable to resolve scope');
220
+ }
96
221
 
97
222
  const variableDef = (() => {
98
223
  while (currentScope != null) {
@@ -105,9 +230,10 @@ const getTransforms = (code: string, scopeManager: ScopeManager) => {
105
230
  }
106
231
  })();
107
232
 
108
- // No variable found, it must be global. Using the `React` variable is enough in this case.
233
+ // No variable found, it is not imported.
234
+ // It could be a global though if isValidReactImportOrGlobal returns true.
109
235
  if (variableDef == null) {
110
- return VALID_REACT_IMPORTS.has(id.name) || id.name.startsWith('React$');
236
+ return false;
111
237
  }
112
238
 
113
239
  const def = variableDef.defs[0];
@@ -138,6 +264,407 @@ const getTransforms = (code: string, scopeManager: ScopeManager) => {
138
264
  return false;
139
265
  }
140
266
 
267
+ function EnumImpl(
268
+ node: FlowESTree.EnumDeclaration | FlowESTree.DeclareEnum,
269
+ ): DeclarationOrUnsupported<
270
+ [TSESTree.TSEnumDeclaration, TSESTree.TSModuleDeclaration],
271
+ > {
272
+ const body = node.body;
273
+ if (body.type === 'EnumSymbolBody') {
274
+ /*
275
+ There's unfortunately no way for us to support this in a clean way.
276
+ We can get really close using this code:
277
+ ```
278
+ declare namespace SymbolEnum {
279
+ export const member1: unique symbol;
280
+ export type member1 = typeof member1;
281
+
282
+ export const member2: unique symbol;
283
+ export type member2 = typeof member2;
284
+ }
285
+ type SymbolEnum = typeof SymbolEnum[keyof typeof SymbolEnum];
286
+ ```
287
+
288
+ However as explained in https://github.com/microsoft/TypeScript/issues/43657:
289
+ "A unique symbol type is never transferred from one declaration to another through inference."
290
+ This intended behaviour in TS means that the usage of the fake-enum would look like this:
291
+ ```
292
+ const value: SymbolEnum.member1 = SymbolEnum.member1;
293
+ // ^^^^^^^^^^^^^^^^^^ required to force TS to retain the information
294
+ ```
295
+ Which is really clunky and shitty. It definitely works, but ofc it's not good.
296
+ We can go with this design if users are okay with it!
297
+
298
+ Considering how rarely used symbol enums are ATM, let's just put a pin in it for now.
299
+ */
300
+ return unsupportedDeclaration(
301
+ node,
302
+ 'symbol enums',
303
+ node.id,
304
+ FlowESTree.isDeclareEnum(node),
305
+ );
306
+ }
307
+ if (body.type === 'EnumBooleanBody') {
308
+ /*
309
+ TODO - TS enums only allow strings or numbers as their values - not booleans.
310
+ This means we need a non-ts-enum representation of the enum.
311
+ We can support boolean enums using a construct like this:
312
+ ```ts
313
+ declare namespace BooleanEnum {
314
+ export const member1: true;
315
+ export type member1 = typeof member1;
316
+
317
+ export const member2: false;
318
+ export type member2 = typeof member1;
319
+ }
320
+ declare type BooleanEnum = boolean;
321
+ ```
322
+
323
+ But it's pretty clunky and ugly.
324
+ Considering how rarely used boolean enums are ATM, let's just put a pin in it for now.
325
+ */
326
+ return unsupportedDeclaration(
327
+ node,
328
+ 'boolean enums',
329
+ node.id,
330
+ FlowESTree.isDeclareEnum(node),
331
+ );
332
+ }
333
+
334
+ const members: Array<TSESTree.TSEnumMemberNonComputedName> = [];
335
+ for (const member of body.members) {
336
+ switch (member.type) {
337
+ case 'EnumDefaultedMember': {
338
+ if (body.type === 'EnumNumberBody') {
339
+ // this should be impossible!
340
+ throw unexpectedTranslationError(
341
+ member,
342
+ 'Unexpected defaulted number enum member',
343
+ );
344
+ }
345
+ members.push({
346
+ type: 'TSEnumMember',
347
+ computed: false,
348
+ id: transform.Identifier(member.id, false),
349
+ initializer: ({
350
+ type: 'Literal',
351
+ raw: `"${member.id.name}"`,
352
+ value: member.id.name,
353
+ }: TSESTree.StringLiteral),
354
+ });
355
+ break;
356
+ }
357
+
358
+ case 'EnumNumberMember':
359
+ case 'EnumStringMember':
360
+ members.push({
361
+ type: 'TSEnumMember',
362
+ computed: false,
363
+ id: transform.Identifier(member.id, false),
364
+ initializer:
365
+ member.init.literalType === 'string'
366
+ ? transform.StringLiteral(member.init)
367
+ : transform.NumericLiteral(member.init),
368
+ });
369
+ }
370
+ }
371
+
372
+ const bodyRepresentationType =
373
+ body.type === 'EnumNumberBody'
374
+ ? {type: 'TSNumberKeyword'}
375
+ : {type: 'TSStringKeyword'};
376
+
377
+ const enumName = transform.Identifier(node.id, false);
378
+ return [
379
+ {
380
+ type: 'TSEnumDeclaration',
381
+ const: false,
382
+ declare: true,
383
+ id: enumName,
384
+ members,
385
+ },
386
+ // flow also exports `.cast`, `.isValid`, `.members` and `.getName` for enums
387
+ // we can use declaration merging to declare these functions on the enum:
388
+ /*
389
+ declare enum Foo {
390
+ A = 1,
391
+ B = 2,
392
+ }
393
+ declare namespace Foo {
394
+ export function cast(value: number | null | undefined): Foo;
395
+ export function isValid(value: number | null | undefined): value is Foo;
396
+ export function members(): IterableIterator<Foo>;
397
+ export function getName(value: Foo): string;
398
+ }
399
+ */
400
+ {
401
+ type: 'TSModuleDeclaration',
402
+ declare: true,
403
+ id: enumName,
404
+ body: {
405
+ type: 'TSModuleBlock',
406
+ body: [
407
+ // export function cast(value: number | null | undefined): Foo
408
+ {
409
+ type: 'ExportNamedDeclaration',
410
+ declaration: {
411
+ type: 'TSDeclareFunction',
412
+ id: {
413
+ type: 'Identifier',
414
+ name: 'cast',
415
+ },
416
+ generator: false,
417
+ expression: false,
418
+ async: false,
419
+ params: [
420
+ {
421
+ type: 'Identifier',
422
+ name: 'value',
423
+ typeAnnotation: {
424
+ type: 'TSTypeAnnotation',
425
+ typeAnnotation: {
426
+ type: 'TSUnionType',
427
+ types: [
428
+ bodyRepresentationType,
429
+ {
430
+ type: 'TSNullKeyword',
431
+ },
432
+ {
433
+ type: 'TSUndefinedKeyword',
434
+ },
435
+ ],
436
+ },
437
+ },
438
+ },
439
+ ],
440
+ returnType: {
441
+ type: 'TSTypeAnnotation',
442
+ typeAnnotation: {
443
+ type: 'TSTypeReference',
444
+ typeName: enumName,
445
+ },
446
+ },
447
+ },
448
+ specifiers: [],
449
+ source: null,
450
+ exportKind: 'value',
451
+ assertions: [],
452
+ },
453
+ // export function isValid(value: number | null | undefined): value is Foo;
454
+ {
455
+ type: 'ExportNamedDeclaration',
456
+ declaration: {
457
+ type: 'TSDeclareFunction',
458
+ id: {
459
+ type: 'Identifier',
460
+ name: 'isValid',
461
+ },
462
+ generator: false,
463
+ expression: false,
464
+ async: false,
465
+ params: [
466
+ {
467
+ type: 'Identifier',
468
+ name: 'value',
469
+ typeAnnotation: {
470
+ type: 'TSTypeAnnotation',
471
+ typeAnnotation: {
472
+ type: 'TSUnionType',
473
+ types: [
474
+ bodyRepresentationType,
475
+ {
476
+ type: 'TSNullKeyword',
477
+ },
478
+ {
479
+ type: 'TSUndefinedKeyword',
480
+ },
481
+ ],
482
+ },
483
+ },
484
+ },
485
+ ],
486
+ returnType: {
487
+ type: 'TSTypeAnnotation',
488
+ typeAnnotation: {
489
+ type: 'TSTypePredicate',
490
+ asserts: false,
491
+ parameterName: {
492
+ type: 'Identifier',
493
+ name: 'value',
494
+ },
495
+ typeAnnotation: {
496
+ type: 'TSTypeAnnotation',
497
+ typeAnnotation: {
498
+ type: 'TSTypeReference',
499
+ typeName: enumName,
500
+ },
501
+ },
502
+ },
503
+ },
504
+ },
505
+ specifiers: [],
506
+ source: null,
507
+ exportKind: 'value',
508
+ assertions: [],
509
+ },
510
+ // export function members(): IterableIterator<Foo>;
511
+ {
512
+ type: 'ExportNamedDeclaration',
513
+ declaration: {
514
+ type: 'TSDeclareFunction',
515
+ id: {
516
+ type: 'Identifier',
517
+ name: 'members',
518
+ },
519
+ generator: false,
520
+ expression: false,
521
+ async: false,
522
+ params: [],
523
+ returnType: {
524
+ type: 'TSTypeAnnotation',
525
+ typeAnnotation: {
526
+ type: 'TSTypeReference',
527
+ typeName: {
528
+ type: 'Identifier',
529
+ name: 'IterableIterator',
530
+ },
531
+ typeParameters: {
532
+ type: 'TSTypeParameterInstantiation',
533
+ params: [
534
+ {
535
+ type: 'TSTypeReference',
536
+ typeName: enumName,
537
+ },
538
+ ],
539
+ },
540
+ },
541
+ },
542
+ },
543
+ specifiers: [],
544
+ source: null,
545
+ exportKind: 'value',
546
+ assertions: [],
547
+ },
548
+ // export function getName(value: Foo): string;
549
+ {
550
+ type: 'ExportNamedDeclaration',
551
+ declaration: {
552
+ type: 'TSDeclareFunction',
553
+ id: {
554
+ type: 'Identifier',
555
+ name: 'getName',
556
+ },
557
+ generator: false,
558
+ expression: false,
559
+ async: false,
560
+ params: [
561
+ {
562
+ type: 'Identifier',
563
+ name: 'value',
564
+ typeAnnotation: {
565
+ type: 'TSTypeAnnotation',
566
+ typeAnnotation: {
567
+ type: 'TSTypeReference',
568
+ typeName: enumName,
569
+ },
570
+ },
571
+ },
572
+ ],
573
+ returnType: {
574
+ type: 'TSTypeAnnotation',
575
+ typeAnnotation: {
576
+ type: 'TSStringKeyword',
577
+ },
578
+ },
579
+ },
580
+ specifiers: [],
581
+ source: null,
582
+ exportKind: 'value',
583
+ assertions: [],
584
+ },
585
+ ],
586
+ },
587
+ },
588
+ ];
589
+ }
590
+
591
+ const getPlaceholderNameForTypeofImport: () => string = (() => {
592
+ let typeof_import_count = 0;
593
+ return () => `$$IMPORT_TYPEOF_${++typeof_import_count}$$`;
594
+ })();
595
+
596
+ const transformTypeAnnotationType = (
597
+ node: FlowESTree.TypeAnnotationType,
598
+ ): TSESTree.TypeNode => {
599
+ switch (node.type) {
600
+ case 'AnyTypeAnnotation':
601
+ return transform.AnyTypeAnnotation(node);
602
+ case 'ArrayTypeAnnotation':
603
+ return transform.ArrayTypeAnnotation(node);
604
+ case 'BigIntLiteralTypeAnnotation':
605
+ return transform.BigIntLiteralTypeAnnotation(node);
606
+ case 'BigIntTypeAnnotation':
607
+ return transform.BigIntTypeAnnotation(node);
608
+ case 'BooleanLiteralTypeAnnotation':
609
+ return transform.BooleanLiteralTypeAnnotation(node);
610
+ case 'BooleanTypeAnnotation':
611
+ return transform.BooleanTypeAnnotation(node);
612
+ case 'EmptyTypeAnnotation':
613
+ return transform.EmptyTypeAnnotation(node);
614
+ case 'ExistsTypeAnnotation':
615
+ return transform.ExistsTypeAnnotation(node);
616
+ case 'FunctionTypeAnnotation':
617
+ return transform.FunctionTypeAnnotation(node);
618
+ case 'GenericTypeAnnotation':
619
+ return transform.GenericTypeAnnotation(node);
620
+ case 'IndexedAccessType':
621
+ return transform.IndexedAccessType(node);
622
+ case 'InterfaceTypeAnnotation':
623
+ return transform.InterfaceTypeAnnotation(node);
624
+ case 'IntersectionTypeAnnotation':
625
+ return transform.IntersectionTypeAnnotation(node);
626
+ case 'MixedTypeAnnotation':
627
+ return transform.MixedTypeAnnotation(node);
628
+ case 'NullLiteralTypeAnnotation':
629
+ return transform.NullLiteralTypeAnnotation(node);
630
+ case 'NullableTypeAnnotation':
631
+ return transform.NullableTypeAnnotation(node);
632
+ case 'NumberLiteralTypeAnnotation':
633
+ return transform.NumberLiteralTypeAnnotation(node);
634
+ case 'NumberTypeAnnotation':
635
+ return transform.NumberTypeAnnotation(node);
636
+ case 'ObjectTypeAnnotation':
637
+ return transform.ObjectTypeAnnotation(node);
638
+ case 'OptionalIndexedAccessType':
639
+ return transform.OptionalIndexedAccessType(node);
640
+ case 'QualifiedTypeIdentifier':
641
+ return transform.QualifiedTypeIdentifier(node);
642
+ case 'StringLiteralTypeAnnotation':
643
+ return transform.StringLiteralTypeAnnotation(node);
644
+ case 'StringTypeAnnotation':
645
+ return transform.StringTypeAnnotation(node);
646
+ case 'SymbolTypeAnnotation':
647
+ return transform.SymbolTypeAnnotation(node);
648
+ case 'ThisTypeAnnotation':
649
+ return transform.ThisTypeAnnotation(node);
650
+ case 'TupleTypeAnnotation':
651
+ return transform.TupleTypeAnnotation(node);
652
+ case 'TupleTypeLabeledElement':
653
+ case 'TupleTypeSpreadElement':
654
+ return unsupportedAnnotation(node, node.type);
655
+ case 'TypeofTypeAnnotation':
656
+ return transform.TypeofTypeAnnotation(node);
657
+ case 'UnionTypeAnnotation':
658
+ return transform.UnionTypeAnnotation(node);
659
+ case 'VoidTypeAnnotation':
660
+ return transform.VoidTypeAnnotation(node);
661
+ case 'TypePredicate':
662
+ return unsupportedAnnotation(node, node.type);
663
+ default:
664
+ throw unexpectedTranslationError(node, `Unhandled type ${node.type}`);
665
+ }
666
+ };
667
+
141
668
  const transform = {
142
669
  AnyTypeAnnotation(
143
670
  _node: FlowESTree.AnyTypeAnnotation,
@@ -151,7 +678,7 @@ const getTransforms = (code: string, scopeManager: ScopeManager) => {
151
678
  ): TSESTree.TSArrayType {
152
679
  return {
153
680
  type: 'TSArrayType',
154
- elementType: transform.TypeAnnotationType(node.elementType),
681
+ elementType: transformTypeAnnotationType(node.elementType),
155
682
  };
156
683
  },
157
684
  BigIntLiteral(node: FlowESTree.BigIntLiteral): TSESTree.BigIntLiteral {
@@ -233,13 +760,16 @@ const getTransforms = (code: string, scopeManager: ScopeManager) => {
233
760
  },
234
761
  DeclareClass(
235
762
  node: FlowESTree.DeclareClass,
236
- ): TSESTree.ClassDeclarationWithName {
763
+ ): DeclarationOrUnsupported<TSESTree.ClassDeclarationWithName> {
237
764
  const classMembers: Array<TSESTree.ClassElement> = [];
238
765
  const transformedBody = transform.ObjectTypeAnnotation(node.body);
239
766
  if (transformedBody.type !== 'TSTypeLiteral') {
240
- throw translationError(
767
+ return unsupportedDeclaration(
241
768
  node.body,
242
769
  'Spreads in declare class are not allowed',
770
+ node.id,
771
+ true,
772
+ node.typeParameters,
243
773
  );
244
774
  }
245
775
  for (const member of transformedBody.members) {
@@ -359,9 +889,12 @@ const getTransforms = (code: string, scopeManager: ScopeManager) => {
359
889
  ```
360
890
  Let's put a pin in it for now and deal with it later if the need arises.
361
891
  */
362
- throw unsupportedTranslationError(
892
+ return unsupportedDeclaration(
363
893
  node.body.callProperties[0] ?? node.body,
364
894
  'call signatures on classes',
895
+ node.id,
896
+ true,
897
+ node.typeParameters,
365
898
  );
366
899
  }
367
900
 
@@ -406,12 +939,14 @@ const getTransforms = (code: string, scopeManager: ScopeManager) => {
406
939
  node: FlowESTree.DeclareExportDeclaration,
407
940
  ):
408
941
  | TSESTree.ExportNamedDeclaration
942
+ | Array<TSESTree.ExportNamedDeclaration>
409
943
  | TSESTree.ExportDefaultDeclaration
410
944
  | [
411
945
  (
412
946
  | TSESTree.VariableDeclaration
413
947
  | TSESTree.ClassDeclaration
414
948
  | TSESTree.TSDeclareFunction
949
+ | TSESTree.TSTypeAliasDeclaration
415
950
  ),
416
951
  TSESTree.ExportDefaultDeclaration,
417
952
  ] {
@@ -543,7 +1078,7 @@ const getTransforms = (code: string, scopeManager: ScopeManager) => {
543
1078
  typeAnnotation: {
544
1079
  type: 'TSTypeAnnotation',
545
1080
  typeAnnotation:
546
- transform.TypeAnnotationType(declaration),
1081
+ transformTypeAnnotationType(declaration),
547
1082
  },
548
1083
  },
549
1084
  init: null,
@@ -580,45 +1115,73 @@ const getTransforms = (code: string, scopeManager: ScopeManager) => {
580
1115
  }: TSESTree.ExportNamedDeclarationWithoutSourceWithMultiple);
581
1116
  }
582
1117
 
583
- const {declaration, exportKind} = (() => {
1118
+ const declarations = (() => {
584
1119
  switch (node.declaration.type) {
585
1120
  case 'DeclareClass':
586
- return {
587
- declaration: transform.DeclareClass(node.declaration),
588
- exportKind: 'value',
589
- };
1121
+ return [
1122
+ {
1123
+ declaration: transform.DeclareClass(node.declaration),
1124
+ exportKind: 'value',
1125
+ },
1126
+ ];
590
1127
  case 'DeclareFunction':
591
- return {
592
- declaration: transform.DeclareFunction(node.declaration),
593
- exportKind: 'value',
594
- };
1128
+ return [
1129
+ {
1130
+ declaration: transform.DeclareFunction(node.declaration),
1131
+ exportKind: 'value',
1132
+ },
1133
+ ];
595
1134
  case 'DeclareInterface':
596
- return {
597
- declaration: transform.DeclareInterface(node.declaration),
598
- exportKind: 'type',
599
- };
1135
+ return [
1136
+ {
1137
+ declaration: transform.DeclareInterface(node.declaration),
1138
+ exportKind: 'type',
1139
+ },
1140
+ ];
600
1141
  case 'DeclareOpaqueType':
601
- return {
602
- declaration: transform.DeclareOpaqueType(node.declaration),
603
- exportKind: 'type',
604
- };
1142
+ return [
1143
+ {
1144
+ declaration: transform.DeclareOpaqueType(node.declaration),
1145
+ exportKind: 'type',
1146
+ },
1147
+ ];
605
1148
  case 'DeclareVariable':
606
- return {
607
- declaration: transform.DeclareVariable(node.declaration),
608
- exportKind: 'value',
609
- };
1149
+ return [
1150
+ {
1151
+ declaration: transform.DeclareVariable(node.declaration),
1152
+ exportKind: 'value',
1153
+ },
1154
+ ];
1155
+ case 'DeclareEnum': {
1156
+ const result = transform.DeclareEnum(node.declaration);
1157
+ return Array.isArray(result)
1158
+ ? [
1159
+ {
1160
+ declaration: result[0],
1161
+ exportKind: 'type',
1162
+ },
1163
+ {
1164
+ declaration: result[1],
1165
+ exportKind: 'type',
1166
+ },
1167
+ ]
1168
+ : [{declaration: result, exportKind: 'type'}];
1169
+ }
610
1170
  }
611
1171
  })();
612
1172
 
613
- return ({
614
- type: 'ExportNamedDeclaration',
615
- // flow does not currently support assertions
616
- assertions: [],
617
- declaration,
618
- exportKind,
619
- source: null,
620
- specifiers: [],
621
- }: TSESTree.ExportNamedDeclarationWithoutSourceWithSingle);
1173
+ return declarations.map(
1174
+ ({declaration, exportKind}) =>
1175
+ ({
1176
+ type: 'ExportNamedDeclaration',
1177
+ // flow does not currently support assertions
1178
+ assertions: [],
1179
+ declaration,
1180
+ exportKind,
1181
+ source: null,
1182
+ specifiers: [],
1183
+ }: TSESTree.ExportNamedDeclarationWithoutSourceWithSingle),
1184
+ );
622
1185
  } else {
623
1186
  return ({
624
1187
  type: 'ExportNamedDeclaration',
@@ -691,7 +1254,7 @@ const getTransforms = (code: string, scopeManager: ScopeManager) => {
691
1254
  type: 'TSTypeAliasDeclaration',
692
1255
  declare: node.type === 'DeclareTypeAlias',
693
1256
  id: transform.Identifier(node.id, false),
694
- typeAnnotation: transform.TypeAnnotationType(node.right),
1257
+ typeAnnotation: transformTypeAnnotationType(node.right),
695
1258
  typeParameters:
696
1259
  node.typeParameters == null
697
1260
  ? undefined
@@ -710,353 +1273,56 @@ const getTransforms = (code: string, scopeManager: ScopeManager) => {
710
1273
  declare: true,
711
1274
  id: transform.Identifier(node.id, false),
712
1275
  typeAnnotation:
713
- node.supertype == null
714
- ? {
715
- type: 'TSUnknownKeyword',
716
- }
717
- : transform.TypeAnnotationType(node.supertype),
718
- typeParameters:
719
- node.typeParameters == null
720
- ? undefined
721
- : transform.TypeParameterDeclaration(node.typeParameters),
722
- };
723
- },
724
- DeclareVariable(
725
- node: FlowESTree.DeclareVariable,
726
- ): TSESTree.VariableDeclaration {
727
- return {
728
- type: 'VariableDeclaration',
729
- declare: true,
730
- declarations: [
731
- {
732
- type: 'VariableDeclarator',
733
- declare: true,
734
- id: transform.Identifier(node.id, true),
735
- init: null,
736
- },
737
- ],
738
- kind: 'var',
739
- };
740
- },
741
- EmptyTypeAnnotation(
742
- node: FlowESTree.EmptyTypeAnnotation,
743
- ): TSESTree.TypeNode {
744
- // Flow's `empty` type doesn't map well to any types in TS.
745
- // The closest is `never`, but `never` has a number of different semantics
746
- // In reality no human code should ever directly use the `empty` type in flow
747
- // So let's put a pin in it for now
748
- throw unsupportedTranslationError(node, 'empty type');
749
- },
750
- EnumDeclaration(
751
- node: FlowESTree.EnumDeclaration,
752
- ): [TSESTree.TSEnumDeclaration, TSESTree.TSModuleDeclaration] {
753
- const body = node.body;
754
- if (body.type === 'EnumSymbolBody') {
755
- /*
756
- There's unfortunately no way for us to support this in a clean way.
757
- We can get really close using this code:
758
- ```
759
- declare namespace SymbolEnum {
760
- export const member1: unique symbol;
761
- export type member1 = typeof member1;
762
-
763
- export const member2: unique symbol;
764
- export type member2 = typeof member2;
765
- }
766
- type SymbolEnum = typeof SymbolEnum[keyof typeof SymbolEnum];
767
- ```
768
-
769
- However as explained in https://github.com/microsoft/TypeScript/issues/43657:
770
- "A unique symbol type is never transferred from one declaration to another through inference."
771
- This intended behaviour in TS means that the usage of the fake-enum would look like this:
772
- ```
773
- const value: SymbolEnum.member1 = SymbolEnum.member1;
774
- // ^^^^^^^^^^^^^^^^^^ required to force TS to retain the information
775
- ```
776
- Which is really clunky and shitty. It definitely works, but ofc it's not good.
777
- We can go with this design if users are okay with it!
778
-
779
- Considering how rarely used symbol enums are ATM, let's just put a pin in it for now.
780
- */
781
- throw unsupportedTranslationError(node, 'symbol enums');
782
- }
783
- if (body.type === 'EnumBooleanBody') {
784
- /*
785
- TODO - TS enums only allow strings or numbers as their values - not booleans.
786
- This means we need a non-ts-enum representation of the enum.
787
- We can support boolean enums using a construct like this:
788
- ```ts
789
- declare namespace BooleanEnum {
790
- export const member1: true;
791
- export type member1 = typeof member1;
792
-
793
- export const member2: false;
794
- export type member2 = typeof member1;
795
- }
796
- declare type BooleanEnum = boolean;
797
- ```
798
-
799
- But it's pretty clunky and ugly.
800
- Considering how rarely used boolean enums are ATM, let's just put a pin in it for now.
801
- */
802
- throw unsupportedTranslationError(node, 'boolean enums');
803
- }
804
-
805
- const members: Array<TSESTree.TSEnumMemberNonComputedName> = [];
806
- for (const member of body.members) {
807
- switch (member.type) {
808
- case 'EnumDefaultedMember': {
809
- if (body.type === 'EnumNumberBody') {
810
- // this should be impossible!
811
- throw unexpectedTranslationError(
812
- member,
813
- 'Unexpected defaulted number enum member',
814
- );
815
- }
816
- members.push({
817
- type: 'TSEnumMember',
818
- computed: false,
819
- id: transform.Identifier(member.id, false),
820
- initializer: ({
821
- type: 'Literal',
822
- raw: `"${member.id.name}"`,
823
- value: member.id.name,
824
- }: TSESTree.StringLiteral),
825
- });
826
- break;
827
- }
828
-
829
- case 'EnumNumberMember':
830
- case 'EnumStringMember':
831
- members.push({
832
- type: 'TSEnumMember',
833
- computed: false,
834
- id: transform.Identifier(member.id, false),
835
- initializer:
836
- member.init.literalType === 'string'
837
- ? transform.StringLiteral(member.init)
838
- : transform.NumericLiteral(member.init),
839
- });
840
- }
841
- }
842
-
843
- const bodyRepresentationType =
844
- body.type === 'EnumNumberBody'
845
- ? {type: 'TSNumberKeyword'}
846
- : {type: 'TSStringKeyword'};
847
-
848
- const enumName = transform.Identifier(node.id, false);
849
- return [
850
- {
851
- type: 'TSEnumDeclaration',
852
- const: false,
853
- declare: true,
854
- id: enumName,
855
- members,
856
- },
857
- // flow also exports `.cast`, `.isValid`, `.members` and `.getName` for enums
858
- // we can use declaration merging to declare these functions on the enum:
859
- /*
860
- declare enum Foo {
861
- A = 1,
862
- B = 2,
863
- }
864
- declare namespace Foo {
865
- export function cast(value: number | null | undefined): Foo;
866
- export function isValid(value: number | null | undefined): value is Foo;
867
- export function members(): IterableIterator<Foo>;
868
- export function getName(value: Foo): string;
869
- }
870
- */
871
- {
872
- type: 'TSModuleDeclaration',
873
- declare: true,
874
- id: enumName,
875
- body: {
876
- type: 'TSModuleBlock',
877
- body: [
878
- // export function cast(value: number | null | undefined): Foo
879
- {
880
- type: 'ExportNamedDeclaration',
881
- declaration: {
882
- type: 'TSDeclareFunction',
883
- id: {
884
- type: 'Identifier',
885
- name: 'cast',
886
- },
887
- generator: false,
888
- expression: false,
889
- async: false,
890
- params: [
891
- {
892
- type: 'Identifier',
893
- name: 'value',
894
- typeAnnotation: {
895
- type: 'TSTypeAnnotation',
896
- typeAnnotation: {
897
- type: 'TSUnionType',
898
- types: [
899
- bodyRepresentationType,
900
- {
901
- type: 'TSNullKeyword',
902
- },
903
- {
904
- type: 'TSUndefinedKeyword',
905
- },
906
- ],
907
- },
908
- },
909
- },
910
- ],
911
- returnType: {
912
- type: 'TSTypeAnnotation',
913
- typeAnnotation: {
914
- type: 'TSTypeReference',
915
- typeName: enumName,
916
- },
917
- },
918
- },
919
- specifiers: [],
920
- source: null,
921
- exportKind: 'value',
922
- assertions: [],
923
- },
924
- // export function isValid(value: number | null | undefined): value is Foo;
925
- {
926
- type: 'ExportNamedDeclaration',
927
- declaration: {
928
- type: 'TSDeclareFunction',
929
- id: {
930
- type: 'Identifier',
931
- name: 'isValid',
932
- },
933
- generator: false,
934
- expression: false,
935
- async: false,
936
- params: [
937
- {
938
- type: 'Identifier',
939
- name: 'value',
940
- typeAnnotation: {
941
- type: 'TSTypeAnnotation',
942
- typeAnnotation: {
943
- type: 'TSUnionType',
944
- types: [
945
- bodyRepresentationType,
946
- {
947
- type: 'TSNullKeyword',
948
- },
949
- {
950
- type: 'TSUndefinedKeyword',
951
- },
952
- ],
953
- },
954
- },
955
- },
956
- ],
957
- returnType: {
958
- type: 'TSTypeAnnotation',
959
- typeAnnotation: {
960
- type: 'TSTypePredicate',
961
- asserts: false,
962
- parameterName: {
963
- type: 'Identifier',
964
- name: 'value',
965
- },
966
- typeAnnotation: {
967
- type: 'TSTypeAnnotation',
968
- typeAnnotation: {
969
- type: 'TSTypeReference',
970
- typeName: enumName,
971
- },
972
- },
973
- },
974
- },
975
- },
976
- specifiers: [],
977
- source: null,
978
- exportKind: 'value',
979
- assertions: [],
980
- },
981
- // export function members(): IterableIterator<Foo>;
982
- {
983
- type: 'ExportNamedDeclaration',
984
- declaration: {
985
- type: 'TSDeclareFunction',
986
- id: {
987
- type: 'Identifier',
988
- name: 'members',
989
- },
990
- generator: false,
991
- expression: false,
992
- async: false,
993
- params: [],
994
- returnType: {
995
- type: 'TSTypeAnnotation',
996
- typeAnnotation: {
997
- type: 'TSTypeReference',
998
- typeName: {
999
- type: 'Identifier',
1000
- name: 'IterableIterator',
1001
- },
1002
- typeParameters: {
1003
- type: 'TSTypeParameterInstantiation',
1004
- params: [
1005
- {
1006
- type: 'TSTypeReference',
1007
- typeName: enumName,
1008
- },
1009
- ],
1010
- },
1011
- },
1012
- },
1013
- },
1014
- specifiers: [],
1015
- source: null,
1016
- exportKind: 'value',
1017
- assertions: [],
1018
- },
1019
- // export function getName(value: Foo): string;
1020
- {
1021
- type: 'ExportNamedDeclaration',
1022
- declaration: {
1023
- type: 'TSDeclareFunction',
1024
- id: {
1025
- type: 'Identifier',
1026
- name: 'getName',
1027
- },
1028
- generator: false,
1029
- expression: false,
1030
- async: false,
1031
- params: [
1032
- {
1033
- type: 'Identifier',
1034
- name: 'value',
1035
- typeAnnotation: {
1036
- type: 'TSTypeAnnotation',
1037
- typeAnnotation: {
1038
- type: 'TSTypeReference',
1039
- typeName: enumName,
1040
- },
1041
- },
1042
- },
1043
- ],
1044
- returnType: {
1045
- type: 'TSTypeAnnotation',
1046
- typeAnnotation: {
1047
- type: 'TSStringKeyword',
1048
- },
1049
- },
1050
- },
1051
- specifiers: [],
1052
- source: null,
1053
- exportKind: 'value',
1054
- assertions: [],
1055
- },
1056
- ],
1276
+ node.supertype == null
1277
+ ? {
1278
+ type: 'TSUnknownKeyword',
1279
+ }
1280
+ : transformTypeAnnotationType(node.supertype),
1281
+ typeParameters:
1282
+ node.typeParameters == null
1283
+ ? undefined
1284
+ : transform.TypeParameterDeclaration(node.typeParameters),
1285
+ };
1286
+ },
1287
+ DeclareVariable(
1288
+ node: FlowESTree.DeclareVariable,
1289
+ ): TSESTree.VariableDeclaration {
1290
+ return {
1291
+ type: 'VariableDeclaration',
1292
+ declare: true,
1293
+ declarations: [
1294
+ {
1295
+ type: 'VariableDeclarator',
1296
+ declare: true,
1297
+ id: transform.Identifier(node.id, true),
1298
+ init: null,
1057
1299
  },
1058
- },
1059
- ];
1300
+ ],
1301
+ kind: node.kind,
1302
+ };
1303
+ },
1304
+ DeclareEnum(
1305
+ node: FlowESTree.DeclareEnum,
1306
+ ): DeclarationOrUnsupported<
1307
+ [TSESTree.TSEnumDeclaration, TSESTree.TSModuleDeclaration],
1308
+ > {
1309
+ return EnumImpl(node);
1310
+ },
1311
+ EmptyTypeAnnotation(
1312
+ node: FlowESTree.EmptyTypeAnnotation,
1313
+ ): TSESTree.TypeNode {
1314
+ // Flow's `empty` type doesn't map well to any types in TS.
1315
+ // The closest is `never`, but `never` has a number of different semantics
1316
+ // In reality no human code should ever directly use the `empty` type in flow
1317
+ // So let's put a pin in it for now
1318
+ return unsupportedAnnotation(node, 'empty type');
1319
+ },
1320
+ EnumDeclaration(
1321
+ node: FlowESTree.EnumDeclaration,
1322
+ ): DeclarationOrUnsupported<
1323
+ [TSESTree.TSEnumDeclaration, TSESTree.TSModuleDeclaration],
1324
+ > {
1325
+ return EnumImpl(node);
1060
1326
  },
1061
1327
  DeclareModuleExports(
1062
1328
  node: FlowESTree.DeclareModuleExports,
@@ -1068,7 +1334,7 @@ const getTransforms = (code: string, scopeManager: ScopeManager) => {
1068
1334
  ): TSESTree.TypeNode {
1069
1335
  // The existential type does not map to any types in TS
1070
1336
  // It's also super deprecated - so let's not ever worry
1071
- throw unsupportedTranslationError(node, 'exestential type');
1337
+ return unsupportedAnnotation(node, 'existential type');
1072
1338
  },
1073
1339
  ExportNamedDeclaration(
1074
1340
  node: FlowESTree.ExportNamedDeclaration,
@@ -1091,11 +1357,12 @@ const getTransforms = (code: string, scopeManager: ScopeManager) => {
1091
1357
 
1092
1358
  const [exportedDeclaration, mergedDeclaration] = (() => {
1093
1359
  if (node.declaration == null) {
1094
- return [null];
1360
+ return [null, null];
1095
1361
  }
1096
1362
 
1097
1363
  switch (node.declaration.type) {
1098
1364
  case 'ClassDeclaration':
1365
+ case 'ComponentDeclaration':
1099
1366
  case 'FunctionDeclaration':
1100
1367
  case 'VariableDeclaration':
1101
1368
  // These cases shouldn't happen in flow defs because they have their own special
@@ -1105,8 +1372,10 @@ const getTransforms = (code: string, scopeManager: ScopeManager) => {
1105
1372
  `Unexpected named declaration found ${node.declaration.type}`,
1106
1373
  );
1107
1374
 
1108
- case 'EnumDeclaration':
1109
- return transform.EnumDeclaration(node.declaration);
1375
+ case 'EnumDeclaration': {
1376
+ const result = transform.EnumDeclaration(node.declaration);
1377
+ return Array.isArray(result) ? result : [result, null];
1378
+ }
1110
1379
  case 'InterfaceDeclaration':
1111
1380
  return [transform.InterfaceDeclaration(node.declaration), null];
1112
1381
  case 'OpaqueType':
@@ -1163,7 +1432,7 @@ const getTransforms = (code: string, scopeManager: ScopeManager) => {
1163
1432
  name: 'this',
1164
1433
  typeAnnotation: {
1165
1434
  type: 'TSTypeAnnotation',
1166
- typeAnnotation: transform.TypeAnnotationType(
1435
+ typeAnnotation: transformTypeAnnotationType(
1167
1436
  node.this.typeAnnotation,
1168
1437
  ),
1169
1438
  },
@@ -1182,7 +1451,7 @@ const getTransforms = (code: string, scopeManager: ScopeManager) => {
1182
1451
  : transform.Identifier(rest.name, false),
1183
1452
  typeAnnotation: {
1184
1453
  type: 'TSTypeAnnotation',
1185
- typeAnnotation: transform.TypeAnnotationType(rest.typeAnnotation),
1454
+ typeAnnotation: transformTypeAnnotationType(rest.typeAnnotation),
1186
1455
  },
1187
1456
  });
1188
1457
  }
@@ -1192,7 +1461,7 @@ const getTransforms = (code: string, scopeManager: ScopeManager) => {
1192
1461
  params,
1193
1462
  returnType: {
1194
1463
  type: 'TSTypeAnnotation',
1195
- typeAnnotation: transform.TypeAnnotationType(node.returnType),
1464
+ typeAnnotation: transformTypeAnnotationType(node.returnType),
1196
1465
  },
1197
1466
  typeParameters:
1198
1467
  node.typeParameters == null
@@ -1209,7 +1478,7 @@ const getTransforms = (code: string, scopeManager: ScopeManager) => {
1209
1478
  name: node.name == null ? `$$PARAM_${idx}$$` : node.name.name,
1210
1479
  typeAnnotation: {
1211
1480
  type: 'TSTypeAnnotation',
1212
- typeAnnotation: transform.TypeAnnotationType(node.typeAnnotation),
1481
+ typeAnnotation: transformTypeAnnotationType(node.typeAnnotation),
1213
1482
  },
1214
1483
  optional: node.optional,
1215
1484
  };
@@ -1256,7 +1525,7 @@ const getTransforms = (code: string, scopeManager: ScopeManager) => {
1256
1525
 
1257
1526
  const res = [];
1258
1527
  for (const param of node.typeParameters.params) {
1259
- res.push(transform.TypeAnnotationType(param));
1528
+ res.push(transformTypeAnnotationType(param));
1260
1529
  }
1261
1530
  return res;
1262
1531
  }
@@ -1299,7 +1568,7 @@ const getTransforms = (code: string, scopeManager: ScopeManager) => {
1299
1568
  type PropType = ReturnType<ExtractPropType<Obj>>; // number
1300
1569
  ```
1301
1570
  */
1302
- throw unsupportedTranslationError(node, fullTypeName);
1571
+ return unsupportedAnnotation(node, fullTypeName);
1303
1572
  }
1304
1573
 
1305
1574
  case '$Diff':
@@ -1547,20 +1816,95 @@ const getTransforms = (code: string, scopeManager: ScopeManager) => {
1547
1816
  }
1548
1817
 
1549
1818
  // React special conversion:
1550
- if (isReactImport(baseId)) {
1551
- const reactId = {
1552
- type: 'Identifier',
1553
- name: `React`,
1819
+ const validReactImportOrGlobal = isValidReactImportOrGlobal(baseId);
1820
+ const reactImport = isReactImport(baseId);
1821
+ if (validReactImportOrGlobal || reactImport) {
1822
+ // Returns appropriate Identifier for `React` import.
1823
+ // If a global is in use, set a flag to indicate that we should add the import.
1824
+ const getReactIdentifier = () => {
1825
+ if (shouldAddReactImport !== false) {
1826
+ shouldAddReactImport = !reactImport;
1827
+ }
1828
+
1829
+ return {
1830
+ type: 'Identifier',
1831
+ name: `React`,
1832
+ };
1554
1833
  };
1555
1834
 
1556
1835
  switch (fullTypeName) {
1836
+ // TODO: In flow this is `ChildrenArray<T> = T | $ReadOnlyArray<ChildrenArray<T>>`.
1837
+ // The recursive nature of it is rarely needed, so we're simplifying this for now
1838
+ // but omitting that aspect. Once we're able to provide utility types for our translations,
1839
+ // we should update this.
1840
+ // React.ChildrenArray<T> -> T | ReadonlyArray<T>
1841
+ // React$ChildrenArray<T> -> T | ReadonlyArray<T>
1842
+ case 'React.ChildrenArray':
1843
+ case 'React$ChildrenArray': {
1844
+ const [param] = assertHasExactlyNTypeParameters(1);
1845
+ return {
1846
+ type: 'TSUnionType',
1847
+ types: [
1848
+ param,
1849
+ {
1850
+ type: 'TSTypeReference',
1851
+ typeName: {
1852
+ type: 'Identifier',
1853
+ name: 'ReadonlyArray',
1854
+ },
1855
+ typeParameters: {
1856
+ type: 'TSTypeParameterInstantiation',
1857
+ params: [param],
1858
+ },
1859
+ },
1860
+ ],
1861
+ };
1862
+ }
1863
+ // React.Component<A,B> -> React.Component<A,B>
1864
+ // React$Component<A,B> -> React.Component<A,B>
1865
+ case 'React.Component':
1866
+ case 'React$Component': {
1867
+ const typeParameters = node.typeParameters;
1868
+ if (typeParameters == null || typeParameters.params.length === 0) {
1869
+ throw translationError(
1870
+ node,
1871
+ `Expected at least 1 type parameter with \`${fullTypeName}\``,
1872
+ );
1873
+ }
1874
+ const params = typeParameters.params;
1875
+ if (params.length > 2) {
1876
+ throw translationError(
1877
+ node,
1878
+ `Expected at no more than 2 type parameters with \`${fullTypeName}\``,
1879
+ );
1880
+ }
1881
+
1882
+ return {
1883
+ type: 'TSTypeReference',
1884
+ typeName: {
1885
+ type: 'TSQualifiedName',
1886
+ left: getReactIdentifier(),
1887
+ right: {
1888
+ type: 'Identifier',
1889
+ name: 'Component',
1890
+ },
1891
+ },
1892
+ typeParameters: {
1893
+ type: 'TSTypeParameterInstantiation',
1894
+ params: params.map(param => transformTypeAnnotationType(param)),
1895
+ },
1896
+ };
1897
+ }
1898
+
1899
+ // React.Context<A> -> React.Context<A>
1900
+ // React$Context<A> -> React.Context<A>
1557
1901
  case 'React$Context':
1558
1902
  case 'React.Context':
1559
1903
  return {
1560
1904
  type: 'TSTypeReference',
1561
1905
  typeName: {
1562
1906
  type: 'TSQualifiedName',
1563
- left: reactId,
1907
+ left: getReactIdentifier(),
1564
1908
  right: {
1565
1909
  type: 'Identifier',
1566
1910
  name: `Context`,
@@ -1571,6 +1915,40 @@ const getTransforms = (code: string, scopeManager: ScopeManager) => {
1571
1915
  params: assertHasExactlyNTypeParameters(1),
1572
1916
  },
1573
1917
  };
1918
+ // React.Key -> React.Key
1919
+ // React$Key -> React.Key
1920
+ case 'React.Key':
1921
+ case 'React$Key':
1922
+ assertHasExactlyNTypeParameters(0);
1923
+ return {
1924
+ type: 'TSTypeReference',
1925
+ typeName: {
1926
+ type: 'TSQualifiedName',
1927
+ left: getReactIdentifier(),
1928
+ right: {
1929
+ type: 'Identifier',
1930
+ name: 'Key',
1931
+ },
1932
+ },
1933
+ };
1934
+ // React.ElementType -> React.ElementType
1935
+ // React$ElementType -> React.ElementType
1936
+ case 'React$ElementType':
1937
+ case 'React.ElementType': {
1938
+ assertHasExactlyNTypeParameters(0);
1939
+ return {
1940
+ type: 'TSTypeReference',
1941
+ typeName: {
1942
+ type: 'TSQualifiedName',
1943
+ left: getReactIdentifier(),
1944
+ right: {
1945
+ type: 'Identifier',
1946
+ name: `ElementType`,
1947
+ },
1948
+ },
1949
+ typeParameters: undefined,
1950
+ };
1951
+ }
1574
1952
  // React.Node -> React.ReactNode
1575
1953
  case 'React$Node':
1576
1954
  case 'React.Node': {
@@ -1579,7 +1957,7 @@ const getTransforms = (code: string, scopeManager: ScopeManager) => {
1579
1957
  type: 'TSTypeReference',
1580
1958
  typeName: {
1581
1959
  type: 'TSQualifiedName',
1582
- left: reactId,
1960
+ left: getReactIdentifier(),
1583
1961
  right: {
1584
1962
  type: 'Identifier',
1585
1963
  name: `ReactNode`,
@@ -1595,7 +1973,7 @@ const getTransforms = (code: string, scopeManager: ScopeManager) => {
1595
1973
  type: 'TSTypeReference',
1596
1974
  typeName: {
1597
1975
  type: 'TSQualifiedName',
1598
- left: reactId,
1976
+ left: getReactIdentifier(),
1599
1977
  right: {
1600
1978
  type: 'Identifier',
1601
1979
  name: `ReactElement`,
@@ -1615,7 +1993,7 @@ const getTransforms = (code: string, scopeManager: ScopeManager) => {
1615
1993
  type: 'TSTypeReference',
1616
1994
  typeName: {
1617
1995
  type: 'TSQualifiedName',
1618
- left: reactId,
1996
+ left: getReactIdentifier(),
1619
1997
  right: {
1620
1998
  type: 'Identifier',
1621
1999
  name: `ElementRef`,
@@ -1635,7 +2013,7 @@ const getTransforms = (code: string, scopeManager: ScopeManager) => {
1635
2013
  type: 'TSTypeReference',
1636
2014
  typeName: {
1637
2015
  type: 'TSQualifiedName',
1638
- left: reactId,
2016
+ left: getReactIdentifier(),
1639
2017
  right: {
1640
2018
  type: 'Identifier',
1641
2019
  name: `Fragment`,
@@ -1662,15 +2040,32 @@ const getTransforms = (code: string, scopeManager: ScopeManager) => {
1662
2040
  typeParameters: undefined,
1663
2041
  };
1664
2042
  }
1665
- // React.AbstractComponent<Config> -> React.ForwardRefExoticComponent<Config & React.RefAttributes<unknown>>
1666
- // React.AbstractComponent<Config, Instance> -> React.ForwardRefExoticComponent<Config & React.RefAttributes<Instance>>
1667
- // React$AbstractComponent<Config, Instance> -> React.ForwardRefExoticComponent<Config & React.RefAttributes<Instance>>
1668
- // React.ComponentType<Config> -> React.ForwardRefExoticComponent<Config & React.RefAttributes<unknown>>
1669
- // React$ComponentType<Config> -> React.ForwardRefExoticComponent<Config & React.RefAttributes<unknown>>
1670
- case 'React.AbstractComponent':
1671
- case 'React$AbstractComponent':
2043
+ // React.ComponentType<Config> -> React.ComponentType<Config>
2044
+ // React$ComponentType<Config> -> React.ComponentType<Config>
1672
2045
  case 'React.ComponentType':
1673
2046
  case 'React$ComponentType': {
2047
+ return {
2048
+ type: 'TSTypeReference',
2049
+ typeName: {
2050
+ type: 'TSQualifiedName',
2051
+ left: getReactIdentifier(),
2052
+ right: {
2053
+ type: 'Identifier',
2054
+ name: 'ComponentType',
2055
+ },
2056
+ },
2057
+ typeParameters: {
2058
+ type: 'TSTypeParameterInstantiation',
2059
+ params: assertHasExactlyNTypeParameters(1),
2060
+ },
2061
+ };
2062
+ }
2063
+ // React.AbstractComponent<Config> -> React.ComponentType<Config>
2064
+ // React$AbstractComponent<Config> -> React.ComponentType<Config>
2065
+ // React.AbstractComponent<Config, Instance> -> React.ComponentType<Config & React.RefAttributes<Instance>>
2066
+ // React$AbstractComponent<Config, Instance> -> React.ComponentType<Config & React.RefAttributes<Instance>>
2067
+ case 'React.AbstractComponent':
2068
+ case 'React$AbstractComponent': {
1674
2069
  const typeParameters = node.typeParameters;
1675
2070
  if (typeParameters == null || typeParameters.params.length === 0) {
1676
2071
  throw translationError(
@@ -1686,56 +2081,142 @@ const getTransforms = (code: string, scopeManager: ScopeManager) => {
1686
2081
  );
1687
2082
  }
1688
2083
 
1689
- let newTypeParam = transform.TypeAnnotationType(params[0]);
1690
- newTypeParam = {
1691
- type: 'TSIntersectionType',
1692
- types: [
1693
- newTypeParam,
2084
+ const newParams = (() => {
2085
+ if (params.length === 1) {
2086
+ return assertHasExactlyNTypeParameters(1);
2087
+ }
2088
+
2089
+ const [props, ref] = assertHasExactlyNTypeParameters(2);
2090
+
2091
+ return [
1694
2092
  {
1695
- type: 'TSTypeReference',
1696
- typeName: {
1697
- type: 'TSQualifiedName',
1698
- left: {
1699
- type: 'Identifier',
1700
- name: 'React',
1701
- },
1702
- right: {
1703
- type: 'Identifier',
1704
- name: 'RefAttributes',
2093
+ type: 'TSIntersectionType',
2094
+ types: [
2095
+ props,
2096
+ {
2097
+ type: 'TSTypeReference',
2098
+ typeName: {
2099
+ type: 'TSQualifiedName',
2100
+ left: {
2101
+ type: 'Identifier',
2102
+ name: 'React',
2103
+ },
2104
+ right: {
2105
+ type: 'Identifier',
2106
+ name: 'RefAttributes',
2107
+ },
2108
+ },
2109
+ typeParameters: {
2110
+ type: 'TSTypeParameterInstantiation',
2111
+ params: [ref],
2112
+ },
1705
2113
  },
1706
- },
1707
- typeParameters: {
1708
- type: 'TSTypeParameterInstantiation',
1709
- params: [
1710
- params[1]
1711
- ? transform.TypeAnnotationType(params[1])
1712
- : {
1713
- type: 'TSUnknownKeyword',
1714
- },
1715
- ],
1716
- },
2114
+ ],
1717
2115
  },
1718
- ],
1719
- };
2116
+ ];
2117
+ })();
1720
2118
 
1721
2119
  return {
1722
2120
  type: 'TSTypeReference',
1723
2121
  typeName: {
1724
2122
  type: 'TSQualifiedName',
1725
- left: reactId,
2123
+ left: getReactIdentifier(),
2124
+ right: {
2125
+ type: 'Identifier',
2126
+ name: 'ComponentType',
2127
+ },
2128
+ },
2129
+ typeParameters: {
2130
+ type: 'TSTypeParameterInstantiation',
2131
+ params: newParams,
2132
+ },
2133
+ };
2134
+ }
2135
+ // React.ElementConfig<A> -> JSX.LibraryManagedAttributes<A, React.ComponentProps<A>>
2136
+ // React$ElementConfig<A> -> JSX.LibraryManagedAttributes<A, React.ComponentProps<A>>
2137
+ case 'React.ElementConfig':
2138
+ case 'React$ElementConfig': {
2139
+ const [param] = assertHasExactlyNTypeParameters(1);
2140
+ return {
2141
+ type: 'TSTypeReference',
2142
+ typeName: {
2143
+ type: 'TSQualifiedName',
2144
+ left: {
2145
+ type: 'Identifier',
2146
+ name: 'JSX',
2147
+ },
1726
2148
  right: {
1727
2149
  type: 'Identifier',
1728
- name: `ForwardRefExoticComponent`,
2150
+ name: 'LibraryManagedAttributes',
1729
2151
  },
1730
2152
  },
1731
2153
  typeParameters: {
1732
2154
  type: 'TSTypeParameterInstantiation',
1733
- params: [newTypeParam],
2155
+ params: [
2156
+ param,
2157
+ {
2158
+ type: 'TSTypeReference',
2159
+ typeName: {
2160
+ type: 'TSQualifiedName',
2161
+ left: getReactIdentifier(),
2162
+ right: {
2163
+ type: 'Identifier',
2164
+ name: `ComponentProps`,
2165
+ },
2166
+ },
2167
+ typeParameters: {
2168
+ type: 'TSTypeParameterInstantiation',
2169
+ params: [param],
2170
+ },
2171
+ },
2172
+ ],
1734
2173
  },
1735
2174
  };
1736
2175
  }
2176
+ // React.Ref<C> -> NonNullable<React.Ref<C> | string | number>
2177
+ // React$Ref<C> -> NonNullable<React.Ref<C> | string | number>
2178
+ case 'React.Ref':
2179
+ case 'React$Ref':
2180
+ return {
2181
+ type: 'TSTypeReference',
2182
+ typeName: {
2183
+ type: 'Identifier',
2184
+ name: 'NonNullable',
2185
+ },
2186
+ typeParameters: {
2187
+ type: 'TSTypeParameterInstantiation',
2188
+ params: [
2189
+ {
2190
+ type: 'TSUnionType',
2191
+ types: [
2192
+ {
2193
+ type: 'TSTypeReference',
2194
+ typeName: {
2195
+ type: 'TSQualifiedName',
2196
+ left: getReactIdentifier(),
2197
+ right: {
2198
+ type: 'Identifier',
2199
+ name: 'Ref',
2200
+ },
2201
+ },
2202
+ typeParameters: {
2203
+ type: 'TSTypeParameterInstantiation',
2204
+ params: assertHasExactlyNTypeParameters(1),
2205
+ },
2206
+ },
2207
+ {
2208
+ type: 'TSStringKeyword',
2209
+ },
2210
+ {
2211
+ type: 'TSNumberKeyword',
2212
+ },
2213
+ ],
2214
+ },
2215
+ ],
2216
+ },
2217
+ };
1737
2218
  default:
1738
- throw unsupportedTranslationError(node, fullTypeName);
2219
+ return unsupportedAnnotation(node, fullTypeName);
1739
2220
  }
1740
2221
  }
1741
2222
 
@@ -1770,8 +2251,8 @@ const getTransforms = (code: string, scopeManager: ScopeManager) => {
1770
2251
  ): TSESTree.TSIndexedAccessType {
1771
2252
  return {
1772
2253
  type: 'TSIndexedAccessType',
1773
- objectType: transform.TypeAnnotationType(node.objectType),
1774
- indexType: transform.TypeAnnotationType(node.indexType),
2254
+ objectType: transformTypeAnnotationType(node.objectType),
2255
+ indexType: transformTypeAnnotationType(node.indexType),
1775
2256
  };
1776
2257
  },
1777
2258
  InterfaceDeclaration(
@@ -1793,67 +2274,77 @@ const getTransforms = (code: string, scopeManager: ScopeManager) => {
1793
2274
  },
1794
2275
  ImportDeclaration(
1795
2276
  node: FlowESTree.ImportDeclaration,
1796
- ): TSESTree.ImportDeclaration {
1797
- if (node.importKind === 'typeof') {
1798
- /*
1799
- TODO - this is a complicated change to support because TS
1800
- does not have typeof imports.
1801
- Making it a `type` import would change the meaning!
1802
- The only way to truly support this is to prefix all **usages** with `typeof T`.
1803
- eg:
2277
+ ): Array<DeclarationOrUnsupported<TSESTree.ImportDeclaration>> {
2278
+ const importKind = node.importKind;
1804
2279
 
1805
- ```
1806
- import typeof Foo from 'Foo';
1807
- type T = Foo;
1808
- ```
2280
+ const specifiers = [];
2281
+ const unsupportedSpecifiers = [];
2282
+ node.specifiers.forEach(spec => {
2283
+ let id = (() => {
2284
+ if (node.importKind === 'typeof' || spec.importKind === 'typeof') {
2285
+ const id = {
2286
+ type: 'Identifier',
2287
+ name: getPlaceholderNameForTypeofImport(),
2288
+ };
1809
2289
 
1810
- would become:
2290
+ unsupportedSpecifiers.push({
2291
+ type: 'TSTypeAliasDeclaration',
2292
+ id: transform.Identifier(spec.local, false),
2293
+ typeAnnotation: {
2294
+ type: 'TSTypeQuery',
2295
+ exprName: id,
2296
+ },
2297
+ });
1811
2298
 
1812
- ```
1813
- import type Foo from 'Foo';
1814
- type T = typeof Foo;
1815
- ```
2299
+ return id;
2300
+ }
1816
2301
 
1817
- This seems simple, but will actually be super complicated for us to do with
1818
- our current translation architecture
1819
- */
1820
- throw unsupportedTranslationError(node, 'typeof imports');
1821
- }
1822
- const importKind = node.importKind;
2302
+ return transform.Identifier(spec.local, false);
2303
+ })();
1823
2304
 
1824
- return {
1825
- type: 'ImportDeclaration',
1826
- assertions: node.assertions.map(transform.ImportAttribute),
1827
- importKind: importKind ?? 'value',
1828
- source: transform.StringLiteral(node.source),
1829
- specifiers: node.specifiers.map(spec => {
1830
- switch (spec.type) {
1831
- case 'ImportDefaultSpecifier':
1832
- return {
1833
- type: 'ImportDefaultSpecifier',
1834
- local: transform.Identifier(spec.local, false),
1835
- };
2305
+ switch (spec.type) {
2306
+ case 'ImportDefaultSpecifier':
2307
+ specifiers.push({
2308
+ type: 'ImportDefaultSpecifier',
2309
+ local: id,
2310
+ });
2311
+ return;
1836
2312
 
1837
- case 'ImportNamespaceSpecifier':
1838
- return {
1839
- type: 'ImportNamespaceSpecifier',
1840
- local: transform.Identifier(spec.local, false),
1841
- };
2313
+ case 'ImportNamespaceSpecifier':
2314
+ specifiers.push({
2315
+ type: 'ImportNamespaceSpecifier',
2316
+ local: id,
2317
+ });
2318
+ return;
2319
+
2320
+ case 'ImportSpecifier':
2321
+ specifiers.push({
2322
+ type: 'ImportSpecifier',
2323
+ importKind:
2324
+ spec.importKind === 'typeof' || spec.importKind === 'type'
2325
+ ? 'type'
2326
+ : null,
2327
+ imported: transform.Identifier(spec.imported, false),
2328
+ local: id,
2329
+ });
2330
+ return;
2331
+ }
2332
+ });
2333
+
2334
+ const out = specifiers.length
2335
+ ? [
2336
+ {
2337
+ type: 'ImportDeclaration',
2338
+ assertions: node.assertions.map(transform.ImportAttribute),
2339
+ importKind:
2340
+ importKind === 'typeof' ? 'type' : importKind ?? 'value',
2341
+ source: transform.StringLiteral(node.source),
2342
+ specifiers,
2343
+ },
2344
+ ]
2345
+ : [];
1842
2346
 
1843
- case 'ImportSpecifier':
1844
- if (spec.importKind === 'typeof') {
1845
- // see above
1846
- throw unsupportedTranslationError(node, 'typeof imports');
1847
- }
1848
- return {
1849
- type: 'ImportSpecifier',
1850
- importKind: spec.importKind === 'type' ? 'type' : null,
1851
- imported: transform.Identifier(spec.imported, false),
1852
- local: transform.Identifier(spec.local, false),
1853
- };
1854
- }
1855
- }),
1856
- };
2347
+ return [...out, ...unsupportedSpecifiers];
1857
2348
  },
1858
2349
  InterfaceExtends(
1859
2350
  node: FlowESTree.InterfaceExtends,
@@ -1897,7 +2388,7 @@ const getTransforms = (code: string, scopeManager: ScopeManager) => {
1897
2388
  ): TSESTree.TSIntersectionType {
1898
2389
  return {
1899
2390
  type: 'TSIntersectionType',
1900
- types: node.types.map(transform.TypeAnnotationType),
2391
+ types: node.types.map(transformTypeAnnotationType),
1901
2392
  };
1902
2393
  },
1903
2394
  Literal(node: FlowESTree.Literal): TSESTree.Literal {
@@ -1951,7 +2442,7 @@ const getTransforms = (code: string, scopeManager: ScopeManager) => {
1951
2442
  {
1952
2443
  type: 'TSUndefinedKeyword',
1953
2444
  },
1954
- transform.TypeAnnotationType(node.typeAnnotation),
2445
+ transformTypeAnnotationType(node.typeAnnotation),
1955
2446
  ],
1956
2447
  };
1957
2448
  },
@@ -1983,7 +2474,10 @@ const getTransforms = (code: string, scopeManager: ScopeManager) => {
1983
2474
  },
1984
2475
  ObjectTypeAnnotation(
1985
2476
  node: FlowESTree.ObjectTypeAnnotation,
1986
- ): TSESTree.TSTypeLiteral | TSESTree.TSIntersectionType {
2477
+ ):
2478
+ | TSESTree.TSTypeLiteral
2479
+ | TSESTree.TSIntersectionType
2480
+ | TSESTree.TSAnyKeyword {
1987
2481
  // we want to preserve the source order of the members
1988
2482
  // unfortunately flow has unordered properties storing things
1989
2483
  // so store all elements with their start index and sort the
@@ -2010,10 +2504,7 @@ const getTransforms = (code: string, scopeManager: ScopeManager) => {
2010
2504
  They're really rarely used (if ever) - so let's just ignore them for now
2011
2505
  */
2012
2506
  if (node.internalSlots.length > 0) {
2013
- throw unsupportedTranslationError(
2014
- node.internalSlots[0],
2015
- 'internal slots',
2016
- );
2507
+ return unsupportedAnnotation(node.internalSlots[0], 'internal slots');
2017
2508
  }
2018
2509
 
2019
2510
  if (!node.properties.find(FlowESTree.isObjectTypeSpreadProperty)) {
@@ -2024,6 +2515,14 @@ const getTransforms = (code: string, scopeManager: ScopeManager) => {
2024
2515
  throw unexpectedTranslationError(property, 'Impossible state');
2025
2516
  }
2026
2517
 
2518
+ if (property.type === 'ObjectTypeMappedTypeProperty') {
2519
+ // TODO - Support mapped types
2520
+ return unsupportedAnnotation(
2521
+ property,
2522
+ 'object type with mapped type property',
2523
+ );
2524
+ }
2525
+
2027
2526
  members.push({
2028
2527
  start: property.range[0],
2029
2528
  node: transform.ObjectTypeProperty(property),
@@ -2110,7 +2609,7 @@ const getTransforms = (code: string, scopeManager: ScopeManager) => {
2110
2609
  */
2111
2610
 
2112
2611
  if (members.length > 0) {
2113
- throw unsupportedTranslationError(
2612
+ return unsupportedAnnotation(
2114
2613
  node,
2115
2614
  'object types with spreads, indexers and/or call properties at the same time',
2116
2615
  );
@@ -2120,21 +2619,27 @@ const getTransforms = (code: string, scopeManager: ScopeManager) => {
2120
2619
  for (const property of node.properties) {
2121
2620
  if (property.type === 'ObjectTypeSpreadProperty') {
2122
2621
  if (members.length > 0) {
2123
- throw unsupportedTranslationError(
2622
+ return unsupportedAnnotation(
2124
2623
  property,
2125
2624
  'object types with spreads in the middle or at the end',
2126
2625
  );
2127
2626
  }
2128
2627
 
2129
- const spreadType = transform.TypeAnnotationType(property.argument);
2628
+ const spreadType = transformTypeAnnotationType(property.argument);
2130
2629
  if (spreadType.type !== 'TSTypeReference') {
2131
- throw unsupportedTranslationError(
2630
+ return unsupportedAnnotation(
2132
2631
  property,
2133
2632
  'object types with complex spreads',
2134
2633
  );
2135
2634
  }
2136
2635
 
2137
2636
  typesToIntersect.push(spreadType);
2637
+ } else if (property.type === 'ObjectTypeMappedTypeProperty') {
2638
+ // TODO - Support mapped types
2639
+ return unsupportedAnnotation(
2640
+ property,
2641
+ 'object type with mapped type property',
2642
+ );
2138
2643
  } else {
2139
2644
  members.push({
2140
2645
  start: property.range[0],
@@ -2208,7 +2713,7 @@ const getTransforms = (code: string, scopeManager: ScopeManager) => {
2208
2713
  name: node.id == null ? '$$Key$$' : node.id.name,
2209
2714
  typeAnnotation: {
2210
2715
  type: 'TSTypeAnnotation',
2211
- typeAnnotation: transform.TypeAnnotationType(node.key),
2716
+ typeAnnotation: transformTypeAnnotationType(node.key),
2212
2717
  },
2213
2718
  },
2214
2719
  ],
@@ -2216,7 +2721,7 @@ const getTransforms = (code: string, scopeManager: ScopeManager) => {
2216
2721
  static: node.static,
2217
2722
  typeAnnotation: {
2218
2723
  type: 'TSTypeAnnotation',
2219
- typeAnnotation: transform.TypeAnnotationType(node.value),
2724
+ typeAnnotation: transformTypeAnnotationType(node.value),
2220
2725
  },
2221
2726
  };
2222
2727
  },
@@ -2273,7 +2778,7 @@ const getTransforms = (code: string, scopeManager: ScopeManager) => {
2273
2778
  static: node.static,
2274
2779
  typeAnnotation: {
2275
2780
  type: 'TSTypeAnnotation',
2276
- typeAnnotation: transform.TypeAnnotationType(node.value),
2781
+ typeAnnotation: transformTypeAnnotationType(node.value),
2277
2782
  },
2278
2783
  };
2279
2784
  },
@@ -2302,10 +2807,10 @@ const getTransforms = (code: string, scopeManager: ScopeManager) => {
2302
2807
  },
2303
2808
  typeParameters: {
2304
2809
  type: 'TSTypeParameterInstantiation',
2305
- params: [transform.TypeAnnotationType(node.objectType)],
2810
+ params: [transformTypeAnnotationType(node.objectType)],
2306
2811
  },
2307
2812
  },
2308
- indexType: transform.TypeAnnotationType(node.indexType),
2813
+ indexType: transformTypeAnnotationType(node.indexType),
2309
2814
  };
2310
2815
  },
2311
2816
  QualifiedTypeIdentifier(
@@ -2322,6 +2827,20 @@ const getTransforms = (code: string, scopeManager: ScopeManager) => {
2322
2827
  right: transform.Identifier(node.id, false),
2323
2828
  };
2324
2829
  },
2830
+ QualifiedTypeofIdentifier(
2831
+ node: FlowESTree.QualifiedTypeofIdentifier,
2832
+ ): TSESTree.TSQualifiedName {
2833
+ const qual = node.qualification;
2834
+
2835
+ return {
2836
+ type: 'TSQualifiedName',
2837
+ left:
2838
+ qual.type === 'Identifier'
2839
+ ? transform.Identifier(qual, false)
2840
+ : transform.QualifiedTypeofIdentifier(qual),
2841
+ right: transform.Identifier(node.id, false),
2842
+ };
2843
+ },
2325
2844
  RegExpLiteral(node: FlowESTree.RegExpLiteral): TSESTree.RegExpLiteral {
2326
2845
  return {
2327
2846
  type: 'Literal',
@@ -2378,7 +2897,7 @@ const getTransforms = (code: string, scopeManager: ScopeManager) => {
2378
2897
  ): TSESTree.TSTupleType {
2379
2898
  return {
2380
2899
  type: 'TSTupleType',
2381
- elementTypes: node.types.map(transform.TypeAnnotationType),
2900
+ elementTypes: node.types.map(transformTypeAnnotationType),
2382
2901
  };
2383
2902
  },
2384
2903
  TypeAlias(node: FlowESTree.TypeAlias): TSESTree.TSTypeAliasDeclaration {
@@ -2387,89 +2906,26 @@ const getTransforms = (code: string, scopeManager: ScopeManager) => {
2387
2906
  TypeAnnotation(node: FlowESTree.TypeAnnotation): TSESTree.TSTypeAnnotation {
2388
2907
  return {
2389
2908
  type: 'TSTypeAnnotation',
2390
- typeAnnotation: transform.TypeAnnotationType(node.typeAnnotation),
2909
+ typeAnnotation: transformTypeAnnotationType(node.typeAnnotation),
2391
2910
  };
2392
2911
  },
2393
- TypeAnnotationType(node: FlowESTree.TypeAnnotationType): TSESTree.TypeNode {
2394
- switch (node.type) {
2395
- case 'AnyTypeAnnotation':
2396
- return transform.AnyTypeAnnotation(node);
2397
- case 'ArrayTypeAnnotation':
2398
- return transform.ArrayTypeAnnotation(node);
2399
- case 'BigIntLiteralTypeAnnotation':
2400
- return transform.BigIntLiteralTypeAnnotation(node);
2401
- case 'BigIntTypeAnnotation':
2402
- return transform.BigIntTypeAnnotation(node);
2403
- case 'BooleanLiteralTypeAnnotation':
2404
- return transform.BooleanLiteralTypeAnnotation(node);
2405
- case 'BooleanTypeAnnotation':
2406
- return transform.BooleanTypeAnnotation(node);
2407
- case 'EmptyTypeAnnotation':
2408
- return transform.EmptyTypeAnnotation(node);
2409
- case 'ExistsTypeAnnotation':
2410
- return transform.ExistsTypeAnnotation(node);
2411
- case 'FunctionTypeAnnotation':
2412
- return transform.FunctionTypeAnnotation(node);
2413
- case 'GenericTypeAnnotation':
2414
- return transform.GenericTypeAnnotation(node);
2415
- case 'IndexedAccessType':
2416
- return transform.IndexedAccessType(node);
2417
- case 'InterfaceTypeAnnotation':
2418
- return transform.InterfaceTypeAnnotation(node);
2419
- case 'IntersectionTypeAnnotation':
2420
- return transform.IntersectionTypeAnnotation(node);
2421
- case 'MixedTypeAnnotation':
2422
- return transform.MixedTypeAnnotation(node);
2423
- case 'NullLiteralTypeAnnotation':
2424
- return transform.NullLiteralTypeAnnotation(node);
2425
- case 'NullableTypeAnnotation':
2426
- return transform.NullableTypeAnnotation(node);
2427
- case 'NumberLiteralTypeAnnotation':
2428
- return transform.NumberLiteralTypeAnnotation(node);
2429
- case 'NumberTypeAnnotation':
2430
- return transform.NumberTypeAnnotation(node);
2431
- case 'ObjectTypeAnnotation':
2432
- return transform.ObjectTypeAnnotation(node);
2433
- case 'OptionalIndexedAccessType':
2434
- return transform.OptionalIndexedAccessType(node);
2435
- case 'QualifiedTypeIdentifier':
2436
- return transform.QualifiedTypeIdentifier(node);
2437
- case 'StringLiteralTypeAnnotation':
2438
- return transform.StringLiteralTypeAnnotation(node);
2439
- case 'StringTypeAnnotation':
2440
- return transform.StringTypeAnnotation(node);
2441
- case 'SymbolTypeAnnotation':
2442
- return transform.SymbolTypeAnnotation(node);
2443
- case 'ThisTypeAnnotation':
2444
- return transform.ThisTypeAnnotation(node);
2445
- case 'TupleTypeAnnotation':
2446
- return transform.TupleTypeAnnotation(node);
2447
- case 'TypeofTypeAnnotation':
2448
- return transform.TypeofTypeAnnotation(node);
2449
- case 'UnionTypeAnnotation':
2450
- return transform.UnionTypeAnnotation(node);
2451
- case 'VoidTypeAnnotation':
2452
- return transform.VoidTypeAnnotation(node);
2453
- default:
2454
- throw unexpectedTranslationError(node, `Unhandled type ${node.type}`);
2455
- }
2456
- },
2457
2912
  TypeofTypeAnnotation(
2458
2913
  node: FlowESTree.TypeofTypeAnnotation,
2459
2914
  ): TSESTree.TSTypeQuery {
2460
- const argument = transform.TypeAnnotationType(node.argument);
2461
- if (argument.type !== 'TSTypeReference') {
2462
- throw unexpectedTranslationError(
2463
- node,
2464
- `Expected to find a type reference as the argument to the TypeofTypeAnnotation, but got ${node.argument.type}`,
2465
- );
2915
+ switch (node.argument.type) {
2916
+ case 'Identifier':
2917
+ return {
2918
+ type: 'TSTypeQuery',
2919
+ exprName: transform.Identifier(node.argument),
2920
+ typeParameters: undefined,
2921
+ };
2922
+ case 'QualifiedTypeofIdentifier':
2923
+ return {
2924
+ type: 'TSTypeQuery',
2925
+ exprName: transform.QualifiedTypeofIdentifier(node.argument),
2926
+ typeParameters: undefined,
2927
+ };
2466
2928
  }
2467
-
2468
- return {
2469
- type: 'TSTypeQuery',
2470
- exprName: argument.typeName,
2471
- typeParameters: argument.typeParameters,
2472
- };
2473
2929
  },
2474
2930
  TypeParameter(node: FlowESTree.TypeParameter): TSESTree.TSTypeParameter {
2475
2931
  /*
@@ -2502,11 +2958,11 @@ const getTransforms = (code: string, scopeManager: ScopeManager) => {
2502
2958
  constraint:
2503
2959
  node.bound == null
2504
2960
  ? undefined
2505
- : transform.TypeAnnotationType(node.bound.typeAnnotation),
2961
+ : transformTypeAnnotationType(node.bound.typeAnnotation),
2506
2962
  default:
2507
2963
  node.default == null
2508
2964
  ? undefined
2509
- : transform.TypeAnnotationType(node.default),
2965
+ : transformTypeAnnotationType(node.default),
2510
2966
  in: false,
2511
2967
  out: false,
2512
2968
  // in: variance.has('in'),
@@ -2526,7 +2982,7 @@ const getTransforms = (code: string, scopeManager: ScopeManager) => {
2526
2982
  ): TSESTree.TSTypeParameterInstantiation {
2527
2983
  return {
2528
2984
  type: 'TSTypeParameterInstantiation',
2529
- params: node.params.map(transform.TypeAnnotationType),
2985
+ params: node.params.map(transformTypeAnnotationType),
2530
2986
  };
2531
2987
  },
2532
2988
  UnionTypeAnnotation(
@@ -2534,7 +2990,7 @@ const getTransforms = (code: string, scopeManager: ScopeManager) => {
2534
2990
  ): TSESTree.TSUnionType {
2535
2991
  return {
2536
2992
  type: 'TSUnionType',
2537
- types: node.types.map(transform.TypeAnnotationType),
2993
+ types: node.types.map(transformTypeAnnotationType),
2538
2994
  };
2539
2995
  },
2540
2996
  VoidTypeAnnotation(
@@ -2563,5 +3019,5 @@ const getTransforms = (code: string, scopeManager: ScopeManager) => {
2563
3019
  };
2564
3020
  }
2565
3021
 
2566
- return transform;
3022
+ return [transform, code];
2567
3023
  };