Documentation
¶
Overview ¶
Package starlsp implements a Language Server Protocol server for Starlark.
The server splits work between two parsers, because they answer different questions. gotreesitter answers structural questions on every keystroke: it tolerates a half-typed buffer, which is exactly when an editor is most useful. go.starlark.net answers every question about correctness, because it is the same parser and resolver a Starlark host actually runs — so a diagnostic shown in an editor is one the host would report.
The core knows the Starlark specification and nothing else. A dialect — Bazel, Buck, Tilt, Starkite — supplies its builtins, its load() semantics, and its own lints through the Host interface. See host.go.
Everything here is pure Go. There is no cgo, and therefore no C toolchain and no cross-compilation cliff when embedding the server in another program.
This file carries the wire protocol: JSON-RPC 2.0 over Content-Length framed stdio, plus the LSP structures the server exchanges. The types are hand-written rather than taken from a protocol library, so embedding this server adds one dependency for parsing and none for the protocol.
Index ¶
- Constants
- func SpecFileOptions() *syntax.FileOptions
- type CompletionItem
- type CompletionItemKind
- type Diagnostic
- type DiagnosticSeverity
- type DialectOptions
- type Document
- type DocumentHighlight
- type DocumentHighlightKind
- type DocumentLink
- type DocumentSymbol
- type Documenter
- type FoldingRange
- type Host
- type Kind
- type Linter
- type LoadRequest
- type LoadResolver
- type Location
- type MarkupContent
- type MemberProvider
- type Options
- type ParameterInformation
- type Position
- type Range
- type SelectionRange
- type Server
- type SignatureInformation
- type Symbol
- type SymbolInformation
- type SymbolKind
- type TextDocumentIdentifier
- type TextDocumentItem
- type TextEdit
- type TypeRequest
- type TypeResolver
- type Vanilla
- type VersionedTextDocumentIdentifier
- type WorkspaceEdit
Constants ¶
const Version = "0.3.0"
Version is reported to the client at initialize.
Variables ¶
This section is empty.
Functions ¶
func SpecFileOptions ¶ added in v0.2.0
func SpecFileOptions() *syntax.FileOptions
SpecFileOptions is the Starlark specification's own dialect: no top-level control flow, no while, no set, no global reassignment.
Types ¶
type CompletionItem ¶
type CompletionItem struct {
Label string `json:"label"`
Kind CompletionItemKind `json:"kind,omitempty"`
Detail string `json:"detail,omitempty"`
Documentation *MarkupContent `json:"documentation,omitempty"`
InsertText string `json:"insertText,omitempty"`
SortText string `json:"sortText,omitempty"`
}
type CompletionItemKind ¶
type CompletionItemKind int
type Diagnostic ¶
type Diagnostic struct {
Range Range `json:"range"`
Severity DiagnosticSeverity `json:"severity,omitempty"`
Source string `json:"source,omitempty"`
Message string `json:"message"`
}
type DiagnosticSeverity ¶
type DiagnosticSeverity int
const ( SeverityError DiagnosticSeverity = 1 SeverityWarning DiagnosticSeverity = 2 SeverityInformation DiagnosticSeverity = 3 SeverityHint DiagnosticSeverity = 4 )
Severities a host may set on a Diagnostic it returns.
type DialectOptions ¶ added in v0.2.0
type DialectOptions interface {
FileOptions() *syntax.FileOptions
}
A DialectOptions host controls the language variant the parser and resolver enforce.
Starlark is not one language here either. go.starlark.net gates several behaviours behind syntax.FileOptions, and real dialects differ on them: Tilt permits if and for at the top level, Bazel does not; some hosts enable the set builtin, most do not. Getting this wrong produces a syntax error on a file that its own runtime accepts, which is the worst failure a language server has.
A host that does not implement this gets the specification defaults.
type Document ¶
type Document struct {
// contains filtered or unexported fields
}
Document holds one open buffer, its line index, and its parse tree.
The tree is produced by gotreesitter and is always available, even when the buffer does not parse as valid Starlark. That is the whole reason the structural half of this server uses tree-sitter: a language server is most useful exactly when the buffer is half-typed.
func (*Document) Path ¶ added in v0.1.1
Path is the document's filesystem path, empty when the buffer is not on disk.
func (*Document) PositionOf ¶ added in v0.1.1
PositionOf converts a byte offset into an LSP position.
func (*Document) SpanRange ¶ added in v0.1.1
SpanRange converts a Starlark syntax node's span into an LSP range, so a host can report a diagnostic against a node it found in the AST.
func (*Document) Text ¶ added in v0.1.1
Text returns the buffer's current bytes. The slice must not be modified.
type DocumentHighlight ¶
type DocumentHighlight struct {
Range Range `json:"range"`
Kind DocumentHighlightKind `json:"kind,omitempty"`
}
type DocumentHighlightKind ¶
type DocumentHighlightKind int
DocumentHighlightKind distinguishes a read from a write at a location.
type DocumentLink ¶
type DocumentSymbol ¶
type DocumentSymbol struct {
Name string `json:"name"`
Detail string `json:"detail,omitempty"`
Kind SymbolKind `json:"kind"`
Range Range `json:"range"`
SelectionRange Range `json:"selectionRange"`
Children []DocumentSymbol `json:"children,omitempty"`
}
type Documenter ¶
A Documenter supplies prose and signatures the server cannot introspect.
A Starlark builtin carries neither its parameter names nor its description, so a host that has documentation elsewhere — reference files, stub files, Go doc comments — surfaces it here. Symbols returned from Globals and Members may carry their own documentation instead; this hook exists for hosts whose documentation is not attached to the values.
type FoldingRange ¶
type Host ¶
type Host interface {
// Name identifies the dialect in server logs and in the initialize
// response, for example "bazel" or "starkite".
Name() string
// Globals returns the names the host predeclares, beyond the universe the
// Starlark specification defines. The server treats these as defined, so
// they are also what stops the resolver reporting them as undefined names.
Globals() []Symbol
}
A Host teaches the server about one Starlark dialect.
Starlark has no single dialect. Bazel, Buck, Tilt, and Starkite each add their own builtins, their own meaning for load(), and their own rules about what a well-formed file looks like. The core of this server knows the language specification and nothing else; a Host supplies the rest.
Only Name and Globals are required. Everything else is an optional interface, checked with a type assertion, so a host that merely adds a few builtins implements two methods rather than six.
The vanilla host in this package implements Host alone, and is what the standalone binary uses when no dialect is configured.
func Hosts ¶
Hosts composes several hosts into one.
Names from later hosts take precedence on collision, which lets a dialect layer over a base without either knowing about the other. Optional interfaces are honoured for every member: a composite answers a member query from the first host that has an answer.
type Kind ¶
type Kind int
Kind classifies a name for the editor.
const ( KindUnknown Kind = iota KindFunction KindMethod KindNamespace // a module or package: fs, http KindType // a constructible type: Path, Response KindProperty // an attribute read without a call KindVariable KindConstant KindKeyword KindParameter )
The kinds a host can produce. They map onto LSP completion item kinds and onto semantic token types.
type Linter ¶
type Linter interface {
Lint(doc *Document, file *syntax.File) []Diagnostic
}
A Linter reports diagnostics that belong to the dialect rather than to the language, such as an entry-point convention or a permission rule.
The server calls it after its own analysis. File is nil when the buffer does not parse, so a linter that needs an AST must check for that and return nothing rather than guess at a broken buffer.
type LoadRequest ¶
type LoadRequest struct {
// Target is the literal text inside the load() call, without quotes.
Target string
// From is the filesystem path of the file containing the load(), or empty
// when the buffer is not on disk.
From string
}
LoadRequest describes one load() target to resolve.
type LoadResolver ¶
type LoadResolver interface {
ResolveLoad(req LoadRequest) (path string, ok bool)
}
A LoadResolver maps a load() target to a file.
This is the most dialect-specific hook in the interface. Bazel takes a label, Tilt takes a path, and Starkite takes either a path or a "namespace/name" identity pinned by a lockfile. The core never guesses; without this hook a load() target is highlighted but not navigable.
type MarkupContent ¶
type MemberProvider ¶
A MemberProvider supplies the names reachable through a dot.
owner is either a namespace the host predeclared — "fs" — or a type name that a TypeResolver produced — "fs.path". A host that has no member structure does not implement this, and the server offers nothing after a dot rather than guessing.
type Options ¶
type Options struct {
// Host supplies the dialect. Defaults to NewVanilla, which is Starlark as
// the specification defines it.
Host Host
// In and Out are the client transport. Both default to stdio.
In io.Reader
Out io.Writer
// Log receives server-side messages. It must never be Out, because
// anything written there corrupts the protocol stream.
Log io.Writer
}
Options configures a server.
type ParameterInformation ¶
type ParameterInformation struct {
Label string `json:"label"`
}
type Position ¶
Position is a zero-based line and UTF-16 code-unit offset. The UTF-16 column is the protocol default and is what editors send.
type SelectionRange ¶
type SelectionRange struct {
Range Range `json:"range"`
Parent *SelectionRange `json:"parent,omitempty"`
}
SelectionRange is one step of expand-selection, linked to its parent.
type Server ¶
type Server struct {
// contains filtered or unexported fields
}
Server is a Starlark language server.
It is usable two ways. Run drives it over a transport, which is what the standalone binary does. A host application can also embed it and supply its own Host, which is the reason the type and its Options are exported.
func (*Server) Diagnose ¶ added in v0.2.0
func (s *Server) Diagnose(uri, text string) []Diagnostic
Diagnose analyses one document and returns its diagnostics, with no client and no transport.
It is the whole server minus the protocol: the same parse, the same resolver, the same host lints an editor would see. That makes it usable as a linter — in CI, in a pre-commit hook, or in a program that embeds this package — without pretending to be an editor.
uri may be a file:// URI or a plain path. It is used for error messages and for any host hook that resolves relative to the file.
func (*Server) DiagnoseFile ¶ added in v0.2.0
func (s *Server) DiagnoseFile(path string) ([]Diagnostic, error)
DiagnoseFile reads a file from disk and analyses it.
func (*Server) Probe ¶
Probe renders what this server would offer, without a client.
It exists so a host's surface can be inspected directly. Anyone debugging a missing completion can see exactly which names and owners the configured host exposes, rather than inferring it from an editor.
func (*Server) Run ¶
Run serves the client until the stream closes or the client exits.
Notifications that mutate a document are handled inline, in arrival order, because their order is their meaning: a didChange that overtook its didOpen would corrupt the buffer. Everything else is read-only and runs concurrently.
type SignatureInformation ¶
type SignatureInformation struct {
Label string `json:"label"`
Documentation *MarkupContent `json:"documentation,omitempty"`
Parameters []ParameterInformation `json:"parameters,omitempty"`
}
type Symbol ¶
type Symbol struct {
// Name is the identifier as written in source: "read_text".
Name string
// Owner is the namespace or type that holds it, empty for a global.
Owner string
// Kind classifies it for completion and highlighting.
Kind Kind
// Signature is the full call form when known: "fs.path(p)". The server
// parses parameters out of it for signature help, so the parentheses
// matter.
Signature string
// Returns is the result type, shown beside the name in completion.
Returns string
// Doc is markdown prose shown on hover.
Doc string
// Deprecated marks a name the editor should strike through.
Deprecated bool
// SortText overrides completion ordering. Leave empty for the default,
// which sorts host globals after file-local names.
SortText string
// Detail overrides the right-hand text in the completion list. Leave empty
// to let the server compose it from Signature and Returns.
Detail string
}
A Symbol is one completable, hoverable name.
A host builds these from whatever it actually knows. Only Name is required; everything else improves what the editor shows and none of it is guessed by the server.
type SymbolInformation ¶
type SymbolInformation struct {
Name string `json:"name"`
Kind SymbolKind `json:"kind"`
Location Location `json:"location"`
ContainerName string `json:"containerName,omitempty"`
}
SymbolInformation is the flat symbol shape workspace/symbol returns.
type SymbolKind ¶
type SymbolKind int
type TextDocumentIdentifier ¶
type TextDocumentIdentifier struct {
URI string `json:"uri"`
}
type TextDocumentItem ¶
type TypeRequest ¶
type TypeRequest struct {
// Expr is the receiver expression, for example "fs.path(\"/etc/hosts\")".
Expr string
// Document is the buffer the expression appears in, so a host can consult
// the file's own bindings.
Document *Document
// Resolve re-enters the server's inference for a sub-expression. A host
// uses it to walk a chain without reimplementing the walk.
Resolve func(expr string) (owner string, ok bool)
}
TypeRequest describes one type-inference question.
type TypeResolver ¶
type TypeResolver interface {
ResolveType(req TypeRequest) (owner string, ok bool)
}
A TypeResolver maps an expression to the owner whose members it exposes.
The server calls it with the receiver text left of the caret's dot, already trimmed: "fs", "fs.path(\"/etc\")", or a bare identifier the server has resolved to its binding expression. Returning false means the server offers no completion, which is deliberately better than offering the wrong list.
type Vanilla ¶
type Vanilla struct {
// contains filtered or unexported fields
}
Vanilla is the Host for Starlark as the specification defines it, with no dialect extensions.
Its name set is introspected rather than written down. The globals come from starlark.Universe, and the methods on strings, lists, dicts, sets, and bytes come from calling AttrNames on a zero value of each type. A build of go.starlark.net that adds a method therefore gains completion for it with no change here.
The documentation below is written by hand, because a Starlark builtin carries neither its parameter names nor its description. That is acceptable for exactly one reason: the Starlark specification is frozen. A hand-written table tracking a moving API rots; a hand-written table tracking a specification does not.
func (*Vanilla) ResolveType ¶
func (v *Vanilla) ResolveType(req TypeRequest) (string, bool)
ResolveType infers the type of a literal receiver, so that completion works after a string, list, dict, or set written inline.
type WorkspaceEdit ¶
WorkspaceEdit carries a rename's replacements, keyed by document URI.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
cmd
|
|
|
starlsp
command
Command starlsp is a language server for Starlark.
|
Command starlsp is a language server for Starlark. |
|
dialects
|
|
|
bazel
Package bazel is the Bazel dialect of Starlark, for BUILD, BUILD.bazel, WORKSPACE, MODULE.bazel, and .bzl files.
|
Package bazel is the Bazel dialect of Starlark, for BUILD, BUILD.bazel, WORKSPACE, MODULE.bazel, and .bzl files. |
|
tilt
Package tilt is the Tilt dialect of Starlark, for Tiltfiles.
|
Package tilt is the Tilt dialect of Starlark, for Tiltfiles. |