@likec4/language-server 1.17.1 → 1.18.0

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 (89) hide show
  1. package/contrib/likec4.tmLanguage.json +1 -1
  2. package/dist/browser.cjs +1 -1
  3. package/dist/browser.d.cts +2 -2
  4. package/dist/browser.d.mts +2 -2
  5. package/dist/browser.d.ts +2 -2
  6. package/dist/browser.mjs +2 -2
  7. package/dist/index.cjs +1 -1
  8. package/dist/index.d.cts +2 -2
  9. package/dist/index.d.mts +2 -2
  10. package/dist/index.d.ts +2 -2
  11. package/dist/index.mjs +2 -2
  12. package/dist/protocol.d.cts +8 -5
  13. package/dist/protocol.d.mts +8 -5
  14. package/dist/protocol.d.ts +8 -5
  15. package/dist/shared/{language-server.DKV_FdPN.cjs → language-server.CO_nmHiL.cjs} +5598 -4213
  16. package/dist/shared/{language-server.BQRvVmE0.d.cts → language-server.Da6ey08o.d.cts} +390 -74
  17. package/dist/shared/{language-server.BysPcTxr.d.ts → language-server.De7S3e5Z.d.ts} +390 -74
  18. package/dist/shared/{language-server._wkyPgso.d.mts → language-server.Dj4iDjtB.d.mts} +390 -74
  19. package/dist/shared/{language-server.BIbAD1T-.mjs → language-server.oO_9JoAG.mjs} +5604 -4230
  20. package/package.json +11 -25
  21. package/src/Rpc.ts +6 -3
  22. package/src/ast.ts +124 -71
  23. package/src/generated/ast.ts +655 -39
  24. package/src/generated/grammar.ts +1 -1
  25. package/src/index.ts +1 -0
  26. package/src/like-c4.langium +170 -22
  27. package/src/logger.ts +7 -2
  28. package/src/lsp/CodeLensProvider.ts +0 -1
  29. package/src/lsp/CompletionProvider.ts +17 -2
  30. package/src/lsp/DocumentSymbolProvider.ts +5 -2
  31. package/src/lsp/HoverProvider.ts +34 -2
  32. package/src/lsp/SemanticTokenProvider.ts +58 -32
  33. package/src/model/deployments-index.ts +218 -0
  34. package/src/model/fqn-computation.ts +1 -1
  35. package/src/model/fqn-index.ts +0 -1
  36. package/src/model/index.ts +1 -0
  37. package/src/model/model-builder.ts +172 -37
  38. package/src/model/model-locator.ts +36 -7
  39. package/src/model/model-parser.ts +554 -92
  40. package/src/model-change/changeViewLayout.ts +2 -2
  41. package/src/module.ts +10 -4
  42. package/src/protocol.ts +10 -6
  43. package/src/references/index.ts +1 -0
  44. package/src/references/name-provider.ts +37 -0
  45. package/src/references/scope-computation.ts +130 -21
  46. package/src/references/scope-provider.ts +63 -36
  47. package/src/shared/NodeKindProvider.ts +15 -3
  48. package/src/utils/deploymentRef.ts +31 -0
  49. package/src/{elementRef.ts → utils/elementRef.ts} +1 -1
  50. package/src/utils/stringHash.ts +2 -2
  51. package/src/validation/_shared.ts +7 -5
  52. package/src/validation/deployment-checks.ts +144 -0
  53. package/src/validation/dynamic-view-step.ts +1 -1
  54. package/src/validation/index.ts +7 -0
  55. package/src/validation/relation.ts +1 -1
  56. package/src/validation/view-predicates/deployments.ts +56 -0
  57. package/src/validation/view-predicates/index.ts +1 -0
  58. package/src/view-utils/assignNavigateTo.ts +6 -5
  59. package/src/view-utils/index.ts +0 -1
  60. package/dist/model-graph/index.cjs +0 -10
  61. package/dist/model-graph/index.d.cts +0 -81
  62. package/dist/model-graph/index.d.mts +0 -81
  63. package/dist/model-graph/index.d.ts +0 -81
  64. package/dist/model-graph/index.mjs +0 -1
  65. package/dist/shared/language-server.D2QdbOJO.cjs +0 -1995
  66. package/dist/shared/language-server.DGrBGmsd.mjs +0 -1981
  67. package/src/model-graph/LikeC4ModelGraph.ts +0 -338
  68. package/src/model-graph/compute-view/__test__/fixture.ts +0 -630
  69. package/src/model-graph/compute-view/compute.ts +0 -788
  70. package/src/model-graph/compute-view/index.ts +0 -33
  71. package/src/model-graph/compute-view/predicates.ts +0 -509
  72. package/src/model-graph/dynamic-view/__test__/fixture.ts +0 -61
  73. package/src/model-graph/dynamic-view/compute.ts +0 -313
  74. package/src/model-graph/dynamic-view/index.ts +0 -29
  75. package/src/model-graph/index.ts +0 -3
  76. package/src/model-graph/utils/applyCustomElementProperties.ts +0 -65
  77. package/src/model-graph/utils/applyCustomRelationProperties.ts +0 -41
  78. package/src/model-graph/utils/applyViewRuleStyles.ts +0 -49
  79. package/src/model-graph/utils/buildComputeNodes.ts +0 -113
  80. package/src/model-graph/utils/buildElementNotations.ts +0 -63
  81. package/src/model-graph/utils/elementExpressionToPredicate.ts +0 -39
  82. package/src/model-graph/utils/relationExpressionToPredicates.ts +0 -43
  83. package/src/model-graph/utils/sortNodes.ts +0 -105
  84. package/src/model-graph/utils/uniqueTags.test.ts +0 -42
  85. package/src/model-graph/utils/uniqueTags.ts +0 -19
  86. package/src/utils/graphlib.ts +0 -9
  87. package/src/view-utils/resolve-extended-views.ts +0 -66
  88. package/src/view-utils/resolve-global-rules.ts +0 -88
  89. package/src/view-utils/view-hash.ts +0 -27
@@ -1,13 +1,16 @@
1
- import { type HexColorLiteral, invariant, isNonEmptyArray, nonexhaustive } from '@likec4/core'
2
1
  import type * as c4 from '@likec4/core'
2
+ import { type HexColorLiteral, invariant, isNonEmptyArray, nameFromFqn, nonexhaustive, nonNullable } from '@likec4/core'
3
3
  import type { AstNode, LangiumDocument } from 'langium'
4
4
  import { AstUtils, CstUtils } from 'langium'
5
- import { filter, first, flatMap, isDefined, isNonNullish, isTruthy, map, mapToObj, pipe } from 'remeda'
5
+ import { filter, first, flatMap, isArray, isDefined, isNonNullish, isTruthy, map, mapToObj, pipe } from 'remeda'
6
6
  import stripIndent from 'strip-indent'
7
7
  import type { Writable } from 'type-fest'
8
8
  import type {
9
9
  ChecksFromDiagnostics,
10
10
  FqnIndexedDocument,
11
+ ParsedAstDeployment,
12
+ ParsedAstDeploymentRelation,
13
+ ParsedAstDeploymentView,
11
14
  ParsedAstDynamicView,
12
15
  ParsedAstElement,
13
16
  ParsedAstElementView,
@@ -30,11 +33,12 @@ import {
30
33
  toRelationshipStyleExcludeDefaults,
31
34
  ViewOps
32
35
  } from '../ast'
33
- import { elementRef, getFqnElementRef } from '../elementRef'
34
- import { isGlobalStyle, isGlobalStyleGroup, type NotationProperty } from '../generated/ast'
35
- import { logError, logger, logWarnError } from '../logger'
36
+ import { type NotationProperty } from '../generated/ast'
37
+ import { logger, logWarnError } from '../logger'
36
38
  import type { LikeC4Services } from '../module'
37
39
  import { stringHash } from '../utils'
40
+ import { instanceRef } from '../utils/deploymentRef'
41
+ import { elementRef, getFqnElementRef } from '../utils/elementRef'
38
42
  import { deserializeFromComment, hasManualLayout } from '../view-utils/manual-layout'
39
43
  import type { FqnIndex } from './fqn-index'
40
44
  import { parseWhereClause } from './model-parser-where'
@@ -60,21 +64,13 @@ export class LikeC4ModelParser {
60
64
  logger.debug(`[ModelParser] Created`)
61
65
  }
62
66
 
63
- parse(doc: LangiumDocument | LangiumDocument[]): ParsedLikeC4LangiumDocument[] {
64
- const docs = Array.isArray(doc) ? doc : [doc]
65
- const result = [] as ParsedLikeC4LangiumDocument[]
66
- for (const doc of docs) {
67
- if (!isFqnIndexedDocument(doc)) {
68
- logger.warn(`Not a FqnIndexedDocument: ${doc.uri.toString(true)}`)
69
- continue
70
- }
71
- try {
72
- result.push(this.parseLikeC4Document(doc))
73
- } catch (cause) {
74
- logError(new Error(`Error parsing document ${doc.uri.toString()}`, { cause }))
75
- }
67
+ parse(doc: LangiumDocument): ParsedLikeC4LangiumDocument {
68
+ invariant(isFqnIndexedDocument(doc), `Not a FqnIndexedDocument: ${doc.uri.toString(true)}`)
69
+ try {
70
+ return this.parseLikeC4Document(doc)
71
+ } catch (cause) {
72
+ throw new Error(`Error parsing document ${doc.uri.toString()}`, { cause })
76
73
  }
77
- return result
78
74
  }
79
75
 
80
76
  protected parseLikeC4Document(_doc: FqnIndexedDocument) {
@@ -83,14 +79,21 @@ export class LikeC4ModelParser {
83
79
  this.parseSpecification(doc, isValid)
84
80
  this.parseModel(doc, isValid)
85
81
  this.parseGlobal(doc, isValid)
82
+ this.parseDeployment(doc, isValid)
86
83
  this.parseViews(doc, isValid)
87
84
  return doc
88
85
  }
89
86
 
90
87
  private parseSpecification(doc: ParsedLikeC4LangiumDocument, isValid: IsValidFn) {
91
- const { parseResult, c4Specification } = doc
88
+ const {
89
+ parseResult: {
90
+ value: {
91
+ specifications
92
+ }
93
+ },
94
+ c4Specification
95
+ } = doc
92
96
 
93
- const specifications = parseResult.value.specifications.filter(isValid)
94
97
  const element_specs = specifications.flatMap(s => s.elements.filter(isValid))
95
98
  for (const { kind, props } of element_specs) {
96
99
  try {
@@ -103,9 +106,10 @@ export class LikeC4ModelParser {
103
106
  continue
104
107
  }
105
108
  const style = props.find(ast.isElementStyleProperty)
106
- const bodyProps = mapToObj(
107
- props.filter(ast.isSpecificationElementStringProperty).filter(p => isNonNullish(p.value)) ?? [],
108
- p => [p.key, removeIndent(p.value)] as const
109
+ const bodyProps = pipe(
110
+ props.filter(ast.isSpecificationElementStringProperty) ?? [],
111
+ filter(p => isValid(p) && isNonNullish(p.value)),
112
+ mapToObj(p => [p.key, removeIndent(p.value)] satisfies [string, string])
109
113
  )
110
114
  c4Specification.elements[kindName] = {
111
115
  ...bodyProps,
@@ -129,13 +133,14 @@ export class LikeC4ModelParser {
129
133
  logger.warn(`Relationship kind "${kindName}" is already defined`)
130
134
  continue
131
135
  }
132
- const bodyProps = mapToObj(
133
- props.filter(ast.isSpecificationRelationshipStringProperty).filter(p => isNonNullish(p.value)) ?? [],
134
- p => [p.key, removeIndent(p.value)]
136
+ const bodyProps = pipe(
137
+ props.filter(ast.isSpecificationRelationshipStringProperty) ?? [],
138
+ filter(p => isValid(p) && isNonNullish(p.value)),
139
+ mapToObj(p => [p.key, removeIndent(p.value)] satisfies [string, string])
135
140
  )
136
141
  c4Specification.relationships[kindName] = {
137
142
  ...bodyProps,
138
- ...toRelationshipStyleExcludeDefaults(props)
143
+ ...toRelationshipStyleExcludeDefaults(props, isValid)
139
144
  }
140
145
  } catch (e) {
141
146
  logWarnError(e)
@@ -150,6 +155,15 @@ export class LikeC4ModelParser {
150
155
  }
151
156
  }
152
157
 
158
+ const deploymentNodes_specs = specifications.flatMap(s => s.deploymentNodes.filter(isValid))
159
+ for (const deploymentNode of deploymentNodes_specs) {
160
+ try {
161
+ Object.assign(c4Specification.deployments, this.parseSpecificationDeploymentNodeKind(deploymentNode, isValid))
162
+ } catch (e) {
163
+ logWarnError(e)
164
+ }
165
+ }
166
+
153
167
  const colors_specs = specifications.flatMap(s => s.colors.filter(isValid))
154
168
  for (const { name, color } of colors_specs) {
155
169
  try {
@@ -168,6 +182,31 @@ export class LikeC4ModelParser {
168
182
  }
169
183
  }
170
184
 
185
+ private parseSpecificationDeploymentNodeKind(
186
+ { kind, props }: ast.SpecificationDeploymentNodeKind,
187
+ isValid: IsValidFn
188
+ ): { [key: c4.DeploymentNodeKind]: c4.DeploymentNodeKindSpecification } {
189
+ const kindName = kind.name as c4.DeploymentNodeKind
190
+ if (!isTruthy(kindName)) {
191
+ throw new Error('DeploymentNodeKind name is not resolved')
192
+ }
193
+
194
+ const style = props.find(ast.isElementStyleProperty)
195
+ const bodyProps = pipe(
196
+ props.filter(ast.isSpecificationElementStringProperty) ?? [],
197
+ filter(p => isValid(p) && isNonNullish(p.value)),
198
+ mapToObj(p => [p.key, removeIndent(p.value)] satisfies [string, string])
199
+ )
200
+ return {
201
+ [kindName]: {
202
+ ...bodyProps,
203
+ style: {
204
+ ...toElementStyle(style?.props, isValid)
205
+ }
206
+ }
207
+ }
208
+ }
209
+
171
210
  private parseModel(doc: ParsedLikeC4LangiumDocument, isValid: IsValidFn) {
172
211
  for (const el of streamModel(doc, isValid)) {
173
212
  if (ast.isElement(el)) {
@@ -180,7 +219,7 @@ export class LikeC4ModelParser {
180
219
  }
181
220
  if (ast.isRelation(el)) {
182
221
  try {
183
- doc.c4Relations.push(this.parseRelation(el))
222
+ doc.c4Relations.push(this.parseRelation(el, isValid))
184
223
  } catch (e) {
185
224
  logWarnError(e)
186
225
  }
@@ -192,7 +231,7 @@ export class LikeC4ModelParser {
192
231
 
193
232
  private parseElement(astNode: ast.Element, isValid: IsValidFn): ParsedAstElement {
194
233
  const id = this.resolveFqn(astNode)
195
- const kind = astNode.kind.$refText as c4.ElementKind
234
+ const kind = nonNullable(astNode.kind.ref, 'Element kind is not resolved').name as c4.ElementKind
196
235
  const tags = this.convertTags(astNode.body)
197
236
  const stylePropsAst = astNode.body?.props.find(ast.isElementStyleProperty)?.props
198
237
  const style = toElementStyle(stylePropsAst, isValid)
@@ -201,12 +240,14 @@ export class LikeC4ModelParser {
201
240
 
202
241
  let [title, description, technology] = astNode.props ?? []
203
242
 
204
- const bodyProps = mapToObj(
205
- astNode.body?.props.filter(ast.isElementStringProperty) ?? [],
206
- p => [p.key, p.value || undefined]
243
+ const bodyProps = pipe(
244
+ astNode.body?.props ?? [],
245
+ filter(isValid),
246
+ filter(ast.isElementStringProperty),
247
+ mapToObj(p => [p.key, p.value || undefined])
207
248
  )
208
249
 
209
- title = toSingleLine(title ?? bodyProps.title)
250
+ title = removeIndent(title ?? bodyProps.title)
210
251
  description = removeIndent(bodyProps.description ?? description)
211
252
  technology = toSingleLine(bodyProps.technology ?? technology)
212
253
 
@@ -214,7 +255,7 @@ export class LikeC4ModelParser {
214
255
 
215
256
  // Property has higher priority than from style
216
257
  const iconProp = astNode.body?.props.find(ast.isIconProperty)
217
- if (iconProp) {
258
+ if (iconProp && isValid(iconProp)) {
218
259
  const value = iconProp.libicon?.ref?.name ?? iconProp.value
219
260
  if (isTruthy(value)) {
220
261
  style.icon = value as c4.IconUrl
@@ -235,7 +276,7 @@ export class LikeC4ModelParser {
235
276
  }
236
277
  }
237
278
 
238
- private parseRelation(astNode: ast.Relation): ParsedAstRelation {
279
+ private parseRelation(astNode: ast.Relation, isValid: IsValidFn): ParsedAstRelation {
239
280
  const coupling = resolveRelationPoints(astNode)
240
281
  const target = this.resolveFqn(coupling.target)
241
282
  const source = this.resolveFqn(coupling.source)
@@ -260,14 +301,14 @@ export class LikeC4ModelParser {
260
301
 
261
302
  const title = removeIndent(astNode.title ?? bodyProps.title) ?? ''
262
303
  const description = removeIndent(bodyProps.description)
263
- const technology = removeIndent(astNode.technology) ?? toSingleLine(bodyProps.technology)
304
+ const technology = toSingleLine(astNode.technology) ?? removeIndent(bodyProps.technology)
264
305
 
265
306
  const styleProp = astNode.body?.props.find(ast.isRelationStyleProperty)
266
307
  const id = stringHash(
267
308
  astPath,
268
309
  source,
269
310
  target
270
- ) as c4.RelationID
311
+ ) as c4.RelationId
271
312
  return {
272
313
  id,
273
314
  astPath,
@@ -280,8 +321,8 @@ export class LikeC4ModelParser {
280
321
  ...(kind && { kind }),
281
322
  ...(tags && { tags }),
282
323
  ...(isNonEmptyArray(links) && { links }),
283
- ...toRelationshipStyleExcludeDefaults(styleProp?.props),
284
- ...(navigateTo && { navigateTo: navigateTo as c4.ViewID })
324
+ ...toRelationshipStyleExcludeDefaults(styleProp?.props, isValid),
325
+ ...(navigateTo && { navigateTo: navigateTo as c4.ViewId })
285
326
  }
286
327
  }
287
328
 
@@ -381,11 +422,10 @@ export class LikeC4ModelParser {
381
422
  }
382
423
 
383
424
  private parseViews(doc: ParsedLikeC4LangiumDocument, isValid: IsValidFn) {
384
- const viewBlocks = doc.parseResult.value.views.filter(v => isValid(v))
385
- for (const viewBlock of viewBlocks) {
425
+ for (const viewBlock of doc.parseResult.value.views) {
386
426
  const localStyles = viewBlock.styles.flatMap(s => {
387
427
  try {
388
- return this.parseViewRuleStyleOrGlobalRef(s, isValid)
428
+ return isValid(s) ? this.parseViewRuleStyleOrGlobalRef(s, isValid) : []
389
429
  } catch (e) {
390
430
  logWarnError(e)
391
431
  return []
@@ -397,11 +437,19 @@ export class LikeC4ModelParser {
397
437
  if (!isValid(view)) {
398
438
  continue
399
439
  }
400
- doc.c4Views.push(
401
- ast.isElementView(view)
402
- ? this.parseElementView(view, localStyles, isValid)
403
- : this.parseDynamicElementView(view, localStyles, isValid)
404
- )
440
+ switch (true) {
441
+ case ast.isElementView(view):
442
+ doc.c4Views.push(this.parseElementView(view, localStyles, isValid))
443
+ break
444
+ case ast.isDynamicView(view):
445
+ doc.c4Views.push(this.parseDynamicElementView(view, localStyles, isValid))
446
+ break
447
+ case ast.isDeploymentView(view):
448
+ doc.c4Views.push(this.parseDeploymentView(view, isValid))
449
+ break
450
+ default:
451
+ nonexhaustive(view)
452
+ }
405
453
  } catch (e) {
406
454
  logWarnError(e)
407
455
  }
@@ -412,16 +460,20 @@ export class LikeC4ModelParser {
412
460
  // TODO validate view rules
413
461
  private parseViewRulePredicate(astNode: ast.ViewRulePredicate, _isValid: IsValidFn): c4.ViewRulePredicate {
414
462
  const exprs = [] as c4.Expression[]
415
- let predicate: ast.Predicates | undefined = astNode.predicates
463
+ let predicate = astNode.predicates
416
464
  while (predicate) {
465
+ const { value, prev } = predicate
417
466
  try {
418
- if (isTruthy(predicate.value) && _isValid(predicate.value as any)) {
419
- exprs.unshift(this.parsePredicate(predicate.value, _isValid))
467
+ if (isTruthy(value) && _isValid(value as any)) {
468
+ exprs.unshift(this.parsePredicate(value, _isValid))
420
469
  }
421
470
  } catch (e) {
422
471
  logWarnError(e)
423
472
  }
424
- predicate = predicate.prev
473
+ if (!prev) {
474
+ break
475
+ }
476
+ predicate = prev
425
477
  }
426
478
  return ast.isIncludePredicate(astNode) ? { include: exprs } : { exclude: exprs }
427
479
  }
@@ -441,7 +493,9 @@ export class LikeC4ModelParser {
441
493
  let iter: ast.ElementExpressionsIterator['prev'] = astNode
442
494
  while (iter) {
443
495
  try {
444
- exprs.unshift(this.parseElementExpr(iter.value))
496
+ if (iter.value) {
497
+ exprs.unshift(this.parseElementExpr(iter.value))
498
+ }
445
499
  } catch (e) {
446
500
  logWarnError(e)
447
501
  }
@@ -501,7 +555,8 @@ export class LikeC4ModelParser {
501
555
  const element = this.resolveFqn(elementNode)
502
556
  return {
503
557
  element,
504
- isDescedants: true
558
+ isChildren: astNode.suffix === '.*',
559
+ isDescendants: astNode.suffix === '.**'
505
560
  }
506
561
  }
507
562
  if (ast.isElementRef(astNode)) {
@@ -523,17 +578,19 @@ export class LikeC4ModelParser {
523
578
  const props = astNode.custom?.props ?? []
524
579
  return props.reduce(
525
580
  (acc, prop) => {
581
+ if (!_isValid(prop)) {
582
+ return acc
583
+ }
526
584
  if (ast.isNavigateToProperty(prop)) {
527
585
  const viewId = prop.value.view.$refText
528
586
  if (isTruthy(viewId)) {
529
- acc.custom.navigateTo = viewId as c4.ViewID
587
+ acc.custom.navigateTo = viewId as c4.ViewId
530
588
  }
531
589
  return acc
532
590
  }
533
591
  if (ast.isElementStringProperty(prop)) {
534
592
  if (isDefined(prop.value)) {
535
- let value = prop.key === 'description' ? removeIndent(prop.value) : toSingleLine(prop.value)
536
- acc.custom[prop.key] = value || ''
593
+ acc.custom[prop.key] = removeIndent(prop.value) || ''
537
594
  }
538
595
  return acc
539
596
  }
@@ -664,7 +721,7 @@ export class LikeC4ModelParser {
664
721
  if (ast.isRelationNavigateToProperty(prop)) {
665
722
  const viewId = prop.value.view.ref?.name
666
723
  if (isTruthy(viewId)) {
667
- acc.customRelation.navigateTo = viewId as c4.ViewID
724
+ acc.customRelation.navigateTo = viewId as c4.ViewId
668
725
  }
669
726
  return acc
670
727
  }
@@ -803,7 +860,7 @@ export class LikeC4ModelParser {
803
860
  }
804
861
  }
805
862
 
806
- private parseViewManualLaout(node: ast.DynamicView | ast.ElementView): c4.ViewManualLayout | undefined {
863
+ private parseViewManualLayout(node: ast.LikeC4View): c4.ViewManualLayout | undefined {
807
864
  const commentNode = CstUtils.findCommentNode(node.$cstNode, ['BLOCK_COMMENT'])
808
865
  if (!commentNode || !hasManualLayout(commentNode.text)) {
809
866
  return undefined
@@ -854,47 +911,53 @@ export class LikeC4ModelParser {
854
911
  isBackward: true
855
912
  }
856
913
  }
857
- if (Array.isArray(node.custom?.props)) {
858
- for (const prop of node.custom.props) {
859
- try {
860
- if (ast.isRelationStringProperty(prop) || ast.isNotationProperty(prop) || ast.isNotesProperty(prop)) {
914
+ if (!isArray(node.custom?.props)) {
915
+ return step
916
+ }
917
+ for (const prop of node.custom.props) {
918
+ try {
919
+ switch (true) {
920
+ case ast.isRelationNavigateToProperty(prop): {
921
+ const viewId = prop.value.view.ref?.name
922
+ if (isTruthy(viewId)) {
923
+ step.navigateTo = viewId as c4.ViewId
924
+ }
925
+ break
926
+ }
927
+ case ast.isRelationStringProperty(prop):
928
+ case ast.isNotationProperty(prop):
929
+ case ast.isNotesProperty(prop): {
861
930
  if (isDefined(prop.value)) {
862
931
  step[prop.key] = removeIndent(prop.value) ?? ''
863
932
  }
864
- continue
933
+ break
865
934
  }
866
- if (ast.isArrowProperty(prop)) {
935
+ case ast.isArrowProperty(prop): {
867
936
  if (isDefined(prop.value)) {
868
937
  step[prop.key] = prop.value
869
938
  }
870
- continue
939
+ break
871
940
  }
872
- if (ast.isColorProperty(prop)) {
941
+ case ast.isColorProperty(prop): {
873
942
  const value = toColor(prop)
874
943
  if (isDefined(value)) {
875
944
  step[prop.key] = value
876
945
  }
877
- continue
946
+ break
878
947
  }
879
- if (ast.isLineProperty(prop)) {
948
+ case ast.isLineProperty(prop): {
880
949
  if (isDefined(prop.value)) {
881
950
  step[prop.key] = prop.value
882
951
  }
883
- continue
884
- }
885
- if (ast.isRelationNavigateToProperty(prop)) {
886
- const viewId = prop.value.view.ref?.name
887
- if (isTruthy(viewId)) {
888
- step.navigateTo = viewId as c4.ViewID
889
- }
890
- continue
952
+ break
891
953
  }
892
- nonexhaustive(prop)
893
- }
894
- catch (e) {
895
- logWarnError(e)
954
+ default:
955
+ nonexhaustive(prop)
896
956
  }
897
957
  }
958
+ catch (e) {
959
+ logWarnError(e)
960
+ }
898
961
  }
899
962
  return step
900
963
  }
@@ -925,7 +988,7 @@ export class LikeC4ModelParser {
925
988
  getDocument(astNode).uri.toString(),
926
989
  astPath,
927
990
  viewOf ?? ''
928
- ) as c4.ViewID
991
+ ) as c4.ViewId
929
992
  }
930
993
 
931
994
  const title = toSingleLine(body.props.find(p => p.key === 'title')?.value) ?? null
@@ -934,11 +997,11 @@ export class LikeC4ModelParser {
934
997
  const tags = this.convertTags(body)
935
998
  const links = this.convertLinks(body)
936
999
 
937
- const manualLayout = this.parseViewManualLaout(astNode)
1000
+ const manualLayout = this.parseViewManualLayout(astNode)
938
1001
 
939
1002
  const view: ParsedAstElementView = {
940
1003
  __: 'element',
941
- id: id as c4.ViewID,
1004
+ id: id as c4.ViewId,
942
1005
  astPath,
943
1006
  title,
944
1007
  description,
@@ -964,7 +1027,7 @@ export class LikeC4ModelParser {
964
1027
  const extendsView = astNode.extends.view.ref
965
1028
  invariant(extendsView?.name, 'view extends is not resolved: ' + astNode.$cstNode?.text)
966
1029
  return Object.assign(view, {
967
- extends: extendsView.name as c4.ViewID
1030
+ extends: extendsView.name as c4.ViewId
968
1031
  })
969
1032
  }
970
1033
 
@@ -987,7 +1050,7 @@ export class LikeC4ModelParser {
987
1050
  id = 'dynamic_' + stringHash(
988
1051
  getDocument(astNode).uri.toString(),
989
1052
  astPath
990
- ) as c4.ViewID
1053
+ ) as c4.ViewId
991
1054
  }
992
1055
 
993
1056
  const title = toSingleLine(props.find(p => p.key === 'title')?.value) ?? null
@@ -996,13 +1059,13 @@ export class LikeC4ModelParser {
996
1059
  const tags = this.convertTags(body)
997
1060
  const links = this.convertLinks(body)
998
1061
 
999
- ViewOps.writeId(astNode, id as c4.ViewID)
1062
+ ViewOps.writeId(astNode, id as c4.ViewId)
1000
1063
 
1001
- const manualLayout = this.parseViewManualLaout(astNode)
1064
+ const manualLayout = this.parseViewManualLayout(astNode)
1002
1065
 
1003
1066
  return {
1004
1067
  __: 'dynamic',
1005
- id: id as c4.ViewID,
1068
+ id: id as c4.ViewId,
1006
1069
  astPath,
1007
1070
  title,
1008
1071
  description,
@@ -1061,7 +1124,7 @@ export class LikeC4ModelParser {
1061
1124
  let iter: ast.DynamicViewPredicateIterator | undefined = astRule.predicates
1062
1125
  while (iter) {
1063
1126
  try {
1064
- if (isValid(iter.value as any)) {
1127
+ if (isNonNullish(iter.value) && isValid(iter.value as any)) {
1065
1128
  const c4expr = this.parseElementPredicate(iter.value, isValid)
1066
1129
  include.unshift(c4expr)
1067
1130
  }
@@ -1073,7 +1136,406 @@ export class LikeC4ModelParser {
1073
1136
  return { include }
1074
1137
  }
1075
1138
 
1076
- protected resolveFqn(node: ast.Element | ast.ExtendElement) {
1139
+ private parseDeployment(doc: ParsedLikeC4LangiumDocument, isValid: IsValidFn) {
1140
+ type TraversePair = ast.DeployedInstance | ast.DeploymentNode | ast.DeploymentRelation
1141
+ const traverseStack: TraversePair[] = doc.parseResult.value.deployments.flatMap(d => d.elements)
1142
+
1143
+ let next: TraversePair | undefined
1144
+ while ((next = traverseStack.shift())) {
1145
+ if (ast.isDeploymentRelation(next)) {
1146
+ doc.c4DeploymentRelations.push(this.parseDeploymentRelation(next, isValid))
1147
+ continue
1148
+ }
1149
+ if (!isValid(next)) {
1150
+ continue
1151
+ }
1152
+ try {
1153
+ switch (true) {
1154
+ case ast.isDeployedInstance(next):
1155
+ doc.c4Deployments.push(this.parseDeployedInstance(next, isValid))
1156
+ break
1157
+ case ast.isDeploymentNode(next): {
1158
+ doc.c4Deployments.push(this.parseDeploymentNode(next, isValid))
1159
+ if (next.body && next.body.elements.length > 0) {
1160
+ traverseStack.push(...next.body.elements)
1161
+ }
1162
+ break
1163
+ }
1164
+ default:
1165
+ nonexhaustive(next)
1166
+ }
1167
+ } catch (e) {
1168
+ logWarnError(e)
1169
+ }
1170
+ }
1171
+ }
1172
+
1173
+ public parseDeploymentNode(
1174
+ astNode: ast.DeploymentNode,
1175
+ isValid: IsValidFn
1176
+ ): ParsedAstDeployment.Node {
1177
+ const id = this.resolveFqn(astNode)
1178
+ const kind = nonNullable(astNode.kind.ref, 'DeploymentKind not resolved').name as c4.DeploymentNodeKind
1179
+ const tags = this.convertTags(astNode.body)
1180
+ const stylePropsAst = astNode.body?.props.find(ast.isElementStyleProperty)?.props
1181
+ const style = toElementStyle(stylePropsAst, isValid)
1182
+ const metadata = this.getMetadata(astNode.body?.props.find(ast.isMetadataProperty))
1183
+
1184
+ const bodyProps = pipe(
1185
+ astNode.body?.props ?? [],
1186
+ filter(isValid),
1187
+ filter(ast.isElementStringProperty),
1188
+ mapToObj(p => [p.key, p.value || undefined])
1189
+ )
1190
+
1191
+ const title = removeIndent(astNode.title ?? bodyProps.title)
1192
+ const description = removeIndent(bodyProps.description)
1193
+ const technology = toSingleLine(bodyProps.technology)
1194
+
1195
+ const links = this.convertLinks(astNode.body)
1196
+
1197
+ // Property has higher priority than from style
1198
+ const iconProp = astNode.body?.props.find(ast.isIconProperty)
1199
+ if (iconProp && isValid(iconProp)) {
1200
+ const value = iconProp.libicon?.ref?.name ?? iconProp.value
1201
+ if (isTruthy(value)) {
1202
+ style.icon = value as c4.IconUrl
1203
+ }
1204
+ }
1205
+
1206
+ return {
1207
+ id,
1208
+ kind,
1209
+ title: title ?? nameFromFqn(id),
1210
+ ...(metadata && { metadata }),
1211
+ ...(tags && { tags }),
1212
+ ...(links && isNonEmptyArray(links) && { links }),
1213
+ ...(isTruthy(technology) && { technology }),
1214
+ ...(isTruthy(description) && { description }),
1215
+ style
1216
+ }
1217
+ }
1218
+
1219
+ public parseDeployedInstance(
1220
+ astNode: ast.DeployedInstance,
1221
+ isValid: IsValidFn
1222
+ ): ParsedAstDeployment.Instance {
1223
+ const id = this.resolveFqn(astNode)
1224
+ const element = this.resolveFqn(nonNullable(elementRef(astNode.element), 'DeployedInstance element not found'))
1225
+
1226
+ const tags = this.convertTags(astNode.body)
1227
+ const stylePropsAst = astNode.body?.props.find(ast.isElementStyleProperty)?.props
1228
+ const style = toElementStyle(stylePropsAst, isValid)
1229
+ const metadata = this.getMetadata(astNode.body?.props.find(ast.isMetadataProperty))
1230
+
1231
+ const bodyProps = pipe(
1232
+ astNode.body?.props ?? [],
1233
+ filter(isValid),
1234
+ filter(ast.isElementStringProperty),
1235
+ mapToObj(p => [p.key, p.value || undefined])
1236
+ )
1237
+
1238
+ const title = removeIndent(astNode.title ?? bodyProps.title)
1239
+ const description = removeIndent(bodyProps.description)
1240
+ const technology = toSingleLine(bodyProps.technology)
1241
+
1242
+ const links = this.convertLinks(astNode.body)
1243
+
1244
+ // Property has higher priority than from style
1245
+ const iconProp = astNode.body?.props.find(ast.isIconProperty)
1246
+ if (iconProp && isValid(iconProp)) {
1247
+ const value = iconProp.libicon?.ref?.name ?? iconProp.value
1248
+ if (isTruthy(value)) {
1249
+ style.icon = value as c4.IconUrl
1250
+ }
1251
+ }
1252
+
1253
+ return {
1254
+ id,
1255
+ element,
1256
+ ...(metadata && { metadata }),
1257
+ ...(title && { title }),
1258
+ ...(tags && { tags }),
1259
+ ...(links && isNonEmptyArray(links) && { links }),
1260
+ ...(isTruthy(technology) && { technology }),
1261
+ ...(isTruthy(description) && { description }),
1262
+ style
1263
+ }
1264
+ }
1265
+
1266
+ public parseDeploymentRelation(
1267
+ astNode: ast.DeploymentRelation,
1268
+ isValid: IsValidFn
1269
+ ): ParsedAstDeploymentRelation {
1270
+ const astPath = this.getAstNodePath(astNode)
1271
+ const source = this.parseDeploymentDef(astNode.source)
1272
+ const target = this.parseDeploymentDef(astNode.target)
1273
+
1274
+ const tags = this.convertTags(astNode) ?? this.convertTags(astNode.body)
1275
+ const links = this.convertLinks(astNode.body)
1276
+ const kind = astNode.kind?.ref?.name as (c4.RelationshipKind | undefined)
1277
+ const metadata = this.getMetadata(astNode.body?.props.find(ast.isMetadataProperty))
1278
+
1279
+ const bodyProps = mapToObj(
1280
+ astNode.body?.props.filter(ast.isRelationStringProperty) ?? [],
1281
+ p => [p.key, p.value as string | undefined]
1282
+ )
1283
+
1284
+ const navigateTo = pipe(
1285
+ astNode.body?.props ?? [],
1286
+ filter(ast.isRelationNavigateToProperty),
1287
+ map(p => p.value.view.ref?.name),
1288
+ filter(isTruthy),
1289
+ first()
1290
+ )
1291
+
1292
+ const title = removeIndent(astNode.title ?? bodyProps.title)
1293
+ const description = removeIndent(bodyProps.description)
1294
+ const technology = toSingleLine(astNode.technology) ?? removeIndent(bodyProps.technology)
1295
+
1296
+ const styleProp = astNode.body?.props.find(ast.isRelationStyleProperty)
1297
+
1298
+ const id = stringHash(
1299
+ 'deployment',
1300
+ astPath,
1301
+ source.id,
1302
+ target.id
1303
+ ) as c4.RelationId
1304
+
1305
+ return {
1306
+ id,
1307
+ source,
1308
+ target,
1309
+ ...title && { title },
1310
+ ...(metadata && { metadata }),
1311
+ ...(isTruthy(technology) && { technology }),
1312
+ ...(isTruthy(description) && { description }),
1313
+ ...(kind && { kind }),
1314
+ ...(tags && { tags }),
1315
+ ...(isNonEmptyArray(links) && { links }),
1316
+ ...toRelationshipStyleExcludeDefaults(styleProp?.props, isValid),
1317
+ ...(navigateTo && { navigateTo: navigateTo as c4.ViewId }),
1318
+ astPath
1319
+ }
1320
+ }
1321
+
1322
+ private parseDeploymentView(
1323
+ astNode: ast.DeploymentView,
1324
+ isValid: IsValidFn
1325
+ ): ParsedAstDeploymentView {
1326
+ const body = astNode.body
1327
+ invariant(body, 'DynamicElementView body is not defined')
1328
+ // only valid props
1329
+ const props = body.props.filter(isValid)
1330
+ const astPath = this.getAstNodePath(astNode)
1331
+
1332
+ let id = astNode.name
1333
+ if (!id) {
1334
+ id = 'deployment_' + stringHash(
1335
+ getDocument(astNode).uri.toString(),
1336
+ astPath
1337
+ ) as c4.ViewId
1338
+ }
1339
+
1340
+ const title = toSingleLine(props.find(p => p.key === 'title')?.value) ?? null
1341
+ const description = removeIndent(props.find(p => p.key === 'description')?.value) ?? null
1342
+
1343
+ const tags = this.convertTags(body)
1344
+ const links = this.convertLinks(body)
1345
+
1346
+ ViewOps.writeId(astNode, id as c4.ViewId)
1347
+
1348
+ const manualLayout = this.parseViewManualLayout(astNode)
1349
+
1350
+ return {
1351
+ __: 'deployment',
1352
+ id: id as c4.ViewId,
1353
+ astPath,
1354
+ title,
1355
+ description,
1356
+ tags,
1357
+ links: isNonEmptyArray(links) ? links : null,
1358
+ rules: body.rules.flatMap(n => {
1359
+ try {
1360
+ return isValid(n) ? this.parseDeploymentViewRule(n, isValid) : []
1361
+ } catch (e) {
1362
+ logWarnError(e)
1363
+ return []
1364
+ }
1365
+ }),
1366
+ ...(manualLayout && { manualLayout })
1367
+ }
1368
+ }
1369
+
1370
+ private parseDeploymentViewRule(astRule: ast.DeploymentViewRule, isValid: IsValidFn): c4.DeploymentViewRule {
1371
+ if (ast.isDeploymentViewRulePredicate(astRule)) {
1372
+ return this.parseDeploymentViewRulePredicate(astRule, isValid)
1373
+ }
1374
+ if (ast.isViewRuleAutoLayout(astRule)) {
1375
+ return toAutoLayout(astRule)
1376
+ }
1377
+ if (ast.isDeploymentViewRuleStyle(astRule)) {
1378
+ return this.parseDeploymentViewRuleStyle(astRule, isValid)
1379
+ }
1380
+ nonexhaustive(astRule)
1381
+ }
1382
+
1383
+ private parseDeploymentViewRulePredicate(
1384
+ astRule: ast.DeploymentViewRulePredicate,
1385
+ isValid: IsValidFn
1386
+ ): c4.DeploymentViewRulePredicate {
1387
+ const exprs = [] as c4.DeploymentExpression[]
1388
+ let iterator: ast.DeploymentViewRulePredicateExpression | undefined = astRule.expr
1389
+ while (iterator) {
1390
+ try {
1391
+ const expr = iterator.value
1392
+ if (isNonNullish(expr) && isValid(expr)) {
1393
+ switch (true) {
1394
+ case ast.isDeploymentElementExpression(expr):
1395
+ exprs.unshift(this.parseDeploymentElementExpression(expr))
1396
+ break
1397
+ case ast.isDeploymentRelationExpression(expr):
1398
+ exprs.unshift(this.parseDeploymentRelationExpression(expr))
1399
+ break
1400
+ default:
1401
+ nonexhaustive(expr)
1402
+ }
1403
+ }
1404
+ } catch (e) {
1405
+ logWarnError(e)
1406
+ }
1407
+ iterator = iterator.prev
1408
+ }
1409
+ return astRule.isInclude ? { include: exprs } : { exclude: exprs }
1410
+ }
1411
+
1412
+ private parseDeploymentElementExpression(astNode: ast.DeploymentElementExpression): c4.DeploymentExpression {
1413
+ if (ast.isWildcardExpression(astNode)) {
1414
+ return {
1415
+ wildcard: true
1416
+ }
1417
+ }
1418
+ if (ast.isDeploymentRefExpression(astNode)) {
1419
+ const ref = this.parseDeploymentDef(astNode.ref)
1420
+ switch (true) {
1421
+ case astNode.selector === '._':
1422
+ return {
1423
+ ref,
1424
+ selector: 'expanded'
1425
+ }
1426
+ case astNode.selector === '.**':
1427
+ return {
1428
+ ref,
1429
+ selector: 'descendants'
1430
+ }
1431
+ case astNode.selector === '.*':
1432
+ return {
1433
+ ref,
1434
+ selector: 'children'
1435
+ }
1436
+ default:
1437
+ return { ref }
1438
+ }
1439
+ }
1440
+ nonexhaustive(astNode)
1441
+ }
1442
+
1443
+ private parseDeploymentRelationExpression(
1444
+ astNode: ast.DeploymentRelationExpression
1445
+ ): c4.DeploymentRelationExpression {
1446
+ if (ast.isDirectedDeploymentRelationExpression(astNode)) {
1447
+ return {
1448
+ source: this.parseDeploymentElementExpression(astNode.source.from),
1449
+ target: this.parseDeploymentElementExpression(astNode.target),
1450
+ isBidirectional: astNode.source.isBidirectional
1451
+ }
1452
+ }
1453
+ if (ast.isInOutDeploymentRelationExpression(astNode)) {
1454
+ return {
1455
+ inout: this.parseDeploymentElementExpression(astNode.inout.to)
1456
+ }
1457
+ }
1458
+ if (ast.isOutgoingDeploymentRelationExpression(astNode)) {
1459
+ return {
1460
+ outgoing: this.parseDeploymentElementExpression(astNode.from)
1461
+ }
1462
+ }
1463
+ if (ast.isIncomingDeploymentRelationExpression(astNode)) {
1464
+ return {
1465
+ incoming: this.parseDeploymentElementExpression(astNode.to)
1466
+ }
1467
+ }
1468
+ nonexhaustive(astNode)
1469
+ }
1470
+
1471
+ private parseDeploymentExpressionIterator(
1472
+ astNode: ast.DeploymentExpressionIterator,
1473
+ isValid: IsValidFn
1474
+ ): c4.DeploymentExpression[] {
1475
+ const exprs = [] as c4.DeploymentExpression[]
1476
+ let iter: ast.DeploymentExpressionIterator['prev'] = astNode
1477
+ while (iter) {
1478
+ try {
1479
+ if (isNonNullish(iter.value) && isValid(iter.value)) {
1480
+ exprs.unshift(this.parseDeploymentElementExpression(iter.value))
1481
+ }
1482
+ } catch (e) {
1483
+ logWarnError(e)
1484
+ }
1485
+ iter = iter.prev
1486
+ }
1487
+ return exprs
1488
+ }
1489
+
1490
+ private parseDeploymentDef(astNode: ast.DeploymentRef): c4.DeploymentRef {
1491
+ const refValue = nonNullable(
1492
+ astNode.value.ref,
1493
+ `Deployment ref is empty ${astNode.$cstNode?.range.start.line}:${astNode.$cstNode?.range.start.character}`
1494
+ )
1495
+ if (ast.isDeploymentNode(refValue)) {
1496
+ return {
1497
+ id: this.resolveFqn(refValue)
1498
+ }
1499
+ }
1500
+ const deployedInstanceAst = nonNullable(instanceRef(astNode), 'Instance ref not found')
1501
+ const id = this.resolveFqn(deployedInstanceAst)
1502
+
1503
+ if (ast.isElement(refValue)) {
1504
+ const element = this.resolveFqn(refValue)
1505
+ return {
1506
+ id,
1507
+ element
1508
+ }
1509
+ }
1510
+
1511
+ // To ensure with compiler we processed all types
1512
+ invariant(ast.isDeployedInstance(refValue), 'Invalid deployment ref: ' + this.getAstNodePath(astNode))
1513
+ return {
1514
+ id
1515
+ }
1516
+ }
1517
+
1518
+ protected parseDeploymentViewRuleStyle(
1519
+ astRule: ast.DeploymentViewRuleStyle,
1520
+ isValid: IsValidFn
1521
+ ): c4.DeploymentViewRuleStyle {
1522
+ const styleProps = astRule.props.filter(ast.isStyleProperty)
1523
+ const notationProperty = astRule.props.find(ast.isNotationProperty)
1524
+ const notation = removeIndent(notationProperty?.value)
1525
+ const targets = this.parseDeploymentExpressionIterator(astRule.target, isValid)
1526
+ return {
1527
+ targets,
1528
+ ...(notation && { notation }),
1529
+ style: {
1530
+ ...toElementStyle(styleProps, isValid)
1531
+ }
1532
+ }
1533
+ }
1534
+
1535
+ protected resolveFqn(node: ast.FqnReferenceable): c4.Fqn {
1536
+ if (ast.isDeploymentElement(node)) {
1537
+ return this.services.likec4.DeploymentsIndex.getFqnName(node)
1538
+ }
1077
1539
  if (ast.isExtendElement(node)) {
1078
1540
  return getFqnElementRef(node.element)
1079
1541
  }
@@ -1088,7 +1550,7 @@ export class LikeC4ModelParser {
1088
1550
 
1089
1551
  private getMetadata(metadataAstNode: ast.MetadataProperty | undefined): { [key: string]: string } | undefined {
1090
1552
  return metadataAstNode?.props != null
1091
- ? mapToObj(metadataAstNode.props, (p) => [p.key, removeIndent(p.value)])
1553
+ ? mapToObj(metadataAstNode.props, (p) => [p.key, removeIndent(p.value)] as [string, string])
1092
1554
  : undefined
1093
1555
  }
1094
1556