Skip to main content
Dependency analysis is the process of examining relationships between modules in a codebase to understand structure, find issues, and enforce architectural rules.

What is Dependency Analysis?

In modern JavaScript and TypeScript projects, files import and export functionality from each other. These import relationships form a dependency graph where:
  • Nodes represent files or modules
  • Edges represent import statements
  • Direction shows which file depends on which
For example:
// components/Button.tsx
import { formatText } from '../utils/text'
import './Button.css'

export function Button() { ... }
This creates edges:
  • Button.tsxutils/text.ts
  • Button.tsxButton.css

Why Dependency Analysis Matters

As codebases grow, understanding dependencies becomes critical:

🔍 Find Dead Code

Files and functions that are never imported can be safely removed, reducing bundle size and maintenance burden.

🔄 Detect Circular Dependencies

When File A imports File B, and File B imports File A (directly or transitively), it creates initialization issues and makes refactoring difficult.

🏗️ Enforce Architecture

Ensure UI components don’t import from data layers, or that public APIs don’t depend on internal implementation details.

📦 Optimize Dependencies

Identify unused npm packages, missing declarations, or accidental usage of dev dependencies in production code.

Types of Issues Rev-dep Detects

Circular Dependencies

What it is: A cycle in the import graph where modules depend on each other in a loop.
// services/auth.ts
import { logger } from './logger'
export function authenticate() { logger.log('auth') }

// services/logger.ts  
import { authenticate } from './auth' // ❌ Circular!
export function logger() { authenticate() }
Why it’s problematic:
  • Causes initialization order issues
  • Can lead to runtime errors with undefined values
  • Makes code harder to understand and refactor
How Rev-dep detects it:
// From circularDeps.go:18
var dfs func(node string) bool
dfs = func(node string) bool {
    visited[node] = true
    recStack[node] = true  // Mark as in current path
    
    for _, dep := range nodeDeps {
        if recStack[depPath] {
            // Found a cycle - this dependency is already in our path!
            cycleStart := findCycleStart(path, depPath)
            cycles = append(cycles, extractCycle(path, cycleStart))
        }
    }
    
    recStack[node] = false  // Remove from current path
}

Orphan Files (Dead Code)

What it is: Files that are never imported by any entry point. Why it’s problematic:
  • Increases repository size
  • Confuses developers about what code is active
  • Wastes CI/CD resources building/testing unused code
How Rev-dep detects it: Rev-dep inverts the problem: instead of looking for unused files, it finds all used files by traversing from entry points, then reports everything else as orphaned.
// Entry point: src/index.ts
import { Component } from './components/Component'

// src/components/Component.tsx - ✅ Reachable
export function Component() { ... }

// src/components/OldComponent.tsx - ❌ Orphaned (nothing imports it)
export function OldComponent() { ... }

Unused Exports

What it is: Exported functions, classes, or variables that are never imported anywhere.
// utils/math.ts
export function add(a, b) { return a + b }  // ✅ Used elsewhere
export function multiply(a, b) { return a * b }  // ❌ Never imported
Why it’s problematic:
  • Creates maintenance burden
  • Increases bundle size (if not tree-shaken)
  • Misleads developers about API surface

Unused Node Modules

What it is: Packages declared in package.json but never imported in code.
{
  "dependencies": {
    "lodash": "^4.17.21",  // ❌ Never used
    "react": "^18.2.0"     // ✅ Used in code
  }
}
Why it’s problematic:
  • Increases node_modules size
  • Slows down npm install
  • Creates unnecessary security audit surface
  • Wastes disk space in deployed containers

Missing Node Modules

What it is: Imports from packages not declared in package.json.
// components/Button.tsx
import React from 'react'  // ✅ Declared in dependencies
import _ from 'lodash'     // ❌ Not in package.json
Why it’s problematic:
  • Code works locally (if package is hoisted) but fails in CI or production
  • Violates package manager workspace resolution rules
  • Creates hidden coupling between packages in monorepos

Unresolved Imports

What it is: Import statements that can’t be resolved to actual files.
import { helper } from './helpers'  // ❌ No ./helpers.ts file exists
import { utils } from '@utils'      // ❌ TypeScript alias misconfigured
Why it’s problematic:
  • Causes build failures
  • Breaks at runtime
  • Often happens after refactoring moves

Dev Dependencies in Production

What it is: Imports from devDependencies in production code paths.
{
  "dependencies": { "react": "^18.0.0" },
  "devDependencies": { "@testing-library/react": "^14.0.0" }
}
// src/pages/index.tsx (production code)
import { render } from '@testing-library/react'  // ❌ This is a devDependency!
Why it’s problematic:
  • devDependencies aren’t installed in production environments
  • Causes runtime errors
  • Increases bundle size with test utilities
How Rev-dep detects it: Rev-dep traces from production entry points and checks if any imported modules are listed in devDependencies:
// This is OK - tests can use devDependencies
// tests/Button.test.tsx
import { render } from '@testing-library/react'  // ✅ Test file

// This is BAD - production code shouldn't
// src/Button.tsx  
import { render } from '@testing-library/react'  // ❌ Production file

Rev-dep’s Approach

Rev-dep takes a static analysis approach:

1. Parse Source Code

Custom-built lexer extracts all import/export statements without executing code:
// From parseImports.go:1358
func ParseImportsByte(code []byte, ignoreTypeImports bool, mode ParseMode) []Import {
    // Scan for import/export/require keywords
    for i < n {
        switch code[i] {
        case 'i':
            if next, ok := state.parseImportStatement(i); ok { ... }
        case 'e':
            if next, ok := state.parseExportStatement(i); ok { ... }
        case 'r':
            if next, ok := state.parseRequireStatement(i); ok { ... }
        }
    }
}

2. Resolve Import Paths

Convert import requests to absolute file paths:
import { Button } from '@components/Button'
// ↓ Resolves to ↓
/Users/project/src/components/Button.tsx
Handles:
  • TypeScript path aliases (@components/*)
  • Node module resolution (node_modules lookup)
  • Relative paths (../utils)
  • Package.json exports
  • Monorepo workspace packages

3. Build Dependency Graph

Construct in-memory representation of all relationships:
// From buildDepsGraph.go
type SerializableNode struct {
    Path     string   // Absolute file path
    Children []string // Files this imports
    Parents  []string // Files that import this
    Modules  []string // External packages used
}

4. Run Analysis Algorithms

Each check uses graph algorithms:
  • Circular dependencies - Depth-first search with recursion tracking
  • Orphan files - Inverse reachability from entry points
  • Unused exports - Cross-reference all exports with all imports
  • Module checks - Compare graph edges with package.json

Type-Only Imports

Rev-dep distinguishes between runtime and type imports:
import type { User } from './types'  // Type-only import
import { formatUser } from './utils' // Runtime import
Many checks support ignoreTypeImports flag:
  • Circular dependency detection can ignore type cycles (often safe)
  • Orphan detection can exclude files only imported for types
  • Module checks can allow type-only imports from devDependencies

Configuration Approach

Rev-dep’s config system allows running multiple checks in a single pass, analyzing the dependency graph once and applying all rules in parallel. This is significantly faster than running individual commands separately.

Entry Points

Many analyses start from entry points - files that aren’t imported by anything else:
  • Application entry points: src/index.ts, src/main.tsx
  • Page routes: pages/**/*.tsx (Next.js, etc.)
  • Test files: **/*.test.ts
  • Build scripts: scripts/**/*.ts
Rev-dep can auto-detect entry points or accept explicit patterns:
rev-dep config run  # Uses configured entry points
rev-dep files --entry-point src/index.ts  # Explicit entry point
For accurate analysis, ensure you configure all entry points including test files, scripts, and framework-specific entry points like Next.js pages.

Build docs developers (and LLMs) love