diff --git a/README.md b/README.md
index 8d2d3879d..e3b910b38 100644
--- a/README.md
+++ b/README.md
@@ -9,6 +9,9 @@
+
+
+
diff --git a/packages/language/src/generated/ast.ts b/packages/language/src/generated/ast.ts
index 1f4b25b84..7463fb9da 100644
--- a/packages/language/src/generated/ast.ts
+++ b/packages/language/src/generated/ast.ts
@@ -14,12 +14,6 @@ export function isAbstractDeclaration(item: unknown): item is AbstractDeclaratio
return reflection.isInstance(item, AbstractDeclaration);
}
-export type AttributeName = DataModelAttributeName | DataModelFieldAttributeName | InternalAttributeName;
-
-export function isAttributeName(item: unknown): item is AttributeName {
- return isDataModelAttributeName(item) || isDataModelFieldAttributeName(item) || isInternalAttributeName(item);
-}
-
export type Boolean = boolean;
export function isBoolean(item: unknown): item is Boolean {
@@ -40,18 +34,6 @@ export function isConfigExpr(item: unknown): item is ConfigExpr {
return reflection.isInstance(item, ConfigExpr);
}
-export type DataModelAttributeName = string;
-
-export function isDataModelAttributeName(item: unknown): item is DataModelAttributeName {
- return typeof item === 'string';
-}
-
-export type DataModelFieldAttributeName = string;
-
-export function isDataModelFieldAttributeName(item: unknown): item is DataModelFieldAttributeName {
- return typeof item === 'string';
-}
-
export type Expression = ArrayExpr | BinaryExpr | InvocationExpr | LiteralExpr | MemberAccessExpr | NullExpr | ObjectExpr | ReferenceExpr | ThisExpr | UnaryExpr;
export const Expression = 'Expression';
@@ -66,12 +48,6 @@ export function isExpressionType(item: unknown): item is ExpressionType {
return item === 'String' || item === 'Int' || item === 'Float' || item === 'Boolean' || item === 'DateTime' || item === 'Null' || item === 'Object' || item === 'Any' || item === 'Unsupported';
}
-export type InternalAttributeName = string;
-
-export function isInternalAttributeName(item: unknown): item is InternalAttributeName {
- return typeof item === 'string';
-}
-
export type LiteralExpr = BooleanLiteral | NumberLiteral | StringLiteral;
export const LiteralExpr = 'LiteralExpr';
@@ -80,12 +56,6 @@ export function isLiteralExpr(item: unknown): item is LiteralExpr {
return reflection.isInstance(item, LiteralExpr);
}
-export type QualifiedName = string;
-
-export function isQualifiedName(item: unknown): item is QualifiedName {
- return typeof item === 'string';
-}
-
export type ReferenceTarget = DataModelField | EnumField | FunctionParam;
export const ReferenceTarget = 'ReferenceTarget';
@@ -137,7 +107,8 @@ export interface Attribute extends AstNode {
readonly $container: Model;
readonly $type: 'Attribute';
attributes: Array
- name: AttributeName
+ comments: Array
+ name: string
params: Array
}
@@ -163,6 +134,8 @@ export function isAttributeArg(item: unknown): item is AttributeArg {
export interface AttributeParam extends AstNode {
readonly $container: Attribute;
readonly $type: 'AttributeParam';
+ attributes: Array
+ comments: Array
default: boolean
name: RegularID
type: AttributeParamType
@@ -454,7 +427,7 @@ export function isGeneratorDecl(item: unknown): item is GeneratorDecl {
}
export interface InternalAttribute extends AstNode {
- readonly $container: Attribute | FunctionDecl;
+ readonly $container: Attribute | AttributeParam | FunctionDecl;
readonly $type: 'InternalAttribute';
args: Array
decl: Reference
@@ -519,7 +492,7 @@ export function isModelImport(item: unknown): item is ModelImport {
export interface NullExpr extends AstNode {
readonly $container: Argument | ArrayExpr | AttributeArg | BinaryExpr | ConfigArrayExpr | ConfigField | ConfigInvocationArg | FieldInitializer | FunctionDecl | MemberAccessExpr | PluginField | UnaryExpr | UnsupportedFieldType;
readonly $type: 'NullExpr';
- value: string
+ value: 'null'
}
export const NullExpr = 'NullExpr';
@@ -619,7 +592,7 @@ export function isStringLiteral(item: unknown): item is StringLiteral {
export interface ThisExpr extends AstNode {
readonly $container: Argument | ArrayExpr | AttributeArg | BinaryExpr | ConfigArrayExpr | ConfigField | ConfigInvocationArg | FieldInitializer | FunctionDecl | MemberAccessExpr | PluginField | UnaryExpr | UnsupportedFieldType;
readonly $type: 'ThisExpr';
- value: string
+ value: 'this'
}
export const ThisExpr = 'ThisExpr';
@@ -801,6 +774,7 @@ export class ZModelAstReflection extends AbstractAstReflection {
name: 'Attribute',
mandatory: [
{ name: 'attributes', type: 'array' },
+ { name: 'comments', type: 'array' },
{ name: 'params', type: 'array' }
]
};
@@ -809,6 +783,8 @@ export class ZModelAstReflection extends AbstractAstReflection {
return {
name: 'AttributeParam',
mandatory: [
+ { name: 'attributes', type: 'array' },
+ { name: 'comments', type: 'array' },
{ name: 'default', type: 'boolean' }
]
};
diff --git a/packages/language/src/generated/grammar.ts b/packages/language/src/generated/grammar.ts
index 9ae762b6a..5dbe02014 100644
--- a/packages/language/src/generated/grammar.ts
+++ b/packages/language/src/generated/grammar.ts
@@ -69,7 +69,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel
"terminal": {
"$type": "RuleCall",
"rule": {
- "$ref": "#/rules@67"
+ "$ref": "#/rules@63"
},
"arguments": []
}
@@ -139,7 +139,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel
{
"$type": "RuleCall",
"rule": {
- "$ref": "#/rules@52"
+ "$ref": "#/rules@47"
},
"arguments": []
}
@@ -161,7 +161,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel
{
"$type": "RuleCall",
"rule": {
- "$ref": "#/rules@69"
+ "$ref": "#/rules@65"
},
"arguments": [],
"cardinality": "*"
@@ -177,7 +177,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel
"terminal": {
"$type": "RuleCall",
"rule": {
- "$ref": "#/rules@47"
+ "$ref": "#/rules@46"
},
"arguments": []
}
@@ -221,7 +221,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel
{
"$type": "RuleCall",
"rule": {
- "$ref": "#/rules@69"
+ "$ref": "#/rules@65"
},
"arguments": [],
"cardinality": "*"
@@ -237,7 +237,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel
"terminal": {
"$type": "RuleCall",
"rule": {
- "$ref": "#/rules@47"
+ "$ref": "#/rules@46"
},
"arguments": []
}
@@ -281,7 +281,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel
{
"$type": "RuleCall",
"rule": {
- "$ref": "#/rules@69"
+ "$ref": "#/rules@65"
},
"arguments": [],
"cardinality": "*"
@@ -293,7 +293,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel
"terminal": {
"$type": "RuleCall",
"rule": {
- "$ref": "#/rules@47"
+ "$ref": "#/rules@46"
},
"arguments": []
}
@@ -332,7 +332,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel
{
"$type": "RuleCall",
"rule": {
- "$ref": "#/rules@69"
+ "$ref": "#/rules@65"
},
"arguments": [],
"cardinality": "*"
@@ -348,7 +348,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel
"terminal": {
"$type": "RuleCall",
"rule": {
- "$ref": "#/rules@47"
+ "$ref": "#/rules@46"
},
"arguments": []
}
@@ -392,7 +392,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel
{
"$type": "RuleCall",
"rule": {
- "$ref": "#/rules@69"
+ "$ref": "#/rules@65"
},
"arguments": [],
"cardinality": "*"
@@ -404,7 +404,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel
"terminal": {
"$type": "RuleCall",
"rule": {
- "$ref": "#/rules@47"
+ "$ref": "#/rules@46"
},
"arguments": []
}
@@ -480,7 +480,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel
"terminal": {
"$type": "RuleCall",
"rule": {
- "$ref": "#/rules@68"
+ "$ref": "#/rules@64"
},
"arguments": []
}
@@ -502,7 +502,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel
"terminal": {
"$type": "RuleCall",
"rule": {
- "$ref": "#/rules@67"
+ "$ref": "#/rules@63"
},
"arguments": []
}
@@ -524,7 +524,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel
"terminal": {
"$type": "RuleCall",
"rule": {
- "$ref": "#/rules@62"
+ "$ref": "#/rules@57"
},
"arguments": []
}
@@ -648,7 +648,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel
"terminal": {
"$type": "RuleCall",
"rule": {
- "$ref": "#/rules@66"
+ "$ref": "#/rules@62"
},
"arguments": []
}
@@ -746,7 +746,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel
"terminal": {
"$type": "RuleCall",
"rule": {
- "$ref": "#/rules@66"
+ "$ref": "#/rules@62"
},
"arguments": []
}
@@ -907,11 +907,8 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel
"feature": "value",
"operator": "=",
"terminal": {
- "$type": "RuleCall",
- "rule": {
- "$ref": "#/rules@65"
- },
- "arguments": []
+ "$type": "Keyword",
+ "value": "this"
}
},
"definesHiddenTokens": false,
@@ -929,11 +926,8 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel
"feature": "value",
"operator": "=",
"terminal": {
- "$type": "RuleCall",
- "rule": {
- "$ref": "#/rules@64"
- },
- "arguments": []
+ "$type": "Keyword",
+ "value": "null"
}
},
"definesHiddenTokens": false,
@@ -961,7 +955,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel
"terminal": {
"$type": "RuleCall",
"rule": {
- "$ref": "#/rules@47"
+ "$ref": "#/rules@46"
},
"arguments": []
},
@@ -1177,14 +1171,14 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel
{
"$type": "RuleCall",
"rule": {
- "$ref": "#/rules@47"
+ "$ref": "#/rules@46"
},
"arguments": []
},
{
"$type": "RuleCall",
"rule": {
- "$ref": "#/rules@67"
+ "$ref": "#/rules@63"
},
"arguments": []
}
@@ -1883,7 +1877,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel
"terminal": {
"$type": "RuleCall",
"rule": {
- "$ref": "#/rules@47"
+ "$ref": "#/rules@46"
},
"arguments": []
}
@@ -1929,7 +1923,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel
"terminal": {
"$type": "RuleCall",
"rule": {
- "$ref": "#/rules@69"
+ "$ref": "#/rules@65"
},
"arguments": []
},
@@ -1962,7 +1956,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel
"terminal": {
"$type": "RuleCall",
"rule": {
- "$ref": "#/rules@47"
+ "$ref": "#/rules@46"
},
"arguments": []
}
@@ -2032,7 +2026,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel
"terminal": {
"$type": "RuleCall",
"rule": {
- "$ref": "#/rules@47"
+ "$ref": "#/rules@46"
},
"arguments": []
}
@@ -2067,7 +2061,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel
"terminal": {
"$type": "RuleCall",
"rule": {
- "$ref": "#/rules@56"
+ "$ref": "#/rules@51"
},
"arguments": []
}
@@ -2101,7 +2095,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel
"terminal": {
"$type": "RuleCall",
"rule": {
- "$ref": "#/rules@69"
+ "$ref": "#/rules@65"
},
"arguments": []
},
@@ -2114,7 +2108,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel
"terminal": {
"$type": "RuleCall",
"rule": {
- "$ref": "#/rules@47"
+ "$ref": "#/rules@46"
},
"arguments": []
}
@@ -2138,7 +2132,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel
"terminal": {
"$type": "RuleCall",
"rule": {
- "$ref": "#/rules@55"
+ "$ref": "#/rules@50"
},
"arguments": []
},
@@ -2169,7 +2163,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel
"terminal": {
"$type": "RuleCall",
"rule": {
- "$ref": "#/rules@61"
+ "$ref": "#/rules@56"
},
"arguments": []
}
@@ -2198,7 +2192,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel
"terminal": {
"$type": "RuleCall",
"rule": {
- "$ref": "#/rules@47"
+ "$ref": "#/rules@46"
},
"arguments": []
},
@@ -2297,7 +2291,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel
"terminal": {
"$type": "RuleCall",
"rule": {
- "$ref": "#/rules@69"
+ "$ref": "#/rules@65"
},
"arguments": []
},
@@ -2314,7 +2308,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel
"terminal": {
"$type": "RuleCall",
"rule": {
- "$ref": "#/rules@47"
+ "$ref": "#/rules@46"
},
"arguments": []
}
@@ -2345,7 +2339,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel
"terminal": {
"$type": "RuleCall",
"rule": {
- "$ref": "#/rules@56"
+ "$ref": "#/rules@51"
},
"arguments": []
}
@@ -2379,7 +2373,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel
"terminal": {
"$type": "RuleCall",
"rule": {
- "$ref": "#/rules@69"
+ "$ref": "#/rules@65"
},
"arguments": []
},
@@ -2392,7 +2386,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel
"terminal": {
"$type": "RuleCall",
"rule": {
- "$ref": "#/rules@47"
+ "$ref": "#/rules@46"
},
"arguments": []
}
@@ -2404,7 +2398,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel
"terminal": {
"$type": "RuleCall",
"rule": {
- "$ref": "#/rules@55"
+ "$ref": "#/rules@50"
},
"arguments": []
},
@@ -2428,7 +2422,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel
{
"$type": "RuleCall",
"rule": {
- "$ref": "#/rules@69"
+ "$ref": "#/rules@65"
},
"arguments": [],
"cardinality": "*"
@@ -2444,7 +2438,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel
"terminal": {
"$type": "RuleCall",
"rule": {
- "$ref": "#/rules@47"
+ "$ref": "#/rules@46"
},
"arguments": []
}
@@ -2541,7 +2535,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel
"terminal": {
"$type": "RuleCall",
"rule": {
- "$ref": "#/rules@57"
+ "$ref": "#/rules@52"
},
"arguments": []
},
@@ -2565,7 +2559,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel
{
"$type": "RuleCall",
"rule": {
- "$ref": "#/rules@69"
+ "$ref": "#/rules@65"
},
"arguments": [],
"cardinality": "*"
@@ -2577,7 +2571,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel
"terminal": {
"$type": "RuleCall",
"rule": {
- "$ref": "#/rules@47"
+ "$ref": "#/rules@46"
},
"arguments": []
}
@@ -2633,7 +2627,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel
"terminal": {
"$type": "RuleCall",
"rule": {
- "$ref": "#/rules@60"
+ "$ref": "#/rules@55"
},
"arguments": []
}
@@ -2650,7 +2644,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel
"terminal": {
"$type": "RuleCall",
"rule": {
- "$ref": "#/rules@47"
+ "$ref": "#/rules@46"
},
"arguments": []
},
@@ -2687,58 +2681,6 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel
"parameters": [],
"wildcard": false
},
- {
- "$type": "ParserRule",
- "name": "QualifiedName",
- "dataType": "string",
- "definition": {
- "$type": "Group",
- "elements": [
- {
- "$type": "RuleCall",
- "rule": {
- "$ref": "#/rules@66"
- },
- "arguments": []
- },
- {
- "$type": "Group",
- "elements": [
- {
- "$type": "Keyword",
- "value": "."
- },
- {
- "$type": "Alternatives",
- "elements": [
- {
- "$type": "RuleCall",
- "rule": {
- "$ref": "#/rules@66"
- },
- "arguments": []
- },
- {
- "$type": "RuleCall",
- "rule": {
- "$ref": "#/rules@61"
- },
- "arguments": []
- }
- ]
- }
- ],
- "cardinality": "*"
- }
- ]
- },
- "definesHiddenTokens": false,
- "entry": false,
- "fragment": false,
- "hiddenTokens": [],
- "parameters": [],
- "wildcard": false
- },
{
"$type": "ParserRule",
"name": "RegularID",
@@ -2749,7 +2691,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel
{
"$type": "RuleCall",
"rule": {
- "$ref": "#/rules@66"
+ "$ref": "#/rules@62"
},
"arguments": []
},
@@ -2802,124 +2744,6 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel
"parameters": [],
"wildcard": false
},
- {
- "$type": "ParserRule",
- "name": "InternalAttributeName",
- "dataType": "string",
- "definition": {
- "$type": "Group",
- "elements": [
- {
- "$type": "Keyword",
- "value": "@@@"
- },
- {
- "$type": "RuleCall",
- "rule": {
- "$ref": "#/rules@46"
- },
- "arguments": []
- }
- ]
- },
- "definesHiddenTokens": false,
- "entry": false,
- "fragment": false,
- "hiddenTokens": [],
- "parameters": [],
- "wildcard": false
- },
- {
- "$type": "ParserRule",
- "name": "DataModelAttributeName",
- "dataType": "string",
- "definition": {
- "$type": "Group",
- "elements": [
- {
- "$type": "Keyword",
- "value": "@@"
- },
- {
- "$type": "RuleCall",
- "rule": {
- "$ref": "#/rules@46"
- },
- "arguments": []
- }
- ]
- },
- "definesHiddenTokens": false,
- "entry": false,
- "fragment": false,
- "hiddenTokens": [],
- "parameters": [],
- "wildcard": false
- },
- {
- "$type": "ParserRule",
- "name": "DataModelFieldAttributeName",
- "dataType": "string",
- "definition": {
- "$type": "Group",
- "elements": [
- {
- "$type": "Keyword",
- "value": "@"
- },
- {
- "$type": "RuleCall",
- "rule": {
- "$ref": "#/rules@46"
- },
- "arguments": []
- }
- ]
- },
- "definesHiddenTokens": false,
- "entry": false,
- "fragment": false,
- "hiddenTokens": [],
- "parameters": [],
- "wildcard": false
- },
- {
- "$type": "ParserRule",
- "name": "AttributeName",
- "dataType": "string",
- "definition": {
- "$type": "Alternatives",
- "elements": [
- {
- "$type": "RuleCall",
- "rule": {
- "$ref": "#/rules@49"
- },
- "arguments": []
- },
- {
- "$type": "RuleCall",
- "rule": {
- "$ref": "#/rules@50"
- },
- "arguments": []
- },
- {
- "$type": "RuleCall",
- "rule": {
- "$ref": "#/rules@48"
- },
- "arguments": []
- }
- ]
- },
- "definesHiddenTokens": false,
- "entry": false,
- "fragment": false,
- "hiddenTokens": [],
- "parameters": [],
- "wildcard": false
- },
{
"$type": "ParserRule",
"name": "Attribute",
@@ -2927,11 +2751,16 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel
"$type": "Group",
"elements": [
{
- "$type": "RuleCall",
- "rule": {
- "$ref": "#/rules@69"
+ "$type": "Assignment",
+ "feature": "comments",
+ "operator": "+=",
+ "terminal": {
+ "$type": "RuleCall",
+ "rule": {
+ "$ref": "#/rules@65"
+ },
+ "arguments": []
},
- "arguments": [],
"cardinality": "*"
},
{
@@ -2943,11 +2772,30 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel
"feature": "name",
"operator": "=",
"terminal": {
- "$type": "RuleCall",
- "rule": {
- "$ref": "#/rules@51"
- },
- "arguments": []
+ "$type": "Alternatives",
+ "elements": [
+ {
+ "$type": "RuleCall",
+ "rule": {
+ "$ref": "#/rules@59"
+ },
+ "arguments": []
+ },
+ {
+ "$type": "RuleCall",
+ "rule": {
+ "$ref": "#/rules@60"
+ },
+ "arguments": []
+ },
+ {
+ "$type": "RuleCall",
+ "rule": {
+ "$ref": "#/rules@61"
+ },
+ "arguments": []
+ }
+ ]
}
},
{
@@ -2964,7 +2812,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel
"terminal": {
"$type": "RuleCall",
"rule": {
- "$ref": "#/rules@53"
+ "$ref": "#/rules@48"
},
"arguments": []
}
@@ -2983,7 +2831,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel
"terminal": {
"$type": "RuleCall",
"rule": {
- "$ref": "#/rules@53"
+ "$ref": "#/rules@48"
},
"arguments": []
}
@@ -3005,7 +2853,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel
"terminal": {
"$type": "RuleCall",
"rule": {
- "$ref": "#/rules@57"
+ "$ref": "#/rules@52"
},
"arguments": []
},
@@ -3027,11 +2875,16 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel
"$type": "Group",
"elements": [
{
- "$type": "RuleCall",
- "rule": {
- "$ref": "#/rules@69"
+ "$type": "Assignment",
+ "feature": "comments",
+ "operator": "+=",
+ "terminal": {
+ "$type": "RuleCall",
+ "rule": {
+ "$ref": "#/rules@65"
+ },
+ "arguments": []
},
- "arguments": [],
"cardinality": "*"
},
{
@@ -3051,7 +2904,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel
"terminal": {
"$type": "RuleCall",
"rule": {
- "$ref": "#/rules@47"
+ "$ref": "#/rules@46"
},
"arguments": []
}
@@ -3067,10 +2920,23 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel
"terminal": {
"$type": "RuleCall",
"rule": {
- "$ref": "#/rules@54"
+ "$ref": "#/rules@49"
},
"arguments": []
}
+ },
+ {
+ "$type": "Assignment",
+ "feature": "attributes",
+ "operator": "+=",
+ "terminal": {
+ "$type": "RuleCall",
+ "rule": {
+ "$ref": "#/rules@52"
+ },
+ "arguments": []
+ },
+ "cardinality": "*"
}
]
},
@@ -3100,7 +2966,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel
{
"$type": "RuleCall",
"rule": {
- "$ref": "#/rules@60"
+ "$ref": "#/rules@55"
},
"arguments": []
},
@@ -3131,7 +2997,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel
"terminal": {
"$type": "RuleCall",
"rule": {
- "$ref": "#/rules@47"
+ "$ref": "#/rules@46"
},
"arguments": []
},
@@ -3191,12 +3057,12 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel
"terminal": {
"$type": "CrossReference",
"type": {
- "$ref": "#/rules@52"
+ "$ref": "#/rules@47"
},
"terminal": {
"$type": "RuleCall",
"rule": {
- "$ref": "#/rules@50"
+ "$ref": "#/rules@61"
},
"arguments": []
},
@@ -3213,7 +3079,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel
{
"$type": "RuleCall",
"rule": {
- "$ref": "#/rules@58"
+ "$ref": "#/rules@53"
},
"arguments": [],
"cardinality": "?"
@@ -3243,7 +3109,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel
{
"$type": "RuleCall",
"rule": {
- "$ref": "#/rules@69"
+ "$ref": "#/rules@65"
},
"arguments": [],
"cardinality": "*"
@@ -3255,12 +3121,12 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel
"terminal": {
"$type": "CrossReference",
"type": {
- "$ref": "#/rules@52"
+ "$ref": "#/rules@47"
},
"terminal": {
"$type": "RuleCall",
"rule": {
- "$ref": "#/rules@49"
+ "$ref": "#/rules@60"
},
"arguments": []
},
@@ -3277,7 +3143,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel
{
"$type": "RuleCall",
"rule": {
- "$ref": "#/rules@58"
+ "$ref": "#/rules@53"
},
"arguments": [],
"cardinality": "?"
@@ -3311,12 +3177,12 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel
"terminal": {
"$type": "CrossReference",
"type": {
- "$ref": "#/rules@52"
+ "$ref": "#/rules@47"
},
"terminal": {
"$type": "RuleCall",
"rule": {
- "$ref": "#/rules@48"
+ "$ref": "#/rules@59"
},
"arguments": []
},
@@ -3333,7 +3199,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel
{
"$type": "RuleCall",
"rule": {
- "$ref": "#/rules@58"
+ "$ref": "#/rules@53"
},
"arguments": [],
"cardinality": "?"
@@ -3368,7 +3234,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel
"terminal": {
"$type": "RuleCall",
"rule": {
- "$ref": "#/rules@59"
+ "$ref": "#/rules@54"
},
"arguments": []
}
@@ -3387,7 +3253,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel
"terminal": {
"$type": "RuleCall",
"rule": {
- "$ref": "#/rules@59"
+ "$ref": "#/rules@54"
},
"arguments": []
}
@@ -3419,7 +3285,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel
"terminal": {
"$type": "RuleCall",
"rule": {
- "$ref": "#/rules@47"
+ "$ref": "#/rules@46"
},
"arguments": []
}
@@ -3592,26 +3458,30 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel
},
{
"$type": "TerminalRule",
- "name": "NULL",
+ "name": "INTERNAL_ATTRIBUTE_NAME",
"definition": {
- "$type": "CharacterRange",
- "left": {
- "$type": "Keyword",
- "value": "null"
- }
+ "$type": "RegexToken",
+ "regex": "@@@([_a-zA-Z][\\\\w_]*\\\\.)*[_a-zA-Z][\\\\w_]*"
},
"fragment": false,
"hidden": false
},
{
"$type": "TerminalRule",
- "name": "THIS",
+ "name": "MODEL_ATTRIBUTE_NAME",
"definition": {
- "$type": "CharacterRange",
- "left": {
- "$type": "Keyword",
- "value": "this"
- }
+ "$type": "RegexToken",
+ "regex": "@@([_a-zA-Z][\\\\w_]*\\\\.)*[_a-zA-Z][\\\\w_]*"
+ },
+ "fragment": false,
+ "hidden": false
+ },
+ {
+ "$type": "TerminalRule",
+ "name": "FIELD_ATTRIBUTE_NAME",
+ "definition": {
+ "$type": "RegexToken",
+ "regex": "@([_a-zA-Z][\\\\w_]*\\\\.)*[_a-zA-Z][\\\\w_]*"
},
"fragment": false,
"hidden": false
diff --git a/packages/language/src/zmodel.langium b/packages/language/src/zmodel.langium
index 6760bb87d..da445c792 100644
--- a/packages/language/src/zmodel.langium
+++ b/packages/language/src/zmodel.langium
@@ -68,10 +68,10 @@ ConfigExpr:
type ReferenceTarget = FunctionParam | DataModelField | EnumField;
ThisExpr:
- value=THIS;
+ value='this';
NullExpr:
- value=NULL;
+ value='null';
ReferenceExpr:
target=[ReferenceTarget:RegularID] ('(' ReferenceArgList ')')?;
@@ -221,36 +221,17 @@ FunctionParam:
FunctionParamType:
(type=ExpressionType | reference=[TypeDeclaration:RegularID]) (array?='[' ']')?;
-QualifiedName returns string:
- // TODO: is this the right way to deal with token precedence?
- ID ('.' (ID|BuiltinType))*;
-
// https://github.com/langium/langium/discussions/1012
RegularID returns string:
// include keywords that we'd like to work as ID in most places
ID | 'model' | 'enum' | 'attribute' | 'datasource' | 'plugin' | 'abstract' | 'in' | 'sort' | 'view' | 'import';
-// internal attribute
-InternalAttributeName returns string:
- '@@@' QualifiedName;
-
-// model-level attribute
-DataModelAttributeName returns string:
- '@@' QualifiedName;
-
-// field-level attribute
-DataModelFieldAttributeName returns string:
- '@' QualifiedName;
-
-AttributeName returns string:
- DataModelAttributeName | DataModelFieldAttributeName | InternalAttributeName;
-
// attribute
Attribute:
- TRIPLE_SLASH_COMMENT* 'attribute' name=AttributeName '(' (params+=AttributeParam (',' params+=AttributeParam)*)? ')' (attributes+=InternalAttribute)*;
+ (comments+=TRIPLE_SLASH_COMMENT)* 'attribute' name=(INTERNAL_ATTRIBUTE_NAME|MODEL_ATTRIBUTE_NAME|FIELD_ATTRIBUTE_NAME) '(' (params+=AttributeParam (',' params+=AttributeParam)*)? ')' (attributes+=InternalAttribute)*;
AttributeParam:
- TRIPLE_SLASH_COMMENT* (default?='_')? name=RegularID ':' type=AttributeParamType;
+ (comments+=TRIPLE_SLASH_COMMENT)* (default?='_')? name=RegularID ':' type=AttributeParamType (attributes+=InternalAttribute)*;
// FieldReference refers to fields declared in the current model
// TransitiveFieldReference refers to fields declared in the model type of the current field
@@ -259,13 +240,13 @@ AttributeParamType:
type TypeDeclaration = DataModel | Enum;
DataModelFieldAttribute:
- decl=[Attribute:DataModelFieldAttributeName] ('(' AttributeArgList? ')')?;
+ decl=[Attribute:FIELD_ATTRIBUTE_NAME] ('(' AttributeArgList? ')')?;
DataModelAttribute:
- TRIPLE_SLASH_COMMENT* decl=[Attribute:DataModelAttributeName] ('(' AttributeArgList? ')')?;
+ TRIPLE_SLASH_COMMENT* decl=[Attribute:MODEL_ATTRIBUTE_NAME] ('(' AttributeArgList? ')')?;
InternalAttribute:
- decl=[Attribute:InternalAttributeName] ('(' AttributeArgList? ')')?;
+ decl=[Attribute:INTERNAL_ATTRIBUTE_NAME] ('(' AttributeArgList? ')')?;
fragment AttributeArgList:
args+=AttributeArg (',' args+=AttributeArg)*;
@@ -283,8 +264,9 @@ Boolean returns boolean:
'true' | 'false';
hidden terminal WS: /\s+/;
-terminal NULL: 'null';
-terminal THIS: 'this';
+terminal INTERNAL_ATTRIBUTE_NAME: /@@@([_a-zA-Z][\w_]*\.)*[_a-zA-Z][\w_]*/;
+terminal MODEL_ATTRIBUTE_NAME: /@@([_a-zA-Z][\w_]*\.)*[_a-zA-Z][\w_]*/;
+terminal FIELD_ATTRIBUTE_NAME: /@([_a-zA-Z][\w_]*\.)*[_a-zA-Z][\w_]*/;
terminal ID: /[_a-zA-Z][\w_]*/;
terminal STRING: /"(\\.|[^"\\])*"|'(\\.|[^'\\])*'/;
terminal NUMBER: /[+-]?[0-9]+(\.[0-9]+)?/;
diff --git a/packages/language/syntaxes/zmodel.tmLanguage b/packages/language/syntaxes/zmodel.tmLanguage
index bacb471c3..cf70fb761 100644
--- a/packages/language/syntaxes/zmodel.tmLanguage
+++ b/packages/language/syntaxes/zmodel.tmLanguage
@@ -20,7 +20,7 @@
name
keyword.control.zmodel
match
- \b(Any|Asc|BigInt|Boolean|Bytes|ContextType|DateTime|Decimal|Desc|FieldReference|Float|Int|Json|Null|Object|String|TransitiveFieldReference|Unsupported|abstract|attribute|datasource|enum|extends|false|function|generator|import|in|model|plugin|sort|true|view)\b
+ \b(Any|Asc|BigInt|Boolean|Bytes|ContextType|DateTime|Decimal|Desc|FieldReference|Float|Int|Json|Null|Object|String|TransitiveFieldReference|Unsupported|abstract|attribute|datasource|enum|extends|false|function|generator|import|in|model|null|plugin|sort|this|true|view)\b
name
diff --git a/packages/language/syntaxes/zmodel.tmLanguage.json b/packages/language/syntaxes/zmodel.tmLanguage.json
index d886d9b61..00c737c97 100644
--- a/packages/language/syntaxes/zmodel.tmLanguage.json
+++ b/packages/language/syntaxes/zmodel.tmLanguage.json
@@ -10,7 +10,7 @@
},
{
"name": "keyword.control.zmodel",
- "match": "\\b(Any|Asc|BigInt|Boolean|Bytes|ContextType|DateTime|Decimal|Desc|FieldReference|Float|Int|Json|Null|Object|String|TransitiveFieldReference|Unsupported|abstract|attribute|datasource|enum|extends|false|function|generator|import|in|model|plugin|sort|true|view)\\b"
+ "match": "\\b(Any|Asc|BigInt|Boolean|Bytes|ContextType|DateTime|Decimal|Desc|FieldReference|Float|Int|Json|Null|Object|String|TransitiveFieldReference|Unsupported|abstract|attribute|datasource|enum|extends|false|function|generator|import|in|model|null|plugin|sort|this|true|view)\\b"
},
{
"name": "string.quoted.double.zmodel",
diff --git a/packages/schema/language-configuration.json b/packages/schema/language-configuration.json
index ea1cc2473..4000da2d9 100644
--- a/packages/schema/language-configuration.json
+++ b/packages/schema/language-configuration.json
@@ -17,7 +17,8 @@
["[", "]"],
["(", ")"],
["\"", "\""],
- ["'", "'"]
+ ["'", "'"],
+ { "open": "/**", "close": " */", "notIn": ["string"] }
],
// symbols that can be used to surround a selection
"surroundingPairs": [
@@ -26,5 +27,6 @@
["(", ")"],
["\"", "\""],
["'", "'"]
- ]
+ ],
+ "wordPattern": "(-?\\d*\\.\\d\\w*)|([^\\`\\~\\!\\#\\%\\^\\&\\*\\-\\=\\+\\{\\}\\(\\)\\[\\]\\|\\;\\:\\'\\\"\\,\\.\\<\\>\\/\\s]+)"
}
diff --git a/packages/schema/src/language-server/validator/function-invocation-validator.ts b/packages/schema/src/language-server/validator/function-invocation-validator.ts
index 5e0f1d639..3bc364bd2 100644
--- a/packages/schema/src/language-server/validator/function-invocation-validator.ts
+++ b/packages/schema/src/language-server/validator/function-invocation-validator.ts
@@ -50,7 +50,7 @@ export default class FunctionInvocationValidator implements AstValidator ExpressionContext.DefaultValue)
- .with(P.union('@@allow', '@@deny'), () => ExpressionContext.AccessPolicy)
+ .with(P.union('@@allow', '@@deny', '@allow', '@deny'), () => ExpressionContext.AccessPolicy)
.with('@@validate', () => ExpressionContext.ValidationRule)
.otherwise(() => undefined);
diff --git a/packages/schema/src/language-server/zmodel-completion-provider.ts b/packages/schema/src/language-server/zmodel-completion-provider.ts
new file mode 100644
index 000000000..742f7087f
--- /dev/null
+++ b/packages/schema/src/language-server/zmodel-completion-provider.ts
@@ -0,0 +1,377 @@
+import {
+ DataModelAttribute,
+ DataModelFieldAttribute,
+ ReferenceExpr,
+ StringLiteral,
+ isArrayExpr,
+ isAttribute,
+ isDataModel,
+ isDataModelAttribute,
+ isDataModelField,
+ isDataModelFieldAttribute,
+ isEnum,
+ isEnumField,
+ isFunctionDecl,
+ isInvocationExpr,
+ isMemberAccessExpr,
+} from '@zenstackhq/language/ast';
+import { ZModelCodeGenerator, getAttribute, isEnumFieldReference, isFromStdlib } from '@zenstackhq/sdk';
+import {
+ AstNode,
+ AstNodeDescription,
+ CompletionAcceptor,
+ CompletionContext,
+ CompletionProviderOptions,
+ CompletionValueItem,
+ DefaultCompletionProvider,
+ LangiumDocument,
+ LangiumServices,
+ MaybePromise,
+ NextFeature,
+} from 'langium';
+import { P, match } from 'ts-pattern';
+import { CompletionItemKind, CompletionList, CompletionParams, MarkupContent } from 'vscode-languageserver';
+
+export class ZModelCompletionProvider extends DefaultCompletionProvider {
+ constructor(private readonly services: LangiumServices) {
+ super(services);
+ }
+
+ readonly completionOptions?: CompletionProviderOptions = {
+ triggerCharacters: ['@', '(', ',', '.'],
+ };
+
+ override async getCompletion(
+ document: LangiumDocument,
+ params: CompletionParams
+ ): Promise {
+ try {
+ return await super.getCompletion(document, params);
+ } catch (e) {
+ console.error('Completion error:', (e as Error).message);
+ return undefined;
+ }
+ }
+
+ override completionFor(
+ context: CompletionContext,
+ next: NextFeature,
+ acceptor: CompletionAcceptor
+ ): MaybePromise {
+ if (isDataModelAttribute(context.node) || isDataModelFieldAttribute(context.node)) {
+ const completions = this.getCompletionFromHint(context.node);
+ if (completions) {
+ completions.forEach(acceptor);
+ return;
+ }
+ }
+ return super.completionFor(context, next, acceptor);
+ }
+
+ private getCompletionFromHint(
+ contextNode: DataModelAttribute | DataModelFieldAttribute
+ ): CompletionValueItem[] | undefined {
+ // get completion based on the hint on the next unfilled parameter
+ const unfilledParams = this.getUnfilledAttributeParams(contextNode);
+ const nextParam = unfilledParams[0];
+ if (!nextParam) {
+ return undefined;
+ }
+
+ const hintAttr = getAttribute(nextParam, '@@@completionHint');
+ if (hintAttr) {
+ const hint = hintAttr.args[0];
+ if (hint?.value) {
+ if (isArrayExpr(hint.value)) {
+ return hint.value.items.map((item) => {
+ return {
+ label: `${(item as StringLiteral).value}`,
+ kind: CompletionItemKind.Value,
+ detail: 'Parameter',
+ sortText: '0',
+ };
+ });
+ }
+ }
+ }
+ return undefined;
+ }
+
+ // TODO: this doesn't work when the file contains parse errors
+ private getUnfilledAttributeParams(contextNode: DataModelAttribute | DataModelFieldAttribute) {
+ try {
+ const params = contextNode.decl.ref?.params;
+ if (params) {
+ const args = contextNode.args;
+ let unfilledParams = [...params];
+ args.forEach((arg) => {
+ if (arg.name) {
+ unfilledParams = unfilledParams.filter((p) => p.name !== arg.name);
+ } else {
+ unfilledParams.shift();
+ }
+ });
+
+ return unfilledParams;
+ }
+ } catch {
+ // noop
+ }
+ return [];
+ }
+
+ override completionForCrossReference(
+ context: CompletionContext,
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ crossRef: any,
+ acceptor: CompletionAcceptor
+ ): MaybePromise {
+ if (crossRef.property === 'member' && !isMemberAccessExpr(context.node)) {
+ // for guarding an error in the base implementation
+ return;
+ }
+
+ const customAcceptor = (item: CompletionValueItem) => {
+ // attributes starting with @@@ are for internal use only
+ if (item.insertText?.startsWith('@@@') || item.label?.startsWith('@@@')) {
+ return;
+ }
+
+ if ('nodeDescription' in item) {
+ const node = this.getAstNode(item.nodeDescription);
+ if (!node) {
+ return;
+ }
+
+ // enums in stdlib are not supposed to be referenced directly
+ if ((isEnum(node) || isEnumField(node)) && isFromStdlib(node)) {
+ return;
+ }
+
+ if (
+ (isDataModelAttribute(context.node) || isDataModelFieldAttribute(context.node)) &&
+ !this.filterAttributeApplicationCompletion(context.node, node)
+ ) {
+ // node not matching attribute context
+ return;
+ }
+ }
+ acceptor(item);
+ };
+
+ super.completionForCrossReference(context, crossRef, customAcceptor);
+ }
+
+ override completionForKeyword(
+ context: CompletionContext,
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ keyword: any,
+ acceptor: CompletionAcceptor
+ ): MaybePromise {
+ const customAcceptor = (item: CompletionValueItem) => {
+ if (!this.filterKeywordForContext(context, keyword.value)) {
+ return;
+ }
+ acceptor(item);
+ };
+ super.completionForKeyword(context, keyword, customAcceptor);
+ }
+
+ private filterKeywordForContext(context: CompletionContext, keyword: string) {
+ if (isInvocationExpr(context.node)) {
+ return ['true', 'false', 'null', 'this'].includes(keyword);
+ } else if (isDataModelAttribute(context.node) || isDataModelFieldAttribute(context.node)) {
+ const exprContext = this.getAttributeContextType(context.node);
+ if (exprContext === 'DefaultValue') {
+ return ['true', 'false', 'null'].includes(keyword);
+ } else {
+ return ['true', 'false', 'null', 'this'].includes(keyword);
+ }
+ } else {
+ return true;
+ }
+ }
+
+ private filterAttributeApplicationCompletion(
+ contextNode: DataModelAttribute | DataModelFieldAttribute,
+ node: AstNode
+ ) {
+ const attrContextType = this.getAttributeContextType(contextNode);
+
+ if (isFunctionDecl(node) && attrContextType) {
+ // functions are excluded if they are not allowed in the current context
+ const funcExprContextAttr = getAttribute(node, '@@@expressionContext');
+ if (funcExprContextAttr && funcExprContextAttr.args[0]) {
+ const arg = funcExprContextAttr.args[0];
+ if (isArrayExpr(arg.value)) {
+ return arg.value.items.some(
+ (item) =>
+ isEnumFieldReference(item) && (item as ReferenceExpr).target.$refText === attrContextType
+ );
+ }
+ }
+ return false;
+ }
+
+ if (isDataModelField(node)) {
+ // model fields are not allowed in @default
+ return attrContextType !== 'DefaultValue';
+ }
+
+ return true;
+ }
+
+ private getAttributeContextType(node: DataModelAttribute | DataModelFieldAttribute) {
+ return match(node.decl.$refText)
+ .with('@default', () => 'DefaultValue')
+ .with(P.union('@@allow', '@allow', '@@deny', '@deny'), () => 'AccessPolicy')
+ .with('@@validate', () => 'ValidationRule')
+ .otherwise(() => undefined);
+ }
+
+ override createReferenceCompletionItem(nodeDescription: AstNodeDescription): CompletionValueItem {
+ const node = this.getAstNode(nodeDescription);
+ const documentation = this.getNodeDocumentation(node);
+
+ return match(node)
+ .when(isDataModel, () => ({
+ nodeDescription,
+ kind: CompletionItemKind.Class,
+ detail: 'Data model',
+ sortText: '1',
+ documentation,
+ }))
+ .when(isDataModelField, () => ({
+ nodeDescription,
+ kind: CompletionItemKind.Field,
+ detail: 'Data model field',
+ sortText: '0',
+ documentation,
+ }))
+ .when(isEnum, () => ({
+ nodeDescription,
+ kind: CompletionItemKind.Class,
+ detail: 'Enum',
+ sortText: '1',
+ documentation,
+ }))
+ .when(isEnumField, () => ({
+ nodeDescription,
+ kind: CompletionItemKind.Enum,
+ detail: 'Enum value',
+ sortText: '1',
+ documentation,
+ }))
+ .when(isFunctionDecl, () => ({
+ nodeDescription,
+ insertText: this.getFunctionInsertText(nodeDescription),
+ kind: CompletionItemKind.Function,
+ detail: 'Function',
+ sortText: '1',
+ documentation,
+ }))
+ .when(isAttribute, () => ({
+ nodeDescription,
+ insertText: this.getAttributeInsertText(nodeDescription),
+ kind: CompletionItemKind.Property,
+ detail: 'Attribute',
+ sortText: '1',
+ documentation,
+ }))
+ .otherwise(() => ({
+ nodeDescription,
+ kind: CompletionItemKind.Reference,
+ detail: nodeDescription.type,
+ sortText: '2',
+ documentation,
+ }));
+ }
+
+ private getFunctionInsertText(nodeDescription: AstNodeDescription): string {
+ const node = this.getAstNode(nodeDescription);
+ if (isFunctionDecl(node)) {
+ if (node.params.some((p) => !p.optional)) {
+ return nodeDescription.name;
+ }
+ }
+ return `${nodeDescription.name}()`;
+ }
+
+ private getAttributeInsertText(nodeDescription: AstNodeDescription): string {
+ const node = this.getAstNode(nodeDescription);
+ if (isAttribute(node)) {
+ if (node.name === '@relation') {
+ return `${nodeDescription.name}(fields: [], references: [])`;
+ }
+ }
+ return nodeDescription.name;
+ }
+
+ private getAstNode(nodeDescription: AstNodeDescription) {
+ let node = nodeDescription.node;
+ if (!node) {
+ const doc = this.services.shared.workspace.LangiumDocuments.getOrCreateDocument(
+ nodeDescription.documentUri
+ );
+ if (!doc) {
+ return undefined;
+ }
+ node = this.services.workspace.AstNodeLocator.getAstNode(doc.parseResult.value, nodeDescription.path);
+ if (!node) {
+ return undefined;
+ }
+ }
+ return node;
+ }
+
+ private getNodeDocumentation(node?: AstNode): MarkupContent | undefined {
+ if (!node) {
+ return undefined;
+ }
+ const md = this.commentsToMarkdown(node);
+ return {
+ kind: 'markdown',
+ value: md,
+ };
+ }
+
+ private commentsToMarkdown(node: AstNode): string {
+ const md = this.services.documentation.DocumentationProvider.getDocumentation(node) ?? '';
+ const zModelGenerator = new ZModelCodeGenerator();
+ const docs: string[] = [];
+
+ try {
+ match(node)
+ .when(isAttribute, (attr) => {
+ const zModelGenerator = new ZModelCodeGenerator();
+ docs.push('```prisma', zModelGenerator.generate(attr), '```');
+ })
+ .when(isFunctionDecl, (func) => {
+ docs.push('```ts', zModelGenerator.generate(func), '```');
+ })
+ .when(isDataModel, (model) => {
+ docs.push('```prisma', `model ${model.name} { ... }`, '```');
+ })
+ .when(isEnum, (enumDecl) => {
+ docs.push('```prisma', zModelGenerator.generate(enumDecl), '```');
+ })
+ .when(isDataModelField, (field) => {
+ docs.push(`${field.name}: ${field.type.type ?? field.type.reference?.$refText}`);
+ })
+ .otherwise((ast) => {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ const name = (ast as any).name;
+ if (name) {
+ docs.push(name);
+ }
+ });
+ } catch {
+ // noop
+ }
+
+ if (md) {
+ docs.push('___', md);
+ }
+ return docs.join('\n');
+ }
+}
diff --git a/packages/schema/src/language-server/zmodel-highlight.ts b/packages/schema/src/language-server/zmodel-highlight.ts
new file mode 100644
index 000000000..dc8fcc62e
--- /dev/null
+++ b/packages/schema/src/language-server/zmodel-highlight.ts
@@ -0,0 +1,16 @@
+import { DefaultDocumentHighlightProvider, LangiumDocument } from 'langium';
+import { DocumentHighlight, DocumentHighlightParams } from 'vscode-languageserver';
+
+export class ZModelHighlightProvider extends DefaultDocumentHighlightProvider {
+ override async getDocumentHighlight(
+ document: LangiumDocument,
+ params: DocumentHighlightParams
+ ): Promise {
+ try {
+ return await super.getDocumentHighlight(document, params);
+ } catch (e) {
+ console.error('Highlight error:', (e as Error).message);
+ return undefined;
+ }
+ }
+}
diff --git a/packages/schema/src/language-server/zmodel-hover.ts b/packages/schema/src/language-server/zmodel-hover.ts
new file mode 100644
index 000000000..06c40caaf
--- /dev/null
+++ b/packages/schema/src/language-server/zmodel-hover.ts
@@ -0,0 +1,16 @@
+import { AstNode, LangiumDocument, MultilineCommentHoverProvider } from 'langium';
+import { Hover, HoverParams } from 'vscode-languageclient';
+
+export class ZModelHoverProvider extends MultilineCommentHoverProvider {
+ override async getHoverContent(
+ document: LangiumDocument,
+ params: HoverParams
+ ): Promise {
+ try {
+ return await super.getHoverContent(document, params);
+ } catch (e) {
+ console.error('Hover error:', (e as Error).message);
+ return undefined;
+ }
+ }
+}
diff --git a/packages/schema/src/language-server/zmodel-module.ts b/packages/schema/src/language-server/zmodel-module.ts
index ab0f32b45..07dc223e0 100644
--- a/packages/schema/src/language-server/zmodel-module.ts
+++ b/packages/schema/src/language-server/zmodel-module.ts
@@ -1,6 +1,5 @@
import { ZModelGeneratedModule, ZModelGeneratedSharedModule } from '@zenstackhq/language/module';
import {
- createDefaultModule,
DefaultConfigurationProvider,
DefaultDocumentBuilder,
DefaultIndexManager,
@@ -9,24 +8,29 @@ import {
DefaultLanguageServer,
DefaultServiceRegistry,
DefaultSharedModuleContext,
- inject,
LangiumDefaultSharedServices,
LangiumServices,
LangiumSharedServices,
Module,
MutexLock,
PartialLangiumServices,
+ createGrammarConfig as createDefaultGrammarConfig,
+ createDefaultModule,
+ inject,
} from 'langium';
import { TextDocuments } from 'vscode-languageserver';
import { TextDocument } from 'vscode-languageserver-textdocument';
import { ZModelValidationRegistry, ZModelValidator } from './validator/zmodel-validator';
import { ZModelCodeActionProvider } from './zmodel-code-action';
+import { ZModelCompletionProvider } from './zmodel-completion-provider';
+import { ZModelDefinitionProvider } from './zmodel-definition';
import { ZModelFormatter } from './zmodel-formatter';
+import { ZModelHighlightProvider } from './zmodel-highlight';
+import { ZModelHoverProvider } from './zmodel-hover';
import { ZModelLinker } from './zmodel-linker';
import { ZModelScopeComputation, ZModelScopeProvider } from './zmodel-scope';
-import ZModelWorkspaceManager from './zmodel-workspace-manager';
-import { ZModelDefinitionProvider } from './zmodel-definition';
import { ZModelSemanticTokenProvider } from './zmodel-semantic';
+import ZModelWorkspaceManager from './zmodel-workspace-manager';
/**
* Declaration of custom services - add your own service classes here.
@@ -63,6 +67,12 @@ export const ZModelModule: Module new ZModelCodeActionProvider(services),
DefinitionProvider: (services) => new ZModelDefinitionProvider(services),
SemanticTokenProvider: (services) => new ZModelSemanticTokenProvider(services),
+ CompletionProvider: (services) => new ZModelCompletionProvider(services),
+ HoverProvider: (services) => new ZModelHoverProvider(services),
+ DocumentHighlightProvider: (services) => new ZModelHighlightProvider(services),
+ },
+ parser: {
+ GrammarConfig: (services) => createGrammarConfig(services),
},
};
@@ -115,3 +125,9 @@ export function createZModelServices(context: DefaultSharedModuleContext): {
shared.ServiceRegistry.register(ZModel);
return { shared, ZModel };
}
+
+function createGrammarConfig(services: LangiumServices) {
+ const config = createDefaultGrammarConfig(services);
+ config.nameRegexp = /^[@\w\p{L}]$/u;
+ return config;
+}
diff --git a/packages/schema/src/language-server/zmodel-scope.ts b/packages/schema/src/language-server/zmodel-scope.ts
index a227d9d1c..8eda869e8 100644
--- a/packages/schema/src/language-server/zmodel-scope.ts
+++ b/packages/schema/src/language-server/zmodel-scope.ts
@@ -1,22 +1,34 @@
-import { isEnumField, isModel, Model, DataModel } from '@zenstackhq/language/ast';
+import {
+ DataModel,
+ MemberAccessExpr,
+ Model,
+ isDataModel,
+ isDataModelField,
+ isEnumField,
+ isInvocationExpr,
+ isMemberAccessExpr,
+ isModel,
+ isReferenceExpr,
+} from '@zenstackhq/language/ast';
+import { getAuthModel, getDataModels } from '@zenstackhq/sdk';
import {
AstNode,
AstNodeDescription,
DefaultScopeComputation,
DefaultScopeProvider,
EMPTY_SCOPE,
- equalURI,
- getContainerOfType,
- interruptAndCheck,
LangiumDocument,
LangiumServices,
Mutable,
PrecomputedScopes,
ReferenceInfo,
Scope,
+ StreamScope,
+ equalURI,
+ getContainerOfType,
+ interruptAndCheck,
stream,
streamAllContents,
- StreamScope,
} from 'langium';
import { CancellationToken } from 'vscode-jsonrpc';
import { resolveImportUri } from '../utils/ast-utils';
@@ -125,4 +137,53 @@ export class ZModelScopeProvider extends DefaultScopeProvider {
);
return new StreamScope(importedElements);
}
+
+ override getScope(context: ReferenceInfo): Scope {
+ if (isMemberAccessExpr(context.container) && context.container.operand && context.property === 'member') {
+ return this.getMemberAccessScope(context.container);
+ }
+ return super.getScope(context);
+ }
+
+ private getMemberAccessScope(node: MemberAccessExpr) {
+ if (isReferenceExpr(node.operand)) {
+ // scope to target model's fields
+ const ref = node.operand.target.ref;
+ if (isDataModelField(ref)) {
+ const targetModel = ref.type.reference?.ref;
+ if (isDataModel(targetModel)) {
+ return this.createScopeForNodes(targetModel.fields);
+ }
+ }
+ } else if (isMemberAccessExpr(node.operand)) {
+ // scope to target model's fields
+ const ref = node.operand.member.ref;
+ if (isDataModelField(ref)) {
+ const targetModel = ref.type.reference?.ref;
+ if (isDataModel(targetModel)) {
+ return this.createScopeForNodes(targetModel.fields);
+ }
+ }
+ } else if (isInvocationExpr(node.operand)) {
+ // deal with member access from `auth()` and `future()
+ const funcName = node.operand.function.$refText;
+ if (funcName === 'auth') {
+ // resolve to `User` or `@@auth` model
+ const model = getContainerOfType(node, isModel);
+ if (model) {
+ const authModel = getAuthModel(getDataModels(model));
+ if (authModel) {
+ return this.createScopeForNodes(authModel.fields);
+ }
+ }
+ }
+ if (funcName === 'future') {
+ const thisModel = getContainerOfType(node, isDataModel);
+ if (thisModel) {
+ return this.createScopeForNodes(thisModel.fields);
+ }
+ }
+ }
+ return EMPTY_SCOPE;
+ }
}
diff --git a/packages/schema/src/res/stdlib.zmodel b/packages/schema/src/res/stdlib.zmodel
index 6cd826e47..1a9446d7b 100644
--- a/packages/schema/src/res/stdlib.zmodel
+++ b/packages/schema/src/res/stdlib.zmodel
@@ -1,5 +1,6 @@
/**
* Enum representing referential integrity related actions
+ * @see https://www.prisma.io/docs/orm/prisma-schema/data-model/relations/referential-actions
*/
enum ReferentialAction {
/**
@@ -16,7 +17,6 @@ enum ReferentialAction {
/**
* Similar to 'Restrict', the difference between the two is dependent on the database being used.
- * See details: https://www.prisma.io/docs/concepts/components/prisma-schema/relations/referential-actions#noaction
*/
NoAction
@@ -188,27 +188,55 @@ attribute @@@expressionContext(_ context: ExpressionContext[])
attribute @@@prisma()
/**
- * Defines an ID on the model.
+ * Provides hint for auto-completion.
+ */
+attribute @@@completionHint(_ values: String[])
+
+/**
+ * Defines a single-field ID on the model.
+ *
+ * @param map: The name of the underlying primary key constraint in the database.
+ * @param length: Allows you to specify a maximum length for the subpart of the value to be indexed.
+ * @param sort: Allows you to specify in what order the entries of the ID are stored in the database. The available options are Asc and Desc.
+ * @param clustered: Defines whether the ID is clustered or non-clustered. Defaults to true.
*/
attribute @id(map: String?, length: Int?, sort: String?, clustered: Boolean?) @@@prisma
/**
* Defines a default value for a field.
+ * @param value: An expression (e.g. 5, true, now()).
*/
attribute @default(_ value: ContextType, map: String?) @@@prisma
/**
* Defines a unique constraint for this field.
+ *
+ * @param length: Allows you to specify a maximum length for the subpart of the value to be indexed.
+ * @param sort: Allows you to specify in what order the entries of the constraint are stored in the database. The available options are Asc and Desc.
+ * @param clustered: Boolean Defines whether the constraint is clustered or non-clustered. Defaults to false.
*/
attribute @unique(map: String?, length: Int?, sort: String?, clustered: Boolean?) @@@prisma
/**
* Defines a multi-field ID (composite ID) on the model.
+ *
+ * @param fields: A list of field names - for example, [firstname, lastname]
+ * @param name: The name that Prisma Client will expose for the argument covering all fields, e.g. fullName in fullName: { firstName: "First", lastName: "Last"}
+ * @param map: The name of the underlying primary key constraint in the database.
+ * @param length: Allows you to specify a maximum length for the subpart of the value to be indexed.
+ * @param sort: Allows you to specify in what order the entries of the ID are stored in the database. The available options are Asc and Desc.
+ * @param clustered: Defines whether the ID is clustered or non-clustered. Defaults to true.
*/
attribute @@id(_ fields: FieldReference[], name: String?, map: String?, length: Int?, sort: String?, clustered: Boolean?) @@@prisma
/**
* Defines a compound unique constraint for the specified fields.
+ *
+ * @param fields: A list of field names - for example, [firstname, lastname]. Fields must be mandatory.
+ * @param name: The name of the unique combination of fields - defaults to fieldName1_fieldName2_fieldName3
+ * @param length: Allows you to specify a maximum length for the subpart of the value to be indexed.
+ * @param sort: Allows you to specify in what order the entries of the constraint are stored in the database. The available options are Asc and Desc.
+ * @param clustered: Boolean Defines whether the constraint is clustered or non-clustered. Defaults to false.
*/
attribute @@unique(_ fields: FieldReference[], name: String?, map: String?, length: Int?, sort: String?, clustered: Boolean?) @@@prisma
@@ -226,21 +254,40 @@ enum IndexType {
/**
* Defines an index in the database.
+ *
+ * @params fields: A list of field names - for example, [firstname, lastname]
+ * @params name: The name that Prisma Client will expose for the argument covering all fields, e.g. fullName in fullName: { firstName: "First", lastName: "Last"}
+ * @params map: The name of the index in the underlying database (Prisma generates an index name that respects identifier length limits if you do not specify a name. Prisma uses the following naming convention: tablename.field1_field2_field3_unique)
+ * @params length: Allows you to specify a maximum length for the subpart of the value to be indexed.
+ * @params sort: Allows you to specify in what order the entries of the index or constraint are stored in the database. The available options are asc and desc.
+ * @params clustered: Defines whether the index is clustered or non-clustered. Defaults to false.
+ * @params type: Allows you to specify an index access method. Defaults to BTree.
*/
attribute @@index(_ fields: FieldReference[], name: String?, map: String?, length: Int?, sort: String?, clustered: Boolean?, type: IndexType?) @@@prisma
/**
* Defines meta information about the relation.
+ *
+ * @param name: Sometimes (e.g. to disambiguate a relation) Defines the name of the relationship. In an m-n-relation, it also determines the name of the underlying relation table.
+ * @param fields: A list of fields of the current model
+ * @param references: A list of fields of the model on the other side of the relation
+ * @param map: Defines a custom name for the foreign key in the database.
+ * @param onUpdate: Defines the referential action to perform when a referenced entry in the referenced model is being updated.
+ * @param onDelete: Defines the referential action to perform when a referenced entry in the referenced model is being deleted.
*/
attribute @relation(_ name: String?, fields: FieldReference[]?, references: TransitiveFieldReference[]?, onDelete: ReferentialAction?, onUpdate: ReferentialAction?, map: String?) @@@prisma
/**
* Maps a field name or enum value from the schema to a column with a different name in the database.
+ *
+ * @param name: The database column name.
*/
attribute @map(_ name: String) @@@prisma
/**
* Maps the schema model name to a table with a different name, or an enum name to a different underlying enum in the database.
+ *
+ * @param name: The database column name.
*/
attribute @@map(_ name: String) @@@prisma
@@ -359,29 +406,44 @@ attribute @db.Image() @@@targetField([BytesField]) @@@prisma
/**
* Specifies the schema to use in a multi-schema database. https://www.prisma.io/docs/guides/database/multi-schema.
+ *
+ * @param: The name of the database schema.
*/
attribute @@schema(_ name: String) @@@prisma
/**
* Defines an access policy that allows a set of operations when the given condition is true.
+ *
+ * @param operation: comma-separated list of "create", "read", "update", "delete". Use "all" to denote all operations.
+ * @param condition: a boolean expression that controls if the operation should be allowed.
*/
-attribute @@allow(_ operation: String, _ condition: Boolean)
+attribute @@allow(_ operation: String @@@completionHint(["'create'", "'read'", "'update'", "'delete'", "'all'"]), _ condition: Boolean)
/**
* Defines an access policy that allows the annotated field to be read or updated.
* You can pass a thrid argument as `true` to make it override the model-level policies.
+ *
+ * @param operation: comma-separated list of "create", "read", "update", "delete". Use "all" to denote all operations.
+ * @param condition: a boolean expression that controls if the operation should be allowed.
+ * @param override: a boolean value that controls if the field-level policy should override the model-level policy.
*/
-attribute @allow(_ operation: String, _ condition: Boolean, _ override: Boolean?)
+attribute @allow(_ operation: String @@@completionHint(["'create'", "'read'", "'update'", "'delete'", "'all'"]), _ condition: Boolean, _ override: Boolean?)
/**
* Defines an access policy that denies a set of operations when the given condition is true.
+ *
+ * @param operation: comma-separated list of "create", "read", "update", "delete". Use "all" to denote all operations.
+ * @param condition: a boolean expression that controls if the operation should be denied.
*/
-attribute @@deny(_ operation: String, _ condition: Boolean)
+attribute @@deny(_ operation: String @@@completionHint(["'create'", "'read'", "'update'", "'delete'", "'all'"]), _ condition: Boolean)
/**
* Defines an access policy that denies the annotated field to be read or updated.
+ *
+ * @param operation: comma-separated list of "create", "read", "update", "delete". Use "all" to denote all operations.
+ * @param condition: a boolean expression that controls if the operation should be denied.
*/
-attribute @deny(_ operation: String, _ condition: Boolean)
+attribute @deny(_ operation: String @@@completionHint(["'create'", "'read'", "'update'", "'delete'", "'all'"]), _ condition: Boolean)
/**
* Used to specify the model for resolving `auth()` function call in access policies. A Zmodel
@@ -398,8 +460,8 @@ attribute @@auth()
*
* @see https://www.npmjs.com/package/bcryptjs for details
*
- * @saltLength: length of salt to use (cost factor for the hash function)
- * @salt: salt to use (a pregenerated valid salt)
+ * @param saltLength: length of salt to use (cost factor for the hash function)
+ * @param salt: salt to use (a pregenerated valid salt)
*/
attribute @password(saltLength: Int?, salt: String?) @@@targetField([StringField])
diff --git a/packages/sdk/src/utils.ts b/packages/sdk/src/utils.ts
index abec6092b..d32962f11 100644
--- a/packages/sdk/src/utils.ts
+++ b/packages/sdk/src/utils.ts
@@ -1,5 +1,7 @@
import {
AstNode,
+ Attribute,
+ AttributeParam,
ConfigExpr,
DataModel,
DataModelAttribute,
@@ -110,11 +112,17 @@ export function indentString(string: string, count = 4): string {
return string.replace(/^(?!\s*$)/gm, indent.repeat(count));
}
-export function hasAttribute(decl: DataModel | DataModelField | Enum | EnumField, name: string) {
+export function hasAttribute(
+ decl: DataModel | DataModelField | Enum | EnumField | FunctionDecl | Attribute | AttributeParam,
+ name: string
+) {
return !!getAttribute(decl, name);
}
-export function getAttribute(decl: DataModel | DataModelField | Enum | EnumField, name: string) {
+export function getAttribute(
+ decl: DataModel | DataModelField | Enum | EnumField | FunctionDecl | Attribute | AttributeParam,
+ name: string
+) {
return (decl.attributes as (DataModelAttribute | DataModelFieldAttribute)[]).find(
(attr) => attr.decl.$refText === name
);
diff --git a/packages/sdk/src/zmodel-code-generator.ts b/packages/sdk/src/zmodel-code-generator.ts
index 46cc2010f..1b1f001e1 100644
--- a/packages/sdk/src/zmodel-code-generator.ts
+++ b/packages/sdk/src/zmodel-code-generator.ts
@@ -2,7 +2,10 @@ import {
Argument,
ArrayExpr,
AstNode,
+ Attribute,
AttributeArg,
+ AttributeParam,
+ AttributeParamType,
BinaryExpr,
BinaryExprOperatorPriority,
BooleanLiteral,
@@ -18,6 +21,9 @@ import {
Enum,
EnumField,
FieldInitializer,
+ FunctionDecl,
+ FunctionParam,
+ FunctionParamType,
GeneratorDecl,
InvocationExpr,
LiteralExpr,
@@ -282,7 +288,39 @@ ${ast.fields.map((x) => this.indent + this.generate(x)).join('\n')}${
return 'this';
}
- argument(ast: Argument) {
+ @gen(Attribute)
+ private _generateAttribute(ast: Attribute) {
+ return `attribute ${ast.name}(${ast.params.map((x) => this.generate(x)).join(', ')})`;
+ }
+
+ @gen(AttributeParam)
+ private _generateAttributeParam(ast: AttributeParam) {
+ return `${ast.default ? '_ ' : ''}${ast.name}: ${this.generate(ast.type)}`;
+ }
+
+ @gen(AttributeParamType)
+ private _generateAttributeParamType(ast: AttributeParamType) {
+ return `${ast.type ?? ast.reference?.$refText}${ast.array ? '[]' : ''}${ast.optional ? '?' : ''}`;
+ }
+
+ @gen(FunctionDecl)
+ private _generateFunctionDecl(ast: FunctionDecl) {
+ return `function ${ast.name}(${ast.params.map((x) => this.generate(x)).join(', ')}) ${
+ ast.returnType ? ': ' + this.generate(ast.returnType) : ''
+ } {}`;
+ }
+
+ @gen(FunctionParam)
+ private _generateFunctionParam(ast: FunctionParam) {
+ return `${ast.name}: ${this.generate(ast.type)}`;
+ }
+
+ @gen(FunctionParamType)
+ private _generateFunctionParamType(ast: FunctionParamType) {
+ return `${ast.type ?? ast.reference?.$refText}${ast.array ? '[]' : ''}`;
+ }
+
+ private argument(ast: Argument) {
return `${ast.name ? ast.name + ': ' : ''}${this.generate(ast.value)}`;
}