Unfortunately, the TypeScript built-in in
operator type guard is not as powerful as you expect it to be.
From a bare object
, it will not infer that the tested property is available. It can infer that it is indeed there, only if it is already potentially available, e.g. in one of a union-ed types. I.e. the control flow will not make the tested property "appear", but only try to differentiate between union-ed types.
declare const o: object;
if ("type" in o) {
o.type // Error: Property 'type' does not exist on type 'object'.
//^? object
}
declare const u: Number | String; // Note: using class only for the sake of the demo
if ("toFixed" in u) {
u.toFixed(); // Okay
//^? Number
}
Playground Link
In your case, you could therefore specify a union for the node
argument, with a possible type of e.g. { type: unknown }
.
However, the unknown
top type absorbs all other types in a union, so it would have to be replaced by everything else, e.g. using the special type {}
to represent common types:
const nodeIsUseless = (node: undefined | null | {} | { type: unknown }) =>
node !== null &&
typeof node === "object" &&
// ^? {} | { type: unknown } | undefined
"type" in node &&
// ^? {} | { type: unknown }
typeof node.type === "string" && // Okay
// ^? { type: unknown }
node.type === "JSXText"; // Okay
Playground Link