@longzai-intelligence-oxlint/nestjs-analyzer-plugin 0.1.37 → 0.1.38
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.js +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1 +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=/* @__PURE__ */ 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=/* @__PURE__ */ new Set([`PWD`,`HOME`,`USER`,`PATH`,`SHELL`,`TERM`,`LANG`,`LC_ALL`,`TZ`,`HOSTNAME`,`TMPDIR`]),we=/* @__PURE__ */ 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=/* @__PURE__ */ new Set([`USES_MIGRATION_CONTAINER`,`MIGRATION_CONTAINER`,`IS_MIGRATION_CONTAINER`,`RUN_MIGRATIONS`,`SKIP_MIGRATIONS`]),Ee=/* @__PURE__ */ 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=/* @__PURE__ */ new Set(/* @__PURE__ */ `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=/* @__PURE__ */ new Set([`Get`,`Post`,`Put`,`Delete`,`Patch`,`All`,`Head`,`Options`]),Ie=/* @__PURE__ */ 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&&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=/* @__PURE__ */ 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=/* @__PURE__ */ new Set([`sql`,`sql`,`SQL`,`raw`,`knex`,`knex`,`sequelize`,`sequelize`,`literal`,`typeorm`,`TypeORM`,`drizzle`,`drizzle`,`kysely`,`kysely`,`slonik`,`slonik`,`mikroorm`,`MikroORM`]),dt=/* @__PURE__ */ 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=/* @__PURE__ */ new Set([`sql`,`sql`,`SQL`,`raw`,`knex`,`knex`,`sequelize`,`sequelize`,`literal`,`typeorm`,`TypeORM`,`drizzle`,`drizzle`,`kysely`,`kysely`,`slonik`,`slonik`,`mikroorm`,`MikroORM`]),gt=/* @__PURE__ */ 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`&>.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&&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=/* @__PURE__ */ new Set(/* @__PURE__ */ `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=/* @__PURE__ */ new Set([`fs`]),Wt=/* @__PURE__ */ new Set([`fs`,`node:fs`]),Gt=/* @__PURE__ */ new Set(/* @__PURE__ */ `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=/* @__PURE__ */ new Set,n=/* @__PURE__ */ 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=/* @__PURE__ */ new Set([`crypto`]),en=/* @__PURE__ */ new Set([`crypto`,`node:crypto`]),tn=/* @__PURE__ */ 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=/* @__PURE__ */ new Set,n=/* @__PURE__ */ 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=/* @__PURE__ */ 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=/* @__PURE__ */ new Set([`global`,`globalThis`,`window`,`process.env`]),xn=/* @__PURE__ */ 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=/* @__PURE__ */ new Set,n=/* @__PURE__ */ 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:/* @__PURE__ */ new Set,classMethodNames:/* @__PURE__ */ new Set};return{before(){let n=e.filename;return t={functionNames:/* @__PURE__ */ new Set,classMethodNames:/* @__PURE__ */ 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=/* @__PURE__ */ 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:/* @__PURE__ */ new Set([`200`]),expected:`201 Created`},Delete:{bad:/* @__PURE__ */ 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=/* @__PURE__ */ 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,$=/* @__PURE__ */ 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-oxlint/nestjs-analyzer-plugin`},rules:pr});export{mr as nestjsAnalyzerPlugin,pr as nestjsAnalyzerRules};
|
|
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=/* @__PURE__ */ 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=/* @__PURE__ */ new Set([`PWD`,`HOME`,`USER`,`PATH`,`SHELL`,`TERM`,`LANG`,`LC_ALL`,`TZ`,`HOSTNAME`,`TMPDIR`]),we=/* @__PURE__ */ 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=/* @__PURE__ */ new Set([`USES_MIGRATION_CONTAINER`,`MIGRATION_CONTAINER`,`IS_MIGRATION_CONTAINER`,`RUN_MIGRATIONS`,`SKIP_MIGRATIONS`]),Ee=/* @__PURE__ */ 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=/* @__PURE__ */ new Set(/* @__PURE__ */ `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=/* @__PURE__ */ new Set([`Get`,`Post`,`Put`,`Delete`,`Patch`,`All`,`Head`,`Options`]),Ie=/* @__PURE__ */ 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&&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=/* @__PURE__ */ 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=/* @__PURE__ */ new Set([`sql`,`sql`,`SQL`,`raw`,`knex`,`knex`,`sequelize`,`sequelize`,`literal`,`typeorm`,`TypeORM`,`drizzle`,`drizzle`,`kysely`,`kysely`,`slonik`,`slonik`,`mikroorm`,`MikroORM`]),dt=/* @__PURE__ */ 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=/* @__PURE__ */ new Set([`sql`,`sql`,`SQL`,`raw`,`knex`,`knex`,`sequelize`,`sequelize`,`literal`,`typeorm`,`TypeORM`,`drizzle`,`drizzle`,`kysely`,`kysely`,`slonik`,`slonik`,`mikroorm`,`MikroORM`]),gt=/* @__PURE__ */ 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`&>.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&&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=/* @__PURE__ */ new Set(/* @__PURE__ */ `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=/* @__PURE__ */ new Set([`fs`]),Wt=/* @__PURE__ */ new Set([`fs`,`node:fs`]),Gt=/* @__PURE__ */ new Set(/* @__PURE__ */ `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=/* @__PURE__ */ new Set,n=/* @__PURE__ */ 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=/* @__PURE__ */ new Set([`crypto`]),en=/* @__PURE__ */ new Set([`crypto`,`node:crypto`]),tn=/* @__PURE__ */ 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=/* @__PURE__ */ new Set,n=/* @__PURE__ */ 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=/* @__PURE__ */ 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=/* @__PURE__ */ new Set([`global`,`globalThis`,`window`,`process.env`]),xn=/* @__PURE__ */ 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;else{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=/* @__PURE__ */ new Set,n=/* @__PURE__ */ 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:/* @__PURE__ */ new Set,classMethodNames:/* @__PURE__ */ new Set};return{before(){let n=e.filename;return t={functionNames:/* @__PURE__ */ new Set,classMethodNames:/* @__PURE__ */ 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=/* @__PURE__ */ 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:/* @__PURE__ */ new Set([`200`]),expected:`201 Created`},Delete:{bad:/* @__PURE__ */ 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=/* @__PURE__ */ 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,$=/* @__PURE__ */ 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-oxlint/nestjs-analyzer-plugin`},rules:pr});export{mr as nestjsAnalyzerPlugin,pr as nestjsAnalyzerRules};
|