Skip to content

Commit 782a449

Browse files
authored
feat: import statment validation (#369)
- show an error if cannot find the imported model file - support Go To Definition for valid import statement
1 parent 2fa3aee commit 782a449

File tree

5 files changed

+66
-4
lines changed

5 files changed

+66
-4
lines changed

packages/schema/src/language-server/validator/schema-validator.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,15 @@ import { isDataSource, Model } from '@zenstackhq/language/ast';
33
import { AstValidator } from '../types';
44
import { LangiumDocuments, ValidationAcceptor } from 'langium';
55
import { validateDuplicatedDeclarations } from './utils';
6-
import { getAllDeclarationsFromImports, resolveTransitiveImports } from '../../utils/ast-utils';
6+
import { getAllDeclarationsFromImports, resolveImport, resolveTransitiveImports } from '../../utils/ast-utils';
77

88
/**
99
* Validates toplevel schema.
1010
*/
1111
export default class SchemaValidator implements AstValidator<Model> {
1212
constructor(protected readonly documents: LangiumDocuments) {}
1313
validate(model: Model, accept: ValidationAcceptor): void {
14+
this.validateImports(model, accept);
1415
validateDuplicatedDeclarations(model.declarations, accept);
1516

1617
const importedModels = resolveTransitiveImports(this.documents, model);
@@ -40,4 +41,13 @@ export default class SchemaValidator implements AstValidator<Model> {
4041
accept('error', 'Multiple datasource declarations are not allowed', { node: dataSources[1] });
4142
}
4243
}
44+
45+
private validateImports(model: Model, accept: ValidationAcceptor) {
46+
model.imports.forEach((imp) => {
47+
const importedModel = resolveImport(this.documents, imp);
48+
if (!importedModel) {
49+
accept('error', `Cannot find model file ${imp.path}.zmodel`, { node: imp });
50+
}
51+
});
52+
}
4353
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { DefaultDefinitionProvider, LangiumDocuments, LangiumServices, LeafCstNode, MaybePromise } from 'langium';
2+
import { DefinitionParams, LocationLink, Range } from 'vscode-languageserver';
3+
import { resolveImport } from '../utils/ast-utils';
4+
import { isModelImport } from '@zenstackhq/language/ast';
5+
6+
export class ZModelDefinitionProvider extends DefaultDefinitionProvider {
7+
protected documents: LangiumDocuments;
8+
9+
constructor(services: LangiumServices) {
10+
super(services);
11+
this.documents = services.shared.workspace.LangiumDocuments;
12+
}
13+
protected override collectLocationLinks(
14+
sourceCstNode: LeafCstNode,
15+
_params: DefinitionParams
16+
): MaybePromise<LocationLink[] | undefined> {
17+
if (isModelImport(sourceCstNode.element)) {
18+
const importedModel = resolveImport(this.documents, sourceCstNode.element);
19+
if (importedModel?.$document) {
20+
const targetObject = importedModel;
21+
const selectionRange = this.nameProvider.getNameNode(targetObject)?.range ?? Range.create(0, 0, 0, 0);
22+
const previewRange = targetObject.$cstNode?.range ?? Range.create(0, 0, 0, 0);
23+
return [
24+
LocationLink.create(
25+
importedModel.$document.uri.toString(),
26+
previewRange,
27+
selectionRange,
28+
sourceCstNode.range
29+
),
30+
];
31+
}
32+
return undefined;
33+
}
34+
return super.collectLocationLinks(sourceCstNode, _params);
35+
}
36+
}

packages/schema/src/language-server/zmodel-module.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import { ZModelFormatter } from './zmodel-formatter';
2525
import { ZModelLinker } from './zmodel-linker';
2626
import { ZModelScopeComputation, ZModelScopeProvider } from './zmodel-scope';
2727
import ZModelWorkspaceManager from './zmodel-workspace-manager';
28+
import { ZModelDefinitionProvider } from './zmodel-definition';
2829

2930
/**
3031
* Declaration of custom services - add your own service classes here.
@@ -59,6 +60,7 @@ export const ZModelModule: Module<ZModelServices, PartialLangiumServices & ZMode
5960
lsp: {
6061
Formatter: () => new ZModelFormatter(),
6162
CodeActionProvider: (services) => new ZModelCodeActionProvider(services),
63+
DefinitionProvider: (services) => new ZModelDefinitionProvider(services),
6264
},
6365
};
6466

packages/schema/src/utils/ast-utils.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -211,9 +211,9 @@ function resolveTransitiveImportsInternal(
211211
if (!visited.has(doc.uri)) {
212212
visited.add(doc.uri);
213213
for (const imp of model.imports) {
214-
const importedGrammar = resolveImport(documents, imp);
215-
if (importedGrammar) {
216-
resolveTransitiveImportsInternal(documents, importedGrammar, initialModel, visited, models);
214+
const importedModel = resolveImport(documents, imp);
215+
if (importedModel) {
216+
resolveTransitiveImportsInternal(documents, importedModel, initialModel, visited, models);
217217
}
218218
}
219219
}

packages/schema/tests/schema/validation/schema-validation.test.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,18 @@ describe('Toplevel Schema Validation Tests', () => {
2424
`)
2525
).toContain('Duplicated declaration name "X"');
2626
});
27+
28+
it('not exsited import', async () => {
29+
expect(
30+
await loadModelWithError(`
31+
import 'models/abc'
32+
datasource db1 {
33+
provider = 'postgresql'
34+
url = env('DATABASE_URL')
35+
}
36+
37+
model X {id String @id }
38+
`)
39+
).toContain('Cannot find model file models/abc.zmodel');
40+
});
2741
});

0 commit comments

Comments
 (0)