@@ -119,6 +119,8 @@ type TempScope = {
119119
120120type ScopeCollectionVisitorState = {
121121 sourceId : SourceId ,
122+ freeVariables : Map < string , Array < BindingLocation> > ,
123+ freeVariableStack : Array < Map < string , Array < BindingLocation> >> ,
122124 scope : TempScope ,
123125 scopeStack : Array < TempScope >
124126} ;
@@ -133,11 +135,20 @@ export function parseSourceScopes(sourceId: SourceId): ?Array<ParsedScope> {
133135
134136 const state = {
135137 sourceId,
138+ freeVariables : new Map ( ) ,
139+ freeVariableStack : [ ] ,
136140 scope : lexical ,
137141 scopeStack : [ ]
138142 } ;
139143 t . traverse ( ast , scopeCollectionVisitor , state ) ;
140144
145+ for ( const [ key , freeVariables ] of state . freeVariables ) {
146+ const binding = global . bindings [ key ] ;
147+ if ( binding ) {
148+ binding . refs = freeVariables . concat ( binding . refs ) ;
149+ }
150+ }
151+
141152 // TODO: This should probably check for ".mjs" extension on the
142153 // original file, and should also be skipped if the the generated
143154 // code is an ES6 module rather than a script.
@@ -206,6 +217,9 @@ function pushTempScope(
206217 const scope = createTempScope ( type , displayName , state . scope , loc ) ;
207218
208219 state . scope = scope ;
220+
221+ state . freeVariableStack . push ( state . freeVariables ) ;
222+ state . freeVariables = new Map ( ) ;
209223 return scope ;
210224}
211225
@@ -305,19 +319,6 @@ function isLexicalVariable(node) {
305319 return isNode ( node , "VariableDeclaration" ) && isLetOrConst ( node ) ;
306320}
307321
308- function findIdentifierInScopes (
309- scope : TempScope ,
310- name : string
311- ) : TempScope | null {
312- // Find nearest outer scope with the specifed name and add reference.
313- for ( let s = scope ; s ; s = s . parent ) {
314- if ( name in s . bindings ) {
315- return s ;
316- }
317- }
318- return null ;
319- }
320-
321322function createGlobalScope (
322323 ast : BabelNode ,
323324 sourceId : SourceId
@@ -579,25 +580,31 @@ const scopeCollectionVisitor = {
579580 }
580581 } ) ;
581582 } else if ( t . isIdentifier ( node ) && t . isReferenced ( node , parentNode ) ) {
582- const identScope = findIdentifierInScopes ( state . scope , node . name ) ;
583- if ( identScope ) {
584- identScope . bindings [ node . name ] . refs . push ( {
585- type : "ref" ,
586- start : fromBabelLocation ( node . loc . start , state . sourceId ) ,
587- end : fromBabelLocation ( node . loc . end , state . sourceId ) ,
588- meta : buildMetaBindings ( state . sourceId , node , ancestors )
589- } ) ;
583+ let freeVariables = state . freeVariables . get ( node . name ) ;
584+ if ( ! freeVariables ) {
585+ freeVariables = [ ] ;
586+ state . freeVariables . set ( node . name , freeVariables ) ;
590587 }
588+
589+ freeVariables . push ( {
590+ type : "ref" ,
591+ start : fromBabelLocation ( node . loc . start , state . sourceId ) ,
592+ end : fromBabelLocation ( node . loc . end , state . sourceId ) ,
593+ meta : buildMetaBindings ( state . sourceId , node , ancestors )
594+ } ) ;
591595 } else if ( t . isThisExpression ( node ) ) {
592- const identScope = findIdentifierInScopes ( state . scope , "this" ) ;
593- if ( identScope ) {
594- identScope . bindings . this . refs . push ( {
595- type : "ref" ,
596- start : fromBabelLocation ( node . loc . start , state . sourceId ) ,
597- end : fromBabelLocation ( node . loc . end , state . sourceId ) ,
598- meta : buildMetaBindings ( state . sourceId , node , ancestors )
599- } ) ;
596+ let freeVariables = state . freeVariables . get ( "this" ) ;
597+ if ( ! freeVariables ) {
598+ freeVariables = [ ] ;
599+ state . freeVariables . set ( "this" , freeVariables ) ;
600600 }
601+
602+ freeVariables . push ( {
603+ type : "ref" ,
604+ start : fromBabelLocation ( node . loc . start , state . sourceId ) ,
605+ end : fromBabelLocation ( node . loc . end , state . sourceId ) ,
606+ meta : buildMetaBindings ( state . sourceId , node , ancestors )
607+ } ) ;
601608 } else if ( t . isClassProperty ( parentNode , { value : node } ) ) {
602609 const scope = pushTempScope ( state , "function" , "Class Field" , {
603610 start : fromBabelLocation ( node . loc . start , state . sourceId ) ,
@@ -628,12 +635,49 @@ const scopeCollectionVisitor = {
628635 ancestors : TraversalAncestors ,
629636 state : ScopeCollectionVisitorState
630637 ) {
631- const scope = state . scopeStack . pop ( ) ;
632- if ( ! scope ) {
638+ const currentScope = state . scope ;
639+ const parentScope = state . scopeStack . pop ( ) ;
640+ if ( ! parentScope ) {
633641 throw new Error ( "Assertion failure - unsynchronized pop" ) ;
634642 }
643+ state . scope = parentScope ;
644+
645+ // It is possible, as in the case of function expressions, that a single
646+ // node has added multiple scopes, so we need to traverse upward here
647+ // rather than jumping stright to 'parentScope'.
648+ for (
649+ let scope = currentScope ;
650+ scope && scope !== parentScope ;
651+ scope = scope . parent
652+ ) {
653+ const freeVariables = state . freeVariables ;
654+ state . freeVariables = state . freeVariableStack . pop ( ) ;
655+ const parentFreeVariables = state . freeVariables ;
656+
657+ // Match up any free variables that match this scope's bindings and
658+ // merge then into the refs.
659+ for ( const key of Object . keys ( scope . bindings ) ) {
660+ const binding = scope . bindings [ key ] ;
661+
662+ const freeVars = freeVariables . get ( key ) ;
663+ if ( freeVars ) {
664+ binding . refs . push ( ...freeVars ) ;
665+ freeVariables . delete ( key ) ;
666+ }
667+ }
635668
636- state . scope = scope ;
669+ // Move any undeclared references in this scope into the parent for
670+ // processing in higher scopes.
671+ for ( const [ key , value ] of freeVariables ) {
672+ let refs = parentFreeVariables . get ( key ) ;
673+ if ( ! refs ) {
674+ refs = [ ] ;
675+ parentFreeVariables . set ( key , refs ) ;
676+ }
677+
678+ refs . push ( ...value ) ;
679+ }
680+ }
637681 }
638682} ;
639683
0 commit comments