Strict type checking for property type with property decorator
Asked Answered
D

2

6

Is there a way to validate the type of the property that is decorated in Typescript? I would like a property decorator that only works on boolean class properties, but not on e.g. string (example below). Is this possible?

(Note: I don't want runtime validation via reflect-metadata, just a compile type warning with Typescript.)

function booleanProperty<T extends HTMLElement>(target: T, prop: string) {
  // `target` will be the class' prototype
  Reflect.defineProperty(target, prop, {
    get(this: T): boolean {
      return this.hasAttribute(prop);
    },
    set(this: T, value: boolean): void {
      if (value) {
        this.setAttribute(prop, '');
      } else {
        this.removeAttribute(prop);
      }
    }
  });
}

class MyElement extends HTMLElement {
  @booleanProperty
  foo: boolean;

  @booleanProperty
  bar: string; // TS compile error
}
customElements.define('my-element', MyElement);
Daye answered 22/11, 2017 at 0:43 Comment(1)
Haven't look into the handbook, but I recall there is a third argument and you may define a generic there to scope it. Just a hunch, hope that works for you.Fontana
F
13

What about this?

function booleanProperty<
  T extends HTMLElement & Record<K, boolean>,
  K extends string>(target: T, prop: K) {
    // ... impl here
}

The passed-in target needs to be an HTMLElement which has a boolean property at the key K, where K is the type of the passed-in prop. Let's see if it works:

class MyElement extends HTMLElement {
  @booleanProperty // okay
  foo: boolean;

  @booleanProperty // error
  bar: string;
}

Looks good to me. Does that work for you?

Funambulist answered 22/11, 2017 at 2:26 Comment(3)
Clever! I didn't think about narrowing down the prop parameter in that way.Daye
Unfortunately this only works for public properties. Any chance this can be improved to work with protected/private properties, too?Postlude
It’s not really possible to programmatically manipulate private or protected properties because they are intentionally inaccessible from type functions like keyof.Funambulist
G
2

I've found a way to match the type of the property to the type of the decorator, which also works on private fields, and allows for specifying dataflow direction. I've created a library of it, hope it helps.

Typescript Playground

import { TypedPropertyDecorator } from 'typesafe-decorators';

declare const StringLogger: TypedPropertyDecorator<string, 'get'>;
declare const BooleanValidator: TypedPropertyDecorator<boolean, 'set'>;
declare const EnumValidator: TypedPropertyDecorator<'foo' | 'bar' | 'baz', 'set'>;

class Foo {
  @StringLogger // OK
  private x1!: 'a' | 'b';

  @StringLogger
  // ^^^^^^^^^^ Type of property is not assignable to type of decorator
  private x2!: number;
  // This logger annotation can only log strings

  @BooleanValidator // OK
  private x3!: boolean;

  @EnumValidator // OK
  private x4!: string;

  @EnumValidator
  // ^^^^^^^^^^^ Type of decorator is not assignable to type of property
  private x5!: 'foo' | 'bar';
  // Validator would allow 'baz' too, which does not fit into the property
}
Gratia answered 6/5, 2023 at 17:25 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.