import * as assert from "assert" ; import { Variable , VariableType } from "../variable" ; import { Reference , ImplicitGlobal } from "../reference" ; import { ScopeManager } from "../scopeManager" ; import { Definition } from "../definition" ; import { Syntax } from "estraverse" ; import * as ESTree from "estree" ; export type ScopeType = "TDZ" | "module" | "block" | "switch" | "function" | "catch" | "with" | "function" | "class" | "global" | "function-expression-name" | "for" ; /**
 * Test if scope is strict
 */ function isStrictScope ( scope , block , isMethodDefinition , useDirective ) { let body ; if ( scope . upper && scope . upper . isStrict ) { return true ; } if ( block . type === Syntax . ArrowFunctionExpression ) { return true ; } if ( isMethodDefinition ) { return true ; } if ( scope . type === "class" || scope . type === "module" ) { return true ; } if ( scope . type === "block" || scope . type === "switch" ) { return false ; } if ( scope . type === "function" ) { if ( block . type === Syntax . Program ) { body = block ; } else { body = ( block as any ) . body ; } if ( ! body ) { return false ; } } else if ( scope . type === "global" ) { body = block ; } else { return false ; } if ( useDirective ) { for ( let i = 0 , iz = body . body . length ; i < iz ; ++ i ) { const stmt = body . body [ i ] ; if ( stmt . type !== Syntax . DirectiveStatement ) { break ; } if ( stmt . raw === '"use strict"' || stmt . raw === "'use strict'" ) { return true ; } } } else { for ( let i = 0 , iz = body . body . length ; i < iz ; ++ i ) { const stmt = body . body [ i ] ; if ( stmt . type !== Syntax . ExpressionStatement ) { break ; } const expr = stmt . expression ; if ( expr . type !== Syntax . Literal || typeof expr . value !== "string" ) { break ; } if ( expr . raw !== null && expr . raw !== undefined ) { if ( expr . raw === '"use strict"' || expr . raw === "'use strict'" ) { return true ; } } else { if ( expr . value === "use strict" ) { return true ; } } } } return false ; } function registerScope ( scopeManager , scope ) { scopeManager . scopes . push ( scope ) ; const scopes = scopeManager . __nodeToScope . get ( scope . block ) ; if ( scopes ) { scopes . push ( scope ) ; } else { scopeManager . __nodeToScope . set ( scope . block , [ scope ] ) ; } } function shouldBeStatically ( def ) { return ( def . type === VariableType . ClassName || ( def . type === VariableType . Variable && ( def . parent as ESTree . VariableDeclaration ) . kind !== "var" ) ) ; } export class Scope < BlockType extends ESTree . Node = ESTree . Node > { public dynamic : boolean ; public variableScope : Scope ; public functionExpressionScope : boolean = false ; public directCallToEvalScope : boolean = false ; public thisFound : boolean = false ; public __left : Reference [ ] | null = [ ] ; public isStrict : boolean ; public readonly set : Map < string , Variable > = new Map ( ) ; public readonly taints : Map < string , boolean > = new Map ( ) ; public readonly through : Reference [ ] = [ ] ; public readonly variables : Variable [ ] = [ ] ; public readonly references : Reference [ ] = [ ] ; public readonly childScopes : Scope [ ] = [ ] ; public readonly __declaredVariables : WeakMap < any , Variable [ ] > ; public constructor ( scopeManager , public readonly type , public readonly upper = null , public readonly block , isMethodDefinition ) { /**
         * Generally, through the lexical scoping of JS you can always know
         * which variable an identifier in the source code refers to. There are
         * a few exceptions to this rule. With 'global' and 'with' scopes you
         * can only decide at runtime which variable a reference refers to.
         * Moreover, if 'eval()' is used in a scope, it might introduce new
         * bindings in this or its parent scopes.
         * All those scopes are considered 'dynamic'.
         */ this . dynamic = this . type === "global" || this . type === "with" ; /**
         * For 'global' and 'function' scopes, this is a self-reference. For
         * other scope types this is the <em>variableScope</em> value of the
         * parent scope.
         */ this . variableScope = this . type === "global" || this . type === "function" || this . type === "module" ? this : this . upper ! . variableScope ; member this . isStrict = isStrictScope ( this , block , isMethodDefinition , scopeManager . __useDirective ( ) ) ; if ( this . upper ) { this . upper . childScopes . push ( this ) ; } this . __declaredVariables = scopeManager . __declaredVariables ; registerScope ( scopeManager , this ) ; } public __shouldStaticallyClose ( scopeManager ) { return ! this . dynamic || scopeManager . __isOptimistic ( ) ; } public __shouldStaticallyCloseForGlobal ( ref ) { const name = ref . identifier . name ; if ( ! this . set . has ( name ) ) { return false ; } const variable = this . set . get ( name ) ! ; const defs = variable . defs ; return defs . length > 0 && defs . every ( shouldBeStatically ) ; } public __staticCloseRef ( ref ) { if ( ! this . __resolve ( ref ) ) { this . __delegateToUpperScope ( ref ) ; } } public __dynamicCloseRef ( ref ) { let current = this ; do { current . through . push ( ref ) ; current = current . upper ! ; } while ( current ) ; } public __globalCloseRef ( ref ) { if ( this . __shouldStaticallyCloseForGlobal ( ref ) ) { this . __staticCloseRef ( ref ) ; } else { this . __dynamicCloseRef ( ref ) ; } } public __close ( scopeManager ) { let closeRef ; if ( this . __shouldStaticallyClose ( scopeManager ) ) { closeRef = this . __staticCloseRef ; } else if ( this . type !== "global" ) { closeRef = this . __dynamicCloseRef ; } else { closeRef = this . __globalCloseRef ; } for ( let i = 0 , iz = this . __left ! . length ; i < iz ; ++ i ) { const ref = this . __left ! [ i ] ; closeRef . call ( this , ref ) ; } this . __left = null ; return this . upper ; } public __isValidResolution ( ref , variable ) { return true ; } public __resolve ( ref ) { const name = ref . identifier . name ; if ( ! this . set . has ( name ) ) { return false ; } const variable = this . set . get ( name ) ! ; if ( ! this . __isValidResolution ( ref , variable ) ) { return false ; } variable . references . push ( ref ) ; variable . stack = variable . stack && ref . from . variableScope === this . variableScope ; if ( ref . tainted ) { variable . tainted = true ; this . taints . set ( variable . name , true ) ; } ref . resolved = variable ; return true ; } public __delegateToUpperScope ( ref ) { if ( this . upper ) { this . upper . __left ! . push ( ref ) ; } this . through . push ( ref ) ; } public __addDeclaredVariablesOfNode ( variable , node ) { if ( node === null || node === undefined ) { return ; } let variables = this . __declaredVariables . get ( node ) ; if ( variables === null || variables === undefined ) { variables = [ ] ; this . __declaredVariables . set ( node , variables ) ; } if ( variables . indexOf ( variable ) === - 1 ) { variables . push ( variable ) ; } } protected __defineGeneric ( name , set , variables , node , def ? ) { let variable ; variable = set . get ( name ) ! ; if ( ! variable ) { variable = new Variable ( name , this ) ; set . set ( name , variable ) ; variables . push ( variable ) ; } if ( def ) { variable . defs . push ( def ) ; if ( def . type !== VariableType . TDZ ) { this . __addDeclaredVariablesOfNode ( variable , def . node ) ; this . __addDeclaredVariablesOfNode ( variable , def . parent ) ; } } if ( node ) { variable . identifiers . push ( node ) ; } return variable ; } public __define ( node , def ) { if ( node && node . type === Syntax . Identifier ) { return this . __defineGeneric ( node . name , this . set , this . variables , node , def ) ; } return null ; } public __referencing ( node , assign ? , writeExpr ? , maybeImplicitGlobal ? , partial ? , init ? , isExportingFromLocal = false ) { if ( ! node || node . type !== Syntax . Identifier ) { return ; } if ( node . name === "super" ) { return ; } const ref = new Reference ( node , this , isExportingFromLocal ? Reference . EXPORT : assign || Reference . READ , writeExpr , maybeImplicitGlobal , ! ! partial , ! ! init ) ; this . references . push ( ref ) ; this . __left ! . push ( ref ) ; return ref ; } public __detectEval ( ) { let current = this ; this . directCallToEvalScope = true ; do { current . dynamic = true ; current = current . upper ! ; } while ( current ) ; } public __detectThis ( ) { this . thisFound = true ; } public __isClosed ( ) { return this . __left === null ; } public resolve ( ident ) { let ref , i , iz ; assert ( this . __isClosed ( ) , "Scope should be closed." ) ; assert ( ident . type === Syntax . Identifier , "Target should be identifier." ) ; for ( i = 0 , iz = this . references . length ; i < iz ; ++ i ) { ref = this . references [ i ] ; if ( ref . identifier === ident ) { return ref ; } } return null ; } public isStatic ( ) { return ! this . dynamic ; } public isArgumentsMaterialized ( ) { return true ; } public isThisMaterialized ( ) { return true ; } public isUsedName ( name ) { if ( this . set . has ( name ) ) { return true ; } for ( let i = 0 , iz = this . through . length ; i < iz ; ++ i ) { if ( this . through [ i ] . identifier . name === name ) { return true ; } } return false ; } }