@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,13 @@
1
- import type { ComputedNode, Element, Fqn } from '@likec4/core'
2
- import { compareByFqnHierarchically, DefaultElementShape, DefaultThemeColor, parentFqn } from '@likec4/core'
1
+ import type { ComputedNode, Element, Fqn, NodeId } from '@likec4/core'
2
+ import {
3
+ compareByFqnHierarchically,
4
+ DefaultElementShape,
5
+ DefaultThemeColor,
6
+ ElementKind,
7
+ nonNullable,
8
+ parentFqn
9
+ } from '@likec4/core'
10
+ import { NodesGroup } from '../compute-view/compute'
3
11
 
4
12
  function updateDepthOfAncestors(node: ComputedNode, nodes: ReadonlyMap<Fqn, ComputedNode>) {
5
13
  let parentNd
@@ -14,48 +22,92 @@ function updateDepthOfAncestors(node: ComputedNode, nodes: ReadonlyMap<Fqn, Comp
14
22
  }
15
23
  }
16
24
 
17
- export function buildComputeNodes(elements: Iterable<Element>) {
25
+ export function buildComputeNodes(elements: Iterable<Element>, groups?: NodesGroup[]) {
26
+ const nodesMap = new Map<Fqn, ComputedNode>()
27
+
28
+ const elementToGroup = new Map<Fqn, NodeId>()
29
+
30
+ groups?.forEach(({ id, parent, viewRule, explicits }) => {
31
+ if (parent) {
32
+ nonNullable(nodesMap.get(parent), `Parent group node ${parent} not found`).children.push(id)
33
+ }
34
+ nodesMap.set(id, {
35
+ id,
36
+ parent,
37
+ kind: ElementKind.Group,
38
+ title: viewRule.title ?? '',
39
+ color: viewRule.color ?? 'muted',
40
+ shape: 'rectangle',
41
+ children: [],
42
+ inEdges: [],
43
+ outEdges: [],
44
+ level: 0,
45
+ depth: 0,
46
+ description: null,
47
+ technology: null,
48
+ tags: null,
49
+ links: null,
50
+ style: {
51
+ border: viewRule.border ?? 'dashed',
52
+ opacity: viewRule.opacity ?? 0
53
+ }
54
+ })
55
+ for (const e of explicits) {
56
+ elementToGroup.set(e.id, id)
57
+ }
58
+ })
59
+
18
60
  return (
19
61
  Array.from(elements)
20
62
  // Sort from Top to Bottom
21
63
  // So we can ensure that parent nodes are created before child nodes
22
64
  .sort(compareByFqnHierarchically)
23
- .reduce((map, { id, color, shape, style, ...el }) => {
65
+ .reduce((map, { id, color, shape, style, kind, title, ...el }) => {
24
66
  let parent = parentFqn(id)
25
67
  let level = 0
68
+ let parentNd: ComputedNode | undefined
26
69
  // Find the first ancestor that is already in the map
27
70
  while (parent) {
28
- const parentNd = map.get(parent)
71
+ parentNd = map.get(parent)
29
72
  if (parentNd) {
30
- // if parent has no children and we are about to add first one
31
- // we need to set its depth to 1
32
- if (parentNd.children.length == 0) {
33
- parentNd.depth = 1
34
- // go up the tree and update depth of all parents
35
- updateDepthOfAncestors(parentNd, map)
36
- }
37
- parentNd.children.push(id)
38
- level = parentNd.level + 1
39
73
  break
40
74
  }
41
75
  parent = parentFqn(parent)
42
76
  }
77
+ // If parent is not found in the map, check if it is in a group
78
+ if (!parentNd && elementToGroup.has(id)) {
79
+ parent = elementToGroup.get(id)!
80
+ parentNd = map.get(parent)!
81
+ }
82
+ if (parentNd) {
83
+ // if parent has no children and we are about to add first one
84
+ // we need to set its depth to 1
85
+ if (parentNd.children.length == 0) {
86
+ parentNd.depth = 1
87
+ // go up the tree and update depth of all parents
88
+ updateDepthOfAncestors(parentNd, map)
89
+ }
90
+ parentNd.children.push(id)
91
+ level = parentNd.level + 1
92
+ }
43
93
  const node: ComputedNode = {
44
- ...el,
45
94
  id,
46
95
  parent,
47
- level,
96
+ kind,
97
+ title,
48
98
  color: color ?? DefaultThemeColor,
49
99
  shape: shape ?? DefaultElementShape,
50
100
  children: [],
51
101
  inEdges: [],
52
102
  outEdges: [],
103
+ level,
104
+ ...el,
53
105
  style: {
54
106
  ...style
55
107
  }
56
108
  }
57
109
  map.set(id, node)
58
110
  return map
59
- }, new Map<Fqn, ComputedNode>()) as ReadonlyMap<Fqn, ComputedNode>
111
+ }, nodesMap) as ReadonlyMap<Fqn, ComputedNode>
60
112
  )
61
113
  }
@@ -4,7 +4,9 @@ import { isNullish } from 'remeda'
4
4
 
5
5
  type Predicate<T> = (x: T) => boolean
6
6
 
7
- export function elementExprToPredicate(target: Expr.ElementPredicateExpression): Predicate<Element> {
7
+ export function elementExprToPredicate<T extends Pick<Element, 'id' | 'kind' | 'tags'>>(
8
+ target: Expr.ElementPredicateExpression
9
+ ): Predicate<T> {
8
10
  if (Expr.isElementWhere(target)) {
9
11
  const predicate = elementExprToPredicate(target.where.expr)
10
12
  const where = whereOperatorAsPredicate(target.where.condition)
@@ -0,0 +1,43 @@
1
+ import type { Element, Relation } from '@likec4/core'
2
+ import { Expr, nonexhaustive, whereOperatorAsPredicate } from '@likec4/core'
3
+ import { elementExprToPredicate } from './elementExpressionToPredicate'
4
+
5
+ type Predicate<T> = (x: T) => boolean
6
+ export type FilterableEdge = Pick<Relation, 'kind' | 'tags'> & {
7
+ source: Element
8
+ target: Element
9
+ }
10
+
11
+ export function relationExpressionToPredicates<T extends FilterableEdge>(
12
+ expr: Expr.RelationExpression | Expr.RelationWhereExpr
13
+ ): Predicate<T> {
14
+ switch (true) {
15
+ case Expr.isRelationWhere(expr):
16
+ const predicate = relationExpressionToPredicates(expr.where.expr)
17
+ const where = whereOperatorAsPredicate(expr.where.condition)
18
+
19
+ return e => predicate(e) && where(e)
20
+ case Expr.isRelation(expr): {
21
+ const isSource = elementExprToPredicate(expr.source)
22
+ const isTarget = elementExprToPredicate(expr.target)
23
+ return edge => {
24
+ return (isSource(edge.source) && isTarget(edge.target))
25
+ || (!!expr.isBidirectional && isSource(edge.target) && isTarget(edge.source))
26
+ }
27
+ }
28
+ case Expr.isInOut(expr): {
29
+ const isInOut = elementExprToPredicate(expr.inout)
30
+ return edge => isInOut(edge.source) || isInOut(edge.target)
31
+ }
32
+ case Expr.isIncoming(expr): {
33
+ const isTarget = elementExprToPredicate(expr.incoming)
34
+ return edge => isTarget(edge.target)
35
+ }
36
+ case Expr.isOutgoing(expr): {
37
+ const isSource = elementExprToPredicate(expr.outgoing)
38
+ return edge => isSource(edge.source)
39
+ }
40
+ default:
41
+ nonexhaustive(expr)
42
+ }
43
+ }
@@ -8,7 +8,7 @@ import {
8
8
  invariant,
9
9
  nonNullable
10
10
  } from '@likec4/core'
11
- import { difference, filter, map, pipe, sort } from 'remeda'
11
+ import { difference, filter, map, pipe, sort, tap } from 'remeda'
12
12
  import { Graph, postorder } from '../../utils/graphlib'
13
13
 
14
14
  // side effect
@@ -27,9 +27,16 @@ export function sortNodes({
27
27
  nodes: ComputedNode[]
28
28
  edges: ComputedEdge[]
29
29
  }): ComputedNode[] {
30
- if (edges.length === 0) {
30
+ if (nodes.length < 2) {
31
31
  return nodes
32
32
  }
33
+ if (edges.length === 0) {
34
+ return pipe(
35
+ nodes,
36
+ sort(compareByFqnHierarchically),
37
+ tap(sortChildren)
38
+ )
39
+ }
33
40
 
34
41
  const g = new Graph({
35
42
  compound: false,
@@ -55,9 +62,6 @@ export function sortNodes({
55
62
  for (const n of nodes) {
56
63
  g.setNode(n.id, n.id)
57
64
  if (n.children.length > 0) {
58
- // n.children.forEach(c => {
59
- // g.setEdge(n.id, c, undefined, `${n.id}:${c}`)
60
- // })
61
65
  n.inEdges.forEach(e => {
62
66
  const edge = getEdge(e)
63
67
  // if this edge from leaf to the child of this node
@@ -83,15 +87,15 @@ export function sortNodes({
83
87
  if (sources.length === 0) {
84
88
  sources = pipe(
85
89
  nodes,
86
- sort(compareByFqnHierarchically),
87
90
  filter(n => n.inEdges.length === 0 || n.parent === null),
91
+ sort(compareByFqnHierarchically),
88
92
  map(n => n.id)
89
93
  )
90
94
  }
91
95
  const orderedIds = postorder(g, sources).reverse() as Fqn[]
92
96
  const sorted = orderedIds.map(getNode)
93
97
  if (sorted.length < nodes.length) {
94
- const unsorted = difference(nodes, sorted)
98
+ const unsorted = difference(nodes, sorted).sort(compareByFqnHierarchically)
95
99
  sorted.push(...unsorted)
96
100
  }
97
101
 
@@ -71,8 +71,9 @@ export class LikeC4ScopeComputation extends DefaultScopeComputation {
71
71
  }
72
72
  for (const globalStyleAst of globals.flatMap(g => g.styles)) {
73
73
  try {
74
- if (isTruthy(globalStyleAst.name)) {
75
- docExports.push(this.descriptions.createDescription(globalStyleAst, globalStyleAst.name, document))
74
+ const id = globalStyleAst.id
75
+ if (isTruthy(id.name)) {
76
+ docExports.push(this.descriptions.createDescription(id, id.name, document))
76
77
  }
77
78
  } catch (e) {
78
79
  logError(e)
@@ -9,7 +9,7 @@ import { relationBodyChecks, relationChecks } from './relation'
9
9
  import {
10
10
  elementKindChecks,
11
11
  globalsChecks,
12
- globalStyleChecks,
12
+ globalStyleIdChecks,
13
13
  modelRuleChecks,
14
14
  relationshipChecks,
15
15
  specificationRuleChecks,
@@ -34,7 +34,7 @@ export function registerValidationChecks(services: LikeC4Services) {
34
34
  SpecificationRule: specificationRuleChecks(services),
35
35
  Model: modelRuleChecks(services),
36
36
  Globals: globalsChecks(services),
37
- GlobalStyle: globalStyleChecks(services),
37
+ GlobalStyleId: globalStyleIdChecks(services),
38
38
  DynamicViewStep: dynamicViewStep(services),
39
39
  LikeC4View: viewChecks(services),
40
40
  Element: elementChecks(services),
@@ -130,18 +130,18 @@ export const relationshipChecks = (
130
130
  }
131
131
  }
132
132
 
133
- export const globalStyleChecks = (
133
+ export const globalStyleIdChecks = (
134
134
  services: LikeC4Services
135
- ): ValidationCheck<ast.GlobalStyle> => {
135
+ ): ValidationCheck<ast.GlobalStyleId> => {
136
136
  const index = services.shared.workspace.IndexManager
137
137
  return (node, accept) => {
138
138
  const sameName = index
139
- .allElements(ast.GlobalStyle)
139
+ .allElements(ast.GlobalStyleId)
140
140
  .filter(s => s.name === node.name)
141
141
  .limit(2)
142
142
  .count()
143
143
  if (sameName > 1) {
144
- accept('error', `Duplicate GlobalStyle name '${node.name}'`, {
144
+ accept('error', `Duplicate GlobalStyleId name '${node.name}'`, {
145
145
  node: node,
146
146
  property: 'name'
147
147
  })