[Pkg-javascript-commits] [node-es6-module-transpiler] 01/09: Imported Upstream version 0.10.0
Julien Puydt
julien.puydt at laposte.net
Tue Oct 20 07:14:04 UTC 2015
This is an automated email from the git hooks/post-receive script.
jpuydt-guest pushed a commit to branch master
in repository node-es6-module-transpiler.
commit a279d996c70f1c441f40878f3a704461f7898fe5
Author: Julien Puydt <julien.puydt at laposte.net>
Date: Mon Oct 19 17:31:39 2015 +0200
Imported Upstream version 0.10.0
---
.editorconfig | 5 +
.gitignore | 2 +
.jshintrc | 27 ++
.travis.yml | 5 +
AUTHORS | 1 +
CHANGELOG.md | 152 ++++++
LICENSE | 13 +
README.md | 192 ++++++++
TRANSITION.md | 168 +++++++
bin/compile-modules | 84 ++++
lib/browser/capture_stack_trace_polyfill.js | 75 +++
lib/browser/fs.js | 4 +
lib/browser/index.js | 20 +
lib/cli/convert.js | 152 ++++++
lib/container.js | 255 ++++++++++
lib/declaration_info.js | 82 ++++
lib/exports.js | 365 ++++++++++++++
lib/file_resolver.js | 89 ++++
lib/formatters.js | 5 +
lib/formatters/bundle_formatter.js | 497 +++++++++++++++++++
lib/formatters/commonjs_formatter.js | 537 +++++++++++++++++++++
lib/formatters/formatter.js | 198 ++++++++
lib/imports.js | 221 +++++++++
lib/index.js | 11 +
lib/module.js | 202 ++++++++
lib/module_binding_declaration.js | 133 +++++
lib/module_binding_list.js | 244 ++++++++++
lib/module_binding_specifier.js | 244 ++++++++++
lib/replacement.js | 95 ++++
lib/rewriter.js | 416 ++++++++++++++++
lib/sorting.js | 45 ++
lib/utils.js | 134 +++++
lib/writer.js | 78 +++
package.json | 60 +++
test/examples/bare-import/exporter.js | 3 +
test/examples/bare-import/importer.js | 5 +
test/examples/bindings/exporter.js | 7 +
test/examples/bindings/importer.js | 7 +
test/examples/cycles-defaults/a.js | 5 +
test/examples/cycles-defaults/b.js | 5 +
test/examples/cycles-defaults/importer.js | 9 +
test/examples/cycles-immediate/evens.js | 20 +
test/examples/cycles-immediate/main.js | 24 +
test/examples/cycles-immediate/odds.js | 18 +
test/examples/cycles/a.js | 9 +
test/examples/cycles/b.js | 9 +
test/examples/cycles/c.js | 9 +
test/examples/duplicate-import-fails/exporter.js | 3 +
test/examples/duplicate-import-fails/importer.js | 7 +
.../duplicate-import-specifier-fails/exporter.js | 3 +
.../duplicate-import-specifier-fails/importer.js | 5 +
.../export-and-import-reference-share-var/first.js | 4 +
.../second.js | 15 +
test/examples/export-class-expression/exporter.js | 3 +
test/examples/export-class-expression/importer.js | 5 +
test/examples/export-class/exporter.js | 3 +
test/examples/export-class/importer.js | 5 +
test/examples/export-default-class/exporter.js | 8 +
test/examples/export-default-class/importer.js | 6 +
test/examples/export-default-function/exporter.js | 5 +
test/examples/export-default-function/importer.js | 8 +
.../export-default-named-function/exporter.js | 7 +
.../export-default-named-function/importer.js | 4 +
test/examples/export-default/exporter.js | 16 +
test/examples/export-default/importer.js | 12 +
test/examples/export-from-default/first.js | 3 +
test/examples/export-from-default/second.js | 3 +
test/examples/export-from-default/third.js | 4 +
test/examples/export-from/first.js | 3 +
test/examples/export-from/second.js | 7 +
test/examples/export-from/third.js | 4 +
test/examples/export-function/exporter.js | 6 +
test/examples/export-function/importer.js | 4 +
test/examples/export-list/exporter.js | 11 +
test/examples/export-list/importer.js | 9 +
test/examples/export-mixins/exporter.js | 2 +
test/examples/export-mixins/importer.js | 4 +
test/examples/export-named-class/exporter.js | 4 +
test/examples/export-named-class/importer.js | 5 +
.../export-not-at-top-level-fails/index.js | 6 +
test/examples/export-var/exporter.js | 4 +
test/examples/export-var/importer.js | 4 +
test/examples/import-as/exporter.js | 5 +
test/examples/import-as/importer.js | 7 +
test/examples/import-chain/first.js | 3 +
test/examples/import-chain/second.js | 4 +
test/examples/import-chain/third.js | 4 +
.../import-not-at-top-level-fails/index.js | 6 +
test/examples/module-level-declarations/mod.js | 8 +
.../examples/named-function-expression/exporter.js | 1 +
.../examples/named-function-expression/importer.js | 9 +
.../namespace-reassign-import-fails/exporter.js | 3 +
.../namespace-reassign-import-fails/importer.js | 6 +
.../namespace-update-import-fails/exporter.js | 3 +
.../namespace-update-import-fails/importer.js | 6 +
test/examples/namespaces/exporter.js | 5 +
test/examples/namespaces/importer.js | 13 +
test/examples/re-export-default-import/first.js | 5 +
test/examples/re-export-default-import/second.js | 3 +
test/examples/re-export-default-import/third.js | 4 +
test/examples/re-export-namespace-import/first.js | 2 +
test/examples/re-export-namespace-import/second.js | 2 +
test/examples/re-export-namespace-import/third.js | 4 +
test/examples/reassign-import-fails/exporter.js | 3 +
test/examples/reassign-import-fails/importer.js | 11 +
.../exporter.js | 3 +
.../importer.js | 11 +
test/examples/this-is-global/mod.js | 8 +
.../update-expression-of-import-fails/exporter.js | 3 +
.../update-expression-of-import-fails/importer.js | 6 +
test/runner.js | 250 ++++++++++
test/support/expected_error.js | 116 +++++
test/support/test_formatter.js | 72 +++
test/support/test_resolver.js | 36 ++
test/unit/container_test.js | 120 +++++
test/unit/mkdirp_test.js | 56 +++
test/unit/module_binding_specifier_test.js | 110 +++++
test/unit/rewriter_test.js | 30 ++
test/unit/sorting_test.js | 77 +++
119 files changed, 6158 insertions(+)
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..2d0bffa
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,5 @@
+[*.js]
+indent_style = space
+indent_size = 2
+insert_final_newline = true
+trim_trailing_whitespace = true
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..32efc03
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+node_modules/
+test/results
diff --git a/.jshintrc b/.jshintrc
new file mode 100644
index 0000000..fff515c
--- /dev/null
+++ b/.jshintrc
@@ -0,0 +1,27 @@
+{
+ "node" : true,
+ "browser" : false,
+ "boss" : true,
+ "curly": false,
+ "debug": false,
+ "devel": false,
+ "eqeqeq": true,
+ "evil": true,
+ "forin": false,
+ "immed": false,
+ "laxbreak": false,
+ "newcap": true,
+ "noarg": true,
+ "noempty": false,
+ "nonew": false,
+ "nomen": false,
+ "onevar": false,
+ "plusplus": false,
+ "regexp": false,
+ "undef": false,
+ "sub": true,
+ "strict": false,
+ "white": false,
+ "eqnull": true,
+ "esnext": true
+}
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..3e3b998
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,5 @@
+language: node_js
+node_js:
+ - 0.10
+before_install:
+ - npm install -g grunt-cli
diff --git a/AUTHORS b/AUTHORS
new file mode 100644
index 0000000..4ba0c7b
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1 @@
+Brian Donovan <donovan at squareup.com>
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..3387059
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,152 @@
+# Release Notes
+
+## v0.9.5 <span class="release-date">(2014-11-14)</span>
+
+* Fix source maps to include directory names as well as file names.
+
+## v0.9.4 <span class="release-date">(2014-11-14)</span>
+
+* Fix re-exporting module objects created by namespace imports (e.g. `import * as foo from 'foo'; export { foo }`).
+
+## v0.9.3 <span class="release-date">(2014-11-03)</span>
+
+* Fix bundle format support for named exports of class declarations.
+
+## v0.9.2 <span class="release-date">(2014-11-03)</span>
+
+* Add support for exporting ES6 class declarations and expressions.
+
+## v0.9.1 <span class="release-date">(2014-11-03)</span>
+
+* Clarify README to indicate that ES6 module syntax is now stable.
+* Add `Container#transform` to get transformed code without writing to the file system.
+
+## v0.9.0 <span class="release-date">(2014-10-22)</span>
+
+* Add support for namespace imports (e.g. `import * as foo from 'foo'`).
+
+## v0.8.3 <span class="release-date">(2014-10-17)</span>
+
+* Fixed internal helper `mkdirpSync` that could cause `Container#write` to fail on some machines.
+
+## v0.8.2 <span class="release-date">(2014-10-15)</span>
+
+* Fixed bundle formatter renaming of function or class declarations in export default. Previously they were not properly renamed to prevent collision with other modules.
+
+## v0.8.1 <span class="release-date">(2014-10-15)</span>
+
+* Fixed bundle formatter rewriting identifiers inside named function expressions if the identifier shared a name with a module-scope binding. For example, in this code sample the `a` in `return a` would be rewritten as `mod$$a` when it should have remained `a` (the [fix](https://github.com/benjamn/ast-types/pull/68) was in ast-types).
+
+```js
+// mod.js
+var a;
+var fn = function f() { var a; return a; };
+```
+
+## v0.8.0 <span class="release-date">(2014-09-30)</span>
+
+* Simplify the CommonJS formatter output to use ES3.
+
+## v0.7.0 <span class="release-date">(2014-09-30)</span>
+
+* Use a common base class for all built-in formatters.
+* Various internal cleanups.
+
+## v0.6.2 <span class="release-date">(2014-08-20)</span>
+
+* Prevent all instances of export reassignment, not just those at the top level.
+
+## v0.6.1 <span class="release-date">(2014-08-20)</span>
+
+* Reference a custom fork of esprima.
+* Allow resolvers to set the `Module#src` property to prevent direct file system access.
+
+## v0.6.0 <span class="release-date">(2014-07-28)</span>
+
+* Add support for source maps.
+* Allow formatters to handle export reassignment.
+* Update recast to v0.6.6.
+
+## v0.5.1 <span class="release-date">(2014-06-25)</span>
+
+* Fix the file list to be included in npm.
+
+## v0.5.0 <span class="release-date">(2014-06-24)</span>
+
+* Completely re-written using [recast](https://github.com/benjamn/recast).
+* Removed YUI support, to be re-added as a plugin.
+* Removed AMD support, to be re-added as a plugin.
+* Added "bundle" formatter for concatenating all modules together.
+* Assert on various invalid module syntax usage.
+
+## v0.4.0 <span class="release-date">(2014-04-03)</span>
+
+* Remove trailing whitespace after AMD define (#93)
+* (**breaking**) Default to anonymous modules for AMD output (use `--infer-name` flag for old behavior). Replaces `--anonymous` flag.
+
+## v0.3.6 <span class="release-date">(2013-12-16)</span>
+
+* Rebuilt & republished to fix regression on quoting `static` property (sorry!)
+
+## v0.3.5 <span class="release-date">(2013-12-01)</span>
+
+* Fixed incorrect module path strings in CJS output (#82)
+
+## v0.3.4 <span class="release-date">(2013-11-19)</span>
+
+* CJS: Build a module object when using `module foo from "foo"` for better forward-compatibility.
+* Added YUI transpiler, lovingly created & maintained by the YUI team
+* Fixed `'static'` keyword not being quoted in Esprima, causing issues in some JS runtimes
+
+## v0.3.3 <span class="release-date">(2013-10-25)</span>
+
+* Fix syntax error in CommonJS output with default imports and `compatFix` option
+
+## v0.3.2 <span class="release-date">(2013-10-18)</span>
+
+* Fixes path resolution on the command line (thanks rpflorence!)
+
+## v0.3.1 <span class="release-date">(2013-10-17)</span>
+
+* Use a working commit for Esprima
+
+## v0.3.0 <span class="release-date">(2013-10-16)</span>
+
+This is a **major, breaking version**. See TRANSITION.md for information on upgrading your code.
+
+* Rewrote the transpiler using Esprima
+* Support default exports and named exports in the same module
+ * Default export now exports to `moduleObject.default`, see TRANSITION.md for details
+* Fixed multiline export parsing
+* Added support for `module` keyword (i.e. `module foo from "foo"`)
+* Added support for `import "foo";` form
+* fixed the `--anonymous` flag with the CLI for recursive transpiling (#20)
+* Removed relative pathing for AMD resolution & direct CoffeeScript transpilation (see TRANSITION.md)
+
+## v0.2.0 <span class="release-date">(2013-07-08)</span>
+
+* added support for default export (`export default jQuery`)
+* added support for default import (`import $ from "jquery"`)
+* added support for re-exporting properties (`export { ajax } from "jquery"`)
+* removed support for `export =` syntax (use `export default`)
+* removed support for `import "jquery" as $` (use `import $ from "jquery"`)
+
+## v0.1.3 <span class="release-date">(2013-06-21)</span>
+
+* fixed: import/export statements within block comments are now ignored
+* added support for `export var foo = …`
+* added support for `export function foo() { … }`
+
+## v0.1.2 <span class="release-date">(2013-03-07)</span>
+
+* use Grunt for building the project
+* fixed: use local variables for imports
+
+## v0.1.1 <span class="release-date">(2013-02-24)</span>
+
+* fixed: add missing `--global` option to CLI
+* documentation and clarifications of examples
+
+## v0.1.0 <span class="release-date">(2013-02-11)</span>
+
+* initial release
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..cabe814
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,13 @@
+Copyright 2014 Square Inc.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..8657243
--- /dev/null
+++ b/README.md
@@ -0,0 +1,192 @@
+# ES6 Module Transpiler [![Build Status](https://travis-ci.org/esnext/es6-module-transpiler.png)](https://travis-ci.org/esnext/es6-module-transpiler)
+
+ES6 Module Transpiler is an experimental compiler that allows you to write your
+JavaScript using a subset of the ES6 module syntax, and compile it into
+AMD or CommonJS modules.
+
+This compiler provides a way to experiment with ES6 syntax in real world
+scenarios to see how the syntax holds up. It also provides a nicer, more
+declarative way to write AMD (or CommonJS) modules.
+
+See the [CHANGELOG](./CHANGELOG.md) for the latest updates.
+
+## Usage
+
+### Build tools
+
+The easiest way to use the transpiler is from an existing build tool. There
+several plugins developed for different build tools:
+
+* **Grunt:** [grunt-es6-module-transpiler](https://github.com/joefiorini/grunt-es6-module-transpiler), maintained by @joefiorini (not yet compatible with v0.5.x)
+* **Gulp:** [gulp-es6-module-transpiler](https://github.com/ryanseddon/gulp-es6-module-transpiler), maintained by @ryanseddon
+* **Brunch:** [es6-module-transpiler-brunch](https://github.com/gcollazo/es6-module-transpiler-brunch), maintained by @gcollazo *(CommonJS only)* (not yet compatible with v0.5.x)
+* **Broccoli:** [broccoli-es6-concatenator](https://github.com/joliss/broccoli-es6-concatenator), maintained by @joliss (not yet compatible with v0.5.x)
+* **Mimosa:** [mimosa-es6-module-transpiler](https://github.com/dbashford/mimosa-es6-module-transpiler), maintained by @dbashford (not yet compatible with v0.5.x)
+* **AMD Formatter:** [es6-module-transpiler-amd-formatter](https://github.com/caridy/es6-module-transpiler-amd-formatter), maintained by @caridy (compatible with v0.5.x+ only)
+
+### Executable
+
+The transpiler can be used directly from the command line:
+
+```
+$ npm install -g es6-module-transpiler
+$ compile-modules convert foo.js
+```
+
+Here is the basic usage:
+
+```
+compile-modules convert -I lib -o out FILE [FILE…]
+```
+
+### Library
+
+You can also use the transpiler as a library:
+
+```javascript
+var transpiler = require('es6-module-transpiler');
+var Container = transpiler.Container;
+var FileResolver = transpiler.FileResolver;
+var BundleFormatter = transpiler.formatters.bundle;
+
+var container = new Container({
+ resolvers: [new FileResolver(['lib/'])],
+ formatter: new BundleFormatter()
+});
+
+container.getModule('index');
+container.write('out/mylib.js');
+```
+
+## Supported ES6 Module Syntax
+
+### Named Exports
+
+There are two types of exports. *Named exports* like the following:
+
+```javascript
+// foobar.js
+var foo = 'foo', bar = 'bar';
+
+export { foo, bar };
+```
+
+This module has two named exports, `foo` and `bar`.
+
+You can also write this form as:
+
+```javascript
+// foobar.js
+export var foo = 'foo';
+export var bar = 'bar';
+```
+
+Either way, another module can then import your exports like so:
+
+```js
+import { foo, bar } from 'foobar';
+
+console.log(foo); // 'foo'
+```
+
+### Default Exports
+
+You can also export a *default* export. For example, an ES6ified jQuery might
+look like this:
+
+```javascript
+// jquery.js
+var jQuery = function() {};
+
+jQuery.prototype = {
+ // ...
+};
+
+export default jQuery;
+```
+
+Then, an app that uses jQuery could import it with:
+
+```javascript
+import $ from 'jquery';
+```
+
+The default export of the "jquery" module is now aliased to `$`.
+
+A default export makes the most sense as a module's "main" export, like the
+`jQuery` object in jQuery. You can use default and named exports in parallel.
+
+### Other Syntax
+
+#### `import "foo";`
+
+A "bare import" that doesn't import any identifiers is useful for executing
+side effects in a module. For example:
+
+```js
+// alerter.js
+alert("alert! alert!");
+
+// alertee.js
+import "alerter"; // will pop up alert box
+```
+
+## Compiled Output
+
+### Default Exports
+
+This is super important:
+
+**Default exports bind to an identifier on the module called `default`!**
+
+Internally, the transpiler will use this default identifer when importing, but
+any outside consumer needs to be aware that it should use the `default` key and
+not the module itself. For example, a CommonJS consumer should look like this:
+
+```js
+var $ = require('jquery')['default'];
+```
+
+## Installation
+
+Add this project to your application's package.json by running this:
+
+ $ npm install --save es6-module-transpiler
+
+Or install it globally:
+
+ $ npm install -g es6-module-transpiler
+
+## Acknowledgements
+
+Thanks to [Yehuda Katz](https://twitter.com/wycats) for
+[js_module_transpiler](https://github.com/wycats/js_module_transpiler), the
+library on which this one is based. Thanks to [Dave
+Herman](https://twitter.com/littlecalculist) for his work on ES6 modules.
+Thanks to [Erik Bryn](https://twitter.com/ebryn) for providing the initial push
+to write this library. Thanks to [Domenic
+Denicola](https://twitter.com/domenic), [Jo Liss](https://twitter.com/jo_liss),
+& [Thomas Boyt](https://twitter.com/thomasaboyt) for their efforts to make this
+project even better. And finally thanks to the JavaScript community at Square
+for helping to write and release this library.
+
+## Contributing
+
+1. Fork it
+2. Create your feature branch (`git checkout -b my-new-feature`)
+3. Commit your changes (`git commit -am 'Add some feature'`)
+4. Push to the branch (`git push origin my-new-feature`)
+5. Create new Pull Request
+
+Any contributors to the master es6-module-transpiler repository must sign the
+[Individual Contributor License Agreement (CLA)][cla]. It's a short form that
+covers our bases and makes sure you're eligible to contribute.
+
+[cla]: https://spreadsheets.google.com/spreadsheet/viewform?formkey=dDViT2xzUHAwRkI3X3k5Z0lQM091OGc6MQ&ndplr=1
+
+When you have a change you'd like to see in the master repository, [send a pull
+request](https://github.com/esnext/es6-module-transpiler/pulls). Before we merge
+your request, we'll make sure you're in the list of people who have signed a
+CLA.
+
+Thanks, and enjoy living in the ES6 future!
diff --git a/TRANSITION.md b/TRANSITION.md
new file mode 100644
index 0000000..0a6ebc8
--- /dev/null
+++ b/TRANSITION.md
@@ -0,0 +1,168 @@
+# Transitioning from 0.4.x to 0.5.x
+
+## API Changes
+
+The API has completely changed. We still allow transpiling to CommonJS, but any
+of the other previously supported formats have been removed. Each output format
+is handled by a "formatter", and 3rd-party formatters may be used by using the
+`--format` option in the CLI or initialing a `Container` with a particular
+`formatter` when using as a library.
+
+## Command-line changes
+
+The transpiler still has a CLI, but it is structured completely differently.
+See `compile-modules -h` for details.
+
+## Spec compilance
+
+### Bindings
+
+In order to comply with the spec, this project now supports mutable bindings.
+For example, given this:
+
+```js
+export var count = 0;
+export function incr() { count++; }
+```
+
+And when it's imported, this will work:
+
+```js
+import { count, incr } from './count';
+assert.equal(count, 0);
+incr();
+assert.equal(count, 1);
+```
+
+### Circular References
+
+Cycles now work properly. Note that not all cases of cycles can be properly
+handled - this is simply the nature of cycles. For example, this works:
+
+```js
+// a.js
+import { b } from './b';
+
+export function a(n) {
+ if (n % 2 === 0) {
+ return b(n);
+ } else {
+ return n + 1;
+ }
+}
+
+// b.js
+import { a } from './a';
+
+export function b(n) {
+ if (n % 2 === 0) {
+ return n;
+ } else {
+ return a(n);
+ }
+}
+```
+
+This works because neither `a` nor `b` uses the other until sometime "later".
+This second example will not work:
+
+```js
+// a.js
+import { b } from './b';
+export var a = b;
+
+// b.js
+import { a } from './a';
+export var b = a;
+```
+
+This is a contrived example, obviously, but many more complicated examples
+boil down to this same thing.
+
+# Transitioning from 0.2.x to 0.3.x
+
+## default export changes
+
+### Spec changes
+
+`export default foo;` has been removed in favor of `export default = foo`.
+
+### Internal changes
+
+In 0.2.x, the default export was the module's only export. Now, it's internally a named export called `default`:
+
+```js
+// es6
+export default bar;
+
+//cjs
+exports.default = bar;
+
+// es6
+import bar from "bar";
+
+// cjs
+var bar = require("bar").default;
+```
+
+This means that your "entry point" - anywhere you're importing the transpiled output from AMD or CJS - needs to explicitly import `default`. For example, if your AMD app was using ES6-built Handlebars, you would need to do:
+
+```js
+define("my-module",
+ ["handlebars"],
+ function (handlebars) {
+ var handlebars = handlebars.default;
+ })
+```
+
+## New features you should use
+
+* Multi line exports!
+
+```js
+export default = {
+ foo: "\n to your heart's content!"
+};
+```
+
+* Module keyword!
+
+```js
+module foo from "foo";
+var namedExport = foo.namedExport;
+```
+
+* Mixed default/named exports!
+
+```js
+export default = "foo";
+export var bar = "bar";
+```
+
+* Bare imports!
+
+```js
+// executes side effects in "foo" but doesn't import anything
+import "foo";
+```
+
+## Old features you can no longer use
+
+* Relative pathing in AMD modules has been removed, as it was broken, hacky, and made people sad.
+
+* CoffeeScript support is removed, sort of.
+
+If you're using **the original CoffeeScript compiler**, you can use JS passthrough:
+
+```coffeescript
+foo = "look at this elegant coffeescript!"
+
+`
+// now we're in sad curly-brace land
+export default = foo;
+`
+```
+
+You'll then want to transpile your modules using the compiled JS as a base.
+
+Unfortunately, **this doesn't work with CoffeeScript Redux (or EmberScript)**, because that compiler wraps pass-through in an `eval` call.
diff --git a/bin/compile-modules b/bin/compile-modules
new file mode 100755
index 0000000..9c3f3d0
--- /dev/null
+++ b/bin/compile-modules
@@ -0,0 +1,84 @@
+#!/usr/bin/env node
+
+var Path = require('path');
+var exe = Path.basename(process.argv[1]);
+var getopt = require('posix-getopt');
+var parser = new getopt.BasicParser('h(help)v(version)', process.argv);
+var option;
+
+function usage(puts) {
+ puts(exe + ' [--help] [--version] <command> [<args>]');
+ puts();
+ puts('Commands');
+ puts();
+ puts(' convert Converts modules from `import`/`export` to an ES5 equivalent.');
+ puts(' help Display help for a given command.');
+}
+
+function makeWriteLine(stream) {
+ return function(line) {
+ if (!line || line[line.length - 1] !== '\n') {
+ line = (line || '') + '\n';
+ }
+ stream.write(line);
+ };
+}
+
+var puts = makeWriteLine(process.stdout);
+var eputs = makeWriteLine(process.stderr);
+
+while ((option = parser.getopt()) !== undefined) {
+ if (option.error) {
+ usage(eputs);
+ process.exit(1);
+ }
+
+ switch (option.option) {
+ case 'h':
+ usage(puts);
+ process.exit(0);
+ break;
+
+ case 'v':
+ puts(exe + ' v' + require(Path.join(__dirname, '../package.json')).version);
+ process.exit(0);
+ break;
+ }
+}
+
+var args = process.argv;
+var offset = parser.optind();
+
+var commandName = args[offset];
+if (commandName === 'help') {
+ commandName = args[offset + 1];
+ args = ['--help'].concat(args.slice(offset + 2 /* skip 'help' and command name */));
+} else {
+ args = args.slice(offset + 1);
+}
+
+if (typeof commandName !== 'string') {
+ usage(puts);
+ process.exit(1);
+}
+
+var command;
+
+try {
+ command = require(Path.join('../lib/cli', commandName));
+} catch (ex) {
+ usage(eputs);
+ process.exit(1);
+}
+
+try {
+ var exitCode = command.run(args, puts, eputs);
+ process.exit(exitCode);
+} catch (ex) {
+ if (ex.constructor.name === 'AssertionError') {
+ eputs('error: ' + exe + ' ' + commandName + ' -- ' + ex.message);
+ process.exit(1);
+ } else {
+ throw ex;
+ }
+}
diff --git a/lib/browser/capture_stack_trace_polyfill.js b/lib/browser/capture_stack_trace_polyfill.js
new file mode 100644
index 0000000..f8b2a74
--- /dev/null
+++ b/lib/browser/capture_stack_trace_polyfill.js
@@ -0,0 +1,75 @@
+var DEFAULT_STACK_TRACE_LIMIT = 10;
+
+// Polyfill Error.captureStackTrace, which exists only in v8 (Chrome). This is
+// used in depd, which is used by ast-types.
+if (!Error.captureStackTrace) {
+ /**
+ * Adds a 'stack' property to the given object with stack info.
+ *
+ * @param obj
+ * @returns {Error.stack|*}
+ */
+ Error.captureStackTrace = function(obj) {
+ var stack = new Error().stack;
+ var prepare = Error.prepareStackTrace;
+
+ if (prepare) {
+ stack = prepare(stack, parseStack(stack));
+ }
+
+ obj.stack = stack;
+ };
+}
+
+if (typeof Error.stackTraceLimit === 'undefined') {
+ Error.stackTraceLimit = DEFAULT_STACK_TRACE_LIMIT;
+}
+
+/**
+ * Parse a formatted stack string into an array of call sites.
+ *
+ * @param {string} stack
+ * @returns {Array.<CallSite>}
+ */
+function parseStack(stack) {
+ return stack.split('\n').slice(0, Error.stackTraceLimit).map(CallSite.parse);
+}
+
+/**
+ * Represents a call site in a stack trace.
+ *
+ * @param {string} fnName
+ * @param {string} fileName
+ * @param {number} lineNumber
+ * @param {number} columnNumber
+ * @param {boolean} isEval
+ * @param {string} evalOrigin
+ * @constructor
+ */
+function CallSite(fnName, fileName, lineNumber, columnNumber, isEval, evalOrigin) {
+ this.getFunctionName = function() { return fnName; };
+ this.getFileName = function() { return fileName; };
+ this.getLineNumber = function() { return lineNumber; };
+ this.getColumnNumber = function() { return columnNumber; };
+ this.isEval = function() { return isEval; };
+ this.getEvalOrigin = function() { return evalOrigin; };
+}
+
+/**
+ * Parses a line in a formatted stack trace and returns call site info.
+ *
+ * @param {string} stackTraceLine
+ * @returns {CallSite}
+ */
+CallSite.parse = function(stackTraceLine) {
+ var fnNameAndLocation = stackTraceLine.split('@');
+ var fnName = fnNameAndLocation[0];
+ var location = fnNameAndLocation[1];
+
+ var fileAndLineAndColumn = location ? location.split(':') : [];
+ var fileName = fileAndLineAndColumn[0];
+ var lineNumber = parseInt(fileAndLineAndColumn[1], 10);
+ var columnNumber = parseInt(fileAndLineAndColumn[2], 10);
+
+ return new CallSite(fnName, fileName, lineNumber, columnNumber, fnName === 'eval', '');
+};
diff --git a/lib/browser/fs.js b/lib/browser/fs.js
new file mode 100644
index 0000000..7492fff
--- /dev/null
+++ b/lib/browser/fs.js
@@ -0,0 +1,4 @@
+var Fs = require('fake-fs');
+var fs = new Fs();
+fs.patch();
+module.exports = fs;
diff --git a/lib/browser/index.js b/lib/browser/index.js
new file mode 100644
index 0000000..9bac1f7
--- /dev/null
+++ b/lib/browser/index.js
@@ -0,0 +1,20 @@
+// Polyfill process.umask() since browserify doesn't add it (yet).
+// Remove it once https://github.com/defunctzombie/node-process/pull/22 is
+// merged and included in browserify.
+process.umask = function() { return 0; };
+
+require('./capture_stack_trace_polyfill');
+
+var Container = require('../container');
+var FileResolver = require('../file_resolver');
+var formatters = require('../formatters');
+var Module = require('../module');
+
+exports.FileResolver = FileResolver;
+exports.Container = Container;
+exports.formatters = formatters;
+exports.Module = Module;
+
+// Export the fake filesystem so someone can get stuff in and out.
+exports.fs = require('./fs');
+
diff --git a/lib/cli/convert.js b/lib/cli/convert.js
new file mode 100644
index 0000000..d264587
--- /dev/null
+++ b/lib/cli/convert.js
@@ -0,0 +1,152 @@
+/* jshint node:true, undef:true, unused:true */
+
+var fs = require('fs');
+var assert = require('assert');
+var Path = require('path');
+var recast = require('recast');
+
+var formatters = require('../formatters');
+var Container = require('../container');
+var FileResolver = require('../file_resolver');
+
+var getopt = require('posix-getopt');
+var exe = Path.basename(process.argv[1]);
+
+exports.run = function(args, puts, eputs) {
+ var offset = 0;
+
+ var files = [];
+ var includePaths = [process.cwd()];
+ var output;
+ var formatter = formatters[formatters.DEFAULT];
+ var resolverClasses = [FileResolver];
+
+ while (offset < args.length) {
+ var parser = new getopt.BasicParser('h(help)o:(output)I:(include)f:(format)r:(resolver)', ['', ''].concat(args.slice(offset)));
+ var option;
+
+ while ((option = parser.getopt()) !== undefined) {
+ if (option.error) {
+ usage(eputs);
+ return 1;
+ }
+
+ switch (option.option) {
+ case 'h':
+ usage(puts);
+ return 0;
+
+ case 'o':
+ output = option.optarg;
+ break;
+
+ case 'I':
+ includePaths.push(option.optarg);
+ break;
+
+ case 'f':
+ formatter = formatters[option.optarg];
+ if (!formatter) {
+ try { formatter = require(option.optarg); }
+ catch (ex) {}
+ }
+ if (!formatter) {
+ eputs('Cannot find formatter for ' + option.optarg);
+ usage(eputs);
+ return 1;
+ }
+ break;
+
+ case 'r':
+ try {
+ var resolverPath = option.optarg;
+ if (fs.existsSync(resolverPath)) {
+ resolverClasses.push(require(Path.join(process.cwd(), resolverPath)));
+ } else {
+ resolverClasses.push(require(option.optarg));
+ }
+ }
+ catch (ex) {
+ eputs('Error reading resolver ' + option.optarg + ': ' + ex);
+ usage(eputs);
+ return 1;
+ }
+ break;
+ }
+ }
+
+ for (offset += parser.optind() - 2; args[offset] && args[offset][0] !== '-'; offset++) {
+ files.push(args[offset]);
+ }
+ }
+
+ assert.ok(
+ files.length > 0,
+ 'Please provide at least one file to convert.'
+ );
+
+ if (typeof formatter === 'function') {
+ formatter = new formatter();
+ }
+
+ var resolvers = resolverClasses.map(function(resolverClass) {
+ return new resolverClass(includePaths);
+ });
+ var container = new Container({
+ formatter: formatter,
+ resolvers: resolvers
+ });
+
+ files.forEach(function(file) {
+ container.getModule(file);
+ });
+
+ if (output) {
+ container.write(output);
+ } else {
+ var outputs = container.convert();
+ assert.equal(
+ outputs.length, 1,
+ 'Cannot output ' + outputs.length + ' files to stdout. ' +
+ 'Please use the --output flag to specify where to put the ' +
+ 'files or choose a formatter that concatenates.'
+ );
+ process.stdout.write(recast.print(outputs[0]).code);
+ }
+
+};
+
+function bold(string) {
+ return '\x1b[01m' + string + '\x1b[0m';
+}
+
+function usage(puts) {
+ puts(exe + ' convert [-I <path>] [-o <path>] [-f <path|name>] [-r <path>] <path> [<path> ...]');
+ puts();
+ puts(bold('Description'));
+ puts();
+ puts(' Converts the given modules by changing `import`/`export` statements to an ES5 equivalent.');
+ puts();
+ puts(bold('Options'));
+ puts();
+ puts(' -I, --include <path> Check the given path for imported modules (usable multiple times).');
+ puts(' -o, --output <path> File or directory to output converted files.');
+ puts(' -f, --format <path|name> Path to custom formatter or choose from built-in formats.');
+ puts(' -r, --resolver <path> Path to custom resolver (usable multiple times).');
+ puts(' -h, --help Show this help message.');
+ puts();
+ puts(bold('Formats'));
+ puts();
+ puts(' commonjs - convert modules to files using CommonJS `require` and `exports` objects.');
+ puts(' bundle - concatenate modules into a single file.');
+ puts();
+ puts(' You may provide custom a formatter by passing the path to your module to the `--format` option. See the');
+ puts(' source of any of the built-in formatters for details on how to build your own.');
+ puts();
+ puts(bold('Resolvers'));
+ puts();
+ puts(' Resolvers resolve import paths to modules. The default resolver will search the include paths provided');
+ puts(' by `--include` arguments and the current working directory. To provide custom resolver logic, pass the');
+ puts(' path to your resolver module providing a `resolveModule` function or class with an instance method with');
+ puts(' this signature: `resolveModule(importedPath:String, fromModule:?Module, container:Container): Module`.');
+}
diff --git a/lib/container.js b/lib/container.js
new file mode 100644
index 0000000..48bf0eb
--- /dev/null
+++ b/lib/container.js
@@ -0,0 +1,255 @@
+/* jshint node:true, undef:true, unused:true */
+
+var assert = require('assert');
+var Path = require('path');
+
+var Rewriter = require('./rewriter');
+var Writer = require('./writer');
+var recast = require('recast');
+
+/** @typedef {{resolveModule: function(string, Module, Container): Module}} */
+var Resolver;
+
+/**
+ * Represents a container of modules for the given options.
+ *
+ * @constructor
+ * @param {{resolvers: Resolver[], formatter: Formatter}} options
+ */
+function Container(options) {
+ options = options || {};
+
+ var formatter = options.formatter;
+ if (typeof formatter === 'function') {
+ formatter = new formatter();
+ }
+
+ assert.ok(
+ formatter,
+ 'missing required option `formatter`'
+ );
+
+ var resolvers = options.resolvers;
+
+ assert.ok(
+ resolvers && resolvers.length > 0,
+ 'at least one resolver is required'
+ );
+ resolvers.forEach(function(resolver) {
+ assert.equal(
+ typeof resolver.resolveModule, 'function',
+ '`resolver` must have `resolveModule` function: ' + resolver
+ );
+ });
+
+ Object.defineProperties(this, {
+ modules: {
+ value: Object.create(null)
+ },
+
+ formatter: {
+ value: formatter
+ },
+
+ resolvers: {
+ value: resolvers
+ },
+
+ options: {
+ value: options
+ },
+
+ basePath: {
+ value: options.basePath || process.cwd()
+ },
+
+ sourceRoot: {
+ value: options.sourceRoot || '/'
+ }
+ });
+}
+
+/**
+ * Gets a module by resolving `path`. If `path` is resolved to the same path
+ * as a previous call, the same object will be returned.
+ *
+ * @param {string} importedPath
+ * @param {Module=} fromModule
+ * @return {Module}
+ */
+Container.prototype.getModule = function(importedPath, fromModule) {
+ for (var i = 0, length = this.resolvers.length; i < length; i++) {
+ var resolvedModule = this.resolvers[i].resolveModule(
+ importedPath,
+ fromModule,
+ this
+ );
+
+ if (resolvedModule) {
+ this.addModule(resolvedModule);
+ return resolvedModule;
+ }
+ }
+
+ throw new Error(
+ 'missing module import' +
+ (fromModule ? ' from ' + fromModule.relativePath : '') +
+ ' for path: ' + importedPath
+ );
+};
+
+/**
+ * Adds a module to the internal cache and gives it a unique name.
+ *
+ * @private
+ * @param {Module} mod
+ */
+Container.prototype.addModule = function(mod) {
+ if (mod.path in this.modules) {
+ return;
+ }
+
+ if (this._convertResult) {
+ throw new Error(
+ 'container has already converted contained modules ' +
+ 'and cannot add new module: ' + mod.path
+ );
+ }
+
+ // We have not seen this module before, so let's give it a unique name.
+ var modules = this.getModules();
+ var name = mod.name;
+ var baseName = name;
+ var counter = 0;
+ var nameExists;
+
+ while (true) {
+ nameExists = modules.some(function(existingModule) {
+ return existingModule.name === name;
+ });
+
+ if (!nameExists) {
+ break;
+ } else {
+ counter++;
+ name = baseName + counter;
+ }
+ }
+
+ mod.name = name;
+ delete mod.id;
+ this.modules[mod.path] = mod;
+};
+
+/**
+ * Get a cached module by a resolved path.
+ *
+ * @param {string} resolvedPath
+ * @return {?Module}
+ */
+Container.prototype.getCachedModule = function(resolvedPath) {
+ return this.modules[resolvedPath];
+};
+
+/**
+ * Writes the contents of this container to the given path.
+ *
+ * @param {string} target
+ */
+Container.prototype.write = function(target) {
+ if (!this._convertResult) {
+ this._convertResult = this.convert();
+ }
+ var files = this._convertResult;
+ var writer = new Writer(target, {
+ sourceRoot: this.sourceRoot,
+ basePath: this.basePath
+ });
+ writer.write(files);
+};
+
+/**
+ * Translate and return the contents of this container.
+ *
+ * @return {{filename: string, code: string, map: object}[]}
+ */
+Container.prototype.transform = function() {
+ if (!this._convertResult) {
+ this._convertResult = this.convert();
+ }
+
+ var files = this._convertResult;
+ var codes = [];
+
+ files.forEach(function(file) {
+ var rendered = recast.print(file, {
+ sourceMapName: Path.relative(this.basePath, file.filename),
+ sourceRoot: this.sourceRoot
+ });
+ var code = rendered.code;
+ var map = rendered.map;
+
+ codes.push({
+ filename: file.filename,
+ code: code,
+ map: map
+ });
+ }, this);
+
+ return codes;
+};
+
+/**
+ * Converts the contents of this container using the current formatter.
+ *
+ * @return {File[]}
+ */
+Container.prototype.convert = function() {
+ if (this.formatter.beforeConvert) {
+ this.formatter.beforeConvert(this);
+ }
+
+ var modules = this.getModules();
+
+ var rewriter = new Rewriter(this.formatter);
+ rewriter.rewrite(modules);
+
+ var formatter = this.formatter;
+ return formatter.build(modules);
+};
+
+/**
+ * Follows all imports/exports looking for new modules to add to this container.
+ */
+Container.prototype.findImportedModules = function() {
+ var knownModules;
+ var lastModuleCount = 0;
+
+ while ((knownModules = this.getModules()).length !== lastModuleCount) {
+ lastModuleCount = knownModules.length;
+ for (var i = 0; i < lastModuleCount; i++) {
+ // Force loading of imported modules.
+ noop(knownModules[i].imports.modules);
+ }
+ }
+};
+
+/**
+ * Gets the modules in this container in no particular order.
+ *
+ * @return {Module[]}
+ */
+Container.prototype.getModules = function() {
+ var modules = this.modules;
+ return Object.keys(modules).map(function(key) {
+ return modules[key];
+ });
+};
+
+/**
+ * Does nothing. This is only here to make JSHint/other static analysis
+ * tools happy about using getters for side effects.
+ */
+function noop() {}
+
+module.exports = Container;
diff --git a/lib/declaration_info.js b/lib/declaration_info.js
new file mode 100644
index 0000000..c69b415
--- /dev/null
+++ b/lib/declaration_info.js
@@ -0,0 +1,82 @@
+/* jshint node:true, undef:true, unused:true */
+
+var recast = require('recast');
+var types = recast.types;
+var n = types.namedTypes;
+
+/**
+ * Represents information about a declaration that creates a local binding
+ * represented by `identifier`. For example, given that `declaration` is the
+ * following variable declaration:
+ *
+ * var a = 1;
+ *
+ * Then `identifier` references the `a` node in the variable declaration's
+ * first declarator. Likewise, given that `declaration` is this function
+ * declaration:
+ *
+ * function add(a, b) {}
+ *
+ * Then `identifier` references the `add` node, the declaration's `id`.
+ *
+ * @constructor
+ * @param {Node} declaration
+ * @param {Identifier} identifier
+ */
+function DeclarationInfo(declaration, identifier) {
+ /**
+ * @type {Node}
+ * @name DeclarationInfo#declaration
+ */
+ this.declaration = declaration;
+ /**
+ * @type {Identifier}
+ * @name DeclarationInfo#identifier
+ */
+ this.identifier = identifier;
+}
+
+/**
+ * Get the declaration info for the given identifier path, if the identifier is
+ * actually part of a declaration.
+ *
+ * @param {NodePath} identifierPath
+ * @return {?DeclarationInfo}
+ */
+DeclarationInfo.forIdentifierPath = function(identifierPath) {
+ if (n.VariableDeclarator.check(identifierPath.parent.node)) {
+ return new DeclarationInfo(
+ identifierPath.parent.parent.node,
+ identifierPath.node
+ );
+ } else if (n.ClassDeclaration.check(identifierPath.parent.node)) {
+ return new DeclarationInfo(
+ identifierPath.parent.node,
+ identifierPath.node
+ );
+ } else if (n.FunctionDeclaration.check(identifierPath.parent.node)) {
+ return new DeclarationInfo(
+ identifierPath.parent.node,
+ identifierPath.node
+ );
+ } else if (n.ImportSpecifier.check(identifierPath.parent.node)) {
+ return new DeclarationInfo(
+ identifierPath.parent.parent.node,
+ identifierPath.node
+ );
+ } else if (n.ImportNamespaceSpecifier.check(identifierPath.parent.node)) {
+ return new DeclarationInfo(
+ identifierPath.parent.parent.node,
+ identifierPath.node
+ );
+ } else if (n.ImportDefaultSpecifier.check(identifierPath.parent.node)) {
+ return new DeclarationInfo(
+ identifierPath.parent.parent.node,
+ identifierPath.node
+ );
+ } else {
+ return null;
+ }
+};
+
+module.exports = DeclarationInfo;
diff --git a/lib/exports.js b/lib/exports.js
new file mode 100644
index 0000000..b35e9c0
--- /dev/null
+++ b/lib/exports.js
@@ -0,0 +1,365 @@
+/* jshint node:true, undef:true, unused:true */
+
+var assert = require('assert');
+
+var recast = require('recast');
+var types = recast.types;
+var n = types.namedTypes;
+
+var ModuleBindingList = require('./module_binding_list');
+var ModuleBindingDeclaration = require('./module_binding_declaration');
+var ModuleBindingSpecifier = require('./module_binding_specifier');
+var DeclarationInfo = require('./declaration_info');
+
+var utils = require('./utils');
+var memo = utils.memo;
+var extend = utils.extend;
+var sourcePosition = utils.sourcePosition;
+
+/**
+ * Represents a list of the exports for the given module.
+ *
+ * @constructor
+ * @extends ModuleBindingList
+ * @param {Module} mod
+ */
+function ExportDeclarationList(mod) {
+ ModuleBindingList.call(this, mod);
+}
+extend(ExportDeclarationList, ModuleBindingList);
+
+/**
+ * @private
+ * @param {AST.Declaration} node
+ * @return {boolean}
+ */
+ExportDeclarationList.prototype.isMatchingBinding = function(node) {
+ return n.ExportDeclaration.check(node);
+};
+
+/**
+ * Gets an export declaration for the given `node`.
+ *
+ * @private
+ * @param {AST.ExportDeclaration} node
+ * @return {ExportDeclaration}
+ */
+ExportDeclarationList.prototype.declarationForNode = function(node) {
+ if (node.default) {
+ return new DefaultExportDeclaration(this.module, node);
+ } else if (n.VariableDeclaration.check(node.declaration)) {
+ return new VariableExportDeclaration(this.module, node);
+ } else if (n.FunctionDeclaration.check(node.declaration)) {
+ return new FunctionExportDeclaration(this.module, node);
+ } else if (n.ClassDeclaration.check(node.declaration)) {
+ return new ClassExportDeclaration(this.module, node);
+ } else if (n.ExportBatchSpecifier.check(node.specifiers[0])) {
+ throw new Error(
+ '`export *` found at ' + sourcePosition(this.module, node) +
+ ' is not supported, please use `export { … }` instead'
+ );
+ } else {
+ return new NamedExportDeclaration(this.module, node);
+ }
+};
+
+/**
+ * @param {NodePath} referencePath
+ * @return {?ExportSpecifier}
+ */
+ExportDeclarationList.prototype.findSpecifierForReference = function(referencePath) {
+ if (n.ExportSpecifier.check(referencePath.parent.node) && referencePath.parent.parent.node.source) {
+ // This is a direct export from another module, e.g. `export { foo } from 'foo'`.
+ return /** @type {ExportSpecifier} */this.findSpecifierByIdentifier(referencePath.node);
+ }
+
+ var declaration = this.findDeclarationForReference(referencePath);
+
+ if (!declaration) {
+ return null;
+ }
+
+ var specifier = /** @type {ExportSpecifier} */this.findSpecifierByName(declaration.node.name);
+ assert.ok(
+ specifier,
+ 'no specifier found for `' + referencePath.node.name + '`! this should not happen!'
+ );
+ return specifier;
+};
+
+/**
+ * Contains information about an export declaration.
+ *
+ * @constructor
+ * @abstract
+ * @extends ModuleBindingDeclaration
+ * @param {Module} mod
+ * @param {ExportDeclaration} node
+ */
+function ExportDeclaration(mod, node) {
+ assert.ok(
+ n.ExportDeclaration.check(node),
+ 'expected an export declaration, got ' + (node && node.type)
+ );
+
+ ModuleBindingDeclaration.call(this, mod, node);
+}
+extend(ExportDeclaration, ModuleBindingDeclaration);
+
+/**
+ * Returns a string description suitable for debugging.
+ *
+ * @return {string}
+ */
+ExportDeclaration.prototype.inspect = function() {
+ return recast.print(this.node).code;
+};
+
+/**
+ * @see ExportDeclaration#inspect
+ */
+ExportDeclaration.prototype.toString = ExportDeclaration.prototype.inspect;
+
+/**
+ * Represents an export declaration of the form:
+ *
+ * export default foo;
+ *
+ * @constructor
+ * @extends ExportDeclaration
+ * @param {Module} mod
+ * @param {AST.ExportDeclaration} node
+ */
+function DefaultExportDeclaration(mod, node) {
+ ExportDeclaration.call(this, mod, node);
+}
+extend(DefaultExportDeclaration, ExportDeclaration);
+
+/**
+ * Contains a list of specifier name information for this export.
+ *
+ * @type {ExportSpecifier[]}
+ * @name DefaultExportSpecifier#specifiers
+ */
+memo(DefaultExportDeclaration.prototype, 'specifiers', /** @this DefaultExportDeclaration */function() {
+ var specifier = new DefaultExportSpecifier(this, this.node.declaration);
+ return [specifier];
+});
+
+/**
+ * Represents an export declaration of the form:
+ *
+ * export { foo, bar };
+ *
+ * @constructor
+ * @extends ExportDeclaration
+ * @param {Module} mod
+ * @param {AST.ExportDeclaration} node
+ */
+function NamedExportDeclaration(mod, node) {
+ ExportDeclaration.call(this, mod, node);
+}
+extend(NamedExportDeclaration, ExportDeclaration);
+
+/**
+ * Contains a list of specifier name information for this export.
+ *
+ * @type {ExportSpecifier[]}
+ * @name NamedExportDeclaration#specifiers
+ */
+memo(NamedExportDeclaration.prototype, 'specifiers', /** @this NamedExportDeclaration */function() {
+ var self = this;
+ return this.node.specifiers.map(function(specifier) {
+ return new ExportSpecifier(self, specifier);
+ });
+});
+
+/**
+ * Represents an export declaration of the form:
+ *
+ * export var foo = 1;
+ *
+ * @constructor
+ * @extends ExportDeclaration
+ * @param {Module} mod
+ * @param {AST.ExportDeclaration} node
+ */
+function VariableExportDeclaration(mod, node) {
+ ExportDeclaration.call(this, mod, node);
+}
+extend(VariableExportDeclaration, ExportDeclaration);
+
+/**
+ * Gets the list of export specifiers for this declaration.
+ *
+ * @type {ExportSpecifier[]}
+ * @name VariableExportDeclaration#specifiers
+ */
+memo(VariableExportDeclaration.prototype, 'specifiers', /** @this VariableExportDeclaration */function() {
+ var self = this;
+ return this.node.declaration.declarations.map(function(declarator) {
+ return new ExportSpecifier(self, declarator);
+ });
+});
+
+/**
+ * Represents an export declaration of the form:
+ *
+ * export class Foo {}
+ *
+ * @constructor
+ * @extends ExportDeclaration
+ * @param {Module} mod
+ * @param {AST.ExportDeclaration} node
+ */
+function ClassExportDeclaration(mod, node) {
+ ExportDeclaration.call(this, mod, node);
+}
+extend(ClassExportDeclaration, ExportDeclaration);
+
+/**
+ * Gets the list of export specifiers for this declaration.
+ *
+ * @type {ExportSpecifier[]}
+ * @name ClassExportDeclaration#specifiers
+ */
+memo(ClassExportDeclaration.prototype, 'specifiers', /** @this ClassExportDeclaration */function() {
+ return [new ExportSpecifier(this, this.node.declaration)];
+});
+
+/**
+ * Represents an export declaration of the form:
+ *
+ * export function foo() {}
+ *
+ * @constructor
+ * @extends ExportDeclaration
+ * @param {Module} mod
+ * @param {AST.ExportDeclaration} node
+ */
+function FunctionExportDeclaration(mod, node) {
+ ExportDeclaration.call(this, mod, node);
+}
+extend(FunctionExportDeclaration, ExportDeclaration);
+
+/**
+ * Gets the list of export specifiers for this declaration.
+ *
+ * @type {ExportSpecifier[]}
+ * @name FunctionExportDeclaration#specifiers
+ */
+memo(FunctionExportDeclaration.prototype, 'specifiers', /** @this FunctionExportDeclaration */function() {
+ return [new ExportSpecifier(this, this.node.declaration)];
+});
+
+/**
+ * Represents an export specifier in an export declaration.
+ *
+ * @constructor
+ * @extends ModuleBindingSpecifier
+ * @param {ExportDeclaration} declaration
+ * @param {AST.Node} node
+ */
+function ExportSpecifier(declaration, node) {
+ ModuleBindingSpecifier.call(this, declaration, node);
+}
+extend(ExportSpecifier, ModuleBindingSpecifier);
+
+/**
+ * Contains the local declaration info for this export specifier. For example,
+ * in this module:
+ *
+ * var a = 1;
+ * export { a };
+ *
+ * The module declaration info for the `a` export specifier is the variable
+ * declaration plus the `a` identifier in its first declarator.
+ *
+ * @type {?DeclarationInfo}
+ * @name ExportSpecifier#moduleDeclaration
+ */
+memo(ExportSpecifier.prototype, 'moduleDeclaration', /** @this ExportSpecifier */function() {
+ if (this.declaration.source) {
+ // This is part of a direct export, e.g. `export { ... } from '...'`, so
+ // there is no declaration as part of this module.
+ return null;
+ }
+
+ var bindings = this.moduleScope.getBindings();
+ var identifierPaths = bindings[this.from];
+ assert.ok(
+ identifierPaths && identifierPaths.length === 1,
+ 'expected exactly one declaration for export `' +
+ this.from + '` at ' + sourcePosition(this.module, this.node) +
+ ', found ' + (identifierPaths ? identifierPaths.length : 'none')
+ );
+
+ var identifierPath = identifierPaths[0];
+ var declarationInfo = DeclarationInfo.forIdentifierPath(identifierPath);
+ assert.ok(
+ declarationInfo,
+ 'cannot detect declaration for `' +
+ identifierPath.node.name + '`, found parent.type `' +
+ identifierPath.parent.node.type + '`'
+ );
+
+ return declarationInfo;
+});
+
+/**
+ * Represents an export specifier in a default export declaration.
+ *
+ * @constructor
+ * @extends ExportSpecifier
+ * @param {ExportDeclaration} declaration
+ * @param {AST.Expression} node
+ */
+function DefaultExportSpecifier(declaration, node) {
+ ExportSpecifier.call(this, declaration, node);
+}
+extend(DefaultExportSpecifier, ExportSpecifier);
+
+/**
+ * The node of a default export specifier is an expression, not a specifier.
+ *
+ * @type {AST.Expression}
+ */
+DefaultExportSpecifier.prototype.node = null;
+
+/**
+ * Default export specifier names are always "default".
+ *
+ * @type {string}
+ * @name DefaultExportSpecifier#name
+ * @default "default"
+ */
+DefaultExportSpecifier.prototype.name = 'default';
+
+/**
+ * Default export specifiers do not bind to a local identifier.
+ *
+ * @type {?Identifier}
+ * @name DefaultExportSpecifier#identifier
+ * @default null
+ */
+DefaultExportSpecifier.prototype.identifier = null;
+
+/**
+ * Default export specifiers do not have a local bound name.
+ *
+ * @type {?string}
+ * @name DefaultExportSpecifier#from
+ * @default null
+ */
+DefaultExportSpecifier.prototype.from = null;
+
+/**
+ * Default export specifiers do not have a local declaration.
+ *
+ * @type {?DeclarationInfo}
+ * @name DefaultExportSpecifier#moduleDeclaration
+ * @default null
+ */
+DefaultExportSpecifier.prototype.moduleDeclaration = null;
+
+module.exports = ExportDeclarationList;
diff --git a/lib/file_resolver.js b/lib/file_resolver.js
new file mode 100644
index 0000000..d2b697d
--- /dev/null
+++ b/lib/file_resolver.js
@@ -0,0 +1,89 @@
+/* jshint node:true, undef:true, unused:true */
+
+var assert = require('assert');
+var Path = require('path');
+var fs = require('fs');
+
+var Module = require('./module');
+
+/**
+ * Provides resolution of absolute paths from module import sources.
+ *
+ * @constructor
+ */
+function FileResolver(paths) {
+ assert.ok(
+ paths && paths.length > 0,
+ 'missing required argument `paths`'
+ );
+
+ this.paths = paths.map(function(path) {
+ return Path.resolve(path);
+ });
+}
+
+/**
+ * Resolves `importedPath` imported by the given module `fromModule` to a
+ * module.
+ *
+ * @param {string} importedPath
+ * @param {?Module} fromModule
+ * @param {Container} container
+ * @return {?Module}
+ */
+FileResolver.prototype.resolveModule = function(importedPath, fromModule, container) {
+ var resolvedPath = this.resolvePath(importedPath, fromModule);
+ if (resolvedPath) {
+ var cachedModule = container.getCachedModule(resolvedPath);
+ if (cachedModule) {
+ return cachedModule;
+ } else {
+ if (!Path.extname(importedPath)) {
+ importedPath += Path.extname(resolvedPath);
+ }
+ return new Module(resolvedPath, importedPath, container);
+ }
+ } else {
+ return null;
+ }
+};
+
+/**
+ * Resolves `importedPath` against the importing module `fromModule`, if given,
+ * within this resolver's paths.
+ *
+ * @private
+ * @param {string} importedPath
+ * @param {?Module} fromModule
+ * @return {string}
+ */
+FileResolver.prototype.resolvePath = function(importedPath, fromModule) {
+ var paths = this.paths;
+
+ if (importedPath[0] === '.' && fromModule) {
+ paths = [Path.dirname(fromModule.path)];
+ }
+
+ for (var i = 0, length = paths.length; i < length; i++) {
+ var includePath = paths[i];
+ var resolved = Path.resolve(includePath, importedPath);
+ if (!~resolved.lastIndexOf('.')) {
+ resolved += '.js';
+ }
+
+ if (fs.existsSync(resolved)) {
+ return resolved;
+ }
+
+ // edge cases when a module may have dotted filename, i.e. jquery.min.js
+ // and the module name is passed without the extension
+ resolved += '.js';
+ if (fs.existsSync(resolved)) {
+ return resolved;
+ }
+ }
+
+ return null;
+};
+
+module.exports = FileResolver;
diff --git a/lib/formatters.js b/lib/formatters.js
new file mode 100644
index 0000000..01839b1
--- /dev/null
+++ b/lib/formatters.js
@@ -0,0 +1,5 @@
+/* jshint node:true, undef:true, unused:true */
+
+exports.DEFAULT = 'bundle';
+exports.bundle = require('./formatters/bundle_formatter');
+exports.commonjs = require('./formatters/commonjs_formatter');
diff --git a/lib/formatters/bundle_formatter.js b/lib/formatters/bundle_formatter.js
new file mode 100644
index 0000000..589bd23
--- /dev/null
+++ b/lib/formatters/bundle_formatter.js
@@ -0,0 +1,497 @@
+/* jshint node:true, undef:true, unused:true */
+
+var recast = require('recast');
+var types = recast.types;
+var b = types.builders;
+var n = types.namedTypes;
+
+var Replacement = require('../replacement');
+var utils = require('../utils');
+var IIFE = utils.IIFE;
+var extend = utils.extend;
+var sort = require('../sorting').sort;
+var Formatter = require('./formatter');
+
+
+/**
+ * The 'bundle' formatter aims to increase the compressibility of the generated
+ * source, especially by tools such as Google Closure Compiler or UglifyJS.
+ * For example, given these modules:
+ *
+ * ```js
+ * // a.js
+ * import { b } from './b';
+ * console.log(b);
+ *
+ * // b.js
+ * export var b = 3;
+ * export var b2 = 6;
+ * ```
+ *
+ * The final output will be a single file looking something like this:
+ *
+ * ```js
+ * (function() {
+ * "use strict";
+ * // b.js
+ * var b$$b = 3;
+ * var b$$b2 = 6;
+ *
+ * // a.js
+ * console.log(b$$b);
+ * }).call(this);
+ * ```
+ *
+ * @constructor
+ */
+function BundleFormatter() {
+ Formatter.call(this);
+}
+extend(BundleFormatter, Formatter);
+
+/**
+ * This hook is called by the container before it converts its modules. We use
+ * it to ensure all of the imports are included because we need to know about
+ * them at compile time.
+ *
+ * @param {Container} container
+ */
+BundleFormatter.prototype.beforeConvert = function(container) {
+ container.findImportedModules();
+
+ // Cache all the import and export specifier names.
+ container.getModules().forEach(function(mod) {
+ [mod.imports, mod.exports].forEach(function(bindingList) {
+ bindingList.declarations.forEach(function (declaration) {
+ declaration.specifiers.forEach(function (specifier) {
+ specifier.name;
+ });
+ });
+ });
+ });
+};
+
+/**
+ * @override
+ */
+BundleFormatter.prototype.build = function(modules) {
+ modules = sort(modules);
+ return [b.file(b.program([b.expressionStatement(IIFE(
+ b.expressionStatement(b.literal('use strict')),
+ this.buildNamespaceImportObjects(modules),
+ modules.length === 1 ?
+ modules[0].ast.program.body :
+ modules.reduce(function(statements, mod) {
+ return statements.concat(mod.ast.program.body);
+ }, [])
+ ))]))];
+};
+
+/**
+ * Builds a variable declaration that contains declarations of all the namespace
+ * objects required by `import * as foo from 'foo'` statements.
+ *
+ * @private
+ * @param {Module[]} modules
+ * @return {?AST.VariableDeclaration}
+ */
+BundleFormatter.prototype.buildNamespaceImportObjects = function(modules) {
+ var self = this;
+ var namespaceImportedModules = [];
+
+ // Collect all the modules imported using a namespace import declaration.
+ modules.forEach(function(mod) {
+ mod.imports.namespaceImports.forEach(function(namespaceImportDeclaration) {
+ var namespaceImportedModule = namespaceImportDeclaration.source;
+ if (namespaceImportedModules.indexOf(namespaceImportedModule) < 0) {
+ namespaceImportedModules.push(namespaceImportedModule);
+ }
+ });
+ });
+
+ if (namespaceImportedModules.length === 0) {
+ return null;
+ }
+
+ /**
+ * Builds a variable declarator for the given module whose initial value is an
+ * object with properties for each export from the module being imported. For
+ * example, given a module "foo" with exports "a" and "b" this object will be
+ * created:
+ *
+ * foo$$ = {
+ * get a() {
+ * return foo$$a;
+ * },
+ *
+ * get b() {
+ * return foo$$b;
+ * }
+ * }
+ *
+ * @param {Module} mod
+ * @returns {AST.VariableDeclarator}
+ */
+ function createDeclaratorForModule(mod) {
+ return b.variableDeclarator(
+ b.identifier(mod.id),
+ b.objectExpression(
+ mod.exports.declarations.reduce(function(props, exportDeclaration) {
+ exportDeclaration.specifiers.forEach(function(specifier) {
+ props.push(createGetterForSpecifier(mod, specifier));
+ });
+
+ return props;
+ }, [])
+ )
+ );
+ }
+
+ /**
+ * Builds a getter property for retrieving the value of the given export
+ * specifier at the time it is accessed. For example, given a module "foo"
+ * with export specifier "a" this property will be created:
+ *
+ * get a() {
+ * return foo$$a;
+ * }
+ *
+ * @param {Module} mod
+ * @param {ExportSpecifier} specifier
+ * @returns {AST.Property}
+ */
+ function createGetterForSpecifier(mod, specifier) {
+ return b.property(
+ 'get',
+ b.identifier(specifier.name),
+ b.functionExpression(
+ null,
+ [],
+ b.blockStatement([
+ b.returnStatement(self.reference(mod, specifier.name))
+ ])
+ )
+ );
+ }
+
+ return b.variableDeclaration(
+ 'var',
+ namespaceImportedModules.map(createDeclaratorForModule)
+ );
+};
+
+/**
+ * @override
+ *
+ * ```js
+ * export default <FunctionDeclaration|ClassDeclaration>
+ * // or
+ * export default <declaration|expression>;
+ * ```
+ */
+BundleFormatter.prototype.defaultExport = function(mod, declaration) {
+ if (n.FunctionDeclaration.check(declaration) ||
+ n.ClassDeclaration.check(declaration)) {
+ // export default function foo () {}
+ // -> becomes:
+ // function <moduleName>foo () {}
+ // var <moduleName>default = <moduleName>foo;
+ var renamedDeclaration = Object.create(declaration);
+ renamedDeclaration.id = this.reference(mod, declaration.id);
+ return [
+ renamedDeclaration,
+ b.variableDeclaration(
+ 'var',
+ [b.variableDeclarator(
+ this.reference(mod, 'default'),
+ this.reference(mod, declaration.id)
+ )]
+ )
+ ];
+ }
+ return b.variableDeclaration(
+ 'var',
+ [b.variableDeclarator(
+ this.reference(mod, 'default'),
+ declaration
+ )]
+ );
+};
+
+/**
+ * Get a reference to the original exported value referenced in `mod` at
+ * `referencePath`. If the given reference path does not correspond to an
+ * export, we do not need to rewrite the reference. For example, since `value`
+ * is not exported it does not need to be rewritten:
+ *
+ * ```js
+ * // a.js
+ * var value = 99;
+ * console.log(value);
+ * ```
+ *
+ * If `value` was exported then we would need to rewrite it:
+ *
+ * ```js
+ * // a.js
+ * export var value = 3;
+ * console.log(value);
+ * ```
+ *
+ * In this case we re-write both `value` references to something like
+ * `a$$value`. The tricky part happens when we re-export an imported binding:
+ *
+ * ```js
+ * // a.js
+ * export var value = 11;
+ *
+ * // b.js
+ * import { value } from './a';
+ * export { value };
+ *
+ * // c.js
+ * import { value } from './b';
+ * console.log(value);
+ * ```
+ *
+ * The `value` reference in a.js will be rewritten as something like `a$$value`
+ * as expected. The `value` reference in c.js will not be rewritten as
+ * `b$$value` despite the fact that it is imported from b.js. This is because
+ * we must follow the binding through to its import from a.js. Thus, our
+ * `value` references will both be rewritten to `a$$value` to ensure they
+ * match.
+ *
+ * @override
+ */
+BundleFormatter.prototype.exportedReference = function(mod, referencePath) {
+ var specifier = mod.exports.findSpecifierForReference(referencePath);
+ if (specifier) {
+ specifier = specifier.terminalExportSpecifier;
+ return this.reference(specifier.module, specifier.name);
+ } else {
+ return null;
+ }
+};
+
+/**
+ * Get a reference to the original exported value referenced in `mod` at
+ * `referencePath`. This is very similar to BundleFormatter#exportedReference
+ * in its approach.
+ *
+ * @override
+ */
+BundleFormatter.prototype.importedReference = function(mod, referencePath) {
+ var specifier = mod.imports.findSpecifierForReference(referencePath);
+
+ if (!specifier) {
+ return null;
+ }
+
+ if (specifier.from) {
+ specifier = specifier.terminalExportSpecifier;
+ if (n.ImportNamespaceSpecifier.check(specifier.node)) {
+ // Reference the built namespace object, e.g. mod$$.
+ return b.identifier(specifier.declaration.source.id);
+ }
+ return this.reference(specifier.module, specifier.name);
+ } else {
+ return b.identifier(specifier.declaration.source.id);
+ }
+};
+
+/**
+ * If the given reference has a local declaration at the top-level then we must
+ * rewrite that reference to have a module-scoped name.
+ *
+ * @param {Module} mod
+ * @param {NodePath} referencePath
+ * @return {?Node}
+ */
+BundleFormatter.prototype.localReference = function(mod, referencePath) {
+ var scope = referencePath.scope.lookup(referencePath.node.name);
+ if (scope && scope.isGlobal) {
+ return this.reference(mod, referencePath.node);
+ } else {
+ return null;
+ }
+};
+
+/**
+ * Replaces non-default exports. Exported bindings do not need to be
+ * replaced with actual statements since they only control how local references
+ * are renamed within the module.
+ *
+ * @override
+ */
+BundleFormatter.prototype.processExportDeclaration = function(mod, nodePath) {
+ var node = nodePath.node;
+ if (n.FunctionDeclaration.check(node.declaration)) {
+ return Replacement.swaps(
+ // drop `export`
+ nodePath, node.declaration
+ ).and(
+ // transform the function
+ this.processFunctionDeclaration(mod, nodePath.get('declaration'))
+ );
+ } else if (n.ClassDeclaration.check(node.declaration)) {
+ return Replacement.swaps(
+ // drop `export`
+ nodePath, node.declaration
+ ).and(
+ // transform the class
+ this.processClassDeclaration(mod, nodePath.get('declaration'))
+ );
+ } else if (n.VariableDeclaration.check(node.declaration)) {
+ return Replacement.swaps(
+ // drop `export`
+ nodePath, node.declaration
+ ).and(
+ // transform the variables
+ this.processVariableDeclaration(mod, nodePath.get('declaration'))
+ );
+ } else if (node.declaration) {
+ throw new Error(
+ 'unexpected export style, found a declaration of type: ' +
+ node.declaration.type
+ );
+ } else {
+ /**
+ * This node looks like this:
+ *
+ * ```js
+ * export { foo, bar };
+ * ```
+ *
+ * Which means that it has no value in the generated code as its only
+ * function is to control how imports are rewritten.
+ */
+ return Replacement.removes(nodePath);
+ }
+};
+
+/**
+ * Since named export reassignment is just a local variable, we can ignore it.
+ * e.g.
+ *
+ * ```js
+ * export var foo = 1;
+ * foo = 2;
+ * ```
+ *
+ * @override
+ */
+BundleFormatter.prototype.processExportReassignment = function(mod, nodePath) {
+ return null;
+};
+
+/**
+ * Rename the top-level function declaration to a unique name.
+ *
+ * ```js
+ * function foo() {}
+ * ```
+ *
+ * Becomes e.g.
+ *
+ * ```js
+ * function mod$$foo() {}
+ * ```
+ *
+ * @override
+ */
+BundleFormatter.prototype.processFunctionDeclaration = function(mod, nodePath) {
+ return Replacement.swaps(
+ nodePath.get('id'),
+ this.reference(mod, nodePath.node.id)
+ );
+};
+
+/**
+ * Rename the top-level class declaration to a unique name.
+ *
+ * ```js
+ * class Foo {}
+ * ```
+ *
+ * Becomes e.g.
+ *
+ * ```js
+ * class mod$$Foo {}
+ * ```
+ *
+ * @override
+ */
+BundleFormatter.prototype.processClassDeclaration = function(mod, nodePath) {
+ return Replacement.swaps(
+ nodePath.get('id'),
+ this.reference(mod, nodePath.node.id)
+ );
+};
+
+/**
+ * Since import declarations only control how we rewrite references we can just
+ * remove them -- they don't turn into any actual statements.
+ *
+ * @override
+ */
+BundleFormatter.prototype.processImportDeclaration = function(mod, nodePath) {
+ return Replacement.removes(nodePath);
+};
+
+/**
+ * Process a variable declaration found at the top level of the module. We need
+ * to ensure that exported variables are rewritten appropriately, so we may
+ * need to rewrite some or all of this variable declaration. For example:
+ *
+ * ```js
+ * var a = 1, b, c = 3;
+ * ...
+ * export { a, b };
+ * ```
+ *
+ * We turn those being exported into assignments as needed, e.g.
+ *
+ * ```js
+ * var c = 3;
+ * mod$$a = 1;
+ * ```
+ *
+ * @override
+ */
+BundleFormatter.prototype.processVariableDeclaration = function(mod, nodePath) {
+ var self = this;
+ return Replacement.map(
+ nodePath.get('declarations'),
+ function(declaratorPath) {
+ return Replacement.swaps(
+ declaratorPath.get('id'),
+ self.reference(mod, declaratorPath.get('id').node)
+ );
+ }
+ );
+};
+
+/**
+ * Returns an expression which globally references the export named by
+ * `identifier` for the given module `mod`. For example:
+ *
+ * ```js
+ * // rsvp/defer.js, export default
+ * rsvp$defer$$default
+ *
+ * // rsvp/utils.js, export function isFunction
+ * rsvp$utils$$isFunction
+ * ```
+ *
+ * @param {Module} mod
+ * @param {Identifier|string} identifier
+ * @return {Identifier}
+ * @private
+ */
+BundleFormatter.prototype.reference = function(mod, identifier) {
+ return b.identifier(
+ mod.id + (n.Identifier.check(identifier) ? identifier.name : identifier)
+ );
+};
+
+module.exports = BundleFormatter;
diff --git a/lib/formatters/commonjs_formatter.js b/lib/formatters/commonjs_formatter.js
new file mode 100644
index 0000000..5c6a983
--- /dev/null
+++ b/lib/formatters/commonjs_formatter.js
@@ -0,0 +1,537 @@
+/* jshint node:true, undef:true, unused:true */
+
+var assert = require('assert');
+var recast = require('recast');
+var types = recast.types;
+var n = types.namedTypes;
+var b = types.builders;
+var util = require('ast-util');
+
+var extend = require('../utils').extend;
+var compatMemberExpression = require('../utils').compatMemberExpression;
+var Replacement = require('../replacement');
+var Formatter = require('./formatter');
+
+/**
+ * The 'commonjs' setting for referencing exports aims to produce code that can
+ * be used in environments using the CommonJS module system, such as Node.js.
+ *
+ * @constructor
+ */
+function CommonJSFormatter() {
+ Formatter.call(this);
+}
+extend(CommonJSFormatter, Formatter);
+
+/**
+ * Convert a list of ordered modules into a list of files.
+ *
+ * @param {Module[]} modules Modules in execution order.
+ * @return {File[]}
+ */
+CommonJSFormatter.prototype.build = function(modules) {
+ var self = this;
+ return modules.map(function(mod) {
+ var body = mod.ast.program.body;
+
+ var requiresDeclaration = self.buildRequires(mod);
+ var earlyExportsStatement = self.buildEarlyExports(mod);
+ var lateExports = self.buildLateExports(mod);
+
+ if (requiresDeclaration) {
+ body.unshift(requiresDeclaration);
+ }
+
+ if (earlyExportsStatement) {
+ body.unshift(earlyExportsStatement);
+ }
+
+ body.unshift(b.expressionStatement(b.literal('use strict')));
+
+ if (lateExports) {
+ body.push(lateExports);
+ }
+
+ mod.ast.filename = mod.relativePath;
+ return mod.ast;
+ });
+};
+
+/**
+ * Process all export bindings which may be exported before any module code is
+ * actually run, i.e. function declarations.
+ *
+ * @param {Module} mod
+ * @returns {?AST.Statement}
+ * @private
+ */
+CommonJSFormatter.prototype.buildEarlyExports = function(mod) {
+ var assignments = [];
+ var exportObject = b.identifier('exports');
+
+ this.forEachExportBinding(mod, function(specifier, name) {
+ if (!n.FunctionDeclaration.check(specifier.declaration.node.declaration)) {
+ // Only function declarations are handled as early exports.
+ return;
+ }
+
+ assignments.push(b.assignmentExpression(
+ '=',
+ compatMemberExpression(
+ exportObject,
+ name
+ ),
+ b.identifier(specifier.from)
+ ));
+ });
+
+ if (assignments.length > 0) {
+ return b.expressionStatement(
+ b.sequenceExpression(assignments)
+ );
+ } else {
+ return null;
+ }
+};
+
+/**
+ * Process all export bindings which were not exported at the beginning of the
+ * module, i.e. everything except function declarations.
+ *
+ * @param {Module} mod
+ * @returns {?AST.Statement}
+ * @private
+ */
+CommonJSFormatter.prototype.buildLateExports = function(mod) {
+ var self = this;
+ var assignments = [];
+ var exportObject = b.identifier('exports');
+
+ this.forEachExportBinding(mod, function(specifier, name) {
+ if (n.FunctionDeclaration.check(specifier.declaration.node.declaration)) {
+ // Function declarations are handled as early exports.
+ return;
+ }
+
+ var from;
+
+ if (specifier.importSpecifier) {
+ if (n.ImportNamespaceSpecifier.check(specifier.importSpecifier.node)) {
+ from = b.identifier(specifier.importSpecifier.declaration.source.id)
+ } else {
+ from = self.reference(
+ specifier.importSpecifier.declaration.source,
+ specifier.importSpecifier.from
+ );
+ }
+ } else if (specifier.declaration.source) {
+ from = self.reference(
+ specifier.declaration.source,
+ specifier.name
+ );
+ } else {
+ from = b.identifier(specifier.from);
+ }
+
+ assignments.push(b.assignmentExpression(
+ '=',
+ compatMemberExpression(
+ exportObject,
+ name
+ ),
+ from
+ ));
+ });
+
+ if (assignments.length > 0) {
+ return b.expressionStatement(
+ b.sequenceExpression(assignments)
+ );
+ } else {
+ return null;
+ }
+};
+
+/**
+ * Iterates over each exported binding and calls `iterator` with its specifier.
+ *
+ * @param {Module} mod
+ * @param {function(ModuleBindingSpecifier, string)} iterator
+ * @private
+ */
+CommonJSFormatter.prototype.forEachExportBinding = function(mod, iterator) {
+ mod.exports.names.forEach(function(name) {
+ var specifier = mod.exports.findSpecifierByName(name);
+
+ assert.ok(
+ specifier,
+ 'no export specifier found for export name `' +
+ name + '` from ' + mod.relativePath
+ );
+
+ if (!specifier.from) {
+ // Default exports are handled elsewhere.
+ return;
+ }
+
+ iterator(specifier, name);
+ });
+};
+
+/**
+ * Build a series of requires based on the imports (and exports with sources)
+ * in the given module.
+ *
+ * @private
+ * @param {Module} mod
+ * @return {?AST.VariableDeclaration}
+ */
+CommonJSFormatter.prototype.buildRequires = function(mod) {
+ var declarators = [];
+ var requiredModules = [];
+
+ [mod.imports, mod.exports].forEach(function(declarations) {
+ declarations.modules.forEach(function(sourceModule) {
+ if (requiredModules.indexOf(sourceModule) >= 0) {
+ return;
+ }
+ requiredModules.push(sourceModule);
+
+ var matchingDeclaration;
+ declarations.declarations.some(function(declaration) {
+ if (declaration.source === sourceModule) {
+ matchingDeclaration = declaration;
+ return true;
+ }
+ });
+
+ assert.ok(
+ matchingDeclaration,
+ 'no matching declaration for source module: ' + sourceModule.relativePath
+ );
+
+ // `(import|export) { ... } from 'math'` -> `math$$ = require('math')`
+ declarators.push(b.variableDeclarator(
+ b.identifier(sourceModule.id),
+ b.callExpression(
+ b.identifier('require'),
+ [b.literal(matchingDeclaration.sourcePath)]
+ )
+ ));
+ });
+ });
+
+ if (declarators.length > 0) {
+ return b.variableDeclaration('var', declarators);
+ } else {
+ return null;
+ }
+};
+
+/**
+ * @override
+ *
+ * ```js
+ * export default <FunctionDeclaration|ClassDeclaration>
+ * // or
+ * export default <declaration|expression>;
+ * ```
+ */
+CommonJSFormatter.prototype.defaultExport = function(mod, declaration) {
+ if (n.FunctionDeclaration.check(declaration) ||
+ n.ClassDeclaration.check(declaration)) {
+ // export default function foo () {}
+ // -> becomes:
+ // function foo () {}
+ // export['default'] = foo;
+ return [
+ declaration,
+ b.expressionStatement(b.assignmentExpression(
+ '=',
+ b.memberExpression(
+ b.identifier('exports'),
+ b.literal('default'),
+ true
+ ),
+ declaration.id
+ ))
+ ];
+ }
+ return b.expressionStatement(b.assignmentExpression(
+ '=',
+ b.memberExpression(
+ b.identifier('exports'),
+ b.literal('default'),
+ true
+ ),
+ declaration
+ ));
+};
+
+/**
+ * Because exported references are captured via a closure as part of a getter
+ * on the `exports` object, there's no need to rewrite local references to
+ * exported values. For example, `value` in this example can stay as is:
+ *
+ * // a.js
+ * export var value = 1;
+ *
+ * @override
+ */
+CommonJSFormatter.prototype.exportedReference = function(mod, referencePath) {
+ return null;
+};
+
+/**
+ * Gets a reference to an imported binding by getting the value from the
+ * required module on demand. For example, this module:
+ *
+ * // b.js
+ * import { value } from './a';
+ * console.log(value);
+ *
+ * Would be rewritten to look something like this:
+ *
+ * var a$$ = require('./a');
+ * console.log(a$$.value):
+ *
+ * If the given reference does not refer to an imported binding then no
+ * rewriting is required and `null` will be returned.
+ *
+ * @override
+ */
+CommonJSFormatter.prototype.importedReference = function(mod, referencePath) {
+ var specifier = mod.imports.findSpecifierForReference(referencePath);
+
+ if (!specifier) {
+ return null;
+ }
+
+ if (specifier.from) {
+ // import { value } from './a';
+ // import a from './a';
+ return this.reference(
+ specifier.declaration.source,
+ specifier.from
+ );
+ } else {
+ // import * as a from './a'
+ return b.identifier(specifier.declaration.source.id);
+ }
+};
+
+/**
+ * We do not need to rewrite references to local declarations.
+ *
+ * @override
+ */
+CommonJSFormatter.prototype.localReference = function(mod, referencePath) {
+ return null;
+};
+
+/**
+ * Replaces non-default exports. For declarations we simply remove the `export`
+ * keyword. For export declarations that just specify bindings, e.g.
+ *
+ * export { a, b };
+ *
+ * we remove them entirely since they'll be handled when we define properties on
+ * the `exports` object.
+ *
+ * @override
+ */
+CommonJSFormatter.prototype.processExportDeclaration = function(mod, nodePath) {
+ var node = nodePath.node;
+
+ if (n.FunctionDeclaration.check(node.declaration)) {
+ return Replacement.swaps(nodePath, node.declaration);
+ } else if (n.VariableDeclaration.check(node.declaration)) {
+ return Replacement.swaps(nodePath, node.declaration);
+ } else if (n.ClassDeclaration.check(node.declaration)) {
+ return Replacement.swaps(nodePath, node.declaration);
+ } else if (node.declaration) {
+ throw new Error('unexpected export style, found a declaration of type: ' + node.declaration.type);
+ } else {
+ return Replacement.removes(nodePath);
+ }
+};
+
+/**
+ * We explicitly disallow reassignment because we cannot propagate changes to
+ * importing modules as we would in ES5, e.g. these are both disallowed:
+ *
+ * export var foo = 1;
+ * foo = 2;
+ *
+ * export var bar = 1;
+ * bar++;
+ *
+ * @override
+ */
+CommonJSFormatter.prototype.processExportReassignment = function(mod, nodePath) {
+ var node = nodePath.node;
+ var exportName;
+
+ if (n.AssignmentExpression.check(node)) {
+ exportName = node.left.name;
+ } else if (n.UpdateExpression.check(node)) {
+ exportName = node.argument.name;
+ } else {
+ throw new Error('unexpected export reassignment type: ' + node.type);
+ }
+
+
+ if (n.UpdateExpression.check(node) && !node.prefix) {
+ /**
+ * The result of `a++` is the value of `a` before it is incremented, so we
+ * can't just use the result as the new value for `exports.a`. The question
+ * is whether we need to preserve the result of `a++` or not. In this case,
+ * we do:
+ *
+ * ```js
+ * foo(a++);
+ * ```
+ *
+ * But in this case we don't, since the result is ignored:
+ *
+ * ```js
+ * a++;
+ * ```
+ */
+
+ if (n.ExpressionStatement.check(nodePath.parent.node)) {
+ // The result is ignored here, so `a++` can become `a++, exports.a = a`.
+ return Replacement.swaps(
+ nodePath,
+ b.sequenceExpression([
+ node,
+ b.assignmentExpression(
+ '=',
+ compatMemberExpression(
+ b.identifier('exports'),
+ exportName
+ ),
+ b.identifier(exportName)
+ )
+ ])
+ );
+ } else {
+ // The result is used here, so we need a temporary variable to store it.
+ var result = util.injectVariable(nodePath.scope, util.uniqueIdentifier(nodePath.scope));
+
+ return Replacement.swaps(
+ nodePath,
+ b.sequenceExpression([
+ b.assignmentExpression(
+ '=',
+ result,
+ node
+ ),
+ b.assignmentExpression(
+ '=',
+ compatMemberExpression(
+ b.identifier('exports'),
+ exportName
+ ),
+ b.identifier(exportName)
+ ),
+ result
+ ])
+ );
+ }
+ }
+
+ /**
+ * We can use the result of the update/assignment as-is in this case, e.g.
+ *
+ * ```js
+ * foo(++a);
+ * b = 9;
+ * ```
+ *
+ * Can become:
+ *
+ * ```js
+ * foo(exports.a = ++a);
+ * exports.b = b = 9;
+ * ```
+ */
+ return Replacement.swaps(
+ nodePath,
+ b.assignmentExpression(
+ '=',
+ compatMemberExpression(
+ b.identifier('exports'),
+ exportName
+ ),
+ node
+ )
+ );
+};
+
+/**
+ * Process a function declaration found at the top level of the module. Since
+ * we do not need to rewrite exported functions, we can leave function
+ * declarations alone.
+ *
+ * @override
+ */
+CommonJSFormatter.prototype.processFunctionDeclaration = function(mod, nodePath) {
+ return null;
+};
+
+/**
+ * Process a class declaration found at the top level of the module. Since
+ * we do not need to rewrite exported classes, we can leave class
+ * declarations alone.
+ *
+ * @override
+ */
+CommonJSFormatter.prototype.processClassDeclaration = function(mod, nodePath) {
+ return null;
+};
+
+/**
+ * Since import declarations only control how we rewrite references we can just
+ * remove them -- they don't turn into any actual statements.
+ *
+ * @override
+ */
+CommonJSFormatter.prototype.processImportDeclaration = function(mod, nodePath) {
+ return Replacement.removes(nodePath);
+};
+
+/**
+ * Process a variable declaration found at the top level of the module. Since
+ * we do not need to rewrite exported variables, we can leave variable
+ * declarations alone.
+ *
+ * @override
+ */
+CommonJSFormatter.prototype.processVariableDeclaration = function(mod, nodePath) {
+ return null;
+};
+
+/**
+ * Returns an expression which globally references the export named by
+ * `identifier` for the given module `mod`. For example:
+ *
+ * // rsvp/defer.js, export default
+ * rsvp$defer$$['default']
+ *
+ * // rsvp/utils.js, export function isFunction
+ * rsvp$utils$$.isFunction
+ *
+ * @param {Module} mod
+ * @param {Identifier} identifier
+ * @return {MemberExpression}
+ * @private
+ */
+CommonJSFormatter.prototype.reference = function(mod, identifier) {
+ return compatMemberExpression(
+ b.identifier(mod.id),
+ identifier
+ );
+};
+
+module.exports = CommonJSFormatter;
diff --git a/lib/formatters/formatter.js b/lib/formatters/formatter.js
new file mode 100644
index 0000000..36c206c
--- /dev/null
+++ b/lib/formatters/formatter.js
@@ -0,0 +1,198 @@
+/* jshint node:true, undef:true, unused:true */
+
+var assert = require('assert');
+
+var recast = require('recast');
+var types = recast.types;
+var n = types.namedTypes;
+var b = types.builders;
+
+var Replacement = require('../replacement');
+
+/**
+ * This class provides a base for any concrete formatter classes. Subclasses
+ * of this class will be used by Rewriter to determine how to replace various
+ * parts of an AST while walking it to achieve conversion from ES6 modules to
+ * another format.
+ *
+ * @constructor
+ * @abstract
+ */
+function Formatter() {}
+
+/**
+ * Convert a list of ordered modules into a list of files.
+ *
+ * @param {Module[]} modules Modules in execution order.
+ * @return {File[]}
+ */
+Formatter.prototype.build = function(modules) {
+ throw new Error('#build must be implemented in subclasses');
+};
+
+/**
+ * Replaces default export declarations with something else. Subclasses will
+ * generally return a statement that takes the declaration value and stashes
+ * it somewhere appropriate for the transpiled format, e.g. creates a local
+ * variable, assigns the value to something, or calls a function with it.
+ *
+ * Given an export statement like so:
+ *
+ * ```js
+ * export default foo(bar);
+ * ```
+ *
+ * This method will be called with the module containing the statement and
+ * the AST node corresponding to `foo(bar)`.
+ *
+ * @param {Module} mod
+ * @param {Expression} declaration
+ * @return {Statement}
+ */
+Formatter.prototype.defaultExport = function(mod, declaration) {
+ throw new Error('#defaultExport must be implemented in subclasses');
+};
+
+/**
+ * Resolves references to exported bindings. In the example below, if we refer
+ * to `value` elsewhere in the module then that reference may need to be
+ * rewritten. This method allows us to configure what it is rewritten to.
+ *
+ * ```js
+ * // a.js
+ * export var value = 1;
+ * ```
+ *
+ * Subclasses should return null if the original reference should be left
+ * intact.
+ *
+ * @param {Module} mod
+ * @param {NodePath} referencePath
+ * @return {?Expression}
+ */
+Formatter.prototype.exportedReference = function(mod, referencePath) {
+ throw new Error('#exportedReference must be implemented in subclasses');
+};
+
+/**
+ * Gets a reference to an imported binding. In this example, we will be called
+ * with the NodePath for `value` in `console.log(value)`:
+ *
+ * ```js
+ * // b.js
+ * import { value } from './a';
+ * console.log(value);
+ * ```
+ *
+ * If the given reference does not refer to an imported binding then no
+ * rewriting is required and `null` should be returned.
+ *
+ * @param {Module} mod
+ * @param {NodePath} referencePath
+ * @return {?Expression}
+ */
+Formatter.prototype.importedReference = function(mod, referencePath) {
+ throw new Error('#importedReference must be implemented in subclasses');
+};
+
+/**
+ * Determines what the given reference should be rewritten to, if anything.
+ * Subclasses should override this only if they wish to rename bindings not
+ * associated with imports and exports.
+ *
+ * This is used by the bundle formatter, for example, to ensure that bindings
+ * at module scope are rewritten with unique names to prevent collisions with
+ * bindings from other modules.
+ *
+ * @param {Module} mod
+ * @param {NodePath} referencePath
+ * @return {?Node}
+ */
+Formatter.prototype.localReference = function(mod, referencePath) {
+ return null;
+};
+
+/**
+ * Process a function declaration found at the top level of the module.
+ *
+ * @param {Module} mod
+ * @param {NodePath} nodePath
+ * @return {?Node[]}
+ */
+Formatter.prototype.processFunctionDeclaration = function(mod, nodePath) {
+ throw new Error('#processFunctionDeclaration must be implemented in subclasses');
+};
+
+/**
+ * Process a class declaration found at the top level of the module.
+ *
+ * @param {Module} mod
+ * @param {NodePath} nodePath
+ * @return {?Node[]}
+ */
+Formatter.prototype.processClassDeclaration = function(mod, nodePath) {
+ throw new Error('#processClassDeclaration must be implemented in subclasses');
+};
+
+/**
+ * Process a variable declaration found at the top level of the module.
+ *
+ * @param {Module} mod
+ * @param {NodePath} nodePath
+ * @return {?Node[]}
+ */
+Formatter.prototype.processVariableDeclaration = function(mod, nodePath) {
+ throw new Error('#processVariableDeclaration must be implemented in subclasses');
+};
+
+/**
+ * Replaces non-default exports. These exports are of one of the following
+ * forms:
+ *
+ * ```js
+ * export var a = 1;
+ * export function a() {}
+ * export class a {}
+ * export { a };
+ * ```
+ *
+ * @param {Module} mod
+ * @param {NodePath} nodePath
+ * @return {?Replacement}
+ */
+Formatter.prototype.processExportDeclaration = function(mod, nodePath) {
+ throw new Error('#processExportDeclaration must be implemented in subclasses');
+};
+
+/**
+ * Process and optionally replace an update to an exported binding. This can
+ * either be an assignment expression or an update expression, i.e.
+ *
+ * ```js
+ * export var foo = 1;
+ * foo = 2;
+ * foo++;
+ * ```
+ *
+ * @param {Module} mod
+ * @param {NodePath} nodePath
+ * @return {?Replacement}
+ */
+Formatter.prototype.processExportReassignment = function(mod, nodePath) {
+ throw new Error('#processExportReassignment must be implemented in subclasses');
+};
+
+/**
+ * Optionally replace an import declaration. Subclasses should almost always
+ * replace import declarations. It may be replaced with a dependency lookup, or
+ * perhaps with nothing.
+ *
+ * @param {Module} mod
+ * @param {NodePath} nodePath
+ * @return {?Replacement}
+ */
+Formatter.prototype.processImportDeclaration = function(mod, nodePath) {
+ throw new Error('#processImportDeclaration must be implemented in subclasses');
+};
+
+module.exports = Formatter;
diff --git a/lib/imports.js b/lib/imports.js
new file mode 100644
index 0000000..47931e3
--- /dev/null
+++ b/lib/imports.js
@@ -0,0 +1,221 @@
+/* jshint node:true, undef:true, unused:true */
+
+var assert = require('assert');
+var recast = require('recast');
+var types = recast.types;
+var n = types.namedTypes;
+
+var ModuleBindingList = require('./module_binding_list');
+var ModuleBindingDeclaration = require('./module_binding_declaration');
+var ModuleBindingSpecifier = require('./module_binding_specifier');
+
+var utils = require('./utils');
+var memo = utils.memo;
+var extend = utils.extend;
+var sourcePosition = utils.sourcePosition;
+
+/**
+ * Represents a list of the imports for the given module.
+ *
+ * @constructor
+ * @param {Module} mod
+ * @extends ModuleBindingList
+ */
+function ImportDeclarationList(mod) {
+ ModuleBindingList.call(this, mod);
+}
+extend(ImportDeclarationList, ModuleBindingList);
+
+/**
+ * @private
+ * @param {AST.Node} node
+ * @return {boolean}
+ */
+ImportDeclarationList.prototype.isMatchingBinding = function(node) {
+ return n.ImportDeclaration.check(node);
+};
+
+/**
+ * Gets an import declaration for the given `node`.
+ *
+ * @private
+ * @param {AST.ImportDeclaration} node
+ * @return {ImportDeclaration}
+ */
+ImportDeclarationList.prototype.declarationForNode = function(node) {
+ return new ImportDeclaration(this.module, node);
+};
+
+/**
+ * Gets the namespace imports from the list of imports.
+ *
+ * @private
+ * @type {ImportDeclaration[]}
+ * @name ImportDeclaration#namespaceImports
+ */
+memo(ImportDeclarationList.prototype, 'namespaceImports', /** @this ImportDeclarationList */function() {
+ return this.declarations.filter(function(declaration) {
+ return declaration.hasNamespaceImport;
+ });
+});
+
+/**
+ * Contains information about an import declaration.
+ *
+ * ```js
+ * import foo from 'math';
+ * import { sin, cos } from 'math';
+ * import * as bar from 'math';
+ * import foo, { sin, cos } from 'math';
+ * import foo, * as bar from 'math';
+ * ```
+ *
+ * @constructor
+ * @abstract
+ * @param {Module} mod
+ * @param {AST.ImportDeclaration} node
+ * @extends ModuleBindingDeclaration
+ */
+function ImportDeclaration(mod, node) {
+ assert.ok(
+ n.ImportDeclaration.check(node),
+ 'expected an import declaration, got ' + (node && node.type)
+ );
+
+ ModuleBindingDeclaration.call(this, mod, node);
+}
+extend(ImportDeclaration, ModuleBindingDeclaration);
+
+/**
+ * Contains a list of specifier name information for this import.
+ *
+ * @type {ImportSpecifier[]}
+ * @name ImportDeclaration#specifiers
+ */
+memo(ImportDeclaration.prototype, 'specifiers', /** @this ImportDeclaration */function() {
+ var self = this;
+ return this.node.specifiers.map(function(specifier) {
+ if (n.ImportDefaultSpecifier.check(specifier)) {
+ return new ImportDefaultSpecifier(self, specifier);
+ } else if (n.ImportNamespaceSpecifier.check(specifier)) {
+ return new ImportNamespaceSpecifier(self, specifier);
+ }
+ return new ImportNamedSpecifier(self, specifier);
+ });
+});
+
+/**
+ * @type {boolean}
+ * @name ImportDeclaration#hasNamespaceImport
+ */
+memo(ImportDeclaration.prototype, 'hasNamespaceImport', /** @this ImportDeclaration */function() {
+ return this.specifiers.some(function(specifier) {
+ return specifier instanceof ImportNamespaceSpecifier;
+ });
+});
+
+/**
+ * Represents an import specifier. The "a" and "b as c" are both import
+ * specifiers in the following import statement.
+ *
+ * import { a, b as c } from "a";
+ *
+ * @constructor
+ * @extends ModuleBindingSpecifier
+ * @param {ImportDeclaration} declaration
+ * @param {AST.ImportNamedSpecifier} node
+ */
+function ImportNamedSpecifier(declaration, node) {
+ assert.ok(
+ declaration instanceof ImportDeclaration,
+ 'expected an instance of ImportDeclaration'
+ );
+ ModuleBindingSpecifier.call(this, declaration, node);
+}
+extend(ImportNamedSpecifier, ModuleBindingSpecifier);
+
+/**
+ * @type {ExportSpecifier}
+ * @name ImportNamedSpecifier#exportSpecifier
+ */
+memo(ImportNamedSpecifier.prototype, 'exportSpecifier', /** @this ImportNamedSpecifier */function() {
+ var source = this.declaration.source;
+ assert.ok(source, 'import specifiers must have a valid source');
+ var exportSpecifier = source.exports.findSpecifierByName(this.from);
+ assert.ok(
+ exportSpecifier,
+ 'import `' + this.from + '` at ' +
+ sourcePosition(this.module, this.node) +
+ ' has no matching export in ' + source.relativePath
+ );
+ return exportSpecifier;
+});
+
+
+/**
+ * Represents a default import specifier. The "a" in the following import statement.
+ *
+ * import a from "a";
+ *
+ * @constructor
+ * @extends ModuleBindingSpecifier
+ * @param {ImportDeclaration} declaration
+ * @param {AST.ImportDefaultSpecifier} node
+ */
+function ImportDefaultSpecifier(declaration, node) {
+ assert.ok(
+ declaration instanceof ImportDeclaration,
+ 'expected an instance of ImportDeclaration'
+ );
+ ModuleBindingSpecifier.call(this, declaration, node);
+}
+extend(ImportDefaultSpecifier, ModuleBindingSpecifier);
+
+memo(ImportDefaultSpecifier.prototype, 'exportSpecifier', /** @this ImportSpecifier */function() {
+ var source = this.declaration.source;
+ assert.ok(source, 'import specifiers must have a valid source');
+ var exportSpecifier = source.exports.findSpecifierByName(this.from);
+ assert.ok(
+ exportSpecifier,
+ 'import `default` at ' +
+ sourcePosition(this.module, this.node) +
+ ' has no matching export in ' + source.relativePath
+ );
+ return exportSpecifier;
+});
+
+memo(ImportDefaultSpecifier.prototype, 'from', function() {
+ return 'default';
+});
+
+/**
+ * Represents a namespace import specifier. The "a" in the following import
+ * statement.
+ *
+ * import * as a from "a";
+ *
+ * @constructor
+ * @extends ModuleBindingSpecifier
+ * @param {ImportDeclaration} declaration
+ * @param {AST.ImportNamespaceSpecifier} node
+ */
+function ImportNamespaceSpecifier(declaration, node) {
+ assert.ok(
+ declaration instanceof ImportDeclaration,
+ 'expected an instance of ImportDeclaration'
+ );
+ ModuleBindingSpecifier.call(this, declaration, node);
+}
+extend(ImportNamespaceSpecifier, ModuleBindingSpecifier);
+
+memo(ImportNamespaceSpecifier.prototype, 'exportSpecifier', /** @this ImportNamespaceSpecifier */function() {
+ var source = this.declaration.source;
+ assert.ok(source, 'import specifiers must have a valid source');
+ return null;
+});
+
+memo(ImportNamespaceSpecifier.prototype, 'from', function() {
+ return null;
+});
+
+module.exports = ImportDeclarationList;
diff --git a/lib/index.js b/lib/index.js
new file mode 100644
index 0000000..cf92f6a
--- /dev/null
+++ b/lib/index.js
@@ -0,0 +1,11 @@
+/* jshint node:true, undef:true, unused:true */
+
+var Container = require('./container');
+var FileResolver = require('./file_resolver');
+var formatters = require('./formatters');
+var Module = require('./module');
+
+exports.FileResolver = FileResolver;
+exports.Container = Container;
+exports.formatters = formatters;
+exports.Module = Module;
diff --git a/lib/module.js b/lib/module.js
new file mode 100644
index 0000000..96529c5
--- /dev/null
+++ b/lib/module.js
@@ -0,0 +1,202 @@
+/* jshint node:true, undef:true, unused:true */
+
+var assert = require('assert');
+var fs = require('fs');
+var Path = require('path');
+
+var esprima = require('esprima-fb');
+var recast = require('recast');
+var types = recast.types;
+var n = types.namedTypes;
+var b = types.builders;
+var NodePath = recast.types.NodePath;
+
+var ImportDeclarationList = require('./imports');
+var ExportDeclarationList = require('./exports');
+var utils = require('./utils');
+var memo = utils.memo;
+var endsWith = utils.endsWith;
+
+/**
+ * Represents a JavaScript module at a particular location on disk.
+ *
+ * @param {string} path
+ * @param {string} relativePath
+ * @param {Container} container
+ * @constructor
+ */
+function Module(path, relativePath, container) {
+ Object.defineProperties(this, {
+ /**
+ * @type {string}
+ * @name Module#path
+ */
+ path: {
+ value: path,
+ enumerable: true,
+ writable: false
+ },
+
+ /**
+ * @type {string}
+ * @name Module#relativePath
+ */
+ relativePath: {
+ value: relativePath,
+ enumerable: true,
+ writable: false
+ },
+
+ /**
+ * @type {string}
+ * @name Module#sourceFileName
+ */
+ sourceFileName: {
+ value: Path.relative(container.basePath, path),
+ enumerable: true,
+ writable: false
+ },
+
+ /**
+ * @type {Container}
+ * @name Module#container
+ */
+ container: {
+ value: container,
+ enumerable: true,
+ writable: false
+ }
+ });
+}
+
+/**
+ * Clears the cached data for this module.
+ */
+Module.prototype.reload = function() {
+ delete this.src;
+ delete this.ast;
+ delete this.imports;
+ delete this.exports;
+ delete this.scope;
+};
+
+/**
+ * The list of imports declared by this module.
+ *
+ * @type {ImportDeclarationList}
+ * @name Module#imports
+ */
+memo(Module.prototype, 'imports', /** @this Module */function() {
+ var result = new ImportDeclarationList(this);
+ result.readProgram(this.ast.program);
+ return result;
+});
+
+/**
+ * The list of exports declared by this module.
+ *
+ * @type {ExportDeclarationList}
+ * @name Module#exports
+ */
+memo(Module.prototype, 'exports', /** @this Module */function() {
+ var result = new ExportDeclarationList(this);
+ result.readProgram(this.ast.program);
+ return result;
+});
+
+/**
+ * This module's scope.
+ *
+ * @type {Scope}
+ * @name Module#scope
+ */
+memo(Module.prototype, 'scope', /** @this Module */function() {
+ return new NodePath(this.ast).get('program').get('body').scope;
+});
+
+/**
+ * This module's source code represented as an abstract syntax tree.
+ *
+ * @type {File}
+ * @name Module#ast
+ */
+memo(Module.prototype, 'ast', /** @this Module */function() {
+ return recast.parse(
+ this.src, {
+ esprima: esprima,
+ sourceFileName: this.sourceFileName
+ }
+ );
+});
+
+/**
+ * This module's source code.
+ *
+ * @type {String}
+ * @name Module#src
+ */
+memo(Module.prototype, 'src', /** @this Module */function() {
+ return fs.readFileSync(this.path).toString();
+});
+
+/**
+ * A reference to the options from this module's container.
+ *
+ * @type {object}
+ * @name Module#options
+ */
+memo(Module.prototype, 'options', /** @this Module */function() {
+ return this.container.options;
+});
+
+/**
+ * This module's relative name, like {#relativePath} but without the extension.
+ * This may be modified by a Container if this Module is part of a Container.
+ *
+ * @type {string}
+ * @name Module#name
+ */
+memo(Module.prototype, 'name', /** @this Module */function() {
+ var relativePath = this.relativePath;
+ if (endsWith(relativePath, '.js')) {
+ return relativePath.slice(0, -3);
+ } else {
+ return relativePath;
+ }
+});
+
+/**
+ * A string suitable for a JavaScript identifier named for this module.
+ *
+ * @type {string}
+ * @name Module#id
+ */
+memo(Module.prototype, 'id', /** @this Module */function() {
+ return this.name.replace(/[^\w$_]/g, '$') + '$$';
+});
+
+/**
+ * Gets a Module by path relative to this module.
+ *
+ * @param {string} sourcePath
+ * @return {Module}
+ */
+Module.prototype.getModule = function(sourcePath) {
+ return this.container.getModule(sourcePath, this);
+};
+
+/**
+ * Generate a descriptive string suitable for debugging.
+ *
+ * @return {string}
+ */
+Module.prototype.inspect = function() {
+ return '#<' + this.constructor.name + ' ' + this.relativePath + '>';
+};
+
+/**
+ * @see Module#inspect
+ */
+Module.prototype.toString = Module.prototype.inspect;
+
+module.exports = Module;
diff --git a/lib/module_binding_declaration.js b/lib/module_binding_declaration.js
new file mode 100644
index 0000000..0db6442
--- /dev/null
+++ b/lib/module_binding_declaration.js
@@ -0,0 +1,133 @@
+/* jshint node:true, undef:true, unused:true */
+
+var assert = require('assert');
+
+var recast = require('recast');
+var types = recast.types;
+var n = types.namedTypes;
+
+var utils = require('./utils');
+var memo = utils.memo;
+
+/**
+ * Contains information about a module binding declaration. This corresponds to
+ * the shared functionality of `ExportDeclaration` and `ImportDeclaration` in
+ * the ES6 spec.
+ *
+ * @constructor
+ * @abstract
+ * @param {Module} mod
+ * @param {AST.ImportDeclaration|AST.ExportDeclaration} node
+ */
+function ModuleBindingDeclaration(mod, node) {
+ assert.ok(
+ n.ImportDeclaration.check(node) || n.ExportDeclaration.check(node),
+ 'expected an import or export declaration, got ' + (node && node.type)
+ );
+
+ Object.defineProperties(this, {
+ /**
+ * @name ModuleBindingDeclaration#node
+ * @type {AST.ImportDeclaration|AST.ExportDeclaration}
+ */
+ node: {
+ value: node
+ },
+
+ /**
+ * @name ModuleBindingDeclaration#module
+ * @type {Module}
+ */
+ module: {
+ value: mod
+ }
+ });
+}
+
+/**
+ * Finds the specifier that creates the local binding given by `name`, if one
+ * exists. Otherwise `null` is returned.
+ *
+ * @param {string} name
+ * @return {?ModuleBindingSpecifier}
+ */
+ModuleBindingDeclaration.prototype.findSpecifierByName = function(name) {
+ var specifiers = this.specifiers;
+
+ for (var i = 0, length = specifiers.length; i < length; i++) {
+ var specifier = specifiers[i];
+ if (specifier.name === name) {
+ return specifier;
+ }
+ }
+
+ return null;
+};
+
+/**
+ * @param {AST.Identifier} identifier
+ * @return {?ModuleBindingSpecifier}
+ */
+ModuleBindingDeclaration.prototype.findSpecifierByIdentifier = function(identifier) {
+ for (var i = 0, length = this.specifiers.length; i < length; i++) {
+ var specifier = this.specifiers[i];
+ if (specifier.identifier === identifier) {
+ return specifier;
+ }
+ }
+
+ return null;
+};
+
+/**
+ * Gets the raw path of the `from` part of the declaration, if present. For
+ * example:
+ *
+ * ```js
+ * import { map } from "array";
+ * ```
+ *
+ * The source path for the above declaration is "array".
+ *
+ * @type {?string}
+ * @name ModuleBindingDeclaration#sourcePath
+ */
+memo(ModuleBindingDeclaration.prototype, 'sourcePath', /** @this ModuleBindingDeclaration */function() {
+ return this.node.source ? this.node.source.value : null;
+});
+
+/**
+ * Gets a reference to the module referenced by this declaration.
+ *
+ * @type {Module}
+ * @name ModuleBindingDeclaration#source
+ */
+memo(ModuleBindingDeclaration.prototype, 'source', /** @this ModuleBindingDeclaration */function() {
+ return this.sourcePath ? this.module.getModule(this.sourcePath) : null;
+});
+
+/**
+ * Gets the containing module's scope.
+ *
+ * @type {Scope}
+ * @name ModuleBindingDeclaration#moduleScope
+ */
+memo(ModuleBindingDeclaration.prototype, 'moduleScope', /** @this ModuleBindingDeclaration */function() {
+ return this.module.scope;
+});
+
+/**
+ * Generate a string representing this object to aid debugging.
+ *
+ * @return {string}
+ */
+ModuleBindingDeclaration.prototype.inspect = function() {
+ return recast.print(this.node).code;
+};
+
+/**
+ * @see ModuleBindingDeclaration#inspect
+ */
+ModuleBindingDeclaration.prototype.toString = ModuleBindingDeclaration.prototype.inspect;
+
+module.exports = ModuleBindingDeclaration;
diff --git a/lib/module_binding_list.js b/lib/module_binding_list.js
new file mode 100644
index 0000000..e778dcc
--- /dev/null
+++ b/lib/module_binding_list.js
@@ -0,0 +1,244 @@
+/* jshint node:true, undef:true, unused:true */
+
+var assert = require('assert');
+
+var utils = require('./utils');
+var memo = utils.memo;
+var sourcePosition = utils.sourcePosition;
+
+/**
+ * Represents a list of bindings for the given module. This corresponds to the
+ * shared functionality from `ExportsList` and `ImportsList` from the ES6 spec.
+ *
+ * @abstract
+ * @constructor
+ * @param {Module} mod
+ */
+function ModuleBindingList(mod) {
+ Object.defineProperties(this, {
+ /**
+ * @name ModuleBindingList#_nodes
+ * @type {AST.ImportDeclaration[]|AST.ExportDeclaration[]}
+ * @private
+ */
+ _nodes: {
+ value: []
+ },
+
+ /**
+ * @name ModuleBindingList#module
+ * @type {Module}
+ */
+ module: {
+ value: mod
+ }
+ });
+}
+
+/**
+ * Add all the binding declarations from the given scope body. Generally this
+ * should be the Program node's `body` property, an array of statements.
+ *
+ * @param {AST.Program} program
+ */
+ModuleBindingList.prototype.readProgram = function(program) {
+ var body = program.body;
+ for (var i = 0; i < body.length; i++) {
+ if (this.isMatchingBinding(body[i])) {
+ this.addDeclaration(body[i]);
+ }
+ }
+};
+
+/**
+ * Adds a declaration to the list.
+ *
+ * @private
+ * @param {AST.ImportDeclaration|AST.ExportDeclaration} node
+ */
+ModuleBindingList.prototype.addDeclaration = function(node) {
+ assert.ok(
+ this.isMatchingBinding(node),
+ 'expected node to be an declaration, but got ' +
+ (node && node.type)
+ );
+ this._nodes.push(node);
+
+ // reset the cache
+ delete this.declarations;
+ delete this.specifiers;
+ delete this.modules;
+};
+
+/**
+ * Gets the associated module's scope.
+ *
+ * @type {Scope}
+ * @name ModuleBindingList#moduleScope
+ */
+memo(ModuleBindingList.prototype, 'moduleScope', /** @this ModuleBindingList */function() {
+ return this.module.scope;
+});
+
+/**
+ * Gets all the modules referenced by the declarations in this list.
+ *
+ * @type {Module[]}
+ * @name ModuleBindingList#modules
+ */
+memo(ModuleBindingList.prototype, 'modules', /** @this ModuleBindingList */function() {
+ var modules = [];
+
+ this.declarations.forEach(function(declaration) {
+ if (declaration.source && modules.indexOf(declaration.source) < 0) {
+ modules.push(declaration.source);
+ }
+ });
+
+ return modules;
+});
+
+/**
+ * Finds the specifier that creates the local binding given by `name`, if one
+ * exists. Otherwise `null` is returned.
+ *
+ * @private
+ * @param {string} name
+ * @return {?ModuleBindingSpecifier}
+ */
+ModuleBindingList.prototype.findSpecifierByName = function(name) {
+ for (var i = 0, length = this.declarations.length; i < length; i++) {
+ var specifier = this.declarations[i].findSpecifierByName(name);
+ if (specifier) { return specifier; }
+ }
+
+ return null;
+};
+
+/**
+ * Finds the specifier whose identifier is the given identifier, if one exists.
+ * Otherwise `null` is returned.
+ *
+ * @private
+ * @param {AST.Identifier} identifier
+ * @return {?ModuleBindingSpecifier}
+ */
+ModuleBindingList.prototype.findSpecifierByIdentifier = function(identifier) {
+ for (var i = 0, length = this.declarations.length; i < length; i++) {
+ var specifier = this.declarations[i].findSpecifierByIdentifier(identifier);
+ if (specifier && specifier.identifier === identifier) {
+ return specifier;
+ }
+ }
+
+ return null;
+};
+
+/**
+ * @param {NodePath} referencePath
+ * @return {?ModuleBindingSpecifier}
+ */
+ModuleBindingList.prototype.findSpecifierForReference = function(referencePath) {
+ var declaration = this.findDeclarationForReference(referencePath);
+
+ if (!declaration) {
+ return null;
+ }
+
+ var specifier = this.findSpecifierByIdentifier(declaration.node);
+ assert.ok(
+ specifier,
+ 'no specifier found for `' + referencePath.node.name + '`! this should not happen!'
+ );
+ return specifier;
+};
+
+/**
+ * @private
+ */
+ModuleBindingList.prototype.findDeclarationForReference = function(referencePath) {
+ // Check names to avoid traversing scopes for all references.
+ if (this.names.indexOf(referencePath.node.name) < 0) {
+ return null;
+ }
+
+ var node = referencePath.node;
+ var declaringScope = referencePath.scope.lookup(node.name);
+ assert.ok(
+ declaringScope,
+ '`' + node.name + '` at ' + sourcePosition(this.module, node) +
+ ' cannot be bound if it is not declared'
+ );
+
+ // Bindings are at the top level, so if this isn't then it's shadowing.
+ if (!declaringScope.isGlobal) {
+ return null;
+ }
+
+ var declarations = declaringScope.getBindings()[node.name];
+ if (!declarations || declarations.length !== 1) {
+ throw new SyntaxError(
+ 'expected one declaration for `' + node.name +
+ '`, at ' + sourcePosition(this.module, node) +
+ ' but found ' + (declarations ? declarations.length : 'none')
+ );
+ }
+
+ return declarations[0];
+};
+
+/**
+ * Generate a string representing this object to aid debugging.
+ *
+ * @return {string}
+ */
+ModuleBindingList.prototype.inspect = function() {
+ var result = '#<' + this.constructor.name;
+
+ result += ' module=' + this.module.relativePath;
+
+ if (this.declarations.length > 0) {
+ result += ' declarations=' + this.declarations.map(function(imp) {
+ return imp.inspect();
+ }).join(', ');
+ }
+
+ result += '>';
+ return result;
+};
+
+/**
+ * @see ModuleBindingList#inspect
+ */
+ModuleBindingList.prototype.toString = ModuleBindingList.prototype.inspect;
+
+/**
+ * Contains a list of declarations.
+ *
+ * @type {(ImportDeclaration[]|ExportDeclaration[])}
+ * @name ModuleBindingList#declarations
+ */
+memo(ModuleBindingList.prototype, 'declarations', /** @this ModuleBindingList */function() {
+ var self = this;
+
+ return this._nodes.map(function(child) {
+ return self.declarationForNode(child);
+ });
+});
+
+/**
+ * Contains a combined list of names for all the declarations contained in this
+ * list.
+ *
+ * @type {string[]}
+ * @name ModuleBindingList#names
+ */
+memo(ModuleBindingList.prototype, 'names', /** @this ModuleBindingList */function() {
+ return this.declarations.reduce(function(names, decl) {
+ return names.concat(decl.specifiers.map(function(specifier) {
+ return specifier.name;
+ }));
+ }, []);
+});
+
+module.exports = ModuleBindingList;
diff --git a/lib/module_binding_specifier.js b/lib/module_binding_specifier.js
new file mode 100644
index 0000000..5551e8f
--- /dev/null
+++ b/lib/module_binding_specifier.js
@@ -0,0 +1,244 @@
+/* jshint node:true, undef:true, unused:true */
+
+var assert = require('assert');
+
+var recast = require('recast');
+var types = recast.types;
+var n = types.namedTypes;
+
+var utils = require('./utils');
+var memo = utils.memo;
+var sourcePosition = utils.sourcePosition;
+
+/**
+ * A module binding specifier provides the shared functionality of
+ * ImportSpecifiers and ExportSpecifiers in the ES6 spec.
+ *
+ * @constructor
+ * @param {ModuleBindingDeclaration} declaration
+ * @param {AST.NamedSpecifier} node
+ */
+function ModuleBindingSpecifier(declaration, node) {
+ Object.defineProperties(this, {
+ /**
+ * @name ModuleBindingSpecifier#declaration
+ * @type {ModuleBindingDeclaration}
+ */
+ declaration: {
+ value: declaration
+ },
+
+ /**
+ * @name ModuleBindingSpecifier#node
+ * @type {AST.NamedSpecifier}
+ */
+ node: {
+ value: node
+ }
+ });
+}
+
+/**
+ * Gets the module this specifier is declared in.
+ *
+ * @type Module
+ * @name ModuleBindingSpecifier#module
+ */
+memo(ModuleBindingSpecifier.prototype, 'module', /** @this ModuleBindingSpecifier */function() {
+ return this.declaration.module;
+});
+
+/**
+ * Gets the scope at the top level of the module.
+ *
+ * @type {Scope}
+ * @name ModuleBindingSpecifier#moduleScope
+ */
+memo(ModuleBindingSpecifier.prototype, 'moduleScope', /** @this ModuleBindingSpecifier */function() {
+ return this.declaration.moduleScope;
+});
+
+/**
+ * Gets the name of this specifier. For import specifiers this is the name of
+ * the binding this specifier will create locally, i.e. "foo" in both of these
+ * import statements:
+ *
+ * import { foo } from "util";
+ * import { bar as foo } from "util";
+ *
+ * In export specifiers it is the name of the exported declaration or the alias
+ * given to an internal name, i.e. "foo" in both of these export statements:
+ *
+ * export { bar as foo };
+ * export var foo = 1;
+ *
+ * @type {string}
+ * @name ModuleBindingSpecifier#name
+ */
+memo(ModuleBindingSpecifier.prototype, 'name', /** @this ModuleBindingSpecifier */function() {
+ return this.identifier.name;
+});
+
+/**
+ * Gets the name of the identifier this specifier comes from as distinct from
+ * `name`. This value will only be set if the local name and the
+ * imported/exported name differ, i.e. it will be "foo" in these statements:
+ *
+ * import { foo as bar } from "util";
+ * export { foo as bar };
+ *
+ * And it will be undefined in these statements:
+ *
+ * import { foo } from "util";
+ * export { foo };
+ *
+ * @type {string}
+ * @name ModuleBindingSpecifier#from
+ */
+memo(ModuleBindingSpecifier.prototype, 'from', /** @this ModuleBindingSpecifier */function() {
+ return this.node.id.name;
+});
+
+/**
+ * Gets the node that gives this specifier its name as it would be imported,
+ * i.e. "foo" in these statements:
+ *
+ * import { foo } from "utils";
+ * import { bar as foo } from "utils";
+ * export { foo };
+ * export { bar as foo };
+ *
+ * @type {AST.Identifier}
+ * @name ModuleBindingSpecifier#identifier
+ */
+memo(ModuleBindingSpecifier.prototype, 'identifier', /** @this ModuleBindingSpecifier */function() {
+ return this.node.name || this.node.id;
+});
+
+/**
+ * Gets the export specifier corresponding to this specifier. This can be from
+ * either an import or export declaration, since both can have a "from" part:
+ *
+ * import { map } from "array";
+ * export { map } from "array";
+ *
+ * In both of the above examples, the export specifier of `map` would be part
+ * of the export statement in the "array" module that exports it.
+ *
+ * @type {?ExportSpecifier}
+ * @name ModuleBindingSpecifier#exportSpecifier
+ */
+memo(ModuleBindingSpecifier.prototype, 'exportSpecifier', /** @this ModuleBindingSpecifier */function() {
+ var source = this.declaration.source;
+ if (source) {
+ var exports = source.exports;
+ return exports.findSpecifierByName(this.from);
+ } else {
+ return null;
+ }
+});
+
+/**
+ * Gets the import specifier corresponding to this specifier. This should only
+ * happen when exporting a binding that is imported in the same module, like so:
+ *
+ * import { map } from "array";
+ * export { map };
+ *
+ * The `map` export specifier has the `map` import specifier as its
+ * `importSpecifier` property value. The `map` import specifier has no
+ * `importSpecifier` property value.
+ *
+ * @type {?ImportSpecifier}
+ * @name ModuleBindingSpecifier#importSpecifier
+ */
+memo(ModuleBindingSpecifier.prototype, 'importSpecifier', /** @this ModuleBindingSpecifier */function() {
+ // This may be an export from this module, so find the declaration.
+ var localExportDeclarationInfo = this.moduleDeclaration;
+
+ if (localExportDeclarationInfo && n.ImportDeclaration.check(localExportDeclarationInfo.declaration)) {
+ // It was imported then exported with two separate declarations.
+ var exportModule = this.module;
+ return exportModule.imports.findSpecifierByIdentifier(localExportDeclarationInfo.identifier);
+ } else {
+ return null;
+ }
+});
+
+/**
+ * Gets the original export value by following chains of export/import
+ * statements. For example:
+ *
+ * // a.js
+ * export var a = 1;
+ *
+ * // b.js
+ * export { a } from "./a";
+ *
+ * // c.js
+ * import { a } from "./b";
+ * export { a };
+ *
+ * // d.js
+ * import { a } from "./c";
+ *
+ * The terminal export specifier for all of these specifiers is the export in
+ * a.js, since all of them can be traced back to that one.
+ *
+ * @type {ExportSpecifier}
+ * @name ModuleBindingSpecifier#terminalExportSpecifier
+ */
+memo(ModuleBindingSpecifier.prototype, 'terminalExportSpecifier', /** @this ModuleBindingSpecifier */function() {
+ if (this.exportSpecifier) {
+ // This is true for both imports and exports with a source, e.g.
+ // `import { foo } from 'foo'` or `export { foo } from 'foo'`.
+ return this.exportSpecifier.terminalExportSpecifier;
+ }
+
+ // This is an export from this module, so find the declaration.
+ var importSpecifier = this.importSpecifier;
+ if (importSpecifier) {
+ if (n.ImportNamespaceSpecifier.check(importSpecifier.node)) {
+ // Namespace imports create a local binding, so they are the terminal.
+ return importSpecifier;
+ }
+
+ var nextExportSpecifier = importSpecifier.exportSpecifier;
+ assert.ok(
+ nextExportSpecifier,
+ 'expected matching export in ' + importSpecifier.declaration.source.relativePath +
+ ' for import of `' + importSpecifier.name + '` at ' +
+ sourcePosition(this.module, this.moduleDeclaration.identifier)
+ );
+ return nextExportSpecifier.terminalExportSpecifier;
+ } else {
+ // It was declared in this module, so we are the terminal export specifier.
+ return this;
+ }
+});
+
+/**
+ * @type {?DeclarationInfo}
+ */
+ModuleBindingSpecifier.prototype.moduleDeclaration = null;
+
+/**
+ * Gets a string representation of this module binding specifier suitable for
+ * debugging.
+ *
+ * @return {string}
+ */
+ModuleBindingSpecifier.prototype.inspect = function() {
+ return '#<' + this.constructor.name +
+ ' module=' + this.declaration.module.relativePath +
+ ' name=' + this.name +
+ ' from=' + this.from +
+ '>';
+};
+
+/**
+ * @see ModuleBindingSpecifier#inspect
+ */
+ModuleBindingSpecifier.prototype.toString = ModuleBindingSpecifier.prototype.inspect;
+
+module.exports = ModuleBindingSpecifier;
diff --git a/lib/replacement.js b/lib/replacement.js
new file mode 100644
index 0000000..ffffa69
--- /dev/null
+++ b/lib/replacement.js
@@ -0,0 +1,95 @@
+/* jshint node:true, undef:true, unused:true */
+
+var recast = require('recast');
+
+/** @typedef [NodePath, AST.Node[]] */
+var ReplacementPair;
+
+/**
+ * Represents a replacement of a node path with zero or more nodes.
+ *
+ * @constructor
+ * @param {NodePath=} nodePath
+ * @param {AST.Node[]=} nodes
+ */
+function Replacement(nodePath, nodes) {
+ /**
+ * @private
+ * @type {ReplacementPair[]}
+ */
+ this.queue = [];
+ if (nodePath && nodes) {
+ this.queue.push([nodePath, nodes]);
+ }
+}
+
+/**
+ * Performs the replacement.
+ */
+Replacement.prototype.replace = function() {
+ for (var i = 0, length = this.queue.length; i < length; i++) {
+ var item = this.queue[i];
+ item[0].replace.apply(item[0], item[1]);
+ }
+};
+
+/**
+ * Incorporates the replacements from the given Replacement into this one.
+ *
+ * @param {Replacement} anotherReplacement
+ */
+Replacement.prototype.and = function(anotherReplacement) {
+ this.queue.push.apply(this.queue, anotherReplacement.queue);
+ return this;
+};
+
+/**
+ * Constructs a Replacement that, when run, will remove the node from the AST.
+ *
+ * @param {NodePath} nodePath
+ * @return {Replacement}
+ */
+Replacement.removes = function(nodePath) {
+ return new Replacement(nodePath, []);
+};
+
+/**
+ * Constructs a Replacement that, when run, will insert the given nodes after
+ * the one in nodePath.
+ *
+ * @param {NodePath} nodePath
+ * @param {AST.Node[]} nodes
+ * @return {Replacement}
+ */
+Replacement.adds = function(nodePath, nodes) {
+ return new Replacement(nodePath, [nodePath.node].concat(nodes));
+};
+
+/**
+ * Constructs a Replacement that, when run, swaps the node in nodePath with the
+ * given node or nodes.
+ *
+ * @param {NodePath} nodePath
+ * @param {AST.Node|AST.Node[]} nodes
+ */
+Replacement.swaps = function(nodePath, nodes) {
+ if (!Array.isArray(nodes)) {
+ nodes = [nodes];
+ }
+ return new Replacement(nodePath, nodes);
+};
+
+Replacement.map = function(nodePaths, callback) {
+ var result = new Replacement();
+
+ nodePaths.each(function(nodePath) {
+ var replacement = callback(nodePath);
+ if (replacement) {
+ result.and(replacement);
+ }
+ });
+
+ return result;
+};
+
+module.exports = Replacement;
diff --git a/lib/rewriter.js b/lib/rewriter.js
new file mode 100644
index 0000000..3a9466b
--- /dev/null
+++ b/lib/rewriter.js
@@ -0,0 +1,416 @@
+/* jshint node:true, undef:true, unused:true */
+
+var assert = require('assert');
+var recast = require('recast');
+var types = recast.types;
+var n = types.namedTypes;
+var b = types.builders;
+var astUtil = require('ast-util');
+
+var utils = require('./utils');
+var extend = utils.extend;
+var sourcePosition = utils.sourcePosition;
+var Replacement = require('./replacement');
+
+/**
+ * Replaces references to local bindings created by `mod`'s imports
+ * with references to the original value in the source module.
+ *
+ * @constructor
+ * @param {Formatter} formatter
+ * @extends types.PathVisitor
+ */
+function Rewriter(formatter) {
+ types.PathVisitor.call(this);
+
+ Object.defineProperties(this, {
+ formatter: {
+ value: formatter
+ }
+ });
+}
+extend(Rewriter, types.PathVisitor);
+
+/**
+ * Rewrites references to all imported and exported bindings according to the
+ * rules from this rewriter's formatter. For example, this module:
+ *
+ * ```js
+ * import { sin, cos } from './math';
+ * import fib from './math/fib';
+ *
+ * assert.equal(sin(0), 0);
+ * assert.equal(cos(0), 1);
+ * assert.equal(fib(1), 1);
+ * ```
+ *
+ * has its references to the imported bindings `sin`, `cos`, and `fib`
+ * rewritten to reference the source module:
+ *
+ * ```js
+ * assert.equal(math$$.sin(0), 0);
+ * assert.equal(math$$.cos(0), 1);
+ * assert.equal(math$fib$$.fib(1), 1);
+ * ```
+ *
+ * @param {Module[]} modules
+ */
+Rewriter.prototype.rewrite = function(modules) {
+ var replacements = [];
+
+ // FIXME: This is just here to ensure that all imports and exports know where
+ // they came from. We need this because after we re-write the declarations
+ // will not be there anymore and we'll need to ensure they're cached up front.
+ modules.forEach(function(mod) {
+ [mod.exports, mod.imports].forEach(function(declarations) {
+ declarations.declarations.forEach(function(declaration) {
+ declaration.specifiers.forEach(function(specifier) {
+ return specifier.importSpecifier;
+ });
+ });
+ });
+ });
+
+ this.replacements = replacements;
+ for (var i = 0, length = modules.length; i < length; i++) {
+ var mod = modules[i];
+ if (mod.exports.declarations.length > 0 || mod.imports.declarations.length > 0) {
+ this.currentModule = mod;
+ types.visit(mod.ast.program, this);
+ } else {
+ types.visit(mod.ast.program, new DeclarationLinterVisitor(mod));
+ }
+ }
+ this.currentModule = null;
+ this.replacements = null;
+
+ replacements.forEach(function(replacement) {
+ if (replacement.replace) {
+ replacement.replace();
+ } else {
+ var path = replacement.shift();
+ path.replace.apply(path, replacement);
+ }
+ });
+};
+
+/**
+ * Process all identifiers looking for references to variables in scope.
+ *
+ * @param {NodePath} nodePath
+ * @return {boolean}
+ * @private
+ */
+Rewriter.prototype.visitIdentifier = function(nodePath) {
+ if (astUtil.isReference(nodePath)) {
+ var exportReference = this.getExportReferenceForReference(this.currentModule, nodePath);
+ if (exportReference) {
+ this.replacements.push(Replacement.swaps(nodePath, exportReference));
+ }
+ }
+
+ return false;
+};
+
+/**
+ * Process all variable declarations looking for top-level exported variables.
+ *
+ * @param {NodePath} nodePath
+ * @private
+ */
+Rewriter.prototype.visitVariableDeclaration = function(nodePath) {
+ if (nodePath.scope.isGlobal) {
+ var replacement = this.formatter.processVariableDeclaration(this.currentModule, nodePath);
+ if (replacement) {
+ this.replacements.push(replacement);
+ }
+ }
+
+ this.traverse(nodePath);
+};
+
+/**
+ * We need to ensure that the LHS of this assignment is not an imported binding.
+ * If it is, we throw a "compile"-time error since this is not allowed by the
+ * spec (see section 12.14.1, Assignment Operators / Static Semantics: Early
+ * Errors).
+ *
+ * @param {NodePath} nodePath
+ * @private
+ */
+Rewriter.prototype.visitAssignmentExpression = function(nodePath) {
+ this.assertImportIsNotReassigned(this.currentModule, nodePath.get('left'));
+ if (this.currentModule.exports.findDeclarationForReference(nodePath.get('left'))) {
+ var replacement = this.formatter.processExportReassignment(this.currentModule, nodePath);
+ if (replacement) {
+ this.replacements.push(replacement);
+ }
+ }
+
+ this.traverse(nodePath);
+};
+
+/**
+ * Process all top-level function declarations in case they need to be processed.
+ *
+ * @param {NodePath} nodePath
+ * @private
+ */
+Rewriter.prototype.visitFunctionDeclaration = function(nodePath) {
+ if (n.Program.check(nodePath.parent.node)) {
+ var replacement = this.formatter.processFunctionDeclaration(this.currentModule, nodePath);
+ if (replacement) {
+ this.replacements.push(replacement);
+ }
+ }
+
+ this.traverse(nodePath);
+};
+
+/**
+ * Process all top-level class declarations in case they need to be processed.
+ *
+ * @param {NodePath} nodePath
+ * @private
+ */
+Rewriter.prototype.visitClassDeclaration = function(nodePath) {
+ if (n.Program.check(nodePath.parent.node)) {
+ var replacement = this.formatter.processClassDeclaration(this.currentModule, nodePath);
+ if (replacement) {
+ this.replacements.push(replacement);
+ }
+ }
+
+ this.traverse(nodePath);
+};
+
+/**
+ * Look for all export declarations so we can rewrite them.
+ *
+ * @param {NodePath} nodePath
+ * @private
+ */
+Rewriter.prototype.visitExportDeclaration = function(nodePath) {
+ assertStatementIsTopLevel(this.currentModule, nodePath);
+
+ var replacement;
+ if (nodePath.node.default) {
+ /**
+ * Default exports do not create bindings, so we can safely turn these
+ * into expressions that do something with the exported value.
+ *
+ * Make sure that the exported value is replaced if it is a reference
+ * to an imported binding. For example:
+ *
+ * import { foo } from './foo';
+ * export default foo;
+ *
+ * Might become:
+ *
+ * mod$$.default = foo$$.foo;
+ */
+ var declaration = nodePath.node.declaration;
+ var declarationPath = nodePath.get('declaration');
+ if (astUtil.isReference(declarationPath)) {
+ var exportReference = this.getExportReferenceForReference(this.currentModule, declarationPath);
+ if (exportReference) {
+ declaration = exportReference;
+ }
+ }
+ replacement = Replacement.swaps(nodePath, this.formatter.defaultExport(this.currentModule, declaration));
+ } else {
+ replacement = this.formatter.processExportDeclaration(this.currentModule, nodePath);
+ }
+
+ if (replacement) {
+ this.replacements.push(replacement);
+ }
+
+ this.traverse(nodePath);
+};
+
+/**
+ * Process import declarations so they can be rewritten.
+ *
+ * @param {NodePath} nodePath
+ * @private
+ */
+Rewriter.prototype.visitImportDeclaration = function(nodePath) {
+ assertStatementIsTopLevel(this.currentModule, nodePath);
+ var replacement = this.formatter.processImportDeclaration(this.currentModule, nodePath);
+ if (replacement) {
+ this.replacements.push(replacement);
+ }
+
+ this.traverse(nodePath);
+};
+
+/**
+ * Process update expressions (e.g. `a++`) so we can re-write modifications to
+ * exported variables.
+ *
+ * @param {NodePath} nodePath
+ * @private
+ */
+Rewriter.prototype.visitUpdateExpression = function(nodePath) {
+ this.assertImportIsNotReassigned(this.currentModule, nodePath.get('argument'));
+ if (this.currentModule.exports.findDeclarationForReference(nodePath.get('argument'))) {
+ var replacement = this.formatter.processExportReassignment(this.currentModule, nodePath);
+ if (replacement) {
+ this.replacements.push(replacement);
+ }
+ }
+
+ this.traverse(nodePath);
+};
+
+/**
+ * We need to ensure that reference updates (i.e. `ref =`, `ref++`) are not
+ * allowed for imported bindings. If it is, we throw a "compile"-time error
+ * since this is not allowed by the spec (see section 12.14.1, Assignment
+ * Operators / Static Semantics: Early Errors).
+ *
+ * @private
+ * @param {Module} mod
+ * @param {Identifier} nodePath
+ */
+Rewriter.prototype.assertImportIsNotReassigned = function(mod, nodePath) {
+ var declarationPath;
+ var identifierPath;
+ var bindingDescription;
+
+ if (n.Identifier.check(nodePath.node)) {
+ // Do we have a named import…
+ //
+ // import { foo } from 'foo';
+ //
+ // …that we then try to assign or update?
+ //
+ // foo++;
+ // foo = 1;
+ //
+ declarationPath = mod.imports.findDeclarationForReference(nodePath);
+ if (!declarationPath || !n.ImportSpecifier.check(declarationPath.parent.node)) {
+ return;
+ }
+
+ bindingDescription = '`' + declarationPath.node.name + '`';
+ } else if (n.MemberExpression.check(nodePath.node)) {
+ // Do we have a namespace import…
+ //
+ // import * as foo from 'foo';
+ //
+ // …with a property that we then try to assign or update?
+ //
+ // foo.a++;
+ // foo.a = 1;
+ // foo['a'] = 1;
+ //
+ var objectPath = nodePath.get('object');
+
+ if (!n.Identifier.check(objectPath.node)) {
+ return;
+ }
+
+ declarationPath = mod.imports.findDeclarationForReference(objectPath);
+ if (!declarationPath || !n.ImportNamespaceSpecifier.check(declarationPath.parent.node)) {
+ return;
+ }
+
+ var propertyPath = nodePath.get('property');
+ if (n.Identifier.check(propertyPath.node)) {
+ bindingDescription = '`' + propertyPath.node.name + '`';
+ } else {
+ bindingDescription = 'of namespace `' + objectPath.node.name + '`';
+ }
+ } else {
+ return;
+ }
+
+ throw new SyntaxError(
+ 'Cannot reassign imported binding ' + bindingDescription +
+ ' at ' + sourcePosition(mod, nodePath.node)
+ );
+};
+
+/**
+ * @private
+ */
+Rewriter.prototype.getExportReferenceForReference = function(mod, referencePath) {
+ if (n.ExportSpecifier.check(referencePath.parent.node) && !referencePath.parent.node.default) {
+ // Do not rewrite non-default export specifiers.
+ return null;
+ }
+
+ /**
+ * We need to replace references to variables that are imported or
+ * exported with the correct export expression. The export expression
+ * should be named for the original export for a variable.
+ *
+ * That is, imports must be followed to their export. If that exported
+ * value came from an import then repeat the process until you find a
+ * declaration of the exported value.
+ */
+ var exportSpecifier = mod.exports.findSpecifierByName(referencePath.node.name);
+ if (exportSpecifier && exportSpecifier.declaration.source && exportSpecifier.node !== referencePath.parent.node) {
+ // This is a direct export from another module, e.g. `export { foo } from
+ // 'foo'`. There are no local bindings created by this, so there is no
+ // associated export for this reference and no need to rewrite it.
+ return null;
+ }
+
+ return this.formatter.exportedReference(mod, referencePath) ||
+ this.formatter.importedReference(mod, referencePath) ||
+ this.formatter.localReference(mod, referencePath);
+};
+
+/**
+ * Traverses ASTs only checking for invalid import/export declaration semantics.
+ *
+ * @constructor
+ * @extends types.PathVisitor
+ * @param {Module} mod
+ * @private
+ */
+function DeclarationLinterVisitor(mod) {
+ this.module = mod;
+ types.PathVisitor.call(this);
+}
+extend(DeclarationLinterVisitor, types.PathVisitor);
+
+/**
+ * Checks that the import declaration is at the top level.
+ *
+ * @param {NodePath} nodePath
+ */
+DeclarationLinterVisitor.prototype.visitImportDeclaration = function(nodePath) {
+ assertStatementIsTopLevel(this.module, nodePath);
+};
+
+/**
+ * Checks that the export declaration is at the top level.
+ *
+ * @param {NodePath} nodePath
+ */
+DeclarationLinterVisitor.prototype.visitExportDeclaration = function(nodePath) {
+ assertStatementIsTopLevel(this.module, nodePath);
+};
+
+/**
+ * We need to ensure that all imports/exports are only at the top level. Esprima
+ * should perhaps take care of this for us, but it does not.
+ *
+ * @param {Module} mod
+ * @param {NodePath} nodePath
+ * @private
+ */
+function assertStatementIsTopLevel(mod, nodePath) {
+ if (!nodePath.scope.isGlobal) {
+ throw new SyntaxError(
+ 'Unexpected non-top level ' + nodePath.node.type +
+ ' found at ' + sourcePosition(mod, nodePath.node)
+ );
+ }
+}
+
+module.exports = Rewriter;
diff --git a/lib/sorting.js b/lib/sorting.js
new file mode 100644
index 0000000..372e50d
--- /dev/null
+++ b/lib/sorting.js
@@ -0,0 +1,45 @@
+/**
+ * Determines the execution order of the given modules. This function resolves
+ * cycles by preserving the order in which the modules are visited.
+ *
+ * @param {Module[]} modules
+ * @return {Module[]}
+ */
+function sort(modules) {
+ var result = [];
+ var state = {};
+
+ modules.forEach(function(mod) {
+ visit(mod, result, state);
+ });
+
+ return result;
+}
+exports.sort = sort;
+
+/**
+ * Visits the given module, adding it to `result` after visiting all of the
+ * modules it imports, recursively. The `state` argument is private and maps
+ * module ids to the current visit state.
+ *
+ * @private
+ * @param {Module} mod
+ * @param {Module[]} result
+ * @param {Object.<string,string>} state
+ */
+function visit(mod, result, state) {
+ if (state[mod.id] === 'added') {
+ // already in the list, ignore it
+ return;
+ }
+ if (state[mod.id] === 'seen') {
+ // cycle found, just ignore it
+ return;
+ }
+ state[mod.id] = 'seen';
+ mod.imports.modules.forEach(function(mod) {
+ visit(mod, result, state);
+ });
+ state[mod.id] = 'added';
+ result.push(mod);
+}
diff --git a/lib/utils.js b/lib/utils.js
new file mode 100644
index 0000000..bb58bce
--- /dev/null
+++ b/lib/utils.js
@@ -0,0 +1,134 @@
+/* jshint node:true, undef:true, unused:true */
+
+var recast = require('recast');
+var n = recast.types.namedTypes;
+var b = recast.types.builders;
+var reserved = require('reserved');
+var realFS = require('fs');
+var Path = require('path');
+
+var proto = '__proto__';
+
+function memo(object, property, getter) {
+ Object.defineProperty(object, property, {
+ get: function() {
+ this[property] = getter.call(this);
+ return this[property];
+ },
+
+ set: function(value) {
+ Object.defineProperty(this, property, {
+ value: value,
+ configurable: true,
+ writable: true
+ });
+ }
+ });
+}
+exports.memo = memo;
+
+function startsWith(string, substring) {
+ return string.lastIndexOf(substring, 0) === 0;
+}
+exports.startsWith = startsWith;
+
+function endsWith(string, substring) {
+ var expected = string.length - substring.length;
+ return string.indexOf(substring, expected) === expected;
+}
+exports.endsWith = endsWith;
+
+function extend(subclass, superclass) {
+ subclass[proto] = superclass;
+ subclass.prototype = Object.create(superclass.prototype);
+ subclass.prototype.constructor = subclass;
+}
+exports.extend = extend;
+
+function sourcePosition(mod, node) {
+ var loc = node && node.loc;
+ if (loc) {
+ return mod.relativePath + ':' + loc.start.line + ':' + (loc.start.column + 1);
+ } else {
+ return mod.relativePath;
+ }
+}
+exports.sourcePosition = sourcePosition;
+
+function IIFE() {
+ var body = [];
+ var args = Array.prototype.concat.apply(body, arguments);
+
+ args.forEach(function(node) {
+ if (n.Expression.check(node)) {
+ node = b.expressionStatement(node);
+ }
+ if (n.Statement.check(node)) {
+ body.push(node);
+ }
+ });
+
+ return b.callExpression(
+ b.memberExpression(
+ b.functionExpression(null, [], b.blockStatement(body)),
+ b.identifier("call"),
+ false
+ ),
+ [b.thisExpression()]
+ );
+}
+exports.IIFE = IIFE;
+
+/**
+ * Create a member express that is compatible with ES3. Which means
+ * reserved words will be treated as computed props. E.g.:
+ *
+ * foo["default"] // instead of `foo.default`
+ *
+ * while still supporting identifiers for non-reserved words:
+ *
+ * foo.bar // since bar is not reserved
+ *
+ * @param {Identifier} obj Identifier for the object reference
+ * @param {Identifier|String} prop Identifier or string name for the member property
+ * @return {b.memberExpression} AST for the member expression
+ */
+function compatMemberExpression(obj, prop) {
+ var isIdentifier = n.Identifier.check(prop);
+ var name = isIdentifier ? prop.name : prop;
+ var computed = reserved.indexOf(name) >= 0;
+ return b.memberExpression(
+ obj,
+ computed ? b.literal(name) : (isIdentifier ? prop : b.identifier(prop)),
+ computed
+ );
+}
+
+exports.compatMemberExpression = compatMemberExpression;
+
+/**
+ * Create a hierarchy of directories of it does not already exist.
+ *
+ * @param {string} path
+ * @param {{fs: object=}} options
+ */
+function mkdirpSync(path, options) {
+ var fs = options && options.fs || realFS;
+
+ var ancestors = [];
+ var ancestor = path;
+
+ while (true) {
+ var nextAncestor = Path.dirname(ancestor);
+ if (nextAncestor === ancestor) { break; }
+ ancestors.unshift(ancestor);
+ ancestor = nextAncestor;
+ }
+
+ ancestors.forEach(function(dir) {
+ if (!fs.existsSync(dir)) {
+ fs.mkdirSync(dir);
+ }
+ });
+}
+exports.mkdirpSync = mkdirpSync;
diff --git a/lib/writer.js b/lib/writer.js
new file mode 100644
index 0000000..e9a4d70
--- /dev/null
+++ b/lib/writer.js
@@ -0,0 +1,78 @@
+/* jshint node:true, undef:true, unused:true */
+
+var assert = require('assert');
+var recast = require('recast');
+var fs = require('fs');
+var Path = require('path');
+var mkdirpSync = require('./utils').mkdirpSync;
+
+function Writer(target, options) {
+ options = options || {};
+ this.target = target;
+ this.basePath = options.basePath || process.cwd();
+ this.sourceRoot = options.sourceRoot;
+}
+
+Writer.prototype.write = function(files) {
+ var target = this.target;
+
+ switch (files.length) {
+ case 0:
+ throw new Error('expected at least one file to write, got zero');
+
+ case 1:
+ // We got a single file, so `target` should refer to either a file or a
+ // directory, but only if the file has a name.
+ var isDirectory = false;
+ try {
+ isDirectory = fs.statSync(target).isDirectory();
+ } catch (ex) {}
+
+ assert.ok(
+ !isDirectory || files[0].filename,
+ 'unable to determine filename for output to directory: ' + target
+ );
+ this.writeFile(
+ files[0],
+ isDirectory ? Path.resolve(target, files[0].filename) : target
+ );
+ break;
+
+ default:
+ // We got multiple files to output, so `target` should be a directory or
+ // not exist (so we can create it).
+ var self = this;
+ files.forEach(function(file) {
+ self.writeFile(file, Path.resolve(target, file.filename));
+ });
+ break;
+ }
+};
+
+Writer.prototype.writeFile = function(file, filename) {
+ var sourceMapFilename = filename + '.map';
+
+ var rendered = recast.print(file, {
+ sourceMapName: Path.relative(this.basePath, filename),
+ sourceRoot: this.sourceRoot
+ });
+
+ var code = rendered.code;
+ assert.ok(filename, 'missing filename for file: ' + code);
+
+ mkdirpSync(Path.dirname(filename));
+
+ if (rendered.map) {
+ code += '\n\n//# sourceMappingURL=' + Path.basename(sourceMapFilename);
+
+ fs.writeFileSync(
+ sourceMapFilename,
+ JSON.stringify(rendered.map),
+ 'utf8'
+ );
+ }
+
+ fs.writeFileSync(filename, code, 'utf8');
+};
+
+module.exports = Writer;
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..521c227
--- /dev/null
+++ b/package.json
@@ -0,0 +1,60 @@
+{
+ "name": "es6-module-transpiler",
+ "version": "0.10.0",
+ "description": "es6-module-transpiler is an experimental compiler that allows you to write your JavaScript using a subset of the current ES6 module syntax, and compile it into various formats.",
+ "homepage": "http://esnext.github.io/es6-module-transpiler",
+ "keywords": [
+ "es6",
+ "module",
+ "transpile",
+ "amd",
+ "commonjs"
+ ],
+ "bugs": "https://github.com/square/es6-module-transpiler/issues",
+ "bin": {
+ "compile-modules": "./bin/compile-modules"
+ },
+ "files": [
+ "bin",
+ "lib",
+ "LICENSE",
+ "README.md"
+ ],
+ "directories": {
+ "lib": "./lib",
+ "test": "test"
+ },
+ "main": "lib/index.js",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/square/es6-module-transpiler.git"
+ },
+ "scripts": {
+ "test": "npm run test-bundle && npm run test-commonjs && npm run test-unit",
+ "test-bundle": "node test/runner.js -f bundle",
+ "test-commonjs": "node test/runner.js -f commonjs",
+ "test-unit": "mocha -R spec test/unit",
+ "build-standalone": "browserify -s ModuleTranspiler -e lib/index.js -o es6-module-transpiler.js"
+ },
+ "author": "Square, Inc.",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "ast-util": "^0.5.1",
+ "esprima-fb": "^7001.1.0-dev-harmony-fb",
+ "posix-getopt": "^1.0.0",
+ "recast": "^0.9.5",
+ "reserved": "^0.1.2"
+ },
+ "devDependencies": {
+ "browserify": "^6.3.2",
+ "es6-class": "^0.9.2",
+ "example-runner": "^0.2.0",
+ "fake-fs": "^0.5.0",
+ "mocha": "^2.0.1",
+ "tmp": "0.0.24"
+ },
+ "browser": {
+ "fs": "./lib/browser/fs.js",
+ "./lib/index.js": "./lib/browser/index.js"
+ }
+}
\ No newline at end of file
diff --git a/test/examples/bare-import/exporter.js b/test/examples/bare-import/exporter.js
new file mode 100644
index 0000000..89cf73f
--- /dev/null
+++ b/test/examples/bare-import/exporter.js
@@ -0,0 +1,3 @@
+/* jshint esnext:true */
+
+global.sideEffectyValue = 99;
diff --git a/test/examples/bare-import/importer.js b/test/examples/bare-import/importer.js
new file mode 100644
index 0000000..d9c51a0
--- /dev/null
+++ b/test/examples/bare-import/importer.js
@@ -0,0 +1,5 @@
+/* jshint esnext:true */
+
+import './exporter';
+
+assert.equal(global.sideEffectyValue, 99);
diff --git a/test/examples/bindings/exporter.js b/test/examples/bindings/exporter.js
new file mode 100644
index 0000000..a635466
--- /dev/null
+++ b/test/examples/bindings/exporter.js
@@ -0,0 +1,7 @@
+/* jshint esnext:true */
+
+export var count = 0;
+
+export function incr() {
+ count++;
+}
diff --git a/test/examples/bindings/importer.js b/test/examples/bindings/importer.js
new file mode 100644
index 0000000..df69785
--- /dev/null
+++ b/test/examples/bindings/importer.js
@@ -0,0 +1,7 @@
+/* jshint esnext:true */
+
+import { count, incr } from './exporter';
+
+assert.equal(count, 0);
+incr();
+assert.equal(count, 1);
diff --git a/test/examples/cycles-defaults/a.js b/test/examples/cycles-defaults/a.js
new file mode 100644
index 0000000..4e0289b
--- /dev/null
+++ b/test/examples/cycles-defaults/a.js
@@ -0,0 +1,5 @@
+/* jshint esnext:true */
+
+import b from './b';
+
+export default { a: 1, get b() { return b.b; } };
diff --git a/test/examples/cycles-defaults/b.js b/test/examples/cycles-defaults/b.js
new file mode 100644
index 0000000..3ce87b6
--- /dev/null
+++ b/test/examples/cycles-defaults/b.js
@@ -0,0 +1,5 @@
+/* jshint esnext:true */
+
+import a from './a';
+
+export default { b: 2, get a() { return a.a; } };
diff --git a/test/examples/cycles-defaults/importer.js b/test/examples/cycles-defaults/importer.js
new file mode 100644
index 0000000..7b5708c
--- /dev/null
+++ b/test/examples/cycles-defaults/importer.js
@@ -0,0 +1,9 @@
+/* jshint esnext:true */
+
+import a from './a';
+import b from './b';
+
+assert.equal(a.a, 1);
+assert.equal(a.b, 2);
+assert.equal(b.a, 1);
+assert.equal(b.b, 2);
diff --git a/test/examples/cycles-immediate/evens.js b/test/examples/cycles-immediate/evens.js
new file mode 100644
index 0000000..7626849
--- /dev/null
+++ b/test/examples/cycles-immediate/evens.js
@@ -0,0 +1,20 @@
+/* jshint esnext:true */
+
+import { nextOdd } from './odds';
+
+/**
+ * We go through these gymnastics to eager-bind to nextOdd. This is done to
+ * ensure that both this module and the 'odds' module eagerly use something
+ * from the other.
+ */
+export var nextEven = (function() {
+ return function(n) {
+ var no = nextOdd(n);
+ return (no === n + 2) ?
+ no - 1 : no;
+ };
+})(nextOdd);
+
+export function isEven(n) {
+ return n % 2 === 0;
+}
diff --git a/test/examples/cycles-immediate/main.js b/test/examples/cycles-immediate/main.js
new file mode 100644
index 0000000..cfe2fcd
--- /dev/null
+++ b/test/examples/cycles-immediate/main.js
@@ -0,0 +1,24 @@
+/* jshint esnext:true */
+
+/**
+ * The 'evens' and 'odds' modules are configured in such a way that they both
+ * have two exported functions: isEven, nextEven, isOdd, and nextOdd. Normally
+ * these four functions could be in any order regardless of which depends on
+ * which because of JavaScript function hoisting.
+ *
+ * For the purposes of our test we need to prevent function hoisting, so it has
+ * been arranged that two of them will be function expressions assigned to
+ * variables. Specifically, isOdd and nextEven both eagerly evaluate their
+ * dependencies (i.e. isEven and nextOdd). This allows us to test that exported
+ * function declarations are available before what would be a module's
+ * "execute" step, per the spec.
+ */
+import { nextEven, isEven } from './evens';
+import { nextOdd, isOdd } from './odds';
+
+assert.equal(nextEven(1), 2);
+assert.equal(nextOdd(1), 3);
+assert.ok(isOdd(1));
+assert.ok(!isOdd(0));
+assert.ok(isEven(0));
+assert.ok(!isEven(1));
diff --git a/test/examples/cycles-immediate/odds.js b/test/examples/cycles-immediate/odds.js
new file mode 100644
index 0000000..618bf87
--- /dev/null
+++ b/test/examples/cycles-immediate/odds.js
@@ -0,0 +1,18 @@
+/* jshint esnext:true */
+
+import { isEven } from './evens';
+
+export function nextOdd(n) {
+ return isEven(n) ? n + 1 : n + 2;
+}
+
+/**
+ * We go through these gymnastics to eager-bind to isEven. This is done to
+ * ensure that both this module and the 'evens' module eagerly use something
+ * from the other.
+ */
+export var isOdd = (function(isEven) {
+ return function(n) {
+ return !isEven(n);
+ };
+})(isEven);
diff --git a/test/examples/cycles/a.js b/test/examples/cycles/a.js
new file mode 100644
index 0000000..511370c
--- /dev/null
+++ b/test/examples/cycles/a.js
@@ -0,0 +1,9 @@
+/* jshint esnext:true */
+
+import { b } from './b';
+
+export function getb() {
+ return b;
+}
+
+export var a = 1;
diff --git a/test/examples/cycles/b.js b/test/examples/cycles/b.js
new file mode 100644
index 0000000..5a66f9b
--- /dev/null
+++ b/test/examples/cycles/b.js
@@ -0,0 +1,9 @@
+/* jshint esnext:true */
+
+import { a } from './a';
+
+export function geta() {
+ return a;
+}
+
+export var b = 2;
diff --git a/test/examples/cycles/c.js b/test/examples/cycles/c.js
new file mode 100644
index 0000000..c9a5044
--- /dev/null
+++ b/test/examples/cycles/c.js
@@ -0,0 +1,9 @@
+/* jshint esnext:true */
+
+import { a, getb } from './a';
+import { b, geta } from './b';
+
+assert.equal(geta(), 1);
+assert.equal(a, 1);
+assert.equal(getb(), 2);
+assert.equal(b, 2);
diff --git a/test/examples/duplicate-import-fails/exporter.js b/test/examples/duplicate-import-fails/exporter.js
new file mode 100644
index 0000000..6c0b10d
--- /dev/null
+++ b/test/examples/duplicate-import-fails/exporter.js
@@ -0,0 +1,3 @@
+/* jshint esnext:true */
+
+export var a = 1;
\ No newline at end of file
diff --git a/test/examples/duplicate-import-fails/importer.js b/test/examples/duplicate-import-fails/importer.js
new file mode 100644
index 0000000..0436e34
--- /dev/null
+++ b/test/examples/duplicate-import-fails/importer.js
@@ -0,0 +1,7 @@
+/* jshint esnext:true */
+
+/* error: type=SyntaxError message="expected one declaration for `a`, at importer.js:7:14 but found 2" */
+import { a } from './exporter';
+import { a } from './exporter';
+
+assert.equal(a, 1);
\ No newline at end of file
diff --git a/test/examples/duplicate-import-specifier-fails/exporter.js b/test/examples/duplicate-import-specifier-fails/exporter.js
new file mode 100644
index 0000000..6c0b10d
--- /dev/null
+++ b/test/examples/duplicate-import-specifier-fails/exporter.js
@@ -0,0 +1,3 @@
+/* jshint esnext:true */
+
+export var a = 1;
\ No newline at end of file
diff --git a/test/examples/duplicate-import-specifier-fails/importer.js b/test/examples/duplicate-import-specifier-fails/importer.js
new file mode 100644
index 0000000..7a46f0f
--- /dev/null
+++ b/test/examples/duplicate-import-specifier-fails/importer.js
@@ -0,0 +1,5 @@
+/* jshint esnext:true */
+
+/* error: type=SyntaxError message="expected one declaration for `a`, at importer.js:5:14 but found 2" */
+import { a, a } from './exporter';
+assert.equal(a, 1);
\ No newline at end of file
diff --git a/test/examples/export-and-import-reference-share-var/first.js b/test/examples/export-and-import-reference-share-var/first.js
new file mode 100644
index 0000000..56ee87e
--- /dev/null
+++ b/test/examples/export-and-import-reference-share-var/first.js
@@ -0,0 +1,4 @@
+/* jshint esnext:true */
+
+export var a = 1;
+assert.equal(a, 1);
\ No newline at end of file
diff --git a/test/examples/export-and-import-reference-share-var/second.js b/test/examples/export-and-import-reference-share-var/second.js
new file mode 100644
index 0000000..f1677a0
--- /dev/null
+++ b/test/examples/export-and-import-reference-share-var/second.js
@@ -0,0 +1,15 @@
+/* jshint esnext:true */
+
+import { a } from './first';
+
+// This variable declaration is going to be altered because `b` needs to be
+// re-written. We need to make sure that the `a` re-writing and the unaffected
+// `c` declarator are not being clobbered by that alteration.
+var a_ = a, b = 9, c = 'c';
+
+assert.equal(a, 1);
+assert.equal(a_, 1);
+assert.equal(b, 9);
+assert.equal(c, 'c');
+
+export { b };
diff --git a/test/examples/export-class-expression/exporter.js b/test/examples/export-class-expression/exporter.js
new file mode 100644
index 0000000..08eef6f
--- /dev/null
+++ b/test/examples/export-class-expression/exporter.js
@@ -0,0 +1,3 @@
+/* jshint esnext:true */
+
+export default class {};
diff --git a/test/examples/export-class-expression/importer.js b/test/examples/export-class-expression/importer.js
new file mode 100644
index 0000000..0056bf4
--- /dev/null
+++ b/test/examples/export-class-expression/importer.js
@@ -0,0 +1,5 @@
+/* jshint esnext:true */
+
+import Foo from './exporter';
+
+assert.strictEqual(new Foo().constructor, Foo);
diff --git a/test/examples/export-class/exporter.js b/test/examples/export-class/exporter.js
new file mode 100644
index 0000000..871c7f9
--- /dev/null
+++ b/test/examples/export-class/exporter.js
@@ -0,0 +1,3 @@
+/* jshint esnext:true */
+
+export class Foo {}
diff --git a/test/examples/export-class/importer.js b/test/examples/export-class/importer.js
new file mode 100644
index 0000000..d4d1752
--- /dev/null
+++ b/test/examples/export-class/importer.js
@@ -0,0 +1,5 @@
+/* jshint esnext:true */
+
+import { Foo } from './exporter';
+
+assert.strictEqual(new Foo().constructor, Foo);
diff --git a/test/examples/export-default-class/exporter.js b/test/examples/export-default-class/exporter.js
new file mode 100644
index 0000000..0342727
--- /dev/null
+++ b/test/examples/export-default-class/exporter.js
@@ -0,0 +1,8 @@
+/* jshint esnext:true */
+
+export default class Point {
+ constructor(x, y) {
+ this.x = x;
+ this.y = y;
+ }
+}
diff --git a/test/examples/export-default-class/importer.js b/test/examples/export-default-class/importer.js
new file mode 100644
index 0000000..ff40016
--- /dev/null
+++ b/test/examples/export-default-class/importer.js
@@ -0,0 +1,6 @@
+/* jshint esnext:true */
+
+import Point from './exporter';
+
+assert.strictEqual(new Point(1, 2).x, 1);
+assert.strictEqual(new Point(1, 2).y, 2);
diff --git a/test/examples/export-default-function/exporter.js b/test/examples/export-default-function/exporter.js
new file mode 100644
index 0000000..7203b31
--- /dev/null
+++ b/test/examples/export-default-function/exporter.js
@@ -0,0 +1,5 @@
+/* jshint esnext:true */
+
+export default function () {
+ return 1;
+}
diff --git a/test/examples/export-default-function/importer.js b/test/examples/export-default-function/importer.js
new file mode 100644
index 0000000..7886323
--- /dev/null
+++ b/test/examples/export-default-function/importer.js
@@ -0,0 +1,8 @@
+/* jshint esnext:true */
+
+import fn1 from './exporter';
+
+import { default as fn2 } from './exporter';
+
+assert.equal(fn1(), 1);
+assert.equal(fn2(), 1);
diff --git a/test/examples/export-default-named-function/exporter.js b/test/examples/export-default-named-function/exporter.js
new file mode 100644
index 0000000..5e4bb63
--- /dev/null
+++ b/test/examples/export-default-named-function/exporter.js
@@ -0,0 +1,7 @@
+export default function foo() {
+ return 1;
+}
+
+export function callsFoo() {
+ return foo();
+}
\ No newline at end of file
diff --git a/test/examples/export-default-named-function/importer.js b/test/examples/export-default-named-function/importer.js
new file mode 100644
index 0000000..d6f83dc
--- /dev/null
+++ b/test/examples/export-default-named-function/importer.js
@@ -0,0 +1,4 @@
+import foo, { callsFoo } from './exporter';
+
+assert.strictEqual(foo(), 1);
+assert.strictEqual(callsFoo(), 1);
\ No newline at end of file
diff --git a/test/examples/export-default/exporter.js b/test/examples/export-default/exporter.js
new file mode 100644
index 0000000..39138f4
--- /dev/null
+++ b/test/examples/export-default/exporter.js
@@ -0,0 +1,16 @@
+/* jshint esnext:true */
+
+var a = 42;
+
+export function change() {
+ a++;
+}
+
+assert.equal(a, 42);
+export default a;
+
+// Any replacement for the `export default` above needs to happen in the same
+// location. It cannot be done, say, at the end of the file. Otherwise the new
+// value of `a` will be used and will be incorrect.
+a = 0;
+assert.equal(a, 0);
diff --git a/test/examples/export-default/importer.js b/test/examples/export-default/importer.js
new file mode 100644
index 0000000..e368b11
--- /dev/null
+++ b/test/examples/export-default/importer.js
@@ -0,0 +1,12 @@
+/* jshint esnext:true */
+
+import value from './exporter';
+import { change } from './exporter';
+assert.equal(value, 42);
+
+change();
+assert.equal(
+ value,
+ 42,
+ 'default export should not be bound'
+);
diff --git a/test/examples/export-from-default/first.js b/test/examples/export-from-default/first.js
new file mode 100644
index 0000000..4f74980
--- /dev/null
+++ b/test/examples/export-from-default/first.js
@@ -0,0 +1,3 @@
+/* jshint esnext:true */
+
+export default 1;
diff --git a/test/examples/export-from-default/second.js b/test/examples/export-from-default/second.js
new file mode 100644
index 0000000..ee70dcc
--- /dev/null
+++ b/test/examples/export-from-default/second.js
@@ -0,0 +1,3 @@
+/* jshint esnext:true */
+
+export { default } from './first';
diff --git a/test/examples/export-from-default/third.js b/test/examples/export-from-default/third.js
new file mode 100644
index 0000000..df0fb26
--- /dev/null
+++ b/test/examples/export-from-default/third.js
@@ -0,0 +1,4 @@
+/* jshint esnext:true */
+
+import a from './second';
+assert.equal(a, 1);
diff --git a/test/examples/export-from/first.js b/test/examples/export-from/first.js
new file mode 100644
index 0000000..c17a6e9
--- /dev/null
+++ b/test/examples/export-from/first.js
@@ -0,0 +1,3 @@
+/* jshint esnext:true */
+
+export var a = 1;
diff --git a/test/examples/export-from/second.js b/test/examples/export-from/second.js
new file mode 100644
index 0000000..3d0b980
--- /dev/null
+++ b/test/examples/export-from/second.js
@@ -0,0 +1,7 @@
+/* jshint esnext:true */
+
+export { a } from './first';
+
+// This `a` reference should not be re-written because this export is not
+// creating a local binding.
+assert.equal(typeof a, 'undefined');
diff --git a/test/examples/export-from/third.js b/test/examples/export-from/third.js
new file mode 100644
index 0000000..95314fc
--- /dev/null
+++ b/test/examples/export-from/third.js
@@ -0,0 +1,4 @@
+/* jshint esnext:true */
+
+import { a } from './second';
+assert.equal(a, 1);
diff --git a/test/examples/export-function/exporter.js b/test/examples/export-function/exporter.js
new file mode 100644
index 0000000..f74c2ee
--- /dev/null
+++ b/test/examples/export-function/exporter.js
@@ -0,0 +1,6 @@
+/* jshint esnext:true */
+
+export function foo() {
+ return 121;
+}
+assert.equal(foo(), 121);
diff --git a/test/examples/export-function/importer.js b/test/examples/export-function/importer.js
new file mode 100644
index 0000000..c2bd539
--- /dev/null
+++ b/test/examples/export-function/importer.js
@@ -0,0 +1,4 @@
+/* jshint esnext:true */
+
+import { foo } from './exporter';
+assert.equal(foo(), 121);
diff --git a/test/examples/export-list/exporter.js b/test/examples/export-list/exporter.js
new file mode 100644
index 0000000..d4a462e
--- /dev/null
+++ b/test/examples/export-list/exporter.js
@@ -0,0 +1,11 @@
+/* jshint esnext:true */
+
+var a = 1;
+var b = 2;
+
+function incr() {
+ var c = a++; // Capture `a++` to force us to use a temporary variable.
+ b++;
+}
+
+export { a, b, incr };
diff --git a/test/examples/export-list/importer.js b/test/examples/export-list/importer.js
new file mode 100644
index 0000000..569d434
--- /dev/null
+++ b/test/examples/export-list/importer.js
@@ -0,0 +1,9 @@
+/* jshint esnext:true */
+
+import { a, b, incr } from './exporter';
+
+assert.equal(a, 1);
+assert.equal(b, 2);
+incr();
+assert.equal(a, 2);
+assert.equal(b, 3);
diff --git a/test/examples/export-mixins/exporter.js b/test/examples/export-mixins/exporter.js
new file mode 100644
index 0000000..2621773
--- /dev/null
+++ b/test/examples/export-mixins/exporter.js
@@ -0,0 +1,2 @@
+export default 1;
+export var bar = 2;
diff --git a/test/examples/export-mixins/importer.js b/test/examples/export-mixins/importer.js
new file mode 100644
index 0000000..537592e
--- /dev/null
+++ b/test/examples/export-mixins/importer.js
@@ -0,0 +1,4 @@
+import foo, { bar } from './exporter';
+
+assert.equal(foo, 1);
+assert.equal(bar, 2);
diff --git a/test/examples/export-named-class/exporter.js b/test/examples/export-named-class/exporter.js
new file mode 100644
index 0000000..5ffed58
--- /dev/null
+++ b/test/examples/export-named-class/exporter.js
@@ -0,0 +1,4 @@
+/* jshint esnext:true */
+
+class Foo {}
+export { Foo };
diff --git a/test/examples/export-named-class/importer.js b/test/examples/export-named-class/importer.js
new file mode 100644
index 0000000..d4d1752
--- /dev/null
+++ b/test/examples/export-named-class/importer.js
@@ -0,0 +1,5 @@
+/* jshint esnext:true */
+
+import { Foo } from './exporter';
+
+assert.strictEqual(new Foo().constructor, Foo);
diff --git a/test/examples/export-not-at-top-level-fails/index.js b/test/examples/export-not-at-top-level-fails/index.js
new file mode 100644
index 0000000..a018958
--- /dev/null
+++ b/test/examples/export-not-at-top-level-fails/index.js
@@ -0,0 +1,6 @@
+/* jshint esnext:true */
+
+function foo() {
+ /* error: type=Error message="Line 5: Unexpected reserved word" */
+ export { foo };
+}
diff --git a/test/examples/export-var/exporter.js b/test/examples/export-var/exporter.js
new file mode 100644
index 0000000..e35920b
--- /dev/null
+++ b/test/examples/export-var/exporter.js
@@ -0,0 +1,4 @@
+/* jshint esnext:true */
+
+export var a = 1;
+assert.equal(a, 1);
diff --git a/test/examples/export-var/importer.js b/test/examples/export-var/importer.js
new file mode 100644
index 0000000..e23ff4b
--- /dev/null
+++ b/test/examples/export-var/importer.js
@@ -0,0 +1,4 @@
+/* jshint esnext:true */
+
+import { a } from './exporter';
+assert.equal(a, 1);
diff --git a/test/examples/import-as/exporter.js b/test/examples/import-as/exporter.js
new file mode 100644
index 0000000..6c6f1bd
--- /dev/null
+++ b/test/examples/import-as/exporter.js
@@ -0,0 +1,5 @@
+/* jshint esnext:true */
+
+export var a = 'a';
+export var b = 'b';
+export default 'DEF';
diff --git a/test/examples/import-as/importer.js b/test/examples/import-as/importer.js
new file mode 100644
index 0000000..e9cc6b9
--- /dev/null
+++ b/test/examples/import-as/importer.js
@@ -0,0 +1,7 @@
+/* jshint esnext:true */
+
+import { a as b, b as a, default as def } from './exporter';
+
+assert.equal(b, 'a');
+assert.equal(a, 'b');
+assert.equal(def, 'DEF');
diff --git a/test/examples/import-chain/first.js b/test/examples/import-chain/first.js
new file mode 100644
index 0000000..0cc3795
--- /dev/null
+++ b/test/examples/import-chain/first.js
@@ -0,0 +1,3 @@
+/* jshint esnext:true */
+
+export var value = 42;
diff --git a/test/examples/import-chain/second.js b/test/examples/import-chain/second.js
new file mode 100644
index 0000000..e111ea3
--- /dev/null
+++ b/test/examples/import-chain/second.js
@@ -0,0 +1,4 @@
+/* jshint esnext:true */
+
+import { value } from './first';
+export { value };
diff --git a/test/examples/import-chain/third.js b/test/examples/import-chain/third.js
new file mode 100644
index 0000000..c608ea4
--- /dev/null
+++ b/test/examples/import-chain/third.js
@@ -0,0 +1,4 @@
+/* jshint esnext:true */
+
+import { value } from './second';
+assert.equal(value, 42);
diff --git a/test/examples/import-not-at-top-level-fails/index.js b/test/examples/import-not-at-top-level-fails/index.js
new file mode 100644
index 0000000..3b5a406
--- /dev/null
+++ b/test/examples/import-not-at-top-level-fails/index.js
@@ -0,0 +1,6 @@
+/* jshint esnext:true */
+
+function foo() {
+ /* error: type=Error message="Line 5: Unexpected reserved word" */
+ import foo from './index';
+}
diff --git a/test/examples/module-level-declarations/mod.js b/test/examples/module-level-declarations/mod.js
new file mode 100644
index 0000000..6160767
--- /dev/null
+++ b/test/examples/module-level-declarations/mod.js
@@ -0,0 +1,8 @@
+var a = 1;
+
+assert.equal(a, 1);
+assert.equal(getA(), 1);
+
+function getA() {
+ return a;
+}
\ No newline at end of file
diff --git a/test/examples/named-function-expression/exporter.js b/test/examples/named-function-expression/exporter.js
new file mode 100644
index 0000000..1f3f644
--- /dev/null
+++ b/test/examples/named-function-expression/exporter.js
@@ -0,0 +1 @@
+export var a = 1;
\ No newline at end of file
diff --git a/test/examples/named-function-expression/importer.js b/test/examples/named-function-expression/importer.js
new file mode 100644
index 0000000..2cff8e0
--- /dev/null
+++ b/test/examples/named-function-expression/importer.js
@@ -0,0 +1,9 @@
+import { a } from './exporter';
+
+var getA = function getA() {
+ var a = 2;
+ return a;
+};
+
+assert.strictEqual(a, 1);
+assert.strictEqual(getA(), 2);
\ No newline at end of file
diff --git a/test/examples/namespace-reassign-import-fails/exporter.js b/test/examples/namespace-reassign-import-fails/exporter.js
new file mode 100644
index 0000000..73091db
--- /dev/null
+++ b/test/examples/namespace-reassign-import-fails/exporter.js
@@ -0,0 +1,3 @@
+/* jshint esnext:true */
+
+export var foo = 1;
\ No newline at end of file
diff --git a/test/examples/namespace-reassign-import-fails/importer.js b/test/examples/namespace-reassign-import-fails/importer.js
new file mode 100644
index 0000000..db72f9e
--- /dev/null
+++ b/test/examples/namespace-reassign-import-fails/importer.js
@@ -0,0 +1,6 @@
+/* jshint esnext:true */
+
+import * as exp from './exporter';
+
+/* error: type=SyntaxError message="Cannot reassign imported binding `foo` at importer.js:6:1" */
+exp.foo = 2;
\ No newline at end of file
diff --git a/test/examples/namespace-update-import-fails/exporter.js b/test/examples/namespace-update-import-fails/exporter.js
new file mode 100644
index 0000000..73091db
--- /dev/null
+++ b/test/examples/namespace-update-import-fails/exporter.js
@@ -0,0 +1,3 @@
+/* jshint esnext:true */
+
+export var foo = 1;
\ No newline at end of file
diff --git a/test/examples/namespace-update-import-fails/importer.js b/test/examples/namespace-update-import-fails/importer.js
new file mode 100644
index 0000000..f815834
--- /dev/null
+++ b/test/examples/namespace-update-import-fails/importer.js
@@ -0,0 +1,6 @@
+/* jshint esnext:true */
+
+import * as exp from './exporter';
+
+/* error: type=SyntaxError message="Cannot reassign imported binding of namespace `exp` at importer.js:6:1" */
+exp['foo']++;
\ No newline at end of file
diff --git a/test/examples/namespaces/exporter.js b/test/examples/namespaces/exporter.js
new file mode 100644
index 0000000..6c6f1bd
--- /dev/null
+++ b/test/examples/namespaces/exporter.js
@@ -0,0 +1,5 @@
+/* jshint esnext:true */
+
+export var a = 'a';
+export var b = 'b';
+export default 'DEF';
diff --git a/test/examples/namespaces/importer.js b/test/examples/namespaces/importer.js
new file mode 100644
index 0000000..3d4a3bd
--- /dev/null
+++ b/test/examples/namespaces/importer.js
@@ -0,0 +1,13 @@
+/* jshint esnext:true */
+
+import * as foo from './exporter';
+
+assert.equal(foo['default'], 'DEF');
+assert.equal(foo.b, 'b');
+assert.equal(foo.a, 'a');
+
+var keys = [];
+for (var key in foo) {
+ keys.push(key);
+}
+assert.deepEqual(keys.sort(), ['a', 'b', 'default']);
diff --git a/test/examples/re-export-default-import/first.js b/test/examples/re-export-default-import/first.js
new file mode 100644
index 0000000..81b78df
--- /dev/null
+++ b/test/examples/re-export-default-import/first.js
@@ -0,0 +1,5 @@
+/* jshint esnext:true */
+
+export default function hi() {
+ return 'hi';
+}
diff --git a/test/examples/re-export-default-import/second.js b/test/examples/re-export-default-import/second.js
new file mode 100644
index 0000000..62b7c25
--- /dev/null
+++ b/test/examples/re-export-default-import/second.js
@@ -0,0 +1,3 @@
+/* jshint esnext:true */
+import hi from './first';
+export { hi };
diff --git a/test/examples/re-export-default-import/third.js b/test/examples/re-export-default-import/third.js
new file mode 100644
index 0000000..1e0a5a4
--- /dev/null
+++ b/test/examples/re-export-default-import/third.js
@@ -0,0 +1,4 @@
+/* jshint esnext:true */
+
+import { hi } from './second';
+assert.equal(hi(), 'hi');
diff --git a/test/examples/re-export-namespace-import/first.js b/test/examples/re-export-namespace-import/first.js
new file mode 100644
index 0000000..0b0c330
--- /dev/null
+++ b/test/examples/re-export-namespace-import/first.js
@@ -0,0 +1,2 @@
+export var a = 1;
+export var b = 2;
diff --git a/test/examples/re-export-namespace-import/second.js b/test/examples/re-export-namespace-import/second.js
new file mode 100644
index 0000000..d6c8404
--- /dev/null
+++ b/test/examples/re-export-namespace-import/second.js
@@ -0,0 +1,2 @@
+import * as mod from './first';
+export { mod };
diff --git a/test/examples/re-export-namespace-import/third.js b/test/examples/re-export-namespace-import/third.js
new file mode 100644
index 0000000..10a562f
--- /dev/null
+++ b/test/examples/re-export-namespace-import/third.js
@@ -0,0 +1,4 @@
+import { mod } from './second';
+
+assert.strictEqual(mod.a, 1);
+assert.strictEqual(mod.b, 2);
diff --git a/test/examples/reassign-import-fails/exporter.js b/test/examples/reassign-import-fails/exporter.js
new file mode 100644
index 0000000..2fbf0f7
--- /dev/null
+++ b/test/examples/reassign-import-fails/exporter.js
@@ -0,0 +1,3 @@
+/* jshint esnext:true */
+
+export var x = 1;
\ No newline at end of file
diff --git a/test/examples/reassign-import-fails/importer.js b/test/examples/reassign-import-fails/importer.js
new file mode 100644
index 0000000..35ce0f1
--- /dev/null
+++ b/test/examples/reassign-import-fails/importer.js
@@ -0,0 +1,11 @@
+/* jshint esnext:true */
+
+import { x } from './exporter';
+
+(function() {
+ for(var x = 0; x < 1; x++){}
+ for(var x = 0; x < 1; x++){}
+});
+
+/* error: type=SyntaxError message="Cannot reassign imported binding `x` at importer.js:11:1" */
+x = 10;
diff --git a/test/examples/reassign-import-not-at-top-level-fails/exporter.js b/test/examples/reassign-import-not-at-top-level-fails/exporter.js
new file mode 100644
index 0000000..a56f2ec
--- /dev/null
+++ b/test/examples/reassign-import-not-at-top-level-fails/exporter.js
@@ -0,0 +1,3 @@
+/* jshint esnext:true */
+
+export var x = 1;
diff --git a/test/examples/reassign-import-not-at-top-level-fails/importer.js b/test/examples/reassign-import-not-at-top-level-fails/importer.js
new file mode 100644
index 0000000..8b5bbf9
--- /dev/null
+++ b/test/examples/reassign-import-not-at-top-level-fails/importer.js
@@ -0,0 +1,11 @@
+/* jshint esnext:true */
+
+import { x } from './exporter';
+
+export function foo () {
+ var x = 1;
+}
+export function bar () {
+ /* error: type=SyntaxError message="Cannot reassign imported binding `x` at importer.js:10:3" */
+ x = 1;
+}
diff --git a/test/examples/this-is-global/mod.js b/test/examples/this-is-global/mod.js
new file mode 100644
index 0000000..c6f4da9
--- /dev/null
+++ b/test/examples/this-is-global/mod.js
@@ -0,0 +1,8 @@
+/* jshint esnext:true */
+
+assert.strictEqual(
+ this,
+ global,
+ '`this` (keys=' + Object.keys(this) + ') does not equal ' +
+ '`global` (keys=' + Object.keys(global) + ')'
+);
\ No newline at end of file
diff --git a/test/examples/update-expression-of-import-fails/exporter.js b/test/examples/update-expression-of-import-fails/exporter.js
new file mode 100644
index 0000000..e0f2ffc
--- /dev/null
+++ b/test/examples/update-expression-of-import-fails/exporter.js
@@ -0,0 +1,3 @@
+/* jshint esnext:true */
+
+export var a = 0;
\ No newline at end of file
diff --git a/test/examples/update-expression-of-import-fails/importer.js b/test/examples/update-expression-of-import-fails/importer.js
new file mode 100644
index 0000000..cb81604
--- /dev/null
+++ b/test/examples/update-expression-of-import-fails/importer.js
@@ -0,0 +1,6 @@
+/* jshint esnext:true */
+
+import { a } from './exporter';
+
+/* error: type=SyntaxError message="Cannot reassign imported binding `a` at importer.js:6:1" */
+a++;
\ No newline at end of file
diff --git a/test/runner.js b/test/runner.js
new file mode 100644
index 0000000..d5fcf3d
--- /dev/null
+++ b/test/runner.js
@@ -0,0 +1,250 @@
+/* jshint node:true, undef:true, unused:true */
+
+Error.stackTraceLimit = 50;
+
+var fs = require('fs');
+var Path = require('path');
+var vm = require('vm');
+var assert = require('assert');
+var es6class = require('es6-class');
+
+var modules = require('../lib');
+var utils = require('../lib/utils');
+var endsWith = utils.endsWith;
+var ExpectedError = require('./support/expected_error');
+
+var examples = Path.join(__dirname, 'examples');
+
+var paths = [];
+var formatters = require('../lib/formatters');
+var formatterNames = Object.keys(formatters).filter(function(formatter) {
+ return formatter !== 'DEFAULT';
+});
+var formatter = formatters.DEFAULT;
+
+var getopt = require('posix-getopt');
+var parser = new getopt.BasicParser('h(help)f:(format)', process.argv);
+var option;
+
+while ((option = parser.getopt()) !== undefined) {
+ if (option.error) {
+ usage();
+ process.exit(1);
+ }
+
+ switch (option.option) {
+ case 'f':
+ formatter = option.optarg;
+ if (formatterNames.indexOf(formatter) < 0) {
+ usage();
+ process.exit(1);
+ }
+ break;
+
+ case 'h':
+ usage();
+ process.exit(0);
+ break;
+ }
+}
+
+paths.push.apply(paths, process.argv.slice(parser.optind()));
+
+if (paths.length === 0) {
+ paths = fs.readdirSync(examples).map(function(example) {
+ return Path.join(examples, example);
+ });
+} else {
+ var cwd = process.cwd();
+ paths = paths.map(function(example) {
+ return Path.resolve(cwd, example);
+ });
+}
+
+var results = Path.join(__dirname, 'results');
+if (fs.existsSync(results)) {
+ rmrf(results);
+}
+fs.mkdirSync(results);
+runTests(paths);
+
+function runTests(paths) {
+ var passed = 0, failed = 0;
+ paths.forEach(function(path) {
+ if (runTestDir(path)) {
+ passed++;
+ } else {
+ failed++;
+ }
+ });
+
+ console.log();
+ console.log('%d passed, %s failed.', passed, failed);
+ process.exit(
+ (passed + failed === 0) ? 1 : // no tests, fail
+ failed === 0 ? 0 : // no failed, pass
+ 1); // some failed, fail
+}
+
+function runTestDir(testDir) {
+ var passed = false;
+ var testName = Path.basename(testDir);
+
+ var options = {
+ resolvers: [new modules.FileResolver([testDir])],
+ formatter: formatters[formatter]
+ };
+ var container = new modules.Container(options);
+
+ var expectedError;
+ try {
+ fs.readdirSync(testDir).forEach(function(child) {
+ var mod = container.getModule(child);
+ var contents = fs.readFileSync(mod.path).toString();
+ var newExpectedError = ExpectedError.getFromSource(contents);
+
+ assert.ok(
+ !newExpectedError || !expectedError,
+ 'found more than one error comment!'
+ );
+
+ expectedError = newExpectedError;
+ });
+
+ var resultPath = Path.join(results, testName + '.js');
+ container.write(resultPath);
+
+ var testAssert = wrappedAssert();
+ if (fs.statSync(resultPath).isDirectory()) {
+ fs.readdirSync(resultPath).forEach(function(child) {
+ if (Path.extname(child) === '.js') {
+ requireTestFile('./' + child, resultPath, testAssert);
+ }
+ });
+ } else {
+ requireTestFile(resultPath, process.cwd(), testAssert);
+ }
+
+ assert.ok(
+ expectedError || testAssert.count > 0,
+ 'expected at least one assertion'
+ );
+
+ if (expectedError) {
+ expectedError.assertMatch(null);
+ }
+
+ passed = true;
+ printSuccess(testName);
+ } catch (ex) {
+ if (!(ex instanceof assert.AssertionError) && expectedError) {
+ ex = expectedError.matchError(ex);
+ }
+
+ if (ex) {
+ printFailure(testName, ex);
+ console.log();
+ } else {
+ printSuccess(testName);
+ passed = true;
+ }
+ }
+
+ return passed;
+}
+
+// TODO: Just use the real node require system with proxyquire?
+var testFileCache;
+var testFileGlobal;
+function requireTestFile(path, relativeTo, assert) {
+ if (path[0] === '.') {
+ path = Path.resolve(relativeTo, path);
+ }
+
+ if (!testFileCache) { testFileCache = {}; }
+
+ if (path in testFileCache) {
+ return testFileCache[path];
+ } else if (!fs.existsSync(path) && !endsWith(path, '.js')) {
+ return requireTestFile(path + '.js');
+ }
+
+ var code = fs.readFileSync(path);
+ var mod = {exports: {}};
+ testFileCache[path] = mod.exports;
+
+ if (!testFileGlobal) { testFileGlobal = {}; }
+
+ testFileGlobal.assert = assert;
+ testFileGlobal.global = testFileGlobal;
+ testFileGlobal.module = mod;
+ testFileGlobal.exports = mod.exports;
+ testFileGlobal.require = function(requiredPath) {
+ return requireTestFile(requiredPath, Path.dirname(path), assert);
+ };
+
+ // Hack to work around an issue where vm does not set `this` to the context.
+ code = '(function(){' + es6class.compile(code).code + '\n}).call(global);';
+ vm.runInNewContext(code, testFileGlobal, path);
+
+ testFileCache[path] = mod.exports;
+ return mod.exports;
+}
+
+function wrappedAssert() {
+ var result = {count: 0};
+
+ Object.getOwnPropertyNames(assert).forEach(function(property) {
+ result[property] = function() {
+ result.count++;
+ return assert[property].apply(assert, arguments);
+ };
+ });
+
+ return result;
+}
+
+function rmrf(path) {
+ var stat = fs.statSync(path);
+ if (stat.isDirectory()) {
+ fs.readdirSync(path).forEach(function(child) {
+ rmrf(Path.join(path, child));
+ });
+ fs.rmdirSync(path);
+ } else if (stat.isFile()) {
+ fs.unlinkSync(path);
+ }
+}
+
+/**
+ * Prints a line to stdout for the given test indicating that it passed.
+ *
+ * @param {string} testName
+ */
+function printSuccess(testName) {
+ console.log('\x1b[32m✓ \x1b[0m' + testName);
+}
+
+/**
+ * Prints a line to stdout for the given test indicating that it failed. In
+ * addition, prints any additional information indented one level.
+ *
+ * @param {string} testName
+ * @param {Error} error
+ */
+function printFailure(testName, error) {
+ console.log('\x1b[31m✘ ' + testName + '\x1b[0m');
+ console.log();
+ if (error.stack) {
+ console.log(error.stack);
+ } else {
+ console.log(error.message);
+ }
+}
+
+function usage() {
+ console.log('node test/runner.js [OPTIONS] [EXAMPLE1 [EXAMPLE2 ...]]');
+ console.log();
+ console.log(' -f, --format <name> Choose from: %s.', formatterNames.join(', '));
+ console.log(' -h, --help Show this help message.');
+}
diff --git a/test/support/expected_error.js b/test/support/expected_error.js
new file mode 100644
index 0000000..9a1b596
--- /dev/null
+++ b/test/support/expected_error.js
@@ -0,0 +1,116 @@
+/* jshint node:true, undef:true, unused:true */
+
+var assert = require('assert');
+
+/**
+ * @param {string|Error=} type
+ * @param {string=} message
+ * @constructor
+ */
+function ExpectedError(type, message) {
+ this.type = type;
+ this.message = message;
+}
+
+/**
+ * Builds an ExpectedError from the given source code.
+ *
+ * @param {string} source
+ * @return {?ExpectedError}
+ */
+ExpectedError.getFromSource = function(source) {
+ var errorMatch = source.match(/\/\*\s*error:\s*(.+?)\*\//);
+ if (!errorMatch) {
+ return null;
+ }
+
+ var errorInfo = errorMatch[1];
+ var expectedTypeMatch = errorInfo.match(/type=([a-zA-Z]+)/);
+ var expectedMessageMatch = errorInfo.match(/message="([^"]+)"/);
+
+ assert.ok(
+ expectedTypeMatch || expectedMessageMatch,
+ 'expected error comment contains neither a type or a message: ' +
+ errorInfo
+ );
+
+ return new ExpectedError(
+ expectedTypeMatch && expectedTypeMatch[1],
+ expectedMessageMatch && expectedMessageMatch[1]
+ );
+};
+
+/**
+ * Determines whether the given error matches the expected error type.
+ *
+ * @param {!Error} error
+ * @return {boolean}
+ */
+ExpectedError.prototype.matchesType = function(error) {
+ return !this.type ||
+ (typeof this.type === 'function' && error instanceof this.type) ||
+ (this.type === error.constructor) ||
+ (this.type === error.constructor.name);
+};
+
+/**
+ * Determines whether the given error matches the expected error message.
+ *
+ * @param {!Error} error
+ * @return {boolean}
+ */
+ExpectedError.prototype.matchesMessage = function(error) {
+ return !this.message ||
+ (this.message === error.message) ||
+ (this.message.test && this.message.test(error.message));
+};
+
+/**
+ * Asserts that the given error matches the expected error info.
+ *
+ * @param {?Error} error
+ */
+ExpectedError.prototype.assertMatch = function(error) {
+ var matchError = this.matchError(error);
+ if (matchError) {
+ throw matchError;
+ }
+};
+
+/**
+ * Gets the error to throw if the given error does not match.
+ *
+ * @param {?Error} error
+ * @return {?AssertionError}
+ */
+ExpectedError.prototype.matchError = function(error) {
+ var matchesType = error && this.matchesType(error);
+ var matchesMessage = error && this.matchesMessage(error);
+
+ if (matchesType && matchesMessage) {
+ return null;
+ }
+
+ var assertMessage = 'expected error';
+
+ if (!matchesType && this.type) {
+ assertMessage += ' type to equal ' + this.type;
+ if (!matchesMessage && this.message) {
+ assertMessage += ' and';
+ }
+ }
+ if (!matchesMessage && this.message) {
+ assertMessage += ' message to match ' +
+ (typeof this.message === 'string' ? '"' + this.message + '"' : this.message);
+ }
+
+ if (error) {
+ assertMessage += ', but got ' + error;
+ } else {
+ assertMessage += ', but no error was thrown'
+ }
+
+ return new assert.AssertionError({ message: assertMessage });
+};
+
+module.exports = ExpectedError;
diff --git a/test/support/test_formatter.js b/test/support/test_formatter.js
new file mode 100644
index 0000000..e5837b9
--- /dev/null
+++ b/test/support/test_formatter.js
@@ -0,0 +1,72 @@
+var Formatter = require('../../lib/formatters/formatter');
+var extend = require('../../lib/utils').extend;
+
+/**
+ * This basic formatter does not alter the AST at all, and only exists
+ * help write unit tests for things that depend on formatters.
+ *
+ * @class
+ * @extends Formatter
+ */
+function TestFormatter() {
+ Formatter.call(this);
+ this.processedExportDeclarationCount = 0;
+ this.processedExportReassignmentCount = 0;
+ this.processedImportDeclarationCount = 0;
+ this.processedFunctionDeclarationCount = 0;
+ this.processedVariableDeclarationCount = 0;
+}
+extend(TestFormatter, Formatter);
+
+/**
+ * @override
+ */
+TestFormatter.prototype.build = function(modules) {
+ return modules.map(function(mod) {
+ var ast = mod.ast;
+ ast.filename = mod.relativePath;
+ return ast;
+ });
+};
+
+/**
+ * @override
+ */
+TestFormatter.prototype.processExportDeclaration = function() {
+ this.processedExportDeclarationCount++;
+ return null;
+};
+
+/**
+ * @override
+ */
+TestFormatter.prototype.processExportReassignment = function() {
+ this.processedExportReassignmentCount++;
+ return null;
+};
+
+/**
+ * @override
+ */
+TestFormatter.prototype.processImportDeclaration = function() {
+ this.processedImportDeclarationCount++;
+ return null;
+};
+
+/**
+ * @override
+ */
+TestFormatter.prototype.processFunctionDeclaration = function() {
+ this.processedFunctionDeclarationCount++;
+ return null;
+};
+
+/**
+ * @override
+ */
+TestFormatter.prototype.processVariableDeclaration = function() {
+ this.processedVariableDeclarationCount++;
+ return null;
+};
+
+exports.TestFormatter = TestFormatter;
diff --git a/test/support/test_resolver.js b/test/support/test_resolver.js
new file mode 100644
index 0000000..f6112e5
--- /dev/null
+++ b/test/support/test_resolver.js
@@ -0,0 +1,36 @@
+var Module = require('../../lib/module');
+var Path = require('path');
+
+/**
+ * This basic resolver just returns a module whose #src is set to an
+ * empty string to prevent an attempt to read from the file system.
+ *
+ * @class
+ * @param {Object.<string,string>=} sources
+ */
+function TestResolver(sources) {
+ this.sources = sources || {};
+}
+
+/**
+ * @param {string} path
+ * @param {Module} mod
+ * @param {Container} container
+ * @returns {Module}
+ */
+TestResolver.prototype.resolveModule = function(path, mod, container) {
+ if (mod) {
+ path = Path.normalize(Path.join(mod.relativePath, '..', path));
+ }
+
+ var cachedModule = container.getCachedModule(path);
+ if (cachedModule) {
+ return cachedModule;
+ }
+
+ var resolved = new Module(path, path, container);
+ resolved.src = this.sources[path] || '';
+ return resolved;
+};
+
+exports.TestResolver = TestResolver;
diff --git a/test/unit/container_test.js b/test/unit/container_test.js
new file mode 100644
index 0000000..d0f0a1a
--- /dev/null
+++ b/test/unit/container_test.js
@@ -0,0 +1,120 @@
+var Container = require('../../lib/container');
+var Module = require('../../lib/module');
+var Path = require('path');
+var TestFormatter = require('../support/test_formatter').TestFormatter;
+var TestResolver = require('../support/test_resolver').TestResolver;
+var assert = require('assert');
+var fs = require('fs');
+var tmp = require('tmp');
+
+describe('Container', function() {
+ describe('#write', function() {
+ it('allows multiple calls but only converts once', function(done) {
+ var buildCallCount = 0;
+ var source = 'var a = 1;';
+ var formatter = new TestFormatter();
+
+ /**
+ * This formatter only exists to count the number of times #build is
+ * called and create a result of the right data structure.
+ *
+ * @param {Module[]} modules
+ * @returns {File[]}
+ */
+ formatter.build = function(modules) {
+ buildCallCount++;
+ return TestFormatter.prototype.build.call(this, modules);
+ };
+
+ var container = new Container({
+ formatter: formatter,
+ resolvers: [new TestResolver({
+ 'a.js': source
+ })]
+ });
+
+ // Ensure we have a module to write at all.
+ container.getModule('a.js');
+
+ tmp.dir(function(err, path) {
+ if (err) { return done(err); }
+
+ // Write the contents to a temporary directory.
+ container.write(path);
+ assert.strictEqual(buildCallCount, 1);
+
+ // Ensure that the written file contains the original code.
+ var a1 = fs.readFileSync(Path.join(path, 'a.js'), 'utf8');
+ assert.ok(
+ a1.indexOf(source) === 0,
+ 'expected written source to start with original source, but was: ' + a1
+ );
+
+ tmp.dir(function(err, path2) {
+ if (err) { return done(err); }
+ assert.notStrictEqual(path, path2);
+
+ // Write to yet another temporary directory with the same container.
+ container.write(path2);
+ assert.strictEqual(buildCallCount, 1);
+
+ // Ensure that the written file contains the original code.
+ var a2 = fs.readFileSync(Path.join(path2, 'a.js'), 'utf8');
+ assert.ok(
+ a2.indexOf(source) === 0,
+ 'expected written source to start with original source, but was: ' + a2
+ );
+
+ done();
+ });
+ });
+ });
+
+ it('freezes the container, effectively preventing adding new modules', function(done) {
+ var container = new Container({
+ formatter: new TestFormatter(),
+ resolvers: [new TestResolver()]
+ });
+
+ container.getModule('a.js');
+
+ tmp.dir(function(err, path) {
+ if (err) { return done(err); }
+
+ container.write(path);
+
+ try {
+ container.getModule('b.js');
+ assert.fail('expected an exception');
+ } catch (ex) {
+ assert.strictEqual(
+ 'container has already converted contained modules and cannot add new module: b.js',
+ ex.message
+ );
+ }
+
+ done();
+ });
+ });
+ });
+
+ describe('#transform', function() {
+ var formatter = new TestFormatter();
+ var source = 'export var a = 1;';
+ var container = new Container({
+ formatter: formatter,
+ resolvers: [new TestResolver({
+ 'a.js': source
+ })]
+ });
+
+ // Ensure we have a module to write at all.
+ container.getModule('a.js');
+
+ var files = container.transform();
+ assert.strictEqual(files.length, 1);
+ assert.strictEqual(files[0].filename, 'a.js');
+ assert.strictEqual(files[0].code, source);
+ assert.strictEqual(typeof files[0].map, 'object');
+ });
+});
diff --git a/test/unit/mkdirp_test.js b/test/unit/mkdirp_test.js
new file mode 100644
index 0000000..9d989ee
--- /dev/null
+++ b/test/unit/mkdirp_test.js
@@ -0,0 +1,56 @@
+var assert = require('assert');
+var mkdirpSync = require('../../lib/utils').mkdirpSync;
+
+describe('mkdirpSync', function() {
+ var calls;
+ var fs = {
+ existsSync: function(path) {
+ calls.push(['existsSync', path]);
+ return path === '/' || path === '/path' || path === 'path';
+ },
+
+ mkdirSync: function(path) {
+ calls.push(['mkdirSync', path]);
+ }
+ };
+
+ beforeEach(function() {
+ calls = [];
+ });
+
+ context('given absolute paths', function() {
+ it('checks each path component, making the ones that do not exist', function() {
+ mkdirpSync('/path/to/some/dir', { fs: fs });
+ assert.deepEqual(
+ calls,
+ [
+ ['existsSync', '/path'],
+ ['existsSync', '/path/to'],
+ ['mkdirSync', '/path/to'],
+ ['existsSync', '/path/to/some'],
+ ['mkdirSync', '/path/to/some'],
+ ['existsSync', '/path/to/some/dir'],
+ ['mkdirSync', '/path/to/some/dir']
+ ]
+ );
+ });
+ });
+
+ context('given relative paths', function() {
+ it('checks each path component, making the ones that do not exist', function() {
+ mkdirpSync('path/to/some/dir', { fs: fs });
+ assert.deepEqual(
+ calls,
+ [
+ ['existsSync', 'path'],
+ ['existsSync', 'path/to'],
+ ['mkdirSync', 'path/to'],
+ ['existsSync', 'path/to/some'],
+ ['mkdirSync', 'path/to/some'],
+ ['existsSync', 'path/to/some/dir'],
+ ['mkdirSync', 'path/to/some/dir']
+ ]
+ );
+ });
+ });
+});
\ No newline at end of file
diff --git a/test/unit/module_binding_specifier_test.js b/test/unit/module_binding_specifier_test.js
new file mode 100644
index 0000000..d2134ea
--- /dev/null
+++ b/test/unit/module_binding_specifier_test.js
@@ -0,0 +1,110 @@
+var assert = require('assert');
+var Container = require('../../lib/container');
+var Module = require('../../lib/module');
+var TestFormatter = require('../support/test_formatter').TestFormatter;
+var TestResolver = require('../support/test_resolver').TestResolver;
+
+describe('ModuleBindingSpecifier', function() {
+ describe('#terminalExportSpecifier', function() {
+ var sources;
+ var container;
+
+ beforeEach(function() {
+ container = new Container({
+ formatter: new TestFormatter(),
+ resolvers: [new TestResolver(sources)]
+ });
+ });
+
+ function getExportSpecifier(modulePath, exportedName) {
+ var mod = container.getModule(modulePath);
+ var specifier = mod.exports.findSpecifierByName(exportedName);
+ if (!specifier) {
+ throw new Error('unable to find export `' + exportedName + '` in module: ' + modulePath);
+ }
+ return specifier;
+ }
+
+ context('when the export is a variable declaration', function() {
+ before(function() {
+ sources = { 'index.js': 'export var a = 1;' };
+ });
+
+ it('is the export itself', function() {
+ var specifier = getExportSpecifier('index.js', 'a');
+ assert.strictEqual(specifier.terminalExportSpecifier, specifier);
+ });
+ });
+
+ context('when the export is a function declaration', function() {
+ before(function() {
+ sources = { 'index.js': 'export function a() {}' };
+ });
+
+ it('is the export itself', function() {
+ var specifier = getExportSpecifier('index.js', 'a');
+ assert.strictEqual(specifier.terminalExportSpecifier, specifier);
+ });
+ });
+
+ context('when the export binds an import by name from another module', function() {
+ before(function() {
+ sources = {
+ 'index.js': 'import { a } from "middle.js";\nexport { a };',
+ 'middle.js': 'import { a } from "root.js";\nexport { a };',
+ 'root.js': 'export var a = 1;'
+ };
+ });
+
+ it('follows the trail of imports until it finds the original', function() {
+ var rootA = getExportSpecifier('root.js', 'a');
+ var specifier = getExportSpecifier('index.js', 'a');
+ assert.strictEqual(
+ specifier.terminalExportSpecifier,
+ rootA,
+ 'expected ' + specifier.terminalExportSpecifier + ' to equal ' + rootA
+ );
+ });
+ });
+
+ context('when the export directly re-exports a binding by name from another module', function() {
+ before(function() {
+ sources = {
+ 'index.js': 'import { a } from "middle.js";\nexport { a };',
+ 'middle.js': 'export { a } from "root.js";',
+ 'root.js': 'export var a = 1;'
+ };
+ });
+
+ it('follows the trail of imports until it finds the original', function() {
+ var rootA = getExportSpecifier('root.js', 'a');
+ var specifier = getExportSpecifier('index.js', 'a');
+ assert.strictEqual(
+ specifier.terminalExportSpecifier,
+ rootA,
+ 'expected ' + specifier.terminalExportSpecifier + ' to equal ' + rootA
+ );
+ });
+ });
+
+ xcontext('when the export binds an import through a batch export', function() {
+ before(function() {
+ sources = {
+ 'index.js': 'import { a } from "middle.js";\nexport { a };',
+ 'middle.js': 'export * from "root.js";',
+ 'root.js': 'export var a = 1;'
+ };
+ });
+
+ it('follows the trail of imports until it finds the original', function() {
+ var rootA = getExportSpecifier('root.js', 'a');
+ var specifier = getExportSpecifier('index.js', 'a');
+ assert.strictEqual(
+ getExportSpecifier('index.js', 'a').terminalExportSpecifier,
+ rootA,
+ 'expected ' + specifier.terminalExportSpecifier + ' to equal ' + rootA
+ );
+ });
+ });
+ });
+});
\ No newline at end of file
diff --git a/test/unit/rewriter_test.js b/test/unit/rewriter_test.js
new file mode 100644
index 0000000..0d99548
--- /dev/null
+++ b/test/unit/rewriter_test.js
@@ -0,0 +1,30 @@
+var Container = require('../../lib/container');
+var Module = require('../../lib/module');
+var Rewriter = require('../../lib/rewriter');
+var TestFormatter = require('../support/test_formatter').TestFormatter;
+var TestResolver = require('../support/test_resolver').TestResolver;
+var assert = require('assert');
+
+describe('Rewriter', function() {
+ describe('#rewrite', function() {
+ context('when a module has no imports or exports', function() {
+ it('does not traverse the module at all', function() {
+ var formatter = new TestFormatter();
+ var container = new Container({
+ formatter: formatter,
+ resolvers: [new TestResolver({
+ 'a.js': 'var foo = 1\nfunction bar() {}'
+ })]
+ });
+
+ var a = container.getModule('a.js');
+ var rewriter = new Rewriter(formatter);
+ rewriter.rewrite(container.getModules());
+
+ assert.strictEqual(formatter.processedExportDeclarationCount, 0);
+ assert.strictEqual(formatter.processedFunctionDeclarationCount, 0);
+ assert.strictEqual(formatter.processedVariableDeclarationCount, 0);
+ });
+ });
+ });
+});
\ No newline at end of file
diff --git a/test/unit/sorting_test.js b/test/unit/sorting_test.js
new file mode 100644
index 0000000..3a1e034
--- /dev/null
+++ b/test/unit/sorting_test.js
@@ -0,0 +1,77 @@
+var sort = require('../../lib/sorting').sort;
+var assert = require('assert');
+var util = require('util');
+
+function assertArraysEqual(a, b) {
+ var message = 'expected ' + util.inspect(a) + ' to equal ' + util.inspect(b);
+ assert.ok(a.length === b.length, message);
+
+ for (var i = 0, length = a.length; i < length; i++) {
+ assert.ok(a[i] === b[i], message);
+ }
+}
+
+describe('sort', function() {
+ it('returns an empty list given an empty list', function() {
+ assertArraysEqual(sort([]), []);
+ });
+
+ it('returns a list of a single module given a single module', function() {
+ var mod = { id: 'testmod', imports: { modules: [] } };
+ assertArraysEqual(sort([mod]), [mod]);
+ });
+
+ it('properly orders a linear set of modules', function() {
+ var a = { id: 'a', imports: { modules: [] } };
+ var b = { id: 'b', imports: { modules: [a] } };
+ var c = { id: 'c', imports: { modules: [b] } };
+
+ assertArraysEqual(sort([b, a, c]), [a, b, c]);
+ });
+
+ it('properly orders a tree of modules', function() {
+ var b = { id: 'b', imports: { modules: [] } };
+ var c = { id: 'c', imports: { modules: [] } };
+ var a = { id: 'a', imports: { modules: [b, c] } };
+
+ assertArraysEqual(sort([b, a, c]), [b, c, a]);
+ assertArraysEqual(sort([c, a, b]), [c, b, a]);
+ });
+
+ it('properly orders a DAG of modules', function() {
+ var a = { id: 'a', imports: { modules: [] } };
+ var b = { id: 'b', imports: { modules: [] } };
+ var c = { id: 'c', imports: { modules: [] } };
+
+ a.imports.modules.push(b, c);
+ b.imports.modules.push(c);
+
+ assertArraysEqual(sort([a, b, c]), [c, b, a]);
+ assertArraysEqual(sort([b, a, c]), [c, b, a]);
+ });
+
+ it('orders a simple cyclic graph by last-required', function() {
+ var a = { id: 'a', imports: { modules: [] } };
+ var b = { id: 'b', imports: { modules: [] } };
+
+ a.imports.modules.push(b);
+ b.imports.modules.push(a);
+
+ assertArraysEqual(sort([a, b]), [b, a]);
+ assertArraysEqual(sort([b, a]), [a, b]);
+ });
+
+ it('orders a complex cyclic graph by last-required', function() {
+ var a = { id: 'a', imports: { modules: [] } };
+ var b = { id: 'b', imports: { modules: [a] } };
+ var c = { id: 'c', imports: { modules: [b] } };
+ var d = { id: 'd', imports: { modules: [c] } };
+
+ a.imports.modules.push(d);
+
+ assertArraysEqual(sort([b, c, d, a]), [c, d, a, b]);
+ assertArraysEqual(sort([b, c, a, d]), [c, d, a, b]);
+ assertArraysEqual(sort([c, d, a, b]), [d, a, b, c]);
+ assertArraysEqual(sort([c, b, a, d]), [d, a, b, c]);
+ });
+});
\ No newline at end of file
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-javascript/node-es6-module-transpiler.git
More information about the Pkg-javascript-commits
mailing list