How React-Doctor Detects Dead Code Using Knip: Internal Implementation

React-Doctor wraps Knip in a resilience layer that sanitizes configurations, retries automatically on plugin failures, and transforms raw Knip results into standardized Diagnostic objects to identify unused files, exports, and types.

React-Doctor integrates Knip to provide comprehensive dead code detection for React codebases. The implementation resides in the packages/react-doctor/src/utils/run-knip.ts module and orchestrates sophisticated monorepo handling, configuration sanitization, and robust error recovery. Understanding these internals reveals how the tool reliably identifies unused symbols across complex project structures.

The Entry Point: runKnip Function

The dead code detection flow begins with the runKnip async function exported from packages/react-doctor/src/utils/run-knip.ts:

export const runKnip = async (rootDirectory: string, entryFiles?: string[]): Promise<Diagnostic[]> => { … }

This function serves as the primary interface between React-Doctor and Knip, accepting a root directory and optional entry file overrides. It returns an array of Diagnostic objects categorized as dead code, enabling seamless integration with React-Doctor's unified reporting system.

Monorepo Handling and Configuration Discovery

Before executing Knip, the system attempts to locate the monorepo root using findMonorepoRoot. The logic in runKnipForProject (lines 66-80) handles workspace-specific configurations through a dual-path strategy:

  • Workspace-specific configuration: If a workspace contains its own knip configuration file (detected via hasKnipConfig), Knip executes directly against that folder for isolated analysis.
  • Monorepo fallback: If no local configuration exists, the system falls back to the monorepo root. If this workspace run fails, it retries on the individual project folder to ensure comprehensive coverage.

This approach ensures accurate dependency graph analysis across both isolated workspaces and shared monorepo structures.

Dependency Verification and Pre-flight Guards

React-Doctor prevents unnecessary analysis by verifying that node_modules exists through the hasNodeModules check (lines 88-94). If the dependency directory is absent, the dead code detection step skips entirely rather than failing. This guard ensures Knip receives valid resolution contexts before attempting static analysis, avoiding cryptic resolution errors in uninitialized projects.

Configuration Sanitization and Entry Point Injection

The implementation prepares the Knip session through several defensive normalization steps:

  • Options Creation: The createOptions function instantiates a Knip session with options.parsedConfig containing resolved workspace settings.
  • Pattern Sanitization: The sanitizeKnipConfigPatterns utility (located in packages/react-doctor/src/utils/sanitize-knip-config-patterns.ts) strips empty string patterns that would otherwise trigger picomatch errors during glob resolution.
  • Entry File Merging: When users provide custom entryFiles via React-Doctor configuration, these paths merge into options.parsedConfig.entry (lines 28-34). This allows custom scan entry points beyond Knip's auto-detection, supporting unconventional project structures.

Robustness: Handling Plugin Failures with Automatic Retries

Knip plugins may crash when encountering malformed configuration files. React-Doctor implements a resilient retry mechanism in run-knip.ts (lines 95-111):

  1. Error Extraction: When Knip throws, extractFailedPluginName parses the error message to identify the offending plugin.
  2. Plugin Disabling: tryDisableFailedPlugin marks that specific plugin as disabled in the parsed configuration object.
  3. Retry Logic: The system retries execution up to KNIP_TOTAL_ATTEMPTS (defined as 3 in constants.ts, line 5), progressively eliminating incompatible plugins while preserving analysis functionality for the remainder of the codebase.

This retry loop ensures that a single misconfigured plugin cannot halt the entire dead code detection process.

Silencing Console Noise

Knip and its ecosystem plugins may write directly to console.* methods. The silenced helper (lines 69-88) temporarily replaces console.log, console.warn, and console.error with no-op functions during execution. This ensures clean CLI output while React-Doctor's spinner displays "Detecting dead code…", preventing plugin noise from corrupting the terminal interface.

Transforming Knip Results into Diagnostics

After a successful Knip run produces KnipResults, React-Doctor converts raw issue records into standardized Diagnostic objects:

  • Unused Files: The collectUnusedFilePaths utility (in packages/react-doctor/src/utils/collect-unused-file-paths.ts) extracts absolute paths from Knip's files issue records.
  • Other Issues: collectIssueRecords processes exports, types, and duplicates using the KNIP_ISSUE_TYPE_DESCRIPTORS map (lines 15-62) to assign consistent messages, categories, and severity levels.
  • Diagnostic Creation: Each issue becomes a Diagnostic with plugin: "knip" and category: "Dead Code", containing file paths and specific issue metadata for unified reporting.

Integration with the Scanning Pipeline

In packages/react-doctor/src/scan.ts (lines 80-88), the dead code step executes conditionally when users enable --dead-code (enabled by default) and the run is not in diff mode. The runKnip promise merges with lint diagnostics via combineDiagnostics, feeding unified results to the scoring backend or CLI display. This integration allows dead code detection to run alongside other React-Doctor analyses in a single pass.

Usage Examples

CLI Activation

Enable or disable dead code detection via command-line flags:


# Default behavior includes dead code detection

npx react-doctor@latest /path/to/project

# Explicit control

npx react-doctor@latest /path/to/project --dead-code     # force enable

npx react-doctor@latest /path/to/project --no-dead-code  # disable

Programmatic Integration

Import runKnip directly for custom tooling:

import { runKnip } from "react-doctor";

async function listDeadCode(root: string) {
  // Optional: pass custom entry files
  const diagnostics = await runKnip(root, ["src/index.ts"]);
  
  return diagnostics.map(d => ({
    file: d.filePath,
    message: d.message,
    severity: d.severity,
  }));
}

listDeadCode("/my/project").then(console.log);

Custom Entry Files Configuration

Define additional entry points in react-doctor.config.js:

module.exports = {
  deadCode: true,
  entryFiles: ["src/main.tsx", "src/setupTests.ts"],
};

React-Doctor merges these into Knip's entry configuration during the runKnip execution (lines 28-34).

Summary

  • React-Doctor delegates dead code analysis to Knip, wrapped in a resilience layer that handles monorepos, missing dependencies, and plugin failures.
  • The runKnip function in packages/react-doctor/src/utils/run-knip.ts orchestrates configuration sanitization, entry-file injection, and result transformation.
  • Automatic retry logic disables failing plugins up to three attempts using KNIP_TOTAL_ATTEMPTS, ensuring analysis completes even with configuration errors.
  • Console silencing and dependency guards prevent noise and crashes in uninitialized projects.
  • Results convert to standardized Diagnostic objects with plugin: "knip" and category: "Dead Code" for unified reporting alongside lint results in src/scan.ts.

Frequently Asked Questions

What is Knip and why does react-doctor use it?

Knip is a static analysis tool that identifies unused files, exports, types, and duplicate symbols across JavaScript and TypeScript codebases. React-Doctor integrates Knip to provide comprehensive dead code detection without reimplementing complex dependency graph analysis, leveraging Knip's existing plugin ecosystem and configuration format.

How does react-doctor handle Knip plugin failures?

When a Knip plugin crashes due to invalid configuration, React-Doctor extracts the plugin name from the error using extractFailedPluginName, disables it in the configuration via tryDisableFailedPlugin, and retries the analysis. This process repeats up to KNIP_TOTAL_ATTEMPTS (3 times by default), progressively eliminating problematic plugins while continuing to analyze the codebase with the remaining functional plugins.

Can I customize which files are considered entry points for dead code detection?

Yes. You can specify custom entry files in your react-doctor.config.js using the entryFiles array. These paths merge into Knip's entry configuration during execution (lines 28-34 of run-knip.ts), allowing you to define additional starting points for the dependency graph traversal beyond Knip's automatic detection.

What happens if node_modules is missing when running dead code detection?

React-Doctor checks for the presence of node_modules before invoking Knip using the hasNodeModules guard (lines 88-94). If dependencies are not installed, the dead code detection step skips entirely rather than failing, preventing resolution errors and allowing the remaining diagnostic checks to continue.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →