import { walk } from 'estree-walker'; import { Node } from '../interfaces'; export default function annotateWithScopes ( expression: Node ) { let scope = new Scope( null, false ); walk( expression, { enter ( node: Node ) { if ( /Function/.test( node.type ) ) { if ( node.type === 'FunctionDeclaration' ) { scope.declarations.add( node.id.name ); } else { node._scope = scope = new Scope( scope, false ); if ( node.id ) scope.declarations.add( node.id.name ); } node.params.forEach( ( param: Node ) => { extractNames( param ).forEach( name => { scope.declarations.add( name ); }); }); } else if ( /For(?:In|Of)Statement/.test( node.type ) ) { node._scope = scope = new Scope( scope, true ); } else if ( node.type === 'BlockStatement' ) { node._scope = scope = new Scope( scope, true ); } else if ( /(Function|Class|Variable)Declaration/.test( node.type ) ) { scope.addDeclaration( node ); } }, leave ( node: Node ) { if ( node._scope ) { scope = scope.parent; } } }); return scope; } class Scope { parent: Scope block: boolean declarations: Set constructor ( parent: Scope, block: boolean ) { this.parent = parent; this.block = block; this.declarations = new Set(); } addDeclaration ( node: Node ) { if ( node.kind === 'var' && !this.block && this.parent ) { this.parent.addDeclaration( node ); } else if ( node.type === 'VariableDeclaration' ) { node.declarations.forEach( ( declarator: Node ) => { extractNames( declarator.id ).forEach( name => { this.declarations.add( name ); }); }); } else { this.declarations.add( node.id.name ); } } has ( name: string ) :boolean { return this.declarations.has( name ) || this.parent && this.parent.has( name ); } } function extractNames ( param: Node ) { const names: string[] = []; extractors[ param.type ]( names, param ); return names; } const extractors = { Identifier ( names: string[], param: Node ) { names.push( param.name ); }, ObjectPattern ( names: string[], param: Node ) { param.properties.forEach( ( prop: Node ) => { extractors[ prop.value.type ]( names, prop.value ); }); }, ArrayPattern ( names: string[], param: Node ) { param.elements.forEach( ( element: Node ) => { if ( element ) extractors[ element.type ]( names, element ); }); }, RestElement ( names: string[], param: Node ) { extractors[ param.argument.type ]( names, param.argument ); }, AssignmentPattern ( names: string[], param: Node ) { extractors[ param.left.type ]( names, param.left ); } };