all: apply godoc [Name] link conventions across comments

Every Go-identifier reference in // and /* */ comments now uses
godoc's [Name] linking syntax so pkg.go.dev and `go doc` render
them as clickable cross-references. No behaviour change.

Pattern applied across the tree:
  In-package         [Foo], [Foo.Bar]
  Cross-package      [pkg.Foo], [pkg.Foo.Bar]
  Stdlib             [netip.Prefix], [errors.Is], [context.Context]
  Tailscale          [tailcfg.MapResponse], [tailcfg.Node.CapMap],
                     [tailcfg.NodeAttrSuggestExitNode]

Skip rules:
  - File:line refs left as plain text
  - HuJSON wire keys inside backtick raw strings untouched
  - ACL/policy syntax tokens (tag:foo, autogroup:self, ...) not Go
    symbols, left as plain text
  - JSON/OIDC wire keys, gorm tags, RFC IPv6 placeholders, markdown
    link tags, decorative dividers — all left as-is
This commit is contained in:
Kristoffer Dalby 2026-05-18 18:35:53 +00:00
parent 17236fd284
commit 4cca63155d
124 changed files with 1037 additions and 1011 deletions

View file

@ -6,7 +6,7 @@ import (
)
// CommentHeader returns the // comment header that gets prepended to
// a Capture file when it is written. The header is purely
// a [Capture] file when it is written. The header is purely
// informational; consumers ignore it. Format:
//
// <TestID>
@ -20,7 +20,7 @@ import (
// schema version: <SchemaVersion>
//
// Both `tool_version` and `schema_version` are also stored as
// first-class JSON fields on the Capture struct; the comment lines
// first-class JSON fields on the [Capture] struct; the comment lines
// exist purely so the values are visible at a glance without
// parsing the file.
//
@ -68,8 +68,8 @@ func CommentHeader(c *Capture) string {
// captures at all.
//
// The phrasing depends on which fields the capture uses:
// - SSH captures populate SSHRules
// - other captures populate PacketFilterRules
// - SSH captures populate [NodeCapture.SSHRules]
// - other captures populate [NodeCapture.PacketFilterRules]
//
// If both fields appear (mixed/unusual), filter rules wins.
func captureStats(c *Capture) string {

View file

@ -9,17 +9,17 @@ import (
"github.com/tailscale/hujson"
)
// ErrUnsupportedSchemaVersion is returned by Read when a capture
// advertises a SchemaVersion newer than the current binary supports.
// ErrUnsupportedSchemaVersion is returned by [Read] when a capture
// advertises a [Capture.SchemaVersion] newer than the current binary supports.
var ErrUnsupportedSchemaVersion = errors.New("testcapture: unsupported schema version")
// Read parses a HuJSON capture file from disk into a Capture.
// Read parses a HuJSON capture file from disk into a [Capture].
//
// Comments and trailing commas in the file are stripped before
// unmarshaling. Files advertising a SchemaVersion newer than the
// current binary's are rejected with ErrUnsupportedSchemaVersion;
// SchemaVersion == 0 (pre-versioning) is accepted for backwards compat.
// The returned Capture's CapturedAt is the value recorded in the file
// unmarshaling. Files advertising a [Capture.SchemaVersion] newer than the
// current binary's are rejected with [ErrUnsupportedSchemaVersion];
// [Capture.SchemaVersion] == 0 (pre-versioning) is accepted for backwards compat.
// The returned [Capture]'s [Capture.CapturedAt] is the value recorded in the file
// (not "now").
func Read(path string) (*Capture, error) {
data, err := os.ReadFile(path)
@ -43,8 +43,8 @@ func Read(path string) (*Capture, error) {
}
// unmarshalHuJSON parses HuJSON bytes (JSON with comments / trailing
// commas) into v. Comments are stripped via hujson.Standardize before
// json.Unmarshal is called.
// commas) into v. Comments are stripped via [hujson.Value.Standardize] before
// [json.Unmarshal] is called.
func unmarshalHuJSON(data []byte, v any) error {
ast, err := hujson.Parse(data)
if err != nil {

View file

@ -4,15 +4,15 @@
//
// Files are HuJSON. Wire-format Tailscale data (filter rules, netmap,
// whois, SSH rules) is stored as proper tailcfg/netmap/filtertype/
// apitype values rather than json.RawMessage so that schema drift
// apitype values rather than [json.RawMessage] so that schema drift
// between the capture tool and headscale becomes a compile error
// rather than a silent test failure, and so that consumers don't
// have to repeat json.Unmarshal at every read site. Storing data as
// json.RawMessage previously hid a serious capture-pipeline bug (the
// have to repeat [json.Unmarshal] at every read site. Storing data as
// [json.RawMessage] previously hid a serious capture-pipeline bug (the
// IPN bus initial notification returns a stale Peers slice — see the
// comment on Node.Netmap below) for months.
// comment on [Node.Netmap] below) for months.
//
// All four capture types (acl, routes, grant, ssh) use the same Capture
// All four capture types (acl, routes, grant, ssh) use the same [Capture]
// shape. SSH scenarios populate Captures[name].SSHRules; the others
// populate Captures[name].PacketFilterRules + Captures[name].Netmap.
package testcapture
@ -37,11 +37,11 @@ const SchemaVersion = 1
// Capture is one captured run of one scenario.
//
// All four capture types (acl, routes, grant, ssh) use this same shape.
// SSH scenarios populate Captures[name].SSHRules; the others populate
// Captures[name].PacketFilterRules + Captures[name].Netmap.
// SSH scenarios populate [Capture.Captures][name].SSHRules; the others populate
// [Capture.Captures][name].PacketFilterRules + [Capture.Captures][name].Netmap.
type Capture struct {
// SchemaVersion identifies the on-disk format version. Always set
// to testcapture.SchemaVersion when written.
// to [SchemaVersion] when written.
SchemaVersion int `json:"schema_version"`
// TestID is the stable identifier of the scenario, derived from
@ -67,15 +67,15 @@ type Capture struct {
Tailnet string `json:"tailnet"`
// Error is true when the SaaS API rejected the policy or when
// capture itself failed. In the rejection case, Captures reflects
// the pre-push baseline (deny-all default) and Input.APIResponseBody
// capture itself failed. In the rejection case, [Capture.Captures] reflects
// the pre-push baseline (deny-all default) and [Input.APIResponseBody]
// is populated.
Error bool `json:"error,omitempty"`
// CaptureError is set when the capture itself failed (timeout,
// missing data, etc.). The partially-captured Captures map is
// missing data, etc.). The partially-captured [Capture.Captures] map is
// still included for post-mortem. Distinct from
// Input.APIResponseBody which describes a SaaS API rejection.
// [Input.APIResponseBody] which describes a SaaS API rejection.
CaptureError string `json:"capture_error,omitempty"`
// Input is everything that was sent to the tailnet to produce
@ -94,7 +94,7 @@ type Capture struct {
// Input describes everything that was sent to the tailnet to produce
// the captured state.
//
// Input has a custom UnmarshalJSON to accept both the new on-disk
// [Input] has a custom [Input.UnmarshalJSON] to accept both the new on-disk
// shape (where full_policy is a JSON-encoded string) and the legacy
// shape (where full_policy is a JSON object). The legacy shape is
// re-marshaled to a string at load time so consumers see the typed
@ -109,7 +109,7 @@ type Input struct {
// APIResponseCode is the HTTP status code of the policy POST.
APIResponseCode int `json:"api_response_code"`
// APIResponseBody is only populated when APIResponseCode != 200.
// APIResponseBody is only populated when [Input.APIResponseCode] != 200.
APIResponseBody *APIResponseBody `json:"api_response_body,omitempty"`
// Tailnet describes the tailnet-wide settings the capture tool applied
@ -126,10 +126,10 @@ type Input struct {
ScenarioPath string `json:"scenario_path,omitempty"`
}
// MarshalJSON writes FullPolicy as a raw JSON object rather than a
// MarshalJSON writes [Input.FullPolicy] as a raw JSON object rather than a
// double-quoted string. Consumers (including via_compat_test.go which
// uses its own local types) expect to parse full_policy as a JSON
// object, not a JSON string. The UnmarshalJSON below accepts both
// object, not a JSON string. The [Input.UnmarshalJSON] below accepts both
// forms on read so old and new captures are interchangeable.
func (i Input) MarshalJSON() ([]byte, error) {
type alias Input
@ -153,7 +153,7 @@ func (i Input) MarshalJSON() ([]byte, error) {
// as a JSON-encoded string) and the legacy shape (full_policy as a
// JSON object). Legacy objects are re-marshaled into a string at
// load time so consumers see the typed field uniformly. New captures
// always write the object form via the custom MarshalJSON above.
// always write the object form via the custom [Input.MarshalJSON] above.
func (i *Input) UnmarshalJSON(data []byte) error {
type alias Input
@ -243,7 +243,7 @@ type SettingsInput struct {
// Topology describes the users and nodes present in the tailnet at
// capture time. Headscale's compat tests use this to construct
// equivalent types.User and types.Node objects.
// equivalent [types.User] and [types.Node] objects.
type Topology struct {
// Users in the tailnet. Always populated by the capture tool.
Users []TopologyUser `json:"users"`
@ -266,22 +266,22 @@ type TopologyNode struct {
IPv4 string `json:"ipv4"`
IPv6 string `json:"ipv6"`
// User is the TopologyUser.Name for user-owned nodes. Empty for
// User is the [TopologyUser.Name] for user-owned nodes. Empty for
// tagged nodes.
User string `json:"user,omitempty"`
// RoutableIPs is what the node advertised
// (Hostinfo.RoutableIPs in its own netmap.SelfNode).
// ([tailcfg.Hostinfo.RoutableIPs] in its own [netmap.NetworkMap.SelfNode]).
// May include 0.0.0.0/0 + ::/0 for exit nodes.
RoutableIPs []string `json:"routable_ips"`
// ApprovedRoutes is the subset of RoutableIPs the tailnet has
// ApprovedRoutes is the subset of [TopologyNode.RoutableIPs] the tailnet has
// approved. Used by Headscale's NodeCanApproveRoute test.
ApprovedRoutes []string `json:"approved_routes"`
}
// Node is the captured state for one node, keyed by GivenName in
// Capture.Captures.
// [Capture.Captures].
//
// All four capture types populate the same struct. Different fields are
// used by different test types:
@ -300,7 +300,7 @@ type Node struct {
// PacketFilterMatches is the compiled filter matches (with
// CapMatch) returned by tailscaled localapi
// /debug-packet-filter-matches. Captured alongside
// PacketFilterRules; useful for grant tests that want the
// [Node.PacketFilterRules]; useful for grant tests that want the
// compiled form.
PacketFilterMatches []filtertype.Match `json:"packet_filter_matches,omitempty"`
@ -311,7 +311,7 @@ type Node struct {
// settle on a fresh delta-triggered notification, NOT by reading
// the WatchIPNBus(NotifyInitialNetMap) initial notification.
// The initial notification carries cn.NetMap() which returns
// nb.netMap as-is — the netmap.NetworkMap whose Peers slice was
// nb.netMap as-is — the [netmap.NetworkMap] whose Peers slice was
// set at full-sync time and never re-synchronized from the
// authoritative nb.peers map. The capture tool previously used the initial
// notification and silently captured netmaps with mostly-empty
@ -327,6 +327,6 @@ type Node struct {
Whois map[string]*apitype.WhoIsResponse `json:"whois,omitempty"`
// SSHRules is the SSH rules slice extracted from
// netmap.SSHPolicy.Rules. Populated only for SSH scenarios.
// [netmap.NetworkMap.SSHPolicy].Rules. Populated only for SSH scenarios.
SSHRules []*tailcfg.SSHRule `json:"ssh_rules,omitempty"`
}

View file

@ -138,9 +138,9 @@ func sampleSSHCapture() *testcapture.Capture {
}
// equalViaJSON compares two captures by JSON-marshaling them and
// comparing the bytes. The Capture struct embeds tailcfg view types
// comparing the bytes. The [testcapture.Capture] struct embeds tailcfg view types
// with unexported pointer fields that go-cmp can't traverse, so a
// JSON round-trip is the simplest way to verify Write+Read produced
// JSON round-trip is the simplest way to verify [testcapture.Write]+[testcapture.Read] produced
// equivalent values.
func equalViaJSON(t *testing.T, want, got *testcapture.Capture) {
t.Helper()
@ -416,7 +416,7 @@ func TestCommentHeader_EmptyFilterRulesCountAsEmpty(t *testing.T) {
// TestInputUnmarshal_LegacyObjectForm asserts that a legacy capture
// file written with full_policy as a raw JSON object (not a
// JSON-encoded string) still deserialises into a valid Input, with
// JSON-encoded string) still deserialises into a valid [testcapture.Input], with
// the policy re-marshaled to a compact string so downstream consumers
// see a uniform typed field.
func TestInputUnmarshal_LegacyObjectForm(t *testing.T) {
@ -444,8 +444,8 @@ func TestInputUnmarshal_LegacyObjectForm(t *testing.T) {
t.Errorf("FullPolicy:\n got %q\nwant %q", got.FullPolicy, want)
}
// Round-trip: the new MarshalJSON must emit the object form so
// UnmarshalJSON re-reads it identically.
// Round-trip: the new [testcapture.Input.MarshalJSON] must emit the object form so
// [testcapture.Input.UnmarshalJSON] re-reads it identically.
out, err := json.Marshal(got)
if err != nil {
t.Fatalf("re-marshal: %v", err)

View file

@ -11,7 +11,7 @@ import (
"github.com/tailscale/hujson"
)
// ErrNilCapture is returned by Write when called with a nil Capture.
// ErrNilCapture is returned by [Write] when called with a nil [Capture].
var ErrNilCapture = errors.New("testcapture: nil capture")
// Write serializes c as a HuJSON file with a comment header. The
@ -19,9 +19,9 @@ var ErrNilCapture = errors.New("testcapture: nil capture")
// and is then renamed into place, so concurrent regeneration cannot
// leave a half-written file behind.
//
// The comment header is built by CommentHeader from c's TestID,
// Description, and Captures. The file's parent directory must
// already exist; callers should MkdirAll first.
// The comment header is built by [CommentHeader] from c's [Capture.TestID],
// [Capture.Description], and [Capture.Captures]. The file's parent directory must
// already exist; callers should [os.MkdirAll] first.
func Write(path string, c *Capture) error {
if c == nil {
return fmt.Errorf("testcapture: Write %s: %w", path, ErrNilCapture)
@ -86,7 +86,7 @@ func Write(path string, c *Capture) error {
}
// marshalHuJSON serializes v as HuJSON-formatted bytes. It is
// standard JSON encoding followed by hujson.Format which produces
// standard JSON encoding followed by [hujson.Format] which produces
// consistent indentation/whitespace.
func marshalHuJSON(v any) ([]byte, error) {
raw, err := json.Marshal(v)