Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 66 additions & 3 deletions docs/language-service.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ The library includes a built-in language service that provides IDE-like features
- **Variable value previews** - Hovers on variables show a truncated JSON preview of the value
- **Nested path support** - Hovering over `user.name` resolves and shows the value at that path
- **Syntax Highlighting** - Token-based highlighting for numbers, strings, keywords, operators, etc.
- **Diagnostics** - Error detection for function argument count validation
- **Too few arguments** - Reports when a function is called with fewer arguments than required (e.g., `pow(2)` needs 2 arguments)
- **Too many arguments** - Reports when a function is called with more arguments than allowed (e.g., `random(1, 2, 3)` accepts at most 1)
- **Variadic functions** - Correctly handles functions that accept unlimited arguments (e.g., `min`, `max`, `coalesce`)

## Basic Usage

Expand Down Expand Up @@ -39,6 +43,9 @@ const hover = ls.getHover({

// Get syntax highlighting tokens
const tokens = ls.getHighlighting(doc);

// Get diagnostics (function argument count errors)
const diagnostics = ls.getDiagnostics({ textDocument: doc });
```

## Monaco Editor Integration Sample
Expand All @@ -56,13 +63,15 @@ Then open http://localhost:8080 in your browser. The sample demonstrates:
- Hover documentation for functions and variables
- Live syntax highlighting
- Real-time expression evaluation
- **Diagnostics** - Red squiggly underlines for function argument count errors (select the "Diagnostics Demo" example to see this in action)

The sample code is located in `samples/language-service-sample/` and shows how to:

1. Register a custom language with Monaco
2. Connect the language service to Monaco's completion and hover providers
3. Apply syntax highlighting using decorations
4. Create an LSP-compatible text document wrapper for Monaco models
5. Display diagnostics using Monaco's `setModelMarkers` API

## Advanced Features

Expand Down Expand Up @@ -318,6 +327,54 @@ tokens.forEach(token => {
});
```

### ls.getDiagnostics(params)

Returns a list of diagnostics for the given text document. Currently validates function argument counts.

**Parameters:**
- `params`: `GetDiagnosticsParams`
- `textDocument`: `TextDocument` - The text document to analyze

**Returns:** `Diagnostic[]` - Array of LSP-compatible diagnostic objects

**Diagnostic Properties:**
- `range`: `Range` - The range of the problematic function call
- `severity`: `DiagnosticSeverity` - The severity level (Error)
- `message`: `string` - Human-readable description of the issue
- `source`: `string` - Always `'expr-eval'`

**Example:**
```js
const diagnostics = ls.getDiagnostics({ textDocument: doc });
diagnostics.forEach(d => {
console.log(`${d.message} at line ${d.range.start.line}`);
});

// For expression "pow(2) + random(1, 2, 3)":
// "Function 'pow' expects at least 2 arguments, but got 1." at line 0
// "Function 'random' expects at most 1 argument, but got 3." at line 0
```

**Monaco Editor Integration:**
```js
function applyDiagnostics() {
const doc = makeTextDocument(model);
const diagnostics = ls.getDiagnostics({ textDocument: doc });

const markers = diagnostics.map(d => ({
severity: monaco.MarkerSeverity.Error,
message: d.message,
startLineNumber: d.range.start.line + 1,
startColumn: d.range.start.character + 1,
endLineNumber: d.range.end.line + 1,
endColumn: d.range.end.character + 1,
source: d.source
}));

monaco.editor.setModelMarkers(model, 'expr-eval', markers);
}
```

## TypeScript Types

The library exports the following TypeScript types for use in your applications:
Expand All @@ -330,17 +387,21 @@ import type {
HoverV2,
GetCompletionsParams,
GetHoverParams,
GetDiagnosticsParams,
HighlightToken,
LanguageServiceOptions
LanguageServiceOptions,
ArityInfo
} from '@pro-fa/expr-eval';
```

- **`LanguageServiceApi`** - The main language service interface with `getCompletions`, `getHover`, and `getHighlighting` methods
- **`LanguageServiceApi`** - The main language service interface with `getCompletions`, `getHover`, `getHighlighting`, and `getDiagnostics` methods
- **`HoverV2`** - Extended Hover type with guaranteed `MarkupContent` for contents (not deprecated string/array formats)
- **`GetCompletionsParams`** - Parameters for `getCompletions`: `textDocument`, `position`, and optional `variables`
- **`GetHoverParams`** - Parameters for `getHover`: `textDocument`, `position`, and optional `variables`
- **`GetDiagnosticsParams`** - Parameters for `getDiagnostics`: `textDocument`
- **`HighlightToken`** - Syntax highlighting token with `type`, `start`, `end`, and optional `value`
- **`LanguageServiceOptions`** - Configuration options for creating a language service, including optional `operators` map
- **`ArityInfo`** - Describes a function's expected argument count with `min` and optional `max` (undefined for variadic functions)

### LSP Types

Expand All @@ -354,7 +415,9 @@ import type {
CompletionItemKind,
MarkupContent,
MarkupKind,
InsertTextFormat
InsertTextFormat,
Diagnostic,
DiagnosticSeverity
} from 'vscode-languageserver-types';

import type { TextDocument } from 'vscode-languageserver-textdocument';
Expand Down
4 changes: 3 additions & 1 deletion index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,10 @@ export type {
HoverV2,
GetCompletionsParams,
GetHoverParams,
GetDiagnosticsParams,
HighlightToken,
LanguageServiceOptions
LanguageServiceOptions,
ArityInfo
} from './src/language-service/index.js';

export { createLanguageService, Expression, Parser };
25 changes: 25 additions & 0 deletions samples/language-service-sample/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,30 @@ require(['vs/editor/editor.main'], function () {
highlightDecorations = expressionEditor.deltaDecorations(highlightDecorations, decorations);
}

// Diagnostics - show function argument count errors
function applyDiagnostics() {
const doc = makeTextDocument(expressionModel);
const diagnostics = ls.getDiagnostics({ textDocument: doc });

// Convert LSP diagnostics to Monaco markers
const markers = diagnostics.map(d => {
const startPos = fromLspPosition(d.range.start);
const endPos = fromLspPosition(d.range.end);
return {
severity: monaco.MarkerSeverity.Error,
message: d.message,
startLineNumber: startPos.lineNumber,
startColumn: startPos.column,
endLineNumber: endPos.lineNumber,
endColumn: endPos.column,
source: d.source || 'expr-eval'
};
});

// Set markers on the model
monaco.editor.setModelMarkers(expressionModel, 'expr-eval', markers);
}

// Syntax highlight JSON
function syntaxHighlightJson(json) {
if (typeof json !== 'string') {
Expand Down Expand Up @@ -599,6 +623,7 @@ require(['vs/editor/editor.main'], function () {
// Event listeners for changes
expressionModel.onDidChangeContent(() => {
applyHighlighting();
applyDiagnostics();
evaluate();
});

Expand Down
10 changes: 10 additions & 0 deletions samples/language-service-sample/examples.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,5 +93,15 @@ const exampleCases = [
{"rowId": 3, "state": "unchanged", "data": { "InventoryId": 9362, "Description": "Wood", "Weight": { "Unit": "kg", "Amount": 18 } }}
]
}
},
{
id: 'diagnostics-demo',
title: 'Diagnostics Demo',
description: 'Shows error highlighting for incorrect function arguments',
expression: '// Try functions with wrong argument counts:\n// pow() needs 2 args, random() needs 0-1 args\npow(2) + random(1, 2, 3)',
context: {
x: 5,
y: 10
}
}
];
Loading