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
Stage 1 — File Collection (internal/scan)
The scanner discovers candidate files from scan.paths, applying scan.include and scan.exclude globs.
Collection flow:
- Walk each configured root path.
- Skip excluded directories early.
- Keep files matching include globs.
- 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:
| Extension | Grammar |
|---|---|
.js, .jsx | JavaScript |
.ts | TypeScript |
.tsx | TSX |
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/.jsxandindex.*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:
unused_fileunused_exportunused_functionandunused_variablepossible_dynamic_usagesuspicious_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:
| Level | Meaning |
|---|---|
safe | High certainty dead code candidate |
likely_dead | Likely dead, verify before deletion |
review | Low certainty due to dynamic/entrypoint ambiguity |
Stage 4 — Output Formatting (internal/report)
After filtering by --min-confidence, findings are formatted as:
pretty(ortable, alias): human-readable grouped outputjson: structured report withsummary,findings, andmetadatandjson: 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-intervalscan.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.