@likec4/language-server 1.6.1 → 1.7.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 (51) hide show
  1. package/contrib/likec4.tmLanguage.json +1 -1
  2. package/package.json +23 -19
  3. package/src/Rpc.ts +1 -1
  4. package/src/ast.ts +34 -9
  5. package/src/{browser/index.ts → browser.ts} +4 -1
  6. package/src/generated/ast.ts +498 -152
  7. package/src/generated/grammar.ts +2 -2
  8. package/src/generated/module.ts +1 -1
  9. package/src/index.ts +1 -1
  10. package/src/like-c4.langium +116 -44
  11. package/src/logger.ts +76 -55
  12. package/src/lsp/DocumentLinkProvider.ts +1 -1
  13. package/src/lsp/DocumentSymbolProvider.ts +1 -1
  14. package/src/lsp/HoverProvider.ts +1 -1
  15. package/src/lsp/SemanticTokenProvider.ts +54 -26
  16. package/src/model/model-builder.ts +11 -8
  17. package/src/model/model-locator.ts +12 -25
  18. package/src/model/model-parser-where.ts +75 -0
  19. package/src/model/model-parser.ts +168 -68
  20. package/src/model-change/ModelChanges.ts +2 -3
  21. package/src/model-change/changeElementStyle.ts +4 -1
  22. package/src/model-change/changeViewLayout.ts +8 -8
  23. package/src/model-change/saveManualLayout.ts +4 -6
  24. package/src/model-graph/LikeC4ModelGraph.ts +50 -48
  25. package/src/model-graph/compute-view/__test__/fixture.ts +41 -16
  26. package/src/model-graph/compute-view/compute.ts +135 -69
  27. package/src/model-graph/compute-view/predicates.ts +232 -136
  28. package/src/model-graph/dynamic-view/__test__/fixture.ts +5 -1
  29. package/src/model-graph/dynamic-view/compute.ts +50 -41
  30. package/src/model-graph/utils/applyCustomElementProperties.ts +31 -29
  31. package/src/model-graph/utils/applyCustomRelationProperties.ts +52 -15
  32. package/src/model-graph/utils/elementExpressionToPredicate.ts +8 -3
  33. package/src/module.ts +4 -18
  34. package/src/{node/index.ts → node.ts} +1 -1
  35. package/src/protocol.ts +2 -2
  36. package/src/shared/NodeKindProvider.ts +4 -2
  37. package/src/test/setup.ts +13 -0
  38. package/src/test/testServices.ts +1 -1
  39. package/src/validation/dynamic-view-rule.ts +12 -12
  40. package/src/validation/index.ts +6 -6
  41. package/src/validation/relation.ts +1 -1
  42. package/src/validation/view-predicates/{custom-element-expr.ts → element-with.ts} +11 -10
  43. package/src/validation/view-predicates/expanded-element.ts +2 -10
  44. package/src/validation/view-predicates/incoming.ts +1 -1
  45. package/src/validation/view-predicates/index.ts +2 -2
  46. package/src/validation/view-predicates/outgoing.ts +1 -1
  47. package/src/validation/view-predicates/{custom-relation-expr.ts → relation-with.ts} +2 -2
  48. package/src/validation/view.ts +8 -9
  49. package/src/view-utils/manual-layout.ts +65 -72
  50. package/src/view-utils/resolve-relative-paths.ts +28 -17
  51. package/src/view-utils/view-hash.ts +33 -0
@@ -1,31 +1,44 @@
1
- import type { ComputedDynamicView, ComputedEdge, DynamicView, EdgeId, Element, RelationID } from '@likec4/core'
1
+ import type {
2
+ ComputedDynamicView,
3
+ ComputedEdge,
4
+ DynamicView,
5
+ Element,
6
+ RelationID,
7
+ RelationshipArrowType,
8
+ RelationshipLineType,
9
+ ThemeColor
10
+ } from '@likec4/core'
2
11
  import {
3
12
  ancestorsFqn,
4
13
  commonAncestor,
5
14
  DefaultArrowType,
6
15
  DefaultLineStyle,
7
16
  DefaultRelationshipColor,
8
- invariant,
9
- isCustomElement,
10
17
  isDynamicViewIncludeRule,
11
- isElementRef,
12
- isExpandedElementExpr,
13
18
  isViewRuleAutoLayout,
14
19
  nonNullable,
15
20
  parentFqn,
16
21
  StepEdgeId
17
22
  } from '@likec4/core'
18
23
  import { isTruthy, unique } from 'remeda'
24
+ import { calcViewLayoutHash } from '../../view-utils/view-hash'
19
25
  import type { LikeC4ModelGraph } from '../LikeC4ModelGraph'
20
26
  import { applyCustomElementProperties } from '../utils/applyCustomElementProperties'
21
27
  import { applyViewRuleStyles } from '../utils/applyViewRuleStyles'
22
28
  import { buildComputeNodes } from '../utils/buildComputeNodes'
29
+ import { elementExprToPredicate } from '../utils/elementExpressionToPredicate'
23
30
 
24
31
  export namespace DynamicViewComputeCtx {
25
32
  export interface Step {
26
33
  source: Element
27
34
  target: Element
28
35
  title: string | null
36
+ description?: string
37
+ technology?: string
38
+ color?: ThemeColor
39
+ line?: RelationshipLineType
40
+ head?: RelationshipArrowType
41
+ tail?: RelationshipArrowType
29
42
  relations: RelationID[]
30
43
  isBackward: boolean
31
44
  }
@@ -46,17 +59,24 @@ export class DynamicViewComputeCtx {
46
59
  ) {}
47
60
 
48
61
  protected compute(): ComputedDynamicView {
49
- // reset ctx
50
- const { rules, steps, ...view } = this.view
51
-
52
- // const sources = new Set<Element>()
53
- // const stepsStack = new Set<string>()
54
-
55
- // const sourcesOf = new Map<Fqn, Set<Element>>()
56
-
57
- for (let step of steps) {
58
- const source = this.graph.element(step.source)
59
- const target = this.graph.element(step.target)
62
+ const {
63
+ docUri: _docUri, // exclude docUri
64
+ rules,
65
+ steps: viewSteps,
66
+ ...view
67
+ } = this.view
68
+
69
+ for (
70
+ let {
71
+ source: stepSource,
72
+ target: stepTarget,
73
+ title: stepTitle,
74
+ isBackward,
75
+ ...step
76
+ } of viewSteps
77
+ ) {
78
+ const source = this.graph.element(stepSource)
79
+ const target = this.graph.element(stepTarget)
60
80
 
61
81
  this.explicits.add(source)
62
82
  this.explicits.add(target)
@@ -64,30 +84,20 @@ export class DynamicViewComputeCtx {
64
84
  const { title, relations } = this.findRelations(source, target)
65
85
 
66
86
  this.steps.push({
87
+ ...step,
67
88
  source,
68
89
  target,
69
- title: step.title ?? title,
90
+ title: isTruthy(stepTitle) ? stepTitle : title,
70
91
  relations,
71
- isBackward: step.isBackward ?? false
92
+ isBackward: isBackward ?? false
72
93
  })
73
94
  }
74
95
 
75
96
  for (const rule of rules) {
76
97
  if (isDynamicViewIncludeRule(rule)) {
77
98
  for (const expr of rule.include) {
78
- if (isElementRef(expr)) {
79
- this.explicits.add(this.graph.element(expr.element))
80
- continue
81
- }
82
- if (isExpandedElementExpr(expr)) {
83
- this.explicits.add(this.graph.element(expr.expanded))
84
- continue
85
- }
86
- if (isCustomElement(expr)) {
87
- this.explicits.add(this.graph.element(expr.custom.element))
88
- continue
89
- }
90
- console.warn('Unsupported include expression: ', expr)
99
+ const predicate = elementExprToPredicate(expr)
100
+ this.graph.elements.filter(predicate).forEach(e => this.explicits.add(e))
91
101
  }
92
102
  }
93
103
  }
@@ -95,24 +105,23 @@ export class DynamicViewComputeCtx {
95
105
  const elements = [...this.explicits]
96
106
  const nodesMap = buildComputeNodes(elements)
97
107
 
98
- const edges = this.steps.map(({ source, target, relations, ...step }, index) => {
99
- const sourceNode = nodesMap.get(source.id)
100
- const targetNode = nodesMap.get(target.id)
101
- invariant(sourceNode, `Source node ${source.id} not found`)
102
- invariant(targetNode, `Target node ${target.id} not found`)
108
+ const edges = this.steps.map(({ source, target, relations, title, isBackward, ...step }, index) => {
109
+ const sourceNode = nonNullable(nodesMap.get(source.id), `Source node ${source.id} not found`)
110
+ const targetNode = nonNullable(nodesMap.get(target.id), `Target node ${target.id} not found`)
103
111
  const stepNum = index + 1
104
112
  const edge: ComputedEdge = {
105
113
  id: StepEdgeId(stepNum),
106
114
  parent: commonAncestor(source.id, target.id),
107
115
  source: source.id,
108
116
  target: target.id,
109
- label: step.title,
117
+ label: title,
110
118
  relations,
111
119
  color: DefaultRelationshipColor,
112
120
  line: DefaultLineStyle,
113
- head: DefaultArrowType
121
+ head: DefaultArrowType,
122
+ ...step
114
123
  }
115
- if (step.isBackward) {
124
+ if (isBackward) {
116
125
  edge.dir = 'back'
117
126
  }
118
127
 
@@ -149,12 +158,12 @@ export class DynamicViewComputeCtx {
149
158
 
150
159
  const autoLayoutRule = rules.findLast(isViewRuleAutoLayout)
151
160
 
152
- return {
161
+ return calcViewLayoutHash({
153
162
  ...view,
154
163
  autoLayout: autoLayoutRule?.autoLayout ?? 'LR',
155
164
  nodes,
156
165
  edges
157
- }
166
+ })
158
167
  }
159
168
 
160
169
  private findRelations(source: Element, target: Element): {
@@ -1,6 +1,7 @@
1
1
  import type { ComputedNode, ViewRule } from '@likec4/core'
2
2
  import { Expr, nonNullable } from '@likec4/core'
3
3
  import { isEmpty, isNonNullish, pickBy } from 'remeda'
4
+ import { elementExprToPredicate } from './elementExpressionToPredicate'
4
5
 
5
6
  export function applyCustomElementProperties(_rules: ViewRule[], _nodes: ComputedNode[]) {
6
7
  const rules = _rules.flatMap(r => ('include' in r ? r.include.filter(Expr.isCustomElement) : []))
@@ -10,42 +11,43 @@ export function applyCustomElementProperties(_rules: ViewRule[], _nodes: Compute
10
11
  const nodes = [..._nodes]
11
12
  for (
12
13
  const {
13
- custom: { element, ...props }
14
+ custom: { expr, ...props }
14
15
  } of rules
15
16
  ) {
16
- const nodeIdx = nodes.findIndex(n => n.id === element)
17
- if (nodeIdx === -1) {
18
- continue
19
- }
20
- let node = nonNullable(nodes[nodeIdx])
21
- const { border, opacity, ...rest } = pickBy(props, isNonNullish)
22
- if (!isEmpty(rest)) {
23
- node = {
24
- ...node,
25
- isCustomized: true,
26
- ...rest
17
+ const satisfies = elementExprToPredicate(expr)
18
+ nodes.forEach((node, i) => {
19
+ if (!satisfies(node)) {
20
+ return
27
21
  }
28
- }
29
22
 
30
- let styleOverride: ComputedNode['style'] | undefined
31
- if (border !== undefined) {
32
- styleOverride = { border }
33
- }
34
- if (opacity !== undefined) {
35
- styleOverride = { ...styleOverride, opacity }
36
- }
37
- if (styleOverride) {
38
- node = {
39
- ...node,
40
- isCustomized: true,
41
- style: {
42
- ...node.style,
43
- ...styleOverride
23
+ const { border, opacity, ...rest } = pickBy(props, isNonNullish)
24
+ if (!isEmpty(rest)) {
25
+ node = {
26
+ ...node,
27
+ isCustomized: true,
28
+ ...rest
44
29
  }
45
30
  }
46
- }
47
31
 
48
- nodes[nodeIdx] = node
32
+ let styleOverride: ComputedNode['style'] | undefined
33
+ if (border !== undefined) {
34
+ styleOverride = { border }
35
+ }
36
+ if (opacity !== undefined) {
37
+ styleOverride = { ...styleOverride, opacity }
38
+ }
39
+ if (styleOverride) {
40
+ node = {
41
+ ...node,
42
+ isCustomized: true,
43
+ style: {
44
+ ...node.style,
45
+ ...styleOverride
46
+ }
47
+ }
48
+ }
49
+ nodes[i] = node
50
+ })
49
51
  }
50
52
  return nodes
51
53
  }
@@ -1,8 +1,39 @@
1
- import type { ComputedEdge, ComputedNode, ViewRule } from '@likec4/core'
2
- import { Expr } from '@likec4/core'
1
+ import type { ComputedEdge, ComputedNode, Element, ViewRule } from '@likec4/core'
2
+ import { Expr, nonexhaustive } from '@likec4/core'
3
3
  import { isEmpty } from 'remeda'
4
4
  import { elementExprToPredicate } from './elementExpressionToPredicate'
5
5
 
6
+ function relationExpressionToPredicates(
7
+ expr: Expr.RelationExpression | Expr.RelationWhereExpr
8
+ ): (edge: { source: Element; target: Element }) => boolean {
9
+ switch (true) {
10
+ case Expr.isRelationWhere(expr):
11
+ return relationExpressionToPredicates(expr.where.expr)
12
+ case Expr.isRelation(expr): {
13
+ const isSource = elementExprToPredicate(expr.source)
14
+ const isTarget = elementExprToPredicate(expr.target)
15
+ return edge => {
16
+ return (isSource(edge.source) && isTarget(edge.target))
17
+ || (!!expr.isBidirectional && isSource(edge.target) && isTarget(edge.source))
18
+ }
19
+ }
20
+ case Expr.isInOut(expr): {
21
+ const isInOut = elementExprToPredicate(expr.inout)
22
+ return edge => isInOut(edge.source) || isInOut(edge.target)
23
+ }
24
+ case Expr.isIncoming(expr): {
25
+ const isTarget = elementExprToPredicate(expr.incoming)
26
+ return edge => isTarget(edge.target)
27
+ }
28
+ case Expr.isOutgoing(expr): {
29
+ const isSource = elementExprToPredicate(expr.outgoing)
30
+ return edge => isSource(edge.source)
31
+ }
32
+ default:
33
+ nonexhaustive(expr)
34
+ }
35
+ }
36
+
6
37
  export function applyCustomRelationProperties(
7
38
  _rules: ViewRule[],
8
39
  nodes: ComputedNode[],
@@ -10,7 +41,7 @@ export function applyCustomRelationProperties(
10
41
  ): ComputedEdge[] {
11
42
  const rules = _rules.flatMap(r => ('include' in r ? r.include.filter(Expr.isCustomRelationExpr) : []))
12
43
  const edges = Array.from(_edges)
13
- if (rules.length === 0) {
44
+ if (rules.length === 0 || edges.length === 0) {
14
45
  return edges
15
46
  }
16
47
  for (
@@ -21,22 +52,28 @@ export function applyCustomRelationProperties(
21
52
  if (isEmpty(props) && !title) {
22
53
  continue
23
54
  }
24
- const isSource = elementExprToPredicate(relation.source)
25
- const isTarget = elementExprToPredicate(relation.target)
26
- const satisfies = (edge: ComputedEdge) => {
55
+ const satisfies = relationExpressionToPredicates(relation)
56
+ // const isSource = elementExprToPredicate(relation.source)
57
+ // const isTarget = elementExprToPredicate(relation.target)
58
+ // const satisfies = (edge: ComputedEdge) => {
59
+ // const source = nodes.find(n => n.id === edge.source)
60
+ // const target = nodes.find(n => n.id === edge.target)
61
+ // if (!source || !target) {
62
+ // return false
63
+ // }
64
+ // let result = isSource(source) && isTarget(target)
65
+ // if (!result && relation.isBidirectional) {
66
+ // result = isSource(target) && isTarget(source)
67
+ // }
68
+ // return result
69
+ // }
70
+ edges.forEach((edge, i) => {
27
71
  const source = nodes.find(n => n.id === edge.source)
28
72
  const target = nodes.find(n => n.id === edge.target)
29
73
  if (!source || !target) {
30
- return false
74
+ return
31
75
  }
32
- let result = isSource(source) && isTarget(target)
33
- if (!result && relation.isBidirectional) {
34
- result = isSource(target) && isTarget(source)
35
- }
36
- return result
37
- }
38
- edges.forEach((edge, i) => {
39
- if (satisfies(edge)) {
76
+ if (satisfies({ source, target })) {
40
77
  edges[i] = {
41
78
  ...edge,
42
79
  label: title ?? edge.label,
@@ -1,10 +1,15 @@
1
- import type { ComputedNode } from '@likec4/core'
2
1
  import { Expr, nonexhaustive, parentFqn } from '@likec4/core'
2
+ import { type Element, whereOperatorAsPredicate } from '@likec4/core/types'
3
3
  import { isNullish } from 'remeda'
4
4
 
5
5
  type Predicate<T> = (x: T) => boolean
6
6
 
7
- export function elementExprToPredicate(target: Expr.ElementPredicateExpression): Predicate<ComputedNode> {
7
+ export function elementExprToPredicate(target: Expr.ElementPredicateExpression): Predicate<Element> {
8
+ if (Expr.isElementWhere(target)) {
9
+ const predicate = elementExprToPredicate(target.where.expr)
10
+ const where = whereOperatorAsPredicate(target.where.condition)
11
+ return n => predicate(n) && where(n)
12
+ }
8
13
  if (Expr.isWildcard(target)) {
9
14
  return () => true
10
15
  }
@@ -26,7 +31,7 @@ export function elementExprToPredicate(target: Expr.ElementPredicateExpression):
26
31
  : n => (n.id as string) === element
27
32
  }
28
33
  if (Expr.isCustomElement(target)) {
29
- return n => (n.id as string) === target.custom.element
34
+ return elementExprToPredicate(target.custom.expr)
30
35
  }
31
36
  nonexhaustive(target)
32
37
  }
package/src/module.ts CHANGED
@@ -1,4 +1,3 @@
1
- import { normalizeError } from '@likec4/core'
2
1
  import { EmptyFileSystem, inject, type Module, WorkspaceCache } from 'langium'
3
2
  import {
4
3
  createDefaultModule,
@@ -10,7 +9,7 @@ import {
10
9
  type PartialLangiumSharedServices
11
10
  } from 'langium/lsp'
12
11
  import { LikeC4GeneratedModule, LikeC4GeneratedSharedModule } from './generated/module'
13
- import { logger } from './logger'
12
+ import { logErrorToTelemetry } from './logger'
14
13
  import {
15
14
  LikeC4CodeLensProvider,
16
15
  LikeC4CompletionProvider,
@@ -142,26 +141,13 @@ export function createCustomLanguageServices<I1, I2, I3, I extends I1 & I2 & I3
142
141
  }
143
142
 
144
143
  export function createSharedServices(context: LanguageServicesContext = {}): LikeC4SharedServices {
145
- const connection = context.connection
146
- if (connection) {
147
- // eslint-disable-next-line @typescript-eslint/unbound-method
148
- const original = logger.error.bind(logger)
149
- logger.error = (arg: unknown) => {
150
- if (typeof arg === 'string') {
151
- original(arg)
152
- connection.telemetry.logEvent({ eventName: 'error', error: arg })
153
- return
154
- }
155
- const error = normalizeError(arg)
156
- original(error)
157
- connection.telemetry.logEvent({ eventName: 'error', error: error.stack ?? error.message })
158
- }
159
- }
160
-
161
144
  const moduleContext: DefaultSharedModuleContext = {
162
145
  ...EmptyFileSystem,
163
146
  ...context
164
147
  }
148
+ if (context.connection) {
149
+ logErrorToTelemetry(context.connection)
150
+ }
165
151
 
166
152
  return inject(
167
153
  createDefaultSharedModule(moduleContext),
@@ -1,7 +1,7 @@
1
1
  import { startLanguageServer as startLanguim } from 'langium/lsp'
2
2
  import { NodeFileSystem } from 'langium/node'
3
3
  import { createConnection, ProposedFeatures } from 'vscode-languageserver/node'
4
- import { createLanguageServices } from '../module'
4
+ import { createLanguageServices } from './module'
5
5
 
6
6
  export function startLanguageServer() {
7
7
  /* browser specific setup code */
package/src/protocol.ts CHANGED
@@ -8,8 +8,8 @@ import type {
8
8
  ViewChangeOp,
9
9
  ViewID
10
10
  } from '@likec4/core'
11
- import type { DocumentUri, Location } from 'vscode-languageserver-protocol'
12
- import { NotificationType, RequestType, RequestType0 } from 'vscode-languageserver-protocol'
11
+ import { NotificationType, RequestType, RequestType0 } from 'vscode-jsonrpc'
12
+ import type { DocumentUri, Location } from 'vscode-languageserver-types'
13
13
 
14
14
  // #region From server
15
15
  export const onDidChangeModel = new NotificationType<string>('likec4/onDidChangeModel')
@@ -1,6 +1,6 @@
1
1
  import { type AstNode, type AstNodeDescription } from 'langium'
2
2
  import type { LangiumSharedServices, NodeKindProvider as LspNodeKindProvider } from 'langium/lsp'
3
- import { CompletionItemKind, SymbolKind } from 'vscode-languageserver-protocol'
3
+ import { CompletionItemKind, SymbolKind } from 'vscode-languageserver-types'
4
4
  import { ast } from '../ast'
5
5
 
6
6
  export class NodeKindProvider implements LspNodeKindProvider {
@@ -38,7 +38,7 @@ export class NodeKindProvider implements LspNodeKindProvider {
38
38
  return SymbolKind.TypeParameter
39
39
  }
40
40
  }
41
- return SymbolKind.Constant
41
+ return SymbolKind.Field
42
42
  }
43
43
  /**
44
44
  * Returns a `CompletionItemKind` as used by the `CompletionProvider`.
@@ -61,6 +61,8 @@ export class NodeKindProvider implements LspNodeKindProvider {
61
61
  return CompletionItemKind.Interface
62
62
  case SymbolKind.Event:
63
63
  return CompletionItemKind.Event
64
+ case SymbolKind.Constant:
65
+ return CompletionItemKind.Constant
64
66
  default:
65
67
  return CompletionItemKind.Reference
66
68
  }
@@ -0,0 +1,13 @@
1
+ import { beforeAll, beforeEach, vi } from 'vitest'
2
+ import { logger } from '../logger'
3
+
4
+ beforeAll(() => {
5
+ // Redirect std and console to consola too
6
+ // Calling this once is sufficient
7
+ logger.wrapAll()
8
+ })
9
+
10
+ beforeEach(() => {
11
+ // Vitest
12
+ logger.mockTypes(() => vi.fn())
13
+ })
@@ -1,7 +1,7 @@
1
1
  import { DocumentState, EmptyFileSystem } from 'langium'
2
2
  import * as assert from 'node:assert'
3
3
  import stripIndent from 'strip-indent'
4
- import { type Diagnostic, DiagnosticSeverity } from 'vscode-languageserver-protocol'
4
+ import { type Diagnostic, DiagnosticSeverity } from 'vscode-languageserver-types'
5
5
  import { URI, Utils } from 'vscode-uri'
6
6
  import type { LikeC4LangiumDocument } from '../ast'
7
7
  import { createLanguageServices } from '../module'
@@ -1,28 +1,28 @@
1
1
  import { nonexhaustive } from '@likec4/core'
2
2
  import type { ValidationCheck } from 'langium'
3
- import { ast } from '../ast'
3
+ import { ast, elementExpressionFromPredicate } from '../ast'
4
4
  import type { LikeC4Services } from '../module'
5
5
 
6
6
  export const dynamicViewRulePredicate = (
7
7
  _services: LikeC4Services
8
- ): ValidationCheck<ast.DynamicViewRulePredicateIterator> => {
9
- return (expr, accept) => {
8
+ ): ValidationCheck<ast.DynamicViewPredicateIterator> => {
9
+ return (predicate, accept) => {
10
+ const expr = elementExpressionFromPredicate(predicate.value)
10
11
  switch (true) {
11
- case ast.isElementRef(expr.value):
12
- case ast.isElementDescedantsExpression(expr.value):
13
- case ast.isCustomElementExpression(expr.value):
14
- case ast.isExpandElementExpression(expr.value):
12
+ case ast.isElementRef(expr):
13
+ case ast.isElementDescedantsExpression(expr):
14
+ case ast.isExpandElementExpression(expr):
15
15
  return
16
- case ast.isElementKindExpression(expr.value):
17
- case ast.isElementTagExpression(expr.value):
18
- case ast.isWildcardExpression(expr.value): {
16
+ case ast.isElementKindExpression(expr):
17
+ case ast.isElementTagExpression(expr):
18
+ case ast.isWildcardExpression(expr): {
19
19
  accept('warning', `Predicate is ignored, as not supported in dynamic views`, {
20
- node: expr
20
+ node: predicate
21
21
  })
22
22
  return
23
23
  }
24
24
  default:
25
- nonexhaustive(expr.value)
25
+ nonexhaustive(expr)
26
26
  }
27
27
  }
28
28
  }
@@ -16,11 +16,11 @@ import {
16
16
  } from './specification'
17
17
  import { viewChecks } from './view'
18
18
  import {
19
- customElementExprChecks,
20
- customRelationExprChecks,
19
+ elementPredicateWithChecks,
21
20
  expandElementExprChecks,
22
21
  incomingExpressionChecks,
23
- outgoingExpressionChecks
22
+ outgoingExpressionChecks,
23
+ relationPredicateWithChecks
24
24
  } from './view-predicates'
25
25
 
26
26
  export function registerValidationChecks(services: LikeC4Services) {
@@ -38,9 +38,9 @@ export function registerValidationChecks(services: LikeC4Services) {
38
38
  ElementKind: elementKindChecks(services),
39
39
  Relation: relationChecks(services),
40
40
  Tag: tagChecks(services),
41
- DynamicViewRulePredicateIterator: dynamicViewRulePredicate(services),
42
- CustomElementExpression: customElementExprChecks(services),
43
- CustomRelationExpression: customRelationExprChecks(services),
41
+ DynamicViewPredicateIterator: dynamicViewRulePredicate(services),
42
+ ElementPredicateWith: elementPredicateWithChecks(services),
43
+ RelationPredicateWith: relationPredicateWithChecks(services),
44
44
  ExpandElementExpression: expandElementExprChecks(services),
45
45
  RelationshipKind: relationshipChecks(services),
46
46
  IncomingRelationExpression: incomingExpressionChecks(services),
@@ -44,7 +44,7 @@ export const relationChecks = (services: LikeC4Services): ValidationCheck<ast.Re
44
44
  })
45
45
  }
46
46
 
47
- if (el.tags?.value && el.body?.tags?.value) {
47
+ if (el.tags?.values && el.body?.tags?.values) {
48
48
  accept('error', 'Relation cannot have tags in both header and body', {
49
49
  node: el,
50
50
  property: 'tags'
@@ -4,9 +4,9 @@ import { AstUtils } from 'langium'
4
4
  import { ast } from '../../ast'
5
5
  import type { LikeC4Services } from '../../module'
6
6
 
7
- export const customElementExprChecks = (
7
+ export const elementPredicateWithChecks = (
8
8
  _services: LikeC4Services
9
- ): ValidationCheck<ast.CustomElementExpression> => {
9
+ ): ValidationCheck<ast.ElementPredicateWith> => {
10
10
  return (el, accept) => {
11
11
  const container = AstUtils.getContainerOfType(el, ast.isViewRulePredicate)
12
12
  if (ast.isExcludePredicate(container)) {
@@ -14,21 +14,22 @@ export const customElementExprChecks = (
14
14
  node: el
15
15
  })
16
16
  }
17
+ const subject = ast.isElementPredicateWhere(el.subject) ? el.subject.subject : el.subject
17
18
  switch (true) {
18
- case ast.isElementRef(el.target):
19
- case ast.isElementDescedantsExpression(el.target):
20
- case ast.isExpandElementExpression(el.target):
19
+ case ast.isElementRef(subject):
20
+ case ast.isElementDescedantsExpression(subject):
21
+ case ast.isExpandElementExpression(subject):
22
+ case ast.isWildcardExpression(subject):
21
23
  return
22
- case ast.isElementKindExpression(el.target):
23
- case ast.isElementTagExpression(el.target):
24
- case ast.isWildcardExpression(el.target):
24
+ case ast.isElementKindExpression(subject):
25
+ case ast.isElementTagExpression(subject):
25
26
  accept('error', 'Invalid target (expect reference to specific element)', {
26
27
  node: el,
27
- property: 'target'
28
+ property: 'subject'
28
29
  })
29
30
  return
30
31
  default:
31
- nonexhaustive(el.target)
32
+ nonexhaustive(subject)
32
33
  }
33
34
  }
34
35
  }
@@ -1,5 +1,4 @@
1
- import { nonexhaustive } from '@likec4/core'
2
- import { type AstNode, AstUtils, type ValidationCheck } from 'langium'
1
+ import { AstUtils, type ValidationCheck } from 'langium'
3
2
  import { ast } from '../../ast'
4
3
  import type { LikeC4Services } from '../../module'
5
4
 
@@ -7,17 +6,10 @@ export const expandElementExprChecks = (
7
6
  _services: LikeC4Services
8
7
  ): ValidationCheck<ast.ExpandElementExpression> => {
9
8
  return (el, accept) => {
10
- const isInside = <T extends AstNode>(typePredicate: (n: AstNode) => n is T): boolean =>
11
- !!AstUtils.getContainerOfType(el, typePredicate)
12
- if (isInside(ast.isRelationExpression)) {
9
+ if (AstUtils.hasContainerOfType(el, ast.isRelationExpression)) {
13
10
  accept('warning', `Redundant usage, expand predicate resolves parent element only when used in relations`, {
14
11
  node: el
15
12
  })
16
13
  }
17
- if (isInside(ast.isExcludePredicate)) {
18
- accept('warning', `Expand predicate is ignored in exclude`, {
19
- node: el
20
- })
21
- }
22
14
  }
23
15
  }
@@ -7,7 +7,7 @@ export const incomingExpressionChecks = (
7
7
  _services: LikeC4Services
8
8
  ): ValidationCheck<ast.IncomingRelationExpression> => {
9
9
  return (el, accept) => {
10
- if (ast.isWildcardExpression(el.to) && ast.isExpressions(el.$container)) {
10
+ if (ast.isWildcardExpression(el.to) && !ast.isInOutRelationExpression(el.$container)) {
11
11
  const view = AstUtils.getContainerOfType(el, ast.isElementView)
12
12
  if (isNullish(view?.viewOf)) {
13
13
  accept('warning', 'Predicate is ignored as it concerns all relationships', {
@@ -1,5 +1,5 @@
1
- export * from './custom-element-expr'
2
- export * from './custom-relation-expr'
1
+ export * from './element-with'
3
2
  export * from './expanded-element'
4
3
  export * from './incoming'
5
4
  export * from './outgoing'
5
+ export * from './relation-with'
@@ -7,7 +7,7 @@ export const outgoingExpressionChecks = (
7
7
  _services: LikeC4Services
8
8
  ): ValidationCheck<ast.OutgoingRelationExpression> => {
9
9
  return (el, accept) => {
10
- if (ast.isWildcardExpression(el.from) && ast.isExpressions(el.$container)) {
10
+ if (ast.isWildcardExpression(el.from) && !ast.isDirectedRelationExpression(el.$container)) {
11
11
  const view = AstUtils.getContainerOfType(el, ast.isElementView)
12
12
  if (isNullish(view?.viewOf)) {
13
13
  accept('warning', 'Predicate is ignored as it concerns all relationships', {