carrick 0.3.70 → 0.3.72

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.
@@ -36,17 +36,54 @@ import { canonicalizeUnionsInText } from './type-text-canonicalizer.js';
36
36
  * cycle set somehow misses.
37
37
  */
38
38
  export const MAX_EXPANSION_DEPTH = 12;
39
+ /** True when some override sits strictly below `position`. */
40
+ function hasOverrideBelow({ overrides, position }) {
41
+ for (const key of overrides.types.keys()) {
42
+ if (position === ''
43
+ ? key !== ''
44
+ : key.startsWith(`${position}.`) || key.startsWith(`${position}<`)) {
45
+ return true;
46
+ }
47
+ }
48
+ return false;
49
+ }
50
+ function childCursor(cursor, segment) {
51
+ if (!cursor)
52
+ return undefined;
53
+ const position = segment.startsWith('<')
54
+ ? `${cursor.position}${segment}`
55
+ : cursor.position === ''
56
+ ? segment
57
+ : `${cursor.position}.${segment}`;
58
+ return { overrides: cursor.overrides, position };
59
+ }
39
60
  /**
40
61
  * Recursively render a `Type` as fully-inlined structural text.
41
62
  *
42
63
  * Named object/interface types are expanded to their member structure;
43
64
  * primitives, literals, library types (`Date`, `Promise`, tuples, …) and
44
65
  * functions stay by name. The `seen` set (object type ids on the current
45
- * branch) breaks reference cycles; `depth` is a hard backstop.
66
+ * branch) breaks reference cycles; `depth` is a hard backstop. `overrides`
67
+ * substitutes a type at named member positions (`MemberOverrides`); without
68
+ * it the print is unchanged.
46
69
  */
47
- export function expandTypeStructural(type, seen = new Set(), depth = 0) {
70
+ export function expandTypeStructural(type, seen = new Set(), depth = 0, overrides) {
71
+ return expandAt(type, seen, depth, overrides ? { overrides, position: '' } : undefined);
72
+ }
73
+ function expandAt(type, seen, depth, at) {
48
74
  if (depth > MAX_EXPANSION_DEPTH)
49
75
  return backstopText(type);
76
+ let cursor = at;
77
+ if (cursor) {
78
+ const replacement = cursor.overrides.types.get(cursor.position);
79
+ if (replacement) {
80
+ cursor.overrides.applied.add(cursor.position);
81
+ return expandAt(replacement, seen, depth, undefined);
82
+ }
83
+ // Nothing to substitute below here: print exactly as without overrides.
84
+ if (!hasOverrideBelow(cursor))
85
+ cursor = undefined;
86
+ }
50
87
  // Primitives & literals: nothing to inline.
51
88
  if (type.isString() ||
52
89
  type.isNumber() ||
@@ -65,10 +102,10 @@ export function expandTypeStructural(type, seen = new Set(), depth = 0) {
65
102
  }
66
103
  // Unions / intersections: expand each member, in canonical order.
67
104
  if (type.isUnion()) {
68
- return canonicalMembers(type.getUnionTypes(), seen, depth).join(' | ');
105
+ return canonicalMembers(type.getUnionTypes(), seen, depth, cursor).join(' | ');
69
106
  }
70
107
  if (type.isIntersection()) {
71
- return canonicalMembers(type.getIntersectionTypes(), seen, depth).join(' & ');
108
+ return canonicalMembers(type.getIntersectionTypes(), seen, depth, cursor).join(' & ');
72
109
  }
73
110
  // Tuples are array-like but must keep their `[a, b]` shape, not be walked
74
111
  // as objects (which explodes into `Array.prototype`). Handle before arrays.
@@ -79,7 +116,7 @@ export function expandTypeStructural(type, seen = new Set(), depth = 0) {
79
116
  const element = type.getArrayElementType();
80
117
  if (!element)
81
118
  return namedText(type);
82
- const inner = expandTypeStructural(element, seen, depth + 1);
119
+ const inner = expandAt(element, seen, depth + 1, childCursor(cursor, '<0>'));
83
120
  // Parenthesise a union/intersection element so `(A | B)[]` doesn't misparse
84
121
  // as `A | B[]`. Decide from the TYPE, not the string: a single object
85
122
  // literal like `{ a: A | B }` is NOT a union and must not be parenthesised,
@@ -87,8 +124,10 @@ export function expandTypeStructural(type, seen = new Set(), depth = 0) {
87
124
  const needsParens = element.isUnion() || element.isIntersection();
88
125
  return needsParens ? `(${inner})[]` : `${inner}[]`;
89
126
  }
90
- // Library / built-in types (Date, Promise, RegExp, …): keep by name.
91
- if (isLibraryType(type)) {
127
+ // Library / built-in types (Date, Promise, RegExp, …): keep by name, unless
128
+ // a member below has to be substituted — a schema library declares its
129
+ // inferred object types itself, and they are only walkable, not by-name.
130
+ if (!cursor && isLibraryType(type)) {
92
131
  return namedText(type);
93
132
  }
94
133
  // Callable/constructable object types (functions): keep by name; their
@@ -105,7 +144,7 @@ export function expandTypeStructural(type, seen = new Set(), depth = 0) {
105
144
  const props = type.getProperties();
106
145
  if (props.length === 0)
107
146
  return namedText(type);
108
- const parts = props.map((prop) => expandProperty(prop, nextSeen, depth));
147
+ const parts = props.map((prop) => expandProperty(prop, nextSeen, depth, cursor));
109
148
  return `{ ${parts.join('; ')}; }`;
110
149
  }
111
150
  return namedText(type);
@@ -164,8 +203,8 @@ function backstopText(type) {
164
203
  * Ties can only happen between two members that render identically, in which
165
204
  * case the joined output is the same whichever way round they go.
166
205
  */
167
- function canonicalMembers(members, seen, depth) {
168
- return orderMembers(members, (member) => expandTypeStructural(member, seen, depth + 1));
206
+ function canonicalMembers(members, seen, depth, cursor) {
207
+ return orderMembers(members, (member) => expandAt(member, seen, depth + 1, cursor));
169
208
  }
170
209
  /**
171
210
  * The canonical order itself, over whatever text `render` gives each member.
@@ -186,16 +225,28 @@ function orderMembers(members, render) {
186
225
  return rendered.map((entry) => entry.text);
187
226
  }
188
227
  /** Render a single property as `name[?]: <expanded>`. */
189
- function expandProperty(prop, seen, depth) {
228
+ function expandProperty(prop, seen, depth, at) {
190
229
  const optional = (prop.getFlags() & ts.SymbolFlags.Optional) !== 0;
191
230
  const propDecl = prop.getDeclarations()[0];
192
231
  // Render the key from the declaration's name node so quoted/computed keys
193
232
  // ('x-y', "x y", [Symbol.iterator]) survive as valid TS text rather than
194
233
  // being unquoted into invalid output; fall back to the bare symbol name.
195
234
  const name = renderPropertyName(prop, propDecl);
196
- let propType = propDecl
197
- ? prop.getTypeAtLocation(propDecl)
198
- : prop.getDeclaredType();
235
+ let propType = at
236
+ ? prop.getTypeAtLocation(at.overrides.at)
237
+ : propDecl
238
+ ? prop.getTypeAtLocation(propDecl)
239
+ : prop.getDeclaredType();
240
+ // A substituted member takes the override's TYPE but keeps this key's
241
+ // optionality, and is looked up before the `undefined` strip below so an
242
+ // override that carries `| undefined` strips like any other optional key.
243
+ let cursor = childCursor(at, prop.getName());
244
+ const replacement = cursor && cursor.overrides.types.get(cursor.position);
245
+ if (cursor && replacement) {
246
+ cursor.overrides.applied.add(cursor.position);
247
+ propType = replacement;
248
+ cursor = undefined;
249
+ }
199
250
  // An optional property's type includes `undefined`; the structural label
200
251
  // drops it (`note?: string`, not `note?: string | undefined`).
201
252
  if (optional && propType.isUnion()) {
@@ -206,11 +257,11 @@ function expandProperty(prop, seen, depth) {
206
257
  propType = nonUndefined[0];
207
258
  }
208
259
  else if (nonUndefined.length > 1) {
209
- const inner = canonicalMembers(nonUndefined, seen, depth).join(' | ');
260
+ const inner = canonicalMembers(nonUndefined, seen, depth, cursor).join(' | ');
210
261
  return `${name}?: ${inner}`;
211
262
  }
212
263
  }
213
- const inner = expandTypeStructural(propType, seen, depth + 1);
264
+ const inner = expandAt(propType, seen, depth + 1, cursor);
214
265
  return `${name}${optional ? '?' : ''}: ${inner}`;
215
266
  }
216
267
  /**