Unable to resolve signature of method decorator when called as an expression

127 views Asked by At

I am working on a TypeScript project where I am trying to use decorators to add steps to Playwright tests. My goal is to create a custom decorator that wraps methods with Playwright's test.step function for better logging and readability. However, I am encountering TypeScript errors that I'm unable to resolve.

type GenericFunction = (...args: unknown[]) => unknown;
type ClassesWithStepDecorator = Component;

function step(target: GenericFunction, context: ClassMethodDecoratorContext) {
  return function replacementMethod(this: ClassesWithStepDecorator, ...args: unknown[]) {
    const className = this.constructor.name;
    if (typeof context.name !== 'string') {
      throw new Error('Method name is not a string');
    }

    const methodName = context.name;
    const argsString = args.map(arg => JSON.stringify(arg)).join(', ');

    return test.step(`${className}.${methodName}(${argsString})`, () => {
      return target.call(this, ...args);
    });
  };
}

class Component {
  constructor(private page: Page) {}

  @step
  checkComponent(name: string) {
    expect(name).toEqual('button');
  }
}

"target": "es2018",
"experimentalDecorators": true,
"emitDecoratorMetadata": true

When running this code, it functions as expected in the Playwright environment. However, TypeScript throws the following errors:

 - error TS1241: Unable to resolve signature of method decorator when called as an expression.   Argument of type 'Component' is not assignable to parameter of type 'GenericFunction'.     Type 'Component' provides no match for the signature '(...args: unknown[]): unknown'. 
- error TS1270: Decorator function return type '(this: ClassesWithStepDecorator, ...args: unknown[]) => Promise<unknown>' is not assignable to type 'void | TypedPropertyDescriptor<(name: string) => void>'. 

The TypeScript configuration includes "experimentalDecorators": true and "emitDecoratorMetadata": true.

1

There are 1 answers

0
0x5afe On

If you comment "experimentalDecorators" and "emitDecoratorMetadata" options on (or set their value to false) you should be good.

But if you have to use the "experimentalDecorators" (set to be true), the decorator function definition is a little different:

  • you have as the first parameter the class of the method you decorate,
  • as the second parameter the name of the method (a string),
  • and as a third parameter, a descriptor object which value is a reference of your method.

Your decorator would look like this:

function step(target: any, key: String, descriptor: PropertyDescriptor) {

    descriptor.value = function replacementMethod(this: ClassesWithStepDecorator, ...args: unknown[]) {

        // "this" and "target" is the same reference
        const className = target.constructor.name;

        if (typeof key !== 'string') {
             throw new Error('Method name is not a string');
        }

        // method name is the key
        const methodName = key;
        const argsString = args.map(arg => JSON.stringify(arg)).join(', ');

        return test.step(`${className}.${methodName}(${argsString})`, () => {
              return target.call(this, ...args);
        });
   };
 
   return descriptor;
}