nestjs-doctor 0.4.28 → 0.4.29
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.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import{existsSync as e,readFileSync as t,readdirSync as n,statSync as r}from"node:fs";import{dirname as i,join as a,relative as o,resolve as s}from"node:path";import{readFile as c}from"node:fs/promises";import{glob as l}from"tinyglobby";import{performance as u}from"node:perf_hooks";import{Project as d,SyntaxKind as f,ts as p}from"ts-morph";import{createJiti as m}from"jiti";import h from"picomatch";var g=class extends Error{constructor(e){super(e),this.name=`NestjsDoctorError`}},ee=class extends g{constructor(e){super(e),this.name=`ConfigurationError`}},te=class extends g{constructor(e){super(e),this.name=`ScanError`}},_=class extends g{constructor(e){super(e),this.name=`ValidationError`}};const ne=/^packages\s*:/,re=/^packages\s*:\s*\[(.+)\]/,ie=/^\S/,ae=/^-\s+['"]?([^'"]+)['"]?\s*$/,oe=/^['"]|['"]$/g;function se(e){let t=[],n=e.split(`
|
|
2
|
-
`),r=!1;for(let e of n){let n=e.trim();if(ne.test(n)){let e=n.match(re);if(e){for(let n of e[1].split(`,`)){let e=n.trim().replace(oe,``);e&&t.push(e)}return t}r=!0;continue}if(r){if(ie.test(e)&&n!==``)break;let r=n.match(ae);r&&t.push(r[1])}}return t}async function ce(e){let t=a(e,`nest-cli.json`);try{let e=await c(t,`utf-8`),n=JSON.parse(e);if(!(n.monorepo&&n.projects))return null;let r=new Map;for(let[e,t]of Object.entries(n.projects)){let n=t.root??e;r.set(e,n)}return r.size===0?null:{projects:r}}catch{return null}}function le(e){let t={...e.dependencies,...e.devDependencies,...e.peerDependencies};return!!(t[`@nestjs/core`]||t[`@nestjs/common`])}async function v(e,t){let n=await l(t.map(e=>`${e}/package.json`),{cwd:e,absolute:!0,ignore:[`**/node_modules/**`]}),r=new Map;for(let t of n)try{let n=await c(t,`utf-8`),a=JSON.parse(n);if(le(a)){let n=o(e,i(t)),s=a.name??n;r.set(s,n)}}catch{}return r.size===0?null:{projects:r}}async function ue(e){let t=a(e,`pnpm-workspace.yaml`),n;try{n=await c(t,`utf-8`)}catch{return null}let r=se(n);return r.length===0?null:v(e,r)}function de(e){let t=e.workspaces;if(!t)return[];if(Array.isArray(t))return t.filter(e=>typeof e==`string`);if(typeof t==`object`&&t){let e=t;if(Array.isArray(e.packages))return e.packages.filter(e=>typeof e==`string`)}return[]}async function fe(e){let t=a(e,`package.json`),n;try{n=await c(t,`utf-8`)}catch{return null}let r=de(JSON.parse(n));return r.length===0?null:v(e,r)}async function pe(e){let t=a(e,`lerna.json`),n;try{n=await c(t,`utf-8`)}catch{return null}let r=JSON.parse(n);if(r.useWorkspaces)return null;let i=r.packages??[`packages/*`];return i.length===0?null:v(e,i)}async function me(e){let t=a(e,`nx.json`);try{await c(t,`utf-8`)}catch{return null}let n=await l([`**/project.json`],{cwd:e,absolute:!0,ignore:[`node_modules/**`]}),r=new Map;for(let t of n){let n=i(t),s=o(e,n);if(s===``)continue;let l=a(n,`package.json`);try{let e=await c(l,`utf-8`),t=JSON.parse(e);if(le(t)){let e=t.name??s;r.set(e,s)}}catch{}}return r.size===0?null:{projects:r}}async function he(e){try{return await c(a(e,`pnpm-workspace.yaml`),`utf-8`),!0}catch{return!1}}async function ge(e){let t=await ce(e);if(t)return t;let n=await ue(e);if(n)return n;if(!await he(e)){let t=await fe(e);if(t)return t}return await me(e)||pe(e)}async function _e(e){let t=a(e,`package.json`),n={};try{let e=await c(t,`utf-8`);n=JSON.parse(e)}catch{}let r={...n.dependencies,...n.devDependencies},i=ve(r[`@nestjs/core`]),o=ye(r),s=be(r);return{name:n.name??`unknown`,nestVersion:i,orm:o,framework:s,moduleCount:0,fileCount:0}}function ve(e){return e?e.replace(/[\^~>=<]/g,``):null}function ye(e){return e[`@prisma/client`]?`prisma`:e.typeorm?`typeorm`:e[`@mikro-orm/core`]?`mikro-orm`:e.sequelize?`sequelize`:e.mongoose?`mongoose`:e[`drizzle-orm`]?`drizzle`:null}function be(e){return e[`@nestjs/platform-fastify`]?`fastify`:e[`@nestjs/platform-express`]||e[`@nestjs/core`]?`express`:null}const y={include:[`**/*.ts`],exclude:`**/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,*.config.ts,*.config.js,*.config.mjs,*.config.cjs,*.config.mts,*.config.cts`.split(`,`)},xe=[`nestjs-doctor.config.json`,`.nestjs-doctor.json`];async function Se(e,t){if(t)return Ce(t);for(let t of xe)try{return await Ce(a(e,t))}catch{}try{let t=await c(a(e,`package.json`),`utf-8`),n=JSON.parse(t);if(n[`nestjs-doctor`]&&typeof n[`nestjs-doctor`]==`object`)return we(n[`nestjs-doctor`])}catch{}return{...y}}async function Ce(e){let t=await c(e,`utf-8`);return we(JSON.parse(t))}function we(e){return{...y,...e,exclude:[...y.exclude??[],...e.exclude??[]]}}async function Te(e,t){try{return await Se(e)}catch{return t}}const Ee=[/Repository$/,/\.repository$/,/\.entity$/,/\.schema$/,/\.guard$/,/\.interceptor$/,/\.pipe$/,/\.filter$/,/\.strategy$/],De={meta:{id:`architecture/no-barrel-export-internals`,category:`architecture`,severity:`info`,description:`Don't re-export internal implementation details from barrel files`,help:`Only export the module's public API (services, DTOs, interfaces) from index.ts files.`},check(e){if(e.filePath.endsWith(`/index.ts`))for(let t of e.sourceFile.getExportDeclarations()){let n=t.getModuleSpecifierValue();if(n){Ee.some(e=>e.test(n))&&e.report({filePath:e.filePath,message:`Barrel file re-exports internal module '${n}'.`,help:this.meta.help,line:t.getStartLineNumber(),column:1});for(let n of t.getNamedExports()){let t=n.getName();(t.endsWith(`Repository`)||t.endsWith(`Entity`)||t.endsWith(`Schema`))&&e.report({filePath:e.filePath,message:`Barrel file re-exports internal type '${t}'.`,help:this.meta.help,line:n.getStartLineNumber(),column:1})}}}}},b=new Set([`Get`,`Post`,`Put`,`Patch`,`Delete`,`Head`,`Options`,`All`]);function x(e,t){return e.getDecorator(t)!==void 0}function S(e){return x(e,`Controller`)}function Oe(e){return x(e,`Injectable`)}function ke(e){return x(e,`Injectable`)||x(e,`Controller`)||x(e,`Resolver`)||x(e,`WebSocketGateway`)}function Ae(e){return x(e,`Module`)}function C(e){return e.getDecorators().some(e=>b.has(e.getName()))}const je=new Set([`TsRestHandler`,`GrpcMethod`,`GrpcStreamMethod`]);function Me(e){return e.getDecorators().some(e=>je.has(e.getName()))}const Ne={meta:{id:`architecture/no-business-logic-in-controllers`,category:`architecture`,severity:`error`,description:`Controllers should only handle HTTP concerns — move business logic to services`,help:`Extract branches, loops, and complex calculations into a service method.`},check(e){for(let t of e.sourceFile.getClasses())if(S(t))for(let n of t.getMethods()){if(!n.getDecorators().some(e=>b.has(e.getName())))continue;let t=n.getBody();if(!t)continue;let r=t.getDescendantsOfKind(f.IfStatement),i=t.getDescendantsOfKind(f.ForStatement),a=t.getDescendantsOfKind(f.ForInStatement),o=t.getDescendantsOfKind(f.ForOfStatement),s=t.getDescendantsOfKind(f.WhileStatement),c=t.getDescendantsOfKind(f.SwitchStatement),l=i.length+a.length+o.length+s.length;(r.length>1||l>0||c.length>0)&&e.report({filePath:e.filePath,message:`Controller method '${n.getName()}' contains business logic (${r.length} if, ${l} loops, ${c.length} switch). Move to a service.`,help:this.meta.help,line:n.getStartLineNumber(),column:1});let u=t.getDescendantsOfKind(f.CallExpression).filter(e=>{let t=e.getExpression();if(t.getKind()===f.PropertyAccessExpression){let e=t.asKind(f.PropertyAccessExpression)?.getName();return e===`map`||e===`filter`||e===`reduce`||e===`sort`||e===`flatMap`}return!1});u.length>1&&e.report({filePath:e.filePath,message:`Controller method '${n.getName()}' contains data transformation logic (${u.length} array operations). Move to a service.`,help:this.meta.help,line:n.getStartLineNumber(),column:1})}}};function Pe(e){let t=new Map;try{let n=p.findConfigFile(e,p.sys.fileExists,`tsconfig.json`);if(!n)return t;let{config:r,error:a}=p.readConfigFile(n,p.sys.readFile);if(a||!r)return t;let o=i(n),c=p.parseJsonConfigFileContent(r,p.sys,o),l=c.options.paths;if(!l)return t;let u=c.options.baseUrl??o;for(let[e,n]of Object.entries(l)){let r=n.map(e=>s(u,e));t.set(e,r)}}catch{return t}return t}function Fe(e,t){for(let[n,r]of t){if(r.length===0)continue;let t=n.indexOf(`*`);if(t===-1){if(e===n)return r[0];continue}let i=n.slice(0,t),a=n.slice(t+1);if(e.startsWith(i)&&e.endsWith(a)&&e.length>=i.length+a.length){let t=e.slice(i.length,e.length-a.length),n=r[0],o=n.indexOf(`*`);return o===-1?n:n.slice(0,o)+t+n.slice(o+1)}}}const Ie=/=>\s*(\w+)/,Le=/\.js$/;function Re(e,t,n){let r=[];for(let i of e.getClasses()){let e=i.getDecorator(`Module`);if(!e)continue;let a=i.getName()??`AnonymousModule`,o=e.getArguments()[0],s={name:a,filePath:t,classDeclaration:i,imports:[],exports:[],providers:[],controllers:[]};if(o&&o.getKind()===f.ObjectLiteralExpression){let e=o.asKind(f.ObjectLiteralExpression);e&&(s.imports=w(e,`imports`,n),s.exports=w(e,`exports`,n),s.providers=w(e,`providers`,n),s.controllers=w(e,`controllers`,n))}r.push(s)}return r}function ze(e,t,n=new Map){let r=new Map,i=new Map;for(let i of t){let t=e.getSourceFile(i);if(t)for(let e of Re(t,i,n))r.set(e.name,e)}for(let[e,t]of r){let n=new Set;for(let e of t.imports)r.has(e)&&n.add(e);i.set(e,n)}let a=new Map;for(let e of r.values())for(let t of e.providers)a.set(t,e);return{modules:r,edges:i,providerToModule:a}}const Be=new Set([`forRoot`,`forRootAsync`,`forFeature`,`forFeatureAsync`,`forChild`,`forChildAsync`,`register`,`registerAsync`]);function w(e,t,n){let r=e.getProperty(t);if(!r)return[];let i=r.asKind(f.PropertyAssignment);if(!i)return[];let a=i.getInitializer();return a?T(a,e.getSourceFile(),0,n):[]}function T(e,t,n,r){if(n>5)return[];let i=e.getKind();if(i===f.ArrayLiteralExpression){let i=e.asKindOrThrow(f.ArrayLiteralExpression),a=[];for(let e of i.getElements())a.push(...Ve(e,t,n,r));return a}return i===f.CallExpression?He(e.asKindOrThrow(f.CallExpression),t,n,r):i===f.Identifier?O(e.getText(),t,n+1,r):[]}function Ve(e,t,n,r){let i=e.getText();if(i.startsWith(`forwardRef`)){let e=i.match(Ie);return e?[e[1]]:[i]}let a=e.getKind();return a===f.SpreadElement?T(e.asKindOrThrow(f.SpreadElement).getExpression(),t,n,r):a===f.CallExpression?He(e.asKindOrThrow(f.CallExpression),t,n,r):a===f.PropertyAccessExpression?[e.asKindOrThrow(f.PropertyAccessExpression).getExpression().getText()]:(f.Identifier,[i])}function He(e,t,n,r){let i=e.getExpression();if(i.getKind()===f.PropertyAccessExpression){let a=i.asKindOrThrow(f.PropertyAccessExpression),o=a.getName();if(o===`concat`){let i=T(a.getExpression(),t,n,r),o=[];for(let i of e.getArguments())o.push(...T(i,t,n,r));return[...i,...o]}return Be.has(o),[a.getExpression().getText()]}return i.getKind()===f.Identifier?k(i.getText(),t,n+1,r):[]}function E(e,t,n){if(!e.startsWith(`.`)){let r=Fe(e,n);if(!r)return;let i=t.getProject(),a=[`${r}.ts`,`${r}/index.ts`,r,r.replace(Le,`.ts`)];for(let e of a){let t=i.getSourceFile(e);if(t)return t}return}let r=s(i(t.getFilePath()),e),a=t.getProject(),o=[`${r}.ts`,`${r}/index.ts`,r,r.replace(Le,`.ts`)];for(let e of o){let t=a.getSourceFile(e);if(t)return t}}function D(e,t,n){for(let r of t.getImportDeclarations())for(let i of r.getNamedImports())if((i.getAliasNode()?i.getAliasNode().getText():i.getName())===e){let e=E(r.getModuleSpecifierValue(),t,n);return e?{sourceFile:e,localName:i.getName()}:void 0}for(let r of t.getExportDeclarations())if(r.getModuleSpecifierValue()){for(let i of r.getNamedExports())if((i.getAliasNode()?i.getAliasNode().getText():i.getName())===e){let e=E(r.getModuleSpecifierValue(),t,n);return e?{sourceFile:e,localName:i.getName()}:void 0}}}function O(e,t,n,r){if(n>5)return[];for(let i of t.getStatements()){if(i.getKind()!==f.VariableStatement)continue;let a=i.asKindOrThrow(f.VariableStatement);for(let i of a.getDeclarations())if(i.getName()===e){let e=i.getInitializer();if(e)return T(e,t,n,r)}}let i=D(e,t,r);return i?O(i.localName,i.sourceFile,n+1,r):[]}function Ue(e,t,n,r){for(let i of t.getStatements()){if(i.getKind()!==f.VariableStatement)continue;let a=i.asKindOrThrow(f.VariableStatement);for(let i of a.getDeclarations()){if(i.getName()!==e)continue;let a=i.getInitializer();if(!a||a.getKind()!==f.ArrowFunction)continue;let o=a.asKindOrThrow(f.ArrowFunction).getBody();if(o.getKind()!==f.Block)return T(o,t,n,r);let s=[];for(let e of o.getDescendantsOfKind(f.ReturnStatement)){let i=e.getExpression();i&&s.push(...T(i,t,n,r))}return s}}}function k(e,t,n,r){if(n>5)return[];for(let i of t.getStatements()){if(i.getKind()!==f.FunctionDeclaration)continue;let a=i.asKindOrThrow(f.FunctionDeclaration);if(a.getName()!==e)continue;let o=[];for(let e of a.getDescendantsOfKind(f.ReturnStatement)){let i=e.getExpression();i&&o.push(...T(i,t,n,r))}return o}let i=Ue(e,t,n,r);if(i)return i;let a=D(e,t,r);return a?k(a.localName,a.sourceFile,n+1,r):[]}function A(e,t,n,r=new Map){for(let[t,r]of e.modules)if(r.filePath===n){e.modules.delete(t),e.edges.delete(t);for(let t of r.providers)e.providerToModule.get(t)===r&&e.providerToModule.delete(t);for(let n of e.edges.values())n.delete(t)}let i=t.getSourceFile(n);if(!i)return;let a=Re(i,n,r);for(let t of a)e.modules.set(t.name,t);for(let t of a){let n=new Set;for(let r of t.imports)e.modules.has(r)&&n.add(r);e.edges.set(t.name,n);for(let n of t.providers)e.providerToModule.set(n,t)}for(let[t,r]of e.modules){if(r.filePath===n)continue;let i=new Set;for(let t of r.imports)e.modules.has(t)&&i.add(t);e.edges.set(t,i)}}function We(e){let t=[],n=new Set,r=new Set;function i(a,o){n.add(a),r.add(a);let s=e.edges.get(a)??new Set;for(let e of s)if(!n.has(e))i(e,[...o,e]);else if(r.has(e)){let n=o.indexOf(e);n===-1?t.push([...o,e]):t.push(o.slice(n))}r.delete(a)}for(let t of e.modules.keys())n.has(t)||i(t,[t]);return t}function j(e,t,n,r,i,a){let o=[];for(let i of e.providers){let e=n.get(i);if(e)for(let n of e.dependencies){let e=r.get(n);e&&e.name===t.name&&o.push({consumer:i,dependency:n})}}for(let n of e.controllers)for(let e of a){let a=i.getSourceFile(e);if(a)for(let e of a.getClasses()){if(e.getName()!==n)continue;let i=e.getConstructors()[0];if(i)for(let e of i.getParameters()){let i=e.getTypeNode(),a=i?i.getText():e.getType().getText(),s=a.split(`.`).pop()?.split(`<`)[0]??a,c=r.get(s);c&&c.name===t.name&&o.push({consumer:n,dependency:s})}}}return o}const M=`Break the cycle by extracting shared logic into a separate module or using forwardRef().`;function Ge(e,t){let{moduleGraph:n,providers:r,project:i,files:a}=t,o=[],s;for(let t=0;t<e.length;t++){let c=e[t],l=e[(t+1)%e.length],u=n.modules.get(c),d=n.modules.get(l);if(!(u&&d))continue;let f=j(u,d,r,n.providerToModule,i,a);if(f.length===0)continue;let p=new Map;for(let e of f){let t=p.get(e.consumer);t?t.push(e.dependency):p.set(e.consumer,[e.dependency])}let m=[];for(let[e,t]of p){let n=t.map(e=>`${e} (from ${l})`).join(`, `);m.push(`${e} (in ${c}) injects ${n}`)}let h=`${c} -> ${l}: ${m.join(`; `)}`;o.push(h),(!s||f.length<s.count)&&(s={description:`${c} -> ${l}`,count:f.length})}if(o.length===0)return M;let c=o.join(`
|
|
3
|
-
`);if(s){let e=s.count===1?`dependency`:`dependencies`,t=s.description.split(` -> `)[0],o=s.description.split(` -> `)[1],l=n.modules.get(t),u=n.modules.get(o);if(l&&u){let t=j(l,u,r,n.providerToModule,i,a),o=[...new Set(t.map(e=>e.dependency))].join(`, `);c+=`\nConsider extracting ${o} into a shared module — it would break the ${s.description} edge (${s.count} ${e}).`}}return c}const Ke={meta:{id:`architecture/no-circular-module-deps`,category:`architecture`,severity:`error`,description:`Circular dependencies in @Module() import graph`,help:M,scope:`project`},check(e){let t=We(e.moduleGraph);for(let n of t){let t=n.join(` -> `),r=e.moduleGraph.modules.get(n[0]),i=Ge(n,e);e.report({filePath:r?.filePath??`unknown`,message:`Circular module dependency detected: ${t}`,help:i,line:r?.classDeclaration.getStartLineNumber()??1,column:1})}}},qe=[`Service`,`Repository`,`Gateway`,`Resolver`],Je=[`Guard`,`Interceptor`,`Pipe`,`Filter`];function N(e){return typeof e==`object`&&!!e}function Ye(e){if(!N(e))return new Set;let t=e.excludeClasses;if(Array.isArray(t))return new Set(t.filter(e=>typeof e==`string`));let n=e.options;if(!N(n))return new Set;let r=n.excludeClasses;return Array.isArray(r)?new Set(r.filter(e=>typeof e==`string`)):new Set}const Xe={meta:{id:`architecture/no-manual-instantiation`,category:`architecture`,severity:`error`,description:`Do not manually instantiate @Injectable classes — use NestJS dependency injection`,help:`Register the class as a provider in a module and inject it via the constructor.`},check(e){let t=Ye(e.config?.rules?.[this.meta.id]),n=e.sourceFile.getDescendantsOfKind(f.NewExpression);for(let r of n){let n=r.getExpression().getText(),i=n.split(`.`).pop()??n;if(t.has(n)||t.has(i))continue;let a=qe.some(e=>n.endsWith(e)),o=Je.some(e=>n.endsWith(e));if(a||o){if(o){if(r.getFirstAncestorByKind(f.Decorator))continue;let e=r.getFirstAncestorByKind(f.MethodDeclaration),t=r.getFirstAncestorByKind(f.Constructor);if(!(e||t))continue}e.report({filePath:e.filePath,message:`Manual instantiation of '${n}' detected. Use dependency injection instead.`,help:this.meta.help,line:r.getStartLineNumber(),column:1})}}}},Ze=/\.(\w+)$/,Qe=/^(\w+)</,$e=new Set([`PrismaService`,`PrismaClient`,`EntityManager`,`DataSource`,`Repository`,`Connection`,`MongooseModel`,`InjectModel`,`InjectRepository`,`MikroORM`,`DrizzleService`]),et={meta:{id:`architecture/no-orm-in-controllers`,category:`architecture`,severity:`error`,description:`Controllers must not inject ORM services directly — use a service layer`,help:`Inject a service that wraps the ORM instead of using the ORM directly in controllers.`},check(e){for(let t of e.sourceFile.getClasses()){if(!S(t))continue;let n=t.getConstructors()[0];if(n){for(let t of n.getParameters()){let n=tt(t.getType().getText());if($e.has(n)){let r=t.getNameNode();e.report({filePath:e.filePath,message:`Controller injects ORM type '${n}' directly. Use a service layer.`,help:this.meta.help,line:r.getStartLineNumber(),column:r.getStartLinePos()+1})}}for(let n of t.getConstructors()[0]?.getParameters()??[])for(let t of n.getDecorators()){let n=t.getName();(n===`InjectRepository`||n===`InjectModel`)&&e.report({filePath:e.filePath,message:`Controller uses @${n}() decorator. Move data access to a service.`,help:this.meta.help,line:t.getStartLineNumber(),column:1})}}}}};function tt(e){let t=e.match(Ze);if(t)return t[1];let n=e.match(Qe);return n?n[1]:e}const nt=/\.(\w+)$/,rt=/^(\w+)</,it=new Set([`PrismaService`,`PrismaClient`,`EntityManager`,`DataSource`,`Connection`,`MikroORM`]),at={meta:{id:`architecture/no-orm-in-services`,category:`architecture`,severity:`warning`,description:`Services should use repository abstractions instead of ORM directly`,help:`Create a repository class that wraps ORM calls and inject that instead.`},check(e){for(let t of e.sourceFile.getClasses()){if(!Oe(t))continue;let n=t.getName()??``;if(n.endsWith(`Repository`)||n.endsWith(`Repo`))continue;let r=t.getConstructors()[0];if(r)for(let t of r.getParameters()){let n=ot(t.getType().getText());if(it.has(n)){let r=t.getNameNode();e.report({filePath:e.filePath,message:`Service injects ORM type '${n}' directly. Consider using a repository abstraction.`,help:this.meta.help,line:r.getStartLineNumber(),column:r.getStartLinePos()+1})}for(let n of t.getDecorators()){let t=n.getName();(t===`InjectRepository`||t===`InjectModel`)&&e.report({filePath:e.filePath,message:`Service uses @${t}() directly. Consider wrapping in a repository class.`,help:this.meta.help,line:n.getStartLineNumber(),column:1})}}}}};function ot(e){let t=e.match(nt);if(t)return t[1];let n=e.match(rt);return n?n[1]:e}const st=/\.(\w+)$/,ct=/^(\w+)</,lt=[/Repository$/,/Repo$/],ut={meta:{id:`architecture/no-repository-in-controllers`,category:`architecture`,severity:`error`,description:`Controllers must not inject repositories directly — use the service layer`,help:`Move database access to a service and inject the service into the controller instead.`},check(e){for(let t of e.sourceFile.getClasses()){if(!S(t))continue;let n=t.getConstructors()[0];if(n){for(let t of n.getParameters()){let n=dt(t.getType().getText());if(lt.some(e=>e.test(n))){let r=t.getNameNode();e.report({filePath:e.filePath,message:`Controller injects repository '${n}' directly. Use a service layer instead.`,help:this.meta.help,line:r.getStartLineNumber(),column:r.getStartLinePos()+1})}}for(let t of e.sourceFile.getImportDeclarations()){let n=t.getModuleSpecifierValue();(n.includes(`/repositories/`)||n.includes(`/repositories`))&&e.report({filePath:e.filePath,message:`Controller imports from repository path '${n}'.`,help:this.meta.help,line:t.getStartLineNumber(),column:1})}}}}};function dt(e){let t=e.match(st);if(t)return t[1];let n=e.match(ct);return n?n[1]:e}const ft={meta:{id:`architecture/no-service-locator`,category:`architecture`,severity:`warning`,description:`Avoid using ModuleRef.get() or ModuleRef.resolve() — prefer explicit constructor injection`,help:`Replace ModuleRef.get()/resolve() with constructor injection for explicit, testable dependencies.`},check(e){let t=e.sourceFile.getDescendantsOfKind(f.CallExpression);for(let n of t){let t=n.getExpression();if(t.getKind()!==f.PropertyAccessExpression)continue;let r=t.asKind(f.PropertyAccessExpression);if(!r)continue;let i=r.getName();if(i!==`get`&&i!==`resolve`)continue;let a=r.getExpression().getText();(a===`moduleRef`||a===`this.moduleRef`)&&e.report({filePath:e.filePath,message:`Service locator pattern: '${a}.${i}()' hides dependencies. Use constructor injection instead.`,help:this.meta.help,line:n.getStartLineNumber(),column:1})}}},pt={meta:{id:`architecture/prefer-constructor-injection`,category:`architecture`,severity:`warning`,description:`Prefer constructor injection over @Inject() property injection`,help:`Move the dependency to a constructor parameter instead of using property injection.`},check(e){for(let t of e.sourceFile.getClasses())if(ke(t))for(let n of t.getProperties())n.getDecorator(`Inject`)&&e.report({filePath:e.filePath,message:`Property '${n.getName()}' uses @Inject() decorator. Prefer constructor injection.`,help:this.meta.help,line:n.getStartLineNumber(),column:1})}},mt=[`/repositories/`,`/entities/`,`/dto/`,`/guards/`,`/interceptors/`,`/pipes/`,`/strategies/`],ht={meta:{id:`architecture/require-module-boundaries`,category:`architecture`,severity:`info`,description:`Avoid deep imports into other feature modules' internals`,help:`Import from the module's public API (barrel export) instead of reaching into its internals.`},check(e){for(let t of e.sourceFile.getImportDeclarations()){let n=t.getModuleSpecifierValue();n.startsWith(`.`)&&n.includes(`../`)&&mt.some(e=>n.includes(e))&&e.report({filePath:e.filePath,message:`Import '${n}' reaches into another module's internals.`,help:this.meta.help,line:t.getStartLineNumber(),column:1})}}};function P(e){return e.getDescendantsOfKind(f.ReturnStatement).some(e=>{let t=e.getExpression();return!t||t.getKind()!==f.NewExpression?!1:t.asKindOrThrow(f.NewExpression).getExpression().getText()===`Promise`})}const gt={meta:{id:`correctness/no-async-without-await`,category:`correctness`,severity:`warning`,description:`Async functions/methods should contain at least one await expression`,help:`Either add an await expression or remove the async keyword.`},check(e){for(let t of e.sourceFile.getClasses())for(let n of t.getMethods()){if(!n.isAsync()||S(t)&&C(n)||Me(n))continue;let r=n.getBody();if(r&&r.getDescendantsOfKind(f.AwaitExpression).filter(e=>{let t=e.getParent();for(;t&&t!==r;){if(t.getKind()===f.ArrowFunction||t.getKind()===f.FunctionExpression||t.getKind()===f.FunctionDeclaration)return!1;t=t.getParent()}return!0}).length===0){let t=n.getName();P(r)?e.report({filePath:e.filePath,message:`Async method '${t}()' returns a Promise directly — remove the async keyword.`,help:`The async keyword is unnecessary when you are already constructing a Promise manually. Remove async to avoid double-wrapping.`,line:n.getStartLineNumber(),column:1}):e.report({filePath:e.filePath,message:`Async method '${t}()' has no await expression.`,help:this.meta.help,line:n.getStartLineNumber(),column:1})}}for(let t of e.sourceFile.getFunctions()){if(!t.isAsync())continue;let n=t.getBody();if(n&&n.getDescendantsOfKind(f.AwaitExpression).filter(e=>{let t=e.getParent();for(;t&&t!==n;){if(t.getKind()===f.ArrowFunction||t.getKind()===f.FunctionExpression||t.getKind()===f.FunctionDeclaration)return!1;t=t.getParent()}return!0}).length===0){let r=t.getName()??`anonymous`;P(n)?e.report({filePath:e.filePath,message:`Async function '${r}()' returns a Promise directly — remove the async keyword.`,help:`The async keyword is unnecessary when you are already constructing a Promise manually. Remove async to avoid double-wrapping.`,line:t.getStartLineNumber(),column:1}):e.report({filePath:e.filePath,message:`Async function '${r}()' has no await expression.`,help:this.meta.help,line:t.getStartLineNumber(),column:1})}}}},_t=[`providers`,`controllers`,`imports`,`exports`],vt={meta:{id:`correctness/no-duplicate-module-metadata`,category:`correctness`,severity:`warning`,description:`Same identifier should not appear twice in a module metadata array`,help:`Remove the duplicate entry from the module metadata.`},check(e){for(let t of e.sourceFile.getClasses()){if(!Ae(t))continue;let n=t.getDecorator(`Module`);if(!n)continue;let r=n.getArguments()[0];if(!r||r.getKind()!==f.ObjectLiteralExpression)continue;let i=r.asKind(f.ObjectLiteralExpression);if(i)for(let t of _t){let n=i.getProperty(t);if(!n)continue;let r=n.getChildrenOfKind(f.ArrayLiteralExpression)[0];if(!r)continue;let a=new Set;for(let n of r.getElements()){let r=n.getText();a.has(r)?e.report({filePath:e.filePath,message:`Duplicate '${r}' in @Module() ${t} array.`,help:this.meta.help,line:n.getStartLineNumber(),column:1}):a.add(r)}}}}},yt=new Set([`Get`,`Post`,`Put`,`Delete`,`Patch`,`All`,`Head`,`Options`]),bt={meta:{id:`correctness/no-duplicate-routes`,category:`correctness`,severity:`error`,description:`Same HTTP method + route path + version should not appear twice in a single controller`,help:`Remove or rename one of the duplicate route handlers.`},check(e){for(let t of e.sourceFile.getClasses()){if(!S(t))continue;let n=new Map;for(let r of t.getMethods())for(let t of r.getDecorators()){let i=t.getName();if(!yt.has(i))continue;let a=t.getArguments(),o=a.length>0?a[0].getText():`""`,s=r.getDecorator(`Version`),c=`${i}:${o}:${s?s.getArguments()[0]?.getText()??``:``}`,l=n.get(c);l?e.report({filePath:e.filePath,message:`Duplicate route: @${i}(${o}) is already defined in '${l}()'.`,help:this.meta.help,line:r.getStartLineNumber(),column:1}):n.set(c,r.getName())}}}},xt={meta:{id:`correctness/no-empty-handlers`,category:`correctness`,severity:`warning`,description:`Controller HTTP handlers should not have empty bodies`,help:`Add implementation to the handler method or remove it if unnecessary.`},check(e){for(let t of e.sourceFile.getClasses())if(S(t))for(let n of t.getMethods()){if(!n.getDecorators().some(e=>b.has(e.getName())))continue;let t=n.getBody();if(!t)continue;let r=t.asKind(f.Block);r&&r.getStatements().length===0&&e.report({filePath:e.filePath,message:`Handler '${n.getName()}()' has an empty body.`,help:this.meta.help,line:n.getStartLineNumber(),column:1})}}};function St(e){let t=e.getReturnType().getText();return t.startsWith(`Promise<`)||t===`Promise`?!0:t===`any`||t===`error`?`unknown`:!1}const F=new Set([`save`,`create`,`insert`,`update`,`delete`,`remove`,`send`,`emit`,`publish`,`dispatch`,`execute`,`fetch`,`load`,`upload`,`download`,`process`]),Ct={meta:{id:`correctness/no-fire-and-forget-async`,category:`correctness`,severity:`warning`,description:`Calling async functions without await leads to unhandled promise rejections`,help:`Add await before the async call, or use void with explicit error handling if fire-and-forget is intentional.`},check(e){for(let t of e.sourceFile.getClasses())for(let n of t.getMethods()){if(C(n))continue;let t=n.getBody();if(!t)continue;let r=t.getDescendantsOfKind(f.ExpressionStatement);for(let t of r){let r=t.getExpression();if(r.getKind()===f.VoidExpression||r.getKind()===f.AwaitExpression||r.getKind()!==f.CallExpression)continue;let i=r.asKind(f.CallExpression);if(!i)continue;let a=i.getExpression().getText().split(`.`).pop()??``,o=St(i);if(o!==!1){if(o===`unknown`){let e=a.toLowerCase();if(!(F.has(e)||[...F].some(t=>e.startsWith(t)&&e!==t)))continue}t.getFirstAncestorByKind(f.MethodDeclaration)===n&&e.report({filePath:e.filePath,message:`Async call '${a}()' is not awaited — unhandled rejections will crash the process.`,help:this.meta.help,line:t.getStartLineNumber(),column:1})}}}}},wt={meta:{id:`correctness/no-missing-filter-catch`,category:`correctness`,severity:`error`,description:`Exception filter classes decorated with @Catch() must implement the catch() method`,help:`Add a catch(exception, host: ArgumentsHost) method to the filter class.`},check(e){for(let t of e.sourceFile.getClasses())x(t,`Catch`)&&(t.getExtends()||t.getMethods().some(e=>e.getName()===`catch`)||e.report({filePath:e.filePath,message:`Exception filter '${t.getName()}' has @Catch() but is missing the 'catch()' method.`,help:this.meta.help,line:t.getStartLineNumber(),column:1}))}},Tt={meta:{id:`correctness/no-missing-guard-method`,category:`correctness`,severity:`error`,description:`Guard classes must implement the canActivate() method`,help:`Add a canActivate(context: ExecutionContext) method to the guard class.`},check(e){for(let t of e.sourceFile.getClasses()){let n=t.getName()??``;n.endsWith(`Guard`)&&x(t,`Injectable`)&&(t.getExtends()||t.getMethods().some(e=>e.getName()===`canActivate`)||e.report({filePath:e.filePath,message:`Guard '${n}' is missing the 'canActivate()' method.`,help:this.meta.help,line:t.getStartLineNumber(),column:1}))}}},Et={meta:{id:`correctness/no-missing-injectable`,category:`correctness`,severity:`error`,description:`Provider classes with constructor dependencies must have the @Injectable() decorator`,help:`Add @Injectable() to providers that inject constructor dependencies.`,scope:`project`},check(e){let t=new Set([...e.providers.values()].map(e=>e.name)),n=new Map;for(let t of e.files){let r=e.project.getSourceFile(t);if(r)for(let e of r.getClasses()){let r=e.getName();if(r){let i=n.get(r)??[];i.push({cls:e,filePath:t}),n.set(r,i)}}}for(let r of e.moduleGraph.modules.values())for(let i of r.providers){if(t.has(i))continue;let a=n.get(i);if(a)for(let{cls:t,filePath:n}of a){let a=(t.getConstructors()[0]?.getParameters().length??0)>0;!(t.getDecorator(`Injectable`)||t.getDecorator(`Resolver`)||t.getDecorator(`WebSocketGateway`))&&a&&e.report({filePath:n,message:`Class '${i}' is listed in '${r.name}' providers but is missing @Injectable() decorator.`,help:this.meta.help,line:t.getStartLineNumber(),column:1})}}}},Dt={meta:{id:`correctness/no-missing-interceptor-method`,category:`correctness`,severity:`error`,description:`Interceptor classes must implement the intercept() method`,help:`Add an intercept(context: ExecutionContext, next: CallHandler) method to the interceptor class.`},check(e){for(let t of e.sourceFile.getClasses()){let n=t.getName()??``;n.endsWith(`Interceptor`)&&x(t,`Injectable`)&&(t.getExtends()||t.getMethods().some(e=>e.getName()===`intercept`)||e.report({filePath:e.filePath,message:`Interceptor '${n}' is missing the 'intercept()' method.`,help:this.meta.help,line:t.getStartLineNumber(),column:1}))}}},Ot={meta:{id:`correctness/no-missing-module-decorator`,category:`correctness`,severity:`warning`,description:`Classes named *Module should have a @Module() decorator`,help:`Add @Module({}) decorator to the class, or rename it if it is not a NestJS module.`},check(e){for(let t of e.sourceFile.getClasses()){let n=t.getName()??``;n.endsWith(`Module`)&&(n===`Module`||n===`DynamicModule`||x(t,`Module`)||e.report({filePath:e.filePath,message:`Class '${n}' is named like a module but is missing the @Module() decorator.`,help:this.meta.help,line:t.getStartLineNumber(),column:1}))}}},kt={meta:{id:`correctness/no-missing-pipe-method`,category:`correctness`,severity:`error`,description:`Pipe classes must implement the transform() method`,help:`Add a transform(value: any, metadata: ArgumentMetadata) method to the pipe class.`},check(e){for(let t of e.sourceFile.getClasses()){let n=t.getName()??``;n.endsWith(`Pipe`)&&x(t,`Injectable`)&&(t.getExtends()||t.getMethods().some(e=>e.getName()===`transform`)||e.report({filePath:e.filePath,message:`Pipe '${n}' is missing the 'transform()' method.`,help:this.meta.help,line:t.getStartLineNumber(),column:1}))}}},At={meta:{id:`correctness/prefer-readonly-injection`,category:`correctness`,severity:`warning`,description:`Constructor DI parameters should be readonly to prevent accidental reassignment`,help:`Add the 'readonly' modifier to the constructor parameter.`},check(e){for(let t of e.sourceFile.getClasses()){if(!(Oe(t)||S(t)))continue;let n=t.getConstructors()[0];if(n){for(let t of n.getParameters())if((t.hasModifier(`private`)||t.hasModifier(`protected`)||t.hasModifier(`public`))&&!t.isReadonly()){let n=t.getNameNode();e.report({filePath:e.filePath,message:`Constructor parameter '${t.getName()}' should be readonly.`,help:this.meta.help,line:n.getStartLineNumber(),column:n.getStartLinePos()+1})}}}}},jt={meta:{id:`correctness/require-inject-decorator`,category:`correctness`,severity:`error`,description:`Constructor parameters without type annotations must have @Inject() decorator for NestJS DI to resolve them`,help:`Add a type annotation or @Inject() decorator to the constructor parameter.`},check(e){for(let t of e.sourceFile.getClasses()){if(!ke(t))continue;let n=t.getConstructors()[0];if(n)for(let r of n.getParameters()){let n=r.getTypeNode(),i=r.getDecorators().some(e=>e.getName()===`Inject`);n||i||e.report({filePath:e.filePath,message:`Constructor parameter '${r.getName()}' in '${t.getName()}' has no type annotation and no @Inject() decorator — NestJS cannot resolve it.`,help:this.meta.help,line:r.getStartLineNumber(),column:1})}}}},Mt={onModuleInit:`OnModuleInit`,onModuleDestroy:`OnModuleDestroy`,onApplicationBootstrap:`OnApplicationBootstrap`,onApplicationShutdown:`OnApplicationShutdown`,beforeApplicationShutdown:`BeforeApplicationShutdown`},Nt={meta:{id:`correctness/require-lifecycle-interface`,category:`correctness`,severity:`warning`,description:`Classes with lifecycle methods should implement the corresponding NestJS interface`,help:`Add 'implements OnModuleInit' (or the appropriate interface) to the class declaration.`},check(e){for(let t of e.sourceFile.getClasses()){let n=t.getImplements().map(e=>e.getText());for(let r of t.getMethods()){let i=r.getName(),a=Mt[i];a&&(n.some(e=>e.includes(a))||e.report({filePath:e.filePath,message:`Class '${t.getName()}' has '${i}()' but does not implement '${a}'.`,help:this.meta.help,line:r.getStartLineNumber(),column:1}))}}}},Pt=new Set([f.ForStatement,f.ForOfStatement,f.ForInStatement,f.WhileStatement,f.DoStatement,f.AwaitExpression]),Ft={meta:{id:`performance/no-blocking-constructor`,category:`performance`,severity:`warning`,description:`Constructors in Injectable/Controller classes should not contain heavy operations`,help:`Move complex initialization logic to onModuleInit() lifecycle method instead.`},check(e){for(let t of e.sourceFile.getClasses()){if(!(x(t,`Injectable`)||x(t,`Controller`)))continue;let n=t.getConstructors()[0];if(!n)continue;let r=n.getBody();if(r){for(let i of r.getDescendants())if(Pt.has(i.getKind())){e.report({filePath:e.filePath,message:`Constructor in '${t.getName()}' contains blocking operation — use onModuleInit() instead.`,help:this.meta.help,line:n.getStartLineNumber(),column:1});break}}}}},It={meta:{id:`performance/no-dynamic-require`,category:`performance`,severity:`warning`,description:`Dynamic require() with variable arguments prevents bundler optimization`,help:`Use static import statements or dynamic import() with string literals.`},check(e){let t=e.sourceFile.getDescendantsOfKind(f.CallExpression);for(let n of t){if(n.getExpression().getText()!==`require`)continue;let t=n.getArguments();t.length!==0&&t[0].getKind()!==f.StringLiteral&&e.report({filePath:e.filePath,message:`Dynamic require() with non-literal argument prevents bundler optimization.`,help:this.meta.help,line:n.getStartLineNumber(),column:1})}}},Lt={meta:{id:`performance/no-orphan-modules`,category:`performance`,severity:`info`,description:`Module is never imported by any other module and may be dead code`,help:`Import this module in another module or remove it if it is unused.`,scope:`project`},check(e){let t=new Set;for(let n of e.moduleGraph.modules.values())for(let e of n.imports)t.add(e);for(let n of e.moduleGraph.modules.values())n.name!==`AppModule`&&(t.has(n.name)||e.report({filePath:n.filePath,message:`Module '${n.name}' is never imported by any other module.`,help:this.meta.help,line:n.classDeclaration.getStartLineNumber(),column:1}))}},Rt={meta:{id:`performance/no-request-scope-abuse`,category:`performance`,severity:`warning`,description:`Scope.REQUEST creates a new provider instance per request — use only when necessary`,help:`Remove Scope.REQUEST unless the provider genuinely needs per-request state (e.g., request-scoped context). Consider Scope.DEFAULT or Scope.TRANSIENT instead.`},check(e){let t=e.sourceFile.getDescendantsOfKind(f.PropertyAccessExpression);for(let n of t)n.getName()===`REQUEST`&&n.getExpression().getText()===`Scope`&&e.report({filePath:e.filePath,message:`Scope.REQUEST creates a new instance per request, which impacts performance and propagates request scope to all dependents.`,help:this.meta.help,line:n.getStartLineNumber(),column:1})}},zt=new Set([`readFileSync`,`writeFileSync`,`existsSync`,`mkdirSync`,`readdirSync`,`statSync`,`accessSync`,`appendFileSync`,`copyFileSync`,`renameSync`,`unlinkSync`]),Bt={meta:{id:`performance/no-sync-io`,category:`performance`,severity:`warning`,description:`Synchronous I/O calls block the event loop and should be avoided in NestJS applications`,help:`Use the async variant (e.g., readFile instead of readFileSync) with await.`},check(e){let t=e.sourceFile.getDescendantsOfKind(f.CallExpression);for(let n of t){let t=n.getExpression().getText().split(`.`).pop()??``;zt.has(t)&&e.report({filePath:e.filePath,message:`Synchronous I/O call '${t}()' blocks the event loop.`,help:this.meta.help,line:n.getStartLineNumber(),column:1})}}},Vt={meta:{id:`performance/no-unused-module-exports`,category:`performance`,severity:`info`,description:`Module exports a provider that no importing module actually uses`,help:`Remove the unused export or use the provider in an importing module.`,scope:`project`},check(e){for(let t of e.moduleGraph.modules.values()){if(t.exports.length===0)continue;let n=[];for(let r of e.moduleGraph.modules.values())r.name!==t.name&&r.imports.includes(t.name)&&n.push(r.name);if(n.length===0)continue;let r=new Set;for(let i of n){let n=e.moduleGraph.modules.get(i);if(n){for(let t of n.providers){let n=e.providers.get(t);if(n)for(let e of n.dependencies)r.add(e)}if(n.exports.includes(t.name))for(let e of t.exports)r.add(e);for(let t of n.controllers)for(let n of e.files){let i=e.project.getSourceFile(n);if(i)for(let e of i.getClasses()){if(e.getName()!==t)continue;let n=e.getConstructors()[0];if(n)for(let e of n.getParameters()){let t=e.getTypeNode(),n=t?t.getText():e.getType().getText(),i=n.split(`.`).pop()?.split(`<`)[0]??n;r.add(i)}}}}}for(let n of t.exports)e.moduleGraph.modules.has(n)||r.has(n)||e.report({filePath:t.filePath,message:`Module '${t.name}' exports '${n}' but no importing module uses it.`,help:this.meta.help,line:t.classDeclaration.getStartLineNumber(),column:1})}}},Ht=[`Guard`,`Interceptor`,`Filter`,`Middleware`,`Strategy`],Ut={meta:{id:`performance/no-unused-providers`,category:`performance`,severity:`warning`,description:`Injectable providers that are never injected by any other provider may be dead code`,help:`Remove the unused provider or inject it where needed.`,scope:`project`},check(e){let t=new Set;for(let n of e.providers.values())for(let e of n.dependencies)t.add(e);let n=[`Controller`,`Resolver`,`WebSocketGateway`];for(let r of e.files){let i=e.project.getSourceFile(r);if(i)for(let e of i.getClasses()){if(!n.some(t=>e.getDecorator(t)!==void 0))continue;let r=e.getConstructors()[0];if(r)for(let e of r.getParameters()){let n=e.getTypeNode(),r=n?n.getText():e.getType().getText(),i=r.split(`.`).pop()?.split(`<`)[0]??r;t.add(i)}}}for(let n of e.providers.values()){let r=n.name;if(Ht.some(e=>r.endsWith(e))||t.has(r))continue;let i=!1;for(let t of e.moduleGraph.modules.values())if(t.exports.includes(r)){i=!0;break}i||e.report({filePath:n.filePath,message:`Provider '${r}' is never injected by any other provider or controller.`,help:this.meta.help,line:n.classDeclaration.getStartLineNumber(),column:1})}}},Wt={meta:{id:`schema/require-cascade-rule`,category:`schema`,scope:`schema`,severity:`info`,description:`Relations should have explicit onDelete/cascade behavior defined`,help:`Add an explicit onDelete option (e.g. CASCADE, SET NULL) to avoid relying on database defaults.`},check(e){for(let t of e.schemaGraph.relations)if(!(t.type!==`many-to-one`&&t.type!==`one-to-one`)&&!t.onDelete){let n=e.schemaGraph.entities.get(t.fromEntity);if(!n)continue;e.report({filePath:n.filePath,entity:n.name,message:`Relation '${t.propertyName}' on '${t.fromEntity}' has no explicit onDelete behavior.`,help:this.meta.help})}}},Gt={meta:{id:`schema/require-primary-key`,category:`schema`,scope:`schema`,severity:`error`,description:`Every entity must have at least one primary key column`,help:`Add a primary key column (e.g. @id in Prisma, @PrimaryColumn/@PrimaryGeneratedColumn in TypeORM).`},check(e){for(let t of e.schemaGraph.entities.values())t.columns.some(e=>e.isPrimary)||e.report({filePath:t.filePath,entity:t.name,message:`Entity '${t.name}' has no primary key column.`,help:this.meta.help})}},Kt=/delete/i;function qt(e,t){let n=new Set(e.columns.map(e=>e.name.toLowerCase()));return n.has(`createdat`)||n.has(`created_at`)?!0:t===`typeorm`?e.columns.some(e=>e.type===`timestamp`&&e.isGenerated&&!Kt.test(e.name)):t===`prisma`?e.columns.some(e=>e.type===`DateTime`&&e.defaultValue!==void 0&&e.defaultValue.includes(`now()`)):t===`drizzle`?e.columns.some(e=>(e.type===`timestamp`||e.type===`date`||e.type===`datetime`)&&e.defaultValue!==void 0&&e.defaultValue.includes(`now()`)):!1}const Jt={meta:{id:`schema/require-timestamps`,category:`schema`,scope:`schema`,severity:`warning`,description:`Entities should have timestamp columns (createdAt/updatedAt)`,help:`Add createdAt/updatedAt columns to track when records are created and modified.`},check(e){for(let t of e.schemaGraph.entities.values())qt(t,e.orm)||e.report({filePath:t.filePath,entity:t.name,message:`Entity '${t.name}' has no timestamp columns (createdAt/updatedAt).`,help:this.meta.help})}},Yt={meta:{id:`security/no-csrf-disabled`,category:`security`,severity:`error`,description:`CSRF protection should not be explicitly disabled`,help:`Enable CSRF protection or remove the explicit disabling of it.`},check(e){let t=e.sourceFile.getDescendantsOfKind(f.PropertyAssignment);for(let n of t){let t=n.getName();if(t!==`csrf`&&t!==`csrfProtection`)continue;let r=n.getInitializer();r&&r.getText()===`false`&&e.report({filePath:e.filePath,message:`CSRF protection explicitly disabled (${t}: false).`,help:this.meta.help,line:n.getStartLineNumber(),column:1})}}},Xt={meta:{id:`security/no-dangerous-redirects`,category:`security`,severity:`error`,description:`Redirects using user-controlled input (from @Query/@Param) are an open redirect vulnerability`,help:`Validate redirect URLs against an allowlist of safe destinations.`},check(e){for(let t of e.sourceFile.getClasses())if(S(t))for(let n of t.getMethods()){let t=new Set;for(let e of n.getParameters())e.getDecorators().some(e=>e.getName()===`Query`||e.getName()===`Param`)&&t.add(e.getName());if(t.size===0)continue;let r=n.getDescendantsOfKind(f.CallExpression);for(let n of r)if(n.getExpression().getText().endsWith(`redirect`))for(let r of n.getArguments()){let i=r.getText();t.has(i)&&e.report({filePath:e.filePath,message:`Redirect uses user-controlled parameter '${i}' — open redirect risk.`,help:this.meta.help,line:n.getStartLineNumber(),column:1})}let i=n.getDecorators().find(e=>e.getName()===`Redirect`);if(i)for(let n of i.getArguments()){let r=n.getText();t.has(r)&&e.report({filePath:e.filePath,message:`@Redirect() uses user-controlled parameter '${r}' — open redirect risk.`,help:this.meta.help,line:i.getStartLineNumber(),column:1})}}}},Zt={meta:{id:`security/no-eval`,category:`security`,severity:`error`,description:`Usage of eval() or new Function() is a security risk and should be avoided`,help:`Refactor to avoid eval() and new Function(). Use safer alternatives like JSON.parse() or a sandboxed interpreter.`},check(e){let t=e.sourceFile.getDescendantsOfKind(f.CallExpression);for(let n of t)n.getExpression().getText()===`eval`&&e.report({filePath:e.filePath,message:`Usage of eval() is a security risk.`,help:this.meta.help,line:n.getStartLineNumber(),column:1});let n=e.sourceFile.getDescendantsOfKind(f.NewExpression);for(let t of n)t.getExpression().getText()===`Function`&&e.report({filePath:e.filePath,message:`Usage of new Function() is a security risk.`,help:this.meta.help,line:t.getStartLineNumber(),column:1})}},Qt={meta:{id:`security/no-exposed-env-vars`,category:`security`,severity:`warning`,description:`Use NestJS ConfigService instead of direct process.env access in Injectable/Controller classes`,help:`Inject ConfigService and use configService.get('VAR_NAME') instead of process.env.VAR_NAME.`},check(e){for(let t of e.sourceFile.getClasses()){if(!(x(t,`Injectable`)||x(t,`Controller`)))continue;let n=t.getDescendantsOfKind(f.PropertyAccessExpression);for(let r of n)r.getExpression().getText()===`process.env`&&e.report({filePath:e.filePath,message:`Direct 'process.env.${r.getName()}' access in '${t.getName()}'. Use ConfigService instead.`,help:this.meta.help,line:r.getStartLineNumber(),column:1})}}},$t=/^(error|err|e|ex|exception)$/,en={meta:{id:`security/no-exposed-stack-trace`,category:`security`,severity:`warning`,description:`Stack traces should not be exposed in responses — they leak internal implementation details`,help:`Log the stack trace internally and return a generic error message to the client.`},check(e){let t=e.sourceFile.getDescendantsOfKind(f.PropertyAccessExpression);for(let n of t){if(n.getName()!==`stack`)continue;let t=n.getExpression().getText();if(!($t.test(t)||t.endsWith(`.error`)||t.endsWith(`.err`)))continue;let r=n.getParent();if(!r)continue;let i=r.getKind();(i===f.ReturnStatement||i===f.PropertyAssignment||i===f.ShorthandPropertyAssignment||i===f.CallExpression)&&e.report({filePath:e.filePath,message:`Stack trace '${t}.stack' may be exposed in response — leaks implementation details.`,help:this.meta.help,line:n.getStartLineNumber(),column:1})}}},tn=[{pattern:/^(?=.*\d)[A-Za-z0-9+/]{40,}={0,2}$/,name:`Base64 key`},{pattern:/^sk[-_][a-zA-Z0-9]{20,}$/,name:`Secret key`},{pattern:/^pk[-_][a-zA-Z0-9]{20,}$/,name:`Public key (in source)`},{pattern:/^ghp_[a-zA-Z0-9]{36,}$/,name:`GitHub personal access token`},{pattern:/^github_pat_[a-zA-Z0-9_]{22,}$/,name:`GitHub fine-grained PAT`},{pattern:/^gho_[a-zA-Z0-9]{36,}$/,name:`GitHub OAuth token`},{pattern:/^xox[bpras]-[a-zA-Z0-9-]+$/,name:`Slack token`},{pattern:/^eyJ[a-zA-Z0-9_-]{10,}\.[a-zA-Z0-9_-]{10,}\./,name:`JWT token`},{pattern:/^AKIA[0-9A-Z]{16}$/,name:`AWS Access Key ID`},{pattern:/^[a-f0-9]{64}$/,name:`Hex-encoded secret (64 chars)`}],nn=[/secret/i,/password/i,/passwd/i,/api[_-]?key/i,/auth[_-]?token/i,/private[_-]?key/i,/access[_-]?key/i,/client[_-]?secret/i],rn=new Set([`your-secret-here`,`changeme`,`password`]),an=/^[A-Za-z][A-Za-z0-9_]*(\.[A-Za-z][A-Za-z0-9_]*)+$/,I=new Set([`cursor`,`nextCursor`,`prevCursor`,`previousCursor`,`startCursor`,`endCursor`,`pageToken`,`nextPageToken`,`continuationToken`,`continuation`,`nextPage`,`afterCursor`,`beforeCursor`]);function L(e){return!(e.length<8||e.includes("${")||e.startsWith(`process.env`)||rn.has(e)||e.includes(` `)||an.test(e))}function R(e){return nn.some(t=>t.test(e))}function on(e){try{let t=Buffer.from(e,`base64`).toString(`utf-8`);return JSON.parse(t),!0}catch{return!1}}function sn(e){let t=new Map;for(let n of e)t.set(n,(t.get(n)??0)+1);let n=0;for(let r of t.values()){let t=r/e.length;n-=t*Math.log2(t)}return n}const z=new Set([...`aeiouyAEIOUY`]),cn=/^[A-Z]{2,4}_/,ln=/(?<=[a-z])(?=[A-Z])|(?<=[A-Za-z])(?=\d)|(?<=\d)(?=[A-Za-z])|_/,un=/[a-zA-Z]/;function dn(e){let t=e.includes(`_`),n=e.split(ln).filter(e=>e.length>0).filter(e=>un.test(e)),r=n.filter(e=>e.length>=4&&[...e].some(e=>z.has(e)));return n.slice(0,6).filter(e=>e.length>=4&&[...e].some(e=>z.has(e))).length>=2||t&&e.split(`_`).filter(e=>e.length>=3).length>=2||cn.test(e)?!0:(sn(e)>4.9&&!t&&r.length,!1)}function fn(e){let t=e.getParent();if(!t)return!1;let n=t.asKind(f.PropertyAssignment);if(n)return I.has(n.getName());let r=t.asKind(f.VariableDeclaration);return r?I.has(r.getName()):!1}const pn={meta:{id:`security/no-hardcoded-secrets`,category:`security`,severity:`error`,description:`Detect hardcoded secrets, API keys, and tokens in source code`,help:`Move secrets to environment variables and access them via ConfigService.`},check(e){let t=e.sourceFile.getDescendantsOfKind(f.StringLiteral);for(let n of t){let t=n.getLiteralValue();if(!(t.length<16)&&n.getParent()?.getKind()!==f.ImportDeclaration){for(let{pattern:r,name:i}of tn)if(r.test(t)){if(i===`Base64 key`&&(on(t)||fn(n)||dn(t)))break;e.report({filePath:e.filePath,message:`Possible hardcoded ${i} detected.`,help:this.meta.help,line:n.getStartLineNumber(),column:1});break}}}let n=e.sourceFile.getDescendantsOfKind(f.VariableDeclaration);for(let t of n){let n=t.getName(),r=t.getInitializer();!r||r.getKind()!==f.StringLiteral||R(n)&&L(r.getText().slice(1,-1))&&e.report({filePath:e.filePath,message:`Variable '${n}' appears to contain a hardcoded secret.`,help:this.meta.help,line:t.getStartLineNumber(),column:1})}let r=e.sourceFile.getDescendantsOfKind(f.PropertyAssignment);for(let t of r){let n=t.getName(),r=t.getInitializer();!r||r.getKind()!==f.StringLiteral||R(n)&&L(r.getText().slice(1,-1))&&e.report({filePath:e.filePath,message:`Property '${n}' appears to contain a hardcoded secret.`,help:this.meta.help,line:t.getStartLineNumber(),column:1})}}},mn=RegExp(`(?:^|[^a-zA-Z])\\w*(?:${[`Entity`,`Model`].join(`|`)})(?:[^a-zA-Z]|$)`),hn={meta:{id:`security/no-raw-entity-in-response`,category:`security`,severity:`warning`,description:`Returning ORM entities directly from controllers can leak internal fields like passwords or IDs`,help:`Map entities to DTOs or use class-transformer's @Exclude()/@Expose() decorators before returning. This rule detects classes with Entity/Model suffix in return types.`},check(e){for(let t of e.sourceFile.getClasses())if(S(t))for(let n of t.getMethods()){if(!C(n))continue;let t=n.getReturnType().getText();mn.test(t)&&!t.includes(`DTO`)&&!t.includes(`Dto`)&&!t.includes(`Response`)&&e.report({filePath:e.filePath,message:`Controller method '${n.getName()}' returns a raw entity type. This may leak internal fields.`,help:this.meta.help,line:n.getStartLineNumber(),column:1})}}},gn={meta:{id:`security/no-synchronize-in-production`,category:`security`,severity:`error`,description:`TypeORM synchronize: true auto-syncs schema and can drop columns or tables in production`,help:`Set synchronize: false and use migrations for production schema changes.`},check(e){let t=e.sourceFile.getDescendantsOfKind(f.PropertyAssignment);for(let n of t){if(n.getName()!==`synchronize`)continue;let t=n.getInitializer();t&&t.getText()===`true`&&e.report({filePath:e.filePath,message:`TypeORM 'synchronize: true' can auto-drop columns and tables in production.`,help:this.meta.help,line:n.getStartLineNumber(),column:1})}}},_n=new Set([`md5`,`sha1`]),B=[Ne,ut,et,at,Xe,ft,pt,ht,De,Ke,At,Nt,xt,bt,Tt,kt,wt,Dt,gt,vt,Ot,jt,Ct,Et,pn,Zt,{meta:{id:`security/no-weak-crypto`,category:`security`,severity:`warning`,description:`Weak hashing algorithms (MD5, SHA1) should not be used for security purposes`,help:`Use a stronger algorithm like SHA-256 or bcrypt for password hashing.`},check(e){let t=e.sourceFile.getDescendantsOfKind(f.CallExpression);for(let n of t){if(!n.getExpression().getText().endsWith(`createHash`))continue;let t=n.getArguments();if(t.length===0)continue;let r=t[0];if(r.getKind()!==f.StringLiteral)continue;let i=r.getText().slice(1,-1).toLowerCase();_n.has(i)&&e.report({filePath:e.filePath,message:`Weak hashing algorithm '${i}' used in createHash().`,help:this.meta.help,line:n.getStartLineNumber(),column:1})}}},Qt,Yt,en,Xt,gn,hn,Bt,Ft,It,Rt,Ut,Vt,Lt,Gt,Jt,Wt];function vn(){return[...B]}function yn(e){return e.meta.scope===`project`}function bn(e){return e.meta.scope===`schema`}function xn(e,t,n){if(t.length===0)return e;let r=new Set(e.map(e=>e.meta.id)),i=[...e];for(let e of t){if(r.has(e.meta.id)){n.push(`Custom rule "${e.meta.id}" conflicts with a built-in rule and was skipped`);continue}i.push(e)}return i}function Sn(e,t){return t.filter(t=>{let n=e.rules?.[t.meta.id];return!(n===!1||typeof n==`object`&&n.enabled===!1||e.categories?.[t.meta.category]===!1)})}function Cn(e){let t=[],n=[],r=[];for(let i of e)bn(i)?r.push(i):yn(i)?n.push(i):t.push(i);return{fileRules:t,projectRules:n,schemaRules:r}}const wn=new Set([`security`,`performance`,`correctness`,`architecture`]),Tn=new Set([`error`,`warning`,`info`]),En=new Set([`file`,`project`]),Dn=`custom/`;function On(e){if(typeof e!=`object`||!e)return!1;let t=e;if(typeof t.check!=`function`||typeof t.meta!=`object`||t.meta===null)return!1;let n=t.meta;return!(typeof n.id!=`string`||n.id.trim()===``||typeof n.description!=`string`||typeof n.help!=`string`||!wn.has(n.category)||!Tn.has(n.severity)||n.scope!==void 0&&!En.has(n.scope))}function kn(e){return e.meta.id.startsWith(Dn)?e:{...e,meta:{...e.meta,id:`${Dn}${e.meta.id}`}}}async function An(t,i){let a=[],o=[],c=s(i,t);if(!e(c))return o.push(`Custom rules directory not found: ${c}`),{rules:a,warnings:o};if(!r(c).isDirectory())return o.push(`Custom rules path is not a directory: ${c}`),{rules:a,warnings:o};let l;try{l=n(c)}catch(e){return o.push(`Failed to read custom rules directory: ${e instanceof Error?e.message:String(e)}`),{rules:a,warnings:o}}let u=l.filter(e=>e.endsWith(`.ts`));if(u.length===0)return o.push(`No rule files (.ts) found in: ${c}`),{rules:a,warnings:o};let d=m(c,{interopDefault:!0});for(let e of u){let t=s(c,e),n;try{n=await d.import(t)}catch(t){o.push(`Failed to load custom rule file "${e}": ${t instanceof Error?t.message:String(t)}`);continue}let r=!1;for(let[t,i]of Object.entries(n))On(i)?(a.push(kn(i)),r=!0):t!==`__esModule`&&typeof i==`object`&&i&&`meta`in i&&o.push(`Invalid rule export "${t}" in "${e}": missing or invalid required fields (check, meta.id, meta.description, meta.help, meta.category, meta.severity)`);!r&&Object.keys(n).length>0&&(Object.values(n).some(e=>typeof e==`object`&&!!e&&(`meta`in e||`check`in e))||o.push(`No valid rule exports found in "${e}"`))}return{rules:a,warnings:o}}function jn(e,t){return e.customRulesDir?An(e.customRulesDir,t):Promise.resolve({rules:[],warnings:[]})}async function V(e,t){let n=await Se(e,t),{rules:r,warnings:i}=await jn(n,e),a=xn(B,r,i),{fileRules:o,projectRules:s,schemaRules:c}=Cn(Sn(n,a));return{combinedRules:a,config:n,customRuleWarnings:i,fileRules:o,projectRules:s,schemaRules:c}}async function Mn(e,t={}){return(await l(t.include??y.include,{cwd:e,absolute:!0,ignore:t.exclude??y.exclude})).sort()}async function Nn(e,t,n={}){let r=await Promise.all([...t.projects.entries()].map(async([t,r])=>[t,await Mn(a(e,r),n)])),i=new Map;for(let[e,t]of r)i.set(e,t);return i}function Pn(e){let t=new d({compilerOptions:{strict:!0,target:99,module:99,skipFileDependencyResolution:!0},skipAddingFilesFromTsConfig:!0});for(let n of e)t.addSourceFileAtPath(n);return t}const Fn=/import\([^)]+\)\.(\w+)/,In=/^(\w+)</;function Ln(e,t){let n=[];for(let r of e.getClasses()){if(!r.getDecorator(`Injectable`))continue;let e=r.getName();if(!e)continue;let i=r.getConstructors()[0],a=i?i.getParameters().map(e=>{let t=e.getTypeNode();return Bn(t?t.getText():e.getType().getText())}):[],o=r.getMethods().filter(e=>{let t=e.getScope();return!t||t===`public`}).length;n.push({name:e,filePath:t,classDeclaration:r,dependencies:a,publicMethodCount:o})}return n}function Rn(e,t){let n=new Map;for(let r of t){let t=e.getSourceFile(r);if(t)for(let e of Ln(t,r))n.set(e.name,e)}return n}function zn(e,t,n){for(let[t,r]of e)r.filePath===n&&e.delete(t);let r=t.getSourceFile(n);if(r)for(let t of Ln(r,n))e.set(t.name,t)}function Bn(e){let t=e.match(Fn);if(t)return t[1];let n=e.match(In);return n?n[1]:e}const H=/^['"`]|['"`]$/g,Vn=/\/+/g,Hn=/\/$/;function Un(e,t){let n=e;for(;n&&n!==t;){let e=n.getParent();if(!e||e===t)break;let r=e.getKind();if(r===f.IfStatement){let t=e.asKindOrThrow(f.IfStatement);if(n===t.getThenStatement()||n===t.getElseStatement())return!0}if(r===f.ConditionalExpression){let t=e.asKindOrThrow(f.ConditionalExpression);if(n===t.getWhenTrue()||n===t.getWhenFalse())return!0}let i=n.getKind();if(i===f.CaseClause||i===f.DefaultClause||i===f.CatchClause)return!0;n=e}return!1}function Wn(e){let t=e.getDecorator(`Controller`);if(!t)return``;let n=t.getArguments();if(n.length===0)return``;let r=n[0];if(r.getKind()===f.ObjectLiteralExpression){let e=r.asKindOrThrow(f.ObjectLiteralExpression).getProperty(`path`);if(!e)return``;let t=e.asKind(f.PropertyAssignment);if(!t)return``;let n=t.getInitializer();return n?n.getText().replace(H,``):``}return r.getText().replace(H,``)}function Gn(e){for(let t of e.getDecorators()){let e=t.getName();if(!b.has(e))continue;let n=t.getArguments(),r=n.length>0?n[0].getText().replace(H,``):``;return{httpMethod:e.toUpperCase(),path:r}}}function Kn(e,t){return`/${[e,t].filter(Boolean).join(`/`)}`.replace(Vn,`/`).replace(Hn,``)||`/`}function U(e){let t=new Map,n=e.getConstructors()[0];if(!n)return t;for(let e of n.getParameters()){let n=e.getName(),r=e.getTypeNode(),i=r?r.getText():e.getType().getText();t.set(n,Bn(i))}return t}function qn(e,t){let n=e.getBody();if(!n)return[];let r=new Map,i=0,a=n.getDescendantsOfKind(f.CallExpression);for(let e of a){let a=e.getExpression();if(a.getKind()!==f.PropertyAccessExpression)continue;let o=a.asKindOrThrow(f.PropertyAccessExpression),s=o.getName(),c=o.getExpression();if(c.getKind()!==f.PropertyAccessExpression)continue;let l=c.asKindOrThrow(f.PropertyAccessExpression);if(l.getExpression().getKind()!==f.ThisKeyword)continue;let u=l.getName();if(!t.has(u))continue;r.has(u)||r.set(u,new Map);let d=r.get(u),p=Un(e,n),m=d.get(s);m?m.isUnconditional=m.isUnconditional||!p:d.set(s,{isUnconditional:!p,order:i++})}let o=[];for(let[e,n]of r){let r=[];for(let[e,t]of n)r.push({name:e,conditional:!t.isUnconditional,order:t.order});r.sort((e,t)=>e.order-t.order),o.push({className:t.get(e),methodsCalled:r})}return o}function Jn(e){return e.endsWith(`Repository`)?`repository`:e.endsWith(`Guard`)?`guard`:e.endsWith(`Interceptor`)?`interceptor`:e.endsWith(`Pipe`)?`pipe`:e.endsWith(`Filter`)?`filter`:e.endsWith(`Gateway`)?`gateway`:`service`}function W(e,t,n){let r=[],i=new Set,a=[],o=[];for(let t of e)if(t.methodsCalled.length===0)o.push(t);else for(let e of t.methodsCalled)a.push({className:t.className,mc:e,dep:t});a.sort((e,t)=>e.mc.order-t.mc.order);for(let e of o){if(n.has(e.className)||i.has(e.className))continue;i.add(e.className),n.add(e.className);let a=t.get(e.className),o=[];a&&(o=a.dependencies.map(e=>({className:e,methodsCalled:[]}))),r.push({className:e.className,conditional:!1,dependencies:W(o,t,new Set(n)),filePath:a?.filePath??``,line:0,methodName:null,order:0,totalMethods:a?.publicMethodCount??0,type:Jn(e.className)})}let s=new Map;for(let{className:e,dep:t}of a)s.has(e)||s.set(e,t.methodsCalled);for(let{className:e,mc:o}of a){if(n.has(e))continue;let a=t.get(e),c=!i.has(e);c&&i.add(e);let l=[];if(c&&a){let r=new Set(n);r.add(e);let i=U(a.classDeclaration),o=new Map,c=0,u=s.get(e)??[];for(let e of u){let t=a.classDeclaration.getInstanceMethod(e.name);if(t)for(let e of qn(t,i)){o.has(e.className)||o.set(e.className,new Map);let t=o.get(e.className);for(let n of e.methodsCalled){let e=t.get(n.name);e?e.isUnconditional=e.isUnconditional||!n.conditional:t.set(n.name,{isUnconditional:!n.conditional,order:c++})}}}l=W([...o.entries()].map(([e,t])=>({className:e,methodsCalled:[...t.entries()].map(([e,t])=>({name:e,conditional:!t.isUnconditional,order:t.order})).sort((e,t)=>e.order-t.order)})),t,r)}let u=0;if(a){let e=a.classDeclaration.getInstanceMethod(o.name);e&&(u=e.getStartLineNumber())}r.push({className:e,conditional:o.conditional,dependencies:l,filePath:a?.filePath??``,line:u,methodName:o.name,order:o.order,totalMethods:a?.publicMethodCount??0,type:Jn(e)})}return r}function Yn(e,t,n){let r=[];for(let i of e.getClasses()){if(!S(i))continue;let e=Wn(i),a=i.getName()??`AnonymousController`,o=U(i);for(let s of i.getMethods()){let i=Gn(s);if(!i)continue;let c=Kn(e,i.path),l=W(qn(s,o),n,new Set);r.push({controllerClass:a,dependencies:l,filePath:t,handlerMethod:s.getName(),httpMethod:i.httpMethod,line:s.getStartLineNumber(),routePath:c})}}return r}function G(e,t,n){let r=[];for(let i of t){let t=e.getSourceFile(i);t&&r.push(...Yn(t,i,n))}return{endpoints:r}}function K(e,t,n,r,i,a){if(i>10)return[];let o=e.getBody();if(!o)return[];let s=[],c=o.getDescendantsOfKind(f.CallExpression);for(let e of c){let o=e.getExpression();if(o.getKind()!==f.PropertyAccessExpression)continue;let c=o.asKindOrThrow(f.PropertyAccessExpression),l=c.getName(),u=c.getExpression();if(u.getKind()===f.PropertyAccessExpression){let e=u.asKindOrThrow(f.PropertyAccessExpression);if(e.getExpression().getKind()!==f.ThisKeyword)continue;let a=e.getName(),o=t.get(a);if(!o)continue;let c=`${o}.${l}`;if(r.has(c)){s.push({calls:[],circular:!0,className:o,filePath:``,line:0,methodName:l});continue}r.add(c);let d=n.get(o),p=[],m=``,h=0;if(d){m=d.filePath;let e=d.classDeclaration.getInstanceMethod(l);e&&(h=e.getStartLineNumber(),p=K(e,U(d.classDeclaration),n,new Set(r),i+1,d.classDeclaration))}s.push({calls:p,className:o,filePath:m,line:h,methodName:l})}else if(u.getKind()===f.ThisKeyword&&a){let e=a.getInstanceMethod(l);if(!e)continue;let o=`${a.getName()??`Anonymous`}.${l}`;if(r.has(o))continue;r.add(o);let c=K(e,t,n,new Set(r),i+1,a);s.push(...c)}}return s}function Xn(e,t,n){let r=n.getSourceFile(e.filePath);if(!r)return[];let i=r.getClasses().find(t=>t.getName()===e.controllerClass);if(!i)return[];let a=i.getInstanceMethod(e.handlerMethod);return a?K(a,U(i),t,new Set,0,i):[]}function Zn(e,t,n,r){e.endpoints=e.endpoints.filter(e=>e.filePath!==n);let i=t.getSourceFile(n);i&&e.endpoints.push(...Yn(i,n,r))}const Qn=new Set([`pgTable`,`mysqlTable`,`sqliteTable`]),$n=new Set([`serial`,`bigserial`,`smallserial`]),er=/=>\s*(\w+)/;function tr(e){let t={type:`unknown`,isPrimary:!1,isNullable:!0,isGenerated:!1,isUnique:!1};function n(e){if(e.getKind()===f.CallExpression){let r=e.asKindOrThrow(f.CallExpression),i=r.getExpression();if(i.getKind()===f.PropertyAccessExpression){let e=i.asKindOrThrow(f.PropertyAccessExpression);switch(e.getName()){case`primaryKey`:t.isPrimary=!0;break;case`notNull`:t.isNullable=!1;break;case`unique`:t.isUnique=!0;break;case`default`:{let e=r.getArguments();e.length>0&&(t.defaultValue=e[0].getText().replace(/['"]/g,``));break}case`defaultNow`:t.defaultValue=`now()`;break;case`generatedAlwaysAsIdentity`:case`autoincrement`:t.isGenerated=!0;break;case`references`:{let e=r.getArguments();if(e.length>0){let n=e[0].getText(),r=er.exec(n);if(r&&(t.reference={toEntity:r[1]}),e.length>1){let n=e[1];if(n.getKind()===f.ObjectLiteralExpression){let e=n.asKindOrThrow(f.ObjectLiteralExpression);for(let n of e.getProperties())if(n.getKind()===f.PropertyAssignment){let e=n.asKindOrThrow(f.PropertyAssignment);if(e.getName()===`onDelete`){let n=e.getInitializer()?.getText();n&&(t.reference.onDelete=n.replace(/['"]/g,``))}}}}}break}default:break}n(e.getExpression())}else if(i.getKind()===f.Identifier){let e=i.getText();t.type=e,$n.has(e)&&(t.isGenerated=!0)}}}return n(e),t}function nr(e){let t=[];for(let n of e.getProperties()){if(n.getKind()!==f.PropertyAssignment)continue;let e=n.asKindOrThrow(f.PropertyAssignment),r=e.getName(),i=e.getInitializer();if(!i)continue;let a=tr(i);t.push({name:r,type:a.type,isPrimary:a.isPrimary,isNullable:a.isNullable,isGenerated:a.isGenerated,isUnique:a.isUnique,defaultValue:a.defaultValue})}return t}function rr(e,t){let n=[];for(let r of e.getProperties()){if(r.getKind()!==f.PropertyAssignment)continue;let e=r.asKindOrThrow(f.PropertyAssignment),i=e.getName(),a=e.getInitializer();if(!a)continue;let o=tr(a);o.reference&&n.push({type:`many-to-one`,fromEntity:t,toEntity:o.reference.toEntity,propertyName:i,isNullable:o.isNullable,...o.reference.onDelete?{onDelete:o.reference.onDelete}:{}})}return n}function ir(e){let t=[],n=e.getDescendantsOfKind(f.CallExpression);for(let e of n){let n=e.getExpression();if(n.getKind()!==f.PropertyAccessExpression||n.asKindOrThrow(f.PropertyAccessExpression).getName()!==`on`)continue;let r=[];for(let t of e.getArguments())if(t.getKind()===f.PropertyAccessExpression){let e=t.asKindOrThrow(f.PropertyAccessExpression);r.push(e.getName())}if(r.length===0)continue;let i=e.getText().includes(`uniqueIndex`);t.push({columns:r,isUnique:i})}return t}function ar(e){let t=[],n=e.getFilePath();for(let r of e.getDescendantsOfKind(f.VariableDeclaration)){let e=r.getInitializer();if(!e||e.getKind()!==f.CallExpression)continue;let i=e.asKindOrThrow(f.CallExpression),a=i.getExpression();if(a.getKind()!==f.Identifier)continue;let o=a.getText();if(!Qn.has(o))continue;let s=i.getArguments();if(s.length<2)continue;let c=s[0],l=r.getName();c.getKind()===f.StringLiteral&&(l=c.asKindOrThrow(f.StringLiteral).getLiteralValue());let u=s[1];if(u.getKind()!==f.ObjectLiteralExpression)continue;let d=u.asKindOrThrow(f.ObjectLiteralExpression),p=r.getName(),m=nr(d),h=rr(d,p),g;if(s.length>=3&&(g=ir(s[2]),g))for(let e of g)for(let t of e.columns){let e=m.find(e=>e.name===t);e&&(e.hasIndex=!0)}t.push({name:p,tableName:l,filePath:n,columns:m,relations:h,indexes:g})}return t}const or={supportsIncrementalUpdate:!0,extract(e,t){let n=[];for(let r of t){let t=e.getSourceFile(r);t&&n.push(...ar(t))}return n}},sr=/^model\s+(\w+)\s*\{/,cr=/^enum\s+(\w+)\s*\{/,lr=/^(\w+)\s+(\w+)(\?)?(\[\])?(.*)$/,ur=/@(\w+)(\((?:[^()]*|\([^()]*\))*\))?/g,dr=/@default\(((?:[^()]*|\([^()]*\))*)\)/,fr=/^@@map\(\s*"([^"]+)"\s*\)/;function pr(r){let i=a(r,`prisma`,`schema.prisma`);if(e(i)){let e=a(r,`prisma`),t=n(e).filter(e=>e.endsWith(`.prisma`));return t.length>1?t.map(t=>a(e,t)):[i]}let o=a(r,`schema.prisma`);if(e(o))return[o];try{let n=a(r,`package.json`),i=JSON.parse(t(n,`utf-8`)).prisma?.schema;if(i){let t=a(r,i);if(e(t))return[t]}}catch{}return[]}function mr(e){let n=[],r=new Set;for(let i of e){let e;try{e=t(i,`utf-8`)}catch{continue}let a=e.split(`
|
|
4
|
-
`),o=null,s=[],c=[],l=[],u;for(let e of a){let t=e.trim(),a=
|
|
5
|
-
`);for(let e of n){let n={config:r,sourceFile:o,filePath:t,report(t){let n=[],r=Math.max(0,t.line-6),a=Math.min(s.length,t.line+5);for(let e=r;e<a;e++)n.push({line:e+1,text:s[e]});i.push({...t,rule:e.meta.id,category:e.meta.category,scope:`file`,severity:e.meta.severity,sourceLines:n})}};try{e.check(n)}catch(t){a.push({ruleId:e.meta.id,error:t})}}return{diagnostics:i,errors:a}}function
|
|
2
|
+
`),r=!1;for(let e of n){let n=e.trim();if(ne.test(n)){let e=n.match(re);if(e){for(let n of e[1].split(`,`)){let e=n.trim().replace(oe,``);e&&t.push(e)}return t}r=!0;continue}if(r){if(ie.test(e)&&n!==``)break;let r=n.match(ae);r&&t.push(r[1])}}return t}async function ce(e){let t=a(e,`nest-cli.json`);try{let e=await c(t,`utf-8`),n=JSON.parse(e);if(!(n.monorepo&&n.projects))return null;let r=new Map;for(let[e,t]of Object.entries(n.projects)){let n=t.root??e;r.set(e,n)}return r.size===0?null:{projects:r}}catch{return null}}function le(e){let t={...e.dependencies,...e.devDependencies,...e.peerDependencies};return!!(t[`@nestjs/core`]||t[`@nestjs/common`])}async function v(e,t){let n=await l(t.map(e=>`${e}/package.json`),{cwd:e,absolute:!0,ignore:[`**/node_modules/**`]}),r=new Map;for(let t of n)try{let n=await c(t,`utf-8`),a=JSON.parse(n);if(le(a)){let n=o(e,i(t)),s=a.name??n;r.set(s,n)}}catch{}return r.size===0?null:{projects:r}}async function ue(e){let t=a(e,`pnpm-workspace.yaml`),n;try{n=await c(t,`utf-8`)}catch{return null}let r=se(n);return r.length===0?null:v(e,r)}function de(e){let t=e.workspaces;if(!t)return[];if(Array.isArray(t))return t.filter(e=>typeof e==`string`);if(typeof t==`object`&&t){let e=t;if(Array.isArray(e.packages))return e.packages.filter(e=>typeof e==`string`)}return[]}async function fe(e){let t=a(e,`package.json`),n;try{n=await c(t,`utf-8`)}catch{return null}let r=de(JSON.parse(n));return r.length===0?null:v(e,r)}async function pe(e){let t=a(e,`lerna.json`),n;try{n=await c(t,`utf-8`)}catch{return null}let r=JSON.parse(n);if(r.useWorkspaces)return null;let i=r.packages??[`packages/*`];return i.length===0?null:v(e,i)}async function me(e){let t=a(e,`nx.json`);try{await c(t,`utf-8`)}catch{return null}let n=await l([`**/project.json`],{cwd:e,absolute:!0,ignore:[`node_modules/**`]}),r=new Map;for(let t of n){let n=i(t),s=o(e,n);if(s===``)continue;let l=a(n,`package.json`);try{let e=await c(l,`utf-8`),t=JSON.parse(e);if(le(t)){let e=t.name??s;r.set(e,s)}}catch{}}return r.size===0?null:{projects:r}}async function he(e){try{return await c(a(e,`pnpm-workspace.yaml`),`utf-8`),!0}catch{return!1}}async function ge(e){let t=await ce(e);if(t)return t;let n=await ue(e);if(n)return n;if(!await he(e)){let t=await fe(e);if(t)return t}return await me(e)||pe(e)}async function _e(e){let t=a(e,`package.json`),n={};try{let e=await c(t,`utf-8`);n=JSON.parse(e)}catch{}let r={...n.dependencies,...n.devDependencies},i=ve(r[`@nestjs/core`]),o=ye(r),s=be(r);return{name:n.name??`unknown`,nestVersion:i,orm:o,framework:s,moduleCount:0,fileCount:0}}function ve(e){return e?e.replace(/[\^~>=<]/g,``):null}function ye(e){return e[`@prisma/client`]?`prisma`:e.typeorm?`typeorm`:e[`@mikro-orm/core`]?`mikro-orm`:e.sequelize?`sequelize`:e.mongoose?`mongoose`:e[`drizzle-orm`]?`drizzle`:null}function be(e){return e[`@nestjs/platform-fastify`]?`fastify`:e[`@nestjs/platform-express`]||e[`@nestjs/core`]?`express`:null}const y={include:[`**/*.ts`],exclude:`**/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,*.config.ts,*.config.js,*.config.mjs,*.config.cjs,*.config.mts,*.config.cts`.split(`,`)},xe=[`nestjs-doctor.config.json`,`.nestjs-doctor.json`];async function b(e,t){if(t)return x(t);for(let t of xe)try{return await x(a(e,t))}catch{}try{let t=await c(a(e,`package.json`),`utf-8`),n=JSON.parse(t);if(n[`nestjs-doctor`]&&typeof n[`nestjs-doctor`]==`object`)return S(n[`nestjs-doctor`])}catch{}return{...y}}async function x(e){let t=await c(e,`utf-8`);return S(JSON.parse(t))}function S(e){return{...y,...e,exclude:[...y.exclude??[],...e.exclude??[]]}}async function Se(e,t){try{return await b(e)}catch{return t}}const Ce=[/Repository$/,/\.repository$/,/\.entity$/,/\.schema$/,/\.guard$/,/\.interceptor$/,/\.pipe$/,/\.filter$/,/\.strategy$/],we={meta:{id:`architecture/no-barrel-export-internals`,category:`architecture`,severity:`info`,description:`Don't re-export internal implementation details from barrel files`,help:`Only export the module's public API (services, DTOs, interfaces) from index.ts files.`},check(e){if(e.filePath.endsWith(`/index.ts`))for(let t of e.sourceFile.getExportDeclarations()){let n=t.getModuleSpecifierValue();if(n){Ce.some(e=>e.test(n))&&e.report({filePath:e.filePath,message:`Barrel file re-exports internal module '${n}'.`,help:this.meta.help,line:t.getStartLineNumber(),column:1});for(let n of t.getNamedExports()){let t=n.getName();(t.endsWith(`Repository`)||t.endsWith(`Entity`)||t.endsWith(`Schema`))&&e.report({filePath:e.filePath,message:`Barrel file re-exports internal type '${t}'.`,help:this.meta.help,line:n.getStartLineNumber(),column:1})}}}}},C=new Set([`Get`,`Post`,`Put`,`Patch`,`Delete`,`Head`,`Options`,`All`]);function w(e,t){return e.getDecorator(t)!==void 0}function T(e){return w(e,`Controller`)}function E(e){return w(e,`Injectable`)}function D(e){return w(e,`Injectable`)||w(e,`Controller`)||w(e,`Resolver`)||w(e,`WebSocketGateway`)}function O(e){return w(e,`Module`)}function k(e){return e.getDecorators().some(e=>C.has(e.getName()))}const Te=new Set([`TsRestHandler`,`GrpcMethod`,`GrpcStreamMethod`]);function Ee(e){return e.getDecorators().some(e=>Te.has(e.getName()))}const De={meta:{id:`architecture/no-business-logic-in-controllers`,category:`architecture`,severity:`error`,description:`Controllers should only handle HTTP concerns — move business logic to services`,help:`Extract branches, loops, and complex calculations into a service method.`},check(e){for(let t of e.sourceFile.getClasses())if(T(t))for(let n of t.getMethods()){if(!n.getDecorators().some(e=>C.has(e.getName())))continue;let t=n.getBody();if(!t)continue;let r=t.getDescendantsOfKind(f.IfStatement),i=t.getDescendantsOfKind(f.ForStatement),a=t.getDescendantsOfKind(f.ForInStatement),o=t.getDescendantsOfKind(f.ForOfStatement),s=t.getDescendantsOfKind(f.WhileStatement),c=t.getDescendantsOfKind(f.SwitchStatement),l=i.length+a.length+o.length+s.length;(r.length>1||l>0||c.length>0)&&e.report({filePath:e.filePath,message:`Controller method '${n.getName()}' contains business logic (${r.length} if, ${l} loops, ${c.length} switch). Move to a service.`,help:this.meta.help,line:n.getStartLineNumber(),column:1});let u=t.getDescendantsOfKind(f.CallExpression).filter(e=>{let t=e.getExpression();if(t.getKind()===f.PropertyAccessExpression){let e=t.asKind(f.PropertyAccessExpression)?.getName();return e===`map`||e===`filter`||e===`reduce`||e===`sort`||e===`flatMap`}return!1});u.length>1&&e.report({filePath:e.filePath,message:`Controller method '${n.getName()}' contains data transformation logic (${u.length} array operations). Move to a service.`,help:this.meta.help,line:n.getStartLineNumber(),column:1})}}};function Oe(e){let t=new Map;try{let n=p.findConfigFile(e,p.sys.fileExists,`tsconfig.json`);if(!n)return t;let{config:r,error:a}=p.readConfigFile(n,p.sys.readFile);if(a||!r)return t;let o=i(n),c=p.parseJsonConfigFileContent(r,p.sys,o),l=c.options.paths;if(!l)return t;let u=c.options.baseUrl??o;for(let[e,n]of Object.entries(l)){let r=n.map(e=>s(u,e));t.set(e,r)}}catch{return t}return t}function ke(e,t){for(let[n,r]of t){if(r.length===0)continue;let t=n.indexOf(`*`);if(t===-1){if(e===n)return r[0];continue}let i=n.slice(0,t),a=n.slice(t+1);if(e.startsWith(i)&&e.endsWith(a)&&e.length>=i.length+a.length){let t=e.slice(i.length,e.length-a.length),n=r[0],o=n.indexOf(`*`);return o===-1?n:n.slice(0,o)+t+n.slice(o+1)}}}const Ae=/=>\s*(\w+)/,A=/\.js$/;function j(e,t,n){let r=[];for(let i of e.getClasses()){let e=i.getDecorator(`Module`);if(!e)continue;let a=i.getName()??`AnonymousModule`,o=e.getArguments()[0],s={name:a,filePath:t,classDeclaration:i,imports:[],exports:[],providers:[],controllers:[]};if(o&&o.getKind()===f.ObjectLiteralExpression){let e=o.asKind(f.ObjectLiteralExpression);e&&(s.imports=N(e,`imports`,n),s.exports=N(e,`exports`,n),s.providers=N(e,`providers`,n),s.controllers=N(e,`controllers`,n))}r.push(s)}return r}function M(e,t,n=new Map){let r=new Map,i=new Map;for(let i of t){let t=e.getSourceFile(i);if(t)for(let e of j(t,i,n))r.set(e.name,e)}for(let[e,t]of r){let n=new Set;for(let e of t.imports)r.has(e)&&n.add(e);i.set(e,n)}let a=new Map;for(let e of r.values())for(let t of e.providers)a.set(t,e);return{modules:r,edges:i,providerToModule:a}}const je=new Set([`forRoot`,`forRootAsync`,`forFeature`,`forFeatureAsync`,`forChild`,`forChildAsync`,`register`,`registerAsync`]);function N(e,t,n){let r=e.getProperty(t);if(!r)return[];let i=r.asKind(f.PropertyAssignment);if(!i)return[];let a=i.getInitializer();return a?P(a,e.getSourceFile(),0,n):[]}function P(e,t,n,r){if(n>5)return[];let i=e.getKind();if(i===f.ArrayLiteralExpression){let i=e.asKindOrThrow(f.ArrayLiteralExpression),a=[];for(let e of i.getElements())a.push(...Me(e,t,n,r));return a}return i===f.CallExpression?F(e.asKindOrThrow(f.CallExpression),t,n,r):i===f.Identifier?R(e.getText(),t,n+1,r):[]}function Me(e,t,n,r){let i=e.getText();if(i.startsWith(`forwardRef`)){let e=i.match(Ae);return e?[e[1]]:[i]}let a=e.getKind();return a===f.SpreadElement?P(e.asKindOrThrow(f.SpreadElement).getExpression(),t,n,r):a===f.CallExpression?F(e.asKindOrThrow(f.CallExpression),t,n,r):a===f.PropertyAccessExpression?[e.asKindOrThrow(f.PropertyAccessExpression).getExpression().getText()]:(f.Identifier,[i])}function F(e,t,n,r){let i=e.getExpression();if(i.getKind()===f.PropertyAccessExpression){let a=i.asKindOrThrow(f.PropertyAccessExpression),o=a.getName();if(o===`concat`){let i=P(a.getExpression(),t,n,r),o=[];for(let i of e.getArguments())o.push(...P(i,t,n,r));return[...i,...o]}return je.has(o),[a.getExpression().getText()]}return i.getKind()===f.Identifier?Pe(i.getText(),t,n+1,r):[]}function I(e,t,n){if(!e.startsWith(`.`)){let r=ke(e,n);if(!r)return;let i=t.getProject(),a=[`${r}.ts`,`${r}/index.ts`,r,r.replace(A,`.ts`)];for(let e of a){let t=i.getSourceFile(e);if(t)return t}return}let r=s(i(t.getFilePath()),e),a=t.getProject(),o=[`${r}.ts`,`${r}/index.ts`,r,r.replace(A,`.ts`)];for(let e of o){let t=a.getSourceFile(e);if(t)return t}}function L(e,t,n){for(let r of t.getImportDeclarations())for(let i of r.getNamedImports())if((i.getAliasNode()?i.getAliasNode().getText():i.getName())===e){let e=I(r.getModuleSpecifierValue(),t,n);return e?{sourceFile:e,localName:i.getName()}:void 0}for(let r of t.getExportDeclarations())if(r.getModuleSpecifierValue()){for(let i of r.getNamedExports())if((i.getAliasNode()?i.getAliasNode().getText():i.getName())===e){let e=I(r.getModuleSpecifierValue(),t,n);return e?{sourceFile:e,localName:i.getName()}:void 0}}}function R(e,t,n,r){if(n>5)return[];for(let i of t.getStatements()){if(i.getKind()!==f.VariableStatement)continue;let a=i.asKindOrThrow(f.VariableStatement);for(let i of a.getDeclarations())if(i.getName()===e){let e=i.getInitializer();if(e)return P(e,t,n,r)}}let i=L(e,t,r);return i?R(i.localName,i.sourceFile,n+1,r):[]}function Ne(e,t,n,r){for(let i of t.getStatements()){if(i.getKind()!==f.VariableStatement)continue;let a=i.asKindOrThrow(f.VariableStatement);for(let i of a.getDeclarations()){if(i.getName()!==e)continue;let a=i.getInitializer();if(!a||a.getKind()!==f.ArrowFunction)continue;let o=a.asKindOrThrow(f.ArrowFunction).getBody();if(o.getKind()!==f.Block)return P(o,t,n,r);let s=[];for(let e of o.getDescendantsOfKind(f.ReturnStatement)){let i=e.getExpression();i&&s.push(...P(i,t,n,r))}return s}}}function Pe(e,t,n,r){if(n>5)return[];for(let i of t.getStatements()){if(i.getKind()!==f.FunctionDeclaration)continue;let a=i.asKindOrThrow(f.FunctionDeclaration);if(a.getName()!==e)continue;let o=[];for(let e of a.getDescendantsOfKind(f.ReturnStatement)){let i=e.getExpression();i&&o.push(...P(i,t,n,r))}return o}let i=Ne(e,t,n,r);if(i)return i;let a=L(e,t,r);return a?Pe(a.localName,a.sourceFile,n+1,r):[]}function Fe(e,t,n,r=new Map){for(let[t,r]of e.modules)if(r.filePath===n){e.modules.delete(t),e.edges.delete(t);for(let t of r.providers)e.providerToModule.get(t)===r&&e.providerToModule.delete(t);for(let n of e.edges.values())n.delete(t)}let i=t.getSourceFile(n);if(!i)return;let a=j(i,n,r);for(let t of a)e.modules.set(t.name,t);for(let t of a){let n=new Set;for(let r of t.imports)e.modules.has(r)&&n.add(r);e.edges.set(t.name,n);for(let n of t.providers)e.providerToModule.set(n,t)}for(let[t,r]of e.modules){if(r.filePath===n)continue;let i=new Set;for(let t of r.imports)e.modules.has(t)&&i.add(t);e.edges.set(t,i)}}function Ie(e){let t=[],n=new Set,r=new Set;function i(a,o){n.add(a),r.add(a);let s=e.edges.get(a)??new Set;for(let e of s)if(!n.has(e))i(e,[...o,e]);else if(r.has(e)){let n=o.indexOf(e);n===-1?t.push([...o,e]):t.push(o.slice(n))}r.delete(a)}for(let t of e.modules.keys())n.has(t)||i(t,[t]);return t}function Le(e,t,n,r,i,a){let o=[];for(let i of e.providers){let e=n.get(i);if(e)for(let n of e.dependencies){let e=r.get(n);e&&e.name===t.name&&o.push({consumer:i,dependency:n})}}for(let n of e.controllers)for(let e of a){let a=i.getSourceFile(e);if(a)for(let e of a.getClasses()){if(e.getName()!==n)continue;let i=e.getConstructors()[0];if(i)for(let e of i.getParameters()){let i=e.getTypeNode(),a=i?i.getText():e.getType().getText(),s=a.split(`.`).pop()?.split(`<`)[0]??a,c=r.get(s);c&&c.name===t.name&&o.push({consumer:n,dependency:s})}}}return o}const Re=`Break the cycle by extracting shared logic into a separate module or using forwardRef().`;function ze(e,t){let{moduleGraph:n,providers:r,project:i,files:a}=t,o=[],s;for(let t=0;t<e.length;t++){let c=e[t],l=e[(t+1)%e.length],u=n.modules.get(c),d=n.modules.get(l);if(!(u&&d))continue;let f=Le(u,d,r,n.providerToModule,i,a);if(f.length===0)continue;let p=new Map;for(let e of f){let t=p.get(e.consumer);t?t.push(e.dependency):p.set(e.consumer,[e.dependency])}let m=[];for(let[e,t]of p){let n=t.map(e=>`${e} (from ${l})`).join(`, `);m.push(`${e} (in ${c}) injects ${n}`)}let h=`${c} -> ${l}: ${m.join(`; `)}`;o.push(h),(!s||f.length<s.count)&&(s={description:`${c} -> ${l}`,count:f.length})}if(o.length===0)return Re;let c=o.join(`
|
|
3
|
+
`);if(s){let e=s.count===1?`dependency`:`dependencies`,t=s.description.split(` -> `)[0],o=s.description.split(` -> `)[1],l=n.modules.get(t),u=n.modules.get(o);if(l&&u){let t=Le(l,u,r,n.providerToModule,i,a),o=[...new Set(t.map(e=>e.dependency))].join(`, `);c+=`\nConsider extracting ${o} into a shared module — it would break the ${s.description} edge (${s.count} ${e}).`}}return c}const Be={meta:{id:`architecture/no-circular-module-deps`,category:`architecture`,severity:`error`,description:`Module import graph must not contain circular dependencies`,help:Re,scope:`project`},check(e){let t=Ie(e.moduleGraph);for(let n of t){let t=n.join(` -> `),r=e.moduleGraph.modules.get(n[0]),i=ze(n,e);e.report({filePath:r?.filePath??`unknown`,message:`Circular module dependency detected: ${t}`,help:i,line:r?.classDeclaration.getStartLineNumber()??1,column:1})}}},Ve=[`Service`,`Repository`,`Gateway`,`Resolver`],He=[`Guard`,`Interceptor`,`Pipe`,`Filter`];function Ue(e){return typeof e==`object`&&!!e}function We(e){if(!Ue(e))return new Set;let t=e.excludeClasses;if(Array.isArray(t))return new Set(t.filter(e=>typeof e==`string`));let n=e.options;if(!Ue(n))return new Set;let r=n.excludeClasses;return Array.isArray(r)?new Set(r.filter(e=>typeof e==`string`)):new Set}const Ge={meta:{id:`architecture/no-manual-instantiation`,category:`architecture`,severity:`error`,description:`Do not manually instantiate @Injectable classes — use NestJS dependency injection`,help:`Register the class as a provider in a module and inject it via the constructor.`},check(e){let t=We(e.config?.rules?.[this.meta.id]),n=e.sourceFile.getDescendantsOfKind(f.NewExpression);for(let r of n){let n=r.getExpression().getText(),i=n.split(`.`).pop()??n;if(t.has(n)||t.has(i))continue;let a=Ve.some(e=>n.endsWith(e)),o=He.some(e=>n.endsWith(e));if(a||o){if(o){if(r.getFirstAncestorByKind(f.Decorator))continue;let e=r.getFirstAncestorByKind(f.MethodDeclaration),t=r.getFirstAncestorByKind(f.Constructor);if(!(e||t))continue}e.report({filePath:e.filePath,message:`Manual instantiation of '${n}' detected. Use dependency injection instead.`,help:this.meta.help,line:r.getStartLineNumber(),column:1})}}}},Ke=/\.(\w+)$/,qe=/^(\w+)</,Je=new Set([`PrismaService`,`PrismaClient`,`EntityManager`,`DataSource`,`Repository`,`Connection`,`MongooseModel`,`InjectModel`,`InjectRepository`,`MikroORM`,`DrizzleService`]),Ye={meta:{id:`architecture/no-orm-in-controllers`,category:`architecture`,severity:`error`,description:`Controllers must not inject ORM services directly — use a service layer`,help:`Inject a service that wraps the ORM instead of using the ORM directly in controllers.`},check(e){for(let t of e.sourceFile.getClasses()){if(!T(t))continue;let n=t.getConstructors()[0];if(n){for(let t of n.getParameters()){let n=Xe(t.getType().getText());if(Je.has(n)){let r=t.getNameNode();e.report({filePath:e.filePath,message:`Controller injects ORM type '${n}' directly. Use a service layer.`,help:this.meta.help,line:r.getStartLineNumber(),column:r.getStartLinePos()+1})}}for(let n of t.getConstructors()[0]?.getParameters()??[])for(let t of n.getDecorators()){let n=t.getName();(n===`InjectRepository`||n===`InjectModel`)&&e.report({filePath:e.filePath,message:`Controller uses @${n}() decorator. Move data access to a service.`,help:this.meta.help,line:t.getStartLineNumber(),column:1})}}}}};function Xe(e){let t=e.match(Ke);if(t)return t[1];let n=e.match(qe);return n?n[1]:e}const Ze=/\.(\w+)$/,Qe=/^(\w+)</,$e=new Set([`PrismaService`,`PrismaClient`,`EntityManager`,`DataSource`,`Connection`,`MikroORM`]),et={meta:{id:`architecture/no-orm-in-services`,category:`architecture`,severity:`info`,description:`Services should use repository abstractions instead of ORM directly`,help:`Create a repository class that wraps ORM calls and inject that instead. Note: If your project follows the official NestJS Prisma recipe (injecting PrismaService directly), you can disable this rule.`},check(e){for(let t of e.sourceFile.getClasses()){if(!E(t))continue;let n=t.getName()??``;if(n.endsWith(`Repository`)||n.endsWith(`Repo`))continue;let r=t.getConstructors()[0];if(r)for(let t of r.getParameters()){let n=tt(t.getType().getText());if($e.has(n)){let r=t.getNameNode();e.report({filePath:e.filePath,message:`Service injects ORM type '${n}' directly. Consider using a repository abstraction.`,help:this.meta.help,line:r.getStartLineNumber(),column:r.getStartLinePos()+1})}for(let n of t.getDecorators()){let t=n.getName();(t===`InjectRepository`||t===`InjectModel`)&&e.report({filePath:e.filePath,message:`Service uses @${t}() directly. Consider wrapping in a repository class.`,help:this.meta.help,line:n.getStartLineNumber(),column:1})}}}}};function tt(e){let t=e.match(Ze);if(t)return t[1];let n=e.match(Qe);return n?n[1]:e}const nt=/\.(\w+)$/,rt=/^(\w+)</,it=[/Repository$/,/Repo$/],at={meta:{id:`architecture/no-repository-in-controllers`,category:`architecture`,severity:`error`,description:`Controllers must not inject repositories directly — use the service layer`,help:`Move database access to a service and inject the service into the controller instead.`},check(e){for(let t of e.sourceFile.getClasses()){if(!T(t))continue;let n=t.getConstructors()[0];if(n){for(let t of n.getParameters()){let n=ot(t.getType().getText());if(it.some(e=>e.test(n))){let r=t.getNameNode();e.report({filePath:e.filePath,message:`Controller injects repository '${n}' directly. Use a service layer instead.`,help:this.meta.help,line:r.getStartLineNumber(),column:r.getStartLinePos()+1})}}for(let t of e.sourceFile.getImportDeclarations()){let n=t.getModuleSpecifierValue();(n.includes(`/repositories/`)||n.includes(`/repositories`))&&e.report({filePath:e.filePath,message:`Controller imports from repository path '${n}'.`,help:this.meta.help,line:t.getStartLineNumber(),column:1})}}}}};function ot(e){let t=e.match(nt);if(t)return t[1];let n=e.match(rt);return n?n[1]:e}const st={meta:{id:`architecture/no-service-locator`,category:`architecture`,severity:`warning`,description:`Avoid using ModuleRef.get() or ModuleRef.resolve() — prefer explicit constructor injection`,help:`Replace ModuleRef.get()/resolve() with constructor injection for explicit, testable dependencies.`},check(e){let t=e.sourceFile.getDescendantsOfKind(f.CallExpression);for(let n of t){let t=n.getExpression();if(t.getKind()!==f.PropertyAccessExpression)continue;let r=t.asKind(f.PropertyAccessExpression);if(!r)continue;let i=r.getName();if(i!==`get`&&i!==`resolve`)continue;let a=r.getExpression().getText();(a===`moduleRef`||a===`this.moduleRef`)&&e.report({filePath:e.filePath,message:`Service locator pattern: '${a}.${i}()' hides dependencies. Use constructor injection instead.`,help:this.meta.help,line:n.getStartLineNumber(),column:1})}}},ct={meta:{id:`architecture/prefer-constructor-injection`,category:`architecture`,severity:`warning`,description:`Prefer constructor injection over @Inject() property injection`,help:`Move the dependency to a constructor parameter instead of using property injection.`},check(e){for(let t of e.sourceFile.getClasses())if(D(t))for(let n of t.getProperties())n.getDecorator(`Inject`)&&e.report({filePath:e.filePath,message:`Property '${n.getName()}' uses @Inject() decorator. Prefer constructor injection.`,help:this.meta.help,line:n.getStartLineNumber(),column:1})}},lt=[`/repositories/`,`/entities/`,`/dto/`,`/guards/`,`/interceptors/`,`/pipes/`,`/strategies/`],ut={meta:{id:`architecture/require-module-boundaries`,category:`architecture`,severity:`info`,description:`Avoid deep imports into other feature modules' internals`,help:`Import from the module's public API (barrel export) instead of reaching into its internals.`},check(e){for(let t of e.sourceFile.getImportDeclarations()){let n=t.getModuleSpecifierValue();n.startsWith(`.`)&&n.includes(`../`)&<.some(e=>n.includes(e))&&e.report({filePath:e.filePath,message:`Import '${n}' reaches into another module's internals.`,help:this.meta.help,line:t.getStartLineNumber(),column:1})}}},dt={meta:{id:`correctness/factory-inject-matches-params`,category:`correctness`,severity:`error`,description:`useFactory inject array length must match the factory function parameter count`,help:`Ensure the 'inject' array has one entry per factory function parameter.`},check(e){for(let t of e.sourceFile.getClasses()){if(!O(t))continue;let n=t.getDecorator(`Module`);if(!n)continue;let r=n.getArguments()[0];if(!r||r.getKind()!==f.ObjectLiteralExpression)continue;let i=r.asKind(f.ObjectLiteralExpression);if(!i)continue;let a=i.getProperty(`providers`);if(!a)continue;let o=a.getChildrenOfKind(f.ArrayLiteralExpression)[0];if(o)for(let t of o.getElements()){if(t.getKind()!==f.ObjectLiteralExpression)continue;let n=t.asKind(f.ObjectLiteralExpression);if(!n)continue;let r=n.getProperty(`useFactory`),i=n.getProperty(`inject`);if(!(r&&i))continue;let a=i.getChildrenOfKind(f.ArrayLiteralExpression)[0];if(!a)continue;let o=a.getElements().length,s,c=r.asKind(f.MethodDeclaration);if(c)s=c.getParameters().length;else{let e=r.asKind(f.PropertyAssignment);if(!e)continue;let t=e.getInitializer();if(!t)continue;t.getKind()===f.ArrowFunction?s=t.asKind(f.ArrowFunction)?.getParameters().length:t.getKind()===f.FunctionExpression&&(s=t.asKind(f.FunctionExpression)?.getParameters().length)}s!==void 0&&o!==s&&e.report({filePath:e.filePath,message:`Factory has ${s} parameter(s) but inject array has ${o} element(s).`,help:this.meta.help,line:t.getStartLineNumber(),column:1})}}}},ft=[`Guard`,`Interceptor`,`Filter`,`Pipe`,`Middleware`,`Strategy`,`Subscriber`,`Listener`,`Processor`,`Consumer`,`Worker`,`Scheduler`,`Cron`,`HealthIndicator`],pt={meta:{id:`correctness/injectable-must-be-provided`,category:`correctness`,severity:`info`,description:`@Injectable() classes should be registered in at least one module's providers array`,help:`Add this class to a module's providers array, or remove the @Injectable() decorator if unused.`,scope:`project`},check(e){let t=new Set;for(let n of e.moduleGraph.modules.values()){for(let e of n.providers)t.add(e);for(let e of n.controllers)t.add(e)}for(let n of e.files){let r=e.project.getSourceFile(n);if(r)for(let e of r.getClasses()){if(!O(e))continue;let n=e.getDecorator(`Module`);if(!n)continue;let r=n.getArguments()[0];if(!r||r.getKind()!==f.ObjectLiteralExpression)continue;let i=r.asKind(f.ObjectLiteralExpression);if(!i)continue;let a=i.getProperty(`providers`);if(!a)continue;let o=a.getChildrenOfKind(f.ArrayLiteralExpression)[0];if(o)for(let e of o.getElements()){if(e.getKind()!==f.ObjectLiteralExpression)continue;let n=e.asKind(f.ObjectLiteralExpression);if(n)for(let e of[`useClass`,`useExisting`]){let r=n.getProperty(e);if(!r)continue;let i=r.asKind(f.PropertyAssignment);if(!i)continue;let a=i.getInitializer();a&&t.add(a.getText())}}}}for(let n of e.files){if(n.includes(`.spec.`)||n.includes(`.test.`)||n.includes(`__test__`)||n.includes(`__tests__`))continue;let r=e.project.getSourceFile(n);if(r)for(let i of r.getClasses()){if(!i.getDecorator(`Injectable`))continue;let r=i.getName();r&&(ft.some(e=>r.endsWith(e))||t.has(r)||e.report({filePath:n,message:`@Injectable() class '${r}' is not registered in any module's providers array.`,help:this.meta.help,line:i.getStartLineNumber(),column:1}))}}}};function mt(e){return e.getDescendantsOfKind(f.ReturnStatement).some(e=>{let t=e.getExpression();return!t||t.getKind()!==f.NewExpression?!1:t.asKindOrThrow(f.NewExpression).getExpression().getText()===`Promise`})}const ht={meta:{id:`correctness/no-async-without-await`,category:`correctness`,severity:`warning`,description:`Async functions/methods should contain at least one await expression`,help:`Either add an await expression or remove the async keyword. HTTP handlers with route decorators are exempted, as async is conventional for controller methods.`},check(e){for(let t of e.sourceFile.getClasses())for(let n of t.getMethods()){if(!n.isAsync()||T(t)&&k(n)||Ee(n))continue;let r=n.getBody();if(r&&r.getDescendantsOfKind(f.AwaitExpression).filter(e=>{let t=e.getParent();for(;t&&t!==r;){if(t.getKind()===f.ArrowFunction||t.getKind()===f.FunctionExpression||t.getKind()===f.FunctionDeclaration)return!1;t=t.getParent()}return!0}).length===0){let t=n.getName();mt(r)?e.report({filePath:e.filePath,message:`Async method '${t}()' returns a Promise directly — remove the async keyword.`,help:`The async keyword is unnecessary when you are already constructing a Promise manually. Remove async to avoid double-wrapping.`,line:n.getStartLineNumber(),column:1}):e.report({filePath:e.filePath,message:`Async method '${t}()' has no await expression.`,help:this.meta.help,line:n.getStartLineNumber(),column:1})}}for(let t of e.sourceFile.getFunctions()){if(!t.isAsync())continue;let n=t.getBody();if(n&&n.getDescendantsOfKind(f.AwaitExpression).filter(e=>{let t=e.getParent();for(;t&&t!==n;){if(t.getKind()===f.ArrowFunction||t.getKind()===f.FunctionExpression||t.getKind()===f.FunctionDeclaration)return!1;t=t.getParent()}return!0}).length===0){let r=t.getName()??`anonymous`;mt(n)?e.report({filePath:e.filePath,message:`Async function '${r}()' returns a Promise directly — remove the async keyword.`,help:`The async keyword is unnecessary when you are already constructing a Promise manually. Remove async to avoid double-wrapping.`,line:t.getStartLineNumber(),column:1}):e.report({filePath:e.filePath,message:`Async function '${r}()' has no await expression.`,help:this.meta.help,line:t.getStartLineNumber(),column:1})}}}},gt=new Set([`ApiResponse`,`ApiQuery`,`ApiParam`,`ApiHeader`,`ApiSecurity`,`SetMetadata`,`Roles`,`Header`,`Throttle`]),_t={meta:{id:`correctness/no-duplicate-decorators`,category:`correctness`,severity:`warning`,description:`Same decorator should not appear twice on a single target`,help:`Remove the duplicate decorator — it was likely copy-pasted by mistake.`},check(e){for(let t of e.sourceFile.getClasses()){z(t.getDecorators(),e,this.meta.help);for(let n of t.getMethods())z(n.getDecorators(),e,this.meta.help);for(let n of t.getProperties())z(n.getDecorators(),e,this.meta.help);for(let n of t.getConstructors())for(let t of n.getParameters())z(t.getDecorators(),e,this.meta.help)}}};function z(e,t,n){let r=new Set;for(let i of e){let e=i.getName();gt.has(e)||(r.has(e)?t.report({filePath:t.filePath,message:`Duplicate @${e}() decorator on the same target.`,help:n,line:i.getStartLineNumber(),column:1}):r.add(e))}}const vt=[`providers`,`controllers`,`imports`,`exports`],yt={meta:{id:`correctness/no-duplicate-module-metadata`,category:`correctness`,severity:`warning`,description:`Same identifier should not appear twice in a module metadata array`,help:`Remove the duplicate entry from the module metadata.`},check(e){for(let t of e.sourceFile.getClasses()){if(!O(t))continue;let n=t.getDecorator(`Module`);if(!n)continue;let r=n.getArguments()[0];if(!r||r.getKind()!==f.ObjectLiteralExpression)continue;let i=r.asKind(f.ObjectLiteralExpression);if(i)for(let t of vt){let n=i.getProperty(t);if(!n)continue;let r=n.getChildrenOfKind(f.ArrayLiteralExpression)[0];if(!r)continue;let a=new Set;for(let n of r.getElements()){let r=n.getText();a.has(r)?e.report({filePath:e.filePath,message:`Duplicate '${r}' in @Module() ${t} array.`,help:this.meta.help,line:n.getStartLineNumber(),column:1}):a.add(r)}}}}},bt={meta:{id:`correctness/no-duplicate-routes`,category:`correctness`,severity:`error`,description:`Same HTTP method + route path + version should not appear twice in a single controller`,help:`Remove or rename one of the duplicate route handlers.`},check(e){for(let t of e.sourceFile.getClasses()){if(!T(t))continue;let n=new Map;for(let r of t.getMethods())for(let t of r.getDecorators()){let i=t.getName();if(!C.has(i))continue;let a=t.getArguments(),o=a.length>0?a[0].getText():`""`,s=r.getDecorator(`Version`),c=`${i}:${o}:${s?s.getArguments()[0]?.getText()??``:``}`,l=n.get(c);l?e.report({filePath:e.filePath,message:`Duplicate route: @${i}(${o}) is already defined in '${l}()'.`,help:this.meta.help,line:r.getStartLineNumber(),column:1}):n.set(c,r.getName())}}}},xt={meta:{id:`correctness/no-empty-handlers`,category:`correctness`,severity:`info`,description:`Controller HTTP handlers should not have empty bodies`,help:`Add implementation to the handler method or remove it if unnecessary.`},check(e){for(let t of e.sourceFile.getClasses())if(T(t))for(let n of t.getMethods()){if(!n.getDecorators().some(e=>C.has(e.getName())))continue;let t=n.getBody();if(!t)continue;let r=t.asKind(f.Block);r&&r.getStatements().length===0&&e.report({filePath:e.filePath,message:`Handler '${n.getName()}()' has an empty body.`,help:this.meta.help,line:n.getStartLineNumber(),column:1})}}};function St(e){let t=e.getReturnType().getText();return t.startsWith(`Promise<`)||t===`Promise`?!0:t===`any`||t===`error`?`unknown`:!1}const Ct=new Set([`save`,`create`,`insert`,`update`,`delete`,`remove`,`send`,`emit`,`publish`,`dispatch`,`execute`,`fetch`,`load`,`upload`,`download`,`process`]),wt={meta:{id:`correctness/no-fire-and-forget-async`,category:`correctness`,severity:`warning`,description:`Calling async functions without await leads to unhandled promise rejections`,help:`Add await before the async call, or use void with explicit error handling if fire-and-forget is intentional.`},check(e){for(let t of e.sourceFile.getClasses())for(let n of t.getMethods()){if(k(n))continue;let t=n.getBody();if(!t)continue;let r=t.getDescendantsOfKind(f.ExpressionStatement);for(let t of r){let r=t.getExpression();if(r.getKind()===f.VoidExpression||r.getKind()===f.AwaitExpression||r.getKind()!==f.CallExpression)continue;let i=r.asKind(f.CallExpression);if(!i)continue;let a=i.getExpression().getText().split(`.`).pop()??``,o=St(i);if(o!==!1){if(o===`unknown`){let e=a.toLowerCase();if(!(Ct.has(e)||[...Ct].some(t=>e.startsWith(t)&&e!==t)))continue}t.getFirstAncestorByKind(f.MethodDeclaration)===n&&e.report({filePath:e.filePath,message:`Async call '${a}()' is not awaited — unhandled rejections will crash the process.`,help:this.meta.help,line:t.getStartLineNumber(),column:1})}}}}},Tt={meta:{id:`correctness/no-missing-filter-catch`,category:`correctness`,severity:`error`,description:`Exception filter classes decorated with @Catch() must implement the catch() method`,help:`Add a catch(exception, host: ArgumentsHost) method to the filter class.`},check(e){for(let t of e.sourceFile.getClasses())w(t,`Catch`)&&(t.getExtends()||t.getMethods().some(e=>e.getName()===`catch`)||e.report({filePath:e.filePath,message:`Exception filter '${t.getName()}' has @Catch() but is missing the 'catch()' method.`,help:this.meta.help,line:t.getStartLineNumber(),column:1}))}},Et={meta:{id:`correctness/no-missing-guard-method`,category:`correctness`,severity:`error`,description:`Guard classes must implement the canActivate() method`,help:`Add a canActivate(context: ExecutionContext) method to the guard class. Note: This rule identifies guards by the 'Guard' class name suffix.`},check(e){for(let t of e.sourceFile.getClasses()){let n=t.getName()??``;n.endsWith(`Guard`)&&w(t,`Injectable`)&&(t.getExtends()||t.getMethods().some(e=>e.getName()===`canActivate`)||e.report({filePath:e.filePath,message:`Guard '${n}' is missing the 'canActivate()' method.`,help:this.meta.help,line:t.getStartLineNumber(),column:1}))}}},Dt={meta:{id:`correctness/no-missing-injectable`,category:`correctness`,severity:`error`,description:`Provider classes with constructor dependencies must have the @Injectable() decorator`,help:`Add @Injectable() to providers that inject constructor dependencies.`,scope:`project`},check(e){let t=new Set([...e.providers.values()].map(e=>e.name)),n=new Map;for(let t of e.files){let r=e.project.getSourceFile(t);if(r)for(let e of r.getClasses()){let r=e.getName();if(r){let i=n.get(r)??[];i.push({cls:e,filePath:t}),n.set(r,i)}}}for(let r of e.moduleGraph.modules.values())for(let i of r.providers){if(t.has(i))continue;let a=n.get(i);if(a)for(let{cls:t,filePath:n}of a){let a=(t.getConstructors()[0]?.getParameters().length??0)>0;!(t.getDecorator(`Injectable`)||t.getDecorator(`Resolver`)||t.getDecorator(`WebSocketGateway`))&&a&&e.report({filePath:n,message:`Class '${i}' is listed in '${r.name}' providers but is missing @Injectable() decorator.`,help:this.meta.help,line:t.getStartLineNumber(),column:1})}}}},Ot={meta:{id:`correctness/no-missing-interceptor-method`,category:`correctness`,severity:`error`,description:`Interceptor classes must implement the intercept() method`,help:`Add an intercept(context: ExecutionContext, next: CallHandler) method to the interceptor class.`},check(e){for(let t of e.sourceFile.getClasses()){let n=t.getName()??``;n.endsWith(`Interceptor`)&&w(t,`Injectable`)&&(t.getExtends()||t.getMethods().some(e=>e.getName()===`intercept`)||e.report({filePath:e.filePath,message:`Interceptor '${n}' is missing the 'intercept()' method.`,help:this.meta.help,line:t.getStartLineNumber(),column:1}))}}},kt={meta:{id:`correctness/no-missing-module-decorator`,category:`correctness`,severity:`warning`,description:`Classes named *Module should have a @Module() decorator`,help:`Add @Module({}) decorator to the class, or rename it if it is not a NestJS module.`},check(e){for(let t of e.sourceFile.getClasses()){let n=t.getName()??``;n.endsWith(`Module`)&&(n===`Module`||n===`DynamicModule`||w(t,`Module`)||e.report({filePath:e.filePath,message:`Class '${n}' is named like a module but is missing the @Module() decorator.`,help:this.meta.help,line:t.getStartLineNumber(),column:1}))}}},At={meta:{id:`correctness/no-missing-pipe-method`,category:`correctness`,severity:`error`,description:`Pipe classes must implement the transform() method`,help:`Add a transform(value: any, metadata: ArgumentMetadata) method to the pipe class.`},check(e){for(let t of e.sourceFile.getClasses()){let n=t.getName()??``;n.endsWith(`Pipe`)&&w(t,`Injectable`)&&(t.getExtends()||t.getMethods().some(e=>e.getName()===`transform`)||e.report({filePath:e.filePath,message:`Pipe '${n}' is missing the 'transform()' method.`,help:this.meta.help,line:t.getStartLineNumber(),column:1}))}}},jt=/:(\w+)/g,Mt={meta:{id:`correctness/param-decorator-matches-route`,category:`correctness`,severity:`error`,description:`@Param() decorator name must match a :param in the route path`,help:`Ensure the @Param('name') argument matches a ':name' segment in the route path (including controller prefix).`},check(e){for(let t of e.sourceFile.getClasses()){if(!T(t))continue;let n=t.getDecorator(`Controller`),r=``;if(n){let e=n.getArguments();if(e.length>0){let t=e[0];if(t.getKind()===f.ObjectLiteralExpression){let e=t.asKind(f.ObjectLiteralExpression);if(e){let t=e.getProperty(`path`);if(t){let e=t.asKind(f.PropertyAssignment);if(e){let t=e.getInitializer();t&&(r=t.getText().replace(/^['"`]|['"`]$/g,``))}}}}else r=t.getText().replace(/^['"`]|['"`]$/g,``)}}let i=new Set;for(let e of r.matchAll(jt))i.add(e[1]);for(let n of t.getMethods()){let t=``,r=!1;for(let e of n.getDecorators())if(C.has(e.getName())){r=!0;let n=e.getArguments();n.length>0&&(t=n[0].getText().replace(/^['"`]|['"`]$/g,``));break}if(!r)continue;let a=new Set;for(let e of t.matchAll(jt))a.add(e[1]);let o=new Set([...i,...a]);for(let t of n.getParameters())for(let n of t.getDecorators()){if(n.getName()!==`Param`)continue;let t=n.getArguments();if(t.length===0)continue;let r=t[0].getText().replace(/^['"`]|['"`]$/g,``);o.has(r)||e.report({filePath:e.filePath,message:`@Param('${r}') does not match any route parameter. Available: ${o.size>0?[...o].map(e=>`:${e}`).join(`, `):`(none)`}.`,help:this.meta.help,line:n.getStartLineNumber(),column:1})}}}}},Nt={meta:{id:`correctness/prefer-readonly-injection`,category:`correctness`,severity:`warning`,description:`Constructor DI parameters should be readonly to prevent accidental reassignment`,help:`Add the 'readonly' modifier to the constructor parameter.`},check(e){for(let t of e.sourceFile.getClasses()){if(!(E(t)||T(t)))continue;let n=t.getConstructors()[0];if(n){for(let t of n.getParameters())if((t.hasModifier(`private`)||t.hasModifier(`protected`)||t.hasModifier(`public`))&&!t.isReadonly()){let n=t.getNameNode();e.report({filePath:e.filePath,message:`Constructor parameter '${t.getName()}' should be readonly.`,help:this.meta.help,line:n.getStartLineNumber(),column:n.getStartLinePos()+1})}}}}},Pt={meta:{id:`correctness/require-inject-decorator`,category:`correctness`,severity:`error`,description:`Constructor parameters without type annotations must have @Inject() decorator for NestJS DI to resolve them`,help:`Add a type annotation or @Inject() decorator to the constructor parameter.`},check(e){for(let t of e.sourceFile.getClasses()){if(!D(t))continue;let n=t.getConstructors()[0];if(n)for(let r of n.getParameters()){let n=r.getTypeNode(),i=r.getDecorators().some(e=>e.getName()===`Inject`);n||i||e.report({filePath:e.filePath,message:`Constructor parameter '${r.getName()}' in '${t.getName()}' has no type annotation and no @Inject() decorator — NestJS cannot resolve it.`,help:this.meta.help,line:r.getStartLineNumber(),column:1})}}}},Ft={onModuleInit:`OnModuleInit`,onModuleDestroy:`OnModuleDestroy`,onApplicationBootstrap:`OnApplicationBootstrap`,onApplicationShutdown:`OnApplicationShutdown`,beforeApplicationShutdown:`BeforeApplicationShutdown`},It={meta:{id:`correctness/require-lifecycle-interface`,category:`correctness`,severity:`warning`,description:`Classes with lifecycle methods should implement the corresponding NestJS interface`,help:`Add 'implements OnModuleInit' (or the appropriate interface) to the class declaration.`},check(e){for(let t of e.sourceFile.getClasses()){let n=t.getImplements().map(e=>e.getText());for(let r of t.getMethods()){let i=r.getName(),a=Ft[i];a&&(n.some(e=>e===a||e.startsWith(`${a}<`))||e.report({filePath:e.filePath,message:`Class '${t.getName()}' has '${i}()' but does not implement '${a}'.`,help:this.meta.help,line:r.getStartLineNumber(),column:1}))}}}},Lt=/each\s*:\s*true/,Rt={meta:{id:`correctness/validate-nested-array-each`,category:`correctness`,severity:`warning`,description:`@ValidateNested() on array-typed properties must use { each: true }`,help:`Change @ValidateNested() to @ValidateNested({ each: true }) for array properties.`},check(e){for(let t of e.sourceFile.getClasses())for(let n of t.getProperties()){let t=n.getDecorators(),r=t.find(e=>e.getName()===`ValidateNested`);if(!r)continue;let i=zt(n),a=t.some(e=>e.getName()===`IsArray`);(i||a)&&(Bt(r)||e.report({filePath:e.filePath,message:`Property '${n.getName()}' is an array with @ValidateNested() but missing { each: true }.`,help:this.meta.help,line:r.getStartLineNumber(),column:1}))}}};function zt(e){let t=e.getTypeNode();if(!t)return!1;let n=t.getText().replace(/\s/g,``);return!!(n.endsWith(`[]`)||n.startsWith(`Array<`))}function Bt(e){let t=e.getArguments();if(t.length===0)return!1;let n=t[0];if(n.getKind()!==f.ObjectLiteralExpression)return!1;let r=n.getText();return Lt.test(r)}const Vt=new Set(`ValidateNested.IsString.IsNumber.IsBoolean.IsEmail.IsArray.IsEnum.IsNotEmpty.IsDefined.IsOptional.IsDate.IsObject.IsInt.IsPositive.IsNegative.IsUUID.IsUrl.IsISO8601.Matches.Min.Max.MinLength.MaxLength.ArrayMinSize.ArrayMaxSize.ArrayNotEmpty.IsIn.IsNotIn.Length.Contains.IsAlpha.IsAlphanumeric.IsDecimal.IsHexColor.IsJSON.IsPhoneNumber.IsIP.IsCreditCard.IsDateString.IsMilitaryTime.IsMongoId.IsPort.IsSemVer.IsStrongPassword`.split(`.`)),Ht=new Set([`string`,`number`,`boolean`,`Date`,`any`,`unknown`,`bigint`,`symbol`,`undefined`,`null`,`void`,`never`]),Ut=/\s/g,Wt=/\[\]$/,Gt=/^Array<(.+)>$/,Kt=/^["']/,qt=/^\d+$/;function B(e){let t=e.replace(Ut,``);if(t.includes(`|`))return t.split(`|`).every(e=>B(e));if(Ht.has(t)||Wt.test(t)&&B(t.replace(Wt,``)))return!0;let n=t.match(Gt);return!!(n&&B(n[1])||Kt.test(t)||qt.test(t))}const Jt={meta:{id:`correctness/validated-non-primitive-needs-type`,category:`correctness`,severity:`warning`,description:`DTO properties with class-validator decorators on non-primitive types must have @Type() from class-transformer`,help:`Add @Type(() => ClassName) from 'class-transformer' to ensure proper transformation.`},check(e){for(let t of e.sourceFile.getClasses())for(let n of t.getProperties()){let t=n.getDecorators();if(t.length===0||!t.some(e=>Vt.has(e.getName()))||t.some(e=>e.getName()===`Type`)||t.some(e=>e.getName()===`IsEnum`))continue;let r=n.getTypeNode();if(!r)continue;let i=r.getText();B(i)||e.report({filePath:e.filePath,message:`Property '${n.getName()}' has type '${i}' with class-validator decorators but is missing @Type() decorator.`,help:this.meta.help,line:n.getStartLineNumber(),column:1})}}},Yt=new Set([f.ForStatement,f.ForOfStatement,f.ForInStatement,f.WhileStatement,f.DoStatement]),Xt={meta:{id:`performance/no-blocking-constructor`,category:`performance`,severity:`warning`,description:`Constructors in Injectable/Controller classes should not contain heavy operations`,help:`Move heavy initialization logic to the onModuleInit() lifecycle method. Constructors cannot be async, so asynchronous work should always use lifecycle hooks.`},check(e){for(let t of e.sourceFile.getClasses()){if(!(w(t,`Injectable`)||w(t,`Controller`)))continue;let n=t.getConstructors()[0];if(!n)continue;let r=n.getBody();if(r){for(let i of r.getDescendants())if(Yt.has(i.getKind())){e.report({filePath:e.filePath,message:`Constructor in '${t.getName()}' contains blocking operation — use onModuleInit() instead.`,help:this.meta.help,line:n.getStartLineNumber(),column:1});break}}}}},Zt={meta:{id:`performance/no-dynamic-require`,category:`performance`,severity:`warning`,description:`Dynamic require() with variable arguments prevents bundler optimization`,help:`Use static import statements or dynamic import() with string literals.`},check(e){let t=e.sourceFile.getDescendantsOfKind(f.CallExpression);for(let n of t){if(n.getExpression().getText()!==`require`)continue;let t=n.getArguments();t.length!==0&&t[0].getKind()!==f.StringLiteral&&e.report({filePath:e.filePath,message:`Dynamic require() with non-literal argument prevents bundler optimization.`,help:this.meta.help,line:n.getStartLineNumber(),column:1})}}},Qt={meta:{id:`performance/no-orphan-modules`,category:`performance`,severity:`info`,description:`Module is never imported by any other module and may be dead code`,help:`Import this module in another module or remove it if it is unused.`,scope:`project`},check(e){let t=new Set;for(let n of e.moduleGraph.modules.values())for(let e of n.imports)t.add(e);for(let n of e.moduleGraph.modules.values())n.name!==`AppModule`&&(t.has(n.name)||e.report({filePath:n.filePath,message:`Module '${n.name}' is never imported by any other module.`,help:this.meta.help,line:n.classDeclaration.getStartLineNumber(),column:1}))}},$t={meta:{id:`performance/no-request-scope-abuse`,category:`performance`,severity:`warning`,description:`Scope.REQUEST creates a new provider instance per request — use only when necessary`,help:`Remove Scope.REQUEST unless the provider genuinely needs per-request state (e.g., request-scoped context). Consider Scope.DEFAULT or Scope.TRANSIENT instead.`},check(e){let t=e.sourceFile.getDescendantsOfKind(f.PropertyAccessExpression);for(let n of t)n.getName()===`REQUEST`&&n.getExpression().getText()===`Scope`&&e.report({filePath:e.filePath,message:`Scope.REQUEST creates a new instance per request, which impacts performance and propagates request scope to all dependents.`,help:this.meta.help,line:n.getStartLineNumber(),column:1})}},en=new Set([`readFileSync`,`writeFileSync`,`existsSync`,`mkdirSync`,`readdirSync`,`statSync`,`accessSync`,`appendFileSync`,`copyFileSync`,`renameSync`,`unlinkSync`]),tn={meta:{id:`performance/no-sync-io`,category:`performance`,severity:`warning`,description:`Synchronous I/O calls block the event loop and should be avoided in NestJS applications`,help:`Use the async variant (e.g., readFile instead of readFileSync) with await.`},check(e){let t=e.sourceFile.getDescendantsOfKind(f.CallExpression);for(let n of t){let t=n.getExpression().getText().split(`.`).pop()??``;en.has(t)&&e.report({filePath:e.filePath,message:`Synchronous I/O call '${t}()' blocks the event loop.`,help:this.meta.help,line:n.getStartLineNumber(),column:1})}}},nn={meta:{id:`performance/no-unused-module-exports`,category:`performance`,severity:`info`,description:`Module exports a provider that no importing module actually uses`,help:`Remove the unused export or use the provider in an importing module.`,scope:`project`},check(e){for(let t of e.moduleGraph.modules.values()){if(t.exports.length===0)continue;let n=[];for(let r of e.moduleGraph.modules.values())r.name!==t.name&&r.imports.includes(t.name)&&n.push(r.name);if(n.length===0)continue;let r=new Set;for(let i of n){let n=e.moduleGraph.modules.get(i);if(n){for(let t of n.providers){let n=e.providers.get(t);if(n)for(let e of n.dependencies)r.add(e)}if(n.exports.includes(t.name))for(let e of t.exports)r.add(e);for(let t of n.controllers)for(let n of e.files){let i=e.project.getSourceFile(n);if(i)for(let e of i.getClasses()){if(e.getName()!==t)continue;let n=e.getConstructors()[0];if(n)for(let e of n.getParameters()){let t=e.getTypeNode(),n=t?t.getText():e.getType().getText(),i=n.split(`.`).pop()?.split(`<`)[0]??n;r.add(i)}}}}}for(let n of t.exports)e.moduleGraph.modules.has(n)||r.has(n)||e.report({filePath:t.filePath,message:`Module '${t.name}' exports '${n}' but no importing module uses it.`,help:this.meta.help,line:t.classDeclaration.getStartLineNumber(),column:1})}}},rn=new Set([`Cron`,`Interval`,`Timeout`,`OnEvent`,`Process`,`OnQueueEvent`,`EventSubscriber`,`SubscribeMessage`,`WebSocketGateway`]);function an(e){for(let t of e.getDecorators())if(rn.has(t.getName()))return!0;for(let t of e.getMethods())for(let e of t.getDecorators())if(rn.has(e.getName()))return!0;return!1}const on={meta:{id:`performance/no-unused-providers`,category:`performance`,severity:`warning`,description:`Injectable providers that are never injected and have no self-activating decorators may be dead code`,help:`Remove the unused provider, inject it where needed, or verify it is activated by a framework decorator (e.g. @Cron, @OnEvent).`,scope:`project`},check(e){let t=new Set;for(let n of e.providers.values())for(let e of n.dependencies)t.add(e);let n=[`Controller`,`Resolver`,`WebSocketGateway`];for(let r of e.files){let i=e.project.getSourceFile(r);if(i)for(let e of i.getClasses()){if(!n.some(t=>e.getDecorator(t)!==void 0))continue;let r=e.getConstructors()[0];if(r)for(let e of r.getParameters()){let n=e.getTypeNode(),r=n?n.getText():e.getType().getText(),i=r.split(`.`).pop()?.split(`<`)[0]??r;t.add(i)}}}for(let n of e.providers.values()){let r=n.name;if(ft.some(e=>r.endsWith(e))||t.has(r)||an(n.classDeclaration))continue;let i=!1;for(let t of e.moduleGraph.modules.values())if(t.exports.includes(r)){i=!0;break}i||e.report({filePath:n.filePath,message:`Provider '${r}' is never injected by any other provider or controller.`,help:this.meta.help,line:n.classDeclaration.getStartLineNumber(),column:1})}}},sn={meta:{id:`schema/require-cascade-rule`,category:`schema`,scope:`schema`,severity:`info`,description:`Relations should have explicit onDelete/cascade behavior defined`,help:`Add an explicit onDelete option (e.g. CASCADE, SET NULL) to avoid relying on database defaults.`},check(e){for(let t of e.schemaGraph.relations)if(!(t.type!==`many-to-one`&&t.type!==`one-to-one`)&&!t.onDelete){let n=e.schemaGraph.entities.get(t.fromEntity);if(!n)continue;e.report({filePath:n.filePath,entity:n.name,message:`Relation '${t.propertyName}' on '${t.fromEntity}' has no explicit onDelete behavior.`,help:this.meta.help})}}},cn={meta:{id:`schema/require-primary-key`,category:`schema`,scope:`schema`,severity:`error`,description:`Every entity must have at least one primary key column`,help:`Add a primary key column (e.g. @id in Prisma, @PrimaryColumn/@PrimaryGeneratedColumn in TypeORM).`},check(e){for(let t of e.schemaGraph.entities.values())t.columns.some(e=>e.isPrimary)||e.report({filePath:t.filePath,entity:t.name,message:`Entity '${t.name}' has no primary key column.`,help:this.meta.help})}},ln=/delete/i;function un(e,t){let n=new Set(e.columns.map(e=>e.name.toLowerCase()));return n.has(`createdat`)||n.has(`created_at`)?!0:t===`typeorm`?e.columns.some(e=>e.type===`timestamp`&&e.isGenerated&&!ln.test(e.name)):t===`prisma`?e.columns.some(e=>e.type===`DateTime`&&e.defaultValue!==void 0&&e.defaultValue.includes(`now()`)):t===`drizzle`?e.columns.some(e=>(e.type===`timestamp`||e.type===`date`||e.type===`datetime`)&&e.defaultValue!==void 0&&e.defaultValue.includes(`now()`)):!1}const dn={meta:{id:`schema/require-timestamps`,category:`schema`,scope:`schema`,severity:`warning`,description:`Entities should have timestamp columns (createdAt/updatedAt)`,help:`Add createdAt/updatedAt columns to track when records are created and modified.`},check(e){for(let t of e.schemaGraph.entities.values())un(t,e.orm)||e.report({filePath:t.filePath,entity:t.name,message:`Entity '${t.name}' has no timestamp columns (createdAt/updatedAt).`,help:this.meta.help})}},fn={meta:{id:`security/no-csrf-disabled`,category:`security`,severity:`error`,description:`CSRF protection should not be explicitly disabled`,help:`Enable CSRF protection or remove the explicit disabling of it.`},check(e){let t=e.sourceFile.getDescendantsOfKind(f.PropertyAssignment);for(let n of t){let t=n.getName();if(t!==`csrf`&&t!==`csrfProtection`)continue;let r=n.getInitializer();r&&r.getText()===`false`&&e.report({filePath:e.filePath,message:`CSRF protection explicitly disabled (${t}: false).`,help:this.meta.help,line:n.getStartLineNumber(),column:1})}}},pn={meta:{id:`security/no-dangerous-redirects`,category:`security`,severity:`error`,description:`Redirects using user-controlled input (from @Query/@Param) are an open redirect vulnerability`,help:`Validate redirect URLs against an allowlist of safe destinations.`},check(e){for(let t of e.sourceFile.getClasses())if(T(t))for(let n of t.getMethods()){let t=new Set;for(let e of n.getParameters())e.getDecorators().some(e=>e.getName()===`Query`||e.getName()===`Param`)&&t.add(e.getName());if(t.size===0)continue;let r=n.getDescendantsOfKind(f.CallExpression);for(let n of r)if(n.getExpression().getText().endsWith(`redirect`))for(let r of n.getArguments()){let i=r.getText();t.has(i)&&e.report({filePath:e.filePath,message:`Redirect uses user-controlled parameter '${i}' — open redirect risk.`,help:this.meta.help,line:n.getStartLineNumber(),column:1})}let i=n.getDecorators().find(e=>e.getName()===`Redirect`);if(i)for(let n of i.getArguments()){let r=n.getText();t.has(r)&&e.report({filePath:e.filePath,message:`@Redirect() uses user-controlled parameter '${r}' — open redirect risk.`,help:this.meta.help,line:i.getStartLineNumber(),column:1})}}}},mn={meta:{id:`security/no-eval`,category:`security`,severity:`error`,description:`Usage of eval() or new Function() is a security risk and should be avoided`,help:`Refactor to avoid eval() and new Function(). Use safer alternatives like JSON.parse() or a sandboxed interpreter.`},check(e){let t=e.sourceFile.getDescendantsOfKind(f.CallExpression);for(let n of t)n.getExpression().getText()===`eval`&&e.report({filePath:e.filePath,message:`Usage of eval() is a security risk.`,help:this.meta.help,line:n.getStartLineNumber(),column:1});let n=e.sourceFile.getDescendantsOfKind(f.NewExpression);for(let t of n)t.getExpression().getText()===`Function`&&e.report({filePath:e.filePath,message:`Usage of new Function() is a security risk.`,help:this.meta.help,line:t.getStartLineNumber(),column:1})}},hn={meta:{id:`security/no-exposed-env-vars`,category:`security`,severity:`warning`,description:`Use NestJS ConfigService instead of direct process.env access in Injectable/Controller classes`,help:`Inject ConfigService and use configService.get('VAR_NAME') instead of process.env.VAR_NAME.`},check(e){for(let t of e.sourceFile.getClasses()){if(!(w(t,`Injectable`)||w(t,`Controller`)))continue;let n=t.getDescendantsOfKind(f.PropertyAccessExpression);for(let r of n)r.getExpression().getText()===`process.env`&&e.report({filePath:e.filePath,message:`Direct 'process.env.${r.getName()}' access in '${t.getName()}'. Use ConfigService instead.`,help:this.meta.help,line:r.getStartLineNumber(),column:1})}}},gn=/^(error|err|e|ex|exception)$/,_n={meta:{id:`security/no-exposed-stack-trace`,category:`security`,severity:`warning`,description:`Stack traces should not be exposed in responses — they leak internal implementation details`,help:`Log the stack trace internally and return a generic error message to the client.`},check(e){let t=e.sourceFile.getDescendantsOfKind(f.PropertyAccessExpression);for(let n of t){if(n.getName()!==`stack`)continue;let t=n.getExpression().getText();if(!(gn.test(t)||t.endsWith(`.error`)||t.endsWith(`.err`)))continue;let r=n.getParent();if(!r)continue;let i=r.getKind();(i===f.ReturnStatement||i===f.PropertyAssignment||i===f.ShorthandPropertyAssignment||i===f.CallExpression)&&e.report({filePath:e.filePath,message:`Stack trace '${t}.stack' may be exposed in response — leaks implementation details.`,help:this.meta.help,line:n.getStartLineNumber(),column:1})}}},vn=[{pattern:/^(?=.*\d)[A-Za-z0-9+/]{40,}={0,2}$/,name:`Base64 key`},{pattern:/^sk[-_][a-zA-Z0-9]{20,}$/,name:`Secret key`},{pattern:/^pk[-_][a-zA-Z0-9]{20,}$/,name:`Public key (in source)`},{pattern:/^ghp_[a-zA-Z0-9]{36,}$/,name:`GitHub personal access token`},{pattern:/^github_pat_[a-zA-Z0-9_]{22,}$/,name:`GitHub fine-grained PAT`},{pattern:/^gho_[a-zA-Z0-9]{36,}$/,name:`GitHub OAuth token`},{pattern:/^xox[bpras]-[a-zA-Z0-9-]+$/,name:`Slack token`},{pattern:/^eyJ[a-zA-Z0-9_-]{10,}\.[a-zA-Z0-9_-]{10,}\./,name:`JWT token`},{pattern:/^AKIA[0-9A-Z]{16}$/,name:`AWS Access Key ID`},{pattern:/^[a-f0-9]{64}$/,name:`Hex-encoded secret (64 chars)`}],yn=[/secret/i,/password/i,/passwd/i,/api[_-]?key/i,/auth[_-]?token/i,/private[_-]?key/i,/access[_-]?key/i,/client[_-]?secret/i],bn=new Set([`your-secret-here`,`changeme`,`password`]),xn=/^[A-Za-z][A-Za-z0-9_]*(\.[A-Za-z][A-Za-z0-9_]*)+$/,Sn=new Set([`cursor`,`nextCursor`,`prevCursor`,`previousCursor`,`startCursor`,`endCursor`,`pageToken`,`nextPageToken`,`continuationToken`,`continuation`,`nextPage`,`afterCursor`,`beforeCursor`]);function Cn(e){return!(e.length<8||e.includes("${")||e.startsWith(`process.env`)||bn.has(e)||e.includes(` `)||xn.test(e))}function wn(e){return yn.some(t=>t.test(e))}function Tn(e){try{let t=Buffer.from(e,`base64`).toString(`utf-8`);return JSON.parse(t),!0}catch{return!1}}function En(e){let t=new Map;for(let n of e)t.set(n,(t.get(n)??0)+1);let n=0;for(let r of t.values()){let t=r/e.length;n-=t*Math.log2(t)}return n}const Dn=new Set([...`aeiouyAEIOUY`]),On=/^[A-Z]{2,4}_/,kn=/(?<=[a-z])(?=[A-Z])|(?<=[A-Za-z])(?=\d)|(?<=\d)(?=[A-Za-z])|_/,An=/[a-zA-Z]/;function jn(e){let t=e.includes(`_`),n=e.split(kn).filter(e=>e.length>0).filter(e=>An.test(e)),r=n.filter(e=>e.length>=4&&[...e].some(e=>Dn.has(e)));return n.slice(0,6).filter(e=>e.length>=4&&[...e].some(e=>Dn.has(e))).length>=2||t&&e.split(`_`).filter(e=>e.length>=3).length>=2||On.test(e)?!0:(En(e)>4.9&&!t&&r.length,!1)}function Mn(e){let t=e.getParent();if(!t)return!1;let n=t.asKind(f.PropertyAssignment);if(n)return Sn.has(n.getName());let r=t.asKind(f.VariableDeclaration);return r?Sn.has(r.getName()):!1}const Nn={meta:{id:`security/no-hardcoded-secrets`,category:`security`,severity:`error`,description:`Detect hardcoded secrets, API keys, and tokens in source code`,help:`Move secrets to environment variables and access them via ConfigService.`},check(e){let t=e.sourceFile.getDescendantsOfKind(f.StringLiteral);for(let n of t){let t=n.getLiteralValue();if(!(t.length<16)&&n.getParent()?.getKind()!==f.ImportDeclaration){for(let{pattern:r,name:i}of vn)if(r.test(t)){if(i===`Base64 key`&&(Tn(t)||Mn(n)||jn(t)))break;e.report({filePath:e.filePath,message:`Possible hardcoded ${i} detected.`,help:this.meta.help,line:n.getStartLineNumber(),column:1});break}}}let n=e.sourceFile.getDescendantsOfKind(f.VariableDeclaration);for(let t of n){let n=t.getName(),r=t.getInitializer();!r||r.getKind()!==f.StringLiteral||wn(n)&&Cn(r.getText().slice(1,-1))&&e.report({filePath:e.filePath,message:`Variable '${n}' appears to contain a hardcoded secret.`,help:this.meta.help,line:t.getStartLineNumber(),column:1})}let r=e.sourceFile.getDescendantsOfKind(f.PropertyAssignment);for(let t of r){let n=t.getName(),r=t.getInitializer();!r||r.getKind()!==f.StringLiteral||wn(n)&&Cn(r.getText().slice(1,-1))&&e.report({filePath:e.filePath,message:`Property '${n}' appears to contain a hardcoded secret.`,help:this.meta.help,line:t.getStartLineNumber(),column:1})}}},Pn=RegExp(`(?:^|[^a-zA-Z])\\w*(?:${[`Entity`,`Model`].join(`|`)})(?:[^a-zA-Z]|$)`),Fn={meta:{id:`security/no-raw-entity-in-response`,category:`security`,severity:`warning`,description:`Returning ORM entities directly from controllers can leak internal fields like passwords or IDs`,help:`Map entities to DTOs or use class-transformer's @Exclude()/@Expose() decorators before returning.`},check(e){for(let t of e.sourceFile.getClasses())if(T(t))for(let n of t.getMethods()){if(!k(n))continue;let t=n.getReturnType().getText();Pn.test(t)&&!t.includes(`DTO`)&&!t.includes(`Dto`)&&!t.includes(`Response`)&&e.report({filePath:e.filePath,message:`Controller method '${n.getName()}' returns a raw entity type. This may leak internal fields.`,help:this.meta.help,line:n.getStartLineNumber(),column:1})}}},In={meta:{id:`security/no-synchronize-in-production`,category:`security`,severity:`error`,description:`TypeORM synchronize: true auto-syncs schema and can drop columns or tables in production`,help:`Set synchronize: false and use migrations for production schema changes.`},check(e){let t=e.sourceFile.getDescendantsOfKind(f.PropertyAssignment);for(let n of t){if(n.getName()!==`synchronize`)continue;let t=n.getInitializer();t&&t.getText()===`true`&&e.report({filePath:e.filePath,message:`TypeORM 'synchronize: true' can auto-drop columns and tables in production.`,help:this.meta.help,line:n.getStartLineNumber(),column:1})}}},Ln=new Set([`md5`,`sha1`]),Rn={meta:{id:`security/no-weak-crypto`,category:`security`,severity:`warning`,description:`Weak hashing algorithms (MD5, SHA1) should not be used for security purposes`,help:`Use a stronger algorithm like SHA-256 or bcrypt for password hashing.`},check(e){let t=e.sourceFile.getDescendantsOfKind(f.CallExpression);for(let n of t){if(!n.getExpression().getText().endsWith(`createHash`))continue;let t=n.getArguments();if(t.length===0)continue;let r=t[0];if(r.getKind()!==f.StringLiteral)continue;let i=r.getText().slice(1,-1).toLowerCase();Ln.has(i)&&e.report({filePath:e.filePath,message:`Weak hashing algorithm '${i}' used in createHash().`,help:this.meta.help,line:n.getStartLineNumber(),column:1})}}},zn=new Set([`Public`,`AllowAnonymous`,`SkipAuth`,`IsPublic`]),Bn=[De,at,Ye,et,Ge,st,ct,ut,we,Be,Nt,It,xt,bt,Et,At,Tt,Ot,ht,yt,kt,Pt,wt,Mt,dt,Jt,_t,Rt,Dt,pt,Nn,mn,Rn,hn,fn,_n,pn,In,Fn,{meta:{id:`security/require-guards-on-endpoints`,category:`security`,severity:`warning`,description:`Controller endpoints should be protected by @UseGuards() at class or method level`,help:`Add @UseGuards(AuthGuard) to the controller class or individual route handlers, or mark routes as @Public(). If you use a global guard via APP_GUARD, you can disable this rule.`},check(e){for(let t of e.sourceFile.getClasses())if(T(t)&&t.getDecorator(`UseGuards`)===void 0&&!t.getDecorators().some(e=>zn.has(e.getName())))for(let n of t.getMethods())k(n)&&n.getDecorator(`UseGuards`)===void 0&&(n.getDecorators().some(e=>zn.has(e.getName()))||e.report({filePath:e.filePath,message:`Endpoint '${n.getName()}' has no @UseGuards() at class or method level.`,help:this.meta.help,line:n.getStartLineNumber(),column:1}))}},tn,Xt,Zt,$t,on,nn,Qt,cn,dn,sn];function Vn(){return[...Bn]}function Hn(e){return e.meta.scope===`project`}function Un(e){return e.meta.scope===`schema`}function Wn(e,t,n){if(t.length===0)return e;let r=new Set(e.map(e=>e.meta.id)),i=[...e];for(let e of t){if(r.has(e.meta.id)){n.push(`Custom rule "${e.meta.id}" conflicts with a built-in rule and was skipped`);continue}i.push(e)}return i}function Gn(e,t){return t.filter(t=>{let n=e.rules?.[t.meta.id];return!(n===!1||typeof n==`object`&&n.enabled===!1||e.categories?.[t.meta.category]===!1)})}function Kn(e){let t=[],n=[],r=[];for(let i of e)Un(i)?r.push(i):Hn(i)?n.push(i):t.push(i);return{fileRules:t,projectRules:n,schemaRules:r}}const qn=new Set([`security`,`performance`,`correctness`,`architecture`]),Jn=new Set([`error`,`warning`,`info`]),Yn=new Set([`file`,`project`]),Xn=`custom/`;function Zn(e){if(typeof e!=`object`||!e)return!1;let t=e;if(typeof t.check!=`function`||typeof t.meta!=`object`||t.meta===null)return!1;let n=t.meta;return!(typeof n.id!=`string`||n.id.trim()===``||typeof n.description!=`string`||typeof n.help!=`string`||!qn.has(n.category)||!Jn.has(n.severity)||n.scope!==void 0&&!Yn.has(n.scope))}function Qn(e){return e.meta.id.startsWith(Xn)?e:{...e,meta:{...e.meta,id:`${Xn}${e.meta.id}`}}}async function $n(t,i){let a=[],o=[],c=s(i,t);if(!e(c))return o.push(`Custom rules directory not found: ${c}`),{rules:a,warnings:o};if(!r(c).isDirectory())return o.push(`Custom rules path is not a directory: ${c}`),{rules:a,warnings:o};let l;try{l=n(c)}catch(e){return o.push(`Failed to read custom rules directory: ${e instanceof Error?e.message:String(e)}`),{rules:a,warnings:o}}let u=l.filter(e=>e.endsWith(`.ts`));if(u.length===0)return o.push(`No rule files (.ts) found in: ${c}`),{rules:a,warnings:o};let d=m(c,{interopDefault:!0});for(let e of u){let t=s(c,e),n;try{n=await d.import(t)}catch(t){o.push(`Failed to load custom rule file "${e}": ${t instanceof Error?t.message:String(t)}`);continue}let r=!1;for(let[t,i]of Object.entries(n))Zn(i)?(a.push(Qn(i)),r=!0):t!==`__esModule`&&typeof i==`object`&&i&&`meta`in i&&o.push(`Invalid rule export "${t}" in "${e}": missing or invalid required fields (check, meta.id, meta.description, meta.help, meta.category, meta.severity)`);!r&&Object.keys(n).length>0&&(Object.values(n).some(e=>typeof e==`object`&&!!e&&(`meta`in e||`check`in e))||o.push(`No valid rule exports found in "${e}"`))}return{rules:a,warnings:o}}function er(e,t){return e.customRulesDir?$n(e.customRulesDir,t):Promise.resolve({rules:[],warnings:[]})}async function V(e,t){let n=await b(e,t),{rules:r,warnings:i}=await er(n,e),a=Wn(Bn,r,i),{fileRules:o,projectRules:s,schemaRules:c}=Kn(Gn(n,a));return{combinedRules:a,config:n,customRuleWarnings:i,fileRules:o,projectRules:s,schemaRules:c}}async function tr(e,t={}){return(await l(t.include??y.include,{cwd:e,absolute:!0,ignore:t.exclude??y.exclude})).sort()}async function nr(e,t,n={}){let r=await Promise.all([...t.projects.entries()].map(async([t,r])=>[t,await tr(a(e,r),n)])),i=new Map;for(let[e,t]of r)i.set(e,t);return i}function rr(e){let t=new d({compilerOptions:{strict:!0,target:99,module:99,skipFileDependencyResolution:!0},skipAddingFilesFromTsConfig:!0});for(let n of e)t.addSourceFileAtPath(n);return t}const ir=/import\([^)]+\)\.(\w+)/,ar=/^(\w+)</;function or(e,t){let n=[];for(let r of e.getClasses()){if(!r.getDecorator(`Injectable`))continue;let e=r.getName();if(!e)continue;let i=r.getConstructors()[0],a=i?i.getParameters().map(e=>{let t=e.getTypeNode();return lr(t?t.getText():e.getType().getText())}):[],o=r.getMethods().filter(e=>{let t=e.getScope();return!t||t===`public`}).length;n.push({name:e,filePath:t,classDeclaration:r,dependencies:a,publicMethodCount:o})}return n}function sr(e,t){let n=new Map;for(let r of t){let t=e.getSourceFile(r);if(t)for(let e of or(t,r))n.set(e.name,e)}return n}function cr(e,t,n){for(let[t,r]of e)r.filePath===n&&e.delete(t);let r=t.getSourceFile(n);if(r)for(let t of or(r,n))e.set(t.name,t)}function lr(e){let t=e.match(ir);if(t)return t[1];let n=e.match(ar);return n?n[1]:e}const H=/^['"`]|['"`]$/g,ur=/\/+/g,dr=/\/$/;function fr(e,t){let n=e;for(;n&&n!==t;){let e=n.getParent();if(!e||e===t)break;let r=e.getKind();if(r===f.IfStatement){let t=e.asKindOrThrow(f.IfStatement);if(n===t.getThenStatement()||n===t.getElseStatement())return!0}if(r===f.ConditionalExpression){let t=e.asKindOrThrow(f.ConditionalExpression);if(n===t.getWhenTrue()||n===t.getWhenFalse())return!0}let i=n.getKind();if(i===f.CaseClause||i===f.DefaultClause||i===f.CatchClause)return!0;n=e}return!1}function pr(e){let t=e.getDecorator(`Controller`);if(!t)return``;let n=t.getArguments();if(n.length===0)return``;let r=n[0];if(r.getKind()===f.ObjectLiteralExpression){let e=r.asKindOrThrow(f.ObjectLiteralExpression).getProperty(`path`);if(!e)return``;let t=e.asKind(f.PropertyAssignment);if(!t)return``;let n=t.getInitializer();return n?n.getText().replace(H,``):``}return r.getText().replace(H,``)}function mr(e){for(let t of e.getDecorators()){let e=t.getName();if(!C.has(e))continue;let n=t.getArguments(),r=n.length>0?n[0].getText().replace(H,``):``;return{httpMethod:e.toUpperCase(),path:r}}}function hr(e,t){return`/${[e,t].filter(Boolean).join(`/`)}`.replace(ur,`/`).replace(dr,``)||`/`}function U(e){let t=new Map,n=e.getConstructors()[0];if(!n)return t;for(let e of n.getParameters()){let n=e.getName(),r=e.getTypeNode(),i=r?r.getText():e.getType().getText();t.set(n,lr(i))}return t}function gr(e,t){let n=e.getBody();if(!n)return[];let r=new Map,i=0,a=n.getDescendantsOfKind(f.CallExpression);for(let e of a){let a=e.getExpression();if(a.getKind()!==f.PropertyAccessExpression)continue;let o=a.asKindOrThrow(f.PropertyAccessExpression),s=o.getName(),c=o.getExpression();if(c.getKind()!==f.PropertyAccessExpression)continue;let l=c.asKindOrThrow(f.PropertyAccessExpression);if(l.getExpression().getKind()!==f.ThisKeyword)continue;let u=l.getName();if(!t.has(u))continue;r.has(u)||r.set(u,new Map);let d=r.get(u),p=fr(e,n),m=d.get(s);m?m.isUnconditional=m.isUnconditional||!p:d.set(s,{isUnconditional:!p,order:i++})}let o=[];for(let[e,n]of r){let r=[];for(let[e,t]of n)r.push({name:e,conditional:!t.isUnconditional,order:t.order});r.sort((e,t)=>e.order-t.order),o.push({className:t.get(e),methodsCalled:r})}return o}function _r(e){return e.endsWith(`Repository`)?`repository`:e.endsWith(`Guard`)?`guard`:e.endsWith(`Interceptor`)?`interceptor`:e.endsWith(`Pipe`)?`pipe`:e.endsWith(`Filter`)?`filter`:e.endsWith(`Gateway`)?`gateway`:`service`}function W(e,t,n){let r=[],i=new Set,a=[],o=[];for(let t of e)if(t.methodsCalled.length===0)o.push(t);else for(let e of t.methodsCalled)a.push({className:t.className,mc:e,dep:t});a.sort((e,t)=>e.mc.order-t.mc.order);for(let e of o){if(n.has(e.className)||i.has(e.className))continue;i.add(e.className),n.add(e.className);let a=t.get(e.className),o=[];a&&(o=a.dependencies.map(e=>({className:e,methodsCalled:[]}))),r.push({className:e.className,conditional:!1,dependencies:W(o,t,new Set(n)),filePath:a?.filePath??``,line:0,methodName:null,order:0,totalMethods:a?.publicMethodCount??0,type:_r(e.className)})}let s=new Map;for(let{className:e,dep:t}of a)s.has(e)||s.set(e,t.methodsCalled);for(let{className:e,mc:o}of a){if(n.has(e))continue;let a=t.get(e),c=!i.has(e);c&&i.add(e);let l=[];if(c&&a){let r=new Set(n);r.add(e);let i=U(a.classDeclaration),o=new Map,c=0,u=s.get(e)??[];for(let e of u){let t=a.classDeclaration.getInstanceMethod(e.name);if(t)for(let e of gr(t,i)){o.has(e.className)||o.set(e.className,new Map);let t=o.get(e.className);for(let n of e.methodsCalled){let e=t.get(n.name);e?e.isUnconditional=e.isUnconditional||!n.conditional:t.set(n.name,{isUnconditional:!n.conditional,order:c++})}}}l=W([...o.entries()].map(([e,t])=>({className:e,methodsCalled:[...t.entries()].map(([e,t])=>({name:e,conditional:!t.isUnconditional,order:t.order})).sort((e,t)=>e.order-t.order)})),t,r)}let u=0;if(a){let e=a.classDeclaration.getInstanceMethod(o.name);e&&(u=e.getStartLineNumber())}r.push({className:e,conditional:o.conditional,dependencies:l,filePath:a?.filePath??``,line:u,methodName:o.name,order:o.order,totalMethods:a?.publicMethodCount??0,type:_r(e)})}return r}function vr(e,t,n){let r=[];for(let i of e.getClasses()){if(!T(i))continue;let e=pr(i),a=i.getName()??`AnonymousController`,o=U(i);for(let s of i.getMethods()){let i=mr(s);if(!i)continue;let c=hr(e,i.path),l=W(gr(s,o),n,new Set);r.push({controllerClass:a,dependencies:l,filePath:t,handlerMethod:s.getName(),httpMethod:i.httpMethod,line:s.getStartLineNumber(),routePath:c})}}return r}function G(e,t,n){let r=[];for(let i of t){let t=e.getSourceFile(i);t&&r.push(...vr(t,i,n))}return{endpoints:r}}function K(e,t,n,r,i,a){if(i>10)return[];let o=e.getBody();if(!o)return[];let s=[],c=o.getDescendantsOfKind(f.CallExpression);for(let e of c){let o=e.getExpression();if(o.getKind()!==f.PropertyAccessExpression)continue;let c=o.asKindOrThrow(f.PropertyAccessExpression),l=c.getName(),u=c.getExpression();if(u.getKind()===f.PropertyAccessExpression){let e=u.asKindOrThrow(f.PropertyAccessExpression);if(e.getExpression().getKind()!==f.ThisKeyword)continue;let a=e.getName(),o=t.get(a);if(!o)continue;let c=`${o}.${l}`;if(r.has(c)){s.push({calls:[],circular:!0,className:o,filePath:``,line:0,methodName:l});continue}r.add(c);let d=n.get(o),p=[],m=``,h=0;if(d){m=d.filePath;let e=d.classDeclaration.getInstanceMethod(l);e&&(h=e.getStartLineNumber(),p=K(e,U(d.classDeclaration),n,new Set(r),i+1,d.classDeclaration))}s.push({calls:p,className:o,filePath:m,line:h,methodName:l})}else if(u.getKind()===f.ThisKeyword&&a){let e=a.getInstanceMethod(l);if(!e)continue;let o=`${a.getName()??`Anonymous`}.${l}`;if(r.has(o))continue;r.add(o);let c=K(e,t,n,new Set(r),i+1,a);s.push(...c)}}return s}function yr(e,t,n){let r=n.getSourceFile(e.filePath);if(!r)return[];let i=r.getClasses().find(t=>t.getName()===e.controllerClass);if(!i)return[];let a=i.getInstanceMethod(e.handlerMethod);return a?K(a,U(i),t,new Set,0,i):[]}function br(e,t,n,r){e.endpoints=e.endpoints.filter(e=>e.filePath!==n);let i=t.getSourceFile(n);i&&e.endpoints.push(...vr(i,n,r))}const xr=new Set([`pgTable`,`mysqlTable`,`sqliteTable`]),Sr=new Set([`serial`,`bigserial`,`smallserial`]),Cr=/=>\s*(\w+)/;function wr(e){let t={type:`unknown`,isPrimary:!1,isNullable:!0,isGenerated:!1,isUnique:!1};function n(e){if(e.getKind()===f.CallExpression){let r=e.asKindOrThrow(f.CallExpression),i=r.getExpression();if(i.getKind()===f.PropertyAccessExpression){let e=i.asKindOrThrow(f.PropertyAccessExpression);switch(e.getName()){case`primaryKey`:t.isPrimary=!0;break;case`notNull`:t.isNullable=!1;break;case`unique`:t.isUnique=!0;break;case`default`:{let e=r.getArguments();e.length>0&&(t.defaultValue=e[0].getText().replace(/['"]/g,``));break}case`defaultNow`:t.defaultValue=`now()`;break;case`generatedAlwaysAsIdentity`:case`autoincrement`:t.isGenerated=!0;break;case`references`:{let e=r.getArguments();if(e.length>0){let n=e[0].getText(),r=Cr.exec(n);if(r&&(t.reference={toEntity:r[1]}),e.length>1){let n=e[1];if(n.getKind()===f.ObjectLiteralExpression){let e=n.asKindOrThrow(f.ObjectLiteralExpression);for(let n of e.getProperties())if(n.getKind()===f.PropertyAssignment){let e=n.asKindOrThrow(f.PropertyAssignment);if(e.getName()===`onDelete`){let n=e.getInitializer()?.getText();n&&(t.reference.onDelete=n.replace(/['"]/g,``))}}}}}break}default:break}n(e.getExpression())}else if(i.getKind()===f.Identifier){let e=i.getText();t.type=e,Sr.has(e)&&(t.isGenerated=!0)}}}return n(e),t}function Tr(e){let t=[];for(let n of e.getProperties()){if(n.getKind()!==f.PropertyAssignment)continue;let e=n.asKindOrThrow(f.PropertyAssignment),r=e.getName(),i=e.getInitializer();if(!i)continue;let a=wr(i);t.push({name:r,type:a.type,isPrimary:a.isPrimary,isNullable:a.isNullable,isGenerated:a.isGenerated,isUnique:a.isUnique,defaultValue:a.defaultValue})}return t}function Er(e,t){let n=[];for(let r of e.getProperties()){if(r.getKind()!==f.PropertyAssignment)continue;let e=r.asKindOrThrow(f.PropertyAssignment),i=e.getName(),a=e.getInitializer();if(!a)continue;let o=wr(a);o.reference&&n.push({type:`many-to-one`,fromEntity:t,toEntity:o.reference.toEntity,propertyName:i,isNullable:o.isNullable,...o.reference.onDelete?{onDelete:o.reference.onDelete}:{}})}return n}function Dr(e){let t=[],n=e.getDescendantsOfKind(f.CallExpression);for(let e of n){let n=e.getExpression();if(n.getKind()!==f.PropertyAccessExpression||n.asKindOrThrow(f.PropertyAccessExpression).getName()!==`on`)continue;let r=[];for(let t of e.getArguments())if(t.getKind()===f.PropertyAccessExpression){let e=t.asKindOrThrow(f.PropertyAccessExpression);r.push(e.getName())}if(r.length===0)continue;let i=e.getText().includes(`uniqueIndex`);t.push({columns:r,isUnique:i})}return t}function Or(e){let t=[],n=e.getFilePath();for(let r of e.getDescendantsOfKind(f.VariableDeclaration)){let e=r.getInitializer();if(!e||e.getKind()!==f.CallExpression)continue;let i=e.asKindOrThrow(f.CallExpression),a=i.getExpression();if(a.getKind()!==f.Identifier)continue;let o=a.getText();if(!xr.has(o))continue;let s=i.getArguments();if(s.length<2)continue;let c=s[0],l=r.getName();c.getKind()===f.StringLiteral&&(l=c.asKindOrThrow(f.StringLiteral).getLiteralValue());let u=s[1];if(u.getKind()!==f.ObjectLiteralExpression)continue;let d=u.asKindOrThrow(f.ObjectLiteralExpression),p=r.getName(),m=Tr(d),h=Er(d,p),g;if(s.length>=3&&(g=Dr(s[2]),g))for(let e of g)for(let t of e.columns){let e=m.find(e=>e.name===t);e&&(e.hasIndex=!0)}t.push({name:p,tableName:l,filePath:n,columns:m,relations:h,indexes:g})}return t}const kr={supportsIncrementalUpdate:!0,extract(e,t){let n=[];for(let r of t){let t=e.getSourceFile(r);t&&n.push(...Or(t))}return n}},Ar=/^model\s+(\w+)\s*\{/,jr=/^enum\s+(\w+)\s*\{/,Mr=/^(\w+)\s+(\w+)(\?)?(\[\])?(.*)$/,Nr=/@(\w+)(\((?:[^()]*|\([^()]*\))*\))?/g,Pr=/@default\(((?:[^()]*|\([^()]*\))*)\)/,Fr=/^@@map\(\s*"([^"]+)"\s*\)/;function Ir(r){let i=a(r,`prisma`,`schema.prisma`);if(e(i)){let e=a(r,`prisma`),t=n(e).filter(e=>e.endsWith(`.prisma`));return t.length>1?t.map(t=>a(e,t)):[i]}let o=a(r,`schema.prisma`);if(e(o))return[o];try{let n=a(r,`package.json`),i=JSON.parse(t(n,`utf-8`)).prisma?.schema;if(i){let t=a(r,i);if(e(t))return[t]}}catch{}return[]}function Lr(e){let n=[],r=new Set;for(let i of e){let e;try{e=t(i,`utf-8`)}catch{continue}let a=e.split(`
|
|
4
|
+
`),o=null,s=[],c=[],l=[],u;for(let e of a){let t=e.trim(),a=Ar.exec(t);if(a){o={type:`model`,name:a[1]},s=[],c=[],l=[],u=void 0;continue}let d=jr.exec(t);if(d){o={type:`enum`,name:d[1]},r.add(d[1]);continue}if(t===`}`){o?.type===`model`&&n.push({name:o.name,fields:s,indexes:c,compositeIdColumns:l,filePath:i,tableName:u}),o=null,s=[],c=[],l=[],u=void 0;continue}if(o?.type===`model`&&t&&!t.startsWith(`//`)){if(t.startsWith(`@@`)){let e=zr.exec(t);e&&(l=e[1].split(`,`).map(e=>e.trim()));let n=Br(t);n&&c.push(n);let r=Fr.exec(t);r&&(u=r[1]);continue}let e=Vr(t);e&&s.push(e)}}}return{models:n,enums:r}}const Rr=/^@@(index|unique)\(\[([^\]]*)\]\)/,zr=/^@@id\(\[([^\]]*)\]\)/;function Br(e){let t=Rr.exec(e);if(!t)return null;let n=t[1]===`unique`,r=t[2].split(`,`).map(e=>e.trim()).filter(Boolean);return r.length===0?null:{columns:r,isUnique:n}}function Vr(e){let t=Mr.exec(e);if(!t)return null;let n=t[1],r=t[2],i=t[3]===`?`,a=t[4]===`[]`,o=t[5]??``,s=[],c=new RegExp(Nr.source,Nr.flags),l=c.exec(o);for(;l!==null;)s.push(`@${l[1]}${l[2]??``}`),l=c.exec(o);return{name:n,type:r,isOptional:i,isList:a,attributes:s}}function Hr(e){let t=e.attributes.some(e=>e.startsWith(`@id`)),n=e.attributes.some(e=>e.startsWith(`@unique`)),r=e.attributes.find(e=>e.startsWith(`@default(`)),i=!1,a;if(r){let e=Pr.exec(r);if(e){let t=e[1];a=t,(t===`autoincrement()`||t===`uuid()`||t===`cuid()`||t===`dbgenerated()`)&&(i=!0)}}return{name:e.name,type:e.type,isPrimary:t,isNullable:e.isOptional,isGenerated:i,isUnique:n,defaultValue:a}}const Ur=/onDelete:\s*(\w+)/;function Wr(e){let t=e.attributes.find(e=>e.startsWith(`@relation`));if(!t)return;let n=Ur.exec(t);return n?n[1]:void 0}function Gr(e,t){let n=new Set(e.map(e=>e.name));return e.map(r=>{let i=[],a=[],o=new Set;for(let e of r.indexes)for(let t of e.columns)o.add(t);let s=new Set(r.compositeIdColumns);for(let c of r.fields)if(n.has(c.type)&&!t.has(c.type)){let t;t=c.isList?`one-to-many`:`many-to-one`;let n=c.isOptional;c.isList&&e.find(e=>e.name===c.type)?.fields.find(e=>e!==c&&e.type===r.name&&e.isList)&&(t=`many-to-many`);let i=Wr(c);a.push({type:t,fromEntity:r.name,toEntity:c.type,propertyName:c.name,isNullable:n??!1,...i?{onDelete:i}:{}})}else if(!c.attributes.some(e=>e.startsWith(`@relation`))){let e=Hr(c);s.has(c.name)&&(e.isPrimary=!0),(o.has(c.name)||c.attributes.some(e=>e.startsWith(`@unique`)))&&(e.hasIndex=!0),i.push(e)}return{name:r.name,tableName:r.tableName??r.name,filePath:r.filePath,columns:i,relations:a,indexes:r.indexes}})}const Kr={supportsIncrementalUpdate:!1,extract(e,t,n){let r=Ir(n);if(r.length===0)return[];let{models:i,enums:a}=Lr(r);return Gr(i,a)}},qr=/=>\s*(\w+)/,Jr=new Set([`Column`,`PrimaryColumn`,`PrimaryGeneratedColumn`,`CreateDateColumn`,`UpdateDateColumn`,`DeleteDateColumn`,`VersionColumn`]),Yr={OneToOne:`one-to-one`,OneToMany:`one-to-many`,ManyToOne:`many-to-one`,ManyToMany:`many-to-many`};function q(e){let t=e.getArguments();for(let e of t)if(e.getKind()===f.ObjectLiteralExpression){let t={},n=e.asKind(f.ObjectLiteralExpression);if(!n)continue;for(let e of n.getProperties())if(e.getKind()===f.PropertyAssignment){let n=e.asKind(f.PropertyAssignment);n&&(t[n.getName()]=n.getInitializer()?.getText()??``)}return t}return null}function J(e){let t=e.getArguments();if(t.length===0)return null;let n=t[0];return n.getKind()===f.StringLiteral?n.asKind(f.StringLiteral)?.getLiteralValue()??null:null}function Xr(e){let t=e.getDecorator(`Entity`);if(!t)return e.getName()??`UnknownEntity`;let n=J(t);if(n)return n;let r=q(t);return r?.name?r.name.replace(/['"]/g,``):e.getName()??`UnknownEntity`}function Zr(e,t){let n=t.getName(),r=n===`PrimaryColumn`||n===`PrimaryGeneratedColumn`,i=n===`PrimaryGeneratedColumn`||n===`CreateDateColumn`||n===`UpdateDateColumn`||n===`DeleteDateColumn`||n===`VersionColumn`,a=`unknown`,o=!1,s=!1,c,l=J(t);l&&(a=l);let u=q(t);return u&&(u.type&&(a=u.type.replace(/['"]/g,``)),u.nullable===`true`&&(o=!0),u.unique===`true`&&(s=!0),u.default!==void 0&&(c=u.default)),a===`unknown`&&(n===`PrimaryGeneratedColumn`?a=`integer`:n===`CreateDateColumn`||n===`UpdateDateColumn`||n===`DeleteDateColumn`?a=`timestamp`:n===`VersionColumn`&&(a=`integer`)),{name:e,type:a,isPrimary:r,isNullable:o,isGenerated:i,isUnique:s,defaultValue:c}}function Qr(e,t,n){let r=Yr[n.getName()];if(!r)return null;let i=n.getArguments();if(i.length===0)return null;let a=i[0].getText(),o=qr.exec(a);if(!o)return null;let s=o[1],c=q(n),l=c?.nullable===`true`,u=c?.onDelete?.replace(/['"]/g,``);return{type:r,fromEntity:e,toEntity:s,propertyName:t,isNullable:l,...u?{onDelete:u}:{}}}function $r(e){if(!w(e,`Entity`))return null;let t=e.getName();if(!t)return null;let n=Xr(e),r=e.getSourceFile().getFilePath(),i=[],a=[],o=[];for(let t of e.getDecorators())if(t.getName()===`Index`){let e=t.getArguments();for(let n of e)if(n.getKind()===f.ArrayLiteralExpression){let e=n.asKind(f.ArrayLiteralExpression);if(e){let n=e.getElements().map(e=>e.getKind()===f.StringLiteral?e.asKind(f.StringLiteral)?.getLiteralValue()??``:``).filter(Boolean);if(n.length>0){let e=q(t);o.push({columns:n,isUnique:e?.unique===`true`})}}}}let s=new Set;for(let n of e.getProperties()){let e=n.getName(),r=n.getDecorators(),c=r.some(e=>e.getName()===`Index`);c&&(s.add(e),o.push({columns:[e],isUnique:!1}));for(let n of r){let r=n.getName();if(Jr.has(r)){let t=Zr(e,n);c&&(t.hasIndex=!0),i.push(t);break}if(r in Yr){let r=Qr(t,e,n);r&&a.push(r);break}}}for(let e of o)for(let t of e.columns){let e=i.find(e=>e.name===t);e&&(e.hasIndex=!0)}return{name:t,tableName:n,filePath:r,columns:i,relations:a,indexes:o}}const ei={prisma:Kr,typeorm:{supportsIncrementalUpdate:!0,extract(e,t){let n=[];for(let r of t){let t=e.getSourceFile(r);if(t)for(let e of t.getClasses()){let t=$r(e);t&&n.push(t)}}return n}},drizzle:kr};function Y(e,t,n,r){let i={entities:new Map,relations:[],orm:n??`unknown`};if(!n)return i;let a=ei[n];if(!a)return i;let o=a.extract(e,t,r),s=new Map,c=[];for(let e of o)s.set(e.name,e),c.push(...e.relations);return{entities:s,relations:c,orm:n}}function ti(e){return{entities:[...e.entities.values()],relations:e.relations,orm:e.orm}}function ni(e,t,n,r){for(let[t,r]of e.entities)r.filePath===n&&e.entities.delete(t);let i=ei[e.orm];if(!i?.supportsIncrementalUpdate){ri(e);return}let a=i.extract(t,[n],r);for(let t of a)e.entities.set(t.name,t);ri(e)}function ri(e){let t=[];for(let n of e.entities.values())t.push(...n.relations);e.relations=t}async function X(e,t){let{config:n,fileRules:r,projectRules:i,schemaRules:a}=t,[o,s]=await Promise.all([tr(e,n),_e(e)]),c=rr(o),l=Oe(e),u=M(c,o,l),d=sr(c,o);return{astProject:c,config:n,endpointGraph:G(c,o,d),fileRules:r,files:o,moduleGraph:u,pathAliases:l,project:s,projectRules:i,providers:d,schemaGraph:Y(c,o,s.orm,e),schemaRules:a,targetPath:e}}async function ii(e,t={}){let n=await V(e,t.config);return{context:await X(e,n),customRuleWarnings:n.customRuleWarnings}}function ai(e,t){let n=e.astProject.getSourceFile(t);n&&e.astProject.removeSourceFile(n),e.astProject.addSourceFileAtPath(t),e.files.includes(t)||e.files.push(t),Fe(e.moduleGraph,e.astProject,t,e.pathAliases),cr(e.providers,e.astProject,t),br(e.endpointGraph,e.astProject,t,e.providers),e.schemaGraph&&ni(e.schemaGraph,e.astProject,t,e.targetPath)}async function oi(e,t,n){let{config:r,combinedRules:i}=t,o=await nr(e,n,r),s=await Promise.all([...o.entries()].filter(([,e])=>e.length>0).map(async([t,o])=>{let s=a(e,n.projects.get(t)),[c,l]=await Promise.all([_e(s),Se(s,r)]),u=rr(o),d=Oe(s),f=M(u,o,d),p=sr(u,o),m=G(u,o,p),h=Y(u,o,c.orm,s),{fileRules:g,projectRules:ee,schemaRules:te}=Kn(Gn(l,i));return[t,{astProject:u,config:l,endpointGraph:m,fileRules:g,files:o,moduleGraph:f,pathAliases:d,project:c,projectRules:ee,providers:p,schemaGraph:h,schemaRules:te,targetPath:s}]}));return{subProjects:new Map(s)}}const si=e=>h.makeRe(e,{windows:!1}),ci=/\\/g,li=/\/$/,ui=(e,t,n)=>{let r=new Set(Array.isArray(t.ignore?.rules)?t.ignore.rules:[]),i=Array.isArray(t.ignore?.files)?t.ignore.files.map(si):[];if(r.size===0&&i.length===0)return e;let a=n.replace(ci,`/`).replace(li,``);return e.filter(e=>{if(r.has(e.rule))return!1;let t=e.filePath.replace(ci,`/`),n=t.startsWith(`${a}/`)?t.slice(a.length+1):t;return!i.some(e=>e.test(n))})};function di(e,t,n,r){let i=[],a=[],o=e.getSourceFile(t);if(!o)return{diagnostics:i,errors:a};let s=o.getFullText().split(`
|
|
5
|
+
`);for(let e of n){let n={config:r,sourceFile:o,filePath:t,report(t){let n=[],r=Math.max(0,t.line-6),a=Math.min(s.length,t.line+5);for(let e=r;e<a;e++)n.push({line:e+1,text:s[e]});i.push({...t,rule:e.meta.id,category:e.meta.category,scope:`file`,severity:e.meta.severity,sourceLines:n})}};try{e.check(n)}catch(t){a.push({ruleId:e.meta.id,error:t})}}return{diagnostics:i,errors:a}}function fi(e,t,n,r){let i=[],a=[];for(let o of t){let t=di(e,o,n,r);i.push(...t.diagnostics),a.push(...t.errors)}return{diagnostics:i,errors:a}}function pi(e,t,n,r){let i=[],a=[];for(let o of n){let n={project:e,files:t,moduleGraph:r.moduleGraph,providers:r.providers,config:r.config,report(e){i.push({...e,rule:o.meta.id,category:o.meta.category,scope:`project`,severity:o.meta.severity})}};try{o.check(n)}catch(e){a.push({ruleId:o.meta.id,error:e})}}return{diagnostics:i,errors:a}}function mi(e,t){let n=[],r=[];for(let i of t){let t={schemaGraph:e,orm:e.orm,report(e){n.push({...e,rule:i.meta.id,category:i.meta.category,scope:`schema`,severity:i.meta.severity})}};try{i.check(t)}catch(e){r.push({ruleId:i.meta.id,error:e})}}return{diagnostics:n,errors:r}}function hi(e){return e instanceof Error?e.message:String(e)}function Z(e,t,n){return{diagnostics:ui(e,n.config,n.targetPath),errors:t.map(e=>({ruleId:e.ruleId,error:hi(e.error)}))}}function gi(e,t){let n=fi(e.astProject,[t],e.fileRules,e.config);return Z(n.diagnostics,n.errors,e)}function _i(e){let t=fi(e.astProject,e.files,e.fileRules,e.config);return Z(t.diagnostics,t.errors,e)}function vi(e){let t={moduleGraph:e.moduleGraph,providers:e.providers,config:e.config},n=pi(e.astProject,e.files,e.projectRules,t),{diagnostics:r,errors:i}=Z(n.diagnostics,n.errors,e),a=yi(e);return r.push(...a.diagnostics),i.push(...a.errors),{diagnostics:r,errors:i}}function yi(e){if(!e.schemaGraph||e.schemaRules.length===0||e.schemaGraph.entities.size===0)return{diagnostics:[],errors:[]};let t=mi(e.schemaGraph,e.schemaRules);return Z(t.diagnostics,t.errors,e)}function Q(e){let t=u.now(),n=_i(e),r=vi(e),i=u.now()-t;return{diagnostics:[...n.diagnostics,...r.diagnostics],elapsedMs:i,ruleErrors:[...n.errors,...r.errors]}}function bi(e){return e>=90?`Excellent`:e>=75?`Good`:e>=50?`Fair`:e>=25?`Poor`:`Critical`}const xi={error:3,warning:1.5,info:.5},Si={security:1.5,correctness:1.3,schema:1.1,architecture:1,performance:.8};function Ci(e,t){if(t===0)return{value:100,label:bi(100)};let n=0;for(let t of e){let e=xi[t.severity],r=Si[t.category];n+=e*r}let r=n/t,i=Math.max(0,Math.min(100,Math.round(100-r*10)));return{value:i,label:bi(i)}}function wi(e){let t={total:0,errors:0,warnings:0,info:0,byCategory:{security:0,performance:0,correctness:0,architecture:0,schema:0}};for(let n of e)t.total++,n.severity===`error`?t.errors++:n.severity===`warning`?t.warnings++:t.info++,t.byCategory[n.category]++;return t}function $(e,t,n=[]){let{diagnostics:r,ruleErrors:i,elapsedMs:a}=t,o=e.schemaGraph??Y(e.astProject,e.files,e.project.orm,e.targetPath),s=Ci(r,e.files.length),c=wi(r);return{result:{score:s,diagnostics:r,endpoints:e.endpointGraph,project:{...e.project,fileCount:e.files.length,moduleCount:e.moduleGraph.modules.size},summary:c,ruleErrors:i,elapsedMs:a,schema:ti(o)},moduleGraph:e.moduleGraph,schemaGraph:o,customRuleWarnings:n,files:e.files,providers:e.providers}}function Ti(e,t,n,r){let i=[],a=[],o=[],s=new Map,c=[],l=0,u=[],d=[],f=``;for(let[n,r]of e.subProjects){let e=$(r,t.get(n));i.push({name:n,result:e.result}),s.set(n,e.moduleGraph),a.push(...e.result.diagnostics),o.push(...e.result.ruleErrors),l+=e.result.project.fileCount,e.result.endpoints&&c.push(...e.result.endpoints.endpoints),e.result.schema&&(u.push(...e.result.schema.entities),d.push(...e.result.schema.relations),e.result.schema.orm&&e.result.schema.orm!==`unknown`&&(f=e.result.schema.orm))}let p=Ci(a,l),m=wi(a);return{moduleGraphs:s,customRuleWarnings:n,result:{isMonorepo:!0,subProjects:i,combined:{score:p,diagnostics:a,endpoints:c.length>0?{endpoints:c}:void 0,project:{name:`monorepo`,nestVersion:i[0]?.result.project.nestVersion??null,orm:f||(i[0]?.result.project.orm??null),framework:i[0]?.result.project.framework??null,fileCount:l,moduleCount:i.reduce((e,t)=>e+t.result.project.moduleCount,0)},summary:m,ruleErrors:o,elapsedMs:r,schema:u.length>0?{entities:u,relations:d,orm:f||`unknown`}:void 0},elapsedMs:r}}}async function Ei(e,t,n){let r=u.now(),i=await oi(e,t,n),a=new Map;for(let[e,t]of i.subProjects)a.set(e,Q(t));let o=u.now()-r;return Ti(i,a,t.customRuleWarnings,o)}async function Di(e,t={}){let n=await V(e,t.config),r=await ge(e);if(r)return{isMonorepo:!0,monorepo:await Ei(e,n,r)};let i=await X(e,n);return{isMonorepo:!1,single:$(i,Q(i),n.customRuleWarnings)}}function Oi(e){return`line`in e}function ki(e){return`entity`in e}function Ai(t){if(!t||t.trim()===``)throw new _(`Path must be a non-empty string. Received an empty path.`);let n=s(t);if(!e(n))throw new _(`Path does not exist: ${n}`);if(!r(n).isDirectory())throw new _(`Path must be a directory, not a file: ${n}`);return n}async function ji(e,t={}){let n=Ai(e),r=await V(n,t.config),i=await X(n,r),{result:a}=$(i,Q(i),r.customRuleWarnings);return a}async function Mi(e,t={}){let n=Ai(e),r=await V(n,t.config),i=await ge(n);if(!i){let e=await X(n,r),{result:t}=$(e,Q(e),r.customRuleWarnings);return{isMonorepo:!1,subProjects:[{name:`default`,result:t}],combined:t,elapsedMs:t.elapsedMs}}let{result:a}=await Ei(n,r,i);return a}export{ee as ConfigurationError,g as NestjsDoctorError,te as ScanError,_ as ValidationError,Di as autoScan,X as buildAnalysisContext,G as buildEndpointGraph,$ as buildResult,_i as checkAllFiles,gi as checkFile,vi as checkProject,yi as checkSchema,ji as diagnose,Mi as diagnoseMonorepo,Y as extractSchema,Vn as getRules,Oi as isCodeDiagnostic,ki as isSchemaDiagnostic,ii as prepareAnalysis,V as resolveScanConfig,yr as traceEndpointCalls,br as updateEndpointGraphForFile,ai as updateFile,Fe as updateModuleGraphForFile,cr as updateProvidersForFile};
|
package/dist/cli/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import{createRequire as e}from"node:module";import{defineCommand as t,runMain as n}from"citty";import{readFile as r}from"node:fs/promises";import{dirname as i,join as a,relative as o,resolve as s}from"node:path";import{glob as c}from"tinyglobby";import{performance as l}from"node:perf_hooks";import{Project as u,SyntaxKind as d,ts as f}from"ts-morph";import{existsSync as p,readFileSync as m,readdirSync as h,statSync as g}from"node:fs";import{createJiti as _}from"jiti";import v from"picomatch";import y from"picocolors";import b from"ora";const ee=/^packages\s*:/,te=/^packages\s*:\s*\[(.+)\]/,ne=/^\S/,re=/^-\s+['"]?([^'"]+)['"]?\s*$/,ie=/^['"]|['"]$/g;function ae(e){let t=[],n=e.split(`
|
|
3
|
-
`),r=!1;for(let e of n){let n=e.trim();if(ee.test(n)){let e=n.match(te);if(e){for(let n of e[1].split(`,`)){let e=n.trim().replace(ie,``);e&&t.push(e)}return t}r=!0;continue}if(r){if(ne.test(e)&&n!==``)break;let r=n.match(re);r&&t.push(r[1])}}return t}async function oe(e){let t=a(e,`nest-cli.json`);try{let e=await r(t,`utf-8`),n=JSON.parse(e);if(!(n.monorepo&&n.projects))return null;let i=new Map;for(let[e,t]of Object.entries(n.projects)){let n=t.root??e;i.set(e,n)}return i.size===0?null:{projects:i}}catch{return null}}function se(e){let t={...e.dependencies,...e.devDependencies,...e.peerDependencies};return!!(t[`@nestjs/core`]||t[`@nestjs/common`])}async function x(e,t){let n=await c(t.map(e=>`${e}/package.json`),{cwd:e,absolute:!0,ignore:[`**/node_modules/**`]}),a=new Map;for(let t of n)try{let n=await r(t,`utf-8`),s=JSON.parse(n);if(se(s)){let n=o(e,i(t)),r=s.name??n;a.set(r,n)}}catch{}return a.size===0?null:{projects:a}}async function ce(e){let t=a(e,`pnpm-workspace.yaml`),n;try{n=await r(t,`utf-8`)}catch{return null}let i=ae(n);return i.length===0?null:x(e,i)}function le(e){let t=e.workspaces;if(!t)return[];if(Array.isArray(t))return t.filter(e=>typeof e==`string`);if(typeof t==`object`&&t){let e=t;if(Array.isArray(e.packages))return e.packages.filter(e=>typeof e==`string`)}return[]}async function ue(e){let t=a(e,`package.json`),n;try{n=await r(t,`utf-8`)}catch{return null}let i=le(JSON.parse(n));return i.length===0?null:x(e,i)}async function de(e){let t=a(e,`lerna.json`),n;try{n=await r(t,`utf-8`)}catch{return null}let i=JSON.parse(n);if(i.useWorkspaces)return null;let o=i.packages??[`packages/*`];return o.length===0?null:x(e,o)}async function fe(e){let t=a(e,`nx.json`);try{await r(t,`utf-8`)}catch{return null}let n=await c([`**/project.json`],{cwd:e,absolute:!0,ignore:[`node_modules/**`]}),s=new Map;for(let t of n){let n=i(t),c=o(e,n);if(c===``)continue;let l=a(n,`package.json`);try{let e=await r(l,`utf-8`),t=JSON.parse(e);if(se(t)){let e=t.name??c;s.set(e,c)}}catch{}}return s.size===0?null:{projects:s}}async function pe(e){try{return await r(a(e,`pnpm-workspace.yaml`),`utf-8`),!0}catch{return!1}}async function me(e){let t=await oe(e);if(t)return t;let n=await ce(e);if(n)return n;if(!await pe(e)){let t=await ue(e);if(t)return t}return await fe(e)||de(e)}async function he(e){for(let t of[`lerna.json`,`turbo.json`,`nx.json`,`pnpm-workspace.yaml`])try{return await r(a(e,t),`utf-8`),!0}catch{}try{let t=await r(a(e,`package.json`),`utf-8`);if(JSON.parse(t).workspaces)return!0}catch{}return!1}async function ge(e){let t=a(e,`package.json`),n={};try{let e=await r(t,`utf-8`);n=JSON.parse(e)}catch{}let i={...n.dependencies,...n.devDependencies},o=_e(i[`@nestjs/core`]),s=ve(i),c=ye(i);return{name:n.name??`unknown`,nestVersion:o,orm:s,framework:c,moduleCount:0,fileCount:0}}function _e(e){return e?e.replace(/[\^~>=<]/g,``):null}function ve(e){return e[`@prisma/client`]?`prisma`:e.typeorm?`typeorm`:e[`@mikro-orm/core`]?`mikro-orm`:e.sequelize?`sequelize`:e.mongoose?`mongoose`:e[`drizzle-orm`]?`drizzle`:null}function ye(e){return e[`@nestjs/platform-fastify`]?`fastify`:e[`@nestjs/platform-express`]||e[`@nestjs/core`]?`express`:null}const be={verbose:{type:`boolean`,description:`Show file paths and line numbers per diagnostic`,default:!1},score:{type:`boolean`,description:`Output only the numeric score (for CI)`,default:!1},json:{type:`boolean`,description:`JSON output`,default:!1},"min-score":{type:`string`,description:`Minimum passing score (0-100). Exits with code 1 if below threshold`},config:{type:`string`,description:`Config file path`},report:{type:`boolean`,alias:`graph`,description:`Generate an interactive HTML report (summary, diagnostics, module graph, rule lab)`,default:!1},init:{type:`boolean`,description:`Set up the nestjs-doctor skill for AI coding agents (Claude Code, Cursor, Codex, etc.)`,default:!1}},S={include:[`**/*.ts`],exclude:`**/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,*.config.ts,*.config.js,*.config.mjs,*.config.cjs,*.config.mts,*.config.cts`.split(`,`)},xe=[`nestjs-doctor.config.json`,`.nestjs-doctor.json`];async function Se(e,t){if(t)return Ce(t);for(let t of xe)try{return await Ce(a(e,t))}catch{}try{let t=await r(a(e,`package.json`),`utf-8`),n=JSON.parse(t);if(n[`nestjs-doctor`]&&typeof n[`nestjs-doctor`]==`object`)return we(n[`nestjs-doctor`])}catch{}return{...S}}async function Ce(e){let t=await r(e,`utf-8`);return we(JSON.parse(t))}function we(e){return{...S,...e,exclude:[...S.exclude??[],...e.exclude??[]]}}async function Te(e,t){try{return await Se(e)}catch{return t}}const Ee=[/Repository$/,/\.repository$/,/\.entity$/,/\.schema$/,/\.guard$/,/\.interceptor$/,/\.pipe$/,/\.filter$/,/\.strategy$/],De={meta:{id:`architecture/no-barrel-export-internals`,category:`architecture`,severity:`info`,description:`Don't re-export internal implementation details from barrel files`,help:`Only export the module's public API (services, DTOs, interfaces) from index.ts files.`},check(e){if(e.filePath.endsWith(`/index.ts`))for(let t of e.sourceFile.getExportDeclarations()){let n=t.getModuleSpecifierValue();if(n){Ee.some(e=>e.test(n))&&e.report({filePath:e.filePath,message:`Barrel file re-exports internal module '${n}'.`,help:this.meta.help,line:t.getStartLineNumber(),column:1});for(let n of t.getNamedExports()){let t=n.getName();(t.endsWith(`Repository`)||t.endsWith(`Entity`)||t.endsWith(`Schema`))&&e.report({filePath:e.filePath,message:`Barrel file re-exports internal type '${t}'.`,help:this.meta.help,line:n.getStartLineNumber(),column:1})}}}}},C=new Set([`Get`,`Post`,`Put`,`Patch`,`Delete`,`Head`,`Options`,`All`]);function w(e,t){return e.getDecorator(t)!==void 0}function T(e){return w(e,`Controller`)}function Oe(e){return w(e,`Injectable`)}function ke(e){return w(e,`Injectable`)||w(e,`Controller`)||w(e,`Resolver`)||w(e,`WebSocketGateway`)}function Ae(e){return w(e,`Module`)}function E(e){return e.getDecorators().some(e=>C.has(e.getName()))}const je=new Set([`TsRestHandler`,`GrpcMethod`,`GrpcStreamMethod`]);function Me(e){return e.getDecorators().some(e=>je.has(e.getName()))}const Ne={meta:{id:`architecture/no-business-logic-in-controllers`,category:`architecture`,severity:`error`,description:`Controllers should only handle HTTP concerns — move business logic to services`,help:`Extract branches, loops, and complex calculations into a service method.`},check(e){for(let t of e.sourceFile.getClasses())if(T(t))for(let n of t.getMethods()){if(!n.getDecorators().some(e=>C.has(e.getName())))continue;let t=n.getBody();if(!t)continue;let r=t.getDescendantsOfKind(d.IfStatement),i=t.getDescendantsOfKind(d.ForStatement),a=t.getDescendantsOfKind(d.ForInStatement),o=t.getDescendantsOfKind(d.ForOfStatement),s=t.getDescendantsOfKind(d.WhileStatement),c=t.getDescendantsOfKind(d.SwitchStatement),l=i.length+a.length+o.length+s.length;(r.length>1||l>0||c.length>0)&&e.report({filePath:e.filePath,message:`Controller method '${n.getName()}' contains business logic (${r.length} if, ${l} loops, ${c.length} switch). Move to a service.`,help:this.meta.help,line:n.getStartLineNumber(),column:1});let u=t.getDescendantsOfKind(d.CallExpression).filter(e=>{let t=e.getExpression();if(t.getKind()===d.PropertyAccessExpression){let e=t.asKind(d.PropertyAccessExpression)?.getName();return e===`map`||e===`filter`||e===`reduce`||e===`sort`||e===`flatMap`}return!1});u.length>1&&e.report({filePath:e.filePath,message:`Controller method '${n.getName()}' contains data transformation logic (${u.length} array operations). Move to a service.`,help:this.meta.help,line:n.getStartLineNumber(),column:1})}}};function Pe(e){let t=new Map;try{let n=f.findConfigFile(e,f.sys.fileExists,`tsconfig.json`);if(!n)return t;let{config:r,error:a}=f.readConfigFile(n,f.sys.readFile);if(a||!r)return t;let o=i(n),c=f.parseJsonConfigFileContent(r,f.sys,o),l=c.options.paths;if(!l)return t;let u=c.options.baseUrl??o;for(let[e,n]of Object.entries(l)){let r=n.map(e=>s(u,e));t.set(e,r)}}catch{return t}return t}function Fe(e,t){for(let[n,r]of t){if(r.length===0)continue;let t=n.indexOf(`*`);if(t===-1){if(e===n)return r[0];continue}let i=n.slice(0,t),a=n.slice(t+1);if(e.startsWith(i)&&e.endsWith(a)&&e.length>=i.length+a.length){let t=e.slice(i.length,e.length-a.length),n=r[0],o=n.indexOf(`*`);return o===-1?n:n.slice(0,o)+t+n.slice(o+1)}}}const Ie=/=>\s*(\w+)/,Le=/\.js$/;function Re(e,t,n){let r=[];for(let i of e.getClasses()){let e=i.getDecorator(`Module`);if(!e)continue;let a=i.getName()??`AnonymousModule`,o=e.getArguments()[0],s={name:a,filePath:t,classDeclaration:i,imports:[],exports:[],providers:[],controllers:[]};if(o&&o.getKind()===d.ObjectLiteralExpression){let e=o.asKind(d.ObjectLiteralExpression);e&&(s.imports=D(e,`imports`,n),s.exports=D(e,`exports`,n),s.providers=D(e,`providers`,n),s.controllers=D(e,`controllers`,n))}r.push(s)}return r}function ze(e,t,n=new Map){let r=new Map,i=new Map;for(let i of t){let t=e.getSourceFile(i);if(t)for(let e of Re(t,i,n))r.set(e.name,e)}for(let[e,t]of r){let n=new Set;for(let e of t.imports)r.has(e)&&n.add(e);i.set(e,n)}let a=new Map;for(let e of r.values())for(let t of e.providers)a.set(t,e);return{modules:r,edges:i,providerToModule:a}}const Be=new Set([`forRoot`,`forRootAsync`,`forFeature`,`forFeatureAsync`,`forChild`,`forChildAsync`,`register`,`registerAsync`]);function D(e,t,n){let r=e.getProperty(t);if(!r)return[];let i=r.asKind(d.PropertyAssignment);if(!i)return[];let a=i.getInitializer();return a?O(a,e.getSourceFile(),0,n):[]}function O(e,t,n,r){if(n>5)return[];let i=e.getKind();if(i===d.ArrayLiteralExpression){let i=e.asKindOrThrow(d.ArrayLiteralExpression),a=[];for(let e of i.getElements())a.push(...Ve(e,t,n,r));return a}return i===d.CallExpression?He(e.asKindOrThrow(d.CallExpression),t,n,r):i===d.Identifier?Ge(e.getText(),t,n+1,r):[]}function Ve(e,t,n,r){let i=e.getText();if(i.startsWith(`forwardRef`)){let e=i.match(Ie);return e?[e[1]]:[i]}let a=e.getKind();return a===d.SpreadElement?O(e.asKindOrThrow(d.SpreadElement).getExpression(),t,n,r):a===d.CallExpression?He(e.asKindOrThrow(d.CallExpression),t,n,r):a===d.PropertyAccessExpression?[e.asKindOrThrow(d.PropertyAccessExpression).getExpression().getText()]:(d.Identifier,[i])}function He(e,t,n,r){let i=e.getExpression();if(i.getKind()===d.PropertyAccessExpression){let a=i.asKindOrThrow(d.PropertyAccessExpression),o=a.getName();if(o===`concat`){let i=O(a.getExpression(),t,n,r),o=[];for(let i of e.getArguments())o.push(...O(i,t,n,r));return[...i,...o]}return Be.has(o),[a.getExpression().getText()]}return i.getKind()===d.Identifier?qe(i.getText(),t,n+1,r):[]}function Ue(e,t,n){if(!e.startsWith(`.`)){let r=Fe(e,n);if(!r)return;let i=t.getProject(),a=[`${r}.ts`,`${r}/index.ts`,r,r.replace(Le,`.ts`)];for(let e of a){let t=i.getSourceFile(e);if(t)return t}return}let r=s(i(t.getFilePath()),e),a=t.getProject(),o=[`${r}.ts`,`${r}/index.ts`,r,r.replace(Le,`.ts`)];for(let e of o){let t=a.getSourceFile(e);if(t)return t}}function We(e,t,n){for(let r of t.getImportDeclarations())for(let i of r.getNamedImports())if((i.getAliasNode()?i.getAliasNode().getText():i.getName())===e){let e=Ue(r.getModuleSpecifierValue(),t,n);return e?{sourceFile:e,localName:i.getName()}:void 0}for(let r of t.getExportDeclarations())if(r.getModuleSpecifierValue()){for(let i of r.getNamedExports())if((i.getAliasNode()?i.getAliasNode().getText():i.getName())===e){let e=Ue(r.getModuleSpecifierValue(),t,n);return e?{sourceFile:e,localName:i.getName()}:void 0}}}function Ge(e,t,n,r){if(n>5)return[];for(let i of t.getStatements()){if(i.getKind()!==d.VariableStatement)continue;let a=i.asKindOrThrow(d.VariableStatement);for(let i of a.getDeclarations())if(i.getName()===e){let e=i.getInitializer();if(e)return O(e,t,n,r)}}let i=We(e,t,r);return i?Ge(i.localName,i.sourceFile,n+1,r):[]}function Ke(e,t,n,r){for(let i of t.getStatements()){if(i.getKind()!==d.VariableStatement)continue;let a=i.asKindOrThrow(d.VariableStatement);for(let i of a.getDeclarations()){if(i.getName()!==e)continue;let a=i.getInitializer();if(!a||a.getKind()!==d.ArrowFunction)continue;let o=a.asKindOrThrow(d.ArrowFunction).getBody();if(o.getKind()!==d.Block)return O(o,t,n,r);let s=[];for(let e of o.getDescendantsOfKind(d.ReturnStatement)){let i=e.getExpression();i&&s.push(...O(i,t,n,r))}return s}}}function qe(e,t,n,r){if(n>5)return[];for(let i of t.getStatements()){if(i.getKind()!==d.FunctionDeclaration)continue;let a=i.asKindOrThrow(d.FunctionDeclaration);if(a.getName()!==e)continue;let o=[];for(let e of a.getDescendantsOfKind(d.ReturnStatement)){let i=e.getExpression();i&&o.push(...O(i,t,n,r))}return o}let i=Ke(e,t,n,r);if(i)return i;let a=We(e,t,r);return a?qe(a.localName,a.sourceFile,n+1,r):[]}function Je(e){let t=new Map,n=new Map,r=new Map;for(let[i,a]of e){for(let[e,n]of a.modules){let r=`${i}/${e}`,o={...n,name:r,imports:n.imports.map(e=>a.modules.has(e)?`${i}/${e}`:e),exports:n.exports.map(e=>a.modules.has(e)?`${i}/${e}`:e)};t.set(r,o)}for(let[e,t]of a.edges){let r=`${i}/${e}`,a=new Set;for(let e of t)a.add(`${i}/${e}`);n.set(r,a)}for(let[e,n]of a.providerToModule){let a=`${i}/${n.name}`,o=t.get(a);o&&r.set(`${i}/${e}`,o)}}return{modules:t,edges:n,providerToModule:r}}function Ye(e){let t=[],n=new Set,r=new Set;function i(a,o){n.add(a),r.add(a);let s=e.edges.get(a)??new Set;for(let e of s)if(!n.has(e))i(e,[...o,e]);else if(r.has(e)){let n=o.indexOf(e);n===-1?t.push([...o,e]):t.push(o.slice(n))}r.delete(a)}for(let t of e.modules.keys())n.has(t)||i(t,[t]);return t}function Xe(e,t,n,r,i,a){let o=[];for(let i of e.providers){let e=n.get(i);if(e)for(let n of e.dependencies){let e=r.get(n);e&&e.name===t.name&&o.push({consumer:i,dependency:n})}}for(let n of e.controllers)for(let e of a){let a=i.getSourceFile(e);if(a)for(let e of a.getClasses()){if(e.getName()!==n)continue;let i=e.getConstructors()[0];if(i)for(let e of i.getParameters()){let i=e.getTypeNode(),a=i?i.getText():e.getType().getText(),s=a.split(`.`).pop()?.split(`<`)[0]??a,c=r.get(s);c&&c.name===t.name&&o.push({consumer:n,dependency:s})}}}return o}const Ze=`Break the cycle by extracting shared logic into a separate module or using forwardRef().`;function Qe(e,t){let{moduleGraph:n,providers:r,project:i,files:a}=t,o=[],s;for(let t=0;t<e.length;t++){let c=e[t],l=e[(t+1)%e.length],u=n.modules.get(c),d=n.modules.get(l);if(!(u&&d))continue;let f=Xe(u,d,r,n.providerToModule,i,a);if(f.length===0)continue;let p=new Map;for(let e of f){let t=p.get(e.consumer);t?t.push(e.dependency):p.set(e.consumer,[e.dependency])}let m=[];for(let[e,t]of p){let n=t.map(e=>`${e} (from ${l})`).join(`, `);m.push(`${e} (in ${c}) injects ${n}`)}let h=`${c} -> ${l}: ${m.join(`; `)}`;o.push(h),(!s||f.length<s.count)&&(s={description:`${c} -> ${l}`,count:f.length})}if(o.length===0)return Ze;let c=o.join(`
|
|
4
|
-
`);if(s){let e=s.count===1?`dependency`:`dependencies`,t=s.description.split(` -> `)[0],o=s.description.split(` -> `)[1],l=n.modules.get(t),u=n.modules.get(o);if(l&&u){let t=Xe(l,u,r,n.providerToModule,i,a),o=[...new Set(t.map(e=>e.dependency))].join(`, `);c+=`\nConsider extracting ${o} into a shared module — it would break the ${s.description} edge (${s.count} ${e}).`}}return c}const $e={meta:{id:`architecture/no-circular-module-deps`,category:`architecture`,severity:`error`,description:`Circular dependencies in @Module() import graph`,help:Ze,scope:`project`},check(e){let t=Ye(e.moduleGraph);for(let n of t){let t=n.join(` -> `),r=e.moduleGraph.modules.get(n[0]),i=Qe(n,e);e.report({filePath:r?.filePath??`unknown`,message:`Circular module dependency detected: ${t}`,help:i,line:r?.classDeclaration.getStartLineNumber()??1,column:1})}}},et=[`Service`,`Repository`,`Gateway`,`Resolver`],tt=[`Guard`,`Interceptor`,`Pipe`,`Filter`];function k(e){return typeof e==`object`&&!!e}function nt(e){if(!k(e))return new Set;let t=e.excludeClasses;if(Array.isArray(t))return new Set(t.filter(e=>typeof e==`string`));let n=e.options;if(!k(n))return new Set;let r=n.excludeClasses;return Array.isArray(r)?new Set(r.filter(e=>typeof e==`string`)):new Set}const rt={meta:{id:`architecture/no-manual-instantiation`,category:`architecture`,severity:`error`,description:`Do not manually instantiate @Injectable classes — use NestJS dependency injection`,help:`Register the class as a provider in a module and inject it via the constructor.`},check(e){let t=nt(e.config?.rules?.[this.meta.id]),n=e.sourceFile.getDescendantsOfKind(d.NewExpression);for(let r of n){let n=r.getExpression().getText(),i=n.split(`.`).pop()??n;if(t.has(n)||t.has(i))continue;let a=et.some(e=>n.endsWith(e)),o=tt.some(e=>n.endsWith(e));if(a||o){if(o){if(r.getFirstAncestorByKind(d.Decorator))continue;let e=r.getFirstAncestorByKind(d.MethodDeclaration),t=r.getFirstAncestorByKind(d.Constructor);if(!(e||t))continue}e.report({filePath:e.filePath,message:`Manual instantiation of '${n}' detected. Use dependency injection instead.`,help:this.meta.help,line:r.getStartLineNumber(),column:1})}}}},it=/\.(\w+)$/,at=/^(\w+)</,ot=new Set([`PrismaService`,`PrismaClient`,`EntityManager`,`DataSource`,`Repository`,`Connection`,`MongooseModel`,`InjectModel`,`InjectRepository`,`MikroORM`,`DrizzleService`]),st={meta:{id:`architecture/no-orm-in-controllers`,category:`architecture`,severity:`error`,description:`Controllers must not inject ORM services directly — use a service layer`,help:`Inject a service that wraps the ORM instead of using the ORM directly in controllers.`},check(e){for(let t of e.sourceFile.getClasses()){if(!T(t))continue;let n=t.getConstructors()[0];if(n){for(let t of n.getParameters()){let n=ct(t.getType().getText());if(ot.has(n)){let r=t.getNameNode();e.report({filePath:e.filePath,message:`Controller injects ORM type '${n}' directly. Use a service layer.`,help:this.meta.help,line:r.getStartLineNumber(),column:r.getStartLinePos()+1})}}for(let n of t.getConstructors()[0]?.getParameters()??[])for(let t of n.getDecorators()){let n=t.getName();(n===`InjectRepository`||n===`InjectModel`)&&e.report({filePath:e.filePath,message:`Controller uses @${n}() decorator. Move data access to a service.`,help:this.meta.help,line:t.getStartLineNumber(),column:1})}}}}};function ct(e){let t=e.match(it);if(t)return t[1];let n=e.match(at);return n?n[1]:e}const lt=/\.(\w+)$/,ut=/^(\w+)</,dt=new Set([`PrismaService`,`PrismaClient`,`EntityManager`,`DataSource`,`Connection`,`MikroORM`]),ft={meta:{id:`architecture/no-orm-in-services`,category:`architecture`,severity:`warning`,description:`Services should use repository abstractions instead of ORM directly`,help:`Create a repository class that wraps ORM calls and inject that instead.`},check(e){for(let t of e.sourceFile.getClasses()){if(!Oe(t))continue;let n=t.getName()??``;if(n.endsWith(`Repository`)||n.endsWith(`Repo`))continue;let r=t.getConstructors()[0];if(r)for(let t of r.getParameters()){let n=pt(t.getType().getText());if(dt.has(n)){let r=t.getNameNode();e.report({filePath:e.filePath,message:`Service injects ORM type '${n}' directly. Consider using a repository abstraction.`,help:this.meta.help,line:r.getStartLineNumber(),column:r.getStartLinePos()+1})}for(let n of t.getDecorators()){let t=n.getName();(t===`InjectRepository`||t===`InjectModel`)&&e.report({filePath:e.filePath,message:`Service uses @${t}() directly. Consider wrapping in a repository class.`,help:this.meta.help,line:n.getStartLineNumber(),column:1})}}}}};function pt(e){let t=e.match(lt);if(t)return t[1];let n=e.match(ut);return n?n[1]:e}const mt=/\.(\w+)$/,ht=/^(\w+)</,gt=[/Repository$/,/Repo$/],_t={meta:{id:`architecture/no-repository-in-controllers`,category:`architecture`,severity:`error`,description:`Controllers must not inject repositories directly — use the service layer`,help:`Move database access to a service and inject the service into the controller instead.`},check(e){for(let t of e.sourceFile.getClasses()){if(!T(t))continue;let n=t.getConstructors()[0];if(n){for(let t of n.getParameters()){let n=vt(t.getType().getText());if(gt.some(e=>e.test(n))){let r=t.getNameNode();e.report({filePath:e.filePath,message:`Controller injects repository '${n}' directly. Use a service layer instead.`,help:this.meta.help,line:r.getStartLineNumber(),column:r.getStartLinePos()+1})}}for(let t of e.sourceFile.getImportDeclarations()){let n=t.getModuleSpecifierValue();(n.includes(`/repositories/`)||n.includes(`/repositories`))&&e.report({filePath:e.filePath,message:`Controller imports from repository path '${n}'.`,help:this.meta.help,line:t.getStartLineNumber(),column:1})}}}}};function vt(e){let t=e.match(mt);if(t)return t[1];let n=e.match(ht);return n?n[1]:e}const yt={meta:{id:`architecture/no-service-locator`,category:`architecture`,severity:`warning`,description:`Avoid using ModuleRef.get() or ModuleRef.resolve() — prefer explicit constructor injection`,help:`Replace ModuleRef.get()/resolve() with constructor injection for explicit, testable dependencies.`},check(e){let t=e.sourceFile.getDescendantsOfKind(d.CallExpression);for(let n of t){let t=n.getExpression();if(t.getKind()!==d.PropertyAccessExpression)continue;let r=t.asKind(d.PropertyAccessExpression);if(!r)continue;let i=r.getName();if(i!==`get`&&i!==`resolve`)continue;let a=r.getExpression().getText();(a===`moduleRef`||a===`this.moduleRef`)&&e.report({filePath:e.filePath,message:`Service locator pattern: '${a}.${i}()' hides dependencies. Use constructor injection instead.`,help:this.meta.help,line:n.getStartLineNumber(),column:1})}}},bt={meta:{id:`architecture/prefer-constructor-injection`,category:`architecture`,severity:`warning`,description:`Prefer constructor injection over @Inject() property injection`,help:`Move the dependency to a constructor parameter instead of using property injection.`},check(e){for(let t of e.sourceFile.getClasses())if(ke(t))for(let n of t.getProperties())n.getDecorator(`Inject`)&&e.report({filePath:e.filePath,message:`Property '${n.getName()}' uses @Inject() decorator. Prefer constructor injection.`,help:this.meta.help,line:n.getStartLineNumber(),column:1})}},xt=[`/repositories/`,`/entities/`,`/dto/`,`/guards/`,`/interceptors/`,`/pipes/`,`/strategies/`],St={meta:{id:`architecture/require-module-boundaries`,category:`architecture`,severity:`info`,description:`Avoid deep imports into other feature modules' internals`,help:`Import from the module's public API (barrel export) instead of reaching into its internals.`},check(e){for(let t of e.sourceFile.getImportDeclarations()){let n=t.getModuleSpecifierValue();n.startsWith(`.`)&&n.includes(`../`)&&xt.some(e=>n.includes(e))&&e.report({filePath:e.filePath,message:`Import '${n}' reaches into another module's internals.`,help:this.meta.help,line:t.getStartLineNumber(),column:1})}}};function A(e){return e.getDescendantsOfKind(d.ReturnStatement).some(e=>{let t=e.getExpression();return!t||t.getKind()!==d.NewExpression?!1:t.asKindOrThrow(d.NewExpression).getExpression().getText()===`Promise`})}const Ct={meta:{id:`correctness/no-async-without-await`,category:`correctness`,severity:`warning`,description:`Async functions/methods should contain at least one await expression`,help:`Either add an await expression or remove the async keyword.`},check(e){for(let t of e.sourceFile.getClasses())for(let n of t.getMethods()){if(!n.isAsync()||T(t)&&E(n)||Me(n))continue;let r=n.getBody();if(r&&r.getDescendantsOfKind(d.AwaitExpression).filter(e=>{let t=e.getParent();for(;t&&t!==r;){if(t.getKind()===d.ArrowFunction||t.getKind()===d.FunctionExpression||t.getKind()===d.FunctionDeclaration)return!1;t=t.getParent()}return!0}).length===0){let t=n.getName();A(r)?e.report({filePath:e.filePath,message:`Async method '${t}()' returns a Promise directly — remove the async keyword.`,help:`The async keyword is unnecessary when you are already constructing a Promise manually. Remove async to avoid double-wrapping.`,line:n.getStartLineNumber(),column:1}):e.report({filePath:e.filePath,message:`Async method '${t}()' has no await expression.`,help:this.meta.help,line:n.getStartLineNumber(),column:1})}}for(let t of e.sourceFile.getFunctions()){if(!t.isAsync())continue;let n=t.getBody();if(n&&n.getDescendantsOfKind(d.AwaitExpression).filter(e=>{let t=e.getParent();for(;t&&t!==n;){if(t.getKind()===d.ArrowFunction||t.getKind()===d.FunctionExpression||t.getKind()===d.FunctionDeclaration)return!1;t=t.getParent()}return!0}).length===0){let r=t.getName()??`anonymous`;A(n)?e.report({filePath:e.filePath,message:`Async function '${r}()' returns a Promise directly — remove the async keyword.`,help:`The async keyword is unnecessary when you are already constructing a Promise manually. Remove async to avoid double-wrapping.`,line:t.getStartLineNumber(),column:1}):e.report({filePath:e.filePath,message:`Async function '${r}()' has no await expression.`,help:this.meta.help,line:t.getStartLineNumber(),column:1})}}}},wt=[`providers`,`controllers`,`imports`,`exports`],Tt={meta:{id:`correctness/no-duplicate-module-metadata`,category:`correctness`,severity:`warning`,description:`Same identifier should not appear twice in a module metadata array`,help:`Remove the duplicate entry from the module metadata.`},check(e){for(let t of e.sourceFile.getClasses()){if(!Ae(t))continue;let n=t.getDecorator(`Module`);if(!n)continue;let r=n.getArguments()[0];if(!r||r.getKind()!==d.ObjectLiteralExpression)continue;let i=r.asKind(d.ObjectLiteralExpression);if(i)for(let t of wt){let n=i.getProperty(t);if(!n)continue;let r=n.getChildrenOfKind(d.ArrayLiteralExpression)[0];if(!r)continue;let a=new Set;for(let n of r.getElements()){let r=n.getText();a.has(r)?e.report({filePath:e.filePath,message:`Duplicate '${r}' in @Module() ${t} array.`,help:this.meta.help,line:n.getStartLineNumber(),column:1}):a.add(r)}}}}},Et=new Set([`Get`,`Post`,`Put`,`Delete`,`Patch`,`All`,`Head`,`Options`]),Dt={meta:{id:`correctness/no-duplicate-routes`,category:`correctness`,severity:`error`,description:`Same HTTP method + route path + version should not appear twice in a single controller`,help:`Remove or rename one of the duplicate route handlers.`},check(e){for(let t of e.sourceFile.getClasses()){if(!T(t))continue;let n=new Map;for(let r of t.getMethods())for(let t of r.getDecorators()){let i=t.getName();if(!Et.has(i))continue;let a=t.getArguments(),o=a.length>0?a[0].getText():`""`,s=r.getDecorator(`Version`),c=`${i}:${o}:${s?s.getArguments()[0]?.getText()??``:``}`,l=n.get(c);l?e.report({filePath:e.filePath,message:`Duplicate route: @${i}(${o}) is already defined in '${l}()'.`,help:this.meta.help,line:r.getStartLineNumber(),column:1}):n.set(c,r.getName())}}}},Ot={meta:{id:`correctness/no-empty-handlers`,category:`correctness`,severity:`warning`,description:`Controller HTTP handlers should not have empty bodies`,help:`Add implementation to the handler method or remove it if unnecessary.`},check(e){for(let t of e.sourceFile.getClasses())if(T(t))for(let n of t.getMethods()){if(!n.getDecorators().some(e=>C.has(e.getName())))continue;let t=n.getBody();if(!t)continue;let r=t.asKind(d.Block);r&&r.getStatements().length===0&&e.report({filePath:e.filePath,message:`Handler '${n.getName()}()' has an empty body.`,help:this.meta.help,line:n.getStartLineNumber(),column:1})}}};function kt(e){let t=e.getReturnType().getText();return t.startsWith(`Promise<`)||t===`Promise`?!0:t===`any`||t===`error`?`unknown`:!1}const j=new Set([`save`,`create`,`insert`,`update`,`delete`,`remove`,`send`,`emit`,`publish`,`dispatch`,`execute`,`fetch`,`load`,`upload`,`download`,`process`]),At={meta:{id:`correctness/no-fire-and-forget-async`,category:`correctness`,severity:`warning`,description:`Calling async functions without await leads to unhandled promise rejections`,help:`Add await before the async call, or use void with explicit error handling if fire-and-forget is intentional.`},check(e){for(let t of e.sourceFile.getClasses())for(let n of t.getMethods()){if(E(n))continue;let t=n.getBody();if(!t)continue;let r=t.getDescendantsOfKind(d.ExpressionStatement);for(let t of r){let r=t.getExpression();if(r.getKind()===d.VoidExpression||r.getKind()===d.AwaitExpression||r.getKind()!==d.CallExpression)continue;let i=r.asKind(d.CallExpression);if(!i)continue;let a=i.getExpression().getText().split(`.`).pop()??``,o=kt(i);if(o!==!1){if(o===`unknown`){let e=a.toLowerCase();if(!(j.has(e)||[...j].some(t=>e.startsWith(t)&&e!==t)))continue}t.getFirstAncestorByKind(d.MethodDeclaration)===n&&e.report({filePath:e.filePath,message:`Async call '${a}()' is not awaited — unhandled rejections will crash the process.`,help:this.meta.help,line:t.getStartLineNumber(),column:1})}}}}},jt={meta:{id:`correctness/no-missing-filter-catch`,category:`correctness`,severity:`error`,description:`Exception filter classes decorated with @Catch() must implement the catch() method`,help:`Add a catch(exception, host: ArgumentsHost) method to the filter class.`},check(e){for(let t of e.sourceFile.getClasses())w(t,`Catch`)&&(t.getExtends()||t.getMethods().some(e=>e.getName()===`catch`)||e.report({filePath:e.filePath,message:`Exception filter '${t.getName()}' has @Catch() but is missing the 'catch()' method.`,help:this.meta.help,line:t.getStartLineNumber(),column:1}))}},Mt={meta:{id:`correctness/no-missing-guard-method`,category:`correctness`,severity:`error`,description:`Guard classes must implement the canActivate() method`,help:`Add a canActivate(context: ExecutionContext) method to the guard class.`},check(e){for(let t of e.sourceFile.getClasses()){let n=t.getName()??``;n.endsWith(`Guard`)&&w(t,`Injectable`)&&(t.getExtends()||t.getMethods().some(e=>e.getName()===`canActivate`)||e.report({filePath:e.filePath,message:`Guard '${n}' is missing the 'canActivate()' method.`,help:this.meta.help,line:t.getStartLineNumber(),column:1}))}}},Nt={meta:{id:`correctness/no-missing-injectable`,category:`correctness`,severity:`error`,description:`Provider classes with constructor dependencies must have the @Injectable() decorator`,help:`Add @Injectable() to providers that inject constructor dependencies.`,scope:`project`},check(e){let t=new Set([...e.providers.values()].map(e=>e.name)),n=new Map;for(let t of e.files){let r=e.project.getSourceFile(t);if(r)for(let e of r.getClasses()){let r=e.getName();if(r){let i=n.get(r)??[];i.push({cls:e,filePath:t}),n.set(r,i)}}}for(let r of e.moduleGraph.modules.values())for(let i of r.providers){if(t.has(i))continue;let a=n.get(i);if(a)for(let{cls:t,filePath:n}of a){let a=(t.getConstructors()[0]?.getParameters().length??0)>0;!(t.getDecorator(`Injectable`)||t.getDecorator(`Resolver`)||t.getDecorator(`WebSocketGateway`))&&a&&e.report({filePath:n,message:`Class '${i}' is listed in '${r.name}' providers but is missing @Injectable() decorator.`,help:this.meta.help,line:t.getStartLineNumber(),column:1})}}}},Pt={meta:{id:`correctness/no-missing-interceptor-method`,category:`correctness`,severity:`error`,description:`Interceptor classes must implement the intercept() method`,help:`Add an intercept(context: ExecutionContext, next: CallHandler) method to the interceptor class.`},check(e){for(let t of e.sourceFile.getClasses()){let n=t.getName()??``;n.endsWith(`Interceptor`)&&w(t,`Injectable`)&&(t.getExtends()||t.getMethods().some(e=>e.getName()===`intercept`)||e.report({filePath:e.filePath,message:`Interceptor '${n}' is missing the 'intercept()' method.`,help:this.meta.help,line:t.getStartLineNumber(),column:1}))}}},Ft={meta:{id:`correctness/no-missing-module-decorator`,category:`correctness`,severity:`warning`,description:`Classes named *Module should have a @Module() decorator`,help:`Add @Module({}) decorator to the class, or rename it if it is not a NestJS module.`},check(e){for(let t of e.sourceFile.getClasses()){let n=t.getName()??``;n.endsWith(`Module`)&&(n===`Module`||n===`DynamicModule`||w(t,`Module`)||e.report({filePath:e.filePath,message:`Class '${n}' is named like a module but is missing the @Module() decorator.`,help:this.meta.help,line:t.getStartLineNumber(),column:1}))}}},It={meta:{id:`correctness/no-missing-pipe-method`,category:`correctness`,severity:`error`,description:`Pipe classes must implement the transform() method`,help:`Add a transform(value: any, metadata: ArgumentMetadata) method to the pipe class.`},check(e){for(let t of e.sourceFile.getClasses()){let n=t.getName()??``;n.endsWith(`Pipe`)&&w(t,`Injectable`)&&(t.getExtends()||t.getMethods().some(e=>e.getName()===`transform`)||e.report({filePath:e.filePath,message:`Pipe '${n}' is missing the 'transform()' method.`,help:this.meta.help,line:t.getStartLineNumber(),column:1}))}}},Lt={meta:{id:`correctness/prefer-readonly-injection`,category:`correctness`,severity:`warning`,description:`Constructor DI parameters should be readonly to prevent accidental reassignment`,help:`Add the 'readonly' modifier to the constructor parameter.`},check(e){for(let t of e.sourceFile.getClasses()){if(!(Oe(t)||T(t)))continue;let n=t.getConstructors()[0];if(n){for(let t of n.getParameters())if((t.hasModifier(`private`)||t.hasModifier(`protected`)||t.hasModifier(`public`))&&!t.isReadonly()){let n=t.getNameNode();e.report({filePath:e.filePath,message:`Constructor parameter '${t.getName()}' should be readonly.`,help:this.meta.help,line:n.getStartLineNumber(),column:n.getStartLinePos()+1})}}}}},Rt={meta:{id:`correctness/require-inject-decorator`,category:`correctness`,severity:`error`,description:`Constructor parameters without type annotations must have @Inject() decorator for NestJS DI to resolve them`,help:`Add a type annotation or @Inject() decorator to the constructor parameter.`},check(e){for(let t of e.sourceFile.getClasses()){if(!ke(t))continue;let n=t.getConstructors()[0];if(n)for(let r of n.getParameters()){let n=r.getTypeNode(),i=r.getDecorators().some(e=>e.getName()===`Inject`);n||i||e.report({filePath:e.filePath,message:`Constructor parameter '${r.getName()}' in '${t.getName()}' has no type annotation and no @Inject() decorator — NestJS cannot resolve it.`,help:this.meta.help,line:r.getStartLineNumber(),column:1})}}}},zt={onModuleInit:`OnModuleInit`,onModuleDestroy:`OnModuleDestroy`,onApplicationBootstrap:`OnApplicationBootstrap`,onApplicationShutdown:`OnApplicationShutdown`,beforeApplicationShutdown:`BeforeApplicationShutdown`},Bt={meta:{id:`correctness/require-lifecycle-interface`,category:`correctness`,severity:`warning`,description:`Classes with lifecycle methods should implement the corresponding NestJS interface`,help:`Add 'implements OnModuleInit' (or the appropriate interface) to the class declaration.`},check(e){for(let t of e.sourceFile.getClasses()){let n=t.getImplements().map(e=>e.getText());for(let r of t.getMethods()){let i=r.getName(),a=zt[i];a&&(n.some(e=>e.includes(a))||e.report({filePath:e.filePath,message:`Class '${t.getName()}' has '${i}()' but does not implement '${a}'.`,help:this.meta.help,line:r.getStartLineNumber(),column:1}))}}}},Vt=new Set([d.ForStatement,d.ForOfStatement,d.ForInStatement,d.WhileStatement,d.DoStatement,d.AwaitExpression]),Ht={meta:{id:`performance/no-blocking-constructor`,category:`performance`,severity:`warning`,description:`Constructors in Injectable/Controller classes should not contain heavy operations`,help:`Move complex initialization logic to onModuleInit() lifecycle method instead.`},check(e){for(let t of e.sourceFile.getClasses()){if(!(w(t,`Injectable`)||w(t,`Controller`)))continue;let n=t.getConstructors()[0];if(!n)continue;let r=n.getBody();if(r){for(let i of r.getDescendants())if(Vt.has(i.getKind())){e.report({filePath:e.filePath,message:`Constructor in '${t.getName()}' contains blocking operation — use onModuleInit() instead.`,help:this.meta.help,line:n.getStartLineNumber(),column:1});break}}}}},Ut={meta:{id:`performance/no-dynamic-require`,category:`performance`,severity:`warning`,description:`Dynamic require() with variable arguments prevents bundler optimization`,help:`Use static import statements or dynamic import() with string literals.`},check(e){let t=e.sourceFile.getDescendantsOfKind(d.CallExpression);for(let n of t){if(n.getExpression().getText()!==`require`)continue;let t=n.getArguments();t.length!==0&&t[0].getKind()!==d.StringLiteral&&e.report({filePath:e.filePath,message:`Dynamic require() with non-literal argument prevents bundler optimization.`,help:this.meta.help,line:n.getStartLineNumber(),column:1})}}},Wt={meta:{id:`performance/no-orphan-modules`,category:`performance`,severity:`info`,description:`Module is never imported by any other module and may be dead code`,help:`Import this module in another module or remove it if it is unused.`,scope:`project`},check(e){let t=new Set;for(let n of e.moduleGraph.modules.values())for(let e of n.imports)t.add(e);for(let n of e.moduleGraph.modules.values())n.name!==`AppModule`&&(t.has(n.name)||e.report({filePath:n.filePath,message:`Module '${n.name}' is never imported by any other module.`,help:this.meta.help,line:n.classDeclaration.getStartLineNumber(),column:1}))}},Gt={meta:{id:`performance/no-request-scope-abuse`,category:`performance`,severity:`warning`,description:`Scope.REQUEST creates a new provider instance per request — use only when necessary`,help:`Remove Scope.REQUEST unless the provider genuinely needs per-request state (e.g., request-scoped context). Consider Scope.DEFAULT or Scope.TRANSIENT instead.`},check(e){let t=e.sourceFile.getDescendantsOfKind(d.PropertyAccessExpression);for(let n of t)n.getName()===`REQUEST`&&n.getExpression().getText()===`Scope`&&e.report({filePath:e.filePath,message:`Scope.REQUEST creates a new instance per request, which impacts performance and propagates request scope to all dependents.`,help:this.meta.help,line:n.getStartLineNumber(),column:1})}},Kt=new Set([`readFileSync`,`writeFileSync`,`existsSync`,`mkdirSync`,`readdirSync`,`statSync`,`accessSync`,`appendFileSync`,`copyFileSync`,`renameSync`,`unlinkSync`]),qt={meta:{id:`performance/no-sync-io`,category:`performance`,severity:`warning`,description:`Synchronous I/O calls block the event loop and should be avoided in NestJS applications`,help:`Use the async variant (e.g., readFile instead of readFileSync) with await.`},check(e){let t=e.sourceFile.getDescendantsOfKind(d.CallExpression);for(let n of t){let t=n.getExpression().getText().split(`.`).pop()??``;Kt.has(t)&&e.report({filePath:e.filePath,message:`Synchronous I/O call '${t}()' blocks the event loop.`,help:this.meta.help,line:n.getStartLineNumber(),column:1})}}},Jt={meta:{id:`performance/no-unused-module-exports`,category:`performance`,severity:`info`,description:`Module exports a provider that no importing module actually uses`,help:`Remove the unused export or use the provider in an importing module.`,scope:`project`},check(e){for(let t of e.moduleGraph.modules.values()){if(t.exports.length===0)continue;let n=[];for(let r of e.moduleGraph.modules.values())r.name!==t.name&&r.imports.includes(t.name)&&n.push(r.name);if(n.length===0)continue;let r=new Set;for(let i of n){let n=e.moduleGraph.modules.get(i);if(n){for(let t of n.providers){let n=e.providers.get(t);if(n)for(let e of n.dependencies)r.add(e)}if(n.exports.includes(t.name))for(let e of t.exports)r.add(e);for(let t of n.controllers)for(let n of e.files){let i=e.project.getSourceFile(n);if(i)for(let e of i.getClasses()){if(e.getName()!==t)continue;let n=e.getConstructors()[0];if(n)for(let e of n.getParameters()){let t=e.getTypeNode(),n=t?t.getText():e.getType().getText(),i=n.split(`.`).pop()?.split(`<`)[0]??n;r.add(i)}}}}}for(let n of t.exports)e.moduleGraph.modules.has(n)||r.has(n)||e.report({filePath:t.filePath,message:`Module '${t.name}' exports '${n}' but no importing module uses it.`,help:this.meta.help,line:t.classDeclaration.getStartLineNumber(),column:1})}}},Yt=[`Guard`,`Interceptor`,`Filter`,`Middleware`,`Strategy`],Xt={meta:{id:`performance/no-unused-providers`,category:`performance`,severity:`warning`,description:`Injectable providers that are never injected by any other provider may be dead code`,help:`Remove the unused provider or inject it where needed.`,scope:`project`},check(e){let t=new Set;for(let n of e.providers.values())for(let e of n.dependencies)t.add(e);let n=[`Controller`,`Resolver`,`WebSocketGateway`];for(let r of e.files){let i=e.project.getSourceFile(r);if(i)for(let e of i.getClasses()){if(!n.some(t=>e.getDecorator(t)!==void 0))continue;let r=e.getConstructors()[0];if(r)for(let e of r.getParameters()){let n=e.getTypeNode(),r=n?n.getText():e.getType().getText(),i=r.split(`.`).pop()?.split(`<`)[0]??r;t.add(i)}}}for(let n of e.providers.values()){let r=n.name;if(Yt.some(e=>r.endsWith(e))||t.has(r))continue;let i=!1;for(let t of e.moduleGraph.modules.values())if(t.exports.includes(r)){i=!0;break}i||e.report({filePath:n.filePath,message:`Provider '${r}' is never injected by any other provider or controller.`,help:this.meta.help,line:n.classDeclaration.getStartLineNumber(),column:1})}}},Zt={meta:{id:`schema/require-cascade-rule`,category:`schema`,scope:`schema`,severity:`info`,description:`Relations should have explicit onDelete/cascade behavior defined`,help:`Add an explicit onDelete option (e.g. CASCADE, SET NULL) to avoid relying on database defaults.`},check(e){for(let t of e.schemaGraph.relations)if(!(t.type!==`many-to-one`&&t.type!==`one-to-one`)&&!t.onDelete){let n=e.schemaGraph.entities.get(t.fromEntity);if(!n)continue;e.report({filePath:n.filePath,entity:n.name,message:`Relation '${t.propertyName}' on '${t.fromEntity}' has no explicit onDelete behavior.`,help:this.meta.help})}}},Qt={meta:{id:`schema/require-primary-key`,category:`schema`,scope:`schema`,severity:`error`,description:`Every entity must have at least one primary key column`,help:`Add a primary key column (e.g. @id in Prisma, @PrimaryColumn/@PrimaryGeneratedColumn in TypeORM).`},check(e){for(let t of e.schemaGraph.entities.values())t.columns.some(e=>e.isPrimary)||e.report({filePath:t.filePath,entity:t.name,message:`Entity '${t.name}' has no primary key column.`,help:this.meta.help})}},$t=/delete/i;function en(e,t){let n=new Set(e.columns.map(e=>e.name.toLowerCase()));return n.has(`createdat`)||n.has(`created_at`)?!0:t===`typeorm`?e.columns.some(e=>e.type===`timestamp`&&e.isGenerated&&!$t.test(e.name)):t===`prisma`?e.columns.some(e=>e.type===`DateTime`&&e.defaultValue!==void 0&&e.defaultValue.includes(`now()`)):t===`drizzle`?e.columns.some(e=>(e.type===`timestamp`||e.type===`date`||e.type===`datetime`)&&e.defaultValue!==void 0&&e.defaultValue.includes(`now()`)):!1}const tn={meta:{id:`schema/require-timestamps`,category:`schema`,scope:`schema`,severity:`warning`,description:`Entities should have timestamp columns (createdAt/updatedAt)`,help:`Add createdAt/updatedAt columns to track when records are created and modified.`},check(e){for(let t of e.schemaGraph.entities.values())en(t,e.orm)||e.report({filePath:t.filePath,entity:t.name,message:`Entity '${t.name}' has no timestamp columns (createdAt/updatedAt).`,help:this.meta.help})}},nn={meta:{id:`security/no-csrf-disabled`,category:`security`,severity:`error`,description:`CSRF protection should not be explicitly disabled`,help:`Enable CSRF protection or remove the explicit disabling of it.`},check(e){let t=e.sourceFile.getDescendantsOfKind(d.PropertyAssignment);for(let n of t){let t=n.getName();if(t!==`csrf`&&t!==`csrfProtection`)continue;let r=n.getInitializer();r&&r.getText()===`false`&&e.report({filePath:e.filePath,message:`CSRF protection explicitly disabled (${t}: false).`,help:this.meta.help,line:n.getStartLineNumber(),column:1})}}},rn={meta:{id:`security/no-dangerous-redirects`,category:`security`,severity:`error`,description:`Redirects using user-controlled input (from @Query/@Param) are an open redirect vulnerability`,help:`Validate redirect URLs against an allowlist of safe destinations.`},check(e){for(let t of e.sourceFile.getClasses())if(T(t))for(let n of t.getMethods()){let t=new Set;for(let e of n.getParameters())e.getDecorators().some(e=>e.getName()===`Query`||e.getName()===`Param`)&&t.add(e.getName());if(t.size===0)continue;let r=n.getDescendantsOfKind(d.CallExpression);for(let n of r)if(n.getExpression().getText().endsWith(`redirect`))for(let r of n.getArguments()){let i=r.getText();t.has(i)&&e.report({filePath:e.filePath,message:`Redirect uses user-controlled parameter '${i}' — open redirect risk.`,help:this.meta.help,line:n.getStartLineNumber(),column:1})}let i=n.getDecorators().find(e=>e.getName()===`Redirect`);if(i)for(let n of i.getArguments()){let r=n.getText();t.has(r)&&e.report({filePath:e.filePath,message:`@Redirect() uses user-controlled parameter '${r}' — open redirect risk.`,help:this.meta.help,line:i.getStartLineNumber(),column:1})}}}},an={meta:{id:`security/no-eval`,category:`security`,severity:`error`,description:`Usage of eval() or new Function() is a security risk and should be avoided`,help:`Refactor to avoid eval() and new Function(). Use safer alternatives like JSON.parse() or a sandboxed interpreter.`},check(e){let t=e.sourceFile.getDescendantsOfKind(d.CallExpression);for(let n of t)n.getExpression().getText()===`eval`&&e.report({filePath:e.filePath,message:`Usage of eval() is a security risk.`,help:this.meta.help,line:n.getStartLineNumber(),column:1});let n=e.sourceFile.getDescendantsOfKind(d.NewExpression);for(let t of n)t.getExpression().getText()===`Function`&&e.report({filePath:e.filePath,message:`Usage of new Function() is a security risk.`,help:this.meta.help,line:t.getStartLineNumber(),column:1})}},on={meta:{id:`security/no-exposed-env-vars`,category:`security`,severity:`warning`,description:`Use NestJS ConfigService instead of direct process.env access in Injectable/Controller classes`,help:`Inject ConfigService and use configService.get('VAR_NAME') instead of process.env.VAR_NAME.`},check(e){for(let t of e.sourceFile.getClasses()){if(!(w(t,`Injectable`)||w(t,`Controller`)))continue;let n=t.getDescendantsOfKind(d.PropertyAccessExpression);for(let r of n)r.getExpression().getText()===`process.env`&&e.report({filePath:e.filePath,message:`Direct 'process.env.${r.getName()}' access in '${t.getName()}'. Use ConfigService instead.`,help:this.meta.help,line:r.getStartLineNumber(),column:1})}}},sn=/^(error|err|e|ex|exception)$/,cn={meta:{id:`security/no-exposed-stack-trace`,category:`security`,severity:`warning`,description:`Stack traces should not be exposed in responses — they leak internal implementation details`,help:`Log the stack trace internally and return a generic error message to the client.`},check(e){let t=e.sourceFile.getDescendantsOfKind(d.PropertyAccessExpression);for(let n of t){if(n.getName()!==`stack`)continue;let t=n.getExpression().getText();if(!(sn.test(t)||t.endsWith(`.error`)||t.endsWith(`.err`)))continue;let r=n.getParent();if(!r)continue;let i=r.getKind();(i===d.ReturnStatement||i===d.PropertyAssignment||i===d.ShorthandPropertyAssignment||i===d.CallExpression)&&e.report({filePath:e.filePath,message:`Stack trace '${t}.stack' may be exposed in response — leaks implementation details.`,help:this.meta.help,line:n.getStartLineNumber(),column:1})}}},ln=[{pattern:/^(?=.*\d)[A-Za-z0-9+/]{40,}={0,2}$/,name:`Base64 key`},{pattern:/^sk[-_][a-zA-Z0-9]{20,}$/,name:`Secret key`},{pattern:/^pk[-_][a-zA-Z0-9]{20,}$/,name:`Public key (in source)`},{pattern:/^ghp_[a-zA-Z0-9]{36,}$/,name:`GitHub personal access token`},{pattern:/^github_pat_[a-zA-Z0-9_]{22,}$/,name:`GitHub fine-grained PAT`},{pattern:/^gho_[a-zA-Z0-9]{36,}$/,name:`GitHub OAuth token`},{pattern:/^xox[bpras]-[a-zA-Z0-9-]+$/,name:`Slack token`},{pattern:/^eyJ[a-zA-Z0-9_-]{10,}\.[a-zA-Z0-9_-]{10,}\./,name:`JWT token`},{pattern:/^AKIA[0-9A-Z]{16}$/,name:`AWS Access Key ID`},{pattern:/^[a-f0-9]{64}$/,name:`Hex-encoded secret (64 chars)`}],un=[/secret/i,/password/i,/passwd/i,/api[_-]?key/i,/auth[_-]?token/i,/private[_-]?key/i,/access[_-]?key/i,/client[_-]?secret/i],dn=new Set([`your-secret-here`,`changeme`,`password`]),fn=/^[A-Za-z][A-Za-z0-9_]*(\.[A-Za-z][A-Za-z0-9_]*)+$/,M=new Set([`cursor`,`nextCursor`,`prevCursor`,`previousCursor`,`startCursor`,`endCursor`,`pageToken`,`nextPageToken`,`continuationToken`,`continuation`,`nextPage`,`afterCursor`,`beforeCursor`]);function N(e){return!(e.length<8||e.includes("${")||e.startsWith(`process.env`)||dn.has(e)||e.includes(` `)||fn.test(e))}function P(e){return un.some(t=>t.test(e))}function pn(e){try{let t=Buffer.from(e,`base64`).toString(`utf-8`);return JSON.parse(t),!0}catch{return!1}}function mn(e){let t=new Map;for(let n of e)t.set(n,(t.get(n)??0)+1);let n=0;for(let r of t.values()){let t=r/e.length;n-=t*Math.log2(t)}return n}const F=new Set([...`aeiouyAEIOUY`]),hn=/^[A-Z]{2,4}_/,gn=/(?<=[a-z])(?=[A-Z])|(?<=[A-Za-z])(?=\d)|(?<=\d)(?=[A-Za-z])|_/,_n=/[a-zA-Z]/;function vn(e){let t=e.includes(`_`),n=e.split(gn).filter(e=>e.length>0).filter(e=>_n.test(e)),r=n.filter(e=>e.length>=4&&[...e].some(e=>F.has(e)));return n.slice(0,6).filter(e=>e.length>=4&&[...e].some(e=>F.has(e))).length>=2||t&&e.split(`_`).filter(e=>e.length>=3).length>=2||hn.test(e)?!0:(mn(e)>4.9&&!t&&r.length,!1)}function yn(e){let t=e.getParent();if(!t)return!1;let n=t.asKind(d.PropertyAssignment);if(n)return M.has(n.getName());let r=t.asKind(d.VariableDeclaration);return r?M.has(r.getName()):!1}const bn={meta:{id:`security/no-hardcoded-secrets`,category:`security`,severity:`error`,description:`Detect hardcoded secrets, API keys, and tokens in source code`,help:`Move secrets to environment variables and access them via ConfigService.`},check(e){let t=e.sourceFile.getDescendantsOfKind(d.StringLiteral);for(let n of t){let t=n.getLiteralValue();if(!(t.length<16)&&n.getParent()?.getKind()!==d.ImportDeclaration){for(let{pattern:r,name:i}of ln)if(r.test(t)){if(i===`Base64 key`&&(pn(t)||yn(n)||vn(t)))break;e.report({filePath:e.filePath,message:`Possible hardcoded ${i} detected.`,help:this.meta.help,line:n.getStartLineNumber(),column:1});break}}}let n=e.sourceFile.getDescendantsOfKind(d.VariableDeclaration);for(let t of n){let n=t.getName(),r=t.getInitializer();!r||r.getKind()!==d.StringLiteral||P(n)&&N(r.getText().slice(1,-1))&&e.report({filePath:e.filePath,message:`Variable '${n}' appears to contain a hardcoded secret.`,help:this.meta.help,line:t.getStartLineNumber(),column:1})}let r=e.sourceFile.getDescendantsOfKind(d.PropertyAssignment);for(let t of r){let n=t.getName(),r=t.getInitializer();!r||r.getKind()!==d.StringLiteral||P(n)&&N(r.getText().slice(1,-1))&&e.report({filePath:e.filePath,message:`Property '${n}' appears to contain a hardcoded secret.`,help:this.meta.help,line:t.getStartLineNumber(),column:1})}}},xn=RegExp(`(?:^|[^a-zA-Z])\\w*(?:${[`Entity`,`Model`].join(`|`)})(?:[^a-zA-Z]|$)`),Sn={meta:{id:`security/no-raw-entity-in-response`,category:`security`,severity:`warning`,description:`Returning ORM entities directly from controllers can leak internal fields like passwords or IDs`,help:`Map entities to DTOs or use class-transformer's @Exclude()/@Expose() decorators before returning. This rule detects classes with Entity/Model suffix in return types.`},check(e){for(let t of e.sourceFile.getClasses())if(T(t))for(let n of t.getMethods()){if(!E(n))continue;let t=n.getReturnType().getText();xn.test(t)&&!t.includes(`DTO`)&&!t.includes(`Dto`)&&!t.includes(`Response`)&&e.report({filePath:e.filePath,message:`Controller method '${n.getName()}' returns a raw entity type. This may leak internal fields.`,help:this.meta.help,line:n.getStartLineNumber(),column:1})}}},Cn={meta:{id:`security/no-synchronize-in-production`,category:`security`,severity:`error`,description:`TypeORM synchronize: true auto-syncs schema and can drop columns or tables in production`,help:`Set synchronize: false and use migrations for production schema changes.`},check(e){let t=e.sourceFile.getDescendantsOfKind(d.PropertyAssignment);for(let n of t){if(n.getName()!==`synchronize`)continue;let t=n.getInitializer();t&&t.getText()===`true`&&e.report({filePath:e.filePath,message:`TypeORM 'synchronize: true' can auto-drop columns and tables in production.`,help:this.meta.help,line:n.getStartLineNumber(),column:1})}}},wn=new Set([`md5`,`sha1`]),Tn=[Ne,_t,st,ft,rt,yt,bt,St,De,$e,Lt,Bt,Ot,Dt,Mt,It,jt,Pt,Ct,Tt,Ft,Rt,At,Nt,bn,an,{meta:{id:`security/no-weak-crypto`,category:`security`,severity:`warning`,description:`Weak hashing algorithms (MD5, SHA1) should not be used for security purposes`,help:`Use a stronger algorithm like SHA-256 or bcrypt for password hashing.`},check(e){let t=e.sourceFile.getDescendantsOfKind(d.CallExpression);for(let n of t){if(!n.getExpression().getText().endsWith(`createHash`))continue;let t=n.getArguments();if(t.length===0)continue;let r=t[0];if(r.getKind()!==d.StringLiteral)continue;let i=r.getText().slice(1,-1).toLowerCase();wn.has(i)&&e.report({filePath:e.filePath,message:`Weak hashing algorithm '${i}' used in createHash().`,help:this.meta.help,line:n.getStartLineNumber(),column:1})}}},on,nn,cn,rn,Cn,Sn,qt,Ht,Ut,Gt,Xt,Jt,Wt,Qt,tn,Zt];function En(e){return e.meta.scope===`project`}function Dn(e){return e.meta.scope===`schema`}function On(e,t,n){if(t.length===0)return e;let r=new Set(e.map(e=>e.meta.id)),i=[...e];for(let e of t){if(r.has(e.meta.id)){n.push(`Custom rule "${e.meta.id}" conflicts with a built-in rule and was skipped`);continue}i.push(e)}return i}function I(e,t){return t.filter(t=>{let n=e.rules?.[t.meta.id];return!(n===!1||typeof n==`object`&&n.enabled===!1||e.categories?.[t.meta.category]===!1)})}function kn(e){let t=[],n=[],r=[];for(let i of e)Dn(i)?r.push(i):En(i)?n.push(i):t.push(i);return{fileRules:t,projectRules:n,schemaRules:r}}const An=new Set([`security`,`performance`,`correctness`,`architecture`]),jn=new Set([`error`,`warning`,`info`]),Mn=new Set([`file`,`project`]),Nn=`custom/`;function Pn(e){if(typeof e!=`object`||!e)return!1;let t=e;if(typeof t.check!=`function`||typeof t.meta!=`object`||t.meta===null)return!1;let n=t.meta;return!(typeof n.id!=`string`||n.id.trim()===``||typeof n.description!=`string`||typeof n.help!=`string`||!An.has(n.category)||!jn.has(n.severity)||n.scope!==void 0&&!Mn.has(n.scope))}function Fn(e){return e.meta.id.startsWith(Nn)?e:{...e,meta:{...e.meta,id:`${Nn}${e.meta.id}`}}}async function In(e,t){let n=[],r=[],i=s(t,e);if(!p(i))return r.push(`Custom rules directory not found: ${i}`),{rules:n,warnings:r};if(!g(i).isDirectory())return r.push(`Custom rules path is not a directory: ${i}`),{rules:n,warnings:r};let a;try{a=h(i)}catch(e){return r.push(`Failed to read custom rules directory: ${e instanceof Error?e.message:String(e)}`),{rules:n,warnings:r}}let o=a.filter(e=>e.endsWith(`.ts`));if(o.length===0)return r.push(`No rule files (.ts) found in: ${i}`),{rules:n,warnings:r};let c=_(i,{interopDefault:!0});for(let e of o){let t=s(i,e),a;try{a=await c.import(t)}catch(t){r.push(`Failed to load custom rule file "${e}": ${t instanceof Error?t.message:String(t)}`);continue}let o=!1;for(let[t,i]of Object.entries(a))Pn(i)?(n.push(Fn(i)),o=!0):t!==`__esModule`&&typeof i==`object`&&i&&`meta`in i&&r.push(`Invalid rule export "${t}" in "${e}": missing or invalid required fields (check, meta.id, meta.description, meta.help, meta.category, meta.severity)`);!o&&Object.keys(a).length>0&&(Object.values(a).some(e=>typeof e==`object`&&!!e&&(`meta`in e||`check`in e))||r.push(`No valid rule exports found in "${e}"`))}return{rules:n,warnings:r}}function Ln(e,t){return e.customRulesDir?In(e.customRulesDir,t):Promise.resolve({rules:[],warnings:[]})}async function Rn(e,t){let n=await Se(e,t),{rules:r,warnings:i}=await Ln(n,e),a=On(Tn,r,i),{fileRules:o,projectRules:s,schemaRules:c}=kn(I(n,a));return{combinedRules:a,config:n,customRuleWarnings:i,fileRules:o,projectRules:s,schemaRules:c}}async function zn(e,t={}){return(await c(t.include??S.include,{cwd:e,absolute:!0,ignore:t.exclude??S.exclude})).sort()}async function Bn(e,t,n={}){let r=await Promise.all([...t.projects.entries()].map(async([t,r])=>[t,await zn(a(e,r),n)])),i=new Map;for(let[e,t]of r)i.set(e,t);return i}function Vn(e){let t=new u({compilerOptions:{strict:!0,target:99,module:99,skipFileDependencyResolution:!0},skipAddingFilesFromTsConfig:!0});for(let n of e)t.addSourceFileAtPath(n);return t}const Hn=/import\([^)]+\)\.(\w+)/,Un=/^(\w+)</;function Wn(e,t){let n=[];for(let r of e.getClasses()){if(!r.getDecorator(`Injectable`))continue;let e=r.getName();if(!e)continue;let i=r.getConstructors()[0],a=i?i.getParameters().map(e=>{let t=e.getTypeNode();return Kn(t?t.getText():e.getType().getText())}):[],o=r.getMethods().filter(e=>{let t=e.getScope();return!t||t===`public`}).length;n.push({name:e,filePath:t,classDeclaration:r,dependencies:a,publicMethodCount:o})}return n}function Gn(e,t){let n=new Map;for(let r of t){let t=e.getSourceFile(r);if(t)for(let e of Wn(t,r))n.set(e.name,e)}return n}function Kn(e){let t=e.match(Hn);if(t)return t[1];let n=e.match(Un);return n?n[1]:e}const L=/^['"`]|['"`]$/g,qn=/\/+/g,Jn=/\/$/;function Yn(e,t){let n=e;for(;n&&n!==t;){let e=n.getParent();if(!e||e===t)break;let r=e.getKind();if(r===d.IfStatement){let t=e.asKindOrThrow(d.IfStatement);if(n===t.getThenStatement()||n===t.getElseStatement())return!0}if(r===d.ConditionalExpression){let t=e.asKindOrThrow(d.ConditionalExpression);if(n===t.getWhenTrue()||n===t.getWhenFalse())return!0}let i=n.getKind();if(i===d.CaseClause||i===d.DefaultClause||i===d.CatchClause)return!0;n=e}return!1}function Xn(e){let t=e.getDecorator(`Controller`);if(!t)return``;let n=t.getArguments();if(n.length===0)return``;let r=n[0];if(r.getKind()===d.ObjectLiteralExpression){let e=r.asKindOrThrow(d.ObjectLiteralExpression).getProperty(`path`);if(!e)return``;let t=e.asKind(d.PropertyAssignment);if(!t)return``;let n=t.getInitializer();return n?n.getText().replace(L,``):``}return r.getText().replace(L,``)}function Zn(e){for(let t of e.getDecorators()){let e=t.getName();if(!C.has(e))continue;let n=t.getArguments(),r=n.length>0?n[0].getText().replace(L,``):``;return{httpMethod:e.toUpperCase(),path:r}}}function Qn(e,t){return`/${[e,t].filter(Boolean).join(`/`)}`.replace(qn,`/`).replace(Jn,``)||`/`}function $n(e){let t=new Map,n=e.getConstructors()[0];if(!n)return t;for(let e of n.getParameters()){let n=e.getName(),r=e.getTypeNode(),i=r?r.getText():e.getType().getText();t.set(n,Kn(i))}return t}function er(e,t){let n=e.getBody();if(!n)return[];let r=new Map,i=0,a=n.getDescendantsOfKind(d.CallExpression);for(let e of a){let a=e.getExpression();if(a.getKind()!==d.PropertyAccessExpression)continue;let o=a.asKindOrThrow(d.PropertyAccessExpression),s=o.getName(),c=o.getExpression();if(c.getKind()!==d.PropertyAccessExpression)continue;let l=c.asKindOrThrow(d.PropertyAccessExpression);if(l.getExpression().getKind()!==d.ThisKeyword)continue;let u=l.getName();if(!t.has(u))continue;r.has(u)||r.set(u,new Map);let f=r.get(u),p=Yn(e,n),m=f.get(s);m?m.isUnconditional=m.isUnconditional||!p:f.set(s,{isUnconditional:!p,order:i++})}let o=[];for(let[e,n]of r){let r=[];for(let[e,t]of n)r.push({name:e,conditional:!t.isUnconditional,order:t.order});r.sort((e,t)=>e.order-t.order),o.push({className:t.get(e),methodsCalled:r})}return o}function tr(e){return e.endsWith(`Repository`)?`repository`:e.endsWith(`Guard`)?`guard`:e.endsWith(`Interceptor`)?`interceptor`:e.endsWith(`Pipe`)?`pipe`:e.endsWith(`Filter`)?`filter`:e.endsWith(`Gateway`)?`gateway`:`service`}function R(e,t,n){let r=[],i=new Set,a=[],o=[];for(let t of e)if(t.methodsCalled.length===0)o.push(t);else for(let e of t.methodsCalled)a.push({className:t.className,mc:e,dep:t});a.sort((e,t)=>e.mc.order-t.mc.order);for(let e of o){if(n.has(e.className)||i.has(e.className))continue;i.add(e.className),n.add(e.className);let a=t.get(e.className),o=[];a&&(o=a.dependencies.map(e=>({className:e,methodsCalled:[]}))),r.push({className:e.className,conditional:!1,dependencies:R(o,t,new Set(n)),filePath:a?.filePath??``,line:0,methodName:null,order:0,totalMethods:a?.publicMethodCount??0,type:tr(e.className)})}let s=new Map;for(let{className:e,dep:t}of a)s.has(e)||s.set(e,t.methodsCalled);for(let{className:e,mc:o}of a){if(n.has(e))continue;let a=t.get(e),c=!i.has(e);c&&i.add(e);let l=[];if(c&&a){let r=new Set(n);r.add(e);let i=$n(a.classDeclaration),o=new Map,c=0,u=s.get(e)??[];for(let e of u){let t=a.classDeclaration.getInstanceMethod(e.name);if(t)for(let e of er(t,i)){o.has(e.className)||o.set(e.className,new Map);let t=o.get(e.className);for(let n of e.methodsCalled){let e=t.get(n.name);e?e.isUnconditional=e.isUnconditional||!n.conditional:t.set(n.name,{isUnconditional:!n.conditional,order:c++})}}}l=R([...o.entries()].map(([e,t])=>({className:e,methodsCalled:[...t.entries()].map(([e,t])=>({name:e,conditional:!t.isUnconditional,order:t.order})).sort((e,t)=>e.order-t.order)})),t,r)}let u=0;if(a){let e=a.classDeclaration.getInstanceMethod(o.name);e&&(u=e.getStartLineNumber())}r.push({className:e,conditional:o.conditional,dependencies:l,filePath:a?.filePath??``,line:u,methodName:o.name,order:o.order,totalMethods:a?.publicMethodCount??0,type:tr(e)})}return r}function nr(e,t,n){let r=[];for(let i of e.getClasses()){if(!T(i))continue;let e=Xn(i),a=i.getName()??`AnonymousController`,o=$n(i);for(let s of i.getMethods()){let i=Zn(s);if(!i)continue;let c=Qn(e,i.path),l=R(er(s,o),n,new Set);r.push({controllerClass:a,dependencies:l,filePath:t,handlerMethod:s.getName(),httpMethod:i.httpMethod,line:s.getStartLineNumber(),routePath:c})}}return r}function rr(e,t,n){let r=[];for(let i of t){let t=e.getSourceFile(i);t&&r.push(...nr(t,i,n))}return{endpoints:r}}const ir=new Set([`pgTable`,`mysqlTable`,`sqliteTable`]),ar=new Set([`serial`,`bigserial`,`smallserial`]),or=/=>\s*(\w+)/;function sr(e){let t={type:`unknown`,isPrimary:!1,isNullable:!0,isGenerated:!1,isUnique:!1};function n(e){if(e.getKind()===d.CallExpression){let r=e.asKindOrThrow(d.CallExpression),i=r.getExpression();if(i.getKind()===d.PropertyAccessExpression){let e=i.asKindOrThrow(d.PropertyAccessExpression);switch(e.getName()){case`primaryKey`:t.isPrimary=!0;break;case`notNull`:t.isNullable=!1;break;case`unique`:t.isUnique=!0;break;case`default`:{let e=r.getArguments();e.length>0&&(t.defaultValue=e[0].getText().replace(/['"]/g,``));break}case`defaultNow`:t.defaultValue=`now()`;break;case`generatedAlwaysAsIdentity`:case`autoincrement`:t.isGenerated=!0;break;case`references`:{let e=r.getArguments();if(e.length>0){let n=e[0].getText(),r=or.exec(n);if(r&&(t.reference={toEntity:r[1]}),e.length>1){let n=e[1];if(n.getKind()===d.ObjectLiteralExpression){let e=n.asKindOrThrow(d.ObjectLiteralExpression);for(let n of e.getProperties())if(n.getKind()===d.PropertyAssignment){let e=n.asKindOrThrow(d.PropertyAssignment);if(e.getName()===`onDelete`){let n=e.getInitializer()?.getText();n&&(t.reference.onDelete=n.replace(/['"]/g,``))}}}}}break}default:break}n(e.getExpression())}else if(i.getKind()===d.Identifier){let e=i.getText();t.type=e,ar.has(e)&&(t.isGenerated=!0)}}}return n(e),t}function cr(e){let t=[];for(let n of e.getProperties()){if(n.getKind()!==d.PropertyAssignment)continue;let e=n.asKindOrThrow(d.PropertyAssignment),r=e.getName(),i=e.getInitializer();if(!i)continue;let a=sr(i);t.push({name:r,type:a.type,isPrimary:a.isPrimary,isNullable:a.isNullable,isGenerated:a.isGenerated,isUnique:a.isUnique,defaultValue:a.defaultValue})}return t}function lr(e,t){let n=[];for(let r of e.getProperties()){if(r.getKind()!==d.PropertyAssignment)continue;let e=r.asKindOrThrow(d.PropertyAssignment),i=e.getName(),a=e.getInitializer();if(!a)continue;let o=sr(a);o.reference&&n.push({type:`many-to-one`,fromEntity:t,toEntity:o.reference.toEntity,propertyName:i,isNullable:o.isNullable,...o.reference.onDelete?{onDelete:o.reference.onDelete}:{}})}return n}function ur(e){let t=[],n=e.getDescendantsOfKind(d.CallExpression);for(let e of n){let n=e.getExpression();if(n.getKind()!==d.PropertyAccessExpression||n.asKindOrThrow(d.PropertyAccessExpression).getName()!==`on`)continue;let r=[];for(let t of e.getArguments())if(t.getKind()===d.PropertyAccessExpression){let e=t.asKindOrThrow(d.PropertyAccessExpression);r.push(e.getName())}if(r.length===0)continue;let i=e.getText().includes(`uniqueIndex`);t.push({columns:r,isUnique:i})}return t}function dr(e){let t=[],n=e.getFilePath();for(let r of e.getDescendantsOfKind(d.VariableDeclaration)){let e=r.getInitializer();if(!e||e.getKind()!==d.CallExpression)continue;let i=e.asKindOrThrow(d.CallExpression),a=i.getExpression();if(a.getKind()!==d.Identifier)continue;let o=a.getText();if(!ir.has(o))continue;let s=i.getArguments();if(s.length<2)continue;let c=s[0],l=r.getName();c.getKind()===d.StringLiteral&&(l=c.asKindOrThrow(d.StringLiteral).getLiteralValue());let u=s[1];if(u.getKind()!==d.ObjectLiteralExpression)continue;let f=u.asKindOrThrow(d.ObjectLiteralExpression),p=r.getName(),m=cr(f),h=lr(f,p),g;if(s.length>=3&&(g=ur(s[2]),g))for(let e of g)for(let t of e.columns){let e=m.find(e=>e.name===t);e&&(e.hasIndex=!0)}t.push({name:p,tableName:l,filePath:n,columns:m,relations:h,indexes:g})}return t}const fr={supportsIncrementalUpdate:!0,extract(e,t){let n=[];for(let r of t){let t=e.getSourceFile(r);t&&n.push(...dr(t))}return n}},pr=/^model\s+(\w+)\s*\{/,mr=/^enum\s+(\w+)\s*\{/,hr=/^(\w+)\s+(\w+)(\?)?(\[\])?(.*)$/,gr=/@(\w+)(\((?:[^()]*|\([^()]*\))*\))?/g,_r=/@default\(((?:[^()]*|\([^()]*\))*)\)/,vr=/^@@map\(\s*"([^"]+)"\s*\)/;function yr(e){let t=a(e,`prisma`,`schema.prisma`);if(p(t)){let n=a(e,`prisma`),r=h(n).filter(e=>e.endsWith(`.prisma`));return r.length>1?r.map(e=>a(n,e)):[t]}let n=a(e,`schema.prisma`);if(p(n))return[n];try{let t=a(e,`package.json`),n=JSON.parse(m(t,`utf-8`)).prisma?.schema;if(n){let t=a(e,n);if(p(t))return[t]}}catch{}return[]}function br(e){let t=[],n=new Set;for(let r of e){let e;try{e=m(r,`utf-8`)}catch{continue}let i=e.split(`
|
|
5
|
-
`),a=null,o=[],s=[],c=[],l;for(let e of i){let i=e.trim(),u=
|
|
6
|
-
`);for(let e of n){let n={config:r,sourceFile:o,filePath:t,report(t){let n=[],r=Math.max(0,t.line-6),a=Math.min(s.length,t.line+5);for(let e=r;e<a;e++)n.push({line:e+1,text:s[e]});i.push({...t,rule:e.meta.id,category:e.meta.category,scope:`file`,severity:e.meta.severity,sourceLines:n})}};try{e.check(n)}catch(t){a.push({ruleId:e.meta.id,error:t})}}return{diagnostics:i,errors:a}}function qr(e,t,n,r){let i=[],a=[];for(let o of t){let t=Kr(e,o,n,r);i.push(...t.diagnostics),a.push(...t.errors)}return{diagnostics:i,errors:a}}function Jr(e,t,n,r){let i=[],a=[];for(let o of n){let n={project:e,files:t,moduleGraph:r.moduleGraph,providers:r.providers,config:r.config,report(e){i.push({...e,rule:o.meta.id,category:o.meta.category,scope:`project`,severity:o.meta.severity})}};try{o.check(n)}catch(e){a.push({ruleId:o.meta.id,error:e})}}return{diagnostics:i,errors:a}}function Yr(e,t){let n=[],r=[];for(let i of t){let t={schemaGraph:e,orm:e.orm,report(e){n.push({...e,rule:i.meta.id,category:i.meta.category,scope:`schema`,severity:i.meta.severity})}};try{i.check(t)}catch(e){r.push({ruleId:i.meta.id,error:e})}}return{diagnostics:n,errors:r}}function Xr(e){return e instanceof Error?e.message:String(e)}function V(e,t,n){return{diagnostics:Gr(e,n.config,n.targetPath),errors:t.map(e=>({ruleId:e.ruleId,error:Xr(e.error)}))}}function Zr(e){let t=qr(e.astProject,e.files,e.fileRules,e.config);return V(t.diagnostics,t.errors,e)}function Qr(e){let t={moduleGraph:e.moduleGraph,providers:e.providers,config:e.config},n=Jr(e.astProject,e.files,e.projectRules,t),{diagnostics:r,errors:i}=V(n.diagnostics,n.errors,e),a=$r(e);return r.push(...a.diagnostics),i.push(...a.errors),{diagnostics:r,errors:i}}function $r(e){if(!e.schemaGraph||e.schemaRules.length===0||e.schemaGraph.entities.size===0)return{diagnostics:[],errors:[]};let t=Yr(e.schemaGraph,e.schemaRules);return V(t.diagnostics,t.errors,e)}function H(e){let t=l.now(),n=Zr(e),r=Qr(e),i=l.now()-t;return{diagnostics:[...n.diagnostics,...r.diagnostics],elapsedMs:i,ruleErrors:[...n.errors,...r.errors]}}function ei(e){return e>=90?`Excellent`:e>=75?`Good`:e>=50?`Fair`:e>=25?`Poor`:`Critical`}const ti={error:3,warning:1.5,info:.5},ni={security:1.5,correctness:1.3,schema:1.1,architecture:1,performance:.8};function ri(e,t){if(t===0)return{value:100,label:ei(100)};let n=0;for(let t of e){let e=ti[t.severity],r=ni[t.category];n+=e*r}let r=n/t,i=Math.max(0,Math.min(100,Math.round(100-r*10)));return{value:i,label:ei(i)}}function ii(e){let t={total:0,errors:0,warnings:0,info:0,byCategory:{security:0,performance:0,correctness:0,architecture:0,schema:0}};for(let n of e)t.total++,n.severity===`error`?t.errors++:n.severity===`warning`?t.warnings++:t.info++,t.byCategory[n.category]++;return t}function U(e,t,n=[]){let{diagnostics:r,ruleErrors:i,elapsedMs:a}=t,o=e.schemaGraph??B(e.astProject,e.files,e.project.orm,e.targetPath),s=ri(r,e.files.length),c=ii(r);return{result:{score:s,diagnostics:r,endpoints:e.endpointGraph,project:{...e.project,fileCount:e.files.length,moduleCount:e.moduleGraph.modules.size},summary:c,ruleErrors:i,elapsedMs:a,schema:zr(o)},moduleGraph:e.moduleGraph,schemaGraph:o,customRuleWarnings:n,files:e.files,providers:e.providers}}function ai(e,t,n,r){let i=[],a=[],o=[],s=new Map,c=[],l=0,u=[],d=[],f=``;for(let[n,r]of e.subProjects){let e=U(r,t.get(n));i.push({name:n,result:e.result}),s.set(n,e.moduleGraph),a.push(...e.result.diagnostics),o.push(...e.result.ruleErrors),l+=e.result.project.fileCount,e.result.endpoints&&c.push(...e.result.endpoints.endpoints),e.result.schema&&(u.push(...e.result.schema.entities),d.push(...e.result.schema.relations),e.result.schema.orm&&e.result.schema.orm!==`unknown`&&(f=e.result.schema.orm))}let p=ri(a,l),m=ii(a);return{moduleGraphs:s,customRuleWarnings:n,result:{isMonorepo:!0,subProjects:i,combined:{score:p,diagnostics:a,endpoints:c.length>0?{endpoints:c}:void 0,project:{name:`monorepo`,nestVersion:i[0]?.result.project.nestVersion??null,orm:f||(i[0]?.result.project.orm??null),framework:i[0]?.result.project.framework??null,fileCount:l,moduleCount:i.reduce((e,t)=>e+t.result.project.moduleCount,0)},summary:m,ruleErrors:o,elapsedMs:r,schema:u.length>0?{entities:u,relations:d,orm:f||`unknown`}:void 0},elapsedMs:r}}}const oi=e=>{if(e.trim()===``)return`Invalid --min-score value: "${e}". Must be an integer between 0 and 100.`;let t=Number(e);return!Number.isInteger(t)||t<0||t>100?`Invalid --min-score value: "${e}". Must be an integer between 0 and 100.`:null},si=(e,t)=>e===void 0?t:Number(e),ci=(e,t)=>t===void 0?!0:e>=t,W={error:y.red,warn:y.yellow,info:y.cyan,success:y.green,dim:y.dim},G=e=>{console.log(e)},K={error(...e){G(W.error(e.join(` `)))},warn(...e){G(W.warn(e.join(` `)))},info(...e){G(W.info(e.join(` `)))},success(...e){G(W.success(e.join(` `)))},dim(...e){G(W.dim(e.join(` `)))},log(...e){G(e.join(` `))},break(){G(``)}},li=1e3,ui={error:0,warning:1,info:2},q=(e,t=e)=>({plainText:e,renderedText:t}),J=(e,t)=>t>=75?W.success(e):t>=50?W.warn(e):W.error(e),di=(e,t)=>t===`error`?W.error(e):t===`warning`?W.warn(e):W.info(e),fi=e=>e===`error`?`✗`:e===`warning`?`⚠`:`●`,pi=e=>e<li?`${Math.round(e)}ms`:`${(e/li).toFixed(1)}s`,mi=e=>e>=75?[`◠ ◠ ◠`,`╰───╯`]:e>=50?[`• • •`,`╰───╯`]:[`x x x`,`╰───╯`],hi=e=>e>=90?`★★★★★`:e>=75?`★★★★☆`:e>=50?`★★★☆☆`:e>=25?`★★☆☆☆`:`★☆☆☆☆`,gi=e=>{let t=Math.round(e/100*50),n=50-t;return{filled:`█`.repeat(t),empty:`░`.repeat(n)}},_i=e=>{let{filled:t,empty:n}=gi(e);return`${t}${n}`},vi=e=>{let{filled:t,empty:n}=gi(e);return J(t,e)+W.dim(n)},yi=e=>{if(e.length===0)return;let t=W.dim,n=` `.repeat(2),r=` `.repeat(1),i=Math.max(...e.map(e=>e.plainText.length)),a=`─`.repeat(i+2);K.log(`${n}${t(`┌${a}┐`)}`);for(let a of e){let e=` `.repeat(i-a.plainText.length);K.log(`${n}${t(`│`)}${r}${a.renderedText}${e}${r}${t(`│`)}`)}K.log(`${n}${t(`└${a}┘`)}`)},bi=e=>{let t=new Map;for(let n of e){let e=n.rule,r=t.get(e)??[];r.push(n),t.set(e,r)}return t},xi=e=>[...e].sort(([,e],[,t])=>ui[e[0].severity]-ui[t[0].severity]),Si=e=>{let t=new Map;for(let n of e){let e=t.get(n.filePath)??[];`line`in n&&n.line>0&&e.push(n.line),t.set(n.filePath,e)}return t},Ci=e=>new Set(e.map(e=>e.filePath)),wi=(e,t)=>{let n=xi([...bi(e).entries()]);for(let[,e]of n){let n=e[0],r=di(fi(n.severity),n.severity),i=e.length,a=i>1?di(` (${i})`,n.severity):``;if(K.log(` ${r} ${n.message}${a}`),n.help&&K.dim(` ${n.help}`),t){let t=Si(e);for(let[e,n]of t){let t=n.length>0?`: ${n.join(`, `)}`:``;K.dim(` ${e}${t}`)}}K.break()}};function Ti(e,t){let{score:n,diagnostics:r,project:i,summary:a,elapsedMs:o}=e;K.break();let s=[],c=e=>J(e,n.value),[l,u]=mi(n.value);s.push(q(`┌───────┐`,c(`┌───────┐`))),s.push(q(`│ ${l} │ NestJS Doctor`,`${c(`│ ${l} │`)} NestJS Doctor`)),s.push(q(`│ ${u} │`,c(`│ ${u} │`))),s.push(q(`└───────┘`,c(`└───────┘`))),s.push(q(``));let d=hi(n.value),f=`${n.value} / 100 ${d} ${n.label}`,p=`${J(String(n.value),n.value)} / 100 ${J(d,n.value)} ${J(n.label,n.value)}`;s.push(q(f,p)),s.push(q(``)),s.push(q(_i(n.value),vi(n.value))),s.push(q(``));let m=pi(o),h=Ci(r).size,g=[],_=[];if(a.errors>0){let e=`✗ ${a.errors} error${a.errors===1?``:`s`}`;_.push(e),g.push(W.error(e))}if(a.warnings>0){let e=`⚠ ${a.warnings} warning${a.warnings===1?``:`s`}`;_.push(e),g.push(W.warn(e))}if(a.info>0){let e=`● ${a.info} info`;_.push(e),g.push(W.info(e))}if(r.length===0){let e=`No issues found!`;_.push(e),g.push(W.success(e))}let v=r.length>0?`across ${h}/${i.fileCount} files`:`${i.fileCount} files scanned`,y=`in ${m}`;_.push(v),_.push(y),g.push(W.dim(v)),g.push(W.dim(y)),s.push(q(_.join(` `),g.join(` `))),yi(s),K.break();let b=[`Project: ${i.name}`];if(i.nestVersion&&b.push(`NestJS ${i.nestVersion}`),i.orm&&b.push(i.orm),b.push(`${i.moduleCount} modules`),K.dim(` ${b.join(` | `)}`),K.break(),r.length!==0){if(wi(r,t),t&&e.ruleErrors.length>0){K.warn(` ${e.ruleErrors.length} rule(s) failed during execution:`);for(let t of e.ruleErrors)K.dim(` ${t.ruleId}: ${t.error}`);K.break()}t||(K.dim(` Run with --verbose for file paths and line numbers`),K.break())}}function Ei(e,t){Ti(e.combined,t),K.log(` Sub-project breakdown:`),K.break();for(let t of e.subProjects){let{name:e,result:n}=t,r=J(String(n.score.value),n.score.value),i=[`${W.info(e)}: ${r}/100`,`${n.project.fileCount} files`];n.summary.errors>0&&i.push(W.error(`${n.summary.errors} errors`)),n.summary.warnings>0&&i.push(W.warn(`${n.summary.warnings} warnings`)),n.summary.info>0&&i.push(`${n.summary.info} info`),n.diagnostics.length===0&&i.push(W.success(`clean`)),K.log(` ${i.join(` | `)}`)}K.break()}function Y(e){console.log(JSON.stringify(e,null,2))}const X=(e,t,n)=>{let r=e.score.value;ci(r,t)||(n||K.error(`Score ${r} is below the minimum threshold of ${t}.`),process.exit(1))},Di=e=>{e.summary.errors>0&&process.exit(1)},Oi=(e,t,n,r)=>{let{result:i}=e;if(r.score){console.log(i.combined.score.value),X(i.combined,t,n);return}if(r.json){Y(i.combined),X(i.combined,t,n);return}Ei(i,r.verbose),X(i.combined,t,n),Di(i.combined)},ki=(e,t,n,r)=>{let{result:i}=e;if(r.score){console.log(i.score.value),X(i,t,n);return}if(r.json){Y(i),X(i,t,n);return}Ti(i,r.verbose),X(i,t,n),Di(i)};let Z=null,Q=0;const $=new Set,Ai=(e,t,n)=>{if($.delete(t),Q--,Q<=0||!Z){Z?.[e](n),Z=null,Q=0;return}Z.stop(),b(n).start()[e](n);let[r]=$;r&&(Z.text=r),Z.start()},ji=e=>({start(){return Q++,$.add(e),Z?Z.text=e:Z=b({text:e}).start(),{succeed:t=>Ai(`succeed`,e,t),fail:t=>Ai(`fail`,e,t)}}}),Mi=(e,t)=>{if(!t)for(let t of e)K.warn(t)};var Ni=class{options;resolvedMinimumScore;scanConfig;steps=[];targetPath;constructor(e,t){this.targetPath=e,this.options=t}resolveConfig(){return this.steps.push(async()=>{this.scanConfig=await Rn(this.targetPath,this.options.configPath),this.resolvedMinimumScore=si(this.options.minScore,this.scanConfig.config.minScore)}),this}warnCustomRules(){return this.steps.push(()=>{Mi(this.scanConfig.customRuleWarnings,this.options.isMachineReadable)}),this}async run(){let e=this.options.isMachineReadable?null:ji(`Scanning...`).start();for(let e of this.steps)await e();e?.succeed(`Scan complete`)}},Pi=class extends Ni{monorepo;monorepoCtx;rawOutputs=new Map;result;scanStartTime;constructor(e,t,n){super(e,n),this.monorepo=t}buildContext(){return this.steps.push(async()=>{this.scanStartTime=l.now(),this.monorepoCtx=await Vr(this.targetPath,this.scanConfig,this.monorepo)}),this}runRules(){return this.steps.push(()=>{for(let[e,t]of this.monorepoCtx.subProjects)this.rawOutputs.set(e,H(t))}),this}buildResult(){return this.steps.push(()=>{let e=l.now()-this.scanStartTime;this.result=ai(this.monorepoCtx,this.rawOutputs,this.scanConfig.customRuleWarnings,e)}),this}output(){return this.steps.push(()=>{Oi(this.result,this.resolvedMinimumScore,this.options.isMachineReadable,{json:this.options.json,score:this.options.score,verbose:this.options.verbose})}),this}},Fi=class extends Ni{context;rawOutput;result;buildContext(){return this.steps.push(async()=>{this.context=await Br(this.targetPath,this.scanConfig)}),this}runRules(){return this.steps.push(()=>{this.rawOutput=H(this.context)}),this}buildResult(){return this.steps.push(()=>{this.result=U(this.context,this.rawOutput,this.scanConfig.customRuleWarnings)}),this}output(){return this.steps.push(()=>{ki(this.result,this.resolvedMinimumScore,this.options.isMachineReadable,{json:this.options.json,score:this.options.score,verbose:this.options.verbose})}),this}},Ii=class{args;steps=[];version;targetPath=``;constructor(e,t){this.args=e,this.version=t}resolveTargetPath(){return this.steps.push(()=>(this.targetPath=s(this.args.path??`.`),!0)),this}handleInit(){return this.steps.push(async()=>{if(this.args.init){let{initSkill:e}=await import(`../init-DBZfqc-K.mjs`);return await e(this.targetPath,this.version),!1}return!0}),this}handleReport(){return this.steps.push(async()=>{if(this.args.report){let{runReport:e}=await import(`../setup-Bc6apGDV.mjs`);return await e(this.targetPath,this.args.config),!1}return!0}),this}validateMinScore(){return this.steps.push(()=>{if(this.args[`min-score`]!==void 0){let e=oi(this.args[`min-score`]);e&&(K.error(e),process.exit(2))}return!0}),this}async run(){for(let e of this.steps)if(!await e())return null;return{targetPath:this.targetPath,options:{configPath:this.args.config,isMachineReadable:this.args.score||this.args.json,json:this.args.json??!1,minScore:this.args[`min-score`],score:this.args.score??!1,verbose:this.args.verbose??!1}}}};const{version:Li}=e(import.meta.url)(`../../package.json`);n(t({meta:{name:`nestjs-doctor`,version:Li,description:`Static analysis tool for NestJS — health score, diagnostics, and interactive HTML report`},args:{path:{type:`positional`,description:`Path to the NestJS project (defaults to current directory)`,default:`.`,required:!1},...be},async run({args:e}){let t=await new Ii(e,Li).resolveTargetPath().handleInit().handleReport().validateMinScore().run();if(!t)return;let{targetPath:n,options:r}=t,i=await me(n);if(i){await new Pi(n,i,r).resolveConfig().buildContext().runRules().buildResult().warnCustomRules().output().run();return}await he(n)&&console.warn(`Warning: This directory appears to be a monorepo, but no NestJS packages were found.
|
|
7
|
-
Consider running on a specific sub-project instead.`),await new
|
|
3
|
+
`),r=!1;for(let e of n){let n=e.trim();if(ee.test(n)){let e=n.match(te);if(e){for(let n of e[1].split(`,`)){let e=n.trim().replace(ie,``);e&&t.push(e)}return t}r=!0;continue}if(r){if(ne.test(e)&&n!==``)break;let r=n.match(re);r&&t.push(r[1])}}return t}async function oe(e){let t=a(e,`nest-cli.json`);try{let e=await r(t,`utf-8`),n=JSON.parse(e);if(!(n.monorepo&&n.projects))return null;let i=new Map;for(let[e,t]of Object.entries(n.projects)){let n=t.root??e;i.set(e,n)}return i.size===0?null:{projects:i}}catch{return null}}function se(e){let t={...e.dependencies,...e.devDependencies,...e.peerDependencies};return!!(t[`@nestjs/core`]||t[`@nestjs/common`])}async function x(e,t){let n=await c(t.map(e=>`${e}/package.json`),{cwd:e,absolute:!0,ignore:[`**/node_modules/**`]}),a=new Map;for(let t of n)try{let n=await r(t,`utf-8`),s=JSON.parse(n);if(se(s)){let n=o(e,i(t)),r=s.name??n;a.set(r,n)}}catch{}return a.size===0?null:{projects:a}}async function ce(e){let t=a(e,`pnpm-workspace.yaml`),n;try{n=await r(t,`utf-8`)}catch{return null}let i=ae(n);return i.length===0?null:x(e,i)}function le(e){let t=e.workspaces;if(!t)return[];if(Array.isArray(t))return t.filter(e=>typeof e==`string`);if(typeof t==`object`&&t){let e=t;if(Array.isArray(e.packages))return e.packages.filter(e=>typeof e==`string`)}return[]}async function ue(e){let t=a(e,`package.json`),n;try{n=await r(t,`utf-8`)}catch{return null}let i=le(JSON.parse(n));return i.length===0?null:x(e,i)}async function de(e){let t=a(e,`lerna.json`),n;try{n=await r(t,`utf-8`)}catch{return null}let i=JSON.parse(n);if(i.useWorkspaces)return null;let o=i.packages??[`packages/*`];return o.length===0?null:x(e,o)}async function fe(e){let t=a(e,`nx.json`);try{await r(t,`utf-8`)}catch{return null}let n=await c([`**/project.json`],{cwd:e,absolute:!0,ignore:[`node_modules/**`]}),s=new Map;for(let t of n){let n=i(t),c=o(e,n);if(c===``)continue;let l=a(n,`package.json`);try{let e=await r(l,`utf-8`),t=JSON.parse(e);if(se(t)){let e=t.name??c;s.set(e,c)}}catch{}}return s.size===0?null:{projects:s}}async function pe(e){try{return await r(a(e,`pnpm-workspace.yaml`),`utf-8`),!0}catch{return!1}}async function me(e){let t=await oe(e);if(t)return t;let n=await ce(e);if(n)return n;if(!await pe(e)){let t=await ue(e);if(t)return t}return await fe(e)||de(e)}async function he(e){for(let t of[`lerna.json`,`turbo.json`,`nx.json`,`pnpm-workspace.yaml`])try{return await r(a(e,t),`utf-8`),!0}catch{}try{let t=await r(a(e,`package.json`),`utf-8`);if(JSON.parse(t).workspaces)return!0}catch{}return!1}async function ge(e){let t=a(e,`package.json`),n={};try{let e=await r(t,`utf-8`);n=JSON.parse(e)}catch{}let i={...n.dependencies,...n.devDependencies},o=_e(i[`@nestjs/core`]),s=ve(i),c=ye(i);return{name:n.name??`unknown`,nestVersion:o,orm:s,framework:c,moduleCount:0,fileCount:0}}function _e(e){return e?e.replace(/[\^~>=<]/g,``):null}function ve(e){return e[`@prisma/client`]?`prisma`:e.typeorm?`typeorm`:e[`@mikro-orm/core`]?`mikro-orm`:e.sequelize?`sequelize`:e.mongoose?`mongoose`:e[`drizzle-orm`]?`drizzle`:null}function ye(e){return e[`@nestjs/platform-fastify`]?`fastify`:e[`@nestjs/platform-express`]||e[`@nestjs/core`]?`express`:null}const be={verbose:{type:`boolean`,description:`Show file paths and line numbers per diagnostic`,default:!1},score:{type:`boolean`,description:`Output only the numeric score (for CI)`,default:!1},json:{type:`boolean`,description:`JSON output`,default:!1},"min-score":{type:`string`,description:`Minimum passing score (0-100). Exits with code 1 if below threshold`},config:{type:`string`,description:`Config file path`},report:{type:`boolean`,alias:`graph`,description:`Generate an interactive HTML report (summary, diagnostics, module graph, rule lab)`,default:!1},init:{type:`boolean`,description:`Set up the nestjs-doctor skill for AI coding agents (Claude Code, Cursor, Codex, etc.)`,default:!1}},S={include:[`**/*.ts`],exclude:`**/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,*.config.ts,*.config.js,*.config.mjs,*.config.cjs,*.config.mts,*.config.cts`.split(`,`)},xe=[`nestjs-doctor.config.json`,`.nestjs-doctor.json`];async function Se(e,t){if(t)return Ce(t);for(let t of xe)try{return await Ce(a(e,t))}catch{}try{let t=await r(a(e,`package.json`),`utf-8`),n=JSON.parse(t);if(n[`nestjs-doctor`]&&typeof n[`nestjs-doctor`]==`object`)return we(n[`nestjs-doctor`])}catch{}return{...S}}async function Ce(e){let t=await r(e,`utf-8`);return we(JSON.parse(t))}function we(e){return{...S,...e,exclude:[...S.exclude??[],...e.exclude??[]]}}async function Te(e,t){try{return await Se(e)}catch{return t}}const Ee=[/Repository$/,/\.repository$/,/\.entity$/,/\.schema$/,/\.guard$/,/\.interceptor$/,/\.pipe$/,/\.filter$/,/\.strategy$/],De={meta:{id:`architecture/no-barrel-export-internals`,category:`architecture`,severity:`info`,description:`Don't re-export internal implementation details from barrel files`,help:`Only export the module's public API (services, DTOs, interfaces) from index.ts files.`},check(e){if(e.filePath.endsWith(`/index.ts`))for(let t of e.sourceFile.getExportDeclarations()){let n=t.getModuleSpecifierValue();if(n){Ee.some(e=>e.test(n))&&e.report({filePath:e.filePath,message:`Barrel file re-exports internal module '${n}'.`,help:this.meta.help,line:t.getStartLineNumber(),column:1});for(let n of t.getNamedExports()){let t=n.getName();(t.endsWith(`Repository`)||t.endsWith(`Entity`)||t.endsWith(`Schema`))&&e.report({filePath:e.filePath,message:`Barrel file re-exports internal type '${t}'.`,help:this.meta.help,line:n.getStartLineNumber(),column:1})}}}}},C=new Set([`Get`,`Post`,`Put`,`Patch`,`Delete`,`Head`,`Options`,`All`]);function w(e,t){return e.getDecorator(t)!==void 0}function T(e){return w(e,`Controller`)}function E(e){return w(e,`Injectable`)}function D(e){return w(e,`Injectable`)||w(e,`Controller`)||w(e,`Resolver`)||w(e,`WebSocketGateway`)}function O(e){return w(e,`Module`)}function k(e){return e.getDecorators().some(e=>C.has(e.getName()))}const Oe=new Set([`TsRestHandler`,`GrpcMethod`,`GrpcStreamMethod`]);function ke(e){return e.getDecorators().some(e=>Oe.has(e.getName()))}const Ae={meta:{id:`architecture/no-business-logic-in-controllers`,category:`architecture`,severity:`error`,description:`Controllers should only handle HTTP concerns — move business logic to services`,help:`Extract branches, loops, and complex calculations into a service method.`},check(e){for(let t of e.sourceFile.getClasses())if(T(t))for(let n of t.getMethods()){if(!n.getDecorators().some(e=>C.has(e.getName())))continue;let t=n.getBody();if(!t)continue;let r=t.getDescendantsOfKind(d.IfStatement),i=t.getDescendantsOfKind(d.ForStatement),a=t.getDescendantsOfKind(d.ForInStatement),o=t.getDescendantsOfKind(d.ForOfStatement),s=t.getDescendantsOfKind(d.WhileStatement),c=t.getDescendantsOfKind(d.SwitchStatement),l=i.length+a.length+o.length+s.length;(r.length>1||l>0||c.length>0)&&e.report({filePath:e.filePath,message:`Controller method '${n.getName()}' contains business logic (${r.length} if, ${l} loops, ${c.length} switch). Move to a service.`,help:this.meta.help,line:n.getStartLineNumber(),column:1});let u=t.getDescendantsOfKind(d.CallExpression).filter(e=>{let t=e.getExpression();if(t.getKind()===d.PropertyAccessExpression){let e=t.asKind(d.PropertyAccessExpression)?.getName();return e===`map`||e===`filter`||e===`reduce`||e===`sort`||e===`flatMap`}return!1});u.length>1&&e.report({filePath:e.filePath,message:`Controller method '${n.getName()}' contains data transformation logic (${u.length} array operations). Move to a service.`,help:this.meta.help,line:n.getStartLineNumber(),column:1})}}};function A(e){let t=new Map;try{let n=f.findConfigFile(e,f.sys.fileExists,`tsconfig.json`);if(!n)return t;let{config:r,error:a}=f.readConfigFile(n,f.sys.readFile);if(a||!r)return t;let o=i(n),c=f.parseJsonConfigFileContent(r,f.sys,o),l=c.options.paths;if(!l)return t;let u=c.options.baseUrl??o;for(let[e,n]of Object.entries(l)){let r=n.map(e=>s(u,e));t.set(e,r)}}catch{return t}return t}function je(e,t){for(let[n,r]of t){if(r.length===0)continue;let t=n.indexOf(`*`);if(t===-1){if(e===n)return r[0];continue}let i=n.slice(0,t),a=n.slice(t+1);if(e.startsWith(i)&&e.endsWith(a)&&e.length>=i.length+a.length){let t=e.slice(i.length,e.length-a.length),n=r[0],o=n.indexOf(`*`);return o===-1?n:n.slice(0,o)+t+n.slice(o+1)}}}const Me=/=>\s*(\w+)/,j=/\.js$/;function Ne(e,t,n){let r=[];for(let i of e.getClasses()){let e=i.getDecorator(`Module`);if(!e)continue;let a=i.getName()??`AnonymousModule`,o=e.getArguments()[0],s={name:a,filePath:t,classDeclaration:i,imports:[],exports:[],providers:[],controllers:[]};if(o&&o.getKind()===d.ObjectLiteralExpression){let e=o.asKind(d.ObjectLiteralExpression);e&&(s.imports=N(e,`imports`,n),s.exports=N(e,`exports`,n),s.providers=N(e,`providers`,n),s.controllers=N(e,`controllers`,n))}r.push(s)}return r}function M(e,t,n=new Map){let r=new Map,i=new Map;for(let i of t){let t=e.getSourceFile(i);if(t)for(let e of Ne(t,i,n))r.set(e.name,e)}for(let[e,t]of r){let n=new Set;for(let e of t.imports)r.has(e)&&n.add(e);i.set(e,n)}let a=new Map;for(let e of r.values())for(let t of e.providers)a.set(t,e);return{modules:r,edges:i,providerToModule:a}}const Pe=new Set([`forRoot`,`forRootAsync`,`forFeature`,`forFeatureAsync`,`forChild`,`forChildAsync`,`register`,`registerAsync`]);function N(e,t,n){let r=e.getProperty(t);if(!r)return[];let i=r.asKind(d.PropertyAssignment);if(!i)return[];let a=i.getInitializer();return a?P(a,e.getSourceFile(),0,n):[]}function P(e,t,n,r){if(n>5)return[];let i=e.getKind();if(i===d.ArrayLiteralExpression){let i=e.asKindOrThrow(d.ArrayLiteralExpression),a=[];for(let e of i.getElements())a.push(...Fe(e,t,n,r));return a}return i===d.CallExpression?Ie(e.asKindOrThrow(d.CallExpression),t,n,r):i===d.Identifier?ze(e.getText(),t,n+1,r):[]}function Fe(e,t,n,r){let i=e.getText();if(i.startsWith(`forwardRef`)){let e=i.match(Me);return e?[e[1]]:[i]}let a=e.getKind();return a===d.SpreadElement?P(e.asKindOrThrow(d.SpreadElement).getExpression(),t,n,r):a===d.CallExpression?Ie(e.asKindOrThrow(d.CallExpression),t,n,r):a===d.PropertyAccessExpression?[e.asKindOrThrow(d.PropertyAccessExpression).getExpression().getText()]:(d.Identifier,[i])}function Ie(e,t,n,r){let i=e.getExpression();if(i.getKind()===d.PropertyAccessExpression){let a=i.asKindOrThrow(d.PropertyAccessExpression),o=a.getName();if(o===`concat`){let i=P(a.getExpression(),t,n,r),o=[];for(let i of e.getArguments())o.push(...P(i,t,n,r));return[...i,...o]}return Pe.has(o),[a.getExpression().getText()]}return i.getKind()===d.Identifier?Ve(i.getText(),t,n+1,r):[]}function Le(e,t,n){if(!e.startsWith(`.`)){let r=je(e,n);if(!r)return;let i=t.getProject(),a=[`${r}.ts`,`${r}/index.ts`,r,r.replace(j,`.ts`)];for(let e of a){let t=i.getSourceFile(e);if(t)return t}return}let r=s(i(t.getFilePath()),e),a=t.getProject(),o=[`${r}.ts`,`${r}/index.ts`,r,r.replace(j,`.ts`)];for(let e of o){let t=a.getSourceFile(e);if(t)return t}}function Re(e,t,n){for(let r of t.getImportDeclarations())for(let i of r.getNamedImports())if((i.getAliasNode()?i.getAliasNode().getText():i.getName())===e){let e=Le(r.getModuleSpecifierValue(),t,n);return e?{sourceFile:e,localName:i.getName()}:void 0}for(let r of t.getExportDeclarations())if(r.getModuleSpecifierValue()){for(let i of r.getNamedExports())if((i.getAliasNode()?i.getAliasNode().getText():i.getName())===e){let e=Le(r.getModuleSpecifierValue(),t,n);return e?{sourceFile:e,localName:i.getName()}:void 0}}}function ze(e,t,n,r){if(n>5)return[];for(let i of t.getStatements()){if(i.getKind()!==d.VariableStatement)continue;let a=i.asKindOrThrow(d.VariableStatement);for(let i of a.getDeclarations())if(i.getName()===e){let e=i.getInitializer();if(e)return P(e,t,n,r)}}let i=Re(e,t,r);return i?ze(i.localName,i.sourceFile,n+1,r):[]}function Be(e,t,n,r){for(let i of t.getStatements()){if(i.getKind()!==d.VariableStatement)continue;let a=i.asKindOrThrow(d.VariableStatement);for(let i of a.getDeclarations()){if(i.getName()!==e)continue;let a=i.getInitializer();if(!a||a.getKind()!==d.ArrowFunction)continue;let o=a.asKindOrThrow(d.ArrowFunction).getBody();if(o.getKind()!==d.Block)return P(o,t,n,r);let s=[];for(let e of o.getDescendantsOfKind(d.ReturnStatement)){let i=e.getExpression();i&&s.push(...P(i,t,n,r))}return s}}}function Ve(e,t,n,r){if(n>5)return[];for(let i of t.getStatements()){if(i.getKind()!==d.FunctionDeclaration)continue;let a=i.asKindOrThrow(d.FunctionDeclaration);if(a.getName()!==e)continue;let o=[];for(let e of a.getDescendantsOfKind(d.ReturnStatement)){let i=e.getExpression();i&&o.push(...P(i,t,n,r))}return o}let i=Be(e,t,n,r);if(i)return i;let a=Re(e,t,r);return a?Ve(a.localName,a.sourceFile,n+1,r):[]}function He(e){let t=new Map,n=new Map,r=new Map;for(let[i,a]of e){for(let[e,n]of a.modules){let r=`${i}/${e}`,o={...n,name:r,imports:n.imports.map(e=>a.modules.has(e)?`${i}/${e}`:e),exports:n.exports.map(e=>a.modules.has(e)?`${i}/${e}`:e)};t.set(r,o)}for(let[e,t]of a.edges){let r=`${i}/${e}`,a=new Set;for(let e of t)a.add(`${i}/${e}`);n.set(r,a)}for(let[e,n]of a.providerToModule){let a=`${i}/${n.name}`,o=t.get(a);o&&r.set(`${i}/${e}`,o)}}return{modules:t,edges:n,providerToModule:r}}function Ue(e){let t=[],n=new Set,r=new Set;function i(a,o){n.add(a),r.add(a);let s=e.edges.get(a)??new Set;for(let e of s)if(!n.has(e))i(e,[...o,e]);else if(r.has(e)){let n=o.indexOf(e);n===-1?t.push([...o,e]):t.push(o.slice(n))}r.delete(a)}for(let t of e.modules.keys())n.has(t)||i(t,[t]);return t}function We(e,t,n,r,i,a){let o=[];for(let i of e.providers){let e=n.get(i);if(e)for(let n of e.dependencies){let e=r.get(n);e&&e.name===t.name&&o.push({consumer:i,dependency:n})}}for(let n of e.controllers)for(let e of a){let a=i.getSourceFile(e);if(a)for(let e of a.getClasses()){if(e.getName()!==n)continue;let i=e.getConstructors()[0];if(i)for(let e of i.getParameters()){let i=e.getTypeNode(),a=i?i.getText():e.getType().getText(),s=a.split(`.`).pop()?.split(`<`)[0]??a,c=r.get(s);c&&c.name===t.name&&o.push({consumer:n,dependency:s})}}}return o}const Ge=`Break the cycle by extracting shared logic into a separate module or using forwardRef().`;function Ke(e,t){let{moduleGraph:n,providers:r,project:i,files:a}=t,o=[],s;for(let t=0;t<e.length;t++){let c=e[t],l=e[(t+1)%e.length],u=n.modules.get(c),d=n.modules.get(l);if(!(u&&d))continue;let f=We(u,d,r,n.providerToModule,i,a);if(f.length===0)continue;let p=new Map;for(let e of f){let t=p.get(e.consumer);t?t.push(e.dependency):p.set(e.consumer,[e.dependency])}let m=[];for(let[e,t]of p){let n=t.map(e=>`${e} (from ${l})`).join(`, `);m.push(`${e} (in ${c}) injects ${n}`)}let h=`${c} -> ${l}: ${m.join(`; `)}`;o.push(h),(!s||f.length<s.count)&&(s={description:`${c} -> ${l}`,count:f.length})}if(o.length===0)return Ge;let c=o.join(`
|
|
4
|
+
`);if(s){let e=s.count===1?`dependency`:`dependencies`,t=s.description.split(` -> `)[0],o=s.description.split(` -> `)[1],l=n.modules.get(t),u=n.modules.get(o);if(l&&u){let t=We(l,u,r,n.providerToModule,i,a),o=[...new Set(t.map(e=>e.dependency))].join(`, `);c+=`\nConsider extracting ${o} into a shared module — it would break the ${s.description} edge (${s.count} ${e}).`}}return c}const qe={meta:{id:`architecture/no-circular-module-deps`,category:`architecture`,severity:`error`,description:`Module import graph must not contain circular dependencies`,help:Ge,scope:`project`},check(e){let t=Ue(e.moduleGraph);for(let n of t){let t=n.join(` -> `),r=e.moduleGraph.modules.get(n[0]),i=Ke(n,e);e.report({filePath:r?.filePath??`unknown`,message:`Circular module dependency detected: ${t}`,help:i,line:r?.classDeclaration.getStartLineNumber()??1,column:1})}}},Je=[`Service`,`Repository`,`Gateway`,`Resolver`],Ye=[`Guard`,`Interceptor`,`Pipe`,`Filter`];function Xe(e){return typeof e==`object`&&!!e}function Ze(e){if(!Xe(e))return new Set;let t=e.excludeClasses;if(Array.isArray(t))return new Set(t.filter(e=>typeof e==`string`));let n=e.options;if(!Xe(n))return new Set;let r=n.excludeClasses;return Array.isArray(r)?new Set(r.filter(e=>typeof e==`string`)):new Set}const Qe={meta:{id:`architecture/no-manual-instantiation`,category:`architecture`,severity:`error`,description:`Do not manually instantiate @Injectable classes — use NestJS dependency injection`,help:`Register the class as a provider in a module and inject it via the constructor.`},check(e){let t=Ze(e.config?.rules?.[this.meta.id]),n=e.sourceFile.getDescendantsOfKind(d.NewExpression);for(let r of n){let n=r.getExpression().getText(),i=n.split(`.`).pop()??n;if(t.has(n)||t.has(i))continue;let a=Je.some(e=>n.endsWith(e)),o=Ye.some(e=>n.endsWith(e));if(a||o){if(o){if(r.getFirstAncestorByKind(d.Decorator))continue;let e=r.getFirstAncestorByKind(d.MethodDeclaration),t=r.getFirstAncestorByKind(d.Constructor);if(!(e||t))continue}e.report({filePath:e.filePath,message:`Manual instantiation of '${n}' detected. Use dependency injection instead.`,help:this.meta.help,line:r.getStartLineNumber(),column:1})}}}},$e=/\.(\w+)$/,et=/^(\w+)</,tt=new Set([`PrismaService`,`PrismaClient`,`EntityManager`,`DataSource`,`Repository`,`Connection`,`MongooseModel`,`InjectModel`,`InjectRepository`,`MikroORM`,`DrizzleService`]),nt={meta:{id:`architecture/no-orm-in-controllers`,category:`architecture`,severity:`error`,description:`Controllers must not inject ORM services directly — use a service layer`,help:`Inject a service that wraps the ORM instead of using the ORM directly in controllers.`},check(e){for(let t of e.sourceFile.getClasses()){if(!T(t))continue;let n=t.getConstructors()[0];if(n){for(let t of n.getParameters()){let n=rt(t.getType().getText());if(tt.has(n)){let r=t.getNameNode();e.report({filePath:e.filePath,message:`Controller injects ORM type '${n}' directly. Use a service layer.`,help:this.meta.help,line:r.getStartLineNumber(),column:r.getStartLinePos()+1})}}for(let n of t.getConstructors()[0]?.getParameters()??[])for(let t of n.getDecorators()){let n=t.getName();(n===`InjectRepository`||n===`InjectModel`)&&e.report({filePath:e.filePath,message:`Controller uses @${n}() decorator. Move data access to a service.`,help:this.meta.help,line:t.getStartLineNumber(),column:1})}}}}};function rt(e){let t=e.match($e);if(t)return t[1];let n=e.match(et);return n?n[1]:e}const it=/\.(\w+)$/,at=/^(\w+)</,ot=new Set([`PrismaService`,`PrismaClient`,`EntityManager`,`DataSource`,`Connection`,`MikroORM`]),st={meta:{id:`architecture/no-orm-in-services`,category:`architecture`,severity:`info`,description:`Services should use repository abstractions instead of ORM directly`,help:`Create a repository class that wraps ORM calls and inject that instead. Note: If your project follows the official NestJS Prisma recipe (injecting PrismaService directly), you can disable this rule.`},check(e){for(let t of e.sourceFile.getClasses()){if(!E(t))continue;let n=t.getName()??``;if(n.endsWith(`Repository`)||n.endsWith(`Repo`))continue;let r=t.getConstructors()[0];if(r)for(let t of r.getParameters()){let n=ct(t.getType().getText());if(ot.has(n)){let r=t.getNameNode();e.report({filePath:e.filePath,message:`Service injects ORM type '${n}' directly. Consider using a repository abstraction.`,help:this.meta.help,line:r.getStartLineNumber(),column:r.getStartLinePos()+1})}for(let n of t.getDecorators()){let t=n.getName();(t===`InjectRepository`||t===`InjectModel`)&&e.report({filePath:e.filePath,message:`Service uses @${t}() directly. Consider wrapping in a repository class.`,help:this.meta.help,line:n.getStartLineNumber(),column:1})}}}}};function ct(e){let t=e.match(it);if(t)return t[1];let n=e.match(at);return n?n[1]:e}const lt=/\.(\w+)$/,ut=/^(\w+)</,dt=[/Repository$/,/Repo$/],ft={meta:{id:`architecture/no-repository-in-controllers`,category:`architecture`,severity:`error`,description:`Controllers must not inject repositories directly — use the service layer`,help:`Move database access to a service and inject the service into the controller instead.`},check(e){for(let t of e.sourceFile.getClasses()){if(!T(t))continue;let n=t.getConstructors()[0];if(n){for(let t of n.getParameters()){let n=pt(t.getType().getText());if(dt.some(e=>e.test(n))){let r=t.getNameNode();e.report({filePath:e.filePath,message:`Controller injects repository '${n}' directly. Use a service layer instead.`,help:this.meta.help,line:r.getStartLineNumber(),column:r.getStartLinePos()+1})}}for(let t of e.sourceFile.getImportDeclarations()){let n=t.getModuleSpecifierValue();(n.includes(`/repositories/`)||n.includes(`/repositories`))&&e.report({filePath:e.filePath,message:`Controller imports from repository path '${n}'.`,help:this.meta.help,line:t.getStartLineNumber(),column:1})}}}}};function pt(e){let t=e.match(lt);if(t)return t[1];let n=e.match(ut);return n?n[1]:e}const mt={meta:{id:`architecture/no-service-locator`,category:`architecture`,severity:`warning`,description:`Avoid using ModuleRef.get() or ModuleRef.resolve() — prefer explicit constructor injection`,help:`Replace ModuleRef.get()/resolve() with constructor injection for explicit, testable dependencies.`},check(e){let t=e.sourceFile.getDescendantsOfKind(d.CallExpression);for(let n of t){let t=n.getExpression();if(t.getKind()!==d.PropertyAccessExpression)continue;let r=t.asKind(d.PropertyAccessExpression);if(!r)continue;let i=r.getName();if(i!==`get`&&i!==`resolve`)continue;let a=r.getExpression().getText();(a===`moduleRef`||a===`this.moduleRef`)&&e.report({filePath:e.filePath,message:`Service locator pattern: '${a}.${i}()' hides dependencies. Use constructor injection instead.`,help:this.meta.help,line:n.getStartLineNumber(),column:1})}}},ht={meta:{id:`architecture/prefer-constructor-injection`,category:`architecture`,severity:`warning`,description:`Prefer constructor injection over @Inject() property injection`,help:`Move the dependency to a constructor parameter instead of using property injection.`},check(e){for(let t of e.sourceFile.getClasses())if(D(t))for(let n of t.getProperties())n.getDecorator(`Inject`)&&e.report({filePath:e.filePath,message:`Property '${n.getName()}' uses @Inject() decorator. Prefer constructor injection.`,help:this.meta.help,line:n.getStartLineNumber(),column:1})}},gt=[`/repositories/`,`/entities/`,`/dto/`,`/guards/`,`/interceptors/`,`/pipes/`,`/strategies/`],_t={meta:{id:`architecture/require-module-boundaries`,category:`architecture`,severity:`info`,description:`Avoid deep imports into other feature modules' internals`,help:`Import from the module's public API (barrel export) instead of reaching into its internals.`},check(e){for(let t of e.sourceFile.getImportDeclarations()){let n=t.getModuleSpecifierValue();n.startsWith(`.`)&&n.includes(`../`)&>.some(e=>n.includes(e))&&e.report({filePath:e.filePath,message:`Import '${n}' reaches into another module's internals.`,help:this.meta.help,line:t.getStartLineNumber(),column:1})}}},vt={meta:{id:`correctness/factory-inject-matches-params`,category:`correctness`,severity:`error`,description:`useFactory inject array length must match the factory function parameter count`,help:`Ensure the 'inject' array has one entry per factory function parameter.`},check(e){for(let t of e.sourceFile.getClasses()){if(!O(t))continue;let n=t.getDecorator(`Module`);if(!n)continue;let r=n.getArguments()[0];if(!r||r.getKind()!==d.ObjectLiteralExpression)continue;let i=r.asKind(d.ObjectLiteralExpression);if(!i)continue;let a=i.getProperty(`providers`);if(!a)continue;let o=a.getChildrenOfKind(d.ArrayLiteralExpression)[0];if(o)for(let t of o.getElements()){if(t.getKind()!==d.ObjectLiteralExpression)continue;let n=t.asKind(d.ObjectLiteralExpression);if(!n)continue;let r=n.getProperty(`useFactory`),i=n.getProperty(`inject`);if(!(r&&i))continue;let a=i.getChildrenOfKind(d.ArrayLiteralExpression)[0];if(!a)continue;let o=a.getElements().length,s,c=r.asKind(d.MethodDeclaration);if(c)s=c.getParameters().length;else{let e=r.asKind(d.PropertyAssignment);if(!e)continue;let t=e.getInitializer();if(!t)continue;t.getKind()===d.ArrowFunction?s=t.asKind(d.ArrowFunction)?.getParameters().length:t.getKind()===d.FunctionExpression&&(s=t.asKind(d.FunctionExpression)?.getParameters().length)}s!==void 0&&o!==s&&e.report({filePath:e.filePath,message:`Factory has ${s} parameter(s) but inject array has ${o} element(s).`,help:this.meta.help,line:t.getStartLineNumber(),column:1})}}}},yt=[`Guard`,`Interceptor`,`Filter`,`Pipe`,`Middleware`,`Strategy`,`Subscriber`,`Listener`,`Processor`,`Consumer`,`Worker`,`Scheduler`,`Cron`,`HealthIndicator`],bt={meta:{id:`correctness/injectable-must-be-provided`,category:`correctness`,severity:`info`,description:`@Injectable() classes should be registered in at least one module's providers array`,help:`Add this class to a module's providers array, or remove the @Injectable() decorator if unused.`,scope:`project`},check(e){let t=new Set;for(let n of e.moduleGraph.modules.values()){for(let e of n.providers)t.add(e);for(let e of n.controllers)t.add(e)}for(let n of e.files){let r=e.project.getSourceFile(n);if(r)for(let e of r.getClasses()){if(!O(e))continue;let n=e.getDecorator(`Module`);if(!n)continue;let r=n.getArguments()[0];if(!r||r.getKind()!==d.ObjectLiteralExpression)continue;let i=r.asKind(d.ObjectLiteralExpression);if(!i)continue;let a=i.getProperty(`providers`);if(!a)continue;let o=a.getChildrenOfKind(d.ArrayLiteralExpression)[0];if(o)for(let e of o.getElements()){if(e.getKind()!==d.ObjectLiteralExpression)continue;let n=e.asKind(d.ObjectLiteralExpression);if(n)for(let e of[`useClass`,`useExisting`]){let r=n.getProperty(e);if(!r)continue;let i=r.asKind(d.PropertyAssignment);if(!i)continue;let a=i.getInitializer();a&&t.add(a.getText())}}}}for(let n of e.files){if(n.includes(`.spec.`)||n.includes(`.test.`)||n.includes(`__test__`)||n.includes(`__tests__`))continue;let r=e.project.getSourceFile(n);if(r)for(let i of r.getClasses()){if(!i.getDecorator(`Injectable`))continue;let r=i.getName();r&&(yt.some(e=>r.endsWith(e))||t.has(r)||e.report({filePath:n,message:`@Injectable() class '${r}' is not registered in any module's providers array.`,help:this.meta.help,line:i.getStartLineNumber(),column:1}))}}}};function xt(e){return e.getDescendantsOfKind(d.ReturnStatement).some(e=>{let t=e.getExpression();return!t||t.getKind()!==d.NewExpression?!1:t.asKindOrThrow(d.NewExpression).getExpression().getText()===`Promise`})}const St={meta:{id:`correctness/no-async-without-await`,category:`correctness`,severity:`warning`,description:`Async functions/methods should contain at least one await expression`,help:`Either add an await expression or remove the async keyword. HTTP handlers with route decorators are exempted, as async is conventional for controller methods.`},check(e){for(let t of e.sourceFile.getClasses())for(let n of t.getMethods()){if(!n.isAsync()||T(t)&&k(n)||ke(n))continue;let r=n.getBody();if(r&&r.getDescendantsOfKind(d.AwaitExpression).filter(e=>{let t=e.getParent();for(;t&&t!==r;){if(t.getKind()===d.ArrowFunction||t.getKind()===d.FunctionExpression||t.getKind()===d.FunctionDeclaration)return!1;t=t.getParent()}return!0}).length===0){let t=n.getName();xt(r)?e.report({filePath:e.filePath,message:`Async method '${t}()' returns a Promise directly — remove the async keyword.`,help:`The async keyword is unnecessary when you are already constructing a Promise manually. Remove async to avoid double-wrapping.`,line:n.getStartLineNumber(),column:1}):e.report({filePath:e.filePath,message:`Async method '${t}()' has no await expression.`,help:this.meta.help,line:n.getStartLineNumber(),column:1})}}for(let t of e.sourceFile.getFunctions()){if(!t.isAsync())continue;let n=t.getBody();if(n&&n.getDescendantsOfKind(d.AwaitExpression).filter(e=>{let t=e.getParent();for(;t&&t!==n;){if(t.getKind()===d.ArrowFunction||t.getKind()===d.FunctionExpression||t.getKind()===d.FunctionDeclaration)return!1;t=t.getParent()}return!0}).length===0){let r=t.getName()??`anonymous`;xt(n)?e.report({filePath:e.filePath,message:`Async function '${r}()' returns a Promise directly — remove the async keyword.`,help:`The async keyword is unnecessary when you are already constructing a Promise manually. Remove async to avoid double-wrapping.`,line:t.getStartLineNumber(),column:1}):e.report({filePath:e.filePath,message:`Async function '${r}()' has no await expression.`,help:this.meta.help,line:t.getStartLineNumber(),column:1})}}}},Ct=new Set([`ApiResponse`,`ApiQuery`,`ApiParam`,`ApiHeader`,`ApiSecurity`,`SetMetadata`,`Roles`,`Header`,`Throttle`]),wt={meta:{id:`correctness/no-duplicate-decorators`,category:`correctness`,severity:`warning`,description:`Same decorator should not appear twice on a single target`,help:`Remove the duplicate decorator — it was likely copy-pasted by mistake.`},check(e){for(let t of e.sourceFile.getClasses()){F(t.getDecorators(),e,this.meta.help);for(let n of t.getMethods())F(n.getDecorators(),e,this.meta.help);for(let n of t.getProperties())F(n.getDecorators(),e,this.meta.help);for(let n of t.getConstructors())for(let t of n.getParameters())F(t.getDecorators(),e,this.meta.help)}}};function F(e,t,n){let r=new Set;for(let i of e){let e=i.getName();Ct.has(e)||(r.has(e)?t.report({filePath:t.filePath,message:`Duplicate @${e}() decorator on the same target.`,help:n,line:i.getStartLineNumber(),column:1}):r.add(e))}}const Tt=[`providers`,`controllers`,`imports`,`exports`],Et={meta:{id:`correctness/no-duplicate-module-metadata`,category:`correctness`,severity:`warning`,description:`Same identifier should not appear twice in a module metadata array`,help:`Remove the duplicate entry from the module metadata.`},check(e){for(let t of e.sourceFile.getClasses()){if(!O(t))continue;let n=t.getDecorator(`Module`);if(!n)continue;let r=n.getArguments()[0];if(!r||r.getKind()!==d.ObjectLiteralExpression)continue;let i=r.asKind(d.ObjectLiteralExpression);if(i)for(let t of Tt){let n=i.getProperty(t);if(!n)continue;let r=n.getChildrenOfKind(d.ArrayLiteralExpression)[0];if(!r)continue;let a=new Set;for(let n of r.getElements()){let r=n.getText();a.has(r)?e.report({filePath:e.filePath,message:`Duplicate '${r}' in @Module() ${t} array.`,help:this.meta.help,line:n.getStartLineNumber(),column:1}):a.add(r)}}}}},Dt={meta:{id:`correctness/no-duplicate-routes`,category:`correctness`,severity:`error`,description:`Same HTTP method + route path + version should not appear twice in a single controller`,help:`Remove or rename one of the duplicate route handlers.`},check(e){for(let t of e.sourceFile.getClasses()){if(!T(t))continue;let n=new Map;for(let r of t.getMethods())for(let t of r.getDecorators()){let i=t.getName();if(!C.has(i))continue;let a=t.getArguments(),o=a.length>0?a[0].getText():`""`,s=r.getDecorator(`Version`),c=`${i}:${o}:${s?s.getArguments()[0]?.getText()??``:``}`,l=n.get(c);l?e.report({filePath:e.filePath,message:`Duplicate route: @${i}(${o}) is already defined in '${l}()'.`,help:this.meta.help,line:r.getStartLineNumber(),column:1}):n.set(c,r.getName())}}}},Ot={meta:{id:`correctness/no-empty-handlers`,category:`correctness`,severity:`info`,description:`Controller HTTP handlers should not have empty bodies`,help:`Add implementation to the handler method or remove it if unnecessary.`},check(e){for(let t of e.sourceFile.getClasses())if(T(t))for(let n of t.getMethods()){if(!n.getDecorators().some(e=>C.has(e.getName())))continue;let t=n.getBody();if(!t)continue;let r=t.asKind(d.Block);r&&r.getStatements().length===0&&e.report({filePath:e.filePath,message:`Handler '${n.getName()}()' has an empty body.`,help:this.meta.help,line:n.getStartLineNumber(),column:1})}}};function kt(e){let t=e.getReturnType().getText();return t.startsWith(`Promise<`)||t===`Promise`?!0:t===`any`||t===`error`?`unknown`:!1}const At=new Set([`save`,`create`,`insert`,`update`,`delete`,`remove`,`send`,`emit`,`publish`,`dispatch`,`execute`,`fetch`,`load`,`upload`,`download`,`process`]),jt={meta:{id:`correctness/no-fire-and-forget-async`,category:`correctness`,severity:`warning`,description:`Calling async functions without await leads to unhandled promise rejections`,help:`Add await before the async call, or use void with explicit error handling if fire-and-forget is intentional.`},check(e){for(let t of e.sourceFile.getClasses())for(let n of t.getMethods()){if(k(n))continue;let t=n.getBody();if(!t)continue;let r=t.getDescendantsOfKind(d.ExpressionStatement);for(let t of r){let r=t.getExpression();if(r.getKind()===d.VoidExpression||r.getKind()===d.AwaitExpression||r.getKind()!==d.CallExpression)continue;let i=r.asKind(d.CallExpression);if(!i)continue;let a=i.getExpression().getText().split(`.`).pop()??``,o=kt(i);if(o!==!1){if(o===`unknown`){let e=a.toLowerCase();if(!(At.has(e)||[...At].some(t=>e.startsWith(t)&&e!==t)))continue}t.getFirstAncestorByKind(d.MethodDeclaration)===n&&e.report({filePath:e.filePath,message:`Async call '${a}()' is not awaited — unhandled rejections will crash the process.`,help:this.meta.help,line:t.getStartLineNumber(),column:1})}}}}},Mt={meta:{id:`correctness/no-missing-filter-catch`,category:`correctness`,severity:`error`,description:`Exception filter classes decorated with @Catch() must implement the catch() method`,help:`Add a catch(exception, host: ArgumentsHost) method to the filter class.`},check(e){for(let t of e.sourceFile.getClasses())w(t,`Catch`)&&(t.getExtends()||t.getMethods().some(e=>e.getName()===`catch`)||e.report({filePath:e.filePath,message:`Exception filter '${t.getName()}' has @Catch() but is missing the 'catch()' method.`,help:this.meta.help,line:t.getStartLineNumber(),column:1}))}},Nt={meta:{id:`correctness/no-missing-guard-method`,category:`correctness`,severity:`error`,description:`Guard classes must implement the canActivate() method`,help:`Add a canActivate(context: ExecutionContext) method to the guard class. Note: This rule identifies guards by the 'Guard' class name suffix.`},check(e){for(let t of e.sourceFile.getClasses()){let n=t.getName()??``;n.endsWith(`Guard`)&&w(t,`Injectable`)&&(t.getExtends()||t.getMethods().some(e=>e.getName()===`canActivate`)||e.report({filePath:e.filePath,message:`Guard '${n}' is missing the 'canActivate()' method.`,help:this.meta.help,line:t.getStartLineNumber(),column:1}))}}},Pt={meta:{id:`correctness/no-missing-injectable`,category:`correctness`,severity:`error`,description:`Provider classes with constructor dependencies must have the @Injectable() decorator`,help:`Add @Injectable() to providers that inject constructor dependencies.`,scope:`project`},check(e){let t=new Set([...e.providers.values()].map(e=>e.name)),n=new Map;for(let t of e.files){let r=e.project.getSourceFile(t);if(r)for(let e of r.getClasses()){let r=e.getName();if(r){let i=n.get(r)??[];i.push({cls:e,filePath:t}),n.set(r,i)}}}for(let r of e.moduleGraph.modules.values())for(let i of r.providers){if(t.has(i))continue;let a=n.get(i);if(a)for(let{cls:t,filePath:n}of a){let a=(t.getConstructors()[0]?.getParameters().length??0)>0;!(t.getDecorator(`Injectable`)||t.getDecorator(`Resolver`)||t.getDecorator(`WebSocketGateway`))&&a&&e.report({filePath:n,message:`Class '${i}' is listed in '${r.name}' providers but is missing @Injectable() decorator.`,help:this.meta.help,line:t.getStartLineNumber(),column:1})}}}},Ft={meta:{id:`correctness/no-missing-interceptor-method`,category:`correctness`,severity:`error`,description:`Interceptor classes must implement the intercept() method`,help:`Add an intercept(context: ExecutionContext, next: CallHandler) method to the interceptor class.`},check(e){for(let t of e.sourceFile.getClasses()){let n=t.getName()??``;n.endsWith(`Interceptor`)&&w(t,`Injectable`)&&(t.getExtends()||t.getMethods().some(e=>e.getName()===`intercept`)||e.report({filePath:e.filePath,message:`Interceptor '${n}' is missing the 'intercept()' method.`,help:this.meta.help,line:t.getStartLineNumber(),column:1}))}}},It={meta:{id:`correctness/no-missing-module-decorator`,category:`correctness`,severity:`warning`,description:`Classes named *Module should have a @Module() decorator`,help:`Add @Module({}) decorator to the class, or rename it if it is not a NestJS module.`},check(e){for(let t of e.sourceFile.getClasses()){let n=t.getName()??``;n.endsWith(`Module`)&&(n===`Module`||n===`DynamicModule`||w(t,`Module`)||e.report({filePath:e.filePath,message:`Class '${n}' is named like a module but is missing the @Module() decorator.`,help:this.meta.help,line:t.getStartLineNumber(),column:1}))}}},Lt={meta:{id:`correctness/no-missing-pipe-method`,category:`correctness`,severity:`error`,description:`Pipe classes must implement the transform() method`,help:`Add a transform(value: any, metadata: ArgumentMetadata) method to the pipe class.`},check(e){for(let t of e.sourceFile.getClasses()){let n=t.getName()??``;n.endsWith(`Pipe`)&&w(t,`Injectable`)&&(t.getExtends()||t.getMethods().some(e=>e.getName()===`transform`)||e.report({filePath:e.filePath,message:`Pipe '${n}' is missing the 'transform()' method.`,help:this.meta.help,line:t.getStartLineNumber(),column:1}))}}},Rt=/:(\w+)/g,zt={meta:{id:`correctness/param-decorator-matches-route`,category:`correctness`,severity:`error`,description:`@Param() decorator name must match a :param in the route path`,help:`Ensure the @Param('name') argument matches a ':name' segment in the route path (including controller prefix).`},check(e){for(let t of e.sourceFile.getClasses()){if(!T(t))continue;let n=t.getDecorator(`Controller`),r=``;if(n){let e=n.getArguments();if(e.length>0){let t=e[0];if(t.getKind()===d.ObjectLiteralExpression){let e=t.asKind(d.ObjectLiteralExpression);if(e){let t=e.getProperty(`path`);if(t){let e=t.asKind(d.PropertyAssignment);if(e){let t=e.getInitializer();t&&(r=t.getText().replace(/^['"`]|['"`]$/g,``))}}}}else r=t.getText().replace(/^['"`]|['"`]$/g,``)}}let i=new Set;for(let e of r.matchAll(Rt))i.add(e[1]);for(let n of t.getMethods()){let t=``,r=!1;for(let e of n.getDecorators())if(C.has(e.getName())){r=!0;let n=e.getArguments();n.length>0&&(t=n[0].getText().replace(/^['"`]|['"`]$/g,``));break}if(!r)continue;let a=new Set;for(let e of t.matchAll(Rt))a.add(e[1]);let o=new Set([...i,...a]);for(let t of n.getParameters())for(let n of t.getDecorators()){if(n.getName()!==`Param`)continue;let t=n.getArguments();if(t.length===0)continue;let r=t[0].getText().replace(/^['"`]|['"`]$/g,``);o.has(r)||e.report({filePath:e.filePath,message:`@Param('${r}') does not match any route parameter. Available: ${o.size>0?[...o].map(e=>`:${e}`).join(`, `):`(none)`}.`,help:this.meta.help,line:n.getStartLineNumber(),column:1})}}}}},Bt={meta:{id:`correctness/prefer-readonly-injection`,category:`correctness`,severity:`warning`,description:`Constructor DI parameters should be readonly to prevent accidental reassignment`,help:`Add the 'readonly' modifier to the constructor parameter.`},check(e){for(let t of e.sourceFile.getClasses()){if(!(E(t)||T(t)))continue;let n=t.getConstructors()[0];if(n){for(let t of n.getParameters())if((t.hasModifier(`private`)||t.hasModifier(`protected`)||t.hasModifier(`public`))&&!t.isReadonly()){let n=t.getNameNode();e.report({filePath:e.filePath,message:`Constructor parameter '${t.getName()}' should be readonly.`,help:this.meta.help,line:n.getStartLineNumber(),column:n.getStartLinePos()+1})}}}}},Vt={meta:{id:`correctness/require-inject-decorator`,category:`correctness`,severity:`error`,description:`Constructor parameters without type annotations must have @Inject() decorator for NestJS DI to resolve them`,help:`Add a type annotation or @Inject() decorator to the constructor parameter.`},check(e){for(let t of e.sourceFile.getClasses()){if(!D(t))continue;let n=t.getConstructors()[0];if(n)for(let r of n.getParameters()){let n=r.getTypeNode(),i=r.getDecorators().some(e=>e.getName()===`Inject`);n||i||e.report({filePath:e.filePath,message:`Constructor parameter '${r.getName()}' in '${t.getName()}' has no type annotation and no @Inject() decorator — NestJS cannot resolve it.`,help:this.meta.help,line:r.getStartLineNumber(),column:1})}}}},Ht={onModuleInit:`OnModuleInit`,onModuleDestroy:`OnModuleDestroy`,onApplicationBootstrap:`OnApplicationBootstrap`,onApplicationShutdown:`OnApplicationShutdown`,beforeApplicationShutdown:`BeforeApplicationShutdown`},Ut={meta:{id:`correctness/require-lifecycle-interface`,category:`correctness`,severity:`warning`,description:`Classes with lifecycle methods should implement the corresponding NestJS interface`,help:`Add 'implements OnModuleInit' (or the appropriate interface) to the class declaration.`},check(e){for(let t of e.sourceFile.getClasses()){let n=t.getImplements().map(e=>e.getText());for(let r of t.getMethods()){let i=r.getName(),a=Ht[i];a&&(n.some(e=>e===a||e.startsWith(`${a}<`))||e.report({filePath:e.filePath,message:`Class '${t.getName()}' has '${i}()' but does not implement '${a}'.`,help:this.meta.help,line:r.getStartLineNumber(),column:1}))}}}},Wt=/each\s*:\s*true/,Gt={meta:{id:`correctness/validate-nested-array-each`,category:`correctness`,severity:`warning`,description:`@ValidateNested() on array-typed properties must use { each: true }`,help:`Change @ValidateNested() to @ValidateNested({ each: true }) for array properties.`},check(e){for(let t of e.sourceFile.getClasses())for(let n of t.getProperties()){let t=n.getDecorators(),r=t.find(e=>e.getName()===`ValidateNested`);if(!r)continue;let i=Kt(n),a=t.some(e=>e.getName()===`IsArray`);(i||a)&&(qt(r)||e.report({filePath:e.filePath,message:`Property '${n.getName()}' is an array with @ValidateNested() but missing { each: true }.`,help:this.meta.help,line:r.getStartLineNumber(),column:1}))}}};function Kt(e){let t=e.getTypeNode();if(!t)return!1;let n=t.getText().replace(/\s/g,``);return!!(n.endsWith(`[]`)||n.startsWith(`Array<`))}function qt(e){let t=e.getArguments();if(t.length===0)return!1;let n=t[0];if(n.getKind()!==d.ObjectLiteralExpression)return!1;let r=n.getText();return Wt.test(r)}const Jt=new Set(`ValidateNested.IsString.IsNumber.IsBoolean.IsEmail.IsArray.IsEnum.IsNotEmpty.IsDefined.IsOptional.IsDate.IsObject.IsInt.IsPositive.IsNegative.IsUUID.IsUrl.IsISO8601.Matches.Min.Max.MinLength.MaxLength.ArrayMinSize.ArrayMaxSize.ArrayNotEmpty.IsIn.IsNotIn.Length.Contains.IsAlpha.IsAlphanumeric.IsDecimal.IsHexColor.IsJSON.IsPhoneNumber.IsIP.IsCreditCard.IsDateString.IsMilitaryTime.IsMongoId.IsPort.IsSemVer.IsStrongPassword`.split(`.`)),Yt=new Set([`string`,`number`,`boolean`,`Date`,`any`,`unknown`,`bigint`,`symbol`,`undefined`,`null`,`void`,`never`]),Xt=/\s/g,Zt=/\[\]$/,Qt=/^Array<(.+)>$/,$t=/^["']/,en=/^\d+$/;function I(e){let t=e.replace(Xt,``);if(t.includes(`|`))return t.split(`|`).every(e=>I(e));if(Yt.has(t)||Zt.test(t)&&I(t.replace(Zt,``)))return!0;let n=t.match(Qt);return!!(n&&I(n[1])||$t.test(t)||en.test(t))}const tn={meta:{id:`correctness/validated-non-primitive-needs-type`,category:`correctness`,severity:`warning`,description:`DTO properties with class-validator decorators on non-primitive types must have @Type() from class-transformer`,help:`Add @Type(() => ClassName) from 'class-transformer' to ensure proper transformation.`},check(e){for(let t of e.sourceFile.getClasses())for(let n of t.getProperties()){let t=n.getDecorators();if(t.length===0||!t.some(e=>Jt.has(e.getName()))||t.some(e=>e.getName()===`Type`)||t.some(e=>e.getName()===`IsEnum`))continue;let r=n.getTypeNode();if(!r)continue;let i=r.getText();I(i)||e.report({filePath:e.filePath,message:`Property '${n.getName()}' has type '${i}' with class-validator decorators but is missing @Type() decorator.`,help:this.meta.help,line:n.getStartLineNumber(),column:1})}}},nn=new Set([d.ForStatement,d.ForOfStatement,d.ForInStatement,d.WhileStatement,d.DoStatement]),rn={meta:{id:`performance/no-blocking-constructor`,category:`performance`,severity:`warning`,description:`Constructors in Injectable/Controller classes should not contain heavy operations`,help:`Move heavy initialization logic to the onModuleInit() lifecycle method. Constructors cannot be async, so asynchronous work should always use lifecycle hooks.`},check(e){for(let t of e.sourceFile.getClasses()){if(!(w(t,`Injectable`)||w(t,`Controller`)))continue;let n=t.getConstructors()[0];if(!n)continue;let r=n.getBody();if(r){for(let i of r.getDescendants())if(nn.has(i.getKind())){e.report({filePath:e.filePath,message:`Constructor in '${t.getName()}' contains blocking operation — use onModuleInit() instead.`,help:this.meta.help,line:n.getStartLineNumber(),column:1});break}}}}},an={meta:{id:`performance/no-dynamic-require`,category:`performance`,severity:`warning`,description:`Dynamic require() with variable arguments prevents bundler optimization`,help:`Use static import statements or dynamic import() with string literals.`},check(e){let t=e.sourceFile.getDescendantsOfKind(d.CallExpression);for(let n of t){if(n.getExpression().getText()!==`require`)continue;let t=n.getArguments();t.length!==0&&t[0].getKind()!==d.StringLiteral&&e.report({filePath:e.filePath,message:`Dynamic require() with non-literal argument prevents bundler optimization.`,help:this.meta.help,line:n.getStartLineNumber(),column:1})}}},on={meta:{id:`performance/no-orphan-modules`,category:`performance`,severity:`info`,description:`Module is never imported by any other module and may be dead code`,help:`Import this module in another module or remove it if it is unused.`,scope:`project`},check(e){let t=new Set;for(let n of e.moduleGraph.modules.values())for(let e of n.imports)t.add(e);for(let n of e.moduleGraph.modules.values())n.name!==`AppModule`&&(t.has(n.name)||e.report({filePath:n.filePath,message:`Module '${n.name}' is never imported by any other module.`,help:this.meta.help,line:n.classDeclaration.getStartLineNumber(),column:1}))}},sn={meta:{id:`performance/no-request-scope-abuse`,category:`performance`,severity:`warning`,description:`Scope.REQUEST creates a new provider instance per request — use only when necessary`,help:`Remove Scope.REQUEST unless the provider genuinely needs per-request state (e.g., request-scoped context). Consider Scope.DEFAULT or Scope.TRANSIENT instead.`},check(e){let t=e.sourceFile.getDescendantsOfKind(d.PropertyAccessExpression);for(let n of t)n.getName()===`REQUEST`&&n.getExpression().getText()===`Scope`&&e.report({filePath:e.filePath,message:`Scope.REQUEST creates a new instance per request, which impacts performance and propagates request scope to all dependents.`,help:this.meta.help,line:n.getStartLineNumber(),column:1})}},cn=new Set([`readFileSync`,`writeFileSync`,`existsSync`,`mkdirSync`,`readdirSync`,`statSync`,`accessSync`,`appendFileSync`,`copyFileSync`,`renameSync`,`unlinkSync`]),ln={meta:{id:`performance/no-sync-io`,category:`performance`,severity:`warning`,description:`Synchronous I/O calls block the event loop and should be avoided in NestJS applications`,help:`Use the async variant (e.g., readFile instead of readFileSync) with await.`},check(e){let t=e.sourceFile.getDescendantsOfKind(d.CallExpression);for(let n of t){let t=n.getExpression().getText().split(`.`).pop()??``;cn.has(t)&&e.report({filePath:e.filePath,message:`Synchronous I/O call '${t}()' blocks the event loop.`,help:this.meta.help,line:n.getStartLineNumber(),column:1})}}},un={meta:{id:`performance/no-unused-module-exports`,category:`performance`,severity:`info`,description:`Module exports a provider that no importing module actually uses`,help:`Remove the unused export or use the provider in an importing module.`,scope:`project`},check(e){for(let t of e.moduleGraph.modules.values()){if(t.exports.length===0)continue;let n=[];for(let r of e.moduleGraph.modules.values())r.name!==t.name&&r.imports.includes(t.name)&&n.push(r.name);if(n.length===0)continue;let r=new Set;for(let i of n){let n=e.moduleGraph.modules.get(i);if(n){for(let t of n.providers){let n=e.providers.get(t);if(n)for(let e of n.dependencies)r.add(e)}if(n.exports.includes(t.name))for(let e of t.exports)r.add(e);for(let t of n.controllers)for(let n of e.files){let i=e.project.getSourceFile(n);if(i)for(let e of i.getClasses()){if(e.getName()!==t)continue;let n=e.getConstructors()[0];if(n)for(let e of n.getParameters()){let t=e.getTypeNode(),n=t?t.getText():e.getType().getText(),i=n.split(`.`).pop()?.split(`<`)[0]??n;r.add(i)}}}}}for(let n of t.exports)e.moduleGraph.modules.has(n)||r.has(n)||e.report({filePath:t.filePath,message:`Module '${t.name}' exports '${n}' but no importing module uses it.`,help:this.meta.help,line:t.classDeclaration.getStartLineNumber(),column:1})}}},dn=new Set([`Cron`,`Interval`,`Timeout`,`OnEvent`,`Process`,`OnQueueEvent`,`EventSubscriber`,`SubscribeMessage`,`WebSocketGateway`]);function fn(e){for(let t of e.getDecorators())if(dn.has(t.getName()))return!0;for(let t of e.getMethods())for(let e of t.getDecorators())if(dn.has(e.getName()))return!0;return!1}const pn={meta:{id:`performance/no-unused-providers`,category:`performance`,severity:`warning`,description:`Injectable providers that are never injected and have no self-activating decorators may be dead code`,help:`Remove the unused provider, inject it where needed, or verify it is activated by a framework decorator (e.g. @Cron, @OnEvent).`,scope:`project`},check(e){let t=new Set;for(let n of e.providers.values())for(let e of n.dependencies)t.add(e);let n=[`Controller`,`Resolver`,`WebSocketGateway`];for(let r of e.files){let i=e.project.getSourceFile(r);if(i)for(let e of i.getClasses()){if(!n.some(t=>e.getDecorator(t)!==void 0))continue;let r=e.getConstructors()[0];if(r)for(let e of r.getParameters()){let n=e.getTypeNode(),r=n?n.getText():e.getType().getText(),i=r.split(`.`).pop()?.split(`<`)[0]??r;t.add(i)}}}for(let n of e.providers.values()){let r=n.name;if(yt.some(e=>r.endsWith(e))||t.has(r)||fn(n.classDeclaration))continue;let i=!1;for(let t of e.moduleGraph.modules.values())if(t.exports.includes(r)){i=!0;break}i||e.report({filePath:n.filePath,message:`Provider '${r}' is never injected by any other provider or controller.`,help:this.meta.help,line:n.classDeclaration.getStartLineNumber(),column:1})}}},mn={meta:{id:`schema/require-cascade-rule`,category:`schema`,scope:`schema`,severity:`info`,description:`Relations should have explicit onDelete/cascade behavior defined`,help:`Add an explicit onDelete option (e.g. CASCADE, SET NULL) to avoid relying on database defaults.`},check(e){for(let t of e.schemaGraph.relations)if(!(t.type!==`many-to-one`&&t.type!==`one-to-one`)&&!t.onDelete){let n=e.schemaGraph.entities.get(t.fromEntity);if(!n)continue;e.report({filePath:n.filePath,entity:n.name,message:`Relation '${t.propertyName}' on '${t.fromEntity}' has no explicit onDelete behavior.`,help:this.meta.help})}}},hn={meta:{id:`schema/require-primary-key`,category:`schema`,scope:`schema`,severity:`error`,description:`Every entity must have at least one primary key column`,help:`Add a primary key column (e.g. @id in Prisma, @PrimaryColumn/@PrimaryGeneratedColumn in TypeORM).`},check(e){for(let t of e.schemaGraph.entities.values())t.columns.some(e=>e.isPrimary)||e.report({filePath:t.filePath,entity:t.name,message:`Entity '${t.name}' has no primary key column.`,help:this.meta.help})}},gn=/delete/i;function _n(e,t){let n=new Set(e.columns.map(e=>e.name.toLowerCase()));return n.has(`createdat`)||n.has(`created_at`)?!0:t===`typeorm`?e.columns.some(e=>e.type===`timestamp`&&e.isGenerated&&!gn.test(e.name)):t===`prisma`?e.columns.some(e=>e.type===`DateTime`&&e.defaultValue!==void 0&&e.defaultValue.includes(`now()`)):t===`drizzle`?e.columns.some(e=>(e.type===`timestamp`||e.type===`date`||e.type===`datetime`)&&e.defaultValue!==void 0&&e.defaultValue.includes(`now()`)):!1}const vn={meta:{id:`schema/require-timestamps`,category:`schema`,scope:`schema`,severity:`warning`,description:`Entities should have timestamp columns (createdAt/updatedAt)`,help:`Add createdAt/updatedAt columns to track when records are created and modified.`},check(e){for(let t of e.schemaGraph.entities.values())_n(t,e.orm)||e.report({filePath:t.filePath,entity:t.name,message:`Entity '${t.name}' has no timestamp columns (createdAt/updatedAt).`,help:this.meta.help})}},yn={meta:{id:`security/no-csrf-disabled`,category:`security`,severity:`error`,description:`CSRF protection should not be explicitly disabled`,help:`Enable CSRF protection or remove the explicit disabling of it.`},check(e){let t=e.sourceFile.getDescendantsOfKind(d.PropertyAssignment);for(let n of t){let t=n.getName();if(t!==`csrf`&&t!==`csrfProtection`)continue;let r=n.getInitializer();r&&r.getText()===`false`&&e.report({filePath:e.filePath,message:`CSRF protection explicitly disabled (${t}: false).`,help:this.meta.help,line:n.getStartLineNumber(),column:1})}}},bn={meta:{id:`security/no-dangerous-redirects`,category:`security`,severity:`error`,description:`Redirects using user-controlled input (from @Query/@Param) are an open redirect vulnerability`,help:`Validate redirect URLs against an allowlist of safe destinations.`},check(e){for(let t of e.sourceFile.getClasses())if(T(t))for(let n of t.getMethods()){let t=new Set;for(let e of n.getParameters())e.getDecorators().some(e=>e.getName()===`Query`||e.getName()===`Param`)&&t.add(e.getName());if(t.size===0)continue;let r=n.getDescendantsOfKind(d.CallExpression);for(let n of r)if(n.getExpression().getText().endsWith(`redirect`))for(let r of n.getArguments()){let i=r.getText();t.has(i)&&e.report({filePath:e.filePath,message:`Redirect uses user-controlled parameter '${i}' — open redirect risk.`,help:this.meta.help,line:n.getStartLineNumber(),column:1})}let i=n.getDecorators().find(e=>e.getName()===`Redirect`);if(i)for(let n of i.getArguments()){let r=n.getText();t.has(r)&&e.report({filePath:e.filePath,message:`@Redirect() uses user-controlled parameter '${r}' — open redirect risk.`,help:this.meta.help,line:i.getStartLineNumber(),column:1})}}}},xn={meta:{id:`security/no-eval`,category:`security`,severity:`error`,description:`Usage of eval() or new Function() is a security risk and should be avoided`,help:`Refactor to avoid eval() and new Function(). Use safer alternatives like JSON.parse() or a sandboxed interpreter.`},check(e){let t=e.sourceFile.getDescendantsOfKind(d.CallExpression);for(let n of t)n.getExpression().getText()===`eval`&&e.report({filePath:e.filePath,message:`Usage of eval() is a security risk.`,help:this.meta.help,line:n.getStartLineNumber(),column:1});let n=e.sourceFile.getDescendantsOfKind(d.NewExpression);for(let t of n)t.getExpression().getText()===`Function`&&e.report({filePath:e.filePath,message:`Usage of new Function() is a security risk.`,help:this.meta.help,line:t.getStartLineNumber(),column:1})}},Sn={meta:{id:`security/no-exposed-env-vars`,category:`security`,severity:`warning`,description:`Use NestJS ConfigService instead of direct process.env access in Injectable/Controller classes`,help:`Inject ConfigService and use configService.get('VAR_NAME') instead of process.env.VAR_NAME.`},check(e){for(let t of e.sourceFile.getClasses()){if(!(w(t,`Injectable`)||w(t,`Controller`)))continue;let n=t.getDescendantsOfKind(d.PropertyAccessExpression);for(let r of n)r.getExpression().getText()===`process.env`&&e.report({filePath:e.filePath,message:`Direct 'process.env.${r.getName()}' access in '${t.getName()}'. Use ConfigService instead.`,help:this.meta.help,line:r.getStartLineNumber(),column:1})}}},Cn=/^(error|err|e|ex|exception)$/,wn={meta:{id:`security/no-exposed-stack-trace`,category:`security`,severity:`warning`,description:`Stack traces should not be exposed in responses — they leak internal implementation details`,help:`Log the stack trace internally and return a generic error message to the client.`},check(e){let t=e.sourceFile.getDescendantsOfKind(d.PropertyAccessExpression);for(let n of t){if(n.getName()!==`stack`)continue;let t=n.getExpression().getText();if(!(Cn.test(t)||t.endsWith(`.error`)||t.endsWith(`.err`)))continue;let r=n.getParent();if(!r)continue;let i=r.getKind();(i===d.ReturnStatement||i===d.PropertyAssignment||i===d.ShorthandPropertyAssignment||i===d.CallExpression)&&e.report({filePath:e.filePath,message:`Stack trace '${t}.stack' may be exposed in response — leaks implementation details.`,help:this.meta.help,line:n.getStartLineNumber(),column:1})}}},Tn=[{pattern:/^(?=.*\d)[A-Za-z0-9+/]{40,}={0,2}$/,name:`Base64 key`},{pattern:/^sk[-_][a-zA-Z0-9]{20,}$/,name:`Secret key`},{pattern:/^pk[-_][a-zA-Z0-9]{20,}$/,name:`Public key (in source)`},{pattern:/^ghp_[a-zA-Z0-9]{36,}$/,name:`GitHub personal access token`},{pattern:/^github_pat_[a-zA-Z0-9_]{22,}$/,name:`GitHub fine-grained PAT`},{pattern:/^gho_[a-zA-Z0-9]{36,}$/,name:`GitHub OAuth token`},{pattern:/^xox[bpras]-[a-zA-Z0-9-]+$/,name:`Slack token`},{pattern:/^eyJ[a-zA-Z0-9_-]{10,}\.[a-zA-Z0-9_-]{10,}\./,name:`JWT token`},{pattern:/^AKIA[0-9A-Z]{16}$/,name:`AWS Access Key ID`},{pattern:/^[a-f0-9]{64}$/,name:`Hex-encoded secret (64 chars)`}],En=[/secret/i,/password/i,/passwd/i,/api[_-]?key/i,/auth[_-]?token/i,/private[_-]?key/i,/access[_-]?key/i,/client[_-]?secret/i],Dn=new Set([`your-secret-here`,`changeme`,`password`]),On=/^[A-Za-z][A-Za-z0-9_]*(\.[A-Za-z][A-Za-z0-9_]*)+$/,kn=new Set([`cursor`,`nextCursor`,`prevCursor`,`previousCursor`,`startCursor`,`endCursor`,`pageToken`,`nextPageToken`,`continuationToken`,`continuation`,`nextPage`,`afterCursor`,`beforeCursor`]);function An(e){return!(e.length<8||e.includes("${")||e.startsWith(`process.env`)||Dn.has(e)||e.includes(` `)||On.test(e))}function jn(e){return En.some(t=>t.test(e))}function Mn(e){try{let t=Buffer.from(e,`base64`).toString(`utf-8`);return JSON.parse(t),!0}catch{return!1}}function Nn(e){let t=new Map;for(let n of e)t.set(n,(t.get(n)??0)+1);let n=0;for(let r of t.values()){let t=r/e.length;n-=t*Math.log2(t)}return n}const Pn=new Set([...`aeiouyAEIOUY`]),Fn=/^[A-Z]{2,4}_/,In=/(?<=[a-z])(?=[A-Z])|(?<=[A-Za-z])(?=\d)|(?<=\d)(?=[A-Za-z])|_/,Ln=/[a-zA-Z]/;function Rn(e){let t=e.includes(`_`),n=e.split(In).filter(e=>e.length>0).filter(e=>Ln.test(e)),r=n.filter(e=>e.length>=4&&[...e].some(e=>Pn.has(e)));return n.slice(0,6).filter(e=>e.length>=4&&[...e].some(e=>Pn.has(e))).length>=2||t&&e.split(`_`).filter(e=>e.length>=3).length>=2||Fn.test(e)?!0:(Nn(e)>4.9&&!t&&r.length,!1)}function zn(e){let t=e.getParent();if(!t)return!1;let n=t.asKind(d.PropertyAssignment);if(n)return kn.has(n.getName());let r=t.asKind(d.VariableDeclaration);return r?kn.has(r.getName()):!1}const Bn={meta:{id:`security/no-hardcoded-secrets`,category:`security`,severity:`error`,description:`Detect hardcoded secrets, API keys, and tokens in source code`,help:`Move secrets to environment variables and access them via ConfigService.`},check(e){let t=e.sourceFile.getDescendantsOfKind(d.StringLiteral);for(let n of t){let t=n.getLiteralValue();if(!(t.length<16)&&n.getParent()?.getKind()!==d.ImportDeclaration){for(let{pattern:r,name:i}of Tn)if(r.test(t)){if(i===`Base64 key`&&(Mn(t)||zn(n)||Rn(t)))break;e.report({filePath:e.filePath,message:`Possible hardcoded ${i} detected.`,help:this.meta.help,line:n.getStartLineNumber(),column:1});break}}}let n=e.sourceFile.getDescendantsOfKind(d.VariableDeclaration);for(let t of n){let n=t.getName(),r=t.getInitializer();!r||r.getKind()!==d.StringLiteral||jn(n)&&An(r.getText().slice(1,-1))&&e.report({filePath:e.filePath,message:`Variable '${n}' appears to contain a hardcoded secret.`,help:this.meta.help,line:t.getStartLineNumber(),column:1})}let r=e.sourceFile.getDescendantsOfKind(d.PropertyAssignment);for(let t of r){let n=t.getName(),r=t.getInitializer();!r||r.getKind()!==d.StringLiteral||jn(n)&&An(r.getText().slice(1,-1))&&e.report({filePath:e.filePath,message:`Property '${n}' appears to contain a hardcoded secret.`,help:this.meta.help,line:t.getStartLineNumber(),column:1})}}},Vn=RegExp(`(?:^|[^a-zA-Z])\\w*(?:${[`Entity`,`Model`].join(`|`)})(?:[^a-zA-Z]|$)`),Hn={meta:{id:`security/no-raw-entity-in-response`,category:`security`,severity:`warning`,description:`Returning ORM entities directly from controllers can leak internal fields like passwords or IDs`,help:`Map entities to DTOs or use class-transformer's @Exclude()/@Expose() decorators before returning.`},check(e){for(let t of e.sourceFile.getClasses())if(T(t))for(let n of t.getMethods()){if(!k(n))continue;let t=n.getReturnType().getText();Vn.test(t)&&!t.includes(`DTO`)&&!t.includes(`Dto`)&&!t.includes(`Response`)&&e.report({filePath:e.filePath,message:`Controller method '${n.getName()}' returns a raw entity type. This may leak internal fields.`,help:this.meta.help,line:n.getStartLineNumber(),column:1})}}},Un={meta:{id:`security/no-synchronize-in-production`,category:`security`,severity:`error`,description:`TypeORM synchronize: true auto-syncs schema and can drop columns or tables in production`,help:`Set synchronize: false and use migrations for production schema changes.`},check(e){let t=e.sourceFile.getDescendantsOfKind(d.PropertyAssignment);for(let n of t){if(n.getName()!==`synchronize`)continue;let t=n.getInitializer();t&&t.getText()===`true`&&e.report({filePath:e.filePath,message:`TypeORM 'synchronize: true' can auto-drop columns and tables in production.`,help:this.meta.help,line:n.getStartLineNumber(),column:1})}}},Wn=new Set([`md5`,`sha1`]),Gn={meta:{id:`security/no-weak-crypto`,category:`security`,severity:`warning`,description:`Weak hashing algorithms (MD5, SHA1) should not be used for security purposes`,help:`Use a stronger algorithm like SHA-256 or bcrypt for password hashing.`},check(e){let t=e.sourceFile.getDescendantsOfKind(d.CallExpression);for(let n of t){if(!n.getExpression().getText().endsWith(`createHash`))continue;let t=n.getArguments();if(t.length===0)continue;let r=t[0];if(r.getKind()!==d.StringLiteral)continue;let i=r.getText().slice(1,-1).toLowerCase();Wn.has(i)&&e.report({filePath:e.filePath,message:`Weak hashing algorithm '${i}' used in createHash().`,help:this.meta.help,line:n.getStartLineNumber(),column:1})}}},Kn=new Set([`Public`,`AllowAnonymous`,`SkipAuth`,`IsPublic`]),qn=[Ae,ft,nt,st,Qe,mt,ht,_t,De,qe,Bt,Ut,Ot,Dt,Nt,Lt,Mt,Ft,St,Et,It,Vt,jt,zt,vt,tn,wt,Gt,Pt,bt,Bn,xn,Gn,Sn,yn,wn,bn,Un,Hn,{meta:{id:`security/require-guards-on-endpoints`,category:`security`,severity:`warning`,description:`Controller endpoints should be protected by @UseGuards() at class or method level`,help:`Add @UseGuards(AuthGuard) to the controller class or individual route handlers, or mark routes as @Public(). If you use a global guard via APP_GUARD, you can disable this rule.`},check(e){for(let t of e.sourceFile.getClasses())if(T(t)&&t.getDecorator(`UseGuards`)===void 0&&!t.getDecorators().some(e=>Kn.has(e.getName())))for(let n of t.getMethods())k(n)&&n.getDecorator(`UseGuards`)===void 0&&(n.getDecorators().some(e=>Kn.has(e.getName()))||e.report({filePath:e.filePath,message:`Endpoint '${n.getName()}' has no @UseGuards() at class or method level.`,help:this.meta.help,line:n.getStartLineNumber(),column:1}))}},ln,rn,an,sn,pn,un,on,hn,vn,mn];function Jn(e){return e.meta.scope===`project`}function Yn(e){return e.meta.scope===`schema`}function Xn(e,t,n){if(t.length===0)return e;let r=new Set(e.map(e=>e.meta.id)),i=[...e];for(let e of t){if(r.has(e.meta.id)){n.push(`Custom rule "${e.meta.id}" conflicts with a built-in rule and was skipped`);continue}i.push(e)}return i}function Zn(e,t){return t.filter(t=>{let n=e.rules?.[t.meta.id];return!(n===!1||typeof n==`object`&&n.enabled===!1||e.categories?.[t.meta.category]===!1)})}function Qn(e){let t=[],n=[],r=[];for(let i of e)Yn(i)?r.push(i):Jn(i)?n.push(i):t.push(i);return{fileRules:t,projectRules:n,schemaRules:r}}const $n=new Set([`security`,`performance`,`correctness`,`architecture`]),er=new Set([`error`,`warning`,`info`]),tr=new Set([`file`,`project`]),nr=`custom/`;function rr(e){if(typeof e!=`object`||!e)return!1;let t=e;if(typeof t.check!=`function`||typeof t.meta!=`object`||t.meta===null)return!1;let n=t.meta;return!(typeof n.id!=`string`||n.id.trim()===``||typeof n.description!=`string`||typeof n.help!=`string`||!$n.has(n.category)||!er.has(n.severity)||n.scope!==void 0&&!tr.has(n.scope))}function ir(e){return e.meta.id.startsWith(nr)?e:{...e,meta:{...e.meta,id:`${nr}${e.meta.id}`}}}async function ar(e,t){let n=[],r=[],i=s(t,e);if(!p(i))return r.push(`Custom rules directory not found: ${i}`),{rules:n,warnings:r};if(!g(i).isDirectory())return r.push(`Custom rules path is not a directory: ${i}`),{rules:n,warnings:r};let a;try{a=h(i)}catch(e){return r.push(`Failed to read custom rules directory: ${e instanceof Error?e.message:String(e)}`),{rules:n,warnings:r}}let o=a.filter(e=>e.endsWith(`.ts`));if(o.length===0)return r.push(`No rule files (.ts) found in: ${i}`),{rules:n,warnings:r};let c=_(i,{interopDefault:!0});for(let e of o){let t=s(i,e),a;try{a=await c.import(t)}catch(t){r.push(`Failed to load custom rule file "${e}": ${t instanceof Error?t.message:String(t)}`);continue}let o=!1;for(let[t,i]of Object.entries(a))rr(i)?(n.push(ir(i)),o=!0):t!==`__esModule`&&typeof i==`object`&&i&&`meta`in i&&r.push(`Invalid rule export "${t}" in "${e}": missing or invalid required fields (check, meta.id, meta.description, meta.help, meta.category, meta.severity)`);!o&&Object.keys(a).length>0&&(Object.values(a).some(e=>typeof e==`object`&&!!e&&(`meta`in e||`check`in e))||r.push(`No valid rule exports found in "${e}"`))}return{rules:n,warnings:r}}function or(e,t){return e.customRulesDir?ar(e.customRulesDir,t):Promise.resolve({rules:[],warnings:[]})}async function sr(e,t){let n=await Se(e,t),{rules:r,warnings:i}=await or(n,e),a=Xn(qn,r,i),{fileRules:o,projectRules:s,schemaRules:c}=Qn(Zn(n,a));return{combinedRules:a,config:n,customRuleWarnings:i,fileRules:o,projectRules:s,schemaRules:c}}async function cr(e,t={}){return(await c(t.include??S.include,{cwd:e,absolute:!0,ignore:t.exclude??S.exclude})).sort()}async function lr(e,t,n={}){let r=await Promise.all([...t.projects.entries()].map(async([t,r])=>[t,await cr(a(e,r),n)])),i=new Map;for(let[e,t]of r)i.set(e,t);return i}function ur(e){let t=new u({compilerOptions:{strict:!0,target:99,module:99,skipFileDependencyResolution:!0},skipAddingFilesFromTsConfig:!0});for(let n of e)t.addSourceFileAtPath(n);return t}const dr=/import\([^)]+\)\.(\w+)/,fr=/^(\w+)</;function pr(e,t){let n=[];for(let r of e.getClasses()){if(!r.getDecorator(`Injectable`))continue;let e=r.getName();if(!e)continue;let i=r.getConstructors()[0],a=i?i.getParameters().map(e=>{let t=e.getTypeNode();return hr(t?t.getText():e.getType().getText())}):[],o=r.getMethods().filter(e=>{let t=e.getScope();return!t||t===`public`}).length;n.push({name:e,filePath:t,classDeclaration:r,dependencies:a,publicMethodCount:o})}return n}function mr(e,t){let n=new Map;for(let r of t){let t=e.getSourceFile(r);if(t)for(let e of pr(t,r))n.set(e.name,e)}return n}function hr(e){let t=e.match(dr);if(t)return t[1];let n=e.match(fr);return n?n[1]:e}const L=/^['"`]|['"`]$/g,gr=/\/+/g,_r=/\/$/;function vr(e,t){let n=e;for(;n&&n!==t;){let e=n.getParent();if(!e||e===t)break;let r=e.getKind();if(r===d.IfStatement){let t=e.asKindOrThrow(d.IfStatement);if(n===t.getThenStatement()||n===t.getElseStatement())return!0}if(r===d.ConditionalExpression){let t=e.asKindOrThrow(d.ConditionalExpression);if(n===t.getWhenTrue()||n===t.getWhenFalse())return!0}let i=n.getKind();if(i===d.CaseClause||i===d.DefaultClause||i===d.CatchClause)return!0;n=e}return!1}function yr(e){let t=e.getDecorator(`Controller`);if(!t)return``;let n=t.getArguments();if(n.length===0)return``;let r=n[0];if(r.getKind()===d.ObjectLiteralExpression){let e=r.asKindOrThrow(d.ObjectLiteralExpression).getProperty(`path`);if(!e)return``;let t=e.asKind(d.PropertyAssignment);if(!t)return``;let n=t.getInitializer();return n?n.getText().replace(L,``):``}return r.getText().replace(L,``)}function br(e){for(let t of e.getDecorators()){let e=t.getName();if(!C.has(e))continue;let n=t.getArguments(),r=n.length>0?n[0].getText().replace(L,``):``;return{httpMethod:e.toUpperCase(),path:r}}}function xr(e,t){return`/${[e,t].filter(Boolean).join(`/`)}`.replace(gr,`/`).replace(_r,``)||`/`}function Sr(e){let t=new Map,n=e.getConstructors()[0];if(!n)return t;for(let e of n.getParameters()){let n=e.getName(),r=e.getTypeNode(),i=r?r.getText():e.getType().getText();t.set(n,hr(i))}return t}function Cr(e,t){let n=e.getBody();if(!n)return[];let r=new Map,i=0,a=n.getDescendantsOfKind(d.CallExpression);for(let e of a){let a=e.getExpression();if(a.getKind()!==d.PropertyAccessExpression)continue;let o=a.asKindOrThrow(d.PropertyAccessExpression),s=o.getName(),c=o.getExpression();if(c.getKind()!==d.PropertyAccessExpression)continue;let l=c.asKindOrThrow(d.PropertyAccessExpression);if(l.getExpression().getKind()!==d.ThisKeyword)continue;let u=l.getName();if(!t.has(u))continue;r.has(u)||r.set(u,new Map);let f=r.get(u),p=vr(e,n),m=f.get(s);m?m.isUnconditional=m.isUnconditional||!p:f.set(s,{isUnconditional:!p,order:i++})}let o=[];for(let[e,n]of r){let r=[];for(let[e,t]of n)r.push({name:e,conditional:!t.isUnconditional,order:t.order});r.sort((e,t)=>e.order-t.order),o.push({className:t.get(e),methodsCalled:r})}return o}function wr(e){return e.endsWith(`Repository`)?`repository`:e.endsWith(`Guard`)?`guard`:e.endsWith(`Interceptor`)?`interceptor`:e.endsWith(`Pipe`)?`pipe`:e.endsWith(`Filter`)?`filter`:e.endsWith(`Gateway`)?`gateway`:`service`}function R(e,t,n){let r=[],i=new Set,a=[],o=[];for(let t of e)if(t.methodsCalled.length===0)o.push(t);else for(let e of t.methodsCalled)a.push({className:t.className,mc:e,dep:t});a.sort((e,t)=>e.mc.order-t.mc.order);for(let e of o){if(n.has(e.className)||i.has(e.className))continue;i.add(e.className),n.add(e.className);let a=t.get(e.className),o=[];a&&(o=a.dependencies.map(e=>({className:e,methodsCalled:[]}))),r.push({className:e.className,conditional:!1,dependencies:R(o,t,new Set(n)),filePath:a?.filePath??``,line:0,methodName:null,order:0,totalMethods:a?.publicMethodCount??0,type:wr(e.className)})}let s=new Map;for(let{className:e,dep:t}of a)s.has(e)||s.set(e,t.methodsCalled);for(let{className:e,mc:o}of a){if(n.has(e))continue;let a=t.get(e),c=!i.has(e);c&&i.add(e);let l=[];if(c&&a){let r=new Set(n);r.add(e);let i=Sr(a.classDeclaration),o=new Map,c=0,u=s.get(e)??[];for(let e of u){let t=a.classDeclaration.getInstanceMethod(e.name);if(t)for(let e of Cr(t,i)){o.has(e.className)||o.set(e.className,new Map);let t=o.get(e.className);for(let n of e.methodsCalled){let e=t.get(n.name);e?e.isUnconditional=e.isUnconditional||!n.conditional:t.set(n.name,{isUnconditional:!n.conditional,order:c++})}}}l=R([...o.entries()].map(([e,t])=>({className:e,methodsCalled:[...t.entries()].map(([e,t])=>({name:e,conditional:!t.isUnconditional,order:t.order})).sort((e,t)=>e.order-t.order)})),t,r)}let u=0;if(a){let e=a.classDeclaration.getInstanceMethod(o.name);e&&(u=e.getStartLineNumber())}r.push({className:e,conditional:o.conditional,dependencies:l,filePath:a?.filePath??``,line:u,methodName:o.name,order:o.order,totalMethods:a?.publicMethodCount??0,type:wr(e)})}return r}function Tr(e,t,n){let r=[];for(let i of e.getClasses()){if(!T(i))continue;let e=yr(i),a=i.getName()??`AnonymousController`,o=Sr(i);for(let s of i.getMethods()){let i=br(s);if(!i)continue;let c=xr(e,i.path),l=R(Cr(s,o),n,new Set);r.push({controllerClass:a,dependencies:l,filePath:t,handlerMethod:s.getName(),httpMethod:i.httpMethod,line:s.getStartLineNumber(),routePath:c})}}return r}function Er(e,t,n){let r=[];for(let i of t){let t=e.getSourceFile(i);t&&r.push(...Tr(t,i,n))}return{endpoints:r}}const Dr=new Set([`pgTable`,`mysqlTable`,`sqliteTable`]),Or=new Set([`serial`,`bigserial`,`smallserial`]),kr=/=>\s*(\w+)/;function Ar(e){let t={type:`unknown`,isPrimary:!1,isNullable:!0,isGenerated:!1,isUnique:!1};function n(e){if(e.getKind()===d.CallExpression){let r=e.asKindOrThrow(d.CallExpression),i=r.getExpression();if(i.getKind()===d.PropertyAccessExpression){let e=i.asKindOrThrow(d.PropertyAccessExpression);switch(e.getName()){case`primaryKey`:t.isPrimary=!0;break;case`notNull`:t.isNullable=!1;break;case`unique`:t.isUnique=!0;break;case`default`:{let e=r.getArguments();e.length>0&&(t.defaultValue=e[0].getText().replace(/['"]/g,``));break}case`defaultNow`:t.defaultValue=`now()`;break;case`generatedAlwaysAsIdentity`:case`autoincrement`:t.isGenerated=!0;break;case`references`:{let e=r.getArguments();if(e.length>0){let n=e[0].getText(),r=kr.exec(n);if(r&&(t.reference={toEntity:r[1]}),e.length>1){let n=e[1];if(n.getKind()===d.ObjectLiteralExpression){let e=n.asKindOrThrow(d.ObjectLiteralExpression);for(let n of e.getProperties())if(n.getKind()===d.PropertyAssignment){let e=n.asKindOrThrow(d.PropertyAssignment);if(e.getName()===`onDelete`){let n=e.getInitializer()?.getText();n&&(t.reference.onDelete=n.replace(/['"]/g,``))}}}}}break}default:break}n(e.getExpression())}else if(i.getKind()===d.Identifier){let e=i.getText();t.type=e,Or.has(e)&&(t.isGenerated=!0)}}}return n(e),t}function jr(e){let t=[];for(let n of e.getProperties()){if(n.getKind()!==d.PropertyAssignment)continue;let e=n.asKindOrThrow(d.PropertyAssignment),r=e.getName(),i=e.getInitializer();if(!i)continue;let a=Ar(i);t.push({name:r,type:a.type,isPrimary:a.isPrimary,isNullable:a.isNullable,isGenerated:a.isGenerated,isUnique:a.isUnique,defaultValue:a.defaultValue})}return t}function Mr(e,t){let n=[];for(let r of e.getProperties()){if(r.getKind()!==d.PropertyAssignment)continue;let e=r.asKindOrThrow(d.PropertyAssignment),i=e.getName(),a=e.getInitializer();if(!a)continue;let o=Ar(a);o.reference&&n.push({type:`many-to-one`,fromEntity:t,toEntity:o.reference.toEntity,propertyName:i,isNullable:o.isNullable,...o.reference.onDelete?{onDelete:o.reference.onDelete}:{}})}return n}function Nr(e){let t=[],n=e.getDescendantsOfKind(d.CallExpression);for(let e of n){let n=e.getExpression();if(n.getKind()!==d.PropertyAccessExpression||n.asKindOrThrow(d.PropertyAccessExpression).getName()!==`on`)continue;let r=[];for(let t of e.getArguments())if(t.getKind()===d.PropertyAccessExpression){let e=t.asKindOrThrow(d.PropertyAccessExpression);r.push(e.getName())}if(r.length===0)continue;let i=e.getText().includes(`uniqueIndex`);t.push({columns:r,isUnique:i})}return t}function Pr(e){let t=[],n=e.getFilePath();for(let r of e.getDescendantsOfKind(d.VariableDeclaration)){let e=r.getInitializer();if(!e||e.getKind()!==d.CallExpression)continue;let i=e.asKindOrThrow(d.CallExpression),a=i.getExpression();if(a.getKind()!==d.Identifier)continue;let o=a.getText();if(!Dr.has(o))continue;let s=i.getArguments();if(s.length<2)continue;let c=s[0],l=r.getName();c.getKind()===d.StringLiteral&&(l=c.asKindOrThrow(d.StringLiteral).getLiteralValue());let u=s[1];if(u.getKind()!==d.ObjectLiteralExpression)continue;let f=u.asKindOrThrow(d.ObjectLiteralExpression),p=r.getName(),m=jr(f),h=Mr(f,p),g;if(s.length>=3&&(g=Nr(s[2]),g))for(let e of g)for(let t of e.columns){let e=m.find(e=>e.name===t);e&&(e.hasIndex=!0)}t.push({name:p,tableName:l,filePath:n,columns:m,relations:h,indexes:g})}return t}const Fr={supportsIncrementalUpdate:!0,extract(e,t){let n=[];for(let r of t){let t=e.getSourceFile(r);t&&n.push(...Pr(t))}return n}},Ir=/^model\s+(\w+)\s*\{/,Lr=/^enum\s+(\w+)\s*\{/,Rr=/^(\w+)\s+(\w+)(\?)?(\[\])?(.*)$/,zr=/@(\w+)(\((?:[^()]*|\([^()]*\))*\))?/g,Br=/@default\(((?:[^()]*|\([^()]*\))*)\)/,Vr=/^@@map\(\s*"([^"]+)"\s*\)/;function Hr(e){let t=a(e,`prisma`,`schema.prisma`);if(p(t)){let n=a(e,`prisma`),r=h(n).filter(e=>e.endsWith(`.prisma`));return r.length>1?r.map(e=>a(n,e)):[t]}let n=a(e,`schema.prisma`);if(p(n))return[n];try{let t=a(e,`package.json`),n=JSON.parse(m(t,`utf-8`)).prisma?.schema;if(n){let t=a(e,n);if(p(t))return[t]}}catch{}return[]}function Ur(e){let t=[],n=new Set;for(let r of e){let e;try{e=m(r,`utf-8`)}catch{continue}let i=e.split(`
|
|
5
|
+
`),a=null,o=[],s=[],c=[],l;for(let e of i){let i=e.trim(),u=Ir.exec(i);if(u){a={type:`model`,name:u[1]},o=[],s=[],c=[],l=void 0;continue}let d=Lr.exec(i);if(d){a={type:`enum`,name:d[1]},n.add(d[1]);continue}if(i===`}`){a?.type===`model`&&t.push({name:a.name,fields:o,indexes:s,compositeIdColumns:c,filePath:r,tableName:l}),a=null,o=[],s=[],c=[],l=void 0;continue}if(a?.type===`model`&&i&&!i.startsWith(`//`)){if(i.startsWith(`@@`)){let e=Gr.exec(i);e&&(c=e[1].split(`,`).map(e=>e.trim()));let t=Kr(i);t&&s.push(t);let n=Vr.exec(i);n&&(l=n[1]);continue}let e=qr(i);e&&o.push(e)}}}return{models:t,enums:n}}const Wr=/^@@(index|unique)\(\[([^\]]*)\]\)/,Gr=/^@@id\(\[([^\]]*)\]\)/;function Kr(e){let t=Wr.exec(e);if(!t)return null;let n=t[1]===`unique`,r=t[2].split(`,`).map(e=>e.trim()).filter(Boolean);return r.length===0?null:{columns:r,isUnique:n}}function qr(e){let t=Rr.exec(e);if(!t)return null;let n=t[1],r=t[2],i=t[3]===`?`,a=t[4]===`[]`,o=t[5]??``,s=[],c=new RegExp(zr.source,zr.flags),l=c.exec(o);for(;l!==null;)s.push(`@${l[1]}${l[2]??``}`),l=c.exec(o);return{name:n,type:r,isOptional:i,isList:a,attributes:s}}function Jr(e){let t=e.attributes.some(e=>e.startsWith(`@id`)),n=e.attributes.some(e=>e.startsWith(`@unique`)),r=e.attributes.find(e=>e.startsWith(`@default(`)),i=!1,a;if(r){let e=Br.exec(r);if(e){let t=e[1];a=t,(t===`autoincrement()`||t===`uuid()`||t===`cuid()`||t===`dbgenerated()`)&&(i=!0)}}return{name:e.name,type:e.type,isPrimary:t,isNullable:e.isOptional,isGenerated:i,isUnique:n,defaultValue:a}}const Yr=/onDelete:\s*(\w+)/;function Xr(e){let t=e.attributes.find(e=>e.startsWith(`@relation`));if(!t)return;let n=Yr.exec(t);return n?n[1]:void 0}function Zr(e,t){let n=new Set(e.map(e=>e.name));return e.map(r=>{let i=[],a=[],o=new Set;for(let e of r.indexes)for(let t of e.columns)o.add(t);let s=new Set(r.compositeIdColumns);for(let c of r.fields)if(n.has(c.type)&&!t.has(c.type)){let t;t=c.isList?`one-to-many`:`many-to-one`;let n=c.isOptional;c.isList&&e.find(e=>e.name===c.type)?.fields.find(e=>e!==c&&e.type===r.name&&e.isList)&&(t=`many-to-many`);let i=Xr(c);a.push({type:t,fromEntity:r.name,toEntity:c.type,propertyName:c.name,isNullable:n??!1,...i?{onDelete:i}:{}})}else if(!c.attributes.some(e=>e.startsWith(`@relation`))){let e=Jr(c);s.has(c.name)&&(e.isPrimary=!0),(o.has(c.name)||c.attributes.some(e=>e.startsWith(`@unique`)))&&(e.hasIndex=!0),i.push(e)}return{name:r.name,tableName:r.tableName??r.name,filePath:r.filePath,columns:i,relations:a,indexes:r.indexes}})}const Qr={supportsIncrementalUpdate:!1,extract(e,t,n){let r=Hr(n);if(r.length===0)return[];let{models:i,enums:a}=Ur(r);return Zr(i,a)}},$r=/=>\s*(\w+)/,ei=new Set([`Column`,`PrimaryColumn`,`PrimaryGeneratedColumn`,`CreateDateColumn`,`UpdateDateColumn`,`DeleteDateColumn`,`VersionColumn`]),ti={OneToOne:`one-to-one`,OneToMany:`one-to-many`,ManyToOne:`many-to-one`,ManyToMany:`many-to-many`};function z(e){let t=e.getArguments();for(let e of t)if(e.getKind()===d.ObjectLiteralExpression){let t={},n=e.asKind(d.ObjectLiteralExpression);if(!n)continue;for(let e of n.getProperties())if(e.getKind()===d.PropertyAssignment){let n=e.asKind(d.PropertyAssignment);n&&(t[n.getName()]=n.getInitializer()?.getText()??``)}return t}return null}function ni(e){let t=e.getArguments();if(t.length===0)return null;let n=t[0];return n.getKind()===d.StringLiteral?n.asKind(d.StringLiteral)?.getLiteralValue()??null:null}function ri(e){let t=e.getDecorator(`Entity`);if(!t)return e.getName()??`UnknownEntity`;let n=ni(t);if(n)return n;let r=z(t);return r?.name?r.name.replace(/['"]/g,``):e.getName()??`UnknownEntity`}function ii(e,t){let n=t.getName(),r=n===`PrimaryColumn`||n===`PrimaryGeneratedColumn`,i=n===`PrimaryGeneratedColumn`||n===`CreateDateColumn`||n===`UpdateDateColumn`||n===`DeleteDateColumn`||n===`VersionColumn`,a=`unknown`,o=!1,s=!1,c,l=ni(t);l&&(a=l);let u=z(t);return u&&(u.type&&(a=u.type.replace(/['"]/g,``)),u.nullable===`true`&&(o=!0),u.unique===`true`&&(s=!0),u.default!==void 0&&(c=u.default)),a===`unknown`&&(n===`PrimaryGeneratedColumn`?a=`integer`:n===`CreateDateColumn`||n===`UpdateDateColumn`||n===`DeleteDateColumn`?a=`timestamp`:n===`VersionColumn`&&(a=`integer`)),{name:e,type:a,isPrimary:r,isNullable:o,isGenerated:i,isUnique:s,defaultValue:c}}function ai(e,t,n){let r=ti[n.getName()];if(!r)return null;let i=n.getArguments();if(i.length===0)return null;let a=i[0].getText(),o=$r.exec(a);if(!o)return null;let s=o[1],c=z(n),l=c?.nullable===`true`,u=c?.onDelete?.replace(/['"]/g,``);return{type:r,fromEntity:e,toEntity:s,propertyName:t,isNullable:l,...u?{onDelete:u}:{}}}function oi(e){if(!w(e,`Entity`))return null;let t=e.getName();if(!t)return null;let n=ri(e),r=e.getSourceFile().getFilePath(),i=[],a=[],o=[];for(let t of e.getDecorators())if(t.getName()===`Index`){let e=t.getArguments();for(let n of e)if(n.getKind()===d.ArrayLiteralExpression){let e=n.asKind(d.ArrayLiteralExpression);if(e){let n=e.getElements().map(e=>e.getKind()===d.StringLiteral?e.asKind(d.StringLiteral)?.getLiteralValue()??``:``).filter(Boolean);if(n.length>0){let e=z(t);o.push({columns:n,isUnique:e?.unique===`true`})}}}}let s=new Set;for(let n of e.getProperties()){let e=n.getName(),r=n.getDecorators(),c=r.some(e=>e.getName()===`Index`);c&&(s.add(e),o.push({columns:[e],isUnique:!1}));for(let n of r){let r=n.getName();if(ei.has(r)){let t=ii(e,n);c&&(t.hasIndex=!0),i.push(t);break}if(r in ti){let r=ai(t,e,n);r&&a.push(r);break}}}for(let e of o)for(let t of e.columns){let e=i.find(e=>e.name===t);e&&(e.hasIndex=!0)}return{name:t,tableName:n,filePath:r,columns:i,relations:a,indexes:o}}const si={prisma:Qr,typeorm:{supportsIncrementalUpdate:!0,extract(e,t){let n=[];for(let r of t){let t=e.getSourceFile(r);if(t)for(let e of t.getClasses()){let t=oi(e);t&&n.push(t)}}return n}},drizzle:Fr};function B(e,t,n,r){let i={entities:new Map,relations:[],orm:n??`unknown`};if(!n)return i;let a=si[n];if(!a)return i;let o=a.extract(e,t,r),s=new Map,c=[];for(let e of o)s.set(e.name,e),c.push(...e.relations);return{entities:s,relations:c,orm:n}}function ci(e){return{entities:[...e.entities.values()],relations:e.relations,orm:e.orm}}async function li(e,t){let{config:n,fileRules:r,projectRules:i,schemaRules:a}=t,[o,s]=await Promise.all([cr(e,n),ge(e)]),c=ur(o),l=A(e),u=M(c,o,l),d=mr(c,o);return{astProject:c,config:n,endpointGraph:Er(c,o,d),fileRules:r,files:o,moduleGraph:u,pathAliases:l,project:s,projectRules:i,providers:d,schemaGraph:B(c,o,s.orm,e),schemaRules:a,targetPath:e}}async function ui(e,t,n){let{config:r,combinedRules:i}=t,o=await lr(e,n,r),s=await Promise.all([...o.entries()].filter(([,e])=>e.length>0).map(async([t,o])=>{let s=a(e,n.projects.get(t)),[c,l]=await Promise.all([ge(s),Te(s,r)]),u=ur(o),d=A(s),f=M(u,o,d),p=mr(u,o),m=Er(u,o,p),h=B(u,o,c.orm,s),{fileRules:g,projectRules:_,schemaRules:v}=Qn(Zn(l,i));return[t,{astProject:u,config:l,endpointGraph:m,fileRules:g,files:o,moduleGraph:f,pathAliases:d,project:c,projectRules:_,providers:p,schemaGraph:h,schemaRules:v,targetPath:s}]}));return{subProjects:new Map(s)}}const di=e=>v.makeRe(e,{windows:!1}),fi=/\\/g,pi=/\/$/,mi=(e,t,n)=>{let r=new Set(Array.isArray(t.ignore?.rules)?t.ignore.rules:[]),i=Array.isArray(t.ignore?.files)?t.ignore.files.map(di):[];if(r.size===0&&i.length===0)return e;let a=n.replace(fi,`/`).replace(pi,``);return e.filter(e=>{if(r.has(e.rule))return!1;let t=e.filePath.replace(fi,`/`),n=t.startsWith(`${a}/`)?t.slice(a.length+1):t;return!i.some(e=>e.test(n))})};function hi(e,t,n,r){let i=[],a=[],o=e.getSourceFile(t);if(!o)return{diagnostics:i,errors:a};let s=o.getFullText().split(`
|
|
6
|
+
`);for(let e of n){let n={config:r,sourceFile:o,filePath:t,report(t){let n=[],r=Math.max(0,t.line-6),a=Math.min(s.length,t.line+5);for(let e=r;e<a;e++)n.push({line:e+1,text:s[e]});i.push({...t,rule:e.meta.id,category:e.meta.category,scope:`file`,severity:e.meta.severity,sourceLines:n})}};try{e.check(n)}catch(t){a.push({ruleId:e.meta.id,error:t})}}return{diagnostics:i,errors:a}}function gi(e,t,n,r){let i=[],a=[];for(let o of t){let t=hi(e,o,n,r);i.push(...t.diagnostics),a.push(...t.errors)}return{diagnostics:i,errors:a}}function _i(e,t,n,r){let i=[],a=[];for(let o of n){let n={project:e,files:t,moduleGraph:r.moduleGraph,providers:r.providers,config:r.config,report(e){i.push({...e,rule:o.meta.id,category:o.meta.category,scope:`project`,severity:o.meta.severity})}};try{o.check(n)}catch(e){a.push({ruleId:o.meta.id,error:e})}}return{diagnostics:i,errors:a}}function vi(e,t){let n=[],r=[];for(let i of t){let t={schemaGraph:e,orm:e.orm,report(e){n.push({...e,rule:i.meta.id,category:i.meta.category,scope:`schema`,severity:i.meta.severity})}};try{i.check(t)}catch(e){r.push({ruleId:i.meta.id,error:e})}}return{diagnostics:n,errors:r}}function yi(e){return e instanceof Error?e.message:String(e)}function V(e,t,n){return{diagnostics:mi(e,n.config,n.targetPath),errors:t.map(e=>({ruleId:e.ruleId,error:yi(e.error)}))}}function bi(e){let t=gi(e.astProject,e.files,e.fileRules,e.config);return V(t.diagnostics,t.errors,e)}function xi(e){let t={moduleGraph:e.moduleGraph,providers:e.providers,config:e.config},n=_i(e.astProject,e.files,e.projectRules,t),{diagnostics:r,errors:i}=V(n.diagnostics,n.errors,e),a=Si(e);return r.push(...a.diagnostics),i.push(...a.errors),{diagnostics:r,errors:i}}function Si(e){if(!e.schemaGraph||e.schemaRules.length===0||e.schemaGraph.entities.size===0)return{diagnostics:[],errors:[]};let t=vi(e.schemaGraph,e.schemaRules);return V(t.diagnostics,t.errors,e)}function H(e){let t=l.now(),n=bi(e),r=xi(e),i=l.now()-t;return{diagnostics:[...n.diagnostics,...r.diagnostics],elapsedMs:i,ruleErrors:[...n.errors,...r.errors]}}function Ci(e){return e>=90?`Excellent`:e>=75?`Good`:e>=50?`Fair`:e>=25?`Poor`:`Critical`}const wi={error:3,warning:1.5,info:.5},Ti={security:1.5,correctness:1.3,schema:1.1,architecture:1,performance:.8};function Ei(e,t){if(t===0)return{value:100,label:Ci(100)};let n=0;for(let t of e){let e=wi[t.severity],r=Ti[t.category];n+=e*r}let r=n/t,i=Math.max(0,Math.min(100,Math.round(100-r*10)));return{value:i,label:Ci(i)}}function Di(e){let t={total:0,errors:0,warnings:0,info:0,byCategory:{security:0,performance:0,correctness:0,architecture:0,schema:0}};for(let n of e)t.total++,n.severity===`error`?t.errors++:n.severity===`warning`?t.warnings++:t.info++,t.byCategory[n.category]++;return t}function U(e,t,n=[]){let{diagnostics:r,ruleErrors:i,elapsedMs:a}=t,o=e.schemaGraph??B(e.astProject,e.files,e.project.orm,e.targetPath),s=Ei(r,e.files.length),c=Di(r);return{result:{score:s,diagnostics:r,endpoints:e.endpointGraph,project:{...e.project,fileCount:e.files.length,moduleCount:e.moduleGraph.modules.size},summary:c,ruleErrors:i,elapsedMs:a,schema:ci(o)},moduleGraph:e.moduleGraph,schemaGraph:o,customRuleWarnings:n,files:e.files,providers:e.providers}}function W(e,t,n,r){let i=[],a=[],o=[],s=new Map,c=[],l=0,u=[],d=[],f=``;for(let[n,r]of e.subProjects){let e=U(r,t.get(n));i.push({name:n,result:e.result}),s.set(n,e.moduleGraph),a.push(...e.result.diagnostics),o.push(...e.result.ruleErrors),l+=e.result.project.fileCount,e.result.endpoints&&c.push(...e.result.endpoints.endpoints),e.result.schema&&(u.push(...e.result.schema.entities),d.push(...e.result.schema.relations),e.result.schema.orm&&e.result.schema.orm!==`unknown`&&(f=e.result.schema.orm))}let p=Ei(a,l),m=Di(a);return{moduleGraphs:s,customRuleWarnings:n,result:{isMonorepo:!0,subProjects:i,combined:{score:p,diagnostics:a,endpoints:c.length>0?{endpoints:c}:void 0,project:{name:`monorepo`,nestVersion:i[0]?.result.project.nestVersion??null,orm:f||(i[0]?.result.project.orm??null),framework:i[0]?.result.project.framework??null,fileCount:l,moduleCount:i.reduce((e,t)=>e+t.result.project.moduleCount,0)},summary:m,ruleErrors:o,elapsedMs:r,schema:u.length>0?{entities:u,relations:d,orm:f||`unknown`}:void 0},elapsedMs:r}}}const Oi=e=>{if(e.trim()===``)return`Invalid --min-score value: "${e}". Must be an integer between 0 and 100.`;let t=Number(e);return!Number.isInteger(t)||t<0||t>100?`Invalid --min-score value: "${e}". Must be an integer between 0 and 100.`:null},ki=(e,t)=>e===void 0?t:Number(e),Ai=(e,t)=>t===void 0?!0:e>=t,G={error:y.red,warn:y.yellow,info:y.cyan,success:y.green,dim:y.dim},K=e=>{console.log(e)},q={error(...e){K(G.error(e.join(` `)))},warn(...e){K(G.warn(e.join(` `)))},info(...e){K(G.info(e.join(` `)))},success(...e){K(G.success(e.join(` `)))},dim(...e){K(G.dim(e.join(` `)))},log(...e){K(e.join(` `))},break(){K(``)}},ji=1e3,Mi={error:0,warning:1,info:2},J=(e,t=e)=>({plainText:e,renderedText:t}),Y=(e,t)=>t>=75?G.success(e):t>=50?G.warn(e):G.error(e),Ni=(e,t)=>t===`error`?G.error(e):t===`warning`?G.warn(e):G.info(e),Pi=e=>e===`error`?`✗`:e===`warning`?`⚠`:`●`,Fi=e=>e<ji?`${Math.round(e)}ms`:`${(e/ji).toFixed(1)}s`,Ii=e=>e>=75?[`◠ ◠ ◠`,`╰───╯`]:e>=50?[`• • •`,`╰───╯`]:[`x x x`,`╰───╯`],Li=e=>e>=90?`★★★★★`:e>=75?`★★★★☆`:e>=50?`★★★☆☆`:e>=25?`★★☆☆☆`:`★☆☆☆☆`,Ri=e=>{let t=Math.round(e/100*50),n=50-t;return{filled:`█`.repeat(t),empty:`░`.repeat(n)}},zi=e=>{let{filled:t,empty:n}=Ri(e);return`${t}${n}`},Bi=e=>{let{filled:t,empty:n}=Ri(e);return Y(t,e)+G.dim(n)},Vi=e=>{if(e.length===0)return;let t=G.dim,n=` `.repeat(2),r=` `.repeat(1),i=Math.max(...e.map(e=>e.plainText.length)),a=`─`.repeat(i+2);q.log(`${n}${t(`┌${a}┐`)}`);for(let a of e){let e=` `.repeat(i-a.plainText.length);q.log(`${n}${t(`│`)}${r}${a.renderedText}${e}${r}${t(`│`)}`)}q.log(`${n}${t(`└${a}┘`)}`)},Hi=e=>{let t=new Map;for(let n of e){let e=n.rule,r=t.get(e)??[];r.push(n),t.set(e,r)}return t},Ui=e=>[...e].sort(([,e],[,t])=>Mi[e[0].severity]-Mi[t[0].severity]),Wi=e=>{let t=new Map;for(let n of e){let e=t.get(n.filePath)??[];`line`in n&&n.line>0&&e.push(n.line),t.set(n.filePath,e)}return t},Gi=e=>new Set(e.map(e=>e.filePath)),Ki=(e,t)=>{let n=Ui([...Hi(e).entries()]);for(let[,e]of n){let n=e[0],r=Ni(Pi(n.severity),n.severity),i=e.length,a=i>1?Ni(` (${i})`,n.severity):``;if(q.log(` ${r} ${n.message}${a}`),n.help&&q.dim(` ${n.help}`),t){let t=Wi(e);for(let[e,n]of t){let t=n.length>0?`: ${n.join(`, `)}`:``;q.dim(` ${e}${t}`)}}q.break()}};function qi(e,t){let{score:n,diagnostics:r,project:i,summary:a,elapsedMs:o}=e;q.break();let s=[],c=e=>Y(e,n.value),[l,u]=Ii(n.value);s.push(J(`┌───────┐`,c(`┌───────┐`))),s.push(J(`│ ${l} │ NestJS Doctor`,`${c(`│ ${l} │`)} NestJS Doctor`)),s.push(J(`│ ${u} │`,c(`│ ${u} │`))),s.push(J(`└───────┘`,c(`└───────┘`))),s.push(J(``));let d=Li(n.value),f=`${n.value} / 100 ${d} ${n.label}`,p=`${Y(String(n.value),n.value)} / 100 ${Y(d,n.value)} ${Y(n.label,n.value)}`;s.push(J(f,p)),s.push(J(``)),s.push(J(zi(n.value),Bi(n.value))),s.push(J(``));let m=Fi(o),h=Gi(r).size,g=[],_=[];if(a.errors>0){let e=`✗ ${a.errors} error${a.errors===1?``:`s`}`;_.push(e),g.push(G.error(e))}if(a.warnings>0){let e=`⚠ ${a.warnings} warning${a.warnings===1?``:`s`}`;_.push(e),g.push(G.warn(e))}if(a.info>0){let e=`● ${a.info} info`;_.push(e),g.push(G.info(e))}if(r.length===0){let e=`No issues found!`;_.push(e),g.push(G.success(e))}let v=r.length>0?`across ${h}/${i.fileCount} files`:`${i.fileCount} files scanned`,y=`in ${m}`;_.push(v),_.push(y),g.push(G.dim(v)),g.push(G.dim(y)),s.push(J(_.join(` `),g.join(` `))),Vi(s),q.break();let b=[`Project: ${i.name}`];if(i.nestVersion&&b.push(`NestJS ${i.nestVersion}`),i.orm&&b.push(i.orm),b.push(`${i.moduleCount} modules`),q.dim(` ${b.join(` | `)}`),q.break(),r.length!==0){if(Ki(r,t),t&&e.ruleErrors.length>0){q.warn(` ${e.ruleErrors.length} rule(s) failed during execution:`);for(let t of e.ruleErrors)q.dim(` ${t.ruleId}: ${t.error}`);q.break()}t||(q.dim(` Run with --verbose for file paths and line numbers`),q.break())}}function Ji(e,t){qi(e.combined,t),q.log(` Sub-project breakdown:`),q.break();for(let t of e.subProjects){let{name:e,result:n}=t,r=Y(String(n.score.value),n.score.value),i=[`${G.info(e)}: ${r}/100`,`${n.project.fileCount} files`];n.summary.errors>0&&i.push(G.error(`${n.summary.errors} errors`)),n.summary.warnings>0&&i.push(G.warn(`${n.summary.warnings} warnings`)),n.summary.info>0&&i.push(`${n.summary.info} info`),n.diagnostics.length===0&&i.push(G.success(`clean`)),q.log(` ${i.join(` | `)}`)}q.break()}function Yi(e){console.log(JSON.stringify(e,null,2))}const X=(e,t,n)=>{let r=e.score.value;Ai(r,t)||(n||q.error(`Score ${r} is below the minimum threshold of ${t}.`),process.exit(1))},Xi=e=>{e.summary.errors>0&&process.exit(1)},Zi=(e,t,n,r)=>{let{result:i}=e;if(r.score){console.log(i.combined.score.value),X(i.combined,t,n);return}if(r.json){Yi(i.combined),X(i.combined,t,n);return}Ji(i,r.verbose),X(i.combined,t,n),Xi(i.combined)},Qi=(e,t,n,r)=>{let{result:i}=e;if(r.score){console.log(i.score.value),X(i,t,n);return}if(r.json){Yi(i),X(i,t,n);return}qi(i,r.verbose),X(i,t,n),Xi(i)};let Z=null,Q=0;const $=new Set,$i=(e,t,n)=>{if($.delete(t),Q--,Q<=0||!Z){Z?.[e](n),Z=null,Q=0;return}Z.stop(),b(n).start()[e](n);let[r]=$;r&&(Z.text=r),Z.start()},ea=e=>({start(){return Q++,$.add(e),Z?Z.text=e:Z=b({text:e}).start(),{succeed:t=>$i(`succeed`,e,t),fail:t=>$i(`fail`,e,t)}}}),ta=(e,t)=>{if(!t)for(let t of e)q.warn(t)};var na=class{options;resolvedMinimumScore;scanConfig;steps=[];targetPath;constructor(e,t){this.targetPath=e,this.options=t}resolveConfig(){return this.steps.push(async()=>{this.scanConfig=await sr(this.targetPath,this.options.configPath),this.resolvedMinimumScore=ki(this.options.minScore,this.scanConfig.config.minScore)}),this}warnCustomRules(){return this.steps.push(()=>{ta(this.scanConfig.customRuleWarnings,this.options.isMachineReadable)}),this}async run(){let e=this.options.isMachineReadable?null:ea(`Scanning...`).start();for(let e of this.steps)await e();e?.succeed(`Scan complete`)}},ra=class extends na{monorepo;monorepoCtx;rawOutputs=new Map;result;scanStartTime;constructor(e,t,n){super(e,n),this.monorepo=t}buildContext(){return this.steps.push(async()=>{this.scanStartTime=l.now(),this.monorepoCtx=await ui(this.targetPath,this.scanConfig,this.monorepo)}),this}runRules(){return this.steps.push(()=>{for(let[e,t]of this.monorepoCtx.subProjects)this.rawOutputs.set(e,H(t))}),this}buildResult(){return this.steps.push(()=>{let e=l.now()-this.scanStartTime;this.result=W(this.monorepoCtx,this.rawOutputs,this.scanConfig.customRuleWarnings,e)}),this}output(){return this.steps.push(()=>{Zi(this.result,this.resolvedMinimumScore,this.options.isMachineReadable,{json:this.options.json,score:this.options.score,verbose:this.options.verbose})}),this}},ia=class extends na{context;rawOutput;result;buildContext(){return this.steps.push(async()=>{this.context=await li(this.targetPath,this.scanConfig)}),this}runRules(){return this.steps.push(()=>{this.rawOutput=H(this.context)}),this}buildResult(){return this.steps.push(()=>{this.result=U(this.context,this.rawOutput,this.scanConfig.customRuleWarnings)}),this}output(){return this.steps.push(()=>{Qi(this.result,this.resolvedMinimumScore,this.options.isMachineReadable,{json:this.options.json,score:this.options.score,verbose:this.options.verbose})}),this}},aa=class{args;steps=[];version;targetPath=``;constructor(e,t){this.args=e,this.version=t}resolveTargetPath(){return this.steps.push(()=>(this.targetPath=s(this.args.path??`.`),!0)),this}handleInit(){return this.steps.push(async()=>{if(this.args.init){let{initSkill:e}=await import(`../init-DX6EkkPo.mjs`);return await e(this.targetPath,this.version),!1}return!0}),this}handleReport(){return this.steps.push(async()=>{if(this.args.report){let{runReport:e}=await import(`../setup-CPsd2HN2.mjs`);return await e(this.targetPath,this.args.config),!1}return!0}),this}validateMinScore(){return this.steps.push(()=>{if(this.args[`min-score`]!==void 0){let e=Oi(this.args[`min-score`]);e&&(q.error(e),process.exit(2))}return!0}),this}async run(){for(let e of this.steps)if(!await e())return null;return{targetPath:this.targetPath,options:{configPath:this.args.config,isMachineReadable:this.args.score||this.args.json,json:this.args.json??!1,minScore:this.args[`min-score`],score:this.args.score??!1,verbose:this.args.verbose??!1}}}};const{version:oa}=e(import.meta.url)(`../../package.json`);n(t({meta:{name:`nestjs-doctor`,version:oa,description:`Static analysis tool for NestJS — health score, diagnostics, and interactive HTML report`},args:{path:{type:`positional`,description:`Path to the NestJS project (defaults to current directory)`,default:`.`,required:!1},...be},async run({args:e}){let t=await new aa(e,oa).resolveTargetPath().handleInit().handleReport().validateMinScore().run();if(!t)return;let{targetPath:n,options:r}=t,i=await me(n);if(i){await new ra(n,i,r).resolveConfig().buildContext().runRules().buildResult().warnCustomRules().output().run();return}await he(n)&&console.warn(`Warning: This directory appears to be a monorepo, but no NestJS packages were found.
|
|
7
|
+
Consider running on a specific sub-project instead.`),await new ia(n,r).resolveConfig().buildContext().runRules().buildResult().warnCustomRules().output().run()}}));export{U as a,ui as c,He as d,me as f,W as i,sr as l,q as n,H as o,G as r,li as s,ea as t,Ue as u};
|
|
@@ -33,7 +33,7 @@ npx nestjs-doctor@latest . --json
|
|
|
33
33
|
`,f=e=>{try{return s(process.platform===`win32`?`where ${e}`:`which ${e}`,{stdio:`ignore`}),!0}catch{return!1}},p=async e=>{await n(e,{recursive:!0}),await i(a(e,`AGENTS.md`),u,`utf-8`)},m=async(e,t)=>{await n(e,{recursive:!0}),await i(a(e,`SKILL.md`),t,`utf-8`),await i(a(e,`AGENTS.md`),u,`utf-8`)},h=async e=>{await n(e,{recursive:!0}),await i(a(e,`AGENTS.md`),d,`utf-8`)},g=async(e,t)=>{await n(e,{recursive:!0}),await i(a(e,`SKILL.md`),t,`utf-8`),await i(a(e,`AGENTS.md`),d,`utf-8`)},_=c(),v=[{name:`Claude Code`,detect:()=>o(a(_,`.claude`)),install:async e=>{await m(a(_,`.claude`,`skills`,`nestjs-doctor`),e.main),await g(a(_,`.claude`,`skills`,`nestjs-doctor-create-rule`),e.createRule)}},{name:`Amp Code`,detect:()=>o(a(_,`.amp`)),install:async()=>{await p(a(_,`.config`,`amp`,`skills`,`nestjs-doctor`)),await h(a(_,`.config`,`amp`,`skills`,`nestjs-doctor-create-rule`))}},{name:`Cursor`,detect:()=>o(a(_,`.cursor`)),install:async()=>{await p(a(_,`.cursor`,`skills`,`nestjs-doctor`)),await h(a(_,`.cursor`,`skills`,`nestjs-doctor-create-rule`))}},{name:`OpenCode`,detect:()=>f(`opencode`)||o(a(_,`.config`,`opencode`)),install:async()=>{await p(a(_,`.config`,`opencode`,`skills`,`nestjs-doctor`)),await h(a(_,`.config`,`opencode`,`skills`,`nestjs-doctor-create-rule`))}},{name:`Windsurf`,detect:()=>o(a(_,`.codeium`))||o(a(_,`Library`,`Application Support`,`Windsurf`)),install:async()=>{let e=a(_,`.codeium`,`windsurf`,`memories`,`global_rules.md`),s=u+d;if(o(e)){if((await r(e,`utf-8`)).includes(`# NestJS Doctor`))return;await t(e,`\n${s}`,`utf-8`)}else await n(a(_,`.codeium`,`windsurf`,`memories`),{recursive:!0}),await i(e,s,`utf-8`)}},{name:`Antigravity`,detect:()=>f(`agy`)||o(a(_,`.gemini`,`antigravity`)),install:async()=>{await p(a(_,`.gemini`,`antigravity`,`skills`,`nestjs-doctor`)),await h(a(_,`.gemini`,`antigravity`,`skills`,`nestjs-doctor-create-rule`))}},{name:`Gemini CLI`,detect:()=>f(`gemini`)||o(a(_,`.gemini`)),install:async()=>{await p(a(_,`.gemini`,`skills`,`nestjs-doctor`)),await h(a(_,`.gemini`,`skills`,`nestjs-doctor-create-rule`))}},{name:`Codex`,detect:()=>f(`codex`)||o(a(_,`.codex`)),install:async()=>{await p(a(_,`.codex`,`skills`,`nestjs-doctor`)),await h(a(_,`.codex`,`skills`,`nestjs-doctor-create-rule`));let e=a(_,`.codex`,`agents`);await n(e,{recursive:!0}),await i(a(e,`openai.yaml`),`interface:
|
|
34
34
|
display_name: "nestjs-doctor"
|
|
35
35
|
short_description: "Diagnose and fix NestJS codebase health issues"
|
|
36
|
-
`,`utf-8`)}}],y=async(t,n)=>{let r={main:"---\ndescription: Scan a NestJS project with nestjs-doctor, present a health report, and fix issues interactively\ndisable-model-invocation: true\nallowed-tools: Bash, Read, Edit, Glob, Grep, Write\n---\n\n# /nestjs-doctor — NestJS Health Scanner & Fixer\n\n> v0.4.18\n\nScan the NestJS codebase, present a prioritized health report, and offer to fix every issue found.\n\n## Step 1: Scan\n\nRun nestjs-doctor and capture the full JSON output:\n\n```!\nnpx nestjs-doctor $ARGUMENTS --json 2>/dev/null\n```\n\n## Step 2: Summarize\n\nParse the JSON output above. Present a summary:\n\n- **Score**: value / 100 (label)\n- **Severity counts**: errors, warnings, info\n- **Project info**: name, NestJS version, ORM, file count, module count\n\n## Step 3: Report\n\nGroup diagnostics by severity (errors first, then warnings, then info). Within each severity, group by category (security > correctness > architecture > performance).\n\nFor each group show:\n- Rule name and severity\n- Message and help text\n- Number of occurrences\n- Affected file paths (with line numbers)\n\nIf there are more than 30 diagnostics, show the top 30 (prioritizing errors and warnings) and ask if the user wants to see the rest.\n\n## Step 4: Offer to Fix\n\nAsk the developer what they want to fix:\n1. **Fix all** — apply fixes for every diagnostic\n2. **Fix by category** — fix all issues in a specific category (security, correctness, architecture, performance)\n3. **Fix specific rules** — fix only specific rule violations\n4. **Review only** — no changes, just the report\n\n## Step 5: Fix\n\nFor each diagnostic to fix:\n1. Read the affected file\n2. Apply the appropriate fix based on the rule (see Fix Guide below)\n3. Explain what was changed and why\n\n### Fix Guide by Rule\n\n#### Security\n\n- **no-hardcoded-secrets**: Extract the secret value to an environment variable. Import `ConfigService`, inject it, and use `this.configService.get('ENV_VAR_NAME')`. Add the env var name to `.env.example` if it exists.\n- **no-eval**: Remove `eval()` or `new Function()`. Replace with safe alternatives: `JSON.parse()` for JSON, a proper expression parser for dynamic evaluation, or refactor to avoid dynamic code execution entirely.\n- **no-csrf-disabled**: Remove the code that explicitly disables CSRF protection, or add a comment explaining why it's intentionally disabled with a `// nestjs-doctor-ignore` comment.\n- **no-dangerous-redirects**: Validate redirect URLs against an allowlist of trusted domains. Never pass user input directly to `res.redirect()`.\n- **no-weak-crypto**: Replace `createHash('md5')` or `createHash('sha1')` with `createHash('sha256')` or stronger.\n- **no-exposed-env-vars**: Replace direct `process.env.X` access with `ConfigService`. Inject `ConfigService` and use `this.configService.get('X')` or `this.configService.getOrThrow('X')`.\n- **no-exposed-stack-trace**: Remove `error.stack` from response objects. Log the stack trace server-side with `this.logger.error(error.stack)` and return a generic error message to the client.\n- **no-synchronize-in-production**: Set `synchronize: false` in TypeORM config. Use migrations (`typeorm migration:generate` and `typeorm migration:run`) for production schema changes. If needed only for development, guard with `synchronize: process.env.NODE_ENV !== 'production'`.\n- **no-raw-entity-in-response**: Create a DTO class for the response and map entity fields to it. Alternatively, use `class-transformer`'s `@Exclude()` decorator on sensitive entity fields and `@SerializeOptions()` on the controller. Never return raw ORM entities from controller methods.\n\n#### Correctness\n\n- **no-missing-injectable**: Add `@Injectable()` decorator to the class. Import it from `@nestjs/common`.\n- **no-duplicate-routes**: Remove or rename the duplicate route. Change the HTTP method or path to make each route unique.\n- **no-missing-guard-method**: Add the `canActivate(context: ExecutionContext): boolean | Promise<boolean> | Observable<boolean>` method to the guard class. Implement `CanActivate` interface.\n- **no-missing-pipe-method**: Add the `transform(value: any, metadata: ArgumentMetadata)` method to the pipe class. Implement `PipeTransform` interface.\n- **no-missing-filter-catch**: Add the `catch(exception: T, host: ArgumentHost)` method to the exception filter class. Implement `ExceptionFilter` interface.\n- **no-missing-interceptor-method**: Add the `intercept(context: ExecutionContext, next: CallHandler): Observable<any>` method. Implement `NestInterceptor` interface.\n- **require-inject-decorator**: Add `@Inject('TOKEN_NAME')` decorator to untyped constructor parameters, or add a proper type annotation.\n- **prefer-readonly-injection**: Add the `readonly` modifier to constructor-injected parameters: `constructor(private readonly myService: MyService)`.\n- **require-lifecycle-interface**: Add the corresponding interface to the class implements clause. E.g., if using `onModuleInit()`, add `implements OnModuleInit`.\n- **no-empty-handlers**: Add implementation to the empty HTTP handler. If it's a placeholder, add a `throw new NotImplementedException()`.\n- **no-async-without-await**: If the message says \"returns a Promise directly\", remove the `async` keyword since a `new Promise()` is already being constructed manually. Otherwise, either add an `await` expression or remove the `async` keyword.\n- **no-duplicate-module-metadata**: Remove duplicate entries from `@Module()` arrays (providers, controllers, imports, exports).\n- **no-missing-module-decorator**: Add `@Module({})` decorator to the class. Import it from `@nestjs/common`.\n- **no-fire-and-forget-async**: Add `await` before the async call to properly handle rejections. If fire-and-forget is intentional, prefix with `void` and add a `.catch()` handler: `void this.service.sendEmail().catch(err => this.logger.error(err))`.\n\n#### Architecture\n\n- **no-business-logic-in-controllers**: Extract the business logic (loops, complex conditionals, data transforms) into a service method. The controller should only call the service and return the result.\n- **no-repository-in-controllers**: Remove the repository injection from the controller. Create or use an existing service that wraps the repository, and inject that service instead.\n- **no-orm-in-controllers**: Remove PrismaService/EntityManager/DataSource injection from the controller. Create or use an existing service that wraps the ORM operations.\n- **no-circular-module-deps**: Break the circular dependency. Extract shared functionality into a separate module, use `forwardRef(() => Module)`, or restructure the module boundaries.\n- **no-manual-instantiation**: Replace `new SomeService()` with constructor injection. Add the service to the module's providers and inject it via the constructor.\n- **no-service-locator**: Replace `this.moduleRef.get(SomeService)` with constructor injection: add `private readonly someService: SomeService` to the constructor. If the service is truly dynamic or lazily resolved, document the reason with a comment.\n- **no-orm-in-services**: Consider introducing a repository layer. Create a repository class that wraps ORM operations, and inject the repository into the service instead of the ORM directly.\n- **prefer-constructor-injection**: Replace `@Inject()` property injection with constructor injection. Move the dependency to a constructor parameter.\n- **require-module-boundaries**: Replace deep imports (`import { X } from '../other-module/services/x.service'`) with imports through the module's public API (barrel file / index.ts).\n- **no-barrel-export-internals**: Remove repository re-exports from barrel files. Repositories should only be accessible within their own module.\n\n#### Performance\n\n- **no-sync-io**: Replace synchronous I/O (`readFileSync`, `writeFileSync`, etc.) with async equivalents (`readFile`, `writeFile` from `fs/promises`).\n- **no-blocking-constructor**: Move async operations and loops out of the constructor into `onModuleInit()` lifecycle hook. Implement `OnModuleInit` interface.\n- **no-dynamic-require**: Replace `require(variable)` with a static import or a switch/map pattern that uses static `require()` calls.\n- **no-unused-providers**: Remove the unused provider from the module's providers array, or start using it. If it's intended for external consumers, add it to the module's exports.\n- **no-request-scope-abuse**: Remove `Scope.REQUEST` unless the provider genuinely needs per-request state (e.g., request-scoped context like `REQUEST` object). Use `Scope.DEFAULT` (singleton) or `Scope.TRANSIENT` instead. Remember that request scope propagates to all dependents.\n- **no-unused-module-exports**: Remove the unused export from the module's exports array, or start importing the module where the exported provider is needed.\n- **no-orphan-modules**: Import this module in another module that needs it, or remove it if it's truly unused. If it's the root module, this can be ignored.\n\n## Step 6: Verify\n\nAfter applying fixes, suggest re-running the scan:\n\n> Fixes applied. Run `/nestjs-doctor` again to verify the score improved.\n".replace(l,`> v${n}`),createRule:`---
|
|
36
|
+
`,`utf-8`)}}],y=async(t,n)=>{let r={main:"---\ndescription: Scan a NestJS project with nestjs-doctor, present a health report, and fix issues interactively\ndisable-model-invocation: true\nallowed-tools: Bash, Read, Edit, Glob, Grep, Write\n---\n\n# /nestjs-doctor — NestJS Health Scanner & Fixer\n\n> v0.4.18\n\nScan the NestJS codebase, present a prioritized health report, and offer to fix every issue found.\n\n## Step 1: Scan\n\nRun nestjs-doctor and capture the full JSON output:\n\n```!\nnpx nestjs-doctor $ARGUMENTS --json 2>/dev/null\n```\n\n## Step 2: Summarize\n\nParse the JSON output above. Present a summary:\n\n- **Score**: value / 100 (label)\n- **Severity counts**: errors, warnings, info\n- **Project info**: name, NestJS version, ORM, file count, module count\n\n## Step 3: Report\n\nGroup diagnostics by severity (errors first, then warnings, then info). Within each severity, group by category (security > correctness > architecture > performance).\n\nFor each group show:\n- Rule name and severity\n- Message and help text\n- Number of occurrences\n- Affected file paths (with line numbers)\n\nIf there are more than 30 diagnostics, show the top 30 (prioritizing errors and warnings) and ask if the user wants to see the rest.\n\n## Step 4: Offer to Fix\n\nAsk the developer what they want to fix:\n1. **Fix all** — apply fixes for every diagnostic\n2. **Fix by category** — fix all issues in a specific category (security, correctness, architecture, performance)\n3. **Fix specific rules** — fix only specific rule violations\n4. **Review only** — no changes, just the report\n\n## Step 5: Fix\n\nFor each diagnostic to fix:\n1. Read the affected file\n2. Apply the appropriate fix based on the rule (see Fix Guide below)\n3. Explain what was changed and why\n\n### Fix Guide by Rule\n\n#### Security\n\n- **no-hardcoded-secrets**: Extract the secret value to an environment variable. Import `ConfigService`, inject it, and use `this.configService.get('ENV_VAR_NAME')`. Add the env var name to `.env.example` if it exists.\n- **no-eval**: Remove `eval()` or `new Function()`. Replace with safe alternatives: `JSON.parse()` for JSON, a proper expression parser for dynamic evaluation, or refactor to avoid dynamic code execution entirely.\n- **no-csrf-disabled**: Remove the code that explicitly disables CSRF protection, or add a comment explaining why it's intentionally disabled with a `// nestjs-doctor-ignore` comment.\n- **no-dangerous-redirects**: Validate redirect URLs against an allowlist of trusted domains. Never pass user input directly to `res.redirect()`.\n- **no-weak-crypto**: Replace `createHash('md5')` or `createHash('sha1')` with `createHash('sha256')` or stronger.\n- **no-exposed-env-vars**: Replace direct `process.env.X` access with `ConfigService`. Inject `ConfigService` and use `this.configService.get('X')` or `this.configService.getOrThrow('X')`.\n- **no-exposed-stack-trace**: Remove `error.stack` from response objects. Log the stack trace server-side with `this.logger.error(error.stack)` and return a generic error message to the client.\n- **no-synchronize-in-production**: Set `synchronize: false` in TypeORM config. Use migrations (`typeorm migration:generate` and `typeorm migration:run`) for production schema changes. If needed only for development, guard with `synchronize: process.env.NODE_ENV !== 'production'`.\n- **no-raw-entity-in-response**: Create a DTO class for the response and map entity fields to it. Alternatively, use `class-transformer`'s `@Exclude()` decorator on sensitive entity fields and `@SerializeOptions()` on the controller. Never return raw ORM entities from controller methods.\n\n#### Correctness\n\n- **no-missing-injectable**: Add `@Injectable()` decorator to the class. Import it from `@nestjs/common`.\n- **no-duplicate-routes**: Remove or rename the duplicate route. Change the HTTP method or path to make each route unique.\n- **no-missing-guard-method**: Add the `canActivate(context: ExecutionContext): boolean | Promise<boolean> | Observable<boolean>` method to the guard class. Implement `CanActivate` interface.\n- **no-missing-pipe-method**: Add the `transform(value: any, metadata: ArgumentMetadata)` method to the pipe class. Implement `PipeTransform` interface.\n- **no-missing-filter-catch**: Add the `catch(exception: T, host: ArgumentHost)` method to the exception filter class. Implement `ExceptionFilter` interface.\n- **no-missing-interceptor-method**: Add the `intercept(context: ExecutionContext, next: CallHandler): Observable<any>` method. Implement `NestInterceptor` interface.\n- **require-inject-decorator**: Add `@Inject('TOKEN_NAME')` decorator to untyped constructor parameters, or add a proper type annotation.\n- **prefer-readonly-injection**: Add the `readonly` modifier to constructor-injected parameters: `constructor(private readonly myService: MyService)`.\n- **require-lifecycle-interface**: Add the corresponding interface to the class implements clause. E.g., if using `onModuleInit()`, add `implements OnModuleInit`.\n- **no-empty-handlers**: Add implementation to the empty HTTP handler. If it's a placeholder, add a `throw new NotImplementedException()`.\n- **no-async-without-await**: If the message says \"returns a Promise directly\", remove the `async` keyword since a `new Promise()` is already being constructed manually. Otherwise, either add an `await` expression or remove the `async` keyword.\n- **no-duplicate-module-metadata**: Remove duplicate entries from `@Module()` arrays (providers, controllers, imports, exports).\n- **no-missing-module-decorator**: Add `@Module({})` decorator to the class. Import it from `@nestjs/common`.\n- **no-fire-and-forget-async**: Add `await` before the async call to properly handle rejections. If fire-and-forget is intentional, prefix with `void` and add a `.catch()` handler: `void this.service.sendEmail().catch(err => this.logger.error(err))`.\n\n#### Architecture\n\n- **no-business-logic-in-controllers**: Extract the business logic (loops, complex conditionals, data transforms) into a service method. The controller should only call the service and return the result.\n- **no-repository-in-controllers**: Remove the repository injection from the controller. Create or use an existing service that wraps the repository, and inject that service instead.\n- **no-orm-in-controllers**: Remove PrismaService/EntityManager/DataSource injection from the controller. Create or use an existing service that wraps the ORM operations.\n- **no-circular-module-deps**: Break the circular dependency. Extract shared functionality into a separate module, use `forwardRef(() => Module)`, or restructure the module boundaries.\n- **no-manual-instantiation**: Replace `new SomeService()` with constructor injection. Add the service to the module's providers and inject it via the constructor.\n- **no-service-locator**: Replace `this.moduleRef.get(SomeService)` with constructor injection: add `private readonly someService: SomeService` to the constructor. If the service is truly dynamic or lazily resolved, document the reason with a comment.\n- **no-orm-in-services**: Consider introducing a repository layer. Create a repository class that wraps ORM operations, and inject the repository into the service instead of the ORM directly.\n- **prefer-constructor-injection**: Replace `@Inject()` property injection with constructor injection. Move the dependency to a constructor parameter.\n- **require-module-boundaries**: Replace deep imports (`import { X } from '../other-module/services/x.service'`) with imports through the module's public API (barrel file / index.ts).\n- **no-barrel-export-internals**: Remove repository re-exports from barrel files. Repositories should only be accessible within their own module.\n\n#### Performance\n\n- **no-sync-io**: Replace synchronous I/O (`readFileSync`, `writeFileSync`, etc.) with async equivalents (`readFile`, `writeFile` from `fs/promises`).\n- **no-blocking-constructor**: Move async operations and loops out of the constructor into `onModuleInit()` lifecycle hook. Implement `OnModuleInit` interface.\n- **no-dynamic-require**: Replace `require(variable)` with a static import or a switch/map pattern that uses static `require()` calls.\n- **no-unused-providers**: Remove the unused provider from the module's providers array, or start using it. Providers with self-activating decorators (@Cron, @OnEvent, @Process) are automatically excluded. If it's intended for external consumers, add it to the module's exports.\n- **no-request-scope-abuse**: Remove `Scope.REQUEST` unless the provider genuinely needs per-request state (e.g., request-scoped context like `REQUEST` object). Use `Scope.DEFAULT` (singleton) or `Scope.TRANSIENT` instead. Remember that request scope propagates to all dependents.\n- **no-unused-module-exports**: Remove the unused export from the module's exports array, or start importing the module where the exported provider is needed.\n- **no-orphan-modules**: Import this module in another module that needs it, or remove it if it's truly unused. If it's the root module, this can be ignored.\n\n## Step 6: Verify\n\nAfter applying fixes, suggest re-running the scan:\n\n> Fixes applied. Run `/nestjs-doctor` again to verify the score improved.\n".replace(l,`> v${n}`),createRule:`---
|
|
37
37
|
description: Create a custom nestjs-doctor rule that detects a specific pattern or anti-pattern in a NestJS codebase
|
|
38
38
|
disable-model-invocation: true
|
|
39
39
|
allowed-tools: Bash, Read, Edit, Glob, Grep, Write
|
|
@@ -74,6 +74,19 @@ export class UserController {
|
|
|
74
74
|
const user = await this.userService.findOne(id);
|
|
75
75
|
return new UserResponseDto(user); // Controlled shape
|
|
76
76
|
}
|
|
77
|
+
}`},"security/require-guards-on-endpoints":{bad:`@Controller('orders')
|
|
78
|
+
export class OrderController {
|
|
79
|
+
@Get()
|
|
80
|
+
findAll() {
|
|
81
|
+
return this.orderService.findAll(); // No guard, no @Public()
|
|
82
|
+
}
|
|
83
|
+
}`,good:`@UseGuards(AuthGuard)
|
|
84
|
+
@Controller('orders')
|
|
85
|
+
export class OrderController {
|
|
86
|
+
@Get()
|
|
87
|
+
findAll() {
|
|
88
|
+
return this.orderService.findAll();
|
|
89
|
+
}
|
|
77
90
|
}`},"correctness/no-missing-injectable":{bad:`// user.service.ts
|
|
78
91
|
export class UserService { // Missing @Injectable()
|
|
79
92
|
constructor(private readonly db: DatabaseService) {}
|
|
@@ -206,6 +219,66 @@ export class OrderService {
|
|
|
206
219
|
await this.notificationService.sendEmail(orderId);
|
|
207
220
|
await this.analyticsService.track('order_completed');
|
|
208
221
|
}
|
|
222
|
+
}`},"correctness/factory-inject-matches-params":{bad:`@Module({
|
|
223
|
+
providers: [{
|
|
224
|
+
provide: 'DATA_SOURCE',
|
|
225
|
+
useFactory: (config: ConfigService) => createDataSource(config),
|
|
226
|
+
inject: [ConfigService, Logger], // 2 tokens but 1 param!
|
|
227
|
+
}],
|
|
228
|
+
})
|
|
229
|
+
export class AppModule {}`,good:`@Module({
|
|
230
|
+
providers: [{
|
|
231
|
+
provide: 'DATA_SOURCE',
|
|
232
|
+
useFactory: (config: ConfigService, logger: Logger) =>
|
|
233
|
+
createDataSource(config, logger),
|
|
234
|
+
inject: [ConfigService, Logger], // 2 tokens, 2 params
|
|
235
|
+
}],
|
|
236
|
+
})
|
|
237
|
+
export class AppModule {}`},"correctness/injectable-must-be-provided":{bad:`@Injectable()
|
|
238
|
+
export class CacheService { // Not in any module's providers
|
|
239
|
+
get(key: string) { /* ... */ }
|
|
240
|
+
}`,good:`@Injectable()
|
|
241
|
+
export class CacheService {
|
|
242
|
+
get(key: string) { /* ... */ }
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
@Module({ providers: [CacheService] })
|
|
246
|
+
export class CacheModule {}`},"correctness/no-duplicate-decorators":{bad:`@Controller('users')
|
|
247
|
+
export class UserController {
|
|
248
|
+
@Get(':id')
|
|
249
|
+
@Get(':id') // Duplicate decorator!
|
|
250
|
+
findOne(@Param('id') id: string) { /* ... */ }
|
|
251
|
+
}`,good:`@Controller('users')
|
|
252
|
+
export class UserController {
|
|
253
|
+
@Get(':id')
|
|
254
|
+
findOne(@Param('id') id: string) { /* ... */ }
|
|
255
|
+
}`},"correctness/param-decorator-matches-route":{bad:`@Controller('users')
|
|
256
|
+
export class UserController {
|
|
257
|
+
@Get(':id')
|
|
258
|
+
findOne(@Param('userId') userId: string) { // 'userId' not in route
|
|
259
|
+
return this.userService.findOne(userId);
|
|
260
|
+
}
|
|
261
|
+
}`,good:`@Controller('users')
|
|
262
|
+
export class UserController {
|
|
263
|
+
@Get(':id')
|
|
264
|
+
findOne(@Param('id') id: string) { // 'id' matches :id in route
|
|
265
|
+
return this.userService.findOne(id);
|
|
266
|
+
}
|
|
267
|
+
}`},"correctness/validate-nested-array-each":{bad:`export class CreateOrderDto {
|
|
268
|
+
@ValidateNested() // Missing { each: true } for array
|
|
269
|
+
@Type(() => OrderItemDto)
|
|
270
|
+
items: OrderItemDto[];
|
|
271
|
+
}`,good:`export class CreateOrderDto {
|
|
272
|
+
@ValidateNested({ each: true }) // Validates each item in the array
|
|
273
|
+
@Type(() => OrderItemDto)
|
|
274
|
+
items: OrderItemDto[];
|
|
275
|
+
}`},"correctness/validated-non-primitive-needs-type":{bad:`export class CreateUserDto {
|
|
276
|
+
@ValidateNested()
|
|
277
|
+
address: AddressDto; // Missing @Type() — validator can't instantiate
|
|
278
|
+
}`,good:`export class CreateUserDto {
|
|
279
|
+
@ValidateNested()
|
|
280
|
+
@Type(() => AddressDto)
|
|
281
|
+
address: AddressDto;
|
|
209
282
|
}`},"architecture/no-business-logic-in-controllers":{bad:`@Controller('orders')
|
|
210
283
|
export class OrderController {
|
|
211
284
|
@Post()
|
|
@@ -388,7 +461,57 @@ export class AnalyticsModule {}`,good:`// Import it in the parent module:
|
|
|
388
461
|
@Module({
|
|
389
462
|
imports: [AnalyticsModule],
|
|
390
463
|
})
|
|
391
|
-
export class AppModule {}`}
|
|
464
|
+
export class AppModule {}`},"schema/require-primary-key":{bad:`@Entity()
|
|
465
|
+
export class AuditLog {
|
|
466
|
+
@Column()
|
|
467
|
+
action: string;
|
|
468
|
+
|
|
469
|
+
@Column()
|
|
470
|
+
timestamp: Date;
|
|
471
|
+
// No primary key column!
|
|
472
|
+
}`,good:`@Entity()
|
|
473
|
+
export class AuditLog {
|
|
474
|
+
@PrimaryGeneratedColumn('uuid')
|
|
475
|
+
id: string;
|
|
476
|
+
|
|
477
|
+
@Column()
|
|
478
|
+
action: string;
|
|
479
|
+
|
|
480
|
+
@Column()
|
|
481
|
+
timestamp: Date;
|
|
482
|
+
}`},"schema/require-timestamps":{bad:`@Entity()
|
|
483
|
+
export class Product {
|
|
484
|
+
@PrimaryGeneratedColumn()
|
|
485
|
+
id: number;
|
|
486
|
+
|
|
487
|
+
@Column()
|
|
488
|
+
name: string;
|
|
489
|
+
// No createdAt / updatedAt columns
|
|
490
|
+
}`,good:`@Entity()
|
|
491
|
+
export class Product {
|
|
492
|
+
@PrimaryGeneratedColumn()
|
|
493
|
+
id: number;
|
|
494
|
+
|
|
495
|
+
@Column()
|
|
496
|
+
name: string;
|
|
497
|
+
|
|
498
|
+
@CreateDateColumn()
|
|
499
|
+
createdAt: Date;
|
|
500
|
+
|
|
501
|
+
@UpdateDateColumn()
|
|
502
|
+
updatedAt: Date;
|
|
503
|
+
}`},"schema/require-cascade-rule":{bad:`@Entity()
|
|
504
|
+
export class User {
|
|
505
|
+
@OneToMany(() => Order, (order) => order.user, {
|
|
506
|
+
cascade: true, // Risky: may unintentionally persist/remove orders
|
|
507
|
+
})
|
|
508
|
+
orders: Order[];
|
|
509
|
+
}`,good:`@Entity()
|
|
510
|
+
export class User {
|
|
511
|
+
@OneToMany(() => Order, (order) => order.user)
|
|
512
|
+
orders: Order[];
|
|
513
|
+
// Persist/remove orders explicitly via OrderRepository
|
|
514
|
+
}`}}}function S(e,t,n){let r=[];for(let t of e.modules.values()){let e=t.name.indexOf(`/`),i=n&&n.length>0&&e!==-1?t.name.slice(0,e):void 0;r.push({name:t.name,filePath:t.filePath,imports:t.imports,exports:t.exports,providers:t.providers,controllers:t.controllers,project:i})}let i=[];for(let[t,n]of e.edges)for(let e of n)i.push({from:t,to:e});let a=d(e),o={};for(let e of t.diagnostics)if(e.rule===`architecture/no-circular-module-deps`)for(let t of a){let n=t.join(` -> `);e.message.includes(n)&&(o[t.join(`,`)]=e.help)}return{modules:r,edges:i,circularDeps:a,circularDepRecommendations:o,projects:n??[]}}function C(e){return e.replace(/<\/script/gi,`<\\/script`).replace(/<!--/g,`<\\!--`)}function w(e){return[...e.values()].map(e=>({name:e.name,filePath:e.filePath,dependencies:e.dependencies,publicMethodCount:e.publicMethodCount}))}function T(e){let t={};for(let n of e)try{t[n]=h(n,`utf-8`)}catch{}return t}function E(e,t,n){let r=S(e,t,n?.projects),i=t.diagnostics.map(e=>{if(`sourceLines`in e){let{sourceLines:t,...n}=e;return n}return e}),a=t.diagnostics.map(e=>`sourceLines`in e?e.sourceLines??null:null),o=C(JSON.stringify(r)),s=C(JSON.stringify({name:t.project.name,score:t.score,moduleCount:t.project.moduleCount,fileCount:t.project.fileCount,framework:t.project.framework,nestVersion:t.project.nestVersion,orm:t.project.orm})),c=C(JSON.stringify(i)),l=C(JSON.stringify(t.summary)),u=C(JSON.stringify(t.elapsedMs)),d=C(JSON.stringify(a)),f=C(JSON.stringify(x())),p=T(n?.files??[]),m=C(JSON.stringify(p)),h=w(n?.providers??new Map);return{graphJson:o,projectJson:s,diagnosticsJson:c,summaryJson:l,elapsedMsJson:u,sourceLinesJson:d,examplesJson:f,fileSourcesJson:m,providersJson:C(JSON.stringify(h)),schemaJson:C(JSON.stringify(t.schema??{entities:[],relations:[],orm:``})),endpointsJson:C(JSON.stringify(t.endpoints??{endpoints:[]}))}}function D(){return`<script type="importmap">
|
|
392
515
|
{
|
|
393
516
|
"imports": {
|
|
394
517
|
"style-mod": "https://esm.sh/style-mod@4.1.2",
|
package/package.json
CHANGED