I’m coding a type-safe class system using ES5 and JSDoc.
There is no notion of new functions in ES5; the type of function that constructs an object is just (...args: any) => void.
To make it clear to JSDoc (and TypeScript) that a function in fact constructs an object, I transform it using Parameters and InstanceType:
type Prototype = { construtor: (...args: any) => void };
type Class = {
new (...args: Parameters<Prototype["construtor"]>): InstanceType<
Prototype["construtor"]
> &
Omit<Prototype, "construct">;
};
Ignore the error on InstanceType, as it is irrelevant.
For some reason the type parameters of constructor do not work properly: on properties that should infer type information from a generic parameter I get <Type parameter> extends <Constraint>, instead of qualified type.
/**
* @param {Prototype & ThisType<InstanceType<Prototype["construtor"]> & Prototype>} prototype
* @template {{ construtor: (...args: any) => void }} Prototype
* @returns {{
* new (...args: Parameters<Prototype["construtor"]>): InstanceType<
* Prototype["construtor"]
* > &
* Omit<Prototype, "construct">;
* }}
*/
function cls(prototype) {
// Ignore the implementation
return;
}
var A = cls({
/**
* @param {T} prop
* @template {string} T
*/
construtor: function A(prop) {
this.prop = prop;
},
prop1: "prop1"
});
var a = new A("a");
a.prop; // T extends string
// should be "a"
I’ve found a solution to this problem, but it’s far from ideal:
- Instead of passing the constructor as a property of
prototype, pass it as its own argument - Intersect with
Constructorin return type
/**
* @param {Prototype & ThisType<InstanceType<Constructor> & Prototype>} prototype
* @template {object} Prototype
* @param {Constructor} constructor
* @template {(...args: any) => void} Constructor
* @returns {Constructor & {
* new (...args: Parameters<Constructor>): InstanceType<
* Constructor
* > &
* Omit<Prototype, "construct">;
* }}
*/
function cls(prototype, constructor) {
// Ignore the implementation
return;
}
/** @class */
var A = cls(
/** @lends A.prototype */
{
prop1: "prop1",
},
/**
* @constructs A
* @param {T} prop
* @template {string} T
*/
function A(prop) {
this.prop = prop;
this.prop1; // Property 'prop1' does not exist on type 'A<T>'. Did you mean 'prop'?ts(2551)
}
);
var a = new A("a");
a.prop; // "a"
a.prop1; // Property 'prop1' does not exist on type 'A<"a">'. Did you mean 'prop'?ts(2551)
Problems with this approach:
- There is no way to specify the type of
thisfor constructor (cannot accessprop1in constructor) - Resulting
newfunction has two signatures, one of them does not includePrototypein instance type (cannot accessprop1of instance)

