@likec4/language-server 1.17.0 → 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.
- package/contrib/likec4.tmLanguage.json +1 -1
- package/dist/browser.cjs +1 -1
- package/dist/browser.d.cts +2 -2
- package/dist/browser.d.mts +2 -2
- package/dist/browser.d.ts +2 -2
- package/dist/browser.mjs +2 -2
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.mjs +2 -2
- package/dist/protocol.d.cts +8 -5
- package/dist/protocol.d.mts +8 -5
- package/dist/protocol.d.ts +8 -5
- package/dist/shared/{language-server.DZRuJVSg.cjs → language-server.CO_nmHiL.cjs} +5605 -4215
- package/dist/shared/{language-server.DJo88TnT.d.cts → language-server.Da6ey08o.d.cts} +391 -110
- package/dist/shared/{language-server.PEjk7U9s.d.ts → language-server.De7S3e5Z.d.ts} +391 -110
- package/dist/shared/{language-server.BgDKnNok.d.mts → language-server.Dj4iDjtB.d.mts} +391 -110
- package/dist/shared/{language-server.B8qSDsWW.mjs → language-server.oO_9JoAG.mjs} +5594 -4215
- package/package.json +17 -31
- package/src/Rpc.ts +6 -3
- package/src/ast.ts +124 -71
- package/src/formatting/LikeC4Formatter.ts +9 -4
- package/src/generated/ast.ts +656 -40
- package/src/generated/grammar.ts +2 -2
- package/src/generated/module.ts +3 -2
- package/src/index.ts +1 -0
- package/src/like-c4.langium +170 -22
- package/src/logger.ts +7 -2
- package/src/lsp/CodeLensProvider.ts +0 -1
- package/src/lsp/CompletionProvider.ts +17 -2
- package/src/lsp/DocumentSymbolProvider.ts +5 -2
- package/src/lsp/HoverProvider.ts +34 -2
- package/src/lsp/SemanticTokenProvider.ts +58 -32
- package/src/model/deployments-index.ts +218 -0
- package/src/model/fqn-computation.ts +1 -1
- package/src/model/fqn-index.ts +0 -1
- package/src/model/index.ts +1 -0
- package/src/model/model-builder.ts +172 -37
- package/src/model/model-locator.ts +36 -7
- package/src/model/model-parser.ts +554 -92
- package/src/model-change/changeViewLayout.ts +2 -2
- package/src/module.ts +10 -4
- package/src/protocol.ts +10 -6
- package/src/references/index.ts +1 -0
- package/src/references/name-provider.ts +37 -0
- package/src/references/scope-computation.ts +130 -21
- package/src/references/scope-provider.ts +63 -36
- package/src/shared/NodeKindProvider.ts +15 -3
- package/src/utils/deploymentRef.ts +31 -0
- package/src/{elementRef.ts → utils/elementRef.ts} +1 -1
- package/src/utils/stringHash.ts +2 -2
- package/src/validation/_shared.ts +7 -5
- package/src/validation/deployment-checks.ts +144 -0
- package/src/validation/dynamic-view-step.ts +1 -1
- package/src/validation/index.ts +7 -0
- package/src/validation/relation.ts +1 -1
- package/src/validation/view-predicates/deployments.ts +56 -0
- package/src/validation/view-predicates/index.ts +1 -0
- package/src/view-utils/assignNavigateTo.ts +6 -5
- package/src/view-utils/index.ts +0 -1
- package/dist/model-graph/index.cjs +0 -10
- package/dist/model-graph/index.d.cts +0 -81
- package/dist/model-graph/index.d.mts +0 -81
- package/dist/model-graph/index.d.ts +0 -81
- package/dist/model-graph/index.mjs +0 -1
- package/dist/shared/language-server.BGGRJRnr.d.mts +0 -1338
- package/dist/shared/language-server.BXFhlTPo.mjs +0 -1953
- package/dist/shared/language-server.Bmpq16Gw.d.ts +0 -1338
- package/dist/shared/language-server.C1ZfM22X.d.cts +0 -1338
- package/dist/shared/language-server.N8HLDQqz.cjs +0 -1967
- package/src/model-graph/LikeC4ModelGraph.ts +0 -338
- package/src/model-graph/compute-view/__test__/fixture.ts +0 -630
- package/src/model-graph/compute-view/compute.ts +0 -788
- package/src/model-graph/compute-view/index.ts +0 -33
- package/src/model-graph/compute-view/predicates.ts +0 -509
- package/src/model-graph/dynamic-view/__test__/fixture.ts +0 -61
- package/src/model-graph/dynamic-view/compute.ts +0 -281
- package/src/model-graph/dynamic-view/index.ts +0 -29
- package/src/model-graph/index.ts +0 -3
- package/src/model-graph/utils/applyCustomElementProperties.ts +0 -65
- package/src/model-graph/utils/applyCustomRelationProperties.ts +0 -41
- package/src/model-graph/utils/applyViewRuleStyles.ts +0 -49
- package/src/model-graph/utils/buildComputeNodes.ts +0 -113
- package/src/model-graph/utils/buildElementNotations.ts +0 -63
- package/src/model-graph/utils/elementExpressionToPredicate.ts +0 -39
- package/src/model-graph/utils/relationExpressionToPredicates.ts +0 -43
- package/src/model-graph/utils/sortNodes.ts +0 -105
- package/src/model-graph/utils/uniqueTags.test.ts +0 -42
- package/src/model-graph/utils/uniqueTags.ts +0 -19
- package/src/utils/graphlib.ts +0 -9
- package/src/view-utils/resolve-extended-views.ts +0 -66
- package/src/view-utils/resolve-global-rules.ts +0 -88
- package/src/view-utils/view-hash.ts +0 -27
|
@@ -1,788 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
type Color,
|
|
3
|
-
type ComputedEdge,
|
|
4
|
-
type ComputedElementView,
|
|
5
|
-
type ComputedNode,
|
|
6
|
-
type EdgeId,
|
|
7
|
-
type Element,
|
|
8
|
-
ElementKind,
|
|
9
|
-
type ElementPredicateExpression,
|
|
10
|
-
type ElementView,
|
|
11
|
-
type Fqn,
|
|
12
|
-
type NodeId,
|
|
13
|
-
type NonEmptyArray,
|
|
14
|
-
type Relation,
|
|
15
|
-
type RelationPredicateExpression,
|
|
16
|
-
type RelationshipArrowType,
|
|
17
|
-
type RelationshipKind,
|
|
18
|
-
type RelationshipLineType,
|
|
19
|
-
type Tag,
|
|
20
|
-
type ViewID,
|
|
21
|
-
type ViewRuleGroup,
|
|
22
|
-
type ViewRulePredicate
|
|
23
|
-
} from '@likec4/core'
|
|
24
|
-
import {
|
|
25
|
-
commonAncestor,
|
|
26
|
-
commonHead,
|
|
27
|
-
compareRelations,
|
|
28
|
-
Expr,
|
|
29
|
-
invariant,
|
|
30
|
-
isAncestor,
|
|
31
|
-
isScopedElementView,
|
|
32
|
-
isViewRuleAutoLayout,
|
|
33
|
-
isViewRuleGroup,
|
|
34
|
-
isViewRulePredicate,
|
|
35
|
-
nonexhaustive,
|
|
36
|
-
nonNullable,
|
|
37
|
-
whereOperatorAsPredicate
|
|
38
|
-
} from '@likec4/core'
|
|
39
|
-
import {
|
|
40
|
-
anyPass,
|
|
41
|
-
concat,
|
|
42
|
-
difference,
|
|
43
|
-
filter,
|
|
44
|
-
first,
|
|
45
|
-
hasAtLeast,
|
|
46
|
-
isNonNull,
|
|
47
|
-
isTruthy,
|
|
48
|
-
last,
|
|
49
|
-
map,
|
|
50
|
-
omit,
|
|
51
|
-
only,
|
|
52
|
-
pipe,
|
|
53
|
-
reduce,
|
|
54
|
-
reverse,
|
|
55
|
-
sort,
|
|
56
|
-
unique
|
|
57
|
-
} from 'remeda'
|
|
58
|
-
import { resolveGlobalRulesInElementView } from '../../view-utils/resolve-global-rules'
|
|
59
|
-
import { calcViewLayoutHash } from '../../view-utils/view-hash'
|
|
60
|
-
import type { LikeC4ModelGraph } from '../LikeC4ModelGraph'
|
|
61
|
-
import { applyCustomElementProperties } from '../utils/applyCustomElementProperties'
|
|
62
|
-
import { applyCustomRelationProperties } from '../utils/applyCustomRelationProperties'
|
|
63
|
-
import { applyViewRuleStyles } from '../utils/applyViewRuleStyles'
|
|
64
|
-
import { buildComputeNodes } from '../utils/buildComputeNodes'
|
|
65
|
-
import { buildElementNotations } from '../utils/buildElementNotations'
|
|
66
|
-
import { sortNodes } from '../utils/sortNodes'
|
|
67
|
-
import { uniqueTags } from '../utils/uniqueTags'
|
|
68
|
-
import {
|
|
69
|
-
type ElementPredicateFn,
|
|
70
|
-
excludeElementKindOrTag,
|
|
71
|
-
excludeElementRef,
|
|
72
|
-
excludeExpandedElementExpr,
|
|
73
|
-
excludeIncomingExpr,
|
|
74
|
-
excludeInOutExpr,
|
|
75
|
-
excludeOutgoingExpr,
|
|
76
|
-
excludeRelationExpr,
|
|
77
|
-
excludeWildcardRef,
|
|
78
|
-
includeElementKindOrTag,
|
|
79
|
-
includeElementRef,
|
|
80
|
-
includeExpandedElementExpr,
|
|
81
|
-
includeIncomingExpr,
|
|
82
|
-
includeInOutExpr,
|
|
83
|
-
includeOutgoingExpr,
|
|
84
|
-
includeRelationExpr,
|
|
85
|
-
includeWildcardRef,
|
|
86
|
-
type RelationPredicateFn
|
|
87
|
-
} from './predicates'
|
|
88
|
-
|
|
89
|
-
// eslint-disable-next-line @typescript-eslint/no-namespace
|
|
90
|
-
export namespace ComputeCtx {
|
|
91
|
-
// Intermediate ComputedEdge
|
|
92
|
-
export interface Edge {
|
|
93
|
-
source: Element
|
|
94
|
-
target: Element
|
|
95
|
-
relations: Relation[]
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
function compareEdges(a: ComputeCtx.Edge, b: ComputeCtx.Edge) {
|
|
100
|
-
return compareRelations(
|
|
101
|
-
{ source: a.source.id, target: a.target.id },
|
|
102
|
-
{ source: b.source.id, target: b.target.id }
|
|
103
|
-
)
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// If there is only one relation and it has same source and target as edge
|
|
107
|
-
function isDirectEdge({ relations: [rel, ...tail], source, target }: ComputeCtx.Edge) {
|
|
108
|
-
if (rel && tail.length === 0) {
|
|
109
|
-
return rel.source === source.id && rel.target === target.id
|
|
110
|
-
}
|
|
111
|
-
return false
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
export class NodesGroup {
|
|
115
|
-
static readonly kind = ElementKind.Group
|
|
116
|
-
|
|
117
|
-
static root() {
|
|
118
|
-
return new NodesGroup('@root' as NodeId, { title: null, groupRules: [] })
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
static is(node: ComputedNode) {
|
|
122
|
-
return node.kind === NodesGroup.kind
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
readonly explicits = new Set<Element>()
|
|
126
|
-
readonly implicits = new Set<Element>()
|
|
127
|
-
|
|
128
|
-
constructor(
|
|
129
|
-
public id: NodeId,
|
|
130
|
-
public viewRule: ViewRuleGroup,
|
|
131
|
-
public parent: NodeId | null = null
|
|
132
|
-
) {
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
* Add element explicitly
|
|
137
|
-
* Included even without relationships
|
|
138
|
-
*/
|
|
139
|
-
addElement(...el: Element[]) {
|
|
140
|
-
for (const r of el) {
|
|
141
|
-
this.explicits.add(r)
|
|
142
|
-
this.implicits.add(r)
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
/**
|
|
147
|
-
* Add element implicitly
|
|
148
|
-
* Included if only has relationships
|
|
149
|
-
*/
|
|
150
|
-
addImplicit(...el: Element[]) {
|
|
151
|
-
for (const r of el) {
|
|
152
|
-
this.implicits.add(r)
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
excludeElement(...excludes: Element[]) {
|
|
157
|
-
for (const el of excludes) {
|
|
158
|
-
this.explicits.delete(el)
|
|
159
|
-
this.implicits.delete(el)
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
isEmpty() {
|
|
164
|
-
return this.explicits.size === 0 && this.implicits.size === 0
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
export class ComputeCtx {
|
|
169
|
-
// Intermediate state
|
|
170
|
-
private explicits = new Set<Element>()
|
|
171
|
-
private implicits = new Set<Element>()
|
|
172
|
-
private ctxEdges = [] as ComputeCtx.Edge[]
|
|
173
|
-
|
|
174
|
-
/**
|
|
175
|
-
* Root group - not included in the groups
|
|
176
|
-
* But used to accumulate elements that are not in any group
|
|
177
|
-
*/
|
|
178
|
-
private __rootGroup = NodesGroup.root()
|
|
179
|
-
private groups = [] as NodesGroup[]
|
|
180
|
-
private activeGroupStack = [] as NodesGroup[]
|
|
181
|
-
|
|
182
|
-
protected get activeGroup() {
|
|
183
|
-
return this.activeGroupStack[0] ?? this.__rootGroup
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
protected get includedElements() {
|
|
187
|
-
return new Set([
|
|
188
|
-
...this.explicits,
|
|
189
|
-
...this.ctxEdges.flatMap(e => [e.source, e.target])
|
|
190
|
-
]) as ReadonlySet<Element>
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
protected get resolvedElements() {
|
|
194
|
-
return new Set([
|
|
195
|
-
...this.explicits,
|
|
196
|
-
...this.implicits,
|
|
197
|
-
...this.ctxEdges.flatMap(e => [e.source, e.target])
|
|
198
|
-
]) as ReadonlySet<Element>
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
protected get edges() {
|
|
202
|
-
return this.ctxEdges
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
protected get root() {
|
|
206
|
-
return isScopedElementView(this.view) ? this.view.viewOf : null
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
public static elementView(view: ElementView, graph: LikeC4ModelGraph) {
|
|
210
|
-
return new ComputeCtx(view, graph).compute()
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
private constructor(
|
|
214
|
-
protected view: ElementView,
|
|
215
|
-
protected graph: LikeC4ModelGraph
|
|
216
|
-
) {}
|
|
217
|
-
|
|
218
|
-
protected compute(): ComputedElementView {
|
|
219
|
-
this.reset()
|
|
220
|
-
const {
|
|
221
|
-
docUri: _docUri, // exclude docUri
|
|
222
|
-
rules: _rules, // exclude rules
|
|
223
|
-
...view
|
|
224
|
-
} = this.view
|
|
225
|
-
|
|
226
|
-
const rules = resolveGlobalRulesInElementView(this.view, this.graph.globals)
|
|
227
|
-
|
|
228
|
-
const viewPredicates = rules.filter(anyPass([isViewRulePredicate, isViewRuleGroup])) as Array<
|
|
229
|
-
ViewRulePredicate | ViewRuleGroup
|
|
230
|
-
>
|
|
231
|
-
if (this.root && viewPredicates.length == 0) {
|
|
232
|
-
this.addElement(this.graph.element(this.root))
|
|
233
|
-
}
|
|
234
|
-
this.processPredicates(viewPredicates)
|
|
235
|
-
this.removeRedundantImplicitEdges()
|
|
236
|
-
if (this.groups.length > 0) {
|
|
237
|
-
this.cleanGroupElements()
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
const elements = [...this.includedElements]
|
|
241
|
-
const nodesMap = buildComputeNodes(elements, this.groups)
|
|
242
|
-
|
|
243
|
-
const edges = this.computeEdges()
|
|
244
|
-
const edgesMap = new Map<EdgeId, ComputedEdge>(edges.map(edge => [edge.id, edge]))
|
|
245
|
-
|
|
246
|
-
this.linkNodesAndEdges(nodesMap, edges)
|
|
247
|
-
|
|
248
|
-
// nodesMap sorted hierarchically,
|
|
249
|
-
// but we need to keep the initial sort
|
|
250
|
-
let initialSort = elements.map(e => nonNullable(nodesMap.get(e.id), `Node ${e.id} not found in nodesMap`))
|
|
251
|
-
|
|
252
|
-
if (this.groups.length > 0) {
|
|
253
|
-
initialSort = concat(
|
|
254
|
-
this.groups.map(g => nonNullable(nodesMap.get(g.id), `Node ${g.id} not found in nodesMap`)),
|
|
255
|
-
initialSort
|
|
256
|
-
)
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
const nodes = applyCustomElementProperties(
|
|
260
|
-
rules,
|
|
261
|
-
applyViewRuleStyles(
|
|
262
|
-
rules,
|
|
263
|
-
// Build graph and apply postorder sort
|
|
264
|
-
sortNodes({
|
|
265
|
-
nodes: initialSort,
|
|
266
|
-
edges
|
|
267
|
-
})
|
|
268
|
-
)
|
|
269
|
-
)
|
|
270
|
-
const sortedEdges = new Set([
|
|
271
|
-
...nodes.flatMap(n => n.children.length === 0 ? n.outEdges.flatMap(id => edgesMap.get(id) ?? []) : []),
|
|
272
|
-
...edges
|
|
273
|
-
])
|
|
274
|
-
|
|
275
|
-
const autoLayoutRule = this.view.rules.findLast(isViewRuleAutoLayout)
|
|
276
|
-
|
|
277
|
-
const elementNotations = buildElementNotations(nodes)
|
|
278
|
-
|
|
279
|
-
return calcViewLayoutHash({
|
|
280
|
-
...view,
|
|
281
|
-
autoLayout: {
|
|
282
|
-
direction: autoLayoutRule?.direction ?? 'TB',
|
|
283
|
-
...(autoLayoutRule?.nodeSep && { nodeSep: autoLayoutRule.nodeSep }),
|
|
284
|
-
...(autoLayoutRule?.rankSep && { rankSep: autoLayoutRule.rankSep })
|
|
285
|
-
},
|
|
286
|
-
nodes: map(nodes, n => {
|
|
287
|
-
// omit notation
|
|
288
|
-
delete n.notation
|
|
289
|
-
if (n.icon === 'none') {
|
|
290
|
-
delete n.icon
|
|
291
|
-
}
|
|
292
|
-
return n
|
|
293
|
-
}),
|
|
294
|
-
edges: applyCustomRelationProperties(rules, nodes, sortedEdges),
|
|
295
|
-
...(elementNotations.length > 0 && {
|
|
296
|
-
notation: {
|
|
297
|
-
elements: elementNotations
|
|
298
|
-
}
|
|
299
|
-
})
|
|
300
|
-
})
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
protected computeEdges(): ComputedEdge[] {
|
|
304
|
-
return this.ctxEdges.map((e): ComputedEdge => {
|
|
305
|
-
invariant(hasAtLeast(e.relations, 1), 'Edge must have at least one relation')
|
|
306
|
-
const relations = sort(e.relations, compareRelations)
|
|
307
|
-
const source = e.source.id
|
|
308
|
-
const target = e.target.id
|
|
309
|
-
|
|
310
|
-
const edge: ComputedEdge = {
|
|
311
|
-
id: `${source}:${target}` as EdgeId,
|
|
312
|
-
parent: commonAncestor(source, target),
|
|
313
|
-
source,
|
|
314
|
-
target,
|
|
315
|
-
label: null,
|
|
316
|
-
relations: relations.map(r => r.id)
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
let relation: {
|
|
320
|
-
// TODO refactor with type-fest
|
|
321
|
-
title: string
|
|
322
|
-
description?: string | undefined
|
|
323
|
-
technology?: string | undefined
|
|
324
|
-
kind?: RelationshipKind | undefined
|
|
325
|
-
color?: Color | undefined
|
|
326
|
-
line?: RelationshipLineType | undefined
|
|
327
|
-
head?: RelationshipArrowType | undefined
|
|
328
|
-
tail?: RelationshipArrowType | undefined
|
|
329
|
-
tags?: NonEmptyArray<Tag>
|
|
330
|
-
navigateTo?: ViewID | undefined
|
|
331
|
-
} | undefined
|
|
332
|
-
relation = only(relations) ?? pipe(
|
|
333
|
-
relations,
|
|
334
|
-
filter(r => r.source === source && r.target === target),
|
|
335
|
-
only()
|
|
336
|
-
)
|
|
337
|
-
|
|
338
|
-
// This edge represents mutliple relations
|
|
339
|
-
// We use label if only it is the same for all relations
|
|
340
|
-
if (!relation) {
|
|
341
|
-
const allprops = pipe(
|
|
342
|
-
relations,
|
|
343
|
-
reduce((acc, r) => {
|
|
344
|
-
if (isTruthy(r.title) && !acc.title.includes(r.title)) {
|
|
345
|
-
acc.title.push(r.title)
|
|
346
|
-
}
|
|
347
|
-
if (isTruthy(r.description) && !acc.description.includes(r.description)) {
|
|
348
|
-
acc.description.push(r.description)
|
|
349
|
-
}
|
|
350
|
-
if (isTruthy(r.technology) && !acc.technology.includes(r.technology)) {
|
|
351
|
-
acc.technology.push(r.technology)
|
|
352
|
-
}
|
|
353
|
-
if (isTruthy(r.kind) && !acc.kind.includes(r.kind)) {
|
|
354
|
-
acc.kind.push(r.kind)
|
|
355
|
-
}
|
|
356
|
-
if (isTruthy(r.color) && !acc.color.includes(r.color)) {
|
|
357
|
-
acc.color.push(r.color)
|
|
358
|
-
}
|
|
359
|
-
if (isTruthy(r.line) && !acc.line.includes(r.line)) {
|
|
360
|
-
acc.line.push(r.line)
|
|
361
|
-
}
|
|
362
|
-
if (isTruthy(r.head) && !acc.head.includes(r.head)) {
|
|
363
|
-
acc.head.push(r.head)
|
|
364
|
-
}
|
|
365
|
-
if (isTruthy(r.tail) && !acc.tail.includes(r.tail)) {
|
|
366
|
-
acc.tail.push(r.tail)
|
|
367
|
-
}
|
|
368
|
-
if (isTruthy(r.navigateTo) && !acc.navigateTo.includes(r.navigateTo)) {
|
|
369
|
-
acc.navigateTo.push(r.navigateTo)
|
|
370
|
-
}
|
|
371
|
-
return acc
|
|
372
|
-
}, {
|
|
373
|
-
title: [] as string[],
|
|
374
|
-
description: [] as string[],
|
|
375
|
-
technology: [] as string[],
|
|
376
|
-
kind: [] as RelationshipKind[],
|
|
377
|
-
head: [] as RelationshipArrowType[],
|
|
378
|
-
tail: [] as RelationshipArrowType[],
|
|
379
|
-
color: [] as Color[],
|
|
380
|
-
line: [] as RelationshipLineType[],
|
|
381
|
-
navigateTo: [] as ViewID[]
|
|
382
|
-
})
|
|
383
|
-
)
|
|
384
|
-
relation = {
|
|
385
|
-
title: only(allprops.title) ?? '[...]',
|
|
386
|
-
description: only(allprops.description),
|
|
387
|
-
technology: only(allprops.technology),
|
|
388
|
-
kind: only(allprops.kind),
|
|
389
|
-
head: only(allprops.head),
|
|
390
|
-
tail: only(allprops.tail),
|
|
391
|
-
color: only(allprops.color),
|
|
392
|
-
line: only(allprops.line),
|
|
393
|
-
navigateTo: only(allprops.navigateTo)
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
const tags = uniqueTags(relations)
|
|
398
|
-
|
|
399
|
-
return Object.assign(
|
|
400
|
-
edge,
|
|
401
|
-
this.getEdgeLabel(relation),
|
|
402
|
-
isTruthy(relation.description) && { description: relation.description },
|
|
403
|
-
isTruthy(relation.technology) && { technology: relation.technology },
|
|
404
|
-
isTruthy(relation.kind) && { kind: relation.kind },
|
|
405
|
-
relation.color && { color: relation.color },
|
|
406
|
-
relation.line && { line: relation.line },
|
|
407
|
-
relation.head && { head: relation.head },
|
|
408
|
-
relation.tail && { tail: relation.tail },
|
|
409
|
-
relation.navigateTo && { navigateTo: relation.navigateTo },
|
|
410
|
-
tags && { tags }
|
|
411
|
-
)
|
|
412
|
-
})
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
protected linkNodesAndEdges(nodesMap: ReadonlyMap<Fqn, ComputedNode>, edges: ComputedEdge[]) {
|
|
416
|
-
for (const edge of edges) {
|
|
417
|
-
const source = nodesMap.get(edge.source)
|
|
418
|
-
const target = nodesMap.get(edge.target)
|
|
419
|
-
invariant(source, `Source node ${edge.source} not found`)
|
|
420
|
-
invariant(target, `Target node ${edge.target} not found`)
|
|
421
|
-
// These ancestors are reversed: from bottom to top
|
|
422
|
-
const sourceAncestors = this.ancestorsOf(source, nodesMap)
|
|
423
|
-
const targetAncestors = this.ancestorsOf(target, nodesMap)
|
|
424
|
-
|
|
425
|
-
const edgeParent = last(
|
|
426
|
-
commonHead(
|
|
427
|
-
reverse(sourceAncestors),
|
|
428
|
-
reverse(targetAncestors)
|
|
429
|
-
)
|
|
430
|
-
)
|
|
431
|
-
edge.parent = edgeParent?.id ?? null
|
|
432
|
-
source.outEdges.push(edge.id)
|
|
433
|
-
target.inEdges.push(edge.id)
|
|
434
|
-
// Process edge source ancestors
|
|
435
|
-
for (const sourceAncestor of sourceAncestors) {
|
|
436
|
-
if (sourceAncestor === edgeParent) {
|
|
437
|
-
break
|
|
438
|
-
}
|
|
439
|
-
sourceAncestor.outEdges.push(edge.id)
|
|
440
|
-
}
|
|
441
|
-
// Process target hierarchy
|
|
442
|
-
for (const targetAncestor of targetAncestors) {
|
|
443
|
-
if (targetAncestor === edgeParent) {
|
|
444
|
-
break
|
|
445
|
-
}
|
|
446
|
-
targetAncestor.inEdges.push(edge.id)
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
protected addEdges(edges: ComputeCtx.Edge[]) {
|
|
452
|
-
const added = [] as ComputeCtx.Edge[]
|
|
453
|
-
for (const e of edges) {
|
|
454
|
-
if (!hasAtLeast(e.relations, 1)) {
|
|
455
|
-
continue
|
|
456
|
-
}
|
|
457
|
-
const existing = this.ctxEdges.find(
|
|
458
|
-
_e => _e.source.id === e.source.id && _e.target.id === e.target.id
|
|
459
|
-
)
|
|
460
|
-
if (existing) {
|
|
461
|
-
existing.relations = unique([...existing.relations, ...e.relations])
|
|
462
|
-
added.push(existing)
|
|
463
|
-
continue
|
|
464
|
-
}
|
|
465
|
-
added.push(e)
|
|
466
|
-
this.ctxEdges.push(e)
|
|
467
|
-
}
|
|
468
|
-
return added
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
/**
|
|
472
|
-
* Add element explicitly
|
|
473
|
-
* Included even without relationships
|
|
474
|
-
*/
|
|
475
|
-
protected addElement(...el: Element[]) {
|
|
476
|
-
for (const r of el) {
|
|
477
|
-
if (!this.explicits.has(r)) {
|
|
478
|
-
this.activeGroup.addElement(r)
|
|
479
|
-
} else if (this.activeGroup !== this.__rootGroup) {
|
|
480
|
-
this.groups.forEach(g => {
|
|
481
|
-
g.implicits.delete(r)
|
|
482
|
-
})
|
|
483
|
-
this.activeGroup.addImplicit(r)
|
|
484
|
-
}
|
|
485
|
-
this.explicits.add(r)
|
|
486
|
-
this.implicits.add(r)
|
|
487
|
-
}
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
/**
|
|
491
|
-
* Add element implicitly
|
|
492
|
-
* Included if only has relationships
|
|
493
|
-
*/
|
|
494
|
-
protected addImplicit(...el: Element[]) {
|
|
495
|
-
for (const r of el) {
|
|
496
|
-
this.implicits.add(r)
|
|
497
|
-
// Remove implicits from other groups
|
|
498
|
-
if (this.activeGroup !== this.__rootGroup) {
|
|
499
|
-
this.groups.forEach(g => {
|
|
500
|
-
g.implicits.delete(r)
|
|
501
|
-
})
|
|
502
|
-
}
|
|
503
|
-
}
|
|
504
|
-
this.activeGroup.addImplicit(...el)
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
protected excludeElement(...excludes: Element[]) {
|
|
508
|
-
for (const el of excludes) {
|
|
509
|
-
this.ctxEdges = this.ctxEdges.filter(e => e.source.id !== el.id && e.target.id !== el.id)
|
|
510
|
-
this.explicits.delete(el)
|
|
511
|
-
this.implicits.delete(el)
|
|
512
|
-
}
|
|
513
|
-
this.__rootGroup.excludeElement(...excludes)
|
|
514
|
-
this.groups.forEach(g => {
|
|
515
|
-
g.excludeElement(...excludes)
|
|
516
|
-
})
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
protected excludeRelation(...relations: Relation[]) {
|
|
520
|
-
if (relations.length === 0) {
|
|
521
|
-
return
|
|
522
|
-
}
|
|
523
|
-
const excludedImplicits = new Set<Element>()
|
|
524
|
-
const ctxEdges = pipe(
|
|
525
|
-
this.ctxEdges,
|
|
526
|
-
map(edge => {
|
|
527
|
-
const edgerelations = edge.relations.filter(r => !relations.includes(r))
|
|
528
|
-
if (edgerelations.length === 0) {
|
|
529
|
-
excludedImplicits.add(edge.source)
|
|
530
|
-
excludedImplicits.add(edge.target)
|
|
531
|
-
return null
|
|
532
|
-
}
|
|
533
|
-
if (edgerelations.length !== edge.relations.length) {
|
|
534
|
-
return {
|
|
535
|
-
...edge,
|
|
536
|
-
relations: edgerelations
|
|
537
|
-
}
|
|
538
|
-
}
|
|
539
|
-
return edge
|
|
540
|
-
}),
|
|
541
|
-
filter(isNonNull)
|
|
542
|
-
)
|
|
543
|
-
this.ctxEdges = ctxEdges
|
|
544
|
-
const remaining = this.includedElements
|
|
545
|
-
if (remaining.size === 0) {
|
|
546
|
-
this.implicits.clear()
|
|
547
|
-
this.__rootGroup.implicits.clear()
|
|
548
|
-
this.groups.forEach(g => g.implicits.clear())
|
|
549
|
-
return
|
|
550
|
-
}
|
|
551
|
-
for (const el of excludedImplicits) {
|
|
552
|
-
if (!remaining.has(el)) {
|
|
553
|
-
this.implicits.delete(el)
|
|
554
|
-
this.__rootGroup.implicits.delete(el)
|
|
555
|
-
this.groups.forEach(g => g.implicits.delete(el))
|
|
556
|
-
}
|
|
557
|
-
}
|
|
558
|
-
}
|
|
559
|
-
|
|
560
|
-
protected reset() {
|
|
561
|
-
this.explicits.clear()
|
|
562
|
-
this.implicits.clear()
|
|
563
|
-
this.ctxEdges = []
|
|
564
|
-
// Reset groups
|
|
565
|
-
this.__rootGroup = NodesGroup.root()
|
|
566
|
-
this.groups = []
|
|
567
|
-
this.activeGroupStack = []
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
// Filter out edges if there are edges between descendants
|
|
571
|
-
// i.e. remove implicit edges, derived from childs
|
|
572
|
-
protected removeRedundantImplicitEdges() {
|
|
573
|
-
const processedRelations = new Set<Relation>()
|
|
574
|
-
|
|
575
|
-
// Returns relations, that are not processed/included
|
|
576
|
-
const excludeProcessed = (relations: Relation[]) =>
|
|
577
|
-
relations.reduce((acc, rel) => {
|
|
578
|
-
if (!processedRelations.has(rel)) {
|
|
579
|
-
acc.push(rel)
|
|
580
|
-
processedRelations.add(rel)
|
|
581
|
-
}
|
|
582
|
-
return acc
|
|
583
|
-
}, [] as Relation[])
|
|
584
|
-
|
|
585
|
-
// Returns predicate
|
|
586
|
-
const isNestedEdgeOf = (parent: ComputeCtx.Edge) => {
|
|
587
|
-
const { source, target } = parent
|
|
588
|
-
// Checks if edge is between descendants of source and target of the parent edge
|
|
589
|
-
return (edge: ComputeCtx.Edge) => {
|
|
590
|
-
const isSameSource = source.id === edge.source.id
|
|
591
|
-
const isSameTarget = target.id === edge.target.id
|
|
592
|
-
if (isSameSource && isSameTarget) {
|
|
593
|
-
return true
|
|
594
|
-
}
|
|
595
|
-
const isSourceNested = isAncestor(source.id, edge.source.id)
|
|
596
|
-
const isTargetNested = isAncestor(target.id, edge.target.id)
|
|
597
|
-
return (
|
|
598
|
-
(isSourceNested && isTargetNested)
|
|
599
|
-
|| (isSameSource && isTargetNested)
|
|
600
|
-
|| (isSameTarget && isSourceNested)
|
|
601
|
-
)
|
|
602
|
-
}
|
|
603
|
-
}
|
|
604
|
-
|
|
605
|
-
// Sort edges from bottom to top (i.e. from more specific edges to implicit or between ancestors)
|
|
606
|
-
const edges = [...this.ctxEdges].sort(compareEdges).reverse()
|
|
607
|
-
this.ctxEdges = edges.reduce((acc, e) => {
|
|
608
|
-
const relations = excludeProcessed(e.relations)
|
|
609
|
-
if (relations.length === 0) {
|
|
610
|
-
return acc
|
|
611
|
-
}
|
|
612
|
-
// If there is an edge between descendants of current edge,
|
|
613
|
-
// then we don't add this edge
|
|
614
|
-
if (acc.length > 0 && acc.some(isNestedEdgeOf(e))) {
|
|
615
|
-
return acc
|
|
616
|
-
}
|
|
617
|
-
acc.push({
|
|
618
|
-
source: e.source,
|
|
619
|
-
target: e.target,
|
|
620
|
-
relations
|
|
621
|
-
})
|
|
622
|
-
return acc
|
|
623
|
-
}, [] as ComputeCtx.Edge[])
|
|
624
|
-
}
|
|
625
|
-
|
|
626
|
-
protected cleanGroupElements() {
|
|
627
|
-
const unprocessed = new Set<Element>(difference(
|
|
628
|
-
[...this.includedElements],
|
|
629
|
-
[...this.__rootGroup.explicits]
|
|
630
|
-
))
|
|
631
|
-
// Step 1 - Process explicits
|
|
632
|
-
for (const group of this.groups) {
|
|
633
|
-
const explicits = [...group.explicits]
|
|
634
|
-
group.explicits.clear()
|
|
635
|
-
for (const el of explicits) {
|
|
636
|
-
if (unprocessed.delete(el)) {
|
|
637
|
-
group.explicits.add(el)
|
|
638
|
-
}
|
|
639
|
-
}
|
|
640
|
-
}
|
|
641
|
-
// Step 2 - Process implicits
|
|
642
|
-
for (const group of this.groups) {
|
|
643
|
-
for (const el of group.implicits) {
|
|
644
|
-
if (unprocessed.delete(el)) {
|
|
645
|
-
group.explicits.add(el)
|
|
646
|
-
}
|
|
647
|
-
}
|
|
648
|
-
group.implicits.clear()
|
|
649
|
-
}
|
|
650
|
-
}
|
|
651
|
-
|
|
652
|
-
protected processPredicates(viewRules: Array<ViewRulePredicate | ViewRuleGroup>): this {
|
|
653
|
-
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
654
|
-
for (const rule of viewRules) {
|
|
655
|
-
if (isViewRuleGroup(rule)) {
|
|
656
|
-
const parent = first(this.activeGroupStack)
|
|
657
|
-
const groupId = `@gr${this.groups.length + 1}` as NodeId
|
|
658
|
-
const group = new NodesGroup(groupId, rule, parent?.id ?? null)
|
|
659
|
-
this.groups.push(group)
|
|
660
|
-
this.activeGroupStack.unshift(group)
|
|
661
|
-
this.processPredicates(rule.groupRules)
|
|
662
|
-
this.activeGroupStack.shift()
|
|
663
|
-
continue
|
|
664
|
-
}
|
|
665
|
-
const isInclude = 'include' in rule
|
|
666
|
-
const exprs = rule.include ?? rule.exclude
|
|
667
|
-
for (const expr of exprs) {
|
|
668
|
-
if (Expr.isElementPredicateExpr(expr)) {
|
|
669
|
-
this.processElementPredicate(expr, isInclude)
|
|
670
|
-
continue
|
|
671
|
-
}
|
|
672
|
-
if (Expr.isRelationPredicateExpr(expr)) {
|
|
673
|
-
this.processRelationPredicate(expr, isInclude)
|
|
674
|
-
continue
|
|
675
|
-
}
|
|
676
|
-
nonexhaustive(expr)
|
|
677
|
-
}
|
|
678
|
-
}
|
|
679
|
-
return this
|
|
680
|
-
}
|
|
681
|
-
|
|
682
|
-
protected processElementPredicate(
|
|
683
|
-
expr: ElementPredicateExpression,
|
|
684
|
-
isInclude: boolean,
|
|
685
|
-
where?: ElementPredicateFn
|
|
686
|
-
): this {
|
|
687
|
-
if (Expr.isCustomElement(expr)) {
|
|
688
|
-
if (isInclude) {
|
|
689
|
-
this.processElementPredicate(expr.custom.expr, isInclude)
|
|
690
|
-
}
|
|
691
|
-
return this
|
|
692
|
-
}
|
|
693
|
-
if (Expr.isElementWhere(expr)) {
|
|
694
|
-
const where = whereOperatorAsPredicate(expr.where.condition)
|
|
695
|
-
this.processElementPredicate(expr.where.expr, isInclude, where)
|
|
696
|
-
return this
|
|
697
|
-
}
|
|
698
|
-
if (Expr.isExpandedElementExpr(expr)) {
|
|
699
|
-
isInclude
|
|
700
|
-
? includeExpandedElementExpr.call(this, expr, where)
|
|
701
|
-
: excludeExpandedElementExpr.call(this, expr, where)
|
|
702
|
-
return this
|
|
703
|
-
}
|
|
704
|
-
if (Expr.isElementKindExpr(expr) || Expr.isElementTagExpr(expr)) {
|
|
705
|
-
isInclude
|
|
706
|
-
? includeElementKindOrTag.call(this, expr, where)
|
|
707
|
-
: excludeElementKindOrTag.call(this, expr, where)
|
|
708
|
-
return this
|
|
709
|
-
}
|
|
710
|
-
if (Expr.isElementRef(expr)) {
|
|
711
|
-
isInclude ? includeElementRef.call(this, expr, where) : excludeElementRef.call(this, expr, where)
|
|
712
|
-
return this
|
|
713
|
-
}
|
|
714
|
-
if (Expr.isWildcard(expr)) {
|
|
715
|
-
isInclude ? includeWildcardRef.call(this, expr, where) : excludeWildcardRef.call(this, expr, where)
|
|
716
|
-
return this
|
|
717
|
-
}
|
|
718
|
-
nonexhaustive(expr)
|
|
719
|
-
}
|
|
720
|
-
|
|
721
|
-
protected processRelationPredicate(
|
|
722
|
-
expr: RelationPredicateExpression,
|
|
723
|
-
isInclude: boolean,
|
|
724
|
-
where?: RelationPredicateFn
|
|
725
|
-
): this {
|
|
726
|
-
if (Expr.isCustomRelationExpr(expr)) {
|
|
727
|
-
if (isInclude) {
|
|
728
|
-
this.processRelationPredicate(expr.customRelation.relation, isInclude)
|
|
729
|
-
}
|
|
730
|
-
return this
|
|
731
|
-
}
|
|
732
|
-
if (Expr.isRelationWhere(expr)) {
|
|
733
|
-
const where = whereOperatorAsPredicate(expr.where.condition)
|
|
734
|
-
this.processRelationPredicate(expr.where.expr, isInclude, where)
|
|
735
|
-
return this
|
|
736
|
-
}
|
|
737
|
-
if (Expr.isIncoming(expr)) {
|
|
738
|
-
isInclude ? includeIncomingExpr.call(this, expr, where) : excludeIncomingExpr.call(this, expr, where)
|
|
739
|
-
return this
|
|
740
|
-
}
|
|
741
|
-
if (Expr.isOutgoing(expr)) {
|
|
742
|
-
isInclude ? includeOutgoingExpr.call(this, expr, where) : excludeOutgoingExpr.call(this, expr, where)
|
|
743
|
-
return this
|
|
744
|
-
}
|
|
745
|
-
if (Expr.isInOut(expr)) {
|
|
746
|
-
isInclude ? includeInOutExpr.call(this, expr, where) : excludeInOutExpr.call(this, expr, where)
|
|
747
|
-
return this
|
|
748
|
-
}
|
|
749
|
-
if (Expr.isRelation(expr)) {
|
|
750
|
-
isInclude ? includeRelationExpr.call(this, expr, where) : excludeRelationExpr.call(this, expr, where)
|
|
751
|
-
return this
|
|
752
|
-
}
|
|
753
|
-
nonexhaustive(expr)
|
|
754
|
-
}
|
|
755
|
-
|
|
756
|
-
private ancestorsOf(node: ComputedNode, nodesMap: ReadonlyMap<Fqn, ComputedNode>): ComputedNode[] {
|
|
757
|
-
const ancestors = [] as ComputedNode[]
|
|
758
|
-
let parent = node.parent
|
|
759
|
-
while (parent) {
|
|
760
|
-
const parentNode = nodesMap.get(parent)
|
|
761
|
-
if (!parentNode) {
|
|
762
|
-
break
|
|
763
|
-
}
|
|
764
|
-
ancestors.push(parentNode)
|
|
765
|
-
parent = parentNode.parent
|
|
766
|
-
}
|
|
767
|
-
return ancestors
|
|
768
|
-
}
|
|
769
|
-
|
|
770
|
-
protected getEdgeLabel(
|
|
771
|
-
relation: {
|
|
772
|
-
title: string
|
|
773
|
-
technology?: string | undefined
|
|
774
|
-
}
|
|
775
|
-
) {
|
|
776
|
-
const labelParts: string[] = []
|
|
777
|
-
|
|
778
|
-
if (isTruthy(relation.title)) {
|
|
779
|
-
labelParts.push(relation.title)
|
|
780
|
-
}
|
|
781
|
-
|
|
782
|
-
if (isTruthy(relation.technology)) {
|
|
783
|
-
labelParts.push(`[${relation.technology}]`)
|
|
784
|
-
}
|
|
785
|
-
|
|
786
|
-
return labelParts.length > 0 ? { label: labelParts.join('\n') } : {}
|
|
787
|
-
}
|
|
788
|
-
}
|