@typeorm/codemod 1.0.1 → 1.0.3-nightly.20261009

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 (77) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +2 -1
  3. package/dist/cli/print-usage.js +1 -0
  4. package/dist/cli/print-usage.js.map +1 -1
  5. package/dist/cli/run-transforms.js +167 -79
  6. package/dist/cli/run-transforms.js.map +1 -1
  7. package/dist/dependencies/v1.js +1 -0
  8. package/dist/dependencies/v1.js.map +1 -1
  9. package/dist/index.js +8 -0
  10. package/dist/index.js.map +1 -1
  11. package/dist/transforms/ast-helpers.js +820 -26
  12. package/dist/transforms/ast-helpers.js.map +1 -1
  13. package/dist/transforms/stats.js +11 -0
  14. package/dist/transforms/stats.js.map +1 -1
  15. package/dist/transforms/todo.js +22 -5
  16. package/dist/transforms/todo.js.map +1 -1
  17. package/dist/transforms/v1/column-readonly.js +28 -8
  18. package/dist/transforms/v1/column-readonly.js.map +1 -1
  19. package/dist/transforms/v1/column-unsigned-numeric.js +21 -18
  20. package/dist/transforms/v1/column-unsigned-numeric.js.map +1 -1
  21. package/dist/transforms/v1/column-width-zerofill.js +9 -3
  22. package/dist/transforms/v1/column-width-zerofill.js.map +1 -1
  23. package/dist/transforms/v1/connection-options-reader.js +23 -7
  24. package/dist/transforms/v1/connection-options-reader.js.map +1 -1
  25. package/dist/transforms/v1/connection-to-datasource.js +871 -88
  26. package/dist/transforms/v1/connection-to-datasource.js.map +1 -1
  27. package/dist/transforms/v1/datasource-expo.js +79 -0
  28. package/dist/transforms/v1/datasource-expo.js.map +1 -0
  29. package/dist/transforms/v1/datasource-mongodb.js +77 -50
  30. package/dist/transforms/v1/datasource-mongodb.js.map +1 -1
  31. package/dist/transforms/v1/datasource-mssql.js +88 -44
  32. package/dist/transforms/v1/datasource-mssql.js.map +1 -1
  33. package/dist/transforms/v1/datasource-mysql-connector.js +22 -6
  34. package/dist/transforms/v1/datasource-mysql-connector.js.map +1 -1
  35. package/dist/transforms/v1/datasource-name.js +17 -14
  36. package/dist/transforms/v1/datasource-name.js.map +1 -1
  37. package/dist/transforms/v1/datasource-sap.js +51 -39
  38. package/dist/transforms/v1/datasource-sap.js.map +1 -1
  39. package/dist/transforms/v1/datasource-sqlite-options.js +4 -1
  40. package/dist/transforms/v1/datasource-sqlite-options.js.map +1 -1
  41. package/dist/transforms/v1/file-logger.js +2 -21
  42. package/dist/transforms/v1/file-logger.js.map +1 -1
  43. package/dist/transforms/v1/find-options-string-relations.js +75 -11
  44. package/dist/transforms/v1/find-options-string-relations.js.map +1 -1
  45. package/dist/transforms/v1/find-options-string-select.js +75 -13
  46. package/dist/transforms/v1/find-options-string-select.js.map +1 -1
  47. package/dist/transforms/v1/global-functions.js +79 -21
  48. package/dist/transforms/v1/global-functions.js.map +1 -1
  49. package/dist/transforms/v1/index.js +6 -1
  50. package/dist/transforms/v1/index.js.map +1 -1
  51. package/dist/transforms/v1/mongodb-stats.js +18 -11
  52. package/dist/transforms/v1/mongodb-stats.js.map +1 -1
  53. package/dist/transforms/v1/mongodb-types.js +62 -2
  54. package/dist/transforms/v1/mongodb-types.js.map +1 -1
  55. package/dist/transforms/v1/query-builder-on-conflict.js +30 -17
  56. package/dist/transforms/v1/query-builder-on-conflict.js.map +1 -1
  57. package/dist/transforms/v1/query-builder-print-sql.js +15 -12
  58. package/dist/transforms/v1/query-builder-print-sql.js.map +1 -1
  59. package/dist/transforms/v1/query-builder-replace-property-names.js +13 -12
  60. package/dist/transforms/v1/query-builder-replace-property-names.js.map +1 -1
  61. package/dist/transforms/v1/query-builder-where-expression.js +29 -12
  62. package/dist/transforms/v1/query-builder-where-expression.js.map +1 -1
  63. package/dist/transforms/v1/query-runner-loaded-tables-views.js +0 -1
  64. package/dist/transforms/v1/query-runner-loaded-tables-views.js.map +1 -1
  65. package/dist/transforms/v1/relation-count.js +3 -0
  66. package/dist/transforms/v1/relation-count.js.map +1 -1
  67. package/dist/transforms/v1/repository-abstract.js +146 -29
  68. package/dist/transforms/v1/repository-abstract.js.map +1 -1
  69. package/dist/transforms/v1/repository-exist.js +18 -1
  70. package/dist/transforms/v1/repository-exist.js.map +1 -1
  71. package/dist/transforms/v1/repository-find-by-ids.js +67 -24
  72. package/dist/transforms/v1/repository-find-by-ids.js.map +1 -1
  73. package/dist/transforms/v1/repository-find-one-by-id.js +3 -0
  74. package/dist/transforms/v1/repository-find-one-by-id.js.map +1 -1
  75. package/dist/transforms/v1/use-container.js +60 -12
  76. package/dist/transforms/v1/use-container.js.map +1 -1
  77. package/package.json +16 -17
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.renameMemberMethod = exports.removeImportSpecifiers = exports.removeObjectProperties = exports.forEachDecoratorObjectArg = exports.expandLocalNamesForImports = exports.TYPEORM_COLUMN_DECORATORS = exports.forEachIdentifierParam = exports.getLocalNamesForImport = exports.fileImportsFrom = exports.isIdentifier = exports.setStringValue = exports.getStringValue = void 0;
3
+ exports.isRepositoryReceiver = exports.collectRepositoryBindings = exports.TYPEORM_DATASOURCE_TYPES = exports.TYPEORM_REPOSITORY_TYPES = exports.getTypeReferenceRootName = exports.renameMemberMethod = exports.renameReExportSpecifiers = exports.removeReExportSpecifiers = exports.removeImportSpecifiers = exports.removeObjectPropertiesWhere = exports.removeObjectProperties = exports.unwrapTsExpression = exports.isFindMethodCallArgument = exports.isObjectFromEntriesCall = exports.TYPEORM_FIND_OPTIONS_METHODS = exports.forEachColumnMetadataOptionsArg = exports.getObjectPropertyKeyName = exports.forEachDecoratorObjectArg = exports.expandLocalNamesForImports = exports.TYPEORM_COLUMN_DECORATORS = exports.forEachIdentifierParam = exports.getNamespaceLocalNames = exports.getLocalNamesForImport = exports.fileImportsFrom = exports.isIdentifier = exports.setStringValue = exports.getStringValue = void 0;
4
4
  /**
5
5
  * Extracts a string value from a StringLiteral or Literal node.
6
6
  * Returns null if the node is not a string literal.
@@ -30,11 +30,12 @@ exports.setStringValue = setStringValue;
30
30
  const isIdentifier = (node) => node.type === "Identifier";
31
31
  exports.isIdentifier = isIdentifier;
32
32
  /**
33
- * Checks whether the file contains an import from the given module. Matches
34
- * both the exact module name (`"typeorm"`) and any sub-path (`"typeorm/..."`),
35
- * and recognizes ESM `import`, TypeScript `import = require(...)`, and
36
- * CommonJS `require(...)` forms so that `.js`/`.jsx` callers still pass the
37
- * scope guard.
33
+ * Checks whether the file references the given module as an import or a
34
+ * re-export source. Matches the exact module name (`"typeorm"`) and any
35
+ * sub-path (`"typeorm/..."`), and recognizes ESM `import`, ESM
36
+ * `export { X } from "..."` / `export * from "..."`, TypeScript
37
+ * `import = require(...)`, and CommonJS `require(...)` forms so barrel
38
+ * files and `.js`/`.jsx` callers still pass the scope guard.
38
39
  */
39
40
  const fileImportsFrom = (root, j, moduleName) => {
40
41
  const prefix = `${moduleName}/`;
@@ -46,6 +47,19 @@ const fileImportsFrom = (root, j, moduleName) => {
46
47
  .some((path) => matchesModule(path.node.source.value))) {
47
48
  return true;
48
49
  }
50
+ // ESM: export { X } from "typeorm[/subpath]" / export * from "..."
51
+ // Barrel files that only re-export TypeORM APIs have no import at all,
52
+ // but their re-export source still counts as a reference to the module.
53
+ if (root
54
+ .find(j.ExportNamedDeclaration)
55
+ .some((path) => matchesModule(path.node.source?.value))) {
56
+ return true;
57
+ }
58
+ if (root
59
+ .find(j.ExportAllDeclaration)
60
+ .some((path) => matchesModule(path.node.source?.value))) {
61
+ return true;
62
+ }
49
63
  // TS: import ... = require("typeorm[/subpath]")
50
64
  if (root.find(j.TSImportEqualsDeclaration).some((path) => {
51
65
  const ref = path.node.moduleReference;
@@ -73,31 +87,69 @@ exports.fileImportsFrom = fileImportsFrom;
73
87
  * import { RelationCount as RC } from "typeorm" → { "RC" }
74
88
  * const { RelationCount } = require("typeorm") → { "RelationCount" }
75
89
  * const { RelationCount: RC } = require("typeorm")→ { "RC" }
90
+ *
91
+ * Matches both the exact module name and any sub-path — e.g. when moduleName
92
+ * is `"typeorm"`, imports from `"typeorm/metadata/ColumnMetadata"` are also
93
+ * recognised. TypeORM users reach internal types through deep imports, and
94
+ * the codemod must rewrite them regardless of import shape.
95
+ *
96
+ * CommonJS `require` destructures are collected from module scope only; an
97
+ * inside-function `require` would sit in an inner scope where the call-site
98
+ * shadow guards reject rewrites, so collecting it would silently mislead
99
+ * callers. See the inline comment on the CJS branch for details.
100
+ *
101
+ * @param opts.valueOnly When true, skip ESM type-only imports — both the
102
+ * declaration-level `import type { X }` and the per-specifier
103
+ * `import { type X }` form. Use this when the local is going to appear as
104
+ * a *value* in the rewritten code (e.g. callee of `new X(...)` or
105
+ * `X.method()`); a type-only import creates no runtime binding and must
106
+ * not influence value-level matching. CommonJS `require` destructures and
107
+ * TS `import = require` forms always produce value bindings so they are
108
+ * included regardless.
76
109
  */
77
- const getLocalNamesForImport = (root, j, moduleName, importedName) => {
110
+ const getLocalNamesForImport = (root, j, moduleName, importedName, opts = {}) => {
78
111
  const localNames = new Set();
79
- // ESM: `import { X [as Y] } from "moduleName"`
80
- root.find(j.ImportDeclaration, {
81
- source: { value: moduleName },
82
- }).forEach((importPath) => {
112
+ const { valueOnly = false } = opts;
113
+ const prefix = `${moduleName}/`;
114
+ const matchesModule = (source) => typeof source === "string" &&
115
+ (source === moduleName || source.startsWith(prefix));
116
+ // ESM: `import { X [as Y] } from "moduleName[/subpath]"`
117
+ root.find(j.ImportDeclaration).forEach((importPath) => {
118
+ if (!matchesModule(importPath.node.source.value))
119
+ return;
120
+ const declKind = importPath.node.importKind;
121
+ if (valueOnly && declKind === "type")
122
+ return;
83
123
  for (const spec of importPath.node.specifiers ?? []) {
84
124
  if (spec.type !== "ImportSpecifier" ||
85
125
  spec.imported.type !== "Identifier" ||
86
126
  spec.imported.name !== importedName) {
87
127
  continue;
88
128
  }
129
+ const specKind = spec.importKind;
130
+ if (valueOnly && specKind === "type")
131
+ continue;
89
132
  const local = spec.local ?? spec.imported;
90
133
  if (local.type === "Identifier") {
91
134
  localNames.add(local.name);
92
135
  }
93
136
  }
94
137
  });
95
- // CommonJS: `const { X [: Y] } = require("moduleName")`
138
+ // CommonJS: `const { X [: Y] } = require("moduleName[/subpath]")`
139
+ // Only top-level requires are collected. An inside-function require
140
+ // creates a binding at non-module scope, and the call-site scope guard
141
+ // in `forEachColumnMetadataOptionsArg` rejects callees whose declaration
142
+ // lives in any inner scope (shadow guard). Collecting inner-scope
143
+ // requires here would make the collector and the guard disagree — the
144
+ // name would be in `classLocalNames` but the rewrite would never fire.
145
+ // Users with inside-function requires can hoist them to module scope.
96
146
  root.find(j.CallExpression, {
97
147
  callee: { type: "Identifier", name: "require" },
98
148
  }).forEach((callPath) => {
99
149
  const [arg] = callPath.node.arguments;
100
- if (!arg || (0, exports.getStringValue)(arg) !== moduleName)
150
+ if (!arg || !matchesModule((0, exports.getStringValue)(arg)))
151
+ return;
152
+ if (callPath.scope?.isGlobal !== true)
101
153
  return;
102
154
  const parent = callPath.parent.node;
103
155
  if (parent.type !== "VariableDeclarator")
@@ -132,6 +184,80 @@ const getLocalNamesForImport = (root, j, moduleName, importedName) => {
132
184
  return localNames;
133
185
  };
134
186
  exports.getLocalNamesForImport = getLocalNamesForImport;
187
+ /**
188
+ * Collects local namespace bindings for a module. Covers:
189
+ *
190
+ * import * as typeorm from "typeorm" → "typeorm"
191
+ * const typeorm = require("typeorm") → "typeorm"
192
+ * import typeorm = require("typeorm") → "typeorm"
193
+ *
194
+ * Useful when a transform needs to recognise `typeorm.Foo` member-expression
195
+ * references alongside named `Foo` imports handled by `getLocalNamesForImport`.
196
+ *
197
+ * CJS namespace `require` bindings are collected from module scope only,
198
+ * matching the module-scope guard that `getLocalNamesForImport` applies for
199
+ * destructured requires — same reasoning, see the CJS branch comment there.
200
+ *
201
+ * @param opts.valueOnly When true, skip ESM type-only namespace imports
202
+ * (`import type * as ns from "..."`). Use this when the local name is
203
+ * going to appear as a *value* in the rewritten code (e.g. callee of
204
+ * `new ns.X(...)`); a type-only import creates no runtime binding. CJS
205
+ * `require` and TS `import = require` forms are always value bindings and
206
+ * remain included regardless.
207
+ */
208
+ const getNamespaceLocalNames = (root, j, moduleName, opts = {}) => {
209
+ const localNames = new Set();
210
+ const { valueOnly = false } = opts;
211
+ const prefix = `${moduleName}/`;
212
+ const matchesModule = (source) => typeof source === "string" &&
213
+ (source === moduleName || source.startsWith(prefix));
214
+ // ESM: `import * as ns from "moduleName[/subpath]"`
215
+ root.find(j.ImportDeclaration).forEach((importPath) => {
216
+ if (!matchesModule(importPath.node.source.value))
217
+ return;
218
+ const declKind = importPath.node.importKind;
219
+ if (valueOnly && declKind === "type")
220
+ return;
221
+ for (const spec of importPath.node.specifiers ?? []) {
222
+ if (spec.type === "ImportNamespaceSpecifier" &&
223
+ spec.local?.type === "Identifier") {
224
+ localNames.add(spec.local.name);
225
+ }
226
+ }
227
+ });
228
+ // TypeScript: `import ns = require("moduleName[/subpath]")`
229
+ root.find(j.TSImportEqualsDeclaration).forEach((importPath) => {
230
+ const ref = importPath.node.moduleReference;
231
+ if (ref.type !== "TSExternalModuleReference")
232
+ return;
233
+ if (!matchesModule((0, exports.getStringValue)(ref.expression)))
234
+ return;
235
+ if (importPath.node.id?.type === "Identifier") {
236
+ localNames.add(importPath.node.id.name);
237
+ }
238
+ });
239
+ // CommonJS: `const ns = require("moduleName[/subpath]")`
240
+ // Top-level only — see the note in `getLocalNamesForImport` for why the
241
+ // collector and the call-site scope guard must agree on module scope.
242
+ root.find(j.CallExpression, {
243
+ callee: { type: "Identifier", name: "require" },
244
+ }).forEach((callPath) => {
245
+ const [arg] = callPath.node.arguments;
246
+ if (!arg || !matchesModule((0, exports.getStringValue)(arg)))
247
+ return;
248
+ if (callPath.scope?.isGlobal !== true)
249
+ return;
250
+ const parent = callPath.parent.node;
251
+ if (parent.type !== "VariableDeclarator" ||
252
+ parent.id.type !== "Identifier") {
253
+ return;
254
+ }
255
+ const name = parent.id.name;
256
+ localNames.add(name);
257
+ });
258
+ return localNames;
259
+ };
260
+ exports.getNamespaceLocalNames = getNamespaceLocalNames;
135
261
  /**
136
262
  * Calls `callback` for each Identifier parameter found in function-like
137
263
  * nodes (functions, methods, arrows) and TSParameterProperty nodes.
@@ -173,11 +299,16 @@ exports.TYPEORM_COLUMN_DECORATORS = new Set([
173
299
  * the file — covers ESM aliases (`import { Column as C }`) and CJS aliases
174
300
  * (`const { Column: C } = require(...)`). Returns a union set suitable for
175
301
  * alias-aware identifier matching.
302
+ *
303
+ * @param opts.valueOnly Forwarded to `getLocalNamesForImport` — see there
304
+ * for semantics. Use when the expanded names will be matched against
305
+ * value-level usages (callees, method receivers) rather than type
306
+ * annotations.
176
307
  */
177
- const expandLocalNamesForImports = (root, j, moduleName, importedNames) => {
308
+ const expandLocalNamesForImports = (root, j, moduleName, importedNames, opts = {}) => {
178
309
  const expanded = new Set();
179
310
  for (const name of importedNames) {
180
- for (const local of (0, exports.getLocalNamesForImport)(root, j, moduleName, name)) {
311
+ for (const local of (0, exports.getLocalNamesForImport)(root, j, moduleName, name, opts)) {
181
312
  expanded.add(local);
182
313
  }
183
314
  }
@@ -216,25 +347,258 @@ const forEachDecoratorObjectArg = (root, j, callback, decoratorNames) => {
216
347
  });
217
348
  };
218
349
  exports.forEachDecoratorObjectArg = forEachDecoratorObjectArg;
350
+ /**
351
+ * Returns the key name for an `ObjectProperty` / `Property` node. Handles
352
+ * both identifier keys (`name`) and string-literal keys (`"name"`), including
353
+ * the computed-string form (`['name']: …`) whose runtime key is still
354
+ * statically knowable. Returns null for dynamic computed keys (`[someVar]`,
355
+ * `[expr]`), numeric keys, and for node types that don't carry a key at all
356
+ * (spread elements, etc.).
357
+ */
358
+ const getObjectPropertyKeyName = (prop) => {
359
+ if (prop.type !== "Property" && prop.type !== "ObjectProperty")
360
+ return null;
361
+ // Computed keys whose expression is a string literal (`['foo']`) are
362
+ // equivalent at runtime to the plain-identifier form (`foo`), so match
363
+ // them. Dynamic computed keys (`[variable]`, `[fn()]`) stay opaque.
364
+ if (prop.computed)
365
+ return (0, exports.getStringValue)(prop.key);
366
+ if (prop.key.type === "Identifier")
367
+ return prop.key.name;
368
+ return (0, exports.getStringValue)(prop.key);
369
+ };
370
+ exports.getObjectPropertyKeyName = getObjectPropertyKeyName;
371
+ /**
372
+ * Walks `new ColumnMetadata({ args: { options: {...} } })` constructor calls
373
+ * and invokes `callback` with the inner `options` ObjectExpression. Mirrors
374
+ * `forEachDecoratorObjectArg` but for the ColumnMetadata class constructor:
375
+ * its `args.options` object is typed `ColumnOptions`, so transforms that
376
+ * rewrite `ColumnOptions` fields (`readonly` → `update`, `width`/`zerofill`
377
+ * removal) must also cover this path. Rare in user code but shows up in
378
+ * multi-tenant / metadata-manipulation patterns.
379
+ *
380
+ * Recognised import / binding shapes, all via
381
+ * `expandLocalNamesForImports` / `getNamespaceLocalNames`:
382
+ *
383
+ * import { ColumnMetadata } from "typeorm"
384
+ * import { ColumnMetadata as CM } from "typeorm"
385
+ * import { ColumnMetadata } from "typeorm/metadata/ColumnMetadata" // sub-path
386
+ * import * as typeorm from "typeorm" // namespace
387
+ * import typeorm = require("typeorm") // TS import-equals
388
+ * const { ColumnMetadata } = require("typeorm") // CJS destructure
389
+ * const typeorm = require("typeorm") // CJS namespace
390
+ *
391
+ * Type-only imports (declaration-level `import type { X }` and per-specifier
392
+ * `import { type X }`, same for `import type * as`) are filtered via
393
+ * `valueOnly: true` — `new X(...)` needs a runtime binding, and a type-only
394
+ * import creates none.
395
+ *
396
+ * Name-level matches are further gated by a scope check: the callee
397
+ * identifier must resolve to the module-level import binding and not a
398
+ * shadowing declaration in an inner scope (function parameter, nested
399
+ * `const`/`let`/`var`, class declaration, catch binding).
400
+ */
401
+ const forEachColumnMetadataOptionsArg = (root, j, target, callback) => {
402
+ const classLocalNames = (0, exports.expandLocalNamesForImports)(root, j, target.moduleName, new Set([target.className]), { valueOnly: true });
403
+ const namespaceLocalNames = (0, exports.getNamespaceLocalNames)(root, j, target.moduleName, { valueOnly: true });
404
+ if (classLocalNames.size === 0 && namespaceLocalNames.size === 0)
405
+ return;
406
+ const resolvesToModuleScope = (path, name) => {
407
+ let scope = path.scope;
408
+ while (scope) {
409
+ if (scope.isGlobal === true || !scope.parent)
410
+ return true;
411
+ if (scope.declares(name))
412
+ return false;
413
+ scope = scope.parent;
414
+ }
415
+ return true;
416
+ };
417
+ const matchesCallee = (callee, path) => {
418
+ if (callee.type === "Identifier") {
419
+ if (!classLocalNames.has(callee.name))
420
+ return false;
421
+ return resolvesToModuleScope(path, callee.name);
422
+ }
423
+ if ((callee.type === "MemberExpression" ||
424
+ callee.type === "OptionalMemberExpression") &&
425
+ callee.computed !== true) {
426
+ const obj = callee.object;
427
+ const prop = callee.property;
428
+ if ((0, exports.isIdentifier)(obj) &&
429
+ (0, exports.isIdentifier)(prop) &&
430
+ prop.name === target.className) {
431
+ if (!namespaceLocalNames.has(obj.name))
432
+ return false;
433
+ return resolvesToModuleScope(path, obj.name);
434
+ }
435
+ }
436
+ return false;
437
+ };
438
+ // Returns the property value as an ObjectExpression if the property with
439
+ // the given key exists and its value is an ObjectExpression (after peeling
440
+ // TS wrappers like `as` / `satisfies` / `!` / parens). Uses `findLast` so
441
+ // duplicate keys match JS object-literal semantics (last occurrence wins).
442
+ const findObjectPropertyValue = (obj, keyName) => {
443
+ const prop = obj.properties.findLast((p) => (0, exports.getObjectPropertyKeyName)(p) === keyName);
444
+ if (!prop ||
445
+ (prop.type !== "Property" && prop.type !== "ObjectProperty")) {
446
+ return null;
447
+ }
448
+ const value = (0, exports.unwrapTsExpression)(prop.value);
449
+ return value.type === "ObjectExpression" ? value : null;
450
+ };
451
+ root.find(j.NewExpression).forEach((path) => {
452
+ if (!matchesCallee(path.node.callee, path))
453
+ return;
454
+ const [firstArg] = path.node.arguments;
455
+ const arg = firstArg ? (0, exports.unwrapTsExpression)(firstArg) : undefined;
456
+ if (arg?.type !== "ObjectExpression")
457
+ return;
458
+ const argsValue = findObjectPropertyValue(arg, "args");
459
+ if (!argsValue)
460
+ return;
461
+ const optionsValue = findObjectPropertyValue(argsValue, "options");
462
+ if (!optionsValue)
463
+ return;
464
+ callback(optionsValue, path);
465
+ });
466
+ };
467
+ exports.forEachColumnMetadataOptionsArg = forEachColumnMetadataOptionsArg;
468
+ /**
469
+ * TypeORM `Repository` / `EntityManager` methods that accept a FindOptions
470
+ * object (with `select`/`relations`/`where`/…). Used to scope the
471
+ * `select: [...]` / `relations: [...]` → object-form transforms to
472
+ * arguments passed into these methods. Deliberately excludes the
473
+ * `*By` variants (`findBy`, `findOneBy`, `findOneByOrFail`,
474
+ * `findAndCountBy`, `countBy`, `existsBy`) — those accept a plain WHERE
475
+ * object, so rewriting a top-level `select` or `relations` key there would
476
+ * mangle matches against entity fields of those names.
477
+ *
478
+ * The method-name check (rather than a file-level typeorm import gate)
479
+ * lets the transforms fire in NestJS-style service files that only pull
480
+ * TypeORM types via a wrapper module.
481
+ */
482
+ exports.TYPEORM_FIND_OPTIONS_METHODS = new Set([
483
+ "find",
484
+ "findAndCount",
485
+ "findOne",
486
+ "findOneOrFail",
487
+ "count",
488
+ "exists",
489
+ ]);
490
+ /**
491
+ * Returns true when `node` is a call of shape `Object.fromEntries(...)`.
492
+ * Used by the find-option transforms to stay idempotent — a second pass
493
+ * must not wrap an already-wrapped dynamic value in another `fromEntries`.
494
+ */
495
+ const isObjectFromEntriesCall = (node) => {
496
+ if (node.type !== "CallExpression")
497
+ return false;
498
+ const callee = node.callee;
499
+ if (callee.type !== "MemberExpression")
500
+ return false;
501
+ const m = callee;
502
+ return (m.object.type === "Identifier" &&
503
+ m.object.name === "Object" &&
504
+ m.property.type === "Identifier" &&
505
+ m.property.name === "fromEntries");
506
+ };
507
+ exports.isObjectFromEntriesCall = isObjectFromEntriesCall;
508
+ /**
509
+ * Returns true when the given `ObjectProperty` lives inside an object that
510
+ * is an argument to one of the TYPEORM_FIND_OPTIONS_METHODS. Matches both
511
+ * `repo.find({ select: [...] })` (object is the single argument) and
512
+ * `manager.find(Entity, { select: [...] })` (object is the second arg).
513
+ */
514
+ const isFindMethodCallArgument = (propPath) => {
515
+ const objExprPath = propPath.parent;
516
+ if (objExprPath?.node.type !== "ObjectExpression") {
517
+ return false;
518
+ }
519
+ // Walk up through TS expression wrappers (`{ select: [...] } as T`,
520
+ // `satisfies Opts`, parens) before expecting a CallExpression. Without
521
+ // this, `repo.find({ select: [...] } as FindOptions)` is missed.
522
+ let ancestor = objExprPath.parent;
523
+ while (ancestor) {
524
+ const t = ancestor.node.type;
525
+ if (t === "TSAsExpression" ||
526
+ t === "TSSatisfiesExpression" ||
527
+ t === "TSTypeAssertion" ||
528
+ t === "TSNonNullExpression" ||
529
+ t === "ParenthesizedExpression") {
530
+ ancestor = ancestor.parent;
531
+ continue;
532
+ }
533
+ break;
534
+ }
535
+ if (!ancestor)
536
+ return false;
537
+ const callNode = ancestor.node;
538
+ if (callNode.type !== "CallExpression" &&
539
+ callNode.type !== "OptionalCallExpression") {
540
+ return false;
541
+ }
542
+ const callee = callNode.callee;
543
+ if (callee.type !== "MemberExpression" &&
544
+ callee.type !== "OptionalMemberExpression") {
545
+ return false;
546
+ }
547
+ const prop = callee.property;
548
+ if (prop.type !== "Identifier")
549
+ return false;
550
+ return exports.TYPEORM_FIND_OPTIONS_METHODS.has(prop.name);
551
+ };
552
+ exports.isFindMethodCallArgument = isFindMethodCallArgument;
553
+ /**
554
+ * Peels TypeScript expression wrappers and parenthesized expressions around
555
+ * a value so callers see the underlying node. Handles `as X` / `x!` /
556
+ * `x satisfies X` / `<X>x` / `(x)`. Used by transforms that inspect values
557
+ * which users may annotate with type assertions — e.g.
558
+ * `type: "expo" as const`, `type: ("mongodb")`,
559
+ * `{ logPath: "x" } as Options`.
560
+ *
561
+ * Generic over the node type so callers can keep their original narrowing
562
+ * — in practice the returned node is the same type as the input (with TS
563
+ * wrapper variants peeled off).
564
+ */
565
+ const unwrapTsExpression = (node) => {
566
+ let current = node;
567
+ while (current.type === "TSAsExpression" ||
568
+ current.type === "TSNonNullExpression" ||
569
+ current.type === "TSSatisfiesExpression" ||
570
+ current.type === "TSTypeAssertion" ||
571
+ current.type === "ParenthesizedExpression") {
572
+ const inner = current.expression;
573
+ if (!inner)
574
+ break;
575
+ current = inner;
576
+ }
577
+ return current;
578
+ };
579
+ exports.unwrapTsExpression = unwrapTsExpression;
219
580
  /**
220
581
  * Removes properties matching the given key names from an ObjectExpression.
221
582
  * Matches both identifier keys (`name`) and string-literal keys (`"name"`).
222
583
  * Returns true if any properties were removed.
223
584
  */
224
- const removeObjectProperties = (obj, propertyNames) => {
585
+ const removeObjectProperties = (obj, propertyNames) => (0, exports.removeObjectPropertiesWhere)(obj, (prop) => {
586
+ const key = (0, exports.getObjectPropertyKeyName)(prop);
587
+ return key !== null && propertyNames.has(key);
588
+ });
589
+ exports.removeObjectProperties = removeObjectProperties;
590
+ /**
591
+ * Removes every property from `obj` that satisfies `predicate`. Returns true
592
+ * if any property was removed. Use this over `removeObjectProperties` when
593
+ * removal needs to inspect the property value (not just the key name) —
594
+ * e.g. "remove `driver` only when its value is `require("expo-sqlite")`".
595
+ */
596
+ const removeObjectPropertiesWhere = (obj, predicate) => {
225
597
  const original = obj.properties.length;
226
- obj.properties = obj.properties.filter((prop) => {
227
- if (prop.type !== "Property" && prop.type !== "ObjectProperty") {
228
- return true;
229
- }
230
- const keyName = prop.key.type === "Identifier"
231
- ? prop.key.name
232
- : (0, exports.getStringValue)(prop.key);
233
- return keyName === null ? true : !propertyNames.has(keyName);
234
- });
598
+ obj.properties = obj.properties.filter((prop) => !predicate(prop));
235
599
  return obj.properties.length !== original;
236
600
  };
237
- exports.removeObjectProperties = removeObjectProperties;
601
+ exports.removeObjectPropertiesWhere = removeObjectPropertiesWhere;
238
602
  /**
239
603
  * Finds imports from a module, removes the specified named import specifiers,
240
604
  * and removes the entire import declaration if no specifiers remain.
@@ -264,6 +628,77 @@ const removeImportSpecifiers = (root, j, moduleName, specifierNames) => {
264
628
  return removed;
265
629
  };
266
630
  exports.removeImportSpecifiers = removeImportSpecifiers;
631
+ // Returns true for `"moduleName"` and any sub-path like `"moduleName/..."`.
632
+ // Used so re-export helpers can match `export { X } from "typeorm"` as well
633
+ // as `export { SapConnectionOptions } from "typeorm/driver/sap/SapConnectionOptions"`.
634
+ const matchesModuleOrSubPath = (source, moduleName) => typeof source === "string" &&
635
+ (source === moduleName || source.startsWith(`${moduleName}/`));
636
+ /**
637
+ * Finds re-exports from a module (`export { X } from "module"`) and removes
638
+ * the named specifiers listed in `specifierNames`. Also matches sub-path
639
+ * re-exports (`export { X } from "module/sub/path"`). Removes the entire
640
+ * `ExportNamedDeclaration` if no specifiers remain. Returns true if any
641
+ * specifiers were removed.
642
+ */
643
+ const removeReExportSpecifiers = (root, j, moduleName, specifierNames) => {
644
+ let removed = false;
645
+ root.find(j.ExportNamedDeclaration).forEach((exportPath) => {
646
+ const source = exportPath.node.source?.value;
647
+ if (!matchesModuleOrSubPath(source, moduleName))
648
+ return;
649
+ const remaining = exportPath.node.specifiers?.filter((spec) => {
650
+ if (spec.type === "ExportSpecifier" &&
651
+ spec.local?.type === "Identifier" &&
652
+ specifierNames.has(spec.local.name)) {
653
+ removed = true;
654
+ return false;
655
+ }
656
+ return true;
657
+ });
658
+ if (remaining?.length === 0) {
659
+ j(exportPath).remove();
660
+ }
661
+ else if (remaining) {
662
+ exportPath.node.specifiers = remaining;
663
+ }
664
+ });
665
+ return removed;
666
+ };
667
+ exports.removeReExportSpecifiers = removeReExportSpecifiers;
668
+ /**
669
+ * Finds re-exports from a module (`export { X } from "module"`) and renames
670
+ * specifiers according to the `renames` map. Also matches sub-path
671
+ * re-exports (`export { X } from "module/sub/path"`). When the re-export has
672
+ * an alias (`export { X as Y }`), only the local name is renamed so
673
+ * downstream consumers continue to see the same exported name. Returns true
674
+ * if any specifiers were renamed.
675
+ */
676
+ const renameReExportSpecifiers = (root, j, moduleName, renames) => {
677
+ let renamed = false;
678
+ root.find(j.ExportNamedDeclaration).forEach((exportPath) => {
679
+ const source = exportPath.node.source?.value;
680
+ if (!matchesModuleOrSubPath(source, moduleName))
681
+ return;
682
+ exportPath.node.specifiers?.forEach((spec) => {
683
+ if (spec.type !== "ExportSpecifier" ||
684
+ spec.local?.type !== "Identifier") {
685
+ return;
686
+ }
687
+ const newName = renames[spec.local.name];
688
+ if (!newName)
689
+ return;
690
+ const wasAlias = spec.exported.type === "Identifier" &&
691
+ spec.exported.name !== spec.local.name;
692
+ spec.local.name = newName;
693
+ if (!wasAlias && spec.exported.type === "Identifier") {
694
+ spec.exported.name = newName;
695
+ }
696
+ renamed = true;
697
+ });
698
+ });
699
+ return renamed;
700
+ };
701
+ exports.renameReExportSpecifiers = renameReExportSpecifiers;
267
702
  /**
268
703
  * Finds CallExpression nodes with a MemberExpression callee where the
269
704
  * property matches `oldName`, and renames the property to `newName`.
@@ -286,4 +721,363 @@ const renameMemberMethod = (root, j, oldName, newName) => {
286
721
  return renamed;
287
722
  };
288
723
  exports.renameMemberMethod = renameMemberMethod;
724
+ /**
725
+ * Walks a TypeScript type-annotation node and returns the root identifier
726
+ * name — e.g. `Repository<User>` → `"Repository"`, `Repository<User> | null`
727
+ * → `"Repository"`, `typeof Repository` → `"Repository"`. Returns null when
728
+ * the annotation doesn't root on a TSTypeReference with an Identifier name.
729
+ *
730
+ * When `typeormNamespaceNames` is passed, qualified references like
731
+ * `ns.Repository<User>` are only returned if `ns` is one of the provided
732
+ * namespace locals — otherwise the qualified reference is rejected so that
733
+ * unrelated namespaces (`Foo.Repository<T>` from some-other-lib) are not
734
+ * mistaken for TypeORM types. Without the parameter, any qualifier is
735
+ * accepted, which matches legacy callers that don't care about provenance.
736
+ */
737
+ // Walks the members of a union/intersection type and prefers the first
738
+ // TypeORM-family name (so `FooBar | Repository<T>` or `null | Repository<T>`
739
+ // classify correctly). Falls back to the first non-null root name to keep
740
+ // pre-existing behavior for callers that don't care about TypeORM gating.
741
+ const findUnionOrIntersectionRoot = (types, typeormNamespaceNames) => {
742
+ let firstName = null;
743
+ for (const member of types) {
744
+ const name = (0, exports.getTypeReferenceRootName)(member, typeormNamespaceNames);
745
+ if (!name)
746
+ continue;
747
+ if (exports.TYPEORM_REPOSITORY_TYPES.has(name) ||
748
+ exports.TYPEORM_DATASOURCE_TYPES.has(name)) {
749
+ return name;
750
+ }
751
+ firstName ??= name;
752
+ }
753
+ return firstName;
754
+ };
755
+ // Returns the rightmost segment of a `TSQualifiedName` (`a.b.c` in type
756
+ // position) — always an `Identifier` in valid TS, since the nesting lives
757
+ // on `.left`. When `typeormNamespaceNames` is provided, the leftmost segment
758
+ // is checked against the set of typeorm namespace locals and the rightmost
759
+ // name is only returned if the qualifier matches — so `typeorm.Repository`
760
+ // resolves to `"Repository"` while `other.Repository` returns null, avoiding
761
+ // false-positive classification of identically-named types from other
762
+ // modules. Without the parameter, any qualifier is accepted (legacy
763
+ // behaviour for callers that only look at the rightmost segment).
764
+ const getQualifiedNameRightmost = (node, typeormNamespaceNames) => {
765
+ const n = node;
766
+ if (n.right.type !== "Identifier")
767
+ return null;
768
+ if (typeormNamespaceNames !== undefined) {
769
+ // Walk down `.left` to the leftmost identifier (qualifier root).
770
+ let leftmost = n.left;
771
+ while (leftmost.type === "TSQualifiedName") {
772
+ leftmost = leftmost.left;
773
+ }
774
+ if (leftmost.type !== "Identifier")
775
+ return null;
776
+ if (!typeormNamespaceNames.has(leftmost.name))
777
+ return null;
778
+ }
779
+ return n.right.name;
780
+ };
781
+ const getTypeReferenceRootName = (node, typeormNamespaceNames) => {
782
+ if (!node)
783
+ return null;
784
+ if (node.type === "TSTypeReference") {
785
+ const n = node;
786
+ if (n.typeName.type === "Identifier") {
787
+ return n.typeName.name;
788
+ }
789
+ // `typeorm.Repository<User>` — TSTypeReference wraps a TSQualifiedName
790
+ // (namespace-import access). Without this branch,
791
+ // `import * as typeorm from "typeorm"` callers would be missed.
792
+ if (n.typeName.type === "TSQualifiedName") {
793
+ return getQualifiedNameRightmost(n.typeName, typeormNamespaceNames);
794
+ }
795
+ }
796
+ // `const X: typeof Repository` — TSTypeQuery wraps the referenced
797
+ // identifier in `exprName`. Without this branch, type-of annotations on
798
+ // typeorm-family types wouldn't register as repository bindings.
799
+ if (node.type === "TSTypeQuery") {
800
+ const n = node;
801
+ if (n.exprName.type === "Identifier") {
802
+ return n.exprName.name;
803
+ }
804
+ if (n.exprName.type === "TSQualifiedName") {
805
+ return getQualifiedNameRightmost(n.exprName, typeormNamespaceNames);
806
+ }
807
+ }
808
+ if (node.type === "TSTypeAnnotation") {
809
+ const n = node;
810
+ return (0, exports.getTypeReferenceRootName)(n.typeAnnotation, typeormNamespaceNames);
811
+ }
812
+ if (node.type === "TSUnionType" || node.type === "TSIntersectionType") {
813
+ return findUnionOrIntersectionRoot(node.types, typeormNamespaceNames);
814
+ }
815
+ return null;
816
+ };
817
+ exports.getTypeReferenceRootName = getTypeReferenceRootName;
818
+ /**
819
+ * TypeORM Repository-family type names used to detect bindings that hold a
820
+ * Repository/EntityManager instance, so transforms can scope method renames
821
+ * (`.findByIds`, `.findOneById`, `.exist`, `.stats`) to those receivers.
822
+ */
823
+ exports.TYPEORM_REPOSITORY_TYPES = new Set([
824
+ "Repository",
825
+ "TreeRepository",
826
+ "MongoRepository",
827
+ "EntityRepository",
828
+ "AbstractRepository",
829
+ "EntityManager",
830
+ "MongoEntityManager",
831
+ "SqljsEntityManager",
832
+ ]);
833
+ /**
834
+ * Call shapes whose return value is a Repository/EntityManager:
835
+ * `.getRepository(...)`, `.getMongoRepository(...)`, `.getTreeRepository(...)`,
836
+ * `.getCustomRepository(...)`, `.manager` (property access, see caller).
837
+ */
838
+ const REPOSITORY_RETURNING_METHODS = new Set([
839
+ "getRepository",
840
+ "getMongoRepository",
841
+ "getTreeRepository",
842
+ "getCustomRepository",
843
+ ]);
844
+ // Returns true when `node` is a call expression whose callee ends in one of
845
+ // the Repository-returning method names (e.g. `ds.getRepository(User)`).
846
+ const isRepositoryReturningCall = (node) => {
847
+ if (node.type !== "CallExpression" &&
848
+ node.type !== "OptionalCallExpression")
849
+ return false;
850
+ const call = node;
851
+ const callee = call.callee;
852
+ if (callee.type !== "MemberExpression" &&
853
+ callee.type !== "OptionalMemberExpression")
854
+ return false;
855
+ const member = callee;
856
+ if (member.property.type === "Identifier") {
857
+ return REPOSITORY_RETURNING_METHODS.has(member.property.name);
858
+ }
859
+ if (member.computed) {
860
+ const name = (0, exports.getStringValue)(member.property);
861
+ return name !== null && REPOSITORY_RETURNING_METHODS.has(name);
862
+ }
863
+ return false;
864
+ };
865
+ /**
866
+ * TypeORM DataSource-family type names, recognized on local bindings so
867
+ * `dataSource.manager.X()` can be classified as a Repository receiver via
868
+ * the `.manager` accessor chain.
869
+ */
870
+ exports.TYPEORM_DATASOURCE_TYPES = new Set([
871
+ "DataSource",
872
+ "Connection",
873
+ ]);
874
+ // Classifies a type name into one of the tracked receiver buckets, or
875
+ // returns null when the type isn't a TypeORM receiver we care about.
876
+ const classifyRepositoryTypeName = (typeName, bindings, forThisMember) => {
877
+ if (exports.TYPEORM_REPOSITORY_TYPES.has(typeName)) {
878
+ return forThisMember ? bindings.classProps : bindings.locals;
879
+ }
880
+ if (exports.TYPEORM_DATASOURCE_TYPES.has(typeName)) {
881
+ return forThisMember
882
+ ? bindings.dataSourceClassProps
883
+ : bindings.dataSourceLocals;
884
+ }
885
+ return null;
886
+ };
887
+ // Records a typed identifier (variable / parameter) into the appropriate
888
+ // binding bucket based on its TypeScript annotation.
889
+ const recordTypedIdentifier = (id, annotation, bindings, typeormNamespaceNames) => {
890
+ if (id.type !== "Identifier")
891
+ return;
892
+ const typeName = (0, exports.getTypeReferenceRootName)(annotation, typeormNamespaceNames);
893
+ if (!typeName)
894
+ return;
895
+ const bucket = classifyRepositoryTypeName(typeName, bindings, false);
896
+ bucket?.add(id.name);
897
+ };
898
+ // Resolves the underlying `Identifier` inside a function parameter — peeling
899
+ // `AssignmentPattern` (default params) and `TSParameterProperty` (constructor
900
+ // parameter properties). `isParameterProperty` is true only for
901
+ // TSParameterProperty, so callers can additionally record it as a
902
+ // `this.<name>` class member.
903
+ const unwrapParameterIdentifier = (param) => {
904
+ if (param.type === "Identifier") {
905
+ return {
906
+ id: param,
907
+ annotation: param.typeAnnotation ?? null,
908
+ isParameterProperty: false,
909
+ };
910
+ }
911
+ if (param.type !== "AssignmentPattern" &&
912
+ param.type !== "TSParameterProperty") {
913
+ return null;
914
+ }
915
+ const inner = param.left ??
916
+ param.parameter;
917
+ if (inner?.type !== "Identifier")
918
+ return null;
919
+ return {
920
+ id: inner,
921
+ annotation: inner.typeAnnotation ?? null,
922
+ isParameterProperty: param.type === "TSParameterProperty",
923
+ };
924
+ };
925
+ // Scans VariableDeclarators for typed identifiers and initializers that
926
+ // return a Repository via `.getRepository()`-family calls.
927
+ const collectDeclaratorBindings = (root, j, bindings, typeormNamespaceNames) => {
928
+ root.find(j.VariableDeclarator).forEach((p) => {
929
+ const id = p.node.id;
930
+ if (id.type !== "Identifier")
931
+ return;
932
+ recordTypedIdentifier(id, id.typeAnnotation ?? null, bindings, typeormNamespaceNames);
933
+ const init = p.node.init;
934
+ if (init && isRepositoryReturningCall(init)) {
935
+ bindings.locals.add(id.name);
936
+ }
937
+ });
938
+ root.find(j.AssignmentExpression).forEach((p) => {
939
+ const left = p.node.left;
940
+ if (left.type !== "Identifier")
941
+ return;
942
+ if (!isRepositoryReturningCall(p.node.right))
943
+ return;
944
+ bindings.locals.add(left.name);
945
+ });
946
+ };
947
+ // Scans function parameters on every function-like node. TSParameterProperty
948
+ // bindings (`constructor(private repo: Repository<T>)`) are tracked in both
949
+ // `locals` (for accesses inside the constructor body) AND `classProps` (for
950
+ // `this.repo` accesses elsewhere in the class).
951
+ const collectFunctionParamBindings = (root, j, bindings, typeormNamespaceNames) => {
952
+ const visit = (node) => {
953
+ for (const param of node.params) {
954
+ const unwrapped = unwrapParameterIdentifier(param);
955
+ if (!unwrapped)
956
+ continue;
957
+ recordTypedIdentifier(unwrapped.id, unwrapped.annotation, bindings, typeormNamespaceNames);
958
+ if (unwrapped.isParameterProperty &&
959
+ unwrapped.id.type === "Identifier") {
960
+ const typeName = (0, exports.getTypeReferenceRootName)(unwrapped.annotation, typeormNamespaceNames);
961
+ if (typeName) {
962
+ const classBucket = classifyRepositoryTypeName(typeName, bindings, true);
963
+ classBucket?.add(unwrapped.id.name);
964
+ }
965
+ }
966
+ }
967
+ };
968
+ root.find(j.FunctionDeclaration).forEach((p) => visit(p.node));
969
+ root.find(j.FunctionExpression).forEach((p) => visit(p.node));
970
+ root.find(j.ArrowFunctionExpression).forEach((p) => visit(p.node));
971
+ root.find(j.ClassMethod).forEach((p) => visit(p.node));
972
+ root.find(j.TSDeclareMethod).forEach((p) => visit(p.node));
973
+ };
974
+ // Scans class properties — `this.<name>` receivers are tracked separately
975
+ // from local identifiers because the access shape is different.
976
+ const collectClassPropertyBindings = (root, j, bindings, typeormNamespaceNames) => {
977
+ root.find(j.ClassProperty).forEach((p) => {
978
+ const key = p.node.key;
979
+ if (key.type !== "Identifier")
980
+ return;
981
+ const annotation = p.node.typeAnnotation ?? null;
982
+ const typeName = (0, exports.getTypeReferenceRootName)(annotation, typeormNamespaceNames);
983
+ if (!typeName)
984
+ return;
985
+ const bucket = classifyRepositoryTypeName(typeName, bindings, true);
986
+ bucket?.add(key.name);
987
+ });
988
+ };
989
+ const collectRepositoryBindings = (root, j) => {
990
+ const bindings = {
991
+ locals: new Set(),
992
+ classProps: new Set(),
993
+ dataSourceLocals: new Set(),
994
+ dataSourceClassProps: new Set(),
995
+ };
996
+ // Qualified type references like `typeorm.Repository<T>` must only be
997
+ // classified as TypeORM receivers when the qualifier is an actual
998
+ // namespace-local of `import * as typeorm from "typeorm"` (or the
999
+ // `import ns = require("typeorm")` variants). Without this gate, a
1000
+ // file containing both a typeorm import and `Foo.Repository<T>` from
1001
+ // some-other-lib would treat `Foo.Repository`-typed bindings as
1002
+ // Repository receivers and mis-rewrite their method calls.
1003
+ const typeormNamespaceNames = (0, exports.getNamespaceLocalNames)(root, j, "typeorm");
1004
+ collectDeclaratorBindings(root, j, bindings, typeormNamespaceNames);
1005
+ collectFunctionParamBindings(root, j, bindings, typeormNamespaceNames);
1006
+ collectClassPropertyBindings(root, j, bindings, typeormNamespaceNames);
1007
+ return bindings;
1008
+ };
1009
+ exports.collectRepositoryBindings = collectRepositoryBindings;
1010
+ /**
1011
+ * Returns true when `receiver` is a MemberExpression-eligible node whose
1012
+ * root identifier is a Repository-bound local or `this.X` where `X` is a
1013
+ * Repository-typed class property. Also accepts fresh inline
1014
+ * `.getRepository(...)` call-expression receivers and DataSource-typed
1015
+ * bindings dereferenced via `.manager` (e.g. `ds.manager.findByIds(...)`).
1016
+ *
1017
+ * When the file contains no Repository-typed bindings at all (no typed
1018
+ * variables, no `.getRepository()` assignments, no Repository class
1019
+ * properties), falls back to permissive matching — the codemod has no
1020
+ * type signal and must trust the historical `fileImportsFrom("typeorm")`
1021
+ * file-level guard. Callers should add negative fixtures to pin the
1022
+ * common false-positive vectors.
1023
+ */
1024
+ // Matches `this.<prop>` where `<prop>` is a tracked DataSource class prop.
1025
+ const isThisDataSourceProp = (node, dsClassProps) => (node.type === "MemberExpression" ||
1026
+ node.type === "OptionalMemberExpression") &&
1027
+ node.object.type === "ThisExpression" &&
1028
+ node.property.type === "Identifier" &&
1029
+ (dsClassProps?.has(node.property.name) ??
1030
+ false);
1031
+ // `<ds>.manager` — DataSource local/class-prop's `.manager` accessor
1032
+ // returns an EntityManager, whose find-family methods match Repository's.
1033
+ const isDataSourceManagerChain = (member, dsLocals, dsClassProps) => {
1034
+ if (member.property.type !== "Identifier" ||
1035
+ member.property.name !== "manager") {
1036
+ return false;
1037
+ }
1038
+ if (member.object.type === "Identifier" &&
1039
+ (dsLocals?.has(member.object.name) ?? false)) {
1040
+ return true;
1041
+ }
1042
+ return isThisDataSourceProp(member.object, dsClassProps);
1043
+ };
1044
+ // Classifies MemberExpression / OptionalMemberExpression receivers.
1045
+ const classifyMemberReceiver = (receiver, bindings, noBindingsFound) => {
1046
+ const member = receiver;
1047
+ if (isDataSourceManagerChain(member, bindings.dataSourceLocals, bindings.dataSourceClassProps)) {
1048
+ return true;
1049
+ }
1050
+ if (member.object.type === "ThisExpression" &&
1051
+ member.property.type === "Identifier") {
1052
+ if (bindings.classProps.has(member.property.name))
1053
+ return true;
1054
+ return noBindingsFound;
1055
+ }
1056
+ // Chained access like `service.userRepo.findByIds(...)` — accept only
1057
+ // when we have no binding info to disambiguate.
1058
+ return noBindingsFound;
1059
+ };
1060
+ const isRepositoryReceiver = (receiver, bindings) => {
1061
+ const noBindingsFound = bindings.locals.size === 0 &&
1062
+ bindings.classProps.size === 0 &&
1063
+ (bindings.dataSourceLocals?.size ?? 0) === 0 &&
1064
+ (bindings.dataSourceClassProps?.size ?? 0) === 0;
1065
+ if (receiver.type === "Identifier") {
1066
+ if (bindings.locals.has(receiver.name))
1067
+ return true;
1068
+ return noBindingsFound;
1069
+ }
1070
+ if (receiver.type === "MemberExpression" ||
1071
+ receiver.type === "OptionalMemberExpression") {
1072
+ return classifyMemberReceiver(receiver, bindings, noBindingsFound);
1073
+ }
1074
+ if (receiver.type === "CallExpression" ||
1075
+ receiver.type === "OptionalCallExpression") {
1076
+ if (isRepositoryReturningCall(receiver))
1077
+ return true;
1078
+ return noBindingsFound;
1079
+ }
1080
+ return noBindingsFound;
1081
+ };
1082
+ exports.isRepositoryReceiver = isRepositoryReceiver;
289
1083
  //# sourceMappingURL=ast-helpers.js.map