skir 0.0.1 → 0.0.2

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 (115) hide show
  1. package/dist/casing.d.ts +1 -4
  2. package/dist/casing.d.ts.map +1 -1
  3. package/dist/casing.js +0 -29
  4. package/dist/casing.js.map +1 -1
  5. package/dist/casing.test.js +2 -13
  6. package/dist/casing.test.js.map +1 -1
  7. package/dist/command_line_parser.d.ts +3 -3
  8. package/dist/command_line_parser.d.ts.map +1 -1
  9. package/dist/command_line_parser.js +35 -38
  10. package/dist/command_line_parser.js.map +1 -1
  11. package/dist/command_line_parser.test.js +73 -78
  12. package/dist/command_line_parser.test.js.map +1 -1
  13. package/dist/compatibility_checker.d.ts +9 -3
  14. package/dist/compatibility_checker.d.ts.map +1 -1
  15. package/dist/compatibility_checker.js +17 -4
  16. package/dist/compatibility_checker.js.map +1 -1
  17. package/dist/compatibility_checker.test.js +55 -1
  18. package/dist/compatibility_checker.test.js.map +1 -1
  19. package/dist/compiler.js +34 -17
  20. package/dist/compiler.js.map +1 -1
  21. package/dist/config.d.ts +5 -35
  22. package/dist/config.d.ts.map +1 -1
  23. package/dist/definition_finder.d.ts +1 -1
  24. package/dist/definition_finder.d.ts.map +1 -1
  25. package/dist/doc_comment_parser.d.ts +3 -0
  26. package/dist/doc_comment_parser.d.ts.map +1 -0
  27. package/dist/doc_comment_parser.js +219 -0
  28. package/dist/doc_comment_parser.js.map +1 -0
  29. package/dist/doc_comment_parser.test.d.ts +2 -0
  30. package/dist/doc_comment_parser.test.d.ts.map +1 -0
  31. package/dist/doc_comment_parser.test.js +494 -0
  32. package/dist/doc_comment_parser.test.js.map +1 -0
  33. package/dist/error_renderer.d.ts +1 -1
  34. package/dist/error_renderer.d.ts.map +1 -1
  35. package/dist/error_renderer.js +84 -65
  36. package/dist/error_renderer.js.map +1 -1
  37. package/dist/formatter.d.ts +15 -2
  38. package/dist/formatter.d.ts.map +1 -1
  39. package/dist/formatter.js +191 -234
  40. package/dist/formatter.js.map +1 -1
  41. package/dist/formatter.test.js +322 -88
  42. package/dist/formatter.test.js.map +1 -1
  43. package/dist/index.d.ts +1 -5
  44. package/dist/index.d.ts.map +1 -1
  45. package/dist/index.js +1 -4
  46. package/dist/index.js.map +1 -1
  47. package/dist/language_server.js +1 -1
  48. package/dist/language_server.js.map +1 -1
  49. package/dist/literals.d.ts +1 -2
  50. package/dist/literals.d.ts.map +1 -1
  51. package/dist/literals.js +1 -12
  52. package/dist/literals.js.map +1 -1
  53. package/dist/literals.test.js +1 -4
  54. package/dist/literals.test.js.map +1 -1
  55. package/dist/module_set.d.ts +3 -7
  56. package/dist/module_set.d.ts.map +1 -1
  57. package/dist/module_set.js +205 -51
  58. package/dist/module_set.js.map +1 -1
  59. package/dist/module_set.test.js +595 -28
  60. package/dist/module_set.test.js.map +1 -1
  61. package/dist/parser.d.ts +3 -4
  62. package/dist/parser.d.ts.map +1 -1
  63. package/dist/parser.js +185 -92
  64. package/dist/parser.js.map +1 -1
  65. package/dist/parser.test.js +243 -15
  66. package/dist/parser.test.js.map +1 -1
  67. package/dist/project_initializer.d.ts +2 -0
  68. package/dist/project_initializer.d.ts.map +1 -0
  69. package/dist/project_initializer.js +30 -0
  70. package/dist/project_initializer.js.map +1 -0
  71. package/dist/snapshotter.d.ts +3 -0
  72. package/dist/snapshotter.d.ts.map +1 -1
  73. package/dist/snapshotter.js +43 -6
  74. package/dist/snapshotter.js.map +1 -1
  75. package/dist/tokenizer.d.ts +8 -2
  76. package/dist/tokenizer.d.ts.map +1 -1
  77. package/dist/tokenizer.js +26 -20
  78. package/dist/tokenizer.js.map +1 -1
  79. package/dist/tokenizer.test.js +285 -269
  80. package/dist/tokenizer.test.js.map +1 -1
  81. package/package.json +7 -5
  82. package/src/casing.ts +1 -36
  83. package/src/command_line_parser.ts +42 -48
  84. package/src/compatibility_checker.ts +29 -7
  85. package/src/compiler.ts +35 -18
  86. package/src/definition_finder.ts +1 -1
  87. package/src/doc_comment_parser.ts +246 -0
  88. package/src/error_renderer.ts +90 -66
  89. package/src/formatter.ts +249 -238
  90. package/src/index.ts +0 -6
  91. package/src/language_server.ts +8 -8
  92. package/src/literals.ts +5 -14
  93. package/src/module_set.ts +259 -79
  94. package/src/parser.ts +213 -98
  95. package/src/project_initializer.ts +39 -0
  96. package/src/snapshotter.ts +46 -5
  97. package/src/tokenizer.ts +47 -25
  98. package/dist/encoding.d.ts +0 -2
  99. package/dist/encoding.d.ts.map +0 -1
  100. package/dist/encoding.js +0 -38
  101. package/dist/encoding.js.map +0 -1
  102. package/dist/encoding.test.d.ts +0 -2
  103. package/dist/encoding.test.d.ts.map +0 -1
  104. package/dist/encoding.test.js +0 -23
  105. package/dist/encoding.test.js.map +0 -1
  106. package/dist/index.test.d.ts +0 -2
  107. package/dist/index.test.d.ts.map +0 -1
  108. package/dist/index.test.js +0 -14
  109. package/dist/index.test.js.map +0 -1
  110. package/dist/types.d.ts +0 -375
  111. package/dist/types.d.ts.map +0 -1
  112. package/dist/types.js +0 -2
  113. package/dist/types.js.map +0 -1
  114. package/src/encoding.ts +0 -32
  115. package/src/types.ts +0 -518
package/src/module_set.ts CHANGED
@@ -1,40 +1,44 @@
1
1
  import * as paths from "path";
2
+ import {
3
+ unquoteAndUnescape,
4
+ type DenseJson,
5
+ type ErrorSink,
6
+ type FieldPath,
7
+ type Import,
8
+ type ImportAlias,
9
+ type Method,
10
+ type Module,
11
+ type MutableArrayType,
12
+ type MutableConstant,
13
+ type MutableDocReference,
14
+ type MutableMethod,
15
+ type MutableModule,
16
+ type MutableRecord,
17
+ type MutableRecordField,
18
+ type MutableRecordLocation,
19
+ type MutableResolvedType,
20
+ type MutableValue,
21
+ type Record,
22
+ type RecordKey,
23
+ type RecordLocation,
24
+ type ResolvedRecordRef,
25
+ type ResolvedType,
26
+ type Result,
27
+ type SkirError,
28
+ type Token,
29
+ type UnresolvedRecordRef,
30
+ type UnresolvedType,
31
+ type Value,
32
+ } from "skir-internal";
2
33
  import { FileReader } from "./io.js";
3
34
  import {
4
35
  isStringLiteral,
5
36
  literalValueToDenseJson,
6
37
  literalValueToIdentity,
7
- unquoteAndUnescape,
8
38
  valueHasPrimitiveType,
9
39
  } from "./literals.js";
10
40
  import { parseModule } from "./parser.js";
11
41
  import { tokenizeModule } from "./tokenizer.js";
12
- import type {
13
- DenseJson,
14
- ErrorSink,
15
- FieldPath,
16
- Import,
17
- ImportAlias,
18
- Method,
19
- Module,
20
- MutableArrayType,
21
- MutableModule,
22
- MutableRecord,
23
- MutableRecordLocation,
24
- MutableResolvedType,
25
- MutableValue,
26
- Record,
27
- RecordKey,
28
- RecordLocation,
29
- ResolvedRecordRef,
30
- ResolvedType,
31
- Result,
32
- SkirError,
33
- Token,
34
- UnresolvedRecordRef,
35
- UnresolvedType,
36
- Value,
37
- } from "./types.js";
38
42
 
39
43
  export class ModuleSet {
40
44
  static create(fileReader: FileReader, rootPath: string): ModuleSet {
@@ -42,7 +46,11 @@ export class ModuleSet {
42
46
  }
43
47
 
44
48
  static fromMap(map: ReadonlyMap<string, string>): ModuleSet {
45
- return new ModuleSet(new MapBasedModuleParser(map));
49
+ const result = new ModuleSet(new MapBasedModuleParser(map));
50
+ for (const modulePath of map.keys()) {
51
+ result.parseAndResolve(modulePath);
52
+ }
53
+ return result;
46
54
  }
47
55
 
48
56
  constructor(private readonly moduleParser: ModuleParser) {}
@@ -141,6 +149,29 @@ export class ModuleSet {
141
149
  errorIsInOtherModule: true,
142
150
  });
143
151
  }
152
+ } else if (declaration.kind === "import") {
153
+ // Make sure that the symbols we are importing exist in the imported
154
+ // module and are not imported symbols themselves.
155
+ for (const importedName of declaration.importedNames) {
156
+ const importedDeclaration =
157
+ otherModule.result.nameToDeclaration[importedName.text];
158
+ if (importedDeclaration === undefined) {
159
+ errors.push({
160
+ token: importedName,
161
+ message: "Not found",
162
+ });
163
+ } else if (importedDeclaration.kind === "import") {
164
+ errors.push({
165
+ token: importedName,
166
+ message: "Cannot reimport imported record",
167
+ });
168
+ } else if (importedDeclaration.kind !== "record") {
169
+ errors.push({
170
+ token: importedName,
171
+ message: "Not a record",
172
+ });
173
+ }
174
+ }
144
175
  }
145
176
  }
146
177
 
@@ -232,7 +263,6 @@ export class ModuleSet {
232
263
  const usedImports = new Set<string>();
233
264
  const typeResolver = new TypeResolver(
234
265
  module,
235
- modulePath,
236
266
  this.modules,
237
267
  usedImports,
238
268
  errors,
@@ -248,16 +278,25 @@ export class ModuleSet {
248
278
  // type depends on the record where the field is defined.
249
279
  // Store the result in the Field object.
250
280
  this.storeFieldRecursivity(record);
251
- // If the record has explicit numbering, register an error if any field
252
- // has a direct dependency on a record with implicit numbering.
253
- this.verifyNumberingConstraint(record, errors);
254
281
  // Verify that the `key` field of every array type is valid.
255
282
  for (const field of record.fields) {
256
283
  const { type } = field;
257
284
  if (type) {
258
285
  this.validateArrayKeys(type, errors);
259
286
  }
287
+ // Resolve the references in the doc comments of the field.
288
+ this.resolveDocReferences(
289
+ {
290
+ kind: "field",
291
+ field: field,
292
+ record: record,
293
+ },
294
+ module,
295
+ errors,
296
+ );
260
297
  }
298
+ // Resolve the references in the doc comments of the record.
299
+ this.resolveDocReferences(record, module, errors);
261
300
  }
262
301
  // Resolve every request/response type of every method in the module.
263
302
  // Store the result in the Method object.
@@ -290,6 +329,8 @@ export class ModuleSet {
290
329
  message: `Same number as ${otherMethodName} in ${otherModulePath}`,
291
330
  });
292
331
  }
332
+ // Resolve the references in the doc comments of the method.
333
+ this.resolveDocReferences(method, module, errors);
293
334
  }
294
335
  // Resolve every constant type. Store the result in the constant object.
295
336
  for (const constant of module.constants) {
@@ -301,6 +342,8 @@ export class ModuleSet {
301
342
  constant.valueAsDenseJson = //
302
343
  this.valueToDenseJson(constant.value, type, errors);
303
344
  }
345
+ // Resolve the references in the doc comments of the constant.
346
+ this.resolveDocReferences(constant, module, errors);
304
347
  }
305
348
 
306
349
  ensureAllImportsAreUsed(module, usedImports, errors);
@@ -371,46 +414,6 @@ export class ModuleSet {
371
414
  }
372
415
  }
373
416
 
374
- /**
375
- * If the record has explicit numbering, register an error if any field has a
376
- * direct dependency on a record with implicit numbering.
377
- */
378
- private verifyNumberingConstraint(record: Record, errors: ErrorSink): void {
379
- if (record.numbering !== "explicit") {
380
- return;
381
- }
382
- for (const field of record.fields) {
383
- if (!field.type) continue;
384
- const invalidRef = this.referencesImplicitlyNumberedRecord(field.type);
385
- if (invalidRef) {
386
- errors.push({
387
- token: invalidRef.refToken,
388
- message:
389
- `Field type references a ${invalidRef.recordType} with implicit ` +
390
- `numbering, but field belongs to a ${record.recordType} with ` +
391
- `explicit numbering`,
392
- });
393
- }
394
- }
395
- }
396
-
397
- private referencesImplicitlyNumberedRecord(
398
- input: ResolvedType,
399
- ): ResolvedRecordRef | false {
400
- switch (input.kind) {
401
- case "array":
402
- return this.referencesImplicitlyNumberedRecord(input.item);
403
- case "optional":
404
- return this.referencesImplicitlyNumberedRecord(input.other);
405
- case "primitive":
406
- return false;
407
- case "record": {
408
- const record = this.recordMap.get(input.key)!.record;
409
- return record.numbering === "implicit" && input;
410
- }
411
- }
412
- }
413
-
414
417
  /**
415
418
  * Verifies that the `key` field of every array type found in `topLevelType`
416
419
  * is valid. Populates the `keyType` field of every field path.
@@ -467,7 +470,7 @@ export class ModuleSet {
467
470
  if (fieldName.text !== "kind") {
468
471
  errors.push({
469
472
  token: fieldName,
470
- expected: '"kind"',
473
+ expected: "'kind'",
471
474
  });
472
475
  return undefined;
473
476
  }
@@ -799,6 +802,171 @@ export class ModuleSet {
799
802
  }
800
803
  }
801
804
 
805
+ /** Resolve the references in the doc comments of the given declaration. */
806
+ private resolveDocReferences(
807
+ documentee:
808
+ | MutableConstant
809
+ | MutableMethod
810
+ | MutableRecord
811
+ | MutableRecordField,
812
+ module: Module,
813
+ errors: ErrorSink,
814
+ ): void {
815
+ const doc =
816
+ documentee.kind === "field" ? documentee.field.doc : documentee.doc;
817
+
818
+ const docReferences = doc.pieces.filter(
819
+ (p): p is MutableDocReference => p.kind === "reference",
820
+ );
821
+ if (docReferences.length <= 0) {
822
+ return;
823
+ }
824
+
825
+ // Try to resolve a reference by looking it up in the given scope.
826
+ // Returns true if resolved, false otherwise.
827
+ const tryResolveReference = (
828
+ ref: MutableDocReference,
829
+ nameChain: readonly Token[],
830
+ scope: Record | Module,
831
+ ): boolean => {
832
+ if (ref.absolute && scope !== module) {
833
+ return false;
834
+ }
835
+ const firstName = nameChain[0]!;
836
+ const match = scope.nameToDeclaration[firstName.text];
837
+ if (!match) {
838
+ return false;
839
+ }
840
+ if (nameChain.length === 1) {
841
+ // Single name: must refer to a declaration.
842
+ switch (match.kind) {
843
+ case "constant":
844
+ case "method":
845
+ case "record": {
846
+ ref.referee = match;
847
+ return true;
848
+ }
849
+ case "field": {
850
+ if (scope.kind !== "record") {
851
+ throw new TypeError(scope.kind);
852
+ }
853
+ ref.referee = {
854
+ kind: "field",
855
+ field: match,
856
+ record: scope,
857
+ };
858
+ return true;
859
+ }
860
+ case "import":
861
+ case "import-alias":
862
+ case "removed":
863
+ return false;
864
+ }
865
+ } else {
866
+ // Multi-part name: first part must be a naming scope.
867
+ switch (match.kind) {
868
+ case "record":
869
+ return tryResolveReference(ref, nameChain.slice(1), match);
870
+ case "import":
871
+ case "import-alias": {
872
+ if (scope !== module) {
873
+ // Cannot refer to other module's imports.
874
+ return false;
875
+ }
876
+ const { resolvedModulePath } = match;
877
+ if (!resolvedModulePath) {
878
+ return false;
879
+ }
880
+ const importedModule = this.modules.get(resolvedModulePath!);
881
+ if (!importedModule?.result) {
882
+ return false;
883
+ }
884
+ const newNameChain = nameChain.slice(
885
+ match.kind === "import" ? 0 : 1,
886
+ );
887
+ return tryResolveReference(
888
+ ref,
889
+ newNameChain,
890
+ importedModule.result,
891
+ );
892
+ }
893
+ case "constant":
894
+ case "field":
895
+ case "method":
896
+ case "removed":
897
+ return false;
898
+ }
899
+ }
900
+ };
901
+
902
+ const { recordMap } = this;
903
+ // Build list of naming scopes to search, in order of priority.
904
+ const scopes: Array<Record | Module> = [];
905
+ const pushRecordAncestorsToScopes = (record: Record): void => {
906
+ const { key } = record;
907
+ const location = recordMap.get(key)!;
908
+ const ancestors = [...location.recordAncestors].reverse();
909
+ for (const ancestor of ancestors) {
910
+ scopes.push(ancestor);
911
+ }
912
+ };
913
+ const pushTypeToScopes = (type: ResolvedType | undefined): void => {
914
+ if (type) {
915
+ const recordKey = tryFindRecordForType(type);
916
+ if (recordKey) {
917
+ const { record } = recordMap.get(recordKey)!;
918
+ scopes.push(record);
919
+ }
920
+ }
921
+ };
922
+ switch (documentee.kind) {
923
+ case "constant": {
924
+ scopes.push(module);
925
+ pushTypeToScopes(documentee.type);
926
+ break;
927
+ }
928
+ case "field": {
929
+ const { field, record } = documentee;
930
+ pushRecordAncestorsToScopes(record);
931
+ scopes.push(module);
932
+ pushTypeToScopes(field.type);
933
+ break;
934
+ }
935
+ case "method": {
936
+ scopes.push(module);
937
+ pushTypeToScopes(documentee.requestType);
938
+ pushTypeToScopes(documentee.responseType);
939
+ break;
940
+ }
941
+ case "record": {
942
+ pushRecordAncestorsToScopes(documentee);
943
+ scopes.push(module);
944
+ break;
945
+ }
946
+ }
947
+
948
+ // Resolve each reference by searching through scopes in priority order.
949
+ for (const reference of docReferences) {
950
+ const { nameChain } = reference;
951
+ if (nameChain.length <= 0) {
952
+ continue;
953
+ }
954
+ let resolved = false;
955
+ for (const scope of scopes) {
956
+ if (tryResolveReference(reference, nameChain, scope)) {
957
+ resolved = true;
958
+ break;
959
+ }
960
+ }
961
+ if (!resolved) {
962
+ errors.push({
963
+ token: reference.referenceRange,
964
+ message: "Cannot resolve reference",
965
+ });
966
+ }
967
+ }
968
+ }
969
+
802
970
  private readonly modules = new Map<string, Result<Module | null>>();
803
971
  private readonly mutableRecordMap = new Map<RecordKey, RecordLocation>();
804
972
  private readonly mutableResolvedModules: MutableModule[] = [];
@@ -916,7 +1084,6 @@ function validateKeyedItems(
916
1084
  class TypeResolver {
917
1085
  constructor(
918
1086
  private readonly module: Module,
919
- private readonly modulePath: string,
920
1087
  private readonly modules: Map<string, Result<Module | null>>,
921
1088
  private readonly usedImports: Set<string>,
922
1089
  private readonly errors: ErrorSink,
@@ -1006,12 +1173,12 @@ class TypeResolver {
1006
1173
  } else if (newIt.kind === "record") {
1007
1174
  it = newIt;
1008
1175
  } else if (newIt.kind === "import" || newIt.kind === "import-alias") {
1009
- const cannotReimportError = (): SkirError => ({
1176
+ const transitiveImportError = (): SkirError => ({
1010
1177
  token: namePart,
1011
- message: `Cannot reimport imported name '${name}'`,
1178
+ message: "Cannot refer to imports of imported module",
1012
1179
  });
1013
1180
  if (i !== 0) {
1014
- errors.push(cannotReimportError());
1181
+ errors.push(transitiveImportError());
1015
1182
  return undefined;
1016
1183
  }
1017
1184
  usedImports.add(name);
@@ -1035,7 +1202,7 @@ class TypeResolver {
1035
1202
  if (!newIt || newIt.kind !== "record") {
1036
1203
  this.errors.push(
1037
1204
  newIt.kind === "import" || newIt.kind === "import-alias"
1038
- ? cannotReimportError()
1205
+ ? transitiveImportError()
1039
1206
  : makeNotARecordError(namePart),
1040
1207
  );
1041
1208
  return undefined;
@@ -1115,7 +1282,7 @@ abstract class ModuleParserBase implements ModuleParser {
1115
1282
  };
1116
1283
  }
1117
1284
 
1118
- return parseModule(tokens.result, modulePath, code);
1285
+ return parseModule(tokens.result);
1119
1286
  }
1120
1287
  }
1121
1288
 
@@ -1173,3 +1340,16 @@ function resolveModulePath(
1173
1340
  }
1174
1341
  return modulePath;
1175
1342
  }
1343
+
1344
+ function tryFindRecordForType(type: ResolvedType): RecordKey | null {
1345
+ switch (type.kind) {
1346
+ case "array":
1347
+ return tryFindRecordForType(type.item);
1348
+ case "optional":
1349
+ return tryFindRecordForType(type.other);
1350
+ case "record":
1351
+ return type.key;
1352
+ case "primitive":
1353
+ return null;
1354
+ }
1355
+ }