Back to News & Insights
Web Development September 30, 2026 ยท 8 min read

TypeScript Compiler API: Preserving Child Node Narrowing in Reusable Type Guards ๐Ÿ”ง

Hoi hoi! ๐Ÿ‘‹ I'm @nyaomaru, a frontend engineer exploring new possibilities with Jev ๐Ÿ˜ธ (I'm also...

TypeScript Compiler API: Preserving Child Node Narrowing in Reusable Type Guards ๐Ÿ”ง

I'm @nyaomaru, a frontend engineer exploring new possibilities with Jev ๐Ÿ˜ธ (I'm also curious about "Decisions API" from OpenAI)

Recently, I was looking into the TypeScript Compiler API and asking a pretty specific question

Can reusable type guards preserve not only the AST node type, but also a narrowed child property?

We want to know two things: Is this a CallExpression? Is its expression an Identifier?

And honestly, if this check appears only once, I would probably leave it exactly like this.

Now suppose the same AST shape appears in several places: a visitor filter find another transformation another lint rule

Since TypeScript v5.5, simple functions can often infer a type predicate automatically.

So if we want the extracted predicate to preserve both facts, we have to describe the refined type explicitly.

It would be nice if we could compose the checks and let the type follow them. ๐Ÿ˜ธ

within the composed narrowing chain, checks one property on the parent already narrowed by ts.isCallExpression and preserves the checked child type.

Check the child once at runtime, then carry that same fact back to the parent type.

The Compiler API is simply a really good stress test for it because AST code contains this pattern everywhere.

The Compiler API is just an advanced example of a more generic composition problem.

Another thing I wanted to avoid was wrapping TypeScript's existing predicates unnecessarily.

I like keeping this separate from refineKey because absence is runtime behavior, not just a TypeScript annotation.

Suppose we want a function-like declaration whose: body exists body is a block first statement exists first statement is a return statement

We did not prove that every property from some wider key domain passed the same test.

That's why the refinement helpers intentionally work with one concrete key or index.

Want to discuss this further?

Book a free strategy call with our team to see how these insights apply to your specific business goals.

Book a consultation