skir 1.2.3 → 1.2.5

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 (99) hide show
  1. package/README.md +1 -1
  2. package/dist/config_parser.d.ts.map +1 -1
  3. package/dist/module_collector.d.ts.map +1 -1
  4. package/dist/parser.d.ts.map +1 -1
  5. package/package.json +6 -4
  6. package/src/config_parser.ts +4 -1
  7. package/src/module_collector.ts +18 -20
  8. package/src/parser.ts +12 -0
  9. package/src/project_initializer.ts +18 -0
  10. package/dist/casing.d.ts +0 -5
  11. package/dist/casing.js +0 -23
  12. package/dist/casing.js.map +0 -1
  13. package/dist/command_line_parser.d.ts +0 -36
  14. package/dist/command_line_parser.d.ts.map +0 -1
  15. package/dist/command_line_parser.js +0 -240
  16. package/dist/command_line_parser.js.map +0 -1
  17. package/dist/compatibility_checker.d.ts +0 -74
  18. package/dist/compatibility_checker.js +0 -331
  19. package/dist/compatibility_checker.js.map +0 -1
  20. package/dist/compiler.d.ts +0 -3
  21. package/dist/compiler.js +0 -406
  22. package/dist/compiler.js.map +0 -1
  23. package/dist/completion_helper.d.ts +0 -27
  24. package/dist/completion_helper.js +0 -101
  25. package/dist/completion_helper.js.map +0 -1
  26. package/dist/config.d.ts +0 -18
  27. package/dist/config.js +0 -19
  28. package/dist/config.js.map +0 -1
  29. package/dist/config_parser.d.ts +0 -34
  30. package/dist/config_parser.js +0 -214
  31. package/dist/config_parser.js.map +0 -1
  32. package/dist/definition_finder.d.ts +0 -16
  33. package/dist/definition_finder.js +0 -375
  34. package/dist/definition_finder.js.map +0 -1
  35. package/dist/dependency_manager.d.ts +0 -14
  36. package/dist/dependency_manager.js +0 -109
  37. package/dist/dependency_manager.js.map +0 -1
  38. package/dist/doc_comment_parser.d.ts +0 -6
  39. package/dist/doc_comment_parser.js +0 -269
  40. package/dist/doc_comment_parser.js.map +0 -1
  41. package/dist/doc_reference_resolver.d.ts +0 -5
  42. package/dist/doc_reference_resolver.js +0 -232
  43. package/dist/doc_reference_resolver.js.map +0 -1
  44. package/dist/error_renderer.d.ts +0 -24
  45. package/dist/error_renderer.js +0 -326
  46. package/dist/error_renderer.js.map +0 -1
  47. package/dist/exit_error.d.ts +0 -8
  48. package/dist/exit_error.d.ts.map +0 -1
  49. package/dist/exit_error.js +0 -8
  50. package/dist/exit_error.js.map +0 -1
  51. package/dist/expected_names.d.ts +0 -11
  52. package/dist/expected_names.js +0 -34
  53. package/dist/expected_names.js.map +0 -1
  54. package/dist/formatter.d.ts +0 -28
  55. package/dist/formatter.js +0 -338
  56. package/dist/formatter.js.map +0 -1
  57. package/dist/get_dependencies_flow.d.ts +0 -40
  58. package/dist/get_dependencies_flow.js +0 -177
  59. package/dist/get_dependencies_flow.js.map +0 -1
  60. package/dist/import_block_formatter.d.ts +0 -3
  61. package/dist/import_block_formatter.js +0 -36
  62. package/dist/import_block_formatter.js.map +0 -1
  63. package/dist/index.d.ts +0 -2
  64. package/dist/index.d.ts.map +0 -1
  65. package/dist/index.js +0 -2
  66. package/dist/index.js.map +0 -1
  67. package/dist/io.d.ts +0 -23
  68. package/dist/io.d.ts.map +0 -1
  69. package/dist/io.js +0 -58
  70. package/dist/io.js.map +0 -1
  71. package/dist/literals.d.ts +0 -12
  72. package/dist/literals.js +0 -96
  73. package/dist/literals.js.map +0 -1
  74. package/dist/module_collector.d.ts +0 -9
  75. package/dist/module_collector.js +0 -75
  76. package/dist/module_collector.js.map +0 -1
  77. package/dist/module_set.d.ts +0 -51
  78. package/dist/module_set.js +0 -1331
  79. package/dist/module_set.js.map +0 -1
  80. package/dist/package_downloader.d.ts +0 -4
  81. package/dist/package_downloader.js +0 -256
  82. package/dist/package_downloader.js.map +0 -1
  83. package/dist/package_types.d.ts +0 -30
  84. package/dist/package_types.d.ts.map +0 -1
  85. package/dist/package_types.js +0 -2
  86. package/dist/package_types.js.map +0 -1
  87. package/dist/parser.d.ts +0 -7
  88. package/dist/parser.js +0 -1323
  89. package/dist/parser.js.map +0 -1
  90. package/dist/project_initializer.d.ts +0 -2
  91. package/dist/project_initializer.d.ts.map +0 -1
  92. package/dist/project_initializer.js +0 -189
  93. package/dist/project_initializer.js.map +0 -1
  94. package/dist/snapshotter.d.ts +0 -16
  95. package/dist/snapshotter.js +0 -263
  96. package/dist/snapshotter.js.map +0 -1
  97. package/dist/tokenizer.d.ts +0 -18
  98. package/dist/tokenizer.js +0 -244
  99. package/dist/tokenizer.js.map +0 -1
@@ -1,1331 +0,0 @@
1
- import { unquoteAndUnescape, } from "skir-internal";
2
- import { resolveDocReferences } from "./doc_reference_resolver.js";
3
- import { declarationsToExpectedNames, ExpectedNamesCollector, } from "./expected_names.js";
4
- import { isStringLiteral, literalValueToDenseJson, literalValueToIdentity, valueHasPrimitiveType, } from "./literals.js";
5
- import { extractPackagePrefix, parseModule } from "./parser.js";
6
- import { tokenizeModule } from "./tokenizer.js";
7
- /**
8
- * Result of compiling a set of modules. Immutable.
9
- *
10
- * Support incremental compilation by accepting an optional cache of the previous
11
- * module set. This cache can be used to avoid re-parsing and re-resolving
12
- * modules that haven't changed since the last compilation.
13
- */
14
- export class ModuleSet {
15
- static compile(modulePathToContent, cache, parseMode = "strict") {
16
- return new ModuleSet(modulePathToContent, cache, parseMode);
17
- }
18
- static compileForCompletion(currentModulePath, currentPosition, modulePathToContent, cache) {
19
- if (!modulePathToContent.has(currentModulePath)) {
20
- throw new Error(`Not found: ${currentModulePath}`);
21
- }
22
- const moduleSet = new ModuleSet(modulePathToContent, cache, "lenient", {
23
- modulePath: currentModulePath,
24
- position: currentPosition,
25
- });
26
- return moduleSet.modules.get(currentModulePath);
27
- }
28
- constructor(modulePathToContent, cache, parseMode, completionMode) {
29
- this.modulePathToContent = modulePathToContent;
30
- this.parseMode = parseMode;
31
- this.completionMode = completionMode;
32
- this.moduleBundles = new Map();
33
- this.registry = new DeclarationRegistry();
34
- this.cache = cache
35
- ? new Cache(modulePathToContent, cache.moduleBundles, cache.registry)
36
- : undefined;
37
- // In completion mode, no need to recompile modules which are not dependencies of
38
- // the current module.
39
- const modulePaths = completionMode
40
- ? [completionMode.modulePath]
41
- : modulePathToContent.keys();
42
- for (const modulePath of modulePaths) {
43
- this.parseAndResolve(modulePath, new Set());
44
- }
45
- this.finalizationResult = this.finalize();
46
- // So it can be garbage collected.
47
- this.cache = undefined;
48
- }
49
- parseAndResolve(modulePath, inProgressSet) {
50
- const inMap = this.moduleBundles.get(modulePath);
51
- if (inMap !== undefined) {
52
- return inMap;
53
- }
54
- const result = this.doParseAndResolve(modulePath, inProgressSet);
55
- if (result) {
56
- this.moduleBundles.set(modulePath, result);
57
- }
58
- return result;
59
- }
60
- /** Called by `parseAndResolve` when the module is not in the map already. */
61
- doParseAndResolve(modulePath, inProgressSet) {
62
- const moduleContent = this.modulePathToContent.get(modulePath);
63
- if (moduleContent === undefined) {
64
- return null;
65
- }
66
- let moduleTokens;
67
- {
68
- let moduleCacheResult;
69
- if (modulePath === this.completionMode?.modulePath) {
70
- moduleCacheResult = { kind: "no-cache" };
71
- }
72
- else {
73
- moduleCacheResult = this.cache?.getModuleCacheResult(modulePath) ?? {
74
- kind: "no-cache",
75
- };
76
- }
77
- switch (moduleCacheResult.kind) {
78
- case "no-cache": {
79
- moduleTokens = tokenizeModule(moduleContent, modulePath, this.completionMode);
80
- break;
81
- }
82
- case "module-tokens": {
83
- moduleTokens = moduleCacheResult.tokens;
84
- break;
85
- }
86
- case "module-bundle": {
87
- this.registry.mergeFrom(moduleCacheResult.bundle.registry);
88
- return moduleCacheResult.bundle;
89
- }
90
- }
91
- }
92
- let module;
93
- const errors = [];
94
- {
95
- const parseResult = parseModule(moduleTokens.result, this.parseMode);
96
- errors.push(...parseResult.errors);
97
- module = parseResult.result;
98
- }
99
- const moduleBundle = new ModuleBundle(moduleTokens, {
100
- result: module,
101
- errors: errors,
102
- });
103
- // Process all imports.
104
- for (const declaration of module.declarations) {
105
- if (declaration.kind !== "import" &&
106
- declaration.kind !== "import-alias") {
107
- continue;
108
- }
109
- const otherModulePath = declaration.resolvedModulePath;
110
- if (otherModulePath === undefined) {
111
- // An error was already registered.
112
- continue;
113
- }
114
- // Add the imported module to the module set.
115
- const circularDependencyMessage = "Circular dependency between modules";
116
- if (inProgressSet.has(modulePath)) {
117
- errors.push({
118
- token: declaration.modulePath,
119
- message: circularDependencyMessage,
120
- });
121
- continue;
122
- }
123
- inProgressSet.add(modulePath);
124
- const otherModule = this.parseAndResolve(otherModulePath, inProgressSet);
125
- inProgressSet.delete(modulePath);
126
- if (otherModule === null) {
127
- errors.push({
128
- token: declaration.modulePath,
129
- message: "Module not found",
130
- expectedNames: suggestModulePaths(unquoteAndUnescape(declaration.modulePath.text), modulePath, this.modulePathToContent.keys()),
131
- });
132
- }
133
- else if (otherModule.tokens.errors.length !== 0 ||
134
- otherModule.module.errors.length !== 0) {
135
- const hasCircularDependency = otherModule.module.errors.some((e) => e.message === circularDependencyMessage);
136
- if (hasCircularDependency) {
137
- errors.push({
138
- token: declaration.modulePath,
139
- message: circularDependencyMessage,
140
- });
141
- }
142
- else {
143
- errors.push({
144
- token: declaration.modulePath,
145
- message: "Imported module has errors",
146
- errorIsInOtherModule: true,
147
- });
148
- }
149
- }
150
- else if (declaration.kind === "import") {
151
- // Make sure that the symbols we are importing exist in the imported
152
- // module and are not imported symbols themselves.
153
- for (const importedName of declaration.importedNames) {
154
- const importedDeclaration = otherModule.module.result.nameToDeclaration[importedName.text];
155
- if (importedDeclaration === undefined) {
156
- errors.push({
157
- token: importedName,
158
- message: "Not found",
159
- expectedNames: declarationsToExpectedNames(otherModule.module.result.nameToDeclaration, (d) => d.kind === "record"),
160
- });
161
- }
162
- else if (importedDeclaration.kind === "import") {
163
- errors.push({
164
- token: importedName,
165
- message: "Cannot reimport imported record",
166
- });
167
- }
168
- else if (importedDeclaration.kind !== "record") {
169
- errors.push({
170
- token: importedName,
171
- message: "Not a record",
172
- });
173
- }
174
- }
175
- }
176
- }
177
- if (errors.length && !this.completionMode) {
178
- return moduleBundle;
179
- }
180
- // We can't merge these 3 loops into a single one, each operation must run
181
- // after the last operation ran on the whole map.
182
- // Loop 1: merge the module records map into the cross-module record map.
183
- for (const record of module.records) {
184
- const { key } = record.record;
185
- this.registry.recordMap.set(key, record);
186
- moduleBundle.registry.recordMap.set(key, record);
187
- const { recordNumber } = record.record;
188
- if (recordNumber != null && !modulePath.startsWith("@")) {
189
- moduleBundle.registry.pushNumberRecord(recordNumber, key);
190
- }
191
- }
192
- // Loop 2: resolve every field type of every record in the module.
193
- // Store the result in the Field object.
194
- const usedImports = new Set();
195
- const typeResolver = new TypeResolver(module, this.moduleBundles, this.completionMode
196
- ? this.cache?.registry.topLevelNameToRecordLocations
197
- : undefined, usedImports, errors);
198
- for (const record of module.records) {
199
- this.storeResolvedFieldTypes(record, typeResolver);
200
- }
201
- // Loop 3: once all the types of record fields have been resolved.
202
- const doResolveDocReferences = (documentee) => resolveDocReferences(documentee, module, (p) => this.moduleBundles.get(p)?.module.result, this.recordMap, errors);
203
- for (const moduleRecord of module.records) {
204
- const { record } = moduleRecord;
205
- // For every field, determine if the field is recursive, i.e. the field
206
- // type depends on the record where the field is defined.
207
- // Store the result in the Field object.
208
- this.storeFieldRecursivity(record);
209
- // Verify that the `key` field of every array type is valid.
210
- for (const field of record.fields) {
211
- const { type } = field;
212
- if (type) {
213
- this.validateArrayKeys(type, errors);
214
- }
215
- // Resolve the references in the doc comments of the field.
216
- doResolveDocReferences({
217
- kind: "field",
218
- field: field,
219
- record: record,
220
- });
221
- }
222
- // Resolve the references in the doc comments of the record.
223
- doResolveDocReferences(record);
224
- }
225
- const resolveTopLevelTypeAndValidate = (type) => {
226
- const resolvedType = typeResolver.resolve(type, "top-level");
227
- if (resolvedType) {
228
- this.validateArrayKeys(resolvedType, errors);
229
- }
230
- return resolvedType;
231
- };
232
- // Resolve every request/response type of every method in the module.
233
- // Store the result in the Method object.
234
- for (const method of module.methods) {
235
- const { unresolvedRequestType, unresolvedResponseType } = method;
236
- // Resolve request type.
237
- method.requestType = resolveTopLevelTypeAndValidate(unresolvedRequestType);
238
- // Resolve response type.
239
- method.responseType = resolveTopLevelTypeAndValidate(unresolvedResponseType);
240
- const { number } = method;
241
- if (!modulePath.startsWith("@")) {
242
- moduleBundle.registry.pushNumberMethod(number, method);
243
- }
244
- // Resolve the references in the doc comments of the method.
245
- doResolveDocReferences(method);
246
- }
247
- for (const method of module.brokenMethods) {
248
- const { unresolvedRequestType, unresolvedResponseType } = method;
249
- resolveTopLevelTypeAndValidate(unresolvedRequestType);
250
- if (unresolvedResponseType) {
251
- resolveTopLevelTypeAndValidate(unresolvedResponseType);
252
- }
253
- }
254
- // Resolve every constant type. Store the result in the constant object.
255
- for (const constant of module.constants) {
256
- const { unresolvedType } = constant;
257
- const type = resolveTopLevelTypeAndValidate(unresolvedType);
258
- constant.type = type;
259
- if (type) {
260
- constant.valueAsDenseJson = //
261
- this.valueToDenseJson(constant.value, type, errors);
262
- }
263
- // Resolve the references in the doc comments of the constant.
264
- doResolveDocReferences(constant);
265
- }
266
- for (const constant of module.brokenConstants) {
267
- const { unresolvedType } = constant;
268
- resolveTopLevelTypeAndValidate(unresolvedType);
269
- }
270
- moduleBundle.registry.pushTopLevelNames(module.declarations
271
- .filter((d) => d.kind === "record")
272
- .map((d) => ({
273
- name: d.name.text,
274
- doc: d.doc,
275
- modulePath: modulePath,
276
- })));
277
- ensureAllImportsAreUsed(module, usedImports, errors);
278
- this.registry.mergeFrom(moduleBundle.registry);
279
- return moduleBundle;
280
- }
281
- storeResolvedFieldTypes(record, typeResolver) {
282
- for (const field of record.record.fields) {
283
- if (field.unresolvedType === undefined) {
284
- // A constant enum field.
285
- continue;
286
- }
287
- field.type = typeResolver.resolve(field.unresolvedType, record);
288
- }
289
- }
290
- storeFieldRecursivity(record) {
291
- for (const field of record.fields) {
292
- if (!field.type)
293
- continue;
294
- const modes = record.recordType === "struct"
295
- ? ["hard", "via-optional", "soft"]
296
- : ["soft"];
297
- for (const mode of modes) {
298
- const deps = new Set();
299
- this.collectTypeDeps(field.type, mode, deps);
300
- if (deps.has(record.key)) {
301
- field.isRecursive = mode;
302
- break;
303
- }
304
- }
305
- }
306
- }
307
- collectTypeDeps(input, mode, out) {
308
- switch (input.kind) {
309
- case "record": {
310
- if (mode === "via-optional")
311
- return;
312
- const { key } = input;
313
- if (out.has(key))
314
- return;
315
- out.add(key);
316
- // Recursively add deps of all fields of the record.
317
- const record = this.recordMap.get(key)?.record;
318
- if (!record) {
319
- console.error("collectTypeDeps: record not found", key);
320
- return;
321
- }
322
- if (mode === "hard" && record.recordType === "enum") {
323
- return;
324
- }
325
- for (const field of record.fields) {
326
- if (field.type === undefined)
327
- continue;
328
- this.collectTypeDeps(field.type, mode, out);
329
- }
330
- break;
331
- }
332
- case "array": {
333
- if (mode === "soft") {
334
- this.collectTypeDeps(input.item, mode, out);
335
- }
336
- break;
337
- }
338
- case "optional": {
339
- switch (mode) {
340
- case "soft": {
341
- this.collectTypeDeps(input.other, mode, out);
342
- break;
343
- }
344
- case "via-optional": {
345
- // Important: must pass "hard" here.
346
- this.collectTypeDeps(input.other, "hard", out);
347
- break;
348
- }
349
- default: {
350
- const _ = mode;
351
- }
352
- }
353
- break;
354
- }
355
- }
356
- }
357
- /**
358
- * Verifies that the `key` field of every array type found in `topLevelType`
359
- * is valid. Populates the `keyType` field of every field path.
360
- */
361
- validateArrayKeys(topLevelType, errors) {
362
- const validate = (type) => {
363
- const { key, item } = type;
364
- if (!key) {
365
- return;
366
- }
367
- const { path } = key;
368
- // Iterate the fields in the sequence.
369
- let currentType = item;
370
- let enumRef;
371
- for (let i = 0; i < path.length; ++i) {
372
- const pathItem = path[i];
373
- const fieldName = pathItem.name;
374
- if (currentType.kind !== "record") {
375
- if (i === 0) {
376
- errors.push({
377
- token: key.pipeToken,
378
- message: "Item must have struct type",
379
- });
380
- }
381
- else {
382
- const previousFieldName = path[i - 1].name;
383
- errors.push({
384
- token: previousFieldName,
385
- message: "Must have struct type",
386
- });
387
- }
388
- return;
389
- }
390
- const record = this.recordMap.get(currentType.key)?.record;
391
- if (!record) {
392
- console.error("validateArrayKeys: record not found", currentType.key);
393
- return;
394
- }
395
- if (record.recordType === "struct") {
396
- const field = record.nameToDeclaration[fieldName.text];
397
- if (field?.kind !== "field") {
398
- errors.push({
399
- token: fieldName,
400
- message: `Field not found in struct ${record.name.text}`,
401
- expectedNames: declarationsToExpectedNames(record.nameToDeclaration, (d) => d.kind === "field"),
402
- });
403
- return undefined;
404
- }
405
- pathItem.declaration = field;
406
- if (!field.type) {
407
- // An error was already registered.
408
- return;
409
- }
410
- currentType = field.type;
411
- }
412
- else {
413
- // An enum.
414
- if (fieldName.text !== "kind") {
415
- errors.push({
416
- token: fieldName,
417
- expected: "'kind'",
418
- expectedNames: [{ name: "kind" }],
419
- });
420
- return undefined;
421
- }
422
- enumRef = currentType;
423
- currentType = {
424
- kind: "primitive",
425
- primitive: "string",
426
- };
427
- }
428
- }
429
- if (currentType.kind !== "primitive") {
430
- errors.push({
431
- token: path.at(-1).name,
432
- message: "Does not have primitive type",
433
- });
434
- return;
435
- }
436
- // If the last field name of the `kind` field of an enum, we store a
437
- // reference to the enum in the `keyType` field of the array type.
438
- key.keyType = enumRef || currentType;
439
- };
440
- const traverseType = (type) => {
441
- switch (type.kind) {
442
- case "array":
443
- validate(type);
444
- return traverseType(type.item);
445
- case "optional":
446
- return traverseType(type.other);
447
- }
448
- };
449
- traverseType(topLevelType);
450
- }
451
- valueToDenseJson(value, expectedType, errors) {
452
- switch (expectedType.kind) {
453
- case "optional": {
454
- if (value.kind === "literal" && value.token.text === "null") {
455
- value.type = { kind: "null" };
456
- return null;
457
- }
458
- return this.valueToDenseJson(value, expectedType.other, errors);
459
- }
460
- case "array": {
461
- if (value.kind !== "array") {
462
- errors.push({
463
- token: value.token,
464
- expected: "array",
465
- });
466
- return undefined;
467
- }
468
- const json = [];
469
- let allGood = true;
470
- for (const item of value.items) {
471
- const itemJson = //
472
- this.valueToDenseJson(item, expectedType.item, errors);
473
- if (itemJson !== undefined) {
474
- json.push(itemJson);
475
- }
476
- else {
477
- // Even if we could return now, better to verify the type of the
478
- // other items.
479
- allGood = false;
480
- }
481
- }
482
- if (!allGood) {
483
- return undefined;
484
- }
485
- const { key } = expectedType;
486
- value.key = key;
487
- if (key) {
488
- validateKeyedItems(value.items, key, errors);
489
- }
490
- return json;
491
- }
492
- case "record": {
493
- const record = this.recordMap.get(expectedType.key);
494
- if (!record) {
495
- // An error was already registered.
496
- return undefined;
497
- }
498
- return record.record.recordType === "struct"
499
- ? this.structValueToDenseJson(value, record.record, errors)
500
- : this.enumValueToDenseJson(value, record.record, errors);
501
- }
502
- case "primitive": {
503
- const { token } = value;
504
- const { primitive } = expectedType;
505
- if (value.kind !== "literal" ||
506
- !valueHasPrimitiveType(token.text, primitive)) {
507
- errors.push({
508
- token: value.token,
509
- expected: primitive,
510
- });
511
- return undefined;
512
- }
513
- value.type = expectedType;
514
- return literalValueToDenseJson(token.text, expectedType.primitive);
515
- }
516
- }
517
- }
518
- structValueToDenseJson(value, expectedStruct, errors) {
519
- const { token } = value;
520
- if (value.kind !== "object") {
521
- errors.push({
522
- token: token,
523
- expected: "object",
524
- });
525
- return undefined;
526
- }
527
- const json = Array(expectedStruct.numSlotsInclRemovedNumbers).fill(0);
528
- let allGood = true;
529
- const fieldNameTokens = Object.values(value.entries)
530
- .map((e) => e.name)
531
- .concat(value.orphanNames);
532
- const fieldNames = new Set(fieldNameTokens.map((t) => t.text));
533
- for (const fieldName of fieldNameTokens) {
534
- const field = expectedStruct.nameToDeclaration[fieldName.text];
535
- if (field?.kind !== "field") {
536
- errors.push({
537
- token: fieldName,
538
- message: `Field not found in struct ${expectedStruct.name.text}`,
539
- expectedNames: declarationsToExpectedNames(expectedStruct.nameToDeclaration, (d) => d.kind === "field" && !fieldNames.has(d.name.text)),
540
- });
541
- allGood = false;
542
- continue;
543
- }
544
- }
545
- let arrayLen = 0;
546
- for (const field of expectedStruct.fields) {
547
- const { type } = field;
548
- if (!type) {
549
- allGood = false;
550
- continue;
551
- }
552
- const fieldEntry = value.entries[field.name.text];
553
- let valueJson;
554
- if (fieldEntry) {
555
- fieldEntry.fieldDeclaration = field;
556
- valueJson = this.valueToDenseJson(fieldEntry.value, type, errors);
557
- }
558
- else {
559
- // Unless the object is declared partial, all fields are required.
560
- if (value.partial) {
561
- valueJson = this.getDefaultJson(type);
562
- }
563
- else {
564
- errors.push({
565
- token: token,
566
- message: `Missing entry: ${field.name.text}`,
567
- });
568
- }
569
- }
570
- if (valueJson === undefined) {
571
- allGood = false;
572
- continue;
573
- }
574
- json[field.number] = valueJson;
575
- const hasDefaultValue = type.kind === "optional"
576
- ? valueJson === null
577
- : !valueJson ||
578
- (Array.isArray(valueJson) && !valueJson.length) ||
579
- (type.kind === "primitive" &&
580
- (type.primitive === "int64" || type.primitive === "hash64") &&
581
- valueJson === "0");
582
- if (!hasDefaultValue) {
583
- arrayLen = Math.max(arrayLen, field.number + 1);
584
- }
585
- }
586
- if (!allGood) {
587
- return undefined;
588
- }
589
- value.record = expectedStruct;
590
- return json.slice(0, arrayLen);
591
- }
592
- enumValueToDenseJson(value, expectedEnum, errors) {
593
- const { token } = value;
594
- if (value.kind === "literal" && isStringLiteral(token.text)) {
595
- // The value is a string.
596
- // It must match the name of one of the constants defined in the enum.
597
- const fieldName = unquoteAndUnescape(token.text);
598
- if (fieldName === "UNKNOWN") {
599
- // Present on every enum.
600
- return 0;
601
- }
602
- const field = expectedEnum.nameToDeclaration[fieldName];
603
- if (field?.kind !== "field") {
604
- errors.push({
605
- token: token,
606
- message: `Variant not found in enum ${expectedEnum.name.text}`,
607
- expectedNames: [{ name: "UNKNOWN" }].concat(declarationsToExpectedNames(expectedEnum.nameToDeclaration, (d) => d.kind === "field" && !d.type)),
608
- });
609
- return undefined;
610
- }
611
- if (field.type) {
612
- errors.push({
613
- token: token,
614
- message: "Refers to a wrapper variant",
615
- });
616
- return undefined;
617
- }
618
- value.type = {
619
- kind: "enum",
620
- enum: expectedEnum,
621
- variant: field,
622
- };
623
- return field.number;
624
- }
625
- else if (value.kind === "object") {
626
- // The value is an object. It must have exactly two entries:
627
- // · 'kind' must match the name of one of the wrapper variants defined in
628
- // the enum
629
- // · 'value' must match the type of the wrapper variant
630
- const { entries } = value;
631
- const kindEntry = entries.kind;
632
- const enumValue = entries.value;
633
- const nameTokens = Object.values(entries)
634
- .map((e) => e.name)
635
- .concat(value.orphanNames);
636
- for (const nameToken of nameTokens) {
637
- if (nameToken.text === "kind" || nameToken.text === "value") {
638
- continue;
639
- }
640
- const expectedNames = (kindEntry ? [] : [{ name: "kind" }]).concat(enumValue ? [] : [{ name: "value" }]);
641
- errors.push({
642
- token: nameToken,
643
- message: "Extraneous entry",
644
- expectedNames: expectedNames,
645
- });
646
- return undefined;
647
- }
648
- if (!kindEntry) {
649
- errors.push({
650
- token: token,
651
- message: "Missing entry: kind",
652
- });
653
- return undefined;
654
- }
655
- const kindValueToken = kindEntry.value.token;
656
- if (kindEntry.value.kind !== "literal" ||
657
- !isStringLiteral(kindValueToken.text)) {
658
- errors.push({
659
- token: kindValueToken,
660
- expected: "string",
661
- });
662
- return undefined;
663
- }
664
- const variantName = unquoteAndUnescape(kindValueToken.text);
665
- const variant = expectedEnum.nameToDeclaration[variantName];
666
- if (variant?.kind !== "field") {
667
- errors.push({
668
- token: kindValueToken,
669
- message: `Variant not found in enum ${expectedEnum.name.text}`,
670
- expectedNames: declarationsToExpectedNames(expectedEnum.nameToDeclaration, (d) => d.kind === "field" && !!d.type),
671
- });
672
- return undefined;
673
- }
674
- if (!variant.type) {
675
- errors.push({
676
- token: kindValueToken,
677
- message: "Refers to a constant variant",
678
- });
679
- return undefined;
680
- }
681
- if (!enumValue) {
682
- errors.push({
683
- token: token,
684
- message: "Missing entry: value",
685
- });
686
- return undefined;
687
- }
688
- const valueJson = //
689
- this.valueToDenseJson(enumValue.value, variant.type, errors);
690
- if (valueJson === undefined) {
691
- return undefined;
692
- }
693
- value.record = expectedEnum;
694
- // Return an array of length 2.
695
- return [variant.number, valueJson];
696
- }
697
- else {
698
- // The value is neither a string nor an object. It can't be of enum type.
699
- errors.push({
700
- token: token,
701
- expected: "string or object",
702
- });
703
- return undefined;
704
- }
705
- }
706
- getDefaultJson(type) {
707
- switch (type.kind) {
708
- case "primitive": {
709
- switch (type.primitive) {
710
- case "bool":
711
- case "int32":
712
- case "int64":
713
- case "hash64":
714
- case "float32":
715
- case "float64":
716
- case "timestamp":
717
- return 0;
718
- case "string":
719
- case "bytes":
720
- return "";
721
- default: {
722
- const _ = type.primitive;
723
- throw new TypeError(_);
724
- }
725
- }
726
- }
727
- case "array":
728
- return [];
729
- case "optional":
730
- return null;
731
- case "record": {
732
- const record = this.recordMap.get(type.key);
733
- switch (record.record.recordType) {
734
- case "struct":
735
- return [];
736
- case "enum":
737
- return 0;
738
- }
739
- }
740
- }
741
- }
742
- finalize() {
743
- const modules = new Map();
744
- for (const [modulePath, moduleBundle] of this.moduleBundles) {
745
- const { module, tokens } = moduleBundle;
746
- const moduleErrors = tokens.errors.length ? tokens.errors : module.errors;
747
- modules.set(modulePath, {
748
- result: module.result,
749
- errors: [...moduleErrors],
750
- });
751
- }
752
- // Look for duplicate method numbers.
753
- for (const methods of this.registry.numberToMethods.values()) {
754
- if (methods.length <= 1) {
755
- continue;
756
- }
757
- const pushError = (method, other) => {
758
- const modulePath = method.name.line.modulePath;
759
- const moduleResult = modules.get(modulePath);
760
- const otherMethodName = other.name.text;
761
- const otherModulePath = other.name.line.modulePath;
762
- moduleResult.errors.push({
763
- token: method.name,
764
- message: `Same number as ${otherMethodName} in ${otherModulePath}`,
765
- });
766
- };
767
- pushError(methods[0], methods[1]);
768
- for (let i = 1; i < methods.length; ++i) {
769
- pushError(methods[i], methods[0]);
770
- }
771
- }
772
- // Look for duplicate record numbers.
773
- for (const records of this.registry.numberToRecords.values()) {
774
- if (records.length <= 1) {
775
- continue;
776
- }
777
- const pushError = (recordKey, otherKey) => {
778
- const record = this.registry.recordMap.get(recordKey);
779
- const other = this.registry.recordMap.get(otherKey);
780
- const modulePath = record.record.name.line.modulePath;
781
- const moduleResult = modules.get(modulePath);
782
- const otherRecordName = other.record.name.text;
783
- const otherModulePath = other.modulePath;
784
- moduleResult.errors.push({
785
- token: record.record.name,
786
- message: `Same number as ${otherRecordName} in ${otherModulePath}`,
787
- });
788
- };
789
- pushError(records[0], records[1]);
790
- for (let i = 1; i < records.length; ++i) {
791
- pushError(records[i], records[0]);
792
- }
793
- }
794
- // Aggregate errors across all modules.
795
- const errors = [];
796
- for (const moduleBundle of modules.values()) {
797
- errors.push(...moduleBundle.errors);
798
- }
799
- return {
800
- modules: modules,
801
- errors: errors,
802
- };
803
- }
804
- // END PROPERTIES
805
- findRecordByNumber(recordNumber) {
806
- const { numberToRecords, recordMap } = this.registry;
807
- const recordKeys = numberToRecords.get(recordNumber);
808
- return recordKeys?.length === 1 ? recordMap.get(recordKeys[0]) : undefined;
809
- }
810
- findMethodByNumber(methodNumber) {
811
- const { numberToMethods } = this.registry;
812
- const methods = numberToMethods.get(methodNumber);
813
- return methods?.length === 1 ? methods[0] : undefined;
814
- }
815
- get recordMap() {
816
- return this.registry.recordMap;
817
- }
818
- get errors() {
819
- return this.finalizationResult.errors;
820
- }
821
- get modules() {
822
- return this.finalizationResult.modules;
823
- }
824
- }
825
- /**
826
- * If the array type is keyed, the array value must satisfy two conditions.
827
- * First: the key field of every item must be set.
828
- * Second: not two items can have the same key.
829
- */
830
- function validateKeyedItems(items, fieldPath, errors) {
831
- const { keyType, path } = fieldPath;
832
- const tryExtractKeyFromItem = (item) => {
833
- let value = item;
834
- for (const pathItem of path) {
835
- const fieldName = pathItem.name;
836
- if (value.kind === "literal" && fieldName.text === "kind") {
837
- // An enum constant.
838
- return value;
839
- }
840
- if (value.kind !== "object") {
841
- // An error was already registered.
842
- return undefined;
843
- }
844
- const entry = value.entries[fieldName.text];
845
- if (!entry) {
846
- errors.push({
847
- token: value.token,
848
- message: `Missing entry: ${fieldName.text}`,
849
- });
850
- return;
851
- }
852
- value = entry.value;
853
- }
854
- return value;
855
- };
856
- const keyIdentityToKeys = new Map();
857
- for (const item of items) {
858
- const key = tryExtractKeyFromItem(item);
859
- if (!key) {
860
- return;
861
- }
862
- if (key.kind !== "literal") {
863
- // Cannot happen.
864
- return;
865
- }
866
- let keyIdentity;
867
- const keyToken = key.token.text;
868
- if (keyType.kind === "primitive") {
869
- const { primitive } = keyType;
870
- if (!valueHasPrimitiveType(keyToken, primitive)) {
871
- continue;
872
- }
873
- keyIdentity = literalValueToIdentity(keyToken, primitive);
874
- }
875
- else {
876
- // The key is an enum, use the enum field name as the key identity.
877
- if (!isStringLiteral(keyToken)) {
878
- continue;
879
- }
880
- keyIdentity = unquoteAndUnescape(keyToken);
881
- }
882
- if (keyIdentityToKeys.has(keyIdentity)) {
883
- keyIdentityToKeys.get(keyIdentity).push(key);
884
- }
885
- else {
886
- keyIdentityToKeys.set(keyIdentity, [key]);
887
- }
888
- }
889
- // Verify that every key in `keyIdentityToItems` has a single value.
890
- for (const duplicateKeys of keyIdentityToKeys.values()) {
891
- if (duplicateKeys.length <= 1) {
892
- continue;
893
- }
894
- for (const key of duplicateKeys) {
895
- errors.push({
896
- token: key.token,
897
- message: "Duplicate key",
898
- });
899
- }
900
- }
901
- }
902
- class TypeResolver {
903
- constructor(module, moduleBundles,
904
- /** For automatic imports in completion mode. */
905
- topLevelNameToRecordLocations, usedImports, errors) {
906
- this.module = module;
907
- this.moduleBundles = moduleBundles;
908
- this.topLevelNameToRecordLocations = topLevelNameToRecordLocations;
909
- this.usedImports = usedImports;
910
- this.errors = errors;
911
- }
912
- resolve(input, recordOrigin) {
913
- switch (input.kind) {
914
- case "primitive":
915
- return input;
916
- case "array": {
917
- const item = this.resolve(input.item, recordOrigin);
918
- if (!item) {
919
- return undefined;
920
- }
921
- return { kind: "array", item: item, key: input.key };
922
- }
923
- case "optional": {
924
- const value = this.resolve(input.other, recordOrigin);
925
- if (!value) {
926
- return undefined;
927
- }
928
- return { kind: "optional", other: value };
929
- }
930
- case "record": {
931
- return this.resolveRecordRef(input, recordOrigin);
932
- }
933
- }
934
- }
935
- /**
936
- * Finds the definition of the actual record referenced from a value type.
937
- * This is where we implement the name resolution algorithm.
938
- */
939
- resolveRecordRef(recordRef, recordOrigin) {
940
- const firstNamePart = recordRef.nameParts[0];
941
- const makeNotARecordError = (name) => ({
942
- token: name,
943
- message: "Does not refer to a struct or an enum",
944
- });
945
- const makeCannotFindNameError = (name, expectedNames) => ({
946
- token: name,
947
- message: `Cannot find name '${name.text}'`,
948
- expectedNames: expectedNames,
949
- });
950
- const { errors, module, moduleBundles, usedImports } = this;
951
- // The most nested record/module which contains the first name in the record
952
- // reference, or the module if the record reference is absolute (starts with
953
- // a dot).
954
- let start;
955
- const expectedNamesCollector = new ExpectedNamesCollector();
956
- if (recordOrigin !== "top-level" && !recordRef.absolute) {
957
- // Traverse the chain of ancestors from most nested to top-level.
958
- for (const fromRecord of [...recordOrigin.recordAncestors].reverse()) {
959
- const matchMaybe = fromRecord.nameToDeclaration[firstNamePart.text];
960
- if (matchMaybe && matchMaybe.kind === "record") {
961
- start = fromRecord;
962
- break;
963
- }
964
- else {
965
- expectedNamesCollector.collect(declarationsToExpectedNames(fromRecord.nameToDeclaration, (d) => d.kind === "record"));
966
- }
967
- }
968
- }
969
- if (start === undefined) {
970
- const maybeMatch = module.nameToDeclaration[firstNamePart.text];
971
- if (!maybeMatch) {
972
- expectedNamesCollector.collect(declarationsToExpectedNames(module.nameToDeclaration, (d) => d.kind === "record" ||
973
- d.kind === "import" ||
974
- d.kind === "import-alias"));
975
- // For automatic imports.
976
- expectedNamesCollector.collect(this.topLevelNameToRecordLocations
977
- ?.get(firstNamePart.text)
978
- ?.filter((l) => l.modulePath !== module.path) ?? []);
979
- errors.push(makeCannotFindNameError(firstNamePart, expectedNamesCollector.expectedNames));
980
- return undefined;
981
- }
982
- start = module;
983
- }
984
- let it = start;
985
- const nameParts = [];
986
- for (let i = 0; i < recordRef.nameParts.length; ++i) {
987
- const namePart = recordRef.nameParts[i];
988
- const name = namePart.text;
989
- let newIt = it.nameToDeclaration[name];
990
- if (newIt === undefined) {
991
- errors.push(makeCannotFindNameError(namePart, declarationsToExpectedNames(it.nameToDeclaration, (d) => d.kind === "record" ||
992
- (i === 0 && d.kind === "import-alias") ||
993
- (i === 0 && d.kind === "import"))));
994
- return undefined;
995
- }
996
- else if (newIt.kind === "record") {
997
- it = newIt;
998
- }
999
- else if (newIt.kind === "import" || newIt.kind === "import-alias") {
1000
- const transitiveImportError = () => ({
1001
- token: namePart,
1002
- message: "Cannot refer to imports of imported module",
1003
- });
1004
- if (i !== 0) {
1005
- errors.push(transitiveImportError());
1006
- return undefined;
1007
- }
1008
- usedImports.add(name);
1009
- const newModulePath = newIt.resolvedModulePath;
1010
- if (newModulePath === undefined) {
1011
- return undefined;
1012
- }
1013
- const newModuleResult = moduleBundles.get(newModulePath);
1014
- if (!newModuleResult) {
1015
- // The module was not found or has errors: an error was already
1016
- // registered, no need to register a new one.
1017
- return undefined;
1018
- }
1019
- const newModule = newModuleResult.module.result;
1020
- if (newIt.kind === "import") {
1021
- newIt = newModule.nameToDeclaration[name];
1022
- if (!newIt) {
1023
- errors.push(makeCannotFindNameError(namePart, declarationsToExpectedNames(newModule.nameToDeclaration, (d) => d.kind === "record")));
1024
- return undefined;
1025
- }
1026
- if (newIt.kind !== "record") {
1027
- this.errors.push(newIt.kind === "import" || newIt.kind === "import-alias"
1028
- ? transitiveImportError()
1029
- : makeNotARecordError(namePart));
1030
- return undefined;
1031
- }
1032
- it = newIt;
1033
- }
1034
- else {
1035
- it = newModule;
1036
- }
1037
- }
1038
- else {
1039
- this.errors.push(makeNotARecordError(namePart));
1040
- return undefined;
1041
- }
1042
- nameParts.push({ token: namePart, declaration: newIt });
1043
- }
1044
- if (it.kind !== "record") {
1045
- const name = recordRef.nameParts[0];
1046
- this.errors.push(makeNotARecordError(name));
1047
- return undefined;
1048
- }
1049
- return {
1050
- kind: "record",
1051
- key: it.key,
1052
- recordType: it.recordType,
1053
- nameParts: nameParts,
1054
- refToken: recordRef.nameParts.at(-1),
1055
- };
1056
- }
1057
- }
1058
- class Cache {
1059
- constructor(modulePathToNewContent, modulePathToOldBundle, registry) {
1060
- this.registry = registry;
1061
- this.modulePathToCacheResult = new Map();
1062
- const unchangedModulePaths = new Set();
1063
- for (const [modulePath, newContent] of modulePathToNewContent) {
1064
- const oldBundle = modulePathToOldBundle.get(modulePath);
1065
- if (oldBundle?.tokens.result.sourceCode === newContent) {
1066
- unchangedModulePaths.add(modulePath);
1067
- }
1068
- }
1069
- const classify = (modulePath) => {
1070
- const oldBundle = modulePathToOldBundle.get(modulePath);
1071
- if (!oldBundle) {
1072
- return { kind: "no-cache" };
1073
- }
1074
- {
1075
- const inMap = this.modulePathToCacheResult.get(modulePath);
1076
- if (inMap) {
1077
- return inMap;
1078
- }
1079
- }
1080
- let result;
1081
- const newContent = modulePathToNewContent.get(modulePath);
1082
- if (newContent === oldBundle.tokens.result.sourceCode) {
1083
- // Assume best case, may downgrade later.
1084
- this.modulePathToCacheResult.set(modulePath, {
1085
- kind: "module-bundle",
1086
- bundle: oldBundle,
1087
- });
1088
- const directDependencies = Object.keys(oldBundle.module.result?.pathToImportedNames);
1089
- result = directDependencies.every((dep) => classify(dep).kind === "module-bundle")
1090
- ? { kind: "module-bundle", bundle: oldBundle }
1091
- : { kind: "module-tokens", tokens: oldBundle.tokens };
1092
- }
1093
- else {
1094
- result = { kind: "no-cache" };
1095
- }
1096
- this.modulePathToCacheResult.set(modulePath, result);
1097
- return result;
1098
- };
1099
- for (const modulePath of modulePathToOldBundle.keys()) {
1100
- classify(modulePath);
1101
- }
1102
- }
1103
- getModuleCacheResult(modulePath) {
1104
- return this.modulePathToCacheResult.get(modulePath) ?? { kind: "no-cache" };
1105
- }
1106
- }
1107
- /** Registry of declarations possibly across multiple modules. */
1108
- class DeclarationRegistry {
1109
- constructor() {
1110
- this.recordMap = new Map();
1111
- this.numberToRecords = new Map();
1112
- this.numberToMethods = new Map();
1113
- /**
1114
- * Key: name of a record defined at the top level of a module
1115
- * Value: array of record locations (record name, doc, module path)
1116
- */
1117
- this.topLevelNameToRecordLocations = new Map();
1118
- }
1119
- mergeFrom(other) {
1120
- for (const [key, value] of other.recordMap) {
1121
- this.recordMap.set(key, value);
1122
- }
1123
- for (const [number, value] of other.numberToRecords) {
1124
- let existing = this.numberToRecords.get(number);
1125
- if (!existing) {
1126
- existing = [];
1127
- this.numberToRecords.set(number, existing);
1128
- }
1129
- existing.push(...value);
1130
- }
1131
- for (const [number, value] of other.numberToMethods) {
1132
- let existing = this.numberToMethods.get(number);
1133
- if (!existing) {
1134
- existing = [];
1135
- this.numberToMethods.set(number, existing);
1136
- }
1137
- existing.push(...value);
1138
- }
1139
- for (const [topLevelName, recordLocations,] of other.topLevelNameToRecordLocations) {
1140
- let existing = this.topLevelNameToRecordLocations.get(topLevelName);
1141
- if (!existing) {
1142
- existing = [];
1143
- this.topLevelNameToRecordLocations.set(topLevelName, existing);
1144
- }
1145
- existing.push(...recordLocations);
1146
- }
1147
- }
1148
- pushNumberRecord(number, record) {
1149
- let value = this.numberToRecords.get(number);
1150
- if (!value) {
1151
- value = [];
1152
- this.numberToRecords.set(number, value);
1153
- }
1154
- value.push(record);
1155
- }
1156
- pushNumberMethod(number, method) {
1157
- let value = this.numberToMethods.get(number);
1158
- if (!value) {
1159
- value = [];
1160
- this.numberToMethods.set(number, value);
1161
- }
1162
- value.push(method);
1163
- }
1164
- pushTopLevelNames(recordLocations) {
1165
- for (const recordName of recordLocations) {
1166
- let value = this.topLevelNameToRecordLocations.get(recordName.name);
1167
- if (!value) {
1168
- value = [];
1169
- this.topLevelNameToRecordLocations.set(recordName.name, value);
1170
- }
1171
- value.push(recordName);
1172
- }
1173
- }
1174
- }
1175
- class ModuleBundle {
1176
- constructor(tokens, module) {
1177
- this.tokens = tokens;
1178
- this.module = module;
1179
- /**
1180
- * Registry of declarations found in this module only.
1181
- * Will be merged into the "global" registry.
1182
- */
1183
- this.registry = new DeclarationRegistry();
1184
- }
1185
- }
1186
- function ensureAllImportsAreUsed(module, usedImports, errors) {
1187
- for (const declaration of module.declarations) {
1188
- if (declaration.kind === "import") {
1189
- for (const importedName of declaration.importedNames) {
1190
- if (!usedImports.has(importedName.text)) {
1191
- errors.push({
1192
- token: importedName,
1193
- message: "Unused import",
1194
- });
1195
- }
1196
- }
1197
- }
1198
- else if (declaration.kind === "import-alias") {
1199
- if (!usedImports.has(declaration.name.text)) {
1200
- errors.push({
1201
- token: declaration.name,
1202
- message: "Unused import alias",
1203
- });
1204
- }
1205
- }
1206
- }
1207
- }
1208
- function posixDirname(p) {
1209
- const i = p.lastIndexOf("/");
1210
- return i >= 0 ? p.slice(0, i) : "";
1211
- }
1212
- function posixJoin(base, rel) {
1213
- const parts = (base + "/" + rel).split("/");
1214
- const result = [];
1215
- for (const part of parts) {
1216
- if (part === "..") {
1217
- if (result.length > 0 && result[result.length - 1] !== "..") {
1218
- result.pop();
1219
- }
1220
- else {
1221
- result.push("..");
1222
- }
1223
- }
1224
- else if (part !== "." && part !== "") {
1225
- result.push(part);
1226
- }
1227
- }
1228
- return result.join("/");
1229
- }
1230
- function posixRelative(from, to) {
1231
- const fromParts = from === "" ? [] : from.split("/");
1232
- const toParts = to === "" ? [] : to.split("/");
1233
- let commonLen = 0;
1234
- while (commonLen < fromParts.length &&
1235
- commonLen < toParts.length &&
1236
- fromParts[commonLen] === toParts[commonLen]) {
1237
- commonLen++;
1238
- }
1239
- const upCount = fromParts.length - commonLen;
1240
- const downParts = toParts.slice(commonLen);
1241
- return [...Array(upCount).fill(".."), ...downParts].join("/");
1242
- }
1243
- /**
1244
- * Returns suggested module paths for import auto-completion.
1245
- *
1246
- * Given the partial path the user has typed, returns suggestions from
1247
- * `modulePathToContent`. For paths where there is more after the matched
1248
- * segment, the suggestion is truncated at the next "/" and a trailing "/" is
1249
- * appended to signal that it is a directory, not a file.
1250
- *
1251
- * Handles both absolute paths (e.g. "bb/ee") and relative paths (e.g.
1252
- * "./other", "../sibling").
1253
- */
1254
- function suggestModulePaths(typedPath, originModulePath, modulePathToContent) {
1255
- const isRelative = typedPath.startsWith("./") || typedPath.startsWith("../");
1256
- // Compute the absolute path prefix to match against all module paths.
1257
- let absolutePrefix;
1258
- if (isRelative) {
1259
- // Split at the last "/" so that the directory component (fully typed) can
1260
- // be resolved cleanly, while the tail (partial filename/dir prefix) is
1261
- // appended afterwards.
1262
- const lastSlash = typedPath.lastIndexOf("/");
1263
- const dirComponent = typedPath.slice(0, lastSlash + 1);
1264
- const filePrefix = typedPath.slice(lastSlash + 1);
1265
- // Append a dummy filename so Paths.join/normalize treat the directory
1266
- // component as a file path (avoids trailing-slash edge-cases), then strip
1267
- // it off again.
1268
- const dummy = dirComponent + "__dummy__";
1269
- const resolvedDummy = posixJoin(posixDirname(originModulePath), dummy.startsWith("./") ? dummy.slice(2) : dummy);
1270
- const absoluteDir = resolvedDummy.slice(0, resolvedDummy.length - "__dummy__".length);
1271
- absolutePrefix = absoluteDir + filePrefix;
1272
- }
1273
- else if (originModulePath.startsWith("@") && !typedPath.startsWith("@")) {
1274
- absolutePrefix = extractPackagePrefix(originModulePath) + typedPath;
1275
- }
1276
- else {
1277
- absolutePrefix = typedPath;
1278
- }
1279
- absolutePrefix = absolutePrefix.replace(/\\/g, "/");
1280
- if (absolutePrefix.startsWith("../")) {
1281
- // Typed path escapes the root; no valid suggestions.
1282
- return [];
1283
- }
1284
- const originBaseDir = posixDirname(originModulePath);
1285
- const suggestions = new Set();
1286
- for (const path of modulePathToContent) {
1287
- if (!path.startsWith(absolutePrefix))
1288
- continue;
1289
- if (path === originModulePath)
1290
- continue;
1291
- // When the user typed a relative path, never suggest modules that live
1292
- // under a package root ("@...") — those must be imported with an absolute
1293
- // path.
1294
- if (isRelative && path.startsWith("@"))
1295
- continue;
1296
- const remaining = path.slice(absolutePrefix.length);
1297
- const slashIndex = remaining.indexOf("/");
1298
- // If there is a sub-path after the matched prefix, collapse to the next
1299
- // directory segment and append "/". Otherwise use the full path.
1300
- const absoluteSuggestion = slashIndex >= 0
1301
- ? absolutePrefix + remaining.slice(0, slashIndex + 1)
1302
- : path;
1303
- if (isRelative) {
1304
- const isDir = absoluteSuggestion.endsWith("/");
1305
- const absPath = isDir
1306
- ? absoluteSuggestion.slice(0, -1)
1307
- : absoluteSuggestion;
1308
- let rel = posixRelative(originBaseDir, absPath);
1309
- // Paths.relative returns "" when both paths are the same; normalise to
1310
- // "." so the subsequent prefix-check and directory-slash logic work
1311
- // correctly (avoids producing the invalid path ".//" for same-dir cases).
1312
- if (rel === "") {
1313
- rel = ".";
1314
- }
1315
- if (!rel.startsWith(".")) {
1316
- rel = "./" + rel;
1317
- }
1318
- const suggestion = isDir ? rel + "/" : rel;
1319
- // When the user typed "../", don't suggest "./" — they would have typed
1320
- // "./" directly if they wanted files in their own directory.
1321
- if (typedPath.startsWith("../") && suggestion === "./")
1322
- continue;
1323
- suggestions.add(suggestion);
1324
- }
1325
- else {
1326
- suggestions.add(absoluteSuggestion);
1327
- }
1328
- }
1329
- return [...suggestions].map((name) => ({ name }));
1330
- }
1331
- //# sourceMappingURL=module_set.js.map