Issue 1:
Bug Report

Current Behavior

In loose mode, @babel/plugin-proposal-class-properties emits a property assignment for TypeScript definite assignments.

Input Code

REPL (I'm not sure if it's possible to turn turn on loose mode for the plugin)

TS Playground

// Like Object.assign but will not assign properties which are already defined
declare const assignDefaults: typeof Object.assign

class Foo {
bar!: number;

constructor(overrides: { bar: number }) {
assignDefaults(this, overrides);
}
}

Expected behavior/code

A definite assignment should not emit any JS.

// Like Object.assign but will not assign properties which are already defined
class Foo {
constructor(overrides) {
assignDefaults(this, overrides);
}

}

Babel Configuration (.babelrc, package.json, cli command)

{
"presets": ["@babel/preset-typescript"],
"plugins": [["@babel/plugin-proposal-class-properties", { "loose": true }]]
}

Environment

Babel version(s): 7.5.5

Node/npm version: Node 12.6 / yarn 1.15.2

OS: macOS 10.14.5

Monorepo: no

How you are using Babel: cli

Additional context/Screenshots

This was previously raised in #7997, but considered expected behavior. It was mentioned that it could be reconsidered for loose mode, however.

I do feel like it should be reconsidered one way or another; emitting for these declarations is problematic in the same way emitting for a declare would be. Additionally, this syntax is not valid JS so there the only applicable spec compliance is that of TS.

Issue 2:
Choose one: is this a bug report or feature request?

Bug report.

Input Code

class Text extends TextRecord implements Types.TextNode {
public readonly firstChild: Types.NestedNode
public readonly nextSibling: Types.NestedNode
public readonly type: Types.NodeType.Text
public readonly uuid: string
public readonly value: string

public constructor(props: Types.TextNode) {
super(props)
}
}

Babel/Babylon Configuration (.babelrc, package.json, cli command)

module.exports = {
presets: [
['@babel/env', {
exclude: [
'transform-async-to-generator',
'transform-regenerator',
],
targets: {
browsers: ['last 2 versions'],
},
modules: 'commonjs',
loose: true,
useBuiltIns: 'usage',
}],
'@babel/react',
['@babel/stage-0', {
loose: true,
}],
'@babel/typescript',
],
plugins: [
'autobind-class-methods',
['module:fast-async', {
compiler: {
lazyThenables: true,
parser: {
sourceType: 'module',
},
promises: true,
wrapAwait: true,
},
useRuntimeModule: true,
}],
'lodash',
'./src/vendor/babel-plugin-ramda',
'react-loadable/babel',
'reflective-bind/babel',
['styled-components', {
displayName: true,
ssr: true,
}],
'transform-promise-to-bluebird',
['@babel/transform-regenerator', {
async: false,
asyncGenerators: true,
generators: true,
}],
'@babel/transform-runtime',
],
}

Expected Behavior

Babel should strip the type annotations from the class altogether, à la TypeScript's transpiled code:

var Text = /** @class */ (function (_super) {
__extends(Text, _super);
function Text(props) {
return _super.call(this, props) || this;
}
return Text;
}(TextRecord));

Current Behavior

Babel adds code to the constructor assigning void 0 to the annotated class properties:

var Text =
/*#__PURE__*/
function (_TextRecord) {
(0, _inheritsLoose2.default)(Text, _TextRecord);

function Text(props) {
var _this12;

_this12 = _TextRecord.call(this, props) || this;
_this12.firstChild = void 0;
_this12.nextSibling = void 0;
_this12.type = void 0;
_this12.uuid = void 0;
_this12.value = void 0;
return _this12;
}

return Text;
}(TextRecord);

Possible Solution

It would be great if either the TypeScript transform specifically stripped annotation-only class properties or the class properties transform generally didn't add assignments for properties that aren't defined.

Context

It's a common pattern when using Immutable.js with TypeScript to thread strong typings through Immutable Records by creating classes that extend the records and add type annotations to their properties. This works with the TypeScript compiler because TypeScript completely strips the type annotations from the class properties; however, it breaks with Babel because after the super call Babel is effectively attempting to set new properties on an Immutable Record, which throws a runtime error:

saga.ts:89 Error: Cannot set on an immutable record.
at invariant (immutable.es.js:1873)
at Text.set (immutable.es.js:5550)
at new Text (records.ts:251)
at Array.postReviver (lib.ts:33)
at fromJSWith (immutable.es.js:5682)
at immutable.es.js:5685
at immutable.es.js:1222
at ArraySeq.__iterate (immutable.es.js:432)
at IndexedCollection.mappedSequence.__iterateUncached (immutable.es.js:1221)
at IndexedCollection.__iterate (immutable.es.js:302)
at IndexedCollection.toArray (immutable.es.js:4575)
at List (immutable.es.js:3064)
at IndexedCollection.toList (immutable.es.js:4633)
at Object.postReviver (lib.ts:39)
at fromJSWith (immutable.es.js:5682)
at immutable.es.js:5685

I know it's a bit of a corner case, but it's making it pretty challenging to enjoy the benefits of TypeScript with Immutable.

Your Environment

software
version(s)

Babel
v7.0.0-beta.42

Babylon
N/A

node
v9.8.0

npm
v5.7.1

Operating System
macOS 10.13.4