hermes-parser 0.15.0 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,671 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @flow strict
8
+ * @format
9
+ */
10
+
11
+ /**
12
+ * This transform strips Flow types that are not supported past Babel 7.
13
+ *
14
+ * It is expected that all transforms create valid ESTree AST output. If
15
+ * the transform requires outputting Babel specific AST nodes then it
16
+ * should live in `ConvertESTreeToBabel.js`
17
+ */
18
+
19
+ 'use strict';
20
+
21
+ import type {ParserOptions} from '../ParserOptions';
22
+ import type {
23
+ Program,
24
+ ESNode,
25
+ DeclareComponent,
26
+ DeclareVariable,
27
+ ComponentDeclaration,
28
+ FunctionDeclaration,
29
+ TypeAnnotation,
30
+ ComponentParameter,
31
+ SourceLocation,
32
+ Position,
33
+ ObjectPattern,
34
+ Identifier,
35
+ Range,
36
+ RestElement,
37
+ DestructuringObjectProperty,
38
+ VariableDeclaration,
39
+ ModuleDeclaration,
40
+ Statement,
41
+ } from 'hermes-estree';
42
+
43
+ import {SimpleTransform} from '../transform/SimpleTransform';
44
+ import {shallowCloneNode} from '../transform/astNodeMutationHelpers';
45
+ import {SimpleTraverser} from '../traverse/SimpleTraverser';
46
+
47
+ const nodeWith = SimpleTransform.nodeWith;
48
+
49
+ // Rely on the mapper to fix up parent relationships.
50
+ const EMPTY_PARENT: $FlowFixMe = null;
51
+
52
+ function createSyntaxError(node: ESNode, err: string): SyntaxError {
53
+ const syntaxError = new SyntaxError(err);
54
+ // $FlowExpectedError[prop-missing]
55
+ syntaxError.loc = {
56
+ line: node.loc.start.line,
57
+ column: node.loc.start.column,
58
+ };
59
+
60
+ return syntaxError;
61
+ }
62
+
63
+ function createDefaultPosition(): Position {
64
+ return {
65
+ line: 1,
66
+ column: 0,
67
+ };
68
+ }
69
+
70
+ function mapDeclareComponent(node: DeclareComponent): DeclareVariable {
71
+ return {
72
+ type: 'DeclareVariable',
73
+ id: nodeWith(node.id, {
74
+ typeAnnotation: {
75
+ type: 'TypeAnnotation',
76
+ typeAnnotation: {
77
+ type: 'AnyTypeAnnotation',
78
+ loc: node.loc,
79
+ range: node.range,
80
+ parent: EMPTY_PARENT,
81
+ },
82
+ loc: node.loc,
83
+ range: node.range,
84
+ parent: EMPTY_PARENT,
85
+ },
86
+ }),
87
+ kind: 'const',
88
+ loc: node.loc,
89
+ range: node.range,
90
+ parent: node.parent,
91
+ };
92
+ }
93
+
94
+ function getComponentParameterName(
95
+ paramName: ComponentParameter['name'],
96
+ ): string {
97
+ switch (paramName.type) {
98
+ case 'Identifier':
99
+ return paramName.name;
100
+ case 'Literal':
101
+ return paramName.value;
102
+ default:
103
+ throw createSyntaxError(
104
+ paramName,
105
+ `Unknown Component parameter name type of "${paramName.type}"`,
106
+ );
107
+ }
108
+ }
109
+
110
+ function createPropsTypeAnnotation(
111
+ loc: ?SourceLocation,
112
+ range: ?Range,
113
+ ): TypeAnnotation {
114
+ // Create empty loc for type annotation nodes
115
+ const createParamsTypeLoc = () => ({
116
+ loc: {
117
+ start: loc?.start != null ? loc.start : createDefaultPosition(),
118
+ end: loc?.end != null ? loc.end : createDefaultPosition(),
119
+ },
120
+ range: range ?? [0, 0],
121
+ parent: EMPTY_PARENT,
122
+ });
123
+
124
+ return {
125
+ type: 'TypeAnnotation',
126
+ typeAnnotation: {
127
+ type: 'GenericTypeAnnotation',
128
+ id: {
129
+ type: 'Identifier',
130
+ name: '$ReadOnly',
131
+ optional: false,
132
+ typeAnnotation: null,
133
+ ...createParamsTypeLoc(),
134
+ },
135
+ typeParameters: {
136
+ type: 'TypeParameterInstantiation',
137
+ params: [
138
+ {
139
+ type: 'ObjectTypeAnnotation',
140
+ callProperties: [],
141
+ properties: [],
142
+ indexers: [],
143
+ internalSlots: [],
144
+ exact: false,
145
+ inexact: true,
146
+ ...createParamsTypeLoc(),
147
+ },
148
+ ],
149
+ ...createParamsTypeLoc(),
150
+ },
151
+ ...createParamsTypeLoc(),
152
+ },
153
+ ...createParamsTypeLoc(),
154
+ };
155
+ }
156
+
157
+ function mapComponentParameters(
158
+ params: $ReadOnlyArray<ComponentParameter | RestElement>,
159
+ ): $ReadOnly<{
160
+ props: ?(ObjectPattern | Identifier),
161
+ ref: ?Identifier,
162
+ }> {
163
+ if (params.length === 0) {
164
+ return {props: null, ref: null};
165
+ }
166
+
167
+ // Optimize `component Foo(...props: Props) {}` to `function Foo(props: Props) {}
168
+ if (
169
+ params.length === 1 &&
170
+ params[0].type === 'RestElement' &&
171
+ (params[0].argument.type === 'Identifier' ||
172
+ params[0].argument.type === 'ObjectPattern')
173
+ ) {
174
+ const restElementArgument = params[0].argument;
175
+ return {
176
+ props: nodeWith(restElementArgument, {
177
+ typeAnnotation: createPropsTypeAnnotation(
178
+ restElementArgument.typeAnnotation?.loc,
179
+ restElementArgument.typeAnnotation?.range,
180
+ ),
181
+ }),
182
+ ref: null,
183
+ };
184
+ }
185
+
186
+ // Filter out any ref param and capture it's details.
187
+ let refParam = null;
188
+ const paramsWithoutRef = params.filter(param => {
189
+ if (
190
+ param.type === 'ComponentParameter' &&
191
+ getComponentParameterName(param.name) === 'ref'
192
+ ) {
193
+ refParam = param;
194
+ return false;
195
+ }
196
+
197
+ return true;
198
+ });
199
+
200
+ const propsProperties = paramsWithoutRef.map(mapComponentParameter);
201
+
202
+ let props = null;
203
+ if (propsProperties.length === 0) {
204
+ if (refParam == null) {
205
+ throw new Error(
206
+ 'TransformReactScriptForBabel: Invalid state, ref should always be set at this point if props are empty',
207
+ );
208
+ }
209
+ const emptyParamsLoc = {
210
+ start: refParam.loc.start,
211
+ end: refParam.loc.start,
212
+ };
213
+ const emptyParamsRange = [refParam.range[0], refParam.range[0]];
214
+ // no properties provided (must have had a single ref)
215
+ props = {
216
+ type: 'Identifier',
217
+ name: '_$$empty_props_placeholder$$',
218
+ optional: false,
219
+ typeAnnotation: createPropsTypeAnnotation(
220
+ emptyParamsLoc,
221
+ emptyParamsRange,
222
+ ),
223
+ loc: emptyParamsLoc,
224
+ range: emptyParamsRange,
225
+ parent: EMPTY_PARENT,
226
+ };
227
+ } else {
228
+ const lastPropsProperty = propsProperties[propsProperties.length - 1];
229
+ props = {
230
+ type: 'ObjectPattern',
231
+ properties: propsProperties,
232
+ typeAnnotation: createPropsTypeAnnotation(
233
+ {
234
+ start: lastPropsProperty.loc.end,
235
+ end: lastPropsProperty.loc.end,
236
+ },
237
+ [lastPropsProperty.range[1], lastPropsProperty.range[1]],
238
+ ),
239
+ loc: {
240
+ start: propsProperties[0].loc.start,
241
+ end: lastPropsProperty.loc.end,
242
+ },
243
+ range: [propsProperties[0].range[0], lastPropsProperty.range[1]],
244
+ parent: EMPTY_PARENT,
245
+ };
246
+ }
247
+
248
+ let ref = null;
249
+ if (refParam != null) {
250
+ const refType = refParam.local;
251
+ ref = {
252
+ type: 'Identifier',
253
+ name: 'ref',
254
+ optional: false,
255
+ typeAnnotation:
256
+ refType.type === 'AssignmentPattern'
257
+ ? refType.left.typeAnnotation
258
+ : refType.typeAnnotation,
259
+ loc: refParam.loc,
260
+ range: refParam.range,
261
+ parent: EMPTY_PARENT,
262
+ };
263
+ }
264
+
265
+ return {
266
+ props,
267
+ ref,
268
+ };
269
+ }
270
+
271
+ function mapComponentParameter(
272
+ param: ComponentParameter | RestElement,
273
+ ): DestructuringObjectProperty | RestElement {
274
+ switch (param.type) {
275
+ case 'RestElement': {
276
+ const a = nodeWith(param, {
277
+ typeAnnotation: null,
278
+ argument: nodeWith(param.argument, {typeAnnotation: null}),
279
+ });
280
+ return a;
281
+ }
282
+ case 'ComponentParameter': {
283
+ if (getComponentParameterName(param.name) === 'ref') {
284
+ throw createSyntaxError(
285
+ param,
286
+ 'Component parameters named "ref" are currently not supported',
287
+ );
288
+ }
289
+
290
+ let value;
291
+ if (param.local.type === 'AssignmentPattern') {
292
+ value = nodeWith(param.local, {
293
+ left: nodeWith(param.local.left, {
294
+ typeAnnotation: null,
295
+ optional: false,
296
+ }),
297
+ });
298
+ } else {
299
+ value = nodeWith(param.local, {
300
+ typeAnnotation: null,
301
+ optional: false,
302
+ });
303
+ }
304
+
305
+ // Shorthand params
306
+ if (
307
+ param.name.type === 'Identifier' &&
308
+ param.shorthand &&
309
+ (value.type === 'Identifier' || value.type === 'AssignmentPattern')
310
+ ) {
311
+ return {
312
+ type: 'Property',
313
+ key: param.name,
314
+ kind: 'init',
315
+ value,
316
+ method: false,
317
+ shorthand: true,
318
+ computed: false,
319
+ loc: param.loc,
320
+ range: param.range,
321
+ parent: EMPTY_PARENT,
322
+ };
323
+ }
324
+
325
+ // Complex params
326
+ return {
327
+ type: 'Property',
328
+ key: param.name,
329
+ kind: 'init',
330
+ value,
331
+ method: false,
332
+ shorthand: false,
333
+ computed: false,
334
+ loc: param.loc,
335
+ range: param.range,
336
+ parent: EMPTY_PARENT,
337
+ };
338
+ }
339
+ default: {
340
+ throw createSyntaxError(
341
+ param,
342
+ `Unknown Component parameter type of "${param.type}"`,
343
+ );
344
+ }
345
+ }
346
+ }
347
+
348
+ type ForwardRefDetails = {
349
+ forwardRefStatement: VariableDeclaration,
350
+ internalCompId: Identifier,
351
+ forwardRefCompId: Identifier,
352
+ };
353
+
354
+ function createForwardRefWrapper(
355
+ originalComponent: ComponentDeclaration,
356
+ ): ForwardRefDetails {
357
+ const internalCompId = {
358
+ type: 'Identifier',
359
+ name: `${originalComponent.id.name}_withRef`,
360
+ optional: false,
361
+ typeAnnotation: null,
362
+ loc: originalComponent.id.loc,
363
+ range: originalComponent.id.range,
364
+ parent: EMPTY_PARENT,
365
+ };
366
+ return {
367
+ forwardRefStatement: {
368
+ type: 'VariableDeclaration',
369
+ kind: 'const',
370
+ declarations: [
371
+ {
372
+ type: 'VariableDeclarator',
373
+ id: shallowCloneNode(originalComponent.id),
374
+ init: {
375
+ type: 'CallExpression',
376
+ callee: {
377
+ type: 'MemberExpression',
378
+ object: {
379
+ type: 'Identifier',
380
+ name: 'React',
381
+ optional: false,
382
+ typeAnnotation: null,
383
+ loc: originalComponent.loc,
384
+ range: originalComponent.range,
385
+ parent: EMPTY_PARENT,
386
+ },
387
+ property: {
388
+ type: 'Identifier',
389
+ name: 'forwardRef',
390
+ optional: false,
391
+ typeAnnotation: null,
392
+ loc: originalComponent.loc,
393
+ range: originalComponent.range,
394
+ parent: EMPTY_PARENT,
395
+ },
396
+ computed: false,
397
+ optional: false,
398
+ loc: originalComponent.loc,
399
+ range: originalComponent.range,
400
+ parent: EMPTY_PARENT,
401
+ },
402
+ arguments: [shallowCloneNode(internalCompId)],
403
+ typeArguments: null,
404
+ optional: false,
405
+ loc: originalComponent.loc,
406
+ range: originalComponent.range,
407
+ parent: EMPTY_PARENT,
408
+ },
409
+ loc: originalComponent.loc,
410
+ range: originalComponent.range,
411
+ parent: EMPTY_PARENT,
412
+ },
413
+ ],
414
+ loc: originalComponent.loc,
415
+ range: originalComponent.range,
416
+ parent: originalComponent.parent,
417
+ },
418
+ internalCompId: internalCompId,
419
+ forwardRefCompId: originalComponent.id,
420
+ };
421
+ }
422
+
423
+ function mapComponentDeclaration(node: ComponentDeclaration): {
424
+ comp: FunctionDeclaration,
425
+ forwardRefDetails: ?ForwardRefDetails,
426
+ } {
427
+ let rendersType = node.rendersType;
428
+ if (rendersType == null) {
429
+ // Create empty loc for return type annotation nodes
430
+ const createRendersTypeLoc = () => ({
431
+ loc: {
432
+ start: node.body.loc.end,
433
+ end: node.body.loc.end,
434
+ },
435
+ range: [node.body.range[1], node.body.range[1]],
436
+ parent: EMPTY_PARENT,
437
+ });
438
+
439
+ rendersType = {
440
+ type: 'TypeAnnotation',
441
+ typeAnnotation: {
442
+ type: 'GenericTypeAnnotation',
443
+ id: {
444
+ type: 'QualifiedTypeIdentifier',
445
+ qualification: {
446
+ type: 'Identifier',
447
+ name: 'React',
448
+ optional: false,
449
+ typeAnnotation: null,
450
+ ...createRendersTypeLoc(),
451
+ },
452
+ id: {
453
+ type: 'Identifier',
454
+ name: 'Node',
455
+ optional: false,
456
+ typeAnnotation: null,
457
+ ...createRendersTypeLoc(),
458
+ },
459
+ ...createRendersTypeLoc(),
460
+ },
461
+ typeParameters: null,
462
+ ...createRendersTypeLoc(),
463
+ },
464
+ ...createRendersTypeLoc(),
465
+ };
466
+ }
467
+
468
+ const {props, ref} = mapComponentParameters(node.params);
469
+
470
+ let forwardRefDetails: ?ForwardRefDetails = null;
471
+
472
+ if (ref != null) {
473
+ forwardRefDetails = createForwardRefWrapper(node);
474
+ }
475
+
476
+ const comp = {
477
+ type: 'FunctionDeclaration',
478
+ id:
479
+ forwardRefDetails != null
480
+ ? shallowCloneNode(forwardRefDetails.internalCompId)
481
+ : shallowCloneNode(node.id),
482
+ __componentDeclaration: true,
483
+ typeParameters: node.typeParameters,
484
+ params: props == null ? [] : ref == null ? [props] : [props, ref],
485
+ returnType: rendersType,
486
+ body: node.body,
487
+ async: false,
488
+ generator: false,
489
+ predicate: null,
490
+ loc: node.loc,
491
+ range: node.range,
492
+ parent: node.parent,
493
+ };
494
+
495
+ return {comp, forwardRefDetails};
496
+ }
497
+
498
+ /**
499
+ * Scan a list of statements and return the position of the
500
+ * first statement that contains a reference to a given component
501
+ * or null of no references were found.
502
+ */
503
+ function scanForFirstComponentReference(
504
+ compName: string,
505
+ bodyList: Array<Statement | ModuleDeclaration>,
506
+ ): ?number {
507
+ for (let i = 0; i < bodyList.length; i++) {
508
+ const bodyNode = bodyList[i];
509
+ let referencePos = null;
510
+ SimpleTraverser.traverse(bodyNode, {
511
+ enter(node: ESNode) {
512
+ switch (node.type) {
513
+ case 'Identifier': {
514
+ if (node.name === compName) {
515
+ // We found a reference, record it and stop.
516
+ referencePos = i;
517
+ throw SimpleTraverser.Break;
518
+ }
519
+ }
520
+ }
521
+ },
522
+ leave(_node: ESNode) {},
523
+ });
524
+ if (referencePos != null) {
525
+ return referencePos;
526
+ }
527
+ }
528
+
529
+ return null;
530
+ }
531
+
532
+ function mapComponentDeclarationIntoList(
533
+ node: ComponentDeclaration,
534
+ newBody: Array<Statement | ModuleDeclaration>,
535
+ insertExport?: (Identifier | FunctionDeclaration) => ModuleDeclaration,
536
+ ) {
537
+ const {comp, forwardRefDetails} = mapComponentDeclaration(node);
538
+ if (forwardRefDetails != null) {
539
+ // Scan for references to our component.
540
+ const referencePos = scanForFirstComponentReference(
541
+ forwardRefDetails.forwardRefCompId.name,
542
+ newBody,
543
+ );
544
+
545
+ // If a reference is found insert the forwardRef statement before it (to simulate function hoisting).
546
+ if (referencePos != null) {
547
+ newBody.splice(referencePos, 0, forwardRefDetails.forwardRefStatement);
548
+ } else {
549
+ newBody.push(forwardRefDetails.forwardRefStatement);
550
+ }
551
+
552
+ newBody.push(comp);
553
+
554
+ if (insertExport != null) {
555
+ newBody.push(insertExport(forwardRefDetails.forwardRefCompId));
556
+ }
557
+ return;
558
+ }
559
+
560
+ newBody.push(insertExport != null ? insertExport(comp) : comp);
561
+ }
562
+
563
+ function mapStatementList(
564
+ stmts: $ReadOnlyArray<Statement | ModuleDeclaration>,
565
+ ) {
566
+ const newBody: Array<Statement | ModuleDeclaration> = [];
567
+ for (const node of stmts) {
568
+ switch (node.type) {
569
+ case 'ComponentDeclaration': {
570
+ mapComponentDeclarationIntoList(node, newBody);
571
+ break;
572
+ }
573
+ case 'ExportNamedDeclaration': {
574
+ if (node.declaration?.type === 'ComponentDeclaration') {
575
+ mapComponentDeclarationIntoList(
576
+ node.declaration,
577
+ newBody,
578
+ componentOrRef => {
579
+ switch (componentOrRef.type) {
580
+ case 'FunctionDeclaration': {
581
+ // No ref, so we can export the component directly.
582
+ return nodeWith(node, {declaration: componentOrRef});
583
+ }
584
+ case 'Identifier': {
585
+ // If a ref is inserted, we should just export that id.
586
+ return {
587
+ type: 'ExportNamedDeclaration',
588
+ declaration: null,
589
+ specifiers: [
590
+ {
591
+ type: 'ExportSpecifier',
592
+ exported: shallowCloneNode(componentOrRef),
593
+ local: shallowCloneNode(componentOrRef),
594
+ loc: node.loc,
595
+ range: node.range,
596
+ parent: EMPTY_PARENT,
597
+ },
598
+ ],
599
+ exportKind: 'value',
600
+ source: null,
601
+ loc: node.loc,
602
+ range: node.range,
603
+ parent: node.parent,
604
+ };
605
+ }
606
+ }
607
+ },
608
+ );
609
+ break;
610
+ }
611
+
612
+ newBody.push(node);
613
+ break;
614
+ }
615
+ case 'ExportDefaultDeclaration': {
616
+ if (node.declaration?.type === 'ComponentDeclaration') {
617
+ mapComponentDeclarationIntoList(
618
+ node.declaration,
619
+ newBody,
620
+ componentOrRef => nodeWith(node, {declaration: componentOrRef}),
621
+ );
622
+ break;
623
+ }
624
+
625
+ newBody.push(node);
626
+ break;
627
+ }
628
+ default: {
629
+ newBody.push(node);
630
+ }
631
+ }
632
+ }
633
+
634
+ return newBody;
635
+ }
636
+
637
+ export function transformProgram(
638
+ program: Program,
639
+ _options: ParserOptions,
640
+ ): Program {
641
+ return SimpleTransform.transformProgram(program, {
642
+ transform(node: ESNode) {
643
+ switch (node.type) {
644
+ case 'DeclareComponent': {
645
+ return mapDeclareComponent(node);
646
+ }
647
+ case 'Program':
648
+ case 'BlockStatement': {
649
+ return nodeWith(node, {body: mapStatementList(node.body)});
650
+ }
651
+ case 'SwitchCase': {
652
+ return nodeWith(node, {
653
+ /* $FlowExpectedError[incompatible-call] We know `mapStatementList` will
654
+ not return `ModuleDeclaration` nodes if it is not passed any */
655
+ consequent: mapStatementList(node.consequent),
656
+ });
657
+ }
658
+ case 'ComponentDeclaration': {
659
+ throw createSyntaxError(
660
+ node,
661
+ `Components must be defined at the top level of a module or within a ` +
662
+ `BlockStatement, instead got parent of "${node.parent?.type}".`,
663
+ );
664
+ }
665
+ default: {
666
+ return node;
667
+ }
668
+ }
669
+ },
670
+ });
671
+ }