Tags: web-dev concept

Static Analysis

Date: 2026-08-17


Examining code without running it. Linting and type checking are the familiar members; the more useful and less used ones answer questions about the codebase as a whole — what depends on what, what’s unreachable, and what’s getting worse.


Static analysis derives properties of a program from its source, without executing it.

The family

ToolQuestion
LinterDoes this match our rules? — Linting
Type checkerAre the types consistent? — Type Checking in CI
Dependency analysisWhat imports what?
Dead code detectionWhat’s never reached?
Complexity metricsWhich code is hardest to change?
Security scanningAre there known-vulnerable patterns?
Bundle analysisWhat ends up shipped? — Bundle Analysis

The first two are near-universal; the rest are where the unexploited value is.

Dependency rules

The most useful under-used capability: enforcing architectural boundaries in code rather than in a document.

INTENDED
  features/checkout  →  shared/ui
  features/checkout  →  shared/api
  features/checkout  ✗   features/account

ACTUAL, after 18 months
  everything imports everything

Architecture documented in a diagram erodes silently. Enforced by a tool, it can’t:

"import/no-restricted-paths": ["error", {
  "zones": [{
    "target": "./src/features/checkout",
    "from": "./src/features/account",
    "message": "Features must not import
                each other — use shared/"
  }]
}]

This turns a convention into a constraint, and it’s the single highest-value static analysis rule most codebases don’t have — Coupling and Cohesion.

Circular dependencies are the related check. They break tree-shaking, cause confusing initialisation-order bugs, and indicate the module boundary is wrong.

Dead code

UNUSED EXPORTS      exported, never imported
UNREACHABLE CODE    after a return
UNUSED FILES        in no import graph
UNUSED DEPENDENCIES declared, never imported
UNUSED CSS          harder — dynamic classes
                    defeat it

Tools like knip and depcheck find these, and the results are usually surprising — a mature codebase carries a lot of code nobody has referenced in years.

Deleting dead code is the cheapest possible improvement: smaller bundles, faster builds, less to read, less to maintain, fewer dependencies to keep current.

The caveat: dynamic imports, string-keyed lookups and framework conventions defeat the analysis, so verify before deleting rather than trusting the report.

Complexity metrics

CYCLOMATIC COMPLEXITY   independent paths
                        through a function

COGNITIVE COMPLEXITY    how hard it is for a
                        HUMAN to follow
                        ← the more useful of
                          the two

FILE LENGTH             crude, and correlates
FUNCTION LENGTH         with pain

Use them as a signal, never as a target. A high-complexity function is worth looking at; a rule failing the build at a threshold produces artificially split functions that are no easier to understand — and Goodhart’s law applies immediately.

The genuinely useful application is the trend. Complexity rising in a module over months indicates where the design is straining, which is a better prompt for refactoring than anyone’s opinion — Abstraction and Leaky Abstractions.

Security scanning

SAST            source scanned for
                vulnerable patterns
DEPENDENCY      known CVEs in packages
SCANNING        — Dependency Management
SECRET          credentials committed
SCANNING        — Secrets Management
IaC SCANNING    misconfigured infrastructure

See: Dependency Management · Secrets Management

Secret scanning is the one to have first, because it’s high-signal, low-noise, and the failure it prevents is severe. SAST tools are noisier and need triage discipline or they get ignored like any other wall of warnings.

Making it stick

  • Fail the build on new violations, not on the backlog. A ratchet, not an ultimatum
  • High signal-to-noise. One tool producing 30 real findings beats four producing 800 mixed ones
  • Run it where the feedback is useful — the editor first, CI as the gate
  • Delete rules nobody acts on. A permanently-failing check is a check that has been switched off socially

The recurring failure across every tool here is noise. Any output people learn to skip past has negative value, because it also hides the findings that mattered.