appsmith's bundled ECharts tops activity risk — 5 functions to address first

Four of the five top hotspots in appsmithorg/appsmith trace to echarts@5.4.3.js, a vendored bundle committed to source. The fifth, evaluateTree, is real application logic in the data evaluation worker — CC 61, nesting depth 6, fan-out 43.

Stephen Collins ·
oss typescript refactoring code-health
Activity Risk24.42Low
Hottest Functioncv

Antipatterns Detected

complex_branching4cyclic_hub4exit_heavy3deeply_nested2god_function1long_function1middle_man1

Key Points

What is a cyclic hub and why does it matter in appsmith?

A cyclic hub is a function that sits at the centre of a call graph, dispatching to many other functions while also being called from many places — it is both a junction point and a dependency magnet. In the context of structural analysis, fan-out (the count of distinct functions a given function directly calls) is the key signal: a fan-out of 12 or higher, as seen in `z` from echarts@5.4.3.js, means a change to that function can ripple into a dozen downstream callees in unpredictable ways. The cyclic part of the label indicates the function is entangled in dependency cycles, making it hard to test in isolation and hard to reason about without tracing the full call graph. In appsmith's current snapshot, four functions carry the cyclic_hub pattern — all of them in the committed ECharts bundle, which explains the pattern geometrically: minified module graphs are dense by construction.

How do I reduce cyclomatic complexity in TypeScript?

The most effective first step is decompose-conditional: identify the largest `if`/`switch` block inside the function and extract each branch into a named helper function with a clear single responsibility. For TypeScript specifically, discriminated unions and exhaustive pattern matching via `switch` on a `kind` or `type` field can replace long chains of `if/else if` with a structure the compiler can verify for completeness. A cyclomatic complexity above 15 warrants splitting the function; above 30 — as seen with `yu` at CC 29, or `cv` at the extreme of 107 — the function should be treated as a refactoring priority before the next feature is added on top of it. A concrete first step for any fire-quadrant function in this range: run `npx ts-morph` or a similar AST tool to enumerate all return sites, then extract each exit path into its own named function, which directly reduces both cyclomatic complexity and the exit-heavy pattern at the same time.

Is appsmith actively maintained?

Yes — the fire quadrant contains 3,540 functions, meaning those functions are both structurally complex and recently active, which is a strong indicator of ongoing development. The top-ranked functions in this snapshot all show 1 commit touching them in the last 30 days and were last changed 15 days ago, though in this case that activity reflects a bundle refresh on a vendored file rather than iterative application logic. After excluding the committed ECharts library, the fire-quadrant count across the remaining 20,576-odd functions will give a cleaner picture of where active development is actually happening. High structural complexity and active maintenance are not mutually exclusive — large, actively developed codebases routinely accumulate both.

How do I reproduce this analysis?

The analysis was run against commit `99b2c28` of `appsmithorg/appsmith` using the Hotspots CLI, available at https://github.com/hotspots-dev/hotspots. After running `git checkout 99b2c28` in a local clone of the repository, execute `hotspots analyze . --mode snapshot --explain-patterns --force` to reproduce the exact results. The same command works on any local git repository without additional configuration.

What does activity-weighted risk mean?

Activity-weighted risk combines a function's structural complexity — derived from cyclomatic complexity, maximum nesting depth, and fan-out — with how frequently the function has been touched by recent commits. A function with a cyclomatic complexity of 107 that has never been committed scores much lower than one with CC 30 that is being changed every week, because the dormant complex function has lower near-term regression probability. The intuition is that complexity alone is debt; complexity plus active churn is a live regression risk, because every change to a hard-to-understand function is a change where a subtle bug can slip through. This prioritization helps teams direct refactoring effort toward functions where the probability of introducing a defect right now is highest — not just where the code looks complicated in the abstract.

Across 20,580 TypeScript functions in appsmithorg/appsmith, 1,045 fall into the critical band — and four of the five highest activity-weighted risk scores belong to the same file: app/client/public/libraries/echarts@5.4.3.js. The top function, cv, carries an activity-weighted risk score of 24.42 with a cyclomatic complexity of 107, and it sits in the “fire” quadrant — meaning it is both structurally extreme and was touched 15 days ago. That combination makes it a live regression concern, not a backlog cleanup item. The rank-3 entry, evaluateTree, is the only genuine application code in the table — it warrants its own attention after the ECharts bundle is excluded.

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
cvapp/client/public/libraries/echarts@5.4.3.js24.4107452
yuapp/client/public/libraries/echarts@5.4.3.js21.529315
evaluateTreeapp/client/src/workers/common/DataTreeEvaluator/index.ts18.861643
tcapp/client/public/libraries/echarts@5.4.3.js18.722416
Vlapp/client/public/libraries/echarts@5.4.3.js18.723517

Large Repo Analysis

appsmith is a large repository. To stay within memory constraints, this analysis used hybrid touch mode: structural complexity — CC, ND, FO — is measured precisely for every function. Git activity is tracked at the function level (via git log -L) only for files with 5 or more commits in the last 30 days; other files use a file-level approximation. Rankings therefore surface functions that are both structurally complex and in the most actively-changing parts of the codebase. Dormant code with high structural complexity will rank lower than it would under a full per-function analysis — to surface it, run hotspots analyze . --per-function-touches on a machine with sufficient memory.

Codemod / Tooling Files in Results

Four of the five functions in the top hotspots table — cv, yu, tc, and Vl — come from app/client/public/libraries/echarts@5.4.3.js, a minified third-party bundle committed directly to the repository. The fifth, evaluateTree, is application code in the data evaluation worker and is not affected by the exclusion. Minified files produce artificially extreme structural metrics because bundlers merge, inline, and rename symbols in ways that inflate cyclomatic complexity and fan-out far beyond what the original source would show. To exclude this directory and any other committed bundles in public/libraries/, add the following to your .hotspotsrc.json: { "exclude": ["app/client/public/libraries/"] }. The longer-term fix is to manage ECharts as a versioned npm dependency so it never appears in the git-tracked source tree.

Before getting into individual functions, the headline finding deserves to be stated plainly: four of the five entries in the top hotspots table resolve to app/client/public/libraries/echarts@5.4.3.js. The scoring is real — the structural complexity metrics are computed from what the parser sees — but for those four the actionable insight is architectural, not functional. evaluateTree is the exception and is addressed separately below.


cvecharts@5.4.3.js

Bottom line: a cyclomatic complexity of 107 is extreme by any measure, and cv touched 15 days ago in the fire quadrant means the scorer treats this as a live, high-priority surface. With 107 independent execution paths and fan-out of 52 — meaning cv calls into 52 distinct functions — this is structurally the centre of gravity for the entire bundle as the scorer sees it. The complex_branching and exit_heavy patterns both fire here: 107 paths means 107 minimum test cases to achieve branch coverage, and multiple early-return exits compound that burden further. An activity-weighted risk score of 24.42 is the highest in the repository at this commit.

In reality, cv is almost certainly a minified identifier — a single letter produced by a bundler’s name-mangling pass — not a meaningful function name. The 1 commit touching this file in the last 30 days, with a single author and zero bug-linked commits or reverts in the external signals, suggests the “activity” here is likely an upstream version bump or a manual bundle refresh rather than iterative development on the logic itself. The correct response is not to refactor cv; it is to exclude this file from analysis and pull ECharts via a package manager so the minified source never lives in the repo.

yuecharts@5.4.3.js

yu scores an activity-weighted risk of 21.49 with a cyclomatic complexity of 29 — moderate-to-high on its own, but amplified by the exit_heavy pattern. Twenty-nine execution paths through a minified function with 15 fan-out callees means the scorer flags it as a broad coupling point inside the bundle. Like cv, it was last changed 15 days ago in the same single commit, with one author and no historical defect signals. The risk score is real but the source is the same root cause: a committed minified bundle inflates every structural metric because minifiers merge and inline logic aggressively. I would not open this function in an editor — I would add the exclude rule.

evaluateTree — workers/common/DataTreeEvaluator/index.ts

evaluateTree is the one entry in this table that belongs to actual application logic, and it deserves more than a note about an exclusion rule. Cyclomatic complexity of 61 and maximum nesting depth of 6 place it firmly in deeply_nested and complex_branching territory: 61 independent execution paths through a function with six levels of conditional logic means every new widget type or binding edge case adds another branch to an already-dense decision tree. Fan-out of 43 — calling into 43 distinct functions — makes it a structural hub inside the evaluation worker, where a change can ripple in unpredictable directions across a wide surface of callees. After excluding the ECharts bundle, this function will rank first.

The decomposition path is clear: identify the top-level dispatch mechanism — likely a switch or if/else if chain over node types or evaluation modes — and extract each arm into a named handler with a single responsibility. That move reduces both CC and ND simultaneously and distributes the fan-out concentration across the new handlers. TypeScript discriminated unions are a natural fit for making the compiler enforce exhaustiveness on whatever the dispatch key turns out to be.

tcecharts@5.4.3.js

tc places fourth with an activity-weighted risk of 18.7, a cyclomatic complexity of 22, and fan-out of 16. The complex_branching pattern applies: 22 execution paths is consistent with a minified symbol produced by collapsing several small module-level handlers into a single renamed identifier. Nesting depth of 4 is moderate but expected for minified output. Same single-commit activity history as cv and yu — a bundle refresh rather than iterative development on the logic itself. The exclusion rule removes it; no editor tab needed.

Vlecharts@5.4.3.js

Vl closes the table at a risk score of 18.7, matching tc closely. Cyclomatic complexity 23 and nesting depth 5 with fan-out 17 give it essentially the same structural profile as tc — another bundler symbol accumulating merged branching from the original ECharts source. No independent defect history, no iterative commit pattern. Same recommendation as every other ECharts entry: add the exclusion rule and this function disappears from all future analysis runs.


Key Takeaways

  • Exclude the ECharts bundle immediately. Add "app/client/public/libraries/" to your .hotspotsrc.json exclude list. Four of the five top hotspots disappear, leaving evaluateTree as the clear rank-1 priority — and the critical-band count of 1,045 will drop to reflect only application code.
  • Audit what else lives in app/client/public/libraries/. If ECharts is committed as a flat JS file, other libraries may be too. Migrating to npm/yarn dependencies eliminates the analysis noise and keeps version bumps auditable through a lockfile rather than a binary diff.
  • After exclusion, re-run the analysis to find the real application hotspots. The 3,540 fire-quadrant functions and 1,045 critical-band functions in the current snapshot are dominated by this one file. The next run will surface the actual application logic that warrants refactoring attention.

Patterns Found

Antipatterns detected across the top functions in this snapshot:

PatternOccurrences
complex_branching4
cyclic_hub4
exit_heavy3
deeply_nested2
god_function1
long_function1
middle_man1

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.

Reproduce This Analysis

git clone https://github.com/appsmithorg/appsmith
cd appsmith
git checkout 99b2c28a83037941d2593fe1f7f4caba994d9ff7
hotspots analyze . --mode snapshot --explain-patterns --force --hybrid-touches 5

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 →

Run this on your own codebase

Hotspots runs locally in under a minute — no account, no data leaves your machine.

macOS
$ brew install Stephen-Collins-tech/tap/hotspots
Linux / cargo
$ cargo install hotspots-cli
Run in any repo
$ hotspots analyze .
★ Star on GitHub

Related Analyses