Repository: decaffeinate/decaffeinate
Pull request #1975 (2020-07-09 16:30:21)

fix: upgrade babel dependencies
Babel changed the way it handles calling `super` and decaffeinate's hack to allow `this` before `super` no longer works. Rather than trying to fix it, this commit just allows invalid constructors to pass through by default. This is a BREAKING CHANGE.


Patch:
diff --git a/README.md b/README.md
index 97e92b9d..0c5b6474 100644
--- a/README.md
+++ b/README.md
@@ -176,8 +176,6 @@ decaffeinate could be improved, feel free to file an issue on the [issues] page.
 - `--loose-includes`: Do not wrap in `Array.from` when converting `in` to `includes`.
 - `--loose-comparison-negation`: Allow unsafe simplifications like `!(a > b)` to `a <= b`.
 - `--loose-js-modules`: Allow named exports when converting to JS modules.
-- `--disable-babel-constructor-workaround`: Never include the Babel/TypeScript
-  workaround code to allow `this` before `super` in constructors.
 - `--disallow-invalid-constructors`: Give an error when constructors use `this`
   before `super` or omit the `super` call in a subclass.
 
diff --git a/docs/suggestions.md b/docs/suggestions.md
index ae432a46..f0423a2d 100644
--- a/docs/suggestions.md
+++ b/docs/suggestions.md
@@ -8,11 +8,12 @@ about the JavaScript produced by decaffeinate and how to clean it up without
 introducing bugs.
 
 There are two ways to use this list:
-* After running decaffeinate, each resulting JavaScript file has a list of
+
+- After running decaffeinate, each resulting JavaScript file has a list of
   suggestion codes with cleanup suggestions relevant to that file. You can jump
   to the docs for these specific suggestions, since they will be most useful to
   you.
-* If you're trying to run decaffeinate on a large codebase, it's probably a good
+- If you're trying to run decaffeinate on a large codebase, it's probably a good
   idea to read all points in this list to get an understanding of what cleanup
   steps will be required.
 
@@ -26,7 +27,8 @@ Generated using https://tableofcontents.herokuapp.com/
 -->
 
 - [DS0XX: Highest priority](#ds0xx-highest-priority)
-  - [DS001: Remove Babel/TypeScript constructor workaround](#ds001-remove-babeltypescript-constructor-workaround)
+  - ~~[DS001: Remove Babel/TypeScript constructor workaround](#ds001-remove-babeltypescript-constructor-workaround)~~
+  - [DS002: Fix invalid constructor](#ds002-fix-invalid-constructor)
 - [DS1XX: Common cleanup tasks](#ds1xx-common-cleanup-tasks)
   - [DS101: Remove unnecessary use of `Array.from`](#ds101-remove-unnecessary-use-of-arrayfrom)
   - [DS102: Remove unnecessary code created because of implicit returns](#ds102-remove-unnecessary-code-created-because-of-implicit-returns)
@@ -51,7 +53,9 @@ Generated using https://tableofcontents.herokuapp.com/
 
 # DS0XX: Highest priority
 
-## DS001: Remove Babel/TypeScript constructor workaround
+## ~~DS001: Remove Babel/TypeScript constructor workaround~~
+
+> This workaround was removed, so this is here only for historical documentation purposes.
 
 ### Overview
 
@@ -67,6 +71,7 @@ so it's best seen as an ugly hack that you should remove as soon as you can.
 ### Example
 
 #### CoffeeScript
+
 ```coffee
 class FriendlyPerson extends Person
   handleGreeting: =>
@@ -75,13 +80,18 @@ class FriendlyPerson extends Person
 ```
 
 #### JavaScript after decaffeinate
+
 ```js
 class FriendlyPerson extends Person {
   constructor(...args) {
     {
       // Hack: trick babel into allowing this before super.
-      if (false) { super(); }
-      let thisFn = (() => { this; }).toString();
+      if (false) {
+        super();
+      }
+      let thisFn = (() => {
+        this;
+      }).toString();
       let thisName = thisFn.slice(thisFn.indexOf('{') + 1, thisFn.indexOf(';')).trim();
       eval(`${thisName} = this;`);
     }
@@ -96,6 +106,7 @@ class FriendlyPerson extends Person {
 ```
 
 #### Cleaner JavaScript (may not be equivalent)
+
 ```js
 class FriendlyPerson extends Person {
   constructor(...args) {
@@ -126,6 +137,70 @@ If you prefer, you can run decaffeinate with
 `--disable-babel-constructor-workaround` or `--disallow-invalid-constructors` to
 avoid generating the workaround code.
 
+## DS002: Fix invalid constructor
+
+### Overview
+
+CoffeeScript classes allow `this` to be used before `super` in a subclass
+constructor, while JavaScript classes do not. Decaffeinate used to produce code
+to work around this issue, but no longer does. **It's up to you to fix this or
+your code will not work**. Again, the code produced is _invalid_ and must be
+fixed.
+
+### Example
+
+#### CoffeeScript
+
+```coffee
+class FriendlyPerson extends Person
+  handleGreeting: =>
+    @respond('Hello!')
+    return
+```
+
+#### JavaScript after decaffeinate
+
+```js
+class FriendlyPerson extends Person {
+  constructor(...args) {
+    this.handleGreeting = this.handleGreeting.bind(this);
+    super(...args);
+  }
+
+  handleGreeting() {
+    this.respond('Hello!');
+  }
+}
+```
+
+#### Cleaner JavaScript (may not be equivalent)
+
+```js
+class FriendlyPerson extends Person {
+  constructor(...args) {
+    super(...args);
+    this.handleGreeting = this.handleGreeting.bind(this);
+  }
+
+  handleGreeting() {
+    this.respond('Hello!');
+  }
+}
+```
+
+### How to address
+
+In many cases, it is safe to simply move the `this` usage after the `super`
+call. If no `super` call was present, you should instead add a `super` call
+before all `this` usages. However, you should watch out for any work that is
+done during the super call and make sure that it is ok for the class fields to
+not be assigned yet. In some frameworks like Backbone, the `super` call does
+significant work, and you may need to rewrite some of your code to ensure that
+method bindings and field assignments are done before they are used.
+
+If you prefer, you can run decaffeinate with `--disallow-invalid-constructors` to
+avoid generating invalid code.
+
 # DS1XX: Common cleanup tasks
 
 ## DS101: Remove unnecessary use of `Array.from`
@@ -142,7 +217,7 @@ and JS `for-of` loops, destructuring, and array spread all require the target to
 be iterable. decaffeinate also produces code using array methods like `map`,
 `filter`, and `includes`.
 
-In *most* cases in real-world code, these array operations are done on regular
+In _most_ cases in real-world code, these array operations are done on regular
 JavaScript arrays (which are both array-like and iterable), but some values like
 strings, jQuery collections, FileList, and NodeList are array-like but are
 either not iterable or do not contain all array methods. To correctly handle
@@ -153,6 +228,7 @@ in many array operations to convert the value to an actual array if necessary.
 ### Example
 
 #### CoffeeScript
+
 ```coffee
 for lion in lions
   lion.roar()
@@ -163,6 +239,7 @@ eat(avocados...)
 ```
 
 #### JavaScript after decaffeinate
+
 ```js
 for (let lion of Array.from(lions)) {
   lion.roar();
@@ -175,6 +252,7 @@ eat(...Array.from(avocados || []));
 ```
 
 #### Cleaner JavaScript (may not be equivalent)
+
 ```js
 for (let lion of lions) {
   lion.roar();
@@ -221,6 +299,7 @@ is actually used, so it always mimics CoffeeScript's behavior.
 ### Example
 
 #### CoffeeScript
+
 ```coffee
 greetFriends = (people) ->
   for person in people
@@ -229,6 +308,7 @@ greetFriends = (people) ->
 ```
 
 #### JavaScript after decaffeinate
+
 ```js
 let greetFriends = people =>
   (() => {
@@ -241,11 +321,11 @@ let greetFriends = people =>
       }
     }
     return result;
-  })()
-;
+  })();
 ```
 
 #### Cleaner JavaScript (may not be equivalent)
+
 ```js
 function greetFriends(people) {
   for (const person of people) {
@@ -266,7 +346,7 @@ accidentally be relying on an unintentional implicit return value, so you should
 be careful to check all usages to make sure the return value is actually never
 used.
 
-Alternatively, you can try to add *explicit* returns to your code before running
+Alternatively, you can try to add _explicit_ returns to your code before running
 it through decaffeinate. If a function has `return` as its last statement, then
 decaffeinate will make the function return no value and remove the `return` from
 the JavaScript code since it isn't necessary. One way to detect and fix implicit
@@ -289,6 +369,7 @@ to implement the conditional evaluation behavior.
 ### Example
 
 #### CoffeeScript
+
 ```coffee
 cleanUp = (apartment) ->
   apartment?.getBathroom()?.shower.scrub()
@@ -296,16 +377,18 @@ cleanUp = (apartment) ->
 ```
 
 #### JavaScript after decaffeinate
+
 ```js
 let cleanUp = function(apartment) {
   __guard__(apartment != null ? apartment.getBathroom() : undefined, x => x.shower.scrub());
 };
 function __guard__(value, transform) {
-  return (typeof value !== 'undefined' && value !== null) ? transform(value) : undefined;
+  return typeof value !== 'undefined' && value !== null ? transform(value) : undefined;
 }
 ```
 
 #### Cleaner JavaScript (may not be equivalent)
+
 ```js
 function cleanUp(apartment) {
   const bathroom = apartment && apartment.getBathroom();
@@ -327,14 +410,14 @@ string.
 In plain JavaScript, usually the cleanest approach is `&&`, sometimes combined
 with intermediate variables. Some external libraries also help:
 
-* [_.get](https://lodash.com/docs/4.17.4#get) from Lodash provides a similar
+- [\_.get](https://lodash.com/docs/4.17.4#get) from Lodash provides a similar
   (although less powerful) mechanism for safe nested access.
-* Facebook has an open source project called [idx](https://github.com/facebookincubator/idx)
+- Facebook has an open source project called [idx](https://github.com/facebookincubator/idx)
   that uses exceptions to implement safe nested access in plain JS and includes
   a Babel transform to translate the code to not use exceptions. See more
   information in the
   [announcement blog post](https://facebook.github.io/react-native/blog/2017/03/13/idx-the-existential-function.html).
-* There is a language proposal called
+- There is a language proposal called
   [Optional Chaining for JavaScript](https://github.com/tc39/proposal-optional-chaining)
   that adds a `?.` operator in JavaScript. As of this writing, the proposal is
   in relatively early stages, but has a
@@ -354,17 +437,20 @@ to read, and the variable name is auto-generated by decaffeinate.
 ### Example
 
 #### CoffeeScript
+
 ```coffee
 accounts[getAccountId()] //= splitFactor
 ```
 
 #### JavaScript after decaffeinate
+
 ```js
 let name;
-accounts[name = getAccountId()] = Math.floor(accounts[name] / splitFactor);
+accounts[(name = getAccountId())] = Math.floor(accounts[name] / splitFactor);
 ```
 
 #### Cleaner JavaScript
+
 ```js
 const accountId = getAccountId();
 accounts[accountId] = Math.floor(accounts[accountId] / splitFactor);
@@ -392,6 +478,7 @@ instead.
 ### Example
 
 #### CoffeeScript
+
 ```coffee
 guessPosition = (pos = 0) ->
   {grid = []} = board
@@ -402,10 +489,14 @@ guessPosition = (pos = 0) ->
 ```
 
 #### JavaScript after decaffeinate
+
 ```js
 let guessPosition = function(pos) {
-  if (pos == null) { pos = 0; }
-  let val = board.grid, grid = val != null ? val : [];
+  if (pos == null) {
+    pos = 0;
+  }
+  let val = board.grid,
+    grid = val != null ? val : [];
   if (grid[pos] === 'ship') {
     return 'You sunk my battleship.';
   } else {
@@ -415,9 +506,10 @@ let guessPosition = function(pos) {
 ```
 
 #### Cleaner JavaScript (may not be equivalent)
+
 ```js
 function guessPosition(pos = 0) {
-  const {grid = []} = board;
+  const { grid = [] } = board;
   if (grid[pos] === 'ship') {
     return 'You sunk my battleship.';
   } else {
@@ -447,9 +539,10 @@ decaffeinate to automatically use default params in the generated code.
 Nested destructure operations can in some cases be converted safely to
 JavaScript as a destructure operation, but in some complex cases, JavaScript
 assignment syntax is incorrect or not flexible enough:
-* Array destructures.
-* Intermediate expansion/rest nodes (`...` in the middle of an array destructure).
-* Default values.
+
+- Array destructures.
+- Intermediate expansion/rest nodes (`...` in the middle of an array destructure).
+- Default values.
 
 In these cases, decaffeinate reimplements the more straightforward algorithm
 using repeated assignments.
@@ -457,11 +550,13 @@ using repeated assignments.
 ### Example
 
 #### CoffeeScript
+
 ```coffee
 {left: {right: {left: {left: {right: [firstValue, ..., lastValue]}}}}} = tree;
 ```
 
 #### JavaScript after decaffeinate
+
 ```js
 let obj = tree.left,
   obj1 = obj.right,
@@ -473,8 +568,17 @@ let obj = tree.left,
 ```
 
 #### Cleaner JavaScript
+
 ```js
-const {left: {right: {left: {left: {right: values}}}}} = tree;
+const {
+  left: {
+    right: {
+      left: {
+        left: { right: values }
+      }
+    }
+  }
+} = tree;
 const firstValue = values[0];
 const lastValue = values[values.length - 1];
 ```
@@ -498,19 +602,26 @@ much more verbose than they need to be.
 ### Example
 
 #### CoffeeScript
+
 ```coffee
 for guess in [firstGuess..lastGuess]
   makeGuess(guess)
 ```
 
 #### JavaScript after decaffeinate
+
 ```js
-for (let guess = firstGuess, end = lastGuess, asc = firstGuess <= end; asc ? guess <= end : guess >= end; asc ? guess++ : guess--) {
+for (
+  let guess = firstGuess, end = lastGuess, asc = firstGuess <= end;
+  asc ? guess <= end : guess >= end;
+  asc ? guess++ : guess--
+) {
   makeGuess(guess);
 }
 ```
 
 #### Cleaner JavaScript (may not be equivalent)
+
 ```js
 for (let guess = firstGuess; guess <= lastGuess; guess++) {
   makeGuess(guess);
@@ -536,12 +647,14 @@ decaffeinate inserts `|| {}` to handle that case.
 ### Example
 
 #### CoffeeScript
+
 ```coffee
 for own name of teamMembers
   console.log "Great work, #{name}!"
 ```
 
 #### JavaScript after decaffeinate
+
 ```js
 for (let name of Object.keys(teamMembers || {})) {
   console.log(`Great work, ${name}!`);
@@ -549,6 +662,7 @@ for (let name of Object.keys(teamMembers || {})) {
 ```
 
 #### Cleaner JavaScript (may not be equivalent)
+
 ```js
 for (let name of Object.keys(teamMembers)) {
   console.log(`Great work, ${name}!`);
@@ -573,20 +687,23 @@ left-hand side to a variable that gets evaluated first.
 ### Example
 
 #### CoffeeScript
+
 ```coffee
 if getLime() in getCoconut()
   drinkThemBoth()
 ```
 
 #### JavaScript after decaffeinate
+
 ```js
 let needle;
-if ((needle = getLime(), Array.from(getCoconut()).includes(needle))) {
+if (((needle = getLime()), Array.from(getCoconut()).includes(needle))) {
   drinkThemBoth();
 }
 ```
 
 #### Cleaner JavaScript (may not be equivalent)
+
 ```js
 if (getCoconut().includes(getLime())) {
   drinkThemBoth();
@@ -612,6 +729,7 @@ read.
 ### Example
 
 #### CoffeeScript
+
 ```coffee
 eat(switch desiredFlavor()
   when 'sweet'
@@ -623,18 +741,24 @@ eat(switch desiredFlavor()
 ```
 
 #### JavaScript after decaffeinate
+
 ```js
-eat((() => { switch (desiredFlavor()) {
-  case 'sweet':
-    return 'ice cream';
-  case 'savory':
-    return 'grilled cheese';
-  default:
-    return 'water';
-} })());
+eat(
+  (() => {
+    switch (desiredFlavor()) {
+      case 'sweet':
+        return 'ice cream';
+      case 'savory':
+        return 'grilled cheese';
+      default:
+        return 'water';
+    }
+  })()
+);
 ```
 
 #### Cleaner JavaScript
+
 ```js
 switch (desiredFlavor()) {
   case 'sweet':
@@ -668,6 +792,7 @@ code.
 ### Example
 
 #### CoffeeScript
+
 ```coffee
 class Circle
   PI = 3.14159265358979
@@ -678,6 +803,7 @@ class Circle
 ```
 
 #### JavaScript after decaffeinate
+
 ```js
 var Circle = (function() {
   let PI = undefined;
@@ -699,6 +825,7 @@ var Circle = (function() {
 ```
 
 #### Cleaner JavaScript (may not be equivalent)
+
 ```js
 const PI = 3.14159265358979;
 class Circle {
@@ -717,13 +844,14 @@ class Circle {
 ### How to address
 
 Many use cases of code within class bodies can be replaced by other mechanisms:
-* Constants used within the class can usually be pulled out as external
+
+- Constants used within the class can usually be pulled out as external
   constants.
-* Nested classes can be pulled out into multiple classes at the top level.
-* Prototype field assignments can often (but not always) be replaced by an
+- Nested classes can be pulled out into multiple classes at the top level.
+- Prototype field assignments can often (but not always) be replaced by an
   assignment in the constructor. Note that assigning in the constructor creates
-  a property *on the instance*, while assigning on the prototype creates a
-  single shared property *used by all instances*. The
+  a property _on the instance_, while assigning on the prototype creates a
+  single shared property _used by all instances_. The
   [class fields](https://github.com/tc39/proposal-class-fields) language
   proposal adds a new syntax equivalent to assigning within the constructor, so
   it's not exactly the same as CoffeeScript class fields, but behaves similarly.
@@ -742,12 +870,14 @@ often it is preferable to simply use the value as a boolean.
 ### Example
 
 #### CoffeeScript
+
 ```coffee
 if currentUser()?
   sendMessage(customMessage() ? 'Hello')
 ```
 
 #### JavaScript after decaffeinate
+
 ```js
 if (currentUser() != null) {
   let left;
@@ -756,6 +886,7 @@ if (currentUser() != null) {
 ```
 
 #### Cleaner JavaScript (may not be equivalent)
+
 ```js
 if (currentUser()) {
   sendMessage(customMessage() || 'Hello');
@@ -774,9 +905,10 @@ expected.
 ### Overview
 
 Top-level `this` in JavaScript can mean several things, depending on context:
-* In the browser, it refers to `window`.
-* In Node.js, it refers to `module.exports`.
-* In JS modules, it is never defined.
+
+- In the browser, it refers to `window`.
+- In Node.js, it refers to `module.exports`.
+- In JS modules, it is never defined.
 
 decaffeinate is unopinionated and produces JavaScript code that still uses
 `this` at the top level, but Babel converts all top-level `this` usages to
diff --git a/src/cli.ts b/src/cli.ts
index 0c6d1583..c2c88c58 100644
--- a/src/cli.ts
+++ b/src/cli.ts
@@ -88,7 +88,7 @@ function parseArguments(args: Array<string>): CLIOptions {
         break;
 
       case '--disable-babel-constructor-workaround':
-        baseOptions.disableBabelConstructorWorkaround = true;
+        console.warn(arg, 'no longer has any effect as it is the only supported behavior');
         break;
 
       case '--disallow-invalid-constructors':
@@ -131,7 +131,7 @@ function parseArguments(args: Array<string>): CLIOptions {
         break;
 
       case '--allow-invalid-constructors':
-        baseOptions.disableBabelConstructorWorkaround = true;
+        baseOptions.disallowInvalidConstructors = false;
         break;
 
       default:
@@ -273,9 +273,6 @@ function usage(): void {
   console.log('  --loose-includes         Do not wrap in Array.from when converting in to includes.');
   console.log('  --loose-comparison-negation');
   console.log('                           Allow unsafe simplifications like `!(a > b)` to `a <= b`.');
-  console.log('  --disable-babel-constructor-workaround');
-  console.log('                           Never include the Babel/TypeScript workaround code to allow');
-  console.log('                           this before super in constructors.');
   console.log('  --disallow-invalid-constructors');
   console.log('                           Give an error when constructors use this before super or');
   console.log('                           omit the super call in a subclass.');
diff --git a/src/options.ts b/src/options.ts
index 2693ae16..9d67e518 100644
--- a/src/options.ts
+++ b/src/options.ts
@@ -15,7 +15,6 @@ export interface Options {
   looseForOf?: boolean;
   looseIncludes?: boolean;
   looseComparisonNegation?: boolean;
-  disableBabelConstructorWorkaround?: boolean;
   disallowInvalidConstructors?: boolean;
 }
 
@@ -36,7 +35,6 @@ export const DEFAULT_OPTIONS: Options = {
   looseForOf: false,
   looseIncludes: false,
   looseComparisonNegation: false,
-  disableBabelConstructorWorkaround: false,
   disallowInvalidConstructors: false
 };
 
diff --git a/src/stages/main/patchers/ClassBlockPatcher.ts b/src/stages/main/patchers/ClassBlockPatcher.ts
index cb996f69..8e6c48e4 100644
--- a/src/stages/main/patchers/ClassBlockPatcher.ts
+++ b/src/stages/main/patchers/ClassBlockPatcher.ts
@@ -1,8 +1,6 @@
 import { Node } from 'decaffeinate-parser/dist/nodes';
 import { PatchOptions } from '../../../patchers/types';
-import { REMOVE_BABEL_WORKAROUND } from '../../../suggestions';
 import adjustIndent from '../../../utils/adjustIndent';
-import babelConstructorWorkaroundLines from '../../../utils/babelConstructorWorkaroundLines';
 import getBindingCodeForMethod from '../../../utils/getBindingCodeForMethod';
 import getInvalidConstructorErrorMessage from '../../../utils/getInvalidConstructorErrorMessage';
 import { PatcherClass } from './../../../patchers/NodePatcher';
@@ -10,6 +8,7 @@ import BlockPatcher from './BlockPatcher';
 import ClassAssignOpPatcher from './ClassAssignOpPatcher';
 import ClassPatcher from './ClassPatcher';
 import ConstructorPatcher from './ConstructorPatcher';
+import { FIX_INVALID_CONSTRUCTOR } from '../../../suggestions';
 
 export default class ClassBlockPatcher extends BlockPatcher {
   static patcherClassForChildNode(node: Node, property: string): PatcherClass | null {
@@ -34,6 +33,8 @@ export default class ClassBlockPatcher extends BlockPatcher {
           throw this.error(
             getInvalidConstructorErrorMessage('Cannot automatically convert a subclass that uses bound methods.')
           );
+        } else if (isSubclass) {
+          this.addSuggestion(FIX_INVALID_CONSTRUCTOR);
         }
 
         const { source } = this.context;
@@ -43,11 +44,6 @@ export default class ClassBlockPatcher extends BlockPatcher {
         let constructor = '';
         if (isSubclass) {
           constructor += `constructor(...args) {\n`;
-          if (this.shouldEnableBabelWorkaround()) {
-            for (const line of babelConstructorWorkaroundLines) {
-              constructor += `${methodBodyIndent}${line}\n`;
-            }
-          }
         } else {
           constructor += `constructor() {\n`;
         }
@@ -67,14 +63,6 @@ export default class ClassBlockPatcher extends BlockPatcher {
     return !this.options.disallowInvalidConstructors;
   }
 
-  shouldEnableBabelWorkaround(): boolean {
-    const shouldEnable = !this.options.disableBabelConstructorWorkaround;
-    if (shouldEnable) {
-      this.addSuggestion(REMOVE_BABEL_WORKAROUND);
-    }
-    return shouldEnable;
-  }
-
   getClassPatcher(): ClassPatcher {
     if (!(this.parent instanceof ClassPatcher)) {
       throw this.error('Expected class block parent to be a class.');
diff --git a/src/stages/main/patchers/ConstructorPatcher.ts b/src/stages/main/patchers/ConstructorPatcher.ts
index eb03122e..8bcd56e8 100644
--- a/src/stages/main/patchers/ConstructorPatcher.ts
+++ b/src/stages/main/patchers/ConstructorPatcher.ts
@@ -1,8 +1,7 @@
 import { Class, This } from 'decaffeinate-parser/dist/nodes';
 import NodePatcher from '../../../patchers/NodePatcher';
 import { PatcherContext, PatchOptions } from '../../../patchers/types';
-import { REMOVE_BABEL_WORKAROUND } from '../../../suggestions';
-import babelConstructorWorkaroundLines from '../../../utils/babelConstructorWorkaroundLines';
+import { FIX_INVALID_CONSTRUCTOR } from '../../../suggestions';
 import containsDescendant from '../../../utils/containsDescendant';
 import containsSuperCall from '../../../utils/containsSuperCall';
 import getBindingCodeForMethod from '../../../utils/getBindingCodeForMethod';
@@ -53,9 +52,6 @@ export default class ConstructorPatcher extends ObjectBodyMemberPatcher {
 
   getLinesToInsert(): Array<string> {
     let lines: Array<string> = [];
-    if (this.shouldAddBabelWorkaround()) {
-      lines = lines.concat(babelConstructorWorkaroundLines);
-    }
     lines = lines.concat(this.getBindings());
     return lines;
   }
@@ -65,23 +61,15 @@ export default class ConstructorPatcher extends ObjectBodyMemberPatcher {
    * call or uses `this` before `super`.
    */
   checkForConstructorErrors(): void {
-    if (!this.options.disallowInvalidConstructors) {
-      return;
-    }
-
     const errorMessage = this.getInvalidConstructorMessage();
-    if (errorMessage) {
-      throw this.error(getInvalidConstructorErrorMessage(errorMessage));
-    }
-  }
 
-  shouldAddBabelWorkaround(): boolean {
-    const shouldEnable =
-      !this.options.disableBabelConstructorWorkaround && this.getInvalidConstructorMessage() !== null;
-    if (shouldEnable) {
-      this.addSuggestion(REMOVE_BABEL_WORKAROUND);
+    if (errorMessage) {
+      if (!this.options.disallowInvalidConstructors) {
+        this.addSuggestion(FIX_INVALID_CONSTRUCTOR);
+      } else {
+        throw this.error(getInvalidConstructorErrorMessage(errorMessage));
+      }
     }
-    return shouldEnable;
   }
 
   /**
diff --git a/src/suggestions.ts b/src/suggestions.ts
index 609901a7..cde3598f 100644
--- a/src/suggestions.ts
+++ b/src/suggestions.ts
@@ -3,9 +3,9 @@ export interface Suggestion {
   message: string;
 }
 
-export const REMOVE_BABEL_WORKAROUND = {
-  suggestionCode: 'DS001',
-  message: 'Remove Babel/TypeScript constructor workaround'
+export const FIX_INVALID_CONSTRUCTOR = {
+  suggestionCode: 'DS002',
+  message: 'Fix invalid constructor'
 };
 
 export const REMOVE_ARRAY_FROM = {
diff --git a/src/utils/babelConstructorWorkaroundLines.ts b/src/utils/babelConstructorWorkaroundLines.ts
deleted file mode 100644
index 63afd994..00000000
--- a/src/utils/babelConstructorWorkaroundLines.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-/**
- * A code snippet that can be placed at the top of a constructor to allow the
- * constructor to use `this` before `super`, at least when run through Babel or
- * TypeScript.
- *
- * This makes use of two techniques:
- * - Babel does a static analysis check to make sure that all `this` accesses
- *   at least have a chance of happening after the first `super` call. We can
- *   wrap a super call in an `if (false)` at the top to silence this check
- *   without changing the runtime behavior (and later super calls will still
- *   work).
- * - Babel compiles `this` usages in constructors to a separate variable,
- *   usually called `_this`, which gets assigned in the `super` line. However,
- *   the assignment to `_this` only happens when `super` actually runs, so it
- *   will normally be undefined before the `super` call. We can make `_this`
- *   resolve to `this` before the constructor by running `eval('_this = this;')`
- *   to escape Babel's rewriting. However, the variable is not always called
- *   `_this`. We can still get the right variable name, though, but making an
- *   arrow function using `this`, calling `toString`, and parsing the variable
- *   name from it. Additionally, the reference to `_this` may be wrapped in an
- *   assertion-possibly multiple times-so we strip that out too if present.
- */
-export default [
-  '{',
-  '  // Hack: trick Babel/TypeScript into allowing this before super.',
-  '  if (false) { super(); }',
-  '  let thisFn = (() => { return this; }).toString();',
-  '  let thisName = thisFn.match(/return (?:_assertThisInitialized\\()*(\\w+)\\)*;/)[1];',
-  '  eval(`${thisName} = this;`);',
-  '}'
-];


Test patch:
diff --git a/test/class_test.ts b/test/class_test.ts
index 50dd963e..4c747eca 100644
--- a/test/class_test.ts
+++ b/test/class_test.ts
@@ -87,12 +87,7 @@ describe('classes', () => {
       (class extends Parent {
         constructor() {}
       });
-    `,
-      {
-        options: {
-          disableBabelConstructorWorkaround: true
-        }
-      }
+    `
     );
   });
 
@@ -303,7 +298,7 @@ describe('classes', () => {
       );
     });
 
-    it('disables the babel workaround when using this before super in a constructor', () => {
+    it('passes invalid this-before-super uses through', () => {
       checkCS1(
         `
         class A extends B
@@ -318,16 +313,11 @@ describe('classes', () => {
             super(...arguments);
           }
         }
-      `,
-        {
-          options: {
-            disableBabelConstructorWorkaround: true
-          }
-        }
+      `
       );
     });
 
-    it('disables the babel workaround when a subclass constructor omits super', () => {
+    it('passes invalid no-super constructors through', () => {
       check(
         `
         class A extends B
@@ -340,12 +330,7 @@ describe('classes', () => {
             this.a = 2;
           }
         }
-      `,
-        {
-          options: {
-            disableBabelConstructorWorkaround: true
-          }
-        }
+      `
       );
     });
 
@@ -367,12 +352,7 @@ describe('classes', () => {
           return 1;
         }
       }
-    `,
-        {
-          options: {
-            disableBabelConstructorWorkaround: true
-          }
-        }
+    `
       );
     });
 
@@ -399,16 +379,11 @@ describe('classes', () => {
           this.b = 2;
         }
       }
-    `,
-        {
-          options: {
-            disableBabelConstructorWorkaround: true
-          }
-        }
+    `
       );
     });
 
-    it('generates workaround code when using this before super in a constructor', () => {
+    it('allows this before super', () => {
       checkCS1(
         `
         class A extends B
@@ -419,13 +394,6 @@ describe('classes', () => {
         `
         class A extends B {
           constructor() {
-            {
-              // Hack: trick Babel/TypeScript into allowing this before super.
-              if (false) { super(); }
-              let thisFn = (() => { return this; }).toString();
-              let thisName = thisFn.match(/return (?:_assertThisInitialized\\()*(\\w+)\\)*;/)[1];
-              eval(\`\${thisName} = this;\`);
-            }
             this.a = 2;
             super(...arguments);
           }
@@ -434,7 +402,7 @@ describe('classes', () => {
       );
     });
 
-    it('generates workaround code when a subclass constructor omits super', () => {
+    it('passes invalid code through when a subclass constructor omits super', () => {
       check(
         `
         class A extends B
@@ -444,13 +412,6 @@ describe('classes', () => {
         `
         class A extends B {
           constructor() {
-            {
-              // Hack: trick Babel/TypeScript into allowing this before super.
-              if (false) { super(); }
-              let thisFn = (() => { return this; }).toString();
-              let thisName = thisFn.match(/return (?:_assertThisInitialized\\()*(\\w+)\\)*;/)[1];
-              eval(\`\${thisName} = this;\`);
-            }
             this.a = 2;
           }
         }
@@ -458,36 +419,7 @@ describe('classes', () => {
       );
     });
 
-    it('generates workaround code when a subclass has a bound method and no constructor', () => {
-      check(
-        `
-        class A extends B
-          foo: =>
-            null
-      `,
-        `
-        class A extends B {
-          constructor(...args) {
-            {
-              // Hack: trick Babel/TypeScript into allowing this before super.
-              if (false) { super(); }
-              let thisFn = (() => { return this; }).toString();
-              let thisName = thisFn.match(/return (?:_assertThisInitialized\\()*(\\w+)\\)*;/)[1];
-              eval(\`\${thisName} = this;\`);
-            }
-            this.foo = this.foo.bind(this);
-            super(...args);
-          }
-        
-          foo() {
-            return null;
-          }
-        }
-      `
-      );
-    });
-
-    it('generates workaround code when a subclass has a bound method and a normal constructor', () => {
+    it('passes invalid code through when a subclass has a bound method and a normal constructor', () => {
       checkCS1(
         `
         class A extends B
@@ -501,13 +433,6 @@ describe('classes', () => {
         `
         class A extends B {
           constructor() {
-            {
-              // Hack: trick Babel/TypeScript into allowing this before super.
-              if (false) { super(); }
-              let thisFn = (() => { return this; }).toString();
-              let thisName = thisFn.match(/return (?:_assertThisInitialized\\()*(\\w+)\\)*;/)[1];
-              eval(\`\${thisName} = this;\`);
-            }
             this.foo = this.foo.bind(this);
             super(...arguments);
             this.x = 3;
@@ -521,7 +446,7 @@ describe('classes', () => {
       );
     });
 
-    it('generates workaround code when a subclass has a bound method and an empty constructor', () => {
+    it('passes invalid code through when a subclass has a bound method and an empty constructor', () => {
       check(
         `
         class A extends B
@@ -533,13 +458,6 @@ describe('classes', () => {
         `
         class A extends B {
           constructor() {
-            {
-              // Hack: trick Babel/TypeScript into allowing this before super.
-              if (false) { super(); }
-              let thisFn = (() => { return this; }).toString();
-              let thisName = thisFn.match(/return (?:_assertThisInitialized\\()*(\\w+)\\)*;/)[1];
-              eval(\`\${thisName} = this;\`);
-            }
             this.foo = this.foo.bind(this);
           }
         
@@ -551,52 +469,6 @@ describe('classes', () => {
       );
     });
 
-    it('does not generate workaround code when the workaround is unnecessary', () => {
-      checkCS1(
-        `
-        class A extends B
-          constructor: ->
-            super
-            @x = 3
-      `,
-        `
-        class A extends B {
-          constructor() {
-            super(...arguments);
-            this.x = 3;
-          }
-        }
-      `
-      );
-    });
-
-    it('properly generates workaround code when constructors have default parameters', () => {
-      checkCS1(
-        `
-        class A extends B
-          constructor: (c={}) ->
-            @d = e
-            super
-      `,
-        `
-        class A extends B {
-          constructor(c) {
-            {
-              // Hack: trick Babel/TypeScript into allowing this before super.
-              if (false) { super(); }
-              let thisFn = (() => { return this; }).toString();
-              let thisName = thisFn.match(/return (?:_assertThisInitialized\\()*(\\w+)\\)*;/)[1];
-              eval(\`\${thisName} = this;\`);
-            }
-            if (c == null) { c = {}; }
-            this.d = e;
-            super(...arguments);
-          }
-        }
-      `
-      );
-    });
-
     it('chooses variables that do not conflict', () => {
       check(
         `
@@ -724,12 +596,7 @@ describe('classes', () => {
       class A extends B {
         constructor() {}
       }
-    `,
-      {
-        options: {
-          disableBabelConstructorWorkaround: true
-        }
-      }
+    `
     );
   });
 
@@ -744,12 +611,7 @@ describe('classes', () => {
       class A extends (B = class B extends C {}) {
         constructor() {}
       }
-    `,
-      {
-        options: {
-          disableBabelConstructorWorkaround: true
-        }
-      }
+    `
     );
   });
 
@@ -1192,12 +1054,7 @@ describe('classes', () => {
       
         add() {}
       }
-    `,
-      {
-        options: {
-          disableBabelConstructorWorkaround: true
-        }
-      }
+    `
     );
   });
 
@@ -1269,12 +1126,7 @@ describe('classes', () => {
       
         add() {}
       }
-    `,
-      {
-        options: {
-          disableBabelConstructorWorkaround: true
-        }
-      }
+    `
     );
   });
 
@@ -2312,8 +2164,8 @@ describe('classes', () => {
     );
   });
 
-  it('produces a Babel/TS constructor hack that allows this before super in a constructor and produces correct values', () => {
-    validateCS1(
+  it('does not allow constructors that reference this before super', () => {
+    assertError(
       `
     class A
       constructor: ->
@@ -2324,11 +2176,9 @@ describe('classes', () => {
       constructor: ->
         @value = 3
         super
-    b = new B()
-    setResult(b.value)
   `,
-      4,
-      { options: {}, skipNodeCheck: true }
+      'Cannot automatically convert a subclass with a constructor that uses `this` before `super`.',
+      { disallowInvalidConstructors: true }
     );
   });
 });
diff --git a/test/cli_test.ts b/test/cli_test.ts
index 3bcf5f01..334547ab 100644
--- a/test/cli_test.ts
+++ b/test/cli_test.ts
@@ -319,9 +319,9 @@ describe('decaffeinate CLI', () => {
     );
   });
 
-  it('adds the Babel constructor workaround by default', () => {
+  it('treats the --disable-babel-constructor-workaround option as a no-op', () => {
     runCli(
-      '',
+      '--disable-babel-constructor-workaround',
       `
       class A extends B
         constructor: ->
@@ -331,18 +331,11 @@ describe('decaffeinate CLI', () => {
       `
       /*
        * decaffeinate suggestions:
-       * DS001: Remove Babel/TypeScript constructor workaround
+       * DS002: Fix invalid constructor
        * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
        */
       class A extends B {
         constructor() {
-          {
-            // Hack: trick Babel/TypeScript into allowing this before super.
-            if (false) { super(); }
-            let thisFn = (() => { return this; }).toString();
-            let thisName = thisFn.match(/return (?:_assertThisInitialized\\()*(\\w+)\\)*;/)[1];
-            eval(\`$\{thisName} = this;\`);
-          }
           this.a = 1;
           super(...arguments);
         }
@@ -351,9 +344,9 @@ describe('decaffeinate CLI', () => {
     );
   });
 
-  it('treats the --enable-babel-constructor-workaround option as a no-op', () => {
+  it('respects the --allow-invalid-constructors option as a no-op', () => {
     runCli(
-      '--enable-babel-constructor-workaround',
+      '--allow-invalid-constructors',
       `
       class A extends B
         constructor: ->
@@ -363,56 +356,9 @@ describe('decaffeinate CLI', () => {
       `
       /*
        * decaffeinate suggestions:
-       * DS001: Remove Babel/TypeScript constructor workaround
+       * DS002: Fix invalid constructor
        * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
        */
-      class A extends B {
-        constructor() {
-          {
-            // Hack: trick Babel/TypeScript into allowing this before super.
-            if (false) { super(); }
-            let thisFn = (() => { return this; }).toString();
-            let thisName = thisFn.match(/return (?:_assertThisInitialized\\()*(\\w+)\\)*;/)[1];
-            eval(\`$\{thisName} = this;\`);
-          }
-          this.a = 1;
-          super(...arguments);
-        }
-      }
-    `
-    );
-  });
-
-  it('respects the --disable-babel-constructor-workaround option', () => {
-    runCli(
-      '--disable-babel-constructor-workaround',
-      `
-      class A extends B
-        constructor: ->
-          @a = 1
-          super
-    `,
-      `
-      class A extends B {
-        constructor() {
-          this.a = 1;
-          super(...arguments);
-        }
-      }
-    `
-    );
-  });
-
-  it('respects the --allow-invalid-constructors option as an alias for --disable-babel-constructor-workaround', () => {
-    runCli(
-      '--allow-invalid-constructors',
-      `
-      class A extends B
-        constructor: ->
-          @a = 1
-          super
-    `,
-      `
       class A extends B {
         constructor() {
           this.a = 1;
diff --git a/test/suggestions_test.ts b/test/suggestions_test.ts
index 991d3039..8e0d7215 100644
--- a/test/suggestions_test.ts
+++ b/test/suggestions_test.ts
@@ -1,7 +1,7 @@
 import check, { checkCS1 } from './support/check';
 
 describe('suggestions', () => {
-  it('provides a suggestion for the babel constructor workaround', () => {
+  it('provides a suggestion for invalid constructors', () => {
     check(
       `
       class A extends B
@@ -11,19 +11,12 @@ describe('suggestions', () => {
       `
       /*
        * decaffeinate suggestions:
-       * DS001: Remove Babel/TypeScript constructor workaround
+       * DS002: Fix invalid constructor
        * DS102: Remove unnecessary code created because of implicit returns
        * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
        */
       class A extends B {
         constructor(...args) {
-          {
-            // Hack: trick Babel/TypeScript into allowing this before super.
-            if (false) { super(); }
-            let thisFn = (() => { return this; }).toString();
-            let thisName = thisFn.match(/return (?:_assertThisInitialized\\()*(\\w+)\\)*;/)[1];
-            eval(\`$\{thisName} = this;\`);
-          }
           this.c = this.c.bind(this);
           super(...args);
         }
@@ -70,31 +63,17 @@ describe('suggestions', () => {
       `
       /*
        * decaffeinate suggestions:
-       * DS001: Remove Babel/TypeScript constructor workaround
+       * DS002: Fix invalid constructor
        * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
        */
       class A extends B {
         constructor(c) {
-          {
-            // Hack: trick Babel/TypeScript into allowing this before super.
-            if (false) { super(); }
-            let thisFn = (() => { return this; }).toString();
-            let thisName = thisFn.match(/return (?:_assertThisInitialized\\()*(\\w+)\\)*;/)[1];
-            eval(\`$\{thisName} = this;\`);
-          }
           this.c = c;
           super(...arguments);
         }
       }
       class E extends F {
         constructor(g) {
-          {
-            // Hack: trick Babel/TypeScript into allowing this before super.
-            if (false) { super(); }
-            let thisFn = (() => { return this; }).toString();
-            let thisName = thisFn.match(/return (?:_assertThisInitialized\\()*(\\w+)\\)*;/)[1];
-            eval(\`$\{thisName} = this;\`);
-          }
           this.g = g;
           super(...arguments);
         }