DEV Community

Cover image for Solved: I’ve released a Biome plugin to prevent Typescript type assertions
Darian Vance
Darian Vance

Posted on • Originally published at wp.me

Solved: I’ve released a Biome plugin to prevent Typescript type assertions

🚀 Executive Summary

TL;DR: TypeScript’s as keyword (type assertions) can lead to silent runtime errors by overriding the compiler’s type inference without validating data shape. This article presents safer alternatives like conditional checks and type guards, culminating in the new Biome noAsAssertion plugin to enforce these practices and prevent as assertions across a codebase.

🎯 Key Takeaways

  • Type assertions (as keyword) bypass TypeScript’s type checking, creating a ‘lie’ to the compiler that can result in runtime TypeError if the actual data shape differs.
  • Type guards provide a robust, reusable, and type-safe mechanism to validate an object’s shape at runtime, allowing TypeScript to correctly infer types within conditional blocks without assertions.
  • The Biome noAsAssertion plugin offers a ‘nuclear’ option to enforce the prohibition of as assertions across a project, integrating into CI/CD to prevent future introduction of this code smell.

Tired of TypeScript’s as keyword causing runtime errors? Learn why type assertions are a code smell and how to enforce safer alternatives across your team using practices like type guards and the new Biome noAsAssertion plugin.

That TypeScript as Keyword Is a Landmine. Let’s Defuse It.

I still remember the pager going off at 3 AM. A deployment from the day before had silently corrupted user session data on prod-auth-svc-04. The culprit? A single line of code, buried deep in a utility function, that looked something like const user = sessionData as User;. The developer had “pinky promised” the TypeScript compiler that sessionData would always be a User object. But a rare edge case sent a null value down the pipe, and at runtime, our code choked. That tiny as keyword cost us a morning of frantic debugging and a whole lot of user trust. It’s a lie waiting to be exposed, and I’ve had enough of it.

The “Why”: You’re Telling, Not Asking

Before we jump into the fixes, let’s get one thing straight. The core problem with type assertions (using as or the older `` syntax) is that you are overriding the compiler’s own type inference. You are essentially telling TypeScript, “Hey, shut up, I know better than you.”

And sometimes you do! But 99% of the time, you’re just kicking a potential runtime error down the road. You’re not validating the data’s shape; you’re just forcing a label onto it. When the runtime reality doesn’t match the label you’ve slapped on, you get a classic TypeError: Cannot read properties of undefined. It’s a guarantee for future headaches.

The Solutions: From Band-Aid to Body Armor

Okay, you’re convinced. You want to stop the bleeding. Here are three ways to handle this, from a quick patch to a permanent, team-wide solution.

1. The Quick Fix: A Conditional Check

Let’s say you’re in a hurry. The pull request is due, and you just need to make the code safer without a major refactor. The simplest thing you can do is wrap your logic in a good old-fashioned if check before you use the object.

Instead of this brittle assertion:

`
function getUsername(data: unknown): string {
const user = data as { name: string };
return user.name; // This will crash if user is null or not an object!
}
`

Do this instead:

`
function getUsername(data: unknown): string | undefined {
if (data && typeof data === 'object' && 'name' in data) {
// Inside this block, TypeScript is now smart enough to know the shape!
const user = data as { name: string }; // The assertion is now much safer
return user.name;
}
return undefined; // Handle the failure case gracefully
}
`

Darian’s Take: This is still using as, but it’s a “safe” assertion. You’ve proven the shape of the object to yourself and the compiler before you make the claim. It’s an acceptable compromise in legacy code, but for new features, we can do better.

2. The Permanent Fix: The Type Guard

This is the solution I push my team to use. A type guard is a function whose return type is a special “type predicate.” It’s a reusable, clean, and explicit way to validate an object’s shape. It’s the TypeScript equivalent of “show, don’t tell.”

Let’s define our User type and a type guard for it:

`
interface User {
id: number;
name: string;
email: string;
}

// This is the type guard function!
function isUser(obj: any): obj is User {
return (
obj &&
typeof obj.id === 'number' &&
typeof obj.name === 'string' &&
typeof obj.email === 'string'
);
}

// Now let's use it
function processUserData(data: unknown) {
if (isUser(data)) {
// No 'as' needed! TypeScript knows data is a User here.
console.log(Processing user: ${data.name.toUpperCase()});
} else {
console.error("Invalid data received. Not a user object.");
}
}
`

This is beautiful. It’s self-documenting, reusable, and completely type-safe at runtime. No more lies to the compiler.

3. The ‘Nuclear’ Option: Enforce It Everywhere with Biome

Okay, you’ve written a dozen type guards, you’ve evangelized them in PR reviews, but new as assertions keep popping up. It’s time to stop asking nicely. This is where tooling comes in. There’s a new Biome plugin on the scene, no-as-assertion, that makes using as a linting error across your entire codebase.

It’s the ultimate way to enforce best practices and train your team to think in terms of type safety first. You simply add it to your biome.json configuration.

Here’s how you set it up:

`
{
"linter": {
"enabled": true,
"rules": {
"suspicious": {
"noAsAssertion": "error"
}
}
}
}
`

Once that’s in place, your CI/CD pipeline will fail any pull request that tries to introduce a new as assertion. It forces the developer to stop and think, “How can I *prove* this type instead of just asserting it?” It forces them to write a type guard or a proper validation check.

Warning: Dropping this into a large, existing codebase can be painful. You’ll likely have to fix dozens, if not hundreds, of existing assertions. I’d recommend enabling it as a ”warn” first, cleaning up the existing tech debt, and then switching it to ”error” to prevent backsliding.

Summary: Choose Your Weapon

Let’s break it down.

Solution Effort Safety Best For
1. Conditional Check Low Medium Quick patches in legacy files or hotfixes.
2. Type Guard Function Medium High All new features and any code that deals with external data (APIs, etc.). The gold standard.
3. Biome Plugin Rule High (initially) Maximum Enforcing team-wide standards and preventing future tech debt.

Stop telling the compiler what to believe and start proving your types are what you say they are. Your future self, awake and resting peacefully at 3 AM, will thank you for it.


Darian Vance

👉 Read the original article on TechResolve.blog


Support my work

If this article helped you, you can buy me a coffee:

👉 https://buymeacoffee.com/darianvance

Top comments (0)