Skip to content

Commit 4a2681e

Browse files
committed
fix(zmodel): member access from auth() is not properly resolved when the auth model is imported
Fixes #1257
1 parent b0f5d3b commit 4a2681e

File tree

9 files changed

+129
-55
lines changed

9 files changed

+129
-55
lines changed

packages/plugins/trpc/tests/projects/t3-trpc-v10/prisma/schema.prisma

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,28 +4,28 @@
44
//////////////////////////////////////////////////////////////////////////////////////////////
55

66
datasource db {
7-
provider = "sqlite"
8-
url = "file:./dev.db"
7+
provider = "sqlite"
8+
url = "file:./dev.db"
99
}
1010

1111
generator client {
12-
provider = "prisma-client-js"
12+
provider = "prisma-client-js"
1313
}
1414

1515
model User {
16-
id Int @id() @default(autoincrement())
17-
email String @unique()
18-
posts Post[]
16+
id Int @id() @default(autoincrement())
17+
email String @unique()
18+
posts Post[]
1919
}
2020

2121
model Post {
22-
id Int @id() @default(autoincrement())
23-
name String
24-
createdAt DateTime @default(now())
25-
updatedAt DateTime @updatedAt()
26-
published Boolean @default(false)
27-
author User @relation(fields: [authorId], references: [id])
28-
authorId Int
22+
id Int @id() @default(autoincrement())
23+
name String
24+
createdAt DateTime @default(now())
25+
updatedAt DateTime @updatedAt()
26+
published Boolean @default(false)
27+
author User @relation(fields: [authorId], references: [id])
28+
authorId Int
2929
30-
@@index([name])
31-
}
30+
@@index([name])
31+
}

packages/schema/src/cli/cli-util.ts

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -64,23 +64,30 @@ export async function loadDocument(fileName: string): Promise<Model> {
6464
}
6565
);
6666

67-
const validationErrors = langiumDocuments.all
68-
.flatMap((d) => d.diagnostics ?? [])
69-
.filter((e) => e.severity === 1)
67+
const diagnostics = langiumDocuments.all
68+
.flatMap((doc) => (doc.diagnostics ?? []).map((diag) => ({ doc, diag })))
69+
.filter(({ diag }) => diag.severity === 1 || diag.severity === 2)
7070
.toArray();
7171

72-
if (validationErrors.length > 0) {
73-
console.error(colors.red('Validation errors:'));
74-
for (const validationError of validationErrors) {
75-
console.error(
76-
colors.red(
77-
`line ${validationError.range.start.line + 1}: ${
78-
validationError.message
79-
} [${document.textDocument.getText(validationError.range)}]`
80-
)
81-
);
72+
let hasErrors = false;
73+
74+
if (diagnostics.length > 0) {
75+
for (const { doc, diag } of diagnostics) {
76+
const message = `${path.relative(process.cwd(), doc.uri.fsPath)}:${diag.range.start.line + 1}:${
77+
diag.range.start.character + 1
78+
} - ${diag.message}`;
79+
80+
if (diag.severity === 1) {
81+
console.error(colors.red(message));
82+
hasErrors = true;
83+
} else {
84+
console.warn(colors.yellow(message));
85+
}
86+
}
87+
88+
if (hasErrors) {
89+
throw new CliError('Schema contains validation errors');
8290
}
83-
throw new CliError('schema validation errors');
8491
}
8592

8693
const model = document.parseResult.value as Model;

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

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
isLiteralExpr,
1111
isMemberAccessExpr,
1212
isNullExpr,
13+
isReferenceExpr,
1314
isThisExpr,
1415
} from '@zenstackhq/language/ast';
1516
import { isAuthInvocation, isDataModelFieldReference, isEnumFieldReference } from '@zenstackhq/sdk';
@@ -33,9 +34,21 @@ export default class ExpressionValidator implements AstValidator<Expression> {
3334
{ node: expr }
3435
);
3536
} else {
36-
accept('error', 'expression cannot be resolved', {
37-
node: expr,
37+
const hasReferenceResolutionError = streamAst(expr).some((node) => {
38+
if (isMemberAccessExpr(node)) {
39+
return !!node.member.error;
40+
}
41+
if (isReferenceExpr(node)) {
42+
return !!node.target.error;
43+
}
44+
return false;
3845
});
46+
if (!hasReferenceResolutionError) {
47+
// report silent errors not involving linker errors
48+
accept('error', 'Expression cannot be resolved', {
49+
node: expr,
50+
});
51+
}
3952
}
4053
}
4154

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Model, isDataModel, isDataSource } from '@zenstackhq/language/ast';
22
import { hasAttribute } from '@zenstackhq/sdk';
33
import { LangiumDocuments, ValidationAcceptor } from 'langium';
4-
import { getAllDeclarationsFromImports, resolveImport, resolveTransitiveImports } from '../../utils/ast-utils';
4+
import { getAllDeclarationsIncludingImports, resolveImport, resolveTransitiveImports } from '../../utils/ast-utils';
55
import { PLUGIN_MODULE_NAME, STD_LIB_MODULE_NAME } from '../constants';
66
import { AstValidator } from '../types';
77
import { validateDuplicatedDeclarations } from './utils';
@@ -43,7 +43,7 @@ export default class SchemaValidator implements AstValidator<Model> {
4343
}
4444

4545
private validateDataSources(model: Model, accept: ValidationAcceptor) {
46-
const dataSources = getAllDeclarationsFromImports(this.documents, model).filter((d) => isDataSource(d));
46+
const dataSources = getAllDeclarationsIncludingImports(this.documents, model).filter((d) => isDataSource(d));
4747
if (dataSources.length > 1) {
4848
accept('error', 'Multiple datasource declarations are not allowed', { node: dataSources[1] });
4949
}

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

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,9 @@ import {
3636
isStringLiteral,
3737
} from '@zenstackhq/language/ast';
3838
import {
39+
getAuthModel,
3940
getContainingModel,
4041
getModelFieldsWithBases,
41-
hasAttribute,
4242
isAuthInvocation,
4343
isFutureExpr,
4444
} from '@zenstackhq/sdk';
@@ -58,7 +58,7 @@ import {
5858
} from 'langium';
5959
import { match } from 'ts-pattern';
6060
import { CancellationToken } from 'vscode-jsonrpc';
61-
import { getAllDeclarationsFromImports, getContainingDataModel } from '../utils/ast-utils';
61+
import { getAllDataModelsIncludingImports, getContainingDataModel } from '../utils/ast-utils';
6262
import { mapBuiltinTypeToExpressionType } from './validator/utils';
6363

6464
interface DefaultReference extends Reference {
@@ -287,14 +287,8 @@ export class ZModelLinker extends DefaultLinker {
287287
const model = getContainingModel(node);
288288

289289
if (model) {
290-
let authModel = getAllDeclarationsFromImports(this.langiumDocuments(), model).find((d) => {
291-
return isDataModel(d) && hasAttribute(d, '@@auth');
292-
});
293-
if (!authModel) {
294-
authModel = getAllDeclarationsFromImports(this.langiumDocuments(), model).find((d) => {
295-
return isDataModel(d) && d.name === 'User';
296-
});
297-
}
290+
const allDataModels = getAllDataModelsIncludingImports(this.langiumDocuments(), model);
291+
const authModel = getAuthModel(allDataModels);
298292
if (authModel) {
299293
node.$resolvedType = { decl: authModel, nullable: true };
300294
}

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

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,7 @@ import {
1010
isReferenceExpr,
1111
isThisExpr,
1212
} from '@zenstackhq/language/ast';
13-
import {
14-
getAuthModel,
15-
getDataModels,
16-
getModelFieldsWithBases,
17-
getRecursiveBases,
18-
isAuthInvocation,
19-
} from '@zenstackhq/sdk';
13+
import { getAuthModel, getModelFieldsWithBases, getRecursiveBases, isAuthInvocation } from '@zenstackhq/sdk';
2014
import {
2115
AstNode,
2216
AstNodeDescription,
@@ -37,7 +31,12 @@ import {
3731
} from 'langium';
3832
import { match } from 'ts-pattern';
3933
import { CancellationToken } from 'vscode-jsonrpc';
40-
import { isCollectionPredicate, isFutureInvocation, resolveImportUri } from '../utils/ast-utils';
34+
import {
35+
getAllDataModelsIncludingImports,
36+
isCollectionPredicate,
37+
isFutureInvocation,
38+
resolveImportUri,
39+
} from '../utils/ast-utils';
4140
import { PLUGIN_MODULE_NAME, STD_LIB_MODULE_NAME } from './constants';
4241

4342
/**
@@ -88,7 +87,7 @@ export class ZModelScopeComputation extends DefaultScopeComputation {
8887
}
8988

9089
export class ZModelScopeProvider extends DefaultScopeProvider {
91-
constructor(services: LangiumServices) {
90+
constructor(private readonly services: LangiumServices) {
9291
super(services);
9392
}
9493

@@ -222,7 +221,11 @@ export class ZModelScopeProvider extends DefaultScopeProvider {
222221
private createScopeForAuthModel(node: AstNode, globalScope: Scope) {
223222
const model = getContainerOfType(node, isModel);
224223
if (model) {
225-
const authModel = getAuthModel(getDataModels(model, true));
224+
const allDataModels = getAllDataModelsIncludingImports(
225+
this.services.shared.workspace.LangiumDocuments,
226+
model
227+
);
228+
const authModel = getAuthModel(allDataModels);
226229
if (authModel) {
227230
return this.createScopeForModel(authModel, globalScope);
228231
}

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -216,11 +216,15 @@ export function resolveImport(documents: LangiumDocuments, imp: ModelImport): Mo
216216
return undefined;
217217
}
218218

219-
export function getAllDeclarationsFromImports(documents: LangiumDocuments, model: Model) {
219+
export function getAllDeclarationsIncludingImports(documents: LangiumDocuments, model: Model) {
220220
const imports = resolveTransitiveImports(documents, model);
221221
return model.declarations.concat(...imports.map((imp) => imp.declarations));
222222
}
223223

224+
export function getAllDataModelsIncludingImports(documents: LangiumDocuments, model: Model) {
225+
return getAllDeclarationsIncludingImports(documents, model).filter(isDataModel);
226+
}
227+
224228
export function isCollectionPredicate(node: AstNode): node is BinaryExpr {
225229
return isBinaryExpr(node) && ['?', '!', '^'].includes(node.operator);
226230
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1081,7 +1081,7 @@ describe('Attribute tests', () => {
10811081
@@allow('all', auth().email != null)
10821082
}
10831083
`)
1084-
).toContain(`expression cannot be resolved`);
1084+
).toContain(`Could not resolve reference to DataModelField named 'email'.`);
10851085
});
10861086

10871087
it('collection predicate expression check', async () => {
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { FILE_SPLITTER, loadSchema } from '@zenstackhq/testtools';
2+
3+
describe('issue 1210', () => {
4+
it('regression', async () => {
5+
await loadSchema(
6+
`schema.zmodel
7+
import "./user"
8+
import "./image"
9+
10+
generator client {
11+
provider = "prisma-client-js"
12+
}
13+
14+
datasource db {
15+
provider = "postgresql"
16+
url = env("DATABASE_URL")
17+
}
18+
19+
${FILE_SPLITTER}base.zmodel
20+
abstract model Base {
21+
id Int @id @default(autoincrement())
22+
}
23+
24+
${FILE_SPLITTER}user.zmodel
25+
import "./base"
26+
import "./image"
27+
28+
enum Role {
29+
Admin
30+
}
31+
32+
model User extends Base {
33+
email String @unique
34+
role Role
35+
@@auth
36+
}
37+
38+
${FILE_SPLITTER}image.zmodel
39+
import "./user"
40+
import "./base"
41+
42+
model Image extends Base {
43+
width Int @default(0)
44+
height Int @default(0)
45+
46+
@@allow('read', true)
47+
@@allow('all', auth().role == Admin)
48+
}
49+
`,
50+
{ addPrelude: false, pushDb: false }
51+
);
52+
});
53+
});

0 commit comments

Comments
 (0)