Freerange: The TypeScript Analyzer That Turns `console.assert` Into Proofs
A zero-annotation static analysis engine for catching NaN, division by zero, and layout bugs before they ship.
- Freerange’s most important trick is social, not mathematical: it turns `console.assert` into a static proof boundary that TypeScript engineers already understand.
- Its range engine is precise about the bugs that actually break UI code, including NaN, Infinity, division by zero, and excluded points like 0.
- The analyzer works by lowering TypeScript into a smaller IR, then propagating facts through control flow instead of just checking types.
- The audit mode matters because it turns failures into repair patterns, which makes the tool useful for humans and AI coding agents alike.
The clever part of Freerange is not that it knows math. Plenty of tools know math. The clever part is that it borrows a familiar JavaScript habit, `console.assert`, and upgrades it into a compile-time contract surface.
Introducing: Freerange, a zero-API tool that automatically deduces your code's numerical ranges. By doing so, Freerange is able to prove that e.g.: - your TS layouts obey your specified sizing - that they're free of NaNs and Infinity - that your array indices stay within bounds.
The weirdly powerful idea: `console.assert` becomes a proof boundary
Freerange treats assertions at function entry as caller requirements. Assertions inside the body become internal obligations. That split is simple, but it changes the whole shape of the problem: the tool no longer needs a new annotation language to tell it what must be true.
That is why the project feels so legible. It does not ask a TypeScript team to become a proof team. It asks them to keep writing ordinary code, then lets the analyzer infer where the boundaries are.
Why layout code is the perfect target
UI code is full of arithmetic that looks harmless and fails in ugly ways. A width divided by a column count can hit zero. A computed size can drift into `Infinity`. A branch meant to exclude invalid values can still let `NaN` leak through.
| Failure mode | Why TypeScript misses it | What Freerange tracks |
|---|---|---|
| Division by zero | `number` stays opaque | Range plus excluded points like `0` |
| NaN propagation | Valid at the type level | `mayBeNaN` and arithmetic rules |
| Infinity edge cases | Still just `number` | Upper and lower bounds with IEEE 754 behavior |
| Out-of-bounds indexing | Index is a number | Facts such as `belowLength` and `nonzero` |
How Freerange lowers TypeScript into a smaller world
Under the hood, Freerange does not analyze the full TypeScript AST as-is. It lowers code into a smaller intermediate representation, then runs the analysis over that simplified world. That matters because the smaller the language, the easier it is to reason about numeric behavior without drowning in syntax.
Inside the number domain
The core abstraction is not a raw `number`. It is an `AbstractNumber` that can carry lower and upper bounds, integer-ness, a `mayBeNaN` flag, and an `excludesPoint` constraint. That last one is quietly powerful. A range can include zero and still know that zero is impossible.
type AbstractNumber = {
lower: number;
upper: number;
integer: boolean;
mayBeNaN: boolean;
excludesPoint?: number;
};
// Conceptually: [0, 10] but not 0.
// That is more useful than a coarse "positive" tag.
The implementation also respects floating-point reality. Functions like `nextUp` and `nextDown` use typed-array tricks to find the adjacent representable double, which means strict comparisons are modeled more honestly than a hand-wavy interval checker usually would.
Facts flow through the program, not just values
This is where Freerange stops looking like a range linter and starts looking like symbolic execution. The engine tracks `valueFacts` such as `nonzero` and `belowLength`, then uses branch conditions to strengthen those facts as code paths split and reconverge.
That is also why the analysis feels useful on real UI logic. A guard does not just satisfy the analyzer. It changes what the analyzer knows downstream, including across function calls in the same file.
A tiny proof sketch
function widthFor(cols: number, total: number) {
console.assert(cols > 0);
const cell = total / cols;
if (!Number.isFinite(cell)) throw new Error("bad layout");
return cell;
}
// Freerange can treat the assertion as a caller requirement
// and the division as safe only when the fact `cols !== 0` holds.
What the audit mode adds for humans and agents
Freerange is not content to print a warning and walk away. Its audit mode maps failures to refactor guides, which makes the output much more actionable for humans and much more machine-readable for AI assistants.
The first day we introduced Freerange into our Midjourney codebase, we've caught 12 layout bugs that slipped through the best agents. Can't really blame LLMs for not catching those across thousands of files and shaky floating point calculations.
That is the bigger bet. Freerange is not only a bug finder. It is a feedback engine for code that humans and agents can both repair.
What Freerange replaces, and what it intentionally avoids
Freerange sits in a useful gap. It is lighter than refinement-type systems, which usually demand annotations and solver-backed proofs. It is also far less ceremonious than full formal verification workflows, which can be excellent but expensive to introduce into ordinary product code.
| Approach | Annotations | Solver dependence | Fit for app code | AI-agent friendliness |
|---|---|---|---|---|
| Branded types | Manual | None | Lightweight but shallow | Medium |
| Refinement types | Heavy | Usually yes | Strong, but proof tax is real | Low |
| Formal verification | Very heavy | Often yes | Excellent for critical systems | Low |
| Freerange | None for the common path | Custom analyzer, no SMT baseline | Designed for ordinary TypeScript layouts | High |
That tradeoff is the whole point. Freerange gives up the ceremony so it can reach code that teams actually ship.
Why this matters now
As more code arrives from AI tools, the bottleneck is shifting from generation to verification. Teams need safety rails that are machine-checkable, fast, and usable without a proof language in the room.
Freerange is deliberately designed to cater to a useful (and growing) subset of TypeScript... so that your code and math can meet in the middle to unlock the most proof power without much ergonomics drawbacks.
That is why Freerange feels timely. It is a bridge from vibe coding to proof engineering, built with just enough mathematical honesty to catch the bugs that matter and just enough ergonomics to fit inside a normal TypeScript workflow.





