New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Question: What impact does CoffeeScript 2 have on the decaffeinate project #1130

Closed
sudowork opened this Issue Jul 7, 2017 · 7 comments

Comments

Projects
None yet
2 participants
@sudowork
Copy link

sudowork commented Jul 7, 2017

First off, thanks so much for building this project! My team and I have recently just finished successfully decaffeinating all of our CoffeeScript code: ~200k lines of code, ~1400 files. So we appreciate this effort a lot.

I'm in the midst of writing a blog post detailing our process. One of the points I want to discuss is what the impact of CoffeeScript 2.x has. With the CS2 compiler outputting ES2016+/ES.next code, I was curious what your view is on how people should think about decaffeinate and the CS2 compiler.

Despite the output being somewhat comparable, the goals of the projects are of course completely different. One benefit of decaffeinate is the ability to preserve whitespace, formatting, etc. Another huge benefit is the tooling around with bulk-decaffeinate and integration with other tools like eslint, jscodeshift, etc.

Some differences I've taken note of so far:

  • Project goals
    • Decaffeinate: one-time full conversion into ES.next code
    • CS2: Interoperability with ES.next code but still keeping CoffeeScript
  • Maintaining whitespace, formatting
  • Handling of bound subclasses
    • Erroring/providing potential polyfill vs run-time error (in the latest coffeescript@2.0.0-beta3)
  • Behavior with polyfilling __guard__, __guardMethod__, __range__ vs inlining and declaring intermediary vars.
  • Other instances where I would argue that decaffeinate outputs code much more akin to what a human may have output.
  • Code architecture of the decaffeinate vs coffeescript projects
    • Multi-stage patchers vs compilation of AST
  • No need for source maps since the purpose is doing a final conversion to ES code
  • Probably many things I'm missing

Thoughts?

@sudowork

This comment has been minimized.

Copy link

sudowork commented Jul 7, 2017

Here's a contrived comparison of some example input/output:

CoffeeScript Input

class Foo extends Bar
  boundMethod: => console.log(42)

response?.body?.foo?.bar = 42
console.log(response?.body?.foo?.bar)
  
x in xs

if x in [0..100]
  console.log('foo')

CoffeeScript 2 output

var Foo, i, ref, ref1, ref2, ref3, results,
  boundMethodCheck = function(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new Error('Bound instance method accessed before binding'); } },
  indexOf = [].indexOf;

Foo = class Foo extends Bar {
  constructor() {
    super(...arguments);
    this.boundMethod = this.boundMethod.bind(this);
  }

  boundMethod() {
    boundMethodCheck(this, Foo);
    return console.log(42);
  }

};

if (typeof response !== "undefined" && response !== null) {
  if ((ref = response.body) != null) {
    if ((ref1 = ref.foo) != null) {
      ref1.bar = 42;
    }
  }
}

console.log(typeof response !== "undefined" && response !== null ? (ref2 = response.body) != null ? (ref3 = ref2.foo) != null ? ref3.bar : void 0 : void 0 : void 0);

indexOf.call(xs, x) >= 0;

if (indexOf.call((function() {
  results = [];
  for (i = 0; i <= 100; i++){ results.push(i); }
  return results;
}).apply(this), x) >= 0) {
  console.log('foo');
}

Decaffeinate output

NOTE: Run with --allow-invalid-constructors on due to the bound method.

class Foo extends Bar {
  constructor(...args) {
    this.boundMethod = this.boundMethod.bind(this);
    super(...args);
  }

  boundMethod() { return console.log(42); }
}

__guard__(__guard__(typeof response !== 'undefined' && response !== null ? response.body : undefined, x1 => x1.foo), x => x.bar = 42);
console.log(__guard__(__guard__(typeof response !== 'undefined' && response !== null ? response.body : undefined, x3 => x3.foo), x2 => x2.bar));

Array.from(xs).includes(x);

if (Array.from(__range__(0, 100, true)).includes(x)) {
  console.log('foo');
}
function __guard__(value, transform) {
  return (typeof value !== 'undefined' && value !== null) ? transform(value) : undefined;
}
function __range__(left, right, inclusive) {
  let range = [];
  let ascending = left < right;
  let end = !inclusive ? right : ascending ? right + 1 : right - 1;
  for (let i = left; ascending ? i < end : i > end; ascending ? i++ : i--) {
    range.push(i);
  }
  return range;
}
@alangpierce

This comment has been minimized.

Copy link
Member

alangpierce commented Jul 8, 2017

Hey @sudowork, thanks for sharing, really cool to hear that decaffeinate has been successfully used on such a big codebase!

Yeah, it's certainly interesting to compare decaffeinate with CS2. CS2 is a language implementation and decaffeinate is a codebase conversion tool, so you can expect the objectives are different even if the tools are similar in what they do. As I understand things, CS2 wants to generate new-style JavaScript so that the languages can be closer aligned. If JS comes out with some fancy new feature, CS wants to just compile to that feature rather than needing to re-implementing it. And someone familiar with JavaScript who wants to try out CoffeeScript should find it familiar, but with a cleaner syntax, and not feel like there's anything missing.

Your differences are pretty spot on. Some other things I might add:

  • Theoretically, decaffeinate doesn't need to care about correctness in obscure cases as much. For example, see How decaffeinate approaches correctness for a list of correctness issues that were intentionally not fixed in decaffeinate because they're obscure and fixing them would make regular code look worse. decaffeinate also has a --loose option (really a family of options) that focuses on better code at the expense of 100% correctness, which wouldn't make sense in CoffeeScript.
  • CoffeeScript (including CS2) has less motivation to make the output code high-quality and idiomatic. That said, it often is quite high-quality compared to most code generators, since it helps people understand the language and helps people debug through the JS. But CS tends to not implement special cases like #1024.
  • As of decaffeinate 3.0 (released a few days ago), decaffeinate now has a suggestions system that points out cleanup tasks that you might want to do, and includes a bunch of documentation on why the code looks a certain way. More generally, decaffeinate's position is in being a tool to help you with a one-time conversion. For example, decaffeinate might in the future have an interactive mode where it asks you questions about your code or prompts you with multiple options and has you pick the best-looking one (although I don't have any specific plans).
  • decaffeinate is quite a bit slower than the CoffeeScript compiler. Performance is a low priority since it's not meant to be run regularly.
  • CS2 has some relatively minor breaking changes, whereas decaffeinate wants to run on all valid CS1 code. For example, this meant CS2 could come up with a pretty good compromise for the bound subclass problem.
  • decaffeinate has a more well-defined scope; for example, decaffeinate would never add new CS language features.

Some other random thoughts:

  • The __guard__ thing is annoying, and something I'm hoping to improve; see #336 and #1103. It's mostly just the easiest thing I could do that works in all cases, rather than an intentional difference from CoffeeScript's output. Arguably decaffeinate should focus on streamlining manual cleanup rather than generating nice-looking code. For example, even leaving a comment with the original ?. usages might be useful.
  • I sometimes wonder if preserving formatting and whitespace in decaffeinate was a mistake. The patcher approach used by decaffeinate is relatively fragile and has let to a lot of complexity. It certainly has its advantages, but there are also some cases where decaffeinate generates poorly-formatted code that's later cleaned up by eslint. And more and more people are using prettier these days, which intentionally discards all custom formatting.

@alangpierce alangpierce added the question label Jul 9, 2017

@alangpierce

This comment has been minimized.

Copy link
Member

alangpierce commented Jul 9, 2017

Closing, but happy to continue the discussion if you want.

@alangpierce alangpierce closed this Jul 9, 2017

@sudowork

This comment has been minimized.

Copy link

sudowork commented Jul 10, 2017

Thanks for the thoughtful insights!

Arguably decaffeinate should focus on streamlining manual cleanup rather than generating nice-looking code. For example, even leaving a comment with the original ?. usages might be useful.

I think this makes a lot of sense. Throughout our cleanup process, There are a lot of times I've had to navigate back to the original CS as a faster way to figure out the intent of some code rather than try mentally parsing the converted code.

On the point of preserving whitespace, I personally have bought into prettier's anti-bikeshed philosophy. However, for our team, we ended up making a conscious decision not to run the converted code through prettier (at least not yet). I can see how going with an alternative approach to patching could make certain transformations and optimizations easier.

@sudowork

This comment has been minimized.

Copy link

sudowork commented Jul 18, 2017

Hey @alangpierce, thanks very much for your help earlier! I just published our blog post that goes over our process for decaffeinating our codebase: Decaffeinating a Large CoffeeScript Codebase Without Losing Sleep.

@alangpierce

This comment has been minimized.

Copy link
Member

alangpierce commented Jul 18, 2017

@sudowork nice! Really cool to see all of the details of your process, and I'll have to take a look at the codemods you published. At my work, we converted all tests a while ago, and have been slowly converting production code as we work with it, but are starting to run decaffeinate in larger and larger batches now, so it's nice to see more thoughts on how to run it on a large scale.

@sudowork

This comment has been minimized.

Copy link

sudowork commented Jul 18, 2017

Thanks! Some of the codemods were definitely targeted towards patterns we had in our CoffeeScript codebase, but I hope they're helpful :).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment