typescript-eslint
typescript-eslint copied to clipboard
Rule proposal: no-unsafe-typeof-function
Before You File a Proposal Please Confirm You Have Done The Following...
- [X] I have searched for related issues and found none that match my proposal.
- [X] I have searched the current rule list and found no rules that match my proposal.
- [X] I have read the FAQ and my problem is not listed.
My proposal is suitable for this project
- [X] My proposal specifically checks TypeScript syntax, or it proposes a check that requires type information to be accurate.
- [X] My proposal is not a "formatting rule"; meaning it does not just enforce how code is formatted (whitespace, brace placement, etc).
- [X] I believe my proposal would be useful to the broader TypeScript community (meaning it is not a niche proposal).
Description
Unintuitively, TS narrows typeof x === 'function'
to the unsafe Function
type if x is not already constrained. However, it is safe to use on a constrained type. We should report on unconstrained callables.
Fail Cases
declare const maybeFunction: unknown;
if (typeof maybeFunction === 'function') {
// TS allows this.
maybeFunction('call', 'with', 'any', 'args');
}
Pass Cases
declare const maybeFunction: string | (() => string);
if (typeof maybeFunction === 'function') {
// TS errors if more args are provided than the declared call signature, so this is safe.
maybeFunction();
}
Additional Info
it's probably fine/good to allow typeof x === 'function
as long as x
isn't called. I'm not sure exactly where the line should be drawn between reporting and not reporting. It seems like it should be reported if we pass x
to a function that might expect a specific call signature. Maybe a heuristic could be typeof x === 'function
is ok as long as x
isn't referenced in the true
branch of the conditional?
This would probably be better as a part of no-unsafe-call
. Eg ban all calls of a Function
typed value, just like it bans all calls of an any
typed values
I think I'm ok with that, too. After a little experimentation i think that's safe as far as passing the narrowed function, too, since
declare const looselyTypedFunction: Function;
// TS error, not assignable.
const strictlyTypedFunction: () => void = looselyTypedFunction;
// ditto for passing as a parameter to functions
declare function callsWithAConcreteCallSignature(f: () => void): void;
// TS error
callsWithAConcreteCallSignature(looselyTypedFunction)
So I think Function
is only unsafe when directly called, which would get caught with no-unsafe-call
I guess the downside is just that I could see wanting no-unsafe-call
disabled but still wanting the sneaky Function
call safety