jashkenas/coffee-script — pull request #3100 by epidemian (2013-08-01 04:38:13+00:00)
Title: Fix #3023, Change how error messages are shown

This PR is an attempt at fixing #3023 and make the display of compiler errors less problematic/fragile.

Instead of throwing the syntax errors with their source file location and needing to then catch them and call a `prettyErrorMessage` function in order to get the formatted error message, now syntax errors know how to pretty-print themselves (their `toString` method gets overridden).

An intermediate `catch` & re-`throw` is needed at the level of `CoffeeScript.compile` function and friends to add the source file information to the errors. But the benefit of this approach is that now libraries (or people =P) that use the `CoffeeScript` object directly don't need to bother catching the possible compilation errors and calling a special function in order to get the nice error messages; they can just print the error itself (or let it bubble up) and the error will know how to pretty-print itself.

The bit about overriding the `stack` property of the syntax errors (2b4a37296ff8fd2559364f59c386f9d37aab3e49) is a little involved i think, but that's basically what fixes #3023. Originally, i though that overriding the `toString` method and deleting the `stack` property [was enough](https://github.com/epidemian/coffee-script/commit/3f9cdcf1faaa34ce7322cea79be724003c57d51e#L6R146) to make the formatted error messages appear on Node.js. It turns out that only on Node's *REPL* (and Coffee's REPL too) the `toString` method of the error is used to display the message if the error doesn't have a `stack`. On vanilla Node only the error's `stack` is shown, and if it doesn't have one it just shows the `: ` style of message.

Example using current Coffee's master to compile some broken code using the compiler as a library (and relying on the require extension, which is basically what Mocha [does](https://github.com/jashkenas/coffee-script/issues/3023#issuecomment-19172246)):

```
$ echo 'class class class' > /tmp/error.coffee
$ node -e 'require("coffee-script"); require("/tmp/error.coffee");'

/usr/lib/node_modules/coffee-script/lib/coffee-script/coffee-script.js:183
throw err;
^
SyntaxError: unexpected CLASS
at Object.exports.throwSyntaxError (/usr/lib/node_modules/coffee-script/lib/coffee-script/helpers.js:197:13)
at Object.parser.yy.parseError (/usr/lib/node_modules/coffee-script/lib/coffee-script/coffee-script.js:273:20)
at Object.parse (/usr/lib/node_modules/coffee-script/lib/coffee-script/parser.js:616:22)
at exports.compile.compile (/usr/lib/node_modules/coffee-script/lib/coffee-script/coffee-script.js:38:24)
at compileFile (/usr/lib/node_modules/coffee-script/lib/coffee-script/coffee-script.js:174:16)
at Object.loadFile (/usr/lib/node_modules/coffee-script/lib/coffee-script/coffee-script.js:190:14)
at Module.load (/usr/lib/node_modules/coffee-script/lib/coffee-script/coffee-script.js:219:36)
at Function.Module._load (module.js:312:12)
at Module.require (module.js:364:17)
at require (module.js:380:17)
at [eval]:1:27
at Object. ([eval]-wrapper:6:22)
at Module._compile (module.js:456:26)
at evalScript (node.js:532:25)
at startup (node.js:80:7)
at node.js:901:3
```

Not a very helpful error if you want to know where the heck is the problem.

Only deleting the `stack` property:

```
$ node -e 'require("coffee-script"); require("/tmp/error.coffee");'

/usr/lib/node_modules/coffee-script/lib/coffee-script/coffee-script.js:197
throw err;
^
SyntaxError: unexpected CLASS
```

Less noisy but still not very helpful.

Overriding the `stack`:

```
$ node -e 'require("coffee-script"); require("/tmp/error.coffee");'

/usr/lib/node_modules/coffee-script/lib/coffee-script/coffee-script.js:197
throw err;
^
/tmp/error.coffee:1:7: error: unexpected CLASS
class class class
^^^^^
```

Yeah! *Now* i know which `class` is wrong! :stuck_out_tongue_closed_eyes: (the example is very simple; but image a many-hundred LOC file with an error somewhere in the middle; the error location and highlight helps a lot in those cases)