From 993108a4ea10383436cfe92cea3b469cf9600aee Mon Sep 17 00:00:00 2001 From: ymc9 <104139426+ymc9@users.noreply.github.com> Date: Tue, 23 Sep 2025 17:23:23 -0700 Subject: [PATCH 1/4] fix(language): ref resolution failure causes exception in validator --- packages/ide/vscode/package.json | 2 +- packages/language/src/utils.ts | 20 ++++++++++++-------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/packages/ide/vscode/package.json b/packages/ide/vscode/package.json index 91c8c5a2..00efbd79 100644 --- a/packages/ide/vscode/package.json +++ b/packages/ide/vscode/package.json @@ -1,7 +1,7 @@ { "name": "zenstack-v3", "publisher": "zenstack", - "version": "3.0.6", + "version": "3.0.8", "displayName": "ZenStack V3 Language Tools", "description": "VSCode extension for ZenStack (v3) ZModel language", "private": true, diff --git a/packages/language/src/utils.ts b/packages/language/src/utils.ts index b4b5dd30..3aac3254 100644 --- a/packages/language/src/utils.ts +++ b/packages/language/src/utils.ts @@ -517,13 +517,15 @@ export function getAllFields( const fields: DataField[] = []; for (const mixin of decl.mixins) { - invariant(mixin.ref, `Mixin ${mixin.$refText} is not resolved`); - fields.push(...getAllFields(mixin.ref, includeIgnored, seen)); + if (mixin.ref) { + fields.push(...getAllFields(mixin.ref, includeIgnored, seen)); + } } if (isDataModel(decl) && decl.baseModel) { - invariant(decl.baseModel.ref, `Base model ${decl.baseModel.$refText} is not resolved`); - fields.push(...getAllFields(decl.baseModel.ref, includeIgnored, seen)); + if (decl.baseModel.ref) { + fields.push(...getAllFields(decl.baseModel.ref, includeIgnored, seen)); + } } fields.push(...decl.fields.filter((f) => includeIgnored || !hasAttribute(f, '@ignore'))); @@ -541,13 +543,15 @@ export function getAllAttributes( const attributes: DataModelAttribute[] = []; for (const mixin of decl.mixins) { - invariant(mixin.ref, `Mixin ${mixin.$refText} is not resolved`); - attributes.push(...getAllAttributes(mixin.ref, seen)); + if (mixin.ref) { + attributes.push(...getAllAttributes(mixin.ref, seen)); + } } if (isDataModel(decl) && decl.baseModel) { - invariant(decl.baseModel.ref, `Base model ${decl.baseModel.$refText} is not resolved`); - attributes.push(...getAllAttributes(decl.baseModel.ref, seen)); + if (decl.baseModel.ref) { + attributes.push(...getAllAttributes(decl.baseModel.ref, seen)); + } } attributes.push(...decl.attributes); From cf807713fa7a3e98b3d9ba270c32a07ceeaf93b4 Mon Sep 17 00:00:00 2001 From: ymc9 <104139426+ymc9@users.noreply.github.com> Date: Tue, 23 Sep 2025 17:25:22 -0700 Subject: [PATCH 2/4] fix lint --- packages/language/src/utils.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/language/src/utils.ts b/packages/language/src/utils.ts index 3aac3254..70b879fe 100644 --- a/packages/language/src/utils.ts +++ b/packages/language/src/utils.ts @@ -1,4 +1,3 @@ -import { invariant } from '@zenstackhq/common-helpers'; import { AstUtils, URI, type AstNode, type LangiumDocument, type LangiumDocuments, type Reference } from 'langium'; import fs from 'node:fs'; import path from 'path'; From d11bde42e0f7a1056bfe244747c68b9b7ac99e94 Mon Sep 17 00:00:00 2001 From: ymc9 <104139426+ymc9@users.noreply.github.com> Date: Tue, 23 Sep 2025 18:47:54 -0700 Subject: [PATCH 3/4] update --- packages/language/src/module.ts | 2 + packages/language/src/utils.ts | 5 ++- packages/language/src/validator.ts | 37 +++++-------------- .../language/src/zmodel-document-builder.ts | 22 +++++++++++ 4 files changed, 38 insertions(+), 28 deletions(-) create mode 100644 packages/language/src/zmodel-document-builder.ts diff --git a/packages/language/src/module.ts b/packages/language/src/module.ts index 00e54985..1b853945 100644 --- a/packages/language/src/module.ts +++ b/packages/language/src/module.ts @@ -9,6 +9,7 @@ import { } from 'langium/lsp'; import { ZModelGeneratedModule, ZModelGeneratedSharedModule, ZModelLanguageMetaData } from './generated/module'; import { ZModelValidator, registerValidationChecks } from './validator'; +import { ZModelDocumentBuilder } from './zmodel-document-builder'; import { ZModelLinker } from './zmodel-linker'; import { ZModelScopeComputation, ZModelScopeProvider } from './zmodel-scope'; import { ZModelWorkspaceManager } from './zmodel-workspace-manager'; @@ -49,6 +50,7 @@ export type ZModelSharedServices = LangiumSharedServices; export const ZModelSharedModule: Module> = { workspace: { + DocumentBuilder: (services) => new ZModelDocumentBuilder(services), WorkspaceManager: (services) => new ZModelWorkspaceManager(services), }, }; diff --git a/packages/language/src/utils.ts b/packages/language/src/utils.ts index 70b879fe..2f9b4a75 100644 --- a/packages/language/src/utils.ts +++ b/packages/language/src/utils.ts @@ -171,7 +171,10 @@ export function getRecursiveBases( } seen.add(decl); decl.mixins.forEach((mixin) => { - const baseDecl = mixin.ref; + // avoid using mixin.ref since this function can be called before linking + const baseDecl = decl.$container.declarations.find( + (d): d is TypeDef => isTypeDef(d) && d.name === mixin.$refText, + ); if (baseDecl) { if (!includeDelegate && isDelegateModel(baseDecl)) { return; diff --git a/packages/language/src/validator.ts b/packages/language/src/validator.ts index 7b7e117c..efbc3794 100644 --- a/packages/language/src/validator.ts +++ b/packages/language/src/validator.ts @@ -1,6 +1,4 @@ -/* eslint-disable @typescript-eslint/no-unused-expressions */ - -import type { AstNode, LangiumDocument, ValidationAcceptor, ValidationChecks } from 'langium'; +import type { ValidationAcceptor, ValidationChecks } from 'langium'; import type { Attribute, DataModel, @@ -50,54 +48,39 @@ export function registerValidationChecks(services: ZModelServices) { export class ZModelValidator { constructor(protected readonly services: ZModelServices) {} - private shouldCheck(node: AstNode) { - let doc: LangiumDocument | undefined; - let currNode: AstNode | undefined = node; - while (currNode) { - if (currNode.$document) { - doc = currNode.$document; - break; - } - currNode = currNode.$container; - } - - return doc?.parseResult.lexerErrors.length === 0 && doc?.parseResult.parserErrors.length === 0; - } - checkModel(node: Model, accept: ValidationAcceptor): void { - this.shouldCheck(node) && - new SchemaValidator(this.services.shared.workspace.LangiumDocuments).validate(node, accept); + new SchemaValidator(this.services.shared.workspace.LangiumDocuments).validate(node, accept); } checkDataSource(node: DataSource, accept: ValidationAcceptor): void { - this.shouldCheck(node) && new DataSourceValidator().validate(node, accept); + new DataSourceValidator().validate(node, accept); } checkDataModel(node: DataModel, accept: ValidationAcceptor): void { - this.shouldCheck(node) && new DataModelValidator().validate(node, accept); + new DataModelValidator().validate(node, accept); } checkTypeDef(node: TypeDef, accept: ValidationAcceptor): void { - this.shouldCheck(node) && new TypeDefValidator().validate(node, accept); + new TypeDefValidator().validate(node, accept); } checkEnum(node: Enum, accept: ValidationAcceptor): void { - this.shouldCheck(node) && new EnumValidator().validate(node, accept); + new EnumValidator().validate(node, accept); } checkAttribute(node: Attribute, accept: ValidationAcceptor): void { - this.shouldCheck(node) && new AttributeValidator().validate(node, accept); + new AttributeValidator().validate(node, accept); } checkExpression(node: Expression, accept: ValidationAcceptor): void { - this.shouldCheck(node) && new ExpressionValidator().validate(node, accept); + new ExpressionValidator().validate(node, accept); } checkFunctionInvocation(node: InvocationExpr, accept: ValidationAcceptor): void { - this.shouldCheck(node) && new FunctionInvocationValidator().validate(node, accept); + new FunctionInvocationValidator().validate(node, accept); } checkFunctionDecl(node: FunctionDecl, accept: ValidationAcceptor): void { - this.shouldCheck(node) && new FunctionDeclValidator().validate(node, accept); + new FunctionDeclValidator().validate(node, accept); } } diff --git a/packages/language/src/zmodel-document-builder.ts b/packages/language/src/zmodel-document-builder.ts new file mode 100644 index 00000000..8e55e267 --- /dev/null +++ b/packages/language/src/zmodel-document-builder.ts @@ -0,0 +1,22 @@ +import { DefaultDocumentBuilder, type BuildOptions, type LangiumDocument } from 'langium'; + +export class ZModelDocumentBuilder extends DefaultDocumentBuilder { + override buildDocuments(documents: LangiumDocument[], options: BuildOptions, cancelToken: any): Promise { + return super.buildDocuments( + documents, + { + ...options, + validation: + // force overriding validation options + options.validation === false || options.validation === undefined + ? options.validation + : { + stopAfterLexingErrors: true, + stopAfterParsingErrors: true, + stopAfterLinkingErrors: true, + }, + }, + cancelToken, + ); + } +} From 72427de36b5f919a4962c1b9d3b181df160cc392 Mon Sep 17 00:00:00 2001 From: ymc9 <104139426+ymc9@users.noreply.github.com> Date: Tue, 23 Sep 2025 18:57:50 -0700 Subject: [PATCH 4/4] fix tests --- packages/language/test/expression-validation.test.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/language/test/expression-validation.test.ts b/packages/language/test/expression-validation.test.ts index 5a3179f3..100f02b2 100644 --- a/packages/language/test/expression-validation.test.ts +++ b/packages/language/test/expression-validation.test.ts @@ -2,7 +2,7 @@ import { describe, it } from 'vitest'; import { loadSchema, loadSchemaWithError } from './utils'; describe('Expression Validation Tests', () => { - it('should reject model comparison', async () => { + it('should reject model comparison1', async () => { await loadSchemaWithError( ` model User { @@ -15,6 +15,7 @@ describe('Expression Validation Tests', () => { id Int @id title String author User @relation(fields: [authorId], references: [id]) + authorId Int @@allow('all', author == this) } `, @@ -22,7 +23,7 @@ describe('Expression Validation Tests', () => { ); }); - it('should reject model comparison', async () => { + it('should reject model comparison2', async () => { await loadSchemaWithError( ` model User {