# 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", ->
  eqJS """
    if Meteor.isServer
      `import { foo, bar as baz } from 'lib'`""",
  """
    if (Meteor.isServer) {
      import { foo, bar as baz } from 'lib';
    }"""

test "import an entire module for side effects only, without importing any bindings", ->
  eqJS "import 'lib'",
  "import 'lib';"

test "import default member from module, adding the member to the current scope", ->
  eqJS """
    import foo from 'lib'
    foo.fooMethod()""",
  """
    import foo from 'lib';

    foo.fooMethod();"""

test "import an entire module's contents as an alias, adding the alias to the current scope", ->
  eqJS """
    import * as foo from 'lib'
    foo.fooMethod()""",
  """
    import * as foo from 'lib';

    foo.fooMethod();"""

test "import empty object", ->
  eqJS "import { } from 'lib'",
  "import {} from 'lib';"

test "import empty object", ->
  eqJS "import {} from 'lib'",
  "import {} from 'lib';"

test "import a single member of a module, adding the member to the current scope", ->
  eqJS """
    import { foo } from 'lib'
    foo.fooMethod()""",
  """
    import {
      foo
    } from 'lib';

    foo.fooMethod();"""

test "import a single member of a module as an alias, adding the alias to the current scope", ->
  eqJS """
    import { foo as bar } from 'lib'
    bar.barMethod()""",
  """
    import {
      foo as bar
    } from 'lib';

    bar.barMethod();"""

test "import multiple members of a module, adding the members to the current scope", ->
  eqJS """
    import { foo, bar } from 'lib'
    foo.fooMethod()
    bar.barMethod()""",
  """
    import {
      foo,
      bar
    } from 'lib';

    foo.fooMethod();

    bar.barMethod();"""

test "import multiple members of a module where some are aliased, adding the members or aliases to the current scope", ->
  eqJS """
    import { foo, bar as baz } from 'lib'
    foo.fooMethod()
    baz.bazMethod()""",
  """
    import {
      foo,
      bar as baz
    } from 'lib';

    foo.fooMethod();

    baz.bazMethod();"""

test "import default member and other members of a module, adding the members to the current scope", ->
  eqJS """
    import foo, { bar, baz as qux } from 'lib'
    foo.fooMethod()
    bar.barMethod()
    qux.quxMethod()""",
  """
    import foo, {
      bar,
      baz as qux
    } from 'lib';

    foo.fooMethod();

    bar.barMethod();

    qux.quxMethod();"""

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", ->
  eqJS """
    import foo, * as bar from 'lib'
    foo.fooMethod()
    bar.barMethod()""",
  """
    import foo, * as bar from 'lib';

    foo.fooMethod();

    bar.barMethod();"""

test "multiline simple import", ->
  eqJS """
    import {
      foo,
      bar as baz
    } from 'lib'""",
  """
    import {
      foo,
      bar as baz
    } from 'lib';"""

test "multiline complex import", ->
  eqJS """
    import foo, {
      bar,
      baz as qux
    } from 'lib'""",
  """
    import foo, {
      bar,
      baz as qux
    } from 'lib';"""

test "import with optional commas", ->
  eqJS "import { foo, bar, } from 'lib'",
  """
    import {
      foo,
      bar
    } from 'lib';"""

test "multiline import without commas", ->
  eqJS """
    import {
      foo
      bar
    } from 'lib'""",
  """
    import {
      foo,
      bar
    } from 'lib';"""

test "multiline import with optional commas", ->
  eqJS """
    import {
      foo,
      bar,
    } from 'lib'""",
  """
    import {
      foo,
      bar
    } from 'lib';"""

test "a variable can be assigned after an import", ->
  eqJS """
    import { foo } from 'lib'
    bar = 5""",
  """
    var bar;

    import {
      foo
    } from 'lib';

    bar = 5;"""

test "variables can be assigned before and after an import", ->
  eqJS """
    foo = 5
    import { bar } from 'lib'
    baz = 7""",
  """
    var baz, foo;

    foo = 5;

    import {
      bar
    } from 'lib';

    baz = 7;"""

# Export statements

test "export empty object", ->
  eqJS "export { }",
  "export {};"

test "export empty object", ->
  eqJS "export {}",
  "export {};"

test "export named members within an object", ->
  eqJS "export { foo, bar }",
  """
    export {
      foo,
      bar
    };"""

test "export named members as aliases, within an object", ->
  eqJS "export { foo as bar, baz as qux }",
  """
    export {
      foo as bar,
      baz as qux
    };"""

test "export named members within an object, with an optional comma", ->
  eqJS "export { foo, bar, }",
  """
    export {
      foo,
      bar
    };"""

test "multiline export named members within an object", ->
  eqJS """
    export {
      foo,
      bar
    }""",
  """
    export {
      foo,
      bar
    };"""

test "multiline export named members within an object, with an optional comma", ->
  eqJS """
    export {
      foo,
      bar,
    }""",
  """
    export {
      foo,
      bar
    };"""

test "export default string", ->
  eqJS "export default 'foo'",
  "export default 'foo';"

test "export default number", ->
  eqJS "export default 5",
  "export default 5;"

test "export default object", ->
  eqJS "export default { foo: 'bar', baz: 'qux' }",
  """
    export default {
      foo: 'bar',
      baz: 'qux'
    };"""

test "export default implicit object", ->
  eqJS "export default foo: 'bar', baz: 'qux'",
  """
    export default {
      foo: 'bar',
      baz: 'qux'
    };"""

test "export default multiline implicit object", ->
  eqJS """
    export default
      foo: 'bar'
      baz: 'qux'
    """,
  """
    export default {
      foo: 'bar',
      baz: 'qux'
    };"""

test "export default multiline implicit object with internal braces", ->
  eqJS """
    export default
      foo: yes
      bar: {
        baz
      }
      quz: no
    """,
  """
    export default {
      foo: true,
      bar: {baz},
      quz: false
    };"""

test "export default assignment expression", ->
  eqJS "export default foo = 'bar'",
  """
    var foo;

    export default foo = 'bar';"""

test "export assignment expression", ->
  eqJS "export foo = 'bar'",
  "export var foo = 'bar';"

test "export multiline assignment expression", ->
  eqJS """
    export foo =
    'bar'""",
    "export var foo = 'bar';"

test "export multiline indented assignment expression", ->
  eqJS """
    export foo =
      'bar'""",
      "export var foo = 'bar';"

test "export default function", ->
  eqJS "export default ->",
  "export default function() {};"

test "export default multiline function", ->
  eqJS """
    export default (foo) ->
      console.log foo""",
    """
    export default function(foo) {
      return console.log(foo);
    };"""

test "export assignment function", ->
  eqJS """
    export foo = (bar) ->
      console.log bar""",
    """
    export var foo = function(bar) {
      return console.log(bar);
    };"""

test "export assignment function which contains assignments in its body", ->
  eqJS """
    export foo = (bar) ->
      baz = '!'
      console.log bar + baz""",
    """
    export var foo = function(bar) {
      var baz;
      baz = '!';
      return console.log(bar + baz);
    };"""

test "export default predefined function", ->
  eqJS """
    foo = (bar) ->
      console.log bar
    export default foo""",
  """
    var foo;

    foo = function(bar) {
      return console.log(bar);
    };

    export default foo;"""

test "export default class", ->
  eqJS """
    export default class foo extends bar
      baz: ->
        console.log 'hello, world!'""",
      """
    var foo;

    export default foo = class foo extends bar {
      baz() {
        return console.log('hello, world!');
      }

    };"""

test "export class", ->
  eqJS """
    export class foo
      baz: ->
        console.log 'hello, world!'""",
      """
    export var foo = class foo {
      baz() {
        return console.log('hello, world!');
      }

    };"""

test "export class that extends", ->
  eqJS """
    export class foo extends bar
      baz: ->
        console.log 'hello, world!'""",
      """
    export var foo = class foo extends bar {
      baz() {
        return console.log('hello, world!');
      }

    };"""

test "export default class that extends", ->
  eqJS """
    export default class foo extends bar
      baz: ->
        console.log 'hello, world!'""",
      """
    var foo;

    export default foo = class foo extends bar {
      baz() {
        return console.log('hello, world!');
      }

    };"""

test "export default named member, within an object", ->
  eqJS "export { foo as default, bar }",
  """
    export {
      foo as default,
      bar
    };"""

# Import and export in the same statement

test "export an entire module's contents", ->
  eqJS "export * from 'lib'",
  "export * from 'lib';"

test "export members imported from another module", ->
  eqJS "export { foo, bar } from 'lib'",
  """
    export {
      foo,
      bar
    } from 'lib';"""

test "export as aliases members imported from another module", ->
  eqJS "export { foo as bar, baz as qux } from 'lib'",
  """
    export {
      foo as bar,
      baz as qux
    } from 'lib';"""

test "export list can contain CoffeeScript keywords", ->
  eqJS "export { unless, and } from 'lib'",
  """
    export {
      unless,
      and
    } from 'lib';"""

test "export list can contain CoffeeScript keywords when aliasing", ->
  eqJS "export { when as bar, baz as unless, and as foo, booze as not } from 'lib'",
  """
    export {
      when as bar,
      baz as unless,
      and as foo,
      booze as not
    } from 'lib';"""


# Edge cases

test "multiline import with comments", ->
  eqJS """
    import {
      foo, # Not as good as bar
      bar as baz # I prefer qux
    } from 'lib'""",
  """
    import {
      foo, // Not as good as bar
      bar as baz // I prefer qux
    } from 'lib';"""

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", ->
  eqJS """
    import { foo } from 'lib'
    from = 5""",
  """
    var from;

    import {
      foo
    } from 'lib';

    from = 5;"""

test "`from` can be assigned after a multiline import", ->
  eqJS """
    import {
      foo
    } from 'lib'
    from = 5""",
  """
    var from;

    import {
      foo
    } from 'lib';

    from = 5;"""

test "`from` can be imported as a member name", ->
  eqJS "import { from } from 'lib'",
  """
    import {
      from
    } from 'lib';"""

test "`from` can be imported as a member name and aliased", ->
  eqJS "import { from as foo } from 'lib'",
  """
    import {
      from as foo
    } from 'lib';"""

test "`from` can be used as an alias name", ->
  eqJS "import { foo as from } from 'lib'",
  """
    import {
      foo as from
    } from 'lib';"""

test "`as` can be imported as a member name", ->
  eqJS "import { as } from 'lib'",
  """
    import {
      as
    } from 'lib';"""

test "`as` can be imported as a member name and aliased", ->
  eqJS "import { as as foo } from 'lib'",
  """
    import {
      as as foo
    } from 'lib';"""

test "`as` can be used as an alias name", ->
  eqJS "import { foo as as } from 'lib'",
  """
    import {
      foo as as
    } from 'lib';"""

test "CoffeeScript keywords can be used as imported names in import lists", ->
  eqJS """
    import { unless as bar, and as computedAnd } from 'lib'
    bar.barMethod()""",
  """
    import {
      unless as bar,
      and as computedAnd
    } from 'lib';

    bar.barMethod();"""

test "`*` can be used in an expression on the same line as an export keyword", ->
  eqJS "export foo = (x) -> x * x",
  """
    export var foo = function(x) {
      return x * x;
    };"""
  eqJS "export default foo = (x) -> x * x",
  """
    var foo;

    export default foo = function(x) {
      return x * x;
    };"""

test "`*` and `from` can be used in an export default expression", ->
  eqJS """
    export default foo.extend
      bar: ->
        from = 5
        from = from * 3""",
      """
    export default foo.extend({
      bar: function() {
        var from;
        from = 5;
        return from = from * 3;
      }
    });"""

test "wrapped members can be imported multiple times if aliased", ->
  eqJS "import { foo, foo as bar } from 'lib'",
  """
    import {
      foo,
      foo as bar
    } from 'lib';"""

test "default and wrapped members can be imported multiple times if aliased", ->
  eqJS "import foo, { foo as bar } from 'lib'",
  """
    import foo, {
      foo as bar
    } from 'lib';"""

test "import a member named default", ->
  eqJS "import { default } from 'lib'",
  """
    import {
      default
    } from 'lib';"""

test "import an aliased member named default", ->
  eqJS "import { default as def } from 'lib'",
  """
    import {
      default as def
    } from 'lib';"""

test "export a member named default", ->
  eqJS "export { default }",
  """
    export {
      default
    };"""

test "export an aliased member named default", ->
  eqJS "export { def as default }",
  """
    export {
      def as default
    };"""

test "import an imported member named default", ->
  eqJS "import { default } from 'lib'",
  """
    import {
      default
    } from 'lib';"""

test "import an imported aliased member named default", ->
  eqJS "import { default as def } from 'lib'",
  """
    import {
      default as def
    } from 'lib';"""

test "export an imported member named default", ->
  eqJS "export { default } from 'lib'",
  """
    export {
      default
    } from 'lib';"""

test "export an imported aliased member named default", ->
  eqJS "export { default as def } from 'lib'",
  """
    export {
      default as def
    } from 'lib';"""

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

    x = 1;

    export {
      x
    };
  """

test "#4451: `default` in an export statement is only treated as a keyword when it follows `export` or `as`", ->
  eqJS "export default { default: 1 }",
  """
    export default {
      default: 1
    };
  """

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

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

    foo(as);

    3 * as(4);

    from('foo');
    """

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

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

    foo(as);

    3 * as(4);

    from('foo');
    """

  eqJS """
    import * as lib from 'lib'

    foo as
    3 * as 4
    from 'foo'
    """,
  """
    import * as lib from 'lib';

    foo(as);

    3 * as(4);

    from('foo');
    """

  eqJS """
    export {
      foo,
      bar
    }

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

    foo(as);

    3 * as(4);

    from('foo');
    """

  eqJS """
    export * from 'lib'

    foo as
    3 * as 4
    from 'foo'
    """,
  """
    export * from 'lib';

    foo(as);

    3 * as(4);

    from('foo');
    """

# Issue #4874: Backslash not supported in import or export statements
test "#4874: backslash `import`", ->

  eqJS """
    import foo \
        from 'lib'

    foo a
    """,
  """
    import foo from 'lib';

    foo(a);
    """

  eqJS """
    import \
                    foo \
        from \
    'lib'

    foo a
    """,
  """
    import foo from 'lib';

    foo(a);
    """

  eqJS """
    import \
          utilityBelt \
    , {
      each
    } from \
    'underscore'
    """,
  """
    import utilityBelt, {
      each
    } from 'underscore';
    """

test "#4874: backslash `export`", ->
  eqJS """
    export \
      * \
            from \
      'underscore'
    """,
  """
    export * from 'underscore';
    """

  eqJS """
    export \
        { max, min } \
              from \
      'underscore'
  """,
  """
    export {
      max,
      min
    } from 'underscore';
    """

test "#4834: dynamic import", ->
  eqJS """
    import('module').then ->
  """,
  """
    import('module').then(function() {});
  """

  eqJS """
    foo = ->
      bar = await import('bar')
  """,
  """
    var foo;

    foo = async function() {
      var bar;
      return bar = (await import('bar'));
    };
  """

  eqJS """
    console.log import('foo')
  """,
  """
    console.log(import('foo'));
  """

test "#5317: Support import.meta", ->
  eqJS """
    foo = import.meta
  """,
  """
    var foo;

    foo = import.meta;
  """

  eqJS """
    foo = import
        .meta
  """,
  """
    var foo;

    foo = import.meta;
  """

  eqJS """
    foo = import.
        meta
  """,
  """
    var foo;

    foo = import.meta;
  """

  eqJS """
    foo = import.meta.bar
  """,
  """
    var foo;

    foo = import.meta.bar;
  """

  eqJS """
    foo = import
        .meta
        .bar
  """,
  """
    var foo;

    foo = import.meta.bar;
  """

  eqJS """
    console.log import.meta.url
  """,
  """
    console.log(import.meta.url);
  """
