@infra-x/create-eslint-config 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/dist/index.mjs +40 -0
- package/dist/template/README.md +56 -0
- package/dist/template/eslint.config.mts +15 -0
- package/dist/template/package.json +62 -0
- package/dist/template/src/configs/a11y.ts +26 -0
- package/dist/template/src/configs/boundaries.ts +43 -0
- package/dist/template/src/configs/depend.ts +37 -0
- package/dist/template/src/configs/ignores.ts +78 -0
- package/dist/template/src/configs/imports.ts +91 -0
- package/dist/template/src/configs/javascript.ts +36 -0
- package/dist/template/src/configs/jsdoc.ts +24 -0
- package/dist/template/src/configs/nextjs.ts +24 -0
- package/dist/template/src/configs/package-json.ts +32 -0
- package/dist/template/src/configs/prettier.ts +25 -0
- package/dist/template/src/configs/react.ts +32 -0
- package/dist/template/src/configs/storybook.ts +22 -0
- package/dist/template/src/configs/stylistic.ts +37 -0
- package/dist/template/src/configs/tailwind.ts +32 -0
- package/dist/template/src/configs/typescript.ts +51 -0
- package/dist/template/src/configs/unicorn.ts +25 -0
- package/dist/template/src/configs/vitest.ts +56 -0
- package/dist/template/src/index.ts +204 -0
- package/dist/template/src/types.ts +152 -0
- package/dist/template/src/utils.ts +80 -0
- package/dist/template/tsconfig.json +7 -0
- package/package.json +39 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { access, cp, readdir } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { confirm, intro, log, outro } from "@clack/prompts";
|
|
6
|
+
|
|
7
|
+
//#region src/index.ts
|
|
8
|
+
async function getConflicts(templateDir$1, destDir$1) {
|
|
9
|
+
const entries = await readdir(templateDir$1, {
|
|
10
|
+
recursive: true,
|
|
11
|
+
withFileTypes: true
|
|
12
|
+
});
|
|
13
|
+
const conflicts$1 = [];
|
|
14
|
+
for (const entry of entries) {
|
|
15
|
+
if (!entry.isFile()) continue;
|
|
16
|
+
const rel = path.join(entry.parentPath, entry.name).slice(templateDir$1.length + 1);
|
|
17
|
+
const destPath = path.join(destDir$1, rel);
|
|
18
|
+
try {
|
|
19
|
+
await access(destPath);
|
|
20
|
+
conflicts$1.push(rel);
|
|
21
|
+
} catch {}
|
|
22
|
+
}
|
|
23
|
+
return conflicts$1;
|
|
24
|
+
}
|
|
25
|
+
intro("@infra-x/create-eslint-config");
|
|
26
|
+
const templateDir = path.join(fileURLToPath(new URL(".", import.meta.url)), "template");
|
|
27
|
+
const destDir = path.join(process.cwd(), "eslint-config");
|
|
28
|
+
const conflicts = await getConflicts(templateDir, destDir);
|
|
29
|
+
if (conflicts.length > 0) {
|
|
30
|
+
log.warn(`以下文件已存在:\n${conflicts.map((f) => ` ${f}`).join("\n")}`);
|
|
31
|
+
if (!await confirm({ message: "是否覆盖这些文件?" })) {
|
|
32
|
+
outro("已取消");
|
|
33
|
+
process.exit(0);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
await cp(templateDir, destDir, { recursive: true });
|
|
37
|
+
outro("复制完成!");
|
|
38
|
+
|
|
39
|
+
//#endregion
|
|
40
|
+
export { };
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# @workspace/eslint-config
|
|
2
|
+
|
|
3
|
+
统一基础设施配置工具 - 集成 ESLint、Prettier、TypeScript、EditorConfig
|
|
4
|
+
|
|
5
|
+
## 安装
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm add -D @workspace/eslint-config eslint prettier typescript
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## 使用
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
// eslint.config.mts
|
|
15
|
+
import { composeConfig } from '@workspace/eslint-config'
|
|
16
|
+
|
|
17
|
+
export default composeConfig({
|
|
18
|
+
typescript: { tsconfigRootDir: import.meta.dirname },
|
|
19
|
+
react: true,
|
|
20
|
+
imports: { typescript: true },
|
|
21
|
+
prettier: true,
|
|
22
|
+
})
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## 配置项
|
|
26
|
+
|
|
27
|
+
### 默认开启
|
|
28
|
+
|
|
29
|
+
- `ignores` - 忽略文件配置
|
|
30
|
+
- `javascript` - JavaScript 基础规则
|
|
31
|
+
- `typescript` - TypeScript 规则
|
|
32
|
+
- `stylistic` - 代码风格规则
|
|
33
|
+
- `unicorn` - 最佳实践
|
|
34
|
+
- `depend` - 依赖优化建议
|
|
35
|
+
|
|
36
|
+
### 可选配置
|
|
37
|
+
|
|
38
|
+
- `react` - React 规则
|
|
39
|
+
- `nextjs` - Next.js 规则
|
|
40
|
+
- `imports` - Import 排序和规则
|
|
41
|
+
- `prettier` - Prettier 格式化
|
|
42
|
+
- `a11y` - 无障碍访问规则
|
|
43
|
+
- `jsdoc` - JSDoc 文档规则
|
|
44
|
+
- `boundaries` - 模块边界规则
|
|
45
|
+
- `packageJson` - package.json 规则
|
|
46
|
+
- `vitest` - Vitest 测试规则
|
|
47
|
+
- `storybook` - Storybook 规则
|
|
48
|
+
|
|
49
|
+
每个配置项支持:
|
|
50
|
+
- `true` - 使用默认配置
|
|
51
|
+
- `{ ...options }` - 自定义配置
|
|
52
|
+
- `false` - 禁用(仅限默认开启的配置)
|
|
53
|
+
|
|
54
|
+
## License
|
|
55
|
+
|
|
56
|
+
PROPRIETARY
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { composeConfig } from './src/index'
|
|
2
|
+
|
|
3
|
+
import type { Linter } from 'eslint'
|
|
4
|
+
|
|
5
|
+
const config: Linter.Config[] = composeConfig({
|
|
6
|
+
typescript: { tsconfigRootDir: import.meta.dirname },
|
|
7
|
+
imports: {
|
|
8
|
+
typescript: true,
|
|
9
|
+
overrides: {
|
|
10
|
+
// 本包内部使用相对导入,因为没有配置路径别名
|
|
11
|
+
'no-restricted-imports': 'off',
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
})
|
|
15
|
+
export default config
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@workspace/eslint-config",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"description": "A shared ESLint config for infra-x projects.",
|
|
6
|
+
"author": "infra-x",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": "./src/index.ts",
|
|
10
|
+
"./package.json": "./package.json"
|
|
11
|
+
},
|
|
12
|
+
"main": "./src/index.ts",
|
|
13
|
+
"types": "./src/index.ts",
|
|
14
|
+
"files": [
|
|
15
|
+
"src"
|
|
16
|
+
],
|
|
17
|
+
"scripts": {
|
|
18
|
+
"test": "vitest",
|
|
19
|
+
"typecheck": "tsc --noEmit"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@types/eslint-plugin-jsx-a11y": "^6.10.1",
|
|
23
|
+
"@types/node": "^25.0.3",
|
|
24
|
+
"eslint": "^9.39.2",
|
|
25
|
+
"globals": "16.5.0",
|
|
26
|
+
"jiti": "2.6.1",
|
|
27
|
+
"prettier": "^3.7.4",
|
|
28
|
+
"typescript": "^5.9.3",
|
|
29
|
+
"vitest": "^4.0.18"
|
|
30
|
+
},
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"@eslint-react/eslint-plugin": "^2.11.0",
|
|
33
|
+
"@eslint/compat": "1.4.1",
|
|
34
|
+
"@eslint/core": "0.17.0",
|
|
35
|
+
"@eslint/js": "^9.39.2",
|
|
36
|
+
"@next/eslint-plugin-next": "^16.1.6",
|
|
37
|
+
"@stylistic/eslint-plugin": "^5.7.1",
|
|
38
|
+
"@vitest/eslint-plugin": "^1.6.6",
|
|
39
|
+
"eslint-config-prettier": "^10.1.8",
|
|
40
|
+
"eslint-import-resolver-typescript": "^4.4.4",
|
|
41
|
+
"eslint-plugin-better-tailwindcss": "^4.1.1",
|
|
42
|
+
"eslint-plugin-boundaries": "^5.4.0",
|
|
43
|
+
"eslint-plugin-depend": "^1.4.0",
|
|
44
|
+
"eslint-plugin-import-x": "4.16.1",
|
|
45
|
+
"eslint-plugin-jsdoc": "^61.7.1",
|
|
46
|
+
"eslint-plugin-jsx-a11y": "^6.10.2",
|
|
47
|
+
"eslint-plugin-package-json": "^0.64.0",
|
|
48
|
+
"eslint-plugin-prettier": "^5.5.5",
|
|
49
|
+
"eslint-plugin-react-hooks": "^7.0.1",
|
|
50
|
+
"eslint-plugin-react-refresh": "^0.4.26",
|
|
51
|
+
"eslint-plugin-storybook": "^10.2.7",
|
|
52
|
+
"eslint-plugin-unicorn": "^57.0.0",
|
|
53
|
+
"typescript-eslint": "^8.54.0"
|
|
54
|
+
},
|
|
55
|
+
"peerDependencies": {
|
|
56
|
+
"eslint": "^9.39.2",
|
|
57
|
+
"globals": "16.5.0",
|
|
58
|
+
"jiti": "2.6.1",
|
|
59
|
+
"prettier": "^3.7.4",
|
|
60
|
+
"typescript": "^5.9.3"
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* eslint-plugin-jsx-a11y 配置,用于检测 JSX 中的无障碍问题(WCAG 标准)
|
|
3
|
+
*/
|
|
4
|
+
import { defineConfig } from 'eslint/config'
|
|
5
|
+
import jsxA11y from 'eslint-plugin-jsx-a11y'
|
|
6
|
+
|
|
7
|
+
import { GLOB_JSX } from '../utils'
|
|
8
|
+
|
|
9
|
+
import type { A11yOptions } from '../types'
|
|
10
|
+
import type { Linter } from 'eslint'
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* 可访问性规则配置
|
|
14
|
+
*/
|
|
15
|
+
export function a11y(options: A11yOptions = {}): Linter.Config[] {
|
|
16
|
+
const { files = [GLOB_JSX], overrides = {} } = options
|
|
17
|
+
|
|
18
|
+
return defineConfig({
|
|
19
|
+
name: 'a11y/rules',
|
|
20
|
+
files,
|
|
21
|
+
extends: [jsxA11y.flatConfigs.recommended],
|
|
22
|
+
rules: {
|
|
23
|
+
...overrides,
|
|
24
|
+
},
|
|
25
|
+
})
|
|
26
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* eslint-plugin-boundaries 配置,用于强制执行架构边界规则
|
|
3
|
+
*/
|
|
4
|
+
import { defineConfig } from 'eslint/config'
|
|
5
|
+
import boundariesPlugin from 'eslint-plugin-boundaries'
|
|
6
|
+
|
|
7
|
+
import { GLOB_SRC } from '../utils'
|
|
8
|
+
|
|
9
|
+
import type { BoundariesOptions } from '../types'
|
|
10
|
+
import type { Linter } from 'eslint'
|
|
11
|
+
|
|
12
|
+
export function boundaries(options: BoundariesOptions = {}): Linter.Config[] {
|
|
13
|
+
const { elements, rules, files = [GLOB_SRC], overrides = {} } = options
|
|
14
|
+
|
|
15
|
+
if (!elements?.length || !rules?.length) return []
|
|
16
|
+
|
|
17
|
+
return defineConfig([
|
|
18
|
+
{
|
|
19
|
+
name: 'boundaries/rules',
|
|
20
|
+
files,
|
|
21
|
+
plugins: {
|
|
22
|
+
boundaries: boundariesPlugin,
|
|
23
|
+
},
|
|
24
|
+
settings: {
|
|
25
|
+
'boundaries/elements': elements,
|
|
26
|
+
},
|
|
27
|
+
rules: {
|
|
28
|
+
'boundaries/element-types': [
|
|
29
|
+
'error',
|
|
30
|
+
{
|
|
31
|
+
default: 'disallow',
|
|
32
|
+
rules: rules.map((rule) => ({
|
|
33
|
+
from: Array.isArray(rule.from) ? rule.from : [rule.from],
|
|
34
|
+
allow: rule.allow,
|
|
35
|
+
message: rule.message,
|
|
36
|
+
})),
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
...overrides,
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
])
|
|
43
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 依赖优化规则配置,检测可用原生 API 替代的包和微型工具库
|
|
3
|
+
*/
|
|
4
|
+
import { fixupPluginRules } from '@eslint/compat'
|
|
5
|
+
import dependPlugin from 'eslint-plugin-depend'
|
|
6
|
+
|
|
7
|
+
import { GLOB_SRC } from '../utils'
|
|
8
|
+
|
|
9
|
+
import type { DependOptions } from '../types'
|
|
10
|
+
import type { ESLint, Linter } from 'eslint'
|
|
11
|
+
|
|
12
|
+
const DEFAULT_PRESETS: DependOptions['presets'] = ['native', 'microutilities', 'preferred']
|
|
13
|
+
|
|
14
|
+
export function depend(options: DependOptions = {}): Linter.Config[] {
|
|
15
|
+
const { presets = DEFAULT_PRESETS, modules = [], allowed = [], overrides = {} } = options
|
|
16
|
+
|
|
17
|
+
return [
|
|
18
|
+
{
|
|
19
|
+
name: 'depend/rules',
|
|
20
|
+
files: [GLOB_SRC],
|
|
21
|
+
plugins: {
|
|
22
|
+
depend: fixupPluginRules(dependPlugin as unknown as ESLint.Plugin),
|
|
23
|
+
},
|
|
24
|
+
rules: {
|
|
25
|
+
'depend/ban-dependencies': [
|
|
26
|
+
'error',
|
|
27
|
+
{
|
|
28
|
+
presets,
|
|
29
|
+
modules,
|
|
30
|
+
allowed,
|
|
31
|
+
},
|
|
32
|
+
],
|
|
33
|
+
...overrides,
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
]
|
|
37
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ESLint 忽略文件配置,支持默认忽略规则和 .gitignore 导入
|
|
3
|
+
*/
|
|
4
|
+
import { existsSync } from 'node:fs'
|
|
5
|
+
import path from 'node:path'
|
|
6
|
+
|
|
7
|
+
import { includeIgnoreFile } from '@eslint/compat'
|
|
8
|
+
|
|
9
|
+
import type { IgnoresOptions } from '../types'
|
|
10
|
+
import type { Linter } from 'eslint'
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* 默认忽略的文件和目录
|
|
14
|
+
*/
|
|
15
|
+
export const DEFAULT_IGNORES: string[] = [
|
|
16
|
+
// 依赖目录
|
|
17
|
+
'**/node_modules/**',
|
|
18
|
+
'**/.pnp.*',
|
|
19
|
+
|
|
20
|
+
// 构建产物
|
|
21
|
+
'**/dist/**',
|
|
22
|
+
'**/build/**',
|
|
23
|
+
'**/out/**',
|
|
24
|
+
'**/.next/**',
|
|
25
|
+
|
|
26
|
+
// 缓存目录
|
|
27
|
+
'**/.cache/**',
|
|
28
|
+
'**/.turbo/**',
|
|
29
|
+
'**/.eslintcache',
|
|
30
|
+
|
|
31
|
+
// 版本控制
|
|
32
|
+
'**/.git/**',
|
|
33
|
+
'**/.svn/**',
|
|
34
|
+
'**/.hg/**',
|
|
35
|
+
'**/public/**',
|
|
36
|
+
|
|
37
|
+
// 类型文件
|
|
38
|
+
'**/*.d.ts',
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
export function ignores(options: IgnoresOptions = {}): Linter.Config[] {
|
|
42
|
+
const { ignores: userIgnores, gitignore: gitignorePath } = options
|
|
43
|
+
const configs: Linter.Config[] = []
|
|
44
|
+
|
|
45
|
+
if (gitignorePath !== false) {
|
|
46
|
+
try {
|
|
47
|
+
const gitignoreFile
|
|
48
|
+
= typeof gitignorePath === 'string'
|
|
49
|
+
? path.resolve(gitignorePath)
|
|
50
|
+
: path.resolve(process.cwd(), '.gitignore')
|
|
51
|
+
|
|
52
|
+
if (existsSync(gitignoreFile)) {
|
|
53
|
+
configs.push(includeIgnoreFile(gitignoreFile))
|
|
54
|
+
}
|
|
55
|
+
} catch {
|
|
56
|
+
//
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const finalIgnores
|
|
61
|
+
= userIgnores === false
|
|
62
|
+
? []
|
|
63
|
+
: (userIgnores
|
|
64
|
+
? [...DEFAULT_IGNORES, ...userIgnores]
|
|
65
|
+
: DEFAULT_IGNORES)
|
|
66
|
+
|
|
67
|
+
if (finalIgnores.length > 0) {
|
|
68
|
+
configs.push({
|
|
69
|
+
name: 'defaults',
|
|
70
|
+
ignores: finalIgnores,
|
|
71
|
+
})
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return configs.map((config) => ({
|
|
75
|
+
...config,
|
|
76
|
+
name: `ignores/globals/${config.name}`,
|
|
77
|
+
}))
|
|
78
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Import 模块导入规则配置,提供导入排序、循环依赖检测和 TypeScript 支持
|
|
3
|
+
*/
|
|
4
|
+
import { defineConfig } from 'eslint/config'
|
|
5
|
+
import { importX } from 'eslint-plugin-import-x'
|
|
6
|
+
|
|
7
|
+
import { GLOB_SRC } from '../utils'
|
|
8
|
+
|
|
9
|
+
import type { ImportsOptions } from '../types'
|
|
10
|
+
import type { ESLint, Linter } from 'eslint'
|
|
11
|
+
|
|
12
|
+
export function imports(options: ImportsOptions = {}): Linter.Config[] {
|
|
13
|
+
const { overrides = {}, stylistic = true, typescript = false, noRelativeParentImports = false } = options
|
|
14
|
+
|
|
15
|
+
const files = [GLOB_SRC]
|
|
16
|
+
|
|
17
|
+
const settingsForTypescript = typescript
|
|
18
|
+
? {
|
|
19
|
+
...importX.configs['flat/recommended'].settings,
|
|
20
|
+
'import-x/resolver': { typescript: { alwaysTryTypes: true } },
|
|
21
|
+
}
|
|
22
|
+
: {}
|
|
23
|
+
|
|
24
|
+
const rulesForTypescript = typescript
|
|
25
|
+
? {
|
|
26
|
+
...importX.configs['flat/typescript'].rules,
|
|
27
|
+
'import-x/named': 'off',
|
|
28
|
+
'import-x/namespace': 'off',
|
|
29
|
+
'import-x/default': 'off',
|
|
30
|
+
'import-x/no-named-as-default-member': 'off',
|
|
31
|
+
'import-x/no-unresolved': 'off',
|
|
32
|
+
}
|
|
33
|
+
: {}
|
|
34
|
+
|
|
35
|
+
const rulesForStylistic: Record<string, Linter.RuleEntry> = stylistic
|
|
36
|
+
? {
|
|
37
|
+
'import-x/newline-after-import': ['error', { count: 1 }],
|
|
38
|
+
'import-x/order': [
|
|
39
|
+
'error',
|
|
40
|
+
{
|
|
41
|
+
'groups': [
|
|
42
|
+
'builtin',
|
|
43
|
+
'external',
|
|
44
|
+
'internal',
|
|
45
|
+
['parent', 'sibling'],
|
|
46
|
+
'index',
|
|
47
|
+
'type',
|
|
48
|
+
],
|
|
49
|
+
'newlines-between': 'always',
|
|
50
|
+
'alphabetize': {
|
|
51
|
+
order: 'asc',
|
|
52
|
+
caseInsensitive: true,
|
|
53
|
+
},
|
|
54
|
+
'pathGroups': [
|
|
55
|
+
{
|
|
56
|
+
pattern: '@/**',
|
|
57
|
+
group: 'internal',
|
|
58
|
+
position: 'before',
|
|
59
|
+
},
|
|
60
|
+
],
|
|
61
|
+
'pathGroupsExcludedImportTypes': ['type'],
|
|
62
|
+
'distinctGroup': true,
|
|
63
|
+
},
|
|
64
|
+
],
|
|
65
|
+
}
|
|
66
|
+
: {}
|
|
67
|
+
|
|
68
|
+
return defineConfig([
|
|
69
|
+
{
|
|
70
|
+
name: 'imports/rules',
|
|
71
|
+
files,
|
|
72
|
+
plugins: {
|
|
73
|
+
'import-x': importX as unknown as ESLint.Plugin,
|
|
74
|
+
},
|
|
75
|
+
settings: settingsForTypescript,
|
|
76
|
+
rules: {
|
|
77
|
+
...importX.configs['flat/recommended'].rules,
|
|
78
|
+
...rulesForTypescript,
|
|
79
|
+
...rulesForStylistic,
|
|
80
|
+
'import-x/consistent-type-specifier-style': 'error',
|
|
81
|
+
'import-x/no-named-as-default': 'warn',
|
|
82
|
+
'import-x/no-cycle': 'error',
|
|
83
|
+
'import-x/no-unused-modules': 'error',
|
|
84
|
+
'import-x/no-deprecated': 'warn',
|
|
85
|
+
'import-x/no-extraneous-dependencies': 'error',
|
|
86
|
+
'import-x/no-relative-parent-imports': noRelativeParentImports ? 'error' : 'off',
|
|
87
|
+
...overrides,
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
])
|
|
91
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JavaScript ESLint 基础配置,提供推荐规则和 ES2026+ 全局变量支持
|
|
3
|
+
*/
|
|
4
|
+
import js from '@eslint/js'
|
|
5
|
+
import { defineConfig } from 'eslint/config'
|
|
6
|
+
import globals from 'globals'
|
|
7
|
+
|
|
8
|
+
import { GLOB_SRC } from '../utils'
|
|
9
|
+
|
|
10
|
+
import type { JavaScriptOptions } from '../types'
|
|
11
|
+
import type { Linter } from 'eslint'
|
|
12
|
+
|
|
13
|
+
export function javascript(options: JavaScriptOptions = {}): Linter.Config[] {
|
|
14
|
+
const { files = [GLOB_SRC], overrides = {} } = options
|
|
15
|
+
|
|
16
|
+
return defineConfig({
|
|
17
|
+
name: 'javascript/rules',
|
|
18
|
+
files,
|
|
19
|
+
extends: [js.configs.recommended],
|
|
20
|
+
languageOptions: {
|
|
21
|
+
ecmaVersion: 'latest',
|
|
22
|
+
globals: {
|
|
23
|
+
...globals.es2026,
|
|
24
|
+
},
|
|
25
|
+
parserOptions: {
|
|
26
|
+
ecmaFeatures: {
|
|
27
|
+
jsx: true,
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
sourceType: 'module',
|
|
31
|
+
},
|
|
32
|
+
rules: {
|
|
33
|
+
...overrides,
|
|
34
|
+
},
|
|
35
|
+
})
|
|
36
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSDoc 注释规范配置,使用 TypeScript 优化的推荐规则验证注释正确性
|
|
3
|
+
*/
|
|
4
|
+
import { defineConfig } from 'eslint/config'
|
|
5
|
+
import jsdocPlugin from 'eslint-plugin-jsdoc'
|
|
6
|
+
|
|
7
|
+
import type { JsdocOptions } from '../types'
|
|
8
|
+
import type { Linter } from 'eslint'
|
|
9
|
+
|
|
10
|
+
export function jsdoc(options: JsdocOptions = {}): Linter.Config[] {
|
|
11
|
+
const { overrides = {} } = options
|
|
12
|
+
|
|
13
|
+
return defineConfig([
|
|
14
|
+
{
|
|
15
|
+
name: 'jsdoc/rules',
|
|
16
|
+
extends: [jsdocPlugin.configs['flat/contents-typescript']],
|
|
17
|
+
rules: {
|
|
18
|
+
'jsdoc/match-description': 'off',
|
|
19
|
+
'jsdoc/informative-docs': 'off',
|
|
20
|
+
...overrides,
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
])
|
|
24
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Next.js ESLint 配置,使用官方插件检测 Next.js 最佳实践和 Core Web Vitals 性能优化
|
|
3
|
+
*/
|
|
4
|
+
import nextjsPlugin from '@next/eslint-plugin-next'
|
|
5
|
+
import { defineConfig } from 'eslint/config'
|
|
6
|
+
|
|
7
|
+
import type { NextjsOptions } from '../types'
|
|
8
|
+
import type { Linter } from 'eslint'
|
|
9
|
+
|
|
10
|
+
const recommended = nextjsPlugin.configs.recommended as unknown as Linter.Config
|
|
11
|
+
const coreWebVitals = nextjsPlugin.configs['core-web-vitals'] as unknown as Linter.Config
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Next.js 规则配置
|
|
15
|
+
*/
|
|
16
|
+
export function nextjs(options: NextjsOptions = {}): Linter.Config[] {
|
|
17
|
+
const { overrides = {} } = options
|
|
18
|
+
|
|
19
|
+
return defineConfig({
|
|
20
|
+
name: 'nextjs/rules',
|
|
21
|
+
extends: [recommended, coreWebVitals],
|
|
22
|
+
rules: overrides,
|
|
23
|
+
})
|
|
24
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Package.json 文件规则配置,检查文件一致性、字段验证和依赖管理
|
|
3
|
+
*/
|
|
4
|
+
import { defineConfig } from 'eslint/config'
|
|
5
|
+
import plugin from 'eslint-plugin-package-json'
|
|
6
|
+
|
|
7
|
+
import type { PackageJsonOptions } from '../types'
|
|
8
|
+
import type { Linter } from 'eslint'
|
|
9
|
+
|
|
10
|
+
export function packageJson(options: PackageJsonOptions = {}): Linter.Config[] {
|
|
11
|
+
const { stylistic = true, enforceForPrivate, overrides = {} } = options
|
|
12
|
+
|
|
13
|
+
return defineConfig({
|
|
14
|
+
name: 'package-json/rules',
|
|
15
|
+
plugins: {
|
|
16
|
+
'package-json': plugin,
|
|
17
|
+
},
|
|
18
|
+
extends: [plugin.configs.recommended],
|
|
19
|
+
rules: {
|
|
20
|
+
...(stylistic ? plugin.configs.stylistic.rules : {}),
|
|
21
|
+
'package-json/valid-local-dependency': 'off',
|
|
22
|
+
...overrides,
|
|
23
|
+
},
|
|
24
|
+
...(enforceForPrivate !== undefined && {
|
|
25
|
+
settings: {
|
|
26
|
+
packageJson: {
|
|
27
|
+
enforceForPrivate,
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
}),
|
|
31
|
+
})
|
|
32
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prettier 代码格式化配置,使用官方推荐规则集
|
|
3
|
+
*/
|
|
4
|
+
import { defineConfig } from 'eslint/config'
|
|
5
|
+
import prettierConfig from 'eslint-plugin-prettier/recommended'
|
|
6
|
+
|
|
7
|
+
import { GLOB_SRC } from '../utils'
|
|
8
|
+
|
|
9
|
+
import type { PrettierOptions } from '../types'
|
|
10
|
+
import type { Linter } from 'eslint'
|
|
11
|
+
|
|
12
|
+
export function prettier(options: PrettierOptions = {}): Linter.Config[] {
|
|
13
|
+
const { overrides = {} } = options
|
|
14
|
+
|
|
15
|
+
const files = [GLOB_SRC]
|
|
16
|
+
|
|
17
|
+
return defineConfig([
|
|
18
|
+
{
|
|
19
|
+
name: 'prettier/rules',
|
|
20
|
+
files,
|
|
21
|
+
extends: [prettierConfig],
|
|
22
|
+
rules: overrides,
|
|
23
|
+
},
|
|
24
|
+
])
|
|
25
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* React ESLint 配置,集成 @eslint-react、react-hooks 和 react-refresh 插件
|
|
3
|
+
*/
|
|
4
|
+
import reactPlugin from '@eslint-react/eslint-plugin'
|
|
5
|
+
import { defineConfig } from 'eslint/config'
|
|
6
|
+
import reactHooksPlugin from 'eslint-plugin-react-hooks'
|
|
7
|
+
import reactRefresh from 'eslint-plugin-react-refresh'
|
|
8
|
+
|
|
9
|
+
import { GLOB_JSX } from '../utils'
|
|
10
|
+
|
|
11
|
+
import type { ReactOptions } from '../types'
|
|
12
|
+
import type { Linter } from 'eslint'
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* React 规则配置
|
|
16
|
+
*/
|
|
17
|
+
export function react(options: ReactOptions = {}): Linter.Config[] {
|
|
18
|
+
const { files = [GLOB_JSX], overrides = {} } = options
|
|
19
|
+
|
|
20
|
+
return defineConfig({
|
|
21
|
+
name: 'react/rules',
|
|
22
|
+
files,
|
|
23
|
+
extends: [
|
|
24
|
+
reactPlugin.configs['recommended-typescript'],
|
|
25
|
+
reactHooksPlugin.configs.flat['recommended-latest'],
|
|
26
|
+
reactRefresh.configs.recommended,
|
|
27
|
+
],
|
|
28
|
+
rules: {
|
|
29
|
+
...overrides,
|
|
30
|
+
},
|
|
31
|
+
})
|
|
32
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Storybook ESLint 配置,使用官方插件检测 Storybook 最佳实践
|
|
3
|
+
*/
|
|
4
|
+
import { defineConfig } from 'eslint/config'
|
|
5
|
+
import storybookPlugin from 'eslint-plugin-storybook'
|
|
6
|
+
|
|
7
|
+
import type { StorybookOptions } from '../types'
|
|
8
|
+
import type { Linter } from 'eslint'
|
|
9
|
+
|
|
10
|
+
const flatRecommended = storybookPlugin.configs['flat/recommended'] as unknown as Linter.Config
|
|
11
|
+
|
|
12
|
+
export function storybook(options: StorybookOptions = {}): Linter.Config[] {
|
|
13
|
+
const { overrides = {} } = options
|
|
14
|
+
|
|
15
|
+
return defineConfig({
|
|
16
|
+
name: 'storybook/recommended',
|
|
17
|
+
extends: [flatRecommended],
|
|
18
|
+
rules: {
|
|
19
|
+
...overrides,
|
|
20
|
+
},
|
|
21
|
+
})
|
|
22
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stylistic 代码风格配置,提供缩进、引号、逗号等格式化规则
|
|
3
|
+
*/
|
|
4
|
+
import stylisticPlugin from '@stylistic/eslint-plugin'
|
|
5
|
+
import { defineConfig } from 'eslint/config'
|
|
6
|
+
|
|
7
|
+
import { GLOB_SRC } from '../utils'
|
|
8
|
+
|
|
9
|
+
import type { StylisticOptions } from '../types'
|
|
10
|
+
import type { Linter } from 'eslint'
|
|
11
|
+
|
|
12
|
+
export function stylistic(options: StylisticOptions = {}): Linter.Config[] {
|
|
13
|
+
const { overrides = {} } = options
|
|
14
|
+
|
|
15
|
+
const files = [GLOB_SRC]
|
|
16
|
+
|
|
17
|
+
return defineConfig([
|
|
18
|
+
{
|
|
19
|
+
name: 'stylistic/rules',
|
|
20
|
+
files,
|
|
21
|
+
extends: [
|
|
22
|
+
stylisticPlugin.configs.customize({
|
|
23
|
+
indent: 2,
|
|
24
|
+
semi: false,
|
|
25
|
+
quotes: 'single',
|
|
26
|
+
quoteProps: 'consistent-as-needed',
|
|
27
|
+
commaDangle: 'always-multiline',
|
|
28
|
+
arrowParens: true,
|
|
29
|
+
blockSpacing: true,
|
|
30
|
+
braceStyle: '1tbs',
|
|
31
|
+
jsx: true,
|
|
32
|
+
}),
|
|
33
|
+
],
|
|
34
|
+
rules: overrides,
|
|
35
|
+
},
|
|
36
|
+
])
|
|
37
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tailwind CSS ESLint 配置,使用官方推荐规则集
|
|
3
|
+
*/
|
|
4
|
+
import { defineConfig } from 'eslint/config'
|
|
5
|
+
import eslintPluginBetterTailwindcss from 'eslint-plugin-better-tailwindcss'
|
|
6
|
+
|
|
7
|
+
import type { TailwindOptions } from '../types'
|
|
8
|
+
import type { Linter } from 'eslint'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Tailwind CSS 规则配置
|
|
12
|
+
*/
|
|
13
|
+
export function tailwind(options: TailwindOptions = {}): Linter.Config[] {
|
|
14
|
+
const { files, overrides = {}, entryPoint = 'src/global.css' } = options
|
|
15
|
+
|
|
16
|
+
return defineConfig({
|
|
17
|
+
name: 'tailwind/rules',
|
|
18
|
+
files: files ?? undefined,
|
|
19
|
+
extends: [
|
|
20
|
+
eslintPluginBetterTailwindcss.configs.recommended,
|
|
21
|
+
],
|
|
22
|
+
rules: {
|
|
23
|
+
'better-tailwindcss/enforce-consistent-line-wrapping': ['error', { printWidth: 0 }],
|
|
24
|
+
...overrides,
|
|
25
|
+
},
|
|
26
|
+
settings: {
|
|
27
|
+
'better-tailwindcss': {
|
|
28
|
+
entryPoint,
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
})
|
|
32
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TypeScript ESLint 配置,提供类型感知的代码检查(推荐规则 + 风格规则)
|
|
3
|
+
*/
|
|
4
|
+
import { defineConfig } from 'eslint/config'
|
|
5
|
+
import { configs, parser, plugin } from 'typescript-eslint'
|
|
6
|
+
|
|
7
|
+
import { GLOB_TS } from '../utils'
|
|
8
|
+
|
|
9
|
+
import type { TypeScriptOptions } from '../types'
|
|
10
|
+
import type { Linter } from 'eslint'
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* TypeScript 规则配置
|
|
14
|
+
*/
|
|
15
|
+
export function typescript(options: TypeScriptOptions = {}): Linter.Config[] {
|
|
16
|
+
const { files = [GLOB_TS], tsconfigRootDir, overrides = {} } = options
|
|
17
|
+
|
|
18
|
+
return defineConfig({
|
|
19
|
+
name: 'typescript/rules',
|
|
20
|
+
files,
|
|
21
|
+
plugins: {
|
|
22
|
+
'@typescript-eslint': plugin,
|
|
23
|
+
},
|
|
24
|
+
extends: [configs.recommendedTypeChecked, configs.stylisticTypeChecked],
|
|
25
|
+
languageOptions: {
|
|
26
|
+
parser,
|
|
27
|
+
parserOptions: {
|
|
28
|
+
projectService: true,
|
|
29
|
+
tsconfigRootDir: tsconfigRootDir ?? process.cwd(),
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
rules: {
|
|
33
|
+
'@typescript-eslint/consistent-type-definitions': 'off',
|
|
34
|
+
'@typescript-eslint/consistent-type-imports': 'error',
|
|
35
|
+
'@typescript-eslint/no-unused-vars': 'off',
|
|
36
|
+
'@typescript-eslint/no-deprecated': 'warn',
|
|
37
|
+
'@typescript-eslint/no-inferrable-types': 'off',
|
|
38
|
+
'@typescript-eslint/unbound-method': 'warn',
|
|
39
|
+
'@typescript-eslint/no-misused-promises': [
|
|
40
|
+
'error',
|
|
41
|
+
{
|
|
42
|
+
checksVoidReturn: {
|
|
43
|
+
attributes: false,
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
],
|
|
47
|
+
|
|
48
|
+
...overrides,
|
|
49
|
+
},
|
|
50
|
+
})
|
|
51
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unicorn ESLint 配置,提供 100+ 强大规则改进代码质量和一致性
|
|
3
|
+
*/
|
|
4
|
+
import { defineConfig } from 'eslint/config'
|
|
5
|
+
import eslintPluginUnicorn from 'eslint-plugin-unicorn'
|
|
6
|
+
|
|
7
|
+
import { GLOB_SRC } from '../utils'
|
|
8
|
+
|
|
9
|
+
import type { UnicornOptions } from '../types'
|
|
10
|
+
import type { Linter } from 'eslint'
|
|
11
|
+
|
|
12
|
+
export function unicorn(options: UnicornOptions = {}): Linter.Config[] {
|
|
13
|
+
const { files = [GLOB_SRC], overrides = {} } = options
|
|
14
|
+
|
|
15
|
+
return defineConfig({
|
|
16
|
+
name: 'unicorn/rules',
|
|
17
|
+
files,
|
|
18
|
+
extends: [eslintPluginUnicorn.configs.recommended],
|
|
19
|
+
rules: {
|
|
20
|
+
'unicorn/no-null': 'off',
|
|
21
|
+
'unicorn/prevent-abbreviations': 'off',
|
|
22
|
+
...overrides,
|
|
23
|
+
},
|
|
24
|
+
})
|
|
25
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vitest 测试规则配置,包含测试最佳实践和质量保证规则
|
|
3
|
+
*/
|
|
4
|
+
import { fixupPluginRules } from '@eslint/compat'
|
|
5
|
+
import vitestPlugin from '@vitest/eslint-plugin'
|
|
6
|
+
import { defineConfig } from 'eslint/config'
|
|
7
|
+
|
|
8
|
+
import { GLOB_TESTS, isInEditorEnv } from '../utils'
|
|
9
|
+
|
|
10
|
+
import type { VitestOptions } from '../types'
|
|
11
|
+
import type { ESLint, Linter } from 'eslint'
|
|
12
|
+
|
|
13
|
+
export function vitest(options: VitestOptions = {}): Linter.Config[] {
|
|
14
|
+
const {
|
|
15
|
+
files = GLOB_TESTS,
|
|
16
|
+
overrides = {},
|
|
17
|
+
isInEditor = isInEditorEnv(),
|
|
18
|
+
} = options
|
|
19
|
+
|
|
20
|
+
return defineConfig([
|
|
21
|
+
{
|
|
22
|
+
name: 'vitest/rules',
|
|
23
|
+
files,
|
|
24
|
+
plugins: {
|
|
25
|
+
vitest: fixupPluginRules(vitestPlugin as unknown as ESLint.Plugin),
|
|
26
|
+
},
|
|
27
|
+
rules: {
|
|
28
|
+
...vitestPlugin.configs.recommended.rules,
|
|
29
|
+
|
|
30
|
+
'no-console': 'off',
|
|
31
|
+
'no-restricted-globals': 'off',
|
|
32
|
+
'no-restricted-syntax': 'off',
|
|
33
|
+
'no-undef': 'off',
|
|
34
|
+
'@typescript-eslint/ban-ts-comment': 'off',
|
|
35
|
+
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
|
36
|
+
'@typescript-eslint/unbound-method': 'off',
|
|
37
|
+
'unicorn/no-null': 'off',
|
|
38
|
+
|
|
39
|
+
'vitest/consistent-test-it': ['error', { fn: 'it', withinDescribe: 'it' }],
|
|
40
|
+
'vitest/no-identical-title': 'error',
|
|
41
|
+
'vitest/prefer-hooks-in-order': 'error',
|
|
42
|
+
'vitest/prefer-lowercase-title': 'error',
|
|
43
|
+
|
|
44
|
+
'vitest/no-disabled-tests': isInEditor ? 'warn' : 'error',
|
|
45
|
+
'vitest/no-focused-tests': isInEditor ? 'warn' : 'error',
|
|
46
|
+
|
|
47
|
+
...overrides,
|
|
48
|
+
},
|
|
49
|
+
settings: {
|
|
50
|
+
vitest: {
|
|
51
|
+
typecheck: true,
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
])
|
|
56
|
+
}
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @workspace/eslint-config
|
|
3
|
+
*
|
|
4
|
+
* 统一 ESLint 配置工具
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```typescript
|
|
8
|
+
* import { composeConfig } from '@workspace/eslint-config'
|
|
9
|
+
*
|
|
10
|
+
* export default composeConfig({
|
|
11
|
+
* typescript: { tsconfigRootDir: import.meta.dirname },
|
|
12
|
+
* react: true,
|
|
13
|
+
* tailwind: true,
|
|
14
|
+
* imports: { typescript: true },
|
|
15
|
+
* prettier: true,
|
|
16
|
+
* })
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { a11y } from './configs/a11y'
|
|
21
|
+
import { boundaries } from './configs/boundaries'
|
|
22
|
+
import { depend } from './configs/depend'
|
|
23
|
+
import { ignores } from './configs/ignores'
|
|
24
|
+
import { imports } from './configs/imports'
|
|
25
|
+
import { javascript } from './configs/javascript'
|
|
26
|
+
import { jsdoc } from './configs/jsdoc'
|
|
27
|
+
import { nextjs } from './configs/nextjs'
|
|
28
|
+
import { packageJson } from './configs/package-json'
|
|
29
|
+
import { prettier } from './configs/prettier'
|
|
30
|
+
import { react } from './configs/react'
|
|
31
|
+
import { storybook } from './configs/storybook'
|
|
32
|
+
import { stylistic } from './configs/stylistic'
|
|
33
|
+
import { tailwind } from './configs/tailwind'
|
|
34
|
+
import { typescript } from './configs/typescript'
|
|
35
|
+
import { unicorn } from './configs/unicorn'
|
|
36
|
+
import { vitest } from './configs/vitest'
|
|
37
|
+
|
|
38
|
+
import type {
|
|
39
|
+
A11yOptions,
|
|
40
|
+
BoundariesOptions,
|
|
41
|
+
DependOptions,
|
|
42
|
+
IgnoresOptions,
|
|
43
|
+
ImportsOptions,
|
|
44
|
+
JavaScriptOptions,
|
|
45
|
+
JsdocOptions,
|
|
46
|
+
NextjsOptions,
|
|
47
|
+
PackageJsonOptions,
|
|
48
|
+
PrettierOptions,
|
|
49
|
+
ReactOptions,
|
|
50
|
+
StorybookOptions,
|
|
51
|
+
StylisticOptions,
|
|
52
|
+
TailwindOptions,
|
|
53
|
+
TypeScriptOptions,
|
|
54
|
+
UnicornOptions,
|
|
55
|
+
VitestOptions,
|
|
56
|
+
} from './types'
|
|
57
|
+
import type { Linter } from 'eslint'
|
|
58
|
+
|
|
59
|
+
// ============================================================================
|
|
60
|
+
// 类型定义
|
|
61
|
+
// ============================================================================
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* composeConfig 配置选项
|
|
65
|
+
*
|
|
66
|
+
* 每个配置项支持:
|
|
67
|
+
* - `true` - 使用默认选项
|
|
68
|
+
* - `对象` - 传入自定义选项
|
|
69
|
+
* - `false` - 关闭(默认开启的配置需要显式关闭)
|
|
70
|
+
* - 不写 - 不启用(非默认开启的配置)
|
|
71
|
+
*/
|
|
72
|
+
export interface ComposeConfigOptions {
|
|
73
|
+
// 基础配置(默认开启)
|
|
74
|
+
/** 忽略文件配置 @default true */
|
|
75
|
+
ignores?: boolean | IgnoresOptions
|
|
76
|
+
/** JavaScript 基础配置 @default true */
|
|
77
|
+
javascript?: boolean | JavaScriptOptions
|
|
78
|
+
/** TypeScript 配置 @default true */
|
|
79
|
+
typescript?: boolean | TypeScriptOptions
|
|
80
|
+
/** 代码风格规则 @default true */
|
|
81
|
+
stylistic?: boolean | StylisticOptions
|
|
82
|
+
/** Unicorn 最佳实践 @default true */
|
|
83
|
+
unicorn?: boolean | UnicornOptions
|
|
84
|
+
/** 依赖优化建议 @default true */
|
|
85
|
+
depend?: boolean | DependOptions
|
|
86
|
+
|
|
87
|
+
// 框架配置
|
|
88
|
+
/** React 配置 */
|
|
89
|
+
react?: boolean | ReactOptions
|
|
90
|
+
/** Next.js 配置 */
|
|
91
|
+
nextjs?: boolean | NextjsOptions
|
|
92
|
+
/** Tailwind CSS 配置 */
|
|
93
|
+
tailwind?: boolean | TailwindOptions
|
|
94
|
+
|
|
95
|
+
// 工具配置
|
|
96
|
+
/** Import 排序和规则 */
|
|
97
|
+
imports?: boolean | ImportsOptions
|
|
98
|
+
/** Prettier 格式化 */
|
|
99
|
+
prettier?: boolean | PrettierOptions
|
|
100
|
+
|
|
101
|
+
// 质量配置
|
|
102
|
+
/** 无障碍访问规则 */
|
|
103
|
+
a11y?: boolean | A11yOptions
|
|
104
|
+
/** JSDoc 文档规则 */
|
|
105
|
+
jsdoc?: boolean | JsdocOptions
|
|
106
|
+
/** 模块边界规则 */
|
|
107
|
+
boundaries?: boolean | BoundariesOptions
|
|
108
|
+
/** package.json 规则 */
|
|
109
|
+
packageJson?: boolean | PackageJsonOptions
|
|
110
|
+
|
|
111
|
+
// 测试配置
|
|
112
|
+
/** Vitest 测试规则 */
|
|
113
|
+
vitest?: boolean | VitestOptions
|
|
114
|
+
/** Storybook 规则 */
|
|
115
|
+
storybook?: boolean | StorybookOptions
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// ============================================================================
|
|
119
|
+
// 主函数
|
|
120
|
+
// ============================================================================
|
|
121
|
+
|
|
122
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
123
|
+
type ConfigFn = (opts: any) => Linter.Config[]
|
|
124
|
+
|
|
125
|
+
type ConfigEntry = {
|
|
126
|
+
key: keyof ComposeConfigOptions
|
|
127
|
+
fn: ConfigFn
|
|
128
|
+
defaultOn?: boolean
|
|
129
|
+
/** 根据全局 options 派生注入值,优先级低于用户配置 */
|
|
130
|
+
inject?: (options: ComposeConfigOptions) => Record<string, unknown>
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const CONFIG_REGISTRY: ConfigEntry[] = [
|
|
134
|
+
// 默认开启
|
|
135
|
+
{ key: 'ignores', fn: ignores, defaultOn: true },
|
|
136
|
+
{ key: 'javascript', fn: javascript, defaultOn: true },
|
|
137
|
+
{ key: 'typescript', fn: typescript, defaultOn: true },
|
|
138
|
+
{ key: 'stylistic', fn: stylistic, defaultOn: true },
|
|
139
|
+
{ key: 'unicorn', fn: unicorn, defaultOn: true },
|
|
140
|
+
{ key: 'depend', fn: depend, defaultOn: true },
|
|
141
|
+
// 按需开启(顺序固定,prettier 最后)
|
|
142
|
+
{
|
|
143
|
+
key: 'imports',
|
|
144
|
+
fn: imports,
|
|
145
|
+
inject: (options) => ({ typescript: options.typescript !== false }),
|
|
146
|
+
},
|
|
147
|
+
{ key: 'react', fn: react },
|
|
148
|
+
{ key: 'nextjs', fn: nextjs },
|
|
149
|
+
{ key: 'tailwind', fn: tailwind },
|
|
150
|
+
{ key: 'a11y', fn: a11y },
|
|
151
|
+
{ key: 'jsdoc', fn: jsdoc },
|
|
152
|
+
{ key: 'boundaries', fn: boundaries },
|
|
153
|
+
{ key: 'packageJson', fn: packageJson },
|
|
154
|
+
{ key: 'vitest', fn: vitest },
|
|
155
|
+
{ key: 'storybook', fn: storybook },
|
|
156
|
+
{ key: 'prettier', fn: prettier },
|
|
157
|
+
]
|
|
158
|
+
|
|
159
|
+
/** 组合 ESLint 配置,内部按正确顺序组合 */
|
|
160
|
+
export function composeConfig(options: ComposeConfigOptions = {}): Linter.Config[] {
|
|
161
|
+
const configs: Linter.Config[] = []
|
|
162
|
+
|
|
163
|
+
for (const { key, fn, defaultOn, inject } of CONFIG_REGISTRY) {
|
|
164
|
+
const opt = options[key]
|
|
165
|
+
const enabled = defaultOn ? opt !== false : !!opt
|
|
166
|
+
if (!enabled) continue
|
|
167
|
+
|
|
168
|
+
const base = typeof opt === 'object' ? opt : {}
|
|
169
|
+
const injected = inject ? inject(options) : {}
|
|
170
|
+
configs.push(...fn({ ...injected, ...base }))
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return configs
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// ============================================================================
|
|
177
|
+
// 类型导出
|
|
178
|
+
// ============================================================================
|
|
179
|
+
|
|
180
|
+
export type {
|
|
181
|
+
A11yOptions,
|
|
182
|
+
BoundariesOptions,
|
|
183
|
+
DependOptions,
|
|
184
|
+
IgnoresOptions,
|
|
185
|
+
ImportsOptions,
|
|
186
|
+
JavaScriptOptions,
|
|
187
|
+
JsdocOptions,
|
|
188
|
+
NextjsOptions,
|
|
189
|
+
PackageJsonOptions,
|
|
190
|
+
PrettierOptions,
|
|
191
|
+
ReactOptions,
|
|
192
|
+
StorybookOptions,
|
|
193
|
+
StylisticOptions,
|
|
194
|
+
TailwindOptions,
|
|
195
|
+
TypeScriptOptions,
|
|
196
|
+
UnicornOptions,
|
|
197
|
+
VitestOptions,
|
|
198
|
+
} from './types'
|
|
199
|
+
|
|
200
|
+
// ============================================================================
|
|
201
|
+
// 常量导出
|
|
202
|
+
// ============================================================================
|
|
203
|
+
|
|
204
|
+
export { GLOB_SRC, GLOB_JS, GLOB_TS, GLOB_JSX, GLOB_TESTS, GLOB_JSON, GLOB_MARKDOWN } from './utils'
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import type { RulesConfig } from '@eslint/core'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 规则覆盖选项
|
|
5
|
+
*/
|
|
6
|
+
export interface OptionsOverrides {
|
|
7
|
+
/**
|
|
8
|
+
* 自定义规则覆盖
|
|
9
|
+
*/
|
|
10
|
+
overrides?: Partial<RulesConfig>
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* 风格化选项
|
|
15
|
+
*/
|
|
16
|
+
export interface OptionsStylistic {
|
|
17
|
+
/**
|
|
18
|
+
* 是否启用风格化规则
|
|
19
|
+
* @default true
|
|
20
|
+
*/
|
|
21
|
+
stylistic?: boolean
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* 文件匹配选项
|
|
26
|
+
*/
|
|
27
|
+
export interface OptionsFiles {
|
|
28
|
+
/**
|
|
29
|
+
* 自定义文件匹配模式
|
|
30
|
+
*/
|
|
31
|
+
files?: string[]
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* TypeScript 配置选项
|
|
36
|
+
*/
|
|
37
|
+
export interface OptionsTypeScript {
|
|
38
|
+
tsconfigRootDir?: string
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Tailwind CSS 配置选项
|
|
43
|
+
*/
|
|
44
|
+
export interface OptionsTailwind {
|
|
45
|
+
/**
|
|
46
|
+
* Tailwind CSS 入口文件路径
|
|
47
|
+
* @default 'src/global.css'
|
|
48
|
+
*/
|
|
49
|
+
entryPoint?: string
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// ============================================================================
|
|
53
|
+
// Config Options 类型
|
|
54
|
+
// ============================================================================
|
|
55
|
+
|
|
56
|
+
export type A11yOptions = OptionsFiles & OptionsOverrides
|
|
57
|
+
|
|
58
|
+
export interface BoundariesOptions extends OptionsFiles, OptionsOverrides {
|
|
59
|
+
elements?: {
|
|
60
|
+
type: string
|
|
61
|
+
pattern: string | string[]
|
|
62
|
+
capture?: string[]
|
|
63
|
+
mode?: 'file' | 'folder' | 'full'
|
|
64
|
+
}[]
|
|
65
|
+
rules?: {
|
|
66
|
+
from: string | string[]
|
|
67
|
+
allow?: (string | [string, Record<string, string>])[]
|
|
68
|
+
disallow?: string[]
|
|
69
|
+
message?: string
|
|
70
|
+
}[]
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface DependOptions extends OptionsOverrides {
|
|
74
|
+
/**
|
|
75
|
+
* 预设列表
|
|
76
|
+
*
|
|
77
|
+
* - `native`: 检测可用原生 JavaScript API 替代的包(如 `is-nan` → `Number.isNaN()`)
|
|
78
|
+
* - `microutilities`: 检测微型工具库(一行代码可实现的包)
|
|
79
|
+
* - `preferred`: 推荐更轻量、维护更好的替代方案
|
|
80
|
+
*
|
|
81
|
+
* @default ['native', 'microutilities', 'preferred']
|
|
82
|
+
*/
|
|
83
|
+
presets?: ('native' | 'microutilities' | 'preferred')[]
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* 自定义禁用的模块列表
|
|
87
|
+
* @default []
|
|
88
|
+
*/
|
|
89
|
+
modules?: string[]
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* 允许使用的模块列表(即使在 presets 中)
|
|
93
|
+
* @default []
|
|
94
|
+
*/
|
|
95
|
+
allowed?: string[]
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export interface IgnoresOptions {
|
|
99
|
+
/** 用户自定义忽略规则,传入 false 禁用默认规则 */
|
|
100
|
+
ignores?: string[] | false
|
|
101
|
+
/** gitignore 文件路径或启用标志 */
|
|
102
|
+
gitignore?: string | boolean
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export interface ImportsOptions extends OptionsOverrides, OptionsStylistic {
|
|
106
|
+
/**
|
|
107
|
+
* 是否启用 TypeScript 支持
|
|
108
|
+
* 当全局 typescript 开启时,由 composeConfig 自动注入为 true
|
|
109
|
+
* @default false(独立调用时)/ 跟随全局 typescript 选项(通过 composeConfig)
|
|
110
|
+
*/
|
|
111
|
+
typescript?: boolean
|
|
112
|
+
/**
|
|
113
|
+
* 禁止使用 ../ 父级相对导入
|
|
114
|
+
* @default false
|
|
115
|
+
*/
|
|
116
|
+
noRelativeParentImports?: boolean
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export type JavaScriptOptions = OptionsFiles & OptionsOverrides
|
|
120
|
+
|
|
121
|
+
export type JsdocOptions = OptionsOverrides
|
|
122
|
+
|
|
123
|
+
export type NextjsOptions = OptionsOverrides
|
|
124
|
+
|
|
125
|
+
export interface PackageJsonOptions extends OptionsOverrides {
|
|
126
|
+
/**
|
|
127
|
+
* @default true
|
|
128
|
+
*/
|
|
129
|
+
stylistic?: boolean
|
|
130
|
+
/**
|
|
131
|
+
* @default false
|
|
132
|
+
*/
|
|
133
|
+
enforceForPrivate?: boolean
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export type PrettierOptions = OptionsOverrides
|
|
137
|
+
|
|
138
|
+
export type ReactOptions = OptionsFiles & OptionsOverrides
|
|
139
|
+
|
|
140
|
+
export type StorybookOptions = OptionsOverrides
|
|
141
|
+
|
|
142
|
+
export type StylisticOptions = OptionsOverrides
|
|
143
|
+
|
|
144
|
+
export type TailwindOptions = OptionsFiles & OptionsOverrides & OptionsTailwind
|
|
145
|
+
|
|
146
|
+
export type TypeScriptOptions = OptionsFiles & OptionsOverrides & OptionsTypeScript
|
|
147
|
+
|
|
148
|
+
export type UnicornOptions = OptionsFiles & OptionsOverrides
|
|
149
|
+
|
|
150
|
+
export type VitestOptions = OptionsFiles & OptionsOverrides & {
|
|
151
|
+
isInEditor?: boolean
|
|
152
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 工具函数和常量
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// ============================================================================
|
|
6
|
+
// 环境检测
|
|
7
|
+
// ============================================================================
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* 检测是否在 Git Hooks 或 lint-staged 中运行
|
|
11
|
+
*/
|
|
12
|
+
function isInGitHooksOrLintStaged(): boolean {
|
|
13
|
+
const envVars = [
|
|
14
|
+
process.env['GIT_PARAMS'],
|
|
15
|
+
process.env['VSCODE_GIT_COMMAND'],
|
|
16
|
+
]
|
|
17
|
+
return envVars.some(Boolean) || process.env['npm_lifecycle_script']?.startsWith('lint-staged') === true
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* 检测是否在编辑器环境中运行
|
|
22
|
+
*
|
|
23
|
+
* 在以下情况返回 false:
|
|
24
|
+
* - CI 环境
|
|
25
|
+
* - Git Hooks
|
|
26
|
+
* - lint-staged
|
|
27
|
+
*
|
|
28
|
+
* 在以下情况返回 true:
|
|
29
|
+
* - VSCode
|
|
30
|
+
* - JetBrains IDE
|
|
31
|
+
* - Vim / Neovim
|
|
32
|
+
*/
|
|
33
|
+
export function isInEditorEnv(): boolean {
|
|
34
|
+
if (process.env['CI'])
|
|
35
|
+
return false
|
|
36
|
+
if (isInGitHooksOrLintStaged())
|
|
37
|
+
return false
|
|
38
|
+
const envVars = [
|
|
39
|
+
process.env['VSCODE_PID'],
|
|
40
|
+
process.env['VSCODE_CWD'],
|
|
41
|
+
process.env['JETBRAINS_IDE'],
|
|
42
|
+
process.env['VIM'],
|
|
43
|
+
process.env['NVIM'],
|
|
44
|
+
]
|
|
45
|
+
return envVars.some(Boolean)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// ============================================================================
|
|
49
|
+
// 文件匹配模式 (Globs)
|
|
50
|
+
// ============================================================================
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* ESLint 文件匹配模式常量
|
|
54
|
+
*
|
|
55
|
+
* 统一的 glob 模式,确保所有配置使用一致的文件匹配规则
|
|
56
|
+
*/
|
|
57
|
+
|
|
58
|
+
/** 所有 JS/TS 源文件(完整覆盖 ESM/CJS) */
|
|
59
|
+
export const GLOB_SRC = '**/*.{js,mjs,cjs,jsx,ts,mts,cts,tsx}'
|
|
60
|
+
|
|
61
|
+
/** 仅 JavaScript 文件 */
|
|
62
|
+
export const GLOB_JS = '**/*.{js,mjs,cjs,jsx}'
|
|
63
|
+
|
|
64
|
+
/** 仅 TypeScript 文件 */
|
|
65
|
+
export const GLOB_TS = '**/*.{ts,mts,cts,tsx}'
|
|
66
|
+
|
|
67
|
+
/** JSX/TSX 文件(React 相关) */
|
|
68
|
+
export const GLOB_JSX = '**/*.{jsx,tsx}'
|
|
69
|
+
|
|
70
|
+
/** 测试文件 */
|
|
71
|
+
export const GLOB_TESTS: string[] = [
|
|
72
|
+
'**/*.{test,spec}.{js,mjs,cjs,jsx,ts,mts,cts,tsx}',
|
|
73
|
+
'**/__tests__/**/*.{js,mjs,cjs,jsx,ts,mts,cts,tsx}',
|
|
74
|
+
]
|
|
75
|
+
|
|
76
|
+
/** JSON 文件 */
|
|
77
|
+
export const GLOB_JSON = '**/*.json'
|
|
78
|
+
|
|
79
|
+
/** Markdown 文件 */
|
|
80
|
+
export const GLOB_MARKDOWN = '**/*.md'
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@infra-x/create-eslint-config",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"description": "CLI to copy ESLint config source into a monorepo.",
|
|
6
|
+
"author": "infra-x",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"publishConfig": {
|
|
9
|
+
"access": "public"
|
|
10
|
+
},
|
|
11
|
+
"bin": {
|
|
12
|
+
"create-eslint-config": "./dist/index.mjs"
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist"
|
|
16
|
+
],
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@clack/prompts": "^0.11.0"
|
|
19
|
+
},
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"@types/node": "^25.0.3",
|
|
22
|
+
"eslint": "^9.0.0",
|
|
23
|
+
"tsdown": "^0.18.1",
|
|
24
|
+
"typescript": "^5.9.3",
|
|
25
|
+
"@infra-x/eslint-config": "0.1.0",
|
|
26
|
+
"@infra-x/typescript-config": "0.1.0"
|
|
27
|
+
},
|
|
28
|
+
"main": "./dist/index.mjs",
|
|
29
|
+
"module": "./dist/index.mjs",
|
|
30
|
+
"exports": {
|
|
31
|
+
".": "./dist/index.mjs",
|
|
32
|
+
"./package.json": "./package.json"
|
|
33
|
+
},
|
|
34
|
+
"scripts": {
|
|
35
|
+
"build": "tsdown",
|
|
36
|
+
"dev": "tsdown --watch",
|
|
37
|
+
"typecheck": "tsc --noEmit"
|
|
38
|
+
}
|
|
39
|
+
}
|