@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
@@ -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,13 +18,10 @@ 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) {
23
+ isInitialized = true;
24
24
  await services.shared.workspace.WorkspaceLock.write(async (_cancelToken) => {
25
- if (isInitialized) {
26
- return;
27
- }
28
- isInitialized = true;
29
25
  services.shared.workspace.WorkspaceManager.initialize({
30
26
  capabilities: {},
31
27
  processId: null,
@@ -45,13 +41,17 @@ export function createTestServices(workspace = "file:///test/workspace") {
45
41
  docUri
46
42
  );
47
43
  langiumDocuments.addDocument(document);
44
+ return document;
45
+ };
46
+ const parse = async (input, uri) => {
47
+ const document = await addDocument(input, uri);
48
48
  await services.shared.workspace.WorkspaceLock.write(async (_cancelToken) => {
49
49
  await documentBuilder.build([document], { validation: false });
50
50
  });
51
51
  return document;
52
52
  };
53
53
  const validate = async (input, uri) => {
54
- const document = typeof input === "string" ? await parse(input, uri) : input;
54
+ const document = typeof input === "string" ? await addDocument(input, uri) : input;
55
55
  await services.shared.workspace.WorkspaceLock.write(async (_cancelToken) => {
56
56
  await documentBuilder.build([document], { validation: true });
57
57
  });
@@ -80,13 +80,11 @@ export function createTestServices(workspace = "file:///test/workspace") {
80
80
  return TextDocument.applyEdits(document.textDocument, edits ?? []);
81
81
  };
82
82
  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
83
  const docs = langiumDocuments.all.toArray();
89
84
  assert.ok(docs.length > 0, "no documents to validate");
85
+ await services.shared.workspace.WorkspaceLock.write(async (cancelToken) => {
86
+ await documentBuilder.build(docs, { validation: true }, cancelToken);
87
+ });
90
88
  const diagnostics = docs.flatMap((doc) => doc.diagnostics ?? []);
91
89
  const warnings = diagnostics.flatMap((d) => d.severity === DiagnosticSeverity.Warning ? d.message : []);
92
90
  const errors = diagnostics.flatMap((d) => d.severity === DiagnosticSeverity.Error ? d.message : []);
@@ -97,16 +95,20 @@ export function createTestServices(workspace = "file:///test/workspace") {
97
95
  };
98
96
  };
99
97
  const buildModel = async () => {
100
- await validateAll();
101
- const model = await modelBuilder.buildComputedModel();
102
- if (!model) throw new Error("No model found");
103
- return model;
98
+ if (langiumDocuments.all.some((doc) => doc.state < DocumentState.Validated)) {
99
+ await validateAll();
100
+ }
101
+ const likec4model = await modelBuilder.buildLikeC4Model();
102
+ if (!likec4model) throw new Error("No model found");
103
+ return likec4model.$model;
104
104
  };
105
105
  const buildLikeC4Model = async () => {
106
- await validateAll();
107
- const model = await modelBuilder.buildComputedModel();
108
- if (!model) throw new Error("No model found");
109
- return LikeC4Model.create(model);
106
+ if (langiumDocuments.all.some((doc) => doc.state < DocumentState.Validated)) {
107
+ await validateAll();
108
+ }
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
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@likec4/language-server",
3
3
  "description": "LikeC4 Language Server",
4
- "version": "1.23.1",
4
+ "version": "1.24.1",
5
5
  "license": "MIT",
6
6
  "bugs": "https://github.com/likec4/likec4/issues",
7
7
  "homepage": "https://likec4.dev",
@@ -84,11 +84,11 @@
84
84
  "@hpcc-js/wasm-graphviz": "1.7.0"
85
85
  },
86
86
  "devDependencies": {
87
- "@msgpack/msgpack": "^3.0.0",
87
+ "@msgpack/msgpack": "^3.1.0",
88
88
  "@smithy/util-base64": "^4.0.0",
89
89
  "@types/node": "^20.17.17",
90
90
  "@types/which": "^3.0.4",
91
- "@vitest/coverage-v8": "^3.0.4",
91
+ "@vitest/coverage-v8": "^3.0.6",
92
92
  "esm-env": "^1.2.2",
93
93
  "fast-equals": "^5.2.2",
94
94
  "fdir": "^6.4.3",
@@ -96,28 +96,27 @@
96
96
  "json5": "^2.2.3",
97
97
  "langium": "3.3.1",
98
98
  "langium-cli": "3.3.0",
99
- "mnemonist": "^0.40.2",
100
99
  "natural-compare-lite": "^1.4.0",
101
100
  "p-debounce": "^4.0.0",
102
- "remeda": "^2.20.1",
101
+ "remeda": "^2.20.2",
103
102
  "strip-indent": "^4.0.0",
104
- "tsx": "~4.19.2",
103
+ "tsx": "~4.19.3",
105
104
  "turbo": "^2.4.2",
106
105
  "type-fest": "4.34.1",
107
106
  "typescript": "5.7.3",
108
107
  "ufo": "^1.5.4",
109
108
  "unbuild": "^3.3.1",
110
- "vitest": "^3.0.4",
109
+ "vitest": "^3.0.6",
111
110
  "vscode-jsonrpc": "8.2.0",
112
111
  "vscode-languageserver": "9.0.1",
113
112
  "vscode-languageserver-types": "3.17.5",
114
113
  "vscode-uri": "3.1.0",
115
114
  "which": "^5.0.0",
116
- "@likec4/icons": "1.23.1",
117
- "@likec4/layouts": "1.23.1",
118
- "@likec4/tsconfig": "1.23.1",
119
- "@likec4/log": "1.23.1",
120
- "@likec4/core": "1.23.1"
115
+ "@likec4/core": "1.24.1",
116
+ "@likec4/icons": "1.24.1",
117
+ "@likec4/layouts": "1.24.1",
118
+ "@likec4/log": "1.24.1",
119
+ "@likec4/tsconfig": "1.24.1"
121
120
  },
122
121
  "scripts": {
123
122
  "typecheck": "tsc --noEmit",
@@ -126,7 +125,7 @@
126
125
  "watch:langium": "langium generate --watch",
127
126
  "watch:ts": "tsc --watch",
128
127
  "generate": "langium generate && tsx scripts/generate-icons.ts",
129
- "dev": "run-p 'watch:*'",
128
+ "dev": "run-p \"watch:*\"",
130
129
  "lint": "run -T eslint src/ --fix",
131
130
  "clean": "rm -r -f dist contrib",
132
131
  "test": "vitest run --no-isolate",
package/dist/bundled.d.ts DELETED
@@ -1,8 +0,0 @@
1
- import { type LikeC4Services, type LikeC4SharedServices } from './module';
2
- /**
3
- * This is used as `bin` entry point to start the language server.
4
- */
5
- export declare function startLanguageServer(): Promise<{
6
- shared: LikeC4SharedServices;
7
- likec4: LikeC4Services;
8
- }>;
package/dist/bundled.js DELETED
@@ -1,25 +0,0 @@
1
- import { configureLogger, getConsoleSink } from "@likec4/log";
2
- import { startLanguageServer as startLanguim } from "langium/lsp";
3
- import { createConnection, ProposedFeatures } from "vscode-languageserver/node";
4
- import { LikeC4FileSystem } from "./LikeC4FileSystem.js";
5
- import { logger } from "./logger.js";
6
- import { createCustomLanguageServices } from "./module.js";
7
- import { ConfigurableLayouter } from "./views/configurable-layouter.js";
8
- export async function startLanguageServer() {
9
- const connection = createConnection(ProposedFeatures.all);
10
- await configureLogger({
11
- sinks: {
12
- console: getConsoleSink()
13
- },
14
- loggers: [
15
- {
16
- category: ["likec4"],
17
- sinks: ["console"]
18
- }
19
- ]
20
- });
21
- logger.info("Starting LikeC4 language server");
22
- const services = createCustomLanguageServices({ connection, ...LikeC4FileSystem }, ConfigurableLayouter);
23
- startLanguim(services.shared);
24
- return services;
25
- }
@@ -1,3 +0,0 @@
1
- import { type LikeC4LangiumDocument } from '../ast';
2
- import type { LikeC4Services } from '../module';
3
- export declare function computeDocumentFqn(document: LikeC4LangiumDocument, services: LikeC4Services): void;