bash Refactoring October 1, 2025

Dead Code Finder

A script that detects unused exports, unreferenced files, and dead code in TypeScript projects using the TypeScript compiler API.

dead-coderefactoringtypescriptcleanupautomation

Description

Uses ts-prune and custom analysis to find dead code in TypeScript projects — unused exports, orphan files, and unreferenced modules. Outputs a prioritized cleanup list.

Features

  • Unused exports detection via ts-prune
  • Orphan file detection — files not imported anywhere
  • Dead dependency check — packages in package.json not imported in source
  • JSON output for CI integration

The Script

#!/bin/bash
# dead-code-finder.sh — Find unused exports and orphan files

set -euo pipefail

SRC_DIR="${1:-src}"
REPORT_FILE="dead-code-report.json"
VIOLATIONS=0

echo "🔍 Dead Code Finder"
echo "   Source: $SRC_DIR"
echo ""

# ── 1. Unused Exports (ts-prune) ──
echo "📦 Checking unused exports..."
if command -v npx &> /dev/null && npx ts-prune --help &> /dev/null 2>&1; then
  UNUSED_EXPORTS=$(npx ts-prune --project tsconfig.json 2>/dev/null | grep -v '(used in module)' || true)
  UNUSED_COUNT=$(echo "$UNUSED_EXPORTS" | grep -c "." || echo "0")

  if [ "$UNUSED_COUNT" -gt 0 ]; then
    echo "   ⚠️  $UNUSED_COUNT unused export(s) found:"
    echo "$UNUSED_EXPORTS" | head -20 | while IFS= read -r line; do
      echo "      $line"
    done
    if [ "$UNUSED_COUNT" -gt 20 ]; then
      echo "      ... and $((UNUSED_COUNT - 20)) more"
    fi
    VIOLATIONS=$((VIOLATIONS + UNUSED_COUNT))
  else
    echo "   ✅ No unused exports"
  fi
else
  echo "   ⏭️  ts-prune not available, skipping export check"
  echo "   Install: npm install -D ts-prune"
fi

echo ""

# ── 2. Orphan Files ──
echo "📄 Checking for orphan files..."

# Get all TS/JS source files
ALL_FILES=$(find "$SRC_DIR" -type f \( -name "*.ts" -o -name "*.tsx" -o -name "*.js" -o -name "*.jsx" \) \
  ! -path "*/node_modules/*" \
  ! -path "*/__tests__/*" \
  ! -path "*/test/*" \
  ! -name "*.test.*" \
  ! -name "*.spec.*" \
  ! -name "*.d.ts" \
  ! -name "index.ts" \
  ! -name "index.js" \
  ! -name "main.ts" \
  ! -name "app.ts" \
  | sort)

ORPHANS=""
ORPHAN_COUNT=0

for file in $ALL_FILES; do
  # Get the module name without extension
  base=$(basename "$file" | sed 's/\.\(ts\|tsx\|js\|jsx\)$//')
  dir=$(dirname "$file")

  # Check if this file is imported anywhere
  IMPORT_CHECK=$(grep -rl --include="*.ts" --include="*.tsx" --include="*.js" --include="*.jsx" \
    -E "(from|import).*['\"].*${base}['\"]" "$SRC_DIR" 2>/dev/null | grep -v "$file" | head -1 || true)

  if [ -z "$IMPORT_CHECK" ]; then
    ORPHANS="$ORPHANS\n      $file"
    ORPHAN_COUNT=$((ORPHAN_COUNT + 1))
  fi
done

if [ "$ORPHAN_COUNT" -gt 0 ]; then
  echo "   ⚠️  $ORPHAN_COUNT potentially orphaned file(s):"
  echo -e "$ORPHANS"
  VIOLATIONS=$((VIOLATIONS + ORPHAN_COUNT))
else
  echo "   ✅ No orphan files detected"
fi

echo ""

# ── 3. Unused Dependencies ──
echo "📦 Checking for unused dependencies..."

if [ -f "package.json" ]; then
  DEPS=$(node -e "
    const pkg = require('./package.json');
    const deps = Object.keys(pkg.dependencies || {});
    deps.forEach(d => console.log(d));
  " 2>/dev/null || true)

  UNUSED_DEPS=""
  UNUSED_DEP_COUNT=0

  for dep in $DEPS; do
    # Skip framework deps that might be used implicitly
    if [[ "$dep" == @types/* ]] || [[ "$dep" == typescript ]] || [[ "$dep" == eslint* ]]; then
      continue
    fi

    # Check if the dep is imported in source code
    IMPORT_FOUND=$(grep -rl --include="*.ts" --include="*.tsx" --include="*.js" --include="*.jsx" \
      -E "(from|require|import).*['\"]${dep}['\"/]" "$SRC_DIR" 2>/dev/null | head -1 || true)

    if [ -z "$IMPORT_FOUND" ]; then
      UNUSED_DEPS="$UNUSED_DEPS\n      $dep"
      UNUSED_DEP_COUNT=$((UNUSED_DEP_COUNT + 1))
    fi
  done

  if [ "$UNUSED_DEP_COUNT" -gt 0 ]; then
    echo "   ⚠️  $UNUSED_DEP_COUNT potentially unused dependencie(s):"
    echo -e "$UNUSED_DEPS"
    VIOLATIONS=$((VIOLATIONS + UNUSED_DEP_COUNT))
  else
    echo "   ✅ No unused dependencies detected"
  fi
else
  echo "   ⏭️  No package.json found"
fi

echo ""

# ── Summary ──
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
if [ "$VIOLATIONS" -gt 0 ]; then
  echo "$VIOLATIONS potential dead code item(s) found"
  echo "   Review and remove what's truly unused."
  exit 1
else
  echo "✅ No dead code detected — codebase is clean!"
  exit 0
fi

Usage

# Make executable
chmod +x dead-code-finder.sh

# Run against src/
./dead-code-finder.sh src

# Run in CI (fails on violations)
./dead-code-finder.sh src || echo "Dead code found — please clean up"