scanner

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: 17 Imported by: 0

README

scanner — maintainer notes

This document is the long-form companion to the scanner package code. The source files keep godoc concise; complex invariants, design trade-offs, and known quirks live here.

The scanner package owns package loading and entity discovery. It turns a set of Go package patterns into a ScanCtx that exposes the classified per-decl inventory (meta, routes, operations, models, parameters, responses) consumed by the builder layer.


Table of contents

  • §optionsOptions.DescWithRef shape and rationale
  • §descwithref — the description-only-decoration $ref shape and why it has a flag
  • §diagnosticsOnDiagnostic contract and experimental-API caveat
  • §model-lookupGetModel vs FindModel — pure read vs implicit registration
  • §classifierdetectNodes bitmask semantics and struct-annotation exclusivity
  • §quirks-open — deferred follow-ups

§options — Options overview

Options is the externally-visible configuration struct. It is re-exported from the package root as codescan.Options. The default zero value is a valid configuration: every flag defaults to false and every slice/map defaults to nil.

Most fields are simple toggles (scope inclusion, debug, vendor extension suppression). Two fields carry non-trivial semantics that warrant the inline godoc and the deeper notes below:

  • DescWithRef — controls the $ref shape used when a struct field resolves to a named type and its only decoration is a description. See §descwithref.
  • OnDiagnostic — diagnostic callback hook. See §diagnostics.

§descwithref — description-only-decoration $ref shape

When a struct field's Go type resolves to a named type (so the spec emits a $ref to its definition) and its only field-level decoration is a description (no validations, no user-authored vendor extensions), the spec has two possible shapes:

  1. Bare $ref{$ref: ...}. The field's description is dropped. This is the conservative default when DescWithRef is false.
  2. Single-arm allOf{description: "...", allOf: [{$ref}]}. The description is preserved by wrapping the $ref in a single-arm allOf compound. This is JSON-Schema-draft-4 correct for sibling description.

DescWithRef=true opts into the second shape. The default is false because the bare-$ref shape interoperates more broadly with Swagger 2.0 tooling that does not implement the allOf compound.

When the field also carries validation overrides (pattern, enum, example, etc.) or user-authored vendor extensions, the allOf compound is mandatory regardless of DescWithRef — the override would be lost otherwise.

§diagnostics — OnDiagnostic callback

Options.OnDiagnostic, when non-nil, is invoked for every grammar.Diagnostic the builder layer records: lexer/parser warnings, semantic-validation failures from the validations package, and any future diagnostic class wired into the builder pipeline.

Contract:

  • The callback fires once per diagnostic, in source order.
  • Diagnostics never block the build. An invalid construct is silently dropped from the output spec; the explanation flows through this channel instead.
  • The callback may be called from any per-decl builder; it is the caller's responsibility to make it goroutine-safe if the consumer ever drives codescan.Run concurrently (today it is single- goroutine, but the callback contract makes no such guarantee).

The diagnostic surface is experimental. Once the LSP integration matures the shape is expected to grow: typed severity classes, structural deduplication, per-position provenance. Callers that adopt OnDiagnostic today should treat the signature as subject to breaking change in a future minor release.

ScanCtx.OnDiagnostic returns the user-supplied callback verbatim; builders pipe diagnostics through it via common.Builder.RecordDiagnostic.

§model-lookup — GetModel vs FindModel

ScanCtx exposes two lookup helpers with similar signatures but different side-effect contracts. The choice between them is load-bearing for the shape of the emitted spec.

GetModel(pkgPath, name) — pure read

Looks up a model decl across three sources, in order:

  1. Models — decls annotated with swagger:model. Always emitted as top-level definitions regardless of lookup.
  2. ExtraModels — decls discovered as dependencies of other emitted shapes. Already enqueued for top-level emission.
  3. FindDecl — fall through to a syntactic search over the loaded packages.

No side effect. A FindDecl hit through GetModel does not register the decl in ExtraModels. Callers that want the lookup to also surface the decl as a top-level definition must follow up with AddDiscoveredModel explicitly.

FindModel(pkgPath, name) — implicit registration

The older sibling of GetModel. It does the same three-source lookup, but a FindDecl hit also writes the decl into ExtraModels as a side effect.

FindModel is deprecated. The implicit registration surprises readers and pulls stdlib types (notably time.Time, json.RawMessage) into the spec's top-level definitions when they should be inlined where referenced. Builders that need the registration should use the explicit GetModel + AddDiscoveredModel pair.

AddDiscoveredModel — explicit registration

Registers a decl in ExtraModels. No-op for decls already in Models (annotated decls are emitted unconditionally — registering them as discovered would create a Models↔ExtraModels bouncing loop in the spec orchestrator's joinExtraModels pass). Nil and Ident-less decls are silently ignored, which is defensive against the scanner emitting partial decls during error recovery.

§classifier — detectNodes bitmask

TypeIndex.detectNodes scans every comment group in a file and returns a bitmask of detected annotation kinds. Each kind drives downstream processing:

Bit Annotation Downstream
metaNode swagger:meta file-level meta block
routeNode swagger:route path-level route annotations
operationNode swagger:operation path-level operation annotations
modelNode swagger:model per-decl model registration
parametersNode swagger:parameters per-decl parameter registration
responseNode swagger:response per-decl response registration

route, operation, and meta accumulate freely across comment groups in a file. The three struct-level annotations (model, parameters, response) are mutually exclusive within a single comment group — a struct cannot simultaneously be a model and a parameters bag, for instance. checkStructConflict enforces the rule per comment group and returns an error if the constraint is violated.

The annotation vocabulary recognised by the classifier is a closed set. Unknown annotations beginning with swagger: raise a classifier error. A handful of annotation tokens (strfmt, name, enum, default, alias, type, …) are recognised but produce no bit — they are field-level decorations that downstream builders parse out of the comment block directly.

§quirks-open — deferred follow-ups

  • FindModel deprecation. The deprecated alias is still on the ScanCtx surface for in-tree callers. Once every builder has been audited and migrated to the GetModel + AddDiscoveredModel pair, the deprecated method can be removed in a future major release.
  • Recognised-but-unused annotation tokens. detectNodes recognises a list of field-level tokens (strfmt, name, discriminated, file, enum, default, alias, type, allOf, ignore) only to avoid raising the "unknown annotation" error. Promoting them to per-file bits would let downstream builders skip whole files that carry no decorations — an optimisation, not a correctness change.
  • shouldAcceptTag precedence. When both includeTags and excludeTags are populated, includeTags wins (a tag in includeTags admits the operation even if it also appears in excludeTags). This is deliberate but easy to mis-read; an explicit "the include list takes precedence" doc on Options would help callers, but the field-level prose is already dense.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var ErrScanner = errors.New("codescan:scanner")

ErrScanner is the sentinel error for all errors originating from the scanner package.

Functions

This section is empty.

Types

type EntityDecl

type EntityDecl struct {
	Comments *ast.CommentGroup
	Type     *types.Named
	Alias    *types.Alias // added to supplement Named, after go1.22
	Ident    *ast.Ident
	Spec     *ast.TypeSpec
	File     *ast.File
	Pkg      *packages.Package
	// contains filtered or unexported fields
}

func (*EntityDecl) HasModelAnnotation

func (d *EntityDecl) HasModelAnnotation() bool

func (*EntityDecl) HasParameterAnnotation

func (d *EntityDecl) HasParameterAnnotation() bool

func (*EntityDecl) HasResponseAnnotation

func (d *EntityDecl) HasResponseAnnotation() bool

func (*EntityDecl) Names

func (d *EntityDecl) Names() (name, goName string)

func (*EntityDecl) Obj

func (d *EntityDecl) Obj() *types.TypeName

Obj returns the type name for the declaration defining the named type or alias t.

func (*EntityDecl) ObjType

func (d *EntityDecl) ObjType() types.Type

func (*EntityDecl) OperationIDs

func (d *EntityDecl) OperationIDs() (result []string)

func (*EntityDecl) ResponseNames

func (d *EntityDecl) ResponseNames() (name, goName string)

type Options

type Options struct {
	Packages                []string
	InputSpec               *spec.Swagger
	ScanModels              bool
	WorkDir                 string
	BuildTags               string
	ExcludeDeps             bool
	Include                 []string
	Exclude                 []string
	IncludeTags             []string
	ExcludeTags             []string
	SetXNullableForPointers bool
	RefAliases              bool // aliases result in $ref, otherwise aliases are expanded
	TransparentAliases      bool // aliases are completely transparent, never creating definitions
	// DescWithRef controls description preservation on $ref'd fields
	// in the description-only-decoration case: when a struct field's
	// Go type resolves to a named type ($ref) and its only
	// field-level decoration is a description (no validations, no
	// user-authored extensions).
	//
	//   - false (default): the description is dropped and the field
	//     emits as a bare `{$ref: ...}`.
	//   - true: the description is preserved by wrapping the $ref in
	//     a single-arm `allOf` compound — `{description: "...",
	//     allOf: [{$ref}]}` — the JSON-Schema-draft-4 correct shape
	//     for sibling description.
	//
	// When the field also carries validation overrides (pattern,
	// enum, example, etc.) or user-authored vendor extensions, the
	// allOf compound is mandatory regardless of this flag — the
	// override would be lost otherwise.
	//
	// See [§descwithref](./README.md#descwithref).
	DescWithRef    bool
	SkipExtensions bool // skip generating x-go-* vendor extensions in the spec
	Debug          bool // enable verbose debug logging during scanning

	// OnDiagnostic, when non-nil, is invoked for every diagnostic the
	// builder layer records (lexer/parser warnings, semantic-validation
	// failures from the validations package, etc.). The callback fires
	// once per diagnostic in source order; diagnostics never block the
	// build — invalid constructs are silently dropped from the output
	// spec while their explanation flows through this channel.
	//
	// Experimental: the public API surface for diagnostics is subject
	// to change while LSP integration matures. See
	// [§diagnostics](./README.md#diagnostics).
	OnDiagnostic func(grammar.Diagnostic)
}

Options configures a scan. The zero value is a valid configuration: every flag defaults to false and every slice/map defaults to nil.

Details

See [§options](./README.md#options) for the field overview, and [§descwithref](./README.md#descwithref) and [§diagnostics](./README.md#diagnostics) for the two fields with non-trivial semantics (DescWithRef and OnDiagnostic).

type ScanCtx

type ScanCtx struct {
	// contains filtered or unexported fields
}

func NewScanCtx

func NewScanCtx(opts *Options) (*ScanCtx, error)

func (*ScanCtx) AddDiscoveredModel added in v0.34.1

func (s *ScanCtx) AddDiscoveredModel(decl *EntityDecl)

AddDiscoveredModel registers decl in the ExtraModels index so the spec orchestrator emits a top-level definition for it.

No-op when decl is already an annotated swagger:model (in Models); annotated decls are emitted unconditionally and re-registering them as "discovered" would create a Models↔ExtraModels bouncing loop in joinExtraModels. Nil and Ident-less decls are silently ignored.

Use only at sites that explicitly intend the registration — pure-read lookups should use GetModel. See [§model-lookup](./README.md#model-lookup).

func (*ScanCtx) Debug

func (s *ScanCtx) Debug() bool

func (*ScanCtx) DeclForType

func (s *ScanCtx) DeclForType(t types.Type) (*EntityDecl, bool)

func (*ScanCtx) DescWithRef

func (s *ScanCtx) DescWithRef() bool

func (*ScanCtx) ExtraModels

func (s *ScanCtx) ExtraModels() iter.Seq2[*ast.Ident, *EntityDecl]

func (*ScanCtx) FileSet added in v0.34.1

func (s *ScanCtx) FileSet() *token.FileSet

FileSet returns the shared *token.FileSet used by the scan's loaded packages.

Callers that construct a grammar.Parser for comment groups not owned by a single EntityDecl's *packages.Package (notably operation and route path-level annotations aggregated across packages) read the FileSet from here so the produced positions resolve against the same file table the rest of the scan uses.

func (*ScanCtx) FindComments

func (s *ScanCtx) FindComments(pkg *packages.Package, name string) (*ast.CommentGroup, bool)

func (*ScanCtx) FindDecl

func (s *ScanCtx) FindDecl(pkgPath, name string) (*EntityDecl, bool)

func (*ScanCtx) FindEnumValues

func (s *ScanCtx) FindEnumValues(pkg *packages.Package, enumName string) (list []any, descList []string, _ bool)

func (*ScanCtx) FindModel deprecated

func (s *ScanCtx) FindModel(pkgPath, name string) (*EntityDecl, bool)

FindModel returns the model decl for (pkgPath, name) and, when the hit comes from FindDecl fallback, registers it in ExtraModels as a side effect.

Deprecated: prefer the explicit pair GetModel (pure read) and AddDiscoveredModel (explicit registration). The implicit registration side effect surprises readers and pulls stdlib types (notably time.Time, json.RawMessage) into the spec's top-level definitions when they should be inlined where referenced. See [§model-lookup](./README.md#model-lookup).

func (*ScanCtx) GetModel added in v0.34.1

func (s *ScanCtx) GetModel(pkgPath, name string) (*EntityDecl, bool)

GetModel is a pure read: it returns the model decl for (pkgPath, name) without any side effect.

Details

See [§model-lookup](./README.md#model-lookup) — the three-source lookup order (Models, ExtraModels, FindDecl), and how this differs from FindModel.

Returns (nil, false) when no matching decl exists in any of the three sources. Callers that want the lookup hit registered as a discovered model must follow up with AddDiscoveredModel explicitly.

func (*ScanCtx) Meta

func (s *ScanCtx) Meta() iter.Seq[*ast.CommentGroup]

func (*ScanCtx) Models

func (s *ScanCtx) Models() iter.Seq2[*ast.Ident, *EntityDecl]

func (*ScanCtx) MoveExtraToModel

func (s *ScanCtx) MoveExtraToModel(k *ast.Ident)

func (*ScanCtx) NumExtraModels

func (s *ScanCtx) NumExtraModels() int

func (*ScanCtx) OnDiagnostic added in v0.34.1

func (s *ScanCtx) OnDiagnostic() func(grammar.Diagnostic)

OnDiagnostic returns the user-supplied diagnostic sink, or nil when the consumer has not opted into diagnostic delivery.

Details

See [§diagnostics](./README.md#diagnostics) — callback contract, ordering guarantee, experimental-API caveat.

func (*ScanCtx) Operations

func (s *ScanCtx) Operations() iter.Seq[parsers.ParsedPathContent]

func (*ScanCtx) Parameters

func (s *ScanCtx) Parameters() iter.Seq[*EntityDecl]

func (*ScanCtx) PkgForType

func (s *ScanCtx) PkgForType(t types.Type) (*packages.Package, bool)

func (*ScanCtx) PosOf added in v0.34.1

func (s *ScanCtx) PosOf(p token.Pos) token.Position

PosOf resolves p to a token.Position via the active FileSet. Returns the zero token.Position when p is invalid or no FileSet is available. Useful for attaching a source location to a Diagnostic without each caller re-deriving the FileSet.

func (*ScanCtx) RefAliases

func (s *ScanCtx) RefAliases() bool

func (*ScanCtx) Responses

func (s *ScanCtx) Responses() iter.Seq[*EntityDecl]

func (*ScanCtx) Routes

func (s *ScanCtx) Routes() iter.Seq[parsers.ParsedPathContent]

func (*ScanCtx) SetXNullableForPointers

func (s *ScanCtx) SetXNullableForPointers() bool

func (*ScanCtx) SkipExtensions

func (s *ScanCtx) SkipExtensions() bool

func (*ScanCtx) TransparentAliases

func (s *ScanCtx) TransparentAliases() bool

type TypeIndex

type TypeIndex struct {
	AllPackages map[string]*packages.Package
	Models      map[*ast.Ident]*EntityDecl
	ExtraModels map[*ast.Ident]*EntityDecl
	Meta        []*ast.CommentGroup
	Routes      []parsers.ParsedPathContent
	Operations  []parsers.ParsedPathContent
	Parameters  []*EntityDecl
	Responses   []*EntityDecl
	// contains filtered or unexported fields
}

func NewTypeIndex

func NewTypeIndex(pkgs []*packages.Package, opts ...TypeIndexOption) (*TypeIndex, error)

type TypeIndexOption

type TypeIndexOption func(*TypeIndex)

func WithDebug

func WithDebug(enabled bool) TypeIndexOption

func WithExcludeDeps

func WithExcludeDeps(excluded bool) TypeIndexOption

func WithExcludePkgs

func WithExcludePkgs(excluded []string) TypeIndexOption

func WithExcludeTags

func WithExcludeTags(excluded map[string]bool) TypeIndexOption

func WithIncludePkgs

func WithIncludePkgs(included []string) TypeIndexOption

func WithIncludeTags

func WithIncludeTags(included map[string]bool) TypeIndexOption

func WithRefAliases

func WithRefAliases(enabled bool) TypeIndexOption

func WithTransparentAliases

func WithTransparentAliases(enabled bool) TypeIndexOption

func WithXNullableForPointers

func WithXNullableForPointers(enabled bool) TypeIndexOption

Directories

Path Synopsis
Package classify provides small classification predicates used by the scanner and by builders to decide whether a given name or comment line belongs to a particular Swagger-annotation family.
Package classify provides small classification predicates used by the scanner and by builders to decide whether a given name or comment line belongs to a particular Swagger-annotation family.

Jump to

Keyboard shortcuts

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