@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
@@ -1,5 +1,5 @@
1
- import { Expr, whereOperatorAsPredicate, parentFqn, nonexhaustive, isViewRuleStyle, compareByFqnHierarchically, DefaultThemeColor, DefaultElementShape, compareRelations, invariant, nonNullable, isAncestor, isViewRulePredicate, ancestorsFqn, isViewRuleAutoLayout, isScopedElementView, commonAncestor, isDynamicViewIncludeRule, StepEdgeId, 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, first, isString, isArray } from 'remeda';
1
+ import { Expr, whereOperatorAsPredicate, parentFqn, nonexhaustive, isViewRuleStyle, compareByFqnHierarchically, DefaultThemeColor, DefaultElementShape, compareRelations, invariant, nonNullable, 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, reduce, only, isNonNull, isString, isArray } from 'remeda';
3
3
  import graphlib, { Graph } from '@dagrejs/graphlib';
4
4
  import objectHash from 'object-hash';
5
5
 
@@ -288,8 +288,8 @@ function buildElementNotations(nodes) {
288
288
  }))
289
289
  ),
290
290
  sortBy(
291
- prop("title"),
292
291
  prop("shape"),
292
+ prop("title"),
293
293
  [
294
294
  (n) => n.kinds.length,
295
295
  "desc"
@@ -614,13 +614,11 @@ const filterEdges = (edges, where) => {
614
614
  );
615
615
  };
616
616
  const filterRelations = (edges, where) => {
617
- if (!where) {
618
- return edges.flatMap((e) => e.relations);
619
- }
620
617
  return pipe(
621
618
  edges,
622
619
  flatMap((e) => e.relations),
623
- filter(where)
620
+ where ? filter(where) : Identity,
621
+ unique()
624
622
  );
625
623
  };
626
624
  function includeIncomingExpr(expr, where) {
@@ -868,7 +866,7 @@ class ComputeCtx {
868
866
  computeEdges() {
869
867
  return this.ctxEdges.map((e) => {
870
868
  invariant(hasAtLeast(e.relations, 1), "Edge must have at least one relation");
871
- const relations = e.relations.toSorted(compareRelations);
869
+ const relations = sort(e.relations, compareRelations);
872
870
  const source = e.source.id;
873
871
  const target = e.target.id;
874
872
  const edge = {
@@ -880,48 +878,62 @@ class ComputeCtx {
880
878
  relations: relations.map((r) => r.id)
881
879
  };
882
880
  let relation;
883
- if (relations.length === 1) {
884
- relation = relations[0];
885
- } else {
886
- relation = relations.find((r) => r.source === source && r.target === target);
887
- }
881
+ relation = relations.length === 1 ? relations[0] : relations.find((r) => r.source === source && r.target === target);
888
882
  if (!relation) {
889
- relation = relations.reduce((acc, r) => {
890
- if (r.color && acc.color !== r.color) {
891
- acc.color = void 0;
892
- }
893
- if (r.kind && acc.kind !== r.kind) {
894
- acc.kind = void 0;
895
- }
896
- if (r.head && acc.head !== r.head) {
897
- acc.head = void 0;
898
- }
899
- if (r.tail && acc.tail !== r.tail) {
900
- acc.tail = void 0;
901
- }
902
- if (r.line && acc.line !== r.line) {
903
- acc.line = void 0;
904
- }
905
- if (r.description && acc.description !== r.description) {
906
- acc.description = void 0;
907
- }
908
- if (r.technology && acc.technology !== r.technology) {
909
- acc.technology = void 0;
910
- }
911
- if (isTruthy(r.title) && acc.title !== r.title) {
912
- acc.title = "[...]";
913
- }
914
- return acc;
915
- }, {
916
- title: first(flatMap(relations, (r) => isTruthy(r.title) ? r.title : [])) ?? "[...]",
917
- description: first(flatMap(relations, (r) => isTruthy(r.description) ? r.description : [])),
918
- technology: first(flatMap(relations, (r) => isTruthy(r.technology) ? r.technology : [])),
919
- kind: first(flatMap(relations, (r) => isTruthy(r.kind) ? r.kind : [])),
920
- head: first(flatMap(relations, (r) => isTruthy(r.head) ? r.head : [])),
921
- tail: first(flatMap(relations, (r) => isTruthy(r.tail) ? r.tail : [])),
922
- color: first(flatMap(relations, (r) => isTruthy(r.color) ? r.color : [])),
923
- line: first(flatMap(relations, (r) => isTruthy(r.line) ? r.line : []))
924
- });
883
+ const allprops = pipe(
884
+ relations,
885
+ reduce((acc, r) => {
886
+ if (isTruthy(r.title) && !acc.title.includes(r.title)) {
887
+ acc.title.push(r.title);
888
+ }
889
+ if (isTruthy(r.description) && !acc.description.includes(r.description)) {
890
+ acc.description.push(r.description);
891
+ }
892
+ if (isTruthy(r.technology) && !acc.technology.includes(r.technology)) {
893
+ acc.technology.push(r.technology);
894
+ }
895
+ if (isTruthy(r.kind) && !acc.kind.includes(r.kind)) {
896
+ acc.kind.push(r.kind);
897
+ }
898
+ if (isTruthy(r.color) && !acc.color.includes(r.color)) {
899
+ acc.color.push(r.color);
900
+ }
901
+ if (isTruthy(r.line) && !acc.line.includes(r.line)) {
902
+ acc.line.push(r.line);
903
+ }
904
+ if (isTruthy(r.head) && !acc.head.includes(r.head)) {
905
+ acc.head.push(r.head);
906
+ }
907
+ if (isTruthy(r.tail) && !acc.tail.includes(r.tail)) {
908
+ acc.tail.push(r.tail);
909
+ }
910
+ if (isTruthy(r.navigateTo) && !acc.navigateTo.includes(r.navigateTo)) {
911
+ acc.navigateTo.push(r.navigateTo);
912
+ }
913
+ return acc;
914
+ }, {
915
+ title: [],
916
+ description: [],
917
+ technology: [],
918
+ kind: [],
919
+ head: [],
920
+ tail: [],
921
+ color: [],
922
+ line: [],
923
+ navigateTo: []
924
+ })
925
+ );
926
+ relation = {
927
+ title: only(allprops.title) ?? "[...]",
928
+ description: only(allprops.description),
929
+ technology: only(allprops.technology),
930
+ kind: only(allprops.kind),
931
+ head: only(allprops.head),
932
+ tail: only(allprops.tail),
933
+ color: only(allprops.color),
934
+ line: only(allprops.line),
935
+ navigateTo: only(allprops.navigateTo)
936
+ };
925
937
  }
926
938
  const tags = unique(flatMap(relations, (r) => r.tags ?? []));
927
939
  return Object.assign(
@@ -934,6 +946,7 @@ class ComputeCtx {
934
946
  relation.line && { line: relation.line },
935
947
  relation.head && { head: relation.head },
936
948
  relation.tail && { tail: relation.tail },
949
+ relation.navigateTo && { navigateTo: relation.navigateTo },
937
950
  hasAtLeast(tags, 1) && { tags }
938
951
  );
939
952
  });
@@ -995,28 +1008,39 @@ class ComputeCtx {
995
1008
  this.implicits.delete(el);
996
1009
  }
997
1010
  }
998
- excludeImplicit(...excludes) {
999
- for (const el of excludes) {
1000
- this.implicits.delete(el);
1001
- }
1002
- }
1011
+ // protected excludeImplicit(...excludes: Element[]) {
1012
+ // for (const el of excludes) {
1013
+ // this.implicits.delete(el)
1014
+ // }
1015
+ // }
1003
1016
  excludeRelation(...relations) {
1017
+ if (relations.length === 0) {
1018
+ return;
1019
+ }
1004
1020
  const excludedImplicits = /* @__PURE__ */ new Set();
1005
- for (const relation of relations) {
1006
- let edge;
1007
- while (edge = this.ctxEdges.find((e) => e.relations.includes(relation))) {
1008
- if (edge.relations.length === 1) {
1021
+ const ctxEdges = pipe(
1022
+ this.ctxEdges,
1023
+ map((edge) => {
1024
+ const edgerelations = edge.relations.filter((r) => !relations.includes(r));
1025
+ if (edgerelations.length === 0) {
1009
1026
  excludedImplicits.add(edge.source);
1010
1027
  excludedImplicits.add(edge.target);
1011
- this.ctxEdges.splice(this.ctxEdges.indexOf(edge), 1);
1012
- continue;
1028
+ return null;
1013
1029
  }
1014
- edge.relations = edge.relations.filter((r) => r !== relation);
1015
- }
1016
- }
1030
+ if (edgerelations.length !== edge.relations.length) {
1031
+ return {
1032
+ ...edge,
1033
+ relations: edgerelations
1034
+ };
1035
+ }
1036
+ return edge;
1037
+ }),
1038
+ filter(isNonNull)
1039
+ );
1017
1040
  if (excludedImplicits.size === 0) {
1018
1041
  return;
1019
1042
  }
1043
+ this.ctxEdges = ctxEdges;
1020
1044
  const remaining = this.includedElements;
1021
1045
  if (remaining.size === 0) {
1022
1046
  this.implicits.clear();
@@ -1160,7 +1184,7 @@ class ComputeCtx {
1160
1184
  if (isTruthy(relation.technology)) {
1161
1185
  labelParts.push(`[${relation.technology}]`);
1162
1186
  }
1163
- return labelParts.length > 0 && { label: labelParts.join("\n") };
1187
+ return labelParts.length > 0 ? { label: labelParts.join("\n") } : {};
1164
1188
  }
1165
1189
  }
1166
1190
 
@@ -1193,6 +1217,38 @@ class DynamicViewComputeCtx {
1193
1217
  static compute(view, graph) {
1194
1218
  return new DynamicViewComputeCtx(view, graph).compute();
1195
1219
  }
1220
+ addStep({
1221
+ source: stepSource,
1222
+ target: stepTarget,
1223
+ title: stepTitle,
1224
+ isBackward,
1225
+ navigateTo: stepNavigateTo,
1226
+ ...step
1227
+ }, index, parent) {
1228
+ const id = parent ? StepEdgeId(parent, index) : StepEdgeId(index);
1229
+ const source = this.graph.element(stepSource);
1230
+ const target = this.graph.element(stepTarget);
1231
+ this.explicits.add(source);
1232
+ this.explicits.add(target);
1233
+ const {
1234
+ title,
1235
+ relations,
1236
+ tags,
1237
+ navigateTo: derivedNavigateTo
1238
+ } = this.findRelations(source, target);
1239
+ const navigateTo = isTruthy(stepNavigateTo) && stepNavigateTo !== this.view.id ? stepNavigateTo : derivedNavigateTo;
1240
+ this.steps.push({
1241
+ id,
1242
+ ...step,
1243
+ source,
1244
+ target,
1245
+ title: stepTitle ?? title,
1246
+ relations: relations ?? [],
1247
+ isBackward: isBackward ?? false,
1248
+ ...navigateTo ? { navigateTo } : {},
1249
+ ...tags ? { tags } : {}
1250
+ });
1251
+ }
1196
1252
  compute() {
1197
1253
  const {
1198
1254
  docUri: _docUri,
@@ -1201,27 +1257,21 @@ class DynamicViewComputeCtx {
1201
1257
  steps: viewSteps,
1202
1258
  ...view
1203
1259
  } = this.view;
1204
- for (let {
1205
- source: stepSource,
1206
- target: stepTarget,
1207
- title: stepTitle,
1208
- isBackward,
1209
- ...step
1210
- } of viewSteps) {
1211
- const source = this.graph.element(stepSource);
1212
- const target = this.graph.element(stepTarget);
1213
- this.explicits.add(source);
1214
- this.explicits.add(target);
1215
- const { title, relations, tags } = this.findRelations(source, target);
1216
- this.steps.push({
1217
- ...step,
1218
- source,
1219
- target,
1220
- title: isTruthy(stepTitle) ? stepTitle : title,
1221
- relations: relations ?? [],
1222
- isBackward: isBackward ?? false,
1223
- ...tags ? { tags } : {}
1224
- });
1260
+ let stepNum = 1;
1261
+ for (const step of viewSteps) {
1262
+ if (isDynamicViewParallelSteps(step)) {
1263
+ if (step.__parallel.length === 0) {
1264
+ continue;
1265
+ }
1266
+ if (step.__parallel.length === 1) {
1267
+ this.addStep(step.__parallel[0], stepNum);
1268
+ } else {
1269
+ step.__parallel.forEach((s, i) => this.addStep(s, i + 1, stepNum));
1270
+ }
1271
+ } else {
1272
+ this.addStep(step, stepNum);
1273
+ }
1274
+ stepNum++;
1225
1275
  }
1226
1276
  for (const rule of rules) {
1227
1277
  if (isDynamicViewIncludeRule(rule)) {
@@ -1233,12 +1283,10 @@ class DynamicViewComputeCtx {
1233
1283
  }
1234
1284
  const elements = [...this.explicits];
1235
1285
  const nodesMap = buildComputeNodes(elements);
1236
- const edges = this.steps.map(({ source, target, relations, title, isBackward, ...step }, index) => {
1286
+ const edges = this.steps.map(({ source, target, relations, title, isBackward, ...step }) => {
1237
1287
  const sourceNode = nonNullable(nodesMap.get(source.id), `Source node ${source.id} not found`);
1238
1288
  const targetNode = nonNullable(nodesMap.get(target.id), `Target node ${target.id} not found`);
1239
- const stepNum = index + 1;
1240
1289
  const edge = {
1241
- id: StepEdgeId(stepNum),
1242
1290
  parent: commonAncestor(source.id, target.id),
1243
1291
  source: source.id,
1244
1292
  target: target.id,
@@ -1295,41 +1343,43 @@ class DynamicViewComputeCtx {
1295
1343
  }
1296
1344
  findRelations(source, target) {
1297
1345
  const relationships = unique(this.graph.edgesBetween(source, target).flatMap((e) => e.relations));
1298
- const alltags = unique(relationships.flatMap((r) => r.tags ?? []));
1299
- const tags = hasAtLeast(alltags, 1) ? alltags : null;
1300
- const relations = hasAtLeast(relationships, 1) ? map(relationships, (r) => r.id) : null;
1301
1346
  if (relationships.length === 0) {
1302
1347
  return {
1303
1348
  title: null,
1304
- tags,
1305
- relations
1306
- };
1307
- }
1308
- let relation;
1309
- if (relationships.length === 1) {
1310
- relation = relationships[0];
1311
- } else {
1312
- relation = relationships.find((r) => r.source === source.id && r.target === target.id);
1313
- }
1314
- if (relation && isTruthy(relation.title)) {
1315
- return {
1316
- title: relation.title,
1317
- tags,
1318
- relations
1319
- };
1320
- }
1321
- const labels = unique(relationships.flatMap((r) => isTruthy(r.title) ? r.title : []));
1322
- if (labels.length === 1) {
1323
- return {
1324
- title: labels[0],
1325
- tags,
1326
- relations
1349
+ tags: null,
1350
+ relations: null,
1351
+ navigateTo: null
1327
1352
  };
1328
1353
  }
1354
+ const alltags = pipe(
1355
+ relationships,
1356
+ flatMap((r) => r.tags),
1357
+ filter(isTruthy),
1358
+ unique()
1359
+ );
1360
+ const tags = hasAtLeast(alltags, 1) ? alltags : null;
1361
+ const relations = hasAtLeast(relationships, 1) ? map(relationships, (r) => r.id) : null;
1362
+ const relation = only(relationships) || relationships.find((r) => r.source === source.id && r.target === target.id);
1363
+ const title = isTruthy(relation?.title) ? relation.title : pipe(
1364
+ relationships,
1365
+ map((r) => r.title),
1366
+ filter(isTruthy),
1367
+ unique(),
1368
+ only()
1369
+ );
1370
+ const navigateTo = !!relation?.navigateTo && relation.navigateTo !== this.view.id ? relation.navigateTo : pipe(
1371
+ relationships,
1372
+ map((r) => r.navigateTo),
1373
+ filter(isTruthy),
1374
+ filter((v) => v !== this.view.id),
1375
+ unique(),
1376
+ only()
1377
+ );
1329
1378
  return {
1330
- title: null,
1379
+ title: title ?? null,
1331
1380
  tags,
1332
- relations
1381
+ relations,
1382
+ navigateTo: navigateTo ?? null
1333
1383
  };
1334
1384
  }
1335
1385
  }