flow-api-translator 0.10.0 → 0.11.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.
@@ -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);
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
  ] {
@@ -466,56 +1001,51 @@ const getTransforms = (code: string, scopeManager: ScopeManager) => {
466
1001
  // only Identifiers can be handled here.
467
1002
  if (referencedId.type === 'Identifier') {
468
1003
  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
- ) {
1004
+ if (exportedVar != null && exportedVar.defs.length === 1) {
1005
+ const def = exportedVar.defs[0];
1006
+ switch (def.type) {
1007
+ case 'ImportBinding': {
1008
+ // `import type { Wut } from 'mod'; declare export default Wut;`
1009
+ // `import { type Wut } from 'mod'; declare export default Wut;`
1010
+ // these cases should be wrapped in a variable because they're exporting a type, not a value
1011
+ const specifier = def.node;
1012
+ if (
1013
+ specifier.importKind === 'type' ||
1014
+ specifier.parent.importKind === 'type'
1015
+ ) {
1016
+ // fallthrough to the "default" handling
1017
+ break;
1018
+ }
1019
+
1020
+ // intentional fallthrough to the "value" handling
1021
+ }
1022
+ case 'ClassName':
1023
+ case 'Enum':
1024
+ case 'FunctionName':
1025
+ case 'ImplicitGlobalVariable':
1026
+ case 'Variable':
1027
+ // there's already a variable defined to hold the type
1028
+ return {
1029
+ type: 'ExportDefaultDeclaration',
1030
+ declaration: {
1031
+ type: 'Identifier',
1032
+ name: referencedId.name,
1033
+ },
1034
+ exportKind: 'value',
1035
+ };
1036
+
1037
+ case 'CatchClause':
1038
+ case 'Parameter':
1039
+ case 'TypeParameter':
1040
+ throw translationError(
1041
+ def.node,
1042
+ `Unexpected variable def type: ${def.type}`,
1043
+ );
1044
+
1045
+ case 'Type':
487
1046
  // fallthrough to the "default" handling
488
1047
  break;
489
- }
490
-
491
- // intentional fallthrough to the "value" handling
492
1048
  }
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
1049
  }
520
1050
  }
521
1051
 
@@ -548,7 +1078,7 @@ const getTransforms = (code: string, scopeManager: ScopeManager) => {
548
1078
  typeAnnotation: {
549
1079
  type: 'TSTypeAnnotation',
550
1080
  typeAnnotation:
551
- transform.TypeAnnotationType(declaration),
1081
+ transformTypeAnnotationType(declaration),
552
1082
  },
553
1083
  },
554
1084
  init: null,
@@ -585,45 +1115,73 @@ const getTransforms = (code: string, scopeManager: ScopeManager) => {
585
1115
  }: TSESTree.ExportNamedDeclarationWithoutSourceWithMultiple);
586
1116
  }
587
1117
 
588
- const {declaration, exportKind} = (() => {
1118
+ const declarations = (() => {
589
1119
  switch (node.declaration.type) {
590
1120
  case 'DeclareClass':
591
- return {
592
- declaration: transform.DeclareClass(node.declaration),
593
- exportKind: 'value',
594
- };
1121
+ return [
1122
+ {
1123
+ declaration: transform.DeclareClass(node.declaration),
1124
+ exportKind: 'value',
1125
+ },
1126
+ ];
595
1127
  case 'DeclareFunction':
596
- return {
597
- declaration: transform.DeclareFunction(node.declaration),
598
- exportKind: 'value',
599
- };
1128
+ return [
1129
+ {
1130
+ declaration: transform.DeclareFunction(node.declaration),
1131
+ exportKind: 'value',
1132
+ },
1133
+ ];
600
1134
  case 'DeclareInterface':
601
- return {
602
- declaration: transform.DeclareInterface(node.declaration),
603
- exportKind: 'type',
604
- };
1135
+ return [
1136
+ {
1137
+ declaration: transform.DeclareInterface(node.declaration),
1138
+ exportKind: 'type',
1139
+ },
1140
+ ];
605
1141
  case 'DeclareOpaqueType':
606
- return {
607
- declaration: transform.DeclareOpaqueType(node.declaration),
608
- exportKind: 'type',
609
- };
1142
+ return [
1143
+ {
1144
+ declaration: transform.DeclareOpaqueType(node.declaration),
1145
+ exportKind: 'type',
1146
+ },
1147
+ ];
610
1148
  case 'DeclareVariable':
611
- return {
612
- declaration: transform.DeclareVariable(node.declaration),
613
- exportKind: 'value',
614
- };
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
+ }
615
1170
  }
616
1171
  })();
617
1172
 
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);
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
+ );
627
1185
  } else {
628
1186
  return ({
629
1187
  type: 'ExportNamedDeclaration',
@@ -696,7 +1254,7 @@ const getTransforms = (code: string, scopeManager: ScopeManager) => {
696
1254
  type: 'TSTypeAliasDeclaration',
697
1255
  declare: node.type === 'DeclareTypeAlias',
698
1256
  id: transform.Identifier(node.id, false),
699
- typeAnnotation: transform.TypeAnnotationType(node.right),
1257
+ typeAnnotation: transformTypeAnnotationType(node.right),
700
1258
  typeParameters:
701
1259
  node.typeParameters == null
702
1260
  ? undefined
@@ -719,7 +1277,7 @@ const getTransforms = (code: string, scopeManager: ScopeManager) => {
719
1277
  ? {
720
1278
  type: 'TSUnknownKeyword',
721
1279
  }
722
- : transform.TypeAnnotationType(node.supertype),
1280
+ : transformTypeAnnotationType(node.supertype),
723
1281
  typeParameters:
724
1282
  node.typeParameters == null
725
1283
  ? undefined
@@ -740,9 +1298,16 @@ const getTransforms = (code: string, scopeManager: ScopeManager) => {
740
1298
  init: null,
741
1299
  },
742
1300
  ],
743
- kind: 'var',
1301
+ kind: node.kind,
744
1302
  };
745
1303
  },
1304
+ DeclareEnum(
1305
+ node: FlowESTree.DeclareEnum,
1306
+ ): DeclarationOrUnsupported<
1307
+ [TSESTree.TSEnumDeclaration, TSESTree.TSModuleDeclaration],
1308
+ > {
1309
+ return EnumImpl(node);
1310
+ },
746
1311
  EmptyTypeAnnotation(
747
1312
  node: FlowESTree.EmptyTypeAnnotation,
748
1313
  ): TSESTree.TypeNode {
@@ -750,318 +1315,14 @@ const getTransforms = (code: string, scopeManager: ScopeManager) => {
750
1315
  // The closest is `never`, but `never` has a number of different semantics
751
1316
  // In reality no human code should ever directly use the `empty` type in flow
752
1317
  // 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
- ];
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);
1065
1326
  },
1066
1327
  DeclareModuleExports(
1067
1328
  node: FlowESTree.DeclareModuleExports,
@@ -1073,7 +1334,7 @@ const getTransforms = (code: string, scopeManager: ScopeManager) => {
1073
1334
  ): TSESTree.TypeNode {
1074
1335
  // The existential type does not map to any types in TS
1075
1336
  // It's also super deprecated - so let's not ever worry
1076
- throw unsupportedTranslationError(node, 'exestential type');
1337
+ return unsupportedAnnotation(node, 'existential type');
1077
1338
  },
1078
1339
  ExportNamedDeclaration(
1079
1340
  node: FlowESTree.ExportNamedDeclaration,
@@ -1096,11 +1357,12 @@ const getTransforms = (code: string, scopeManager: ScopeManager) => {
1096
1357
 
1097
1358
  const [exportedDeclaration, mergedDeclaration] = (() => {
1098
1359
  if (node.declaration == null) {
1099
- return [null];
1360
+ return [null, null];
1100
1361
  }
1101
1362
 
1102
1363
  switch (node.declaration.type) {
1103
1364
  case 'ClassDeclaration':
1365
+ case 'ComponentDeclaration':
1104
1366
  case 'FunctionDeclaration':
1105
1367
  case 'VariableDeclaration':
1106
1368
  // These cases shouldn't happen in flow defs because they have their own special
@@ -1110,8 +1372,10 @@ const getTransforms = (code: string, scopeManager: ScopeManager) => {
1110
1372
  `Unexpected named declaration found ${node.declaration.type}`,
1111
1373
  );
1112
1374
 
1113
- case 'EnumDeclaration':
1114
- return transform.EnumDeclaration(node.declaration);
1375
+ case 'EnumDeclaration': {
1376
+ const result = transform.EnumDeclaration(node.declaration);
1377
+ return Array.isArray(result) ? result : [result, null];
1378
+ }
1115
1379
  case 'InterfaceDeclaration':
1116
1380
  return [transform.InterfaceDeclaration(node.declaration), null];
1117
1381
  case 'OpaqueType':
@@ -1168,7 +1432,7 @@ const getTransforms = (code: string, scopeManager: ScopeManager) => {
1168
1432
  name: 'this',
1169
1433
  typeAnnotation: {
1170
1434
  type: 'TSTypeAnnotation',
1171
- typeAnnotation: transform.TypeAnnotationType(
1435
+ typeAnnotation: transformTypeAnnotationType(
1172
1436
  node.this.typeAnnotation,
1173
1437
  ),
1174
1438
  },
@@ -1187,7 +1451,7 @@ const getTransforms = (code: string, scopeManager: ScopeManager) => {
1187
1451
  : transform.Identifier(rest.name, false),
1188
1452
  typeAnnotation: {
1189
1453
  type: 'TSTypeAnnotation',
1190
- typeAnnotation: transform.TypeAnnotationType(rest.typeAnnotation),
1454
+ typeAnnotation: transformTypeAnnotationType(rest.typeAnnotation),
1191
1455
  },
1192
1456
  });
1193
1457
  }
@@ -1197,7 +1461,7 @@ const getTransforms = (code: string, scopeManager: ScopeManager) => {
1197
1461
  params,
1198
1462
  returnType: {
1199
1463
  type: 'TSTypeAnnotation',
1200
- typeAnnotation: transform.TypeAnnotationType(node.returnType),
1464
+ typeAnnotation: transformTypeAnnotationType(node.returnType),
1201
1465
  },
1202
1466
  typeParameters:
1203
1467
  node.typeParameters == null
@@ -1214,7 +1478,7 @@ const getTransforms = (code: string, scopeManager: ScopeManager) => {
1214
1478
  name: node.name == null ? `$$PARAM_${idx}$$` : node.name.name,
1215
1479
  typeAnnotation: {
1216
1480
  type: 'TSTypeAnnotation',
1217
- typeAnnotation: transform.TypeAnnotationType(node.typeAnnotation),
1481
+ typeAnnotation: transformTypeAnnotationType(node.typeAnnotation),
1218
1482
  },
1219
1483
  optional: node.optional,
1220
1484
  };
@@ -1261,7 +1525,7 @@ const getTransforms = (code: string, scopeManager: ScopeManager) => {
1261
1525
 
1262
1526
  const res = [];
1263
1527
  for (const param of node.typeParameters.params) {
1264
- res.push(transform.TypeAnnotationType(param));
1528
+ res.push(transformTypeAnnotationType(param));
1265
1529
  }
1266
1530
  return res;
1267
1531
  }
@@ -1304,7 +1568,7 @@ const getTransforms = (code: string, scopeManager: ScopeManager) => {
1304
1568
  type PropType = ReturnType<ExtractPropType<Obj>>; // number
1305
1569
  ```
1306
1570
  */
1307
- throw unsupportedTranslationError(node, fullTypeName);
1571
+ return unsupportedAnnotation(node, fullTypeName);
1308
1572
  }
1309
1573
 
1310
1574
  case '$Diff':
@@ -1498,13 +1762,6 @@ const getTransforms = (code: string, scopeManager: ScopeManager) => {
1498
1762
  };
1499
1763
  }
1500
1764
 
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
1765
  case '$Values': {
1509
1766
  // `$Values<T>` => `T[keyof T]`
1510
1767
  const transformedType = assertHasExactlyNTypeParameters(1)[0];
@@ -1559,16 +1816,148 @@ const getTransforms = (code: string, scopeManager: ScopeManager) => {
1559
1816
  }
1560
1817
 
1561
1818
  // React special conversion:
1562
- if (isReactImport(baseId)) {
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
+ };
1833
+ };
1834
+
1563
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>
1901
+ case 'React$Context':
1902
+ case 'React.Context':
1903
+ return {
1904
+ type: 'TSTypeReference',
1905
+ typeName: {
1906
+ type: 'TSQualifiedName',
1907
+ left: getReactIdentifier(),
1908
+ right: {
1909
+ type: 'Identifier',
1910
+ name: `Context`,
1911
+ },
1912
+ },
1913
+ typeParameters: {
1914
+ type: 'TSTypeParameterInstantiation',
1915
+ params: assertHasExactlyNTypeParameters(1),
1916
+ },
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
+ }
1564
1952
  // React.Node -> React.ReactNode
1953
+ case 'React$Node':
1565
1954
  case 'React.Node': {
1566
1955
  assertHasExactlyNTypeParameters(0);
1567
1956
  return {
1568
1957
  type: 'TSTypeReference',
1569
1958
  typeName: {
1570
1959
  type: 'TSQualifiedName',
1571
- left: transform.Identifier(baseId, false),
1960
+ left: getReactIdentifier(),
1572
1961
  right: {
1573
1962
  type: 'Identifier',
1574
1963
  name: `ReactNode`,
@@ -1578,12 +1967,13 @@ const getTransforms = (code: string, scopeManager: ScopeManager) => {
1578
1967
  };
1579
1968
  }
1580
1969
  // React.Element<typeof Component> -> React.ReactElement<typeof Component>
1970
+ case 'React$Element':
1581
1971
  case 'React.Element': {
1582
1972
  return {
1583
1973
  type: 'TSTypeReference',
1584
1974
  typeName: {
1585
1975
  type: 'TSQualifiedName',
1586
- left: transform.Identifier(baseId, false),
1976
+ left: getReactIdentifier(),
1587
1977
  right: {
1588
1978
  type: 'Identifier',
1589
1979
  name: `ReactElement`,
@@ -1595,7 +1985,43 @@ const getTransforms = (code: string, scopeManager: ScopeManager) => {
1595
1985
  },
1596
1986
  };
1597
1987
  }
1988
+ // React.ElementRef<typeof Component> -> React.ElementRef<typeof Component>
1989
+ // React$ElementRef<typeof Component> -> React.ElementRef<typeof Component>
1990
+ case 'React$ElementRef':
1991
+ case 'React.ElementRef':
1992
+ return {
1993
+ type: 'TSTypeReference',
1994
+ typeName: {
1995
+ type: 'TSQualifiedName',
1996
+ left: getReactIdentifier(),
1997
+ right: {
1998
+ type: 'Identifier',
1999
+ name: `ElementRef`,
2000
+ },
2001
+ },
2002
+ typeParameters: {
2003
+ type: 'TSTypeParameterInstantiation',
2004
+ params: assertHasExactlyNTypeParameters(1),
2005
+ },
2006
+ };
2007
+ // React$Fragment -> React.Fragment
2008
+ // React.Fragment -> React.Fragment
2009
+ case 'React$FragmentType':
2010
+ case 'React.Fragment':
2011
+ assertHasExactlyNTypeParameters(0);
2012
+ return {
2013
+ type: 'TSTypeReference',
2014
+ typeName: {
2015
+ type: 'TSQualifiedName',
2016
+ left: getReactIdentifier(),
2017
+ right: {
2018
+ type: 'Identifier',
2019
+ name: `Fragment`,
2020
+ },
2021
+ },
2022
+ };
1598
2023
  // React.MixedElement -> JSX.Element
2024
+ case 'React$MixedElement':
1599
2025
  case 'React.MixedElement': {
1600
2026
  assertHasExactlyNTypeParameters(0);
1601
2027
  return {
@@ -1614,9 +2040,32 @@ const getTransforms = (code: string, scopeManager: ScopeManager) => {
1614
2040
  typeParameters: undefined,
1615
2041
  };
1616
2042
  }
1617
- // React.AbstractComponent<Config> -> React.ForwardRefExoticComponent<Config>
1618
- // React.AbstractComponent<Config, Instance> -> React.ForwardRefExoticComponent<Config & React.RefAttributes<Instance>>
1619
- case 'React.AbstractComponent': {
2043
+ // React.ComponentType<Config> -> React.ComponentType<Config>
2044
+ // React$ComponentType<Config> -> React.ComponentType<Config>
2045
+ case 'React.ComponentType':
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': {
1620
2069
  const typeParameters = node.typeParameters;
1621
2070
  if (typeParameters == null || typeParameters.params.length === 0) {
1622
2071
  throw translationError(
@@ -1632,50 +2081,142 @@ const getTransforms = (code: string, scopeManager: ScopeManager) => {
1632
2081
  );
1633
2082
  }
1634
2083
 
1635
- let newTypeParam = transform.TypeAnnotationType(params[0]);
1636
- if (params[1] != null) {
1637
- newTypeParam = {
1638
- type: 'TSIntersectionType',
1639
- types: [
1640
- newTypeParam,
2084
+ const newParams = (() => {
2085
+ if (params.length === 1) {
2086
+ return assertHasExactlyNTypeParameters(1);
2087
+ }
2088
+
2089
+ const [props, ref] = assertHasExactlyNTypeParameters(2);
2090
+
2091
+ return [
2092
+ {
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
+ },
2113
+ },
2114
+ ],
2115
+ },
2116
+ ];
2117
+ })();
2118
+
2119
+ return {
2120
+ type: 'TSTypeReference',
2121
+ typeName: {
2122
+ type: 'TSQualifiedName',
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
+ },
2148
+ right: {
2149
+ type: 'Identifier',
2150
+ name: 'LibraryManagedAttributes',
2151
+ },
2152
+ },
2153
+ typeParameters: {
2154
+ type: 'TSTypeParameterInstantiation',
2155
+ params: [
2156
+ param,
1641
2157
  {
1642
2158
  type: 'TSTypeReference',
1643
2159
  typeName: {
1644
2160
  type: 'TSQualifiedName',
1645
- left: {
1646
- type: 'Identifier',
1647
- name: 'React',
1648
- },
2161
+ left: getReactIdentifier(),
1649
2162
  right: {
1650
2163
  type: 'Identifier',
1651
- name: 'RefAttributes',
2164
+ name: `ComponentProps`,
1652
2165
  },
1653
2166
  },
1654
2167
  typeParameters: {
1655
2168
  type: 'TSTypeParameterInstantiation',
1656
- params: [transform.TypeAnnotationType(params[1])],
2169
+ params: [param],
1657
2170
  },
1658
2171
  },
1659
2172
  ],
1660
- };
1661
- }
1662
-
2173
+ },
2174
+ };
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':
1663
2180
  return {
1664
2181
  type: 'TSTypeReference',
1665
2182
  typeName: {
1666
- type: 'TSQualifiedName',
1667
- left: transform.Identifier(baseId, false),
1668
- right: {
1669
- type: 'Identifier',
1670
- name: `ForwardRefExoticComponent`,
1671
- },
2183
+ type: 'Identifier',
2184
+ name: 'NonNullable',
1672
2185
  },
1673
2186
  typeParameters: {
1674
2187
  type: 'TSTypeParameterInstantiation',
1675
- params: [newTypeParam],
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
+ ],
1676
2216
  },
1677
2217
  };
1678
- }
2218
+ default:
2219
+ return unsupportedAnnotation(node, fullTypeName);
1679
2220
  }
1680
2221
  }
1681
2222
 
@@ -1710,8 +2251,8 @@ const getTransforms = (code: string, scopeManager: ScopeManager) => {
1710
2251
  ): TSESTree.TSIndexedAccessType {
1711
2252
  return {
1712
2253
  type: 'TSIndexedAccessType',
1713
- objectType: transform.TypeAnnotationType(node.objectType),
1714
- indexType: transform.TypeAnnotationType(node.indexType),
2254
+ objectType: transformTypeAnnotationType(node.objectType),
2255
+ indexType: transformTypeAnnotationType(node.indexType),
1715
2256
  };
1716
2257
  },
1717
2258
  InterfaceDeclaration(
@@ -1733,67 +2274,77 @@ const getTransforms = (code: string, scopeManager: ScopeManager) => {
1733
2274
  },
1734
2275
  ImportDeclaration(
1735
2276
  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:
2277
+ ): Array<DeclarationOrUnsupported<TSESTree.ImportDeclaration>> {
2278
+ const importKind = node.importKind;
1744
2279
 
1745
- ```
1746
- import typeof Foo from 'Foo';
1747
- type T = Foo;
1748
- ```
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
+ };
1749
2289
 
1750
- 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
+ });
1751
2298
 
1752
- ```
1753
- import type Foo from 'Foo';
1754
- type T = typeof Foo;
1755
- ```
2299
+ return id;
2300
+ }
1756
2301
 
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;
2302
+ return transform.Identifier(spec.local, false);
2303
+ })();
1763
2304
 
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
- };
2305
+ switch (spec.type) {
2306
+ case 'ImportDefaultSpecifier':
2307
+ specifiers.push({
2308
+ type: 'ImportDefaultSpecifier',
2309
+ local: id,
2310
+ });
2311
+ return;
1776
2312
 
1777
- case 'ImportNamespaceSpecifier':
1778
- return {
1779
- type: 'ImportNamespaceSpecifier',
1780
- local: transform.Identifier(spec.local, false),
1781
- };
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
+ : [];
1782
2346
 
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
- };
2347
+ return [...out, ...unsupportedSpecifiers];
1797
2348
  },
1798
2349
  InterfaceExtends(
1799
2350
  node: FlowESTree.InterfaceExtends,
@@ -1837,7 +2388,7 @@ const getTransforms = (code: string, scopeManager: ScopeManager) => {
1837
2388
  ): TSESTree.TSIntersectionType {
1838
2389
  return {
1839
2390
  type: 'TSIntersectionType',
1840
- types: node.types.map(transform.TypeAnnotationType),
2391
+ types: node.types.map(transformTypeAnnotationType),
1841
2392
  };
1842
2393
  },
1843
2394
  Literal(node: FlowESTree.Literal): TSESTree.Literal {
@@ -1891,7 +2442,7 @@ const getTransforms = (code: string, scopeManager: ScopeManager) => {
1891
2442
  {
1892
2443
  type: 'TSUndefinedKeyword',
1893
2444
  },
1894
- transform.TypeAnnotationType(node.typeAnnotation),
2445
+ transformTypeAnnotationType(node.typeAnnotation),
1895
2446
  ],
1896
2447
  };
1897
2448
  },
@@ -1923,7 +2474,10 @@ const getTransforms = (code: string, scopeManager: ScopeManager) => {
1923
2474
  },
1924
2475
  ObjectTypeAnnotation(
1925
2476
  node: FlowESTree.ObjectTypeAnnotation,
1926
- ): TSESTree.TSTypeLiteral | TSESTree.TSIntersectionType {
2477
+ ):
2478
+ | TSESTree.TSTypeLiteral
2479
+ | TSESTree.TSIntersectionType
2480
+ | TSESTree.TSAnyKeyword {
1927
2481
  // we want to preserve the source order of the members
1928
2482
  // unfortunately flow has unordered properties storing things
1929
2483
  // so store all elements with their start index and sort the
@@ -1950,10 +2504,7 @@ const getTransforms = (code: string, scopeManager: ScopeManager) => {
1950
2504
  They're really rarely used (if ever) - so let's just ignore them for now
1951
2505
  */
1952
2506
  if (node.internalSlots.length > 0) {
1953
- throw unsupportedTranslationError(
1954
- node.internalSlots[0],
1955
- 'internal slots',
1956
- );
2507
+ return unsupportedAnnotation(node.internalSlots[0], 'internal slots');
1957
2508
  }
1958
2509
 
1959
2510
  if (!node.properties.find(FlowESTree.isObjectTypeSpreadProperty)) {
@@ -1964,6 +2515,14 @@ const getTransforms = (code: string, scopeManager: ScopeManager) => {
1964
2515
  throw unexpectedTranslationError(property, 'Impossible state');
1965
2516
  }
1966
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
+
1967
2526
  members.push({
1968
2527
  start: property.range[0],
1969
2528
  node: transform.ObjectTypeProperty(property),
@@ -2050,7 +2609,7 @@ const getTransforms = (code: string, scopeManager: ScopeManager) => {
2050
2609
  */
2051
2610
 
2052
2611
  if (members.length > 0) {
2053
- throw unsupportedTranslationError(
2612
+ return unsupportedAnnotation(
2054
2613
  node,
2055
2614
  'object types with spreads, indexers and/or call properties at the same time',
2056
2615
  );
@@ -2060,21 +2619,27 @@ const getTransforms = (code: string, scopeManager: ScopeManager) => {
2060
2619
  for (const property of node.properties) {
2061
2620
  if (property.type === 'ObjectTypeSpreadProperty') {
2062
2621
  if (members.length > 0) {
2063
- throw unsupportedTranslationError(
2622
+ return unsupportedAnnotation(
2064
2623
  property,
2065
2624
  'object types with spreads in the middle or at the end',
2066
2625
  );
2067
2626
  }
2068
2627
 
2069
- const spreadType = transform.TypeAnnotationType(property.argument);
2628
+ const spreadType = transformTypeAnnotationType(property.argument);
2070
2629
  if (spreadType.type !== 'TSTypeReference') {
2071
- throw unsupportedTranslationError(
2630
+ return unsupportedAnnotation(
2072
2631
  property,
2073
2632
  'object types with complex spreads',
2074
2633
  );
2075
2634
  }
2076
2635
 
2077
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
+ );
2078
2643
  } else {
2079
2644
  members.push({
2080
2645
  start: property.range[0],
@@ -2148,7 +2713,7 @@ const getTransforms = (code: string, scopeManager: ScopeManager) => {
2148
2713
  name: node.id == null ? '$$Key$$' : node.id.name,
2149
2714
  typeAnnotation: {
2150
2715
  type: 'TSTypeAnnotation',
2151
- typeAnnotation: transform.TypeAnnotationType(node.key),
2716
+ typeAnnotation: transformTypeAnnotationType(node.key),
2152
2717
  },
2153
2718
  },
2154
2719
  ],
@@ -2156,7 +2721,7 @@ const getTransforms = (code: string, scopeManager: ScopeManager) => {
2156
2721
  static: node.static,
2157
2722
  typeAnnotation: {
2158
2723
  type: 'TSTypeAnnotation',
2159
- typeAnnotation: transform.TypeAnnotationType(node.value),
2724
+ typeAnnotation: transformTypeAnnotationType(node.value),
2160
2725
  },
2161
2726
  };
2162
2727
  },
@@ -2213,7 +2778,7 @@ const getTransforms = (code: string, scopeManager: ScopeManager) => {
2213
2778
  static: node.static,
2214
2779
  typeAnnotation: {
2215
2780
  type: 'TSTypeAnnotation',
2216
- typeAnnotation: transform.TypeAnnotationType(node.value),
2781
+ typeAnnotation: transformTypeAnnotationType(node.value),
2217
2782
  },
2218
2783
  };
2219
2784
  },
@@ -2242,10 +2807,10 @@ const getTransforms = (code: string, scopeManager: ScopeManager) => {
2242
2807
  },
2243
2808
  typeParameters: {
2244
2809
  type: 'TSTypeParameterInstantiation',
2245
- params: [transform.TypeAnnotationType(node.objectType)],
2810
+ params: [transformTypeAnnotationType(node.objectType)],
2246
2811
  },
2247
2812
  },
2248
- indexType: transform.TypeAnnotationType(node.indexType),
2813
+ indexType: transformTypeAnnotationType(node.indexType),
2249
2814
  };
2250
2815
  },
2251
2816
  QualifiedTypeIdentifier(
@@ -2262,6 +2827,20 @@ const getTransforms = (code: string, scopeManager: ScopeManager) => {
2262
2827
  right: transform.Identifier(node.id, false),
2263
2828
  };
2264
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
+ },
2265
2844
  RegExpLiteral(node: FlowESTree.RegExpLiteral): TSESTree.RegExpLiteral {
2266
2845
  return {
2267
2846
  type: 'Literal',
@@ -2318,7 +2897,7 @@ const getTransforms = (code: string, scopeManager: ScopeManager) => {
2318
2897
  ): TSESTree.TSTupleType {
2319
2898
  return {
2320
2899
  type: 'TSTupleType',
2321
- elementTypes: node.types.map(transform.TypeAnnotationType),
2900
+ elementTypes: node.types.map(transformTypeAnnotationType),
2322
2901
  };
2323
2902
  },
2324
2903
  TypeAlias(node: FlowESTree.TypeAlias): TSESTree.TSTypeAliasDeclaration {
@@ -2327,89 +2906,26 @@ const getTransforms = (code: string, scopeManager: ScopeManager) => {
2327
2906
  TypeAnnotation(node: FlowESTree.TypeAnnotation): TSESTree.TSTypeAnnotation {
2328
2907
  return {
2329
2908
  type: 'TSTypeAnnotation',
2330
- typeAnnotation: transform.TypeAnnotationType(node.typeAnnotation),
2909
+ typeAnnotation: transformTypeAnnotationType(node.typeAnnotation),
2331
2910
  };
2332
2911
  },
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
2912
  TypeofTypeAnnotation(
2398
2913
  node: FlowESTree.TypeofTypeAnnotation,
2399
2914
  ): 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
- );
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
+ };
2406
2928
  }
2407
-
2408
- return {
2409
- type: 'TSTypeQuery',
2410
- exprName: argument.typeName,
2411
- typeParameters: argument.typeParameters,
2412
- };
2413
2929
  },
2414
2930
  TypeParameter(node: FlowESTree.TypeParameter): TSESTree.TSTypeParameter {
2415
2931
  /*
@@ -2442,11 +2958,11 @@ const getTransforms = (code: string, scopeManager: ScopeManager) => {
2442
2958
  constraint:
2443
2959
  node.bound == null
2444
2960
  ? undefined
2445
- : transform.TypeAnnotationType(node.bound.typeAnnotation),
2961
+ : transformTypeAnnotationType(node.bound.typeAnnotation),
2446
2962
  default:
2447
2963
  node.default == null
2448
2964
  ? undefined
2449
- : transform.TypeAnnotationType(node.default),
2965
+ : transformTypeAnnotationType(node.default),
2450
2966
  in: false,
2451
2967
  out: false,
2452
2968
  // in: variance.has('in'),
@@ -2466,7 +2982,7 @@ const getTransforms = (code: string, scopeManager: ScopeManager) => {
2466
2982
  ): TSESTree.TSTypeParameterInstantiation {
2467
2983
  return {
2468
2984
  type: 'TSTypeParameterInstantiation',
2469
- params: node.params.map(transform.TypeAnnotationType),
2985
+ params: node.params.map(transformTypeAnnotationType),
2470
2986
  };
2471
2987
  },
2472
2988
  UnionTypeAnnotation(
@@ -2474,7 +2990,7 @@ const getTransforms = (code: string, scopeManager: ScopeManager) => {
2474
2990
  ): TSESTree.TSUnionType {
2475
2991
  return {
2476
2992
  type: 'TSUnionType',
2477
- types: node.types.map(transform.TypeAnnotationType),
2993
+ types: node.types.map(transformTypeAnnotationType),
2478
2994
  };
2479
2995
  },
2480
2996
  VoidTypeAnnotation(
@@ -2503,5 +3019,5 @@ const getTransforms = (code: string, scopeManager: ScopeManager) => {
2503
3019
  };
2504
3020
  }
2505
3021
 
2506
- return transform;
3022
+ return [transform, code];
2507
3023
  };