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
|
-
|
|
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
|
|
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 (
|
|
373
|
+
## Rules (50)
|
|
374
374
|
|
|
375
|
-
### Security (
|
|
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 (
|
|
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` |
|
|
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` |
|
|
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
|
|
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
|
|
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 |
|
package/dist/api/index.d.mts
CHANGED
|
@@ -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.
|