update
This commit is contained in:
140
.eslintrc.cjs
Normal file
140
.eslintrc.cjs
Normal file
@@ -0,0 +1,140 @@
|
||||
/**
|
||||
* ESLint 配置 —— 只 lint renderer 业务代码(src/renderer, src/shared, src/preload)。
|
||||
*
|
||||
* 不 lint src/main:那里是 Electron 主进程,跑的是 Node 18 的能力,规则集合不一样,
|
||||
* 引入会让 CI 噪声很大、价值很低。先把 renderer 这一层质量守住。
|
||||
*
|
||||
* 主要规则选型:
|
||||
* - "@typescript-eslint/recommended-type-checked" 启用类型感知规则,副作用比 no-type-checked 系列少。
|
||||
* - react/jsx-runtime(自动从 react/jsx-runtime 引入),不用 import React。
|
||||
* - jsx-a11y 全套,覆盖 ARIA、键盘、对比度前的标签语义(颜色用 tailwind,不在 a11y 范围里)。
|
||||
* - 自定义规则:
|
||||
* * no-restricted-syntax 禁止 console.* 在 src 代码里残留(要日志走 electron log);
|
||||
* * no-restricted-imports 禁掉 cross-process import(renderer 永远不该 import src/main)。
|
||||
*
|
||||
* 故意没开:prettier(用 prettier --check)、import/order(争论太多收益太小)。
|
||||
*/
|
||||
module.exports = {
|
||||
root: true,
|
||||
env: { browser: true, es2022: true, node: true },
|
||||
parser: '@typescript-eslint/parser',
|
||||
parserOptions: {
|
||||
ecmaVersion: 'latest',
|
||||
sourceType: 'module',
|
||||
ecmaFeatures: { jsx: true },
|
||||
project: ['./tsconfig.json'],
|
||||
tsconfigRootDir: __dirname
|
||||
},
|
||||
settings: { react: { version: 'detect' } },
|
||||
extends: [
|
||||
'eslint:recommended',
|
||||
'plugin:@typescript-eslint/recommended-type-checked',
|
||||
'plugin:react/recommended',
|
||||
'plugin:react/jsx-runtime',
|
||||
'plugin:react-hooks/recommended',
|
||||
'plugin:jsx-a11y/recommended'
|
||||
],
|
||||
plugins: ['@typescript-eslint', 'react', 'react-hooks', 'jsx-a11y'],
|
||||
ignorePatterns: [
|
||||
'out/**',
|
||||
'node_modules/**',
|
||||
'dist/**',
|
||||
'release/**',
|
||||
'coverage/**',
|
||||
'test-results/**',
|
||||
'playwright-report/**',
|
||||
'*.config.cjs',
|
||||
'*.config.js',
|
||||
'*.config.ts',
|
||||
'.eslintrc.cjs',
|
||||
'tailwind.config.cjs',
|
||||
'postcss.config.cjs'
|
||||
],
|
||||
rules: {
|
||||
// 显式 any 在这种规模的代码里其实有必要 —— @typescript-eslint/no-explicit-any 在 type-checked
|
||||
// 模式下噪声太大(每个 IpcError catch 都会撞)。先关掉,等真有滥用时再开。
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
// ts-ignore 用一行注释比挨个枚举类型便宜;只在违规真的不可避免时才允许
|
||||
'@typescript-eslint/ban-ts-comment': ['warn', { 'ts-ignore': 'allow-with-description' }],
|
||||
// React 17+ 没用 React.* API,没必要强制默认写 React import
|
||||
'react/react-in-jsx-scope': 'off',
|
||||
'react/prop-types': 'off',
|
||||
// 性能优化 / a11y 都需要 conditional roles(比如 isSelected ? 'true' : undefined)
|
||||
'react/jsx-boolean-value': ['warn', 'never'],
|
||||
// hooks 依赖数组:我们有 useEffect 用 useRef 当 token 守门,react-hooks 会误报
|
||||
'react-hooks/exhaustive-deps': 'warn',
|
||||
// a11y 误报:role="button" 在 <tr>/<g> 上是正确做法;jsx-a11y v6 仍把它当 interactive
|
||||
// 控件要求 tabIndex,规则偶尔会和组件本身定的 roving tabindex 打架 → warn 不 block
|
||||
'jsx-a11y/no-noninteractive-element-interactions': 'warn',
|
||||
'jsx-a11y/click-events-have-key-events': 'warn',
|
||||
// src/shared 是纯类型和常量,应该优先用 type 而不是 interface —— 同名 interface 合并
|
||||
// 在 React 项目里几乎总是误用
|
||||
'@typescript-eslint/consistent-type-imports': ['warn', { prefer: 'type-imports' }],
|
||||
// onClick={async () => await foo()} 在 React 里是常见且正确的写法 —— React 会忽略返回
|
||||
// 的 Promise(这是 React 的设计缺陷,但用 ESLint 卡它没有收益,只会让所有按钮代码都
|
||||
// 包一层 void 包装)。同理表单 onSubmit。
|
||||
'@typescript-eslint/no-misused-promises': [
|
||||
'error',
|
||||
{ checksVoidReturn: { arguments: false, attributes: false } }
|
||||
],
|
||||
|
||||
'no-restricted-syntax': [
|
||||
'warn',
|
||||
{
|
||||
// renderer 里允许 console.error 用于诊断,但不允许 console.log —— 那是 debug 残留
|
||||
selector: "CallExpression[callee.object.name='console'][callee.property.name!=/^(error|warn)$/]",
|
||||
message: 'renderer 进程不要留 console.log/warn;日志走 main 进程的 electron-log。'
|
||||
}
|
||||
],
|
||||
'no-restricted-imports': [
|
||||
'error',
|
||||
{
|
||||
// 阻止 renderer 跨进程边界拉主进程代码 —— 一旦发生,contextBridge 的隔离就破了
|
||||
patterns: [
|
||||
{
|
||||
group: ['**/src/main/**', '**/main/**'],
|
||||
message: 'renderer 不允许 import src/main/;走 preload 暴露的 IPC 接口。'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
// 测试文件允许 any 和未使用变量(fixture/dummy 经常违反)。console.log 也允许——
|
||||
// 测试期间的诊断输出没法走 electron-log。require-await 在 mock fn 里大量误报
|
||||
// (vi.fn().mockResolvedValue 总是返回 Promise,写成 async 是统一形式):
|
||||
files: ['**/*.test.{ts,tsx}', '**/__tests__/**', 'e2e/**'],
|
||||
rules: {
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
'@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }],
|
||||
'no-restricted-syntax': 'off',
|
||||
'@typescript-eslint/require-await': 'off'
|
||||
}
|
||||
},
|
||||
{
|
||||
// e2e 测试禁止固定 sleep:waitForTimeout(>=1000) 几乎一定意味着「等某件事完成」,
|
||||
// 而这件事可以用 web-first 断言(expect().toBeVisible/toHaveText/toPass)
|
||||
// 表达成「等某件事真的发生」。固定 sleep 在 CI 慢的时候假阴、快的机器上又假阳。
|
||||
// 例外:≤50ms 紧跟在 keyboard.press / type 后面,给 DOM 派发的事件循环时间,
|
||||
// 这种替代方案不如直接 sleep 直观。
|
||||
files: ['e2e/**'],
|
||||
rules: {
|
||||
'no-restricted-syntax': [
|
||||
'error',
|
||||
{
|
||||
selector:
|
||||
"CallExpression[callee.object.name='page'][callee.property.name='waitForTimeout'][arguments.0.value>=1000]",
|
||||
message:
|
||||
'禁止固定 sleep >=1s。用 web-first 断言代替(expect.toBeVisible/toPass 等),CI 慢机器才不会假阴、快的机器才不会假阳。'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
files: ['src/preload/**'],
|
||||
// preload 在沙箱里跑,没有 Node DOM,但有 Node 全局
|
||||
env: { browser: false, node: true }
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user