Skip to content

forcedotcom/apex-language-support

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

371 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

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.

graph TD
    subgraph "Core Components"
        apex-parser-ast[apex-parser-ast]
        custom-services[custom-services]
        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 --> custom-services
    apex-parser-ast --> lsp-compliant-services
    apex-lsp-shared --> custom-services
    apex-lsp-shared --> lsp-compliant-services

    %% Runtime implementation
    custom-services --> apex-ls
    lsp-compliant-services --> apex-ls
    apex-ls --> apex-lsp-vscode-extension

    %% Testing dependencies
    apex-ls --> apex-lsp-testbed
Loading

Package Descriptions

Core Components

  • apex-parser-ast: Provides AST (Abstract Syntax Tree) parsing capabilities for Apex code
  • custom-services: Implements custom services beyond the standard LSP specification
  • 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.

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
Loading

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: false
apex.experimental.workers.poolSize  number    default: 2 (min: 1, max: cpus-2)

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 and the sequence diagrams in .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:

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.

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.

npm install @salesforce/apex-lsp-testbed

Requirements

  • Node.js (latest LTS recommended)
  • npm

Installation

# Clone the repository
git clone <repository-url>
cd apex-language-support

# Install dependencies
npm install

Development

To build all packages, run the following command from the root of the repository:

# Build all packages
npm run compile

Other useful commands for development include:

# Run all tests
npm run test

# Run all tests with coverage
npm run test:coverage

# Lint all packages
npm run lint

# Fix linting issues
npm 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:

# Build and package the VS Code extension
npm run package --workspace=apex-language-server-extension

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

# Run all tests with coverage
npm run test:coverage

# Generate a consolidated coverage report for the entire repository
npm 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:

    # 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:

    # 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

About

experimental, do not use

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Contributors