@likec4/language-server 0.5.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 (47) 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/ast.js +16 -15
  27. package/dist/generated/ast.d.ts +16 -12
  28. package/dist/generated/ast.js +15 -5
  29. package/dist/generated/grammar.js +1534 -1379
  30. package/dist/index.d.ts +1 -1
  31. package/dist/index.js +0 -2
  32. package/dist/lsp/SemanticTokenProvider.js +32 -58
  33. package/dist/model/model-builder.js +13 -12
  34. package/dist/model/model-builder.spec.d.ts +1 -0
  35. package/dist/model/model-builder.spec.js +141 -0
  36. package/dist/protocol.d.ts +16 -12
  37. package/dist/protocol.js +2 -2
  38. package/dist/registerProtocolHandlers.js +24 -9
  39. package/dist/validation/element.spec.d.ts +1 -0
  40. package/dist/validation/element.spec.js +65 -0
  41. package/dist/validation/relation.spec.d.ts +1 -0
  42. package/dist/validation/relation.spec.js +93 -0
  43. package/dist/validation/specification.spec.d.ts +1 -0
  44. package/dist/validation/specification.spec.js +31 -0
  45. package/dist/validation/view.spec.d.ts +1 -0
  46. package/dist/validation/view.spec.js +20 -0
  47. package/package.json +22 -18
package/dist/index.d.ts CHANGED
@@ -1,3 +1,3 @@
1
1
  export { createLanguageServices } from './module';
2
+ export type { LikeC4Services } from './module';
2
3
  export { LikeC4LanguageMetaData as LanguageMetaData } from './generated/module';
3
- export { Rpc } from './protocol';
package/dist/index.js CHANGED
@@ -1,6 +1,5 @@
1
1
  // import { type DefaultSharedModuleContext, startLanguageServer as startLangiumLanguageServer } from 'langium'
2
2
  export { createLanguageServices } from './module';
3
- // export type { C4XServices }
4
3
  // export type { C4XModel } from './c4x-model'
5
4
  // export type { C4XLangiumDocument } from './ast'
6
5
  // export {
@@ -13,4 +12,3 @@ export { LikeC4LanguageMetaData as LanguageMetaData } from './generated/module';
13
12
  // startLangiumLanguageServer(shared)
14
13
  // return likec4
15
14
  // }
16
- export { Rpc } from './protocol';
@@ -15,17 +15,8 @@ export class LikeC4SemanticTokenProvider extends AbstractSemanticTokenProvider {
15
15
  property: 'el',
16
16
  type: isElementRefHead(node) ? SemanticTokenTypes.variable : SemanticTokenTypes.property
17
17
  });
18
- // acceptor({
19
- // node,
20
- // property: 'el',
21
- // type: SemanticTokenTypes.variable,
22
- // })
23
18
  return;
24
19
  }
25
- // if (ast.isSpec(node)) {
26
- // keyword('spec')
27
- // return
28
- // }
29
20
  if (ast.isWildcardExpression(node)) {
30
21
  acceptor({
31
22
  node,
@@ -56,21 +47,6 @@ export class LikeC4SemanticTokenProvider extends AbstractSemanticTokenProvider {
56
47
  }
57
48
  return;
58
49
  }
59
- // if (ast.isDynamicViewStep(node)) {
60
- // keyword(node.isReverse ? '<-' : '->')
61
- // if (hasTitle(node)) {
62
- // acceptor({
63
- // node,
64
- // property: 'title',
65
- // type: SemanticTokenTypes.string
66
- // })
67
- // }
68
- // return
69
- // }
70
- // if (ast.isStyleProperties(node)) {
71
- // keyword('style')
72
- // return
73
- // }
74
50
  if (ast.isElementKind(node)) {
75
51
  acceptor({
76
52
  node,
@@ -122,7 +98,7 @@ export class LikeC4SemanticTokenProvider extends AbstractSemanticTokenProvider {
122
98
  });
123
99
  return;
124
100
  }
125
- if (ast.isColorProperty(node) || ast.isShapeProperty(node)) {
101
+ if (ast.isAStyleProperty(node)) {
126
102
  acceptor({
127
103
  node,
128
104
  property: 'key',
@@ -135,36 +111,34 @@ export class LikeC4SemanticTokenProvider extends AbstractSemanticTokenProvider {
135
111
  });
136
112
  return;
137
113
  }
138
- if (ast.isElementProperty(node) || ast.isRelationProperty(node) || ast.isViewProperty(node)) {
114
+ if (ast.isAnyStringProperty(node)) {
139
115
  acceptor({
140
116
  node,
141
117
  property: 'key',
142
118
  type: SemanticTokenTypes.keyword
143
119
  });
144
- if ('value' in node) {
145
- acceptor({
146
- node,
147
- property: 'value',
148
- type: SemanticTokenTypes.string
149
- });
150
- }
151
- return;
152
- }
153
- if (ast.isModel(node)) {
154
- keyword('model');
155
- return;
156
- }
157
- if (ast.isModelViews(node)) {
158
- keyword('views');
120
+ acceptor({
121
+ node,
122
+ property: 'value',
123
+ type: SemanticTokenTypes.string
124
+ });
159
125
  return;
160
126
  }
127
+ // if (ast.isModel(node)) {
128
+ // keyword('model')
129
+ // return
130
+ // }
131
+ // if (ast.isModelViews(node)) {
132
+ // keyword('views')
133
+ // return
134
+ // }
161
135
  if (ast.isElement(node)) {
162
136
  return this.highlightAstElement(node, acceptor);
163
137
  }
164
- if (ast.isExtendElement(node)) {
165
- keyword('extend');
166
- return;
167
- }
138
+ // if (ast.isExtendElement(node)) {
139
+ // keyword('extend')
140
+ // return
141
+ // }
168
142
  // if (ast.isElementProperty(node) || ast.isRelationProperty(node) || ast.isViewProperty(node)) {
169
143
  // acceptor({
170
144
  // node,
@@ -188,19 +162,19 @@ export class LikeC4SemanticTokenProvider extends AbstractSemanticTokenProvider {
188
162
  // keyword('steps')
189
163
  // return
190
164
  // }
191
- if (ast.isViewRuleAutoLayout(node)) {
192
- keyword('autoLayout');
193
- return;
194
- }
195
- if (ast.isViewRuleStyle(node)) {
196
- keyword('style');
197
- return;
198
- }
199
- if (ast.isViewRuleExpression(node)) {
200
- keyword(node.isInclude ? 'include' : 'exclude');
201
- return;
202
- }
203
- //
165
+ // if (ast.isViewRuleAutoLayout(node)) {
166
+ // keyword('autoLayout')
167
+ // return
168
+ // }
169
+ // if (ast.isViewRuleStyle(node)) {
170
+ // keyword('style')
171
+ // return
172
+ // }
173
+ // if (ast.isViewRuleExpression(node)) {
174
+ // keyword(node.isInclude ? 'include' : 'exclude')
175
+ // return
176
+ // }
177
+ // //
204
178
  }
205
179
  highlightAstElement(node, acceptor) {
206
180
  acceptor({
@@ -6,10 +6,11 @@ import { DocumentState, getDocument, interruptAndCheck } from 'langium';
6
6
  import objectHash from 'object-hash';
7
7
  import { clone, isNil, mergeDeepRight, omit, reduce } from 'rambdax';
8
8
  import invariant from 'tiny-invariant';
9
+ import { toAutoLayout } from '../ast';
9
10
  import { ElementViewOps, ast, c4hash, cleanParsedModel, isLikeC4LangiumDocument, isParsedLikeC4LangiumDocument, resolveRelationPoints, streamModel, toElementStyle } from '../ast';
10
11
  import { elementRef, strictElementRefFqn } from '../elementRef';
11
12
  import { logger } from '../logger';
12
- import { Rpc } from '../protocol';
13
+ import { Rpc } from '@likec4/language-protocol';
13
14
  import { failExpectedNever } from '../utils';
14
15
  export class LikeC4ModelBuilder {
15
16
  services;
@@ -258,29 +259,29 @@ export class LikeC4ModelBuilder {
258
259
  }
259
260
  failExpectedNever(astNode);
260
261
  }
261
- parseViewRule(astNode) {
262
- if (ast.isViewRuleExpression(astNode)) {
263
- 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));
264
265
  return {
265
- isInclude: astNode.isInclude,
266
+ isInclude: astRule.isInclude,
266
267
  exprs
267
268
  };
268
269
  }
269
- if (ast.isViewRuleStyle(astNode)) {
270
- const styleProps = toElementStyle(astNode.props);
270
+ if (ast.isViewRuleStyle(astRule)) {
271
+ const styleProps = toElementStyle(astRule.props);
271
272
  return {
272
- targets: astNode.targets.map(n => this.parseElementExpression(n)),
273
+ targets: astRule.targets.map(n => this.parseElementExpression(n)),
273
274
  style: {
274
275
  ...styleProps
275
276
  }
276
277
  };
277
278
  }
278
- if (ast.isViewRuleAutoLayout(astNode)) {
279
+ if (ast.isViewRuleAutoLayout(astRule)) {
279
280
  return {
280
- autoLayout: astNode.direction
281
+ autoLayout: toAutoLayout(astRule.direction)
281
282
  };
282
283
  }
283
- failExpectedNever(astNode);
284
+ failExpectedNever(astRule);
284
285
  }
285
286
  parseElementView(astNode) {
286
287
  const viewOfEl = astNode.viewOf && elementRef(astNode.viewOf);
@@ -326,6 +327,6 @@ export class LikeC4ModelBuilder {
326
327
  return;
327
328
  }
328
329
  logger.debug('Send onDidChangeModel');
329
- await connection.sendNotification(Rpc.onDidChangeModel, null);
330
+ await connection.sendNotification(Rpc.onDidChangeModel);
330
331
  }
331
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
  }
@@ -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 {};