`"""` (and `"`) strings are lexed into an array of tokens, consisting of
strings and interpolations. Previously, the minimum indententation
inside `"""` strings was stripped from the beginning of _all_ of those
string tokens. Usually, the indentation is longer than any other
sequence of spaces in a `"""` string, so the problem didn't occur in
most cases. This commit makes sure to only strip indentation after
newlines.
Fixes#4314.
Very large decimal number literals, binary number literals and octal
literals are lexed into an INFINITY token (instead of a NUMBER token)
and compiled into `2e308`. That is is supposed to be the case for very
large hexdecimal dumber literals as well, but previously wasn't.
Before:
$ node -p 'require("./").tokens(`0x${Array(256 + 1).join("f")}`)[0][0]'
NUMBER
After:
$ node -p 'require("./").tokens(`0x${Array(256 + 1).join("f")}`)[0][0]'
INFINITY
This commit also cleans up `numberToken` in lexer.coffee a bit.
`isLiteralArguments` mistakenly looked at `Literal`s instead of
`IdentifierLiteral`s.
This also gets rid of the ugly `.asKey` hack in nodes.coffee.
Fixes#4320.
Before:
```
$ cat tmp.coffee.md
test
a
$ ./bin/coffee tmp.coffee.md
ReferenceError: a is not defined
at Object.<anonymous> (/src/coffee-script/tmp.coffee.md:2:3)
...
```
Note how the line and column numbers (2 and 3, respectively) are not
correct.
After:
```
$ ./bin/coffee tmp.coffee.md
ReferenceError: a is not defined
at Object.<anonymous> (/home/lydell/forks/coffee-script/tmp.coffee.md:3:5)
...
```
Line 3, column 5 is the actual position of the `a` in tmp.coffee.md.
Supersedes and fixes#4204.
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.
In f609036bee, a line was changed from
`if length > 0 then (length - 1) else 0` to `Math.max 0, length - 1`. However,
in some cases, the `length` variable can be `undefined`. The previous code would
correctly compute `lastCharacter` as 0, but the new code would compute it as
`NaN`. This would cause trouble later on: the end location would just be the end
of the current chunk, which would be incorrect.
Here's a specific case where the parser was behaving incorrectly:
```
a {
b: ->
return c d,
if e
f
}
g
```
The OUTDENT tokens after the `f` had an undefined length, so the `NaN` made it
so the end location was at the end of the file. That meant that various nodes in
the AST, like the `return` node, would incorrectly have an end location at the
end of the file.
To fix, I just reverted the change to that particular line.
In for example `for` loops, a variable called `i` is generated (for the
loop index). If that name is unavailable, `j` is used instead, then `k`,
`l`, etc. all the way to `z`. Then, `aa`, `ab`, `ac` etc. are used.
This meant that, eventually, `do` would be used, but that's not a valid
variable name since `do` is a JavaScript keyword.
This logic was also inefficiently implemented. For example, going from
`aa` to `ab` or from `az` to `ba` required lots of loop iterations.
This commit changes the variable naming convention. Now, `i`, `j`, `k`,
etc. to `z` are used like before. Then comes `i1`, `j1`, `k1`, etc. Then
`i2`, `j2`, `k2` and so on. This is simpler, efficient and easier to
understand. `i1` is more obvious to be a loop index than `aa`.
Fixes#4267.
- Inline source maps are now shorter by not using pretty-printed JSON.
- `.register()`ed files are now given more information in their inline source
maps: The name and contents of the source file.
- Some code cleanup.
If you decode the inline source map generated (when using `.register()`) for a
file test.coffee with the contents `console.log "it works!"`, here is the
output:
Before:
{
"version": 3,
"file": "",
"sourceRoot": "",
"sources": [
""
],
"names": [],
"mappings": "AAAA;EAAA,OAAO,CAAC,GAAR,CAAY,eAAZ;AAAA"
}
After:
{"version":3,"file":"","sourceRoot":"","sources":["test.coffee"],"names":[],"mappings":"AAAA;EAAA,OAAO,CAAC,GAAR,CAAY,WAAZ;AAAA","sourcesContent":["console.log \"it works!\"\n"]}
Related: #4214.
- Split out a PROPERTY token from the IDENTIFIER token.
- Split out Property from the Identifier in the grammar.
- Split out PropertyLiteral from IdentifierLiteral.
- Show the same type of error message for compound assignment as for `=`
assignment when the LHS is invalid.
- Show the same type of error message when trying to assign to a CoffeeScript
keyword as when trying to assign to a JavaScript keyword.
- Now longer treat `&& =` as `&&=`. The same goes for `and=`, `||=` and `or=`.
- Unify the error message to: `<optional type> '<value>' can't be assigned`.
Previously, the parser created `Literal` nodes for many things. This resulted in
information loss. Instead of being able to check the node type, we had to use
regexes to tell the different types of `Literal`s apart. That was a bit like
parsing literals twice: Once in the lexer, and once (or more) in the compiler.
It also caused problems, such as `` `this` `` and `this` being indistinguishable
(fixes#2009).
Instead returning `new Literal` in the grammar, subtypes of it are now returned
instead, such as `NumberLiteral`, `StringLiteral` and `IdentifierLiteral`. `new
Literal` by itself is only used to represent code chunks that fit no category.
(While mentioning `NumberLiteral`, there's also `InfinityLiteral` now, which is
a subtype of `NumberLiteral`.)
`StringWithInterpolations` has been added as a subtype of `Parens`, and
`RegexWithInterpolations` as a subtype of `Call`. This makes it easier for other
programs to make use of CoffeeScript's "AST" (nodes). For example, it is now
possible to distinguish between `"a #{b} c"` and `"a " + b + " c"`. Fixes#4192.
`SuperCall` has been added as a subtype of `Call`.
Note, though, that some information is still lost, especially in the lexer. For
example, there is no way to distinguish a heredoc from a regular string, or a
heregex without interpolations from a regular regex. Binary and octal number
literals are indistinguishable from hexadecimal literals.
After the new subtypes were added, they were taken advantage of, removing most
regexes in nodes.coffee. `SIMPLENUM` (which matches non-hex integers) had to be
kept, though, because such numbers need special handling in JavaScript (for
example in `1..toString()`).
An especially nice hack to get rid of was using `new String()` for the token
value for reserved identifiers (to be able to set a property on them which could
survive through the parser). Now it's a good old regular string.
In range literals, slices, splices and for loop steps when number literals
are involved, CoffeeScript can do some optimizations, such as precomputing the
value of, say, `5 - 3` (outputting `2` instead of `5 - 3` literally). As a side
bonus, this now also works with hexadecimal number literals, such as `0x02`.
Finally, this also improves the output of `coffee --nodes`:
# Before:
$ bin/coffee -ne 'while true
"#{a}"
break'
Block
While
Value
Bool
Block
Value
Parens
Block
Op +
Value """"
Value
Parens
Block
Value "a" "break"
# After:
$ bin/coffee -ne 'while true
"#{a}"
break'
Block
While
Value BooleanLiteral: true
Block
Value
StringWithInterpolations
Block
Op +
Value StringLiteral: ""
Value
Parens
Block
Value IdentifierLiteral: a
StatementLiteral: break
Commit 347a6255 is a bit problematic:
- It doesn't include the built .js files.
- It breaks `CoffeeScript.register()`. This can be seen by running the tests;
four of them fails. The error is that `CoffeeScript.register()` calls
`CoffeeScript._compileFile()` with the `sourceMap` option enabled, which
returns an object while the code expected a string.
This commit fixes the broken `CoffeeScript.register()`, by setting the
`sourceMap` option to `false` (but still keeping the `inlineMap` option enabled,
which was the intention of commit 347a6255). It also commits the built .js
files. The tests now pass.
`({a = 1}) ->` and `({a: b}) ->` worked, but not the combination of the two:
`({a: b = 1}) ->`. That destrucuring worked for normal assignments, though:
`{a: b = 1} = c`. This commit fixes the param case.
This breaks compatibility with
->
yield for i in [1..3]
i * 2
and
->
yield
i * 2
yield's behaviour now mirrors that of return in that it can be used stand alone as well as with expressions. Thus, it currently also inherits the above limitations.
This let's you do things like:
fullName = ({first = 'John', last = 'Doe'}) -> "#{first} #{last}"
Note: CoffeeScrits treats `undefined` and `null` the same, and that's true in
the case of destructuring defaults as well, as opposed to ES2015 which only uses
the default value if the target is `undefined`. A similar ES2015 difference
already exists for function parameter defaults. It is important for CoffeeScript
to be consistent with itself.
fullName2 = (first = 'John', last = 'Doe') -> "#{first} #{last}"
assert fullName('Bob', null) is fullName2(first: 'Bob', last: null)
Fixes#1558, #3288 and #4005.
Let me know if there's something I should be doing differently as this is my first contribution to coffeescript.
I fixed the reported issue where a generated variable could clash with a user-defined one in a try/catch block.
I added a test for a few scenarios with different variable names for a try/catch, to confirm the fix and avoid regressions.
Testing with `'[object Array]' is Object::toString.call element` allows arrays from another JS context to be properly handled. The specific use case here is to support jest, which sets up JS contexts using Node/io.js's "vm" module. This approach works in ES3 environments in contrast with ES5's `Array.isArray`.