📖 4 min read (~ 800 words).

Cleaning godoc doc-links

A Go doc comment can use godoc’s doc-link syntax — [Gadget], [Order.CustName], reference-style [text]: url lines. Those render as live links in pkg.go.dev, but carried verbatim into a spec title / description they read as bracket noise, and the bracketed Go identifier is rarely the name the schema is actually exposed under.

CleanGoDoc tidies that up. With the option on, godoc doc-link brackets are removed and — when a link resolves to a scanned schema — the span is recomposed to the name that schema is exposed under, so the prose stays true to the generated definitions. It applies only to godoc-derived prose; an author-written swagger:title / swagger:description override is deliberate text and is never touched.

Each pane below pairs the annotated Go (left) with the exact fragment the scanner emits (right), from the test-covered docs/examples/shaping/godoclinks package.

What gets cleaned

This model — its doc comment and its fields — is dense with doc-link syntax: a self-reference, links to other models, a pointer, a cross-package link, an unknown identifier, ordinary brackets, and a reference-definition line:

// Widget is the primary [Gadget] holder and references [Order.CustName].
//
// More detail mentions a [*Gadget] pointer, a [inventory.Ledger], and an
// unknown [Sprocket].
//
// swagger:model gizmo
type Widget struct {
	// Holder points at the [Gadget] that owns this widget.
	Holder string `json:"holder"`

	// Ledger is the cross-package [inventory.Ledger] reference.
	Ledger *inventory.Ledger `json:"ledger"`

	// Index is element [0] in the [see notes] list; the [id] stays bare.
	Index int `json:"index"`

	// Spec points at [Gadget]; the reference-definition line below is godoc
	// link plumbing that carries no prose.
	//
	// [the spec]: https://example.com/spec
	Spec string `json:"spec"`
}

Full source: docs/examples/shaping/godoclinks/godoclinks.go

Scanned with CleanGoDoc off the godoc is emitted verbatim; on, every doc-link is resolved or humanized and the reference-definition line is dropped:

Default — verbatim
{
  "description": "More detail mentions a [*Gadget] pointer, a [inventory.Ledger], and an\nunknown [Sprocket].",
  "type": "object",
  "title": "Widget is the primary [Gadget] holder and references [Order.CustName].",
  "properties": {
    "holder": {
      "description": "Holder points at the [Gadget] that owns this widget.",
      "type": "string",
      "x-go-name": "Holder"
    },
    "index": {
      "description": "Index is element [0] in the [see notes] list; the [id] stays bare.",
      "type": "integer",
      "format": "int64",
      "x-go-name": "Index"
    },
    "ledger": {
      "$ref": "#/definitions/Ledger"
    },
    "spec": {
      "description": "Spec points at [Gadget]; the reference-definition line below is godoc\nlink plumbing that carries no prose.\n\n[the spec]: https://example.com/spec",
      "type": "string",
      "x-go-name": "Spec"
    }
  },
  "x-go-name": "Widget",
  "x-go-package": "github.com/go-openapi/codescan/docs/examples/shaping/godoclinks"
}

Full source: docs/examples/shaping/godoclinks/testdata/gizmo_off.json

CleanGoDoc — cleaned
{
  "description": "More detail mentions a Gadget pointer, a Ledger, and an\nunknown sprocket.",
  "type": "object",
  "title": "Gizmo is the primary Gadget holder and references Order.customer_name.",
  "properties": {
    "holder": {
      "description": "Holder points at the Gadget that owns this widget.",
      "type": "string",
      "x-go-name": "Holder"
    },
    "index": {
      "description": "Index is element [0] in the [see notes] list; the [id] stays bare.",
      "type": "integer",
      "format": "int64",
      "x-go-name": "Index"
    },
    "ledger": {
      "$ref": "#/definitions/Ledger"
    },
    "spec": {
      "description": "Spec points at Gadget; the reference-definition line below is godoc\nlink plumbing that carries no prose.",
      "type": "string",
      "x-go-name": "Spec"
    }
  },
  "x-go-name": "Widget",
  "x-go-package": "github.com/go-openapi/codescan/docs/examples/shaping/godoclinks"
}

Full source: docs/examples/shaping/godoclinks/testdata/gizmo_on.json

Reading the cleaned pane:

  • Links recompose to the exposed name. [Gadget]Gadget (no override, so its Go name); the [Order.CustName] member link → Order.customer_name (the model name plus the field’s json name); and the leading self-name WidgetGizmo, because the model is published as swagger:model gizmo (restored to sentence case). A cross-package [inventory.Ledger] resolves through the file’s imports to Ledger.
  • Unresolved links are humanized. [Sprocket] names no scanned model, so it becomes the plain word sprocket rather than a dangling bracket.
  • Reference-definition lines are dropped. The [the spec]: https://… line on the spec field is link plumbing carrying no prose, so the whole line is removed.
Info

It recomposes to the final exposed name. The substitution runs after codescan resolves definition names, so a link to a model that gets renamed to deconflict a collision points at the renamed definition, not the original Go identifier.

Conservative by design

Only a genuine doc-link is rewritten — a dotted chain ([pkg.Type]) or an uppercase-led identifier ([Widget]). Ordinary prose brackets are left exactly as written, as the index field above shows: [0], [see notes] and the bare-lowercase [id] all survive untouched.

CleanGoDoc is opt-in and defaults to off — with it off, output is byte-identical to before, so existing specs never shift under you.

What’s next