carrick 0.3.72 → 0.3.73

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 (31) hide show
  1. package/package.json +6 -6
  2. package/sidecar/dist/src/capture/anchors.d.ts +15 -0
  3. package/sidecar/dist/src/capture/anchors.js +70 -5
  4. package/sidecar/dist/src/capture/api.d.ts +39 -4
  5. package/sidecar/dist/src/capture/check-classify.d.ts +2 -0
  6. package/sidecar/dist/src/capture/check-classify.js +28 -0
  7. package/sidecar/dist/src/capture/check-deep.js +1 -1
  8. package/sidecar/dist/src/capture/check-probe.d.ts +1 -1
  9. package/sidecar/dist/src/capture/check-probe.js +16 -0
  10. package/sidecar/dist/src/capture/deep-walk.d.ts +33 -6
  11. package/sidecar/dist/src/capture/deep-walk.js +81 -28
  12. package/sidecar/dist/src/capture/index.js +16 -6
  13. package/sidecar/dist/src/capture/installed-package.d.ts +58 -0
  14. package/sidecar/dist/src/capture/installed-package.js +311 -0
  15. package/sidecar/dist/src/capture/lockfile.d.ts +8 -0
  16. package/sidecar/dist/src/capture/lockfile.js +1 -1
  17. package/sidecar/dist/src/capture/node-builder.d.ts +27 -0
  18. package/sidecar/dist/src/capture/node-builder.js +107 -1
  19. package/sidecar/dist/src/capture/paths-rewrite.d.ts +23 -6
  20. package/sidecar/dist/src/capture/paths-rewrite.js +43 -11
  21. package/sidecar/dist/src/capture/self-check.js +12 -3
  22. package/sidecar/dist/src/capture/unresolved.d.ts +28 -0
  23. package/sidecar/dist/src/capture/unresolved.js +107 -0
  24. package/sidecar/dist/src/type-inferrer.d.ts +108 -11
  25. package/sidecar/dist/src/type-inferrer.js +513 -90
  26. package/sidecar/dist/src/type-structural-expander.d.ts +23 -2
  27. package/sidecar/dist/src/type-structural-expander.js +55 -17
  28. package/sidecar/dist/src/type-text-canonicalizer.d.ts +14 -0
  29. package/sidecar/dist/src/type-text-canonicalizer.js +23 -2
  30. package/sidecar/dist/src/validators.d.ts +10 -0
  31. package/sidecar/dist/src/validators.js +1 -0
@@ -62,6 +62,17 @@ export interface MemberOverrides {
62
62
  readonly applied: Set<string>;
63
63
  readonly at: Node;
64
64
  }
65
+ /**
66
+ * The representation a type is printed in.
67
+ *
68
+ * `'declared'` prints the type the source declares. `'json'` prints what a
69
+ * JSON serialiser puts on the wire for a value of that type (carrick#1163):
70
+ * `JSON.stringify` calls a value's `toJSON()` and serialises its RESULT, so a
71
+ * `Date` travels as the string its `toJSON` returns, and so does any other
72
+ * type that declares one. The mapping is read from the compiler's own
73
+ * signature, never from a list of type names.
74
+ */
75
+ export type WireFormat = 'declared' | 'json';
65
76
  /**
66
77
  * Recursively render a `Type` as fully-inlined structural text.
67
78
  *
@@ -70,9 +81,19 @@ export interface MemberOverrides {
70
81
  * functions stay by name. The `seen` set (object type ids on the current
71
82
  * branch) breaks reference cycles; `depth` is a hard backstop. `overrides`
72
83
  * substitutes a type at named member positions (`MemberOverrides`); without
73
- * it the print is unchanged.
84
+ * it the print is unchanged. `wire` picks the representation (`WireFormat`).
85
+ */
86
+ export declare function expandTypeStructural(type: Type, seen?: Set<number>, depth?: number, overrides?: MemberOverrides, wire?: WireFormat): string;
87
+ /**
88
+ * The type `JSON.stringify` serialises in place of a value of `type`: the
89
+ * return type of the value's own `toJSON()`, or `undefined` when it declares
90
+ * none (carrick#1163).
91
+ *
92
+ * Only a callable `toJSON` member counts, and only a return type that says
93
+ * something: an `any` return describes nothing, and a `toJSON` that returns
94
+ * its own type again maps nothing either.
74
95
  */
75
- export declare function expandTypeStructural(type: Type, seen?: Set<number>, depth?: number, overrides?: MemberOverrides): string;
96
+ export declare function jsonWireType(type: Type): Type | undefined;
76
97
  /**
77
98
  * Non-expanded text for a type. Passes `undefined` as the enclosing node so
78
99
  * the compiler can't throw on an invalid node context (tuples and some
@@ -29,7 +29,7 @@
29
29
  * structural form rather than a dangling name.
30
30
  */
31
31
  import { ts } from 'ts-morph';
32
- import { canonicalizeUnionsInText } from './type-text-canonicalizer.js';
32
+ import { canonicalizeUnionsInText, foldBooleanLiterals, } from './type-text-canonicalizer.js';
33
33
  /**
34
34
  * Bound on the structural-expansion recursion. Deep enough for every realistic
35
35
  * request/response shape; a backstop against pathological/recursive types the
@@ -65,12 +65,43 @@ function childCursor(cursor, segment) {
65
65
  * functions stay by name. The `seen` set (object type ids on the current
66
66
  * branch) breaks reference cycles; `depth` is a hard backstop. `overrides`
67
67
  * substitutes a type at named member positions (`MemberOverrides`); without
68
- * it the print is unchanged.
68
+ * it the print is unchanged. `wire` picks the representation (`WireFormat`).
69
69
  */
70
- export function expandTypeStructural(type, seen = new Set(), depth = 0, overrides) {
71
- return expandAt(type, seen, depth, overrides ? { overrides, position: '' } : undefined);
70
+ export function expandTypeStructural(type, seen = new Set(), depth = 0, overrides, wire = 'declared') {
71
+ return expandAt(type, seen, depth, overrides ? { overrides, position: '' } : undefined, wire);
72
72
  }
73
- function expandAt(type, seen, depth, at) {
73
+ /**
74
+ * The type `JSON.stringify` serialises in place of a value of `type`: the
75
+ * return type of the value's own `toJSON()`, or `undefined` when it declares
76
+ * none (carrick#1163).
77
+ *
78
+ * Only a callable `toJSON` member counts, and only a return type that says
79
+ * something: an `any` return describes nothing, and a `toJSON` that returns
80
+ * its own type again maps nothing either.
81
+ */
82
+ export function jsonWireType(type) {
83
+ if (!type.isObject() || type.isArray() || isTuple(type))
84
+ return undefined;
85
+ const member = type.getProperty('toJSON');
86
+ if (!member)
87
+ return undefined;
88
+ const declaration = member.getValueDeclaration() ?? member.getDeclarations()[0];
89
+ const memberType = declaration
90
+ ? member.getTypeAtLocation(declaration)
91
+ : member.getDeclaredType();
92
+ const signature = memberType.getCallSignatures()[0];
93
+ if (!signature)
94
+ return undefined;
95
+ const serialised = signature.getReturnType();
96
+ if (serialised.isAny() || serialised.isUnknown())
97
+ return undefined;
98
+ const id = type.compilerType.id;
99
+ const serialisedId = serialised.compilerType.id;
100
+ if (id != null && id === serialisedId)
101
+ return undefined;
102
+ return serialised;
103
+ }
104
+ function expandAt(type, seen, depth, at, wire) {
74
105
  if (depth > MAX_EXPANSION_DEPTH)
75
106
  return backstopText(type);
76
107
  let cursor = at;
@@ -78,7 +109,7 @@ function expandAt(type, seen, depth, at) {
78
109
  const replacement = cursor.overrides.types.get(cursor.position);
79
110
  if (replacement) {
80
111
  cursor.overrides.applied.add(cursor.position);
81
- return expandAt(replacement, seen, depth, undefined);
112
+ return expandAt(replacement, seen, depth, undefined, wire);
82
113
  }
83
114
  // Nothing to substitute below here: print exactly as without overrides.
84
115
  if (!hasOverrideBelow(cursor))
@@ -102,10 +133,10 @@ function expandAt(type, seen, depth, at) {
102
133
  }
103
134
  // Unions / intersections: expand each member, in canonical order.
104
135
  if (type.isUnion()) {
105
- return canonicalMembers(type.getUnionTypes(), seen, depth, cursor).join(' | ');
136
+ return canonicalMembers(type.getUnionTypes(), seen, depth, cursor, wire).join(' | ');
106
137
  }
107
138
  if (type.isIntersection()) {
108
- return canonicalMembers(type.getIntersectionTypes(), seen, depth, cursor).join(' & ');
139
+ return canonicalMembers(type.getIntersectionTypes(), seen, depth, cursor, wire).join(' & ');
109
140
  }
110
141
  // Tuples are array-like but must keep their `[a, b]` shape, not be walked
111
142
  // as objects (which explodes into `Array.prototype`). Handle before arrays.
@@ -116,7 +147,7 @@ function expandAt(type, seen, depth, at) {
116
147
  const element = type.getArrayElementType();
117
148
  if (!element)
118
149
  return namedText(type);
119
- const inner = expandAt(element, seen, depth + 1, childCursor(cursor, '<0>'));
150
+ const inner = expandAt(element, seen, depth + 1, childCursor(cursor, '<0>'), wire);
120
151
  // Parenthesise a union/intersection element so `(A | B)[]` doesn't misparse
121
152
  // as `A | B[]`. Decide from the TYPE, not the string: a single object
122
153
  // literal like `{ a: A | B }` is NOT a union and must not be parenthesised,
@@ -124,6 +155,13 @@ function expandAt(type, seen, depth, at) {
124
155
  const needsParens = element.isUnion() || element.isIntersection();
125
156
  return needsParens ? `(${inner})[]` : `${inner}[]`;
126
157
  }
158
+ // On the JSON wire a value with `toJSON()` is its serialised form, library
159
+ // type or not, so this runs before the by-name bail-out below.
160
+ if (wire === 'json') {
161
+ const serialised = jsonWireType(type);
162
+ if (serialised)
163
+ return expandAt(serialised, seen, depth + 1, undefined, wire);
164
+ }
127
165
  // Library / built-in types (Date, Promise, RegExp, …): keep by name, unless
128
166
  // a member below has to be substituted — a schema library declares its
129
167
  // inferred object types itself, and they are only walkable, not by-name.
@@ -144,7 +182,7 @@ function expandAt(type, seen, depth, at) {
144
182
  const props = type.getProperties();
145
183
  if (props.length === 0)
146
184
  return namedText(type);
147
- const parts = props.map((prop) => expandProperty(prop, nextSeen, depth, cursor));
185
+ const parts = props.map((prop) => expandProperty(prop, nextSeen, depth, cursor, wire));
148
186
  return `{ ${parts.join('; ')}; }`;
149
187
  }
150
188
  return namedText(type);
@@ -203,8 +241,8 @@ function backstopText(type) {
203
241
  * Ties can only happen between two members that render identically, in which
204
242
  * case the joined output is the same whichever way round they go.
205
243
  */
206
- function canonicalMembers(members, seen, depth, cursor) {
207
- return orderMembers(members, (member) => expandAt(member, seen, depth + 1, cursor));
244
+ function canonicalMembers(members, seen, depth, cursor, wire) {
245
+ return orderMembers(members, (member) => expandAt(member, seen, depth + 1, cursor, wire));
208
246
  }
209
247
  /**
210
248
  * The canonical order itself, over whatever text `render` gives each member.
@@ -213,9 +251,9 @@ function canonicalMembers(members, seen, depth, cursor) {
213
251
  * cannot be ordered two ways depending on how deep it sits.
214
252
  */
215
253
  function orderMembers(members, render) {
216
- const rendered = members.map((member, index) => ({
254
+ const rendered = foldBooleanLiterals(members.map(render)).map((text, index) => ({
217
255
  index,
218
- text: render(member),
256
+ text,
219
257
  }));
220
258
  rendered.sort((a, b) => {
221
259
  if (a.text !== b.text)
@@ -225,7 +263,7 @@ function orderMembers(members, render) {
225
263
  return rendered.map((entry) => entry.text);
226
264
  }
227
265
  /** Render a single property as `name[?]: <expanded>`. */
228
- function expandProperty(prop, seen, depth, at) {
266
+ function expandProperty(prop, seen, depth, at, wire) {
229
267
  const optional = (prop.getFlags() & ts.SymbolFlags.Optional) !== 0;
230
268
  const propDecl = prop.getDeclarations()[0];
231
269
  // Render the key from the declaration's name node so quoted/computed keys
@@ -257,11 +295,11 @@ function expandProperty(prop, seen, depth, at) {
257
295
  propType = nonUndefined[0];
258
296
  }
259
297
  else if (nonUndefined.length > 1) {
260
- const inner = canonicalMembers(nonUndefined, seen, depth, cursor).join(' | ');
298
+ const inner = canonicalMembers(nonUndefined, seen, depth, cursor, wire).join(' | ');
261
299
  return `${name}?: ${inner}`;
262
300
  }
263
301
  }
264
- const inner = expandAt(propType, seen, depth + 1, cursor);
302
+ const inner = expandAt(propType, seen, depth + 1, cursor, wire);
265
303
  return `${name}${optional ? '?' : ''}: ${inner}`;
266
304
  }
267
305
  /**
@@ -38,3 +38,17 @@
38
38
  * a definition resolve must not fail because one member confused a scanner.
39
39
  */
40
40
  export declare function canonicalizeUnionsInText(text: string): string;
41
+ /**
42
+ * A union's rendered members with the literal pair `false`, `true` folded into
43
+ * `boolean` (carrick#1165).
44
+ *
45
+ * The compiler stores `boolean` as exactly that pair, and its own printer folds
46
+ * it back. Anything that renders a union member by member — the structural
47
+ * walk, and this file's reordering of a compiler print — sees the two literals
48
+ * separately, and sorting them apart produced `false | null | true` for
49
+ * `boolean | null`. Folding before the sort gives the one print a reader and a
50
+ * diff expect. A lone literal is a literal type and is kept. Shared by
51
+ * `orderMembers` in `type-structural-expander.ts`, so both halves of the normal
52
+ * form fold the same way.
53
+ */
54
+ export declare function foldBooleanLiterals(members: string[]): string[];
@@ -156,8 +156,8 @@ function canonicalizeMember(member) {
156
156
  * the compiler rendered it.
157
157
  */
158
158
  function orderTextMembers(members) {
159
- return members
160
- .map((text, index) => ({ text: text.trim(), index }))
159
+ return foldBooleanLiterals(members.map((text) => text.trim()))
160
+ .map((text, index) => ({ text, index }))
161
161
  .sort((a, b) => {
162
162
  if (a.text !== b.text)
163
163
  return a.text < b.text ? -1 : 1;
@@ -165,6 +165,27 @@ function orderTextMembers(members) {
165
165
  })
166
166
  .map((entry) => entry.text);
167
167
  }
168
+ /**
169
+ * A union's rendered members with the literal pair `false`, `true` folded into
170
+ * `boolean` (carrick#1165).
171
+ *
172
+ * The compiler stores `boolean` as exactly that pair, and its own printer folds
173
+ * it back. Anything that renders a union member by member — the structural
174
+ * walk, and this file's reordering of a compiler print — sees the two literals
175
+ * separately, and sorting them apart produced `false | null | true` for
176
+ * `boolean | null`. Folding before the sort gives the one print a reader and a
177
+ * diff expect. A lone literal is a literal type and is kept. Shared by
178
+ * `orderMembers` in `type-structural-expander.ts`, so both halves of the normal
179
+ * form fold the same way.
180
+ */
181
+ export function foldBooleanLiterals(members) {
182
+ const falseAt = members.indexOf('false');
183
+ const trueAt = members.indexOf('true');
184
+ if (falseAt === -1 || trueAt === -1)
185
+ return members;
186
+ const rest = members.filter((member) => member !== 'false' && member !== 'true');
187
+ return rest.includes('boolean') ? rest : [...rest, 'boolean'];
188
+ }
168
189
  /**
169
190
  * The spans between depth-zero occurrences of `separator`. Depth counts the
170
191
  * four bracket pairs; quoted runs are skipped whole, so a separator inside a
@@ -415,16 +415,19 @@ export declare const CaptureV2RequestSchema: z.ZodObject<{
415
415
  alias: z.ZodString;
416
416
  type_text: z.ZodString;
417
417
  anchor_origin: z.ZodEnum<["llm-symbol", "deterministic-infer", "anchor-backfill"]>;
418
+ source_file: z.ZodOptional<z.ZodString>;
418
419
  }, "strip", z.ZodTypeAny, {
419
420
  kind: "literal";
420
421
  alias: string;
421
422
  anchor_origin: "llm-symbol" | "deterministic-infer" | "anchor-backfill";
422
423
  type_text: string;
424
+ source_file?: string | undefined;
423
425
  }, {
424
426
  kind: "literal";
425
427
  alias: string;
426
428
  anchor_origin: "llm-symbol" | "deterministic-infer" | "anchor-backfill";
427
429
  type_text: string;
430
+ source_file?: string | undefined;
428
431
  }>]>, "many">;
429
432
  out_dir: z.ZodString;
430
433
  tsconfig_path: z.ZodOptional<z.ZodString>;
@@ -461,6 +464,7 @@ export declare const CaptureV2RequestSchema: z.ZodObject<{
461
464
  alias: string;
462
465
  anchor_origin: "llm-symbol" | "deterministic-infer" | "anchor-backfill";
463
466
  type_text: string;
467
+ source_file?: string | undefined;
464
468
  })[];
465
469
  out_dir: string;
466
470
  tsconfig_path?: string | undefined;
@@ -497,6 +501,7 @@ export declare const CaptureV2RequestSchema: z.ZodObject<{
497
501
  alias: string;
498
502
  anchor_origin: "llm-symbol" | "deterministic-infer" | "anchor-backfill";
499
503
  type_text: string;
504
+ source_file?: string | undefined;
500
505
  })[];
501
506
  out_dir: string;
502
507
  tsconfig_path?: string | undefined;
@@ -1476,16 +1481,19 @@ export declare const SidecarRequestSchema: z.ZodDiscriminatedUnion<"action", [z.
1476
1481
  alias: z.ZodString;
1477
1482
  type_text: z.ZodString;
1478
1483
  anchor_origin: z.ZodEnum<["llm-symbol", "deterministic-infer", "anchor-backfill"]>;
1484
+ source_file: z.ZodOptional<z.ZodString>;
1479
1485
  }, "strip", z.ZodTypeAny, {
1480
1486
  kind: "literal";
1481
1487
  alias: string;
1482
1488
  anchor_origin: "llm-symbol" | "deterministic-infer" | "anchor-backfill";
1483
1489
  type_text: string;
1490
+ source_file?: string | undefined;
1484
1491
  }, {
1485
1492
  kind: "literal";
1486
1493
  alias: string;
1487
1494
  anchor_origin: "llm-symbol" | "deterministic-infer" | "anchor-backfill";
1488
1495
  type_text: string;
1496
+ source_file?: string | undefined;
1489
1497
  }>]>, "many">;
1490
1498
  out_dir: z.ZodString;
1491
1499
  tsconfig_path: z.ZodOptional<z.ZodString>;
@@ -1522,6 +1530,7 @@ export declare const SidecarRequestSchema: z.ZodDiscriminatedUnion<"action", [z.
1522
1530
  alias: string;
1523
1531
  anchor_origin: "llm-symbol" | "deterministic-infer" | "anchor-backfill";
1524
1532
  type_text: string;
1533
+ source_file?: string | undefined;
1525
1534
  })[];
1526
1535
  out_dir: string;
1527
1536
  tsconfig_path?: string | undefined;
@@ -1558,6 +1567,7 @@ export declare const SidecarRequestSchema: z.ZodDiscriminatedUnion<"action", [z.
1558
1567
  alias: string;
1559
1568
  anchor_origin: "llm-symbol" | "deterministic-infer" | "anchor-backfill";
1560
1569
  type_text: string;
1570
+ source_file?: string | undefined;
1561
1571
  })[];
1562
1572
  out_dir: string;
1563
1573
  tsconfig_path?: string | undefined;
@@ -219,6 +219,7 @@ const CaptureAnchorRequestSchema = z.discriminatedUnion('kind', [
219
219
  alias: z.string().min(1),
220
220
  type_text: z.string().min(1),
221
221
  anchor_origin: AnchorOriginSchema,
222
+ source_file: z.string().min(1).optional(),
222
223
  }),
223
224
  ]);
224
225
  export const CaptureV2RequestSchema = BaseRequestSchema.extend({