diff --git a/src/Module.js b/src/Module.js index 0308278..946a96e 100644 --- a/src/Module.js +++ b/src/Module.js @@ -6,7 +6,7 @@ import Statement from './Statement'; import walk from './ast/walk'; import analyse from './ast/analyse'; import { blank, keys } from './utils/object'; -import { sequence } from './utils/promise'; +import { first, sequence } from './utils/promise'; import { isImportDeclaration, isExportDeclaration } from './utils/map-helpers'; import getLocation from './utils/getLocation'; import makeLegalIdentifier from './utils/makeLegalIdentifier'; @@ -43,6 +43,11 @@ export default class Module { this.imports = blank(); this.exports = blank(); + this.exportAlls = blank(); + + // array of all-export sources + this.exportDelegates = []; + this.canonicalNames = blank(); this.definitions = blank(); @@ -124,6 +129,15 @@ export default class Module { }; } } + + // Store `export * from '...'` statements in an array of delegates. + // When an unknown import is encountered, we see if one of them can satisfy it. + else { + this.exportDelegates.push({ + statement, + source + }); + } } addImport ( statement ) { @@ -195,6 +209,13 @@ export default class Module { keys( statement.stronglyDependsOn ).forEach( name => { if ( statement.defines[ name ] ) return; + const exportAllDeclaration = this.exportAlls[ name ]; + + if ( exportAllDeclaration && exportAllDeclaration.module && !exportAllDeclaration.module.isExternal ) { + strongDependencies[ exportAllDeclaration.module.id ] = exportAllDeclaration.module; + return; + } + const importDeclaration = this.imports[ name ]; if ( importDeclaration && importDeclaration.module && !importDeclaration.module.isExternal ) { @@ -287,7 +308,13 @@ export default class Module { exporterLocalName = importDeclaration.name; } else { const exportDeclaration = module.exports[ importDeclaration.name ]; - exporterLocalName = exportDeclaration.localName; + + // The export declaration of the particular name is known. + if (exportDeclaration) { + exporterLocalName = exportDeclaration.localName; + } else { // export * from '...' + exporterLocalName = importDeclaration.name; + } } canonicalName = module.getCanonicalName( exporterLocalName ); @@ -362,7 +389,27 @@ export default class Module { const exportDeclaration = module.exports[ importDeclaration.name ]; if ( !exportDeclaration ) { - throw new Error( `Module ${module.id} does not export ${importDeclaration.name} (imported by ${this.id})` ); + const noExport = new Error( `Module ${module.id} does not export ${importDeclaration.name} (imported by ${this.id})` ); + + // See if there exists an export delegate that defines `name`. + return first( module.exportDelegates, noExport, declaration => { + return module.bundle.fetchModule( declaration.source, module.id ).then( submodule => { + declaration.module = submodule; + + return submodule.mark( name ).then( result => { + if ( !result.length ) throw noExport; + + // It's found! This module exports `name` through declaration. + // It is however not imported into this scope. + module.exportAlls[ name ] = declaration; + + declaration.statement.dependsOn[ name ] = + declaration.statement.stronglyDependsOn[ name ] = result; + + return result; + }); + }); + }); } return module.mark( exportDeclaration.localName ); diff --git a/src/Statement.js b/src/Statement.js index 96d47c8..6f0b5b6 100644 --- a/src/Statement.js +++ b/src/Statement.js @@ -31,6 +31,7 @@ export default class Statement { // some facts about this statement... this.isImportDeclaration = node.type === 'ImportDeclaration'; this.isExportDeclaration = /^Export/.test( node.type ); + this.isExportAllDeclaration = /^ExportAll/.test( node.type ); } analyse () { diff --git a/src/utils/promise.js b/src/utils/promise.js index 5f60489..e37489d 100644 --- a/src/utils/promise.js +++ b/src/utils/promise.js @@ -19,4 +19,24 @@ export function sequence ( arr, callback ) { } return promise.then( () => results ); -} \ No newline at end of file +} + + +export function first ( arr, fail, callback ) { + const len = arr.length; + + let promise = Promise.reject( fail ); + + function next ( i ) { + return promise + .catch(() => callback( arr[i], i )); + } + + let i; + + for ( i = 0; i < len; i += 1 ) { + promise = next( i ); + } + + return promise; +} diff --git a/test/function/export-all/_config.js b/test/function/export-all/_config.js new file mode 100644 index 0000000..24639c6 --- /dev/null +++ b/test/function/export-all/_config.js @@ -0,0 +1,5 @@ +var assert = require( 'assert' ); + +module.exports = { + description: 'allows export *' +}; diff --git a/test/function/export-all/main.js b/test/function/export-all/main.js new file mode 100644 index 0000000..8ba6367 --- /dev/null +++ b/test/function/export-all/main.js @@ -0,0 +1,2 @@ +import { wat } from './wat'; +assert.equal( wat(), 4711 ); diff --git a/test/function/export-all/wat-impl.js b/test/function/export-all/wat-impl.js new file mode 100644 index 0000000..d21f786 --- /dev/null +++ b/test/function/export-all/wat-impl.js @@ -0,0 +1 @@ +export function wat () { return 4711; } diff --git a/test/function/export-all/wat.js b/test/function/export-all/wat.js new file mode 100644 index 0000000..f37162c --- /dev/null +++ b/test/function/export-all/wat.js @@ -0,0 +1 @@ +export * from './wat-impl';