@longzai-intelligence-oxlint/code-style-plugin 0.2.2 → 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/dist/index.d.ts +11 -1
- package/dist/index.js +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -14,5 +14,6 @@ Longzai Intelligence Oxlint 的代码风格插件,统一命名约定、导出
|
|
|
14
14
|
- 接口 I 前缀命名要求
|
|
15
15
|
- DTO 命名要求
|
|
16
16
|
- 类后缀要求与类后缀别名禁止
|
|
17
|
+
- 禁止 eslint-disable、oxlint-disable、biome-ignore、stylelint-disable、@ts-ignore、@ts-nocheck 等抑制注释
|
|
17
18
|
- 内联类型检测、接口前缀判断、DTO 文件判断、类后缀映射等工具函数与类型守卫导出
|
|
18
19
|
- 同时兼容 Oxlint 与 ESLint 运行时
|
package/dist/index.d.ts
CHANGED
|
@@ -178,6 +178,16 @@ declare function isMethodDefinition(node: unknown): node is MethodDefinitionNode
|
|
|
178
178
|
*/
|
|
179
179
|
declare const noInlineTypeDefinitionRule: import("@oxlint/plugins").Rule;
|
|
180
180
|
//#endregion
|
|
181
|
+
//#region src/rules/no-lint-suppression.rule.d.ts
|
|
182
|
+
/**
|
|
183
|
+
* 禁止 lint 抑制注释的门禁规则
|
|
184
|
+
*
|
|
185
|
+
* @remarks
|
|
186
|
+
* 违例处置只有两条正路,从源头消除违例,或在包级配置登记路径豁免
|
|
187
|
+
* 报告落在指令注释自身,指令压制的是后续行的报告,压制不了对指令本身的报告
|
|
188
|
+
*/
|
|
189
|
+
declare const noLintSuppressionRule: import("@oxlint/plugins").Rule;
|
|
190
|
+
//#endregion
|
|
181
191
|
//#region src/rules/prefer-split-export-const.rule.d.ts
|
|
182
192
|
/**
|
|
183
193
|
* prefer-split-export-const 规则定义
|
|
@@ -253,4 +263,4 @@ declare const requireInterfaceNamingRule: import("@oxlint/plugins").Rule;
|
|
|
253
263
|
*/
|
|
254
264
|
declare const codeStylePlugin: import("@oxlint/plugins").Plugin;
|
|
255
265
|
//#endregion
|
|
256
|
-
export { SUFFIX_MAPPINGS, type SuffixMapping, codeStylePlugin, codeStylePlugin as default, getInlineTypeDescription, getParamName, hasIPrefix, hasInlineType, isDtoFile, isFunctionNode, isMethodDefinition, isTSFunctionType, isTSIntersectionType, isTSTypeLiteral, isTSUnionType, noClassSuffixAliasRule, noConsoleRule, noHandwrittenShebangRule, noInlineTypeDefinitionRule, preferSplitExportConstRule, requireClassSuffixRule, requireDtoNamingRule, requireInterfaceNamingRule };
|
|
266
|
+
export { SUFFIX_MAPPINGS, type SuffixMapping, codeStylePlugin, codeStylePlugin as default, getInlineTypeDescription, getParamName, hasIPrefix, hasInlineType, isDtoFile, isFunctionNode, isMethodDefinition, isTSFunctionType, isTSIntersectionType, isTSTypeLiteral, isTSUnionType, noClassSuffixAliasRule, noConsoleRule, noHandwrittenShebangRule, noInlineTypeDefinitionRule, noLintSuppressionRule, preferSplitExportConstRule, requireClassSuffixRule, requireDtoNamingRule, requireInterfaceNamingRule };
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import"./rolldown-runtime-BqT_7tdF.js";import{defineRule as e,eslintCompatPlugin as t}from"@oxlint/plugins";import{existsSync as n,readFileSync as r}from"node:fs";import{dirname as i,join as a}from"node:path";import*as o from"zod/mini";import{hasIPrefix as s,isValidDtoNameCamelCase as ee,isValidDtoNameUpperCase as te}from"@longzai-intelligence-oxlint/utils";function c(e){return e.endsWith(`Class`)}const l=e({meta:{type:`problem`,docs:{description:`禁止在 import 别名中使用 "Class" 后缀`}},createOnce(e){return{ImportSpecifier(t){let n=t.local.name,r=t.imported.type===`Identifier`?t.imported.name:n;n!==r&&c(n)&&e.report({node:t,message:`导入别名 "${n}" 使用了 "Class" 后缀,这是不允许的命名方式。请使用更具描述性的名称,或直接使用原始名称 "${r}"`})}}}}),u=/* @__PURE__ */ new Set([`console.log`,`console.warn`,`console.error`,`console.info`,`console.debug`,`console.trace`,`console.dir`,`console.table`]);function d(e){let t=e.callee;return t.type===`Identifier`?t.name:t.type===`MemberExpression`&&t.property.type===`Identifier`&&t.object.type===`Identifier`?`${t.object.name}.${t.property.name}`:null}const f=e({meta:{type:`problem`,docs:{description:`禁止使用 console 方法,必须使用 @longzai-intelligence/logger-preset-* 或 react-devtools(ADR 0038)`}},createOnce(e){return{CallExpression(t){if(t.type!==`CallExpression`)return;let n=d(t);n&&u.has(n)&&e.report({node:t,message:`检测到 '${n}' — 必须使用 @longzai-intelligence/logger-preset-* 代替(ADR 0038)`})}}}}),p=/\/src\/(index|main|cli)\.(ts|mts|cts)$/,m=/(^|\/)dist\//;function ne(e){return e.replace(/\\/g,`/`)}function h(e){return typeof e==`object`&&!!e}function re(e){return e.startsWith(`#!`)}function g(e){return p.test(ne(e))}function ie(e){let t=e;for(let e=0;e<4;e+=1){let e=a(t,`package.json`);if(n(e))try{let t=JSON.parse(r(e,`utf8`));return h(t)?t:null}catch{return null}let o=i(t);if(o===t)break;t=o}return null}function ae(e){let t=e.bin;return(typeof t==`string`?[t]:h(t)?Object.values(t).filter(e=>typeof e==`string`):[]).some(e=>m.test(e.replace(/^\.\//,``)))}const _={noHandwrittenShebang:`入口源码手写 shebang 属被裁定的错误方式,bin 产物首行 shebang 应由 "lzi-builder.config.ts" 的 banner 在构建期注入。请删除源码首行的 "#!" 行并补 banner 配置。`},v=e({meta:{type:`problem`,docs:{description:`禁止所属包 bin 指向 dist 产物的入口源码手写 shebang`}},createOnce(e){return{before(){let t=e.filename;if(!g(t))return!1},Program(t){let n=e.sourceCode.text;if(!re(n))return;let r=ie(i(i(e.filename)));r!==null&&ae(r)&&e.report({node:t,message:_.noHandwrittenShebang})}}}}),y=o.object({type:o.literal(`TSTypeLiteral`)}),b=o.object({type:o.literal(`TSFunctionType`)}),x=o.object({type:o.literal(`TSUnionType`),types:o.array(o.unknown())}),oe=o.object({type:o.literal(`TSIntersectionType`),types:o.array(o.unknown())}),se=o.object({type:o.literal(`Identifier`),name:o.string()}),ce=o.object({type:o.literal(`RestElement`),argument:o.object({name:o.string()})}),le=o.union([se,ce]),S=o.object({typeAnnotation:o.unknown()}),ue=o.object({type:o.literal(`MethodDefinition`),key:o.object({type:o.string(),name:o.optional(o.string())}),value:o.object({params:o.optional(o.array(o.unknown())),returnType:o.optional(o.union([S,o.null()]))}),kind:o.enum([`method`,`constructor`,`get`,`set`])}),de=o.object({returnType:S}),fe=o.object({id:o.optional(o.object({name:o.optional(o.string())})),params:o.optional(o.array(o.unknown())),returnType:o.optional(o.unknown())}),pe=o.object({params:o.optional(o.array(o.unknown()))});function C(e){return y.safeParse(e).success}function w(e){return b.safeParse(e).success}function T(e){return x.safeParse(e).success}function E(e){return oe.safeParse(e).success}function D(e){return le.safeParse(e).success}function O(e){if(typeof e!=`object`||!e||!(`type`in e))return!1;let t=[`FunctionDeclaration`,`FunctionExpression`,`ArrowFunctionExpression`,`MethodDefinition`],n=e.type;return typeof n==`string`&&t.includes(n)}function k(e){return ue.safeParse(e).success}function A(e){return de.safeParse(e).success}function j(e){return fe.safeParse(e).success}function M(e){return pe.safeParse(e).success}function N(e){return C(e)||w(e)?!0:T(e)||E(e)?e.types.some(N):!1}function P(e){return C(e)?`对象类型`:w(e)?`函数类型`:`内联类型`}function F(e){return D(e)?e.type===`Identifier`?e.name:e.type===`RestElement`?e.argument.name:null:null}function I(e,t,n,r,i,a){i?e.report({node:t,message:`参数 "${n}" 使用了内联${r}定义。请将其提取为具名的 type。`}):a?e.report({node:t,message:`方法 "${a}" 的参数 "${n}" 使用了内联${r}定义。请将其提取为具名的 type。`}):e.report({node:t,message:`参数 "${n}" 使用了内联${r}定义。请将其提取为具名的 type。`})}function L(e,t,n,r,i){i?e.report({node:t,message:`方法 "${i}" 的返回值使用了内联${n}定义。请将其提取为具名的 type。`}):r?e.report({node:t,message:`函数 "${r}" 的返回值使用了内联${n}定义。请将其提取为具名的 type。`}):e.report({node:t,message:`匿名函数的返回值使用了内联${n}定义。请将其提取为具名的 type。`})}function R(e,t,n,r,i){if(typeof e!=`object`||!e||!(`typeAnnotation`in e)||e.typeAnnotation===null||typeof e.typeAnnotation!=`object`||!(`typeAnnotation`in e.typeAnnotation))return;let a=e.typeAnnotation.typeAnnotation;N(a)&&I(t,n,F(e)??`未知参数`,P(a),r,i)}function z(e,t,n,r,i){for(let a of e)R(a,t,n,i??!1,r)}function B(e,t,n,r,i){if(typeof e!=`object`||!e||!(`typeAnnotation`in e))return;let a=e.typeAnnotation;N(a)&&L(t,n,P(a),r,i)}function V(e,t){if(!O(e)||!j(e))return;let n=null;`id`in e&&e.id!==null&&typeof e.id==`object`&&`name`in e.id&&typeof e.id.name==`string`&&(n=e.id.name),M(e)&&z(e.params??[],t,e),A(e)&&B(e.returnType,t,e,n??void 0)}function me(e,t){O(e)&&M(e)&&(M(e)&&z(e.params??[],t,e),A(e)&&B(e.returnType,t,e))}function he(e,t){if(!k(e))return;let n=e.key,r=n&&`name`in n&&typeof n.name==`string`?n.name:`未知方法`,i=e.value;if(i&&`params`in i&&Array.isArray(i.params)&&z(i.params,t,e,r,e.kind===`constructor`),i&&`returnType`in i&&i.returnType&&typeof i.returnType==`object`&&`typeAnnotation`in i.returnType){let n=i.returnType;B(n,t,e,void 0,r)}}const H=e({meta:{type:`suggestion`,docs:{description:`禁止在函数参数和返回值中使用内联类型定义,强制使用具名类型`}},createOnce(e){return{FunctionDeclaration(t){V(t,e)},FunctionExpression(t){V(t,e)},ArrowFunctionExpression(t){me(t,e)},MethodDefinition(t){he(t,e)}}}});function ge(e){let t=[];if(e.type===`Identifier`&&t.push(e.name),e.type===`ObjectPattern`)for(let n of e.properties)n.type===`Property`&&`value`in n?n.value.type===`Identifier`&&t.push(n.value.name):n.type===`RestElement`&&n.argument.type===`Identifier`&&t.push(n.argument.name);if(e.type===`ArrayPattern`)for(let n of e.elements)n&&n.type===`Identifier`&&t.push(n.name);return t}const U=e({meta:{type:`suggestion`,docs:{description:`将 export const/let/var 拆分为独立的声明和导出语句`}},createOnce(e){return{ExportNamedDeclaration(t){if(!t.declaration||t.declaration.type!==`VariableDeclaration`)return;let n=t.declaration;if(n.kind!==`const`&&n.kind!==`let`&&n.kind!==`var`||n.declarations.length===0)return;let r=[];for(let e of n.declarations)r.push(...ge(e.id));r.length!==0&&e.report({node:t,message:`应将 \`export ${n.kind} ${r.join(`, `)}\` 拆分为独立的声明和导出语句`})}}}}),W=[{suffix:`.service.drizzle.ts`,classSuffix:`Service`,isInterface:!1,isPrefix:!1},{suffix:`.service.memory.ts`,classSuffix:`Service`,isInterface:!1,isPrefix:!1},{suffix:`.service.ts`,classSuffix:`Service`,isInterface:!1,isPrefix:!1},{suffix:`.controller.ts`,classSuffix:`Controller`,isInterface:!1,isPrefix:!1},{suffix:`.module.ts`,classSuffix:`Module`,isInterface:!1,isPrefix:!1},{suffix:`.repository.drizzle.ts`,classSuffix:`Repository`,isInterface:!1,isPrefix:!1},{suffix:`.repository.memory.ts`,classSuffix:`Repository`,isInterface:!1,isPrefix:!1},{suffix:`.repository.interface.ts`,classSuffix:`Repository`,isInterface:!0,isPrefix:!0},{suffix:`.repository.ts`,classSuffix:`Repository`,isInterface:!1,isPrefix:!1},{suffix:`.entity.ts`,classSuffix:`Entity`,isInterface:!1,isPrefix:!1},{suffix:`.orm-entity.ts`,classSuffix:`OrmEntity`,isInterface:!1,isPrefix:!1},{suffix:`.dto.ts`,classSuffix:`DTO`,isInterface:!1,isPrefix:!1},{suffix:`.port.interface.ts`,classSuffix:`Port`,isInterface:!0,isPrefix:!0},{suffix:`.interface.ts`,classSuffix:``,isInterface:!0,isPrefix:!0},{suffix:`.port.ts`,classSuffix:`Port`,isInterface:!0,isPrefix:!1},{suffix:`.port.adapter.ts`,classSuffix:`PortAdapter`,isInterface:!1,isPrefix:!1},{suffix:`.adapter.ts`,classSuffix:`Adapter`,isInterface:!1,isPrefix:!1},{suffix:`.events.ts`,classSuffix:`Event`,isInterface:!1,isPrefix:!1},{suffix:`.mapper.ts`,classSuffix:`Mapper`,isInterface:!1,isPrefix:!1},{suffix:`.vo.ts`,classSuffix:`VO`,isInterface:!1,isPrefix:!1},{suffix:`.usecase.ts`,classSuffix:`UseCase`,isInterface:!1,isPrefix:!1},{suffix:`.errors.ts`,classSuffix:`Error`,isInterface:!1,isPrefix:!1}];function G(e){for(let t of W)if(e.endsWith(t.suffix))return t;return null}function K(e,t){if(t.isPrefix&&t.isInterface){if(t.classSuffix){let n=t.classSuffix;return e.startsWith(`I`)&&e.endsWith(n)&&e.length>1+n.length}return e.startsWith(`I`)&&e.length>1}return t.isPrefix?e.startsWith(`I`)&&e.length>1:e.endsWith(t.classSuffix)&&e.length>t.classSuffix.length}function q(e){return e.isPrefix&&e.isInterface?e.classSuffix?`I${e.classSuffix}(如 IUser${e.classSuffix})`:`I 前缀(如 IUser)`:e.isPrefix?`I 前缀(如 IUser)`:`${e.classSuffix} 后缀`}const J=e({meta:{type:`problem`,docs:{description:`强制执行类名与文件类型后缀的一致性`}},createOnce(e){return{before(){let t=e.filename.replace(/\\/g,`/`).split(`/`).pop()??``;if(t.endsWith(`.test.ts`)||t.endsWith(`.spec.ts`)||!G(t))return!1},ClassDeclaration(t){let n=e.filename.replace(/\\/g,`/`).split(`/`).pop()??``,r=G(n);if(!r||r.isInterface||!t.id)return;let i=t.id.name;K(i,r)||e.report({node:t,message:`类名 "${i}" 不符合命名规范。文件 "${n}" 中的类应使用 ${q(r)}`})},TSInterfaceDeclaration(t){let n=e.filename.replace(/\\/g,`/`).split(`/`).pop()??``,r=G(n);if(!r||!r.isInterface)return;let i=t.id.name;K(i,r)||e.report({node:t,message:`接口名 "${i}" 不符合命名规范。文件 "${n}" 中的接口应使用 ${q(r)}`})}}}});function Y(e){return e.endsWith(`.dto.ts`)}function X(e){return e.endsWith(`DTO`)}const Z=e({meta:{type:`problem`,docs:{description:`强制执行 DTO 文件中的接口命名规范(Dto 后缀,驼峰式)`}},createOnce(e){return{before(){let t=e.filename.replace(/\\/g,`/`).split(`/`).pop()??``;if(t.endsWith(`.test.ts`)||t.endsWith(`.spec.ts`)||!Y(t))return!1},TSInterfaceDeclaration(t){let n=t.id.name;X(n)?e.report({node:t,message:`接口名 "${n}" 后缀应使用 Dto(驼峰式)而非 DTO(全大写)`}):ee(n)||e.report({node:t,message:`DTO 文件中的接口名 "${n}" 应以 Dto 结尾(如 UserResponseDto)`})}}}}),_e=[`types.ts`,`types.d.ts`],ve=[`.port.interface.ts`,`.repository.interface.ts`,`.types.ts`,`.types.d.ts`,`.events.ts`,`.d.ts`];function ye(e,t){return s(e)?!1:!t.endsWith(`.dto.ts`)||te(e)}function be(e){return e.endsWith(`.dto.ts`)?`DTO 后缀(如 UserDTO),禁止 I 前缀`:`PascalCase(如 User、UserRepository),禁止 I 前缀`}function xe(e){if(_e.includes(e))return!0;for(let t of ve)if(e.endsWith(t))return!0;return!1}function Se(e){return e.endsWith(`.port.ts`)?e.replace(`.port.ts`,`.port.interface.ts`):e.endsWith(`.repository.ts`)?e.replace(`.repository.ts`,`.repository.interface.ts`):e.replace(`.ts`,`.interface.ts`)}const Q=e({meta:{type:`problem`,docs:{description:`强制执行接口命名规范(禁止 I 前缀,使用 PascalCase)和文件命名规范`}},createOnce(e){return{before(){let t=e.filename.replace(/\\/g,`/`).split(`/`).pop()??``;if(t.endsWith(`.test.ts`)||t.endsWith(`.spec.ts`)||xe(t))return!1},TSInterfaceDeclaration(t){let n=e.filename.replace(/\\/g,`/`).split(`/`).pop()??``,r=n.endsWith(`.interface.ts`)||n.endsWith(`.interface.tsx`),i=t.id.name;ye(i,n)||(s(i)?e.report({node:t,message:`接口名 "${i}" 使用了 I 前缀。现代 TypeScript 不推荐使用匈牙利命名法,建议使用 "${i.substring(1)}"`}):n.endsWith(`.dto.ts`)&&i.endsWith(`Dto`)?e.report({node:t,message:`接口名 "${i}" 后缀应使用 DTO(全大写)而非 Dto(驼峰式)`}):e.report({node:t,message:`接口名 "${i}" 不符合命名规范。接口名应使用 ${be(n)}`})),!r&&!n.endsWith(`.dto.ts`)&&!n.endsWith(`.tsx`)&&e.report({node:t,message:`包含接口 "${i}" 的文件 "${n}" 应命名为 "${Se(n)}"`})}}}}),$=t({meta:{name:`@longzai-intelligence-oxlint/code-style-plugin`},rules:{"no-console":f,"no-inline-type-definition":H,"prefer-split-export-const":U,"require-interface-naming":Q,"require-dto-naming":Z,"require-class-suffix":J,"no-class-suffix-alias":l,"no-handwritten-shebang":v}});export{W as SUFFIX_MAPPINGS,$ as codeStylePlugin,$ as default,P as getInlineTypeDescription,F as getParamName,s as hasIPrefix,N as hasInlineType,Y as isDtoFile,O as isFunctionNode,k as isMethodDefinition,w as isTSFunctionType,E as isTSIntersectionType,C as isTSTypeLiteral,T as isTSUnionType,l as noClassSuffixAliasRule,f as noConsoleRule,v as noHandwrittenShebangRule,H as noInlineTypeDefinitionRule,U as preferSplitExportConstRule,J as requireClassSuffixRule,Z as requireDtoNamingRule,Q as requireInterfaceNamingRule};
|
|
1
|
+
import"./rolldown-runtime-BqT_7tdF.js";import{defineRule as e,eslintCompatPlugin as t}from"@oxlint/plugins";import{existsSync as n,readFileSync as r}from"node:fs";import{dirname as i,join as a}from"node:path";import*as o from"zod/mini";import{hasIPrefix as s,isValidDtoNameCamelCase as ee,isValidDtoNameUpperCase as te}from"@longzai-intelligence-oxlint/utils";function c(e){return e.endsWith(`Class`)}const l=e({meta:{type:`problem`,docs:{description:`禁止在 import 别名中使用 "Class" 后缀`}},createOnce(e){return{ImportSpecifier(t){let n=t.local.name,r=t.imported.type===`Identifier`?t.imported.name:n;n!==r&&c(n)&&e.report({node:t,message:`导入别名 "${n}" 使用了 "Class" 后缀,这是不允许的命名方式。请使用更具描述性的名称,或直接使用原始名称 "${r}"`})}}}}),u=/* @__PURE__ */ new Set([`console.log`,`console.warn`,`console.error`,`console.info`,`console.debug`,`console.trace`,`console.dir`,`console.table`]);function d(e){let t=e.callee;return t.type===`Identifier`?t.name:t.type===`MemberExpression`&&t.property.type===`Identifier`&&t.object.type===`Identifier`?`${t.object.name}.${t.property.name}`:null}const f=e({meta:{type:`problem`,docs:{description:`禁止使用 console 方法,必须使用 @longzai-intelligence/logger-preset-* 或 react-devtools(ADR 0038)`}},createOnce(e){return{CallExpression(t){if(t.type!==`CallExpression`)return;let n=d(t);n&&u.has(n)&&e.report({node:t,message:`检测到 '${n}' — 必须使用 @longzai-intelligence/logger-preset-* 代替(ADR 0038)`})}}}}),p=/\/src\/(index|main|cli)\.(ts|mts|cts)$/,m=/(^|\/)dist\//;function ne(e){return e.replace(/\\/g,`/`)}function h(e){return typeof e==`object`&&!!e}function re(e){return e.startsWith(`#!`)}function g(e){return p.test(ne(e))}function ie(e){let t=e;for(let e=0;e<4;e+=1){let e=a(t,`package.json`);if(n(e))try{let t=JSON.parse(r(e,`utf8`));return h(t)?t:null}catch{return null}let o=i(t);if(o===t)break;t=o}return null}function ae(e){let t=e.bin;return(typeof t==`string`?[t]:h(t)?Object.values(t).filter(e=>typeof e==`string`):[]).some(e=>m.test(e.replace(/^\.\//,``)))}const oe={noHandwrittenShebang:`入口源码手写 shebang 属被裁定的错误方式,bin 产物首行 shebang 应由 "lzi-builder.config.ts" 的 banner 在构建期注入。请删除源码首行的 "#!" 行并补 banner 配置。`},_=e({meta:{type:`problem`,docs:{description:`禁止所属包 bin 指向 dist 产物的入口源码手写 shebang`}},createOnce(e){return{before(){let t=e.filename;if(!g(t))return!1},Program(t){let n=e.sourceCode.text;if(!re(n))return;let r=ie(i(i(e.filename)));r!==null&&ae(r)&&e.report({node:t,message:oe.noHandwrittenShebang})}}}}),v=o.object({type:o.literal(`TSTypeLiteral`)}),y=o.object({type:o.literal(`TSFunctionType`)}),b=o.object({type:o.literal(`TSUnionType`),types:o.array(o.unknown())}),se=o.object({type:o.literal(`TSIntersectionType`),types:o.array(o.unknown())}),ce=o.object({type:o.literal(`Identifier`),name:o.string()}),le=o.object({type:o.literal(`RestElement`),argument:o.object({name:o.string()})}),ue=o.union([ce,le]),x=o.object({typeAnnotation:o.unknown()}),de=o.object({type:o.literal(`MethodDefinition`),key:o.object({type:o.string(),name:o.optional(o.string())}),value:o.object({params:o.optional(o.array(o.unknown())),returnType:o.optional(o.union([x,o.null()]))}),kind:o.enum([`method`,`constructor`,`get`,`set`])}),fe=o.object({returnType:x}),pe=o.object({id:o.optional(o.object({name:o.optional(o.string())})),params:o.optional(o.array(o.unknown())),returnType:o.optional(o.unknown())}),me=o.object({params:o.optional(o.array(o.unknown()))});function S(e){return v.safeParse(e).success}function C(e){return y.safeParse(e).success}function w(e){return b.safeParse(e).success}function T(e){return se.safeParse(e).success}function E(e){return ue.safeParse(e).success}function D(e){if(typeof e!=`object`||!e||!(`type`in e))return!1;let t=[`FunctionDeclaration`,`FunctionExpression`,`ArrowFunctionExpression`,`MethodDefinition`],n=e.type;return typeof n==`string`&&t.includes(n)}function O(e){return de.safeParse(e).success}function k(e){return fe.safeParse(e).success}function A(e){return pe.safeParse(e).success}function j(e){return me.safeParse(e).success}function M(e){return S(e)||C(e)?!0:w(e)||T(e)?e.types.some(M):!1}function N(e){return S(e)?`对象类型`:C(e)?`函数类型`:`内联类型`}function P(e){return E(e)?e.type===`Identifier`?e.name:e.type===`RestElement`?e.argument.name:null:null}function F(e,t,n,r,i,a){i?e.report({node:t,message:`参数 "${n}" 使用了内联${r}定义。请将其提取为具名的 type。`}):a?e.report({node:t,message:`方法 "${a}" 的参数 "${n}" 使用了内联${r}定义。请将其提取为具名的 type。`}):e.report({node:t,message:`参数 "${n}" 使用了内联${r}定义。请将其提取为具名的 type。`})}function I(e,t,n,r,i){i?e.report({node:t,message:`方法 "${i}" 的返回值使用了内联${n}定义。请将其提取为具名的 type。`}):r?e.report({node:t,message:`函数 "${r}" 的返回值使用了内联${n}定义。请将其提取为具名的 type。`}):e.report({node:t,message:`匿名函数的返回值使用了内联${n}定义。请将其提取为具名的 type。`})}function L(e,t,n,r,i){if(typeof e!=`object`||!e||!(`typeAnnotation`in e)||e.typeAnnotation===null||typeof e.typeAnnotation!=`object`||!(`typeAnnotation`in e.typeAnnotation))return;let a=e.typeAnnotation.typeAnnotation;M(a)&&F(t,n,P(e)??`未知参数`,N(a),r,i)}function R(e,t,n,r,i){for(let a of e)L(a,t,n,i??!1,r)}function z(e,t,n,r,i){if(typeof e!=`object`||!e||!(`typeAnnotation`in e))return;let a=e.typeAnnotation;M(a)&&I(t,n,N(a),r,i)}function B(e,t){if(!D(e)||!A(e))return;let n=null;`id`in e&&e.id!==null&&typeof e.id==`object`&&`name`in e.id&&typeof e.id.name==`string`&&(n=e.id.name),j(e)&&R(e.params??[],t,e),k(e)&&z(e.returnType,t,e,n??void 0)}function he(e,t){D(e)&&j(e)&&(j(e)&&R(e.params??[],t,e),k(e)&&z(e.returnType,t,e))}function ge(e,t){if(!O(e))return;let n=e.key,r=n&&`name`in n&&typeof n.name==`string`?n.name:`未知方法`,i=e.value;if(i&&`params`in i&&Array.isArray(i.params)&&R(i.params,t,e,r,e.kind===`constructor`),i&&`returnType`in i&&i.returnType&&typeof i.returnType==`object`&&`typeAnnotation`in i.returnType){let n=i.returnType;z(n,t,e,void 0,r)}}const V=e({meta:{type:`suggestion`,docs:{description:`禁止在函数参数和返回值中使用内联类型定义,强制使用具名类型`}},createOnce(e){return{FunctionDeclaration(t){B(t,e)},FunctionExpression(t){B(t,e)},ArrowFunctionExpression(t){he(t,e)},MethodDefinition(t){ge(t,e)}}}}),H=/^(?:eslint-disable(?:-next-line|-line)?|eslint-enable|oxlint-disable(?:-next-line|-line)?|oxlint-enable|biome-ignore|stylelint-disable(?:-next-line|-line)?|stylelint-enable|@ts-ignore|@ts-nocheck)(?=[\s,]|$)/;function _e(e){let t=e.trimStart();return H.exec(t)?.[0]??null}const U=e({meta:{type:`problem`,docs:{description:`禁止 eslint-disable、oxlint-disable、biome-ignore、stylelint-disable、@ts-ignore、@ts-nocheck 等抑制注释`}},createOnce(e){return{Program(){let t=e.sourceCode.getAllComments();for(let n of t){if(n.type===`Shebang`)continue;let t=_e(n.value);t!==null&&e.report({node:n,message:`检测到抑制指令 ${t},抑制注释不是合法豁免手段,请从源头消除违例或在包级 oxlint.config.ts 的 ignorePatterns 登记路径豁免`})}}}}});function ve(e){let t=[];if(e.type===`Identifier`&&t.push(e.name),e.type===`ObjectPattern`)for(let n of e.properties)n.type===`Property`&&`value`in n?n.value.type===`Identifier`&&t.push(n.value.name):n.type===`RestElement`&&n.argument.type===`Identifier`&&t.push(n.argument.name);if(e.type===`ArrayPattern`)for(let n of e.elements)n&&n.type===`Identifier`&&t.push(n.name);return t}const W=e({meta:{type:`suggestion`,docs:{description:`将 export const/let/var 拆分为独立的声明和导出语句`}},createOnce(e){return{ExportNamedDeclaration(t){if(!t.declaration||t.declaration.type!==`VariableDeclaration`)return;let n=t.declaration;if(n.kind!==`const`&&n.kind!==`let`&&n.kind!==`var`||n.declarations.length===0)return;let r=[];for(let e of n.declarations)r.push(...ve(e.id));r.length!==0&&e.report({node:t,message:`应将 \`export ${n.kind} ${r.join(`, `)}\` 拆分为独立的声明和导出语句`})}}}}),G=[{suffix:`.service.drizzle.ts`,classSuffix:`Service`,isInterface:!1,isPrefix:!1},{suffix:`.service.memory.ts`,classSuffix:`Service`,isInterface:!1,isPrefix:!1},{suffix:`.service.ts`,classSuffix:`Service`,isInterface:!1,isPrefix:!1},{suffix:`.controller.ts`,classSuffix:`Controller`,isInterface:!1,isPrefix:!1},{suffix:`.module.ts`,classSuffix:`Module`,isInterface:!1,isPrefix:!1},{suffix:`.repository.drizzle.ts`,classSuffix:`Repository`,isInterface:!1,isPrefix:!1},{suffix:`.repository.memory.ts`,classSuffix:`Repository`,isInterface:!1,isPrefix:!1},{suffix:`.repository.interface.ts`,classSuffix:`Repository`,isInterface:!0,isPrefix:!0},{suffix:`.repository.ts`,classSuffix:`Repository`,isInterface:!1,isPrefix:!1},{suffix:`.entity.ts`,classSuffix:`Entity`,isInterface:!1,isPrefix:!1},{suffix:`.orm-entity.ts`,classSuffix:`OrmEntity`,isInterface:!1,isPrefix:!1},{suffix:`.dto.ts`,classSuffix:`DTO`,isInterface:!1,isPrefix:!1},{suffix:`.port.interface.ts`,classSuffix:`Port`,isInterface:!0,isPrefix:!0},{suffix:`.interface.ts`,classSuffix:``,isInterface:!0,isPrefix:!0},{suffix:`.port.ts`,classSuffix:`Port`,isInterface:!0,isPrefix:!1},{suffix:`.port.adapter.ts`,classSuffix:`PortAdapter`,isInterface:!1,isPrefix:!1},{suffix:`.adapter.ts`,classSuffix:`Adapter`,isInterface:!1,isPrefix:!1},{suffix:`.events.ts`,classSuffix:`Event`,isInterface:!1,isPrefix:!1},{suffix:`.mapper.ts`,classSuffix:`Mapper`,isInterface:!1,isPrefix:!1},{suffix:`.vo.ts`,classSuffix:`VO`,isInterface:!1,isPrefix:!1},{suffix:`.usecase.ts`,classSuffix:`UseCase`,isInterface:!1,isPrefix:!1},{suffix:`.errors.ts`,classSuffix:`Error`,isInterface:!1,isPrefix:!1}];function K(e){for(let t of G)if(e.endsWith(t.suffix))return t;return null}function q(e,t){if(t.isPrefix&&t.isInterface){if(t.classSuffix){let n=t.classSuffix;return e.startsWith(`I`)&&e.endsWith(n)&&e.length>1+n.length}return e.startsWith(`I`)&&e.length>1}return t.isPrefix?e.startsWith(`I`)&&e.length>1:e.endsWith(t.classSuffix)&&e.length>t.classSuffix.length}function J(e){return e.isPrefix&&e.isInterface?e.classSuffix?`I${e.classSuffix}(如 IUser${e.classSuffix})`:`I 前缀(如 IUser)`:e.isPrefix?`I 前缀(如 IUser)`:`${e.classSuffix} 后缀`}const Y=e({meta:{type:`problem`,docs:{description:`强制执行类名与文件类型后缀的一致性`}},createOnce(e){return{before(){let t=e.filename.replace(/\\/g,`/`).split(`/`).pop()??``;if(t.endsWith(`.test.ts`)||t.endsWith(`.spec.ts`)||!K(t))return!1},ClassDeclaration(t){let n=e.filename.replace(/\\/g,`/`).split(`/`).pop()??``,r=K(n);if(!r||r.isInterface||!t.id)return;let i=t.id.name;q(i,r)||e.report({node:t,message:`类名 "${i}" 不符合命名规范。文件 "${n}" 中的类应使用 ${J(r)}`})},TSInterfaceDeclaration(t){let n=e.filename.replace(/\\/g,`/`).split(`/`).pop()??``,r=K(n);if(!r||!r.isInterface)return;let i=t.id.name;q(i,r)||e.report({node:t,message:`接口名 "${i}" 不符合命名规范。文件 "${n}" 中的接口应使用 ${J(r)}`})}}}});function X(e){return e.endsWith(`.dto.ts`)}function ye(e){return e.endsWith(`DTO`)}const Z=e({meta:{type:`problem`,docs:{description:`强制执行 DTO 文件中的接口命名规范(Dto 后缀,驼峰式)`}},createOnce(e){return{before(){let t=e.filename.replace(/\\/g,`/`).split(`/`).pop()??``;if(t.endsWith(`.test.ts`)||t.endsWith(`.spec.ts`)||!X(t))return!1},TSInterfaceDeclaration(t){let n=t.id.name;ye(n)?e.report({node:t,message:`接口名 "${n}" 后缀应使用 Dto(驼峰式)而非 DTO(全大写)`}):ee(n)||e.report({node:t,message:`DTO 文件中的接口名 "${n}" 应以 Dto 结尾(如 UserResponseDto)`})}}}}),be=[`types.ts`,`types.d.ts`],xe=[`.port.interface.ts`,`.repository.interface.ts`,`.types.ts`,`.types.d.ts`,`.events.ts`,`.d.ts`];function Se(e,t){return s(e)?!1:!t.endsWith(`.dto.ts`)||te(e)}function Ce(e){return e.endsWith(`.dto.ts`)?`DTO 后缀(如 UserDTO),禁止 I 前缀`:`PascalCase(如 User、UserRepository),禁止 I 前缀`}function we(e){if(be.includes(e))return!0;for(let t of xe)if(e.endsWith(t))return!0;return!1}function Te(e){return e.endsWith(`.port.ts`)?e.replace(`.port.ts`,`.port.interface.ts`):e.endsWith(`.repository.ts`)?e.replace(`.repository.ts`,`.repository.interface.ts`):e.replace(`.ts`,`.interface.ts`)}const Q=e({meta:{type:`problem`,docs:{description:`强制执行接口命名规范(禁止 I 前缀,使用 PascalCase)和文件命名规范`}},createOnce(e){return{before(){let t=e.filename.replace(/\\/g,`/`).split(`/`).pop()??``;if(t.endsWith(`.test.ts`)||t.endsWith(`.spec.ts`)||we(t))return!1},TSInterfaceDeclaration(t){let n=e.filename.replace(/\\/g,`/`).split(`/`).pop()??``,r=n.endsWith(`.interface.ts`)||n.endsWith(`.interface.tsx`),i=t.id.name;Se(i,n)||(s(i)?e.report({node:t,message:`接口名 "${i}" 使用了 I 前缀。现代 TypeScript 不推荐使用匈牙利命名法,建议使用 "${i.substring(1)}"`}):n.endsWith(`.dto.ts`)&&i.endsWith(`Dto`)?e.report({node:t,message:`接口名 "${i}" 后缀应使用 DTO(全大写)而非 Dto(驼峰式)`}):e.report({node:t,message:`接口名 "${i}" 不符合命名规范。接口名应使用 ${Ce(n)}`})),!r&&!n.endsWith(`.dto.ts`)&&!n.endsWith(`.tsx`)&&e.report({node:t,message:`包含接口 "${i}" 的文件 "${n}" 应命名为 "${Te(n)}"`})}}}}),$=t({meta:{name:`@longzai-intelligence-oxlint/code-style-plugin`},rules:{"no-console":f,"no-inline-type-definition":V,"prefer-split-export-const":W,"require-interface-naming":Q,"require-dto-naming":Z,"require-class-suffix":Y,"no-class-suffix-alias":l,"no-handwritten-shebang":_,"no-lint-suppression":U}});export{G as SUFFIX_MAPPINGS,$ as codeStylePlugin,$ as default,N as getInlineTypeDescription,P as getParamName,s as hasIPrefix,M as hasInlineType,X as isDtoFile,D as isFunctionNode,O as isMethodDefinition,C as isTSFunctionType,T as isTSIntersectionType,S as isTSTypeLiteral,w as isTSUnionType,l as noClassSuffixAliasRule,f as noConsoleRule,_ as noHandwrittenShebangRule,V as noInlineTypeDefinitionRule,U as noLintSuppressionRule,W as preferSplitExportConstRule,Y as requireClassSuffixRule,Z as requireDtoNamingRule,Q as requireInterfaceNamingRule};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@longzai-intelligence-oxlint/code-style-plugin",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"description": "Longzai Intelligence oxlint 代码风格规则插件",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"code-style",
|
|
@@ -43,8 +43,8 @@
|
|
|
43
43
|
"typecheck:test": "lzi-tsgo typecheck tsconfig/test.json"
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"@longzai-intelligence-oxlint/core": "0.2.
|
|
47
|
-
"@longzai-intelligence-oxlint/utils": "0.2.
|
|
46
|
+
"@longzai-intelligence-oxlint/core": "0.2.3",
|
|
47
|
+
"@longzai-intelligence-oxlint/utils": "0.2.3",
|
|
48
48
|
"@oxlint/plugins": "^1.74.0",
|
|
49
49
|
"zod": "^4.4.3"
|
|
50
50
|
},
|