swag's parsing core carries the highest activity risk — 5 functions to address first

In swaggo/swag, four of the five highest-risk functions are untouched structural debt across operation.go, parser.go, const.go and package.go, with CC values from 57 to 172.

Stephen Collins ·
Generated by hotspots · free & open source
pip
$ pip install hotspots-cli
Activity Risk18.55Low
Hottest FunctionParseParamComment

Antipatterns Detected

exit_heavy8god_function8long_function8complex_branching5stale_complex5deeply_nested4

Run this on your own codebase

See if your own repo has a ParseParamComment-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 a god function and why does it matter in swag?

A god function is one that does too many unrelated jobs, typically signaled by a large body, high cyclomatic complexity and a wide fan-out to other functions. It matters because every responsibility inside it shares local state, so a change to one branch can interact with another, and testing it in isolation is difficult. Eight of the functions I examined in swag carry this pattern, including `ParseParamComment` (fan-out of 23) and `parseGeneralAPIInfo` (fan-out of 24). Each one needs many test cases just to cover its branches.

How do I reduce cyclomatic complexity in go?

The main technique is extract-method, often paired with decompose-conditional or a lookup table in place of a long switch. As a rule of thumb, I plan a split once a function passes a cyclomatic complexity of 15 and treat anything past 30 as needing immediate attention. A first step you can take today in swag is to move the per-attribute cases of `parseGeneralAPIInfo` (complexity 102) into a map of small handler functions, which would take most of the cases out of the main body at once. Go's explicit error returns also add branches, so extracting helpers that return a single error keeps the calling function flatter.

Is swag actively maintained?

The data shows a mix. The fire quadrant holds 5 functions, and `ParseAPIMultiSearchDir` was changed today, with 1 touch in the last year. At the same time, 150 functions are in the debt quadrant: `EvaluateBinary`, `parseGeneralAPIInfo` and `evaluateConstValue` haven't been changed in 285 days with 0 touches in the last year, and `ParseParamComment` hasn't been changed in 195 days. That is consistent with an actively developed project whose parsing core is stable but structurally heavy, and the two conditions can easily coexist.

How do I reproduce this analysis?

Clone the repository, run `git checkout eb13189` to match the commit I analyzed, and then run `hotspots analyze . --mode snapshot --explain-patterns --force` using the hotspots CLI from GitHub. The same command works on any local git repository without extra configuration.

What does activity-weighted risk mean?

Activity-weighted risk combines structural complexity (cyclomatic complexity, nesting depth and fan-out) with how often a function has recently been committed to. Functions that are both hard to understand and actively changing score highest, because that is where a new regression is most likely to be introduced. Recent change activity raises a function's score, but a very complex function can still rank high even when it has been dormant for months. The score is a prioritization aid for review and refactoring effort, not a judgment of the code's quality.

ParseParamComment in operation.go hasn’t been changed in 195 days. It has a cyclomatic complexity of 81 and nesting seven levels deep, and I read that as a maintenance risk rather than an emergency: the next developer to touch it inherits all of that structure cold. Across swag’s 516 functions, 87 are in the critical band and 150 sit in the debt quadrant. Four of my top five are dormant: ParseParamComment (an activity-weighted risk score of 18.55), parseGeneralAPIInfo (17.06), EvaluateBinary (16.91) and evaluateConstValue (15.81). The only live-fire entry is ParseAPIMultiSearchDir in parser.go, which was changed today and scores 17.8.

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
ParseParamCommentoperation.go18.681723
ParseAPIMultiSearchDirparser.go17.854522
parseGeneralAPIInfoparser.go17.1102524
EvaluateBinaryconst.go16.9172522
evaluateConstValuepackage.go15.857418
Triage Band Distribution
Fire5Debt150Watch1OK360

516 functions analyzed

The distribution is lopsided in an instructive way. Of 516 functions, only 5 are in the fire quadrant, while 150 are debt: complex, but not changing recently. That is the shape of a project whose parsing core has settled, with most of its risk stored in code that works and is rarely edited.

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

Among the top hotspots and context functions, three patterns each appear 8 times: exit_heavy (many return paths), god_function (one function doing many jobs) and long_function (a very large body). Five carry the stale_complex marker, meaning complexity that has sat untouched. To me that tells one story: large functions with many return paths, written once and left alone.

ParseParamComment — operation.go

ParseParamComment
operation.go
18.55
debt
CC 81
ND 7
FO 23
touches/yr 0

I would start with ParseParamComment. It has the deepest nesting in my top five (7 levels), a cyclomatic complexity of 81 and a fan-out of 23, and it hasn’t been changed in 195 days, with 0 touches in the last year. It parses the param annotation line. From the excerpt, it matches a regular expression, classifies the type as object, array or primitive, resolves enum schemas, and then branches by parameter location (path, header, query, formData, and others), with a further loop over struct properties in the object case. That is at least four separate responsibilities in one function.

Cyclomatic Complexity 81
threshold: 10

The file’s history adds some weight. operation.go shows a bug-fix rate of about 35% across 17 commits, with no bug-linked commits and no reverts recorded, and a single distinct author in the last 90 days. None of that is evidence of defects in this function. But the PR review comment density of 6.4 is the highest among my top five, which suggests this code draws more discussion than its neighbors. A single recent author also means the working knowledge of this file may sit in one head.

My recommendation is extract-method along the seams already visible in the code. Pull type detection (array, primitive, object) into one helper, enum resolution into a second, and the per-location validation switch into a third. The object-case property loop, with its query and formData extension-name lookups, is the deepest nest, so I would lift it out first.

ParseAPIMultiSearchDir — parser.go

ParseAPIMultiSearchDir
parser.go
17.8
fire
CC 54
ND 5
FO 22
touches/yr 1

This is the only function in my top five that is changing right now: 0 days since last change and 1 touch in the last year. A cyclomatic complexity of 54, nesting of 5 and fan-out of 22 make it the live regression risk on the list. From the excerpt, it handles the main API file path, then branches between Go package loading, per-directory scanning, and two dependency-resolution strategies (a package-listing route and a dependency-tree route), with an early error return at almost every step.

parser.go carries a bug-fix rate of about 47% across 30 commits, including 3 bug-linked commits, and 2 distinct authors in the last 90 days. Those are file-level numbers and don’t prove this function is defective. Still, a complex function with bug-linked file history, edited today, deserves review before the next merge. Every early return is a path that needs a test, and the dependency branches multiply the cases.

I would split the two dependency strategies into separate methods and leave the top-level function only choosing between them. That alone removes the deepest nesting and lets each strategy be tested with its own fixtures.

parseGeneralAPIInfo — parser.go

parseGeneralAPIInfo
parser.go
17.06
debt
CC 102
ND 5
FO 24
touches/yr 0

A cyclomatic complexity of 102 and a fan-out of 24 put this in the extreme range, for a function untouched in 285 days. From the excerpt, it loops over comment lines and runs a large switch on the annotation attribute (version, title, host, basepath, accept, produce, schemes, tag fields and more), with markdown-loading and error paths inside several cases. A switch like this is complex mostly by enumeration, but any one case can pull in state from the others through the shared previous-attribute and current-tag variables.

It shares parser.go’s file-level history with the previous function: a bug-fix rate of about 47% across 30 commits and 3 bug-linked commits. That raises review priority for the next change, though it says nothing about today’s behavior. Because the function is dormant, the blast radius is what worries me: the next person to add an annotation will edit a 102-path function that has been stable for 285 days.

I would convert the switch into a table of handlers keyed by attribute, so each annotation gets its own small function and its own test. The tag-related cases, which depend on the shared current-tag state, are the first group I would extract.

EvaluateBinary — const.go

EvaluateBinary
const.go
16.91
debt
CC 172
ND 5
FO 22
touches/yr 0

The cyclomatic complexity of 172 is the highest in this analysis, and the function hasn’t been changed in 285 days. The excerpt shows why: shift operations are handled with nested type switches over ten integer types for both left and right shifts, followed by a reflection-based path for the remaining operators. Much of that complexity is repetition rather than tangled logic, which makes it more tractable than the number suggests.

Cyclomatic Complexity 172
threshold: 30

The file history is quiet: 4 commits in total, a bug-fix rate of 25%, no bug-linked commits, no reverts and no recent authors. I would frame this as maintainability debt, not an active risk. The danger is that a function this size is nearly impossible to cover exhaustively by hand, so any future edit is hard to verify.

The practical fix is a reflection-driven helper that normalizes both operands once, then applies the operator. Go generics, or a small table keyed by operator, would also collapse the repeated integer cases. Before touching it, I would add a table-driven test over operator and type pairs.

evaluateConstValue — package.go

evaluateConstValue
package.go
15.81
debt
CC 57
ND 4
FO 18
touches/yr 0

This function walks Go AST expression nodes (identifiers, selectors, literals, unary and binary expressions, parentheses, calls) to compute constant values. It calls EvaluateBinary for the binary case, so the two functions form a dependency chain: a change to one can alter results through the other. It has a cyclomatic complexity of 57, nesting of 4 and fan-out of 18, and it hasn’t been changed in 285 days.

The excerpt includes a panic(err) in the integer-literal branch, reached when neither signed nor unsigned parsing succeeds. In a tool that processes arbitrary user source, I would flag that for review, since a panic there ends the whole run instead of returning an error. That observation comes from the excerpt alone; I haven’t checked how callers guard against it.

File history is thin: 5 commits, a bug-fix rate of 40%, no bug-linked commits and no recent authors. I would split the function by AST node type, giving each case a small evaluator, and change the panic into an error return with a test for the malformed-literal case.

Context from outside the top five

A few other functions put this in perspective without being in the top five. The generator and CLI entry points are also being edited alongside the parser, and one more parser.go function is dormant debt with a profile similar to ParseParamComment.

If I had one afternoon, I would spend it on ParseAPIMultiSearchDir, because it is the only top-five function that is complex and changing at the same moment. I would schedule the four debt functions by how likely the next feature is to touch them. The annotation handlers in parseGeneralAPIInfo and ParseParamComment come first, since new annotations are the most plausible reason to edit either.

Patterns Found

Antipatterns detected across the top functions in this snapshot:

PatternOccurrences
exit_heavy8
god_function8
long_function8
complex_branching5
stale_complex5
deeply_nested4

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/swaggo/swag
cd swag
git checkout eb131898e464c54a946c502830c45379e428b5cd
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