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
- func BareName(name string) string
- func IncludeNameOf(file uri.URI) string
- func IsBasicType(t string) bool
- func ParseIdent(cur uri.URI, includes []*syntax.Include, identifier string) (include, ident string)
- func RefKindsFor(k DefinitionKind) []store.RefKind
- func TypeReferenceName(ft *syntax.FieldType) string
- type Action
- type ActionProvider
- type AddIncludeFixer
- type Analyzer
- type Config
- type CycleCheck
- type DefinitionKind
- type Diagnostic
- type DuplicateCheck
- type Edit
- type EnumImplicitValue
- type EnumValueCheck
- type EnumValuesProvider
- type FieldIDCheck
- type FieldQualifierProvider
- type File
- type FileAnalyzer
- type Fix
- type FixResult
- type Fixer
- type Graph
- type Hit
- type Include
- type IncludeShadowCheck
- type Index
- func (x *Index) FindInWorkspace(ctx context.Context, name string) (*Resolved, error)
- func (x *Index) References(ctx context.Context, file uri.URI, name string, kinds ...store.RefKind) ([]Hit, error)
- func (x *Index) ReferencesTo(ctx context.Context, def *Resolved, kinds ...store.RefKind) ([]Hit, error)
- func (x *Index) ReferencingFiles(file uri.URI) []uri.URI
- func (x *Index) ResolveService(ctx context.Context, from *store.ParsedFile, ident *syntax.Identifier) (*Resolved, error)
- func (x *Index) ResolveType(ctx context.Context, from *store.ParsedFile, ft *syntax.FieldType) (*Resolved, error)
- func (x *Index) ResolveValue(ctx context.Context, from *store.ParsedFile, v *syntax.ConstValue) (*Resolved, error)
- func (x *Index) UnderlyingType(ctx context.Context, from *store.ParsedFile, ft *syntax.FieldType) (*syntax.FieldType, *store.ParsedFile)
- func (x *Index) View() Graph
- type NonScalarMapKeyCheck
- type ParseCheck
- type Pipeline
- func (p *Pipeline) CodeActions(ctx context.Context, view Graph, file uri.URI, span Span, report Report) []Action
- func (p *Pipeline) FixAll(ctx context.Context, view Store, targets []uri.URI, ...) (FixResult, error)
- func (p *Pipeline) FixesForFile(ctx context.Context, view Graph, file uri.URI, report Report) []Fix
- func (p *Pipeline) Run(ctx context.Context, view Graph, changed []uri.URI) (Report, error)
- func (p *Pipeline) WithAnalyzers(analyzers ...Analyzer) *Pipeline
- func (p *Pipeline) WithFixers(fs ...Fixer) *Pipeline
- func (p *Pipeline) WithProviders(ps ...ActionProvider) *Pipeline
- type Report
- type Resolved
- type Run
- type SemanticAnalysis
- type Severity
- type SkippedFix
- type Span
- type Store
- type UnusedIncludeCheck
Constants ¶
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.
const NonScalarMapKeyCheckName = "NonScalarMapKeyCheck"
NonScalarMapKeyCheckName is the configuration name of NonScalarMapKeyCheck.
Variables ¶
This section is empty.
Functions ¶
func BareName ¶
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 ¶
IncludeNameOf returns the include name of a file URI: the base name without extension. file:///base.thrift -> "base".
func IsBasicType ¶
IsBasicType reports whether t is a built-in base type.
func ParseIdent ¶
ParseIdent parses an identifier. identifier format:
- identifier
- 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 ¶
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 AddIncludeFixer ¶
type AddIncludeFixer struct{}
AddIncludeFixer offers the include that makes an undefined type resolve: the definition is searched across the workspace, so the fix works project-wide. It fixes only "undefined-type" diagnostics.
func (AddIncludeFixer) Fix ¶
func (f AddIncludeFixer) Fix(ctx context.Context, file File, d Diagnostic) []Fix
type Analyzer ¶
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).
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 ¶
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 CycleCheck ¶
type CycleCheck struct{}
CycleCheck reports include edges that close a cycle: the include X -> Y is reported when Y transitively includes X back. Cycles of any length are caught, including self-includes.
func (*CycleCheck) Name ¶
func (c *CycleCheck) Name() string
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 DuplicateCheck ¶
type DuplicateCheck struct{}
DuplicateCheck reports the duplicates the compiler rejects: a name defined twice in one scope — structs, unions, exceptions, enums, members, fields, function arguments, service functions, typedefs, constants — enum members whose resolved value collides, and map keys or set values that repeat in a constant. Duplicates are ambiguous on the wire and in generated code, so they are errors.
func (*DuplicateCheck) AnalyzeFile ¶
func (c *DuplicateCheck) AnalyzeFile(ctx context.Context, f File) ([]Diagnostic, error)
func (*DuplicateCheck) Name ¶
func (c *DuplicateCheck) Name() string
type Edit ¶
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 EnumValueCheck ¶
type EnumValueCheck struct{}
func (*EnumValueCheck) AnalyzeFile ¶
func (c *EnumValueCheck) AnalyzeFile(ctx context.Context, f File) ([]Diagnostic, error)
func (*EnumValueCheck) Name ¶
func (c *EnumValueCheck) Name() string
EnumValueCheck warns on enum members that lack an explicit value.
The compiler auto-increments implicit members: 0 for the first member, one greater than the preceding member's value otherwise. Their on-wire value therefore follows their position; inserting, removing, or reordering members silently changes serialized data.
type EnumValuesProvider ¶
type EnumValuesProvider struct{}
EnumValuesProvider offers the rewrite that appends an explicit value to every member of the enum containing the selection, mirroring the auto-incremented constants the compiler would assign. When a diagnostic overlaps the selection, the same action is also offered as its quickfix.
type FieldIDCheck ¶
type FieldIDCheck struct{}
func (*FieldIDCheck) AnalyzeFile ¶
func (c *FieldIDCheck) AnalyzeFile(ctx context.Context, f File) ([]Diagnostic, error)
func (*FieldIDCheck) Name ¶
func (c *FieldIDCheck) Name() string
FieldIDCheck checks struct, union, exception, function parameter, and throws field ids: they must be unique positive integers in [1, 32767].
type FieldQualifierProvider ¶
type FieldQualifierProvider struct{}
FieldQualifierProvider offers the "Make field required" and "Make field optional" actions for the fields whose declaration line the selection covers. Every covered field yields one action per qualifier it does not already carry: an unqualified field gets both, a required field gets "Make field optional", and vice versa. Union fields never offer "Make field required": unions have no required members.
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.
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 ¶
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
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 IncludeShadowCheck ¶
type IncludeShadowCheck struct{}
IncludeShadowCheck warns when one include path matches files under more than one include path. The nearest include path wins; the warning names the rest.
func (*IncludeShadowCheck) AnalyzeFile ¶
func (c *IncludeShadowCheck) AnalyzeFile(ctx context.Context, f File) ([]Diagnostic, error)
func (*IncludeShadowCheck) Name ¶
func (c *IncludeShadowCheck) Name() string
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 (*Index) FindInWorkspace ¶
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 ¶
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.
type NonScalarMapKeyCheck ¶ added in v0.2.0
type NonScalarMapKeyCheck struct{}
NonScalarMapKeyCheck reports map key types that are not scalar or enum types. Structs, unions, exceptions, and containers are not valid map keys in standard Thrift, while typedefs are followed to their underlying type.
func (*NonScalarMapKeyCheck) AnalyzeFile ¶ added in v0.2.0
func (c *NonScalarMapKeyCheck) AnalyzeFile(ctx context.Context, f File) ([]Diagnostic, error)
func (*NonScalarMapKeyCheck) Name ¶ added in v0.2.0
func (c *NonScalarMapKeyCheck) Name() string
type ParseCheck ¶
type ParseCheck struct{}
ParseCheck reports the lexer's and parser's errors and warnings for every changed file. It is a whole-run analyzer because it must see files whose AST is too broken for the other analyzers — they skip those, this one reports them.
func (*ParseCheck) Name ¶
func (p *ParseCheck) Name() string
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 DefaultPipeline ¶
DefaultPipeline composes the built-in analyzers with their fixers and action providers.
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
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 ¶
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
WithAnalyzers returns a copy of the pipeline with analyzers appended.
func (*Pipeline) WithFixers ¶
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.
type SemanticAnalysis ¶
type SemanticAnalysis struct{}
func (*SemanticAnalysis) AnalyzeFile ¶
func (s *SemanticAnalysis) AnalyzeFile(ctx context.Context, f File) ([]Diagnostic, error)
func (*SemanticAnalysis) Name ¶
func (s *SemanticAnalysis) Name() string
type SkippedFix ¶ added in v0.2.0
SkippedFix is a fix FixAll could not apply.
type Span ¶
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.
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.
type UnusedIncludeCheck ¶
type UnusedIncludeCheck struct{}
UnusedIncludeCheck reports an include no reference in the file resolves into. Unused includes bloat the compile and make the dependency graph look worse than it is.
func (*UnusedIncludeCheck) AnalyzeFile ¶
func (c *UnusedIncludeCheck) AnalyzeFile(ctx context.Context, f File) ([]Diagnostic, error)
func (*UnusedIncludeCheck) Name ¶
func (c *UnusedIncludeCheck) Name() string