@likec4/language-server 0.33.1 → 0.35.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.monarch.ts +17 -3
- package/dist/ast.d.ts +24 -11
- package/dist/ast.js +12 -9
- package/dist/elementRef.d.ts +1 -1
- package/dist/elementRef.js +2 -3
- package/dist/generated/ast.d.ts +29 -10
- package/dist/generated/ast.js +20 -1
- package/dist/generated/grammar.d.ts +1 -1
- package/dist/generated/grammar.js +11 -11
- package/dist/generated/module.d.ts +7 -3
- package/dist/generated/module.js +3 -3
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/logger.d.ts +6 -4
- package/dist/logger.js +34 -6
- package/dist/{shared → lsp}/CodeLensProvider.d.ts +3 -3
- package/dist/{shared → lsp}/DocumentLinkProvider.d.ts +3 -2
- package/dist/{shared → lsp}/DocumentLinkProvider.js +3 -3
- package/dist/lsp/DocumentSymbolProvider.js +3 -3
- package/dist/lsp/index.d.ts +2 -0
- package/dist/lsp/index.js +2 -0
- package/dist/model/fqn-computation.js +6 -3
- package/dist/model/fqn-index.d.ts +4 -7
- package/dist/model/fqn-index.js +51 -21
- package/dist/model/index.d.ts +1 -0
- package/dist/model/index.js +1 -0
- package/dist/model/model-builder.d.ts +1 -18
- package/dist/model/model-builder.js +111 -340
- package/dist/model/model-locator.js +19 -24
- package/dist/model/model-parser.d.ts +27 -0
- package/dist/model/model-parser.js +293 -0
- package/dist/module.d.ts +2 -1
- package/dist/module.js +20 -29
- package/dist/protocol.d.ts +8 -16
- package/dist/protocol.js +2 -6
- package/dist/references/scope-computation.js +11 -5
- package/dist/references/scope-provider.js +10 -4
- package/dist/registerProtocolHandlers.js +33 -17
- package/dist/shared/index.d.ts +0 -2
- package/dist/shared/index.js +0 -2
- package/dist/test/testServices.d.ts +2 -2
- package/dist/test/testServices.js +16 -10
- package/dist/utils.d.ts +2 -2
- package/dist/utils.js +2 -7
- package/dist/validation/element.d.ts +1 -1
- package/dist/validation/element.js +23 -3
- package/dist/validation/index.js +26 -0
- package/dist/validation/relation.d.ts +1 -1
- package/dist/validation/relation.js +32 -26
- package/package.json +10 -8
- /package/dist/{shared → lsp}/CodeLensProvider.js +0 -0
|
@@ -1,71 +1,20 @@
|
|
|
1
|
-
import { ModelIndex, assignNavigateTo, compareByFqnHierarchically, computeView,
|
|
2
|
-
import { DocumentState, getDocument } from 'langium';
|
|
3
|
-
import objectHash from 'object-hash';
|
|
1
|
+
import { ModelIndex, assignNavigateTo, compareByFqnHierarchically, computeView, parentFqn } from '@likec4/core';
|
|
4
2
|
import { clone } from 'rambdax';
|
|
5
3
|
import * as R from 'remeda';
|
|
6
|
-
import
|
|
7
|
-
import {
|
|
8
|
-
import { elementRef, strictElementRefFqn } from '../elementRef';
|
|
9
|
-
import { logger } from '../logger';
|
|
4
|
+
import { isValidLikeC4LangiumDocument } from '../ast';
|
|
5
|
+
import { logError, logWarnError, logger } from '../logger';
|
|
10
6
|
import { Rpc } from '../protocol';
|
|
11
|
-
import {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
let countOfChangedDocs = 0;
|
|
23
|
-
for (const doc of docs) {
|
|
24
|
-
if (!isLikeC4LangiumDocument(doc)) {
|
|
25
|
-
continue;
|
|
26
|
-
}
|
|
27
|
-
countOfChangedDocs++;
|
|
28
|
-
try {
|
|
29
|
-
this.parseDocument(doc);
|
|
30
|
-
}
|
|
31
|
-
catch (e) {
|
|
32
|
-
logger.error(`Error parsing document ${doc.uri.toString()}`);
|
|
33
|
-
logger.error(e);
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
if (countOfChangedDocs > 0) {
|
|
37
|
-
this.cleanCache();
|
|
38
|
-
this.notifyClient(cancelToken);
|
|
39
|
-
}
|
|
40
|
-
});
|
|
41
|
-
}
|
|
42
|
-
cleanCache() {
|
|
43
|
-
delete this.cachedModel.last;
|
|
44
|
-
}
|
|
45
|
-
documents() {
|
|
46
|
-
return this.langiumDocuments.all.filter(isValidLikeC4LangiumDocument).toArray();
|
|
47
|
-
}
|
|
48
|
-
buildModel() {
|
|
49
|
-
if ('last' in this.cachedModel) {
|
|
50
|
-
logger.debug('returning cached model');
|
|
51
|
-
return this.cachedModel.last;
|
|
52
|
-
}
|
|
53
|
-
return (this.cachedModel.last = this._buildModel());
|
|
54
|
-
}
|
|
55
|
-
_buildModel() {
|
|
56
|
-
logger.debug('_buildModel');
|
|
57
|
-
const docs = this.documents();
|
|
58
|
-
if (docs.length === 0) {
|
|
59
|
-
logger.debug('No documents to build model from');
|
|
60
|
-
return null;
|
|
61
|
-
}
|
|
62
|
-
// TODO:
|
|
63
|
-
try {
|
|
64
|
-
const c4Specification = {
|
|
65
|
-
kinds: {}
|
|
66
|
-
};
|
|
67
|
-
R.forEach(R.map(docs, R.prop('c4Specification')), spec => Object.assign(c4Specification.kinds, spec.kinds));
|
|
68
|
-
const toModelElement = ({ astPath, tags, links, ...parsed }) => {
|
|
7
|
+
import { printDocs } from '../utils';
|
|
8
|
+
function buildModel(docs) {
|
|
9
|
+
const c4Specification = {
|
|
10
|
+
kinds: {}
|
|
11
|
+
};
|
|
12
|
+
R.forEach(R.map(docs, R.prop('c4Specification')), spec => Object.assign(c4Specification.kinds, spec.kinds));
|
|
13
|
+
const toModelElement = (doc) => {
|
|
14
|
+
const base = new URL(doc.uri.toString());
|
|
15
|
+
const resolveLinks = (links) => links.map((l) => new URL(l, base).toString());
|
|
16
|
+
return ({ astPath, tags, links, ...parsed }) => {
|
|
17
|
+
try {
|
|
69
18
|
const kind = c4Specification.kinds[parsed.kind];
|
|
70
19
|
if (kind) {
|
|
71
20
|
return {
|
|
@@ -73,310 +22,132 @@ export class LikeC4ModelBuilder {
|
|
|
73
22
|
description: null,
|
|
74
23
|
technology: null,
|
|
75
24
|
tags: tags ?? null,
|
|
76
|
-
links: links
|
|
25
|
+
links: links ? resolveLinks(links) : null,
|
|
77
26
|
...parsed
|
|
78
27
|
};
|
|
79
28
|
}
|
|
80
|
-
return null;
|
|
81
|
-
};
|
|
82
|
-
const elements = R.pipe(R.flatMap(docs, d => d.c4Elements), R.map(toModelElement), R.compact, R.sort(compareByFqnHierarchically), R.reduce((acc, el) => {
|
|
83
|
-
const parent = parentFqn(el.id);
|
|
84
|
-
if (parent && R.isNil(acc[parent])) {
|
|
85
|
-
logger.warn(`No parent found for ${el.id}`);
|
|
86
|
-
return acc;
|
|
87
|
-
}
|
|
88
|
-
if (el.id in acc) {
|
|
89
|
-
// should not happen, as validated
|
|
90
|
-
logger.warn(`Duplicate element id: ${el.id}`);
|
|
91
|
-
return acc;
|
|
92
|
-
}
|
|
93
|
-
acc[el.id] = el;
|
|
94
|
-
return acc;
|
|
95
|
-
}, {}));
|
|
96
|
-
const toModelRelation = ({ astPath, source, target, ...model }) => {
|
|
97
|
-
if (source in elements && target in elements) {
|
|
98
|
-
return {
|
|
99
|
-
source,
|
|
100
|
-
target,
|
|
101
|
-
...model
|
|
102
|
-
};
|
|
103
|
-
}
|
|
104
|
-
return null;
|
|
105
|
-
};
|
|
106
|
-
const relations = R.pipe(R.flatMap(docs, d => d.c4Relations), R.map(toModelRelation), R.compact, R.mapToObj(r => [r.id, r]));
|
|
107
|
-
const modelIndex = ModelIndex.from({ elements, relations });
|
|
108
|
-
const toModelView = (view) => {
|
|
109
|
-
// eslint-disable-next-line prefer-const
|
|
110
|
-
let { astPath, rules, title, description, tags, links, ...model } = view;
|
|
111
|
-
if (!title && view.viewOf) {
|
|
112
|
-
title = elements[view.viewOf]?.title;
|
|
113
|
-
}
|
|
114
|
-
if (!title && view.id === 'index') {
|
|
115
|
-
title = 'Landscape view';
|
|
116
|
-
}
|
|
117
|
-
return computeView({
|
|
118
|
-
...model,
|
|
119
|
-
title: title ?? null,
|
|
120
|
-
description: description ?? null,
|
|
121
|
-
tags: tags ?? null,
|
|
122
|
-
links: links ?? null,
|
|
123
|
-
rules: clone(rules)
|
|
124
|
-
}, modelIndex);
|
|
125
|
-
};
|
|
126
|
-
const views = R.pipe(R.flatMap(docs, d => d.c4Views), R.map(toModelView), R.compact);
|
|
127
|
-
assignNavigateTo(views);
|
|
128
|
-
return {
|
|
129
|
-
elements,
|
|
130
|
-
relations,
|
|
131
|
-
views: R.mapToObj(views, v => [v.id, v])
|
|
132
|
-
};
|
|
133
|
-
}
|
|
134
|
-
catch (e) {
|
|
135
|
-
logger.error(e);
|
|
136
|
-
return null;
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
/**
|
|
140
|
-
* @returns if the document was changed
|
|
141
|
-
*/
|
|
142
|
-
parseDocument(doc) {
|
|
143
|
-
const { elements, relations, views, specification } = cleanParsedModel(doc);
|
|
144
|
-
const specs = doc.parseResult.value.specification?.specs.filter(ast.isSpecificationElementKind);
|
|
145
|
-
if (specs) {
|
|
146
|
-
for (const { kind, style } of specs) {
|
|
147
|
-
try {
|
|
148
|
-
specification.kinds[kind.name] = toElementStyleExcludeDefaults(style?.props);
|
|
149
|
-
}
|
|
150
|
-
catch (e) {
|
|
151
|
-
logger.warn(e);
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
for (const el of streamModel(doc)) {
|
|
156
|
-
if (ast.isElement(el)) {
|
|
157
|
-
try {
|
|
158
|
-
elements.push(this.parseElement(el));
|
|
159
|
-
}
|
|
160
|
-
catch (e) {
|
|
161
|
-
logger.warn(e);
|
|
162
|
-
}
|
|
163
|
-
continue;
|
|
164
|
-
}
|
|
165
|
-
if (ast.isRelation(el)) {
|
|
166
|
-
try {
|
|
167
|
-
relations.push(this.parseRelation(el));
|
|
168
|
-
}
|
|
169
|
-
catch (e) {
|
|
170
|
-
logger.warn(e);
|
|
171
|
-
}
|
|
172
|
-
continue;
|
|
173
29
|
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
const docviews = doc.parseResult.value.views?.views;
|
|
177
|
-
if (docviews) {
|
|
178
|
-
for (const view of docviews) {
|
|
179
|
-
try {
|
|
180
|
-
const v = this.parseElementView(view);
|
|
181
|
-
ElementViewOps.writeId(view, v.id);
|
|
182
|
-
views.push(v);
|
|
183
|
-
}
|
|
184
|
-
catch (e) {
|
|
185
|
-
logger.warn(e);
|
|
186
|
-
}
|
|
30
|
+
catch (e) {
|
|
31
|
+
logWarnError(e);
|
|
187
32
|
}
|
|
188
|
-
|
|
189
|
-
// const prevHash = doc.c4hash ?? ''
|
|
190
|
-
// doc.c4hash = c4hash(doc)
|
|
191
|
-
// return prevHash !== doc.c4hash
|
|
192
|
-
}
|
|
193
|
-
parseElement(astNode) {
|
|
194
|
-
const id = this.resolveFqn(astNode);
|
|
195
|
-
invariant(astNode.kind.ref, 'Element kind is not resolved: ' + astNode.name);
|
|
196
|
-
const kind = astNode.kind.ref.name;
|
|
197
|
-
const tags = this.convertTags(astNode.body);
|
|
198
|
-
const stylePropsAst = astNode.body?.props.find(ast.isStyleProperties)?.props;
|
|
199
|
-
const styleProps = toElementStyleExcludeDefaults(stylePropsAst);
|
|
200
|
-
const astPath = this.getAstNodePath(astNode);
|
|
201
|
-
let [title, description, technology] = astNode.props;
|
|
202
|
-
const bodyProps = astNode.body?.props.filter((p) => ast.isElementStringProperty(p)) ?? [];
|
|
203
|
-
title = title ?? bodyProps.find(p => p.key === 'title')?.value;
|
|
204
|
-
description = description ?? bodyProps.find(p => p.key === 'description')?.value;
|
|
205
|
-
technology = technology ?? bodyProps.find(p => p.key === 'technology')?.value;
|
|
206
|
-
const links = astNode.body?.props.filter(ast.isLinkProperty).map(p => p.value);
|
|
207
|
-
return {
|
|
208
|
-
id,
|
|
209
|
-
kind,
|
|
210
|
-
astPath,
|
|
211
|
-
title: title ? stripIndent(title).trim() : astNode.name,
|
|
212
|
-
...(tags && { tags }),
|
|
213
|
-
...(links && isNonEmptyArray(links) && { links }),
|
|
214
|
-
...(technology && { technology }),
|
|
215
|
-
...(description && { description: stripIndent(description).trim() }),
|
|
216
|
-
...styleProps
|
|
217
|
-
};
|
|
218
|
-
}
|
|
219
|
-
parseRelation(astNode) {
|
|
220
|
-
const coupling = resolveRelationPoints(astNode);
|
|
221
|
-
const target = this.resolveFqn(coupling.target);
|
|
222
|
-
const source = this.resolveFqn(coupling.source);
|
|
223
|
-
const hashdata = {
|
|
224
|
-
astPath: this.getAstNodePath(astNode),
|
|
225
|
-
source,
|
|
226
|
-
target
|
|
227
|
-
};
|
|
228
|
-
const id = objectHash(hashdata);
|
|
229
|
-
const title = astNode.title ?? astNode.body?.props.find(p => p.key === 'title')?.value ?? '';
|
|
230
|
-
return {
|
|
231
|
-
id,
|
|
232
|
-
...hashdata,
|
|
233
|
-
title
|
|
33
|
+
return null;
|
|
234
34
|
};
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
}
|
|
242
|
-
if (
|
|
243
|
-
|
|
35
|
+
};
|
|
36
|
+
const elements = R.pipe(R.flatMap(docs, d => d.c4Elements.map(toModelElement(d))), R.compact, R.sort(compareByFqnHierarchically), R.reduce((acc, el) => {
|
|
37
|
+
const parent = parentFqn(el.id);
|
|
38
|
+
if (parent && R.isNil(acc[parent])) {
|
|
39
|
+
logWarnError(`No parent found for ${el.id}`);
|
|
40
|
+
return acc;
|
|
41
|
+
}
|
|
42
|
+
if (el.id in acc) {
|
|
43
|
+
// should not happen, as validated
|
|
44
|
+
logWarnError(`Duplicate element id: ${el.id}`);
|
|
45
|
+
return acc;
|
|
46
|
+
}
|
|
47
|
+
acc[el.id] = el;
|
|
48
|
+
return acc;
|
|
49
|
+
}, {}));
|
|
50
|
+
const toModelRelation = ({ astPath, source, target, ...model }) => {
|
|
51
|
+
if (source in elements && target in elements) {
|
|
244
52
|
return {
|
|
245
|
-
|
|
246
|
-
|
|
53
|
+
source,
|
|
54
|
+
target,
|
|
55
|
+
...model
|
|
247
56
|
};
|
|
248
57
|
}
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
58
|
+
return null;
|
|
59
|
+
};
|
|
60
|
+
const relations = R.pipe(R.flatMap(docs, d => d.c4Relations), R.map(toModelRelation), R.compact, R.mapToObj(r => [r.id, r]));
|
|
61
|
+
const modelIndex = ModelIndex.from({ elements, relations });
|
|
62
|
+
const toModelView = (view) => {
|
|
63
|
+
try {
|
|
64
|
+
// eslint-disable-next-line prefer-const
|
|
65
|
+
let { astPath, rules, title, description, tags, links, ...model } = view;
|
|
66
|
+
if (!title && view.viewOf) {
|
|
67
|
+
title = elements[view.viewOf]?.title;
|
|
68
|
+
}
|
|
69
|
+
if (!title && view.id === 'index') {
|
|
70
|
+
title = 'Landscape view';
|
|
71
|
+
}
|
|
72
|
+
const computeResult = computeView({
|
|
73
|
+
...model,
|
|
74
|
+
title: title ?? null,
|
|
75
|
+
description: description ?? null,
|
|
76
|
+
tags: tags ?? null,
|
|
77
|
+
links: links ?? null,
|
|
78
|
+
rules: clone(rules)
|
|
79
|
+
}, modelIndex);
|
|
80
|
+
if (!computeResult.isSuccess) {
|
|
81
|
+
logWarnError(computeResult.error);
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
return computeResult.view;
|
|
255
85
|
}
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
return {
|
|
260
|
-
element: this.resolveFqn(element),
|
|
261
|
-
isDescedants: astNode.isDescedants
|
|
262
|
-
};
|
|
86
|
+
catch (e) {
|
|
87
|
+
logError(e);
|
|
88
|
+
return null;
|
|
263
89
|
}
|
|
264
|
-
|
|
90
|
+
};
|
|
91
|
+
const views = R.pipe(R.flatMap(docs, d => d.c4Views), R.map(toModelView), R.compact);
|
|
92
|
+
assignNavigateTo(views);
|
|
93
|
+
return {
|
|
94
|
+
elements,
|
|
95
|
+
relations,
|
|
96
|
+
views: R.mapToObj(views, v => [v.id, v])
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
export class LikeC4ModelBuilder {
|
|
100
|
+
services;
|
|
101
|
+
langiumDocuments;
|
|
102
|
+
cachedModel = {};
|
|
103
|
+
constructor(services) {
|
|
104
|
+
this.services = services;
|
|
105
|
+
this.langiumDocuments = services.shared.workspace.LangiumDocuments;
|
|
106
|
+
services.likec4.ModelParser.onParsed(() => {
|
|
107
|
+
this.cleanCache();
|
|
108
|
+
this.notifyClient();
|
|
109
|
+
});
|
|
265
110
|
}
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
return this.parseElementExpression(astNode);
|
|
269
|
-
}
|
|
270
|
-
if (ast.isIncomingExpression(astNode)) {
|
|
271
|
-
return {
|
|
272
|
-
incoming: this.parseElementExpression(astNode.target)
|
|
273
|
-
};
|
|
274
|
-
}
|
|
275
|
-
if (ast.isOutgoingExpression(astNode)) {
|
|
276
|
-
return {
|
|
277
|
-
outgoing: this.parseElementExpression(astNode.source)
|
|
278
|
-
};
|
|
279
|
-
}
|
|
280
|
-
if (ast.isInOutExpression(astNode)) {
|
|
281
|
-
return {
|
|
282
|
-
inout: this.parseElementExpression(astNode.inout.target)
|
|
283
|
-
};
|
|
284
|
-
}
|
|
285
|
-
if (ast.isRelationExpression(astNode)) {
|
|
286
|
-
return {
|
|
287
|
-
source: this.parseElementExpression(astNode.source),
|
|
288
|
-
target: this.parseElementExpression(astNode.target)
|
|
289
|
-
};
|
|
290
|
-
}
|
|
291
|
-
failExpectedNever(astNode);
|
|
111
|
+
cleanCache() {
|
|
112
|
+
delete this.cachedModel.last;
|
|
292
113
|
}
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
const exprs = astRule.expressions.map(n => this.parseExpression(n));
|
|
296
|
-
return {
|
|
297
|
-
isInclude: astRule.isInclude,
|
|
298
|
-
exprs
|
|
299
|
-
};
|
|
300
|
-
}
|
|
301
|
-
if (ast.isViewRuleStyle(astRule)) {
|
|
302
|
-
const styleProps = toElementStyle(astRule.props);
|
|
303
|
-
return {
|
|
304
|
-
targets: astRule.targets.map(n => this.parseElementExpression(n)),
|
|
305
|
-
style: {
|
|
306
|
-
...styleProps
|
|
307
|
-
}
|
|
308
|
-
};
|
|
309
|
-
}
|
|
310
|
-
if (ast.isViewRuleAutoLayout(astRule)) {
|
|
311
|
-
return {
|
|
312
|
-
autoLayout: toAutoLayout(astRule.direction)
|
|
313
|
-
};
|
|
314
|
-
}
|
|
315
|
-
failExpectedNever(astRule);
|
|
114
|
+
documents() {
|
|
115
|
+
return this.langiumDocuments.all.filter(isValidLikeC4LangiumDocument).toArray();
|
|
316
116
|
}
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
let id = astNode.name;
|
|
322
|
-
if (!id) {
|
|
323
|
-
const doc = getDocument(astNode).uri.toString();
|
|
324
|
-
id = objectHash({
|
|
325
|
-
doc,
|
|
326
|
-
astPath,
|
|
327
|
-
viewOf: viewOf ?? null
|
|
328
|
-
});
|
|
117
|
+
buildModel() {
|
|
118
|
+
if ('last' in this.cachedModel) {
|
|
119
|
+
logger.debug('[ModelBuilder] returning cached model');
|
|
120
|
+
return this.cachedModel.last;
|
|
329
121
|
}
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
...(title && { title }),
|
|
339
|
-
...(description && { description }),
|
|
340
|
-
...(tags && { tags }),
|
|
341
|
-
...(links && isNonEmptyArray(links) && { links }),
|
|
342
|
-
rules: astNode.rules.map(n => this.parseViewRule(n))
|
|
343
|
-
};
|
|
344
|
-
}
|
|
345
|
-
resolveFqn(node) {
|
|
346
|
-
if (ast.isExtendElement(node)) {
|
|
347
|
-
return strictElementRefFqn(node.element);
|
|
122
|
+
try {
|
|
123
|
+
const docs = this.documents();
|
|
124
|
+
if (docs.length === 0) {
|
|
125
|
+
logger.debug('[ModelBuilder] No documents to build model from');
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
logger.debug(`[ModelBuilder] buildModel from ${docs.length} docs:\n${printDocs(docs)}`);
|
|
129
|
+
return (this.cachedModel.last = buildModel(docs));
|
|
348
130
|
}
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
return fqn;
|
|
352
|
-
}
|
|
353
|
-
getAstNodePath(node) {
|
|
354
|
-
return this.services.workspace.AstNodeLocator.getAstNodePath(node);
|
|
355
|
-
}
|
|
356
|
-
convertTags(withTags) {
|
|
357
|
-
if (!withTags) {
|
|
131
|
+
catch (e) {
|
|
132
|
+
logError(e);
|
|
358
133
|
return null;
|
|
359
134
|
}
|
|
360
|
-
const tags = withTags.tags?.value.flatMap(({ ref }) => (ref ? ref.name : []));
|
|
361
|
-
return tags && isNonEmptyArray(tags) ? tags : null;
|
|
362
135
|
}
|
|
363
136
|
scheduledCb = null;
|
|
364
|
-
notifyClient(
|
|
137
|
+
notifyClient() {
|
|
365
138
|
const connection = this.services.shared.lsp.Connection;
|
|
366
139
|
if (!connection) {
|
|
367
140
|
return;
|
|
368
141
|
}
|
|
369
142
|
if (this.scheduledCb) {
|
|
370
|
-
logger.debug('debounce
|
|
143
|
+
logger.debug('[ModelBuilder] debounce onDidChangeModel');
|
|
371
144
|
clearTimeout(this.scheduledCb);
|
|
372
145
|
}
|
|
373
146
|
this.scheduledCb = setTimeout(() => {
|
|
374
|
-
logger.debug('send onDidChangeModel');
|
|
375
147
|
this.scheduledCb = null;
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
}, 300);
|
|
148
|
+
logger.debug('[ModelBuilder] send onDidChangeModel');
|
|
149
|
+
void connection.sendNotification(Rpc.onDidChangeModel, '');
|
|
150
|
+
}, 200);
|
|
380
151
|
}
|
|
381
152
|
}
|
|
382
153
|
//# sourceMappingURL=model-builder.js.map
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { InvalidModelError } from '@likec4/core';
|
|
1
2
|
import { findNodeForProperty, getDocument } from 'langium';
|
|
2
|
-
import {
|
|
3
|
+
import { ast, isParsedLikeC4LangiumDocument } from '../ast';
|
|
4
|
+
import {} from './fqn-index';
|
|
3
5
|
export class LikeC4ModelLocator {
|
|
4
6
|
services;
|
|
5
7
|
fqnIndex;
|
|
@@ -13,7 +15,7 @@ export class LikeC4ModelLocator {
|
|
|
13
15
|
return this.langiumDocuments.all.filter(isParsedLikeC4LangiumDocument);
|
|
14
16
|
}
|
|
15
17
|
getParsedElement(astNode) {
|
|
16
|
-
const fqn =
|
|
18
|
+
const fqn = this.fqnIndex.getFqn(astNode);
|
|
17
19
|
if (!fqn)
|
|
18
20
|
return null;
|
|
19
21
|
const doc = getDocument(astNode);
|
|
@@ -23,28 +25,18 @@ export class LikeC4ModelLocator {
|
|
|
23
25
|
return doc.c4Elements.find(e => e.id === fqn) ?? null;
|
|
24
26
|
}
|
|
25
27
|
locateElement(fqn, property = 'name') {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
}
|
|
30
|
-
const element = doc.c4Elements.find(e => e.id === fqn);
|
|
31
|
-
if (!element) {
|
|
32
|
-
continue;
|
|
33
|
-
}
|
|
34
|
-
const node = this.services.workspace.AstNodeLocator.getAstNode(doc.parseResult.value, element.astPath);
|
|
35
|
-
if (!ast.isElement(node)) {
|
|
36
|
-
continue;
|
|
37
|
-
}
|
|
38
|
-
const propertyNode = findNodeForProperty(node.$cstNode, property) ?? node.$cstNode;
|
|
39
|
-
if (!propertyNode) {
|
|
40
|
-
return null;
|
|
41
|
-
}
|
|
42
|
-
return {
|
|
43
|
-
uri: doc.uri.toString(),
|
|
44
|
-
range: propertyNode.range
|
|
45
|
-
};
|
|
28
|
+
const entry = this.fqnIndex.byFqn(fqn).head();
|
|
29
|
+
if (!entry) {
|
|
30
|
+
return null;
|
|
46
31
|
}
|
|
47
|
-
|
|
32
|
+
const propertyNode = findNodeForProperty(entry.el.$cstNode, property) ?? entry.el.$cstNode;
|
|
33
|
+
if (!propertyNode) {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
return {
|
|
37
|
+
uri: entry.doc.uri.toString(),
|
|
38
|
+
range: propertyNode.range
|
|
39
|
+
};
|
|
48
40
|
}
|
|
49
41
|
locateRelation(relationId) {
|
|
50
42
|
for (const doc of this.documents()) {
|
|
@@ -68,6 +60,9 @@ export class LikeC4ModelLocator {
|
|
|
68
60
|
};
|
|
69
61
|
}
|
|
70
62
|
}
|
|
63
|
+
if (node.arr == null) {
|
|
64
|
+
throw new InvalidModelError('Relation.arr is not defined, but should be');
|
|
65
|
+
}
|
|
71
66
|
const targetNode = findNodeForProperty(node.$cstNode, 'arr');
|
|
72
67
|
if (!targetNode) {
|
|
73
68
|
return null;
|
|
@@ -75,7 +70,7 @@ export class LikeC4ModelLocator {
|
|
|
75
70
|
return {
|
|
76
71
|
uri: doc.uri.toString(),
|
|
77
72
|
range: {
|
|
78
|
-
start: targetNode.range.
|
|
73
|
+
start: targetNode.range.start,
|
|
79
74
|
end: targetNode.range.end
|
|
80
75
|
}
|
|
81
76
|
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type c4 } from '@likec4/core';
|
|
2
|
+
import type { LangiumDocument } from 'langium';
|
|
3
|
+
import { Disposable, type CancellationToken } from 'vscode-languageserver-protocol';
|
|
4
|
+
import type { LikeC4LangiumDocument } from '../ast';
|
|
5
|
+
import { ast } from '../ast';
|
|
6
|
+
import type { LikeC4Services } from '../module';
|
|
7
|
+
export type ModelParsedListener = () => void;
|
|
8
|
+
export declare class LikeC4ModelParser {
|
|
9
|
+
private services;
|
|
10
|
+
private fqnIndex;
|
|
11
|
+
protected readonly listeners: ModelParsedListener[];
|
|
12
|
+
constructor(services: LikeC4Services);
|
|
13
|
+
onParsed(callback: ModelParsedListener): Disposable;
|
|
14
|
+
protected onValidated(docs: LangiumDocument[], cancelToken: CancellationToken): Promise<void>;
|
|
15
|
+
protected parseDocument(doc: LikeC4LangiumDocument, cancelToken: CancellationToken): Promise<void>;
|
|
16
|
+
private parseElement;
|
|
17
|
+
private parseRelation;
|
|
18
|
+
private parseElementExpression;
|
|
19
|
+
private parseExpression;
|
|
20
|
+
private parseViewRule;
|
|
21
|
+
private parseElementView;
|
|
22
|
+
protected resolveFqn(node: ast.Element | ast.ExtendElement): c4.Fqn;
|
|
23
|
+
private getAstNodePath;
|
|
24
|
+
private convertTags;
|
|
25
|
+
private notifyListeners;
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=model-parser.d.ts.map
|