Skip to content

useDefineForClassFields defaults to true when target is es2022, breaks decorators. #48814

Closed

Description

Bug Report

useDefineForClassFields defaults to true when target is set to es2022 or esnext. This is breaking lots of projects that use decorators.

This was reported in #45584, but that issue was closed apparently because the title didn't include which versions of TypeScript set useDefineForClassFields and because the TypeScript playground doesn't exhibit this same behavior. I've included a reproduction for tsc here.

This behavior is not documented anywhere in the TypeScript docs that I can find.

🔎 Search Terms

useDefineForClassFields decorators

🕗 Version & Regression Information

Since 4.2.4? according to #45584
Present in 4.6.3 and 4.7.0-dev.20220422

⏯ Playground Link

This playground link does not demonstrate the problem, as it doesn't default useDefineForClassFields like tsc does. But if you set useDefineForClassFields to true you will see that that decorators could not work because the field is not an instance property that shadows the decorator defined prototype accessors.

Playground link with relevant code

This git repo reproduces the problem locally: https://github.com/justinfagnani/ts-decorators-bug

💻 Code

This does not work with target es2022:

const observe = (proto: Object, name: PropertyKey) => {
  const key = Symbol();
  Object.defineProperty(proto, name, {
    get() {
      return this[key];
    },
    set(v: unknown) {
      this[key] = v;
      this.onChange?.(name, v);
    }
  });
};


class A {
  @observe
  foo = 'abc';

  onChange(name: string, v: unknown) {
    console.log(`${name} changed to ${v}`);
  }
}

const a = new A();
a.foo = 'x'

🙁 Actual behavior

useDefineForClassFields defaults to true

🙂 Expected behavior

Option 1: useDefineForClassFields defaults to false like for other permutations of module and target until the new behavior is documented and users are notified with a prominent breaking change notice on the decorators documentation and the release notes.

Option 2: Use constructor initialization for decorated properties even if useDefineForClassFields is true. This will keep projects from breaking regardless of the config.

Option 3: Disallow both useDefineForClassFields: true and experimentalDecorators in the same config.

This behavior is breaking a large number of our users. Sometimes they are in control of their own tsconfig and can set useDefineForClassFields to false, but sometimes the config is generated for them via some other tool and they don't know how to set the value. In this case their project is just broken and they blame our library, not the update to TypeScript that included the undocumented breaking change.

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

Metadata

Assignees

No one assigned

    Labels

    Working as IntendedThe behavior described is the intended behavior; this is not a bug

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions