import { Node , walk } from 'estree-walker' ; import extractAssignedNames from './extractAssignedNames' ; import { AttachedScope , AttachScopes } from './pluginutils' ; const blockDeclarations = { const : true , let : true } ; interface ScopeOptions { parent ? : AttachedScope ; block ? : boolean ; params ? : Array < Node > ; } class Scope implements AttachedScope { parent ? : AttachedScope ; isBlockScope : boolean ; declarations : { [ key ] : boolean ; } ; constructor ( options = { } ) { this . parent = options . parent ; this . isBlockScope = ! ! options . block ; this . declarations = Object . create ( null ) ; if ( options . params ) { options . params . forEach ( param => { extractAssignedNames ( param ) . forEach ( name => { this . declarations [ name ] = true ; } ) ; } ) ; } } addDeclaration ( node , isBlockDeclaration , isVar ) { if ( ! isBlockDeclaration && this . isBlockScope ) { this . parent ! . addDeclaration ( node , isBlockDeclaration , isVar ) ; } else if ( node . id ) { extractAssignedNames ( node . id ) . forEach ( name => { this . declarations [ name ] = true ; } ) ; } } contains ( name ) { return this . declarations [ name ] || ( this . parent ? this . parent . contains ( name ) : false ) ; } } const attachScopes = function attachScopes ( ast , propertyName = 'scope' ) { let scope = new Scope ( ) ; walk ( ast , { enter ( node , parent ) { if ( /(Function|Class)Declaration/ . test ( node . type ) ) { scope . addDeclaration ( node , false , false ) ; } if ( node . type === 'VariableDeclaration' ) { const kind = node . kind ; const isBlockDeclaration = blockDeclarations [ kind ] ; node . declarations . forEach ( ( declaration ) => { scope . addDeclaration ( declaration , isBlockDeclaration , true ) ; } ) ; } let newScope ; if ( /Function/ . test ( node . type ) ) { newScope = new Scope ( { parent : scope , block : false , params : node . params } ) ; if ( node . type === 'FunctionExpression' && node . id ) { newScope . addDeclaration ( node , false , false ) ; } } if ( node . type === 'BlockStatement' && ! /Function/ . test ( parent ! . type ) ) { newScope = new Scope ( { parent : scope , block : true } ) ; } if ( node . type === 'CatchClause' ) { newScope = new Scope ( { parent : scope , params : node . param ? [ node . param ] : [ ] , block : true } ) ; } if ( newScope ) { Object . defineProperty ( node , propertyName , { value : newScope , configurable : true } ) ; scope = newScope ; } } , leave ( node ) { if ( node [ propertyName ] ) scope = scope . parent ! ; } } ) ; return scope ; } ; export { attachScopes as default } ;