nestjs-doctor 0.4.20 → 0.4.22

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.
@@ -1,4 +1,4 @@
1
- import{existsSync as e,readFileSync as t,readdirSync as n,statSync as r}from"node:fs";import{join as i,resolve as a}from"node:path";import{readFile as o}from"node:fs/promises";import{performance as s}from"node:perf_hooks";import{Project as c,SyntaxKind as l}from"ts-morph";import{createJiti as u}from"jiti";import{glob as d}from"tinyglobby";import f from"picomatch";var p=class extends Error{constructor(e){super(e),this.name=`NestjsDoctorError`}},m=class extends p{constructor(e){super(e),this.name=`ConfigurationError`}},h=class extends p{constructor(e){super(e),this.name=`ScanError`}},g=class extends p{constructor(e){super(e),this.name=`ValidationError`}};async function _(e){let t=i(e,`nest-cli.json`);try{let e=await o(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 ee(e){let t=i(e,`package.json`),n={};try{let e=await o(t,`utf-8`);n=JSON.parse(e)}catch{}let r={...n.dependencies,...n.devDependencies},a=te(r[`@nestjs/core`]),s=ne(r),c=re(r);return{name:n.name??`unknown`,nestVersion:a,orm:s,framework:c,moduleCount:0,fileCount:0}}function te(e){return e?e.replace(/[\^~>=<]/g,``):null}function ne(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 re(e){return e[`@nestjs/platform-fastify`]?`fastify`:e[`@nestjs/platform-express`]||e[`@nestjs/core`]?`express`:null}const v={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`]},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(i(e,t))}catch{}try{let t=await o(i(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{...v}}async function oe(e){let t=await o(e,`utf-8`);return se(JSON.parse(t))}function se(e){return{...v,...e,exclude:[...v.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})}}}}},y=new Set([`Get`,`Post`,`Put`,`Patch`,`Delete`,`Head`,`Options`,`All`]);function b(e,t){return e.getDecorator(t)!==void 0}function x(e){return b(e,`Controller`)}function S(e){return b(e,`Injectable`)}function C(e){return b(e,`Injectable`)||b(e,`Controller`)||b(e,`Resolver`)||b(e,`WebSocketGateway`)}function de(e){return b(e,`Module`)}function w(e){return e.getDecorators().some(e=>y.has(e.getName()))}const fe=new Set([`TsRestHandler`,`GrpcMethod`,`GrpcStreamMethod`]);function pe(e){return e.getDecorators().some(e=>fe.has(e.getName()))}const me={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(x(t))for(let n of t.getMethods()){if(!n.getDecorators().some(e=>y.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})}}},he=/=>\s*(\w+)/;function T(e,t){let n=[];for(let r of e.getClasses()){let e=r.getDecorator(`Module`);if(!e)continue;let i=r.getName()??`AnonymousModule`,a=e.getArguments()[0],o={name:i,filePath:t,classDeclaration:r,imports:[],exports:[],providers:[],controllers:[]};if(a&&a.getKind()===l.ObjectLiteralExpression){let e=a.asKind(l.ObjectLiteralExpression);e&&(o.imports=D(e,`imports`),o.exports=D(e,`exports`),o.providers=D(e,`providers`),o.controllers=D(e,`controllers`))}n.push(o)}return n}function E(e,t){let n=new Map,r=new Map;for(let r of t){let t=e.getSourceFile(r);if(t)for(let e of T(t,r))n.set(e.name,e)}for(let[e,t]of n){let i=new Set;for(let e of t.imports)n.has(e)&&i.add(e);r.set(e,i)}let i=new Map;for(let e of n.values())for(let t of e.providers)i.set(t,e);return{modules:n,edges:r,providerToModule:i}}function D(e,t){let n=e.getProperty(t);if(!n)return[];let r=n.getChildrenOfKind(l.ArrayLiteralExpression)[0];return r?r.getElements().map(e=>{let t=e.getText();if(t.startsWith(`forwardRef`)){let e=t.match(he);return e?e[1]:t}return t.startsWith(`...`)?t.slice(3).trim():t}):[]}function O(e,t,n){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 r=t.getSourceFile(n);if(!r)return;let i=T(r,n);for(let t of i)e.modules.set(t.name,t);for(let t of i){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 ge(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 _e(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(`
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=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 ve={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=ge(e.moduleGraph);for(let n of t){let t=n.join(` -> `),r=e.moduleGraph.modules.get(n[0]),i=_e(n,e);e.report({filePath:r?.filePath??`unknown`,message:`Circular module dependency detected: ${t}`,help:i,line:r?.classDeclaration.getStartLineNumber()??1,column:1})}}},ye=[`Service`,`Repository`,`Gateway`,`Resolver`],be=[`Guard`,`Interceptor`,`Pipe`,`Filter`];function j(e){return typeof e==`object`&&!!e}function xe(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 Se={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=xe(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=ye.some(e=>n.endsWith(e)),o=be.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})}}}},Ce=/\.(\w+)$/,we=/^(\w+)</,Te=new Set([`PrismaService`,`PrismaClient`,`EntityManager`,`DataSource`,`Repository`,`Connection`,`MongooseModel`,`InjectModel`,`InjectRepository`,`MikroORM`,`DrizzleService`]),Ee={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(!x(t))continue;let n=t.getConstructors()[0];if(n){for(let t of n.getParameters()){let n=De(t.getType().getText());if(Te.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 De(e){let t=e.match(Ce);if(t)return t[1];let n=e.match(we);return n?n[1]:e}const Oe=/\.(\w+)$/,ke=/^(\w+)</,Ae=new Set([`PrismaService`,`PrismaClient`,`EntityManager`,`DataSource`,`Connection`,`MikroORM`]),je={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(!S(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=Me(t.getType().getText());if(Ae.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 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=[/Repository$/,/Repo$/],Ie={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(!x(t))continue;let n=t.getConstructors()[0];if(n){for(let t of n.getParameters()){let n=Le(t.getType().getText());if(Fe.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 Le(e){let t=e.match(Ne);if(t)return t[1];let n=e.match(Pe);return n?n[1]:e}const Re={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})}}},ze={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(C(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})}},Be=[`/repositories/`,`/entities/`,`/dto/`,`/guards/`,`/interceptors/`,`/pipes/`,`/strategies/`],Ve={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(`../`)&&Be.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(l.ReturnStatement).some(e=>{let t=e.getExpression();return!t||t.getKind()!==l.NewExpression?!1:t.asKindOrThrow(l.NewExpression).getExpression().getText()===`Promise`})}const He={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()||x(t)&&w(n)||pe(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();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(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`;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})}}}},Ue=[`providers`,`controllers`,`imports`,`exports`],We={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(!de(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 Ue){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)}}}}},Ge=new Set([`Get`,`Post`,`Put`,`Delete`,`Patch`,`All`,`Head`,`Options`]),Ke={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(!x(t))continue;let n=new Map;for(let r of t.getMethods())for(let t of r.getDecorators()){let i=t.getName();if(!Ge.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(x(t))for(let n of t.getMethods()){if(!n.getDecorators().some(e=>y.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})}}},N=new Set([`save`,`create`,`insert`,`update`,`delete`,`remove`,`send`,`emit`,`publish`,`dispatch`,`execute`,`fetch`,`load`,`upload`,`download`,`process`]),Je={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(w(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=a.toLowerCase();(N.has(o)||[...N].some(e=>o.startsWith(e)&&o!==e))&&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})}}}},Ye={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())b(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}))}},Xe={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`)&&b(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}))}}},Ze={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})}}}},Qe={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`)&&b(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}))}}},$e={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`||b(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}))}}},et={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`)&&b(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}))}}},tt={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(!(S(t)||x(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})}}}}},nt={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(!C(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})}}}},rt={onModuleInit:`OnModuleInit`,onModuleDestroy:`OnModuleDestroy`,onApplicationBootstrap:`OnApplicationBootstrap`,onApplicationShutdown:`OnApplicationShutdown`,beforeApplicationShutdown:`BeforeApplicationShutdown`},it={meta:{id:`correctness/require-lifecycle-interface`,category:`correctness`,severity:`warning`,description:`Classes with lifecycle methods should implement the corresponding NestJS interface`,help:`Add 'implements OnModuleInit' (or the appropriate interface) to the class declaration.`},check(e){for(let t of e.sourceFile.getClasses()){let n=t.getImplements().map(e=>e.getText());for(let r of t.getMethods()){let i=r.getName(),a=rt[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([l.ForStatement,l.ForOfStatement,l.ForInStatement,l.WhileStatement,l.DoStatement,l.AwaitExpression]),ot={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(!(b(t,`Injectable`)||b(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}}}}},st={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})}}},ct={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}))}},lt={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})}},ut=new Set([`readFileSync`,`writeFileSync`,`existsSync`,`mkdirSync`,`readdirSync`,`statSync`,`accessSync`,`appendFileSync`,`copyFileSync`,`renameSync`,`unlinkSync`]),dt={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()??``;ut.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})}}},ft={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})}}},pt=[`Guard`,`Interceptor`,`Filter`,`Middleware`,`Strategy`],mt={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(pt.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})}}},ht={meta:{id:`schema/require-cascade-rule`,category:`schema`,scope:`schema`,severity:`info`,description:`Relations should have explicit onDelete/cascade behavior defined`,help:`Add an explicit onDelete option (e.g. CASCADE, SET NULL) to avoid relying on database defaults.`},check(e){for(let t of e.schemaGraph.relations)if(!(t.type!==`many-to-one`&&t.type!==`one-to-one`)&&!t.onDelete){let n=e.schemaGraph.entities.get(t.fromEntity);if(!n)continue;e.report({filePath:n.filePath,entity:n.name,message:`Relation '${t.propertyName}' on '${t.fromEntity}' has no explicit onDelete behavior.`,help:this.meta.help})}}},gt={meta:{id:`schema/require-primary-key`,category:`schema`,scope:`schema`,severity:`error`,description:`Every entity must have at least one primary key column`,help:`Add a primary key column (e.g. @id in Prisma, @PrimaryColumn/@PrimaryGeneratedColumn in TypeORM).`},check(e){for(let t of e.schemaGraph.entities.values())t.columns.some(e=>e.isPrimary)||e.report({filePath:t.filePath,entity:t.name,message:`Entity '${t.name}' has no primary key column.`,help:this.meta.help})}},_t=/delete/i;function vt(e,t){let n=new Set(e.columns.map(e=>e.name.toLowerCase()));return n.has(`createdat`)||n.has(`created_at`)?!0:t===`typeorm`?e.columns.some(e=>e.type===`timestamp`&&e.isGenerated&&!_t.test(e.name)):t===`prisma`?e.columns.some(e=>e.type===`DateTime`&&e.defaultValue!==void 0&&e.defaultValue.includes(`now()`)):!1}const yt={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())vt(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})}},bt={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})}}},xt={meta:{id:`security/no-dangerous-redirects`,category:`security`,severity:`error`,description:`Redirects using user-controlled input (from @Query/@Param) are an open redirect vulnerability`,help:`Validate redirect URLs against an allowlist of safe destinations.`},check(e){for(let t of e.sourceFile.getClasses())if(x(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})}}}},St={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})}},Ct={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(!(b(t,`Injectable`)||b(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})}}},wt=/^(error|err|e|ex|exception)$/,Tt={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(!(wt.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})}}},Et=[{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)`}],Dt=[/secret/i,/password/i,/passwd/i,/api[_-]?key/i,/auth[_-]?token/i,/private[_-]?key/i,/access[_-]?key/i,/client[_-]?secret/i],Ot=new Set([`your-secret-here`,`changeme`,`password`]),kt=/^[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`)||Ot.has(e)||e.includes(` `)||kt.test(e))}function I(e){return Dt.some(t=>t.test(e))}function At(e){try{let t=Buffer.from(e,`base64`).toString(`utf-8`);return JSON.parse(t),!0}catch{return!1}}function jt(e){let t=e.getParent();if(!t)return!1;let n=t.asKind(l.PropertyAssignment);if(n)return P.has(n.getName());let r=t.asKind(l.VariableDeclaration);return r?P.has(r.getName()):!1}const Mt={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 Et)if(r.test(t)){if(i===`Base64 key`&&(At(t)||jt(n)))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||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(l.PropertyAssignment);for(let t of r){let n=t.getName(),r=t.getInitializer();!r||r.getKind()!==l.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})}}},Nt=RegExp(`(?:^|[^a-zA-Z])\\w*(?:${[`Entity`,`Model`].join(`|`)})(?:[^a-zA-Z]|$)`),Pt={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(x(t))for(let n of t.getMethods()){if(!w(n))continue;let t=n.getReturnType().getText();Nt.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})}}},Ft={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})}}},It=new Set([`md5`,`sha1`]),L=[me,Ie,Ee,je,Se,Re,ze,Ve,ue,ve,tt,it,qe,Ke,Xe,et,Ye,Qe,He,We,$e,nt,Je,Ze,Mt,St,{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();It.has(i)&&e.report({filePath:e.filePath,message:`Weak hashing algorithm '${i}' used in createHash().`,help:this.meta.help,line:n.getStartLineNumber(),column:1})}}},Ct,bt,Tt,xt,Ft,Pt,dt,ot,st,lt,mt,ft,ct,gt,yt,ht];function Lt(){return[...L]}function Rt(e){return e.meta.scope===`project`}function zt(e){return e.meta.scope===`schema`}function Bt(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 z(e){let t=[],n=[],r=[];for(let i of e)zt(i)?r.push(i):Rt(i)?n.push(i):t.push(i);return{fileRules:t,projectRules:n,schemaRules:r}}const Vt=new Set([`security`,`performance`,`correctness`,`architecture`]),Ht=new Set([`error`,`warning`,`info`]),Ut=new Set([`file`,`project`]),B=`custom/`;function Wt(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`||!Vt.has(n.category)||!Ht.has(n.severity)||n.scope!==void 0&&!Ut.has(n.scope))}function Gt(e){return e.meta.id.startsWith(B)?e:{...e,meta:{...e.meta,id:`${B}${e.meta.id}`}}}async function Kt(t,i){let o=[],s=[],c=a(i,t);if(!e(c))return s.push(`Custom rules directory not found: ${c}`),{rules:o,warnings:s};if(!r(c).isDirectory())return s.push(`Custom rules path is not a directory: ${c}`),{rules:o,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:o,warnings:s}}let d=l.filter(e=>e.endsWith(`.ts`));if(d.length===0)return s.push(`No rule files (.ts) found in: ${c}`),{rules:o,warnings:s};let f=u(c,{interopDefault:!0});for(let e of d){let t=a(c,e),n;try{n=await f.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))Wt(i)?(o.push(Gt(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:o,warnings:s}}function qt(e,t){return e.customRulesDir?Kt(e.customRulesDir,t):Promise.resolve({rules:[],warnings:[]})}async function V(e,t){let n=await ae(e,t),{rules:r,warnings:i}=await qt(n,e),a=Bt(L,r,i),{fileRules:o,projectRules:s,schemaRules:c}=z(R(n,a));return{combinedRules:a,config:n,customRuleWarnings:i,fileRules:o,projectRules:s,schemaRules:c}}async function H(e,t={}){return(await d(t.include??v.include,{cwd:e,absolute:!0,ignore:t.exclude??v.exclude})).sort()}async function Jt(e,t,n={}){let r=await Promise.all([...t.projects.entries()].map(async([t,r])=>[t,await H(i(e,r),n)])),a=new Map;for(let[e,t]of r)a.set(e,t);return a}function Yt(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 Xt=/import\([^)]+\)\.(\w+)/,Zt=/^(\w+)</;function U(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 Qt(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 W(e,t){let n=new Map;for(let r of t){let t=e.getSourceFile(r);if(t)for(let e of U(t,r))n.set(e.name,e)}return n}function G(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 U(r,n))e.set(t.name,t)}function Qt(e){let t=e.match(Xt);if(t)return t[1];let n=e.match(Zt);return n?n[1]:e}const $t=/^model\s+(\w+)\s*\{/,en=/^enum\s+(\w+)\s*\{/,tn=/^(\w+)\s+(\w+)(\?)?(\[\])?(.*)$/,nn=/@(\w+)(\((?:[^()]*|\([^()]*\))*\))?/g,rn=/@default\(((?:[^()]*|\([^()]*\))*)\)/,an=/^@@map\(\s*"([^"]+)"\s*\)/;function on(r){let a=i(r,`prisma`,`schema.prisma`);if(e(a)){let e=i(r,`prisma`),t=n(e).filter(e=>e.endsWith(`.prisma`));return t.length>1?t.map(t=>i(e,t)):[a]}let o=i(r,`schema.prisma`);if(e(o))return[o];try{let n=i(r,`package.json`),a=JSON.parse(t(n,`utf-8`)).prisma?.schema;if(a){let t=i(r,a);if(e(t))return[t]}}catch{}return[]}function sn(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=$t.exec(t);if(a){o={type:`model`,name:a[1]},s=[],c=[],l=[],u=void 0;continue}let d=en.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=ln.exec(t);e&&(l=e[1].split(`,`).map(e=>e.trim()));let n=un(t);n&&c.push(n);let r=an.exec(t);r&&(u=r[1]);continue}let e=dn(t);e&&s.push(e)}}}return{models:n,enums:r}}const cn=/^@@(index|unique)\(\[([^\]]*)\]\)/,ln=/^@@id\(\[([^\]]*)\]\)/;function un(e){let t=cn.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 dn(e){let t=tn.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(nn.source,nn.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 fn(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=rn.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 pn=/onDelete:\s*(\w+)/;function mn(e){let t=e.attributes.find(e=>e.startsWith(`@relation`));if(!t)return;let n=pn.exec(t);return n?n[1]:void 0}function hn(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=mn(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=fn(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=on(n);if(r.length===0)return[];let{models:i,enums:a}=sn(r);return hn(i,a)}},_n=/=>\s*(\w+)/,vn=new Set([`Column`,`PrimaryColumn`,`PrimaryGeneratedColumn`,`CreateDateColumn`,`UpdateDateColumn`,`DeleteDateColumn`,`VersionColumn`]),yn={OneToOne:`one-to-one`,OneToMany:`one-to-many`,ManyToOne:`many-to-one`,ManyToMany:`many-to-many`};function K(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 bn(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 xn(e){let t=e.getDecorator(`Entity`);if(!t)return e.getName()??`UnknownEntity`;let n=bn(t);if(n)return n;let r=K(t);return r?.name?r.name.replace(/['"]/g,``):e.getName()??`UnknownEntity`}function Sn(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=bn(t);l&&(a=l);let u=K(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 Cn(e,t,n){let r=yn[n.getName()];if(!r)return null;let i=n.getArguments();if(i.length===0)return null;let a=i[0].getText(),o=_n.exec(a);if(!o)return null;let s=o[1],c=K(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 wn(e){if(!b(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()===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=K(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(vn.has(r)){let t=Sn(e,n);c&&(t.hasIndex=!0),i.push(t);break}if(r in yn){let r=Cn(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 Tn={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=wn(e);t&&n.push(t)}}return n}}};function q(e,t,n,r){let i={entities:new Map,relations:[],orm:n??`unknown`};if(!n)return i;let a=Tn[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 En(e){return{entities:[...e.entities.values()],relations:e.relations,orm:e.orm}}function Dn(e,t,n,r){for(let[t,r]of e.entities)r.filePath===n&&e.entities.delete(t);let i=Tn[e.orm];if(!i?.supportsIncrementalUpdate){On(e);return}let a=i.extract(t,[n],r);for(let t of a)e.entities.set(t.name,t);On(e)}function On(e){let t=[];for(let n of e.entities.values())t.push(...n.relations);e.relations=t}async function J(e,t){let{config:n,fileRules:r,projectRules:i,schemaRules:a}=t,[o,s]=await Promise.all([H(e,n),ee(e)]),c=Yt(o);return{astProject:c,config:n,fileRules:r,files:o,moduleGraph:E(c,o),project:s,projectRules:i,providers:W(c,o),schemaGraph:q(c,o,s.orm,e),schemaRules:a,targetPath:e}}async function kn(e,t={}){let n=await V(e,t.config);return{context:await J(e,n),customRuleWarnings:n.customRuleWarnings}}function An(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),O(e.moduleGraph,e.astProject,t),G(e.providers,e.astProject,t),e.schemaGraph&&Dn(e.schemaGraph,e.astProject,t,e.targetPath)}async function jn(e,t,n){let{config:r,combinedRules:a}=t,o=await Jt(e,n,r),s=await Promise.all([...o.entries()].filter(([,e])=>e.length>0).map(async([t,o])=>{let s=i(e,n.projects.get(t)),[c,l]=await Promise.all([ee(s),ce(s,r)]),u=Yt(o),d=E(u,o),f=W(u,o),p=q(u,o,c.orm,s),{fileRules:m,projectRules:h,schemaRules:g}=z(R(l,a));return[t,{astProject:u,config:l,fileRules:m,files:o,moduleGraph:d,project:c,projectRules:h,providers:f,schemaGraph:p,schemaRules:g,targetPath:s}]}));return{subProjects:new Map(s)}}const Mn=e=>f.makeRe(e,{windows:!1}),Nn=/\\/g,Pn=/\/$/,Fn=(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(Mn):[];if(r.size===0&&i.length===0)return e;let a=n.replace(Nn,`/`).replace(Pn,``);return e.filter(e=>{if(r.has(e.rule))return!1;let t=e.filePath.replace(Nn,`/`),n=t.startsWith(`${a}/`)?t.slice(a.length+1):t;return!i.some(e=>e.test(n))})};function In(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 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 Ln(e,t,n,r){let i=[],a=[];for(let o of t){let t=In(e,o,n,r);i.push(...t.diagnostics),a.push(...t.errors)}return{diagnostics:i,errors:a}}function Rn(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 zn(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 Bn(e){return e instanceof Error?e.message:String(e)}function Y(e,t,n){return{diagnostics:Fn(e,n.config,n.targetPath),errors:t.map(e=>({ruleId:e.ruleId,error:Bn(e.error)}))}}function Vn(e,t){let n=Ln(e.astProject,[t],e.fileRules,e.config);return Y(n.diagnostics,n.errors,e)}function Hn(e){let t=Ln(e.astProject,e.files,e.fileRules,e.config);return Y(t.diagnostics,t.errors,e)}function Un(e){let t={moduleGraph:e.moduleGraph,providers:e.providers,config:e.config},n=Rn(e.astProject,e.files,e.projectRules,t),{diagnostics:r,errors:i}=Y(n.diagnostics,n.errors,e),a=X(e);return r.push(...a.diagnostics),i.push(...a.errors),{diagnostics:r,errors:i}}function X(e){if(!e.schemaGraph||e.schemaRules.length===0||e.schemaGraph.entities.size===0)return{diagnostics:[],errors:[]};let t=zn(e.schemaGraph,e.schemaRules);return Y(t.diagnostics,t.errors,e)}function Z(e){let t=s.now(),n=Hn(e),r=Un(e),i=s.now()-t;return{diagnostics:[...n.diagnostics,...r.diagnostics],elapsedMs:i,ruleErrors:[...n.errors,...r.errors]}}function Wn(e){return e>=90?`Excellent`:e>=75?`Good`:e>=50?`Fair`:e>=25?`Poor`:`Critical`}const Gn={error:3,warning:1.5,info:.5},Kn={security:1.5,correctness:1.3,schema:1.1,architecture:1,performance:.8};function qn(e,t){if(t===0)return{value:100,label:Wn(100)};let n=0;for(let t of e){let e=Gn[t.severity],r=Kn[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:Wn(i)}}function Jn(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 Q(e,t,n=[]){let{diagnostics:r,ruleErrors:i,elapsedMs:a}=t,o=e.schemaGraph??q(e.astProject,e.files,e.project.orm,e.targetPath),s=qn(r,e.files.length),c=Jn(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:En(o)},moduleGraph:e.moduleGraph,schemaGraph:o,customRuleWarnings:n,files:e.files,providers:e.providers}}function Yn(e,t,n,r){let i=[],a=[],o=[],s=new Map,c=0;for(let[n,r]of e.subProjects){let e=Q(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=qn(a,c),u=Jn(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}}}async function Xn(e,t,n){let r=s.now(),i=await jn(e,t,n),a=new Map;for(let[e,t]of i.subProjects)a.set(e,Z(t));let o=s.now()-r;return Yn(i,a,t.customRuleWarnings,o)}async function Zn(e,t={}){let n=await V(e,t.config),r=await _(e);if(r)return{isMonorepo:!0,monorepo:await Xn(e,n,r)};let i=await J(e,n);return{isMonorepo:!1,single:Q(i,Z(i),n.customRuleWarnings)}}function Qn(e){return`line`in e}function $n(e){return`entity`in e}function $(t){if(!t||t.trim()===``)throw new g(`Path must be a non-empty string. Received an empty path.`);let n=a(t);if(!e(n))throw new g(`Path does not exist: ${n}`);if(!r(n).isDirectory())throw new g(`Path must be a directory, not a file: ${n}`);return n}async function er(e,t={}){let n=$(e),r=await V(n,t.config),i=await J(n,r),{result:a}=Q(i,Z(i),r.customRuleWarnings);return a}async function tr(e,t={}){let n=$(e),r=await V(n,t.config),i=await _(n);if(!i){let e=await J(n,r),{result:t}=Q(e,Z(e),r.customRuleWarnings);return{isMonorepo:!1,subProjects:[{name:`default`,result:t}],combined:t,elapsedMs:t.elapsedMs}}let{result:a}=await Xn(n,r,i);return a}export{m as ConfigurationError,p as NestjsDoctorError,h as ScanError,g as ValidationError,Zn as autoScan,J as buildAnalysisContext,Q as buildResult,Hn as checkAllFiles,Vn as checkFile,Un as checkProject,X as checkSchema,er as diagnose,tr as diagnoseMonorepo,q as extractSchema,Lt as getRules,Qn as isCodeDiagnostic,$n as isSchemaDiagnostic,kn as prepareAnalysis,V as resolveScanConfig,An as updateFile,O as updateModuleGraphForFile,G as updateProvidersForFile};
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}from"ts-morph";import{createJiti as d}from"jiti";import{glob as f}from"tinyglobby";import p from"picomatch";var m=class extends Error{constructor(e){super(e),this.name=`NestjsDoctorError`}},h=class extends m{constructor(e){super(e),this.name=`ConfigurationError`}},g=class extends m{constructor(e){super(e),this.name=`ScanError`}},_=class extends m{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 v={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`]},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{...v}}async function se(e){let t=await s(e,`utf-8`);return ce(JSON.parse(t))}function ce(e){return{...v,...e,exclude:[...v.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})}}}}},y=new Set([`Get`,`Post`,`Put`,`Patch`,`Delete`,`Head`,`Options`,`All`]);function b(e,t){return e.getDecorator(t)!==void 0}function x(e){return b(e,`Controller`)}function S(e){return b(e,`Injectable`)}function C(e){return b(e,`Injectable`)||b(e,`Controller`)||b(e,`Resolver`)||b(e,`WebSocketGateway`)}function fe(e){return b(e,`Module`)}function w(e){return e.getDecorators().some(e=>y.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(x(t))for(let n of t.getMethods()){if(!n.getDecorators().some(e=>y.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})}}},ge=/=>\s*(\w+)/,_e=/\.js$/;function T(e,t){let n=[];for(let r of e.getClasses()){let e=r.getDecorator(`Module`);if(!e)continue;let i=r.getName()??`AnonymousModule`,a=e.getArguments()[0],o={name:i,filePath:t,classDeclaration:r,imports:[],exports:[],providers:[],controllers:[]};if(a&&a.getKind()===u.ObjectLiteralExpression){let e=a.asKind(u.ObjectLiteralExpression);e&&(o.imports=D(e,`imports`),o.exports=D(e,`exports`),o.providers=D(e,`providers`),o.controllers=D(e,`controllers`))}n.push(o)}return n}function E(e,t){let n=new Map,r=new Map;for(let r of t){let t=e.getSourceFile(r);if(t)for(let e of T(t,r))n.set(e.name,e)}for(let[e,t]of n){let i=new Set;for(let e of t.imports)n.has(e)&&i.add(e);r.set(e,i)}let i=new Map;for(let e of n.values())for(let t of e.providers)i.set(t,e);return{modules:n,edges:r,providerToModule:i}}const ve=new Set([`forRoot`,`forRootAsync`,`forFeature`,`forFeatureAsync`,`forChild`,`forChildAsync`,`register`,`registerAsync`]);function D(e,t){let n=e.getProperty(t);if(!n)return[];let r=n.asKind(u.PropertyAssignment);if(!r)return[];let i=r.getInitializer();return i?O(i,e.getSourceFile(),0):[]}function O(e,t,n){if(n>5)return[];let r=e.getKind();if(r===u.ArrayLiteralExpression){let r=e.asKindOrThrow(u.ArrayLiteralExpression),i=[];for(let e of r.getElements())i.push(...ye(e,t,n));return i}return r===u.CallExpression?k(e.asKindOrThrow(u.CallExpression),t,n):r===u.Identifier?M(e.getText(),t,n+1):[]}function ye(e,t,n){let r=e.getText();if(r.startsWith(`forwardRef`)){let e=r.match(ge);return e?[e[1]]:[r]}let i=e.getKind();return i===u.SpreadElement?O(e.asKindOrThrow(u.SpreadElement).getExpression(),t,n):i===u.CallExpression?k(e.asKindOrThrow(u.CallExpression),t,n):i===u.PropertyAccessExpression?[e.asKindOrThrow(u.PropertyAccessExpression).getExpression().getText()]:(u.Identifier,[r])}function k(e,t,n){let r=e.getExpression();if(r.getKind()===u.PropertyAccessExpression){let i=r.asKindOrThrow(u.PropertyAccessExpression),a=i.getName();if(a===`concat`){let r=O(i.getExpression(),t,n),a=[];for(let r of e.getArguments())a.push(...O(r,t,n));return[...r,...a]}return ve.has(a),[i.getExpression().getText()]}return r.getKind()===u.Identifier?N(r.getText(),t,n+1):[]}function A(e,t){if(!e.startsWith(`.`))return;let n=o(i(t.getFilePath()),e),r=t.getProject(),a=[`${n}.ts`,`${n}/index.ts`,n,n.replace(_e,`.ts`)];for(let e of a){let t=r.getSourceFile(e);if(t)return t}}function j(e,t){for(let n of t.getImportDeclarations())for(let r of n.getNamedImports())if((r.getAliasNode()?r.getAliasNode().getText():r.getName())===e){let e=A(n.getModuleSpecifierValue(),t);return e?{sourceFile:e,localName:r.getName()}:void 0}for(let n of t.getExportDeclarations())if(n.getModuleSpecifierValue()){for(let r of n.getNamedExports())if((r.getAliasNode()?r.getAliasNode().getText():r.getName())===e){let e=A(n.getModuleSpecifierValue(),t);return e?{sourceFile:e,localName:r.getName()}:void 0}}}function M(e,t,n){if(n>5)return[];for(let r of t.getStatements()){if(r.getKind()!==u.VariableStatement)continue;let i=r.asKindOrThrow(u.VariableStatement);for(let r of i.getDeclarations())if(r.getName()===e){let e=r.getInitializer();if(e)return O(e,t,n)}}let r=j(e,t);return r?M(r.localName,r.sourceFile,n+1):[]}function be(e,t,n){for(let r of t.getStatements()){if(r.getKind()!==u.VariableStatement)continue;let i=r.asKindOrThrow(u.VariableStatement);for(let r of i.getDeclarations()){if(r.getName()!==e)continue;let i=r.getInitializer();if(!i||i.getKind()!==u.ArrowFunction)continue;let a=i.asKindOrThrow(u.ArrowFunction).getBody();if(a.getKind()!==u.Block)return O(a,t,n);let o=[];for(let e of a.getDescendantsOfKind(u.ReturnStatement)){let r=e.getExpression();r&&o.push(...O(r,t,n))}return o}}}function N(e,t,n){if(n>5)return[];for(let r of t.getStatements()){if(r.getKind()!==u.FunctionDeclaration)continue;let i=r.asKindOrThrow(u.FunctionDeclaration);if(i.getName()!==e)continue;let a=[];for(let e of i.getDescendantsOfKind(u.ReturnStatement)){let r=e.getExpression();r&&a.push(...O(r,t,n))}return a}let r=be(e,t,n);if(r)return r;let i=j(e,t);return i?N(i.localName,i.sourceFile,n+1):[]}function P(e,t,n){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 r=t.getSourceFile(n);if(!r)return;let i=T(r,n);for(let t of i)e.modules.set(t.name,t);for(let t of i){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 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 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=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(`
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=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 Ce={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=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 L(e){return typeof e==`object`&&!!e}function Ee(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 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(!x(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(!S(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(!x(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(C(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 R(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()||x(t)&&w(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();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(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`;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})}}}},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(!x(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(x(t))for(let n of t.getMethods()){if(!n.getDecorators().some(e=>y.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})}}},z=new Set([`save`,`create`,`insert`,`update`,`delete`,`remove`,`send`,`emit`,`publish`,`dispatch`,`execute`,`fetch`,`load`,`upload`,`download`,`process`]),$e={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(w(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=a.toLowerCase();(z.has(o)||[...z].some(e=>o.startsWith(e)&&o!==e))&&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})}}}},et={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())b(t,`Catch`)&&(t.getExtends()||t.getMethods().some(e=>e.getName()===`catch`)||e.report({filePath:e.filePath,message:`Exception filter '${t.getName()}' has @Catch() but is missing the 'catch()' method.`,help:this.meta.help,line:t.getStartLineNumber(),column:1}))}},tt={meta:{id:`correctness/no-missing-guard-method`,category:`correctness`,severity:`error`,description:`Guard classes must implement the canActivate() method`,help:`Add a canActivate(context: ExecutionContext) method to the guard class.`},check(e){for(let t of e.sourceFile.getClasses()){let n=t.getName()??``;n.endsWith(`Guard`)&&b(t,`Injectable`)&&(t.getExtends()||t.getMethods().some(e=>e.getName()===`canActivate`)||e.report({filePath:e.filePath,message:`Guard '${n}' is missing the 'canActivate()' method.`,help:this.meta.help,line:t.getStartLineNumber(),column:1}))}}},nt={meta:{id:`correctness/no-missing-injectable`,category:`correctness`,severity:`error`,description:`Provider classes with constructor dependencies must have the @Injectable() decorator`,help:`Add @Injectable() to providers that inject constructor dependencies.`,scope:`project`},check(e){let t=new Set([...e.providers.values()].map(e=>e.name)),n=new Map;for(let t of e.files){let r=e.project.getSourceFile(t);if(r)for(let e of r.getClasses()){let r=e.getName();if(r){let i=n.get(r)??[];i.push({cls:e,filePath:t}),n.set(r,i)}}}for(let r of e.moduleGraph.modules.values())for(let i of r.providers){if(t.has(i))continue;let a=n.get(i);if(a)for(let{cls:t,filePath:n}of a){let a=(t.getConstructors()[0]?.getParameters().length??0)>0;!(t.getDecorator(`Injectable`)||t.getDecorator(`Resolver`)||t.getDecorator(`WebSocketGateway`))&&a&&e.report({filePath:n,message:`Class '${i}' is listed in '${r.name}' providers but is missing @Injectable() decorator.`,help:this.meta.help,line:t.getStartLineNumber(),column:1})}}}},rt={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`)&&b(t,`Injectable`)&&(t.getExtends()||t.getMethods().some(e=>e.getName()===`intercept`)||e.report({filePath:e.filePath,message:`Interceptor '${n}' is missing the 'intercept()' method.`,help:this.meta.help,line:t.getStartLineNumber(),column:1}))}}},it={meta:{id:`correctness/no-missing-module-decorator`,category:`correctness`,severity:`warning`,description:`Classes named *Module should have a @Module() decorator`,help:`Add @Module({}) decorator to the class, or rename it if it is not a NestJS module.`},check(e){for(let t of e.sourceFile.getClasses()){let n=t.getName()??``;n.endsWith(`Module`)&&(n===`Module`||n===`DynamicModule`||b(t,`Module`)||e.report({filePath:e.filePath,message:`Class '${n}' is named like a module but is missing the @Module() decorator.`,help:this.meta.help,line:t.getStartLineNumber(),column:1}))}}},at={meta:{id:`correctness/no-missing-pipe-method`,category:`correctness`,severity:`error`,description:`Pipe classes must implement the transform() method`,help:`Add a transform(value: any, metadata: ArgumentMetadata) method to the pipe class.`},check(e){for(let t of e.sourceFile.getClasses()){let n=t.getName()??``;n.endsWith(`Pipe`)&&b(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}))}}},ot={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(!(S(t)||x(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})}}}}},st={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(!C(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})}}}},ct={onModuleInit:`OnModuleInit`,onModuleDestroy:`OnModuleDestroy`,onApplicationBootstrap:`OnApplicationBootstrap`,onApplicationShutdown:`OnApplicationShutdown`,beforeApplicationShutdown:`BeforeApplicationShutdown`},lt={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=ct[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}))}}}},ut=new Set([u.ForStatement,u.ForOfStatement,u.ForInStatement,u.WhileStatement,u.DoStatement,u.AwaitExpression]),dt={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(!(b(t,`Injectable`)||b(t,`Controller`)))continue;let n=t.getConstructors()[0];if(!n)continue;let r=n.getBody();if(r){for(let i of r.getDescendants())if(ut.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}}}}},ft={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})}}},pt={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}))}},mt={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})}},ht=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(u.CallExpression);for(let n of t){let t=n.getExpression().getText().split(`.`).pop()??``;ht.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})}}},_t={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})}}},vt=[`Guard`,`Interceptor`,`Filter`,`Middleware`,`Strategy`],yt={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(vt.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})}}},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})}},St=/delete/i;function Ct(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&&!St.test(e.name)):t===`prisma`?e.columns.some(e=>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())Ct(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})}},Tt={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})}}},Et={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(x(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})}}}},Dt={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})}},Ot={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(!(b(t,`Injectable`)||b(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})}}},kt=/^(error|err|e|ex|exception)$/,At={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(!(kt.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})}}},jt=[{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)`}],Mt=[/secret/i,/password/i,/passwd/i,/api[_-]?key/i,/auth[_-]?token/i,/private[_-]?key/i,/access[_-]?key/i,/client[_-]?secret/i],Nt=new Set([`your-secret-here`,`changeme`,`password`]),Pt=/^[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`)||Nt.has(e)||e.includes(` `)||Pt.test(e))}function H(e){return Mt.some(t=>t.test(e))}function Ft(e){try{let t=Buffer.from(e,`base64`).toString(`utf-8`);return JSON.parse(t),!0}catch{return!1}}function It(e){let t=e.getParent();if(!t)return!1;let n=t.asKind(u.PropertyAssignment);if(n)return B.has(n.getName());let r=t.asKind(u.VariableDeclaration);return r?B.has(r.getName()):!1}const Lt={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 jt)if(r.test(t)){if(i===`Base64 key`&&(Ft(t)||It(n)))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||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(u.PropertyAssignment);for(let t of r){let n=t.getName(),r=t.getInitializer();!r||r.getKind()!==u.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})}}},Rt=RegExp(`(?:^|[^a-zA-Z])\\w*(?:${[`Entity`,`Model`].join(`|`)})(?:[^a-zA-Z]|$)`),zt={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(x(t))for(let n of t.getMethods()){if(!w(n))continue;let t=n.getReturnType().getText();Rt.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})}}},Bt={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})}}},Vt=new Set([`md5`,`sha1`]),U=[he,Ve,je,Ie,De,Ue,We,Ke,de,Ce,ot,lt,Qe,Ze,tt,at,et,rt,qe,Ye,it,st,$e,nt,Lt,Dt,{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();Vt.has(i)&&e.report({filePath:e.filePath,message:`Weak hashing algorithm '${i}' used in createHash().`,help:this.meta.help,line:n.getStartLineNumber(),column:1})}}},Ot,Tt,At,Et,Bt,zt,gt,dt,ft,mt,yt,_t,pt,xt,wt,bt];function Ht(){return[...U]}function Ut(e){return e.meta.scope===`project`}function Wt(e){return e.meta.scope===`schema`}function Gt(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 W(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 Kt(e){let t=[],n=[],r=[];for(let i of e)Wt(i)?r.push(i):Ut(i)?n.push(i):t.push(i);return{fileRules:t,projectRules:n,schemaRules:r}}const qt=new Set([`security`,`performance`,`correctness`,`architecture`]),Jt=new Set([`error`,`warning`,`info`]),Yt=new Set([`file`,`project`]),Xt=`custom/`;function Zt(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`||!qt.has(n.category)||!Jt.has(n.severity)||n.scope!==void 0&&!Yt.has(n.scope))}function Qt(e){return e.meta.id.startsWith(Xt)?e:{...e,meta:{...e.meta,id:`${Xt}${e.meta.id}`}}}async function $t(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 f=d(c,{interopDefault:!0});for(let e of u){let t=o(c,e),n;try{n=await f.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))Zt(i)?(a.push(Qt(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 en(e,t){return e.customRulesDir?$t(e.customRulesDir,t):Promise.resolve({rules:[],warnings:[]})}async function G(e,t){let n=await oe(e,t),{rules:r,warnings:i}=await en(n,e),a=Gt(U,r,i),{fileRules:o,projectRules:s,schemaRules:c}=Kt(W(n,a));return{combinedRules:a,config:n,customRuleWarnings:i,fileRules:o,projectRules:s,schemaRules:c}}async function tn(e,t={}){return(await f(t.include??v.include,{cwd:e,absolute:!0,ignore:t.exclude??v.exclude})).sort()}async function nn(e,t,n={}){let r=await Promise.all([...t.projects.entries()].map(async([t,r])=>[t,await tn(a(e,r),n)])),i=new Map;for(let[e,t]of r)i.set(e,t);return i}function rn(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 an=/import\([^)]+\)\.(\w+)/,on=/^(\w+)</;function sn(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 un(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 cn(e,t){let n=new Map;for(let r of t){let t=e.getSourceFile(r);if(t)for(let e of sn(t,r))n.set(e.name,e)}return n}function ln(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 sn(r,n))e.set(t.name,t)}function un(e){let t=e.match(an);if(t)return t[1];let n=e.match(on);return n?n[1]:e}const dn=/^model\s+(\w+)\s*\{/,fn=/^enum\s+(\w+)\s*\{/,pn=/^(\w+)\s+(\w+)(\?)?(\[\])?(.*)$/,mn=/@(\w+)(\((?:[^()]*|\([^()]*\))*\))?/g,hn=/@default\(((?:[^()]*|\([^()]*\))*)\)/,gn=/^@@map\(\s*"([^"]+)"\s*\)/;function _n(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 vn(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=dn.exec(t);if(a){o={type:`model`,name:a[1]},s=[],c=[],l=[],u=void 0;continue}let d=fn.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=bn.exec(t);e&&(l=e[1].split(`,`).map(e=>e.trim()));let n=xn(t);n&&c.push(n);let r=gn.exec(t);r&&(u=r[1]);continue}let e=Sn(t);e&&s.push(e)}}}return{models:n,enums:r}}const yn=/^@@(index|unique)\(\[([^\]]*)\]\)/,bn=/^@@id\(\[([^\]]*)\]\)/;function xn(e){let t=yn.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 Sn(e){let t=pn.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 Cn(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=hn.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 wn=/onDelete:\s*(\w+)/;function Tn(e){let t=e.attributes.find(e=>e.startsWith(`@relation`));if(!t)return;let n=wn.exec(t);return n?n[1]:void 0}function En(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=Tn(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=Cn(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 Dn={supportsIncrementalUpdate:!1,extract(e,t,n){let r=_n(n);if(r.length===0)return[];let{models:i,enums:a}=vn(r);return En(i,a)}},On=/=>\s*(\w+)/,kn=new Set([`Column`,`PrimaryColumn`,`PrimaryGeneratedColumn`,`CreateDateColumn`,`UpdateDateColumn`,`DeleteDateColumn`,`VersionColumn`]),An={OneToOne:`one-to-one`,OneToMany:`one-to-many`,ManyToOne:`many-to-one`,ManyToMany:`many-to-many`};function K(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 jn(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 Mn(e){let t=e.getDecorator(`Entity`);if(!t)return e.getName()??`UnknownEntity`;let n=jn(t);if(n)return n;let r=K(t);return r?.name?r.name.replace(/['"]/g,``):e.getName()??`UnknownEntity`}function Nn(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=jn(t);l&&(a=l);let u=K(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 Pn(e,t,n){let r=An[n.getName()];if(!r)return null;let i=n.getArguments();if(i.length===0)return null;let a=i[0].getText(),o=On.exec(a);if(!o)return null;let s=o[1],c=K(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 Fn(e){if(!b(e,`Entity`))return null;let t=e.getName();if(!t)return null;let n=Mn(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=K(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(kn.has(r)){let t=Nn(e,n);c&&(t.hasIndex=!0),i.push(t);break}if(r in An){let r=Pn(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 In={prisma:Dn,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=Fn(e);t&&n.push(t)}}return n}}};function q(e,t,n,r){let i={entities:new Map,relations:[],orm:n??`unknown`};if(!n)return i;let a=In[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 Ln(e){return{entities:[...e.entities.values()],relations:e.relations,orm:e.orm}}function Rn(e,t,n,r){for(let[t,r]of e.entities)r.filePath===n&&e.entities.delete(t);let i=In[e.orm];if(!i?.supportsIncrementalUpdate){zn(e);return}let a=i.extract(t,[n],r);for(let t of a)e.entities.set(t.name,t);zn(e)}function zn(e){let t=[];for(let n of e.entities.values())t.push(...n.relations);e.relations=t}async function J(e,t){let{config:n,fileRules:r,projectRules:i,schemaRules:a}=t,[o,s]=await Promise.all([tn(e,n),te(e)]),c=rn(o);return{astProject:c,config:n,fileRules:r,files:o,moduleGraph:E(c,o),project:s,projectRules:i,providers:cn(c,o),schemaGraph:q(c,o,s.orm,e),schemaRules:a,targetPath:e}}async function Bn(e,t={}){let n=await G(e,t.config);return{context:await J(e,n),customRuleWarnings:n.customRuleWarnings}}function Vn(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),ln(e.providers,e.astProject,t),e.schemaGraph&&Rn(e.schemaGraph,e.astProject,t,e.targetPath)}async function Hn(e,t,n){let{config:r,combinedRules:i}=t,o=await nn(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=rn(o),d=E(u,o),f=cn(u,o),p=q(u,o,c.orm,s),{fileRules:m,projectRules:h,schemaRules:g}=Kt(W(l,i));return[t,{astProject:u,config:l,fileRules:m,files:o,moduleGraph:d,project:c,projectRules:h,providers:f,schemaGraph:p,schemaRules:g,targetPath:s}]}));return{subProjects:new Map(s)}}const Un=e=>p.makeRe(e,{windows:!1}),Wn=/\\/g,Gn=/\/$/,Kn=(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(Un):[];if(r.size===0&&i.length===0)return e;let a=n.replace(Wn,`/`).replace(Gn,``);return e.filter(e=>{if(r.has(e.rule))return!1;let t=e.filePath.replace(Wn,`/`),n=t.startsWith(`${a}/`)?t.slice(a.length+1):t;return!i.some(e=>e.test(n))})};function qn(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 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 Jn(e,t,n,r){let i=[],a=[];for(let o of t){let t=qn(e,o,n,r);i.push(...t.diagnostics),a.push(...t.errors)}return{diagnostics:i,errors:a}}function Yn(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 Xn(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 Zn(e){return e instanceof Error?e.message:String(e)}function Y(e,t,n){return{diagnostics:Kn(e,n.config,n.targetPath),errors:t.map(e=>({ruleId:e.ruleId,error:Zn(e.error)}))}}function Qn(e,t){let n=Jn(e.astProject,[t],e.fileRules,e.config);return Y(n.diagnostics,n.errors,e)}function $n(e){let t=Jn(e.astProject,e.files,e.fileRules,e.config);return Y(t.diagnostics,t.errors,e)}function er(e){let t={moduleGraph:e.moduleGraph,providers:e.providers,config:e.config},n=Yn(e.astProject,e.files,e.projectRules,t),{diagnostics:r,errors:i}=Y(n.diagnostics,n.errors,e),a=tr(e);return r.push(...a.diagnostics),i.push(...a.errors),{diagnostics:r,errors:i}}function tr(e){if(!e.schemaGraph||e.schemaRules.length===0||e.schemaGraph.entities.size===0)return{diagnostics:[],errors:[]};let t=Xn(e.schemaGraph,e.schemaRules);return Y(t.diagnostics,t.errors,e)}function X(e){let t=c.now(),n=$n(e),r=er(e),i=c.now()-t;return{diagnostics:[...n.diagnostics,...r.diagnostics],elapsedMs:i,ruleErrors:[...n.errors,...r.errors]}}function nr(e){return e>=90?`Excellent`:e>=75?`Good`:e>=50?`Fair`:e>=25?`Poor`:`Critical`}const rr={error:3,warning:1.5,info:.5},ir={security:1.5,correctness:1.3,schema:1.1,architecture:1,performance:.8};function Z(e,t){if(t===0)return{value:100,label:nr(100)};let n=0;for(let t of e){let e=rr[t.severity],r=ir[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:nr(i)}}function ar(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 Q(e,t,n=[]){let{diagnostics:r,ruleErrors:i,elapsedMs:a}=t,o=e.schemaGraph??q(e.astProject,e.files,e.project.orm,e.targetPath),s=Z(r,e.files.length),c=ar(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:Ln(o)},moduleGraph:e.moduleGraph,schemaGraph:o,customRuleWarnings:n,files:e.files,providers:e.providers}}function or(e,t,n,r){let i=[],a=[],o=[],s=new Map,c=0;for(let[n,r]of e.subProjects){let e=Q(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=Z(a,c),u=ar(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}}}async function sr(e,t,n){let r=c.now(),i=await Hn(e,t,n),a=new Map;for(let[e,t]of i.subProjects)a.set(e,X(t));let o=c.now()-r;return or(i,a,t.customRuleWarnings,o)}async function cr(e,t={}){let n=await G(e,t.config),r=await ee(e);if(r)return{isMonorepo:!0,monorepo:await sr(e,n,r)};let i=await J(e,n);return{isMonorepo:!1,single:Q(i,X(i),n.customRuleWarnings)}}function lr(e){return`line`in e}function ur(e){return`entity`in e}function $(t){if(!t||t.trim()===``)throw new _(`Path must be a non-empty string. Received an empty path.`);let n=o(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 dr(e,t={}){let n=$(e),r=await G(n,t.config),i=await J(n,r),{result:a}=Q(i,X(i),r.customRuleWarnings);return a}async function fr(e,t={}){let n=$(e),r=await G(n,t.config),i=await ee(n);if(!i){let e=await J(n,r),{result:t}=Q(e,X(e),r.customRuleWarnings);return{isMonorepo:!1,subProjects:[{name:`default`,result:t}],combined:t,elapsedMs:t.elapsedMs}}let{result:a}=await sr(n,r,i);return a}export{h as ConfigurationError,m as NestjsDoctorError,g as ScanError,_ as ValidationError,cr as autoScan,J as buildAnalysisContext,Q as buildResult,$n as checkAllFiles,Qn as checkFile,er as checkProject,tr as checkSchema,dr as diagnose,fr as diagnoseMonorepo,q as extractSchema,Ht as getRules,lr as isCodeDiagnostic,ur as isSchemaDiagnostic,Bn as prepareAnalysis,G as resolveScanConfig,Vn as updateFile,P as updateModuleGraphForFile,ln as updateProvidersForFile};
@@ -1,5 +1,5 @@
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{join as i,resolve as a}from"node:path";import{performance as o}from"node:perf_hooks";import{Project as s,SyntaxKind as c}from"ts-morph";import{existsSync as l,readFileSync as u,readdirSync as d,statSync as f}from"node:fs";import{createJiti as p}from"jiti";import{glob as m}from"tinyglobby";import h from"picomatch";import g from"picocolors";import _ from"ora";async function v(e){let t=i(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 y(e){let t=i(e,`package.json`),n={};try{let e=await r(t,`utf-8`);n=JSON.parse(e)}catch{}let a={...n.dependencies,...n.devDependencies},o=b(a[`@nestjs/core`]),s=ee(a),c=te(a);return{name:n.name??`unknown`,nestVersion:o,orm:s,framework:c,moduleCount:0,fileCount:0}}function b(e){return e?e.replace(/[\^~>=<]/g,``):null}function ee(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 te(e){return e[`@nestjs/platform-fastify`]?`fastify`:e[`@nestjs/platform-express`]||e[`@nestjs/core`]?`express`:null}const ne={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}},x={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`]},re=[`nestjs-doctor.config.json`,`.nestjs-doctor.json`];async function S(e,t){if(t)return ie(t);for(let t of re)try{return await ie(i(e,t))}catch{}try{let t=await r(i(e,`package.json`),`utf-8`),n=JSON.parse(t);if(n[`nestjs-doctor`]&&typeof n[`nestjs-doctor`]==`object`)return ae(n[`nestjs-doctor`])}catch{}return{...x}}async function ie(e){let t=await r(e,`utf-8`);return ae(JSON.parse(t))}function ae(e){return{...x,...e,exclude:[...x.exclude??[],...e.exclude??[]]}}async function oe(e,t){try{return await S(e)}catch{return t}}const se=[/Repository$/,/\.repository$/,/\.entity$/,/\.schema$/,/\.guard$/,/\.interceptor$/,/\.pipe$/,/\.filter$/,/\.strategy$/],ce={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){se.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 le(e){return w(e,`Injectable`)}function ue(e){return w(e,`Injectable`)||w(e,`Controller`)||w(e,`Resolver`)||w(e,`WebSocketGateway`)}function de(e){return w(e,`Module`)}function E(e){return e.getDecorators().some(e=>C.has(e.getName()))}const fe=new Set([`TsRestHandler`,`GrpcMethod`,`GrpcStreamMethod`]);function pe(e){return e.getDecorators().some(e=>fe.has(e.getName()))}const me={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(c.IfStatement),i=t.getDescendantsOfKind(c.ForStatement),a=t.getDescendantsOfKind(c.ForInStatement),o=t.getDescendantsOfKind(c.ForOfStatement),s=t.getDescendantsOfKind(c.WhileStatement),l=t.getDescendantsOfKind(c.SwitchStatement),u=i.length+a.length+o.length+s.length;(r.length>1||u>0||l.length>0)&&e.report({filePath:e.filePath,message:`Controller method '${n.getName()}' contains business logic (${r.length} if, ${u} loops, ${l.length} switch). Move to a service.`,help:this.meta.help,line:n.getStartLineNumber(),column:1});let d=t.getDescendantsOfKind(c.CallExpression).filter(e=>{let t=e.getExpression();if(t.getKind()===c.PropertyAccessExpression){let e=t.asKind(c.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})}}},he=/=>\s*(\w+)/;function ge(e,t){let n=[];for(let r of e.getClasses()){let e=r.getDecorator(`Module`);if(!e)continue;let i=r.getName()??`AnonymousModule`,a=e.getArguments()[0],o={name:i,filePath:t,classDeclaration:r,imports:[],exports:[],providers:[],controllers:[]};if(a&&a.getKind()===c.ObjectLiteralExpression){let e=a.asKind(c.ObjectLiteralExpression);e&&(o.imports=O(e,`imports`),o.exports=O(e,`exports`),o.providers=O(e,`providers`),o.controllers=O(e,`controllers`))}n.push(o)}return n}function D(e,t){let n=new Map,r=new Map;for(let r of t){let t=e.getSourceFile(r);if(t)for(let e of ge(t,r))n.set(e.name,e)}for(let[e,t]of n){let i=new Set;for(let e of t.imports)n.has(e)&&i.add(e);r.set(e,i)}let i=new Map;for(let e of n.values())for(let t of e.providers)i.set(t,e);return{modules:n,edges:r,providerToModule:i}}function O(e,t){let n=e.getProperty(t);if(!n)return[];let r=n.getChildrenOfKind(c.ArrayLiteralExpression)[0];return r?r.getElements().map(e=>{let t=e.getText();if(t.startsWith(`forwardRef`)){let e=t.match(he);return e?e[1]:t}return t.startsWith(`...`)?t.slice(3).trim():t}):[]}function _e(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 k(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 A(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 j=`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=A(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 j;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=A(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 ye={meta:{id:`architecture/no-circular-module-deps`,category:`architecture`,severity:`error`,description:`Circular dependencies in @Module() import graph`,help:j,scope:`project`},check(e){let t=k(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})}}},be=[`Service`,`Repository`,`Gateway`,`Resolver`],xe=[`Guard`,`Interceptor`,`Pipe`,`Filter`];function M(e){return typeof e==`object`&&!!e}function Se(e){if(!M(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(!M(n))return new Set;let r=n.excludeClasses;return Array.isArray(r)?new Set(r.filter(e=>typeof e==`string`)):new Set}const Ce={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=Se(e.config?.rules?.[this.meta.id]),n=e.sourceFile.getDescendantsOfKind(c.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=be.some(e=>n.endsWith(e)),o=xe.some(e=>n.endsWith(e));if(a||o){if(o){if(r.getFirstAncestorByKind(c.Decorator))continue;let e=r.getFirstAncestorByKind(c.MethodDeclaration),t=r.getFirstAncestorByKind(c.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})}}}},we=/\.(\w+)$/,Te=/^(\w+)</,Ee=new Set([`PrismaService`,`PrismaClient`,`EntityManager`,`DataSource`,`Repository`,`Connection`,`MongooseModel`,`InjectModel`,`InjectRepository`,`MikroORM`,`DrizzleService`]),De={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=Oe(t.getType().getText());if(Ee.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 Oe(e){let t=e.match(we);if(t)return t[1];let n=e.match(Te);return n?n[1]:e}const ke=/\.(\w+)$/,Ae=/^(\w+)</,je=new Set([`PrismaService`,`PrismaClient`,`EntityManager`,`DataSource`,`Connection`,`MikroORM`]),Me={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(!le(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=Ne(t.getType().getText());if(je.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 Ne(e){let t=e.match(ke);if(t)return t[1];let n=e.match(Ae);return n?n[1]:e}const Pe=/\.(\w+)$/,Fe=/^(\w+)</,Ie=[/Repository$/,/Repo$/],Le={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=Re(t.getType().getText());if(Ie.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 Re(e){let t=e.match(Pe);if(t)return t[1];let n=e.match(Fe);return n?n[1]:e}const ze={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(c.CallExpression);for(let n of t){let t=n.getExpression();if(t.getKind()!==c.PropertyAccessExpression)continue;let r=t.asKind(c.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})}}},Be={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(ue(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})}},Ve=[`/repositories/`,`/entities/`,`/dto/`,`/guards/`,`/interceptors/`,`/pipes/`,`/strategies/`],He={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(`../`)&&Ve.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 N(e){return e.getDescendantsOfKind(c.ReturnStatement).some(e=>{let t=e.getExpression();return!t||t.getKind()!==c.NewExpression?!1:t.asKindOrThrow(c.NewExpression).getExpression().getText()===`Promise`})}const Ue={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)||pe(n))continue;let r=n.getBody();if(r&&r.getDescendantsOfKind(c.AwaitExpression).filter(e=>{let t=e.getParent();for(;t&&t!==r;){if(t.getKind()===c.ArrowFunction||t.getKind()===c.FunctionExpression||t.getKind()===c.FunctionDeclaration)return!1;t=t.getParent()}return!0}).length===0){let t=n.getName();N(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(c.AwaitExpression).filter(e=>{let t=e.getParent();for(;t&&t!==n;){if(t.getKind()===c.ArrowFunction||t.getKind()===c.FunctionExpression||t.getKind()===c.FunctionDeclaration)return!1;t=t.getParent()}return!0}).length===0){let r=t.getName()??`anonymous`;N(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})}}}},We=[`providers`,`controllers`,`imports`,`exports`],Ge={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(!de(t))continue;let n=t.getDecorator(`Module`);if(!n)continue;let r=n.getArguments()[0];if(!r||r.getKind()!==c.ObjectLiteralExpression)continue;let i=r.asKind(c.ObjectLiteralExpression);if(i)for(let t of We){let n=i.getProperty(t);if(!n)continue;let r=n.getChildrenOfKind(c.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)}}}}},Ke=new Set([`Get`,`Post`,`Put`,`Delete`,`Patch`,`All`,`Head`,`Options`]),qe={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(!Ke.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())}}}},Je={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(c.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})}}},P=new Set([`save`,`create`,`insert`,`update`,`delete`,`remove`,`send`,`emit`,`publish`,`dispatch`,`execute`,`fetch`,`load`,`upload`,`download`,`process`]),Ye={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(c.ExpressionStatement);for(let t of r){let r=t.getExpression();if(r.getKind()===c.VoidExpression||r.getKind()===c.AwaitExpression||r.getKind()!==c.CallExpression)continue;let i=r.asKind(c.CallExpression);if(!i)continue;let a=i.getExpression().getText().split(`.`).pop()??``,o=a.toLowerCase();(P.has(o)||[...P].some(e=>o.startsWith(e)&&o!==e))&&t.getFirstAncestorByKind(c.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})}}}},Xe={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}))}},Ze={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}))}}},Qe={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})}}}},$e={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}))}}},et={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}))}}},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`)&&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}))}}},nt={meta:{id:`correctness/prefer-readonly-injection`,category:`correctness`,severity:`warning`,description:`Constructor DI parameters should be readonly to prevent accidental reassignment`,help:`Add the 'readonly' modifier to the constructor parameter.`},check(e){for(let t of e.sourceFile.getClasses()){if(!(le(t)||T(t)))continue;let n=t.getConstructors()[0];if(n){for(let t of n.getParameters())if((t.hasModifier(`private`)||t.hasModifier(`protected`)||t.hasModifier(`public`))&&!t.isReadonly()){let n=t.getNameNode();e.report({filePath:e.filePath,message:`Constructor parameter '${t.getName()}' should be readonly.`,help:this.meta.help,line:n.getStartLineNumber(),column:n.getStartLinePos()+1})}}}}},rt={meta:{id:`correctness/require-inject-decorator`,category:`correctness`,severity:`error`,description:`Constructor parameters without type annotations must have @Inject() decorator for NestJS DI to resolve them`,help:`Add a type annotation or @Inject() decorator to the constructor parameter.`},check(e){for(let t of e.sourceFile.getClasses()){if(!ue(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})}}}},it={onModuleInit:`OnModuleInit`,onModuleDestroy:`OnModuleDestroy`,onApplicationBootstrap:`OnApplicationBootstrap`,onApplicationShutdown:`OnApplicationShutdown`,beforeApplicationShutdown:`BeforeApplicationShutdown`},at={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=it[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}))}}}},ot=new Set([c.ForStatement,c.ForOfStatement,c.ForInStatement,c.WhileStatement,c.DoStatement,c.AwaitExpression]),st={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(ot.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}}}}},ct={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(c.CallExpression);for(let n of t){if(n.getExpression().getText()!==`require`)continue;let t=n.getArguments();t.length!==0&&t[0].getKind()!==c.StringLiteral&&e.report({filePath:e.filePath,message:`Dynamic require() with non-literal argument prevents bundler optimization.`,help:this.meta.help,line:n.getStartLineNumber(),column:1})}}},lt={meta:{id:`performance/no-orphan-modules`,category:`performance`,severity:`info`,description:`Module is never imported by any other module and may be dead code`,help:`Import this module in another module or remove it if it is unused.`,scope:`project`},check(e){let t=new Set;for(let n of e.moduleGraph.modules.values())for(let e of n.imports)t.add(e);for(let n of e.moduleGraph.modules.values())n.name!==`AppModule`&&(t.has(n.name)||e.report({filePath:n.filePath,message:`Module '${n.name}' is never imported by any other module.`,help:this.meta.help,line:n.classDeclaration.getStartLineNumber(),column:1}))}},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(c.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})}},dt=new Set([`readFileSync`,`writeFileSync`,`existsSync`,`mkdirSync`,`readdirSync`,`statSync`,`accessSync`,`appendFileSync`,`copyFileSync`,`renameSync`,`unlinkSync`]),ft={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(c.CallExpression);for(let n of t){let t=n.getExpression().getText().split(`.`).pop()??``;dt.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})}}},pt={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})}}},mt=[`Guard`,`Interceptor`,`Filter`,`Middleware`,`Strategy`],ht={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(mt.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})}}},gt={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})}}},_t={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})}},vt=/delete/i;function yt(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&&!vt.test(e.name)):t===`prisma`?e.columns.some(e=>e.type===`DateTime`&&e.defaultValue!==void 0&&e.defaultValue.includes(`now()`)):!1}const bt={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())yt(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})}},xt={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(c.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})}}},St={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(c.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})}}}},Ct={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(c.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(c.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})}},wt={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(c.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})}}},Tt=/^(error|err|e|ex|exception)$/,Et={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(c.PropertyAccessExpression);for(let n of t){if(n.getName()!==`stack`)continue;let t=n.getExpression().getText();if(!(Tt.test(t)||t.endsWith(`.error`)||t.endsWith(`.err`)))continue;let r=n.getParent();if(!r)continue;let i=r.getKind();(i===c.ReturnStatement||i===c.PropertyAssignment||i===c.ShorthandPropertyAssignment||i===c.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})}}},Dt=[{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)`}],Ot=[/secret/i,/password/i,/passwd/i,/api[_-]?key/i,/auth[_-]?token/i,/private[_-]?key/i,/access[_-]?key/i,/client[_-]?secret/i],kt=new Set([`your-secret-here`,`changeme`,`password`]),At=/^[A-Za-z][A-Za-z0-9_]*(\.[A-Za-z][A-Za-z0-9_]*)+$/,F=new Set([`cursor`,`nextCursor`,`prevCursor`,`previousCursor`,`startCursor`,`endCursor`,`pageToken`,`nextPageToken`,`continuationToken`,`continuation`,`nextPage`,`afterCursor`,`beforeCursor`]);function I(e){return!(e.length<8||e.includes("${")||e.startsWith(`process.env`)||kt.has(e)||e.includes(` `)||At.test(e))}function L(e){return Ot.some(t=>t.test(e))}function jt(e){try{let t=Buffer.from(e,`base64`).toString(`utf-8`);return JSON.parse(t),!0}catch{return!1}}function Mt(e){let t=e.getParent();if(!t)return!1;let n=t.asKind(c.PropertyAssignment);if(n)return F.has(n.getName());let r=t.asKind(c.VariableDeclaration);return r?F.has(r.getName()):!1}const Nt={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(c.StringLiteral);for(let n of t){let t=n.getLiteralValue();if(!(t.length<16)&&n.getParent()?.getKind()!==c.ImportDeclaration){for(let{pattern:r,name:i}of Dt)if(r.test(t)){if(i===`Base64 key`&&(jt(t)||Mt(n)))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(c.VariableDeclaration);for(let t of n){let n=t.getName(),r=t.getInitializer();!r||r.getKind()!==c.StringLiteral||L(n)&&I(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(c.PropertyAssignment);for(let t of r){let n=t.getName(),r=t.getInitializer();!r||r.getKind()!==c.StringLiteral||L(n)&&I(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})}}},Pt=RegExp(`(?:^|[^a-zA-Z])\\w*(?:${[`Entity`,`Model`].join(`|`)})(?:[^a-zA-Z]|$)`),Ft={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();Pt.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})}}},It={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(c.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})}}},Lt=new Set([`md5`,`sha1`]),Rt=[me,Le,De,Me,Ce,ze,Be,He,ce,ye,nt,at,Je,qe,Ze,tt,Xe,$e,Ue,Ge,et,rt,Ye,Qe,Nt,Ct,{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(c.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()!==c.StringLiteral)continue;let i=r.getText().slice(1,-1).toLowerCase();Lt.has(i)&&e.report({filePath:e.filePath,message:`Weak hashing algorithm '${i}' used in createHash().`,help:this.meta.help,line:n.getStartLineNumber(),column:1})}}},wt,xt,Et,St,It,Ft,ft,st,ct,ut,ht,pt,lt,_t,bt,gt];function zt(e){return e.meta.scope===`project`}function Bt(e){return e.meta.scope===`schema`}function Vt(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 Ht(e){let t=[],n=[],r=[];for(let i of e)Bt(i)?r.push(i):zt(i)?n.push(i):t.push(i);return{fileRules:t,projectRules:n,schemaRules:r}}const Ut=new Set([`security`,`performance`,`correctness`,`architecture`]),Wt=new Set([`error`,`warning`,`info`]),Gt=new Set([`file`,`project`]),Kt=`custom/`;function qt(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`||!Ut.has(n.category)||!Wt.has(n.severity)||n.scope!==void 0&&!Gt.has(n.scope))}function Jt(e){return e.meta.id.startsWith(Kt)?e:{...e,meta:{...e.meta,id:`${Kt}${e.meta.id}`}}}async function Yt(e,t){let n=[],r=[],i=a(t,e);if(!l(i))return r.push(`Custom rules directory not found: ${i}`),{rules:n,warnings:r};if(!f(i).isDirectory())return r.push(`Custom rules path is not a directory: ${i}`),{rules:n,warnings:r};let o;try{o=d(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=o.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=p(i,{interopDefault:!0});for(let e of s){let t=a(i,e),o;try{o=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(o))qt(i)?(n.push(Jt(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(o).length>0&&(Object.values(o).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 Xt(e,t){return e.customRulesDir?Yt(e.customRulesDir,t):Promise.resolve({rules:[],warnings:[]})}async function Zt(e,t){let n=await S(e,t),{rules:r,warnings:i}=await Xt(n,e),a=Vt(Rt,r,i),{fileRules:o,projectRules:s,schemaRules:c}=Ht(R(n,a));return{combinedRules:a,config:n,customRuleWarnings:i,fileRules:o,projectRules:s,schemaRules:c}}async function Qt(e,t={}){return(await m(t.include??x.include,{cwd:e,absolute:!0,ignore:t.exclude??x.exclude})).sort()}async function $t(e,t,n={}){let r=await Promise.all([...t.projects.entries()].map(async([t,r])=>[t,await Qt(i(e,r),n)])),a=new Map;for(let[e,t]of r)a.set(e,t);return a}function en(e){let t=new s({compilerOptions:{strict:!0,target:99,module:99,skipFileDependencyResolution:!0},skipAddingFilesFromTsConfig:!0});for(let n of e)t.addSourceFileAtPath(n);return t}const tn=/import\([^)]+\)\.(\w+)/,nn=/^(\w+)</;function rn(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 on(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 an(e,t){let n=new Map;for(let r of t){let t=e.getSourceFile(r);if(t)for(let e of rn(t,r))n.set(e.name,e)}return n}function on(e){let t=e.match(tn);if(t)return t[1];let n=e.match(nn);return n?n[1]:e}const sn=/^model\s+(\w+)\s*\{/,cn=/^enum\s+(\w+)\s*\{/,ln=/^(\w+)\s+(\w+)(\?)?(\[\])?(.*)$/,un=/@(\w+)(\((?:[^()]*|\([^()]*\))*\))?/g,dn=/@default\(((?:[^()]*|\([^()]*\))*)\)/,fn=/^@@map\(\s*"([^"]+)"\s*\)/;function pn(e){let t=i(e,`prisma`,`schema.prisma`);if(l(t)){let n=i(e,`prisma`),r=d(n).filter(e=>e.endsWith(`.prisma`));return r.length>1?r.map(e=>i(n,e)):[t]}let n=i(e,`schema.prisma`);if(l(n))return[n];try{let t=i(e,`package.json`),n=JSON.parse(u(t,`utf-8`)).prisma?.schema;if(n){let t=i(e,n);if(l(t))return[t]}}catch{}return[]}function mn(e){let t=[],n=new Set;for(let r of e){let e;try{e=u(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=sn.exec(i);if(u){a={type:`model`,name:u[1]},o=[],s=[],c=[],l=void 0;continue}let d=cn.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=gn.exec(i);e&&(c=e[1].split(`,`).map(e=>e.trim()));let t=_n(i);t&&s.push(t);let n=fn.exec(i);n&&(l=n[1]);continue}let e=vn(i);e&&o.push(e)}}}return{models:t,enums:n}}const hn=/^@@(index|unique)\(\[([^\]]*)\]\)/,gn=/^@@id\(\[([^\]]*)\]\)/;function _n(e){let t=hn.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 vn(e){let t=ln.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(un.source,un.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=dn.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 bn=/onDelete:\s*(\w+)/;function xn(e){let t=e.attributes.find(e=>e.startsWith(`@relation`));if(!t)return;let n=bn.exec(t);return n?n[1]:void 0}function Sn(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=xn(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 Cn={supportsIncrementalUpdate:!1,extract(e,t,n){let r=pn(n);if(r.length===0)return[];let{models:i,enums:a}=mn(r);return Sn(i,a)}},wn=/=>\s*(\w+)/,Tn=new Set([`Column`,`PrimaryColumn`,`PrimaryGeneratedColumn`,`CreateDateColumn`,`UpdateDateColumn`,`DeleteDateColumn`,`VersionColumn`]),En={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()===c.ObjectLiteralExpression){let t={},n=e.asKind(c.ObjectLiteralExpression);if(!n)continue;for(let e of n.getProperties())if(e.getKind()===c.PropertyAssignment){let n=e.asKind(c.PropertyAssignment);n&&(t[n.getName()]=n.getInitializer()?.getText()??``)}return t}return null}function Dn(e){let t=e.getArguments();if(t.length===0)return null;let n=t[0];return n.getKind()===c.StringLiteral?n.asKind(c.StringLiteral)?.getLiteralValue()??null:null}function On(e){let t=e.getDecorator(`Entity`);if(!t)return e.getName()??`UnknownEntity`;let n=Dn(t);if(n)return n;let r=z(t);return r?.name?r.name.replace(/['"]/g,``):e.getName()??`UnknownEntity`}function kn(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=Dn(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 An(e,t,n){let r=En[n.getName()];if(!r)return null;let i=n.getArguments();if(i.length===0)return null;let a=i[0].getText(),o=wn.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 jn(e){if(!w(e,`Entity`))return null;let t=e.getName();if(!t)return null;let n=On(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()===c.ArrayLiteralExpression){let e=n.asKind(c.ArrayLiteralExpression);if(e){let n=e.getElements().map(e=>e.getKind()===c.StringLiteral?e.asKind(c.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(Tn.has(r)){let t=kn(e,n);c&&(t.hasIndex=!0),i.push(t);break}if(r in En){let r=An(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 Mn={prisma:Cn,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=jn(e);t&&n.push(t)}}return n}}};function B(e,t,n,r){let i={entities:new Map,relations:[],orm:n??`unknown`};if(!n)return i;let a=Mn[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 Nn(e){return{entities:[...e.entities.values()],relations:e.relations,orm:e.orm}}async function Pn(e,t){let{config:n,fileRules:r,projectRules:i,schemaRules:a}=t,[o,s]=await Promise.all([Qt(e,n),y(e)]),c=en(o);return{astProject:c,config:n,fileRules:r,files:o,moduleGraph:D(c,o),project:s,projectRules:i,providers:an(c,o),schemaGraph:B(c,o,s.orm,e),schemaRules:a,targetPath:e}}async function Fn(e,t,n){let{config:r,combinedRules:a}=t,o=await $t(e,n,r),s=await Promise.all([...o.entries()].filter(([,e])=>e.length>0).map(async([t,o])=>{let s=i(e,n.projects.get(t)),[c,l]=await Promise.all([y(s),oe(s,r)]),u=en(o),d=D(u,o),f=an(u,o),p=B(u,o,c.orm,s),{fileRules:m,projectRules:h,schemaRules:g}=Ht(R(l,a));return[t,{astProject:u,config:l,fileRules:m,files:o,moduleGraph:d,project:c,projectRules:h,providers:f,schemaGraph:p,schemaRules:g,targetPath:s}]}));return{subProjects:new Map(s)}}const In=e=>h.makeRe(e,{windows:!1}),Ln=/\\/g,Rn=/\/$/,zn=(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(In):[];if(r.size===0&&i.length===0)return e;let a=n.replace(Ln,`/`).replace(Rn,``);return e.filter(e=>{if(r.has(e.rule))return!1;let t=e.filePath.replace(Ln,`/`),n=t.startsWith(`${a}/`)?t.slice(a.length+1):t;return!i.some(e=>e.test(n))})};function Bn(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 Vn(e,t,n,r){let i=[],a=[];for(let o of t){let t=Bn(e,o,n,r);i.push(...t.diagnostics),a.push(...t.errors)}return{diagnostics:i,errors:a}}function Hn(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 Un(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 Wn(e){return e instanceof Error?e.message:String(e)}function V(e,t,n){return{diagnostics:zn(e,n.config,n.targetPath),errors:t.map(e=>({ruleId:e.ruleId,error:Wn(e.error)}))}}function Gn(e){let t=Vn(e.astProject,e.files,e.fileRules,e.config);return V(t.diagnostics,t.errors,e)}function Kn(e){let t={moduleGraph:e.moduleGraph,providers:e.providers,config:e.config},n=Hn(e.astProject,e.files,e.projectRules,t),{diagnostics:r,errors:i}=V(n.diagnostics,n.errors,e),a=qn(e);return r.push(...a.diagnostics),i.push(...a.errors),{diagnostics:r,errors:i}}function qn(e){if(!e.schemaGraph||e.schemaRules.length===0||e.schemaGraph.entities.size===0)return{diagnostics:[],errors:[]};let t=Un(e.schemaGraph,e.schemaRules);return V(t.diagnostics,t.errors,e)}function H(e){let t=o.now(),n=Gn(e),r=Kn(e),i=o.now()-t;return{diagnostics:[...n.diagnostics,...r.diagnostics],elapsedMs:i,ruleErrors:[...n.errors,...r.errors]}}function Jn(e){return e>=90?`Excellent`:e>=75?`Good`:e>=50?`Fair`:e>=25?`Poor`:`Critical`}const Yn={error:3,warning:1.5,info:.5},Xn={security:1.5,correctness:1.3,schema:1.1,architecture:1,performance:.8};function Zn(e,t){if(t===0)return{value:100,label:Jn(100)};let n=0;for(let t of e){let e=Yn[t.severity],r=Xn[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:Jn(i)}}function Qn(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=Zn(r,e.files.length),c=Qn(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:Nn(o)},moduleGraph:e.moduleGraph,schemaGraph:o,customRuleWarnings:n,files:e.files,providers:e.providers}}function $n(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=Zn(a,c),u=Qn(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 er=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},tr=(e,t)=>e===void 0?t:Number(e),nr=(e,t)=>t===void 0?!0:e>=t,W={error:g.red,warn:g.yellow,info:g.cyan,success:g.green,dim:g.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(``)}},rr=1e3,ir={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),ar=(e,t)=>t===`error`?W.error(e):t===`warning`?W.warn(e):W.info(e),or=e=>e===`error`?`✗`:e===`warning`?`⚠`:`●`,sr=e=>e<rr?`${Math.round(e)}ms`:`${(e/rr).toFixed(1)}s`,cr=e=>e>=75?[`◠ ◠ ◠`,`╰───╯`]:e>=50?[`• • •`,`╰───╯`]:[`x x x`,`╰───╯`],lr=e=>e>=90?`★★★★★`:e>=75?`★★★★☆`:e>=50?`★★★☆☆`:e>=25?`★★☆☆☆`:`★☆☆☆☆`,ur=e=>{let t=Math.round(e/100*50),n=50-t;return{filled:`█`.repeat(t),empty:`░`.repeat(n)}},dr=e=>{let{filled:t,empty:n}=ur(e);return`${t}${n}`},fr=e=>{let{filled:t,empty:n}=ur(e);return J(t,e)+W.dim(n)},pr=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}┘`)}`)},mr=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},hr=e=>[...e].sort(([,e],[,t])=>ir[e[0].severity]-ir[t[0].severity]),gr=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},_r=e=>new Set(e.map(e=>e.filePath)),vr=(e,t)=>{let n=hr([...mr(e).entries()]);for(let[,e]of n){let n=e[0],r=ar(or(n.severity),n.severity),i=e.length,a=i>1?ar(` (${i})`,n.severity):``;if(K.log(` ${r} ${n.message}${a}`),n.help&&K.dim(` ${n.help}`),t){let t=gr(e);for(let[e,n]of t){let t=n.length>0?`: ${n.join(`, `)}`:``;K.dim(` ${e}${t}`)}}K.break()}};function yr(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]=cr(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=lr(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(dr(n.value),fr(n.value))),s.push(q(``));let m=sr(o),h=_r(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(` `))),pr(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(vr(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 br(e,t){yr(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;nr(r,t)||(n||K.error(`Score ${r} is below the minimum threshold of ${t}.`),process.exit(1))},xr=e=>{e.summary.errors>0&&process.exit(1)},Sr=(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}br(i,r.verbose),X(i.combined,t,n),xr(i.combined)},Cr=(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}yr(i,r.verbose),X(i,t,n),xr(i)};let Z=null,Q=0;const $=new Set,wr=(e,t,n)=>{if($.delete(t),Q--,Q<=0||!Z){Z?.[e](n),Z=null,Q=0;return}Z.stop(),_(n).start()[e](n);let[r]=$;r&&(Z.text=r),Z.start()},Tr=e=>({start(){return Q++,$.add(e),Z?Z.text=e:Z=_({text:e}).start(),{succeed:t=>wr(`succeed`,e,t),fail:t=>wr(`fail`,e,t)}}}),Er=(e,t)=>{if(!t)for(let t of e)K.warn(t)};var Dr=class{options;resolvedMinimumScore;scanConfig;steps=[];targetPath;constructor(e,t){this.targetPath=e,this.options=t}resolveConfig(){return this.steps.push(async()=>{this.scanConfig=await Zt(this.targetPath,this.options.configPath),this.resolvedMinimumScore=tr(this.options.minScore,this.scanConfig.config.minScore)}),this}warnCustomRules(){return this.steps.push(()=>{Er(this.scanConfig.customRuleWarnings,this.options.isMachineReadable)}),this}async run(){let e=this.options.isMachineReadable?null:Tr(`Scanning...`).start();for(let e of this.steps)await e();e?.succeed(`Scan complete`)}},Or=class extends Dr{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=o.now(),this.monorepoCtx=await Fn(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=o.now()-this.scanStartTime;this.result=$n(this.monorepoCtx,this.rawOutputs,this.scanConfig.customRuleWarnings,e)}),this}output(){return this.steps.push(()=>{Sr(this.result,this.resolvedMinimumScore,this.options.isMachineReadable,{json:this.options.json,score:this.options.score,verbose:this.options.verbose})}),this}},kr=class extends Dr{context;rawOutput;result;buildContext(){return this.steps.push(async()=>{this.context=await Pn(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(()=>{Cr(this.result,this.resolvedMinimumScore,this.options.isMachineReadable,{json:this.options.json,score:this.options.score,verbose:this.options.verbose})}),this}},Ar=class{args;steps=[];version;targetPath=``;constructor(e,t){this.args=e,this.version=t}resolveTargetPath(){return this.steps.push(()=>(this.targetPath=a(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=er(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:jr}=e(import.meta.url)(`../../package.json`);n(t({meta:{name:`nestjs-doctor`,version:jr,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},...ne},async run({args:e}){let t=await new Ar(e,jr).resolveTargetPath().handleInit().handleReport().validateMinScore().run();if(!t)return;let{targetPath:n,options:r}=t,i=await v(n);if(i){await new Or(n,i,r).resolveConfig().buildContext().runRules().buildResult().warnCustomRules().output().run();return}await new kr(n,r).resolveConfig().buildContext().runRules().buildResult().warnCustomRules().output().run()}}));export{U as a,Fn as c,_e as d,v as f,$n as i,Zt as l,K as n,H as o,W as r,Pn as s,Tr as t,k 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,resolve as o}from"node:path";import{performance as s}from"node:perf_hooks";import{Project as c,SyntaxKind as l}from"ts-morph";import{existsSync as u,readFileSync as d,readdirSync as f,statSync as p}from"node:fs";import{createJiti as m}from"jiti";import{glob as h}from"tinyglobby";import g from"picomatch";import _ from"picocolors";import v from"ora";async function y(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 b(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}},x={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`]},ie=[`nestjs-doctor.config.json`,`.nestjs-doctor.json`];async function S(e,t){if(t)return ae(t);for(let t of ie)try{return await ae(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 oe(n[`nestjs-doctor`])}catch{}return{...x}}async function ae(e){let t=await r(e,`utf-8`);return oe(JSON.parse(t))}function oe(e){return{...x,...e,exclude:[...x.exclude??[],...e.exclude??[]]}}async function se(e,t){try{return await S(e)}catch{return t}}const ce=[/Repository$/,/\.repository$/,/\.entity$/,/\.schema$/,/\.guard$/,/\.interceptor$/,/\.pipe$/,/\.filter$/,/\.strategy$/],le={meta:{id:`architecture/no-barrel-export-internals`,category:`architecture`,severity:`info`,description:`Don't re-export internal implementation details from barrel files`,help:`Only export the module's public API (services, DTOs, interfaces) from index.ts files.`},check(e){if(e.filePath.endsWith(`/index.ts`))for(let t of e.sourceFile.getExportDeclarations()){let n=t.getModuleSpecifierValue();if(n){ce.some(e=>e.test(n))&&e.report({filePath:e.filePath,message:`Barrel file re-exports internal module '${n}'.`,help:this.meta.help,line:t.getStartLineNumber(),column:1});for(let n of t.getNamedExports()){let t=n.getName();(t.endsWith(`Repository`)||t.endsWith(`Entity`)||t.endsWith(`Schema`))&&e.report({filePath:e.filePath,message:`Barrel file re-exports internal type '${t}'.`,help:this.meta.help,line:n.getStartLineNumber(),column:1})}}}}},C=new Set([`Get`,`Post`,`Put`,`Patch`,`Delete`,`Head`,`Options`,`All`]);function w(e,t){return e.getDecorator(t)!==void 0}function T(e){return w(e,`Controller`)}function ue(e){return w(e,`Injectable`)}function de(e){return w(e,`Injectable`)||w(e,`Controller`)||w(e,`Resolver`)||w(e,`WebSocketGateway`)}function fe(e){return w(e,`Module`)}function E(e){return e.getDecorators().some(e=>C.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(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})}}},ge=/=>\s*(\w+)/,_e=/\.js$/;function ve(e,t){let n=[];for(let r of e.getClasses()){let e=r.getDecorator(`Module`);if(!e)continue;let i=r.getName()??`AnonymousModule`,a=e.getArguments()[0],o={name:i,filePath:t,classDeclaration:r,imports:[],exports:[],providers:[],controllers:[]};if(a&&a.getKind()===l.ObjectLiteralExpression){let e=a.asKind(l.ObjectLiteralExpression);e&&(o.imports=O(e,`imports`),o.exports=O(e,`exports`),o.providers=O(e,`providers`),o.controllers=O(e,`controllers`))}n.push(o)}return n}function D(e,t){let n=new Map,r=new Map;for(let r of t){let t=e.getSourceFile(r);if(t)for(let e of ve(t,r))n.set(e.name,e)}for(let[e,t]of n){let i=new Set;for(let e of t.imports)n.has(e)&&i.add(e);r.set(e,i)}let i=new Map;for(let e of n.values())for(let t of e.providers)i.set(t,e);return{modules:n,edges:r,providerToModule:i}}const ye=new Set([`forRoot`,`forRootAsync`,`forFeature`,`forFeatureAsync`,`forChild`,`forChildAsync`,`register`,`registerAsync`]);function O(e,t){let n=e.getProperty(t);if(!n)return[];let r=n.asKind(l.PropertyAssignment);if(!r)return[];let i=r.getInitializer();return i?k(i,e.getSourceFile(),0):[]}function k(e,t,n){if(n>5)return[];let r=e.getKind();if(r===l.ArrayLiteralExpression){let r=e.asKindOrThrow(l.ArrayLiteralExpression),i=[];for(let e of r.getElements())i.push(...be(e,t,n));return i}return r===l.CallExpression?A(e.asKindOrThrow(l.CallExpression),t,n):r===l.Identifier?N(e.getText(),t,n+1):[]}function be(e,t,n){let r=e.getText();if(r.startsWith(`forwardRef`)){let e=r.match(ge);return e?[e[1]]:[r]}let i=e.getKind();return i===l.SpreadElement?k(e.asKindOrThrow(l.SpreadElement).getExpression(),t,n):i===l.CallExpression?A(e.asKindOrThrow(l.CallExpression),t,n):i===l.PropertyAccessExpression?[e.asKindOrThrow(l.PropertyAccessExpression).getExpression().getText()]:(l.Identifier,[r])}function A(e,t,n){let r=e.getExpression();if(r.getKind()===l.PropertyAccessExpression){let i=r.asKindOrThrow(l.PropertyAccessExpression),a=i.getName();if(a===`concat`){let r=k(i.getExpression(),t,n),a=[];for(let r of e.getArguments())a.push(...k(r,t,n));return[...r,...a]}return ye.has(a),[i.getExpression().getText()]}return r.getKind()===l.Identifier?P(r.getText(),t,n+1):[]}function j(e,t){if(!e.startsWith(`.`))return;let n=o(i(t.getFilePath()),e),r=t.getProject(),a=[`${n}.ts`,`${n}/index.ts`,n,n.replace(_e,`.ts`)];for(let e of a){let t=r.getSourceFile(e);if(t)return t}}function M(e,t){for(let n of t.getImportDeclarations())for(let r of n.getNamedImports())if((r.getAliasNode()?r.getAliasNode().getText():r.getName())===e){let e=j(n.getModuleSpecifierValue(),t);return e?{sourceFile:e,localName:r.getName()}:void 0}for(let n of t.getExportDeclarations())if(n.getModuleSpecifierValue()){for(let r of n.getNamedExports())if((r.getAliasNode()?r.getAliasNode().getText():r.getName())===e){let e=j(n.getModuleSpecifierValue(),t);return e?{sourceFile:e,localName:r.getName()}:void 0}}}function N(e,t,n){if(n>5)return[];for(let r of t.getStatements()){if(r.getKind()!==l.VariableStatement)continue;let i=r.asKindOrThrow(l.VariableStatement);for(let r of i.getDeclarations())if(r.getName()===e){let e=r.getInitializer();if(e)return k(e,t,n)}}let r=M(e,t);return r?N(r.localName,r.sourceFile,n+1):[]}function xe(e,t,n){for(let r of t.getStatements()){if(r.getKind()!==l.VariableStatement)continue;let i=r.asKindOrThrow(l.VariableStatement);for(let r of i.getDeclarations()){if(r.getName()!==e)continue;let i=r.getInitializer();if(!i||i.getKind()!==l.ArrowFunction)continue;let a=i.asKindOrThrow(l.ArrowFunction).getBody();if(a.getKind()!==l.Block)return k(a,t,n);let o=[];for(let e of a.getDescendantsOfKind(l.ReturnStatement)){let r=e.getExpression();r&&o.push(...k(r,t,n))}return o}}}function P(e,t,n){if(n>5)return[];for(let r of t.getStatements()){if(r.getKind()!==l.FunctionDeclaration)continue;let i=r.asKindOrThrow(l.FunctionDeclaration);if(i.getName()!==e)continue;let a=[];for(let e of i.getDescendantsOfKind(l.ReturnStatement)){let r=e.getExpression();r&&a.push(...k(r,t,n))}return a}let r=xe(e,t,n);if(r)return r;let i=M(e,t);return i?P(i.localName,i.sourceFile,n+1):[]}function Se(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 F(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 I(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 L=`Break the cycle by extracting shared logic into a separate module or using forwardRef().`;function Ce(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=I(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 L;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=I(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 we={meta:{id:`architecture/no-circular-module-deps`,category:`architecture`,severity:`error`,description:`Circular dependencies in @Module() import graph`,help:L,scope:`project`},check(e){let t=F(e.moduleGraph);for(let n of t){let t=n.join(` -> `),r=e.moduleGraph.modules.get(n[0]),i=Ce(n,e);e.report({filePath:r?.filePath??`unknown`,message:`Circular module dependency detected: ${t}`,help:i,line:r?.classDeclaration.getStartLineNumber()??1,column:1})}}},Te=[`Service`,`Repository`,`Gateway`,`Resolver`],Ee=[`Guard`,`Interceptor`,`Pipe`,`Filter`];function R(e){return typeof e==`object`&&!!e}function De(e){if(!R(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(!R(n))return new Set;let r=n.excludeClasses;return Array.isArray(r)?new Set(r.filter(e=>typeof e==`string`)):new Set}const Oe={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=De(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=Te.some(e=>n.endsWith(e)),o=Ee.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})}}}},ke=/\.(\w+)$/,Ae=/^(\w+)</,je=new Set([`PrismaService`,`PrismaClient`,`EntityManager`,`DataSource`,`Repository`,`Connection`,`MongooseModel`,`InjectModel`,`InjectRepository`,`MikroORM`,`DrizzleService`]),Me={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=Ne(t.getType().getText());if(je.has(n)){let r=t.getNameNode();e.report({filePath:e.filePath,message:`Controller injects ORM type '${n}' directly. Use a service layer.`,help:this.meta.help,line:r.getStartLineNumber(),column:r.getStartLinePos()+1})}}for(let n of t.getConstructors()[0]?.getParameters()??[])for(let t of n.getDecorators()){let n=t.getName();(n===`InjectRepository`||n===`InjectModel`)&&e.report({filePath:e.filePath,message:`Controller uses @${n}() decorator. Move data access to a service.`,help:this.meta.help,line:t.getStartLineNumber(),column:1})}}}}};function Ne(e){let t=e.match(ke);if(t)return t[1];let n=e.match(Ae);return n?n[1]:e}const Pe=/\.(\w+)$/,Fe=/^(\w+)</,Ie=new Set([`PrismaService`,`PrismaClient`,`EntityManager`,`DataSource`,`Connection`,`MikroORM`]),Le={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(!ue(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=Re(t.getType().getText());if(Ie.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 Re(e){let t=e.match(Pe);if(t)return t[1];let n=e.match(Fe);return n?n[1]:e}const ze=/\.(\w+)$/,Be=/^(\w+)</,Ve=[/Repository$/,/Repo$/],He={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=Ue(t.getType().getText());if(Ve.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 Ue(e){let t=e.match(ze);if(t)return t[1];let n=e.match(Be);return n?n[1]:e}const We={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})}}},Ge={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})}},Ke=[`/repositories/`,`/entities/`,`/dto/`,`/guards/`,`/interceptors/`,`/pipes/`,`/strategies/`],qe={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(`../`)&&Ke.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 Je(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 Ye={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(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();Je(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`;Je(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})}}}},Xe=[`providers`,`controllers`,`imports`,`exports`],Ze={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()!==l.ObjectLiteralExpression)continue;let i=r.asKind(l.ObjectLiteralExpression);if(i)for(let t of Xe){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)}}}}},Qe=new Set([`Get`,`Post`,`Put`,`Delete`,`Patch`,`All`,`Head`,`Options`]),$e={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(!Qe.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(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})}}},tt=new Set([`save`,`create`,`insert`,`update`,`delete`,`remove`,`send`,`emit`,`publish`,`dispatch`,`execute`,`fetch`,`load`,`upload`,`download`,`process`]),nt={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=a.toLowerCase();(tt.has(o)||[...tt].some(e=>o.startsWith(e)&&o!==e))&&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})}}}},rt={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}))}},it={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}))}}},at={meta:{id:`correctness/no-missing-injectable`,category:`correctness`,severity:`error`,description:`Provider classes with constructor dependencies must have the @Injectable() decorator`,help:`Add @Injectable() to providers that inject constructor dependencies.`,scope:`project`},check(e){let t=new Set([...e.providers.values()].map(e=>e.name)),n=new Map;for(let t of e.files){let r=e.project.getSourceFile(t);if(r)for(let e of r.getClasses()){let r=e.getName();if(r){let i=n.get(r)??[];i.push({cls:e,filePath:t}),n.set(r,i)}}}for(let r of e.moduleGraph.modules.values())for(let i of r.providers){if(t.has(i))continue;let a=n.get(i);if(a)for(let{cls:t,filePath:n}of a){let a=(t.getConstructors()[0]?.getParameters().length??0)>0;!(t.getDecorator(`Injectable`)||t.getDecorator(`Resolver`)||t.getDecorator(`WebSocketGateway`))&&a&&e.report({filePath:n,message:`Class '${i}' is listed in '${r.name}' providers but is missing @Injectable() decorator.`,help:this.meta.help,line:t.getStartLineNumber(),column:1})}}}},ot={meta:{id:`correctness/no-missing-interceptor-method`,category:`correctness`,severity:`error`,description:`Interceptor classes must implement the intercept() method`,help:`Add an intercept(context: ExecutionContext, next: CallHandler) method to the interceptor class.`},check(e){for(let t of e.sourceFile.getClasses()){let n=t.getName()??``;n.endsWith(`Interceptor`)&&w(t,`Injectable`)&&(t.getExtends()||t.getMethods().some(e=>e.getName()===`intercept`)||e.report({filePath:e.filePath,message:`Interceptor '${n}' is missing the 'intercept()' method.`,help:this.meta.help,line:t.getStartLineNumber(),column:1}))}}},st={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}))}}},ct={meta:{id:`correctness/no-missing-pipe-method`,category:`correctness`,severity:`error`,description:`Pipe classes must implement the transform() method`,help:`Add a transform(value: any, metadata: ArgumentMetadata) method to the pipe class.`},check(e){for(let t of e.sourceFile.getClasses()){let n=t.getName()??``;n.endsWith(`Pipe`)&&w(t,`Injectable`)&&(t.getExtends()||t.getMethods().some(e=>e.getName()===`transform`)||e.report({filePath:e.filePath,message:`Pipe '${n}' is missing the 'transform()' method.`,help:this.meta.help,line:t.getStartLineNumber(),column:1}))}}},lt={meta:{id:`correctness/prefer-readonly-injection`,category:`correctness`,severity:`warning`,description:`Constructor DI parameters should be readonly to prevent accidental reassignment`,help:`Add the 'readonly' modifier to the constructor parameter.`},check(e){for(let t of e.sourceFile.getClasses()){if(!(ue(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})}}}}},ut={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})}}}},dt={onModuleInit:`OnModuleInit`,onModuleDestroy:`OnModuleDestroy`,onApplicationBootstrap:`OnApplicationBootstrap`,onApplicationShutdown:`OnApplicationShutdown`,beforeApplicationShutdown:`BeforeApplicationShutdown`},ft={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=dt[i];a&&(n.some(e=>e.includes(a))||e.report({filePath:e.filePath,message:`Class '${t.getName()}' has '${i}()' but does not implement '${a}'.`,help:this.meta.help,line:r.getStartLineNumber(),column:1}))}}}},pt=new Set([l.ForStatement,l.ForOfStatement,l.ForInStatement,l.WhileStatement,l.DoStatement,l.AwaitExpression]),mt={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(pt.has(i.getKind())){e.report({filePath:e.filePath,message:`Constructor in '${t.getName()}' contains blocking operation — use onModuleInit() instead.`,help:this.meta.help,line:n.getStartLineNumber(),column:1});break}}}}},ht={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})}}},gt={meta:{id:`performance/no-orphan-modules`,category:`performance`,severity:`info`,description:`Module is never imported by any other module and may be dead code`,help:`Import this module in another module or remove it if it is unused.`,scope:`project`},check(e){let t=new Set;for(let n of e.moduleGraph.modules.values())for(let e of n.imports)t.add(e);for(let n of e.moduleGraph.modules.values())n.name!==`AppModule`&&(t.has(n.name)||e.report({filePath:n.filePath,message:`Module '${n.name}' is never imported by any other module.`,help:this.meta.help,line:n.classDeclaration.getStartLineNumber(),column:1}))}},_t={meta:{id:`performance/no-request-scope-abuse`,category:`performance`,severity:`warning`,description:`Scope.REQUEST creates a new provider instance per request — use only when necessary`,help:`Remove Scope.REQUEST unless the provider genuinely needs per-request state (e.g., request-scoped context). Consider Scope.DEFAULT or Scope.TRANSIENT instead.`},check(e){let t=e.sourceFile.getDescendantsOfKind(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})}},vt=new Set([`readFileSync`,`writeFileSync`,`existsSync`,`mkdirSync`,`readdirSync`,`statSync`,`accessSync`,`appendFileSync`,`copyFileSync`,`renameSync`,`unlinkSync`]),yt={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()??``;vt.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})}}},bt={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})}}},xt=[`Guard`,`Interceptor`,`Filter`,`Middleware`,`Strategy`],St={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(xt.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})}}},Ct={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})}}},wt={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})}},Tt=/delete/i;function Et(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&&!Tt.test(e.name)):t===`prisma`?e.columns.some(e=>e.type===`DateTime`&&e.defaultValue!==void 0&&e.defaultValue.includes(`now()`)):!1}const Dt={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())Et(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})}},Ot={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})}}},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(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})}}}},At={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})}},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(!(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})}}},Mt=/^(error|err|e|ex|exception)$/,Nt={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(!(Mt.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})}}},Pt=[{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)`}],Ft=[/secret/i,/password/i,/passwd/i,/api[_-]?key/i,/auth[_-]?token/i,/private[_-]?key/i,/access[_-]?key/i,/client[_-]?secret/i],It=new Set([`your-secret-here`,`changeme`,`password`]),Lt=/^[A-Za-z][A-Za-z0-9_]*(\.[A-Za-z][A-Za-z0-9_]*)+$/,Rt=new Set([`cursor`,`nextCursor`,`prevCursor`,`previousCursor`,`startCursor`,`endCursor`,`pageToken`,`nextPageToken`,`continuationToken`,`continuation`,`nextPage`,`afterCursor`,`beforeCursor`]);function zt(e){return!(e.length<8||e.includes("${")||e.startsWith(`process.env`)||It.has(e)||e.includes(` `)||Lt.test(e))}function Bt(e){return Ft.some(t=>t.test(e))}function Vt(e){try{let t=Buffer.from(e,`base64`).toString(`utf-8`);return JSON.parse(t),!0}catch{return!1}}function Ht(e){let t=e.getParent();if(!t)return!1;let n=t.asKind(l.PropertyAssignment);if(n)return Rt.has(n.getName());let r=t.asKind(l.VariableDeclaration);return r?Rt.has(r.getName()):!1}const Ut={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 Pt)if(r.test(t)){if(i===`Base64 key`&&(Vt(t)||Ht(n)))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||Bt(n)&&zt(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||Bt(n)&&zt(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})}}},Wt=RegExp(`(?:^|[^a-zA-Z])\\w*(?:${[`Entity`,`Model`].join(`|`)})(?:[^a-zA-Z]|$)`),Gt={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();Wt.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})}}},Kt={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})}}},qt=new Set([`md5`,`sha1`]),Jt=[he,He,Me,Le,Oe,We,Ge,qe,le,we,lt,ft,et,$e,it,ct,rt,ot,Ye,Ze,st,ut,nt,at,Ut,At,{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();qt.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,Ot,Nt,kt,Kt,Gt,yt,mt,ht,_t,St,bt,gt,wt,Dt,Ct];function Yt(e){return e.meta.scope===`project`}function Xt(e){return e.meta.scope===`schema`}function Zt(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 Qt(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 $t(e){let t=[],n=[],r=[];for(let i of e)Xt(i)?r.push(i):Yt(i)?n.push(i):t.push(i);return{fileRules:t,projectRules:n,schemaRules:r}}const en=new Set([`security`,`performance`,`correctness`,`architecture`]),tn=new Set([`error`,`warning`,`info`]),nn=new Set([`file`,`project`]),rn=`custom/`;function an(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`||!en.has(n.category)||!tn.has(n.severity)||n.scope!==void 0&&!nn.has(n.scope))}function on(e){return e.meta.id.startsWith(rn)?e:{...e,meta:{...e.meta,id:`${rn}${e.meta.id}`}}}async function sn(e,t){let n=[],r=[],i=o(t,e);if(!u(i))return r.push(`Custom rules directory not found: ${i}`),{rules:n,warnings:r};if(!p(i).isDirectory())return r.push(`Custom rules path is not a directory: ${i}`),{rules:n,warnings:r};let a;try{a=f(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=m(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))an(i)?(n.push(on(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 cn(e,t){return e.customRulesDir?sn(e.customRulesDir,t):Promise.resolve({rules:[],warnings:[]})}async function ln(e,t){let n=await S(e,t),{rules:r,warnings:i}=await cn(n,e),a=Zt(Jt,r,i),{fileRules:o,projectRules:s,schemaRules:c}=$t(Qt(n,a));return{combinedRules:a,config:n,customRuleWarnings:i,fileRules:o,projectRules:s,schemaRules:c}}async function un(e,t={}){return(await h(t.include??x.include,{cwd:e,absolute:!0,ignore:t.exclude??x.exclude})).sort()}async function dn(e,t,n={}){let r=await Promise.all([...t.projects.entries()].map(async([t,r])=>[t,await un(a(e,r),n)])),i=new Map;for(let[e,t]of r)i.set(e,t);return i}function fn(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 pn=/import\([^)]+\)\.(\w+)/,mn=/^(\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 _n(t?t.getText():e.getType().getText())}):[],o=r.getMethods().filter(e=>{let t=e.getScope();return!t||t===`public`}).length;n.push({name:e,filePath:t,classDeclaration:r,dependencies:a,publicMethodCount:o})}return n}function gn(e,t){let n=new Map;for(let r of t){let t=e.getSourceFile(r);if(t)for(let e of hn(t,r))n.set(e.name,e)}return n}function _n(e){let t=e.match(pn);if(t)return t[1];let n=e.match(mn);return n?n[1]:e}const vn=/^model\s+(\w+)\s*\{/,yn=/^enum\s+(\w+)\s*\{/,bn=/^(\w+)\s+(\w+)(\?)?(\[\])?(.*)$/,xn=/@(\w+)(\((?:[^()]*|\([^()]*\))*\))?/g,Sn=/@default\(((?:[^()]*|\([^()]*\))*)\)/,Cn=/^@@map\(\s*"([^"]+)"\s*\)/;function wn(e){let t=a(e,`prisma`,`schema.prisma`);if(u(t)){let n=a(e,`prisma`),r=f(n).filter(e=>e.endsWith(`.prisma`));return r.length>1?r.map(e=>a(n,e)):[t]}let n=a(e,`schema.prisma`);if(u(n))return[n];try{let t=a(e,`package.json`),n=JSON.parse(d(t,`utf-8`)).prisma?.schema;if(n){let t=a(e,n);if(u(t))return[t]}}catch{}return[]}function Tn(e){let t=[],n=new Set;for(let r of e){let e;try{e=d(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=vn.exec(i);if(u){a={type:`model`,name:u[1]},o=[],s=[],c=[],l=void 0;continue}let d=yn.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=Dn.exec(i);e&&(c=e[1].split(`,`).map(e=>e.trim()));let t=On(i);t&&s.push(t);let n=Cn.exec(i);n&&(l=n[1]);continue}let e=kn(i);e&&o.push(e)}}}return{models:t,enums:n}}const En=/^@@(index|unique)\(\[([^\]]*)\]\)/,Dn=/^@@id\(\[([^\]]*)\]\)/;function On(e){let t=En.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 kn(e){let t=bn.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(xn.source,xn.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 An(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=Sn.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 jn=/onDelete:\s*(\w+)/;function Mn(e){let t=e.attributes.find(e=>e.startsWith(`@relation`));if(!t)return;let n=jn.exec(t);return n?n[1]:void 0}function Nn(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=Mn(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=An(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 Pn={supportsIncrementalUpdate:!1,extract(e,t,n){let r=wn(n);if(r.length===0)return[];let{models:i,enums:a}=Tn(r);return Nn(i,a)}},Fn=/=>\s*(\w+)/,In=new Set([`Column`,`PrimaryColumn`,`PrimaryGeneratedColumn`,`CreateDateColumn`,`UpdateDateColumn`,`DeleteDateColumn`,`VersionColumn`]),Ln={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 Rn(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 zn(e){let t=e.getDecorator(`Entity`);if(!t)return e.getName()??`UnknownEntity`;let n=Rn(t);if(n)return n;let r=z(t);return r?.name?r.name.replace(/['"]/g,``):e.getName()??`UnknownEntity`}function Bn(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=Rn(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 Vn(e,t,n){let r=Ln[n.getName()];if(!r)return null;let i=n.getArguments();if(i.length===0)return null;let a=i[0].getText(),o=Fn.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 Hn(e){if(!w(e,`Entity`))return null;let t=e.getName();if(!t)return null;let n=zn(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(In.has(r)){let t=Bn(e,n);c&&(t.hasIndex=!0),i.push(t);break}if(r in Ln){let r=Vn(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 Un={prisma:Pn,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=Hn(e);t&&n.push(t)}}return n}}};function B(e,t,n,r){let i={entities:new Map,relations:[],orm:n??`unknown`};if(!n)return i;let a=Un[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 Wn(e){return{entities:[...e.entities.values()],relations:e.relations,orm:e.orm}}async function Gn(e,t){let{config:n,fileRules:r,projectRules:i,schemaRules:a}=t,[o,s]=await Promise.all([un(e,n),b(e)]),c=fn(o);return{astProject:c,config:n,fileRules:r,files:o,moduleGraph:D(c,o),project:s,projectRules:i,providers:gn(c,o),schemaGraph:B(c,o,s.orm,e),schemaRules:a,targetPath:e}}async function Kn(e,t,n){let{config:r,combinedRules:i}=t,o=await dn(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([b(s),se(s,r)]),u=fn(o),d=D(u,o),f=gn(u,o),p=B(u,o,c.orm,s),{fileRules:m,projectRules:h,schemaRules:g}=$t(Qt(l,i));return[t,{astProject:u,config:l,fileRules:m,files:o,moduleGraph:d,project:c,projectRules:h,providers:f,schemaGraph:p,schemaRules:g,targetPath:s}]}));return{subProjects:new Map(s)}}const qn=e=>g.makeRe(e,{windows:!1}),Jn=/\\/g,Yn=/\/$/,Xn=(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(qn):[];if(r.size===0&&i.length===0)return e;let a=n.replace(Jn,`/`).replace(Yn,``);return e.filter(e=>{if(r.has(e.rule))return!1;let t=e.filePath.replace(Jn,`/`),n=t.startsWith(`${a}/`)?t.slice(a.length+1):t;return!i.some(e=>e.test(n))})};function Zn(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 Qn(e,t,n,r){let i=[],a=[];for(let o of t){let t=Zn(e,o,n,r);i.push(...t.diagnostics),a.push(...t.errors)}return{diagnostics:i,errors:a}}function $n(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 er(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 tr(e){return e instanceof Error?e.message:String(e)}function V(e,t,n){return{diagnostics:Xn(e,n.config,n.targetPath),errors:t.map(e=>({ruleId:e.ruleId,error:tr(e.error)}))}}function nr(e){let t=Qn(e.astProject,e.files,e.fileRules,e.config);return V(t.diagnostics,t.errors,e)}function rr(e){let t={moduleGraph:e.moduleGraph,providers:e.providers,config:e.config},n=$n(e.astProject,e.files,e.projectRules,t),{diagnostics:r,errors:i}=V(n.diagnostics,n.errors,e),a=ir(e);return r.push(...a.diagnostics),i.push(...a.errors),{diagnostics:r,errors:i}}function ir(e){if(!e.schemaGraph||e.schemaRules.length===0||e.schemaGraph.entities.size===0)return{diagnostics:[],errors:[]};let t=er(e.schemaGraph,e.schemaRules);return V(t.diagnostics,t.errors,e)}function H(e){let t=s.now(),n=nr(e),r=rr(e),i=s.now()-t;return{diagnostics:[...n.diagnostics,...r.diagnostics],elapsedMs:i,ruleErrors:[...n.errors,...r.errors]}}function ar(e){return e>=90?`Excellent`:e>=75?`Good`:e>=50?`Fair`:e>=25?`Poor`:`Critical`}const or={error:3,warning:1.5,info:.5},sr={security:1.5,correctness:1.3,schema:1.1,architecture:1,performance:.8};function cr(e,t){if(t===0)return{value:100,label:ar(100)};let n=0;for(let t of e){let e=or[t.severity],r=sr[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:ar(i)}}function lr(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=cr(r,e.files.length),c=lr(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:Wn(o)},moduleGraph:e.moduleGraph,schemaGraph:o,customRuleWarnings:n,files:e.files,providers:e.providers}}function ur(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=cr(a,c),u=lr(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 dr=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},fr=(e,t)=>e===void 0?t:Number(e),pr=(e,t)=>t===void 0?!0:e>=t,W={error:_.red,warn:_.yellow,info:_.cyan,success:_.green,dim:_.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(``)}},mr=1e3,hr={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),gr=(e,t)=>t===`error`?W.error(e):t===`warning`?W.warn(e):W.info(e),_r=e=>e===`error`?`✗`:e===`warning`?`⚠`:`●`,vr=e=>e<mr?`${Math.round(e)}ms`:`${(e/mr).toFixed(1)}s`,yr=e=>e>=75?[`◠ ◠ ◠`,`╰───╯`]:e>=50?[`• • •`,`╰───╯`]:[`x x x`,`╰───╯`],br=e=>e>=90?`★★★★★`:e>=75?`★★★★☆`:e>=50?`★★★☆☆`:e>=25?`★★☆☆☆`:`★☆☆☆☆`,xr=e=>{let t=Math.round(e/100*50),n=50-t;return{filled:`█`.repeat(t),empty:`░`.repeat(n)}},Sr=e=>{let{filled:t,empty:n}=xr(e);return`${t}${n}`},Cr=e=>{let{filled:t,empty:n}=xr(e);return J(t,e)+W.dim(n)},wr=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}┘`)}`)},Tr=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},Er=e=>[...e].sort(([,e],[,t])=>hr[e[0].severity]-hr[t[0].severity]),Dr=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},Or=e=>new Set(e.map(e=>e.filePath)),kr=(e,t)=>{let n=Er([...Tr(e).entries()]);for(let[,e]of n){let n=e[0],r=gr(_r(n.severity),n.severity),i=e.length,a=i>1?gr(` (${i})`,n.severity):``;if(K.log(` ${r} ${n.message}${a}`),n.help&&K.dim(` ${n.help}`),t){let t=Dr(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]=yr(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=br(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(Sr(n.value),Cr(n.value))),s.push(q(``));let m=vr(o),h=Or(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(` `))),wr(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(kr(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 Ar(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 jr(e){console.log(JSON.stringify(e,null,2))}const X=(e,t,n)=>{let r=e.score.value;pr(r,t)||(n||K.error(`Score ${r} is below the minimum threshold of ${t}.`),process.exit(1))},Mr=e=>{e.summary.errors>0&&process.exit(1)},Nr=(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){jr(i.combined),X(i.combined,t,n);return}Ar(i,r.verbose),X(i.combined,t,n),Mr(i.combined)},Pr=(e,t,n,r)=>{let{result:i}=e;if(r.score){console.log(i.score.value),X(i,t,n);return}if(r.json){jr(i),X(i,t,n);return}Y(i,r.verbose),X(i,t,n),Mr(i)};let Z=null,Q=0;const $=new Set,Fr=(e,t,n)=>{if($.delete(t),Q--,Q<=0||!Z){Z?.[e](n),Z=null,Q=0;return}Z.stop(),v(n).start()[e](n);let[r]=$;r&&(Z.text=r),Z.start()},Ir=e=>({start(){return Q++,$.add(e),Z?Z.text=e:Z=v({text:e}).start(),{succeed:t=>Fr(`succeed`,e,t),fail:t=>Fr(`fail`,e,t)}}}),Lr=(e,t)=>{if(!t)for(let t of e)K.warn(t)};var Rr=class{options;resolvedMinimumScore;scanConfig;steps=[];targetPath;constructor(e,t){this.targetPath=e,this.options=t}resolveConfig(){return this.steps.push(async()=>{this.scanConfig=await ln(this.targetPath,this.options.configPath),this.resolvedMinimumScore=fr(this.options.minScore,this.scanConfig.config.minScore)}),this}warnCustomRules(){return this.steps.push(()=>{Lr(this.scanConfig.customRuleWarnings,this.options.isMachineReadable)}),this}async run(){let e=this.options.isMachineReadable?null:Ir(`Scanning...`).start();for(let e of this.steps)await e();e?.succeed(`Scan complete`)}},zr=class extends Rr{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 Kn(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=ur(this.monorepoCtx,this.rawOutputs,this.scanConfig.customRuleWarnings,e)}),this}output(){return this.steps.push(()=>{Nr(this.result,this.resolvedMinimumScore,this.options.isMachineReadable,{json:this.options.json,score:this.options.score,verbose:this.options.verbose})}),this}},Br=class extends Rr{context;rawOutput;result;buildContext(){return this.steps.push(async()=>{this.context=await Gn(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(()=>{Pr(this.result,this.resolvedMinimumScore,this.options.isMachineReadable,{json:this.options.json,score:this.options.score,verbose:this.options.verbose})}),this}},Vr=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=dr(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:Hr}=e(import.meta.url)(`../../package.json`);n(t({meta:{name:`nestjs-doctor`,version:Hr,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 Vr(e,Hr).resolveTargetPath().handleInit().handleReport().validateMinScore().run();if(!t)return;let{targetPath:n,options:r}=t,i=await y(n);if(i){await new zr(n,i,r).resolveConfig().buildContext().runRules().buildResult().warnCustomRules().output().run();return}await new Br(n,r).resolveConfig().buildContext().runRules().buildResult().warnCustomRules().output().run()}}));export{U as a,Kn as c,Se as d,y as f,ur as i,ln as l,K as n,H as o,W as r,Gn as s,Ir as t,F as u};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nestjs-doctor",
3
- "version": "0.4.20",
3
+ "version": "0.4.22",
4
4
  "description": "Static analysis tool for NestJS that produces a health score with actionable diagnostics and an interactive report",
5
5
  "type": "module",
6
6
  "bin": {