Language Tooling
GTS provides full IDE support through a Volar-based language server, a TypeScript Language Service Plugin, and a VS Code extension.
Architecture
┌──────────────────────────────────────────────────────────────┐
│ VS Code Extension │
│ (gts-vscode) │
│ ┌───────────────────┐ ┌──────────────────────────────────┐ │
│ │ Extension Client │ │ TS Extension Patch (patch.ts) │ │
│ │ (extension.ts) │ │ Adds "gaming-ts" to TS modes │ │
│ └────────┬──────────┘ └──────────────┬───────────────────┘ │
│ │ │ │
│ ┌────────┴────────────────────────────┴──────────────────┐ │
│ │ Language Server (node.ts) │ │
│ │ ┌────────────────────┐ ┌──────────────────────────┐ │ │
│ │ │ TypeScript Service │ │ Diagnostics Plugin │ │ │
│ │ │ (volar-service-ts) │ │ (GTS transpiler errors) │ │ │
│ │ └────────────────────┘ └──────────────────────────┘ │ │
│ │ ┌──────────────────────────────────────────────────┐ │ │
│ │ │ Language Plugin │ │ │
│ │ │ ┌────────────────────────────────────────────┐ │ │ │
│ │ │ │ GtsVirtualCode │ │ │ │
│ │ │ │ (transpileForVolar -> code + mappings) │ │ │ │
│ │ │ └────────────────────────────────────────────┘ │ │ │
│ │ └──────────────────────────────────────────────────┘ │ │
│ └────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────┘Language Plugin (@gi-tcg/gts-language-plugin)
The language plugin is the bridge between the GTS transpiler and the Volar framework. It implements the LanguagePlugin interface from @volar/language-core.
GtsVirtualCode (virtual_code.ts)
Implements the Volar VirtualCode interface:
class GtsVirtualCode implements VirtualCode {
id = "root";
languageId = "gaming-ts";
mappings: CodeMapping[];
snapshot: ts.IScriptSnapshot;
errors: GtsTranspilerError[];
}Constructor:
- Gets the source text from the snapshot
- Calls
transpileForVolar(source, filename, config) - On success: stores the generated code and Volar mappings
- On error: stores the error, generates an empty (whitespace-only) snapshot with a single verification mapping so that the error can be reported as a diagnostic
Error recovery: When transpilation fails, the virtual code returns a snapshot filled with spaces (matching the source line lengths). This prevents the language server from crashing while still providing the source location for error diagnostics.
Language Server (@gi-tcg/gts-language-server)
The language server implements the Language Server Protocol (LSP). It has two entry points Node.js Server (node.ts) and **Browser Server (browser.ts).
Custom Services
- TypeScript Services — wraps
volar-service-typescriptto:- adds space as a signature help trigger character. This is because GTS syntax uses
name arg1, arg2which transpiles toname(arg1, arg2), so pressing space after an attribute name should trigger signature help. - adds
gtsAttributeas a supported semantic token modifier (used for Semantic Token Service).
- adds space as a signature help trigger character. This is because GTS syntax uses
- Diagnostics Service — surfaces
GtsTranspilerErrorinstances from the virtual code as LSP diagnostics. Converts the transpiler's 1-based line/column positions to 0-based LSP positions. - Completion Service — triggered when user press
:, and return result from TS semantic service by replacing trigger character from:to.. Hide suggestion result that starts with__gts_. - Semantic Token Service — override existing TS semantic service by add italic markup for mappings that are recognized as GTS attribute name.
- Code Lens Service — add a Code Lens line above each direct function to split itself from previous attribute definitions clearer.
TypeScript Language Service Plugin (@gi-tcg/gts-typescript-language-service-plugin)
A CJS module that integrates GTS into TypeScript's built-in language service, which enables GTS features into TSServer so that import foo from "./foo.gts" can works in TS as well. It's loaded as a TypeScript plugin through tsconfig.json:
{
"compilerOptions": {
"plugins": [{ "name": "@gi-tcg/gts-typescript-language-service-plugin" }]
}
}NOTE: it WONT support TS7 (Tsgo) for now.
VS Code Extension (gts-vscode)
TypeScript Extension Patch (patch.ts)
The VS Code extension patches the built-in TypeScript extension to recognize .gts files by intercepts require("fs").readFileSync for the TypeScript extension's main JS file.
NOTE: it WONT support TS7 (Tsgo) for now.
Regex-based Syntax Highlighting (syntaxes/GamingTS.tmLanguage.json)
A generated TextMate grammar from official TS one, that provides syntax highlighting for GTS files, for covering:
- GTS-specific keyword (
define) - Attribute definitions and blocks
- Shortcut function syntax (
:identifier,:( expr ),:{ stmts })
How IDE Features Work
Completions
- User types in a
.gtsfile - The language plugin transpiles (see below) the source to TypeScript with Volar mappings
- TypeScript's completions service runs on the generated code
- Volar maps the completions back to the source positions
For attribute names: the generated code creates typed variables like __gts_attr_obj_0.id(...), so TypeScript provides completions based on the ViewModel's attribute definitions.
Diagnostics
Two sources of diagnostics:
- Transpiler errors — surfaced by the diagnostics plugin (syntax errors, unsupported features)
- TypeScript errors — type checking on the generated code, mapped back to source positions (type mismatches, etc. Done by Volar.js and our point-to-point mapping of each syntax production)
Signature Help
When the user types a space after an attribute name (e.g., id ), the language server triggers signature help because space is registered as a trigger character. The generated code contains a function call (__gts_attr_obj_0.id(...)), so TypeScript provides parameter information. The lParenLoc recording in the parser ensures correct mapping for function calls.
Go-to-Definition / Hover
These work through the Volar mappings — source positions map to generated positions, and TypeScript resolves definitions/types in the generated code. The preservation of leading comments during transpilation keeps documentation of definition when Hover.
Auto-Import Insertion
This is done by resolving the location where TSServer inserts new imports. When auto-importing (code action or completion), TSServer determines the insertion point by looking at existing import declarations. The language server intercepts this through the Volar transform by:
-
Making generated imports unsorted — an unrelated
ExpressionStatement(0;) is inserted between system-generated import declarations and the last import group. This makes the generated imports appear "unsorted" to TSServer, so it always chooses the position after the final generated import as the insertion point. -
Mapping to content start — if the last import is a generated one, it will gets an extra range mapping that maps a newline after it to the content start offset in the source file. The "content start" is calculated by
getContentStartOffset()(volar/content_start.ts), that skips hashbang lines (#!/usr/bin/env node) and leading block-level comments (until two consecutive blank lines or non-comment content is encountered), yielding the character offset where meaningful content begins. This is used as the source mapping target so auto-imports are placed after file headers but before the main code.
Volar Transform (src/transform/volar/)
The Volar transform generates TypeScript type declarations for IDE features. Instead of producing runnable code, it generates type-level constructs that let TypeScript's type checker validate GTS definitions.
Overview
The Volar pipeline differs from the runtime pipeline:
- Uses a typing walker instead of the runtime visitor
- Generates type aliases and typed variables instead of function calls
- Uses a replacement system for complex type constructs (expanded after printing)
- Uses
espolarfor printing, which produces Volar CodeMappings natively
Typing Walker (volar/walker.ts)
The gtsToTypingsWalker visitor generates type information by maintaining stacks:
vmDefTypeIdStack— tracks the type of the current ViewModel's definition (what attributes are available)metaTypeIdStack— tracks the current meta type (accumulated state from attribute calls)finalMetaTypeIdStack— tracks the final meta type after all attributes in a blockattrsOfCurrentVm— tracks which attribute names have been used (for required attribute validation)
Key operations:
enterVMFromRoot(state)— starts processing adefineblock. Emits type aliases for the root VM's definition type and initial meta type.enterVMFromAttr(state, returningId)— enters a nested ViewModel from an attribute's return type (e.g.,skillattribute returns aSkillVM).exitVM(state)— validates that all required attributes have been provided. Emits a type check that produces an error if required attributes are missing.enterAttr(state, attrName)— prepares to call an attribute. Creates a typed variable that combines the current meta with the VM definition.exitAttr(state, returningId)— updates the meta type based on the attribute's return type (some attributes can rewrite the meta, e.g., adding variable names).insertHintStatement(state, whiteSpaceStart, whiteSpaceEnd)— inserts a syntheticGTSAttributeNameHintStatementnode that maps whitespace regions insidedefineblocks to virtual code. When printed, this becomes__gts_attr_obj. ;where the whitespace is source-mapped. This enables Volar to provide attribute name completions when the user's cursor is in whitespace areas between attributes in adefineblock.genBindingTyping(state, info)— generates a type for a binding export (theasclause).
Replacement System (volar/replacements.ts)
Complex type constructs can't be expressed directly in the AST. Instead, the walker emits placeholder tagged template expressions:
__gts_replacement_tag`{"type":"enterVMFromRoot","vm":"__root_vm",...}`;After printing with espolar, applyReplacements() regex-replaces these placeholders with actual TypeScript type code, and adjusts the generated offsets in the already-produced CodeMapping[] to account for length differences. For example, enterVMFromRoot becomes:
type __gts_rootVmDefType_0 = (typeof __root_vm)[__gts_symbols_namedDef];
type __gts_rootVmInitMetaType_1 = __gts_rootVmDefType_0[__gts_symbols_meta];The exitVM replacement generates a required-attribute validation check:
namespace __rans {
export type Collected = "id" | "since" | "tags";
export type Expected = { [K in keyof DefType]: ... }[keyof DefType];
}
((_: __rans.Expected extends __rans.Collected ? string : __rans.Expected) => 0)("...");This produces a TypeScript error if required attributes are missing.
Attribute Name Hints (GTSAttributeNameHintStatement)
When editing inside a define block, users often need completions for available attribute names in whitespace areas. For example, after typing a semicolon or inside an empty block body:
define Foo { // cursor here -> need attr name completions
id 1; // cursor here -> need attr name completions
}The typing walker inserts synthetic GTSAttributeNameHintStatement nodes in GTSNamedAttributeBlock at two positions:
- After the block opening
{: Maps whitespace between{and the first attribute to__gts_attr_obj. ;. - After each attribute's semicolon: Maps whitespace between an attribute's end and the next token to
__gts_attr_obj. ;.
These hint statements reuse the enterAttr/exitAttr mechanism with a special attribute name "~attrNameHint", but generate hintOnly: true replacements (producing {} instead of { Meta: ... } in the type variable), avoiding unnecessary meta-type accumulation. The dedicated printer outputs object. followed by context.writeSource() for the whitespace range and a trailing ";", creating a source-to-generated mapping so Volar can trigger completions at those positions.
Printing & Mappings (volar/printer.ts, volar/mappings.ts)
The Volar pipeline uses espolar (instead of esrap) for printing. espolar generates Volar CodeMapping[] directly alongside the code output, eliminating the need for a separate source-map-to-mapping conversion step.
Runtime vs Volar Printers
| Pipeline | Printer | Output |
|---|---|---|
Runtime (transpile) | esrap | { code, sourceMap } (source map v3, decoded from VLQ) |
IDE (transpileForVolar) | espolar | { code, mappings } (Volar CodeMapping[], produced natively) |
Configuration for Language Tooling
Language tooling reads GTS configuration from the nearest package.json using resolveGtsConfigSync(). This determines which provider to use, which affects the ViewModel types available in completions. See Configuration for details.