@longzai-intelligence/oxlint-plugin-nestjs-analyzer 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs ADDED
@@ -0,0 +1 @@
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require(`@oxlint/plugins`);function t(e){return typeof e!=`object`||!e?!1:`name`in e}function n(e){return typeof e!=`object`||!e?!1:`value`in e}function r(e){return typeof e!=`object`||!e?!1:`id`in e}function i(e){return typeof e!=`object`||!e?!1:`type`in e}function a(e){return typeof e!=`object`||!e?!1:`implements`in e}function o(e){return!(typeof e!=`object`||!e||!(`body`in e))}function s(e){return t(e)?e.name:null}function c(e){return n(e)&&typeof e.value==`string`?e.value:null}function l(e){let t=e.callee;if(t.type===`Identifier`)return s(t);if(t.type===`MemberExpression`&&t.property.type===`Identifier`){let e=s(t.property);if(e===null)return null;if(t.object.type===`Identifier`){let n=s(t.object);return n===null?null:`${n}.${e}`}if(t.object.type===`MemberExpression`&&t.object.property.type===`Identifier`&&t.object.object.type===`Identifier`){let n=s(t.object.object),r=s(t.object.property);return n===null||r===null?null:`${n}.${r}.${e}`}}return null}function u(e){return e.callee.type===`Identifier`?s(e.callee):null}function d(e){if(!r(e))return null;let n=e.id;return n&&typeof n==`object`&&i(n)&&n.type===`Identifier`&&t(n)?n.name:null}function f(e){return e.key.type===`Identifier`?s(e.key):e.key.type===`Literal`?c(e.key):e.key.type===`PrivateIdentifier`?s(e.key):null}function p(e){return[`Service`,`Repository`,`Guard`,`Interceptor`,`Pipe`,`Filter`,`Middleware`,`Gateway`,`Resolver`,`Strategy`,`Factory`,`Handler`,`Subscriber`,`Listener`,`Processor`,`Consumer`,`Adapter`].some(t=>e.endsWith(t))}function m(e){let t=[];if(!a(e))return t;let n=e.implements;if(!Array.isArray(n))return t;for(let e of n){if(!e||typeof e!=`object`)continue;let n=e;if(`expression`in n&&n.expression&&typeof n.expression==`object`){let e=n.expression;i(e)&&e.type===`Identifier`&&`name`in e&&t.push(String(e.name))}}return t}function h(e){return o(e)?e.body:null}function g(e){return typeof e==`object`&&!!e&&`parent`in e}function _(e){return typeof e==`object`&&!!e&&`value`in e}function v(e){return typeof e==`object`&&!!e&&`expression`in e}function y(e){return typeof e==`object`&&!!e&&`callee`in e}function ee(e){return typeof e==`object`&&!!e&&`object`in e}function te(e){return typeof e==`object`&&!!e&&`property`in e}function b(e){return typeof e==`object`&&!!e&&`params`in e}function ne(e){return typeof e==`object`&&!!e&&`static`in e}function x(e){return typeof e==`object`&&!!e&&`key`in e}function re(e){return typeof e==`object`&&!!e&&`init`in e}function S(e){return g(e)?e.parent:null}function C(e){let t=e.replace(/\\/g,`/`);return!!(t.includes(`.spec.`)||t.includes(`.test.`)||t.includes(`/cypress/`)||t.includes(`/__tests__/`)||t.includes(`/test/`)||t.includes(`/tests/`))}function w(e){let t=e.replace(/\\/g,`/`);return!!(t.includes(`/migrations/`)||t.includes(`/migration/`))}function T(e){let t=e.replace(/\\/g,`/`);return/[/\\]cli[/\\]/.test(t)||/[/\\]bin[/\\]/.test(t)||/[/\\]scripts[/\\]/.test(t)||t.startsWith(`cli/`)||t.startsWith(`bin/`)||t.startsWith(`scripts/`)}function ie(e){return e.includes(`/bootstrap/`)||e.startsWith(`bootstrap/`)}function ae(e){return e.startsWith(`setUp`)||e.startsWith(`setup`)}function oe(e,t){return e.includes(`.instrumentation.`)||e.endsWith(`/instrumentation.ts`)||e.endsWith(`/instrumentation.js`)||t===`instrumentation.ts`||t===`instrumentation.js`||e.includes(`/instrumentation/`)}function se(e){return e.includes(`run-migrations`)||e.includes(`run-migration`)||e.includes(`migrate.ts`)||e.includes(`migrate.js`)}function ce(e,t){return!!(e.includes(`data-source`)||e.includes(`datasource`)||e.includes(`ormconfig`)||t.includes(`Datasource`)||t.includes(`DataSource`))}function le(e){return e.includes(`typePicker`)||e.includes(`type-picker`)||e.includes(`columnType`)||e.includes(`column-type`)}function ue(e){return e.includes(`loadOrm`)||e.includes(`orm-config`)||e.includes(`OrmConfig`)}function de(e){return e.includes(`ClientFactory`)||e.includes(`clientFactory`)||e.includes(`PrismaFactory`)||e.includes(`prismaFactory`)}function fe(e,t){return t.includes(`Migrator`)||t.includes(`migrator`)||e.includes(`/migration/`)||e.includes(`/migrator/`)}function pe(e,t){return t===`seed.ts`||t===`seed.js`||e.includes(`/seeds/`)||e.includes(`/seeders/`)}function E(e){let t=e.replace(/\\/g,`/`),n=t.split(`/`).pop()||``;return!!(ie(t)||ae(n)||oe(t,n)||se(t)||ce(t,n)||le(n)||ue(n)||de(n)||fe(t,n)||pe(t,n))}function D(e){let t=e.replace(/\\/g,`/`);return t.includes(`/libs/`)||t.startsWith(`libs/`)}function me(e){let t=e.replace(/\\/g,`/`);return t.includes(`/mcp/`)||t.startsWith(`mcp/`)}function he(e){let t=e.replace(/\\/g,`/`).split(`/`).pop()||``;return t.includes(`loadConfig`)||t.includes(`load-config`)||t.includes(`ormconfig`)||t.includes(`OrmConfig`)||t===`data-source.ts`||t===`data-source.js`||t.includes(`DataSource`)||t.includes(`datasource`)}function ge(e){let t=e.replace(/\\/g,`/`),n=t.split(`/`).pop()||``;return n===`seed.ts`||n===`seed.js`||n===`seeds.ts`||n===`seeds.js`||t.includes(`/seeds/`)||t.includes(`/seeders/`)||t.includes(`/prisma/seed`)}const _e=[/[/\\]DataFactories[/\\]/,/[/\\]Factories[/\\]/,/[/\\]factories[/\\]/,/[/\\]Strategies[/\\]/,/[/\\]strategies[/\\]/];function ve(e){return _e.some(t=>t.test(e))}function ye(e){let t=e.replace(/\\/g,`/`).split(`/`).pop()||``;return t.includes(`Factory.ts`)||t.includes(`Factory.js`)||t.includes(`factory.ts`)||t.includes(`factory.js`)||t.includes(`ClientFactory`)||t.includes(`clientFactory`)}const be=new Set([`console.log`,`console.warn`,`console.error`,`console.info`,`console.debug`,`console.trace`,`console.dir`,`console.table`]),xe=(0,e.defineRule)({meta:{type:`suggestion`,docs:{description:`禁止直接使用 console 方法,应使用 NestJS Logger 服务`}},createOnce(e){let t=e.filename,n=t.split(`/`).pop()||``;if(C(t)||w(t)||T(t)||n.startsWith(`Mock`)||n.startsWith(`mock`)||E(t))return{};let r=e.sourceCode.text;return r.includes(`eslint-disable no-console`)||r.includes(`eslint-disable-next-line no-console`)||r.includes(`@Command(`)||r.includes(`@SubCommand(`)?{}:{CallExpression(t){if(t.type!==`CallExpression`)return;let n=l(t);n&&be.has(n)&&e.report({node:t,message:`检测到 '${n}' — 请使用 NestJS Logger 服务代替`})}}}}),Se=new Set([`PWD`,`HOME`,`USER`,`PATH`,`SHELL`,`TERM`,`LANG`,`LC_ALL`,`TZ`,`HOSTNAME`,`TMPDIR`]),Ce=new Set([`GIT_VERSION`,`GIT_COMMIT`,`GIT_BRANCH`,`GIT_TAG`,`BUILD_ID`,`BUILD_NUMBER`,`BUILD_VERSION`,`CI_COMMIT_SHA`,`CI_BUILD_ID`,`GITHUB_SHA`,`GITHUB_REF`]),we=new Set([`USES_MIGRATION_CONTAINER`,`MIGRATION_CONTAINER`,`IS_MIGRATION_CONTAINER`,`RUN_MIGRATIONS`,`SKIP_MIGRATIONS`]),Te=new Set([`=`,`+=`,`-=`,`*=`,`/=`,`%=`,`**=`,`<<=`,`>>=`,`>>>=`,`|=`,`^=`,`&=`,`||=`,`&&=`,`??=`]);function Ee(e){let n=e;for(;n&&typeof n==`object`&&g(n);){let e=n.parent;if(!e)break;if(i(e)&&e.type===`Decorator`&&v(e)&&typeof e.expression==`object`&&e.expression!==null){let n=e.expression;if(i(n)&&n.type===`CallExpression`&&y(n)&&typeof n.callee==`object`){let e=n.callee;if(e&&i(e)&&e.type===`Identifier`&&t(e)&&(e.name===`Module`||e.name===`Global`))return!0}}n=e}return!1}function De(e){let n=e;for(;n&&typeof n==`object`&&g(n);){let e=n.parent;if(!e)break;if(i(e)&&e.type===`CallExpression`&&y(e)&&typeof e.callee==`object`&&e.callee!==null){let n=e.callee;if(i(n)&&n.type===`MemberExpression`&&ee(n)&&te(n)&&typeof n.object==`object`&&typeof n.property==`object`){let e=n.object,r=n.property;if(e&&i(e)&&e.type===`Identifier`&&t(e)&&e.name===`ConfigModule`&&r&&i(r)&&r.type===`Identifier`&&t(r)&&(r.name===`forRoot`||r.name===`forRootAsync`))return!0}}n=e}return!1}function Oe(e){let t=e;for(;t&&typeof t==`object`&&g(t);){let e=t.parent;if(!e)break;if(i(e)&&(e.type===`AssignmentExpression`&&`left`in e&&e.left===t&&`operator`in e&&typeof e.operator==`string`&&Te.has(e.operator)||e.type===`AssignmentPattern`&&`left`in e&&e.left===t))return!0;t=e}return!1}function ke(e){return e.includes(`main.ts`)||e.includes(`.config.`)||e.includes(`/config/`)}function Ae(e){return e.includes(`.module.`)||e.endsWith(`Module.ts`)||e.endsWith(`Module.js`)}function je(e){if(C(e))return!0;let t=e.replace(/\\/g,`/`);return!!(ke(t)||Ae(t)||D(e)||w(e)||T(e)||E(e))}function Me(e,t){if(e.type!==`MemberExpression`||e.object.type!==`MemberExpression`)return;let n=e.object;if(n.object.type!==`Identifier`||n.object.name!==`process`||n.property.type!==`Identifier`||n.property.name!==`env`||Oe(e)||Ee(e)||De(e))return;if(`computed`in e&&e.computed===!0){t.report({node:e,message:`直接访问 'process.env[...]' — 请使用 ConfigService.get() 代替`});return}let r=e.property.type===`Identifier`?e.property.name:`<dynamic>`;Se.has(r)||Ce.has(r)||we.has(r)||t.report({node:e,message:`直接访问 'process.env.${r}' — 请使用 ConfigService.get() 代替`})}const Ne=(0,e.defineRule)({meta:{type:`suggestion`,docs:{description:`禁止直接访问 process.env,应使用 ConfigService.get()`}},createOnce(e){return je(e.filename)?{}:{MemberExpression(t){Me(t,e)}}}}),O=new Set(`Error.TypeError.RangeError.SyntaxError.ReferenceError.Date.Map.Set.WeakMap.WeakSet.Promise.Proxy.RegExp.Array.Object.URL.URLSearchParams.Headers.Request.Response.FormData.Buffer.EventEmitter.Subject.BehaviorSubject.ReplaySubject.Observable.ValidationPipe.ParseIntPipe.ParseBoolPipe.ParseArrayPipe.ParseUUIDPipe.ParseFloatPipe.DefaultValuePipe.ParseEnumPipe.HttpException.BadRequestException.UnauthorizedException.ForbiddenException.NotFoundException.ConflictException.InternalServerErrorException.NotImplementedException.BadGatewayException.ServiceUnavailableException.GatewayTimeoutException.PrismaClient`.split(`.`)),k=new Set([`Get`,`Post`,`Put`,`Delete`,`Patch`,`All`,`Head`,`Options`]),Pe=new Set([`Injectable`,`Controller`,`Module`,`Resolver`,`Catch`,`EventsHandler`,`CommandHandler`,`QueryHandler`,`Saga`,`WebSocketGateway`,`Processor`]);function Fe(e){return typeof e!=`object`||!e?!1:`value`in e}function A(e){return`decorators`in e}function j(e){let t=e.expression;return t.type===`Identifier`?t.name:t.type===`CallExpression`&&t.callee.type===`Identifier`?t.callee.name:t.type===`CallExpression`&&t.callee.type===`MemberExpression`&&t.callee.property.type===`Identifier`?t.callee.property.name:null}function M(e){return e.expression.type===`CallExpression`?e.expression.arguments.filter(e=>e.type!==`SpreadElement`):[]}function Ie(e){let t=M(e);if(t.length===0)return null;let n=t[0];if(!n)return null;if(n.type===`Literal`&&Fe(n)){let e=n.value;if(typeof e==`string`)return e}return null}function N(e){if(!A(e)||!Array.isArray(e.decorators))return[];let t=e.decorators??[],n=[];for(let e of t){let t=j(e);t&&n.push({name:t,arguments:M(e)})}return n}function P(e,t){return!A(e)||!Array.isArray(e.decorators)?!1:(e.decorators??[]).some(e=>j(e)===t)}function F(e,t){return!A(e)||!Array.isArray(e.decorators)?null:(e.decorators??[]).find(e=>j(e)===t)??null}const Le=Array.from(Pe);function I(e){return e.type===`ClassDeclaration`||e.type===`ClassExpression`}function Re(e){return I(e)?e.abstract===!0:!1}function L(e){return e.type===`MethodDefinition`}function ze(e){let t=(h(e)?.body??[]).filter(L);return t.length===0?!1:t.every(e=>e.static===!0)}function Be(e){let t=h(e)?.body??[];for(let e of t)if(L(e)&&e.kind===`constructor`)return e.value.params.length>0;return!1}function Ve(e){return I(e)?Array.isArray(e.implements)&&e.implements.length>0:!1}const He=(0,e.defineRule)({meta:{type:`suggestion`,docs:{description:`检测缺少 @Injectable() 装饰器的 provider 类`}},createOnce(e){return{before(){let t=e.filename;if(C(t)||w(t)||T(t)||ve(t))return!1},ClassDeclaration(t){let n=d(t);n&&p(n)&&(Re(t)||ze(t)||Be(t)&&(n.endsWith(`Factory`)&&Ve(t)||Le.some(e=>P(t,e))||e.report({node:t,message:`类 '${n}' 看起来像 provider 但缺少 @Injectable()`})))}}}}),Ue={onModuleInit:`OnModuleInit`,onModuleDestroy:`OnModuleDestroy`,onApplicationBootstrap:`OnApplicationBootstrap`,onApplicationShutdown:`OnApplicationShutdown`,beforeApplicationShutdown:`BeforeApplicationShutdown`},We=(0,e.defineRule)({meta:{type:`suggestion`,docs:{description:`检测缺少生命周期钩子接口声明的类`}},createOnce(e){return{ClassDeclaration(t){let n=d(t)??`<anonymous>`,r=m(t),i=h(t)?.body??[];for(let t of i){if(t.type!==`MethodDefinition`)continue;let i=f(t);if(!i)continue;let a=Ue[i];a&&(r.includes(a)||e.report({node:t,message:`'${n}.${i}()' 缺少 'implements ${a}'`}))}}}}});function Ge(e){return typeof e!=`object`||!e?!1:`value`in e}function Ke(e,t,n,r){let i=h(e)?.body??[];for(let e of i){if(e.type!==`MethodDefinition`)continue;let i=N(e);for(let a of i){if(!k.has(a.name))continue;let i=``;if(a.arguments.length>0&&a.arguments[0]){let e=a.arguments[0];if(e.type===`Literal`&&Ge(e)){let t=e.value;typeof t==`string`&&(i=t)}}let o=[n,i].filter(Boolean).join(`/`).replace(/\/+/g,`/`)||`/`,s=`${a.name}:${o}`,c={method:a.name,path:o,controllerName:t,handlerName:f(e)??`<computed>`,node:e},l=r.get(s)??[];l.push(c),r.set(s,l)}}}function qe(e,t){for(let[,n]of e)if(n.length>1)for(let e of n)t.report({node:e.node,message:`重复路由: ${e.method} ${e.path} 在 ${e.controllerName}.${e.handlerName}()`})}const Je=(0,e.defineRule)({meta:{type:`suggestion`,docs:{description:`检测单文件内的重复路由`}},createOnce(e){return{before(){let t=e.filename;if(C(t)||w(t)||T(t)||E(t))return!1},ClassDeclaration(t){let n=F(t,`Controller`);if(!n)return;let r=d(t)??`<anonymous>`,i=Ie(n)??``,a=new Map;Ke(t,r,i,a),qe(a,e)}}}}),Ye=(0,e.defineRule)({meta:{type:`suggestion`,docs:{description:`检测未配置全局异常过滤器的 NestJS 应用`}},createOnce(e){let t=e.sourceCode.text;return!t.includes(`NestFactory`)||t.includes(`useGlobalFilters`)||t.includes(`APP_FILTER`)||t.includes(`AllExceptionsFilter`)||t.includes(`HttpExceptionFilter`)||t.includes(`GlobalExceptionFilter`)?{}:{Program(t){e.report({node:t,message:`未配置全局异常过滤器 — 请使用 useGlobalFilters() 或 APP_FILTER provider`})}}}}),R=/(?:^|[._])(?:password|passwd|secret|api[_-]?key|auth[_-]?token|access[_-]?token|refresh[_-]?token|private[_-]?key|client[_-]?secret|db[_-]?pass(?:word)?|jwt[_-]?secret|encryption[_-]?key|signing[_-]?key|secret[_-]?key|connection[_-]?string)$/i,Xe=/^(true|false|null|undefined|none|default|test|example|placeholder|changeme|todo|fixme|xxx|mock|fake|dummy|sample|dev|local|development|staging|sandbox|demo|your[_-]?secret|replace[_-]?me|insert[_-]?here|<[^>]+>|\$\{[^}]+\}|%[^%]+%)$/i;function z(e){return!e||typeof e!=`object`?!1:`type`in e&&e.type===`Identifier`}function Ze(e){return!e||typeof e!=`object`?!1:`type`in e&&e.type===`Literal`}function Qe(e){return!e||typeof e!=`object`?!1:`type`in e&&e.type===`TemplateLiteral`}function $e(e){return`value`in e&&typeof e.value==`string`?e.value:null}function et(e){if(e.expressions.length===0&&e.quasis.length===1){let t=e.quasis[0];if(t&&`value`in t&&`cooked`in t.value)return t.value.cooked}return null}function B(e){return Ze(e)?$e(e):Qe(e)?et(e):null}function tt(e){return`id`in e}function nt(e){return`init`in e}function V(e){return`key`in e}function H(e){return`value`in e}function U(e){return e.length===0||e.length<=3?!1:!Xe.test(e)}function rt(e,t){if(!tt(e))return;let n=e.id;if(!z(n))return;let r=n.name;if(!R.test(r)||!nt(e))return;let i=e.init;if(!i||typeof i!=`object`)return;let a=B(i);!a||!U(a)||t.report({node:e,message:`在 '${r}' 中发现硬编码密钥 — 请移至环境变量`})}function it(e,t){if(!V(e))return;let n=e.key;if(!z(n))return;let r=n.name;if(!R.test(r)||!H(e))return;let i=e.value;if(!i||typeof i!=`object`)return;let a=B(i);!a||!U(a)||t.report({node:e,message:`在 '${r}' 中发现硬编码密钥 — 请移至环境变量`})}function at(e,t){if(!V(e))return;let n=e.key;if(!z(n))return;let r=n.name;if(!R.test(r)||!H(e))return;let i=e.value;if(!i||typeof i!=`object`)return;let a=B(i);!a||!U(a)||t.report({node:e,message:`在 '${r}' 中发现硬编码密钥 — 请移至环境变量`})}const ot=(0,e.defineRule)({meta:{type:`problem`,docs:{description:`检测硬编码的密钥,应移至环境变量`}},createOnce(e){return C(e.filename)?{}:{VariableDeclarator(t){rt(t,e)},ObjectProperty(t){it(t,e)},PropertyDefinition(t){at(t,e)}}}}),W=/\b(SELECT\s+\*|SELECT\s+\w+\b|SELECT\s+DISTINCT\b|INSERT\s+INTO\b|UPDATE\s+\w+\s+SET\b|DELETE\s+FROM\b|DROP\s+(TABLE|INDEX|DATABASE)\b|ALTER\s+TABLE\b|TRUNCATE(\s+TABLE)?\b|CREATE\s+(TABLE|INDEX|DATABASE|POLICY)\b)/i,G=new Set([`sql`,`sql`,`SQL`,`raw`,`knex`,`knex`,`sequelize`,`sequelize`,`literal`,`typeorm`,`TypeORM`,`drizzle`,`drizzle`,`kysely`,`kysely`,`slonik`,`slonik`,`mikroorm`,`MikroORM`]),st=new Set([`Prisma`,`prisma`,`knex`,`knex`,`sequelize`,`sequelize`,`db`,`DB`,`database`,`connection`,`pool`]);function ct(e){return e.type===`Identifier`?G.has(e.name):!!(e.type===`MemberExpression`&&e.property.type===`Identifier`&&(G.has(e.property.name)||e.object.type===`Identifier`&&st.has(e.object.name)))}function lt(e,t){if(e.type!==`TaggedTemplateExpression`)return;let n=e;if(ct(n.tag))return;let r=n.quasi;if(r.expressions.length===0)return;let i=r.quasis.map(e=>e.value.raw).join("${...}");W.test(i)&&t.report({node:e,message:`使用模板插值的原始 SQL — 请改用参数化查询`})}function ut(e,t){if(e.type!==`BinaryExpression`)return;let n=e;n.operator===`+`&&(n.left.type!==`Literal`||typeof n.left.value!=`string`||W.test(n.left.value)&&t.report({node:e,message:`使用字符串拼接的原始 SQL — 请改用参数化查询`}))}const dt=(0,e.defineRule)({meta:{type:`problem`,docs:{description:`检测 SQL 注入风险,应使用参数化查询`}},createOnce(e){return{TaggedTemplateExpression(t){lt(t,e)},BinaryExpression(t){ut(t,e)}}}}),K=/\b(SELECT\s+\*|SELECT\s+\w+\b|SELECT\s+DISTINCT\b|INSERT\s+INTO\b|UPDATE\s+\w+\s+SET\b|DELETE\s+FROM\b|DROP\s+(TABLE|INDEX|DATABASE)\b|ALTER\s+TABLE\b|TRUNCATE(\s+TABLE)?\b|CREATE\s+(TABLE|INDEX|DATABASE|POLICY)\b)/i,q=new Set([`sql`,`sql`,`SQL`,`raw`,`knex`,`knex`,`sequelize`,`sequelize`,`literal`,`typeorm`,`TypeORM`,`drizzle`,`drizzle`,`kysely`,`kysely`,`slonik`,`slonik`,`mikroorm`,`MikroORM`]),ft=new Set([`Prisma`,`prisma`,`knex`,`knex`,`sequelize`,`sequelize`,`db`,`DB`,`database`,`connection`,`pool`]);function pt(e){return e.type===`Identifier`?q.has(e.name):!!(e.type===`MemberExpression`&&e.property.type===`Identifier`&&(q.has(e.property.name)||e.object.type===`Identifier`&&ft.has(e.object.name)))}function mt(e,t){if(e.type!==`TaggedTemplateExpression`)return;let n=e;if(pt(n.tag)||n.quasi.expressions.length===0)return;let r=n.quasi.quasis.map(e=>e.value.raw).join("${...}");K.test(r)&&t.report({node:e,message:`使用模板插值的原始 SQL — 请改用参数化查询`})}function ht(e,t){if(e.type!==`BinaryExpression`)return;let n=e;n.operator===`+`&&(n.left.type!==`Literal`||typeof n.left.value!=`string`||K.test(n.left.value)&&t.report({node:e,message:`使用字符串拼接的原始 SQL — 请改用参数化查询`}))}const gt=(0,e.defineRule)({meta:{type:`problem`,docs:{description:`检测原始 SQL 查询,应使用参数化查询`}},createOnce(e){return C(e.filename)||w(e.filename)?{}:{TaggedTemplateExpression(t){mt(t,e)},BinaryExpression(t){ht(t,e)}}}}),_t=[/run[_-]?migrations?\.ts$/i,/migrations?\.ts$/i,/data[_-]?source\.ts$/i,/seed(er)?\.ts$/i,/cli\.ts$/i,/console\.ts$/i,/worker\.ts$/i,/job\.ts$/i,/cron\.ts$/i,/task\.ts$/i,/command\.ts$/i,/repl\.ts$/i];function vt(e){let t=e.replace(/\\/g,`/`);return _t.some(e=>e.test(t))}const yt=(0,e.defineRule)({meta:{type:`suggestion`,docs:{description:`检测未配置全局 ValidationPipe 的 NestJS 应用`}},createOnce(e){let t=e.sourceCode.text;return!t.includes(`NestFactory`)||vt(e.filename)||t.includes(`createApplicationContext`)||t.includes(`ValidationPipe`)||t.includes(`APP_PIPE`)||t.includes(`useGlobalPipes`)||t.includes(`GlobalValidationPipe`)||t.includes(`CustomValidationPipe`)||/validation.*pipe/i.test(t)?{}:{Program(t){e.report({node:t,message:`应用程序引导缺少全局 ValidationPipe`})}}}}),bt=[/Response(Dto|DTO)$/,/Results?(Dto|DTO)$/,/Output(Dto|DTO)$/,/^(Get|List|Find|Fetch|Read)[A-Z].*?(Dto|DTO)$/,/With(Display|View|Full|Extended|Details?)[A-Z].*?(Dto|DTO)$/,/Details(Dto|DTO)$/,/^Base[A-Z].*?(Dto|DTO)$/,/Fields?(Dto|DTO)$/,/Option(s)?(Dto|DTO)$/,/Info(Dto|DTO)$/,/Summary(Dto|DTO)$/,/Status(Dto|DTO)$/,/For[A-Z][a-zA-Z]*(Dto|DTO)$/,/View(Dto|DTO)$/,/Display(Dto|DTO)$/,/Model(Dto|DTO)$/],xt=[/^Create[A-Z]/,/^Update[A-Z]/,/^Patch[A-Z]/,/^Delete[A-Z]/,/^Add[A-Z]/,/^Remove[A-Z]/,/^Set[A-Z]/,/Request(Dto|DTO)$/,/Input(Dto|DTO)$/,/Params(Dto|DTO)$/,/Query(Dto|DTO)$/,/Body(Dto|DTO)$/];function J(e){return xt.some(t=>t.test(e))}function Y(e){let t=M(e);for(let e of t)if(e.type===`ObjectExpression`){for(let t of e.properties)if(!(t.type!==`Property`||t.key.type!==`Identifier`||t.key.name!==`readOnly`)&&t.value.type===`Literal`&&_(t.value)&&t.value.value===!0)return!0}return!1}function St(e){for(let t of e){let e=j(t);if((e===`ApiProperty`||e===`ApiPropertyOptional`)&&Y(t))return!0}return!1}function Ct(e){for(let t of e){let e=j(t);if(!e||e===`ApiProperty`||e===`ApiPropertyOptional`||e===`ApiResponseProperty`)continue;if(e.startsWith(`Is`)||e.startsWith(`Has`)||e.startsWith(`Validate`)||e.startsWith(`Check`)||e.startsWith(`Must`))return!0;let n=M(t);for(let e of n)if(e.type===`ObjectExpression`){for(let t of e.properties)if(!(t.type!==`Property`||t.key.type!==`Identifier`)&&(t.key.name===`message`||t.key.name===`groups`||t.key.name===`each`))return!0}}return!1}function wt(e){if(!e||typeof e!=`object`||!(`typeAnnotation`in e))return!1;let t=e.typeAnnotation;if(!t||typeof t!=`object`||!(`typeAnnotation`in t))return!1;let n=t.typeAnnotation;if(!n||typeof n!=`object`||!(`typeName`in n))return!1;let r=n.typeName;return!!r&&typeof r==`object`&&`name`in r&&typeof r.name==`string`&&r.name.endsWith(`Entity`)}function Tt(e,t){let n=h(e)?.body??[];for(let e of n){if(e.type!==`MethodDefinition`||!ne(e)||!e.static)continue;let n=f(e);if(n&&(n.startsWith(`to`)&&(n.endsWith(`Dto`)||n.endsWith(`DTO`))||n.startsWith(`from`)||n===`toDto`||n===`toDTO`||n===`create`&&Et(e)||t&&n.toLowerCase()===`to${t.toLowerCase()}`||n===`of`&&b(e)||n===`map`||n===`transform`||n===`mapFrom`))return!0}return!1}function Et(e){return!_(e)||!b(e.value)||!Array.isArray(e.value.params)||e.value.params.length===0?!1:wt(e.value.params[0])}function Dt(e){if(m(e).includes(`Partial`))return!0;if(!a(e))return!1;let t=e.implements;if(!Array.isArray(t))return!1;for(let e of t){if(!e||typeof e!=`object`)continue;let t=e;if(!v(t)||typeof t.expression!=`object`)continue;let n=t.expression;if(!n||typeof n!=`object`||!(`type`in n))continue;let r=n;if(r.type===`TSInstantiationExpression`&&v(r)){let e=r.expression;if(e&&typeof e==`object`&&`type`in e&&e.type===`Identifier`&&`name`in e&&e.name===`Partial`)return!0}}return!1}function Ot(e){let n=h(e)?.body??[],r=[];for(let e of n){if(e.type!==`PropertyDefinition`||!x(e)||!e.key||typeof e.key!=`object`)continue;let n=e.key;if(!(`type`in n)||n.type!==`Identifier`||!(`name`in n))continue;let i=t(n)?n.name:``;if(!i)continue;let a=`decorators`in e&&Array.isArray(e.decorators)?e.decorators:[];r.push({member:e,decorators:a,propName:i})}return r}function kt(e){return e.length===0?!1:e.every(e=>e.decorators.length===0)}function At(e){return e.some(e=>e.decorators.some(e=>j(e)===`ApiResponseProperty`))}function jt(e){return`readonly`in e}function Mt(e){return jt(e)&&e.readonly===!0}function Nt(e,t,n){if(t.length===0||J(e))return!1;let r=!1,i=!1,a=!1;for(let e of t){if(e.decorators.length===0){a=!0;continue}let t=Mt(e.member);for(let o of e.decorators){let e=j(o);if(e){if(n.has(e))return!1;(e===`ApiProperty`||e===`ApiPropertyOptional`||e===`ApiResponseProperty`||e===`ApiHideProperty`)&&(r=!0,Y(o)||t?i=!0:e!==`ApiHideProperty`&&e!==`ApiResponseProperty`&&(a=!0))}}}return r&&!(i&&a)}function Pt(e,t){let n=!1,r=!1,i=!0,a=0,o=!0,s=0,c=!1;for(let l of e)if(l.decorators.length===0&&(c=!0),l.decorators.some(e=>j(e)===`ApiResponseProperty`)&&(n=!0),Mt(l.member)?a++:i=!1,St(l.decorators)?s++:o=!1,l.propName===`id`){let{hasApi:e,hasVal:n}=Ft(l,t);e&&!n&&(r=!0)}return n||i&&a>=2||r&&!c||o&&s>=2}function Ft(e,t){return{hasApi:e.decorators.some(e=>{let t=j(e);return t===`ApiProperty`||t===`ApiPropertyOptional`}),hasVal:e.decorators.some(e=>{let n=j(e);return n!==null&&t.has(n)})}}function It(e,t,n){let r=Ot(e);return r.length===0?!1:bt.some(e=>e.test(t))||Tt(e,t)||Dt(e)?!0:J(t)?!1:kt(r)||At(r)||Nt(t,r,n)?!0:Pt(r,n)}const Lt=new Set(`IsString.IsNotEmpty.IsEmail.IsUrl.IsUUID.IsDateString.IsPhoneNumber.IsAlpha.IsAlphanumeric.IsAscii.IsBase64.IsCreditCard.IsHexColor.IsIP.IsJSON.IsJWT.IsLowercase.IsUppercase.Length.MinLength.MaxLength.Matches.Contains.NotContains.IsNumber.IsInt.IsPositive.IsNegative.Min.Max.IsDivisibleBy.IsBoolean.IsDate.MinDate.MaxDate.IsArray.ArrayMinSize.ArrayMaxSize.ArrayContains.ArrayNotContains.ArrayNotEmpty.ArrayUnique.IsObject.IsNotEmptyObject.ValidateNested.IsEnum.IsOptional.IsDefined.IsIn.IsNotIn.Equals.NotEquals.Type.Transform.Exclude.Expose`.split(`.`));function Rt(e){return e.some(e=>{let t=j(e);return t!==null&&Lt.has(t)})}function zt(e,n){let r=d(e);if(!r||!r.endsWith(`Dto`)&&!r.endsWith(`DTO`)||It(e,r,Lt))return;let i=h(e)?.body??[];for(let e of i){if(e.type!==`PropertyDefinition`||!(`key`in e)||!e.key||typeof e.key!=`object`)continue;let i=e.key;if(!t(i))continue;let a=i.name,o=`decorators`in e&&Bt(e)?e.decorators??[]:[];St(o)||Ct(o)||Rt(o)||n.report({node:e,message:`DTO '${r}' 中的属性 '${a}' 没有验证装饰器`})}}function Bt(e){return typeof e==`object`&&!!e&&`decorators`in e}const Vt=(0,e.defineRule)({meta:{type:`suggestion`,docs:{description:`检测 DTO 类中缺少验证装饰器的属性`}},createOnce(e){return{before(){let t=e.filename;return!(C(t)||w(t)||T(t))},ClassDeclaration(t){zt(t,e)}}}}),Ht=new Set([`fs`]),Ut=new Set([`fs`,`node:fs`]),Wt=new Set(`readFileSync.writeFileSync.appendFileSync.copyFileSync.mkdirSync.rmdirSync.readdirSync.statSync.lstatSync.accessSync.chmodSync.chownSync.renameSync.unlinkSync.existsSync.openSync.closeSync.fstatSync.ftruncateSync.futimesSync.linkSync.symlinkSync.readlinkSync.realpathSync.truncateSync.utimesSync.rmSync.cpSync`.split(`.`));function Gt(e,t,n,r,i){let a=e.split(`.`);if(a.length>=2){let e=a[0];return e&&n.has(e)?!0:e?r.has(e):!1}return i.has(t)}function Kt(e,t){return e.includes(`.module.`)||t.endsWith(`Module.ts`)||t.endsWith(`Module.js`)}function qt(e,t){return e.includes(`/cypress/`)||e.startsWith(`cypress/`)||t.includes(`jest`)||t.includes(`vitest`)}function Jt(e){return e.includes(`@Command(`)||e.includes(`@SubCommand(`)}function Yt(e,t,n){let r=e.source.type===`Literal`&&typeof e.source.value==`string`?e.source.value:null;if(!(!r||!Ut.has(r)))for(let r of e.specifiers)r.type===`ImportDefaultSpecifier`||r.type===`ImportNamespaceSpecifier`?t.add(r.local.name):r.type===`ImportSpecifier`&&n.add(r.local.name)}function Xt(e,t,n,r){if(e.type!==`CallExpression`)return;let i=l(e);if(!i)return;let a=i.split(`.`).pop();!a||!Wt.has(a)||Gt(i,a,Ht,n,r)&&t.report({node:e,message:`同步的 '${a}' 会阻塞事件循环 — 请使用异步版本`})}const Zt=(0,e.defineRule)({meta:{type:`suggestion`,docs:{description:`检测同步文件系统操作,应使用异步版本`}},createOnce(e){let t=new Set,n=new Set;return{before(){let t=e.filename,n=t.replace(/\\/g,`/`),r=n.split(`/`).pop()||``;if(Kt(t,r)||T(t)||qt(n,r))return!1;let i=e.sourceCode.text;return!Jt(i)},ImportDeclaration(e){Yt(e,t,n)},CallExpression(r){Xt(r,e,t,n)}}}}),Qt=new Set([`crypto`]),$t=new Set([`crypto`,`node:crypto`]),en=new Set([`pbkdf2Sync`,`scryptSync`,`generateKeyPairSync`,`generateKeySync`,`randomFillSync`]);function tn(e,t,n,r,i){let a=e.split(`.`);if(a.length>=2){let e=a[0];return e&&n.has(e)?!0:e?r.has(e):!1}return i.has(t)}function nn(e,t,n){let r=e.source.type===`Literal`&&typeof e.source.value==`string`?e.source.value:null;if(!(!r||!$t.has(r)))for(let r of e.specifiers)r.type===`ImportDefaultSpecifier`||r.type===`ImportNamespaceSpecifier`?t.add(r.local.name):r.type===`ImportSpecifier`&&n.add(r.local.name)}function rn(e,t,n,r){if(e.type!==`CallExpression`)return;let i=l(e);if(!i)return;let a=i.split(`.`).pop();!a||!en.has(a)||tn(i,a,Qt,n,r)&&t.report({node:e,message:`阻塞的 '${a}' 在请求路径中 — 请使用异步版本`})}const an=(0,e.defineRule)({meta:{type:`suggestion`,docs:{description:`检测阻塞加密操作,应使用异步版本`}},createOnce(e){let t=new Set,n=new Set;return{ImportDeclaration(e){nn(e,t,n)},CallExpression(r){rn(r,e,t,n)}}}}),on=(0,e.defineRule)({meta:{type:`suggestion`,docs:{description:`检测未配置缓存策略的 NestJS 应用`}},createOnce(e){let t=e.sourceCode.text;return!t.includes(`NestFactory`)&&!t.includes(`AppModule`)||t.includes(`CacheModule`)||t.includes(`CacheInterceptor`)||t.includes(`@Cacheable`)||t.includes(`cache-manager`)||t.includes(`redis`)||t.includes(`memcached`)||t.includes(`ioredis`)||!t.includes(`NestFactory`)?{}:{Program(t){e.report({node:t,message:`未配置缓存策略 — 对于读取密集型工作负载,建议使用 @nestjs/cache-manager`})}}}});function sn(e){return e.callee.type===`MemberExpression`&&e.callee.property.type===`Identifier`?e.callee.property.name:e.callee.type===`Identifier`?e.callee.name:null}function cn(e,t,n){if(e.type===`BlockStatement`&&o(e)){for(let r of e.body)if(r.type===`ExpressionStatement`&&r.expression.type===`CallExpression`){let e=sn(r.expression);e&&t.has(e)&&n.report({node:r.expression,message:`'${e}' 在循环中 — 建议使用 findMany/findByIds 批量处理或预加载`})}}}function X(e,t,n){o(e)&&cn(e.body,t,n)}function ln(e,t,n){if(e.type!==`CallExpression`||e.callee.type!==`MemberExpression`||e.callee.property.type!==`Identifier`||e.callee.property.name!==`map`||!l(e)?.endsWith(`.map`)||e.arguments.length===0)return;let r=e.arguments[0];r&&(r.type!==`ArrowFunctionExpression`&&r.type!==`FunctionExpression`||o(r)&&cn(r.body,t,n))}const un=(0,e.defineRule)({meta:{type:`suggestion`,docs:{description:`检测循环中的数据库查询,可能存在 N+1 问题`}},createOnce(e){let t=new Set([`findOne`,`findUnique`,`findFirst`,`findOneBy`]);return{before(){let t=e.filename;return!(C(t)||w(t)||T(t))},ForStatement(n){X(n,t,e)},ForInStatement(n){X(n,t,e)},ForOfStatement(n){X(n,t,e)},CallExpression(n){ln(n,t,e)}}}}),dn=(0,e.defineRule)({meta:{type:`suggestion`,docs:{description:`检测路由数量超过 10 个的过大控制器`}},createOnce(e){return{ClassDeclaration(t){if(!P(t,`Controller`))return;let n=0,r=h(t)?.body??[];for(let e of r)e.type===`MethodDefinition`&&N(e).some(e=>k.has(e.name))&&n++;if(n>10){let r=d(t)??`<anonymous>`;e.report({node:t,message:`控制器 '${r}' 有 ${n} 个路由(阈值:10)`})}}}}});function fn(e,t){if(!P(e,`Injectable`))return;let n=e.loc;if(!n)return;let r=n.start.line,i=n.end.line-r+1;if(i>400){let n=d(e)??`<anonymous>`;t.report({node:e,message:`服务 '${n}' 有 ${i} 行(阈值:400)`})}}const pn=(0,e.defineRule)({meta:{type:`suggestion`,docs:{description:`检测代码行数超过 400 行的过大服务`}},createOnce(e){return{before(){let t=e.filename;if(D(t)||C(t)||w(t)||T(t)||E(t))return!1},ClassDeclaration(t){fn(t,e)}}}});function mn(e){return typeof e!=`object`||!e?!1:`key`in e}function hn(e){return typeof e!=`object`||!e?!1:`value`in e}function gn(e){return!(typeof e!=`object`||!e||!(`elements`in e))}function _n(e){return typeof e!=`object`||!e?!1:`name`in e}const vn=(0,e.defineRule)({meta:{type:`suggestion`,docs:{description:`检测单文件内的模块循环依赖`}},createOnce(e){return{ClassDeclaration(t){if(!P(t,`Module`))return;let n=d(t);if(!n)return;let r=F(t,`Module`);if(!r)return;let i=M(r);if(i.length===0)return;let a=i[0];!a||a.type!==`ObjectExpression`||yn(a,`imports`).includes(n)&&e.report({node:t,message:`检测到自引用循环依赖:${n} → ${n}`})}}}});function yn(e,t){let n=[];for(let r of e.properties){if(r.type!==`Property`||!mn(r)||!r.key||typeof r.key!=`object`)continue;let e=r.key;if(!(`type`in e)||e.type!==`Identifier`||!(`name`in e)||!_n(e)||e.name!==t||!hn(r)||!r.value||typeof r.value!=`object`)continue;let i=r.value;if(!(!(`type`in i)||i.type!==`ArrayExpression`||!gn(i))){for(let e of i.elements)if(e&&typeof e==`object`&&`type`in e){let t=e;t.type===`Identifier`&&`name`in t&&typeof t.name==`string`&&n.push(t.name)}}}return n}const bn=new Set([`global`,`globalThis`,`window`,`process.env`]),xn=new Set([`Injectable`,`Controller`,`Module`]);function Sn(e,t){return e===`process`&&t===`env`||e===`console`}function Cn(e){return e.type===`NewExpression`}function wn(e){return e.type===`MemberExpression`}function Tn(e){for(let t of xn)if(P(e,t))return!0;return!1}function En(e,t){if(!Cn(e))return;let n=u(e);n&&(O.has(n)||p(n)&&t.report({node:e,message:`直接实例化 '${n}' 绕过了依赖注入 — 应该通过构造函数注入`}))}function Dn(e,t){if(!wn(e)||e.object.type!==`Identifier`)return;let n=e.object.name;if(!bn.has(n)||e.property.type!==`Identifier`)return;let r=e.property.name;Sn(n,r)||t.report({node:e,message:`访问全局状态 '${n}.${r}' — 应该通过依赖注入传递配置`})}function On(e,t){let n=d(e);n&&p(n)&&(Tn(e)||t.report({node:e,message:`类 '${n}' 匹配提供者命名约定但缺少 @Injectable() 装饰器`}))}const kn=(0,e.defineRule)({meta:{type:`suggestion`,docs:{description:`检测绕过依赖注入的模式`}},createOnce(e){return C(e.filename)?{}:{NewExpression(t){En(t,e)},MemberExpression(t){Dn(t,e)},ClassDeclaration(t){On(t,e)}}}});function Z(e){return!e||typeof e!=`object`||!t(e)?null:e.name}function An(e){let t=e;for(;t&&typeof t==`object`&&g(t);){let e=t.parent;if(!e)break;if(!i(e)){t=e;continue}if((e.type===`Property`||e.type===`ObjectProperty`)&&x(e)&&Z(e.key)===`useFactory`||e.type===`MethodDefinition`&&x(e)&&Z(e.key)===`useFactory`)return!0;t=e}return!1}function jn(e,n){if(!i(e)||e.type!==`FunctionDeclaration`||!r(e))return!1;let a=e.id;return a&&typeof a==`object`&&i(a)&&a.type===`Identifier`&&t(a)?n.functionNames.has(a.name):!1}function Mn(e,n){if(!i(e)||e.type!==`VariableDeclarator`||!r(e))return!1;let a=e.id;if(!a||typeof a!=`object`||!i(a)||a.type!==`Identifier`||!t(a))return!1;let o=a.name;if(!n.functionNames.has(o)||!re(e))return!1;let s=e.init;return!s||typeof s!=`object`||!i(s)?!1:s.type===`ArrowFunctionExpression`||s.type===`FunctionExpression`}function Nn(e,n){if(!i(e)||e.type!==`MethodDefinition`||!x(e))return!1;let a=Z(e.key);if(!a||n.classMethodNames.size===0)return!1;let o=S(e);if(!o||!i(o)||o.type!==`ClassBody`)return!1;let s=S(o);if(!s||!i(s)||s.type!==`ClassDeclaration`||!r(s))return!1;let c=s.id;if(!c||typeof c!=`object`||!i(c)||c.type!==`Identifier`||!t(c))return!1;let l=`${c.name}.${a}`;return n.classMethodNames.has(l)}function Pn(e,t){let n=e;for(;n&&typeof n==`object`&&g(n);){let e=n.parent;if(!e)break;if(jn(e,t)||Mn(e,t)||Nn(e,t))return!0;n=e}return!1}function Fn(e){let t=new Set,n=new Set,r=/useFactory\s*:\s*([A-Za-z_$][\w$]*)/g,i;for(;(i=r.exec(e))!==null;){let e=i[1];e&&t.add(e)}let a=/useFactory\s*:\s*([A-Za-z_$][\w$]*)\.([A-Za-z_$][\w$]*)/g;for(;(i=a.exec(e))!==null;){let e=i[1],t=i[2];e&&t&&n.add(`${e}.${t}`)}return{functionNames:t,classMethodNames:n}}function In(e){return!(C(e)||D(e)||E(e)||me(e)||he(e)||ge(e)||ye(e))}const Ln=(0,e.defineRule)({meta:{type:`suggestion`,docs:{description:`检测直接实例化服务类,应使用依赖注入`}},createOnce(e){let t={functionNames:new Set,classMethodNames:new Set};return{before(){let n=e.filename;return t={functionNames:new Set,classMethodNames:new Set},In(n)},Program(n){let r=e.sourceCode.text;t=Fn(r)},NewExpression(n){if(n.type!==`NewExpression`)return;let r=u(n);r&&(O.has(r)||An(n)||Pn(n,t)||p(r)&&e.report({node:n,message:`直接实例化 '${r}' — 应该使用依赖注入`}))}}}}),Rn=new Set([`ApiResponse`,`ApiOkResponse`,`ApiCreatedResponse`,`ApiAcceptedResponse`,`ApiNoContentResponse`,`ApiBadRequestResponse`,`ApiUnauthorizedResponse`,`ApiForbiddenResponse`,`ApiNotFoundResponse`,`ApiInternalServerErrorResponse`,`ApiDefaultResponse`]),zn=(0,e.defineRule)({meta:{type:`suggestion`,docs:{description:`检测缺少 Swagger 文档装饰器的路由处理器`}},createOnce(e){return C(e.filename)?{}:{ClassDeclaration(t){if(!P(t,`Controller`))return;let n=h(t)?.body??[];for(let t of n){if(t.type!==`MethodDefinition`)continue;let n=N(t);if(!n.some(e=>k.has(e.name))||P(t,`AllowUnauthenticated`)||P(t,`ApiExcludeEndpoint`))continue;let r=P(t,`ApiOperation`),i=n.some(e=>Rn.has(e.name));if(!r||!i){let n=f(t)??`<computed>`,a=[!r&&`@ApiOperation`,!i&&`@ApiResponse`].filter(Boolean).join(` and `);e.report({node:t,message:`Handler '${n}' is missing ${a}`})}}}}}});function Bn(e){return typeof e!=`object`||!e?!1:`typeName`in e}function Vn(e){return!(typeof e!=`object`||!e||!(`type`in e)||!(`name`in e))}function Hn(e){return!(typeof e!=`object`||!e||!(`typeAnnotation`in e))}function Un(e){return!(!(`returnType`in e)||!e.returnType)}function Wn(e){if(e.type!==`TSTypeReference`||!Bn(e))return null;let t=e.typeName;return typeof t!=`object`||!t||!(`type`in t)||!Vn(t)||t.type!==`Identifier`||typeof t.name!=`string`?null:t.name.endsWith(`Entity`)?t.name:null}function Gn(e){return Wn(e)||(Hn(e)?Gn(e.typeAnnotation):null)}function Kn(e){return N(e).some(e=>k.has(e.name))}function qn(e,t){if(!Un(e))return;let n=Gn(e.returnType.typeAnnotation);if(n){let r=f(e)??`<computed>`;t.report({node:e,message:`Handler '${r}' returns ORM entity '${n}' directly — map to a DTO`})}}const Jn=(0,e.defineRule)({meta:{type:`suggestion`,docs:{description:`检测路由处理器直接返回 ORM 实体类型`}},createOnce(e){return{ClassDeclaration(t){if(!P(t,`Controller`))return;let n=h(t)?.body??[];for(let t of n)t.type===`MethodDefinition`&&Kn(t)&&qn(t,e)}}}}),Yn={Post:{bad:new Set([`200`]),expected:`201 Created`},Delete:{bad:new Set([`200`,`201`]),expected:`204 No Content`}},Xn={OK:`200`,CREATED:`201`,ACCEPTED:`202`,NO_CONTENT:`204`};function Zn(e){return e.type===`Literal`&&`value`in e&&typeof e.value==`number`?{statusCode:String(e.value),statusDisplay:String(e.value)}:{statusCode:void 0,statusDisplay:void 0}}function Qn(e){if(e.type!==`MemberExpression`)return{statusCode:void 0,statusDisplay:void 0};let t=e.object,n=e.property;if(t.type===`Identifier`&&t.name===`HttpStatus`&&n.type===`Identifier`){let e=n.name;return{statusCode:Xn[e],statusDisplay:`HttpStatus.${e}`}}return{statusCode:void 0,statusDisplay:void 0}}function $n(e){let t=Zn(e);return t.statusCode?t:Qn(e)}function er(e,t){for(let[n,{bad:r,expected:i}]of Object.entries(Yn)){if(!P(e,n))continue;let a=F(e,`HttpCode`);if(!a)continue;let o=M(a);if(o.length===0)continue;let s=o[0];if(!s)continue;let{statusCode:c,statusDisplay:l}=$n(s);if(c&&r.has(c)){let r=f(e)??`<computed>`;t.report({node:a,message:`@${n} handler '${r}' uses @HttpCode(${l}) — should return ${i}`})}}}const tr=(0,e.defineRule)({meta:{type:`suggestion`,docs:{description:`检测 @HttpCode() 值与 HTTP 方法语义冲突的处理器`}},createOnce(e){return{ClassDeclaration(t){if(!P(t,`Controller`))return;let n=h(t)?.body??[];for(let t of n)t.type===`MethodDefinition`&&er(t,e)}}}}),nr=new Set([`Error`,`TypeError`,`RangeError`]);function rr(e){return`argument`in e&&e.argument!==null&&e.argument!==void 0}function ir(e){return!i(e)||e.type!==`NewExpression`||!v(e)||typeof e.callee!=`object`||!i(e.callee)||e.callee.type!==`Identifier`?null:e.callee.name}const ar=(0,e.defineRule)({meta:{type:`suggestion`,docs:{description:`检测使用通用异常的控制器,应使用 NestJS HTTP 异常`}},createOnce(e){let t=e.filename;return C(t)||D(t)?{}:{ThrowStatement(t){if(!rr(t))return;let n=t.argument;if(!n)return;let r=ir(n);r&&nr.has(r)&&e.report({node:t,message:`Throwing generic '${r}' — use NestJS HttpException or specific exceptions like BadRequestException`})}}}}),or=/^(find|get|list|fetch|load|search|query)(All|Many|List)?$/i,Q=new Set([`limit`,`offset`,`page`,`cursor`,`skip`,`take`,`pagesize`]),sr=(0,e.defineRule)({meta:{type:`suggestion`,docs:{description:`检测返回集合但没有分页参数的 GET 处理器`}},createOnce(e){return{ClassDeclaration(t){if(!P(t,`Controller`))return;let n=h(t)?.body??[];for(let t of n){if(t.type!==`MethodDefinition`||!P(t,`Get`))continue;let n=f(t);n&&or.test(n)&&(fr(t)||e.report({node:t,message:`Handler '${n}' returns a collection without pagination — consider adding limit/offset or cursor parameters`}))}}}}});function cr(e){return!(`name`in e)||typeof e.name!=`string`?!1:Q.has(e.name.toLowerCase())}function lr(e){if(!(`parameter`in e))return!1;let t=e.parameter;return!t||typeof t!=`object`||!(`type`in t)||!(`name`in t)||typeof t.name!=`string`?!1:Q.has(t.name.toLowerCase())}function ur(e){if(!(`decorators`in e))return!1;let t=e;if(!Array.isArray(t.decorators))return!1;for(let e of t.decorators)if(j(e)===`Query`&&dr(e))return!0;return!1}function dr(e){let t=M(e);if(t.length===0||!t[0])return!1;let n=t[0];return n.type!==`Literal`||!(`value`in n)?!1:typeof n.value==`string`&&Q.has(n.value.toLowerCase())}function fr(e){for(let t of e.value.params)if(t.type===`Identifier`&&cr(t)||t.type===`TSParameterProperty`&&lr(t)||ur(t))return!0;return!1}const $={"no-console-log":xe,"no-process-env-direct":Ne,"missing-injectable":He,"lifecycle-hook-interface":We,"no-duplicate-route":Je,"missing-exception-filter":Ye,"no-hardcoded-secrets":ot,"no-sql-injection":dt,"no-raw-sql":gt,"require-validation-pipe":yt,"missing-class-validator":Vt,"no-sync-fs":Zt,"no-blocking-crypto":an,"missing-caching":on,"no-n-plus-one":un,"no-god-controller":dn,"no-god-service":pn,"no-circular-dependency":vn,"no-di-bypass":kn,"no-hardcoded-dependency":Ln,"missing-swagger-decorators":zn,"no-entity-as-response":Jn,"no-inconsistent-http-status":tr,"no-generic-exception":ar,"prefer-pagination":sr},pr=(0,e.eslintCompatPlugin)({meta:{name:`@longzai-intelligence/nestjs-analyzer`},rules:$});exports.nestjsAnalyzerPlugin=pr,exports.nestjsAnalyzerRules=$;
@@ -0,0 +1,8 @@
1
+ import * as _$_oxlint_plugins0 from "@oxlint/plugins";
2
+ import { Rule } from "@oxlint/plugins";
3
+
4
+ //#region src/index.d.ts
5
+ declare const nestjsAnalyzerRules: Record<string, Rule>;
6
+ declare const nestjsAnalyzerPlugin: _$_oxlint_plugins0.Plugin;
7
+ //#endregion
8
+ export { nestjsAnalyzerPlugin, nestjsAnalyzerRules };
@@ -0,0 +1,8 @@
1
+ import * as _$_oxlint_plugins0 from "@oxlint/plugins";
2
+ import { Rule } from "@oxlint/plugins";
3
+
4
+ //#region src/index.d.ts
5
+ declare const nestjsAnalyzerRules: Record<string, Rule>;
6
+ declare const nestjsAnalyzerPlugin: _$_oxlint_plugins0.Plugin;
7
+ //#endregion
8
+ export { nestjsAnalyzerPlugin, nestjsAnalyzerRules };
package/dist/index.mjs ADDED
@@ -0,0 +1 @@
1
+ import{defineRule as e,eslintCompatPlugin as t}from"@oxlint/plugins";function n(e){return typeof e!=`object`||!e?!1:`name`in e}function r(e){return typeof e!=`object`||!e?!1:`value`in e}function i(e){return typeof e!=`object`||!e?!1:`id`in e}function a(e){return typeof e!=`object`||!e?!1:`type`in e}function o(e){return typeof e!=`object`||!e?!1:`implements`in e}function s(e){return!(typeof e!=`object`||!e||!(`body`in e))}function c(e){return n(e)?e.name:null}function l(e){return r(e)&&typeof e.value==`string`?e.value:null}function u(e){let t=e.callee;if(t.type===`Identifier`)return c(t);if(t.type===`MemberExpression`&&t.property.type===`Identifier`){let e=c(t.property);if(e===null)return null;if(t.object.type===`Identifier`){let n=c(t.object);return n===null?null:`${n}.${e}`}if(t.object.type===`MemberExpression`&&t.object.property.type===`Identifier`&&t.object.object.type===`Identifier`){let n=c(t.object.object),r=c(t.object.property);return n===null||r===null?null:`${n}.${r}.${e}`}}return null}function d(e){return e.callee.type===`Identifier`?c(e.callee):null}function f(e){if(!i(e))return null;let t=e.id;return t&&typeof t==`object`&&a(t)&&t.type===`Identifier`&&n(t)?t.name:null}function p(e){return e.key.type===`Identifier`?c(e.key):e.key.type===`Literal`?l(e.key):e.key.type===`PrivateIdentifier`?c(e.key):null}function m(e){return[`Service`,`Repository`,`Guard`,`Interceptor`,`Pipe`,`Filter`,`Middleware`,`Gateway`,`Resolver`,`Strategy`,`Factory`,`Handler`,`Subscriber`,`Listener`,`Processor`,`Consumer`,`Adapter`].some(t=>e.endsWith(t))}function ee(e){let t=[];if(!o(e))return t;let n=e.implements;if(!Array.isArray(n))return t;for(let e of n){if(!e||typeof e!=`object`)continue;let n=e;if(`expression`in n&&n.expression&&typeof n.expression==`object`){let e=n.expression;a(e)&&e.type===`Identifier`&&`name`in e&&t.push(String(e.name))}}return t}function h(e){return s(e)?e.body:null}function g(e){return typeof e==`object`&&!!e&&`parent`in e}function _(e){return typeof e==`object`&&!!e&&`value`in e}function v(e){return typeof e==`object`&&!!e&&`expression`in e}function y(e){return typeof e==`object`&&!!e&&`callee`in e}function te(e){return typeof e==`object`&&!!e&&`object`in e}function ne(e){return typeof e==`object`&&!!e&&`property`in e}function b(e){return typeof e==`object`&&!!e&&`params`in e}function re(e){return typeof e==`object`&&!!e&&`static`in e}function x(e){return typeof e==`object`&&!!e&&`key`in e}function ie(e){return typeof e==`object`&&!!e&&`init`in e}function S(e){return g(e)?e.parent:null}function C(e){let t=e.replace(/\\/g,`/`);return!!(t.includes(`.spec.`)||t.includes(`.test.`)||t.includes(`/cypress/`)||t.includes(`/__tests__/`)||t.includes(`/test/`)||t.includes(`/tests/`))}function w(e){let t=e.replace(/\\/g,`/`);return!!(t.includes(`/migrations/`)||t.includes(`/migration/`))}function T(e){let t=e.replace(/\\/g,`/`);return/[/\\]cli[/\\]/.test(t)||/[/\\]bin[/\\]/.test(t)||/[/\\]scripts[/\\]/.test(t)||t.startsWith(`cli/`)||t.startsWith(`bin/`)||t.startsWith(`scripts/`)}function ae(e){return e.includes(`/bootstrap/`)||e.startsWith(`bootstrap/`)}function oe(e){return e.startsWith(`setUp`)||e.startsWith(`setup`)}function se(e,t){return e.includes(`.instrumentation.`)||e.endsWith(`/instrumentation.ts`)||e.endsWith(`/instrumentation.js`)||t===`instrumentation.ts`||t===`instrumentation.js`||e.includes(`/instrumentation/`)}function ce(e){return e.includes(`run-migrations`)||e.includes(`run-migration`)||e.includes(`migrate.ts`)||e.includes(`migrate.js`)}function le(e,t){return!!(e.includes(`data-source`)||e.includes(`datasource`)||e.includes(`ormconfig`)||t.includes(`Datasource`)||t.includes(`DataSource`))}function ue(e){return e.includes(`typePicker`)||e.includes(`type-picker`)||e.includes(`columnType`)||e.includes(`column-type`)}function de(e){return e.includes(`loadOrm`)||e.includes(`orm-config`)||e.includes(`OrmConfig`)}function fe(e){return e.includes(`ClientFactory`)||e.includes(`clientFactory`)||e.includes(`PrismaFactory`)||e.includes(`prismaFactory`)}function pe(e,t){return t.includes(`Migrator`)||t.includes(`migrator`)||e.includes(`/migration/`)||e.includes(`/migrator/`)}function me(e,t){return t===`seed.ts`||t===`seed.js`||e.includes(`/seeds/`)||e.includes(`/seeders/`)}function E(e){let t=e.replace(/\\/g,`/`),n=t.split(`/`).pop()||``;return!!(ae(t)||oe(n)||se(t,n)||ce(t)||le(t,n)||ue(n)||de(n)||fe(n)||pe(t,n)||me(t,n))}function D(e){let t=e.replace(/\\/g,`/`);return t.includes(`/libs/`)||t.startsWith(`libs/`)}function he(e){let t=e.replace(/\\/g,`/`);return t.includes(`/mcp/`)||t.startsWith(`mcp/`)}function ge(e){let t=e.replace(/\\/g,`/`).split(`/`).pop()||``;return t.includes(`loadConfig`)||t.includes(`load-config`)||t.includes(`ormconfig`)||t.includes(`OrmConfig`)||t===`data-source.ts`||t===`data-source.js`||t.includes(`DataSource`)||t.includes(`datasource`)}function _e(e){let t=e.replace(/\\/g,`/`),n=t.split(`/`).pop()||``;return n===`seed.ts`||n===`seed.js`||n===`seeds.ts`||n===`seeds.js`||t.includes(`/seeds/`)||t.includes(`/seeders/`)||t.includes(`/prisma/seed`)}const ve=[/[/\\]DataFactories[/\\]/,/[/\\]Factories[/\\]/,/[/\\]factories[/\\]/,/[/\\]Strategies[/\\]/,/[/\\]strategies[/\\]/];function ye(e){return ve.some(t=>t.test(e))}function be(e){let t=e.replace(/\\/g,`/`).split(`/`).pop()||``;return t.includes(`Factory.ts`)||t.includes(`Factory.js`)||t.includes(`factory.ts`)||t.includes(`factory.js`)||t.includes(`ClientFactory`)||t.includes(`clientFactory`)}const xe=new Set([`console.log`,`console.warn`,`console.error`,`console.info`,`console.debug`,`console.trace`,`console.dir`,`console.table`]),Se=e({meta:{type:`suggestion`,docs:{description:`禁止直接使用 console 方法,应使用 NestJS Logger 服务`}},createOnce(e){let t=e.filename,n=t.split(`/`).pop()||``;if(C(t)||w(t)||T(t)||n.startsWith(`Mock`)||n.startsWith(`mock`)||E(t))return{};let r=e.sourceCode.text;return r.includes(`eslint-disable no-console`)||r.includes(`eslint-disable-next-line no-console`)||r.includes(`@Command(`)||r.includes(`@SubCommand(`)?{}:{CallExpression(t){if(t.type!==`CallExpression`)return;let n=u(t);n&&xe.has(n)&&e.report({node:t,message:`检测到 '${n}' — 请使用 NestJS Logger 服务代替`})}}}}),Ce=new Set([`PWD`,`HOME`,`USER`,`PATH`,`SHELL`,`TERM`,`LANG`,`LC_ALL`,`TZ`,`HOSTNAME`,`TMPDIR`]),we=new Set([`GIT_VERSION`,`GIT_COMMIT`,`GIT_BRANCH`,`GIT_TAG`,`BUILD_ID`,`BUILD_NUMBER`,`BUILD_VERSION`,`CI_COMMIT_SHA`,`CI_BUILD_ID`,`GITHUB_SHA`,`GITHUB_REF`]),Te=new Set([`USES_MIGRATION_CONTAINER`,`MIGRATION_CONTAINER`,`IS_MIGRATION_CONTAINER`,`RUN_MIGRATIONS`,`SKIP_MIGRATIONS`]),Ee=new Set([`=`,`+=`,`-=`,`*=`,`/=`,`%=`,`**=`,`<<=`,`>>=`,`>>>=`,`|=`,`^=`,`&=`,`||=`,`&&=`,`??=`]);function De(e){let t=e;for(;t&&typeof t==`object`&&g(t);){let e=t.parent;if(!e)break;if(a(e)&&e.type===`Decorator`&&v(e)&&typeof e.expression==`object`&&e.expression!==null){let t=e.expression;if(a(t)&&t.type===`CallExpression`&&y(t)&&typeof t.callee==`object`){let e=t.callee;if(e&&a(e)&&e.type===`Identifier`&&n(e)&&(e.name===`Module`||e.name===`Global`))return!0}}t=e}return!1}function Oe(e){let t=e;for(;t&&typeof t==`object`&&g(t);){let e=t.parent;if(!e)break;if(a(e)&&e.type===`CallExpression`&&y(e)&&typeof e.callee==`object`&&e.callee!==null){let t=e.callee;if(a(t)&&t.type===`MemberExpression`&&te(t)&&ne(t)&&typeof t.object==`object`&&typeof t.property==`object`){let e=t.object,r=t.property;if(e&&a(e)&&e.type===`Identifier`&&n(e)&&e.name===`ConfigModule`&&r&&a(r)&&r.type===`Identifier`&&n(r)&&(r.name===`forRoot`||r.name===`forRootAsync`))return!0}}t=e}return!1}function ke(e){let t=e;for(;t&&typeof t==`object`&&g(t);){let e=t.parent;if(!e)break;if(a(e)&&(e.type===`AssignmentExpression`&&`left`in e&&e.left===t&&`operator`in e&&typeof e.operator==`string`&&Ee.has(e.operator)||e.type===`AssignmentPattern`&&`left`in e&&e.left===t))return!0;t=e}return!1}function Ae(e){return e.includes(`main.ts`)||e.includes(`.config.`)||e.includes(`/config/`)}function je(e){return e.includes(`.module.`)||e.endsWith(`Module.ts`)||e.endsWith(`Module.js`)}function Me(e){if(C(e))return!0;let t=e.replace(/\\/g,`/`);return!!(Ae(t)||je(t)||D(e)||w(e)||T(e)||E(e))}function Ne(e,t){if(e.type!==`MemberExpression`||e.object.type!==`MemberExpression`)return;let n=e.object;if(n.object.type!==`Identifier`||n.object.name!==`process`||n.property.type!==`Identifier`||n.property.name!==`env`||ke(e)||De(e)||Oe(e))return;if(`computed`in e&&e.computed===!0){t.report({node:e,message:`直接访问 'process.env[...]' — 请使用 ConfigService.get() 代替`});return}let r=e.property.type===`Identifier`?e.property.name:`<dynamic>`;Ce.has(r)||we.has(r)||Te.has(r)||t.report({node:e,message:`直接访问 'process.env.${r}' — 请使用 ConfigService.get() 代替`})}const Pe=e({meta:{type:`suggestion`,docs:{description:`禁止直接访问 process.env,应使用 ConfigService.get()`}},createOnce(e){return Me(e.filename)?{}:{MemberExpression(t){Ne(t,e)}}}}),Fe=new Set(`Error.TypeError.RangeError.SyntaxError.ReferenceError.Date.Map.Set.WeakMap.WeakSet.Promise.Proxy.RegExp.Array.Object.URL.URLSearchParams.Headers.Request.Response.FormData.Buffer.EventEmitter.Subject.BehaviorSubject.ReplaySubject.Observable.ValidationPipe.ParseIntPipe.ParseBoolPipe.ParseArrayPipe.ParseUUIDPipe.ParseFloatPipe.DefaultValuePipe.ParseEnumPipe.HttpException.BadRequestException.UnauthorizedException.ForbiddenException.NotFoundException.ConflictException.InternalServerErrorException.NotImplementedException.BadGatewayException.ServiceUnavailableException.GatewayTimeoutException.PrismaClient`.split(`.`)),O=new Set([`Get`,`Post`,`Put`,`Delete`,`Patch`,`All`,`Head`,`Options`]),Ie=new Set([`Injectable`,`Controller`,`Module`,`Resolver`,`Catch`,`EventsHandler`,`CommandHandler`,`QueryHandler`,`Saga`,`WebSocketGateway`,`Processor`]);function Le(e){return typeof e!=`object`||!e?!1:`value`in e}function k(e){return`decorators`in e}function A(e){let t=e.expression;return t.type===`Identifier`?t.name:t.type===`CallExpression`&&t.callee.type===`Identifier`?t.callee.name:t.type===`CallExpression`&&t.callee.type===`MemberExpression`&&t.callee.property.type===`Identifier`?t.callee.property.name:null}function j(e){return e.expression.type===`CallExpression`?e.expression.arguments.filter(e=>e.type!==`SpreadElement`):[]}function Re(e){let t=j(e);if(t.length===0)return null;let n=t[0];if(!n)return null;if(n.type===`Literal`&&Le(n)){let e=n.value;if(typeof e==`string`)return e}return null}function M(e){if(!k(e)||!Array.isArray(e.decorators))return[];let t=e.decorators??[],n=[];for(let e of t){let t=A(e);t&&n.push({name:t,arguments:j(e)})}return n}function N(e,t){return!k(e)||!Array.isArray(e.decorators)?!1:(e.decorators??[]).some(e=>A(e)===t)}function P(e,t){return!k(e)||!Array.isArray(e.decorators)?null:(e.decorators??[]).find(e=>A(e)===t)??null}const ze=Array.from(Ie);function Be(e){return e.type===`ClassDeclaration`||e.type===`ClassExpression`}function Ve(e){return Be(e)?e.abstract===!0:!1}function He(e){return e.type===`MethodDefinition`}function Ue(e){let t=(h(e)?.body??[]).filter(He);return t.length===0?!1:t.every(e=>e.static===!0)}function We(e){let t=h(e)?.body??[];for(let e of t)if(He(e)&&e.kind===`constructor`)return e.value.params.length>0;return!1}function Ge(e){return Be(e)?Array.isArray(e.implements)&&e.implements.length>0:!1}const Ke=e({meta:{type:`suggestion`,docs:{description:`检测缺少 @Injectable() 装饰器的 provider 类`}},createOnce(e){return{before(){let t=e.filename;if(C(t)||w(t)||T(t)||ye(t))return!1},ClassDeclaration(t){let n=f(t);n&&m(n)&&(Ve(t)||Ue(t)||We(t)&&(n.endsWith(`Factory`)&&Ge(t)||ze.some(e=>N(t,e))||e.report({node:t,message:`类 '${n}' 看起来像 provider 但缺少 @Injectable()`})))}}}}),qe={onModuleInit:`OnModuleInit`,onModuleDestroy:`OnModuleDestroy`,onApplicationBootstrap:`OnApplicationBootstrap`,onApplicationShutdown:`OnApplicationShutdown`,beforeApplicationShutdown:`BeforeApplicationShutdown`},Je=e({meta:{type:`suggestion`,docs:{description:`检测缺少生命周期钩子接口声明的类`}},createOnce(e){return{ClassDeclaration(t){let n=f(t)??`<anonymous>`,r=ee(t),i=h(t)?.body??[];for(let t of i){if(t.type!==`MethodDefinition`)continue;let i=p(t);if(!i)continue;let a=qe[i];a&&(r.includes(a)||e.report({node:t,message:`'${n}.${i}()' 缺少 'implements ${a}'`}))}}}}});function Ye(e){return typeof e!=`object`||!e?!1:`value`in e}function Xe(e,t,n,r){let i=h(e)?.body??[];for(let e of i){if(e.type!==`MethodDefinition`)continue;let i=M(e);for(let a of i){if(!O.has(a.name))continue;let i=``;if(a.arguments.length>0&&a.arguments[0]){let e=a.arguments[0];if(e.type===`Literal`&&Ye(e)){let t=e.value;typeof t==`string`&&(i=t)}}let o=[n,i].filter(Boolean).join(`/`).replace(/\/+/g,`/`)||`/`,s=`${a.name}:${o}`,c={method:a.name,path:o,controllerName:t,handlerName:p(e)??`<computed>`,node:e},l=r.get(s)??[];l.push(c),r.set(s,l)}}}function Ze(e,t){for(let[,n]of e)if(n.length>1)for(let e of n)t.report({node:e.node,message:`重复路由: ${e.method} ${e.path} 在 ${e.controllerName}.${e.handlerName}()`})}const Qe=e({meta:{type:`suggestion`,docs:{description:`检测单文件内的重复路由`}},createOnce(e){return{before(){let t=e.filename;if(C(t)||w(t)||T(t)||E(t))return!1},ClassDeclaration(t){let n=P(t,`Controller`);if(!n)return;let r=f(t)??`<anonymous>`,i=Re(n)??``,a=new Map;Xe(t,r,i,a),Ze(a,e)}}}}),$e=e({meta:{type:`suggestion`,docs:{description:`检测未配置全局异常过滤器的 NestJS 应用`}},createOnce(e){let t=e.sourceCode.text;return!t.includes(`NestFactory`)||t.includes(`useGlobalFilters`)||t.includes(`APP_FILTER`)||t.includes(`AllExceptionsFilter`)||t.includes(`HttpExceptionFilter`)||t.includes(`GlobalExceptionFilter`)?{}:{Program(t){e.report({node:t,message:`未配置全局异常过滤器 — 请使用 useGlobalFilters() 或 APP_FILTER provider`})}}}}),F=/(?:^|[._])(?:password|passwd|secret|api[_-]?key|auth[_-]?token|access[_-]?token|refresh[_-]?token|private[_-]?key|client[_-]?secret|db[_-]?pass(?:word)?|jwt[_-]?secret|encryption[_-]?key|signing[_-]?key|secret[_-]?key|connection[_-]?string)$/i,et=/^(true|false|null|undefined|none|default|test|example|placeholder|changeme|todo|fixme|xxx|mock|fake|dummy|sample|dev|local|development|staging|sandbox|demo|your[_-]?secret|replace[_-]?me|insert[_-]?here|<[^>]+>|\$\{[^}]+\}|%[^%]+%)$/i;function I(e){return!e||typeof e!=`object`?!1:`type`in e&&e.type===`Identifier`}function tt(e){return!e||typeof e!=`object`?!1:`type`in e&&e.type===`Literal`}function nt(e){return!e||typeof e!=`object`?!1:`type`in e&&e.type===`TemplateLiteral`}function rt(e){return`value`in e&&typeof e.value==`string`?e.value:null}function it(e){if(e.expressions.length===0&&e.quasis.length===1){let t=e.quasis[0];if(t&&`value`in t&&`cooked`in t.value)return t.value.cooked}return null}function L(e){return tt(e)?rt(e):nt(e)?it(e):null}function at(e){return`id`in e}function ot(e){return`init`in e}function R(e){return`key`in e}function z(e){return`value`in e}function B(e){return e.length===0||e.length<=3?!1:!et.test(e)}function st(e,t){if(!at(e))return;let n=e.id;if(!I(n))return;let r=n.name;if(!F.test(r)||!ot(e))return;let i=e.init;if(!i||typeof i!=`object`)return;let a=L(i);!a||!B(a)||t.report({node:e,message:`在 '${r}' 中发现硬编码密钥 — 请移至环境变量`})}function ct(e,t){if(!R(e))return;let n=e.key;if(!I(n))return;let r=n.name;if(!F.test(r)||!z(e))return;let i=e.value;if(!i||typeof i!=`object`)return;let a=L(i);!a||!B(a)||t.report({node:e,message:`在 '${r}' 中发现硬编码密钥 — 请移至环境变量`})}function lt(e,t){if(!R(e))return;let n=e.key;if(!I(n))return;let r=n.name;if(!F.test(r)||!z(e))return;let i=e.value;if(!i||typeof i!=`object`)return;let a=L(i);!a||!B(a)||t.report({node:e,message:`在 '${r}' 中发现硬编码密钥 — 请移至环境变量`})}const ut=e({meta:{type:`problem`,docs:{description:`检测硬编码的密钥,应移至环境变量`}},createOnce(e){return C(e.filename)?{}:{VariableDeclarator(t){st(t,e)},ObjectProperty(t){ct(t,e)},PropertyDefinition(t){lt(t,e)}}}}),V=/\b(SELECT\s+\*|SELECT\s+\w+\b|SELECT\s+DISTINCT\b|INSERT\s+INTO\b|UPDATE\s+\w+\s+SET\b|DELETE\s+FROM\b|DROP\s+(TABLE|INDEX|DATABASE)\b|ALTER\s+TABLE\b|TRUNCATE(\s+TABLE)?\b|CREATE\s+(TABLE|INDEX|DATABASE|POLICY)\b)/i,H=new Set([`sql`,`sql`,`SQL`,`raw`,`knex`,`knex`,`sequelize`,`sequelize`,`literal`,`typeorm`,`TypeORM`,`drizzle`,`drizzle`,`kysely`,`kysely`,`slonik`,`slonik`,`mikroorm`,`MikroORM`]),dt=new Set([`Prisma`,`prisma`,`knex`,`knex`,`sequelize`,`sequelize`,`db`,`DB`,`database`,`connection`,`pool`]);function ft(e){return e.type===`Identifier`?H.has(e.name):!!(e.type===`MemberExpression`&&e.property.type===`Identifier`&&(H.has(e.property.name)||e.object.type===`Identifier`&&dt.has(e.object.name)))}function pt(e,t){if(e.type!==`TaggedTemplateExpression`)return;let n=e;if(ft(n.tag))return;let r=n.quasi;if(r.expressions.length===0)return;let i=r.quasis.map(e=>e.value.raw).join("${...}");V.test(i)&&t.report({node:e,message:`使用模板插值的原始 SQL — 请改用参数化查询`})}function mt(e,t){if(e.type!==`BinaryExpression`)return;let n=e;n.operator===`+`&&(n.left.type!==`Literal`||typeof n.left.value!=`string`||V.test(n.left.value)&&t.report({node:e,message:`使用字符串拼接的原始 SQL — 请改用参数化查询`}))}const ht=e({meta:{type:`problem`,docs:{description:`检测 SQL 注入风险,应使用参数化查询`}},createOnce(e){return{TaggedTemplateExpression(t){pt(t,e)},BinaryExpression(t){mt(t,e)}}}}),U=/\b(SELECT\s+\*|SELECT\s+\w+\b|SELECT\s+DISTINCT\b|INSERT\s+INTO\b|UPDATE\s+\w+\s+SET\b|DELETE\s+FROM\b|DROP\s+(TABLE|INDEX|DATABASE)\b|ALTER\s+TABLE\b|TRUNCATE(\s+TABLE)?\b|CREATE\s+(TABLE|INDEX|DATABASE|POLICY)\b)/i,W=new Set([`sql`,`sql`,`SQL`,`raw`,`knex`,`knex`,`sequelize`,`sequelize`,`literal`,`typeorm`,`TypeORM`,`drizzle`,`drizzle`,`kysely`,`kysely`,`slonik`,`slonik`,`mikroorm`,`MikroORM`]),gt=new Set([`Prisma`,`prisma`,`knex`,`knex`,`sequelize`,`sequelize`,`db`,`DB`,`database`,`connection`,`pool`]);function _t(e){return e.type===`Identifier`?W.has(e.name):!!(e.type===`MemberExpression`&&e.property.type===`Identifier`&&(W.has(e.property.name)||e.object.type===`Identifier`&&gt.has(e.object.name)))}function vt(e,t){if(e.type!==`TaggedTemplateExpression`)return;let n=e;if(_t(n.tag)||n.quasi.expressions.length===0)return;let r=n.quasi.quasis.map(e=>e.value.raw).join("${...}");U.test(r)&&t.report({node:e,message:`使用模板插值的原始 SQL — 请改用参数化查询`})}function yt(e,t){if(e.type!==`BinaryExpression`)return;let n=e;n.operator===`+`&&(n.left.type!==`Literal`||typeof n.left.value!=`string`||U.test(n.left.value)&&t.report({node:e,message:`使用字符串拼接的原始 SQL — 请改用参数化查询`}))}const bt=e({meta:{type:`problem`,docs:{description:`检测原始 SQL 查询,应使用参数化查询`}},createOnce(e){return C(e.filename)||w(e.filename)?{}:{TaggedTemplateExpression(t){vt(t,e)},BinaryExpression(t){yt(t,e)}}}}),xt=[/run[_-]?migrations?\.ts$/i,/migrations?\.ts$/i,/data[_-]?source\.ts$/i,/seed(er)?\.ts$/i,/cli\.ts$/i,/console\.ts$/i,/worker\.ts$/i,/job\.ts$/i,/cron\.ts$/i,/task\.ts$/i,/command\.ts$/i,/repl\.ts$/i];function St(e){let t=e.replace(/\\/g,`/`);return xt.some(e=>e.test(t))}const Ct=e({meta:{type:`suggestion`,docs:{description:`检测未配置全局 ValidationPipe 的 NestJS 应用`}},createOnce(e){let t=e.sourceCode.text;return!t.includes(`NestFactory`)||St(e.filename)||t.includes(`createApplicationContext`)||t.includes(`ValidationPipe`)||t.includes(`APP_PIPE`)||t.includes(`useGlobalPipes`)||t.includes(`GlobalValidationPipe`)||t.includes(`CustomValidationPipe`)||/validation.*pipe/i.test(t)?{}:{Program(t){e.report({node:t,message:`应用程序引导缺少全局 ValidationPipe`})}}}}),wt=[/Response(Dto|DTO)$/,/Results?(Dto|DTO)$/,/Output(Dto|DTO)$/,/^(Get|List|Find|Fetch|Read)[A-Z].*?(Dto|DTO)$/,/With(Display|View|Full|Extended|Details?)[A-Z].*?(Dto|DTO)$/,/Details(Dto|DTO)$/,/^Base[A-Z].*?(Dto|DTO)$/,/Fields?(Dto|DTO)$/,/Option(s)?(Dto|DTO)$/,/Info(Dto|DTO)$/,/Summary(Dto|DTO)$/,/Status(Dto|DTO)$/,/For[A-Z][a-zA-Z]*(Dto|DTO)$/,/View(Dto|DTO)$/,/Display(Dto|DTO)$/,/Model(Dto|DTO)$/],Tt=[/^Create[A-Z]/,/^Update[A-Z]/,/^Patch[A-Z]/,/^Delete[A-Z]/,/^Add[A-Z]/,/^Remove[A-Z]/,/^Set[A-Z]/,/Request(Dto|DTO)$/,/Input(Dto|DTO)$/,/Params(Dto|DTO)$/,/Query(Dto|DTO)$/,/Body(Dto|DTO)$/];function G(e){return Tt.some(t=>t.test(e))}function K(e){let t=j(e);for(let e of t)if(e.type===`ObjectExpression`){for(let t of e.properties)if(!(t.type!==`Property`||t.key.type!==`Identifier`||t.key.name!==`readOnly`)&&t.value.type===`Literal`&&_(t.value)&&t.value.value===!0)return!0}return!1}function q(e){for(let t of e){let e=A(t);if((e===`ApiProperty`||e===`ApiPropertyOptional`)&&K(t))return!0}return!1}function Et(e){for(let t of e){let e=A(t);if(!e||e===`ApiProperty`||e===`ApiPropertyOptional`||e===`ApiResponseProperty`)continue;if(e.startsWith(`Is`)||e.startsWith(`Has`)||e.startsWith(`Validate`)||e.startsWith(`Check`)||e.startsWith(`Must`))return!0;let n=j(t);for(let e of n)if(e.type===`ObjectExpression`){for(let t of e.properties)if(!(t.type!==`Property`||t.key.type!==`Identifier`)&&(t.key.name===`message`||t.key.name===`groups`||t.key.name===`each`))return!0}}return!1}function Dt(e){if(!e||typeof e!=`object`||!(`typeAnnotation`in e))return!1;let t=e.typeAnnotation;if(!t||typeof t!=`object`||!(`typeAnnotation`in t))return!1;let n=t.typeAnnotation;if(!n||typeof n!=`object`||!(`typeName`in n))return!1;let r=n.typeName;return!!r&&typeof r==`object`&&`name`in r&&typeof r.name==`string`&&r.name.endsWith(`Entity`)}function Ot(e,t){let n=h(e)?.body??[];for(let e of n){if(e.type!==`MethodDefinition`||!re(e)||!e.static)continue;let n=p(e);if(n&&(n.startsWith(`to`)&&(n.endsWith(`Dto`)||n.endsWith(`DTO`))||n.startsWith(`from`)||n===`toDto`||n===`toDTO`||n===`create`&&kt(e)||t&&n.toLowerCase()===`to${t.toLowerCase()}`||n===`of`&&b(e)||n===`map`||n===`transform`||n===`mapFrom`))return!0}return!1}function kt(e){return!_(e)||!b(e.value)||!Array.isArray(e.value.params)||e.value.params.length===0?!1:Dt(e.value.params[0])}function At(e){if(ee(e).includes(`Partial`))return!0;if(!o(e))return!1;let t=e.implements;if(!Array.isArray(t))return!1;for(let e of t){if(!e||typeof e!=`object`)continue;let t=e;if(!v(t)||typeof t.expression!=`object`)continue;let n=t.expression;if(!n||typeof n!=`object`||!(`type`in n))continue;let r=n;if(r.type===`TSInstantiationExpression`&&v(r)){let e=r.expression;if(e&&typeof e==`object`&&`type`in e&&e.type===`Identifier`&&`name`in e&&e.name===`Partial`)return!0}}return!1}function jt(e){let t=h(e)?.body??[],r=[];for(let e of t){if(e.type!==`PropertyDefinition`||!x(e)||!e.key||typeof e.key!=`object`)continue;let t=e.key;if(!(`type`in t)||t.type!==`Identifier`||!(`name`in t))continue;let i=n(t)?t.name:``;if(!i)continue;let a=`decorators`in e&&Array.isArray(e.decorators)?e.decorators:[];r.push({member:e,decorators:a,propName:i})}return r}function Mt(e){return e.length===0?!1:e.every(e=>e.decorators.length===0)}function Nt(e){return e.some(e=>e.decorators.some(e=>A(e)===`ApiResponseProperty`))}function Pt(e){return`readonly`in e}function J(e){return Pt(e)&&e.readonly===!0}function Ft(e,t,n){if(t.length===0||G(e))return!1;let r=!1,i=!1,a=!1;for(let e of t){if(e.decorators.length===0){a=!0;continue}let t=J(e.member);for(let o of e.decorators){let e=A(o);if(e){if(n.has(e))return!1;(e===`ApiProperty`||e===`ApiPropertyOptional`||e===`ApiResponseProperty`||e===`ApiHideProperty`)&&(r=!0,K(o)||t?i=!0:e!==`ApiHideProperty`&&e!==`ApiResponseProperty`&&(a=!0))}}}return r&&!(i&&a)}function It(e,t){let n=!1,r=!1,i=!0,a=0,o=!0,s=0,c=!1;for(let l of e)if(l.decorators.length===0&&(c=!0),l.decorators.some(e=>A(e)===`ApiResponseProperty`)&&(n=!0),J(l.member)?a++:i=!1,q(l.decorators)?s++:o=!1,l.propName===`id`){let{hasApi:e,hasVal:n}=Lt(l,t);e&&!n&&(r=!0)}return n||i&&a>=2||r&&!c||o&&s>=2}function Lt(e,t){return{hasApi:e.decorators.some(e=>{let t=A(e);return t===`ApiProperty`||t===`ApiPropertyOptional`}),hasVal:e.decorators.some(e=>{let n=A(e);return n!==null&&t.has(n)})}}function Rt(e,t,n){let r=jt(e);return r.length===0?!1:wt.some(e=>e.test(t))||Ot(e,t)||At(e)?!0:G(t)?!1:Mt(r)||Nt(r)||Ft(t,r,n)?!0:It(r,n)}const Y=new Set(`IsString.IsNotEmpty.IsEmail.IsUrl.IsUUID.IsDateString.IsPhoneNumber.IsAlpha.IsAlphanumeric.IsAscii.IsBase64.IsCreditCard.IsHexColor.IsIP.IsJSON.IsJWT.IsLowercase.IsUppercase.Length.MinLength.MaxLength.Matches.Contains.NotContains.IsNumber.IsInt.IsPositive.IsNegative.Min.Max.IsDivisibleBy.IsBoolean.IsDate.MinDate.MaxDate.IsArray.ArrayMinSize.ArrayMaxSize.ArrayContains.ArrayNotContains.ArrayNotEmpty.ArrayUnique.IsObject.IsNotEmptyObject.ValidateNested.IsEnum.IsOptional.IsDefined.IsIn.IsNotIn.Equals.NotEquals.Type.Transform.Exclude.Expose`.split(`.`));function zt(e){return e.some(e=>{let t=A(e);return t!==null&&Y.has(t)})}function Bt(e,t){let r=f(e);if(!r||!r.endsWith(`Dto`)&&!r.endsWith(`DTO`)||Rt(e,r,Y))return;let i=h(e)?.body??[];for(let e of i){if(e.type!==`PropertyDefinition`||!(`key`in e)||!e.key||typeof e.key!=`object`)continue;let i=e.key;if(!n(i))continue;let a=i.name,o=`decorators`in e&&Vt(e)?e.decorators??[]:[];q(o)||Et(o)||zt(o)||t.report({node:e,message:`DTO '${r}' 中的属性 '${a}' 没有验证装饰器`})}}function Vt(e){return typeof e==`object`&&!!e&&`decorators`in e}const Ht=e({meta:{type:`suggestion`,docs:{description:`检测 DTO 类中缺少验证装饰器的属性`}},createOnce(e){return{before(){let t=e.filename;return!(C(t)||w(t)||T(t))},ClassDeclaration(t){Bt(t,e)}}}}),Ut=new Set([`fs`]),Wt=new Set([`fs`,`node:fs`]),Gt=new Set(`readFileSync.writeFileSync.appendFileSync.copyFileSync.mkdirSync.rmdirSync.readdirSync.statSync.lstatSync.accessSync.chmodSync.chownSync.renameSync.unlinkSync.existsSync.openSync.closeSync.fstatSync.ftruncateSync.futimesSync.linkSync.symlinkSync.readlinkSync.realpathSync.truncateSync.utimesSync.rmSync.cpSync`.split(`.`));function Kt(e,t,n,r,i){let a=e.split(`.`);if(a.length>=2){let e=a[0];return e&&n.has(e)?!0:e?r.has(e):!1}return i.has(t)}function qt(e,t){return e.includes(`.module.`)||t.endsWith(`Module.ts`)||t.endsWith(`Module.js`)}function Jt(e,t){return e.includes(`/cypress/`)||e.startsWith(`cypress/`)||t.includes(`jest`)||t.includes(`vitest`)}function Yt(e){return e.includes(`@Command(`)||e.includes(`@SubCommand(`)}function Xt(e,t,n){let r=e.source.type===`Literal`&&typeof e.source.value==`string`?e.source.value:null;if(!(!r||!Wt.has(r)))for(let r of e.specifiers)r.type===`ImportDefaultSpecifier`||r.type===`ImportNamespaceSpecifier`?t.add(r.local.name):r.type===`ImportSpecifier`&&n.add(r.local.name)}function Zt(e,t,n,r){if(e.type!==`CallExpression`)return;let i=u(e);if(!i)return;let a=i.split(`.`).pop();!a||!Gt.has(a)||Kt(i,a,Ut,n,r)&&t.report({node:e,message:`同步的 '${a}' 会阻塞事件循环 — 请使用异步版本`})}const Qt=e({meta:{type:`suggestion`,docs:{description:`检测同步文件系统操作,应使用异步版本`}},createOnce(e){let t=new Set,n=new Set;return{before(){let t=e.filename,n=t.replace(/\\/g,`/`),r=n.split(`/`).pop()||``;if(qt(t,r)||T(t)||Jt(n,r))return!1;let i=e.sourceCode.text;return!Yt(i)},ImportDeclaration(e){Xt(e,t,n)},CallExpression(r){Zt(r,e,t,n)}}}}),$t=new Set([`crypto`]),en=new Set([`crypto`,`node:crypto`]),tn=new Set([`pbkdf2Sync`,`scryptSync`,`generateKeyPairSync`,`generateKeySync`,`randomFillSync`]);function nn(e,t,n,r,i){let a=e.split(`.`);if(a.length>=2){let e=a[0];return e&&n.has(e)?!0:e?r.has(e):!1}return i.has(t)}function rn(e,t,n){let r=e.source.type===`Literal`&&typeof e.source.value==`string`?e.source.value:null;if(!(!r||!en.has(r)))for(let r of e.specifiers)r.type===`ImportDefaultSpecifier`||r.type===`ImportNamespaceSpecifier`?t.add(r.local.name):r.type===`ImportSpecifier`&&n.add(r.local.name)}function an(e,t,n,r){if(e.type!==`CallExpression`)return;let i=u(e);if(!i)return;let a=i.split(`.`).pop();!a||!tn.has(a)||nn(i,a,$t,n,r)&&t.report({node:e,message:`阻塞的 '${a}' 在请求路径中 — 请使用异步版本`})}const on=e({meta:{type:`suggestion`,docs:{description:`检测阻塞加密操作,应使用异步版本`}},createOnce(e){let t=new Set,n=new Set;return{ImportDeclaration(e){rn(e,t,n)},CallExpression(r){an(r,e,t,n)}}}}),sn=e({meta:{type:`suggestion`,docs:{description:`检测未配置缓存策略的 NestJS 应用`}},createOnce(e){let t=e.sourceCode.text;return!t.includes(`NestFactory`)&&!t.includes(`AppModule`)||t.includes(`CacheModule`)||t.includes(`CacheInterceptor`)||t.includes(`@Cacheable`)||t.includes(`cache-manager`)||t.includes(`redis`)||t.includes(`memcached`)||t.includes(`ioredis`)||!t.includes(`NestFactory`)?{}:{Program(t){e.report({node:t,message:`未配置缓存策略 — 对于读取密集型工作负载,建议使用 @nestjs/cache-manager`})}}}});function cn(e){return e.callee.type===`MemberExpression`&&e.callee.property.type===`Identifier`?e.callee.property.name:e.callee.type===`Identifier`?e.callee.name:null}function X(e,t,n){if(e.type===`BlockStatement`&&s(e)){for(let r of e.body)if(r.type===`ExpressionStatement`&&r.expression.type===`CallExpression`){let e=cn(r.expression);e&&t.has(e)&&n.report({node:r.expression,message:`'${e}' 在循环中 — 建议使用 findMany/findByIds 批量处理或预加载`})}}}function Z(e,t,n){s(e)&&X(e.body,t,n)}function ln(e,t,n){if(e.type!==`CallExpression`||e.callee.type!==`MemberExpression`||e.callee.property.type!==`Identifier`||e.callee.property.name!==`map`||!u(e)?.endsWith(`.map`)||e.arguments.length===0)return;let r=e.arguments[0];r&&(r.type!==`ArrowFunctionExpression`&&r.type!==`FunctionExpression`||s(r)&&X(r.body,t,n))}const un=e({meta:{type:`suggestion`,docs:{description:`检测循环中的数据库查询,可能存在 N+1 问题`}},createOnce(e){let t=new Set([`findOne`,`findUnique`,`findFirst`,`findOneBy`]);return{before(){let t=e.filename;return!(C(t)||w(t)||T(t))},ForStatement(n){Z(n,t,e)},ForInStatement(n){Z(n,t,e)},ForOfStatement(n){Z(n,t,e)},CallExpression(n){ln(n,t,e)}}}}),dn=e({meta:{type:`suggestion`,docs:{description:`检测路由数量超过 10 个的过大控制器`}},createOnce(e){return{ClassDeclaration(t){if(!N(t,`Controller`))return;let n=0,r=h(t)?.body??[];for(let e of r)e.type===`MethodDefinition`&&M(e).some(e=>O.has(e.name))&&n++;if(n>10){let r=f(t)??`<anonymous>`;e.report({node:t,message:`控制器 '${r}' 有 ${n} 个路由(阈值:10)`})}}}}});function fn(e,t){if(!N(e,`Injectable`))return;let n=e.loc;if(!n)return;let r=n.start.line,i=n.end.line-r+1;if(i>400){let n=f(e)??`<anonymous>`;t.report({node:e,message:`服务 '${n}' 有 ${i} 行(阈值:400)`})}}const pn=e({meta:{type:`suggestion`,docs:{description:`检测代码行数超过 400 行的过大服务`}},createOnce(e){return{before(){let t=e.filename;if(D(t)||C(t)||w(t)||T(t)||E(t))return!1},ClassDeclaration(t){fn(t,e)}}}});function mn(e){return typeof e!=`object`||!e?!1:`key`in e}function hn(e){return typeof e!=`object`||!e?!1:`value`in e}function gn(e){return!(typeof e!=`object`||!e||!(`elements`in e))}function _n(e){return typeof e!=`object`||!e?!1:`name`in e}const vn=e({meta:{type:`suggestion`,docs:{description:`检测单文件内的模块循环依赖`}},createOnce(e){return{ClassDeclaration(t){if(!N(t,`Module`))return;let n=f(t);if(!n)return;let r=P(t,`Module`);if(!r)return;let i=j(r);if(i.length===0)return;let a=i[0];!a||a.type!==`ObjectExpression`||yn(a,`imports`).includes(n)&&e.report({node:t,message:`检测到自引用循环依赖:${n} → ${n}`})}}}});function yn(e,t){let n=[];for(let r of e.properties){if(r.type!==`Property`||!mn(r)||!r.key||typeof r.key!=`object`)continue;let e=r.key;if(!(`type`in e)||e.type!==`Identifier`||!(`name`in e)||!_n(e)||e.name!==t||!hn(r)||!r.value||typeof r.value!=`object`)continue;let i=r.value;if(!(!(`type`in i)||i.type!==`ArrayExpression`||!gn(i))){for(let e of i.elements)if(e&&typeof e==`object`&&`type`in e){let t=e;t.type===`Identifier`&&`name`in t&&typeof t.name==`string`&&n.push(t.name)}}}return n}const bn=new Set([`global`,`globalThis`,`window`,`process.env`]),xn=new Set([`Injectable`,`Controller`,`Module`]);function Sn(e,t){return e===`process`&&t===`env`||e===`console`}function Cn(e){return e.type===`NewExpression`}function wn(e){return e.type===`MemberExpression`}function Tn(e){for(let t of xn)if(N(e,t))return!0;return!1}function En(e,t){if(!Cn(e))return;let n=d(e);n&&(Fe.has(n)||m(n)&&t.report({node:e,message:`直接实例化 '${n}' 绕过了依赖注入 — 应该通过构造函数注入`}))}function Dn(e,t){if(!wn(e)||e.object.type!==`Identifier`)return;let n=e.object.name;if(!bn.has(n)||e.property.type!==`Identifier`)return;let r=e.property.name;Sn(n,r)||t.report({node:e,message:`访问全局状态 '${n}.${r}' — 应该通过依赖注入传递配置`})}function On(e,t){let n=f(e);n&&m(n)&&(Tn(e)||t.report({node:e,message:`类 '${n}' 匹配提供者命名约定但缺少 @Injectable() 装饰器`}))}const kn=e({meta:{type:`suggestion`,docs:{description:`检测绕过依赖注入的模式`}},createOnce(e){return C(e.filename)?{}:{NewExpression(t){En(t,e)},MemberExpression(t){Dn(t,e)},ClassDeclaration(t){On(t,e)}}}});function Q(e){return!e||typeof e!=`object`||!n(e)?null:e.name}function An(e){let t=e;for(;t&&typeof t==`object`&&g(t);){let e=t.parent;if(!e)break;if(!a(e)){t=e;continue}if((e.type===`Property`||e.type===`ObjectProperty`)&&x(e)&&Q(e.key)===`useFactory`||e.type===`MethodDefinition`&&x(e)&&Q(e.key)===`useFactory`)return!0;t=e}return!1}function jn(e,t){if(!a(e)||e.type!==`FunctionDeclaration`||!i(e))return!1;let r=e.id;return r&&typeof r==`object`&&a(r)&&r.type===`Identifier`&&n(r)?t.functionNames.has(r.name):!1}function Mn(e,t){if(!a(e)||e.type!==`VariableDeclarator`||!i(e))return!1;let r=e.id;if(!r||typeof r!=`object`||!a(r)||r.type!==`Identifier`||!n(r))return!1;let o=r.name;if(!t.functionNames.has(o)||!ie(e))return!1;let s=e.init;return!s||typeof s!=`object`||!a(s)?!1:s.type===`ArrowFunctionExpression`||s.type===`FunctionExpression`}function Nn(e,t){if(!a(e)||e.type!==`MethodDefinition`||!x(e))return!1;let r=Q(e.key);if(!r||t.classMethodNames.size===0)return!1;let o=S(e);if(!o||!a(o)||o.type!==`ClassBody`)return!1;let s=S(o);if(!s||!a(s)||s.type!==`ClassDeclaration`||!i(s))return!1;let c=s.id;if(!c||typeof c!=`object`||!a(c)||c.type!==`Identifier`||!n(c))return!1;let l=`${c.name}.${r}`;return t.classMethodNames.has(l)}function Pn(e,t){let n=e;for(;n&&typeof n==`object`&&g(n);){let e=n.parent;if(!e)break;if(jn(e,t)||Mn(e,t)||Nn(e,t))return!0;n=e}return!1}function Fn(e){let t=new Set,n=new Set,r=/useFactory\s*:\s*([A-Za-z_$][\w$]*)/g,i;for(;(i=r.exec(e))!==null;){let e=i[1];e&&t.add(e)}let a=/useFactory\s*:\s*([A-Za-z_$][\w$]*)\.([A-Za-z_$][\w$]*)/g;for(;(i=a.exec(e))!==null;){let e=i[1],t=i[2];e&&t&&n.add(`${e}.${t}`)}return{functionNames:t,classMethodNames:n}}function In(e){return!(C(e)||D(e)||E(e)||he(e)||ge(e)||_e(e)||be(e))}const Ln=e({meta:{type:`suggestion`,docs:{description:`检测直接实例化服务类,应使用依赖注入`}},createOnce(e){let t={functionNames:new Set,classMethodNames:new Set};return{before(){let n=e.filename;return t={functionNames:new Set,classMethodNames:new Set},In(n)},Program(n){let r=e.sourceCode.text;t=Fn(r)},NewExpression(n){if(n.type!==`NewExpression`)return;let r=d(n);r&&(Fe.has(r)||An(n)||Pn(n,t)||m(r)&&e.report({node:n,message:`直接实例化 '${r}' — 应该使用依赖注入`}))}}}}),Rn=new Set([`ApiResponse`,`ApiOkResponse`,`ApiCreatedResponse`,`ApiAcceptedResponse`,`ApiNoContentResponse`,`ApiBadRequestResponse`,`ApiUnauthorizedResponse`,`ApiForbiddenResponse`,`ApiNotFoundResponse`,`ApiInternalServerErrorResponse`,`ApiDefaultResponse`]),zn=e({meta:{type:`suggestion`,docs:{description:`检测缺少 Swagger 文档装饰器的路由处理器`}},createOnce(e){return C(e.filename)?{}:{ClassDeclaration(t){if(!N(t,`Controller`))return;let n=h(t)?.body??[];for(let t of n){if(t.type!==`MethodDefinition`)continue;let n=M(t);if(!n.some(e=>O.has(e.name))||N(t,`AllowUnauthenticated`)||N(t,`ApiExcludeEndpoint`))continue;let r=N(t,`ApiOperation`),i=n.some(e=>Rn.has(e.name));if(!r||!i){let n=p(t)??`<computed>`,a=[!r&&`@ApiOperation`,!i&&`@ApiResponse`].filter(Boolean).join(` and `);e.report({node:t,message:`Handler '${n}' is missing ${a}`})}}}}}});function Bn(e){return typeof e!=`object`||!e?!1:`typeName`in e}function Vn(e){return!(typeof e!=`object`||!e||!(`type`in e)||!(`name`in e))}function Hn(e){return!(typeof e!=`object`||!e||!(`typeAnnotation`in e))}function Un(e){return!(!(`returnType`in e)||!e.returnType)}function Wn(e){if(e.type!==`TSTypeReference`||!Bn(e))return null;let t=e.typeName;return typeof t!=`object`||!t||!(`type`in t)||!Vn(t)||t.type!==`Identifier`||typeof t.name!=`string`?null:t.name.endsWith(`Entity`)?t.name:null}function Gn(e){return Wn(e)||(Hn(e)?Gn(e.typeAnnotation):null)}function Kn(e){return M(e).some(e=>O.has(e.name))}function qn(e,t){if(!Un(e))return;let n=Gn(e.returnType.typeAnnotation);if(n){let r=p(e)??`<computed>`;t.report({node:e,message:`Handler '${r}' returns ORM entity '${n}' directly — map to a DTO`})}}const Jn=e({meta:{type:`suggestion`,docs:{description:`检测路由处理器直接返回 ORM 实体类型`}},createOnce(e){return{ClassDeclaration(t){if(!N(t,`Controller`))return;let n=h(t)?.body??[];for(let t of n)t.type===`MethodDefinition`&&Kn(t)&&qn(t,e)}}}}),Yn={Post:{bad:new Set([`200`]),expected:`201 Created`},Delete:{bad:new Set([`200`,`201`]),expected:`204 No Content`}},Xn={OK:`200`,CREATED:`201`,ACCEPTED:`202`,NO_CONTENT:`204`};function Zn(e){return e.type===`Literal`&&`value`in e&&typeof e.value==`number`?{statusCode:String(e.value),statusDisplay:String(e.value)}:{statusCode:void 0,statusDisplay:void 0}}function Qn(e){if(e.type!==`MemberExpression`)return{statusCode:void 0,statusDisplay:void 0};let t=e.object,n=e.property;if(t.type===`Identifier`&&t.name===`HttpStatus`&&n.type===`Identifier`){let e=n.name;return{statusCode:Xn[e],statusDisplay:`HttpStatus.${e}`}}return{statusCode:void 0,statusDisplay:void 0}}function $n(e){let t=Zn(e);return t.statusCode?t:Qn(e)}function er(e,t){for(let[n,{bad:r,expected:i}]of Object.entries(Yn)){if(!N(e,n))continue;let a=P(e,`HttpCode`);if(!a)continue;let o=j(a);if(o.length===0)continue;let s=o[0];if(!s)continue;let{statusCode:c,statusDisplay:l}=$n(s);if(c&&r.has(c)){let r=p(e)??`<computed>`;t.report({node:a,message:`@${n} handler '${r}' uses @HttpCode(${l}) — should return ${i}`})}}}const tr=e({meta:{type:`suggestion`,docs:{description:`检测 @HttpCode() 值与 HTTP 方法语义冲突的处理器`}},createOnce(e){return{ClassDeclaration(t){if(!N(t,`Controller`))return;let n=h(t)?.body??[];for(let t of n)t.type===`MethodDefinition`&&er(t,e)}}}}),nr=new Set([`Error`,`TypeError`,`RangeError`]);function rr(e){return`argument`in e&&e.argument!==null&&e.argument!==void 0}function ir(e){return!a(e)||e.type!==`NewExpression`||!v(e)||typeof e.callee!=`object`||!a(e.callee)||e.callee.type!==`Identifier`?null:e.callee.name}const ar=e({meta:{type:`suggestion`,docs:{description:`检测使用通用异常的控制器,应使用 NestJS HTTP 异常`}},createOnce(e){let t=e.filename;return C(t)||D(t)?{}:{ThrowStatement(t){if(!rr(t))return;let n=t.argument;if(!n)return;let r=ir(n);r&&nr.has(r)&&e.report({node:t,message:`Throwing generic '${r}' — use NestJS HttpException or specific exceptions like BadRequestException`})}}}}),or=/^(find|get|list|fetch|load|search|query)(All|Many|List)?$/i,$=new Set([`limit`,`offset`,`page`,`cursor`,`skip`,`take`,`pagesize`]),sr=e({meta:{type:`suggestion`,docs:{description:`检测返回集合但没有分页参数的 GET 处理器`}},createOnce(e){return{ClassDeclaration(t){if(!N(t,`Controller`))return;let n=h(t)?.body??[];for(let t of n){if(t.type!==`MethodDefinition`||!N(t,`Get`))continue;let n=p(t);n&&or.test(n)&&(fr(t)||e.report({node:t,message:`Handler '${n}' returns a collection without pagination — consider adding limit/offset or cursor parameters`}))}}}}});function cr(e){return!(`name`in e)||typeof e.name!=`string`?!1:$.has(e.name.toLowerCase())}function lr(e){if(!(`parameter`in e))return!1;let t=e.parameter;return!t||typeof t!=`object`||!(`type`in t)||!(`name`in t)||typeof t.name!=`string`?!1:$.has(t.name.toLowerCase())}function ur(e){if(!(`decorators`in e))return!1;let t=e;if(!Array.isArray(t.decorators))return!1;for(let e of t.decorators)if(A(e)===`Query`&&dr(e))return!0;return!1}function dr(e){let t=j(e);if(t.length===0||!t[0])return!1;let n=t[0];return n.type!==`Literal`||!(`value`in n)?!1:typeof n.value==`string`&&$.has(n.value.toLowerCase())}function fr(e){for(let t of e.value.params)if(t.type===`Identifier`&&cr(t)||t.type===`TSParameterProperty`&&lr(t)||ur(t))return!0;return!1}const pr={"no-console-log":Se,"no-process-env-direct":Pe,"missing-injectable":Ke,"lifecycle-hook-interface":Je,"no-duplicate-route":Qe,"missing-exception-filter":$e,"no-hardcoded-secrets":ut,"no-sql-injection":ht,"no-raw-sql":bt,"require-validation-pipe":Ct,"missing-class-validator":Ht,"no-sync-fs":Qt,"no-blocking-crypto":on,"missing-caching":sn,"no-n-plus-one":un,"no-god-controller":dn,"no-god-service":pn,"no-circular-dependency":vn,"no-di-bypass":kn,"no-hardcoded-dependency":Ln,"missing-swagger-decorators":zn,"no-entity-as-response":Jn,"no-inconsistent-http-status":tr,"no-generic-exception":ar,"prefer-pagination":sr},mr=t({meta:{name:`@longzai-intelligence/nestjs-analyzer`},rules:pr});export{mr as nestjsAnalyzerPlugin,pr as nestjsAnalyzerRules};
package/package.json ADDED
@@ -0,0 +1,79 @@
1
+ {
2
+ "name": "@longzai-intelligence/oxlint-plugin-nestjs-analyzer",
3
+ "version": "0.0.1",
4
+ "description": "Oxlint NestJS 分析插件,提供 NestJS 应用深度分析规则",
5
+ "license": "UNLICENSED",
6
+ "files": [
7
+ "dist"
8
+ ],
9
+ "type": "module",
10
+ "main": "./dist/index.cjs",
11
+ "module": "./dist/index.mjs",
12
+ "types": "./dist/index.d.mts",
13
+ "exports": {
14
+ ".": {
15
+ "types": {
16
+ "import": "./dist/index.d.mts",
17
+ "require": "./dist/index.d.cts"
18
+ },
19
+ "import": "./dist/index.mjs",
20
+ "require": "./dist/index.cjs"
21
+ }
22
+ },
23
+ "scripts": {
24
+ "build": "tsdown",
25
+ "build:prod": "NODE_ENV=production tsdown",
26
+ "prepublishOnly": "bun run build:prod",
27
+ "dev": "tsdown --watch",
28
+ "clean": "rimraf dist",
29
+ "test": "vitest run",
30
+ "test:watch": "vitest",
31
+ "test:coverage": "vitest run --coverage",
32
+ "lint": "bun run lint:oxlint",
33
+ "lint:oxlint": "oxlint",
34
+ "lint:eslint": "eslint .",
35
+ "lint:fix": "bun run oxc:fix",
36
+ "lint:fix:oxlint": "oxlint --fix",
37
+ "lint:fix:eslint": "eslint . --fix",
38
+ "typecheck": "bun run typecheck:app && bun run typecheck:node",
39
+ "typecheck:app": "tsc --noEmit -p tsconfig/app.json",
40
+ "typecheck:node": "tsc --noEmit -p tsconfig/node.json",
41
+ "lint:file": "bun run lint:file:oxlint",
42
+ "lint:file:oxlint": "oxlint",
43
+ "lint:file:eslint": "eslint",
44
+ "fmt": "oxfmt",
45
+ "fmt:check": "oxfmt --check",
46
+ "oxc": "bun run lint:oxlint && bun run fmt:check",
47
+ "oxc:fix": "bun run lint:fix:oxlint && bun run fmt"
48
+ },
49
+ "dependencies": {
50
+ "@oxlint/plugins": "^1.64.0"
51
+ },
52
+ "devDependencies": {
53
+ "@longzai-intelligence/eslint-preset-library": "0.2.13",
54
+ "@longzai-intelligence/oxlint-config": "0.0.1",
55
+ "@longzai-intelligence/oxlint-plugin-architecture": "0.0.1",
56
+ "@longzai-intelligence/oxlint-plugin-code-style": "0.0.1",
57
+ "@longzai-intelligence/oxlint-plugin-package-json": "0.0.1",
58
+ "@longzai-intelligence/oxlint-plugin-perfectionist": "0.0.1",
59
+ "@longzai-intelligence/oxlint-plugin-schema-type-separation": "0.0.1",
60
+ "@longzai-intelligence/oxlint-plugin-stylistic": "0.0.1",
61
+ "@longzai-intelligence/oxlint-plugin-tsdoc": "0.0.1",
62
+ "@longzai-intelligence/oxlint-plugin-typescript-eslint": "0.0.1",
63
+ "@longzai-intelligence/oxlint-plugin-zod": "0.0.1",
64
+ "@longzai-intelligence/oxlint-testing": "0.0.1",
65
+ "@longzai-intelligence/oxlint-utils": "0.0.1",
66
+ "@longzai-intelligence/tsdown-config": "0.0.1",
67
+ "@longzai-intelligence/typescript-config": "0.0.3",
68
+ "@longzai-intelligence/vitest-config": "0.0.10",
69
+ "@types/node": "^25.7.0",
70
+ "oxlint": "^1.64.0",
71
+ "rimraf": "^6.1.3",
72
+ "tsdown": "^0.21.10",
73
+ "typescript": "^6.0.3",
74
+ "vitest": "^4.1.6"
75
+ },
76
+ "peerDependencies": {
77
+ "oxlint": ">=1.0.0"
78
+ }
79
+ }