@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
@@ -3,6 +3,7 @@
3
3
  const core = require('@likec4/core');
4
4
  const remeda = require('remeda');
5
5
  const dagre = require('@dagrejs/dagre');
6
+ const log = require('@likec4/log');
6
7
  const objectHash = require('object-hash');
7
8
 
8
9
  function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }
@@ -10,6 +11,51 @@ function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'defau
10
11
  const dagre__default = /*#__PURE__*/_interopDefaultCompat(dagre);
11
12
  const objectHash__default = /*#__PURE__*/_interopDefaultCompat(objectHash);
12
13
 
14
+ function resolveGlobalRulesInElementView(view, globals) {
15
+ return view.rules.reduce((acc, rule) => {
16
+ if (core.isViewRuleGlobalPredicateRef(rule)) {
17
+ const globalPredicates = globals.predicates[rule.predicateId];
18
+ if (remeda.isNullish(globalPredicates)) {
19
+ log.logger.warn(`Global predicate not found: ${rule.predicateId}`);
20
+ return acc;
21
+ }
22
+ return acc.concat(globalPredicates);
23
+ }
24
+ if (core.isViewRuleGlobalStyle(rule)) {
25
+ const globalStyles = globals.styles[rule.styleId];
26
+ if (remeda.isNullish(globalStyles)) {
27
+ log.logger.warn(`Global style not found: ${rule.styleId}`);
28
+ return acc;
29
+ }
30
+ return acc.concat(globalStyles);
31
+ }
32
+ acc.push(rule);
33
+ return acc;
34
+ }, []);
35
+ }
36
+ function resolveGlobalRulesInDynamicView(view, globals) {
37
+ return view.rules.reduce((acc, rule) => {
38
+ if (core.isViewRuleGlobalPredicateRef(rule)) {
39
+ const globalPredicates = globals.dynamicPredicates[rule.predicateId];
40
+ if (remeda.isNullish(globalPredicates)) {
41
+ log.logger.warn(`Global predicate not found: ${rule.predicateId}`);
42
+ return acc;
43
+ }
44
+ return acc.concat(globalPredicates);
45
+ }
46
+ if (core.isViewRuleGlobalStyle(rule)) {
47
+ const globalStyles = globals.styles[rule.styleId];
48
+ if (remeda.isNullish(globalStyles)) {
49
+ log.logger.warn(`Global style not found: ${rule.styleId}`);
50
+ return acc;
51
+ }
52
+ return acc.concat(globalStyles);
53
+ }
54
+ acc.push(rule);
55
+ return acc;
56
+ }, []);
57
+ }
58
+
13
59
  function calcViewLayoutHash(view) {
14
60
  const tohash = {
15
61
  id: view.id,
@@ -121,7 +167,9 @@ function applyCustomElementProperties(_rules, _nodes) {
121
167
  function relationExpressionToPredicates(expr) {
122
168
  switch (true) {
123
169
  case core.Expr.isRelationWhere(expr):
124
- return relationExpressionToPredicates(expr.where.expr);
170
+ const predicate = relationExpressionToPredicates(expr.where.expr);
171
+ const where = core.whereOperatorAsPredicate(expr.where.condition);
172
+ return (e) => predicate(e) && where(e);
125
173
  case core.Expr.isRelation(expr): {
126
174
  const isSource = elementExprToPredicate(expr.source);
127
175
  const isTarget = elementExprToPredicate(expr.target);
@@ -145,6 +193,7 @@ function relationExpressionToPredicates(expr) {
145
193
  core.nonexhaustive(expr);
146
194
  }
147
195
  }
196
+
148
197
  function applyCustomRelationProperties(_rules, nodes, _edges) {
149
198
  const rules = _rules.flatMap(flattenGroupRules(core.Expr.isCustomRelationExpr));
150
199
  const edges = Array.from(_edges);
@@ -162,12 +211,12 @@ function applyCustomRelationProperties(_rules, nodes, _edges) {
162
211
  if (!source || !target) {
163
212
  return;
164
213
  }
165
- if (satisfies({ source, target })) {
214
+ if (satisfies({ source, target, ...remeda.pick(edge, ["kind", "tags"]) })) {
166
215
  edges[i] = {
167
216
  ...edge,
217
+ ...props,
168
218
  label: title ?? edge.label,
169
- isCustomized: true,
170
- ...props
219
+ isCustomized: true
171
220
  };
172
221
  }
173
222
  });
@@ -183,10 +232,6 @@ function applyViewRuleStyles(_rules, nodes) {
183
232
  for (const rule of rules) {
184
233
  const predicates = [];
185
234
  for (const target of rule.targets) {
186
- if (core.Expr.isWildcard(target)) {
187
- predicates.push(() => true);
188
- break;
189
- }
190
235
  predicates.push(elementExprToPredicate(target));
191
236
  }
192
237
  remeda.pipe(
@@ -926,6 +971,25 @@ class ComputeCtx {
926
971
  get activeGroup() {
927
972
  return this.activeGroupStack[0] ?? this.__rootGroup;
928
973
  }
974
+ get includedElements() {
975
+ return /* @__PURE__ */ new Set([
976
+ ...this.explicits,
977
+ ...this.ctxEdges.flatMap((e) => [e.source, e.target])
978
+ ]);
979
+ }
980
+ get resolvedElements() {
981
+ return /* @__PURE__ */ new Set([
982
+ ...this.explicits,
983
+ ...this.implicits,
984
+ ...this.ctxEdges.flatMap((e) => [e.source, e.target])
985
+ ]);
986
+ }
987
+ get edges() {
988
+ return this.ctxEdges;
989
+ }
990
+ get root() {
991
+ return core.isScopedElementView(this.view) ? this.view.viewOf : null;
992
+ }
929
993
  static elementView(view, graph) {
930
994
  return new ComputeCtx(view, graph).compute();
931
995
  }
@@ -934,9 +998,11 @@ class ComputeCtx {
934
998
  const {
935
999
  docUri: _docUri,
936
1000
  // exclude docUri
937
- rules,
1001
+ rules: _rules,
1002
+ // exclude rules
938
1003
  ...view
939
1004
  } = this.view;
1005
+ const rules = resolveGlobalRulesInElementView(this.view, this.graph.globals);
940
1006
  const viewPredicates = rules.filter(remeda.anyPass([core.isViewRulePredicate, core.isViewRuleGroup]));
941
1007
  if (this.root && viewPredicates.length == 0) {
942
1008
  this.addElement(this.graph.element(this.root));
@@ -948,51 +1014,9 @@ class ComputeCtx {
948
1014
  }
949
1015
  const elements = [...this.includedElements];
950
1016
  const nodesMap = buildComputeNodes(elements, this.groups);
951
- const ancestorsOf = (node) => {
952
- const ancestors = [];
953
- let parent = node.parent;
954
- while (parent) {
955
- const parentNode = nodesMap.get(parent);
956
- if (!parentNode) {
957
- break;
958
- }
959
- ancestors.push(parentNode);
960
- parent = parentNode.parent;
961
- }
962
- return ancestors;
963
- };
964
- const edgesMap = /* @__PURE__ */ new Map();
965
1017
  const edges = this.computeEdges();
966
- for (const edge of edges) {
967
- edgesMap.set(edge.id, edge);
968
- const source = nodesMap.get(edge.source);
969
- const target = nodesMap.get(edge.target);
970
- core.invariant(source, `Source node ${edge.source} not found`);
971
- core.invariant(target, `Target node ${edge.target} not found`);
972
- const sourceAncestors = ancestorsOf(source);
973
- const targetAncestors = ancestorsOf(target);
974
- const edgeParent = remeda.last(
975
- core.commonHead(
976
- remeda.reverse(sourceAncestors),
977
- remeda.reverse(targetAncestors)
978
- )
979
- );
980
- edge.parent = edgeParent?.id ?? null;
981
- source.outEdges.push(edge.id);
982
- target.inEdges.push(edge.id);
983
- for (const sourceAncestor of sourceAncestors) {
984
- if (sourceAncestor === edgeParent) {
985
- break;
986
- }
987
- sourceAncestor.outEdges.push(edge.id);
988
- }
989
- for (const targetAncestor of targetAncestors) {
990
- if (targetAncestor === edgeParent) {
991
- break;
992
- }
993
- targetAncestor.inEdges.push(edge.id);
994
- }
995
- }
1018
+ const edgesMap = new Map(edges.map((edge) => [edge.id, edge]));
1019
+ this.linkNodesAndEdges(nodesMap, edges);
996
1020
  let initialSort = elements.map((e) => core.nonNullable(nodesMap.get(e.id), `Node ${e.id} not found in nodesMap`));
997
1021
  if (this.groups.length > 0) {
998
1022
  initialSort = remeda.concat(
@@ -1033,9 +1057,6 @@ class ComputeCtx {
1033
1057
  }
1034
1058
  });
1035
1059
  }
1036
- get root() {
1037
- return core.isScopedElementView(this.view) ? this.view.viewOf : null;
1038
- }
1039
1060
  computeEdges() {
1040
1061
  return this.ctxEdges.map((e) => {
1041
1062
  core.invariant(remeda.hasAtLeast(e.relations, 1), "Edge must have at least one relation");
@@ -1128,21 +1149,36 @@ class ComputeCtx {
1128
1149
  );
1129
1150
  });
1130
1151
  }
1131
- get includedElements() {
1132
- return /* @__PURE__ */ new Set([
1133
- ...this.explicits,
1134
- ...this.ctxEdges.flatMap((e) => [e.source, e.target])
1135
- ]);
1136
- }
1137
- get resolvedElements() {
1138
- return /* @__PURE__ */ new Set([
1139
- ...this.explicits,
1140
- ...this.implicits,
1141
- ...this.ctxEdges.flatMap((e) => [e.source, e.target])
1142
- ]);
1143
- }
1144
- get edges() {
1145
- return this.ctxEdges;
1152
+ linkNodesAndEdges(nodesMap, edges) {
1153
+ for (const edge of edges) {
1154
+ const source = nodesMap.get(edge.source);
1155
+ const target = nodesMap.get(edge.target);
1156
+ core.invariant(source, `Source node ${edge.source} not found`);
1157
+ core.invariant(target, `Target node ${edge.target} not found`);
1158
+ const sourceAncestors = this.ancestorsOf(source, nodesMap);
1159
+ const targetAncestors = this.ancestorsOf(target, nodesMap);
1160
+ const edgeParent = remeda.last(
1161
+ core.commonHead(
1162
+ remeda.reverse(sourceAncestors),
1163
+ remeda.reverse(targetAncestors)
1164
+ )
1165
+ );
1166
+ edge.parent = edgeParent?.id ?? null;
1167
+ source.outEdges.push(edge.id);
1168
+ target.inEdges.push(edge.id);
1169
+ for (const sourceAncestor of sourceAncestors) {
1170
+ if (sourceAncestor === edgeParent) {
1171
+ break;
1172
+ }
1173
+ sourceAncestor.outEdges.push(edge.id);
1174
+ }
1175
+ for (const targetAncestor of targetAncestors) {
1176
+ if (targetAncestor === edgeParent) {
1177
+ break;
1178
+ }
1179
+ targetAncestor.inEdges.push(edge.id);
1180
+ }
1181
+ }
1146
1182
  }
1147
1183
  addEdges(edges) {
1148
1184
  const added = [];
@@ -1231,9 +1267,6 @@ class ComputeCtx {
1231
1267
  }),
1232
1268
  remeda.filter(remeda.isNonNull)
1233
1269
  );
1234
- if (excludedImplicits.size === 0) {
1235
- return;
1236
- }
1237
1270
  this.ctxEdges = ctxEdges;
1238
1271
  const remaining = this.includedElements;
1239
1272
  if (remaining.size === 0) {
@@ -1410,6 +1443,19 @@ class ComputeCtx {
1410
1443
  }
1411
1444
  core.nonexhaustive(expr);
1412
1445
  }
1446
+ ancestorsOf(node, nodesMap) {
1447
+ const ancestors = [];
1448
+ let parent = node.parent;
1449
+ while (parent) {
1450
+ const parentNode = nodesMap.get(parent);
1451
+ if (!parentNode) {
1452
+ break;
1453
+ }
1454
+ ancestors.push(parentNode);
1455
+ parent = parentNode.parent;
1456
+ }
1457
+ return ancestors;
1458
+ }
1413
1459
  getEdgeLabel(relation) {
1414
1460
  const labelParts = [];
1415
1461
  if (remeda.isTruthy(relation.title)) {
@@ -1487,7 +1533,8 @@ class DynamicViewComputeCtx {
1487
1533
  const {
1488
1534
  docUri: _docUri,
1489
1535
  // exclude docUri
1490
- rules,
1536
+ rules: _rules,
1537
+ // exclude rules
1491
1538
  steps: viewSteps,
1492
1539
  ...view
1493
1540
  } = this.view;
@@ -1507,6 +1554,7 @@ class DynamicViewComputeCtx {
1507
1554
  }
1508
1555
  stepNum++;
1509
1556
  }
1557
+ const rules = resolveGlobalRulesInDynamicView(this.view, this.graph.globals);
1510
1558
  for (const rule of rules) {
1511
1559
  if (core.isDynamicViewIncludeRule(rule)) {
1512
1560
  for (const expr of rule.include) {
@@ -1660,7 +1708,13 @@ class LikeC4ModelGraph {
1660
1708
  // Relationships inside the element, among descendants
1661
1709
  #internal = new MapRelations();
1662
1710
  #cacheAscendingSiblings = /* @__PURE__ */ new Map();
1663
- constructor({ elements, relations }) {
1711
+ globals;
1712
+ constructor({ elements, relations, globals }) {
1713
+ this.globals = globals ?? {
1714
+ predicates: {},
1715
+ dynamicPredicates: {},
1716
+ styles: {}
1717
+ };
1664
1718
  for (const el of Object.values(elements)) {
1665
1719
  this.addElement(el);
1666
1720
  }
@@ -1,8 +1,54 @@
1
- import { Expr, whereOperatorAsPredicate, parentFqn, nonexhaustive, ComputedNode, isViewRuleGroup, isViewRulePredicate, isViewRuleStyle, nonNullable, ElementKind, compareByFqnHierarchically, DefaultThemeColor, DefaultElementShape, compareRelations, invariant, compareNatural, hasAtLeast, isAncestor, commonHead, isViewRuleAutoLayout, isScopedElementView, commonAncestor, StepEdgeId, isDynamicViewParallelSteps, isDynamicViewIncludeRule, DefaultRelationshipColor, DefaultLineStyle, DefaultArrowType, ancestorsFqn, isSameHierarchy } from '@likec4/core';
2
- import { pipe, map, pick, mapToObj, isTruthy, isNullish, omitBy, isEmpty, filter, isNot, anyPass, forEach, isDefined, groupBy, prop, mapValues, piped, unique, entries, flatMap, sortBy, sort, tap, difference, allPass, last, reverse, concat, omit, hasAtLeast as hasAtLeast$1, only, reduce, isNonNull, first, isString, isArray } from 'remeda';
1
+ import { isViewRuleGlobalPredicateRef, isViewRuleGlobalStyle, Expr, whereOperatorAsPredicate, parentFqn, nonexhaustive, ComputedNode, isViewRuleGroup, isViewRulePredicate, isViewRuleStyle, nonNullable, ElementKind, compareByFqnHierarchically, DefaultThemeColor, DefaultElementShape, compareRelations, invariant, compareNatural, hasAtLeast, isAncestor, isScopedElementView, isViewRuleAutoLayout, commonAncestor, commonHead, StepEdgeId, isDynamicViewParallelSteps, isDynamicViewIncludeRule, DefaultRelationshipColor, DefaultLineStyle, DefaultArrowType, ancestorsFqn, isSameHierarchy } from '@likec4/core';
2
+ import { isNullish, pipe, map, pick, mapToObj, isTruthy, omitBy, isEmpty, filter, isNot, anyPass, forEach, isDefined, groupBy, prop, mapValues, piped, unique, entries, flatMap, sortBy, sort, tap, difference, allPass, concat, omit, hasAtLeast as hasAtLeast$1, only, reduce, last, reverse, isNonNull, first, isString, isArray } from 'remeda';
3
3
  import dagre from '@dagrejs/dagre';
4
+ import { logger } from '@likec4/log';
4
5
  import objectHash from 'object-hash';
5
6
 
7
+ function resolveGlobalRulesInElementView(view, globals) {
8
+ return view.rules.reduce((acc, rule) => {
9
+ if (isViewRuleGlobalPredicateRef(rule)) {
10
+ const globalPredicates = globals.predicates[rule.predicateId];
11
+ if (isNullish(globalPredicates)) {
12
+ logger.warn(`Global predicate not found: ${rule.predicateId}`);
13
+ return acc;
14
+ }
15
+ return acc.concat(globalPredicates);
16
+ }
17
+ if (isViewRuleGlobalStyle(rule)) {
18
+ const globalStyles = globals.styles[rule.styleId];
19
+ if (isNullish(globalStyles)) {
20
+ logger.warn(`Global style not found: ${rule.styleId}`);
21
+ return acc;
22
+ }
23
+ return acc.concat(globalStyles);
24
+ }
25
+ acc.push(rule);
26
+ return acc;
27
+ }, []);
28
+ }
29
+ function resolveGlobalRulesInDynamicView(view, globals) {
30
+ return view.rules.reduce((acc, rule) => {
31
+ if (isViewRuleGlobalPredicateRef(rule)) {
32
+ const globalPredicates = globals.dynamicPredicates[rule.predicateId];
33
+ if (isNullish(globalPredicates)) {
34
+ logger.warn(`Global predicate not found: ${rule.predicateId}`);
35
+ return acc;
36
+ }
37
+ return acc.concat(globalPredicates);
38
+ }
39
+ if (isViewRuleGlobalStyle(rule)) {
40
+ const globalStyles = globals.styles[rule.styleId];
41
+ if (isNullish(globalStyles)) {
42
+ logger.warn(`Global style not found: ${rule.styleId}`);
43
+ return acc;
44
+ }
45
+ return acc.concat(globalStyles);
46
+ }
47
+ acc.push(rule);
48
+ return acc;
49
+ }, []);
50
+ }
51
+
6
52
  function calcViewLayoutHash(view) {
7
53
  const tohash = {
8
54
  id: view.id,
@@ -114,7 +160,9 @@ function applyCustomElementProperties(_rules, _nodes) {
114
160
  function relationExpressionToPredicates(expr) {
115
161
  switch (true) {
116
162
  case Expr.isRelationWhere(expr):
117
- return relationExpressionToPredicates(expr.where.expr);
163
+ const predicate = relationExpressionToPredicates(expr.where.expr);
164
+ const where = whereOperatorAsPredicate(expr.where.condition);
165
+ return (e) => predicate(e) && where(e);
118
166
  case Expr.isRelation(expr): {
119
167
  const isSource = elementExprToPredicate(expr.source);
120
168
  const isTarget = elementExprToPredicate(expr.target);
@@ -138,6 +186,7 @@ function relationExpressionToPredicates(expr) {
138
186
  nonexhaustive(expr);
139
187
  }
140
188
  }
189
+
141
190
  function applyCustomRelationProperties(_rules, nodes, _edges) {
142
191
  const rules = _rules.flatMap(flattenGroupRules(Expr.isCustomRelationExpr));
143
192
  const edges = Array.from(_edges);
@@ -155,12 +204,12 @@ function applyCustomRelationProperties(_rules, nodes, _edges) {
155
204
  if (!source || !target) {
156
205
  return;
157
206
  }
158
- if (satisfies({ source, target })) {
207
+ if (satisfies({ source, target, ...pick(edge, ["kind", "tags"]) })) {
159
208
  edges[i] = {
160
209
  ...edge,
210
+ ...props,
161
211
  label: title ?? edge.label,
162
- isCustomized: true,
163
- ...props
212
+ isCustomized: true
164
213
  };
165
214
  }
166
215
  });
@@ -176,10 +225,6 @@ function applyViewRuleStyles(_rules, nodes) {
176
225
  for (const rule of rules) {
177
226
  const predicates = [];
178
227
  for (const target of rule.targets) {
179
- if (Expr.isWildcard(target)) {
180
- predicates.push(() => true);
181
- break;
182
- }
183
228
  predicates.push(elementExprToPredicate(target));
184
229
  }
185
230
  pipe(
@@ -919,6 +964,25 @@ class ComputeCtx {
919
964
  get activeGroup() {
920
965
  return this.activeGroupStack[0] ?? this.__rootGroup;
921
966
  }
967
+ get includedElements() {
968
+ return /* @__PURE__ */ new Set([
969
+ ...this.explicits,
970
+ ...this.ctxEdges.flatMap((e) => [e.source, e.target])
971
+ ]);
972
+ }
973
+ get resolvedElements() {
974
+ return /* @__PURE__ */ new Set([
975
+ ...this.explicits,
976
+ ...this.implicits,
977
+ ...this.ctxEdges.flatMap((e) => [e.source, e.target])
978
+ ]);
979
+ }
980
+ get edges() {
981
+ return this.ctxEdges;
982
+ }
983
+ get root() {
984
+ return isScopedElementView(this.view) ? this.view.viewOf : null;
985
+ }
922
986
  static elementView(view, graph) {
923
987
  return new ComputeCtx(view, graph).compute();
924
988
  }
@@ -927,9 +991,11 @@ class ComputeCtx {
927
991
  const {
928
992
  docUri: _docUri,
929
993
  // exclude docUri
930
- rules,
994
+ rules: _rules,
995
+ // exclude rules
931
996
  ...view
932
997
  } = this.view;
998
+ const rules = resolveGlobalRulesInElementView(this.view, this.graph.globals);
933
999
  const viewPredicates = rules.filter(anyPass([isViewRulePredicate, isViewRuleGroup]));
934
1000
  if (this.root && viewPredicates.length == 0) {
935
1001
  this.addElement(this.graph.element(this.root));
@@ -941,51 +1007,9 @@ class ComputeCtx {
941
1007
  }
942
1008
  const elements = [...this.includedElements];
943
1009
  const nodesMap = buildComputeNodes(elements, this.groups);
944
- const ancestorsOf = (node) => {
945
- const ancestors = [];
946
- let parent = node.parent;
947
- while (parent) {
948
- const parentNode = nodesMap.get(parent);
949
- if (!parentNode) {
950
- break;
951
- }
952
- ancestors.push(parentNode);
953
- parent = parentNode.parent;
954
- }
955
- return ancestors;
956
- };
957
- const edgesMap = /* @__PURE__ */ new Map();
958
1010
  const edges = this.computeEdges();
959
- for (const edge of edges) {
960
- edgesMap.set(edge.id, edge);
961
- const source = nodesMap.get(edge.source);
962
- const target = nodesMap.get(edge.target);
963
- invariant(source, `Source node ${edge.source} not found`);
964
- invariant(target, `Target node ${edge.target} not found`);
965
- const sourceAncestors = ancestorsOf(source);
966
- const targetAncestors = ancestorsOf(target);
967
- const edgeParent = last(
968
- commonHead(
969
- reverse(sourceAncestors),
970
- reverse(targetAncestors)
971
- )
972
- );
973
- edge.parent = edgeParent?.id ?? null;
974
- source.outEdges.push(edge.id);
975
- target.inEdges.push(edge.id);
976
- for (const sourceAncestor of sourceAncestors) {
977
- if (sourceAncestor === edgeParent) {
978
- break;
979
- }
980
- sourceAncestor.outEdges.push(edge.id);
981
- }
982
- for (const targetAncestor of targetAncestors) {
983
- if (targetAncestor === edgeParent) {
984
- break;
985
- }
986
- targetAncestor.inEdges.push(edge.id);
987
- }
988
- }
1011
+ const edgesMap = new Map(edges.map((edge) => [edge.id, edge]));
1012
+ this.linkNodesAndEdges(nodesMap, edges);
989
1013
  let initialSort = elements.map((e) => nonNullable(nodesMap.get(e.id), `Node ${e.id} not found in nodesMap`));
990
1014
  if (this.groups.length > 0) {
991
1015
  initialSort = concat(
@@ -1026,9 +1050,6 @@ class ComputeCtx {
1026
1050
  }
1027
1051
  });
1028
1052
  }
1029
- get root() {
1030
- return isScopedElementView(this.view) ? this.view.viewOf : null;
1031
- }
1032
1053
  computeEdges() {
1033
1054
  return this.ctxEdges.map((e) => {
1034
1055
  invariant(hasAtLeast$1(e.relations, 1), "Edge must have at least one relation");
@@ -1121,21 +1142,36 @@ class ComputeCtx {
1121
1142
  );
1122
1143
  });
1123
1144
  }
1124
- get includedElements() {
1125
- return /* @__PURE__ */ new Set([
1126
- ...this.explicits,
1127
- ...this.ctxEdges.flatMap((e) => [e.source, e.target])
1128
- ]);
1129
- }
1130
- get resolvedElements() {
1131
- return /* @__PURE__ */ new Set([
1132
- ...this.explicits,
1133
- ...this.implicits,
1134
- ...this.ctxEdges.flatMap((e) => [e.source, e.target])
1135
- ]);
1136
- }
1137
- get edges() {
1138
- return this.ctxEdges;
1145
+ linkNodesAndEdges(nodesMap, edges) {
1146
+ for (const edge of edges) {
1147
+ const source = nodesMap.get(edge.source);
1148
+ const target = nodesMap.get(edge.target);
1149
+ invariant(source, `Source node ${edge.source} not found`);
1150
+ invariant(target, `Target node ${edge.target} not found`);
1151
+ const sourceAncestors = this.ancestorsOf(source, nodesMap);
1152
+ const targetAncestors = this.ancestorsOf(target, nodesMap);
1153
+ const edgeParent = last(
1154
+ commonHead(
1155
+ reverse(sourceAncestors),
1156
+ reverse(targetAncestors)
1157
+ )
1158
+ );
1159
+ edge.parent = edgeParent?.id ?? null;
1160
+ source.outEdges.push(edge.id);
1161
+ target.inEdges.push(edge.id);
1162
+ for (const sourceAncestor of sourceAncestors) {
1163
+ if (sourceAncestor === edgeParent) {
1164
+ break;
1165
+ }
1166
+ sourceAncestor.outEdges.push(edge.id);
1167
+ }
1168
+ for (const targetAncestor of targetAncestors) {
1169
+ if (targetAncestor === edgeParent) {
1170
+ break;
1171
+ }
1172
+ targetAncestor.inEdges.push(edge.id);
1173
+ }
1174
+ }
1139
1175
  }
1140
1176
  addEdges(edges) {
1141
1177
  const added = [];
@@ -1224,9 +1260,6 @@ class ComputeCtx {
1224
1260
  }),
1225
1261
  filter(isNonNull)
1226
1262
  );
1227
- if (excludedImplicits.size === 0) {
1228
- return;
1229
- }
1230
1263
  this.ctxEdges = ctxEdges;
1231
1264
  const remaining = this.includedElements;
1232
1265
  if (remaining.size === 0) {
@@ -1403,6 +1436,19 @@ class ComputeCtx {
1403
1436
  }
1404
1437
  nonexhaustive(expr);
1405
1438
  }
1439
+ ancestorsOf(node, nodesMap) {
1440
+ const ancestors = [];
1441
+ let parent = node.parent;
1442
+ while (parent) {
1443
+ const parentNode = nodesMap.get(parent);
1444
+ if (!parentNode) {
1445
+ break;
1446
+ }
1447
+ ancestors.push(parentNode);
1448
+ parent = parentNode.parent;
1449
+ }
1450
+ return ancestors;
1451
+ }
1406
1452
  getEdgeLabel(relation) {
1407
1453
  const labelParts = [];
1408
1454
  if (isTruthy(relation.title)) {
@@ -1480,7 +1526,8 @@ class DynamicViewComputeCtx {
1480
1526
  const {
1481
1527
  docUri: _docUri,
1482
1528
  // exclude docUri
1483
- rules,
1529
+ rules: _rules,
1530
+ // exclude rules
1484
1531
  steps: viewSteps,
1485
1532
  ...view
1486
1533
  } = this.view;
@@ -1500,6 +1547,7 @@ class DynamicViewComputeCtx {
1500
1547
  }
1501
1548
  stepNum++;
1502
1549
  }
1550
+ const rules = resolveGlobalRulesInDynamicView(this.view, this.graph.globals);
1503
1551
  for (const rule of rules) {
1504
1552
  if (isDynamicViewIncludeRule(rule)) {
1505
1553
  for (const expr of rule.include) {
@@ -1653,7 +1701,13 @@ class LikeC4ModelGraph {
1653
1701
  // Relationships inside the element, among descendants
1654
1702
  #internal = new MapRelations();
1655
1703
  #cacheAscendingSiblings = /* @__PURE__ */ new Map();
1656
- constructor({ elements, relations }) {
1704
+ globals;
1705
+ constructor({ elements, relations, globals }) {
1706
+ this.globals = globals ?? {
1707
+ predicates: {},
1708
+ dynamicPredicates: {},
1709
+ styles: {}
1710
+ };
1657
1711
  for (const el of Object.values(elements)) {
1658
1712
  this.addElement(el);
1659
1713
  }