@likec4/language-server 1.13.0 → 1.15.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 (48) 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.80ITEDo5.cjs} +272 -64
  17. package/dist/shared/{language-server.C2ebP2pZ.cjs → language-server.BUtiWTKg.cjs} +383 -32
  18. package/dist/shared/{language-server.DFLaUdYu.mjs → language-server.DXC9g4_f.mjs} +274 -66
  19. package/dist/shared/{language-server.ryB8CivX.d.mts → language-server.DfMwkd2l.d.mts} +81 -15
  20. package/dist/shared/{language-server.Cnq_hgfm.d.cts → language-server.U2piOAVt.d.cts} +81 -15
  21. package/dist/shared/{language-server.eY70DuKx.d.ts → language-server.j-ShR6as.d.ts} +81 -15
  22. package/dist/shared/{language-server.CrE0nFSB.mjs → language-server.zY53FGJE.mjs} +385 -34
  23. package/package.json +12 -12
  24. package/src/ast.ts +11 -0
  25. package/src/generated/ast.ts +177 -17
  26. package/src/generated/grammar.ts +1 -1
  27. package/src/generated-lib/icons.ts +0 -1
  28. package/src/like-c4.langium +50 -4
  29. package/src/lsp/CompletionProvider.ts +70 -2
  30. package/src/model/model-builder.ts +25 -3
  31. package/src/model/model-parser.ts +150 -33
  32. package/src/model-graph/compute-view/__test__/fixture.ts +45 -4
  33. package/src/model-graph/compute-view/compute.ts +223 -40
  34. package/src/model-graph/compute-view/predicates.ts +12 -6
  35. package/src/model-graph/utils/applyCustomElementProperties.ts +18 -4
  36. package/src/model-graph/utils/applyCustomRelationProperties.ts +2 -1
  37. package/src/model-graph/utils/applyViewRuleStyles.ts +30 -25
  38. package/src/model-graph/utils/buildComputeNodes.ts +69 -17
  39. package/src/model-graph/utils/elementExpressionToPredicate.ts +3 -1
  40. package/src/model-graph/utils/sortNodes.ts +11 -7
  41. package/src/references/scope-computation.ts +24 -1
  42. package/src/validation/index.ts +4 -0
  43. package/src/validation/specification.ts +30 -0
  44. package/src/view-utils/index.ts +1 -0
  45. package/src/view-utils/resolve-global-rules.ts +72 -0
  46. package/dist/shared/language-server.B-9_mDoo.d.mts +0 -1238
  47. package/dist/shared/language-server.C3oS5yhF.d.cts +0 -1238
  48. package/dist/shared/language-server.r5AXAWzc.d.ts +0 -1238
@@ -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) {
@@ -135,7 +146,7 @@ function relationExpressionToPredicates(expr) {
135
146
  }
136
147
  }
137
148
  function applyCustomRelationProperties(_rules, nodes, _edges) {
138
- const rules = _rules.flatMap((r) => "include" in r ? r.include.filter(core.Expr.isCustomRelationExpr) : []);
149
+ const rules = _rules.flatMap(flattenGroupRules(core.Expr.isCustomRelationExpr));
139
150
  const edges = Array.from(_edges);
140
151
  if (rules.length === 0 || edges.length === 0) {
141
152
  return edges;
@@ -178,29 +189,34 @@ function applyViewRuleStyles(_rules, nodes) {
178
189
  }
179
190
  predicates.push(elementExprToPredicate(target));
180
191
  }
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
- });
192
+ remeda.pipe(
193
+ nodes,
194
+ remeda.filter(remeda.isNot(core.ComputedNode.isNodesGroup)),
195
+ remeda.filter(remeda.anyPass(predicates)),
196
+ remeda.forEach((n) => {
197
+ n.shape = rule.style.shape ?? n.shape;
198
+ n.color = rule.style.color ?? n.color;
199
+ if (remeda.isDefined(rule.style.icon)) {
200
+ n.icon = rule.style.icon;
201
+ }
202
+ if (remeda.isDefined(rule.notation)) {
203
+ n.notation = rule.notation;
204
+ }
205
+ let styleOverride;
206
+ if (remeda.isDefined(rule.style.border)) {
207
+ styleOverride = { border: rule.style.border };
208
+ }
209
+ if (remeda.isDefined(rule.style.opacity)) {
210
+ styleOverride = { ...styleOverride, opacity: rule.style.opacity };
211
+ }
212
+ if (styleOverride) {
213
+ n.style = {
214
+ ...n.style,
215
+ ...styleOverride
216
+ };
217
+ }
218
+ })
219
+ );
204
220
  }
205
221
  return nodes;
206
222
  }
@@ -216,40 +232,80 @@ function updateDepthOfAncestors(node, nodes) {
216
232
  node = parentNd;
217
233
  }
218
234
  }
219
- function buildComputeNodes(elements) {
220
- return Array.from(elements).sort(core.compareByFqnHierarchically).reduce((map, { id, color, shape, style, ...el }) => {
235
+ function buildComputeNodes(elements, groups) {
236
+ const nodesMap = /* @__PURE__ */ new Map();
237
+ const elementToGroup = /* @__PURE__ */ new Map();
238
+ groups?.forEach(({ id, parent, viewRule, explicits }) => {
239
+ if (parent) {
240
+ core.nonNullable(nodesMap.get(parent), `Parent group node ${parent} not found`).children.push(id);
241
+ }
242
+ nodesMap.set(id, {
243
+ id,
244
+ parent,
245
+ kind: core.ElementKind.Group,
246
+ title: viewRule.title ?? "",
247
+ color: viewRule.color ?? "muted",
248
+ shape: "rectangle",
249
+ children: [],
250
+ inEdges: [],
251
+ outEdges: [],
252
+ level: 0,
253
+ depth: 0,
254
+ description: null,
255
+ technology: null,
256
+ tags: null,
257
+ links: null,
258
+ style: {
259
+ border: viewRule.border ?? "dashed",
260
+ opacity: viewRule.opacity ?? 0
261
+ }
262
+ });
263
+ for (const e of explicits) {
264
+ elementToGroup.set(e.id, id);
265
+ }
266
+ });
267
+ return Array.from(elements).sort(core.compareByFqnHierarchically).reduce((map, { id, color, shape, style, kind, title, ...el }) => {
221
268
  let parent = core.parentFqn(id);
222
269
  let level = 0;
270
+ let parentNd;
223
271
  while (parent) {
224
- const parentNd = map.get(parent);
272
+ parentNd = map.get(parent);
225
273
  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
274
  break;
233
275
  }
234
276
  parent = core.parentFqn(parent);
235
277
  }
278
+ if (!parentNd && elementToGroup.has(id)) {
279
+ parent = elementToGroup.get(id);
280
+ parentNd = map.get(parent);
281
+ }
282
+ if (parentNd) {
283
+ if (parentNd.children.length == 0) {
284
+ parentNd.depth = 1;
285
+ updateDepthOfAncestors(parentNd, map);
286
+ }
287
+ parentNd.children.push(id);
288
+ level = parentNd.level + 1;
289
+ }
236
290
  const node = {
237
- ...el,
238
291
  id,
239
292
  parent,
240
- level,
293
+ kind,
294
+ title,
241
295
  color: color ?? core.DefaultThemeColor,
242
296
  shape: shape ?? core.DefaultElementShape,
243
297
  children: [],
244
298
  inEdges: [],
245
299
  outEdges: [],
300
+ level,
301
+ ...el,
246
302
  style: {
247
303
  ...style
248
304
  }
249
305
  };
250
306
  map.set(id, node);
251
307
  return map;
252
- }, /* @__PURE__ */ new Map());
308
+ }, nodesMap);
253
309
  }
254
310
 
255
311
  function buildElementNotations(nodes) {
@@ -321,9 +377,16 @@ function sortNodes({
321
377
  nodes,
322
378
  edges
323
379
  }) {
324
- if (edges.length === 0) {
380
+ if (nodes.length < 2) {
325
381
  return nodes;
326
382
  }
383
+ if (edges.length === 0) {
384
+ return remeda.pipe(
385
+ nodes,
386
+ remeda.sort(core.compareByFqnHierarchically),
387
+ remeda.tap(sortChildren)
388
+ );
389
+ }
327
390
  const g = new Graph({
328
391
  compound: false,
329
392
  directed: true,
@@ -358,15 +421,15 @@ function sortNodes({
358
421
  if (sources.length === 0) {
359
422
  sources = remeda.pipe(
360
423
  nodes,
361
- remeda.sort(core.compareByFqnHierarchically),
362
424
  remeda.filter((n) => n.inEdges.length === 0 || n.parent === null),
425
+ remeda.sort(core.compareByFqnHierarchically),
363
426
  remeda.map((n) => n.id)
364
427
  );
365
428
  }
366
429
  const orderedIds = postorder(g, sources).reverse();
367
430
  const sorted = orderedIds.map(getNode);
368
431
  if (sorted.length < nodes.length) {
369
- const unsorted = remeda.difference(nodes, sorted);
432
+ const unsorted = remeda.difference(nodes, sorted).sort(core.compareByFqnHierarchically);
370
433
  sorted.push(...unsorted);
371
434
  }
372
435
  core.invariant(sorted.length === nodes.length, "Not all nodes were processed by graphlib");
@@ -453,7 +516,9 @@ function includeWildcardRef(_expr, where = NoFilter) {
453
516
  ])
454
517
  ];
455
518
  for (const el of children) {
456
- this.addEdges(this.graph.anyEdgesBetween(el, neighbours));
519
+ this.addEdges(this.graph.anyEdgesBetween(el, neighbours)).forEach((edge) => {
520
+ this.addImplicit(edge.source, edge.target);
521
+ });
457
522
  }
458
523
  if (!hasChildren && _elRoot) {
459
524
  const edgesWithSiblings = this.graph.anyEdgesBetween(_elRoot, this.graph.siblings(root));
@@ -644,8 +709,9 @@ function includeIncomingExpr(expr, where) {
644
709
  if (edges.length === 0) {
645
710
  return;
646
711
  }
647
- this.addEdges(edges);
648
- this.addImplicit(...edges.map((e) => e.target));
712
+ this.addEdges(edges).forEach((edge) => {
713
+ this.addImplicit(edge.target);
714
+ });
649
715
  }
650
716
  function excludeIncomingExpr(expr, where) {
651
717
  let relations = filterRelations(edgesIncomingExpr.call(this, expr.incoming), where);
@@ -685,8 +751,9 @@ function includeOutgoingExpr(expr, where) {
685
751
  if (edges.length === 0) {
686
752
  return;
687
753
  }
688
- this.addEdges(edges);
689
- this.addImplicit(...edges.map((e) => e.source));
754
+ this.addEdges(edges).forEach((edge) => {
755
+ this.addImplicit(edge.source);
756
+ });
690
757
  }
691
758
  function excludeOutgoingExpr(expr, where) {
692
759
  const relations = filterRelations(edgesOutgoingExpr.call(this, expr.outgoing), where);
@@ -771,7 +838,9 @@ function includeRelationExpr(expr, where) {
771
838
  if (expr.isBidirectional === true) {
772
839
  edges.push(...this.graph.edgesBetween(targets, sources));
773
840
  }
774
- this.addEdges(filterEdges(edges, where));
841
+ this.addEdges(filterEdges(edges, where)).forEach((edge) => {
842
+ this.activeGroup.addImplicit(edge.source, edge.target);
843
+ });
775
844
  }
776
845
  function excludeRelationExpr(expr, where) {
777
846
  const isSource = elementExprToPredicate(expr.source);
@@ -794,6 +863,50 @@ function compareEdges(a, b) {
794
863
  { source: b.source.id, target: b.target.id }
795
864
  );
796
865
  }
866
+ class NodesGroup {
867
+ constructor(id, viewRule, parent = null) {
868
+ this.id = id;
869
+ this.viewRule = viewRule;
870
+ this.parent = parent;
871
+ }
872
+ static kind = core.ElementKind.Group;
873
+ static root() {
874
+ return new NodesGroup("@root", { title: null, groupRules: [] });
875
+ }
876
+ static is(node) {
877
+ return node.kind === NodesGroup.kind;
878
+ }
879
+ explicits = /* @__PURE__ */ new Set();
880
+ implicits = /* @__PURE__ */ new Set();
881
+ /**
882
+ * Add element explicitly
883
+ * Included even without relationships
884
+ */
885
+ addElement(...el) {
886
+ for (const r of el) {
887
+ this.explicits.add(r);
888
+ this.implicits.add(r);
889
+ }
890
+ }
891
+ /**
892
+ * Add element implicitly
893
+ * Included if only has relationships
894
+ */
895
+ addImplicit(...el) {
896
+ for (const r of el) {
897
+ this.implicits.add(r);
898
+ }
899
+ }
900
+ excludeElement(...excludes) {
901
+ for (const el of excludes) {
902
+ this.explicits.delete(el);
903
+ this.implicits.delete(el);
904
+ }
905
+ }
906
+ isEmpty() {
907
+ return this.explicits.size === 0 && this.implicits.size === 0;
908
+ }
909
+ }
797
910
  class ComputeCtx {
798
911
  constructor(view, graph) {
799
912
  this.view = view;
@@ -803,6 +916,16 @@ class ComputeCtx {
803
916
  explicits = /* @__PURE__ */ new Set();
804
917
  implicits = /* @__PURE__ */ new Set();
805
918
  ctxEdges = [];
919
+ /**
920
+ * Root group - not included in the groups
921
+ * But used to accumulate elements that are not in any group
922
+ */
923
+ __rootGroup = NodesGroup.root();
924
+ groups = [];
925
+ activeGroupStack = [];
926
+ get activeGroup() {
927
+ return this.activeGroupStack[0] ?? this.__rootGroup;
928
+ }
806
929
  static elementView(view, graph) {
807
930
  return new ComputeCtx(view, graph).compute();
808
931
  }
@@ -814,14 +937,30 @@ class ComputeCtx {
814
937
  rules,
815
938
  ...view
816
939
  } = this.view;
817
- const viewPredicates = rules.filter(core.isViewRulePredicate);
940
+ const viewPredicates = rules.filter(remeda.anyPass([core.isViewRulePredicate, core.isViewRuleGroup]));
818
941
  if (this.root && viewPredicates.length == 0) {
819
942
  this.addElement(this.graph.element(this.root));
820
943
  }
821
944
  this.processPredicates(viewPredicates);
822
945
  this.removeRedundantImplicitEdges();
946
+ if (this.groups.length > 0) {
947
+ this.cleanGroupElements();
948
+ }
823
949
  const elements = [...this.includedElements];
824
- const nodesMap = buildComputeNodes(elements);
950
+ 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
+ };
825
964
  const edgesMap = /* @__PURE__ */ new Map();
826
965
  const edges = this.computeEdges();
827
966
  for (const edge of edges) {
@@ -830,25 +969,37 @@ class ComputeCtx {
830
969
  const target = nodesMap.get(edge.target);
831
970
  core.invariant(source, `Source node ${edge.source} not found`);
832
971
  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
- }
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;
836
981
  source.outEdges.push(edge.id);
837
982
  target.inEdges.push(edge.id);
838
- for (const sourceAncestor of core.ancestorsFqn(edge.source)) {
839
- if (sourceAncestor === edge.parent) {
983
+ for (const sourceAncestor of sourceAncestors) {
984
+ if (sourceAncestor === edgeParent) {
840
985
  break;
841
986
  }
842
- nodesMap.get(sourceAncestor)?.outEdges.push(edge.id);
987
+ sourceAncestor.outEdges.push(edge.id);
843
988
  }
844
- for (const targetAncestor of core.ancestorsFqn(edge.target)) {
845
- if (targetAncestor === edge.parent) {
989
+ for (const targetAncestor of targetAncestors) {
990
+ if (targetAncestor === edgeParent) {
846
991
  break;
847
992
  }
848
- nodesMap.get(targetAncestor)?.inEdges.push(edge.id);
993
+ targetAncestor.inEdges.push(edge.id);
849
994
  }
850
995
  }
851
- const initialSort = elements.flatMap((e) => nodesMap.get(e.id) ?? []);
996
+ let initialSort = elements.map((e) => core.nonNullable(nodesMap.get(e.id), `Node ${e.id} not found in nodesMap`));
997
+ if (this.groups.length > 0) {
998
+ initialSort = remeda.concat(
999
+ this.groups.map((g) => core.nonNullable(nodesMap.get(g.id), `Node ${g.id} not found in nodesMap`)),
1000
+ initialSort
1001
+ );
1002
+ }
852
1003
  const nodes = applyCustomElementProperties(
853
1004
  rules,
854
1005
  applyViewRuleStyles(
@@ -994,6 +1145,7 @@ class ComputeCtx {
994
1145
  return this.ctxEdges;
995
1146
  }
996
1147
  addEdges(edges) {
1148
+ const added = [];
997
1149
  for (const e of edges) {
998
1150
  if (!remeda.hasAtLeast(e.relations, 1)) {
999
1151
  continue;
@@ -1003,10 +1155,13 @@ class ComputeCtx {
1003
1155
  );
1004
1156
  if (existing) {
1005
1157
  existing.relations = remeda.unique([...existing.relations, ...e.relations]);
1158
+ added.push(existing);
1006
1159
  continue;
1007
1160
  }
1161
+ added.push(e);
1008
1162
  this.ctxEdges.push(e);
1009
1163
  }
1164
+ return added;
1010
1165
  }
1011
1166
  /**
1012
1167
  * Add element explicitly
@@ -1014,6 +1169,14 @@ class ComputeCtx {
1014
1169
  */
1015
1170
  addElement(...el) {
1016
1171
  for (const r of el) {
1172
+ if (!this.explicits.has(r)) {
1173
+ this.activeGroup.addElement(r);
1174
+ } else if (this.activeGroup !== this.__rootGroup) {
1175
+ this.groups.forEach((g) => {
1176
+ g.implicits.delete(r);
1177
+ });
1178
+ this.activeGroup.addImplicit(r);
1179
+ }
1017
1180
  this.explicits.add(r);
1018
1181
  this.implicits.add(r);
1019
1182
  }
@@ -1025,7 +1188,13 @@ class ComputeCtx {
1025
1188
  addImplicit(...el) {
1026
1189
  for (const r of el) {
1027
1190
  this.implicits.add(r);
1191
+ if (this.activeGroup !== this.__rootGroup) {
1192
+ this.groups.forEach((g) => {
1193
+ g.implicits.delete(r);
1194
+ });
1195
+ }
1028
1196
  }
1197
+ this.activeGroup.addImplicit(...el);
1029
1198
  }
1030
1199
  excludeElement(...excludes) {
1031
1200
  for (const el of excludes) {
@@ -1033,12 +1202,11 @@ class ComputeCtx {
1033
1202
  this.explicits.delete(el);
1034
1203
  this.implicits.delete(el);
1035
1204
  }
1205
+ this.__rootGroup.excludeElement(...excludes);
1206
+ this.groups.forEach((g) => {
1207
+ g.excludeElement(...excludes);
1208
+ });
1036
1209
  }
1037
- // protected excludeImplicit(...excludes: Element[]) {
1038
- // for (const el of excludes) {
1039
- // this.implicits.delete(el)
1040
- // }
1041
- // }
1042
1210
  excludeRelation(...relations) {
1043
1211
  if (relations.length === 0) {
1044
1212
  return;
@@ -1070,11 +1238,15 @@ class ComputeCtx {
1070
1238
  const remaining = this.includedElements;
1071
1239
  if (remaining.size === 0) {
1072
1240
  this.implicits.clear();
1241
+ this.__rootGroup.implicits.clear();
1242
+ this.groups.forEach((g) => g.implicits.clear());
1073
1243
  return;
1074
1244
  }
1075
1245
  for (const el of excludedImplicits) {
1076
1246
  if (!remaining.has(el)) {
1077
1247
  this.implicits.delete(el);
1248
+ this.__rootGroup.implicits.delete(el);
1249
+ this.groups.forEach((g) => g.implicits.delete(el));
1078
1250
  }
1079
1251
  }
1080
1252
  }
@@ -1082,6 +1254,9 @@ class ComputeCtx {
1082
1254
  this.explicits.clear();
1083
1255
  this.implicits.clear();
1084
1256
  this.ctxEdges = [];
1257
+ this.__rootGroup = NodesGroup.root();
1258
+ this.groups = [];
1259
+ this.activeGroupStack = [];
1085
1260
  }
1086
1261
  // Filter out edges if there are edges between descendants
1087
1262
  // i.e. remove implicit edges, derived from childs
@@ -1124,8 +1299,41 @@ class ComputeCtx {
1124
1299
  return acc;
1125
1300
  }, []);
1126
1301
  }
1302
+ cleanGroupElements() {
1303
+ const unprocessed = new Set(remeda.difference(
1304
+ [...this.includedElements],
1305
+ [...this.__rootGroup.explicits]
1306
+ ));
1307
+ for (const group of this.groups) {
1308
+ const explicits = [...group.explicits];
1309
+ group.explicits.clear();
1310
+ for (const el of explicits) {
1311
+ if (unprocessed.delete(el)) {
1312
+ group.explicits.add(el);
1313
+ }
1314
+ }
1315
+ }
1316
+ for (const group of this.groups) {
1317
+ for (const el of group.implicits) {
1318
+ if (unprocessed.delete(el)) {
1319
+ group.explicits.add(el);
1320
+ }
1321
+ }
1322
+ group.implicits.clear();
1323
+ }
1324
+ }
1127
1325
  processPredicates(viewRules) {
1128
1326
  for (const rule of viewRules) {
1327
+ if (core.isViewRuleGroup(rule)) {
1328
+ const parent = remeda.first(this.activeGroupStack);
1329
+ const groupId = `@gr${this.groups.length + 1}`;
1330
+ const group = new NodesGroup(groupId, rule, parent?.id ?? null);
1331
+ this.groups.push(group);
1332
+ this.activeGroupStack.unshift(group);
1333
+ this.processPredicates(rule.groupRules);
1334
+ this.activeGroupStack.shift();
1335
+ continue;
1336
+ }
1129
1337
  const isInclude = "include" in rule;
1130
1338
  const exprs = rule.include ?? rule.exclude;
1131
1339
  for (const expr of exprs) {