Skip to content

Commit 361dec9

Browse files
authored
Add function argument count validation to language service (#31)
* Add function argument count validation to language service Added getDiagnostics() method to the language service API that validates function argument counts and returns LSP diagnostics when functions are called with too few or too many arguments.om>
1 parent c9042c3 commit 361dec9

File tree

9 files changed

+683
-8
lines changed

9 files changed

+683
-8
lines changed

docs/language-service.md

Lines changed: 66 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ The library includes a built-in language service that provides IDE-like features
1212
- **Variable value previews** - Hovers on variables show a truncated JSON preview of the value
1313
- **Nested path support** - Hovering over `user.name` resolves and shows the value at that path
1414
- **Syntax Highlighting** - Token-based highlighting for numbers, strings, keywords, operators, etc.
15+
- **Diagnostics** - Error detection for function argument count validation
16+
- **Too few arguments** - Reports when a function is called with fewer arguments than required (e.g., `pow(2)` needs 2 arguments)
17+
- **Too many arguments** - Reports when a function is called with more arguments than allowed (e.g., `random(1, 2, 3)` accepts at most 1)
18+
- **Variadic functions** - Correctly handles functions that accept unlimited arguments (e.g., `min`, `max`, `coalesce`)
1519

1620
## Basic Usage
1721

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

4044
// Get syntax highlighting tokens
4145
const tokens = ls.getHighlighting(doc);
46+
47+
// Get diagnostics (function argument count errors)
48+
const diagnostics = ls.getDiagnostics({ textDocument: doc });
4249
```
4350

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

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

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

6776
## Advanced Features
6877

@@ -318,6 +327,54 @@ tokens.forEach(token => {
318327
});
319328
```
320329
330+
### ls.getDiagnostics(params)
331+
332+
Returns a list of diagnostics for the given text document. Currently validates function argument counts.
333+
334+
**Parameters:**
335+
- `params`: `GetDiagnosticsParams`
336+
- `textDocument`: `TextDocument` - The text document to analyze
337+
338+
**Returns:** `Diagnostic[]` - Array of LSP-compatible diagnostic objects
339+
340+
**Diagnostic Properties:**
341+
- `range`: `Range` - The range of the problematic function call
342+
- `severity`: `DiagnosticSeverity` - The severity level (Error)
343+
- `message`: `string` - Human-readable description of the issue
344+
- `source`: `string` - Always `'expr-eval'`
345+
346+
**Example:**
347+
```js
348+
const diagnostics = ls.getDiagnostics({ textDocument: doc });
349+
diagnostics.forEach(d => {
350+
console.log(`${d.message} at line ${d.range.start.line}`);
351+
});
352+
353+
// For expression "pow(2) + random(1, 2, 3)":
354+
// "Function 'pow' expects at least 2 arguments, but got 1." at line 0
355+
// "Function 'random' expects at most 1 argument, but got 3." at line 0
356+
```
357+
358+
**Monaco Editor Integration:**
359+
```js
360+
function applyDiagnostics() {
361+
const doc = makeTextDocument(model);
362+
const diagnostics = ls.getDiagnostics({ textDocument: doc });
363+
364+
const markers = diagnostics.map(d => ({
365+
severity: monaco.MarkerSeverity.Error,
366+
message: d.message,
367+
startLineNumber: d.range.start.line + 1,
368+
startColumn: d.range.start.character + 1,
369+
endLineNumber: d.range.end.line + 1,
370+
endColumn: d.range.end.character + 1,
371+
source: d.source
372+
}));
373+
374+
monaco.editor.setModelMarkers(model, 'expr-eval', markers);
375+
}
376+
```
377+
321378
## TypeScript Types
322379
323380
The library exports the following TypeScript types for use in your applications:
@@ -330,17 +387,21 @@ import type {
330387
HoverV2,
331388
GetCompletionsParams,
332389
GetHoverParams,
390+
GetDiagnosticsParams,
333391
HighlightToken,
334-
LanguageServiceOptions
392+
LanguageServiceOptions,
393+
ArityInfo
335394
} from '@pro-fa/expr-eval';
336395
```
337396
338-
- **`LanguageServiceApi`** - The main language service interface with `getCompletions`, `getHover`, and `getHighlighting` methods
397+
- **`LanguageServiceApi`** - The main language service interface with `getCompletions`, `getHover`, `getHighlighting`, and `getDiagnostics` methods
339398
- **`HoverV2`** - Extended Hover type with guaranteed `MarkupContent` for contents (not deprecated string/array formats)
340399
- **`GetCompletionsParams`** - Parameters for `getCompletions`: `textDocument`, `position`, and optional `variables`
341400
- **`GetHoverParams`** - Parameters for `getHover`: `textDocument`, `position`, and optional `variables`
401+
- **`GetDiagnosticsParams`** - Parameters for `getDiagnostics`: `textDocument`
342402
- **`HighlightToken`** - Syntax highlighting token with `type`, `start`, `end`, and optional `value`
343403
- **`LanguageServiceOptions`** - Configuration options for creating a language service, including optional `operators` map
404+
- **`ArityInfo`** - Describes a function's expected argument count with `min` and optional `max` (undefined for variadic functions)
344405
345406
### LSP Types
346407
@@ -354,7 +415,9 @@ import type {
354415
CompletionItemKind,
355416
MarkupContent,
356417
MarkupKind,
357-
InsertTextFormat
418+
InsertTextFormat,
419+
Diagnostic,
420+
DiagnosticSeverity
358421
} from 'vscode-languageserver-types';
359422

360423
import type { TextDocument } from 'vscode-languageserver-textdocument';

index.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,10 @@ export type {
4343
HoverV2,
4444
GetCompletionsParams,
4545
GetHoverParams,
46+
GetDiagnosticsParams,
4647
HighlightToken,
47-
LanguageServiceOptions
48+
LanguageServiceOptions,
49+
ArityInfo
4850
} from './src/language-service/index.js';
4951

5052
export { createLanguageService, Expression, Parser };

samples/language-service-sample/app.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,30 @@ require(['vs/editor/editor.main'], function () {
417417
highlightDecorations = expressionEditor.deltaDecorations(highlightDecorations, decorations);
418418
}
419419

420+
// Diagnostics - show function argument count errors
421+
function applyDiagnostics() {
422+
const doc = makeTextDocument(expressionModel);
423+
const diagnostics = ls.getDiagnostics({ textDocument: doc });
424+
425+
// Convert LSP diagnostics to Monaco markers
426+
const markers = diagnostics.map(d => {
427+
const startPos = fromLspPosition(d.range.start);
428+
const endPos = fromLspPosition(d.range.end);
429+
return {
430+
severity: monaco.MarkerSeverity.Error,
431+
message: d.message,
432+
startLineNumber: startPos.lineNumber,
433+
startColumn: startPos.column,
434+
endLineNumber: endPos.lineNumber,
435+
endColumn: endPos.column,
436+
source: d.source || 'expr-eval'
437+
};
438+
});
439+
440+
// Set markers on the model
441+
monaco.editor.setModelMarkers(expressionModel, 'expr-eval', markers);
442+
}
443+
420444
// Syntax highlight JSON
421445
function syntaxHighlightJson(json) {
422446
if (typeof json !== 'string') {
@@ -599,6 +623,7 @@ require(['vs/editor/editor.main'], function () {
599623
// Event listeners for changes
600624
expressionModel.onDidChangeContent(() => {
601625
applyHighlighting();
626+
applyDiagnostics();
602627
evaluate();
603628
});
604629

samples/language-service-sample/examples.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,5 +93,15 @@ const exampleCases = [
9393
{"rowId": 3, "state": "unchanged", "data": { "InventoryId": 9362, "Description": "Wood", "Weight": { "Unit": "kg", "Amount": 18 } }}
9494
]
9595
}
96+
},
97+
{
98+
id: 'diagnostics-demo',
99+
title: 'Diagnostics Demo',
100+
description: 'Shows error highlighting for incorrect function arguments',
101+
expression: '// Try functions with wrong argument counts:\n// pow() needs 2 args, random() needs 0-1 args\npow(2) + random(1, 2, 3)',
102+
context: {
103+
x: 5,
104+
y: 10
105+
}
96106
}
97107
];

0 commit comments

Comments
 (0)