Hybrid LSP Type Resolution in codebase-memory-mcp: A Complete Technical Guide
The hybrid LSP type resolver in codebase-memory-mcp combines JavaScript, TypeScript, JSX, and TSX parsing into a single Tree-sitter-based engine that resolves symbols across mixed-language projects using a unified type-info cache and the CBM infrastructure for cross-file lookups.
The codebase-memory-mcp project implements a sophisticated Language Server Protocol (LSP) server capable of handling multiple JavaScript-family dialects through one unified resolution system. This article examines the hybrid LSP type resolution architecture that enables accurate symbol resolution across JavaScript, TypeScript, JSX, and TSX files in a single pass.
What Is Hybrid LSP Type Resolution?
Hybrid LSP type resolution refers to the architecture in codebase-memory-mcp that merges parsing rules for plain JavaScript, TypeScript, JSX, and TSX into a single resolver. Instead of maintaining separate parsers for each syntactic variant, the system uses Tree-sitter's TypeScript grammar to handle all four languages simultaneously. This approach registers every symbol—including declarations, imports, exports, and JSX component usages—into a centralized type-info cache that serves LSP clients with accurate type information regardless of the source file extension.
Core Implementation in ts_lsp.c
The heart of the hybrid resolver lives in internal/cbm/lsp/ts_lsp.c. This file implements the request handling logic that bridges Tree-sitter parsing with the CBM (Codebase Memory) infrastructure.
Unified Grammar Processing
The resolver initializes a Tree-sitter parser configured for TypeScript, which inherently understands JSX and TSX syntax nodes. When processing a file, the system calls ts_parser_new() and sets the language to tree_sitter_typescript(), enabling a single parser instance to handle JavaScript-family source code without switching grammars.
After parsing, the resolver walks the syntax tree to construct a symbol table via build_symbol_table(). This traversal captures identifiers, their scopes, and inferred types—including JSX component props—regardless of whether the source is vanilla JavaScript or TypeScript with JSX.
Cross-File Resolution
The hybrid resolver leverages the CBM infrastructure to resolve symbols defined in separate modules. When encountering an import statement, the system invokes cbm_load_file() to dynamically parse dependency files. This allows the resolver to handle scenarios where a .tsx file imports a .js module, maintaining type consistency across language boundaries.
The cbm_load_file routine integrates with the type-info cache, ensuring that symbols loaded from external files are available for subsequent queries without re-parsing.
Standard Library Integration
Generated stub files provide pre-populated type information for built-in APIs. Located in internal/cbm/lsp/generated/, files such as python_stdlib_data.c, go_stdlib_data.c, and cpp_stdlib_data.c contain type definitions for standard libraries.
Before responding to LSP requests, the hybrid resolver merges these standard-library stubs with project-specific types. This ensures that calls to standard APIs are correctly typed regardless of whether the project uses JavaScript or TypeScript variants.
Practical Code Examples
Below are implementations showing how the hybrid resolver handles LSP requests and testing scenarios.
Handling Definition Requests
The entry point for hybrid resolution appears in ts_lsp_handle_request, defined in internal/cbm/lsp/ts_lsp.c:
static void ts_lsp_handle_request(cbmlsp_server *srv,
const char *method,
const json_t *params) {
if (strcmp(method, "textDocument/definition") == 0) {
const char *uri = json_string_value(json_object_get(params, "uri"));
const char *position = json_string_value(json_object_get(params, "position"));
/* 1. Parse the file using the shared TypeScript grammar */
TSParser *parser = ts_parser_new();
ts_parser_set_language(parser, tree_sitter_typescript());
TSTree *tree = ts_parser_parse_string(parser, NULL,
source_content, source_len);
/* 2. Build symbol table from the syntax tree */
SymbolTable *symtab = build_symbol_table(tree);
/* 3. Resolve definition location (may cross file boundaries) */
Location loc = resolve_definition(symtab, uri, position);
/* 4. Return response to LSP client */
cbmlsp_respond(srv, method, location_to_json(loc));
}
}
This function demonstrates the hybrid approach: one parser handles all JavaScript variants, while resolve_definition manages cross-file lookups through the CBM infrastructure.
Testing Hybrid Resolution
The test suite in tests/test_ts_lsp.c validates JSX component resolution:
static void test_jsx_component_resolution(void) {
const char *src =
"import React from 'react';\n"
"export const Button = (props) => <button>{props.label}</button>;\n"
"export const App = () => <Button label=\"Hi\"/>;\n";
/* Load source into virtual file */
cbm_file *f = cbm_load_virtual_file("App.tsx", src);
/* Resolve definition for Button component inside JSX */
Location loc = ts_lsp_resolve_definition(f, "App.tsx", line=4, col=15);
/* Verify location points to the export statement */
assert(loc.line == 2);
}
This test verifies that the hybrid resolver correctly identifies component definitions within JSX syntax, treating TSX and JavaScript imports uniformly.
Benefits of the Hybrid Approach
The hybrid LSP type resolution system offers several advantages for language server implementations:
-
Single Parser Maintenance: By utilizing Tree-sitter's TypeScript grammar for all JavaScript-family languages, the codebase eliminates redundant parser implementations. Updates to the grammar automatically propagate to JSX and TSX handling.
-
Consistent Type Information: Users receive accurate completions and go-to-definition behavior across mixed-language projects, such as React applications combining
.js,.ts, and.tsxfiles. -
Extensible Architecture: Adding support for additional JavaScript-derived dialects requires only minor grammar adjustments rather than entirely new resolver implementations.
-
Cross-Language Resolution: The integration with
cbm_load_fileenables seamless symbol resolution across module boundaries, even when importing files written in different dialects.
Summary
- Hybrid LSP type resolution in codebase-memory-mcp unifies JavaScript, TypeScript, JSX, and TSX processing through a single Tree-sitter parser located in
internal/cbm/lsp/ts_lsp.c. - The resolver constructs symbol tables via
build_symbol_table()and handles cross-file lookups using thecbm_load_fileinfrastructure. - Standard library type information from generated files like
python_stdlib_data.caugments project-specific types before LSP responses are sent. - The architecture reduces maintenance overhead while providing consistent type information across mixed-language codebases.
- Integration occurs through
internal/cbm/lsp_all.c, which aggregates all resolver modules for the finalcbm_lspbinary.
Frequently Asked Questions
How does the hybrid resolver handle different JavaScript dialects without separate parsers?
The resolver uses Tree-sitter's TypeScript grammar, which inherently includes rules for JSX and TSX syntax. By calling tree_sitter_typescript() in ts_parser_new(), the system parses JavaScript, TypeScript, JSX, and TSX files using the same grammar tree, eliminating the need for dialect-specific parsers while maintaining accurate syntax recognition.
What mechanism enables cross-file symbol resolution in the hybrid LSP system?
Cross-file resolution works through the CBM infrastructure's cbm_load_file function. When the resolver encounters an import statement, it dynamically loads and parses the referenced file, caching the results in the type-info cache. This allows symbols defined in .js modules to be resolved from .tsx files and vice versa.
Where is the hybrid LSP type resolution logic integrated into the build system?
All LSP-related source files are compiled together via internal/cbm/lsp_all.c, which includes the individual resolver modules including ts_lsp.c. This aggregated compilation unit produces the cbm_lsp binary, exposing the standard LSP JSON-RPC interface to clients.
How does the resolver provide type information for standard library functions?
The system merges auto-generated stub files located in internal/cbm/lsp/generated/—such as python_stdlib_data.c, go_stdlib_data.c, and cpp_stdlib_data.c—with project-specific symbol tables. These stubs pre-populate the type-info cache with standard library definitions, ensuring accurate typing for built-in API calls across all supported JavaScript variants.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →