Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unable to expectError on decorator applicability #206

Closed
tbroyer opened this issue Jan 14, 2024 · 5 comments · Fixed by #207
Closed

Unable to expectError on decorator applicability #206

tbroyer opened this issue Jan 14, 2024 · 5 comments · Fixed by #207

Comments

@tbroyer
Copy link
Contributor

tbroyer commented Jan 14, 2024

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.

@tbroyer
Copy link
Contributor Author

tbroyer commented Jan 15, 2024

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>);
});

@mrazauskas
Copy link
Contributor

mrazauskas commented Jan 15, 2024

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

@tbroyer
Copy link
Contributor Author

tbroyer commented Jan 16, 2024

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!)

@mrazauskas
Copy link
Contributor

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());

@mrazauskas
Copy link
Contributor

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants