Skip to content

Commit

Permalink
feat: import statment validation (zenstackhq#369)
Browse files Browse the repository at this point in the history
- show an error if cannot find the imported model file
- support Go To Definition for valid import statement
  • Loading branch information
jiashengguo authored Apr 29, 2023
1 parent 2fa3aee commit 782a449
Show file tree
Hide file tree
Showing 5 changed files with 66 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@ 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.
*/
export default class SchemaValidator implements AstValidator<Model> {
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);
Expand Down Expand Up @@ -40,4 +41,13 @@ export default class SchemaValidator implements AstValidator<Model> {
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 });
}
});
}
}
36 changes: 36 additions & 0 deletions packages/schema/src/language-server/zmodel-definition.ts
Original file line number Diff line number Diff line change
@@ -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<LocationLink[] | undefined> {
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);
}
}
2 changes: 2 additions & 0 deletions packages/schema/src/language-server/zmodel-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -59,6 +60,7 @@ export const ZModelModule: Module<ZModelServices, PartialLangiumServices & ZMode
lsp: {
Formatter: () => new ZModelFormatter(),
CodeActionProvider: (services) => new ZModelCodeActionProvider(services),
DefinitionProvider: (services) => new ZModelDefinitionProvider(services),
},
};

Expand Down
6 changes: 3 additions & 3 deletions packages/schema/src/utils/ast-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
}
Expand Down
14 changes: 14 additions & 0 deletions packages/schema/tests/schema/validation/schema-validation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');
});
});

0 comments on commit 782a449

Please sign in to comment.