@longzai-intelligence-oxlint/file-naming-plugin 0.1.18 → 0.1.19

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 CHANGED
@@ -1,17 +1,15 @@
1
1
  # @longzai-intelligence-oxlint/file-naming-plugin
2
2
 
3
- ## 意图
3
+ Longzai Intelligence Oxlint 的文件命名插件,统一文件与类型命名约定。
4
4
 
5
- 在静态检查阶段统一文件与类型命名约定,避免命名风格混乱导致的检索与维护成本。
5
+ ## 简介
6
6
 
7
- ## 定位
7
+ 治理命名风格混乱的问题:文件命名风格、类型命名与类型后缀在静态检查阶段统一约束,防止命名混乱推高检索与维护成本。
8
8
 
9
- Oxlint 生态的文件命名治理插件,通过 `eslintCompatPlugin` 包装以同时兼容 Oxlint 与 ESLint 运行时。
9
+ ## 能力
10
10
 
11
- ## 职能
12
-
13
- 提供 `file-naming` 规则约束文件命名风格、`require-type-naming` 规则约束类型命名、`require-type-suffix` 规则约束类型后缀;插件以 `@longzai-intelligence/file-naming` 名称注册。
14
-
15
- ## 实现情况
16
-
17
- 三条规则均已实现并按规则文件组织,规则与插件对象一并导出;当前规则集合聚焦命名维度,覆盖文件与类型两层约束。
11
+ - `file-naming` — 文件命名风格约束
12
+ - `require-type-naming` — 类型命名约束
13
+ - `require-type-suffix` — 类型后缀约束
14
+ - 规则以 `@longzai-intelligence/file-naming` 名称注册
15
+ - 同时兼容 Oxlint 与 ESLint 运行时
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=[{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)`}],d=[{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:/\.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)`}],f=[{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]*\.hooks\.ts$/,caseStyle:`PascalCase`,description:`Hooks 文件可使用 PascalCase(如 DataVisualizer.hooks.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)`}],p={pattern:/^[a-z][a-z0-9-]*\.tsx$/,caseStyle:`kebab-case`,description:`TSX 组件文件可使用 kebab-case(如 button.tsx)`},m={pattern:/^[A-Z][a-zA-Z0-9]*\.tsx$/,caseStyle:`PascalCase`,description:`React 组件文件可使用 PascalCase(如 ChatPage.tsx)`},h=[...u,...f,...d,p,m];function g(e){return/^[a-z][a-z0-9]*(-[a-z0-9]+)*$/.test(e)}function _(e){return e.split(`.`).every(e=>g(e)&&e.length>0)}function v(e){return/^[A-Z][a-zA-Z0-9]*$/.test(e)}function y(e){return/^[a-z][a-zA-Z0-9]*$/.test(e)}function b(e){let t=e.split(`.`)[0];return t!==void 0&&t.length>0&&v(t)}function x(e){return e.split(`.`).every(e=>g(e)&&e.length>0)}function S(e,t){switch(t){case`kebab-case`:return _(e);case`PascalCase`:return v(e);case`camelCase`:return y(e);case`kebab-case-test`:return x(e);default:return!0}}function C(e){return/^v\d+(-[a-z0-9-]+)?\.ts$/.test(e)}function w(e){return!!(l.includes(e)||e.endsWith(`.d.ts`)||C(e)||e.startsWith(`[`)||e.startsWith(`_`))}function T(e,t){if(t===m)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 E=new Map;function D(e){let t=E.get(e);if(t!==void 0)return t;let r;try{r=n(e)}catch{r=!1}return E.set(e,r),r}const O=/^([A-Z][a-zA-Z0-9]*)\.[a-z][a-z0-9-]*(\.[a-z][a-z0-9-]*)*\.ts$/;function k(e){return e.match(O)?.[1]??null}function A(e,t){let n=k(t);if(!n)return!1;let r=`${n}.tsx`,a=e.substring(0,e.lastIndexOf(`/`));return D(i.join(a,r))}function j(e){let t=e.replace(/\.(test|spec)\.(tsx|ts)$/,``);return v(t)||b(t)}function M(e,t){if(!(t.endsWith(`.test.tsx`)||t.endsWith(`.spec.tsx`)||t.endsWith(`.test.ts`)||t.endsWith(`.spec.ts`))||!j(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 P(e,n)||P(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(N(s,n)||N(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 N(e,t){if(D(i.join(e,t)))return!0;try{let n=r(e,{withFileTypes:!0});for(let r of n)if(r.isDirectory()&&D(i.join(e,r.name,t)))return!0}catch{}return!1}function P(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(N(s,t))return!0;if(s===r)break;let e=i.dirname(s);if(e===s)break;s=e}return!1}const F=e({meta:{type:`problem`,docs:{description:`强制执行文件命名规范`}},createOnce(e){return{before(){if(w(e.filename.replace(/\\/g,`/`).split(`/`).pop()??``))return!1},Program(t){let n=e.filename.replace(/\\/g,`/`).split(`/`).pop()??``,r=e.filename.replace(/\\/g,`/`);if(A(r,n))return;if(M(r,n).status===`component-test-found`){let e=n.replace(/\.(test|spec)\.(tsx|ts)$/,``);if(v(e)||b(e))return}let i=h.find(e=>e.pattern.test(n));i&&(S(T(n,i),i.caseStyle)||e.report({node:t,message:`文件命名违规: ${n}。${i.description}`}))}}}}),I=[`.types.ts`,`.types.d.ts`];function L(e){return I.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`)||!L(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=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":F,"require-type-naming":R,"require-type-suffix":Q}});export{$ as default,$ as fileNamingPlugin,F 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=[{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)`}],d=[{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:/\.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)`}],f=[{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]*\.hooks\.ts$/,caseStyle:`PascalCase`,description:`Hooks 文件可使用 PascalCase(如 DataVisualizer.hooks.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)`}],p={pattern:/^[a-z][a-z0-9-]*\.tsx$/,caseStyle:`kebab-case`,description:`TSX 组件文件可使用 kebab-case(如 button.tsx)`},m={pattern:/^[A-Z][a-zA-Z0-9]*\.tsx$/,caseStyle:`PascalCase`,description:`React 组件文件可使用 PascalCase(如 ChatPage.tsx)`},h=[...u,...f,...d,p,m];function g(e){return/^[a-z][a-z0-9]*(-[a-z0-9]+)*$/.test(e)}function _(e){return e.split(`.`).every(e=>g(e)&&e.length>0)}function v(e){return/^[A-Z][a-zA-Z0-9]*$/.test(e)}function y(e){return/^[a-z][a-zA-Z0-9]*$/.test(e)}function b(e){let t=e.split(`.`)[0];return t!==void 0&&t.length>0&&v(t)}function x(e){return e.split(`.`).every(e=>g(e)&&e.length>0)}function S(e,t){switch(t){case`kebab-case`:return _(e);case`PascalCase`:return v(e);case`camelCase`:return y(e);case`kebab-case-test`:return x(e);default:return!0}}function C(e){return/^v\d+(-[a-z0-9-]+)?\.ts$/.test(e)}function w(e){return!!(l.includes(e)||e.endsWith(`.d.ts`)||C(e)||e.startsWith(`[`)||e.startsWith(`_`))}function T(e,t){if(t===m)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 E=/* @__PURE__ */ new Map;function D(e){let t=E.get(e);if(t!==void 0)return t;let r;try{r=n(e)}catch{r=!1}return E.set(e,r),r}const O=/^([A-Z][a-zA-Z0-9]*)\.[a-z][a-z0-9-]*(\.[a-z][a-z0-9-]*)*\.ts$/;function k(e){return e.match(O)?.[1]??null}function A(e,t){let n=k(t);if(!n)return!1;let r=`${n}.tsx`,a=e.substring(0,e.lastIndexOf(`/`));return D(i.join(a,r))}function j(e){let t=e.replace(/\.(test|spec)\.(tsx|ts)$/,``);return v(t)||b(t)}function M(e,t){if(!(t.endsWith(`.test.tsx`)||t.endsWith(`.spec.tsx`)||t.endsWith(`.test.ts`)||t.endsWith(`.spec.ts`))||!j(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 P(e,n)||P(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(N(s,n)||N(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 N(e,t){if(D(i.join(e,t)))return!0;try{let n=r(e,{withFileTypes:!0});for(let r of n)if(r.isDirectory()&&D(i.join(e,r.name,t)))return!0}catch{}return!1}function P(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(N(s,t))return!0;if(s===r)break;let e=i.dirname(s);if(e===s)break;s=e}return!1}const F=e({meta:{type:`problem`,docs:{description:`强制执行文件命名规范`}},createOnce(e){return{before(){if(w(e.filename.replace(/\\/g,`/`).split(`/`).pop()??``))return!1},Program(t){let n=e.filename.replace(/\\/g,`/`).split(`/`).pop()??``,r=e.filename.replace(/\\/g,`/`);if(A(r,n))return;if(M(r,n).status===`component-test-found`){let e=n.replace(/\.(test|spec)\.(tsx|ts)$/,``);if(v(e)||b(e))return}let i=h.find(e=>e.pattern.test(n));i&&(S(T(n,i),i.caseStyle)||e.report({node:t,message:`文件命名违规: ${n}。${i.description}`}))}}}}),I=[`.types.ts`,`.types.d.ts`];function L(e){return I.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`)||!L(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":F,"require-type-naming":R,"require-type-suffix":Q}});export{$ as default,$ as fileNamingPlugin,F 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.18",
3
+ "version": "0.1.19",
4
4
  "license": "UNLICENSED",
5
5
  "files": [
6
6
  "dist"
@@ -35,8 +35,8 @@
35
35
  "typecheck:test": "lzi-tsgo typecheck tsconfig/test.json"
36
36
  },
37
37
  "dependencies": {
38
- "@longzai-intelligence-oxlint/core": "0.1.18",
39
- "@longzai-intelligence-oxlint/utils": "0.1.18",
38
+ "@longzai-intelligence-oxlint/core": "0.1.19",
39
+ "@longzai-intelligence-oxlint/utils": "0.1.19",
40
40
  "@oxlint/plugins": "^1.74.0"
41
41
  },
42
42
  "devDependencies": {