@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.
Files changed (94) 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.DZRuJVSg.cjs → language-server.CO_nmHiL.cjs} +5605 -4215
  16. package/dist/shared/{language-server.DJo88TnT.d.cts → language-server.Da6ey08o.d.cts} +391 -110
  17. package/dist/shared/{language-server.PEjk7U9s.d.ts → language-server.De7S3e5Z.d.ts} +391 -110
  18. package/dist/shared/{language-server.BgDKnNok.d.mts → language-server.Dj4iDjtB.d.mts} +391 -110
  19. package/dist/shared/{language-server.B8qSDsWW.mjs → language-server.oO_9JoAG.mjs} +5594 -4215
  20. package/package.json +17 -31
  21. package/src/Rpc.ts +6 -3
  22. package/src/ast.ts +124 -71
  23. package/src/formatting/LikeC4Formatter.ts +9 -4
  24. package/src/generated/ast.ts +656 -40
  25. package/src/generated/grammar.ts +2 -2
  26. package/src/generated/module.ts +3 -2
  27. package/src/index.ts +1 -0
  28. package/src/like-c4.langium +170 -22
  29. package/src/logger.ts +7 -2
  30. package/src/lsp/CodeLensProvider.ts +0 -1
  31. package/src/lsp/CompletionProvider.ts +17 -2
  32. package/src/lsp/DocumentSymbolProvider.ts +5 -2
  33. package/src/lsp/HoverProvider.ts +34 -2
  34. package/src/lsp/SemanticTokenProvider.ts +58 -32
  35. package/src/model/deployments-index.ts +218 -0
  36. package/src/model/fqn-computation.ts +1 -1
  37. package/src/model/fqn-index.ts +0 -1
  38. package/src/model/index.ts +1 -0
  39. package/src/model/model-builder.ts +172 -37
  40. package/src/model/model-locator.ts +36 -7
  41. package/src/model/model-parser.ts +554 -92
  42. package/src/model-change/changeViewLayout.ts +2 -2
  43. package/src/module.ts +10 -4
  44. package/src/protocol.ts +10 -6
  45. package/src/references/index.ts +1 -0
  46. package/src/references/name-provider.ts +37 -0
  47. package/src/references/scope-computation.ts +130 -21
  48. package/src/references/scope-provider.ts +63 -36
  49. package/src/shared/NodeKindProvider.ts +15 -3
  50. package/src/utils/deploymentRef.ts +31 -0
  51. package/src/{elementRef.ts → utils/elementRef.ts} +1 -1
  52. package/src/utils/stringHash.ts +2 -2
  53. package/src/validation/_shared.ts +7 -5
  54. package/src/validation/deployment-checks.ts +144 -0
  55. package/src/validation/dynamic-view-step.ts +1 -1
  56. package/src/validation/index.ts +7 -0
  57. package/src/validation/relation.ts +1 -1
  58. package/src/validation/view-predicates/deployments.ts +56 -0
  59. package/src/validation/view-predicates/index.ts +1 -0
  60. package/src/view-utils/assignNavigateTo.ts +6 -5
  61. package/src/view-utils/index.ts +0 -1
  62. package/dist/model-graph/index.cjs +0 -10
  63. package/dist/model-graph/index.d.cts +0 -81
  64. package/dist/model-graph/index.d.mts +0 -81
  65. package/dist/model-graph/index.d.ts +0 -81
  66. package/dist/model-graph/index.mjs +0 -1
  67. package/dist/shared/language-server.BGGRJRnr.d.mts +0 -1338
  68. package/dist/shared/language-server.BXFhlTPo.mjs +0 -1953
  69. package/dist/shared/language-server.Bmpq16Gw.d.ts +0 -1338
  70. package/dist/shared/language-server.C1ZfM22X.d.cts +0 -1338
  71. package/dist/shared/language-server.N8HLDQqz.cjs +0 -1967
  72. package/src/model-graph/LikeC4ModelGraph.ts +0 -338
  73. package/src/model-graph/compute-view/__test__/fixture.ts +0 -630
  74. package/src/model-graph/compute-view/compute.ts +0 -788
  75. package/src/model-graph/compute-view/index.ts +0 -33
  76. package/src/model-graph/compute-view/predicates.ts +0 -509
  77. package/src/model-graph/dynamic-view/__test__/fixture.ts +0 -61
  78. package/src/model-graph/dynamic-view/compute.ts +0 -281
  79. package/src/model-graph/dynamic-view/index.ts +0 -29
  80. package/src/model-graph/index.ts +0 -3
  81. package/src/model-graph/utils/applyCustomElementProperties.ts +0 -65
  82. package/src/model-graph/utils/applyCustomRelationProperties.ts +0 -41
  83. package/src/model-graph/utils/applyViewRuleStyles.ts +0 -49
  84. package/src/model-graph/utils/buildComputeNodes.ts +0 -113
  85. package/src/model-graph/utils/buildElementNotations.ts +0 -63
  86. package/src/model-graph/utils/elementExpressionToPredicate.ts +0 -39
  87. package/src/model-graph/utils/relationExpressionToPredicates.ts +0 -43
  88. package/src/model-graph/utils/sortNodes.ts +0 -105
  89. package/src/model-graph/utils/uniqueTags.test.ts +0 -42
  90. package/src/model-graph/utils/uniqueTags.ts +0 -19
  91. package/src/utils/graphlib.ts +0 -9
  92. package/src/view-utils/resolve-extended-views.ts +0 -66
  93. package/src/view-utils/resolve-global-rules.ts +0 -88
  94. package/src/view-utils/view-hash.ts +0 -27
@@ -1,281 +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
- } = this.findRelations(source, target)
96
-
97
- const navigateTo = isTruthy(stepNavigateTo) && stepNavigateTo !== this.view.id ? stepNavigateTo : derivedNavigateTo
98
-
99
- this.steps.push({
100
- id,
101
- ...step,
102
- source,
103
- target,
104
- title: stepTitle ?? title,
105
- relations: relations ?? [],
106
- isBackward: isBackward ?? false,
107
- ...(navigateTo ? { navigateTo } : {}),
108
- ...(tags ? { tags } : {})
109
- })
110
- }
111
-
112
- protected compute(): ComputedDynamicView {
113
- const {
114
- docUri: _docUri, // exclude docUri
115
- rules: _rules, // exclude rules
116
- steps: viewSteps,
117
- ...view
118
- } = this.view
119
-
120
- let stepNum = 1
121
- for (const step of viewSteps) {
122
- if (isDynamicViewParallelSteps(step)) {
123
- if (step.__parallel.length === 0) {
124
- continue
125
- }
126
- if (step.__parallel.length === 1) {
127
- this.addStep(step.__parallel[0]!, stepNum)
128
- } else {
129
- step.__parallel.forEach((s, i) => this.addStep(s, i + 1, stepNum))
130
- }
131
- } else {
132
- this.addStep(step, stepNum)
133
- }
134
- stepNum++
135
- }
136
-
137
- const rules = resolveGlobalRulesInDynamicView(this.view, this.graph.globals)
138
-
139
- for (const rule of rules) {
140
- if (isDynamicViewIncludeRule(rule)) {
141
- for (const expr of rule.include) {
142
- const predicate = elementExprToPredicate(expr)
143
- this.graph.elements.filter(predicate).forEach(e => this.explicits.add(e))
144
- }
145
- }
146
- }
147
-
148
- const elements = [...this.explicits]
149
- const nodesMap = buildComputeNodes(elements)
150
-
151
- const edges = this.steps.map(({ source, target, relations, title, isBackward, ...step }) => {
152
- const sourceNode = nonNullable(nodesMap.get(source.id), `Source node ${source.id} not found`)
153
- const targetNode = nonNullable(nodesMap.get(target.id), `Target node ${target.id} not found`)
154
- const edge: ComputedEdge = {
155
- parent: commonAncestor(source.id, target.id),
156
- source: source.id,
157
- target: target.id,
158
- label: title,
159
- relations,
160
- color: DefaultRelationshipColor,
161
- line: DefaultLineStyle,
162
- head: DefaultArrowType,
163
- ...step
164
- }
165
- if (isBackward) {
166
- edge.dir = 'back'
167
- }
168
-
169
- while (edge.parent && !nodesMap.has(edge.parent)) {
170
- edge.parent = parentFqn(edge.parent)
171
- }
172
- sourceNode.outEdges.push(edge.id)
173
- targetNode.inEdges.push(edge.id)
174
- // Process edge source ancestors
175
- for (const sourceAncestor of ancestorsFqn(edge.source)) {
176
- if (sourceAncestor === edge.parent) {
177
- break
178
- }
179
- nodesMap.get(sourceAncestor)?.outEdges.push(edge.id)
180
- }
181
- // Process target hierarchy
182
- for (const targetAncestor of ancestorsFqn(edge.target)) {
183
- if (targetAncestor === edge.parent) {
184
- break
185
- }
186
- nodesMap.get(targetAncestor)?.inEdges.push(edge.id)
187
- }
188
- return edge
189
- })
190
-
191
- const nodes = applyCustomElementProperties(
192
- rules,
193
- applyViewRuleStyles(
194
- rules,
195
- // Keep order of elements
196
- elements.map(e => nonNullable(nodesMap.get(e.id)))
197
- )
198
- )
199
-
200
- const autoLayoutRule = rules.findLast(isViewRuleAutoLayout)
201
-
202
- const elementNotations = buildElementNotations(nodes)
203
-
204
- return calcViewLayoutHash({
205
- ...view,
206
- autoLayout: {
207
- direction: autoLayoutRule?.direction ?? 'LR',
208
- ...(autoLayoutRule?.nodeSep && { nodeSep: autoLayoutRule.nodeSep }),
209
- ...(autoLayoutRule?.rankSep && { rankSep: autoLayoutRule.rankSep })
210
- },
211
- nodes: map(nodes, n => {
212
- // omit notation
213
- delete n.notation
214
- if (n.icon === 'none') {
215
- delete n.icon
216
- }
217
- return n
218
- }),
219
- edges,
220
- ...(elementNotations.length > 0 && {
221
- notation: {
222
- elements: elementNotations
223
- }
224
- })
225
- })
226
- }
227
-
228
- private findRelations(source: Element, target: Element): {
229
- title: string | null
230
- tags: NonEmptyArray<Tag> | null
231
- relations: NonEmptyArray<RelationID> | null
232
- navigateTo: ViewID | null
233
- } {
234
- const relationships = unique(this.graph.edgesBetween(source, target).flatMap(e => e.relations))
235
- if (relationships.length === 0) {
236
- return {
237
- title: null,
238
- tags: null,
239
- relations: null,
240
- navigateTo: null
241
- }
242
- }
243
- const alltags = pipe(
244
- relationships,
245
- flatMap(r => r.tags),
246
- filter(isTruthy),
247
- unique()
248
- )
249
- const tags = hasAtLeast(alltags, 1) ? alltags : null
250
- const relations = hasAtLeast(relationships, 1) ? map(relationships, r => r.id) : null
251
-
252
- // Most closest relation
253
- const relation = only(relationships) || relationships.find(r => r.source === source.id && r.target === target.id)
254
-
255
- // This edge represents mutliple relations
256
- // We use label if only it is the same for all relations
257
- const title = isTruthy(relation?.title) ? relation.title : pipe(
258
- relationships,
259
- map(r => r.title),
260
- filter(isTruthy),
261
- unique(),
262
- only()
263
- )
264
-
265
- const navigateTo = !!relation?.navigateTo && relation.navigateTo !== this.view.id ? relation.navigateTo : pipe(
266
- relationships,
267
- map(r => r.navigateTo),
268
- filter(isTruthy),
269
- filter(v => v !== this.view.id),
270
- unique(),
271
- only()
272
- )
273
-
274
- return {
275
- title: title ?? null,
276
- tags,
277
- relations,
278
- navigateTo: navigateTo ?? null
279
- }
280
- }
281
- }
@@ -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
- }
@@ -1,39 +0,0 @@
1
- import { Expr, nonexhaustive, parentFqn } from '@likec4/core'
2
- import { type Element, whereOperatorAsPredicate } from '@likec4/core'
3
- import { isNullish } from 'remeda'
4
-
5
- type Predicate<T> = (x: T) => boolean
6
-
7
- export function elementExprToPredicate<T extends Pick<Element, 'id' | 'kind' | 'tags'>>(
8
- target: Expr.ElementPredicateExpression
9
- ): Predicate<T> {
10
- if (Expr.isElementWhere(target)) {
11
- const predicate = elementExprToPredicate(target.where.expr)
12
- const where = whereOperatorAsPredicate(target.where.condition)
13
- return n => predicate(n) && where(n)
14
- }
15
- if (Expr.isWildcard(target)) {
16
- return () => true
17
- }
18
- if (Expr.isElementKindExpr(target)) {
19
- return target.isEqual ? n => n.kind === target.elementKind : n => n.kind !== target.elementKind
20
- }
21
- if (Expr.isElementTagExpr(target)) {
22
- return target.isEqual
23
- ? ({ tags }) => !!tags && tags.includes(target.elementTag)
24
- : ({ tags }) => isNullish(tags) || !tags.includes(target.elementTag)
25
- }
26
- if (Expr.isExpandedElementExpr(target)) {
27
- return n => n.id === target.expanded || parentFqn(n.id) === target.expanded
28
- }
29
- if (Expr.isElementRef(target)) {
30
- const { element, isDescedants } = target
31
- return isDescedants
32
- ? n => n.id.startsWith(element + '.')
33
- : n => (n.id as string) === element
34
- }
35
- if (Expr.isCustomElement(target)) {
36
- return elementExprToPredicate(target.custom.expr)
37
- }
38
- nonexhaustive(target)
39
- }