@likec4/language-server 0.46.1 → 0.47.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 (41) hide show
  1. package/contrib/likec4.monarch.ts +5 -8
  2. package/contrib/likec4.tmLanguage.json +1 -1
  3. package/dist/ast.d.ts +8 -3
  4. package/dist/ast.js +12 -8
  5. package/dist/generated/ast.d.ts +152 -110
  6. package/dist/generated/ast.js +149 -94
  7. package/dist/generated/grammar.js +1 -1
  8. package/dist/lsp/DocumentSymbolProvider.d.ts +7 -2
  9. package/dist/lsp/DocumentSymbolProvider.js +63 -56
  10. package/dist/lsp/HoverProvider.js +2 -8
  11. package/dist/lsp/SemanticTokenProvider.js +15 -25
  12. package/dist/model/fqn-index.d.ts +2 -7
  13. package/dist/model/fqn-index.js +14 -26
  14. package/dist/model/model-builder.js +5 -3
  15. package/dist/model/model-parser.d.ts +3 -2
  16. package/dist/model/model-parser.js +111 -51
  17. package/dist/module.js +3 -2
  18. package/dist/references/scope-computation.js +35 -15
  19. package/dist/references/scope-provider.d.ts +1 -2
  20. package/dist/references/scope-provider.js +17 -25
  21. package/dist/shared/NodeKindProvider.d.ts +4 -2
  22. package/dist/shared/NodeKindProvider.js +40 -13
  23. package/dist/shared/WorkspaceSymbolProvider.d.ts +4 -0
  24. package/dist/shared/WorkspaceSymbolProvider.js +3 -0
  25. package/dist/shared/index.d.ts +2 -0
  26. package/dist/shared/index.js +2 -0
  27. package/dist/test/testServices.d.ts +5 -0
  28. package/dist/test/testServices.js +25 -13
  29. package/dist/validation/index.js +22 -14
  30. package/dist/validation/specification.js +2 -1
  31. package/dist/validation/view-predicates/custom-element-expr.d.ts +5 -0
  32. package/dist/validation/view-predicates/custom-element-expr.js +16 -0
  33. package/dist/validation/view-predicates/incoming.d.ts +1 -1
  34. package/dist/validation/view-predicates/incoming.js +1 -1
  35. package/dist/validation/view-predicates/index.d.ts +1 -0
  36. package/dist/validation/view-predicates/index.js +1 -0
  37. package/dist/validation/view-predicates/outgoing.d.ts +1 -1
  38. package/dist/validation/view-predicates/outgoing.js +1 -1
  39. package/dist/validation/view.js +1 -1
  40. package/dist/view-utils/assignNavigateTo.js +3 -0
  41. package/package.json +6 -5
@@ -11,13 +11,6 @@ export class LikeC4SemanticTokenProvider extends AbstractSemanticTokenProvider {
11
11
  modifier: [SemanticTokenModifiers.definition]
12
12
  });
13
13
  }
14
- if (ast.isElementRef(node) || ast.isFqnElementRef(node)) {
15
- acceptor({
16
- node,
17
- property: "el",
18
- type: node.parent ? SemanticTokenTypes.property : SemanticTokenTypes.variable
19
- });
20
- }
21
14
  if (ast.isElementViewRef(node)) {
22
15
  acceptor({
23
16
  node,
@@ -25,21 +18,19 @@ export class LikeC4SemanticTokenProvider extends AbstractSemanticTokenProvider {
25
18
  type: SemanticTokenTypes.variable
26
19
  });
27
20
  }
28
- if (ast.isElementRefExpression(node) && node.isDescedants) {
21
+ if (ast.isDescedantsExpr(node) && node.$cstNode) {
29
22
  acceptor({
30
- node,
31
- keyword: ".*",
23
+ cst: node.$cstNode,
32
24
  type: SemanticTokenTypes.variable
33
25
  });
34
26
  }
35
- if (ast.isWildcardExpression(node)) {
27
+ if (ast.isWildcardExpr(node) && node.$cstNode) {
36
28
  acceptor({
37
- node,
38
- property: "isWildcard",
29
+ cst: node.$cstNode,
39
30
  type: SemanticTokenTypes.variable
40
31
  });
41
32
  }
42
- if (ast.isElementKindExpression(node)) {
33
+ if (ast.isElementKindExpr(node)) {
43
34
  acceptor({
44
35
  node,
45
36
  property: "kind",
@@ -47,7 +38,7 @@ export class LikeC4SemanticTokenProvider extends AbstractSemanticTokenProvider {
47
38
  modifier: [SemanticTokenModifiers.definition]
48
39
  });
49
40
  }
50
- if (ast.isElementTagExpression(node)) {
41
+ if (ast.isElementTagExpr(node)) {
51
42
  acceptor({
52
43
  node,
53
44
  property: "tag",
@@ -55,7 +46,14 @@ export class LikeC4SemanticTokenProvider extends AbstractSemanticTokenProvider {
55
46
  modifier: [SemanticTokenModifiers.definition]
56
47
  });
57
48
  }
58
- if (ast.isSpecificationElementKind(node)) {
49
+ if (ast.isElementRef(node) || ast.isFqnElementRef(node)) {
50
+ acceptor({
51
+ node,
52
+ property: "el",
53
+ type: node.parent ? SemanticTokenTypes.property : SemanticTokenTypes.variable
54
+ });
55
+ }
56
+ if (ast.isSpecificationElementKind(node) || ast.isSpecificationRelationshipKind(node)) {
59
57
  acceptor({
60
58
  node,
61
59
  property: "kind",
@@ -79,14 +77,6 @@ export class LikeC4SemanticTokenProvider extends AbstractSemanticTokenProvider {
79
77
  modifier: [SemanticTokenModifiers.definition]
80
78
  });
81
79
  }
82
- if (ast.isSpecificationRelationshipKind(node)) {
83
- acceptor({
84
- node,
85
- property: "kind",
86
- type: SemanticTokenTypes.type,
87
- modifier: [SemanticTokenModifiers.definition]
88
- });
89
- }
90
80
  if (ast.isColorProperty(node) || ast.isShapeProperty(node) || ast.isArrowProperty(node) || ast.isLineProperty(node)) {
91
81
  acceptor({
92
82
  node,
@@ -104,7 +94,7 @@ export class LikeC4SemanticTokenProvider extends AbstractSemanticTokenProvider {
104
94
  if (ast.isElement(node)) {
105
95
  this.highlightAstElement(node, acceptor);
106
96
  }
107
- if (ast.isView(node)) {
97
+ if (ast.isElementView(node)) {
108
98
  this.highlightView(node, acceptor);
109
99
  }
110
100
  }
@@ -1,12 +1,7 @@
1
1
  import type { Fqn } from '@likec4/core';
2
- import type { LangiumDocument, LangiumDocuments, Stream } from 'langium';
3
- import type { ast } from '../ast';
4
- import { type LikeC4LangiumDocument } from '../ast';
2
+ import type { LangiumDocuments, Stream } from 'langium';
3
+ import type { FqnIndexedDocument, ast } from '../ast';
5
4
  import type { LikeC4Services } from '../module';
6
- export type FqnIndexedDocument = Omit<LikeC4LangiumDocument, 'c4fqns'> & {
7
- c4fqns: NonNullable<LikeC4LangiumDocument['c4fqns']>;
8
- };
9
- export declare function isFqnIndexedDocument(doc: LangiumDocument): doc is FqnIndexedDocument;
10
5
  export interface FqnIndexEntry {
11
6
  fqn: Fqn;
12
7
  name: string;
@@ -1,13 +1,10 @@
1
1
  import { nameFromFqn, parentFqn } from "@likec4/core";
2
- import { DONE_RESULT, DocumentState, MultiMap, StreamImpl, getDocument, stream } from "langium";
3
- import { isNil } from "remeda";
4
- import { ElementOps, isLikeC4LangiumDocument } from "../ast.js";
2
+ import { DONE_RESULT, DocumentState, MultiMap, StreamImpl, stream } from "langium";
3
+ import { ElementOps, isFqnIndexedDocument, isLikeC4LangiumDocument } from "../ast.js";
5
4
  import { logError, logger } from "../logger.js";
6
5
  import { printDocs } from "../utils.js";
7
6
  import { computeDocumentFqn } from "./fqn-computation.js";
8
- export function isFqnIndexedDocument(doc) {
9
- return isLikeC4LangiumDocument(doc) && doc.state >= DocumentState.IndexedContent && !isNil(doc.c4fqns);
10
- }
7
+ const True = () => true;
11
8
  export class FqnIndex {
12
9
  constructor(services) {
13
10
  this.services = services;
@@ -40,37 +37,28 @@ export class FqnIndex {
40
37
  get documents() {
41
38
  return this.langiumDocuments.all.filter(isFqnIndexedDocument);
42
39
  }
43
- entries(filterByFqn = () => true) {
40
+ entries(filterByFqn = True) {
44
41
  return this.documents.flatMap(
45
- (doc) => doc.c4fqns.entries().filter(([fqn]) => filterByFqn(fqn)).map(([fqn, entry]) => {
46
- const el = entry.el.deref();
47
- if (el) {
48
- return { ...entry, fqn, el, doc };
42
+ (doc) => doc.c4fqns.entries().flatMap(([fqn, entry]) => {
43
+ if (filterByFqn(fqn)) {
44
+ const el = entry.el.deref();
45
+ if (el) {
46
+ return { ...entry, fqn, el, doc };
47
+ }
49
48
  }
50
- return null;
51
- }).nonNullable()
49
+ return [];
50
+ })
52
51
  );
53
52
  }
54
53
  getFqn(el) {
55
- let fqn = ElementOps.readId(el) ?? null;
56
- if (fqn) {
57
- const doc = getDocument(el);
58
- if (isFqnIndexedDocument(doc) && doc.c4fqns.has(fqn)) {
59
- return fqn;
60
- }
61
- const path = this.services.workspace.AstNodeLocator.getAstNodePath(el);
62
- logError(`Clean cached FQN ${fqn} at ${path}`);
63
- ElementOps.writeId(el, null);
64
- fqn = null;
65
- }
66
- return fqn;
54
+ return ElementOps.readId(el) ?? null;
67
55
  }
68
56
  byFqn(fqn) {
69
57
  return this.documents.flatMap((doc) => {
70
58
  return doc.c4fqns.get(fqn).flatMap((entry) => {
71
59
  const el = entry.el.deref();
72
60
  if (el) {
73
- return [{ fqn, el, doc, path: entry.path, name: entry.name }];
61
+ return { fqn, el, doc, path: entry.path, name: entry.name };
74
62
  }
75
63
  return [];
76
64
  });
@@ -240,9 +240,11 @@ ${printDocs(docs)}`);
240
240
  );
241
241
  const computedView = result.view;
242
242
  computedView.nodes.forEach((node) => {
243
- const navigateTo = R.find(allElementViews, (v) => v.viewOf === node.id);
244
- if (navigateTo) {
245
- node.navigateTo = navigateTo.id;
243
+ if (!node.navigateTo) {
244
+ const navigateTo = R.find(allElementViews, (v) => v.viewOf === node.id);
245
+ if (navigateTo) {
246
+ node.navigateTo = navigateTo.id;
247
+ }
246
248
  }
247
249
  });
248
250
  return computedView;
@@ -15,8 +15,9 @@ export declare class LikeC4ModelParser {
15
15
  private parseElement;
16
16
  private parseRelation;
17
17
  private parseViews;
18
- private parseElementExpression;
19
- private parseExpression;
18
+ private parseElementExpr;
19
+ private parseCustomElementExpr;
20
+ private parsePredicateExpr;
20
21
  private parseViewRule;
21
22
  private parseElementView;
22
23
  protected resolveFqn(node: ast.Element | ast.ExtendElement): c4.Fqn;
@@ -1,4 +1,9 @@
1
- import { InvalidModelError, invariant, isNonEmptyArray, nonexhaustive } from "@likec4/core";
1
+ import {
2
+ InvalidModelError,
3
+ invariant,
4
+ isNonEmptyArray,
5
+ nonexhaustive
6
+ } from "@likec4/core";
2
7
  import { getDocument } from "langium";
3
8
  import objectHash from "object-hash";
4
9
  import stripIndent from "strip-indent";
@@ -6,6 +11,7 @@ import {
6
11
  ElementViewOps,
7
12
  ast,
8
13
  cleanParsedModel,
14
+ isFqnIndexedDocument,
9
15
  isLikeC4LangiumDocument,
10
16
  resolveRelationPoints,
11
17
  streamModel,
@@ -16,6 +22,13 @@ import {
16
22
  } from "../ast.js";
17
23
  import { elementRef, getFqnElementRef } from "../elementRef.js";
18
24
  import { logError, logWarnError, logger } from "../logger.js";
25
+ import { isTruthy } from "remeda";
26
+ function toSingleLine(str) {
27
+ return str?.split("\n").join(" ").trim();
28
+ }
29
+ function removeIndent(str) {
30
+ return str ? stripIndent(str).trim() : void 0;
31
+ }
19
32
  export class LikeC4ModelParser {
20
33
  constructor(services) {
21
34
  this.services = services;
@@ -39,6 +52,7 @@ export class LikeC4ModelParser {
39
52
  return result;
40
53
  }
41
54
  parseLikeC4Document(_doc) {
55
+ invariant(isFqnIndexedDocument(_doc), "Not a FqnIndexedDocument");
42
56
  const doc = cleanParsedModel(_doc);
43
57
  this.parseSpecification(doc);
44
58
  this.parseModel(doc);
@@ -100,29 +114,26 @@ export class LikeC4ModelParser {
100
114
  }
101
115
  parseElement(astNode) {
102
116
  const id = this.resolveFqn(astNode);
103
- invariant(astNode.kind.ref, "Element kind is not resolved: " + astNode.name);
104
- const kind = astNode.kind.ref.name;
117
+ const kind = astNode.kind.$refText;
105
118
  const tags = this.convertTags(astNode.body);
106
119
  const stylePropsAst = astNode.body?.props.find(ast.isStyleProperties)?.props;
107
120
  const styleProps = toElementStyleExcludeDefaults(stylePropsAst);
108
121
  const astPath = this.getAstNodePath(astNode);
109
122
  let [title, description, technology] = astNode.props;
110
- const bodyProps = astNode.body?.props.filter(
111
- (p) => ast.isElementStringProperty(p)
112
- ) ?? [];
113
- title = title ?? bodyProps.find((p) => p.key === "title")?.value;
114
- description = description ?? bodyProps.find((p) => p.key === "description")?.value;
115
- technology = technology ?? bodyProps.find((p) => p.key === "technology")?.value;
123
+ const bodyProps = astNode.body?.props.filter(ast.isElementStringProperty) ?? [];
124
+ title = toSingleLine(title ?? bodyProps.find((p) => p.key === "title")?.value);
125
+ description = removeIndent(description ?? bodyProps.find((p) => p.key === "description")?.value);
126
+ technology = toSingleLine(technology ?? bodyProps.find((p) => p.key === "technology")?.value);
116
127
  const links = astNode.body?.props.filter(ast.isLinkProperty).map((p) => p.value);
117
128
  return {
118
129
  id,
119
130
  kind,
120
131
  astPath,
121
- title: title ? stripIndent(title).trim() : astNode.name,
132
+ title: title ?? astNode.name,
122
133
  ...tags && { tags },
123
134
  ...links && isNonEmptyArray(links) && { links },
124
- ...technology && { technology },
125
- ...description && { description: stripIndent(description).trim() },
135
+ ...isTruthy(technology) && { technology },
136
+ ...isTruthy(description) && { description },
126
137
  ...styleProps
127
138
  };
128
139
  }
@@ -133,7 +144,7 @@ export class LikeC4ModelParser {
133
144
  const tags = this.convertTags(astNode);
134
145
  const kind = astNode.kind?.ref?.name;
135
146
  const astPath = this.getAstNodePath(astNode);
136
- const title = astNode.title ?? astNode.props.find((p) => p.key === "title")?.value ?? "";
147
+ const title = toSingleLine(astNode.title ?? astNode.props.find((p) => p.key === "title")?.value) ?? "";
137
148
  const id = objectHash({
138
149
  astPath,
139
150
  source,
@@ -160,81 +171,130 @@ export class LikeC4ModelParser {
160
171
  }
161
172
  }
162
173
  }
163
- parseElementExpression(astNode) {
164
- if (ast.isWildcardExpression(astNode)) {
174
+ parseElementExpr(astNode) {
175
+ if (ast.isWildcardExpr(astNode)) {
165
176
  return {
166
177
  wildcard: true
167
178
  };
168
179
  }
169
- if (ast.isElementKindExpression(astNode)) {
170
- invariant(
171
- astNode.kind.ref,
172
- "ElementKindExpression kind is not resolved: " + astNode.$cstNode?.text
173
- );
180
+ if (ast.isElementKindExpr(astNode)) {
174
181
  return {
175
- elementKind: astNode.kind.ref.name,
182
+ elementKind: astNode.kind.$refText,
176
183
  isEqual: astNode.isEqual
177
184
  };
178
185
  }
179
- if (ast.isElementTagExpression(astNode)) {
180
- invariant(
181
- astNode.tag.ref,
182
- "ElementTagExpression tag is not resolved: " + astNode.$cstNode?.text
183
- );
186
+ if (ast.isElementTagExpr(astNode)) {
187
+ let elementTag = astNode.tag.$refText;
188
+ if (elementTag.startsWith("#")) {
189
+ elementTag = elementTag.slice(1);
190
+ }
184
191
  return {
185
- elementTag: astNode.tag.ref.name,
192
+ elementTag,
186
193
  isEqual: astNode.isEqual
187
194
  };
188
195
  }
189
- if (ast.isElementRefExpression(astNode)) {
190
- const elementNode = elementRef(astNode.id);
191
- invariant(elementNode, "Element not found " + astNode.id.$cstNode?.text);
196
+ if (ast.isDescedantsExpr(astNode)) {
197
+ const elementNode = elementRef(astNode.parent);
198
+ invariant(elementNode, "Element not found " + astNode.parent.$cstNode?.text);
192
199
  const element = this.resolveFqn(elementNode);
193
- return astNode.isDescedants ? {
200
+ return {
194
201
  element,
195
- isDescedants: astNode.isDescedants
196
- } : {
202
+ isDescedants: true
203
+ };
204
+ }
205
+ if (ast.isElementRef(astNode)) {
206
+ const elementNode = elementRef(astNode);
207
+ invariant(elementNode, "Element not found " + astNode.$cstNode?.text);
208
+ const element = this.resolveFqn(elementNode);
209
+ return {
197
210
  element
198
211
  };
199
212
  }
200
213
  nonexhaustive(astNode);
201
214
  }
202
- parseExpression(astNode) {
203
- if (ast.isElementExpression(astNode)) {
204
- return this.parseElementExpression(astNode);
205
- }
206
- if (ast.isIncomingExpression(astNode)) {
215
+ parseCustomElementExpr(astNode) {
216
+ invariant(ast.isElementRef(astNode.target), "ElementRef expected as target of custom element");
217
+ const elementNode = elementRef(astNode.target);
218
+ invariant(elementNode, "element not found: " + astNode.$cstNode?.text);
219
+ const element = this.resolveFqn(elementNode);
220
+ const props = astNode.body?.props ?? [];
221
+ return props.reduce(
222
+ (acc, prop) => {
223
+ if (ast.isNavigateToProperty(prop)) {
224
+ const viewId = prop.value.view.$refText;
225
+ if (isTruthy(viewId)) {
226
+ acc.custom.navigateTo = viewId;
227
+ }
228
+ return acc;
229
+ }
230
+ if (ast.isElementStringProperty(prop)) {
231
+ const value = prop.key === "description" ? removeIndent(prop.value) : toSingleLine(prop.value);
232
+ acc.custom[prop.key] = value.trim();
233
+ return acc;
234
+ }
235
+ if (ast.isColorProperty(prop)) {
236
+ acc.custom[prop.key] = prop.value;
237
+ return acc;
238
+ }
239
+ if (ast.isShapeProperty(prop)) {
240
+ acc.custom[prop.key] = prop.value;
241
+ return acc;
242
+ }
243
+ nonexhaustive(prop);
244
+ },
245
+ {
246
+ custom: {
247
+ element
248
+ }
249
+ }
250
+ );
251
+ }
252
+ parsePredicateExpr(astNode) {
253
+ if (ast.isRelationExpr(astNode)) {
207
254
  return {
208
- incoming: this.parseElementExpression(astNode.target)
255
+ source: this.parseElementExpr(astNode.source),
256
+ target: this.parseElementExpr(astNode.target)
209
257
  };
210
258
  }
211
- if (ast.isOutgoingExpression(astNode)) {
259
+ if (ast.isInOutExpr(astNode)) {
212
260
  return {
213
- outgoing: this.parseElementExpression(astNode.source)
261
+ inout: this.parseElementExpr(astNode.inout.to)
214
262
  };
215
263
  }
216
- if (ast.isInOutExpression(astNode)) {
264
+ if (ast.isOutgoingExpr(astNode)) {
217
265
  return {
218
- inout: this.parseElementExpression(astNode.inout.target)
266
+ outgoing: this.parseElementExpr(astNode.from)
219
267
  };
220
268
  }
221
- if (ast.isRelationExpression(astNode)) {
269
+ if (ast.isIncomingExpr(astNode)) {
222
270
  return {
223
- source: this.parseElementExpression(astNode.source),
224
- target: this.parseElementExpression(astNode.target)
271
+ incoming: this.parseElementExpr(astNode.to)
225
272
  };
226
273
  }
274
+ if (ast.isCustomElementExpr(astNode)) {
275
+ return this.parseCustomElementExpr(astNode);
276
+ }
277
+ if (ast.isElementExpr(astNode)) {
278
+ return this.parseElementExpr(astNode);
279
+ }
227
280
  nonexhaustive(astNode);
228
281
  }
229
282
  parseViewRule(astRule) {
230
- if (ast.isViewRuleExpression(astRule)) {
231
- const exprs = astRule.expressions.map((n) => this.parseExpression(n));
232
- return astRule.isInclude ? { include: exprs } : { exclude: exprs };
283
+ if (ast.isIncludePredicate(astRule) || ast.isExcludePredicate(astRule)) {
284
+ const exprs = astRule.expressions.flatMap((n) => {
285
+ try {
286
+ return this.parsePredicateExpr(n);
287
+ } catch (e) {
288
+ logWarnError(e);
289
+ return [];
290
+ }
291
+ });
292
+ return ast.isIncludePredicate(astRule) ? { include: exprs } : { exclude: exprs };
233
293
  }
234
294
  if (ast.isViewRuleStyle(astRule)) {
235
- const styleProps = toElementStyle(astRule.props);
295
+ const styleProps = toElementStyle(astRule.styleprops);
236
296
  return {
237
- targets: astRule.targets.map((n) => this.parseElementExpression(n)),
297
+ targets: astRule.targets.map((n) => this.parseElementExpr(n)),
238
298
  style: {
239
299
  ...styleProps
240
300
  }
package/dist/module.js CHANGED
@@ -16,7 +16,7 @@ import { Rpc } from "./Rpc.js";
16
16
  import { registerValidationChecks } from "./validation/index.js";
17
17
  import { logger } from "./logger.js";
18
18
  import { serializeError } from "@likec4/core";
19
- import { NodeKindProvider } from "./shared/NodeKindProvider.js";
19
+ import { WorkspaceSymbolProvider, NodeKindProvider } from "./shared/index.js";
20
20
  function bind(Type) {
21
21
  return (services) => new Type(services);
22
22
  }
@@ -43,7 +43,8 @@ export const LikeC4Module = {
43
43
  };
44
44
  const LikeC4SharedModule = {
45
45
  lsp: {
46
- NodeKindProvider: () => new NodeKindProvider()
46
+ NodeKindProvider: (services) => new NodeKindProvider(services),
47
+ WorkspaceSymbolProvider: (services) => new WorkspaceSymbolProvider(services)
47
48
  }
48
49
  // workspace: {
49
50
  // WorkspaceManager: services => new LikeC4WorkspaceManager(services)
@@ -2,54 +2,72 @@ import {
2
2
  DefaultScopeComputation,
3
3
  MultiMap
4
4
  } from "langium";
5
- import { hasAtLeast, isEmpty } from "remeda";
5
+ import { isEmpty, isTruthy } from "remeda";
6
6
  import { ast } from "../ast.js";
7
7
  import { logError } from "../logger.js";
8
8
  export class LikeC4ScopeComputation extends DefaultScopeComputation {
9
9
  computeExports(document, _cancelToken) {
10
+ const docExports = [];
11
+ const { specifications, models, views } = document.parseResult.value;
10
12
  try {
11
- const { specifications, models, views } = document.parseResult.value;
12
- const docExports = [];
13
13
  for (const spec of specifications.flatMap((s) => s.elements)) {
14
- if (spec.kind && !isEmpty(spec.kind.name)) {
14
+ if (spec.kind && isTruthy(spec.kind.name)) {
15
15
  docExports.push(this.descriptions.createDescription(spec.kind, spec.kind.name, document));
16
16
  }
17
17
  }
18
+ } catch (e) {
19
+ logError(e);
20
+ }
21
+ try {
18
22
  for (const spec of specifications.flatMap((s) => s.tags)) {
19
- if (spec.tag && !isEmpty(spec.tag.name)) {
20
- docExports.push(this.descriptions.createDescription(spec.tag, spec.tag.name, document));
23
+ if (spec.tag && isTruthy(spec.tag.name)) {
21
24
  docExports.push(
22
25
  this.descriptions.createDescription(spec.tag, "#" + spec.tag.name, document)
23
26
  );
24
27
  }
25
28
  }
29
+ } catch (e) {
30
+ logError(e);
31
+ }
32
+ try {
26
33
  for (const spec of specifications.flatMap((s) => s.relationships)) {
27
- if (spec.kind && !isEmpty(spec.kind.name)) {
34
+ if (spec.kind && isTruthy(spec.kind.name)) {
28
35
  docExports.push(this.descriptions.createDescription(spec.kind, spec.kind.name, document));
29
36
  }
30
37
  }
38
+ } catch (e) {
39
+ logError(e);
40
+ }
41
+ try {
31
42
  for (const elAst of models.flatMap((m) => m.elements)) {
32
43
  if (ast.isElement(elAst) && !isEmpty(elAst.name)) {
33
44
  docExports.push(this.descriptions.createDescription(elAst, elAst.name, document));
34
45
  }
35
46
  }
47
+ } catch (e) {
48
+ logError(e);
49
+ }
50
+ try {
36
51
  for (const viewAst of views.flatMap((v) => v.views)) {
37
- if (viewAst.name && !isEmpty(viewAst.name)) {
52
+ if (isTruthy(viewAst.name)) {
38
53
  docExports.push(this.descriptions.createDescription(viewAst, viewAst.name, document));
39
54
  }
40
55
  }
41
- return Promise.resolve(docExports);
42
56
  } catch (e) {
43
57
  logError(e);
44
- return Promise.reject(e);
45
58
  }
59
+ return Promise.resolve(docExports);
46
60
  }
47
61
  async computeLocalScopes(document, _cancelToken) {
48
62
  const root = document.parseResult.value;
49
63
  const scopes = new MultiMap();
50
- if (hasAtLeast(root.models, 1)) {
51
- const nested = this.processContainer(root.models[0], scopes, document);
52
- scopes.addAll(root, nested.values());
64
+ for (const model of root.models) {
65
+ try {
66
+ const nested = this.processContainer(model, scopes, document);
67
+ scopes.addAll(root, nested.values());
68
+ } catch (e) {
69
+ logError(e);
70
+ }
53
71
  }
54
72
  return Promise.resolve(scopes);
55
73
  }
@@ -61,8 +79,10 @@ export class LikeC4ScopeComputation extends DefaultScopeComputation {
61
79
  continue;
62
80
  }
63
81
  let subcontainer;
64
- if (ast.isElement(el) && !isEmpty(el.name)) {
65
- localScope.add(el.name, this.descriptions.createDescription(el, el.name, document));
82
+ if (ast.isElement(el)) {
83
+ if (isTruthy(el.name)) {
84
+ localScope.add(el.name, this.descriptions.createDescription(el, el.name, document));
85
+ }
66
86
  subcontainer = el.body;
67
87
  } else if (ast.isExtendElement(el)) {
68
88
  subcontainer = el.body;
@@ -1,4 +1,3 @@
1
- import type { AstNode } from 'langium';
2
1
  import { DefaultScopeProvider, type ReferenceInfo, type Scope } from 'langium';
3
2
  import type { LikeC4Services } from '../module';
4
3
  export declare class LikeC4ScopeProvider extends DefaultScopeProvider {
@@ -10,7 +9,7 @@ export declare class LikeC4ScopeProvider extends DefaultScopeProvider {
10
9
  private scopeExtendElement;
11
10
  private scopeElementView;
12
11
  getScope(context: ReferenceInfo): Scope;
13
- protected computeScope(node: AstNode, referenceType: string): Scope;
12
+ protected computeScope(context: ReferenceInfo): Scope;
14
13
  /**
15
14
  * Create a global scope filtered for the given reference type.
16
15
  */
@@ -58,15 +58,12 @@ export class LikeC4ScopeProvider extends DefaultScopeProvider {
58
58
  scopeElementRef(ref) {
59
59
  return this.uniqueDescedants(() => ref.el.ref);
60
60
  }
61
- scopeExtendElement(extend) {
62
- return this.uniqueDescedants(() => elementRef(extend.element));
61
+ scopeExtendElement({ element }) {
62
+ return stream([element.el.$nodeDescription]).nonNullable().concat(this.uniqueDescedants(() => elementRef(element)));
63
63
  }
64
64
  scopeElementView({ viewOf, extends: ext }) {
65
65
  if (viewOf) {
66
- return stream([viewOf]).flatMap((v) => {
67
- const el = elementRef(v);
68
- return el ? this.descriptions.createDescription(el, el.name) : [];
69
- }).concat(this.uniqueDescedants(() => elementRef(viewOf)));
66
+ return stream([viewOf.el.$nodeDescription]).nonNullable().concat(this.uniqueDescedants(() => elementRef(viewOf)));
70
67
  }
71
68
  if (ext) {
72
69
  return stream([ext]).flatMap((v) => {
@@ -93,38 +90,33 @@ export class LikeC4ScopeProvider extends DefaultScopeProvider {
93
90
  return new StreamScope(this.scopeElementRef(parent));
94
91
  }
95
92
  }
96
- return this.computeScope(container, referenceType);
93
+ return this.computeScope(context);
97
94
  } catch (e) {
98
95
  logError(e);
99
96
  return this.getGlobalScope(referenceType);
100
97
  }
101
98
  }
102
- computeScope(node, referenceType) {
99
+ computeScope(context) {
100
+ const referenceType = this.reflection.getReferenceType(context);
103
101
  const scopes = [];
104
- const doc = getDocument(node);
102
+ const doc = getDocument(context.container);
105
103
  const precomputed = doc.precomputedScopes;
106
104
  const byReferenceType = (desc) => this.reflection.isSubtype(desc.type, referenceType);
107
- if (precomputed) {
108
- const elements = precomputed.get(node).filter(byReferenceType);
109
- if (elements.length > 0) {
105
+ let container = context.container;
106
+ while (container) {
107
+ const elements = precomputed?.get(container).filter(byReferenceType);
108
+ if (elements && elements.length > 0) {
110
109
  scopes.push(stream(elements));
111
110
  }
112
- let container = node.$container;
113
- while (container) {
114
- const elements2 = precomputed.get(container).filter(byReferenceType);
115
- if (elements2.length > 0) {
116
- scopes.push(stream(elements2));
111
+ if (referenceType === ast.Element) {
112
+ if (ast.isExtendElementBody(container)) {
113
+ scopes.push(this.scopeExtendElement(container.$container));
117
114
  }
118
- if (referenceType === ast.Element) {
119
- if (ast.isExtendElementBody(container)) {
120
- scopes.push(this.scopeExtendElement(container.$container));
121
- }
122
- if (ast.isElementViewBody(container)) {
123
- scopes.push(this.scopeElementView(container.$container));
124
- }
115
+ if (ast.isElementViewBody(container)) {
116
+ scopes.push(this.scopeElementView(container.$container));
125
117
  }
126
- container = container.$container;
127
118
  }
119
+ container = container.$container;
128
120
  }
129
121
  return scopes.reduceRight((outerScope, elements) => {
130
122
  return this.createScope(elements, outerScope);