@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.
- package/contrib/likec4.monarch.ts +31 -0
- package/contrib/likec4.tmLanguage.json +73 -0
- package/dist/ast.d.ts +75 -0
- package/dist/ast.js +143 -0
- package/dist/builtin.d.ts +5 -0
- package/dist/builtin.js +9 -0
- package/dist/elementRef.d.ts +7 -0
- package/dist/elementRef.js +40 -0
- package/dist/generated/ast.d.ts +364 -0
- package/dist/generated/ast.js +389 -0
- package/dist/generated/grammar.d.ts +7 -0
- package/dist/generated/grammar.js +2548 -0
- package/dist/generated/module.d.ts +10 -0
- package/dist/generated/module.js +27 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +3 -0
- package/dist/logger.d.ts +9 -0
- package/dist/logger.js +21 -0
- package/dist/lsp/DocumentSymbolProvider.d.ts +22 -0
- package/dist/lsp/DocumentSymbolProvider.js +167 -0
- package/dist/lsp/HoverProvider.d.ts +9 -0
- package/dist/lsp/HoverProvider.js +55 -0
- package/dist/lsp/SemanticTokenProvider.d.ts +7 -0
- package/dist/lsp/SemanticTokenProvider.js +222 -0
- package/dist/lsp/index.d.ts +4 -0
- package/dist/lsp/index.js +4 -0
- package/dist/model/fqn-index.d.ts +41 -0
- package/dist/model/fqn-index.js +119 -0
- package/dist/model/index.d.ts +4 -0
- package/dist/model/index.js +4 -0
- package/dist/model/model-builder.d.ts +27 -0
- package/dist/model/model-builder.js +339 -0
- package/dist/model/model-locator.d.ts +17 -0
- package/dist/model/model-locator.js +116 -0
- package/dist/module.d.ts +21 -0
- package/dist/module.js +66 -0
- package/dist/protocol.d.ts +37 -0
- package/dist/protocol.js +20 -0
- package/dist/references/fqn-computation.d.ts +4 -0
- package/dist/references/fqn-computation.js +41 -0
- package/dist/references/index.d.ts +3 -0
- package/dist/references/index.js +3 -0
- package/dist/references/scope-computation.d.ts +14 -0
- package/dist/references/scope-computation.js +86 -0
- package/dist/references/scope-provider.d.ts +19 -0
- package/dist/references/scope-provider.js +120 -0
- package/dist/registerProtocolHandlers.d.ts +3 -0
- package/dist/registerProtocolHandlers.js +65 -0
- package/dist/shared/CodeLensProvider.d.ts +9 -0
- package/dist/shared/CodeLensProvider.js +36 -0
- package/dist/shared/WorkspaceManager.d.ts +14 -0
- package/dist/shared/WorkspaceManager.js +18 -0
- package/dist/shared/index.d.ts +3 -0
- package/dist/shared/index.js +3 -0
- package/dist/test/index.d.ts +2 -0
- package/dist/test/index.js +2 -0
- package/dist/test/testServices.d.ts +16 -0
- package/dist/test/testServices.js +58 -0
- package/dist/utils.d.ts +3 -0
- package/dist/utils.js +8 -0
- package/dist/validation/element.d.ts +6 -0
- package/dist/validation/element.js +21 -0
- package/dist/validation/index.d.ts +3 -0
- package/dist/validation/index.js +23 -0
- package/dist/validation/relation.d.ts +5 -0
- package/dist/validation/relation.js +54 -0
- package/dist/validation/specification.d.ts +6 -0
- package/dist/validation/specification.js +34 -0
- package/dist/validation/view.d.ts +5 -0
- package/dist/validation/view.js +21 -0
- 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,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
|
package/dist/module.d.ts
ADDED
|
@@ -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
|