nestjs-doctor 0.3.2 → 0.4.1

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/README.md CHANGED
@@ -13,7 +13,7 @@
13
13
  </p>
14
14
 
15
15
  <p align="center">
16
- 45 built-in rules across <b>security</b>, <b>performance</b>, <b>correctness</b>, and <b>architecture</b>. Outputs a <b>0-100 score</b> with actionable diagnostics. Zero config. Monorepo support. Built to catch the anti-patterns that AI-generated code loves to introduce.
16
+ 42 built-in rules across <b>security</b>, <b>performance</b>, <b>correctness</b>, and <b>architecture</b>. Outputs a <b>0-100 score</b> with actionable diagnostics. Zero config. Monorepo support. Built to catch the anti-patterns that AI-generated code loves to introduce.
17
17
  </p>
18
18
 
19
19
  ---
@@ -153,8 +153,6 @@ Optional. Create `nestjs-doctor.config.json` in your project root:
153
153
  "performance": false
154
154
  },
155
155
  "thresholds": {
156
- "godModuleProviders": 15,
157
- "godModuleImports": 20,
158
156
  "godServiceMethods": 12,
159
157
  "godServiceDeps": 10
160
158
  }
@@ -166,13 +164,13 @@ Or use a `"nestjs-doctor"` key in `package.json`.
166
164
  | Key | Type | Description |
167
165
  |-----|------|-------------|
168
166
  | `include` | `string[]` | Glob patterns to scan (default: `["**/*.ts"]`) |
169
- | `exclude` | `string[]` | Glob patterns to skip (default includes `node_modules`, `dist`, `build`, `coverage`, `*.spec.ts`, `*.test.ts`, `*.e2e-spec.ts`, `*.e2e-test.ts`, `*.d.ts`, `test/`, `tests/`, `__tests__/`, `__mocks__/`, `__fixtures__/`) |
167
+ | `exclude` | `string[]` | Glob patterns to skip (default includes `node_modules`, `dist`, `build`, `coverage`, `*.spec.ts`, `*.test.ts`, `*.e2e-spec.ts`, `*.e2e-test.ts`, `*.d.ts`, `test/`, `tests/`, `__tests__/`, `__mocks__/`, `__fixtures__/`, `mock/`, `mocks/`, `*.mock.ts`, `seeder/`, `seeders/`, `*.seed.ts`, `*.seeder.ts`) |
170
168
  | `minScore` | `number` | Minimum passing score (0-100). Exits with code 1 if below threshold |
171
169
  | `ignore.rules` | `string[]` | Rule IDs to suppress |
172
170
  | `ignore.files` | `string[]` | Glob patterns for files whose diagnostics are hidden |
173
171
  | `rules` | `Record<string, boolean>` | Enable/disable individual rules |
174
172
  | `categories` | `Record<string, boolean>` | Enable/disable entire categories |
175
- | `thresholds` | `object` | Customize limits for god-module / god-service rules |
173
+ | `thresholds` | `object` | Customize limits for god-service rules |
176
174
 
177
175
  ---
178
176
 
@@ -234,7 +232,7 @@ mono.combined; // Merged DiagnoseResult
234
232
 
235
233
  ---
236
234
 
237
- ## Rules (45)
235
+ ## Rules (42)
238
236
 
239
237
  ### Security (9)
240
238
 
@@ -250,7 +248,7 @@ mono.combined; // Merged DiagnoseResult
250
248
  | `no-exposed-stack-trace` | warning | `error.stack` exposed in responses |
251
249
  | `require-auth-guard` | info | Controller without `@UseGuards()` |
252
250
 
253
- ### Correctness (13)
251
+ ### Correctness (14)
254
252
 
255
253
  | Rule | Severity | What it catches |
256
254
  |------|----------|-----------------|
@@ -264,11 +262,12 @@ mono.combined; // Merged DiagnoseResult
264
262
  | `prefer-readonly-injection` | warning | Constructor DI params missing `readonly` |
265
263
  | `require-lifecycle-interface` | warning | Lifecycle method without corresponding interface |
266
264
  | `no-empty-handlers` | warning | HTTP handler with empty body |
267
- | `no-async-without-await` | warning | Async function/method with no `await` |
265
+ | `no-async-without-await` | warning | Async function/method with no `await` or redundant `async` on `new Promise()` return |
266
+ | `prefer-await-in-handlers` | warning | Async HTTP handler missing await — risks broken exception filters and lost stack traces |
268
267
  | `no-duplicate-module-metadata` | warning | Duplicate entries in `@Module()` arrays |
269
268
  | `no-missing-module-decorator` | warning | Class named `*Module` without `@Module()` |
270
269
 
271
- ### Architecture (13)
270
+ ### Architecture (12)
272
271
 
273
272
  | Rule | Severity | What it catches |
274
273
  |------|----------|-----------------|
@@ -278,7 +277,6 @@ mono.combined; // Merged DiagnoseResult
278
277
  | `no-circular-module-deps` | error | Cycles in `@Module()` import graph |
279
278
  | `no-manual-instantiation` | error | `new SomeService()` for injectable classes |
280
279
  | `no-orm-in-services` | warning | Services using ORM directly (should use repositories) |
281
- | `no-god-module` | warning | >10 providers or >15 imports |
282
280
  | `no-god-service` | warning | >10 public methods or >8 dependencies |
283
281
  | `require-feature-modules` | warning | AppModule declaring too many providers directly |
284
282
  | `prefer-constructor-injection` | warning | `@Inject()` property injection |
@@ -286,18 +284,15 @@ mono.combined; // Merged DiagnoseResult
286
284
  | `prefer-interface-injection` | info | Concrete service-to-service injection |
287
285
  | `no-barrel-export-internals` | info | Re-exporting repositories from barrel files |
288
286
 
289
- ### Performance (10)
287
+ ### Performance (7)
290
288
 
291
289
  | Rule | Severity | What it catches |
292
290
  |------|----------|-----------------|
293
291
  | `no-sync-io` | warning | `readFileSync`, `writeFileSync`, etc. |
294
- | `no-query-in-loop` | warning | `await` inside loops (N+1 pattern) |
295
292
  | `no-blocking-constructor` | warning | Loops/await in Injectable/Controller constructors |
296
293
  | `no-dynamic-require` | warning | `require()` with non-literal argument |
297
294
  | `no-unused-providers` | warning | Provider never injected anywhere |
298
- | `no-logging-in-loops` | info | `console.*` / `this.logger.*` in loops |
299
295
  | `no-unnecessary-async` | info | Async method with no `await` |
300
- | `prefer-pagination` | info | `findMany()` / `find()` without pagination args |
301
296
  | `no-unused-module-exports` | info | Module exports unused by importers |
302
297
  | `no-orphan-modules` | info | Module never imported by any other module |
303
298
 
@@ -220,7 +220,8 @@ function resolveProviders(project, files) {
220
220
  if (!name) continue;
221
221
  const ctor = cls.getConstructors()[0];
222
222
  const dependencies = ctor ? ctor.getParameters().map((p) => {
223
- return extractSimpleTypeName(p.getType().getText());
223
+ const typeNode = p.getTypeNode();
224
+ return extractSimpleTypeName(typeNode ? typeNode.getText() : p.getType().getText());
224
225
  }) : [];
225
226
  const publicMethodCount = cls.getMethods().filter((m) => {
226
227
  const scope = m.getScope();
@@ -309,7 +310,7 @@ function isModule(cls) {
309
310
 
310
311
  //#endregion
311
312
  //#region src/rules/architecture/no-business-logic-in-controllers.ts
312
- const HTTP_DECORATORS$1 = new Set([
313
+ const HTTP_DECORATORS$3 = new Set([
313
314
  "Get",
314
315
  "Post",
315
316
  "Put",
@@ -331,7 +332,7 @@ const noBusinessLogicInControllers = {
331
332
  for (const cls of context.sourceFile.getClasses()) {
332
333
  if (!isController(cls)) continue;
333
334
  for (const method of cls.getMethods()) {
334
- if (!method.getDecorators().some((d) => HTTP_DECORATORS$1.has(d.getName()))) continue;
335
+ if (!method.getDecorators().some((d) => HTTP_DECORATORS$3.has(d.getName()))) continue;
335
336
  const body = method.getBody();
336
337
  if (!body) continue;
337
338
  const ifStatements = body.getDescendantsOfKind(ts_morph.SyntaxKind.IfStatement);
@@ -395,41 +396,6 @@ const noCircularModuleDeps = {
395
396
  }
396
397
  };
397
398
 
398
- //#endregion
399
- //#region src/rules/architecture/no-god-module.ts
400
- const DEFAULT_MAX_PROVIDERS = 10;
401
- const DEFAULT_MAX_IMPORTS = 15;
402
- const noGodModule = {
403
- meta: {
404
- id: "architecture/no-god-module",
405
- category: "architecture",
406
- severity: "warning",
407
- description: "Modules with too many providers or imports should be split into smaller feature modules",
408
- help: "Split this module into smaller, focused feature modules.",
409
- scope: "project"
410
- },
411
- check(context) {
412
- const maxProviders = context.config.thresholds?.godModuleProviders ?? DEFAULT_MAX_PROVIDERS;
413
- const maxImports = context.config.thresholds?.godModuleImports ?? DEFAULT_MAX_IMPORTS;
414
- for (const mod of context.moduleGraph.modules.values()) {
415
- if (mod.providers.length > maxProviders) context.report({
416
- filePath: mod.filePath,
417
- message: `Module '${mod.name}' has ${mod.providers.length} providers (max: ${maxProviders}). Consider splitting into smaller modules.`,
418
- help: this.meta.help,
419
- line: mod.classDeclaration.getStartLineNumber(),
420
- column: 1
421
- });
422
- if (mod.imports.length > maxImports) context.report({
423
- filePath: mod.filePath,
424
- message: `Module '${mod.name}' has ${mod.imports.length} imports (max: ${maxImports}). Consider grouping into feature modules.`,
425
- help: this.meta.help,
426
- line: mod.classDeclaration.getStartLineNumber(),
427
- column: 1
428
- });
429
- }
430
- }
431
- };
432
-
433
399
  //#endregion
434
400
  //#region src/rules/architecture/no-god-service.ts
435
401
  const DEFAULT_MAX_METHODS = 10;
@@ -812,6 +778,23 @@ const requireModuleBoundaries = {
812
778
 
813
779
  //#endregion
814
780
  //#region src/rules/correctness/no-async-without-await.ts
781
+ const HTTP_DECORATORS$2 = new Set([
782
+ "Get",
783
+ "Post",
784
+ "Put",
785
+ "Delete",
786
+ "Patch",
787
+ "All",
788
+ "Head",
789
+ "Options"
790
+ ]);
791
+ function returnsNewPromise$1(body) {
792
+ return body.getDescendantsOfKind(ts_morph.SyntaxKind.ReturnStatement).some((ret) => {
793
+ const expr = ret.getExpression();
794
+ if (!expr || expr.getKind() !== ts_morph.SyntaxKind.NewExpression) return false;
795
+ return expr.asKindOrThrow(ts_morph.SyntaxKind.NewExpression).getExpression().getText() === "Promise";
796
+ });
797
+ }
815
798
  const noAsyncWithoutAwait = {
816
799
  meta: {
817
800
  id: "correctness/no-async-without-await",
@@ -823,6 +806,9 @@ const noAsyncWithoutAwait = {
823
806
  check(context) {
824
807
  for (const cls of context.sourceFile.getClasses()) for (const method of cls.getMethods()) {
825
808
  if (!method.isAsync()) continue;
809
+ if (isController(cls)) {
810
+ if (method.getDecorators().some((d) => HTTP_DECORATORS$2.has(d.getName()))) continue;
811
+ }
826
812
  const body = method.getBody();
827
813
  if (!body) continue;
828
814
  if (body.getDescendantsOfKind(ts_morph.SyntaxKind.AwaitExpression).filter((expr) => {
@@ -832,13 +818,23 @@ const noAsyncWithoutAwait = {
832
818
  parent = parent.getParent();
833
819
  }
834
820
  return true;
835
- }).length === 0) context.report({
836
- filePath: context.filePath,
837
- message: `Async method '${method.getName()}()' has no await expression.`,
838
- help: this.meta.help,
839
- line: method.getStartLineNumber(),
840
- column: 1
841
- });
821
+ }).length === 0) {
822
+ const name = method.getName();
823
+ if (returnsNewPromise$1(body)) context.report({
824
+ filePath: context.filePath,
825
+ message: `Async method '${name}()' returns a Promise directly — remove the async keyword.`,
826
+ help: "The async keyword is unnecessary when you are already constructing a Promise manually. Remove async to avoid double-wrapping.",
827
+ line: method.getStartLineNumber(),
828
+ column: 1
829
+ });
830
+ else context.report({
831
+ filePath: context.filePath,
832
+ message: `Async method '${name}()' has no await expression.`,
833
+ help: this.meta.help,
834
+ line: method.getStartLineNumber(),
835
+ column: 1
836
+ });
837
+ }
842
838
  }
843
839
  for (const fn of context.sourceFile.getFunctions()) {
844
840
  if (!fn.isAsync()) continue;
@@ -851,13 +847,23 @@ const noAsyncWithoutAwait = {
851
847
  parent = parent.getParent();
852
848
  }
853
849
  return true;
854
- }).length === 0) context.report({
855
- filePath: context.filePath,
856
- message: `Async function '${fn.getName() ?? "anonymous"}()' has no await expression.`,
857
- help: this.meta.help,
858
- line: fn.getStartLineNumber(),
859
- column: 1
860
- });
850
+ }).length === 0) {
851
+ const name = fn.getName() ?? "anonymous";
852
+ if (returnsNewPromise$1(body)) context.report({
853
+ filePath: context.filePath,
854
+ message: `Async function '${name}()' returns a Promise directly — remove the async keyword.`,
855
+ help: "The async keyword is unnecessary when you are already constructing a Promise manually. Remove async to avoid double-wrapping.",
856
+ line: fn.getStartLineNumber(),
857
+ column: 1
858
+ });
859
+ else context.report({
860
+ filePath: context.filePath,
861
+ message: `Async function '${name}()' has no await expression.`,
862
+ help: this.meta.help,
863
+ line: fn.getStartLineNumber(),
864
+ column: 1
865
+ });
866
+ }
861
867
  }
862
868
  }
863
869
  };
@@ -956,7 +962,7 @@ const noDuplicateRoutes = {
956
962
 
957
963
  //#endregion
958
964
  //#region src/rules/correctness/no-empty-handlers.ts
959
- const HTTP_DECORATORS = new Set([
965
+ const HTTP_DECORATORS$1 = new Set([
960
966
  "Get",
961
967
  "Post",
962
968
  "Put",
@@ -978,7 +984,7 @@ const noEmptyHandlers = {
978
984
  for (const cls of context.sourceFile.getClasses()) {
979
985
  if (!isController(cls)) continue;
980
986
  for (const method of cls.getMethods()) {
981
- if (!method.getDecorators().some((d) => HTTP_DECORATORS.has(d.getName()))) continue;
987
+ if (!method.getDecorators().some((d) => HTTP_DECORATORS$1.has(d.getName()))) continue;
982
988
  const body = method.getBody();
983
989
  if (!body) continue;
984
990
  if (body.getStatements().length === 0) context.report({
@@ -1169,6 +1175,69 @@ const noMissingPipeMethod = {
1169
1175
  }
1170
1176
  };
1171
1177
 
1178
+ //#endregion
1179
+ //#region src/rules/correctness/prefer-await-in-handlers.ts
1180
+ const HTTP_DECORATORS = new Set([
1181
+ "Get",
1182
+ "Post",
1183
+ "Put",
1184
+ "Delete",
1185
+ "Patch",
1186
+ "All",
1187
+ "Head",
1188
+ "Options"
1189
+ ]);
1190
+ function returnsNewPromise(body) {
1191
+ return body.getDescendantsOfKind(ts_morph.SyntaxKind.ReturnStatement).some((ret) => {
1192
+ const expr = ret.getExpression();
1193
+ if (!expr || expr.getKind() !== ts_morph.SyntaxKind.NewExpression) return false;
1194
+ return expr.asKindOrThrow(ts_morph.SyntaxKind.NewExpression).getExpression().getText() === "Promise";
1195
+ });
1196
+ }
1197
+ const preferAwaitInHandlers = {
1198
+ meta: {
1199
+ id: "correctness/prefer-await-in-handlers",
1200
+ category: "correctness",
1201
+ severity: "warning",
1202
+ description: "Async HTTP handler missing await — risks broken exception filters and lost stack traces",
1203
+ help: "Add await when calling services. This ensures exception filters trigger correctly, stack traces point to the handler, and error handling is consistent across handlers."
1204
+ },
1205
+ check(context) {
1206
+ for (const cls of context.sourceFile.getClasses()) {
1207
+ if (!isController(cls)) continue;
1208
+ for (const method of cls.getMethods()) {
1209
+ if (!(method.getDecorators().some((d) => HTTP_DECORATORS.has(d.getName())) && method.isAsync())) continue;
1210
+ const body = method.getBody();
1211
+ if (!body) continue;
1212
+ if (body.getDescendantsOfKind(ts_morph.SyntaxKind.AwaitExpression).filter((expr) => {
1213
+ let parent = expr.getParent();
1214
+ while (parent && parent !== body) {
1215
+ if (parent.getKind() === ts_morph.SyntaxKind.ArrowFunction || parent.getKind() === ts_morph.SyntaxKind.FunctionExpression || parent.getKind() === ts_morph.SyntaxKind.FunctionDeclaration) return false;
1216
+ parent = parent.getParent();
1217
+ }
1218
+ return true;
1219
+ }).length === 0) {
1220
+ const name = method.getName();
1221
+ if (returnsNewPromise(body)) context.report({
1222
+ filePath: context.filePath,
1223
+ message: `Handler '${name}()' returns a Promise directly — remove async and return the promise, or await the resolved value.`,
1224
+ help: this.meta.help,
1225
+ line: method.getStartLineNumber(),
1226
+ column: 1
1227
+ });
1228
+ else context.report({
1229
+ filePath: context.filePath,
1230
+ message: `Async handler '${name}()' does not await any expression.`,
1231
+ help: this.meta.help,
1232
+ line: method.getStartLineNumber(),
1233
+ column: 1
1234
+ });
1235
+ }
1236
+ }
1237
+ }
1238
+ }
1239
+ };
1240
+
1172
1241
  //#endregion
1173
1242
  //#region src/rules/correctness/prefer-readonly-injection.ts
1174
1243
  const preferReadonlyInjection = {
@@ -1334,56 +1403,6 @@ const noDynamicRequire = {
1334
1403
  }
1335
1404
  };
1336
1405
 
1337
- //#endregion
1338
- //#region src/rules/performance/no-logging-in-loops.ts
1339
- const LOOP_KINDS$1 = new Set([
1340
- ts_morph.SyntaxKind.ForStatement,
1341
- ts_morph.SyntaxKind.ForOfStatement,
1342
- ts_morph.SyntaxKind.ForInStatement,
1343
- ts_morph.SyntaxKind.WhileStatement,
1344
- ts_morph.SyntaxKind.DoStatement
1345
- ]);
1346
- const CONSOLE_METHODS = new Set([
1347
- "log",
1348
- "warn",
1349
- "error",
1350
- "info",
1351
- "debug",
1352
- "trace"
1353
- ]);
1354
- const noLoggingInLoops = {
1355
- meta: {
1356
- id: "performance/no-logging-in-loops",
1357
- category: "performance",
1358
- severity: "info",
1359
- description: "Logging inside loops can degrade performance and flood log output",
1360
- help: "Move logging outside the loop, aggregate results, or use conditional logging."
1361
- },
1362
- check(context) {
1363
- const callExpressions = context.sourceFile.getDescendantsOfKind(ts_morph.SyntaxKind.CallExpression);
1364
- for (const call of callExpressions) {
1365
- const exprText = call.getExpression().getText();
1366
- const isConsoleCall = exprText.startsWith("console.") && CONSOLE_METHODS.has(exprText.split(".")[1]);
1367
- const isLoggerCall = exprText.startsWith("this.logger.") && CONSOLE_METHODS.has(exprText.split(".")[2]);
1368
- if (!(isConsoleCall || isLoggerCall)) continue;
1369
- let parent = call.getParent();
1370
- while (parent) {
1371
- if (LOOP_KINDS$1.has(parent.getKind())) {
1372
- context.report({
1373
- filePath: context.filePath,
1374
- message: `Logging call '${exprText}()' inside a loop.`,
1375
- help: this.meta.help,
1376
- line: call.getStartLineNumber(),
1377
- column: 1
1378
- });
1379
- break;
1380
- }
1381
- parent = parent.getParent();
1382
- }
1383
- }
1384
- }
1385
- };
1386
-
1387
1406
  //#endregion
1388
1407
  //#region src/rules/performance/no-orphan-modules.ts
1389
1408
  const noOrphanModules = {
@@ -1411,44 +1430,6 @@ const noOrphanModules = {
1411
1430
  }
1412
1431
  };
1413
1432
 
1414
- //#endregion
1415
- //#region src/rules/performance/no-query-in-loop.ts
1416
- const LOOP_KINDS = new Set([
1417
- ts_morph.SyntaxKind.ForStatement,
1418
- ts_morph.SyntaxKind.ForOfStatement,
1419
- ts_morph.SyntaxKind.ForInStatement,
1420
- ts_morph.SyntaxKind.WhileStatement,
1421
- ts_morph.SyntaxKind.DoStatement
1422
- ]);
1423
- const noQueryInLoop = {
1424
- meta: {
1425
- id: "performance/no-query-in-loop",
1426
- category: "performance",
1427
- severity: "warning",
1428
- description: "Await expressions inside loops cause N+1 query patterns — batch or parallelize instead",
1429
- help: "Use Promise.all(), batch queries, or database-level operations (e.g., WHERE IN) instead of awaiting in a loop."
1430
- },
1431
- check(context) {
1432
- const awaitExpressions = context.sourceFile.getDescendantsOfKind(ts_morph.SyntaxKind.AwaitExpression);
1433
- for (const awaitExpr of awaitExpressions) {
1434
- let parent = awaitExpr.getParent();
1435
- while (parent) {
1436
- if (LOOP_KINDS.has(parent.getKind())) {
1437
- context.report({
1438
- filePath: context.filePath,
1439
- message: "Await expression inside a loop — potential N+1 query pattern.",
1440
- help: this.meta.help,
1441
- line: awaitExpr.getStartLineNumber(),
1442
- column: 1
1443
- });
1444
- break;
1445
- }
1446
- parent = parent.getParent();
1447
- }
1448
- }
1449
- }
1450
- };
1451
-
1452
1433
  //#endregion
1453
1434
  //#region src/rules/performance/no-sync-io.ts
1454
1435
  const SYNC_IO_METHODS = new Set([
@@ -1550,6 +1531,7 @@ const noUnusedModuleExports = {
1550
1531
  if (!provider) continue;
1551
1532
  for (const dep of provider.dependencies) usedProviders.add(dep);
1552
1533
  }
1534
+ if (importer.exports.includes(mod.name)) for (const exp of mod.exports) usedProviders.add(exp);
1553
1535
  for (const controllerName of importer.controllers) for (const filePath of context.files) {
1554
1536
  const sourceFile = context.project.getSourceFile(filePath);
1555
1537
  if (!sourceFile) continue;
@@ -1558,20 +1540,24 @@ const noUnusedModuleExports = {
1558
1540
  const ctor = cls.getConstructors()[0];
1559
1541
  if (!ctor) continue;
1560
1542
  for (const param of ctor.getParameters()) {
1561
- const typeText = param.getType().getText();
1543
+ const typeNode = param.getTypeNode();
1544
+ const typeText = typeNode ? typeNode.getText() : param.getType().getText();
1562
1545
  const simpleName = typeText.split(".").pop()?.split("<")[0] ?? typeText;
1563
1546
  usedProviders.add(simpleName);
1564
1547
  }
1565
1548
  }
1566
1549
  }
1567
1550
  }
1568
- for (const exportedName of mod.exports) if (!usedProviders.has(exportedName)) context.report({
1569
- filePath: mod.filePath,
1570
- message: `Module '${mod.name}' exports '${exportedName}' but no importing module uses it.`,
1571
- help: this.meta.help,
1572
- line: mod.classDeclaration.getStartLineNumber(),
1573
- column: 1
1574
- });
1551
+ for (const exportedName of mod.exports) {
1552
+ if (context.moduleGraph.modules.has(exportedName)) continue;
1553
+ if (!usedProviders.has(exportedName)) context.report({
1554
+ filePath: mod.filePath,
1555
+ message: `Module '${mod.name}' exports '${exportedName}' but no importing module uses it.`,
1556
+ help: this.meta.help,
1557
+ line: mod.classDeclaration.getStartLineNumber(),
1558
+ column: 1
1559
+ });
1560
+ }
1575
1561
  }
1576
1562
  }
1577
1563
  };
@@ -1605,7 +1591,8 @@ const noUnusedProviders = {
1605
1591
  const ctor = cls.getConstructors()[0];
1606
1592
  if (!ctor) continue;
1607
1593
  for (const param of ctor.getParameters()) {
1608
- const typeText = param.getType().getText();
1594
+ const typeNode = param.getTypeNode();
1595
+ const typeText = typeNode ? typeNode.getText() : param.getType().getText();
1609
1596
  const simpleName = typeText.split(".").pop()?.split("<")[0] ?? typeText;
1610
1597
  allDependencies.add(simpleName);
1611
1598
  }
@@ -1632,57 +1619,6 @@ const noUnusedProviders = {
1632
1619
  }
1633
1620
  };
1634
1621
 
1635
- //#endregion
1636
- //#region src/rules/performance/prefer-pagination.ts
1637
- const FIND_MANY_METHODS = new Set(["findMany", "find"]);
1638
- const PAGINATION_ARGS = new Set([
1639
- "take",
1640
- "skip",
1641
- "limit",
1642
- "offset",
1643
- "page",
1644
- "perPage",
1645
- "pageSize"
1646
- ]);
1647
- const preferPagination = {
1648
- meta: {
1649
- id: "performance/prefer-pagination",
1650
- category: "performance",
1651
- severity: "info",
1652
- description: "Database findMany/find queries should include pagination to avoid loading excessive data",
1653
- help: "Add pagination arguments (take/skip, limit/offset) to the query."
1654
- },
1655
- check(context) {
1656
- const callExpressions = context.sourceFile.getDescendantsOfKind(ts_morph.SyntaxKind.CallExpression);
1657
- for (const call of callExpressions) {
1658
- const methodName = call.getExpression().getText().split(".").pop() ?? "";
1659
- if (!FIND_MANY_METHODS.has(methodName)) continue;
1660
- const args = call.getArguments();
1661
- if (args.length === 0) {
1662
- context.report({
1663
- filePath: context.filePath,
1664
- message: `'${methodName}()' called without pagination arguments.`,
1665
- help: this.meta.help,
1666
- line: call.getStartLineNumber(),
1667
- column: 1
1668
- });
1669
- continue;
1670
- }
1671
- const firstArg = args[0];
1672
- if (firstArg.getKind() !== ts_morph.SyntaxKind.ObjectLiteralExpression) continue;
1673
- const obj = firstArg.asKind(ts_morph.SyntaxKind.ObjectLiteralExpression);
1674
- if (!obj) continue;
1675
- if (!obj.getProperties().map((p) => p.getText().split(":")[0].trim()).some((p) => PAGINATION_ARGS.has(p))) context.report({
1676
- filePath: context.filePath,
1677
- message: `'${methodName}()' called without pagination arguments.`,
1678
- help: this.meta.help,
1679
- line: call.getStartLineNumber(),
1680
- column: 1
1681
- });
1682
- }
1683
- }
1684
- };
1685
-
1686
1622
  //#endregion
1687
1623
  //#region src/rules/security/no-csrf-disabled.ts
1688
1624
  const noCsrfDisabled = {
@@ -2075,7 +2011,6 @@ const allRules = [
2075
2011
  requireModuleBoundaries,
2076
2012
  noBarrelExportInternals,
2077
2013
  noCircularModuleDeps,
2078
- noGodModule,
2079
2014
  noGodService,
2080
2015
  requireFeatureModules,
2081
2016
  preferReadonlyInjection,
@@ -2087,6 +2022,7 @@ const allRules = [
2087
2022
  noMissingFilterCatch,
2088
2023
  noMissingInterceptorMethod,
2089
2024
  noAsyncWithoutAwait,
2025
+ preferAwaitInHandlers,
2090
2026
  noDuplicateModuleMetadata,
2091
2027
  noMissingModuleDecorator,
2092
2028
  requireInjectDecorator,
@@ -2101,11 +2037,8 @@ const allRules = [
2101
2037
  noExposedStackTrace,
2102
2038
  noDangerousRedirects,
2103
2039
  noSyncIo,
2104
- noQueryInLoop,
2105
- noLoggingInLoops,
2106
2040
  noUnnecessaryAsync,
2107
2041
  noBlockingConstructor,
2108
- preferPagination,
2109
2042
  noDynamicRequire,
2110
2043
  noUnusedProviders,
2111
2044
  noUnusedModuleExports,
@@ -2198,7 +2131,14 @@ const DEFAULT_CONFIG = {
2198
2131
  "**/tests/**",
2199
2132
  "**/__tests__/**",
2200
2133
  "**/__mocks__/**",
2201
- "**/__fixtures__/**"
2134
+ "**/__fixtures__/**",
2135
+ "**/mock/**",
2136
+ "**/mocks/**",
2137
+ "**/*.mock.ts",
2138
+ "**/seeder/**",
2139
+ "**/seeders/**",
2140
+ "**/*.seed.ts",
2141
+ "**/*.seeder.ts"
2202
2142
  ]
2203
2143
  };
2204
2144
 
@@ -56,8 +56,6 @@ interface NestjsDoctorConfig {
56
56
  minScore?: number;
57
57
  rules?: Record<string, RuleOverride | boolean>;
58
58
  thresholds?: {
59
- godModuleProviders?: number;
60
- godModuleImports?: number;
61
59
  godServiceMethods?: number;
62
60
  godServiceDeps?: number;
63
61
  };
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.cts","names":[],"sources":["../../src/engine/module-graph.ts","../../src/engine/type-resolver.ts","../../src/types/diagnostic.ts","../../src/types/config.ts","../../src/rules/types.ts","../../src/rules/index.ts","../../src/types/errors.ts","../../src/types/result.ts","../../src/api/index.ts"],"mappings":"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"}
1
+ {"version":3,"file":"index.d.cts","names":[],"sources":["../../src/engine/module-graph.ts","../../src/engine/type-resolver.ts","../../src/types/diagnostic.ts","../../src/types/config.ts","../../src/rules/types.ts","../../src/rules/index.ts","../../src/types/errors.ts","../../src/types/result.ts","../../src/api/index.ts"],"mappings":"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"}
@@ -56,8 +56,6 @@ interface NestjsDoctorConfig {
56
56
  minScore?: number;
57
57
  rules?: Record<string, RuleOverride | boolean>;
58
58
  thresholds?: {
59
- godModuleProviders?: number;
60
- godModuleImports?: number;
61
59
  godServiceMethods?: number;
62
60
  godServiceDeps?: number;
63
61
  };
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/engine/module-graph.ts","../../src/engine/type-resolver.ts","../../src/types/diagnostic.ts","../../src/types/config.ts","../../src/rules/types.ts","../../src/rules/index.ts","../../src/types/errors.ts","../../src/types/result.ts","../../src/api/index.ts"],"mappings":"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"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/engine/module-graph.ts","../../src/engine/type-resolver.ts","../../src/types/diagnostic.ts","../../src/types/config.ts","../../src/rules/types.ts","../../src/rules/index.ts","../../src/types/errors.ts","../../src/types/result.ts","../../src/api/index.ts"],"mappings":"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"}