@likec4/language-server 1.15.1 → 1.17.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 (49) 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 +35 -5
  8. package/dist/index.d.cts +17 -4
  9. package/dist/index.d.mts +17 -4
  10. package/dist/index.d.ts +17 -4
  11. package/dist/index.mjs +34 -5
  12. package/dist/model-graph/index.cjs +1 -1
  13. package/dist/model-graph/index.d.cts +4 -2
  14. package/dist/model-graph/index.d.mts +4 -2
  15. package/dist/model-graph/index.d.ts +4 -2
  16. package/dist/model-graph/index.mjs +1 -1
  17. package/dist/shared/{language-server.BuChFlda.mjs → language-server.B8qSDsWW.mjs} +211 -96
  18. package/dist/shared/language-server.BGGRJRnr.d.mts +1338 -0
  19. package/dist/shared/{language-server.BT4WTbFI.mjs → language-server.BXFhlTPo.mjs} +139 -76
  20. package/dist/shared/{language-server.DfMwkd2l.d.mts → language-server.BgDKnNok.d.mts} +45 -11
  21. package/dist/shared/language-server.Bmpq16Gw.d.ts +1338 -0
  22. package/dist/shared/language-server.C1ZfM22X.d.cts +1338 -0
  23. package/dist/shared/{language-server.U2piOAVt.d.cts → language-server.DJo88TnT.d.cts} +45 -11
  24. package/dist/shared/{language-server.DfjkvknB.cjs → language-server.DZRuJVSg.cjs} +209 -94
  25. package/dist/shared/{language-server.B8s2wfT_.cjs → language-server.N8HLDQqz.cjs} +137 -74
  26. package/dist/shared/{language-server.j-ShR6as.d.ts → language-server.PEjk7U9s.d.ts} +45 -11
  27. package/package.json +7 -7
  28. package/src/LikeC4FileSystem.ts +36 -0
  29. package/src/Rpc.ts +2 -2
  30. package/src/ast.ts +11 -3
  31. package/src/generated/ast.ts +112 -11
  32. package/src/generated/grammar.ts +1 -1
  33. package/src/index.ts +3 -3
  34. package/src/like-c4.langium +20 -1
  35. package/src/lsp/SemanticTokenProvider.ts +26 -8
  36. package/src/model/fqn-computation.ts +6 -2
  37. package/src/model/model-builder.ts +25 -30
  38. package/src/model/model-parser.ts +91 -18
  39. package/src/model-graph/LikeC4ModelGraph.ts +22 -11
  40. package/src/model-graph/compute-view/__test__/fixture.ts +69 -19
  41. package/src/model-graph/compute-view/compute.ts +85 -73
  42. package/src/model-graph/dynamic-view/compute.ts +12 -2
  43. package/src/model-graph/utils/applyCustomElementProperties.ts +1 -3
  44. package/src/model-graph/utils/applyViewRuleStyles.ts +0 -4
  45. package/src/references/scope-computation.ts +10 -0
  46. package/src/validation/index.ts +3 -0
  47. package/src/validation/specification.ts +21 -0
  48. package/src/view-utils/index.ts +0 -1
  49. package/src/view-utils/resolve-global-rules.ts +66 -50
@@ -8,6 +8,7 @@ import {
8
8
  ElementKind,
9
9
  type ElementPredicateExpression,
10
10
  type ElementView,
11
+ type Fqn,
11
12
  type NodeId,
12
13
  type NonEmptyArray,
13
14
  type Relation,
@@ -54,6 +55,7 @@ import {
54
55
  sort,
55
56
  unique
56
57
  } from 'remeda'
58
+ import { resolveGlobalRulesInElementView } from '../../view-utils/resolve-global-rules'
57
59
  import { calcViewLayoutHash } from '../../view-utils/view-hash'
58
60
  import type { LikeC4ModelGraph } from '../LikeC4ModelGraph'
59
61
  import { applyCustomElementProperties } from '../utils/applyCustomElementProperties'
@@ -181,6 +183,29 @@ export class ComputeCtx {
181
183
  return this.activeGroupStack[0] ?? this.__rootGroup
182
184
  }
183
185
 
186
+ protected get includedElements() {
187
+ return new Set([
188
+ ...this.explicits,
189
+ ...this.ctxEdges.flatMap(e => [e.source, e.target])
190
+ ]) as ReadonlySet<Element>
191
+ }
192
+
193
+ protected get resolvedElements() {
194
+ return new Set([
195
+ ...this.explicits,
196
+ ...this.implicits,
197
+ ...this.ctxEdges.flatMap(e => [e.source, e.target])
198
+ ]) as ReadonlySet<Element>
199
+ }
200
+
201
+ protected get edges() {
202
+ return this.ctxEdges
203
+ }
204
+
205
+ protected get root() {
206
+ return isScopedElementView(this.view) ? this.view.viewOf : null
207
+ }
208
+
184
209
  public static elementView(view: ElementView, graph: LikeC4ModelGraph) {
185
210
  return new ComputeCtx(view, graph).compute()
186
211
  }
@@ -194,10 +219,12 @@ export class ComputeCtx {
194
219
  this.reset()
195
220
  const {
196
221
  docUri: _docUri, // exclude docUri
197
- rules,
222
+ rules: _rules, // exclude rules
198
223
  ...view
199
224
  } = this.view
200
225
 
226
+ const rules = resolveGlobalRulesInElementView(this.view, this.graph.globals)
227
+
201
228
  const viewPredicates = rules.filter(anyPass([isViewRulePredicate, isViewRuleGroup])) as Array<
202
229
  ViewRulePredicate | ViewRuleGroup
203
230
  >
@@ -213,56 +240,10 @@ export class ComputeCtx {
213
240
  const elements = [...this.includedElements]
214
241
  const nodesMap = buildComputeNodes(elements, this.groups)
215
242
 
216
- const ancestorsOf = (node: ComputedNode) => {
217
- const ancestors = [] as ComputedNode[]
218
- let parent = node.parent
219
- while (parent) {
220
- const parentNode = nodesMap.get(parent)
221
- if (!parentNode) {
222
- break
223
- }
224
- ancestors.push(parentNode)
225
- parent = parentNode.parent
226
- }
227
- return ancestors
228
- }
229
-
230
- const edgesMap = new Map<EdgeId, ComputedEdge>()
231
243
  const edges = this.computeEdges()
232
- for (const edge of edges) {
233
- edgesMap.set(edge.id, edge)
234
- const source = nodesMap.get(edge.source)
235
- const target = nodesMap.get(edge.target)
236
- invariant(source, `Source node ${edge.source} not found`)
237
- invariant(target, `Target node ${edge.target} not found`)
238
- // These ancestors are reversed: from bottom to top
239
- const sourceAncestors = ancestorsOf(source)
240
- const targetAncestors = ancestorsOf(target)
244
+ const edgesMap = new Map<EdgeId, ComputedEdge>(edges.map(edge => [edge.id, edge]))
241
245
 
242
- const edgeParent = last(
243
- commonHead(
244
- reverse(sourceAncestors),
245
- reverse(targetAncestors)
246
- )
247
- )
248
- edge.parent = edgeParent?.id ?? null
249
- source.outEdges.push(edge.id)
250
- target.inEdges.push(edge.id)
251
- // Process edge source ancestors
252
- for (const sourceAncestor of sourceAncestors) {
253
- if (sourceAncestor === edgeParent) {
254
- break
255
- }
256
- sourceAncestor.outEdges.push(edge.id)
257
- }
258
- // Process target hierarchy
259
- for (const targetAncestor of targetAncestors) {
260
- if (targetAncestor === edgeParent) {
261
- break
262
- }
263
- targetAncestor.inEdges.push(edge.id)
264
- }
265
- }
246
+ this.linkNodesAndEdges(nodesMap, edges)
266
247
 
267
248
  // nodesMap sorted hierarchically,
268
249
  // but we need to keep the initial sort
@@ -302,7 +283,14 @@ export class ComputeCtx {
302
283
  ...(autoLayoutRule?.nodeSep && { nodeSep: autoLayoutRule.nodeSep }),
303
284
  ...(autoLayoutRule?.rankSep && { rankSep: autoLayoutRule.rankSep })
304
285
  },
305
- nodes: map(nodes, omit(['notation'])),
286
+ nodes: map(nodes, n => {
287
+ // omit notation
288
+ delete n.notation
289
+ if (n.icon === 'none') {
290
+ delete n.icon
291
+ }
292
+ return n
293
+ }),
306
294
  edges: applyCustomRelationProperties(rules, nodes, sortedEdges),
307
295
  ...(elementNotations.length > 0 && {
308
296
  notation: {
@@ -312,10 +300,6 @@ export class ComputeCtx {
312
300
  })
313
301
  }
314
302
 
315
- protected get root() {
316
- return isScopedElementView(this.view) ? this.view.viewOf : null
317
- }
318
-
319
303
  protected computeEdges(): ComputedEdge[] {
320
304
  return this.ctxEdges.map((e): ComputedEdge => {
321
305
  invariant(hasAtLeast(e.relations, 1), 'Edge must have at least one relation')
@@ -428,23 +412,40 @@ export class ComputeCtx {
428
412
  })
429
413
  }
430
414
 
431
- protected get includedElements() {
432
- return new Set([
433
- ...this.explicits,
434
- ...this.ctxEdges.flatMap(e => [e.source, e.target])
435
- ]) as ReadonlySet<Element>
436
- }
437
-
438
- protected get resolvedElements() {
439
- return new Set([
440
- ...this.explicits,
441
- ...this.implicits,
442
- ...this.ctxEdges.flatMap(e => [e.source, e.target])
443
- ]) as ReadonlySet<Element>
444
- }
415
+ protected linkNodesAndEdges(nodesMap: ReadonlyMap<Fqn, ComputedNode>, edges: ComputedEdge[]) {
416
+ for (const edge of edges) {
417
+ const source = nodesMap.get(edge.source)
418
+ const target = nodesMap.get(edge.target)
419
+ invariant(source, `Source node ${edge.source} not found`)
420
+ invariant(target, `Target node ${edge.target} not found`)
421
+ // These ancestors are reversed: from bottom to top
422
+ const sourceAncestors = this.ancestorsOf(source, nodesMap)
423
+ const targetAncestors = this.ancestorsOf(target, nodesMap)
445
424
 
446
- protected get edges() {
447
- return this.ctxEdges
425
+ const edgeParent = last(
426
+ commonHead(
427
+ reverse(sourceAncestors),
428
+ reverse(targetAncestors)
429
+ )
430
+ )
431
+ edge.parent = edgeParent?.id ?? null
432
+ source.outEdges.push(edge.id)
433
+ target.inEdges.push(edge.id)
434
+ // Process edge source ancestors
435
+ for (const sourceAncestor of sourceAncestors) {
436
+ if (sourceAncestor === edgeParent) {
437
+ break
438
+ }
439
+ sourceAncestor.outEdges.push(edge.id)
440
+ }
441
+ // Process target hierarchy
442
+ for (const targetAncestor of targetAncestors) {
443
+ if (targetAncestor === edgeParent) {
444
+ break
445
+ }
446
+ targetAncestor.inEdges.push(edge.id)
447
+ }
448
+ }
448
449
  }
449
450
 
450
451
  protected addEdges(edges: ComputeCtx.Edge[]) {
@@ -539,9 +540,6 @@ export class ComputeCtx {
539
540
  }),
540
541
  filter(isNonNull)
541
542
  )
542
- if (excludedImplicits.size === 0) {
543
- return
544
- }
545
543
  this.ctxEdges = ctxEdges
546
544
  const remaining = this.includedElements
547
545
  if (remaining.size === 0) {
@@ -755,6 +753,20 @@ export class ComputeCtx {
755
753
  nonexhaustive(expr)
756
754
  }
757
755
 
756
+ private ancestorsOf(node: ComputedNode, nodesMap: ReadonlyMap<Fqn, ComputedNode>): ComputedNode[] {
757
+ const ancestors = [] as ComputedNode[]
758
+ let parent = node.parent
759
+ while (parent) {
760
+ const parentNode = nodesMap.get(parent)
761
+ if (!parentNode) {
762
+ break
763
+ }
764
+ ancestors.push(parentNode)
765
+ parent = parentNode.parent
766
+ }
767
+ return ancestors
768
+ }
769
+
758
770
  protected getEdgeLabel(
759
771
  relation: {
760
772
  title: string
@@ -26,6 +26,7 @@ import {
26
26
  StepEdgeId
27
27
  } from '@likec4/core'
28
28
  import { filter, flatMap, hasAtLeast, isTruthy, map, omit, only, pipe, unique } from 'remeda'
29
+ import { resolveGlobalRulesInDynamicView } from '../../view-utils/resolve-global-rules'
29
30
  import { calcViewLayoutHash } from '../../view-utils/view-hash'
30
31
  import type { LikeC4ModelGraph } from '../LikeC4ModelGraph'
31
32
  import { applyCustomElementProperties } from '../utils/applyCustomElementProperties'
@@ -111,7 +112,7 @@ export class DynamicViewComputeCtx {
111
112
  protected compute(): ComputedDynamicView {
112
113
  const {
113
114
  docUri: _docUri, // exclude docUri
114
- rules,
115
+ rules: _rules, // exclude rules
115
116
  steps: viewSteps,
116
117
  ...view
117
118
  } = this.view
@@ -133,6 +134,8 @@ export class DynamicViewComputeCtx {
133
134
  stepNum++
134
135
  }
135
136
 
137
+ const rules = resolveGlobalRulesInDynamicView(this.view, this.graph.globals)
138
+
136
139
  for (const rule of rules) {
137
140
  if (isDynamicViewIncludeRule(rule)) {
138
141
  for (const expr of rule.include) {
@@ -205,7 +208,14 @@ export class DynamicViewComputeCtx {
205
208
  ...(autoLayoutRule?.nodeSep && { nodeSep: autoLayoutRule.nodeSep }),
206
209
  ...(autoLayoutRule?.rankSep && { rankSep: autoLayoutRule.rankSep })
207
210
  },
208
- nodes: map(nodes, omit(['notation'])),
211
+ nodes: map(nodes, n => {
212
+ // omit notation
213
+ delete n.notation
214
+ if (n.icon === 'none') {
215
+ delete n.icon
216
+ }
217
+ return n
218
+ }),
209
219
  edges,
210
220
  ...(elementNotations.length > 0 && {
211
221
  notation: {
@@ -1,7 +1,5 @@
1
- import { ComputedNode, type Expression, type ViewRule } from '@likec4/core'
2
- import { Expr, isViewRuleGroup, isViewRulePredicate } from '@likec4/core'
1
+ import { ComputedNode, Expr, type Expression, isViewRuleGroup, isViewRulePredicate, type ViewRule } from '@likec4/core'
3
2
  import { isEmpty, isNullish, omitBy } from 'remeda'
4
- import { NodesGroup } from '../compute-view/compute'
5
3
  import { elementExprToPredicate } from './elementExpressionToPredicate'
6
4
 
7
5
  export function flattenGroupRules<T extends Expression>(guard: (expr: Expression) => expr is T) {
@@ -13,10 +13,6 @@ export function applyViewRuleStyles(_rules: ViewRule[], nodes: ComputedNode[]) {
13
13
  for (const rule of rules) {
14
14
  const predicates = [] as Predicate<ComputedNode>[]
15
15
  for (const target of rule.targets) {
16
- if (Expr.isWildcard(target)) {
17
- predicates.push(() => true)
18
- break
19
- }
20
16
  predicates.push(elementExprToPredicate(target) as Predicate<ComputedNode>)
21
17
  }
22
18
  pipe(
@@ -69,6 +69,16 @@ export class LikeC4ScopeComputation extends DefaultScopeComputation {
69
69
  if (isNullish(globals) || globals.length === 0) {
70
70
  return
71
71
  }
72
+ for (const globalPredicateAst of globals.flatMap(g => g.predicates)) {
73
+ try {
74
+ const id = globalPredicateAst
75
+ if (isTruthy(id.name)) {
76
+ docExports.push(this.descriptions.createDescription(id, id.name, document))
77
+ }
78
+ } catch (e) {
79
+ logError(e)
80
+ }
81
+ }
72
82
  for (const globalStyleAst of globals.flatMap(g => g.styles)) {
73
83
  try {
74
84
  const id = globalStyleAst.id
@@ -8,6 +8,7 @@ import { iconPropertyRuleChecks, notesPropertyRuleChecks, opacityPropertyRuleChe
8
8
  import { relationBodyChecks, relationChecks } from './relation'
9
9
  import {
10
10
  elementKindChecks,
11
+ globalPredicateChecks,
11
12
  globalsChecks,
12
13
  globalStyleIdChecks,
13
14
  modelRuleChecks,
@@ -34,6 +35,8 @@ export function registerValidationChecks(services: LikeC4Services) {
34
35
  SpecificationRule: specificationRuleChecks(services),
35
36
  Model: modelRuleChecks(services),
36
37
  Globals: globalsChecks(services),
38
+ GlobalPredicateGroup: globalPredicateChecks(services),
39
+ GlobalDynamicPredicateGroup: globalPredicateChecks(services),
37
40
  GlobalStyleId: globalStyleIdChecks(services),
38
41
  DynamicViewStep: dynamicViewStep(services),
39
42
  LikeC4View: viewChecks(services),
@@ -130,6 +130,27 @@ export const relationshipChecks = (
130
130
  }
131
131
  }
132
132
 
133
+ export const globalPredicateChecks = (
134
+ services: LikeC4Services
135
+ ): ValidationCheck<ast.GlobalPredicateGroup | ast.GlobalDynamicPredicateGroup> => {
136
+ const index = services.shared.workspace.IndexManager
137
+ return (node, accept) => {
138
+ const predicateGroups = index.allElements(ast.GlobalPredicateGroup)
139
+ const dynamicPredicateGroups = index.allElements(ast.GlobalDynamicPredicateGroup)
140
+ const sameName = predicateGroups
141
+ .concat(dynamicPredicateGroups)
142
+ .filter(s => s.name === node.name)
143
+ .limit(2)
144
+ .count()
145
+ if (sameName > 1) {
146
+ accept('error', `Duplicate GlobalPredicateGroup or GlobalDynamicPredicateGroup name '${node.name}'`, {
147
+ node: node,
148
+ property: 'name'
149
+ })
150
+ }
151
+ }
152
+ }
153
+
133
154
  export const globalStyleIdChecks = (
134
155
  services: LikeC4Services
135
156
  ): ValidationCheck<ast.GlobalStyleId> => {
@@ -1,4 +1,3 @@
1
1
  export * from './assignNavigateTo'
2
2
  export * from './resolve-extended-views'
3
- export * from './resolve-global-rules'
4
3
  export * from './resolve-relative-paths'
@@ -1,72 +1,88 @@
1
1
  import {
2
2
  type DynamicView,
3
+ type DynamicViewRule,
3
4
  type ElementView,
4
- type GlobalStyle,
5
- type GlobalStyleID,
6
5
  isDynamicView,
7
6
  isElementView,
7
+ isViewRuleGlobalPredicateRef,
8
8
  isViewRuleGlobalStyle,
9
- type LikeC4View,
10
- nonexhaustive
9
+ type ModelGlobals,
10
+ nonexhaustive,
11
+ type ViewRule,
12
+ type ViewRuleGlobalPredicateRef,
13
+ type ViewRuleGlobalStyle
11
14
  } from '@likec4/core'
12
15
  import { logger } from '@likec4/log'
16
+ import { isNullish } from 'remeda'
13
17
 
14
- export function resolveGlobalRules(
15
- view: LikeC4View,
16
- globalStyles: Record<GlobalStyleID, GlobalStyle>
17
- ): LikeC4View {
18
+ export function resolveGlobalRules<V extends DynamicView | ElementView>(
19
+ view: V,
20
+ globals: ModelGlobals
21
+ ): V {
18
22
  if (isElementView(view)) {
19
- return resolveGlobalStyleInElementView(view, globalStyles)
23
+ return {
24
+ ...view,
25
+ rules: resolveGlobalRulesInElementView(view, globals)
26
+ }
20
27
  } else if (isDynamicView(view)) {
21
- return resolveGlobalStyleInDynamicView(view, globalStyles)
28
+ return {
29
+ ...view,
30
+ rules: resolveGlobalRulesInDynamicView(view, globals)
31
+ }
22
32
  }
23
33
  nonexhaustive(view)
24
34
  }
25
35
 
26
- function resolveGlobalStyleInElementView(
36
+ type ViewRuleGlobal = ViewRuleGlobalPredicateRef | ViewRuleGlobalStyle
37
+
38
+ export function resolveGlobalRulesInElementView(
27
39
  view: ElementView,
28
- globalStyles: Record<GlobalStyleID, GlobalStyle>
29
- ): LikeC4View {
30
- const resolvedRules = view.rules
31
- .flatMap(rule => {
32
- if (isViewRuleGlobalStyle(rule)) {
33
- const globalStyle = globalStyles[rule.styleId]
34
- if (globalStyle === undefined) {
35
- logger.warn(`Global style not found: ${rule.styleId}`)
36
- return []
37
- }
38
- return globalStyle.styles
39
- } else {
40
- return rule
40
+ globals: ModelGlobals
41
+ ): Array<Exclude<ViewRule, ViewRuleGlobal>> {
42
+ return view.rules.reduce((acc, rule) => {
43
+ if (isViewRuleGlobalPredicateRef(rule)) {
44
+ const globalPredicates = globals.predicates[rule.predicateId]
45
+ if (isNullish(globalPredicates)) {
46
+ logger.warn(`Global predicate not found: ${rule.predicateId}`)
47
+ return acc
41
48
  }
42
- })
43
-
44
- return {
45
- ...view,
46
- rules: resolvedRules
47
- }
49
+ return acc.concat(globalPredicates)
50
+ }
51
+ if (isViewRuleGlobalStyle(rule)) {
52
+ const globalStyles = globals.styles[rule.styleId]
53
+ if (isNullish(globalStyles)) {
54
+ logger.warn(`Global style not found: ${rule.styleId}`)
55
+ return acc
56
+ }
57
+ return acc.concat(globalStyles)
58
+ }
59
+ acc.push(rule)
60
+ return acc
61
+ }, [] as Array<Exclude<ViewRule, ViewRuleGlobal>>)
48
62
  }
49
63
 
50
- function resolveGlobalStyleInDynamicView(
64
+ export function resolveGlobalRulesInDynamicView(
51
65
  view: DynamicView,
52
- globalStyles: Record<GlobalStyleID, GlobalStyle>
53
- ): LikeC4View {
54
- const resolvedRules = view.rules
55
- .flatMap(rule => {
56
- if (isViewRuleGlobalStyle(rule)) {
57
- const globalStyle = globalStyles[rule.styleId]
58
- if (globalStyle === undefined) {
59
- logger.warn(`Global style not found: ${rule.styleId}`)
60
- return []
61
- }
62
- return globalStyle.styles
63
- } else {
64
- return rule
66
+ globals: ModelGlobals
67
+ ): Array<Exclude<DynamicViewRule, ViewRuleGlobal>> {
68
+ return view.rules.reduce((acc, rule) => {
69
+ if (isViewRuleGlobalPredicateRef(rule)) {
70
+ const globalPredicates = globals.dynamicPredicates[rule.predicateId]
71
+ if (isNullish(globalPredicates)) {
72
+ logger.warn(`Global predicate not found: ${rule.predicateId}`)
73
+ return acc
65
74
  }
66
- })
67
-
68
- return {
69
- ...view,
70
- rules: resolvedRules
71
- }
75
+ return acc.concat(globalPredicates)
76
+ }
77
+ if (isViewRuleGlobalStyle(rule)) {
78
+ const globalStyles = globals.styles[rule.styleId]
79
+ if (isNullish(globalStyles)) {
80
+ logger.warn(`Global style not found: ${rule.styleId}`)
81
+ return acc
82
+ }
83
+ return acc.concat(globalStyles)
84
+ }
85
+ acc.push(rule)
86
+ return acc
87
+ }, [] as Array<Exclude<DynamicViewRule, ViewRuleGlobal>>)
72
88
  }