# Apex Language Support
# This repository is experimental - DO NOT USE
This repository contains a set of packages that collectively implement language support for Salesforce Apex, following the Language Server Protocol (LSP) specification.
## Architecture Overview
The project is structured as a monorepo with several interconnected packages that serve different purposes in the language support ecosystem.
```mermaid
graph TD
subgraph "Core Components"
apex-parser-ast[apex-parser-ast]
lsp-compliant-services[lsp-compliant-services]
apex-lsp-shared[apex-lsp-shared]
end
subgraph "Runtime"
apex-ls[apex-ls]
apex-lsp-vscode-extension[apex-lsp-vscode-extension]
end
subgraph "Testing & Development"
apex-lsp-testbed[apex-lsp-testbed]
end
%% Core dependencies
apex-parser-ast --> lsp-compliant-services
apex-lsp-shared --> lsp-compliant-services
%% Runtime implementation
lsp-compliant-services --> apex-ls
apex-ls --> apex-lsp-vscode-extension
%% Testing dependencies
apex-ls --> apex-lsp-testbed
```
## Package Descriptions
### Core Components
- **apex-parser-ast**: Provides AST (Abstract Syntax Tree) parsing capabilities for Apex code
- **lsp-compliant-services**: Implements standard LSP services (completion, hover, etc.)
- **apex-lsp-shared**: Provides shared utilities including logging, notifications, and common functionality used across the language server ecosystem
### Runtime
- **apex-ls**: Apex Language Server that works across browser, Node.js, and web worker environments
- **apex-lsp-vscode-extension**: The VS Code extension package that integrates with VS Code's extension API
### Testing & Development
- **apex-lsp-testbed**: Testing utilities and integration tests for the language server
## Language Server Implementation
The `apex-ls` package implements the language server. It runs in Node.js (desktop), browser, and web worker environments, using the appropriate storage backend for each (file system for Node.js, IndexedDB for browser). Feature parity is maintained across all environments through the same LSP handlers and capabilities.
### Request Processing Pipeline
All LSP requests follow a single mandatory path regardless of whether workers are active:
```
LSP Client → LCSAdapter (connection handler)
→ LSPQueueManager (priority scheduling)
→ WorkerDispatcher (route to coordinator or worker)
→ Response
```
The `LSPQueueManager` implements a five-level priority scheduler with starvation relief. Priority scheduling and worker dispatch are orthogonal concerns: the queue controls *when* a request runs; the dispatcher controls *where*.
#### Coordinator-only request types
Three request types always execute on the coordinator thread:
| Type | Reason |
|------|--------|
| `completion` | Requires stateful session context |
| `signatureHelp` | Coupled to the completion session |
| `rename` | Requires cross-file coordination |
All other types — hover, definition, diagnostics, documentOpen/Change/Save/Close, references, implementation, codeLens, documentSymbol, foldingRange — are dispatchable to workers when the topology is active.
### Multi-Worker Topology
When `apex.experimental.workers.enabled` is `true`, the language server spawns an internal worker topology using `@effect/platform` Worker primitives. The LSP client is unaware of workers; they are entirely internal to the server process.
```mermaid
graph TB
subgraph "VS Code Extension Host"
Client["LSP Client"]
end
subgraph "Coordinator Thread"
LCS["LCSAdapter\nLSP Connection Handler"]
Queue["LSPQueueManager\nPriority Scheduler"]
Dispatch["WorkerDispatcher\nRoute by request type"]
Mediator["AssistanceMediator\nCross-worker IPC router"]
RLProxy["ResourceLoaderProxy\nStdlib IPC bridge"]
LCS --> Queue
Queue --> Dispatch
LCS --- Mediator
Mediator --- RLProxy
end
subgraph "Resource-Loader Worker"
RL["ResourceLoader\nProtobuf cache · ZIP archive"]
end
subgraph "Data-Owner Worker"
DO["SymbolManager\nCanonical workspace symbol graph\nDocument lifecycle · Batch ingestion"]
end
subgraph "Enrichment Worker Pool (×N)"
EW1["Enrichment Worker #1"]
EW2["Enrichment Worker #2"]
end
Client <-->|"LSP protocol"| LCS
Dispatch -->|"DispatchHover · DispatchDefinition\nDispatchDiagnostic"| EW1
Dispatch -->|"DispatchHover · DispatchDefinition\nDispatchDiagnostic"| EW2
Dispatch -->|"DispatchDocumentOpen/Change\nWorkspaceBatchIngest\nQueryGraphData"| DO
EW1 <-.->|"QuerySymbolSubset\nUpdateSymbolSubset\nresourceLoader:*"| Mediator
EW2 <-.->|"QuerySymbolSubset\nUpdateSymbolSubset\nresourceLoader:*"| Mediator
Mediator <-.->|"read · write-back"| DO
Mediator <-.->|"stdlib queries"| RLProxy
RLProxy <-->|"@effect/platform\nWorker protocol"| RL
```
#### Worker roles
| Role | Count | Responsibility |
|------|-------|----------------|
| **Coordinator** | 1 (main thread) | LSP connection, queue scheduling, worker dispatch, cross-worker IPC mediation |
| **Data-owner** | 1 | Canonical `SymbolManager`; document lifecycle (open/change/save/close); workspace batch ingestion; `QuerySymbolSubset`; `QueryGraphData` |
| **Enrichment pool** | N (default 2, max `cpus-2`) | Stateless handlers: hover, definition, diagnostics, references, implementation, documentSymbol, codeLens, foldingRange |
| **Resource-loader** | 1 | Stdlib loading from protobuf cache and ZIP archive via `ResourceLoaderService` |
#### Cross-worker IPC patterns
Enrichment workers are stateless. For each request they:
1. **Load** — send `QuerySymbolSubset` to data-owner via the coordinator's `AssistanceMediator` to fetch symbol data for the target file.
2. **Process** — run the LSP handler (e.g. hover, definition) locally using a transient `SymbolManager`.
3. **Resolve stdlib** — send `resourceLoader:resolveClass` / `resourceLoader:getSymbolTable` through the mediator to the resource-loader worker when a standard library type is encountered.
4. **Write back** — send `UpdateSymbolSubset` to data-owner if enrichment produced new symbol data, advancing the detail level for subsequent requests.
#### Wire protocol
Worker messages use `@effect/schema` `TaggedRequest` classes defined in `apex-lsp-shared/workerWireSchemas.ts`. Each request/response pair is versioned (`WIRE_PROTOCOL_VERSION`), fully type-safe, and JSON round-trip safe (no structured-clone hazards).
#### Transport isolation
`WorkerTopologyTransport` abstracts spawn/send/dispatch/shutdown behind opaque `WorkerHandle`/`PoolHandle` types. Production uses `@effect/platform` directly via `makeNodeWorkerLayer`. A `MockWorkerTransport` enables unit testing without real threads.
### Worker Settings
```
apex.experimental.workers.enabled boolean default: true
apex.experimental.workers.poolSize number default: 2 (min: 1, max: cpus-2)
apex.experimental.workers.resourceLoader boolean default: true
```
The `enabled` default is resolved authoritatively from the server-side defaults (`DEFAULT_APEX_SETTINGS`), so every client (VS Code, web, tests, direct LSP) sees the same value. Setting the `APEX_WORKER_EXPERIMENT` environment variable forces the topology on regardless of configuration.
Workers inherit the main process heap size (`apex.environment.jsHeapSizeGB`), debug flags (`apex.debug`, `apex.debugPort`), and profiling mode (`apex.environment.profilingMode`) automatically via `WorkerExecArgvBuilder`. Worker debug ports are auto-assigned and logged to the Output panel with role labels (e.g. `[apex-worker-dataOwner]`).
For full details see [`docs/adr-effect-worker-topology.md`](docs/adr-effect-worker-topology.md) and the sequence diagrams in [`.session-files/diagrams/worker-architecture.md`](.session-files/diagrams/worker-architecture.md).
## Server Mode Configuration
The Apex Language Server supports different operational modes optimized for different environments:
### Server Modes
- **Production Mode**: Optimized for performance and stability in production environments
- **Development Mode**: Full feature set with enhanced debugging and development workflows
- **Test Mode**: Testing-specific features and configurations
### Mode Configuration
The server mode can be configured through multiple methods:
1. **Environment Variable Override**: Set `APEX_LS_MODE=production` or `APEX_LS_MODE=development`
2. **Extension Mode**: Automatically determined based on VS Code extension mode
3. **NODE_ENV**: Falls back to `NODE_ENV` environment variable
For detailed information about server mode configuration and capabilities, see:
- [Server Mode Override Documentation](packages/apex-lsp-vscode-extension/docs/server-mode-override.md)
- [LSP Compliant Services README](packages/lsp-compliant-services/README.md)
## Client Libraries
### Unified Language Server
The `apex-ls` package provides a unified Apex Language Server that works across browser, Node.js, and web worker environments. It consolidates the functionality previously provided by separate browser and Node.js packages.
```bash
npm install @salesforce/apex-ls
```
### Testbed
The `apex-lsp-testbed` package provides a testbed for performance and qualitative analysis of different Apex language server implementations.
```bash
npm install @salesforce/apex-lsp-testbed
```
## Requirements
- Node.js (latest LTS recommended)
- pnpm (managed through Corepack)
## Installation
```bash
# Clone the repository
git clone <repository-url>
cd apex-language-support
# Install dependencies
corepack enable
pnpm install
```
## Development
To build all packages, run the following command from the root of the repository:
```bash
# Build all packages
pnpm run compile
```
Other useful commands for development include:
```bash
# Run all tests
pnpm run test
# Run all tests with coverage
pnpm run test:coverage
# Lint all packages
pnpm run lint
# Fix linting issues
pnpm run lint:fix
```
### Building and Packaging the VS Code Extension
To build and package the VS Code extension (`.vsix` file), run the following command from the root of the repository:
```bash
# Build and package the VS Code extension
pnpm --filter apex-language-server-extension run package
```
The packaged extension will be available in the `packages/apex-lsp-vscode-extension` directory.
## Testing and Code Coverage
This project includes comprehensive test coverage for all packages. Test coverage reports are generated using Jest and Istanbul.
### Running Tests with Coverage
```bash
# Run all tests with coverage
pnpm run test:coverage
# Generate a consolidated coverage report for the entire repository
pnpm run test:coverage:report
```
### Coverage Reports
After running the test coverage commands, coverage reports are available:
- **Package-level reports:** Generated in each package's `coverage` directory
- **Consolidated repository report:** Generated in the root `coverage` directory
The coverage reports include:
- HTML reports for interactive viewing (`coverage/lcov-report/index.html`)
- LCOV reports for CI integration
- Text summaries in the console
- JSON coverage data for further processing
### Coverage Thresholds
Global coverage thresholds are set in the Jest configuration file:
- Statements: 50%
- Branches: 50%
- Functions: 50%
- Lines: 50%
These thresholds can be adjusted per package as needed.
## CI/CD and Release Processes
This project uses GitHub Actions for continuous integration and automated releases. The workflows are designed to handle both VS Code extensions and NPM packages.
### Automated Workflows
#### CI Pipeline
- **Trigger**: Push to `main` branch or pull requests
- **Platforms**: Ubuntu and Windows with Node.js 20.x and LTS versions
- **Tasks**: Linting, compilation, testing with coverage, and packaging
#### Release Workflows
##### VS Code Extensions Release
The `nightly-extensions.yml` workflow handles automated releases of VS Code extensions:
- **Supported Extensions**:
- `apex-lsp-vscode-extension` (VS Code Marketplace and OpenVSX Registry)
- **Triggers**: Manual dispatch, workflow calls, or scheduled nightly builds
- **Features**:
- Smart change detection (only releases extensions with changes)
- Version bumping with even/odd minor version strategy for pre-releases
- Dry-run mode for testing release plans
- Support for multiple registries (VSCE, OVSX)
- Automated GitHub releases with VSIX artifacts
##### NPM Packages Release
The `release-npm.yml` workflow handles automated releases of NPM packages:
- **Supported Packages**: All packages in the monorepo
- **Triggers**: Manual dispatch or workflow calls
- **Features**:
- Conventional commit-based version bumping
- Automated NPM publishing
- Change detection to avoid unnecessary releases
### Release Strategies
#### Version Bumping
- **Auto**: Determined by conventional commit messages
- **Manual**: Patch, minor, or major bumps
- **Pre-release**: Uses odd minor versions (1.1.x, 1.3.x)
- **Stable**: Uses even minor versions (1.0.x, 1.2.x)
#### Nightly Builds
- Scheduled builds that create pre-release versions
- Use patch version bumps with nightly timestamps
- Automatically marked as pre-releases
### Manual Release Process
To manually trigger a release:
1. **VS Code Extensions**:
```bash
# Go to Actions tab in GitHub
# Select "Release VS Code Extensions"
# Choose extensions, registries, and options
# Set dry-run=true to test first
```
2. **NPM Packages**:
```bash
# Go to Actions tab in GitHub
# Select "Release NPM Packages"
# Choose packages and options
```
### Dry-Run Mode
Both release workflows support dry-run mode for testing:
- Simulates the entire release process without making actual changes
- Shows what would be released and to which registries
- Displays version bump calculations
- Perfect for testing release configurations
### Dependencies
The workflows require several tools and dependencies:
- **Node.js 20.x**: For building and packaging
- **jq**: For JSON processing (pre-installed on Ubuntu runners)
- **GitHub CLI**: For creating releases
- **VSCE/OVSX**: For publishing to marketplaces
## License
Licensed under the BSD 3-Clause license.
For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause