Across 14,187 functions in wailsapp/wails at commit fbbfc01, Hotspots flagged 556 as critical-band — and all five of the top hotspots sit in the ‘fire’ quadrant, meaning they are both structurally complex and actively changing right now. The highest-scoring function, WndProc in webview_window_windows.go, carries an activity-weighted risk score of 20.16 with a cyclomatic complexity of 58 and fan-out of 62 — and it was last touched five days ago. I would start there, because complexity at that level combined with live commit activity is a regression risk you can’t defer.
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 |
|---|---|---|---|---|---|
WndProc | v3/pkg/application/webview_window_windows.go | 20.2 | 58 | 7 | 62 |
MenuBarWndProc | v3/pkg/w32/menubar.go | 19.5 | 45 | 7 | 44 |
Collect | v3/internal/generator/collect/model.go | 19.0 | 17 | 8 | 48 |
convertType | v2/internal/typescriptify/typescriptify.go | 18.3 | 12 | 9 | 37 |
generalWndProc | v2/internal/frontend/desktop/windows/winc/wndproc.go | 18.1 | 25 | 6 | 55 |
Large Repo Analysis
wails 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
The drawRect function in v3/examples/screen/assets/main.js appears in the context_only list and is a JavaScript example asset rather than core Go library code. It scored in the ‘watch’ band due to recent activity on an example file, not a structural concern in the library itself. To exclude example assets and bundled JavaScript from future analyses, add { "exclude": ["**/examples/**", "**/assets/**/*.js"] } to your .hotspotsrc.json.
Quadrant and Pattern Overview
14,187 functions analyzed
Every function Hotspots scored lands in either ‘fire’ or ‘watch’ — there is no dormant structural debt here. All 2,174 fire-quadrant functions are both complex and actively changing. The five functions analyzed below are the most urgent representatives of that group.
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.Exit Heavy×5Exit Heavy
Multiple return or throw paths dispersed through the body — each exit needs separate test coverage.God Function×5God Function
Calls an unusually large number of distinct functions (high fan-out), making it the structural centre of gravity for a subsystem.Long Function×5Long Function
Function body is too long to review in a single pass; likely contains multiple distinct responsibilities.
Every top hotspot shares the same five antipatterns simultaneously: complex branching, deep nesting, multiple exit paths, god-function coupling, and sheer length. That uniform co-occurrence is a signal in itself — the Windows windowing layer and the type-generation pipeline have both grown by accretion rather than by deliberate decomposition.
WndProc — webview_window_windows.go
WndProc is the top-level Windows message procedure for the WebView2 window in v3. From the excerpt, it dispatches across a large switch on msg, handles frameless decorations, modal re-enabling, composition hosting routing, cursor management, focus events, and chromium shutdown — all in one function. That accounts directly for the cyclomatic complexity of 58 (well above the CC 30 threshold that warrants immediate attention) and a fan-out of 62, meaning any single change here can ripple to 62 distinct callees.
The nesting depth of 7 reflects the layered conditionals visible in the excerpt: composition-hosting checks wrap message-routing calls, which themselves wrap event emissions and DWM frame extension behind further if guards. Each nesting level is another context a reviewer must hold in mind simultaneously. The exit_heavy pattern is confirmed by the multiple early return statements scattered across message cases — each is a test path that needs its own coverage.
This function was touched once in the last 30 days and was last modified five days ago, placing it squarely in live-fire territory. The file’s sole commit has a bug-fix fraction of 1.0 and a PR review comment density of 6.0 — the highest comment density among all five hotspots — suggesting reviewers are already spending meaningful effort on this code.
Recommendation: Extract each WM_* case into a dedicated handler method on windowsWebviewWindow. A message case like WM_CLOSE already has distinct sub-logic (modal re-enable, chromium shutdown, deferred unregister) that stands alone as a named method. Doing this for the five or six heaviest cases would cut the function’s CC roughly in half and make each path independently testable.
MenuBarWndProc — menubar.go
MenuBarWndProc handles custom menu bar drawing for themed Windows menu bars — dark mode, maximized-window coordinate adjustments, per-item hover state, and border painting. The excerpt shows it branches immediately on a nil theme guard, then enters a switch on msg where the WM_UAHDRAWMENU case alone contains a maximized-vs-normal split, rect-offset arithmetic, and a manual item-drawing loop. That loop nests another GetMenuItemRect check and coordinate conversion inside it, contributing to the nesting depth of 7.
With fan-out of 44, changes here reach across a wide surface of Win32 drawing primitives. The commented-out debug println blocks in the excerpt are a soft signal that this code is still being actively understood and adjusted by its authors — consistent with the fact that it was touched five days ago. Like WndProc, the file shows a bug-fix fraction of 1.0 on its single recorded commit.
The god-function and long-function patterns here are partly structural necessity — Win32 owner-draw message handling is inherently stateful — but the maximized-window branch is self-contained enough to extract. The coordinate adjustment and the manual item-drawing loop could each become named helpers, reducing both CC and the nesting depth without changing the external contract of the function.
Recommendation: Extract the maximized-window coordinate-and-draw block into a drawMaximizedMenuBar helper. This is the deepest and most logic-dense branch in the excerpt, and isolating it would make the remaining cases in MenuBarWndProc easier to trace and test independently.
Collect — collect/model.go
Collect on ModelInfo is the entry point for gathering type metadata used by wails’ v3 code generator — it resolves type parameters, parses directives from doc comments, precomputes JSON and text marshaling predicates, and then branches on the concrete Go type (alias, struct, etc.) to populate the model’s definition and any associated constants. The once.Do wrapper makes it safe for concurrent calls, which matters in a generator that may walk a type graph in parallel goroutines.
The nesting depth of 8 is the deepest in the top five and a strong refactoring signal on its own. From the excerpt, the nesting builds through the once.Do closure, the doc-comment loop, the inner comment loop, the type-parameter branch, and then the switch on concrete type — each layer adds a frame of context that must be tracked simultaneously. The cyclomatic complexity of 17 is more moderate than the WndProc functions, but paired with an ND of 8 and fan-out of 48, the reasoning burden is disproportionate to the CC alone.
The inline comment — “Changes in the following logic must be reflected adequately by the predicates in properties.go, by ImportMap.AddType and by all render.Module methods” — is a manually maintained coupling contract. That kind of comment is a blast-radius marker: whoever touches Collect next also needs to audit at least three other locations.
Recommendation: The predicate precomputation block (the Predicates struct initialization) and the type-parameter recording block are both self-contained and could be extracted into private methods on ModelInfo. Flattening those out of the once.Do closure would cut nesting depth by at least two levels and make the coupling to properties.go easier to trace from a single, smaller call site.
convertType — typescriptify.go
convertType is a recursive function in the v2 TypeScript type emitter. It takes a reflect.Type, checks a visited-set to avoid cycles, then walks the struct’s fields dispatching on field kind: simple transform, enum, explicit TS type, struct (recursive call), map, and likely slice and primitive cases beyond the excerpt. The recursion is explicit — t.convertType(depth+1, field.Type, customCode) — and the result is assembled by string concatenation, prepending each nested type chunk.
At a nesting depth of 9 — the highest in the entire top five — this function is the clearest refactoring candidate on pure readability grounds. The CC of 12 is the lowest among the top hotspots, but the depth-9 nesting means that the innermost branch (visible in the reflect.Struct case in the excerpt) requires tracking the recursive call frame, the field loop, the pointer-dereference check, the JSON name check, and multiple field-kind branches simultaneously. The println statements visible in the excerpt (println("KnownStructs:", println("Not found:") suggest active debugging is happening here, consistent with the five-day-old touch.
This file lives in v2/ — the older major version — but the active commit activity confirms it is not retired code.
Recommendation: Extract the per-field dispatch into a convertField method that takes a single reflect.StructField and returns a string fragment or error. That one change collapses the deepest nesting levels and makes each field-kind branch independently unit-testable without needing a full recursive type graph as input.
generalWndProc — winc/wndproc.go
generalWndProc is the generic Windows message router in the v2 desktop frontend’s winc widget layer. Where WndProc in v3 handles one specific window type, generalWndProc is a catch-all dispatcher: it looks up the registered message handler for the given hwnd, invokes controller.WndProc, then re-dispatches a second time into child controls for reflected messages (WM_NOTIFY, WM_COMMAND, WM_CONTEXTMENU). That double-dispatch pattern — controller handling followed by child reflection — is what drives the nesting depth of 6 and the fan-out of 55.
The excerpt shows that several message cases contain three nested levels of null-checking and controller lookups before reaching the actual event firing call. With a fan-out of 55, this function is the broadest coupling point among the top five — a change to the dispatch contract here touches more downstream callees than any other hotspot in the list. The exit_heavy pattern is present in the inner if ret != 0 { return w32.TRUE } guards repeated across reflected message cases.
This is v2 code, but the single touch in the last 30 days and five-day modification age confirm it is still receiving changes.
Recommendation: The child-reflection logic for WM_NOTIFY, WM_COMMAND, and WM_CONTEXTMENU each follow the same lookup-then-dispatch pattern and could be consolidated into a reflectToChild(hwnd, msg, wparam, lparam) helper. That alone would reduce both the cyclomatic complexity and the nesting depth in the main function body, and would give the reflection behavior a single place to test and audit.
Codebase Risk Distribution
All five top hotspots share the same structural patterns (complex_branching, deeply_nested, exit_heavy, god_function, long_function), which is typical of the highest-risk functions in any large codebase — they accumulate every structural signal on the way to the top. More useful context is how the risk is distributed across all 14,187 analyzed functions:
| Band | Functions |
|---|---|
| Critical | 556 |
| High | 1,618 |
| Moderate | 6,366 |
| Low | 5,647 |
Hotspot patterns 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.
Reproduce This Analysis
git clone https://github.com/wailsapp/wails
cd wails
git checkout fbbfc011ee49243e328aa897467b83af01a407af
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 →