skir-internal 0.0.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 (132) hide show
  1. package/README.md +2 -0
  2. package/dist/casing.d.ts +8 -0
  3. package/dist/casing.d.ts.map +1 -0
  4. package/dist/casing.js +43 -0
  5. package/dist/casing.js.map +1 -0
  6. package/dist/casing.test.d.ts.map +1 -0
  7. package/dist/casing.test.js +136 -0
  8. package/dist/casing.test.js.map +1 -0
  9. package/dist/command_line_parser.d.ts +33 -0
  10. package/dist/command_line_parser.d.ts.map +1 -0
  11. package/dist/command_line_parser.js +168 -0
  12. package/dist/command_line_parser.js.map +1 -0
  13. package/dist/command_line_parser.test.d.ts +2 -0
  14. package/dist/command_line_parser.test.d.ts.map +1 -0
  15. package/dist/command_line_parser.test.js +297 -0
  16. package/dist/command_line_parser.test.js.map +1 -0
  17. package/dist/compatibility_checker.d.ts +74 -0
  18. package/dist/compatibility_checker.d.ts.map +1 -0
  19. package/dist/compatibility_checker.js +341 -0
  20. package/dist/compatibility_checker.js.map +1 -0
  21. package/dist/compatibility_checker.test.d.ts +2 -0
  22. package/dist/compatibility_checker.test.d.ts.map +1 -0
  23. package/dist/compatibility_checker.test.js +582 -0
  24. package/dist/compatibility_checker.test.js.map +1 -0
  25. package/dist/compiler.d.ts.map +1 -0
  26. package/dist/compiler.js +375 -0
  27. package/dist/compiler.js.map +1 -0
  28. package/dist/config.d.ts +47 -0
  29. package/dist/config.d.ts.map +1 -0
  30. package/dist/config.js +23 -0
  31. package/dist/config.js.map +1 -0
  32. package/dist/definition_finder.d.ts +12 -0
  33. package/dist/definition_finder.d.ts.map +1 -0
  34. package/dist/definition_finder.js +180 -0
  35. package/dist/definition_finder.js.map +1 -0
  36. package/dist/definition_finder.test.d.ts +2 -0
  37. package/dist/definition_finder.test.d.ts.map +1 -0
  38. package/dist/definition_finder.test.js +164 -0
  39. package/dist/definition_finder.test.js.map +1 -0
  40. package/dist/doc_comment_parser.d.ts +3 -0
  41. package/dist/doc_comment_parser.d.ts.map +1 -0
  42. package/dist/doc_comment_parser.js +219 -0
  43. package/dist/doc_comment_parser.js.map +1 -0
  44. package/dist/doc_comment_parser.test.d.ts +2 -0
  45. package/dist/doc_comment_parser.test.d.ts.map +1 -0
  46. package/dist/doc_comment_parser.test.js +494 -0
  47. package/dist/doc_comment_parser.test.js.map +1 -0
  48. package/dist/encoding.d.ts +2 -0
  49. package/dist/encoding.d.ts.map +1 -0
  50. package/dist/encoding.js +38 -0
  51. package/dist/encoding.js.map +1 -0
  52. package/dist/encoding.test.d.ts +2 -0
  53. package/dist/encoding.test.d.ts.map +1 -0
  54. package/dist/encoding.test.js +23 -0
  55. package/dist/encoding.test.js.map +1 -0
  56. package/dist/error_renderer.d.ts +10 -0
  57. package/dist/error_renderer.d.ts.map +1 -0
  58. package/dist/error_renderer.js +266 -0
  59. package/dist/error_renderer.js.map +1 -0
  60. package/dist/formatter.d.ts +16 -0
  61. package/dist/formatter.d.ts.map +1 -0
  62. package/dist/formatter.js +220 -0
  63. package/dist/formatter.js.map +1 -0
  64. package/dist/formatter.test.d.ts +2 -0
  65. package/dist/formatter.test.d.ts.map +1 -0
  66. package/dist/formatter.test.js +390 -0
  67. package/dist/formatter.test.js.map +1 -0
  68. package/dist/formatter2.d.ts +3 -0
  69. package/dist/formatter2.d.ts.map +1 -0
  70. package/dist/formatter2.js +67 -0
  71. package/dist/formatter2.js.map +1 -0
  72. package/dist/index.d.ts +6 -0
  73. package/dist/index.d.ts.map +1 -0
  74. package/dist/index.js +5 -0
  75. package/dist/index.js.map +1 -0
  76. package/dist/index.test.d.ts.map +1 -0
  77. package/dist/index.test.js +14 -0
  78. package/dist/index.test.js.map +1 -0
  79. package/dist/io.d.ts.map +1 -0
  80. package/dist/io.js +22 -0
  81. package/dist/io.js.map +1 -0
  82. package/dist/language_server.d.ts +15 -0
  83. package/dist/language_server.d.ts.map +1 -0
  84. package/dist/language_server.js +248 -0
  85. package/dist/language_server.js.map +1 -0
  86. package/dist/literals.d.ts +13 -0
  87. package/dist/literals.d.ts.map +1 -0
  88. package/dist/literals.js +100 -0
  89. package/dist/literals.js.map +1 -0
  90. package/dist/literals.test.d.ts.map +1 -0
  91. package/dist/literals.test.js +149 -0
  92. package/dist/literals.test.js.map +1 -0
  93. package/dist/module_collector.d.ts +3 -0
  94. package/dist/module_collector.d.ts.map +1 -0
  95. package/dist/module_collector.js +22 -0
  96. package/dist/module_collector.js.map +1 -0
  97. package/dist/module_set.d.ts +40 -0
  98. package/dist/module_set.d.ts.map +1 -0
  99. package/dist/module_set.js +1178 -0
  100. package/dist/module_set.js.map +1 -0
  101. package/dist/module_set.test.d.ts.map +1 -0
  102. package/dist/module_set.test.js +1897 -0
  103. package/dist/module_set.test.js.map +1 -0
  104. package/dist/parser.d.ts +7 -0
  105. package/dist/parser.d.ts.map +1 -0
  106. package/dist/parser.js +1075 -0
  107. package/dist/parser.js.map +1 -0
  108. package/dist/parser.test.d.ts.map +1 -0
  109. package/dist/parser.test.js +1594 -0
  110. package/dist/parser.test.js.map +1 -0
  111. package/dist/project_initializer.d.ts +2 -0
  112. package/dist/project_initializer.d.ts.map +1 -0
  113. package/dist/project_initializer.js +30 -0
  114. package/dist/project_initializer.js.map +1 -0
  115. package/dist/snapshotter.d.ts +9 -0
  116. package/dist/snapshotter.d.ts.map +1 -0
  117. package/dist/snapshotter.js +144 -0
  118. package/dist/snapshotter.js.map +1 -0
  119. package/dist/tokenizer.d.ts +10 -0
  120. package/dist/tokenizer.d.ts.map +1 -0
  121. package/dist/tokenizer.js +198 -0
  122. package/dist/tokenizer.js.map +1 -0
  123. package/dist/tokenizer.test.d.ts.map +1 -0
  124. package/dist/tokenizer.test.js +441 -0
  125. package/dist/tokenizer.test.js.map +1 -0
  126. package/dist/types.d.ts +439 -0
  127. package/dist/types.d.ts.map +1 -0
  128. package/dist/types.js +2 -0
  129. package/dist/types.js.map +1 -0
  130. package/package.json +58 -0
  131. package/src/casing.ts +53 -0
  132. package/src/types.ts +585 -0
@@ -0,0 +1,1178 @@
1
+ import * as paths from "path";
2
+ import { isStringLiteral, literalValueToDenseJson, literalValueToIdentity, unquoteAndUnescape, valueHasPrimitiveType, } from "./literals.js";
3
+ import { parseModule } from "./parser.js";
4
+ import { tokenizeModule } from "./tokenizer.js";
5
+ export class ModuleSet {
6
+ static create(fileReader, rootPath) {
7
+ return new ModuleSet(new DefaultModuleParser(fileReader, rootPath));
8
+ }
9
+ static fromMap(map) {
10
+ const result = new ModuleSet(new MapBasedModuleParser(map));
11
+ for (const modulePath of map.keys()) {
12
+ result.parseAndResolve(modulePath);
13
+ }
14
+ return result;
15
+ }
16
+ constructor(moduleParser) {
17
+ this.moduleParser = moduleParser;
18
+ this.modules = new Map();
19
+ this.mutableRecordMap = new Map();
20
+ this.mutableResolvedModules = [];
21
+ this.numberToRecord = new Map();
22
+ this.numberToMethod = new Map();
23
+ this.mutableErrors = [];
24
+ }
25
+ parseAndResolve(modulePath, inProgressSet) {
26
+ const inMap = this.modules.get(modulePath);
27
+ if (inMap !== undefined) {
28
+ return inMap;
29
+ }
30
+ const result = this.doParseAndResolve(modulePath, inProgressSet || new Set());
31
+ this.modules.set(modulePath, result);
32
+ this.mutableErrors.push(...result.errors);
33
+ return result;
34
+ }
35
+ /** Called by `parseAndResolve` when the module is not in the map already. */
36
+ doParseAndResolve(modulePath, inProgressSet) {
37
+ const errors = [];
38
+ let module;
39
+ {
40
+ const parseResult = this.moduleParser.parseModule(modulePath);
41
+ if (parseResult.result === null) {
42
+ return parseResult;
43
+ }
44
+ errors.push(...parseResult.errors);
45
+ module = parseResult.result;
46
+ }
47
+ // Process all imports.
48
+ const pathToImports = new Map();
49
+ for (const declaration of module.declarations) {
50
+ if (declaration.kind !== "import" &&
51
+ declaration.kind !== "import-alias") {
52
+ continue;
53
+ }
54
+ const otherModulePath = resolveModulePath(declaration.modulePath, modulePath, errors);
55
+ declaration.resolvedModulePath = otherModulePath;
56
+ if (otherModulePath === undefined) {
57
+ // An error was already registered.
58
+ continue;
59
+ }
60
+ let imports = pathToImports.get(otherModulePath);
61
+ if (!imports) {
62
+ imports = [];
63
+ pathToImports.set(otherModulePath, imports);
64
+ }
65
+ imports.push(declaration);
66
+ // Add the imported module to the module set.
67
+ const circularDependencyMessage = "Circular dependency between modules";
68
+ if (inProgressSet.has(modulePath)) {
69
+ errors.push({
70
+ token: declaration.modulePath,
71
+ message: circularDependencyMessage,
72
+ });
73
+ continue;
74
+ }
75
+ inProgressSet.add(modulePath);
76
+ const otherModule = this.parseAndResolve(otherModulePath, inProgressSet);
77
+ inProgressSet.delete(modulePath);
78
+ if (otherModule.result === null) {
79
+ errors.push({
80
+ token: declaration.modulePath,
81
+ message: "Module not found",
82
+ });
83
+ }
84
+ else if (otherModule.errors.length !== 0) {
85
+ const hasCircularDependency = otherModule.errors.some((e) => e.message === circularDependencyMessage);
86
+ if (hasCircularDependency) {
87
+ errors.push({
88
+ token: declaration.modulePath,
89
+ message: circularDependencyMessage,
90
+ });
91
+ }
92
+ else {
93
+ errors.push({
94
+ token: declaration.modulePath,
95
+ message: "Imported module has errors",
96
+ errorIsInOtherModule: true,
97
+ });
98
+ }
99
+ }
100
+ else if (declaration.kind === "import") {
101
+ // Make sure that the symbols we are importing exist in the imported
102
+ // module and are not imported symbols themselves.
103
+ for (const importedName of declaration.importedNames) {
104
+ const importedDeclaration = otherModule.result.nameToDeclaration[importedName.text];
105
+ if (importedDeclaration === undefined) {
106
+ errors.push({
107
+ token: importedName,
108
+ message: "Not found",
109
+ });
110
+ }
111
+ else if (importedDeclaration.kind === "import") {
112
+ errors.push({
113
+ token: importedName,
114
+ message: "Cannot reimport imported record",
115
+ });
116
+ }
117
+ else if (importedDeclaration.kind !== "record") {
118
+ errors.push({
119
+ token: importedName,
120
+ message: "Not a record",
121
+ });
122
+ }
123
+ }
124
+ }
125
+ }
126
+ const pathToImportedNames = module.pathToImportedNames;
127
+ for (const [path, imports] of pathToImports.entries()) {
128
+ const importsNoAlias = imports.filter((i) => i.kind === "import");
129
+ const importsWithAlias = imports.filter((i) => i.kind === "import-alias");
130
+ if (importsNoAlias.length && importsWithAlias.length) {
131
+ for (const importNoAlias of importsNoAlias) {
132
+ errors.push({
133
+ token: importNoAlias.modulePath,
134
+ message: "Module already imported with an alias",
135
+ });
136
+ }
137
+ continue;
138
+ }
139
+ if (importsWithAlias.length >= 2) {
140
+ for (const importWithAlias of importsWithAlias.slice(1)) {
141
+ errors.push({
142
+ token: importWithAlias.modulePath,
143
+ message: "Module already imported with a different alias",
144
+ });
145
+ }
146
+ continue;
147
+ }
148
+ if (importsNoAlias.length) {
149
+ const names = new Set();
150
+ for (const importNoAlias of importsNoAlias) {
151
+ for (const importedName of importNoAlias.importedNames) {
152
+ names.add(importedName.text);
153
+ }
154
+ }
155
+ pathToImportedNames[path] = {
156
+ kind: "some",
157
+ names: names,
158
+ };
159
+ }
160
+ else {
161
+ const alias = importsWithAlias[0].name.text;
162
+ pathToImportedNames[path] = {
163
+ kind: "all",
164
+ alias: alias,
165
+ };
166
+ }
167
+ }
168
+ const result = {
169
+ result: module,
170
+ errors: errors,
171
+ };
172
+ if (errors.length) {
173
+ return result;
174
+ }
175
+ this.mutableResolvedModules.push(module);
176
+ // We can't merge these 3 loops into a single one, each operation must run
177
+ // after the last operation ran on the whole map.
178
+ // Loop 1: merge the module records map into the cross-module record map.
179
+ for (const record of module.records) {
180
+ const { key } = record.record;
181
+ this.mutableRecordMap.set(key, record);
182
+ const { recordNumber } = record.record;
183
+ if (recordNumber != null) {
184
+ const existing = this.numberToRecord.get(recordNumber);
185
+ if (existing === undefined) {
186
+ this.numberToRecord.set(recordNumber, key);
187
+ }
188
+ else {
189
+ const otherRecord = this.recordMap.get(existing);
190
+ const otherRecordName = otherRecord.record.name.text;
191
+ const otherModulePath = otherRecord.modulePath;
192
+ errors.push({
193
+ token: record.record.name,
194
+ message: `Same number as ${otherRecordName} in ${otherModulePath}`,
195
+ });
196
+ }
197
+ }
198
+ }
199
+ // Loop 2: resolve every field type of every record in the module.
200
+ // Store the result in the Field object.
201
+ const usedImports = new Set();
202
+ const typeResolver = new TypeResolver(module, this.modules, usedImports, errors);
203
+ for (const record of module.records) {
204
+ this.storeResolvedFieldTypes(record, typeResolver);
205
+ }
206
+ // Loop 3: once all the types of record fields have been resolved.
207
+ for (const moduleRecord of module.records) {
208
+ const { record } = moduleRecord;
209
+ // For every field, determine if the field is recursive, i.e. the field
210
+ // type depends on the record where the field is defined.
211
+ // Store the result in the Field object.
212
+ this.storeFieldRecursivity(record);
213
+ // Verify that the `key` field of every array type is valid.
214
+ for (const field of record.fields) {
215
+ const { type } = field;
216
+ if (type) {
217
+ this.validateArrayKeys(type, errors);
218
+ }
219
+ // Resolve the references in the doc comments of the field.
220
+ this.resolveDocReferences({
221
+ kind: "field",
222
+ field: field,
223
+ record: record,
224
+ }, module, errors);
225
+ }
226
+ // Resolve the references in the doc comments of the record.
227
+ this.resolveDocReferences(record, module, errors);
228
+ }
229
+ // Resolve every request/response type of every method in the module.
230
+ // Store the result in the Method object.
231
+ for (const method of module.methods) {
232
+ {
233
+ const request = method.unresolvedRequestType;
234
+ const requestType = typeResolver.resolve(request, "top-level");
235
+ method.requestType = requestType;
236
+ if (requestType) {
237
+ this.validateArrayKeys(requestType, errors);
238
+ }
239
+ }
240
+ {
241
+ const response = method.unresolvedResponseType;
242
+ const responseType = typeResolver.resolve(response, "top-level");
243
+ method.responseType = responseType;
244
+ if (responseType) {
245
+ this.validateArrayKeys(responseType, errors);
246
+ }
247
+ }
248
+ const { number } = method;
249
+ const existing = this.numberToMethod.get(number);
250
+ if (existing === undefined) {
251
+ this.numberToMethod.set(number, method);
252
+ }
253
+ else {
254
+ const otherMethodName = existing.name.text;
255
+ const otherModulePath = existing.name.line.modulePath;
256
+ errors.push({
257
+ token: method.name,
258
+ message: `Same number as ${otherMethodName} in ${otherModulePath}`,
259
+ });
260
+ }
261
+ // Resolve the references in the doc comments of the method.
262
+ this.resolveDocReferences(method, module, errors);
263
+ }
264
+ // Resolve every constant type. Store the result in the constant object.
265
+ for (const constant of module.constants) {
266
+ const { unresolvedType } = constant;
267
+ const type = typeResolver.resolve(unresolvedType, "top-level");
268
+ constant.type = type;
269
+ if (type) {
270
+ this.validateArrayKeys(type, errors);
271
+ constant.valueAsDenseJson = //
272
+ this.valueToDenseJson(constant.value, type, errors);
273
+ }
274
+ // Resolve the references in the doc comments of the constant.
275
+ this.resolveDocReferences(constant, module, errors);
276
+ }
277
+ ensureAllImportsAreUsed(module, usedImports, errors);
278
+ return result;
279
+ }
280
+ storeResolvedFieldTypes(record, typeResolver) {
281
+ for (const field of record.record.fields) {
282
+ if (field.unresolvedType === undefined) {
283
+ // A constant enum field.
284
+ continue;
285
+ }
286
+ field.type = typeResolver.resolve(field.unresolvedType, record);
287
+ }
288
+ }
289
+ storeFieldRecursivity(record) {
290
+ for (const field of record.fields) {
291
+ if (!field.type)
292
+ continue;
293
+ const modes = record.recordType === "struct" ? ["hard", "soft"] : ["soft"];
294
+ for (const mode of modes) {
295
+ const deps = new Set();
296
+ this.collectTypeDeps(field.type, mode, deps);
297
+ if (deps.has(record.key)) {
298
+ field.isRecursive = mode;
299
+ break;
300
+ }
301
+ }
302
+ }
303
+ }
304
+ collectTypeDeps(input, mode, out) {
305
+ switch (input.kind) {
306
+ case "record": {
307
+ const { key } = input;
308
+ if (out.has(key))
309
+ return;
310
+ out.add(key);
311
+ // Recursively add deps of all fields of the record.
312
+ const record = this.recordMap.get(key).record;
313
+ if (mode === "hard" && record.recordType === "enum") {
314
+ return;
315
+ }
316
+ for (const field of record.fields) {
317
+ if (field.type === undefined)
318
+ continue;
319
+ this.collectTypeDeps(field.type, mode, out);
320
+ }
321
+ break;
322
+ }
323
+ case "array": {
324
+ if (mode === "hard")
325
+ break;
326
+ this.collectTypeDeps(input.item, mode, out);
327
+ break;
328
+ }
329
+ case "optional": {
330
+ if (mode === "hard")
331
+ break;
332
+ this.collectTypeDeps(input.other, mode, out);
333
+ break;
334
+ }
335
+ }
336
+ }
337
+ /**
338
+ * Verifies that the `key` field of every array type found in `topLevelType`
339
+ * is valid. Populates the `keyType` field of every field path.
340
+ */
341
+ validateArrayKeys(topLevelType, errors) {
342
+ const validate = (type) => {
343
+ const { key, item } = type;
344
+ if (!key) {
345
+ return;
346
+ }
347
+ const { path } = key;
348
+ // Iterate the fields in the sequence.
349
+ let currentType = item;
350
+ let enumRef;
351
+ for (let i = 0; i < path.length; ++i) {
352
+ const pathItem = path[i];
353
+ const fieldName = pathItem.name;
354
+ if (currentType.kind !== "record") {
355
+ if (i === 0) {
356
+ errors.push({
357
+ token: key.pipeToken,
358
+ message: "Item must have struct type",
359
+ });
360
+ }
361
+ else {
362
+ const previousFieldName = path[i - 1].name;
363
+ errors.push({
364
+ token: previousFieldName,
365
+ message: "Must have struct type",
366
+ });
367
+ }
368
+ return;
369
+ }
370
+ const record = this.recordMap.get(currentType.key).record;
371
+ if (record.recordType === "struct") {
372
+ const field = record.nameToDeclaration[fieldName.text];
373
+ if (!field || field.kind !== "field") {
374
+ errors.push({
375
+ token: fieldName,
376
+ message: `Field not found in struct ${record.name.text}`,
377
+ });
378
+ return undefined;
379
+ }
380
+ pathItem.declaration = field;
381
+ if (!field.type) {
382
+ // An error was already registered.
383
+ return;
384
+ }
385
+ currentType = field.type;
386
+ }
387
+ else {
388
+ // An enum.
389
+ if (fieldName.text !== "kind") {
390
+ errors.push({
391
+ token: fieldName,
392
+ expected: "'kind'",
393
+ });
394
+ return undefined;
395
+ }
396
+ enumRef = currentType;
397
+ currentType = {
398
+ kind: "primitive",
399
+ primitive: "string",
400
+ };
401
+ }
402
+ }
403
+ if (currentType.kind !== "primitive") {
404
+ errors.push({
405
+ token: path.at(-1).name,
406
+ message: "Does not have primitive type",
407
+ });
408
+ return;
409
+ }
410
+ // If the last field name of the `kind` field of an enum, we store a
411
+ // reference to the enum in the `keyType` field of the array type.
412
+ key.keyType = enumRef || currentType;
413
+ };
414
+ const traverseType = (type) => {
415
+ switch (type.kind) {
416
+ case "array":
417
+ validate(type);
418
+ return traverseType(type.item);
419
+ case "optional":
420
+ return traverseType(type.other);
421
+ }
422
+ };
423
+ traverseType(topLevelType);
424
+ }
425
+ valueToDenseJson(value, expectedType, errors) {
426
+ switch (expectedType.kind) {
427
+ case "optional": {
428
+ if (value.kind === "literal" && value.token.text === "null") {
429
+ value.type = { kind: "null" };
430
+ return null;
431
+ }
432
+ return this.valueToDenseJson(value, expectedType.other, errors);
433
+ }
434
+ case "array": {
435
+ if (value.kind !== "array") {
436
+ errors.push({
437
+ token: value.token,
438
+ expected: "array",
439
+ });
440
+ return undefined;
441
+ }
442
+ const json = [];
443
+ let allGood = true;
444
+ for (const item of value.items) {
445
+ const itemJson = //
446
+ this.valueToDenseJson(item, expectedType.item, errors);
447
+ if (itemJson !== undefined) {
448
+ json.push(itemJson);
449
+ }
450
+ else {
451
+ // Even if we could return now, better to verify the type of the
452
+ // other items.
453
+ allGood = false;
454
+ }
455
+ }
456
+ if (!allGood) {
457
+ return undefined;
458
+ }
459
+ const { key } = expectedType;
460
+ value.key = key;
461
+ if (key) {
462
+ validateKeyedItems(value.items, key, errors);
463
+ }
464
+ return json;
465
+ }
466
+ case "record": {
467
+ const record = this.recordMap.get(expectedType.key);
468
+ if (!record) {
469
+ // An error was already registered.
470
+ return undefined;
471
+ }
472
+ return record.record.recordType === "struct"
473
+ ? this.structValueToDenseJson(value, record.record, errors)
474
+ : this.enumValueToDenseJson(value, record.record, errors);
475
+ }
476
+ case "primitive": {
477
+ const { token } = value;
478
+ const { primitive } = expectedType;
479
+ if (value.kind !== "literal" ||
480
+ !valueHasPrimitiveType(token.text, primitive)) {
481
+ errors.push({
482
+ token: value.token,
483
+ expected: primitive,
484
+ });
485
+ return undefined;
486
+ }
487
+ value.type = expectedType;
488
+ return literalValueToDenseJson(token.text, expectedType.primitive);
489
+ }
490
+ }
491
+ }
492
+ structValueToDenseJson(value, expectedStruct, errors) {
493
+ const { token } = value;
494
+ if (value.kind !== "object") {
495
+ errors.push({
496
+ token: token,
497
+ expected: "object",
498
+ });
499
+ return undefined;
500
+ }
501
+ const json = Array(expectedStruct.numSlotsInclRemovedNumbers).fill(0);
502
+ let allGood = true;
503
+ for (const [fieldName, fieldEntry] of Object.entries(value.entries)) {
504
+ const field = expectedStruct.nameToDeclaration[fieldName];
505
+ if (!field || field.kind !== "field") {
506
+ errors.push({
507
+ token: fieldEntry.name,
508
+ message: `Field not found in struct ${expectedStruct.name.text}`,
509
+ });
510
+ allGood = false;
511
+ continue;
512
+ }
513
+ }
514
+ let arrayLen = 0;
515
+ for (const field of expectedStruct.fields) {
516
+ const { type } = field;
517
+ if (!type) {
518
+ allGood = false;
519
+ continue;
520
+ }
521
+ const fieldEntry = value.entries[field.name.text];
522
+ let valueJson;
523
+ if (fieldEntry) {
524
+ valueJson = this.valueToDenseJson(fieldEntry.value, type, errors);
525
+ }
526
+ else {
527
+ // Unless the object is declared partial, all fields are required.
528
+ if (value.partial) {
529
+ valueJson = this.getDefaultJson(type);
530
+ }
531
+ else {
532
+ errors.push({
533
+ token: token,
534
+ message: `Missing entry: ${field.name.text}`,
535
+ });
536
+ }
537
+ }
538
+ if (valueJson === undefined) {
539
+ allGood = false;
540
+ continue;
541
+ }
542
+ json[field.number] = valueJson;
543
+ const hasDefaultValue = type.kind === "optional"
544
+ ? valueJson === null
545
+ : !valueJson ||
546
+ (Array.isArray(valueJson) && !valueJson.length) ||
547
+ (type.kind === "primitive" &&
548
+ (type.primitive === "int64" || type.primitive === "uint64") &&
549
+ valueJson === "0");
550
+ if (!hasDefaultValue) {
551
+ arrayLen = Math.max(arrayLen, field.number + 1);
552
+ }
553
+ }
554
+ if (!allGood) {
555
+ return undefined;
556
+ }
557
+ value.type = expectedStruct.key;
558
+ return json.slice(0, arrayLen);
559
+ }
560
+ enumValueToDenseJson(value, expectedEnum, errors) {
561
+ const { token } = value;
562
+ if (value.kind === "literal" && isStringLiteral(token.text)) {
563
+ // The value is a string.
564
+ // It must match the name of one of the constants defined in the enum.
565
+ const fieldName = unquoteAndUnescape(token.text);
566
+ if (fieldName === "?") {
567
+ // Present on every enum.
568
+ return 0;
569
+ }
570
+ const field = expectedEnum.nameToDeclaration[fieldName];
571
+ if (!field || field.kind !== "field") {
572
+ errors.push({
573
+ token: token,
574
+ message: `Field not found in enum ${expectedEnum.name.text}`,
575
+ });
576
+ return undefined;
577
+ }
578
+ if (field.type) {
579
+ errors.push({
580
+ token: token,
581
+ message: "Refers to a wrapper field",
582
+ });
583
+ return undefined;
584
+ }
585
+ value.type = {
586
+ kind: "enum",
587
+ key: expectedEnum.key,
588
+ };
589
+ return field.number;
590
+ }
591
+ else if (value.kind === "object") {
592
+ // The value is an object. It must have exactly two entries:
593
+ // · 'kind' must match the name of one of the wrapper fields defined in
594
+ // the enum
595
+ // · 'value' must match the type of the wrapper field
596
+ const entries = { ...value.entries };
597
+ const kindEntry = entries.kind;
598
+ if (!kindEntry) {
599
+ errors.push({
600
+ token: token,
601
+ message: "Missing entry: kind",
602
+ });
603
+ return undefined;
604
+ }
605
+ delete entries.kind;
606
+ const kindValueToken = kindEntry.value.token;
607
+ if (kindEntry.value.kind !== "literal" ||
608
+ !isStringLiteral(kindValueToken.text)) {
609
+ errors.push({
610
+ token: kindValueToken,
611
+ expected: "string",
612
+ });
613
+ return undefined;
614
+ }
615
+ const fieldName = unquoteAndUnescape(kindValueToken.text);
616
+ const field = expectedEnum.nameToDeclaration[fieldName];
617
+ if (!field || field.kind !== "field") {
618
+ errors.push({
619
+ token: kindValueToken,
620
+ message: `Field not found in enum ${expectedEnum.name.text}`,
621
+ });
622
+ return undefined;
623
+ }
624
+ if (!field.type) {
625
+ errors.push({
626
+ token: kindValueToken,
627
+ message: "Refers to a constant field",
628
+ });
629
+ return undefined;
630
+ }
631
+ const enumValue = entries.value;
632
+ if (!enumValue) {
633
+ errors.push({
634
+ token: token,
635
+ message: "Missing entry: value",
636
+ });
637
+ return undefined;
638
+ }
639
+ delete entries.value;
640
+ const valueJson = //
641
+ this.valueToDenseJson(enumValue.value, field.type, errors);
642
+ if (valueJson === undefined) {
643
+ return undefined;
644
+ }
645
+ const extraEntries = Object.values(entries);
646
+ if (extraEntries.length !== 0) {
647
+ const extraEntry = extraEntries[0];
648
+ errors.push({
649
+ token: extraEntry.name,
650
+ message: "Extraneous entry",
651
+ });
652
+ return undefined;
653
+ }
654
+ value.type = expectedEnum.key;
655
+ // Return an array of length 2.
656
+ return [field.number, valueJson];
657
+ }
658
+ else {
659
+ // The value is neither a string nor an object. It can't be of enum type.
660
+ errors.push({
661
+ token: token,
662
+ expected: "string or object",
663
+ });
664
+ return undefined;
665
+ }
666
+ }
667
+ getDefaultJson(type) {
668
+ switch (type.kind) {
669
+ case "primitive": {
670
+ switch (type.primitive) {
671
+ case "bool":
672
+ case "int32":
673
+ case "int64":
674
+ case "uint64":
675
+ case "float32":
676
+ case "float64":
677
+ case "timestamp":
678
+ return 0;
679
+ case "string":
680
+ case "bytes":
681
+ return "";
682
+ default: {
683
+ const _ = type.primitive;
684
+ throw new TypeError(_);
685
+ }
686
+ }
687
+ }
688
+ case "array":
689
+ return [];
690
+ case "optional":
691
+ return null;
692
+ case "record": {
693
+ const record = this.recordMap.get(type.key);
694
+ switch (record.record.recordType) {
695
+ case "struct":
696
+ return [];
697
+ case "enum":
698
+ return 0;
699
+ }
700
+ }
701
+ }
702
+ }
703
+ /** Resolve the references in the doc comments of the given declaration. */
704
+ resolveDocReferences(documentee, module, errors) {
705
+ const doc = documentee.kind === "field" ? documentee.field.doc : documentee.doc;
706
+ const docReferences = doc.pieces.filter((p) => p.kind === "reference");
707
+ if (docReferences.length <= 0) {
708
+ return;
709
+ }
710
+ // Try to resolve a reference by looking it up in the given scope.
711
+ // Returns true if resolved, false otherwise.
712
+ const tryResolveReference = (ref, nameChain, scope) => {
713
+ if (ref.absolute && scope !== module) {
714
+ return false;
715
+ }
716
+ const firstName = nameChain[0];
717
+ const match = scope.nameToDeclaration[firstName.text];
718
+ if (!match) {
719
+ return false;
720
+ }
721
+ if (nameChain.length === 1) {
722
+ // Single name: must refer to a declaration.
723
+ switch (match.kind) {
724
+ case "constant":
725
+ case "method":
726
+ case "record": {
727
+ ref.referee = match;
728
+ return true;
729
+ }
730
+ case "field": {
731
+ if (scope.kind !== "record") {
732
+ throw new TypeError(scope.kind);
733
+ }
734
+ ref.referee = {
735
+ kind: "field",
736
+ field: match,
737
+ record: scope,
738
+ };
739
+ return true;
740
+ }
741
+ case "import":
742
+ case "import-alias":
743
+ case "removed":
744
+ return false;
745
+ }
746
+ }
747
+ else {
748
+ // Multi-part name: first part must be a naming scope.
749
+ switch (match.kind) {
750
+ case "record":
751
+ return tryResolveReference(ref, nameChain.slice(1), match);
752
+ case "import":
753
+ case "import-alias": {
754
+ if (scope !== module) {
755
+ // Cannot refer to other module's imports.
756
+ return false;
757
+ }
758
+ const { resolvedModulePath } = match;
759
+ if (!resolvedModulePath) {
760
+ return false;
761
+ }
762
+ const importedModule = this.modules.get(resolvedModulePath);
763
+ if (!importedModule?.result) {
764
+ return false;
765
+ }
766
+ const newNameChain = nameChain.slice(match.kind === "import" ? 0 : 1);
767
+ return tryResolveReference(ref, newNameChain, importedModule.result);
768
+ }
769
+ case "constant":
770
+ case "field":
771
+ case "method":
772
+ case "removed":
773
+ return false;
774
+ }
775
+ }
776
+ };
777
+ const { recordMap } = this;
778
+ // Build list of naming scopes to search, in order of priority.
779
+ const scopes = [];
780
+ const pushRecordAncestorsToScopes = (record) => {
781
+ const { key } = record;
782
+ const location = recordMap.get(key);
783
+ const ancestors = [...location.recordAncestors].reverse();
784
+ for (const ancestor of ancestors) {
785
+ scopes.push(ancestor);
786
+ }
787
+ };
788
+ const pushTypeToScopes = (type) => {
789
+ if (type) {
790
+ const recordKey = tryFindRecordForType(type);
791
+ if (recordKey) {
792
+ const { record } = recordMap.get(recordKey);
793
+ scopes.push(record);
794
+ }
795
+ }
796
+ };
797
+ switch (documentee.kind) {
798
+ case "constant": {
799
+ scopes.push(module);
800
+ pushTypeToScopes(documentee.type);
801
+ break;
802
+ }
803
+ case "field": {
804
+ const { field, record } = documentee;
805
+ pushRecordAncestorsToScopes(record);
806
+ scopes.push(module);
807
+ pushTypeToScopes(field.type);
808
+ break;
809
+ }
810
+ case "method": {
811
+ scopes.push(module);
812
+ pushTypeToScopes(documentee.requestType);
813
+ pushTypeToScopes(documentee.responseType);
814
+ break;
815
+ }
816
+ case "record": {
817
+ pushRecordAncestorsToScopes(documentee);
818
+ scopes.push(module);
819
+ break;
820
+ }
821
+ }
822
+ // Resolve each reference by searching through scopes in priority order.
823
+ for (const reference of docReferences) {
824
+ const { nameChain } = reference;
825
+ if (nameChain.length <= 0) {
826
+ continue;
827
+ }
828
+ let resolved = false;
829
+ for (const scope of scopes) {
830
+ if (tryResolveReference(reference, nameChain, scope)) {
831
+ resolved = true;
832
+ break;
833
+ }
834
+ }
835
+ if (!resolved) {
836
+ errors.push({
837
+ token: reference.referenceRange,
838
+ message: "Cannot resolve reference",
839
+ });
840
+ }
841
+ }
842
+ }
843
+ get recordMap() {
844
+ return this.mutableRecordMap;
845
+ }
846
+ get resolvedModules() {
847
+ return this.mutableResolvedModules;
848
+ }
849
+ findRecordByNumber(recordNumber) {
850
+ const recordKey = this.numberToRecord.get(recordNumber);
851
+ if (recordKey === undefined) {
852
+ return undefined;
853
+ }
854
+ return this.recordMap.get(recordKey);
855
+ }
856
+ findMethodByNumber(methodNumber) {
857
+ return this.numberToMethod.get(methodNumber);
858
+ }
859
+ get errors() {
860
+ return this.mutableErrors;
861
+ }
862
+ }
863
+ /**
864
+ * If the array type is keyed, the array value must satisfy two conditions.
865
+ * First: the key field of every item must be set.
866
+ * Second: not two items can have the same key.
867
+ */
868
+ function validateKeyedItems(items, fieldPath, errors) {
869
+ const { keyType, path } = fieldPath;
870
+ const tryExtractKeyFromItem = (item) => {
871
+ let value = item;
872
+ for (const pathItem of path) {
873
+ const fieldName = pathItem.name;
874
+ if (value.kind === "literal" && fieldName.text === "kind") {
875
+ // An enum constant.
876
+ return value;
877
+ }
878
+ if (value.kind !== "object") {
879
+ // An error was already registered.
880
+ return undefined;
881
+ }
882
+ const entry = value.entries[fieldName.text];
883
+ if (!entry) {
884
+ errors.push({
885
+ token: value.token,
886
+ message: `Missing entry: ${fieldName.text}`,
887
+ });
888
+ return;
889
+ }
890
+ value = entry.value;
891
+ }
892
+ return value;
893
+ };
894
+ const keyIdentityToKeys = new Map();
895
+ for (const item of items) {
896
+ const key = tryExtractKeyFromItem(item);
897
+ if (!key) {
898
+ return;
899
+ }
900
+ if (key.kind !== "literal") {
901
+ // Cannot happen.
902
+ return;
903
+ }
904
+ let keyIdentity;
905
+ const keyToken = key.token.text;
906
+ if (keyType.kind === "primitive") {
907
+ const { primitive } = keyType;
908
+ if (!valueHasPrimitiveType(keyToken, primitive)) {
909
+ continue;
910
+ }
911
+ keyIdentity = literalValueToIdentity(keyToken, primitive);
912
+ }
913
+ else {
914
+ // The key is an enum, use the enum field name as the key identity.
915
+ if (!isStringLiteral(keyToken)) {
916
+ continue;
917
+ }
918
+ keyIdentity = unquoteAndUnescape(keyToken);
919
+ }
920
+ if (keyIdentityToKeys.has(keyIdentity)) {
921
+ keyIdentityToKeys.get(keyIdentity).push(key);
922
+ }
923
+ else {
924
+ keyIdentityToKeys.set(keyIdentity, [key]);
925
+ }
926
+ }
927
+ // Verify that every key in `keyIdentityToItems` has a single value.
928
+ for (const duplicateKeys of keyIdentityToKeys.values()) {
929
+ if (duplicateKeys.length <= 1) {
930
+ continue;
931
+ }
932
+ for (const key of duplicateKeys) {
933
+ errors.push({
934
+ token: key.token,
935
+ message: "Duplicate key",
936
+ });
937
+ }
938
+ }
939
+ }
940
+ class TypeResolver {
941
+ constructor(module, modules, usedImports, errors) {
942
+ this.module = module;
943
+ this.modules = modules;
944
+ this.usedImports = usedImports;
945
+ this.errors = errors;
946
+ }
947
+ resolve(input, recordOrigin) {
948
+ switch (input.kind) {
949
+ case "primitive":
950
+ return input;
951
+ case "array": {
952
+ const item = this.resolve(input.item, recordOrigin);
953
+ if (!item) {
954
+ return undefined;
955
+ }
956
+ return { kind: "array", item: item, key: input.key };
957
+ }
958
+ case "optional": {
959
+ const value = this.resolve(input.other, recordOrigin);
960
+ if (!value) {
961
+ return undefined;
962
+ }
963
+ return { kind: "optional", other: value };
964
+ }
965
+ case "record": {
966
+ return this.resolveRecordRef(input, recordOrigin);
967
+ }
968
+ }
969
+ }
970
+ /**
971
+ * Finds the definition of the actual record referenced from a value type.
972
+ * This is where we implement the name resolution algorithm.
973
+ */
974
+ resolveRecordRef(recordRef, recordOrigin) {
975
+ const firstNamePart = recordRef.nameParts[0];
976
+ // The most nested record/module which contains the first name in the record
977
+ // reference, or the module if the record reference is absolute (starts with
978
+ // a dot).
979
+ let start;
980
+ const { errors, module, modules, usedImports } = this;
981
+ if (recordOrigin !== "top-level") {
982
+ if (!recordRef.absolute) {
983
+ // Traverse the chain of ancestors from most nested to top-level.
984
+ for (const fromRecord of [...recordOrigin.recordAncestors].reverse()) {
985
+ const matchMaybe = fromRecord.nameToDeclaration[firstNamePart.text];
986
+ if (matchMaybe && matchMaybe.kind === "record") {
987
+ start = fromRecord;
988
+ break;
989
+ }
990
+ }
991
+ }
992
+ if (!start) {
993
+ start = module;
994
+ }
995
+ }
996
+ else {
997
+ start = module;
998
+ }
999
+ const makeNotARecordError = (name) => ({
1000
+ token: name,
1001
+ message: "Does not refer to a struct or an enum",
1002
+ });
1003
+ const makeCannotFindNameError = (name) => ({
1004
+ token: name,
1005
+ message: `Cannot find name '${name.text}'`,
1006
+ });
1007
+ let it = start;
1008
+ const nameParts = [];
1009
+ for (let i = 0; i < recordRef.nameParts.length; ++i) {
1010
+ const namePart = recordRef.nameParts[i];
1011
+ const name = namePart.text;
1012
+ let newIt = it.nameToDeclaration[name];
1013
+ if (newIt === undefined) {
1014
+ errors.push(makeCannotFindNameError(namePart));
1015
+ return undefined;
1016
+ }
1017
+ else if (newIt.kind === "record") {
1018
+ it = newIt;
1019
+ }
1020
+ else if (newIt.kind === "import" || newIt.kind === "import-alias") {
1021
+ const transitiveImportError = () => ({
1022
+ token: namePart,
1023
+ message: "Cannot refer to imports of imported module",
1024
+ });
1025
+ if (i !== 0) {
1026
+ errors.push(transitiveImportError());
1027
+ return undefined;
1028
+ }
1029
+ usedImports.add(name);
1030
+ const newModulePath = newIt.resolvedModulePath;
1031
+ if (newModulePath === undefined) {
1032
+ return undefined;
1033
+ }
1034
+ const newModuleResult = modules.get(newModulePath);
1035
+ if (newModuleResult === undefined || newModuleResult.result === null) {
1036
+ // The module was not found or has errors: an error was already
1037
+ // registered, no need to register a new one.
1038
+ return undefined;
1039
+ }
1040
+ const newModule = newModuleResult.result;
1041
+ if (newIt.kind === "import") {
1042
+ newIt = newModule.nameToDeclaration[name];
1043
+ if (!newIt) {
1044
+ errors.push(makeCannotFindNameError(namePart));
1045
+ return undefined;
1046
+ }
1047
+ if (!newIt || newIt.kind !== "record") {
1048
+ this.errors.push(newIt.kind === "import" || newIt.kind === "import-alias"
1049
+ ? transitiveImportError()
1050
+ : makeNotARecordError(namePart));
1051
+ return undefined;
1052
+ }
1053
+ it = newIt;
1054
+ }
1055
+ else {
1056
+ it = newModule;
1057
+ }
1058
+ }
1059
+ else {
1060
+ this.errors.push(makeNotARecordError(namePart));
1061
+ return undefined;
1062
+ }
1063
+ nameParts.push({ token: namePart, declaration: newIt });
1064
+ }
1065
+ if (it.kind !== "record") {
1066
+ const name = recordRef.nameParts[0];
1067
+ this.errors.push(makeNotARecordError(name));
1068
+ return undefined;
1069
+ }
1070
+ return {
1071
+ kind: "record",
1072
+ key: it.key,
1073
+ recordType: it.recordType,
1074
+ nameParts: nameParts,
1075
+ refToken: recordRef.nameParts.at(-1),
1076
+ };
1077
+ }
1078
+ }
1079
+ function ensureAllImportsAreUsed(module, usedImports, errors) {
1080
+ for (const declaration of module.declarations) {
1081
+ if (declaration.kind === "import") {
1082
+ for (const importedName of declaration.importedNames) {
1083
+ if (!usedImports.has(importedName.text)) {
1084
+ errors.push({
1085
+ token: importedName,
1086
+ message: "Unused import",
1087
+ });
1088
+ }
1089
+ }
1090
+ }
1091
+ else if (declaration.kind === "import-alias") {
1092
+ if (!usedImports.has(declaration.name.text)) {
1093
+ errors.push({
1094
+ token: declaration.name,
1095
+ message: "Unused import alias",
1096
+ });
1097
+ }
1098
+ }
1099
+ }
1100
+ }
1101
+ class ModuleParserBase {
1102
+ parseModule(modulePath) {
1103
+ const code = this.readSourceCode(modulePath);
1104
+ if (code === undefined) {
1105
+ return {
1106
+ result: null,
1107
+ errors: [],
1108
+ };
1109
+ }
1110
+ const tokens = tokenizeModule(code, modulePath);
1111
+ if (tokens.errors.length !== 0) {
1112
+ return {
1113
+ result: null,
1114
+ errors: tokens.errors,
1115
+ };
1116
+ }
1117
+ return parseModule(tokens.result);
1118
+ }
1119
+ }
1120
+ class DefaultModuleParser extends ModuleParserBase {
1121
+ constructor(fileReader, rootPath) {
1122
+ super();
1123
+ this.fileReader = fileReader;
1124
+ this.rootPath = rootPath;
1125
+ }
1126
+ readSourceCode(modulePath) {
1127
+ return this.fileReader.readTextFile(paths.join(this.rootPath, modulePath));
1128
+ }
1129
+ }
1130
+ class MapBasedModuleParser extends ModuleParserBase {
1131
+ constructor(moduleMap) {
1132
+ super();
1133
+ this.moduleMap = moduleMap;
1134
+ }
1135
+ readSourceCode(modulePath) {
1136
+ return this.moduleMap.get(modulePath);
1137
+ }
1138
+ }
1139
+ function resolveModulePath(pathToken, originModulePath, errors) {
1140
+ let modulePath = unquoteAndUnescape(pathToken.text);
1141
+ if (/\\/.test(modulePath)) {
1142
+ errors.push({
1143
+ token: pathToken,
1144
+ message: "Replace backslash with slash",
1145
+ });
1146
+ return undefined;
1147
+ }
1148
+ if (modulePath.startsWith("./") || modulePath.startsWith("../")) {
1149
+ // This is a relative path from the module. Let's transform it into a
1150
+ // relative path from root.
1151
+ modulePath = paths.join(originModulePath, "..", modulePath);
1152
+ }
1153
+ // "a/./b/../c" => "a/c"
1154
+ // Note that `paths.normalize` will use backslashes on Windows.
1155
+ // We don't want that.
1156
+ modulePath = paths.normalize(modulePath).replace(/\\/g, "/");
1157
+ if (modulePath.startsWith(`../`)) {
1158
+ errors.push({
1159
+ token: pathToken,
1160
+ message: "Module path must point to a file within root",
1161
+ });
1162
+ return undefined;
1163
+ }
1164
+ return modulePath;
1165
+ }
1166
+ function tryFindRecordForType(type) {
1167
+ switch (type.kind) {
1168
+ case "array":
1169
+ return tryFindRecordForType(type.item);
1170
+ case "optional":
1171
+ return tryFindRecordForType(type.other);
1172
+ case "record":
1173
+ return type.key;
1174
+ case "primitive":
1175
+ return null;
1176
+ }
1177
+ }
1178
+ //# sourceMappingURL=module_set.js.map