@likec4/language-server 0.6.0 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/contrib/likec4.monarch.ts +31 -0
  2. package/contrib/likec4.tmLanguage.json +73 -0
  3. package/dist/__test__/parser-smoke/01-Specification.d.ts +3 -0
  4. package/dist/__test__/parser-smoke/01-Specification.js +42 -0
  5. package/dist/__test__/parser-smoke/02-Model.d.ts +9 -0
  6. package/dist/__test__/parser-smoke/02-Model.js +110 -0
  7. package/dist/__test__/parser-smoke/03-ModelRelation.d.ts +6 -0
  8. package/dist/__test__/parser-smoke/03-ModelRelation.js +81 -0
  9. package/dist/__test__/parser-smoke/04-Scope.d.ts +2 -0
  10. package/dist/__test__/parser-smoke/04-Scope.js +38 -0
  11. package/dist/__test__/parser-smoke/05-StrictElementRef.d.ts +3 -0
  12. package/dist/__test__/parser-smoke/05-StrictElementRef.js +46 -0
  13. package/dist/__test__/parser-smoke/06-ElementRef.d.ts +2 -0
  14. package/dist/__test__/parser-smoke/06-ElementRef.js +59 -0
  15. package/dist/__test__/parser-smoke/07-Views.d.ts +10 -0
  16. package/dist/__test__/parser-smoke/07-Views.js +146 -0
  17. package/dist/__test__/parser-smoke/08-Structurizr.d.ts +1 -0
  18. package/dist/__test__/parser-smoke/08-Structurizr.js +22 -0
  19. package/dist/__test__/parser-smoke/index.d.ts +8 -0
  20. package/dist/__test__/parser-smoke/index.js +8 -0
  21. package/dist/__test__/parser-smoke-extendsElement.spec.d.ts +1 -0
  22. package/dist/__test__/parser-smoke-extendsElement.spec.js +36 -0
  23. package/dist/__test__/parser-smoke.spec.d.ts +1 -0
  24. package/dist/__test__/parser-smoke.spec.js +28 -0
  25. package/dist/ast.d.ts +1 -0
  26. package/dist/index.d.ts +0 -1
  27. package/dist/index.js +0 -1
  28. package/dist/lsp/DocumentSymbolProvider.d.ts +1 -0
  29. package/dist/model/model-builder.js +12 -12
  30. package/dist/model/model-builder.spec.d.ts +1 -0
  31. package/dist/model/model-builder.spec.js +141 -0
  32. package/dist/protocol.d.ts +16 -12
  33. package/dist/protocol.js +2 -2
  34. package/dist/registerProtocolHandlers.js +24 -9
  35. package/dist/validation/element.d.ts +1 -0
  36. package/dist/validation/element.spec.d.ts +1 -0
  37. package/dist/validation/element.spec.js +65 -0
  38. package/dist/validation/relation.spec.d.ts +1 -0
  39. package/dist/validation/relation.spec.js +93 -0
  40. package/dist/validation/specification.spec.d.ts +1 -0
  41. package/dist/validation/specification.spec.js +31 -0
  42. package/dist/validation/view.spec.d.ts +1 -0
  43. package/dist/validation/view.spec.js +20 -0
  44. package/package.json +17 -16
@@ -0,0 +1,22 @@
1
+ const model = `
2
+ specification {
3
+ element person
4
+ element group
5
+ element softwareSystem
6
+ element container
7
+ element component
8
+ }
9
+ `;
10
+ export const valid_08_Structurizr = model +
11
+ `
12
+ model {
13
+ u = person "User"
14
+ s = softwareSystem "Software System" {
15
+ webapp = container "Web Application" "" "Spring Boot"
16
+ database = container "Database" "" "Relational database schema"
17
+ }
18
+
19
+ u -> webapp "Uses"
20
+ webapp -> database "Reads from and writes to"
21
+ }
22
+ `;
@@ -0,0 +1,8 @@
1
+ export * from './01-Specification';
2
+ export * from './02-Model';
3
+ export * from './03-ModelRelation';
4
+ export * from './04-Scope';
5
+ export * from './05-StrictElementRef';
6
+ export * from './06-ElementRef';
7
+ export * from './07-Views';
8
+ export * from './08-Structurizr';
@@ -0,0 +1,8 @@
1
+ export * from './01-Specification';
2
+ export * from './02-Model';
3
+ export * from './03-ModelRelation';
4
+ export * from './04-Scope';
5
+ export * from './05-StrictElementRef';
6
+ export * from './06-ElementRef';
7
+ export * from './07-Views';
8
+ export * from './08-Structurizr';
@@ -0,0 +1,36 @@
1
+ import { expect, test } from 'vitest';
2
+ import { createTestServices } from '../test';
3
+ const document1 = `
4
+ specification {
5
+ element component
6
+ }
7
+ model {
8
+ component system {
9
+ sub = component {
10
+ component sub1
11
+ }
12
+ }
13
+ }
14
+ `;
15
+ const document2 = `
16
+ model {
17
+ extend system.sub {
18
+ component sub2 {
19
+ -> sub1
20
+ }
21
+ }
22
+ }
23
+ `;
24
+ const document3 = `
25
+ model {
26
+ system.sub1 -> system.sub2
27
+ }
28
+ `;
29
+ test('parser smoke: ExtendsElement Scope', async () => {
30
+ const { parse, validateAll } = createTestServices();
31
+ await parse(document1);
32
+ await parse(document2);
33
+ await parse(document3);
34
+ const { errors } = await validateAll();
35
+ expect(errors).toEqual([]);
36
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,28 @@
1
+ import { toPairs } from 'rambdax';
2
+ import { describe, vi, it } from 'vitest';
3
+ import { createTestServices } from '../test';
4
+ import * as testfiles from './parser-smoke';
5
+ vi.mock('../logger', () => ({
6
+ logger: {
7
+ log: vi.fn(),
8
+ error: vi.fn(),
9
+ warn: vi.fn(),
10
+ info: vi.fn(),
11
+ debug: vi.fn()
12
+ }
13
+ }));
14
+ describe('parser smoke', () => {
15
+ toPairs(testfiles).forEach(([name, document]) => {
16
+ it.concurrent(name, async ({ expect }) => {
17
+ const { validate } = createTestServices();
18
+ const { diagnostics } = await validate(document);
19
+ const errors = diagnostics.map(d => d.message);
20
+ if (name.startsWith('invalid_')) {
21
+ expect(errors).not.toEqual([]);
22
+ }
23
+ else {
24
+ expect(errors).toEqual([]);
25
+ }
26
+ });
27
+ });
28
+ });
package/dist/ast.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ /// <reference types="react" />
1
2
  import * as ast from './generated/ast';
2
3
  import type { LangiumDocument } from 'langium';
3
4
  import type { LikeC4Document } from './generated/ast';
package/dist/index.d.ts CHANGED
@@ -1,4 +1,3 @@
1
1
  export { createLanguageServices } from './module';
2
2
  export type { LikeC4Services } from './module';
3
3
  export { LikeC4LanguageMetaData as LanguageMetaData } from './generated/module';
4
- export { Rpc } from './protocol';
package/dist/index.js CHANGED
@@ -12,4 +12,3 @@ export { LikeC4LanguageMetaData as LanguageMetaData } from './generated/module';
12
12
  // startLangiumLanguageServer(shared)
13
13
  // return likec4
14
14
  // }
15
- export { Rpc } from './protocol';
@@ -3,6 +3,7 @@
3
3
  * This program and the accompanying materials are made available under the
4
4
  * terms of the MIT License, which is available in the project root.
5
5
  ******************************************************************************/
6
+ /// <reference types="react" />
6
7
  import { type DocumentSymbolProvider, type MaybePromise } from 'langium';
7
8
  import { type DocumentSymbol } from 'vscode-languageserver-protocol';
8
9
  import { type LikeC4LangiumDocument, ast } from '../ast';
@@ -10,7 +10,7 @@ import { toAutoLayout } from '../ast';
10
10
  import { ElementViewOps, ast, c4hash, cleanParsedModel, isLikeC4LangiumDocument, isParsedLikeC4LangiumDocument, resolveRelationPoints, streamModel, toElementStyle } from '../ast';
11
11
  import { elementRef, strictElementRefFqn } from '../elementRef';
12
12
  import { logger } from '../logger';
13
- import { Rpc } from '../protocol';
13
+ import { Rpc } from '@likec4/language-protocol';
14
14
  import { failExpectedNever } from '../utils';
15
15
  export class LikeC4ModelBuilder {
16
16
  services;
@@ -259,29 +259,29 @@ export class LikeC4ModelBuilder {
259
259
  }
260
260
  failExpectedNever(astNode);
261
261
  }
262
- parseViewRule(astNode) {
263
- if (ast.isViewRuleExpression(astNode)) {
264
- const exprs = astNode.expressions.map(n => this.parseExpression(n));
262
+ parseViewRule(astRule) {
263
+ if (ast.isViewRuleExpression(astRule)) {
264
+ const exprs = astRule.expressions.map(n => this.parseExpression(n));
265
265
  return {
266
- isInclude: astNode.isInclude,
266
+ isInclude: astRule.isInclude,
267
267
  exprs
268
268
  };
269
269
  }
270
- if (ast.isViewRuleStyle(astNode)) {
271
- const styleProps = toElementStyle(astNode.props);
270
+ if (ast.isViewRuleStyle(astRule)) {
271
+ const styleProps = toElementStyle(astRule.props);
272
272
  return {
273
- targets: astNode.targets.map(n => this.parseElementExpression(n)),
273
+ targets: astRule.targets.map(n => this.parseElementExpression(n)),
274
274
  style: {
275
275
  ...styleProps
276
276
  }
277
277
  };
278
278
  }
279
- if (ast.isViewRuleAutoLayout(astNode)) {
279
+ if (ast.isViewRuleAutoLayout(astRule)) {
280
280
  return {
281
- autoLayout: toAutoLayout(astNode.direction)
281
+ autoLayout: toAutoLayout(astRule.direction)
282
282
  };
283
283
  }
284
- failExpectedNever(astNode);
284
+ failExpectedNever(astRule);
285
285
  }
286
286
  parseElementView(astNode) {
287
287
  const viewOfEl = astNode.viewOf && elementRef(astNode.viewOf);
@@ -327,6 +327,6 @@ export class LikeC4ModelBuilder {
327
327
  return;
328
328
  }
329
329
  logger.debug('Send onDidChangeModel');
330
- await connection.sendNotification(Rpc.onDidChangeModel, null);
330
+ await connection.sendNotification(Rpc.onDidChangeModel);
331
331
  }
332
332
  }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,141 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { createTestServices } from '../test';
3
+ import { keys } from 'rambdax';
4
+ describe('LikeC4ModelBuilder', () => {
5
+ it('builds model', async () => {
6
+ const { validate, buildModel } = createTestServices();
7
+ const { diagnostics } = await validate(`
8
+ specification {
9
+ element component
10
+ element user {
11
+ style {
12
+ shape: person
13
+ }
14
+ }
15
+ tag deprecated
16
+ }
17
+ model {
18
+ user client {
19
+ -> frontend
20
+ }
21
+ component system {
22
+ backend = component 'Backend' {
23
+ technology 'NodeJS'
24
+
25
+ style {
26
+ color secondary
27
+ }
28
+ }
29
+ component frontend {
30
+ #deprecated
31
+ description 'Frontend description'
32
+
33
+ style {
34
+ color: muted
35
+ shape: browser
36
+ }
37
+
38
+ -> backend 'requests'
39
+ }
40
+ }
41
+ }
42
+ `);
43
+ expect(diagnostics).toHaveLength(0);
44
+ const model = await buildModel();
45
+ expect(model).toBeDefined();
46
+ expect(model.elements).toMatchObject({
47
+ client: {
48
+ kind: 'user',
49
+ shape: 'person'
50
+ },
51
+ 'system.backend': {
52
+ color: 'secondary',
53
+ title: 'Backend',
54
+ technology: 'NodeJS'
55
+ },
56
+ 'system.frontend': {
57
+ color: 'muted',
58
+ shape: 'browser',
59
+ description: 'Frontend description'
60
+ }
61
+ });
62
+ expect(model.elements['client']).not.toHaveProperty('color');
63
+ expect(model.elements['system']).not.toHaveProperty('color');
64
+ expect(model.elements['system']).not.toHaveProperty('shape');
65
+ expect(model.elements['system.backend']).toHaveProperty('color', 'secondary');
66
+ expect(model.elements['system.backend']).not.toHaveProperty('description');
67
+ expect(model).toMatchSnapshot();
68
+ });
69
+ it('builds model with extend', async () => {
70
+ const { parse, validateAll, buildModel } = createTestServices();
71
+ await parse(`
72
+ specification {
73
+ element component
74
+ element user
75
+ tag deprecated
76
+ }
77
+ model {
78
+ user client
79
+ component system {
80
+ backend = component
81
+ component frontend
82
+ }
83
+ }
84
+ `);
85
+ await parse(`
86
+ model {
87
+ extend system.backend {
88
+ component api
89
+ }
90
+ system.frontend -> api 'requests'
91
+ client -> system.frontend {
92
+ title 'opens'
93
+ }
94
+ }
95
+ views {
96
+ view index {
97
+ title 'Index'
98
+ include *
99
+ }
100
+
101
+ view v1 of api {
102
+ include *
103
+ autoLayout LeftRight
104
+ }
105
+
106
+ view of system.frontend {
107
+ include *
108
+ }
109
+ }
110
+ `);
111
+ const { errors } = await validateAll();
112
+ expect(errors).toEqual([]);
113
+ const model = await buildModel();
114
+ expect(model).toBeDefined();
115
+ expect(model.elements).toMatchObject({
116
+ client: {
117
+ kind: 'user'
118
+ },
119
+ 'system.backend.api': {
120
+ kind: 'component'
121
+ }
122
+ });
123
+ expect(keys(model.relations)).toHaveLength(2);
124
+ expect(keys(model.views)).toHaveLength(3);
125
+ expect(model.views).toMatchObject({
126
+ index: {
127
+ id: 'index',
128
+ title: 'Index',
129
+ autoLayout: 'TB'
130
+ },
131
+ v1: {
132
+ id: 'v1',
133
+ viewOf: 'system.backend.api',
134
+ title: 'api',
135
+ autoLayout: 'LR'
136
+ }
137
+ });
138
+ expect(model.views['index']).not.toHaveProperty('viewOf');
139
+ expect(model).toMatchSnapshot();
140
+ });
141
+ });
@@ -4,32 +4,36 @@ import { NotificationType, RequestType0, RequestType } from 'vscode-languageserv
4
4
  export declare const onDidChangeLikeC4Model: NotificationType<unknown>;
5
5
  export declare const fetchLikeC4Model: RequestType0<{
6
6
  model: LikeC4Model | null;
7
- }, unknown>;
8
- export declare const buildDocuments: RequestType<string[], void, unknown>;
7
+ }, void>;
8
+ export declare const buildDocuments: RequestType<string[], void, void>;
9
9
  export declare const locateElement: RequestType<{
10
10
  element: Fqn;
11
- property?: string;
12
- }, Location | null, unknown>;
11
+ property: string | null;
12
+ }, {
13
+ location: Location | null;
14
+ }, void>;
13
15
  export declare const locateRelation: RequestType<{
14
16
  id: RelationID;
15
- }, Location | null, unknown>;
17
+ }, Location | null, void>;
16
18
  export declare const locateView: RequestType<{
17
19
  id: ViewID;
18
- }, Location | null, unknown>;
20
+ }, Location | null, void>;
19
21
  export declare const Rpc: {
20
22
  readonly onDidChangeModel: NotificationType<unknown>;
21
23
  readonly fetchModel: RequestType0<{
22
24
  model: LikeC4Model | null;
23
- }, unknown>;
24
- readonly buildDocuments: RequestType<string[], void, unknown>;
25
+ }, void>;
26
+ readonly buildDocuments: RequestType<string[], void, void>;
25
27
  readonly locateElement: RequestType<{
26
28
  element: Fqn;
27
- property?: string;
28
- }, Location | null, unknown>;
29
+ property: string | null;
30
+ }, {
31
+ location: Location | null;
32
+ }, void>;
29
33
  readonly locateRelation: RequestType<{
30
34
  id: RelationID;
31
- }, Location | null, unknown>;
35
+ }, Location | null, void>;
32
36
  readonly locateView: RequestType<{
33
37
  id: ViewID;
34
- }, Location | null, unknown>;
38
+ }, Location | null, void>;
35
39
  };
package/dist/protocol.js CHANGED
@@ -2,13 +2,13 @@ import { NotificationType, RequestType0, RequestType } from 'vscode-languageserv
2
2
  //#region From server
3
3
  export const onDidChangeLikeC4Model = new NotificationType('likec4/onDidChangeModel');
4
4
  //#endregion
5
- // //#region To server
5
+ //#region To server
6
6
  export const fetchLikeC4Model = new RequestType0('likec4/fetchModel');
7
7
  export const buildDocuments = new RequestType('likec4/buildDocuments');
8
8
  export const locateElement = new RequestType('likec4/locateElement');
9
9
  export const locateRelation = new RequestType('likec4/locateRelation');
10
10
  export const locateView = new RequestType('likec4/locateView');
11
- // //#endregion
11
+ //#endregion
12
12
  export const Rpc = {
13
13
  onDidChangeModel: onDidChangeLikeC4Model,
14
14
  fetchModel: fetchLikeC4Model,
@@ -1,5 +1,5 @@
1
1
  import { logger } from './logger';
2
- import { Rpc } from './protocol';
2
+ import { buildDocuments, fetchLikeC4Model, locateElement, locateRelation, locateView } from '@likec4/language-protocol';
3
3
  export function registerProtocolHandlers(services) {
4
4
  const connection = services.shared.lsp.Connection;
5
5
  if (!connection) {
@@ -8,7 +8,7 @@ export function registerProtocolHandlers(services) {
8
8
  const modelBuilder = services.likec4.ModelBuilder;
9
9
  const modelLocator = services.likec4.ModelLocator;
10
10
  const LangiumDocuments = services.shared.workspace.LangiumDocuments;
11
- connection.onRequest(Rpc.fetchModel, async (_cancelToken) => {
11
+ connection.onRequest(fetchLikeC4Model, async (_cancelToken) => {
12
12
  let model;
13
13
  try {
14
14
  model = modelBuilder.buildModel() ?? null;
@@ -21,7 +21,7 @@ export function registerProtocolHandlers(services) {
21
21
  model: model ?? null
22
22
  });
23
23
  });
24
- connection.onRequest(Rpc.buildDocuments, async (docs, cancelToken) => {
24
+ connection.onRequest(buildDocuments, async (docs, cancelToken) => {
25
25
  const changed = [];
26
26
  for (const d of docs) {
27
27
  const uri = d;
@@ -37,13 +37,28 @@ export function registerProtocolHandlers(services) {
37
37
  ]`);
38
38
  await services.shared.workspace.DocumentBuilder.update(changed, [], cancelToken);
39
39
  });
40
- connection.onRequest(Rpc.locateElement, ({ element, property }, _cancelToken) => {
41
- return modelLocator.locateElement(element, property);
40
+ connection.onRequest(locateElement, async ({ element, property }, _cancelToken) => {
41
+ try {
42
+ return Promise.resolve(modelLocator.locateElement(element, property ?? 'name'));
43
+ }
44
+ catch (e) {
45
+ return Promise.reject(e);
46
+ }
42
47
  });
43
- connection.onRequest(Rpc.locateRelation, ({ id }, _cancelToken) => {
44
- return modelLocator.locateRelation(id);
48
+ connection.onRequest(locateRelation, ({ id }, _cancelToken) => {
49
+ try {
50
+ return Promise.resolve(modelLocator.locateRelation(id));
51
+ }
52
+ catch (e) {
53
+ return Promise.reject(e);
54
+ }
45
55
  });
46
- connection.onRequest(Rpc.locateView, ({ id }, _cancelToken) => {
47
- return modelLocator.locateView(id);
56
+ connection.onRequest(locateView, ({ id }, _cancelToken) => {
57
+ try {
58
+ return Promise.resolve(modelLocator.locateView(id));
59
+ }
60
+ catch (e) {
61
+ return Promise.reject(e);
62
+ }
48
63
  });
49
64
  }
@@ -1,3 +1,4 @@
1
+ /// <reference types="react" />
1
2
  import type { ValidationCheck } from 'langium';
2
3
  import type { ast } from '../ast';
3
4
  import type { LikeC4Services } from '../module';
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,65 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { createTestServices } from '../test';
3
+ describe('elementChecks', () => {
4
+ it('should report duplicate element names', async () => {
5
+ const { validate } = createTestServices();
6
+ const { diagnostics } = await validate(`
7
+ specification {
8
+ element component
9
+ }
10
+ model {
11
+ component c1
12
+ component c2
13
+ component c1
14
+ }
15
+ `);
16
+ expect(diagnostics).toHaveLength(2);
17
+ for (const diagnostic of diagnostics) {
18
+ expect(diagnostic.severity, 'diagnostic severity').toBe(1);
19
+ expect(diagnostic.message, 'diagnostic message').toBe('Duplicate element name c1');
20
+ }
21
+ });
22
+ it('should report duplicate element names in extendElement', async () => {
23
+ const { parse, validateAll } = createTestServices();
24
+ await parse(`
25
+ specification {
26
+ element component
27
+ }
28
+ model {
29
+ component c1 {
30
+ component c2 {
31
+ component c3
32
+ }
33
+ }
34
+ }
35
+ `);
36
+ await parse(`
37
+ model {
38
+ extend c1.c2 {
39
+ component c3
40
+ }
41
+ }
42
+ `);
43
+ const { diagnostics } = await validateAll();
44
+ expect(diagnostics).toHaveLength(2);
45
+ for (const diagnostic of diagnostics) {
46
+ expect(diagnostic.severity, 'diagnostic severity').toBe(1);
47
+ expect(diagnostic.message, 'diagnostic message').toBe('Duplicate element name c3 (c1.c2.c3)');
48
+ }
49
+ });
50
+ it('should not report duplicate element names in nested', async () => {
51
+ const { validate } = createTestServices();
52
+ const { errors } = await validate(`
53
+ specification {
54
+ element component
55
+ }
56
+ model {
57
+ component c1
58
+ component c2 {
59
+ component c1
60
+ }
61
+ }
62
+ `);
63
+ expect(errors).toEqual([]);
64
+ });
65
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,93 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { createTestServices } from '../test';
3
+ describe('relationChecks', () => {
4
+ it('should not report invalid relations', async () => {
5
+ const { validate } = createTestServices();
6
+ const { errors } = await validate(`
7
+ specification {
8
+ element component
9
+ }
10
+ model {
11
+ component c1 {
12
+ component c2 {
13
+ -> c3
14
+ }
15
+ }
16
+ component c3 {
17
+ this -> c1
18
+ }
19
+ c3 -> c2
20
+ }
21
+ `);
22
+ expect(errors).toEqual([]);
23
+ });
24
+ it('should report invalid relation: parent -> child', async () => {
25
+ const { validate } = createTestServices();
26
+ const { errors } = await validate(`
27
+ specification {
28
+ element component
29
+ }
30
+ model {
31
+ component c1 {
32
+ component c2 {
33
+ component c3
34
+ }
35
+ }
36
+ c1 -> c3
37
+ }
38
+ `);
39
+ expect(errors).toEqual(['Invalid relation (same hierarchy)']);
40
+ });
41
+ it('should report invalid relation: -> nested child', async () => {
42
+ const { validate } = createTestServices();
43
+ const { errors } = await validate(`
44
+ specification {
45
+ element component
46
+ }
47
+ model {
48
+ component c1 {
49
+ component c2 {
50
+ component c3
51
+ }
52
+ -> c3
53
+ }
54
+ }
55
+ `);
56
+ expect(errors).toEqual(['Invalid relation (same hierarchy)']);
57
+ });
58
+ it('should report invalid relation: child -> parent', async () => {
59
+ const { validate } = createTestServices();
60
+ const { errors } = await validate(`
61
+ specification {
62
+ element component
63
+ }
64
+ model {
65
+ component c1 {
66
+ component c2 {
67
+ component c3
68
+ }
69
+ }
70
+ c3 -> c2
71
+ }
72
+ `);
73
+ expect(errors).toEqual(['Invalid relation (same hierarchy)']);
74
+ });
75
+ it('should report invalid relation: nested child -> parent', async () => {
76
+ const { validate } = createTestServices();
77
+ const { errors } = await validate(`
78
+ specification {
79
+ element component
80
+ }
81
+ model {
82
+ component c1 {
83
+ component c2 {
84
+ component c3 {
85
+ -> c1
86
+ }
87
+ }
88
+ }
89
+ }
90
+ `);
91
+ expect(errors).toEqual(['Invalid relation (same hierarchy)']);
92
+ });
93
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,31 @@
1
+ import { expect, test } from 'vitest';
2
+ import { createTestServices } from '../test';
3
+ const { validate } = createTestServices();
4
+ test('elementKindChecks', async () => {
5
+ const { diagnostics } = await validate(`
6
+ specification {
7
+ element component
8
+ element user
9
+ element component
10
+ }
11
+ `);
12
+ expect(diagnostics).toHaveLength(2);
13
+ for (const diagnostic of diagnostics) {
14
+ expect(diagnostic.severity, 'diagnostic severity').toBe(1);
15
+ expect(diagnostic.message, 'diagnostic message').toBe("Duplicate element kind 'component'");
16
+ }
17
+ });
18
+ test('tagChecks', async () => {
19
+ const { diagnostics } = await validate(`
20
+ specification {
21
+ tag tag1
22
+ tag tag2
23
+ tag tag1
24
+ }
25
+ `);
26
+ expect(diagnostics).toHaveLength(2);
27
+ for (const diagnostic of diagnostics) {
28
+ expect(diagnostic.severity, 'diagnostic severity').toBe(1);
29
+ expect(diagnostic.message, 'diagnostic message').toBe("Duplicate tag 'tag1'");
30
+ }
31
+ });
@@ -0,0 +1 @@
1
+ export {};