Skip to content
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

Class property is non-writable when used with decorator without initializer #7391

Closed
yhpark opened this issue Feb 18, 2018 · 1 comment · Fixed by #7429
Closed

Class property is non-writable when used with decorator without initializer #7391

yhpark opened this issue Feb 18, 2018 · 1 comment · Fixed by #7429
Labels
outdated A closed issue/PR that is archived due to age. Recommended to make a new issue

Comments

@yhpark
Copy link
Contributor

yhpark commented Feb 18, 2018

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

Input Code

function Dec(target, key, descriptor) {
    // do nothing
    return descriptor;
}

class Test {
    @Dec
    a;
}

let t = new Test();

console.log(t.a); // prints 'undefined'

console.log(Object.getOwnPropertyDescriptor(t, 'a')); // writable: false (should be true)
/* prints:
{ value: undefined,
  writable: false,
  enumerable: true,
  configurable: false }
*/

t.a = 2;

console.log(t.a); // prints 'undefined' (should be 2)

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

{
  "presets": [
    ["@babel/preset-env", {
      "targets": {
        "node": "current"
      }
    }],
    "@babel/preset-stage-2"
  ],
  "plugins": [
    "@babel/plugin-proposal-decorators",
    [
      "@babel/plugin-proposal-class-properties",
      { "loose": true }
    ]
  ]
}

Expected Behavior

The property a should be writable.

Current Behavior

writable in a's descriptor is set to false.

Possible Solution

Currently Babel transpiles the code with _applyDecoratedDescriptor function declaration as following:

function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) { var desc = {}; Object['ke' + 'ys'](descriptor).forEach(function (key) { desc[key] = descriptor[key]; }); desc.enumerable = !!desc.enumerable; desc.configurable = !!desc.configurable; if ('value' in desc || desc.initializer) { desc.writable = true; } desc = decorators.slice().reverse().reduce(function (desc, decorator) { return decorator(target, property, desc) || desc; }, desc); if (context && desc.initializer !== void 0) { desc.value = desc.initializer ? desc.initializer.call(context) : void 0; desc.initializer = undefined; } if (desc.initializer === void 0) { Object['define' + 'Property'](target, property, desc); desc = null; } return desc; }

which sets desc.writable true in the following condition:
if ('value' in desc || desc.initializer) { desc.writable = true; }
I don't understand this part. Should this be changed somehow?

Context

This behavior occurs only when both of the following conditions are met:

  • A decorator is present on the class property.
  • No initializer is defined in the class property declaration.

For example, if there's no decorator:

class Test {
    a;
}

a is writable.

Transpiled code:
function Dec(target, key, descriptor) {
  // do nothing
  return descriptor;
}

class Test {
  constructor() {
    this.a = void 0;
  }

}

let t = new Test();
console.log(t.a); // prints 'undefined'

console.log(Object.getOwnPropertyDescriptor(t, 'a')); // writable: false (should be true)

t.a = 2;
console.log(t.a); // prints 'undefined' (should be 2)

Also, if I set an initializer:

class Test {
    @Dec
    a = 1;
}

a is writable.

Transpiled code:
var _class, _descriptor;

function _initializerDefineProperty(target, property, descriptor, context) { if (!descriptor) return; Object.defineProperty(target, property, { enumerable: descriptor.enumerable, configurable: descriptor.configurable, writable: descriptor.writable, value: descriptor.initializer ? descriptor.initializer.call(context) : void 0 }); }

function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) { var desc = {}; Object['ke' + 'ys'](descriptor).forEach(function (key) { desc[key] = descriptor[key]; }); desc.enumerable = !!desc.enumerable; desc.configurable = !!desc.configurable; if ('value' in desc || desc.initializer) { desc.writable = true; } desc = decorators.slice().reverse().reduce(function (desc, decorator) { return decorator(target, property, desc) || desc; }, desc); if (context && desc.initializer !== void 0) { desc.value = desc.initializer ? desc.initializer.call(context) : void 0; desc.initializer = undefined; } if (desc.initializer === void 0) { Object['define' + 'Property'](target, property, desc); desc = null; } return desc; }

function _initializerWarningHelper(descriptor, context) { throw new Error('Decorating class property failed. Please ensure that ' + 'proposal-class-properties is enabled and set to use loose mode. ' + 'To use proposal-class-properties in spec mode with decorators, wait for ' + 'the next major version of decorators in stage 2.'); }

function Dec(target, key, descriptor) {
  // do nothing
  return descriptor;
}

let Test = (_class = class Test {
  constructor() {
    _initializerDefineProperty(this, "a", _descriptor, this);
  }

}, (_descriptor = _applyDecoratedDescriptor(_class.prototype, "a", [Dec], {
  enumerable: true,
  initializer: function () {
    return 1;
  }
})), _class);
let t = new Test();
console.log(t.a); // prints 'undefined'

console.log(Object.getOwnPropertyDescriptor(t, 'a')); // writable: false (should be true)

t.a = 2;
console.log(t.a); // prints 'undefined' (should be 2)

Your Environment

software version(s)
Babel see below
Babylon
node
npm
Operating System
    "@babel/cli": "^7.0.0-beta.40",
    "@babel/core": "^7.0.0-beta.40",
    "@babel/plugin-proposal-class-properties": "^7.0.0-beta.40",
    "@babel/plugin-proposal-decorators": "^7.0.0-beta.39",
    "@babel/preset-env": "^7.0.0-beta.39",
    "@babel/preset-stage-2": "^7.0.0-beta.39"
@babel-bot
Copy link
Collaborator

Hey @yhpark! We really appreciate you taking the time to report an issue. The collaborators
on this project attempt to help as many people as possible, but we're a limited number of volunteers,
so it's possible this won't be addressed swiftly.

If you need any help, or just have general Babel or JavaScript questions, we have a vibrant Slack
community that typically always has someone willing to help. You can sign-up here
for an invite.

@yhpark yhpark changed the title Class property is non-writable in a specific setting Class property is non-writable when used with decorator without initializer Feb 18, 2018
@lock lock bot added the outdated A closed issue/PR that is archived due to age. Recommended to make a new issue label Nov 16, 2018
@lock lock bot locked as resolved and limited conversation to collaborators Nov 16, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
outdated A closed issue/PR that is archived due to age. Recommended to make a new issue
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants