@likec4/language-server 1.9.0 → 1.10.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 +1 -1
  4. package/dist/browser.d.mts +1 -1
  5. package/dist/browser.d.ts +1 -1
  6. package/dist/browser.mjs +1 -1
  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 +1 -1
  12. package/dist/model-graph/index.cjs +1 -1
  13. package/dist/model-graph/index.mjs +1 -1
  14. package/dist/node.cjs +1 -1
  15. package/dist/node.d.cts +1 -1
  16. package/dist/node.d.mts +1 -1
  17. package/dist/node.d.ts +1 -1
  18. package/dist/node.mjs +1 -1
  19. package/dist/shared/{language-server.86lmJ8ZN.d.cts → language-server.CjFzaJwI.d.cts} +42 -13
  20. package/dist/shared/{language-server.RjhrBZS0.d.ts → language-server.CtKHXJDD.d.ts} +42 -13
  21. package/dist/shared/{language-server.CFTY6j4e.d.mts → language-server.D-84I33F.d.mts} +42 -13
  22. package/dist/shared/{language-server.Q-wtPShM.mjs → language-server.DBJJUUgF.mjs} +485 -108
  23. package/dist/shared/{language-server.CCB4ESN5.mjs → language-server.DtBRb9os.mjs} +166 -116
  24. package/dist/shared/{language-server.D0bOlrCi.cjs → language-server.DwyCJvXm.cjs} +164 -114
  25. package/dist/shared/{language-server.B1TZgyoH.cjs → language-server.JWkqVjGv.cjs} +481 -104
  26. package/package.json +6 -5
  27. package/src/ast.ts +8 -6
  28. package/src/formatting/LikeC4Formatter.ts +388 -0
  29. package/src/formatting/utils.ts +26 -0
  30. package/src/generated/ast.ts +104 -10
  31. package/src/generated/grammar.ts +1 -1
  32. package/src/like-c4.langium +34 -7
  33. package/src/lsp/DocumentLinkProvider.ts +27 -15
  34. package/src/lsp/SemanticTokenProvider.ts +1 -1
  35. package/src/lsp/index.ts +1 -1
  36. package/src/model/fqn-index.ts +0 -1
  37. package/src/model/model-builder.ts +43 -32
  38. package/src/model/model-parser.ts +43 -21
  39. package/src/model-graph/compute-view/compute.ts +104 -78
  40. package/src/model-graph/compute-view/predicates.ts +3 -5
  41. package/src/model-graph/dynamic-view/compute.ts +96 -60
  42. package/src/model-graph/utils/buildElementNotations.ts +1 -1
  43. package/src/module.ts +6 -9
  44. package/src/test/testServices.ts +27 -7
  45. package/src/validation/index.ts +2 -1
  46. package/src/validation/property-checks.ts +13 -1
  47. package/src/validation/specification.ts +3 -3
  48. package/src/view-utils/resolve-relative-paths.ts +14 -17
@@ -295,8 +295,8 @@ function buildElementNotations(nodes) {
295
295
  }))
296
296
  ),
297
297
  remeda.sortBy(
298
- remeda.prop("title"),
299
298
  remeda.prop("shape"),
299
+ remeda.prop("title"),
300
300
  [
301
301
  (n) => n.kinds.length,
302
302
  "desc"
@@ -621,13 +621,11 @@ const filterEdges = (edges, where) => {
621
621
  );
622
622
  };
623
623
  const filterRelations = (edges, where) => {
624
- if (!where) {
625
- return edges.flatMap((e) => e.relations);
626
- }
627
624
  return remeda.pipe(
628
625
  edges,
629
626
  remeda.flatMap((e) => e.relations),
630
- remeda.filter(where)
627
+ where ? remeda.filter(where) : Identity,
628
+ remeda.unique()
631
629
  );
632
630
  };
633
631
  function includeIncomingExpr(expr, where) {
@@ -875,7 +873,7 @@ class ComputeCtx {
875
873
  computeEdges() {
876
874
  return this.ctxEdges.map((e) => {
877
875
  core.invariant(remeda.hasAtLeast(e.relations, 1), "Edge must have at least one relation");
878
- const relations = e.relations.toSorted(core.compareRelations);
876
+ const relations = remeda.sort(e.relations, core.compareRelations);
879
877
  const source = e.source.id;
880
878
  const target = e.target.id;
881
879
  const edge = {
@@ -887,48 +885,62 @@ class ComputeCtx {
887
885
  relations: relations.map((r) => r.id)
888
886
  };
889
887
  let relation;
890
- if (relations.length === 1) {
891
- relation = relations[0];
892
- } else {
893
- relation = relations.find((r) => r.source === source && r.target === target);
894
- }
888
+ relation = relations.length === 1 ? relations[0] : relations.find((r) => r.source === source && r.target === target);
895
889
  if (!relation) {
896
- relation = relations.reduce((acc, r) => {
897
- if (r.color && acc.color !== r.color) {
898
- acc.color = void 0;
899
- }
900
- if (r.kind && acc.kind !== r.kind) {
901
- acc.kind = void 0;
902
- }
903
- if (r.head && acc.head !== r.head) {
904
- acc.head = void 0;
905
- }
906
- if (r.tail && acc.tail !== r.tail) {
907
- acc.tail = void 0;
908
- }
909
- if (r.line && acc.line !== r.line) {
910
- acc.line = void 0;
911
- }
912
- if (r.description && acc.description !== r.description) {
913
- acc.description = void 0;
914
- }
915
- if (r.technology && acc.technology !== r.technology) {
916
- acc.technology = void 0;
917
- }
918
- if (remeda.isTruthy(r.title) && acc.title !== r.title) {
919
- acc.title = "[...]";
920
- }
921
- return acc;
922
- }, {
923
- title: remeda.first(remeda.flatMap(relations, (r) => remeda.isTruthy(r.title) ? r.title : [])) ?? "[...]",
924
- description: remeda.first(remeda.flatMap(relations, (r) => remeda.isTruthy(r.description) ? r.description : [])),
925
- technology: remeda.first(remeda.flatMap(relations, (r) => remeda.isTruthy(r.technology) ? r.technology : [])),
926
- kind: remeda.first(remeda.flatMap(relations, (r) => remeda.isTruthy(r.kind) ? r.kind : [])),
927
- head: remeda.first(remeda.flatMap(relations, (r) => remeda.isTruthy(r.head) ? r.head : [])),
928
- tail: remeda.first(remeda.flatMap(relations, (r) => remeda.isTruthy(r.tail) ? r.tail : [])),
929
- color: remeda.first(remeda.flatMap(relations, (r) => remeda.isTruthy(r.color) ? r.color : [])),
930
- line: remeda.first(remeda.flatMap(relations, (r) => remeda.isTruthy(r.line) ? r.line : []))
931
- });
890
+ const allprops = remeda.pipe(
891
+ relations,
892
+ remeda.reduce((acc, r) => {
893
+ if (remeda.isTruthy(r.title) && !acc.title.includes(r.title)) {
894
+ acc.title.push(r.title);
895
+ }
896
+ if (remeda.isTruthy(r.description) && !acc.description.includes(r.description)) {
897
+ acc.description.push(r.description);
898
+ }
899
+ if (remeda.isTruthy(r.technology) && !acc.technology.includes(r.technology)) {
900
+ acc.technology.push(r.technology);
901
+ }
902
+ if (remeda.isTruthy(r.kind) && !acc.kind.includes(r.kind)) {
903
+ acc.kind.push(r.kind);
904
+ }
905
+ if (remeda.isTruthy(r.color) && !acc.color.includes(r.color)) {
906
+ acc.color.push(r.color);
907
+ }
908
+ if (remeda.isTruthy(r.line) && !acc.line.includes(r.line)) {
909
+ acc.line.push(r.line);
910
+ }
911
+ if (remeda.isTruthy(r.head) && !acc.head.includes(r.head)) {
912
+ acc.head.push(r.head);
913
+ }
914
+ if (remeda.isTruthy(r.tail) && !acc.tail.includes(r.tail)) {
915
+ acc.tail.push(r.tail);
916
+ }
917
+ if (remeda.isTruthy(r.navigateTo) && !acc.navigateTo.includes(r.navigateTo)) {
918
+ acc.navigateTo.push(r.navigateTo);
919
+ }
920
+ return acc;
921
+ }, {
922
+ title: [],
923
+ description: [],
924
+ technology: [],
925
+ kind: [],
926
+ head: [],
927
+ tail: [],
928
+ color: [],
929
+ line: [],
930
+ navigateTo: []
931
+ })
932
+ );
933
+ relation = {
934
+ title: remeda.only(allprops.title) ?? "[...]",
935
+ description: remeda.only(allprops.description),
936
+ technology: remeda.only(allprops.technology),
937
+ kind: remeda.only(allprops.kind),
938
+ head: remeda.only(allprops.head),
939
+ tail: remeda.only(allprops.tail),
940
+ color: remeda.only(allprops.color),
941
+ line: remeda.only(allprops.line),
942
+ navigateTo: remeda.only(allprops.navigateTo)
943
+ };
932
944
  }
933
945
  const tags = remeda.unique(remeda.flatMap(relations, (r) => r.tags ?? []));
934
946
  return Object.assign(
@@ -941,6 +953,7 @@ class ComputeCtx {
941
953
  relation.line && { line: relation.line },
942
954
  relation.head && { head: relation.head },
943
955
  relation.tail && { tail: relation.tail },
956
+ relation.navigateTo && { navigateTo: relation.navigateTo },
944
957
  remeda.hasAtLeast(tags, 1) && { tags }
945
958
  );
946
959
  });
@@ -1002,28 +1015,39 @@ class ComputeCtx {
1002
1015
  this.implicits.delete(el);
1003
1016
  }
1004
1017
  }
1005
- excludeImplicit(...excludes) {
1006
- for (const el of excludes) {
1007
- this.implicits.delete(el);
1008
- }
1009
- }
1018
+ // protected excludeImplicit(...excludes: Element[]) {
1019
+ // for (const el of excludes) {
1020
+ // this.implicits.delete(el)
1021
+ // }
1022
+ // }
1010
1023
  excludeRelation(...relations) {
1024
+ if (relations.length === 0) {
1025
+ return;
1026
+ }
1011
1027
  const excludedImplicits = /* @__PURE__ */ new Set();
1012
- for (const relation of relations) {
1013
- let edge;
1014
- while (edge = this.ctxEdges.find((e) => e.relations.includes(relation))) {
1015
- if (edge.relations.length === 1) {
1028
+ const ctxEdges = remeda.pipe(
1029
+ this.ctxEdges,
1030
+ remeda.map((edge) => {
1031
+ const edgerelations = edge.relations.filter((r) => !relations.includes(r));
1032
+ if (edgerelations.length === 0) {
1016
1033
  excludedImplicits.add(edge.source);
1017
1034
  excludedImplicits.add(edge.target);
1018
- this.ctxEdges.splice(this.ctxEdges.indexOf(edge), 1);
1019
- continue;
1035
+ return null;
1020
1036
  }
1021
- edge.relations = edge.relations.filter((r) => r !== relation);
1022
- }
1023
- }
1037
+ if (edgerelations.length !== edge.relations.length) {
1038
+ return {
1039
+ ...edge,
1040
+ relations: edgerelations
1041
+ };
1042
+ }
1043
+ return edge;
1044
+ }),
1045
+ remeda.filter(remeda.isNonNull)
1046
+ );
1024
1047
  if (excludedImplicits.size === 0) {
1025
1048
  return;
1026
1049
  }
1050
+ this.ctxEdges = ctxEdges;
1027
1051
  const remaining = this.includedElements;
1028
1052
  if (remaining.size === 0) {
1029
1053
  this.implicits.clear();
@@ -1167,7 +1191,7 @@ class ComputeCtx {
1167
1191
  if (remeda.isTruthy(relation.technology)) {
1168
1192
  labelParts.push(`[${relation.technology}]`);
1169
1193
  }
1170
- return labelParts.length > 0 && { label: labelParts.join("\n") };
1194
+ return labelParts.length > 0 ? { label: labelParts.join("\n") } : {};
1171
1195
  }
1172
1196
  }
1173
1197
 
@@ -1200,6 +1224,38 @@ class DynamicViewComputeCtx {
1200
1224
  static compute(view, graph) {
1201
1225
  return new DynamicViewComputeCtx(view, graph).compute();
1202
1226
  }
1227
+ addStep({
1228
+ source: stepSource,
1229
+ target: stepTarget,
1230
+ title: stepTitle,
1231
+ isBackward,
1232
+ navigateTo: stepNavigateTo,
1233
+ ...step
1234
+ }, index, parent) {
1235
+ const id = parent ? core.StepEdgeId(parent, index) : core.StepEdgeId(index);
1236
+ const source = this.graph.element(stepSource);
1237
+ const target = this.graph.element(stepTarget);
1238
+ this.explicits.add(source);
1239
+ this.explicits.add(target);
1240
+ const {
1241
+ title,
1242
+ relations,
1243
+ tags,
1244
+ navigateTo: derivedNavigateTo
1245
+ } = this.findRelations(source, target);
1246
+ const navigateTo = remeda.isTruthy(stepNavigateTo) && stepNavigateTo !== this.view.id ? stepNavigateTo : derivedNavigateTo;
1247
+ this.steps.push({
1248
+ id,
1249
+ ...step,
1250
+ source,
1251
+ target,
1252
+ title: stepTitle ?? title,
1253
+ relations: relations ?? [],
1254
+ isBackward: isBackward ?? false,
1255
+ ...navigateTo ? { navigateTo } : {},
1256
+ ...tags ? { tags } : {}
1257
+ });
1258
+ }
1203
1259
  compute() {
1204
1260
  const {
1205
1261
  docUri: _docUri,
@@ -1208,27 +1264,21 @@ class DynamicViewComputeCtx {
1208
1264
  steps: viewSteps,
1209
1265
  ...view
1210
1266
  } = this.view;
1211
- for (let {
1212
- source: stepSource,
1213
- target: stepTarget,
1214
- title: stepTitle,
1215
- isBackward,
1216
- ...step
1217
- } of viewSteps) {
1218
- const source = this.graph.element(stepSource);
1219
- const target = this.graph.element(stepTarget);
1220
- this.explicits.add(source);
1221
- this.explicits.add(target);
1222
- const { title, relations, tags } = this.findRelations(source, target);
1223
- this.steps.push({
1224
- ...step,
1225
- source,
1226
- target,
1227
- title: remeda.isTruthy(stepTitle) ? stepTitle : title,
1228
- relations: relations ?? [],
1229
- isBackward: isBackward ?? false,
1230
- ...tags ? { tags } : {}
1231
- });
1267
+ let stepNum = 1;
1268
+ for (const step of viewSteps) {
1269
+ if (core.isDynamicViewParallelSteps(step)) {
1270
+ if (step.__parallel.length === 0) {
1271
+ continue;
1272
+ }
1273
+ if (step.__parallel.length === 1) {
1274
+ this.addStep(step.__parallel[0], stepNum);
1275
+ } else {
1276
+ step.__parallel.forEach((s, i) => this.addStep(s, i + 1, stepNum));
1277
+ }
1278
+ } else {
1279
+ this.addStep(step, stepNum);
1280
+ }
1281
+ stepNum++;
1232
1282
  }
1233
1283
  for (const rule of rules) {
1234
1284
  if (core.isDynamicViewIncludeRule(rule)) {
@@ -1240,12 +1290,10 @@ class DynamicViewComputeCtx {
1240
1290
  }
1241
1291
  const elements = [...this.explicits];
1242
1292
  const nodesMap = buildComputeNodes(elements);
1243
- const edges = this.steps.map(({ source, target, relations, title, isBackward, ...step }, index) => {
1293
+ const edges = this.steps.map(({ source, target, relations, title, isBackward, ...step }) => {
1244
1294
  const sourceNode = core.nonNullable(nodesMap.get(source.id), `Source node ${source.id} not found`);
1245
1295
  const targetNode = core.nonNullable(nodesMap.get(target.id), `Target node ${target.id} not found`);
1246
- const stepNum = index + 1;
1247
1296
  const edge = {
1248
- id: core.StepEdgeId(stepNum),
1249
1297
  parent: core.commonAncestor(source.id, target.id),
1250
1298
  source: source.id,
1251
1299
  target: target.id,
@@ -1302,41 +1350,43 @@ class DynamicViewComputeCtx {
1302
1350
  }
1303
1351
  findRelations(source, target) {
1304
1352
  const relationships = remeda.unique(this.graph.edgesBetween(source, target).flatMap((e) => e.relations));
1305
- const alltags = remeda.unique(relationships.flatMap((r) => r.tags ?? []));
1306
- const tags = remeda.hasAtLeast(alltags, 1) ? alltags : null;
1307
- const relations = remeda.hasAtLeast(relationships, 1) ? remeda.map(relationships, (r) => r.id) : null;
1308
1353
  if (relationships.length === 0) {
1309
1354
  return {
1310
1355
  title: null,
1311
- tags,
1312
- relations
1313
- };
1314
- }
1315
- let relation;
1316
- if (relationships.length === 1) {
1317
- relation = relationships[0];
1318
- } else {
1319
- relation = relationships.find((r) => r.source === source.id && r.target === target.id);
1320
- }
1321
- if (relation && remeda.isTruthy(relation.title)) {
1322
- return {
1323
- title: relation.title,
1324
- tags,
1325
- relations
1326
- };
1327
- }
1328
- const labels = remeda.unique(relationships.flatMap((r) => remeda.isTruthy(r.title) ? r.title : []));
1329
- if (labels.length === 1) {
1330
- return {
1331
- title: labels[0],
1332
- tags,
1333
- relations
1356
+ tags: null,
1357
+ relations: null,
1358
+ navigateTo: null
1334
1359
  };
1335
1360
  }
1361
+ const alltags = remeda.pipe(
1362
+ relationships,
1363
+ remeda.flatMap((r) => r.tags),
1364
+ remeda.filter(remeda.isTruthy),
1365
+ remeda.unique()
1366
+ );
1367
+ const tags = remeda.hasAtLeast(alltags, 1) ? alltags : null;
1368
+ const relations = remeda.hasAtLeast(relationships, 1) ? remeda.map(relationships, (r) => r.id) : null;
1369
+ const relation = remeda.only(relationships) || relationships.find((r) => r.source === source.id && r.target === target.id);
1370
+ const title = remeda.isTruthy(relation?.title) ? relation.title : remeda.pipe(
1371
+ relationships,
1372
+ remeda.map((r) => r.title),
1373
+ remeda.filter(remeda.isTruthy),
1374
+ remeda.unique(),
1375
+ remeda.only()
1376
+ );
1377
+ const navigateTo = !!relation?.navigateTo && relation.navigateTo !== this.view.id ? relation.navigateTo : remeda.pipe(
1378
+ relationships,
1379
+ remeda.map((r) => r.navigateTo),
1380
+ remeda.filter(remeda.isTruthy),
1381
+ remeda.filter((v) => v !== this.view.id),
1382
+ remeda.unique(),
1383
+ remeda.only()
1384
+ );
1336
1385
  return {
1337
- title: null,
1386
+ title: title ?? null,
1338
1387
  tags,
1339
- relations
1388
+ relations,
1389
+ navigateTo: navigateTo ?? null
1340
1390
  };
1341
1391
  }
1342
1392
  }