@likec4/language-server 1.14.0 → 1.15.1

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 (46) 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/likec4lib.cjs +0 -1
  13. package/dist/likec4lib.mjs +0 -1
  14. package/dist/model-graph/index.cjs +1 -1
  15. package/dist/model-graph/index.mjs +1 -1
  16. package/dist/shared/{language-server.CbDa016p.cjs → language-server.B8s2wfT_.cjs} +279 -68
  17. package/dist/shared/{language-server.DFLaUdYu.mjs → language-server.BT4WTbFI.mjs} +281 -70
  18. package/dist/shared/{language-server.DZYziEuF.mjs → language-server.BuChFlda.mjs} +231 -44
  19. package/dist/shared/{language-server.CO6aEDQm.d.mts → language-server.DfMwkd2l.d.mts} +46 -15
  20. package/dist/shared/{language-server.CITj0XN3.cjs → language-server.DfjkvknB.cjs} +230 -43
  21. package/dist/shared/{language-server.Cqyh6-9S.d.cts → language-server.U2piOAVt.d.cts} +46 -15
  22. package/dist/shared/{language-server.BI99piRy.d.ts → language-server.j-ShR6as.d.ts} +46 -15
  23. package/package.json +10 -10
  24. package/src/ast.ts +1 -0
  25. package/src/formatting/LikeC4Formatter.ts +44 -4
  26. package/src/generated/ast.ts +103 -19
  27. package/src/generated/grammar.ts +1 -1
  28. package/src/generated-lib/icons.ts +0 -1
  29. package/src/like-c4.langium +32 -9
  30. package/src/lsp/CompletionProvider.ts +70 -2
  31. package/src/lsp/SemanticTokenProvider.ts +3 -1
  32. package/src/model/model-builder.ts +13 -7
  33. package/src/model/model-parser.ts +71 -20
  34. package/src/model-graph/compute-view/__test__/fixture.ts +96 -29
  35. package/src/model-graph/compute-view/compute.ts +223 -40
  36. package/src/model-graph/compute-view/predicates.ts +12 -6
  37. package/src/model-graph/utils/applyCustomElementProperties.ts +18 -4
  38. package/src/model-graph/utils/applyCustomRelationProperties.ts +9 -39
  39. package/src/model-graph/utils/applyViewRuleStyles.ts +30 -25
  40. package/src/model-graph/utils/buildComputeNodes.ts +69 -17
  41. package/src/model-graph/utils/elementExpressionToPredicate.ts +3 -1
  42. package/src/model-graph/utils/relationExpressionToPredicates.ts +43 -0
  43. package/src/model-graph/utils/sortNodes.ts +11 -7
  44. package/src/references/scope-computation.ts +3 -2
  45. package/src/validation/index.ts +2 -2
  46. package/src/validation/specification.ts +4 -4
@@ -61,8 +61,19 @@ function elementExprToPredicate(target) {
61
61
  core.nonexhaustive(target);
62
62
  }
63
63
 
64
+ function flattenGroupRules(guard) {
65
+ return (rule) => {
66
+ if (core.isViewRuleGroup(rule)) {
67
+ return rule.groupRules.flatMap(flattenGroupRules(guard));
68
+ }
69
+ if (core.isViewRulePredicate(rule)) {
70
+ return "include" in rule ? rule.include.filter(guard) : [];
71
+ }
72
+ return [];
73
+ };
74
+ }
64
75
  function applyCustomElementProperties(_rules, _nodes) {
65
- const rules = _rules.flatMap((r) => "include" in r ? r.include.filter(core.Expr.isCustomElement) : []);
76
+ const rules = _rules.flatMap(flattenGroupRules(core.Expr.isCustomElement));
66
77
  if (rules.length === 0) {
67
78
  return _nodes;
68
79
  }
@@ -74,7 +85,7 @@ function applyCustomElementProperties(_rules, _nodes) {
74
85
  const notEmpty = !remeda.isEmpty(rest);
75
86
  const satisfies = elementExprToPredicate(expr);
76
87
  nodes.forEach((node, i) => {
77
- if (!satisfies(node)) {
88
+ if (core.ComputedNode.isNodesGroup(node) || !satisfies(node)) {
78
89
  return;
79
90
  }
80
91
  if (notEmpty) {
@@ -110,7 +121,9 @@ function applyCustomElementProperties(_rules, _nodes) {
110
121
  function relationExpressionToPredicates(expr) {
111
122
  switch (true) {
112
123
  case core.Expr.isRelationWhere(expr):
113
- return relationExpressionToPredicates(expr.where.expr);
124
+ const predicate = relationExpressionToPredicates(expr.where.expr);
125
+ const where = core.whereOperatorAsPredicate(expr.where.condition);
126
+ return (e) => predicate(e) && where(e);
114
127
  case core.Expr.isRelation(expr): {
115
128
  const isSource = elementExprToPredicate(expr.source);
116
129
  const isTarget = elementExprToPredicate(expr.target);
@@ -134,8 +147,9 @@ function relationExpressionToPredicates(expr) {
134
147
  core.nonexhaustive(expr);
135
148
  }
136
149
  }
150
+
137
151
  function applyCustomRelationProperties(_rules, nodes, _edges) {
138
- const rules = _rules.flatMap((r) => "include" in r ? r.include.filter(core.Expr.isCustomRelationExpr) : []);
152
+ const rules = _rules.flatMap(flattenGroupRules(core.Expr.isCustomRelationExpr));
139
153
  const edges = Array.from(_edges);
140
154
  if (rules.length === 0 || edges.length === 0) {
141
155
  return edges;
@@ -151,12 +165,12 @@ function applyCustomRelationProperties(_rules, nodes, _edges) {
151
165
  if (!source || !target) {
152
166
  return;
153
167
  }
154
- if (satisfies({ source, target })) {
168
+ if (satisfies({ source, target, ...remeda.pick(edge, ["kind", "tags"]) })) {
155
169
  edges[i] = {
156
170
  ...edge,
171
+ ...props,
157
172
  label: title ?? edge.label,
158
- isCustomized: true,
159
- ...props
173
+ isCustomized: true
160
174
  };
161
175
  }
162
176
  });
@@ -178,29 +192,34 @@ function applyViewRuleStyles(_rules, nodes) {
178
192
  }
179
193
  predicates.push(elementExprToPredicate(target));
180
194
  }
181
- remeda.filter(nodes, remeda.anyPass(predicates)).forEach((n) => {
182
- n.shape = rule.style.shape ?? n.shape;
183
- n.color = rule.style.color ?? n.color;
184
- if (remeda.isDefined(rule.style.icon)) {
185
- n.icon = rule.style.icon;
186
- }
187
- if (remeda.isDefined(rule.notation)) {
188
- n.notation = rule.notation;
189
- }
190
- let styleOverride;
191
- if (remeda.isDefined(rule.style.border)) {
192
- styleOverride = { border: rule.style.border };
193
- }
194
- if (remeda.isDefined(rule.style.opacity)) {
195
- styleOverride = { ...styleOverride, opacity: rule.style.opacity };
196
- }
197
- if (styleOverride) {
198
- n.style = {
199
- ...n.style,
200
- ...styleOverride
201
- };
202
- }
203
- });
195
+ remeda.pipe(
196
+ nodes,
197
+ remeda.filter(remeda.isNot(core.ComputedNode.isNodesGroup)),
198
+ remeda.filter(remeda.anyPass(predicates)),
199
+ remeda.forEach((n) => {
200
+ n.shape = rule.style.shape ?? n.shape;
201
+ n.color = rule.style.color ?? n.color;
202
+ if (remeda.isDefined(rule.style.icon)) {
203
+ n.icon = rule.style.icon;
204
+ }
205
+ if (remeda.isDefined(rule.notation)) {
206
+ n.notation = rule.notation;
207
+ }
208
+ let styleOverride;
209
+ if (remeda.isDefined(rule.style.border)) {
210
+ styleOverride = { border: rule.style.border };
211
+ }
212
+ if (remeda.isDefined(rule.style.opacity)) {
213
+ styleOverride = { ...styleOverride, opacity: rule.style.opacity };
214
+ }
215
+ if (styleOverride) {
216
+ n.style = {
217
+ ...n.style,
218
+ ...styleOverride
219
+ };
220
+ }
221
+ })
222
+ );
204
223
  }
205
224
  return nodes;
206
225
  }
@@ -216,40 +235,80 @@ function updateDepthOfAncestors(node, nodes) {
216
235
  node = parentNd;
217
236
  }
218
237
  }
219
- function buildComputeNodes(elements) {
220
- return Array.from(elements).sort(core.compareByFqnHierarchically).reduce((map, { id, color, shape, style, ...el }) => {
238
+ function buildComputeNodes(elements, groups) {
239
+ const nodesMap = /* @__PURE__ */ new Map();
240
+ const elementToGroup = /* @__PURE__ */ new Map();
241
+ groups?.forEach(({ id, parent, viewRule, explicits }) => {
242
+ if (parent) {
243
+ core.nonNullable(nodesMap.get(parent), `Parent group node ${parent} not found`).children.push(id);
244
+ }
245
+ nodesMap.set(id, {
246
+ id,
247
+ parent,
248
+ kind: core.ElementKind.Group,
249
+ title: viewRule.title ?? "",
250
+ color: viewRule.color ?? "muted",
251
+ shape: "rectangle",
252
+ children: [],
253
+ inEdges: [],
254
+ outEdges: [],
255
+ level: 0,
256
+ depth: 0,
257
+ description: null,
258
+ technology: null,
259
+ tags: null,
260
+ links: null,
261
+ style: {
262
+ border: viewRule.border ?? "dashed",
263
+ opacity: viewRule.opacity ?? 0
264
+ }
265
+ });
266
+ for (const e of explicits) {
267
+ elementToGroup.set(e.id, id);
268
+ }
269
+ });
270
+ return Array.from(elements).sort(core.compareByFqnHierarchically).reduce((map, { id, color, shape, style, kind, title, ...el }) => {
221
271
  let parent = core.parentFqn(id);
222
272
  let level = 0;
273
+ let parentNd;
223
274
  while (parent) {
224
- const parentNd = map.get(parent);
275
+ parentNd = map.get(parent);
225
276
  if (parentNd) {
226
- if (parentNd.children.length == 0) {
227
- parentNd.depth = 1;
228
- updateDepthOfAncestors(parentNd, map);
229
- }
230
- parentNd.children.push(id);
231
- level = parentNd.level + 1;
232
277
  break;
233
278
  }
234
279
  parent = core.parentFqn(parent);
235
280
  }
281
+ if (!parentNd && elementToGroup.has(id)) {
282
+ parent = elementToGroup.get(id);
283
+ parentNd = map.get(parent);
284
+ }
285
+ if (parentNd) {
286
+ if (parentNd.children.length == 0) {
287
+ parentNd.depth = 1;
288
+ updateDepthOfAncestors(parentNd, map);
289
+ }
290
+ parentNd.children.push(id);
291
+ level = parentNd.level + 1;
292
+ }
236
293
  const node = {
237
- ...el,
238
294
  id,
239
295
  parent,
240
- level,
296
+ kind,
297
+ title,
241
298
  color: color ?? core.DefaultThemeColor,
242
299
  shape: shape ?? core.DefaultElementShape,
243
300
  children: [],
244
301
  inEdges: [],
245
302
  outEdges: [],
303
+ level,
304
+ ...el,
246
305
  style: {
247
306
  ...style
248
307
  }
249
308
  };
250
309
  map.set(id, node);
251
310
  return map;
252
- }, /* @__PURE__ */ new Map());
311
+ }, nodesMap);
253
312
  }
254
313
 
255
314
  function buildElementNotations(nodes) {
@@ -321,9 +380,16 @@ function sortNodes({
321
380
  nodes,
322
381
  edges
323
382
  }) {
324
- if (edges.length === 0) {
383
+ if (nodes.length < 2) {
325
384
  return nodes;
326
385
  }
386
+ if (edges.length === 0) {
387
+ return remeda.pipe(
388
+ nodes,
389
+ remeda.sort(core.compareByFqnHierarchically),
390
+ remeda.tap(sortChildren)
391
+ );
392
+ }
327
393
  const g = new Graph({
328
394
  compound: false,
329
395
  directed: true,
@@ -358,15 +424,15 @@ function sortNodes({
358
424
  if (sources.length === 0) {
359
425
  sources = remeda.pipe(
360
426
  nodes,
361
- remeda.sort(core.compareByFqnHierarchically),
362
427
  remeda.filter((n) => n.inEdges.length === 0 || n.parent === null),
428
+ remeda.sort(core.compareByFqnHierarchically),
363
429
  remeda.map((n) => n.id)
364
430
  );
365
431
  }
366
432
  const orderedIds = postorder(g, sources).reverse();
367
433
  const sorted = orderedIds.map(getNode);
368
434
  if (sorted.length < nodes.length) {
369
- const unsorted = remeda.difference(nodes, sorted);
435
+ const unsorted = remeda.difference(nodes, sorted).sort(core.compareByFqnHierarchically);
370
436
  sorted.push(...unsorted);
371
437
  }
372
438
  core.invariant(sorted.length === nodes.length, "Not all nodes were processed by graphlib");
@@ -453,7 +519,9 @@ function includeWildcardRef(_expr, where = NoFilter) {
453
519
  ])
454
520
  ];
455
521
  for (const el of children) {
456
- this.addEdges(this.graph.anyEdgesBetween(el, neighbours));
522
+ this.addEdges(this.graph.anyEdgesBetween(el, neighbours)).forEach((edge) => {
523
+ this.addImplicit(edge.source, edge.target);
524
+ });
457
525
  }
458
526
  if (!hasChildren && _elRoot) {
459
527
  const edgesWithSiblings = this.graph.anyEdgesBetween(_elRoot, this.graph.siblings(root));
@@ -644,8 +712,9 @@ function includeIncomingExpr(expr, where) {
644
712
  if (edges.length === 0) {
645
713
  return;
646
714
  }
647
- this.addEdges(edges);
648
- this.addImplicit(...edges.map((e) => e.target));
715
+ this.addEdges(edges).forEach((edge) => {
716
+ this.addImplicit(edge.target);
717
+ });
649
718
  }
650
719
  function excludeIncomingExpr(expr, where) {
651
720
  let relations = filterRelations(edgesIncomingExpr.call(this, expr.incoming), where);
@@ -685,8 +754,9 @@ function includeOutgoingExpr(expr, where) {
685
754
  if (edges.length === 0) {
686
755
  return;
687
756
  }
688
- this.addEdges(edges);
689
- this.addImplicit(...edges.map((e) => e.source));
757
+ this.addEdges(edges).forEach((edge) => {
758
+ this.addImplicit(edge.source);
759
+ });
690
760
  }
691
761
  function excludeOutgoingExpr(expr, where) {
692
762
  const relations = filterRelations(edgesOutgoingExpr.call(this, expr.outgoing), where);
@@ -771,7 +841,9 @@ function includeRelationExpr(expr, where) {
771
841
  if (expr.isBidirectional === true) {
772
842
  edges.push(...this.graph.edgesBetween(targets, sources));
773
843
  }
774
- this.addEdges(filterEdges(edges, where));
844
+ this.addEdges(filterEdges(edges, where)).forEach((edge) => {
845
+ this.activeGroup.addImplicit(edge.source, edge.target);
846
+ });
775
847
  }
776
848
  function excludeRelationExpr(expr, where) {
777
849
  const isSource = elementExprToPredicate(expr.source);
@@ -794,6 +866,50 @@ function compareEdges(a, b) {
794
866
  { source: b.source.id, target: b.target.id }
795
867
  );
796
868
  }
869
+ class NodesGroup {
870
+ constructor(id, viewRule, parent = null) {
871
+ this.id = id;
872
+ this.viewRule = viewRule;
873
+ this.parent = parent;
874
+ }
875
+ static kind = core.ElementKind.Group;
876
+ static root() {
877
+ return new NodesGroup("@root", { title: null, groupRules: [] });
878
+ }
879
+ static is(node) {
880
+ return node.kind === NodesGroup.kind;
881
+ }
882
+ explicits = /* @__PURE__ */ new Set();
883
+ implicits = /* @__PURE__ */ new Set();
884
+ /**
885
+ * Add element explicitly
886
+ * Included even without relationships
887
+ */
888
+ addElement(...el) {
889
+ for (const r of el) {
890
+ this.explicits.add(r);
891
+ this.implicits.add(r);
892
+ }
893
+ }
894
+ /**
895
+ * Add element implicitly
896
+ * Included if only has relationships
897
+ */
898
+ addImplicit(...el) {
899
+ for (const r of el) {
900
+ this.implicits.add(r);
901
+ }
902
+ }
903
+ excludeElement(...excludes) {
904
+ for (const el of excludes) {
905
+ this.explicits.delete(el);
906
+ this.implicits.delete(el);
907
+ }
908
+ }
909
+ isEmpty() {
910
+ return this.explicits.size === 0 && this.implicits.size === 0;
911
+ }
912
+ }
797
913
  class ComputeCtx {
798
914
  constructor(view, graph) {
799
915
  this.view = view;
@@ -803,6 +919,16 @@ class ComputeCtx {
803
919
  explicits = /* @__PURE__ */ new Set();
804
920
  implicits = /* @__PURE__ */ new Set();
805
921
  ctxEdges = [];
922
+ /**
923
+ * Root group - not included in the groups
924
+ * But used to accumulate elements that are not in any group
925
+ */
926
+ __rootGroup = NodesGroup.root();
927
+ groups = [];
928
+ activeGroupStack = [];
929
+ get activeGroup() {
930
+ return this.activeGroupStack[0] ?? this.__rootGroup;
931
+ }
806
932
  static elementView(view, graph) {
807
933
  return new ComputeCtx(view, graph).compute();
808
934
  }
@@ -814,14 +940,30 @@ class ComputeCtx {
814
940
  rules,
815
941
  ...view
816
942
  } = this.view;
817
- const viewPredicates = rules.filter(core.isViewRulePredicate);
943
+ const viewPredicates = rules.filter(remeda.anyPass([core.isViewRulePredicate, core.isViewRuleGroup]));
818
944
  if (this.root && viewPredicates.length == 0) {
819
945
  this.addElement(this.graph.element(this.root));
820
946
  }
821
947
  this.processPredicates(viewPredicates);
822
948
  this.removeRedundantImplicitEdges();
949
+ if (this.groups.length > 0) {
950
+ this.cleanGroupElements();
951
+ }
823
952
  const elements = [...this.includedElements];
824
- const nodesMap = buildComputeNodes(elements);
953
+ const nodesMap = buildComputeNodes(elements, this.groups);
954
+ const ancestorsOf = (node) => {
955
+ const ancestors = [];
956
+ let parent = node.parent;
957
+ while (parent) {
958
+ const parentNode = nodesMap.get(parent);
959
+ if (!parentNode) {
960
+ break;
961
+ }
962
+ ancestors.push(parentNode);
963
+ parent = parentNode.parent;
964
+ }
965
+ return ancestors;
966
+ };
825
967
  const edgesMap = /* @__PURE__ */ new Map();
826
968
  const edges = this.computeEdges();
827
969
  for (const edge of edges) {
@@ -830,25 +972,37 @@ class ComputeCtx {
830
972
  const target = nodesMap.get(edge.target);
831
973
  core.invariant(source, `Source node ${edge.source} not found`);
832
974
  core.invariant(target, `Target node ${edge.target} not found`);
833
- while (edge.parent && !nodesMap.has(edge.parent)) {
834
- edge.parent = core.parentFqn(edge.parent);
835
- }
975
+ const sourceAncestors = ancestorsOf(source);
976
+ const targetAncestors = ancestorsOf(target);
977
+ const edgeParent = remeda.last(
978
+ core.commonHead(
979
+ remeda.reverse(sourceAncestors),
980
+ remeda.reverse(targetAncestors)
981
+ )
982
+ );
983
+ edge.parent = edgeParent?.id ?? null;
836
984
  source.outEdges.push(edge.id);
837
985
  target.inEdges.push(edge.id);
838
- for (const sourceAncestor of core.ancestorsFqn(edge.source)) {
839
- if (sourceAncestor === edge.parent) {
986
+ for (const sourceAncestor of sourceAncestors) {
987
+ if (sourceAncestor === edgeParent) {
840
988
  break;
841
989
  }
842
- nodesMap.get(sourceAncestor)?.outEdges.push(edge.id);
990
+ sourceAncestor.outEdges.push(edge.id);
843
991
  }
844
- for (const targetAncestor of core.ancestorsFqn(edge.target)) {
845
- if (targetAncestor === edge.parent) {
992
+ for (const targetAncestor of targetAncestors) {
993
+ if (targetAncestor === edgeParent) {
846
994
  break;
847
995
  }
848
- nodesMap.get(targetAncestor)?.inEdges.push(edge.id);
996
+ targetAncestor.inEdges.push(edge.id);
849
997
  }
850
998
  }
851
- const initialSort = elements.flatMap((e) => nodesMap.get(e.id) ?? []);
999
+ let initialSort = elements.map((e) => core.nonNullable(nodesMap.get(e.id), `Node ${e.id} not found in nodesMap`));
1000
+ if (this.groups.length > 0) {
1001
+ initialSort = remeda.concat(
1002
+ this.groups.map((g) => core.nonNullable(nodesMap.get(g.id), `Node ${g.id} not found in nodesMap`)),
1003
+ initialSort
1004
+ );
1005
+ }
852
1006
  const nodes = applyCustomElementProperties(
853
1007
  rules,
854
1008
  applyViewRuleStyles(
@@ -994,6 +1148,7 @@ class ComputeCtx {
994
1148
  return this.ctxEdges;
995
1149
  }
996
1150
  addEdges(edges) {
1151
+ const added = [];
997
1152
  for (const e of edges) {
998
1153
  if (!remeda.hasAtLeast(e.relations, 1)) {
999
1154
  continue;
@@ -1003,10 +1158,13 @@ class ComputeCtx {
1003
1158
  );
1004
1159
  if (existing) {
1005
1160
  existing.relations = remeda.unique([...existing.relations, ...e.relations]);
1161
+ added.push(existing);
1006
1162
  continue;
1007
1163
  }
1164
+ added.push(e);
1008
1165
  this.ctxEdges.push(e);
1009
1166
  }
1167
+ return added;
1010
1168
  }
1011
1169
  /**
1012
1170
  * Add element explicitly
@@ -1014,6 +1172,14 @@ class ComputeCtx {
1014
1172
  */
1015
1173
  addElement(...el) {
1016
1174
  for (const r of el) {
1175
+ if (!this.explicits.has(r)) {
1176
+ this.activeGroup.addElement(r);
1177
+ } else if (this.activeGroup !== this.__rootGroup) {
1178
+ this.groups.forEach((g) => {
1179
+ g.implicits.delete(r);
1180
+ });
1181
+ this.activeGroup.addImplicit(r);
1182
+ }
1017
1183
  this.explicits.add(r);
1018
1184
  this.implicits.add(r);
1019
1185
  }
@@ -1025,7 +1191,13 @@ class ComputeCtx {
1025
1191
  addImplicit(...el) {
1026
1192
  for (const r of el) {
1027
1193
  this.implicits.add(r);
1194
+ if (this.activeGroup !== this.__rootGroup) {
1195
+ this.groups.forEach((g) => {
1196
+ g.implicits.delete(r);
1197
+ });
1198
+ }
1028
1199
  }
1200
+ this.activeGroup.addImplicit(...el);
1029
1201
  }
1030
1202
  excludeElement(...excludes) {
1031
1203
  for (const el of excludes) {
@@ -1033,12 +1205,11 @@ class ComputeCtx {
1033
1205
  this.explicits.delete(el);
1034
1206
  this.implicits.delete(el);
1035
1207
  }
1208
+ this.__rootGroup.excludeElement(...excludes);
1209
+ this.groups.forEach((g) => {
1210
+ g.excludeElement(...excludes);
1211
+ });
1036
1212
  }
1037
- // protected excludeImplicit(...excludes: Element[]) {
1038
- // for (const el of excludes) {
1039
- // this.implicits.delete(el)
1040
- // }
1041
- // }
1042
1213
  excludeRelation(...relations) {
1043
1214
  if (relations.length === 0) {
1044
1215
  return;
@@ -1070,11 +1241,15 @@ class ComputeCtx {
1070
1241
  const remaining = this.includedElements;
1071
1242
  if (remaining.size === 0) {
1072
1243
  this.implicits.clear();
1244
+ this.__rootGroup.implicits.clear();
1245
+ this.groups.forEach((g) => g.implicits.clear());
1073
1246
  return;
1074
1247
  }
1075
1248
  for (const el of excludedImplicits) {
1076
1249
  if (!remaining.has(el)) {
1077
1250
  this.implicits.delete(el);
1251
+ this.__rootGroup.implicits.delete(el);
1252
+ this.groups.forEach((g) => g.implicits.delete(el));
1078
1253
  }
1079
1254
  }
1080
1255
  }
@@ -1082,6 +1257,9 @@ class ComputeCtx {
1082
1257
  this.explicits.clear();
1083
1258
  this.implicits.clear();
1084
1259
  this.ctxEdges = [];
1260
+ this.__rootGroup = NodesGroup.root();
1261
+ this.groups = [];
1262
+ this.activeGroupStack = [];
1085
1263
  }
1086
1264
  // Filter out edges if there are edges between descendants
1087
1265
  // i.e. remove implicit edges, derived from childs
@@ -1124,8 +1302,41 @@ class ComputeCtx {
1124
1302
  return acc;
1125
1303
  }, []);
1126
1304
  }
1305
+ cleanGroupElements() {
1306
+ const unprocessed = new Set(remeda.difference(
1307
+ [...this.includedElements],
1308
+ [...this.__rootGroup.explicits]
1309
+ ));
1310
+ for (const group of this.groups) {
1311
+ const explicits = [...group.explicits];
1312
+ group.explicits.clear();
1313
+ for (const el of explicits) {
1314
+ if (unprocessed.delete(el)) {
1315
+ group.explicits.add(el);
1316
+ }
1317
+ }
1318
+ }
1319
+ for (const group of this.groups) {
1320
+ for (const el of group.implicits) {
1321
+ if (unprocessed.delete(el)) {
1322
+ group.explicits.add(el);
1323
+ }
1324
+ }
1325
+ group.implicits.clear();
1326
+ }
1327
+ }
1127
1328
  processPredicates(viewRules) {
1128
1329
  for (const rule of viewRules) {
1330
+ if (core.isViewRuleGroup(rule)) {
1331
+ const parent = remeda.first(this.activeGroupStack);
1332
+ const groupId = `@gr${this.groups.length + 1}`;
1333
+ const group = new NodesGroup(groupId, rule, parent?.id ?? null);
1334
+ this.groups.push(group);
1335
+ this.activeGroupStack.unshift(group);
1336
+ this.processPredicates(rule.groupRules);
1337
+ this.activeGroupStack.shift();
1338
+ continue;
1339
+ }
1129
1340
  const isInclude = "include" in rule;
1130
1341
  const exprs = rule.include ?? rule.exclude;
1131
1342
  for (const expr of exprs) {