eza's CLI entry points carry the highest risk — 5 functions to fix first

In eza-community/eza, the top structural-risk functions cluster in src/main.rs and src/options/view.rs, where deep nesting and multi-exit branching intersect with live commit activity for some functions, and with long-dormant structural debt for others.

Stephen Collins ·
Generated by hotspots · free & open source
pip
$ pip install hotspots-cli
Activity Risk14.13Low
Hottest Functionrender

Antipatterns Detected

exit_heavy7long_function5god_function4complex_branching3deeply_nested1stale_complex1

Run this on your own codebase

See if your own repo has a render-style hotspot — run this in any local git repo:

pip
$ pip install hotspots-cli
npm
$ npm install -g @stephencollinstech/hotspots
Run in any repo
$ hotspots analyze .
★ Star on GitHub

Key Points

What is an exit-heavy function and why does it matter in eza?

An exit-heavy function is one with multiple return or early-exit points scattered through its body, rather than a single path to a result. It matters because every exit point is a branch a test suite has to cover independently, and it's easy for a reviewer to miss a path where cleanup or error handling doesn't run. In eza, seven of the top hotspot functions carry this tag, including `run` in `src/main.rs` (cyclomatic complexity 17) and both `deduce` overloads in `src/options/view.rs`, where flag-precedence logic returns early for each of several mode combinations.

How do I reduce nesting depth in Rust?

The direct technique is decompose-conditional: pull each nested branch's body into its own named function, which flattens the outer function to a single level of dispatch. In eza, `deduce` in `src/options/view.rs` has a nesting depth of 10 against a threshold of 4, which is the strongest single signal in this dataset — that's the function I'd start with. This function sits in the 'debt' quadrant with zero touches in the last 30 days and 160 days since its last change, so it's dormant debt rather than a live edit risk, but the blast radius on the next change is high. A concrete first step is extracting the tree- and grid-mode branches (already visible as self-contained blocks in the source) into `deduce_tree_mode` and `deduce_grid_mode` helpers, which would immediately cut the nesting by several levels without changing behavior.

Is eza actively maintained?

Yes — three of the top five hotspots sit in the 'fire' quadrant: the top-ranked `render` in `src/output/code.rs`, `run` in `src/main.rs`, and the second `deduce` overload in `src/options/view.rs`, each showing one touch in the last 30 days and zero days since their last change, which is direct evidence of ongoing development. At the same time, `print_dirs` in `src/main.rs` sits in the 'debt' quadrant and hasn't been changed in 390 days despite carrying five separate complexity flags, and the first `deduce` overload has sat untouched for 160 days with zero touches in the last 30 days. Active development and accumulated structural debt aren't contradictory here — eza is being actively worked on in its rendering and dispatch code while older, equally complex logic waits for its next touch.

How do I reproduce this analysis?

The analysis was run with the hotspots CLI against commit `471bfbc` of eza-community/eza. After running `git checkout 471bfbc`, the exact command is `hotspots analyze . --mode snapshot --explain-patterns --force`, and the same command works unmodified on any local git repository.

What does activity-weighted risk mean?

Activity-weighted risk combines structural complexity — cyclomatic complexity, nesting depth, and fan-out multiplied together — with how frequently a function has been changed recently, so functions that are both hard to follow and under active edit score highest. A function with very high complexity that hasn't been touched in over a year, like `print_dirs` at 390 days since its last change, scores lower on this measure than a moderately complex function being edited this week, because the dormant one carries lower near-term regression risk even though it's still a maintainability concern. In eza's data this is exactly why `render` in `src/output/code.rs`, with a score of 14.13 and zero days since its last change, outranks functions with similar or higher raw complexity that aren't currently being modified — the goal is to point review effort at where a bug is most likely to land next, not just where the code looks complicated on paper.

Out of 473 functions analyzed in eza-community/eza, 22 land in the critical band, and 21 sit in the ‘fire’ quadrant — meaning they’re complex and being actively edited at the same time. The top finding is render in src/output/code.rs, sitting at an activity-weighted risk score of 14.13 with zero days since its last change and one touch in the last 30 days — that’s a live regression risk, not a backlog item. Right behind it, deduce in src/options/view.rs scores 14.1 but sits in the ‘debt’ quadrant with 160 days since its last change and zero touches in the last 30 days, which tells a different story: this one is dormant structural debt with a ten-level nesting depth waiting to bite whoever touches it next.

The table below ranks functions by activity-weighted risk — a score that multiplies structural complexity by recent commit frequency. A function that is both hard to understand (high cyclomatic complexity) and actively changing is a higher priority than one that is complex but untouched. CC = cyclomatic complexity (independent execution paths); ND = max nesting depth; FO = fan-out (distinct callees).

Top 5 Hotspots

FunctionFileRiskCCNDFO
rendersrc/output/code.rs14.115317
deducesrc/options/view.rs14.16104
runsrc/main.rs13.81739
print_dirssrc/main.rs13.218413
deducesrc/options/view.rs12.51924
Triage Band Distribution
Fire21Debt51Watch31OK370

473 functions analyzed

Detected Antipatterns
Exit Heavy×7Exit Heavy
Multiple return or throw paths dispersed through the body — each exit needs separate test coverage.
Long Function×5Long Function
Function body is too long to review in a single pass; likely contains multiple distinct responsibilities.
God Function×4God Function
Calls an unusually large number of distinct functions (high fan-out), making it the structural centre of gravity for a subsystem.
Complex Branching×3Complex Branching
High cyclomatic complexity — many independent execution paths, each a potential bug surface and required test case.
Deeply Nested×1Deeply Nested
Control structures nested 4+ levels deep, making it hard to reason about the full execution state at inner branches.

Seven of the top hotspots are flagged ‘exit heavy,’ five are ‘long function,’ and four are ‘god function.’ That distribution tells me eza’s risk isn’t a single bad file — it’s a pattern repeating across the CLI’s option-parsing and rendering layers, where functions accumulate early-return branches as more flags get supported.

render — src/output/code.rs

render
src/output/code.rs
14.13
critical
CC 15
ND 3
FO 17
touches/yr 1

This is the top-ranked hotspot in the repo, and it’s in the ‘fire’ quadrant — actively changing right now, not sitting idle. The excerpt shows it building a code-statistics report: sorting languages, computing icon columns, formatting percentage strings, and picking styles conditionally based on colour and icon settings. A fan-out of 17 means it calls out to seventeen distinct functions — locale loading, icon lookup, cell formatting, string painting — a lot of surface area for one function to own. A cyclomatic complexity of 15 combined with that fan-out is what the ‘god function’ and ‘long function’ tags are picking up on. With one touch in the last 30 days and zero days since its last change, whatever is being iterated on here is happening this week, so I’d want a second reviewer on the next diff rather than waiting for a dedicated cleanup pass. My concrete recommendation: extract the styling setup (header, lang_style, count_style, dim, bar_style, total_style) into a small struct built once, separate from the per-language formatting loop — that alone should cut the fan-out meaningfully.

deduce — src/options/view.rs (nesting depth 10)

deduce
src/options/view.rs
14.1
critical
CC 6
ND 10
FO 4
touches/yr 0

This is the second-highest score in the repo, and the number that jumps out is a nesting depth of 10 — far past the depth-4 threshold where code becomes hard to reason about. The cyclomatic complexity of 6 is deceptively low; the risk here isn’t branch count, it’s how deeply those branches are wrapped inside each other while deciding between Code, Details, GridDetails, and Grid view modes based on flag combinations like --long, --grid, --tree, and --oneline. This function sits in the ‘debt’ quadrant with zero touches in the last 30 days and 160 days since its last change, so it isn’t actively being edited — it’s dormant structural debt with a high blast radius whenever someone next needs to add a view mode or flag interaction. The doc comment even states the intent explicitly: flags are scanned right-to-left and the first match wins, exactly the kind of implicit ordering logic that gets harder to verify correctly as nesting grows. The file’s history shows a bug-fix rate of 50% across 8 total commits and a review comment density of 3.2222, meaning past reviewers spent real discussion time here — worth reading before the next change. My recommendation: flatten the mode-selection logic into a match on a tuple of the four booleans, or extract the tree/grid/details branches into named helper functions, before this file sees its next flag addition.

run — src/main.rs

run
src/main.rs
13.79
critical
CC 17
ND 3
FO 9
touches/yr 1

This is the CLI’s top-level dispatch function, and it’s in the ‘fire’ quadrant with one touch in the last 30 days and zero days since its last change — active work is happening here now. A cyclomatic complexity of 17 with a fan-out of 9 lines up with the ‘exit heavy’ and ‘long function’ tags: the excerpt shows an early return for the --code summary path, then separate handling for missing files, directories versus regular files, and permission errors, each with its own early exit. That’s a lot of independent paths to hold in your head, and each one is a required test case if the goal is confidence in a change. The file’s history shows 14 total commits and a review comment density of 2.75, both signs this function already draws real reviewer attention. Given it’s changing right now, I’d treat any pending pull request touching run as a candidate for extracting the --code handling block into its own function — it’s already structurally separate in the excerpt, just not extracted.

print_dirs
src/main.rs
13.23
critical
CC 18
ND 4
FO 13
touches/yr 0

This one carries five separate pattern flags at once: complex branching, exit heavy, god function, long function, and stale complex. It sits in the ‘debt’ quadrant, untouched for 390 days, which is the ‘stale complex’ signal made literal — a function that accumulated complexity long ago and hasn’t been revisited since. A cyclomatic complexity of 18 with a nesting depth of 4 and a fan-out of 13 means it’s both branchy and broadly coupled: the excerpt shows it walking directories, handling permission-denied errors inline with a push to a denied-list, formatting path escapes, filtering and sorting children, and branching into recursive versus non-recursive tree printing. That’s several responsibilities living in one function, and thirteen distinct callees mean a change to any of them (filtering, sorting, escaping) has to be re-verified against this function’s assumptions. Because it hasn’t moved in over a year, the risk isn’t live regression — it’s that whoever eventually adds a new directory-rendering feature inherits all eighteen branches at once. I’d recommend splitting the permission-error handling and the recursion-depth logic into their own functions before the next feature lands here.

deduce — src/options/view.rs (second overload)

deduce
src/options/view.rs
12.55
critical
CC 19
ND 2
FO 4
touches/yr 1

The same file appears twice in the top five, which on its own is a signal: src/options/view.rs has two independently critical functions, one dormant and one live. This second entry shares the identical view-mode-selection logic described above but is scored separately at a cyclomatic complexity of 19 — one of the highest branch counts in the top five — with one touch in the last 30 days and zero days since last change, putting it squarely in ‘fire.’ The ‘exit heavy’ tag reflects the same early-return chain across Code, long, grid, tree, and oneline branches. Given this function is actively being edited while its sibling entry in the same file sits stale, I’d prioritize a single consolidated review pass on src/options/view.rs rather than treating the two as unrelated — a change to the flag-precedence logic in one likely has implications for the other.

Worth noting in passing: paint in src/output/file_name.rs (complexity 34, fan-out 8, one touch in the last 30 days, zero days since its last change) and render in src/output/details.rs (complexity 15, fan-out 13, also one touch in the last 30 days, zero days since its last change) didn’t make the top five but sit close behind in the ‘fire’ quadrant — both in the rendering layer, both actively changing. If a refactor sprint targets src/output/, those two belong on the same list as code.rs’s render.

Patterns Found

Antipatterns detected across the top functions in this snapshot:

PatternOccurrences
exit_heavy7
long_function5
god_function4
complex_branching3
deeply_nested1
stale_complex1

These labels belong to two tiers — Tier 1 (structural): complex_branching, deeply_nested, exit_heavy, long_function, god_function. Tier 2 (relational/temporal): hub_function, cyclic_hub, middle_man, neighbor_risk, stale_complex, churn_magnet, shotgun_target, volatile_god.

See more analyses with these patterns: complex_branching, deeply_nested, exit_heavy, god_function, long_function, stale_complex.

Reproduce This Analysis

git clone https://github.com/eza-community/eza
cd eza
git checkout 471bfbc7b03cbac8c738e8d9050edb06ee79132a
hotspots analyze . --mode snapshot --explain-patterns --force

To run the same analysis on your own codebase, run hotspots analyze . --mode snapshot in any local git repo — no configuration required.

I use Hotspots to highlight structural and activity risk — not “bad code.” I treat these findings as a prioritization aid, not a bug predictor. Editorial policy →

Was this useful? Let me know →

Related Analyses