microsoft / microsoft/TypeScript
Proposal: `decoratedWith` type operator and generic 'value' parameters
Nadie ha tomado este issue todavía.
- Lenguaje dominante
- Go
- Estrellas
- 111k
- Forks
- 14.4k
- Merge medio
- 1 d 19 h
- PR fusionados (30 d)
- 117
Descripción
🔍 Search Terms
Decorators
TypeScript 5
✅ Viability Checklist
- This wouldn't be a breaking change in existing TypeScript/JavaScript code
- This wouldn't change the runtime behavior of existing JavaScript code
- This could be implemented without emitting different JS based on the types of the expressions
- This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, new syntax sugar for JS, etc.)
- 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:
// 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:
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:
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:
// 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
- 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.
- What shortcomings exist with current approaches?
There are not really ways to handle such cases right now.
- What workarounds are you using in the meantime?
There aren't really any workarounds right now as far as my knowledge goes.
Guía de contribución
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Línea de trabajo
El issue contiene una propuesta amplia para un operador de tipos decoratedWith y parámetros de valor genéricos, pero no nombra archivos de implementación, puntos de entrada ni pruebas. Empieza leyendo la arquitectura del comprobador de tipos y el parser de TypeScript, y determina después si la propuesta puede delimitarse como una funcionalidad comprobable de forma independiente. Para darlo por terminado se requerirían un diseño acordado, una implementación y pruebas del compilador que cubran la sintaxis y el comportamiento de tipos propuestos.
Escrito por el modelo de indexación a partir del texto del issue.
Evaluación
- Stack tecnológico
- typescript
- Área
- compilers
- Tipo de issue
- Nueva funcionalidad
- Dificultad
- 5/5
- Tiempo estimado
- Más de una semana
- Estado de actividad
- Estancado
- Claridad
- Necesita aclaración
- Aptitud para principiantes
- 20/100