@dudousxd/nestjs-codegen 0.18.0 → 0.19.0

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,55 @@
1
1
  # @dudousxd/nestjs-codegen
2
2
 
3
+ ## 0.19.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 8ffea16: Discover controller factories called through a property, and warn — loudly —
8
+ when a factory heritage clause cannot be followed at all.
9
+
10
+ A controller whose base came from anything other than a bare function name
11
+ contributed ZERO routes to the generated client:
12
+
13
+ ```ts
14
+ class A extends tables.createTableController(Entity, {}) {} // namespace import
15
+ class B extends TableFactory.create(Entity, {}) {} // static method
16
+ class C extends factories.table(Entity, {}) {} // re-export object
17
+ ```
18
+
19
+ Discovery required the callee of the factory call to be an `Identifier`, so each
20
+ of these resolved to nothing — and unlike the earlier filter gaps, this does not
21
+ mutilate a route's contract, it deletes the whole controller. `tsc` green,
22
+ codegen green, no warning: the first sign is a client call that does not exist.
23
+
24
+ The callee is now resolved through its name node, which covers a namespace import
25
+ (`ns.factory(...)`), a static method (`Factory.create(...)`) and a property of a
26
+ re-export object (`factories.table(...)`, including the `{ createTableController }`
27
+ shorthand). Bare identifiers are unchanged. A callee with no name node — an
28
+ element access (`factories['table'](...)`) or a callee that is itself a call
29
+ (`makeFactory()(Entity)`) — would mean evaluating the program to name the
30
+ function, so it stays unresolved.
31
+
32
+ Unresolved is no longer silent. A `@Controller`-decorated class whose heritage is
33
+ a factory-shaped CALL that discovery cannot follow now prints one line naming the
34
+ file, the class, the callee and what it costs:
35
+
36
+ ```
37
+ [nestjs-codegen/fast] SearchUtilsController in /src/utils/search.controller.ts
38
+ extends makeTableController()(...) but its callee is not a name that can be
39
+ resolved statically — it contributes NO routes to the generated client.
40
+ ```
41
+
42
+ Scoped tightly on purpose: it is a warning and never a throw, and it stays quiet
43
+ for `extends SomeBaseClass` (not a call) and for any class without `@Controller`
44
+ (extending a call expression is ordinary code). So the next unsupported shape
45
+ costs a line on stderr instead of a day.
46
+
47
+ `MixinBinding.factoryName` now carries the resolved DECLARATION's name rather
48
+ than the call-site text — a qualified `tables.createTableController` would never
49
+ match the by-name lookup consumers do inside `factoryFilePath`. Identical for
50
+ every existing bare-identifier call site, bar an import alias, where the
51
+ declaration name is the one that resolves.
52
+
3
53
  ## 0.18.0
4
54
 
5
55
  ### Minor Changes
package/dist/cli/main.cjs CHANGED
@@ -3491,10 +3491,46 @@ function resolveReturnedClass(factoryDecl) {
3491
3491
  }
3492
3492
  return void 0;
3493
3493
  }
3494
+ function factoryCalleeName(call) {
3495
+ const callee = unwrapExpression(call.getExpression());
3496
+ if (import_ts_morph6.Node.isIdentifier(callee)) return callee;
3497
+ if (import_ts_morph6.Node.isPropertyAccessExpression(callee)) {
3498
+ const name = callee.getNameNode();
3499
+ return import_ts_morph6.Node.isIdentifier(name) ? name : void 0;
3500
+ }
3501
+ return void 0;
3502
+ }
3494
3503
  function resolveFactoryDeclaration(call) {
3495
- const callee = call.getExpression();
3496
- if (!import_ts_morph6.Node.isIdentifier(callee)) return void 0;
3497
- return callee.getDefinitions().map((d) => d.getDeclarationNode()).find((n) => import_ts_morph6.Node.isFunctionDeclaration(n));
3504
+ const name = factoryCalleeName(call);
3505
+ return name ? resolveFactoryFromName(name, /* @__PURE__ */ new Set()) : void 0;
3506
+ }
3507
+ function resolveFactoryFromName(name, seen) {
3508
+ if (seen.has(name)) return void 0;
3509
+ seen.add(name);
3510
+ const decls = name.getDefinitions().map((d) => d.getDeclarationNode()).filter((n) => n !== void 0);
3511
+ for (const decl of decls) {
3512
+ if (import_ts_morph6.Node.isFunctionDeclaration(decl) || import_ts_morph6.Node.isMethodDeclaration(decl)) return decl;
3513
+ if (import_ts_morph6.Node.isPropertyAssignment(decl)) {
3514
+ const init = decl.getInitializer();
3515
+ const inner = init ? unwrapExpression(init) : void 0;
3516
+ if (inner && import_ts_morph6.Node.isIdentifier(inner)) {
3517
+ const resolved = resolveFactoryFromName(inner, seen);
3518
+ if (resolved) return resolved;
3519
+ }
3520
+ continue;
3521
+ }
3522
+ if (import_ts_morph6.Node.isShorthandPropertyAssignment(decl)) {
3523
+ const nameNode = decl.getNameNode();
3524
+ const resolved = import_ts_morph6.Node.isIdentifier(nameNode) ? resolveFactoryFromName(nameNode, seen) : void 0;
3525
+ if (resolved) return resolved;
3526
+ }
3527
+ }
3528
+ return void 0;
3529
+ }
3530
+ function warnUnresolvedFactory(cls, calleeText, reason) {
3531
+ console.warn(
3532
+ `[nestjs-codegen/fast] ${cls.getName() ?? "<anonymous class>"} in ${cls.getSourceFile().getFilePath()} extends ${calleeText}(...) but ${reason} \u2014 it contributes NO routes to the generated client. Resolvable factory callees: a name (\`factory(...)\`), a namespace or object property (\`ns.factory(...)\`), or a static method (\`Factory.create(...)\`), each resolving to a function or method declaration that returns a class.`
3533
+ );
3498
3534
  }
3499
3535
  function resolveFactoryStaticClass(node) {
3500
3536
  if (!import_ts_morph6.Node.isPropertyAccessExpression(node)) return void 0;
@@ -3516,12 +3552,26 @@ function resolveFactoryStaticClass(node) {
3516
3552
  function resolveInheritedMethods(cls) {
3517
3553
  const expr = resolveHeritageCall(cls.getExtends()?.getExpression());
3518
3554
  if (!expr) return void 0;
3519
- const callee = expr.getExpression();
3520
- if (!import_ts_morph6.Node.isIdentifier(callee)) return void 0;
3555
+ const isController = cls.getDecorator("Controller") !== void 0;
3556
+ const calleeText = expr.getExpression().getText();
3521
3557
  const factoryDecl = resolveFactoryDeclaration(expr);
3522
- if (!factoryDecl) return void 0;
3558
+ if (!factoryDecl) {
3559
+ if (isController) {
3560
+ warnUnresolvedFactory(
3561
+ cls,
3562
+ calleeText,
3563
+ factoryCalleeName(expr) ? "its callee does not resolve to a function or method declaration" : "its callee is not a name that can be resolved statically"
3564
+ );
3565
+ }
3566
+ return void 0;
3567
+ }
3523
3568
  const returnedClass = resolveReturnedClass(factoryDecl);
3524
- if (!returnedClass) return void 0;
3569
+ if (!returnedClass) {
3570
+ if (isController) {
3571
+ warnUnresolvedFactory(cls, calleeText, "that factory does not return a class declaration");
3572
+ }
3573
+ return void 0;
3574
+ }
3525
3575
  const classArgs = [];
3526
3576
  const namedClassArgs = {};
3527
3577
  for (const arg of expr.getArguments()) {
@@ -3539,7 +3589,12 @@ function resolveInheritedMethods(cls) {
3539
3589
  }
3540
3590
  return {
3541
3591
  methods: returnedClass.getMethods(),
3542
- factoryName: callee.getText(),
3592
+ // The DECLARATION's name, not the call-site text: consumers look the factory
3593
+ // back up by name inside `factoryFilePath`, which a qualified
3594
+ // `tables.createTableController` would never match. For a bare identifier
3595
+ // the two coincide (bar an import alias, where the declaration name is the
3596
+ // one that resolves anyway).
3597
+ factoryName: factoryDecl.getName() ?? calleeText,
3543
3598
  factoryFilePath: factoryDecl.getSourceFile().getFilePath(),
3544
3599
  classArgs,
3545
3600
  namedClassArgs
@@ -5083,7 +5138,7 @@ async function watch(config, onChange, options = {}) {
5083
5138
  }
5084
5139
 
5085
5140
  // src/index.ts
5086
- var VERSION = "0.18.0";
5141
+ var VERSION = "0.19.0";
5087
5142
 
5088
5143
  // src/cli/codegen.ts
5089
5144
  async function runCodegen(opts = {}) {