refactor: 将 unicorn / TypeScript 规则迁移至 oxlint,并完善 oxfmt 配置 (#8067)

* chore: update deps

* chore: update archiver v8

* fix: 还原 vue/html-closing-bracket-newline

* feat: 增加默认配置和配置注释

* feat: 增加oxlint默认插件配置和注释

* refactor: remove unicorn ESLint configuration and update dependencies

- Deleted the unicorn ESLint configuration file from the lint-configs package.
- Removed unicorn configuration from the main index file.
- Updated oxlint-config to include a comprehensive set of unicorn rules.
- Adjusted preferences and vxe-table files to disable specific unicorn rules.
- Removed references to eslint-plugin-unicorn from pnpm-lock.yaml and pnpm-workspace.yaml.

* refactor: 移除 eslint 的 @typescript-eslint/eslint-plugin 依赖,将 TypeScript 配置迁移到 oxlint

* feat: open typeAware

* fix: mac 和linux下报错
This commit is contained in:
xingyu
2026-06-18 16:10:01 +08:00
committed by GitHub
parent 65885ea74b
commit dc3466d77a
24 changed files with 977 additions and 859 deletions

View File

@@ -28,7 +28,6 @@
},
"dependencies": {
"@eslint/js": "catalog:",
"@typescript-eslint/eslint-plugin": "catalog:",
"@typescript-eslint/parser": "catalog:",
"@vben/oxlint-config": "workspace:*",
"eslint": "catalog:",
@@ -36,7 +35,6 @@
"eslint-plugin-n": "catalog:",
"eslint-plugin-perfectionist": "catalog:",
"eslint-plugin-pnpm": "catalog:",
"eslint-plugin-unicorn": "catalog:",
"eslint-plugin-unused-imports": "catalog:",
"eslint-plugin-vue": "catalog:",
"eslint-plugin-yml": "catalog:",

View File

@@ -5,6 +5,5 @@ export * from './node';
export * from './perfectionist';
export * from './pnpm';
export * from './typescript';
export * from './unicorn';
export * from './vue';
export * from './yaml';

View File

@@ -2,24 +2,17 @@ import type { Linter } from 'eslint';
import { interopDefault } from '../util';
const rulesCoveredByOxlint = new Set([
'@typescript-eslint/ban-ts-comment',
'@typescript-eslint/no-non-null-assertion',
'@typescript-eslint/no-unused-expressions',
'@typescript-eslint/no-unused-vars',
'@typescript-eslint/triple-slash-reference',
]);
/**
* @typescript-eslint 的规则已迁移到 oxlinttypescript 插件)。
* 这里仅保留 TS 解析器,供其它 eslint 插件perfectionist、n 等)解析 TS/TSX 文件。
* 因不再有类型感知规则,已移除 parserOptions.projecteslint 解析更快。
*
* 注意:移除 @typescript-eslint 插件后unused-imports/no-unused-vars 会退回核心实现,
* 无法识别 TS 类型签名里的形参(会误报)。故对 TS/TSX/Vue 统一关闭该规则,
* 未使用变量改由 oxlint 的 no-unused-vars类型感知负责。
*/
export async function typescript(): Promise<Linter.Config[]> {
const [pluginTs, parserTs] = await Promise.all([
interopDefault(import('@typescript-eslint/eslint-plugin')),
interopDefault(import('@typescript-eslint/parser')),
] as const);
const strictRules = Object.fromEntries(
Object.entries(pluginTs.configs.strict?.rules ?? {}).filter(
([ruleName]) => !rulesCoveredByOxlint.has(ruleName),
),
);
const parserTs = await interopDefault(import('@typescript-eslint/parser'));
return [
{
@@ -27,30 +20,23 @@ export async function typescript(): Promise<Linter.Config[]> {
languageOptions: {
parser: parserTs,
parserOptions: {
createDefaultProgram: false,
ecmaFeatures: {
jsx: true,
},
ecmaVersion: 'latest',
extraFileExtensions: ['.vue'],
jsxPragma: 'React',
project: './tsconfig.*.json',
sourceType: 'module',
},
},
plugins: {
'@typescript-eslint': pluginTs as any,
},
rules: {
...pluginTs.configs['eslint-recommended']?.overrides?.[0]?.rules,
...strictRules,
// '@typescript-eslint/consistent-type-definitions': ['warn', 'interface'],
'@typescript-eslint/consistent-type-definitions': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-namespace': 'off',
'@typescript-eslint/no-use-before-define': 'off',
'unused-imports/no-unused-vars': 'off',
},
},
{
// Vue `<script>` 的未使用变量同样交给 oxlint避免核心规则误报 TS 类型签名形参
files: ['**/*.vue'],
rules: {
'unused-imports/no-unused-vars': 'off',
},
},

View File

@@ -1,44 +0,0 @@
import type { Linter } from 'eslint';
import { interopDefault } from '../util';
const rulesCoveredByOxlint = new Set([
'unicorn/consistent-function-scoping',
'unicorn/no-process-exit',
'unicorn/prefer-global-this',
'unicorn/prefer-module',
]);
export async function unicorn(): Promise<Linter.Config[]> {
const pluginUnicorn = await interopDefault(import('eslint-plugin-unicorn'));
const recommendedRules = Object.fromEntries(
Object.entries(pluginUnicorn.configs.recommended.rules ?? {}).filter(
([ruleName]) => !rulesCoveredByOxlint.has(ruleName),
),
);
return [
{
plugins: {
unicorn: pluginUnicorn,
},
rules: {
...recommendedRules,
'unicorn/better-regex': 'off',
'unicorn/consistent-destructuring': 'off',
'unicorn/expiring-todo-comments': 'off',
'unicorn/filename-case': 'off',
'unicorn/import-style': 'off',
'unicorn/no-array-for-each': 'off',
'unicorn/no-null': 'off',
'unicorn/no-useless-undefined': 'off',
'unicorn/prefer-at': 'off',
'unicorn/prefer-dom-node-text-content': 'off',
'unicorn/prefer-export-from': ['error', { ignoreUsedVariables: true }],
'unicorn/prefer-top-level-await': 'off',
'unicorn/prevent-abbreviations': 'off',
},
},
];
}

View File

@@ -85,13 +85,7 @@ export async function vue(): Promise<Linter.Config[]> {
'vue/dot-location': ['error', 'property'],
'vue/dot-notation': ['error', { allowKeywords: true }],
'vue/eqeqeq': ['error', 'smart'],
'vue/html-closing-bracket-newline': [
'error',
{
multiline: 'never',
selfClosingTag: { multiline: 'never' },
},
],
'vue/html-closing-bracket-newline': 'error',
'vue/html-indent': 'off',
// 'vue/html-indent': ['error', 2],
'vue/html-quotes': ['error', 'double'],

View File

@@ -8,7 +8,6 @@ import {
perfectionist,
pnpm,
typescript,
unicorn,
vue,
yaml,
} from './configs';
@@ -31,7 +30,6 @@ async function defineConfig(config: FlatConfig[] = []) {
jsonc(),
node(),
perfectionist(),
unicorn(),
yaml(),
pnpm(),
...customConfig,

View File

@@ -2,13 +2,107 @@ import { defineConfig as defineOxfmtConfig } from 'oxfmt';
type OxfmtConfig = Parameters<typeof defineOxfmtConfig>[0];
/**
* oxfmt 配置文件,详见下方链接
* https://oxc.rs/docs/guide/usage/formatter/config-file-reference.html
*/
const oxfmtConfig: OxfmtConfig = defineOxfmtConfig({
/**
* 单行长度 oxfmt适配 prettier 的 80
* Default100
*/
printWidth: 80,
/**
* 缩进宽度
* Default2
*/
tabWidth: 2,
/**
* Markdown、MDX、YAML 文件格式化包裹
* type: always | never | preserve
* Default: preserve
*/
proseWrap: 'never',
/**
* 结尾添加分号
* Defaulttrue
*/
semi: true,
/**
* 使用单引号
* Defaultfalse
*/
singleQuote: true,
/**
* 对象属性添加引号
* Defaultas-needed
*/
quoteProps: 'as-needed',
/**
* 将多行元素的 > 放在最后一行的末尾,而不是单独放在下一行
* Defaultfalse
*/
bracketSameLine: false,
/**
* 对象字面量的大括号间添加空格
* Defaulttrue
*/
bracketSpacing: true,
/**
* 箭头函数参数总是使用括号
* type: always | avoid
* Defaultalways
*/
arrowParens: 'always',
/**
* 配置 package.json 排序,但是 oxfmt 不支持 pnpm-workspace
* 现使用 eslint 搭配 eslint-plugin-pnpm eslint-plugin-yml 支持 package.json 和 pnpm-workspace.yaml 但排序风格不太一致
* Defaulttrue
*/
sortPackageJson: false,
/**
* 配置 import 排序,现在 使用 eslint-plugin-perfectionist但是 oxfmt 不支持 export 等
* 并且 customGroups 不支持 ts-equals-import
* Defaultfalse
*/
sortImports: false,
/**
* 多行结构中的后置逗号
* Defaultall
*/
trailingComma: 'all',
/**
* 行尾换行符
* type: lf | crlf | cr
* Default: lf
*/
endOfLine: 'lf',
/**
* 在文件最后插入一个换行
* Defaulttrue
*/
insertFinalNewline: true,
/**
* 控制格式化文件中例如CSS-in-JS 或 JS-in-Vue 等
* Defaultauto
*/
embeddedLanguageFormatting: 'auto',
/**
* Vue/HTML/Angular/Handlebars 的空白敏感度oxfmt 现会格式化 <template>
* type: css | strict | ignore
* Defaultcss
*/
htmlWhitespaceSensitivity: 'css',
/**
* 暂时关闭,改动较多,后续可以考虑开启,支持 vue 但与现有的 eslint-plugin-better-tailwindcss 格式化会冲突
* eslint-plugin-better-tailwindcss配置在 oxlint在 vue文 件暂时不生效ts等正常
* Default关闭
*/
// sortTailwindcss: {
// functions: ['clsx', 'cn', 'cva', 'tw'],
// stylesheet: './internal/tailwind-config/src/theme.css',
// preserveWhitespace: true,
// },
overrides: [
{
files: [
@@ -23,6 +117,8 @@ const oxfmtConfig: OxfmtConfig = defineOxfmtConfig({
],
options: {
trailingComma: 'none',
quoteProps: 'preserve',
singleQuote: false,
},
},
],

View File

@@ -9,6 +9,15 @@ const overrides: OxlintConfig = {
'typescript/triple-slash-reference': 'off',
},
},
{
// 这些 @typescript-eslint 规则此前不作用于 .vue旧 eslint glob 不含 .vue
// Vue 组件惯用 `interface Props extends XxxProps {}` 声明 props保持迁移前行为放行。
files: ['*.vue', '**/*.vue'],
rules: {
'typescript/no-empty-object-type': 'off',
'typescript/no-unsafe-function-type': 'off',
},
},
{
files: [
'**/__tests__/**/*.js',

View File

@@ -1,7 +1,24 @@
import type { OxlintConfig } from 'oxlint';
const plugins: OxlintConfig = {
plugins: ['import', 'node', 'oxc', 'typescript', 'unicorn', 'vitest', 'vue'],
/**
* oxlint 支持的插件,将默认开启的也显示配置
* type: eslint | react | unicorn | typescript | oxc |
* import | jsdoc | jest | vitest | jsx-a11y | nextjs |
* react-perf | promise | node | vue
*
* Default: eslint,typescript,unicorn,oxc
*/
plugins: [
'eslint',
'import',
'node',
'oxc',
'typescript',
'unicorn',
'vitest',
'vue',
],
};
export { plugins };

View File

@@ -2,25 +2,49 @@ import type { OxlintConfig } from 'oxlint';
const typescript: OxlintConfig = {
rules: {
// —— 从 @typescript-eslint strict 预设迁移而来非类型感知oxlint 原生支持)——
// no-array-constructor / no-useless-constructor 已由 javascript 配置以核心规则覆盖。
'typescript/ban-ts-comment': 'error',
// Keep the first type-aware rollout conservative. These rules currently
// produce high-volume diagnostics and need file-by-file cleanup later.
'typescript/no-duplicate-enum-values': 'error',
'typescript/no-dynamic-delete': 'error',
'typescript/no-empty-object-type': 'error',
'typescript/no-extra-non-null-assertion': 'error',
'typescript/no-extraneous-class': 'error',
'typescript/no-invalid-void-type': 'error',
'typescript/no-misused-new': 'error',
'typescript/no-non-null-asserted-nullish-coalescing': 'error',
'typescript/no-non-null-asserted-optional-chain': 'error',
'typescript/no-non-null-assertion': 'error',
'typescript/no-require-imports': 'error',
'typescript/no-this-alias': 'error',
'typescript/no-unnecessary-type-constraint': 'error',
'typescript/no-unsafe-declaration-merging': 'error',
'typescript/no-unsafe-function-type': 'error',
'typescript/no-var-requires': 'error',
'typescript/no-wrapper-object-types': 'error',
'typescript/prefer-as-const': 'error',
'typescript/prefer-literal-enum-member': 'error',
'typescript/prefer-namespace-keyword': 'error',
'typescript/triple-slash-reference': 'error',
'typescript/unified-signatures': 'error',
'typescript/await-thenable': 'off',
'typescript/consistent-return': 'off',
'typescript/no-base-to-string': 'off',
'typescript/no-duplicate-type-constituents': 'off',
'typescript/no-floating-promises': 'off',
'typescript/no-misused-spread': 'off',
'typescript/no-non-null-assertion': 'error',
'typescript/no-redundant-type-constituents': 'off',
'typescript/no-unnecessary-boolean-literal-compare': 'off',
'typescript/no-unnecessary-type-assertion': 'off',
'typescript/no-unnecessary-type-arguments': 'off',
'typescript/no-unnecessary-template-expression': 'off',
'typescript/no-unnecessary-type-arguments': 'off',
'typescript/no-unnecessary-type-assertion': 'off',
'typescript/no-unnecessary-type-conversion': 'off',
'typescript/no-unnecessary-type-parameters': 'off',
'typescript/no-unsafe-enum-comparison': 'off',
'typescript/no-unsafe-type-assertion': 'off',
'typescript/no-var-requires': 'error',
'typescript/no-useless-default-assignment': 'off',
'typescript/restrict-template-expressions': 'off',
'typescript/triple-slash-reference': 'error',
'typescript/unbound-method': 'off',
},
};

View File

@@ -8,6 +8,123 @@ const unicorn: OxlintConfig = {
'unicorn/no-useless-spread': 'off',
'unicorn/prefer-global-this': 'off',
'unicorn/prefer-module': 'error',
'unicorn/catch-error-name': 'error',
'unicorn/consistent-assert': 'error',
'unicorn/consistent-date-clone': 'error',
'unicorn/consistent-empty-array-spread': 'error',
'unicorn/consistent-existence-index-check': 'error',
'unicorn/consistent-template-literal-escape': 'error',
'unicorn/empty-brace-spaces': 'error',
'unicorn/error-message': 'error',
'unicorn/escape-case': 'error',
'unicorn/explicit-length-check': 'error',
'unicorn/new-for-builtins': 'error',
'unicorn/no-abusive-eslint-disable': 'error',
'unicorn/no-accessor-recursion': 'error',
'unicorn/no-anonymous-default-export': 'error',
'unicorn/no-array-callback-reference': 'error',
'unicorn/no-array-method-this-argument': 'error',
'unicorn/no-array-reduce': 'error',
'unicorn/no-array-reverse': 'error',
'unicorn/no-array-sort': 'error',
'unicorn/no-await-expression-member': 'error',
'unicorn/no-await-in-promise-methods': 'error',
'unicorn/no-console-spaces': 'error',
'unicorn/no-document-cookie': 'error',
'unicorn/no-empty-file': 'error',
'unicorn/no-hex-escape': 'error',
// oxlint 实现较 eslint 更严格,会误报既有代码,暂关闭
'unicorn/no-immediate-mutation': 'off',
'unicorn/no-instanceof-builtins': 'error',
'unicorn/no-invalid-fetch-options': 'error',
'unicorn/no-invalid-remove-event-listener': 'error',
'unicorn/no-lonely-if': 'error',
'unicorn/no-magic-array-flat-depth': 'error',
'unicorn/no-negated-condition': 'error',
'unicorn/no-negation-in-equality-check': 'error',
'unicorn/no-nested-ternary': 'error',
'unicorn/no-new-array': 'error',
'unicorn/no-new-buffer': 'error',
'unicorn/no-object-as-default-parameter': 'error',
'unicorn/no-static-only-class': 'error',
'unicorn/no-thenable': 'error',
'unicorn/no-this-assignment': 'error',
'unicorn/no-typeof-undefined': 'error',
'unicorn/no-unnecessary-array-flat-depth': 'error',
'unicorn/no-unnecessary-array-splice-count': 'error',
'unicorn/no-unnecessary-await': 'error',
'unicorn/no-unnecessary-slice-end': 'error',
'unicorn/no-unreadable-array-destructuring': 'error',
'unicorn/no-unreadable-iife': 'error',
'unicorn/no-useless-collection-argument': 'error',
'unicorn/no-useless-error-capture-stack-trace': 'error',
'unicorn/no-useless-fallback-in-spread': 'error',
'unicorn/no-useless-iterator-to-array': 'error',
'unicorn/no-useless-length-check': 'error',
'unicorn/no-useless-promise-resolve-reject': 'error',
'unicorn/no-useless-switch-case': 'error',
'unicorn/no-zero-fractions': 'error',
'unicorn/number-literal-case': 'error',
'unicorn/numeric-separators-style': 'error',
'unicorn/prefer-add-event-listener': 'error',
'unicorn/prefer-array-find': 'error',
'unicorn/prefer-array-flat': 'error',
'unicorn/prefer-array-flat-map': 'error',
'unicorn/prefer-array-index-of': 'error',
'unicorn/prefer-array-some': 'error',
'unicorn/prefer-bigint-literals': 'error',
'unicorn/prefer-blob-reading-methods': 'error',
'unicorn/prefer-class-fields': 'error',
'unicorn/prefer-classlist-toggle': 'error',
'unicorn/prefer-code-point': 'error',
'unicorn/prefer-date-now': 'error',
'unicorn/prefer-default-parameters': 'error',
'unicorn/prefer-dom-node-append': 'error',
'unicorn/prefer-dom-node-dataset': 'error',
'unicorn/prefer-dom-node-remove': 'error',
'unicorn/prefer-event-target': 'error',
'unicorn/prefer-export-from': ['error', { checkUsedVariables: false }],
'unicorn/prefer-includes': 'error',
'unicorn/prefer-keyboard-event-key': 'error',
'unicorn/prefer-logical-operator-over-ternary': 'error',
'unicorn/prefer-math-min-max': 'error',
'unicorn/prefer-math-trunc': 'error',
'unicorn/prefer-modern-dom-apis': 'error',
'unicorn/prefer-modern-math-apis': 'error',
'unicorn/prefer-native-coercion-functions': 'error',
'unicorn/prefer-negative-index': 'error',
'unicorn/prefer-node-protocol': 'error',
'unicorn/prefer-number-properties': 'error',
'unicorn/prefer-object-from-entries': 'error',
'unicorn/prefer-optional-catch-binding': 'error',
'unicorn/prefer-prototype-methods': 'error',
'unicorn/prefer-query-selector': 'error',
'unicorn/prefer-reflect-apply': 'error',
'unicorn/prefer-regexp-test': 'error',
'unicorn/prefer-response-static-json': 'error',
'unicorn/prefer-set-has': 'error',
'unicorn/prefer-set-size': 'error',
'unicorn/prefer-single-call': 'error',
'unicorn/prefer-spread': 'error',
'unicorn/prefer-string-raw': 'error',
'unicorn/prefer-string-replace-all': 'error',
'unicorn/prefer-string-slice': 'error',
'unicorn/prefer-string-starts-ends-with': 'error',
'unicorn/prefer-string-trim-start-end': 'error',
// oxlint 实现较 eslint 更严格(含 cloneDeep暂关闭以保持迁移前行为
'unicorn/prefer-structured-clone': 'off',
'unicorn/prefer-ternary': 'error',
'unicorn/prefer-type-error': 'error',
'unicorn/relative-url-style': 'error',
'unicorn/require-array-join-separator': 'error',
'unicorn/require-module-attributes': 'error',
'unicorn/require-module-specifiers': 'error',
'unicorn/require-number-to-fixed-digits-argument': 'error',
'unicorn/switch-case-braces': 'error',
'unicorn/switch-case-break-position': 'error',
'unicorn/text-encoding-identifier-case': 'error',
'unicorn/throw-new-error': 'error',
},
};

View File

@@ -6,7 +6,7 @@ import fs from 'node:fs';
import fsp from 'node:fs/promises';
import { join } from 'node:path';
import archiver from 'archiver';
import { ZipArchive } from 'archiver';
export const viteArchiverPlugin = (
options: ArchiverPluginOptions = {},
@@ -49,7 +49,8 @@ async function zipFolder(
): Promise<void> {
return new Promise((resolve, reject) => {
const output = fs.createWriteStream(outputPath);
const archive = archiver('zip', {
const archive = new ZipArchive({
zlib: { level: 9 }, // 设置压缩级别为 9 以实现最高压缩率
});