+88
@@ -0,0 +1,88 @@
|
||||
/******************************************************************************
|
||||
* Copyright 2021 TypeFox GmbH
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the MIT License, which is available in the project root.
|
||||
******************************************************************************/
|
||||
import type { DiagnosticSeverity, Range, Diagnostic } from 'vscode-languageserver-types';
|
||||
import type { LanguageMetaData } from '../languages/language-meta-data.js';
|
||||
import type { ParseResult } from '../parser/langium-parser.js';
|
||||
import type { LangiumCoreServices } from '../services.js';
|
||||
import type { AstNode } from '../syntax-tree.js';
|
||||
import type { LangiumDocument } from '../workspace/documents.js';
|
||||
import type { DiagnosticData, DiagnosticInfo, ValidationAcceptor, ValidationCategory, ValidationRegistry, ValidationSeverity } from './validation-registry.js';
|
||||
import { CancellationToken } from '../utils/cancellation.js';
|
||||
import type { LexingDiagnosticSeverity } from '../parser/token-builder.js';
|
||||
import type { LangiumProfiler } from '../workspace/profiler.js';
|
||||
export interface ValidationOptions {
|
||||
/**
|
||||
* If this is set, only the checks associated with these categories are executed; otherwise
|
||||
* all checks are executed. The default category if not specified to the registry is `'fast'`.
|
||||
*/
|
||||
categories?: ValidationCategory[];
|
||||
/** If true, no further diagnostics are reported if there are lexing errors. */
|
||||
stopAfterLexingErrors?: boolean;
|
||||
/** If true, no further diagnostics are reported if there are parsing errors. Lexing errors are reported first. */
|
||||
stopAfterParsingErrors?: boolean;
|
||||
/** If true, no further diagnostics are reported if there are linking errors. Lexing and parsing errors are reported first. */
|
||||
stopAfterLinkingErrors?: boolean;
|
||||
}
|
||||
/**
|
||||
* Language-specific service for validating `LangiumDocument`s.
|
||||
*/
|
||||
export interface DocumentValidator {
|
||||
/**
|
||||
* Validates the whole specified document.
|
||||
*
|
||||
* @param document specified document to validate
|
||||
* @param options options to control the validation process
|
||||
* @param cancelToken allows to cancel the current operation
|
||||
* @throws `OperationCanceled` if a user action occurs during execution
|
||||
*/
|
||||
validateDocument(document: LangiumDocument, options?: ValidationOptions, cancelToken?: CancellationToken): Promise<Diagnostic[]>;
|
||||
}
|
||||
export interface ValidateSingleNodeOptions {
|
||||
validateNode: boolean;
|
||||
validateChildren: boolean;
|
||||
}
|
||||
export declare const VALIDATE_EACH_NODE: ValidateSingleNodeOptions;
|
||||
export declare class DefaultDocumentValidator implements DocumentValidator {
|
||||
protected readonly validationRegistry: ValidationRegistry;
|
||||
protected readonly metadata: LanguageMetaData;
|
||||
protected readonly profiler: LangiumProfiler | undefined;
|
||||
protected readonly languageId: string;
|
||||
constructor(services: LangiumCoreServices);
|
||||
validateDocument(document: LangiumDocument, options?: ValidationOptions, cancelToken?: CancellationToken): Promise<Diagnostic[]>;
|
||||
protected processLexingErrors(parseResult: ParseResult, diagnostics: Diagnostic[], _options: ValidationOptions): void;
|
||||
protected processParsingErrors(parseResult: ParseResult, diagnostics: Diagnostic[], _options: ValidationOptions): void;
|
||||
protected processLinkingErrors(document: LangiumDocument, diagnostics: Diagnostic[], _options: ValidationOptions): void;
|
||||
protected validateAst(rootNode: AstNode, options: ValidationOptions, cancelToken?: CancellationToken): Promise<Diagnostic[]>;
|
||||
protected validateAstBefore(rootNode: AstNode, options: ValidationOptions, acceptor: ValidationAcceptor, cancelToken?: CancellationToken): Promise<void>;
|
||||
protected validateAstNodes(rootNode: AstNode, options: ValidationOptions, acceptor: ValidationAcceptor, cancelToken?: CancellationToken): Promise<void>;
|
||||
protected validateSingleNodeOptions(_node: AstNode, _options: ValidationOptions): ValidateSingleNodeOptions;
|
||||
protected validateAstAfter(rootNode: AstNode, options: ValidationOptions, acceptor: ValidationAcceptor, cancelToken?: CancellationToken): Promise<void>;
|
||||
protected toDiagnostic<N extends AstNode>(severity: ValidationSeverity, message: string, info: DiagnosticInfo<N, string>): Diagnostic;
|
||||
protected getSource(): string | undefined;
|
||||
}
|
||||
export declare function getDiagnosticRange<N extends AstNode>(info: DiagnosticInfo<N, string>): Range;
|
||||
/**
|
||||
* Transforms the diagnostic severity from the {@link LexingDiagnosticSeverity} format to LSP's `DiagnosticSeverity` format.
|
||||
*
|
||||
* @param severity The lexing diagnostic severity
|
||||
* @returns Diagnostic severity according to `vscode-languageserver-types/lib/esm/main.js#DiagnosticSeverity`
|
||||
*/
|
||||
export declare function toDiagnosticSeverity(severity: LexingDiagnosticSeverity): DiagnosticSeverity;
|
||||
export declare function toDiagnosticData(severity: LexingDiagnosticSeverity): DiagnosticData;
|
||||
export declare namespace DocumentValidator {
|
||||
const LexingError = "lexing-error";
|
||||
const LexingWarning = "lexing-warning";
|
||||
const LexingInfo = "lexing-info";
|
||||
const LexingHint = "lexing-hint";
|
||||
const ParsingError = "parsing-error";
|
||||
const LinkingError = "linking-error";
|
||||
}
|
||||
export interface LinkingErrorData extends DiagnosticData {
|
||||
containerType: string;
|
||||
property: string;
|
||||
refText: string;
|
||||
}
|
||||
//# sourceMappingURL=document-validator.d.ts.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"document-validator.d.ts","sourceRoot":"","sources":["../../src/validation/document-validator.ts"],"names":[],"mappings":"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"}
|
||||
+288
@@ -0,0 +1,288 @@
|
||||
/******************************************************************************
|
||||
* Copyright 2021 TypeFox GmbH
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the MIT License, which is available in the project root.
|
||||
******************************************************************************/
|
||||
import { CancellationToken } from '../utils/cancellation.js';
|
||||
import { findNodeForKeyword, findNodeForProperty } from '../utils/grammar-utils.js';
|
||||
import { streamAst } from '../utils/ast-utils.js';
|
||||
import { tokenToRange } from '../utils/cst-utils.js';
|
||||
import { interruptAndCheck, isOperationCancelled } from '../utils/promise-utils.js';
|
||||
import { diagnosticData } from './validation-registry.js';
|
||||
export const VALIDATE_EACH_NODE = Object.freeze({
|
||||
validateNode: true,
|
||||
validateChildren: true,
|
||||
});
|
||||
export class DefaultDocumentValidator {
|
||||
constructor(services) {
|
||||
this.validationRegistry = services.validation.ValidationRegistry;
|
||||
this.metadata = services.LanguageMetaData;
|
||||
this.profiler = services.shared.profilers.LangiumProfiler;
|
||||
this.languageId = services.LanguageMetaData.languageId;
|
||||
}
|
||||
async validateDocument(document, options = {}, cancelToken = CancellationToken.None) {
|
||||
const parseResult = document.parseResult;
|
||||
const diagnostics = [];
|
||||
await interruptAndCheck(cancelToken);
|
||||
if (!options.categories || options.categories.includes('built-in')) {
|
||||
this.processLexingErrors(parseResult, diagnostics, options);
|
||||
if (options.stopAfterLexingErrors && diagnostics.some(d => d.data?.code === DocumentValidator.LexingError)) {
|
||||
return diagnostics;
|
||||
}
|
||||
this.processParsingErrors(parseResult, diagnostics, options);
|
||||
if (options.stopAfterParsingErrors && diagnostics.some(d => d.data?.code === DocumentValidator.ParsingError)) {
|
||||
return diagnostics;
|
||||
}
|
||||
this.processLinkingErrors(document, diagnostics, options);
|
||||
if (options.stopAfterLinkingErrors && diagnostics.some(d => d.data?.code === DocumentValidator.LinkingError)) {
|
||||
return diagnostics;
|
||||
}
|
||||
}
|
||||
// Process custom validations
|
||||
try {
|
||||
diagnostics.push(...await this.validateAst(parseResult.value, options, cancelToken));
|
||||
}
|
||||
catch (err) {
|
||||
if (isOperationCancelled(err)) {
|
||||
throw err;
|
||||
}
|
||||
console.error('An error occurred during validation:', err);
|
||||
}
|
||||
await interruptAndCheck(cancelToken);
|
||||
return diagnostics;
|
||||
}
|
||||
processLexingErrors(parseResult, diagnostics, _options) {
|
||||
const lexerDiagnostics = [...parseResult.lexerErrors, ...parseResult.lexerReport?.diagnostics ?? []];
|
||||
for (const lexerDiagnostic of lexerDiagnostics) {
|
||||
const severity = lexerDiagnostic.severity ?? 'error';
|
||||
const diagnostic = {
|
||||
severity: toDiagnosticSeverity(severity),
|
||||
range: {
|
||||
start: {
|
||||
line: lexerDiagnostic.line - 1,
|
||||
character: lexerDiagnostic.column - 1
|
||||
},
|
||||
end: {
|
||||
line: lexerDiagnostic.line - 1,
|
||||
character: lexerDiagnostic.column + lexerDiagnostic.length - 1
|
||||
}
|
||||
},
|
||||
message: lexerDiagnostic.message,
|
||||
data: toDiagnosticData(severity),
|
||||
source: this.getSource()
|
||||
};
|
||||
diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
processParsingErrors(parseResult, diagnostics, _options) {
|
||||
for (const parserError of parseResult.parserErrors) {
|
||||
let range = undefined;
|
||||
// We can run into the chevrotain error recovery here
|
||||
// The token contained in the parser error might be automatically inserted
|
||||
// In this case every position value will be `NaN`
|
||||
if (isNaN(parserError.token.startOffset)) {
|
||||
// Some special parser error types contain a `previousToken`
|
||||
// We can simply append our diagnostic to that token
|
||||
if ('previousToken' in parserError) {
|
||||
const token = parserError.previousToken;
|
||||
if (!isNaN(token.startOffset)) {
|
||||
const position = { line: token.endLine - 1, character: token.endColumn };
|
||||
range = { start: position, end: position };
|
||||
}
|
||||
else {
|
||||
// No valid prev token. Might be empty document or containing only hidden tokens.
|
||||
// Point to document start
|
||||
const position = { line: 0, character: 0 };
|
||||
range = { start: position, end: position };
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
range = tokenToRange(parserError.token);
|
||||
}
|
||||
if (range) {
|
||||
const diagnostic = {
|
||||
severity: toDiagnosticSeverity('error'),
|
||||
range,
|
||||
message: parserError.message,
|
||||
data: diagnosticData(DocumentValidator.ParsingError),
|
||||
source: this.getSource()
|
||||
};
|
||||
diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
}
|
||||
processLinkingErrors(document, diagnostics, _options) {
|
||||
for (const reference of document.references) {
|
||||
const linkingError = reference.error;
|
||||
if (linkingError) {
|
||||
const info = {
|
||||
node: linkingError.info.container,
|
||||
range: reference.$refNode?.range,
|
||||
property: linkingError.info.property,
|
||||
index: linkingError.info.index,
|
||||
data: {
|
||||
code: DocumentValidator.LinkingError,
|
||||
containerType: linkingError.info.container.$type,
|
||||
property: linkingError.info.property,
|
||||
refText: linkingError.info.reference.$refText
|
||||
}
|
||||
};
|
||||
diagnostics.push(this.toDiagnostic('error', linkingError.message, info));
|
||||
}
|
||||
}
|
||||
}
|
||||
async validateAst(rootNode, options, cancelToken = CancellationToken.None) {
|
||||
const validationItems = [];
|
||||
const acceptor = (severity, message, info) => {
|
||||
validationItems.push(this.toDiagnostic(severity, message, info));
|
||||
};
|
||||
await this.validateAstBefore(rootNode, options, acceptor, cancelToken);
|
||||
await this.validateAstNodes(rootNode, options, acceptor, cancelToken);
|
||||
await this.validateAstAfter(rootNode, options, acceptor, cancelToken);
|
||||
return validationItems;
|
||||
}
|
||||
async validateAstBefore(rootNode, options, acceptor, cancelToken = CancellationToken.None) {
|
||||
const checksBefore = this.validationRegistry.checksBefore;
|
||||
for (const checkBefore of checksBefore) {
|
||||
await interruptAndCheck(cancelToken);
|
||||
await checkBefore(rootNode, acceptor, options.categories ?? [], cancelToken);
|
||||
}
|
||||
}
|
||||
async validateAstNodes(rootNode, options, acceptor, cancelToken = CancellationToken.None) {
|
||||
if (this.profiler?.isActive('validating')) {
|
||||
const task = this.profiler.createTask('validating', this.languageId);
|
||||
task.start();
|
||||
try {
|
||||
const nodes = streamAst(rootNode).iterator();
|
||||
for (const node of nodes) {
|
||||
task.startSubTask(node.$type);
|
||||
const nodeOptions = this.validateSingleNodeOptions(node, options);
|
||||
if (nodeOptions.validateNode) {
|
||||
try {
|
||||
const checks = this.validationRegistry.getChecks(node.$type, options.categories);
|
||||
for (const check of checks) {
|
||||
await check(node, acceptor, cancelToken);
|
||||
}
|
||||
}
|
||||
finally {
|
||||
task.stopSubTask(node.$type);
|
||||
}
|
||||
}
|
||||
if (!nodeOptions.validateChildren) {
|
||||
nodes.prune();
|
||||
}
|
||||
}
|
||||
}
|
||||
finally {
|
||||
task.stop();
|
||||
}
|
||||
}
|
||||
else {
|
||||
const nodes = streamAst(rootNode).iterator();
|
||||
for (const node of nodes) {
|
||||
await interruptAndCheck(cancelToken);
|
||||
const nodeOptions = this.validateSingleNodeOptions(node, options);
|
||||
if (nodeOptions.validateNode) {
|
||||
const checks = this.validationRegistry.getChecks(node.$type, options.categories);
|
||||
for (const check of checks) {
|
||||
await check(node, acceptor, cancelToken);
|
||||
}
|
||||
}
|
||||
if (!nodeOptions.validateChildren) {
|
||||
nodes.prune();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
validateSingleNodeOptions(_node, _options) {
|
||||
return VALIDATE_EACH_NODE;
|
||||
}
|
||||
async validateAstAfter(rootNode, options, acceptor, cancelToken = CancellationToken.None) {
|
||||
const checksAfter = this.validationRegistry.checksAfter;
|
||||
for (const checkAfter of checksAfter) {
|
||||
await interruptAndCheck(cancelToken);
|
||||
await checkAfter(rootNode, acceptor, options.categories ?? [], cancelToken);
|
||||
}
|
||||
}
|
||||
toDiagnostic(severity, message, info) {
|
||||
return {
|
||||
message,
|
||||
range: getDiagnosticRange(info),
|
||||
severity: toDiagnosticSeverity(severity),
|
||||
code: info.code,
|
||||
codeDescription: info.codeDescription,
|
||||
tags: info.tags,
|
||||
relatedInformation: info.relatedInformation,
|
||||
data: info.data,
|
||||
source: this.getSource()
|
||||
};
|
||||
}
|
||||
getSource() {
|
||||
return this.metadata.languageId;
|
||||
}
|
||||
}
|
||||
export function getDiagnosticRange(info) {
|
||||
if (info.range) {
|
||||
return info.range;
|
||||
}
|
||||
let cstNode;
|
||||
if (typeof info.property === 'string') {
|
||||
cstNode = findNodeForProperty(info.node.$cstNode, info.property, info.index);
|
||||
}
|
||||
else if (typeof info.keyword === 'string') {
|
||||
cstNode = findNodeForKeyword(info.node.$cstNode, info.keyword, info.index);
|
||||
}
|
||||
cstNode ?? (cstNode = info.node.$cstNode);
|
||||
if (!cstNode) {
|
||||
return {
|
||||
start: { line: 0, character: 0 },
|
||||
end: { line: 0, character: 0 }
|
||||
};
|
||||
}
|
||||
return cstNode.range;
|
||||
}
|
||||
/**
|
||||
* Transforms the diagnostic severity from the {@link LexingDiagnosticSeverity} format to LSP's `DiagnosticSeverity` format.
|
||||
*
|
||||
* @param severity The lexing diagnostic severity
|
||||
* @returns Diagnostic severity according to `vscode-languageserver-types/lib/esm/main.js#DiagnosticSeverity`
|
||||
*/
|
||||
export function toDiagnosticSeverity(severity) {
|
||||
switch (severity) {
|
||||
case 'error':
|
||||
return 1;
|
||||
case 'warning':
|
||||
return 2;
|
||||
case 'info':
|
||||
return 3;
|
||||
case 'hint':
|
||||
return 4;
|
||||
default:
|
||||
throw new Error('Invalid diagnostic severity: ' + severity);
|
||||
}
|
||||
}
|
||||
export function toDiagnosticData(severity) {
|
||||
switch (severity) {
|
||||
case 'error':
|
||||
return diagnosticData(DocumentValidator.LexingError);
|
||||
case 'warning':
|
||||
return diagnosticData(DocumentValidator.LexingWarning);
|
||||
case 'info':
|
||||
return diagnosticData(DocumentValidator.LexingInfo);
|
||||
case 'hint':
|
||||
return diagnosticData(DocumentValidator.LexingHint);
|
||||
default:
|
||||
throw new Error('Invalid diagnostic severity: ' + severity);
|
||||
}
|
||||
}
|
||||
export var DocumentValidator;
|
||||
(function (DocumentValidator) {
|
||||
DocumentValidator.LexingError = 'lexing-error';
|
||||
DocumentValidator.LexingWarning = 'lexing-warning';
|
||||
DocumentValidator.LexingInfo = 'lexing-info';
|
||||
DocumentValidator.LexingHint = 'lexing-hint';
|
||||
DocumentValidator.ParsingError = 'parsing-error';
|
||||
DocumentValidator.LinkingError = 'linking-error';
|
||||
})(DocumentValidator || (DocumentValidator = {}));
|
||||
//# sourceMappingURL=document-validator.js.map
|
||||
+1
File diff suppressed because one or more lines are too long
+8
@@ -0,0 +1,8 @@
|
||||
/******************************************************************************
|
||||
* Copyright 2023 TypeFox GmbH
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the MIT License, which is available in the project root.
|
||||
******************************************************************************/
|
||||
export * from './document-validator.js';
|
||||
export * from './validation-registry.js';
|
||||
//# sourceMappingURL=index.d.ts.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/validation/index.ts"],"names":[],"mappings":"AAAA;;;;gFAIgF;AAEhF,cAAc,yBAAyB,CAAC;AACxC,cAAc,0BAA0B,CAAC"}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
/******************************************************************************
|
||||
* Copyright 2023 TypeFox GmbH
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the MIT License, which is available in the project root.
|
||||
******************************************************************************/
|
||||
export * from './document-validator.js';
|
||||
export * from './validation-registry.js';
|
||||
//# sourceMappingURL=index.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/validation/index.ts"],"names":[],"mappings":"AAAA;;;;gFAIgF;AAEhF,cAAc,yBAAyB,CAAC;AACxC,cAAc,0BAA0B,CAAC"}
|
||||
+177
@@ -0,0 +1,177 @@
|
||||
/******************************************************************************
|
||||
* Copyright 2021 TypeFox GmbH
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the MIT License, which is available in the project root.
|
||||
******************************************************************************/
|
||||
import type { CodeDescription, DiagnosticRelatedInformation, DiagnosticTag, integer, Range } from 'vscode-languageserver-types';
|
||||
import type { LangiumCoreServices } from '../services.js';
|
||||
import type { AstNode, AstReflection, Properties } from '../syntax-tree.js';
|
||||
import type { CancellationToken } from '../utils/cancellation.js';
|
||||
import { MultiMap } from '../utils/collections.js';
|
||||
import type { MaybePromise } from '../utils/promise-utils.js';
|
||||
import type { Stream } from '../utils/stream.js';
|
||||
import type { DocumentSegment, LangiumDocument } from '../workspace/documents.js';
|
||||
export type DiagnosticInfo<N extends AstNode, P extends string = Properties<N>> = {
|
||||
/** The AST node to which the diagnostic is attached. */
|
||||
node: N;
|
||||
/** If a property name is given, the diagnostic is restricted to the corresponding text region. */
|
||||
property?: P;
|
||||
/** If the value of a keyword is given, the diagnostic will appear at its corresponding text region */
|
||||
keyword?: string;
|
||||
/** In case of a multi-value property (array), an index can be given to select a specific element. */
|
||||
index?: number;
|
||||
/** If you want to create a diagnostic independent to any property, use the range property. */
|
||||
range?: Range;
|
||||
/** The diagnostic's code, which usually appear in the user interface. */
|
||||
code?: integer | string;
|
||||
/** An optional property to describe the error code. */
|
||||
codeDescription?: CodeDescription;
|
||||
/** Additional metadata about the diagnostic. */
|
||||
tags?: DiagnosticTag[];
|
||||
/** An array of related diagnostic information, e.g. when symbol-names within a scope collide all definitions can be marked via this property. */
|
||||
relatedInformation?: DiagnosticRelatedInformation[];
|
||||
/** A data entry field that is preserved between a `textDocument/publishDiagnostics` notification and `textDocument/codeAction` request. */
|
||||
data?: unknown;
|
||||
};
|
||||
/**
|
||||
* Shape of information commonly used in the `data` field of diagnostics.
|
||||
*/
|
||||
export interface DiagnosticData {
|
||||
/** Diagnostic code for identifying which code action to apply. This code is _not_ shown in the user interface. */
|
||||
code: string;
|
||||
/** Specifies where to apply the code action in the form of a `DocumentSegment`. */
|
||||
actionSegment?: DocumentSegment;
|
||||
/** Specifies where to apply the code action in the form of a `Range`. */
|
||||
actionRange?: Range;
|
||||
}
|
||||
/**
|
||||
* Create DiagnosticData for a given diagnostic code. The result can be put into the `data` field of a DiagnosticInfo.
|
||||
*/
|
||||
export declare function diagnosticData(code: string): DiagnosticData;
|
||||
export type ValidationSeverity = 'error' | 'warning' | 'info' | 'hint';
|
||||
export type ValidationAcceptor = <N extends AstNode>(severity: ValidationSeverity, message: string, info: DiagnosticInfo<N>) => void;
|
||||
export type ValidationCheck<T extends AstNode = AstNode> = (node: T, accept: ValidationAcceptor, cancelToken: CancellationToken) => MaybePromise<void>;
|
||||
/**
|
||||
* A utility type for describing functions which will be called once before or after all the AstNodes of an AST/Langium document are validated.
|
||||
*
|
||||
* The AST is represented by its root AstNode.
|
||||
*
|
||||
* The given validation acceptor helps to report some early or lately detected issues.
|
||||
*
|
||||
* The 'categories' indicate, which validation categories are executed for all the AstNodes.
|
||||
* This helps to tailor the preparations/tear-down logic to the actually executed checks on the nodes.
|
||||
*
|
||||
* It is recommended to support interrupts during long-running logic with 'interruptAndCheck(cancelToken)'.
|
||||
*/
|
||||
export type ValidationPreparation = (rootNode: AstNode, accept: ValidationAcceptor, categories: ValidationCategory[], cancelToken: CancellationToken) => MaybePromise<void>;
|
||||
/**
|
||||
* A utility type for associating non-primitive AST types to corresponding validation checks. For example:
|
||||
*
|
||||
* ```ts
|
||||
* const checks: ValidationChecks<StatemachineAstType> = {
|
||||
* State: validator.checkStateNameStartsWithCapital
|
||||
* };
|
||||
* ```
|
||||
*
|
||||
* If an AST type does not extend AstNode, e.g. if it describes a union of string literals, that type's name must not occur as a key in objects of type `ValidationCheck<...>`.
|
||||
*
|
||||
* @param T a type definition mapping language specific type names (keys) to the corresponding types (values)
|
||||
*/
|
||||
export type ValidationChecks<T> = {
|
||||
[K in keyof T]?: T[K] extends AstNode ? ValidationCheck<T[K]> | Array<ValidationCheck<T[K]>> : never;
|
||||
} & {
|
||||
AstNode?: ValidationCheck<AstNode> | Array<ValidationCheck<AstNode>>;
|
||||
};
|
||||
/**
|
||||
* There are 3 pre-defined categories: `fast`, `slow` and `built-in`.
|
||||
*
|
||||
* `fast` checks can be executed after every document change (i.e. as the user is typing). If a check
|
||||
* is too slow it can delay the response to document changes, yielding bad user experience. By marking
|
||||
* it as `slow`, it will be skipped for normal as-you-type validation. Then it's up to you when to
|
||||
* schedule these long-running checks: after the fast checks are done, or after saving a document,
|
||||
* or with an explicit command, etc.
|
||||
*
|
||||
* `built-in` checks are errors produced by the lexer, the parser, or the linker. They cannot be used
|
||||
* for custom validation checks.
|
||||
*
|
||||
* You can also provide user-defined categories. These check will be skipped by default. Then it's up
|
||||
* to you to schedule these checks: after the fast checks are done, or after saving a document,
|
||||
* or with an explicit command, etc.
|
||||
*/
|
||||
export type ValidationCategory = 'fast' | 'slow' | 'built-in' | (string & {});
|
||||
export declare namespace ValidationCategory {
|
||||
const defaults: readonly ValidationCategory[];
|
||||
/**
|
||||
* @deprecated since 4.2 Use `ValidationCategory.defaults` instead,
|
||||
* since "all" does not include user-defined, custom validation categories.
|
||||
*/
|
||||
const all: readonly ValidationCategory[];
|
||||
}
|
||||
type ValidationCheckEntry = {
|
||||
check: ValidationCheck;
|
||||
category: ValidationCategory;
|
||||
};
|
||||
/**
|
||||
* Manages a set of `ValidationCheck`s to be applied when documents are validated.
|
||||
*/
|
||||
export declare class ValidationRegistry {
|
||||
protected readonly entries: MultiMap<string, ValidationCheckEntry>;
|
||||
protected readonly knownCategories: Set<ValidationCategory>;
|
||||
protected readonly reflection: AstReflection;
|
||||
protected entriesBefore: ValidationPreparation[];
|
||||
protected entriesAfter: ValidationPreparation[];
|
||||
constructor(services: LangiumCoreServices);
|
||||
/**
|
||||
* Register a set of validation checks. Each value in the record can be either a single validation check (i.e. a function)
|
||||
* or an array of validation checks.
|
||||
*
|
||||
* @param checksRecord Set of validation checks to register.
|
||||
* @param thisObj Optional object to be used as `this` when calling the validation check functions.
|
||||
* @param category Optional category for the validation checks (defaults to `'fast'`).
|
||||
*/
|
||||
register<T>(checksRecord: ValidationChecks<T>, thisObj?: ThisParameterType<unknown>, category?: ValidationCategory): void;
|
||||
protected wrapValidationException(check: ValidationCheck, thisObj: unknown): ValidationCheck;
|
||||
protected handleException(functionality: () => MaybePromise<void>, messageContext: string, accept: ValidationAcceptor, node: AstNode): Promise<void>;
|
||||
protected addEntry(type: string, entry: ValidationCheckEntry): void;
|
||||
getChecks(type: string, categories?: ValidationCategory[]): Stream<ValidationCheck>;
|
||||
/**
|
||||
* Register logic which will be executed once before validating all the nodes of an AST/Langium document.
|
||||
* This helps to prepare or initialize some information which are required or reusable for the following checks on the AstNodes.
|
||||
*
|
||||
* As an example, for validating unique fully-qualified names of nodes in the AST,
|
||||
* here the map for mapping names to nodes could be established.
|
||||
* During the usual checks on the nodes, they are put into this map with their name.
|
||||
*
|
||||
* Note that this approach makes validations stateful, which is relevant e.g. when cancelling the validation.
|
||||
* Therefore it is recommended to clear stored information
|
||||
* _before_ validating an AST to validate each AST unaffected from other ASTs
|
||||
* AND _after_ validating the AST to free memory by information which are no longer used.
|
||||
*
|
||||
* @param checkBefore a set-up function which will be called once before actually validating an AST
|
||||
* @param thisObj Optional object to be used as `this` when calling the validation check functions.
|
||||
*/
|
||||
registerBeforeDocument(checkBefore: ValidationPreparation, thisObj?: ThisParameterType<unknown>): void;
|
||||
/**
|
||||
* Register logic which will be executed once after validating all the nodes of an AST/Langium document.
|
||||
* This helps to finally evaluate information which are collected during the checks on the AstNodes.
|
||||
*
|
||||
* As an example, for validating unique fully-qualified names of nodes in the AST,
|
||||
* here the map with all the collected nodes and their names is checked
|
||||
* and validation hints are created for all nodes with the same name.
|
||||
*
|
||||
* Note that this approach makes validations stateful, which is relevant e.g. when cancelling the validation.
|
||||
* Therefore it is recommended to clear stored information
|
||||
* _before_ validating an AST to validate each AST unaffected from other ASTs
|
||||
* AND _after_ validating the AST to free memory by information which are no longer used.
|
||||
*
|
||||
* @param checkBefore a set-up function which will be called once before actually validating an AST
|
||||
* @param thisObj Optional object to be used as `this` when calling the validation check functions.
|
||||
*/
|
||||
registerAfterDocument(checkAfter: ValidationPreparation, thisObj?: ThisParameterType<unknown>): void;
|
||||
protected wrapPreparationException(check: ValidationPreparation, messageContext: string, thisObj: unknown): ValidationPreparation;
|
||||
get checksBefore(): ValidationPreparation[];
|
||||
get checksAfter(): ValidationPreparation[];
|
||||
getAllValidationCategories(_document: LangiumDocument): ReadonlySet<ValidationCategory>;
|
||||
}
|
||||
export {};
|
||||
//# sourceMappingURL=validation-registry.d.ts.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"validation-registry.d.ts","sourceRoot":"","sources":["../../src/validation/validation-registry.ts"],"names":[],"mappings":"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"}
|
||||
+163
@@ -0,0 +1,163 @@
|
||||
/******************************************************************************
|
||||
* Copyright 2021 TypeFox GmbH
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the MIT License, which is available in the project root.
|
||||
******************************************************************************/
|
||||
import { assertUnreachable } from '../index.js';
|
||||
import { MultiMap } from '../utils/collections.js';
|
||||
import { isOperationCancelled } from '../utils/promise-utils.js';
|
||||
import { stream } from '../utils/stream.js';
|
||||
/**
|
||||
* Create DiagnosticData for a given diagnostic code. The result can be put into the `data` field of a DiagnosticInfo.
|
||||
*/
|
||||
export function diagnosticData(code) {
|
||||
return { code };
|
||||
}
|
||||
export var ValidationCategory;
|
||||
(function (ValidationCategory) {
|
||||
ValidationCategory.defaults = ['fast', 'slow', 'built-in'];
|
||||
/**
|
||||
* @deprecated since 4.2 Use `ValidationCategory.defaults` instead,
|
||||
* since "all" does not include user-defined, custom validation categories.
|
||||
*/
|
||||
ValidationCategory.all = ValidationCategory.defaults;
|
||||
})(ValidationCategory || (ValidationCategory = {}));
|
||||
/**
|
||||
* Manages a set of `ValidationCheck`s to be applied when documents are validated.
|
||||
*/
|
||||
export class ValidationRegistry {
|
||||
constructor(services) {
|
||||
this.entries = new MultiMap();
|
||||
this.knownCategories = new Set(ValidationCategory.defaults);
|
||||
this.entriesBefore = [];
|
||||
this.entriesAfter = [];
|
||||
this.reflection = services.shared.AstReflection;
|
||||
}
|
||||
/**
|
||||
* Register a set of validation checks. Each value in the record can be either a single validation check (i.e. a function)
|
||||
* or an array of validation checks.
|
||||
*
|
||||
* @param checksRecord Set of validation checks to register.
|
||||
* @param thisObj Optional object to be used as `this` when calling the validation check functions.
|
||||
* @param category Optional category for the validation checks (defaults to `'fast'`).
|
||||
*/
|
||||
register(checksRecord, thisObj = this, category = 'fast') {
|
||||
if (category === 'built-in') {
|
||||
throw new Error("The 'built-in' category is reserved for lexer, parser, and linker errors.");
|
||||
}
|
||||
this.knownCategories.add(category); // remember custom/user-defined categories
|
||||
for (const [type, ch] of Object.entries(checksRecord)) {
|
||||
const callbacks = ch;
|
||||
if (Array.isArray(callbacks)) {
|
||||
for (const check of callbacks) {
|
||||
const entry = {
|
||||
check: this.wrapValidationException(check, thisObj),
|
||||
category
|
||||
};
|
||||
this.addEntry(type, entry);
|
||||
}
|
||||
}
|
||||
else if (typeof callbacks === 'function') {
|
||||
const entry = {
|
||||
check: this.wrapValidationException(callbacks, thisObj),
|
||||
category
|
||||
};
|
||||
this.addEntry(type, entry);
|
||||
}
|
||||
else {
|
||||
assertUnreachable(callbacks);
|
||||
}
|
||||
}
|
||||
}
|
||||
wrapValidationException(check, thisObj) {
|
||||
return async (node, accept, cancelToken) => {
|
||||
await this.handleException(() => check.call(thisObj, node, accept, cancelToken), 'An error occurred during validation', accept, node);
|
||||
};
|
||||
}
|
||||
async handleException(functionality, messageContext, accept, node) {
|
||||
try {
|
||||
await functionality();
|
||||
}
|
||||
catch (err) {
|
||||
if (isOperationCancelled(err)) {
|
||||
throw err;
|
||||
}
|
||||
console.error(`${messageContext}:`, err);
|
||||
if (err instanceof Error && err.stack) {
|
||||
console.error(err.stack);
|
||||
}
|
||||
const messageDetails = err instanceof Error ? err.message : String(err);
|
||||
accept('error', `${messageContext}: ${messageDetails}`, { node });
|
||||
}
|
||||
}
|
||||
addEntry(type, entry) {
|
||||
if (type === 'AstNode') {
|
||||
this.entries.add('AstNode', entry);
|
||||
return;
|
||||
}
|
||||
for (const subtype of this.reflection.getAllSubTypes(type)) {
|
||||
this.entries.add(subtype, entry);
|
||||
}
|
||||
}
|
||||
getChecks(type, categories) {
|
||||
let checks = stream(this.entries.get(type))
|
||||
.concat(this.entries.get('AstNode'));
|
||||
if (categories) {
|
||||
checks = checks.filter(entry => categories.includes(entry.category));
|
||||
}
|
||||
return checks.map(entry => entry.check);
|
||||
}
|
||||
/**
|
||||
* Register logic which will be executed once before validating all the nodes of an AST/Langium document.
|
||||
* This helps to prepare or initialize some information which are required or reusable for the following checks on the AstNodes.
|
||||
*
|
||||
* As an example, for validating unique fully-qualified names of nodes in the AST,
|
||||
* here the map for mapping names to nodes could be established.
|
||||
* During the usual checks on the nodes, they are put into this map with their name.
|
||||
*
|
||||
* Note that this approach makes validations stateful, which is relevant e.g. when cancelling the validation.
|
||||
* Therefore it is recommended to clear stored information
|
||||
* _before_ validating an AST to validate each AST unaffected from other ASTs
|
||||
* AND _after_ validating the AST to free memory by information which are no longer used.
|
||||
*
|
||||
* @param checkBefore a set-up function which will be called once before actually validating an AST
|
||||
* @param thisObj Optional object to be used as `this` when calling the validation check functions.
|
||||
*/
|
||||
registerBeforeDocument(checkBefore, thisObj = this) {
|
||||
this.entriesBefore.push(this.wrapPreparationException(checkBefore, 'An error occurred during set-up of the validation', thisObj));
|
||||
}
|
||||
/**
|
||||
* Register logic which will be executed once after validating all the nodes of an AST/Langium document.
|
||||
* This helps to finally evaluate information which are collected during the checks on the AstNodes.
|
||||
*
|
||||
* As an example, for validating unique fully-qualified names of nodes in the AST,
|
||||
* here the map with all the collected nodes and their names is checked
|
||||
* and validation hints are created for all nodes with the same name.
|
||||
*
|
||||
* Note that this approach makes validations stateful, which is relevant e.g. when cancelling the validation.
|
||||
* Therefore it is recommended to clear stored information
|
||||
* _before_ validating an AST to validate each AST unaffected from other ASTs
|
||||
* AND _after_ validating the AST to free memory by information which are no longer used.
|
||||
*
|
||||
* @param checkBefore a set-up function which will be called once before actually validating an AST
|
||||
* @param thisObj Optional object to be used as `this` when calling the validation check functions.
|
||||
*/
|
||||
registerAfterDocument(checkAfter, thisObj = this) {
|
||||
this.entriesAfter.push(this.wrapPreparationException(checkAfter, 'An error occurred during tear-down of the validation', thisObj));
|
||||
}
|
||||
wrapPreparationException(check, messageContext, thisObj) {
|
||||
return async (rootNode, accept, categories, cancelToken) => {
|
||||
await this.handleException(() => check.call(thisObj, rootNode, accept, categories, cancelToken), messageContext, accept, rootNode);
|
||||
};
|
||||
}
|
||||
get checksBefore() {
|
||||
return this.entriesBefore;
|
||||
}
|
||||
get checksAfter() {
|
||||
return this.entriesAfter;
|
||||
}
|
||||
getAllValidationCategories(_document) {
|
||||
return this.knownCategories;
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=validation-registry.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"validation-registry.js","sourceRoot":"","sources":["../../src/validation/validation-registry.ts"],"names":[],"mappings":"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"}
|
||||
Reference in New Issue
Block a user