@likec4/language-server 0.45.0 → 0.46.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 (37) hide show
  1. package/contrib/likec4.monarch.ts +9 -8
  2. package/dist/Rpc.js +14 -60
  3. package/dist/ast.d.ts +9 -10
  4. package/dist/ast.js +14 -22
  5. package/dist/elementRef.d.ts +5 -11
  6. package/dist/elementRef.js +5 -32
  7. package/dist/generated/ast.d.ts +62 -60
  8. package/dist/generated/ast.js +59 -33
  9. package/dist/generated/grammar.d.ts +1 -1
  10. package/dist/generated/grammar.js +1 -1
  11. package/dist/generated/module.d.ts +1 -1
  12. package/dist/generated/module.js +1 -0
  13. package/dist/lsp/CodeLensProvider.js +2 -1
  14. package/dist/lsp/DocumentSymbolProvider.d.ts +1 -1
  15. package/dist/lsp/DocumentSymbolProvider.js +8 -5
  16. package/dist/lsp/SemanticTokenProvider.js +29 -63
  17. package/dist/model/fqn-computation.js +5 -5
  18. package/dist/model/fqn-index.d.ts +2 -1
  19. package/dist/model/fqn-index.js +26 -31
  20. package/dist/model/model-builder.js +20 -11
  21. package/dist/model/model-locator.js +4 -11
  22. package/dist/model/model-parser.d.ts +6 -3
  23. package/dist/model/model-parser.js +41 -36
  24. package/dist/module.js +4 -0
  25. package/dist/protocol.d.ts +0 -3
  26. package/dist/protocol.js +1 -4
  27. package/dist/references/scope-computation.js +19 -17
  28. package/dist/references/scope-provider.js +19 -19
  29. package/dist/shared/NodeKindProvider.d.ts +13 -0
  30. package/dist/shared/NodeKindProvider.js +29 -0
  31. package/dist/test/testServices.js +8 -8
  32. package/dist/utils.js +1 -1
  33. package/dist/validation/index.js +13 -3
  34. package/dist/validation/relation.js +12 -18
  35. package/dist/validation/specification.d.ts +3 -0
  36. package/dist/validation/specification.js +30 -0
  37. package/package.json +9 -9
@@ -2,51 +2,53 @@ import {
2
2
  DefaultScopeComputation,
3
3
  MultiMap
4
4
  } from "langium";
5
- import { isEmpty } from "remeda";
5
+ import { hasAtLeast, isEmpty } from "remeda";
6
6
  import { ast } from "../ast.js";
7
+ import { logError } from "../logger.js";
7
8
  export class LikeC4ScopeComputation extends DefaultScopeComputation {
8
9
  computeExports(document, _cancelToken) {
9
- const { specification, model, views } = document.parseResult.value;
10
- const docExports = [];
11
- if (specification) {
12
- for (const spec of specification.elements) {
10
+ try {
11
+ const { specifications, models, views } = document.parseResult.value;
12
+ const docExports = [];
13
+ for (const spec of specifications.flatMap((s) => s.elements)) {
13
14
  if (spec.kind && !isEmpty(spec.kind.name)) {
14
15
  docExports.push(this.descriptions.createDescription(spec.kind, spec.kind.name, document));
15
16
  }
16
17
  }
17
- for (const spec of specification.tags) {
18
+ for (const spec of specifications.flatMap((s) => s.tags)) {
18
19
  if (spec.tag && !isEmpty(spec.tag.name)) {
19
20
  docExports.push(this.descriptions.createDescription(spec.tag, spec.tag.name, document));
20
- docExports.push(this.descriptions.createDescription(spec.tag, "#" + spec.tag.name, document));
21
+ docExports.push(
22
+ this.descriptions.createDescription(spec.tag, "#" + spec.tag.name, document)
23
+ );
21
24
  }
22
25
  }
23
- for (const spec of specification.relationships) {
26
+ for (const spec of specifications.flatMap((s) => s.relationships)) {
24
27
  if (spec.kind && !isEmpty(spec.kind.name)) {
25
28
  docExports.push(this.descriptions.createDescription(spec.kind, spec.kind.name, document));
26
29
  }
27
30
  }
28
- }
29
- if (model && model.elements.length > 0) {
30
- for (const elAst of model.elements) {
31
+ for (const elAst of models.flatMap((m) => m.elements)) {
31
32
  if (ast.isElement(elAst) && !isEmpty(elAst.name)) {
32
33
  docExports.push(this.descriptions.createDescription(elAst, elAst.name, document));
33
34
  }
34
35
  }
35
- }
36
- if (views && views.views.length > 0) {
37
- for (const viewAst of views.views) {
36
+ for (const viewAst of views.flatMap((v) => v.views)) {
38
37
  if (viewAst.name && !isEmpty(viewAst.name)) {
39
38
  docExports.push(this.descriptions.createDescription(viewAst, viewAst.name, document));
40
39
  }
41
40
  }
41
+ return Promise.resolve(docExports);
42
+ } catch (e) {
43
+ logError(e);
44
+ return Promise.reject(e);
42
45
  }
43
- return Promise.resolve(docExports);
44
46
  }
45
47
  async computeLocalScopes(document, _cancelToken) {
46
48
  const root = document.parseResult.value;
47
49
  const scopes = new MultiMap();
48
- if (root.model) {
49
- const nested = this.processContainer(root.model, scopes, document);
50
+ if (hasAtLeast(root.models, 1)) {
51
+ const nested = this.processContainer(root.models[0], scopes, document);
50
52
  scopes.addAll(root, nested.values());
51
53
  }
52
54
  return Promise.resolve(scopes);
@@ -4,13 +4,13 @@ import {
4
4
  EMPTY_STREAM,
5
5
  StreamImpl,
6
6
  StreamScope,
7
+ findNodeForProperty,
7
8
  getDocument,
8
9
  stream,
9
- findNodeForProperty,
10
10
  toDocumentSegment
11
11
  } from "langium";
12
12
  import { ast } from "../ast.js";
13
- import { elementRef, isElementRefHead, parentFqnElementRef } from "../elementRef.js";
13
+ import { elementRef, getFqnElementRef } from "../elementRef.js";
14
14
  import { logError } from "../logger.js";
15
15
  function toAstNodeDescription(entry) {
16
16
  const $cstNode = findNodeForProperty(entry.el.$cstNode, "name");
@@ -56,41 +56,41 @@ export class LikeC4ScopeProvider extends DefaultScopeProvider {
56
56
  );
57
57
  }
58
58
  scopeElementRef(ref) {
59
- const parentNode = ref.$container;
60
- if (!ast.isElementRef(parentNode)) {
61
- throw new Error("Expected be inside ElementRef");
62
- }
63
- return this.uniqueDescedants(() => parentNode.el.ref);
59
+ return this.uniqueDescedants(() => ref.el.ref);
64
60
  }
65
61
  scopeExtendElement(extend) {
66
62
  return this.uniqueDescedants(() => elementRef(extend.element));
67
63
  }
68
64
  scopeElementView({ viewOf, extends: ext }) {
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)));
70
+ }
69
71
  if (ext) {
70
72
  return stream([ext]).flatMap((v) => {
71
73
  const view = v.view.ref;
72
74
  return view ? this.scopeElementView(view) : EMPTY_STREAM;
73
75
  });
74
76
  }
75
- if (viewOf) {
76
- return this.uniqueDescedants(() => elementRef(viewOf));
77
- }
78
77
  return EMPTY_STREAM;
79
78
  }
80
79
  getScope(context) {
81
80
  const referenceType = this.reflection.getReferenceType(context);
82
81
  try {
83
82
  const container = context.container;
84
- if (referenceType === ast.Element) {
85
- if (ast.isStrictElementRef(container)) {
86
- if (isElementRefHead(container)) {
87
- return this.getGlobalScope(referenceType);
88
- }
89
- const parent = parentFqnElementRef(container);
90
- return new StreamScope(this.directChildrenOf(parent));
83
+ if (ast.isFqnElementRef(container) && context.property === "el") {
84
+ const parent = container.parent;
85
+ if (!parent) {
86
+ return this.getGlobalScope(referenceType);
91
87
  }
92
- if (ast.isElementRef(container) && !isElementRefHead(container)) {
93
- return new StreamScope(this.scopeElementRef(container));
88
+ return new StreamScope(this.directChildrenOf(getFqnElementRef(parent)));
89
+ }
90
+ if (ast.isElementRef(container) && context.property === "el") {
91
+ const parent = container.parent;
92
+ if (parent) {
93
+ return new StreamScope(this.scopeElementRef(parent));
94
94
  }
95
95
  }
96
96
  return this.computeScope(container, referenceType);
@@ -0,0 +1,13 @@
1
+ import { type AstNode, type AstNodeDescription } from 'langium';
2
+ import { CompletionItemKind, SymbolKind } from 'vscode-languageserver-protocol';
3
+ export declare class NodeKindProvider implements NodeKindProvider {
4
+ /**
5
+ * Returns a `SymbolKind` as used by `WorkspaceSymbolProvider` or `DocumentSymbolProvider`.
6
+ */
7
+ getSymbolKind(_node: AstNode | AstNodeDescription): SymbolKind;
8
+ /**
9
+ * Returns a `CompletionItemKind` as used by the `CompletionProvider`.
10
+ */
11
+ getCompletionItemKind(node: AstNode | AstNodeDescription): CompletionItemKind;
12
+ }
13
+ //# sourceMappingURL=NodeKindProvider.d.ts.map
@@ -0,0 +1,29 @@
1
+ import { isAstNode } from "langium";
2
+ import { CompletionItemKind, SymbolKind } from "vscode-languageserver-protocol";
3
+ import { ast } from "../ast.js";
4
+ export class NodeKindProvider {
5
+ /**
6
+ * Returns a `SymbolKind` as used by `WorkspaceSymbolProvider` or `DocumentSymbolProvider`.
7
+ */
8
+ getSymbolKind(_node) {
9
+ return SymbolKind.Field;
10
+ }
11
+ /**
12
+ * Returns a `CompletionItemKind` as used by the `CompletionProvider`.
13
+ */
14
+ getCompletionItemKind(node) {
15
+ const hasType = (type) => "type" in node && node.type === type;
16
+ switch (true) {
17
+ case (isAstNode(node) ? ast.isElement(node) : node.type === ast.Element): {
18
+ return CompletionItemKind.Variable;
19
+ }
20
+ case (ast.isElementKind(node) || ast.isRelationshipKind(node) || hasType(ast.ElementKind) || hasType(ast.RelationshipKind)): {
21
+ return CompletionItemKind.Class;
22
+ }
23
+ case (ast.isTag(node) || hasType(ast.Tag)): {
24
+ return CompletionItemKind.EnumMember;
25
+ }
26
+ }
27
+ return CompletionItemKind.Reference;
28
+ }
29
+ }
@@ -13,17 +13,19 @@ export function createTestServices(workspace = "file:///test/workspace") {
13
13
  name: "test",
14
14
  uri: workspaceUri.toString()
15
15
  };
16
- const initPromise = services.shared.workspace.WorkspaceManager.initializeWorkspace([
17
- workspaceFolder
18
- ]);
19
- void initPromise.finally(() => {
16
+ let isInitialized = false;
17
+ const init = async () => {
18
+ if (isInitialized)
19
+ return Promise.resolve();
20
+ isInitialized = true;
21
+ await services.shared.workspace.WorkspaceManager.initializeWorkspace([workspaceFolder]);
20
22
  Object.assign(services.shared.workspace.WorkspaceManager, {
21
23
  folders: [workspaceFolder]
22
24
  });
23
- });
25
+ };
24
26
  let documentIndex = 1;
25
27
  const parse = async (input, uri) => {
26
- await initPromise;
28
+ await init();
27
29
  const docUri = Utils.resolvePath(
28
30
  workspaceUri,
29
31
  "./src/",
@@ -38,7 +40,6 @@ export function createTestServices(workspace = "file:///test/workspace") {
38
40
  return document;
39
41
  };
40
42
  const validate = async (input, uri) => {
41
- await initPromise;
42
43
  const document = typeof input === "string" ? await parse(input, uri) : input;
43
44
  await documentBuilder.build([document], { validation: true });
44
45
  const diagnostics = document.diagnostics ?? [];
@@ -50,7 +51,6 @@ export function createTestServices(workspace = "file:///test/workspace") {
50
51
  };
51
52
  };
52
53
  const validateAll = async () => {
53
- await initPromise;
54
54
  const docs = langiumDocuments.all.toArray();
55
55
  await documentBuilder.build(docs, { validation: true });
56
56
  const diagnostics = docs.flatMap((doc) => doc.diagnostics ?? []);
package/dist/utils.js CHANGED
@@ -1,4 +1,4 @@
1
- export const printDocs = (docs) => docs.map((d) => " - " + d.uri.path).join("\n");
1
+ export const printDocs = (docs) => docs.map((d) => " - " + d.uri).join("\n");
2
2
  export function queueMicrotask(cb) {
3
3
  return Promise.resolve().then(cb);
4
4
  }
@@ -1,13 +1,23 @@
1
1
  import { logger } from "../logger.js";
2
2
  import { elementChecks } from "./element.js";
3
3
  import { relationChecks } from "./relation.js";
4
- import { elementKindChecks, relationshipChecks, tagChecks } from "./specification.js";
4
+ import {
5
+ elementKindChecks,
6
+ modelRuleChecks,
7
+ modelViewsChecks,
8
+ relationshipChecks,
9
+ specificationRuleChecks,
10
+ tagChecks
11
+ } from "./specification.js";
5
12
  import { viewChecks } from "./view.js";
6
13
  import { incomingExpressionChecks, outgoingExpressionChecks } from "./view-predicates/index.js";
7
14
  export function registerValidationChecks(services) {
8
15
  logger.info("registerValidationChecks");
9
16
  const registry = services.validation.ValidationRegistry;
10
17
  registry.register({
18
+ SpecificationRule: specificationRuleChecks(services),
19
+ Model: modelRuleChecks(services),
20
+ ModelViews: modelViewsChecks(services),
11
21
  ElementView: viewChecks(services),
12
22
  Element: elementChecks(services),
13
23
  ElementKind: elementKindChecks(services),
@@ -16,10 +26,10 @@ export function registerValidationChecks(services) {
16
26
  RelationshipKind: relationshipChecks(services),
17
27
  IncomingExpression: incomingExpressionChecks(services),
18
28
  OutgoingExpression: outgoingExpressionChecks(services)
19
- });
29
+ }, "slow");
20
30
  const connection = services.shared.lsp.Connection;
21
31
  if (connection) {
22
- services.shared.workspace.DocumentBuilder.onUpdate((changed, deleted) => {
32
+ services.shared.workspace.DocumentBuilder.onUpdate((_, deleted) => {
23
33
  for (const uri of deleted) {
24
34
  void connection.sendDiagnostics({
25
35
  uri: uri.toString(),
@@ -15,31 +15,25 @@ export const relationChecks = (services) => {
15
15
  });
16
16
  }
17
17
  let sourceEl;
18
- if ("source" in el) {
18
+ if (ast.isExplicitRelation(el)) {
19
19
  sourceEl = elementRef(el.source);
20
- } else {
21
- if (!ast.isElementBody(el.$container)) {
22
- accept(
23
- "error",
24
- "Invalid relation, expected to have source defined or be inside the element",
25
- {
26
- node: el,
27
- keyword: "->"
28
- }
29
- );
30
- } else {
31
- sourceEl = el.$container.$container;
20
+ if (!sourceEl) {
21
+ return accept("error", "Source not found (not parsed/indexed yet)", {
22
+ node: el,
23
+ property: "source"
24
+ });
32
25
  }
26
+ } else {
27
+ sourceEl = el.$container.$container;
33
28
  }
34
- const source = sourceEl && fqnIndex.getFqn(sourceEl);
35
- if (sourceEl && !source) {
29
+ const source = fqnIndex.getFqn(sourceEl);
30
+ if (!source) {
36
31
  accept("error", "Source not found (not parsed/indexed yet)", {
37
- node: el,
38
- property: "source"
32
+ node: el
39
33
  });
40
34
  }
41
35
  if (source && target && isSameHierarchy(source, target)) {
42
- return accept("error", "Invalid parent-child relationship", {
36
+ accept("error", "Invalid parent-child relationship", {
43
37
  node: el
44
38
  });
45
39
  }
@@ -1,6 +1,9 @@
1
1
  import type { ValidationCheck } from 'langium';
2
2
  import { ast } from '../ast';
3
3
  import type { LikeC4Services } from '../module';
4
+ export declare const specificationRuleChecks: (_: LikeC4Services) => ValidationCheck<ast.SpecificationRule>;
5
+ export declare const modelRuleChecks: (_: LikeC4Services) => ValidationCheck<ast.Model>;
6
+ export declare const modelViewsChecks: (_: LikeC4Services) => ValidationCheck<ast.ModelViews>;
4
7
  export declare const elementKindChecks: (services: LikeC4Services) => ValidationCheck<ast.ElementKind>;
5
8
  export declare const tagChecks: (services: LikeC4Services) => ValidationCheck<ast.Tag>;
6
9
  export declare const relationshipChecks: (services: LikeC4Services) => ValidationCheck<ast.RelationshipKind>;
@@ -1,4 +1,34 @@
1
1
  import { ast } from "../ast.js";
2
+ export const specificationRuleChecks = (_) => {
3
+ return (node, accept) => {
4
+ if (node.$containerIndex && node.$containerIndex > 0) {
5
+ accept("error", `Only one specification per document is allowed`, {
6
+ node,
7
+ property: "name"
8
+ });
9
+ }
10
+ };
11
+ };
12
+ export const modelRuleChecks = (_) => {
13
+ return (node, accept) => {
14
+ if (node.$containerIndex && node.$containerIndex > 0) {
15
+ accept("error", `Only one model per document is allowed`, {
16
+ node,
17
+ property: "name"
18
+ });
19
+ }
20
+ };
21
+ };
22
+ export const modelViewsChecks = (_) => {
23
+ return (node, accept) => {
24
+ if (node.$containerIndex && node.$containerIndex > 0) {
25
+ accept("error", `Only one views block per document is allowed`, {
26
+ node,
27
+ property: "name"
28
+ });
29
+ }
30
+ };
31
+ };
2
32
  export const elementKindChecks = (services) => {
3
33
  const index = services.shared.workspace.IndexManager;
4
34
  return (node, accept) => {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@likec4/language-server",
3
3
  "description": "LikeC4 Language Server",
4
- "version": "0.45.0",
4
+ "version": "0.46.0",
5
5
  "license": "MIT",
6
6
  "bugs": "https://github.com/likec4/likec4/issues",
7
7
  "homepage": "https://likec4.dev",
@@ -50,24 +50,24 @@
50
50
  "test": "vitest run"
51
51
  },
52
52
  "dependencies": {
53
- "@likec4/core": "0.45.0",
54
- "@likec4/graph": "0.45.0",
55
- "langium": "^2.0.2",
53
+ "@likec4/core": "0.46.0",
54
+ "@likec4/graph": "0.46.0",
55
+ "langium": "^2.1.2",
56
56
  "object-hash": "^3.0.0",
57
57
  "p-debounce": "^4.0.0",
58
58
  "p-throttle": "^5.1.0",
59
59
  "rambdax": "^9.1.1",
60
- "remeda": "^1.28.0",
60
+ "remeda": "^1.29.0",
61
61
  "strip-indent": "^4.0.0",
62
- "vscode-languageserver": "~8.1.0",
63
- "vscode-languageserver-protocol": "~3.17.3",
64
- "vscode-uri": "~3.0.7"
62
+ "vscode-languageserver": "9.0.1",
63
+ "vscode-languageserver-protocol": "3.17.5",
64
+ "vscode-uri": "3.0.8"
65
65
  },
66
66
  "devDependencies": {
67
67
  "@types/node": "^20.8.7",
68
68
  "@types/object-hash": "^3.0.5",
69
69
  "execa": "^8.0.1",
70
- "langium-cli": "^2.0.1",
70
+ "langium-cli": "^2.1.0",
71
71
  "npm-run-all": "^4.1.5",
72
72
  "typescript": "^5.2.2",
73
73
  "unbuild": "^2.0.0",