skir 0.0.3 → 0.0.6

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 (60) hide show
  1. package/dist/casing.d.ts.map +1 -1
  2. package/dist/casing.js +4 -1
  3. package/dist/casing.js.map +1 -1
  4. package/dist/casing.test.js +35 -1
  5. package/dist/casing.test.js.map +1 -1
  6. package/dist/compatibility_checker.test.js +2 -2
  7. package/dist/compiler.js +23 -42
  8. package/dist/compiler.js.map +1 -1
  9. package/dist/config.d.ts +2 -2
  10. package/dist/config.d.ts.map +1 -1
  11. package/dist/config.js +5 -10
  12. package/dist/config.js.map +1 -1
  13. package/dist/config_parser.d.ts +25 -0
  14. package/dist/config_parser.d.ts.map +1 -0
  15. package/dist/config_parser.js +125 -0
  16. package/dist/config_parser.js.map +1 -0
  17. package/dist/config_parser.test.d.ts +2 -0
  18. package/dist/config_parser.test.d.ts.map +1 -0
  19. package/dist/config_parser.test.js +386 -0
  20. package/dist/config_parser.test.js.map +1 -0
  21. package/dist/doc_comment_parser.d.ts +3 -2
  22. package/dist/doc_comment_parser.d.ts.map +1 -1
  23. package/dist/doc_comment_parser.js +67 -52
  24. package/dist/doc_comment_parser.js.map +1 -1
  25. package/dist/doc_comment_parser.test.js +86 -154
  26. package/dist/doc_comment_parser.test.js.map +1 -1
  27. package/dist/error_renderer.d.ts +4 -0
  28. package/dist/error_renderer.d.ts.map +1 -1
  29. package/dist/error_renderer.js +21 -0
  30. package/dist/error_renderer.js.map +1 -1
  31. package/dist/module_set.d.ts.map +1 -1
  32. package/dist/module_set.js +29 -12
  33. package/dist/module_set.js.map +1 -1
  34. package/dist/module_set.test.js +318 -173
  35. package/dist/module_set.test.js.map +1 -1
  36. package/dist/parser.d.ts.map +1 -1
  37. package/dist/parser.js +10 -10
  38. package/dist/parser.js.map +1 -1
  39. package/dist/project_initializer.js +9 -1
  40. package/dist/project_initializer.js.map +1 -1
  41. package/dist/tokenizer.d.ts +7 -1
  42. package/dist/tokenizer.d.ts.map +1 -1
  43. package/dist/tokenizer.js +12 -0
  44. package/dist/tokenizer.js.map +1 -1
  45. package/package.json +10 -5
  46. package/src/casing.ts +6 -1
  47. package/src/compiler.ts +26 -40
  48. package/src/config.ts +10 -15
  49. package/src/config_parser.ts +169 -0
  50. package/src/doc_comment_parser.ts +76 -52
  51. package/src/error_renderer.ts +34 -0
  52. package/src/module_set.ts +31 -15
  53. package/src/parser.ts +16 -10
  54. package/src/project_initializer.ts +9 -1
  55. package/src/tokenizer.ts +20 -2
  56. package/dist/language_server.d.ts +0 -15
  57. package/dist/language_server.d.ts.map +0 -1
  58. package/dist/language_server.js +0 -248
  59. package/dist/language_server.js.map +0 -1
  60. package/src/language_server.ts +0 -301
@@ -1,301 +0,0 @@
1
- import type {
2
- Module,
3
- MutableModule,
4
- RecordKey,
5
- RecordLocation,
6
- Result,
7
- SkirError,
8
- } from "skir-internal";
9
- import * as yaml from "yaml";
10
- import { fromZodError } from "zod-validation-error";
11
- import { SkirConfig } from "./config.js";
12
- import { ModuleParser, ModuleSet } from "./module_set.js";
13
- import { parseModule } from "./parser.js";
14
- import { tokenizeModule } from "./tokenizer.js";
15
-
16
- export class LanguageServerModuleSet {
17
- constructor(private readonly rootPath: string) {}
18
-
19
- setFileContent(uri: string, content: string): void {
20
- this.deleteFile(uri);
21
- const fileType = getFileType(uri);
22
- switch (fileType) {
23
- case "skir.yml": {
24
- const workspace = this.parseSkirConfig(content, uri);
25
- if (workspace) {
26
- this.workspaces.set(uri, workspace);
27
- this.reassignModulesToWorkspaces();
28
- }
29
- break;
30
- }
31
- case "*.skir": {
32
- const moduleWorkspace = this.findModuleWorkspace(uri);
33
- const moduleBundle = this.parseSkirModule(content, uri);
34
- this.moduleBundles.set(uri, moduleBundle);
35
- if (moduleWorkspace) {
36
- Workspace.addModule(moduleBundle, moduleWorkspace);
37
- }
38
- break;
39
- }
40
- default: {
41
- const _: null = fileType;
42
- }
43
- }
44
- }
45
-
46
- deleteFile(uri: string): void {
47
- const fileType = getFileType(uri);
48
- switch (fileType) {
49
- case "skir.yml": {
50
- if (this.workspaces.delete(uri)) {
51
- this.reassignModulesToWorkspaces();
52
- }
53
- break;
54
- }
55
- case "*.skir": {
56
- const moduleBundle = this.moduleBundles.get(uri);
57
- if (moduleBundle) {
58
- Workspace.removeModule(moduleBundle);
59
- this.moduleBundles.delete(uri);
60
- }
61
- break;
62
- }
63
- default: {
64
- const _: null = fileType;
65
- }
66
- }
67
- }
68
-
69
- private reassignModulesToWorkspaces(): void {
70
- if (this.reassigneModulesTimeout) {
71
- // Already scheduled, do nothing.
72
- return;
73
- }
74
- this.reassigneModulesTimeout = setTimeout(() => {
75
- for (const [moduleUri, moduleBundle] of this.moduleBundles.entries()) {
76
- Workspace.removeModule(moduleBundle);
77
- const newWorkspace = this.findModuleWorkspace(moduleUri);
78
- if (newWorkspace) {
79
- Workspace.addModule(moduleBundle, newWorkspace);
80
- }
81
- }
82
- for (const workspace of this.workspaces.values()) {
83
- workspace.scheduleResolution();
84
- }
85
- this.reassigneModulesTimeout = undefined;
86
- });
87
- }
88
-
89
- private parseSkirConfig(content: string, uri: string): Workspace | null {
90
- let skirConfig: SkirConfig;
91
- {
92
- // `yaml.parse` fail with a helpful error message, no need to add context.
93
- const parseResult = SkirConfig.safeParse(yaml.parse(content));
94
- if (parseResult.success) {
95
- skirConfig = parseResult.data;
96
- } else {
97
- const validationError = fromZodError(parseResult.error);
98
- console.error(
99
- `Error parsing skir.yml at ${uri}:`,
100
- validationError.message,
101
- );
102
- return null;
103
- }
104
- }
105
-
106
- let rootUri = new URL(skirConfig.srcDir || ".", uri).href;
107
- if (!rootUri.endsWith("/")) {
108
- rootUri += "/";
109
- }
110
- return new Workspace(rootUri);
111
- }
112
-
113
- private parseSkirModule(content: string, uri: string): ModuleBundle {
114
- let astTree: Result<Module | null>;
115
- {
116
- const tokens = tokenizeModule(content, uri);
117
- if (tokens.errors.length !== 0) {
118
- astTree = {
119
- result: null,
120
- errors: tokens.errors,
121
- };
122
- } else {
123
- astTree = parseModule(tokens.result);
124
- }
125
- }
126
- return {
127
- astTree,
128
- errors: [],
129
- };
130
- }
131
-
132
- /** Finds the workspace which contains the given module URI. */
133
- private findModuleWorkspace(moduleUri: string): ModuleWorkspace | undefined {
134
- let match: Workspace | undefined;
135
- const leftIsBetter = (
136
- left: Workspace,
137
- right: Workspace | undefined,
138
- ): boolean => {
139
- if (right === undefined || left.rootUri.length < right.rootUri.length) {
140
- return true;
141
- }
142
- if (left.rootUri.length === right.rootUri.length) {
143
- // Completely arbitrary, just to have a consistent order.
144
- return left.rootUri < right.rootUri;
145
- }
146
- return false;
147
- };
148
- for (const workspace of this.workspaces.values()) {
149
- const { rootUri } = workspace;
150
- if (moduleUri.startsWith(rootUri) && leftIsBetter(workspace, match)) {
151
- match = workspace;
152
- }
153
- }
154
- if (!match) {
155
- return undefined;
156
- }
157
- return {
158
- workspace: match,
159
- modulePath: moduleUri.substring(match.rootUri.length),
160
- };
161
- }
162
-
163
- private reassigneModulesTimeout?: NodeJS.Timeout;
164
- private readonly moduleBundles = new Map<string, ModuleBundle>(); // key: file URI
165
- private readonly workspaces = new Map<string, Workspace>(); // key: file URI
166
- }
167
-
168
- function errorToDiagnostic(error: SkirError): Diagnostic {
169
- const { token, message, expected } = error;
170
- return {
171
- range: {
172
- start: token.position,
173
- end: token.position + token.text.length,
174
- },
175
- message: message ? message : `expected: ${expected}`,
176
- };
177
- }
178
-
179
- function getFileType(uri: string): "skir.yml" | "*.skir" | null {
180
- if (uri.endsWith("/skir.yml")) {
181
- return "skir.yml";
182
- } else if (uri.endsWith(".skir")) {
183
- return "*.skir";
184
- }
185
- return null;
186
- }
187
-
188
- interface Diagnostic {
189
- readonly range?: {
190
- readonly start: number;
191
- readonly end: number;
192
- };
193
- readonly message: string;
194
- }
195
-
196
- interface ModuleWorkspace {
197
- readonly workspace: Workspace;
198
- readonly modulePath: string;
199
- }
200
-
201
- interface ModuleBundle {
202
- readonly astTree: Result<Module | null>;
203
- moduleWorkspace?: ModuleWorkspace;
204
- errors: Diagnostic[];
205
- }
206
-
207
- class Workspace implements ModuleParser {
208
- constructor(readonly rootUri: string) {}
209
-
210
- private readonly mutableRecordMap = new Map<RecordKey, RecordLocation>();
211
- // key: module path
212
- private readonly modules = new Map<string, ModuleBundle>();
213
- private scheduledResolution?: {
214
- timeout: NodeJS.Timeout;
215
- promise: Promise<void>;
216
- callback: () => void;
217
- };
218
-
219
- static addModule(
220
- moduleBundle: ModuleBundle,
221
- moduleWorkspace: ModuleWorkspace,
222
- ): void {
223
- // If the module was already in a workspace, remove it from the old workspace.
224
- Workspace.removeModule(moduleBundle);
225
- const { workspace } = moduleWorkspace;
226
- moduleBundle.moduleWorkspace = moduleWorkspace;
227
- workspace.modules.set(moduleWorkspace.modulePath, moduleBundle);
228
- for (const record of moduleBundle.astTree.result?.records || []) {
229
- workspace.mutableRecordMap.set(record.record.key, record);
230
- }
231
- workspace.scheduleResolution();
232
- }
233
-
234
- static removeModule(moduleBundle: ModuleBundle): void {
235
- const { moduleWorkspace } = moduleBundle;
236
- if (!moduleWorkspace) {
237
- return;
238
- }
239
- const { workspace } = moduleWorkspace;
240
- workspace.modules.delete(moduleWorkspace.modulePath);
241
- for (const record of moduleBundle.astTree.result?.records || []) {
242
- workspace.mutableRecordMap.delete(record.record.key);
243
- }
244
- moduleBundle.moduleWorkspace = undefined;
245
- workspace.scheduleResolution();
246
- }
247
-
248
- parseModule(modulePath: string): Result<MutableModule | null> {
249
- const moduleBundle = this.modules.get(modulePath);
250
- if (!moduleBundle) {
251
- return {
252
- result: null,
253
- errors: [],
254
- };
255
- }
256
- return moduleBundle.astTree;
257
- }
258
-
259
- scheduleResolution(): void {
260
- if (this.scheduledResolution) {
261
- clearTimeout(this.scheduledResolution.timeout);
262
- }
263
- const delayMilliseconds = 500;
264
- const timeout = setTimeout(() => {
265
- this.scheduledResolution = undefined;
266
- this.resolve();
267
- }, delayMilliseconds);
268
- const scheduledResolution = {
269
- timeout,
270
- promise: new Promise<void>((resolve) => {
271
- scheduledResolution.callback = resolve;
272
- }),
273
- callback: (() => {
274
- throw new Error("callback not set");
275
- }) as () => void,
276
- };
277
- this.scheduledResolution = scheduledResolution;
278
- }
279
-
280
- get resolutionDone(): Promise<void> {
281
- if (this.scheduledResolution) {
282
- return this.scheduledResolution.promise;
283
- }
284
- return Promise.resolve();
285
- }
286
-
287
- /**
288
- * Synchronously performs type resolution (and validation).
289
- * Stores the errors in every module bundle.
290
- */
291
- private resolve(): void {
292
- const moduleSet = new ModuleSet(this);
293
- for (const [modulePath, moduleBundle] of this.modules.entries()) {
294
- const parseResult = moduleSet.parseAndResolve(modulePath);
295
- moduleBundle.errors = parseResult.errors.map(errorToDiagnostic);
296
- }
297
- if (this.scheduledResolution) {
298
- this.scheduledResolution.callback();
299
- }
300
- }
301
- }