@tailor-platform/sdk-codemod 0.3.0-next.1 → 0.3.0-next.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.
@@ -3,14 +3,28 @@ import { Lang, parse } from "@ast-grep/napi";
3
3
  const TYPE_RENAME_MAP = {
4
4
  TailorUser: "TailorPrincipal",
5
5
  TailorActor: "TailorPrincipal",
6
+ TailorActorType: "TailorPrincipal",
6
7
  TailorInvoker: "TailorPrincipal"
7
8
  };
8
9
  const UNAUTHENTICATED = "unauthenticatedTailorUser";
9
10
  const QUICK_FILTER_NEEDLES = [
10
11
  ...Object.keys(TYPE_RENAME_MAP),
11
12
  UNAUTHENTICATED,
12
- "createResolver"
13
+ "userId",
14
+ "userType",
15
+ "createResolver",
16
+ ".hooks",
17
+ ".validate",
18
+ ".parse"
13
19
  ];
20
+ const ACTOR_PROPERTY_RENAME_MAP = {
21
+ userId: "id",
22
+ userType: "type"
23
+ };
24
+ const ACTOR_TYPE_LITERAL_RENAME_MAP = {
25
+ USER_TYPE_USER: "user",
26
+ USER_TYPE_MACHINE_USER: "machine_user"
27
+ };
14
28
  function quickFilter(source) {
15
29
  if (!source.includes("@tailor-platform/sdk")) return false;
16
30
  return QUICK_FILTER_NEEDLES.some((needle) => source.includes(needle));
@@ -23,14 +37,284 @@ function isInsideImportStatement(node) {
23
37
  }
24
38
  return false;
25
39
  }
26
- function isMemberExpressionObject(node) {
40
+ function memberObjectParent(node) {
27
41
  const parent = node.parent();
28
- if (!parent || parent.kind() !== "member_expression") return false;
42
+ if (!parent || parent.kind() !== "member_expression" && parent.kind() !== "subscript_expression") return null;
29
43
  const obj = parent.field("object");
30
- if (!obj) return false;
44
+ if (!obj) return null;
31
45
  const r = node.range();
32
46
  const or = obj.range();
33
- return r.start.index === or.start.index && r.end.index === or.end.index;
47
+ if (r.start.index !== or.start.index || r.end.index !== or.end.index) return null;
48
+ return parent;
49
+ }
50
+ function isMemberExpressionObject(node) {
51
+ return memberObjectParent(node) !== null;
52
+ }
53
+ function optionalPrincipalReadKind(node) {
54
+ const parent = memberObjectParent(node);
55
+ if (!parent) return null;
56
+ if (parent.text().startsWith(`${node.text()}?.`)) return null;
57
+ if (isAssignmentTargetReference(node)) return null;
58
+ if (parent.kind() === "subscript_expression") return "computed";
59
+ return parent.field("property")?.kind() === "property_identifier" ? "property" : null;
60
+ }
61
+ function isOptionalizableMemberObject(node) {
62
+ return optionalPrincipalReadKind(node) !== null;
63
+ }
64
+ function principalIdentifierReplacement(node, name) {
65
+ const readKind = optionalPrincipalReadKind(node);
66
+ if (readKind === "computed") return `${name}?.`;
67
+ if (readKind === "property") return `${name}?`;
68
+ return name;
69
+ }
70
+ function principalPropertyReplacement(node, name) {
71
+ const parent = node.parent();
72
+ return parent ? principalIdentifierReplacement(parent, name) : name;
73
+ }
74
+ function isObjectDestructureInitializer(node) {
75
+ const parent = node.parent();
76
+ if (!parent || parent.kind() !== "variable_declarator") return false;
77
+ const value = parent.field("value");
78
+ if (!value) return false;
79
+ const valueRange = value.range();
80
+ const nodeRange = node.range();
81
+ if (valueRange.start.index !== nodeRange.start.index || valueRange.end.index !== nodeRange.end.index) return false;
82
+ return parent.field("name")?.kind() === "object_pattern";
83
+ }
84
+ function principalReadReplacement(node, name) {
85
+ return isObjectDestructureInitializer(node) ? `${name} ?? {}` : principalIdentifierReplacement(node, name);
86
+ }
87
+ function nodeRangeContains(outer, inner) {
88
+ const outerRange = outer.range();
89
+ const innerRange = inner.range();
90
+ return innerRange.start.index >= outerRange.start.index && innerRange.end.index <= outerRange.end.index;
91
+ }
92
+ function isAssignmentTargetReference(node) {
93
+ let current = node;
94
+ let parent = current.parent();
95
+ while (parent && (parent.kind() === "member_expression" || parent.kind() === "subscript_expression")) {
96
+ const object = parent.field("object");
97
+ if (!object || !nodeRangeContains(object, current)) break;
98
+ current = parent;
99
+ parent = current.parent();
100
+ }
101
+ if (!parent) return false;
102
+ if (parent.kind() === "update_expression") return true;
103
+ if (parent.kind() !== "assignment_expression" && parent.kind() !== "augmented_assignment_expression") return false;
104
+ const left = parent.field("left");
105
+ return !!left && nodeRangeContains(left, current);
106
+ }
107
+ function parseArgumentCall(node) {
108
+ if (node.kind() !== "shorthand_property_identifier") return null;
109
+ const object = node.parent();
110
+ if (!object || object.kind() !== "object") return null;
111
+ const args = object.parent();
112
+ if (!args || args.kind() !== "arguments") return null;
113
+ const call = args.parent();
114
+ return call && call.kind() === "call_expression" && findMemberCallName(call) === "parse" ? call : null;
115
+ }
116
+ function isSdkFieldParseArgumentShorthand(node, parseContext) {
117
+ const call = parseArgumentCall(node);
118
+ return call ? isSdkFieldMemberCall(call, parseContext.sdkFieldRootNames, parseContext.sdkFieldLocalBindings, parseContext.root) : false;
119
+ }
120
+ function addActorPropertyReplacement(property, edits, transformedActorPropertyStarts) {
121
+ const newName = ACTOR_PROPERTY_RENAME_MAP[property.text()];
122
+ if (!newName) return;
123
+ const start = property.range().start.index;
124
+ if (transformedActorPropertyStarts.has(start)) return;
125
+ transformedActorPropertyStarts.add(start);
126
+ edits.push(property.replace(newName));
127
+ }
128
+ function actorTypeLiteralReplacement(literal) {
129
+ const match = literal.text().match(/^(['"])(USER_TYPE_USER|USER_TYPE_MACHINE_USER|USER_TYPE_UNSPECIFIED)\1$/);
130
+ if (!match) return null;
131
+ const [, quote, value] = match;
132
+ if (value === "USER_TYPE_UNSPECIFIED") return "undefined";
133
+ return `${quote}${ACTOR_TYPE_LITERAL_RENAME_MAP[value]}${quote}`;
134
+ }
135
+ function addActorTypeLiteralReplacement(literal, edits, transformedLiteralStarts) {
136
+ if (literal.kind() !== "string") return;
137
+ const replacement = actorTypeLiteralReplacement(literal);
138
+ if (!replacement) return;
139
+ const start = literal.range().start.index;
140
+ if (transformedLiteralStarts.has(start)) return;
141
+ transformedLiteralStarts.add(start);
142
+ edits.push(literal.replace(replacement));
143
+ }
144
+ function transformActorTypeLiteralsInNode(node, edits, transformedLiteralStarts) {
145
+ if (node.kind() === "string") addActorTypeLiteralReplacement(node, edits, transformedLiteralStarts);
146
+ const literals = node.findAll({ rule: { kind: "string" } });
147
+ for (const literal of literals) addActorTypeLiteralReplacement(literal, edits, transformedLiteralStarts);
148
+ }
149
+ function isTransformedActorTypeMember(node, transformedActorPropertyStarts) {
150
+ if (node.kind() !== "member_expression") return false;
151
+ const property = node.field("property");
152
+ return property?.text() === "userType" && transformedActorPropertyStarts.has(property.range().start.index);
153
+ }
154
+ function nodeContainsTransformedActorTypeMember(node, transformedActorPropertyStarts) {
155
+ if (isTransformedActorTypeMember(node, transformedActorPropertyStarts)) return true;
156
+ return node.findAll({ rule: { kind: "member_expression" } }).some((member) => isTransformedActorTypeMember(member, transformedActorPropertyStarts));
157
+ }
158
+ function switchDiscriminant(node) {
159
+ return node.children().find((child) => child.kind() === "parenthesized_expression") ?? null;
160
+ }
161
+ function switchBody(node) {
162
+ return node.children().find((child) => child.kind() === "switch_body") ?? null;
163
+ }
164
+ function transformActorTypeComparisonLiterals(root, edits, transformedActorPropertyStarts) {
165
+ if (transformedActorPropertyStarts.size === 0) return;
166
+ const transformedLiteralStarts = /* @__PURE__ */ new Set();
167
+ const binaries = root.findAll({ rule: { kind: "binary_expression" } });
168
+ for (const binary of binaries) {
169
+ if (!binary.children().some((child) => isTransformedActorTypeMember(child, transformedActorPropertyStarts))) continue;
170
+ for (const child of binary.children()) addActorTypeLiteralReplacement(child, edits, transformedLiteralStarts);
171
+ }
172
+ const switches = root.findAll({ rule: { kind: "switch_statement" } });
173
+ for (const switchNode of switches) {
174
+ const discriminant = switchDiscriminant(switchNode);
175
+ if (!discriminant || !nodeContainsTransformedActorTypeMember(discriminant, transformedActorPropertyStarts)) continue;
176
+ const body = switchBody(switchNode);
177
+ if (!body) continue;
178
+ const cases = body.findAll({ rule: { kind: "switch_case" } });
179
+ for (const caseNode of cases) for (const child of caseNode.children()) addActorTypeLiteralReplacement(child, edits, transformedLiteralStarts);
180
+ }
181
+ }
182
+ function transformTailorActorTypeInitializerLiterals(root, actorTypeLocalNames, sdkNamespaceNames, edits) {
183
+ if (actorTypeLocalNames.size === 0 && sdkNamespaceNames.size === 0) return;
184
+ const transformedLiteralStarts = /* @__PURE__ */ new Set();
185
+ const declarators = root.findAll({ rule: { kind: "variable_declarator" } });
186
+ for (const decl of declarators) {
187
+ if (!isSdkTypeReference(decl, actorTypeLocalNames, "TailorActorType", sdkNamespaceNames, root)) continue;
188
+ const value = decl.field("value");
189
+ if (!value) continue;
190
+ transformActorTypeLiteralsInNode(value, edits, transformedLiteralStarts);
191
+ }
192
+ }
193
+ function transformActorTypeBindingComparisons(root, binding, edits) {
194
+ const refs = root.findAll({ rule: {
195
+ kind: "identifier",
196
+ regex: `^${escapeRegex(binding.name)}$`
197
+ } });
198
+ const transformedLiteralStarts = /* @__PURE__ */ new Set();
199
+ for (const ref of refs) {
200
+ const binary = ref.parent();
201
+ if (!binary || binary.kind() !== "binary_expression") continue;
202
+ if (isShadowedLocalReference(root, binding.name, ref.range().start.index, binding.bindingStart)) continue;
203
+ for (const child of binary.children()) addActorTypeLiteralReplacement(child, edits, transformedLiteralStarts);
204
+ }
205
+ const switches = root.findAll({ rule: { kind: "switch_statement" } });
206
+ for (const switchNode of switches) {
207
+ const discriminant = switchDiscriminant(switchNode);
208
+ if (!discriminant) continue;
209
+ if (!discriminant.findAll({ rule: {
210
+ kind: "identifier",
211
+ regex: `^${escapeRegex(binding.name)}$`
212
+ } }).some((ref) => !isShadowedLocalReference(root, binding.name, ref.range().start.index, binding.bindingStart))) continue;
213
+ const body = switchBody(switchNode);
214
+ if (!body) continue;
215
+ const cases = body.findAll({ rule: { kind: "switch_case" } });
216
+ for (const caseNode of cases) for (const child of caseNode.children()) addActorTypeLiteralReplacement(child, edits, transformedLiteralStarts);
217
+ }
218
+ }
219
+ function transformTailorActorTypeBindingComparisons(root, actorTypeLocalNames, sdkNamespaceNames, edits) {
220
+ if (actorTypeLocalNames.size === 0 && sdkNamespaceNames.size === 0) return;
221
+ for (const kind of NESTED_FN_KINDS) {
222
+ const fns = root.findAll({ rule: { kind } });
223
+ for (const fn of fns) {
224
+ const param = getFirstFunctionParam(fn);
225
+ if (!param || !isSdkTypeReference(param, actorTypeLocalNames, "TailorActorType", sdkNamespaceNames, root)) continue;
226
+ const pattern = getFunctionParamPattern(param);
227
+ const body = fn.field("body");
228
+ if (!pattern || pattern.kind() !== "identifier" || !body) continue;
229
+ transformActorTypeBindingComparisons(body, {
230
+ name: pattern.text(),
231
+ bindingStart: pattern.range().start.index
232
+ }, edits);
233
+ }
234
+ }
235
+ const declarators = root.findAll({ rule: { kind: "variable_declarator" } });
236
+ for (const decl of declarators) {
237
+ if (!isSdkTypeReference(decl, actorTypeLocalNames, "TailorActorType", sdkNamespaceNames, root)) continue;
238
+ const name = decl.field("name");
239
+ if (!name || name.kind() !== "identifier") continue;
240
+ transformActorTypeBindingComparisons(root, {
241
+ name: name.text(),
242
+ bindingStart: name.range().start.index
243
+ }, edits);
244
+ }
245
+ }
246
+ function transformActorBindingMemberAccesses(root, binding, edits, transformedActorPropertyStarts) {
247
+ const refs = root.findAll({ rule: {
248
+ kind: "identifier",
249
+ regex: `^${escapeRegex(binding.name)}$`
250
+ } });
251
+ for (const ref of refs) {
252
+ const parent = ref.parent();
253
+ if (!parent || parent.kind() !== "member_expression") continue;
254
+ const object = parent.field("object");
255
+ if (!object || object.range().start.index !== ref.range().start.index) continue;
256
+ const property = parent.field("property");
257
+ if (!property || property.kind() !== "property_identifier") continue;
258
+ if (!ACTOR_PROPERTY_RENAME_MAP[property.text()]) continue;
259
+ const pos = ref.range().start.index;
260
+ if (isShadowedLocalReference(root, binding.name, pos, binding.bindingStart)) continue;
261
+ addActorPropertyReplacement(property, edits, transformedActorPropertyStarts);
262
+ }
263
+ }
264
+ function isSdkTypeReference(node, localNames, sdkTypeName, sdkNamespaceNames, root) {
265
+ const typeAnnotation = node.field("type");
266
+ if (!typeAnnotation) return false;
267
+ return typeAnnotation.findAll({ rule: { kind: "type_identifier" } }).some((id) => localNames.has(id.text()) || id.text() === sdkTypeName && isSdkNamespaceQualifiedTypeIdentifier(id, sdkNamespaceNames, root));
268
+ }
269
+ function transformTailorActorTypedMemberAccesses(root, actorTypeLocalNames, sdkNamespaceNames, edits, transformedActorPropertyStarts) {
270
+ if (actorTypeLocalNames.size === 0 && sdkNamespaceNames.size === 0) return;
271
+ for (const kind of NESTED_FN_KINDS) {
272
+ const fns = root.findAll({ rule: { kind } });
273
+ for (const fn of fns) {
274
+ const param = getFirstFunctionParam(fn);
275
+ if (!param || !isSdkTypeReference(param, actorTypeLocalNames, "TailorActor", sdkNamespaceNames, root)) continue;
276
+ const pattern = getFunctionParamPattern(param);
277
+ const body = fn.field("body");
278
+ if (!pattern || pattern.kind() !== "identifier" || !body) continue;
279
+ transformActorBindingMemberAccesses(body, {
280
+ name: pattern.text(),
281
+ bindingStart: pattern.range().start.index
282
+ }, edits, transformedActorPropertyStarts);
283
+ }
284
+ }
285
+ const declarators = root.findAll({ rule: { kind: "variable_declarator" } });
286
+ for (const decl of declarators) {
287
+ if (!isSdkTypeReference(decl, actorTypeLocalNames, "TailorActor", sdkNamespaceNames, root)) continue;
288
+ const name = decl.field("name");
289
+ if (!name || name.kind() !== "identifier") continue;
290
+ transformActorBindingMemberAccesses(root, {
291
+ name: name.text(),
292
+ bindingStart: name.range().start.index
293
+ }, edits, transformedActorPropertyStarts);
294
+ }
295
+ }
296
+ function transformExecutorCtxActorAccesses(body, ctxName, ctxShadowRanges, edits, transformedActorPropertyStarts) {
297
+ const properties = body.findAll({ rule: {
298
+ kind: "property_identifier",
299
+ regex: "^(userId|userType)$"
300
+ } });
301
+ for (const property of properties) {
302
+ const parent = property.parent();
303
+ if (!parent || parent.kind() !== "member_expression") continue;
304
+ const object = parent.field("object");
305
+ if (!object || object.kind() !== "member_expression") continue;
306
+ if (object.field("property")?.text() !== "actor") continue;
307
+ const ctxObject = object.field("object");
308
+ if (!ctxObject || ctxObject.kind() !== "identifier" || ctxObject.text() !== ctxName) continue;
309
+ const pos = ctxObject.range().start.index;
310
+ if (isInsideAnyRange(pos, ctxShadowRanges)) continue;
311
+ addActorPropertyReplacement(property, edits, transformedActorPropertyStarts);
312
+ }
313
+ }
314
+ function renamedTypeIdentifierText(name) {
315
+ if (name === "TailorInvoker") return "(TailorPrincipal | null)";
316
+ if (name === "TailorActorType") return "(TailorPrincipal[\"type\"] | undefined)";
317
+ return TYPE_RENAME_MAP[name] ?? null;
34
318
  }
35
319
  function extractModuleSource(importText) {
36
320
  return importText.match(/from\s+(["'])([^"']+)\1/)?.[2] ?? "@tailor-platform/sdk";
@@ -59,7 +343,7 @@ function* iterateImportSpecs(importStmt) {
59
343
  };
60
344
  }
61
345
  }
62
- function rebuildImportStatement(importStmt, globalEmittedRenamed, unauthenticatedLocalNames) {
346
+ function rebuildImportStatement(importStmt, globalEmittedRenamed, unauthenticatedLocalNames, nullableInvokerAliasLocalNames, actorTypeAliasLocalNames) {
63
347
  const importText = importStmt.text();
64
348
  const isImportType = /^\s*import\s+type\b/.test(importText);
65
349
  const trailingSemi = importText.trimEnd().endsWith(";") ? ";" : "";
@@ -73,12 +357,17 @@ function rebuildImportStatement(importStmt, globalEmittedRenamed, unauthenticate
73
357
  const renamed = TYPE_RENAME_MAP[importedName];
74
358
  if (renamed) {
75
359
  touched = true;
76
- const finalLocal = aliasNode?.text() ?? renamed;
360
+ const dropsAliasForNullableInvoker = importedName === "TailorInvoker" && !!aliasNode;
361
+ const dropsAliasForActorType = importedName === "TailorActorType" && !!aliasNode;
362
+ const dropsAliasForExpandedType = dropsAliasForNullableInvoker || dropsAliasForActorType;
363
+ if (dropsAliasForNullableInvoker) nullableInvokerAliasLocalNames.add(localName);
364
+ if (dropsAliasForActorType) actorTypeAliasLocalNames.add(localName);
365
+ const finalLocal = dropsAliasForExpandedType ? renamed : aliasNode?.text() ?? renamed;
77
366
  if (seenLocal.has(finalLocal)) continue;
78
- if (!aliasNode && globalEmittedRenamed.has(renamed)) continue;
367
+ if ((!aliasNode || dropsAliasForExpandedType) && globalEmittedRenamed.has(renamed)) continue;
79
368
  seenLocal.add(finalLocal);
80
- if (!aliasNode) globalEmittedRenamed.add(renamed);
81
- const asPart = aliasNode ? ` as ${aliasNode.text()}` : "";
369
+ if (!aliasNode || dropsAliasForExpandedType) globalEmittedRenamed.add(renamed);
370
+ const asPart = aliasNode && !dropsAliasForExpandedType ? ` as ${aliasNode.text()}` : "";
82
371
  newSpecTexts.push(`${isTypeOnly ? "type " : ""}${renamed}${asPart}`);
83
372
  } else if (importedName === UNAUTHENTICATED) {
84
373
  touched = true;
@@ -160,6 +449,15 @@ function patternBindsName(pat, name) {
160
449
  }
161
450
  return false;
162
451
  }
452
+ function hasNestedPropertyPattern(pat, name) {
453
+ if (pat.kind() !== "object_pattern") return false;
454
+ for (const child of pat.children()) {
455
+ if (child.kind() !== "pair_pattern") continue;
456
+ if (child.field("key")?.text() !== name) continue;
457
+ if (child.field("value")?.kind() !== "identifier") return true;
458
+ }
459
+ return false;
460
+ }
163
461
  function functionRebindsName(fn, name) {
164
462
  const single = fn.field("parameter");
165
463
  if (single && patternBindsName(single, name)) return true;
@@ -235,6 +533,62 @@ function collectAllShadowRanges(root, name) {
235
533
  }
236
534
  return ranges;
237
535
  }
536
+ function hasUnshadowedIdentifierReference(root, name) {
537
+ const shadowRanges = collectAllShadowRanges(root, name);
538
+ const refs = root.findAll({ rule: {
539
+ kind: "identifier",
540
+ regex: `^${escapeRegex(name)}$`
541
+ } });
542
+ for (const ref of refs) if (!isInsideAnyRange(ref.range().start.index, shadowRanges)) return true;
543
+ return false;
544
+ }
545
+ function hasPrincipalAssignmentTarget(root, name) {
546
+ const shadowRanges = collectAllShadowRanges(root, name);
547
+ const refs = root.findAll({ rule: {
548
+ kind: "identifier",
549
+ regex: `^${escapeRegex(name)}$`
550
+ } });
551
+ for (const ref of refs) {
552
+ const pos = ref.range().start.index;
553
+ if (isInsideAnyRange(pos, shadowRanges)) continue;
554
+ if (isAssignmentTargetReference(ref)) return true;
555
+ }
556
+ return false;
557
+ }
558
+ function rewriteParseArgumentShorthands(root, localName, propertyName, parseContext, edits) {
559
+ const shadowRanges = collectAllShadowRanges(root, localName);
560
+ const shortRefs = root.findAll({ rule: {
561
+ kind: "shorthand_property_identifier",
562
+ regex: `^${escapeRegex(localName)}$`
563
+ } });
564
+ for (const ref of shortRefs) {
565
+ const pos = ref.range().start.index;
566
+ if (isInsideAnyRange(pos, shadowRanges)) continue;
567
+ if (!isSdkFieldParseArgumentShorthand(ref, parseContext)) continue;
568
+ edits.push(ref.replace(`${propertyName}: ${localName}`));
569
+ }
570
+ }
571
+ function guardPrincipalMemberAccesses(root, binding, edits) {
572
+ const refs = root.findAll({ rule: {
573
+ kind: "identifier",
574
+ regex: `^${escapeRegex(binding.name)}$`
575
+ } });
576
+ for (const ref of refs) {
577
+ if (!isOptionalizableMemberObject(ref)) continue;
578
+ const pos = ref.range().start.index;
579
+ if (isShadowedLocalReference(root, binding.name, pos, binding.bindingStart)) continue;
580
+ edits.push(ref.replace(`${binding.name}?`));
581
+ }
582
+ const declarators = root.findAll({ rule: { kind: "variable_declarator" } });
583
+ for (const decl of declarators) {
584
+ const value = decl.field("value");
585
+ if (!value || value.kind() !== "identifier" || value.text() !== binding.name) continue;
586
+ if (!isObjectDestructureInitializer(value)) continue;
587
+ const pos = value.range().start.index;
588
+ if (isShadowedLocalReference(root, binding.name, pos, binding.bindingStart)) continue;
589
+ edits.push(value.replace(`${binding.name} ?? {}`));
590
+ }
591
+ }
238
592
  function findResolverBodyArrow(call) {
239
593
  const args = call.field("arguments");
240
594
  if (!args) return null;
@@ -249,6 +603,45 @@ function findResolverBodyArrow(call) {
249
603
  }
250
604
  return null;
251
605
  }
606
+ function* iterateNamespaceImportLocalNames(importStmt) {
607
+ const namespaceImports = importStmt.findAll({ rule: { kind: "namespace_import" } });
608
+ for (const namespaceImport of namespaceImports) {
609
+ const localName = namespaceImport.children().find((c) => c.kind() === "identifier");
610
+ if (localName) yield localName.text();
611
+ }
612
+ }
613
+ function isUnshadowedNamespaceObject(node, namespaceNames, root) {
614
+ if (node.kind() !== "identifier" || !namespaceNames.has(node.text())) return false;
615
+ return !isInsideAnyRange(node.range().start.index, collectAllShadowRanges(root, node.text()));
616
+ }
617
+ function isSdkNamespaceQualifiedTypeIdentifier(typeId, namespaceNames, root) {
618
+ if (typeId.kind() !== "type_identifier") return false;
619
+ const parent = typeId.parent();
620
+ if (!parent || parent.kind() !== "nested_type_identifier") return false;
621
+ const namespaceObject = parent.children().find((c) => c.kind() === "identifier");
622
+ return !!namespaceObject && isUnshadowedNamespaceObject(namespaceObject, namespaceNames, root);
623
+ }
624
+ function namespaceQualifiedTypeReplacement(typeId, namespaceNames, root) {
625
+ if (typeId.kind() !== "type_identifier") return null;
626
+ const parent = typeId.parent();
627
+ if (!parent || parent.kind() !== "nested_type_identifier") return null;
628
+ const namespaceObject = parent.children().find((c) => c.kind() === "identifier");
629
+ if (!namespaceObject || !isUnshadowedNamespaceObject(namespaceObject, namespaceNames, root)) return null;
630
+ const namespace = namespaceObject.text();
631
+ if (typeId.text() === "TailorInvoker") return {
632
+ target: parent,
633
+ text: `(${namespace}.TailorPrincipal | null)`
634
+ };
635
+ if (typeId.text() === "TailorActorType") return {
636
+ target: parent,
637
+ text: `(${namespace}.TailorPrincipal["type"] | undefined)`
638
+ };
639
+ const renamed = TYPE_RENAME_MAP[typeId.text()];
640
+ return renamed ? {
641
+ target: typeId,
642
+ text: renamed
643
+ } : null;
644
+ }
252
645
  /**
253
646
  * Look for any binding named `caller` in the resolver body or pattern. When
254
647
  * one exists, renaming `user` → `caller` would either shadow it, collide with
@@ -270,23 +663,20 @@ function hasCallerBindingConflict(pattern, body) {
270
663
  if (inner && inner.text() === "caller") return true;
271
664
  }
272
665
  }
273
- if (body.findAll({ rule: {
274
- kind: "identifier",
275
- regex: "^caller$",
276
- inside: { kind: "variable_declarator" }
277
- } }).length > 0) return true;
278
- if (body.findAll({ rule: {
279
- kind: "shorthand_property_identifier_pattern",
280
- regex: "^caller$",
281
- inside: { kind: "variable_declarator" }
282
- } }).length > 0) return true;
666
+ const decls = body.findAll({ rule: { kind: "variable_declarator" } });
667
+ for (const decl of decls) {
668
+ const nameNode = decl.field("name");
669
+ if (nameNode && patternBindsName(nameNode, "caller")) return true;
670
+ }
671
+ const functionDecls = body.findAll({ rule: { kind: "function_declaration" } });
672
+ for (const fn of functionDecls) if (fn.field("name")?.text() === "caller") return true;
283
673
  for (const k of NESTED_FN_KINDS) {
284
674
  const fns = body.findAll({ rule: { kind: k } });
285
675
  for (const fn of fns) if (functionRebindsName(fn, "caller")) return true;
286
676
  }
287
677
  return false;
288
678
  }
289
- function transformResolverBody(arrowNode, edits) {
679
+ function transformResolverBody(arrowNode, edits, parseContext) {
290
680
  const params = arrowNode.field("parameters") ?? arrowNode.field("parameter") ?? arrowNode.children().find((c) => c.kind() === "formal_parameters");
291
681
  const body = arrowNode.field("body");
292
682
  if (!params || !body) return;
@@ -300,24 +690,52 @@ function transformResolverBody(arrowNode, edits) {
300
690
  }
301
691
  if (!pattern) return;
302
692
  if (pattern.kind() === "object_pattern") {
693
+ if (hasNestedPropertyPattern(pattern, "user")) return;
694
+ if (hasPrincipalAssignmentTarget(body, "user")) return;
303
695
  if (hasCallerBindingConflict(pattern, body)) return;
696
+ const aliasRenamedUser = hasUnshadowedIdentifierReference(body, "caller");
697
+ let aliasedShorthandUser = false;
304
698
  let renamedShorthandUser = false;
699
+ const principalAliasBindings = [];
305
700
  for (const child of pattern.children()) {
306
701
  const kind = child.kind();
307
- if (kind === "shorthand_property_identifier_pattern" && child.text() === "user") {
702
+ if (kind === "shorthand_property_identifier_pattern" && child.text() === "user") if (aliasRenamedUser) {
703
+ edits.push(child.replace("caller: user"));
704
+ aliasedShorthandUser = true;
705
+ principalAliasBindings.push({
706
+ name: "user",
707
+ bindingStart: child.range().start.index
708
+ });
709
+ } else {
308
710
  edits.push(child.replace("caller"));
309
711
  renamedShorthandUser = true;
310
- } else if (kind === "pair_pattern") {
712
+ }
713
+ else if (kind === "pair_pattern") {
311
714
  const key = child.field("key");
312
- if (key && key.text() === "user") edits.push(key.replace("caller"));
715
+ if (key && key.text() === "user") {
716
+ edits.push(key.replace("caller"));
717
+ const value = child.field("value");
718
+ if (value?.kind() === "identifier") principalAliasBindings.push({
719
+ name: value.text(),
720
+ bindingStart: value.range().start.index
721
+ });
722
+ }
313
723
  } else if (kind === "object_assignment_pattern") {
314
724
  const inner = child.children().find((c) => c.kind() === "shorthand_property_identifier_pattern");
315
- if (inner && inner.text() === "user") {
725
+ if (inner && inner.text() === "user") if (aliasRenamedUser) {
726
+ edits.push(inner.replace("caller: user"));
727
+ aliasedShorthandUser = true;
728
+ principalAliasBindings.push({
729
+ name: "user",
730
+ bindingStart: inner.range().start.index
731
+ });
732
+ } else {
316
733
  edits.push(inner.replace("caller"));
317
734
  renamedShorthandUser = true;
318
735
  }
319
736
  }
320
737
  }
738
+ if (aliasedShorthandUser) rewriteParseArgumentShorthands(body, "user", "invoker", parseContext, edits);
321
739
  if (renamedShorthandUser) {
322
740
  const shadowRanges = collectAllShadowRanges(body, "user");
323
741
  const refs = body.findAll({ rule: {
@@ -327,7 +745,7 @@ function transformResolverBody(arrowNode, edits) {
327
745
  for (const ref of refs) {
328
746
  const pos = ref.range().start.index;
329
747
  if (isInsideAnyRange(pos, shadowRanges)) continue;
330
- edits.push(ref.replace("caller"));
748
+ edits.push(ref.replace(principalIdentifierReplacement(ref, "caller")));
331
749
  }
332
750
  const shortRefs = body.findAll({ rule: {
333
751
  kind: "shorthand_property_identifier",
@@ -336,9 +754,10 @@ function transformResolverBody(arrowNode, edits) {
336
754
  for (const ref of shortRefs) {
337
755
  const pos = ref.range().start.index;
338
756
  if (isInsideAnyRange(pos, shadowRanges)) continue;
339
- edits.push(ref.replace("user: caller"));
757
+ edits.push(ref.replace(isSdkFieldParseArgumentShorthand(ref, parseContext) ? "invoker: caller" : "user: caller"));
340
758
  }
341
759
  }
760
+ for (const binding of principalAliasBindings) guardPrincipalMemberAccesses(body, binding, edits);
342
761
  return;
343
762
  }
344
763
  const ctxName = pattern.text();
@@ -354,7 +773,7 @@ function transformResolverBody(arrowNode, edits) {
354
773
  if (!(obj && obj.kind() === "identifier" && obj.text() === ctxName)) continue;
355
774
  const pos = obj.range().start.index;
356
775
  if (isInsideAnyRange(pos, ctxShadowRanges)) continue;
357
- edits.push(propId.replace("caller"));
776
+ edits.push(propId.replace(principalPropertyReplacement(propId, "caller")));
358
777
  }
359
778
  const ctxDestructures = body.findAll({ rule: {
360
779
  kind: "variable_declarator",
@@ -364,6 +783,7 @@ function transformResolverBody(arrowNode, edits) {
364
783
  regex: `^${escapeRegex(ctxName)}$`
365
784
  }
366
785
  } });
786
+ const principalAliasBindings = [];
367
787
  for (const decl of ctxDestructures) {
368
788
  const pos = decl.range().start.index;
369
789
  if (isInsideAnyRange(pos, ctxShadowRanges)) continue;
@@ -371,13 +791,556 @@ function transformResolverBody(arrowNode, edits) {
371
791
  if (!pat || pat.kind() !== "object_pattern") continue;
372
792
  for (const child of pat.children()) {
373
793
  const k = child.kind();
374
- if (k === "shorthand_property_identifier_pattern" && child.text() === "user") edits.push(child.replace("caller: user"));
375
- else if (k === "pair_pattern") {
794
+ if (k === "shorthand_property_identifier_pattern" && child.text() === "user") {
795
+ edits.push(child.replace("caller: user"));
796
+ principalAliasBindings.push({
797
+ name: "user",
798
+ bindingStart: child.range().start.index
799
+ });
800
+ } else if (k === "pair_pattern") {
376
801
  const key = child.field("key");
377
- if (key && key.text() === "user") edits.push(key.replace("caller"));
802
+ const value = child.field("value");
803
+ if (key && key.text() === "user" && value?.kind() === "identifier") {
804
+ edits.push(key.replace("caller"));
805
+ principalAliasBindings.push({
806
+ name: value.text(),
807
+ bindingStart: value.range().start.index
808
+ });
809
+ }
810
+ }
811
+ }
812
+ }
813
+ for (const binding of principalAliasBindings) guardPrincipalMemberAccesses(body, binding, edits);
814
+ }
815
+ function findMemberCallName(call) {
816
+ const fn = call.field("function");
817
+ if (!fn || fn.kind() !== "member_expression") return null;
818
+ return fn.field("property")?.text() ?? null;
819
+ }
820
+ function findMemberCallObject(call) {
821
+ const fn = call.field("function");
822
+ if (!fn || fn.kind() !== "member_expression") return null;
823
+ return fn.field("object");
824
+ }
825
+ function isFunctionNode(node) {
826
+ return node.kind() === "arrow_function" || node.kind() === "function_declaration" || node.kind() === "function_expression" || node.kind() === "method_definition";
827
+ }
828
+ function propertyName(node) {
829
+ return node.field("key")?.text() ?? node.field("name")?.text() ?? null;
830
+ }
831
+ function getFirstFunctionParam(fn) {
832
+ const params = fn.field("parameters") ?? fn.field("parameter") ?? fn.children().find((c) => c.kind() === "formal_parameters");
833
+ if (!params) return null;
834
+ if (params.kind() === "object_pattern" || params.kind() === "identifier") return params;
835
+ return params.children().find((c) => c.kind() === "required_parameter" || c.kind() === "optional_parameter" || c.kind() === "identifier" || c.kind() === "object_pattern") ?? null;
836
+ }
837
+ function getFunctionParamPattern(param) {
838
+ if (param.kind() === "object_pattern" || param.kind() === "identifier") return param;
839
+ return param.field("pattern");
840
+ }
841
+ function findExecutorBodyFunctions(call) {
842
+ const objArg = call.field("arguments")?.children().find((c) => c.kind() === "object");
843
+ if (!objArg) return [];
844
+ const functions = [];
845
+ const visitObject = (object) => {
846
+ for (const child of object.children()) {
847
+ if (child.kind() === "method_definition") {
848
+ if (propertyName(child) === "body") functions.push(child);
849
+ continue;
850
+ }
851
+ if (child.kind() !== "pair") continue;
852
+ const value = child.field("value");
853
+ if (!value) continue;
854
+ if (child.field("key")?.text() === "body" && isFunctionNode(value)) functions.push(value);
855
+ else if (value.kind() === "object") visitObject(value);
856
+ }
857
+ };
858
+ visitObject(objArg);
859
+ return functions;
860
+ }
861
+ function transformExecutorBodyActorAccesses(fn, edits, transformedActorPropertyStarts) {
862
+ const param = getFirstFunctionParam(fn);
863
+ const pattern = param ? getFunctionParamPattern(param) : null;
864
+ const body = fn.field("body");
865
+ if (!pattern || !body) return;
866
+ if (pattern.kind() === "identifier") {
867
+ const ctxName = pattern.text();
868
+ transformExecutorCtxActorAccesses(body, ctxName, collectCtxShadowRanges(body, ctxName, fn), edits, transformedActorPropertyStarts);
869
+ return;
870
+ }
871
+ if (pattern.kind() !== "object_pattern") return;
872
+ for (const child of pattern.children()) {
873
+ const kind = child.kind();
874
+ if (kind === "shorthand_property_identifier_pattern" && child.text() === "actor") transformActorBindingMemberAccesses(body, {
875
+ name: "actor",
876
+ bindingStart: child.range().start.index
877
+ }, edits, transformedActorPropertyStarts);
878
+ else if (kind === "pair_pattern") {
879
+ const key = child.field("key");
880
+ const value = child.field("value");
881
+ if (key?.text() === "actor" && value?.kind() === "identifier") transformActorBindingMemberAccesses(body, {
882
+ name: value.text(),
883
+ bindingStart: value.range().start.index
884
+ }, edits, transformedActorPropertyStarts);
885
+ }
886
+ }
887
+ }
888
+ function nullableTailorUserTypeReplacement(typeId, typeContext) {
889
+ if (typeContext.tailorUserTypeLocalNames.has(typeId.text())) return typeId.text() === "TailorUser" ? "TailorPrincipal | null" : `${typeId.text()} | null`;
890
+ return isSdkNamespaceQualifiedTypeIdentifier(typeId, typeContext.sdkNamespaceNames, typeContext.root) ? "TailorPrincipal | null" : null;
891
+ }
892
+ function transformObjectTypeUserProperty(objectType, edits, typeContext) {
893
+ for (const child of objectType.children()) {
894
+ if (child.kind() !== "property_signature") continue;
895
+ const name = child.field("name");
896
+ if (name?.text() !== "user") continue;
897
+ edits.push(name.replace("invoker"));
898
+ const typeAnnotation = child.field("type");
899
+ if (!typeAnnotation || /\bnull\b/.test(typeAnnotation.text())) continue;
900
+ const typeIds = typeAnnotation.findAll({ rule: { kind: "type_identifier" } });
901
+ for (const typeId of typeIds) {
902
+ const replacement = nullableTailorUserTypeReplacement(typeId, typeContext);
903
+ if (!replacement) continue;
904
+ typeContext.transformedPrincipalTypeStarts.add(typeId.range().start.index);
905
+ edits.push(typeId.replace(replacement));
906
+ }
907
+ }
908
+ }
909
+ function localCallbackTypeObject(declaration) {
910
+ return declaration.children().find((c) => c.kind() === "object_type" || c.kind() === "interface_body") ?? null;
911
+ }
912
+ function collectLocalCallbackTypeBindings(root) {
913
+ const rootScope = localBindingRootScope(root);
914
+ const bindings = [];
915
+ for (const kind of ["type_alias_declaration", "interface_declaration"]) {
916
+ const declarations = root.findAll({ rule: { kind } });
917
+ for (const declaration of declarations) {
918
+ if (!localCallbackTypeObject(declaration)) continue;
919
+ const name = declaration.field("name");
920
+ if (!name || name.kind() !== "type_identifier" && name.kind() !== "identifier") continue;
921
+ bindings.push({
922
+ name: name.text(),
923
+ declaration,
924
+ bindingStart: name.range().start.index,
925
+ scope: enclosingScopeRange(declaration) ?? rootScope
926
+ });
927
+ }
928
+ }
929
+ return bindings;
930
+ }
931
+ function isShadowedLocalTypeReference(root, name, pos, bindingStart) {
932
+ for (const kind of ["type_alias_declaration", "interface_declaration"]) {
933
+ const declarations = root.findAll({ rule: { kind } });
934
+ for (const declaration of declarations) {
935
+ const nameNode = declaration.field("name");
936
+ if (!nameNode || nameNode.text() !== name) continue;
937
+ if (nameNode.range().start.index === bindingStart) continue;
938
+ const scope = enclosingScopeRange(declaration);
939
+ if (scope && rangeContains(scope, pos)) return true;
940
+ }
941
+ }
942
+ return false;
943
+ }
944
+ function resolveLocalCallbackTypeBinding(node, context) {
945
+ const pos = node.range().start.index;
946
+ return context.bindings.find((binding) => binding.name === node.text() && rangeContains(binding.scope, pos) && !isShadowedLocalTypeReference(context.root, binding.name, pos, binding.bindingStart)) ?? null;
947
+ }
948
+ function transformNamedPrincipalCallbackType(typeName, edits, context) {
949
+ const binding = resolveLocalCallbackTypeBinding(typeName, context);
950
+ if (!binding) return;
951
+ const start = binding.declaration.range().start.index;
952
+ if (context.transformedTypeStarts.has(start)) return;
953
+ const objectType = localCallbackTypeObject(binding.declaration);
954
+ if (!objectType) return;
955
+ context.transformedTypeStarts.add(start);
956
+ transformObjectTypeUserProperty(objectType, edits, context);
957
+ }
958
+ function transformPrincipalCallbackParamType(param, edits, typeContext) {
959
+ const typeAnnotation = param.field("type");
960
+ const objectType = typeAnnotation?.children().find((c) => c.kind() === "object_type");
961
+ if (objectType) {
962
+ if (typeContext) transformObjectTypeUserProperty(objectType, edits, typeContext);
963
+ return;
964
+ }
965
+ const typeName = typeAnnotation?.children().find((c) => c.kind() === "type_identifier");
966
+ if (typeName && typeContext) transformNamedPrincipalCallbackType(typeName, edits, typeContext);
967
+ }
968
+ function transformPrincipalCallbackParam(fn, edits, typeContext) {
969
+ const param = getFirstFunctionParam(fn);
970
+ if (!param) return;
971
+ const pattern = getFunctionParamPattern(param);
972
+ const body = fn.field("body");
973
+ if (!pattern || !body) return;
974
+ if (pattern.kind() === "identifier") {
975
+ const ctxName = pattern.text();
976
+ const ctxShadowRanges = collectCtxShadowRanges(body, ctxName, fn);
977
+ const propertyAccesses = body.findAll({ rule: {
978
+ kind: "property_identifier",
979
+ regex: "^user$"
980
+ } });
981
+ for (const propId of propertyAccesses) {
982
+ const parent = propId.parent();
983
+ if (!parent || parent.kind() !== "member_expression") continue;
984
+ const obj = parent.field("object");
985
+ if (!(obj && obj.kind() === "identifier" && obj.text() === ctxName)) continue;
986
+ const pos = obj.range().start.index;
987
+ if (isInsideAnyRange(pos, ctxShadowRanges)) continue;
988
+ edits.push(propId.replace(principalPropertyReplacement(propId, "invoker")));
989
+ }
990
+ const ctxDestructures = body.findAll({ rule: {
991
+ kind: "variable_declarator",
992
+ has: {
993
+ field: "value",
994
+ kind: "identifier",
995
+ regex: `^${escapeRegex(ctxName)}$`
996
+ }
997
+ } });
998
+ const principalAliasBindings = [];
999
+ for (const decl of ctxDestructures) {
1000
+ const pos = decl.range().start.index;
1001
+ if (isInsideAnyRange(pos, ctxShadowRanges)) continue;
1002
+ const pat = decl.field("name");
1003
+ if (!pat || pat.kind() !== "object_pattern") continue;
1004
+ for (const child of pat.children()) {
1005
+ const kind = child.kind();
1006
+ if (kind === "shorthand_property_identifier_pattern" && child.text() === "user") {
1007
+ edits.push(child.replace("invoker: user"));
1008
+ principalAliasBindings.push({
1009
+ name: "user",
1010
+ bindingStart: child.range().start.index
1011
+ });
1012
+ } else if (kind === "pair_pattern") {
1013
+ const key = child.field("key");
1014
+ const value = child.field("value");
1015
+ if (key?.text() === "user" && value?.kind() === "identifier") {
1016
+ edits.push(key.replace("invoker"));
1017
+ principalAliasBindings.push({
1018
+ name: value.text(),
1019
+ bindingStart: value.range().start.index
1020
+ });
1021
+ }
1022
+ }
1023
+ }
1024
+ }
1025
+ for (const binding of principalAliasBindings) guardPrincipalMemberAccesses(body, binding, edits);
1026
+ transformPrincipalCallbackParamType(param, edits, typeContext);
1027
+ return;
1028
+ }
1029
+ if (pattern.kind() !== "object_pattern") return;
1030
+ if (hasNestedPropertyPattern(pattern, "user")) return;
1031
+ let hasUserParamProperty = false;
1032
+ let renamesBinding = false;
1033
+ const principalAliasBindings = [];
1034
+ for (const child of pattern.children()) {
1035
+ const kind = child.kind();
1036
+ if (kind === "shorthand_property_identifier_pattern" && child.text() === "user") {
1037
+ hasUserParamProperty = true;
1038
+ renamesBinding = true;
1039
+ } else if (kind === "pair_pattern") {
1040
+ if (child.field("key")?.text() === "user") {
1041
+ hasUserParamProperty = true;
1042
+ const value = child.field("value");
1043
+ if (value?.kind() === "identifier") principalAliasBindings.push({
1044
+ name: value.text(),
1045
+ bindingStart: value.range().start.index
1046
+ });
1047
+ }
1048
+ } else if (kind === "object_assignment_pattern") {
1049
+ if (child.children().find((c) => c.kind() === "shorthand_property_identifier_pattern")?.text() === "user") {
1050
+ hasUserParamProperty = true;
1051
+ renamesBinding = true;
1052
+ }
1053
+ }
1054
+ }
1055
+ if (!hasUserParamProperty) return;
1056
+ if (renamesBinding && hasPrincipalAssignmentTarget(body, "user")) return;
1057
+ if (renamesBinding && patternBindsName(pattern, "invoker")) return;
1058
+ transformPrincipalCallbackParamType(param, edits, typeContext);
1059
+ const aliasRenamedUser = renamesBinding && (collectAllShadowRanges(body, "invoker").length > 0 || hasUnshadowedIdentifierReference(body, "invoker"));
1060
+ let aliasedShorthandUser = false;
1061
+ let renamedShorthandUser = false;
1062
+ for (const child of pattern.children()) {
1063
+ const kind = child.kind();
1064
+ if (kind === "shorthand_property_identifier_pattern" && child.text() === "user") if (aliasRenamedUser) {
1065
+ edits.push(child.replace("invoker: user"));
1066
+ aliasedShorthandUser = true;
1067
+ principalAliasBindings.push({
1068
+ name: "user",
1069
+ bindingStart: child.range().start.index
1070
+ });
1071
+ } else {
1072
+ edits.push(child.replace("invoker"));
1073
+ renamedShorthandUser = true;
1074
+ }
1075
+ else if (kind === "pair_pattern") {
1076
+ const key = child.field("key");
1077
+ if (key?.text() === "user") edits.push(key.replace("invoker"));
1078
+ } else if (kind === "object_assignment_pattern") {
1079
+ const inner = child.children().find((c) => c.kind() === "shorthand_property_identifier_pattern");
1080
+ if (inner?.text() === "user") if (aliasRenamedUser) {
1081
+ edits.push(inner.replace("invoker: user"));
1082
+ aliasedShorthandUser = true;
1083
+ principalAliasBindings.push({
1084
+ name: "user",
1085
+ bindingStart: inner.range().start.index
1086
+ });
1087
+ } else {
1088
+ edits.push(inner.replace("invoker"));
1089
+ renamedShorthandUser = true;
378
1090
  }
379
1091
  }
380
1092
  }
1093
+ if (!renamedShorthandUser) {
1094
+ if (aliasedShorthandUser) rewriteParseArgumentShorthands(body, "user", "invoker", typeContext, edits);
1095
+ for (const binding of principalAliasBindings) guardPrincipalMemberAccesses(body, binding, edits);
1096
+ return;
1097
+ }
1098
+ const shadowRanges = collectAllShadowRanges(body, "user");
1099
+ const refs = body.findAll({ rule: {
1100
+ kind: "identifier",
1101
+ regex: "^user$"
1102
+ } });
1103
+ for (const ref of refs) {
1104
+ const pos = ref.range().start.index;
1105
+ if (isInsideAnyRange(pos, shadowRanges)) continue;
1106
+ edits.push(ref.replace(principalReadReplacement(ref, "invoker")));
1107
+ }
1108
+ const shortRefs = body.findAll({ rule: {
1109
+ kind: "shorthand_property_identifier",
1110
+ regex: "^user$"
1111
+ } });
1112
+ for (const ref of shortRefs) {
1113
+ const pos = ref.range().start.index;
1114
+ if (isInsideAnyRange(pos, shadowRanges)) continue;
1115
+ edits.push(ref.replace(isSdkFieldParseArgumentShorthand(ref, typeContext) ? "invoker" : "user: invoker"));
1116
+ }
1117
+ for (const binding of principalAliasBindings) guardPrincipalMemberAccesses(body, binding, edits);
1118
+ }
1119
+ function rangeContains([start, end], pos) {
1120
+ return pos >= start && pos < end;
1121
+ }
1122
+ function isShadowedLocalReference(root, name, pos, bindingStart) {
1123
+ const declarators = root.findAll({ rule: { kind: "variable_declarator" } });
1124
+ for (const decl of declarators) {
1125
+ const nameNode = decl.field("name");
1126
+ if (!nameNode || !patternBindsName(nameNode, name)) continue;
1127
+ const nameRange = nameNode.range();
1128
+ if (bindingStart >= nameRange.start.index && bindingStart < nameRange.end.index) continue;
1129
+ const scope = enclosingScopeRange(decl);
1130
+ if (scope && rangeContains(scope, pos)) return true;
1131
+ }
1132
+ const functionDecls = root.findAll({ rule: { kind: "function_declaration" } });
1133
+ for (const fn of functionDecls) {
1134
+ const nameNode = fn.field("name");
1135
+ if (!nameNode || nameNode.text() !== name) continue;
1136
+ if (nameNode.range().start.index === bindingStart) continue;
1137
+ const scope = enclosingScopeRange(fn);
1138
+ if (scope && rangeContains(scope, pos)) return true;
1139
+ }
1140
+ for (const kind of NESTED_FN_KINDS) {
1141
+ const fns = root.findAll({ rule: { kind } });
1142
+ for (const fn of fns) {
1143
+ if (!functionRebindsName(fn, name)) continue;
1144
+ const range = fn.range();
1145
+ if (pos >= range.start.index && pos < range.end.index) return true;
1146
+ }
1147
+ }
1148
+ return false;
1149
+ }
1150
+ function resolvesToSdkFieldLocal(node, bindings, root) {
1151
+ if (node.kind() !== "identifier") return false;
1152
+ const pos = node.range().start.index;
1153
+ return bindings.some((binding) => binding.name === node.text() && rangeContains(binding.scope, pos) && !isShadowedLocalReference(root, binding.name, pos, binding.bindingStart));
1154
+ }
1155
+ function isSdkFieldExpression(node, sdkFieldRootNames, sdkFieldLocalBindings, root) {
1156
+ if (node.kind() === "identifier") {
1157
+ const pos = node.range().start.index;
1158
+ return sdkFieldRootNames.has(node.text()) && !isInsideAnyRange(pos, collectAllShadowRanges(root, node.text())) || resolvesToSdkFieldLocal(node, sdkFieldLocalBindings, root);
1159
+ }
1160
+ if (node.kind() === "member_expression") {
1161
+ const object = node.field("object");
1162
+ return object ? isSdkFieldExpression(object, sdkFieldRootNames, sdkFieldLocalBindings, root) : false;
1163
+ }
1164
+ if (node.kind() === "call_expression") {
1165
+ const object = findMemberCallObject(node);
1166
+ return object ? isSdkFieldExpression(object, sdkFieldRootNames, sdkFieldLocalBindings, root) : false;
1167
+ }
1168
+ return false;
1169
+ }
1170
+ function collectSdkFieldLocalBindings(root, sdkFieldRootNames) {
1171
+ const bindings = [];
1172
+ let changed = true;
1173
+ while (changed) {
1174
+ changed = false;
1175
+ const declarators = root.findAll({ rule: { kind: "variable_declarator" } });
1176
+ for (const decl of declarators) {
1177
+ const name = decl.field("name");
1178
+ const value = decl.field("value");
1179
+ if (!name || name.kind() !== "identifier" || !value) continue;
1180
+ const bindingStart = name.range().start.index;
1181
+ if (bindings.some((binding) => binding.bindingStart === bindingStart)) continue;
1182
+ if (!isSdkFieldExpression(value, sdkFieldRootNames, bindings, root)) continue;
1183
+ const rootRange = root.range();
1184
+ const scope = enclosingScopeRange(decl) ?? [rootRange.start.index, rootRange.end.index];
1185
+ bindings.push({
1186
+ name: name.text(),
1187
+ bindingStart,
1188
+ scope
1189
+ });
1190
+ changed = true;
1191
+ }
1192
+ }
1193
+ return bindings;
1194
+ }
1195
+ function isSdkFieldMemberCall(call, sdkFieldRootNames, sdkFieldLocalBindings, root) {
1196
+ const object = findMemberCallObject(call);
1197
+ return object ? isSdkFieldExpression(object, sdkFieldRootNames, sdkFieldLocalBindings, root) : false;
1198
+ }
1199
+ function localBindingRootScope(root) {
1200
+ const rootRange = root.range();
1201
+ return [rootRange.start.index, rootRange.end.index];
1202
+ }
1203
+ function collectLocalCallbackBindings(root) {
1204
+ const rootScope = localBindingRootScope(root);
1205
+ const bindings = [];
1206
+ const declarators = root.findAll({ rule: { kind: "variable_declarator" } });
1207
+ for (const decl of declarators) {
1208
+ const name = decl.field("name");
1209
+ const value = decl.field("value");
1210
+ if (!name || name.kind() !== "identifier" || !value || !isFunctionNode(value)) continue;
1211
+ bindings.push({
1212
+ name: name.text(),
1213
+ fn: value,
1214
+ bindingStart: name.range().start.index,
1215
+ scope: enclosingScopeRange(decl) ?? rootScope
1216
+ });
1217
+ }
1218
+ const functionDecls = root.findAll({ rule: { kind: "function_declaration" } });
1219
+ for (const fn of functionDecls) {
1220
+ const name = fn.field("name");
1221
+ if (!name || name.kind() !== "identifier") continue;
1222
+ bindings.push({
1223
+ name: name.text(),
1224
+ fn,
1225
+ bindingStart: name.range().start.index,
1226
+ scope: enclosingScopeRange(fn) ?? rootScope
1227
+ });
1228
+ }
1229
+ return bindings;
1230
+ }
1231
+ function collectLocalObjectBindings(root) {
1232
+ const rootScope = localBindingRootScope(root);
1233
+ const bindings = [];
1234
+ const declarators = root.findAll({ rule: { kind: "variable_declarator" } });
1235
+ for (const decl of declarators) {
1236
+ const name = decl.field("name");
1237
+ const value = decl.field("value");
1238
+ if (!name || name.kind() !== "identifier" || value?.kind() !== "object") continue;
1239
+ bindings.push({
1240
+ name: name.text(),
1241
+ object: value,
1242
+ bindingStart: name.range().start.index,
1243
+ scope: enclosingScopeRange(decl) ?? rootScope
1244
+ });
1245
+ }
1246
+ return bindings;
1247
+ }
1248
+ function resolveLocalCallbackBinding(node, bindings, root) {
1249
+ if (node.kind() !== "identifier") return null;
1250
+ const pos = node.range().start.index;
1251
+ return bindings.find((binding) => binding.name === node.text() && rangeContains(binding.scope, pos) && !isShadowedLocalReference(root, binding.name, pos, binding.bindingStart)) ?? null;
1252
+ }
1253
+ function resolveLocalObjectBinding(node, bindings, root) {
1254
+ if (node.kind() !== "identifier") return null;
1255
+ const pos = node.range().start.index;
1256
+ return bindings.find((binding) => binding.name === node.text() && rangeContains(binding.scope, pos) && !isShadowedLocalReference(root, binding.name, pos, binding.bindingStart)) ?? null;
1257
+ }
1258
+ function transformPrincipalCallbackNode(node, edits, callbackBindings, transformedCallbackStarts, typeContext, root) {
1259
+ if (isFunctionNode(node)) {
1260
+ transformPrincipalCallbackParam(node, edits, typeContext);
1261
+ return true;
1262
+ }
1263
+ const binding = resolveLocalCallbackBinding(node, callbackBindings, root);
1264
+ if (!binding) return false;
1265
+ const start = binding.fn.range().start.index;
1266
+ if (!transformedCallbackStarts.has(start)) {
1267
+ transformedCallbackStarts.add(start);
1268
+ transformPrincipalCallbackParam(binding.fn, edits, typeContext);
1269
+ }
1270
+ return true;
1271
+ }
1272
+ function transformHookCallbackObject(node, edits, callbackBindings, transformedCallbackStarts, typeContext, root) {
1273
+ if (node.kind() !== "object") return;
1274
+ for (const child of node.children()) {
1275
+ if (child.kind() === "method_definition") {
1276
+ const key = propertyName(child);
1277
+ if (key === "create" || key === "update") transformPrincipalCallbackParam(child, edits, typeContext);
1278
+ continue;
1279
+ }
1280
+ if (child.kind() !== "pair") continue;
1281
+ const key = child.field("key")?.text();
1282
+ const value = child.field("value");
1283
+ if (!value) continue;
1284
+ if (key === "create" || key === "update") {
1285
+ if (!transformPrincipalCallbackNode(value, edits, callbackBindings, transformedCallbackStarts, typeContext, root) && value.kind() === "object") transformHookCallbackObject(value, edits, callbackBindings, transformedCallbackStarts, typeContext, root);
1286
+ } else if (value.kind() === "object") transformHookCallbackObject(value, edits, callbackBindings, transformedCallbackStarts, typeContext, root);
1287
+ }
1288
+ }
1289
+ function transformHookCallbackConfigNode(node, edits, callbackBindings, objectBindings, transformedCallbackStarts, transformedObjectStarts, typeContext, root) {
1290
+ if (node.kind() === "object") {
1291
+ transformHookCallbackObject(node, edits, callbackBindings, transformedCallbackStarts, typeContext, root);
1292
+ return true;
1293
+ }
1294
+ const binding = resolveLocalObjectBinding(node, objectBindings, root);
1295
+ if (!binding) return false;
1296
+ const start = binding.object.range().start.index;
1297
+ if (!transformedObjectStarts.has(start)) {
1298
+ transformedObjectStarts.add(start);
1299
+ transformHookCallbackObject(binding.object, edits, callbackBindings, transformedCallbackStarts, typeContext, root);
1300
+ }
1301
+ return true;
1302
+ }
1303
+ function transformValidateCallbackNode(node, edits, callbackBindings, transformedCallbackStarts, typeContext, root) {
1304
+ if (transformPrincipalCallbackNode(node, edits, callbackBindings, transformedCallbackStarts, typeContext, root)) return;
1305
+ if (node.kind() === "array") {
1306
+ for (const child of node.children()) transformValidateCallbackNode(child, edits, callbackBindings, transformedCallbackStarts, typeContext, root);
1307
+ return;
1308
+ }
1309
+ if (node.kind() !== "object") return;
1310
+ for (const child of node.children()) {
1311
+ if (child.kind() === "method_definition") {
1312
+ transformPrincipalCallbackParam(child, edits, typeContext);
1313
+ continue;
1314
+ }
1315
+ if (child.kind() !== "pair") continue;
1316
+ const value = child.field("value");
1317
+ if (value) transformValidateCallbackNode(value, edits, callbackBindings, transformedCallbackStarts, typeContext, root);
1318
+ }
1319
+ }
1320
+ function transformPrincipalCallbacksInCall(call, edits, sdkFieldRootNames, sdkFieldLocalBindings, callbackBindings, objectBindings, transformedCallbackStarts, transformedObjectStarts, typeContext, root) {
1321
+ const memberName = findMemberCallName(call);
1322
+ if (memberName !== "hooks" && memberName !== "validate") return;
1323
+ if (!isSdkFieldMemberCall(call, sdkFieldRootNames, sdkFieldLocalBindings, root)) return;
1324
+ const args = call.field("arguments");
1325
+ if (!args) return;
1326
+ if (memberName === "hooks") {
1327
+ for (const arg of args.children()) if (transformHookCallbackConfigNode(arg, edits, callbackBindings, objectBindings, transformedCallbackStarts, transformedObjectStarts, typeContext, root)) break;
1328
+ return;
1329
+ }
1330
+ for (const arg of args.children()) transformValidateCallbackNode(arg, edits, callbackBindings, transformedCallbackStarts, typeContext, root);
1331
+ }
1332
+ function transformParseArgsObject(call, edits, sdkFieldRootNames, sdkFieldLocalBindings, root) {
1333
+ if (findMemberCallName(call) !== "parse") return;
1334
+ if (!isSdkFieldMemberCall(call, sdkFieldRootNames, sdkFieldLocalBindings, root)) return;
1335
+ const objArg = call.field("arguments")?.children().find((c) => c.kind() === "object");
1336
+ if (!objArg) return;
1337
+ for (const child of objArg.children()) {
1338
+ const kind = child.kind();
1339
+ if (kind === "pair") {
1340
+ const key = child.field("key");
1341
+ if (key?.text() === "user") edits.push(key.replace("invoker"));
1342
+ } else if (kind === "shorthand_property_identifier" && child.text() === "user") edits.push(child.replace("invoker: user"));
1343
+ }
381
1344
  }
382
1345
  /**
383
1346
  * Migrate user/actor/invoker types and identifiers to the unified TailorPrincipal.
@@ -388,10 +1351,13 @@ function transformResolverBody(arrowNode, edits) {
388
1351
  * - Replaces standalone references to `unauthenticatedTailorUser` with `null`. Member-access
389
1352
  * forms like `unauthenticatedTailorUser.id` are left alone on purpose so the resulting TS
390
1353
  * error after the import is removed points the author at the broken access.
1354
+ * - Rewrites actor member accesses from `userId` / `userType` to `id` / `type`.
391
1355
  * - Renames `user` to `caller` for top-level destructured resolver bodies (`{ input, user }`),
392
1356
  * handles aliased pairs (`{ user: currentUser }`) by rewriting only the property name, and
393
1357
  * rewrites `<ctx>.user` for non-destructured single-param bodies — respecting variable
394
1358
  * shadowing in both directions.
1359
+ * - Renames TailorDB hook/validator callback `user` parameters to `invoker`, and rewrites
1360
+ * `.parse({ user: ... })` arguments to `.parse({ invoker: ... })`.
395
1361
  * @param source - TypeScript source text.
396
1362
  * @returns Transformed source or null when nothing matched.
397
1363
  */
@@ -407,21 +1373,32 @@ function transform(source) {
407
1373
  }
408
1374
  } });
409
1375
  const sdkRenameSourceNames = /* @__PURE__ */ new Set();
410
- for (const importStmt of sdkImports) for (const { importedName, aliasNode } of iterateImportSpecs(importStmt)) if (TYPE_RENAME_MAP[importedName] && !aliasNode) sdkRenameSourceNames.add(importedName);
411
- const typeIdents = tree.findAll({ rule: {
412
- kind: "type_identifier",
413
- not: { inside: { kind: "import_statement" } }
414
- } });
415
- for (const id of typeIdents) {
416
- if (!sdkRenameSourceNames.has(id.text())) continue;
417
- const newName = TYPE_RENAME_MAP[id.text()];
418
- edits.push(id.replace(newName));
1376
+ const tailorUserTypeLocalNames = /* @__PURE__ */ new Set();
1377
+ const nullableInvokerAliasLocalNames = /* @__PURE__ */ new Set();
1378
+ const actorTypeAliasLocalNames = /* @__PURE__ */ new Set();
1379
+ const actorTypeLocalNames = /* @__PURE__ */ new Set();
1380
+ const actorTypeValueLocalNames = /* @__PURE__ */ new Set();
1381
+ const sdkNamespaceNames = /* @__PURE__ */ new Set();
1382
+ for (const importStmt of sdkImports) {
1383
+ for (const namespaceName of iterateNamespaceImportLocalNames(importStmt)) sdkNamespaceNames.add(namespaceName);
1384
+ for (const { importedName, aliasNode, localName } of iterateImportSpecs(importStmt)) {
1385
+ if (TYPE_RENAME_MAP[importedName] && !aliasNode) sdkRenameSourceNames.add(importedName);
1386
+ if (importedName === "TailorUser") tailorUserTypeLocalNames.add(localName);
1387
+ if (importedName === "TailorActor") actorTypeLocalNames.add(localName);
1388
+ if (importedName === "TailorActorType") actorTypeValueLocalNames.add(localName);
1389
+ if (importedName === "TailorInvoker" && aliasNode) nullableInvokerAliasLocalNames.add(localName);
1390
+ if (importedName === "TailorActorType" && aliasNode) actorTypeAliasLocalNames.add(localName);
1391
+ }
419
1392
  }
1393
+ const transformedActorPropertyStarts = /* @__PURE__ */ new Set();
1394
+ transformTailorActorTypedMemberAccesses(tree, actorTypeLocalNames, sdkNamespaceNames, edits, transformedActorPropertyStarts);
1395
+ transformTailorActorTypeInitializerLiterals(tree, actorTypeValueLocalNames, sdkNamespaceNames, edits);
1396
+ transformTailorActorTypeBindingComparisons(tree, actorTypeValueLocalNames, sdkNamespaceNames, edits);
420
1397
  let importRemoved = false;
421
1398
  const globalEmittedRenamed = /* @__PURE__ */ new Set();
422
1399
  const unauthenticatedLocalNames = /* @__PURE__ */ new Set();
423
1400
  for (const importStmt of sdkImports) {
424
- const { newText, touched } = rebuildImportStatement(importStmt, globalEmittedRenamed, unauthenticatedLocalNames);
1401
+ const { newText, touched } = rebuildImportStatement(importStmt, globalEmittedRenamed, unauthenticatedLocalNames, nullableInvokerAliasLocalNames, actorTypeAliasLocalNames);
425
1402
  if (!touched) continue;
426
1403
  edits.push(importStmt.replace(newText));
427
1404
  if (newText === "") importRemoved = true;
@@ -441,7 +1418,20 @@ function transform(source) {
441
1418
  }
442
1419
  }
443
1420
  const createResolverLocalNames = /* @__PURE__ */ new Set();
444
- for (const importStmt of sdkImports) for (const { importedName, localName } of iterateImportSpecs(importStmt)) if (importedName === "createResolver") createResolverLocalNames.add(localName);
1421
+ const createExecutorLocalNames = /* @__PURE__ */ new Set();
1422
+ const sdkFieldRootNames = /* @__PURE__ */ new Set();
1423
+ for (const namespaceName of sdkNamespaceNames) sdkFieldRootNames.add(namespaceName);
1424
+ for (const importStmt of sdkImports) for (const { importedName, localName } of iterateImportSpecs(importStmt)) {
1425
+ if (importedName === "createResolver") createResolverLocalNames.add(localName);
1426
+ if (importedName === "createExecutor") createExecutorLocalNames.add(localName);
1427
+ if (importedName === "t" || importedName === "db") sdkFieldRootNames.add(localName);
1428
+ }
1429
+ const sdkFieldLocalBindings = collectSdkFieldLocalBindings(tree, sdkFieldRootNames);
1430
+ const parseContext = {
1431
+ sdkFieldRootNames,
1432
+ sdkFieldLocalBindings,
1433
+ root: tree
1434
+ };
445
1435
  for (const localName of createResolverLocalNames) {
446
1436
  const shadowRanges = collectAllShadowRanges(tree, localName);
447
1437
  const calls = tree.findAll({ rule: {
@@ -458,8 +1448,106 @@ function transform(source) {
458
1448
  const pos = callee.range().start.index;
459
1449
  if (isInsideAnyRange(pos, shadowRanges)) continue;
460
1450
  const arrow = findResolverBodyArrow(call);
461
- if (arrow) transformResolverBody(arrow, edits);
1451
+ if (arrow) transformResolverBody(arrow, edits, parseContext);
1452
+ }
1453
+ }
1454
+ for (const namespaceName of sdkNamespaceNames) {
1455
+ const shadowRanges = collectAllShadowRanges(tree, namespaceName);
1456
+ const calls = tree.findAll({ rule: {
1457
+ kind: "call_expression",
1458
+ has: {
1459
+ field: "function",
1460
+ kind: "member_expression",
1461
+ has: {
1462
+ field: "property",
1463
+ kind: "property_identifier",
1464
+ regex: "^createResolver$"
1465
+ }
1466
+ }
1467
+ } });
1468
+ for (const call of calls) {
1469
+ const object = call.field("function")?.field("object");
1470
+ if (!object || object.kind() !== "identifier" || object.text() !== namespaceName) continue;
1471
+ const pos = object.range().start.index;
1472
+ if (isInsideAnyRange(pos, shadowRanges)) continue;
1473
+ const arrow = findResolverBodyArrow(call);
1474
+ if (arrow) transformResolverBody(arrow, edits, parseContext);
1475
+ }
1476
+ }
1477
+ for (const localName of createExecutorLocalNames) {
1478
+ const shadowRanges = collectAllShadowRanges(tree, localName);
1479
+ const calls = tree.findAll({ rule: {
1480
+ kind: "call_expression",
1481
+ has: {
1482
+ field: "function",
1483
+ kind: "identifier",
1484
+ regex: `^${escapeRegex(localName)}$`
1485
+ }
1486
+ } });
1487
+ for (const call of calls) {
1488
+ const callee = call.field("function");
1489
+ if (!callee) continue;
1490
+ const pos = callee.range().start.index;
1491
+ if (isInsideAnyRange(pos, shadowRanges)) continue;
1492
+ for (const fn of findExecutorBodyFunctions(call)) transformExecutorBodyActorAccesses(fn, edits, transformedActorPropertyStarts);
1493
+ }
1494
+ }
1495
+ for (const namespaceName of sdkNamespaceNames) {
1496
+ const shadowRanges = collectAllShadowRanges(tree, namespaceName);
1497
+ const calls = tree.findAll({ rule: {
1498
+ kind: "call_expression",
1499
+ has: {
1500
+ field: "function",
1501
+ kind: "member_expression",
1502
+ has: {
1503
+ field: "property",
1504
+ kind: "property_identifier",
1505
+ regex: "^createExecutor$"
1506
+ }
1507
+ }
1508
+ } });
1509
+ for (const call of calls) {
1510
+ const object = call.field("function")?.field("object");
1511
+ if (!object || object.kind() !== "identifier" || object.text() !== namespaceName) continue;
1512
+ const pos = object.range().start.index;
1513
+ if (isInsideAnyRange(pos, shadowRanges)) continue;
1514
+ for (const fn of findExecutorBodyFunctions(call)) transformExecutorBodyActorAccesses(fn, edits, transformedActorPropertyStarts);
1515
+ }
1516
+ }
1517
+ transformActorTypeComparisonLiterals(tree, edits, transformedActorPropertyStarts);
1518
+ const callbackBindings = collectLocalCallbackBindings(tree);
1519
+ const objectBindings = collectLocalObjectBindings(tree);
1520
+ const typeContext = {
1521
+ bindings: collectLocalCallbackTypeBindings(tree),
1522
+ transformedTypeStarts: /* @__PURE__ */ new Set(),
1523
+ transformedPrincipalTypeStarts: /* @__PURE__ */ new Set(),
1524
+ tailorUserTypeLocalNames,
1525
+ sdkNamespaceNames,
1526
+ sdkFieldRootNames,
1527
+ sdkFieldLocalBindings,
1528
+ root: tree
1529
+ };
1530
+ const transformedCallbackStarts = /* @__PURE__ */ new Set();
1531
+ const transformedObjectStarts = /* @__PURE__ */ new Set();
1532
+ const memberCalls = tree.findAll({ rule: { kind: "call_expression" } });
1533
+ for (const call of memberCalls) {
1534
+ transformPrincipalCallbacksInCall(call, edits, sdkFieldRootNames, sdkFieldLocalBindings, callbackBindings, objectBindings, transformedCallbackStarts, transformedObjectStarts, typeContext, tree);
1535
+ transformParseArgsObject(call, edits, sdkFieldRootNames, sdkFieldLocalBindings, tree);
1536
+ }
1537
+ const typeIdents = tree.findAll({ rule: {
1538
+ kind: "type_identifier",
1539
+ not: { inside: { kind: "import_statement" } }
1540
+ } });
1541
+ for (const id of typeIdents) {
1542
+ if (typeContext.transformedPrincipalTypeStarts.has(id.range().start.index)) continue;
1543
+ const qualifiedReplacement = namespaceQualifiedTypeReplacement(id, sdkNamespaceNames, tree);
1544
+ if (qualifiedReplacement) {
1545
+ edits.push(qualifiedReplacement.target.replace(qualifiedReplacement.text));
1546
+ continue;
462
1547
  }
1548
+ const newName = sdkRenameSourceNames.has(id.text()) ? renamedTypeIdentifierText(id.text()) : nullableInvokerAliasLocalNames.has(id.text()) ? renamedTypeIdentifierText("TailorInvoker") : actorTypeAliasLocalNames.has(id.text()) ? renamedTypeIdentifierText("TailorActorType") : null;
1549
+ if (!newName) continue;
1550
+ edits.push(id.replace(newName));
463
1551
  }
464
1552
  if (edits.length === 0) return null;
465
1553
  let result = tree.commitEdits(edits);