@likec4/language-server 1.15.0 → 1.16.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 (47) 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/model-graph/index.cjs +1 -1
  13. package/dist/model-graph/index.d.cts +4 -2
  14. package/dist/model-graph/index.d.mts +4 -2
  15. package/dist/model-graph/index.d.ts +4 -2
  16. package/dist/model-graph/index.mjs +1 -1
  17. package/dist/shared/language-server.7iILaJYc.d.ts +1338 -0
  18. package/dist/shared/{language-server.80ITEDo5.cjs → language-server.Bd3NZ8uH.cjs} +130 -76
  19. package/dist/shared/{language-server.DXC9g4_f.mjs → language-server.C5gxpVUH.mjs} +132 -78
  20. package/dist/shared/language-server.CmBZHwSl.d.cts +1338 -0
  21. package/dist/shared/{language-server.U2piOAVt.d.cts → language-server.CnkCWVtf.d.cts} +44 -10
  22. package/dist/shared/{language-server.DfMwkd2l.d.mts → language-server.DIaiY0-C.d.mts} +44 -10
  23. package/dist/shared/language-server.DViE1Zxi.d.mts +1338 -0
  24. package/dist/shared/{language-server.zY53FGJE.mjs → language-server.D_13fWJQ.mjs} +216 -94
  25. package/dist/shared/{language-server.j-ShR6as.d.ts → language-server.DwyQ1FtY.d.ts} +44 -10
  26. package/dist/shared/{language-server.BUtiWTKg.cjs → language-server.Dym6GL4P.cjs} +214 -92
  27. package/package.json +8 -8
  28. package/src/ast.ts +11 -3
  29. package/src/formatting/LikeC4Formatter.ts +44 -4
  30. package/src/generated/ast.ts +111 -10
  31. package/src/generated/grammar.ts +1 -1
  32. package/src/like-c4.langium +19 -0
  33. package/src/lsp/SemanticTokenProvider.ts +3 -1
  34. package/src/model/model-builder.ts +34 -32
  35. package/src/model/model-parser.ts +91 -18
  36. package/src/model-graph/LikeC4ModelGraph.ts +22 -11
  37. package/src/model-graph/compute-view/__test__/fixture.ts +114 -44
  38. package/src/model-graph/compute-view/compute.ts +77 -72
  39. package/src/model-graph/dynamic-view/compute.ts +4 -1
  40. package/src/model-graph/utils/applyCustomRelationProperties.ts +7 -38
  41. package/src/model-graph/utils/applyViewRuleStyles.ts +0 -4
  42. package/src/model-graph/utils/relationExpressionToPredicates.ts +43 -0
  43. package/src/references/scope-computation.ts +10 -0
  44. package/src/validation/index.ts +3 -0
  45. package/src/validation/specification.ts +21 -0
  46. package/src/view-utils/index.ts +0 -1
  47. package/src/view-utils/resolve-global-rules.ts +66 -50
@@ -8,6 +8,7 @@ import {
8
8
  ElementKind,
9
9
  type ElementPredicateExpression,
10
10
  type ElementView,
11
+ type Fqn,
11
12
  type NodeId,
12
13
  type NonEmptyArray,
13
14
  type Relation,
@@ -54,6 +55,7 @@ import {
54
55
  sort,
55
56
  unique
56
57
  } from 'remeda'
58
+ import { resolveGlobalRulesInElementView } from '../../view-utils/resolve-global-rules'
57
59
  import { calcViewLayoutHash } from '../../view-utils/view-hash'
58
60
  import type { LikeC4ModelGraph } from '../LikeC4ModelGraph'
59
61
  import { applyCustomElementProperties } from '../utils/applyCustomElementProperties'
@@ -179,6 +181,29 @@ export class ComputeCtx {
179
181
 
180
182
  protected get activeGroup() {
181
183
  return this.activeGroupStack[0] ?? this.__rootGroup
184
+ }
185
+
186
+ protected get includedElements() {
187
+ return new Set([
188
+ ...this.explicits,
189
+ ...this.ctxEdges.flatMap(e => [e.source, e.target])
190
+ ]) as ReadonlySet<Element>
191
+ }
192
+
193
+ protected get resolvedElements() {
194
+ return new Set([
195
+ ...this.explicits,
196
+ ...this.implicits,
197
+ ...this.ctxEdges.flatMap(e => [e.source, e.target])
198
+ ]) as ReadonlySet<Element>
199
+ }
200
+
201
+ protected get edges() {
202
+ return this.ctxEdges
203
+ }
204
+
205
+ protected get root() {
206
+ return isScopedElementView(this.view) ? this.view.viewOf : null
182
207
  }
183
208
 
184
209
  public static elementView(view: ElementView, graph: LikeC4ModelGraph) {
@@ -194,10 +219,12 @@ export class ComputeCtx {
194
219
  this.reset()
195
220
  const {
196
221
  docUri: _docUri, // exclude docUri
197
- rules,
222
+ rules: _rules, // exclude rules
198
223
  ...view
199
224
  } = this.view
200
225
 
226
+ const rules = resolveGlobalRulesInElementView(this.view, this.graph.globals)
227
+
201
228
  const viewPredicates = rules.filter(anyPass([isViewRulePredicate, isViewRuleGroup])) as Array<
202
229
  ViewRulePredicate | ViewRuleGroup
203
230
  >
@@ -213,56 +240,10 @@ export class ComputeCtx {
213
240
  const elements = [...this.includedElements]
214
241
  const nodesMap = buildComputeNodes(elements, this.groups)
215
242
 
216
- const ancestorsOf = (node: ComputedNode) => {
217
- const ancestors = [] as ComputedNode[]
218
- let parent = node.parent
219
- while (parent) {
220
- const parentNode = nodesMap.get(parent)
221
- if (!parentNode) {
222
- break
223
- }
224
- ancestors.push(parentNode)
225
- parent = parentNode.parent
226
- }
227
- return ancestors
228
- }
229
-
230
- const edgesMap = new Map<EdgeId, ComputedEdge>()
231
243
  const edges = this.computeEdges()
232
- for (const edge of edges) {
233
- edgesMap.set(edge.id, edge)
234
- const source = nodesMap.get(edge.source)
235
- const target = nodesMap.get(edge.target)
236
- invariant(source, `Source node ${edge.source} not found`)
237
- invariant(target, `Target node ${edge.target} not found`)
238
- // These ancestors are reversed: from bottom to top
239
- const sourceAncestors = ancestorsOf(source)
240
- const targetAncestors = ancestorsOf(target)
244
+ const edgesMap = new Map<EdgeId, ComputedEdge>(edges.map(edge => [edge.id, edge]))
241
245
 
242
- const edgeParent = last(
243
- commonHead(
244
- reverse(sourceAncestors),
245
- reverse(targetAncestors)
246
- )
247
- )
248
- edge.parent = edgeParent?.id ?? null
249
- source.outEdges.push(edge.id)
250
- target.inEdges.push(edge.id)
251
- // Process edge source ancestors
252
- for (const sourceAncestor of sourceAncestors) {
253
- if (sourceAncestor === edgeParent) {
254
- break
255
- }
256
- sourceAncestor.outEdges.push(edge.id)
257
- }
258
- // Process target hierarchy
259
- for (const targetAncestor of targetAncestors) {
260
- if (targetAncestor === edgeParent) {
261
- break
262
- }
263
- targetAncestor.inEdges.push(edge.id)
264
- }
265
- }
246
+ this.linkNodesAndEdges(nodesMap, edges)
266
247
 
267
248
  // nodesMap sorted hierarchically,
268
249
  // but we need to keep the initial sort
@@ -312,10 +293,6 @@ export class ComputeCtx {
312
293
  })
313
294
  }
314
295
 
315
- protected get root() {
316
- return isScopedElementView(this.view) ? this.view.viewOf : null
317
- }
318
-
319
296
  protected computeEdges(): ComputedEdge[] {
320
297
  return this.ctxEdges.map((e): ComputedEdge => {
321
298
  invariant(hasAtLeast(e.relations, 1), 'Edge must have at least one relation')
@@ -428,23 +405,40 @@ export class ComputeCtx {
428
405
  })
429
406
  }
430
407
 
431
- protected get includedElements() {
432
- return new Set([
433
- ...this.explicits,
434
- ...this.ctxEdges.flatMap(e => [e.source, e.target])
435
- ]) as ReadonlySet<Element>
436
- }
437
-
438
- protected get resolvedElements() {
439
- return new Set([
440
- ...this.explicits,
441
- ...this.implicits,
442
- ...this.ctxEdges.flatMap(e => [e.source, e.target])
443
- ]) as ReadonlySet<Element>
444
- }
408
+ protected linkNodesAndEdges(nodesMap: ReadonlyMap<Fqn, ComputedNode>, edges: ComputedEdge[]) {
409
+ for (const edge of edges) {
410
+ const source = nodesMap.get(edge.source)
411
+ const target = nodesMap.get(edge.target)
412
+ invariant(source, `Source node ${edge.source} not found`)
413
+ invariant(target, `Target node ${edge.target} not found`)
414
+ // These ancestors are reversed: from bottom to top
415
+ const sourceAncestors = this.ancestorsOf(source, nodesMap)
416
+ const targetAncestors = this.ancestorsOf(target, nodesMap)
445
417
 
446
- protected get edges() {
447
- return this.ctxEdges
418
+ const edgeParent = last(
419
+ commonHead(
420
+ reverse(sourceAncestors),
421
+ reverse(targetAncestors)
422
+ )
423
+ )
424
+ edge.parent = edgeParent?.id ?? null
425
+ source.outEdges.push(edge.id)
426
+ target.inEdges.push(edge.id)
427
+ // Process edge source ancestors
428
+ for (const sourceAncestor of sourceAncestors) {
429
+ if (sourceAncestor === edgeParent) {
430
+ break
431
+ }
432
+ sourceAncestor.outEdges.push(edge.id)
433
+ }
434
+ // Process target hierarchy
435
+ for (const targetAncestor of targetAncestors) {
436
+ if (targetAncestor === edgeParent) {
437
+ break
438
+ }
439
+ targetAncestor.inEdges.push(edge.id)
440
+ }
441
+ }
448
442
  }
449
443
 
450
444
  protected addEdges(edges: ComputeCtx.Edge[]) {
@@ -539,9 +533,6 @@ export class ComputeCtx {
539
533
  }),
540
534
  filter(isNonNull)
541
535
  )
542
- if (excludedImplicits.size === 0) {
543
- return
544
- }
545
536
  this.ctxEdges = ctxEdges
546
537
  const remaining = this.includedElements
547
538
  if (remaining.size === 0) {
@@ -755,6 +746,20 @@ export class ComputeCtx {
755
746
  nonexhaustive(expr)
756
747
  }
757
748
 
749
+ private ancestorsOf(node: ComputedNode, nodesMap: ReadonlyMap<Fqn, ComputedNode>): ComputedNode[] {
750
+ const ancestors = [] as ComputedNode[]
751
+ let parent = node.parent
752
+ while (parent) {
753
+ const parentNode = nodesMap.get(parent)
754
+ if (!parentNode) {
755
+ break
756
+ }
757
+ ancestors.push(parentNode)
758
+ parent = parentNode.parent
759
+ }
760
+ return ancestors
761
+ }
762
+
758
763
  protected getEdgeLabel(
759
764
  relation: {
760
765
  title: string
@@ -26,6 +26,7 @@ import {
26
26
  StepEdgeId
27
27
  } from '@likec4/core'
28
28
  import { filter, flatMap, hasAtLeast, isTruthy, map, omit, only, pipe, unique } from 'remeda'
29
+ import { resolveGlobalRulesInDynamicView } from '../../view-utils/resolve-global-rules'
29
30
  import { calcViewLayoutHash } from '../../view-utils/view-hash'
30
31
  import type { LikeC4ModelGraph } from '../LikeC4ModelGraph'
31
32
  import { applyCustomElementProperties } from '../utils/applyCustomElementProperties'
@@ -111,7 +112,7 @@ export class DynamicViewComputeCtx {
111
112
  protected compute(): ComputedDynamicView {
112
113
  const {
113
114
  docUri: _docUri, // exclude docUri
114
- rules,
115
+ rules: _rules, // exclude rules
115
116
  steps: viewSteps,
116
117
  ...view
117
118
  } = this.view
@@ -133,6 +134,8 @@ export class DynamicViewComputeCtx {
133
134
  stepNum++
134
135
  }
135
136
 
137
+ const rules = resolveGlobalRulesInDynamicView(this.view, this.graph.globals)
138
+
136
139
  for (const rule of rules) {
137
140
  if (isDynamicViewIncludeRule(rule)) {
138
141
  for (const expr of rule.include) {
@@ -1,39 +1,8 @@
1
- import type { ComputedEdge, ComputedNode, Element, ViewRule } from '@likec4/core'
2
- import { Expr, nonexhaustive } from '@likec4/core'
3
- import { isNullish, omitBy } from 'remeda'
1
+ import type { ComputedEdge, ComputedNode, ViewRule } from '@likec4/core'
2
+ import { Expr } from '@likec4/core'
3
+ import { isNullish, omitBy, pick } from 'remeda'
4
4
  import { flattenGroupRules } from './applyCustomElementProperties'
5
- import { elementExprToPredicate } from './elementExpressionToPredicate'
6
-
7
- function relationExpressionToPredicates(
8
- expr: Expr.RelationExpression | Expr.RelationWhereExpr
9
- ): (edge: { source: Element; target: Element }) => boolean {
10
- switch (true) {
11
- case Expr.isRelationWhere(expr):
12
- return relationExpressionToPredicates(expr.where.expr)
13
- case Expr.isRelation(expr): {
14
- const isSource = elementExprToPredicate(expr.source)
15
- const isTarget = elementExprToPredicate(expr.target)
16
- return edge => {
17
- return (isSource(edge.source) && isTarget(edge.target))
18
- || (!!expr.isBidirectional && isSource(edge.target) && isTarget(edge.source))
19
- }
20
- }
21
- case Expr.isInOut(expr): {
22
- const isInOut = elementExprToPredicate(expr.inout)
23
- return edge => isInOut(edge.source) || isInOut(edge.target)
24
- }
25
- case Expr.isIncoming(expr): {
26
- const isTarget = elementExprToPredicate(expr.incoming)
27
- return edge => isTarget(edge.target)
28
- }
29
- case Expr.isOutgoing(expr): {
30
- const isSource = elementExprToPredicate(expr.outgoing)
31
- return edge => isSource(edge.source)
32
- }
33
- default:
34
- nonexhaustive(expr)
35
- }
36
- }
5
+ import { relationExpressionToPredicates } from './relationExpressionToPredicates'
37
6
 
38
7
  export function applyCustomRelationProperties(
39
8
  _rules: ViewRule[],
@@ -58,12 +27,12 @@ export function applyCustomRelationProperties(
58
27
  if (!source || !target) {
59
28
  return
60
29
  }
61
- if (satisfies({ source, target })) {
30
+ if (satisfies({ source, target, ...pick(edge, ['kind', 'tags']) })) {
62
31
  edges[i] = {
63
32
  ...edge,
33
+ ...props,
64
34
  label: title ?? edge.label,
65
- isCustomized: true,
66
- ...props
35
+ isCustomized: true
67
36
  }
68
37
  }
69
38
  })
@@ -13,10 +13,6 @@ export function applyViewRuleStyles(_rules: ViewRule[], nodes: ComputedNode[]) {
13
13
  for (const rule of rules) {
14
14
  const predicates = [] as Predicate<ComputedNode>[]
15
15
  for (const target of rule.targets) {
16
- if (Expr.isWildcard(target)) {
17
- predicates.push(() => true)
18
- break
19
- }
20
16
  predicates.push(elementExprToPredicate(target) as Predicate<ComputedNode>)
21
17
  }
22
18
  pipe(
@@ -0,0 +1,43 @@
1
+ import type { Element, Relation } from '@likec4/core'
2
+ import { Expr, nonexhaustive, whereOperatorAsPredicate } from '@likec4/core'
3
+ import { elementExprToPredicate } from './elementExpressionToPredicate'
4
+
5
+ type Predicate<T> = (x: T) => boolean
6
+ export type FilterableEdge = Pick<Relation, 'kind' | 'tags'> & {
7
+ source: Element
8
+ target: Element
9
+ }
10
+
11
+ export function relationExpressionToPredicates<T extends FilterableEdge>(
12
+ expr: Expr.RelationExpression | Expr.RelationWhereExpr
13
+ ): Predicate<T> {
14
+ switch (true) {
15
+ case Expr.isRelationWhere(expr):
16
+ const predicate = relationExpressionToPredicates(expr.where.expr)
17
+ const where = whereOperatorAsPredicate(expr.where.condition)
18
+
19
+ return e => predicate(e) && where(e)
20
+ case Expr.isRelation(expr): {
21
+ const isSource = elementExprToPredicate(expr.source)
22
+ const isTarget = elementExprToPredicate(expr.target)
23
+ return edge => {
24
+ return (isSource(edge.source) && isTarget(edge.target))
25
+ || (!!expr.isBidirectional && isSource(edge.target) && isTarget(edge.source))
26
+ }
27
+ }
28
+ case Expr.isInOut(expr): {
29
+ const isInOut = elementExprToPredicate(expr.inout)
30
+ return edge => isInOut(edge.source) || isInOut(edge.target)
31
+ }
32
+ case Expr.isIncoming(expr): {
33
+ const isTarget = elementExprToPredicate(expr.incoming)
34
+ return edge => isTarget(edge.target)
35
+ }
36
+ case Expr.isOutgoing(expr): {
37
+ const isSource = elementExprToPredicate(expr.outgoing)
38
+ return edge => isSource(edge.source)
39
+ }
40
+ default:
41
+ nonexhaustive(expr)
42
+ }
43
+ }
@@ -69,6 +69,16 @@ export class LikeC4ScopeComputation extends DefaultScopeComputation {
69
69
  if (isNullish(globals) || globals.length === 0) {
70
70
  return
71
71
  }
72
+ for (const globalPredicateAst of globals.flatMap(g => g.predicates)) {
73
+ try {
74
+ const id = globalPredicateAst
75
+ if (isTruthy(id.name)) {
76
+ docExports.push(this.descriptions.createDescription(id, id.name, document))
77
+ }
78
+ } catch (e) {
79
+ logError(e)
80
+ }
81
+ }
72
82
  for (const globalStyleAst of globals.flatMap(g => g.styles)) {
73
83
  try {
74
84
  const id = globalStyleAst.id
@@ -8,6 +8,7 @@ import { iconPropertyRuleChecks, notesPropertyRuleChecks, opacityPropertyRuleChe
8
8
  import { relationBodyChecks, relationChecks } from './relation'
9
9
  import {
10
10
  elementKindChecks,
11
+ globalPredicateChecks,
11
12
  globalsChecks,
12
13
  globalStyleIdChecks,
13
14
  modelRuleChecks,
@@ -34,6 +35,8 @@ export function registerValidationChecks(services: LikeC4Services) {
34
35
  SpecificationRule: specificationRuleChecks(services),
35
36
  Model: modelRuleChecks(services),
36
37
  Globals: globalsChecks(services),
38
+ GlobalPredicateGroup: globalPredicateChecks(services),
39
+ GlobalDynamicPredicateGroup: globalPredicateChecks(services),
37
40
  GlobalStyleId: globalStyleIdChecks(services),
38
41
  DynamicViewStep: dynamicViewStep(services),
39
42
  LikeC4View: viewChecks(services),
@@ -130,6 +130,27 @@ export const relationshipChecks = (
130
130
  }
131
131
  }
132
132
 
133
+ export const globalPredicateChecks = (
134
+ services: LikeC4Services
135
+ ): ValidationCheck<ast.GlobalPredicateGroup | ast.GlobalDynamicPredicateGroup> => {
136
+ const index = services.shared.workspace.IndexManager
137
+ return (node, accept) => {
138
+ const predicateGroups = index.allElements(ast.GlobalPredicateGroup)
139
+ const dynamicPredicateGroups = index.allElements(ast.GlobalDynamicPredicateGroup)
140
+ const sameName = predicateGroups
141
+ .concat(dynamicPredicateGroups)
142
+ .filter(s => s.name === node.name)
143
+ .limit(2)
144
+ .count()
145
+ if (sameName > 1) {
146
+ accept('error', `Duplicate GlobalPredicateGroup or GlobalDynamicPredicateGroup name '${node.name}'`, {
147
+ node: node,
148
+ property: 'name'
149
+ })
150
+ }
151
+ }
152
+ }
153
+
133
154
  export const globalStyleIdChecks = (
134
155
  services: LikeC4Services
135
156
  ): ValidationCheck<ast.GlobalStyleId> => {
@@ -1,4 +1,3 @@
1
1
  export * from './assignNavigateTo'
2
2
  export * from './resolve-extended-views'
3
- export * from './resolve-global-rules'
4
3
  export * from './resolve-relative-paths'
@@ -1,72 +1,88 @@
1
1
  import {
2
2
  type DynamicView,
3
+ type DynamicViewRule,
3
4
  type ElementView,
4
- type GlobalStyle,
5
- type GlobalStyleID,
6
5
  isDynamicView,
7
6
  isElementView,
7
+ isViewRuleGlobalPredicateRef,
8
8
  isViewRuleGlobalStyle,
9
- type LikeC4View,
10
- nonexhaustive
9
+ type ModelGlobals,
10
+ nonexhaustive,
11
+ type ViewRule,
12
+ type ViewRuleGlobalPredicateRef,
13
+ type ViewRuleGlobalStyle
11
14
  } from '@likec4/core'
12
15
  import { logger } from '@likec4/log'
16
+ import { isNullish } from 'remeda'
13
17
 
14
- export function resolveGlobalRules(
15
- view: LikeC4View,
16
- globalStyles: Record<GlobalStyleID, GlobalStyle>
17
- ): LikeC4View {
18
+ export function resolveGlobalRules<V extends DynamicView | ElementView>(
19
+ view: V,
20
+ globals: ModelGlobals
21
+ ): V {
18
22
  if (isElementView(view)) {
19
- return resolveGlobalStyleInElementView(view, globalStyles)
23
+ return {
24
+ ...view,
25
+ rules: resolveGlobalRulesInElementView(view, globals)
26
+ }
20
27
  } else if (isDynamicView(view)) {
21
- return resolveGlobalStyleInDynamicView(view, globalStyles)
28
+ return {
29
+ ...view,
30
+ rules: resolveGlobalRulesInDynamicView(view, globals)
31
+ }
22
32
  }
23
33
  nonexhaustive(view)
24
34
  }
25
35
 
26
- function resolveGlobalStyleInElementView(
36
+ type ViewRuleGlobal = ViewRuleGlobalPredicateRef | ViewRuleGlobalStyle
37
+
38
+ export function resolveGlobalRulesInElementView(
27
39
  view: ElementView,
28
- globalStyles: Record<GlobalStyleID, GlobalStyle>
29
- ): LikeC4View {
30
- const resolvedRules = view.rules
31
- .flatMap(rule => {
32
- if (isViewRuleGlobalStyle(rule)) {
33
- const globalStyle = globalStyles[rule.styleId]
34
- if (globalStyle === undefined) {
35
- logger.warn(`Global style not found: ${rule.styleId}`)
36
- return []
37
- }
38
- return globalStyle.styles
39
- } else {
40
- return rule
40
+ globals: ModelGlobals
41
+ ): Array<Exclude<ViewRule, ViewRuleGlobal>> {
42
+ return view.rules.reduce((acc, rule) => {
43
+ if (isViewRuleGlobalPredicateRef(rule)) {
44
+ const globalPredicates = globals.predicates[rule.predicateId]
45
+ if (isNullish(globalPredicates)) {
46
+ logger.warn(`Global predicate not found: ${rule.predicateId}`)
47
+ return acc
41
48
  }
42
- })
43
-
44
- return {
45
- ...view,
46
- rules: resolvedRules
47
- }
49
+ return acc.concat(globalPredicates)
50
+ }
51
+ if (isViewRuleGlobalStyle(rule)) {
52
+ const globalStyles = globals.styles[rule.styleId]
53
+ if (isNullish(globalStyles)) {
54
+ logger.warn(`Global style not found: ${rule.styleId}`)
55
+ return acc
56
+ }
57
+ return acc.concat(globalStyles)
58
+ }
59
+ acc.push(rule)
60
+ return acc
61
+ }, [] as Array<Exclude<ViewRule, ViewRuleGlobal>>)
48
62
  }
49
63
 
50
- function resolveGlobalStyleInDynamicView(
64
+ export function resolveGlobalRulesInDynamicView(
51
65
  view: DynamicView,
52
- globalStyles: Record<GlobalStyleID, GlobalStyle>
53
- ): LikeC4View {
54
- const resolvedRules = view.rules
55
- .flatMap(rule => {
56
- if (isViewRuleGlobalStyle(rule)) {
57
- const globalStyle = globalStyles[rule.styleId]
58
- if (globalStyle === undefined) {
59
- logger.warn(`Global style not found: ${rule.styleId}`)
60
- return []
61
- }
62
- return globalStyle.styles
63
- } else {
64
- return rule
66
+ globals: ModelGlobals
67
+ ): Array<Exclude<DynamicViewRule, ViewRuleGlobal>> {
68
+ return view.rules.reduce((acc, rule) => {
69
+ if (isViewRuleGlobalPredicateRef(rule)) {
70
+ const globalPredicates = globals.dynamicPredicates[rule.predicateId]
71
+ if (isNullish(globalPredicates)) {
72
+ logger.warn(`Global predicate not found: ${rule.predicateId}`)
73
+ return acc
65
74
  }
66
- })
67
-
68
- return {
69
- ...view,
70
- rules: resolvedRules
71
- }
75
+ return acc.concat(globalPredicates)
76
+ }
77
+ if (isViewRuleGlobalStyle(rule)) {
78
+ const globalStyles = globals.styles[rule.styleId]
79
+ if (isNullish(globalStyles)) {
80
+ logger.warn(`Global style not found: ${rule.styleId}`)
81
+ return acc
82
+ }
83
+ return acc.concat(globalStyles)
84
+ }
85
+ acc.push(rule)
86
+ return acc
87
+ }, [] as Array<Exclude<DynamicViewRule, ViewRuleGlobal>>)
72
88
  }