@likec4/language-server 1.8.0 → 1.8.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.
@@ -72,7 +72,7 @@ Model:
72
72
  name='model' '{'
73
73
  elements+=(
74
74
  ExtendElement |
75
- ExplicitRelation |
75
+ Relation<true> |
76
76
  Element
77
77
  )*
78
78
  '}'
@@ -97,7 +97,7 @@ ElementBody: '{'
97
97
  tags=Tags?
98
98
  props+=ElementProperty*
99
99
  elements+=(
100
- Relation |
100
+ Relation<false> |
101
101
  Element
102
102
  )*
103
103
  '}'
@@ -115,7 +115,7 @@ ExtendElement:
115
115
 
116
116
  ExtendElementBody: '{'
117
117
  elements+=(
118
- ExplicitRelation |
118
+ Relation<true> |
119
119
  Element
120
120
  )*
121
121
  '}'
@@ -132,20 +132,18 @@ Tags:
132
132
  (values+=[Tag:TagId])+ ({infer Tags.prev=current} ',' (values+=[Tag:TagId])*)* ';'?
133
133
  ;
134
134
 
135
- Relation:
136
- ExplicitRelation | ImplicitRelation;
137
-
138
- ExplicitRelation:
139
- source=ElementRef RelationFragment;
140
-
141
- ImplicitRelation:
142
- ('this' | 'it')? RelationFragment;
143
-
144
- fragment RelationFragment:
145
- ('->' | '-[' kind=[RelationshipKind] ']->' | kind=[RelationshipKind:DotId] )
135
+ Relation<isExplicit>:
136
+ (<isExplicit> source=ElementRef | <!isExplicit> source=ElementRef?)
137
+ (
138
+ kind=[RelationshipKind:DotId] |
139
+ '-[' kind=[RelationshipKind] ']->' |
140
+ '->'
141
+ )
146
142
  target=ElementRef
147
- title=String?
148
- technology=String?
143
+ (
144
+ title=String
145
+ technology=String?
146
+ )?
149
147
  tags=Tags?
150
148
  body=RelationBody?
151
149
  ;
@@ -1,4 +1,4 @@
1
- import type { likec4 as c4 } from '@likec4/core'
1
+ import { invariant, type likec4 as c4 } from '@likec4/core'
2
2
  import type { AstNode } from 'langium'
3
3
  import {
4
4
  type AstNodeDescription,
@@ -7,6 +7,7 @@ import {
7
7
  DONE_RESULT,
8
8
  EMPTY_SCOPE,
9
9
  EMPTY_STREAM,
10
+ MapScope,
10
11
  type ReferenceInfo,
11
12
  type Scope,
12
13
  type Stream,
@@ -102,6 +103,17 @@ export class LikeC4ScopeProvider extends DefaultScopeProvider {
102
103
  if (parent) {
103
104
  return new StreamScope(this.scopeElementRef(parent))
104
105
  }
106
+ // if we have elementRef "this" or "it" we resolve it to the closest element
107
+ if (context.reference.$refText === 'this' || context.reference.$refText === 'it') {
108
+ const closestElement = AstUtils.getContainerOfType(container, ast.isElement)
109
+ if (closestElement) {
110
+ return new MapScope([
111
+ this.descriptions.createDescription(closestElement, context.reference.$refText)
112
+ ])
113
+ } else {
114
+ return EMPTY_SCOPE
115
+ }
116
+ }
105
117
  }
106
118
  return this.computeScope(context)
107
119
  } catch (e) {
@@ -116,26 +128,26 @@ export class LikeC4ScopeProvider extends DefaultScopeProvider {
116
128
 
117
129
  protected computeScope(context: ReferenceInfo) {
118
130
  const referenceType = this.reflection.getReferenceType(context)
131
+ // computeScope is called only for elements
132
+ invariant(referenceType === ast.Element, 'Invalid reference type')
119
133
  const scopes: Stream<AstNodeDescription>[] = []
120
134
  const doc = getDocument(context.container)
121
135
  const precomputed = doc.precomputedScopes
122
136
 
123
137
  if (precomputed) {
124
138
  const byReferenceType = (desc: AstNodeDescription) => this.reflection.isSubtype(desc.type, referenceType)
125
-
126
139
  let container: AstNode | undefined = context.container
127
140
  while (container) {
128
141
  const elements = precomputed.get(container).filter(byReferenceType)
129
142
  if (elements.length > 0) {
130
143
  scopes.push(stream(elements))
131
144
  }
132
- if (referenceType === ast.Element) {
133
- if (ast.isExtendElementBody(container)) {
134
- scopes.push(this.scopeExtendElement(container.$container))
135
- }
136
- if (ast.isElementViewBody(container)) {
137
- scopes.push(this.scopeElementView(container.$container))
138
- }
145
+
146
+ if (ast.isExtendElementBody(container)) {
147
+ scopes.push(this.scopeExtendElement(container.$container))
148
+ }
149
+ if (ast.isElementViewBody(container)) {
150
+ scopes.push(this.scopeElementView(container.$container))
139
151
  }
140
152
  container = container.$container
141
153
  }
@@ -0,0 +1,24 @@
1
+ import { type AstNode, interruptAndCheck, type ValidationAcceptor, type ValidationCheck } from 'langium'
2
+ import type { CancellationToken } from 'vscode-jsonrpc'
3
+ import { logWarnError } from '../logger'
4
+
5
+ export const RESERVED_WORDS = [
6
+ 'this',
7
+ 'it',
8
+ 'self',
9
+ 'super',
10
+ 'likec4lib'
11
+ ]
12
+
13
+ export function tryOrLog<T extends AstNode>(fn: ValidationCheck<T>): ValidationCheck<T> {
14
+ return async (node: T, accept: ValidationAcceptor, cancelToken: CancellationToken) => {
15
+ if (cancelToken) {
16
+ await interruptAndCheck(cancelToken)
17
+ }
18
+ try {
19
+ await fn(node, accept, cancelToken)
20
+ } catch (e) {
21
+ logWarnError(e)
22
+ }
23
+ }
24
+ }
@@ -1,13 +1,14 @@
1
1
  import { AstUtils, type ValidationCheck } from 'langium'
2
2
  import type { ast } from '../ast'
3
3
  import type { LikeC4Services } from '../module'
4
+ import { RESERVED_WORDS, tryOrLog } from './_shared'
4
5
 
5
6
  const { getDocument } = AstUtils
6
7
 
7
8
  export const elementChecks = (services: LikeC4Services): ValidationCheck<ast.Element> => {
8
9
  const fqnIndex = services.likec4.FqnIndex
9
10
  const locator = services.workspace.AstNodeLocator
10
- return (el, accept) => {
11
+ return tryOrLog((el, accept) => {
11
12
  const fqn = fqnIndex.getFqn(el)
12
13
  if (!fqn) {
13
14
  accept('error', 'Not indexed element', {
@@ -16,6 +17,12 @@ export const elementChecks = (services: LikeC4Services): ValidationCheck<ast.Ele
16
17
  })
17
18
  return
18
19
  }
20
+ if (RESERVED_WORDS.includes(el.name)) {
21
+ accept('error', `Reserved word: ${el.name}`, {
22
+ node: el,
23
+ property: 'name'
24
+ })
25
+ }
19
26
  const doc = getDocument(el)
20
27
  const docUri = doc.uri
21
28
  const elPath = locator.getAstNodePath(el)
@@ -45,12 +52,5 @@ export const elementChecks = (services: LikeC4Services): ValidationCheck<ast.Ele
45
52
  }
46
53
  )
47
54
  }
48
- // for (let i = 3; i < el.props.length; i++) {
49
- // accept('error', `Too many properties, max 3 allowed`, {
50
- // node: el,
51
- // property: 'props',
52
- // index: i
53
- // })
54
- // }
55
- }
55
+ })
56
56
  }
@@ -5,7 +5,7 @@ import { dynamicViewRulePredicate } from './dynamic-view-rule'
5
5
  import { dynamicViewStep } from './dynamic-view-step'
6
6
  import { elementChecks } from './element'
7
7
  import { iconPropertyRuleChecks, opacityPropertyRuleChecks } from './property-checks'
8
- import { relationChecks } from './relation'
8
+ import { relationBodyChecks, relationChecks } from './relation'
9
9
  import {
10
10
  elementKindChecks,
11
11
  modelRuleChecks,
@@ -37,6 +37,7 @@ export function registerValidationChecks(services: LikeC4Services) {
37
37
  Element: elementChecks(services),
38
38
  ElementKind: elementKindChecks(services),
39
39
  Relation: relationChecks(services),
40
+ RelationBody: relationBodyChecks(services),
40
41
  Tag: tagChecks(services),
41
42
  DynamicViewPredicateIterator: dynamicViewRulePredicate(services),
42
43
  ElementPredicateWith: elementPredicateWithChecks(services),
@@ -1,57 +1,63 @@
1
1
  import { isSameHierarchy } from '@likec4/core'
2
2
  import type { ValidationCheck } from 'langium'
3
+ import { isDefined } from 'remeda'
3
4
  import { ast } from '../ast'
4
5
  import { elementRef } from '../elementRef'
5
- import { logError } from '../logger'
6
6
  import type { LikeC4Services } from '../module'
7
+ import { tryOrLog } from './_shared'
7
8
 
8
9
  export const relationChecks = (services: LikeC4Services): ValidationCheck<ast.Relation> => {
9
10
  const fqnIndex = services.likec4.FqnIndex
10
- return (el, accept) => {
11
- try {
12
- const targetEl: ast.Element | undefined = elementRef(el.target)
13
- const target = targetEl && fqnIndex.getFqn(targetEl)
14
- if (!target) {
15
- accept('error', 'Target not found (not parsed/indexed yet)', {
11
+ return tryOrLog((el, accept) => {
12
+ const targetEl: ast.Element | undefined = elementRef(el.target)
13
+ const target = targetEl && fqnIndex.getFqn(targetEl)
14
+ if (!target) {
15
+ accept('error', 'Target not resolved', {
16
+ node: el,
17
+ property: 'target'
18
+ })
19
+ }
20
+ let sourceEl
21
+ if (isDefined(el.source)) {
22
+ sourceEl = elementRef(el.source)
23
+ if (!sourceEl) {
24
+ return accept('error', 'Source not resolved', {
16
25
  node: el,
17
- property: 'target'
26
+ property: 'source'
18
27
  })
19
28
  }
20
- let sourceEl
21
- if (ast.isExplicitRelation(el)) {
22
- sourceEl = elementRef(el.source)
23
- if (!sourceEl) {
24
- return accept('error', 'Source not found (not parsed/indexed yet)', {
25
- node: el,
26
- property: 'source'
27
- })
28
- }
29
- } else {
30
- sourceEl = el.$container.$container
31
- }
32
-
33
- const source = fqnIndex.getFqn(sourceEl)
34
-
35
- if (!source) {
36
- accept('error', 'Source not found (not parsed/indexed yet)', {
29
+ } else {
30
+ if (!ast.isElementBody(el.$container)) {
31
+ return accept('error', 'Sourceless relation must be nested', {
37
32
  node: el
38
33
  })
39
34
  }
35
+ sourceEl = el.$container.$container
36
+ }
40
37
 
41
- if (source && target && isSameHierarchy(source, target)) {
42
- accept('error', 'Invalid parent-child relationship', {
43
- node: el
44
- })
45
- }
38
+ const source = fqnIndex.getFqn(sourceEl)
46
39
 
47
- if (el.tags?.values && el.body?.tags?.values) {
48
- accept('error', 'Relation cannot have tags in both header and body', {
49
- node: el,
50
- property: 'tags'
51
- })
52
- }
53
- } catch (e) {
54
- logError(e)
40
+ if (!source) {
41
+ accept('error', 'Source not resolved', {
42
+ node: el
43
+ })
44
+ }
45
+
46
+ if (source && target && isSameHierarchy(source, target)) {
47
+ accept('error', 'Invalid parent-child relationship', {
48
+ node: el
49
+ })
50
+ }
51
+ })
52
+ }
53
+
54
+ export const relationBodyChecks = (_services: LikeC4Services): ValidationCheck<ast.RelationBody> => {
55
+ return tryOrLog((body, accept) => {
56
+ const relation = body.$container
57
+ if (relation.tags?.values && body.tags?.values) {
58
+ accept('error', 'Relation cannot have tags in both header and body', {
59
+ node: body.tags
60
+ })
55
61
  }
56
- }
62
+ })
57
63
  }
@@ -1,6 +1,7 @@
1
1
  import { AstUtils, type ValidationCheck } from 'langium'
2
2
  import { ast } from '../ast'
3
3
  import type { LikeC4Services } from '../module'
4
+ import { RESERVED_WORDS, tryOrLog } from './_shared'
4
5
 
5
6
  export const specificationRuleChecks = (
6
7
  _: LikeC4Services
@@ -39,7 +40,13 @@ export const modelViewsChecks = (_: LikeC4Services): ValidationCheck<ast.ModelVi
39
40
 
40
41
  export const elementKindChecks = (services: LikeC4Services): ValidationCheck<ast.ElementKind> => {
41
42
  const index = services.shared.workspace.IndexManager
42
- return (node, accept) => {
43
+ return tryOrLog((node, accept) => {
44
+ if (RESERVED_WORDS.includes(node.name)) {
45
+ accept('error', `Reserved word: ${node.name}`, {
46
+ node: node,
47
+ property: 'name'
48
+ })
49
+ }
43
50
  const sameKind = index
44
51
  .allElements(ast.ElementKind)
45
52
  .filter(n => n.name === node.name && n.node !== node)
@@ -62,7 +69,7 @@ export const elementKindChecks = (services: LikeC4Services): ValidationCheck<ast
62
69
  }
63
70
  })
64
71
  }
65
- }
72
+ })
66
73
  }
67
74
 
68
75
  export const tagChecks = (services: LikeC4Services): ValidationCheck<ast.Tag> => {
@@ -103,6 +110,12 @@ export const relationshipChecks = (
103
110
  ): ValidationCheck<ast.RelationshipKind> => {
104
111
  const index = services.shared.workspace.IndexManager
105
112
  return (node, accept) => {
113
+ if (RESERVED_WORDS.includes(node.name)) {
114
+ accept('error', `Reserved word: ${node.name}`, {
115
+ node: node,
116
+ property: 'name'
117
+ })
118
+ }
106
119
  const sameKinds = index
107
120
  .allElements(ast.RelationshipKind)
108
121
  .filter(n => n.name === node.name)
@@ -1,6 +1,7 @@
1
1
  import { type ValidationCheck } from 'langium'
2
2
  import { ast } from '../ast'
3
3
  import type { LikeC4Services } from '../module'
4
+ import { RESERVED_WORDS } from './_shared'
4
5
 
5
6
  export const viewChecks = (services: LikeC4Services): ValidationCheck<ast.LikeC4View> => {
6
7
  const index = services.shared.workspace.IndexManager
@@ -15,6 +16,12 @@ export const viewChecks = (services: LikeC4Services): ValidationCheck<ast.LikeC4
15
16
  if (!el.name) {
16
17
  return
17
18
  }
19
+ if (RESERVED_WORDS.includes(el.name)) {
20
+ accept('error', `Reserved word: ${el.name}`, {
21
+ node: el,
22
+ property: 'name'
23
+ })
24
+ }
18
25
  const anotherViews = index
19
26
  .allElements(ast.LikeC4View)
20
27
  .filter(n => n.name === el.name)