Freerange: Static fit checks for ordinary TypeScript layout code Freerange, a static analysis tool built on the official TypeScript API, lets developers find potential NaN, Infinity, division by zero, and out-of-bounds array indexes in TypeScript code at compile time without annotations or library functions. The tool, which uses a negligible fraction of TypeScript's analysis time, is designed to help AI agents guarantee UI layouts without touching the browser and supports console.assert for static contract checking. Freerange shows you the range of every number in your TypeScript codebase, letting you find potential NaN , Infinity , division by zero, out-of-bounds array indexes, and more. Uses the official TypeScript API . Not a new language, not a fork. No annotations, no library functions. Static . Freerange works at compile build time, like TS. No need to start your app. AI agents can now guarantee UI layouts without ever touching the browser Fast . Uses a negligible fraction of TypeScript's analysis time. Robust . Adversarially tested by agents against thousands of edge cases. Freerange is deliberately designed to cater to a useful and growing subset of TypeScript, and gives concrete guidance for moving important calculations into that subset, so that your code and math can meet in the middle to unlock the most proof power without much ergonomics drawbacks. AI agents are especially well-suited to refactor such code, and we highly recommend you asking them to do so. However, if you/they do find an unsupported TS feature truly valuable, please file an issue bun install --dev @chenglou/freerange There's no API = . Your TypeScript code provides enough information for Freerange's analysis. We recommend that your agents shape code in the analysis-friendly ways described below. fr : print project errors and warnings fr --audit : print every function's contracts, plus refactor suggestions to help Freerange analyze better. Great for agents Pass a file path to either command to filter down to just that file's report. fr directly uses TypeScript under the hood, so it naturally respects your tsconfig . We output TS errors before our analysis, so technically, you can swap out your explicit tsc --noEmit command for fr and nothing changes function gridColumnCount containerWidth: number { return Math.floor containerWidth / 240 } function gridItemWidth containerWidth: number { return containerWidth / gridColumnCount containerWidth } gridItemWidth 200 bun fr outputs: index.ts:9:1 - error inferred-requirement : call to gridItemWidth violates its nonzero divisor requirement division at index.ts:6:10 How it works: Freerange follows 200 into gridItemWidth , then through the call to gridColumnCount . It works out that Math.floor 200 / 240 is 0 , then catches the later division by that result. TypeScript only knows that these values are numbers; Freerange follows their ranges through both functions. Did you know that console.log has a lesser-known sibling: console.assert ? When the assertion is true, it stays silent. When the assertion is false, it reports a failure. By itself, console.assert isn't as universally useful as console.log . Freerange changes that by analyzing console.assert statically : export function itemColumn itemIndex: number, columnCount: number : number { console.assert Number.isInteger columnCount console.assert columnCount = 1 const index = Math.max 0, Math.floor itemIndex const column = index % columnCount console.assert column < columnCount return column } In the example above, calling itemColumn 0, 2.2 produces an error columnCount should be an integer at compile time , not at runtime No need to start a browser to know that the code is wrong here. console.assert calls at the very beginning of a function, before any other statement, are caller requirements. Like parameter types, every caller must satisfy them. Any console.assert later in the function will be proven by Freerange for the function itself. Otherwise, Freerange reports an error. For simplicity and predictability, console.assert currently works only in named top-level functions and accepts simple numeric checks: Number.isInteger , Number.isFinite , Number.isNaN - Strict comparisons === , == , < , , <= , = using number literals, object paths, and array.length - References to module constants. For caller requirements, the constant must resolve to a numeric literal We also don't support aliasing console.assert , e.g. const assert = console.assert . For more complex assertions, like inline calculations, extract them into variables: js const availableWidth = frame.right - frame.left console.assert availableWidth = 0 You can strip console.assert in production with bundler or Bun's drop feature, as you may already be doing. There are infinitely many assertable things. Here are some good, non-noisy ones: - Guarantee that two UI items don't overlap: console.assert input.bottom < content.top - Guarantee that a virtualized list never renders more items than intended: js const visibleItemCount = endIndex - startIndex console.assert visibleItemCount <= MAX VISIBLE ITEM COUNT - Ensure that two separately calculated values are equal: js const frame = { input: {bottom: inputBottom}, inputTray: {bottom: inputBottom}, } console.assert frame.inputTray.bottom === frame.input.bottom Every plain number parameter, including a number field in a fixed-shape object parameter, already requires a finite, non- NaN value. Freerange also checks whether a divisor may be 0 and the other conditions shown by fr --audit . You don't need to assert the same information explicitly. Freerange supports a subset of TS: - Named, synchronous top-level functions in a file; Freerange follows calls between functions in the same file - Numbers, booleans, strings, nullable values, plain objects, tagged unions, dense arrays, and fixed tuples if / else , ternaries, non-fallthrough switch , && , || , , ?? , for , while , and for...of loops- Arithmetic, comparisons, object field and array reads, selected Math operations, and Number.isInteger , Number.isFinite , and Number.isNaN Freerange could theoretically support a much larger subset of TS, and did before its public release. Those patterns often made numeric inference and proofs much harder and slower, however, and some questions are undecidable in general. Now that AI agents write code, we strongly recommend asking agents to refactor important calculations into shapes that Freerange analyzes well, guided by fr --audit . Code that is easy to analyze tends to resemble functional programming: immutable data, explicit inputs and outputs, and clean, direct control flow. - Put important calculations in small named functions. A React component, callback, or async function can call a plain synchronous helper. Keep any helper functions that Freerange needs to inspect in the same file. export function fittedImageHeight frameWidth: number, imageWidth: number, imageHeight: number : number { const width = Math.max 1, imageWidth const height = Math.max 1, imageHeight return frameWidth height / width } function ImageCard props: {frameWidth: number; imageWidth: number; imageHeight: number} { const height = fittedImageHeight props.frameWidth, props.imageWidth, props.imageHeight return