@ -25,12 +25,19 @@ function isFunctionDeclaration ( node, parent ) {
if ( node . type === 'FunctionExpression' && parent . type === 'VariableDeclarator' ) return true ;
}
// Extract the property access from a MemberExpression.
function property ( node ) {
return node . name ? ` . ${ node . name } ` : ` [ ${ node . value } ] ` ;
}
// Recursively traverse the chain of member expressions from `node`,
// returning the access, e.g. `foo.bar[17]`
function chainedMemberExpression ( node ) {
if ( node . object . type === 'MemberExpression' ) {
return chainedMemberExpression ( node . object ) + '.' + node . property . name ;
return chainedMemberExpression ( node . object ) + property ( node . property ) ;
}
return node . object . name + '.' + node . property . name ;
return node . object . name + property ( node . property ) ;
}
export default class Statement {
@ -161,10 +168,10 @@ export default class Statement {
// /update expressions) need to be captured
let writeDepth = 0 ;
// Used to track
let topName ;
let currentMemberExpression = null ;
let namespace = null ;
// Used to track and optimize lookups through namespaces.
let localName ; // The local name of the top-most imported namespace.
let topNode = null ; // The top-node of the member expression.
let namespace = null ; // An instance of `Module`.
if ( ! this . isImportDeclaration ) {
walk ( this . node , {
@ -184,13 +191,21 @@ export default class Statement {
if ( node . _ scope ) scope = scope . parent ;
// Optimize namespace lookups, which manifest as MemberExpressions.
if ( node . type === 'MemberExpression' && ( ! currentMemberExpression || node . object === currentMemberExpression ) ) {
currentMemberExpression = node ;
if ( node . type === 'MemberExpression' && ( ! topNode || node . object === topNode ) ) {
// Ignore anything that doesn't begin with an identifier.
if ( ! topNode && node . object . type !== 'Identifier' ) return ;
topNode = node ;
// If we don't already have a namespace,
// we aren't currently exploring any chain of member expressions.
if ( ! namespace ) {
topName = node . object . name ;
const id = this . module . locals . lookup ( topName ) ;
localName = node . object . name ;
// At first, we don't have a namespace, so we'll try to look one up.
const id = this . module . locals . lookup ( localName ) ;
// It only counts if it exists, is a module, and isn't external.
if ( ! id || ! id . isModule || id . isExternal ) return ;
namespace = id ;
@ -225,16 +240,16 @@ export default class Statement {
namespace . mark ( ) ;
namespace = null ;
currentMemberExpression = null ;
topNode = null ;
return ;
}
const id = namespace . exports . lookup ( name ) ;
// If the namespace doesn't defin e the given name,
// If the namespace doesn't export the given name,
// we can throw an error (even for nested namespaces).
if ( ! id ) {
throw new Error ( ` Module doesn't defin e " ${ name } "! ` ) ;
throw new Error ( ` Module " ${ namespace . id } " doesn't export " ${ name } "! ` ) ;
}
// We can't resolve deeper. Replace the member chain.
@ -244,18 +259,18 @@ export default class Statement {
}
// FIXME: do this better
// If we depend on this name...
if ( this . dependsOn [ top Name ] ) {
// If an earlier stage detected that we depend on this name...
if ( this . dependsOn [ local Name ] ) {
// ... decrement the count...
if ( ! -- this . dependsOn [ top Name ] ) {
if ( ! -- this . dependsOn [ local Name ] ) {
// ... and remove it if the count is 0.
delete this . dependsOn [ top Name ] ;
delete this . dependsOn [ local Name ] ;
}
}
this . namespaceReplacements . push ( [ n ode, id ] ) ;
this . namespaceReplacements . push ( [ topN ode, id ] ) ;
namespace = null ;
currentMemberExpression = null ;
topNode = null ;
return ;
}
@ -486,7 +501,8 @@ export default class Statement {
topLevel = false ;
let newNames = blank ( ) ;
let hasReplacements ;
// Consider a scope to have replacements if there are any namespaceReplacements.
let hasReplacements = statement . namespaceReplacements . length > 0 ;
keys ( names ) . forEach ( name => {
if ( ! scope . declarations [ name ] ) {