Breaking changes for decaffeinate 3.0
See original GitHub issueIn response to a few requests as well as my own personal gut feelings and goals, I’m thinking of making a breaking decaffeinate 3.0 release that changes some of the default behaviors.
In addition to providing an explanation of the changes, I’m looking for feedback in case others have opinions. Some people who might have opinions on all of this: @eventualbuddha @kvz @wordofchristian @nathansobo @gr2m . And of course anyone watching the project.
High-level vision
Someone who has just learned about decaffeinate should be able to run decaffeinate
(or bulk-decaffeinate convert
or another wrapper) on their whole CoffeeScript codebase with no custom configuration and get a working JavaScript codebase with zero bugs introduced (even if the code isn’t pretty everywhere). The only additional task to get things working should be build system changes. As much as possible, decaffeinate should make the resulting JavaScript look nice and provide guidance on how to accurately clean it up by hand.
As a secondary goal, decaffeinate should also support the use case of converting projects incrementally, including files with thorough enough tests that the decaffeinate conversion doesn’t need to be exact.
Proposed changes
- Change
--keep-commonjs
,--enable-babel-constructor-workaround
, and--prefer-const
to be enabled by default. - Add a
--loose
option that enables a reasonable set of--loose...
options all at once. - Add a suggestions (a.k.a. warnings) system that lists suggested cleanup tasks of different priorities.
- New docs to go along with the suggestions system, which attempt to explain all of the nuances in decaffeinate that people should know about when manually cleaning up code.
(Not all of these are actually breaking, but they’re all a bit related.)
Details/motivation
--keep-commonjs
by default
It seems like this is commonly-used, so it seems best to make it the default behavior. Converting to import
/export
doesn’t work in node without Babel, and the conversion isn’t exact because decaffeinate doesn’t (and generally can’t) match named exports to named imports across files.
It’s still possible to correctly convert code to JS modules using --force-default-export
and the cross-file fix-imports
step of bulk-decaffeinate, which is what I do for code at my work, and there will be a bulk-decaffeinate flag/option to do this easily.
--enable-babel-constructor-workaround
by default
This is probably the most controversial. You can’t use this
before super
in constructors in JS classes (which also means no @
params and no bound methods), so this “cheats” by inserting a code snippet that tricks Babel and TypeScript into letting you do it anyway. To clean up the code, you remove the code snippet (which is a self-contained block) and refactor the code to not use this
before super
.
Here’s what it looks like:
class A {}
class B extends A {
constructor() {
{
// Hack: trick babel into allowing this before super.
if (false) { super(); }
let thisFn = (() => { this; }).toString();
let thisName = thisFn.slice(thisFn.indexOf('{') + 1, thisFn.indexOf(';')).trim();
eval(`${thisName} = this;`);
}
this.a = 1;
super(...arguments);
}
}
console.log(new B().a)
Some reasons why I think it’s ok to enable it by default despite the hacks:
- To make sure people are aware of it, it will be the highest-priority suggestion/warning in the new suggestions system (this is the main reason for adding a suggestions system).
- The flag is used for all example projects in the README, so it is very well-tested and stable.
- The behavior when not using Babel/TypeScript is about the same as
--allow-invalid-constructors
(a crash when calling the constructor).
The advantage is that people can worry about the details as a follow-up step rather than before running decaffeinate, and it makes it easier to use the repl and easier to check a large codebase for decaffeinate crashes.
--prefer-const
by default
decaffeinate itself uses let
everywhere except for top-level constants, but I think the most common style in the JS world is to use const
wherever possible. This is reasonably low-priority, but while I’m changing defaults I thought I’d throw it in.
--loose
option
Given posts I’ve seen on the internet, it seems common for people to enable a number of --loose
options when running decaffeinate, so this would enable a number of “suggested” ones at once. Given that every --loose...
option corresponds to a bug that decaffeinate would have introduced in real-world code, I’m hesitant to encourage its use, but it seems reasonable when code has good test coverage or will be manually tested thoroughly, and avoids many of the most annoying manual cleanup steps after decaffeinate.
Suggestions system
For example, decaffeinate will write something like this to stderr after converting a file, with an option to produce a JSON format readable by wrapper scripts:
Consider these suggestions when cleaning up the JS code:
DS001: Remove Babel constructor workaround
DS005: Remove unnecessary use of Array.from
DS011: Use default parameters where possible
DS013: Avoid top-level `this`
See the docs for full explanations:
https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
Online docs will have a description of each suggestion and why decaffeinate behaves the way it does.
Notes
All decaffeinate example projects use decaffeinate --keep-commonjs --prefer-const --enable-babel-constructor-workaround
. Source (see the bulk-decaffeinate.config.js file in each project).
Args used to convert Hubot to JS: decaffeinate --keep-commonjs --prefer-const --loose-default-params --loose-for-expressions --loose-for-of --loose-includes --allow-invalid-constructors
. Source (YouTube video)
@nathansobo in March suggested the following args decaffeinate --keep-commonjs --prefer-const --loose-default-params --loose-for-expressions --loose-for-of
. Source.
The Invig project uses decaffeinate --loose-includes --loose-for-of --allow-invalid-constructors --keep-commonjs --prefer-const --loose-default-params
. Source
Prior discussions in favor of --keep-commonjs
by default: https://github.com/decaffeinate/decaffeinate/issues/403#issuecomment-310102715 and https://github.com/decaffeinate/decaffeinate/issues/904
Prior discussion around --enable-babel-constructor-workaround
: https://github.com/decaffeinate/decaffeinate/issues/1100
If anyone has feedback on this, let me know!
Issue Analytics
- State:
- Created 6 years ago
- Comments:5 (1 by maintainers)
Top GitHub Comments
that all sounds very reasonable. Thanks for the great project, it is tremendously helpful for us!
👍