@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,313 +0,0 @@
1
- import type {
2
- Color,
3
- ComputedDynamicView,
4
- ComputedEdge,
5
- DynamicView,
6
- DynamicViewStep,
7
- Element,
8
- NonEmptyArray,
9
- RelationID,
10
- RelationshipArrowType,
11
- RelationshipLineType,
12
- Tag,
13
- ViewID
14
- } from '@likec4/core'
15
- import {
16
- ancestorsFqn,
17
- commonAncestor,
18
- DefaultArrowType,
19
- DefaultLineStyle,
20
- DefaultRelationshipColor,
21
- isDynamicViewIncludeRule,
22
- isDynamicViewParallelSteps,
23
- isViewRuleAutoLayout,
24
- nonNullable,
25
- parentFqn,
26
- StepEdgeId
27
- } from '@likec4/core'
28
- import { filter, flatMap, hasAtLeast, isTruthy, map, omit, only, pipe, unique } from 'remeda'
29
- import { resolveGlobalRulesInDynamicView } from '../../view-utils/resolve-global-rules'
30
- import { calcViewLayoutHash } from '../../view-utils/view-hash'
31
- import type { LikeC4ModelGraph } from '../LikeC4ModelGraph'
32
- import { applyCustomElementProperties } from '../utils/applyCustomElementProperties'
33
- import { applyViewRuleStyles } from '../utils/applyViewRuleStyles'
34
- import { buildComputeNodes } from '../utils/buildComputeNodes'
35
- import { buildElementNotations } from '../utils/buildElementNotations'
36
- import { elementExprToPredicate } from '../utils/elementExpressionToPredicate'
37
-
38
- export namespace DynamicViewComputeCtx {
39
- export interface Step {
40
- id: StepEdgeId
41
- source: Element
42
- target: Element
43
- title: string | null
44
- description?: string
45
- technology?: string
46
- color?: Color
47
- line?: RelationshipLineType
48
- head?: RelationshipArrowType
49
- tail?: RelationshipArrowType
50
- relations: RelationID[]
51
- isBackward: boolean
52
- navigateTo?: ViewID
53
- tags?: NonEmptyArray<Tag>
54
- }
55
- }
56
-
57
- export class DynamicViewComputeCtx {
58
- // Intermediate state
59
- private explicits = new Set<Element>()
60
- private steps = [] as DynamicViewComputeCtx.Step[]
61
-
62
- public static compute(view: DynamicView, graph: LikeC4ModelGraph): ComputedDynamicView {
63
- return new DynamicViewComputeCtx(view, graph).compute()
64
- }
65
-
66
- private constructor(
67
- protected view: DynamicView,
68
- protected graph: LikeC4ModelGraph
69
- ) {}
70
-
71
- private addStep(
72
- {
73
- source: stepSource,
74
- target: stepTarget,
75
- title: stepTitle,
76
- isBackward,
77
- navigateTo: stepNavigateTo,
78
- ...step
79
- }: DynamicViewStep,
80
- index: number,
81
- parent?: number
82
- ) {
83
- const id = parent ? StepEdgeId(parent, index) : StepEdgeId(index)
84
- const source = this.graph.element(stepSource)
85
- const target = this.graph.element(stepTarget)
86
-
87
- this.explicits.add(source)
88
- this.explicits.add(target)
89
-
90
- const {
91
- title,
92
- relations,
93
- tags,
94
- navigateTo: derivedNavigateTo,
95
- head,
96
- tail,
97
- color,
98
- line,
99
- notation
100
- } = this.findRelations(source, target)
101
-
102
- const navigateTo = isTruthy(stepNavigateTo) && stepNavigateTo !== this.view.id ? stepNavigateTo : derivedNavigateTo
103
-
104
- this.steps.push({
105
- id,
106
- ...step,
107
- source,
108
- target,
109
- title: stepTitle ?? title,
110
- relations: relations ?? [],
111
- isBackward: isBackward ?? false,
112
- ...(navigateTo ? { navigateTo } : {}),
113
- ...(tags ? { tags } : {}),
114
- ...(head ? { head } : {}),
115
- ...(tail ? { tail } : {}),
116
- ...(color ? { color } : {}),
117
- ...(line ? { line } : {}),
118
- ...(notation ? { notation } : {})
119
- })
120
- }
121
-
122
- protected compute(): ComputedDynamicView {
123
- const {
124
- docUri: _docUri, // exclude docUri
125
- rules: _rules, // exclude rules
126
- steps: viewSteps,
127
- ...view
128
- } = this.view
129
-
130
- let stepNum = 1
131
- for (const step of viewSteps) {
132
- if (isDynamicViewParallelSteps(step)) {
133
- if (step.__parallel.length === 0) {
134
- continue
135
- }
136
- if (step.__parallel.length === 1) {
137
- this.addStep(step.__parallel[0]!, stepNum)
138
- } else {
139
- step.__parallel.forEach((s, i) => this.addStep(s, i + 1, stepNum))
140
- }
141
- } else {
142
- this.addStep(step, stepNum)
143
- }
144
- stepNum++
145
- }
146
-
147
- const rules = resolveGlobalRulesInDynamicView(this.view, this.graph.globals)
148
-
149
- for (const rule of rules) {
150
- if (isDynamicViewIncludeRule(rule)) {
151
- for (const expr of rule.include) {
152
- const predicate = elementExprToPredicate(expr)
153
- this.graph.elements.filter(predicate).forEach(e => this.explicits.add(e))
154
- }
155
- }
156
- }
157
-
158
- const elements = [...this.explicits]
159
- const nodesMap = buildComputeNodes(elements)
160
-
161
- const edges = this.steps.map(({ source, target, relations, title, isBackward, ...step }) => {
162
- const sourceNode = nonNullable(nodesMap.get(source.id), `Source node ${source.id} not found`)
163
- const targetNode = nonNullable(nodesMap.get(target.id), `Target node ${target.id} not found`)
164
- const edge: ComputedEdge = {
165
- parent: commonAncestor(source.id, target.id),
166
- source: source.id,
167
- target: target.id,
168
- label: title,
169
- relations,
170
- color: DefaultRelationshipColor,
171
- line: DefaultLineStyle,
172
- head: DefaultArrowType,
173
- ...step
174
- }
175
- if (isBackward) {
176
- edge.dir = 'back'
177
- }
178
-
179
- while (edge.parent && !nodesMap.has(edge.parent)) {
180
- edge.parent = parentFqn(edge.parent)
181
- }
182
- sourceNode.outEdges.push(edge.id)
183
- targetNode.inEdges.push(edge.id)
184
- // Process edge source ancestors
185
- for (const sourceAncestor of ancestorsFqn(edge.source)) {
186
- if (sourceAncestor === edge.parent) {
187
- break
188
- }
189
- nodesMap.get(sourceAncestor)?.outEdges.push(edge.id)
190
- }
191
- // Process target hierarchy
192
- for (const targetAncestor of ancestorsFqn(edge.target)) {
193
- if (targetAncestor === edge.parent) {
194
- break
195
- }
196
- nodesMap.get(targetAncestor)?.inEdges.push(edge.id)
197
- }
198
- return edge
199
- })
200
-
201
- const nodes = applyCustomElementProperties(
202
- rules,
203
- applyViewRuleStyles(
204
- rules,
205
- // Keep order of elements
206
- elements.map(e => nonNullable(nodesMap.get(e.id)))
207
- )
208
- )
209
-
210
- const autoLayoutRule = rules.findLast(isViewRuleAutoLayout)
211
-
212
- const elementNotations = buildElementNotations(nodes)
213
-
214
- return calcViewLayoutHash({
215
- ...view,
216
- autoLayout: {
217
- direction: autoLayoutRule?.direction ?? 'LR',
218
- ...(autoLayoutRule?.nodeSep && { nodeSep: autoLayoutRule.nodeSep }),
219
- ...(autoLayoutRule?.rankSep && { rankSep: autoLayoutRule.rankSep })
220
- },
221
- nodes: map(nodes, n => {
222
- // omit notation
223
- delete n.notation
224
- if (n.icon === 'none') {
225
- delete n.icon
226
- }
227
- return n
228
- }),
229
- edges,
230
- ...(elementNotations.length > 0 && {
231
- notation: {
232
- elements: elementNotations
233
- }
234
- })
235
- })
236
- }
237
-
238
- private findRelations(source: Element, target: Element): {
239
- title: string | null
240
- tags: NonEmptyArray<Tag> | null
241
- relations: NonEmptyArray<RelationID> | null
242
- navigateTo: ViewID | null
243
- tail: RelationshipArrowType | null
244
- head: RelationshipArrowType | null
245
- color: Color | null
246
- line: RelationshipLineType | null
247
- notation: string | null
248
- } {
249
- const relationships = unique(this.graph.edgesBetween(source, target).flatMap(e => e.relations))
250
- if (relationships.length === 0) {
251
- return {
252
- title: null,
253
- tags: null,
254
- relations: null,
255
- navigateTo: null,
256
- tail: null,
257
- head: null,
258
- color: null,
259
- line: null,
260
- notation: null
261
- }
262
- }
263
- const alltags = pipe(
264
- relationships,
265
- flatMap(r => r.tags),
266
- filter(isTruthy),
267
- unique()
268
- )
269
- const tags = hasAtLeast(alltags, 1) ? alltags : null
270
- const relations = hasAtLeast(relationships, 1) ? map(relationships, r => r.id) : null
271
-
272
- // Most closest relation
273
- const relation = only(relationships) || relationships.find(r => r.source === source.id && r.target === target.id)
274
-
275
- const navigateTo = !!relation?.navigateTo && relation.navigateTo !== this.view.id ? relation.navigateTo : pipe(
276
- relationships,
277
- map(r => r.navigateTo),
278
- filter(isTruthy),
279
- filter(v => v !== this.view.id),
280
- unique(),
281
- only()
282
- )
283
-
284
- const commonProperties = relationships.reduce((acc, r) => {
285
- isTruthy(r.title) && acc.title.push(r.title)
286
- isTruthy(r.tail) && acc.tail.push(r.tail)
287
- isTruthy(r.head) && acc.head.push(r.head)
288
- isTruthy(r.color) && acc.color.push(r.color)
289
- isTruthy(r.line) && acc.line.push(r.line)
290
-
291
- return acc
292
- }, {
293
- title: [] as string[],
294
- tail: [] as RelationshipArrowType[],
295
- head: [] as RelationshipArrowType[],
296
- color: [] as Color[],
297
- line: [] as RelationshipLineType[],
298
- notation: [] as string[]
299
- })
300
-
301
- return {
302
- tags,
303
- relations,
304
- navigateTo: navigateTo ?? null,
305
- title: pipe(commonProperties.title, unique(), only()) ?? null,
306
- tail: pipe(commonProperties.tail, unique(), only()) ?? null,
307
- head: pipe(commonProperties.head, unique(), only()) ?? null,
308
- color: pipe(commonProperties.color, unique(), only()) ?? null,
309
- line: pipe(commonProperties.line, unique(), only()) ?? null,
310
- notation: pipe(commonProperties.notation, unique(), only()) ?? null
311
- }
312
- }
313
- }
@@ -1,29 +0,0 @@
1
- import { type ComputedDynamicView, type DynamicView } from '@likec4/core'
2
- import type { LikeC4ModelGraph } from '../LikeC4ModelGraph'
3
- import { DynamicViewComputeCtx } from './compute'
4
-
5
- type ComputeViewResult =
6
- | {
7
- isSuccess: true
8
- view: ComputedDynamicView
9
- }
10
- | {
11
- isSuccess: false
12
- error: Error
13
- view: undefined
14
- }
15
-
16
- export function computeDynamicView(view: DynamicView, graph: LikeC4ModelGraph): ComputeViewResult {
17
- try {
18
- return {
19
- isSuccess: true,
20
- view: DynamicViewComputeCtx.compute(view, graph)
21
- }
22
- } catch (e) {
23
- return {
24
- isSuccess: false,
25
- error: e instanceof Error ? e : new Error(`Unknown error: ${e}`),
26
- view: undefined
27
- }
28
- }
29
- }
@@ -1,3 +0,0 @@
1
- export * from './compute-view'
2
- export * from './dynamic-view'
3
- export * from './LikeC4ModelGraph'
@@ -1,65 +0,0 @@
1
- import { ComputedNode, Expr, type Expression, isViewRuleGroup, isViewRulePredicate, type ViewRule } from '@likec4/core'
2
- import { isEmpty, isNullish, omitBy } from 'remeda'
3
- import { elementExprToPredicate } from './elementExpressionToPredicate'
4
-
5
- export function flattenGroupRules<T extends Expression>(guard: (expr: Expression) => expr is T) {
6
- return (rule: ViewRule): Array<T> => {
7
- if (isViewRuleGroup(rule)) {
8
- return rule.groupRules.flatMap(flattenGroupRules(guard))
9
- }
10
- if (isViewRulePredicate(rule)) {
11
- return 'include' in rule ? rule.include.filter(guard) : []
12
- }
13
-
14
- return []
15
- }
16
- }
17
-
18
- export function applyCustomElementProperties(_rules: ViewRule[], _nodes: ComputedNode[]) {
19
- const rules = _rules.flatMap(flattenGroupRules(Expr.isCustomElement))
20
- if (rules.length === 0) {
21
- return _nodes
22
- }
23
- const nodes = [..._nodes]
24
- for (
25
- const {
26
- custom: { expr, ...props }
27
- } of rules
28
- ) {
29
- const { border, opacity, ...rest } = omitBy(props, isNullish)
30
- const notEmpty = !isEmpty(rest)
31
- const satisfies = elementExprToPredicate(expr)
32
- nodes.forEach((node, i) => {
33
- if (ComputedNode.isNodesGroup(node) || !satisfies(node)) {
34
- return
35
- }
36
- if (notEmpty) {
37
- node = {
38
- ...node,
39
- isCustomized: true,
40
- ...rest
41
- }
42
- }
43
-
44
- let styleOverride: ComputedNode['style'] | undefined
45
- if (border !== undefined) {
46
- styleOverride = { border }
47
- }
48
- if (opacity !== undefined) {
49
- styleOverride = { ...styleOverride, opacity }
50
- }
51
- if (styleOverride) {
52
- node = {
53
- ...node,
54
- isCustomized: true,
55
- style: {
56
- ...node.style,
57
- ...styleOverride
58
- }
59
- }
60
- }
61
- nodes[i] = node
62
- })
63
- }
64
- return nodes
65
- }
@@ -1,41 +0,0 @@
1
- import type { ComputedEdge, ComputedNode, ViewRule } from '@likec4/core'
2
- import { Expr } from '@likec4/core'
3
- import { isNullish, omitBy, pick } from 'remeda'
4
- import { flattenGroupRules } from './applyCustomElementProperties'
5
- import { relationExpressionToPredicates } from './relationExpressionToPredicates'
6
-
7
- export function applyCustomRelationProperties(
8
- _rules: ViewRule[],
9
- nodes: ComputedNode[],
10
- _edges: Iterable<ComputedEdge>
11
- ): ComputedEdge[] {
12
- const rules = _rules.flatMap(flattenGroupRules(Expr.isCustomRelationExpr))
13
- const edges = Array.from(_edges)
14
- if (rules.length === 0 || edges.length === 0) {
15
- return edges
16
- }
17
- for (
18
- const {
19
- customRelation: { relation, title, ...customprops }
20
- } of rules
21
- ) {
22
- const props = omitBy(customprops, isNullish)
23
- const satisfies = relationExpressionToPredicates(relation)
24
- edges.forEach((edge, i) => {
25
- const source = nodes.find(n => n.id === edge.source)
26
- const target = nodes.find(n => n.id === edge.target)
27
- if (!source || !target) {
28
- return
29
- }
30
- if (satisfies({ source, target, ...pick(edge, ['kind', 'tags']) })) {
31
- edges[i] = {
32
- ...edge,
33
- ...props,
34
- label: title ?? edge.label,
35
- isCustomized: true
36
- }
37
- }
38
- })
39
- }
40
- return edges
41
- }
@@ -1,49 +0,0 @@
1
- import { ComputedNode, type ViewRule } from '@likec4/core'
2
- import { Expr, isViewRuleStyle } from '@likec4/core'
3
- import { anyPass, filter, forEach, isDefined, isNot, pipe } from 'remeda'
4
- import { elementExprToPredicate } from './elementExpressionToPredicate'
5
-
6
- type Predicate<T> = (x: T) => boolean
7
-
8
- export function applyViewRuleStyles(_rules: ViewRule[], nodes: ComputedNode[]) {
9
- const rules = _rules.filter(isViewRuleStyle)
10
- if (rules.length === 0) {
11
- return nodes
12
- }
13
- for (const rule of rules) {
14
- const predicates = [] as Predicate<ComputedNode>[]
15
- for (const target of rule.targets) {
16
- predicates.push(elementExprToPredicate(target) as Predicate<ComputedNode>)
17
- }
18
- pipe(
19
- nodes,
20
- filter(isNot(ComputedNode.isNodesGroup)),
21
- filter(anyPass(predicates)),
22
- forEach(n => {
23
- n.shape = rule.style.shape ?? n.shape
24
- n.color = rule.style.color ?? n.color
25
- if (isDefined(rule.style.icon)) {
26
- n.icon = rule.style.icon
27
- }
28
- if (isDefined(rule.notation)) {
29
- n.notation = rule.notation
30
- }
31
- let styleOverride: ComputedNode['style'] | undefined
32
- if (isDefined(rule.style.border)) {
33
- styleOverride = { border: rule.style.border }
34
- }
35
- if (isDefined(rule.style.opacity)) {
36
- styleOverride = { ...styleOverride, opacity: rule.style.opacity }
37
- }
38
- if (styleOverride) {
39
- n.style = {
40
- ...n.style,
41
- ...styleOverride
42
- }
43
- }
44
- })
45
- )
46
- }
47
-
48
- return nodes
49
- }
@@ -1,113 +0,0 @@
1
- import type { ComputedNode, Element, Fqn, NodeId } from '@likec4/core'
2
- import {
3
- compareByFqnHierarchically,
4
- DefaultElementShape,
5
- DefaultThemeColor,
6
- ElementKind,
7
- nonNullable,
8
- parentFqn
9
- } from '@likec4/core'
10
- import { NodesGroup } from '../compute-view/compute'
11
-
12
- function updateDepthOfAncestors(node: ComputedNode, nodes: ReadonlyMap<Fqn, ComputedNode>) {
13
- let parentNd
14
- while (!!node.parent && (parentNd = nodes.get(node.parent))) {
15
- const depth = parentNd.depth ?? 1
16
- parentNd.depth = Math.max(depth, (node.depth ?? 0) + 1)
17
- if (parentNd.depth === depth) {
18
- // stop if we didn't change depth
19
- break
20
- }
21
- node = parentNd
22
- }
23
- }
24
-
25
- export function buildComputeNodes(elements: Iterable<Element>, groups?: NodesGroup[]) {
26
- const nodesMap = new Map<Fqn, ComputedNode>()
27
-
28
- const elementToGroup = new Map<Fqn, NodeId>()
29
-
30
- groups?.forEach(({ id, parent, viewRule, explicits }) => {
31
- if (parent) {
32
- nonNullable(nodesMap.get(parent), `Parent group node ${parent} not found`).children.push(id)
33
- }
34
- nodesMap.set(id, {
35
- id,
36
- parent,
37
- kind: ElementKind.Group,
38
- title: viewRule.title ?? '',
39
- color: viewRule.color ?? 'muted',
40
- shape: 'rectangle',
41
- children: [],
42
- inEdges: [],
43
- outEdges: [],
44
- level: 0,
45
- depth: 0,
46
- description: null,
47
- technology: null,
48
- tags: null,
49
- links: null,
50
- style: {
51
- border: viewRule.border ?? 'dashed',
52
- opacity: viewRule.opacity ?? 0
53
- }
54
- })
55
- for (const e of explicits) {
56
- elementToGroup.set(e.id, id)
57
- }
58
- })
59
-
60
- return (
61
- Array.from(elements)
62
- // Sort from Top to Bottom
63
- // So we can ensure that parent nodes are created before child nodes
64
- .sort(compareByFqnHierarchically)
65
- .reduce((map, { id, color, shape, style, kind, title, ...el }) => {
66
- let parent = parentFqn(id)
67
- let level = 0
68
- let parentNd: ComputedNode | undefined
69
- // Find the first ancestor that is already in the map
70
- while (parent) {
71
- parentNd = map.get(parent)
72
- if (parentNd) {
73
- break
74
- }
75
- parent = parentFqn(parent)
76
- }
77
- // If parent is not found in the map, check if it is in a group
78
- if (!parentNd && elementToGroup.has(id)) {
79
- parent = elementToGroup.get(id)!
80
- parentNd = map.get(parent)!
81
- }
82
- if (parentNd) {
83
- // if parent has no children and we are about to add first one
84
- // we need to set its depth to 1
85
- if (parentNd.children.length == 0) {
86
- parentNd.depth = 1
87
- // go up the tree and update depth of all parents
88
- updateDepthOfAncestors(parentNd, map)
89
- }
90
- parentNd.children.push(id)
91
- level = parentNd.level + 1
92
- }
93
- const node: ComputedNode = {
94
- id,
95
- parent,
96
- kind,
97
- title,
98
- color: color ?? DefaultThemeColor,
99
- shape: shape ?? DefaultElementShape,
100
- children: [],
101
- inEdges: [],
102
- outEdges: [],
103
- level,
104
- ...el,
105
- style: {
106
- ...style
107
- }
108
- }
109
- map.set(id, node)
110
- return map
111
- }, nodesMap) as ReadonlyMap<Fqn, ComputedNode>
112
- )
113
- }
@@ -1,63 +0,0 @@
1
- import type { ComputedNode, ElementNotation } from '@likec4/core'
2
- import { entries, flatMap, groupBy, map, mapValues, pipe, piped, prop, sortBy, unique } from 'remeda'
3
-
4
- /**
5
- * Build element notations from computed nodes:
6
- * 1. Group by notation
7
- * 2. Group by shape
8
- * 3. Group by color
9
- * 4. For each group get unique kinds
10
- * 5. Unwind the groups
11
- */
12
- export function buildElementNotations(nodes: ComputedNode[]): ElementNotation[] {
13
- return pipe(
14
- nodes,
15
- groupBy(prop('notation')),
16
- mapValues(
17
- piped(
18
- groupBy(prop('shape')),
19
- mapValues(
20
- piped(
21
- groupBy(prop('color')),
22
- mapValues(
23
- piped(
24
- map(prop('kind')),
25
- unique()
26
- )
27
- ),
28
- entries(),
29
- map(([color, kinds]) => ({
30
- kinds,
31
- color
32
- }))
33
- )
34
- ),
35
- entries(),
36
- flatMap(([shape, colors]) =>
37
- colors.map(({ color, kinds }) => ({
38
- shape,
39
- color,
40
- kinds
41
- }))
42
- )
43
- )
44
- ),
45
- entries(),
46
- flatMap(([title, shapes]) =>
47
- shapes.map(({ shape, color, kinds }) => ({
48
- title,
49
- shape,
50
- color,
51
- kinds
52
- }))
53
- ),
54
- sortBy(
55
- prop('shape'),
56
- prop('title'),
57
- [
58
- n => n.kinds.length,
59
- 'desc'
60
- ]
61
- )
62
- )
63
- }