@longzai-intelligence-oxlint/file-naming-plugin 0.1.39 → 0.1.41
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 +3 -3
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{defineRule as e,eslintCompatPlugin as t}from"@oxlint/plugins";import{existsSync as n,readdirSync as r}from"node:fs";import i from"node:path";import{hasIPrefix as a}from"@longzai-intelligence-oxlint/utils";const o=[`index.ts`,`index.tsx`,`main.ts`,`main.tsx`,`App.tsx`],s=[`types.ts`,`errors.ts`,`constants.ts`],c=[`persistence.ts`,`typeorm.ts`,`hono.ts`,`react.ts`,`vue.ts`,`next.ts`,`electron.ts`,`browser.ts`,`node.ts`],l=[...o,...s,...c],u=[`impl`,`functions`,`methods`,`handlers`,`classes`,`class`,`manager`,`object`,`processor`,`process`,`common`,`logic`,`hooks`,`component`],d=[{pattern:/^use[A-Z][a-zA-Z]*\.spec\.tsx?$/,caseStyle:`camelCase`,description:`Hook 测试文件应使用 camelCase 并以 use 前缀开头(如 useChat.spec.ts)`},{pattern:/^use[A-Z][a-zA-Z]*\.test\.tsx?$/,caseStyle:`camelCase`,description:`Hook 测试文件应使用 camelCase 并以 use 前缀开头(如 useChat.test.ts)`},{pattern:/^use[A-Z][a-zA-Z]*\.types\.ts$/,caseStyle:`camelCase`,description:`Hook 类型文件应使用 camelCase 并以 use 前缀开头(如 useChat.types.ts)`},{pattern:/\.spec\.ts$/,caseStyle:`kebab-case-test`,description:`测试文件应使用 xxx.spec.ts 格式(kebab-case)`},{pattern:/\.test\.ts$/,caseStyle:`kebab-case-test`,description:`测试文件应使用 xxx.test.ts 格式(kebab-case)`},{pattern:/\.spec\.tsx$/,caseStyle:`kebab-case-test`,description:`测试文件应使用 xxx.spec.tsx 格式(kebab-case)`},{pattern:/\.test\.tsx$/,caseStyle:`kebab-case-test`,description:`测试文件应使用 xxx.test.tsx 格式(kebab-case)`},{pattern:/^use[A-Z][a-zA-Z]*\.tsx?$/,caseStyle:`camelCase`,description:`Hook 文件应使用 camelCase 并以 use 前缀开头(如 useChat.ts)`}],f=[{pattern:/\.service\.drizzle\.ts$/,caseStyle:`kebab-case`,description:`Drizzle Service 文件应使用 xxx.service.drizzle.ts 格式(kebab-case)`},{pattern:/\.service\.memory\.ts$/,caseStyle:`kebab-case`,description:`Memory Service 文件应使用 xxx.service.memory.ts 格式(kebab-case)`},{pattern:/\.service\.ts$/,caseStyle:`kebab-case`,description:`服务文件应使用 xxx.service.ts 格式(kebab-case)`},{pattern:/\.controller\.ts$/,caseStyle:`kebab-case`,description:`控制器文件应使用 xxx.controller.ts 格式(kebab-case)`},{pattern:/\.module\.ts$/,caseStyle:`kebab-case`,description:`模块文件应使用 xxx.module.ts 格式(kebab-case)`},{pattern:/\.repository\.drizzle\.ts$/,caseStyle:`kebab-case`,description:`Drizzle Repository 文件应使用 xxx.repository.drizzle.ts 格式(kebab-case)`},{pattern:/\.repository\.memory\.ts$/,caseStyle:`kebab-case`,description:`Memory Repository 文件应使用 xxx.repository.memory.ts 格式(kebab-case)`},{pattern:/\.repository\.interface\.ts$/,caseStyle:`kebab-case`,description:`Repository 接口文件应使用 xxx.repository.interface.ts 格式(kebab-case)`},{pattern:/\.repository\.types\.ts$/,caseStyle:`kebab-case`,description:`Repository 类型文件应使用 xxx.repository.types.ts 格式(kebab-case)`},{pattern:/\.repository\.ts$/,caseStyle:`kebab-case`,description:`Repository 文件应使用 xxx.repository.ts 格式(kebab-case)`},{pattern:/\.entity\.types\.ts$/,caseStyle:`kebab-case`,description:`Entity 类型文件应使用 xxx.entity.types.ts 格式(kebab-case)`},{pattern:/\.entity\.interface\.ts$/,caseStyle:`kebab-case`,description:`Entity 接口文件应使用 xxx.entity.interface.ts 格式(kebab-case)`},{pattern:/\.entity\.ts$/,caseStyle:`kebab-case`,description:`Entity 文件应使用 xxx.entity.ts 格式(kebab-case)`},{pattern:/\.dto\.ts$/,caseStyle:`kebab-case`,description:`DTO 文件应使用 xxx.dto.ts 格式(kebab-case)`},{pattern:/\.port\.interface\.ts$/,caseStyle:`kebab-case`,description:`Port 接口文件应使用 xxx.port.interface.ts 格式(kebab-case)`},{pattern:/\.interface\.ts$/,caseStyle:`kebab-case`,description:`接口文件应使用 xxx.interface.ts 格式(kebab-case)`},{pattern:/\.interface\.tsx$/,caseStyle:`kebab-case`,description:`接口文件应使用 xxx.interface.tsx 格式(kebab-case)`},{pattern:/\.types\.ts$/,caseStyle:`kebab-case`,description:`类型文件应使用 xxx.types.ts 格式(kebab-case)`},{pattern:/\.schema\.ts$/,caseStyle:`kebab-case`,description:`Schema 文件应使用 xxx.schema.ts 格式(kebab-case)`},{pattern:/\.port\.ts$/,caseStyle:`kebab-case`,description:`Port 文件应使用 xxx.port.ts 格式(kebab-case)`},{pattern:/\.port-adapter\.factory\.ts$/,caseStyle:`kebab-case`,description:`Port Adapter Factory 文件应使用 xxx.port-adapter.factory.ts 格式(kebab-case)`},{pattern:/\.port-adapter\.ts$/,caseStyle:`kebab-case`,description:`Port Adapter 文件应使用 xxx.port-adapter.ts 格式(kebab-case)`},{pattern:/\.adapter\.ts$/,caseStyle:`kebab-case`,description:`Adapter 文件应使用 xxx.adapter.ts 格式(kebab-case)`},{pattern:/\.client\.ts$/,caseStyle:`kebab-case`,description:`客户端文件应使用 xxx.client.ts 格式(kebab-case)`},{pattern:/\.events\.ts$/,caseStyle:`kebab-case`,description:`Events 文件应使用 xxx.events.ts 格式(kebab-case)`},{pattern:/\.mapper\.ts$/,caseStyle:`kebab-case`,description:`Mapper 文件应使用 xxx.mapper.ts 格式(kebab-case)`},{pattern:/\.ipc\.ts$/,caseStyle:`kebab-case`,description:`IPC 文件应使用 xxx.ipc.ts 格式(kebab-case)`},{pattern:/\.config\.ts$/,caseStyle:`kebab-case`,description:`配置文件应使用 xxx.config.ts 格式(kebab-case)`},{pattern:/\.errors\.ts$/,caseStyle:`kebab-case`,description:`错误文件应使用 xxx.errors.ts 格式(kebab-case)`},{pattern:/\.utils\.ts$/,caseStyle:`kebab-case`,description:`工具函数文件应使用 xxx.utils.ts 格式(kebab-case)`},{pattern:/\.constants\.ts$/,caseStyle:`kebab-case`,description:`常量文件应使用 xxx.constants.ts 格式(kebab-case)`},{pattern:/\.atom\.ts$/,caseStyle:`kebab-case`,description:`Atom 文件应使用 xxx.atom.ts 格式(kebab-case)`},{pattern:/\.vo\.ts$/,caseStyle:`kebab-case`,description:`值对象文件应使用 xxx.vo.ts 格式(kebab-case)`},{pattern:/\.usecase\.ts$/,caseStyle:`kebab-case`,description:`UseCase 文件应使用 xxx.usecase.ts 格式(kebab-case)`},{pattern:/\.styles\.css$/,caseStyle:`kebab-case`,description:`样式文件应使用 xxx.styles.css 格式(kebab-case)`},{pattern:/\.extension\.tsx?$/,caseStyle:`kebab-case`,description:`扩展文件应使用 xxx.extension.ts(x) 格式(kebab-case)`}],p=[{pattern:/^[A-Z][a-zA-Z0-9]*\.types\.ts$/,caseStyle:`PascalCase`,description:`类型文件可使用 PascalCase(如 DataExtractor.types.ts)`},{pattern:/^[A-Z][a-zA-Z0-9]*\.utils\.ts$/,caseStyle:`PascalCase`,description:`工具函数文件可使用 PascalCase(如 DataExtractor.utils.ts)`},{pattern:/^[A-Z][a-zA-Z0-9]*\.constants\.ts$/,caseStyle:`PascalCase`,description:`常量文件可使用 PascalCase(如 ByteVisualizer.constants.ts)`},{pattern:/^[A-Z][a-zA-Z0-9]*\.guards\.ts$/,caseStyle:`PascalCase`,description:`守卫文件可使用 PascalCase(如 DataExtractor.guards.ts)`},{pattern:/^[A-Z][a-zA-Z0-9]*\.helpers\.ts$/,caseStyle:`PascalCase`,description:`辅助函数文件可使用 PascalCase(如 TableVisualizer.helpers.ts)`},{pattern:/^[A-Z][a-zA-Z0-9]*\.groups\.ts$/,caseStyle:`PascalCase`,description:`分组文件可使用 PascalCase(如 TableVisualizer.groups.ts)`},{pattern:/^[A-Z][a-zA-Z0-9]*\.type-guards\.ts$/,caseStyle:`PascalCase`,description:`类型守卫文件可使用 PascalCase(如 TableVisualizer.type-guards.ts)`},{pattern:/^[A-Z][a-zA-Z0-9]*\.test\.ts$/,caseStyle:`PascalCase`,description:`测试文件可使用 PascalCase(如 DataExtractor.test.ts)`},{pattern:/^[A-Z][a-zA-Z0-9]*\.[a-z][-a-z]*\.test\.ts$/,caseStyle:`PascalCase`,description:`伴生测试文件可使用 PascalCase(如 DataExtractor.guards.test.ts)`},{pattern:/^[A-Z][a-zA-Z0-9]*\.[a-z][-a-z]*\.test\.tsx$/,caseStyle:`PascalCase`,description:`伴生测试文件可使用 PascalCase(如 PacketCompare.test.tsx)`},{pattern:/^[A-Z][a-zA-Z0-9]*\.test\.tsx$/,caseStyle:`PascalCase`,description:`测试文件可使用 PascalCase(如 ByteVisualizer.test.tsx)`}],m={pattern:/^[a-z][a-zA-Z0-9_-]*\.tsx$/,caseStyle:`PascalCase`,description:`组件文件应使用 PascalCase(如 ChatPage.tsx),小写开头不允许`},h={pattern:/^[A-Z][a-zA-Z0-9]*\.tsx$/,caseStyle:`PascalCase`,description:`React 组件文件可使用 PascalCase(如 ChatPage.tsx)`},g=[...d,...p,...f,m,h];function _(e){return/^[a-z][a-z0-9]*(-[a-z0-9]+)*$/.test(e)}function v(e){return e.split(`.`).every(e=>_(e)&&e.length>0)}function y(e){return/^[A-Z][a-zA-Z0-9]*$/.test(e)}function b(e){return/^[a-z][a-zA-Z0-9]*$/.test(e)}function x(e){let t=e.split(`.`)[0];return t!==void 0&&t.length>0&&y(t)}function S(e){return e.split(`.`).every(e=>_(e)&&e.length>0)}function ee(e,t){switch(t){case`kebab-case`:return v(e);case`PascalCase`:return y(e);case`camelCase`:return b(e);case`kebab-case-test`:return S(e);default:return!0}}function C(e){return/^v\d+(-[a-z0-9-]+)?\.ts$/.test(e)}function w(e){let t=e.replace(/\.tsx?$/,``);for(let e of u){if(t.endsWith(`.${e}`))return e;let n=t.split(`.`);if(n.length>=2&&n[n.length-1]===e)return e}return null}function T(e){return!!(l.includes(e)||e.endsWith(`.d.ts`)||C(e)||e.startsWith(`[`)||e.startsWith(`_`))}function E(e,t){if(t===h)return e.replace(/\.tsx$/,``);let n=t.pattern.source;if(n.startsWith(`^`)&&n.endsWith(`$`)){if(n.includes(`\\.test\\.`)||n.includes(`\\.spec\\.`))return e.replace(/\.(test|spec)\.(tsx?|jsx?)$/,``);let t=e.match(/^([A-Z][a-zA-Z0-9]*)\.[a-z][-a-z]*\.tsx?$/);return t&&t[1]?t[1]:n.includes(`\\.types\\.`)?e.replace(/\.types\.ts$/,``):e.replace(/\.[^.]+$/,``)}return e.replace(t.pattern,``)}[...l];const D=/* @__PURE__ */ new Map;function O(e){let t=D.get(e);if(t!==void 0)return t;let r;try{r=n(e)}catch{r=!1}return D.set(e,r),r}const k=/^([A-Z][a-zA-Z0-9]*)\.[a-z][a-z0-9-]*(\.[a-z][a-z0-9-]*)*\.ts$/;function A(e){return e.match(k)?.[1]??null}function j(e,t){let n=A(t);if(!n)return!1;let r=`${n}.tsx`,a=e.substring(0,e.lastIndexOf(`/`));return O(i.join(a,r))}function M(e){let t=e.replace(/\.(test|spec)\.(tsx|ts)$/,``);return y(t)||x(t)}function N(e,t){if(!(t.endsWith(`.test.tsx`)||t.endsWith(`.spec.tsx`)||t.endsWith(`.test.ts`)||t.endsWith(`.spec.ts`))||!M(t))return{status:`not-component-test`};let n=t.replace(/\.(test|spec)\.(tsx|ts)$/,`.tsx`),r=t.replace(/\.(test|spec)\.(tsx|ts)$/,`.ts`);if(e.includes(`/__tests__/`))return F(e,n)||F(e,r)?{status:`component-test-found`}:{status:`not-component-test`};if(e.includes(`/components/`)){let t=e.substring(0,e.lastIndexOf(`/`)),a=e.indexOf(`/components/`),o=e.substring(0,a+11),s=t;for(;;){if(P(s,n)||P(s,r))return{status:`component-test-found`};if(s===o)break;let e=i.dirname(s);if(e===s)break;s=e}}return{status:`not-component-test`}}function P(e,t){if(O(i.join(e,t)))return!0;try{let n=r(e,{withFileTypes:!0});for(let r of n)if(r.isDirectory()&&O(i.join(e,r.name,t)))return!0}catch{}return!1}function F(e,t){let n=e.indexOf(`/__tests__/`),r=e.substring(0,n),a=e.substring(n+11),o=i.dirname(a),s=o===`.`?r:i.join(r,o);for(;;){if(P(s,t))return!0;if(s===r)break;let e=i.dirname(s);if(e===s)break;s=e}return!1}const I=e({meta:{type:`problem`,docs:{description:`强制执行文件命名规范`}},createOnce(e){return{before(){if(T(e.filename.replace(/\\/g,`/`).split(`/`).pop()??``))return!1},Program(t){let n=e.filename.replace(/\\/g,`/`).split(`/`).pop()??``,r=e.filename.replace(/\\/g,`/`),i=w(n);if(i!==`hooks`&&j(r,n))return;if(N(r,n).status===`component-test-found`){let e=n.replace(/\.(test|spec)\.(tsx|ts)$/,``);if(y(e)||x(e))return}if(i!==null){let r=i===`hooks`?`hook 文件应使用 use 前缀小驼峰且不带后缀,如 useRepos.ts`:i===`component`?`组件文件应使用大驼峰且不带后缀,如 RepoList.tsx`:`.${i} 后缀语义过于泛化,请改用具体语义后缀`;e.report({node:t,message:`文件命名违规: ${n}。禁用 .${i} 后缀,${r}`});return}let a=g.find(e=>e.pattern.test(n));a&&(ee(E(n,a),a.caseStyle)||e.report({node:t,message:`文件命名违规: ${n}。${a.description}`}))}}}}),L=[`.types.ts`,`.types.d.ts`];function te(e){return L.some(t=>e.endsWith(t))}const R=e({meta:{type:`problem`,docs:{description:`强制执行 types 文件中的接口命名规范(禁止 I 前缀)`}},createOnce(e){return{before(){let t=e.filename.replace(/\\/g,`/`).split(`/`).pop()??``;if(t.endsWith(`.test.ts`)||t.endsWith(`.spec.ts`)||!te(t))return!1},TSInterfaceDeclaration(t){let n=t.id.name;a(n)&&e.report({node:t,message:`类型文件中的接口名 "${n}" 使用了 I 前缀。现代 TypeScript 不推荐使用匈牙利命名法,建议使用 "${n.substring(1)}"`})}}}}),z=[`impl`,`functions`,`methods`,`handlers`,`classes`,`class`,`manager`,`object`,`processor`,`process`,`common`,`logic`],B=/* @__PURE__ */ new Set([`index.ts`,`index.tsx`,`main.ts`,`main.tsx`,`App.tsx`,`types.ts`,`errors.ts`,`constants.ts`,`types.d.ts`,`persistence.ts`,`typeorm.ts`,`hono.ts`,`react.ts`,`vue.ts`,`next.ts`,`electron.ts`,`browser.ts`,`node.ts`]);function V(e){if(!e.endsWith(`.ts`)&&!e.endsWith(`.tsx`))return!1;let t=e.replace(/\.tsx?$/,``);return/^use[A-Z][a-zA-Z0-9]*$/.test(t)}function H(e){if(!e.endsWith(`.tsx`))return!1;let t=e.replace(`.tsx`,``);return/^[A-Z][a-zA-Z0-9]*$/.test(t)}function U(e){return/^v\d+(-[a-z0-9-]+)?\.ts$/.test(e)}function W(e){let t=e.replace(/\.tsx?$/,``);return[/s$/,/es$/,/ies$/].some(e=>e.test(t))}function G(e){return e.endsWith(`.types.ts`)?!0:/^[a-z][a-z0-9-]*\.[a-z][a-z0-9-]*\.tsx?$/.test(e)||/^[a-z][a-z0-9-]*\.[a-z][a-z0-9-]*\.[a-z][a-z0-9-]*\.tsx?$/.test(e)}function K(e){let t=e.replace(/\.tsx?$/,``);for(let e of z){if(t.endsWith(`.${e}`))return e;let n=t.split(`.`);if(n.length>=2&&n[n.length-1]===e)return e}return null}function q(e){let t=[],n=e.replace(/\.tsx?$/,``);return(n.includes(`service`)||n.includes(`Service`))&&t.push(`.service.ts`),(n.includes(`controller`)||n.includes(`Controller`))&&t.push(`.controller.ts`),(n.includes(`utils`)||n.includes(`helper`))&&t.push(`.utils.ts`),(n.includes(`constant`)||n.includes(`Constant`))&&t.push(`.constants.ts`),(n.includes(`config`)||n.includes(`Config`))&&t.push(`.config.ts`),t.length===0&&t.push(`.utils.ts`,`.config.ts`,`.constants.ts`),t}function J(e){for(let t of[`.test.ts`,`.test.tsx`,`.spec.ts`,`.spec.tsx`])if(e.endsWith(t)){let n=t.endsWith(`.tsx`)?`.tsx`:`.ts`;return e.replace(t,n)}return e}function Y(e,t){let n=e.replace(/\\/g,`/`);for(let e of t){let t=e.replace(/[.+^${}()|[\]\\]/g,`\\$&`).replace(/\*\*\//g,`<<DOUBLE_STAR_SLASH>>`).replace(/\*\*/g,`<<DOUBLE_STAR>>`).replace(/\*/g,`[^/]*`).replace(/<<DOUBLE_STAR_SLASH>>/g,`(.*\\/)?`).replace(/<<DOUBLE_STAR>>/g,`.*`);if(RegExp(`.*${t}`).test(n))return!0}return!1}function X(e){return typeof e==`object`&&!!e}function Z(e,t,n,r){return!!(n?.skipPatterns&&Y(r,n.skipPatterns)||B.has(t)||t.endsWith(`.d.ts`)||e.startsWith(`[`)||e.startsWith(`_`)||t.endsWith(`.tsx`)||e.endsWith(`.test.ts`)||e.endsWith(`.test.tsx`)||e.endsWith(`.spec.ts`)||e.endsWith(`.spec.tsx`)||U(t)||H(t)||V(t)||!K(t)&&W(t))}const Q=e({meta:{type:`problem`,docs:{description:`强制执行文件类型后缀规范,确保文件名明确表达其用途`},schema:[{type:`object`,properties:{skipPatterns:{type:`array`,items:{type:`string`}}},additionalProperties:!1}]},createOnce(e){return{Program(t){let n=e.options?.[0],r=X(n)?n:void 0,i=e.filename.replace(/\\/g,`/`).split(`/`).pop()??``,a=J(i);if(Z(i,a,r,e.filename))return;let o=K(a);if(o){e.report({node:t,message:`文件 "${i}" 使用了禁止的后缀 ".${o}"。该后缀语义过于泛化,无法表达文件用途。请使用更具体的后缀,如 ".utils"、".service" 等。`});return}if(G(a))return;let s=q(a);e.report({node:t,message:`文件 "${i}" 缺少类型后缀。除特殊文件外,所有文件应使用 xxx.yyy.ts 格式明确表达用途。建议: ${s.join(`, `)}`})}}}}),$=t({meta:{name:`@longzai-intelligence-oxlint/file-naming-plugin`},rules:{"file-naming":I,"require-type-naming":R,"require-type-suffix":Q}});export{$ as default,$ as fileNamingPlugin,I as fileNamingRule,R as requireTypeNamingRule,Q as requireTypeSuffixRule};
|
|
1
|
+
import{defineRule as e,eslintCompatPlugin as t}from"@oxlint/plugins";import{existsSync as n,readdirSync as r}from"node:fs";import i from"node:path";import{hasIPrefix as a}from"@longzai-intelligence-oxlint/utils";const o=[`index.ts`,`index.tsx`,`main.ts`,`main.tsx`,`App.tsx`],s=[`types.ts`,`errors.ts`,`constants.ts`],c=[`persistence.ts`,`typeorm.ts`,`hono.ts`,`react.ts`,`vue.ts`,`next.ts`,`electron.ts`,`browser.ts`,`node.ts`],l=[...o,...s,...c],u=[`impl`,`functions`,`methods`,`handlers`,`classes`,`class`,`manager`,`object`,`processor`,`process`,`common`,`logic`,`hooks`,`component`],d=[{pattern:/^use[A-Z][a-zA-Z]*\.spec\.tsx?$/,caseStyle:`camelCase`,description:`Hook 测试文件应使用 camelCase 并以 use 前缀开头(如 useChat.spec.ts)`},{pattern:/^use[A-Z][a-zA-Z]*\.test\.tsx?$/,caseStyle:`camelCase`,description:`Hook 测试文件应使用 camelCase 并以 use 前缀开头(如 useChat.test.ts)`},{pattern:/^use[A-Z][a-zA-Z]*\.types\.ts$/,caseStyle:`camelCase`,description:`Hook 类型文件应使用 camelCase 并以 use 前缀开头(如 useChat.types.ts)`},{pattern:/\.spec\.ts$/,caseStyle:`kebab-case-test`,description:`测试文件应使用 xxx.spec.ts 格式(kebab-case)`},{pattern:/\.test\.ts$/,caseStyle:`kebab-case-test`,description:`测试文件应使用 xxx.test.ts 格式(kebab-case)`},{pattern:/\.spec\.tsx$/,caseStyle:`kebab-case-test`,description:`测试文件应使用 xxx.spec.tsx 格式(kebab-case)`},{pattern:/\.test\.tsx$/,caseStyle:`kebab-case-test`,description:`测试文件应使用 xxx.test.tsx 格式(kebab-case)`},{pattern:/^use[A-Z][a-zA-Z]*\.tsx?$/,caseStyle:`camelCase`,description:`Hook 文件应使用 camelCase 并以 use 前缀开头(如 useChat.ts)`}],f=[{pattern:/\.service\.drizzle\.ts$/,caseStyle:`kebab-case`,description:`Drizzle Service 文件应使用 xxx.service.drizzle.ts 格式(kebab-case)`},{pattern:/\.service\.memory\.ts$/,caseStyle:`kebab-case`,description:`Memory Service 文件应使用 xxx.service.memory.ts 格式(kebab-case)`},{pattern:/\.service\.ts$/,caseStyle:`kebab-case`,description:`服务文件应使用 xxx.service.ts 格式(kebab-case)`},{pattern:/\.controller\.ts$/,caseStyle:`kebab-case`,description:`控制器文件应使用 xxx.controller.ts 格式(kebab-case)`},{pattern:/\.module\.ts$/,caseStyle:`kebab-case`,description:`模块文件应使用 xxx.module.ts 格式(kebab-case)`},{pattern:/\.repository\.drizzle\.ts$/,caseStyle:`kebab-case`,description:`Drizzle Repository 文件应使用 xxx.repository.drizzle.ts 格式(kebab-case)`},{pattern:/\.repository\.memory\.ts$/,caseStyle:`kebab-case`,description:`Memory Repository 文件应使用 xxx.repository.memory.ts 格式(kebab-case)`},{pattern:/\.repository\.interface\.ts$/,caseStyle:`kebab-case`,description:`Repository 接口文件应使用 xxx.repository.interface.ts 格式(kebab-case)`},{pattern:/\.repository\.types\.ts$/,caseStyle:`kebab-case`,description:`Repository 类型文件应使用 xxx.repository.types.ts 格式(kebab-case)`},{pattern:/\.repository\.ts$/,caseStyle:`kebab-case`,description:`Repository 文件应使用 xxx.repository.ts 格式(kebab-case)`},{pattern:/\.entity\.types\.ts$/,caseStyle:`kebab-case`,description:`Entity 类型文件应使用 xxx.entity.types.ts 格式(kebab-case)`},{pattern:/\.entity\.interface\.ts$/,caseStyle:`kebab-case`,description:`Entity 接口文件应使用 xxx.entity.interface.ts 格式(kebab-case)`},{pattern:/\.entity\.ts$/,caseStyle:`kebab-case`,description:`Entity 文件应使用 xxx.entity.ts 格式(kebab-case)`},{pattern:/\.dto\.ts$/,caseStyle:`kebab-case`,description:`DTO 文件应使用 xxx.dto.ts 格式(kebab-case)`},{pattern:/\.port\.interface\.ts$/,caseStyle:`kebab-case`,description:`Port 接口文件应使用 xxx.port.interface.ts 格式(kebab-case)`},{pattern:/\.interface\.ts$/,caseStyle:`kebab-case`,description:`接口文件应使用 xxx.interface.ts 格式(kebab-case)`},{pattern:/\.interface\.tsx$/,caseStyle:`kebab-case`,description:`接口文件应使用 xxx.interface.tsx 格式(kebab-case)`},{pattern:/\.types\.ts$/,caseStyle:`kebab-case`,description:`类型文件应使用 xxx.types.ts 格式(kebab-case)`},{pattern:/\.schema\.ts$/,caseStyle:`kebab-case`,description:`Schema 文件应使用 xxx.schema.ts 格式(kebab-case)`},{pattern:/\.port\.ts$/,caseStyle:`kebab-case`,description:`Port 文件应使用 xxx.port.ts 格式(kebab-case)`},{pattern:/\.port-adapter\.factory\.ts$/,caseStyle:`kebab-case`,description:`Port Adapter Factory 文件应使用 xxx.port-adapter.factory.ts 格式(kebab-case)`},{pattern:/\.port-adapter\.ts$/,caseStyle:`kebab-case`,description:`Port Adapter 文件应使用 xxx.port-adapter.ts 格式(kebab-case)`},{pattern:/\.adapter\.ts$/,caseStyle:`kebab-case`,description:`Adapter 文件应使用 xxx.adapter.ts 格式(kebab-case)`},{pattern:/\.client\.ts$/,caseStyle:`kebab-case`,description:`客户端文件应使用 xxx.client.ts 格式(kebab-case)`},{pattern:/\.events\.ts$/,caseStyle:`kebab-case`,description:`Events 文件应使用 xxx.events.ts 格式(kebab-case)`},{pattern:/\.mapper\.ts$/,caseStyle:`kebab-case`,description:`Mapper 文件应使用 xxx.mapper.ts 格式(kebab-case)`},{pattern:/\.ipc\.ts$/,caseStyle:`kebab-case`,description:`IPC 文件应使用 xxx.ipc.ts 格式(kebab-case)`},{pattern:/\.config\.ts$/,caseStyle:`kebab-case`,description:`配置文件应使用 xxx.config.ts 格式(kebab-case)`},{pattern:/\.errors\.ts$/,caseStyle:`kebab-case`,description:`错误文件应使用 xxx.errors.ts 格式(kebab-case)`},{pattern:/\.utils\.ts$/,caseStyle:`kebab-case`,description:`工具函数文件应使用 xxx.utils.ts 格式(kebab-case)`},{pattern:/\.constants\.ts$/,caseStyle:`kebab-case`,description:`常量文件应使用 xxx.constants.ts 格式(kebab-case)`},{pattern:/\.atom\.ts$/,caseStyle:`kebab-case`,description:`Atom 文件应使用 xxx.atom.ts 格式(kebab-case)`},{pattern:/\.vo\.ts$/,caseStyle:`kebab-case`,description:`值对象文件应使用 xxx.vo.ts 格式(kebab-case)`},{pattern:/\.usecase\.ts$/,caseStyle:`kebab-case`,description:`UseCase 文件应使用 xxx.usecase.ts 格式(kebab-case)`},{pattern:/\.styles\.css$/,caseStyle:`kebab-case`,description:`样式文件应使用 xxx.styles.css 格式(kebab-case)`},{pattern:/\.extension\.tsx?$/,caseStyle:`kebab-case`,description:`扩展文件应使用 xxx.extension.ts(x) 格式(kebab-case)`}],p=[{pattern:/^[A-Z][a-zA-Z0-9]*\.types\.ts$/,caseStyle:`PascalCase`,description:`类型文件可使用 PascalCase(如 DataExtractor.types.ts)`},{pattern:/^[A-Z][a-zA-Z0-9]*\.utils\.ts$/,caseStyle:`PascalCase`,description:`工具函数文件可使用 PascalCase(如 DataExtractor.utils.ts)`},{pattern:/^[A-Z][a-zA-Z0-9]*\.constants\.ts$/,caseStyle:`PascalCase`,description:`常量文件可使用 PascalCase(如 ByteVisualizer.constants.ts)`},{pattern:/^[A-Z][a-zA-Z0-9]*\.guards\.ts$/,caseStyle:`PascalCase`,description:`守卫文件可使用 PascalCase(如 DataExtractor.guards.ts)`},{pattern:/^[A-Z][a-zA-Z0-9]*\.helpers\.ts$/,caseStyle:`PascalCase`,description:`辅助函数文件可使用 PascalCase(如 TableVisualizer.helpers.ts)`},{pattern:/^[A-Z][a-zA-Z0-9]*\.groups\.ts$/,caseStyle:`PascalCase`,description:`分组文件可使用 PascalCase(如 TableVisualizer.groups.ts)`},{pattern:/^[A-Z][a-zA-Z0-9]*\.type-guards\.ts$/,caseStyle:`PascalCase`,description:`类型守卫文件可使用 PascalCase(如 TableVisualizer.type-guards.ts)`},{pattern:/^[A-Z][a-zA-Z0-9]*\.test\.ts$/,caseStyle:`PascalCase`,description:`测试文件可使用 PascalCase(如 DataExtractor.test.ts)`},{pattern:/^[A-Z][a-zA-Z0-9]*\.[a-z][-a-z]*\.test\.ts$/,caseStyle:`PascalCase`,description:`伴生测试文件可使用 PascalCase(如 DataExtractor.guards.test.ts)`},{pattern:/^[A-Z][a-zA-Z0-9]*\.[a-z][-a-z]*\.test\.tsx$/,caseStyle:`PascalCase`,description:`伴生测试文件可使用 PascalCase(如 PacketCompare.test.tsx)`},{pattern:/^[A-Z][a-zA-Z0-9]*\.test\.tsx$/,caseStyle:`PascalCase`,description:`测试文件可使用 PascalCase(如 ByteVisualizer.test.tsx)`}],m={pattern:/^[a-z][a-zA-Z0-9_-]*\.tsx$/,caseStyle:`PascalCase`,description:`组件文件应使用 PascalCase(如 ChatPage.tsx),小写开头不允许`},h={pattern:/^[A-Z][a-zA-Z0-9]*\.tsx$/,caseStyle:`PascalCase`,description:`React 组件文件可使用 PascalCase(如 ChatPage.tsx)`},g=[...d,...p,...f,m,h];function _(e){return/^[a-z][a-z0-9]*(-[a-z0-9]+)*$/.test(e)}function v(e){return e.split(`.`).every(e=>_(e)&&e.length>0)}function y(e){return/^[A-Z][a-zA-Z0-9]*$/.test(e)}function b(e){return/^[a-z][a-zA-Z0-9]*$/.test(e)}function x(e){let t=e.split(`.`)[0];return t!==void 0&&t.length>0&&y(t)}function S(e){return e.split(`.`).every(e=>_(e)&&e.length>0)}function ee(e,t){switch(t){case`kebab-case`:return v(e);case`PascalCase`:return y(e);case`camelCase`:return b(e);case`kebab-case-test`:return S(e);default:return!0}}function C(e){return/^v\d+(-[a-z0-9-]+)?\.ts$/.test(e)}function w(e){let t=e.replace(/\.tsx?$/,``);for(let e of u){if(t.endsWith(`.${e}`))return e;let n=t.split(`.`);if(n.length>=2&&n[n.length-1]===e)return e}return null}function T(e){return!!(l.includes(e)||e.endsWith(`.d.ts`)||C(e)||e.startsWith(`[`)||e.startsWith(`_`))}function E(e,t){if(t===h)return e.replace(/\.tsx$/,``);let n=t.pattern.source;if(n.startsWith(`^`)&&n.endsWith(`$`)){if(n.includes(`\\.test\\.`)||n.includes(`\\.spec\\.`))return e.replace(/\.(test|spec)\.(tsx?|jsx?)$/,``);let t=e.match(/^([A-Z][a-zA-Z0-9]*)\.[a-z][-a-z]*\.tsx?$/);return t&&t[1]?t[1]:n.includes(`\\.types\\.`)?e.replace(/\.types\.ts$/,``):e.replace(/\.[^.]+$/,``)}return e.replace(t.pattern,``)}[...l];const D=/* @__PURE__ */ new Map;function O(e){let t=D.get(e);if(t!==void 0)return t;let r;try{r=n(e)}catch{r=!1}return D.set(e,r),r}const k=/^([A-Z][a-zA-Z0-9]*)\.[a-z][a-z0-9-]*(\.[a-z][a-z0-9-]*)*\.ts$/;function A(e){return e.match(k)?.[1]??null}function j(e,t){let n=A(t);if(!n)return!1;let r=`${n}.tsx`,a=e.substring(0,e.lastIndexOf(`/`));return O(i.join(a,r))}function M(e){let t=e.replace(/\.(test|spec)\.(tsx|ts)$/,``);return y(t)||x(t)}function N(e,t){if(!(t.endsWith(`.test.tsx`)||t.endsWith(`.spec.tsx`)||t.endsWith(`.test.ts`)||t.endsWith(`.spec.ts`))||!M(t))return{status:`not-component-test`};let n=t.replace(/\.(test|spec)\.(tsx|ts)$/,`.tsx`),r=t.replace(/\.(test|spec)\.(tsx|ts)$/,`.ts`);if(e.includes(`/__tests__/`))return F(e,n)||F(e,r)?{status:`component-test-found`}:{status:`not-component-test`};if(e.includes(`/components/`)){let t=e.substring(0,e.lastIndexOf(`/`)),a=e.indexOf(`/components/`),o=e.substring(0,a+11),s=t;for(;;){if(P(s,n)||P(s,r))return{status:`component-test-found`};if(s===o)break;let e=i.dirname(s);if(e===s)break;s=e}}return{status:`not-component-test`}}function P(e,t){if(O(i.join(e,t)))return!0;try{let n=r(e,{withFileTypes:!0});for(let r of n)if(r.isDirectory()&&O(i.join(e,r.name,t)))return!0}catch{}return!1}function F(e,t){let n=e.indexOf(`/__tests__/`),r=e.substring(0,n),a=e.substring(n+11),o=i.dirname(a),s=o===`.`?r:i.join(r,o);for(;;){if(P(s,t))return!0;if(s===r)break;let e=i.dirname(s);if(e===s)break;s=e}return!1}const I=e({meta:{type:`problem`,docs:{description:`强制执行文件命名规范`}},createOnce(e){return{before(){if(T(e.filename.replace(/\\/g,`/`).split(`/`).pop()??``))return!1},Program(t){let n=e.filename.replace(/\\/g,`/`).split(`/`).pop()??``,r=e.filename.replace(/\\/g,`/`),i=w(n);if(i!==`hooks`&&j(r,n))return;if(N(r,n).status===`component-test-found`){let e=n.replace(/\.(test|spec)\.(tsx|ts)$/,``);if(y(e)||x(e))return}if(i!==null){let r=i===`hooks`?`hook 文件应使用 use 前缀小驼峰且不带后缀,如 useRepos.ts`:i===`component`?`组件文件应使用大驼峰且不带后缀,如 RepoList.tsx`:`.${i} 后缀语义过于泛化,请改用具体语义后缀`;e.report({node:t,message:`文件命名违规: ${n}。禁用 .${i} 后缀,${r}`});return}let a=g.find(e=>e.pattern.test(n));a&&(ee(E(n,a),a.caseStyle)||e.report({node:t,message:`文件命名违规: ${n}。${a.description}`}))}}}}),L=[`.types.ts`,`.types.d.ts`];function te(e){return L.some(t=>e.endsWith(t))}const R=e({meta:{type:`problem`,docs:{description:`强制执行 types 文件中的接口命名规范(禁止 I 前缀)`}},createOnce(e){return{before(){let t=e.filename.replace(/\\/g,`/`).split(`/`).pop()??``;if(t.endsWith(`.test.ts`)||t.endsWith(`.spec.ts`)||!te(t))return!1},TSInterfaceDeclaration(t){let n=t.id.name;a(n)&&e.report({node:t,message:`类型文件中的接口名 "${n}" 使用了 I 前缀。现代 TypeScript 不推荐使用匈牙利命名法,建议使用 "${n.substring(1)}"`})}}}}),z=[`impl`,`functions`,`methods`,`handlers`,`classes`,`class`,`manager`,`object`,`processor`,`process`,`common`,`logic`],B=/* @__PURE__ */ new Set([`index.ts`,`index.tsx`,`main.ts`,`main.tsx`,`App.tsx`,`types.ts`,`errors.ts`,`constants.ts`,`types.d.ts`,`persistence.ts`,`typeorm.ts`,`migrate.ts`,`seed.ts`,`hono.ts`,`react.ts`,`vue.ts`,`next.ts`,`electron.ts`,`browser.ts`,`node.ts`]);function V(e){if(!e.endsWith(`.ts`)&&!e.endsWith(`.tsx`))return!1;let t=e.replace(/\.tsx?$/,``);return/^use[A-Z][a-zA-Z0-9]*$/.test(t)}function H(e){if(!e.endsWith(`.tsx`))return!1;let t=e.replace(`.tsx`,``);return/^[A-Z][a-zA-Z0-9]*$/.test(t)}function U(e){return/^v\d+(-[a-z0-9-]+)?\.ts$/.test(e)}function W(e){let t=e.replace(/\.tsx?$/,``);return[/s$/,/es$/,/ies$/].some(e=>e.test(t))}function G(e){return e.endsWith(`.types.ts`)?!0:/^[a-z][a-z0-9-]*\.[a-z][a-z0-9-]*\.tsx?$/.test(e)||/^[a-z][a-z0-9-]*\.[a-z][a-z0-9-]*\.[a-z][a-z0-9-]*\.tsx?$/.test(e)}function K(e){let t=e.replace(/\.tsx?$/,``);for(let e of z){if(t.endsWith(`.${e}`))return e;let n=t.split(`.`);if(n.length>=2&&n[n.length-1]===e)return e}return null}function q(e){let t=[],n=e.replace(/\.tsx?$/,``);return(n.includes(`service`)||n.includes(`Service`))&&t.push(`.service.ts`),(n.includes(`controller`)||n.includes(`Controller`))&&t.push(`.controller.ts`),(n.includes(`utils`)||n.includes(`helper`))&&t.push(`.utils.ts`),(n.includes(`constant`)||n.includes(`Constant`))&&t.push(`.constants.ts`),(n.includes(`config`)||n.includes(`Config`))&&t.push(`.config.ts`),t.length===0&&t.push(`.utils.ts`,`.config.ts`,`.constants.ts`),t}function J(e){for(let t of[`.test.ts`,`.test.tsx`,`.spec.ts`,`.spec.tsx`])if(e.endsWith(t)){let n=t.endsWith(`.tsx`)?`.tsx`:`.ts`;return e.replace(t,n)}return e}function Y(e,t){let n=e.replace(/\\/g,`/`);for(let e of t){let t=e.replace(/[.+^${}()|[\]\\]/g,`\\$&`).replace(/\*\*\//g,`<<DOUBLE_STAR_SLASH>>`).replace(/\*\*/g,`<<DOUBLE_STAR>>`).replace(/\*/g,`[^/]*`).replace(/<<DOUBLE_STAR_SLASH>>/g,`(.*\\/)?`).replace(/<<DOUBLE_STAR>>/g,`.*`);if(RegExp(`.*${t}`).test(n))return!0}return!1}function X(e){return typeof e==`object`&&!!e}function Z(e,t,n,r){return!!(n?.skipPatterns&&Y(r,n.skipPatterns)||B.has(t)||t.endsWith(`.d.ts`)||e.startsWith(`[`)||e.startsWith(`_`)||t.endsWith(`.tsx`)||e.endsWith(`.test.ts`)||e.endsWith(`.test.tsx`)||e.endsWith(`.spec.ts`)||e.endsWith(`.spec.tsx`)||U(t)||H(t)||V(t)||!K(t)&&W(t))}const Q=e({meta:{type:`problem`,docs:{description:`强制执行文件类型后缀规范,确保文件名明确表达其用途`},schema:[{type:`object`,properties:{skipPatterns:{type:`array`,items:{type:`string`}}},additionalProperties:!1}]},createOnce(e){return{Program(t){let n=e.options?.[0],r=X(n)?n:void 0,i=e.filename.replace(/\\/g,`/`).split(`/`).pop()??``,a=J(i);if(Z(i,a,r,e.filename))return;let o=K(a);if(o){e.report({node:t,message:`文件 "${i}" 使用了禁止的后缀 ".${o}"。该后缀语义过于泛化,无法表达文件用途。请使用更具体的后缀,如 ".utils"、".service" 等。`});return}if(G(a))return;let s=q(a);e.report({node:t,message:`文件 "${i}" 缺少类型后缀。除特殊文件外,所有文件应使用 xxx.yyy.ts 格式明确表达用途。建议: ${s.join(`, `)}`})}}}}),$=t({meta:{name:`@longzai-intelligence-oxlint/file-naming-plugin`},rules:{"file-naming":I,"require-type-naming":R,"require-type-suffix":Q}});export{$ as default,$ as fileNamingPlugin,I as fileNamingRule,R as requireTypeNamingRule,Q as requireTypeSuffixRule};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@longzai-intelligence-oxlint/file-naming-plugin",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.41",
|
|
4
4
|
"description": "Longzai Intelligence oxlint 文件命名规则插件",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"file-naming",
|
|
@@ -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.1.
|
|
47
|
-
"@longzai-intelligence-oxlint/utils": "0.1.
|
|
46
|
+
"@longzai-intelligence-oxlint/core": "0.1.41",
|
|
47
|
+
"@longzai-intelligence-oxlint/utils": "0.1.41",
|
|
48
48
|
"@oxlint/plugins": "^1.74.0"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|