sema

package
v0.2.2 Latest Latest
Warning

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

Go to latest
Published: Sep 4, 2026 License: Apache-2.0 Imports: 14 Imported by: 0

Documentation

Overview

Package sema owns thrift-ls's semantic analysis: the lint pipeline, the diagnostics it produces, and the fixes attached to them. All positions are parser coordinates; frontends translate to their own representations.

Index

Constants

View Source
const (
	CodeParseError        = "parse-error"
	CodeIncludeCycle      = "include-cycle"
	CodeFieldIDRange      = "field-id-range"
	CodeFieldIDConflict   = "field-id-conflict"
	CodeDuplicateDef      = "duplicate-definition"
	CodeDuplicateEnumVal  = "duplicate-enum-value"
	CodeDuplicateValue    = "duplicate-value"
	CodeImplicitEnumValue = "implicit-enum-value"
	CodeUnusedInclude     = "unused-include"
	CodeIncludeShadow     = "include-shadow"
	CodeUndefinedType     = "undefined-type"
	CodeUndefinedValue    = "undefined-value"
	CodeValueTypeMismatch = "value-type-mismatch"
	CodeNonScalarMapKey   = "non-scalar-map-key"
	CodeUnknownAnnotation = "unknown-annotation-type"
)

Diagnostic codes carried on every diagnostic the pipeline reports. Fix providers and frontend filters match on these — never on the message text, which is free to change.

Variables

This section is empty.

Functions

func BareName

func BareName(name string) string

BareName strips the include qualifier from a name: "base.User" becomes "User". References in files that include the definition file use the bare name, so qualified literals must match against it too.

func IncludeNameOf

func IncludeNameOf(file uri.URI) string

IncludeNameOf returns the include name of a file URI: the base name without extension. file:///base.thrift -> "base".

func IsBasicType

func IsBasicType(t string) bool

IsBasicType reports whether t is a built-in base type.

func ParseIdent

func ParseIdent(cur uri.URI, includes []*syntax.Include, identifier string) (include, ident string)

ParseIdent parses an identifier. identifier format:

  1. identifier
  2. include.identifier

it returns include, ident

func RefKindsFor

func RefKindsFor(k DefinitionKind) []store.RefKind

RefKindsFor returns the reference slots a definition kind can appear in.

An exception is thrown (signatures) but never used as a field type; as an annotation type it is legal, since the compiler's get_type resolves any declared type. Enum values and consts live in value positions. Services are extends-only. Every other type can appear in field, signature, and annotation-type slots.

func TypeReferenceName

func TypeReferenceName(ft *syntax.FieldType) string

TypeReferenceName returns the referenced type name of a FieldType, or "" for base types and containers.

Types

type Action

type Action struct {
	Title string
	Fix   bool // true: quickfix for a diagnostic; false: refactor
	File  uri.URI
	Edits []Edit
}

Action is an offered source edit: a quickfix for a diagnostic or a refactor.

type ActionProvider

type ActionProvider interface {
	Actions(ctx context.Context, f File, span Span, report Report) []Action
}

ActionProvider offers source edits for a selection, independent of any diagnostic: refactors. The report is available for providers whose actions double as quickfixes for diagnostics overlapping the selection.

type Analyzer

type Analyzer interface {
	Name() string
	Analyze(ctx context.Context, run *Run) error
}

Analyzer is a whole-run check: its findings may span files (include cycles are the current consumer), and it may need to see files that do not parse (Parse is the current consumer).

func EachFile

func EachFile(a FileAnalyzer) Analyzer

EachFile adapts a FileAnalyzer to Analyzer.

type Config

type Config struct {
	// Disabled names analyzers (by Name) to skip. nil runs all.
	Disabled []string
	// Severity overrides a diagnostic's severity by code.
	Severity map[string]Severity
}

Config selects and tunes analyzers.

func ConfigFromLint

func ConfigFromLint(disabled []string, severity map[string]string) Config

ConfigFromLint builds a Config from the config layer's lint settings: the analyzer names to skip and the severity overrides by code, with severities named "error", "warning", "info", and "hint". Unknown names are ignored; the config sources validate them.

type DefinitionKind

type DefinitionKind uint8

DefinitionKind identifies the kind of a resolved definition.

const (
	DefinitionNone DefinitionKind = iota
	DefinitionStruct
	DefinitionUnion
	DefinitionException
	DefinitionEnum
	DefinitionTypedef
	DefinitionConst
	DefinitionEnumValue
	DefinitionService
)

type Diagnostic

type Diagnostic struct {
	Code     string // stable identity; never matched by message
	Severity Severity
	Message  string
	Span     Span
	Fixes    []Fix
}

Diagnostic is one finding. Fixes are edits that resolve it, computed by the analyzer that reported the diagnostic.

type Edit

type Edit struct {
	Span    Span
	NewText string
}

Edit replaces Span with NewText in the file the diagnostic belongs to. An empty Span inserts.

type EnumImplicitValue

type EnumImplicitValue struct {
	Member *syntax.EnumValue
	Value  int64
	Known  bool // false when the preceding value is broken, so Value is unknowable
}

EnumImplicitValue is an enum member that lacks an explicit value, with the int constant the compiler auto-increments for it.

func EnumImplicitValues

func EnumImplicitValues(enum *syntax.Enum) []EnumImplicitValue

EnumImplicitValues reports the members of an enum that carry no explicit value, together with the value the compiler would auto-increment: 0 for the first member, one greater than the preceding member's value otherwise. Members after an unparseable explicit constant report Known=false until the next parseable constant settles the chain.

type EnumMemberValue added in v0.2.2

type EnumMemberValue struct {
	Member *syntax.EnumValue
	Value  int64
	Known  bool
}

EnumMemberValue is an enum member with the value the compiler resolves for it: explicit constants parse as written (base-0), implicit members auto-increment (0 for the first member, one greater than the preceding member's value otherwise). Known is false when the preceding value is broken, so Value is unknowable until the next parseable constant settles the chain.

func EnumMemberValues added in v0.2.2

func EnumMemberValues(enum *syntax.Enum) []EnumMemberValue

EnumMemberValues resolves every member of an enum to its on-wire value.

type File

type File struct {
	URI uri.URI
	PF  *store.ParsedFile
	// contains filtered or unexported fields
}

File is one file's analysis inputs: its parsed tree plus the run's shared state.

func NewFile added in v0.2.2

func NewFile(file uri.URI, pf *store.ParsedFile, view Graph) File

NewFile builds the File a direct Analyzer, Fixer, or ActionProvider call needs: the parsed file plus a run holding view and a fresh Index. Pipeline runs build Files themselves; this is for unit tests and one-off invocations outside a run.

func (File) Index

func (f File) Index() *Index

Index returns the run's shared cross-file resolver, memoized across every analyzer in the run.

func (File) View

func (f File) View() Graph

View returns the run's view (include resolver, dependency graph).

type FileAnalyzer

type FileAnalyzer interface {
	Name() string
	AnalyzeFile(ctx context.Context, f File) ([]Diagnostic, error)
}

FileAnalyzer is the per-file shape most checks take: findings depend only on the file itself. The runner loops files, skips unparseable ones, and collects read errors; the analyzer never sees the loop.

type Fix

type Fix struct {
	Title string
	Edits []Edit
}

Fix is a named set of edits resolving one diagnostic. The title is what the client shows in its quickfix menu.

func Apply added in v0.2.0

func Apply(content []byte, fixes []Fix) (out []byte, applied, skipped []Fix, err error)

Apply returns content with the fixes applied. A fix is all-or-nothing: when any of its edits overlaps an accepted fix's edits — or its own — the whole fix is skipped. Insertions (empty spans) never conflict with each other; at the same offset they land in argument order, and just before a replacement starting there. applied and skipped partition fixes in argument order. An edit outside content is an error: offsets come from the same parse the fixes were computed on, so a out-of-range span is a fixer bug, not a condition to skip over.

type FixResult added in v0.2.0

type FixResult struct {
	// Applied is the number of fixes applied across all passes.
	Applied int
	// FixedFiles lists the files whose content changed, sorted by URI.
	FixedFiles []uri.URI
	// Skipped lists the fixes that could not apply.
	Skipped []SkippedFix
	// Passes is the number of pipeline runs.
	Passes int
	// Remaining is the last pass's report: the diagnostics left after the
	// final pass, fixable or not.
	Remaining Report
}

FixResult reports one FixAll run.

type Fixer

type Fixer interface {
	Fix(ctx context.Context, f File, d Diagnostic) []Fix
}

Fixer computes fixes for diagnostics reported by other analyzers, on demand — for fixes too expensive to compute during analysis (a workspace search per unresolved type, for instance). The fixer self-filters on the diagnostic's code and returns nothing when it has none.

type Graph added in v0.2.0

type Graph interface {
	Parse(ctx context.Context, file uri.URI) (*store.ParsedFile, error)
	Dependents(file uri.URI) []uri.URI
	Includers(file uri.URI) []uri.URI
	KnownFiles() []uri.URI
	Folder() uri.URI
	WalkFiles(ctx context.Context, root uri.URI, fn func(uri.URI) error) error
	Resolver() *store.Resolver
}

Graph is the read surface analysis needs from the document store: parse files and walk the include graph. Analyzers, fixers, and providers see only this; the store's write and concurrency surface (Update, Evict, generations) stays with the session owner, except for the batch fixer below.

type Hit

type Hit struct {
	File uri.URI
	Span Span   // parser coordinates; the frontend maps them
	Text string // as written: "User", "shared.User", "shared.thrift.User"

	// Kind is the grammar slot the reference sits in, so callers can tell
	// type hits from value hits (e.g. for highlight kinds).
	Kind store.RefKind
}

Hit is one reference occurrence of a name, with the qualifying text preserved so a rename can rewrite includes correctly.

type Index

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

Index answers cross-file semantic queries over one view: definition resolution and reference search. It composes per-file store.FileIndexes over the include graph.

An Index is cheap — construct one per request with NewIndex. Resolutions are memoized per (file, name), so a request resolving the same name in the same file repeatedly (references, diagnostics) resolves it once.

func NewIndex

func NewIndex(view Graph) *Index

NewIndex returns an Index over the view's store.

func (*Index) FindInWorkspace

func (x *Index) FindInWorkspace(ctx context.Context, name string) (*Resolved, error)

FindInWorkspace returns the definition of name in any known file of the workspace, falling back to a directory walk when the workspace has not been indexed yet (e.g. a quick-fix on the first didOpen).

func (*Index) References

func (x *Index) References(ctx context.Context, file uri.URI, name string, kinds ...store.RefKind) ([]Hit, error)

References returns every occurrence of name in file and in every file that transitively includes it, restricted to the given reference kinds. The definition site is not included (no self-referencing hit). Hits are matched by bare name only; use ReferencesTo for resolution-matched results.

func (*Index) ReferencesTo

func (x *Index) ReferencesTo(ctx context.Context, def *Resolved, kinds ...store.RefKind) ([]Hit, error)

ReferencesTo returns every reference to def: in def.File and every file that transitively includes it. Hits are name- and resolution-matched, so same-named definitions elsewhere are not reported.

For an enum def, value references qualified with the enum name ("Color.RED", "shared.Color.RED") are matched too, provided the qualifier resolves to this very enum; the hit covers only the enum segment, so a rename rewrites the qualifier while keeping the member name.

func (*Index) ReferencingFiles

func (x *Index) ReferencingFiles(file uri.URI) []uri.URI

ReferencingFiles returns every file that directly includes file, in graph order.

func (*Index) ResolveService

func (x *Index) ResolveService(ctx context.Context, from *store.ParsedFile, ident *syntax.Identifier) (*Resolved, error)

ResolveService resolves a service name or extends reference, or returns nil when unresolved.

func (*Index) ResolveType

func (x *Index) ResolveType(ctx context.Context, from *store.ParsedFile, ft *syntax.FieldType) (*Resolved, error)

ResolveType resolves a type reference to its definition, or returns nil when unresolved (base types, unresolvable name). Files backing the resolution may fail to read; those count as unresolved.

func (*Index) ResolveValue

func (x *Index) ResolveValue(ctx context.Context, from *store.ParsedFile, v *syntax.ConstValue) (*Resolved, error)

ResolveValue resolves a const-value identifier to its definition (an enum value or a const), or returns nil when unresolved.

func (*Index) UnderlyingType

func (x *Index) UnderlyingType(ctx context.Context, from *store.ParsedFile, ft *syntax.FieldType) (*syntax.FieldType, *store.ParsedFile)

UnderlyingType follows typedef chains — across includes — until it reaches a type that is not itself a typedef, returning that type and the parsed file whose scope it resolves in. Every consumer that classifies a type by kind (value matching, completion, checks) resolves through here instead of comparing surface names, so "typedef map<string,string> M" classifies as a map everywhere.

An identifier that resolves to no definition returns nil — the type has no classifiable kind, and existence is another check's job. A cyclic or pathological chain stops at maxTypedefDepth steps.

func (*Index) View

func (x *Index) View() Graph

View returns the store view the index reads from.

type Pipeline

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

Pipeline runs analyzers over changed files. It is a value: safe to share, no state beyond the analyzers, fixers, providers, and config.

func New

func New(cfg Config, analyzers []Analyzer) *Pipeline

New composes a pipeline. Composition lives at the caller: no global registry.

func (*Pipeline) CodeActions

func (p *Pipeline) CodeActions(ctx context.Context, view Graph, file uri.URI, span Span, report Report) []Action

CodeActions returns the actions for span in file: quickfixes from the report's diagnostics overlapping span (inline fixes first, then the fixers), then the action providers' refactors.

func (*Pipeline) FixAll added in v0.2.0

func (p *Pipeline) FixAll(ctx context.Context, view Store, targets []uri.URI, persist func(context.Context, uri.URI, []byte) error) (FixResult, error)

FixAll runs the pipeline over targets and applies every applicable fix, re-running until a pass applies no fix or maxFixPasses passes have run. Only targets are analyzed and fixed; resolution and fixers read the whole view, so fixing one file of a workspace resolves against all of it and never touches the other files.

persist receives each changed file's new content: it must make the content durable (write to disk, update the editor overlay) and visible to view's file source before returning. FixAll drives view.Update itself, so persist must not.

func (*Pipeline) FixesForFile added in v0.2.0

func (p *Pipeline) FixesForFile(ctx context.Context, view Graph, file uri.URI, report Report) []Fix

FixesForFile returns every fix the pipeline offers for file: the inline fixes of the report's diagnostics for it, then the fixers' output for each diagnostic. Fixes are edits in parser coordinates on the file's current content.

func (*Pipeline) Run

func (p *Pipeline) Run(ctx context.Context, view Graph, changed []uri.URI) (Report, error)

Run analyzes changed over view: one run, one shared Index across all changed files and all analyzers.

func (*Pipeline) WithAnalyzers added in v0.2.0

func (p *Pipeline) WithAnalyzers(analyzers ...Analyzer) *Pipeline

WithAnalyzers returns a copy of the pipeline with analyzers appended.

func (*Pipeline) WithFixers

func (p *Pipeline) WithFixers(fs ...Fixer) *Pipeline

WithFixers returns a copy of the pipeline with the fixers added.

func (*Pipeline) WithProviders

func (p *Pipeline) WithProviders(ps ...ActionProvider) *Pipeline

WithProviders returns a copy of the pipeline with the action providers added.

type Report

type Report map[uri.URI][]Diagnostic

Report is the result of one analysis pass, keyed by file.

type Resolved

type Resolved struct {
	File   uri.URI
	Parsed *store.ParsedFile

	// Name is the definition's identifier node, whose range is the jump
	// target.
	Name *syntax.Identifier

	// Node is the definition itself: *syntax.Struct, *syntax.Enum, etc.
	// For an enum value, Node is the *syntax.Identifier (same as Name).
	Node syntax.Node

	Kind DefinitionKind
}

Resolved is a resolved definition: the target file, the parsed document, the definition identifier (jump target), and its kind.

func DefFromNode

func DefFromNode(pf *store.ParsedFile, n syntax.Node) *Resolved

DefFromNode builds a Resolved from any top-level definition node. Use when the concrete type and Kind are not known statically (e.g. FindInWorkspace).

type Run

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

Run is one analysis pass. Analyzers add findings to it.

func (*Run) Add

func (r *Run) Add(file uri.URI, ds ...Diagnostic)

Add appends findings for file, applying the run's severity overrides. The findings are copied: the caller keeps ownership of its slice.

func (*Run) Files

func (r *Run) Files() []uri.URI

Files returns the files the run analyzes.

func (*Run) Index added in v0.2.1

func (r *Run) Index() *Index

Index returns the run's shared cross-file resolver, memoized across every analyzer in the run.

func (*Run) View added in v0.2.1

func (r *Run) View() Graph

View returns the run's view (include resolver, dependency graph).

type Severity

type Severity uint8

Severity is a diagnostic's display weight.

const (
	SeverityError Severity = iota + 1
	SeverityWarning
	SeverityInfo
	SeverityHint
)

type SkippedFix added in v0.2.0

type SkippedFix struct {
	File   uri.URI
	Fix    Fix
	Reason string
}

SkippedFix is a fix FixAll could not apply.

type Span

type Span struct {
	Start, End syntax.Position
}

Span is a half-open file region in parser coordinates (1-based line and rune column, byte offset). The byte offsets are authoritative; the LSP frontend maps them through the file mapper to UTF-16.

func LineSpan added in v0.2.2

func LineSpan(content []byte, pos syntax.Position) Span

LineSpan returns the span of the whole source line containing pos, the trailing newline included. Fixers use this to delete line-held statements (unused includes) whole.

func SpanOf

func SpanOf(pf *store.ParsedFile, node syntax.Node) Span

SpanOf returns the source span of a node in the parsed file.

func TokenSpan added in v0.2.2

func TokenSpan(tok *syntax.Token) Span

TokenSpan returns a token's source span. Analyzers use this for token-level findings the node-based SpanOf cannot express: field IDs are bare tokens, not nodes.

func (Span) Overlaps

func (s Span) Overlaps(o Span) bool

Overlaps reports whether s and o share at least one position; a cursor at either endpoint counts.

type Store added in v0.2.0

type Store interface {
	Graph
	Update(ctx context.Context, changes ...*store.FileChange) store.ChangeResult
}

Store is the read-write surface batch fixing needs: analyze through Graph, land each pass through Update.

Jump to

Keyboard shortcuts

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