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] );
});
if ( nameList.length > 0 || keys( bundleExports ).length ) {
let topLevel = true;
let topLevel = true;
let depth = 0;
walk( this.node, {
enter ( node, parent ) {
if ( node._skip ) return this.skip();
// special case - variable declarations that need to be rewritten
// as bundle exports
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;
}
walk( this.node, {
enter ( node, parent ) {
if ( node._skip ) return this.skip();
if ( /^Function/.test( node.type ) ) depth += 1;
// `this` is undefined at the top level of ES6 modules
if ( node.type === 'ThisExpression' && depth === 0 ) {
magicString.overwrite( node.start, node.end, 'undefined' );
}
// special case - variable declarations that need to be rewritten
// as bundle exports
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
else {
const exportInitialisers = node.declarations
.map( declarator => declarator.id.name )
.filter( name => !!bundleExports[ name ] )
.map( name => `\n${bundleExports[name]} = ${name};` )
.join( '' );
// TODO clean this up
try {
magicString.insert( node.end, exportInitialisers );
} catch ( err ) {
magicString.append( exportInitialisers );
}
// otherwise, we insert the `exports.foo = foo` after the declaration
else {
const exportInitialisers = node.declarations
.map( declarator => declarator.id.name )
.filter( name => !!bundleExports[ name ] )
.map( name => `\n${bundleExports[name]} = ${name};` )
.join( '' );
// TODO clean this up
try {
magicString.insert( node.end, exportInitialisers );
} catch ( err ) {
magicString.append( exportInitialisers );
}
}
}
}
const scope = node._scope;
if ( scope ) {
topLevel = false;
const scope = node._scope;
let newNames = blank();
let hasReplacements;
if ( scope ) {
topLevel = false;
keys( names ).forEach( key => {
if ( !scope.declarations[ key ] ) {
newNames[ key ] = names[ key ];
hasReplacements = true;
}
});
let newNames = blank();
let hasReplacements;
deshadowList.forEach( name => {
if ( ~scope.declarations[ name ] ) {
newNames[ name ] = name + '$$'; // TODO better mechanism
hasReplacements = true;
}
});
keys( names ).forEach( key => {
if ( !scope.declarations[ key ] ) {
newNames[ key ] = names[ key ];
hasReplacements = true;
}
});
if ( !hasReplacements ) {
return this.skip();
deshadowList.forEach( name => {
if ( ~scope.declarations[ name ] ) {
newNames[ name ] = name + '$$'; // TODO better mechanism
hasReplacements = true;
}
});
names = newNames;
replacementStack.push( newNames );
if ( !hasReplacements && depth > 0 ) {
return this.skip();
}
// 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...?
names = newNames;
replacementStack.push( newNames );
}
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 ) {
magicString.overwrite( node.start, node.end, name );
}
},
const name = names[ node.name ];
leave ( node ) {
if ( node._scope ) {
replacementStack.pop();
names = replacementStack[ replacementStack.length - 1 ];
}
if ( name && name !== node.name ) {
magicString.overwrite( node.start, node.end, name );
}
});
}
},
leave ( node ) {
if ( /^Function/.test( node.type ) ) depth -= 1;
if ( node._scope ) {
replacementStack.pop();
names = replacementStack[ replacementStack.length - 1 ];
}
}
});
return magicString;
}

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

@ -1,3 +1,4 @@
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 );

Loading…
Cancel
Save