@likec4/language-server 1.4.0 → 1.6.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 (45) hide show
  1. package/README.md +1 -1
  2. package/contrib/likec4.tmLanguage.json +1 -1
  3. package/package.json +26 -14
  4. package/src/Rpc.ts +25 -2
  5. package/src/ast.ts +19 -15
  6. package/src/generated/ast.ts +390 -203
  7. package/src/generated/grammar.ts +1 -1
  8. package/src/generated-lib/icons.ts +952 -0
  9. package/src/like-c4.langium +120 -64
  10. package/src/likec4lib.ts +7 -0
  11. package/src/lsp/DocumentSymbolProvider.ts +28 -1
  12. package/src/lsp/SemanticTokenProvider.ts +41 -22
  13. package/src/model/fqn-computation.ts +29 -7
  14. package/src/model/fqn-index.ts +18 -31
  15. package/src/model/model-builder.ts +13 -9
  16. package/src/model/model-locator.ts +7 -7
  17. package/src/model/model-parser.ts +166 -69
  18. package/src/model-change/changeElementStyle.ts +6 -6
  19. package/src/model-graph/compute-view/__test__/fixture.ts +52 -24
  20. package/src/model-graph/compute-view/compute.ts +51 -20
  21. package/src/model-graph/compute-view/predicates.ts +6 -1
  22. package/src/model-graph/dynamic-view/__test__/fixture.ts +2 -2
  23. package/src/model-graph/dynamic-view/compute.ts +2 -2
  24. package/src/model-graph/utils/{applyElementCustomProperties.ts → applyCustomElementProperties.ts} +5 -3
  25. package/src/model-graph/utils/applyCustomRelationProperties.ts +50 -0
  26. package/src/model-graph/utils/applyViewRuleStyles.ts +11 -34
  27. package/src/model-graph/utils/elementExpressionToPredicate.ts +32 -0
  28. package/src/references/scope-computation.ts +113 -60
  29. package/src/references/scope-provider.ts +3 -23
  30. package/src/shared/NodeKindProvider.ts +1 -0
  31. package/src/shared/WorkspaceManager.ts +15 -6
  32. package/src/validation/dynamic-view-rule.ts +19 -26
  33. package/src/validation/element.ts +8 -4
  34. package/src/validation/index.ts +9 -6
  35. package/src/validation/property-checks.ts +23 -1
  36. package/src/validation/view-predicates/custom-element-expr.ts +21 -8
  37. package/src/validation/view-predicates/custom-relation-expr.ts +16 -0
  38. package/src/validation/view-predicates/expanded-element.ts +13 -24
  39. package/src/validation/view-predicates/incoming.ts +5 -5
  40. package/src/validation/view-predicates/index.ts +1 -0
  41. package/src/validation/view-predicates/outgoing.ts +5 -5
  42. package/src/view-utils/assignNavigateTo.ts +2 -2
  43. package/src/view-utils/manual-layout.ts +4 -2
  44. package/src/view-utils/resolve-extended-views.ts +2 -2
  45. package/src/view-utils/resolve-relative-paths.ts +3 -3
@@ -6,7 +6,7 @@ import {
6
6
  MultiMap,
7
7
  type PrecomputedScopes
8
8
  } from 'langium'
9
- import { isTruthy } from 'remeda'
9
+ import { isNullish, isTruthy } from 'remeda'
10
10
  import type { CancellationToken } from 'vscode-languageserver'
11
11
  import { ast, type LikeC4LangiumDocument } from '../ast'
12
12
  import { logError } from '../logger'
@@ -20,77 +20,130 @@ export class LikeC4ScopeComputation extends DefaultScopeComputation {
20
20
  ): Promise<AstNodeDescription[]> {
21
21
  const docExports: AstNodeDescription[] = []
22
22
  try {
23
- const { specifications, models, views } = document.parseResult.value
23
+ const { specifications, models, views, likec4lib } = document.parseResult.value
24
+
25
+ // Process library
26
+ this.exportLibrary(likec4lib, docExports, document)
24
27
 
25
28
  // Process specification
26
- for (
27
- const spec of specifications.flatMap(s => [
28
- ...s.elements,
29
- ...s.relationships,
30
- ...s.tags
31
- ])
32
- ) {
33
- try {
34
- switch (true) {
35
- case ast.isSpecificationElementKind(spec): {
36
- if (isTruthy(spec.kind.name)) {
37
- docExports.push(
38
- this.descriptions.createDescription(spec.kind, spec.kind.name, document)
39
- )
40
- }
41
- continue
42
- }
43
- case ast.isSpecificationTag(spec): {
44
- if (isTruthy(spec.tag.name)) {
45
- docExports.push(
46
- this.descriptions.createDescription(spec.tag, '#' + spec.tag.name, document)
47
- )
48
- }
49
- continue
50
- }
51
- case ast.isSpecificationRelationshipKind(spec): {
52
- if (isTruthy(spec.kind.name)) {
53
- docExports.push(
54
- this.descriptions.createDescription(spec.kind, spec.kind.name, document),
55
- this.descriptions.createDescription(spec.kind, '.' + spec.kind.name, document)
56
- )
57
- }
58
- continue
59
- }
60
- // Thow error if not exhaustive
61
- default:
62
- nonexhaustive(spec)
63
- }
64
- } catch (e) {
65
- logError(e)
29
+ this.exportSpecification(specifications, docExports, document)
30
+
31
+ // Process models
32
+ this.exportModel(models, docExports, document)
33
+
34
+ // Process views
35
+ this.exportViews(views, docExports, document)
36
+ } catch (e) {
37
+ logError(e)
38
+ }
39
+ return docExports
40
+ }
41
+
42
+ private exportViews(
43
+ views: ast.ModelViews[] | undefined,
44
+ docExports: AstNodeDescription[],
45
+ document: LikeC4LangiumDocument
46
+ ) {
47
+ if (isNullish(views) || views.length === 0) {
48
+ return
49
+ }
50
+ for (const viewAst of views.flatMap(v => v.views)) {
51
+ try {
52
+ if (isTruthy(viewAst.name)) {
53
+ docExports.push(this.descriptions.createDescription(viewAst, viewAst.name, document))
66
54
  }
55
+ } catch (e) {
56
+ logError(e)
67
57
  }
58
+ }
59
+ }
68
60
 
69
- // Process models
70
- for (const elAst of models.flatMap(m => m.elements)) {
71
- try {
72
- if (ast.isElement(elAst) && isTruthy(elAst.name)) {
73
- docExports.push(this.descriptions.createDescription(elAst, elAst.name, document))
74
- }
75
- } catch (e) {
76
- logError(e)
61
+ private exportModel(
62
+ models: ast.Model[] | undefined,
63
+ docExports: AstNodeDescription[],
64
+ document: LikeC4LangiumDocument
65
+ ) {
66
+ if (isNullish(models) || models.length === 0) {
67
+ return
68
+ }
69
+ for (const elAst of models.flatMap(m => m.elements)) {
70
+ try {
71
+ if (ast.isElement(elAst) && isTruthy(elAst.name)) {
72
+ docExports.push(this.descriptions.createDescription(elAst, elAst.name, document))
77
73
  }
74
+ } catch (e) {
75
+ logError(e)
78
76
  }
77
+ }
78
+ }
79
79
 
80
- // Process views
81
- for (const viewAst of views.flatMap(v => v.views)) {
82
- try {
83
- if (isTruthy(viewAst.name)) {
84
- docExports.push(this.descriptions.createDescription(viewAst, viewAst.name, document))
80
+ private exportLibrary(
81
+ likec4lib: ast.LikeC4Lib[] | undefined,
82
+ docExports: AstNodeDescription[],
83
+ document: LikeC4LangiumDocument
84
+ ) {
85
+ if (isNullish(likec4lib) || likec4lib.length === 0) {
86
+ return
87
+ }
88
+ for (const iconAst of likec4lib.flatMap(l => l.icons)) {
89
+ try {
90
+ docExports.push(this.descriptions.createDescription(iconAst, iconAst.name, document))
91
+ } catch (e) {
92
+ logError(e)
93
+ }
94
+ }
95
+ }
96
+
97
+ private exportSpecification(
98
+ specifications: ast.SpecificationRule[] | undefined,
99
+ docExports: AstNodeDescription[],
100
+ document: LikeC4LangiumDocument
101
+ ) {
102
+ if (isNullish(specifications) || specifications.length === 0) {
103
+ return
104
+ }
105
+ for (
106
+ const spec of specifications.flatMap(s => [
107
+ ...s.elements,
108
+ ...s.relationships,
109
+ ...s.tags
110
+ ])
111
+ ) {
112
+ try {
113
+ switch (true) {
114
+ case ast.isSpecificationElementKind(spec): {
115
+ if (isTruthy(spec.kind.name)) {
116
+ docExports.push(
117
+ this.descriptions.createDescription(spec.kind, spec.kind.name, document)
118
+ )
119
+ }
120
+ continue
85
121
  }
86
- } catch (e) {
87
- logError(e)
122
+ case ast.isSpecificationTag(spec): {
123
+ if (isTruthy(spec.tag.name)) {
124
+ docExports.push(
125
+ this.descriptions.createDescription(spec.tag, '#' + spec.tag.name, document)
126
+ )
127
+ }
128
+ continue
129
+ }
130
+ case ast.isSpecificationRelationshipKind(spec): {
131
+ if (isTruthy(spec.kind.name)) {
132
+ docExports.push(
133
+ this.descriptions.createDescription(spec.kind, spec.kind.name, document),
134
+ this.descriptions.createDescription(spec.kind, '.' + spec.kind.name, document)
135
+ )
136
+ }
137
+ continue
138
+ }
139
+ // Thow error if not exhaustive
140
+ default:
141
+ nonexhaustive(spec)
88
142
  }
143
+ } catch (e) {
144
+ logError(e)
89
145
  }
90
- } catch (e) {
91
- logError(e)
92
146
  }
93
- return docExports
94
147
  }
95
148
 
96
149
  override computeLocalScopes(
@@ -3,12 +3,10 @@ import type { AstNode } from 'langium'
3
3
  import {
4
4
  type AstNodeDescription,
5
5
  AstUtils,
6
- CstUtils,
7
6
  DefaultScopeProvider,
8
7
  DONE_RESULT,
9
8
  EMPTY_SCOPE,
10
9
  EMPTY_STREAM,
11
- GrammarUtils,
12
10
  type ReferenceInfo,
13
11
  type Scope,
14
12
  type Stream,
@@ -19,29 +17,11 @@ import {
19
17
  import { ast } from '../ast'
20
18
  import { elementRef, getFqnElementRef } from '../elementRef'
21
19
  import { logger } from '../logger'
22
- import type { FqnIndex, FqnIndexEntry } from '../model/fqn-index'
20
+ import type { FqnIndex } from '../model/fqn-index'
23
21
  import type { LikeC4Services } from '../module'
24
22
 
25
- const { findNodeForProperty } = GrammarUtils
26
- const { toDocumentSegment } = CstUtils
27
23
  const { getDocument } = AstUtils
28
24
 
29
- function toAstNodeDescription(entry: FqnIndexEntry): AstNodeDescription {
30
- const $cstNode = findNodeForProperty(entry.el.$cstNode, 'name')
31
- return {
32
- documentUri: entry.doc.uri,
33
- name: entry.name,
34
- ...(entry.el.$cstNode && {
35
- selectionSegment: toDocumentSegment(entry.el.$cstNode)
36
- }),
37
- ...($cstNode && {
38
- nameSegment: toDocumentSegment($cstNode)
39
- }),
40
- path: entry.path,
41
- type: ast.Element
42
- }
43
- }
44
-
45
25
  export class LikeC4ScopeProvider extends DefaultScopeProvider {
46
26
  private fqnIndex: FqnIndex
47
27
 
@@ -51,7 +31,7 @@ export class LikeC4ScopeProvider extends DefaultScopeProvider {
51
31
  }
52
32
 
53
33
  private directChildrenOf(parent: c4.Fqn): Stream<AstNodeDescription> {
54
- return this.fqnIndex.directChildrenOf(parent).map(toAstNodeDescription)
34
+ return this.fqnIndex.directChildrenOf(parent)
55
35
  }
56
36
 
57
37
  // we need lazy resolving here
@@ -61,7 +41,7 @@ export class LikeC4ScopeProvider extends DefaultScopeProvider {
61
41
  const element = of()
62
42
  const fqn = element && this.fqnIndex.getFqn(element)
63
43
  if (fqn) {
64
- return this.fqnIndex.uniqueDescedants(fqn).map(toAstNodeDescription).iterator()
44
+ return this.fqnIndex.uniqueDescedants(fqn).iterator()
65
45
  }
66
46
  return null
67
47
  },
@@ -25,6 +25,7 @@ export class NodeKindProvider implements LspNodeKindProvider {
25
25
  return SymbolKind.Class
26
26
  }
27
27
  case (ast.isTag(node) || hasType(ast.Tag))
28
+ || (ast.isLibIcon(node) || hasType(ast.LibIcon))
28
29
  || (ast.isSpecificationTag(node) || hasType(ast.SpecificationTag)): {
29
30
  return SymbolKind.EnumMember
30
31
  }
@@ -1,21 +1,30 @@
1
1
  import { hasAtLeast, invariant } from '@likec4/core'
2
- import type { LangiumDocument } from 'langium'
2
+ import type { LangiumDocument, LangiumDocumentFactory } from 'langium'
3
3
  import { DefaultWorkspaceManager } from 'langium'
4
+ import type { LangiumSharedServices } from 'langium/lsp'
4
5
  import type { WorkspaceFolder } from 'vscode-languageserver'
5
6
  import { URI } from 'vscode-uri'
7
+ import * as BuiltIn from '../likec4lib'
6
8
 
7
9
  export class LikeC4WorkspaceManager extends DefaultWorkspaceManager {
10
+ private documentFactory: LangiumDocumentFactory
11
+
12
+ constructor(services: LangiumSharedServices) {
13
+ super(services)
14
+ this.documentFactory = services.workspace.LangiumDocumentFactory
15
+ }
16
+
8
17
  /**
9
18
  * Load all additional documents that shall be visible in the context of the given workspace
10
19
  * folders and add them to the collector. This can be used to include built-in libraries of
11
20
  * your language, which can be either loaded from provided files or constructed in memory.
12
21
  */
13
- protected override loadAdditionalDocuments(
14
- _folders: WorkspaceFolder[],
15
- _collector: (document: LangiumDocument) => void
22
+ protected override async loadAdditionalDocuments(
23
+ folders: WorkspaceFolder[],
24
+ collector: (document: LangiumDocument) => void
16
25
  ): Promise<void> {
17
- // collector(this.documentFactory.fromString(builtin.specification.document, URI.parse(builtin.specification.uri)))
18
- return Promise.resolve()
26
+ await super.loadAdditionalDocuments(folders, collector)
27
+ collector(this.documentFactory.fromString(BuiltIn.Content, URI.parse(BuiltIn.Uri)))
19
28
  }
20
29
 
21
30
  public workspace() {
@@ -1,35 +1,28 @@
1
1
  import { nonexhaustive } from '@likec4/core'
2
2
  import type { ValidationCheck } from 'langium'
3
3
  import { ast } from '../ast'
4
- import { logError } from '../logger'
5
4
  import type { LikeC4Services } from '../module'
6
5
 
7
- export const dynamicViewRulePredicate = (_services: LikeC4Services): ValidationCheck<ast.DynamicViewRulePredicate> => {
8
- return (el, accept) => {
9
- try {
10
- for (const expr of el.expressions) {
11
- switch (true) {
12
- case ast.isElementRef(expr):
13
- case ast.isDescedantsExpr(expr):
14
- case ast.isCustomElementExpr(expr):
15
- case ast.isExpandElementExpr(expr):
16
- return
17
- case ast.isRelationExpr(expr):
18
- case ast.isInOutExpr(expr):
19
- case ast.isIncomingExpr(expr):
20
- case ast.isOutgoingExpr(expr):
21
- case ast.isElementKindExpr(expr):
22
- case ast.isElementTagExpr(expr):
23
- case ast.isWildcardExpr(expr):
24
- return accept('warning', `Expression is not supported by dynamic views`, {
25
- node: expr
26
- })
27
- default:
28
- nonexhaustive(expr)
29
- }
6
+ export const dynamicViewRulePredicate = (
7
+ _services: LikeC4Services
8
+ ): ValidationCheck<ast.DynamicViewRulePredicateIterator> => {
9
+ return (expr, accept) => {
10
+ 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):
15
+ return
16
+ case ast.isElementKindExpression(expr.value):
17
+ case ast.isElementTagExpression(expr.value):
18
+ case ast.isWildcardExpression(expr.value): {
19
+ accept('warning', `Predicate is ignored, as not supported in dynamic views`, {
20
+ node: expr
21
+ })
22
+ return
30
23
  }
31
- } catch (e) {
32
- logError(e)
24
+ default:
25
+ nonexhaustive(expr.value)
33
26
  }
34
27
  }
35
28
  }
@@ -6,6 +6,7 @@ const { getDocument } = AstUtils
6
6
 
7
7
  export const elementChecks = (services: LikeC4Services): ValidationCheck<ast.Element> => {
8
8
  const fqnIndex = services.likec4.FqnIndex
9
+ const locator = services.workspace.AstNodeLocator
9
10
  return (el, accept) => {
10
11
  const fqn = fqnIndex.getFqn(el)
11
12
  if (!fqn) {
@@ -15,12 +16,15 @@ export const elementChecks = (services: LikeC4Services): ValidationCheck<ast.Ele
15
16
  })
16
17
  return
17
18
  }
19
+ const doc = getDocument(el)
20
+ const docUri = doc.uri
21
+ const elPath = locator.getAstNodePath(el)
18
22
  const withSameFqn = fqnIndex
19
23
  .byFqn(fqn)
20
- .filter(v => v.el !== el)
24
+ .filter(v => v.documentUri !== docUri || v.path !== elPath)
21
25
  .head()
22
26
  if (withSameFqn) {
23
- const isAnotherDoc = withSameFqn.doc.uri !== getDocument(el).uri
27
+ const isAnotherDoc = withSameFqn.documentUri !== docUri
24
28
  accept(
25
29
  'error',
26
30
  `Duplicate element name ${el.name !== fqn ? el.name + ' (' + fqn + ')' : el.name}`,
@@ -31,8 +35,8 @@ export const elementChecks = (services: LikeC4Services): ValidationCheck<ast.Ele
31
35
  relatedInformation: [
32
36
  {
33
37
  location: {
34
- range: withSameFqn.el.$cstNode!.range,
35
- uri: withSameFqn.doc.uri.toString()
38
+ range: (withSameFqn.nameSegment?.range ?? withSameFqn.selectionSegment?.range)!,
39
+ uri: withSameFqn.documentUri.toString()
36
40
  },
37
41
  message: `conflicting element`
38
42
  }
@@ -4,7 +4,7 @@ import type { LikeC4Services } from '../module'
4
4
  import { dynamicViewRulePredicate } from './dynamic-view-rule'
5
5
  import { dynamicViewStep } from './dynamic-view-step'
6
6
  import { elementChecks } from './element'
7
- import { opacityPropertyRuleChecks } from './property-checks'
7
+ import { iconPropertyRuleChecks, opacityPropertyRuleChecks } from './property-checks'
8
8
  import { relationChecks } from './relation'
9
9
  import {
10
10
  elementKindChecks,
@@ -17,6 +17,7 @@ import {
17
17
  import { viewChecks } from './view'
18
18
  import {
19
19
  customElementExprChecks,
20
+ customRelationExprChecks,
20
21
  expandElementExprChecks,
21
22
  incomingExpressionChecks,
22
23
  outgoingExpressionChecks
@@ -27,6 +28,7 @@ export function registerValidationChecks(services: LikeC4Services) {
27
28
  const registry = services.validation.ValidationRegistry
28
29
  registry.register<ast.LikeC4AstType>({
29
30
  OpacityProperty: opacityPropertyRuleChecks(services),
31
+ IconProperty: iconPropertyRuleChecks(services),
30
32
  SpecificationRule: specificationRuleChecks(services),
31
33
  Model: modelRuleChecks(services),
32
34
  ModelViews: modelViewsChecks(services),
@@ -36,12 +38,13 @@ export function registerValidationChecks(services: LikeC4Services) {
36
38
  ElementKind: elementKindChecks(services),
37
39
  Relation: relationChecks(services),
38
40
  Tag: tagChecks(services),
39
- DynamicViewRulePredicate: dynamicViewRulePredicate(services),
40
- CustomElementExpr: customElementExprChecks(services),
41
- ExpandElementExpr: expandElementExprChecks(services),
41
+ DynamicViewRulePredicateIterator: dynamicViewRulePredicate(services),
42
+ CustomElementExpression: customElementExprChecks(services),
43
+ CustomRelationExpression: customRelationExprChecks(services),
44
+ ExpandElementExpression: expandElementExprChecks(services),
42
45
  RelationshipKind: relationshipChecks(services),
43
- IncomingExpr: incomingExpressionChecks(services),
44
- OutgoingExpr: outgoingExpressionChecks(services)
46
+ IncomingRelationExpression: incomingExpressionChecks(services),
47
+ OutgoingRelationExpression: outgoingExpressionChecks(services)
45
48
  })
46
49
  const connection = services.shared.lsp.Connection
47
50
  if (connection) {
@@ -1,5 +1,5 @@
1
1
  import type { ValidationCheck } from 'langium'
2
- import type { ast } from '../ast'
2
+ import { ast } from '../ast'
3
3
  import type { LikeC4Services } from '../module'
4
4
 
5
5
  export const opacityPropertyRuleChecks = (
@@ -15,3 +15,25 @@ export const opacityPropertyRuleChecks = (
15
15
  }
16
16
  }
17
17
  }
18
+
19
+ export const iconPropertyRuleChecks = (
20
+ _: LikeC4Services
21
+ ): ValidationCheck<ast.IconProperty> => {
22
+ return (node, accept) => {
23
+ const container = node.$container
24
+ const anotherIcon = container.props.some(p => ast.isIconProperty(p) && p !== node)
25
+ if (anotherIcon) {
26
+ accept('error', `Icon must be defined once`, {
27
+ node
28
+ })
29
+ }
30
+ if (
31
+ ast.isStyleProperties(container) && ast.isElementBody(container.$container)
32
+ && container.$container.props.some(p => ast.isIconProperty(p))
33
+ ) {
34
+ accept('warning', `Redundant as icon defined on element`, {
35
+ node
36
+ })
37
+ }
38
+ }
39
+ }
@@ -1,21 +1,34 @@
1
+ import { nonexhaustive } from '@likec4/core'
1
2
  import type { ValidationCheck } from 'langium'
3
+ import { AstUtils } from 'langium'
2
4
  import { ast } from '../../ast'
3
5
  import type { LikeC4Services } from '../../module'
4
6
 
5
7
  export const customElementExprChecks = (
6
8
  _services: LikeC4Services
7
- ): ValidationCheck<ast.CustomElementExpr> => {
9
+ ): ValidationCheck<ast.CustomElementExpression> => {
8
10
  return (el, accept) => {
9
- if (ast.isExcludePredicate(el.$container)) {
10
- accept('error', 'Invalid inside "exclude"', {
11
+ const container = AstUtils.getContainerOfType(el, ast.isViewRulePredicate)
12
+ if (ast.isExcludePredicate(container)) {
13
+ accept('error', 'Invalid usage inside "exclude"', {
11
14
  node: el
12
15
  })
13
16
  }
14
- if (!ast.isElementRef(el.target)) {
15
- accept('error', 'Invalid target for customization', {
16
- node: el,
17
- property: 'target'
18
- })
17
+ switch (true) {
18
+ case ast.isElementRef(el.target):
19
+ case ast.isElementDescedantsExpression(el.target):
20
+ case ast.isExpandElementExpression(el.target):
21
+ return
22
+ case ast.isElementKindExpression(el.target):
23
+ case ast.isElementTagExpression(el.target):
24
+ case ast.isWildcardExpression(el.target):
25
+ accept('error', 'Invalid target (expect reference to specific element)', {
26
+ node: el,
27
+ property: 'target'
28
+ })
29
+ return
30
+ default:
31
+ nonexhaustive(el.target)
19
32
  }
20
33
  }
21
34
  }
@@ -0,0 +1,16 @@
1
+ import { AstUtils, type ValidationCheck } from 'langium'
2
+ import { ast } from '../../ast'
3
+ import type { LikeC4Services } from '../../module'
4
+
5
+ export const customRelationExprChecks = (
6
+ _services: LikeC4Services
7
+ ): ValidationCheck<ast.CustomRelationExpression> => {
8
+ return (el, accept) => {
9
+ const container = AstUtils.getContainerOfType(el, ast.isViewRulePredicate)
10
+ if (ast.isExcludePredicate(container)) {
11
+ accept('error', 'Invalid usage inside "exclude"', {
12
+ node: el
13
+ })
14
+ }
15
+ }
16
+ }
@@ -1,34 +1,23 @@
1
1
  import { nonexhaustive } from '@likec4/core'
2
- import type { ValidationCheck } from 'langium'
2
+ import { type AstNode, AstUtils, type ValidationCheck } from 'langium'
3
3
  import { ast } from '../../ast'
4
4
  import type { LikeC4Services } from '../../module'
5
5
 
6
6
  export const expandElementExprChecks = (
7
7
  _services: LikeC4Services
8
- ): ValidationCheck<ast.ExpandElementExpr> => {
8
+ ): ValidationCheck<ast.ExpandElementExpression> => {
9
9
  return (el, accept) => {
10
- switch (true) {
11
- case ast.isIncludePredicate(el.$container):
12
- case ast.isDynamicViewRulePredicate(el.$container):
13
- case ast.isViewRuleStyle(el.$container):
14
- return
15
- case ast.isCustomElementExpr(el.$container):
16
- return accept('warning', `Custom rules apply only to parent`, {
17
- node: el
18
- })
19
- case ast.isExcludePredicate(el.$container):
20
- return accept('warning', `Ignored, as can't be used in exclude`, {
21
- node: el
22
- })
23
- case ast.isInOutExpr(el.$container):
24
- case ast.isIncomingExpr(el.$container):
25
- case ast.isOutgoingExpr(el.$container):
26
- case ast.isRelationExpr(el.$container):
27
- return accept('warning', `Wrong usage of expanded element in relations predicate`, {
28
- node: el
29
- })
30
- default:
31
- nonexhaustive(el.$container)
10
+ const isInside = <T extends AstNode>(typePredicate: (n: AstNode) => n is T): boolean =>
11
+ !!AstUtils.getContainerOfType(el, typePredicate)
12
+ if (isInside(ast.isRelationExpression)) {
13
+ accept('warning', `Redundant usage, expand predicate resolves parent element only when used in relations`, {
14
+ node: el
15
+ })
16
+ }
17
+ if (isInside(ast.isExcludePredicate)) {
18
+ accept('warning', `Expand predicate is ignored in exclude`, {
19
+ node: el
20
+ })
32
21
  }
33
22
  }
34
23
  }
@@ -1,15 +1,15 @@
1
- import type { ValidationCheck } from 'langium'
1
+ import { AstUtils, type ValidationCheck } from 'langium'
2
2
  import { isNullish } from 'remeda'
3
3
  import { ast } from '../../ast'
4
4
  import type { LikeC4Services } from '../../module'
5
5
 
6
6
  export const incomingExpressionChecks = (
7
7
  _services: LikeC4Services
8
- ): ValidationCheck<ast.IncomingExpr> => {
8
+ ): ValidationCheck<ast.IncomingRelationExpression> => {
9
9
  return (el, accept) => {
10
- if (ast.isWildcardExpr(el.to) && ast.isViewRulePredicate(el.$container)) {
11
- const view = el.$container.$container.$container
12
- if (isNullish(view.viewOf)) {
10
+ if (ast.isWildcardExpression(el.to) && ast.isExpressions(el.$container)) {
11
+ const view = AstUtils.getContainerOfType(el, ast.isElementView)
12
+ if (isNullish(view?.viewOf)) {
13
13
  accept('warning', 'Predicate is ignored as it concerns all relationships', {
14
14
  node: el
15
15
  })
@@ -1,4 +1,5 @@
1
1
  export * from './custom-element-expr'
2
+ export * from './custom-relation-expr'
2
3
  export * from './expanded-element'
3
4
  export * from './incoming'
4
5
  export * from './outgoing'
@@ -1,15 +1,15 @@
1
- import type { ValidationCheck } from 'langium'
1
+ import { AstUtils, type ValidationCheck } from 'langium'
2
2
  import { isNullish } from 'remeda'
3
3
  import { ast } from '../../ast'
4
4
  import type { LikeC4Services } from '../../module'
5
5
 
6
6
  export const outgoingExpressionChecks = (
7
7
  _services: LikeC4Services
8
- ): ValidationCheck<ast.OutgoingExpr> => {
8
+ ): ValidationCheck<ast.OutgoingRelationExpression> => {
9
9
  return (el, accept) => {
10
- if (ast.isWildcardExpr(el.from)) {
11
- const view = el.$container.$container.$container
12
- if (view.$type === 'ElementView' && isNullish(view.viewOf)) {
10
+ if (ast.isWildcardExpression(el.from) && ast.isExpressions(el.$container)) {
11
+ const view = AstUtils.getContainerOfType(el, ast.isElementView)
12
+ if (isNullish(view?.viewOf)) {
13
13
  accept('warning', 'Predicate is ignored as it concerns all relationships', {
14
14
  node: el
15
15
  })
@@ -1,11 +1,11 @@
1
1
  import { type ComputedView, type Fqn, isComputedElementView, type ViewID } from '@likec4/core'
2
- import { find } from 'remeda'
2
+ import { find, isNullish } from 'remeda'
3
3
 
4
4
  export function assignNavigateTo<R extends Iterable<ComputedView>>(views: R): R {
5
5
  const allElementViews = new Map<Fqn, ViewID[]>()
6
6
 
7
7
  for (const v of views) {
8
- if (isComputedElementView(v) && v.viewOf && !v.extends) {
8
+ if (isComputedElementView(v) && v.viewOf && isNullish(v.extends)) {
9
9
  const viewsOf = allElementViews.get(v.viewOf) ?? []
10
10
  viewsOf.push(v.id)
11
11
  allElementViews.set(v.viewOf, viewsOf)