diff --git a/src/Module.js b/src/Module.js index 3fa88fc..f682260 100644 --- a/src/Module.js +++ b/src/Module.js @@ -28,30 +28,59 @@ export default class Module { // Try to extract a list of top-level statements/declarations. If // the parse fails, attach file info and abort + let ast; + try { - const ast = parse( source, { + ast = parse( source, { ecmaVersion: 6, sourceType: 'module', onComment: ( block, text, start, end ) => this.comments.push({ block, text, start, end }) }); - - walk( ast, { - enter: node => { - this.magicString.addSourcemapLocation( node.start ); - this.magicString.addSourcemapLocation( node.end ); - } - }); - - this.statements = ast.body.map( ( node, i ) => { - const magicString = this.magicString.snip( node.start, node.end ).trim(); - return new Statement( node, magicString, this, i ); - }); } catch ( err ) { err.code = 'PARSE_ERROR'; err.file = path; throw err; } + walk( ast, { + enter: node => { + this.magicString.addSourcemapLocation( node.start ); + this.magicString.addSourcemapLocation( node.end ); + } + }); + + this.statements = []; + + ast.body.map( node => { + // special case - top-level var declarations with multiple declarators + // should be split up. Otherwise, we may end up including code we + // don't need, just because an unwanted declarator is included + if ( node.type === 'VariableDeclaration' && node.declarations.length > 1 ) { + node.declarations.forEach( declarator => { + const magicString = this.magicString.snip( declarator.start, declarator.end ).trim(); + magicString.prepend( `${node.kind} ` ).append( ';' ); + + const syntheticNode = { + type: 'VariableDeclaration', + kind: node.kind, + start: node.start, + end: node.end, + declarations: [ declarator ] + }; + + const statement = new Statement( syntheticNode, magicString, this, this.statements.length ); + this.statements.push( statement ); + }); + } + + else { + const magicString = this.magicString.snip( node.start, node.end ).trim(); + const statement = new Statement( node, magicString, this, this.statements.length ); + + this.statements.push( statement ); + } + }); + this.importDeclarations = this.statements.filter( isImportDeclaration ); this.exportDeclarations = this.statements.filter( isExportDeclaration ); diff --git a/test/function/statement-order/_config.js b/test/function/statement-order/_config.js new file mode 100644 index 0000000..92efd7d --- /dev/null +++ b/test/function/statement-order/_config.js @@ -0,0 +1,13 @@ +var assert = require( 'assert' ); + +module.exports = { + description: 'correct statement order is preserved even in weird edge cases', + context: { + getAnswer: function ( obj ) { + return obj.answer; + } + }, + exports: function ( exports ) { + assert.equal( exports, 'right' ); + } +}; diff --git a/test/function/statement-order/answer.js b/test/function/statement-order/answer.js new file mode 100644 index 0000000..d49dcae --- /dev/null +++ b/test/function/statement-order/answer.js @@ -0,0 +1,17 @@ +var prop, +answer; + +var foo = { answer: 'wrong' }; +var bar = { answer: 'right' }; + +if ( typeof bar === "object" ) { + for ( prop in bar ) { + if ( bar.hasOwnProperty(prop) ) { + foo[prop] = bar[prop]; + } + } +} + +answer = getAnswer( foo ); + +export default answer; diff --git a/test/function/statement-order/main.js b/test/function/statement-order/main.js new file mode 100644 index 0000000..808b675 --- /dev/null +++ b/test/function/statement-order/main.js @@ -0,0 +1,3 @@ +import answer from './answer'; +var answer2 = answer; +export default answer2;