diff --git a/packages/schema/src/language-server/validator/schema-validator.ts b/packages/schema/src/language-server/validator/schema-validator.ts index 098489939..5b05d4128 100644 --- a/packages/schema/src/language-server/validator/schema-validator.ts +++ b/packages/schema/src/language-server/validator/schema-validator.ts @@ -3,7 +3,7 @@ import { isDataSource, Model } from '@zenstackhq/language/ast'; import { AstValidator } from '../types'; import { LangiumDocuments, ValidationAcceptor } from 'langium'; import { validateDuplicatedDeclarations } from './utils'; -import { getAllDeclarationsFromImports, resolveTransitiveImports } from '../../utils/ast-utils'; +import { getAllDeclarationsFromImports, resolveImport, resolveTransitiveImports } from '../../utils/ast-utils'; /** * Validates toplevel schema. @@ -11,6 +11,7 @@ import { getAllDeclarationsFromImports, resolveTransitiveImports } from '../../u export default class SchemaValidator implements AstValidator { constructor(protected readonly documents: LangiumDocuments) {} validate(model: Model, accept: ValidationAcceptor): void { + this.validateImports(model, accept); validateDuplicatedDeclarations(model.declarations, accept); const importedModels = resolveTransitiveImports(this.documents, model); @@ -40,4 +41,13 @@ export default class SchemaValidator implements AstValidator { accept('error', 'Multiple datasource declarations are not allowed', { node: dataSources[1] }); } } + + private validateImports(model: Model, accept: ValidationAcceptor) { + model.imports.forEach((imp) => { + const importedModel = resolveImport(this.documents, imp); + if (!importedModel) { + accept('error', `Cannot find model file ${imp.path}.zmodel`, { node: imp }); + } + }); + } } diff --git a/packages/schema/src/language-server/zmodel-definition.ts b/packages/schema/src/language-server/zmodel-definition.ts new file mode 100644 index 000000000..f4bb4a39b --- /dev/null +++ b/packages/schema/src/language-server/zmodel-definition.ts @@ -0,0 +1,36 @@ +import { DefaultDefinitionProvider, LangiumDocuments, LangiumServices, LeafCstNode, MaybePromise } from 'langium'; +import { DefinitionParams, LocationLink, Range } from 'vscode-languageserver'; +import { resolveImport } from '../utils/ast-utils'; +import { isModelImport } from '@zenstackhq/language/ast'; + +export class ZModelDefinitionProvider extends DefaultDefinitionProvider { + protected documents: LangiumDocuments; + + constructor(services: LangiumServices) { + super(services); + this.documents = services.shared.workspace.LangiumDocuments; + } + protected override collectLocationLinks( + sourceCstNode: LeafCstNode, + _params: DefinitionParams + ): MaybePromise { + if (isModelImport(sourceCstNode.element)) { + const importedModel = resolveImport(this.documents, sourceCstNode.element); + if (importedModel?.$document) { + const targetObject = importedModel; + const selectionRange = this.nameProvider.getNameNode(targetObject)?.range ?? Range.create(0, 0, 0, 0); + const previewRange = targetObject.$cstNode?.range ?? Range.create(0, 0, 0, 0); + return [ + LocationLink.create( + importedModel.$document.uri.toString(), + previewRange, + selectionRange, + sourceCstNode.range + ), + ]; + } + return undefined; + } + return super.collectLocationLinks(sourceCstNode, _params); + } +} diff --git a/packages/schema/src/language-server/zmodel-module.ts b/packages/schema/src/language-server/zmodel-module.ts index e25f7015f..195c41031 100644 --- a/packages/schema/src/language-server/zmodel-module.ts +++ b/packages/schema/src/language-server/zmodel-module.ts @@ -25,6 +25,7 @@ import { ZModelFormatter } from './zmodel-formatter'; import { ZModelLinker } from './zmodel-linker'; import { ZModelScopeComputation, ZModelScopeProvider } from './zmodel-scope'; import ZModelWorkspaceManager from './zmodel-workspace-manager'; +import { ZModelDefinitionProvider } from './zmodel-definition'; /** * Declaration of custom services - add your own service classes here. @@ -59,6 +60,7 @@ export const ZModelModule: Module new ZModelFormatter(), CodeActionProvider: (services) => new ZModelCodeActionProvider(services), + DefinitionProvider: (services) => new ZModelDefinitionProvider(services), }, }; diff --git a/packages/schema/src/utils/ast-utils.ts b/packages/schema/src/utils/ast-utils.ts index 6ce870302..ba951a1ea 100644 --- a/packages/schema/src/utils/ast-utils.ts +++ b/packages/schema/src/utils/ast-utils.ts @@ -211,9 +211,9 @@ function resolveTransitiveImportsInternal( if (!visited.has(doc.uri)) { visited.add(doc.uri); for (const imp of model.imports) { - const importedGrammar = resolveImport(documents, imp); - if (importedGrammar) { - resolveTransitiveImportsInternal(documents, importedGrammar, initialModel, visited, models); + const importedModel = resolveImport(documents, imp); + if (importedModel) { + resolveTransitiveImportsInternal(documents, importedModel, initialModel, visited, models); } } } diff --git a/packages/schema/tests/schema/validation/schema-validation.test.ts b/packages/schema/tests/schema/validation/schema-validation.test.ts index 0c063b30d..c7b000338 100644 --- a/packages/schema/tests/schema/validation/schema-validation.test.ts +++ b/packages/schema/tests/schema/validation/schema-validation.test.ts @@ -24,4 +24,18 @@ describe('Toplevel Schema Validation Tests', () => { `) ).toContain('Duplicated declaration name "X"'); }); + + it('not exsited import', async () => { + expect( + await loadModelWithError(` + import 'models/abc' + datasource db1 { + provider = 'postgresql' + url = env('DATABASE_URL') + } + + model X {id String @id } + `) + ).toContain('Cannot find model file models/abc.zmodel'); + }); });