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