@likec4/language-server 1.14.0 → 1.15.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/dist/browser.cjs +1 -1
- package/dist/browser.d.cts +2 -2
- package/dist/browser.d.mts +2 -2
- package/dist/browser.d.ts +2 -2
- package/dist/browser.mjs +2 -2
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.mjs +2 -2
- package/dist/likec4lib.cjs +0 -1
- package/dist/likec4lib.mjs +0 -1
- package/dist/model-graph/index.cjs +1 -1
- package/dist/model-graph/index.mjs +1 -1
- package/dist/shared/{language-server.CbDa016p.cjs → language-server.B8s2wfT_.cjs} +279 -68
- package/dist/shared/{language-server.DFLaUdYu.mjs → language-server.BT4WTbFI.mjs} +281 -70
- package/dist/shared/{language-server.DZYziEuF.mjs → language-server.BuChFlda.mjs} +231 -44
- package/dist/shared/{language-server.CO6aEDQm.d.mts → language-server.DfMwkd2l.d.mts} +46 -15
- package/dist/shared/{language-server.CITj0XN3.cjs → language-server.DfjkvknB.cjs} +230 -43
- package/dist/shared/{language-server.Cqyh6-9S.d.cts → language-server.U2piOAVt.d.cts} +46 -15
- package/dist/shared/{language-server.BI99piRy.d.ts → language-server.j-ShR6as.d.ts} +46 -15
- package/package.json +10 -10
- package/src/ast.ts +1 -0
- package/src/formatting/LikeC4Formatter.ts +44 -4
- package/src/generated/ast.ts +103 -19
- package/src/generated/grammar.ts +1 -1
- package/src/generated-lib/icons.ts +0 -1
- package/src/like-c4.langium +32 -9
- package/src/lsp/CompletionProvider.ts +70 -2
- package/src/lsp/SemanticTokenProvider.ts +3 -1
- package/src/model/model-builder.ts +13 -7
- package/src/model/model-parser.ts +71 -20
- package/src/model-graph/compute-view/__test__/fixture.ts +96 -29
- package/src/model-graph/compute-view/compute.ts +223 -40
- package/src/model-graph/compute-view/predicates.ts +12 -6
- package/src/model-graph/utils/applyCustomElementProperties.ts +18 -4
- package/src/model-graph/utils/applyCustomRelationProperties.ts +9 -39
- package/src/model-graph/utils/applyViewRuleStyles.ts +30 -25
- package/src/model-graph/utils/buildComputeNodes.ts +69 -17
- package/src/model-graph/utils/elementExpressionToPredicate.ts +3 -1
- package/src/model-graph/utils/relationExpressionToPredicates.ts +43 -0
- package/src/model-graph/utils/sortNodes.ts +11 -7
- package/src/references/scope-computation.ts +3 -2
- package/src/validation/index.ts +2 -2
- package/src/validation/specification.ts +4 -4
|
@@ -1,5 +1,13 @@
|
|
|
1
|
-
import type { ComputedNode, Element, Fqn } from '@likec4/core'
|
|
2
|
-
import {
|
|
1
|
+
import type { ComputedNode, Element, Fqn, NodeId } from '@likec4/core'
|
|
2
|
+
import {
|
|
3
|
+
compareByFqnHierarchically,
|
|
4
|
+
DefaultElementShape,
|
|
5
|
+
DefaultThemeColor,
|
|
6
|
+
ElementKind,
|
|
7
|
+
nonNullable,
|
|
8
|
+
parentFqn
|
|
9
|
+
} from '@likec4/core'
|
|
10
|
+
import { NodesGroup } from '../compute-view/compute'
|
|
3
11
|
|
|
4
12
|
function updateDepthOfAncestors(node: ComputedNode, nodes: ReadonlyMap<Fqn, ComputedNode>) {
|
|
5
13
|
let parentNd
|
|
@@ -14,48 +22,92 @@ function updateDepthOfAncestors(node: ComputedNode, nodes: ReadonlyMap<Fqn, Comp
|
|
|
14
22
|
}
|
|
15
23
|
}
|
|
16
24
|
|
|
17
|
-
export function buildComputeNodes(elements: Iterable<Element
|
|
25
|
+
export function buildComputeNodes(elements: Iterable<Element>, groups?: NodesGroup[]) {
|
|
26
|
+
const nodesMap = new Map<Fqn, ComputedNode>()
|
|
27
|
+
|
|
28
|
+
const elementToGroup = new Map<Fqn, NodeId>()
|
|
29
|
+
|
|
30
|
+
groups?.forEach(({ id, parent, viewRule, explicits }) => {
|
|
31
|
+
if (parent) {
|
|
32
|
+
nonNullable(nodesMap.get(parent), `Parent group node ${parent} not found`).children.push(id)
|
|
33
|
+
}
|
|
34
|
+
nodesMap.set(id, {
|
|
35
|
+
id,
|
|
36
|
+
parent,
|
|
37
|
+
kind: ElementKind.Group,
|
|
38
|
+
title: viewRule.title ?? '',
|
|
39
|
+
color: viewRule.color ?? 'muted',
|
|
40
|
+
shape: 'rectangle',
|
|
41
|
+
children: [],
|
|
42
|
+
inEdges: [],
|
|
43
|
+
outEdges: [],
|
|
44
|
+
level: 0,
|
|
45
|
+
depth: 0,
|
|
46
|
+
description: null,
|
|
47
|
+
technology: null,
|
|
48
|
+
tags: null,
|
|
49
|
+
links: null,
|
|
50
|
+
style: {
|
|
51
|
+
border: viewRule.border ?? 'dashed',
|
|
52
|
+
opacity: viewRule.opacity ?? 0
|
|
53
|
+
}
|
|
54
|
+
})
|
|
55
|
+
for (const e of explicits) {
|
|
56
|
+
elementToGroup.set(e.id, id)
|
|
57
|
+
}
|
|
58
|
+
})
|
|
59
|
+
|
|
18
60
|
return (
|
|
19
61
|
Array.from(elements)
|
|
20
62
|
// Sort from Top to Bottom
|
|
21
63
|
// So we can ensure that parent nodes are created before child nodes
|
|
22
64
|
.sort(compareByFqnHierarchically)
|
|
23
|
-
.reduce((map, { id, color, shape, style, ...el }) => {
|
|
65
|
+
.reduce((map, { id, color, shape, style, kind, title, ...el }) => {
|
|
24
66
|
let parent = parentFqn(id)
|
|
25
67
|
let level = 0
|
|
68
|
+
let parentNd: ComputedNode | undefined
|
|
26
69
|
// Find the first ancestor that is already in the map
|
|
27
70
|
while (parent) {
|
|
28
|
-
|
|
71
|
+
parentNd = map.get(parent)
|
|
29
72
|
if (parentNd) {
|
|
30
|
-
// if parent has no children and we are about to add first one
|
|
31
|
-
// we need to set its depth to 1
|
|
32
|
-
if (parentNd.children.length == 0) {
|
|
33
|
-
parentNd.depth = 1
|
|
34
|
-
// go up the tree and update depth of all parents
|
|
35
|
-
updateDepthOfAncestors(parentNd, map)
|
|
36
|
-
}
|
|
37
|
-
parentNd.children.push(id)
|
|
38
|
-
level = parentNd.level + 1
|
|
39
73
|
break
|
|
40
74
|
}
|
|
41
75
|
parent = parentFqn(parent)
|
|
42
76
|
}
|
|
77
|
+
// If parent is not found in the map, check if it is in a group
|
|
78
|
+
if (!parentNd && elementToGroup.has(id)) {
|
|
79
|
+
parent = elementToGroup.get(id)!
|
|
80
|
+
parentNd = map.get(parent)!
|
|
81
|
+
}
|
|
82
|
+
if (parentNd) {
|
|
83
|
+
// if parent has no children and we are about to add first one
|
|
84
|
+
// we need to set its depth to 1
|
|
85
|
+
if (parentNd.children.length == 0) {
|
|
86
|
+
parentNd.depth = 1
|
|
87
|
+
// go up the tree and update depth of all parents
|
|
88
|
+
updateDepthOfAncestors(parentNd, map)
|
|
89
|
+
}
|
|
90
|
+
parentNd.children.push(id)
|
|
91
|
+
level = parentNd.level + 1
|
|
92
|
+
}
|
|
43
93
|
const node: ComputedNode = {
|
|
44
|
-
...el,
|
|
45
94
|
id,
|
|
46
95
|
parent,
|
|
47
|
-
|
|
96
|
+
kind,
|
|
97
|
+
title,
|
|
48
98
|
color: color ?? DefaultThemeColor,
|
|
49
99
|
shape: shape ?? DefaultElementShape,
|
|
50
100
|
children: [],
|
|
51
101
|
inEdges: [],
|
|
52
102
|
outEdges: [],
|
|
103
|
+
level,
|
|
104
|
+
...el,
|
|
53
105
|
style: {
|
|
54
106
|
...style
|
|
55
107
|
}
|
|
56
108
|
}
|
|
57
109
|
map.set(id, node)
|
|
58
110
|
return map
|
|
59
|
-
},
|
|
111
|
+
}, nodesMap) as ReadonlyMap<Fqn, ComputedNode>
|
|
60
112
|
)
|
|
61
113
|
}
|
|
@@ -4,7 +4,9 @@ import { isNullish } from 'remeda'
|
|
|
4
4
|
|
|
5
5
|
type Predicate<T> = (x: T) => boolean
|
|
6
6
|
|
|
7
|
-
export function elementExprToPredicate
|
|
7
|
+
export function elementExprToPredicate<T extends Pick<Element, 'id' | 'kind' | 'tags'>>(
|
|
8
|
+
target: Expr.ElementPredicateExpression
|
|
9
|
+
): Predicate<T> {
|
|
8
10
|
if (Expr.isElementWhere(target)) {
|
|
9
11
|
const predicate = elementExprToPredicate(target.where.expr)
|
|
10
12
|
const where = whereOperatorAsPredicate(target.where.condition)
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { Element, Relation } from '@likec4/core'
|
|
2
|
+
import { Expr, nonexhaustive, whereOperatorAsPredicate } from '@likec4/core'
|
|
3
|
+
import { elementExprToPredicate } from './elementExpressionToPredicate'
|
|
4
|
+
|
|
5
|
+
type Predicate<T> = (x: T) => boolean
|
|
6
|
+
export type FilterableEdge = Pick<Relation, 'kind' | 'tags'> & {
|
|
7
|
+
source: Element
|
|
8
|
+
target: Element
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function relationExpressionToPredicates<T extends FilterableEdge>(
|
|
12
|
+
expr: Expr.RelationExpression | Expr.RelationWhereExpr
|
|
13
|
+
): Predicate<T> {
|
|
14
|
+
switch (true) {
|
|
15
|
+
case Expr.isRelationWhere(expr):
|
|
16
|
+
const predicate = relationExpressionToPredicates(expr.where.expr)
|
|
17
|
+
const where = whereOperatorAsPredicate(expr.where.condition)
|
|
18
|
+
|
|
19
|
+
return e => predicate(e) && where(e)
|
|
20
|
+
case Expr.isRelation(expr): {
|
|
21
|
+
const isSource = elementExprToPredicate(expr.source)
|
|
22
|
+
const isTarget = elementExprToPredicate(expr.target)
|
|
23
|
+
return edge => {
|
|
24
|
+
return (isSource(edge.source) && isTarget(edge.target))
|
|
25
|
+
|| (!!expr.isBidirectional && isSource(edge.target) && isTarget(edge.source))
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
case Expr.isInOut(expr): {
|
|
29
|
+
const isInOut = elementExprToPredicate(expr.inout)
|
|
30
|
+
return edge => isInOut(edge.source) || isInOut(edge.target)
|
|
31
|
+
}
|
|
32
|
+
case Expr.isIncoming(expr): {
|
|
33
|
+
const isTarget = elementExprToPredicate(expr.incoming)
|
|
34
|
+
return edge => isTarget(edge.target)
|
|
35
|
+
}
|
|
36
|
+
case Expr.isOutgoing(expr): {
|
|
37
|
+
const isSource = elementExprToPredicate(expr.outgoing)
|
|
38
|
+
return edge => isSource(edge.source)
|
|
39
|
+
}
|
|
40
|
+
default:
|
|
41
|
+
nonexhaustive(expr)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
invariant,
|
|
9
9
|
nonNullable
|
|
10
10
|
} from '@likec4/core'
|
|
11
|
-
import { difference, filter, map, pipe, sort } from 'remeda'
|
|
11
|
+
import { difference, filter, map, pipe, sort, tap } from 'remeda'
|
|
12
12
|
import { Graph, postorder } from '../../utils/graphlib'
|
|
13
13
|
|
|
14
14
|
// side effect
|
|
@@ -27,9 +27,16 @@ export function sortNodes({
|
|
|
27
27
|
nodes: ComputedNode[]
|
|
28
28
|
edges: ComputedEdge[]
|
|
29
29
|
}): ComputedNode[] {
|
|
30
|
-
if (
|
|
30
|
+
if (nodes.length < 2) {
|
|
31
31
|
return nodes
|
|
32
32
|
}
|
|
33
|
+
if (edges.length === 0) {
|
|
34
|
+
return pipe(
|
|
35
|
+
nodes,
|
|
36
|
+
sort(compareByFqnHierarchically),
|
|
37
|
+
tap(sortChildren)
|
|
38
|
+
)
|
|
39
|
+
}
|
|
33
40
|
|
|
34
41
|
const g = new Graph({
|
|
35
42
|
compound: false,
|
|
@@ -55,9 +62,6 @@ export function sortNodes({
|
|
|
55
62
|
for (const n of nodes) {
|
|
56
63
|
g.setNode(n.id, n.id)
|
|
57
64
|
if (n.children.length > 0) {
|
|
58
|
-
// n.children.forEach(c => {
|
|
59
|
-
// g.setEdge(n.id, c, undefined, `${n.id}:${c}`)
|
|
60
|
-
// })
|
|
61
65
|
n.inEdges.forEach(e => {
|
|
62
66
|
const edge = getEdge(e)
|
|
63
67
|
// if this edge from leaf to the child of this node
|
|
@@ -83,15 +87,15 @@ export function sortNodes({
|
|
|
83
87
|
if (sources.length === 0) {
|
|
84
88
|
sources = pipe(
|
|
85
89
|
nodes,
|
|
86
|
-
sort(compareByFqnHierarchically),
|
|
87
90
|
filter(n => n.inEdges.length === 0 || n.parent === null),
|
|
91
|
+
sort(compareByFqnHierarchically),
|
|
88
92
|
map(n => n.id)
|
|
89
93
|
)
|
|
90
94
|
}
|
|
91
95
|
const orderedIds = postorder(g, sources).reverse() as Fqn[]
|
|
92
96
|
const sorted = orderedIds.map(getNode)
|
|
93
97
|
if (sorted.length < nodes.length) {
|
|
94
|
-
const unsorted = difference(nodes, sorted)
|
|
98
|
+
const unsorted = difference(nodes, sorted).sort(compareByFqnHierarchically)
|
|
95
99
|
sorted.push(...unsorted)
|
|
96
100
|
}
|
|
97
101
|
|
|
@@ -71,8 +71,9 @@ export class LikeC4ScopeComputation extends DefaultScopeComputation {
|
|
|
71
71
|
}
|
|
72
72
|
for (const globalStyleAst of globals.flatMap(g => g.styles)) {
|
|
73
73
|
try {
|
|
74
|
-
|
|
75
|
-
|
|
74
|
+
const id = globalStyleAst.id
|
|
75
|
+
if (isTruthy(id.name)) {
|
|
76
|
+
docExports.push(this.descriptions.createDescription(id, id.name, document))
|
|
76
77
|
}
|
|
77
78
|
} catch (e) {
|
|
78
79
|
logError(e)
|
package/src/validation/index.ts
CHANGED
|
@@ -9,7 +9,7 @@ import { relationBodyChecks, relationChecks } from './relation'
|
|
|
9
9
|
import {
|
|
10
10
|
elementKindChecks,
|
|
11
11
|
globalsChecks,
|
|
12
|
-
|
|
12
|
+
globalStyleIdChecks,
|
|
13
13
|
modelRuleChecks,
|
|
14
14
|
relationshipChecks,
|
|
15
15
|
specificationRuleChecks,
|
|
@@ -34,7 +34,7 @@ export function registerValidationChecks(services: LikeC4Services) {
|
|
|
34
34
|
SpecificationRule: specificationRuleChecks(services),
|
|
35
35
|
Model: modelRuleChecks(services),
|
|
36
36
|
Globals: globalsChecks(services),
|
|
37
|
-
|
|
37
|
+
GlobalStyleId: globalStyleIdChecks(services),
|
|
38
38
|
DynamicViewStep: dynamicViewStep(services),
|
|
39
39
|
LikeC4View: viewChecks(services),
|
|
40
40
|
Element: elementChecks(services),
|
|
@@ -130,18 +130,18 @@ export const relationshipChecks = (
|
|
|
130
130
|
}
|
|
131
131
|
}
|
|
132
132
|
|
|
133
|
-
export const
|
|
133
|
+
export const globalStyleIdChecks = (
|
|
134
134
|
services: LikeC4Services
|
|
135
|
-
): ValidationCheck<ast.
|
|
135
|
+
): ValidationCheck<ast.GlobalStyleId> => {
|
|
136
136
|
const index = services.shared.workspace.IndexManager
|
|
137
137
|
return (node, accept) => {
|
|
138
138
|
const sameName = index
|
|
139
|
-
.allElements(ast.
|
|
139
|
+
.allElements(ast.GlobalStyleId)
|
|
140
140
|
.filter(s => s.name === node.name)
|
|
141
141
|
.limit(2)
|
|
142
142
|
.count()
|
|
143
143
|
if (sameName > 1) {
|
|
144
|
-
accept('error', `Duplicate
|
|
144
|
+
accept('error', `Duplicate GlobalStyleId name '${node.name}'`, {
|
|
145
145
|
node: node,
|
|
146
146
|
property: 'name'
|
|
147
147
|
})
|