This file provides guidance for working with the RIDDL VSCode extension. For RIDDL language/compiler guidance, see ../riddl/CLAUDE.md.
This is a VSCode extension providing language support for RIDDL (Reactive Interface to Domain Definition Language). The extension integrates the RIDDL compiler (Scala.js) via npm package and provides IDE features like syntax highlighting, diagnostics, code completion, and commands.
- Language: TypeScript
- Platform: VSCode Extension API
- RIDDL Integration:
@ossuminc/riddl-libnpm package (Scala.js compiled to JavaScript) - Build Tool: npm + TypeScript compiler
- Testing: Mocha + VSCode Test API
riddl-vscode/
├── src/
│ ├── extension.ts # Extension entry point, activation
│ ├── commands.ts # RIDDL commands (info, parse, validate, translate)
│ ├── semanticTokensProvider.ts # Semantic highlighting
│ ├── hoverProvider.ts # Hover documentation
│ ├── diagnosticsProvider.ts # Real-time error checking
│ ├── completionProvider.ts # Code completion (IntelliSense)
│ ├── definitionProvider.ts # Go to Definition
│ ├── referenceProvider.ts # Find All References
│ └── documentSymbolProvider.ts # Document outline, breadcrumbs, go to symbol
├── syntaxes/
│ └── riddl.tmLanguage.json # TextMate grammar for syntax highlighting
├── test/
│ └── suite/ # Test suites for each feature
├── language-configuration.json # Editor features (brackets, comments, etc.)
├── package.json # Extension manifest and dependencies
├── tsconfig.json # TypeScript configuration
├── COMMANDS.md # User-facing command documentation
├── MILESTONE*.md # Technical implementation documentation
└── README.md # User-facing README
The extension was built incrementally through milestones:
- ✅ Milestone 1: Basic syntax highlighting (TextMate grammar)
- ✅ Milestone 2: RIDDL library integration (@ossuminc/riddl-lib)
- ✅ Milestone 3: Semantic token provider
- ✅ Milestone 4: Hover documentation
- ✅ Milestone 5: Diagnostics (syntax + semantic validation)
- ✅ Milestone 6: Code intelligence (completion, definitions, references)
- ✅ Milestone 7: Commands (info, parse, validate, translate)
- ✅ Milestone 8: Document Outline & Handler Analysis
Each milestone has a corresponding MILESTONEn_*.md file documenting the implementation.
VSCode uses a provider pattern for language features:
// Register a provider with VSCode
context.subscriptions.push(
vscode.languages.registerHoverProvider('riddl', new RiddlHoverProvider())
);Each provider implements a specific interface:
HoverProvider→provideHover()CompletionItemProvider→provideCompletionItems()DefinitionProvider→provideDefinition()ReferenceProvider→provideReferences()DocumentSemanticTokensProvider→provideDocumentSemanticTokens()DocumentSymbolProvider→provideDocumentSymbols()
The extension uses @ossuminc/riddl-lib, a Scala.js npm package:
Installation (from GitHub Packages via .npmrc):
{
"dependencies": {
"@ossuminc/riddl-lib": "1.8.0"
}
}Import and Usage:
import { RiddlAPI } from '@ossuminc/riddl-lib';
// Parse RIDDL source (returns opaque RootAST)
const result = RiddlAPI.parseString(source, origin, verbose);
// Inspect opaque root: RiddlAPI.inspectRoot(result.value)
// Validate RIDDL source (includes parsing + semantic validation)
const validationResult = RiddlAPI.validateString(
source, origin, verbose, noANSI);
// Tokenize for syntax highlighting
const tokensResult = RiddlAPI.parseToTokens(source, origin, verbose);
// Structural analysis (v1.8.0+)
const tree = RiddlAPI.getTree(source, origin); // hierarchical
const outline = RiddlAPI.getOutline(source, origin); // flat
const handlers = RiddlAPI.getHandlerCompleteness(source, origin);
// Get build info
const buildInfo = RiddlAPI.buildInfo;
const formatInfo = RiddlAPI.formatInfo; // Pre-formatted stringTypeScript Declarations:
Types are shipped with the package (index.d.ts). Key types:
ErrorInfo, ParseResult<T>, Token, TreeNode,
ValidationResult, HandlerCompletenessEntry, RootAST (opaque).
Two different display mechanisms:
-
Output Channel (for commands):
const channel = vscode.window.createOutputChannel('RIDDL'); channel.appendLine('Output text'); channel.show();
- User-initiated display (commands)
- Plain text output
- Used by: riddl.info, riddl.parse, riddl.validate, riddl.translate
-
Diagnostics (for real-time errors):
const diagnosticCollection = vscode.languages.createDiagnosticCollection('riddl'); diagnosticCollection.set(document.uri, diagnostics);
- Automatic display (squiggly underlines)
- Integrated with Problems panel
- Used by: diagnosticsProvider
Problem: RIDDL library outputs ANSI color codes, but VSCode Output panel doesn't render them.
Solution: Strip ANSI codes before displaying:
function stripAnsiCodes(text: string): string {
// Remove ANSI escape sequences
let cleaned = text.replace(/\x1b\[[0-9;]*m/g, '');
// Also remove any remaining bracket-style codes
cleaned = cleaned.replace(/\[([0-9]+;)*[0-9]*m/g, '');
return cleaned;
}When to use:
- Apply to all error messages shown in Output channel
- Apply to all diagnostic messages
- NOT needed for internal processing (only display)
RIDDL uses 1-based coordinates, VSCode uses 0-based:
// RIDDL location → VSCode Range
const line = Math.max(0, riddlLocation.line - 1);
const col = Math.max(0, riddlLocation.col - 1);
const range = new vscode.Range(line, col, line, endCol);Always subtract 1 when converting from RIDDL to VSCode coordinates.
For expensive operations (parsing, validation), use debouncing:
private parseTimeout: NodeJS.Timeout | undefined;
private readonly debounceDelay = 500; // ms
public updateDiagnostics(document: vscode.TextDocument): void {
if (this.parseTimeout) {
clearTimeout(this.parseTimeout);
}
this.parseTimeout = setTimeout(() => {
this.parseDiagnostics(document);
}, this.debounceDelay);
}Why: Avoids excessive parsing while user is typing.
Subscribe to document events in extension.ts:
// Document opened
context.subscriptions.push(
vscode.workspace.onDidOpenTextDocument((doc) => {
if (doc.languageId === 'riddl') {
// Initialize providers for this document
}
})
);
// Document changed
context.subscriptions.push(
vscode.workspace.onDidChangeTextDocument((event) => {
if (event.document.languageId === 'riddl') {
// Update diagnostics, semantic tokens, etc.
}
})
);
// Document closed
context.subscriptions.push(
vscode.workspace.onDidCloseTextDocument((doc) => {
if (doc.languageId === 'riddl') {
// Clear diagnostics, dispose resources
}
})
);Always check languageId === 'riddl' to avoid processing non-RIDDL files.
All commands should handle missing editor and wrong file type:
export function riddlParse(): void {
const editor = vscode.window.activeTextEditor;
if (!editor) {
vscode.window.showErrorMessage('No active editor found. Please open a RIDDL file.');
return;
}
if (editor.document.languageId !== 'riddl') {
vscode.window.showErrorMessage('Current file is not a RIDDL file.');
return;
}
// Proceed with command...
}Map RIDDL message types to VSCode severities:
// Parse errors → Error severity
diagnostic.severity = vscode.DiagnosticSeverity.Error;
diagnostic.source = 'RIDDL (syntax)';
// Validation errors → Error severity
diagnostic.severity = vscode.DiagnosticSeverity.Error;
diagnostic.source = 'RIDDL (validation)';
// Warnings → Warning severity
diagnostic.severity = vscode.DiagnosticSeverity.Warning;
diagnostic.source = 'RIDDL (validation)';
// Info → Information severity
diagnostic.severity = vscode.DiagnosticSeverity.Information;
diagnostic.source = 'RIDDL (info)';Source label distinguishes error types in Problems panel.
# Install dependencies
npm install
# Compile TypeScript
npm run compile
# Watch mode (auto-recompile on changes)
npm run watch
# Run tests
npm run test
# Lint
npm run lint
# Package for distribution
npm run package- Open project in VSCode
- Press
F5to launch Extension Development Host - In the new window, open a
.riddlfile - Test features:
- Syntax highlighting
- Hover over keywords
- Code completion (Ctrl+Space)
- Go to Definition (F12)
- Find References (Shift+F12)
- Commands (Cmd+Shift+P)
- Real-time diagnostics (type errors)
The package is published to GitHub Packages. .npmrc configures
the @ossuminc scope to use npm.pkg.github.com.
When the RIDDL library is updated:
-
Update package.json with new version:
"@ossuminc/riddl-lib": "<new-version>"
-
Reinstall:
npm install
-
Check for API changes in the shipped
index.d.ts -
Test the extension with new library
Three types of documentation:
-
MILESTONE.md* - Technical implementation docs
- How features are implemented
- Code snippets and architecture
- Testing results
- For developers/contributors
-
COMMANDS.md - User-facing command reference
- How to use each command
- Example outputs
- Tips and best practices
- For end users
-
README.md - Project overview
- Feature list
- Installation instructions
- Quick start guide
- For all audiences
When adding features, update all three as appropriate.
Wrong:
vscode.workspace.onDidChangeTextDocument((event) => {
updateDiagnostics(event.document); // Runs on ALL files!
});Correct:
vscode.workspace.onDidChangeTextDocument((event) => {
if (event.document.languageId === 'riddl') {
updateDiagnostics(event.document);
}
});Wrong:
const range = new vscode.Range(error.location.line, error.location.col, ...);
// RIDDL uses 1-based, VSCode uses 0-based!Correct:
const line = Math.max(0, error.location.line - 1);
const col = Math.max(0, error.location.col - 1);
const range = new vscode.Range(line, col, ...);Wrong:
channel.appendLine(error.message); // May contain ANSI codesCorrect:
channel.appendLine(stripAnsiCodes(error.message));Commands → Output Channel Real-time errors → Diagnostics
Don't use diagnostics for command output or output channel for real-time errors.
Always dispose resources when deactivated:
export function deactivate() {
disposeCommands();
// Dispose other resources
}- master - Production releases (currently active branch)
- Create feature branches for major new features
- Commit completed milestones to master
Follow this format:
Short description (imperative mood)
Detailed explanation of what changed and why.
Focus on "why" rather than "what".
Features:
- Feature 1
- Feature 2
Fixes:
- Fix 1
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Include:
- Source files (
src/*.ts) - Configuration (
package.json,tsconfig.json) - Documentation (
*.md) - Tests (
test/**/*.ts) - Syntaxes (
syntaxes/*.json)
Exclude (should be in .gitignore):
node_modules/out/(compiled output)*.vsix(packaged extension).vscode-test/(test artifacts)
parseString(source, origin, verbose):
- Returns:
ParseResult<RootAST>(opaque handle) - Use for: Getting AST; pass to
inspectRoot()orgetDomains()
validateString(source, origin, verbose, noANSIMessages):
- Returns:
ValidationResultwithparseErrors: ErrorInfo[]andvalidationMessages: ValidationMessages(not optional) - Use for: Full validation pipeline
parseToTokens(source, origin, verbose):
- Returns:
ParseResult<Token[]> - Use for: Semantic highlighting
getTree(source, origin):
- Returns:
ParseResult<TreeNode[]>(recursive hierarchy) - Use for: Document symbols, outline view
getOutline(source, origin):
- Returns:
ParseResult<OutlineEntry[]>(flat with depth) - Use for: Flat outline/TOC views
getHandlerCompleteness(source, origin):
- Returns:
ParseResult<HandlerCompletenessEntry[]> - Use for: Handler completeness diagnostics
inspectRoot(root):
- Returns:
RootInfowith domains, location, isEmpty - Use for: Inspecting opaque RootAST from parseString
buildInfo / formatInfo:
- Build metadata object / pre-formatted string
interface ErrorInfo {
kind: 'Error' | 'SevereError' | 'Warning'
| 'MissingWarning' | 'StyleWarning'
| 'UsageWarning' | 'Info';
message: string; // May contain ANSI codes
location: {
line: number; // 1-based line number
col: number; // 1-based column number
offset: number; // Character offset
source: string; // File path or "string"
};
}Each feature should have corresponding tests in test/suite/:
semanticTokens.test.tshover.test.tsdiagnostics.test.tscompletion.test.tsdefinition.test.tsreference.test.ts
suite('Feature Name', () => {
test('Test description', async () => {
// Arrange
const document = await vscode.workspace.openTextDocument({
language: 'riddl',
content: 'domain Test is { ??? }'
});
// Act
const result = await provider.provide(document, position);
// Assert
assert.strictEqual(result.length, expectedCount);
});
});npm run testTests run in a VSCode instance automatically.
- Language ID check is critical - Always check
languageId === 'riddl'in event handlers - Coordinate conversion required - RIDDL uses 1-based, VSCode uses 0-based (subtract 1)
- Strip ANSI codes for display - Use
stripAnsiCodes()for all user-visible output - Debounce expensive operations - 500ms delay for parsing/validation
- Two display mechanisms - Output channel for commands, diagnostics for real-time errors
- Update npm package carefully - Update version in package.json, npm install
- Three documentation types - Technical (MILESTONE*.md), User (COMMANDS.md), Overview (README.md)
- Dispose resources on deactivate - Clean up in
deactivate()function - Test each feature - Add tests to
test/suite/for new features - Source labels distinguish error types - "RIDDL (syntax)" vs "RIDDL (validation)" in Problems panel
- RIDDL Language:
../riddl/- Compiler and language implementation - RIDDL Documentation: https://riddl.ossuminc.com
- VSCode Extension API: https://code.visualstudio.com/api
Potential features for future milestones:
- Workspace-wide symbol search
- Refactoring support (rename symbol, extract definition)
- Snippet generation from templates
- RIDDL file wizards (new domain, new entity, etc.)
- Integration with riddlc translation commands
- Visual diagram generation from RIDDL
- Multi-file project support
- Message flow visualization (using
getMessageFlow()) - Entity lifecycle/state machine view (using
getEntityLifecycles())