jashkenas / jashkenas/coffeescript
Enhancement: Output let and const for variable declarations, without breaking changes
- Dominant language
- CoffeeScript
- Stars
- 16.6k
- Forks
- 2k
- PR merge metrics
- No merged PRs in 30d
Description
Migrating this from https://github.com/jashkenas/coffeescript/issues/5344#issuecomment-922667010:
Now that CoffeeScript 2 outputs modern ES6+ syntax, we can use `let` and `const` in our output. Leaving aside the question of supporting block-scoped variables in the CoffeeScript _input,_ (see #4985) there’s no reason that our generated output needs to use `var` when it could use `let` or `const` instead. We could therefore make two improvements in output code readability:
1. Whenever possible, variable declarations should be placed at the block scope (via `let`) rather than always at the top of the function scope (the current behavior with `var`).
1. Whenever possible, variable declarations should use `const`. This would be whenever a variable is never reassigned.
The key here is to _not_ cause any breaking changes. So when in doubt, we would keep the current “define at the top of the function scope” behavior. A [declaration/first assignment within a loop](https://github.com/jashkenas/coffeescript/issues/5344#issuecomment-922636634) is a example of such a case. A `let x` within a loop means that `x` is declared multiple times (once for each iteration of the loop) which might be a breaking change for existing code.
If you look at this example:
```coffee
if new Date().getHours() < 9
breakfast = if new Date().getDay() is 6 then 'donuts' else 'coffee'
alert "Time to make the #{breakfast}!"
else
alert 'Time to get some work done.'
```
`breakfast` is only used within that `if` block, but it’s currently getting a `var breakfast;` line at the top of the output. The output instead could be this:
```js
if (new Date().getHours() < 9) {
let breakfast;
breakfast = new Date().getDay() === 6 ? 'donuts' : 'coffee';
alert(`Time to make the ${breakfast}!`);
} else {
alert('Time to get some work done.');
}
```
And then phase 2 would see that `breakfast` is never reassigned _and_ never referenced before its assignment (read about the `let`/`const` [“temporal dead zone”](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let#temporal_dead_zone_tdz)) and use `const` instead:
```js
if (new Date().getHours() < 9) {
const breakfast = new Date().getDay() === 6 ? 'donuts' : 'coffee';
alert(`Time to make the ${breakfast}!`);
} else {
alert('Time to get some work done.');
}
```
I would try to achieve the two enhancements as separate PRs, probably with the block-scoping first.
Contributor guide
Research direction
Start by tracing how the compiler currently hoists and emits variable declarations, then review the linked discussion and the block-scoping constraints described here. Separate block-scoping from const inference as proposed, preserve existing output where scope changes could break behavior, and verify the examples and relevant compiler tests reflect the intended output.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- coffeescript
- Domain
- compilers
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 30/100