@likec4/language-server 1.23.1 → 1.24.1

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 (73) hide show
  1. package/dist/LikeC4FileSystem.d.ts +1 -0
  2. package/dist/LikeC4FileSystem.js +7 -0
  3. package/dist/Rpc.js +13 -11
  4. package/dist/ast.d.ts +13 -29
  5. package/dist/ast.js +3 -70
  6. package/dist/bundled.mjs +2441 -2610
  7. package/dist/generated/ast.d.ts +36 -8
  8. package/dist/generated/ast.js +44 -2
  9. package/dist/generated/grammar.js +1 -1
  10. package/dist/index.d.ts +1 -0
  11. package/dist/index.js +1 -0
  12. package/dist/likec4lib.d.ts +2 -0
  13. package/dist/likec4lib.js +3 -0
  14. package/dist/lsp/CodeLensProvider.js +10 -6
  15. package/dist/lsp/CompletionProvider.js +20 -2
  16. package/dist/lsp/DocumentLinkProvider.d.ts +3 -3
  17. package/dist/lsp/DocumentLinkProvider.js +5 -5
  18. package/dist/lsp/DocumentSymbolProvider.d.ts +1 -1
  19. package/dist/lsp/DocumentSymbolProvider.js +5 -2
  20. package/dist/lsp/HoverProvider.js +20 -7
  21. package/dist/lsp/SemanticTokenProvider.js +18 -1
  22. package/dist/model/builder/MergedExtends.d.ts +12 -0
  23. package/dist/model/builder/MergedExtends.js +67 -0
  24. package/dist/model/builder/MergedSpecification.d.ts +29 -0
  25. package/dist/model/builder/MergedSpecification.js +203 -0
  26. package/dist/model/builder/buildModel.d.ts +3 -0
  27. package/dist/model/builder/buildModel.js +194 -0
  28. package/dist/model/deployments-index.d.ts +5 -56
  29. package/dist/model/deployments-index.js +61 -137
  30. package/dist/model/fqn-index.d.ts +50 -19
  31. package/dist/model/fqn-index.js +176 -69
  32. package/dist/model/index.d.ts +0 -1
  33. package/dist/model/index.js +0 -1
  34. package/dist/model/model-builder.d.ts +10 -9
  35. package/dist/model/model-builder.js +102 -547
  36. package/dist/model/model-locator.d.ts +2 -1
  37. package/dist/model/model-locator.js +7 -9
  38. package/dist/model/model-parser.d.ts +156 -150
  39. package/dist/model/model-parser.js +68 -38
  40. package/dist/model/parser/Base.d.ts +3 -3
  41. package/dist/model/parser/Base.js +15 -9
  42. package/dist/model/parser/DeploymentModelParser.d.ts +4 -3
  43. package/dist/model/parser/DeploymentModelParser.js +54 -3
  44. package/dist/model/parser/DeploymentViewParser.d.ts +3 -2
  45. package/dist/model/parser/FqnRefParser.d.ts +2 -2
  46. package/dist/model/parser/GlobalsParser.d.ts +3 -2
  47. package/dist/model/parser/ModelParser.d.ts +4 -4
  48. package/dist/model/parser/ModelParser.js +45 -4
  49. package/dist/model/parser/PredicatesParser.d.ts +2 -2
  50. package/dist/model/parser/SpecificationParser.d.ts +2 -2
  51. package/dist/model/parser/ViewsParser.d.ts +3 -2
  52. package/dist/module.d.ts +2 -3
  53. package/dist/module.js +2 -3
  54. package/dist/references/scope-computation.d.ts +1 -1
  55. package/dist/references/scope-computation.js +14 -11
  56. package/dist/references/scope-provider.d.ts +16 -4
  57. package/dist/references/scope-provider.js +64 -30
  58. package/dist/test/testServices.d.ts +2 -1
  59. package/dist/test/testServices.js +23 -20
  60. package/dist/utils/elementRef.d.ts +1 -1
  61. package/dist/utils/elementRef.js +3 -3
  62. package/dist/validation/deployment-checks.d.ts +1 -0
  63. package/dist/validation/deployment-checks.js +12 -0
  64. package/dist/validation/index.d.ts +1 -1
  65. package/dist/validation/index.js +8 -1
  66. package/dist/views/configurable-layouter.js +3 -3
  67. package/dist/views/likec4-views.d.ts +1 -0
  68. package/dist/views/likec4-views.js +11 -11
  69. package/package.json +12 -13
  70. package/dist/bundled.d.ts +0 -8
  71. package/dist/bundled.js +0 -25
  72. package/dist/model/fqn-computation.d.ts +0 -3
  73. package/dist/model/fqn-computation.js +0 -72
@@ -1,103 +1,210 @@
1
- import { nameFromFqn, parentFqn } from "@likec4/core";
2
- import { DocumentState, DONE_RESULT, MultiMap, stream, StreamImpl } from "langium";
3
- import { ElementOps, isFqnIndexedDocument, isLikeC4LangiumDocument } from "../ast.js";
4
- import { logger, logWarnError } from "../logger.js";
1
+ import { invariant, nonNullable } from "@likec4/core";
2
+ import { AsFqn } from "@likec4/core/types";
3
+ import { ancestorsFqn, compareNatural, DefaultWeakMap, MultiMap, sortNaturalByFqn } from "@likec4/core/utils";
4
+ import {
5
+ AstUtils,
6
+ DocumentState,
7
+ stream,
8
+ WorkspaceCache
9
+ } from "langium";
10
+ import { isDefined, isEmpty, isTruthy } from "remeda";
11
+ import {
12
+ ast,
13
+ ElementOps,
14
+ isLikeC4LangiumDocument
15
+ } from "../ast.js";
16
+ import { logWarnError } from "../logger.js";
5
17
  import { ADisposable } from "../utils/index.js";
6
- import { computeDocumentFqn } from "./fqn-computation.js";
18
+ import { readStrictFqn } from "../utils/elementRef.js";
7
19
  export class FqnIndex extends ADisposable {
8
- constructor(services) {
20
+ constructor(services, cachePrefix = "fqn-index") {
9
21
  super();
10
22
  this.services = services;
23
+ this.cachePrefix = cachePrefix;
11
24
  this.langiumDocuments = services.shared.workspace.LangiumDocuments;
25
+ this.documentCache = new DefaultWeakMap((doc) => this.createDocumentIndex(doc));
26
+ this.workspaceCache = new WorkspaceCache(services.shared, DocumentState.IndexedContent);
12
27
  this.onDispose(
13
- services.shared.workspace.DocumentBuilder.onBuildPhase(
28
+ services.shared.workspace.DocumentBuilder.onDocumentPhase(
14
29
  DocumentState.IndexedContent,
15
- async (docs, _cancelToken) => {
16
- for (const doc of docs) {
17
- if (isLikeC4LangiumDocument(doc)) {
18
- delete doc.c4fqnIndex;
19
- delete doc.c4Elements;
20
- delete doc.c4Specification;
21
- delete doc.c4Relations;
22
- delete doc.c4Deployments;
23
- delete doc.c4DeploymentRelations;
24
- delete doc.c4Globals;
25
- delete doc.c4Views;
26
- try {
27
- computeDocumentFqn(doc, services);
28
- } catch (e) {
29
- logWarnError(e);
30
- }
31
- }
30
+ async (doc, _cancelToken) => {
31
+ if (isLikeC4LangiumDocument(doc)) {
32
+ this.documentCache.set(doc, this.createDocumentIndex(doc));
32
33
  }
33
34
  return await Promise.resolve();
34
35
  }
35
36
  )
36
37
  );
37
- logger.debug(`[FqnIndex] Created`);
38
38
  }
39
39
  langiumDocuments;
40
- get documents() {
41
- return this.langiumDocuments.all.filter(isFqnIndexedDocument);
40
+ documentCache;
41
+ workspaceCache;
42
+ documents() {
43
+ return this.langiumDocuments.all.filter(
44
+ (d) => isLikeC4LangiumDocument(d) && d.state >= DocumentState.IndexedContent
45
+ );
42
46
  }
43
- entries(filterByFqn) {
44
- return this.documents.flatMap((doc) => {
45
- return doc.c4fqnIndex.keys().filter(filterByFqn).flatMap((fqn) => doc.c4fqnIndex.get(fqn));
46
- });
47
+ get(document) {
48
+ if (document.state < DocumentState.IndexedContent) {
49
+ logWarnError(`Document ${document.uri.path} is not indexed`);
50
+ }
51
+ return this.documentCache.get(document);
47
52
  }
48
53
  getFqn(el) {
49
- return ElementOps.readId(el) ?? null;
54
+ invariant(ast.isElement(el) || ast.isDeploymentElement(el));
55
+ let id = ElementOps.readId(el);
56
+ if (isTruthy(id)) {
57
+ return id;
58
+ }
59
+ const doc = AstUtils.getDocument(el);
60
+ invariant(isLikeC4LangiumDocument(doc));
61
+ logWarnError(`Document ${doc.uri.path} is not indexed, but getFqn was called`);
62
+ this.get(doc);
63
+ return nonNullable(ElementOps.readId(el), "Element fqn must be set, invalid state");
50
64
  }
51
65
  byFqn(fqn) {
52
- return this.documents.flatMap((doc) => {
53
- return doc.c4fqnIndex.get(fqn);
54
- });
66
+ return stream(this.workspaceCache.get(`${this.cachePrefix}:${fqn}`, () => {
67
+ return this.documents().toArray().flatMap((doc) => {
68
+ return this.get(doc).byFqn(fqn);
69
+ });
70
+ }));
55
71
  }
56
72
  directChildrenOf(parent) {
57
- return stream([parent]).flatMap((_parent) => {
58
- const children = this.entries((fqn) => parentFqn(fqn) === _parent).map((entry) => [entry.name, entry]).toArray();
59
- if (children.length === 0) {
60
- return [];
61
- }
62
- return new MultiMap(children).entriesGroupedByKey().flatMap(([_name, descrs]) => descrs.length === 1 ? descrs : []).iterator();
63
- });
73
+ return stream(
74
+ this.workspaceCache.get(`${this.cachePrefix}:directChildrenOf:${parent}`, () => {
75
+ const allchildren = this.documents().reduce((map, doc) => {
76
+ this.get(doc).children(parent).forEach((desc) => {
77
+ map.set(desc.name, desc);
78
+ });
79
+ return map;
80
+ }, new MultiMap());
81
+ return uniqueByName(allchildren).sort((a, b) => compareNatural(a.name, b.name));
82
+ })
83
+ );
64
84
  }
65
85
  /**
66
86
  * Returns descedant elements with unique names in the scope
67
87
  */
68
88
  uniqueDescedants(parent) {
69
- return new StreamImpl(
70
- () => {
71
- const prefix = `${parent}.`;
72
- const childrenNames = /* @__PURE__ */ new Set();
73
- const descedants = [];
74
- const nested = new MultiMap();
75
- this.entries((f) => f.startsWith(prefix)).forEach((e) => {
76
- const name = nameFromFqn(e.fqn);
77
- const entry = { ...e, name };
78
- if (parentFqn(e.fqn) === parent) {
79
- childrenNames.add(name);
80
- nested.add(name, entry);
81
- } else {
82
- descedants.push(entry);
83
- }
89
+ return stream(
90
+ this.workspaceCache.get(`${this.cachePrefix}:uniqueDescedants:${parent}`, () => {
91
+ const { children, descendants } = this.documents().reduce((map, doc) => {
92
+ const docIndex = this.get(doc);
93
+ docIndex.children(parent).forEach((desc) => {
94
+ map.children.set(desc.name, desc);
95
+ });
96
+ docIndex.descendants(parent).forEach((desc) => {
97
+ map.descendants.set(desc.name, desc);
98
+ });
99
+ return map;
100
+ }, {
101
+ children: new MultiMap(),
102
+ descendants: new MultiMap()
84
103
  });
85
- if (nested.size + descedants.length === 0) {
86
- return null;
104
+ const uniqueChildren = uniqueByName(children).sort((a, b) => compareNatural(a.name, b.name));
105
+ const uniqueDescendants = [...descendants.associations()].flatMap(([_name, descs]) => descs.length === 1 && !children.has(_name) ? descs : []);
106
+ return [
107
+ ...uniqueChildren,
108
+ ...sortNaturalByFqn(uniqueDescendants)
109
+ ];
110
+ })
111
+ );
112
+ }
113
+ createDocumentIndex(document) {
114
+ const rootElements = document.parseResult.value.models.flatMap((m) => m.elements);
115
+ if (rootElements.length === 0) {
116
+ return DocumentFqnIndex.EMPTY;
117
+ }
118
+ const root = new Array();
119
+ const children = new MultiMap();
120
+ const descendants = new MultiMap();
121
+ const byfqn = new MultiMap();
122
+ const Descriptions = this.services.workspace.AstNodeDescriptionProvider;
123
+ const createAndSaveDescription = (node, name, fqn) => {
124
+ const desc = {
125
+ ...Descriptions.createDescription(node, name, document),
126
+ id: fqn
127
+ };
128
+ ElementOps.writeId(node, fqn);
129
+ byfqn.set(fqn, desc);
130
+ return desc;
131
+ };
132
+ const traverseNode = (el, parentFqn) => {
133
+ let thisFqn;
134
+ if (ast.isElement(el)) {
135
+ thisFqn = AsFqn(el.name, parentFqn);
136
+ const desc = createAndSaveDescription(el, el.name, thisFqn);
137
+ if (!parentFqn) {
138
+ root.push(desc);
139
+ } else {
140
+ children.set(parentFqn, desc);
87
141
  }
88
- for (const descedant of descedants) {
89
- if (!childrenNames.has(descedant.name)) {
90
- nested.add(descedant.name, descedant);
142
+ } else {
143
+ thisFqn = readStrictFqn(el.element);
144
+ }
145
+ let _nested = [];
146
+ if (isDefined(el.body) && !isEmpty(el.body.elements)) {
147
+ for (const child of el.body.elements) {
148
+ if (!ast.isRelation(child)) {
149
+ try {
150
+ _nested.push(...traverseNode(child, thisFqn));
151
+ } catch (e) {
152
+ logWarnError(e);
153
+ }
91
154
  }
92
155
  }
93
- return nested.entriesGroupedByKey().flatMap(([_name, descrs]) => descrs.length === 1 ? descrs : []).iterator();
94
- },
95
- (iterator) => {
96
- if (iterator) {
97
- return iterator.next();
156
+ }
157
+ const directChildren = children.get(thisFqn) ?? [];
158
+ _nested = [
159
+ ...directChildren,
160
+ ..._nested
161
+ ];
162
+ for (const child of _nested) {
163
+ descendants.set(thisFqn, child);
164
+ }
165
+ if (ast.isExtendElement(el)) {
166
+ for (const ancestor of ancestorsFqn(thisFqn)) {
167
+ for (const child of _nested) {
168
+ descendants.set(ancestor, child);
169
+ }
98
170
  }
99
- return DONE_RESULT;
100
171
  }
101
- );
172
+ return descendants.get(thisFqn) ?? [];
173
+ };
174
+ for (const node of rootElements) {
175
+ try {
176
+ if (ast.isRelation(node)) {
177
+ continue;
178
+ }
179
+ traverseNode(node, null);
180
+ } catch (e) {
181
+ logWarnError(e);
182
+ }
183
+ }
184
+ return new DocumentFqnIndex(root, children, descendants, byfqn);
185
+ }
186
+ }
187
+ function uniqueByName(multimap) {
188
+ return [...multimap.associations()].flatMap(([_name, descs]) => descs.length === 1 ? descs : []);
189
+ }
190
+ export class DocumentFqnIndex {
191
+ constructor(_rootElements, _children, _descendants, _byfqn) {
192
+ this._rootElements = _rootElements;
193
+ this._children = _children;
194
+ this._descendants = _descendants;
195
+ this._byfqn = _byfqn;
196
+ }
197
+ static EMPTY = new DocumentFqnIndex([], new MultiMap(), new MultiMap(), new MultiMap());
198
+ rootElements() {
199
+ return this._rootElements;
200
+ }
201
+ byFqn(fqn) {
202
+ return this._byfqn.get(fqn) ?? [];
203
+ }
204
+ children(parent) {
205
+ return this._children.get(parent) ?? [];
206
+ }
207
+ descendants(nodeName) {
208
+ return this._descendants.get(nodeName) ?? [];
102
209
  }
103
210
  }
@@ -1,5 +1,4 @@
1
1
  export * from './deployments-index';
2
- export * from './fqn-computation';
3
2
  export * from './fqn-index';
4
3
  export * from './model-builder';
5
4
  export * from './model-locator';
@@ -1,5 +1,4 @@
1
1
  export * from "./deployments-index.js";
2
- export * from "./fqn-computation.js";
3
2
  export * from "./fqn-index.js";
4
3
  export * from "./model-builder.js";
5
4
  export * from "./model-locator.js";
@@ -1,30 +1,31 @@
1
1
  import type * as c4 from '@likec4/core';
2
- import { type ViewId } from '@likec4/core';
3
- import type { Cancellation, URI } from 'langium';
4
- import { Disposable } from 'langium';
2
+ import { type ViewId, LikeC4Model } from '@likec4/core';
3
+ import { type Cancellation, type URI, Disposable } from 'langium';
5
4
  import type { LikeC4Services } from '../module';
6
5
  import { ADisposable } from '../utils';
7
6
  type ModelParsedListener = (docs: URI[]) => void;
8
7
  export declare class LikeC4ModelBuilder extends ADisposable {
9
- private services;
10
- private langiumDocuments;
8
+ private parser;
11
9
  private listeners;
10
+ private cache;
11
+ private DocumentBuilder;
12
+ private LangiumDocuments;
12
13
  constructor(services: LikeC4Services);
13
14
  /**
14
15
  * WARNING:
15
16
  * This method is internal and should to be called only when all documents are known to be parsed.
16
17
  * Otherwise, the model may be incomplete.
17
18
  */
18
- unsafeSyncBuildModel(): c4.ParsedLikeC4Model | null;
19
- buildModel(cancelToken?: Cancellation.CancellationToken): Promise<c4.ParsedLikeC4Model | null>;
19
+ private unsafeSyncParseModel;
20
+ parseModel(cancelToken?: Cancellation.CancellationToken): Promise<c4.ParsedLikeC4Model | null>;
20
21
  private previousViews;
21
22
  /**
22
23
  * WARNING:
23
24
  * This method is internal and should to be called only when all documents are known to be parsed.
24
25
  * Otherwise, the model may be incomplete.
25
26
  */
26
- unsafeSyncBuildComputedModel(model: c4.ParsedLikeC4Model): c4.ComputedLikeC4Model;
27
- buildComputedModel(cancelToken?: Cancellation.CancellationToken): Promise<c4.ComputedLikeC4Model | null>;
27
+ unsafeSyncBuildModel(): LikeC4Model.Computed;
28
+ buildLikeC4Model(cancelToken?: Cancellation.CancellationToken): Promise<LikeC4Model.Computed>;
28
29
  computeView(viewId: ViewId, cancelToken?: Cancellation.CancellationToken): Promise<c4.ComputedView | null>;
29
30
  onModelParsed(callback: ModelParsedListener): Disposable;
30
31
  private documents;