@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.
- package/contrib/likec4.tmLanguage.json +1 -1
- package/package.json +23 -19
- package/src/Rpc.ts +1 -1
- package/src/ast.ts +34 -9
- package/src/{browser/index.ts → browser.ts} +4 -1
- package/src/generated/ast.ts +498 -152
- package/src/generated/grammar.ts +2 -2
- package/src/generated/module.ts +1 -1
- package/src/index.ts +1 -1
- package/src/like-c4.langium +116 -44
- package/src/logger.ts +76 -55
- package/src/lsp/DocumentLinkProvider.ts +1 -1
- package/src/lsp/DocumentSymbolProvider.ts +1 -1
- package/src/lsp/HoverProvider.ts +1 -1
- package/src/lsp/SemanticTokenProvider.ts +54 -26
- package/src/model/model-builder.ts +11 -8
- package/src/model/model-locator.ts +12 -25
- package/src/model/model-parser-where.ts +75 -0
- package/src/model/model-parser.ts +168 -68
- package/src/model-change/ModelChanges.ts +2 -3
- package/src/model-change/changeElementStyle.ts +4 -1
- package/src/model-change/changeViewLayout.ts +8 -8
- package/src/model-change/saveManualLayout.ts +4 -6
- package/src/model-graph/LikeC4ModelGraph.ts +50 -48
- package/src/model-graph/compute-view/__test__/fixture.ts +41 -16
- package/src/model-graph/compute-view/compute.ts +135 -69
- package/src/model-graph/compute-view/predicates.ts +232 -136
- package/src/model-graph/dynamic-view/__test__/fixture.ts +5 -1
- package/src/model-graph/dynamic-view/compute.ts +50 -41
- package/src/model-graph/utils/applyCustomElementProperties.ts +31 -29
- package/src/model-graph/utils/applyCustomRelationProperties.ts +52 -15
- package/src/model-graph/utils/elementExpressionToPredicate.ts +8 -3
- package/src/module.ts +4 -18
- package/src/{node/index.ts → node.ts} +1 -1
- package/src/protocol.ts +2 -2
- package/src/shared/NodeKindProvider.ts +4 -2
- package/src/test/setup.ts +13 -0
- package/src/test/testServices.ts +1 -1
- package/src/validation/dynamic-view-rule.ts +12 -12
- package/src/validation/index.ts +6 -6
- package/src/validation/relation.ts +1 -1
- package/src/validation/view-predicates/{custom-element-expr.ts → element-with.ts} +11 -10
- package/src/validation/view-predicates/expanded-element.ts +2 -10
- package/src/validation/view-predicates/incoming.ts +1 -1
- package/src/validation/view-predicates/index.ts +2 -2
- package/src/validation/view-predicates/outgoing.ts +1 -1
- package/src/validation/view-predicates/{custom-relation-expr.ts → relation-with.ts} +2 -2
- package/src/validation/view.ts +8 -9
- package/src/view-utils/manual-layout.ts +65 -72
- package/src/view-utils/resolve-relative-paths.ts +28 -17
- package/src/view-utils/view-hash.ts +33 -0
|
@@ -1,31 +1,44 @@
|
|
|
1
|
-
import type {
|
|
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
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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:
|
|
90
|
+
title: isTruthy(stepTitle) ? stepTitle : title,
|
|
70
91
|
relations,
|
|
71
|
-
isBackward:
|
|
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
|
-
|
|
79
|
-
|
|
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:
|
|
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 (
|
|
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: {
|
|
14
|
+
custom: { expr, ...props }
|
|
14
15
|
} of rules
|
|
15
16
|
) {
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
|
|
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
|
|
25
|
-
const
|
|
26
|
-
const
|
|
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
|
|
74
|
+
return
|
|
31
75
|
}
|
|
32
|
-
|
|
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<
|
|
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
|
|
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 {
|
|
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 '
|
|
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
|
|
12
|
-
import {
|
|
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-
|
|
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.
|
|
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
|
+
})
|
package/src/test/testServices.ts
CHANGED
|
@@ -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-
|
|
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.
|
|
9
|
-
return (
|
|
8
|
+
): ValidationCheck<ast.DynamicViewPredicateIterator> => {
|
|
9
|
+
return (predicate, accept) => {
|
|
10
|
+
const expr = elementExpressionFromPredicate(predicate.value)
|
|
10
11
|
switch (true) {
|
|
11
|
-
case ast.isElementRef(expr
|
|
12
|
-
case ast.isElementDescedantsExpression(expr
|
|
13
|
-
case ast.
|
|
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
|
|
17
|
-
case ast.isElementTagExpression(expr
|
|
18
|
-
case ast.isWildcardExpression(expr
|
|
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:
|
|
20
|
+
node: predicate
|
|
21
21
|
})
|
|
22
22
|
return
|
|
23
23
|
}
|
|
24
24
|
default:
|
|
25
|
-
nonexhaustive(expr
|
|
25
|
+
nonexhaustive(expr)
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
28
|
}
|
package/src/validation/index.ts
CHANGED
|
@@ -16,11 +16,11 @@ import {
|
|
|
16
16
|
} from './specification'
|
|
17
17
|
import { viewChecks } from './view'
|
|
18
18
|
import {
|
|
19
|
-
|
|
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
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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?.
|
|
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
|
|
7
|
+
export const elementPredicateWithChecks = (
|
|
8
8
|
_services: LikeC4Services
|
|
9
|
-
): ValidationCheck<ast.
|
|
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(
|
|
19
|
-
case ast.isElementDescedantsExpression(
|
|
20
|
-
case ast.isExpandElementExpression(
|
|
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(
|
|
23
|
-
case ast.isElementTagExpression(
|
|
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: '
|
|
28
|
+
property: 'subject'
|
|
28
29
|
})
|
|
29
30
|
return
|
|
30
31
|
default:
|
|
31
|
-
nonexhaustive(
|
|
32
|
+
nonexhaustive(subject)
|
|
32
33
|
}
|
|
33
34
|
}
|
|
34
35
|
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
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
|
-
|
|
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.
|
|
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', {
|
|
@@ -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.
|
|
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', {
|