The dominant risk story in this snapshot of nolimits4web/swiper is structural debt sitting quietly in src/modules/mousewheel/mousewheel.mjs and src/core/update/updateSlides.mjs — functions that haven’t been modified in 97 days but carry cyclomatic complexity scores that would challenge any engineer asked to change them tomorrow. Across 552 total functions, 79 are rated critical and 178 fall into the debt quadrant, meaning they are structurally complex but currently dormant. The one live-fire risk is onTouchMove, with a cyclomatic complexity of 127 and three commits in the last 30 days — I would treat that as the most urgent item precisely because structural complexity and active development are colliding there right now.
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
| Function | File | Risk | CC | ND | FO |
|---|---|---|---|---|---|
handle | src/modules/mousewheel/mousewheel.mjs | 17.9 | 61 | 6 | 29 |
Mousewheel | src/modules/mousewheel/mousewheel.mjs | 17.7 | 45 | 6 | 40 |
updateSlides | src/core/update/updateSlides.mjs | 17.5 | 105 | 5 | 44 |
onTouchMove | src/core/events/onTouchMove.mjs | 17.3 | 127 | 4 | 29 |
Pagination | src/modules/pagination/pagination.mjs | 17.3 | 44 | 5 | 61 |
Large Repo Analysis
swiper 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.
Risk landscape
552 functions analyzed
Multiple return or throw paths dispersed through the body — each exit needs separate test coverage.God Function×9God Function
Calls an unusually large number of distinct functions (high fan-out), making it the structural centre of gravity for a subsystem.Long Function×9Long Function
Function body is too long to review in a single pass; likely contains multiple distinct responsibilities.Complex Branching×7Complex Branching
High cyclomatic complexity — many independent execution paths, each a potential bug surface and required test case.Deeply Nested×5Deeply Nested
Control structures nested 4+ levels deep, making it hard to reason about the full execution state at inner branches.
Of 552 analysed functions, 352 are quiet and low-complexity — not worth immediate attention. The 178 in the debt quadrant are the slow-burn concern: structurally dense code that nobody has touched recently, accumulating risk for whenever the next feature or bug fix requires opening those files. Twelve functions are in the fire quadrant, meaning they are both complex and actively changing. The five functions below represent the sharpest points of that overall distribution.
handle — src/modules/mousewheel/mousewheel.mjs
handle is the primary wheel event dispatcher in the mousewheel module. Based on the source excerpt, it receives a raw DOM WheelEvent, reconciles it against jQuery-wrapped originals, normalises delta values across horizontal and vertical axes, applies RTL direction factors, clamps translation positions to min/max boundaries, manages recentWheelEvents for snap-timing logic, and decides whether to propagate the event to a parent swiper or stop it entirely. That is a lot of distinct responsibilities compressed into one function.
The cyclomatic complexity of 61 means there are at least 61 independent execution paths through this function — each one a required test case and a potential site for a regression. A nesting depth of 6 compounds this: the deepest conditionals sit inside loops or other conditionals that themselves depend on params.loop, params.nested, params.freeMode, and RTL state simultaneously. The fan-out of 29 distinct callees means a change to any one of those collaborators can ripple back here in ways that aren’t obvious from a diff.
What makes this a structural debt risk rather than an emergency today is the 97 days of silence. No commits, no authors active in the last 90 days. The file’s entire commit history amounts to a single commit, and 100% of the commits on this file are tagged as bug fixes — meaning the one time someone did work here, it was to correct a defect. That is thin institutional coverage for a function this complex.
My recommendation: extract the delta-normalisation logic (currently inline and handling legacy wheelDelta, detail, deltaMode, and shift-key cases) into a standalone normalizeDelta(event) function, and pull the snap-timing / recentWheelEvents management into its own handler. Either extraction alone would cut the path count meaningfully and make the remaining routing logic far easier to audit.
Mousewheel — src/modules/mousewheel/mousewheel.mjs
Mousewheel is the module factory function — the outer container that calls extendParams, sets up closure-scoped state (timeout, lastScrollTime, lastEventBeforeSnap, recentWheelEvents), defines all inner functions including normalize and handle, and wires lifecycle event listeners. Its fan-out of 40 is the second highest in this top five, and in JavaScript that number likely undercounts the true coupling: dynamic property lookups on swiper.params.mousewheel.* and callback-style event registration hide dependencies that static analysis cannot fully resolve.
The CC of 45 inside what is essentially a module-initialisation function is a signal that too much conditional logic has been folded into setup rather than deferred to the handlers it registers. A nesting depth of 6 — matching handle — suggests the two functions share deeply nested decision trees, which makes sense given that normalize is defined as a closure inside Mousewheel and therefore entangles the two functions’ complexity budgets.
Like handle, this function carries 97 days without a commit and zero active authors in the last 90 days. The god-function and long-function patterns both apply: the function is both a configuration hub and an implementation container, two roles that will resist independent testing.
The most tractable first step is to move normalize out of the closure entirely — it has no external state dependencies based on what the excerpt shows — which would reduce the factory’s own complexity and make normalize independently testable.
updateSlides — src/core/update/updateSlides.mjs
updateSlides computes slide sizes, positions, snap-grid entries, and CSS custom properties for every slide on every layout recalculation. From the source excerpt, it handles virtual slides, RTL, grid layouts, centered slides, CSS mode offsets, percentage-based spaceBetween, function-valued offset parameters, and per-slide margin resets — all in a single pass. CC 105 is extreme by any reasonable standard; it means there are over a hundred distinct paths through this function, each representing a combination of layout options that could interact unexpectedly.
The fan-out of 44 is the third highest across these five functions and, in a core layout function written in JavaScript, likely understates the real coupling because swiper.params.* is accessed through dynamic property chains that static analysis may not fully enumerate. The nesting depth of 5 is moderate relative to the mousewheel functions but still above the threshold where a developer can hold the full conditional tree in working memory.
This function has sat untouched for 97 days. The blast radius when someone next needs to add a layout option — say, a new cssMode variant or a grid-layout change — is substantial. I would prioritise extracting the grid-initialisation branch (gridEnabled / swiper.grid) into a dedicated applyGridLayout(slides) helper and the CSS-mode offset block into applyCSSModeOffsets(wrapperEl, params). Both are identifiable from the excerpt as cohesive sub-tasks with limited interdependence on the rest of the function.
onTouchMove — src/core/events/onTouchMove.mjs
This is the only fire-quadrant function in the top five and the one I would treat as the most immediately urgent. onTouchMove handles pointer and touch events during an active swipe: it reconciles pointer IDs against tracked touch identifiers, resolves the correct targetTouch, guards against nested swiper conflicts, enforces allowTouchMove, and gates RTL and edge-release logic before computing translation delta. CC 127 is the highest in this entire dataset — 127 independent execution paths through a single event handler that fires continuously during every touch interaction.
Three commits have touched this file in the last 30 days, with two distinct authors. Three of its four total recorded commits are tagged as bug fixes — a 75% rate. I want to be careful here: that does not prove onTouchMove is currently defective, but it does establish a pattern of correctness work on this file that makes the combination of CC 127 and active multi-author editing genuinely concerning. With 127 paths, adding a guard for one edge case can inadvertently disable another, and the exit-heavy pattern — multiple early returns scattered throughout the function — makes it hard to trace which guards are even active at any given call site.
The nesting depth of 4 is notably lower than the mousewheel functions, which tells me the branching here is wide rather than deep: many sequential conditionals rather than deeply stacked ones. That actually makes it harder to extract cleanly, because each branch may depend on shared local state set earlier in the function.
My concrete recommendation: add a characterisation test suite — a set of recorded touch event sequences with asserted outcomes — before making any further changes. With CC 127, meaningful refactoring without a regression safety net is higher risk than leaving the function alone. Once that baseline exists, the pointer-type discrimination block (the pointermove vs touchmove routing near the top of the excerpt) is a natural first extraction candidate.
Pagination — src/modules/pagination/pagination.mjs
Pagination is the module factory for all pagination UI variants — bullets, progressbar, fraction, and custom. From the source excerpt, it defines the full parameter schema (over 20 pagination-specific config keys), initialises closure state, and then defines a series of inner functions: isPaginationDisabled, setSideBullets, getMoveDirection, onBulletClick, and presumably several more for rendering each pagination type. The fan-out of 61 is the highest of any function in this top five — 61 distinct callees — which in a UI factory function likely reflects DOM manipulation helpers, class-name utilities, and swiper lifecycle event registrations spread across multiple modules.
In JavaScript, dynamic property access patterns like bulletEl[\${position === ‘prev’ ? ‘previous’ : ‘next’}ElementSibling`]` (visible in the excerpt) are precisely the kind of coupling that fan-out metrics undercount: the dependency is resolved at runtime and is invisible to static analysis. The god-function and long-function patterns apply here as much as anywhere in this list — this factory is simultaneously a parameter registry, an event-binding layer, and a multi-variant renderer.
Like the other debt-quadrant entries, Pagination has been dormant for 97 days with no active authors. When pagination behaviour next needs to change — a new type, an accessibility fix, a dynamic-bullets edge case — any engineer opening this file will be navigating 61 callees and 44 branching paths in a function that no one has touched in three months.
The most defensible split is to promote each pagination type’s render logic into its own named function outside the factory closure — renderBullets, renderProgressbar, renderFraction likely exist as inner functions today based on the parameter schema shown. Moving them out of the closure reduces the factory’s own path count, makes each renderer independently testable, and shrinks the fan-out surface that has to be audited on any given change.
The broader picture
Beyond the top five, the context_only data surfaces a coherent cluster worth noting. onTouchEnd in src/core/events/onTouchEnd.mjs sits in the fire quadrant with CC 75 and three commits in the last 30 days — the same five-day recency window as onTouchMove and onTouchStart. The entire touch event pipeline (onTouchStart, onTouchMove, onTouchEnd) is in active development simultaneously, which compounds the risk of any single change: these three functions share state through swiper.touchEventsData and changes to one can affect the invariants the others assume.
The 178 debt-quadrant functions represent the longer-term obligation. None of them are on fire today, but swiper’s architecture concentrates complexity in module factories and event handlers that each own far more behaviour than a single function should. The pattern counts — 9 god functions, 9 long functions, 9 exit-heavy functions across just the top hotspots — suggest this is a structural pattern in how the codebase is organised, not isolated incidents.
Patterns Found
Antipatterns detected across the top functions in this snapshot:
| Pattern | Occurrences |
|---|---|
exit_heavy | 9 |
god_function | 9 |
long_function | 9 |
complex_branching | 7 |
deeply_nested | 5 |
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/nolimits4web/swiper
cd swiper
git checkout 929c01470c7ee0dc29789f61383ef84608c582d9
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 →