zenstack 0.1.47 → 0.1.50
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +9 -0
- package/bin/cli +1 -1
- package/bundle/asset/logo-dark-256.png +0 -0
- package/bundle/asset/logo-dark.png +0 -0
- package/bundle/asset/logo-light-256.png +0 -0
- package/bundle/asset/logo-light.png +0 -0
- package/bundle/cli/index.js +6849 -0
- package/bundle/cli/index.js.map +7 -0
- package/bundle/extension.js +39 -0
- package/bundle/extension.js.map +7 -0
- package/bundle/language-server/main.js +6105 -0
- package/bundle/language-server/main.js.map +7 -0
- package/{out/generator → bundle/res}/package.template.json +0 -0
- package/bundle/res/stdlib.zmodel +101 -0
- package/{out/generator → bundle/res}/tsconfig.template.json +0 -0
- package/package.json +38 -14
- package/src/cli/cli-util.ts +71 -0
- package/src/cli/index.ts +182 -0
- package/src/extension.ts +76 -0
- package/src/generator/constants.ts +5 -0
- package/src/generator/index.ts +102 -0
- package/{out/generator/next-auth/index.js → src/generator/next-auth/index.ts} +49 -58
- package/src/generator/prisma/expression-writer.ts +360 -0
- package/src/generator/prisma/index.ts +35 -0
- package/src/generator/prisma/prisma-builder.ts +370 -0
- package/src/generator/prisma/query-gard-generator.ts +213 -0
- package/src/generator/prisma/schema-generator.ts +305 -0
- package/src/generator/prisma/typescript-expression-transformer.ts +108 -0
- package/src/generator/react-hooks/index.ts +184 -0
- package/src/generator/service/index.ts +110 -0
- package/src/generator/types.ts +17 -0
- package/src/generator/utils.ts +18 -0
- package/src/language-server/constants.ts +28 -0
- package/src/language-server/generated/ast.ts +616 -0
- package/{out/language-server/generated/grammar.js → src/language-server/generated/grammar.ts} +5 -8
- package/src/language-server/generated/module.ts +24 -0
- package/src/language-server/langium-ext.d.ts +10 -0
- package/src/language-server/lsp/zmodel-definition-provider.ts +87 -0
- package/src/language-server/main.ts +13 -0
- package/src/language-server/types.ts +25 -0
- package/src/language-server/validator/attribute-validator.ts +11 -0
- package/src/language-server/validator/datamodel-validator.ts +311 -0
- package/src/language-server/validator/datasource-validator.ts +102 -0
- package/src/language-server/validator/enum-validator.ts +14 -0
- package/src/language-server/validator/schema-validator.ts +31 -0
- package/src/language-server/validator/utils.ts +158 -0
- package/src/language-server/validator/zmodel-validator.ts +84 -0
- package/src/language-server/zmodel-linker.ts +446 -0
- package/src/language-server/zmodel-module.ts +136 -0
- package/src/language-server/zmodel-scope.ts +45 -0
- package/src/language-server/zmodel-workspace-manager.ts +23 -0
- package/src/language-server/zmodel.langium +197 -0
- package/{out/cli → src/res}/package.template.json +2 -3
- package/src/res/stdlib.zmodel +101 -0
- package/{out/cli → src/res}/tsconfig.template.json +1 -1
- package/src/utils/exec-utils.ts +8 -0
- package/src/utils/indent-string.ts +9 -0
- package/LICENSE +0 -21
- package/out/cli/cli-util.js +0 -64
- package/out/cli/cli-util.js.map +0 -1
- package/out/cli/generator.js +0 -1
- package/out/cli/generator.js.map +0 -1
- package/out/cli/index.js +0 -124
- package/out/cli/index.js.map +0 -1
- package/out/extension.js +0 -81
- package/out/extension.js.map +0 -1
- package/out/generator/constants.js +0 -9
- package/out/generator/constants.js.map +0 -1
- package/out/generator/data-server/index.js +0 -1
- package/out/generator/data-server/index.js.map +0 -1
- package/out/generator/index.js +0 -98
- package/out/generator/index.js.map +0 -1
- package/out/generator/next-auth/index.js.map +0 -1
- package/out/generator/prisma/expression-writer.js +0 -287
- package/out/generator/prisma/expression-writer.js.map +0 -1
- package/out/generator/prisma/index.js +0 -44
- package/out/generator/prisma/index.js.map +0 -1
- package/out/generator/prisma/plain-expression-builder.js +0 -69
- package/out/generator/prisma/plain-expression-builder.js.map +0 -1
- package/out/generator/prisma/prisma-builder.js +0 -307
- package/out/generator/prisma/prisma-builder.js.map +0 -1
- package/out/generator/prisma/query-gard-generator.js +0 -159
- package/out/generator/prisma/query-gard-generator.js.map +0 -1
- package/out/generator/prisma/schema-generator.js +0 -193
- package/out/generator/prisma/schema-generator.js.map +0 -1
- package/out/generator/query-guard/index.js +0 -2
- package/out/generator/query-guard/index.js.map +0 -1
- package/out/generator/react-hooks/index.js +0 -179
- package/out/generator/react-hooks/index.js.map +0 -1
- package/out/generator/server/data/data-generator.js +0 -376
- package/out/generator/server/data/data-generator.js.map +0 -1
- package/out/generator/server/data/expression-writer.js +0 -287
- package/out/generator/server/data/expression-writer.js.map +0 -1
- package/out/generator/server/data/plain-expression-builder.js +0 -69
- package/out/generator/server/data/plain-expression-builder.js.map +0 -1
- package/out/generator/server/data-generator.js +0 -82
- package/out/generator/server/data-generator.js.map +0 -1
- package/out/generator/server/expression-writer.js +0 -1
- package/out/generator/server/expression-writer.js.map +0 -1
- package/out/generator/server/function/function-generator.js +0 -50
- package/out/generator/server/function/function-generator.js.map +0 -1
- package/out/generator/server/function-generator.js +0 -13
- package/out/generator/server/function-generator.js.map +0 -1
- package/out/generator/server/index.js +0 -88
- package/out/generator/server/index.js.map +0 -1
- package/out/generator/server/js-expression-builder.js +0 -1
- package/out/generator/server/js-expression-builder.js.map +0 -1
- package/out/generator/server/plain-expression-builder.js +0 -1
- package/out/generator/server/plain-expression-builder.js.map +0 -1
- package/out/generator/server/server-code-generator.js +0 -3
- package/out/generator/server/server-code-generator.js.map +0 -1
- package/out/generator/server/server-code-writer.js +0 -1
- package/out/generator/server/server-code-writer.js.map +0 -1
- package/out/generator/service/index.js +0 -133
- package/out/generator/service/index.js.map +0 -1
- package/out/generator/types.js +0 -10
- package/out/generator/types.js.map +0 -1
- package/out/generator/utils.js +0 -10
- package/out/generator/utils.js.map +0 -1
- package/out/langium-ext.js +0 -3
- package/out/langium-ext.js.map +0 -1
- package/out/language-server/constants.js +0 -20
- package/out/language-server/constants.js.map +0 -1
- package/out/language-server/generated/ast.js +0 -390
- package/out/language-server/generated/ast.js.map +0 -1
- package/out/language-server/generated/grammar.js.map +0 -1
- package/out/language-server/generated/module.js +0 -23
- package/out/language-server/generated/module.js.map +0 -1
- package/out/language-server/langium-ext.js +0 -3
- package/out/language-server/langium-ext.js.map +0 -1
- package/out/language-server/main.js +0 -13
- package/out/language-server/main.js.map +0 -1
- package/out/language-server/stdlib.zmodel +0 -23
- package/out/language-server/types.js +0 -3
- package/out/language-server/types.js.map +0 -1
- package/out/language-server/validator/attribute-validator copy.js +0 -12
- package/out/language-server/validator/attribute-validator copy.js.map +0 -1
- package/out/language-server/validator/attribute-validator.js +0 -7
- package/out/language-server/validator/attribute-validator.js.map +0 -1
- package/out/language-server/validator/datamodel-validator.js +0 -199
- package/out/language-server/validator/datamodel-validator.js.map +0 -1
- package/out/language-server/validator/datasource-validator copy.js +0 -77
- package/out/language-server/validator/datasource-validator copy.js.map +0 -1
- package/out/language-server/validator/datasource-validator.js +0 -77
- package/out/language-server/validator/datasource-validator.js.map +0 -1
- package/out/language-server/validator/enum-validator.js +0 -10
- package/out/language-server/validator/enum-validator.js.map +0 -1
- package/out/language-server/validator/model-validator.js +0 -21
- package/out/language-server/validator/model-validator.js.map +0 -1
- package/out/language-server/validator/schema-validator.js +0 -21
- package/out/language-server/validator/schema-validator.js.map +0 -1
- package/out/language-server/validator/utils.js +0 -106
- package/out/language-server/validator/utils.js.map +0 -1
- package/out/language-server/validator/zmodel-validator.js +0 -52
- package/out/language-server/validator/zmodel-validator.js.map +0 -1
- package/out/language-server/zmodel-index.js +0 -11
- package/out/language-server/zmodel-index.js.map +0 -1
- package/out/language-server/zmodel-linker.js +0 -249
- package/out/language-server/zmodel-linker.js.map +0 -1
- package/out/language-server/zmodel-module.js +0 -46
- package/out/language-server/zmodel-module.js.map +0 -1
- package/out/language-server/zmodel-scope.js +0 -41
- package/out/language-server/zmodel-scope.js.map +0 -1
- package/out/language-server/zmodel-validator.js +0 -35
- package/out/language-server/zmodel-validator.js.map +0 -1
- package/out/utils/exec-utils.js +0 -9
- package/out/utils/exec-utils.js.map +0 -1
- package/out/utils/indent-string.js +0 -9
- package/out/utils/indent-string.js.map +0 -1
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/******************************************************************************
|
|
2
|
+
* This file was generated by langium-cli 0.5.0.
|
|
3
|
+
* DO NOT EDIT MANUALLY!
|
|
4
|
+
******************************************************************************/
|
|
5
|
+
|
|
6
|
+
import { LangiumGeneratedServices, LangiumGeneratedSharedServices, LangiumSharedServices, LangiumServices, LanguageMetaData, Module } from 'langium';
|
|
7
|
+
import { ZModelAstReflection } from './ast';
|
|
8
|
+
import { ZModelGrammar } from './grammar';
|
|
9
|
+
|
|
10
|
+
export const ZModelLanguageMetaData: LanguageMetaData = {
|
|
11
|
+
languageId: 'zmodel',
|
|
12
|
+
fileExtensions: ['.zmodel'],
|
|
13
|
+
caseInsensitive: false
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export const ZModelGeneratedSharedModule: Module<LangiumSharedServices, LangiumGeneratedSharedServices> = {
|
|
17
|
+
AstReflection: () => new ZModelAstReflection()
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export const ZModelGeneratedModule: Module<LangiumServices, LangiumGeneratedServices> = {
|
|
21
|
+
Grammar: () => ZModelGrammar(),
|
|
22
|
+
LanguageMetaData: () => ZModelLanguageMetaData,
|
|
23
|
+
parser: {}
|
|
24
|
+
};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CstNode,
|
|
3
|
+
DefinitionProvider,
|
|
4
|
+
findDeclarationNodeAtOffset,
|
|
5
|
+
getDocument,
|
|
6
|
+
GoToLink,
|
|
7
|
+
LangiumDocument,
|
|
8
|
+
LangiumServices,
|
|
9
|
+
MaybePromise,
|
|
10
|
+
NameProvider,
|
|
11
|
+
} from 'langium';
|
|
12
|
+
import { GrammarConfig } from 'langium/lib/grammar/grammar-config';
|
|
13
|
+
import { References } from 'langium/lib/references/references';
|
|
14
|
+
import { DefinitionParams, LocationLink } from 'vscode-languageserver';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Custom Langium DefinitionProvider implementation
|
|
18
|
+
*/
|
|
19
|
+
export default class ZModelDefinitionProvider implements DefinitionProvider {
|
|
20
|
+
protected readonly nameProvider: NameProvider;
|
|
21
|
+
protected readonly references: References;
|
|
22
|
+
protected readonly grammarConfig: GrammarConfig;
|
|
23
|
+
|
|
24
|
+
constructor(services: LangiumServices) {
|
|
25
|
+
this.nameProvider = services.references.NameProvider;
|
|
26
|
+
this.references = services.references.References;
|
|
27
|
+
this.grammarConfig = services.parser.GrammarConfig;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
getDefinition(
|
|
31
|
+
document: LangiumDocument,
|
|
32
|
+
params: DefinitionParams
|
|
33
|
+
): MaybePromise<LocationLink[] | undefined> {
|
|
34
|
+
const rootNode = document.parseResult.value;
|
|
35
|
+
if (rootNode.$cstNode) {
|
|
36
|
+
const cst = rootNode.$cstNode;
|
|
37
|
+
const sourceCstNode = findDeclarationNodeAtOffset(
|
|
38
|
+
cst,
|
|
39
|
+
document.textDocument.offsetAt(params.position),
|
|
40
|
+
this.grammarConfig.nameRegexp
|
|
41
|
+
);
|
|
42
|
+
if (sourceCstNode) {
|
|
43
|
+
return this.collectLocationLinks(sourceCstNode, params);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
protected collectLocationLinks(
|
|
50
|
+
sourceCstNode: CstNode,
|
|
51
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
52
|
+
_params: DefinitionParams
|
|
53
|
+
): MaybePromise<LocationLink[] | undefined> {
|
|
54
|
+
const goToLink = this.findLink(sourceCstNode);
|
|
55
|
+
if (goToLink) {
|
|
56
|
+
if (
|
|
57
|
+
// this condition is for working around a potential Langium bug
|
|
58
|
+
// https://github.com/langium/langium/discussions/732
|
|
59
|
+
!goToLink.targetDocument.textDocument.uri.endsWith(
|
|
60
|
+
'stdlib.zmodel'
|
|
61
|
+
)
|
|
62
|
+
) {
|
|
63
|
+
return [
|
|
64
|
+
LocationLink.create(
|
|
65
|
+
goToLink.targetDocument.textDocument.uri,
|
|
66
|
+
(goToLink.target.element.$cstNode ?? goToLink.target)
|
|
67
|
+
.range,
|
|
68
|
+
goToLink.target.range,
|
|
69
|
+
goToLink.source.range
|
|
70
|
+
),
|
|
71
|
+
];
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
protected findLink(source: CstNode): GoToLink | undefined {
|
|
78
|
+
const target = this.references.findDeclarationNode(source);
|
|
79
|
+
if (target?.element) {
|
|
80
|
+
const targetDocument = getDocument(target.element);
|
|
81
|
+
if (target && targetDocument) {
|
|
82
|
+
return { source, target, targetDocument };
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return undefined;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { startLanguageServer } from 'langium';
|
|
2
|
+
import { NodeFileSystem } from 'langium/node';
|
|
3
|
+
import { createConnection, ProposedFeatures } from 'vscode-languageserver/node';
|
|
4
|
+
import { createZModelServices } from './zmodel-module';
|
|
5
|
+
|
|
6
|
+
// Create a connection to the client
|
|
7
|
+
const connection = createConnection(ProposedFeatures.all);
|
|
8
|
+
|
|
9
|
+
// Inject the shared services and language-specific services
|
|
10
|
+
const { shared } = createZModelServices({ connection, ...NodeFileSystem });
|
|
11
|
+
|
|
12
|
+
// Start the language server with the shared services
|
|
13
|
+
startLanguageServer(shared);
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { AstNode, ValidationAcceptor } from 'langium';
|
|
2
|
+
import { AbstractDeclaration, ExpressionType } from './generated/ast';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Shape of type resolution result: an expression type or reference to a declaration
|
|
6
|
+
*/
|
|
7
|
+
export type ResolvedShape = ExpressionType | AbstractDeclaration;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Resolved type information (attached to expressions by linker)
|
|
11
|
+
*/
|
|
12
|
+
export type ResolvedType = {
|
|
13
|
+
decl?: ResolvedShape;
|
|
14
|
+
array?: boolean;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* AST validator contract
|
|
19
|
+
*/
|
|
20
|
+
export interface AstValidator<T extends AstNode> {
|
|
21
|
+
/**
|
|
22
|
+
* Validates an AST node
|
|
23
|
+
*/
|
|
24
|
+
validate(node: T, accept: ValidationAcceptor): void;
|
|
25
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Attribute } from '@lang/generated/ast';
|
|
2
|
+
import { AstValidator } from '@lang/types';
|
|
3
|
+
import { ValidationAcceptor } from 'langium';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Validates attribute declarations.
|
|
7
|
+
*/
|
|
8
|
+
export default class AttributeValidator implements AstValidator<Attribute> {
|
|
9
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-function
|
|
10
|
+
validate(attr: Attribute, accept: ValidationAcceptor): void {}
|
|
11
|
+
}
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
import { SCALAR_TYPES } from '@lang/constants';
|
|
2
|
+
import {
|
|
3
|
+
ArrayExpr,
|
|
4
|
+
AttributeParam,
|
|
5
|
+
DataModel,
|
|
6
|
+
DataModelAttribute,
|
|
7
|
+
DataModelField,
|
|
8
|
+
DataModelFieldAttribute,
|
|
9
|
+
isDataModel,
|
|
10
|
+
isLiteralExpr,
|
|
11
|
+
ReferenceExpr,
|
|
12
|
+
} from '@lang/generated/ast';
|
|
13
|
+
import { AstValidator } from '@lang/types';
|
|
14
|
+
import { ValidationAcceptor } from 'langium';
|
|
15
|
+
import {
|
|
16
|
+
assignableToAttributeParam,
|
|
17
|
+
validateDuplicatedDeclarations,
|
|
18
|
+
} from './utils';
|
|
19
|
+
import pluralize from 'pluralize';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Validates data model declarations.
|
|
23
|
+
*/
|
|
24
|
+
export default class DataModelValidator implements AstValidator<DataModel> {
|
|
25
|
+
validate(dm: DataModel, accept: ValidationAcceptor): void {
|
|
26
|
+
validateDuplicatedDeclarations(dm.fields, accept);
|
|
27
|
+
this.validateFields(dm, accept);
|
|
28
|
+
this.validateAttributes(dm, accept);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
private validateFields(dm: DataModel, accept: ValidationAcceptor) {
|
|
32
|
+
const idFields = dm.fields.filter((f) =>
|
|
33
|
+
f.attributes.find((attr) => attr.decl.ref?.name === '@id')
|
|
34
|
+
);
|
|
35
|
+
if (idFields.length === 0) {
|
|
36
|
+
accept('error', 'Model must include a field with @id attribute', {
|
|
37
|
+
node: dm,
|
|
38
|
+
});
|
|
39
|
+
} else if (idFields.length > 1) {
|
|
40
|
+
accept(
|
|
41
|
+
'error',
|
|
42
|
+
'Model can include at most one field with @id attribute',
|
|
43
|
+
{
|
|
44
|
+
node: dm,
|
|
45
|
+
}
|
|
46
|
+
);
|
|
47
|
+
} else {
|
|
48
|
+
if (idFields[0].type.optional) {
|
|
49
|
+
accept(
|
|
50
|
+
'error',
|
|
51
|
+
'Field with @id attribute must not be optional',
|
|
52
|
+
{ node: idFields[0] }
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (
|
|
57
|
+
idFields[0].type.array ||
|
|
58
|
+
!idFields[0].type.type ||
|
|
59
|
+
!SCALAR_TYPES.includes(idFields[0].type.type)
|
|
60
|
+
) {
|
|
61
|
+
accept(
|
|
62
|
+
'error',
|
|
63
|
+
'Field with @id attribute must be of scalar type',
|
|
64
|
+
{ node: idFields[0] }
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
dm.fields.forEach((field) => this.validateField(field, accept));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
private validateField(
|
|
73
|
+
field: DataModelField,
|
|
74
|
+
accept: ValidationAcceptor
|
|
75
|
+
): void {
|
|
76
|
+
if (field.type.array && field.type.optional) {
|
|
77
|
+
accept(
|
|
78
|
+
'error',
|
|
79
|
+
'Optional lists are not supported. Use either `Type[]` or `Type?`',
|
|
80
|
+
{ node: field.type }
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
field.attributes.forEach((attr) =>
|
|
85
|
+
this.validateAttributeApplication(attr, accept)
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
if (isDataModel(field.type.reference?.ref)) {
|
|
89
|
+
this.validateRelationField(field, accept);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
private validateAttributes(dm: DataModel, accept: ValidationAcceptor) {
|
|
94
|
+
dm.attributes.forEach((attr) => {
|
|
95
|
+
this.validateAttributeApplication(attr, accept);
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
private validateAttributeApplication(
|
|
100
|
+
attr: DataModelAttribute | DataModelFieldAttribute,
|
|
101
|
+
accept: ValidationAcceptor
|
|
102
|
+
) {
|
|
103
|
+
const decl = attr.decl.ref;
|
|
104
|
+
if (!decl) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const filledParams = new Set<AttributeParam>();
|
|
109
|
+
|
|
110
|
+
for (const arg of attr.args) {
|
|
111
|
+
let paramDecl: AttributeParam | undefined;
|
|
112
|
+
if (!arg.name) {
|
|
113
|
+
paramDecl = decl.params.find(
|
|
114
|
+
(p) => p.default && !filledParams.has(p)
|
|
115
|
+
);
|
|
116
|
+
if (!paramDecl) {
|
|
117
|
+
accept('error', `Unexpected unnamed argument`, {
|
|
118
|
+
node: arg,
|
|
119
|
+
});
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
} else {
|
|
123
|
+
paramDecl = decl.params.find((p) => p.name === arg.name);
|
|
124
|
+
if (!paramDecl) {
|
|
125
|
+
accept(
|
|
126
|
+
'error',
|
|
127
|
+
`Attribute "${decl.name}" doesn't have a parameter named "${arg.name}"`,
|
|
128
|
+
{
|
|
129
|
+
node: arg,
|
|
130
|
+
}
|
|
131
|
+
);
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (!assignableToAttributeParam(arg, paramDecl, attr)) {
|
|
137
|
+
accept('error', `Value is not assignable to parameter`, {
|
|
138
|
+
node: arg,
|
|
139
|
+
});
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (filledParams.has(paramDecl)) {
|
|
144
|
+
accept(
|
|
145
|
+
'error',
|
|
146
|
+
`Parameter "${paramDecl.name}" is already provided`,
|
|
147
|
+
{ node: arg }
|
|
148
|
+
);
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
filledParams.add(paramDecl);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const missingParams = decl.params.filter(
|
|
155
|
+
(p) => !p.type.optional && !filledParams.has(p)
|
|
156
|
+
);
|
|
157
|
+
if (missingParams.length > 0) {
|
|
158
|
+
accept(
|
|
159
|
+
'error',
|
|
160
|
+
`Required ${pluralize(
|
|
161
|
+
'parameter',
|
|
162
|
+
missingParams.length
|
|
163
|
+
)} not provided: ${missingParams
|
|
164
|
+
.map((p) => p.name)
|
|
165
|
+
.join(', ')}`,
|
|
166
|
+
{ node: attr }
|
|
167
|
+
);
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return true;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
private parseRelation(field: DataModelField, accept?: ValidationAcceptor) {
|
|
175
|
+
const relAttr = field.attributes.find(
|
|
176
|
+
(attr) => attr.decl.ref?.name === '@relation'
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
let name: string | undefined;
|
|
180
|
+
let fields: ReferenceExpr[] | undefined;
|
|
181
|
+
let references: ReferenceExpr[] | undefined;
|
|
182
|
+
let valid = true;
|
|
183
|
+
|
|
184
|
+
if (!relAttr) {
|
|
185
|
+
return { attr: relAttr, name, fields, references, valid: true };
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
for (const arg of relAttr.args) {
|
|
189
|
+
if (!arg.name || arg.name === 'name') {
|
|
190
|
+
if (isLiteralExpr(arg.value)) {
|
|
191
|
+
name = arg.value.value as string;
|
|
192
|
+
}
|
|
193
|
+
} else if (arg.name === 'fields') {
|
|
194
|
+
fields = (arg.value as ArrayExpr).items as ReferenceExpr[];
|
|
195
|
+
if (fields.length === 0) {
|
|
196
|
+
if (accept) {
|
|
197
|
+
accept('error', `"fields" value cannot be emtpy`, {
|
|
198
|
+
node: arg,
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
valid = false;
|
|
202
|
+
}
|
|
203
|
+
} else if (arg.name === 'references') {
|
|
204
|
+
references = (arg.value as ArrayExpr).items as ReferenceExpr[];
|
|
205
|
+
if (references.length === 0) {
|
|
206
|
+
if (accept) {
|
|
207
|
+
accept('error', `"references" value cannot be emtpy`, {
|
|
208
|
+
node: arg,
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
valid = false;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return { attr: relAttr, name, fields, references, valid };
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
private validateRelationField(
|
|
220
|
+
field: DataModelField,
|
|
221
|
+
accept: ValidationAcceptor
|
|
222
|
+
) {
|
|
223
|
+
const thisRelation = this.parseRelation(field, accept);
|
|
224
|
+
if (!thisRelation.valid) {
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
229
|
+
const oppositeModel = field.type.reference!.ref! as DataModel;
|
|
230
|
+
|
|
231
|
+
let oppositeFields = oppositeModel.fields.filter(
|
|
232
|
+
(f) => f.type.reference?.ref === field.$container
|
|
233
|
+
);
|
|
234
|
+
oppositeFields = oppositeFields.filter((f) => {
|
|
235
|
+
const fieldRel = this.parseRelation(f);
|
|
236
|
+
return fieldRel.valid && fieldRel.name === thisRelation.name;
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
if (oppositeFields.length === 0) {
|
|
240
|
+
accept(
|
|
241
|
+
'error',
|
|
242
|
+
`The relation field "${field.name}" on model "${field.$container.name}" is missing an opposite relation field on model "${oppositeModel.name}"`,
|
|
243
|
+
{ node: field }
|
|
244
|
+
);
|
|
245
|
+
return;
|
|
246
|
+
} else if (oppositeFields.length > 1) {
|
|
247
|
+
oppositeFields.forEach((f) =>
|
|
248
|
+
accept(
|
|
249
|
+
'error',
|
|
250
|
+
`Fields ${oppositeFields
|
|
251
|
+
.map((f) => '"' + f.name + '"')
|
|
252
|
+
.join(', ')} on model "${
|
|
253
|
+
oppositeModel.name
|
|
254
|
+
}" refer to the same relation to model "${
|
|
255
|
+
field.$container.name
|
|
256
|
+
}"`,
|
|
257
|
+
{ node: f }
|
|
258
|
+
)
|
|
259
|
+
);
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const oppositeField = oppositeFields[0];
|
|
264
|
+
const oppositeRelation = this.parseRelation(oppositeField);
|
|
265
|
+
|
|
266
|
+
let relationOwner: DataModelField;
|
|
267
|
+
|
|
268
|
+
if (thisRelation?.references?.length && thisRelation.fields?.length) {
|
|
269
|
+
if (oppositeRelation?.references || oppositeRelation?.fields) {
|
|
270
|
+
accept(
|
|
271
|
+
'error',
|
|
272
|
+
'"fields" and "references" must be provided only on one side of relation field',
|
|
273
|
+
{ node: oppositeField }
|
|
274
|
+
);
|
|
275
|
+
return;
|
|
276
|
+
} else {
|
|
277
|
+
relationOwner = oppositeField;
|
|
278
|
+
}
|
|
279
|
+
} else if (
|
|
280
|
+
oppositeRelation?.references?.length &&
|
|
281
|
+
oppositeRelation.fields?.length
|
|
282
|
+
) {
|
|
283
|
+
if (thisRelation?.references || thisRelation?.fields) {
|
|
284
|
+
accept(
|
|
285
|
+
'error',
|
|
286
|
+
'"fields" and "references" must be provided only on one side of relation field',
|
|
287
|
+
{ node: field }
|
|
288
|
+
);
|
|
289
|
+
return;
|
|
290
|
+
} else {
|
|
291
|
+
relationOwner = field;
|
|
292
|
+
}
|
|
293
|
+
} else {
|
|
294
|
+
[field, oppositeField].forEach((f) =>
|
|
295
|
+
accept(
|
|
296
|
+
'error',
|
|
297
|
+
'Field for one side of relation must carry @relation attribute with both "fields" and "references" fields',
|
|
298
|
+
{ node: f }
|
|
299
|
+
)
|
|
300
|
+
);
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
if (!relationOwner.type.array && !relationOwner.type.optional) {
|
|
305
|
+
accept('error', 'Relation field needs to be list or optional', {
|
|
306
|
+
node: relationOwner,
|
|
307
|
+
});
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { DataSource, isInvocationExpr } from '@lang/generated/ast';
|
|
2
|
+
import { AstValidator } from '@lang/types';
|
|
3
|
+
import { ValidationAcceptor } from 'langium';
|
|
4
|
+
import { getStringLiteral, validateDuplicatedDeclarations } from './utils';
|
|
5
|
+
import { SUPPORTED_PROVIDERS } from '../constants';
|
|
6
|
+
|
|
7
|
+
const supportedFields = [
|
|
8
|
+
'provider',
|
|
9
|
+
'url',
|
|
10
|
+
'shadowDatabaseUrl',
|
|
11
|
+
'relationMode',
|
|
12
|
+
];
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Validates data source declarations.
|
|
16
|
+
*/
|
|
17
|
+
export default class DataSourceValidator implements AstValidator<DataSource> {
|
|
18
|
+
validate(ds: DataSource, accept: ValidationAcceptor): void {
|
|
19
|
+
validateDuplicatedDeclarations(ds.fields, accept);
|
|
20
|
+
this.validateFields(ds, accept);
|
|
21
|
+
this.validateProvider(ds, accept);
|
|
22
|
+
this.validateUrl(ds, accept);
|
|
23
|
+
this.validateRelationMode(ds, accept);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
private validateFields(ds: DataSource, accept: ValidationAcceptor) {
|
|
27
|
+
ds.fields.forEach((f) => {
|
|
28
|
+
if (!supportedFields.includes(f.name)) {
|
|
29
|
+
accept('error', `Unexpected field "${f.name}"`, { node: f });
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
private validateProvider(ds: DataSource, accept: ValidationAcceptor) {
|
|
35
|
+
const provider = ds.fields.find((f) => f.name === 'provider');
|
|
36
|
+
if (!provider) {
|
|
37
|
+
accept('error', 'datasource must include a "provider" field', {
|
|
38
|
+
node: ds,
|
|
39
|
+
});
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const value = getStringLiteral(provider.value);
|
|
44
|
+
if (!value) {
|
|
45
|
+
accept('error', '"provider" must be set to a string literal', {
|
|
46
|
+
node: provider.value,
|
|
47
|
+
});
|
|
48
|
+
} else if (!SUPPORTED_PROVIDERS.includes(value)) {
|
|
49
|
+
accept(
|
|
50
|
+
'error',
|
|
51
|
+
`Provider "${value}" is not supported. Choose from ${SUPPORTED_PROVIDERS.map(
|
|
52
|
+
(p) => '"' + p + '"'
|
|
53
|
+
).join(' | ')}.`,
|
|
54
|
+
{ node: provider.value }
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
private validateUrl(ds: DataSource, accept: ValidationAcceptor) {
|
|
60
|
+
const url = ds.fields.find((f) => f.name === 'url');
|
|
61
|
+
if (!url) {
|
|
62
|
+
accept('error', 'datasource must include a "url" field', {
|
|
63
|
+
node: ds,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
for (const fieldName of ['url', 'shadowDatabaseUrl']) {
|
|
68
|
+
const field = ds.fields.find((f) => f.name === fieldName);
|
|
69
|
+
if (!field) {
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
const value = getStringLiteral(field.value);
|
|
73
|
+
if (
|
|
74
|
+
!value &&
|
|
75
|
+
!(
|
|
76
|
+
isInvocationExpr(field.value) &&
|
|
77
|
+
field.value.function.ref?.name === 'env'
|
|
78
|
+
)
|
|
79
|
+
) {
|
|
80
|
+
accept(
|
|
81
|
+
'error',
|
|
82
|
+
`"${fieldName}" must be set to a string literal or an invocation of "env" function`,
|
|
83
|
+
{ node: field.value }
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
private validateRelationMode(ds: DataSource, accept: ValidationAcceptor) {
|
|
90
|
+
const field = ds.fields.find((f) => f.name === 'relationMode');
|
|
91
|
+
if (field) {
|
|
92
|
+
const val = getStringLiteral(field.value);
|
|
93
|
+
if (!val || !['foreignKeys', 'prisma'].includes(val)) {
|
|
94
|
+
accept(
|
|
95
|
+
'error',
|
|
96
|
+
'"relationMode" must be set to "foreignKeys" or "prisma"',
|
|
97
|
+
{ node: field.value }
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Enum } from '@lang/generated/ast';
|
|
2
|
+
import { AstValidator } from '@lang/types';
|
|
3
|
+
import { ValidationAcceptor } from 'langium';
|
|
4
|
+
import { validateDuplicatedDeclarations } from './utils';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Validates enum declarations.
|
|
8
|
+
*/
|
|
9
|
+
export default class EnumValidator implements AstValidator<Enum> {
|
|
10
|
+
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
|
11
|
+
validate(_enum: Enum, accept: ValidationAcceptor) {
|
|
12
|
+
validateDuplicatedDeclarations(_enum.fields, accept);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { STD_LIB_MODULE_NAME } from '@lang/constants';
|
|
2
|
+
import { isDataSource, Model } from '@lang/generated/ast';
|
|
3
|
+
import { AstValidator } from '@lang/types';
|
|
4
|
+
import { ValidationAcceptor } from 'langium';
|
|
5
|
+
import { validateDuplicatedDeclarations } from './utils';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Validates toplevel schema.
|
|
9
|
+
*/
|
|
10
|
+
export default class SchemaValidator implements AstValidator<Model> {
|
|
11
|
+
validate(model: Model, accept: ValidationAcceptor): void {
|
|
12
|
+
validateDuplicatedDeclarations(model.declarations, accept);
|
|
13
|
+
|
|
14
|
+
if (!model.$document?.uri.path.endsWith(STD_LIB_MODULE_NAME)) {
|
|
15
|
+
this.validateDataSources(model, accept);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
private validateDataSources(model: Model, accept: ValidationAcceptor) {
|
|
20
|
+
const dataSources = model.declarations.filter((d) => isDataSource(d));
|
|
21
|
+
if (dataSources.length === 0) {
|
|
22
|
+
accept('error', 'Model must define a datasource', { node: model });
|
|
23
|
+
} else if (dataSources.length > 1) {
|
|
24
|
+
accept(
|
|
25
|
+
'error',
|
|
26
|
+
'Multiple datasource declarations are not allowed',
|
|
27
|
+
{ node: dataSources[1] }
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|