@likec4/language-server 1.30.0 → 1.32.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/dist/LikeC4LanguageServices.d.ts +2 -1
- package/dist/LikeC4LanguageServices.js +4 -3
- package/dist/Rpc.js +11 -4
- package/dist/ast.d.ts +11 -15
- package/dist/bundled.mjs +2545 -2475
- package/dist/formatting/LikeC4Formatter.d.ts +26 -2
- package/dist/formatting/LikeC4Formatter.js +106 -8
- package/dist/generated/ast.d.ts +171 -153
- package/dist/generated/ast.js +40 -18
- package/dist/generated/grammar.d.ts +1 -1
- package/dist/generated/grammar.js +1 -1
- package/dist/generated/module.d.ts +1 -1
- package/dist/lsp/CompletionProvider.js +1 -1
- package/dist/lsp/DocumentLinkProvider.js +4 -3
- package/dist/lsp/HoverProvider.js +9 -1
- package/dist/lsp/SemanticTokenProvider.js +18 -4
- package/dist/mcp/LikeC4MCPServerFactory.d.ts +5 -2
- package/dist/mcp/LikeC4MCPServerFactory.js +2 -72
- package/dist/mcp/LikeC4MCPTools.js +5 -3
- package/dist/mcp/sseserver/MCPServerFactory.d.ts +8 -0
- package/dist/mcp/sseserver/MCPServerFactory.js +78 -0
- package/dist/mcp/sseserver/with-mcp-server.d.ts +2 -0
- package/dist/mcp/sseserver/with-mcp-server.js +5 -1
- package/dist/mcp/utils.d.ts +5 -4
- package/dist/mcp/utils.js +2 -2
- package/dist/model/builder/MergedExtends.d.ts +4 -4
- package/dist/model/builder/MergedExtends.js +1 -1
- package/dist/model/builder/MergedSpecification.d.ts +4 -3
- package/dist/model/builder/MergedSpecification.js +9 -8
- package/dist/model/builder/assignTagColors.d.ts +7 -0
- package/dist/model/builder/assignTagColors.js +51 -0
- package/dist/model/builder/buildModel.d.ts +2 -2
- package/dist/model/builder/buildModel.js +40 -18
- package/dist/model/deployments-index.js +2 -2
- package/dist/model/fqn-index.d.ts +1 -1
- package/dist/model/fqn-index.js +6 -6
- package/dist/model/model-builder.d.ts +10 -4
- package/dist/model/model-builder.js +22 -19
- package/dist/model/model-locator.d.ts +10 -2
- package/dist/model/model-locator.js +65 -9
- package/dist/model/model-parser-where.d.ts +1 -1
- package/dist/model/model-parser-where.js +1 -1
- package/dist/model/model-parser.d.ts +10 -11
- package/dist/model/model-parser.js +11 -7
- package/dist/model/parser/Base.js +2 -3
- package/dist/model/parser/DeploymentModelParser.d.ts +1 -1
- package/dist/model/parser/DeploymentModelParser.js +7 -5
- package/dist/model/parser/DeploymentViewParser.d.ts +3 -3
- package/dist/model/parser/DeploymentViewParser.js +2 -1
- package/dist/model/parser/FqnRefParser.d.ts +1 -1
- package/dist/model/parser/FqnRefParser.js +2 -5
- package/dist/model/parser/GlobalsParser.d.ts +23 -24
- package/dist/model/parser/GlobalsParser.js +4 -4
- package/dist/model/parser/ModelParser.d.ts +1 -1
- package/dist/model/parser/ModelParser.js +1 -1
- package/dist/model/parser/PredicatesParser.d.ts +12 -12
- package/dist/model/parser/SpecificationParser.d.ts +5 -2
- package/dist/model/parser/SpecificationParser.js +18 -30
- package/dist/model/parser/ViewsParser.d.ts +23 -23
- package/dist/model/parser/ViewsParser.js +12 -19
- package/dist/model-change/changeElementStyle.d.ts +2 -2
- package/dist/model-change/changeElementStyle.js +6 -2
- package/dist/module.d.ts +1 -1
- package/dist/module.js +5 -2
- package/dist/protocol.d.ts +24 -3
- package/dist/protocol.js +4 -0
- package/dist/references/scope-computation.js +2 -3
- package/dist/test/testServices.d.ts +15 -0
- package/dist/test/testServices.js +4 -1
- package/dist/validation/relation.js +1 -1
- package/dist/validation/specification.js +1 -1
- package/dist/view-utils/assignNavigateTo.d.ts +1 -1
- package/dist/view-utils/assignNavigateTo.js +3 -3
- package/dist/views/likec4-views.d.ts +1 -3
- package/dist/views/likec4-views.js +68 -44
- package/dist/workspace/WorkspaceManager.d.ts +2 -2
- package/dist/workspace/WorkspaceManager.js +2 -2
- package/package.json +27 -25
- /package/dist/mcp/sseserver/{SSELikeC4MCPServer.d.ts → MCPServer.d.ts} +0 -0
- /package/dist/mcp/sseserver/{SSELikeC4MCPServer.js → MCPServer.js} +0 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import * as c4 from "@likec4/core";
|
|
1
2
|
import {
|
|
2
3
|
computeColorValues,
|
|
3
|
-
|
|
4
|
+
isDeploymentNode,
|
|
4
5
|
isGlobalFqn,
|
|
5
6
|
parentFqn,
|
|
6
7
|
sortByFqnHierarchically
|
|
@@ -9,10 +10,12 @@ import { resolveRulesExtendedViews } from "@likec4/core/compute-view";
|
|
|
9
10
|
import {
|
|
10
11
|
filter,
|
|
11
12
|
flatMap,
|
|
13
|
+
forEach,
|
|
12
14
|
indexBy,
|
|
13
15
|
isDefined,
|
|
14
16
|
isNullish,
|
|
15
17
|
isTruthy,
|
|
18
|
+
keys,
|
|
16
19
|
map,
|
|
17
20
|
mapValues,
|
|
18
21
|
pipe,
|
|
@@ -23,14 +26,20 @@ import { logger } from "../../logger.js";
|
|
|
23
26
|
import { resolveRelativePaths } from "../../view-utils/index.js";
|
|
24
27
|
import { MergedExtends } from "./MergedExtends.js";
|
|
25
28
|
import { MergedSpecification } from "./MergedSpecification.js";
|
|
26
|
-
export function buildModelData(docs) {
|
|
29
|
+
export function buildModelData(projectId, docs) {
|
|
27
30
|
const c4Specification = new MergedSpecification(docs);
|
|
28
|
-
const
|
|
31
|
+
const customColors = mapValues(
|
|
29
32
|
c4Specification.specs.colors,
|
|
30
33
|
(c) => computeColorValues(c.color)
|
|
31
34
|
);
|
|
35
|
+
const metadataKeys = /* @__PURE__ */ new Set();
|
|
32
36
|
const elementExtends = new MergedExtends();
|
|
33
37
|
const deploymentExtends = new MergedExtends();
|
|
38
|
+
const scanMetadataKeys = (obj) => {
|
|
39
|
+
if (obj?.metadata) {
|
|
40
|
+
keys(obj.metadata).forEach((key) => metadataKeys.add(key));
|
|
41
|
+
}
|
|
42
|
+
};
|
|
34
43
|
const elements = pipe(
|
|
35
44
|
docs,
|
|
36
45
|
flatMap((d) => {
|
|
@@ -48,7 +57,8 @@ export function buildModelData(docs) {
|
|
|
48
57
|
logger.debug`No parent found for ${el.id}`;
|
|
49
58
|
return acc;
|
|
50
59
|
}
|
|
51
|
-
acc[el.id] = elementExtends.
|
|
60
|
+
acc[el.id] = elementExtends.applyExtended(el);
|
|
61
|
+
scanMetadataKeys(acc[el.id]);
|
|
52
62
|
return acc;
|
|
53
63
|
},
|
|
54
64
|
{}
|
|
@@ -59,14 +69,16 @@ export function buildModelData(docs) {
|
|
|
59
69
|
flatMap((d) => map(d.c4Relations, c4Specification.toModelRelation)),
|
|
60
70
|
filter((rel) => {
|
|
61
71
|
if (!rel) return false;
|
|
62
|
-
|
|
72
|
+
const source = c4.FqnRef.flatten(rel.source), target = c4.FqnRef.flatten(rel.target);
|
|
73
|
+
if (isNullish(elements[source]) && !isGlobalFqn(source) || isNullish(elements[target]) && !isGlobalFqn(target)) {
|
|
63
74
|
logger.debug`Invalid relation ${rel.id}
|
|
64
|
-
source: ${
|
|
65
|
-
target: ${
|
|
75
|
+
source: ${source} resolved: ${!!elements[source]}
|
|
76
|
+
target: ${target} resolved: ${!!elements[target]}\n`;
|
|
66
77
|
return false;
|
|
67
78
|
}
|
|
68
79
|
return true;
|
|
69
80
|
}),
|
|
81
|
+
forEach(scanMetadataKeys),
|
|
70
82
|
indexBy(prop("id"))
|
|
71
83
|
);
|
|
72
84
|
const deploymentElements = pipe(
|
|
@@ -86,7 +98,8 @@ export function buildModelData(docs) {
|
|
|
86
98
|
logger.debug`No parent found for deployment element ${el.id}`;
|
|
87
99
|
return acc;
|
|
88
100
|
}
|
|
89
|
-
acc[el.id] =
|
|
101
|
+
acc[el.id] = isDeploymentNode(el) ? deploymentExtends.applyExtended(el) : el;
|
|
102
|
+
scanMetadataKeys(acc[el.id]);
|
|
90
103
|
return acc;
|
|
91
104
|
},
|
|
92
105
|
{}
|
|
@@ -97,10 +110,10 @@ export function buildModelData(docs) {
|
|
|
97
110
|
flatMap((d) => map(d.c4DeploymentRelations, c4Specification.toDeploymentRelation)),
|
|
98
111
|
filter((rel) => {
|
|
99
112
|
if (!rel) return false;
|
|
100
|
-
if (isNullish(deploymentElements[rel.source.
|
|
113
|
+
if (isNullish(deploymentElements[rel.source.deployment]) || isNullish(deploymentElements[rel.target.deployment])) {
|
|
101
114
|
logger.debug`Invalid deployment relation ${rel.id}
|
|
102
|
-
source: ${rel.source.
|
|
103
|
-
target: ${rel.target.
|
|
115
|
+
source: ${rel.source.deployment} resolved: ${!!deploymentElements[rel.source.deployment]}
|
|
116
|
+
target: ${rel.target.deployment} resolved: ${!!deploymentElements[rel.target.deployment]}\n`;
|
|
104
117
|
return false;
|
|
105
118
|
}
|
|
106
119
|
return true;
|
|
@@ -111,6 +124,7 @@ export function buildModelData(docs) {
|
|
|
111
124
|
logger.debug`Duplicate deployment relation ${el.id}`;
|
|
112
125
|
return acc;
|
|
113
126
|
}
|
|
127
|
+
scanMetadataKeys(el);
|
|
114
128
|
acc[el.id] = el;
|
|
115
129
|
return acc;
|
|
116
130
|
},
|
|
@@ -129,7 +143,7 @@ export function buildModelData(docs) {
|
|
|
129
143
|
// model should include discriminant __
|
|
130
144
|
...model
|
|
131
145
|
} = parsedAstView;
|
|
132
|
-
if (parsedAstView.
|
|
146
|
+
if (parsedAstView[c4._type] === "element" && isNullish(title) && "viewOf" in parsedAstView) {
|
|
133
147
|
title = elements[parsedAstView.viewOf]?.title ?? null;
|
|
134
148
|
}
|
|
135
149
|
if (isNullish(title) && id === "index") {
|
|
@@ -137,7 +151,7 @@ export function buildModelData(docs) {
|
|
|
137
151
|
}
|
|
138
152
|
return {
|
|
139
153
|
...model,
|
|
140
|
-
|
|
154
|
+
[c4._stage]: "parsed",
|
|
141
155
|
docUri,
|
|
142
156
|
description,
|
|
143
157
|
title,
|
|
@@ -153,13 +167,13 @@ export function buildModelData(docs) {
|
|
|
153
167
|
);
|
|
154
168
|
if (!parsedViews.some((v) => v.id === "index")) {
|
|
155
169
|
parsedViews.unshift({
|
|
156
|
-
|
|
170
|
+
[c4._stage]: "parsed",
|
|
171
|
+
[c4._type]: "element",
|
|
157
172
|
id: "index",
|
|
158
173
|
title: "Landscape view",
|
|
159
174
|
description: null,
|
|
160
175
|
tags: null,
|
|
161
176
|
links: null,
|
|
162
|
-
customColorDefinitions,
|
|
163
177
|
rules: [
|
|
164
178
|
{
|
|
165
179
|
include: [
|
|
@@ -178,11 +192,19 @@ export function buildModelData(docs) {
|
|
|
178
192
|
);
|
|
179
193
|
return {
|
|
180
194
|
data: {
|
|
195
|
+
[c4._stage]: "parsed",
|
|
196
|
+
projectId,
|
|
181
197
|
specification: {
|
|
182
|
-
tags:
|
|
198
|
+
tags: c4Specification.tags,
|
|
183
199
|
elements: c4Specification.specs.elements,
|
|
184
|
-
relationships: c4Specification.specs.relationships,
|
|
185
|
-
|
|
200
|
+
relationships: mapValues(c4Specification.specs.relationships, ({ notation, technology, ...style }) => ({
|
|
201
|
+
...notation && { notation },
|
|
202
|
+
...technology && { technology },
|
|
203
|
+
style
|
|
204
|
+
})),
|
|
205
|
+
deployments: c4Specification.specs.deployments,
|
|
206
|
+
...metadataKeys.size > 0 && { metadataKeys: [...metadataKeys].sort(c4.compareNatural) },
|
|
207
|
+
customColors
|
|
186
208
|
},
|
|
187
209
|
elements,
|
|
188
210
|
relations,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ancestorsFqn,
|
|
1
|
+
import { ancestorsFqn, Fqn } from "@likec4/core";
|
|
2
2
|
import { MultiMap } from "@likec4/core/utils";
|
|
3
3
|
import { isDefined, isTruthy } from "remeda";
|
|
4
4
|
import {
|
|
@@ -46,7 +46,7 @@ export class DeploymentsIndex extends FqnIndex {
|
|
|
46
46
|
if (!isTruthy(name)) {
|
|
47
47
|
return [];
|
|
48
48
|
}
|
|
49
|
-
thisFqn =
|
|
49
|
+
thisFqn = Fqn(name, parentFqn);
|
|
50
50
|
const desc = createAndSaveDescription(node, name, thisFqn);
|
|
51
51
|
if (!parentFqn) {
|
|
52
52
|
root.push(desc);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type ProjectId, Fqn } from '@likec4/core/types';
|
|
2
2
|
import { DefaultWeakMap, MultiMap } from '@likec4/core/utils';
|
|
3
3
|
import { type Stream, WorkspaceCache } from 'langium';
|
|
4
4
|
import { type AstNodeDescriptionWithFqn, type LikeC4LangiumDocument, ast } from '../ast';
|
package/dist/model/fqn-index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { invariant, nonNullable } from "@likec4/core";
|
|
2
|
-
import {
|
|
2
|
+
import { Fqn } from "@likec4/core/types";
|
|
3
3
|
import { ancestorsFqn, compareNatural, DefaultWeakMap, MultiMap, sortNaturalByFqn } from "@likec4/core/utils";
|
|
4
4
|
import {
|
|
5
5
|
AstUtils,
|
|
@@ -89,7 +89,7 @@ export class FqnIndex extends ADisposable {
|
|
|
89
89
|
});
|
|
90
90
|
return map;
|
|
91
91
|
}, new MultiMap());
|
|
92
|
-
return uniqueByName(allchildren)
|
|
92
|
+
return uniqueByName(allchildren);
|
|
93
93
|
})
|
|
94
94
|
);
|
|
95
95
|
}
|
|
@@ -102,7 +102,7 @@ export class FqnIndex extends ADisposable {
|
|
|
102
102
|
});
|
|
103
103
|
return map;
|
|
104
104
|
}, new MultiMap());
|
|
105
|
-
return uniqueByName(allchildren)
|
|
105
|
+
return uniqueByName(allchildren);
|
|
106
106
|
})
|
|
107
107
|
);
|
|
108
108
|
}
|
|
@@ -125,7 +125,7 @@ export class FqnIndex extends ADisposable {
|
|
|
125
125
|
children: new MultiMap(),
|
|
126
126
|
descendants: new MultiMap()
|
|
127
127
|
});
|
|
128
|
-
const uniqueChildren = uniqueByName(children)
|
|
128
|
+
const uniqueChildren = uniqueByName(children);
|
|
129
129
|
const uniqueDescendants = [...descendants.associations()].flatMap(([_name, descs]) => descs.length === 1 && !children.has(_name) ? descs : []);
|
|
130
130
|
return [
|
|
131
131
|
...uniqueChildren,
|
|
@@ -158,7 +158,7 @@ export class FqnIndex extends ADisposable {
|
|
|
158
158
|
const traverseNode = (el, parentFqn) => {
|
|
159
159
|
let thisFqn;
|
|
160
160
|
if (ast.isElement(el)) {
|
|
161
|
-
thisFqn =
|
|
161
|
+
thisFqn = Fqn(el.name, parentFqn);
|
|
162
162
|
const desc = createAndSaveDescription(el, el.name, thisFqn);
|
|
163
163
|
if (!parentFqn) {
|
|
164
164
|
root.push(desc);
|
|
@@ -211,7 +211,7 @@ export class FqnIndex extends ADisposable {
|
|
|
211
211
|
}
|
|
212
212
|
}
|
|
213
213
|
function uniqueByName(multimap) {
|
|
214
|
-
return [...multimap.associations()].flatMap(([_name, descs]) => descs.length === 1 ? descs : []);
|
|
214
|
+
return [...multimap.associations()].flatMap(([_name, descs]) => descs.length === 1 ? descs : []).sort((a, b) => compareNatural(a.name, b.name));
|
|
215
215
|
}
|
|
216
216
|
export class DocumentFqnIndex {
|
|
217
217
|
constructor(_rootElements, _children, _descendants, _byfqn, projectId) {
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { type ViewId
|
|
1
|
+
import * as c4 from '@likec4/core';
|
|
2
|
+
import { type ViewId } from '@likec4/core';
|
|
3
|
+
import { LikeC4Model } from '@likec4/core/model';
|
|
3
4
|
import { type URI, Disposable } from 'langium';
|
|
4
5
|
import { CancellationToken } from 'vscode-jsonrpc';
|
|
5
6
|
import type { LikeC4Services } from '../module';
|
|
6
7
|
import { ADisposable } from '../utils';
|
|
7
8
|
type ModelParsedListener = (docs: URI[]) => void;
|
|
8
9
|
export interface LikeC4ModelBuilder {
|
|
9
|
-
parseModel(projectId?: c4.ProjectId | undefined, cancelToken?: CancellationToken): Promise<
|
|
10
|
+
parseModel(projectId?: c4.ProjectId | undefined, cancelToken?: CancellationToken): Promise<LikeC4Model.Parsed | null>;
|
|
10
11
|
unsafeSyncBuildModel(projectId: c4.ProjectId): LikeC4Model.Computed;
|
|
11
12
|
buildLikeC4Model(projectId?: c4.ProjectId | undefined, cancelToken?: CancellationToken): Promise<LikeC4Model.Computed>;
|
|
12
13
|
computeView(viewId: ViewId, projectId?: c4.ProjectId | undefined, cancelToken?: CancellationToken): Promise<c4.ComputedView | null>;
|
|
@@ -24,10 +25,15 @@ export declare class DefaultLikeC4ModelBuilder extends ADisposable implements Li
|
|
|
24
25
|
* WARNING:
|
|
25
26
|
* This method is internal and should to be called only when all documents are known to be parsed.
|
|
26
27
|
* Otherwise, the model may be incomplete.
|
|
28
|
+
*
|
|
29
|
+
* To avoid circular dependencies, we do not resolve imports here.
|
|
27
30
|
*/
|
|
28
31
|
private unsafeSyncParseModelData;
|
|
32
|
+
/**
|
|
33
|
+
* To avoid circular dependencies, first we parse all documents and then we join them.
|
|
34
|
+
*/
|
|
29
35
|
private unsafeSyncJoinedModelData;
|
|
30
|
-
parseModel(projectId?: c4.ProjectId | undefined, cancelToken?: CancellationToken): Promise<
|
|
36
|
+
parseModel(projectId?: c4.ProjectId | undefined, cancelToken?: CancellationToken): Promise<LikeC4Model.Parsed | null>;
|
|
31
37
|
private previousViews;
|
|
32
38
|
/**
|
|
33
39
|
* WARNING:
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import * as c4 from "@likec4/core";
|
|
1
2
|
import {
|
|
2
|
-
isScopedElementView
|
|
3
|
-
LikeC4Model
|
|
3
|
+
isScopedElementView
|
|
4
4
|
} from "@likec4/core";
|
|
5
|
+
import { computeView } from "@likec4/core/compute-view";
|
|
6
|
+
import { LikeC4Model } from "@likec4/core/model";
|
|
5
7
|
import { loggable } from "@likec4/log";
|
|
6
8
|
import { deepEqual as eq } from "fast-equals";
|
|
7
9
|
import {
|
|
@@ -69,6 +71,8 @@ export class DefaultLikeC4ModelBuilder extends ADisposable {
|
|
|
69
71
|
* WARNING:
|
|
70
72
|
* This method is internal and should to be called only when all documents are known to be parsed.
|
|
71
73
|
* Otherwise, the model may be incomplete.
|
|
74
|
+
*
|
|
75
|
+
* To avoid circular dependencies, we do not resolve imports here.
|
|
72
76
|
*/
|
|
73
77
|
unsafeSyncParseModelData(projectId) {
|
|
74
78
|
const cache = this.cache;
|
|
@@ -85,13 +89,16 @@ export class DefaultLikeC4ModelBuilder extends ADisposable {
|
|
|
85
89
|
return null;
|
|
86
90
|
}
|
|
87
91
|
log.debug`unsafeSyncBuildModelData, project ${projectId}`;
|
|
88
|
-
return buildModelData(docs);
|
|
92
|
+
return buildModelData(projectId, docs);
|
|
89
93
|
} catch (e) {
|
|
90
94
|
logWarnError(e);
|
|
91
95
|
return null;
|
|
92
96
|
}
|
|
93
97
|
});
|
|
94
98
|
}
|
|
99
|
+
/**
|
|
100
|
+
* To avoid circular dependencies, first we parse all documents and then we join them.
|
|
101
|
+
*/
|
|
95
102
|
unsafeSyncJoinedModelData(projectId) {
|
|
96
103
|
const cache = this.cache;
|
|
97
104
|
const key = parsedModelCacheKey(projectId);
|
|
@@ -100,6 +107,7 @@ export class DefaultLikeC4ModelBuilder extends ADisposable {
|
|
|
100
107
|
if (!result) {
|
|
101
108
|
return null;
|
|
102
109
|
}
|
|
110
|
+
let parsedData = result.data;
|
|
103
111
|
if (result.imports.size > 0) {
|
|
104
112
|
logger.debug`processing imports of ${projectId}`;
|
|
105
113
|
const imports = [...result.imports.associations()].reduce((acc, [projectId2, fqns]) => {
|
|
@@ -115,12 +123,12 @@ export class DefaultLikeC4ModelBuilder extends ADisposable {
|
|
|
115
123
|
}
|
|
116
124
|
return acc;
|
|
117
125
|
}, {});
|
|
118
|
-
|
|
126
|
+
parsedData = {
|
|
119
127
|
...result.data,
|
|
120
128
|
imports
|
|
121
129
|
};
|
|
122
130
|
}
|
|
123
|
-
return
|
|
131
|
+
return LikeC4Model.create(parsedData);
|
|
124
132
|
});
|
|
125
133
|
}
|
|
126
134
|
async parseModel(projectId, cancelToken = CancellationToken.None) {
|
|
@@ -150,18 +158,13 @@ export class DefaultLikeC4ModelBuilder extends ADisposable {
|
|
|
150
158
|
const cache = this.cache;
|
|
151
159
|
const viewsCache = this.cache;
|
|
152
160
|
return cache.get(computedModelCacheKey(projectId), () => {
|
|
153
|
-
const
|
|
154
|
-
if (!
|
|
161
|
+
const parsedModel = this.unsafeSyncJoinedModelData(projectId);
|
|
162
|
+
if (!parsedModel) {
|
|
155
163
|
return LikeC4Model.EMPTY;
|
|
156
164
|
}
|
|
157
|
-
const {
|
|
158
|
-
views: parsedViews,
|
|
159
|
-
...model
|
|
160
|
-
} = parsed;
|
|
161
|
-
const computeView = LikeC4Model.makeCompute(parsed);
|
|
162
165
|
const allViews = [];
|
|
163
|
-
for (const view of values(
|
|
164
|
-
const result = computeView(view);
|
|
166
|
+
for (const view of values(parsedModel.$data.views)) {
|
|
167
|
+
const result = computeView(view, parsedModel);
|
|
165
168
|
if (!result.isSuccess) {
|
|
166
169
|
logger.warn(loggable(result.error));
|
|
167
170
|
continue;
|
|
@@ -178,7 +181,8 @@ export class DefaultLikeC4ModelBuilder extends ADisposable {
|
|
|
178
181
|
});
|
|
179
182
|
this.previousViews = { ...this.previousViews, ...views };
|
|
180
183
|
return LikeC4Model.create({
|
|
181
|
-
...
|
|
184
|
+
...parsedModel.$data,
|
|
185
|
+
[c4._stage]: "computed",
|
|
182
186
|
views
|
|
183
187
|
});
|
|
184
188
|
});
|
|
@@ -213,21 +217,20 @@ export class DefaultLikeC4ModelBuilder extends ADisposable {
|
|
|
213
217
|
return null;
|
|
214
218
|
}
|
|
215
219
|
return cache.get(cacheKey, () => {
|
|
216
|
-
const view = parsed.views[viewId];
|
|
220
|
+
const view = parsed.$data.views[viewId];
|
|
217
221
|
if (!view) {
|
|
218
222
|
log.warn`computeView: cant find view ${viewId}`;
|
|
219
223
|
return null;
|
|
220
224
|
}
|
|
221
225
|
log.debug`computeView: ${viewId}`;
|
|
222
|
-
const
|
|
223
|
-
const result = computeView(view);
|
|
226
|
+
const result = computeView(view, parsed);
|
|
224
227
|
if (!result.isSuccess) {
|
|
225
228
|
logWarnError(result.error);
|
|
226
229
|
return null;
|
|
227
230
|
}
|
|
228
231
|
let computedView = result.view;
|
|
229
232
|
const allElementViews = pipe(
|
|
230
|
-
parsed.views,
|
|
233
|
+
parsed.$data.views,
|
|
231
234
|
values(),
|
|
232
235
|
filter(isScopedElementView),
|
|
233
236
|
filter((v) => v.id !== viewId),
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type * as c4 from '@likec4/core';
|
|
2
|
-
import type {
|
|
2
|
+
import type { Cancellation } from 'langium';
|
|
3
|
+
import type { Location, Range } from 'vscode-languageserver-types';
|
|
4
|
+
import { URI } from 'vscode-uri';
|
|
3
5
|
import type { ParsedAstElement, ParsedAstView, ParsedLikeC4LangiumDocument } from '../ast';
|
|
4
6
|
import { ast } from '../ast';
|
|
5
7
|
import type { LikeC4Services } from '../module';
|
|
@@ -14,7 +16,7 @@ export declare class LikeC4ModelLocator {
|
|
|
14
16
|
private documents;
|
|
15
17
|
getParsedElement(...args: [ast.Element] | [c4.Fqn] | [c4.Fqn, c4.ProjectId]): ParsedAstElement | null;
|
|
16
18
|
locateElement(fqn: c4.Fqn, projectId?: c4.ProjectId | undefined): Location | null;
|
|
17
|
-
locateDeploymentElement(
|
|
19
|
+
locateDeploymentElement(deploymentFqn: c4.DeploymentFqn, projectId?: c4.ProjectId | undefined): Location | null;
|
|
18
20
|
locateRelation(relationId: c4.RelationId, projectId?: c4.ProjectId): Location | null;
|
|
19
21
|
locateViewAst(viewId: c4.ViewId, projectId?: c4.ProjectId | undefined): null | {
|
|
20
22
|
doc: ParsedLikeC4LangiumDocument;
|
|
@@ -22,4 +24,10 @@ export declare class LikeC4ModelLocator {
|
|
|
22
24
|
viewAst: ast.LikeC4View;
|
|
23
25
|
};
|
|
24
26
|
locateView(viewId: c4.ViewId, projectId?: c4.ProjectId): Location | null;
|
|
27
|
+
locateDocumentTags(documentUri: URI, cancelToken?: Cancellation.CancellationToken): Promise<Array<{
|
|
28
|
+
name: string;
|
|
29
|
+
color: string;
|
|
30
|
+
range: Range;
|
|
31
|
+
isSpecification: boolean;
|
|
32
|
+
}>>;
|
|
25
33
|
}
|
|
@@ -1,10 +1,14 @@
|
|
|
1
|
-
import { splitGlobalFqn } from "@likec4/core";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
1
|
+
import { ifilter, invariant, splitGlobalFqn, toArray } from "@likec4/core";
|
|
2
|
+
import { loggable } from "@likec4/log";
|
|
3
|
+
import { AstUtils, DocumentState, GrammarUtils } from "langium";
|
|
4
|
+
import { flatMap, isString, pipe } from "remeda";
|
|
5
|
+
import { ast, isLikeC4LangiumDocument } from "../ast.js";
|
|
6
|
+
import { logger as serverLogger } from "../logger.js";
|
|
5
7
|
import { projectIdFrom } from "../utils/index.js";
|
|
8
|
+
import { MergedSpecification } from "./builder/MergedSpecification.js";
|
|
6
9
|
const { findNodeForKeyword, findNodeForProperty } = GrammarUtils;
|
|
7
|
-
const { getDocument } = AstUtils;
|
|
10
|
+
const { getDocument, streamAllContents } = AstUtils;
|
|
11
|
+
const logger = serverLogger.getChild("ModelLocator");
|
|
8
12
|
export class LikeC4ModelLocator {
|
|
9
13
|
constructor(services) {
|
|
10
14
|
this.services = services;
|
|
@@ -63,10 +67,10 @@ export class LikeC4ModelLocator {
|
|
|
63
67
|
range: docsegment.range
|
|
64
68
|
};
|
|
65
69
|
}
|
|
66
|
-
locateDeploymentElement(
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
const entry = this.deploymentsIndex.byFqn(_projectId,
|
|
70
|
+
locateDeploymentElement(deploymentFqn, projectId) {
|
|
71
|
+
const _projectId = this.projects.ensureProjectId(projectId);
|
|
72
|
+
const fqn = deploymentFqn;
|
|
73
|
+
const entry = this.deploymentsIndex.byFqn(_projectId, fqn).head();
|
|
70
74
|
const docsegment = entry?.nameSegment ?? entry?.selectionSegment;
|
|
71
75
|
if (!entry || !docsegment) {
|
|
72
76
|
return null;
|
|
@@ -92,6 +96,7 @@ export class LikeC4ModelLocator {
|
|
|
92
96
|
}
|
|
93
97
|
let targetNode = node.title ? findNodeForProperty(node.$cstNode, "title") : void 0;
|
|
94
98
|
targetNode ??= node.kind ? findNodeForProperty(node.$cstNode, "kind") : void 0;
|
|
99
|
+
targetNode ??= findNodeForKeyword(node.$cstNode, "->");
|
|
95
100
|
targetNode ??= findNodeForProperty(node.$cstNode, "target");
|
|
96
101
|
targetNode ??= node.$cstNode;
|
|
97
102
|
if (!targetNode) {
|
|
@@ -142,4 +147,55 @@ export class LikeC4ModelLocator {
|
|
|
142
147
|
range: targetNode.range
|
|
143
148
|
};
|
|
144
149
|
}
|
|
150
|
+
async locateDocumentTags(documentUri, cancelToken) {
|
|
151
|
+
const doc = this.langiumDocuments.getDocument(documentUri);
|
|
152
|
+
if (!doc || !isLikeC4LangiumDocument(doc)) {
|
|
153
|
+
return [];
|
|
154
|
+
}
|
|
155
|
+
if (doc.state < DocumentState.Validated) {
|
|
156
|
+
logger.debug(`Waiting for document ${doc.uri.path} to be Validated`);
|
|
157
|
+
await this.services.shared.workspace.DocumentBuilder.waitUntil(DocumentState.Validated, doc.uri, cancelToken);
|
|
158
|
+
}
|
|
159
|
+
const projectId = projectIdFrom(doc);
|
|
160
|
+
logger.debug(`locate document tags for ${doc.uri.path} in project ${projectId}`);
|
|
161
|
+
try {
|
|
162
|
+
const tagSpecs = new MergedSpecification(this.documents(projectId).toArray()).tags;
|
|
163
|
+
logger.debug(`Assigned colors to tags`, { tagSpecs });
|
|
164
|
+
const tags = pipe(
|
|
165
|
+
streamAllContents(doc.parseResult.value),
|
|
166
|
+
ifilter((astNode) => ast.isTag(astNode) || ast.isTagRef(astNode)),
|
|
167
|
+
toArray(),
|
|
168
|
+
flatMap((tagRef) => {
|
|
169
|
+
let name;
|
|
170
|
+
let $cstNode;
|
|
171
|
+
try {
|
|
172
|
+
if (ast.isTag(tagRef)) {
|
|
173
|
+
name = tagRef.name;
|
|
174
|
+
$cstNode = tagRef.$cstNode;
|
|
175
|
+
} else {
|
|
176
|
+
name = tagRef.tag.$refText;
|
|
177
|
+
$cstNode = tagRef.tag.$refNode;
|
|
178
|
+
}
|
|
179
|
+
const specification = tagSpecs[name];
|
|
180
|
+
invariant(specification, `Tag ${name} not found in merged specification`);
|
|
181
|
+
invariant($cstNode, `Tag ${name} does not have a $cstNode`);
|
|
182
|
+
return {
|
|
183
|
+
name,
|
|
184
|
+
color: specification.color,
|
|
185
|
+
range: $cstNode.range,
|
|
186
|
+
isSpecification: ast.isTag(tagRef)
|
|
187
|
+
};
|
|
188
|
+
} catch (err) {
|
|
189
|
+
logger.warn(`Fail on tag ${name}`, { err });
|
|
190
|
+
return [];
|
|
191
|
+
}
|
|
192
|
+
})
|
|
193
|
+
);
|
|
194
|
+
logger.debug(`Found ${tags.length} tags in document ${doc.uri.path}`);
|
|
195
|
+
return tags;
|
|
196
|
+
} catch (e) {
|
|
197
|
+
logger.warn(loggable(e));
|
|
198
|
+
return [];
|
|
199
|
+
}
|
|
200
|
+
}
|
|
145
201
|
}
|
|
@@ -23,7 +23,7 @@ function parseParticipant(astNode) {
|
|
|
23
23
|
export function parseWhereClause(astNode) {
|
|
24
24
|
switch (true) {
|
|
25
25
|
case ast.isWhereTagEqual(astNode): {
|
|
26
|
-
const tag = astNode.value
|
|
26
|
+
const tag = astNode.value.tag.ref?.name;
|
|
27
27
|
const participant = parseParticipant(astNode);
|
|
28
28
|
invariant(tag, "Expected tag name");
|
|
29
29
|
const tagOperator = { tag: parseEquals(astNode, tag) };
|
|
@@ -12,16 +12,15 @@ declare const DocumentParserFromMixins: {
|
|
|
12
12
|
parseGlobalDynamicPredicateGroup(astRule: import("../generated/ast").GlobalDynamicPredicateGroup): ProjectId[];
|
|
13
13
|
parseGlobalStyleOrGroup(astRule: import("../generated/ast").GlobalStyle | import("../generated/ast").GlobalStyleGroup): ProjectId[];
|
|
14
14
|
parseViews(): void;
|
|
15
|
-
parseElementView(astNode: import("../generated/ast").ElementView, additionalStyles:
|
|
16
|
-
|
|
15
|
+
parseElementView(astNode: import("../generated/ast").ElementView, additionalStyles: any[]): import("../ast").ParsedAstElementView;
|
|
16
|
+
parseElementViewRule(astRule: import("../generated/ast").ViewRule): ProjectId;
|
|
17
17
|
parseViewRulePredicate(astNode: import("../generated/ast").ViewRulePredicate): ProjectId;
|
|
18
18
|
parseViewRuleGlobalPredicateRef(astRule: import("../generated/ast").ViewRuleGlobalPredicateRef | import("../generated/ast").DynamicViewGlobalPredicateRef): ProjectId;
|
|
19
|
-
parseViewRuleStyleOrGlobalRef(astRule: import("../generated/ast").ViewRuleStyleOrGlobalRef):
|
|
19
|
+
parseViewRuleStyleOrGlobalRef(astRule: import("../generated/ast").ViewRuleStyleOrGlobalRef): any;
|
|
20
20
|
parseViewRuleGroup(astNode: import("../generated/ast").ViewRuleGroup): ProjectId;
|
|
21
21
|
parseViewRuleStyle(astRule: import("../generated/ast").ViewRuleStyle | import("../generated/ast").GlobalStyle): ProjectId;
|
|
22
|
-
parseRuleStyle(styleProperties: import("../generated/ast").StyleProperty[], elementExpressionsIterator: import("../generated/ast").FqnExpressions, notationProperty?: import("../generated/ast").NotationProperty): ProjectId;
|
|
23
22
|
parseViewRuleGlobalStyle(astRule: import("../generated/ast").ViewRuleGlobalStyle): ProjectId;
|
|
24
|
-
parseDynamicElementView(astNode: import("../generated/ast").DynamicView, additionalStyles:
|
|
23
|
+
parseDynamicElementView(astNode: import("../generated/ast").DynamicView, additionalStyles: any[]): import("../ast").ParsedAstDynamicView;
|
|
25
24
|
parseDynamicViewRule(astRule: import("../generated/ast").DynamicViewRule): ProjectId;
|
|
26
25
|
parseDynamicViewIncludePredicate(astRule: import("../generated/ast").DynamicViewIncludePredicate): ProjectId;
|
|
27
26
|
parseDynamicParallelSteps(node: import("../generated/ast").DynamicViewParallelSteps): ProjectId;
|
|
@@ -89,16 +88,15 @@ declare const DocumentParserFromMixins: {
|
|
|
89
88
|
} & {
|
|
90
89
|
new (...args: any[]): {
|
|
91
90
|
parseViews(): void;
|
|
92
|
-
parseElementView(astNode: import("../generated/ast").ElementView, additionalStyles:
|
|
93
|
-
|
|
91
|
+
parseElementView(astNode: import("../generated/ast").ElementView, additionalStyles: any[]): import("../ast").ParsedAstElementView;
|
|
92
|
+
parseElementViewRule(astRule: import("../generated/ast").ViewRule): ProjectId;
|
|
94
93
|
parseViewRulePredicate(astNode: import("../generated/ast").ViewRulePredicate): ProjectId;
|
|
95
94
|
parseViewRuleGlobalPredicateRef(astRule: import("../generated/ast").ViewRuleGlobalPredicateRef | import("../generated/ast").DynamicViewGlobalPredicateRef): ProjectId;
|
|
96
|
-
parseViewRuleStyleOrGlobalRef(astRule: import("../generated/ast").ViewRuleStyleOrGlobalRef):
|
|
95
|
+
parseViewRuleStyleOrGlobalRef(astRule: import("../generated/ast").ViewRuleStyleOrGlobalRef): any;
|
|
97
96
|
parseViewRuleGroup(astNode: import("../generated/ast").ViewRuleGroup): ProjectId;
|
|
98
97
|
parseViewRuleStyle(astRule: import("../generated/ast").ViewRuleStyle | import("../generated/ast").GlobalStyle): ProjectId;
|
|
99
|
-
parseRuleStyle(styleProperties: import("../generated/ast").StyleProperty[], elementExpressionsIterator: import("../generated/ast").FqnExpressions, notationProperty?: import("../generated/ast").NotationProperty): ProjectId;
|
|
100
98
|
parseViewRuleGlobalStyle(astRule: import("../generated/ast").ViewRuleGlobalStyle): ProjectId;
|
|
101
|
-
parseDynamicElementView(astNode: import("../generated/ast").DynamicView, additionalStyles:
|
|
99
|
+
parseDynamicElementView(astNode: import("../generated/ast").DynamicView, additionalStyles: any[]): import("../ast").ParsedAstDynamicView;
|
|
102
100
|
parseDynamicViewRule(astRule: import("../generated/ast").DynamicViewRule): ProjectId;
|
|
103
101
|
parseDynamicViewIncludePredicate(astRule: import("../generated/ast").DynamicViewIncludePredicate): ProjectId;
|
|
104
102
|
parseDynamicParallelSteps(node: import("../generated/ast").DynamicViewParallelSteps): ProjectId;
|
|
@@ -166,7 +164,8 @@ declare const DocumentParserFromMixins: {
|
|
|
166
164
|
} & {
|
|
167
165
|
new (...args: any[]): {
|
|
168
166
|
parseSpecification(): void;
|
|
169
|
-
|
|
167
|
+
parseElementSpecificationNode(specAst: import("../generated/ast").SpecificationElementKind): {};
|
|
168
|
+
parseElementSpecificationNode(specAst: import("../generated/ast").SpecificationDeploymentNodeKind): {};
|
|
170
169
|
isValid: import("../validation").IsValidFn;
|
|
171
170
|
readonly services: LikeC4Services;
|
|
172
171
|
readonly doc: ParsedLikeC4LangiumDocument;
|
|
@@ -77,7 +77,7 @@ export class LikeC4ModelParser {
|
|
|
77
77
|
createParser(doc) {
|
|
78
78
|
const props = {
|
|
79
79
|
c4Specification: {
|
|
80
|
-
tags:
|
|
80
|
+
tags: {},
|
|
81
81
|
elements: {},
|
|
82
82
|
relationships: {},
|
|
83
83
|
colors: {},
|
|
@@ -99,12 +99,16 @@ export class LikeC4ModelParser {
|
|
|
99
99
|
};
|
|
100
100
|
doc = Object.assign(doc, props);
|
|
101
101
|
const parser = new DocumentParser(this.services, doc);
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
102
|
+
try {
|
|
103
|
+
parser.parseSpecification();
|
|
104
|
+
parser.parseImports();
|
|
105
|
+
parser.parseModel();
|
|
106
|
+
parser.parseGlobals();
|
|
107
|
+
parser.parseDeployment();
|
|
108
|
+
parser.parseViews();
|
|
109
|
+
} catch (e) {
|
|
110
|
+
logger.error(`Error parsing document ${doc.uri.toString()}`, { cause: e });
|
|
111
|
+
}
|
|
108
112
|
return parser;
|
|
109
113
|
}
|
|
110
114
|
}
|
|
@@ -86,7 +86,7 @@ export class BaseParser {
|
|
|
86
86
|
while (iter) {
|
|
87
87
|
try {
|
|
88
88
|
if (this.isValid(iter)) {
|
|
89
|
-
const values = iter.values.map((t) => t.ref?.name).filter(isTruthy);
|
|
89
|
+
const values = iter.values.map((t) => t.tag.ref?.name).filter(isTruthy);
|
|
90
90
|
if (values.length > 0) {
|
|
91
91
|
tags.push(...values);
|
|
92
92
|
}
|
|
@@ -95,8 +95,7 @@ export class BaseParser {
|
|
|
95
95
|
}
|
|
96
96
|
iter = iter.prev;
|
|
97
97
|
}
|
|
98
|
-
tags
|
|
99
|
-
return isNonEmptyArray(tags) ? tags : null;
|
|
98
|
+
return isNonEmptyArray(tags) ? unique(tags) : null;
|
|
100
99
|
}
|
|
101
100
|
convertLinks(source) {
|
|
102
101
|
return this.parseLinks(source);
|
|
@@ -12,7 +12,7 @@ export declare function DeploymentModelParser<TBase extends WithExpressionV2>(B:
|
|
|
12
12
|
_resolveDeploymentRelationSource(node: ast.DeploymentRelation): FqnRef;
|
|
13
13
|
parseDeploymentRelation(astNode: ast.DeploymentRelation): ParsedAstDeploymentRelation;
|
|
14
14
|
parseFqnRef(astNode: ast.FqnRef): c4.FqnRef;
|
|
15
|
-
parseExpressionV2(astNode: ast.ExpressionV2): c4.
|
|
15
|
+
parseExpressionV2(astNode: ast.ExpressionV2): c4.Expression;
|
|
16
16
|
parseFqnExprOrWith(astNode: ast.FqnExprOrWith): c4.FqnExpr.Any;
|
|
17
17
|
parseFqnExprWith(astNode: ast.FqnExprWith): c4.FqnExpr.Custom;
|
|
18
18
|
parseFqnExprOrWhere(astNode: ast.FqnExprOrWhere): c4.FqnExpr.OrWhere;
|