@@ -97,6 +97,8 @@ type RestoreCallback = {
9797 callback : ScriptLetRestoreCallback
9898}
9999
100+ type TypeGenHelper = { generateUniqueId : ( base : string ) => string }
101+
100102/**
101103 * A class that handles script fragments.
102104 * The script fragment AST node remaps and connects to the original directive AST node.
@@ -110,6 +112,10 @@ export class ScriptLetContext {
110112
111113 private readonly closeScopeCallbacks : ( ( ) => void ) [ ] = [ ]
112114
115+ private uniqueIdSeq = 1
116+
117+ private readonly usedUniqueIds = new Set < string > ( )
118+
113119 public constructor ( ctx : Context ) {
114120 this . script = ctx . sourceCode . scripts
115121 this . ctx = ctx
@@ -327,7 +333,12 @@ export class ScriptLetContext {
327333 nodes : ESTree . Pattern [ ] ,
328334 options : ScriptLetCallbackOption ,
329335 ) => void ,
330- typings : string [ ] ,
336+ typings :
337+ | string [ ]
338+ | ( ( helper : TypeGenHelper ) => {
339+ typings : string [ ]
340+ preparationScript ?: string
341+ } ) ,
331342 ) : void
332343
333344 public nestBlock (
@@ -338,8 +349,34 @@ export class ScriptLetContext {
338349 nodes : ESTree . Pattern [ ] ,
339350 options : ScriptLetCallbackOption ,
340351 ) => void ,
341- typings ?: string [ ] ,
352+ typings ?:
353+ | string [ ]
354+ | ( ( helper : TypeGenHelper ) => {
355+ typings : string [ ]
356+ preparationScript ?: string
357+ } ) ,
342358 ) : 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+ }
343380 if ( ! params ) {
344381 const restore = this . appendScript (
345382 `{` ,
@@ -381,7 +418,7 @@ export class ScriptLetContext {
381418 range,
382419 } )
383420 if ( this . ctx . isTypeScript ( ) ) {
384- source += ` : (${ typings ! [ index ] } )`
421+ source += ` : (${ arrayTypings [ index ] } )`
385422 }
386423 }
387424 const restore = this . appendScript (
@@ -407,6 +444,8 @@ export class ScriptLetContext {
407444 line : typeAnnotation . loc . start . line ,
408445 column : typeAnnotation . loc . start . column ,
409446 }
447+
448+ removeAllReference ( typeAnnotation , result )
410449 }
411450 }
412451
@@ -462,21 +501,37 @@ export class ScriptLetContext {
462501 options : ScriptLetCallbackOption ,
463502 ) => void ,
464503 ) {
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 ) => {
471507 this . fixLocations (
472508 node ,
473509 tokens ,
474510 comments ,
475- offset - startOffset ,
511+ offset - resultCallback . start ,
476512 result . visitorKeys ,
477513 )
478514 callback ( node , tokens , comments , result )
479515 } ,
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,
480535 }
481536 this . restoreCallbacks . push ( restoreCallback )
482537 return restoreCallback
@@ -746,6 +801,19 @@ export class ScriptLetContext {
746801 applyLocs ( t , locs )
747802 }
748803 }
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+ }
749817}
750818
751819/**
@@ -775,22 +843,15 @@ function getScope(scopeManager: ScopeManager, currentNode: ESTree.Node): Scope {
775843function getInnermostScope ( initialScope : Scope , node : ESTree . Node ) : Scope {
776844 const location = node . range ! [ 0 ]
777845
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 !
784848
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 )
790851 }
791- } while ( found )
852+ }
792853
793- return scope
854+ return initialScope
794855}
795856
796857/**
@@ -807,10 +868,76 @@ function applyLocs(target: Locations | ESTree.Node, locs: Locations) {
807868 }
808869}
809870
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+
810923/** Remove reference */
811924function 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+ }
813939
940+ let scope : Scope | null = baseScope
814941 while ( scope ) {
815942 const refIndex = scope . references . indexOf ( reference )
816943 if ( refIndex >= 0 ) {
0 commit comments