@likec4/language-server 1.8.1 → 1.10.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 +21 -0
- package/dist/browser.d.cts +22 -0
- package/dist/browser.d.mts +22 -0
- package/dist/browser.d.ts +22 -0
- package/dist/browser.mjs +19 -0
- package/dist/index.cjs +10 -0
- package/dist/index.d.cts +18 -0
- package/dist/index.d.mts +18 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.mjs +1 -0
- package/dist/likec4lib.cjs +961 -0
- package/dist/likec4lib.d.cts +6 -0
- package/dist/likec4lib.d.mts +6 -0
- package/dist/likec4lib.d.ts +6 -0
- package/dist/likec4lib.mjs +957 -0
- package/dist/model-graph/index.cjs +10 -0
- package/dist/model-graph/index.d.cts +79 -0
- package/dist/model-graph/index.d.mts +79 -0
- package/dist/model-graph/index.d.ts +79 -0
- package/dist/model-graph/index.mjs +1 -0
- package/dist/node.cjs +18 -0
- package/dist/node.d.cts +20 -0
- package/dist/node.d.mts +20 -0
- package/dist/node.d.ts +20 -0
- package/dist/node.mjs +16 -0
- package/dist/protocol.cjs +25 -0
- package/dist/protocol.d.cts +43 -0
- package/dist/protocol.d.mts +43 -0
- package/dist/protocol.d.ts +43 -0
- package/dist/protocol.mjs +17 -0
- package/dist/shared/language-server.CjFzaJwI.d.cts +1223 -0
- package/dist/shared/language-server.CtKHXJDD.d.ts +1223 -0
- package/dist/shared/language-server.D-84I33F.d.mts +1223 -0
- package/dist/shared/language-server.DBJJUUgF.mjs +5737 -0
- package/dist/shared/language-server.DtBRb9os.mjs +1656 -0
- package/dist/shared/language-server.DwyCJvXm.cjs +1669 -0
- package/dist/shared/language-server.JWkqVjGv.cjs +5748 -0
- package/package.json +36 -20
- package/src/ast.ts +48 -36
- package/src/browser.ts +0 -3
- package/src/elementRef.ts +1 -1
- package/src/formatting/LikeC4Formatter.ts +388 -0
- package/src/formatting/utils.ts +26 -0
- package/src/generated/ast.ts +170 -12
- package/src/generated/grammar.ts +1 -1
- package/src/generated-lib/icons.ts +1 -1
- package/src/like-c4.langium +49 -8
- package/src/likec4lib.ts +2 -3
- package/src/logger.ts +9 -1
- package/src/lsp/DocumentLinkProvider.ts +27 -15
- package/src/lsp/RenameProvider.ts +8 -0
- package/src/lsp/SemanticTokenProvider.ts +20 -2
- package/src/lsp/index.ts +1 -0
- package/src/model/fqn-computation.ts +33 -23
- package/src/model/fqn-index.ts +5 -21
- package/src/model/model-builder.ts +180 -112
- package/src/model/model-locator.ts +1 -1
- package/src/model/model-parser-where.ts +3 -2
- package/src/model/model-parser.ts +99 -39
- package/src/model-graph/LikeC4ModelGraph.ts +42 -21
- package/src/model-graph/compute-view/__test__/fixture.ts +16 -14
- package/src/model-graph/compute-view/compute.ts +110 -81
- package/src/model-graph/compute-view/predicates.ts +6 -8
- package/src/model-graph/dynamic-view/__test__/fixture.ts +1 -0
- package/src/model-graph/dynamic-view/compute.ts +98 -61
- package/src/model-graph/utils/buildElementNotations.ts +1 -1
- package/src/model-graph/utils/elementExpressionToPredicate.ts +1 -1
- package/src/model-graph/utils/sortNodes.ts +2 -6
- package/src/module.ts +21 -4
- package/src/protocol.ts +4 -5
- package/src/references/scope-computation.ts +10 -1
- package/src/references/scope-provider.ts +2 -1
- package/src/shared/NodeKindProvider.ts +73 -34
- package/src/test/setup.ts +3 -8
- package/src/test/testServices.ts +27 -7
- package/src/utils/graphlib.ts +11 -0
- package/src/validation/index.ts +2 -1
- package/src/validation/property-checks.ts +13 -1
- package/src/validation/specification.ts +3 -3
- package/src/view-utils/manual-layout.ts +1 -1
- package/src/view-utils/resolve-extended-views.ts +19 -10
- package/src/view-utils/resolve-relative-paths.ts +19 -24
- package/src/view-utils/view-hash.ts +1 -1
- package/src/reset.d.ts +0 -2
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type HexColorLiteral, invariant, isNonEmptyArray, nonexhaustive } from '@likec4/core'
|
|
2
|
+
import type * as c4 from '@likec4/core'
|
|
2
3
|
import type { AstNode, LangiumDocument } from 'langium'
|
|
3
4
|
import { AstUtils, CstUtils } from 'langium'
|
|
4
|
-
import { filter, flatMap, isDefined, isNonNullish, isTruthy, mapToObj, pipe } from 'remeda'
|
|
5
|
+
import { filter, first, flatMap, isDefined, isNonNullish, isTruthy, map, mapToObj, pipe } from 'remeda'
|
|
5
6
|
import stripIndent from 'strip-indent'
|
|
6
7
|
import type { Writable } from 'type-fest'
|
|
7
8
|
import type {
|
|
@@ -23,6 +24,7 @@ import {
|
|
|
23
24
|
resolveRelationPoints,
|
|
24
25
|
streamModel,
|
|
25
26
|
toAutoLayout,
|
|
27
|
+
toColor,
|
|
26
28
|
toElementStyle,
|
|
27
29
|
toRelationshipStyleExcludeDefaults,
|
|
28
30
|
ViewOps
|
|
@@ -67,7 +69,7 @@ export class LikeC4ModelParser {
|
|
|
67
69
|
try {
|
|
68
70
|
result.push(this.parseLikeC4Document(doc))
|
|
69
71
|
} catch (cause) {
|
|
70
|
-
logError(new
|
|
72
|
+
logError(new Error(`Error parsing document ${doc.uri.toString()}`, { cause }))
|
|
71
73
|
}
|
|
72
74
|
}
|
|
73
75
|
return result
|
|
@@ -89,20 +91,23 @@ export class LikeC4ModelParser {
|
|
|
89
91
|
const element_specs = specifications.flatMap(s => s.elements.filter(isValid))
|
|
90
92
|
for (const { kind, props } of element_specs) {
|
|
91
93
|
try {
|
|
92
|
-
const style = props.find(ast.isElementStyleProperty)
|
|
93
94
|
const kindName = kind.name as c4.ElementKind
|
|
94
|
-
if (kindName
|
|
95
|
+
if (!isTruthy(kindName)) {
|
|
96
|
+
continue
|
|
97
|
+
}
|
|
98
|
+
if (kindName in c4Specification.elements) {
|
|
95
99
|
logger.warn(`Element kind "${kindName}" is already defined`)
|
|
96
100
|
continue
|
|
97
101
|
}
|
|
102
|
+
const style = props.find(ast.isElementStyleProperty)
|
|
98
103
|
const bodyProps = mapToObj(
|
|
99
104
|
props.filter(ast.isSpecificationElementStringProperty).filter(p => isNonNullish(p.value)) ?? [],
|
|
100
|
-
p => [p.key, removeIndent(p.value)]
|
|
105
|
+
p => [p.key, removeIndent(p.value)] as const
|
|
101
106
|
)
|
|
102
|
-
c4Specification.
|
|
107
|
+
c4Specification.elements[kindName] = {
|
|
103
108
|
...bodyProps,
|
|
104
109
|
style: {
|
|
105
|
-
...toElementStyle(style?.props)
|
|
110
|
+
...toElementStyle(style?.props, isValid)
|
|
106
111
|
}
|
|
107
112
|
}
|
|
108
113
|
} catch (e) {
|
|
@@ -114,13 +119,16 @@ export class LikeC4ModelParser {
|
|
|
114
119
|
for (const { kind, props } of relations_specs) {
|
|
115
120
|
try {
|
|
116
121
|
const kindName = kind.name as c4.RelationshipKind
|
|
122
|
+
if (!isTruthy(kindName)) {
|
|
123
|
+
continue
|
|
124
|
+
}
|
|
117
125
|
if (kindName in c4Specification.relationships) {
|
|
118
126
|
logger.warn(`Relationship kind "${kindName}" is already defined`)
|
|
119
127
|
continue
|
|
120
128
|
}
|
|
121
129
|
const bodyProps = mapToObj(
|
|
122
130
|
props.filter(ast.isSpecificationRelationshipStringProperty).filter(p => isNonNullish(p.value)) ?? [],
|
|
123
|
-
p => [p.key, p.value]
|
|
131
|
+
p => [p.key, removeIndent(p.value)]
|
|
124
132
|
)
|
|
125
133
|
c4Specification.relationships[kindName] = {
|
|
126
134
|
...bodyProps,
|
|
@@ -130,13 +138,38 @@ export class LikeC4ModelParser {
|
|
|
130
138
|
logWarnError(e)
|
|
131
139
|
}
|
|
132
140
|
}
|
|
141
|
+
|
|
142
|
+
const tags_specs = specifications.flatMap(s => s.tags.filter(isValid))
|
|
143
|
+
for (const tagSpec of tags_specs) {
|
|
144
|
+
const tag = tagSpec.tag.name as c4.Tag
|
|
145
|
+
if (isTruthy(tag)) {
|
|
146
|
+
c4Specification.tags.add(tag)
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const colors_specs = specifications.flatMap(s => s.colors.filter(isValid))
|
|
151
|
+
for (const { name, color } of colors_specs) {
|
|
152
|
+
try {
|
|
153
|
+
const colorName = name.name as c4.CustomColor
|
|
154
|
+
if (colorName in c4Specification.colors) {
|
|
155
|
+
logger.warn(`Custom color "${colorName}" is already defined`)
|
|
156
|
+
continue
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
c4Specification.colors[colorName] = {
|
|
160
|
+
color: color as HexColorLiteral
|
|
161
|
+
}
|
|
162
|
+
} catch (e) {
|
|
163
|
+
logWarnError(e)
|
|
164
|
+
}
|
|
165
|
+
}
|
|
133
166
|
}
|
|
134
167
|
|
|
135
168
|
private parseModel(doc: ParsedLikeC4LangiumDocument, isValid: IsValidFn) {
|
|
136
169
|
for (const el of streamModel(doc, isValid)) {
|
|
137
170
|
if (ast.isElement(el)) {
|
|
138
171
|
try {
|
|
139
|
-
doc.c4Elements.push(this.parseElement(el))
|
|
172
|
+
doc.c4Elements.push(this.parseElement(el, isValid))
|
|
140
173
|
} catch (e) {
|
|
141
174
|
logWarnError(e)
|
|
142
175
|
}
|
|
@@ -154,12 +187,12 @@ export class LikeC4ModelParser {
|
|
|
154
187
|
}
|
|
155
188
|
}
|
|
156
189
|
|
|
157
|
-
private parseElement(astNode: ast.Element): ParsedAstElement {
|
|
190
|
+
private parseElement(astNode: ast.Element, isValid: IsValidFn): ParsedAstElement {
|
|
158
191
|
const id = this.resolveFqn(astNode)
|
|
159
192
|
const kind = astNode.kind.$refText as c4.ElementKind
|
|
160
193
|
const tags = this.convertTags(astNode.body)
|
|
161
194
|
const stylePropsAst = astNode.body?.props.find(ast.isElementStyleProperty)?.props
|
|
162
|
-
const style = toElementStyle(stylePropsAst)
|
|
195
|
+
const style = toElementStyle(stylePropsAst, isValid)
|
|
163
196
|
const metadata = this.getMetadata(astNode.body?.props.find(ast.isMetadataProperty))
|
|
164
197
|
const astPath = this.getAstNodePath(astNode)
|
|
165
198
|
|
|
@@ -214,6 +247,14 @@ export class LikeC4ModelParser {
|
|
|
214
247
|
p => [p.key, p.value]
|
|
215
248
|
)
|
|
216
249
|
|
|
250
|
+
const navigateTo = pipe(
|
|
251
|
+
astNode.body?.props ?? [],
|
|
252
|
+
filter(ast.isRelationNavigateToProperty),
|
|
253
|
+
map(p => p.value.view.ref?.name),
|
|
254
|
+
filter(isTruthy),
|
|
255
|
+
first()
|
|
256
|
+
)
|
|
257
|
+
|
|
217
258
|
const title = removeIndent(astNode.title ?? bodyProps.title) ?? ''
|
|
218
259
|
const description = removeIndent(bodyProps.description)
|
|
219
260
|
const technology = removeIndent(astNode.technology) ?? toSingleLine(bodyProps.technology)
|
|
@@ -236,7 +277,8 @@ export class LikeC4ModelParser {
|
|
|
236
277
|
...(kind && { kind }),
|
|
237
278
|
...(tags && { tags }),
|
|
238
279
|
...(isNonEmptyArray(links) && { links }),
|
|
239
|
-
...toRelationshipStyleExcludeDefaults(styleProp?.props)
|
|
280
|
+
...toRelationshipStyleExcludeDefaults(styleProp?.props),
|
|
281
|
+
...(navigateTo && { navigateTo: navigateTo as c4.ViewID })
|
|
240
282
|
}
|
|
241
283
|
}
|
|
242
284
|
|
|
@@ -392,8 +434,9 @@ export class LikeC4ModelParser {
|
|
|
392
434
|
return acc
|
|
393
435
|
}
|
|
394
436
|
if (ast.isColorProperty(prop)) {
|
|
395
|
-
|
|
396
|
-
|
|
437
|
+
const value = toColor(prop)
|
|
438
|
+
if (isDefined(value)) {
|
|
439
|
+
acc.custom[prop.key] = value
|
|
397
440
|
}
|
|
398
441
|
return acc
|
|
399
442
|
}
|
|
@@ -480,7 +523,7 @@ export class LikeC4ModelParser {
|
|
|
480
523
|
const props = astNode.custom?.props ?? []
|
|
481
524
|
return props.reduce(
|
|
482
525
|
(acc, prop) => {
|
|
483
|
-
if (ast.isRelationStringProperty(prop)) {
|
|
526
|
+
if (ast.isRelationStringProperty(prop) || ast.isNotationProperty(prop) || ast.isNotesProperty(prop)) {
|
|
484
527
|
if (isDefined(prop.value)) {
|
|
485
528
|
acc.customRelation[prop.key] = removeIndent(prop.value) ?? ''
|
|
486
529
|
}
|
|
@@ -493,8 +536,9 @@ export class LikeC4ModelParser {
|
|
|
493
536
|
return acc
|
|
494
537
|
}
|
|
495
538
|
if (ast.isColorProperty(prop)) {
|
|
496
|
-
|
|
497
|
-
|
|
539
|
+
const value = toColor(prop)
|
|
540
|
+
if (isTruthy(value)) {
|
|
541
|
+
acc.customRelation[prop.key] = value
|
|
498
542
|
}
|
|
499
543
|
return acc
|
|
500
544
|
}
|
|
@@ -504,9 +548,10 @@ export class LikeC4ModelParser {
|
|
|
504
548
|
}
|
|
505
549
|
return acc
|
|
506
550
|
}
|
|
507
|
-
if (ast.
|
|
508
|
-
|
|
509
|
-
|
|
551
|
+
if (ast.isRelationNavigateToProperty(prop)) {
|
|
552
|
+
const viewId = prop.value.view.ref?.name
|
|
553
|
+
if (isTruthy(viewId)) {
|
|
554
|
+
acc.customRelation.navigateTo = viewId as c4.ViewID
|
|
510
555
|
}
|
|
511
556
|
return acc
|
|
512
557
|
}
|
|
@@ -551,7 +596,7 @@ export class LikeC4ModelParser {
|
|
|
551
596
|
return this.parseViewRulePredicate(astRule, isValid)
|
|
552
597
|
}
|
|
553
598
|
if (ast.isViewRuleStyle(astRule)) {
|
|
554
|
-
const styleProps = toElementStyle(astRule.props.filter(ast.isStyleProperty))
|
|
599
|
+
const styleProps = toElementStyle(astRule.props.filter(ast.isStyleProperty), isValid)
|
|
555
600
|
const notation = removeIndent(astRule.props.find(ast.isNotationProperty)?.value)
|
|
556
601
|
const targets = this.parseElementExpressionsIterator(astRule.target)
|
|
557
602
|
return {
|
|
@@ -589,6 +634,12 @@ export class LikeC4ModelParser {
|
|
|
589
634
|
}
|
|
590
635
|
}
|
|
591
636
|
|
|
637
|
+
private parseDynamicParallelSteps(node: ast.DynamicViewParallelSteps): c4.DynamicViewParallelSteps {
|
|
638
|
+
return {
|
|
639
|
+
__parallel: node.steps.map(step => this.parseDynamicStep(step))
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
|
|
592
643
|
private parseDynamicStep(node: ast.DynamicViewStep): c4.DynamicViewStep {
|
|
593
644
|
const sourceEl = elementRef(node.source)
|
|
594
645
|
if (!sourceEl) {
|
|
@@ -600,9 +651,7 @@ export class LikeC4ModelParser {
|
|
|
600
651
|
}
|
|
601
652
|
let source = this.resolveFqn(sourceEl)
|
|
602
653
|
let target = this.resolveFqn(targetEl)
|
|
603
|
-
const title = removeIndent(
|
|
604
|
-
node.title ?? node.custom?.props.find((p): p is ast.RelationStringProperty => p.key === 'title')?.value
|
|
605
|
-
) ?? ''
|
|
654
|
+
const title = removeIndent(node.title) ?? null
|
|
606
655
|
|
|
607
656
|
let step: Writable<c4.DynamicViewStep> = {
|
|
608
657
|
source,
|
|
@@ -620,28 +669,35 @@ export class LikeC4ModelParser {
|
|
|
620
669
|
if (Array.isArray(node.custom?.props)) {
|
|
621
670
|
for (const prop of node.custom.props) {
|
|
622
671
|
try {
|
|
623
|
-
if (ast.isRelationStringProperty(prop)) {
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
step[prop.key] = value
|
|
672
|
+
if (ast.isRelationStringProperty(prop) || ast.isNotationProperty(prop) || ast.isNotesProperty(prop)) {
|
|
673
|
+
if (isDefined(prop.value)) {
|
|
674
|
+
step[prop.key] = removeIndent(prop.value) ?? ''
|
|
627
675
|
}
|
|
628
676
|
continue
|
|
629
677
|
}
|
|
630
678
|
if (ast.isArrowProperty(prop)) {
|
|
631
|
-
|
|
679
|
+
if (isDefined(prop.value)) {
|
|
680
|
+
step[prop.key] = prop.value
|
|
681
|
+
}
|
|
632
682
|
continue
|
|
633
683
|
}
|
|
634
684
|
if (ast.isColorProperty(prop)) {
|
|
635
|
-
|
|
685
|
+
const value = toColor(prop)
|
|
686
|
+
if (isDefined(value)) {
|
|
687
|
+
step[prop.key] = value
|
|
688
|
+
}
|
|
636
689
|
continue
|
|
637
690
|
}
|
|
638
691
|
if (ast.isLineProperty(prop)) {
|
|
639
|
-
|
|
692
|
+
if (isDefined(prop.value)) {
|
|
693
|
+
step[prop.key] = prop.value
|
|
694
|
+
}
|
|
640
695
|
continue
|
|
641
696
|
}
|
|
642
|
-
if (ast.
|
|
643
|
-
|
|
644
|
-
|
|
697
|
+
if (ast.isRelationNavigateToProperty(prop)) {
|
|
698
|
+
const viewId = prop.value.view.ref?.name
|
|
699
|
+
if (isTruthy(viewId)) {
|
|
700
|
+
step.navigateTo = viewId as c4.ViewID
|
|
645
701
|
}
|
|
646
702
|
continue
|
|
647
703
|
}
|
|
@@ -778,7 +834,7 @@ export class LikeC4ModelParser {
|
|
|
778
834
|
return acc
|
|
779
835
|
}
|
|
780
836
|
if (ast.isViewRuleStyle(n)) {
|
|
781
|
-
const styleProps = toElementStyle(n.props.filter(ast.isStyleProperty))
|
|
837
|
+
const styleProps = toElementStyle(n.props.filter(ast.isStyleProperty), isValid)
|
|
782
838
|
const notation = removeIndent(n.props.find(ast.isNotationProperty)?.value)
|
|
783
839
|
const targets = this.parseElementExpressionsIterator(n.target)
|
|
784
840
|
if (targets.length > 0) {
|
|
@@ -807,13 +863,17 @@ export class LikeC4ModelParser {
|
|
|
807
863
|
steps: body.steps.reduce((acc, n) => {
|
|
808
864
|
try {
|
|
809
865
|
if (isValid(n)) {
|
|
810
|
-
|
|
866
|
+
if (ast.isDynamicViewParallelSteps(n)) {
|
|
867
|
+
acc.push(this.parseDynamicParallelSteps(n))
|
|
868
|
+
} else {
|
|
869
|
+
acc.push(this.parseDynamicStep(n))
|
|
870
|
+
}
|
|
811
871
|
}
|
|
812
872
|
} catch (e) {
|
|
813
873
|
logWarnError(e)
|
|
814
874
|
}
|
|
815
875
|
return acc
|
|
816
|
-
}, [] as c4.
|
|
876
|
+
}, [] as c4.DynamicViewStepOrParallel[]),
|
|
817
877
|
...(manualLayout && { manualLayout })
|
|
818
878
|
}
|
|
819
879
|
}
|
|
@@ -845,7 +905,7 @@ export class LikeC4ModelParser {
|
|
|
845
905
|
const tags = [] as c4.Tag[]
|
|
846
906
|
while (iter) {
|
|
847
907
|
try {
|
|
848
|
-
const values = iter.values.map(t => t.ref?.name).filter(
|
|
908
|
+
const values = iter.values.map(t => t.ref?.name).filter(isTruthy) as c4.Tag[]
|
|
849
909
|
if (values.length > 0) {
|
|
850
910
|
tags.unshift(...values)
|
|
851
911
|
}
|
|
@@ -3,14 +3,13 @@ import {
|
|
|
3
3
|
commonAncestor,
|
|
4
4
|
type Element,
|
|
5
5
|
type Fqn,
|
|
6
|
-
InvalidModelError,
|
|
7
6
|
invariant,
|
|
8
7
|
isSameHierarchy,
|
|
9
|
-
isString,
|
|
10
8
|
parentFqn,
|
|
11
9
|
type Relation,
|
|
12
10
|
type RelationID
|
|
13
11
|
} from '@likec4/core'
|
|
12
|
+
import { isArray, isString } from 'remeda'
|
|
14
13
|
|
|
15
14
|
type Params = {
|
|
16
15
|
elements: Record<Fqn, Element>
|
|
@@ -25,7 +24,7 @@ type RelationEdge = {
|
|
|
25
24
|
}
|
|
26
25
|
|
|
27
26
|
type FqnOrElement = Fqn | Element
|
|
28
|
-
type FqnsOrElements = Fqn
|
|
27
|
+
type FqnsOrElements = ReadonlyArray<Fqn> | ReadonlyArray<Element>
|
|
29
28
|
|
|
30
29
|
const RelationsSet = Set<Relation>
|
|
31
30
|
const MapRelations = Map<Fqn, Set<Relation>>
|
|
@@ -37,9 +36,16 @@ function intersection<T>(a: Set<T>, b: Set<T>) {
|
|
|
37
36
|
return new Set([...a].filter(value => b.has(value)))
|
|
38
37
|
}
|
|
39
38
|
|
|
39
|
+
/**
|
|
40
|
+
* Used only for views calculations.
|
|
41
|
+
* Subject to change.
|
|
42
|
+
*/
|
|
40
43
|
export class LikeC4ModelGraph {
|
|
41
44
|
#elements = new Map<Fqn, Element>()
|
|
42
|
-
|
|
45
|
+
// Parent element for given FQN
|
|
46
|
+
#parents = new Map<Fqn, Element>()
|
|
47
|
+
// Children elements for given FQN
|
|
48
|
+
#children = new Map<Fqn, Element[]>()
|
|
43
49
|
#rootElements = new Set<Element>()
|
|
44
50
|
|
|
45
51
|
#relations = new Map<RelationID, Relation>()
|
|
@@ -47,7 +53,7 @@ export class LikeC4ModelGraph {
|
|
|
47
53
|
#incoming = new MapRelations()
|
|
48
54
|
// Outgoing from an element or its descendants
|
|
49
55
|
#outgoing = new MapRelations()
|
|
50
|
-
// Relationships inside the element descendants
|
|
56
|
+
// Relationships inside the element, among descendants
|
|
51
57
|
#internal = new MapRelations()
|
|
52
58
|
|
|
53
59
|
#cacheAscendingSiblings = new Map<Fqn, Element[]>()
|
|
@@ -80,7 +86,7 @@ export class LikeC4ModelGraph {
|
|
|
80
86
|
}
|
|
81
87
|
|
|
82
88
|
public children(id: Fqn) {
|
|
83
|
-
return this._childrenOf(id).
|
|
89
|
+
return this._childrenOf(id).slice()
|
|
84
90
|
}
|
|
85
91
|
|
|
86
92
|
// Get children or element itself if no children
|
|
@@ -93,19 +99,30 @@ export class LikeC4ModelGraph {
|
|
|
93
99
|
public siblings(element: Fqn | Element) {
|
|
94
100
|
const id = isString(element) ? element : element.id
|
|
95
101
|
const parent = parentFqn(id)
|
|
96
|
-
const
|
|
97
|
-
return
|
|
102
|
+
const siblings = parent ? this._childrenOf(parent) : this.rootElements
|
|
103
|
+
return siblings.filter(e => e.id !== id)
|
|
98
104
|
}
|
|
99
105
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
106
|
+
/**
|
|
107
|
+
* Get all ancestor elements (i.e. parent, parent’s parent, etc.)
|
|
108
|
+
* (from closest to root)
|
|
109
|
+
*/
|
|
110
|
+
public ancestors(element: Fqn | Element): Array<Element> {
|
|
111
|
+
let id = isString(element) ? element : element.id
|
|
112
|
+
const result = [] as Element[]
|
|
113
|
+
let parent
|
|
114
|
+
while (parent = this.#parents.get(id)) {
|
|
115
|
+
result.push(parent)
|
|
116
|
+
id = parent.id
|
|
117
|
+
}
|
|
118
|
+
return result as Array<Element>
|
|
103
119
|
}
|
|
104
120
|
|
|
105
121
|
/**
|
|
106
122
|
* Resolve siblings of the element and its ancestors
|
|
123
|
+
* (from closest to root)
|
|
107
124
|
*/
|
|
108
|
-
public ascendingSiblings(element: Fqn | Element) {
|
|
125
|
+
public ascendingSiblings(element: Fqn | Element): Array<Element> {
|
|
109
126
|
const id = isString(element) ? element : element.id
|
|
110
127
|
let siblings = this.#cacheAscendingSiblings.get(id)
|
|
111
128
|
if (!siblings) {
|
|
@@ -121,7 +138,10 @@ export class LikeC4ModelGraph {
|
|
|
121
138
|
/**
|
|
122
139
|
* Resolve all RelationEdges between element and others (any direction)
|
|
123
140
|
*/
|
|
124
|
-
public anyEdgesBetween(
|
|
141
|
+
public anyEdgesBetween(
|
|
142
|
+
_element: Fqn | Element,
|
|
143
|
+
others: ReadonlyArray<Fqn> | ReadonlyArray<Element>
|
|
144
|
+
): Array<RelationEdge> {
|
|
125
145
|
if (others.length === 0) {
|
|
126
146
|
return []
|
|
127
147
|
}
|
|
@@ -169,7 +189,7 @@ export class LikeC4ModelGraph {
|
|
|
169
189
|
/**
|
|
170
190
|
* Resolve all RelationEdges between elements (any direction)
|
|
171
191
|
*/
|
|
172
|
-
public edgesWithin<T extends Fqn[] | Element[]>(elements: T): RelationEdge
|
|
192
|
+
public edgesWithin<T extends Fqn[] | Element[]>(elements: T): Array<RelationEdge> {
|
|
173
193
|
if (elements.length < 2) {
|
|
174
194
|
return []
|
|
175
195
|
}
|
|
@@ -190,8 +210,8 @@ export class LikeC4ModelGraph {
|
|
|
190
210
|
_sources: FqnOrElement | FqnsOrElements,
|
|
191
211
|
_targets: FqnOrElement | FqnsOrElements
|
|
192
212
|
) {
|
|
193
|
-
const sources =
|
|
194
|
-
const targets =
|
|
213
|
+
const sources = isArray(_sources) ? _sources : [_sources]
|
|
214
|
+
const targets = isArray(_targets) ? _targets : [_targets]
|
|
195
215
|
if (sources.length === 0 || targets.length === 0) {
|
|
196
216
|
return []
|
|
197
217
|
}
|
|
@@ -226,12 +246,13 @@ export class LikeC4ModelGraph {
|
|
|
226
246
|
|
|
227
247
|
private addElement(el: Element) {
|
|
228
248
|
if (this.#elements.has(el.id)) {
|
|
229
|
-
throw new
|
|
249
|
+
throw new Error(`Element ${el.id} already exists`)
|
|
230
250
|
}
|
|
231
251
|
this.#elements.set(el.id, el)
|
|
232
|
-
const
|
|
233
|
-
if (
|
|
234
|
-
this.
|
|
252
|
+
const parentId = parentFqn(el.id)
|
|
253
|
+
if (parentId) {
|
|
254
|
+
this.#parents.set(el.id, this.element(parentId))
|
|
255
|
+
this._childrenOf(parentId).push(el)
|
|
235
256
|
} else {
|
|
236
257
|
this.#rootElements.add(el)
|
|
237
258
|
}
|
|
@@ -239,7 +260,7 @@ export class LikeC4ModelGraph {
|
|
|
239
260
|
|
|
240
261
|
private addRelation(rel: Relation) {
|
|
241
262
|
if (this.#relations.has(rel.id)) {
|
|
242
|
-
throw new
|
|
263
|
+
throw new Error(`Relation ${rel.id} already exists`)
|
|
243
264
|
}
|
|
244
265
|
this.#relations.set(rel.id, rel)
|
|
245
266
|
this._incomingTo(rel.target).add(rel)
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
type BorderStyle,
|
|
3
|
+
type Color,
|
|
3
4
|
type ComputedView,
|
|
4
5
|
type CustomElementExpr as C4CustomElementExpr,
|
|
5
6
|
type CustomRelationExpr as C4CustomRelationExpr,
|
|
@@ -17,7 +18,6 @@ import {
|
|
|
17
18
|
type RelationshipLineType,
|
|
18
19
|
type RelationWhereExpr,
|
|
19
20
|
type Tag,
|
|
20
|
-
type ThemeColor,
|
|
21
21
|
type ViewID,
|
|
22
22
|
type ViewRule,
|
|
23
23
|
type ViewRulePredicate,
|
|
@@ -210,28 +210,29 @@ export const fakeElements = {
|
|
|
210
210
|
|
|
211
211
|
export type FakeElementIds = keyof typeof fakeElements
|
|
212
212
|
|
|
213
|
-
const rel = ({
|
|
213
|
+
const rel = <Source extends FakeElementIds, Target extends FakeElementIds>({
|
|
214
214
|
source,
|
|
215
215
|
target,
|
|
216
216
|
title,
|
|
217
217
|
...props
|
|
218
218
|
}: {
|
|
219
|
-
source:
|
|
220
|
-
target:
|
|
219
|
+
source: Source
|
|
220
|
+
target: Target
|
|
221
221
|
title?: string
|
|
222
222
|
kind?: string
|
|
223
|
-
color?:
|
|
223
|
+
color?: Color
|
|
224
224
|
line?: RelationshipLineType
|
|
225
225
|
head?: RelationshipArrowType
|
|
226
226
|
tail?: RelationshipArrowType
|
|
227
227
|
tags?: NonEmptyArray<TestTag>
|
|
228
|
-
})
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
228
|
+
}) =>
|
|
229
|
+
({
|
|
230
|
+
id: `${source}:${target}` as RelationID,
|
|
231
|
+
title: title ?? '',
|
|
232
|
+
source: source as Fqn,
|
|
233
|
+
target: target as Fqn,
|
|
234
|
+
...(props as any)
|
|
235
|
+
}) as Omit<Relation, 'id'> & { id: `${Source}:${Target}` }
|
|
235
236
|
|
|
236
237
|
export const fakeRelations = [
|
|
237
238
|
rel({
|
|
@@ -321,7 +322,7 @@ export const fakeRelations = [
|
|
|
321
322
|
})
|
|
322
323
|
]
|
|
323
324
|
|
|
324
|
-
export type FakeRelationIds =
|
|
325
|
+
export type FakeRelationIds = (typeof fakeRelations)[number]['id']
|
|
325
326
|
|
|
326
327
|
export const fakeModel = new LikeC4ModelGraph({
|
|
327
328
|
elements: fakeElements,
|
|
@@ -335,6 +336,7 @@ const emptyView = {
|
|
|
335
336
|
description: null,
|
|
336
337
|
tags: null,
|
|
337
338
|
links: null,
|
|
339
|
+
customColorDefinitions: {},
|
|
338
340
|
rules: []
|
|
339
341
|
}
|
|
340
342
|
|
|
@@ -370,7 +372,7 @@ export function $custom(
|
|
|
370
372
|
description?: string
|
|
371
373
|
technology?: string
|
|
372
374
|
shape?: ElementShape
|
|
373
|
-
color?:
|
|
375
|
+
color?: Color
|
|
374
376
|
border?: BorderStyle
|
|
375
377
|
icon?: string
|
|
376
378
|
opacity?: number
|