# Modules, a.k.a. ES2015 import/export
# ------------------------------------
#
# Remember, we’re 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 `{ … }`.


# 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 implicit object", ->
  input = "export default foo: 'bar', baz: 'qux'"
  output = """
    export default {
      foo: 'bar',
      baz: 'qux'
    };"""
  eq toJS(input), output

test "export default multiline implicit 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 CoffeeScript keywords", ->
  input = "export { unless } from 'lib'"
  output = """
    export {
      unless
    } from 'lib';"""
  eq toJS(input), output

test "export list can contain CoffeeScript 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


# 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 "CoffeeScript 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

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

test "import a member named default", ->
  input = "import { default } from 'lib'"
  output = """
    import {
      default
    } from 'lib';"""
  eq toJS(input), output

test "import an aliased member named default", ->
  input = "import { default as def } from 'lib'"
  output = """
    import {
      default as def
    } from 'lib';"""
  eq toJS(input), output

test "export a member named default", ->
  input = "export { default }"
  output = """
    export {
      default
    };"""
  eq toJS(input), output

test "export an aliased member named default", ->
  input = "export { def as default }"
  output = """
    export {
      def as default
    };"""
  eq toJS(input), output

test "import an imported member named default", ->
  input = "import { default } from 'lib'"
  output = """
    import {
      default
    } from 'lib';"""
  eq toJS(input), output

test "import an imported aliased member named default", ->
  input = "import { default as def } from 'lib'"
  output = """
    import {
      default as def
    } from 'lib';"""
  eq toJS(input), output

test "export an imported member named default", ->
  input = "export { default } from 'lib'"
  output = """
    export {
      default
    } from 'lib';"""
  eq toJS(input), output

test "export an imported aliased member named default", ->
  input = "export { default as def } from 'lib'"
  output = """
    export {
      default as def
    } from 'lib';"""
  eq toJS(input), output

test "#4394: export shouldn't prevent variable declarations", ->
  input = """
    x = 1
    export { x }
  """
  output = """
    var x;

    x = 1;

    export {
      x
    };
  """
  eq toJS(input), output

test "#4451: `default` in an export statement is only treated as a keyword when it follows `export` or `as`", ->
  input = "export default { default: 1 }"
  output = """
    export default {
      "default": 1
    };
  """
  eq toJS(input), output

test "#4491: import- and export-specific lexing should stop after import/export statement", ->
  input = """
    import {
      foo,
      bar as baz
    } from 'lib'

    foo as
    3 * as 4
    from 'foo'
    """
  output = """
    import {
      foo,
      bar as baz
    } from 'lib';

    foo(as);

    3 * as(4);

    from('foo');
    """
  eq toJS(input), output

  input = """
    import { foo, bar as baz } from 'lib'

    foo as
    3 * as 4
    from 'foo'
    """
  output = """
    import {
      foo,
      bar as baz
    } from 'lib';

    foo(as);

    3 * as(4);

    from('foo');
    """
  eq toJS(input), output

  input = """
    import * as lib from 'lib'

    foo as
    3 * as 4
    from 'foo'
    """
  output = """
    import * as lib from 'lib';

    foo(as);

    3 * as(4);

    from('foo');
    """
  eq toJS(input), output

  input = """
    export {
      foo,
      bar
    }

    foo as
    3 * as 4
    from 'foo'
    """
  output = """
    export {
      foo,
      bar
    };

    foo(as);

    3 * as(4);

    from('foo');
    """
  eq toJS(input), output

  input = """
    export * from 'lib'

    foo as
    3 * as 4
    from 'foo'
    """
  output = """
    export * from 'lib';

    foo(as);

    3 * as(4);

    from('foo');
    """
  eq toJS(input), output
