What is the right typescript definition for a specific javascript instantiation pattern

89 views Asked by At

I'm currently working on Paper.js library typescript definition and I have trouble finding the right way to document some parts of the API.

The case can be reduced to the following: let's say we have an Animal class which has a static property Dog that is used as a custom constructor for the class:

var Animal = function(type) {};
Animal.Dog = function() {
    return new Animal('dog');
};

An Animal instance can be built in 2 ways:

var dog = new Animal('dog');

Or:

var dog = new Animal.Dog();

In both cases, I need the type of the dog variable to be inferred as Animal.


I first tried:

declare class Animal
{
    constructor ( type )
    static Dog (): Animal
}

But TSLint fails with the error: "Only a void function can be called with the 'new' keyword.", because Animal.Dog() function return type is Animal.

And if I set the return type of Animal.Dog() as void:

declare class Animal
{
    constructor ( type )
    static Dog (): void
}

TSLint pass but I get void as the inferred type...


So I tried another way:

declare class Animal
{
    constructor ( type )
}

declare namespace Animal
{
    export class Dog extends Animal
    {
        constructor()
    }
}

With this, TSLint pass but in the case of:

var dog = new Animal.Dog();

The inferred type of dog variable is Animal.Dog and not Animal as I would want to.

That's not a big problem because Animal.Dog type extends Animal but there is no Animal.Dog in the library so I found this workaround misleading for the user.

Does anyone know a better way to handle this case ?

Edit

Elaborating from @stramski solution, I add to the problem, the fact that Animal.Dog can have multiple signatures (e.g. Animal.Dog() and Animal.Dog(color)) and my goal is to document them separately.

2

There are 2 answers

4
Stramski On BEST ANSWER

What about something like this:

declare class Animal
{
    constructor ( type )
    static Dog : (new () => Animal)
}

Edit

As there are overloaded constructors the typing is a little different:

declare class Animal
{
    constructor ( type )
    static Dog : (new () => Animal) & (new (color) => Animal)
}
2
Fallenreaper On

Since you are subclassing.. Keeping things clean and concise is important. In the way written above, you can see that Dog was created an animal, but it has no differences than being an animal. In some cases, there are some variables or methods which are overridden. That being said, I find it better if you implemented something akin to:

class Animal {
  constructor(){}
  communicate() { return "Makes Noise"; }
}

class Dog extends Animal {
  constructor(){
    super();
  }

  communicate() { return "Barks"; }
}

from there you can override methods or variables in order to properly differentiate a Dog from Animal, from other Animal sub classes.