@likec4/language-server 1.25.0 → 1.26.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 (61) hide show
  1. package/bin/likec4-language-server.mjs +1 -1
  2. package/dist/LikeC4FileSystem.js +6 -1
  3. package/dist/LikeC4LanguageServices.d.ts +77 -0
  4. package/dist/LikeC4LanguageServices.js +118 -0
  5. package/dist/Rpc.js +106 -28
  6. package/dist/ast.d.ts +10 -0
  7. package/dist/bundled.mjs +2352 -2278
  8. package/dist/config/index.d.ts +1 -0
  9. package/dist/config/index.js +1 -0
  10. package/dist/config/schema.d.ts +8 -0
  11. package/dist/config/schema.js +19 -0
  12. package/dist/index.d.ts +1 -0
  13. package/dist/lsp/CodeLensProvider.js +3 -1
  14. package/dist/model/builder/buildModel.d.ts +8 -1
  15. package/dist/model/deployments-index.js +1 -1
  16. package/dist/model/fqn-index.d.ts +9 -6
  17. package/dist/model/fqn-index.js +24 -14
  18. package/dist/model/model-builder.d.ts +16 -6
  19. package/dist/model/model-builder.js +64 -50
  20. package/dist/model/model-locator.d.ts +11 -10
  21. package/dist/model/model-locator.js +32 -14
  22. package/dist/model/model-parser.d.ts +148 -148
  23. package/dist/model/model-parser.js +2 -2
  24. package/dist/model/parser/ModelParser.js +14 -2
  25. package/dist/model-change/ModelChanges.d.ts +1 -1
  26. package/dist/model-change/ModelChanges.js +2 -2
  27. package/dist/module.d.ts +9 -3
  28. package/dist/module.js +19 -5
  29. package/dist/protocol.d.ts +98 -16
  30. package/dist/protocol.js +21 -6
  31. package/dist/references/scope-provider.d.ts +24 -11
  32. package/dist/references/scope-provider.js +97 -68
  33. package/dist/shared/index.d.ts +0 -1
  34. package/dist/shared/index.js +0 -1
  35. package/dist/test/testServices.d.ts +15 -1
  36. package/dist/test/testServices.js +65 -17
  37. package/dist/utils/index.d.ts +3 -0
  38. package/dist/utils/index.js +3 -0
  39. package/dist/utils/projectId.d.ts +3 -0
  40. package/dist/utils/projectId.js +6 -0
  41. package/dist/validation/deployment-checks.js +5 -2
  42. package/dist/validation/element.js +2 -1
  43. package/dist/validation/specification.js +14 -7
  44. package/dist/validation/view.js +10 -8
  45. package/dist/views/index.d.ts +1 -1
  46. package/dist/views/index.js +1 -1
  47. package/dist/views/likec4-views.d.ts +17 -8
  48. package/dist/views/likec4-views.js +12 -11
  49. package/dist/workspace/AstNodeDescriptionProvider.d.ts +7 -0
  50. package/dist/workspace/AstNodeDescriptionProvider.js +18 -0
  51. package/dist/workspace/IndexManager.d.ts +10 -0
  52. package/dist/workspace/IndexManager.js +17 -0
  53. package/dist/workspace/LangiumDocuments.d.ts +14 -0
  54. package/dist/workspace/LangiumDocuments.js +31 -0
  55. package/dist/workspace/ProjectsManager.d.ts +48 -0
  56. package/dist/workspace/ProjectsManager.js +150 -0
  57. package/dist/{shared → workspace}/WorkspaceManager.d.ts +8 -2
  58. package/dist/{shared → workspace}/WorkspaceManager.js +22 -0
  59. package/dist/workspace/index.d.ts +5 -0
  60. package/dist/workspace/index.js +5 -0
  61. package/package.json +21 -12
@@ -0,0 +1,150 @@
1
+ import { BiMap, invariant, nonNullable } from "@likec4/core";
2
+ import { URI, WorkspaceCache } from "langium";
3
+ import { hasAtLeast, map, pipe, prop, sortBy } from "remeda";
4
+ import { hasProtocol, joinRelativeURL, parseFilename, withoutProtocol, withProtocol } from "ufo";
5
+ import { parseConfigJson } from "../config/index.js";
6
+ import { logger as mainLogger } from "../logger.js";
7
+ const logger = mainLogger.getChild("ProjectsManager");
8
+ export class ProjectsManager {
9
+ constructor(services) {
10
+ this.services = services;
11
+ logger.debug`created`;
12
+ }
13
+ /**
14
+ * The global project ID used for all documents
15
+ * that are not part of a specific project.
16
+ */
17
+ static DefaultProjectId = "default";
18
+ static ConfigFileNames = [
19
+ ".likec4rc",
20
+ ".likec4.config.json",
21
+ "likec4.config.json"
22
+ ];
23
+ /**
24
+ * The mapping between project config files and project IDs.
25
+ */
26
+ projectIdToFolder = new BiMap();
27
+ // The mapping between document URIs and their corresponding project IDs.
28
+ _mappingsToProject;
29
+ /**
30
+ * Registered projects.
31
+ * Sorted descending by the number of segments in the folder path.
32
+ * This ensures that the most specific project is used for a document.
33
+ */
34
+ _projects = [];
35
+ get defaultProjectId() {
36
+ if (this._projects.length > 1) {
37
+ return void 0;
38
+ }
39
+ return this._projects[0]?.id ?? ProjectsManager.DefaultProjectId;
40
+ }
41
+ get all() {
42
+ if (hasAtLeast(this._projects, 1)) {
43
+ return [
44
+ ...map(this._projects, prop("id")),
45
+ ProjectsManager.DefaultProjectId
46
+ ];
47
+ }
48
+ return [ProjectsManager.DefaultProjectId];
49
+ }
50
+ getProject(projectId) {
51
+ if (projectId === ProjectsManager.DefaultProjectId) {
52
+ const folder = this.services.workspace.WorkspaceManager.workspaceUri;
53
+ return {
54
+ folder,
55
+ config: {
56
+ name: ProjectsManager.DefaultProjectId
57
+ }
58
+ };
59
+ }
60
+ const project = nonNullable(this._projects.find(({ id }) => id === projectId), `Project "${projectId}" not found`);
61
+ return {
62
+ folder: URI.parse(project.folder),
63
+ config: project.config
64
+ };
65
+ }
66
+ ensureProjectId(projectId) {
67
+ if (projectId === ProjectsManager.DefaultProjectId) {
68
+ return projectId;
69
+ }
70
+ if (projectId) {
71
+ invariant(this.projectIdToFolder.has(projectId), `Project ID ${projectId} is not registered`);
72
+ return projectId;
73
+ }
74
+ return nonNullable(
75
+ this.defaultProjectId,
76
+ () => `Specify exact project, known: [${[...this.projectIdToFolder.keys()].join(", ")}]`
77
+ );
78
+ }
79
+ hasMultipleProjects() {
80
+ return this._projects.length > 1;
81
+ }
82
+ /**
83
+ * Checks if the provided file system entry is a valid project config file.
84
+ *
85
+ * @param entry The file system entry to check
86
+ * @returns {boolean} Returns true if the entry is a valid config file, false otherwise.
87
+ */
88
+ async loadConfigFile(entry) {
89
+ if (entry.isDirectory) {
90
+ return false;
91
+ }
92
+ const filename = parseFilename(entry.uri.fsPath, { strict: false });
93
+ if (!filename) {
94
+ return false;
95
+ }
96
+ if (ProjectsManager.ConfigFileNames.includes(filename)) {
97
+ await this.registerProject(entry.uri);
98
+ return true;
99
+ }
100
+ return false;
101
+ }
102
+ async registerProject(opts) {
103
+ if (URI.isUri(opts)) {
104
+ const configFile = opts;
105
+ const cfg = await this.services.workspace.FileSystemProvider.readFile(configFile);
106
+ const config2 = parseConfigJson(cfg);
107
+ const path = joinRelativeURL(configFile.path, "..");
108
+ const folderUri2 = configFile.with({ path });
109
+ return this.registerProject({ config: config2, folderUri: folderUri2 });
110
+ }
111
+ const { config, folderUri } = opts;
112
+ const id = config.name;
113
+ if (this._projects.some(({ id: existingId }) => existingId === id)) {
114
+ throw new Error(`Project ID ${id} already registered`);
115
+ }
116
+ let folder;
117
+ if (URI.isUri(folderUri)) {
118
+ folder = folderUri.toString();
119
+ } else {
120
+ folder = hasProtocol(folderUri) ? folderUri : withProtocol(folderUri, "file://");
121
+ }
122
+ this._projects = pipe(
123
+ [...this._projects, { folder, config, id }],
124
+ sortBy(
125
+ [({ folder: folder2 }) => withoutProtocol(folder2).split("/").length, "desc"]
126
+ )
127
+ );
128
+ this.projectIdToFolder.set(id, folder);
129
+ logger.debug`registered project ${id} folder: ${folder})`;
130
+ }
131
+ belongsTo(document) {
132
+ let documentUri;
133
+ if (typeof document === "string") {
134
+ documentUri = hasProtocol(document) ? document : withProtocol(document, "file://");
135
+ } else if (URI.isUri(document)) {
136
+ documentUri = document.toString();
137
+ } else {
138
+ documentUri = document.uri.toString();
139
+ }
140
+ return this.mappingsToProject.get(documentUri, () => this.getProjectId(documentUri));
141
+ }
142
+ getProjectId(documentUri) {
143
+ const project = this._projects.find(({ folder }) => documentUri.toString().startsWith(folder));
144
+ return project?.id ?? ProjectsManager.DefaultProjectId;
145
+ }
146
+ get mappingsToProject() {
147
+ this._mappingsToProject ??= new WorkspaceCache(this.services);
148
+ return this._mappingsToProject;
149
+ }
150
+ }
@@ -1,17 +1,23 @@
1
1
  import type { LangiumDocument } from 'langium';
2
2
  import { DefaultWorkspaceManager } from 'langium';
3
- import type { LangiumSharedServices } from 'langium/lsp';
4
3
  import type { WorkspaceFolder } from 'vscode-languageserver';
5
4
  import { URI } from 'vscode-uri';
5
+ import type { LikeC4SharedServices } from '../module';
6
6
  export declare class LikeC4WorkspaceManager extends DefaultWorkspaceManager {
7
7
  private documentFactory;
8
- constructor(services: LangiumSharedServices);
8
+ private projects;
9
+ constructor(services: LikeC4SharedServices);
9
10
  /**
10
11
  * Load all additional documents that shall be visible in the context of the given workspace
11
12
  * folders and add them to the collector. This can be used to include built-in libraries of
12
13
  * your language, which can be either loaded from provided files or constructed in memory.
13
14
  */
14
15
  protected loadAdditionalDocuments(folders: WorkspaceFolder[], collector: (document: LangiumDocument) => void): Promise<void>;
16
+ /**
17
+ * We override the default implementation to process project config files during the traversal.
18
+ * This is necessary to ensure that the project config files are loaded and processed correctly.
19
+ */
20
+ protected traverseFolder(workspaceFolder: WorkspaceFolder, folderPath: URI, fileExtensions: string[], collector: (document: LangiumDocument) => void): Promise<void>;
15
21
  workspace(): any;
16
22
  get workspaceUri(): URI;
17
23
  get workspaceURL(): URL;
@@ -4,9 +4,11 @@ import { URI } from "vscode-uri";
4
4
  import * as BuiltIn from "../likec4lib.js";
5
5
  export class LikeC4WorkspaceManager extends DefaultWorkspaceManager {
6
6
  documentFactory;
7
+ projects;
7
8
  constructor(services) {
8
9
  super(services);
9
10
  this.documentFactory = services.workspace.LangiumDocumentFactory;
11
+ this.projects = services.workspace.ProjectsManager;
10
12
  }
11
13
  /**
12
14
  * Load all additional documents that shall be visible in the context of the given workspace
@@ -17,6 +19,26 @@ export class LikeC4WorkspaceManager extends DefaultWorkspaceManager {
17
19
  collector(this.documentFactory.fromString(BuiltIn.Content, URI.parse(BuiltIn.Uri)));
18
20
  await super.loadAdditionalDocuments(folders, collector);
19
21
  }
22
+ /**
23
+ * We override the default implementation to process project config files during the traversal.
24
+ * This is necessary to ensure that the project config files are loaded and processed correctly.
25
+ */
26
+ async traverseFolder(workspaceFolder, folderPath, fileExtensions, collector) {
27
+ const content = await this.fileSystemProvider.readDirectory(folderPath);
28
+ await Promise.all(content.map(async (entry) => {
29
+ if (await this.projects.loadConfigFile(entry)) {
30
+ return;
31
+ }
32
+ if (this.includeEntry(workspaceFolder, entry, fileExtensions)) {
33
+ if (entry.isDirectory) {
34
+ await this.traverseFolder(workspaceFolder, entry.uri, fileExtensions, collector);
35
+ } else if (entry.isFile) {
36
+ const document = await this.langiumDocuments.getOrCreateDocument(entry.uri);
37
+ collector(document);
38
+ }
39
+ }
40
+ }));
41
+ }
20
42
  workspace() {
21
43
  if (this.folders && hasAtLeast(this.folders, 1)) {
22
44
  return this.folders[0];
@@ -0,0 +1,5 @@
1
+ export * from './AstNodeDescriptionProvider';
2
+ export * from './IndexManager';
3
+ export * from './LangiumDocuments';
4
+ export * from './ProjectsManager';
5
+ export * from './WorkspaceManager';
@@ -0,0 +1,5 @@
1
+ export * from "./AstNodeDescriptionProvider.js";
2
+ export * from "./IndexManager.js";
3
+ export * from "./LangiumDocuments.js";
4
+ export * from "./ProjectsManager.js";
5
+ export * from "./WorkspaceManager.js";
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.25.0",
4
+ "version": "1.26.0",
5
5
  "license": "MIT",
6
6
  "bugs": "https://github.com/likec4/likec4/issues",
7
7
  "homepage": "https://likec4.dev",
@@ -42,6 +42,14 @@
42
42
  "default": "./dist/browser.js"
43
43
  }
44
44
  },
45
+ "./config": {
46
+ "sources": "./src/config/index.ts",
47
+ "default": {
48
+ "types": "./dist/config/index.d.ts",
49
+ "import": "./dist/config/index.js",
50
+ "default": "./dist/config/index.js"
51
+ }
52
+ },
45
53
  "./likec4lib": {
46
54
  "sources": "./src/likec4lib.ts",
47
55
  "default": {
@@ -84,9 +92,9 @@
84
92
  "@hpcc-js/wasm-graphviz": "1.7.0",
85
93
  "@msgpack/msgpack": "^3.1.0",
86
94
  "@smithy/util-base64": "^4.0.0",
87
- "@types/node": "^20.17.17",
95
+ "@types/node": "^20.17.23",
88
96
  "@types/which": "^3.0.4",
89
- "@vitest/coverage-v8": "^3.0.6",
97
+ "@vitest/coverage-v8": "^3.0.8",
90
98
  "esm-env": "^1.2.2",
91
99
  "fast-equals": "^5.2.2",
92
100
  "fdir": "^6.4.3",
@@ -97,25 +105,26 @@
97
105
  "natural-compare-lite": "^1.4.0",
98
106
  "p-debounce": "^4.0.0",
99
107
  "pretty-ms": "^9.2.0",
100
- "remeda": "^2.20.2",
108
+ "remeda": "^2.21.0",
101
109
  "strip-indent": "^4.0.0",
102
110
  "tsx": "~4.19.3",
103
- "turbo": "^2.4.2",
111
+ "turbo": "^2.4.4",
104
112
  "type-fest": "4.34.1",
105
- "typescript": "5.7.3",
113
+ "typescript": "5.8.2",
106
114
  "ufo": "^1.5.4",
107
115
  "unbuild": "^3.3.1",
108
- "vitest": "^3.0.6",
116
+ "valibot": "^1.0.0-rc.3",
117
+ "vitest": "^3.0.8",
109
118
  "vscode-jsonrpc": "8.2.0",
110
119
  "vscode-languageserver": "9.0.1",
111
120
  "vscode-languageserver-types": "3.17.5",
112
121
  "vscode-uri": "3.1.0",
113
122
  "which": "^5.0.0",
114
- "@likec4/core": "1.25.0",
115
- "@likec4/layouts": "1.25.0",
116
- "@likec4/log": "1.25.0",
117
- "@likec4/tsconfig": "1.25.0",
118
- "@likec4/icons": "1.25.0"
123
+ "@likec4/core": "1.26.0",
124
+ "@likec4/icons": "1.26.0",
125
+ "@likec4/layouts": "1.26.0",
126
+ "@likec4/log": "1.26.0",
127
+ "@likec4/tsconfig": "1.26.0"
119
128
  },
120
129
  "scripts": {
121
130
  "typecheck": "tsc --noEmit",