nestjs-doctor 0.4.28 → 0.4.30

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
@@ -19,7 +19,7 @@
19
19
  </p>
20
20
 
21
21
  <p align="center">
22
- 43 built-in rules across <b>security</b>, <b>performance</b>, <b>correctness</b>, <b>architecture</b>, and <b>schema</b>. Outputs a <b>0-100 score</b> with actionable diagnostics. Zero config. Monorepo support. Catches the anti-patterns that AI-generated code introduce (slop code).
22
+ 50 built-in rules across <b>security</b>, <b>performance</b>, <b>correctness</b>, <b>architecture</b>, and <b>schema</b>. Outputs a <b>0-100 score</b> with actionable diagnostics. Zero config. Monorepo support. Catches the anti-patterns that AI-generated code introduce (slop code).
23
23
  </p>
24
24
 
25
25
  ---
@@ -60,7 +60,7 @@ Install [NestJS Doctor](https://marketplace.visualstudio.com/items?itemName=rolo
60
60
  npm install -D nestjs-doctor
61
61
  ```
62
62
 
63
- Same 43 rules as the CLI, surfaced as inline diagnostics in the editor and in the Problems panel. Files are scanned on open and on save with a configurable debounce.
63
+ Same 50 rules as the CLI, surfaced as inline diagnostics in the editor and in the Problems panel. Files are scanned on open and on save with a configurable debounce.
64
64
 
65
65
  Use `NestJS Doctor: Scan Project` from the command palette to trigger a full scan manually.
66
66
 
@@ -370,9 +370,9 @@ mono.combined; // Merged DiagnoseResult
370
370
 
371
371
  ---
372
372
 
373
- ## Rules (43)
373
+ ## Rules (50)
374
374
 
375
- ### Security (9)
375
+ ### Security (10)
376
376
 
377
377
  | Rule | Severity | What it catches |
378
378
  |------|----------|-----------------|
@@ -385,8 +385,9 @@ mono.combined; // Merged DiagnoseResult
385
385
  | `no-exposed-env-vars` | warning | Direct `process.env` in Injectable/Controller |
386
386
  | `no-exposed-stack-trace` | warning | `error.stack` exposed in responses |
387
387
  | `no-raw-entity-in-response` | warning | Returning ORM entities directly from controllers -- leaks internal fields |
388
+ | `require-guards-on-endpoints` | warning | Endpoints without `@UseGuards()` at class or method level |
388
389
 
389
- ### Correctness (14)
390
+ ### Correctness (20)
390
391
 
391
392
  | Rule | Severity | What it catches |
392
393
  |------|----------|-----------------|
@@ -399,11 +400,17 @@ mono.combined; // Merged DiagnoseResult
399
400
  | `require-inject-decorator` | error | Untyped constructor param without `@Inject()` |
400
401
  | `prefer-readonly-injection` | warning | Constructor DI params missing `readonly` |
401
402
  | `require-lifecycle-interface` | warning | Lifecycle method without corresponding interface |
402
- | `no-empty-handlers` | warning | HTTP handler with empty body |
403
+ | `no-empty-handlers` | info | HTTP handler with empty body |
403
404
  | `no-async-without-await` | warning | Async function/method with no `await` |
404
405
  | `no-duplicate-module-metadata` | warning | Duplicate entries in `@Module()` arrays |
405
406
  | `no-missing-module-decorator` | warning | Class named `*Module` without `@Module()` |
406
407
  | `no-fire-and-forget-async` | warning | Async call without `await` in non-handler methods |
408
+ | `param-decorator-matches-route` | error | `@Param()` name doesn't match any `:param` in the route path |
409
+ | `factory-inject-matches-params` | error | `useFactory` inject array length mismatches factory parameter count |
410
+ | `no-duplicate-decorators` | warning | Same decorator appears twice on a single target |
411
+ | `validated-non-primitive-needs-type` | warning | Non-primitive DTO property with class-validator decorators missing `@Type()` |
412
+ | `validate-nested-array-each` | warning | `@ValidateNested()` on array property missing `{ each: true }` |
413
+ | `injectable-must-be-provided` | info | `@Injectable()` class not registered in any module's providers |
407
414
 
408
415
  ### Architecture (10)
409
416
 
@@ -414,7 +421,7 @@ mono.combined; // Merged DiagnoseResult
414
421
  | `no-orm-in-controllers` | error | PrismaService / EntityManager / DataSource in controllers |
415
422
  | `no-circular-module-deps` | error | Cycles in `@Module()` import graph |
416
423
  | `no-manual-instantiation` | error | `new SomeService()` for injectable classes |
417
- | `no-orm-in-services` | warning | Services using ORM directly (should use repositories) |
424
+ | `no-orm-in-services` | info | Services using ORM directly (should use repositories) |
418
425
  | `no-service-locator` | warning | `ModuleRef.get()`/`resolve()` hides dependencies |
419
426
  | `prefer-constructor-injection` | warning | `@Inject()` property injection |
420
427
  | `require-module-boundaries` | info | Deep imports into other modules' internals |
@@ -425,9 +432,9 @@ mono.combined; // Merged DiagnoseResult
425
432
  | Rule | Severity | What it catches |
426
433
  |------|----------|-----------------|
427
434
  | `no-sync-io` | warning | `readFileSync`, `writeFileSync`, etc. |
428
- | `no-blocking-constructor` | warning | Loops/await in Injectable/Controller constructors |
435
+ | `no-blocking-constructor` | warning | Loops in Injectable/Controller constructors |
429
436
  | `no-dynamic-require` | warning | `require()` with non-literal argument |
430
- | `no-unused-providers` | warning | Provider never injected anywhere |
437
+ | `no-unused-providers` | warning | Provider never injected and no self-activating decorators |
431
438
  | `no-request-scope-abuse` | warning | `Scope.REQUEST` creates new instance per request |
432
439
  | `no-unused-module-exports` | info | Module exports unused by importers |
433
440
  | `no-orphan-modules` | info | Module never imported by any other module |
@@ -187,22 +187,92 @@ declare function isSchemaDiagnostic(d: Diagnostic): d is SchemaDiagnostic;
187
187
  /**
188
188
  * Classifies a dependency's role in the NestJS application.
189
189
  */
190
- type DependencyType = "service" | "repository" | "guard" | "interceptor" | "pipe" | "filter" | "gateway" | "unknown";
190
+ type DependencyType = "service" | "repository" | "guard" | "interceptor" | "pipe" | "filter" | "gateway" | "step" | "throw" | "unknown";
191
+ interface StepStatement {
192
+ assignedTo: string | null;
193
+ text: string;
194
+ }
195
+ /**
196
+ * Merged guard-throw info attached to a call node when the return value
197
+ * is immediately null-checked and throws an exception.
198
+ */
199
+ interface GuardThrow {
200
+ branchKind: string | null;
201
+ callSiteLine: number;
202
+ className: string;
203
+ conditionText: string | null;
204
+ message: string | null;
205
+ }
191
206
  /**
192
207
  * A per-method dependency node. Each method call becomes its own node
193
208
  * so that call order and conditionality are visible in the graph.
194
209
  */
195
210
  interface MethodDependencyNode {
211
+ /** Variable name the return value is assigned to (e.g. "existing" from `const existing = ...`) */
212
+ assignedTo: string | null;
213
+ /** Shared ID for mutually exclusive branches from the same conditional (e.g., "L358") */
214
+ branchGroupId: string | null;
215
+ /** Branch type: "if" | "else-if" | "else" | "case" | "default" | "catch" | "ternary-true" | "ternary-false" */
216
+ branchKind: string | null;
217
+ /** Line where the call to this dependency is made in the parent method */
218
+ callSiteLine: number;
196
219
  className: string;
220
+ /** Leading comment above the call site (e.g. "Verify pool belongs to organization") */
221
+ comment: string | null;
197
222
  conditional: boolean;
223
+ /** Condition expression text (e.g., "!owner"), null if unconditional */
224
+ conditionText: string | null;
198
225
  dependencies: MethodDependencyNode[];
226
+ /** Last line of the method declaration (for full-function highlighting) */
227
+ endLine: number;
199
228
  filePath: string;
229
+ /** Merged guard-throw for call nodes (fetch + null-check + throw pattern) */
230
+ guardThrow: GuardThrow | null;
231
+ /** Iteration context: "loop" | "callback" | "concurrent" | null */
232
+ iterationKind: "loop" | "callback" | "concurrent" | null;
233
+ /** Short label for the construct: "map" | "forEach" | "for-of" | "all" | etc. */
234
+ iterationLabel: string | null;
200
235
  line: number;
201
236
  methodName: string | null;
202
237
  order: number;
238
+ /** Method parameter names and types (from TS signature) */
239
+ parameters: MethodParameterInfo[];
240
+ /** Return type from TS method signature (unwrapped from Promise/Observable) */
241
+ returnType: string | null;
242
+ /** Inline logic statements for step nodes (only populated when type === "step") */
243
+ stepStatements: StepStatement[];
244
+ /** Exception message for standalone throw nodes */
245
+ throwMessage: string | null;
203
246
  totalMethods: number;
204
247
  type: DependencyType;
205
248
  }
249
+ interface MethodParameterInfo {
250
+ name: string;
251
+ type: string | null;
252
+ }
253
+ interface ApiBodyInfo {
254
+ description: string | null;
255
+ type: string | null;
256
+ }
257
+ interface ApiParamInfo {
258
+ description: string | null;
259
+ name: string;
260
+ required: boolean;
261
+ type: string | null;
262
+ }
263
+ interface ApiResponseInfo {
264
+ description: string | null;
265
+ status: number;
266
+ type: string | null;
267
+ }
268
+ interface SwaggerMetadata {
269
+ body: ApiBodyInfo | null;
270
+ description: string | null;
271
+ params: ApiParamInfo[];
272
+ queryParams: ApiParamInfo[];
273
+ responses: ApiResponseInfo[];
274
+ summary: string | null;
275
+ }
206
276
  /**
207
277
  * Represents a single HTTP endpoint in a NestJS controller.
208
278
  * Contains a per-method dependency tree.
@@ -210,11 +280,17 @@ interface MethodDependencyNode {
210
280
  interface EndpointNode {
211
281
  controllerClass: string;
212
282
  dependencies: MethodDependencyNode[];
283
+ /** Last line of the handler method (for full-function highlighting) */
284
+ endLine: number;
213
285
  filePath: string;
214
286
  handlerMethod: string;
215
287
  httpMethod: string;
216
288
  line: number;
289
+ /** Return type from TS method signature (unwrapped from Promise/Observable) */
290
+ returnType: string | null;
217
291
  routePath: string;
292
+ /** Swagger/OpenAPI metadata, null when no swagger decorators present */
293
+ swagger: SwaggerMetadata | null;
218
294
  }
219
295
  /**
220
296
  * Layer 2: method-level call trace node for deep dependency analysis.