Documentation
¶
Overview ¶
Package engine defines the ports an engine implements and the catalogue that selects engines for a request.
Roles and ports ¶
Each Role has its own port interface, such as Outliner or Planner. An engine that cannot serve a role does not implement its port, and the catalogue selects engines by type assertion. An engine can also decline a role of a port it implements by declaring trust.None for it.
Evidence ¶
An engine declares a fixed fidelity and cost per role and returns a Result with its items, coverage, and caveats. Services turn the result into an Answer with Publish, which takes the tier from the engine. An engine cannot overstate its evidence.
Selection ¶
Catalog.For orders the engines of a language and role by fidelity, then by cost. Ask tries them in order. ErrDecline moves on to the next engine. Any other error stops the search, because a weaker answer would hide a broken engine.
AskEach and AskAny apply Ask to the languages of a request:
- AskEach asks every language for the read path. A failed language is recorded in Declined, and the other languages still answer. A refusal is returned when no answer Examined a file of its language.
- AskAny returns the first answer for the write path that is not skipped. A skipped answer moves on to the next language, because the scope contains no file of the language that skipped it. Any error stops it, because the next language can answer about a different declaration.
Refusals ¶
An engine refuses a request that the caller must change with ErrRefuse, such as a request about a path that does not exist. Refusal returns the reason of a refusal. A service returns it in the answer of Refused, as it returns a request that no engine serves in the answer of Unsupported.
A Verifier declines a request that names a suite it does not run with the error of Unrun, because an answer without that suite says nothing about it.
Waits ¶
Timing gives the context of one call a Waited. An engine brackets each wait for a process outside techne with Waiting, such as a request to a language server, and the caller reads the sum with Waited.Total. The time of the call without the waits is the time that techne itself took.
Dependency position ¶
Imports the standard library, core/edit, core/sema, core/source, core/trust, and core/internal/wire.
Index ¶
- Constants
- Variables
- func Ask[T any](ctx context.Context, c *Catalog, language source.Language, role Role, ...) (Answer[T], bool, Declined, error)
- func AskAny[T any](ctx context.Context, c *Catalog, r Router, req Request, role Role, ...) (Answer[T], bool, Declined, error)
- func AskEach[T any](ctx context.Context, c *Catalog, r Router, req Request, role Role, ...) ([]Answer[T], Declined, error)
- func Examined[T any](answers []Answer[T]) bool
- func Languages(r Router, req Request) []source.Language
- func Refusal(err error) (string, bool)
- func Unrouted(r Router, req Request) error
- func Unrun(name string, runs, asked []string) error
- func Untimed(ctx context.Context) context.Context
- func Waiting(ctx context.Context) func()
- type Answer
- type Available
- type Bindings
- type Capability
- type Catalog
- type Checker
- type Closer
- type Cost
- type Declined
- type Engine
- type Formatter
- type Indexer
- type Invalidation
- type Outliner
- type Planner
- type Query
- type Relator
- type Request
- type Resolver
- type Result
- type Role
- type Router
- type Searcher
- type Verifier
- type Waited
Constants ¶
const Root source.Path = "."
Root is the scope of the whole workspace.
Variables ¶
var ErrDecline = errors.New("engine: decline")
ErrDecline reports that an engine serves the role but cannot serve this request. Ask records the reason in Declined and tries the next engine. Wrap it with the reason, as in fmt.Errorf("%w: reason", ErrDecline).
var ErrRefuse = errors.New("engine: refuse")
ErrRefuse reports that an engine understands a request and will not serve it, for a reason the caller can act on. Ask returns it without trying another engine, because the request must change before any engine can serve it. The read path and the write path report it as a refusal with the reason that Refusal returns.
Functions ¶
func Ask ¶
func Ask[T any]( ctx context.Context, c *Catalog, language source.Language, role Role, want trust.Fidelity, call func(Engine) (Result[T], error), ) (Answer[T], bool, Declined, error)
Ask asks the engines that serve role for language, in catalogue order, and returns the first answer and true. An engine that returns ErrDecline is skipped and its reason recorded in Declined. Any other error stops the search and is returned, because an answer from a weaker engine would hide a broken stronger one.
call invokes the port of role on one engine. If no engine returns an answer, Ask returns false and the reasons of the engines that declined.
func AskAny ¶
func AskAny[T any]( ctx context.Context, c *Catalog, r Router, req Request, role Role, call func(Engine) (Result[T], error), ) (Answer[T], bool, Declined, error)
AskAny asks the languages the request is about, in order, and returns the first answer that is not skipped, and true. A skipped answer states that the scope contains no file of its language, so AskAny asks the next language. Any error stops the search and is returned. The answer of the first language defines the result, so asking the next language after a failure can return an answer about a different declaration.
The write path calls AskAny to plan a change and to check it.
func AskEach ¶
func AskEach[T any]( ctx context.Context, c *Catalog, r Router, req Request, role Role, call func(Engine) (Result[T], error), ) ([]Answer[T], Declined, error)
AskEach asks every language the request is about and returns every answer. A language that fails is recorded in Declined with its error, and the other languages are still asked. AskEach returns the first failure only when no language answered or ctx is done. It returns the first refusal, an error that wraps ErrRefuse, when no answer Examined a file of its language, because the request must change before a language can serve it.
The read path calls AskEach and merges the answers. A merge marks the coverage of an answer with a Declined entry as partial.
func Examined ¶
Examined reports whether an answer of answers examined a file of its language. A skipped answer did not.
func Languages ¶
Languages returns the languages a request is about. It returns the request's Language if one is set. Otherwise it returns the language that claims the scope's extension. A scope with an extension that no language claims is about no language, and a scope without an extension is treated as a directory and is about every language.
The read path and the write path both call Languages, so both route a path to the same languages. A directory whose name contains a dot is treated as a file.
func Refusal ¶
Refusal returns the reason of err, the text after the text of ErrRefuse, and reports whether err wraps ErrRefuse. An error that wraps ErrRefuse without its text returns its whole text.
func Unrouted ¶
Unrouted returns an error that wraps ErrRefuse when req names a language that r does not serve, and when req names a language and a file that another language of r claims by its extension. It returns nil otherwise, also for a language named with a path that no language claims.
func Unrun ¶
Unrun returns nil when the engine named name runs every suite of asked, and otherwise an error that wraps ErrDecline and names the suites of asked that runs does not contain. A Verifier returns it for a request that names a suite it does not run, because an answer without that suite says nothing about it.
Types ¶
type Answer ¶
type Answer[T any] struct { // Language is the language of the engine that published the answer. It is // empty for an answer that a service merged from the answers of several // languages. Language source.Language Items []T Provenance trust.Provenance Status trust.Status // Skipped is [Result.Skipped] of the engine that answered. Services use it // to leave the answer out of a merge across languages. Callers read // Provenance. Skipped bool }
Answer is a published result: the items an engine returned and the evidence behind them. Services build an Answer with Publish. Engines return a Result and never build an Answer.
Empty Items do not prove absence. trust.Status.Answered reports whether an engine ran, and trust.Provenance.SupportsNegativeClaim reports whether an empty list proves that none exist.
func Publish ¶
Publish stamps r with the evidence behind it. The engine name, the tier and the language come from e, never from r. The status is Degraded when the tier is below want, Partial when the coverage is partial, and OK otherwise. Degraded takes precedence over Partial.
Services call Publish. Engines never do.
func Refused ¶
Refused returns the answer for a request that an engine refused, for a reason that the caller can act on: no items, status Refused, and a caveat with the reason.
func Unsupported ¶
Unsupported returns the answer for a request that nothing could serve: no items, status Unsupported, and a caveat with the reason.
type Available ¶
Available is implemented by an engine that depends on something outside the process, such as a language server binary. The catalogue does not select an engine whose Available returns an error, and Catalog.Capabilities reports the error. An engine that does not implement Available is always available.
type Bindings ¶
type Bindings uint8
Bindings are the bindings that an answer contains beside the declarations that a file offers to the rest of a program. The zero value contains none of them.
const ( // BindImports contains what a file brings into scope. BindImports Bindings = 1 << iota // BindParameters contains the parameters and the type parameters of each // signature. BindParameters // BindLocals contains the declarations inside the body of a callable or // the value of a binding, which [sema.Locals] reports. BindLocals // BindLabels contains the labels of statements. BindLabels // BindAll contains every binding. BindAll = BindImports | BindParameters | BindLocals | BindLabels )
func (Bindings) Keeps ¶
Keeps reports whether an answer with the bindings b contains a declaration of kind. local reports that the declaration is inside the body of a callable or the value of a binding. An import, a parameter, a type parameter and a label take their own binding before local applies.
type Capability ¶
type Capability struct {
Language source.Language
Engine string
// Unavailable is the reason the engine cannot run, or empty.
Role Role
Fidelity trust.Fidelity
Cost Cost
// Available reports whether the engine can run now.
Available bool
}
Capability is one role that one engine serves for one language.
type Catalog ¶
type Catalog struct {
// contains filtered or unexported fields
}
Catalog is the set of registered engines. Add is not safe for concurrent use. Build the catalog before serving requests, after which every other method is safe for concurrent use.
func (*Catalog) Add ¶
Add registers an engine. It returns an error if an engine with the same name already serves the same language, because a provenance identifies the engine that answered by name. One name can serve more than one language, as typescript-language-server does for TypeScript and JavaScript.
func (*Catalog) Capabilities ¶
func (c *Catalog) Capabilities(ctx context.Context) []Capability
Capabilities returns one Capability for every role that each engine offers, in registration order. It includes the engines that cannot run, with Available false and the error text in Unavailable, so a caller can tell a missing server from a missing capability.
func (*Catalog) Close ¶
Close closes every registered engine that implements Closer. It calls every engine even when one fails, and returns the failures joined with errors.Join. The catalogue is not usable after Close, and Close is not safe for concurrent use with other methods.
type Checker ¶
type Checker interface {
Check(ctx context.Context, files map[source.Path][]byte) (Result[edit.Finding], error)
}
Checker reports the findings for content that is not on disk. The write path calls it on the projected content of a change before it writes, and a dry run calls it without writing.
A Checker judges content in memory. A Verifier judges the workspace on disk. An engine can implement either port without the other.
type Closer ¶
Closer is implemented by an engine that keeps a subprocess or a connection open across calls. Catalog.Close closes it.
type Cost ¶
type Cost uint8
Cost is what producing an answer takes, cheapest first. It is independent of fidelity: the catalogue orders engines by fidelity and breaks ties by cost.
const ( // CostMemory is a lookup in a structure already in memory. CostMemory Cost = iota // CostParse is a parse of each file in scope. CostParse // CostAnalyze is a type check of a package graph. CostAnalyze // CostSession is expensive once and cheap afterwards, such as a language // server that loads the workspace when it starts. CostSession // CostProcess is a subprocess per call. CostProcess )
type Declined ¶
type Declined []string
Declined lists why engines could not answer a request, one reason per engine that declined or language that failed.
type Engine ¶
type Engine interface {
// Name identifies the engine in a provenance and a capability report.
Name() string
// Language returns the language the engine serves.
Language() source.Language
// Fidelity returns the tier the engine declares for a role. The value is
// fixed for the engine's lifetime. [trust.None] declines the role. The
// catalogue calls Fidelity only for roles whose port the engine
// implements.
Fidelity(Role) trust.Fidelity
// Cost returns what serving a role takes.
Cost(Role) Cost
}
Engine is the interface every adapter implements. An adapter also implements the port of each role it serves.
type Formatter ¶
type Formatter interface {
Format(ctx context.Context, paths []source.Path) (Result[edit.Change], error)
}
Formatter returns the changes that format paths. The changes do not touch any other path.
type Indexer ¶
type Indexer interface {
Index(ctx context.Context, p source.Path) (Result[sema.Symbol], error)
Granularity() Invalidation
Affected(changed source.Path) []source.Path
}
Indexer produces the declarations an index stores for one file.
Granularity returns the widest invalidation a change can cause. It is fixed for the engine's lifetime. Affected returns the paths to index again after a change to one path.
type Invalidation ¶
type Invalidation uint8
Invalidation is the set of files that a change to one file makes stale. The values are ordered from the narrowest to the widest. The zero value is InvalidateFile.
const ( // InvalidateFile marks only the changed file stale. InvalidateFile Invalidation = iota // InvalidateUnit marks every file of the changed file's unit stale. InvalidateUnit // InvalidateWorkspace marks every file stale, so the index rebuilds // everything after each change. InvalidateWorkspace )
type Planner ¶
type Planner interface {
Plan(
ctx context.Context,
req Request,
op edit.Operation,
target edit.Target,
args edit.Args,
) (Result[edit.Change], error)
}
Planner computes the changes an operation makes. It does not write them. The write path seals, checks and writes the changes, so atomicity does not depend on the engine.
type Query ¶
type Query struct {
// Text matches declaration names by fuzzy match and documentation by
// content.
Text string
// Kind restricts matches to one kind. KindUnknown matches every kind.
Kind sema.Kind
// Private includes declarations that are not visible outside their
// unit.
Private bool
// Include selects the bindings that the search returns beside the
// declarations that a file offers to the rest of a program. The engine
// applies it before Limit.
Include Bindings
// Limit caps the number of items. Zero selects the engine's default.
Limit int
}
Query is a search for declarations.
type Relator ¶
type Relator interface {
Relate(ctx context.Context, req Request, of sema.ID, kind sema.RelationKind) (Result[sema.Relation], error)
}
Relator returns the relations of one kind from a declaration. The engine returns them in the direction that kind names, whichever direction it stores, and may stop at the Request.Limit of the caller. Request.Declared is the span at which the caller found the declaration.
type Request ¶
type Request struct {
// Scope is a file or a directory, relative to the workspace root.
Scope source.Path
// Language restricts the request to one language. Empty selects the
// languages that [Languages] returns for Scope.
Language source.Language
// Declared is the span of the declaration that [Relator.Relate] starts
// from, as the outline of the caller reports it, or the zero span. An
// engine whose own declarations do not match the ID can find the
// declaration at this span.
Declared source.Span
// Limit is the number of relations that the caller keeps from
// [Relator.Relate], and zero keeps every relation. An engine may return
// only the first Limit relations by the path and offset of their sites. It
// then adds a [trust.CaveatTruncated] caveat that counts the relations it
// found.
Limit int
// Preferred is the lowest tier the caller wants. An answer below it is
// published as [trust.Degraded], not refused.
Preferred trust.Fidelity
// Tests includes the files the language treats as tests. The language
// module defines which paths those are.
Tests bool
}
Request is the scope of one question. Limit and Declared apply to Relator.Relate alone.
type Resolver ¶
type Resolver interface {
Resolve(ctx context.Context, req Request, at source.Position) (Result[sema.Symbol], error)
}
Resolver returns the declarations that the name at a position denotes. More than one item means the name is ambiguous.
The position can contain only a line and a column. The engine computes the offset from the file, so Resolve is the one port where source.Position.Offset is not authoritative on input.
type Result ¶
type Result[T any] struct { Items []T // Caveats are limits on this answer, such as files the engine did not // read. Caveats []trust.Caveat // Completeness is how much of the requested scope the engine examined. Completeness trust.Completeness // Lowered is a tier below the engine's declared one, for an answer worth // less than usual, such as a type check of code that does not compile. // Publish uses the lower of the two. The zero value keeps the declared // tier. Lowered trust.Fidelity // Skipped reports that the scope contains no file of the engine's // language. Services leave skipped answers out of the evidence they merge // across languages. An engine that read files without a match returns an // empty result with Skipped false. An engine that cannot find what the // request names returns ErrDecline. Skipped bool }
Result is the return value of every engine port. It has no field for the engine's name or tier, and Lowered can only lower the tier the engine declares, so an engine cannot overstate its evidence. Services turn a Result into an Answer with Publish.
type Role ¶
type Role uint8
Role is a question an engine can answer. Each role has its own port interface, and engines declare a fidelity and a cost per role. The zero value is RoleUnset.
const ( // RoleUnset is no role. It is never valid on a request. RoleUnset Role = iota // RoleOutline lists the declarations in a scope. See Outliner. RoleOutline // RoleSearch finds declarations by name. See Searcher. RoleSearch // RoleResolve finds the declaration a name denotes. See Resolver. RoleResolve // RoleRelate finds the edges of a declaration. See Relator. RoleRelate // RolePlan computes the edits of an operation. See Planner. RolePlan // RoleFormat computes formatting edits. See Formatter. RoleFormat // RoleCheck reports problems in content that is not on disk. See // Checker. RoleCheck // RoleVerify reports problems in a scope on disk. See Verifier. RoleVerify // RoleIndex produces the facts an index stores. See Indexer. RoleIndex )
type Router ¶
type Router interface {
// LanguageOf returns the language that claims p by its extension.
LanguageOf(p source.Path) (source.Language, bool)
// Languages returns every registered language, in a fixed order.
Languages() []source.Language
}
Router maps paths to languages. The language registry implements it.
type Searcher ¶
type Searcher interface {
Search(ctx context.Context, req Request, q Query) (Result[sema.Symbol], error)
}
Searcher finds the declarations in a scope that match a query.
type Verifier ¶
type Verifier interface {
Verify(ctx context.Context, req Request, suites []string) (Result[edit.Finding], error)
}
Verifier reports the findings of the language's toolchain for a scope on disk. Suites names the checks to run in the language's own terms, such as linters or test runners. An empty list runs the language's default. An engine declines a request that names a suite it does not run, with the error of Unrun.
type Waited ¶
type Waited struct {
// contains filtered or unexported fields
}
Waited sums the time that the engines of one call wait for a process outside techne, such as the reply of a language server. Waits that overlap each add their whole time, so the sum can exceed the time of the call. Waited is safe for concurrent use.