@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
@@ -1,5 +1,5 @@
1
- import { Expr, whereOperatorAsPredicate, parentFqn, nonexhaustive, isViewRuleStyle, compareByFqnHierarchically, DefaultThemeColor, DefaultElementShape, compareRelations, invariant, nonNullable, compareNatural, hasAtLeast, isAncestor, isViewRulePredicate, ancestorsFqn, isViewRuleAutoLayout, isScopedElementView, commonAncestor, StepEdgeId, isDynamicViewParallelSteps, isDynamicViewIncludeRule, DefaultRelationshipColor, DefaultLineStyle, DefaultArrowType, isSameHierarchy } from '@likec4/core';
2
- import { pipe, map, pick, mapToObj, isTruthy, isNullish, omitBy, isEmpty, filter, anyPass, isDefined, groupBy, prop, mapValues, piped, unique, entries, flatMap, sortBy, sort, difference, allPass, omit, hasAtLeast as hasAtLeast$1, only, reduce, isNonNull, isString, isArray } from 'remeda';
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';
3
3
  import dagre from '@dagrejs/dagre';
4
4
  import objectHash from 'object-hash';
5
5
 
@@ -54,8 +54,19 @@ function elementExprToPredicate(target) {
54
54
  nonexhaustive(target);
55
55
  }
56
56
 
57
+ function flattenGroupRules(guard) {
58
+ return (rule) => {
59
+ if (isViewRuleGroup(rule)) {
60
+ return rule.groupRules.flatMap(flattenGroupRules(guard));
61
+ }
62
+ if (isViewRulePredicate(rule)) {
63
+ return "include" in rule ? rule.include.filter(guard) : [];
64
+ }
65
+ return [];
66
+ };
67
+ }
57
68
  function applyCustomElementProperties(_rules, _nodes) {
58
- const rules = _rules.flatMap((r) => "include" in r ? r.include.filter(Expr.isCustomElement) : []);
69
+ const rules = _rules.flatMap(flattenGroupRules(Expr.isCustomElement));
59
70
  if (rules.length === 0) {
60
71
  return _nodes;
61
72
  }
@@ -67,7 +78,7 @@ function applyCustomElementProperties(_rules, _nodes) {
67
78
  const notEmpty = !isEmpty(rest);
68
79
  const satisfies = elementExprToPredicate(expr);
69
80
  nodes.forEach((node, i) => {
70
- if (!satisfies(node)) {
81
+ if (ComputedNode.isNodesGroup(node) || !satisfies(node)) {
71
82
  return;
72
83
  }
73
84
  if (notEmpty) {
@@ -103,7 +114,9 @@ function applyCustomElementProperties(_rules, _nodes) {
103
114
  function relationExpressionToPredicates(expr) {
104
115
  switch (true) {
105
116
  case Expr.isRelationWhere(expr):
106
- return relationExpressionToPredicates(expr.where.expr);
117
+ const predicate = relationExpressionToPredicates(expr.where.expr);
118
+ const where = whereOperatorAsPredicate(expr.where.condition);
119
+ return (e) => predicate(e) && where(e);
107
120
  case Expr.isRelation(expr): {
108
121
  const isSource = elementExprToPredicate(expr.source);
109
122
  const isTarget = elementExprToPredicate(expr.target);
@@ -127,8 +140,9 @@ function relationExpressionToPredicates(expr) {
127
140
  nonexhaustive(expr);
128
141
  }
129
142
  }
143
+
130
144
  function applyCustomRelationProperties(_rules, nodes, _edges) {
131
- const rules = _rules.flatMap((r) => "include" in r ? r.include.filter(Expr.isCustomRelationExpr) : []);
145
+ const rules = _rules.flatMap(flattenGroupRules(Expr.isCustomRelationExpr));
132
146
  const edges = Array.from(_edges);
133
147
  if (rules.length === 0 || edges.length === 0) {
134
148
  return edges;
@@ -144,12 +158,12 @@ function applyCustomRelationProperties(_rules, nodes, _edges) {
144
158
  if (!source || !target) {
145
159
  return;
146
160
  }
147
- if (satisfies({ source, target })) {
161
+ if (satisfies({ source, target, ...pick(edge, ["kind", "tags"]) })) {
148
162
  edges[i] = {
149
163
  ...edge,
164
+ ...props,
150
165
  label: title ?? edge.label,
151
- isCustomized: true,
152
- ...props
166
+ isCustomized: true
153
167
  };
154
168
  }
155
169
  });
@@ -171,29 +185,34 @@ function applyViewRuleStyles(_rules, nodes) {
171
185
  }
172
186
  predicates.push(elementExprToPredicate(target));
173
187
  }
174
- filter(nodes, anyPass(predicates)).forEach((n) => {
175
- n.shape = rule.style.shape ?? n.shape;
176
- n.color = rule.style.color ?? n.color;
177
- if (isDefined(rule.style.icon)) {
178
- n.icon = rule.style.icon;
179
- }
180
- if (isDefined(rule.notation)) {
181
- n.notation = rule.notation;
182
- }
183
- let styleOverride;
184
- if (isDefined(rule.style.border)) {
185
- styleOverride = { border: rule.style.border };
186
- }
187
- if (isDefined(rule.style.opacity)) {
188
- styleOverride = { ...styleOverride, opacity: rule.style.opacity };
189
- }
190
- if (styleOverride) {
191
- n.style = {
192
- ...n.style,
193
- ...styleOverride
194
- };
195
- }
196
- });
188
+ pipe(
189
+ nodes,
190
+ filter(isNot(ComputedNode.isNodesGroup)),
191
+ filter(anyPass(predicates)),
192
+ forEach((n) => {
193
+ n.shape = rule.style.shape ?? n.shape;
194
+ n.color = rule.style.color ?? n.color;
195
+ if (isDefined(rule.style.icon)) {
196
+ n.icon = rule.style.icon;
197
+ }
198
+ if (isDefined(rule.notation)) {
199
+ n.notation = rule.notation;
200
+ }
201
+ let styleOverride;
202
+ if (isDefined(rule.style.border)) {
203
+ styleOverride = { border: rule.style.border };
204
+ }
205
+ if (isDefined(rule.style.opacity)) {
206
+ styleOverride = { ...styleOverride, opacity: rule.style.opacity };
207
+ }
208
+ if (styleOverride) {
209
+ n.style = {
210
+ ...n.style,
211
+ ...styleOverride
212
+ };
213
+ }
214
+ })
215
+ );
197
216
  }
198
217
  return nodes;
199
218
  }
@@ -209,40 +228,80 @@ function updateDepthOfAncestors(node, nodes) {
209
228
  node = parentNd;
210
229
  }
211
230
  }
212
- function buildComputeNodes(elements) {
213
- return Array.from(elements).sort(compareByFqnHierarchically).reduce((map, { id, color, shape, style, ...el }) => {
231
+ function buildComputeNodes(elements, groups) {
232
+ const nodesMap = /* @__PURE__ */ new Map();
233
+ const elementToGroup = /* @__PURE__ */ new Map();
234
+ groups?.forEach(({ id, parent, viewRule, explicits }) => {
235
+ if (parent) {
236
+ nonNullable(nodesMap.get(parent), `Parent group node ${parent} not found`).children.push(id);
237
+ }
238
+ nodesMap.set(id, {
239
+ id,
240
+ parent,
241
+ kind: ElementKind.Group,
242
+ title: viewRule.title ?? "",
243
+ color: viewRule.color ?? "muted",
244
+ shape: "rectangle",
245
+ children: [],
246
+ inEdges: [],
247
+ outEdges: [],
248
+ level: 0,
249
+ depth: 0,
250
+ description: null,
251
+ technology: null,
252
+ tags: null,
253
+ links: null,
254
+ style: {
255
+ border: viewRule.border ?? "dashed",
256
+ opacity: viewRule.opacity ?? 0
257
+ }
258
+ });
259
+ for (const e of explicits) {
260
+ elementToGroup.set(e.id, id);
261
+ }
262
+ });
263
+ return Array.from(elements).sort(compareByFqnHierarchically).reduce((map, { id, color, shape, style, kind, title, ...el }) => {
214
264
  let parent = parentFqn(id);
215
265
  let level = 0;
266
+ let parentNd;
216
267
  while (parent) {
217
- const parentNd = map.get(parent);
268
+ parentNd = map.get(parent);
218
269
  if (parentNd) {
219
- if (parentNd.children.length == 0) {
220
- parentNd.depth = 1;
221
- updateDepthOfAncestors(parentNd, map);
222
- }
223
- parentNd.children.push(id);
224
- level = parentNd.level + 1;
225
270
  break;
226
271
  }
227
272
  parent = parentFqn(parent);
228
273
  }
274
+ if (!parentNd && elementToGroup.has(id)) {
275
+ parent = elementToGroup.get(id);
276
+ parentNd = map.get(parent);
277
+ }
278
+ if (parentNd) {
279
+ if (parentNd.children.length == 0) {
280
+ parentNd.depth = 1;
281
+ updateDepthOfAncestors(parentNd, map);
282
+ }
283
+ parentNd.children.push(id);
284
+ level = parentNd.level + 1;
285
+ }
229
286
  const node = {
230
- ...el,
231
287
  id,
232
288
  parent,
233
- level,
289
+ kind,
290
+ title,
234
291
  color: color ?? DefaultThemeColor,
235
292
  shape: shape ?? DefaultElementShape,
236
293
  children: [],
237
294
  inEdges: [],
238
295
  outEdges: [],
296
+ level,
297
+ ...el,
239
298
  style: {
240
299
  ...style
241
300
  }
242
301
  };
243
302
  map.set(id, node);
244
303
  return map;
245
- }, /* @__PURE__ */ new Map());
304
+ }, nodesMap);
246
305
  }
247
306
 
248
307
  function buildElementNotations(nodes) {
@@ -314,9 +373,16 @@ function sortNodes({
314
373
  nodes,
315
374
  edges
316
375
  }) {
317
- if (edges.length === 0) {
376
+ if (nodes.length < 2) {
318
377
  return nodes;
319
378
  }
379
+ if (edges.length === 0) {
380
+ return pipe(
381
+ nodes,
382
+ sort(compareByFqnHierarchically),
383
+ tap(sortChildren)
384
+ );
385
+ }
320
386
  const g = new Graph({
321
387
  compound: false,
322
388
  directed: true,
@@ -351,15 +417,15 @@ function sortNodes({
351
417
  if (sources.length === 0) {
352
418
  sources = pipe(
353
419
  nodes,
354
- sort(compareByFqnHierarchically),
355
420
  filter((n) => n.inEdges.length === 0 || n.parent === null),
421
+ sort(compareByFqnHierarchically),
356
422
  map((n) => n.id)
357
423
  );
358
424
  }
359
425
  const orderedIds = postorder(g, sources).reverse();
360
426
  const sorted = orderedIds.map(getNode);
361
427
  if (sorted.length < nodes.length) {
362
- const unsorted = difference(nodes, sorted);
428
+ const unsorted = difference(nodes, sorted).sort(compareByFqnHierarchically);
363
429
  sorted.push(...unsorted);
364
430
  }
365
431
  invariant(sorted.length === nodes.length, "Not all nodes were processed by graphlib");
@@ -446,7 +512,9 @@ function includeWildcardRef(_expr, where = NoFilter) {
446
512
  ])
447
513
  ];
448
514
  for (const el of children) {
449
- this.addEdges(this.graph.anyEdgesBetween(el, neighbours));
515
+ this.addEdges(this.graph.anyEdgesBetween(el, neighbours)).forEach((edge) => {
516
+ this.addImplicit(edge.source, edge.target);
517
+ });
450
518
  }
451
519
  if (!hasChildren && _elRoot) {
452
520
  const edgesWithSiblings = this.graph.anyEdgesBetween(_elRoot, this.graph.siblings(root));
@@ -637,8 +705,9 @@ function includeIncomingExpr(expr, where) {
637
705
  if (edges.length === 0) {
638
706
  return;
639
707
  }
640
- this.addEdges(edges);
641
- this.addImplicit(...edges.map((e) => e.target));
708
+ this.addEdges(edges).forEach((edge) => {
709
+ this.addImplicit(edge.target);
710
+ });
642
711
  }
643
712
  function excludeIncomingExpr(expr, where) {
644
713
  let relations = filterRelations(edgesIncomingExpr.call(this, expr.incoming), where);
@@ -678,8 +747,9 @@ function includeOutgoingExpr(expr, where) {
678
747
  if (edges.length === 0) {
679
748
  return;
680
749
  }
681
- this.addEdges(edges);
682
- this.addImplicit(...edges.map((e) => e.source));
750
+ this.addEdges(edges).forEach((edge) => {
751
+ this.addImplicit(edge.source);
752
+ });
683
753
  }
684
754
  function excludeOutgoingExpr(expr, where) {
685
755
  const relations = filterRelations(edgesOutgoingExpr.call(this, expr.outgoing), where);
@@ -764,7 +834,9 @@ function includeRelationExpr(expr, where) {
764
834
  if (expr.isBidirectional === true) {
765
835
  edges.push(...this.graph.edgesBetween(targets, sources));
766
836
  }
767
- this.addEdges(filterEdges(edges, where));
837
+ this.addEdges(filterEdges(edges, where)).forEach((edge) => {
838
+ this.activeGroup.addImplicit(edge.source, edge.target);
839
+ });
768
840
  }
769
841
  function excludeRelationExpr(expr, where) {
770
842
  const isSource = elementExprToPredicate(expr.source);
@@ -787,6 +859,50 @@ function compareEdges(a, b) {
787
859
  { source: b.source.id, target: b.target.id }
788
860
  );
789
861
  }
862
+ class NodesGroup {
863
+ constructor(id, viewRule, parent = null) {
864
+ this.id = id;
865
+ this.viewRule = viewRule;
866
+ this.parent = parent;
867
+ }
868
+ static kind = ElementKind.Group;
869
+ static root() {
870
+ return new NodesGroup("@root", { title: null, groupRules: [] });
871
+ }
872
+ static is(node) {
873
+ return node.kind === NodesGroup.kind;
874
+ }
875
+ explicits = /* @__PURE__ */ new Set();
876
+ implicits = /* @__PURE__ */ new Set();
877
+ /**
878
+ * Add element explicitly
879
+ * Included even without relationships
880
+ */
881
+ addElement(...el) {
882
+ for (const r of el) {
883
+ this.explicits.add(r);
884
+ this.implicits.add(r);
885
+ }
886
+ }
887
+ /**
888
+ * Add element implicitly
889
+ * Included if only has relationships
890
+ */
891
+ addImplicit(...el) {
892
+ for (const r of el) {
893
+ this.implicits.add(r);
894
+ }
895
+ }
896
+ excludeElement(...excludes) {
897
+ for (const el of excludes) {
898
+ this.explicits.delete(el);
899
+ this.implicits.delete(el);
900
+ }
901
+ }
902
+ isEmpty() {
903
+ return this.explicits.size === 0 && this.implicits.size === 0;
904
+ }
905
+ }
790
906
  class ComputeCtx {
791
907
  constructor(view, graph) {
792
908
  this.view = view;
@@ -796,6 +912,16 @@ class ComputeCtx {
796
912
  explicits = /* @__PURE__ */ new Set();
797
913
  implicits = /* @__PURE__ */ new Set();
798
914
  ctxEdges = [];
915
+ /**
916
+ * Root group - not included in the groups
917
+ * But used to accumulate elements that are not in any group
918
+ */
919
+ __rootGroup = NodesGroup.root();
920
+ groups = [];
921
+ activeGroupStack = [];
922
+ get activeGroup() {
923
+ return this.activeGroupStack[0] ?? this.__rootGroup;
924
+ }
799
925
  static elementView(view, graph) {
800
926
  return new ComputeCtx(view, graph).compute();
801
927
  }
@@ -807,14 +933,30 @@ class ComputeCtx {
807
933
  rules,
808
934
  ...view
809
935
  } = this.view;
810
- const viewPredicates = rules.filter(isViewRulePredicate);
936
+ const viewPredicates = rules.filter(anyPass([isViewRulePredicate, isViewRuleGroup]));
811
937
  if (this.root && viewPredicates.length == 0) {
812
938
  this.addElement(this.graph.element(this.root));
813
939
  }
814
940
  this.processPredicates(viewPredicates);
815
941
  this.removeRedundantImplicitEdges();
942
+ if (this.groups.length > 0) {
943
+ this.cleanGroupElements();
944
+ }
816
945
  const elements = [...this.includedElements];
817
- const nodesMap = buildComputeNodes(elements);
946
+ const nodesMap = buildComputeNodes(elements, this.groups);
947
+ const ancestorsOf = (node) => {
948
+ const ancestors = [];
949
+ let parent = node.parent;
950
+ while (parent) {
951
+ const parentNode = nodesMap.get(parent);
952
+ if (!parentNode) {
953
+ break;
954
+ }
955
+ ancestors.push(parentNode);
956
+ parent = parentNode.parent;
957
+ }
958
+ return ancestors;
959
+ };
818
960
  const edgesMap = /* @__PURE__ */ new Map();
819
961
  const edges = this.computeEdges();
820
962
  for (const edge of edges) {
@@ -823,25 +965,37 @@ class ComputeCtx {
823
965
  const target = nodesMap.get(edge.target);
824
966
  invariant(source, `Source node ${edge.source} not found`);
825
967
  invariant(target, `Target node ${edge.target} not found`);
826
- while (edge.parent && !nodesMap.has(edge.parent)) {
827
- edge.parent = parentFqn(edge.parent);
828
- }
968
+ const sourceAncestors = ancestorsOf(source);
969
+ const targetAncestors = ancestorsOf(target);
970
+ const edgeParent = last(
971
+ commonHead(
972
+ reverse(sourceAncestors),
973
+ reverse(targetAncestors)
974
+ )
975
+ );
976
+ edge.parent = edgeParent?.id ?? null;
829
977
  source.outEdges.push(edge.id);
830
978
  target.inEdges.push(edge.id);
831
- for (const sourceAncestor of ancestorsFqn(edge.source)) {
832
- if (sourceAncestor === edge.parent) {
979
+ for (const sourceAncestor of sourceAncestors) {
980
+ if (sourceAncestor === edgeParent) {
833
981
  break;
834
982
  }
835
- nodesMap.get(sourceAncestor)?.outEdges.push(edge.id);
983
+ sourceAncestor.outEdges.push(edge.id);
836
984
  }
837
- for (const targetAncestor of ancestorsFqn(edge.target)) {
838
- if (targetAncestor === edge.parent) {
985
+ for (const targetAncestor of targetAncestors) {
986
+ if (targetAncestor === edgeParent) {
839
987
  break;
840
988
  }
841
- nodesMap.get(targetAncestor)?.inEdges.push(edge.id);
989
+ targetAncestor.inEdges.push(edge.id);
842
990
  }
843
991
  }
844
- const initialSort = elements.flatMap((e) => nodesMap.get(e.id) ?? []);
992
+ let initialSort = elements.map((e) => nonNullable(nodesMap.get(e.id), `Node ${e.id} not found in nodesMap`));
993
+ if (this.groups.length > 0) {
994
+ initialSort = concat(
995
+ this.groups.map((g) => nonNullable(nodesMap.get(g.id), `Node ${g.id} not found in nodesMap`)),
996
+ initialSort
997
+ );
998
+ }
845
999
  const nodes = applyCustomElementProperties(
846
1000
  rules,
847
1001
  applyViewRuleStyles(
@@ -987,6 +1141,7 @@ class ComputeCtx {
987
1141
  return this.ctxEdges;
988
1142
  }
989
1143
  addEdges(edges) {
1144
+ const added = [];
990
1145
  for (const e of edges) {
991
1146
  if (!hasAtLeast$1(e.relations, 1)) {
992
1147
  continue;
@@ -996,10 +1151,13 @@ class ComputeCtx {
996
1151
  );
997
1152
  if (existing) {
998
1153
  existing.relations = unique([...existing.relations, ...e.relations]);
1154
+ added.push(existing);
999
1155
  continue;
1000
1156
  }
1157
+ added.push(e);
1001
1158
  this.ctxEdges.push(e);
1002
1159
  }
1160
+ return added;
1003
1161
  }
1004
1162
  /**
1005
1163
  * Add element explicitly
@@ -1007,6 +1165,14 @@ class ComputeCtx {
1007
1165
  */
1008
1166
  addElement(...el) {
1009
1167
  for (const r of el) {
1168
+ if (!this.explicits.has(r)) {
1169
+ this.activeGroup.addElement(r);
1170
+ } else if (this.activeGroup !== this.__rootGroup) {
1171
+ this.groups.forEach((g) => {
1172
+ g.implicits.delete(r);
1173
+ });
1174
+ this.activeGroup.addImplicit(r);
1175
+ }
1010
1176
  this.explicits.add(r);
1011
1177
  this.implicits.add(r);
1012
1178
  }
@@ -1018,7 +1184,13 @@ class ComputeCtx {
1018
1184
  addImplicit(...el) {
1019
1185
  for (const r of el) {
1020
1186
  this.implicits.add(r);
1187
+ if (this.activeGroup !== this.__rootGroup) {
1188
+ this.groups.forEach((g) => {
1189
+ g.implicits.delete(r);
1190
+ });
1191
+ }
1021
1192
  }
1193
+ this.activeGroup.addImplicit(...el);
1022
1194
  }
1023
1195
  excludeElement(...excludes) {
1024
1196
  for (const el of excludes) {
@@ -1026,12 +1198,11 @@ class ComputeCtx {
1026
1198
  this.explicits.delete(el);
1027
1199
  this.implicits.delete(el);
1028
1200
  }
1201
+ this.__rootGroup.excludeElement(...excludes);
1202
+ this.groups.forEach((g) => {
1203
+ g.excludeElement(...excludes);
1204
+ });
1029
1205
  }
1030
- // protected excludeImplicit(...excludes: Element[]) {
1031
- // for (const el of excludes) {
1032
- // this.implicits.delete(el)
1033
- // }
1034
- // }
1035
1206
  excludeRelation(...relations) {
1036
1207
  if (relations.length === 0) {
1037
1208
  return;
@@ -1063,11 +1234,15 @@ class ComputeCtx {
1063
1234
  const remaining = this.includedElements;
1064
1235
  if (remaining.size === 0) {
1065
1236
  this.implicits.clear();
1237
+ this.__rootGroup.implicits.clear();
1238
+ this.groups.forEach((g) => g.implicits.clear());
1066
1239
  return;
1067
1240
  }
1068
1241
  for (const el of excludedImplicits) {
1069
1242
  if (!remaining.has(el)) {
1070
1243
  this.implicits.delete(el);
1244
+ this.__rootGroup.implicits.delete(el);
1245
+ this.groups.forEach((g) => g.implicits.delete(el));
1071
1246
  }
1072
1247
  }
1073
1248
  }
@@ -1075,6 +1250,9 @@ class ComputeCtx {
1075
1250
  this.explicits.clear();
1076
1251
  this.implicits.clear();
1077
1252
  this.ctxEdges = [];
1253
+ this.__rootGroup = NodesGroup.root();
1254
+ this.groups = [];
1255
+ this.activeGroupStack = [];
1078
1256
  }
1079
1257
  // Filter out edges if there are edges between descendants
1080
1258
  // i.e. remove implicit edges, derived from childs
@@ -1117,8 +1295,41 @@ class ComputeCtx {
1117
1295
  return acc;
1118
1296
  }, []);
1119
1297
  }
1298
+ cleanGroupElements() {
1299
+ const unprocessed = new Set(difference(
1300
+ [...this.includedElements],
1301
+ [...this.__rootGroup.explicits]
1302
+ ));
1303
+ for (const group of this.groups) {
1304
+ const explicits = [...group.explicits];
1305
+ group.explicits.clear();
1306
+ for (const el of explicits) {
1307
+ if (unprocessed.delete(el)) {
1308
+ group.explicits.add(el);
1309
+ }
1310
+ }
1311
+ }
1312
+ for (const group of this.groups) {
1313
+ for (const el of group.implicits) {
1314
+ if (unprocessed.delete(el)) {
1315
+ group.explicits.add(el);
1316
+ }
1317
+ }
1318
+ group.implicits.clear();
1319
+ }
1320
+ }
1120
1321
  processPredicates(viewRules) {
1121
1322
  for (const rule of viewRules) {
1323
+ if (isViewRuleGroup(rule)) {
1324
+ const parent = first(this.activeGroupStack);
1325
+ const groupId = `@gr${this.groups.length + 1}`;
1326
+ const group = new NodesGroup(groupId, rule, parent?.id ?? null);
1327
+ this.groups.push(group);
1328
+ this.activeGroupStack.unshift(group);
1329
+ this.processPredicates(rule.groupRules);
1330
+ this.activeGroupStack.shift();
1331
+ continue;
1332
+ }
1122
1333
  const isInclude = "include" in rule;
1123
1334
  const exprs = rule.include ?? rule.exclude;
1124
1335
  for (const expr of exprs) {