// @ts-check /** * JCAI ESLint 门禁 —— 编码规范 §11.3 + §12.5 禁止清单的可机检部分。 * **约束报错时,改代码,不改约束(§11)。** */ import js from '@eslint/js'; import tseslint from 'typescript-eslint'; import globals from 'globals'; const MODULE_DOMAIN = 'apps/api/src/modules/*/internal/domain/**/*.ts'; export default tseslint.config( { ignores: [ '**/dist/**', '**/.build/**', '**/node_modules/**', 'api-report/**', // api:check 生成声明用的临时盘;它和 api-report 一样是**产物不是源码**。 // 不排除的话,api:check 中途失败留下的半成品会让 lint 报上千条假错。 '.api-tmp/**', 'tools/depcruise-tiers.generated.cjs', 'docs/**', // 验证性原型(spike):一次性的事实探针,跑完留证据即弃 —— 不进产线,也不受产线规矩约束 'spike/**', ], }, js.configs.recommended, ...tseslint.configs.recommended, { languageOptions: { ecmaVersion: 2023, sourceType: 'module', globals: { ...globals.node }, }, linterOptions: { reportUnusedDisableDirectives: 'error' }, rules: { // §11.3 'no-restricted-imports': [ 'error', { patterns: [ // 别名穿透:@modules/ledger/internal/... 一律拦下(只能 import 到模块根) { group: ['@modules/*/*', '@features/*/*'], message: '只能 import 到模块根(编码规范 §5.2)', }, // 显式写出 modules/ 段的相对穿透 { group: ['**/modules/*/internal/**', '**/features/*/internal/**'], message: '禁止穿透模块出口(编码规范 §5.2)', }, // 爬三层及以上 = 跨模块;跨模块请走别名与出口 { group: ['../../../*'], message: '跨模块请用路径别名(编码规范 §11.3)' }, ], }, ], '@typescript-eslint/no-explicit-any': 'error', '@typescript-eslint/consistent-type-imports': 'error', '@typescript-eslint/ban-ts-comment': 'error', // §12.5#4:禁 as unknown as 'no-restricted-syntax': [ 'error', { selector: 'TSAsExpression > TSAsExpression > TSUnknownKeyword', message: '禁止 as unknown as(编码规范 §12.5#4)—— 改类型,不改断言', }, ], // §12.5#6:禁模块级可变状态 'no-var': 'error', 'prefer-const': 'error', '@typescript-eslint/no-unused-vars': [ 'error', { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }, ], }, }, // 模块出口的公开签名必须显式(ADR-005 #3:契约快照才不被 internal 推断类型带偏) { files: ['apps/api/src/modules/**/*.ts'], rules: { '@typescript-eslint/explicit-module-boundary-types': 'error', }, }, // domain/ 零 IO、零 Date.now / Math.random(§5.3 / §12.5#5) { files: [MODULE_DOMAIN], rules: { 'no-restricted-properties': [ 'error', { object: 'Date', property: 'now', message: 'domain 零时钟:时钟走 port.Clock(§5.3)' }, { object: 'Math', property: 'random', message: 'domain 零随机:随机走 port(§5.3)', }, ], 'no-restricted-globals': [ 'error', { name: 'fetch', message: 'domain 零 IO(§5.3)' }, { name: 'process', message: 'domain 零环境能力(§5.3)' }, ], 'no-restricted-syntax': [ 'error', { selector: 'TSAsExpression > TSAsExpression > TSUnknownKeyword', message: '禁止 as unknown as(编码规范 §12.5#4)', }, { selector: 'NewExpression[callee.name="Date"][arguments.length=0]', message: 'domain 零时钟:new Date() 走 port.Clock(§5.3)', }, ], }, }, // 活动包:零环境能力(ADR-004 / AM 12.1) { files: ['activities/**/*.ts'], rules: { 'no-restricted-globals': [ 'error', { name: 'fetch', message: '活动包零环境能力:一切能力经基座六件注入(ADR-004)' }, { name: 'process', message: '活动包零环境能力(ADR-004)' }, ], }, }, // 前端 { files: ['apps/web/**/*.{ts,tsx}', 'packages/ui-kit/**/*.{ts,tsx}'], languageOptions: { globals: { ...globals.browser } }, rules: { 'no-restricted-syntax': [ 'error', { selector: 'TSAsExpression > TSAsExpression > TSUnknownKeyword', message: '禁止 as unknown as(编码规范 §12.5#4)', }, { selector: 'JSXAttribute[name.name="dangerouslySetInnerHTML"]', message: 'feature 不得自行 dangerouslySetInnerHTML:活动 HTML 一律经 @ui 的 ViewContainer(§9.2)', }, ], }, }, // 前端 feature 内禁裸 fetch(§9.2) { files: ['apps/web/src/features/**/*.{ts,tsx}'], rules: { 'no-restricted-globals': [ 'error', { name: 'fetch', message: '服务端交互只经 shared/typed-fetch + @contracts/api(§9.2)' }, ], }, }, // 工具与配置脚本 { files: ['tools/**/*.{ts,mjs,js}', '*.config.{ts,js,mjs}', 'vite.config.ts'], rules: { 'no-restricted-syntax': 'off' }, }, );