Skip to content

Developer Guide

raleigh-g-thompson edited this page Mar 30, 2026 · 4 revisions

CQL Developer Guide

This guide covers build setup, architecture, branch workflow, and local integration testing for contributors to the CQL VS Code extension and language server.

Architecture Overview

The project is split into two sub-projects that communicate over stdio:

vscode-cql (TypeScript VS Code extension)
       │  JSON-RPC / stdio (LSP)
       ▼
cql-language-server (fat JAR, Kotlin/JVM)

vscode-cql

File Role
src/extension.ts Extension entry point; activates LSP client and registers commands
src/cql-service/javaServiceInstaller.ts Downloads the language server JAR from Maven Central

The extension spawns the language server JAR as a child process and communicates with it via JSON-RPC over stdio using the Language Server Protocol (LSP).

cql-language-server

Class Role
CqlLanguageServer LSP server root; wires document and workspace services
CqlTextDocumentService Handles hover, diagnostics, formatting, go-to-definition, symbols, references
CqlWorkspaceService Dispatches workspace/executeCommand (Execute CQL, View ELM, Call Graph)
CqlCompilationManager Compiles CQL on each request (no caching)

Plugin discovery uses ServiceLoader; the debug plugin (plugin/debug/) registers CqlLanguageServerPluginFactory via @AutoService.

Build & Test Setup

Prerequisites

  • Java 11 or later
  • Maven 3.8 or later
  • Node.js 20 or later

Language Server

cd cql-language-server

# Run all tests
mvn test

# Build fat JAR (skip tests)
mvn package -DskipTests

# Build and install to local Maven repo
mvn install -DskipTests

Note: mvn test does NOT update the running JAR used by the extension. You must run mvn package or mvn install and then restart the extension to pick up changes.

VS Code Extension

cd vscode-cql
npm install
npm run compile

Extension Development Workflow

Running the Extension in Dev Mode

  1. Open vscode-cql/ as a workspace in VS Code.
  2. Press F5 to launch the Extension Development Host with the extension loaded.
  3. Open a CQL file in the dev host to trigger language server activation.

Wiring a Locally-Built JAR

By default the extension downloads the JAR version specified in package.json. To test against a locally built JAR:

  1. Build and install the language server:

    cd cql-language-server
    mvn install -DskipTests
  2. Update the version in vscode-cql/package.json to match the built JAR:

    "javaDependencies": {
      "cql-language-server": {
        "version": "<your-local-version>"
      }
    }
  3. Press F5 in vscode-cql/ to launch with the new JAR.

End-to-End Verification

After launching the dev host:

  • Open a .cql file — diagnostics and hover should work.
  • Trigger Execute CQL, View ELM, and View Call Graph via the Command Palette.
  • Confirm go-to-definition (F12) and document symbols (Ctrl+Shift+O) resolve correctly.

Versioning

Versioning is critical to both the VSCode extension and CQL Language Server. Deployment systems for both projects have well defined rules on verisoning. Publishing will fail if the versioning rules are not followed.

VSCode Extension

The VScode extension is published to the VSCode Marketplace.

Note (2026-03-29): There are plans to support publishing to Open VSX Registry.

VSCode Marketplace expects projects to use semver. Convention dictates that even minor versions are for release versions and odd minor versions are for pre-release versions. Prerelease version suffixes, e.g. -alpha, -prerelease, etc, are not supported

Development should be done on Prerelease versions. Once the pre-release has based UAT'd a release version can be published to the VS Code Marketplace.

Prior to promoting a Prerelease version to a Release version, ensure that the next Prerelease is published.

"VS Code will automatically update extensions to the highest version available, so even if a user opted-into a pre-release version and there is an extension release with a higher version, the user will be updated to the released version. So, we recommend that extensions use major.EVEN_NUMBER.patch for release versions and major.ODD_NUMBER.patch for pre-release versions. For example: 0.2.for release and 0.3. for pre-release." "If extension authors do not want their pre-release users to be updated to the release version, we recommend always incrementing and publishing a new pre-release version before publishing a release version to make sure that the pre-release version is always higher. Note that while pre-release users will be updated to a release version if it is higher, they still remain eligible to automatically update to future pre-releases with higher version numbers than the release version." VSCode Publishing Extensions - Prerelease notes

CQL Language Server (CQL-LS)

The CQL Language Server, which is a FAT jar, is published to Maven Central.

CQL-LS uses semver for release, and also supports -SNAPSHOT versions. Snapshots are pre-release versions of the next release.

Branching Strategy

This project uses trunk-based branching. master is the trunk — the single long-lived branch. All work happens on short-lived branches that merge back to master via pull requests. There is no develop, release, or hotfix tier.

  • Always branch from master. Never commit directly to master.

  • Branch naming:

    • feature/<name> for new features
    • bugfix/<name> for bug fixes
    • hotfix/<name> for hotfixes
    • task/<name> for code-sprecific changes, e.g. documentation, github CI/CD, version changes, etc.
  • Keep PRs small and focused on a single concern. When a feature is large, split it into multiple focused PRs rather than one large PR — reviews are faster and easier to revert.

  • Check your current branch before starting work:

    git branch --show-current
  • When one feature depends on another that has not yet merged, stack branches:

    git checkout feature/first-feature
    git checkout -b feature/second-feature

    Rebase the stacked branch onto master after the base PR merges.

Hot Fixes

Since there is no long-lived release branches, when faced with having to create a Hotfix for an existing release a developer will have to pull the tagged release and use that as the base for the hotfix.

Since tags are technically "immutable" (unchanging) pointers to a specific commit, you can't just "edit" a tag. Instead, you branch off the tag, fix the code, and create a new tag.

  1. Identify the Point of Failure First, find the tag associated with the buggy release (e.g., v1.0.0).

  2. Create a Branch from the Tag You need a workspace to apply your fix. Create a new branch starting exactly where the old release was.

    git checkout -b hotfix/fix-login-error v1.0.0
  3. Apply and Commit the Fix Fix the bug, test it, and commit the changes to this temporary branch.

    git add .
    git commit -m "fix: resolve login authentication timeout"
  4. Tag the New Release Now, create a new semantic version tag (e.g., v1.0.1). This signals to your CI/CD pipeline that a new, stable version is ready for production.

    git tag -a v1.0.1 -m "Hotfix for login timeout"
    git push origin v1.0.1
  5. Don't Forget the Trunk! This is the most important step in trunk-based development. You must ensure the fix also exists in the main branch so the bug doesn't reappear in the next major release.

  • If the fix is small: Cherry-pick the commit into main.
  • If it's complex: Merge the hotfix branch back into main.

Releases

Developers may encounter different situtations leading up to an offical release cycle. This example is for those common scenario, but its left to the developers to handle each release appropiately as circumstances dictate.

The typical release cycle is:

  • developers following trunk-based branching against master
  • master has a Prerelease version, with the patch version being updated on each published Prerelease

Versioning and Publishing

  1. Prep Release version:

    1. Create new task/release-vX.X.0 branch off of master
    2. Update package.json - version to the next Release, i.e. even minor, version. Expected patch version is 0.
    3. Update CHANGELOG.md - add a new section at the top with a summary of changes (see existing entries for format)
    4. Push changes to origin
    5. Create PR the version change
    6. After passing CI Build = ready for publishing to the VS Code Marketplace
    7. Merge into master
    8. Create a tagged version in github
  2. Prep next Prerelease version:

    1. Create new task/prerelease-vX.X.0 branch
    2. Update package.json - version to the next Prerelease, i.e. odd minor, version. Expected patch version is 0.
    3. Update CHANGELOG.md - add a new section at the top with a summary of changes (see existing entries for format)
    4. Push changes to origin
    5. Create PR the version change
    6. After passing CI Build = ready for publishing to the VS Code Marketplace
    7. Merge into master
    8. Create a tagged version in github
  3. Publish the next Prerelease version to the VS Code Marketplace

    1. Pull master, and confirm the version is the same as the Prerelease version from Step 2
    2. Run vsce login cqframework to authenticate
    3. Run vsce publish --prerelease
    4. Wait for publishing to complete. Following publishing process at the Manage Publishers & Extensions page.
  4. Publish the Release version to the VS Code Marketplace

    1. Checkout the tagged version from Step 1
    git checkout <tag_name>
    1. Run vsce login cqframework to authenticate
    2. Run vsce publish --prerelease

Note: Once a semver has been published to the marketplace it cannont be reused. Publishing the Prerelease version X.<ODD>.0 is needed to keep VSCode from overwriting a user's pre-release version. This is a limitation of VSCode and how extensions are handled.

Working with the Prerelease version

The Prerelease version is meant to be the "working" version of the master branch. During development, updates should be made to the Prerelease version of the master branch. Once Prerelease changes are stable bump versions, as detailed above, to publish an offical release and the next Prerelease starter version.

The Prelease working version can be created immediately after the previous offical release, or the developers can wait until there are changes ready for merging to master.

Example Prerelease work flow
  1. Update package.json - version to X.X.1, keep the same major and minor version of the Prerelease version only change the patch
  2. Push changes to origin
  3. Create PR the version change
  4. After passing CI Build = ready for publishing to the VS Code Marketplace
  5. Merge into master
  6. Development can now continue on the new master branch using the Prerelease patched version.

Only the verison is changed, there is no publish step. These changes are only for development.

CQL Language Server

Language Server is kotlin-based and uses snapshot-based release flow:

  • master always carries a SNAPSHOT version (e.g., 1.2.0-SNAPSHOT).
  • When the team decides to release, a build is cut from master and published at the release version (e.g., 1.2.0) — to Maven Central for cql-language-server
  • master is immediately bumped to the next minor SNAPSHOT (1.3.0-SNAPSHOT) in pom.xml / package.json respectively.
  • There are no release branches — releases are point-in-time cuts from the trunk.
  • Releases are unscheduled and at the discretion of the primary development team.

Dev Sandbox

dev-sandbox is an ephemeral integration branch — outside the trunk-based flow — used to combine multiple in-flight feature branches for shared experimentation before any of them are merged.

Creating dev-sandbox

# In cql-language-server
cd cql-language-server
git checkout master && git pull origin master
git checkout -b dev-sandbox

# In vscode-cql
cd ../vscode-cql
git checkout master && git pull origin master
git checkout -b dev-sandbox

Merging a Feature Branch for Testing

git checkout dev-sandbox
git merge feature/<name>

Repeat for each feature branch you want to test together.

Resetting dev-sandbox Back to master

When you are done testing or want a clean slate:

git checkout dev-sandbox
git reset --hard master

dev-sandbox can be pushed to origin to share an in-progress integration environment with other developers. Force-push is expected when resetting back to master:

git push --force origin dev-sandbox