@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,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
|
-
}
|
package/src/model-graph/index.ts
DELETED
|
@@ -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
|
-
}
|