Skip to content

Commit b739dce

Browse files
authored
Allow user defined validation categories (#1837)
1 parent d4a60c0 commit b739dce

File tree

3 files changed

+91
-5
lines changed

3 files changed

+91
-5
lines changed

packages/langium/src/test/langium-test.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -556,10 +556,11 @@ export type ValidationHelperOptions = ParseHelperOptions & { failOnParsingErrors
556556
export function validationHelper<T extends AstNode = AstNode>(services: LangiumCoreServices): (input: string, options?: ValidationHelperOptions) => Promise<ValidationResult<T>> {
557557
const parse = parseHelper<T>(services);
558558
return async (input, options) => {
559-
const document = await parse(input, {
559+
const parseOptions: ValidationHelperOptions = {
560560
...(options ?? {}),
561-
validation: true
562-
});
561+
};
562+
parseOptions.validation ??= true;
563+
const document = await parse(input, parseOptions);
563564
const result = {
564565
document,
565566
diagnostics: document.diagnostics ?? [],

packages/langium/src/validation/validation-registry.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,8 @@ export type ValidationChecks<T> = {
9898
}
9999

100100
/**
101+
* There are 3 pre-defined categories: `fast`, `slow` and `built-in`.
102+
*
101103
* `fast` checks can be executed after every document change (i.e. as the user is typing). If a check
102104
* is too slow it can delay the response to document changes, yielding bad user experience. By marking
103105
* it as `slow`, it will be skipped for normal as-you-type validation. Then it's up to you when to
@@ -106,8 +108,13 @@ export type ValidationChecks<T> = {
106108
*
107109
* `built-in` checks are errors produced by the lexer, the parser, or the linker. They cannot be used
108110
* for custom validation checks.
111+
*
112+
* You can also provide user-defined categories. These check will be skipped by default. Then it's up
113+
* to you to schedule these checks: after the fast checks are done, or after saving a document,
114+
* or with an explicit command, etc.
109115
*/
110-
export type ValidationCategory = 'fast' | 'slow' | 'built-in'
116+
// eslint-disable-next-line @typescript-eslint/ban-types
117+
export type ValidationCategory = 'fast' | 'slow' | 'built-in' | (string & {});
111118

112119
export namespace ValidationCategory {
113120
export const all: readonly ValidationCategory[] = ['fast', 'slow', 'built-in'];

packages/langium/test/validation/document-validator.test.ts

Lines changed: 79 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { AstUtils } from 'langium';
88
import type { AstNode, ValidationAcceptor, ValidationChecks } from 'langium';
99
import { createServicesForGrammar } from 'langium/grammar';
1010
import type { LangiumServices } from 'langium/lsp';
11-
import type { ValidationResult } from 'langium/test';
11+
import type { ValidationHelperOptions, ValidationResult } from 'langium/test';
1212
import { validationHelper } from 'langium/test';
1313
import { beforeAll, describe, expect, test } from 'vitest';
1414
import { Position, Range } from 'vscode-languageserver';
@@ -259,4 +259,82 @@ describe('Register Before/AfterDocument logic for validations with state', () =>
259259
});
260260
});
261261

262+
describe('Custom validation categories', () => {
263+
264+
const grammar = `grammar Test
265+
266+
entry Model:
267+
'model' name=ID;
268+
269+
hidden terminal WS: /\\s+/;
270+
terminal ID: /[_a-zA-Z][\\w_]*/;
271+
`;
272+
273+
let validate: (input: string, options?: ValidationHelperOptions) => Promise<ValidationResult<AstNode>>;
274+
275+
beforeAll(async () => {
276+
const services = await createServicesForGrammar({
277+
grammar
278+
});
279+
const validationChecksUser: ValidationChecks<object> = {
280+
AstNode: [
281+
(node, accept) => {
282+
accept('error', 'TEST', { node });
283+
}
284+
]
285+
};
286+
services.validation.ValidationRegistry.register(validationChecksUser, services.validation.ValidationRegistry, 'user');
287+
const validationChecksFast: ValidationChecks<object> = {
288+
AstNode: [
289+
(node, accept) => {
290+
accept('warning', 'TEST', { node });
291+
}
292+
]
293+
};
294+
services.validation.ValidationRegistry.register(validationChecksFast);
295+
296+
validate = validationHelper(services);
297+
});
298+
299+
test('empty categories', async () => {
300+
const validationResult = await validate('model test', {validation:{categories:[]}});
301+
const diagnostics = validationResult.diagnostics;
302+
expect(diagnostics).toHaveLength(0);
303+
expect(diagnostics.filter(d => d.severity === 1)).toHaveLength(0); // 0 error
304+
expect(diagnostics.filter(d => d.severity === 2)).toHaveLength(0); // 0 warning
305+
});
306+
307+
test('fast category', async () => {
308+
const validationResult = await validate('model test', {validation:{categories:['fast']}});
309+
const diagnostics = validationResult.diagnostics;
310+
expect(diagnostics).toHaveLength(1);
311+
expect(diagnostics.filter(d => d.severity === 1)).toHaveLength(0); // 0 error
312+
expect(diagnostics.filter(d => d.severity === 2)).toHaveLength(1); // 1 warning
313+
});
314+
315+
test('user category', async () => {
316+
const validationResult = await validate('model test', {validation:{categories:['user']}});
317+
const diagnostics = validationResult.diagnostics;
318+
expect(diagnostics).toHaveLength(1);
319+
expect(diagnostics.filter(d => d.severity === 1)).toHaveLength(1); // 1 error
320+
expect(diagnostics.filter(d => d.severity === 2)).toHaveLength(0); // 0 warning
321+
});
322+
323+
test('user&fast categories', async () => {
324+
const validationResult = await validate('model test', {validation:{categories:['user', 'fast']}});
325+
const diagnostics = validationResult.diagnostics;
326+
expect(diagnostics).toHaveLength(2);
327+
expect(diagnostics.filter(d => d.severity === 1)).toHaveLength(1); // 1 error
328+
expect(diagnostics.filter(d => d.severity === 2)).toHaveLength(1); // 1 warning
329+
});
330+
331+
test('default categories', async () => {
332+
const validationResult = await validate('model test');
333+
const diagnostics = validationResult.diagnostics;
334+
expect(diagnostics).toHaveLength(2);
335+
expect(diagnostics.filter(d => d.severity === 1)).toHaveLength(1); // 1 error
336+
expect(diagnostics.filter(d => d.severity === 2)).toHaveLength(1); // 1 warning
337+
});
338+
});
339+
262340
});

0 commit comments

Comments
 (0)