From 95f82dd2b1796baca613ae712a6ca1acb65fb28a Mon Sep 17 00:00:00 2001
From: ymc9 <104139426+ymc9@users.noreply.github.com>
Date: Wed, 11 Oct 2023 15:55:50 -0700
Subject: [PATCH] fix: support string literal keys for object expressions in
ZModel
---
packages/language/src/generated/ast.ts | 2 +-
packages/language/src/generated/grammar.ts | 22 +++++++---
packages/language/src/zmodel.langium | 2 +-
packages/schema/package.json | 1 +
.../tests/generator/expression-writer.test.ts | 2 +-
.../tests/generator/prisma-generator.test.ts | 2 +-
.../tests/generator/zmodel-generator.test.ts | 2 +-
packages/schema/tests/schema/abstract.test.ts | 2 +-
packages/schema/tests/schema/cal-com.test.ts | 2 +-
packages/schema/tests/schema/parser.test.ts | 2 +-
.../schema/tests/schema/sample-todo.test.ts | 2 +-
packages/schema/tests/schema/stdlib.test.ts | 2 +-
.../schema/tests/schema/trigger-dev.test.ts | 2 +-
.../validation/attribute-validation.test.ts | 2 +-
.../validation/datamodel-validation.test.ts | 2 +-
.../validation/datasource-validation.test.ts | 2 +-
.../schema/validation/enum-validation.test.ts | 2 +-
.../validation/schema-validation.test.ts | 2 +-
packages/sdk/src/utils.ts | 13 ++++--
packages/testtools/package.json | 2 +
packages/testtools/src/index.ts | 3 +-
.../tests/utils.ts => testtools/src/model.ts} | 12 +++---
pnpm-lock.yaml | 9 +++++
.../tests/regression/issue-744.test.ts | 40 +++++++++++++++++++
24 files changed, 104 insertions(+), 30 deletions(-)
rename packages/{schema/tests/utils.ts => testtools/src/model.ts} (86%)
create mode 100644 tests/integration/tests/regression/issue-744.test.ts
diff --git a/packages/language/src/generated/ast.ts b/packages/language/src/generated/ast.ts
index d5f462277..1f4b25b84 100644
--- a/packages/language/src/generated/ast.ts
+++ b/packages/language/src/generated/ast.ts
@@ -386,7 +386,7 @@ export function isEnumField(item: unknown): item is EnumField {
export interface FieldInitializer extends AstNode {
readonly $container: ObjectExpr;
readonly $type: 'FieldInitializer';
- name: RegularID
+ name: RegularID | string
value: Expression
}
diff --git a/packages/language/src/generated/grammar.ts b/packages/language/src/generated/grammar.ts
index 7bdf53b6a..8cad3d2cf 100644
--- a/packages/language/src/generated/grammar.ts
+++ b/packages/language/src/generated/grammar.ts
@@ -1171,11 +1171,23 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel
"feature": "name",
"operator": "=",
"terminal": {
- "$type": "RuleCall",
- "rule": {
- "$ref": "#/rules@47"
- },
- "arguments": []
+ "$type": "Alternatives",
+ "elements": [
+ {
+ "$type": "RuleCall",
+ "rule": {
+ "$ref": "#/rules@47"
+ },
+ "arguments": []
+ },
+ {
+ "$type": "RuleCall",
+ "rule": {
+ "$ref": "#/rules@67"
+ },
+ "arguments": []
+ }
+ ]
}
},
{
diff --git a/packages/language/src/zmodel.langium b/packages/language/src/zmodel.langium
index 5f1aa5008..47dfa1866 100644
--- a/packages/language/src/zmodel.langium
+++ b/packages/language/src/zmodel.langium
@@ -90,7 +90,7 @@ ObjectExpr:
'}';
FieldInitializer:
- name=RegularID ':' value=(Expression);
+ name=(RegularID | STRING) ':' value=(Expression);
InvocationExpr:
function=[FunctionDecl] '(' ArgumentList? ')';
diff --git a/packages/schema/package.json b/packages/schema/package.json
index 3b35633c5..486c158cd 100644
--- a/packages/schema/package.json
+++ b/packages/schema/package.json
@@ -126,6 +126,7 @@
"@typescript-eslint/parser": "^5.42.0",
"@vscode/vsce": "^2.19.0",
"@zenstackhq/runtime": "workspace:*",
+ "@zenstackhq/testtools": "workspace:*",
"concurrently": "^7.4.0",
"copyfiles": "^2.4.1",
"dotenv": "^16.0.3",
diff --git a/packages/schema/tests/generator/expression-writer.test.ts b/packages/schema/tests/generator/expression-writer.test.ts
index d4a5fe5db..5c905d904 100644
--- a/packages/schema/tests/generator/expression-writer.test.ts
+++ b/packages/schema/tests/generator/expression-writer.test.ts
@@ -4,7 +4,7 @@ import { DataModel, Enum, Expression, isDataModel, isEnum } from '@zenstackhq/la
import * as tmp from 'tmp';
import { Project, VariableDeclarationKind } from 'ts-morph';
import { ExpressionWriter } from '../../src/plugins/access-policy/expression-writer';
-import { loadModel } from '../utils';
+import { loadModel } from '@zenstackhq/testtools';
describe('Expression Writer Tests', () => {
it('boolean literal', async () => {
diff --git a/packages/schema/tests/generator/prisma-generator.test.ts b/packages/schema/tests/generator/prisma-generator.test.ts
index 8ba127842..c1d835245 100644
--- a/packages/schema/tests/generator/prisma-generator.test.ts
+++ b/packages/schema/tests/generator/prisma-generator.test.ts
@@ -6,7 +6,7 @@ import path from 'path';
import tmp from 'tmp';
import { loadDocument } from '../../src/cli/cli-util';
import PrismaSchemaGenerator from '../../src/plugins/prisma/schema-generator';
-import { loadModel } from '../utils';
+import { loadModel } from '@zenstackhq/testtools';
describe('Prisma generator test', () => {
it('datasource coverage', async () => {
diff --git a/packages/schema/tests/generator/zmodel-generator.test.ts b/packages/schema/tests/generator/zmodel-generator.test.ts
index 91ddacca2..9240ae741 100644
--- a/packages/schema/tests/generator/zmodel-generator.test.ts
+++ b/packages/schema/tests/generator/zmodel-generator.test.ts
@@ -1,4 +1,4 @@
-import { loadModel } from '../utils';
+import { loadModel } from '@zenstackhq/testtools';
import ZModelCodeGenerator from '../../src/plugins/prisma/zmodel-code-generator';
import { DataModel, DataModelAttribute, DataModelFieldAttribute } from '@zenstackhq/language/ast';
diff --git a/packages/schema/tests/schema/abstract.test.ts b/packages/schema/tests/schema/abstract.test.ts
index 621e7998a..6681a1e58 100644
--- a/packages/schema/tests/schema/abstract.test.ts
+++ b/packages/schema/tests/schema/abstract.test.ts
@@ -1,6 +1,6 @@
import * as fs from 'fs';
import path from 'path';
-import { loadModel } from '../utils';
+import { loadModel } from '@zenstackhq/testtools';
describe('Abstract Schema Tests', () => {
it('model loading', async () => {
diff --git a/packages/schema/tests/schema/cal-com.test.ts b/packages/schema/tests/schema/cal-com.test.ts
index 05da241b9..9bf58e64b 100644
--- a/packages/schema/tests/schema/cal-com.test.ts
+++ b/packages/schema/tests/schema/cal-com.test.ts
@@ -1,6 +1,6 @@
import * as fs from 'fs';
import path from 'path';
-import { loadModel } from '../utils';
+import { loadModel } from '@zenstackhq/testtools';
describe('Cal.com Schema Tests', () => {
it('model loading', async () => {
diff --git a/packages/schema/tests/schema/parser.test.ts b/packages/schema/tests/schema/parser.test.ts
index 9b4150cd5..99a1753f3 100644
--- a/packages/schema/tests/schema/parser.test.ts
+++ b/packages/schema/tests/schema/parser.test.ts
@@ -20,7 +20,7 @@ import {
StringLiteral,
UnaryExpr,
} from '@zenstackhq/language/ast';
-import { loadModel } from '../utils';
+import { loadModel } from '@zenstackhq/testtools';
describe('Parsing Tests', () => {
it('data source', async () => {
diff --git a/packages/schema/tests/schema/sample-todo.test.ts b/packages/schema/tests/schema/sample-todo.test.ts
index 40387604c..1f4eaefbe 100644
--- a/packages/schema/tests/schema/sample-todo.test.ts
+++ b/packages/schema/tests/schema/sample-todo.test.ts
@@ -1,6 +1,6 @@
import * as fs from 'fs';
import path from 'path';
-import { loadModel } from '../utils';
+import { loadModel } from '@zenstackhq/testtools';
describe('Sample Todo Schema Tests', () => {
it('model loading', async () => {
diff --git a/packages/schema/tests/schema/stdlib.test.ts b/packages/schema/tests/schema/stdlib.test.ts
index e4fa5b966..c34accd11 100644
--- a/packages/schema/tests/schema/stdlib.test.ts
+++ b/packages/schema/tests/schema/stdlib.test.ts
@@ -1,8 +1,8 @@
+import { SchemaLoadingError } from '@zenstackhq/testtools';
import { NodeFileSystem } from 'langium/node';
import path from 'path';
import { URI } from 'vscode-uri';
import { createZModelServices } from '../../src/language-server/zmodel-module';
-import { SchemaLoadingError } from '../utils';
describe('Stdlib Tests', () => {
it('stdlib', async () => {
diff --git a/packages/schema/tests/schema/trigger-dev.test.ts b/packages/schema/tests/schema/trigger-dev.test.ts
index c712ad25d..599ec7a4f 100644
--- a/packages/schema/tests/schema/trigger-dev.test.ts
+++ b/packages/schema/tests/schema/trigger-dev.test.ts
@@ -1,6 +1,6 @@
import * as fs from 'fs';
import path from 'path';
-import { loadModel } from '../utils';
+import { loadModel } from '@zenstackhq/testtools';
describe('Trigger.dev Schema Tests', () => {
it('model loading', async () => {
diff --git a/packages/schema/tests/schema/validation/attribute-validation.test.ts b/packages/schema/tests/schema/validation/attribute-validation.test.ts
index 5c638a841..51af8f460 100644
--- a/packages/schema/tests/schema/validation/attribute-validation.test.ts
+++ b/packages/schema/tests/schema/validation/attribute-validation.test.ts
@@ -1,6 +1,6 @@
///
-import { loadModel, loadModelWithError } from '../../utils';
+import { loadModel, loadModelWithError } from '@zenstackhq/testtools';
describe('Attribute tests', () => {
const prelude = `
diff --git a/packages/schema/tests/schema/validation/datamodel-validation.test.ts b/packages/schema/tests/schema/validation/datamodel-validation.test.ts
index a2d68f2c0..bcfc4f54a 100644
--- a/packages/schema/tests/schema/validation/datamodel-validation.test.ts
+++ b/packages/schema/tests/schema/validation/datamodel-validation.test.ts
@@ -1,4 +1,4 @@
-import { loadModel, loadModelWithError } from '../../utils';
+import { loadModel, loadModelWithError } from '@zenstackhq/testtools';
describe('Data Model Validation Tests', () => {
const prelude = `
diff --git a/packages/schema/tests/schema/validation/datasource-validation.test.ts b/packages/schema/tests/schema/validation/datasource-validation.test.ts
index 19be1f076..a6596b7cd 100644
--- a/packages/schema/tests/schema/validation/datasource-validation.test.ts
+++ b/packages/schema/tests/schema/validation/datasource-validation.test.ts
@@ -1,4 +1,4 @@
-import { loadModel, loadModelWithError } from '../../utils';
+import { loadModel, loadModelWithError } from '@zenstackhq/testtools';
describe('Datasource Validation Tests', () => {
it('missing fields', async () => {
diff --git a/packages/schema/tests/schema/validation/enum-validation.test.ts b/packages/schema/tests/schema/validation/enum-validation.test.ts
index fc31a0c92..c20d386d5 100644
--- a/packages/schema/tests/schema/validation/enum-validation.test.ts
+++ b/packages/schema/tests/schema/validation/enum-validation.test.ts
@@ -1,4 +1,4 @@
-import { loadModelWithError } from '../../utils';
+import { loadModelWithError } from '@zenstackhq/testtools';
describe('Enum Validation Tests', () => {
const prelude = `
diff --git a/packages/schema/tests/schema/validation/schema-validation.test.ts b/packages/schema/tests/schema/validation/schema-validation.test.ts
index c7b000338..9e90b28ec 100644
--- a/packages/schema/tests/schema/validation/schema-validation.test.ts
+++ b/packages/schema/tests/schema/validation/schema-validation.test.ts
@@ -1,4 +1,4 @@
-import { loadModelWithError } from '../../utils';
+import { loadModelWithError } from '@zenstackhq/testtools';
describe('Toplevel Schema Validation Tests', () => {
it('too many datasources', async () => {
diff --git a/packages/sdk/src/utils.ts b/packages/sdk/src/utils.ts
index 80366480a..f10ae7b1e 100644
--- a/packages/sdk/src/utils.ts
+++ b/packages/sdk/src/utils.ts
@@ -49,10 +49,17 @@ export function resolved(ref: Reference): T {
export function getLiteral(
expr: Expression | ConfigExpr | undefined
): T | undefined {
- if (!isLiteralExpr(expr)) {
- return getObjectLiteral(expr);
+ switch (expr?.$type) {
+ case 'ObjectExpr':
+ return getObjectLiteral(expr);
+ case 'StringLiteral':
+ case 'BooleanLiteral':
+ return expr.value as T;
+ case 'NumberLiteral':
+ return parseFloat(expr.value) as T;
+ default:
+ return undefined;
}
- return expr.value as T;
}
export function getArray(expr: Expression | ConfigExpr | undefined) {
diff --git a/packages/testtools/package.json b/packages/testtools/package.json
index 4e9e5118f..9624ef461 100644
--- a/packages/testtools/package.json
+++ b/packages/testtools/package.json
@@ -24,8 +24,10 @@
"@zenstackhq/runtime": "workspace:*",
"@zenstackhq/sdk": "workspace:*",
"json5": "^2.2.3",
+ "langium": "1.2.0",
"pg": "^8.11.1",
"tmp": "^0.2.1",
+ "vscode-uri": "^3.0.6",
"zenstack": "workspace:*"
},
"devDependencies": {
diff --git a/packages/testtools/src/index.ts b/packages/testtools/src/index.ts
index 3c1996686..488498b2f 100644
--- a/packages/testtools/src/index.ts
+++ b/packages/testtools/src/index.ts
@@ -1,2 +1,3 @@
-export * from './schema';
export * from './db';
+export * from './model';
+export * from './schema';
diff --git a/packages/schema/tests/utils.ts b/packages/testtools/src/model.ts
similarity index 86%
rename from packages/schema/tests/utils.ts
rename to packages/testtools/src/model.ts
index f362b4019..4be8a1613 100644
--- a/packages/schema/tests/utils.ts
+++ b/packages/testtools/src/model.ts
@@ -1,11 +1,11 @@
-import { Model } from '@zenstackhq/language/ast';
+import { Model } from '@zenstackhq/sdk/ast';
import * as fs from 'fs';
import { NodeFileSystem } from 'langium/node';
import * as path from 'path';
import * as tmp from 'tmp';
import { URI } from 'vscode-uri';
-import { createZModelServices } from '../src/language-server/zmodel-module';
-import { mergeBaseModel } from '../src/utils/ast-utils';
+import { createZModelServices } from 'zenstack/language-server/zmodel-module';
+import { mergeBaseModel } from 'zenstack/utils/ast-utils';
export class SchemaLoadingError extends Error {
constructor(public readonly errors: string[]) {
@@ -18,7 +18,7 @@ export async function loadModel(content: string, validate = true, verbose = true
fs.writeFileSync(docPath, content);
const { shared } = createZModelServices(NodeFileSystem);
const stdLib = shared.workspace.LangiumDocuments.getOrCreateDocument(
- URI.file(path.resolve('src/res/stdlib.zmodel'))
+ URI.file(path.resolve(__dirname, '../../schema/src/res/stdlib.zmodel'))
);
const doc = shared.workspace.LangiumDocuments.getOrCreateDocument(URI.file(docPath));
@@ -60,7 +60,9 @@ export async function loadModelWithError(content: string, verbose = false) {
try {
await loadModel(content, true, verbose);
} catch (err) {
- expect(err).toBeInstanceOf(SchemaLoadingError);
+ if (!(err instanceof SchemaLoadingError)) {
+ throw err;
+ }
return (err as SchemaLoadingError).errors;
}
throw new Error('No error is thrown');
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 09952fa02..ad62571c9 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -567,6 +567,9 @@ importers:
'@zenstackhq/runtime':
specifier: workspace:*
version: link:../runtime/dist
+ '@zenstackhq/testtools':
+ specifier: workspace:*
+ version: link:../testtools/dist
concurrently:
specifier: ^7.4.0
version: 7.4.0
@@ -779,12 +782,18 @@ importers:
json5:
specifier: ^2.2.3
version: 2.2.3
+ langium:
+ specifier: 1.2.0
+ version: 1.2.0
pg:
specifier: ^8.11.1
version: 8.11.1
tmp:
specifier: ^0.2.1
version: 0.2.1
+ vscode-uri:
+ specifier: ^3.0.6
+ version: 3.0.7
zenstack:
specifier: workspace:*
version: link:../schema/dist
diff --git a/tests/integration/tests/regression/issue-744.test.ts b/tests/integration/tests/regression/issue-744.test.ts
new file mode 100644
index 000000000..d46d110ec
--- /dev/null
+++ b/tests/integration/tests/regression/issue-744.test.ts
@@ -0,0 +1,40 @@
+import { getObjectLiteral } from '@zenstackhq/sdk';
+import { Plugin, PluginField, isPlugin } from '@zenstackhq/sdk/ast';
+import { loadModel } from '@zenstackhq/testtools';
+
+describe('Regression: issue 744', () => {
+ it('regression', async () => {
+ const model = await loadModel(
+ `
+ generator client {
+ provider = "prisma-client-js"
+ }
+
+ datasource db {
+ provider = "postgresql"
+ url = env("DATABASE_URL")
+ }
+
+ plugin zod {
+ provider = '@core/zod'
+ settings = {
+ '200': { status: 'ok' },
+ 'x-y-z': 200,
+ foo: 'bar'
+ }
+ }
+
+ model Foo {
+ id String @id @default(cuid())
+ }
+ `
+ );
+
+ const plugin = model.declarations.find((d): d is Plugin => isPlugin(d));
+ const settings = plugin?.fields.find((f): f is PluginField => f.name === 'settings');
+ const value: any = getObjectLiteral(settings?.value);
+ expect(value['200']).toMatchObject({ status: 'ok' });
+ expect(value['x-y-z']).toBe(200);
+ expect(value.foo).toBe('bar');
+ });
+});