@@ -7,7 +7,13 @@ import { JSUtils, ExtendedPluginBuilder } from './js-utils';
7
7
import type { EmberTemplateCompiler , PreprocessOptions } from './ember-template-compiler' ;
8
8
import { LegacyModuleName } from './public-types' ;
9
9
import { ScopeLocals } from './scope-locals' ;
10
- import { ASTPluginBuilder , getTemplateLocals , preprocess , print } from '@glimmer/syntax' ;
10
+ import {
11
+ ASTPluginBuilder ,
12
+ ASTPluginEnvironment ,
13
+ NodeVisitor ,
14
+ preprocess ,
15
+ print ,
16
+ } from '@glimmer/syntax' ;
11
17
12
18
export * from './public-types' ;
13
19
@@ -372,33 +378,80 @@ function runtimeErrorIIFE(babel: typeof Babel, replacements: { ERROR_MESSAGE: st
372
378
373
379
function buildScopeLocals (
374
380
userTypedOptions : Record < string , unknown > ,
375
- formatOptions : ModuleConfig ,
376
- templateContent : string
381
+ formatOptions : ModuleConfig
377
382
) : ScopeLocals {
378
383
if ( formatOptions . rfc931Support && userTypedOptions . eval ) {
379
- return discoverLocals ( templateContent ) ;
384
+ return new ScopeLocals ( ) ;
380
385
} else if ( userTypedOptions . scope ) {
381
386
return userTypedOptions . scope as ScopeLocals ;
382
387
} else {
383
388
return new ScopeLocals ( ) ;
384
389
}
385
390
}
386
391
387
- function discoverLocals ( templateContent : string ) : ScopeLocals {
388
- // this is wrong, but the right thing is unreleased in
389
- // https://github.com/glimmerjs/glimmer-vm/pull/1421, so for the moment I'm
390
- // sticking with the exact behavior that ember-templates-imports has.
391
- //
392
- // (the reason it's wrong is that the correct answer depends on not just the
393
- // template, but the ambient javascript scope. Anything in locals needs to win
394
- // over ember keywords. Otherwise we can never introduce new keywords.)
395
- let scopeLocals = new ScopeLocals ( ) ;
396
- for ( let local of getTemplateLocals ( templateContent ) ) {
397
- if ( local . match ( / ^ [ $ A - Z _ ] [ 0 - 9 A - Z _ $ ] * $ / i) ) {
398
- scopeLocals . add ( local ) ;
392
+ function discoverLocals < EnvSpecificOptions > (
393
+ jsPath : NodePath < t . Expression > ,
394
+ state : State < EnvSpecificOptions > ,
395
+ locals : string [ ] ,
396
+ scope : ScopeLocals
397
+ ) {
398
+ function add ( name : string ) {
399
+ if ( locals . includes ( name ) ) return ;
400
+ locals . push ( name ) ;
401
+ }
402
+
403
+ let astScope : string [ ] = [ ] ;
404
+ function isInScope ( name : string ) {
405
+ return astScope . flat ( ) . includes ( name ) ;
406
+ }
407
+
408
+ function isInJsScope ( name : string ) {
409
+ if ( jsPath . scope . getBinding ( name ) ) return true ;
410
+ if ( [ 'this' , 'globalThis' ] . includes ( name ) ) return true ;
411
+ if ( scope . locals . includes ( name ) ) return true ;
412
+ if ( state . originalImportedNames . has ( name ) ) {
413
+ return true ;
399
414
}
415
+ return false ;
400
416
}
401
- return scopeLocals ;
417
+
418
+ return ( _env : ASTPluginEnvironment ) : { name : string ; visitor : NodeVisitor } => {
419
+ return {
420
+ name : 'discover-locals' ,
421
+ visitor : {
422
+ All : {
423
+ enter ( _node , path ) {
424
+ const blockParams = ( path . parentNode as any ) ?. blockParams ;
425
+ if ( blockParams && [ 'children' , 'body' ] . includes ( path . parentKey ! ) ) {
426
+ astScope . push ( blockParams ) ;
427
+ }
428
+ } ,
429
+ exit ( _node , path ) {
430
+ const blockParams = ( path . parentNode as any ) ?. blockParams ;
431
+ if ( blockParams && [ 'children' , 'body' ] . includes ( path . parentKey ! ) ) {
432
+ const i = astScope . indexOf ( blockParams ) ;
433
+ astScope . splice ( i , 1 ) ;
434
+ }
435
+ } ,
436
+ } ,
437
+ PathExpression ( node ) {
438
+ if (
439
+ node . head . type === 'VarHead' &&
440
+ ! isInScope ( node . head . name ) &&
441
+ isInJsScope ( node . head . name )
442
+ ) {
443
+ add ( node . head . name ) ;
444
+ }
445
+ } ,
446
+ ElementNode ( node ) {
447
+ const name = node . tag . split ( '.' ) [ 0 ] ;
448
+ if ( ! isInScope ( name ) && isInJsScope ( name ) ) {
449
+ add ( name ) ;
450
+ }
451
+ } ,
452
+ } ,
453
+ } ;
454
+ } ;
402
455
}
403
456
404
457
function buildPrecompileOptions < EnvSpecificOptions > (
@@ -437,6 +490,9 @@ function buildPrecompileOptions<EnvSpecificOptions>(
437
490
} ,
438
491
} ;
439
492
493
+ const locals : string [ ] = [ ] ;
494
+ output . plugins ?. ast ?. push ( discoverLocals ( target , state , locals , scope ) ) ;
495
+
440
496
for ( let [ key , value ] of Object . entries ( userTypedOptions ) ) {
441
497
if ( key !== 'scope' ) {
442
498
// `scope` in the user-facing API becomes `locals` in the low-level
@@ -445,7 +501,7 @@ function buildPrecompileOptions<EnvSpecificOptions>(
445
501
}
446
502
}
447
503
448
- output . locals = scope . locals ;
504
+ output . locals = locals ;
449
505
450
506
if ( config . rfc931Support ) {
451
507
output . strictMode = true ;
@@ -483,7 +539,7 @@ function insertCompiledTemplate<EnvSpecificOptions>(
483
539
backingClass : NodePath < Parameters < typeof t . callExpression > [ 1 ] [ number ] > | undefined
484
540
) {
485
541
let t = babel . types ;
486
- let scopeLocals = buildScopeLocals ( userTypedOptions , config , template ) ;
542
+ let scopeLocals = buildScopeLocals ( userTypedOptions , config ) ;
487
543
let options = buildPrecompileOptions (
488
544
babel ,
489
545
target ,
@@ -513,6 +569,8 @@ function insertCompiledTemplate<EnvSpecificOptions>(
513
569
configFile : false ,
514
570
} ) as t . File ;
515
571
572
+ scopeLocals . locals . length = 0 ;
573
+ scopeLocals . locals . push ( ...options . locals ! ) ;
516
574
ensureImportedNames ( target , scopeLocals , state . util , state . originalImportedNames ) ;
517
575
remapIdentifiers ( precompileResultAST , babel , scopeLocals ) ;
518
576
@@ -560,7 +618,7 @@ function insertTransformedTemplate<EnvSpecificOptions>(
560
618
backingClass : NodePath < Parameters < typeof t . callExpression > [ 1 ] [ number ] > | undefined
561
619
) {
562
620
let t = babel . types ;
563
- let scopeLocals = buildScopeLocals ( userTypedOptions , formatOptions , template ) ;
621
+ let scopeLocals = buildScopeLocals ( userTypedOptions , formatOptions ) ;
564
622
let options = buildPrecompileOptions (
565
623
babel ,
566
624
target ,
@@ -574,14 +632,14 @@ function insertTransformedTemplate<EnvSpecificOptions>(
574
632
let transformed = print ( ast , { entityEncoding : 'raw' } ) ;
575
633
if ( target . isCallExpression ( ) ) {
576
634
( target . get ( 'arguments.0' ) as NodePath < t . Node > ) . replaceWith ( t . stringLiteral ( transformed ) ) ;
577
- if ( ! scopeLocals . isEmpty ( ) ) {
578
- if ( ! formatOptions . enableScope ) {
579
- maybePruneImport ( state . util , target . get ( 'callee' ) ) ;
580
- target . set ( 'callee' , precompileTemplate ( state . util , target ) ) ;
581
- }
582
- ensureImportedNames ( target , scopeLocals , state . util , state . originalImportedNames ) ;
583
- updateScope ( babel , target , scopeLocals ) ;
635
+ if ( ! formatOptions . enableScope ) {
636
+ maybePruneImport ( state . util , target . get ( 'callee' ) ) ;
637
+ target . set ( 'callee' , precompileTemplate ( state . util , target ) ) ;
584
638
}
639
+ scopeLocals . locals . length = 0 ;
640
+ scopeLocals . locals . push ( ...options . locals ! ) ;
641
+ ensureImportedNames ( target , scopeLocals , state . util , state . originalImportedNames ) ;
642
+ updateScope ( babel , target , scopeLocals ) ;
585
643
586
644
if ( formatOptions . rfc931Support === 'polyfilled' ) {
587
645
maybePruneImport ( state . util , target . get ( 'callee' ) ) ;
@@ -615,6 +673,8 @@ function insertTransformedTemplate<EnvSpecificOptions>(
615
673
t . callExpression ( precompileTemplate ( state . util , target ) , [ t . stringLiteral ( transformed ) ] )
616
674
) [ 0 ] ;
617
675
ensureImportedNames ( newCall , scopeLocals , state . util , state . originalImportedNames ) ;
676
+ scopeLocals . locals . length = 0 ;
677
+ scopeLocals . locals . push ( ...options . locals ! ) ;
618
678
updateScope ( babel , newCall , scopeLocals ) ;
619
679
} else {
620
680
( target . get ( 'quasi' ) . get ( 'quasis.0' ) as NodePath < t . TemplateElement > ) . replaceWith (
@@ -635,14 +695,13 @@ function templateFactoryConfig(opts: NormalizedOpts) {
635
695
636
696
function buildScope ( babel : typeof Babel , locals : ScopeLocals ) {
637
697
let t = babel . types ;
698
+
638
699
return t . arrowFunctionExpression (
639
700
[ ] ,
640
701
t . objectExpression (
641
- locals
642
- . entries ( )
643
- . map ( ( [ name , identifier ] ) =>
644
- t . objectProperty ( t . identifier ( name ) , t . identifier ( identifier ) , false , true )
645
- )
702
+ locals . entries ( ) . map ( ( [ name , identifier ] ) =>
703
+ t . objectProperty ( t . identifier ( name ) , t . identifier ( identifier ) , false , true )
704
+ )
646
705
)
647
706
) ;
648
707
}
@@ -656,13 +715,16 @@ function updateScope(babel: typeof Babel, target: NodePath<t.CallExpression>, lo
656
715
} ) ;
657
716
if ( scope ) {
658
717
scope . set ( 'value' , buildScope ( babel , locals ) ) ;
659
- } else {
718
+ if ( locals . isEmpty ( ) ) {
719
+ scope . remove ( ) ;
720
+ }
721
+ } else if ( ! locals . isEmpty ( ) ) {
660
722
secondArg . pushContainer (
661
723
'properties' ,
662
724
t . objectProperty ( t . identifier ( 'scope' ) , buildScope ( babel , locals ) )
663
725
) ;
664
726
}
665
- } else {
727
+ } else if ( ! locals . isEmpty ( ) ) {
666
728
target . pushContainer (
667
729
'arguments' ,
668
730
t . objectExpression ( [ t . objectProperty ( t . identifier ( 'scope' ) , buildScope ( babel , locals ) ) ] )
0 commit comments