1
0
Fork 0
mirror of https://github.com/jashkenas/coffeescript.git synced 2022-11-09 12:23:24 -05:00
jashkenas--coffeescript/test/modules.coffee

688 lines
15 KiB
CoffeeScript
Raw Normal View History

Support import and export of ES2015 modules (#4300) This pull request adds support for ES2015 modules, by recognizing `import` and `export` statements. The following syntaxes are supported, based on the MDN [import](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import) and [export](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/export) pages: ```js import "module-name" import defaultMember from "module-name" import * as name from "module-name" import { } from "module-name" import { member } from "module-name" import { member as alias } from "module-name" import { member1, member2 as alias2, … } from "module-name" import defaultMember, * as name from "module-name" import defaultMember, { … } from "module-name" export default expression export class name export { } export { name } export { name as exportedName } export { name as default } export { name1, name2 as exportedName2, name3 as default, … } export * from "module-name" export { … } from "module-name" ``` As a subsitute for ECMAScript’s `export var name = …` and `export function name {}`, CoffeeScript also supports: ```js export name = … ``` CoffeeScript also supports optional commas within `{ … }`. This PR converts the supported `import` and `export` statements into ES2015 `import` and `export` statements; it **does not resolve the modules**. So any CoffeeScript with `import` or `export` statements will be output as ES2015, and will need to be transpiled by another tool such as Babel before it can be used in a browser. We will need to add a warning to the documentation explaining this. This should be fully backwards-compatible, as `import` and `export` were previously reserved keywords. No flags are used. There are extensive tests included, though because no current JavaScript runtime supports `import` or `export`, the tests compare strings of what the compiled CoffeeScript output is against what the expected ES2015 should be. I also conducted two more elaborate tests: * I forked the [ember-piqu](https://github.com/pauc/piqu-ember) project, which was an Ember CLI app that used ember-cli-coffeescript and [ember-cli-coffees6](https://github.com/alexspeller/ember-cli-coffees6) (which adds “support” for `import`/`export` by wrapping such statements in backticks before passing the result to the CoffeeScript compiler). I removed `ember-cli-coffees6` and replaced the CoffeeScript compiler used in the build chain with this code, and the app built without errors. [Demo here.](https://github.com/GeoffreyBooth/coffeescript-modules-test-piqu) * I also forked the [CoffeeScript version of Meteor’s Todos example app](https://github.com/meteor/todos/tree/coffeescript), and replaced all of its `require` statements with the `import` and `export` statements from the original ES2015 version of the app on its `master` branch. I then updated the `coffeescript` Meteor package in the app to use this new code, and again the app builds without errors. [Demo here.](https://github.com/GeoffreyBooth/coffeescript-modules-test-meteor-todos) The discussion history for this work started [here](https://github.com/jashkenas/coffeescript/pull/4160) and continued [here](https://github.com/GeoffreyBooth/coffeescript/pull/2). @lydell provided guidance, and @JimPanic and @rattrayalex contributed essential code.
2016-09-14 14:46:05 -04:00
# Modules, a.k.a. ES2015 import/export
# ------------------------------------
#
# Remember, were not *resolving* modules, just outputting valid ES2015 syntax.
# This is the CoffeeScript import and export syntax, closely modeled after the ES2015 syntax
# https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import
# https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/export
# import "module-name"
# import defaultMember from "module-name"
# import * as name from "module-name"
# import { } from "module-name"
# import { member } from "module-name"
# import { member as alias } from "module-name"
# import { member1, member2 as alias2, … } from "module-name"
# import defaultMember, * as name from "module-name"
# import defaultMember, { … } from "module-name"
# export default expression
# export class name
# export { }
# export { name }
# export { name as exportedName }
# export { name as default }
# export { name1, name2 as exportedName2, name3 as default, … }
#
# export * from "module-name"
# export { … } from "module-name"
#
# As a subsitute for `export var name = …` and `export function name {}`,
# CoffeeScript also supports:
# export name = …
# CoffeeScript also supports optional commas within `{ … }`.
# Helper function
toJS = (str) ->
CoffeeScript.compile str, bare: yes
.replace /^\s+|\s+$/g, '' # Trim leading/trailing whitespace
# Import statements
test "backticked import statement", ->
input = """
if Meteor.isServer
`import { foo, bar as baz } from 'lib'`"""
output = """
if (Meteor.isServer) {
import { foo, bar as baz } from 'lib';
}"""
eq toJS(input), output
test "import an entire module for side effects only, without importing any bindings", ->
input = "import 'lib'"
output = "import 'lib';"
eq toJS(input), output
test "import default member from module, adding the member to the current scope", ->
input = """
import foo from 'lib'
foo.fooMethod()"""
output = """
import foo from 'lib';
foo.fooMethod();"""
eq toJS(input), output
test "import an entire module's contents as an alias, adding the alias to the current scope", ->
input = """
import * as foo from 'lib'
foo.fooMethod()"""
output = """
import * as foo from 'lib';
foo.fooMethod();"""
eq toJS(input), output
test "import empty object", ->
input = "import { } from 'lib'"
output = "import {} from 'lib';"
eq toJS(input), output
test "import empty object", ->
input = "import {} from 'lib'"
output = "import {} from 'lib';"
eq toJS(input), output
test "import a single member of a module, adding the member to the current scope", ->
input = """
import { foo } from 'lib'
foo.fooMethod()"""
output = """
import {
foo
} from 'lib';
foo.fooMethod();"""
eq toJS(input), output
test "import a single member of a module as an alias, adding the alias to the current scope", ->
input = """
import { foo as bar } from 'lib'
bar.barMethod()"""
output = """
import {
foo as bar
} from 'lib';
bar.barMethod();"""
eq toJS(input), output
test "import multiple members of a module, adding the members to the current scope", ->
input = """
import { foo, bar } from 'lib'
foo.fooMethod()
bar.barMethod()"""
output = """
import {
foo,
bar
} from 'lib';
foo.fooMethod();
bar.barMethod();"""
eq toJS(input), output
test "import multiple members of a module where some are aliased, adding the members or aliases to the current scope", ->
input = """
import { foo, bar as baz } from 'lib'
foo.fooMethod()
baz.bazMethod()"""
output = """
import {
foo,
bar as baz
} from 'lib';
foo.fooMethod();
baz.bazMethod();"""
eq toJS(input), output
test "import default member and other members of a module, adding the members to the current scope", ->
input = """
import foo, { bar, baz as qux } from 'lib'
foo.fooMethod()
bar.barMethod()
qux.quxMethod()"""
output = """
import foo, {
bar,
baz as qux
} from 'lib';
foo.fooMethod();
bar.barMethod();
qux.quxMethod();"""
eq toJS(input), output
test "import default member from a module as well as the entire module's contents as an alias, adding the member and alias to the current scope", ->
input = """
import foo, * as bar from 'lib'
foo.fooMethod()
bar.barMethod()"""
output = """
import foo, * as bar from 'lib';
foo.fooMethod();
bar.barMethod();"""
eq toJS(input), output
test "multiline simple import", ->
input = """
import {
foo,
bar as baz
} from 'lib'"""
output = """
import {
foo,
bar as baz
} from 'lib';"""
eq toJS(input), output
test "multiline complex import", ->
input = """
import foo, {
bar,
baz as qux
} from 'lib'"""
output = """
import foo, {
bar,
baz as qux
} from 'lib';"""
eq toJS(input), output
test "import with optional commas", ->
input = "import { foo, bar, } from 'lib'"
output = """
import {
foo,
bar
} from 'lib';"""
eq toJS(input), output
test "multiline import without commas", ->
input = """
import {
foo
bar
} from 'lib'"""
output = """
import {
foo,
bar
} from 'lib';"""
eq toJS(input), output
test "multiline import with optional commas", ->
input = """
import {
foo,
bar,
} from 'lib'"""
output = """
import {
foo,
bar
} from 'lib';"""
eq toJS(input), output
test "a variable can be assigned after an import", ->
input = """
import { foo } from 'lib'
bar = 5"""
output = """
var bar;
import {
foo
} from 'lib';
bar = 5;"""
eq toJS(input), output
test "variables can be assigned before and after an import", ->
input = """
foo = 5
import { bar } from 'lib'
baz = 7"""
output = """
var baz, foo;
foo = 5;
import {
bar
} from 'lib';
baz = 7;"""
eq toJS(input), output
# Export statements
test "export empty object", ->
input = "export { }"
output = "export {};"
eq toJS(input), output
test "export empty object", ->
input = "export {}"
output = "export {};"
eq toJS(input), output
test "export named members within an object", ->
input = "export { foo, bar }"
output = """
export {
foo,
bar
};"""
eq toJS(input), output
test "export named members as aliases, within an object", ->
input = "export { foo as bar, baz as qux }"
output = """
export {
foo as bar,
baz as qux
};"""
eq toJS(input), output
test "export named members within an object, with an optional comma", ->
input = "export { foo, bar, }"
output = """
export {
foo,
bar
};"""
eq toJS(input), output
test "multiline export named members within an object", ->
input = """
export {
foo,
bar
}"""
output = """
export {
foo,
bar
};"""
eq toJS(input), output
test "multiline export named members within an object, with an optional comma", ->
input = """
export {
foo,
bar,
}"""
output = """
export {
foo,
bar
};"""
eq toJS(input), output
test "export default string", ->
input = "export default 'foo'"
output = "export default 'foo';"
eq toJS(input), output
test "export default number", ->
input = "export default 5"
output = "export default 5;"
eq toJS(input), output
test "export default object", ->
input = "export default { foo: 'bar', baz: 'qux' }"
output = """
export default {
foo: 'bar',
baz: 'qux'
};"""
eq toJS(input), output
test "export default assignment expression", ->
input = "export default foo = 'bar'"
output = """
var foo;
export default foo = 'bar';"""
eq toJS(input), output
test "export assignment expression", ->
input = "export foo = 'bar'"
output = "export var foo = 'bar';"
eq toJS(input), output
test "export multiline assignment expression", ->
input = """
export foo =
'bar'"""
output = "export var foo = 'bar';"
eq toJS(input), output
test "export multiline indented assignment expression", ->
input = """
export foo =
'bar'"""
output = "export var foo = 'bar';"
eq toJS(input), output
test "export default function", ->
input = "export default ->"
output = "export default function() {};"
eq toJS(input), output
test "export default multiline function", ->
input = """
export default (foo) ->
console.log foo"""
output = """
export default function(foo) {
return console.log(foo);
};"""
eq toJS(input), output
test "export assignment function", ->
input = """
export foo = (bar) ->
console.log bar"""
output = """
export var foo = function(bar) {
return console.log(bar);
};"""
eq toJS(input), output
test "export assignment function which contains assignments in its body", ->
input = """
export foo = (bar) ->
baz = '!'
console.log bar + baz"""
output = """
export var foo = function(bar) {
var baz;
baz = '!';
return console.log(bar + baz);
};"""
eq toJS(input), output
test "export default predefined function", ->
input = """
foo = (bar) ->
console.log bar
export default foo"""
output = """
var foo;
foo = function(bar) {
return console.log(bar);
};
export default foo;"""
eq toJS(input), output
# Uncomment this test once ES2015+ `class` support is added
# test "export default class", ->
# input = """
# export default class foo extends bar
# baz: ->
# console.log 'hello, world!'"""
# output = """
# export default class foo extends bar {
# baz: function {
# return console.log('hello, world!');
# }
# }"""
# eq toJS(input), output
# Very limited tests for now, testing that `export class foo` either compiles
# identically (ES2015+) or at least into some function, leaving the specifics
# vague in case the CoffeeScript `class` interpretation changes
test "export class", ->
input = """
export class foo
baz: ->
console.log 'hello, world!'"""
output = toJS input
ok /^export (class foo|var foo = \(function)/.test toJS input
test "export class that extends", ->
input = """
export class foo extends bar
baz: ->
console.log 'hello, world!'"""
output = toJS input
ok /export (class foo|var foo = \(function)/.test(output) and \
not /var foo(;|,)/.test output
test "export default class that extends", ->
input = """
export default class foo extends bar
baz: ->
console.log 'hello, world!'"""
ok /export default (class foo|foo = \(function)/.test toJS input
test "export default named member, within an object", ->
input = "export { foo as default, bar }"
output = """
export {
foo as default,
bar
};"""
eq toJS(input), output
# Import and export in the same statement
test "export an entire module's contents", ->
input = "export * from 'lib'"
output = "export * from 'lib';"
eq toJS(input), output
test "export members imported from another module", ->
input = "export { foo, bar } from 'lib'"
output = """
export {
foo,
bar
} from 'lib';"""
eq toJS(input), output
test "export as aliases members imported from another module", ->
input = "export { foo as bar, baz as qux } from 'lib'"
output = """
export {
foo as bar,
baz as qux
} from 'lib';"""
eq toJS(input), output
test "export list can contain CS only keywords", ->
input = "export { unless } from 'lib'"
output = """
export {
unless
} from 'lib';"""
eq toJS(input), output
test "export list can contain CS only keywords when aliasing", ->
input = "export { when as bar, baz as unless } from 'lib'"
output = """
export {
when as bar,
baz as unless
} from 'lib';"""
eq toJS(input), output
Support import and export of ES2015 modules (#4300) This pull request adds support for ES2015 modules, by recognizing `import` and `export` statements. The following syntaxes are supported, based on the MDN [import](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import) and [export](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/export) pages: ```js import "module-name" import defaultMember from "module-name" import * as name from "module-name" import { } from "module-name" import { member } from "module-name" import { member as alias } from "module-name" import { member1, member2 as alias2, … } from "module-name" import defaultMember, * as name from "module-name" import defaultMember, { … } from "module-name" export default expression export class name export { } export { name } export { name as exportedName } export { name as default } export { name1, name2 as exportedName2, name3 as default, … } export * from "module-name" export { … } from "module-name" ``` As a subsitute for ECMAScript’s `export var name = …` and `export function name {}`, CoffeeScript also supports: ```js export name = … ``` CoffeeScript also supports optional commas within `{ … }`. This PR converts the supported `import` and `export` statements into ES2015 `import` and `export` statements; it **does not resolve the modules**. So any CoffeeScript with `import` or `export` statements will be output as ES2015, and will need to be transpiled by another tool such as Babel before it can be used in a browser. We will need to add a warning to the documentation explaining this. This should be fully backwards-compatible, as `import` and `export` were previously reserved keywords. No flags are used. There are extensive tests included, though because no current JavaScript runtime supports `import` or `export`, the tests compare strings of what the compiled CoffeeScript output is against what the expected ES2015 should be. I also conducted two more elaborate tests: * I forked the [ember-piqu](https://github.com/pauc/piqu-ember) project, which was an Ember CLI app that used ember-cli-coffeescript and [ember-cli-coffees6](https://github.com/alexspeller/ember-cli-coffees6) (which adds “support” for `import`/`export` by wrapping such statements in backticks before passing the result to the CoffeeScript compiler). I removed `ember-cli-coffees6` and replaced the CoffeeScript compiler used in the build chain with this code, and the app built without errors. [Demo here.](https://github.com/GeoffreyBooth/coffeescript-modules-test-piqu) * I also forked the [CoffeeScript version of Meteor’s Todos example app](https://github.com/meteor/todos/tree/coffeescript), and replaced all of its `require` statements with the `import` and `export` statements from the original ES2015 version of the app on its `master` branch. I then updated the `coffeescript` Meteor package in the app to use this new code, and again the app builds without errors. [Demo here.](https://github.com/GeoffreyBooth/coffeescript-modules-test-meteor-todos) The discussion history for this work started [here](https://github.com/jashkenas/coffeescript/pull/4160) and continued [here](https://github.com/GeoffreyBooth/coffeescript/pull/2). @lydell provided guidance, and @JimPanic and @rattrayalex contributed essential code.
2016-09-14 14:46:05 -04:00
# Edge cases
test "multiline import with comments", ->
input = """
import {
foo, # Not as good as bar
bar as baz # I prefer qux
} from 'lib'"""
output = """
import {
foo,
bar as baz
} from 'lib';"""
eq toJS(input), output
test "`from` not part of an import or export statement can still be assigned", ->
from = 5
eq 5, from
test "a variable named `from` can be assigned after an import", ->
input = """
import { foo } from 'lib'
from = 5"""
output = """
var from;
import {
foo
} from 'lib';
from = 5;"""
eq toJS(input), output
test "`from` can be assigned after a multiline import", ->
input = """
import {
foo
} from 'lib'
from = 5"""
output = """
var from;
import {
foo
} from 'lib';
from = 5;"""
eq toJS(input), output
test "`from` can be imported as a member name", ->
input = "import { from } from 'lib'"
output = """
import {
from
} from 'lib';"""
eq toJS(input), output
test "`from` can be imported as a member name and aliased", ->
input = "import { from as foo } from 'lib'"
output = """
import {
from as foo
} from 'lib';"""
eq toJS(input), output
test "`from` can be used as an alias name", ->
input = "import { foo as from } from 'lib'"
output = """
import {
foo as from
} from 'lib';"""
eq toJS(input), output
test "`as` can be imported as a member name", ->
input = "import { as } from 'lib'"
output = """
import {
as
} from 'lib';"""
eq toJS(input), output
test "`as` can be imported as a member name and aliased", ->
input = "import { as as foo } from 'lib'"
output = """
import {
as as foo
} from 'lib';"""
eq toJS(input), output
test "`as` can be used as an alias name", ->
input = "import { foo as as } from 'lib'"
output = """
import {
foo as as
} from 'lib';"""
eq toJS(input), output
test "CS only keywords can be used as imported names in import lists", ->
input = """
import { unless as bar } from 'lib'
bar.barMethod()"""
output = """
import {
unless as bar
} from 'lib';
bar.barMethod();"""
eq toJS(input), output
test "`*` can be used in an expression on the same line as an export keyword", ->
input = "export foo = (x) -> x * x"
output = """
export var foo = function(x) {
return x * x;
};"""
eq toJS(input), output
input = "export default foo = (x) -> x * x"
output = """
var foo;
export default foo = function(x) {
return x * x;
};"""
eq toJS(input), output
Support import and export of ES2015 modules (#4300) This pull request adds support for ES2015 modules, by recognizing `import` and `export` statements. The following syntaxes are supported, based on the MDN [import](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import) and [export](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/export) pages: ```js import "module-name" import defaultMember from "module-name" import * as name from "module-name" import { } from "module-name" import { member } from "module-name" import { member as alias } from "module-name" import { member1, member2 as alias2, … } from "module-name" import defaultMember, * as name from "module-name" import defaultMember, { … } from "module-name" export default expression export class name export { } export { name } export { name as exportedName } export { name as default } export { name1, name2 as exportedName2, name3 as default, … } export * from "module-name" export { … } from "module-name" ``` As a subsitute for ECMAScript’s `export var name = …` and `export function name {}`, CoffeeScript also supports: ```js export name = … ``` CoffeeScript also supports optional commas within `{ … }`. This PR converts the supported `import` and `export` statements into ES2015 `import` and `export` statements; it **does not resolve the modules**. So any CoffeeScript with `import` or `export` statements will be output as ES2015, and will need to be transpiled by another tool such as Babel before it can be used in a browser. We will need to add a warning to the documentation explaining this. This should be fully backwards-compatible, as `import` and `export` were previously reserved keywords. No flags are used. There are extensive tests included, though because no current JavaScript runtime supports `import` or `export`, the tests compare strings of what the compiled CoffeeScript output is against what the expected ES2015 should be. I also conducted two more elaborate tests: * I forked the [ember-piqu](https://github.com/pauc/piqu-ember) project, which was an Ember CLI app that used ember-cli-coffeescript and [ember-cli-coffees6](https://github.com/alexspeller/ember-cli-coffees6) (which adds “support” for `import`/`export` by wrapping such statements in backticks before passing the result to the CoffeeScript compiler). I removed `ember-cli-coffees6` and replaced the CoffeeScript compiler used in the build chain with this code, and the app built without errors. [Demo here.](https://github.com/GeoffreyBooth/coffeescript-modules-test-piqu) * I also forked the [CoffeeScript version of Meteor’s Todos example app](https://github.com/meteor/todos/tree/coffeescript), and replaced all of its `require` statements with the `import` and `export` statements from the original ES2015 version of the app on its `master` branch. I then updated the `coffeescript` Meteor package in the app to use this new code, and again the app builds without errors. [Demo here.](https://github.com/GeoffreyBooth/coffeescript-modules-test-meteor-todos) The discussion history for this work started [here](https://github.com/jashkenas/coffeescript/pull/4160) and continued [here](https://github.com/GeoffreyBooth/coffeescript/pull/2). @lydell provided guidance, and @JimPanic and @rattrayalex contributed essential code.
2016-09-14 14:46:05 -04:00
test "`*` and `from` can be used in an export default expression", ->
input = """
export default foo.extend
bar: ->
from = 5
from = from * 3"""
output = """
export default foo.extend({
bar: function() {
var from;
from = 5;
return from = from * 3;
}
});"""
eq toJS(input), output
test "wrapped members can be imported multiple times if aliased", ->
input = "import { foo, foo as bar } from 'lib'"
output = """
import {
foo,
foo as bar
} from 'lib';"""
eq toJS(input), output
test "default and wrapped members can be imported multiple times if aliased", ->
input = "import foo, { foo as bar } from 'lib'"
output = """
import foo, {
foo as bar
} from 'lib';"""
eq toJS(input), output