@strict class decorator

# Naveen Chawla (7 years ago)

So here it is, the holy grail of allowing classes to have fixed definitions...

OK what do I mean?

suppose

@strict
export class Person{
    name;
}

is followed by

const person = new Person();
person.age = 27;

I would like to see the IDE flag it as " "age" is not a property in strict class "Person" "

This reminds the developer to then add age as a property, thereby ensuring every instance of the class always has full autocomplete, quick property renaming etc. across the project, for all properties added to it. For large projects with multiple developers this can also have the benefit of ensuring that all properties added to each instance are necessarily documented in one place, instead of checking whether someone has dynamically added a property somewhere.

I would expect this example to throw a runtime exception like @readonly does. I don't think that's a problem because I think the IDE would inform much sooner. But I'm open to the idea of the runtime allowing it, since the real benefit for me is during development. Maybe instead a @strictDev decorator for that behavior, not sure.

Support?

# Logan Smyth (7 years ago)

Not necessarily 100% what you're going for, but you can get an error for this behavior if you use Object.preventExtensions() on the class, e.g.

class Person {
  name;
  constructor() {
    Object.preventExtensions(this);
  }
}

Currently because of an edge-case in Babel 6's class fields, every property would need an initializer value, but that will be fixed in Babel 7. You can see an example here: babeljs.io/repl/#?babili=false&evaluate=true&lineWrap=false&presets=stage-2&targets=&browsers=&builtIns=false&debug=false&code_lz=EQVwzgpgBGAuBOBLAxrYBuAUJ5AbAhmGFAAoTxgD2AdlAN6ZRTX4C20AvFAMxZPI048EKkrwAFAEp6jJlADyAIwBWEVADoADvAgA3CNVgBRAB6wDYRIPGwAFojCT0UWQF9M7nINhRN5KrRc1BAA7qT-NFJYfhQ06vgA5pxQAEwA7OhAA&experimental=false&loose=false&spec=false&playground=false

As for the IDE errors, it doesn't seem like @strict would be enough, because there are plenty of dynamic ways that you could pass class constructors or instance objects around that would cause an IDE to lose track of what objects to look at. This is the type of problem that Flowtype and Typescript already aim to solve, since the type annotations and inference allow them to track what types go where. They also already provide nice IDE errors for just this situation:

www.typescriptlang.org/play/index.html#src= export class Person{ name%3B } const person %3D new Person()%3B person.age %3D 27%3B * flow.org/try/#0PQKgBAAgZgNg9gdzCYAodBTAHgBzgJwBcwBjGAQwGdKwAFDfSuAOwG9UxOxnyBbDANyoAvuhItKxHAybMwAXm4Yk9RiwAUASiHS1zAHTkA5hgVgATAHYBQA

Given the limitations of type inference without an explicit typechecker, it doesn't seem like an annotation like @strict could be powerful enough to be useful for an IDE usecase. And for the non-IDE usecase, Object.preventExtensions seems to do what you want already.

# Darien Valentine (7 years ago)

For a class which is not intended to be subclassable, this can be done today with Object.preventExtensions() or Object.seal(), depending on your intent:

class Foo {
  constructor() {
    this.bar = 1;
    this.baz = 2;

    Object.preventExtensions(this);
  }
}

const foo = new Foo();

foo.bar = 3; // okay
foo.qux = 4; // throws in strict mode

But this approach doesn’t work when subclassing is or may be in play. It’s also not directly possible with the decorator proposal as it stands today — but there has been discussion of it and it sounds like it’s something that’s on people’s minds:

2017 July 27

DE: Omitted features: instance finishers. Yehuda?

YK: an instance finisher is a function that is executed at the end of instantiation of the class at any subclass level and passes at the instance. this is at the end of Reflect.construct. the use case is a decorator to confirm that all instances are frozen or sealed. Another: you want to register created instance into a map. The subclass provides the key, the superclass expresses that the instance should be registered.

DE: instance finishers change how instances are created. It's complicated and so wants to separate it out.

...looking forward to this, too.


Edit: replied before seeing Logan’s response, hence the repetition.

# Naveen Chawla (7 years ago)

Ah great! So then how about having a @seal and/or @preventExtensions decorator as a shorthand?

I accept that IDEs can only do so much in checking your code, and beyond that, you're on your own...

Secretly I want Javascript to become like TypeScript... all optional, backwards compatible, etc.

# Isiah Meadows (7 years ago)

IIRC, the module core-decorators (a module of utility decorators) have both of those. That seems ideal, since it's not something that would massively benefit from being within the standard library itself.

# Naveen Chawla (7 years ago)

I don't see them. Which ones in core-decorators are they?

Apart from removing a dependency that could be very commonly used, you would be right although I see that as a very compelling case in of itself. I see standard library as a good place for widely useful tasks that span all industry domains. For example, I wouldn't have a library specific to "automotive vehicles" but something that would aid development across the board such as these I think could benefit. Hence why I think they are called "core-decorators"

# Isiah Meadows (7 years ago)

I think it might be just @sealed (preventExtensions not available), but I'm just going off the top of my head. I'd probably recommend it there rather than in the spec.

I personally see minimal value in it being in the spec proper, because the use case is hardly there, and it's pretty much one line extra in the constructor.

# Naveen Chawla (7 years ago)

Nope not there (according to a Co+F keyboard search!)

Use case is any time someone wants basic strict typing during development, but I agree it's not quite as compelling as if/when types in params (e.g. : Person) are introduced in Javascript (which I'd like honestly)

# Isiah Meadows (7 years ago)

Yeah, I would've checked had I been on a computer, not a phone... ;-)

Out of curiosity, though, what do you mean by if/when types? I don't recall hearing about it.

# Naveen Chawla (7 years ago)

Sorry my writing style was bad. I meant if or when there is an introduction of types in parameters (like in ActionScript, TypeScript etc.) e,g, sayHelloToPerson(person : Person); which I said I'd like

# Sebastian Malton (7 years ago)

An HTML attachment was scrubbed... URL: esdiscuss/attachments/20170808/0effcda7/attachment

# Naveen Chawla (7 years ago)

Name changes are fine but the type would remain the same, right?

# Michael Theriot (7 years ago)

Subclassing can work too.

class A {
  constructor() {
    this.bar = 1;
    this.baz = 2;
    if (new.target === A) Object.preventExtensions(this);
  }
}

class B extends A {
  constructor() {
    super();
    this.bat = 3;
    if (new.target === B) Object.preventExtensions(this);
  }
}

No decorator needed to do this today.

I am not keeping up with decorators but @sealed implies to me that the class cannot be subclassed? At least that's what it means in C# and would confuse me if it simply meant Object.seal(this)...