nestjs-doctor 0.4.25 → 0.4.27
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 +58 -1
- package/dist/api/index.mjs +5 -4
- package/dist/cli/index.mjs +6 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -243,7 +243,11 @@ Invalid rules produce warnings but never crash the scan. Common issues — missi
|
|
|
243
243
|
|
|
244
244
|
## Monorepo Support
|
|
245
245
|
|
|
246
|
-
|
|
246
|
+
Monorepo detection supports five strategies (checked in priority order — first match wins):
|
|
247
|
+
|
|
248
|
+
### 1. `nest-cli.json` (takes precedence)
|
|
249
|
+
|
|
250
|
+
When `"monorepo": true` is set, each sub-project is scanned independently and results are merged.
|
|
247
251
|
|
|
248
252
|
```json
|
|
249
253
|
{
|
|
@@ -256,6 +260,59 @@ Auto-detected from `nest-cli.json`. When `"monorepo": true` is set, each sub-pro
|
|
|
256
260
|
}
|
|
257
261
|
```
|
|
258
262
|
|
|
263
|
+
### 2. `pnpm-workspace.yaml` (pnpm / Turborepo)
|
|
264
|
+
|
|
265
|
+
Reads `pnpm-workspace.yaml`, expands the `packages` globs, and filters to packages that depend on `@nestjs/core` or `@nestjs/common`.
|
|
266
|
+
|
|
267
|
+
```yaml
|
|
268
|
+
packages:
|
|
269
|
+
- "apps/*"
|
|
270
|
+
- "packages/*"
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
### 3. `package.json` workspaces (npm / Yarn)
|
|
274
|
+
|
|
275
|
+
Reads the `workspaces` field from the root `package.json`. Both array and object formats are supported. Skipped when `pnpm-workspace.yaml` exists to avoid duplicate detection.
|
|
276
|
+
|
|
277
|
+
```json
|
|
278
|
+
{
|
|
279
|
+
"workspaces": ["apps/*", "packages/*"]
|
|
280
|
+
}
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
```json
|
|
284
|
+
{
|
|
285
|
+
"workspaces": {
|
|
286
|
+
"packages": ["apps/*", "packages/*"]
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
### 4. `nx.json` (Nx)
|
|
292
|
+
|
|
293
|
+
Detects `nx.json` and discovers sub-projects by scanning for `project.json` files. Each project must have a `package.json` with a NestJS dependency to be included.
|
|
294
|
+
|
|
295
|
+
```json
|
|
296
|
+
// nx.json
|
|
297
|
+
{
|
|
298
|
+
"targetDefaults": { "build": { "dependsOn": ["^build"] } }
|
|
299
|
+
}
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
### 5. `lerna.json` (standalone Lerna)
|
|
303
|
+
|
|
304
|
+
Reads `lerna.json` when `useWorkspaces` is **not** set. Uses the `packages` globs (defaults to `["packages/*"]`). When `useWorkspaces` is `true`, detection is handled by strategy 3 instead.
|
|
305
|
+
|
|
306
|
+
```json
|
|
307
|
+
{
|
|
308
|
+
"packages": ["packages/*"]
|
|
309
|
+
}
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
Non-NestJS packages are always filtered out automatically — only packages with `@nestjs/core` or `@nestjs/common` are included.
|
|
313
|
+
|
|
314
|
+
If a monorepo is detected but no NestJS packages are found, nestjs-doctor emits a warning and falls back to single-project mode.
|
|
315
|
+
|
|
259
316
|
Output includes a combined score and a per-project breakdown.
|
|
260
317
|
|
|
261
318
|
---
|
package/dist/api/index.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import{existsSync as e,readFileSync as t,readdirSync as n,statSync as r}from"node:fs";import{dirname as i,join as a,resolve as o}from"node:path";import{readFile as s}from"node:fs/promises";import{performance as c}from"node:perf_hooks";import{Project as l,SyntaxKind as u,ts as d}from"ts-morph";import{createJiti as f}from"jiti";import{glob as p}from"tinyglobby";import m from"picomatch";var h=class extends Error{constructor(e){super(e),this.name=`NestjsDoctorError`}},g=class extends h{constructor(e){super(e),this.name=`ConfigurationError`}},_=class extends h{constructor(e){super(e),this.name=`ScanError`}},v=class extends h{constructor(e){super(e),this.name=`ValidationError`}};async function ee(e){let t=a(e,`nest-cli.json`);try{let e=await s(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}}async function te(e){let t=a(e,`package.json`),n={};try{let e=await s(t,`utf-8`);n=JSON.parse(e)}catch{}let r={...n.dependencies,...n.devDependencies},i=ne(r[`@nestjs/core`]),o=re(r),c=ie(r);return{name:n.name??`unknown`,nestVersion:i,orm:o,framework:c,moduleCount:0,fileCount:0}}function ne(e){return e?e.replace(/[\^~>=<]/g,``):null}function re(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 ie(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(`,`)},ae=[`nestjs-doctor.config.json`,`.nestjs-doctor.json`];async function oe(e,t){if(t)return se(t);for(let t of ae)try{return await se(a(e,t))}catch{}try{let t=await s(a(e,`package.json`),`utf-8`),n=JSON.parse(t);if(n[`nestjs-doctor`]&&typeof n[`nestjs-doctor`]==`object`)return ce(n[`nestjs-doctor`])}catch{}return{...y}}async function se(e){let t=await s(e,`utf-8`);return ce(JSON.parse(t))}function ce(e){return{...y,...e,exclude:[...y.exclude??[],...e.exclude??[]]}}async function le(e,t){try{return await oe(e)}catch{return t}}const ue=[/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){ue.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 C(e){return x(e,`Injectable`)}function w(e){return x(e,`Injectable`)||x(e,`Controller`)||x(e,`Resolver`)||x(e,`WebSocketGateway`)}function fe(e){return x(e,`Module`)}function T(e){return e.getDecorators().some(e=>b.has(e.getName()))}const pe=new Set([`TsRestHandler`,`GrpcMethod`,`GrpcStreamMethod`]);function me(e){return e.getDecorators().some(e=>pe.has(e.getName()))}const he={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(u.IfStatement),i=t.getDescendantsOfKind(u.ForStatement),a=t.getDescendantsOfKind(u.ForInStatement),o=t.getDescendantsOfKind(u.ForOfStatement),s=t.getDescendantsOfKind(u.WhileStatement),c=t.getDescendantsOfKind(u.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 d=t.getDescendantsOfKind(u.CallExpression).filter(e=>{let t=e.getExpression();if(t.getKind()===u.PropertyAccessExpression){let e=t.asKind(u.PropertyAccessExpression)?.getName();return e===`map`||e===`filter`||e===`reduce`||e===`sort`||e===`flatMap`}return!1});d.length>1&&e.report({filePath:e.filePath,message:`Controller method '${n.getName()}' contains data transformation logic (${d.length} array operations). Move to a service.`,help:this.meta.help,line:n.getStartLineNumber(),column:1})}}};function E(e){let t=new Map;try{let n=d.findConfigFile(e,d.sys.fileExists,`tsconfig.json`);if(!n)return t;let{config:r,error:a}=d.readConfigFile(n,d.sys.readFile);if(a||!r)return t;let s=i(n),c=d.parseJsonConfigFileContent(r,d.sys,s),l=c.options.paths;if(!l)return t;let u=c.options.baseUrl??s;for(let[e,n]of Object.entries(l)){let r=n.map(e=>o(u,e));t.set(e,r)}}catch{return t}return t}function ge(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 _e=/=>\s*(\w+)/,D=/\.js$/;function O(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()===u.ObjectLiteralExpression){let e=o.asKind(u.ObjectLiteralExpression);e&&(s.imports=A(e,`imports`,n),s.exports=A(e,`exports`,n),s.providers=A(e,`providers`,n),s.controllers=A(e,`controllers`,n))}r.push(s)}return r}function k(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 O(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 ve=new Set([`forRoot`,`forRootAsync`,`forFeature`,`forFeatureAsync`,`forChild`,`forChildAsync`,`register`,`registerAsync`]);function A(e,t,n){let r=e.getProperty(t);if(!r)return[];let i=r.asKind(u.PropertyAssignment);if(!i)return[];let a=i.getInitializer();return a?j(a,e.getSourceFile(),0,n):[]}function j(e,t,n,r){if(n>5)return[];let i=e.getKind();if(i===u.ArrayLiteralExpression){let i=e.asKindOrThrow(u.ArrayLiteralExpression),a=[];for(let e of i.getElements())a.push(...ye(e,t,n,r));return a}return i===u.CallExpression?M(e.asKindOrThrow(u.CallExpression),t,n,r):i===u.Identifier?F(e.getText(),t,n+1,r):[]}function ye(e,t,n,r){let i=e.getText();if(i.startsWith(`forwardRef`)){let e=i.match(_e);return e?[e[1]]:[i]}let a=e.getKind();return a===u.SpreadElement?j(e.asKindOrThrow(u.SpreadElement).getExpression(),t,n,r):a===u.CallExpression?M(e.asKindOrThrow(u.CallExpression),t,n,r):a===u.PropertyAccessExpression?[e.asKindOrThrow(u.PropertyAccessExpression).getExpression().getText()]:(u.Identifier,[i])}function M(e,t,n,r){let i=e.getExpression();if(i.getKind()===u.PropertyAccessExpression){let a=i.asKindOrThrow(u.PropertyAccessExpression),o=a.getName();if(o===`concat`){let i=j(a.getExpression(),t,n,r),o=[];for(let i of e.getArguments())o.push(...j(i,t,n,r));return[...i,...o]}return ve.has(o),[a.getExpression().getText()]}return i.getKind()===u.Identifier?I(i.getText(),t,n+1,r):[]}function N(e,t,n){if(!e.startsWith(`.`)){let r=ge(e,n);if(!r)return;let i=t.getProject(),a=[`${r}.ts`,`${r}/index.ts`,r,r.replace(D,`.ts`)];for(let e of a){let t=i.getSourceFile(e);if(t)return t}return}let r=o(i(t.getFilePath()),e),a=t.getProject(),s=[`${r}.ts`,`${r}/index.ts`,r,r.replace(D,`.ts`)];for(let e of s){let t=a.getSourceFile(e);if(t)return t}}function P(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=N(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=N(r.getModuleSpecifierValue(),t,n);return e?{sourceFile:e,localName:i.getName()}:void 0}}}function F(e,t,n,r){if(n>5)return[];for(let i of t.getStatements()){if(i.getKind()!==u.VariableStatement)continue;let a=i.asKindOrThrow(u.VariableStatement);for(let i of a.getDeclarations())if(i.getName()===e){let e=i.getInitializer();if(e)return j(e,t,n,r)}}let i=P(e,t,r);return i?F(i.localName,i.sourceFile,n+1,r):[]}function be(e,t,n,r){for(let i of t.getStatements()){if(i.getKind()!==u.VariableStatement)continue;let a=i.asKindOrThrow(u.VariableStatement);for(let i of a.getDeclarations()){if(i.getName()!==e)continue;let a=i.getInitializer();if(!a||a.getKind()!==u.ArrowFunction)continue;let o=a.asKindOrThrow(u.ArrowFunction).getBody();if(o.getKind()!==u.Block)return j(o,t,n,r);let s=[];for(let e of o.getDescendantsOfKind(u.ReturnStatement)){let i=e.getExpression();i&&s.push(...j(i,t,n,r))}return s}}}function I(e,t,n,r){if(n>5)return[];for(let i of t.getStatements()){if(i.getKind()!==u.FunctionDeclaration)continue;let a=i.asKindOrThrow(u.FunctionDeclaration);if(a.getName()!==e)continue;let o=[];for(let e of a.getDescendantsOfKind(u.ReturnStatement)){let i=e.getExpression();i&&o.push(...j(i,t,n,r))}return o}let i=be(e,t,n,r);if(i)return i;let a=P(e,t,r);return a?I(a.localName,a.sourceFile,n+1,r):[]}function L(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=O(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 xe(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 R(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 z=`Break the cycle by extracting shared logic into a separate module or using forwardRef().`;function Se(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=R(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 z;let c=o.join(`
|
|
2
|
-
`);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=R(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 Ce={meta:{id:`architecture/no-circular-module-deps`,category:`architecture`,severity:`error`,description:`Circular dependencies in @Module() import graph`,help:z,scope:`project`},check(e){let t=xe(e.moduleGraph);for(let n of t){let t=n.join(` -> `),r=e.moduleGraph.modules.get(n[0]),i=Se(n,e);e.report({filePath:r?.filePath??`unknown`,message:`Circular module dependency detected: ${t}`,help:i,line:r?.classDeclaration.getStartLineNumber()??1,column:1})}}},we=[`Service`,`Repository`,`Gateway`,`Resolver`],Te=[`Guard`,`Interceptor`,`Pipe`,`Filter`];function B(e){return typeof e==`object`&&!!e}function Ee(e){if(!B(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(!B(n))return new Set;let r=n.excludeClasses;return Array.isArray(r)?new Set(r.filter(e=>typeof e==`string`)):new Set}const De={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=Ee(e.config?.rules?.[this.meta.id]),n=e.sourceFile.getDescendantsOfKind(u.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=we.some(e=>n.endsWith(e)),o=Te.some(e=>n.endsWith(e));if(a||o){if(o){if(r.getFirstAncestorByKind(u.Decorator))continue;let e=r.getFirstAncestorByKind(u.MethodDeclaration),t=r.getFirstAncestorByKind(u.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})}}}},Oe=/\.(\w+)$/,ke=/^(\w+)</,Ae=new Set([`PrismaService`,`PrismaClient`,`EntityManager`,`DataSource`,`Repository`,`Connection`,`MongooseModel`,`InjectModel`,`InjectRepository`,`MikroORM`,`DrizzleService`]),je={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=Me(t.getType().getText());if(Ae.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 Me(e){let t=e.match(Oe);if(t)return t[1];let n=e.match(ke);return n?n[1]:e}const Ne=/\.(\w+)$/,Pe=/^(\w+)</,Fe=new Set([`PrismaService`,`PrismaClient`,`EntityManager`,`DataSource`,`Connection`,`MikroORM`]),Ie={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(!C(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=Le(t.getType().getText());if(Fe.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 Le(e){let t=e.match(Ne);if(t)return t[1];let n=e.match(Pe);return n?n[1]:e}const Re=/\.(\w+)$/,ze=/^(\w+)</,Be=[/Repository$/,/Repo$/],Ve={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=He(t.getType().getText());if(Be.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 He(e){let t=e.match(Re);if(t)return t[1];let n=e.match(ze);return n?n[1]:e}const Ue={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(u.CallExpression);for(let n of t){let t=n.getExpression();if(t.getKind()!==u.PropertyAccessExpression)continue;let r=t.asKind(u.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})}}},We={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(w(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})}},Ge=[`/repositories/`,`/entities/`,`/dto/`,`/guards/`,`/interceptors/`,`/pipes/`,`/strategies/`],Ke={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(`../`)&&Ge.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 V(e){return e.getDescendantsOfKind(u.ReturnStatement).some(e=>{let t=e.getExpression();return!t||t.getKind()!==u.NewExpression?!1:t.asKindOrThrow(u.NewExpression).getExpression().getText()===`Promise`})}const qe={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)&&T(n)||me(n))continue;let r=n.getBody();if(r&&r.getDescendantsOfKind(u.AwaitExpression).filter(e=>{let t=e.getParent();for(;t&&t!==r;){if(t.getKind()===u.ArrowFunction||t.getKind()===u.FunctionExpression||t.getKind()===u.FunctionDeclaration)return!1;t=t.getParent()}return!0}).length===0){let t=n.getName();V(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(u.AwaitExpression).filter(e=>{let t=e.getParent();for(;t&&t!==n;){if(t.getKind()===u.ArrowFunction||t.getKind()===u.FunctionExpression||t.getKind()===u.FunctionDeclaration)return!1;t=t.getParent()}return!0}).length===0){let r=t.getName()??`anonymous`;V(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})}}}},Je=[`providers`,`controllers`,`imports`,`exports`],Ye={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(!fe(t))continue;let n=t.getDecorator(`Module`);if(!n)continue;let r=n.getArguments()[0];if(!r||r.getKind()!==u.ObjectLiteralExpression)continue;let i=r.asKind(u.ObjectLiteralExpression);if(i)for(let t of Je){let n=i.getProperty(t);if(!n)continue;let r=n.getChildrenOfKind(u.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)}}}}},Xe=new Set([`Get`,`Post`,`Put`,`Delete`,`Patch`,`All`,`Head`,`Options`]),Ze={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(!Xe.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())}}}},Qe={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(u.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 $e(e){let t=e.getReturnType().getText();return t.startsWith(`Promise<`)||t===`Promise`?!0:t===`any`||t===`error`?`unknown`:!1}const H=new Set([`save`,`create`,`insert`,`update`,`delete`,`remove`,`send`,`emit`,`publish`,`dispatch`,`execute`,`fetch`,`load`,`upload`,`download`,`process`]),et={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(T(n))continue;let t=n.getBody();if(!t)continue;let r=t.getDescendantsOfKind(u.ExpressionStatement);for(let t of r){let r=t.getExpression();if(r.getKind()===u.VoidExpression||r.getKind()===u.AwaitExpression||r.getKind()!==u.CallExpression)continue;let i=r.asKind(u.CallExpression);if(!i)continue;let a=i.getExpression().getText().split(`.`).pop()??``,o=$e(i);if(o!==!1){if(o===`unknown`){let e=a.toLowerCase();if(!(H.has(e)||[...H].some(t=>e.startsWith(t)&&e!==t)))continue}t.getFirstAncestorByKind(u.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())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}))}},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.`},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}))}}},rt={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})}}}},it={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}))}}},at={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}))}}},ot={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}))}}},st={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(!(C(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})}}}}},ct={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(!w(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})}}}},lt={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=lt[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}))}}}},dt=new Set([u.ForStatement,u.ForOfStatement,u.ForInStatement,u.WhileStatement,u.DoStatement,u.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(dt.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}}}}},pt={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(u.CallExpression);for(let n of t){if(n.getExpression().getText()!==`require`)continue;let t=n.getArguments();t.length!==0&&t[0].getKind()!==u.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})}}},mt={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}))}},ht={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(u.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})}},gt=new Set([`readFileSync`,`writeFileSync`,`existsSync`,`mkdirSync`,`readdirSync`,`statSync`,`accessSync`,`appendFileSync`,`copyFileSync`,`renameSync`,`unlinkSync`]),_t={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(u.CallExpression);for(let n of t){let t=n.getExpression().getText().split(`.`).pop()??``;gt.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})}}},yt=[`Guard`,`Interceptor`,`Filter`,`Middleware`,`Strategy`],bt={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})}}},xt={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})}}},St={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})}},Ct=/delete/i;function wt(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&&!Ct.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 Tt={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())wt(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})}},Et={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(u.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})}}},Dt={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(u.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})}}}},Ot={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(u.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(u.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})}},kt={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(u.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})}}},At=/^(error|err|e|ex|exception)$/,jt={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(u.PropertyAccessExpression);for(let n of t){if(n.getName()!==`stack`)continue;let t=n.getExpression().getText();if(!(At.test(t)||t.endsWith(`.error`)||t.endsWith(`.err`)))continue;let r=n.getParent();if(!r)continue;let i=r.getKind();(i===u.ReturnStatement||i===u.PropertyAssignment||i===u.ShorthandPropertyAssignment||i===u.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})}}},Mt=[{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)`}],Nt=[/secret/i,/password/i,/passwd/i,/api[_-]?key/i,/auth[_-]?token/i,/private[_-]?key/i,/access[_-]?key/i,/client[_-]?secret/i],Pt=new Set([`your-secret-here`,`changeme`,`password`]),Ft=/^[A-Za-z][A-Za-z0-9_]*(\.[A-Za-z][A-Za-z0-9_]*)+$/,U=new Set([`cursor`,`nextCursor`,`prevCursor`,`previousCursor`,`startCursor`,`endCursor`,`pageToken`,`nextPageToken`,`continuationToken`,`continuation`,`nextPage`,`afterCursor`,`beforeCursor`]);function W(e){return!(e.length<8||e.includes("${")||e.startsWith(`process.env`)||Pt.has(e)||e.includes(` `)||Ft.test(e))}function G(e){return Nt.some(t=>t.test(e))}function It(e){try{let t=Buffer.from(e,`base64`).toString(`utf-8`);return JSON.parse(t),!0}catch{return!1}}function Lt(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 Rt=new Set([...`aeiouyAEIOUY`]),zt=/^[A-Z]{2,4}_/,Bt=/(?<=[a-z])(?=[A-Z])|(?<=[A-Za-z])(?=\d)|(?<=\d)(?=[A-Za-z])|_/,Vt=/[a-zA-Z]/;function Ht(e){let t=e.includes(`_`),n=e.split(Bt).filter(e=>e.length>0).filter(e=>Vt.test(e)),r=n.filter(e=>e.length>=4&&[...e].some(e=>Rt.has(e)));return n.slice(0,6).filter(e=>e.length>=4&&[...e].some(e=>Rt.has(e))).length>=2||t&&e.split(`_`).filter(e=>e.length>=3).length>=2||zt.test(e)?!0:(Lt(e)>4.9&&!t&&r.length,!1)}function Ut(e){let t=e.getParent();if(!t)return!1;let n=t.asKind(u.PropertyAssignment);if(n)return U.has(n.getName());let r=t.asKind(u.VariableDeclaration);return r?U.has(r.getName()):!1}const Wt={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(u.StringLiteral);for(let n of t){let t=n.getLiteralValue();if(!(t.length<16)&&n.getParent()?.getKind()!==u.ImportDeclaration){for(let{pattern:r,name:i}of Mt)if(r.test(t)){if(i===`Base64 key`&&(It(t)||Ut(n)||Ht(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(u.VariableDeclaration);for(let t of n){let n=t.getName(),r=t.getInitializer();!r||r.getKind()!==u.StringLiteral||G(n)&&W(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(u.PropertyAssignment);for(let t of r){let n=t.getName(),r=t.getInitializer();!r||r.getKind()!==u.StringLiteral||G(n)&&W(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})}}},Gt=RegExp(`(?:^|[^a-zA-Z])\\w*(?:${[`Entity`,`Model`].join(`|`)})(?:[^a-zA-Z]|$)`),Kt={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(!T(n))continue;let t=n.getReturnType().getText();Gt.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})}}},qt={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(u.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})}}},Jt=new Set([`md5`,`sha1`]),Yt=[he,Ve,je,Ie,De,Ue,We,Ke,de,Ce,st,ut,Qe,Ze,nt,ot,tt,it,qe,Ye,at,ct,et,rt,Wt,Ot,{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(u.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()!==u.StringLiteral)continue;let i=r.getText().slice(1,-1).toLowerCase();Jt.has(i)&&e.report({filePath:e.filePath,message:`Weak hashing algorithm '${i}' used in createHash().`,help:this.meta.help,line:n.getStartLineNumber(),column:1})}}},kt,Et,jt,Dt,qt,Kt,_t,ft,pt,ht,bt,vt,mt,St,Tt,xt];function Xt(){return[...Yt]}function Zt(e){return e.meta.scope===`project`}function Qt(e){return e.meta.scope===`schema`}function $t(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 en(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 tn(e){let t=[],n=[],r=[];for(let i of e)Qt(i)?r.push(i):Zt(i)?n.push(i):t.push(i);return{fileRules:t,projectRules:n,schemaRules:r}}const nn=new Set([`security`,`performance`,`correctness`,`architecture`]),rn=new Set([`error`,`warning`,`info`]),an=new Set([`file`,`project`]),on=`custom/`;function sn(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`||!nn.has(n.category)||!rn.has(n.severity)||n.scope!==void 0&&!an.has(n.scope))}function cn(e){return e.meta.id.startsWith(on)?e:{...e,meta:{...e.meta,id:`${on}${e.meta.id}`}}}async function ln(t,i){let a=[],s=[],c=o(i,t);if(!e(c))return s.push(`Custom rules directory not found: ${c}`),{rules:a,warnings:s};if(!r(c).isDirectory())return s.push(`Custom rules path is not a directory: ${c}`),{rules:a,warnings:s};let l;try{l=n(c)}catch(e){return s.push(`Failed to read custom rules directory: ${e instanceof Error?e.message:String(e)}`),{rules:a,warnings:s}}let u=l.filter(e=>e.endsWith(`.ts`));if(u.length===0)return s.push(`No rule files (.ts) found in: ${c}`),{rules:a,warnings:s};let d=f(c,{interopDefault:!0});for(let e of u){let t=o(c,e),n;try{n=await d.import(t)}catch(t){s.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))sn(i)?(a.push(cn(i)),r=!0):t!==`__esModule`&&typeof i==`object`&&i&&`meta`in i&&s.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))||s.push(`No valid rule exports found in "${e}"`))}return{rules:a,warnings:s}}function un(e,t){return e.customRulesDir?ln(e.customRulesDir,t):Promise.resolve({rules:[],warnings:[]})}async function K(e,t){let n=await oe(e,t),{rules:r,warnings:i}=await un(n,e),a=$t(Yt,r,i),{fileRules:o,projectRules:s,schemaRules:c}=tn(en(n,a));return{combinedRules:a,config:n,customRuleWarnings:i,fileRules:o,projectRules:s,schemaRules:c}}async function dn(e,t={}){return(await p(t.include??y.include,{cwd:e,absolute:!0,ignore:t.exclude??y.exclude})).sort()}async function fn(e,t,n={}){let r=await Promise.all([...t.projects.entries()].map(async([t,r])=>[t,await dn(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 l({compilerOptions:{strict:!0,target:99,module:99,skipFileDependencyResolution:!0},skipAddingFilesFromTsConfig:!0});for(let n of e)t.addSourceFileAtPath(n);return t}const mn=/import\([^)]+\)\.(\w+)/,hn=/^(\w+)</;function gn(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 yn(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 _n(e,t){let n=new Map;for(let r of t){let t=e.getSourceFile(r);if(t)for(let e of gn(t,r))n.set(e.name,e)}return n}function vn(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 gn(r,n))e.set(t.name,t)}function yn(e){let t=e.match(mn);if(t)return t[1];let n=e.match(hn);return n?n[1]:e}const bn=new Set([`pgTable`,`mysqlTable`,`sqliteTable`]),xn=new Set([`serial`,`bigserial`,`smallserial`]),Sn=/=>\s*(\w+)/;function Cn(e){let t={type:`unknown`,isPrimary:!1,isNullable:!0,isGenerated:!1,isUnique:!1};function n(e){if(e.getKind()===u.CallExpression){let r=e.asKindOrThrow(u.CallExpression),i=r.getExpression();if(i.getKind()===u.PropertyAccessExpression){let e=i.asKindOrThrow(u.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=Sn.exec(n);if(r&&(t.reference={toEntity:r[1]}),e.length>1){let n=e[1];if(n.getKind()===u.ObjectLiteralExpression){let e=n.asKindOrThrow(u.ObjectLiteralExpression);for(let n of e.getProperties())if(n.getKind()===u.PropertyAssignment){let e=n.asKindOrThrow(u.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()===u.Identifier){let e=i.getText();t.type=e,xn.has(e)&&(t.isGenerated=!0)}}}return n(e),t}function wn(e){let t=[];for(let n of e.getProperties()){if(n.getKind()!==u.PropertyAssignment)continue;let e=n.asKindOrThrow(u.PropertyAssignment),r=e.getName(),i=e.getInitializer();if(!i)continue;let a=Cn(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 Tn(e,t){let n=[];for(let r of e.getProperties()){if(r.getKind()!==u.PropertyAssignment)continue;let e=r.asKindOrThrow(u.PropertyAssignment),i=e.getName(),a=e.getInitializer();if(!a)continue;let o=Cn(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 En(e){let t=[],n=e.getDescendantsOfKind(u.CallExpression);for(let e of n){let n=e.getExpression();if(n.getKind()!==u.PropertyAccessExpression||n.asKindOrThrow(u.PropertyAccessExpression).getName()!==`on`)continue;let r=[];for(let t of e.getArguments())if(t.getKind()===u.PropertyAccessExpression){let e=t.asKindOrThrow(u.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 Dn(e){let t=[],n=e.getFilePath();for(let r of e.getDescendantsOfKind(u.VariableDeclaration)){let e=r.getInitializer();if(!e||e.getKind()!==u.CallExpression)continue;let i=e.asKindOrThrow(u.CallExpression),a=i.getExpression();if(a.getKind()!==u.Identifier)continue;let o=a.getText();if(!bn.has(o))continue;let s=i.getArguments();if(s.length<2)continue;let c=s[0],l=r.getName();c.getKind()===u.StringLiteral&&(l=c.asKindOrThrow(u.StringLiteral).getLiteralValue());let d=s[1];if(d.getKind()!==u.ObjectLiteralExpression)continue;let f=d.asKindOrThrow(u.ObjectLiteralExpression),p=r.getName(),m=wn(f),h=Tn(f,p),g;if(s.length>=3&&(g=En(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 On={supportsIncrementalUpdate:!0,extract(e,t){let n=[];for(let r of t){let t=e.getSourceFile(r);t&&n.push(...Dn(t))}return n}},kn=/^model\s+(\w+)\s*\{/,An=/^enum\s+(\w+)\s*\{/,jn=/^(\w+)\s+(\w+)(\?)?(\[\])?(.*)$/,Mn=/@(\w+)(\((?:[^()]*|\([^()]*\))*\))?/g,Nn=/@default\(((?:[^()]*|\([^()]*\))*)\)/,Pn=/^@@map\(\s*"([^"]+)"\s*\)/;function Fn(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 In(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(`
|
|
3
|
-
`),o=null,s=[],c=[],l=[],u;for(let e of a){let t=e.trim(),a=kn.exec(t);if(a){o={type:`model`,name:a[1]},s=[],c=[],l=[],u=void 0;continue}let d=An.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=Rn.exec(t);e&&(l=e[1].split(`,`).map(e=>e.trim()));let n=zn(t);n&&c.push(n);let r=Pn.exec(t);r&&(u=r[1]);continue}let e=Bn(t);e&&s.push(e)}}}return{models:n,enums:r}}const Ln=/^@@(index|unique)\(\[([^\]]*)\]\)/,Rn=/^@@id\(\[([^\]]*)\]\)/;function zn(e){let t=Ln.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 Bn(e){let t=jn.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(Mn.source,Mn.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 Vn(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=Nn.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 Hn=/onDelete:\s*(\w+)/;function Un(e){let t=e.attributes.find(e=>e.startsWith(`@relation`));if(!t)return;let n=Hn.exec(t);return n?n[1]:void 0}function Wn(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=Un(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=Vn(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 Gn={supportsIncrementalUpdate:!1,extract(e,t,n){let r=Fn(n);if(r.length===0)return[];let{models:i,enums:a}=In(r);return Wn(i,a)}},Kn=/=>\s*(\w+)/,qn=new Set([`Column`,`PrimaryColumn`,`PrimaryGeneratedColumn`,`CreateDateColumn`,`UpdateDateColumn`,`DeleteDateColumn`,`VersionColumn`]),Jn={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()===u.ObjectLiteralExpression){let t={},n=e.asKind(u.ObjectLiteralExpression);if(!n)continue;for(let e of n.getProperties())if(e.getKind()===u.PropertyAssignment){let n=e.asKind(u.PropertyAssignment);n&&(t[n.getName()]=n.getInitializer()?.getText()??``)}return t}return null}function Yn(e){let t=e.getArguments();if(t.length===0)return null;let n=t[0];return n.getKind()===u.StringLiteral?n.asKind(u.StringLiteral)?.getLiteralValue()??null:null}function Xn(e){let t=e.getDecorator(`Entity`);if(!t)return e.getName()??`UnknownEntity`;let n=Yn(t);if(n)return n;let r=q(t);return r?.name?r.name.replace(/['"]/g,``):e.getName()??`UnknownEntity`}function Zn(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=Yn(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 Qn(e,t,n){let r=Jn[n.getName()];if(!r)return null;let i=n.getArguments();if(i.length===0)return null;let a=i[0].getText(),o=Kn.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 $n(e){if(!x(e,`Entity`))return null;let t=e.getName();if(!t)return null;let n=Xn(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()===u.ArrayLiteralExpression){let e=n.asKind(u.ArrayLiteralExpression);if(e){let n=e.getElements().map(e=>e.getKind()===u.StringLiteral?e.asKind(u.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(qn.has(r)){let t=Zn(e,n);c&&(t.hasIndex=!0),i.push(t);break}if(r in Jn){let r=Qn(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 er={prisma:Gn,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=$n(e);t&&n.push(t)}}return n}},drizzle:On};function J(e,t,n,r){let i={entities:new Map,relations:[],orm:n??`unknown`};if(!n)return i;let a=er[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 tr(e){return{entities:[...e.entities.values()],relations:e.relations,orm:e.orm}}function nr(e,t,n,r){for(let[t,r]of e.entities)r.filePath===n&&e.entities.delete(t);let i=er[e.orm];if(!i?.supportsIncrementalUpdate){rr(e);return}let a=i.extract(t,[n],r);for(let t of a)e.entities.set(t.name,t);rr(e)}function rr(e){let t=[];for(let n of e.entities.values())t.push(...n.relations);e.relations=t}async function Y(e,t){let{config:n,fileRules:r,projectRules:i,schemaRules:a}=t,[o,s]=await Promise.all([dn(e,n),te(e)]),c=pn(o),l=E(e);return{astProject:c,config:n,fileRules:r,files:o,moduleGraph:k(c,o,l),pathAliases:l,project:s,projectRules:i,providers:_n(c,o),schemaGraph:J(c,o,s.orm,e),schemaRules:a,targetPath:e}}async function ir(e,t={}){let n=await K(e,t.config);return{context:await Y(e,n),customRuleWarnings:n.customRuleWarnings}}function ar(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),L(e.moduleGraph,e.astProject,t,e.pathAliases),vn(e.providers,e.astProject,t),e.schemaGraph&&nr(e.schemaGraph,e.astProject,t,e.targetPath)}async function or(e,t,n){let{config:r,combinedRules:i}=t,o=await fn(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([te(s),le(s,r)]),u=pn(o),d=E(s),f=k(u,o,d),p=_n(u,o),m=J(u,o,c.orm,s),{fileRules:h,projectRules:g,schemaRules:_}=tn(en(l,i));return[t,{astProject:u,config:l,fileRules:h,files:o,moduleGraph:f,pathAliases:d,project:c,projectRules:g,providers:p,schemaGraph:m,schemaRules:_,targetPath:s}]}));return{subProjects:new Map(s)}}const sr=e=>m.makeRe(e,{windows:!1}),cr=/\\/g,lr=/\/$/,ur=(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(sr):[];if(r.size===0&&i.length===0)return e;let a=n.replace(cr,`/`).replace(lr,``);return e.filter(e=>{if(r.has(e.rule))return!1;let t=e.filePath.replace(cr,`/`),n=t.startsWith(`${a}/`)?t.slice(a.length+1):t;return!i.some(e=>e.test(n))})};function dr(e,t,n,r){let i=[],a=[],o=e.getSourceFile(t);if(!o)return{diagnostics:i,errors:a};let s=o.getFullText().split(`
|
|
4
|
-
`);for(let e of
|
|
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 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 Se(n[`nestjs-doctor`])}catch{}return{...y}}async function x(e){let t=await c(e,`utf-8`);return Se(JSON.parse(t))}function Se(e){return{...y,...e,exclude:[...y.exclude??[],...e.exclude??[]]}}async function Ce(e,t){try{return await b(e)}catch{return t}}const we=[/Repository$/,/\.repository$/,/\.entity$/,/\.schema$/,/\.guard$/,/\.interceptor$/,/\.pipe$/,/\.filter$/,/\.strategy$/],Te={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){we.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})}}}}},S=new Set([`Get`,`Post`,`Put`,`Patch`,`Delete`,`Head`,`Options`,`All`]);function C(e,t){return e.getDecorator(t)!==void 0}function w(e){return C(e,`Controller`)}function Ee(e){return C(e,`Injectable`)}function De(e){return C(e,`Injectable`)||C(e,`Controller`)||C(e,`Resolver`)||C(e,`WebSocketGateway`)}function Oe(e){return C(e,`Module`)}function T(e){return e.getDecorators().some(e=>S.has(e.getName()))}const ke=new Set([`TsRestHandler`,`GrpcMethod`,`GrpcStreamMethod`]);function Ae(e){return e.getDecorators().some(e=>ke.has(e.getName()))}const je={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(w(t))for(let n of t.getMethods()){if(!n.getDecorators().some(e=>S.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 Me(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 Ne(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 Pe=/=>\s*(\w+)/,Fe=/\.js$/;function Ie(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=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 E(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 Ie(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 Le=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(f.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===f.ArrayLiteralExpression){let i=e.asKindOrThrow(f.ArrayLiteralExpression),a=[];for(let e of i.getElements())a.push(...Re(e,t,n,r));return a}return i===f.CallExpression?k(e.asKindOrThrow(f.CallExpression),t,n,r):i===f.Identifier?M(e.getText(),t,n+1,r):[]}function Re(e,t,n,r){let i=e.getText();if(i.startsWith(`forwardRef`)){let e=i.match(Pe);return e?[e[1]]:[i]}let a=e.getKind();return a===f.SpreadElement?O(e.asKindOrThrow(f.SpreadElement).getExpression(),t,n,r):a===f.CallExpression?k(e.asKindOrThrow(f.CallExpression),t,n,r):a===f.PropertyAccessExpression?[e.asKindOrThrow(f.PropertyAccessExpression).getExpression().getText()]:(f.Identifier,[i])}function k(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=O(a.getExpression(),t,n,r),o=[];for(let i of e.getArguments())o.push(...O(i,t,n,r));return[...i,...o]}return Le.has(o),[a.getExpression().getText()]}return i.getKind()===f.Identifier?N(i.getText(),t,n+1,r):[]}function A(e,t,n){if(!e.startsWith(`.`)){let r=Ne(e,n);if(!r)return;let i=t.getProject(),a=[`${r}.ts`,`${r}/index.ts`,r,r.replace(Fe,`.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(Fe,`.ts`)];for(let e of o){let t=a.getSourceFile(e);if(t)return t}}function j(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=A(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=A(r.getModuleSpecifierValue(),t,n);return e?{sourceFile:e,localName:i.getName()}:void 0}}}function M(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 O(e,t,n,r)}}let i=j(e,t,r);return i?M(i.localName,i.sourceFile,n+1,r):[]}function ze(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 O(o,t,n,r);let s=[];for(let e of o.getDescendantsOfKind(f.ReturnStatement)){let i=e.getExpression();i&&s.push(...O(i,t,n,r))}return s}}}function N(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(...O(i,t,n,r))}return o}let i=ze(e,t,n,r);if(i)return i;let a=j(e,t,r);return a?N(a.localName,a.sourceFile,n+1,r):[]}function P(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=Ie(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 Be(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 F(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 I=`Break the cycle by extracting shared logic into a separate module or using forwardRef().`;function Ve(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=F(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 I;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=F(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 He={meta:{id:`architecture/no-circular-module-deps`,category:`architecture`,severity:`error`,description:`Circular dependencies in @Module() import graph`,help:I,scope:`project`},check(e){let t=Be(e.moduleGraph);for(let n of t){let t=n.join(` -> `),r=e.moduleGraph.modules.get(n[0]),i=Ve(n,e);e.report({filePath:r?.filePath??`unknown`,message:`Circular module dependency detected: ${t}`,help:i,line:r?.classDeclaration.getStartLineNumber()??1,column:1})}}},Ue=[`Service`,`Repository`,`Gateway`,`Resolver`],We=[`Guard`,`Interceptor`,`Pipe`,`Filter`];function L(e){return typeof e==`object`&&!!e}function Ge(e){if(!L(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(!L(n))return new Set;let r=n.excludeClasses;return Array.isArray(r)?new Set(r.filter(e=>typeof e==`string`)):new Set}const Ke={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=Ge(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=Ue.some(e=>n.endsWith(e)),o=We.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})}}}},qe=/\.(\w+)$/,Je=/^(\w+)</,Ye=new Set([`PrismaService`,`PrismaClient`,`EntityManager`,`DataSource`,`Repository`,`Connection`,`MongooseModel`,`InjectModel`,`InjectRepository`,`MikroORM`,`DrizzleService`]),Xe={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(!w(t))continue;let n=t.getConstructors()[0];if(n){for(let t of n.getParameters()){let n=Ze(t.getType().getText());if(Ye.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 Ze(e){let t=e.match(qe);if(t)return t[1];let n=e.match(Je);return n?n[1]:e}const Qe=/\.(\w+)$/,$e=/^(\w+)</,et=new Set([`PrismaService`,`PrismaClient`,`EntityManager`,`DataSource`,`Connection`,`MikroORM`]),tt={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(!Ee(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=nt(t.getType().getText());if(et.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 nt(e){let t=e.match(Qe);if(t)return t[1];let n=e.match($e);return n?n[1]:e}const rt=/\.(\w+)$/,it=/^(\w+)</,at=[/Repository$/,/Repo$/],ot={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(!w(t))continue;let n=t.getConstructors()[0];if(n){for(let t of n.getParameters()){let n=st(t.getType().getText());if(at.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 st(e){let t=e.match(rt);if(t)return t[1];let n=e.match(it);return n?n[1]:e}const ct={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})}}},lt={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(De(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})}},ut=[`/repositories/`,`/entities/`,`/dto/`,`/guards/`,`/interceptors/`,`/pipes/`,`/strategies/`],dt={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(`../`)&&ut.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 R(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 ft={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()||w(t)&&T(n)||Ae(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();R(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`;R(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})}}}},pt=[`providers`,`controllers`,`imports`,`exports`],mt={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(!Oe(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 pt){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)}}}}},ht=new Set([`Get`,`Post`,`Put`,`Delete`,`Patch`,`All`,`Head`,`Options`]),gt={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(!w(t))continue;let n=new Map;for(let r of t.getMethods())for(let t of r.getDecorators()){let i=t.getName();if(!ht.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())}}}},_t={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(w(t))for(let n of t.getMethods()){if(!n.getDecorators().some(e=>S.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 vt(e){let t=e.getReturnType().getText();return t.startsWith(`Promise<`)||t===`Promise`?!0:t===`any`||t===`error`?`unknown`:!1}const z=new Set([`save`,`create`,`insert`,`update`,`delete`,`remove`,`send`,`emit`,`publish`,`dispatch`,`execute`,`fetch`,`load`,`upload`,`download`,`process`]),yt={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(T(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=vt(i);if(o!==!1){if(o===`unknown`){let e=a.toLowerCase();if(!(z.has(e)||[...z].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})}}}}},bt={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())C(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}))}},xt={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`)&&C(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}))}}},St={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})}}}},Ct={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`)&&C(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}))}}},wt={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`||C(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}))}}},Tt={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`)&&C(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}))}}},Et={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(!(Ee(t)||w(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})}}}}},Dt={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(!De(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})}}}},Ot={onModuleInit:`OnModuleInit`,onModuleDestroy:`OnModuleDestroy`,onApplicationBootstrap:`OnApplicationBootstrap`,onApplicationShutdown:`OnApplicationShutdown`,beforeApplicationShutdown:`BeforeApplicationShutdown`},kt={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=Ot[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}))}}}},At=new Set([f.ForStatement,f.ForOfStatement,f.ForInStatement,f.WhileStatement,f.DoStatement,f.AwaitExpression]),jt={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(!(C(t,`Injectable`)||C(t,`Controller`)))continue;let n=t.getConstructors()[0];if(!n)continue;let r=n.getBody();if(r){for(let i of r.getDescendants())if(At.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}}}}},Mt={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})}}},Nt={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}))}},Pt={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})}},Ft=new Set([`readFileSync`,`writeFileSync`,`existsSync`,`mkdirSync`,`readdirSync`,`statSync`,`accessSync`,`appendFileSync`,`copyFileSync`,`renameSync`,`unlinkSync`]),It={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()??``;Ft.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})}}},Lt={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})}}},Rt=[`Guard`,`Interceptor`,`Filter`,`Middleware`,`Strategy`],zt={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(Rt.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})}}},Bt={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})}}},Vt={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})}},Ht=/delete/i;function Ut(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&&!Ht.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 Wt={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())Ut(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})}},Gt={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})}}},Kt={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(w(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})}}}},qt={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})}},Jt={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(!(C(t,`Injectable`)||C(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})}}},Yt=/^(error|err|e|ex|exception)$/,Xt={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(!(Yt.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})}}},Zt=[{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)`}],Qt=[/secret/i,/password/i,/passwd/i,/api[_-]?key/i,/auth[_-]?token/i,/private[_-]?key/i,/access[_-]?key/i,/client[_-]?secret/i],$t=new Set([`your-secret-here`,`changeme`,`password`]),en=/^[A-Za-z][A-Za-z0-9_]*(\.[A-Za-z][A-Za-z0-9_]*)+$/,B=new Set([`cursor`,`nextCursor`,`prevCursor`,`previousCursor`,`startCursor`,`endCursor`,`pageToken`,`nextPageToken`,`continuationToken`,`continuation`,`nextPage`,`afterCursor`,`beforeCursor`]);function V(e){return!(e.length<8||e.includes("${")||e.startsWith(`process.env`)||$t.has(e)||e.includes(` `)||en.test(e))}function H(e){return Qt.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 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 U=new Set([...`aeiouyAEIOUY`]),rn=/^[A-Z]{2,4}_/,an=/(?<=[a-z])(?=[A-Z])|(?<=[A-Za-z])(?=\d)|(?<=\d)(?=[A-Za-z])|_/,on=/[a-zA-Z]/;function sn(e){let t=e.includes(`_`),n=e.split(an).filter(e=>e.length>0).filter(e=>on.test(e)),r=n.filter(e=>e.length>=4&&[...e].some(e=>U.has(e)));return n.slice(0,6).filter(e=>e.length>=4&&[...e].some(e=>U.has(e))).length>=2||t&&e.split(`_`).filter(e=>e.length>=3).length>=2||rn.test(e)?!0:(nn(e)>4.9&&!t&&r.length,!1)}function cn(e){let t=e.getParent();if(!t)return!1;let n=t.asKind(f.PropertyAssignment);if(n)return B.has(n.getName());let r=t.asKind(f.VariableDeclaration);return r?B.has(r.getName()):!1}const ln={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 Zt)if(r.test(t)){if(i===`Base64 key`&&(tn(t)||cn(n)||sn(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||H(n)&&V(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||H(n)&&V(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})}}},un=RegExp(`(?:^|[^a-zA-Z])\\w*(?:${[`Entity`,`Model`].join(`|`)})(?:[^a-zA-Z]|$)`),dn={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(w(t))for(let n of t.getMethods()){if(!T(n))continue;let t=n.getReturnType().getText();un.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})}}},fn={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})}}},pn=new Set([`md5`,`sha1`]),W=[je,ot,Xe,tt,Ke,ct,lt,dt,Te,He,Et,kt,_t,gt,xt,Tt,bt,Ct,ft,mt,wt,Dt,yt,St,ln,qt,{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();pn.has(i)&&e.report({filePath:e.filePath,message:`Weak hashing algorithm '${i}' used in createHash().`,help:this.meta.help,line:n.getStartLineNumber(),column:1})}}},Jt,Gt,Xt,Kt,fn,dn,It,jt,Mt,Pt,zt,Lt,Nt,Vt,Wt,Bt];function mn(){return[...W]}function hn(e){return e.meta.scope===`project`}function gn(e){return e.meta.scope===`schema`}function _n(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 G(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 vn(e){let t=[],n=[],r=[];for(let i of e)gn(i)?r.push(i):hn(i)?n.push(i):t.push(i);return{fileRules:t,projectRules:n,schemaRules:r}}const yn=new Set([`security`,`performance`,`correctness`,`architecture`]),bn=new Set([`error`,`warning`,`info`]),xn=new Set([`file`,`project`]),Sn=`custom/`;function Cn(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`||!yn.has(n.category)||!bn.has(n.severity)||n.scope!==void 0&&!xn.has(n.scope))}function wn(e){return e.meta.id.startsWith(Sn)?e:{...e,meta:{...e.meta,id:`${Sn}${e.meta.id}`}}}async function Tn(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))Cn(i)?(a.push(wn(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 En(e,t){return e.customRulesDir?Tn(e.customRulesDir,t):Promise.resolve({rules:[],warnings:[]})}async function K(e,t){let n=await b(e,t),{rules:r,warnings:i}=await En(n,e),a=_n(W,r,i),{fileRules:o,projectRules:s,schemaRules:c}=vn(G(n,a));return{combinedRules:a,config:n,customRuleWarnings:i,fileRules:o,projectRules:s,schemaRules:c}}async function Dn(e,t={}){return(await l(t.include??y.include,{cwd:e,absolute:!0,ignore:t.exclude??y.exclude})).sort()}async function On(e,t,n={}){let r=await Promise.all([...t.projects.entries()].map(async([t,r])=>[t,await Dn(a(e,r),n)])),i=new Map;for(let[e,t]of r)i.set(e,t);return i}function kn(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 An=/import\([^)]+\)\.(\w+)/,jn=/^(\w+)</;function Mn(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 Fn(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 Nn(e,t){let n=new Map;for(let r of t){let t=e.getSourceFile(r);if(t)for(let e of Mn(t,r))n.set(e.name,e)}return n}function Pn(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 Mn(r,n))e.set(t.name,t)}function Fn(e){let t=e.match(An);if(t)return t[1];let n=e.match(jn);return n?n[1]:e}const In=new Set([`pgTable`,`mysqlTable`,`sqliteTable`]),Ln=new Set([`serial`,`bigserial`,`smallserial`]),Rn=/=>\s*(\w+)/;function zn(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=Rn.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,Ln.has(e)&&(t.isGenerated=!0)}}}return n(e),t}function Bn(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=zn(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 Vn(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=zn(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 Hn(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 Un(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(!In.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=Bn(d),h=Vn(d,p),g;if(s.length>=3&&(g=Hn(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 Wn={supportsIncrementalUpdate:!0,extract(e,t){let n=[];for(let r of t){let t=e.getSourceFile(r);t&&n.push(...Un(t))}return n}},Gn=/^model\s+(\w+)\s*\{/,Kn=/^enum\s+(\w+)\s*\{/,qn=/^(\w+)\s+(\w+)(\?)?(\[\])?(.*)$/,Jn=/@(\w+)(\((?:[^()]*|\([^()]*\))*\))?/g,Yn=/@default\(((?:[^()]*|\([^()]*\))*)\)/,Xn=/^@@map\(\s*"([^"]+)"\s*\)/;function Zn(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 Qn(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=Gn.exec(t);if(a){o={type:`model`,name:a[1]},s=[],c=[],l=[],u=void 0;continue}let d=Kn.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=er.exec(t);e&&(l=e[1].split(`,`).map(e=>e.trim()));let n=tr(t);n&&c.push(n);let r=Xn.exec(t);r&&(u=r[1]);continue}let e=nr(t);e&&s.push(e)}}}return{models:n,enums:r}}const $n=/^@@(index|unique)\(\[([^\]]*)\]\)/,er=/^@@id\(\[([^\]]*)\]\)/;function tr(e){let t=$n.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 nr(e){let t=qn.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(Jn.source,Jn.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 rr(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=Yn.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 ir=/onDelete:\s*(\w+)/;function ar(e){let t=e.attributes.find(e=>e.startsWith(`@relation`));if(!t)return;let n=ir.exec(t);return n?n[1]:void 0}function or(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=ar(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=rr(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 sr={supportsIncrementalUpdate:!1,extract(e,t,n){let r=Zn(n);if(r.length===0)return[];let{models:i,enums:a}=Qn(r);return or(i,a)}},cr=/=>\s*(\w+)/,lr=new Set([`Column`,`PrimaryColumn`,`PrimaryGeneratedColumn`,`CreateDateColumn`,`UpdateDateColumn`,`DeleteDateColumn`,`VersionColumn`]),ur={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 dr(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 fr(e){let t=e.getDecorator(`Entity`);if(!t)return e.getName()??`UnknownEntity`;let n=dr(t);if(n)return n;let r=q(t);return r?.name?r.name.replace(/['"]/g,``):e.getName()??`UnknownEntity`}function pr(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=dr(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 mr(e,t,n){let r=ur[n.getName()];if(!r)return null;let i=n.getArguments();if(i.length===0)return null;let a=i[0].getText(),o=cr.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 hr(e){if(!C(e,`Entity`))return null;let t=e.getName();if(!t)return null;let n=fr(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(lr.has(r)){let t=pr(e,n);c&&(t.hasIndex=!0),i.push(t);break}if(r in ur){let r=mr(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 gr={prisma:sr,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=hr(e);t&&n.push(t)}}return n}},drizzle:Wn};function J(e,t,n,r){let i={entities:new Map,relations:[],orm:n??`unknown`};if(!n)return i;let a=gr[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 _r(e){return{entities:[...e.entities.values()],relations:e.relations,orm:e.orm}}function vr(e,t,n,r){for(let[t,r]of e.entities)r.filePath===n&&e.entities.delete(t);let i=gr[e.orm];if(!i?.supportsIncrementalUpdate){yr(e);return}let a=i.extract(t,[n],r);for(let t of a)e.entities.set(t.name,t);yr(e)}function yr(e){let t=[];for(let n of e.entities.values())t.push(...n.relations);e.relations=t}async function Y(e,t){let{config:n,fileRules:r,projectRules:i,schemaRules:a}=t,[o,s]=await Promise.all([Dn(e,n),_e(e)]),c=kn(o),l=Me(e);return{astProject:c,config:n,fileRules:r,files:o,moduleGraph:E(c,o,l),pathAliases:l,project:s,projectRules:i,providers:Nn(c,o),schemaGraph:J(c,o,s.orm,e),schemaRules:a,targetPath:e}}async function br(e,t={}){let n=await K(e,t.config);return{context:await Y(e,n),customRuleWarnings:n.customRuleWarnings}}function xr(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),P(e.moduleGraph,e.astProject,t,e.pathAliases),Pn(e.providers,e.astProject,t),e.schemaGraph&&vr(e.schemaGraph,e.astProject,t,e.targetPath)}async function Sr(e,t,n){let{config:r,combinedRules:i}=t,o=await On(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),Ce(s,r)]),u=kn(o),d=Me(s),f=E(u,o,d),p=Nn(u,o),m=J(u,o,c.orm,s),{fileRules:h,projectRules:g,schemaRules:ee}=vn(G(l,i));return[t,{astProject:u,config:l,fileRules:h,files:o,moduleGraph:f,pathAliases:d,project:c,projectRules:g,providers:p,schemaGraph:m,schemaRules:ee,targetPath:s}]}));return{subProjects:new Map(s)}}const Cr=e=>h.makeRe(e,{windows:!1}),wr=/\\/g,Tr=/\/$/,Er=(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(Cr):[];if(r.size===0&&i.length===0)return e;let a=n.replace(wr,`/`).replace(Tr,``);return e.filter(e=>{if(r.has(e.rule))return!1;let t=e.filePath.replace(wr,`/`),n=t.startsWith(`${a}/`)?t.slice(a.length+1):t;return!i.some(e=>e.test(n))})};function Dr(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 Or(e,t,n,r){let i=[],a=[];for(let o of t){let t=Dr(e,o,n,r);i.push(...t.diagnostics),a.push(...t.errors)}return{diagnostics:i,errors:a}}function kr(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 Ar(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 jr(e){return e instanceof Error?e.message:String(e)}function X(e,t,n){return{diagnostics:Er(e,n.config,n.targetPath),errors:t.map(e=>({ruleId:e.ruleId,error:jr(e.error)}))}}function Mr(e,t){let n=Or(e.astProject,[t],e.fileRules,e.config);return X(n.diagnostics,n.errors,e)}function Nr(e){let t=Or(e.astProject,e.files,e.fileRules,e.config);return X(t.diagnostics,t.errors,e)}function Z(e){let t={moduleGraph:e.moduleGraph,providers:e.providers,config:e.config},n=kr(e.astProject,e.files,e.projectRules,t),{diagnostics:r,errors:i}=X(n.diagnostics,n.errors,e),a=Pr(e);return r.push(...a.diagnostics),i.push(...a.errors),{diagnostics:r,errors:i}}function Pr(e){if(!e.schemaGraph||e.schemaRules.length===0||e.schemaGraph.entities.size===0)return{diagnostics:[],errors:[]};let t=Ar(e.schemaGraph,e.schemaRules);return X(t.diagnostics,t.errors,e)}function Q(e){let t=u.now(),n=Nr(e),r=Z(e),i=u.now()-t;return{diagnostics:[...n.diagnostics,...r.diagnostics],elapsedMs:i,ruleErrors:[...n.errors,...r.errors]}}function Fr(e){return e>=90?`Excellent`:e>=75?`Good`:e>=50?`Fair`:e>=25?`Poor`:`Critical`}const Ir={error:3,warning:1.5,info:.5},Lr={security:1.5,correctness:1.3,schema:1.1,architecture:1,performance:.8};function Rr(e,t){if(t===0)return{value:100,label:Fr(100)};let n=0;for(let t of e){let e=Ir[t.severity],r=Lr[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:Fr(i)}}function zr(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??J(e.astProject,e.files,e.project.orm,e.targetPath),s=Rr(r,e.files.length),c=zr(r);return{result:{score:s,diagnostics:r,project:{...e.project,fileCount:e.files.length,moduleCount:e.moduleGraph.modules.size},summary:c,ruleErrors:i,elapsedMs:a,schema:_r(o)},moduleGraph:e.moduleGraph,schemaGraph:o,customRuleWarnings:n,files:e.files,providers:e.providers}}function Br(e,t,n,r){let i=[],a=[],o=[],s=new Map,c=0,l=[],u=[],d=``;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),c+=e.result.project.fileCount,e.result.schema&&(l.push(...e.result.schema.entities),u.push(...e.result.schema.relations),e.result.schema.orm&&e.result.schema.orm!==`unknown`&&(d=e.result.schema.orm))}let f=Rr(a,c),p=zr(a);return{moduleGraphs:s,customRuleWarnings:n,result:{isMonorepo:!0,subProjects:i,combined:{score:f,diagnostics:a,project:{name:`monorepo`,nestVersion:i[0]?.result.project.nestVersion??null,orm:d||(i[0]?.result.project.orm??null),framework:i[0]?.result.project.framework??null,fileCount:c,moduleCount:i.reduce((e,t)=>e+t.result.project.moduleCount,0)},summary:p,ruleErrors:o,elapsedMs:r,schema:l.length>0?{entities:l,relations:u,orm:d||`unknown`}:void 0},elapsedMs:r}}}async function Vr(e,t,n){let r=u.now(),i=await Sr(e,t,n),a=new Map;for(let[e,t]of i.subProjects)a.set(e,Q(t));let o=u.now()-r;return Br(i,a,t.customRuleWarnings,o)}async function Hr(e,t={}){let n=await K(e,t.config),r=await ge(e);if(r)return{isMonorepo:!0,monorepo:await Vr(e,n,r)};let i=await Y(e,n);return{isMonorepo:!1,single:$(i,Q(i),n.customRuleWarnings)}}function Ur(e){return`line`in e}function Wr(e){return`entity`in e}function Gr(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 Kr(e,t={}){let n=Gr(e),r=await K(n,t.config),i=await Y(n,r),{result:a}=$(i,Q(i),r.customRuleWarnings);return a}async function qr(e,t={}){let n=Gr(e),r=await K(n,t.config),i=await ge(n);if(!i){let e=await Y(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 Vr(n,r,i);return a}export{ee as ConfigurationError,g as NestjsDoctorError,te as ScanError,_ as ValidationError,Hr as autoScan,Y as buildAnalysisContext,$ as buildResult,Nr as checkAllFiles,Mr as checkFile,Z as checkProject,Pr as checkSchema,Kr as diagnose,qr as diagnoseMonorepo,J as extractSchema,mn as getRules,Ur as isCodeDiagnostic,Wr as isSchemaDiagnostic,br as prepareAnalysis,K as resolveScanConfig,xr as updateFile,P as updateModuleGraphForFile,Pn as updateProvidersForFile};
|
package/dist/cli/index.mjs
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
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,resolve as o}from"node:path";import{performance as s}from"node:perf_hooks";import{Project as c,SyntaxKind as l,ts as u}from"ts-morph";import{existsSync as d,readFileSync as f,readdirSync as p,statSync as m}from"node:fs";import{createJiti as h}from"jiti";import{glob as g}from"tinyglobby";import _ from"picomatch";import v from"picocolors";import y from"ora";async function b(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}}async function x(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=ee(i[`@nestjs/core`]),s=te(i),c=ne(i);return{name:n.name??`unknown`,nestVersion:o,orm:s,framework:c,moduleCount:0,fileCount:0}}function ee(e){return e?e.replace(/[\^~>=<]/g,``):null}function te(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 ne(e){return e[`@nestjs/platform-fastify`]?`fastify`:e[`@nestjs/platform-express`]||e[`@nestjs/core`]?`express`:null}const re={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(`,`)},ie=[`nestjs-doctor.config.json`,`.nestjs-doctor.json`];async function ae(e,t){if(t)return oe(t);for(let t of ie)try{return await oe(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 se(n[`nestjs-doctor`])}catch{}return{...S}}async function oe(e){let t=await r(e,`utf-8`);return se(JSON.parse(t))}function se(e){return{...S,...e,exclude:[...S.exclude??[],...e.exclude??[]]}}async function ce(e,t){try{return await ae(e)}catch{return t}}const le=[/Repository$/,/\.repository$/,/\.entity$/,/\.schema$/,/\.guard$/,/\.interceptor$/,/\.pipe$/,/\.filter$/,/\.strategy$/],ue={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){le.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 de(e){return w(e,`Injectable`)}function fe(e){return w(e,`Injectable`)||w(e,`Controller`)||w(e,`Resolver`)||w(e,`WebSocketGateway`)}function pe(e){return w(e,`Module`)}function E(e){return e.getDecorators().some(e=>C.has(e.getName()))}const me=new Set([`TsRestHandler`,`GrpcMethod`,`GrpcStreamMethod`]);function he(e){return e.getDecorators().some(e=>me.has(e.getName()))}const ge={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(l.IfStatement),i=t.getDescendantsOfKind(l.ForStatement),a=t.getDescendantsOfKind(l.ForInStatement),o=t.getDescendantsOfKind(l.ForOfStatement),s=t.getDescendantsOfKind(l.WhileStatement),c=t.getDescendantsOfKind(l.SwitchStatement),u=i.length+a.length+o.length+s.length;(r.length>1||u>0||c.length>0)&&e.report({filePath:e.filePath,message:`Controller method '${n.getName()}' contains business logic (${r.length} if, ${u} loops, ${c.length} switch). Move to a service.`,help:this.meta.help,line:n.getStartLineNumber(),column:1});let d=t.getDescendantsOfKind(l.CallExpression).filter(e=>{let t=e.getExpression();if(t.getKind()===l.PropertyAccessExpression){let e=t.asKind(l.PropertyAccessExpression)?.getName();return e===`map`||e===`filter`||e===`reduce`||e===`sort`||e===`flatMap`}return!1});d.length>1&&e.report({filePath:e.filePath,message:`Controller method '${n.getName()}' contains data transformation logic (${d.length} array operations). Move to a service.`,help:this.meta.help,line:n.getStartLineNumber(),column:1})}}};function _e(e){let t=new Map;try{let n=u.findConfigFile(e,u.sys.fileExists,`tsconfig.json`);if(!n)return t;let{config:r,error:a}=u.readConfigFile(n,u.sys.readFile);if(a||!r)return t;let s=i(n),c=u.parseJsonConfigFileContent(r,u.sys,s),l=c.options.paths;if(!l)return t;let d=c.options.baseUrl??s;for(let[e,n]of Object.entries(l)){let r=n.map(e=>o(d,e));t.set(e,r)}}catch{return t}return t}function ve(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 ye=/=>\s*(\w+)/,D=/\.js$/;function be(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()===l.ObjectLiteralExpression){let e=o.asKind(l.ObjectLiteralExpression);e&&(s.imports=k(e,`imports`,n),s.exports=k(e,`exports`,n),s.providers=k(e,`providers`,n),s.controllers=k(e,`controllers`,n))}r.push(s)}return r}function O(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 be(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 xe=new Set([`forRoot`,`forRootAsync`,`forFeature`,`forFeatureAsync`,`forChild`,`forChildAsync`,`register`,`registerAsync`]);function k(e,t,n){let r=e.getProperty(t);if(!r)return[];let i=r.asKind(l.PropertyAssignment);if(!i)return[];let a=i.getInitializer();return a?A(a,e.getSourceFile(),0,n):[]}function A(e,t,n,r){if(n>5)return[];let i=e.getKind();if(i===l.ArrayLiteralExpression){let i=e.asKindOrThrow(l.ArrayLiteralExpression),a=[];for(let e of i.getElements())a.push(...Se(e,t,n,r));return a}return i===l.CallExpression?j(e.asKindOrThrow(l.CallExpression),t,n,r):i===l.Identifier?P(e.getText(),t,n+1,r):[]}function Se(e,t,n,r){let i=e.getText();if(i.startsWith(`forwardRef`)){let e=i.match(ye);return e?[e[1]]:[i]}let a=e.getKind();return a===l.SpreadElement?A(e.asKindOrThrow(l.SpreadElement).getExpression(),t,n,r):a===l.CallExpression?j(e.asKindOrThrow(l.CallExpression),t,n,r):a===l.PropertyAccessExpression?[e.asKindOrThrow(l.PropertyAccessExpression).getExpression().getText()]:(l.Identifier,[i])}function j(e,t,n,r){let i=e.getExpression();if(i.getKind()===l.PropertyAccessExpression){let a=i.asKindOrThrow(l.PropertyAccessExpression),o=a.getName();if(o===`concat`){let i=A(a.getExpression(),t,n,r),o=[];for(let i of e.getArguments())o.push(...A(i,t,n,r));return[...i,...o]}return xe.has(o),[a.getExpression().getText()]}return i.getKind()===l.Identifier?F(i.getText(),t,n+1,r):[]}function M(e,t,n){if(!e.startsWith(`.`)){let r=ve(e,n);if(!r)return;let i=t.getProject(),a=[`${r}.ts`,`${r}/index.ts`,r,r.replace(D,`.ts`)];for(let e of a){let t=i.getSourceFile(e);if(t)return t}return}let r=o(i(t.getFilePath()),e),a=t.getProject(),s=[`${r}.ts`,`${r}/index.ts`,r,r.replace(D,`.ts`)];for(let e of s){let t=a.getSourceFile(e);if(t)return t}}function N(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=M(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=M(r.getModuleSpecifierValue(),t,n);return e?{sourceFile:e,localName:i.getName()}:void 0}}}function P(e,t,n,r){if(n>5)return[];for(let i of t.getStatements()){if(i.getKind()!==l.VariableStatement)continue;let a=i.asKindOrThrow(l.VariableStatement);for(let i of a.getDeclarations())if(i.getName()===e){let e=i.getInitializer();if(e)return A(e,t,n,r)}}let i=N(e,t,r);return i?P(i.localName,i.sourceFile,n+1,r):[]}function Ce(e,t,n,r){for(let i of t.getStatements()){if(i.getKind()!==l.VariableStatement)continue;let a=i.asKindOrThrow(l.VariableStatement);for(let i of a.getDeclarations()){if(i.getName()!==e)continue;let a=i.getInitializer();if(!a||a.getKind()!==l.ArrowFunction)continue;let o=a.asKindOrThrow(l.ArrowFunction).getBody();if(o.getKind()!==l.Block)return A(o,t,n,r);let s=[];for(let e of o.getDescendantsOfKind(l.ReturnStatement)){let i=e.getExpression();i&&s.push(...A(i,t,n,r))}return s}}}function F(e,t,n,r){if(n>5)return[];for(let i of t.getStatements()){if(i.getKind()!==l.FunctionDeclaration)continue;let a=i.asKindOrThrow(l.FunctionDeclaration);if(a.getName()!==e)continue;let o=[];for(let e of a.getDescendantsOfKind(l.ReturnStatement)){let i=e.getExpression();i&&o.push(...A(i,t,n,r))}return o}let i=Ce(e,t,n,r);if(i)return i;let a=N(e,t,r);return a?F(a.localName,a.sourceFile,n+1,r):[]}function we(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 I(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 L(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 R=`Break the cycle by extracting shared logic into a separate module or using forwardRef().`;function Te(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=L(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 R;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=L(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 Ee={meta:{id:`architecture/no-circular-module-deps`,category:`architecture`,severity:`error`,description:`Circular dependencies in @Module() import graph`,help:R,scope:`project`},check(e){let t=I(e.moduleGraph);for(let n of t){let t=n.join(` -> `),r=e.moduleGraph.modules.get(n[0]),i=Te(n,e);e.report({filePath:r?.filePath??`unknown`,message:`Circular module dependency detected: ${t}`,help:i,line:r?.classDeclaration.getStartLineNumber()??1,column:1})}}},De=[`Service`,`Repository`,`Gateway`,`Resolver`],Oe=[`Guard`,`Interceptor`,`Pipe`,`Filter`];function ke(e){return typeof e==`object`&&!!e}function Ae(e){if(!ke(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(!ke(n))return new Set;let r=n.excludeClasses;return Array.isArray(r)?new Set(r.filter(e=>typeof e==`string`)):new Set}const je={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=Ae(e.config?.rules?.[this.meta.id]),n=e.sourceFile.getDescendantsOfKind(l.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=De.some(e=>n.endsWith(e)),o=Oe.some(e=>n.endsWith(e));if(a||o){if(o){if(r.getFirstAncestorByKind(l.Decorator))continue;let e=r.getFirstAncestorByKind(l.MethodDeclaration),t=r.getFirstAncestorByKind(l.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})}}}},Me=/\.(\w+)$/,Ne=/^(\w+)</,Pe=new Set([`PrismaService`,`PrismaClient`,`EntityManager`,`DataSource`,`Repository`,`Connection`,`MongooseModel`,`InjectModel`,`InjectRepository`,`MikroORM`,`DrizzleService`]),Fe={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=Ie(t.getType().getText());if(Pe.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 Ie(e){let t=e.match(Me);if(t)return t[1];let n=e.match(Ne);return n?n[1]:e}const Le=/\.(\w+)$/,Re=/^(\w+)</,ze=new Set([`PrismaService`,`PrismaClient`,`EntityManager`,`DataSource`,`Connection`,`MikroORM`]),Be={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(!de(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=Ve(t.getType().getText());if(ze.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 Ve(e){let t=e.match(Le);if(t)return t[1];let n=e.match(Re);return n?n[1]:e}const He=/\.(\w+)$/,Ue=/^(\w+)</,We=[/Repository$/,/Repo$/],Ge={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=Ke(t.getType().getText());if(We.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 Ke(e){let t=e.match(He);if(t)return t[1];let n=e.match(Ue);return n?n[1]:e}const qe={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(l.CallExpression);for(let n of t){let t=n.getExpression();if(t.getKind()!==l.PropertyAccessExpression)continue;let r=t.asKind(l.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})}}},Je={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(fe(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})}},Ye=[`/repositories/`,`/entities/`,`/dto/`,`/guards/`,`/interceptors/`,`/pipes/`,`/strategies/`],Xe={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(`../`)&&Ye.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 Ze(e){return e.getDescendantsOfKind(l.ReturnStatement).some(e=>{let t=e.getExpression();return!t||t.getKind()!==l.NewExpression?!1:t.asKindOrThrow(l.NewExpression).getExpression().getText()===`Promise`})}const Qe={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)||he(n))continue;let r=n.getBody();if(r&&r.getDescendantsOfKind(l.AwaitExpression).filter(e=>{let t=e.getParent();for(;t&&t!==r;){if(t.getKind()===l.ArrowFunction||t.getKind()===l.FunctionExpression||t.getKind()===l.FunctionDeclaration)return!1;t=t.getParent()}return!0}).length===0){let t=n.getName();Ze(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(l.AwaitExpression).filter(e=>{let t=e.getParent();for(;t&&t!==n;){if(t.getKind()===l.ArrowFunction||t.getKind()===l.FunctionExpression||t.getKind()===l.FunctionDeclaration)return!1;t=t.getParent()}return!0}).length===0){let r=t.getName()??`anonymous`;Ze(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})}}}},$e=[`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(!pe(t))continue;let n=t.getDecorator(`Module`);if(!n)continue;let r=n.getArguments()[0];if(!r||r.getKind()!==l.ObjectLiteralExpression)continue;let i=r.asKind(l.ObjectLiteralExpression);if(i)for(let t of $e){let n=i.getProperty(t);if(!n)continue;let r=n.getChildrenOfKind(l.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)}}}}},tt=new Set([`Get`,`Post`,`Put`,`Delete`,`Patch`,`All`,`Head`,`Options`]),nt={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(!tt.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())}}}},rt={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(l.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 it(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`]),ot={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(l.ExpressionStatement);for(let t of r){let r=t.getExpression();if(r.getKind()===l.VoidExpression||r.getKind()===l.AwaitExpression||r.getKind()!==l.CallExpression)continue;let i=r.asKind(l.CallExpression);if(!i)continue;let a=i.getExpression().getText().split(`.`).pop()??``,o=it(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(l.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})}}}}},st={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}))}},ct={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}))}}},lt={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})}}}},ut={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}))}}},dt={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}))}}},ft={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}))}}},pt={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(!(de(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})}}}}},mt={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(!fe(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`},gt={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.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}))}}}},_t=new Set([l.ForStatement,l.ForOfStatement,l.ForInStatement,l.WhileStatement,l.DoStatement,l.AwaitExpression]),vt={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(_t.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}}}}},yt={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(l.CallExpression);for(let n of t){if(n.getExpression().getText()!==`require`)continue;let t=n.getArguments();t.length!==0&&t[0].getKind()!==l.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})}}},bt={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}))}},xt={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(l.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})}},St=new Set([`readFileSync`,`writeFileSync`,`existsSync`,`mkdirSync`,`readdirSync`,`statSync`,`accessSync`,`appendFileSync`,`copyFileSync`,`renameSync`,`unlinkSync`]),Ct={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(l.CallExpression);for(let n of t){let t=n.getExpression().getText().split(`.`).pop()??``;St.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})}}},wt={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})}}},Tt=[`Guard`,`Interceptor`,`Filter`,`Middleware`,`Strategy`],Et={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(Tt.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})}}},Dt={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})}}},Ot={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 At(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())At(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})}},Mt={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(l.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})}}},Nt={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(l.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})}}}},Pt={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(l.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(l.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})}},Ft={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(l.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})}}},It=/^(error|err|e|ex|exception)$/,Lt={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(l.PropertyAccessExpression);for(let n of t){if(n.getName()!==`stack`)continue;let t=n.getExpression().getText();if(!(It.test(t)||t.endsWith(`.error`)||t.endsWith(`.err`)))continue;let r=n.getParent();if(!r)continue;let i=r.getKind();(i===l.ReturnStatement||i===l.PropertyAssignment||i===l.ShorthandPropertyAssignment||i===l.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})}}},Rt=[{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)`}],zt=[/secret/i,/password/i,/passwd/i,/api[_-]?key/i,/auth[_-]?token/i,/private[_-]?key/i,/access[_-]?key/i,/client[_-]?secret/i],Bt=new Set([`your-secret-here`,`changeme`,`password`]),Vt=/^[A-Za-z][A-Za-z0-9_]*(\.[A-Za-z][A-Za-z0-9_]*)+$/,Ht=new Set([`cursor`,`nextCursor`,`prevCursor`,`previousCursor`,`startCursor`,`endCursor`,`pageToken`,`nextPageToken`,`continuationToken`,`continuation`,`nextPage`,`afterCursor`,`beforeCursor`]);function Ut(e){return!(e.length<8||e.includes("${")||e.startsWith(`process.env`)||Bt.has(e)||e.includes(` `)||Vt.test(e))}function Wt(e){return zt.some(t=>t.test(e))}function Gt(e){try{let t=Buffer.from(e,`base64`).toString(`utf-8`);return JSON.parse(t),!0}catch{return!1}}function Kt(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 qt=new Set([...`aeiouyAEIOUY`]),Jt=/^[A-Z]{2,4}_/,Yt=/(?<=[a-z])(?=[A-Z])|(?<=[A-Za-z])(?=\d)|(?<=\d)(?=[A-Za-z])|_/,Xt=/[a-zA-Z]/;function Zt(e){let t=e.includes(`_`),n=e.split(Yt).filter(e=>e.length>0).filter(e=>Xt.test(e)),r=n.filter(e=>e.length>=4&&[...e].some(e=>qt.has(e)));return n.slice(0,6).filter(e=>e.length>=4&&[...e].some(e=>qt.has(e))).length>=2||t&&e.split(`_`).filter(e=>e.length>=3).length>=2||Jt.test(e)?!0:(Kt(e)>4.9&&!t&&r.length,!1)}function Qt(e){let t=e.getParent();if(!t)return!1;let n=t.asKind(l.PropertyAssignment);if(n)return Ht.has(n.getName());let r=t.asKind(l.VariableDeclaration);return r?Ht.has(r.getName()):!1}const $t={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(l.StringLiteral);for(let n of t){let t=n.getLiteralValue();if(!(t.length<16)&&n.getParent()?.getKind()!==l.ImportDeclaration){for(let{pattern:r,name:i}of Rt)if(r.test(t)){if(i===`Base64 key`&&(Gt(t)||Qt(n)||Zt(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(l.VariableDeclaration);for(let t of n){let n=t.getName(),r=t.getInitializer();!r||r.getKind()!==l.StringLiteral||Wt(n)&&Ut(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(l.PropertyAssignment);for(let t of r){let n=t.getName(),r=t.getInitializer();!r||r.getKind()!==l.StringLiteral||Wt(n)&&Ut(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})}}},en=RegExp(`(?:^|[^a-zA-Z])\\w*(?:${[`Entity`,`Model`].join(`|`)})(?:[^a-zA-Z]|$)`),tn={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();en.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})}}},nn={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(l.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})}}},rn=new Set([`md5`,`sha1`]),an=[ge,Ge,Fe,Be,je,qe,Je,Xe,ue,Ee,pt,gt,rt,nt,ct,ft,st,ut,Qe,et,dt,mt,ot,lt,$t,Pt,{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(l.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()!==l.StringLiteral)continue;let i=r.getText().slice(1,-1).toLowerCase();rn.has(i)&&e.report({filePath:e.filePath,message:`Weak hashing algorithm '${i}' used in createHash().`,help:this.meta.help,line:n.getStartLineNumber(),column:1})}}},Ft,Mt,Lt,Nt,nn,tn,Ct,vt,yt,xt,Et,wt,bt,Ot,jt,Dt];function on(e){return e.meta.scope===`project`}function sn(e){return e.meta.scope===`schema`}function cn(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 ln(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 un(e){let t=[],n=[],r=[];for(let i of e)sn(i)?r.push(i):on(i)?n.push(i):t.push(i);return{fileRules:t,projectRules:n,schemaRules:r}}const dn=new Set([`security`,`performance`,`correctness`,`architecture`]),fn=new Set([`error`,`warning`,`info`]),pn=new Set([`file`,`project`]),mn=`custom/`;function hn(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`||!dn.has(n.category)||!fn.has(n.severity)||n.scope!==void 0&&!pn.has(n.scope))}function gn(e){return e.meta.id.startsWith(mn)?e:{...e,meta:{...e.meta,id:`${mn}${e.meta.id}`}}}async function _n(e,t){let n=[],r=[],i=o(t,e);if(!d(i))return r.push(`Custom rules directory not found: ${i}`),{rules:n,warnings:r};if(!m(i).isDirectory())return r.push(`Custom rules path is not a directory: ${i}`),{rules:n,warnings:r};let a;try{a=p(i)}catch(e){return r.push(`Failed to read custom rules directory: ${e instanceof Error?e.message:String(e)}`),{rules:n,warnings:r}}let s=a.filter(e=>e.endsWith(`.ts`));if(s.length===0)return r.push(`No rule files (.ts) found in: ${i}`),{rules:n,warnings:r};let c=h(i,{interopDefault:!0});for(let e of s){let t=o(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 s=!1;for(let[t,i]of Object.entries(a))hn(i)?(n.push(gn(i)),s=!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)`);!s&&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 vn(e,t){return e.customRulesDir?_n(e.customRulesDir,t):Promise.resolve({rules:[],warnings:[]})}async function yn(e,t){let n=await ae(e,t),{rules:r,warnings:i}=await vn(n,e),a=cn(an,r,i),{fileRules:o,projectRules:s,schemaRules:c}=un(ln(n,a));return{combinedRules:a,config:n,customRuleWarnings:i,fileRules:o,projectRules:s,schemaRules:c}}async function bn(e,t={}){return(await g(t.include??S.include,{cwd:e,absolute:!0,ignore:t.exclude??S.exclude})).sort()}async function xn(e,t,n={}){let r=await Promise.all([...t.projects.entries()].map(async([t,r])=>[t,await bn(a(e,r),n)])),i=new Map;for(let[e,t]of r)i.set(e,t);return i}function Sn(e){let t=new c({compilerOptions:{strict:!0,target:99,module:99,skipFileDependencyResolution:!0},skipAddingFilesFromTsConfig:!0});for(let n of e)t.addSourceFileAtPath(n);return t}const Cn=/import\([^)]+\)\.(\w+)/,wn=/^(\w+)</;function Tn(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 Dn(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 En(e,t){let n=new Map;for(let r of t){let t=e.getSourceFile(r);if(t)for(let e of Tn(t,r))n.set(e.name,e)}return n}function Dn(e){let t=e.match(Cn);if(t)return t[1];let n=e.match(wn);return n?n[1]:e}const On=new Set([`pgTable`,`mysqlTable`,`sqliteTable`]),kn=new Set([`serial`,`bigserial`,`smallserial`]),An=/=>\s*(\w+)/;function jn(e){let t={type:`unknown`,isPrimary:!1,isNullable:!0,isGenerated:!1,isUnique:!1};function n(e){if(e.getKind()===l.CallExpression){let r=e.asKindOrThrow(l.CallExpression),i=r.getExpression();if(i.getKind()===l.PropertyAccessExpression){let e=i.asKindOrThrow(l.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=An.exec(n);if(r&&(t.reference={toEntity:r[1]}),e.length>1){let n=e[1];if(n.getKind()===l.ObjectLiteralExpression){let e=n.asKindOrThrow(l.ObjectLiteralExpression);for(let n of e.getProperties())if(n.getKind()===l.PropertyAssignment){let e=n.asKindOrThrow(l.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()===l.Identifier){let e=i.getText();t.type=e,kn.has(e)&&(t.isGenerated=!0)}}}return n(e),t}function Mn(e){let t=[];for(let n of e.getProperties()){if(n.getKind()!==l.PropertyAssignment)continue;let e=n.asKindOrThrow(l.PropertyAssignment),r=e.getName(),i=e.getInitializer();if(!i)continue;let a=jn(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 Nn(e,t){let n=[];for(let r of e.getProperties()){if(r.getKind()!==l.PropertyAssignment)continue;let e=r.asKindOrThrow(l.PropertyAssignment),i=e.getName(),a=e.getInitializer();if(!a)continue;let o=jn(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 Pn(e){let t=[],n=e.getDescendantsOfKind(l.CallExpression);for(let e of n){let n=e.getExpression();if(n.getKind()!==l.PropertyAccessExpression||n.asKindOrThrow(l.PropertyAccessExpression).getName()!==`on`)continue;let r=[];for(let t of e.getArguments())if(t.getKind()===l.PropertyAccessExpression){let e=t.asKindOrThrow(l.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 Fn(e){let t=[],n=e.getFilePath();for(let r of e.getDescendantsOfKind(l.VariableDeclaration)){let e=r.getInitializer();if(!e||e.getKind()!==l.CallExpression)continue;let i=e.asKindOrThrow(l.CallExpression),a=i.getExpression();if(a.getKind()!==l.Identifier)continue;let o=a.getText();if(!On.has(o))continue;let s=i.getArguments();if(s.length<2)continue;let c=s[0],u=r.getName();c.getKind()===l.StringLiteral&&(u=c.asKindOrThrow(l.StringLiteral).getLiteralValue());let d=s[1];if(d.getKind()!==l.ObjectLiteralExpression)continue;let f=d.asKindOrThrow(l.ObjectLiteralExpression),p=r.getName(),m=Mn(f),h=Nn(f,p),g;if(s.length>=3&&(g=Pn(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:u,filePath:n,columns:m,relations:h,indexes:g})}return t}const In={supportsIncrementalUpdate:!0,extract(e,t){let n=[];for(let r of t){let t=e.getSourceFile(r);t&&n.push(...Fn(t))}return n}},Ln=/^model\s+(\w+)\s*\{/,Rn=/^enum\s+(\w+)\s*\{/,zn=/^(\w+)\s+(\w+)(\?)?(\[\])?(.*)$/,Bn=/@(\w+)(\((?:[^()]*|\([^()]*\))*\))?/g,Vn=/@default\(((?:[^()]*|\([^()]*\))*)\)/,Hn=/^@@map\(\s*"([^"]+)"\s*\)/;function Un(e){let t=a(e,`prisma`,`schema.prisma`);if(d(t)){let n=a(e,`prisma`),r=p(n).filter(e=>e.endsWith(`.prisma`));return r.length>1?r.map(e=>a(n,e)):[t]}let n=a(e,`schema.prisma`);if(d(n))return[n];try{let t=a(e,`package.json`),n=JSON.parse(f(t,`utf-8`)).prisma?.schema;if(n){let t=a(e,n);if(d(t))return[t]}}catch{}return[]}function Wn(e){let t=[],n=new Set;for(let r of e){let e;try{e=f(r,`utf-8`)}catch{continue}let i=e.split(`
|
|
4
|
-
`),a=null,o=[],s=[],c=[],l;for(let e of i){let i=e.trim(),u=Ln.exec(i);if(u){a={type:`model`,name:u[1]},o=[],s=[],c=[],l=void 0;continue}let d=Rn.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=Kn.exec(i);e&&(c=e[1].split(`,`).map(e=>e.trim()));let t=qn(i);t&&s.push(t);let n=Hn.exec(i);n&&(l=n[1]);continue}let e=Jn(i);e&&o.push(e)}}}return{models:t,enums:n}}const Gn=/^@@(index|unique)\(\[([^\]]*)\]\)/,Kn=/^@@id\(\[([^\]]*)\]\)/;function qn(e){let t=Gn.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 Jn(e){let t=zn.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(Bn.source,Bn.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 Yn(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=Vn.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 Xn=/onDelete:\s*(\w+)/;function Zn(e){let t=e.attributes.find(e=>e.startsWith(`@relation`));if(!t)return;let n=Xn.exec(t);return n?n[1]:void 0}function Qn(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=Zn(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=Yn(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 $n={supportsIncrementalUpdate:!1,extract(e,t,n){let r=Un(n);if(r.length===0)return[];let{models:i,enums:a}=Wn(r);return Qn(i,a)}},er=/=>\s*(\w+)/,tr=new Set([`Column`,`PrimaryColumn`,`PrimaryGeneratedColumn`,`CreateDateColumn`,`UpdateDateColumn`,`DeleteDateColumn`,`VersionColumn`]),nr={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()===l.ObjectLiteralExpression){let t={},n=e.asKind(l.ObjectLiteralExpression);if(!n)continue;for(let e of n.getProperties())if(e.getKind()===l.PropertyAssignment){let n=e.asKind(l.PropertyAssignment);n&&(t[n.getName()]=n.getInitializer()?.getText()??``)}return t}return null}function rr(e){let t=e.getArguments();if(t.length===0)return null;let n=t[0];return n.getKind()===l.StringLiteral?n.asKind(l.StringLiteral)?.getLiteralValue()??null:null}function ir(e){let t=e.getDecorator(`Entity`);if(!t)return e.getName()??`UnknownEntity`;let n=rr(t);if(n)return n;let r=z(t);return r?.name?r.name.replace(/['"]/g,``):e.getName()??`UnknownEntity`}function ar(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=rr(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 or(e,t,n){let r=nr[n.getName()];if(!r)return null;let i=n.getArguments();if(i.length===0)return null;let a=i[0].getText(),o=er.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 sr(e){if(!w(e,`Entity`))return null;let t=e.getName();if(!t)return null;let n=ir(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()===l.ArrayLiteralExpression){let e=n.asKind(l.ArrayLiteralExpression);if(e){let n=e.getElements().map(e=>e.getKind()===l.StringLiteral?e.asKind(l.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(tr.has(r)){let t=ar(e,n);c&&(t.hasIndex=!0),i.push(t);break}if(r in nr){let r=or(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 cr={prisma:$n,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=sr(e);t&&n.push(t)}}return n}},drizzle:In};function B(e,t,n,r){let i={entities:new Map,relations:[],orm:n??`unknown`};if(!n)return i;let a=cr[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 lr(e){return{entities:[...e.entities.values()],relations:e.relations,orm:e.orm}}async function ur(e,t){let{config:n,fileRules:r,projectRules:i,schemaRules:a}=t,[o,s]=await Promise.all([bn(e,n),x(e)]),c=Sn(o),l=_e(e);return{astProject:c,config:n,fileRules:r,files:o,moduleGraph:O(c,o,l),pathAliases:l,project:s,projectRules:i,providers:En(c,o),schemaGraph:B(c,o,s.orm,e),schemaRules:a,targetPath:e}}async function dr(e,t,n){let{config:r,combinedRules:i}=t,o=await xn(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([x(s),ce(s,r)]),u=Sn(o),d=_e(s),f=O(u,o,d),p=En(u,o),m=B(u,o,c.orm,s),{fileRules:h,projectRules:g,schemaRules:_}=un(ln(l,i));return[t,{astProject:u,config:l,fileRules:h,files:o,moduleGraph:f,pathAliases:d,project:c,projectRules:g,providers:p,schemaGraph:m,schemaRules:_,targetPath:s}]}));return{subProjects:new Map(s)}}const fr=e=>_.makeRe(e,{windows:!1}),pr=/\\/g,mr=/\/$/,hr=(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(fr):[];if(r.size===0&&i.length===0)return e;let a=n.replace(pr,`/`).replace(mr,``);return e.filter(e=>{if(r.has(e.rule))return!1;let t=e.filePath.replace(pr,`/`),n=t.startsWith(`${a}/`)?t.slice(a.length+1):t;return!i.some(e=>e.test(n))})};function gr(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 _r(e,t,n,r){let i=[],a=[];for(let o of t){let t=gr(e,o,n,r);i.push(...t.diagnostics),a.push(...t.errors)}return{diagnostics:i,errors:a}}function vr(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 br(e){return e instanceof Error?e.message:String(e)}function V(e,t,n){return{diagnostics:hr(e,n.config,n.targetPath),errors:t.map(e=>({ruleId:e.ruleId,error:br(e.error)}))}}function xr(e){let t=_r(e.astProject,e.files,e.fileRules,e.config);return V(t.diagnostics,t.errors,e)}function Sr(e){let t={moduleGraph:e.moduleGraph,providers:e.providers,config:e.config},n=vr(e.astProject,e.files,e.projectRules,t),{diagnostics:r,errors:i}=V(n.diagnostics,n.errors,e),a=Cr(e);return r.push(...a.diagnostics),i.push(...a.errors),{diagnostics:r,errors:i}}function Cr(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=s.now(),n=xr(e),r=Sr(e),i=s.now()-t;return{diagnostics:[...n.diagnostics,...r.diagnostics],elapsedMs:i,ruleErrors:[...n.errors,...r.errors]}}function wr(e){return e>=90?`Excellent`:e>=75?`Good`:e>=50?`Fair`:e>=25?`Poor`:`Critical`}const Tr={error:3,warning:1.5,info:.5},Er={security:1.5,correctness:1.3,schema:1.1,architecture:1,performance:.8};function Dr(e,t){if(t===0)return{value:100,label:wr(100)};let n=0;for(let t of e){let e=Tr[t.severity],r=Er[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:wr(i)}}function Or(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=Dr(r,e.files.length),c=Or(r);return{result:{score:s,diagnostics:r,project:{...e.project,fileCount:e.files.length,moduleCount:e.moduleGraph.modules.size},summary:c,ruleErrors:i,elapsedMs:a,schema:lr(o)},moduleGraph:e.moduleGraph,schemaGraph:o,customRuleWarnings:n,files:e.files,providers:e.providers}}function kr(e,t,n,r){let i=[],a=[],o=[],s=new Map,c=0;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),c+=e.result.project.fileCount}let l=Dr(a,c),u=Or(a);return{moduleGraphs:s,customRuleWarnings:n,result:{isMonorepo:!0,subProjects:i,combined:{score:l,diagnostics:a,project:{name:`monorepo`,nestVersion:i[0]?.result.project.nestVersion??null,orm:i[0]?.result.project.orm??null,framework:i[0]?.result.project.framework??null,fileCount:c,moduleCount:i.reduce((e,t)=>e+t.result.project.moduleCount,0)},summary:u,ruleErrors:o,elapsedMs:r},elapsedMs:r}}}const Ar=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},jr=(e,t)=>e===void 0?t:Number(e),Mr=(e,t)=>t===void 0?!0:e>=t,W={error:v.red,warn:v.yellow,info:v.cyan,success:v.green,dim:v.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(``)}},Nr=1e3,Pr={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),Fr=(e,t)=>t===`error`?W.error(e):t===`warning`?W.warn(e):W.info(e),Ir=e=>e===`error`?`✗`:e===`warning`?`⚠`:`●`,Lr=e=>e<Nr?`${Math.round(e)}ms`:`${(e/Nr).toFixed(1)}s`,Rr=e=>e>=75?[`◠ ◠ ◠`,`╰───╯`]:e>=50?[`• • •`,`╰───╯`]:[`x x x`,`╰───╯`],zr=e=>e>=90?`★★★★★`:e>=75?`★★★★☆`:e>=50?`★★★☆☆`:e>=25?`★★☆☆☆`:`★☆☆☆☆`,Br=e=>{let t=Math.round(e/100*50),n=50-t;return{filled:`█`.repeat(t),empty:`░`.repeat(n)}},Vr=e=>{let{filled:t,empty:n}=Br(e);return`${t}${n}`},Hr=e=>{let{filled:t,empty:n}=Br(e);return J(t,e)+W.dim(n)},Ur=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}┘`)}`)},Wr=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},Gr=e=>[...e].sort(([,e],[,t])=>Pr[e[0].severity]-Pr[t[0].severity]),Kr=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},qr=e=>new Set(e.map(e=>e.filePath)),Jr=(e,t)=>{let n=Gr([...Wr(e).entries()]);for(let[,e]of n){let n=e[0],r=Fr(Ir(n.severity),n.severity),i=e.length,a=i>1?Fr(` (${i})`,n.severity):``;if(K.log(` ${r} ${n.message}${a}`),n.help&&K.dim(` ${n.help}`),t){let t=Kr(e);for(let[e,n]of t){let t=n.length>0?`: ${n.join(`, `)}`:``;K.dim(` ${e}${t}`)}}K.break()}};function Y(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]=Rr(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=zr(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(Vr(n.value),Hr(n.value))),s.push(q(``));let m=Lr(o),h=qr(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(` `))),Ur(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(Jr(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 Yr(e,t){Y(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 Xr(e){console.log(JSON.stringify(e,null,2))}const X=(e,t,n)=>{let r=e.score.value;Mr(r,t)||(n||K.error(`Score ${r} is below the minimum threshold of ${t}.`),process.exit(1))},Zr=e=>{e.summary.errors>0&&process.exit(1)},Qr=(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){Xr(i.combined),X(i.combined,t,n);return}Yr(i,r.verbose),X(i.combined,t,n),Zr(i.combined)},$r=(e,t,n,r)=>{let{result:i}=e;if(r.score){console.log(i.score.value),X(i,t,n);return}if(r.json){Xr(i),X(i,t,n);return}Y(i,r.verbose),X(i,t,n),Zr(i)};let Z=null,Q=0;const $=new Set,ei=(e,t,n)=>{if($.delete(t),Q--,Q<=0||!Z){Z?.[e](n),Z=null,Q=0;return}Z.stop(),y(n).start()[e](n);let[r]=$;r&&(Z.text=r),Z.start()},ti=e=>({start(){return Q++,$.add(e),Z?Z.text=e:Z=y({text:e}).start(),{succeed:t=>ei(`succeed`,e,t),fail:t=>ei(`fail`,e,t)}}}),ni=(e,t)=>{if(!t)for(let t of e)K.warn(t)};var ri=class{options;resolvedMinimumScore;scanConfig;steps=[];targetPath;constructor(e,t){this.targetPath=e,this.options=t}resolveConfig(){return this.steps.push(async()=>{this.scanConfig=await yn(this.targetPath,this.options.configPath),this.resolvedMinimumScore=jr(this.options.minScore,this.scanConfig.config.minScore)}),this}warnCustomRules(){return this.steps.push(()=>{ni(this.scanConfig.customRuleWarnings,this.options.isMachineReadable)}),this}async run(){let e=this.options.isMachineReadable?null:ti(`Scanning...`).start();for(let e of this.steps)await e();e?.succeed(`Scan complete`)}},ii=class extends ri{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=s.now(),this.monorepoCtx=await dr(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=s.now()-this.scanStartTime;this.result=kr(this.monorepoCtx,this.rawOutputs,this.scanConfig.customRuleWarnings,e)}),this}output(){return this.steps.push(()=>{Qr(this.result,this.resolvedMinimumScore,this.options.isMachineReadable,{json:this.options.json,score:this.options.score,verbose:this.options.verbose})}),this}},ai=class extends ri{context;rawOutput;result;buildContext(){return this.steps.push(async()=>{this.context=await ur(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(()=>{$r(this.result,this.resolvedMinimumScore,this.options.isMachineReadable,{json:this.options.json,score:this.options.score,verbose:this.options.verbose})}),this}},oi=class{args;steps=[];version;targetPath=``;constructor(e,t){this.args=e,this.version=t}resolveTargetPath(){return this.steps.push(()=>(this.targetPath=o(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-9FpyLvnO.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=Ar(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:si}=e(import.meta.url)(`../../package.json`);n(t({meta:{name:`nestjs-doctor`,version:si,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},...re},async run({args:e}){let t=await new oi(e,si).resolveTargetPath().handleInit().handleReport().validateMinScore().run();if(!t)return;let{targetPath:n,options:r}=t,i=await b(n);if(i){await new ii(n,i,r).resolveConfig().buildContext().runRules().buildResult().warnCustomRules().output().run();return}await new ai(n,r).resolveConfig().buildContext().runRules().buildResult().warnCustomRules().output().run()}}));export{U as a,dr as c,we as d,b as f,kr as i,yn as l,K as n,H as o,W as r,ur as s,ti as t,I as u};
|
|
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 k(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 A=`Break the cycle by extracting shared logic into a separate module or using forwardRef().`;function Xe(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=k(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 A;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=k(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 Ze={meta:{id:`architecture/no-circular-module-deps`,category:`architecture`,severity:`error`,description:`Circular dependencies in @Module() import graph`,help:A,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=Xe(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`],$e=[`Guard`,`Interceptor`,`Pipe`,`Filter`];function j(e){return typeof e==`object`&&!!e}function et(e){if(!j(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(!j(n))return new Set;let r=n.excludeClasses;return Array.isArray(r)?new Set(r.filter(e=>typeof e==`string`)):new Set}const tt={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=et(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=Qe.some(e=>n.endsWith(e)),o=$e.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})}}}},nt=/\.(\w+)$/,rt=/^(\w+)</,it=new Set([`PrismaService`,`PrismaClient`,`EntityManager`,`DataSource`,`Repository`,`Connection`,`MongooseModel`,`InjectModel`,`InjectRepository`,`MikroORM`,`DrizzleService`]),at={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=ot(t.getType().getText());if(it.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 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=new Set([`PrismaService`,`PrismaClient`,`EntityManager`,`DataSource`,`Connection`,`MikroORM`]),ut={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=dt(t.getType().getText());if(lt.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 dt(e){let t=e.match(st);if(t)return t[1];let n=e.match(ct);return n?n[1]:e}const ft=/\.(\w+)$/,pt=/^(\w+)</,mt=[/Repository$/,/Repo$/],ht={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=gt(t.getType().getText());if(mt.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 gt(e){let t=e.match(ft);if(t)return t[1];let n=e.match(pt);return n?n[1]:e}const _t={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})}}},vt={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})}},yt=[`/repositories/`,`/entities/`,`/dto/`,`/guards/`,`/interceptors/`,`/pipes/`,`/strategies/`],bt={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(`../`)&&yt.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 M(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 xt={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();M(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`;M(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})}}}},St=[`providers`,`controllers`,`imports`,`exports`],Ct={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 St){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)}}}}},wt=new Set([`Get`,`Post`,`Put`,`Delete`,`Patch`,`All`,`Head`,`Options`]),Tt={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(!wt.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())}}}},Et={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 Dt(e){let t=e.getReturnType().getText();return t.startsWith(`Promise<`)||t===`Promise`?!0:t===`any`||t===`error`?`unknown`:!1}const N=new Set([`save`,`create`,`insert`,`update`,`delete`,`remove`,`send`,`emit`,`publish`,`dispatch`,`execute`,`fetch`,`load`,`upload`,`download`,`process`]),Ot={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=Dt(i);if(o!==!1){if(o===`unknown`){let e=a.toLowerCase();if(!(N.has(e)||[...N].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})}}}}},kt={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}))}},At={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}))}}},jt={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})}}}},Mt={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}))}}},Nt={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}))}}},Pt={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}))}}},Ft={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})}}}}},It={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})}}}},Lt={onModuleInit:`OnModuleInit`,onModuleDestroy:`OnModuleDestroy`,onApplicationBootstrap:`OnApplicationBootstrap`,onApplicationShutdown:`OnApplicationShutdown`,beforeApplicationShutdown:`BeforeApplicationShutdown`},Rt={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=Lt[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}))}}}},zt=new Set([d.ForStatement,d.ForOfStatement,d.ForInStatement,d.WhileStatement,d.DoStatement,d.AwaitExpression]),Bt={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(zt.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}}}}},Vt={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})}}},Ht={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}))}},Ut={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})}},Wt=new Set([`readFileSync`,`writeFileSync`,`existsSync`,`mkdirSync`,`readdirSync`,`statSync`,`accessSync`,`appendFileSync`,`copyFileSync`,`renameSync`,`unlinkSync`]),Gt={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()??``;Wt.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})}}},Kt={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})}}},qt=[`Guard`,`Interceptor`,`Filter`,`Middleware`,`Strategy`],Jt={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(qt.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})}}},Yt={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})}}},Xt={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})}},Zt=/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&&!Zt.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 $t={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})}},en={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})}}},tn={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})}}}},nn={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})}},rn={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})}}},an=/^(error|err|e|ex|exception)$/,on={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(!(an.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})}}},sn=[{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)`}],cn=[/secret/i,/password/i,/passwd/i,/api[_-]?key/i,/auth[_-]?token/i,/private[_-]?key/i,/access[_-]?key/i,/client[_-]?secret/i],ln=new Set([`your-secret-here`,`changeme`,`password`]),un=/^[A-Za-z][A-Za-z0-9_]*(\.[A-Za-z][A-Za-z0-9_]*)+$/,P=new Set([`cursor`,`nextCursor`,`prevCursor`,`previousCursor`,`startCursor`,`endCursor`,`pageToken`,`nextPageToken`,`continuationToken`,`continuation`,`nextPage`,`afterCursor`,`beforeCursor`]);function F(e){return!(e.length<8||e.includes("${")||e.startsWith(`process.env`)||ln.has(e)||e.includes(` `)||un.test(e))}function I(e){return cn.some(t=>t.test(e))}function dn(e){try{let t=Buffer.from(e,`base64`).toString(`utf-8`);return JSON.parse(t),!0}catch{return!1}}function fn(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 L=new Set([...`aeiouyAEIOUY`]),pn=/^[A-Z]{2,4}_/,mn=/(?<=[a-z])(?=[A-Z])|(?<=[A-Za-z])(?=\d)|(?<=\d)(?=[A-Za-z])|_/,hn=/[a-zA-Z]/;function gn(e){let t=e.includes(`_`),n=e.split(mn).filter(e=>e.length>0).filter(e=>hn.test(e)),r=n.filter(e=>e.length>=4&&[...e].some(e=>L.has(e)));return n.slice(0,6).filter(e=>e.length>=4&&[...e].some(e=>L.has(e))).length>=2||t&&e.split(`_`).filter(e=>e.length>=3).length>=2||pn.test(e)?!0:(fn(e)>4.9&&!t&&r.length,!1)}function _n(e){let t=e.getParent();if(!t)return!1;let n=t.asKind(d.PropertyAssignment);if(n)return P.has(n.getName());let r=t.asKind(d.VariableDeclaration);return r?P.has(r.getName()):!1}const vn={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 sn)if(r.test(t)){if(i===`Base64 key`&&(dn(t)||_n(n)||gn(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||I(n)&&F(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||I(n)&&F(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})}}},yn=RegExp(`(?:^|[^a-zA-Z])\\w*(?:${[`Entity`,`Model`].join(`|`)})(?:[^a-zA-Z]|$)`),bn={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();yn.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})}}},xn={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})}}},Sn=new Set([`md5`,`sha1`]),Cn=[Ne,ht,at,ut,tt,_t,vt,bt,De,Ze,Ft,Rt,Et,Tt,At,Pt,kt,Mt,xt,Ct,Nt,It,Ot,jt,vn,nn,{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();Sn.has(i)&&e.report({filePath:e.filePath,message:`Weak hashing algorithm '${i}' used in createHash().`,help:this.meta.help,line:n.getStartLineNumber(),column:1})}}},rn,en,on,tn,xn,bn,Gt,Bt,Vt,Ut,Jt,Kt,Ht,Xt,$t,Yt];function wn(e){return e.meta.scope===`project`}function Tn(e){return e.meta.scope===`schema`}function En(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 R(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 Dn(e){let t=[],n=[],r=[];for(let i of e)Tn(i)?r.push(i):wn(i)?n.push(i):t.push(i);return{fileRules:t,projectRules:n,schemaRules:r}}const On=new Set([`security`,`performance`,`correctness`,`architecture`]),kn=new Set([`error`,`warning`,`info`]),An=new Set([`file`,`project`]),jn=`custom/`;function Mn(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`||!On.has(n.category)||!kn.has(n.severity)||n.scope!==void 0&&!An.has(n.scope))}function Nn(e){return e.meta.id.startsWith(jn)?e:{...e,meta:{...e.meta,id:`${jn}${e.meta.id}`}}}async function Pn(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))Mn(i)?(n.push(Nn(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 Fn(e,t){return e.customRulesDir?Pn(e.customRulesDir,t):Promise.resolve({rules:[],warnings:[]})}async function In(e,t){let n=await Se(e,t),{rules:r,warnings:i}=await Fn(n,e),a=En(Cn,r,i),{fileRules:o,projectRules:s,schemaRules:c}=Dn(R(n,a));return{combinedRules:a,config:n,customRuleWarnings:i,fileRules:o,projectRules:s,schemaRules:c}}async function Ln(e,t={}){return(await c(t.include??S.include,{cwd:e,absolute:!0,ignore:t.exclude??S.exclude})).sort()}async function Rn(e,t,n={}){let r=await Promise.all([...t.projects.entries()].map(async([t,r])=>[t,await Ln(a(e,r),n)])),i=new Map;for(let[e,t]of r)i.set(e,t);return i}function zn(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 Bn=/import\([^)]+\)\.(\w+)/,Vn=/^(\w+)</;function Hn(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 Wn(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 Un(e,t){let n=new Map;for(let r of t){let t=e.getSourceFile(r);if(t)for(let e of Hn(t,r))n.set(e.name,e)}return n}function Wn(e){let t=e.match(Bn);if(t)return t[1];let n=e.match(Vn);return n?n[1]:e}const Gn=new Set([`pgTable`,`mysqlTable`,`sqliteTable`]),Kn=new Set([`serial`,`bigserial`,`smallserial`]),qn=/=>\s*(\w+)/;function Jn(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=qn.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,Kn.has(e)&&(t.isGenerated=!0)}}}return n(e),t}function Yn(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=Jn(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 Xn(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=Jn(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 Zn(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 Qn(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(!Gn.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=Yn(f),h=Xn(f,p),g;if(s.length>=3&&(g=Zn(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 $n={supportsIncrementalUpdate:!0,extract(e,t){let n=[];for(let r of t){let t=e.getSourceFile(r);t&&n.push(...Qn(t))}return n}},er=/^model\s+(\w+)\s*\{/,tr=/^enum\s+(\w+)\s*\{/,nr=/^(\w+)\s+(\w+)(\?)?(\[\])?(.*)$/,rr=/@(\w+)(\((?:[^()]*|\([^()]*\))*\))?/g,ir=/@default\(((?:[^()]*|\([^()]*\))*)\)/,ar=/^@@map\(\s*"([^"]+)"\s*\)/;function or(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 sr(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=er.exec(i);if(u){a={type:`model`,name:u[1]},o=[],s=[],c=[],l=void 0;continue}let d=tr.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=lr.exec(i);e&&(c=e[1].split(`,`).map(e=>e.trim()));let t=ur(i);t&&s.push(t);let n=ar.exec(i);n&&(l=n[1]);continue}let e=dr(i);e&&o.push(e)}}}return{models:t,enums:n}}const cr=/^@@(index|unique)\(\[([^\]]*)\]\)/,lr=/^@@id\(\[([^\]]*)\]\)/;function ur(e){let t=cr.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 dr(e){let t=nr.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(rr.source,rr.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 fr(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=ir.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 pr=/onDelete:\s*(\w+)/;function mr(e){let t=e.attributes.find(e=>e.startsWith(`@relation`));if(!t)return;let n=pr.exec(t);return n?n[1]:void 0}function hr(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=mr(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=fr(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 gr={supportsIncrementalUpdate:!1,extract(e,t,n){let r=or(n);if(r.length===0)return[];let{models:i,enums:a}=sr(r);return hr(i,a)}},_r=/=>\s*(\w+)/,vr=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 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 br(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 xr(e){let t=e.getDecorator(`Entity`);if(!t)return e.getName()??`UnknownEntity`;let n=br(t);if(n)return n;let r=z(t);return r?.name?r.name.replace(/['"]/g,``):e.getName()??`UnknownEntity`}function Sr(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=br(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 Cr(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=_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 wr(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()===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(vr.has(r)){let t=Sr(e,n);c&&(t.hasIndex=!0),i.push(t);break}if(r in yr){let r=Cr(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 Tr={prisma:gr,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=wr(e);t&&n.push(t)}}return n}},drizzle:$n};function B(e,t,n,r){let i={entities:new Map,relations:[],orm:n??`unknown`};if(!n)return i;let a=Tr[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 Er(e){return{entities:[...e.entities.values()],relations:e.relations,orm:e.orm}}async function Dr(e,t){let{config:n,fileRules:r,projectRules:i,schemaRules:a}=t,[o,s]=await Promise.all([Ln(e,n),ge(e)]),c=zn(o),l=Pe(e);return{astProject:c,config:n,fileRules:r,files:o,moduleGraph:ze(c,o,l),pathAliases:l,project:s,projectRules:i,providers:Un(c,o),schemaGraph:B(c,o,s.orm,e),schemaRules:a,targetPath:e}}async function Or(e,t,n){let{config:r,combinedRules:i}=t,o=await Rn(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=zn(o),d=Pe(s),f=ze(u,o,d),p=Un(u,o),m=B(u,o,c.orm,s),{fileRules:h,projectRules:g,schemaRules:_}=Dn(R(l,i));return[t,{astProject:u,config:l,fileRules:h,files:o,moduleGraph:f,pathAliases:d,project:c,projectRules:g,providers:p,schemaGraph:m,schemaRules:_,targetPath:s}]}));return{subProjects:new Map(s)}}const kr=e=>v.makeRe(e,{windows:!1}),Ar=/\\/g,jr=/\/$/,Mr=(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(kr):[];if(r.size===0&&i.length===0)return e;let a=n.replace(Ar,`/`).replace(jr,``);return e.filter(e=>{if(r.has(e.rule))return!1;let t=e.filePath.replace(Ar,`/`),n=t.startsWith(`${a}/`)?t.slice(a.length+1):t;return!i.some(e=>e.test(n))})};function Nr(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 Pr(e,t,n,r){let i=[],a=[];for(let o of t){let t=Nr(e,o,n,r);i.push(...t.diagnostics),a.push(...t.errors)}return{diagnostics:i,errors:a}}function Fr(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 Ir(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 Lr(e){return e instanceof Error?e.message:String(e)}function V(e,t,n){return{diagnostics:Mr(e,n.config,n.targetPath),errors:t.map(e=>({ruleId:e.ruleId,error:Lr(e.error)}))}}function Rr(e){let t=Pr(e.astProject,e.files,e.fileRules,e.config);return V(t.diagnostics,t.errors,e)}function zr(e){let t={moduleGraph:e.moduleGraph,providers:e.providers,config:e.config},n=Fr(e.astProject,e.files,e.projectRules,t),{diagnostics:r,errors:i}=V(n.diagnostics,n.errors,e),a=Br(e);return r.push(...a.diagnostics),i.push(...a.errors),{diagnostics:r,errors:i}}function Br(e){if(!e.schemaGraph||e.schemaRules.length===0||e.schemaGraph.entities.size===0)return{diagnostics:[],errors:[]};let t=Ir(e.schemaGraph,e.schemaRules);return V(t.diagnostics,t.errors,e)}function H(e){let t=l.now(),n=Rr(e),r=zr(e),i=l.now()-t;return{diagnostics:[...n.diagnostics,...r.diagnostics],elapsedMs:i,ruleErrors:[...n.errors,...r.errors]}}function Vr(e){return e>=90?`Excellent`:e>=75?`Good`:e>=50?`Fair`:e>=25?`Poor`:`Critical`}const Hr={error:3,warning:1.5,info:.5},Ur={security:1.5,correctness:1.3,schema:1.1,architecture:1,performance:.8};function Wr(e,t){if(t===0)return{value:100,label:Vr(100)};let n=0;for(let t of e){let e=Hr[t.severity],r=Ur[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:Vr(i)}}function Gr(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=Wr(r,e.files.length),c=Gr(r);return{result:{score:s,diagnostics:r,project:{...e.project,fileCount:e.files.length,moduleCount:e.moduleGraph.modules.size},summary:c,ruleErrors:i,elapsedMs:a,schema:Er(o)},moduleGraph:e.moduleGraph,schemaGraph:o,customRuleWarnings:n,files:e.files,providers:e.providers}}function Kr(e,t,n,r){let i=[],a=[],o=[],s=new Map,c=0,l=[],u=[],d=``;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),c+=e.result.project.fileCount,e.result.schema&&(l.push(...e.result.schema.entities),u.push(...e.result.schema.relations),e.result.schema.orm&&e.result.schema.orm!==`unknown`&&(d=e.result.schema.orm))}let f=Wr(a,c),p=Gr(a);return{moduleGraphs:s,customRuleWarnings:n,result:{isMonorepo:!0,subProjects:i,combined:{score:f,diagnostics:a,project:{name:`monorepo`,nestVersion:i[0]?.result.project.nestVersion??null,orm:d||(i[0]?.result.project.orm??null),framework:i[0]?.result.project.framework??null,fileCount:c,moduleCount:i.reduce((e,t)=>e+t.result.project.moduleCount,0)},summary:p,ruleErrors:o,elapsedMs:r,schema:l.length>0?{entities:l,relations:u,orm:d||`unknown`}:void 0},elapsedMs:r}}}const qr=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},Jr=(e,t)=>e===void 0?t:Number(e),Yr=(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(``)}},Xr=1e3,Zr={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),Qr=(e,t)=>t===`error`?W.error(e):t===`warning`?W.warn(e):W.info(e),$r=e=>e===`error`?`✗`:e===`warning`?`⚠`:`●`,ei=e=>e<Xr?`${Math.round(e)}ms`:`${(e/Xr).toFixed(1)}s`,ti=e=>e>=75?[`◠ ◠ ◠`,`╰───╯`]:e>=50?[`• • •`,`╰───╯`]:[`x x x`,`╰───╯`],ni=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)}},ii=e=>{let{filled:t,empty:n}=ri(e);return`${t}${n}`},ai=e=>{let{filled:t,empty:n}=ri(e);return J(t,e)+W.dim(n)},oi=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}┘`)}`)},si=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},ci=e=>[...e].sort(([,e],[,t])=>Zr[e[0].severity]-Zr[t[0].severity]),li=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},ui=e=>new Set(e.map(e=>e.filePath)),di=(e,t)=>{let n=ci([...si(e).entries()]);for(let[,e]of n){let n=e[0],r=Qr($r(n.severity),n.severity),i=e.length,a=i>1?Qr(` (${i})`,n.severity):``;if(K.log(` ${r} ${n.message}${a}`),n.help&&K.dim(` ${n.help}`),t){let t=li(e);for(let[e,n]of t){let t=n.length>0?`: ${n.join(`, `)}`:``;K.dim(` ${e}${t}`)}}K.break()}};function fi(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]=ti(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=ni(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(ii(n.value),ai(n.value))),s.push(q(``));let m=ei(o),h=ui(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(` `))),oi(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(di(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 pi(e,t){fi(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;Yr(r,t)||(n||K.error(`Score ${r} is below the minimum threshold of ${t}.`),process.exit(1))},mi=e=>{e.summary.errors>0&&process.exit(1)},hi=(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}pi(i,r.verbose),X(i.combined,t,n),mi(i.combined)},gi=(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}fi(i,r.verbose),X(i,t,n),mi(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()},vi=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)}}}),yi=(e,t)=>{if(!t)for(let t of e)K.warn(t)};var bi=class{options;resolvedMinimumScore;scanConfig;steps=[];targetPath;constructor(e,t){this.targetPath=e,this.options=t}resolveConfig(){return this.steps.push(async()=>{this.scanConfig=await In(this.targetPath,this.options.configPath),this.resolvedMinimumScore=Jr(this.options.minScore,this.scanConfig.config.minScore)}),this}warnCustomRules(){return this.steps.push(()=>{yi(this.scanConfig.customRuleWarnings,this.options.isMachineReadable)}),this}async run(){let e=this.options.isMachineReadable?null:vi(`Scanning...`).start();for(let e of this.steps)await e();e?.succeed(`Scan complete`)}},xi=class extends bi{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 Or(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=Kr(this.monorepoCtx,this.rawOutputs,this.scanConfig.customRuleWarnings,e)}),this}output(){return this.steps.push(()=>{hi(this.result,this.resolvedMinimumScore,this.options.isMachineReadable,{json:this.options.json,score:this.options.score,verbose:this.options.verbose})}),this}},Si=class extends bi{context;rawOutput;result;buildContext(){return this.steps.push(async()=>{this.context=await Dr(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(()=>{gi(this.result,this.resolvedMinimumScore,this.options.isMachineReadable,{json:this.options.json,score:this.options.score,verbose:this.options.verbose})}),this}},Ci=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-9FpyLvnO.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=qr(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:wi}=e(import.meta.url)(`../../package.json`);n(t({meta:{name:`nestjs-doctor`,version:wi,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 Ci(e,wi).resolveTargetPath().handleInit().handleReport().validateMinScore().run();if(!t)return;let{targetPath:n,options:r}=t,i=await me(n);if(i){await new xi(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 Si(n,r).resolveConfig().buildContext().runRules().buildResult().warnCustomRules().output().run()}}));export{U as a,Or as c,Je as d,me as f,Kr as i,In as l,K as n,H as o,W as r,Dr as s,vi as t,Ye as u};
|
package/package.json
CHANGED