@longzai-intelligence-oxlint/architecture-domain-plugin 0.1.0

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 ADDED
@@ -0,0 +1,17 @@
1
+ # @longzai-intelligence-oxlint/architecture-domain-plugin
2
+
3
+ ## 意图
4
+
5
+ 在静态检查阶段约束领域层(DDD)架构:Domain 导出规范、TypeORM 导入边界、实体命名、PortAdapter 实现规范、分层目录 README 检查。
6
+
7
+ ## 定位
8
+
9
+ `architecture` 插件族的领域层子插件,承载 DDD 架构约束类规则。从原 `plugin-architecture` 单包拆分而来(ADR 0002)。
10
+
11
+ ## 规则
12
+
13
+ - `domain-export` — Domain 导出规范
14
+ - `domain-typeorm-import` — TypeORM 导入边界
15
+ - `layer-readme-required` — 分层目录 README 必需
16
+ - `require-domain-entities-naming` — 实体命名规范
17
+ - `port-adapter-implementation` — PortAdapter 实现规范
@@ -0,0 +1,17 @@
1
+ //#region src/rules/index.d.ts
2
+ /**
3
+ * domain 规则集合
4
+ */
5
+ declare const domainRules: {
6
+ 'domain-export': import("@oxlint/plugins").Rule;
7
+ 'domain-typeorm-import': import("@oxlint/plugins").Rule;
8
+ 'layer-readme-required': import("@oxlint/plugins").Rule;
9
+ 'require-domain-entities-naming': import("@oxlint/plugins").Rule;
10
+ 'port-adapter-implementation': import("@oxlint/plugins").Rule;
11
+ };
12
+ /**
13
+ * architecture-domain 子插件
14
+ */
15
+ declare const architectureDomainPlugin: import("@oxlint/plugins").Plugin;
16
+ //#endregion
17
+ export { architectureDomainPlugin, architectureDomainPlugin as default, domainRules };
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ import{defineRule as e,eslintCompatPlugin as t}from"@oxlint/plugins";import{existsSync as n}from"node:fs";import{join as r}from"node:path";const i=[{pattern:/^(?!create)[A-Za-z]+Repository(Drizzle|Memory|Sqlite|Mysql)$/,message:`具体仓储实现类应从 typeorm 入口导出`},{pattern:/[A-Z_]+_REPOSITORY_TOKEN$/,message:`Repository Token 不应从主入口导出`},{pattern:/^(?!create)[A-Za-z]*Table$/,message:`数据库表定义不应从主入口导出`},{pattern:/[A-Za-z]+OrmEntity$/,message:`ORM 实体应从 typeorm 入口导出`},{pattern:/[A-Z_]+_ORM_ENTITIES$/,message:`实体集合应从 typeorm 入口导出`},{pattern:/[A-Za-z]+OrmMapper$/,message:`ORM Mapper 不应从主入口导出`}];function a(e){let t=e.replace(/\\/g,`/`);return!!(t.includes(`/domains/`)&&t.endsWith(`/src/index.ts`)||t.includes(`/domains/`)&&t.endsWith(`/index.ts`)&&(t.includes(`/domain/`)||t.includes(`/application/`)||t.includes(`/infrastructure/`)))}function o(e){let t=e.replace(/\\/g,`/`);return a(t)&&t.split(`/`).length>6}function s(e){if(/^create|^make|^build/.test(e))return null;for(let{pattern:t,message:n}of i)if(t.test(e))return n;return null}function c(e){return e.exported.type===`Identifier`?e.exported.name:e.exported.type===`Literal`&&typeof e.exported.value==`string`?e.exported.value:null}function l(e,t){for(let n of e){if(n.type!==`ExportSpecifier`)continue;let e=c(n);if(!e)continue;let r=s(e);r&&t.report({node:n,message:`禁止导出 "${e}"。${r}。`})}}function u(e,t){if(e.declarations.length!==0){for(let n of e.declarations)if(n.id.type===`Identifier`){let e=n.id.name,r=s(e);r&&t.report({node:n,message:`禁止导出 "${e}"。${r}。`})}}}const d=e({meta:{type:`problem`,docs:{description:`检查 Domain 包入口文件的导出是否符合规范`}},createOnce(e){return{before(){let t=e.filename;if(!a(t))return!1},ExportAllDeclaration(t){e.report({node:t,message:`禁止使用 export * 通配符导出。请使用显式导出,明确导出内容。`})},ExportNamedDeclaration(t){o(e.filename)||(t.specifiers&&l(t.specifiers,e),t.declaration?.type===`VariableDeclaration`&&u(t.declaration,e))}}}}),f=[`data-source.ts`,`data-source.config.ts`,`database.module.ts`,`database.ts`,`database.config.ts`,`database.service.ts`];function p(e){return f.some(t=>e===t||e.endsWith(t))}function m(e){return e.replace(/\\/g,`/`).includes(`/domains/`)}const h=e({meta:{type:`problem`,docs:{description:`检查 Domain 包中的 TypeORM 相关导入是否符合规范`}},createOnce(e){return{before(){let t=e.filename;if(!m(t))return!1},ImportDeclaration(t){let n=t.source.type===`Literal`&&typeof t.source.value==`string`?t.source.value:null;if(!n)return;let r=e.filename.replace(/\\/g,`/`).split(`/`).pop()??``;/\/typeorm$/.test(n)&&!p(r)&&e.report({node:t,message:`禁止从 TypeORM 入口 "${n}" 导入。只有 DataSource 文件允许导入 TypeORM 入口。`}),/\/infrastructure\//.test(n)&&e.report({node:t,message:`禁止从 Infrastructure 层深度导入 "${n}"。Domain 层应通过 Contract 包的 Port 接口访问基础设施。`})}}}}),g=[{name:`apps`,severity:`error`,description:`应用层目录`},{name:`domains`,severity:`error`,description:`领域层目录`},{name:`packages`,severity:`error`,description:`技术层目录`},{name:`infrastructure`,severity:`warn`,description:`基础设施层目录`},{name:`templates`,severity:`warn`,description:`模板层目录`},{name:`docs`,severity:`warn`,description:`文档层目录`}];function _(e){return typeof e==`object`&&!!e&&`directories`in e}function v(e,t){return n(r(e,t,`README.md`))}const y=new Set,b=e({meta:{type:`problem`,docs:{description:`检查分层目录是否包含 README.md 文件`}},createOnce(e){let t=null;function i(){if(t===null){let n=e.options?.[0],r=n&&typeof n==`object`?n:{};t=_(r)&&Array.isArray(r.directories)?r.directories:g}return t}return{before(){i()},Program(t){let a=e.cwd;if(y.has(a))return;y.add(a);let o=i();for(let i of o){let o=i.name;n(r(a,o))&&(v(a,o)||((i.severity||`error`)===`warn`?e.report({node:t,message:`分层目录 "${o}" (${i.description??o}) 建议添加 README.md 文件,阐明该层职责。`}):e.report({node:t,message:`分层目录 "${o}" (${i.description??o}) 缺少 README.md 文件。分层目录必须包含 README.md 文件,阐明该层职责。`})))}}}}});function x(e){let t=e.replace(/\\/g,`/`);return t.endsWith(`.port-adapter.ts`)||t.endsWith(`.port.adapter.ts`)}function S(e){let t=e.replace(/\\/g,`/`);return t.includes(`/entities/`)&&t.endsWith(`.entity.ts`)&&t.includes(`/domain/`)&&t.includes(`/implementation/`)}function C(e){if(e.type!==`ClassDeclaration`||!(`implements`in e)||!Array.isArray(e.implements))return null;let t=e.implements;function n(e){return typeof e==`object`&&!!e&&`expression`in e}return t.every(n)?t:null}function w(e){if(`accessibility`in e){let t=e.accessibility;if(typeof t==`string`)return t}}function T(e){let t=C(e);if(!t)return!1;for(let e of t)if(e.expression.type===`Identifier`&&e.expression.name.endsWith(`Port`))return!0;return!1}const E=e({meta:{type:`problem`,docs:{description:`检查 PortAdapter 实现是否符合规范`}},createOnce(e){return{ClassDeclaration(t){x(e.filename)&&(T(t)||e.report({node:t,message:`PortAdapter 类必须实现 Port 接口。请添加 implements 语句,如 class XxxPortAdapter implements IXxxPort。`}))},MethodDefinition(t){if(!x(e.filename)||t.kind===`constructor`)return;let n=w(t);if(n===`private`||n===`protected`)return;let r=e.sourceCode.getText(t).toString();!r.includes(`Result`)&&!r.includes(`Promise<Result`)&&t.key.type===`Identifier`&&e.report({node:t,message:`PortAdapter 公共方法 "${t.key.name}" 必须返回 Result 或 Promise<Result> 类型。`})},ImportDeclaration(t){if(!S(e.filename))return;let n=t.source.type===`Literal`&&typeof t.source.value==`string`?t.source.value:null;if(!(!n||!n.endsWith(`-contract`)))for(let n of t.specifiers){if(n.type!==`ImportSpecifier`)continue;let t=n.imported.type===`Identifier`?n.imported.name:null;t&&(t.endsWith(`Props`)||t.endsWith(`Input`))&&e.report({node:n,message:`Entity 文件禁止从 Contract 包导入 "${t}"。Props 和 Input 类型属于 Contract 层,Entity 应使用自己的内部类型。`})}}}}}),D=new Set([`DOMAIN_ENTITIES`,`ALL_ENTITIES`,`ENTITIES`,`ORM_ENTITIES`]);function O(e){let t=e.replace(/\\/g,`/`);return t.includes(`/domains/`)&&t.includes(`/implementation/src/typeorm.ts`)}function k(e){return e.replace(/\\/g,`/`).match(/\/domains\/([^/]+)\/implementation\/src\/typeorm\.ts$/)?.[1]?.toUpperCase()??null}function A(e,t){let n=e.length,r=t.length,i=Array.from({length:r+1},(e,t)=>t);for(let a=1;a<=n;a++){let n=[a];for(let o=1;o<=r;o++)n[o]=e.charAt(a-1)===t.charAt(o-1)?i[o-1]??0:1+Math.min(i[o]??0,n[o-1]??0,i[o-1]??0);i=[...n]}return i[r]??0}function j(e,t){return e===t?null:e.endsWith(`_ENTITIES`)&&!e.endsWith(`_ORM_ENTITIES`)?`导出名称 "${e}" 缺少 ORM 前缀。应使用 "${t}" 而非 "${e.replace(`_ORM_ENTITIES`,`_ENTITIES`)}"`:D.has(e)?`导出名称 "${e}" 使用了通用名称。应使用具体的域名称 "${t}"`:A(e,t)<=2?`导出名称 "${e}" 可能是 "${t}" 的拼写错误`:e.toUpperCase()===t.toUpperCase()?`导出名称 "${e}" 大小写不正确。应使用 "${t}"`:`导出名称 "${e}" 不符合命名规范。应使用 "${t}"`}const M={"domain-export":d,"domain-typeorm-import":h,"layer-readme-required":b,"require-domain-entities-naming":e({meta:{type:`problem`,docs:{description:`检查 Domain 实体集合的导出命名是否符合规范`}},createOnce(e){return{before(){let t=e.filename;if(!O(t))return!1},ExportNamedDeclaration(t){let n=k(e.filename);if(!n)return;let r=`${n}_ORM_ENTITIES`;if(t.specifiers)for(let n of t.specifiers){if(n.type!==`ExportSpecifier`)continue;let t=n.exported.type===`Identifier`?n.exported.name:null;if(!t||!t.endsWith(`_ENTITIES`)&&!t.endsWith(`_ORM_ENTITIES`))continue;let i=j(t,r);i&&e.report({node:n,message:i})}if(t.declaration?.type===`VariableDeclaration`)for(let n of t.declaration.declarations){if(n.id.type!==`Identifier`)continue;let t=n.id.name;if(!t.endsWith(`_ENTITIES`)&&!t.endsWith(`_ORM_ENTITIES`))continue;let i=j(t,r);i&&e.report({node:n,message:i})}}}}}),"port-adapter-implementation":E},N=t({meta:{name:`@longzai-intelligence-oxlint/architecture-domain-plugin`},rules:M});export{N as architectureDomainPlugin,N as default,M as domainRules};
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@longzai-intelligence-oxlint/architecture-domain-plugin",
3
+ "version": "0.1.0",
4
+ "license": "UNLICENSED",
5
+ "files": [
6
+ "dist"
7
+ ],
8
+ "type": "module",
9
+ "main": "./dist/index.js",
10
+ "types": "./dist/index.d.ts",
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/index.d.ts",
14
+ "import": "./dist/index.js"
15
+ }
16
+ },
17
+ "scripts": {
18
+ "build": "lzi-builder",
19
+ "build:prod": "NODE_ENV=production lzi-builder",
20
+ "prepublishOnly": "bun run build:prod",
21
+ "dev": "lzi-builder --watch",
22
+ "clean": "rimraf dist",
23
+ "test": "vitest run",
24
+ "test:watch": "vitest",
25
+ "test:coverage": "vitest run --coverage",
26
+ "lint": "oxlint && oxfmt --check",
27
+ "lint:fix": "oxlint --fix && oxfmt",
28
+ "typecheck": "bun run typecheck:app && bun run typecheck:node && bun run typecheck:test",
29
+ "typecheck:app": "tsgo --noEmit -p tsconfig/app.json",
30
+ "typecheck:node": "tsgo --noEmit -p tsconfig/node.json",
31
+ "typecheck:test": "tsgo --noEmit -p tsconfig/test.json"
32
+ },
33
+ "dependencies": {
34
+ "@longzai-intelligence-oxlint/core": "0.1.0",
35
+ "@oxlint/plugins": "^1.74.0"
36
+ },
37
+ "devDependencies": {
38
+ "@longzai-intelligence-oxlint/testing": "0.1.0",
39
+ "@vitest/coverage-v8": "^4.1.10",
40
+ "typescript": "^6.0.3"
41
+ },
42
+ "peerDependencies": {
43
+ "oxlint": ">=1.0.0"
44
+ }
45
+ }