Browse Source

`this` at the top level is undefined (fixes #28)

contingency-plan
Rich Harris 10 years ago
parent
commit
69509da0ad
  1. 148
      src/Statement.js
  2. 3
      test/function/this-is-undefined/_config.js
  3. 16
      test/function/this-is-undefined/main.js

148
src/Statement.js

@ -266,94 +266,102 @@ export default class Statement {
deshadowList.push( replacement.split( '.' )[0] ); deshadowList.push( replacement.split( '.' )[0] );
}); });
if ( nameList.length > 0 || keys( bundleExports ).length ) { let topLevel = true;
let topLevel = true; let depth = 0;
walk( this.node, { walk( this.node, {
enter ( node, parent ) { enter ( node, parent ) {
if ( node._skip ) return this.skip(); if ( node._skip ) return this.skip();
// special case - variable declarations that need to be rewritten if ( /^Function/.test( node.type ) ) depth += 1;
// as bundle exports
if ( topLevel ) { // `this` is undefined at the top level of ES6 modules
if ( node.type === 'VariableDeclaration' ) { if ( node.type === 'ThisExpression' && depth === 0 ) {
// if this contains a single declarator, and it's one that magicString.overwrite( node.start, node.end, 'undefined' );
// needs to be rewritten, we replace the whole lot }
const name = node.declarations[0].id.name;
if ( node.declarations.length === 1 && bundleExports[ name ] ) { // special case - variable declarations that need to be rewritten
magicString.overwrite( node.start, node.declarations[0].id.end, bundleExports[ name ] ); // as bundle exports
node.declarations[0].id._skip = true; if ( topLevel ) {
} if ( node.type === 'VariableDeclaration' ) {
// if this contains a single declarator, and it's one that
// needs to be rewritten, we replace the whole lot
const name = node.declarations[0].id.name;
if ( node.declarations.length === 1 && bundleExports[ name ] ) {
magicString.overwrite( node.start, node.declarations[0].id.end, bundleExports[ name ] );
node.declarations[0].id._skip = true;
}
// otherwise, we insert the `exports.foo = foo` after the declaration // otherwise, we insert the `exports.foo = foo` after the declaration
else { else {
const exportInitialisers = node.declarations const exportInitialisers = node.declarations
.map( declarator => declarator.id.name ) .map( declarator => declarator.id.name )
.filter( name => !!bundleExports[ name ] ) .filter( name => !!bundleExports[ name ] )
.map( name => `\n${bundleExports[name]} = ${name};` ) .map( name => `\n${bundleExports[name]} = ${name};` )
.join( '' ); .join( '' );
// TODO clean this up // TODO clean this up
try { try {
magicString.insert( node.end, exportInitialisers ); magicString.insert( node.end, exportInitialisers );
} catch ( err ) { } catch ( err ) {
magicString.append( exportInitialisers ); magicString.append( exportInitialisers );
}
} }
} }
} }
}
const scope = node._scope; const scope = node._scope;
if ( scope ) {
topLevel = false;
let newNames = blank(); if ( scope ) {
let hasReplacements; topLevel = false;
keys( names ).forEach( key => { let newNames = blank();
if ( !scope.declarations[ key ] ) { let hasReplacements;
newNames[ key ] = names[ key ];
hasReplacements = true;
}
});
deshadowList.forEach( name => { keys( names ).forEach( key => {
if ( ~scope.declarations[ name ] ) { if ( !scope.declarations[ key ] ) {
newNames[ name ] = name + '$$'; // TODO better mechanism newNames[ key ] = names[ key ];
hasReplacements = true; hasReplacements = true;
} }
}); });
if ( !hasReplacements ) { deshadowList.forEach( name => {
return this.skip(); if ( ~scope.declarations[ name ] ) {
newNames[ name ] = name + '$$'; // TODO better mechanism
hasReplacements = true;
} }
});
names = newNames; if ( !hasReplacements && depth > 0 ) {
replacementStack.push( newNames ); return this.skip();
} }
// We want to rewrite identifiers (that aren't property names etc) names = newNames;
if ( node.type !== 'Identifier' ) return; replacementStack.push( newNames );
if ( parent.type === 'MemberExpression' && !parent.computed && node !== parent.object ) return; }
if ( parent.type === 'Property' && node !== parent.value ) return;
// TODO others...?
const name = names[ node.name ]; // We want to rewrite identifiers (that aren't property names etc)
if ( node.type !== 'Identifier' ) return;
if ( parent.type === 'MemberExpression' && !parent.computed && node !== parent.object ) return;
if ( parent.type === 'Property' && node !== parent.value ) return;
// TODO others...?
if ( name && name !== node.name ) { const name = names[ node.name ];
magicString.overwrite( node.start, node.end, name );
}
},
leave ( node ) { if ( name && name !== node.name ) {
if ( node._scope ) { magicString.overwrite( node.start, node.end, name );
replacementStack.pop();
names = replacementStack[ replacementStack.length - 1 ];
}
} }
}); },
}
leave ( node ) {
if ( /^Function/.test( node.type ) ) depth -= 1;
if ( node._scope ) {
replacementStack.pop();
names = replacementStack[ replacementStack.length - 1 ];
}
}
});
return magicString; return magicString;
} }

3
test/function/this-is-undefined/_config.js

@ -1,3 +1,4 @@
module.exports = { module.exports = {
description: 'this at top level is undefined' description: 'this at top level is undefined',
babel: [ 'es6.arrowFunctions', 'es6.blockScoping' ]
}; };

16
test/function/this-is-undefined/main.js

@ -1 +1,17 @@
const fooContext = {}
function foo () {
// inside a function, `this` should be untouched...
assert.strictEqual( this, fooContext );
}
const bar = () => {
// ...unless it's an arrow function
assert.strictEqual( this, undefined );
}
foo.call( fooContext );
bar.call( {} );
// outside a function, `this` is undefined
assert.strictEqual( this, undefined ); assert.strictEqual( this, undefined );

Loading…
Cancel
Save