TypeScript

TypeScript Code Review with LGTM

AI code review for TypeScript: LGTM catches type-narrowing bugs, unsafe assertions, async race conditions, React stale-closure bugs, and unused exports — across the diff and the wider repo context.

How LGTM reviews TypeScript PRs

When you connect a TypeScript repo to LGTM, our tree-sitter indexer parses every .ts/.tsx file and builds a symbol graph: exported functions, types, interfaces, classes, React components, and the imports that connect them. On a PR, personalised PageRank ranks the symbols related to the diff and the 6 specialist agents (Bugs, Security, Performance, Readability, Best-Practices, Documentation) see the changed code with its actual repo context attached.

TypeScript-specific signals our agents look at: type narrowing inside conditions (the classic 'I checked for undefined above but TS still says possibly undefined' confusion), unsafe `as` casts, `any` introductions, missing return types on exported functions, untyped event handlers, React hook dependency arrays, and async functions whose promises aren't awaited.

Findings come back as inline GitHub PR comments anchored to the changed lines. False positives get filtered by the synthesizer using a confidence threshold — TypeScript projects with `strict: false` get a more relaxed pass than projects with `strict: true` because the same pattern means different things in those modes.

Common TypeScript bugs LGTM catches

Type assertions that lie. `const user = data as User` — but data is actually `unknown` from an API response. LGTM flags assertions on inputs that haven't been validated upstream.

Narrowing gone wrong. After `if (typeof x === 'string')`, calling a callback that closes over x — the narrowing doesn't propagate. The Bugs agent recognises this pattern.

Async functions not awaited. `async function save() { db.write() }` — db.write() returns a Promise that floats untouched. Easy to miss in review; the agent catches missing `await` on Promise-returning calls.

Stale closures in React. `useEffect(() => { setTimeout(() => doThing(state), 1000) }, [])` — state captured at mount, doThing runs with stale data. The agent reads hook dependency arrays and the closure body together.

Discriminated union exhaustiveness. `switch (action.type)` with a default fall-through that should be `never`. Catches off-by-one when a new action type is added.

Unused exported symbols. A `export function helper()` that no other file imports — flagged for cleanup. Crosses the diff boundary because the indexer sees the whole repo.

Tree-sitter coverage for TypeScript

Tree-sitter's TypeScript grammar is mature — it's the language tree-sitter was originally co-developed against (Atom/GitHub). All major constructs parse cleanly: ESM imports, CommonJS requires, JSX/TSX, generics, conditional types, mapped types, decorators.

Edge cases: deeply nested template literal types parse but generate noisy trees. Heavy macro-like generic abuse (libraries doing type-level computation) can produce ERROR nodes in places — LGTM falls back to diff-only review for those files and notes it in the review summary.

Monorepo support: TypeScript project references via `tsconfig.json` are followed. Each package gets its own symbol graph, with cross-package imports linking them. NX-style monorepos work without configuration; Turborepo same.

Setup notes for TypeScript projects

Install the LGTM GitHub App on your TypeScript repos. The first index run takes 30-90 seconds for a typical project, 2-5 minutes for a large monorepo. After that, incremental updates run on every push to default branch — only changed files re-parse.

Generated TypeScript (Prisma client, GraphQL codegen, etc.) is auto-detected by common heuristics (`/generated/`, `.gen.ts`, `// THIS FILE IS AUTO-GENERATED`) and excluded from review. If you have custom-generated paths, add them to `.lgtmignore` at the repo root.

BYOK works as expected — your OpenAI / Anthropic / Gemini key handles the LLM call. Per-repo model overrides let you pin GPT-4o-class on a critical service repo and Haiku/Mini on a marketing site. The same key, different models per repo.

Example bugs LGTM catches

Stale closure bug LGTM catches
// ❌ Bug: state captured at mount, never updates
function Counter() {
  const [count, setCount] = useState(0);
  useEffect(() => {
    setInterval(() => console.log(count), 1000);
  }, []); // <-- missing 'count' dep
}

// LGTM finding:
// "setInterval closure references 'count' but the effect's
//  dependency array is empty. Add 'count' to deps or use a
//  functional setter to read latest value."
Unsafe cast on API data
// ❌ Bug: assuming structure without validation
async function load() {
  const res = await fetch('/api/user');
  const user = (await res.json()) as User; // <-- LIES
  return user.email.toLowerCase();         // could crash
}

// LGTM finding:
// "fetch().json() returns 'unknown'. Cast asserts type
//  without runtime check — recommend zod/yup schema OR
//  defensive access pattern."

See LGTM's full code review pipeline

6 specialists · synthesizer · tree-sitter context · BYOK

Go to the product page

TypeScript review FAQs

Does LGTM support React + TSX?

Yes. Tree-sitter's TypeScript grammar handles JSX/TSX cleanly. The Bugs and Performance agents have specific React heuristics: hook dependency arrays, stale closures, key prop missing on lists, useEffect with async function pattern, useMemo/useCallback misuse.

What about Vue + Svelte + Solid?

TypeScript inside Vue SFCs and Svelte components is partially supported — the .vue/.svelte files don't fully parse with tree-sitter's TS grammar, so the agent gets less context. Pure .ts/.tsx files work fully. Solid uses .tsx and works the same as React.

How does LGTM compare to ESLint + tsc for catching bugs?

Complementary. ESLint + tsc catch deterministic violations (rule R triggered on file F). LGTM's AI agents catch judgment-call patterns: 'this looks like a stale closure', 'this cast is suspicious given the upstream code', 'this naming choice will confuse future readers'. Run both — they don't overlap much.

Can I use my own OpenAI / Anthropic / Gemini key for TS reviews?

Yes — BYOK is the default model. Add your provider key in Settings → AI Providers, pick your default model, optionally override per-repo. Token spend goes to YOUR provider account, not LGTM's.

What's the cost for a typical TypeScript review?

Token cost depends on diff size and model. A 300-line TS PR on GPT-4o costs about $0.08-$0.15 in tokens (paid to OpenAI via your BYOK key). Switch to Claude Haiku or GPT-4o-mini and the same review drops to ~$0.01. LGTM's flat subscription fee (₹399/mo Pro, or free for 20 reviews/mo) covers the orchestration; tokens are your direct provider cost.

Related across LGTM

Other languages