Repository: TypeScript
Issue: Proposal: `decoratedWith` type operator and generic 'value' parameters
Labels: Suggestion,Awaiting More Feedback

### 🔍 Search Terms

Decorators
TypeScript 5

### ✅ Viability Checklist

- [X] This wouldn't be a breaking change in existing TypeScript/JavaScript code
- [X] This wouldn't change the runtime behavior of existing JavaScript code
- [X] This could be implemented without emitting different JS based on the types of the expressions
- [X] This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, new syntax sugar for JS, etc.)
- [X] This feature would agree with the rest of our Design Goals: https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals

### ⭐ Suggestion

_WIP: Goal is to get this to canonical ASAP, all help appreciated._

This is a proposal introduces two things  a type operator named: `decoratoredWith` and generic 'value' parameters.

First off, the `decoratedWith` type operator.
This type operator would check if a method, property/field, getter, setter, auto-accessor or class is decorated with a specific decorator function. (I think it shouldn't support legacy decorators)

To illustrate:
```typescript
// Decorator functions
function EntityOnly<C, T>(_target: undefined, context: ClassFieldDecoratorContext<C, T>) {
  // Does something
}

function SchemaProperty<C, T>(schema: any) {
  return function(_target: undefined, context: ClassFieldDecoratorContext<C, T>) {
    // Does something
  }
}

// Class with some fields decorated
class User {
  id: number;
  @SchemaProperty({ type: string, })
  username: string;
  @SchemaProperty({ type: string, })
  displayName: string;
  @EntityOnly
  hashedPasssword: any;
  @EntityOnly
  IBAN: string;
}

// A simple example of the decoratedWith type operator albeit this is not where this operator would shine.
type IsEntityOnly = User['hashedPassword'] decoratedWith EntityOnly ? true : false;

// Should also work with functions returning decorators.
type IsEntityOnly = User['username'] decoratedWith SchemaProperty ? true : false;
```

Should work the same with methods, properties/fields, getters, setters, auto-accessors and classes too.

#### _Revision 1: Generic 'value' parameters_

Disclaimer: I understand that the following might be such a substantial addition to the proposal that it should be a separate proposal but I am looking for feedback first before adding yet another proposal. Perhaps this part is already covered in another proposal, if so, great, if not then perhaps there is a reason this hasn't been proposed yet.

My initial proposal featured some code using the `decoratedWith` proposal with a generic type which wasn't really a type but rather a value pointing to a decorator function. (as pointed out by @jcalz) And although this is currently not possible in TypeScript I think it could be very beneficial to be able to generalize decorator functions as values. Perhaps these "generic 'value' parameters should be supported by their own group of 'value' operators which would refer to a value rather than a type. These values must be constant in nature, so would be limited to functions, const variables, enums, classes, etc. Naturally they cannot refer to types. Here's the list of 'value' operators:

- `valueOf` - The one 'value' operator to rule them all. This 'value' operator accepts any value constant in nature.
- `funcOf` - This 'value' operator accepts functions.
- `constOf` - This 'value' operator refers to const variables.
- `enumOf` - This 'value' operator refers to enums.
- `classOf`- This 'value' operator refers to classes.

These new operators could streamline some processes like referring to an actual class rather than just it's constructor.
Let me start by illustrating some problems that generic 'value' operators could solve on their own:

The first problem would be that you can't enforce (at least not in a simple way as far as my knowledge goes, any examples that contradict this are appreciated as I could use that to enhance my proposal) a generic type parameter to be a class. The problem illustrated:

```typescript
type ClassType<T> = new () => T;

// This user class takes a generic type parameter.
// The constructor has a parameter which requires a value with a constructor returning type T.
// Yet you cannot demand the generic parameter to be a class.
class User<T> {
  private _classType: ClassType<T>;
  constructor(classType: ClassType<T>) {
    this._classType = classType;
  }
}

interface Entity {

}
class Item implements Entity {
}
// As shown here, generic parameter T can be an interface while you might to enforce the type being a class.
new User<Entity>(Item);
```

What my proposal would do is, it would enable users to do the following:

```typescript
class User<classOf T> {
  private _classType = T;
}

interface Entity {
}
class Item extends Entity {
}
// This shouldn't work because Entity is not a class.
new User<Entity>();
// This should work because Item is a class.
new User<Item>();
```

Here the 'value' operator `classOf` is used to denote a generic 'value' parameter (limited to class values). Generic 'value' parameters accept values rather than types and can be used throughout a class or function as value. The `User` class as described in the problem would translate to a class similar as that of the `User` class described in the previously illustrated problem which the `classOf` operator should solve. The only difference being that the `classOf` operator shortens the code and could restrict the generic parameter to actual classes ,

The other 'value' operators operate the same way where `valueOf` is the swiss army knife of the bunch which accepts every value accepted by all the other 'value' operators.

Generic 'value' parameters powered by 'value' operators can enable you to use constant values in type declarations.
To take my proposed `decoratedWith` for example:

```typescript
// Together with the 'value' operators and generic 'value' parameters this is where `decoratedWith` could truly shine.
// Here it only returns the key names of the members of generic parameter T that are decorated with the decorator function provided in the generic 'value' parameter DT which must be a function.
type DecoratedMembers<T, funcOf DT> = keyof { [P in keyof T as T[P] decoratedWith DT ? P : never]: any; };

// This variable would have the union type: "hashedPassword" | "IBAN"
const entityOnlyMembers: DecoratedMembers<User, EntityOnly>;

// This variable would have the union type: "username" | "displayName"
const entityOnlyMembers: DecoratedMembers<User, SchemaProperty>;
```

This is all theoretically possible within TypeScript without requiring any change in the JavaScript spec. I am open for feedback. Also I am no longer sure if this is indeed not a breaking change (as it adds keywords), so if anyone could comment on that, that be great too.


### 📃 Motivating Example

You can now work with types that are computed based on the fact whether methods, properties/fields, getters, setters, auto-accessors or classes include certain decorators or not.

### 💻 Use Cases

1. What do you want to use this for?

To be able to act on the inclusion or exclusion of decorators on methods, properties/fields, getters, setters auto-accessors and classes.

2. What shortcomings exist with current approaches?

There are not really ways to handle such cases right now.

3. What workarounds are you using in the meantime?

There aren't really any workarounds right now as far as my knowledge goes.