common

package
v0.34.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 11, 2026 License: Apache-2.0 Imports: 6 Imported by: 0

README

common builder — maintainer notes

This document is the long-form companion to the common.Builder code.

The source files keep godoc concise; complex invariants, design trade-offs, and intentionally-deferred follow-ups live here.

common.Builder is the shared state every per-decl builder embeds (schema, parameters, responses, routes, operations, spec).

It owns the scanner context, the active declaration, the parsed-block memoisation cache, the diagnostic accumulator, the post-decl queue, and the slog logger.


Table of contents

  • §blockcacheParseBlock / ParseBlocks memoisation strategy and scope
  • §makeref — why MakeRef lives on the common base
  • §diagnostics — accumulator ordering, dedup posture, LSP-evolution caveat
  • §postdecls — per-Builder dedup index + cross-Builder re-dedup in the orchestrator
  • §quirks-open — deferred follow-ups

§blockcache — ParseBlock / ParseBlocks memoisation

Builder.blockCache memoises grammar.NewParser(...).ParseAll(cg) results keyed by *ast.CommentGroup pointer. Two reasons:

  1. Recursive type descent re-visits the same comment. A struct field whose type is itself a struct triggers a nested buildFromDecl/buildFromType pass; without memoisation each level re-lexes and re-parses the same field-doc comment group.
  2. Multi-annotation visibility. ParseAll yields one Block per annotation on the comment group (the swagger:type + swagger:model co-decl is the canonical double-annotation case). Callers that only need the first annotation use ParseBlock; callers that need every annotation iterate ParseBlocks.

The cache is per-Builder (one top-level decl build), so no synchronisation is needed: a Builder is single-goroutine for its entire lifetime. Crossing a Builder boundary discards the cache, which is fine — the scanner context owns the FileSet, so a parser constructed in a sibling Builder still produces position-stable output.

ParseBlock(cg) always returns a non-nil Block (the parser yields at least one Block even for a nil comment group, conventionally an UnboundBlock). Callers can read AnnotationKind(), AnnotationArg(), etc. on the result unconditionally.

§makeref — why MakeRef lives on the common base

MakeRef writes $ref: #/definitions/<name> onto a target via SwaggerTypable.SetRef, then enqueues the referenced declaration on the Builder's post-decl queue so the spec orchestrator visits it during the discovery loop.

The name source is decl.Names() (first entry — top-level decls in this codebase have a single name).

The method lives on common.Builder rather than per-package because every builder needs the same operation with the same side effect. Hoisting also means future cross-cutting refinements — a name-collision diagnostic, a discovery-loop instrumentation counter, a guard against emitting $ref to an unexported name — are one-place edits.

§diagnostics — accumulator + LSP-evolution caveat

Diagnostics() returns every accumulated grammar.Diagnostic in source order. No deduplication is applied. Two consumers may see the same diagnostic via the OnDiagnostic callback AND via the returned slice — that's intentional under the current contract.

The diagnostic surface is experimental and expected to evolve once the LSP integration matures. Likely changes when that lands: typed severity classes, structural deduplication, per-position provenance. The shape is conservative today (slice of grammar.Diagnostic + a callback hook) precisely so it can be widened without breaking callers.

RecordDiagnostic appends to the slice and fires Ctx.OnDiagnostic() when wired. Walkers' Diagnostic callback points at this method so grammar-level warnings flow into the same accumulator.

§postdecls — dedup index + orchestrator re-dedup

AppendPostDecl(decl) enqueues decl for post-processing by the spec orchestrator's discovery loop. The Builder maintains a per-instance dedup index (postDeclSet, keyed by *ast.Ident) so a single decl re-discovered N times during one Build pass only enqueues once.

A SECOND dedup runs in spec.Builder.buildDiscovered at consumption time, because two different per-decl Builders may surface the same post-decl independently. The double-guard means a discovered decl never reaches a second Build pass even when sibling Builders race to register it.

Nil and Ident-less decls are silently ignored — defensive against the scanner emitting partial decls during error recovery.

§quirks-open — deferred follow-ups

These are real maintenance items the package author noted; they remain open for a future pass.

  • logger configurability. New instantiates slog.Default(). An option to accept a user-supplied *slog.Logger (level, coloured output, structured fields) would let callers opt into a consistent logging surface across builders. Currently every builder's Warn/Debug writes through the global default.
  • ireturn on ParseBlock. The nolint:ireturn directive on ParseBlock carries because grammar.Block is a polymorphic interface — that's the documented return type. The lint could be disabled package-wide rather than per-function; consider as a .golangci.yml exclusion once the broader lint posture is reviewed.

Documentation

Overview

Package common holds shared per-Builder state every concrete per-decl builder (schema, parameters, responses, routes, operations, spec) embeds. See [./README.md](./README.md) for the long-form maintainer notes on cache scope, diagnostic posture, and the post-decl queue's double-dedup design.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Builder

type Builder struct {
	Ctx  *scanner.ScanCtx
	Decl *scanner.EntityDecl
	// contains filtered or unexported fields
}

Builder holds the per-decl state shared across every concrete builder via embedding.

See §blockcache, §diagnostics, and §postdecls for the cache scope, accumulator posture, and discovery queue's dedup design.

func New

func New(ctx *scanner.ScanCtx, decl *scanner.EntityDecl) *Builder

New builds a Builder bound to ctx and decl.

The blockCache is pre-allocated empty; logger defaults to slog.Default.

See §quirks-open for the planned configurability.

func (*Builder) AppendPostDecl

func (s *Builder) AppendPostDecl(decl *scanner.EntityDecl)

AppendPostDecl marks decl for post-processing by the spec orchestrator's discovery loop. Idempotent per-Builder: re-appending a decl whose Ident was already seen is a no-op. Nil and Ident-less decls are silently ignored.

Details

See [§postdecls](./README.md#postdecls) — per-Builder dedup index and the second dedup applied at consumption time by spec.Builder.buildDiscovered.

func (*Builder) Debug

func (s *Builder) Debug(msg string, args ...any)

Debug writes a debug message to the Builder's slog logger.

func (*Builder) Diagnostics

func (s *Builder) Diagnostics() []grammar.Diagnostic

Diagnostics returns every grammar.Diagnostic accumulated by this Builder during a Build pass.

Source order is preserved; no deduplication is applied. The slice is owned by the Builder; callers must not mutate it. Returns nil before Build is invoked or when no diagnostics were recorded.

Details

See [§diagnostics](./README.md#diagnostics) — accumulator ordering, dedup posture, and the LSP-evolution caveat (the diagnostic surface is expected to widen once IDE integration matures).

func (*Builder) MakeRef

func (s *Builder) MakeRef(decl *scanner.EntityDecl, prop ifaces.SwaggerTypable) error

MakeRef writes a `$ref: "#/definitions/<name>"` onto prop and registers decl with the discovery loop via AppendPostDecl. The name comes from decl.Names() (the first entry — top-level decls in this codebase have a single name). Returns an error only if oaispec.NewRef rejects the JSON pointer.

Details

See [§makeref](./README.md#makeref) — why the operation lives on the common base and what kinds of cross-cutting refinements that shape enables.

func (*Builder) ParseBlock

func (s *Builder) ParseBlock(cg *ast.CommentGroup) grammar.Block

ParseBlock returns the first Block from Builder.ParseBlocks.

This is the "primary" annotation for callers that don't need multi-annotation visibility (title/description, field-level lookups).

func (*Builder) ParseBlocks

func (s *Builder) ParseBlocks(cg *ast.CommentGroup) []grammar.Block

ParseBlocks returns the cached grammar.Block slice for cg (one entry per annotation), parsing on first access and memoising the result.

Always returns a non-nil slice with at least one Block, so consumers can call [Block.AnnotationKind], [Block.AnnotationArg] / etc. unconditionally on the first element.

Details

See [§blockcache](./README.md#blockcache) — memoisation scope, why ParseAll is preferred over Parse, and the per-Builder (single-goroutine) lifetime that obviates synchronisation.

func (*Builder) PostDeclarations

func (s *Builder) PostDeclarations() []*scanner.EntityDecl

PostDeclarations returns the post-decl queue accumulated by this Builder during a Build pass, in source order.

See [§postdecls](./README.md#postdecls).

func (*Builder) RecordDiagnostic

func (s *Builder) RecordDiagnostic(d grammar.Diagnostic)

RecordDiagnostic accumulates one diagnostic on the Builder and fires the consumer's [Options.OnDiagnostic] callback when wired.

Walker.Diagnostic is bound to this method, so grammar-level warnings flow through the same accumulator as builder-level ones.

func (*Builder) Warn

func (s *Builder) Warn(msg string, args ...any)

Warn writes a warning to the Builder's slog logger.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL