golang

package
v0.0.0-...-431ef0a Latest Latest
Warning

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

Go to latest
Published: Aug 24, 2026 License: BSD-2-Clause Imports: 13 Imported by: 0

Documentation

Overview

Package golang is the Go frontend: it binds annotations to the constructs of the host language and lowers them into the language neutral ir.

It is the only package allowed to know go/ast and go/types. Everything it produces is ir, so rules, diagnostics and backends stay language agnostic (see docs/plan.md §3.1).

There is no extraction step, no synthetic file and no position mapping: annotation files are ordinary Go and part of the normal build, so the Go compiler has already checked arity, argument types, field names, enum values and every identifier reference before speclink looks at anything.

Index

Constants

View Source
const (
	// RuleCtxNoUIImport fires when a domain package reaches into the user
	// interface.
	RuleCtxNoUIImport = "K6-CTX-NO-UI-IMPORT"
	// RuleCtxUIPackage fires when the ui directory does not declare ui<ctx>.
	RuleCtxUIPackage = "K6-CTX-UI-PKG"
	// RuleCtxUseCases fires when the use cases of a context are not bundled.
	RuleCtxUseCases = "K6-CTX-USECASES"
)
View Source
const (
	// RuleMainExists fires when a module has no entry point at all.
	RuleMainExists = "K8-MAIN-EXISTS"
	// RuleMainLocation fires for a main package outside the command root.
	RuleMainLocation = "K8-MAIN-LOCATION"
	// RuleInfraDomainFree fires when infrastructure carries domain knowledge.
	RuleInfraDomainFree = "K7-INFRA-DOMAIN-FREE"
)

Rule IDs of the layout linters. They appear in diagnostics and in spec.Waive calls, so they are public surface and must stay stable.

View Source
const (
	// RuleUCFile fires when a use case is not declared in its own uc_ file.
	RuleUCFile = "K5-UC-FILE"
	// RuleUCConstructor fires when the constructor is missing or misplaced.
	RuleUCConstructor = "K5-UC-CONSTRUCTOR"
	// RuleUCSignature fires for a use case with the wrong shape.
	RuleUCSignature = "K5-UC-SIGNATURE"
	// RuleUCAuthz fires when nothing in the implementation looks like an
	// authorisation check.
	RuleUCAuthz = "K5-UC-AUTHZ"
	// RuleUCPermission fires when no permission is bound to the use case.
	RuleUCPermission = "K5-UC-PERMISSION"
	// RuleUCPermissionI18n fires when permission texts are hardcoded.
	RuleUCPermissionI18n = "K5-UC-PERMISSION-I18N"
	// RuleUCDeps fires when a use case reaches for package level state instead
	// of taking its dependencies through the constructor.
	RuleUCDeps = "K5-UC-DEPS"
)
View Source
const (
	// AnnotationSuffix marks a sidecar file that asserts facts about the
	// constructs of its neighbour: commands.go -> commands.annotation.go.
	AnnotationSuffix = ".annotation.go"
	// RequirementSuffix marks a requirement declaration file in the
	// requirement tree: R-QUOTE-SUBMIT.spec.go.
	RequirementSuffix = ".spec.go"
)
View Source
const RuleNoGenericCRUD = "K4-NO-GENERIC-CRUD"

RuleNoGenericCRUD forbids the generic CRUD constructs of the framework.

It is the only rule that forbids constructs instead of reporting contradictions, and it follows from P9 (analysability before brevity):

  • These factories produce specification facts at run time — six permissions derived from a prefix, a repository, three routes. A static analysis can only see them by reimplementing framework internals, and if the framework changes its naming rule the tool lies silently.
  • They yield modules that can only be adapted as a whole. A hand written use case can be changed one at a time.

Generic factories exist to save humans typing. Once an LLM writes the code that benefit is worthless, while the opacity remains.

The ban is empirically free: the reference project uses these constructs zero times, against 160 hand written permission.Declare calls. It forbids nothing that is in use; it closes a door before anyone walks through it.

View Source
const SpecPkgPath = "github.com/worldiety/speclink/spec"

SpecPkgPath is the import path of the public directive catalogue. Calls resolving into this package are the annotation language; everything else in an annotation file is a whitelist violation.

Variables

This section is empty.

Functions

func CheckBoundedContexts

func CheckBoundedContexts(pkgs []*Package, cfg config.Config, root string, out *diag.Set)

CheckBoundedContexts verifies the layout and the dependency direction of the bounded contexts.

func CheckInfrastructure

func CheckInfrastructure(pkgs []*Package, cfg config.Config, root string, out *diag.Set)

CheckInfrastructure verifies that infrastructure packages carry no domain knowledge.

The rule is stated in reverse on purpose. Deciding what counts as an infrastructure helper is a judgement call speclink cannot make; deciding whether a package under pkg/ knows about the domain is exact. Two markers suffice, and both are unambiguous:

  1. it imports a bounded context
  2. it declares a use case, i.e. a function taking an auth subject

Either one means the dependency points the wrong way: infrastructure is what the domain builds on, not the other way round.

func CheckMainPackages

func CheckMainPackages(pkgs []*Package, cfg config.Config, root string, out *diag.Set)

CheckMainPackages verifies that the module has an entry point and that every entry point lives where entry points belong.

Both halves matter. A module without a main package is a library, and saying so out loud is cheap; a main package scattered somewhere in the tree is the classic way a "small helper program" grows into a second application nobody reviews.

func CheckUseCases

func CheckUseCases(pkgs []*Package, cfg config.Config, root string, waived ir.Waivers, out *diag.Set)

CheckUseCases verifies the shape, the file layout, the authorisation and the dependency injection of every use case in a bounded context.

func TypeErrors

func TypeErrors(pkgs []*Package) []packages.Error

TypeErrors collects the Go compiler errors of the loaded packages.

Phase V2 is the Go compilation itself. When it fails there is no annotation feedback at all, and the loop runner has to prioritise accordingly: the build order is Go compiler, then speclink, then tests.

Types

type Package

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

Package is a loaded Go package together with the classification of its files.

func Load

func Load(dir string, patterns ...string) ([]*Package, error)

Load loads the given patterns from dir with full type information.

It returns an error only for failures of the load itself. Type errors in the loaded packages are reported through TypeErrors, because they belong to phase V2 which is the Go compilation and not a speclink phase.

func (*Package) CheckGenericCRUD

func (p *Package) CheckGenericCRUD(out *diag.Set)

CheckGenericCRUD reports every use of a generic CRUD factory.

func (*Package) CheckOrphans

func (p *Package) CheckOrphans(out *diag.Set)

CheckOrphans reports annotation files whose neighbouring source file is gone.

func (*Package) CheckWhitelist

func (p *Package) CheckWhitelist(out *diag.Set)

CheckWhitelist verifies that every annotation and requirement file of the package contains nothing but permitted nodes.

func (*Package) Infer

func (p *Package) Infer() []ir.Construct

Infer walks the ordinary source files of the package and returns the architectural constructs it recognises.

Annotation and requirement files are skipped: they carry the statements about constructs, not constructs themselves.

func (*Package) PersistedModels

func (p *Package) PersistedModels() map[string]bool

PersistedModels returns the fully qualified names of the types this package stores through a repository.

A repository interface says that something is kept somewhere; it does not say in what form, and a type may be declared far from the place that decides to store it. The construction is where the decision is made and where the form is fixed, so that is what is read here.

The framework offers the choice explicitly, and the two halves mean different things:

NewJSONRepository[Domain, DomainID, Persistence, PersistenceID]
NewSloppyJSONRepository[Domain, DomainID]

The first separates the two models and maps between them, so only the persistence model is promised and the domain model stays free. The second serialises the domain model directly; the framework's own documentation calls it a shorthand for throw-away prototypes where neither model has been stabilised. Choosing it ties the domain to the wire, and from then on every rename in the domain is a change to stored data.

func (*Package) PkgPath

func (p *Package) PkgPath() string

PkgPath returns the import path of the package.

func (*Package) ReadBindings

func (p *Package) ReadBindings(out *diag.Set) []ir.Binding

ReadBindings extracts every binding term of the package's annotation files.

Everything here reads the *typed* AST. Type resolution and evaluation are two different things: speclink resolves fully (via go/types) but never executes. That is what makes order irrelevant — terms are collected in one pass, no annotation can change the meaning of another.

func (*Package) ReadRequirements

func (p *Package) ReadRequirements(out *diag.Set) []*ir.Requirement

ReadRequirements extracts the requirement declarations of the package's *.spec.go files.

DerivedFrom and Supersedes are recorded as qualified Go identifiers, not as requirement IDs. Resolving them to IDs is the job of the second pass, once every declaration has been collected. That is what makes forward references legal and the input order irrelevant.

func (*Package) ReadSchema

func (p *Package) ReadSchema(models map[string]bool) []ir.SchemaType

ReadSchema returns the persisted shape of every type of this package whose form outlives the code.

Two things qualify. An event is self evident: it implements Evolve and carries a discriminator, so the struct is the wire format by definition. A persistence model is not, because nothing in its declaration says it is stored — that is decided where a repository is built over it, possibly in another package, so the names are collected beforehand and passed in.

Only the wire relevant facts are collected: the discriminator, and per field its Go name, the name it carries in JSON and its underlying shape. Everything else about the type may change freely.

Jump to

Keyboard shortcuts

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