@likec4/language-server 0.6.1 → 0.6.2
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/package.json +5 -5
- package/contrib/likec4.monarch.ts +0 -31
- package/contrib/likec4.tmLanguage.json +0 -73
- package/dist/__test__/parser-smoke/01-Specification.d.ts +0 -3
- package/dist/__test__/parser-smoke/01-Specification.js +0 -42
- package/dist/__test__/parser-smoke/02-Model.d.ts +0 -9
- package/dist/__test__/parser-smoke/02-Model.js +0 -110
- package/dist/__test__/parser-smoke/03-ModelRelation.d.ts +0 -6
- package/dist/__test__/parser-smoke/03-ModelRelation.js +0 -81
- package/dist/__test__/parser-smoke/04-Scope.d.ts +0 -2
- package/dist/__test__/parser-smoke/04-Scope.js +0 -38
- package/dist/__test__/parser-smoke/05-StrictElementRef.d.ts +0 -3
- package/dist/__test__/parser-smoke/05-StrictElementRef.js +0 -46
- package/dist/__test__/parser-smoke/06-ElementRef.d.ts +0 -2
- package/dist/__test__/parser-smoke/06-ElementRef.js +0 -59
- package/dist/__test__/parser-smoke/07-Views.d.ts +0 -10
- package/dist/__test__/parser-smoke/07-Views.js +0 -146
- package/dist/__test__/parser-smoke/08-Structurizr.d.ts +0 -1
- package/dist/__test__/parser-smoke/08-Structurizr.js +0 -22
- package/dist/__test__/parser-smoke/index.d.ts +0 -8
- package/dist/__test__/parser-smoke/index.js +0 -8
- package/dist/__test__/parser-smoke-extendsElement.spec.d.ts +0 -1
- package/dist/__test__/parser-smoke-extendsElement.spec.js +0 -36
- package/dist/__test__/parser-smoke.spec.d.ts +0 -1
- package/dist/__test__/parser-smoke.spec.js +0 -28
- package/dist/ast.d.ts +0 -73
- package/dist/ast.js +0 -133
- package/dist/builtin.d.ts +0 -4
- package/dist/builtin.js +0 -8
- package/dist/elementRef.d.ts +0 -6
- package/dist/elementRef.js +0 -39
- package/dist/generated/ast.d.ts +0 -359
- package/dist/generated/ast.js +0 -376
- package/dist/generated/grammar.d.ts +0 -6
- package/dist/generated/grammar.js +0 -2542
- package/dist/generated/module.d.ts +0 -9
- package/dist/generated/module.js +0 -26
- package/dist/index.d.ts +0 -3
- package/dist/index.js +0 -14
- package/dist/logger.d.ts +0 -8
- package/dist/logger.js +0 -20
- package/dist/lsp/DocumentSymbolProvider.d.ts +0 -21
- package/dist/lsp/DocumentSymbolProvider.js +0 -149
- package/dist/lsp/HoverProvider.d.ts +0 -8
- package/dist/lsp/HoverProvider.js +0 -54
- package/dist/lsp/SemanticTokenProvider.d.ts +0 -6
- package/dist/lsp/SemanticTokenProvider.js +0 -221
- package/dist/lsp/index.d.ts +0 -3
- package/dist/lsp/index.js +0 -3
- package/dist/model/fqn-index.d.ts +0 -17
- package/dist/model/fqn-index.js +0 -138
- package/dist/model/index.d.ts +0 -3
- package/dist/model/index.js +0 -3
- package/dist/model/model-builder.d.ts +0 -26
- package/dist/model/model-builder.js +0 -332
- package/dist/model/model-builder.spec.d.ts +0 -1
- package/dist/model/model-builder.spec.js +0 -141
- package/dist/model/model-locator.d.ts +0 -16
- package/dist/model/model-locator.js +0 -108
- package/dist/module.d.ts +0 -18
- package/dist/module.js +0 -61
- package/dist/protocol.d.ts +0 -39
- package/dist/protocol.js +0 -19
- package/dist/references/index.d.ts +0 -2
- package/dist/references/index.js +0 -2
- package/dist/references/scope-computation.d.ts +0 -10
- package/dist/references/scope-computation.js +0 -76
- package/dist/references/scope-provider.d.ts +0 -15
- package/dist/references/scope-provider.js +0 -110
- package/dist/registerProtocolHandlers.d.ts +0 -2
- package/dist/registerProtocolHandlers.js +0 -64
- package/dist/shared/CodeLensProvider.d.ts +0 -8
- package/dist/shared/CodeLensProvider.js +0 -35
- package/dist/shared/WorkspaceManager.d.ts +0 -13
- package/dist/shared/WorkspaceManager.js +0 -19
- package/dist/shared/index.d.ts +0 -2
- package/dist/shared/index.js +0 -2
- package/dist/test/index.d.ts +0 -1
- package/dist/test/index.js +0 -1
- package/dist/test/testServices.d.ts +0 -15
- package/dist/test/testServices.js +0 -57
- package/dist/utils.d.ts +0 -2
- package/dist/utils.js +0 -7
- package/dist/validation/element.d.ts +0 -5
- package/dist/validation/element.js +0 -20
- package/dist/validation/element.spec.d.ts +0 -1
- package/dist/validation/element.spec.js +0 -65
- package/dist/validation/index.d.ts +0 -2
- package/dist/validation/index.js +0 -22
- package/dist/validation/relation.d.ts +0 -4
- package/dist/validation/relation.js +0 -53
- package/dist/validation/relation.spec.d.ts +0 -1
- package/dist/validation/relation.spec.js +0 -93
- package/dist/validation/specification.d.ts +0 -5
- package/dist/validation/specification.js +0 -33
- package/dist/validation/specification.spec.d.ts +0 -1
- package/dist/validation/specification.spec.js +0 -31
- package/dist/validation/view.d.ts +0 -4
- package/dist/validation/view.js +0 -20
- package/dist/validation/view.spec.d.ts +0 -1
- package/dist/validation/view.spec.js +0 -20
package/dist/model/fqn-index.js
DELETED
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
import { DocumentState, DONE_RESULT, MultiMap, StreamImpl } from 'langium';
|
|
2
|
-
import { ast, ElementOps } from '../ast';
|
|
3
|
-
import { logger } from '../logger';
|
|
4
|
-
import { parentFqn } from '@likec4/core/utils';
|
|
5
|
-
import { Fqn } from '@likec4/core/types';
|
|
6
|
-
import { strictElementRefFqn } from '../elementRef';
|
|
7
|
-
export class FqnIndex {
|
|
8
|
-
services;
|
|
9
|
-
// #fqnMap = new WeakMap<ast.Element, Fqn>()
|
|
10
|
-
#index = new MultiMap();
|
|
11
|
-
descriptions;
|
|
12
|
-
constructor(services) {
|
|
13
|
-
this.services = services;
|
|
14
|
-
this.descriptions = services.workspace.AstNodeDescriptionProvider;
|
|
15
|
-
services.shared.workspace.DocumentBuilder.onUpdate((_changed, removed) => {
|
|
16
|
-
for (const uri of [...removed]) {
|
|
17
|
-
this.cleanIndexedElements(uri);
|
|
18
|
-
}
|
|
19
|
-
});
|
|
20
|
-
services.shared.workspace.DocumentBuilder.onBuildPhase(DocumentState.ComputedScopes, (docs, _cancelToken) => {
|
|
21
|
-
for (const doc of docs) {
|
|
22
|
-
this.cleanIndexedElements(doc.uri);
|
|
23
|
-
this.doIndexElements(doc);
|
|
24
|
-
}
|
|
25
|
-
});
|
|
26
|
-
}
|
|
27
|
-
get(el) {
|
|
28
|
-
let fqn = ElementOps.readId(el) ?? null;
|
|
29
|
-
// if ()
|
|
30
|
-
// let fqn = this.#fqnMap.get(el) ?? null
|
|
31
|
-
if (fqn && !this.#index.has(fqn)) {
|
|
32
|
-
const path = this.services.workspace.AstNodeLocator.getAstNodePath(el);
|
|
33
|
-
logger.error(`Clean cached FQN ${fqn} at ${path}`);
|
|
34
|
-
// this.#fqnMap.delete(el)
|
|
35
|
-
fqn = null;
|
|
36
|
-
}
|
|
37
|
-
return fqn;
|
|
38
|
-
}
|
|
39
|
-
byFqn(fqn) {
|
|
40
|
-
return this.#index.get(fqn);
|
|
41
|
-
}
|
|
42
|
-
directChildrenOf(parent) {
|
|
43
|
-
return this.#index
|
|
44
|
-
.entriesGroupedByKey()
|
|
45
|
-
.flatMap(([fqn, descrs]) => (descrs.length === 1 && parentFqn(fqn) === parent ? descrs : []));
|
|
46
|
-
}
|
|
47
|
-
uniqueDescedants(parent) {
|
|
48
|
-
return new StreamImpl(() => {
|
|
49
|
-
const prefix = `${parent}.`;
|
|
50
|
-
const children = [];
|
|
51
|
-
const childrenNames = new Set();
|
|
52
|
-
const descedants = [];
|
|
53
|
-
this.#index.entries().forEach(([fqn, desc]) => {
|
|
54
|
-
if (fqn.startsWith(prefix)) {
|
|
55
|
-
if (parentFqn(fqn) === parent) {
|
|
56
|
-
childrenNames.add(desc.name);
|
|
57
|
-
children.push([fqn, desc]);
|
|
58
|
-
}
|
|
59
|
-
else {
|
|
60
|
-
descedants.push([fqn, desc]);
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
});
|
|
64
|
-
if (children.length + descedants.length === 0) {
|
|
65
|
-
return null;
|
|
66
|
-
}
|
|
67
|
-
const nested = new MultiMap(children.map(([_fqn, desc]) => [desc.name, desc]));
|
|
68
|
-
for (const [_, indexed] of descedants) {
|
|
69
|
-
if (!childrenNames.has(indexed.name)) {
|
|
70
|
-
nested.add(indexed.name, indexed);
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
return nested
|
|
74
|
-
.entriesGroupedByKey()
|
|
75
|
-
.flatMap(([_name, descrs]) => (descrs.length === 1 ? descrs : []))
|
|
76
|
-
.iterator();
|
|
77
|
-
}, iterator => {
|
|
78
|
-
if (iterator) {
|
|
79
|
-
return iterator.next();
|
|
80
|
-
}
|
|
81
|
-
return DONE_RESULT;
|
|
82
|
-
});
|
|
83
|
-
}
|
|
84
|
-
doIndexElements(doc) {
|
|
85
|
-
const visitElement = (element, parent = null) => {
|
|
86
|
-
try {
|
|
87
|
-
const name = element.name;
|
|
88
|
-
const fqn = Fqn(name, parent);
|
|
89
|
-
this.#index.add(fqn, this.descriptions.createDescription(element, name, doc));
|
|
90
|
-
ElementOps.writeId(element, fqn);
|
|
91
|
-
// this.#fqnMap.set(element, fqn)
|
|
92
|
-
if (element.body) {
|
|
93
|
-
for (const nested of element.body.elements) {
|
|
94
|
-
if (ast.isElement(nested)) {
|
|
95
|
-
visitElement(nested, fqn);
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
catch (e) {
|
|
101
|
-
logger.warn(e);
|
|
102
|
-
}
|
|
103
|
-
};
|
|
104
|
-
const visitExtendElement = (extendElement) => {
|
|
105
|
-
try {
|
|
106
|
-
const fqn = strictElementRefFqn(extendElement.element);
|
|
107
|
-
for (const nested of extendElement.body.elements) {
|
|
108
|
-
if (ast.isElement(nested)) {
|
|
109
|
-
visitElement(nested, fqn);
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
catch (e) {
|
|
114
|
-
logger.warn(e);
|
|
115
|
-
}
|
|
116
|
-
};
|
|
117
|
-
const elements = doc.parseResult.value.model?.elements ?? [];
|
|
118
|
-
for (const modelElement of elements) {
|
|
119
|
-
if (ast.isExtendElement(modelElement)) {
|
|
120
|
-
visitExtendElement(modelElement);
|
|
121
|
-
continue;
|
|
122
|
-
}
|
|
123
|
-
if (ast.isElement(modelElement)) {
|
|
124
|
-
visitElement(modelElement);
|
|
125
|
-
continue;
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
cleanIndexedElements(docUri) {
|
|
130
|
-
const docUriAsString = docUri.toString();
|
|
131
|
-
const toDelete = this.#index
|
|
132
|
-
.entries()
|
|
133
|
-
.filter(([, indexed]) => indexed.documentUri.toString() === docUriAsString);
|
|
134
|
-
for (const [fqn, indexed] of toDelete.toArray()) {
|
|
135
|
-
this.#index.delete(fqn, indexed);
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
}
|
package/dist/model/index.d.ts
DELETED
package/dist/model/index.js
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,332 +0,0 @@
|
|
|
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 '@likec4/language-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
|
-
invariant(!(el.id in acc), 'Duplicate element id: ' + el.id);
|
|
83
|
-
acc[el.id] = el;
|
|
84
|
-
}
|
|
85
|
-
return acc;
|
|
86
|
-
}));
|
|
87
|
-
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) => {
|
|
88
|
-
invariant(!(el.id in acc), 'Duplicate relation id: ' + el.id);
|
|
89
|
-
acc[el.id] = el;
|
|
90
|
-
return acc;
|
|
91
|
-
}));
|
|
92
|
-
const toModelView = (view) => {
|
|
93
|
-
let title = view.title;
|
|
94
|
-
if (!title && view.viewOf) {
|
|
95
|
-
title = elements[view.viewOf]?.title;
|
|
96
|
-
}
|
|
97
|
-
return {
|
|
98
|
-
...omit(['astPath', 'rules', 'tite'], view),
|
|
99
|
-
...(!!title ? { title } : {}),
|
|
100
|
-
rules: clone(view.rules)
|
|
101
|
-
};
|
|
102
|
-
};
|
|
103
|
-
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) => {
|
|
104
|
-
invariant(!(v.id in acc), 'Duplicate view id: ' + v.id);
|
|
105
|
-
acc[v.id] = v;
|
|
106
|
-
return acc;
|
|
107
|
-
}));
|
|
108
|
-
return computeViews({
|
|
109
|
-
elements,
|
|
110
|
-
relations,
|
|
111
|
-
views
|
|
112
|
-
});
|
|
113
|
-
}
|
|
114
|
-
catch (e) {
|
|
115
|
-
logger.error(e);
|
|
116
|
-
return;
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
/**
|
|
120
|
-
* @returns if the document was changed
|
|
121
|
-
*/
|
|
122
|
-
parseDocument(doc) {
|
|
123
|
-
const { elements, relations, views, specification } = cleanParsedModel(doc);
|
|
124
|
-
const spec = doc.parseResult.value.specification;
|
|
125
|
-
if (spec) {
|
|
126
|
-
for (const { kind, style } of spec.elementKinds) {
|
|
127
|
-
try {
|
|
128
|
-
const styleProps = toElementStyle(style?.props);
|
|
129
|
-
specification.kinds[kind.name] = {
|
|
130
|
-
color: styleProps.color ?? DefaultThemeColor,
|
|
131
|
-
shape: styleProps.shape ?? DefaultElementShape
|
|
132
|
-
};
|
|
133
|
-
}
|
|
134
|
-
catch (e) {
|
|
135
|
-
logger.warn(e);
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
for (const el of streamModel(doc)) {
|
|
140
|
-
if (ast.isElement(el)) {
|
|
141
|
-
try {
|
|
142
|
-
elements.push(this.parseElement(el));
|
|
143
|
-
}
|
|
144
|
-
catch (e) {
|
|
145
|
-
logger.warn(e);
|
|
146
|
-
}
|
|
147
|
-
continue;
|
|
148
|
-
}
|
|
149
|
-
if (ast.isRelation(el)) {
|
|
150
|
-
try {
|
|
151
|
-
relations.push(this.parseRelation(el));
|
|
152
|
-
}
|
|
153
|
-
catch (e) {
|
|
154
|
-
logger.warn(e);
|
|
155
|
-
}
|
|
156
|
-
continue;
|
|
157
|
-
}
|
|
158
|
-
failExpectedNever(el);
|
|
159
|
-
}
|
|
160
|
-
const docviews = doc.parseResult.value.views?.views;
|
|
161
|
-
if (docviews) {
|
|
162
|
-
for (const view of docviews) {
|
|
163
|
-
try {
|
|
164
|
-
const v = this.parseElementView(view);
|
|
165
|
-
ElementViewOps.writeId(view, v.id);
|
|
166
|
-
views.push(v);
|
|
167
|
-
}
|
|
168
|
-
catch (e) {
|
|
169
|
-
logger.warn(e);
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
const prevHash = doc.c4hash ?? '';
|
|
174
|
-
doc.c4hash = c4hash(doc);
|
|
175
|
-
return prevHash !== doc.c4hash;
|
|
176
|
-
}
|
|
177
|
-
parseElement(astNode) {
|
|
178
|
-
const id = this.resolveFqn(astNode);
|
|
179
|
-
invariant(astNode.kind.ref, 'Element kind is not resolved: ' + astNode.name);
|
|
180
|
-
const kind = astNode.kind.ref.name;
|
|
181
|
-
const tags = (astNode.body && this.convertTags(astNode.body)) ?? [];
|
|
182
|
-
const styleProps = astNode.body?.props.find(ast.isElementStyleProperty)?.props;
|
|
183
|
-
const { color, shape } = toElementStyle(styleProps);
|
|
184
|
-
const astPath = this.getAstNodePath(astNode);
|
|
185
|
-
let [title, description, technology] = astNode.props;
|
|
186
|
-
const bodyProps = astNode.body?.props.filter((p) => ast.isElementStringProperty(p)) ?? [];
|
|
187
|
-
title = title ?? bodyProps.find(p => p.key === 'title')?.value;
|
|
188
|
-
description = description ?? bodyProps.find(p => p.key === 'description')?.value;
|
|
189
|
-
technology = technology ?? bodyProps.find(p => p.key === 'technology')?.value;
|
|
190
|
-
return {
|
|
191
|
-
id,
|
|
192
|
-
kind,
|
|
193
|
-
astPath,
|
|
194
|
-
title: title ?? astNode.name,
|
|
195
|
-
...(technology && { technology }),
|
|
196
|
-
...(description && { description }),
|
|
197
|
-
...(tags.length > 0 ? { tags } : {}),
|
|
198
|
-
...(shape && shape !== DefaultElementShape ? { shape } : {}),
|
|
199
|
-
...(color && color !== DefaultThemeColor ? { color } : {})
|
|
200
|
-
};
|
|
201
|
-
}
|
|
202
|
-
parseRelation(astNode) {
|
|
203
|
-
const coupling = resolveRelationPoints(astNode);
|
|
204
|
-
const target = this.resolveFqn(coupling.target);
|
|
205
|
-
const source = this.resolveFqn(coupling.source);
|
|
206
|
-
const hashdata = {
|
|
207
|
-
astPath: this.getAstNodePath(astNode),
|
|
208
|
-
source,
|
|
209
|
-
target
|
|
210
|
-
};
|
|
211
|
-
const id = objectHash(hashdata);
|
|
212
|
-
const title = astNode.title ?? astNode.definition?.props.find(p => p.key === 'title')?.value ?? '';
|
|
213
|
-
return {
|
|
214
|
-
id,
|
|
215
|
-
...hashdata,
|
|
216
|
-
title
|
|
217
|
-
};
|
|
218
|
-
}
|
|
219
|
-
parseElementExpression(astNode) {
|
|
220
|
-
if (ast.isWildcardExpression(astNode)) {
|
|
221
|
-
return {
|
|
222
|
-
wildcard: true
|
|
223
|
-
};
|
|
224
|
-
}
|
|
225
|
-
if (ast.isElementRefExpression(astNode)) {
|
|
226
|
-
const element = elementRef(astNode.id);
|
|
227
|
-
invariant(element, 'Element not found ' + astNode.id.$cstNode?.text);
|
|
228
|
-
return {
|
|
229
|
-
element: this.resolveFqn(element),
|
|
230
|
-
isDescedants: astNode.isDescedants
|
|
231
|
-
};
|
|
232
|
-
}
|
|
233
|
-
failExpectedNever(astNode);
|
|
234
|
-
}
|
|
235
|
-
parseExpression(astNode) {
|
|
236
|
-
if (ast.isElementExpression(astNode)) {
|
|
237
|
-
return this.parseElementExpression(astNode);
|
|
238
|
-
}
|
|
239
|
-
if (ast.isIncomingExpression(astNode)) {
|
|
240
|
-
return {
|
|
241
|
-
incoming: this.parseElementExpression(astNode.target)
|
|
242
|
-
};
|
|
243
|
-
}
|
|
244
|
-
if (ast.isOutgoingExpression(astNode)) {
|
|
245
|
-
return {
|
|
246
|
-
outgoing: this.parseElementExpression(astNode.source)
|
|
247
|
-
};
|
|
248
|
-
}
|
|
249
|
-
if (ast.isInOutExpression(astNode)) {
|
|
250
|
-
return {
|
|
251
|
-
inout: this.parseElementExpression(astNode.inout.target)
|
|
252
|
-
};
|
|
253
|
-
}
|
|
254
|
-
if (ast.isRelationExpression(astNode)) {
|
|
255
|
-
return {
|
|
256
|
-
source: this.parseElementExpression(astNode.source),
|
|
257
|
-
target: this.parseElementExpression(astNode.target)
|
|
258
|
-
};
|
|
259
|
-
}
|
|
260
|
-
failExpectedNever(astNode);
|
|
261
|
-
}
|
|
262
|
-
parseViewRule(astRule) {
|
|
263
|
-
if (ast.isViewRuleExpression(astRule)) {
|
|
264
|
-
const exprs = astRule.expressions.map(n => this.parseExpression(n));
|
|
265
|
-
return {
|
|
266
|
-
isInclude: astRule.isInclude,
|
|
267
|
-
exprs
|
|
268
|
-
};
|
|
269
|
-
}
|
|
270
|
-
if (ast.isViewRuleStyle(astRule)) {
|
|
271
|
-
const styleProps = toElementStyle(astRule.props);
|
|
272
|
-
return {
|
|
273
|
-
targets: astRule.targets.map(n => this.parseElementExpression(n)),
|
|
274
|
-
style: {
|
|
275
|
-
...styleProps
|
|
276
|
-
}
|
|
277
|
-
};
|
|
278
|
-
}
|
|
279
|
-
if (ast.isViewRuleAutoLayout(astRule)) {
|
|
280
|
-
return {
|
|
281
|
-
autoLayout: toAutoLayout(astRule.direction)
|
|
282
|
-
};
|
|
283
|
-
}
|
|
284
|
-
failExpectedNever(astRule);
|
|
285
|
-
}
|
|
286
|
-
parseElementView(astNode) {
|
|
287
|
-
const viewOfEl = astNode.viewOf && elementRef(astNode.viewOf);
|
|
288
|
-
const viewOf = viewOfEl && this.resolveFqn(viewOfEl);
|
|
289
|
-
const astPath = this.getAstNodePath(astNode);
|
|
290
|
-
let id = astNode.name;
|
|
291
|
-
if (!id) {
|
|
292
|
-
const doc = getDocument(astNode).uri.toString();
|
|
293
|
-
id = objectHash({
|
|
294
|
-
doc,
|
|
295
|
-
astPath,
|
|
296
|
-
viewOf: viewOf ?? null
|
|
297
|
-
});
|
|
298
|
-
}
|
|
299
|
-
const title = astNode.properties.find(p => p.key === 'title')?.value;
|
|
300
|
-
const description = astNode.properties.find(p => p.key === 'description')?.value;
|
|
301
|
-
return {
|
|
302
|
-
id,
|
|
303
|
-
astPath,
|
|
304
|
-
...(viewOf && { viewOf }),
|
|
305
|
-
...(title && { title }),
|
|
306
|
-
...(description && { description }),
|
|
307
|
-
rules: astNode.rules.map(n => this.parseViewRule(n))
|
|
308
|
-
};
|
|
309
|
-
}
|
|
310
|
-
resolveFqn(node) {
|
|
311
|
-
if (ast.isExtendElement(node)) {
|
|
312
|
-
return strictElementRefFqn(node.element);
|
|
313
|
-
}
|
|
314
|
-
const fqn = this.fqnIndex.get(node);
|
|
315
|
-
invariant(fqn, `Not indexed element: ${this.getAstNodePath(node)}`);
|
|
316
|
-
return fqn;
|
|
317
|
-
}
|
|
318
|
-
getAstNodePath(node) {
|
|
319
|
-
return this.services.workspace.AstNodeLocator.getAstNodePath(node);
|
|
320
|
-
}
|
|
321
|
-
convertTags(el) {
|
|
322
|
-
return el.tags?.value.map(tagRef => tagRef.ref?.name) ?? [];
|
|
323
|
-
}
|
|
324
|
-
async notifyClient() {
|
|
325
|
-
const connection = this.connection;
|
|
326
|
-
if (!connection) {
|
|
327
|
-
return;
|
|
328
|
-
}
|
|
329
|
-
logger.debug('Send onDidChangeModel');
|
|
330
|
-
await connection.sendNotification(Rpc.onDidChangeModel);
|
|
331
|
-
}
|
|
332
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import { createTestServices } from '../test';
|
|
3
|
-
import { keys } from 'rambdax';
|
|
4
|
-
describe('LikeC4ModelBuilder', () => {
|
|
5
|
-
it('builds model', async () => {
|
|
6
|
-
const { validate, buildModel } = createTestServices();
|
|
7
|
-
const { diagnostics } = await validate(`
|
|
8
|
-
specification {
|
|
9
|
-
element component
|
|
10
|
-
element user {
|
|
11
|
-
style {
|
|
12
|
-
shape: person
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
tag deprecated
|
|
16
|
-
}
|
|
17
|
-
model {
|
|
18
|
-
user client {
|
|
19
|
-
-> frontend
|
|
20
|
-
}
|
|
21
|
-
component system {
|
|
22
|
-
backend = component 'Backend' {
|
|
23
|
-
technology 'NodeJS'
|
|
24
|
-
|
|
25
|
-
style {
|
|
26
|
-
color secondary
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
component frontend {
|
|
30
|
-
#deprecated
|
|
31
|
-
description 'Frontend description'
|
|
32
|
-
|
|
33
|
-
style {
|
|
34
|
-
color: muted
|
|
35
|
-
shape: browser
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
-> backend 'requests'
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
`);
|
|
43
|
-
expect(diagnostics).toHaveLength(0);
|
|
44
|
-
const model = await buildModel();
|
|
45
|
-
expect(model).toBeDefined();
|
|
46
|
-
expect(model.elements).toMatchObject({
|
|
47
|
-
client: {
|
|
48
|
-
kind: 'user',
|
|
49
|
-
shape: 'person'
|
|
50
|
-
},
|
|
51
|
-
'system.backend': {
|
|
52
|
-
color: 'secondary',
|
|
53
|
-
title: 'Backend',
|
|
54
|
-
technology: 'NodeJS'
|
|
55
|
-
},
|
|
56
|
-
'system.frontend': {
|
|
57
|
-
color: 'muted',
|
|
58
|
-
shape: 'browser',
|
|
59
|
-
description: 'Frontend description'
|
|
60
|
-
}
|
|
61
|
-
});
|
|
62
|
-
expect(model.elements['client']).not.toHaveProperty('color');
|
|
63
|
-
expect(model.elements['system']).not.toHaveProperty('color');
|
|
64
|
-
expect(model.elements['system']).not.toHaveProperty('shape');
|
|
65
|
-
expect(model.elements['system.backend']).toHaveProperty('color', 'secondary');
|
|
66
|
-
expect(model.elements['system.backend']).not.toHaveProperty('description');
|
|
67
|
-
expect(model).toMatchSnapshot();
|
|
68
|
-
});
|
|
69
|
-
it('builds model with extend', async () => {
|
|
70
|
-
const { parse, validateAll, buildModel } = createTestServices();
|
|
71
|
-
await parse(`
|
|
72
|
-
specification {
|
|
73
|
-
element component
|
|
74
|
-
element user
|
|
75
|
-
tag deprecated
|
|
76
|
-
}
|
|
77
|
-
model {
|
|
78
|
-
user client
|
|
79
|
-
component system {
|
|
80
|
-
backend = component
|
|
81
|
-
component frontend
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
`);
|
|
85
|
-
await parse(`
|
|
86
|
-
model {
|
|
87
|
-
extend system.backend {
|
|
88
|
-
component api
|
|
89
|
-
}
|
|
90
|
-
system.frontend -> api 'requests'
|
|
91
|
-
client -> system.frontend {
|
|
92
|
-
title 'opens'
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
views {
|
|
96
|
-
view index {
|
|
97
|
-
title 'Index'
|
|
98
|
-
include *
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
view v1 of api {
|
|
102
|
-
include *
|
|
103
|
-
autoLayout LeftRight
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
view of system.frontend {
|
|
107
|
-
include *
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
`);
|
|
111
|
-
const { errors } = await validateAll();
|
|
112
|
-
expect(errors).toEqual([]);
|
|
113
|
-
const model = await buildModel();
|
|
114
|
-
expect(model).toBeDefined();
|
|
115
|
-
expect(model.elements).toMatchObject({
|
|
116
|
-
client: {
|
|
117
|
-
kind: 'user'
|
|
118
|
-
},
|
|
119
|
-
'system.backend.api': {
|
|
120
|
-
kind: 'component'
|
|
121
|
-
}
|
|
122
|
-
});
|
|
123
|
-
expect(keys(model.relations)).toHaveLength(2);
|
|
124
|
-
expect(keys(model.views)).toHaveLength(3);
|
|
125
|
-
expect(model.views).toMatchObject({
|
|
126
|
-
index: {
|
|
127
|
-
id: 'index',
|
|
128
|
-
title: 'Index',
|
|
129
|
-
autoLayout: 'TB'
|
|
130
|
-
},
|
|
131
|
-
v1: {
|
|
132
|
-
id: 'v1',
|
|
133
|
-
viewOf: 'system.backend.api',
|
|
134
|
-
title: 'api',
|
|
135
|
-
autoLayout: 'LR'
|
|
136
|
-
}
|
|
137
|
-
});
|
|
138
|
-
expect(model.views['index']).not.toHaveProperty('viewOf');
|
|
139
|
-
expect(model).toMatchSnapshot();
|
|
140
|
-
});
|
|
141
|
-
});
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import type * as c4 from '@likec4/core/types';
|
|
2
|
-
import type { Location } from 'vscode-languageserver-protocol';
|
|
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
|
-
}
|