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?):
Lines 109 to 111 in bb28db1
As an example, in forked tsd-lite
(it is not maintained anymore), I was passing only semantic errors to evaluate in the matchers: