'Typescript: Override superclass method without needing to know superclass method signature

Here's how I would reliably override a method in vanilla JS without caring about names or number of arguments, or the return value:

import EventEmitter from 'events'

// console.log event + arguments every time this emitter emits anything.
// Just an example.  
class LogEmitter extends EventEmitter {
  emit(...args) {
    console.log('emit', ...args)
    return super.emit(...args)
  }
}

But in TypeScript, I hear complaints:

import { EventEmitter } from 'events'

class LogEmitter extends EventEmitter {
  emit(...args) {                // Rest Parameter 'args' implicitly has an any[] type
    console.log('emit', ...args)
    return super.emit(...args)  // Expected at least 1 arguments, but got 0 or more.
  }
}

I'm not sure how to tell TypeScript that this is ok. I don't want to know anything about the signature of the method I'm overriding, just print whatever arguments you're passed. Means I don't have to update my signature if/when the superclass signature changes, and ideally I would have one way to type this that would work for all methods I override, with maybe an exception of telling it which method name to copy the signature from.

Something like:

The type of ...args should be whatever the Parameters are to super.emit

But my newbie attempt at this isn't valid syntax:

import { EventEmitter } from 'events'

class LogEmitter extends EventEmitter {
  emit(...args: Parameters<super.emit>) {  // 'super' can only be referenced in members of derived classes or object literal expressions.
    console.log('emit', ...args)
    return super.emit(...args)
  }
}

My workaround is to any the required first argument:

import { EventEmitter } from 'events'

class LogEmitter extends EventEmitter {
  emit(type: any, ...args: any[]) {
    console.log('emit', type, ...args)
    return super.emit(type, ...args)
  }
}

But I think this is more like patching symptoms, since now this class reports a less accurate signature. I feel like there's a better solution that gives the correct signature for emit automatically without me having to copy it from super.emit.

How can I type this perfectly without having to know anything about the signature of super.emit?



Solution 1:[1]

Unfortunately, Typescript does not (as of TS4.1) contextually type subclass members by the analogous members in super classes (or implemented interfaces). There are a bunch of similar GitHub issues, but I think the canonical one for this particular situation is microsoft/TypeScript#23911. For now, all you can do is "patch symptoms":

As a workaround, if you want to refer to the super class, you will need to do so explicitly by name (EventEmitter) and not by super, and you can use a lookup type to get the emit method:

class LogEmitter extends EventEmitter {
  emit(...args: Parameters<EventEmitter['emit']>) {
    console.log('emit', ...args)
    return super.emit(...args)
  }
}

Playground link to code

Solution 2:[2]

typeof fn lets you get the type of a function, so Parameters<typeof EventEmitter.prototype.emit> should get things working.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 jcalz
Solution 2 willis