prune

How It Works

Architecture and analysis pipeline — from file collection to reported findings.

Prune's analysis pipeline has four stages: file collection, AST/data collection, rule application, and output formatting.


Overview

prune.yaml scan.CollectWithContext()walks filesystem, applies include/exclude globs js.Collector.Collect()reads files, parses AST, extracts symbols/imports js.applyRules()correlates definitions/imports and emits findings report.Formatterformats findings as pretty, json, or ndjson

Stage 1 — File Collection (internal/scan)

The scanner discovers candidate files from scan.paths, applying scan.include and scan.exclude globs.

Collection flow:

  1. Walk each configured root path.
  2. Skip excluded directories early.
  3. Keep files matching include globs.
  4. Remove any files matching exclude globs.

Relative paths are normalized with forward slashes and used as canonical IDs across the pipeline.


Stage 2 — AST and Data Collection (internal/lang/js)

The Collector reads each file and builds a shared Collected structure for rules.

Parsing

Prune uses Tree-sitter grammars by extension:

ExtensionGrammar
.js, .jsxJavaScript
.tsTypeScript
.tsxTSX

If parsing fails for a file, Prune falls back to regex extraction so analysis still proceeds.

Extracted data

Per file, the collector extracts:

  • imports/import specs and resolved local targets
  • exports and export symbols
  • function and variable declarations
  • identifier counts and usage counts
  • feature-flag pattern hits
  • dynamic indicators (eval, Function, require, import( and configured high-risk patterns)

Import resolution and aliases

Resolver behavior:

  • relative imports resolve against file index (.ts/.tsx/.js/.jsx and index.* fallback)
  • alias imports resolve using ts_config.baseUrl + ts_config.paths
  • path aliases use longest-prefix matching (TypeScript-like behavior)
  • unresolved aliases/imports are marked with lower confidence

Stage 3 — Rule Application (internal/lang/js/rules.go)

Rules run in this order:

  1. unused_file
  2. unused_export
  3. unused_function and unused_variable
  4. possible_dynamic_usage
  5. suspicious_dynamic_usage

unused_file

Emits a finding when a file is not imported and is not matched as an entrypoint.

unused_export

Emits when an exported symbol has no importing usage. For entrypoint files, the default export is skipped entirely (not flagged as unused) — only named exports on entrypoints are considered. Named entrypoint exports are downgraded to review confidence since they may be consumed at runtime.

unused_function and unused_variable

A symbol is considered unused when declaration/usage counters show it is declared but not referenced. Exported symbols are excluded from these rule findings.

If high-risk dynamic indicators are present, confidence is downgraded using if_high_risk_dynamic.

possible_dynamic_usage

Finds feature-flag-like usage based on feature_flags.patterns and emits findings for patterns with uncertain/no static references.

This rule replaced dead_feature_flag, but legacy config keys are still recognized.

suspicious_dynamic_usage

Emits findings for dynamic APIs/patterns configured in rules.suspicious_dynamic_usage.patterns.

Confidence levels

All findings are assigned one of:

LevelMeaning
safeHigh certainty dead code candidate
likely_deadLikely dead, verify before deletion
reviewLow certainty due to dynamic/entrypoint ambiguity

Stage 4 — Output Formatting (internal/report)

After filtering by --min-confidence, findings are formatted as:

  • pretty (or table, alias): human-readable grouped output
  • json: structured report with summary, findings, and metadata
  • ndjson: one finding per line

The pretty formatter also supports --compact, --only, and --deletable display filters.


Streaming Mode

When streaming is enabled, Prune processes files in batches controlled by:

  • scan.stream.interval_ms / --stream-interval
  • scan.stream.batch_size / --stream-batch-size

If output format is ndjson, each batch is written immediately via stream handler. For non-ndjson formats, findings are still accumulated and printed at the end.

On this page