chaijs / assertion-error

Error constructor for test and validation frameworks that implements standardized AssertionError specification.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

The provided typescript typings are suboptimal and do not work

silkentrance opened this issue · comments

PR #15

The typings introduced with #10 are suboptimal and do not work in that they do now allow typescript to infer the prototype/inheritance chain properly during static code analysis.

One result of that is that the mandatory name and message fields are no longer declared, not to mention the fact that AssertionErrorConstructor#new() must return either a class or interface but not a type.

E.g. given the following code

import AssertionError = require('assertion-error');

class MySpecialAssertionError extends AssertionError {

  public constructor(message: string) {
    super(message);
  }
}

const errors: Error[] = [];

errors.push(new MySpecialAssertionError('very special'));

will fail because typescript will complain about MySpecialAssertionError not implementing all of the Error interface, namely name and message.

And while it is nice to have an assertion error prototype that can be extended with additional properties, this does not actually work with typescript classes and their contracts unless specifically declared.

A better solution would be to replace the existing typings with either of the below

  1. plain object oriented approach

Here, additional properties are defined as properties on the derived classes.

declare class AssertionError extends Error {

  public constructor(message: string, ssf?: Function);
}

export = AssertionError;
  1. or with 'mixin' properties

Here, additional properties can be passed in and the interface allows arbitrary keys for accessing these properties, but there will be no 'type safety'.

declare interface AssertionError<T = {}> extends Error {

  [key: string]: any;
}

declare class AssertionError<T = {}> extends Error implements AssertionError<T> {

  public constructor(message: string, props?: T, ssf?: Function);
}

export = AssertionError;

One could further reduce the second approach to just

declare class AssertionError<T = {}> extends Error {

  public constructor(message: string, props?: T, ssf?: Function);

  [key: string]: any;
}

export = AssertionError;

I think that your solution breaks the following inference:

const err = new AssertionError("testError", {foo: 2});
console.log(err.foo); // err.foo is properly found to be a number (or should be)

Regarding your issue and assertion-error's modelization in general, I agree that AssertionError should be a class.

[key: string]: any; looses any type info about the custom params, but now that mapped types exist (they didn't at the time), there's maybe a way to express the way the props object is applied on the instance. I haven't looked into it yet, I'll check it if I have some time.

@demurgos yes, you are right. I have looked into mapped types and I currently do not see a way to mix the type with the class declaration.

@demurgos typescript seems to very forgiving when using the any, in test/typings.ts it reads on line 14:

const bar: number = assertionError.bar;

And this does not fail during compilation.