feat(transiler/dart): re-export imported vars

```
import {Foo} from ‘./foo’;
var localVar = true;
export {Foo, localVar};

===>

import ‘./foo’ show Foo;
export ‘./foo’ show Foo;
var localVar = true;
```

Closes #41
This commit is contained in:
Vojta Jina 2014-11-10 17:18:30 -08:00
parent c5153175b6
commit c68e78075a
6 changed files with 163 additions and 26 deletions

View File

@ -0,0 +1 @@
export var Baz = 'BAZ';

View File

@ -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');

View File

@ -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';

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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);
}