diff --git a/tools/transpiler/spec/baz.js b/tools/transpiler/spec/baz.js new file mode 100644 index 0000000000..280a56ac4d --- /dev/null +++ b/tools/transpiler/spec/baz.js @@ -0,0 +1 @@ +export var Baz = 'BAZ'; diff --git a/tools/transpiler/spec/imports_spec.js b/tools/transpiler/spec/imports_spec.js index 62563f7a28..caadc4ca22 100644 --- a/tools/transpiler/spec/imports_spec.js +++ b/tools/transpiler/spec/imports_spec.js @@ -9,8 +9,14 @@ import * as exportModule from './export'; import {Type} from 'facade/lang'; +import {Baz} from './reexport'; + export function main() { describe('imports', function() { + it('should re-export imported vars', function() { + expect(Baz).toBe('BAZ'); + }); + it('should work', function() { expect(Foo).toBe('FOO'); expect(Bar).toBe('BAR'); diff --git a/tools/transpiler/spec/reexport.js b/tools/transpiler/spec/reexport.js new file mode 100644 index 0000000000..b7ad04b0f6 --- /dev/null +++ b/tools/transpiler/spec/reexport.js @@ -0,0 +1,10 @@ +import {Baz} from './baz'; +import {Bar1} from './bar'; + +var localVar = true; + +export {Baz, localVar, Bar1}; + +// Will become: +// export {Baz} from './baz'; +// export {Bar1} from './bar'; diff --git a/tools/transpiler/src/codegeneration/DartTransformer.js b/tools/transpiler/src/codegeneration/DartTransformer.js index 0baad5cd43..a35f892d28 100644 --- a/tools/transpiler/src/codegeneration/DartTransformer.js +++ b/tools/transpiler/src/codegeneration/DartTransformer.js @@ -8,6 +8,7 @@ import {InstanceOfTransformer} from './InstanceOfTransformer'; import {MultiVarTransformer} from './MultiVarTransformer'; import {StrictEqualityTransformer} from './StrictEqualityTransformer'; import {NamedParamsTransformer} from './NamedParamsTransformer'; +import {ExportTransformer} from './ExportTransformer'; /** * Transforms ES6 + annotations to Dart code. @@ -28,5 +29,6 @@ export class DartTransformer extends MultiTransformer { append(InstanceOfTransformer); append(StrictEqualityTransformer); append(ClassTransformer); + append(ExportTransformer); } } diff --git a/tools/transpiler/src/codegeneration/ExportTransformer.js b/tools/transpiler/src/codegeneration/ExportTransformer.js new file mode 100644 index 0000000000..8627108393 --- /dev/null +++ b/tools/transpiler/src/codegeneration/ExportTransformer.js @@ -0,0 +1,108 @@ +import {ParseTreeTransformer} from './ParseTreeTransformer'; +import {Map} from 'traceur/src/runtime/polyfills/Map'; +import { + ExportDeclaration, + ExportSpecifierSet, + NamedExport, + EmptyStatement +} from 'traceur/src/syntax/trees/ParseTrees'; +import { + IMPORT_DECLARATION, + NAMED_EXPORT +} from 'traceur/src/syntax/trees/ParseTreeType'; + + +// Return the index of the first item that is not of IMPORT_DECLARATION type. +function getIndexOfFirstNonImportStatement(items) { + var index = 0; + + while (index < items.length && items[index].type === IMPORT_DECLARATION) { + index++; + } + + return index; +} + + +/** + * Transforms re-exports: + * ``` + * import {Foo} from './foo'; + * import {Bar} from './bar'; + * var localVar = true; + * + * export {Foo, Bar, localVar} + * + * ===> + * + * import {Foo} from './foo'; + * import {Bar} from './bar'; + * var localVar = true; + * + * export {Foo} from './foo'; + * export {Bar} from './bar'; + * ``` + * + * In Dart, all variables defined in the root context of a module that don't start with + * an underscore are exported. Thus we just drop the "export" keyword for the locally defined vars. + * Variables imported from other modules need to be exported with `export './foo' show Foo`. + * This transformer drops the local exports and add the information about module path for the + * imported variables. + */ +export class ExportTransformer extends ParseTreeTransformer { + constructor(idGenerator, reporter) { + this.reporter_ = reporter; + this.importedVars_ = null; + this.collectedExports_ = null; + } + + transformModule(tree) { + // New context for each file (module). + this.importedVars_ = new Map(); + this.collectedExports_ = []; + + tree = super.transformModule(tree); + + // In Dart, imports and exports have to be at the top, before any other statement. + // Insert the collected exports before the first non-import statement (after all imports). + var items = tree.scriptItemList; + var index = getIndexOfFirstNonImportStatement(items); + tree.scriptItemList = items.slice(0, index).concat(this.collectedExports_, items.slice(index)); + + return tree; + } + + // For each imported variable, store the module path where it comes from. + // For instance, `import {Foo} from './foo'` will map 'Foo' -> './foo'. + // TODO(vojta): deal with `import * as m from './foo'`. + transformImportDeclaration(tree) { + tree.importClause.specifiers.forEach((specifier) => { + this.importedVars_.set(specifier.binding.binding.identifierToken.value, tree.moduleSpecifier); + }); + + return tree; + } + + transformExportDeclaration(tree) { + if (tree.declaration.type === NAMED_EXPORT && tree.declaration.moduleSpecifier === null) { + // export {...} + tree.declaration.specifierSet.specifiers.forEach((specifier) => { + // Filter out local variables, keep only imported ones. + if (!this.importedVars_.has(specifier.lhs.value)) { + return; + } + + // For each specifier, create a new ExportDeclaration and attach the module path (collected + // in `transformImportDeclaration`) to it. + this.collectedExports_.push(new ExportDeclaration(tree.location, + new NamedExport(tree.declaration.location, this.importedVars_.get(specifier.lhs.value), + new ExportSpecifierSet(tree.declaration.specifierSet.location, [specifier])), + tree.annotations)); + }); + + return new EmptyStatement(tree.location); + } + + return tree; + } +} diff --git a/tools/transpiler/src/outputgeneration/DartParseTreeWriter.js b/tools/transpiler/src/outputgeneration/DartParseTreeWriter.js index 503ca3f59b..bd0415ffde 100644 --- a/tools/transpiler/src/outputgeneration/DartParseTreeWriter.js +++ b/tools/transpiler/src/outputgeneration/DartParseTreeWriter.js @@ -25,7 +25,7 @@ import { import {ParseTreeWriter as JavaScriptParseTreeWriter, ObjectLiteralExpression} from 'traceur/src/outputgeneration/ParseTreeWriter'; import {ImportedBinding, BindingIdentifier} from 'traceur/src/syntax/trees/ParseTrees'; import {IdentifierToken} from 'traceur/src/syntax/IdentifierToken'; -import {EXPORT_STAR} from 'traceur/src/syntax/trees/ParseTreeType'; +import {EXPORT_STAR, NAMED_EXPORT} from 'traceur/src/syntax/trees/ParseTreeType'; export class DartParseTreeWriter extends JavaScriptParseTreeWriter { constructor(moduleName, outputPath) { @@ -34,6 +34,8 @@ export class DartParseTreeWriter extends JavaScriptParseTreeWriter { this.annotationContextCounter = 0; } + visitEmptyStatement() {} + // CLASS FIELDS visitPropertyVariableDeclaration(tree) { if (tree.isStatic) { @@ -275,35 +277,43 @@ export class DartParseTreeWriter extends JavaScriptParseTreeWriter { // EXPORTS visitExportDeclaration(tree) { - if (tree.declaration.moduleSpecifier) { - if (tree.declaration.specifierSet.type === EXPORT_STAR) { - // export * from './foo' - // ===> - // export './foo'; - this.write_('export'); - this.writeSpace_(); - this.visitModuleSpecifier(tree.declaration.moduleSpecifier); - this.write_(SEMI_COLON); + if (tree.declaration.type === NAMED_EXPORT) { + // export {...} + // export {...} from './foo' + // export * from './foo' + + if (tree.declaration.moduleSpecifier) { + if (tree.declaration.specifierSet.type === EXPORT_STAR) { + // export * from './foo' + // ===> + // export './foo'; + this.write_('export'); + this.writeSpace_(); + this.visitModuleSpecifier(tree.declaration.moduleSpecifier); + this.write_(SEMI_COLON); + } else { + // export {Foo, Bar} from './foo' + // ===> + // export './foo' show Foo, Bar; + this.write_('export'); + this.writeSpace_(); + this.visitModuleSpecifier(tree.declaration.moduleSpecifier); + this.writeSpace_(); + this.write_('show'); + this.writeSpace_(); + this.writeList_(tree.declaration.specifierSet.specifiers, COMMA, false); + this.write_(SEMI_COLON); + } } else { - // export {Foo, Bar} from './foo' - // ===> - // export './foo' show Foo, Bar; - this.write_('export'); - this.writeSpace_(); - this.visitModuleSpecifier(tree.declaration.moduleSpecifier); - this.writeSpace_(); - this.write_('show'); - this.writeSpace_(); - this.writeList_(tree.declaration.specifierSet.specifiers, COMMA, false); - this.write_(SEMI_COLON); + // export {...} + // This case is handled in `ExportTransformer`. + throw new Error('Should never happen!'); } } else { - // Just remove the "export" keyword. - // export var x = true; + // export var x = true // export class Foo {} - // ===> - // var x = true; - // class Foo {} + // export function bar() {} + // Just remove "export" keyword. this.writeAnnotations_(tree.annotations); this.visitAny(tree.declaration); }