3211 lines
108 KiB
Go
3211 lines
108 KiB
Go
package api
|
|
|
|
import (
|
|
"context"
|
|
"encoding/base64"
|
|
"fmt"
|
|
"slices"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
"sync/atomic"
|
|
|
|
"github.com/microsoft/typescript-go/internal/api/encoder"
|
|
"github.com/microsoft/typescript-go/internal/ast"
|
|
"github.com/microsoft/typescript-go/internal/astnav"
|
|
"github.com/microsoft/typescript-go/internal/checker"
|
|
"github.com/microsoft/typescript-go/internal/collections"
|
|
"github.com/microsoft/typescript-go/internal/compiler"
|
|
"github.com/microsoft/typescript-go/internal/core"
|
|
"github.com/microsoft/typescript-go/internal/json"
|
|
"github.com/microsoft/typescript-go/internal/ls"
|
|
"github.com/microsoft/typescript-go/internal/lsp/lsproto"
|
|
"github.com/microsoft/typescript-go/internal/nodebuilder"
|
|
"github.com/microsoft/typescript-go/internal/pprof"
|
|
"github.com/microsoft/typescript-go/internal/printer"
|
|
"github.com/microsoft/typescript-go/internal/project"
|
|
"github.com/microsoft/typescript-go/internal/tsoptions"
|
|
"github.com/microsoft/typescript-go/internal/tspath"
|
|
)
|
|
|
|
var sessionIDCounter atomic.Uint64
|
|
|
|
// snapshotData holds the per-snapshot state including the snapshot itself
|
|
// and symbol/type registries scoped to this snapshot.
|
|
// Multiple clients may hold references to the same snapshot via ref counting;
|
|
// the registries are cleaned up when refCount reaches zero.
|
|
type snapshotData struct {
|
|
snapshot *project.Snapshot
|
|
refCount int
|
|
|
|
// Symbol IDs come from ast.GetSymbolId, a global atomic counter, so the same
|
|
// *ast.Symbol pointer always has the same unique ID across all projects in the
|
|
// snapshot. Symbols are registered snapshot-wide to ensure identity semantics:
|
|
// querying the same symbol from two different projects returns the same handle.
|
|
symbolRegistry map[SymbolID]*ast.Symbol
|
|
symbolRegistryMu sync.RWMutex
|
|
|
|
// symbolCanonicalProjects records, for each registered symbol, the project it was
|
|
// first observed in. Because symbols are shared snapshot-wide (binder symbols are
|
|
// attached to source files, which can be shared across projects), lookups that need
|
|
// a project context (e.g. member/export ordering, node handle resolution) but don't
|
|
// receive one from the caller default to this canonical project. First-writer wins so
|
|
// the choice is stable. Guarded by symbolRegistryMu.
|
|
symbolCanonicalProjects map[SymbolID]ProjectID
|
|
|
|
projectRegistries map[ProjectID]*projectRegistryData
|
|
projectRegistriesMu sync.RWMutex
|
|
}
|
|
|
|
// projectRegistryData holds per-project type and signature registries.
|
|
// Types and signatures use per-checker sequential IDs, so the same local ID
|
|
// can appear in multiple projects. Separate maps per project prevent collisions
|
|
// and allow clean teardown when a project is removed.
|
|
type projectRegistryData struct {
|
|
typeRegistry map[TypeID]*checker.Type
|
|
typeRegistryMu sync.RWMutex
|
|
|
|
signatureRegistry map[SignatureID]*checker.Signature
|
|
signatureRegistryMu sync.RWMutex
|
|
}
|
|
|
|
// getProgram looks up a program from a project handle within this snapshot.
|
|
func (sd *snapshotData) getProgram(projectHandle ProjectID) (*compiler.Program, error) {
|
|
proj, err := sd.getProject(projectHandle)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
program := proj.GetProgram()
|
|
if program == nil {
|
|
return nil, fmt.Errorf("%w: project has no program", ErrClientError)
|
|
}
|
|
|
|
return program, nil
|
|
}
|
|
|
|
// getProject looks up a project from a project handle within this snapshot.
|
|
func (sd *snapshotData) getProject(projectHandle ProjectID) (*project.Project, error) {
|
|
projectName := parseProjectHandle(projectHandle)
|
|
proj := sd.snapshot.ProjectCollection.GetProjectByPath(projectName)
|
|
if proj == nil {
|
|
return nil, fmt.Errorf("%w: project %s not found", ErrClientError, projectName)
|
|
}
|
|
return proj, nil
|
|
}
|
|
|
|
// nodeHandleFrom creates an index-based node handle (index.kind.path), building a node index table
|
|
// for the file on-demand if needed.
|
|
func (sd *snapshotData) nodeHandleFrom(node *ast.Node) NodeHandle {
|
|
sourceFile := ast.GetSourceFileOfNode(node)
|
|
path := sourceFile.Path()
|
|
table := encoder.GetNodeIndexTable(sourceFile)
|
|
idx := table.GetIndex(node)
|
|
return NodeHandle(fmt.Sprintf("%d.%d.%s", idx, node.Kind, path))
|
|
}
|
|
|
|
// getOrCreateProjectRegistry returns the registry for the given project, creating it if needed.
|
|
func (sd *snapshotData) getOrCreateProjectRegistry(projectID ProjectID) *projectRegistryData {
|
|
if projectID == "" {
|
|
panic("getOrCreateProjectRegistry: empty project ID")
|
|
}
|
|
// Fast path: registry already exists — read lock only.
|
|
sd.projectRegistriesMu.RLock()
|
|
reg := sd.projectRegistries[projectID]
|
|
sd.projectRegistriesMu.RUnlock()
|
|
if reg != nil {
|
|
return reg
|
|
}
|
|
// Slow path: create under write lock.
|
|
sd.projectRegistriesMu.Lock()
|
|
defer sd.projectRegistriesMu.Unlock()
|
|
if sd.projectRegistries[projectID] == nil {
|
|
sd.projectRegistries[projectID] = &projectRegistryData{
|
|
typeRegistry: make(map[TypeID]*checker.Type),
|
|
signatureRegistry: make(map[SignatureID]*checker.Signature),
|
|
}
|
|
}
|
|
return sd.projectRegistries[projectID]
|
|
}
|
|
|
|
// newSymbolResponse registers a symbol in the snapshot's registry and returns the response.
|
|
// canonicalProject is the project the symbol was observed in and must be non-empty; it is recorded
|
|
// as the symbol's canonical project (first writer wins) and returned to the client so it can default
|
|
// project-scoped follow-up lookups (members/exports, node resolution) to it.
|
|
func (sd *snapshotData) newSymbolResponse(symbol *ast.Symbol, canonicalProject ProjectID) *SymbolResponse {
|
|
if symbol == nil {
|
|
return nil
|
|
}
|
|
|
|
id, project := sd.registerSymbol(symbol, canonicalProject)
|
|
resp := &SymbolResponse{
|
|
Id: id,
|
|
Project: project,
|
|
Name: ast.EscapeSymbolName(symbol.Name),
|
|
Flags: uint32(symbol.Flags),
|
|
CheckFlags: uint32(symbol.CheckFlags),
|
|
}
|
|
|
|
if len(symbol.Declarations) > 0 {
|
|
resp.Declarations = make([]NodeHandle, len(symbol.Declarations))
|
|
for i, decl := range symbol.Declarations {
|
|
resp.Declarations[i] = sd.nodeHandleFrom(decl)
|
|
}
|
|
}
|
|
|
|
if symbol.ValueDeclaration != nil {
|
|
resp.ValueDeclaration = sd.nodeHandleFrom(symbol.ValueDeclaration)
|
|
}
|
|
|
|
if symbol.Parent != nil {
|
|
resp.Parent = SymbolHandle(symbol.Parent)
|
|
}
|
|
|
|
if symbol.ExportSymbol != nil {
|
|
resp.ExportSymbol = SymbolHandle(symbol.ExportSymbol)
|
|
}
|
|
|
|
return resp
|
|
}
|
|
|
|
// registerSymbol registers a symbol in the snapshot's registry and returns its handle along with
|
|
// its canonical project. The canonical project is the project the symbol was first observed in
|
|
// (first writer wins for stability) and is always non-empty: every symbol handed to a client must
|
|
// carry a project so that project-scoped follow-up lookups (members/exports, parent, node
|
|
// resolution) have a default context. Callers must supply a non-empty project.
|
|
func (sd *snapshotData) registerSymbol(symbol *ast.Symbol, canonicalProject ProjectID) (SymbolID, ProjectID) {
|
|
if symbol == nil {
|
|
return 0, ""
|
|
}
|
|
if canonicalProject == "" {
|
|
panic("registerSymbol requires a non-empty canonical project")
|
|
}
|
|
id := SymbolHandle(symbol)
|
|
sd.symbolRegistryMu.Lock()
|
|
defer sd.symbolRegistryMu.Unlock()
|
|
existing := sd.symbolRegistry[id]
|
|
if existing != nil {
|
|
if existing != symbol {
|
|
panic("duplicate symbol")
|
|
}
|
|
} else {
|
|
sd.symbolRegistry[id] = symbol
|
|
}
|
|
project, ok := sd.symbolCanonicalProjects[id]
|
|
if !ok {
|
|
sd.symbolCanonicalProjects[id] = canonicalProject
|
|
project = canonicalProject
|
|
}
|
|
return id, project
|
|
}
|
|
|
|
// newTypeResponse registers a type in the project's registry and returns the response.
|
|
func (sd *snapshotData) newTypeResponse(projectID ProjectID, t *checker.Type) *TypeResponse {
|
|
if t == nil {
|
|
return nil
|
|
}
|
|
return newTypeResponse(t, sd.registerType(projectID, t))
|
|
}
|
|
|
|
func (sd *snapshotData) registerType(projectID ProjectID, t *checker.Type) TypeID {
|
|
if t == nil {
|
|
return 0
|
|
}
|
|
id := TypeHandle(t)
|
|
reg := sd.getOrCreateProjectRegistry(projectID)
|
|
reg.typeRegistryMu.Lock()
|
|
defer reg.typeRegistryMu.Unlock()
|
|
existing := reg.typeRegistry[id]
|
|
|
|
if existing != nil {
|
|
if existing != t {
|
|
panic("duplicate type")
|
|
}
|
|
return id
|
|
}
|
|
reg.typeRegistry[id] = t
|
|
return id
|
|
}
|
|
|
|
// resolveSymbolHandle resolves a symbol handle within the snapshot's registry.
|
|
func (sd *snapshotData) resolveSymbolHandle(handle SymbolID) (*ast.Symbol, error) {
|
|
if handle == 0 {
|
|
return nil, fmt.Errorf("%w: empty symbol handle", ErrClientError)
|
|
}
|
|
|
|
sd.symbolRegistryMu.RLock()
|
|
symbol, ok := sd.symbolRegistry[handle]
|
|
sd.symbolRegistryMu.RUnlock()
|
|
|
|
if !ok {
|
|
return nil, fmt.Errorf("%w: symbol handle %d not found in snapshot registry", ErrClientError, handle)
|
|
}
|
|
|
|
return symbol, nil
|
|
}
|
|
|
|
// resolveTypeHandle resolves a type handle within the project's registry.
|
|
func (sd *snapshotData) resolveTypeHandle(projectID ProjectID, handle TypeID) (*checker.Type, error) {
|
|
if handle == 0 {
|
|
return nil, fmt.Errorf("%w: empty type handle", ErrClientError)
|
|
}
|
|
if projectID == "" {
|
|
return nil, fmt.Errorf("%w: empty project ID for type handle %d", ErrClientError, handle)
|
|
}
|
|
|
|
sd.projectRegistriesMu.RLock()
|
|
reg := sd.projectRegistries[projectID]
|
|
sd.projectRegistriesMu.RUnlock()
|
|
|
|
if reg == nil {
|
|
return nil, fmt.Errorf("%w: type handle %d not found (no registry for project %s)", ErrClientError, handle, projectID)
|
|
}
|
|
|
|
reg.typeRegistryMu.RLock()
|
|
t, ok := reg.typeRegistry[handle]
|
|
reg.typeRegistryMu.RUnlock()
|
|
|
|
if !ok {
|
|
return nil, fmt.Errorf("%w: type handle %d not found in project registry", ErrClientError, handle)
|
|
}
|
|
|
|
return t, nil
|
|
}
|
|
|
|
// resolveSignatureHandle resolves a signature handle within the project's registry.
|
|
func (sd *snapshotData) resolveSignatureHandle(projectID ProjectID, handle SignatureID) (*checker.Signature, error) {
|
|
if handle == 0 {
|
|
return nil, fmt.Errorf("%w: empty signature handle", ErrClientError)
|
|
}
|
|
if projectID == "" {
|
|
return nil, fmt.Errorf("%w: empty project ID for signature handle %d", ErrClientError, handle)
|
|
}
|
|
|
|
sd.projectRegistriesMu.RLock()
|
|
reg := sd.projectRegistries[projectID]
|
|
sd.projectRegistriesMu.RUnlock()
|
|
|
|
if reg == nil {
|
|
return nil, fmt.Errorf("%w: signature handle %d not found (no registry for project %s)", ErrClientError, handle, projectID)
|
|
}
|
|
|
|
reg.signatureRegistryMu.RLock()
|
|
sig, ok := reg.signatureRegistry[handle]
|
|
reg.signatureRegistryMu.RUnlock()
|
|
|
|
if !ok {
|
|
return nil, fmt.Errorf("%w: signature handle %d not found in project registry", ErrClientError, handle)
|
|
}
|
|
|
|
return sig, nil
|
|
}
|
|
|
|
// newSignatureResponse registers a signature in the project's registry and returns the response.
|
|
func (sd *snapshotData) newSignatureResponse(projectID ProjectID, sig *checker.Signature) *SignatureResponse {
|
|
if sig == nil {
|
|
return nil
|
|
}
|
|
resp := &SignatureResponse{
|
|
Id: sd.registerSignature(projectID, sig),
|
|
Flags: uint32(sig.Flags()),
|
|
}
|
|
|
|
if sig.Declaration() != nil {
|
|
resp.Declaration = sd.nodeHandleFrom(sig.Declaration())
|
|
}
|
|
|
|
if len(sig.TypeParameters()) > 0 {
|
|
resp.TypeParameters = typeHandles(sig.TypeParameters())
|
|
}
|
|
|
|
if len(sig.Parameters()) > 0 {
|
|
resp.Parameters = symbolHandles(sig.Parameters())
|
|
}
|
|
|
|
if sig.ThisParameter() != nil {
|
|
resp.ThisParameter = SymbolHandle(sig.ThisParameter())
|
|
}
|
|
|
|
if sig.Target() != nil {
|
|
resp.Target = SignatureHandle(sig.Target())
|
|
}
|
|
|
|
return resp
|
|
}
|
|
|
|
func (sd *snapshotData) registerSignature(projectID ProjectID, sig *checker.Signature) SignatureID {
|
|
if sig == nil {
|
|
return 0
|
|
}
|
|
id := SignatureHandle(sig)
|
|
reg := sd.getOrCreateProjectRegistry(projectID)
|
|
reg.signatureRegistryMu.Lock()
|
|
defer reg.signatureRegistryMu.Unlock()
|
|
existing := reg.signatureRegistry[id]
|
|
|
|
if existing != nil {
|
|
if existing != sig {
|
|
panic("duplicate signature")
|
|
}
|
|
return id
|
|
}
|
|
reg.signatureRegistry[id] = sig
|
|
return id
|
|
}
|
|
|
|
// Session represents an API session that provides programmatic access
|
|
// to TypeScript language services through the LSP server.
|
|
// It implements the Handler interface to process incoming API requests.
|
|
// The session supports multiple active snapshots, each with their own
|
|
// symbol and type registries for maintaining object identity.
|
|
type Session struct {
|
|
id string
|
|
projectSession *project.Session
|
|
|
|
// This is set to true when using MessagePackProtocol.
|
|
useBinaryResponses bool
|
|
|
|
// snapshots maps snapshot handles to their data. Each snapshot has its own
|
|
// symbol/type registries.
|
|
//
|
|
// snapshotsMu guards the snapshots map and latestSnapshot. It is held only for
|
|
// short, map-bounded critical sections, never across slow work like a project
|
|
// snapshot update or checker queries. Read handlers (getSnapshotData and the
|
|
// language-service handlers built on it) take it for reading; handleRelease and
|
|
// the bookkeeping tail of handleUpdateSnapshot take it for writing. This is what
|
|
// lets queries against an existing snapshot run concurrently with the building of
|
|
// the next one.
|
|
snapshots map[SnapshotID]*snapshotData
|
|
snapshotsMu sync.RWMutex
|
|
|
|
// latestSnapshot tracks the most recently created snapshot, used as the diff base
|
|
// for the next update. Guarded by snapshotsMu.
|
|
latestSnapshot SnapshotID
|
|
|
|
// openProjects and openFiles track the projects and files this session
|
|
// currently holds open in the project session's API state. The session holds
|
|
// at most one ref per project/file (opens are idempotent), so it can release
|
|
// exactly those refs on Close and never send a close for a ref it doesn't hold.
|
|
// Guarded by updateMu.
|
|
openProjects collections.Set[tspath.Path]
|
|
openFiles collections.Set[tspath.Path]
|
|
|
|
// updateMu serializes the whole of handleUpdateSnapshot (and releaseOpenRefs)
|
|
// against other updates. Unlike snapshotsMu it is held across the slow
|
|
// projectSession.APIUpdate call, because building the request from
|
|
// openProjects/openFiles, applying it, committing the ref tracking, and advancing
|
|
// latestSnapshot must be one atomic step; otherwise concurrent updates could
|
|
// double-count refs or diff against a non-adjacent snapshot. Read handlers do NOT
|
|
// take this lock, so an in-flight update never blocks queries against existing
|
|
// snapshots. Lock ordering is updateMu -> snapshotsMu (never the reverse).
|
|
updateMu sync.Mutex
|
|
|
|
cpuProfiler pprof.CPUProfiler
|
|
}
|
|
|
|
// Ensure Session implements Handler
|
|
var _ Handler = (*Session)(nil)
|
|
|
|
// SessionOptions configures an API session.
|
|
type SessionOptions struct {
|
|
// UseBinaryResponses enables binary responses for msgpack protocol.
|
|
UseBinaryResponses bool
|
|
}
|
|
|
|
// NewSession creates a new API session with the given project session.
|
|
func NewSession(projectSession *project.Session, options *SessionOptions) *Session {
|
|
id := sessionIDCounter.Add(1)
|
|
s := &Session{
|
|
id: formatSessionID(id),
|
|
projectSession: projectSession,
|
|
snapshots: make(map[SnapshotID]*snapshotData),
|
|
}
|
|
if options != nil {
|
|
s.useBinaryResponses = options.UseBinaryResponses
|
|
}
|
|
return s
|
|
}
|
|
|
|
// ID returns the unique identifier for this session.
|
|
func (s *Session) ID() string {
|
|
return s.id
|
|
}
|
|
|
|
// ProjectSession returns the underlying project session.
|
|
func (s *Session) ProjectSession() *project.Session {
|
|
return s.projectSession
|
|
}
|
|
|
|
// snapshotHandle creates a snapshot handle from a snapshot's ID.
|
|
func snapshotHandle(snapshot *project.Snapshot) SnapshotID {
|
|
return SnapshotID(snapshot.ID())
|
|
}
|
|
|
|
// getSnapshotData looks up snapshot data by handle.
|
|
func (s *Session) getSnapshotData(handle SnapshotID) (*snapshotData, error) {
|
|
s.snapshotsMu.RLock()
|
|
sd, ok := s.snapshots[handle]
|
|
s.snapshotsMu.RUnlock()
|
|
if !ok {
|
|
return nil, fmt.Errorf("%w: snapshot %d not found", ErrClientError, handle)
|
|
}
|
|
return sd, nil
|
|
}
|
|
|
|
// checkerSetup holds the common context needed by handlers that require a type checker.
|
|
type checkerSetup struct {
|
|
sd *snapshotData
|
|
program *compiler.Program
|
|
checker *checker.Checker
|
|
done func()
|
|
projectID ProjectID
|
|
}
|
|
|
|
func (setup checkerSetup) newTypeResponse(t *checker.Type) *TypeResponse {
|
|
return setup.sd.newTypeResponse(setup.projectID, t)
|
|
}
|
|
|
|
func (setup checkerSetup) newSymbolResponse(sym *ast.Symbol) *SymbolResponse {
|
|
return setup.sd.newSymbolResponse(sym, setup.projectID)
|
|
}
|
|
|
|
func (setup checkerSetup) newSignatureResponse(sig *checker.Signature) *SignatureResponse {
|
|
return setup.sd.newSignatureResponse(setup.projectID, sig)
|
|
}
|
|
|
|
func (setup checkerSetup) resolveTypeHandle(id TypeID) (*checker.Type, error) {
|
|
return setup.sd.resolveTypeHandle(setup.projectID, id)
|
|
}
|
|
|
|
func (setup checkerSetup) resolveSymbolHandle(id SymbolID) (*ast.Symbol, error) {
|
|
return setup.sd.resolveSymbolHandle(id)
|
|
}
|
|
|
|
func (setup checkerSetup) resolveSignatureHandle(id SignatureID) (*checker.Signature, error) {
|
|
return setup.sd.resolveSignatureHandle(setup.projectID, id)
|
|
}
|
|
|
|
// setupChecker resolves snapshot, program, and type checker for a project.
|
|
// Callers must defer setup.done() to release the checker.
|
|
func (s *Session) setupChecker(ctx context.Context, snapshot SnapshotID, projectHandle ProjectID) (checkerSetup, error) {
|
|
sd, err := s.getSnapshotData(snapshot)
|
|
if err != nil {
|
|
return checkerSetup{}, err
|
|
}
|
|
|
|
program, err := sd.getProgram(projectHandle)
|
|
if err != nil {
|
|
return checkerSetup{}, err
|
|
}
|
|
|
|
c, done := program.GetTypeChecker(core.WithCheckerLifetime(ctx, core.CheckerLifetimeAPI))
|
|
return checkerSetup{
|
|
sd: sd,
|
|
program: program,
|
|
checker: c,
|
|
done: done,
|
|
projectID: projectHandle,
|
|
}, nil
|
|
}
|
|
|
|
// setupLanguageService creates a LanguageService for the given snapshot/project.
|
|
// Unlike setupChecker, this does NOT acquire a checker from the pool, so callers that
|
|
// only need an LS (and not a Checker) can avoid blocking on / holding a pooled checker.
|
|
//
|
|
// The LS acquires its own checker internally (keyed by the ctx's checker lifetime).
|
|
// If a handler returns symbol/type/signature handles the client may later re-query
|
|
// on the API checker (e.g. completion with IncludeSymbol -> GetTypeOfSymbol), wrap
|
|
// ctx with core.WithCheckerLifetime(ctx, core.CheckerLifetimeAPI) so those handles
|
|
// are produced on the persistent API checker and stay resolvable. Only safe when the
|
|
// LS operation acquires a checker exactly once; nested acquisitions (e.g. find-all-
|
|
// references) would deadlock on the single-slot persistent checker.
|
|
func (s *Session) setupLanguageService(sd *snapshotData, program *compiler.Program, projectHandle ProjectID, activeFile string) (*ls.LanguageService, error) {
|
|
projectName := parseProjectHandle(projectHandle)
|
|
proj := sd.snapshot.ProjectCollection.GetProjectByPath(projectName)
|
|
if proj == nil {
|
|
return nil, fmt.Errorf("%w: project %s not found", ErrClientError, projectName)
|
|
}
|
|
return ls.NewLanguageService(proj.ID(), program, sd.snapshot, activeFile), nil
|
|
}
|
|
|
|
// HandleRequest implements Handler.
|
|
func (s *Session) HandleRequest(ctx context.Context, method string, params json.Value) (any, error) {
|
|
// Handle simple methods that don't need param parsing
|
|
switch method {
|
|
case "echo":
|
|
// Return raw binary for msgpack protocol compatibility
|
|
if s.useBinaryResponses {
|
|
return RawBinary(params), nil
|
|
}
|
|
return params, nil
|
|
case "ping":
|
|
return "pong", nil
|
|
}
|
|
|
|
parsed, err := unmarshalPayload(method, params)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("%w: %w", ErrInvalidRequest, err)
|
|
}
|
|
|
|
switch method {
|
|
case string(MethodRelease):
|
|
return s.handleRelease(ctx, parsed.(*ReleaseParams))
|
|
case string(MethodInitialize):
|
|
return s.handleInitialize(ctx)
|
|
case string(MethodUpdateSnapshot):
|
|
return s.handleUpdateSnapshot(ctx, parsed.(*UpdateSnapshotParams))
|
|
case string(MethodParseConfigFile):
|
|
return s.handleParseConfigFile(ctx, parsed.(*ParseConfigFileParams))
|
|
case string(MethodGetDefaultProjectForFile):
|
|
return s.handleGetDefaultProjectForFile(ctx, parsed.(*GetDefaultProjectForFileParams))
|
|
case string(MethodGetSourceFile):
|
|
return s.handleGetSourceFile(ctx, parsed.(*GetSourceFileParams))
|
|
case string(MethodGetSourceFileNames):
|
|
return s.handleGetSourceFileNames(ctx, parsed.(*GetSourceFileNamesParams))
|
|
case string(MethodGetSourceFileMetadata):
|
|
return s.handleGetSourceFileMetadata(ctx, parsed.(*GetSourceFileParams))
|
|
case string(MethodGetSymbolAtPosition):
|
|
return s.handleGetSymbolAtPosition(ctx, parsed.(*GetSymbolAtPositionParams))
|
|
case string(MethodGetSymbolsAtPositions):
|
|
return s.handleGetSymbolsAtPositions(ctx, parsed.(*GetSymbolsAtPositionsParams))
|
|
case string(MethodGetSymbolAtLocation):
|
|
return s.handleGetSymbolAtLocation(ctx, parsed.(*GetSymbolAtLocationParams))
|
|
case string(MethodGetSymbolsAtLocations):
|
|
return s.handleGetSymbolsAtLocations(ctx, parsed.(*GetSymbolsAtLocationsParams))
|
|
case string(MethodGetTypeOfSymbol):
|
|
return s.handleGetTypeOfSymbol(ctx, parsed.(*GetTypeOfSymbolParams))
|
|
case string(MethodGetTypesOfSymbols):
|
|
return s.handleGetTypesOfSymbols(ctx, parsed.(*GetTypesOfSymbolsParams))
|
|
case string(MethodGetDeclaredTypeOfSymbol):
|
|
return s.handleGetDeclaredTypeOfSymbol(ctx, parsed.(*GetTypeOfSymbolParams))
|
|
case string(MethodResolveName):
|
|
return s.handleResolveName(ctx, parsed.(*ResolveNameParams))
|
|
case string(MethodGetSignaturesOfType):
|
|
return s.handleGetSignaturesOfType(ctx, parsed.(*GetSignaturesOfTypeParams))
|
|
case string(MethodGetResolvedSignature):
|
|
return s.handleGetResolvedSignature(ctx, parsed.(*GetResolvedSignatureParams))
|
|
case string(MethodGetTypeAtLocation):
|
|
return s.handleGetTypeAtLocation(ctx, parsed.(*GetTypeAtLocationParams))
|
|
case string(MethodGetTypeAtLocations):
|
|
return s.handleGetTypeAtLocations(ctx, parsed.(*GetTypeAtLocationsParams))
|
|
case string(MethodGetTypeAtPosition):
|
|
return s.handleGetTypeAtPosition(ctx, parsed.(*GetTypeAtPositionParams))
|
|
case string(MethodGetTypesAtPositions):
|
|
return s.handleGetTypesAtPositions(ctx, parsed.(*GetTypesAtPositionsParams))
|
|
case string(MethodGetParentOfSymbol):
|
|
return s.handleGetParentOfSymbol(ctx, parsed.(*GetSymbolPropertyParams))
|
|
case string(MethodGetMembersOfSymbol):
|
|
return s.handleGetMembersOfSymbol(ctx, parsed.(*GetSymbolPropertyParams))
|
|
case string(MethodGetExportsOfSymbol):
|
|
return s.handleGetExportsOfSymbol(ctx, parsed.(*GetSymbolPropertyParams))
|
|
case string(MethodGetExportSymbolOfSymbol):
|
|
return s.handleGetExportSymbolOfSymbol(ctx, parsed.(*GetSymbolPropertyParams))
|
|
case string(MethodGetSymbolOfType):
|
|
return s.handleGetSymbolOfType(ctx, parsed.(*GetTypePropertyParams))
|
|
case string(MethodGetTargetOfType):
|
|
return s.handleGetTargetOfType(ctx, parsed.(*GetTypePropertyParams))
|
|
case string(MethodGetFreshTypeOfType):
|
|
return s.handleGetFreshTypeOfType(ctx, parsed.(*GetTypePropertyParams))
|
|
case string(MethodGetRegularTypeOfType):
|
|
return s.handleGetRegularTypeOfType(ctx, parsed.(*GetTypePropertyParams))
|
|
case string(MethodGetTypesOfType):
|
|
return s.handleGetTypesOfType(ctx, parsed.(*GetTypePropertyParams))
|
|
case string(MethodGetTypeParametersOfType):
|
|
return s.handleGetTypeParametersOfType(ctx, parsed.(*GetTypePropertyParams))
|
|
case string(MethodGetOuterTypeParametersOfType):
|
|
return s.handleGetOuterTypeParametersOfType(ctx, parsed.(*GetTypePropertyParams))
|
|
case string(MethodGetLocalTypeParametersOfType):
|
|
return s.handleGetLocalTypeParametersOfType(ctx, parsed.(*GetTypePropertyParams))
|
|
case string(MethodGetAliasTypeArgumentsOfType):
|
|
return s.handleGetAliasTypeArgumentsOfType(ctx, parsed.(*GetTypePropertyParams))
|
|
case string(MethodGetAliasSymbolOfType):
|
|
return s.handleGetAliasSymbolOfType(ctx, parsed.(*GetTypePropertyParams))
|
|
case string(MethodGetObjectTypeOfType):
|
|
return s.handleGetObjectTypeOfType(ctx, parsed.(*GetTypePropertyParams))
|
|
case string(MethodGetIndexTypeOfType):
|
|
return s.handleGetIndexTypeOfType(ctx, parsed.(*GetTypePropertyParams))
|
|
case string(MethodGetCheckTypeOfType):
|
|
return s.handleGetCheckTypeOfType(ctx, parsed.(*GetTypePropertyParams))
|
|
case string(MethodGetExtendsTypeOfType):
|
|
return s.handleGetExtendsTypeOfType(ctx, parsed.(*GetTypePropertyParams))
|
|
case string(MethodGetBaseTypeOfType):
|
|
return s.handleGetBaseTypeOfType(ctx, parsed.(*GetTypePropertyParams))
|
|
case string(MethodGetConstraintOfType):
|
|
return s.handleGetConstraintOfType(ctx, parsed.(*GetTypePropertyParams))
|
|
case string(MethodGetTrueTypeOfConditionalType):
|
|
return s.handleGetTrueTypeOfConditionalType(ctx, parsed.(*GetTypePropertyParams))
|
|
case string(MethodGetFalseTypeOfConditionalType):
|
|
return s.handleGetFalseTypeOfConditionalType(ctx, parsed.(*GetTypePropertyParams))
|
|
case string(MethodGetTypeParametersOfSignature):
|
|
return s.handleGetTypeParametersOfSignature(ctx, parsed.(*GetSignaturePropertyParams))
|
|
case string(MethodGetParametersOfSignature):
|
|
return s.handleGetParametersOfSignature(ctx, parsed.(*GetSignaturePropertyParams))
|
|
case string(MethodGetThisParameterOfSignature):
|
|
return s.handleGetThisParameterOfSignature(ctx, parsed.(*GetSignaturePropertyParams))
|
|
case string(MethodGetTargetOfSignature):
|
|
return s.handleGetTargetOfSignature(ctx, parsed.(*GetSignaturePropertyParams))
|
|
case string(MethodGetContextualType):
|
|
return s.handleGetContextualType(ctx, parsed.(*GetContextualTypeParams))
|
|
case string(MethodGetBaseTypeOfLiteralType):
|
|
return s.handleGetBaseTypeOfLiteralType(ctx, parsed.(*GetBaseTypeOfLiteralTypeParams))
|
|
case string(MethodGetNonNullableType):
|
|
return s.handleGetNonNullableType(ctx, parsed.(*GetNonNullableTypeParams))
|
|
case string(MethodGetTypeFromTypeNode):
|
|
return s.handleGetTypeFromTypeNode(ctx, parsed.(*GetTypeFromTypeNodeParams))
|
|
case string(MethodGetWidenedType):
|
|
return s.handleGetWidenedType(ctx, parsed.(*GetWidenedTypeParams))
|
|
case string(MethodGetParameterType):
|
|
return s.handleGetParameterType(ctx, parsed.(*GetParameterTypeParams))
|
|
case string(MethodIsArrayLikeType):
|
|
return s.handleIsArrayLikeType(ctx, parsed.(*IsArrayLikeTypeParams))
|
|
case string(MethodIsTypeAssignableTo):
|
|
return s.handleIsTypeAssignableTo(ctx, parsed.(*IsTypeAssignableToParams))
|
|
case string(MethodGetShorthandAssignmentValueSymbol):
|
|
return s.handleGetShorthandAssignmentValueSymbol(ctx, parsed.(*GetTypeAtLocationParams))
|
|
case string(MethodGetTypeOfSymbolAtLocation):
|
|
return s.handleGetTypeOfSymbolAtLocation(ctx, parsed.(*GetTypeOfSymbolAtLocationParams))
|
|
case string(MethodTypeToTypeNode):
|
|
return s.handleTypeToTypeNode(ctx, parsed.(*TypeToTypeNodeParams))
|
|
case string(MethodSignatureToSignatureDeclaration):
|
|
return s.handleSignatureToSignatureDeclaration(ctx, parsed.(*SignatureToSignatureDeclarationParams))
|
|
case string(MethodTypeToString):
|
|
return s.handleTypeToString(ctx, parsed.(*TypeToTypeNodeParams))
|
|
case string(MethodPrintNode):
|
|
return s.handlePrintNode(ctx, parsed.(*PrintNodeParams))
|
|
case string(MethodIsContextSensitive):
|
|
return s.handleIsContextSensitive(ctx, parsed.(*GetContextualTypeParams))
|
|
case string(MethodGetReturnTypeOfSignature):
|
|
return s.handleGetReturnTypeOfSignature(ctx, parsed.(*CheckerSignatureParams))
|
|
case string(MethodGetRestTypeOfSignature):
|
|
return s.handleGetRestTypeOfSignature(ctx, parsed.(*CheckerSignatureParams))
|
|
case string(MethodGetTypePredicateOfSignature):
|
|
return s.handleGetTypePredicateOfSignature(ctx, parsed.(*CheckerSignatureParams))
|
|
case string(MethodGetBaseTypes):
|
|
return s.handleGetBaseTypes(ctx, parsed.(*CheckerTypeParams))
|
|
case string(MethodGetPropertiesOfType):
|
|
return s.handleGetPropertiesOfType(ctx, parsed.(*CheckerTypeParams))
|
|
case string(MethodGetApparentType):
|
|
return s.handleGetApparentType(ctx, parsed.(*CheckerTypeParams))
|
|
case string(MethodGetPropertyOfType):
|
|
return s.handleGetPropertyOfType(ctx, parsed.(*GetPropertyOfTypeParams))
|
|
case string(MethodGetIndexInfosOfType):
|
|
return s.handleGetIndexInfosOfType(ctx, parsed.(*CheckerTypeParams))
|
|
case string(MethodGetConstraintOfTypeParameter):
|
|
return s.handleGetConstraintOfTypeParameter(ctx, parsed.(*CheckerTypeParams))
|
|
case string(MethodGetBaseConstraintOfType):
|
|
return s.handleGetBaseConstraintOfType(ctx, parsed.(*CheckerTypeParams))
|
|
case string(MethodGetTypeArguments):
|
|
return s.handleGetTypeArguments(ctx, parsed.(*CheckerTypeParams))
|
|
case string(MethodGetConstantValue):
|
|
return s.handleGetConstantValue(ctx, parsed.(*CheckerNodeParams))
|
|
case string(MethodGetSignatureFromDeclaration):
|
|
return s.handleGetSignatureFromDeclaration(ctx, parsed.(*CheckerNodeParams))
|
|
case string(MethodGetExportSpecifierLocalTarget):
|
|
return s.handleGetExportSpecifierLocalTargetSymbol(ctx, parsed.(*CheckerNodeParams))
|
|
case string(MethodGetAliasedSymbol):
|
|
return s.handleGetAliasedSymbol(ctx, parsed.(*CheckerSymbolParams))
|
|
case string(MethodGetImmediateAliasedSymbol):
|
|
return s.handleGetImmediateAliasedSymbol(ctx, parsed.(*CheckerSymbolParams))
|
|
case string(MethodGetExportsOfModule):
|
|
return s.handleGetExportsOfModule(ctx, parsed.(*CheckerSymbolParams))
|
|
case string(MethodGetMemberInModuleExports):
|
|
return s.handleGetMemberInModuleExports(ctx, parsed.(*GetMemberInModuleExportsParams))
|
|
case string(MethodGetJSDocTags):
|
|
return s.handleGetJSDocTags(ctx, parsed.(*CheckerSymbolParams))
|
|
case string(MethodGetDocumentationComment):
|
|
return s.handleGetDocumentationComment(ctx, parsed.(*CheckerSymbolParams))
|
|
case string(MethodIsArrayType):
|
|
return s.handleIsArrayType(ctx, parsed.(*CheckerTypeParams))
|
|
case string(MethodIsTupleType):
|
|
return s.handleIsTupleType(ctx, parsed.(*CheckerTypeParams))
|
|
case string(MethodGetAnyType):
|
|
return s.handleGetIntrinsicType(ctx, parsed.(*GetIntrinsicTypeParams), (*checker.Checker).GetAnyType)
|
|
case string(MethodGetStringType):
|
|
return s.handleGetIntrinsicType(ctx, parsed.(*GetIntrinsicTypeParams), (*checker.Checker).GetStringType)
|
|
case string(MethodGetNumberType):
|
|
return s.handleGetIntrinsicType(ctx, parsed.(*GetIntrinsicTypeParams), (*checker.Checker).GetNumberType)
|
|
case string(MethodGetBooleanType):
|
|
return s.handleGetIntrinsicType(ctx, parsed.(*GetIntrinsicTypeParams), (*checker.Checker).GetBooleanType)
|
|
case string(MethodGetVoidType):
|
|
return s.handleGetIntrinsicType(ctx, parsed.(*GetIntrinsicTypeParams), (*checker.Checker).GetVoidType)
|
|
case string(MethodGetUndefinedType):
|
|
return s.handleGetIntrinsicType(ctx, parsed.(*GetIntrinsicTypeParams), (*checker.Checker).GetUndefinedType)
|
|
case string(MethodGetNullType):
|
|
return s.handleGetIntrinsicType(ctx, parsed.(*GetIntrinsicTypeParams), (*checker.Checker).GetNullType)
|
|
case string(MethodGetNeverType):
|
|
return s.handleGetIntrinsicType(ctx, parsed.(*GetIntrinsicTypeParams), (*checker.Checker).GetNeverType)
|
|
case string(MethodGetUnknownType):
|
|
return s.handleGetIntrinsicType(ctx, parsed.(*GetIntrinsicTypeParams), (*checker.Checker).GetUnknownType)
|
|
case string(MethodGetBigIntType):
|
|
return s.handleGetIntrinsicType(ctx, parsed.(*GetIntrinsicTypeParams), (*checker.Checker).GetBigIntType)
|
|
case string(MethodGetESSymbolType):
|
|
return s.handleGetIntrinsicType(ctx, parsed.(*GetIntrinsicTypeParams), (*checker.Checker).GetESSymbolType)
|
|
case string(MethodGetWellKnownSymbols):
|
|
return s.handleGetWellKnownSymbols(ctx, parsed.(*GetIntrinsicTypeParams))
|
|
case string(MethodGetWellKnownSignatures):
|
|
return s.handleGetWellKnownSignatures(ctx, parsed.(*GetIntrinsicTypeParams))
|
|
case string(MethodGetSyntacticDiagnostics):
|
|
return s.handleGetSyntacticDiagnostics(ctx, parsed.(*GetDiagnosticsParams))
|
|
case string(MethodGetBindDiagnostics):
|
|
return s.handleGetBindDiagnostics(ctx, parsed.(*GetDiagnosticsParams))
|
|
case string(MethodGetSemanticDiagnostics):
|
|
return s.handleGetSemanticDiagnostics(ctx, parsed.(*GetDiagnosticsParams))
|
|
case string(MethodGetSuggestionDiagnostics):
|
|
return s.handleGetSuggestionDiagnostics(ctx, parsed.(*GetDiagnosticsParams))
|
|
case string(MethodGetDeclarationDiagnostics):
|
|
return s.handleGetDeclarationDiagnostics(ctx, parsed.(*GetDiagnosticsParams))
|
|
case string(MethodGetProgramDiagnostics):
|
|
return s.handleGetProgramDiagnostics(ctx, parsed.(*GetProjectDiagnosticsParams))
|
|
case string(MethodGetGlobalDiagnostics):
|
|
return s.handleGetGlobalDiagnostics(ctx, parsed.(*GetProjectDiagnosticsParams))
|
|
case string(MethodGetConfigFileParsingDiagnostics):
|
|
return s.handleGetConfigFileParsingDiagnostics(ctx, parsed.(*GetProjectDiagnosticsParams))
|
|
case string(MethodStartCPUProfile):
|
|
return s.handleStartCPUProfile(ctx, parsed.(*ProfileParams))
|
|
case string(MethodStopCPUProfile):
|
|
return s.handleStopCPUProfile(ctx)
|
|
case string(MethodSaveHeapProfile):
|
|
return s.handleSaveHeapProfile(ctx, parsed.(*ProfileParams))
|
|
case string(MethodGetReferencesToSymbolInFile):
|
|
return s.handleGetReferencesToSymbolInFile(ctx, parsed.(*GetReferencesToSymbolInFileParams))
|
|
case string(MethodGetReferencedSymbolsForNode):
|
|
return s.handleGetReferencedSymbolsForNode(ctx, parsed.(*GetReferencedSymbolsForNodeParams))
|
|
case string(MethodGetSignatureUsages):
|
|
return s.handleGetSignatureUsages(ctx, parsed.(*GetSignatureUsagesParams))
|
|
case string(MethodGetCompletionsAtPosition):
|
|
return s.handleGetCompletionsAtPosition(ctx, parsed.(*GetCompletionsAtPositionParams))
|
|
default:
|
|
return nil, fmt.Errorf("unknown method: %s", method)
|
|
}
|
|
}
|
|
|
|
func (s *Session) handleStartCPUProfile(_ context.Context, params *ProfileParams) (any, error) {
|
|
if params == nil || params.Dir == "" {
|
|
return nil, fmt.Errorf("%w: dir is required", ErrClientError)
|
|
}
|
|
if err := s.cpuProfiler.StartCPUProfile(params.Dir); err != nil {
|
|
return nil, fmt.Errorf("%w: failed to start CPU profile: %w", ErrClientError, err)
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
func (s *Session) handleStopCPUProfile(_ context.Context) (*ProfileResult, error) {
|
|
filePath, err := s.cpuProfiler.StopCPUProfile()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("%w: failed to stop CPU profile: %w", ErrClientError, err)
|
|
}
|
|
return &ProfileResult{File: filePath}, nil
|
|
}
|
|
|
|
func (s *Session) handleSaveHeapProfile(_ context.Context, params *ProfileParams) (*ProfileResult, error) {
|
|
if params == nil || params.Dir == "" {
|
|
return nil, fmt.Errorf("%w: dir is required", ErrClientError)
|
|
}
|
|
filePath, err := pprof.SaveHeapProfile(params.Dir)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("%w: failed to save heap profile: %w", ErrClientError, err)
|
|
}
|
|
return &ProfileResult{File: filePath}, nil
|
|
}
|
|
|
|
// HandleNotification implements Handler.
|
|
func (s *Session) HandleNotification(ctx context.Context, method string, params json.Value) error {
|
|
// TODO: Implement notification handling
|
|
return nil
|
|
}
|
|
|
|
func (s *Session) handleInitialize(ctx context.Context) (*InitializeResponse, error) {
|
|
return &InitializeResponse{
|
|
UseCaseSensitiveFileNames: s.projectSession.FS().UseCaseSensitiveFileNames(),
|
|
CurrentDirectory: s.projectSession.GetCurrentDirectory(),
|
|
}, nil
|
|
}
|
|
|
|
// handleUpdateSnapshot creates a new snapshot, optionally opening or closing
|
|
// projects and files. With no args, it adopts the latest LSP state. Opens and
|
|
// closes are ref-counted per session: the session holds at most one ref per
|
|
// project/file, so repeated opens are idempotent and a close only releases a ref
|
|
// the session is actually holding.
|
|
func (s *Session) handleUpdateSnapshot(ctx context.Context, params *UpdateSnapshotParams) (*UpdateSnapshotResponse, error) {
|
|
// Fully serialize updates: snapshot creation, ref tracking, and the
|
|
// latestSnapshot/diff bookkeeping must be atomic with respect to other updates,
|
|
// otherwise concurrent updates could compute diffs against a non-adjacent
|
|
// snapshot or leave latestSnapshot pointing at a stale snapshot.
|
|
s.updateMu.Lock()
|
|
defer s.updateMu.Unlock()
|
|
|
|
fileChanges := s.toFileChangeSummary(params.FileChanges)
|
|
|
|
apiRequest := &project.APISnapshotRequest{}
|
|
|
|
// Open projects: only take a new ref for projects we aren't already holding open.
|
|
var openedProjects []tspath.Path
|
|
for _, p := range params.OpenProjects {
|
|
configFileName := p.ToAbsoluteFileName(s.projectSession.GetCurrentDirectory())
|
|
configPath := s.toPath(configFileName)
|
|
if s.openProjects.Has(configPath) {
|
|
continue
|
|
}
|
|
if apiRequest.OpenProjects == nil {
|
|
apiRequest.OpenProjects = collections.NewSetWithSizeHint[string](len(params.OpenProjects))
|
|
}
|
|
apiRequest.OpenProjects.Add(configFileName)
|
|
openedProjects = append(openedProjects, configPath)
|
|
}
|
|
|
|
// Close projects: only release a ref we currently hold.
|
|
var closedProjects []tspath.Path
|
|
for _, p := range params.CloseProjects {
|
|
configPath := s.toPath(p.ToAbsoluteFileName(s.projectSession.GetCurrentDirectory()))
|
|
if !s.openProjects.Has(configPath) {
|
|
continue
|
|
}
|
|
if apiRequest.CloseProjects == nil {
|
|
apiRequest.CloseProjects = collections.NewSetWithSizeHint[tspath.Path](len(params.CloseProjects))
|
|
}
|
|
apiRequest.CloseProjects.Add(configPath)
|
|
closedProjects = append(closedProjects, configPath)
|
|
}
|
|
|
|
// Open files: only open files we aren't already holding open, so each file is
|
|
// held by at most one API ref from this session.
|
|
var openedFiles []tspath.Path
|
|
for _, f := range params.OpenFiles {
|
|
uri := f.ToURI(s.projectSession.GetCurrentDirectory())
|
|
path := s.toPath(uri.FileName())
|
|
if s.openFiles.Has(path) {
|
|
continue
|
|
}
|
|
if apiRequest.OpenFiles == nil {
|
|
apiRequest.OpenFiles = collections.NewSetWithSizeHint[lsproto.DocumentUri](len(params.OpenFiles))
|
|
}
|
|
apiRequest.OpenFiles.Add(uri)
|
|
openedFiles = append(openedFiles, path)
|
|
}
|
|
|
|
// Close files: only release a ref we currently hold.
|
|
var closedFiles []tspath.Path
|
|
for _, f := range params.CloseFiles {
|
|
path := s.toPath(f.ToURI(s.projectSession.GetCurrentDirectory()).FileName())
|
|
if !s.openFiles.Has(path) {
|
|
continue
|
|
}
|
|
if apiRequest.CloseFiles == nil {
|
|
apiRequest.CloseFiles = collections.NewSetWithSizeHint[tspath.Path](len(params.CloseFiles))
|
|
}
|
|
apiRequest.CloseFiles.Add(path)
|
|
closedFiles = append(closedFiles, path)
|
|
}
|
|
|
|
// Even when nothing is opened or closed, APIUpdate ensures all projects and
|
|
// files opened by the API are up to date. For an API connected to an LSP server,
|
|
// this brings the API state up to date with the LSP state and ensures projects
|
|
// the API cares about are ready to be queried.
|
|
snapshot, err := s.projectSession.APIUpdate(ctx, fileChanges, apiRequest)
|
|
if err != nil {
|
|
// APIUpdate returns a ref'd snapshot even on error; release it.
|
|
snapshot.Deref(s.projectSession)
|
|
return nil, fmt.Errorf("%w: failed to update snapshot: %w", ErrClientError, err)
|
|
}
|
|
|
|
// Commit ref tracking now that the update succeeded.
|
|
for _, configPath := range openedProjects {
|
|
s.openProjects.Add(configPath)
|
|
}
|
|
for _, configPath := range closedProjects {
|
|
s.openProjects.Delete(configPath)
|
|
}
|
|
for _, path := range openedFiles {
|
|
s.openFiles.Add(path)
|
|
}
|
|
for _, path := range closedFiles {
|
|
s.openFiles.Delete(path)
|
|
}
|
|
|
|
// Create or ref-count snapshot data, then atomically read the previous latest
|
|
// snapshot (the diff base) and advance latestSnapshot to the new handle.
|
|
// If the same snapshot ID is returned (no changes), we increment the ref count
|
|
// so each client-side Snapshot can be disposed independently.
|
|
handle := snapshotHandle(snapshot)
|
|
s.snapshotsMu.Lock()
|
|
sd, exists := s.snapshots[handle]
|
|
if exists {
|
|
// Same snapshot already stored — release the caller's ref since
|
|
// the stored snapshot already has one, and bump the API refcount.
|
|
snapshot.Deref(s.projectSession)
|
|
sd.refCount++
|
|
} else {
|
|
sd = &snapshotData{
|
|
snapshot: snapshot,
|
|
refCount: 1,
|
|
symbolRegistry: make(map[SymbolID]*ast.Symbol),
|
|
symbolCanonicalProjects: make(map[SymbolID]ProjectID),
|
|
projectRegistries: make(map[ProjectID]*projectRegistryData),
|
|
}
|
|
s.snapshots[handle] = sd
|
|
}
|
|
prevSD := s.snapshots[s.latestSnapshot]
|
|
s.latestSnapshot = handle
|
|
s.snapshotsMu.Unlock()
|
|
|
|
// Build projects list
|
|
projects := snapshot.ProjectCollection.Projects()
|
|
projectResponses := make([]*ProjectResponse, 0, len(projects))
|
|
for _, proj := range projects {
|
|
if proj.CommandLine == nil {
|
|
continue
|
|
}
|
|
projectResponses = append(projectResponses, NewProjectResponse(proj))
|
|
}
|
|
|
|
// Compute changes from the previous latest snapshot
|
|
var changes *SnapshotChanges
|
|
if prevSD != nil {
|
|
changes = computeSnapshotChanges(prevSD.snapshot, snapshot)
|
|
}
|
|
|
|
return &UpdateSnapshotResponse{
|
|
Snapshot: handle,
|
|
Projects: projectResponses,
|
|
Changes: changes,
|
|
}, nil
|
|
}
|
|
|
|
// handleRelease decrements the ref count for a snapshot.
|
|
// The snapshot and its registries are only cleaned up when the ref count reaches zero.
|
|
func (s *Session) handleRelease(ctx context.Context, params *ReleaseParams) (any, error) {
|
|
if params == nil || params.Snapshot == 0 {
|
|
return nil, fmt.Errorf("%w: empty handle", ErrClientError)
|
|
}
|
|
|
|
s.snapshotsMu.Lock()
|
|
sd := s.snapshots[params.Snapshot]
|
|
if sd == nil {
|
|
s.snapshotsMu.Unlock()
|
|
return nil, fmt.Errorf("%w: snapshot %d not found", ErrClientError, params.Snapshot)
|
|
}
|
|
sd.refCount--
|
|
if sd.refCount <= 0 {
|
|
delete(s.snapshots, params.Snapshot)
|
|
// Release the API session's ref on the project snapshot.
|
|
sd.snapshot.Deref(s.projectSession)
|
|
}
|
|
s.snapshotsMu.Unlock()
|
|
return true, nil
|
|
}
|
|
|
|
// handleGetDefaultProjectForFile returns the default project for a given file,
|
|
// or nil if no project currently contains the file.
|
|
func (s *Session) handleGetDefaultProjectForFile(ctx context.Context, params *GetDefaultProjectForFileParams) (*ProjectResponse, error) {
|
|
sd, err := s.getSnapshotData(params.Snapshot)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
uri := params.File.ToURI(s.projectSession.GetCurrentDirectory())
|
|
proj := sd.snapshot.GetDefaultProject(uri)
|
|
if proj == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
return NewProjectResponse(proj), nil
|
|
}
|
|
|
|
// handleParseConfigFile parses a tsconfig.json file and returns its contents.
|
|
func (s *Session) handleParseConfigFile(ctx context.Context, params *ParseConfigFileParams) (*ConfigFileResponse, error) {
|
|
configFileName := params.File.ToAbsoluteFileName(s.projectSession.GetCurrentDirectory())
|
|
configFileContent, ok := s.projectSession.FS().ReadFile(configFileName)
|
|
if !ok {
|
|
return nil, fmt.Errorf("%w: could not read file %q", ErrClientError, configFileName)
|
|
}
|
|
|
|
configDir := tspath.GetDirectoryPath(configFileName)
|
|
tsConfigSourceFile := tsoptions.NewTsconfigSourceFileFromFilePath(
|
|
configFileName,
|
|
s.toPath(configFileName),
|
|
configFileContent,
|
|
)
|
|
parsedCommandLine := tsoptions.ParseJsonSourceFileConfigFileContent(
|
|
tsConfigSourceFile,
|
|
s.projectSession,
|
|
configDir,
|
|
nil, /*existingOptions*/
|
|
nil, /*existingOptionsRaw*/
|
|
configFileName,
|
|
nil, /*resolutionStack*/
|
|
nil, /*extraFileExtensions*/
|
|
nil, /*extendedConfigCache*/
|
|
)
|
|
|
|
return &ConfigFileResponse{
|
|
FileNames: parsedCommandLine.FileNames(),
|
|
Options: parsedCommandLine.CompilerOptions(),
|
|
}, nil
|
|
}
|
|
|
|
// handleGetSourceFile returns a source file from a project within a snapshot.
|
|
func (s *Session) handleGetSourceFile(ctx context.Context, params *GetSourceFileParams) (any, error) {
|
|
sd, err := s.getSnapshotData(params.Snapshot)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
program, err := sd.getProgram(params.Project)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
sourceFile := program.GetSourceFile(params.File.ToFileName())
|
|
if sourceFile == nil {
|
|
if s.useBinaryResponses {
|
|
return RawBinary(nil), nil
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
// Encode the full source file.
|
|
data, _, err := encoder.EncodeSourceFile(sourceFile)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to encode source file: %w", err)
|
|
}
|
|
|
|
// Return raw binary for msgpack protocol, or base64 for JSON
|
|
if s.useBinaryResponses {
|
|
return RawBinary(data), nil
|
|
}
|
|
return &SourceFileResponse{
|
|
Data: base64.StdEncoding.EncodeToString(data),
|
|
}, nil
|
|
}
|
|
|
|
// handleGetSourceFileNames returns file names of all source files in a project.
|
|
func (s *Session) handleGetSourceFileNames(ctx context.Context, params *GetSourceFileNamesParams) ([]string, error) {
|
|
sd, err := s.getSnapshotData(params.Snapshot)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
program, err := sd.getProgram(params.Project)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
sourceFiles := program.GetSourceFiles()
|
|
result := make([]string, len(sourceFiles))
|
|
for i, sourceFile := range sourceFiles {
|
|
result[i] = sourceFile.FileName()
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
// handleGetSourceFileMetadata returns program-stored metadata for a single source file.
|
|
// The client fetches this lazily per file and caches it.
|
|
func (s *Session) handleGetSourceFileMetadata(ctx context.Context, params *GetSourceFileParams) (*SourceFileMetadata, error) {
|
|
sd, err := s.getSnapshotData(params.Snapshot)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
program, err := sd.getProgram(params.Project)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
sourceFile := program.GetSourceFile(params.File.ToFileName())
|
|
if sourceFile == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
metaData := program.GetSourceFileMetaData(sourceFile.Path())
|
|
return &SourceFileMetadata{
|
|
IsDefaultLibrary: program.IsSourceFileDefaultLibrary(sourceFile.Path()),
|
|
IsFromExternalLibrary: program.IsSourceFileFromExternalLibrary(sourceFile),
|
|
PackageJsonType: metaData.PackageJsonType,
|
|
PackageJsonDirectory: metaData.PackageJsonDirectory,
|
|
ImpliedNodeFormat: metaData.ImpliedNodeFormat,
|
|
}, nil
|
|
}
|
|
|
|
// handleGetSymbolAtPosition returns the symbol at a position in a file.
|
|
func (s *Session) handleGetSymbolAtPosition(ctx context.Context, params *GetSymbolAtPositionParams) (*SymbolResponse, error) {
|
|
setup, err := s.setupChecker(ctx, params.Snapshot, params.Project)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer setup.done()
|
|
|
|
sourceFile := setup.program.GetSourceFile(params.File.ToFileName())
|
|
if sourceFile == nil {
|
|
return nil, fmt.Errorf("%w: source file not found: %v", ErrClientError, params.File)
|
|
}
|
|
|
|
positionMap := sourceFile.GetPositionMap()
|
|
node := astnav.GetTouchingPropertyName(sourceFile, positionMap.UTF16ToUTF8(int(params.Position)))
|
|
if node == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
symbol := setup.checker.GetSymbolAtLocation(node)
|
|
if symbol == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
return setup.newSymbolResponse(symbol), nil
|
|
}
|
|
|
|
// handleGetSymbolsAtPositions returns symbols at multiple positions in a file.
|
|
func (s *Session) handleGetSymbolsAtPositions(ctx context.Context, params *GetSymbolsAtPositionsParams) ([]*SymbolResponse, error) {
|
|
setup, err := s.setupChecker(ctx, params.Snapshot, params.Project)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer setup.done()
|
|
|
|
sourceFile := setup.program.GetSourceFile(params.File.ToFileName())
|
|
if sourceFile == nil {
|
|
return nil, fmt.Errorf("%w: source file not found: %v", ErrClientError, params.File)
|
|
}
|
|
|
|
positionMap := sourceFile.GetPositionMap()
|
|
results := make([]*SymbolResponse, len(params.Positions))
|
|
for i, pos := range params.Positions {
|
|
node := astnav.GetTouchingPropertyName(sourceFile, positionMap.UTF16ToUTF8(int(pos)))
|
|
if node == nil {
|
|
continue
|
|
}
|
|
symbol := setup.checker.GetSymbolAtLocation(node)
|
|
if symbol != nil {
|
|
results[i] = setup.newSymbolResponse(symbol)
|
|
}
|
|
}
|
|
|
|
return results, nil
|
|
}
|
|
|
|
// handleGetSymbolAtLocation returns the symbol at a node location.
|
|
func (s *Session) handleGetSymbolAtLocation(ctx context.Context, params *GetSymbolAtLocationParams) (*SymbolResponse, error) {
|
|
setup, err := s.setupChecker(ctx, params.Snapshot, params.Project)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer setup.done()
|
|
|
|
node, err := setup.sd.resolveNodeHandle(setup.program, params.Location)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if node == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
symbol := setup.checker.GetSymbolAtLocation(node)
|
|
if symbol == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
return setup.newSymbolResponse(symbol), nil
|
|
}
|
|
|
|
// handleGetSymbolsAtLocations returns symbols at multiple node locations.
|
|
func (s *Session) handleGetSymbolsAtLocations(ctx context.Context, params *GetSymbolsAtLocationsParams) ([]*SymbolResponse, error) {
|
|
setup, err := s.setupChecker(ctx, params.Snapshot, params.Project)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer setup.done()
|
|
|
|
results := make([]*SymbolResponse, len(params.Locations))
|
|
for i, loc := range params.Locations {
|
|
node, err := setup.sd.resolveNodeHandle(setup.program, loc)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if node == nil {
|
|
continue
|
|
}
|
|
symbol := setup.checker.GetSymbolAtLocation(node)
|
|
if symbol != nil {
|
|
results[i] = setup.newSymbolResponse(symbol)
|
|
}
|
|
}
|
|
|
|
return results, nil
|
|
}
|
|
|
|
// handleGetTypeOfSymbol returns the type of a symbol.
|
|
func (s *Session) handleGetTypeOfSymbol(ctx context.Context, params *GetTypeOfSymbolParams) (*TypeResponse, error) {
|
|
setup, err := s.setupChecker(ctx, params.Snapshot, params.Project)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer setup.done()
|
|
|
|
symbol, err := setup.resolveSymbolHandle(params.Symbol)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return setup.newTypeResponse(setup.checker.GetTypeOfSymbol(symbol)), nil
|
|
}
|
|
|
|
// handleGetTypesOfSymbols returns the types of multiple symbols.
|
|
func (s *Session) handleGetTypesOfSymbols(ctx context.Context, params *GetTypesOfSymbolsParams) ([]*TypeResponse, error) {
|
|
setup, err := s.setupChecker(ctx, params.Snapshot, params.Project)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer setup.done()
|
|
|
|
results := make([]*TypeResponse, len(params.Symbols))
|
|
for i, symHandle := range params.Symbols {
|
|
symbol, err := setup.resolveSymbolHandle(symHandle)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// resolveSymbolHandle errors on an unresolvable handle and GetTypeOfSymbol
|
|
// never returns nil, so every element resolves to a type (error type at worst).
|
|
results[i] = setup.newTypeResponse(setup.checker.GetTypeOfSymbol(symbol))
|
|
}
|
|
|
|
return results, nil
|
|
}
|
|
|
|
// handleGetDeclaredTypeOfSymbol returns the declared type of a symbol (e.g. the type alias body for type alias symbols).
|
|
func (s *Session) handleGetDeclaredTypeOfSymbol(ctx context.Context, params *GetTypeOfSymbolParams) (*TypeResponse, error) {
|
|
setup, err := s.setupChecker(ctx, params.Snapshot, params.Project)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer setup.done()
|
|
|
|
symbol, err := setup.resolveSymbolHandle(params.Symbol)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return setup.newTypeResponse(setup.checker.GetDeclaredTypeOfSymbol(symbol)), nil
|
|
}
|
|
|
|
// handleResolveName resolves a name to a symbol at a given location.
|
|
func (s *Session) handleResolveName(ctx context.Context, params *ResolveNameParams) (*SymbolResponse, error) {
|
|
setup, err := s.setupChecker(ctx, params.Snapshot, params.Project)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer setup.done()
|
|
|
|
// Resolve location node - either from node handle or from fileName+position
|
|
var location *ast.Node
|
|
if params.Location != "" {
|
|
location, err = setup.sd.resolveNodeHandle(setup.program, params.Location)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
} else if params.File != nil && params.Position != nil {
|
|
sourceFile := setup.program.GetSourceFile(params.File.ToFileName())
|
|
if sourceFile == nil {
|
|
return nil, fmt.Errorf("%w: source file not found: %v", ErrClientError, *params.File)
|
|
}
|
|
location = astnav.GetTouchingPropertyName(sourceFile, sourceFile.GetPositionMap().UTF16ToUTF8(int(*params.Position)))
|
|
}
|
|
|
|
symbol := setup.checker.ResolveName(params.Name, location, ast.SymbolFlags(params.Meaning), params.ExcludeGlobals)
|
|
if symbol == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
return setup.newSymbolResponse(symbol), nil
|
|
}
|
|
|
|
// handleGetSignaturesOfType returns the call or construct signatures of a type.
|
|
func (s *Session) handleGetSignaturesOfType(ctx context.Context, params *GetSignaturesOfTypeParams) ([]*SignatureResponse, error) {
|
|
setup, err := s.setupChecker(ctx, params.Snapshot, params.Project)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer setup.done()
|
|
|
|
t, err := setup.resolveTypeHandle(params.Type)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
sigs := setup.checker.GetSignaturesOfType(t, checker.SignatureKind(params.Kind))
|
|
results := make([]*SignatureResponse, len(sigs))
|
|
for i, sig := range sigs {
|
|
results[i] = setup.newSignatureResponse(sig)
|
|
}
|
|
|
|
return results, nil
|
|
}
|
|
|
|
// handleGetResolvedSignature returns the resolved signature of a call-like expression.
|
|
func (s *Session) handleGetResolvedSignature(ctx context.Context, params *GetResolvedSignatureParams) (*SignatureResponse, error) {
|
|
setup, err := s.setupChecker(ctx, params.Snapshot, params.Project)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer setup.done()
|
|
|
|
node, err := setup.sd.resolveNodeHandle(setup.program, params.Location)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return setup.newSignatureResponse(setup.checker.GetResolvedSignature(node)), nil
|
|
}
|
|
|
|
// handleGetTypeAtLocation returns the type at a node location.
|
|
func (s *Session) handleGetTypeAtLocation(ctx context.Context, params *GetTypeAtLocationParams) (*TypeResponse, error) {
|
|
setup, err := s.setupChecker(ctx, params.Snapshot, params.Project)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer setup.done()
|
|
|
|
node, err := setup.sd.resolveNodeHandle(setup.program, params.Location)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return setup.newTypeResponse(setup.checker.GetTypeAtLocation(node)), nil
|
|
}
|
|
|
|
// handleGetTypeAtLocations returns types at multiple node locations.
|
|
func (s *Session) handleGetTypeAtLocations(ctx context.Context, params *GetTypeAtLocationsParams) ([]*TypeResponse, error) {
|
|
setup, err := s.setupChecker(ctx, params.Snapshot, params.Project)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer setup.done()
|
|
|
|
results := make([]*TypeResponse, len(params.Locations))
|
|
for i, loc := range params.Locations {
|
|
node, err := setup.sd.resolveNodeHandle(setup.program, loc)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// resolveNodeHandle errors on an unresolvable handle and GetTypeAtLocation
|
|
// never returns nil, so every element resolves to a type (error type at worst).
|
|
results[i] = setup.newTypeResponse(setup.checker.GetTypeAtLocation(node))
|
|
}
|
|
|
|
return results, nil
|
|
}
|
|
|
|
// handleGetTypeAtPosition returns the type at a position in a file.
|
|
func (s *Session) handleGetTypeAtPosition(ctx context.Context, params *GetTypeAtPositionParams) (*TypeResponse, error) {
|
|
setup, err := s.setupChecker(ctx, params.Snapshot, params.Project)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer setup.done()
|
|
|
|
sourceFile := setup.program.GetSourceFile(params.File.ToFileName())
|
|
if sourceFile == nil {
|
|
return nil, fmt.Errorf("%w: source file not found: %v", ErrClientError, params.File)
|
|
}
|
|
|
|
positionMap := sourceFile.GetPositionMap()
|
|
node := astnav.GetTouchingPropertyName(sourceFile, positionMap.UTF16ToUTF8(int(params.Position)))
|
|
if node == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
t := setup.checker.GetTypeAtLocation(node)
|
|
if t == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
return setup.newTypeResponse(t), nil
|
|
}
|
|
|
|
// handleGetTypesAtPositions returns types at multiple positions in a file.
|
|
func (s *Session) handleGetTypesAtPositions(ctx context.Context, params *GetTypesAtPositionsParams) ([]*TypeResponse, error) {
|
|
setup, err := s.setupChecker(ctx, params.Snapshot, params.Project)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer setup.done()
|
|
|
|
sourceFile := setup.program.GetSourceFile(params.File.ToFileName())
|
|
if sourceFile == nil {
|
|
return nil, fmt.Errorf("%w: source file not found: %v", ErrClientError, params.File)
|
|
}
|
|
|
|
positionMap := sourceFile.GetPositionMap()
|
|
results := make([]*TypeResponse, len(params.Positions))
|
|
for i, pos := range params.Positions {
|
|
node := astnav.GetTouchingPropertyName(sourceFile, positionMap.UTF16ToUTF8(int(pos)))
|
|
if node == nil {
|
|
continue
|
|
}
|
|
t := setup.checker.GetTypeAtLocation(node)
|
|
if t != nil {
|
|
results[i] = setup.newTypeResponse(t)
|
|
}
|
|
}
|
|
|
|
return results, nil
|
|
}
|
|
|
|
func (s *Session) handleGetParentOfSymbol(_ context.Context, params *GetSymbolPropertyParams) (*SymbolResponse, error) {
|
|
return s.resolveSymbolPropertyOfSymbol(params, func(sym *ast.Symbol) *ast.Symbol { return sym.Parent })
|
|
}
|
|
|
|
func (s *Session) handleGetMembersOfSymbol(ctx context.Context, params *GetSymbolPropertyParams) ([]*SymbolResponse, error) {
|
|
return s.resolveSymbolTablePropertyOfSymbol(ctx, params, func(symbol *ast.Symbol) ast.SymbolTable {
|
|
return symbol.Members
|
|
})
|
|
}
|
|
|
|
func (s *Session) handleGetExportsOfSymbol(ctx context.Context, params *GetSymbolPropertyParams) ([]*SymbolResponse, error) {
|
|
return s.resolveSymbolTablePropertyOfSymbol(ctx, params, func(symbol *ast.Symbol) ast.SymbolTable {
|
|
return symbol.Exports
|
|
})
|
|
}
|
|
|
|
func (s *Session) handleGetExportSymbolOfSymbol(_ context.Context, params *GetSymbolPropertyParams) (*SymbolResponse, error) {
|
|
return s.resolveSymbolPropertyOfSymbol(params, func(sym *ast.Symbol) *ast.Symbol { return sym.ExportSymbol })
|
|
}
|
|
|
|
func (s *Session) handleGetSymbolOfType(_ context.Context, params *GetTypePropertyParams) (*SymbolResponse, error) {
|
|
return s.resolveSymbolPropertyOfType(params, (*checker.Type).Symbol)
|
|
}
|
|
|
|
func (s *Session) handleGetTargetOfType(_ context.Context, params *GetTypePropertyParams) (*TypeResponse, error) {
|
|
return s.resolveTypePropertyOfType(params, (*checker.Type).Target)
|
|
}
|
|
|
|
func (s *Session) handleGetFreshTypeOfType(_ context.Context, params *GetTypePropertyParams) (*TypeResponse, error) {
|
|
return s.resolveTypePropertyOfType(params, func(t *checker.Type) *checker.Type { return t.AsLiteralType().FreshType() })
|
|
}
|
|
|
|
func (s *Session) handleGetRegularTypeOfType(_ context.Context, params *GetTypePropertyParams) (*TypeResponse, error) {
|
|
return s.resolveTypePropertyOfType(params, func(t *checker.Type) *checker.Type { return t.AsLiteralType().RegularType() })
|
|
}
|
|
|
|
func (s *Session) handleGetTypesOfType(_ context.Context, params *GetTypePropertyParams) ([]*TypeResponse, error) {
|
|
return s.resolveTypeArrayPropertyOfType(params, (*checker.Type).Types)
|
|
}
|
|
|
|
func (s *Session) handleGetTypeParametersOfType(_ context.Context, params *GetTypePropertyParams) ([]*TypeResponse, error) {
|
|
return s.resolveTypeArrayPropertyOfType(params, func(t *checker.Type) []*checker.Type { return t.AsInterfaceType().TypeParameters() })
|
|
}
|
|
|
|
func (s *Session) handleGetOuterTypeParametersOfType(_ context.Context, params *GetTypePropertyParams) ([]*TypeResponse, error) {
|
|
return s.resolveTypeArrayPropertyOfType(params, func(t *checker.Type) []*checker.Type { return t.AsInterfaceType().OuterTypeParameters() })
|
|
}
|
|
|
|
func (s *Session) handleGetLocalTypeParametersOfType(_ context.Context, params *GetTypePropertyParams) ([]*TypeResponse, error) {
|
|
return s.resolveTypeArrayPropertyOfType(params, func(t *checker.Type) []*checker.Type { return t.AsInterfaceType().LocalTypeParameters() })
|
|
}
|
|
|
|
func (s *Session) handleGetAliasTypeArgumentsOfType(_ context.Context, params *GetTypePropertyParams) ([]*TypeResponse, error) {
|
|
return s.resolveTypeArrayPropertyOfType(params, func(t *checker.Type) []*checker.Type {
|
|
if t.Alias() == nil {
|
|
return nil
|
|
}
|
|
return t.Alias().TypeArguments()
|
|
})
|
|
}
|
|
|
|
func (s *Session) handleGetAliasSymbolOfType(_ context.Context, params *GetTypePropertyParams) (*SymbolResponse, error) {
|
|
return s.resolveSymbolPropertyOfType(params, func(t *checker.Type) *ast.Symbol {
|
|
if t.Alias() == nil {
|
|
return nil
|
|
}
|
|
return t.Alias().Symbol()
|
|
})
|
|
}
|
|
|
|
func (s *Session) handleGetObjectTypeOfType(_ context.Context, params *GetTypePropertyParams) (*TypeResponse, error) {
|
|
return s.resolveTypePropertyOfType(params, func(t *checker.Type) *checker.Type { return t.AsIndexedAccessType().ObjectType() })
|
|
}
|
|
|
|
func (s *Session) handleGetIndexTypeOfType(_ context.Context, params *GetTypePropertyParams) (*TypeResponse, error) {
|
|
return s.resolveTypePropertyOfType(params, func(t *checker.Type) *checker.Type { return t.AsIndexedAccessType().IndexType() })
|
|
}
|
|
|
|
func (s *Session) handleGetCheckTypeOfType(_ context.Context, params *GetTypePropertyParams) (*TypeResponse, error) {
|
|
return s.resolveTypePropertyOfType(params, func(t *checker.Type) *checker.Type { return t.AsConditionalType().CheckType() })
|
|
}
|
|
|
|
func (s *Session) handleGetExtendsTypeOfType(_ context.Context, params *GetTypePropertyParams) (*TypeResponse, error) {
|
|
return s.resolveTypePropertyOfType(params, func(t *checker.Type) *checker.Type { return t.AsConditionalType().ExtendsType() })
|
|
}
|
|
|
|
func (s *Session) handleGetBaseTypeOfType(_ context.Context, params *GetTypePropertyParams) (*TypeResponse, error) {
|
|
return s.resolveTypePropertyOfType(params, func(t *checker.Type) *checker.Type { return t.AsSubstitutionType().BaseType() })
|
|
}
|
|
|
|
func (s *Session) handleGetConstraintOfType(_ context.Context, params *GetTypePropertyParams) (*TypeResponse, error) {
|
|
return s.resolveTypePropertyOfType(params, func(t *checker.Type) *checker.Type { return t.AsSubstitutionType().SubstConstraint() })
|
|
}
|
|
|
|
func (s *Session) handleGetTypeParametersOfSignature(_ context.Context, params *GetSignaturePropertyParams) ([]*TypeResponse, error) {
|
|
return s.resolveTypeArrayPropertyOfSignature(params, (*checker.Signature).TypeParameters)
|
|
}
|
|
|
|
func (s *Session) handleGetParametersOfSignature(_ context.Context, params *GetSignaturePropertyParams) ([]*SymbolResponse, error) {
|
|
return s.resolveSymbolArrayPropertyOfSignature(params, (*checker.Signature).Parameters)
|
|
}
|
|
|
|
func (s *Session) handleGetThisParameterOfSignature(_ context.Context, params *GetSignaturePropertyParams) (*SymbolResponse, error) {
|
|
return s.resolveSymbolPropertyOfSignature(params, (*checker.Signature).ThisParameter)
|
|
}
|
|
|
|
func (s *Session) handleGetTargetOfSignature(_ context.Context, params *GetSignaturePropertyParams) (*SignatureResponse, error) {
|
|
return s.resolveSignaturePropertyOfSignature(params, (*checker.Signature).Target)
|
|
}
|
|
|
|
// resolveTypePropertyOfType resolves a type property of type `Type` and returns a type response.
|
|
func (s *Session) resolveTypePropertyOfType(params *GetTypePropertyParams, getter func(*checker.Type) *checker.Type) (*TypeResponse, error) {
|
|
sd, err := s.getSnapshotData(params.Snapshot)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
t, err := sd.resolveTypeHandle(params.Project, params.Type)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
result := getter(t)
|
|
if result == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
return sd.newTypeResponse(params.Project, result), nil
|
|
}
|
|
|
|
// resolveTypeArrayPropertyOfType resolves a type property of an array of types and returns an array of type responses.
|
|
func (s *Session) resolveTypeArrayPropertyOfType(params *GetTypePropertyParams, getter func(*checker.Type) []*checker.Type) ([]*TypeResponse, error) {
|
|
sd, err := s.getSnapshotData(params.Snapshot)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
t, err := sd.resolveTypeHandle(params.Project, params.Type)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
types := getter(t)
|
|
if len(types) == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
results := make([]*TypeResponse, len(types))
|
|
for i, sub := range types {
|
|
results[i] = sd.newTypeResponse(params.Project, sub)
|
|
}
|
|
return results, nil
|
|
}
|
|
|
|
// resolveSymbolPropertyOfType resolves a type property of type `Symbol` and returns a symbol response.
|
|
func (s *Session) resolveSymbolPropertyOfType(params *GetTypePropertyParams, getter func(*checker.Type) *ast.Symbol) (*SymbolResponse, error) {
|
|
sd, err := s.getSnapshotData(params.Snapshot)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
t, err := sd.resolveTypeHandle(params.Project, params.Type)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
result := getter(t)
|
|
if result == nil {
|
|
return nil, nil
|
|
}
|
|
return sd.newSymbolResponse(result, params.Project), nil
|
|
}
|
|
|
|
// resolveSymbolTablePropertyOfSymbol resolves a symbol property of type `Symbol` and returns a symbol response.
|
|
func (s *Session) resolveSymbolPropertyOfSymbol(params *GetSymbolPropertyParams, getter func(*ast.Symbol) *ast.Symbol) (*SymbolResponse, error) {
|
|
sd, err := s.getSnapshotData(params.Snapshot)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
symbol, err := sd.resolveSymbolHandle(params.Symbol)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
result := getter(symbol)
|
|
if result == nil {
|
|
return nil, nil
|
|
}
|
|
return sd.newSymbolResponse(result, params.Project), nil
|
|
}
|
|
|
|
// resolveSymbolTablePropertyOfSymbol resolves a symbol property of type `SymbolTable` and returns an array of symbol responses.
|
|
// Results are sorted using the checker's canonical symbol ordering so that API consumers receive
|
|
// a stable, deterministic order instead of Go's randomized map iteration order.
|
|
func (s *Session) resolveSymbolTablePropertyOfSymbol(ctx context.Context, params *GetSymbolPropertyParams, getter func(*ast.Symbol) ast.SymbolTable) ([]*SymbolResponse, error) {
|
|
sd, err := s.getSnapshotData(params.Snapshot)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
symbol, err := sd.resolveSymbolHandle(params.Symbol)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
symbolTable := getter(symbol)
|
|
if len(symbolTable) == 0 {
|
|
return nil, nil
|
|
}
|
|
if len(symbolTable) == 1 {
|
|
for _, sub := range symbolTable {
|
|
return []*SymbolResponse{sd.newSymbolResponse(sub, params.Project)}, nil
|
|
}
|
|
}
|
|
|
|
// More than one symbol, need a checker to sort
|
|
setup, err := s.setupChecker(ctx, params.Snapshot, params.Project)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer setup.done()
|
|
|
|
symbols := make([]*ast.Symbol, 0, len(symbolTable))
|
|
for _, sub := range symbolTable {
|
|
symbols = append(symbols, sub)
|
|
}
|
|
slices.SortFunc(symbols, setup.checker.CompareSymbols)
|
|
|
|
results := make([]*SymbolResponse, len(symbols))
|
|
for i, sub := range symbols {
|
|
results[i] = setup.newSymbolResponse(sub)
|
|
}
|
|
return results, nil
|
|
}
|
|
|
|
// resolveSymbolArrayPropertyOfSignature resolves a signature property of an array of symbols and returns an array of symbol responses.
|
|
func (s *Session) resolveSymbolArrayPropertyOfSignature(params *GetSignaturePropertyParams, getter func(*checker.Signature) []*ast.Symbol) ([]*SymbolResponse, error) {
|
|
sd, err := s.getSnapshotData(params.Snapshot)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
sig, err := sd.resolveSignatureHandle(params.Project, params.Signature)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
symbols := getter(sig)
|
|
if len(symbols) == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
results := make([]*SymbolResponse, len(symbols))
|
|
for i, sym := range symbols {
|
|
results[i] = sd.newSymbolResponse(sym, params.Project)
|
|
}
|
|
return results, nil
|
|
}
|
|
|
|
// resolveSymbolPropertyOfSignature resolves a signature property of type `Symbol` and returns a symbol response.
|
|
func (s *Session) resolveSymbolPropertyOfSignature(params *GetSignaturePropertyParams, getter func(*checker.Signature) *ast.Symbol) (*SymbolResponse, error) {
|
|
sd, err := s.getSnapshotData(params.Snapshot)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
sig, err := sd.resolveSignatureHandle(params.Project, params.Signature)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
result := getter(sig)
|
|
if result == nil {
|
|
return nil, nil
|
|
}
|
|
return sd.newSymbolResponse(result, params.Project), nil
|
|
}
|
|
|
|
func (s *Session) resolveTypeArrayPropertyOfSignature(params *GetSignaturePropertyParams, getter func(signature *checker.Signature) []*checker.Type) ([]*TypeResponse, error) {
|
|
sd, err := s.getSnapshotData(params.Snapshot)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
sig, err := sd.resolveSignatureHandle(params.Project, params.Signature)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
types := getter(sig)
|
|
if len(types) == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
results := make([]*TypeResponse, len(types))
|
|
for i, sub := range types {
|
|
results[i] = sd.newTypeResponse(params.Project, sub)
|
|
}
|
|
return results, nil
|
|
}
|
|
|
|
func (s *Session) resolveSignaturePropertyOfSignature(params *GetSignaturePropertyParams, getter func(*checker.Signature) *checker.Signature) (*SignatureResponse, error) {
|
|
sd, err := s.getSnapshotData(params.Snapshot)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
sig, err := sd.resolveSignatureHandle(params.Project, params.Signature)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
result := getter(sig)
|
|
if result == nil {
|
|
return nil, nil
|
|
}
|
|
return sd.newSignatureResponse(params.Project, result), nil
|
|
}
|
|
|
|
// handleGetContextualType returns the contextual type for a node.
|
|
func (s *Session) handleGetContextualType(ctx context.Context, params *GetContextualTypeParams) (*TypeResponse, error) {
|
|
setup, err := s.setupChecker(ctx, params.Snapshot, params.Project)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer setup.done()
|
|
|
|
node, err := setup.sd.resolveNodeHandle(setup.program, params.Location)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if node == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
t := setup.checker.GetContextualType(node, checker.ContextFlagsNone)
|
|
if t == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
return setup.newTypeResponse(t), nil
|
|
}
|
|
|
|
// handleGetBaseTypeOfLiteralType returns the base type of a literal type (e.g. number for 42).
|
|
func (s *Session) handleGetBaseTypeOfLiteralType(ctx context.Context, params *GetBaseTypeOfLiteralTypeParams) (*TypeResponse, error) {
|
|
setup, err := s.setupChecker(ctx, params.Snapshot, params.Project)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer setup.done()
|
|
|
|
t, err := setup.resolveTypeHandle(params.Type)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return setup.newTypeResponse(setup.checker.GetBaseTypeOfLiteralType(t)), nil
|
|
}
|
|
|
|
// handleGetNonNullableType returns the type with null and undefined removed.
|
|
func (s *Session) handleGetNonNullableType(ctx context.Context, params *GetNonNullableTypeParams) (*TypeResponse, error) {
|
|
setup, err := s.setupChecker(ctx, params.Snapshot, params.Project)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer setup.done()
|
|
|
|
t, err := setup.resolveTypeHandle(params.Type)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return setup.newTypeResponse(setup.checker.GetNonNullableType(t)), nil
|
|
}
|
|
|
|
// handleGetTypeFromTypeNode returns the type for a type node.
|
|
func (s *Session) handleGetTypeFromTypeNode(ctx context.Context, params *GetTypeFromTypeNodeParams) (*TypeResponse, error) {
|
|
setup, err := s.setupChecker(ctx, params.Snapshot, params.Project)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer setup.done()
|
|
|
|
node, err := setup.sd.resolveNodeHandle(setup.program, params.Location)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return setup.newTypeResponse(setup.checker.GetTypeFromTypeNode(node)), nil
|
|
}
|
|
|
|
// handleGetWidenedType returns the widened type.
|
|
func (s *Session) handleGetWidenedType(ctx context.Context, params *GetWidenedTypeParams) (*TypeResponse, error) {
|
|
setup, err := s.setupChecker(ctx, params.Snapshot, params.Project)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer setup.done()
|
|
|
|
t, err := setup.resolveTypeHandle(params.Type)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return setup.newTypeResponse(setup.checker.GetWidenedType(t)), nil
|
|
}
|
|
|
|
// handleGetParameterType returns the type of a parameter at a given index in a signature.
|
|
func (s *Session) handleGetParameterType(ctx context.Context, params *GetParameterTypeParams) (*TypeResponse, error) {
|
|
setup, err := s.setupChecker(ctx, params.Snapshot, params.Project)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer setup.done()
|
|
|
|
sig, err := setup.resolveSignatureHandle(params.Signature)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if params.Index < 0 {
|
|
return nil, fmt.Errorf("%w: invalid parameter index", ErrClientError)
|
|
}
|
|
|
|
return setup.newTypeResponse(setup.checker.GetTypeAtPosition(sig, int(params.Index))), nil
|
|
}
|
|
|
|
// handleIsArrayLikeType returns whether a type is array-like.
|
|
func (s *Session) handleIsArrayLikeType(ctx context.Context, params *IsArrayLikeTypeParams) (bool, error) {
|
|
setup, err := s.setupChecker(ctx, params.Snapshot, params.Project)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
defer setup.done()
|
|
|
|
t, err := setup.resolveTypeHandle(params.Type)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
return setup.checker.IsArrayLikeType(t), nil
|
|
}
|
|
|
|
// handleIsTypeAssignableTo returns whether source is assignable to target.
|
|
func (s *Session) handleIsTypeAssignableTo(ctx context.Context, params *IsTypeAssignableToParams) (bool, error) {
|
|
setup, err := s.setupChecker(ctx, params.Snapshot, params.Project)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
defer setup.done()
|
|
|
|
source, err := setup.resolveTypeHandle(params.Source)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
target, err := setup.resolveTypeHandle(params.Target)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
return setup.checker.IsTypeAssignableTo(source, target), nil
|
|
}
|
|
|
|
// handleGetShorthandAssignmentValueSymbol returns the value symbol of a shorthand property assignment.
|
|
func (s *Session) handleGetShorthandAssignmentValueSymbol(ctx context.Context, params *GetTypeAtLocationParams) (*SymbolResponse, error) {
|
|
setup, err := s.setupChecker(ctx, params.Snapshot, params.Project)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer setup.done()
|
|
|
|
node, err := setup.sd.resolveNodeHandle(setup.program, params.Location)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if node == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
symbol := setup.checker.GetShorthandAssignmentValueSymbol(node)
|
|
if symbol == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
return setup.newSymbolResponse(symbol), nil
|
|
}
|
|
|
|
// handleGetTypeOfSymbolAtLocation returns the narrowed type of a symbol at a specific location.
|
|
func (s *Session) handleGetTypeOfSymbolAtLocation(ctx context.Context, params *GetTypeOfSymbolAtLocationParams) (*TypeResponse, error) {
|
|
setup, err := s.setupChecker(ctx, params.Snapshot, params.Project)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer setup.done()
|
|
|
|
symbol, err := setup.resolveSymbolHandle(params.Symbol)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
node, err := setup.sd.resolveNodeHandle(setup.program, params.Location)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return setup.newTypeResponse(setup.checker.GetTypeOfSymbolAtLocation(symbol, node)), nil
|
|
}
|
|
|
|
// handleTypeToTypeNode converts a Type to a TypeNode AST and returns it as binary-encoded data.
|
|
func (s *Session) handleTypeToTypeNode(ctx context.Context, params *TypeToTypeNodeParams) (any, error) {
|
|
setup, err := s.setupChecker(ctx, params.Snapshot, params.Project)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer setup.done()
|
|
|
|
t, err := setup.resolveTypeHandle(params.Type)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var enclosingDeclaration *ast.Node
|
|
if params.Location != "" {
|
|
enclosingDeclaration, err = setup.sd.resolveNodeHandle(setup.program, params.Location)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
typeNode := setup.checker.TypeToTypeNode(t, enclosingDeclaration, nodebuilder.Flags(params.Flags), nil)
|
|
if typeNode == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
data, _, err := encoder.EncodeNode(typeNode.AsNode(), nil)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to encode type node: %w", err)
|
|
}
|
|
|
|
if s.useBinaryResponses {
|
|
return RawBinary(data), nil
|
|
}
|
|
return &SourceFileResponse{
|
|
Data: base64.StdEncoding.EncodeToString(data),
|
|
}, nil
|
|
}
|
|
|
|
func (s *Session) handleSignatureToSignatureDeclaration(ctx context.Context, params *SignatureToSignatureDeclarationParams) (any, error) {
|
|
setup, err := s.setupChecker(ctx, params.Snapshot, params.Project)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer setup.done()
|
|
|
|
sig, err := setup.resolveSignatureHandle(params.Signature)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var enclosingDeclaration *ast.Node
|
|
if params.Location != "" {
|
|
enclosingDeclaration, err = setup.sd.resolveNodeHandle(setup.program, params.Location)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
node := setup.checker.SignatureToSignatureDeclaration(sig, ast.Kind(params.Kind), enclosingDeclaration, nodebuilder.Flags(params.Flags))
|
|
if node == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
data, _, err := encoder.EncodeNode(node.AsNode(), nil)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to encode signature declaration: %w", err)
|
|
}
|
|
|
|
if s.useBinaryResponses {
|
|
return RawBinary(data), nil
|
|
}
|
|
return &SourceFileResponse{
|
|
Data: base64.StdEncoding.EncodeToString(data),
|
|
}, nil
|
|
}
|
|
|
|
// handleTypeToString converts a Type to its string representation.
|
|
func (s *Session) handleTypeToString(ctx context.Context, params *TypeToTypeNodeParams) (any, error) {
|
|
setup, err := s.setupChecker(ctx, params.Snapshot, params.Project)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer setup.done()
|
|
|
|
t, err := setup.resolveTypeHandle(params.Type)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var enclosingDeclaration *ast.Node
|
|
if params.Location != "" {
|
|
enclosingDeclaration, err = setup.sd.resolveNodeHandle(setup.program, params.Location)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
if params.Flags != 0 {
|
|
return setup.checker.TypeToStringEx(t, enclosingDeclaration, checker.TypeFormatFlags(params.Flags), nil), nil
|
|
}
|
|
return setup.checker.TypeToStringEx(t, enclosingDeclaration, checker.TypeFormatFlagsAllowUniqueESSymbolType|checker.TypeFormatFlagsUseAliasDefinedOutsideCurrentScope, nil), nil
|
|
}
|
|
|
|
// handlePrintNode decodes a binary-encoded AST node and prints it to text.
|
|
func (s *Session) handlePrintNode(_ context.Context, params *PrintNodeParams) (string, error) {
|
|
data, err := base64.StdEncoding.DecodeString(params.Data)
|
|
if err != nil {
|
|
return "", fmt.Errorf("%w: invalid base64 data: %w", ErrClientError, err)
|
|
}
|
|
|
|
node, err := encoder.DecodeNodes(data)
|
|
if err != nil {
|
|
return "", fmt.Errorf("%w: failed to decode AST: %w", ErrClientError, err)
|
|
}
|
|
|
|
p := printer.NewPrinter(printer.PrinterOptions{
|
|
PreserveSourceNewlines: params.PreserveSourceNewlines,
|
|
NeverAsciiEscape: params.NeverAsciiEscape,
|
|
TerminateUnterminatedLiterals: params.TerminateUnterminatedLiterals,
|
|
}, printer.PrintHandlers{}, nil)
|
|
return p.Emit(node, nil), nil
|
|
}
|
|
|
|
// handleGetIntrinsicType returns an intrinsic type (any, string, number, etc.).
|
|
func (s *Session) handleGetIntrinsicType(ctx context.Context, params *GetIntrinsicTypeParams, getter func(*checker.Checker) *checker.Type) (*TypeResponse, error) {
|
|
setup, err := s.setupChecker(ctx, params.Snapshot, params.Project)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer setup.done()
|
|
|
|
t := getter(setup.checker)
|
|
if t == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
return setup.newTypeResponse(t), nil
|
|
}
|
|
|
|
// handleGetWellKnownSymbols returns the handle ids of the per-checker singleton
|
|
// symbols (unknown, undefined, arguments) so the client can identify them by id.
|
|
func (s *Session) handleGetWellKnownSymbols(ctx context.Context, params *GetIntrinsicTypeParams) (*WellKnownSymbolsResponse, error) {
|
|
setup, err := s.setupChecker(ctx, params.Snapshot, params.Project)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer setup.done()
|
|
|
|
unknown, _ := setup.sd.registerSymbol(setup.checker.GetUnknownSymbol(), setup.projectID)
|
|
undefined, _ := setup.sd.registerSymbol(setup.checker.GetUndefinedSymbol(), setup.projectID)
|
|
arguments, _ := setup.sd.registerSymbol(setup.checker.GetArgumentsSymbol(), setup.projectID)
|
|
return &WellKnownSymbolsResponse{
|
|
Unknown: unknown,
|
|
Undefined: undefined,
|
|
Arguments: arguments,
|
|
}, nil
|
|
}
|
|
|
|
// handleGetWellKnownSignatures returns the handle id of the per-checker unknown
|
|
// signature so the client can identify it by id.
|
|
func (s *Session) handleGetWellKnownSignatures(ctx context.Context, params *GetIntrinsicTypeParams) (*WellKnownSignaturesResponse, error) {
|
|
setup, err := s.setupChecker(ctx, params.Snapshot, params.Project)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer setup.done()
|
|
|
|
return &WellKnownSignaturesResponse{
|
|
Unknown: setup.sd.registerSignature(setup.projectID, setup.checker.GetUnknownSignature()),
|
|
}, nil
|
|
}
|
|
|
|
// handleIsContextSensitive returns whether a node is context-sensitive.
|
|
func (s *Session) handleIsContextSensitive(ctx context.Context, params *GetContextualTypeParams) (bool, error) {
|
|
setup, err := s.setupChecker(ctx, params.Snapshot, params.Project)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
defer setup.done()
|
|
|
|
node, err := setup.sd.resolveNodeHandle(setup.program, params.Location)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
if node == nil {
|
|
return false, nil
|
|
}
|
|
|
|
return setup.checker.IsContextSensitive(node), nil
|
|
}
|
|
|
|
// handleGetReturnTypeOfSignature returns the return type of a signature.
|
|
func (s *Session) handleGetReturnTypeOfSignature(ctx context.Context, params *CheckerSignatureParams) (*TypeResponse, error) {
|
|
setup, err := s.setupChecker(ctx, params.Snapshot, params.Project)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer setup.done()
|
|
|
|
sig, err := setup.resolveSignatureHandle(params.Signature)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return setup.newTypeResponse(setup.checker.GetReturnTypeOfSignature(sig)), nil
|
|
}
|
|
|
|
// handleGetRestTypeOfSignature returns the rest type of a signature.
|
|
func (s *Session) handleGetRestTypeOfSignature(ctx context.Context, params *CheckerSignatureParams) (*TypeResponse, error) {
|
|
setup, err := s.setupChecker(ctx, params.Snapshot, params.Project)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer setup.done()
|
|
|
|
sig, err := setup.resolveSignatureHandle(params.Signature)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return setup.newTypeResponse(setup.checker.GetRestTypeOfSignature(sig)), nil
|
|
}
|
|
|
|
// handleGetTypePredicateOfSignature returns the type predicate of a signature.
|
|
func (s *Session) handleGetTypePredicateOfSignature(ctx context.Context, params *CheckerSignatureParams) (*TypePredicateResponse, error) {
|
|
setup, err := s.setupChecker(ctx, params.Snapshot, params.Project)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer setup.done()
|
|
|
|
sig, err := setup.resolveSignatureHandle(params.Signature)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
pred := setup.checker.GetTypePredicateOfSignature(sig)
|
|
if pred == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
resp := &TypePredicateResponse{
|
|
Kind: int32(pred.Kind()),
|
|
ParameterIndex: pred.ParameterIndex(),
|
|
ParameterName: pred.ParameterName(),
|
|
}
|
|
if pred.Type() != nil {
|
|
resp.Type = setup.newTypeResponse(pred.Type())
|
|
}
|
|
|
|
return resp, nil
|
|
}
|
|
|
|
// handleIsArrayType returns whether a type is Array<T> or ReadonlyArray<T>.
|
|
func (s *Session) handleIsArrayType(ctx context.Context, params *CheckerTypeParams) (bool, error) {
|
|
setup, err := s.setupChecker(ctx, params.Snapshot, params.Project)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
defer setup.done()
|
|
|
|
t, err := setup.resolveTypeHandle(params.Type)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
return setup.checker.IsArrayType(t), nil
|
|
}
|
|
|
|
// handleIsTupleType returns whether a type is a tuple type.
|
|
func (s *Session) handleIsTupleType(ctx context.Context, params *CheckerTypeParams) (bool, error) {
|
|
setup, err := s.setupChecker(ctx, params.Snapshot, params.Project)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
defer setup.done()
|
|
|
|
t, err := setup.resolveTypeHandle(params.Type)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
return checker.IsTupleType(t), nil
|
|
}
|
|
|
|
// handleGetBaseTypes returns the base types of an interface/class type.
|
|
func (s *Session) handleGetBaseTypes(ctx context.Context, params *CheckerTypeParams) ([]*TypeResponse, error) {
|
|
setup, err := s.setupChecker(ctx, params.Snapshot, params.Project)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer setup.done()
|
|
|
|
t, err := setup.resolveTypeHandle(params.Type)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
baseTypes := setup.checker.GetBaseTypes(t)
|
|
if len(baseTypes) == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
results := make([]*TypeResponse, len(baseTypes))
|
|
for i, bt := range baseTypes {
|
|
results[i] = setup.newTypeResponse(bt)
|
|
}
|
|
|
|
return results, nil
|
|
}
|
|
|
|
// handleGetPropertiesOfType returns the properties of a type.
|
|
func (s *Session) handleGetPropertiesOfType(ctx context.Context, params *CheckerTypeParams) ([]*SymbolResponse, error) {
|
|
setup, err := s.setupChecker(ctx, params.Snapshot, params.Project)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer setup.done()
|
|
|
|
t, err := setup.resolveTypeHandle(params.Type)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
props := setup.checker.GetPropertiesOfType(t)
|
|
if len(props) == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
results := make([]*SymbolResponse, len(props))
|
|
for i, prop := range props {
|
|
results[i] = setup.newSymbolResponse(prop)
|
|
}
|
|
|
|
return results, nil
|
|
}
|
|
|
|
// handleGetApparentType returns the apparent type of a type.
|
|
func (s *Session) handleGetApparentType(ctx context.Context, params *CheckerTypeParams) (*TypeResponse, error) {
|
|
setup, err := s.setupChecker(ctx, params.Snapshot, params.Project)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer setup.done()
|
|
|
|
t, err := setup.resolveTypeHandle(params.Type)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return setup.newTypeResponse(setup.checker.GetApparentType(t)), nil
|
|
}
|
|
|
|
// handleGetIndexInfosOfType returns the index infos of a type.
|
|
func (s *Session) handleGetIndexInfosOfType(ctx context.Context, params *CheckerTypeParams) ([]*IndexInfoResponse, error) {
|
|
setup, err := s.setupChecker(ctx, params.Snapshot, params.Project)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer setup.done()
|
|
|
|
t, err := setup.resolveTypeHandle(params.Type)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
infos := setup.checker.GetIndexInfosOfType(t)
|
|
if len(infos) == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
results := make([]*IndexInfoResponse, len(infos))
|
|
for i, info := range infos {
|
|
results[i] = &IndexInfoResponse{
|
|
KeyType: *setup.newTypeResponse(info.KeyType()),
|
|
ValueType: *setup.newTypeResponse(info.ValueType()),
|
|
IsReadonly: info.IsReadonly(),
|
|
}
|
|
if info.Declaration() != nil {
|
|
results[i].Declaration = setup.sd.nodeHandleFrom(info.Declaration())
|
|
}
|
|
}
|
|
|
|
return results, nil
|
|
}
|
|
|
|
// handleGetConstraintOfTypeParameter returns the constraint of a type parameter.
|
|
func (s *Session) handleGetConstraintOfTypeParameter(ctx context.Context, params *CheckerTypeParams) (*TypeResponse, error) {
|
|
setup, err := s.setupChecker(ctx, params.Snapshot, params.Project)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer setup.done()
|
|
|
|
t, err := setup.resolveTypeHandle(params.Type)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
constraint := setup.checker.GetConstraintOfTypeParameter(t)
|
|
if constraint == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
return setup.newTypeResponse(constraint), nil
|
|
}
|
|
|
|
// handleGetBaseConstraintOfType returns the base constraint of an instantiable type.
|
|
func (s *Session) handleGetBaseConstraintOfType(ctx context.Context, params *CheckerTypeParams) (*TypeResponse, error) {
|
|
setup, err := s.setupChecker(ctx, params.Snapshot, params.Project)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer setup.done()
|
|
|
|
t, err := setup.resolveTypeHandle(params.Type)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
constraint := setup.checker.GetBaseConstraintOfType(t)
|
|
if constraint == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
return setup.newTypeResponse(constraint), nil
|
|
}
|
|
|
|
// handleGetPropertyOfType returns a named property symbol of a type.
|
|
func (s *Session) handleGetPropertyOfType(ctx context.Context, params *GetPropertyOfTypeParams) (*SymbolResponse, error) {
|
|
setup, err := s.setupChecker(ctx, params.Snapshot, params.Project)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer setup.done()
|
|
|
|
t, err := setup.resolveTypeHandle(params.Type)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
prop := setup.checker.GetPropertyOfType(t, params.Name)
|
|
if prop == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
return setup.newSymbolResponse(prop), nil
|
|
}
|
|
|
|
// handleGetConstantValue returns the constant value of an enum member or const enum access.
|
|
func (s *Session) handleGetConstantValue(ctx context.Context, params *CheckerNodeParams) (any, error) {
|
|
setup, err := s.setupChecker(ctx, params.Snapshot, params.Project)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer setup.done()
|
|
|
|
node, err := setup.sd.resolveNodeHandle(setup.program, params.Location)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if node == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
return literalValueToJSON(setup.checker.GetConstantValue(node)), nil
|
|
}
|
|
|
|
// handleGetSignatureFromDeclaration returns the signature of a function-like declaration.
|
|
func (s *Session) handleGetSignatureFromDeclaration(ctx context.Context, params *CheckerNodeParams) (*SignatureResponse, error) {
|
|
setup, err := s.setupChecker(ctx, params.Snapshot, params.Project)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer setup.done()
|
|
|
|
node, err := setup.sd.resolveNodeHandle(setup.program, params.Location)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return setup.newSignatureResponse(setup.checker.GetSignatureFromDeclaration(node)), nil
|
|
}
|
|
|
|
// handleGetExportSpecifierLocalTargetSymbol returns the local target symbol of an export specifier.
|
|
func (s *Session) handleGetExportSpecifierLocalTargetSymbol(ctx context.Context, params *CheckerNodeParams) (*SymbolResponse, error) {
|
|
setup, err := s.setupChecker(ctx, params.Snapshot, params.Project)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer setup.done()
|
|
|
|
node, err := setup.sd.resolveNodeHandle(setup.program, params.Location)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if node == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
symbol := setup.checker.GetExportSpecifierLocalTargetSymbol(node)
|
|
if symbol == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
return setup.newSymbolResponse(symbol), nil
|
|
}
|
|
|
|
// handleGetAliasedSymbol resolves an alias symbol to its target.
|
|
func (s *Session) handleGetAliasedSymbol(ctx context.Context, params *CheckerSymbolParams) (*SymbolResponse, error) {
|
|
setup, err := s.setupChecker(ctx, params.Snapshot, params.Project)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer setup.done()
|
|
|
|
symbol, err := setup.resolveSymbolHandle(params.Symbol)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return setup.newSymbolResponse(setup.checker.GetAliasedSymbol(symbol)), nil
|
|
}
|
|
|
|
// handleGetImmediateAliasedSymbol resolves one level of alias indirection.
|
|
func (s *Session) handleGetImmediateAliasedSymbol(ctx context.Context, params *CheckerSymbolParams) (*SymbolResponse, error) {
|
|
setup, err := s.setupChecker(ctx, params.Snapshot, params.Project)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer setup.done()
|
|
|
|
symbol, err := setup.resolveSymbolHandle(params.Symbol)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if symbol == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
aliased := setup.checker.GetImmediateAliasedSymbol(symbol)
|
|
if aliased == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
return setup.newSymbolResponse(aliased), nil
|
|
}
|
|
|
|
// handleGetExportsOfModule returns the resolved exports of a module symbol,
|
|
// including those introduced by `export *` and re-exports.
|
|
func (s *Session) handleGetExportsOfModule(ctx context.Context, params *CheckerSymbolParams) ([]*SymbolResponse, error) {
|
|
setup, err := s.setupChecker(ctx, params.Snapshot, params.Project)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer setup.done()
|
|
|
|
symbol, err := setup.resolveSymbolHandle(params.Symbol)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if symbol == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
exports := setup.checker.GetExportsOfModule(symbol)
|
|
if len(exports) == 0 {
|
|
return nil, nil
|
|
}
|
|
slices.SortFunc(exports, setup.checker.CompareSymbols)
|
|
|
|
results := make([]*SymbolResponse, len(exports))
|
|
for i, exp := range exports {
|
|
results[i] = setup.newSymbolResponse(exp)
|
|
}
|
|
|
|
return results, nil
|
|
}
|
|
|
|
// handleGetMemberInModuleExports returns an export by name from a module symbol.
|
|
func (s *Session) handleGetMemberInModuleExports(ctx context.Context, params *GetMemberInModuleExportsParams) (*SymbolResponse, error) {
|
|
setup, err := s.setupChecker(ctx, params.Snapshot, params.Project)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer setup.done()
|
|
|
|
symbol, err := setup.resolveSymbolHandle(params.Symbol)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if symbol == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
member := setup.checker.TryGetMemberInModuleExports(params.Name, symbol)
|
|
if member == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
return setup.newSymbolResponse(member), nil
|
|
}
|
|
|
|
// handleGetJSDocTags returns the JSDoc tags of a symbol as structured name/text pairs.
|
|
func (s *Session) handleGetJSDocTags(ctx context.Context, params *CheckerSymbolParams) ([]*JSDocTagInfo, error) {
|
|
setup, err := s.setupChecker(ctx, params.Snapshot, params.Project)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer setup.done()
|
|
|
|
symbol, err := setup.resolveSymbolHandle(params.Symbol)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if symbol == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
langSvc, err := s.setupLanguageService(setup.sd, setup.program, params.Project, "")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
tags := langSvc.GetSymbolJSDocTags(symbol)
|
|
if len(tags) == 0 {
|
|
return nil, nil
|
|
}
|
|
results := make([]*JSDocTagInfo, len(tags))
|
|
for i, tag := range tags {
|
|
results[i] = &JSDocTagInfo{Name: tag.Name, Text: tag.Text}
|
|
}
|
|
return results, nil
|
|
}
|
|
|
|
// handleGetDocumentationComment returns the rendered documentation comment of a symbol as plain text.
|
|
func (s *Session) handleGetDocumentationComment(ctx context.Context, params *CheckerSymbolParams) (string, error) {
|
|
setup, err := s.setupChecker(ctx, params.Snapshot, params.Project)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
defer setup.done()
|
|
|
|
symbol, err := setup.resolveSymbolHandle(params.Symbol)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if symbol == nil {
|
|
return "", nil
|
|
}
|
|
|
|
langSvc, err := s.setupLanguageService(setup.sd, setup.program, params.Project, "")
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return langSvc.GetSymbolDocumentationComment(setup.checker, symbol), nil
|
|
}
|
|
|
|
// handleGetTypeArguments returns the type arguments of a type reference.
|
|
func (s *Session) handleGetTypeArguments(ctx context.Context, params *CheckerTypeParams) ([]*TypeResponse, error) {
|
|
setup, err := s.setupChecker(ctx, params.Snapshot, params.Project)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer setup.done()
|
|
|
|
t, err := setup.resolveTypeHandle(params.Type)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
typeArgs := setup.checker.GetTypeArguments(t)
|
|
if len(typeArgs) == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
results := make([]*TypeResponse, len(typeArgs))
|
|
for i, ta := range typeArgs {
|
|
results[i] = setup.newTypeResponse(ta)
|
|
}
|
|
|
|
return results, nil
|
|
}
|
|
|
|
func (s *Session) handleGetTrueTypeOfConditionalType(ctx context.Context, params *GetTypePropertyParams) (*TypeResponse, error) {
|
|
setup, err := s.setupChecker(ctx, params.Snapshot, params.Project)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer setup.done()
|
|
|
|
t, err := setup.sd.resolveTypeHandle(params.Project, params.Type)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return setup.sd.newTypeResponse(params.Project, setup.checker.GetTrueTypeOfConditionalType(t)), nil
|
|
}
|
|
|
|
func (s *Session) handleGetFalseTypeOfConditionalType(ctx context.Context, params *GetTypePropertyParams) (*TypeResponse, error) {
|
|
setup, err := s.setupChecker(ctx, params.Snapshot, params.Project)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer setup.done()
|
|
|
|
t, err := setup.sd.resolveTypeHandle(params.Project, params.Type)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return setup.sd.newTypeResponse(params.Project, setup.checker.GetFalseTypeOfConditionalType(t)), nil
|
|
}
|
|
|
|
func (sd *snapshotData) resolveNodeHandle(program *compiler.Program, handle NodeHandle) (*ast.Node, error) {
|
|
s := string(handle)
|
|
// Format: "index.kind.path" — we need index and path, kind is informational only.
|
|
firstDot := strings.IndexByte(s, '.')
|
|
if firstDot == -1 {
|
|
return nil, fmt.Errorf("%w: invalid node handle %q", ErrClientError, handle)
|
|
}
|
|
secondDot := strings.IndexByte(s[firstDot+1:], '.')
|
|
if secondDot == -1 {
|
|
return nil, fmt.Errorf("%w: invalid node handle %q", ErrClientError, handle)
|
|
}
|
|
secondDot += firstDot + 1 // adjust to absolute index
|
|
|
|
idx, err := strconv.ParseUint(s[:firstDot], 10, 32)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("%w: invalid node handle %q: %w", ErrClientError, handle, err)
|
|
}
|
|
path := tspath.Path(s[secondDot+1:])
|
|
|
|
sourceFile := program.GetSourceFileByPath(path)
|
|
if sourceFile == nil {
|
|
return nil, fmt.Errorf("%w: node handle %q could not be resolved (file may not be loaded or handle may be stale)", ErrClientError, handle)
|
|
}
|
|
table := encoder.GetNodeIndexTable(sourceFile)
|
|
|
|
if table != nil && idx < uint64(len(table.Nodes)) {
|
|
node := table.Nodes[idx]
|
|
if node != nil {
|
|
return node, nil
|
|
}
|
|
}
|
|
return nil, fmt.Errorf("%w: node handle %q could not be resolved (file may not be loaded or handle may be stale)", ErrClientError, handle)
|
|
}
|
|
|
|
// computeSnapshotChanges computes the per-project source file differences between
|
|
// two snapshots. It uses DiffOrderedMaps on projects to find changed/removed projects,
|
|
// then DiffMaps on FilesByPath for each changed project to collect file-level changes.
|
|
func computeSnapshotChanges(prev *project.Snapshot, next *project.Snapshot) *SnapshotChanges {
|
|
prevProjects := prev.ProjectCollection.ProjectsByPath()
|
|
nextProjects := next.ProjectCollection.ProjectsByPath()
|
|
|
|
var changes SnapshotChanges
|
|
|
|
collections.DiffOrderedMaps(
|
|
prevProjects, nextProjects,
|
|
// onAdded: new project — nothing to retain from previous snapshot.
|
|
func(_ tspath.Path, _ *project.Project) {},
|
|
// onRemoved: project removed entirely.
|
|
func(_ tspath.Path, oldProj *project.Project) {
|
|
changes.RemovedProjects = append(changes.RemovedProjects, ProjectHandle(oldProj))
|
|
},
|
|
// onModified: project changed, diff its files.
|
|
func(_ tspath.Path, oldProj *project.Project, newProj *project.Project) {
|
|
if oldProj.GetProgram() == newProj.GetProgram() {
|
|
return
|
|
}
|
|
var oldFiles, newFiles map[tspath.Path]*ast.SourceFile
|
|
if p := oldProj.GetProgram(); p != nil {
|
|
oldFiles = p.FilesByPath()
|
|
}
|
|
if p := newProj.GetProgram(); p != nil {
|
|
newFiles = p.FilesByPath()
|
|
}
|
|
var projectChanges ProjectFileChanges
|
|
core.DiffMaps(
|
|
oldFiles, newFiles,
|
|
nil, // onAdded: new file in project, not a change.
|
|
func(path tspath.Path, _ *ast.SourceFile) {
|
|
projectChanges.DeletedFiles = append(projectChanges.DeletedFiles, path)
|
|
},
|
|
func(path tspath.Path, _ *ast.SourceFile, _ *ast.SourceFile) {
|
|
projectChanges.ChangedFiles = append(projectChanges.ChangedFiles, path)
|
|
},
|
|
)
|
|
if len(projectChanges.ChangedFiles) > 0 || len(projectChanges.DeletedFiles) > 0 {
|
|
if changes.ChangedProjects == nil {
|
|
changes.ChangedProjects = make(map[ProjectID]*ProjectFileChanges)
|
|
}
|
|
changes.ChangedProjects[ProjectHandle(newProj)] = &projectChanges
|
|
}
|
|
},
|
|
)
|
|
|
|
return &changes
|
|
}
|
|
|
|
// Close closes the session and releases all active snapshots,
|
|
// regardless of their ref counts.
|
|
func (s *Session) Close() {
|
|
s.releaseOpenRefs()
|
|
|
|
s.snapshotsMu.Lock()
|
|
defer s.snapshotsMu.Unlock()
|
|
for handle, sd := range s.snapshots {
|
|
sd.snapshot.Deref(s.projectSession)
|
|
delete(s.snapshots, handle)
|
|
}
|
|
}
|
|
|
|
// releaseOpenRefs releases every project and file ref this session is holding open
|
|
// in the project session. This keeps the API's ref counts balanced when an API
|
|
// session is shut down while sharing a longer-lived project session (e.g. one
|
|
// backing an LSP server), so API-opened projects and files aren't leaked. Only
|
|
// refs the session currently holds are closed, so it never over-releases.
|
|
func (s *Session) releaseOpenRefs() {
|
|
s.updateMu.Lock()
|
|
defer s.updateMu.Unlock()
|
|
|
|
if s.openProjects.Len() == 0 && s.openFiles.Len() == 0 {
|
|
return
|
|
}
|
|
|
|
apiRequest := &project.APISnapshotRequest{}
|
|
if s.openProjects.Len() > 0 {
|
|
apiRequest.CloseProjects = s.openProjects.Clone()
|
|
}
|
|
if s.openFiles.Len() > 0 {
|
|
apiRequest.CloseFiles = s.openFiles.Clone()
|
|
}
|
|
snapshot, err := s.projectSession.APIUpdate(context.Background(), project.FileChangeSummary{}, apiRequest)
|
|
// APIUpdate returns a ref'd snapshot even on error; always release it.
|
|
snapshot.Deref(s.projectSession)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
s.openProjects.Clear()
|
|
s.openFiles.Clear()
|
|
}
|
|
|
|
func formatSessionID(id uint64) string {
|
|
return fmt.Sprintf("api-session-%d", id)
|
|
}
|
|
|
|
// toPath converts a file name to a normalized path.
|
|
func (s *Session) toPath(fileName string) tspath.Path {
|
|
return tspath.ToPath(fileName, s.projectSession.GetCurrentDirectory(), s.projectSession.FS().UseCaseSensitiveFileNames())
|
|
}
|
|
|
|
// toFileChangeSummary converts API file changes to a project.FileChangeSummary.
|
|
func (s *Session) toFileChangeSummary(changes *APIFileChanges) project.FileChangeSummary {
|
|
if changes == nil {
|
|
return project.FileChangeSummary{}
|
|
}
|
|
var summary project.FileChangeSummary
|
|
if changes.InvalidateAll {
|
|
summary.InvalidateAll = true
|
|
summary.IncludesWatchChangeOutsideNodeModules = true
|
|
return summary
|
|
}
|
|
cwd := s.projectSession.GetCurrentDirectory()
|
|
for _, doc := range changes.Changed {
|
|
uri := doc.ToURI(cwd)
|
|
summary.Changed.Add(uri)
|
|
}
|
|
for _, doc := range changes.Created {
|
|
uri := doc.ToURI(cwd)
|
|
summary.Created.Add(uri)
|
|
}
|
|
for _, doc := range changes.Deleted {
|
|
uri := doc.ToURI(cwd)
|
|
summary.Deleted.Add(uri)
|
|
}
|
|
if summary.Changed.Len()+summary.Created.Len()+summary.Deleted.Len() > 0 {
|
|
summary.IncludesWatchChangeOutsideNodeModules = true
|
|
}
|
|
return summary
|
|
}
|
|
|
|
// handleGetSyntacticDiagnostics returns syntactic diagnostics for a file or all files.
|
|
func (s *Session) handleGetSyntacticDiagnostics(ctx context.Context, params *GetDiagnosticsParams) ([]*DiagnosticResponse, error) {
|
|
ctx = core.WithCheckerLifetime(ctx, core.CheckerLifetimeDiagnostics)
|
|
sd, err := s.getSnapshotData(params.Snapshot)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
program, err := sd.getProgram(params.Project)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
sourceFile, err := s.resolveOptionalSourceFile(program, params.File)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
diags := program.GetSyntacticDiagnostics(ctx, sourceFile)
|
|
return NewDiagnosticResponses(diags), nil
|
|
}
|
|
|
|
// handleGetBindDiagnostics returns bind diagnostics for a file or all files.
|
|
func (s *Session) handleGetBindDiagnostics(ctx context.Context, params *GetDiagnosticsParams) ([]*DiagnosticResponse, error) {
|
|
ctx = core.WithCheckerLifetime(ctx, core.CheckerLifetimeDiagnostics)
|
|
sd, err := s.getSnapshotData(params.Snapshot)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
program, err := sd.getProgram(params.Project)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
sourceFile, err := s.resolveOptionalSourceFile(program, params.File)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
diags := program.GetBindDiagnostics(ctx, sourceFile)
|
|
return NewDiagnosticResponses(diags), nil
|
|
}
|
|
|
|
// handleGetSemanticDiagnostics returns semantic diagnostics for a file or all files.
|
|
func (s *Session) handleGetSemanticDiagnostics(ctx context.Context, params *GetDiagnosticsParams) ([]*DiagnosticResponse, error) {
|
|
ctx = core.WithCheckerLifetime(ctx, core.CheckerLifetimeDiagnostics)
|
|
sd, err := s.getSnapshotData(params.Snapshot)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
program, err := sd.getProgram(params.Project)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
sourceFile, err := s.resolveOptionalSourceFile(program, params.File)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
diags := program.GetSemanticDiagnostics(ctx, sourceFile)
|
|
return NewDiagnosticResponses(diags), nil
|
|
}
|
|
|
|
// handleGetSuggestionDiagnostics returns suggestion diagnostics for a file or all files.
|
|
func (s *Session) handleGetSuggestionDiagnostics(ctx context.Context, params *GetDiagnosticsParams) ([]*DiagnosticResponse, error) {
|
|
ctx = core.WithCheckerLifetime(ctx, core.CheckerLifetimeDiagnostics)
|
|
sd, err := s.getSnapshotData(params.Snapshot)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
program, err := sd.getProgram(params.Project)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
sourceFile, err := s.resolveOptionalSourceFile(program, params.File)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
diags := program.GetSuggestionDiagnostics(ctx, sourceFile)
|
|
return NewDiagnosticResponses(diags), nil
|
|
}
|
|
|
|
// handleGetDeclarationDiagnostics returns declaration diagnostics for a file or all files.
|
|
func (s *Session) handleGetDeclarationDiagnostics(ctx context.Context, params *GetDiagnosticsParams) ([]*DiagnosticResponse, error) {
|
|
ctx = core.WithCheckerLifetime(ctx, core.CheckerLifetimeDiagnostics)
|
|
sd, err := s.getSnapshotData(params.Snapshot)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
program, err := sd.getProgram(params.Project)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
sourceFile, err := s.resolveOptionalSourceFile(program, params.File)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
diags := program.GetDeclarationDiagnostics(ctx, sourceFile)
|
|
return NewDiagnosticResponses(diags), nil
|
|
}
|
|
|
|
// handleGetConfigFileParsingDiagnostics returns config file parsing diagnostics.
|
|
func (s *Session) handleGetConfigFileParsingDiagnostics(ctx context.Context, params *GetProjectDiagnosticsParams) ([]*DiagnosticResponse, error) {
|
|
sd, err := s.getSnapshotData(params.Snapshot)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
program, err := sd.getProgram(params.Project)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
diags := program.GetConfigFileParsingDiagnostics()
|
|
return NewDiagnosticResponses(diags), nil
|
|
}
|
|
|
|
// handleGetProgramDiagnostics returns program-wide diagnostics, including options diagnostics.
|
|
func (s *Session) handleGetProgramDiagnostics(ctx context.Context, params *GetProjectDiagnosticsParams) ([]*DiagnosticResponse, error) {
|
|
sd, err := s.getSnapshotData(params.Snapshot)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
program, err := sd.getProgram(params.Project)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
diags := program.GetProgramDiagnostics()
|
|
return NewDiagnosticResponses(diags), nil
|
|
}
|
|
|
|
// handleGetGlobalDiagnostics returns global (non-file-specific) semantic diagnostics.
|
|
func (s *Session) handleGetGlobalDiagnostics(ctx context.Context, params *GetProjectDiagnosticsParams) ([]*DiagnosticResponse, error) {
|
|
ctx = core.WithCheckerLifetime(ctx, core.CheckerLifetimeDiagnostics)
|
|
sd, err := s.getSnapshotData(params.Snapshot)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
proj, err := sd.getProject(params.Project)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
program := proj.GetProgram()
|
|
if program == nil {
|
|
return nil, fmt.Errorf("%w: project has no program", ErrClientError)
|
|
}
|
|
|
|
// Global diagnostics are accumulated lazily by the project's checker pool as
|
|
// files are checked. Force a full semantic pass so any global (non-file-specific)
|
|
// diagnostics are produced; otherwise this would return an empty result for
|
|
// projects using an external checker pool (the typical API case), since
|
|
// compiler.Program.GetGlobalDiagnostics only reports for the internal pool.
|
|
program.GetSemanticDiagnostics(ctx, nil)
|
|
|
|
diags := core.Filter(proj.GetProjectDiagnostics(ctx), func(d *ast.Diagnostic) bool {
|
|
return d.File() == nil
|
|
})
|
|
return NewDiagnosticResponses(diags), nil
|
|
}
|
|
|
|
// resolveOptionalSourceFile resolves an optional DocumentIdentifier to a source file.
|
|
// Returns nil if the identifier is nil (meaning all files).
|
|
func (s *Session) resolveOptionalSourceFile(program *compiler.Program, file *DocumentIdentifier) (*ast.SourceFile, error) {
|
|
if file == nil {
|
|
return nil, nil
|
|
}
|
|
sourceFile := program.GetSourceFile(file.ToFileName())
|
|
if sourceFile == nil {
|
|
return nil, fmt.Errorf("%w: source file not found: %v", ErrClientError, file)
|
|
}
|
|
return sourceFile, nil
|
|
}
|
|
|
|
// handleGetReferencesToSymbolInFile returns node handles for all identifiers in a file that reference the given symbol.
|
|
func (s *Session) handleGetReferencesToSymbolInFile(ctx context.Context, params *GetReferencesToSymbolInFileParams) ([]NodeHandle, error) {
|
|
setup, err := s.setupChecker(ctx, params.Snapshot, params.Project)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer setup.done()
|
|
|
|
symbol, err := setup.resolveSymbolHandle(params.Symbol)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if symbol == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
sourceFile := setup.program.GetSourceFile(params.File.ToFileName())
|
|
if sourceFile == nil {
|
|
return nil, fmt.Errorf("%w: source file not found: %v", ErrClientError, params.File)
|
|
}
|
|
|
|
nodes := setup.checker.GetReferencesToSymbolInFile(sourceFile, symbol)
|
|
result := make([]NodeHandle, len(nodes))
|
|
for i, node := range nodes {
|
|
result[i] = setup.sd.nodeHandleFrom(node)
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func (s *Session) handleGetSignatureUsages(ctx context.Context, params *GetSignatureUsagesParams) ([]SignatureUsageResponse, error) {
|
|
sd, err := s.getSnapshotData(params.Snapshot)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
program, err := sd.getProgram(params.Project)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
signatureDecl, err := sd.resolveNodeHandle(program, params.SignatureDecl)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if signatureDecl == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
langSvc, err := s.setupLanguageService(sd, program, params.Project, "")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
usages := langSvc.GetSignatureUsages(ctx, signatureDecl)
|
|
if usages == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
result := make([]SignatureUsageResponse, 0, len(usages))
|
|
for _, u := range usages {
|
|
entry := SignatureUsageResponse{
|
|
Name: sd.nodeHandleFrom(u.Name),
|
|
}
|
|
if u.Call != nil {
|
|
entry.Call = sd.nodeHandleFrom(u.Call)
|
|
}
|
|
result = append(result, entry)
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
// handleGetCompletionsAtPosition returns completions at a position in a document.
|
|
func (s *Session) handleGetCompletionsAtPosition(ctx context.Context, params *GetCompletionsAtPositionParams) (*CompletionInfoResponse, error) {
|
|
if params.IncludeSymbol {
|
|
ctx = core.WithCheckerLifetime(ctx, core.CheckerLifetimeAPI)
|
|
}
|
|
sd, err := s.getSnapshotData(params.Snapshot)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
program, err := sd.getProgram(params.Project)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
sourceFile := program.GetSourceFile(params.File.ToFileName())
|
|
if sourceFile == nil {
|
|
return nil, nil
|
|
}
|
|
langSvc, err := s.setupLanguageService(sd, program, params.Project, "")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
positionMap := sourceFile.GetPositionMap()
|
|
internalPos := positionMap.UTF16ToUTF8(int(params.Position))
|
|
result, err := langSvc.GetCompletionsAtPosition(ctx, sourceFile, internalPos, params.TriggerCharacter, params.IncludeSymbol)
|
|
if err != nil || result == nil {
|
|
return nil, err
|
|
}
|
|
entries := make([]*CompletionEntryResponse, 0, len(result.Items))
|
|
for _, item := range result.Items {
|
|
entry := &CompletionEntryResponse{
|
|
Name: item.Label,
|
|
SortText: item.SortText,
|
|
InsertText: item.InsertText,
|
|
FilterText: item.FilterText,
|
|
Detail: item.Detail,
|
|
}
|
|
if item.Kind != nil {
|
|
entry.Kind = uint32(*item.Kind)
|
|
}
|
|
if item.LabelDetails != nil {
|
|
entry.LabelDetails = &CompletionEntryLabelDetailsResponse{
|
|
Detail: item.LabelDetails.Detail,
|
|
Description: item.LabelDetails.Description,
|
|
}
|
|
}
|
|
if item.Symbol != nil {
|
|
entry.Symbol = sd.newSymbolResponse(item.Symbol, params.Project)
|
|
}
|
|
entries = append(entries, entry)
|
|
}
|
|
return &CompletionInfoResponse{
|
|
IsIncomplete: result.IsIncomplete,
|
|
Entries: entries,
|
|
}, nil
|
|
}
|
|
|
|
// handleGetReferencedSymbolsForNode returns node handles for all references found at a node.
|
|
func (s *Session) handleGetReferencedSymbolsForNode(ctx context.Context, params *GetReferencedSymbolsForNodeParams) ([]ReferencedSymbolEntry, error) {
|
|
sd, err := s.getSnapshotData(params.Snapshot)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
program, err := sd.getProgram(params.Project)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
node, err := sd.resolveNodeHandle(program, params.Node)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if node == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
langSvc, err := s.setupLanguageService(sd, program, params.Project, "")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
sourceFiles := program.GetSourceFiles()
|
|
entries := langSvc.GetReferencedSymbolsForNode(ctx, params.Position, node, sourceFiles)
|
|
if entries == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
var result []ReferencedSymbolEntry
|
|
for _, entry := range entries {
|
|
defNode := entry.DefinitionNode()
|
|
if defNode == nil {
|
|
continue
|
|
}
|
|
var refs []NodeHandle
|
|
for _, ref := range entry.References() {
|
|
if ref.IsNodeEntry() {
|
|
refs = append(refs, sd.nodeHandleFrom(ref.Node()))
|
|
}
|
|
}
|
|
re := ReferencedSymbolEntry{
|
|
Definition: sd.nodeHandleFrom(defNode),
|
|
References: refs,
|
|
}
|
|
if sym := entry.DefinitionSymbol(); sym != nil {
|
|
re.Symbol = sd.newSymbolResponse(sym, params.Project)
|
|
}
|
|
result = append(result, re)
|
|
}
|
|
return result, nil
|
|
}
|