We've all been there. You're reading through a codebase and you spot it — a feature flag that's been set to false for two years, a function exported from a module that nothing ever imports, a block of logic sitting underneath a return statement that will never, ever run.
ESLint catches unused variables. TypeScript catches type mismatches. But dead business logic? That's a different beast — and that's what I built this extension to hunt.
What Is "Dead Code" Really?
Most linters think dead code = unused variable. But in production codebases the real offenders are:
1. Unreachable Logic
Code that can never execute — not because of a syntax issue, but because of your business logic:
function calculateDiscount(price: number, userType: string): number {
if (userType === "admin") {
return price * 0;
}
return price * 0.9;
// Never reached — the return above always fires
const bonus = price * 0.05;
console.log("Applying bonus:", bonus);
return price - bonus;
}
2. Unused APIs and Functions
Exported functions nobody calls. Classes defined and never instantiated. Entire modules orphaned after a refactor:
// Exported but nothing in the codebase ever calls this
export class OldReportGenerator {
generate(data: any[]) {
return data.map((d) => JSON.stringify(d)).join("\n");
}
exportToCsv(data: any[]) {
return data.join(",");
}
}
3. Obsolete Feature Flags
The slowest-burning dead code. A flag gets hardcoded, the ticket gets closed, and the condition guards code that either always runs or never runs:
const FEATURE_DARK_MODE = false; // 🚩 Has been false for 18 months
const USE_NEW_CHECKOUT = true; // 🚩 Always true — the else branch is dead
if (FEATURE_DARK_MODE) {
applyDarkTheme(); // Never runs
}
if (USE_NEW_CHECKOUT) {
showNewCheckout();
} else {
showLegacyCheckout(); // Never runs
trackLegacyCheckoutEvent();
}
Traditional static analysis tools struggle with these because they require understanding intent, not just syntax.
The Solution: AI-Powered Analysis
I built Dead Code Detector — a VS Code extension that sends your file to Claude (via the Anthropic API) with a carefully engineered prompt, gets back a structured JSON response, and renders the results as both inline diagnostics and a filterable results panel.
GitHub: github.com/naimulkarim/dead-code-detector
Here's what it looks like in action:
- 🔴 Inline squiggles in the editor (appears in your Problems panel)
- 📊 A results panel that opens beside your file with filtering, severity badges, and jump-to-line
How It Works
The Prompt Engineering
The heart of the extension is the system prompt in src/analyzer.ts. Getting an LLM to return reliable, parseable structured data requires being very explicit:
const SYSTEM_PROMPT = `You are an expert static analysis tool specializing in dead code detection.
Analyze the provided source code and identify:
1. **Unreachable Business Logic** — code after unconditional returns, inside impossible
conditions (e.g. if (false), if (1 === 2)), catch blocks that swallow everything, etc.
2. **Unused APIs / Functions** — exported or defined functions/classes/variables that appear
to never be called or referenced within the file.
3. **Obsolete Feature Flags** — boolean constants or config checks that are hardcoded to
true/false, e.g. const FEATURE_X = false used in conditions.
Respond ONLY with a JSON array (no markdown, no preamble). Each item must have:
{
"category": "unreachable-logic" | "unused-api" | "obsolete-feature-flag" | "dead-code",
"message": "Short one-line summary (< 80 chars)",
"explanation": "2-3 sentences explaining why this is dead/obsolete code",
"suggestion": "What to do — remove, refactor, or clean up",
"line": <integer line number where the issue starts, or null>,
"severity": "high" | "medium" | "low"
}
Return [] if no issues found.`;
Key decisions here:
- "Respond ONLY with a JSON array" — no preamble, no markdown fences in the model's thinking. We strip any stray fences client-side as a safety net.
- Explicit schema — defining every field prevents the model from inventing its own structure.
- "Return [] if no issues found" — so a clean file doesn't produce a parse error.
Calling the API
const response = await fetch("https://api.anthropic.com/v1/messages", {
method: "POST",
headers: {
"Content-Type": "application/json",
"x-api-key": apiKey,
"anthropic-version": "2023-06-01",
},
body: JSON.stringify({
model: "claude-sonnet-4-20250514",
max_tokens: 2048,
system: SYSTEM_PROMPT,
messages: [{ role: "user", content: userMessage }],
}),
});
const data = await response.json();
const text = data.content
.filter((b) => b.type === "text")
.map((b) => b.text)
.join("");
// Strip possible markdown fences before parsing
const clean = text.replace(/```
{% endraw %}
json|
{% raw %}
```/g, "").trim();
const results = JSON.parse(clean);
We cap the file at 12,000 characters before sending — enough for most real-world files without burning tokens unnecessarily.
Turning Results into VS Code Diagnostics
Once we have the JSON array back, we map each issue to a vscode.Diagnostic so it shows up inline in the editor and the Problems panel:
function applyDiagnostics(doc: vscode.TextDocument, results: DeadCodeResult[]) {
const diagnostics = results.map((result) => {
const line = Math.max(0, (result.line ?? 1) - 1);
const range = doc.lineAt(Math.min(line, doc.lineCount - 1)).range;
const diag = new vscode.Diagnostic(
range,
`[${result.category}] ${result.message}`,
vscode.DiagnosticSeverity.Warning
);
diag.source = "Dead Code AI";
diag.code = result.category;
return diag;
});
diagnosticCollection.set(doc.uri, diagnostics);
}
The Results Panel
Results also open in a Webview panel beside your editor. It's filterable by category and sortable by severity, with a Jump to Line button on each card that snaps the cursor to the exact location in your file.
// The webview posts a message back to the extension host
ResultsPanel.currentPanel.webview.onDidReceiveMessage(async (msg) => {
if (msg.command === "jumpTo") {
const editor = vscode.window.visibleTextEditors[0];
const pos = new vscode.Position(Math.max(0, msg.line - 1), 0);
editor.selection = new vscode.Selection(pos, pos);
editor.revealRange(
new vscode.Range(pos, pos),
vscode.TextEditorRevealType.InCenter
);
}
});
Features
- ✅ Detects unreachable logic, unused APIs, and obsolete feature flags
- ✅ Works on JS, TS, JSX, TSX, and Python
- ✅ Inline squiggles in the editor via VS Code's Diagnostics API
- ✅ Filterable results panel with severity badges and jump-to-line
- ✅ Workspace-wide scan (with a confirmation prompt for large repos)
- ✅ Configurable severity level (
error/warning/information/hint) - ✅ Toggle individual checks on/off in settings
- ✅ Works via environment variable (
ANTHROPIC_API_KEY) or Settings UI
Installation & Setup
Prerequisites
- Node.js v18+
- VS Code 1.85+
- An Anthropic API key — get one at console.anthropic.com
From GitHub
# Clone the repo
git clone https://github.com/naimulkarim/dead-code-detector.git
cd dead-code-detector
# Install dependencies
npm install
# Open in VS Code
code .
Then press F5 — this opens a new Extension Development Host window with the extension loaded.
Add Your API Key
Option A — Settings UI (recommended):
- Open Settings (
Cmd+,on Mac /Ctrl+,on Windows) - Search for
Dead Code - Paste your key into Dead Code Detector: Anthropic Api Key
Option B — Environment variable:
export ANTHROPIC_API_KEY=sk-ant-api03-...
Usage
| Action | Shortcut / How |
|---|---|
| Analyze current file |
Cmd+Shift+D / Ctrl+Shift+D
|
| Analyze current file | Right-click in editor → Dead Code: Analyze Current File |
| Analyze entire workspace | Command Palette → Dead Code: Analyze Entire Workspace |
| Clear diagnostics | Command Palette → Dead Code: Clear All Diagnostics |
Test It Immediately
The repo includes sample-test.ts — a file pre-loaded with intentional dead code of all three types. Open it and hit Cmd+Shift+D to see the extension in action right away.
Configuration
// .vscode/settings.json or user settings
{
"deadCode.anthropicApiKey": "sk-ant-...",
"deadCode.severity": "warning",
"deadCode.checks": {
"unreachableLogic": true,
"unusedApis": true,
"obsoleteFeatureFlags": true
}
}
Architecture Overview
User triggers Cmd+Shift+D
↓
extension.ts
→ reads active document text
→ reads enabled checks from config
↓
analyzer.ts
→ builds user prompt with language + filename + code
→ POST /v1/messages → Claude Sonnet
→ strips markdown fences, JSON.parse()
→ returns DeadCodeResult[]
↓
extension.ts
→ vscode.DiagnosticCollection.set() → squiggles in editor
↓
resultsPanel.ts
→ Webview HTML panel with filter buttons + severity cards
→ postMessage() → jump-to-line on click
What's Next
A few things I want to add:
- [ ] Quick Fix actions — a code action that lets you delete the dead code with one click
- [ ] Sidebar tree view — persistent panel showing all issues across the workspace
- [ ] Git blame integration — show who added the dead code and when
- [ ] Caching — skip re-analyzing files that haven't changed since last scan
- [ ] More languages — Go, Ruby, Rust
PRs and issues are very welcome at github.com/naimulkarim/dead-code-detector.
Links
- 🐙 GitHub: github.com/naimulkarim/dead-code-detector
- 🤖 Anthropic API docs: docs.anthropic.com
- 🔑 Get an API key: console.anthropic.com
- 📦 VS Code Extension API: code.visualstudio.com/api
Top comments (0)