@likec4/language-server 0.6.2 → 0.8.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.
Files changed (71) hide show
  1. package/contrib/likec4.monarch.ts +31 -0
  2. package/contrib/likec4.tmLanguage.json +73 -0
  3. package/dist/ast.d.ts +75 -0
  4. package/dist/ast.js +143 -0
  5. package/dist/builtin.d.ts +5 -0
  6. package/dist/builtin.js +9 -0
  7. package/dist/elementRef.d.ts +7 -0
  8. package/dist/elementRef.js +40 -0
  9. package/dist/generated/ast.d.ts +364 -0
  10. package/dist/generated/ast.js +389 -0
  11. package/dist/generated/grammar.d.ts +7 -0
  12. package/dist/generated/grammar.js +2548 -0
  13. package/dist/generated/module.d.ts +10 -0
  14. package/dist/generated/module.js +27 -0
  15. package/dist/index.d.ts +4 -0
  16. package/dist/index.js +3 -0
  17. package/dist/logger.d.ts +9 -0
  18. package/dist/logger.js +21 -0
  19. package/dist/lsp/DocumentSymbolProvider.d.ts +22 -0
  20. package/dist/lsp/DocumentSymbolProvider.js +167 -0
  21. package/dist/lsp/HoverProvider.d.ts +9 -0
  22. package/dist/lsp/HoverProvider.js +55 -0
  23. package/dist/lsp/SemanticTokenProvider.d.ts +7 -0
  24. package/dist/lsp/SemanticTokenProvider.js +222 -0
  25. package/dist/lsp/index.d.ts +4 -0
  26. package/dist/lsp/index.js +4 -0
  27. package/dist/model/fqn-index.d.ts +41 -0
  28. package/dist/model/fqn-index.js +119 -0
  29. package/dist/model/index.d.ts +4 -0
  30. package/dist/model/index.js +4 -0
  31. package/dist/model/model-builder.d.ts +27 -0
  32. package/dist/model/model-builder.js +339 -0
  33. package/dist/model/model-locator.d.ts +17 -0
  34. package/dist/model/model-locator.js +116 -0
  35. package/dist/module.d.ts +21 -0
  36. package/dist/module.js +66 -0
  37. package/dist/protocol.d.ts +37 -0
  38. package/dist/protocol.js +20 -0
  39. package/dist/references/fqn-computation.d.ts +4 -0
  40. package/dist/references/fqn-computation.js +41 -0
  41. package/dist/references/index.d.ts +3 -0
  42. package/dist/references/index.js +3 -0
  43. package/dist/references/scope-computation.d.ts +14 -0
  44. package/dist/references/scope-computation.js +86 -0
  45. package/dist/references/scope-provider.d.ts +19 -0
  46. package/dist/references/scope-provider.js +120 -0
  47. package/dist/registerProtocolHandlers.d.ts +3 -0
  48. package/dist/registerProtocolHandlers.js +65 -0
  49. package/dist/shared/CodeLensProvider.d.ts +9 -0
  50. package/dist/shared/CodeLensProvider.js +36 -0
  51. package/dist/shared/WorkspaceManager.d.ts +14 -0
  52. package/dist/shared/WorkspaceManager.js +18 -0
  53. package/dist/shared/index.d.ts +3 -0
  54. package/dist/shared/index.js +3 -0
  55. package/dist/test/index.d.ts +2 -0
  56. package/dist/test/index.js +2 -0
  57. package/dist/test/testServices.d.ts +16 -0
  58. package/dist/test/testServices.js +58 -0
  59. package/dist/utils.d.ts +3 -0
  60. package/dist/utils.js +8 -0
  61. package/dist/validation/element.d.ts +6 -0
  62. package/dist/validation/element.js +21 -0
  63. package/dist/validation/index.d.ts +3 -0
  64. package/dist/validation/index.js +23 -0
  65. package/dist/validation/relation.d.ts +5 -0
  66. package/dist/validation/relation.js +54 -0
  67. package/dist/validation/specification.d.ts +6 -0
  68. package/dist/validation/specification.js +34 -0
  69. package/dist/validation/view.d.ts +5 -0
  70. package/dist/validation/view.js +21 -0
  71. package/package.json +26 -31
@@ -0,0 +1,119 @@
1
+ import { DocumentState, DONE_RESULT, getDocument, MultiMap, StreamImpl } from 'langium';
2
+ import { ElementOps, isLikeC4LangiumDocument } from '../ast';
3
+ import { logger } from '../logger';
4
+ import { nameFromFqn, parentFqn } from '@likec4/core/utils';
5
+ import '../elementRef';
6
+ import { isNil } from 'rambdax';
7
+ import '../generated/ast';
8
+ const isFqnIndexedDocument = (doc) => isLikeC4LangiumDocument(doc) && doc.state >= DocumentState.ComputedScopes && !isNil(doc.c4fqns);
9
+ export class FqnIndex {
10
+ services;
11
+ // #fqnMap = new WeakMap<ast.Element, Fqn>()
12
+ // protected readonly descriptions: AstNodeDescriptionProvider
13
+ constructor(services) {
14
+ this.services = services;
15
+ // this.descriptions = services.workspace.AstNodeDescriptionProvider
16
+ // services.shared.workspace.DocumentBuilder.onUpdate((_changed, removed) => {
17
+ // for (const uri of [..._changed, ...removed]) {
18
+ // this.cleanIndexedElements(uri)
19
+ // }
20
+ // })
21
+ // services.shared.workspace.DocumentBuilder.onBuildPhase(
22
+ // DocumentState.Parsed,
23
+ // (docs, _cancelToken) => {
24
+ // for (const doc of docs) {
25
+ // this.cleanIndexedElements(doc.uri)
26
+ // this.doIndexElements(doc as LikeC4LangiumDocument)
27
+ // }
28
+ // }
29
+ // )
30
+ }
31
+ documents() {
32
+ return this.services.shared.workspace.LangiumDocuments.all.filter(isFqnIndexedDocument);
33
+ }
34
+ entries() {
35
+ return this.documents().flatMap(doc => doc.c4fqns.entries().map(([fqn, path]) => ({ fqn, path, doc })));
36
+ }
37
+ // private index() {
38
+ // const index = new MultiMap<Fqn, {
39
+ // path: string,
40
+ // doc: LikeC4LangiumDocument
41
+ // }>()
42
+ // this.entries().toMap()
43
+ // this.entries().forEach(({ fqn, path, doc }) => {
44
+ // index.add(fqn, { path, doc })
45
+ // })
46
+ // // for (const doc of this.documents()) {
47
+ // // doc.c4fqns.entries().forEach(([fqn, path]) => {
48
+ // // index.add(fqn, { path, doc })
49
+ // // })
50
+ // // }
51
+ // return index
52
+ // }
53
+ get(el) {
54
+ let fqn = ElementOps.readId(el) ?? null;
55
+ const doc = getDocument(el);
56
+ if (fqn) {
57
+ if (isFqnIndexedDocument(doc) && doc.c4fqns.has(fqn)) {
58
+ return fqn;
59
+ }
60
+ const path = this.services.workspace.AstNodeLocator.getAstNodePath(el);
61
+ logger.error(`Clean cached FQN ${fqn} at ${path}`);
62
+ ElementOps.writeId(el, null);
63
+ fqn = null;
64
+ }
65
+ return fqn;
66
+ }
67
+ byFqn(fqn) {
68
+ return this.documents()
69
+ .flatMap(doc => {
70
+ return doc.c4fqns.get(fqn).map(path => ({ path, doc }));
71
+ });
72
+ }
73
+ directChildrenOf(parent) {
74
+ return this
75
+ .entries()
76
+ .filter(e => parentFqn(e.fqn) === parent)
77
+ .map((e) => ({ ...e, name: nameFromFqn(e.fqn) }));
78
+ }
79
+ uniqueDescedants(parent) {
80
+ return new StreamImpl(() => {
81
+ const prefix = `${parent}.`;
82
+ const children = [];
83
+ const childrenNames = new Set();
84
+ const descedants = [];
85
+ this.entries().forEach(e => {
86
+ if (e.fqn.startsWith(prefix)) {
87
+ const name = nameFromFqn(e.fqn);
88
+ const entry = { ...e, name };
89
+ if (parentFqn(e.fqn) === parent) {
90
+ childrenNames.add(name);
91
+ children.push(entry);
92
+ }
93
+ else {
94
+ descedants.push(entry);
95
+ }
96
+ }
97
+ });
98
+ if (children.length + descedants.length === 0) {
99
+ return null;
100
+ }
101
+ const nested = new MultiMap(children.map(entry => [entry.name, entry]));
102
+ for (const descedant of descedants) {
103
+ if (!childrenNames.has(descedant.name)) {
104
+ nested.add(descedant.name, descedant);
105
+ }
106
+ }
107
+ return nested
108
+ .entriesGroupedByKey()
109
+ .flatMap(([_name, descrs]) => (descrs.length === 1 ? descrs : []))
110
+ .iterator();
111
+ }, iterator => {
112
+ if (iterator) {
113
+ return iterator.next();
114
+ }
115
+ return DONE_RESULT;
116
+ });
117
+ }
118
+ }
119
+ //# sourceMappingURL=fqn-index.js.map
@@ -0,0 +1,4 @@
1
+ export * from './fqn-index';
2
+ export * from './model-builder';
3
+ export * from './model-locator';
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,4 @@
1
+ export * from './fqn-index';
2
+ export * from './model-builder';
3
+ export * from './model-locator';
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,27 @@
1
+ import type * as c4 from '@likec4/core/types';
2
+ import { ast, type LikeC4LangiumDocument } from '../ast';
3
+ import type { LikeC4Services } from '../module';
4
+ export declare class LikeC4ModelBuilder {
5
+ private services;
6
+ private fqnIndex;
7
+ private langiumDocuments;
8
+ constructor(services: LikeC4Services);
9
+ private get connection();
10
+ private documents;
11
+ buildModel(): c4.LikeC4Model | undefined;
12
+ /**
13
+ * @returns if the document was changed
14
+ */
15
+ protected parseDocument(doc: LikeC4LangiumDocument): boolean;
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 notifyClient;
26
+ }
27
+ //# sourceMappingURL=model-builder.d.ts.map
@@ -0,0 +1,339 @@
1
+ import { computeViews } from '@likec4/core/compute-view';
2
+ import { DefaultElementShape, DefaultThemeColor } from '@likec4/core/types';
3
+ import { compareByFqnHierarchically, parentFqn } from '@likec4/core/utils';
4
+ import { A, O, flow, pipe } from '@mobily/ts-belt';
5
+ import { DocumentState, getDocument, interruptAndCheck } from 'langium';
6
+ import objectHash from 'object-hash';
7
+ import { clone, isNil, mergeDeepRight, omit, reduce } from 'rambdax';
8
+ import invariant from 'tiny-invariant';
9
+ import { toAutoLayout } from '../ast';
10
+ import { ElementViewOps, ast, c4hash, cleanParsedModel, isLikeC4LangiumDocument, isParsedLikeC4LangiumDocument, resolveRelationPoints, streamModel, toElementStyle } from '../ast';
11
+ import { elementRef, strictElementRefFqn } from '../elementRef';
12
+ import { logger } from '../logger';
13
+ import { Rpc } from '../protocol';
14
+ import { failExpectedNever } from '../utils';
15
+ export class LikeC4ModelBuilder {
16
+ services;
17
+ fqnIndex;
18
+ langiumDocuments;
19
+ constructor(services) {
20
+ this.services = services;
21
+ this.fqnIndex = services.likec4.FqnIndex;
22
+ this.langiumDocuments = services.shared.workspace.LangiumDocuments;
23
+ services.shared.workspace.DocumentBuilder.onBuildPhase(DocumentState.Validated, async (docs, cancelToken) => {
24
+ let countOfChangedDocs = 0;
25
+ try {
26
+ for (const doc of docs) {
27
+ await interruptAndCheck(cancelToken);
28
+ try {
29
+ if (isLikeC4LangiumDocument(doc) && this.parseDocument(doc)) {
30
+ countOfChangedDocs++;
31
+ }
32
+ }
33
+ catch (e) {
34
+ logger.warn(`Error parsing document ${doc.uri.toString()}`);
35
+ }
36
+ }
37
+ }
38
+ finally {
39
+ if (countOfChangedDocs > 0 && !cancelToken.isCancellationRequested) {
40
+ await this.notifyClient();
41
+ }
42
+ }
43
+ });
44
+ }
45
+ get connection() {
46
+ return this.services.shared.lsp.Connection;
47
+ }
48
+ documents() {
49
+ return this.langiumDocuments.all.toArray().filter(isParsedLikeC4LangiumDocument);
50
+ }
51
+ buildModel() {
52
+ const docs = this.documents();
53
+ if (docs.length === 0) {
54
+ return;
55
+ }
56
+ try {
57
+ const c4Specification = reduce((acc, doc) => mergeDeepRight(acc, doc.c4Specification), {
58
+ kinds: {}
59
+ }, docs);
60
+ // const c4Specification = docs.reduce<ParsedAstSpecification>((acc, doc) => {
61
+ // return mergeDeepRight(acc, doc.c4Specification)
62
+ // }, {
63
+ // kinds: {}
64
+ // })
65
+ const toModelElement = (el) => {
66
+ const kind = c4Specification.kinds[el.kind];
67
+ if (kind) {
68
+ return {
69
+ ...(kind.shape !== DefaultElementShape ? { shape: kind.shape } : {}),
70
+ ...(kind.color !== DefaultThemeColor ? { color: kind.color } : {}),
71
+ ...omit(['astPath'], el)
72
+ };
73
+ }
74
+ return null;
75
+ };
76
+ const toModelRelation = (rel) => {
77
+ return omit(['astPath'], rel);
78
+ };
79
+ const elements = pipe(docs.flatMap(d => d.c4Elements), A.filterMap(flow(toModelElement, O.fromNullable)), A.sort(compareByFqnHierarchically), A.reduce({}, (acc, el) => {
80
+ const parent = parentFqn(el.id);
81
+ if (!parent || parent in acc) {
82
+ if (el.id in acc) {
83
+ logger.warn(`Duplicate element id: ${el.id}`);
84
+ }
85
+ acc[el.id] = el;
86
+ }
87
+ return acc;
88
+ }));
89
+ const relations = pipe(docs.flatMap(d => d.c4Relations), A.filterMap(flow(toModelRelation, O.fromPredicate(({ source, target }) => source in elements && target in elements))), A.reduce({}, (acc, el) => {
90
+ if (el.id in acc) {
91
+ logger.warn(`Duplicate relation id: ${el.id}`);
92
+ }
93
+ acc[el.id] = el;
94
+ return acc;
95
+ }));
96
+ const toModelView = (view) => {
97
+ let title = view.title;
98
+ if (!title && view.viewOf) {
99
+ title = elements[view.viewOf]?.title;
100
+ }
101
+ return {
102
+ ...omit(['astPath', 'rules', 'title'], view),
103
+ ...(!!title ? { title } : {}),
104
+ rules: clone(view.rules)
105
+ };
106
+ };
107
+ const views = pipe(docs.flatMap(d => d.c4Views), A.filterMap(flow(toModelView, O.fromPredicate(v => isNil(v.viewOf) || v.viewOf in elements))), A.reduce({}, (acc, v) => {
108
+ if (v.id in acc) {
109
+ logger.warn(`Duplicate view id: ${v.id}`);
110
+ }
111
+ acc[v.id] = v;
112
+ return acc;
113
+ }));
114
+ return computeViews({
115
+ elements,
116
+ relations,
117
+ views
118
+ });
119
+ }
120
+ catch (e) {
121
+ logger.error(e);
122
+ return;
123
+ }
124
+ }
125
+ /**
126
+ * @returns if the document was changed
127
+ */
128
+ parseDocument(doc) {
129
+ const { elements, relations, views, specification } = cleanParsedModel(doc);
130
+ const spec = doc.parseResult.value.specification;
131
+ if (spec) {
132
+ for (const { kind, style } of spec.elementKinds) {
133
+ try {
134
+ const styleProps = toElementStyle(style?.props);
135
+ specification.kinds[kind.name] = {
136
+ color: styleProps.color ?? DefaultThemeColor,
137
+ shape: styleProps.shape ?? DefaultElementShape
138
+ };
139
+ }
140
+ catch (e) {
141
+ logger.warn(e);
142
+ }
143
+ }
144
+ }
145
+ for (const el of streamModel(doc)) {
146
+ if (ast.isElement(el)) {
147
+ try {
148
+ elements.push(this.parseElement(el));
149
+ }
150
+ catch (e) {
151
+ logger.warn(e);
152
+ }
153
+ continue;
154
+ }
155
+ if (ast.isRelation(el)) {
156
+ try {
157
+ relations.push(this.parseRelation(el));
158
+ }
159
+ catch (e) {
160
+ logger.warn(e);
161
+ }
162
+ continue;
163
+ }
164
+ failExpectedNever(el);
165
+ }
166
+ const docviews = doc.parseResult.value.views?.views;
167
+ if (docviews) {
168
+ for (const view of docviews) {
169
+ try {
170
+ const v = this.parseElementView(view);
171
+ ElementViewOps.writeId(view, v.id);
172
+ views.push(v);
173
+ }
174
+ catch (e) {
175
+ logger.warn(e);
176
+ }
177
+ }
178
+ }
179
+ const prevHash = doc.c4hash ?? '';
180
+ doc.c4hash = c4hash(doc);
181
+ return prevHash !== doc.c4hash;
182
+ }
183
+ parseElement(astNode) {
184
+ const id = this.resolveFqn(astNode);
185
+ invariant(astNode.kind.ref, 'Element kind is not resolved: ' + astNode.name);
186
+ const kind = astNode.kind.ref.name;
187
+ const tags = (astNode.body && this.convertTags(astNode.body)) ?? [];
188
+ const styleProps = astNode.body?.props.find(ast.isElementStyleProperty)?.props;
189
+ const { color, shape } = toElementStyle(styleProps);
190
+ const astPath = this.getAstNodePath(astNode);
191
+ let [title, description, technology] = astNode.props;
192
+ const bodyProps = astNode.body?.props.filter((p) => ast.isElementStringProperty(p)) ?? [];
193
+ title = title ?? bodyProps.find(p => p.key === 'title')?.value;
194
+ description = description ?? bodyProps.find(p => p.key === 'description')?.value;
195
+ technology = technology ?? bodyProps.find(p => p.key === 'technology')?.value;
196
+ return {
197
+ id,
198
+ kind,
199
+ astPath,
200
+ title: title ?? astNode.name,
201
+ ...(technology && { technology }),
202
+ ...(description && { description }),
203
+ ...(tags.length > 0 ? { tags } : {}),
204
+ ...(shape && shape !== DefaultElementShape ? { shape } : {}),
205
+ ...(color && color !== DefaultThemeColor ? { color } : {})
206
+ };
207
+ }
208
+ parseRelation(astNode) {
209
+ const coupling = resolveRelationPoints(astNode);
210
+ const target = this.resolveFqn(coupling.target);
211
+ const source = this.resolveFqn(coupling.source);
212
+ const hashdata = {
213
+ astPath: this.getAstNodePath(astNode),
214
+ source,
215
+ target
216
+ };
217
+ const id = objectHash(hashdata);
218
+ const title = astNode.title ?? astNode.definition?.props.find(p => p.key === 'title')?.value ?? '';
219
+ return {
220
+ id,
221
+ ...hashdata,
222
+ title
223
+ };
224
+ }
225
+ parseElementExpression(astNode) {
226
+ if (ast.isWildcardExpression(astNode)) {
227
+ return {
228
+ wildcard: true
229
+ };
230
+ }
231
+ if (ast.isElementRefExpression(astNode)) {
232
+ const element = elementRef(astNode.id);
233
+ invariant(element, 'Element not found ' + astNode.id.$cstNode?.text);
234
+ return {
235
+ element: this.resolveFqn(element),
236
+ isDescedants: astNode.isDescedants
237
+ };
238
+ }
239
+ failExpectedNever(astNode);
240
+ }
241
+ parseExpression(astNode) {
242
+ if (ast.isElementExpression(astNode)) {
243
+ return this.parseElementExpression(astNode);
244
+ }
245
+ if (ast.isIncomingExpression(astNode)) {
246
+ return {
247
+ incoming: this.parseElementExpression(astNode.target)
248
+ };
249
+ }
250
+ if (ast.isOutgoingExpression(astNode)) {
251
+ return {
252
+ outgoing: this.parseElementExpression(astNode.source)
253
+ };
254
+ }
255
+ if (ast.isInOutExpression(astNode)) {
256
+ return {
257
+ inout: this.parseElementExpression(astNode.inout.target)
258
+ };
259
+ }
260
+ if (ast.isRelationExpression(astNode)) {
261
+ return {
262
+ source: this.parseElementExpression(astNode.source),
263
+ target: this.parseElementExpression(astNode.target)
264
+ };
265
+ }
266
+ failExpectedNever(astNode);
267
+ }
268
+ parseViewRule(astRule) {
269
+ if (ast.isViewRuleExpression(astRule)) {
270
+ const exprs = astRule.expressions.map(n => this.parseExpression(n));
271
+ return {
272
+ isInclude: astRule.isInclude,
273
+ exprs
274
+ };
275
+ }
276
+ if (ast.isViewRuleStyle(astRule)) {
277
+ const styleProps = toElementStyle(astRule.props);
278
+ return {
279
+ targets: astRule.targets.map(n => this.parseElementExpression(n)),
280
+ style: {
281
+ ...styleProps
282
+ }
283
+ };
284
+ }
285
+ if (ast.isViewRuleAutoLayout(astRule)) {
286
+ return {
287
+ autoLayout: toAutoLayout(astRule.direction)
288
+ };
289
+ }
290
+ failExpectedNever(astRule);
291
+ }
292
+ parseElementView(astNode) {
293
+ const viewOfEl = astNode.viewOf && elementRef(astNode.viewOf);
294
+ const viewOf = viewOfEl && this.resolveFqn(viewOfEl);
295
+ const astPath = this.getAstNodePath(astNode);
296
+ let id = astNode.name;
297
+ if (!id) {
298
+ const doc = getDocument(astNode).uri.toString();
299
+ id = objectHash({
300
+ doc,
301
+ astPath,
302
+ viewOf: viewOf ?? null
303
+ });
304
+ }
305
+ const title = astNode.properties.find(p => p.key === 'title')?.value;
306
+ const description = astNode.properties.find(p => p.key === 'description')?.value;
307
+ return {
308
+ id,
309
+ astPath,
310
+ ...(viewOf && { viewOf }),
311
+ ...(title && { title }),
312
+ ...(description && { description }),
313
+ rules: astNode.rules.map(n => this.parseViewRule(n))
314
+ };
315
+ }
316
+ resolveFqn(node) {
317
+ if (ast.isExtendElement(node)) {
318
+ return strictElementRefFqn(node.element);
319
+ }
320
+ const fqn = this.fqnIndex.get(node);
321
+ invariant(fqn, `Not indexed element: ${this.getAstNodePath(node)}`);
322
+ return fqn;
323
+ }
324
+ getAstNodePath(node) {
325
+ return this.services.workspace.AstNodeLocator.getAstNodePath(node);
326
+ }
327
+ convertTags(el) {
328
+ return el.tags?.value.map(tagRef => tagRef.ref?.name) ?? [];
329
+ }
330
+ async notifyClient() {
331
+ const connection = this.connection;
332
+ if (!connection) {
333
+ return;
334
+ }
335
+ logger.debug('Send onDidChangeModel');
336
+ await connection.sendNotification(Rpc.onDidChangeModel);
337
+ }
338
+ }
339
+ //# sourceMappingURL=model-builder.js.map
@@ -0,0 +1,17 @@
1
+ import type * as c4 from '@likec4/core/types';
2
+ import type { Location } from 'vscode-languageserver';
3
+ import type { ParsedAstElement } from '../ast';
4
+ import { ast } from '../ast';
5
+ import type { LikeC4Services } from '../module';
6
+ export declare class LikeC4ModelLocator {
7
+ private services;
8
+ private fqnIndex;
9
+ private langiumDocuments;
10
+ constructor(services: LikeC4Services);
11
+ private documents;
12
+ getParsedElement(astNode: ast.Element): ParsedAstElement | null;
13
+ locateElement(fqn: c4.Fqn, property?: string): Location | null;
14
+ locateRelation(relationId: c4.RelationID): Location | null;
15
+ locateView(viewId: c4.ViewID): Location | null;
16
+ }
17
+ //# sourceMappingURL=model-locator.d.ts.map
@@ -0,0 +1,116 @@
1
+ import { findNodeForKeyword, findNodeForProperty, getDocument } from 'langium';
2
+ import { ast, isParsedLikeC4LangiumDocument } from '../ast';
3
+ export class LikeC4ModelLocator {
4
+ services;
5
+ fqnIndex;
6
+ langiumDocuments;
7
+ constructor(services) {
8
+ this.services = services;
9
+ this.fqnIndex = services.likec4.FqnIndex;
10
+ this.langiumDocuments = services.shared.workspace.LangiumDocuments;
11
+ }
12
+ documents() {
13
+ return this.langiumDocuments.all.toArray().filter(isParsedLikeC4LangiumDocument);
14
+ }
15
+ getParsedElement(astNode) {
16
+ const doc = getDocument(astNode);
17
+ if (!isParsedLikeC4LangiumDocument(doc)) {
18
+ return null;
19
+ }
20
+ const fqn = this.fqnIndex.get(astNode);
21
+ if (!fqn)
22
+ return null;
23
+ return doc.c4Elements.find(e => e.id === fqn) ?? null;
24
+ }
25
+ locateElement(fqn, property = 'name') {
26
+ for (const doc of this.documents()) {
27
+ if (doc.c4fqns && !doc.c4fqns.has(fqn)) {
28
+ continue;
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
+ };
46
+ }
47
+ return null;
48
+ }
49
+ locateRelation(relationId) {
50
+ for (const doc of this.documents()) {
51
+ const relation = doc.c4Relations.find(r => r.id === relationId);
52
+ if (!relation) {
53
+ continue;
54
+ }
55
+ const node = this.services.workspace.AstNodeLocator.getAstNode(doc.parseResult.value, relation.astPath);
56
+ if (!ast.isRelation(node)) {
57
+ continue;
58
+ }
59
+ if (node.title) {
60
+ const targetNode = findNodeForProperty(node.$cstNode, 'title');
61
+ if (targetNode) {
62
+ return {
63
+ uri: doc.uri.toString(),
64
+ range: {
65
+ start: targetNode.range.start,
66
+ end: targetNode.range.start
67
+ }
68
+ };
69
+ }
70
+ }
71
+ const targetNode = findNodeForKeyword(node.$cstNode, '->');
72
+ if (!targetNode) {
73
+ return null;
74
+ }
75
+ return {
76
+ uri: doc.uri.toString(),
77
+ range: {
78
+ start: targetNode.range.end,
79
+ end: targetNode.range.end
80
+ }
81
+ };
82
+ }
83
+ return null;
84
+ }
85
+ locateView(viewId) {
86
+ for (const doc of this.documents()) {
87
+ const view = doc.c4Views.find(r => r.id === viewId);
88
+ if (!view) {
89
+ continue;
90
+ }
91
+ const node = this.services.workspace.AstNodeLocator.getAstNode(doc.parseResult.value, view.astPath);
92
+ if (!ast.isElementView(node)) {
93
+ continue;
94
+ }
95
+ let targetNode = node.$cstNode;
96
+ if (node.name) {
97
+ targetNode = findNodeForProperty(node.$cstNode, 'name') ?? targetNode;
98
+ }
99
+ else if (node.viewOf) {
100
+ targetNode = findNodeForProperty(node.$cstNode, 'viewOf') ?? targetNode;
101
+ }
102
+ if (!targetNode) {
103
+ return null;
104
+ }
105
+ return {
106
+ uri: doc.uri.toString(),
107
+ range: {
108
+ start: targetNode.range.start,
109
+ end: targetNode.range.start
110
+ }
111
+ };
112
+ }
113
+ return null;
114
+ }
115
+ }
116
+ //# sourceMappingURL=model-locator.js.map
@@ -0,0 +1,21 @@
1
+ import type { DefaultSharedModuleContext, LangiumServices, LangiumSharedServices, Module, PartialLangiumServices } from 'langium';
2
+ import { FqnIndex, LikeC4ModelBuilder, LikeC4ModelLocator } from './model';
3
+ /**
4
+ * Declaration of custom services - add your own service classes here.
5
+ */
6
+ export interface LikeC4AddedServices {
7
+ likec4: {
8
+ FqnIndex: FqnIndex;
9
+ ModelBuilder: LikeC4ModelBuilder;
10
+ ModelLocator: LikeC4ModelLocator;
11
+ };
12
+ }
13
+ export type LikeC4Services = LangiumServices & LikeC4AddedServices;
14
+ export declare const LikeC4Module: Module<LikeC4Services, PartialLangiumServices & LikeC4AddedServices>;
15
+ type LanguageServicesContext = Partial<DefaultSharedModuleContext>;
16
+ export declare function createLanguageServices(context?: LanguageServicesContext): {
17
+ shared: LangiumSharedServices;
18
+ likec4: LikeC4Services;
19
+ };
20
+ export {};
21
+ //# sourceMappingURL=module.d.ts.map