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 or ReadonlyArray. 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 }