Skip to content

Commit d223819

Browse files
authored
fix(zmodel): prefer to use triple-slash comments as ZModel documentation (#1817)
1 parent 77817f5 commit d223819

File tree

6 files changed

+90
-30
lines changed

6 files changed

+90
-30
lines changed
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { AstNode, JSDocDocumentationProvider } from 'langium';
2+
3+
/**
4+
* Documentation provider that first tries to use triple-slash comments and falls back to JSDoc comments.
5+
*/
6+
export class ZModelDocumentationProvider extends JSDocDocumentationProvider {
7+
getDocumentation(node: AstNode): string | undefined {
8+
// prefer to use triple-slash comments
9+
if ('comments' in node && Array.isArray(node.comments) && node.comments.length > 0) {
10+
return node.comments.map((c: string) => c.replace(/^[/]*\s*/, '')).join('\n');
11+
}
12+
13+
// fall back to JSDoc comments
14+
return super.getDocumentation(node);
15+
}
16+
}

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,14 @@ import { ZModelValidationRegistry, ZModelValidator } from './validator/zmodel-va
2727
import { ZModelCodeActionProvider } from './zmodel-code-action';
2828
import { ZModelCompletionProvider } from './zmodel-completion-provider';
2929
import { ZModelDefinitionProvider } from './zmodel-definition';
30+
import { ZModelDocumentationProvider } from './zmodel-documentation-provider';
3031
import { ZModelFormatter } from './zmodel-formatter';
3132
import { ZModelHighlightProvider } from './zmodel-highlight';
3233
import { ZModelHoverProvider } from './zmodel-hover';
3334
import { ZModelLinker } from './zmodel-linker';
3435
import { ZModelScopeComputation, ZModelScopeProvider } from './zmodel-scope';
3536
import { ZModelSemanticTokenProvider } from './zmodel-semantic';
36-
import ZModelWorkspaceManager from './zmodel-workspace-manager';
37+
import { ZModelWorkspaceManager } from './zmodel-workspace-manager';
3738

3839
/**
3940
* Declaration of custom services - add your own service classes here.
@@ -77,6 +78,9 @@ export const ZModelModule: Module<ZModelServices, PartialLangiumServices & ZMode
7778
parser: {
7879
GrammarConfig: (services) => createGrammarConfig(services),
7980
},
81+
documentation: {
82+
DocumentationProvider: (services) => new ZModelDocumentationProvider(services),
83+
},
8084
};
8185

8286
// this duplicates createDefaultSharedModule except that a custom WorkspaceManager is used

packages/schema/src/language-server/zmodel-workspace-manager.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { PLUGIN_MODULE_NAME, STD_LIB_MODULE_NAME } from './constants';
99
/**
1010
* Custom Langium WorkspaceManager implementation which automatically loads stdlib.zmodel
1111
*/
12-
export default class ZModelWorkspaceManager extends DefaultWorkspaceManager {
12+
export class ZModelWorkspaceManager extends DefaultWorkspaceManager {
1313
public pluginModels = new Set<string>();
1414

1515
protected async loadAdditionalDocuments(

packages/schema/src/plugins/prisma/schema-generator.ts

Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ export class PrismaSchemaGenerator {
100100
`;
101101

102102
private mode: 'logical' | 'physical' = 'physical';
103+
private customAttributesAsComments = false;
103104

104105
// a mapping from full names to shortened names
105106
private shortNameMap = new Map<string, string>();
@@ -117,6 +118,14 @@ export class PrismaSchemaGenerator {
117118
this.mode = options.mode as 'logical' | 'physical';
118119
}
119120

121+
if (
122+
options.customAttributesAsComments !== undefined &&
123+
typeof options.customAttributesAsComments !== 'boolean'
124+
) {
125+
throw new PluginError(name, 'option "customAttributesAsComments" must be a boolean');
126+
}
127+
this.customAttributesAsComments = options.customAttributesAsComments === true;
128+
120129
const prismaVersion = getPrismaVersion();
121130
if (prismaVersion && semver.lt(prismaVersion, PRISMA_MINIMUM_VERSION)) {
122131
warnings.push(
@@ -282,12 +291,9 @@ export class PrismaSchemaGenerator {
282291
this.generateContainerAttribute(model, attr);
283292
}
284293

285-
decl.attributes
286-
.filter((attr) => attr.decl.ref && !this.isPrismaAttribute(attr))
287-
.forEach((attr) => model.addComment('/// ' + this.zModelGenerator.generate(attr)));
288-
289294
// user defined comments pass-through
290295
decl.comments.forEach((c) => model.addComment(c));
296+
this.getCustomAttributesAsComments(decl).forEach((c) => model.addComment(c));
291297

292298
// generate relation fields on base models linking to concrete models
293299
this.generateDelegateRelationForBase(model, decl);
@@ -763,11 +769,9 @@ export class PrismaSchemaGenerator {
763769
)
764770
.map((attr) => this.makeFieldAttribute(attr));
765771

766-
const nonPrismaAttributes = field.attributes.filter((attr) => attr.decl.ref && !this.isPrismaAttribute(attr));
767-
768-
const documentations = nonPrismaAttributes.map((attr) => '/// ' + this.zModelGenerator.generate(attr));
769-
770-
const result = model.addField(field.name, type, attributes, documentations, addToFront);
772+
// user defined comments pass-through
773+
const docs = [...field.comments, ...this.getCustomAttributesAsComments(field)];
774+
const result = model.addField(field.name, type, attributes, docs, addToFront);
771775

772776
if (this.mode === 'logical') {
773777
if (field.attributes.some((attr) => isDefaultWithAuth(attr))) {
@@ -777,8 +781,6 @@ export class PrismaSchemaGenerator {
777781
}
778782
}
779783

780-
// user defined comments pass-through
781-
field.comments.forEach((c) => result.addComment(c));
782784
return result;
783785
}
784786

@@ -898,23 +900,28 @@ export class PrismaSchemaGenerator {
898900
this.generateContainerAttribute(_enum, attr);
899901
}
900902

901-
decl.attributes
902-
.filter((attr) => attr.decl.ref && !this.isPrismaAttribute(attr))
903-
.forEach((attr) => _enum.addComment('/// ' + this.zModelGenerator.generate(attr)));
904-
905903
// user defined comments pass-through
906904
decl.comments.forEach((c) => _enum.addComment(c));
905+
this.getCustomAttributesAsComments(decl).forEach((c) => _enum.addComment(c));
907906
}
908907

909908
private generateEnumField(_enum: PrismaEnum, field: EnumField) {
910909
const attributes = field.attributes
911910
.filter((attr) => this.isPrismaAttribute(attr))
912911
.map((attr) => this.makeFieldAttribute(attr));
913912

914-
const nonPrismaAttributes = field.attributes.filter((attr) => attr.decl.ref && !this.isPrismaAttribute(attr));
913+
const docs = [...field.comments, ...this.getCustomAttributesAsComments(field)];
914+
_enum.addField(field.name, attributes, docs);
915+
}
915916

916-
const documentations = nonPrismaAttributes.map((attr) => '/// ' + this.zModelGenerator.generate(attr));
917-
_enum.addField(field.name, attributes, documentations.concat(field.comments));
917+
private getCustomAttributesAsComments(decl: DataModel | DataModelField | Enum | EnumField) {
918+
if (!this.customAttributesAsComments) {
919+
return [];
920+
} else {
921+
return decl.attributes
922+
.filter((attr) => attr.decl.ref && !this.isPrismaAttribute(attr))
923+
.map((attr) => `/// ${this.zModelGenerator.generate(attr)}`);
924+
}
918925
}
919926
}
920927

packages/schema/src/res/starter.zmodel

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
// This is a sample model to get you started.
22

3-
/**
4-
* A sample data source using local sqlite db.
5-
*/
3+
/// A sample data source using local sqlite db.
64
datasource db {
75
provider = 'sqlite'
86
url = 'file:./dev.db'
@@ -12,9 +10,7 @@ generator client {
1210
provider = "prisma-client-js"
1311
}
1412

15-
/**
16-
* User model
17-
*/
13+
/// User model
1814
model User {
1915
id String @id @default(cuid())
2016
email String @unique @email @length(6, 32)
@@ -28,9 +24,7 @@ model User {
2824
@@allow('all', auth() == this)
2925
}
3026

31-
/**
32-
* Post model
33-
*/
27+
/// Post model
3428
model Post {
3529
id String @id @default(cuid())
3630
createdAt DateTime @default(now())

packages/schema/tests/generator/prisma-generator.test.ts

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,35 @@ describe('Prisma generator test', () => {
4747
provider = '@core/prisma'
4848
}
4949
50+
/// User roles
51+
enum Role {
52+
/// Admin role
53+
ADMIN
54+
/// Regular role
55+
USER
56+
57+
@@schema("auth")
58+
}
59+
60+
/// My user model
61+
/// defined here
5062
model User {
51-
id String @id
63+
/// the id field
64+
id String @id @allow('read', this == auth())
65+
role Role
5266
5367
@@schema("auth")
68+
@@allow('all', true)
69+
@@deny('update', this != auth())
70+
}
71+
72+
/**
73+
* My post model
74+
* defined here
75+
*/
76+
model Post {
77+
id String @id
78+
@@schema("public")
5479
}
5580
`);
5681

@@ -60,6 +85,7 @@ describe('Prisma generator test', () => {
6085
schemaPath: 'schema.zmodel',
6186
output: 'schema.prisma',
6287
format: false,
88+
customAttributesAsComments: true,
6389
});
6490

6591
const content = fs.readFileSync('schema.prisma', 'utf-8');
@@ -70,6 +96,14 @@ describe('Prisma generator test', () => {
7096
'extensions = [pg_trgm, postgis(version: "3.3.2"), uuid_ossp(map: "uuid-ossp", schema: "extensions")]'
7197
);
7298
expect(content).toContain('schemas = ["auth", "public"]');
99+
expect(content).toContain('/// My user model');
100+
expect(content).toContain(`/// @@allow('all', true)`);
101+
expect(content).toContain(`/// the id field`);
102+
expect(content).toContain(`/// @allow('read', this == auth())`);
103+
expect(content).not.toContain('/// My post model');
104+
expect(content).toContain('/// User roles');
105+
expect(content).toContain('/// Admin role');
106+
expect(content).toContain('/// Regular role');
73107
await getDMMF({ datamodel: content });
74108
});
75109

@@ -172,6 +206,7 @@ describe('Prisma generator test', () => {
172206
provider: '@core/prisma',
173207
schemaPath: 'schema.zmodel',
174208
output: name,
209+
customAttributesAsComments: true,
175210
});
176211

177212
const content = fs.readFileSync(name, 'utf-8');
@@ -204,6 +239,7 @@ describe('Prisma generator test', () => {
204239
provider: '@core/prisma',
205240
schemaPath: 'schema.zmodel',
206241
output: name,
242+
customAttributesAsComments: true,
207243
});
208244

209245
const content = fs.readFileSync(name, 'utf-8');
@@ -397,6 +433,7 @@ describe('Prisma generator test', () => {
397433
schemaPath: 'schema.zmodel',
398434
output: name,
399435
generateClient: false,
436+
customAttributesAsComments: true,
400437
});
401438

402439
const content = fs.readFileSync(name, 'utf-8');
@@ -447,6 +484,7 @@ describe('Prisma generator test', () => {
447484
schemaPath: 'schema.zmodel',
448485
output: name,
449486
format: true,
487+
customAttributesAsComments: true,
450488
});
451489

452490
const content = fs.readFileSync(name, 'utf-8');
@@ -478,6 +516,7 @@ describe('Prisma generator test', () => {
478516
schemaPath: 'schema.zmodel',
479517
output: name,
480518
format: true,
519+
customAttributesAsComments: true,
481520
});
482521

483522
const content = fs.readFileSync(name, 'utf-8');

0 commit comments

Comments
 (0)