carrick 0.3.53

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 (114) hide show
  1. package/LICENSE.md +99 -0
  2. package/README.md +79 -0
  3. package/bin/carrick.mjs +168 -0
  4. package/dist/channel.d.ts +16 -0
  5. package/dist/channel.js +37 -0
  6. package/dist/channel.js.map +1 -0
  7. package/dist/cli.d.ts +29 -0
  8. package/dist/cli.js +60 -0
  9. package/dist/cli.js.map +1 -0
  10. package/dist/contract.d.ts +154 -0
  11. package/dist/contract.js +141 -0
  12. package/dist/contract.js.map +1 -0
  13. package/dist/diagnostics.d.ts +54 -0
  14. package/dist/diagnostics.js +199 -0
  15. package/dist/diagnostics.js.map +1 -0
  16. package/dist/hook/post-edit.d.ts +13 -0
  17. package/dist/hook/post-edit.js +76 -0
  18. package/dist/hook/post-edit.js.map +1 -0
  19. package/dist/hook/session-start.d.ts +2 -0
  20. package/dist/hook/session-start.js +44 -0
  21. package/dist/hook/session-start.js.map +1 -0
  22. package/dist/init/identity.d.ts +20 -0
  23. package/dist/init/identity.js +60 -0
  24. package/dist/init/identity.js.map +1 -0
  25. package/dist/init/repos.d.ts +19 -0
  26. package/dist/init/repos.js +46 -0
  27. package/dist/init/repos.js.map +1 -0
  28. package/dist/init/run.d.ts +11 -0
  29. package/dist/init/run.js +228 -0
  30. package/dist/init/run.js.map +1 -0
  31. package/dist/init/settings.d.ts +50 -0
  32. package/dist/init/settings.js +138 -0
  33. package/dist/init/settings.js.map +1 -0
  34. package/dist/log.d.ts +2 -0
  35. package/dist/log.js +35 -0
  36. package/dist/log.js.map +1 -0
  37. package/dist/native.d.ts +48 -0
  38. package/dist/native.js +127 -0
  39. package/dist/native.js.map +1 -0
  40. package/dist/render.d.ts +65 -0
  41. package/dist/render.js +334 -0
  42. package/dist/render.js.map +1 -0
  43. package/dist/root.d.ts +30 -0
  44. package/dist/root.js +82 -0
  45. package/dist/root.js.map +1 -0
  46. package/dist/server.d.ts +2 -0
  47. package/dist/server.js +255 -0
  48. package/dist/server.js.map +1 -0
  49. package/dist/templates.d.ts +18 -0
  50. package/dist/templates.js +66 -0
  51. package/dist/templates.js.map +1 -0
  52. package/package.json +72 -0
  53. package/plugin/.claude-plugin/plugin.json +6 -0
  54. package/plugin/.lsp.json +14 -0
  55. package/plugin/hooks/hooks.json +27 -0
  56. package/sidecar/dist/src/bundler.d.ts +141 -0
  57. package/sidecar/dist/src/bundler.js +680 -0
  58. package/sidecar/dist/src/capture/anchors.d.ts +61 -0
  59. package/sidecar/dist/src/capture/anchors.js +1132 -0
  60. package/sidecar/dist/src/capture/api.d.ts +378 -0
  61. package/sidecar/dist/src/capture/api.js +10 -0
  62. package/sidecar/dist/src/capture/augmentations.d.ts +20 -0
  63. package/sidecar/dist/src/capture/augmentations.js +60 -0
  64. package/sidecar/dist/src/capture/check-classify.d.ts +58 -0
  65. package/sidecar/dist/src/capture/check-classify.js +189 -0
  66. package/sidecar/dist/src/capture/check-deep.d.ts +32 -0
  67. package/sidecar/dist/src/capture/check-deep.js +91 -0
  68. package/sidecar/dist/src/capture/check-poison.d.ts +40 -0
  69. package/sidecar/dist/src/capture/check-poison.js +155 -0
  70. package/sidecar/dist/src/capture/check-probe.d.ts +70 -0
  71. package/sidecar/dist/src/capture/check-probe.js +147 -0
  72. package/sidecar/dist/src/capture/check-scrub.d.ts +31 -0
  73. package/sidecar/dist/src/capture/check-scrub.js +71 -0
  74. package/sidecar/dist/src/capture/check-workspace.d.ts +52 -0
  75. package/sidecar/dist/src/capture/check-workspace.js +218 -0
  76. package/sidecar/dist/src/capture/check.d.ts +39 -0
  77. package/sidecar/dist/src/capture/check.js +450 -0
  78. package/sidecar/dist/src/capture/deep-walk.d.ts +62 -0
  79. package/sidecar/dist/src/capture/deep-walk.js +243 -0
  80. package/sidecar/dist/src/capture/index.d.ts +36 -0
  81. package/sidecar/dist/src/capture/index.js +477 -0
  82. package/sidecar/dist/src/capture/lockfile.d.ts +48 -0
  83. package/sidecar/dist/src/capture/lockfile.js +490 -0
  84. package/sidecar/dist/src/capture/machinery.d.ts +59 -0
  85. package/sidecar/dist/src/capture/machinery.js +160 -0
  86. package/sidecar/dist/src/capture/node-builder.d.ts +37 -0
  87. package/sidecar/dist/src/capture/node-builder.js +123 -0
  88. package/sidecar/dist/src/capture/paths-rewrite.d.ts +34 -0
  89. package/sidecar/dist/src/capture/paths-rewrite.js +100 -0
  90. package/sidecar/dist/src/capture/self-check.d.ts +38 -0
  91. package/sidecar/dist/src/capture/self-check.js +317 -0
  92. package/sidecar/dist/src/capture/specifiers.d.ts +39 -0
  93. package/sidecar/dist/src/capture/specifiers.js +56 -0
  94. package/sidecar/dist/src/definition-resolver.d.ts +57 -0
  95. package/sidecar/dist/src/definition-resolver.js +153 -0
  96. package/sidecar/dist/src/index.d.ts +14 -0
  97. package/sidecar/dist/src/index.js +564 -0
  98. package/sidecar/dist/src/monorepo-builder.d.ts +129 -0
  99. package/sidecar/dist/src/monorepo-builder.js +584 -0
  100. package/sidecar/dist/src/project-loader.d.ts +130 -0
  101. package/sidecar/dist/src/project-loader.js +399 -0
  102. package/sidecar/dist/src/type-inferrer.d.ts +940 -0
  103. package/sidecar/dist/src/type-inferrer.js +3540 -0
  104. package/sidecar/dist/src/type-structural-expander.d.ts +61 -0
  105. package/sidecar/dist/src/type-structural-expander.js +283 -0
  106. package/sidecar/dist/src/type-text-canonicalizer.d.ts +40 -0
  107. package/sidecar/dist/src/type-text-canonicalizer.js +297 -0
  108. package/sidecar/dist/src/types.d.ts +669 -0
  109. package/sidecar/dist/src/types.js +5 -0
  110. package/sidecar/dist/src/validators.d.ts +2214 -0
  111. package/sidecar/dist/src/validators.js +336 -0
  112. package/sidecar/package.json +6 -0
  113. package/templates/carrick.json +7 -0
  114. package/templates/carrick.yml +45 -0
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Structural type expansion — renders a ts-morph `Type` as fully-inlined
3
+ * structural text, with every named object/interface member expanded to its
4
+ * member structure recursively.
5
+ *
6
+ * `Type.getText()` does NOT inline named members: the compiler prints a
7
+ * referenced type by its symbol name when that symbol is in scope
8
+ * (`total: Money`, not `total: { amountCents: number; currency: string }`).
9
+ * That is fine inside a single project, but a cross-repo bundle carries only
10
+ * the alias lines — no source declarations — so a named reference is a
11
+ * dangling identifier that resolves to `any` downstream. Expanding the shape
12
+ * structurally puts the real members in the bundle so the type checker can
13
+ * compare them.
14
+ *
15
+ * Object/interface types are expanded to their members; primitives, literals,
16
+ * library types (`Date`, `Promise`, tuples, …) and functions stay by name.
17
+ * Bounded recursion + a per-branch cycle set guard against blow-ups; any type
18
+ * that can't be safely expanded falls back to the non-expanded text rather
19
+ * than throwing.
20
+ *
21
+ * Union and intersection members are printed in a canonical order that does not
22
+ * depend on when the checker created each member type — see `orderMembers`
23
+ * (carrick#735), which the depth backstop applies too, and which `namedText`
24
+ * applies as text to everything the walk hands back to the compiler's own
25
+ * print (carrick#775).
26
+ *
27
+ * Shared by `definition-resolver.ts` (bundle alias resolution) and
28
+ * `type-inferrer.ts` (consumer-side inference), so both paths emit the same
29
+ * structural form rather than a dangling name.
30
+ */
31
+ import { type Type } from 'ts-morph';
32
+ /**
33
+ * Bound on the structural-expansion recursion. Deep enough for every realistic
34
+ * request/response shape; a backstop against pathological/recursive types the
35
+ * cycle set somehow misses.
36
+ */
37
+ export declare const MAX_EXPANSION_DEPTH = 12;
38
+ /**
39
+ * Recursively render a `Type` as fully-inlined structural text.
40
+ *
41
+ * Named object/interface types are expanded to their member structure;
42
+ * primitives, literals, library types (`Date`, `Promise`, tuples, …) and
43
+ * functions stay by name. The `seen` set (object type ids on the current
44
+ * branch) breaks reference cycles; `depth` is a hard backstop.
45
+ */
46
+ export declare function expandTypeStructural(type: Type, seen?: Set<number>, depth?: number): string;
47
+ /**
48
+ * Non-expanded text for a type. Passes `undefined` as the enclosing node so
49
+ * the compiler can't throw on an invalid node context (tuples and some
50
+ * generic instantiations do), falling back to the bare `getText()` and
51
+ * finally to `unknown` so a single bad type never aborts the whole resolve.
52
+ *
53
+ * This is the ONE place the walk hands a subtree back to the compiler's own
54
+ * print — for a library type, a type with no properties to walk, a tuple, a
55
+ * function, a cycle, or the depth backstop. Everything inside that print is in
56
+ * type-id order, which is creation order, so the unions it contains are put in
57
+ * the same canonical order the walk gives the ones it renders itself
58
+ * (carrick#775). Doing it here rather than at each caller means no bail-out
59
+ * path can print a union one way while the walk prints it another.
60
+ */
61
+ export declare function namedText(type: Type): string;
@@ -0,0 +1,283 @@
1
+ /**
2
+ * Structural type expansion — renders a ts-morph `Type` as fully-inlined
3
+ * structural text, with every named object/interface member expanded to its
4
+ * member structure recursively.
5
+ *
6
+ * `Type.getText()` does NOT inline named members: the compiler prints a
7
+ * referenced type by its symbol name when that symbol is in scope
8
+ * (`total: Money`, not `total: { amountCents: number; currency: string }`).
9
+ * That is fine inside a single project, but a cross-repo bundle carries only
10
+ * the alias lines — no source declarations — so a named reference is a
11
+ * dangling identifier that resolves to `any` downstream. Expanding the shape
12
+ * structurally puts the real members in the bundle so the type checker can
13
+ * compare them.
14
+ *
15
+ * Object/interface types are expanded to their members; primitives, literals,
16
+ * library types (`Date`, `Promise`, tuples, …) and functions stay by name.
17
+ * Bounded recursion + a per-branch cycle set guard against blow-ups; any type
18
+ * that can't be safely expanded falls back to the non-expanded text rather
19
+ * than throwing.
20
+ *
21
+ * Union and intersection members are printed in a canonical order that does not
22
+ * depend on when the checker created each member type — see `orderMembers`
23
+ * (carrick#735), which the depth backstop applies too, and which `namedText`
24
+ * applies as text to everything the walk hands back to the compiler's own
25
+ * print (carrick#775).
26
+ *
27
+ * Shared by `definition-resolver.ts` (bundle alias resolution) and
28
+ * `type-inferrer.ts` (consumer-side inference), so both paths emit the same
29
+ * structural form rather than a dangling name.
30
+ */
31
+ import { ts } from 'ts-morph';
32
+ import { canonicalizeUnionsInText } from './type-text-canonicalizer.js';
33
+ /**
34
+ * Bound on the structural-expansion recursion. Deep enough for every realistic
35
+ * request/response shape; a backstop against pathological/recursive types the
36
+ * cycle set somehow misses.
37
+ */
38
+ export const MAX_EXPANSION_DEPTH = 12;
39
+ /**
40
+ * Recursively render a `Type` as fully-inlined structural text.
41
+ *
42
+ * Named object/interface types are expanded to their member structure;
43
+ * primitives, literals, library types (`Date`, `Promise`, tuples, …) and
44
+ * functions stay by name. The `seen` set (object type ids on the current
45
+ * branch) breaks reference cycles; `depth` is a hard backstop.
46
+ */
47
+ export function expandTypeStructural(type, seen = new Set(), depth = 0) {
48
+ if (depth > MAX_EXPANSION_DEPTH)
49
+ return backstopText(type);
50
+ // Primitives & literals: nothing to inline.
51
+ if (type.isString() ||
52
+ type.isNumber() ||
53
+ type.isBoolean() ||
54
+ type.isBooleanLiteral() ||
55
+ type.isUndefined() ||
56
+ type.isNull() ||
57
+ type.isVoid() ||
58
+ type.isAny() ||
59
+ type.isUnknown() ||
60
+ type.isNever() ||
61
+ type.isStringLiteral() ||
62
+ type.isNumberLiteral() ||
63
+ type.isEnumLiteral()) {
64
+ return namedText(type);
65
+ }
66
+ // Unions / intersections: expand each member, in canonical order.
67
+ if (type.isUnion()) {
68
+ return canonicalMembers(type.getUnionTypes(), seen, depth).join(' | ');
69
+ }
70
+ if (type.isIntersection()) {
71
+ return canonicalMembers(type.getIntersectionTypes(), seen, depth).join(' & ');
72
+ }
73
+ // Tuples are array-like but must keep their `[a, b]` shape, not be walked
74
+ // as objects (which explodes into `Array.prototype`). Handle before arrays.
75
+ if (isTuple(type)) {
76
+ return namedText(type);
77
+ }
78
+ if (type.isArray()) {
79
+ const element = type.getArrayElementType();
80
+ if (!element)
81
+ return namedText(type);
82
+ const inner = expandTypeStructural(element, seen, depth + 1);
83
+ // Parenthesise a union/intersection element so `(A | B)[]` doesn't misparse
84
+ // as `A | B[]`. Decide from the TYPE, not the string: a single object
85
+ // literal like `{ a: A | B }` is NOT a union and must not be parenthesised,
86
+ // and a union led by an object literal (`{ a: string } | null`) MUST be.
87
+ const needsParens = element.isUnion() || element.isIntersection();
88
+ return needsParens ? `(${inner})[]` : `${inner}[]`;
89
+ }
90
+ // Library / built-in types (Date, Promise, RegExp, …): keep by name.
91
+ if (isLibraryType(type)) {
92
+ return namedText(type);
93
+ }
94
+ // Callable/constructable object types (functions): keep by name; their
95
+ // structural form is the signature, which `getText` already renders.
96
+ if (type.getCallSignatures().length > 0 ||
97
+ type.getConstructSignatures().length > 0) {
98
+ return namedText(type);
99
+ }
100
+ if (type.isObject() || type.isInterface()) {
101
+ const id = type.compilerType.id;
102
+ if (id != null && seen.has(id))
103
+ return namedText(type);
104
+ const nextSeen = id != null ? new Set(seen).add(id) : seen;
105
+ const props = type.getProperties();
106
+ if (props.length === 0)
107
+ return namedText(type);
108
+ const parts = props.map((prop) => expandProperty(prop, nextSeen, depth));
109
+ return `{ ${parts.join('; ')}; }`;
110
+ }
111
+ return namedText(type);
112
+ }
113
+ /**
114
+ * The print for a type the recursion bound stopped at (carrick#775).
115
+ *
116
+ * `namedText` is the compiler's own print, and for a union that print is in
117
+ * type-id order — the creation-order artefact `canonicalMembers` exists to
118
+ * remove. Stopping the recursion must not also stop the normalisation: a union
119
+ * at depth 13 is as much a set as one at depth 2, and a diff reader comparing
120
+ * two `expanded_definition` strings cannot tell which depth a member came from.
121
+ *
122
+ * So the members are rendered by NAME — no recursion, which is the whole point
123
+ * of the bound — and put in the same canonical order as the expanded path.
124
+ * Everything else falls through to the compiler's print unchanged.
125
+ */
126
+ function backstopText(type) {
127
+ if (type.isUnion()) {
128
+ return orderMembers(type.getUnionTypes(), namedText).join(' | ');
129
+ }
130
+ if (type.isIntersection()) {
131
+ return orderMembers(type.getIntersectionTypes(), namedText).join(' & ');
132
+ }
133
+ return namedText(type);
134
+ }
135
+ /**
136
+ * Render every member of a union/intersection and put them in a canonical
137
+ * order (carrick#735).
138
+ *
139
+ * The compiler stores a union's constituents sorted by type id, and ids are
140
+ * handed out in the order the checker CREATES types. A literal type is created
141
+ * the first time some declaration is checked, and it is then interned — so on
142
+ * a tree where `"PENDING" | "TIMED_OUT"` and `"TIMED_OUT" | "PENDING"` are both
143
+ * declared, whichever declaration is reached first decides the printed order of
144
+ * BOTH. Two runs over an unchanged tree can therefore print one type two ways.
145
+ *
146
+ * The order carries no meaning: a union is a set, and the check phase compares
147
+ * these strings by typechecking them, which is order-insensitive. So we impose
148
+ * one — every member sorts by its own rendered text, compared by UTF-16 code
149
+ * unit. That is a pure function of the members, with no dependence on when the
150
+ * checker happened to create any of them.
151
+ *
152
+ * #735 carved out an exception for the intrinsics (`string`, `null`, `number`,
153
+ * …), keeping them ahead of the rest in compiler-id order on the grounds that
154
+ * their ids are fixed before any source file is read, so `string | null` would
155
+ * still print the way the compiler prints it. The second half of that is not
156
+ * true: the compiler's printer does not use id order. On a union of
157
+ * `null | number` the checker's ids give `null` first and `typeToString` prints
158
+ * `number | null`, so keeping id order reproduced neither the compiler's print
159
+ * nor — once carrick#775 put the compiler's own prints under the same rule —
160
+ * the other path's. One rule for every member is the only way a union prints
161
+ * one way wherever it is rendered, and it costs nothing the exception was
162
+ * actually buying. `type-text-canonicalizer.ts` applies the same rule to text.
163
+ *
164
+ * Ties can only happen between two members that render identically, in which
165
+ * case the joined output is the same whichever way round they go.
166
+ */
167
+ function canonicalMembers(members, seen, depth) {
168
+ return orderMembers(members, (member) => expandTypeStructural(member, seen, depth + 1));
169
+ }
170
+ /**
171
+ * The canonical order itself, over whatever text `render` gives each member.
172
+ * Shared by the expanded path (`canonicalMembers`, which renders structurally)
173
+ * and the depth backstop (`backstopText`, which renders by name), so one union
174
+ * cannot be ordered two ways depending on how deep it sits.
175
+ */
176
+ function orderMembers(members, render) {
177
+ const rendered = members.map((member, index) => ({
178
+ index,
179
+ text: render(member),
180
+ }));
181
+ rendered.sort((a, b) => {
182
+ if (a.text !== b.text)
183
+ return a.text < b.text ? -1 : 1;
184
+ return a.index - b.index;
185
+ });
186
+ return rendered.map((entry) => entry.text);
187
+ }
188
+ /** Render a single property as `name[?]: <expanded>`. */
189
+ function expandProperty(prop, seen, depth) {
190
+ const optional = (prop.getFlags() & ts.SymbolFlags.Optional) !== 0;
191
+ const propDecl = prop.getDeclarations()[0];
192
+ // Render the key from the declaration's name node so quoted/computed keys
193
+ // ('x-y', "x y", [Symbol.iterator]) survive as valid TS text rather than
194
+ // being unquoted into invalid output; fall back to the bare symbol name.
195
+ const name = renderPropertyName(prop, propDecl);
196
+ let propType = propDecl
197
+ ? prop.getTypeAtLocation(propDecl)
198
+ : prop.getDeclaredType();
199
+ // An optional property's type includes `undefined`; the structural label
200
+ // drops it (`note?: string`, not `note?: string | undefined`).
201
+ if (optional && propType.isUnion()) {
202
+ const nonUndefined = propType
203
+ .getUnionTypes()
204
+ .filter((member) => !member.isUndefined());
205
+ if (nonUndefined.length === 1) {
206
+ propType = nonUndefined[0];
207
+ }
208
+ else if (nonUndefined.length > 1) {
209
+ const inner = canonicalMembers(nonUndefined, seen, depth).join(' | ');
210
+ return `${name}?: ${inner}`;
211
+ }
212
+ }
213
+ const inner = expandTypeStructural(propType, seen, depth + 1);
214
+ return `${name}${optional ? '?' : ''}: ${inner}`;
215
+ }
216
+ /**
217
+ * The property key as valid TS text. Uses the declaration's name node so a
218
+ * quoted (`'x-y'`) or computed (`[Symbol.iterator]`) key keeps its syntax;
219
+ * `Symbol.getName()` would drop the quoting and emit invalid output. Falls
220
+ * back to the bare symbol name when there's no usable name node.
221
+ */
222
+ function renderPropertyName(prop, decl) {
223
+ const node = decl;
224
+ const text = node?.getNameNode?.()?.getText();
225
+ return text && text.length > 0 ? text : prop.getName();
226
+ }
227
+ /** True for tuple types (`[a, b]`), which must not be walked as objects. */
228
+ function isTuple(type) {
229
+ const compiler = type.compilerType;
230
+ const target = compiler.target ?? compiler;
231
+ return ((target.objectFlags ?? 0) & ts.ObjectFlags.Tuple) !== 0;
232
+ }
233
+ /**
234
+ * True for types declared in `node_modules` or a TS `lib.*.d.ts` (Date,
235
+ * Promise, RegExp, …). These stay by name rather than being inlined.
236
+ */
237
+ function isLibraryType(type) {
238
+ const symbol = type.getSymbol() ?? type.getAliasSymbol();
239
+ if (!symbol)
240
+ return false;
241
+ const decls = symbol.getDeclarations();
242
+ if (decls.length === 0)
243
+ return false;
244
+ return decls.some((decl) => {
245
+ const sf = decl.getSourceFile();
246
+ if (sf.isInNodeModules())
247
+ return true;
248
+ return (sf.isDeclarationFile() &&
249
+ // Normalize separators so a Windows `\\` path still matches lib.*.d.ts.
250
+ /(^|\/)lib\.[^/]*\.d\.ts$/.test(sf.getFilePath().replace(/\\/g, '/')));
251
+ });
252
+ }
253
+ /**
254
+ * Non-expanded text for a type. Passes `undefined` as the enclosing node so
255
+ * the compiler can't throw on an invalid node context (tuples and some
256
+ * generic instantiations do), falling back to the bare `getText()` and
257
+ * finally to `unknown` so a single bad type never aborts the whole resolve.
258
+ *
259
+ * This is the ONE place the walk hands a subtree back to the compiler's own
260
+ * print — for a library type, a type with no properties to walk, a tuple, a
261
+ * function, a cycle, or the depth backstop. Everything inside that print is in
262
+ * type-id order, which is creation order, so the unions it contains are put in
263
+ * the same canonical order the walk gives the ones it renders itself
264
+ * (carrick#775). Doing it here rather than at each caller means no bail-out
265
+ * path can print a union one way while the walk prints it another.
266
+ */
267
+ export function namedText(type) {
268
+ return canonicalizeUnionsInText(compilerText(type));
269
+ }
270
+ /** The compiler's print, with the two fallbacks. */
271
+ function compilerText(type) {
272
+ try {
273
+ return type.getText(undefined, ts.TypeFormatFlags.NoTruncation | ts.TypeFormatFlags.InTypeAlias);
274
+ }
275
+ catch {
276
+ try {
277
+ return type.getText();
278
+ }
279
+ catch {
280
+ return 'unknown';
281
+ }
282
+ }
283
+ }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Canonical union order inside text the COMPILER printed (carrick#775, #760).
3
+ *
4
+ * `expandTypeStructural` walks a type and orders every union it walks
5
+ * (`orderMembers`), so the structural path is stable. But the walk stops at
6
+ * several points and hands the rest of the subtree to `namedText` — the
7
+ * compiler's own print:
8
+ *
9
+ * - library types, which are kept by name (`isLibraryType`), and whose print
10
+ * carries their whole generic instantiation with it;
11
+ * - object types with no named properties (an index signature alone), which
12
+ * have nothing to walk;
13
+ * - tuples, functions, cycles the `seen` set breaks, and the depth backstop.
14
+ *
15
+ * Everything inside one of those prints is in the compiler's own union order,
16
+ * which is type-id order, which is CREATION order — the same instability
17
+ * `orderMembers` exists to remove, one level down where the type API can no
18
+ * longer reach it: at that point the members exist only as text.
19
+ *
20
+ * So they are ordered as text, by the same rule the walk uses: every member
21
+ * sorts by its own rendering, compared by UTF-16 code unit. A union is a set,
22
+ * the check phase compares these strings by typechecking them, and a reader
23
+ * diffing two `expanded_definition` strings cannot tell which side of a
24
+ * `namedText` boundary a member came from — so both sides must order them the
25
+ * same way, which is why `orderMembers` no longer keeps intrinsics in compiler
26
+ * id order (the compiler's printer does not use that order either).
27
+ *
28
+ * The rewrite is deliberately timid. Anything whose meaning could depend on
29
+ * order or on a parse this file does not do — a function type (`=>`), a
30
+ * conditional (`?`) — is returned untouched, and untouched is always a legal
31
+ * answer here because the compiler's print is already valid TypeScript.
32
+ */
33
+ /**
34
+ * Put every union in `text` into canonical order, at every nesting level.
35
+ *
36
+ * Returns `text` unchanged whenever the shape is one this file does not parse
37
+ * (see the module comment). Never throws: a type print is arbitrary text, and
38
+ * a definition resolve must not fail because one member confused a scanner.
39
+ */
40
+ export declare function canonicalizeUnionsInText(text: string): string;
@@ -0,0 +1,297 @@
1
+ /**
2
+ * Canonical union order inside text the COMPILER printed (carrick#775, #760).
3
+ *
4
+ * `expandTypeStructural` walks a type and orders every union it walks
5
+ * (`orderMembers`), so the structural path is stable. But the walk stops at
6
+ * several points and hands the rest of the subtree to `namedText` — the
7
+ * compiler's own print:
8
+ *
9
+ * - library types, which are kept by name (`isLibraryType`), and whose print
10
+ * carries their whole generic instantiation with it;
11
+ * - object types with no named properties (an index signature alone), which
12
+ * have nothing to walk;
13
+ * - tuples, functions, cycles the `seen` set breaks, and the depth backstop.
14
+ *
15
+ * Everything inside one of those prints is in the compiler's own union order,
16
+ * which is type-id order, which is CREATION order — the same instability
17
+ * `orderMembers` exists to remove, one level down where the type API can no
18
+ * longer reach it: at that point the members exist only as text.
19
+ *
20
+ * So they are ordered as text, by the same rule the walk uses: every member
21
+ * sorts by its own rendering, compared by UTF-16 code unit. A union is a set,
22
+ * the check phase compares these strings by typechecking them, and a reader
23
+ * diffing two `expanded_definition` strings cannot tell which side of a
24
+ * `namedText` boundary a member came from — so both sides must order them the
25
+ * same way, which is why `orderMembers` no longer keeps intrinsics in compiler
26
+ * id order (the compiler's printer does not use that order either).
27
+ *
28
+ * The rewrite is deliberately timid. Anything whose meaning could depend on
29
+ * order or on a parse this file does not do — a function type (`=>`), a
30
+ * conditional (`?`) — is returned untouched, and untouched is always a legal
31
+ * answer here because the compiler's print is already valid TypeScript.
32
+ */
33
+ /** Bracket pairs whose interiors are descended into. */
34
+ const CLOSERS = { '{': '}', '<': '>', '(': ')', '[': ']' };
35
+ /**
36
+ * Put every union in `text` into canonical order, at every nesting level.
37
+ *
38
+ * Returns `text` unchanged whenever the shape is one this file does not parse
39
+ * (see the module comment). Never throws: a type print is arbitrary text, and
40
+ * a definition resolve must not fail because one member confused a scanner.
41
+ */
42
+ export function canonicalizeUnionsInText(text) {
43
+ try {
44
+ return canonicalizeType(text);
45
+ }
46
+ catch {
47
+ return text;
48
+ }
49
+ }
50
+ /** Canonicalise one TYPE fragment — not a `key: value` member. */
51
+ function canonicalizeType(text) {
52
+ const trimmed = text.trim();
53
+ if (trimmed.length === 0)
54
+ return text;
55
+ // A function type or a conditional type is left exactly as printed: both
56
+ // carry a top-level `|` that is not a union separator at this level
57
+ // (`() => a | b` is one function returning a union, `T extends U ? a : b`
58
+ // has unions inside its branches), and neither is worth the parse.
59
+ if (hasTopLevel(trimmed, ['=>', '?']))
60
+ return text;
61
+ const members = splitTopLevel(trimmed, '|');
62
+ if (members.length > 1) {
63
+ return orderTextMembers(members.map((m) => canonicalizeType(m))).join(' | ');
64
+ }
65
+ return descendIntoBrackets(trimmed);
66
+ }
67
+ /**
68
+ * Rewrite the interiors of the bracket groups in a fragment that is not itself
69
+ * a union: an object body's member types, a generic argument list, a tuple's
70
+ * elements, a parenthesised type.
71
+ */
72
+ function descendIntoBrackets(text) {
73
+ let out = '';
74
+ let i = 0;
75
+ while (i < text.length) {
76
+ const ch = text[i];
77
+ const quoted = readQuoted(text, i);
78
+ if (quoted !== null) {
79
+ out += text.slice(i, quoted);
80
+ i = quoted;
81
+ continue;
82
+ }
83
+ const closer = CLOSERS[ch];
84
+ if (closer === undefined) {
85
+ out += ch;
86
+ i++;
87
+ continue;
88
+ }
89
+ const end = matchBracket(text, i);
90
+ if (end === -1) {
91
+ // Unbalanced (a truncated print): copy the rest verbatim.
92
+ out += text.slice(i);
93
+ return out;
94
+ }
95
+ const inner = text.slice(i + 1, end);
96
+ out += ch + canonicalizeBody(inner, ch) + closer;
97
+ i = end + 1;
98
+ }
99
+ return out;
100
+ }
101
+ /**
102
+ * Canonicalise the inside of one bracket group.
103
+ *
104
+ * `{ ... }` is a list of `key: type` members separated by `;` (or `,`), and
105
+ * only the type half may be rewritten — a key is not a type, and `a: X | Y`
106
+ * split on `|` would produce the nonsense member `a: X`. Every other bracket
107
+ * holds a comma-separated list of types.
108
+ *
109
+ * Each piece is spliced back at its own offsets, so a body with nothing to
110
+ * reorder comes out byte-identical to the compiler's print — whitespace,
111
+ * separators and all.
112
+ */
113
+ function canonicalizeBody(inner, opener) {
114
+ const separator = opener === '{' ? ';' : ',';
115
+ const spans = splitTopLevelSpans(inner, separator);
116
+ let out = '';
117
+ let cursor = 0;
118
+ for (const span of spans) {
119
+ const piece = inner.slice(span.start, span.end);
120
+ const trimmed = piece.trim();
121
+ if (trimmed.length > 0) {
122
+ const rewritten = canonicalizeMember(trimmed);
123
+ if (rewritten !== trimmed) {
124
+ const at = span.start + piece.indexOf(trimmed);
125
+ out += inner.slice(cursor, at) + rewritten;
126
+ cursor = at + trimmed.length;
127
+ }
128
+ }
129
+ }
130
+ return out + inner.slice(cursor);
131
+ }
132
+ /**
133
+ * One list element: `key: type`, `name?: type`, `[x: string]: type`, or a bare
134
+ * type. Splits at the element's own `:` — the one at depth zero, which an
135
+ * index signature's `[x: string]` and a nested object both sit below — and
136
+ * rewrites only what follows it.
137
+ */
138
+ function canonicalizeMember(member) {
139
+ const colon = indexOfTopLevel(member, ':');
140
+ if (colon === -1)
141
+ return canonicalizeType(member);
142
+ const key = member.slice(0, colon + 1);
143
+ const value = member.slice(colon + 1);
144
+ const leading = value.length - value.trimStart().length;
145
+ const trailing = value.length - value.trimEnd().length;
146
+ return (key +
147
+ value.slice(0, leading) +
148
+ canonicalizeType(value.trim()) +
149
+ value.slice(value.length - trailing));
150
+ }
151
+ /**
152
+ * The canonical order, as text: every member by its own rendering, compared by
153
+ * UTF-16 code unit. This is `orderMembers` in `type-structural-expander.ts`,
154
+ * over the renderings that are all this side has — the two must agree member
155
+ * for member, or one union prints two ways depending on whether the walk or
156
+ * the compiler rendered it.
157
+ */
158
+ function orderTextMembers(members) {
159
+ return members
160
+ .map((text, index) => ({ text: text.trim(), index }))
161
+ .sort((a, b) => {
162
+ if (a.text !== b.text)
163
+ return a.text < b.text ? -1 : 1;
164
+ return a.index - b.index;
165
+ })
166
+ .map((entry) => entry.text);
167
+ }
168
+ /**
169
+ * The spans between depth-zero occurrences of `separator`. Depth counts the
170
+ * four bracket pairs; quoted runs are skipped whole, so a separator inside a
171
+ * string literal never splits.
172
+ */
173
+ function splitTopLevelSpans(text, separator) {
174
+ const spans = [];
175
+ let start = 0;
176
+ let depth = 0;
177
+ let i = 0;
178
+ while (i < text.length) {
179
+ const quoted = readQuoted(text, i);
180
+ if (quoted !== null) {
181
+ i = quoted;
182
+ continue;
183
+ }
184
+ const ch = text[i];
185
+ if (CLOSERS[ch] !== undefined) {
186
+ depth++;
187
+ }
188
+ else if (ch === '}' || ch === ')' || ch === ']' || isTypeArgClose(text, i)) {
189
+ depth = Math.max(0, depth - 1);
190
+ }
191
+ else if (ch === separator && depth === 0) {
192
+ spans.push({ start, end: i });
193
+ start = i + 1;
194
+ }
195
+ i++;
196
+ }
197
+ spans.push({ start, end: text.length });
198
+ return spans;
199
+ }
200
+ /** Split at every depth-zero occurrence of `separator`. */
201
+ function splitTopLevel(text, separator) {
202
+ return splitTopLevelSpans(text, separator).map((span) => text.slice(span.start, span.end));
203
+ }
204
+ /** Index of the first depth-zero `char`, or -1. */
205
+ function indexOfTopLevel(text, char) {
206
+ const parts = splitTopLevel(text, char);
207
+ return parts.length > 1 ? parts[0].length : -1;
208
+ }
209
+ /** True when any of `tokens` occurs at depth zero. */
210
+ function hasTopLevel(text, tokens) {
211
+ for (const token of tokens) {
212
+ if (token === '=>') {
213
+ // Split on '>' would fight the generic-close rule; look for the arrow
214
+ // directly, at depth zero, by walking with the same scanner.
215
+ if (indexOfTopLevelArrow(text) !== -1)
216
+ return true;
217
+ continue;
218
+ }
219
+ if (indexOfTopLevel(text, token) !== -1)
220
+ return true;
221
+ }
222
+ return false;
223
+ }
224
+ /** Index of a depth-zero `=>`, or -1. */
225
+ function indexOfTopLevelArrow(text) {
226
+ let depth = 0;
227
+ let i = 0;
228
+ while (i < text.length) {
229
+ const quoted = readQuoted(text, i);
230
+ if (quoted !== null) {
231
+ i = quoted;
232
+ continue;
233
+ }
234
+ const ch = text[i];
235
+ if (ch === '=' && text[i + 1] === '>') {
236
+ if (depth === 0)
237
+ return i;
238
+ i += 2;
239
+ continue;
240
+ }
241
+ if (CLOSERS[ch] !== undefined)
242
+ depth++;
243
+ else if (ch === '}' || ch === ')' || ch === ']' || isTypeArgClose(text, i))
244
+ depth = Math.max(0, depth - 1);
245
+ i++;
246
+ }
247
+ return -1;
248
+ }
249
+ /** A `>` that closes a type-argument list, rather than the tail of an arrow. */
250
+ function isTypeArgClose(text, i) {
251
+ return text[i] === '>' && text[i - 1] !== '=';
252
+ }
253
+ /**
254
+ * If a quoted run (string literal or template literal) starts at `i`, the
255
+ * index just past its closing quote; otherwise null. Escapes are honoured so a
256
+ * `"a\"|b"` literal is never split.
257
+ */
258
+ function readQuoted(text, i) {
259
+ const quote = text[i];
260
+ if (quote !== '"' && quote !== "'" && quote !== '`')
261
+ return null;
262
+ let j = i + 1;
263
+ while (j < text.length) {
264
+ if (text[j] === '\\') {
265
+ j += 2;
266
+ continue;
267
+ }
268
+ if (text[j] === quote)
269
+ return j + 1;
270
+ j++;
271
+ }
272
+ return text.length;
273
+ }
274
+ /** Index of the bracket closing the one at `open`, or -1 when unbalanced. */
275
+ function matchBracket(text, open) {
276
+ const closer = CLOSERS[text[open]];
277
+ let depth = 0;
278
+ let i = open;
279
+ while (i < text.length) {
280
+ const quoted = readQuoted(text, i);
281
+ if (quoted !== null) {
282
+ i = quoted;
283
+ continue;
284
+ }
285
+ const ch = text[i];
286
+ if (ch === text[open]) {
287
+ depth++;
288
+ }
289
+ else if (ch === closer && !(closer === '>' && text[i - 1] === '=')) {
290
+ depth--;
291
+ if (depth === 0)
292
+ return i;
293
+ }
294
+ i++;
295
+ }
296
+ return -1;
297
+ }