@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.
- package/contrib/likec4.tmLanguage.json +1 -1
- package/package.json +23 -19
- package/src/Rpc.ts +1 -1
- package/src/ast.ts +34 -9
- package/src/{browser/index.ts → browser.ts} +4 -1
- package/src/generated/ast.ts +498 -152
- package/src/generated/grammar.ts +2 -2
- package/src/generated/module.ts +1 -1
- package/src/index.ts +1 -1
- package/src/like-c4.langium +116 -44
- package/src/logger.ts +76 -55
- package/src/lsp/DocumentLinkProvider.ts +1 -1
- package/src/lsp/DocumentSymbolProvider.ts +1 -1
- package/src/lsp/HoverProvider.ts +1 -1
- package/src/lsp/SemanticTokenProvider.ts +54 -26
- package/src/model/model-builder.ts +11 -8
- package/src/model/model-locator.ts +12 -25
- package/src/model/model-parser-where.ts +75 -0
- package/src/model/model-parser.ts +168 -68
- package/src/model-change/ModelChanges.ts +2 -3
- package/src/model-change/changeElementStyle.ts +4 -1
- package/src/model-change/changeViewLayout.ts +8 -8
- package/src/model-change/saveManualLayout.ts +4 -6
- package/src/model-graph/LikeC4ModelGraph.ts +50 -48
- package/src/model-graph/compute-view/__test__/fixture.ts +41 -16
- package/src/model-graph/compute-view/compute.ts +135 -69
- package/src/model-graph/compute-view/predicates.ts +232 -136
- package/src/model-graph/dynamic-view/__test__/fixture.ts +5 -1
- package/src/model-graph/dynamic-view/compute.ts +50 -41
- package/src/model-graph/utils/applyCustomElementProperties.ts +31 -29
- package/src/model-graph/utils/applyCustomRelationProperties.ts +52 -15
- package/src/model-graph/utils/elementExpressionToPredicate.ts +8 -3
- package/src/module.ts +4 -18
- package/src/{node/index.ts → node.ts} +1 -1
- package/src/protocol.ts +2 -2
- package/src/shared/NodeKindProvider.ts +4 -2
- package/src/test/setup.ts +13 -0
- package/src/test/testServices.ts +1 -1
- package/src/validation/dynamic-view-rule.ts +12 -12
- package/src/validation/index.ts +6 -6
- package/src/validation/relation.ts +1 -1
- package/src/validation/view-predicates/{custom-element-expr.ts → element-with.ts} +11 -10
- package/src/validation/view-predicates/expanded-element.ts +2 -10
- package/src/validation/view-predicates/incoming.ts +1 -1
- package/src/validation/view-predicates/index.ts +2 -2
- package/src/validation/view-predicates/outgoing.ts +1 -1
- package/src/validation/view-predicates/{custom-relation-expr.ts → relation-with.ts} +2 -2
- package/src/validation/view.ts +8 -9
- package/src/view-utils/manual-layout.ts +65 -72
- package/src/view-utils/resolve-relative-paths.ts +28 -17
- 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
|
|
47
|
+
#incoming = new MapRelations()
|
|
43
48
|
// Outgoing from an element or its descendants
|
|
44
|
-
#outgoing = new
|
|
49
|
+
#outgoing = new MapRelations()
|
|
45
50
|
// Relationships inside the element descendants
|
|
46
|
-
#internal = new
|
|
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
|
|
72
|
-
return this._incomingTo(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
|
-
|
|
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.
|
|
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.
|
|
136
|
-
const
|
|
137
|
-
const outcoming =
|
|
138
|
-
if (outcoming.
|
|
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
|
|
149
|
+
relations: [...outcoming]
|
|
143
150
|
})
|
|
144
151
|
}
|
|
145
152
|
}
|
|
146
153
|
|
|
147
|
-
if (in_element.
|
|
148
|
-
const
|
|
149
|
-
const incoming =
|
|
150
|
-
if (incoming.
|
|
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
|
|
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.
|
|
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
|
-
|
|
207
|
-
|
|
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).
|
|
244
|
-
this._outgoingFrom(rel.source).
|
|
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).
|
|
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).
|
|
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).
|
|
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
|
|
333
|
-
|
|
334
|
-
|
|
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 {
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
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.
|
|
409
|
-
|
|
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.
|
|
449
|
-
|
|
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
|
}
|