tsdjs/tsd

Unable to expectError on decorator applicability

tbroyer opened this issue · 5 comments

I'm writing a helper library around decorators (standard ECMAScript ones, as supported by TypeScript 5+ and @babel/plugin-proposal-decorators) and have been unable to test errors when applying them.

Here's a self-contained reproduction:

import { expectError } from "tsd";

class Base extends HTMLElement {
  foo() {}
}

const classDec = <T extends new (...args: any[]) => Base>(
  value: T,
  context: ClassDecoratorContext<T>
) => {
  return class DecoratedClass extends value {};
};

(
  @classDec
  class extends Base {}
);
expectError(
  @classDec
  class {}
);
expectError(
  @classDec
  class extends Document {}
);
expectError(
  @classDec
  class extends HTMLElement {}
);
expectError(() => {
  @classDec
  abstract class Test extends Base {}
});

classDec can only be applied to a concrete class extending Base, so the anonymous class extending nothing, Document, HTMLElement, or an abstract class are errors.

This however fails with:

  repro.test-d.ts:19:3
  ✖  19:3  Unable to resolve signature of class decorator when called as an expression.
  Argument of type typeof (Anonymous class) is not assignable to parameter of type new (...args: any[]) => Base.
    Type (Anonymous class) is missing the following properties from type Base: foo, accessKey, accessKeyLabel, autocapitalize, and 291 more.                                                                                                                                                                                                                                                                                                            
  ✖  23:3  Unable to resolve signature of class decorator when called as an expression.
  Argument of type typeof (Anonymous class) is not assignable to parameter of type new (...args: any[]) => Base.
    Type Document is missing the following properties from type Base: foo, accessKey, accessKeyLabel, autocapitalize, and 132 more.                                                                                                                                                                                                                                                                                                                     
  ✖  23:3  Decorator function return type { new (...args: any[]): classDec<new (...args: any[]) => Base>.DecoratedClass; prototype: classDec<any>.DecoratedClass; } & (new (...args: any[]) => Base) is not assignable to type void | typeof (Anonymous class).                                                                                                                                                                                                                                                                                                                                                                                                
  ✖  23:3  Decorator function return type { new (...args: any[]): classDec<new (...args: any[]) => Base>.DecoratedClass; prototype: classDec<any>.DecoratedClass; } & (new (...args: any[]) => Base) is not assignable to type void | typeof (Anonymous class).
  Type { new (...args: any[]): classDec<new (...args: any[]) => Base>.DecoratedClass; prototype: classDec<any>.DecoratedClass; } & (new (...args: any[]) => Base) is not assignable to type typeof (Anonymous class).
    Type classDec<new (...args: any[]) => Base>.DecoratedClass & Base is missing the following properties from type Document: URL, alinkColor, all, anchors, and 92 more.  
  ✖  27:3  Unable to resolve signature of class decorator when called as an expression.
  Argument of type typeof (Anonymous class) is not assignable to parameter of type new (...args: any[]) => Base.
    Property foo is missing in type HTMLElement but required in type Base.                                                                                                                                                                                                                                                                                                                                                                              
  ✖  31:3  Unable to resolve signature of class decorator when called as an expression.
  Argument of type typeof Test is not assignable to parameter of type new (...args: any[]) => Base.
    Cannot assign an abstract constructor type to a non-abstract constructor type.                                                                                                                                                                                                                                                                                                                                                                                   

  6 errors

Same result as if I omitted the expectError from the file.

As a workaround, I can call the decorator myself, but that defeats part of the goals of the tests as I now set explicit types inferred from what I think TypeScript will use, rather than let TypeScript use whatever types it expects.

import { expectError } from "tsd";

class Base extends HTMLElement {
  foo() {}
}

const classDec = <T extends new (...args: any[]) => Base>(
  value: T,
  context: ClassDecoratorContext<T>
) => {
  return class DecoratedClass extends value {};
};
() => {
    class Test extends Base {}
    classDec(Test, {} as ClassDecoratorContext<typeof Test>);
};
expectError(() => {
    class Test {}
    classDec(Test, {} as ClassDecoratorContext<typeof Test>);
});
expectError(() => {
  class Test extends Document {}
  classDec(Test, {} as ClassDecoratorContext<typeof Test>);
});
expectError(() => {
  class Test extends HTMLElement {}
  classDec(Test, {} as ClassDecoratorContext<Test>);
});
expectError(() => {
  abstract class Test extends Base {}
  classDec(Test, {} as ClassDecoratorContext<Test>);
});

FWIW tsd is matching errors using a predefined list of codes. Users must open PRs to add the missed codes one by one. For example, see #205

It appears that most of the error codes here fall into what tsd considers "syntactical errors" (< 2000): 1238 to 1241, 1249, 1270 and 1271, 1278 and 1279, and 1329.

Not quite sure how to handle that (I'm trying to prepare a PR, with tests!)

Here is the place to distinguish between semantic and syntactic errors (if there is an intention to keep them distinct, why to pour everything into one list?):

tsd/source/lib/compiler.ts

Lines 109 to 111 in bb28db1

const tsDiagnostics = program
.getSemanticDiagnostics()
.concat(program.getSyntacticDiagnostics());

As an example, in forked tsd-lite (it is not maintained anymore), I was passing only semantic errors to evaluate in the matchers:

https://github.com/mrazauskas/tsd-lite/blob/e6f439ba361dc3c675a67c16294881463a24e1b8/source/tsdLite.ts#L30-L37