@likec4/language-server 1.6.1 → 1.7.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 (51) hide show
  1. package/contrib/likec4.tmLanguage.json +1 -1
  2. package/package.json +23 -19
  3. package/src/Rpc.ts +1 -1
  4. package/src/ast.ts +34 -9
  5. package/src/{browser/index.ts → browser.ts} +4 -1
  6. package/src/generated/ast.ts +498 -152
  7. package/src/generated/grammar.ts +2 -2
  8. package/src/generated/module.ts +1 -1
  9. package/src/index.ts +1 -1
  10. package/src/like-c4.langium +116 -44
  11. package/src/logger.ts +76 -55
  12. package/src/lsp/DocumentLinkProvider.ts +1 -1
  13. package/src/lsp/DocumentSymbolProvider.ts +1 -1
  14. package/src/lsp/HoverProvider.ts +1 -1
  15. package/src/lsp/SemanticTokenProvider.ts +54 -26
  16. package/src/model/model-builder.ts +11 -8
  17. package/src/model/model-locator.ts +12 -25
  18. package/src/model/model-parser-where.ts +75 -0
  19. package/src/model/model-parser.ts +168 -68
  20. package/src/model-change/ModelChanges.ts +2 -3
  21. package/src/model-change/changeElementStyle.ts +4 -1
  22. package/src/model-change/changeViewLayout.ts +8 -8
  23. package/src/model-change/saveManualLayout.ts +4 -6
  24. package/src/model-graph/LikeC4ModelGraph.ts +50 -48
  25. package/src/model-graph/compute-view/__test__/fixture.ts +41 -16
  26. package/src/model-graph/compute-view/compute.ts +135 -69
  27. package/src/model-graph/compute-view/predicates.ts +232 -136
  28. package/src/model-graph/dynamic-view/__test__/fixture.ts +5 -1
  29. package/src/model-graph/dynamic-view/compute.ts +50 -41
  30. package/src/model-graph/utils/applyCustomElementProperties.ts +31 -29
  31. package/src/model-graph/utils/applyCustomRelationProperties.ts +52 -15
  32. package/src/model-graph/utils/elementExpressionToPredicate.ts +8 -3
  33. package/src/module.ts +4 -18
  34. package/src/{node/index.ts → node.ts} +1 -1
  35. package/src/protocol.ts +2 -2
  36. package/src/shared/NodeKindProvider.ts +4 -2
  37. package/src/test/setup.ts +13 -0
  38. package/src/test/testServices.ts +1 -1
  39. package/src/validation/dynamic-view-rule.ts +12 -12
  40. package/src/validation/index.ts +6 -6
  41. package/src/validation/relation.ts +1 -1
  42. package/src/validation/view-predicates/{custom-element-expr.ts → element-with.ts} +11 -10
  43. package/src/validation/view-predicates/expanded-element.ts +2 -10
  44. package/src/validation/view-predicates/incoming.ts +1 -1
  45. package/src/validation/view-predicates/index.ts +2 -2
  46. package/src/validation/view-predicates/outgoing.ts +1 -1
  47. package/src/validation/view-predicates/{custom-relation-expr.ts → relation-with.ts} +2 -2
  48. package/src/validation/view.ts +8 -9
  49. package/src/view-utils/manual-layout.ts +65 -72
  50. package/src/view-utils/resolve-relative-paths.ts +28 -17
  51. package/src/view-utils/view-hash.ts +33 -0
@@ -11,11 +11,6 @@ import {
11
11
  type Relation,
12
12
  type RelationID
13
13
  } from '@likec4/core'
14
- import { filter, isIncludedIn } from 'remeda'
15
-
16
- function intersection<T>(source: ReadonlyArray<T>, other: ReadonlyArray<T>): Array<T> {
17
- return filter(source, isIncludedIn(other))
18
- }
19
14
 
20
15
  type Params = {
21
16
  elements: Record<Fqn, Element>
@@ -32,6 +27,16 @@ type RelationEdge = {
32
27
  type FqnOrElement = Fqn | Element
33
28
  type FqnsOrElements = Fqn[] | Element[]
34
29
 
30
+ const RelationsSet = Set<Relation>
31
+ const MapRelations = Map<Fqn, Set<Relation>>
32
+
33
+ function intersection<T>(a: Set<T>, b: Set<T>) {
34
+ if (a.size === 0 || b.size === 0) {
35
+ return new Set<T>()
36
+ }
37
+ return new Set([...a].filter(value => b.has(value)))
38
+ }
39
+
35
40
  export class LikeC4ModelGraph {
36
41
  #elements = new Map<Fqn, Element>()
37
42
  #children = new Map<Fqn, Fqn[]>()
@@ -39,11 +44,13 @@ export class LikeC4ModelGraph {
39
44
 
40
45
  #relations = new Map<RelationID, Relation>()
41
46
  // Incoming to an element or its descendants
42
- #incoming = new Map<Fqn, RelationID[]>()
47
+ #incoming = new MapRelations()
43
48
  // Outgoing from an element or its descendants
44
- #outgoing = new Map<Fqn, RelationID[]>()
49
+ #outgoing = new MapRelations()
45
50
  // Relationships inside the element descendants
46
- #internal = new Map<Fqn, RelationID[]>()
51
+ #internal = new MapRelations()
52
+
53
+ #cacheAscendingSiblings = new Map<Fqn, Element[]>()
47
54
 
48
55
  constructor({ elements, relations }: Params) {
49
56
  for (const el of Object.values(elements)) {
@@ -68,16 +75,8 @@ export class LikeC4ModelGraph {
68
75
  return el
69
76
  }
70
77
 
71
- public incoming(id: Fqn) {
72
- return this._incomingTo(id).flatMap(id => this.#relations.get(id) ?? [])
73
- }
74
-
75
- public outgoing(id: Fqn) {
76
- return this._outgoingFrom(id).flatMap(id => this.#relations.get(id) ?? [])
77
- }
78
-
79
- public internal(id: Fqn) {
80
- return this._internalOf(id).flatMap(id => this.#relations.get(id) ?? [])
78
+ public connectedRelations(id: Fqn) {
79
+ return [...this._incomingTo(id), ...this._outgoingFrom(id), ...this._internalOf(id)]
81
80
  }
82
81
 
83
82
  public children(id: Fqn) {
@@ -108,7 +107,15 @@ export class LikeC4ModelGraph {
108
107
  */
109
108
  public ascendingSiblings(element: Fqn | Element) {
110
109
  const id = isString(element) ? element : element.id
111
- return [...this.siblings(id), ...this.ancestors(id).flatMap(a => this.siblings(a.id))]
110
+ let siblings = this.#cacheAscendingSiblings.get(id)
111
+ if (!siblings) {
112
+ siblings = [
113
+ ...this.siblings(id),
114
+ ...this.ancestors(id).flatMap(a => this.siblings(a.id))
115
+ ]
116
+ this.#cacheAscendingSiblings.set(id, siblings)
117
+ }
118
+ return siblings.slice()
112
119
  }
113
120
 
114
121
  /**
@@ -121,7 +128,7 @@ export class LikeC4ModelGraph {
121
128
  const element = isString(_element) ? this.element(_element) : _element
122
129
  const in_element = this._incomingTo(element.id)
123
130
  const element_out = this._outgoingFrom(element.id)
124
- if (in_element.length === 0 && element_out.length === 0) {
131
+ if (in_element.size === 0 && element_out.size === 0) {
125
132
  return []
126
133
  }
127
134
 
@@ -132,26 +139,26 @@ export class LikeC4ModelGraph {
132
139
  continue
133
140
  }
134
141
 
135
- if (element_out.length > 0) {
136
- const in_other = this._incomingTo(other.id)
137
- const outcoming = intersection(element_out, in_other)
138
- if (outcoming.length > 0) {
142
+ if (element_out.size > 0) {
143
+ const outcoming = intersection(this._incomingTo(other.id), element_out)
144
+ // const outcoming = filter(in_other, isOutgoing)
145
+ if (outcoming.size > 0) {
139
146
  result.push({
140
147
  source: element,
141
148
  target: other,
142
- relations: outcoming.flatMap(id => this.#relations.get(id) ?? [])
149
+ relations: [...outcoming]
143
150
  })
144
151
  }
145
152
  }
146
153
 
147
- if (in_element.length > 0) {
148
- const other_out = this._outgoingFrom(other.id)
149
- const incoming = intersection(other_out, in_element)
150
- if (incoming.length > 0) {
154
+ if (in_element.size > 0) {
155
+ const incoming = intersection(this._outgoingFrom(other.id), in_element)
156
+ // const incoming = filter(other_out, isIncoming)
157
+ if (incoming.size > 0) {
151
158
  result.push({
152
159
  source: other,
153
160
  target: element,
154
- relations: incoming.flatMap(id => this.#relations.get(id) ?? [])
161
+ relations: [...incoming]
155
162
  })
156
163
  }
157
164
  }
@@ -193,9 +200,10 @@ export class LikeC4ModelGraph {
193
200
  for (const _source of sources) {
194
201
  const source = isString(_source) ? this.element(_source) : _source
195
202
  const outcoming = this._outgoingFrom(source.id)
196
- if (outcoming.length === 0) {
203
+ if (outcoming.size === 0) {
197
204
  continue
198
205
  }
206
+ // const isSameAsOut = isIncludedIn(outcoming)
199
207
 
200
208
  for (const _target of targets) {
201
209
  const target = isString(_target) ? this.element(_target) : _target
@@ -203,18 +211,12 @@ export class LikeC4ModelGraph {
203
211
  continue
204
212
  }
205
213
  const incoming = this._incomingTo(target.id)
206
- if (incoming.length === 0) {
207
- continue
208
- }
209
-
210
- const relations = intersection(outcoming, incoming).flatMap(
211
- id => this.#relations.get(id) ?? []
212
- )
213
- if (relations.length > 0) {
214
+ const relations = intersection(outcoming, incoming)
215
+ if (relations.size > 0) {
214
216
  result.push({
215
217
  source,
216
218
  target,
217
- relations
219
+ relations: [...relations]
218
220
  })
219
221
  }
220
222
  }
@@ -240,14 +242,14 @@ export class LikeC4ModelGraph {
240
242
  throw new InvalidModelError(`Relation ${rel.id} already exists`)
241
243
  }
242
244
  this.#relations.set(rel.id, rel)
243
- this._incomingTo(rel.target).push(rel.id)
244
- this._outgoingFrom(rel.source).push(rel.id)
245
+ this._incomingTo(rel.target).add(rel)
246
+ this._outgoingFrom(rel.source).add(rel)
245
247
 
246
248
  const relParent = commonAncestor(rel.source, rel.target)
247
249
  // Process internal relationships
248
250
  if (relParent) {
249
251
  for (const ancestor of [relParent, ...ancestorsFqn(relParent)]) {
250
- this._internalOf(ancestor).push(rel.id)
252
+ this._internalOf(ancestor).add(rel)
251
253
  }
252
254
  }
253
255
  // Process source hierarchy
@@ -255,14 +257,14 @@ export class LikeC4ModelGraph {
255
257
  if (sourceAncestor === relParent) {
256
258
  break
257
259
  }
258
- this._outgoingFrom(sourceAncestor).push(rel.id)
260
+ this._outgoingFrom(sourceAncestor).add(rel)
259
261
  }
260
262
  // Process target hierarchy
261
263
  for (const targetAncestor of ancestorsFqn(rel.target)) {
262
264
  if (targetAncestor === relParent) {
263
265
  break
264
266
  }
265
- this._incomingTo(targetAncestor).push(rel.id)
267
+ this._incomingTo(targetAncestor).add(rel)
266
268
  }
267
269
  }
268
270
 
@@ -278,7 +280,7 @@ export class LikeC4ModelGraph {
278
280
  private _incomingTo(id: Fqn) {
279
281
  let incoming = this.#incoming.get(id)
280
282
  if (!incoming) {
281
- incoming = []
283
+ incoming = new RelationsSet()
282
284
  this.#incoming.set(id, incoming)
283
285
  }
284
286
  return incoming
@@ -287,7 +289,7 @@ export class LikeC4ModelGraph {
287
289
  private _outgoingFrom(id: Fqn) {
288
290
  let outgoing = this.#outgoing.get(id)
289
291
  if (!outgoing) {
290
- outgoing = []
292
+ outgoing = new RelationsSet()
291
293
  this.#outgoing.set(id, outgoing)
292
294
  }
293
295
  return outgoing
@@ -296,7 +298,7 @@ export class LikeC4ModelGraph {
296
298
  private _internalOf(id: Fqn) {
297
299
  let internal = this.#internal.get(id)
298
300
  if (!internal) {
299
- internal = []
301
+ internal = new RelationsSet()
300
302
  this.#internal.set(id, internal)
301
303
  }
302
304
  return internal
@@ -1,23 +1,27 @@
1
1
  import type {
2
2
  BorderStyle,
3
3
  ComputedView,
4
+ CustomElementExpr as C4CustomElementExpr,
4
5
  CustomRelationExpr as C4CustomRelationExpr,
5
6
  Element,
6
7
  ElementExpression as C4ElementExpression,
7
8
  ElementKind,
8
9
  ElementShape,
10
+ ElementWhereExpr,
9
11
  Expression as C4Expression,
10
12
  Fqn,
11
13
  Relation,
12
14
  RelationID,
13
15
  RelationshipArrowType,
14
16
  RelationshipLineType,
17
+ RelationWhereExpr,
15
18
  Tag,
16
19
  ThemeColor,
17
20
  ViewID,
18
21
  ViewRule,
19
22
  ViewRulePredicate,
20
- ViewRuleStyle
23
+ ViewRuleStyle,
24
+ WhereOperator
21
25
  } from '@likec4/core'
22
26
  import { indexBy, isString, map, prop } from 'remeda'
23
27
  import { LikeC4ModelGraph } from '../../LikeC4ModelGraph'
@@ -139,7 +143,8 @@ export const fakeElements = {
139
143
  'cloud': el({
140
144
  id: 'cloud',
141
145
  kind: 'system',
142
- title: 'cloud'
146
+ title: 'cloud',
147
+ tags: ['next' as Tag, 'old' as Tag]
143
148
  }),
144
149
  'cloud.backend': el({
145
150
  id: 'cloud.backend',
@@ -177,18 +182,21 @@ export const fakeElements = {
177
182
  'cloud.frontend.dashboard': el({
178
183
  id: 'cloud.frontend.dashboard',
179
184
  kind: 'component',
180
- title: 'dashboard'
185
+ title: 'dashboard',
186
+ tags: ['next' as Tag]
181
187
  }),
182
188
  'amazon': el({
183
189
  id: 'amazon',
184
190
  kind: 'system',
185
- title: 'amazon'
191
+ title: 'amazon',
192
+ tags: ['aws' as Tag]
186
193
  }),
187
194
  'amazon.s3': el({
188
195
  id: 'amazon.s3',
189
196
  kind: 'component',
190
197
  title: 's3',
191
- shape: 'storage'
198
+ shape: 'storage',
199
+ tags: ['aws' as Tag]
192
200
  })
193
201
  } satisfies Record<string, Element>
194
202
 
@@ -329,9 +337,18 @@ type OutgoingExpr = `${ElementRefExpr} ->`
329
337
  type RelationKeyword = '->' | '<->'
330
338
  type RelationExpr = `${ElementRefExpr} ${RelationKeyword} ${ElementRefExpr}`
331
339
 
332
- type CustomExpr = {
333
- custom: {
334
- element: FakeElementIds
340
+ export type Expression =
341
+ | ElementRefExpr
342
+ | InOutExpr
343
+ | IncomingExpr
344
+ | OutgoingExpr
345
+ | RelationExpr
346
+ | ElementWhereExpr
347
+ | RelationWhereExpr
348
+
349
+ export function $custom(
350
+ expr: ElementRefExpr,
351
+ props: {
335
352
  title?: string
336
353
  description?: string
337
354
  technology?: string
@@ -342,16 +359,15 @@ type CustomExpr = {
342
359
  opacity?: number
343
360
  navigateTo?: string
344
361
  }
362
+ ): C4CustomElementExpr {
363
+ return {
364
+ custom: {
365
+ expr: $expr(expr) as any,
366
+ ...props as any
367
+ }
368
+ }
345
369
  }
346
370
 
347
- export type Expression =
348
- | ElementRefExpr
349
- | InOutExpr
350
- | IncomingExpr
351
- | OutgoingExpr
352
- | RelationExpr
353
- | CustomExpr
354
-
355
371
  export function $customRelation(
356
372
  relation: RelationExpr,
357
373
  props: Omit<C4CustomRelationExpr['customRelation'], 'relation'>
@@ -364,6 +380,15 @@ export function $customRelation(
364
380
  }
365
381
  }
366
382
 
383
+ export function $where(expr: Expression | C4Expression, operator: WhereOperator): ElementWhereExpr | RelationWhereExpr {
384
+ return {
385
+ where: {
386
+ expr: $expr(expr) as any,
387
+ condition: operator
388
+ }
389
+ }
390
+ }
391
+
367
392
  export function $expr(expr: Expression | C4Expression): C4Expression {
368
393
  if (!isString(expr)) {
369
394
  return expr as C4Expression
@@ -3,8 +3,13 @@ import type {
3
3
  ComputedElementView,
4
4
  EdgeId,
5
5
  Element,
6
+ ElementPredicateExpression,
6
7
  ElementView,
7
8
  Relation,
9
+ RelationPredicateExpression,
10
+ RelationshipArrowType,
11
+ RelationshipLineType,
12
+ ThemeColor,
8
13
  ViewRulePredicate
9
14
  } from '@likec4/core'
10
15
  import {
@@ -18,9 +23,11 @@ import {
18
23
  isViewRuleAutoLayout,
19
24
  isViewRulePredicate,
20
25
  nonexhaustive,
21
- parentFqn
26
+ parentFqn,
27
+ whereOperatorAsPredicate
22
28
  } from '@likec4/core'
23
29
  import { first, flatMap, hasAtLeast, isTruthy, unique } from 'remeda'
30
+ import { calcViewLayoutHash } from '../../view-utils/view-hash'
24
31
  import type { LikeC4ModelGraph } from '../LikeC4ModelGraph'
25
32
  import { applyCustomElementProperties } from '../utils/applyCustomElementProperties'
26
33
  import { applyCustomRelationProperties } from '../utils/applyCustomRelationProperties'
@@ -28,15 +35,15 @@ import { applyViewRuleStyles } from '../utils/applyViewRuleStyles'
28
35
  import { buildComputeNodes } from '../utils/buildComputeNodes'
29
36
  import { sortNodes } from '../utils/sortNodes'
30
37
  import {
38
+ type ElementPredicateFn,
31
39
  excludeElementKindOrTag,
32
40
  excludeElementRef,
41
+ excludeExpandedElementExpr,
33
42
  excludeIncomingExpr,
34
43
  excludeInOutExpr,
35
44
  excludeOutgoingExpr,
36
45
  excludeRelationExpr,
37
46
  excludeWildcardRef,
38
- includeCustomElement,
39
- includeCustomRelation,
40
47
  includeElementKindOrTag,
41
48
  includeElementRef,
42
49
  includeExpandedElementExpr,
@@ -44,7 +51,8 @@ import {
44
51
  includeInOutExpr,
45
52
  includeOutgoingExpr,
46
53
  includeRelationExpr,
47
- includeWildcardRef
54
+ includeWildcardRef,
55
+ type RelationPredicateFn
48
56
  } from './predicates'
49
57
 
50
58
  // eslint-disable-next-line @typescript-eslint/no-namespace
@@ -90,7 +98,11 @@ export class ComputeCtx {
90
98
  protected compute(): ComputedElementView {
91
99
  // reset ctx
92
100
  this.reset()
93
- const { rules, ...view } = this.view
101
+ const {
102
+ docUri: _docUri, // exclude docUri
103
+ rules,
104
+ ...view
105
+ } = this.view
94
106
 
95
107
  const viewPredicates = rules.filter(isViewRulePredicate)
96
108
  if (this.root && viewPredicates.length == 0) {
@@ -102,7 +114,10 @@ export class ComputeCtx {
102
114
  const elements = [...this.includedElements]
103
115
  const nodesMap = buildComputeNodes(elements)
104
116
 
105
- const edges = this.computedEdges.reduce((acc, edge) => {
117
+ const edgesMap = new Map<EdgeId, ComputedEdge>()
118
+ const edges = this.computeEdges()
119
+ for (const edge of edges) {
120
+ edgesMap.set(edge.id, edge)
106
121
  const source = nodesMap.get(edge.source)
107
122
  const target = nodesMap.get(edge.target)
108
123
  invariant(source, `Source node ${edge.source} not found`)
@@ -126,9 +141,7 @@ export class ComputeCtx {
126
141
  }
127
142
  nodesMap.get(targetAncestor)?.inEdges.push(edge.id)
128
143
  }
129
- acc.push(edge)
130
- return acc
131
- }, [] as ComputedEdge[])
144
+ }
132
145
 
133
146
  // nodesMap sorted hierarchically,
134
147
  // but we need to keep the initial sort
@@ -146,27 +159,25 @@ export class ComputeCtx {
146
159
  )
147
160
  )
148
161
 
149
- const edgesMap = new Map<EdgeId, ComputedEdge>(edges.map(e => [e.id, e]))
150
-
151
162
  const sortedEdges = new Set([
152
163
  ...nodes.flatMap(n => n.children.length === 0 ? n.outEdges.flatMap(id => edgesMap.get(id) ?? []) : []),
153
164
  ...edges
154
165
  ])
155
166
 
156
167
  const autoLayoutRule = this.view.rules.findLast(isViewRuleAutoLayout)
157
- return {
168
+ return calcViewLayoutHash({
158
169
  ...view,
159
170
  autoLayout: autoLayoutRule?.autoLayout ?? 'TB',
160
171
  nodes,
161
172
  edges: applyCustomRelationProperties(rules, nodes, sortedEdges)
162
- }
173
+ })
163
174
  }
164
175
 
165
176
  protected get root() {
166
177
  return isScopedElementView(this.view) ? this.view.viewOf : null
167
178
  }
168
179
 
169
- protected get computedEdges(): ComputedEdge[] {
180
+ protected computeEdges(): ComputedEdge[] {
170
181
  return this.ctxEdges.map((e): ComputedEdge => {
171
182
  invariant(hasAtLeast(e.relations, 1), 'Edge must have at least one relation')
172
183
  const relations = e.relations.toSorted(compareRelations)
@@ -182,7 +193,16 @@ export class ComputeCtx {
182
193
  relations: relations.map(r => r.id)
183
194
  }
184
195
 
185
- let relation
196
+ let relation: Pick<Relation, 'title' | 'description' | 'technology' | 'color' | 'line' | 'head' | 'tail'> | {
197
+ // TODO refactor with type-fest
198
+ title: string
199
+ description?: string | undefined
200
+ technology?: string | undefined
201
+ color?: ThemeColor | undefined
202
+ line?: RelationshipLineType | undefined
203
+ head?: RelationshipArrowType | undefined
204
+ tail?: RelationshipArrowType | undefined
205
+ } | undefined
186
206
  if (relations.length === 1) {
187
207
  relation = relations[0]
188
208
  } else {
@@ -193,7 +213,7 @@ export class ComputeCtx {
193
213
  // This edge represents mutliple relations
194
214
  // We use label if only it is the same for all relations
195
215
  if (!relation) {
196
- const shared = relations.reduce((acc, r) => {
216
+ relation = relations.reduce((acc, r) => {
197
217
  if (r.color && acc.color !== r.color) {
198
218
  acc.color = undefined
199
219
  }
@@ -206,30 +226,42 @@ export class ComputeCtx {
206
226
  if (r.line && acc.line !== r.line) {
207
227
  acc.line = undefined
208
228
  }
229
+ if (r.description && acc.description !== r.description) {
230
+ acc.description = undefined
231
+ }
232
+ if (r.technology && acc.technology !== r.technology) {
233
+ acc.technology = undefined
234
+ }
209
235
  if (isTruthy(r.title) && acc.title !== r.title) {
210
236
  acc.title = '[...]'
211
237
  }
212
238
  return acc
213
239
  }, {
214
- title: first(flatMap(relations, r => isTruthy(r.title) ? r.title : [])),
240
+ title: first(flatMap(relations, r => isTruthy(r.title) ? r.title : [])) ?? '[...]',
241
+ description: first(flatMap(relations, r => isTruthy(r.description) ? r.description : [])),
242
+ technology: first(flatMap(relations, r => isTruthy(r.technology) ? r.technology : [])),
215
243
  head: first(flatMap(relations, r => isTruthy(r.head) ? r.head : [])),
216
244
  tail: first(flatMap(relations, r => isTruthy(r.tail) ? r.tail : [])),
217
245
  color: first(flatMap(relations, r => isTruthy(r.color) ? r.color : [])),
218
246
  line: first(flatMap(relations, r => isTruthy(r.line) ? r.line : []))
219
247
  })
220
- return Object.assign(
221
- edge,
222
- isTruthy(shared.title) && { label: shared.title },
223
- shared.color && { color: shared.color },
224
- shared.line && { line: shared.line },
225
- shared.head && { head: shared.head },
226
- shared.tail && { tail: shared.tail }
227
- )
248
+ // return Object.assign(
249
+ // edge,
250
+ // isTruthy(shared.title) && { label: shared.title },
251
+ // isTruthy(shared.description) && { description: shared.description },
252
+ // isTruthy(shared.technology) && { technology: shared.technology },
253
+ // shared.color && { color: shared.color },
254
+ // shared.line && { line: shared.line },
255
+ // shared.head && { head: shared.head },
256
+ // shared.tail && { tail: shared.tail }
257
+ // )
228
258
  }
229
259
 
230
260
  return Object.assign(
231
261
  edge,
232
262
  isTruthy(relation.title) && { label: relation.title },
263
+ isTruthy(relation.description) && { description: relation.description },
264
+ isTruthy(relation.technology) && { description: relation.technology },
233
265
  relation.color && { color: relation.color },
234
266
  relation.line && { line: relation.line },
235
267
  relation.head && { head: relation.head },
@@ -405,52 +437,12 @@ export class ComputeCtx {
405
437
  const isInclude = 'include' in rule
406
438
  const exprs = rule.include ?? rule.exclude
407
439
  for (const expr of exprs) {
408
- if (Expr.isCustomElement(expr)) {
409
- if (isInclude) {
410
- includeCustomElement.call(this, expr)
411
- }
412
- continue
413
- }
414
- if (Expr.isCustomRelationExpr(expr)) {
415
- if (isInclude) {
416
- includeCustomRelation.call(this, expr)
417
- }
418
- continue
419
- }
420
- if (Expr.isExpandedElementExpr(expr)) {
421
- if (isInclude) {
422
- includeExpandedElementExpr.call(this, expr)
423
- }
424
- continue
425
- }
426
- if (Expr.isElementKindExpr(expr) || Expr.isElementTagExpr(expr)) {
427
- isInclude
428
- ? includeElementKindOrTag.call(this, expr)
429
- : excludeElementKindOrTag.call(this, expr)
430
- continue
431
- }
432
- if (Expr.isElementRef(expr)) {
433
- isInclude ? includeElementRef.call(this, expr) : excludeElementRef.call(this, expr)
434
- continue
435
- }
436
- if (Expr.isWildcard(expr)) {
437
- isInclude ? includeWildcardRef.call(this, expr) : excludeWildcardRef.call(this, expr)
438
- continue
439
- }
440
- if (Expr.isIncoming(expr)) {
441
- isInclude ? includeIncomingExpr.call(this, expr) : excludeIncomingExpr.call(this, expr)
442
- continue
443
- }
444
- if (Expr.isOutgoing(expr)) {
445
- isInclude ? includeOutgoingExpr.call(this, expr) : excludeOutgoingExpr.call(this, expr)
440
+ if (Expr.isElementPredicateExpr(expr)) {
441
+ this.processElementPredicate(expr, isInclude)
446
442
  continue
447
443
  }
448
- if (Expr.isInOut(expr)) {
449
- isInclude ? includeInOutExpr.call(this, expr) : excludeInOutExpr.call(this, expr)
450
- continue
451
- }
452
- if (Expr.isRelation(expr)) {
453
- isInclude ? includeRelationExpr.call(this, expr) : excludeRelationExpr.call(this, expr)
444
+ if (Expr.isRelationPredicateExpr(expr)) {
445
+ this.processRelationPredicate(expr, isInclude)
454
446
  continue
455
447
  }
456
448
  nonexhaustive(expr)
@@ -458,4 +450,78 @@ export class ComputeCtx {
458
450
  }
459
451
  return this
460
452
  }
453
+
454
+ protected processElementPredicate(
455
+ expr: ElementPredicateExpression,
456
+ isInclude: boolean,
457
+ where?: ElementPredicateFn
458
+ ): this {
459
+ if (Expr.isCustomElement(expr)) {
460
+ if (isInclude) {
461
+ this.processElementPredicate(expr.custom.expr, isInclude)
462
+ }
463
+ return this
464
+ }
465
+ if (Expr.isElementWhere(expr)) {
466
+ const where = whereOperatorAsPredicate(expr.where.condition)
467
+ this.processElementPredicate(expr.where.expr, isInclude, where)
468
+ return this
469
+ }
470
+ if (Expr.isExpandedElementExpr(expr)) {
471
+ isInclude
472
+ ? includeExpandedElementExpr.call(this, expr, where)
473
+ : excludeExpandedElementExpr.call(this, expr, where)
474
+ return this
475
+ }
476
+ if (Expr.isElementKindExpr(expr) || Expr.isElementTagExpr(expr)) {
477
+ isInclude
478
+ ? includeElementKindOrTag.call(this, expr, where)
479
+ : excludeElementKindOrTag.call(this, expr, where)
480
+ return this
481
+ }
482
+ if (Expr.isElementRef(expr)) {
483
+ isInclude ? includeElementRef.call(this, expr, where) : excludeElementRef.call(this, expr, where)
484
+ return this
485
+ }
486
+ if (Expr.isWildcard(expr)) {
487
+ isInclude ? includeWildcardRef.call(this, expr, where) : excludeWildcardRef.call(this, expr, where)
488
+ return this
489
+ }
490
+ nonexhaustive(expr)
491
+ }
492
+
493
+ protected processRelationPredicate(
494
+ expr: RelationPredicateExpression,
495
+ isInclude: boolean,
496
+ where?: RelationPredicateFn
497
+ ): this {
498
+ if (Expr.isCustomRelationExpr(expr)) {
499
+ if (isInclude) {
500
+ this.processRelationPredicate(expr.customRelation.relation, isInclude)
501
+ }
502
+ return this
503
+ }
504
+ if (Expr.isRelationWhere(expr)) {
505
+ const where = whereOperatorAsPredicate(expr.where.condition)
506
+ this.processRelationPredicate(expr.where.expr, isInclude, where)
507
+ return this
508
+ }
509
+ if (Expr.isIncoming(expr)) {
510
+ isInclude ? includeIncomingExpr.call(this, expr, where) : excludeIncomingExpr.call(this, expr, where)
511
+ return this
512
+ }
513
+ if (Expr.isOutgoing(expr)) {
514
+ isInclude ? includeOutgoingExpr.call(this, expr, where) : excludeOutgoingExpr.call(this, expr, where)
515
+ return this
516
+ }
517
+ if (Expr.isInOut(expr)) {
518
+ isInclude ? includeInOutExpr.call(this, expr, where) : excludeInOutExpr.call(this, expr, where)
519
+ return this
520
+ }
521
+ if (Expr.isRelation(expr)) {
522
+ isInclude ? includeRelationExpr.call(this, expr, where) : excludeRelationExpr.call(this, expr, where)
523
+ return this
524
+ }
525
+ nonexhaustive(expr)
526
+ }
461
527
  }