@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.
- package/contrib/likec4.tmLanguage.json +1 -1
- package/package.json +5 -5
- package/src/ast.ts +5 -3
- package/src/generated/ast.ts +39 -84
- package/src/generated/grammar.ts +1 -1
- package/src/like-c4.langium +14 -16
- package/src/references/scope-provider.ts +21 -9
- package/src/validation/_shared.ts +24 -0
- package/src/validation/element.ts +9 -9
- package/src/validation/index.ts +2 -1
- package/src/validation/relation.ts +45 -39
- package/src/validation/specification.ts +15 -2
- package/src/validation/view.ts +7 -0
package/src/like-c4.langium
CHANGED
|
@@ -72,7 +72,7 @@ Model:
|
|
|
72
72
|
name='model' '{'
|
|
73
73
|
elements+=(
|
|
74
74
|
ExtendElement |
|
|
75
|
-
|
|
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
|
-
|
|
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
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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
|
-
|
|
148
|
-
|
|
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
|
|
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
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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
|
-
|
|
49
|
-
// accept('error', `Too many properties, max 3 allowed`, {
|
|
50
|
-
// node: el,
|
|
51
|
-
// property: 'props',
|
|
52
|
-
// index: i
|
|
53
|
-
// })
|
|
54
|
-
// }
|
|
55
|
-
}
|
|
55
|
+
})
|
|
56
56
|
}
|
package/src/validation/index.ts
CHANGED
|
@@ -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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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: '
|
|
26
|
+
property: 'source'
|
|
18
27
|
})
|
|
19
28
|
}
|
|
20
|
-
|
|
21
|
-
if (ast.
|
|
22
|
-
|
|
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
|
-
|
|
42
|
-
accept('error', 'Invalid parent-child relationship', {
|
|
43
|
-
node: el
|
|
44
|
-
})
|
|
45
|
-
}
|
|
38
|
+
const source = fqnIndex.getFqn(sourceEl)
|
|
46
39
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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)
|
package/src/validation/view.ts
CHANGED
|
@@ -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)
|