@@ -97,6 +97,8 @@ type RestoreCallback = {
97
97
callback : ScriptLetRestoreCallback
98
98
}
99
99
100
+ type TypeGenHelper = { generateUniqueId : ( base : string ) => string }
101
+
100
102
/**
101
103
* A class that handles script fragments.
102
104
* The script fragment AST node remaps and connects to the original directive AST node.
@@ -110,6 +112,10 @@ export class ScriptLetContext {
110
112
111
113
private readonly closeScopeCallbacks : ( ( ) => void ) [ ] = [ ]
112
114
115
+ private uniqueIdSeq = 1
116
+
117
+ private readonly usedUniqueIds = new Set < string > ( )
118
+
113
119
public constructor ( ctx : Context ) {
114
120
this . script = ctx . sourceCode . scripts
115
121
this . ctx = ctx
@@ -327,7 +333,12 @@ export class ScriptLetContext {
327
333
nodes : ESTree . Pattern [ ] ,
328
334
options : ScriptLetCallbackOption ,
329
335
) => void ,
330
- typings : string [ ] ,
336
+ typings :
337
+ | string [ ]
338
+ | ( ( helper : TypeGenHelper ) => {
339
+ typings : string [ ]
340
+ preparationScript ?: string
341
+ } ) ,
331
342
) : void
332
343
333
344
public nestBlock (
@@ -338,8 +349,34 @@ export class ScriptLetContext {
338
349
nodes : ESTree . Pattern [ ] ,
339
350
options : ScriptLetCallbackOption ,
340
351
) => void ,
341
- typings ?: string [ ] ,
352
+ typings ?:
353
+ | string [ ]
354
+ | ( ( helper : TypeGenHelper ) => {
355
+ typings : string [ ]
356
+ preparationScript ?: string
357
+ } ) ,
342
358
) : void {
359
+ let arrayTypings : string [ ] = [ ]
360
+ if ( typings && this . ctx . isTypeScript ( ) ) {
361
+ if ( Array . isArray ( typings ) ) {
362
+ arrayTypings = typings
363
+ } else {
364
+ const generatedTypes = typings ( {
365
+ generateUniqueId : ( base ) => this . generateUniqueId ( base ) ,
366
+ } )
367
+ arrayTypings = generatedTypes . typings
368
+ if ( generatedTypes . preparationScript ) {
369
+ this . appendScriptWithoutOffset (
370
+ generatedTypes . preparationScript ,
371
+ ( node , tokens , comments , result ) => {
372
+ tokens . length = 0
373
+ comments . length = 0
374
+ removeAllReference ( node , result )
375
+ } ,
376
+ )
377
+ }
378
+ }
379
+ }
343
380
if ( ! params ) {
344
381
const restore = this . appendScript (
345
382
`{` ,
@@ -381,7 +418,7 @@ export class ScriptLetContext {
381
418
range,
382
419
} )
383
420
if ( this . ctx . isTypeScript ( ) ) {
384
- source += ` : (${ typings ! [ index ] } )`
421
+ source += ` : (${ arrayTypings [ index ] } )`
385
422
}
386
423
}
387
424
const restore = this . appendScript (
@@ -407,6 +444,8 @@ export class ScriptLetContext {
407
444
line : typeAnnotation . loc . start . line ,
408
445
column : typeAnnotation . loc . start . column ,
409
446
}
447
+
448
+ removeAllReference ( typeAnnotation , result )
410
449
}
411
450
}
412
451
@@ -462,21 +501,37 @@ export class ScriptLetContext {
462
501
options : ScriptLetCallbackOption ,
463
502
) => void ,
464
503
) {
465
- const { start : startOffset , end : endOffset } = this . script . addLet ( text )
466
-
467
- const restoreCallback : RestoreCallback = {
468
- start : startOffset ,
469
- end : endOffset ,
470
- callback : ( node , tokens , comments , result ) => {
504
+ const resultCallback = this . appendScriptWithoutOffset (
505
+ text ,
506
+ ( node , tokens , comments , result ) => {
471
507
this . fixLocations (
472
508
node ,
473
509
tokens ,
474
510
comments ,
475
- offset - startOffset ,
511
+ offset - resultCallback . start ,
476
512
result . visitorKeys ,
477
513
)
478
514
callback ( node , tokens , comments , result )
479
515
} ,
516
+ )
517
+ return resultCallback
518
+ }
519
+
520
+ private appendScriptWithoutOffset (
521
+ text : string ,
522
+ callback : (
523
+ node : ESTree . Node ,
524
+ tokens : Token [ ] ,
525
+ comments : Comment [ ] ,
526
+ options : ScriptLetCallbackOption ,
527
+ ) => void ,
528
+ ) {
529
+ const { start : startOffset , end : endOffset } = this . script . addLet ( text )
530
+
531
+ const restoreCallback : RestoreCallback = {
532
+ start : startOffset ,
533
+ end : endOffset ,
534
+ callback,
480
535
}
481
536
this . restoreCallbacks . push ( restoreCallback )
482
537
return restoreCallback
@@ -746,6 +801,19 @@ export class ScriptLetContext {
746
801
applyLocs ( t , locs )
747
802
}
748
803
}
804
+
805
+ private generateUniqueId ( base : string ) {
806
+ let candidate = `$_${ base . replace ( / \W / g, "_" ) } ${ this . uniqueIdSeq ++ } `
807
+ while (
808
+ this . usedUniqueIds . has ( candidate ) ||
809
+ this . ctx . code . includes ( candidate ) ||
810
+ this . script . vcode . includes ( candidate )
811
+ ) {
812
+ candidate = `$_${ base . replace ( / \W / g, "_" ) } ${ this . uniqueIdSeq ++ } `
813
+ }
814
+ this . usedUniqueIds . add ( candidate )
815
+ return candidate
816
+ }
749
817
}
750
818
751
819
/**
@@ -775,22 +843,15 @@ function getScope(scopeManager: ScopeManager, currentNode: ESTree.Node): Scope {
775
843
function getInnermostScope ( initialScope : Scope , node : ESTree . Node ) : Scope {
776
844
const location = node . range ! [ 0 ]
777
845
778
- let scope = initialScope
779
- let found = false
780
- do {
781
- found = false
782
- for ( const childScope of scope . childScopes ) {
783
- const range = childScope . block . range !
846
+ for ( const childScope of initialScope . childScopes ) {
847
+ const range = childScope . block . range !
784
848
785
- if ( range [ 0 ] <= location && location < range [ 1 ] ) {
786
- scope = childScope
787
- found = true
788
- break
789
- }
849
+ if ( range [ 0 ] <= location && location < range [ 1 ] ) {
850
+ return getInnermostScope ( childScope , node )
790
851
}
791
- } while ( found )
852
+ }
792
853
793
- return scope
854
+ return initialScope
794
855
}
795
856
796
857
/**
@@ -807,10 +868,76 @@ function applyLocs(target: Locations | ESTree.Node, locs: Locations) {
807
868
}
808
869
}
809
870
871
+ /** Remove all reference */
872
+ function removeAllReference (
873
+ target : ESTree . Node ,
874
+ result : ScriptLetCallbackOption ,
875
+ ) {
876
+ traverseNodes ( target , {
877
+ visitorKeys : result . visitorKeys ,
878
+ enterNode ( node ) {
879
+ if ( node . type === "Identifier" ) {
880
+ const scope = result . getScope ( node )
881
+
882
+ removeIdentifierReference ( node , scope )
883
+ }
884
+ } ,
885
+ leaveNode ( ) {
886
+ // noop
887
+ } ,
888
+ } )
889
+ }
890
+
891
+ /** Remove reference */
892
+ function removeIdentifierReference (
893
+ node : ESTree . Identifier ,
894
+ scope : Scope ,
895
+ ) : boolean {
896
+ const reference = scope . references . find ( ( ref ) => ref . identifier === node )
897
+ if ( reference ) {
898
+ removeReference ( reference , scope )
899
+ return true
900
+ }
901
+ const location = node . range ! [ 0 ]
902
+
903
+ const pendingScopes = [ ]
904
+ for ( const childScope of scope . childScopes ) {
905
+ const range = childScope . block . range !
906
+
907
+ if ( range [ 0 ] <= location && location < range [ 1 ] ) {
908
+ if ( removeIdentifierReference ( node , childScope ) ) {
909
+ return true
910
+ }
911
+ } else {
912
+ pendingScopes . push ( childScope )
913
+ }
914
+ }
915
+ for ( const childScope of pendingScopes ) {
916
+ if ( removeIdentifierReference ( node , childScope ) ) {
917
+ return true
918
+ }
919
+ }
920
+ return false
921
+ }
922
+
810
923
/** Remove reference */
811
924
function removeReference ( reference : Reference , baseScope : Scope ) {
812
- let scope : Scope | null = baseScope
925
+ if (
926
+ reference . resolved &&
927
+ reference . resolved . defs . some ( ( d ) => d . name === reference . identifier )
928
+ ) {
929
+ // remove var
930
+ const varIndex = baseScope . variables . indexOf ( reference . resolved )
931
+ if ( varIndex >= 0 ) {
932
+ baseScope . variables . splice ( varIndex , 1 )
933
+ }
934
+ const name = reference . identifier . name
935
+ if ( reference . resolved === baseScope . set . get ( name ) ) {
936
+ baseScope . set . delete ( name )
937
+ }
938
+ }
813
939
940
+ let scope : Scope | null = baseScope
814
941
while ( scope ) {
815
942
const refIndex = scope . references . indexOf ( reference )
816
943
if ( refIndex >= 0 ) {
0 commit comments