Documentation
¶
Overview ¶
Package contractcheck inspects a project's internal/ tree for contract-shape violations. It is the single home for the analyzers that previously lived as four sibling files under internal/linter/forgeconv/:
- internal-package-contract-names (Service/Deps/New canonical shape)
- interactor-deps-are-interfaces (interactor Deps fields are interfaces)
- adapter-no-rpc (adapter packages don't register RPC)
- utility-package auto-skip (pre-rule filter for the contract-names rule)
The forge entry points that used to call those analyzers individually (`preCodegenContractCheck` in internal/cli/generate.go and `runConventionLint` in internal/cli/lint.go) now route through one Inspect function. Per-call-site differences — which rules to run, what to exclude — are expressed as fields on Options, not as branches inside the engine.
API design notes ¶
Findings carry the same shape as the other forge lint rules (forgeconv.Finding) so they can be folded into the existing `combined := forgeconv.Result{}` pipelines without adapter glue. Severity, rule names, and remediation phrasing are preserved verbatim from the old call sites — this package is a re-organization, not a behavior change.
What this package is NOT ¶
The cmd/contractlint binary (and its supporting internal/linter/contract package) performs a fundamentally different check: it runs as a go/analysis multichecker that needs full type information to assert "exported methods on impl types must be declared in the contract interface". That check operates on *analysis.Pass, not on a rootDir walk, and so cannot share this engine. The two are complementary — contractcheck catches structural-shape mistakes at codegen time (before the type checker has anything to chew on); the multichecker catches behavioral-shape mistakes at vet time.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func AsResult ¶
AsResult wraps findings in a forgeconv.Result for callers that want to feed the output through the existing FormatText / combined-result plumbing without changing their shape.
func HasErrors ¶
HasErrors reports whether any finding in fs has Severity == SeverityError. Convenience so call sites don't have to roll their own loop.
func Inspect ¶
Inspect runs the configured rules over rootDir and returns findings in deterministic (file, line, rule) order. A missing rootDir/internal directory is not an error — it produces an empty result.
The context is honored only at coarse granularity: between rules. Individual rule implementations are tree-walks that complete in milliseconds on real projects, so we don't propagate ctx into the walk loops.
Types ¶
type Options ¶
type Options struct {
// Rules is the subset of rules to run. nil or empty means
// "run all rules in [AllRules]".
Rules []Rule
// Excludes is the contracts.exclude list from forge.yaml — a list
// of module-relative slash paths to skip wholesale. Only the
// internal-package-contract-names rule consults this; the other
// rules ignore it because the marker convention
// (`// forge:adapter` / `// forge:interactor`) is itself opt-in.
Excludes []string
}
Options controls a single Inspect run.
Per-call-site differences between forge's contract-check entry points (pre-codegen check vs. `forge lint --conventions`) all reduce to differences in these fields. Adding a new entry point should never require modifying the engine — it should pick the rule subset and excludes it needs and call Inspect.
type Rule ¶
type Rule string
Rule identifies a contract-shape analyzer by its canonical name. The names match the strings that already appear in finding output, so log-search queries and CI grep patterns survive this refactor unchanged.
const ( // RuleInternalPackageContractNames enforces the canonical // `type Service interface`, `type Deps struct`, and // `func New(Deps) Service` trio on every internal-package // contract.go. Errors gate the build because the bootstrap codegen // template hardcodes these names — a non-canonical contract // produces a bootstrap that doesn't compile. RuleInternalPackageContractNames Rule = "forgeconv-internal-package-contract-names" // RuleInteractorDepsAreInterfaces warns when an interactor-marked // package declares a Deps field whose type is a concrete struct // (or pointer to one) rather than an interface. The pattern // defeats the all-mock-test surface interactors are designed for. RuleInteractorDepsAreInterfaces Rule = "forgeconv-interactor-deps-are-interfaces" // RuleAdapterNoRPC warns when an adapter-marked package registers // a Connect RPC handler. Adapters are outbound-only by convention; // inbound RPC means the package should be a service. RuleAdapterNoRPC Rule = "forgeconv-adapter-no-rpc" )
The set of rules this package owns. New rules added here MUST be surfaced in AllRules so the default "run everything" path picks them up.
func AllRules ¶
func AllRules() []Rule
AllRules is the canonical "run everything" list. Callers that pass Options.Rules == nil get this set. It is a getter (not an exported package var) so the returned slice can't be mutated by callers and so it satisfies the exported-vars contract rule (no mutable exported package state).