@@ -213,6 +213,7 @@ interface AsyncFunctionIdentifiers {
213
213
isSyntheticPromise : babel . types . Identifier ;
214
214
syntheticPromiseSymbol : babel . types . Identifier ;
215
215
demangleError : babel . types . Identifier ;
216
+ assertNotSyntheticPromise : babel . types . Identifier ;
216
217
}
217
218
/**
218
219
* The second step that performs the heavy lifting of turning regular functions
@@ -239,6 +240,7 @@ export const makeMaybeAsyncFunctionPlugin = ({ types: t }: { types: typeof Babel
239
240
const isGeneratedInnerFunction = asNodeKey ( Symbol ( 'isGeneratedInnerFunction' ) ) ;
240
241
const isGeneratedHelper = asNodeKey ( Symbol ( 'isGeneratedHelper' ) ) ;
241
242
const isOriginalBody = asNodeKey ( Symbol ( 'isOriginalBody' ) ) ;
243
+ const isAlwaysSyncFunction = asNodeKey ( Symbol ( 'isAlwaysSyncFunction' ) ) ;
242
244
// Using this key, we store data on Function nodes that contains the identifiers
243
245
// of helpers which are available inside the function.
244
246
const identifierGroupKey = '@@mongosh.identifierGroup' ;
@@ -269,6 +271,15 @@ export const makeMaybeAsyncFunctionPlugin = ({ types: t }: { types: typeof Babel
269
271
}
270
272
` ) ;
271
273
274
+ const assertNotSyntheticPromiseTemplate = babel . template . statement ( `
275
+ function ANSP_IDENTIFIER(p, s) {
276
+ if (p && p[SP_IDENTIFIER]) {
277
+ throw new Error('Result of expression "' + s + '" cannot be used in this context');
278
+ }
279
+ return p;
280
+ }
281
+ ` ) ;
282
+
272
283
const asyncTryCatchWrapperTemplate = babel . template . expression ( `
273
284
async () => {
274
285
try {
@@ -307,6 +318,10 @@ export const makeMaybeAsyncFunctionPlugin = ({ types: t }: { types: typeof Babel
307
318
allowAwaitOutsideFunction : true
308
319
} ) ;
309
320
321
+ const assertNotSyntheticExpressionTemplate = babel . template . expression ( `
322
+ ANSP_IDENTIFIER(NODE, ORIGINAL_SOURCE)
323
+ ` ) ;
324
+
310
325
const rethrowTemplate = babel . template . statement ( `
311
326
try {
312
327
ORIGINAL_CODE;
@@ -350,17 +365,6 @@ export const makeMaybeAsyncFunctionPlugin = ({ types: t }: { types: typeof Babel
350
365
if ( path . parentPath . node [ isGeneratedInnerFunction ] ) return ;
351
366
// Don't wrap helper functions with async-rewriter-generated code.
352
367
if ( path . parentPath . node [ isGeneratedHelper ] ) return ;
353
- // Don't wrap generator functions. There is no good way to handle them.
354
- if ( path . parentPath . node . generator && ! path . parentPath . node . async ) return ;
355
- // Finally, do not wrap constructor functions. This is not a technical
356
- // necessity, but rather a result of the fact that we can't handle
357
- // asynchronicity in constructors well (e.g.: What happens when you
358
- // subclass a class with a constructor that returns asynchronously?).
359
- if ( path . parentPath . isClassMethod ( ) &&
360
- path . parentPath . node . key . type === 'Identifier' &&
361
- path . parentPath . node . key . name === 'constructor' ) {
362
- return ;
363
- }
364
368
365
369
const originalSource = path . parent . start !== undefined ?
366
370
( this . file as any ) . code . slice ( path . parent . start , path . parent . end ) :
@@ -384,6 +388,7 @@ export const makeMaybeAsyncFunctionPlugin = ({ types: t }: { types: typeof Babel
384
388
const expressionHolder = path . scope . generateUidIdentifier ( 'ex' ) ;
385
389
const markSyntheticPromise = existingIdentifiers ?. markSyntheticPromise ?? path . scope . generateUidIdentifier ( 'msp' ) ;
386
390
const isSyntheticPromise = existingIdentifiers ?. isSyntheticPromise ?? path . scope . generateUidIdentifier ( 'isp' ) ;
391
+ const assertNotSyntheticPromise = existingIdentifiers ?. assertNotSyntheticPromise ?? path . scope . generateUidIdentifier ( 'ansp' ) ;
387
392
const syntheticPromiseSymbol = existingIdentifiers ?. syntheticPromiseSymbol ?? path . scope . generateUidIdentifier ( 'sp' ) ;
388
393
const demangleError = existingIdentifiers ?. demangleError ?? path . scope . generateUidIdentifier ( 'de' ) ;
389
394
const identifiersGroup : AsyncFunctionIdentifiers = {
@@ -393,6 +398,7 @@ export const makeMaybeAsyncFunctionPlugin = ({ types: t }: { types: typeof Babel
393
398
expressionHolder,
394
399
markSyntheticPromise,
395
400
isSyntheticPromise,
401
+ assertNotSyntheticPromise,
396
402
syntheticPromiseSymbol,
397
403
demangleError
398
404
} ;
@@ -410,14 +416,17 @@ export const makeMaybeAsyncFunctionPlugin = ({ types: t }: { types: typeof Babel
410
416
// Note that the last check potentially triggers getters and Proxy methods
411
417
// and we may want to replace it by something a bit more sophisticated.
412
418
// All of the top-level AST nodes here are marked as generated helpers.
413
- const promiseHelpers = existingIdentifiers ? [ ] : [
419
+ const commonHelpers = existingIdentifiers ? [ ] : [
414
420
Object . assign (
415
421
syntheticPromiseSymbolTemplate ( {
416
422
SP_IDENTIFIER : syntheticPromiseSymbol ,
417
423
SYMBOL_CONSTRUCTOR : symbolConstructor
418
424
} ) ,
419
425
{ [ isGeneratedHelper ] : true }
420
426
) ,
427
+ ] ;
428
+ const promiseHelpers = existingIdentifiers ? [ ] : [
429
+ ...commonHelpers ,
421
430
Object . assign (
422
431
markSyntheticPromiseTemplate ( {
423
432
MSP_IDENTIFIER : markSyntheticPromise ,
@@ -439,6 +448,16 @@ export const makeMaybeAsyncFunctionPlugin = ({ types: t }: { types: typeof Babel
439
448
{ [ isGeneratedHelper ] : true }
440
449
)
441
450
] ;
451
+ const syncFnHelpers = [
452
+ ...commonHelpers ,
453
+ Object . assign (
454
+ assertNotSyntheticPromiseTemplate ( {
455
+ ANSP_IDENTIFIER : assertNotSyntheticPromise ,
456
+ SP_IDENTIFIER : syntheticPromiseSymbol
457
+ } ) ,
458
+ { [ isGeneratedHelper ] : true }
459
+ )
460
+ ] ;
442
461
443
462
if ( path . parentPath . node . async ) {
444
463
// If we are in an async function, no async wrapping is necessary.
@@ -455,6 +474,25 @@ export const makeMaybeAsyncFunctionPlugin = ({ types: t }: { types: typeof Babel
455
474
return ;
456
475
}
457
476
477
+ // If we are in a non-async generator function, or a class constructor,
478
+ // we throw errors for implicitly asynchronous expressions, because there
479
+ // is just no good way to handle them (e.g.: What happens when you
480
+ // subclass a class with a constructor that returns asynchronously?).
481
+ if ( path . parentPath . node . generator ||
482
+ ( path . parentPath . isClassMethod ( ) &&
483
+ path . parentPath . node . key . type === 'Identifier' &&
484
+ path . parentPath . node . key . name === 'constructor' ) ) {
485
+ Object . assign ( path . parentPath . node , { [ isAlwaysSyncFunction ] : true } ) ;
486
+ path . replaceWith ( t . blockStatement ( [
487
+ originalSourceNode ,
488
+ ...syncFnHelpers ,
489
+ rethrowTemplate ( {
490
+ ORIGINAL_CODE : path . node . body
491
+ } )
492
+ ] ) ) ;
493
+ return ;
494
+ }
495
+
458
496
const asyncTryCatchWrapper = Object . assign (
459
497
asyncTryCatchWrapperTemplate ( {
460
498
FUNCTION_STATE_IDENTIFIER : functionState ,
@@ -493,9 +531,11 @@ export const makeMaybeAsyncFunctionPlugin = ({ types: t }: { types: typeof Babel
493
531
} ,
494
532
exit ( path ) {
495
533
// We have seen an expression. If we're not inside an async function,
496
- // we don't care.
534
+ // or a function that we explicitly marked as needing always-synchronous
535
+ // treatment, we don't care.
497
536
if ( ! path . getFunctionParent ( ) ) return ;
498
- if ( ! path . getFunctionParent ( ) . node . async ) return ;
537
+ if ( ! path . getFunctionParent ( ) . node . async &&
538
+ ! path . getFunctionParent ( ) . node [ isAlwaysSyncFunction ] ) return ;
499
539
// identifierGroup holds the list of helper identifiers available
500
540
// inside this function.
501
541
let identifierGroup : AsyncFunctionIdentifiers ;
@@ -534,8 +574,8 @@ export const makeMaybeAsyncFunctionPlugin = ({ types: t }: { types: typeof Babel
534
574
535
575
// If there is a [isGeneratedHelper] between the function we're in
536
576
// and this node, that means we've already handled this node.
537
- if ( path . findParent (
538
- path => path . isFunction ( ) || ( path . isSequenceExpression ( ) && ! ! path . node [ isGeneratedHelper ] )
577
+ if ( path . find (
578
+ path => path . isFunction ( ) || ! ! path . node [ isGeneratedHelper ]
539
579
) . node [ isGeneratedHelper ] ) {
540
580
return ;
541
581
}
@@ -586,6 +626,25 @@ export const makeMaybeAsyncFunctionPlugin = ({ types: t }: { types: typeof Babel
586
626
return ;
587
627
}
588
628
629
+ const { expressionHolder, isSyntheticPromise, assertNotSyntheticPromise } = identifierGroup ;
630
+ const prettyOriginalString = limitStringLength (
631
+ path . node . start !== undefined ?
632
+ ( this . file as any ) . code . slice ( path . node . start , path . node . end ) :
633
+ '<unknown>' , 24 ) ;
634
+
635
+ if ( ! path . getFunctionParent ( ) . node . async ) {
636
+ // Transform expression `foo` into `assertNotSyntheticPromise(foo, 'foo')`.
637
+ path . replaceWith ( Object . assign (
638
+ assertNotSyntheticExpressionTemplate ( {
639
+ ORIGINAL_SOURCE : t . stringLiteral ( prettyOriginalString ) ,
640
+ NODE : path . node ,
641
+ ANSP_IDENTIFIER : assertNotSyntheticPromise
642
+ } ) ,
643
+ { [ isGeneratedHelper ] : true }
644
+ ) ) ;
645
+ return ;
646
+ }
647
+
589
648
// Transform expression `foo` into
590
649
// `('\uFEFFfoo\uFEFF', ex = foo, isSyntheticPromise(ex) ? await ex : ex)`
591
650
// The first part of the sequence expression is used to identify this
@@ -597,13 +656,8 @@ export const makeMaybeAsyncFunctionPlugin = ({ types: t }: { types: typeof Babel
597
656
// user code accidentally being recognized as the original source code.
598
657
// We limit the string length so that long expressions (e.g. those
599
658
// containing functions) are not included in full length.
600
- const { expressionHolder, isSyntheticPromise } = identifierGroup ;
601
659
const originalSource = t . stringLiteral (
602
- '\ufeff' + limitStringLength (
603
- path . node . start !== undefined ?
604
- ( this . file as any ) . code . slice ( path . node . start , path . node . end ) :
605
- '<unknown>' , 24 ) +
606
- '\ufeff' ) ;
660
+ '\ufeff' + prettyOriginalString + '\ufeff' ) ;
607
661
path . replaceWith ( Object . assign (
608
662
awaitSyntheticPromiseTemplate ( {
609
663
ORIGINAL_SOURCE : originalSource ,
0 commit comments