Files
coffeescript/test/modules.coffee
Geoffrey Booth 6d21dc5495 [CS2] Comments (#4572)
* Make `addLocationDataFn` more DRY

* Style fixes

* Provide access to full parser inside our custom function running in parser.js; rename the function to lay the groundwork for adding data aside from location data

* Fix style.

* Fix style.

* Label test comments

* Update grammar to remove comment tokens; update DSL to call new helper function that preserves comments through parsing

* New implementation of compiling block comments: the lexer pulls them out of the token stream, attaching them as a property to a token; the rewriter moves the attachment around so it lives on a token that is destined to make it through to compilation (and in a good placement); and the nodes render the block comment. All tests but one pass (commented out).

* If a comment follows a class declaration, move the comment inside the class body

* Style

* Improve indentation of multiline comments

* Fix indentation for block comments, at least in the cases covered by the one failing test

* Don’t reverse the order of unshifted comments

* Simplify rewriter’s handling of comments, generalizing the special case

* Expand the list of tokens we need to avoid for passing comments through the parser; get some literal tokens to have nodes created for them so that the comments pass through

* Improve comments; fix multiline flag

* Prepare HereComments for processing line comments

* Line comments, first draft: the tests pass, but the line comments aren’t indented and sometimes trail previous lines when they shouldn’t; updated compiler output in following commit

* Updated compiler, now with line comments

* `process` doesn’t exist in the browser, so we should check for its existence first

* Update parser output

* Test that proves #4290 is fixed

* Indent line comments, first pass

* Compiled output with indented line comments

* Comments that start a new line shouldn’t trail; don’t skip comments attached to generated tokens; stop looking for indentation once we hit a newline

* Revised output

* Cleanup

* Split “multiline” line comment tokens, shifting them forward or back as appropriate

* Fix comments in module specifiers

* Abstract attaching comments to a node

* Line comments in interpolated strings

* Line comments can’t be multiline anymore

* Improve handling of blank lines and indentation of following comments that start a new line (i.e. don’t trail)

* Make comments compilation more object-oriented

* Remove lots of dead code that we don’t need anymore because a comment is never a node, only a fragment

* Improve eqJS helper

* Fix #4290 definitively, with improved output for arrays with interspersed block comments

* Add support for line comments output interspersed within arrays

* Fix mistake, don’t lose the variable we’re working on

* Remove redundant replacements

* Check for indentation only from the start of the string

* Indentations in generated JS are always multiples of two spaces (never tabs) so just look for 2+ spaces

* Update package versions; run Babel twice, once for each preset, temporarily until a Babili bug is fixed that prevents it from running with the env preset

* Don’t rely on `fragment.type`, which can break when the compiler is minified

* Updated generated docs and browser compiler

* Output block comments after function arguments

* Comments appear above scope `var` declarations; better tracking of generated `JS` tokens created only to shepherd comments through to the output

* Create new FuncGlyph node, to hold comments we want to output near the function parameters

* Block comments between `)` and `->`/`=>` get output between `)` and `{`.

* Fix indentation of comments that are the first line inside a bare mode block

* Updated output

* Full Flow example

* Updated browser compiler

* Abstract and organize comment fragment generation code; store more properties on the comment fragment objects; make `throw` behave like `return`

* Abstract token insertion code

* Add missing locationData to STRING_START token, giving it the locationData of the overall StringWithInterpolations token so that comments attached to STRING_START end up on the StringWithInterpolations node

* Allow `SUPER` tokens to carry comments

* Rescue comments from `Existence` nodes and `If` nodes’ conditions

* Rescue comments after `\` line continuation tokens

* Updated compiled output

* Updated browser compiler

* Output block comments in the same `compileFragments` method as line comments, except for inline block comments

* Comments before splice

* Updated browser compiler

* Track compiledComments as a property of Base, to ensure that it’s not a global variable

* Docs: split up the Usage section

* Docs for type annotations via Flow; updated docs output

* Update regular comments documentation

* Updated browser compiler

* Comments before soak

* Comments before static methods, and probably before `@variable =` (this) assignments generally

* Comments before ‘if exists?’, refactor comment before ‘if this.var’ to be more precise, improve helper methods

* Comments before a method that contains ‘super()’ should output above the method property, not above the ‘super.method()’ call

* Fix missing comments before `if not` (i.e. before a UNARY token)

* Fix comments before ‘for’; add test for comment before assignment if (fixed in earlier commit)

* Comments within heregexes

* Updated browser compiler

* Update description to reflect what’s now happening in compileCommentFragments

* Preserve blank lines between line comments; output “whitespace-only” line comments as blank lines, rather than `//` following by whitespace

* Better future-proof comments tests

* Comments before object destructuring; abstract method for setting comments aside before compilation

* Handle more cases of comments before or after `for` loop declaration lines

* Fix indentation of comments preceding `for` loops

* Fix comment before splat function parameter

* Catch another RegexWithInterpolations comment edge case

* Updated browser compiler

* Change heregex example to one that’s more readable; update output

* Remove a few last references to the defunct HERECOMMENT token

* Abstract location hash creation into a function

* Improved clarity per code review notes

* Updated browser compiler
2017-08-02 19:34:34 -07:00

837 lines
16 KiB
CoffeeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 `{ … }`.
# 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 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 } from 'lib'",
"""
export {
unless
} from 'lib';"""
test "export list can contain CoffeeScript keywords when aliasing", ->
eqJS "export { when as bar, baz as unless } from 'lib'",
"""
export {
when as bar,
baz as unless
} 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 } from 'lib'
bar.barMethod()""",
"""
import {
unless as bar
} 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');
"""