@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
@@ -45,8 +45,8 @@ export declare function ViewsParser<TBase extends WithPredicates & WithDeploymen
45
45
  parseTags<E extends {
46
46
  tags?: ast.Tags;
47
47
  }>(withTags?: E): c4.NonEmptyArray<c4.Tag> | null;
48
- convertLinks(source?: ast.LinkProperty["$container"]): import("../../ast").ParsedLink[] | undefined;
49
- parseLinks(source?: ast.LinkProperty["$container"]): import("../../ast").ParsedLink[] | undefined;
48
+ convertLinks(source?: ast.LinkProperty["$container"]): c4.Link[] | undefined;
49
+ parseLinks(source?: ast.LinkProperty["$container"]): c4.Link[] | undefined;
50
50
  parseDeploymentView(astNode: ast.DeploymentView): import("../../ast").ParsedAstDeploymentView;
51
51
  parseDeploymentViewRule(astRule: ast.DeploymentViewRule): c4.DeploymentViewRule;
52
52
  parseDeploymentViewRulePredicate(astRule: ast.DeploymentViewRulePredicate): c4.DeploymentViewRulePredicate;
@@ -61,6 +61,7 @@ export declare function ViewsParser<TBase extends WithPredicates & WithDeploymen
61
61
  parseDeployment(): void;
62
62
  parseDeploymentNode(astNode: ast.DeploymentNode): import("../../ast").ParsedAstDeployment.Node;
63
63
  parseDeployedInstance(astNode: ast.DeployedInstance): import("../../ast").ParsedAstDeployment.Instance;
64
+ parseExtendDeployment(astNode: ast.ExtendDeployment): import("../../ast").ParsedAstExtend | null;
64
65
  parseDeploymentRelation(astNode: ast.DeploymentRelation): import("../../ast").ParsedAstDeploymentRelation;
65
66
  };
66
67
  } & TBase;
package/dist/module.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { GraphvizLayouter } from '@likec4/layouts';
2
- import { type Module, DocumentCache, WorkspaceCache } from 'langium';
2
+ import { type Module, WorkspaceCache } from 'langium';
3
3
  import { type DefaultSharedModuleContext, type LangiumServices, type LangiumSharedServices, type PartialLangiumServices } from 'langium/lsp';
4
4
  import { LikeC4DocumentationProvider } from './documentation';
5
5
  import { LikeC4CodeLensProvider, LikeC4CompletionProvider, LikeC4DocumentHighlightProvider, LikeC4DocumentLinkProvider, LikeC4DocumentSymbolProvider, LikeC4HoverProvider, LikeC4SemanticTokenProvider } from './lsp';
@@ -26,8 +26,7 @@ export interface LikeC4AddedServices {
26
26
  documentation: {
27
27
  DocumentationProvider: LikeC4DocumentationProvider;
28
28
  };
29
- WorkspaceCache: WorkspaceCache<string, any>;
30
- DocumentCache: DocumentCache<string, any>;
29
+ ValidatedWorkspaceCache: WorkspaceCache<string, any>;
31
30
  Rpc: Rpc;
32
31
  likec4: {
33
32
  Views: LikeC4Views;
package/dist/module.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { GraphvizLayouter, GraphvizWasmAdapter } from "@likec4/layouts";
2
2
  import {
3
- DocumentCache,
3
+ DocumentState,
4
4
  EmptyFileSystem,
5
5
  inject,
6
6
  WorkspaceCache
@@ -62,8 +62,7 @@ export const LikeC4Module = {
62
62
  documentation: {
63
63
  DocumentationProvider: bind(LikeC4DocumentationProvider)
64
64
  },
65
- WorkspaceCache: (services) => new WorkspaceCache(services.shared),
66
- DocumentCache: (services) => new DocumentCache(services.shared),
65
+ ValidatedWorkspaceCache: (services) => new WorkspaceCache(services.shared, DocumentState.Validated),
67
66
  Rpc: bind(Rpc),
68
67
  likec4: {
69
68
  Layouter: (_services) => {
@@ -3,7 +3,7 @@ import type { CancellationToken } from 'vscode-languageserver';
3
3
  import { type LikeC4LangiumDocument, ast } from '../ast';
4
4
  import type { LikeC4Services } from '../module';
5
5
  type ElementsContainer = ast.Model | ast.ElementBody | ast.ExtendElementBody;
6
- type DeploymentsContainer = ast.ModelDeployments | ast.DeploymentNodeBody;
6
+ type DeploymentsContainer = ast.ModelDeployments | ast.DeploymentNodeBody | ast.ExtendDeploymentBody;
7
7
  export declare class LikeC4ScopeComputation extends DefaultScopeComputation {
8
8
  constructor(services: LikeC4Services);
9
9
  computeExports(document: LikeC4LangiumDocument, _cancelToken?: CancellationToken): Promise<AstNodeDescription[]>;
@@ -242,19 +242,20 @@ export class LikeC4ScopeComputation extends DefaultScopeComputation {
242
242
  return local;
243
243
  }
244
244
  processDeployments(container, scopes, document) {
245
- const localnames = /* @__PURE__ */ new Set();
245
+ const localScope = new MultiMap();
246
246
  const descedants = [];
247
247
  for (const el of container.elements) {
248
248
  if (ast.isDeploymentRelation(el)) {
249
249
  continue;
250
250
  }
251
- let name = this.nameProvider.getName(el);
252
- if (isTruthy(name)) {
253
- const desc = this.descriptions.createDescription(el, name, document);
254
- scopes.add(container, desc);
255
- localnames.add(desc.name);
251
+ if (!ast.isExtendDeployment(el)) {
252
+ let name = this.nameProvider.getName(el);
253
+ if (isTruthy(name)) {
254
+ const desc = this.descriptions.createDescription(el, name, document);
255
+ localScope.add(name, desc);
256
+ }
256
257
  }
257
- if (ast.isDeploymentNode(el) && el.body) {
258
+ if (!ast.isDeployedInstance(el) && el.body) {
258
259
  try {
259
260
  descedants.push(
260
261
  ...this.processDeployments(el.body, scopes, document)
@@ -264,18 +265,20 @@ export class LikeC4ScopeComputation extends DefaultScopeComputation {
264
265
  }
265
266
  }
266
267
  }
267
- if (descedants.length > 0) {
268
+ if (descedants.length) {
268
269
  pipe(
269
270
  descedants,
270
- filter((desc) => !localnames.has(desc.name)),
271
+ filter((desc) => !localScope.has(desc.name)),
271
272
  groupBy((desc) => desc.name),
272
273
  forEachObj((descs, name) => {
273
274
  if (descs.length === 1) {
274
- scopes.add(container, descs[0]);
275
+ localScope.add(name, descs[0]);
275
276
  }
276
277
  })
277
278
  );
278
279
  }
279
- return [...scopes.get(container).values()];
280
+ const local = [...localScope.values()];
281
+ scopes.addAll(container, local);
282
+ return local;
280
283
  }
281
284
  }
@@ -5,12 +5,24 @@ export declare class LikeC4ScopeProvider extends DefaultScopeProvider {
5
5
  private deploymentsIndex;
6
6
  private fqnIndex;
7
7
  constructor(services: LikeC4Services);
8
- private directChildrenOf;
9
8
  private uniqueDescedants;
10
- private scopeElementRef;
11
- private scopeExtendElement;
12
- private scopeElementView;
13
9
  getScope(context: ReferenceInfo): Scope;
10
+ private getScopeElementRef;
11
+ private getScopeExtendElement;
12
+ private getScopeElementView;
13
+ private getScopeForStrictFqnRef;
14
+ private getScopeExtendDeployment;
14
15
  protected getScopeForFqnRef(container: ast.FqnRef, context: ReferenceInfo): any;
16
+ /**
17
+ * Computes the scope for a given reference context.
18
+ *
19
+ * @param context - The reference information containing the context for which the scope is being computed.
20
+ * @param referenceType - The type of reference being resolved. Defaults to the reference type derived from the context.
21
+ * @returns A scope containing the relevant AST node descriptions for the given reference context.
22
+ *
23
+ * This method first checks if there are precomputed scopes available in the document. If not, it falls back to the global scope.
24
+ * It then iterates through the container hierarchy, collecting relevant scopes based on the reference type and container type.
25
+ * Finally, it combines the collected scopes with the global scope to produce the final scope.
26
+ */
15
27
  protected computeScope(context: ReferenceInfo, referenceType?: any): any;
16
28
  }
@@ -12,7 +12,7 @@ import {
12
12
  } from "langium";
13
13
  import { ast } from "../ast.js";
14
14
  import { logWarnError } from "../logger.js";
15
- import { elementRef, getFqnElementRef } from "../utils/elementRef.js";
15
+ import { elementRef, readStrictFqn } from "../utils/elementRef.js";
16
16
  const { getDocument } = AstUtils;
17
17
  export class LikeC4ScopeProvider extends DefaultScopeProvider {
18
18
  deploymentsIndex;
@@ -22,18 +22,19 @@ export class LikeC4ScopeProvider extends DefaultScopeProvider {
22
22
  this.fqnIndex = services.likec4.FqnIndex;
23
23
  this.deploymentsIndex = services.likec4.DeploymentsIndex;
24
24
  }
25
- directChildrenOf(parent) {
26
- return this.fqnIndex.directChildrenOf(parent);
27
- }
28
25
  // we need lazy resolving here
29
26
  uniqueDescedants(of) {
30
27
  return new StreamImpl(
31
28
  () => {
32
29
  const element = of();
33
- const fqn = element && this.fqnIndex.getFqn(element);
34
- if (fqn) {
30
+ if (element && ast.isElement(element)) {
31
+ const fqn = this.fqnIndex.getFqn(element);
35
32
  return this.fqnIndex.uniqueDescedants(fqn).iterator();
36
33
  }
34
+ if (element && ast.isDeploymentNode(element)) {
35
+ const fqn = this.deploymentsIndex.getFqn(element);
36
+ return this.deploymentsIndex.uniqueDescedants(fqn).iterator();
37
+ }
37
38
  return null;
38
39
  },
39
40
  (iterator) => {
@@ -44,24 +45,6 @@ export class LikeC4ScopeProvider extends DefaultScopeProvider {
44
45
  }
45
46
  );
46
47
  }
47
- scopeElementRef(ref) {
48
- return this.uniqueDescedants(() => ref.el.ref);
49
- }
50
- scopeExtendElement({ element }) {
51
- return stream([element.el.$nodeDescription]).nonNullable().concat(this.uniqueDescedants(() => elementRef(element)));
52
- }
53
- scopeElementView({ viewOf, extends: ext }) {
54
- if (viewOf) {
55
- return stream([viewOf.el.$nodeDescription]).nonNullable().concat(this.uniqueDescedants(() => elementRef(viewOf)));
56
- }
57
- if (ext) {
58
- return stream([ext]).flatMap((v) => {
59
- const view = v.view.ref;
60
- return view ? this.scopeElementView(view) : EMPTY_STREAM;
61
- });
62
- }
63
- return EMPTY_STREAM;
64
- }
65
48
  getScope(context) {
66
49
  try {
67
50
  const referenceType = this.reflection.getReferenceType(context);
@@ -70,6 +53,9 @@ export class LikeC4ScopeProvider extends DefaultScopeProvider {
70
53
  if (ast.isFqnRef(container)) {
71
54
  return this.getScopeForFqnRef(container, context);
72
55
  }
56
+ if (ast.isStrictFqnRef(container)) {
57
+ return this.getScopeForStrictFqnRef(container, context);
58
+ }
73
59
  if (referenceType !== ast.Element) {
74
60
  return this.getGlobalScope(referenceType, context);
75
61
  }
@@ -78,12 +64,12 @@ export class LikeC4ScopeProvider extends DefaultScopeProvider {
78
64
  if (!parent) {
79
65
  return this.getGlobalScope(referenceType, context);
80
66
  }
81
- return new StreamScope(this.directChildrenOf(getFqnElementRef(parent)));
67
+ return new StreamScope(this.fqnIndex.directChildrenOf(readStrictFqn(parent)));
82
68
  }
83
69
  if (ast.isElementRef(container) && context.property === "el") {
84
70
  const parent = container.parent;
85
71
  if (parent) {
86
- return new StreamScope(this.scopeElementRef(parent));
72
+ return new StreamScope(this.getScopeElementRef(parent));
87
73
  }
88
74
  if (context.reference.$refText === "this" || context.reference.$refText === "it") {
89
75
  const closestElement = AstUtils.getContainerOfType(container, ast.isElement);
@@ -106,6 +92,39 @@ export class LikeC4ScopeProvider extends DefaultScopeProvider {
106
92
  return EMPTY_SCOPE;
107
93
  }
108
94
  }
95
+ getScopeElementRef(ref) {
96
+ return this.uniqueDescedants(() => ref.el.ref);
97
+ }
98
+ getScopeExtendElement({ element }) {
99
+ return stream([element.el.$nodeDescription]).nonNullable().concat(this.uniqueDescedants(() => elementRef(element)));
100
+ }
101
+ getScopeElementView({ viewOf, extends: ext }) {
102
+ if (viewOf) {
103
+ return stream([viewOf.el.$nodeDescription]).nonNullable().concat(this.uniqueDescedants(() => elementRef(viewOf)));
104
+ }
105
+ if (ext) {
106
+ return stream([ext]).flatMap((v) => {
107
+ const view = v.view.ref;
108
+ return view ? this.getScopeElementView(view) : EMPTY_STREAM;
109
+ });
110
+ }
111
+ return EMPTY_STREAM;
112
+ }
113
+ getScopeForStrictFqnRef(container, context) {
114
+ const parent = container.parent;
115
+ if (!parent) {
116
+ return this.getGlobalScope(ast.DeploymentNode, context);
117
+ }
118
+ return new StreamScope(
119
+ this.deploymentsIndex.directChildrenOf(readStrictFqn(parent)).filter((desc) => this.reflection.isSubtype(desc.type, ast.DeploymentNode))
120
+ );
121
+ }
122
+ getScopeExtendDeployment({ deploymentNode }) {
123
+ return stream([deploymentNode.value.$nodeDescription]).nonNullable().concat(this.uniqueDescedants(() => {
124
+ const target = deploymentNode.value.ref;
125
+ return target && ast.isDeploymentNode(target) ? target : void 0;
126
+ }));
127
+ }
109
128
  getScopeForFqnRef(container, context) {
110
129
  const parent = container.parent;
111
130
  if (!parent) {
@@ -125,18 +144,30 @@ export class LikeC4ScopeProvider extends DefaultScopeProvider {
125
144
  return EMPTY_SCOPE;
126
145
  }
127
146
  if (ast.isDeploymentNode(parentRef)) {
128
- return new StreamScope(this.deploymentsIndex.nested(parentRef));
147
+ return new StreamScope(this.uniqueDescedants(() => parentRef));
129
148
  }
130
149
  if (ast.isDeployedInstance(parentRef)) {
131
- return new StreamScope(this.scopeElementRef(parentRef.element));
150
+ return new StreamScope(this.getScopeElementRef(parentRef.element));
132
151
  }
133
152
  if (ast.isElement(parentRef)) {
134
153
  return new StreamScope(this.uniqueDescedants(() => parentRef));
135
154
  }
136
155
  return nonexhaustive(parentRef);
137
156
  }
157
+ /**
158
+ * Computes the scope for a given reference context.
159
+ *
160
+ * @param context - The reference information containing the context for which the scope is being computed.
161
+ * @param referenceType - The type of reference being resolved. Defaults to the reference type derived from the context.
162
+ * @returns A scope containing the relevant AST node descriptions for the given reference context.
163
+ *
164
+ * This method first checks if there are precomputed scopes available in the document. If not, it falls back to the global scope.
165
+ * It then iterates through the container hierarchy, collecting relevant scopes based on the reference type and container type.
166
+ * Finally, it combines the collected scopes with the global scope to produce the final scope.
167
+ */
138
168
  computeScope(context, referenceType = this.reflection.getReferenceType(context)) {
139
169
  const isElementReference = this.reflection.isSubtype(referenceType, ast.Element);
170
+ const isDeploymentReference = this.reflection.isSubtype(referenceType, ast.DeploymentElement);
140
171
  const scopes = [];
141
172
  const doc = getDocument(context.container);
142
173
  const precomputed = doc.precomputedScopes;
@@ -150,11 +181,14 @@ export class LikeC4ScopeProvider extends DefaultScopeProvider {
150
181
  if (elements.length > 0) {
151
182
  scopes.push(stream(elements));
152
183
  }
184
+ if (isDeploymentReference && ast.isExtendDeploymentBody(container)) {
185
+ scopes.push(this.getScopeExtendDeployment(container.$container));
186
+ }
153
187
  if (isElementReference && ast.isExtendElementBody(container)) {
154
- scopes.push(this.scopeExtendElement(container.$container));
188
+ scopes.push(this.getScopeExtendElement(container.$container));
155
189
  }
156
190
  if (isElementReference && ast.isElementViewBody(container)) {
157
- scopes.push(this.scopeElementView(container.$container));
191
+ scopes.push(this.getScopeElementView(container.$container));
158
192
  }
159
193
  container = container.$container;
160
194
  }
@@ -1,6 +1,7 @@
1
1
  import type { LikeC4LangiumDocument } from '../ast';
2
2
  export declare function createTestServices(workspace?: string): {
3
3
  services: any;
4
+ addDocument: (input: string, uri?: string) => Promise<LikeC4LangiumDocument>;
4
5
  parse: (input: string, uri?: string) => Promise<LikeC4LangiumDocument>;
5
6
  validate: (input: string | LikeC4LangiumDocument, uri?: string) => Promise<{
6
7
  document: LikeC4LangiumDocument;
@@ -13,7 +14,7 @@ export declare function createTestServices(workspace?: string): {
13
14
  errors: any;
14
15
  warnings: any;
15
16
  }>;
16
- buildModel: () => Promise<any>;
17
+ buildModel: () => Promise<ComputedLikeC4Model>;
17
18
  buildLikeC4Model: () => Promise<any>;
18
19
  resetState: () => Promise<void>;
19
20
  format: (input: string | LikeC4LangiumDocument, uri?: string) => Promise<any>;
@@ -1,4 +1,3 @@
1
- import { LikeC4Model } from "@likec4/core";
2
1
  import { DocumentState, EmptyFileSystem, TextDocument } from "langium";
3
2
  import * as assert from "node:assert";
4
3
  import stripIndent from "strip-indent";
@@ -19,7 +18,7 @@ export function createTestServices(workspace = "file:///test/workspace") {
19
18
  };
20
19
  let isInitialized = false;
21
20
  let documentIndex = 1;
22
- const parse = async (input, uri) => {
21
+ const addDocument = async (input, uri) => {
23
22
  if (!isInitialized) {
24
23
  await services.shared.workspace.WorkspaceLock.write(async (_cancelToken) => {
25
24
  if (isInitialized) {
@@ -45,13 +44,17 @@ export function createTestServices(workspace = "file:///test/workspace") {
45
44
  docUri
46
45
  );
47
46
  langiumDocuments.addDocument(document);
47
+ return document;
48
+ };
49
+ const parse = async (input, uri) => {
50
+ const document = await addDocument(input, uri);
48
51
  await services.shared.workspace.WorkspaceLock.write(async (_cancelToken) => {
49
52
  await documentBuilder.build([document], { validation: false });
50
53
  });
51
54
  return document;
52
55
  };
53
56
  const validate = async (input, uri) => {
54
- const document = typeof input === "string" ? await parse(input, uri) : input;
57
+ const document = typeof input === "string" ? await addDocument(input, uri) : input;
55
58
  await services.shared.workspace.WorkspaceLock.write(async (_cancelToken) => {
56
59
  await documentBuilder.build([document], { validation: true });
57
60
  });
@@ -80,13 +83,12 @@ export function createTestServices(workspace = "file:///test/workspace") {
80
83
  return TextDocument.applyEdits(document.textDocument, edits ?? []);
81
84
  };
82
85
  const validateAll = async () => {
83
- await services.shared.workspace.WorkspaceLock.write(async (_cancelToken) => {
84
- const docs2 = langiumDocuments.all.toArray();
85
- await documentBuilder.build(docs2, { validation: true });
86
- });
87
- await documentBuilder.waitUntil(DocumentState.Validated);
88
86
  const docs = langiumDocuments.all.toArray();
89
87
  assert.ok(docs.length > 0, "no documents to validate");
88
+ await services.shared.workspace.WorkspaceLock.write(async (cancelToken) => {
89
+ await documentBuilder.build(docs, { validation: true }, cancelToken);
90
+ });
91
+ await documentBuilder.waitUntil(DocumentState.Validated);
90
92
  const diagnostics = docs.flatMap((doc) => doc.diagnostics ?? []);
91
93
  const warnings = diagnostics.flatMap((d) => d.severity === DiagnosticSeverity.Warning ? d.message : []);
92
94
  const errors = diagnostics.flatMap((d) => d.severity === DiagnosticSeverity.Error ? d.message : []);
@@ -98,15 +100,15 @@ export function createTestServices(workspace = "file:///test/workspace") {
98
100
  };
99
101
  const buildModel = async () => {
100
102
  await validateAll();
101
- const model = await modelBuilder.buildComputedModel();
102
- if (!model) throw new Error("No model found");
103
- return model;
103
+ const likec4model = await modelBuilder.buildLikeC4Model();
104
+ if (!likec4model) throw new Error("No model found");
105
+ return likec4model.$model;
104
106
  };
105
107
  const buildLikeC4Model = async () => {
106
108
  await validateAll();
107
- const model = await modelBuilder.buildComputedModel();
108
- if (!model) throw new Error("No model found");
109
- return LikeC4Model.create(model);
109
+ const likec4model = await modelBuilder.buildLikeC4Model();
110
+ if (!likec4model) throw new Error("No model found");
111
+ return likec4model;
110
112
  };
111
113
  const resetState = async () => {
112
114
  await services.shared.workspace.WorkspaceLock.write(async (cancelToken) => {
@@ -116,6 +118,7 @@ export function createTestServices(workspace = "file:///test/workspace") {
116
118
  };
117
119
  return {
118
120
  services,
121
+ addDocument,
119
122
  parse,
120
123
  validate,
121
124
  validateAll,
@@ -8,4 +8,4 @@ export declare function elementRef(node: ast.ElementRef | ast.StrictFqnElementRe
8
8
  * Returns FQN of StrictFqnElementRef
9
9
  * a.b.c.d - for c node returns a.b.c
10
10
  */
11
- export declare function getFqnElementRef(node: ast.StrictFqnElementRef): c4.Fqn;
11
+ export declare function readStrictFqn(node: ast.StrictFqnElementRef | ast.StrictFqnRef): c4.Fqn;
@@ -1,11 +1,11 @@
1
1
  export function elementRef(node) {
2
2
  return node.el.ref;
3
3
  }
4
- export function getFqnElementRef(node) {
5
- const name = [node.el.$refText];
4
+ export function readStrictFqn(node) {
5
+ const name = [node.$type === "StrictFqnRef" ? node.value.$refText : node.el.$refText];
6
6
  let parent = node.parent;
7
7
  while (parent) {
8
- name.push(parent.el.$refText);
8
+ name.push(parent.$type === "StrictFqnRef" ? parent.value.$refText : parent.el.$refText);
9
9
  parent = parent.parent;
10
10
  }
11
11
  if (name.length === 1) {
@@ -4,3 +4,4 @@ import type { LikeC4Services } from '../module';
4
4
  export declare const deploymentNodeChecks: (services: LikeC4Services) => ValidationCheck<ast.DeploymentNode>;
5
5
  export declare const deployedInstanceChecks: (services: LikeC4Services) => ValidationCheck<ast.DeployedInstance>;
6
6
  export declare const deploymentRelationChecks: (services: LikeC4Services) => ValidationCheck<ast.DeploymentRelation>;
7
+ export declare const extendDeploymentChecks: (services: LikeC4Services) => ValidationCheck<ast.ExtendDeployment>;
@@ -1,5 +1,6 @@
1
1
  import { FqnRef, isSameHierarchy, nonNullable } from "@likec4/core";
2
2
  import { AstUtils } from "langium";
3
+ import { ast } from "../ast.js";
3
4
  import { RESERVED_WORDS, tryOrLog } from "./_shared.js";
4
5
  const { getDocument } = AstUtils;
5
6
  export const deploymentNodeChecks = (services) => {
@@ -112,3 +113,14 @@ export const deploymentRelationChecks = (services) => {
112
113
  }
113
114
  });
114
115
  };
116
+ export const extendDeploymentChecks = (services) => {
117
+ return tryOrLog((el, accept) => {
118
+ const target = el.deploymentNode.value.ref;
119
+ if (!target || !ast.isDeploymentNode(target)) {
120
+ accept("error", "ExtendDeployment allows only DeploymentNode", {
121
+ node: el,
122
+ property: "deploymentNode"
123
+ });
124
+ }
125
+ });
126
+ };
@@ -3,7 +3,7 @@ import { type LikeC4LangiumDocument, ast } from '../ast';
3
3
  import type { LikeC4Services } from '../module';
4
4
  type Guard<N extends AstNode> = (n: AstNode) => n is N;
5
5
  type Guarded<G> = G extends Guard<infer N> ? N : never;
6
- declare const isValidatableAstNode: (n: AstNode) => n is ast.DeployedInstance | ast.DeploymentNode | ast.DeploymentViewRulePredicate | ast.DeploymentViewRuleStyle | ast.ViewRuleAutoLayout | ast.DynamicViewGlobalPredicateRef | ast.DynamicViewIncludePredicate | ast.ViewRuleGlobalStyle | ast.ViewRuleStyle | ast.ElementDescedantsExpression | ast.ElementKindExpression | ast.ElementRef | ast.ElementTagExpression | ast.ExpandElementExpression | ast.WildcardExpression | ast.ElementPredicateWhere | ast.ElementPredicateWith | ast.ElementPredicateWhereV2 | ast.FqnRefExpr | ast.ElementStringProperty | ast.ElementStyleProperty | ast.IconProperty | ast.LinkProperty | ast.MetadataBody | ast.DirectedRelationExpr | ast.InOutRelationExpr | ast.IncomingRelationExpr | ast.OutgoingRelationExpr | ast.RelationPredicateWhereV2 | ast.Element | ast.ExtendElement | ast.DeploymentView | ast.DynamicView | ast.ElementView | ast.DirectedRelationExpression | ast.InOutRelationExpression | ast.IncomingRelationExpression | ast.OutgoingRelationExpression | ast.RelationPredicateWhere | ast.RelationPredicateWith | ast.RelationStringProperty | ast.ArrowProperty | ast.ColorProperty | ast.LineProperty | ast.PaddingSizeProperty | ast.ShapeSizeProperty | ast.TextSizeProperty | ast.MetadataAttribute | ast.NotationProperty | ast.NotesProperty | ast.SpecificationElementStringProperty | ast.SpecificationRelationshipStringProperty | ast.ViewStringProperty | ast.BorderProperty | ast.MultipleProperty | ast.OpacityProperty | ast.ShapeProperty | ast.ViewRuleGlobalPredicateRef | ast.ViewRuleGroup | ast.ExcludePredicate | ast.IncludePredicate | ast.SpecificationRelationshipKind | ast.GlobalStyle | ast.SpecificationColor | ast.NavigateToProperty | ast.DynamicViewStep | ast.Tags | ast.DeploymentRelation | ast.SpecificationDeploymentNodeKind | ast.DynamicViewParallelSteps | ast.GlobalDynamicPredicateGroup | ast.DynamicViewPredicateIterator | ast.Relation | ast.SpecificationElementKind | ast.GlobalPredicateGroup | ast.Globals | ast.GlobalStyleGroup | ast.SpecificationRule | ast.SpecificationTag;
6
+ declare const isValidatableAstNode: (n: AstNode) => n is ast.DeployedInstance | ast.DeploymentNode | ast.DeploymentViewRulePredicate | ast.DeploymentViewRuleStyle | ast.ViewRuleAutoLayout | ast.DynamicViewGlobalPredicateRef | ast.DynamicViewIncludePredicate | ast.ViewRuleGlobalStyle | ast.ViewRuleStyle | ast.ElementDescedantsExpression | ast.ElementKindExpression | ast.ElementRef | ast.ElementTagExpression | ast.ExpandElementExpression | ast.WildcardExpression | ast.ElementPredicateWhere | ast.ElementPredicateWith | ast.ElementPredicateWhereV2 | ast.FqnRefExpr | ast.ElementStringProperty | ast.ElementStyleProperty | ast.IconProperty | ast.LinkProperty | ast.MetadataBody | ast.DirectedRelationExpr | ast.InOutRelationExpr | ast.IncomingRelationExpr | ast.OutgoingRelationExpr | ast.RelationPredicateWhereV2 | ast.Element | ast.ExtendDeployment | ast.ExtendElement | ast.DeploymentView | ast.DynamicView | ast.ElementView | ast.DirectedRelationExpression | ast.InOutRelationExpression | ast.IncomingRelationExpression | ast.OutgoingRelationExpression | ast.RelationPredicateWhere | ast.RelationPredicateWith | ast.RelationStringProperty | ast.ArrowProperty | ast.ColorProperty | ast.LineProperty | ast.PaddingSizeProperty | ast.ShapeSizeProperty | ast.TextSizeProperty | ast.MetadataAttribute | ast.NotationProperty | ast.NotesProperty | ast.SpecificationElementStringProperty | ast.SpecificationRelationshipStringProperty | ast.ViewStringProperty | ast.BorderProperty | ast.MultipleProperty | ast.OpacityProperty | ast.ShapeProperty | ast.ViewRuleGlobalPredicateRef | ast.ViewRuleGroup | ast.ExcludePredicate | ast.IncludePredicate | ast.SpecificationRelationshipKind | ast.GlobalStyle | ast.SpecificationColor | ast.NavigateToProperty | ast.DynamicViewStep | ast.Tags | ast.DeploymentRelation | ast.SpecificationDeploymentNodeKind | ast.DynamicViewParallelSteps | ast.GlobalDynamicPredicateGroup | ast.DynamicViewPredicateIterator | ast.Relation | ast.SpecificationElementKind | ast.GlobalPredicateGroup | ast.Globals | ast.GlobalStyleGroup | ast.SpecificationRule | ast.SpecificationTag;
7
7
  type ValidatableAstNode = Guarded<typeof isValidatableAstNode>;
8
8
  export declare function checksFromDiagnostics(doc: LikeC4LangiumDocument): {
9
9
  isValid: (n: ValidatableAstNode) => boolean;
@@ -3,7 +3,12 @@ import { isNullish } from "remeda";
3
3
  import { DiagnosticSeverity } from "vscode-languageserver-types";
4
4
  import { ast } from "../ast.js";
5
5
  import { logger } from "../logger.js";
6
- import { deployedInstanceChecks, deploymentNodeChecks, deploymentRelationChecks } from "./deployment-checks.js";
6
+ import {
7
+ deployedInstanceChecks,
8
+ deploymentNodeChecks,
9
+ deploymentRelationChecks,
10
+ extendDeploymentChecks
11
+ } from "./deployment-checks.js";
7
12
  import { dynamicViewRulePredicate } from "./dynamic-view-rule.js";
8
13
  import { dynamicViewStep } from "./dynamic-view-step.js";
9
14
  import { elementChecks } from "./element.js";
@@ -70,6 +75,7 @@ const isValidatableAstNode = validatableAstNodeGuards([
70
75
  ast.isNavigateToProperty,
71
76
  ast.isElement,
72
77
  ast.isExtendElement,
78
+ ast.isExtendDeployment,
73
79
  ast.isSpecificationElementKind,
74
80
  ast.isSpecificationRelationshipKind,
75
81
  ast.isSpecificationDeploymentNodeKind,
@@ -114,6 +120,7 @@ export function registerValidationChecks(services) {
114
120
  DeploymentNodeKind: deploymentNodeKindChecks(services),
115
121
  DeploymentNode: deploymentNodeChecks(services),
116
122
  DeploymentRelation: deploymentRelationChecks(services),
123
+ ExtendDeployment: extendDeploymentChecks(services),
117
124
  FqnRefExpr: fqnRefExprChecks(services),
118
125
  RelationExpr: relationExprChecks(services),
119
126
  NotesProperty: notesPropertyRuleChecks(services),
@@ -19,7 +19,7 @@ export const ConfigurableLayouter = {
19
19
  const layouter = new GraphvizLayouter(wasmAdapter);
20
20
  const langId = services.LanguageMetaData.languageId;
21
21
  services.shared.workspace.ConfigurationProvider.onConfigurationSectionUpdate((update) => {
22
- logger.debug("Configuration update", { update });
22
+ logger.debug("Configuration update: {update}", { update });
23
23
  if (update.section === langId) {
24
24
  try {
25
25
  const { mode, path } = update.configuration.graphviz ?? {
@@ -41,13 +41,13 @@ export const ConfigurableLayouter = {
41
41
  return;
42
42
  }
43
43
  layouter.changePort(new GraphvizBinaryAdapter(binaryPath));
44
- logger.info(`use graphviz binary: ${binaryPath}`);
44
+ logger.info`use graphviz binary: ${binaryPath}`;
45
45
  } catch (error) {
46
46
  logger.error("Failed to update configuration", { error });
47
47
  }
48
48
  return;
49
49
  }
50
- logger.warn("Unexpected configuration update", { update });
50
+ logger.warn("Unexpected configuration update: {update}", { update });
51
51
  });
52
52
  return layouter;
53
53
  }
@@ -15,6 +15,7 @@ export declare class LikeC4Views {
15
15
  private services;
16
16
  private cache;
17
17
  private viewsWithReportedErrors;
18
+ private ModelBuilder;
18
19
  constructor(services: LikeC4Services);
19
20
  get layouter(): GraphvizLayouter;
20
21
  computedViews(cancelToken?: Cancellation.CancellationToken): Promise<ComputedView[]>;
@@ -3,15 +3,17 @@ import { logError, logWarnError } from "../logger.js";
3
3
  export class LikeC4Views {
4
4
  constructor(services) {
5
5
  this.services = services;
6
+ this.ModelBuilder = services.likec4.ModelBuilder;
6
7
  }
7
8
  cache = /* @__PURE__ */ new WeakMap();
8
9
  viewsWithReportedErrors = /* @__PURE__ */ new Set();
10
+ ModelBuilder;
9
11
  get layouter() {
10
12
  return this.services.likec4.Layouter;
11
13
  }
12
14
  async computedViews(cancelToken) {
13
- const model = await this.services.likec4.ModelBuilder.buildComputedModel(cancelToken);
14
- return model ? values(model.views) : [];
15
+ const likeC4Model = await this.ModelBuilder.buildLikeC4Model(cancelToken);
16
+ return values(likeC4Model.$model.views);
15
17
  }
16
18
  async layoutAllViews(cancelToken) {
17
19
  const views = await this.computedViews(cancelToken);
@@ -24,6 +26,7 @@ export class LikeC4Views {
24
26
  this.viewsWithReportedErrors.delete(view.id);
25
27
  tasks.push(
26
28
  this.layouter.layout(view).then((result) => {
29
+ this.viewsWithReportedErrors.delete(view.id);
27
30
  this.cache.set(view, result);
28
31
  return result;
29
32
  }).catch((e) => {
@@ -41,17 +44,14 @@ export class LikeC4Views {
41
44
  return results;
42
45
  }
43
46
  async layoutView(viewId, cancelToken) {
44
- const model = await this.services.likec4.ModelBuilder.buildComputedModel(cancelToken);
45
- if (!model) {
46
- return null;
47
- }
48
- const view = model.views[viewId];
47
+ const model = await this.ModelBuilder.buildLikeC4Model(cancelToken);
48
+ const view = model.findView(viewId)?.$view;
49
49
  if (!view) {
50
50
  return null;
51
51
  }
52
52
  let cached = this.cache.get(view);
53
53
  if (cached) {
54
- return cached;
54
+ return await Promise.resolve(cached);
55
55
  }
56
56
  try {
57
57
  const result = await this.layouter.layout(view);
@@ -63,8 +63,8 @@ export class LikeC4Views {
63
63
  const errMessage = e instanceof Error ? e.message : "" + e;
64
64
  this.services.shared.lsp.Connection?.window.showErrorMessage(`LikeC4: ${errMessage}`);
65
65
  this.viewsWithReportedErrors.add(viewId);
66
+ logError(e);
66
67
  }
67
- logError(e);
68
68
  return Promise.reject(e);
69
69
  }
70
70
  }
@@ -74,7 +74,7 @@ export class LikeC4Views {
74
74
  }
75
75
  async viewsAsGraphvizOut() {
76
76
  const KEY = "All-LayoutedViews-DotWithSvg";
77
- const cache = this.services.WorkspaceCache;
77
+ const cache = this.services.ValidatedWorkspaceCache;
78
78
  if (cache.has(KEY)) {
79
79
  return await Promise.resolve(cache.get(KEY));
80
80
  }
@@ -101,7 +101,7 @@ export class LikeC4Views {
101
101
  }
102
102
  async overviewGraph() {
103
103
  const KEY = "OverviewGraph";
104
- const cache = this.services.WorkspaceCache;
104
+ const cache = this.services.ValidatedWorkspaceCache;
105
105
  if (cache.has(KEY)) {
106
106
  return await Promise.resolve(cache.get(KEY));
107
107
  }