This commit is contained in:
Your Name
2026-08-27 14:04:28 +08:00
parent f7720831be
commit 334890171e
3016 changed files with 263403 additions and 27971 deletions
@@ -0,0 +1,6 @@
// eslint-disable-next-line n/no-extraneous-import
import type { UserConfig } from '@commitlint/types';
declare const userConfig: UserConfig;
export default userConfig;
@@ -0,0 +1,153 @@
import { execSync } from 'node:child_process';
import { getPackagesSync } from '@vben/node-utils';
const { packages } = getPackagesSync();
const allowedScopes = [
...packages.map((pkg) => pkg.packageJson.name),
'project',
'style',
'lint',
'ci',
'dev',
'deploy',
'other',
];
// precomputed scope
const scopeComplete = execSync('git status --porcelain || true')
.toString()
.trim()
.split('\n')
.find((r) => ~r.indexOf('M src'))
?.replaceAll(/(\/)/g, '%%')
?.match(/src%%((\w|-)*)/)?.[1]
?.replace(/s$/, '');
/**
* @type {import('cz-git').UserConfig}
*/
const userConfig = {
extends: ['@commitlint/config-conventional'],
plugins: ['commitlint-plugin-function-rules'],
prompt: {
/** @use `pnpm commit :f` */
alias: {
b: 'build: bump dependencies',
c: 'chore: update config',
f: 'docs: fix typos',
r: 'docs: update README',
s: 'style: update code format',
},
allowCustomIssuePrefixs: false,
// scopes: [...scopes, 'mock'],
allowEmptyIssuePrefixs: false,
customScopesAlign: scopeComplete ? 'bottom' : 'top',
defaultScope: scopeComplete,
// English
typesAppend: [
{ name: 'workflow: workflow improvements', value: 'workflow' },
{ name: 'types: type definition file changes', value: 'types' },
],
// 中英文对照版
// messages: {
// type: '选择你要提交的类型 :',
// scope: '选择一个提交范围 (可选):',
// customScope: '请输入自定义的提交范围 :',
// subject: '填写简短精炼的变更描述 :\n',
// body: '填写更加详细的变更描述 (可选)。使用 "|" 换行 :\n',
// breaking: '列举非兼容性重大的变更 (可选)。使用 "|" 换行 :\n',
// footerPrefixsSelect: '选择关联issue前缀 (可选):',
// customFooterPrefixs: '输入自定义issue前缀 :',
// footer: '列举关联issue (可选) 例如: #31, #I3244 :\n',
// confirmCommit: '是否提交或修改commit ?',
// },
// types: [
// { value: 'feat', name: 'feat: 新增功能' },
// { value: 'fix', name: 'fix: 修复缺陷' },
// { value: 'docs', name: 'docs: 文档变更' },
// { value: 'style', name: 'style: 代码格式' },
// { value: 'refactor', name: 'refactor: 代码重构' },
// { value: 'perf', name: 'perf: 性能优化' },
// { value: 'test', name: 'test: 添加疏漏测试或已有测试改动' },
// { value: 'build', name: 'build: 构建流程、外部依赖变更 (如升级 npm 包、修改打包配置等)' },
// { value: 'ci', name: 'ci: 修改 CI 配置、脚本' },
// { value: 'revert', name: 'revert: 回滚 commit' },
// { value: 'chore', name: 'chore: 对构建过程或辅助工具和库的更改 (不影响源文件、测试用例)' },
// { value: 'wip', name: 'wip: 正在开发中' },
// { value: 'workflow', name: 'workflow: 工作流程改进' },
// { value: 'types', name: 'types: 类型定义文件修改' },
// ],
// emptyScopesAlias: 'empty: 不填写',
// customScopesAlias: 'custom: 自定义',
},
rules: {
/**
* type[scope]: [function] description
*
* ^^^^^^^^^^^^^^ empty line.
* - Something here
*/
'body-leading-blank': [2, 'always'],
/**
* type[scope]: [function] description
*
* - something here
*
* ^^^^^^^^^^^^^^
*/
'footer-leading-blank': [1, 'always'],
/**
* type[scope]: [function] description
* ^^^^^
*/
'function-rules/scope-enum': [
2, // level: error
'always',
(parsed) => {
if (!parsed.scope || allowedScopes.includes(parsed.scope)) {
return [true];
}
return [false, `scope must be one of ${allowedScopes.join(', ')}`];
},
],
/**
* type[scope]: [function] description [No more than 108 characters]
* ^^^^^
*/
'header-max-length': [2, 'always', 108],
'scope-enum': [0],
'subject-case': [0],
'subject-empty': [2, 'never'],
'type-empty': [2, 'never'],
/**
* type[scope]: [function] description
* ^^^^
*/
'type-enum': [
2,
'always',
[
'feat',
'fix',
'perf',
'style',
'docs',
'test',
'refactor',
'build',
'ci',
'chore',
'revert',
'types',
'release',
],
],
},
};
export default userConfig;
@@ -0,0 +1,34 @@
{
"name": "@vben/commitlint-config",
"version": "5.7.0",
"private": true,
"homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": {
"type": "git",
"url": "git+https://github.com/vbenjs/vue-vben-admin.git",
"directory": "internal/lint-configs/commitlint-config"
},
"license": "MIT",
"type": "module",
"files": [
"dist"
],
"main": "./index.mjs",
"module": "./index.mjs",
"exports": {
".": {
"types": "./index.d.ts",
"import": "./index.mjs",
"default": "./index.mjs"
}
},
"dependencies": {
"@commitlint/cli": "catalog:",
"@commitlint/config-conventional": "catalog:",
"@vben/node-utils": "workspace:*",
"commitlint-plugin-function-rules": "catalog:",
"cz-git": "catalog:",
"czg": "catalog:"
}
}
@@ -0,0 +1,45 @@
{
"name": "@vben/eslint-config",
"version": "5.7.0",
"private": true,
"homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": {
"type": "git",
"url": "git+https://github.com/vbenjs/vue-vben-admin.git",
"directory": "internal/lint-configs/eslint-config"
},
"license": "MIT",
"type": "module",
"scripts": {
"stub": "pnpm exec tsdown"
},
"files": [
"dist"
],
"main": "./dist/index.mjs",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.mjs"
}
},
"dependencies": {
"@eslint/js": "catalog:",
"@typescript-eslint/parser": "catalog:",
"@vben/oxlint-config": "workspace:*",
"eslint": "catalog:",
"eslint-plugin-jsonc": "catalog:",
"eslint-plugin-n": "catalog:",
"eslint-plugin-perfectionist": "catalog:",
"eslint-plugin-pnpm": "catalog:",
"eslint-plugin-unused-imports": "catalog:",
"eslint-plugin-vue": "catalog:",
"eslint-plugin-yml": "catalog:",
"globals": "catalog:",
"vue-eslint-parser": "catalog:",
"yaml-eslint-parser": "catalog:"
}
}
@@ -0,0 +1,60 @@
import type { Linter } from 'eslint';
export async function ignores(): Promise<Linter.Config[]> {
return [
{
ignores: [
'**/node_modules',
'**/dist',
'**/dist-*',
'**/*-dist',
'**/.husky',
'**/.nitro',
'**/.output',
'**/Dockerfile',
'**/package-lock.json',
'**/yarn.lock',
'**/pnpm-lock.yaml',
'**/bun.lockb',
'**/output',
'**/coverage',
'**/temp',
'**/.temp',
'**/tmp',
'**/.tmp',
'**/.history',
'**/.turbo',
'**/.nuxt',
'**/.next',
'**/.vercel',
'**/.changeset',
'**/.idea',
'**/.cache',
'**/.output',
'**/.vite-inspect',
'**/CHANGELOG*.md',
'**/*.min.*',
'**/LICENSE*',
'**/__snapshots__',
'**/*.snap',
'**/fixtures/**',
'**/.vitepress/cache/**',
'**/auto-import?(s).d.ts',
'**/components.d.ts',
'**/vite.config.mts.*',
'**/*.sh',
'**/*.ttf',
'**/*.woff',
'**/.github',
'**/lefthook.yml',
'**/.agent/**',
'**/.agents/**',
'**/.codex/**',
'**/.claude/**',
'**/.cursor/**',
],
},
];
}
@@ -0,0 +1,9 @@
export * from './ignores';
export * from './javascript';
export * from './jsonc';
export * from './node';
export * from './perfectionist';
export * from './pnpm';
export * from './typescript';
export * from './vue';
export * from './yaml';
@@ -0,0 +1,185 @@
import type { Linter } from 'eslint';
import js from '@eslint/js';
import pluginUnusedImports from 'eslint-plugin-unused-imports';
import globals from 'globals';
const rulesCoveredByOxlint = new Set([
'constructor-super',
'for-direction',
'getter-return',
'no-async-promise-executor',
'no-case-declarations',
'no-class-assign',
'no-compare-neg-zero',
'no-cond-assign',
'no-const-assign',
'no-constant-binary-expression',
'no-constant-condition',
'no-control-regex',
'no-debugger',
'no-delete-var',
'no-dupe-args',
'no-dupe-class-members',
'no-dupe-else-if',
'no-dupe-keys',
'no-duplicate-case',
'no-empty',
'no-empty-character-class',
'no-empty-pattern',
'no-empty-static-block',
'no-ex-assign',
'no-extra-boolean-cast',
'no-fallthrough',
'no-func-assign',
'no-global-assign',
'no-import-assign',
'no-invalid-regexp',
'no-irregular-whitespace',
'no-loss-of-precision',
'no-misleading-character-class',
'no-new-native-nonconstructor',
'no-nonoctal-decimal-escape',
'no-obj-calls',
'no-prototype-builtins',
'no-redeclare',
'no-regex-spaces',
'no-self-assign',
'no-setter-return',
'no-shadow-restricted-names',
'no-sparse-arrays',
'no-this-before-super',
'no-unassigned-vars',
'no-unexpected-multiline',
'no-unreachable',
'no-unsafe-finally',
'no-unsafe-negation',
'no-unsafe-optional-chaining',
'no-unused-labels',
'no-unused-private-class-members',
'no-unused-vars',
'no-useless-backreference',
'no-useless-catch',
'no-useless-escape',
'no-with',
'preserve-caught-error',
'require-yield',
'use-isnan',
'valid-typeof',
]);
export async function javascript(): Promise<Linter.Config[]> {
const recommendedRules = Object.fromEntries(
Object.entries(js.configs.recommended.rules).filter(
([ruleName]) => !rulesCoveredByOxlint.has(ruleName),
),
);
return [
{
languageOptions: {
ecmaVersion: 'latest',
globals: {
...globals.browser,
...globals.es2021,
...globals.node,
document: 'readonly',
navigator: 'readonly',
window: 'readonly',
},
parserOptions: {
ecmaFeatures: {
jsx: true,
},
ecmaVersion: 'latest',
sourceType: 'module',
},
sourceType: 'module',
},
linterOptions: {
reportUnusedDisableDirectives: true,
},
plugins: {
'unused-imports': pluginUnusedImports,
},
rules: {
...recommendedRules,
'dot-notation': ['error', { allowKeywords: true }],
'keyword-spacing': 'off',
'no-empty-function': 'off',
'no-octal': 'error',
'no-octal-escape': 'error',
'no-restricted-properties': [
'error',
{
message:
'Use `Object.getPrototypeOf` or `Object.setPrototypeOf` instead.',
property: '__proto__',
},
{
message: 'Use `Object.defineProperty` instead.',
property: '__defineGetter__',
},
{
message: 'Use `Object.defineProperty` instead.',
property: '__defineSetter__',
},
{
message: 'Use `Object.getOwnPropertyDescriptor` instead.',
property: '__lookupGetter__',
},
{
message: 'Use `Object.getOwnPropertyDescriptor` instead.',
property: '__lookupSetter__',
},
],
'no-restricted-syntax': [
'error',
'DebuggerStatement',
'LabeledStatement',
'WithStatement',
'TSEnumDeclaration[const=true]',
'TSExportAssignment',
],
'no-undef-init': 'error',
'no-undef': 'off',
'no-unreachable-loop': 'error',
'object-shorthand': [
'error',
'always',
{
avoidQuotes: true,
ignoreConstructors: false,
},
],
'one-var': ['error', { initialized: 'never' }],
'prefer-arrow-callback': [
'error',
{
allowNamedFunctions: false,
allowUnboundThis: true,
},
],
'prefer-regex-literals': [
'error',
{
disallowRedundantWrapping: true,
},
],
'spaced-comment': 'error',
'space-before-function-paren': 'off',
'unused-imports/no-unused-imports': 'error',
'unused-imports/no-unused-vars': [
'error',
{
args: 'after-used',
argsIgnorePattern: '^_',
vars: 'all',
varsIgnorePattern: '^_',
},
],
},
},
];
}
@@ -0,0 +1,269 @@
import type { Linter } from 'eslint';
import { interopDefault } from '../util';
export async function jsonc(): Promise<Linter.Config[]> {
const pluginJsonc = await interopDefault(import('eslint-plugin-jsonc'));
return [
{
files: ['**/*.json', '**/*.json5', '**/*.jsonc', '*.code-workspace'],
language: 'jsonc/x',
plugins: {
jsonc: pluginJsonc as any,
},
rules: {
'jsonc/no-bigint-literals': 'error',
'jsonc/no-binary-expression': 'error',
'jsonc/no-binary-numeric-literals': 'error',
'jsonc/no-dupe-keys': 'error',
'jsonc/no-escape-sequence-in-identifier': 'error',
'jsonc/no-floating-decimal': 'error',
'jsonc/no-hexadecimal-numeric-literals': 'error',
'jsonc/no-infinity': 'error',
'jsonc/no-multi-str': 'error',
'jsonc/no-nan': 'error',
'jsonc/no-number-props': 'error',
'jsonc/no-numeric-separators': 'error',
'jsonc/no-octal': 'error',
'jsonc/no-octal-escape': 'error',
'jsonc/no-octal-numeric-literals': 'error',
'jsonc/no-parenthesized': 'error',
'jsonc/no-plus-sign': 'error',
'jsonc/no-regexp-literals': 'error',
'jsonc/no-sparse-arrays': 'error',
'jsonc/no-template-literals': 'error',
'jsonc/no-undefined-value': 'error',
'jsonc/no-unicode-codepoint-escapes': 'error',
'jsonc/no-useless-escape': 'error',
'jsonc/space-unary-ops': 'error',
'jsonc/valid-json-number': 'error',
'jsonc/vue-custom-block/no-parsing-error': 'error',
},
},
sortTsconfig(),
sortPackageJson(),
sortCspellJson(),
];
}
function sortPackageJson(): Linter.Config {
return {
files: ['**/package.json'],
rules: {
'jsonc/sort-array-values': [
'error',
{
order: { type: 'asc' },
pathPattern: '^files$|^pnpm.neverBuiltDependencies$',
},
],
'jsonc/sort-keys': [
'error',
{
order: [
'name',
'version',
'description',
'private',
'keywords',
'homepage',
'bugs',
'repository',
'license',
'author',
'contributors',
'categories',
'funding',
'type',
'scripts',
'files',
'sideEffects',
'bin',
'main',
'module',
'unpkg',
'jsdelivr',
'types',
'typesVersions',
'imports',
'exports',
'publishConfig',
'icon',
'activationEvents',
'contributes',
'peerDependencies',
'peerDependenciesMeta',
'dependencies',
'optionalDependencies',
'devDependencies',
'engines',
'packageManager',
'pnpm',
'overrides',
'resolutions',
'husky',
'simple-git-hooks',
'lint-staged',
'eslintConfig',
],
pathPattern: '^$',
},
{
order: { type: 'asc' },
pathPattern: '^(?:dev|peer|optional|bundled)?[Dd]ependencies(Meta)?$',
},
{
order: { type: 'asc' },
pathPattern: '^(?:resolutions|overrides|pnpm.overrides)$',
},
{
order: ['types', 'import', 'require', 'default'],
pathPattern: '^exports.*$',
},
],
},
};
}
function sortCspellJson(): Linter.Config {
return {
files: ['**/cspell.json', '**/.cspell.json'],
rules: {
'jsonc/sort-array-values': [
'error',
{
order: { type: 'asc' },
pathPattern: '^words$|^ignorePaths$',
},
],
},
};
}
function sortTsconfig(): Linter.Config {
return {
files: [
'**/tsconfig.json',
'**/tsconfig.*.json',
'internal/tsconfig/*.json',
],
rules: {
'jsonc/sort-keys': [
'error',
{
order: [
'extends',
'compilerOptions',
'references',
'files',
'include',
'exclude',
],
pathPattern: '^$',
},
{
order: [
/* Projects */
'incremental',
'composite',
'tsBuildInfoFile',
'disableSourceOfProjectReferenceRedirect',
'disableSolutionSearching',
'disableReferencedProjectLoad',
/* Language and Environment */
'target',
'jsx',
'jsxFactory',
'jsxFragmentFactory',
'jsxImportSource',
'lib',
'moduleDetection',
'noLib',
'reactNamespace',
'useDefineForClassFields',
'emitDecoratorMetadata',
'experimentalDecorators',
/* Modules */
'baseUrl',
'rootDir',
'rootDirs',
'customConditions',
'module',
'moduleResolution',
'moduleSuffixes',
'noResolve',
'paths',
'resolveJsonModule',
'resolvePackageJsonExports',
'resolvePackageJsonImports',
'typeRoots',
'types',
'allowArbitraryExtensions',
'allowImportingTsExtensions',
'allowUmdGlobalAccess',
/* JavaScript Support */
'allowJs',
'checkJs',
'maxNodeModuleJsDepth',
/* Type Checking */
'strict',
'strictBindCallApply',
'strictFunctionTypes',
'strictNullChecks',
'strictPropertyInitialization',
'allowUnreachableCode',
'allowUnusedLabels',
'alwaysStrict',
'exactOptionalPropertyTypes',
'noFallthroughCasesInSwitch',
'noImplicitAny',
'noImplicitOverride',
'noImplicitReturns',
'noImplicitThis',
'noPropertyAccessFromIndexSignature',
'noUncheckedIndexedAccess',
'noUnusedLocals',
'noUnusedParameters',
'useUnknownInCatchVariables',
/* Emit */
'declaration',
'declarationDir',
'declarationMap',
'downlevelIteration',
'emitBOM',
'emitDeclarationOnly',
'importHelpers',
'importsNotUsedAsValues',
'inlineSourceMap',
'inlineSources',
'mapRoot',
'newLine',
'noEmit',
'noEmitHelpers',
'noEmitOnError',
'outDir',
'outFile',
'preserveConstEnums',
'preserveValueImports',
'removeComments',
'sourceMap',
'sourceRoot',
'stripInternal',
/* Interop Constraints */
'allowSyntheticDefaultImports',
'esModuleInterop',
'forceConsistentCasingInFileNames',
'isolatedModules',
'preserveSymlinks',
'verbatimModuleSyntax',
/* Completeness */
'skipDefaultLibCheck',
'skipLibCheck',
],
pathPattern: '^compilerOptions$',
},
],
},
};
}
@@ -0,0 +1,81 @@
import type { Linter } from 'eslint';
import { interopDefault } from '../util';
export async function node(): Promise<Linter.Config[]> {
const pluginNode = await interopDefault(import('eslint-plugin-n'));
return [
{
plugins: {
n: pluginNode,
},
rules: {
'n/handle-callback-err': ['error', '^(err|error)$'],
'n/no-deprecated-api': 'error',
'n/no-extraneous-import': [
'error',
{
allowModules: [
'tsdown',
'unplugin-vue',
'@vben/vite-config',
'vitest',
'vite',
'@vue/test-utils',
'@playwright/test',
],
},
],
// 'n/no-unpublished-import': 'off',
'n/no-unsupported-features/es-syntax': [
'error',
{
ignores: [],
version: '>=22.18.0',
},
],
'n/prefer-global/buffer': ['error', 'never'],
// 'n/no-missing-import': 'off',
'n/prefer-global/process': ['error', 'never'],
'n/process-exit-as-throw': 'error',
},
},
{
files: [
'**/__tests__/**/*.?([cm])[jt]s?(x)',
'**/*.spec.?([cm])[jt]s?(x)',
'**/*.test.?([cm])[jt]s?(x)',
'**/*.bench.?([cm])[jt]s?(x)',
'**/*.benchmark.?([cm])[jt]s?(x)',
],
rules: {
'n/prefer-global/process': 'off',
},
},
{
files: ['apps/backend-mock/**/**', 'docs/**/**'],
rules: {
'n/no-extraneous-import': 'off',
'n/prefer-global/buffer': 'off',
'n/prefer-global/process': 'off',
},
},
{
files: ['**/**/playwright.config.ts'],
rules: {
'n/prefer-global/buffer': 'off',
'n/prefer-global/process': 'off',
},
},
{
files: [
'scripts/**/*.?([cm])[jt]s?(x)',
'internal/**/*.?([cm])[jt]s?(x)',
],
rules: {
'n/prefer-global/process': 'off',
},
},
];
}
@@ -0,0 +1,105 @@
import type { Linter } from 'eslint';
import { interopDefault } from '../util';
export async function perfectionist(): Promise<Linter.Config[]> {
const perfectionistPlugin = await interopDefault(
import('eslint-plugin-perfectionist'),
);
return [
perfectionistPlugin.configs['recommended-natural'],
{
rules: {
'perfectionist/sort-exports': [
'error',
{
order: 'asc',
type: 'natural',
},
],
'perfectionist/sort-imports': [
'error',
{
customGroups: [
{
selector: 'type',
groupName: 'vben-core-type',
elementNamePattern: '^@vben-core/.+',
},
{
selector: 'type',
groupName: 'vben-type',
elementNamePattern: '^@vben/.+',
},
{
selector: 'type',
groupName: 'vue-type',
elementNamePattern: ['^vue$', '^vue-.+', '^@vue/.+'],
},
{
groupName: 'vben',
elementNamePattern: '^@vben/.+',
},
{
groupName: 'vben-core',
elementNamePattern: '^@vben-core/.+',
},
{
groupName: 'vue',
elementNamePattern: ['^vue$', '^vue-.+', '^@vue/.+'],
},
],
environment: 'node',
groups: [
['type-external', 'type-builtin', 'type-import'],
'vue-type',
'vben-type',
'vben-core-type',
['type-parent', 'type-sibling', 'type-index'],
['type-internal'],
'value-builtin',
'vue',
'vben',
'vben-core',
'value-external',
'value-internal',
['value-parent', 'value-sibling', 'value-index'],
'side-effect',
'side-effect-style',
'style',
'ts-equals-import',
'unknown',
],
internalPattern: ['^#/.+'],
newlinesBetween: 1,
order: 'asc',
type: 'natural',
},
],
'perfectionist/sort-modules': 'off',
'perfectionist/sort-named-exports': [
'error',
{
order: 'asc',
type: 'natural',
},
],
'perfectionist/sort-objects': [
'off',
{
customGroups: {
items: 'items',
list: 'list',
children: 'children',
},
groups: ['unknown', 'items', 'list', 'children'],
ignorePattern: ['children'],
order: 'asc',
type: 'natural',
},
],
},
},
];
}
@@ -0,0 +1,38 @@
import type { Linter } from 'eslint';
import { interopDefault } from '../util';
export async function pnpm(): Promise<Linter.Config[]> {
const [pluginPnpm, parserPnpm] = await Promise.all([
interopDefault(import('eslint-plugin-pnpm')),
interopDefault(import('yaml-eslint-parser')),
] as const);
return [
{
files: ['package.json', '**/package.json'],
language: 'jsonc/x',
plugins: {
pnpm: pluginPnpm,
},
rules: {
'pnpm/json-enforce-catalog': 'error',
'pnpm/json-prefer-workspace-settings': 'error',
'pnpm/json-valid-catalog': 'error',
},
},
{
files: ['pnpm-workspace.yaml'],
languageOptions: {
parser: parserPnpm,
},
plugins: {
pnpm: pluginPnpm,
},
rules: {
'pnpm/yaml-no-duplicate-catalog-item': 'error',
'pnpm/yaml-no-unused-catalog-item': 'error',
},
},
];
}
@@ -0,0 +1,44 @@
import type { Linter } from 'eslint';
import { interopDefault } from '../util';
/**
* @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 parserTs = await interopDefault(import('@typescript-eslint/parser'));
return [
{
files: ['**/*.?([cm])[jt]s?(x)'],
languageOptions: {
parser: parserTs,
parserOptions: {
ecmaFeatures: {
jsx: true,
},
ecmaVersion: 'latest',
extraFileExtensions: ['.vue'],
jsxPragma: 'React',
sourceType: 'module',
},
},
rules: {
'unused-imports/no-unused-vars': 'off',
},
},
{
// Vue `<script>` 的未使用变量同样交给 oxlint,避免核心规则误报 TS 类型签名形参
files: ['**/*.vue'],
rules: {
'unused-imports/no-unused-vars': 'off',
},
},
];
}
@@ -0,0 +1,151 @@
import type { Linter } from 'eslint';
import { interopDefault } from '../util';
export async function vue(): Promise<Linter.Config[]> {
const [pluginVue, parserVue, parserTs] = await Promise.all([
interopDefault(import('eslint-plugin-vue')),
interopDefault(import('vue-eslint-parser')),
interopDefault(import('@typescript-eslint/parser')),
] as const);
const flatEssential = pluginVue.configs?.['flat/essential'] || [];
const flatStronglyRecommended =
pluginVue.configs?.['flat/strongly-recommended'] || [];
const flatRecommended = pluginVue.configs?.['flat/recommended'] || [];
return [
...flatEssential,
...flatStronglyRecommended,
...flatRecommended,
{
files: ['**/*.vue'],
languageOptions: {
// globals: {
// computed: 'readonly',
// defineEmits: 'readonly',
// defineExpose: 'readonly',
// defineProps: 'readonly',
// onMounted: 'readonly',
// onUnmounted: 'readonly',
// reactive: 'readonly',
// ref: 'readonly',
// shallowReactive: 'readonly',
// shallowRef: 'readonly',
// toRef: 'readonly',
// toRefs: 'readonly',
// watch: 'readonly',
// watchEffect: 'readonly',
// },
parser: parserVue,
parserOptions: {
ecmaFeatures: {
jsx: true,
},
extraFileExtensions: ['.vue'],
parser: parserTs,
sourceType: 'module',
},
},
plugins: {
vue: pluginVue,
},
processor: pluginVue.processors?.['.vue'],
rules: {
...pluginVue.configs?.base?.rules,
'vue/attribute-hyphenation': [
'error',
'always',
{
ignore: [],
},
],
'vue/attributes-order': 'off',
'vue/block-order': [
'error',
{
order: ['script', 'template', 'style'],
},
],
'vue/component-name-in-template-casing': ['error', 'PascalCase'],
'vue/component-options-name-casing': ['error', 'PascalCase'],
'vue/custom-event-name-casing': ['error', 'camelCase'],
'vue/define-macros-order': [
'error',
{
order: [
'defineOptions',
'defineProps',
'defineEmits',
'defineSlots',
],
},
],
'vue/dot-location': ['error', 'property'],
'vue/dot-notation': ['error', { allowKeywords: true }],
'vue/eqeqeq': ['error', 'smart'],
'vue/html-closing-bracket-newline': 'error',
'vue/html-indent': 'off',
// 'vue/html-indent': ['error', 2],
'vue/html-quotes': ['error', 'double'],
'vue/html-self-closing': [
'error',
{
html: {
component: 'always',
normal: 'never',
void: 'always',
},
math: 'always',
svg: 'always',
},
],
'vue/max-attributes-per-line': 'off',
'vue/multi-word-component-names': 'off',
'vue/multiline-html-element-content-newline': 'error',
'vue/no-empty-pattern': 'error',
'vue/no-extra-parens': ['error', 'functions'],
'vue/no-irregular-whitespace': 'error',
'vue/no-loss-of-precision': 'error',
'vue/no-reserved-component-names': 'off',
'vue/no-restricted-syntax': [
'error',
'DebuggerStatement',
'LabeledStatement',
'WithStatement',
],
'vue/no-restricted-v-bind': ['error', '/^v-/'],
'vue/no-sparse-arrays': 'error',
'vue/no-unused-refs': 'error',
'vue/no-useless-v-bind': 'error',
'vue/object-shorthand': [
'error',
'always',
{
avoidQuotes: true,
ignoreConstructors: false,
},
],
'vue/one-component-per-file': 'error',
'vue/prefer-separate-static-class': 'error',
'vue/prefer-template': 'error',
'vue/prop-name-casing': ['error', 'camelCase'],
'vue/require-default-prop': 'error',
'vue/require-explicit-emits': 'error',
'vue/require-prop-types': 'off',
'vue/singleline-html-element-content-newline': 'off',
'vue/space-infix-ops': 'error',
'vue/space-unary-ops': ['error', { nonwords: false, words: true }],
'vue/v-on-event-hyphenation': [
'error',
'always',
{
autofix: true,
ignore: [],
},
],
},
},
];
}
@@ -0,0 +1,93 @@
import type { Linter } from 'eslint';
import { interopDefault } from '../util';
export async function yaml(): Promise<Linter.Config[]> {
const [pluginYaml, parserYaml] = await Promise.all([
interopDefault(import('eslint-plugin-yml')),
interopDefault(import('yaml-eslint-parser')),
] as const);
return [
{
files: ['**/*.y?(a)ml'],
plugins: {
yaml: pluginYaml,
},
languageOptions: {
parser: parserYaml,
},
rules: {
'style/spaced-comment': 'off',
'yaml/block-mapping': 'error',
'yaml/block-sequence': 'error',
'yaml/no-empty-key': 'error',
'yaml/no-empty-sequence-entry': 'error',
'yaml/no-irregular-whitespace': 'error',
'yaml/plain-scalar': 'error',
'yaml/vue-custom-block/no-parsing-error': 'error',
'yaml/block-mapping-question-indicator-newline': 'error',
'yaml/block-sequence-hyphen-indicator-newline': 'error',
'yaml/flow-mapping-curly-newline': 'error',
'yaml/flow-mapping-curly-spacing': 'error',
'yaml/flow-sequence-bracket-newline': 'error',
'yaml/flow-sequence-bracket-spacing': 'error',
'yaml/indent': ['error', 2],
'yaml/key-spacing': 'error',
'yaml/no-tab-indent': 'error',
'yaml/quotes': [
'error',
{
avoidEscape: true,
prefer: 'single',
},
],
'yaml/spaced-comment': 'error',
},
},
{
files: ['pnpm-workspace.yaml'],
rules: {
'yaml/sort-keys': [
'error',
{
order: [
'packages',
'publicHoistPattern',
'strictPeerDependencies',
'autoInstallPeers',
'dedupePeerDependents',
'verifyDepsBeforeRun',
'overrides',
'patchedDependencies',
'hoistPattern',
'catalog',
'catalogs',
'allowedDeprecatedVersions',
'allowBuilds',
'allowNonAppliedPatches',
'configDependencies',
'ignoredBuiltDependencies',
'ignoredOptionalDependencies',
'neverBuiltDependencies',
'onlyBuiltDependencies',
'onlyBuiltDependenciesFile',
'packageExtensions',
'peerDependencyRules',
'supportedArchitectures',
],
pathPattern: '^$',
},
{
order: { type: 'asc' },
pathPattern: '^.+$',
},
],
},
},
];
}
@@ -0,0 +1,154 @@
import type { Linter } from 'eslint';
const restrictedImportIgnores = ['**/vite.config.mts'];
const customConfig: Linter.Config[] = [
// shadcn-ui 内部组件是自动生成的,不做太多限制
{
files: ['packages/@core/ui-kit/shadcn-ui/**/**'],
rules: {
'vue/require-default-prop': 'off',
},
},
{
files: [
'apps/**/**',
'packages/effects/**/**',
'packages/utils/**/**',
'packages/types/**/**',
'packages/locales/**/**',
],
ignores: restrictedImportIgnores,
rules: {
'perfectionist/sort-interfaces': 'off',
},
},
{
// apps内部的一些基础规则
files: ['apps/**/**'],
ignores: restrictedImportIgnores,
rules: {
'no-restricted-imports': [
'error',
{
patterns: [
{
group: ['#/api/*'],
message:
'The #/api package cannot be imported, please use the @core package itself',
},
{
group: ['#/layouts/*'],
message:
'The #/layouts package cannot be imported, please use the @core package itself',
},
{
group: ['#/locales/*'],
message:
'The #/locales package cannot be imported, please use the @core package itself',
},
{
group: ['#/stores/*'],
message:
'The #/stores package cannot be imported, please use the @core package itself',
},
],
},
],
},
},
{
// @core内部组件,不能引入@vben/* 里面的包
files: ['packages/@core/**/**'],
ignores: restrictedImportIgnores,
rules: {
'no-restricted-imports': [
'error',
{
patterns: [
{
group: ['@vben/*'],
message:
'The @core package cannot import the @vben package, please use the @core package itself',
},
],
},
],
},
},
{
// @core/shared内部组件,不能引入@vben/* 或者 @vben-core/* 里面的包
files: ['packages/@core/base/**/**'],
ignores: restrictedImportIgnores,
rules: {
'no-restricted-imports': [
'error',
{
patterns: [
{
group: ['@vben/*', '@vben-core/*'],
message:
'The @vben-core/shared package cannot import the @vben package, please use the @core/shared package itself',
},
],
},
],
},
},
{
// 不能引入@vben/*里面的包
files: [
'packages/types/**/**',
'packages/utils/**/**',
'packages/icons/**/**',
'packages/constants/**/**',
'packages/styles/**/**',
'packages/stores/**/**',
'packages/preferences/**/**',
'packages/locales/**/**',
],
ignores: restrictedImportIgnores,
rules: {
'no-restricted-imports': [
'error',
{
patterns: [
{
group: ['@vben/*'],
message:
'The @vben package cannot be imported, please use the @core package itself',
},
],
},
],
},
},
// 后端模拟代码,不需要太多规则
{
files: ['apps/backend-mock/**/**', 'docs/**/**'],
rules: {
'no-console': 'off',
},
},
{
files: ['**/**/playwright.config.ts'],
rules: {
'no-console': 'off',
},
},
{
files: ['internal/**/**', 'scripts/**/**'],
rules: {
'no-console': 'off',
},
},
{
files: ['packages/@core/base/shared/src/utils/inference.ts'],
rules: {
'vue/prefer-import-from-vue': 'off',
},
},
];
export { customConfig };
@@ -0,0 +1,44 @@
import type { Linter } from 'eslint';
import {
ignores,
javascript,
jsonc,
node,
perfectionist,
pnpm,
typescript,
vue,
yaml,
} from './configs';
import { customConfig } from './custom-config';
type FlatConfig = Linter.Config;
type FlatConfigPromise =
| FlatConfig
| FlatConfig[]
| Promise<FlatConfig>
| Promise<FlatConfig[]>;
async function defineConfig(config: FlatConfig[] = []) {
const configs: FlatConfigPromise[] = [
vue(),
javascript(),
ignores(),
typescript(),
jsonc(),
node(),
perfectionist(),
yaml(),
pnpm(),
...customConfig,
...config,
];
const resolved = await Promise.all(configs);
return resolved.flat();
}
export { defineConfig };
@@ -0,0 +1,8 @@
export type Awaitable<T> = Promise<T> | T;
export async function interopDefault<T>(
m: Awaitable<T>,
): Promise<T extends { default: infer U } ? U : T> {
const resolved = await m;
return (resolved as any).default || resolved;
}
@@ -0,0 +1,6 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "@vben/tsconfig/node.json",
"include": ["src"],
"exclude": ["node_modules"]
}
@@ -0,0 +1,16 @@
import { defineConfig } from 'tsdown';
export default defineConfig({
clean: true,
deps: {
skipNodeModulesBundle: true,
},
dts: {
resolver: 'tsc',
},
entry: ['src/index.ts'],
format: ['esm'],
outExtensions: () => ({
dts: '.d.ts',
}),
});
@@ -0,0 +1,32 @@
{
"name": "@vben/oxfmt-config",
"version": "5.7.0",
"private": true,
"homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": {
"type": "git",
"url": "git+https://github.com/vbenjs/vue-vben-admin.git",
"directory": "internal/lint-configs/oxfmt-config"
},
"license": "MIT",
"type": "module",
"scripts": {
"stub": "pnpm exec tsdown"
},
"files": [
"dist"
],
"main": "./dist/index.mjs",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.mjs"
}
},
"dependencies": {
"oxfmt": "catalog:"
}
}
@@ -0,0 +1,135 @@
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: [
'*.json',
'*.json5',
'*.jsonc',
'*.code-workspace',
'**/*.json',
'**/*.json5',
'**/*.jsonc',
'**/*.code-workspace',
],
options: {
trailingComma: 'none',
quoteProps: 'preserve',
singleQuote: false,
},
},
],
});
function defineConfig(config: OxfmtConfig = {}): OxfmtConfig {
return defineOxfmtConfig({
...oxfmtConfig,
...config,
});
}
export { defineConfig, oxfmtConfig };
export type { OxfmtConfig };
@@ -0,0 +1,6 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "@vben/tsconfig/node.json",
"include": ["src"],
"exclude": ["node_modules"]
}
@@ -0,0 +1,11 @@
import { defineConfig } from 'tsdown';
export default defineConfig({
clean: true,
dts: true,
entry: ['src/index.ts'],
format: ['esm'],
outExtensions: () => ({
dts: '.d.ts',
}),
});
@@ -0,0 +1,35 @@
{
"name": "@vben/oxlint-config",
"version": "5.7.0",
"private": true,
"homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": {
"type": "git",
"url": "git+https://github.com/vbenjs/vue-vben-admin.git",
"directory": "internal/lint-configs/oxlint-config"
},
"license": "MIT",
"type": "module",
"scripts": {
"stub": "pnpm exec tsdown"
},
"files": [
"dist"
],
"main": "./dist/index.mjs",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.mjs"
}
},
"dependencies": {
"@eslint-community/eslint-plugin-eslint-comments": "catalog:",
"eslint-plugin-better-tailwindcss": "catalog:",
"eslint-plugin-command": "catalog:",
"oxlint": "catalog:"
}
}
@@ -0,0 +1,15 @@
import type { OxlintConfig } from 'oxlint';
const command: OxlintConfig = {
jsPlugins: [
{
name: 'command',
specifier: 'eslint-plugin-command',
},
],
rules: {
'command/command': 'error',
},
};
export { command };
@@ -0,0 +1,19 @@
import type { OxlintConfig } from 'oxlint';
const comments: OxlintConfig = {
jsPlugins: [
{
name: 'eslint-comments',
specifier: '@eslint-community/eslint-plugin-eslint-comments',
},
],
rules: {
'eslint/no-underscore-dangle': 'off',
'eslint-comments/no-aggregating-enable': 'error',
'eslint-comments/no-duplicate-disable': 'error',
'eslint-comments/no-unlimited-disable': 'error',
'eslint-comments/no-unused-enable': 'error',
},
};
export { comments };
@@ -0,0 +1,17 @@
import type { OxlintConfig } from 'oxlint';
const ignores: OxlintConfig = {
ignorePatterns: [
'**/dist/**',
'**/node_modules/**',
'docs/**',
'playground/public/**',
'**/*.json',
'**/*.md',
'**/*.svg',
'**/*.yaml',
'**/*.yml',
],
};
export { ignores };
@@ -0,0 +1,18 @@
import type { OxlintConfig } from 'oxlint';
const importPluginConfig: OxlintConfig = {
rules: {
'import/consistent-type-specifier-style': ['error', 'prefer-top-level'],
'import/first': 'error',
'import/no-duplicates': 'error',
'import/no-mutable-exports': 'error',
'import/no-named-as-default': 'off',
'import/no-named-as-default-member': 'off',
'import/no-named-default': 'error',
'import/no-self-import': 'error',
'import/no-unassigned-import': 'off',
'import/no-webpack-loader-syntax': 'error',
},
};
export { importPluginConfig };
@@ -0,0 +1,96 @@
import type { OxlintConfig } from 'oxlint';
import { defineConfig as defineOxlintConfig } from 'oxlint';
import { command } from './command';
import { comments } from './comments';
import { ignores } from './ignores';
import { importPluginConfig } from './import';
import { javascript } from './javascript';
import { node } from './node';
import { overrides } from './overrides';
import { plugins } from './plugins';
import { tailwindcss } from './tailwindcss';
import { test } from './test';
import { typescript } from './typescript';
import { unicorn } from './unicorn';
import { vue } from './vue';
function mergeOxlintConfigs(...configs: OxlintConfig[]): OxlintConfig {
const merged: OxlintConfig = {};
for (const config of configs) {
merged.categories =
merged.categories && config.categories
? { ...merged.categories, ...config.categories }
: (config.categories ?? merged.categories);
merged.env =
merged.env && config.env
? { ...merged.env, ...config.env }
: (config.env ?? merged.env);
merged.globals =
merged.globals && config.globals
? { ...merged.globals, ...config.globals }
: (config.globals ?? merged.globals);
merged.ignorePatterns = [
...(merged.ignorePatterns ?? []),
...(config.ignorePatterns ?? []),
];
merged.jsPlugins = [
...new Set([...(merged.jsPlugins ?? []), ...(config.jsPlugins ?? [])]),
];
merged.overrides = [
...(merged.overrides ?? []),
...(config.overrides ?? []),
];
merged.plugins = [
...new Set([...(merged.plugins ?? []), ...(config.plugins ?? [])]),
];
merged.rules =
merged.rules && config.rules
? { ...merged.rules, ...config.rules }
: (config.rules ?? merged.rules);
merged.settings =
merged.settings && config.settings
? { ...merged.settings, ...config.settings }
: (config.settings ?? merged.settings);
}
return merged;
}
const oxlintConfig = defineOxlintConfig(
mergeOxlintConfigs(
javascript,
command,
comments,
ignores,
plugins,
importPluginConfig,
node,
overrides,
tailwindcss,
test,
typescript,
unicorn,
vue,
),
);
export {
command,
comments,
ignores,
importPluginConfig,
javascript,
mergeOxlintConfigs,
node,
overrides,
oxlintConfig,
plugins,
tailwindcss,
test,
typescript,
unicorn,
vue,
};
@@ -0,0 +1,141 @@
import type { OxlintConfig } from 'oxlint';
const javascript: OxlintConfig = {
categories: {
correctness: 'error',
suspicious: 'warn',
},
env: {
browser: true,
es2021: true,
node: true,
},
globals: {
document: 'readonly',
navigator: 'readonly',
window: 'readonly',
},
rules: {
'accessor-pairs': [
'error',
{
enforceForClassMembers: true,
setWithoutGet: true,
},
],
'array-callback-return': 'error',
'block-scoped-var': 'error',
'default-case-last': 'error',
eqeqeq: ['error', 'always'],
'eslint/no-unreachable': 'error',
// 抛出嵌套三元运算格式错误,禁止使用嵌套三元运算。
'no-nested-ternary': 'error',
'new-cap': [
'error',
{
capIsNew: false,
newIsCap: true,
properties: true,
},
],
'no-alert': 'error',
'no-array-constructor': 'error',
'no-caller': 'error',
'no-case-declarations': 'error',
'no-console': ['error', { allow: ['warn', 'error'] }],
'no-control-regex': 'error',
'no-debugger': 'error',
'no-empty': ['error', { allowEmptyCatch: true }],
'no-fallthrough': 'error',
'no-new-func': 'error',
'no-object-constructor': 'error',
'no-new-native-nonconstructor': 'error',
'no-labels': ['error', { allowLoop: false, allowSwitch: false }],
'no-lone-blocks': 'error',
'no-multi-str': 'error',
'no-nonoctal-decimal-escape': 'error',
'no-proto': 'error',
'no-prototype-builtins': 'error',
'no-redeclare': ['error', { builtinGlobals: false }],
'no-regex-spaces': 'error',
'no-self-compare': 'error',
'no-sequences': 'error',
'no-shadow': 'off',
'no-shadow-restricted-names': 'error',
'eslint/no-empty-function': [
'error',
{
allow: ['arrowFunctions', 'functions', 'methods'],
},
],
'no-template-curly-in-string': 'error',
'no-throw-literal': 'error',
'no-unassigned-vars': 'error',
'no-unexpected-multiline': 'error',
'no-unused-expressions': [
'error',
{
allowShortCircuit: true,
allowTaggedTemplates: true,
allowTernary: true,
},
],
'eslint/no-unused-vars': [
'error',
{
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
},
],
'no-var': 'error',
'no-eval': 'error',
'no-iterator': 'error',
'no-new-wrappers': 'error',
'no-restricted-globals': [
'error',
{ message: 'Use `globalThis` instead.', name: 'global' },
{ message: 'Use `globalThis` instead.', name: 'self' },
],
'no-useless-call': 'error',
'no-useless-computed-key': 'error',
'no-useless-constructor': 'error',
'no-useless-return': 'error',
'prefer-const': [
'error',
{
destructuring: 'all',
ignoreReadBeforeAssign: true,
},
],
'prefer-exponentiation-operator': 'error',
'prefer-promise-reject-errors': 'error',
'prefer-rest-params': 'error',
'prefer-spread': 'error',
'prefer-template': 'error',
'preserve-caught-error': [
'error',
{
requireCatchParameter: false,
},
],
'symbol-description': 'error',
'unicode-bom': ['error', 'never'],
'use-isnan': [
'error',
{
enforceForIndexOf: true,
enforceForSwitchCase: true,
},
],
'valid-typeof': [
'error',
{
requireStringLiterals: true,
},
],
'vars-on-top': 'error',
yoda: ['error', 'never'],
},
};
export { javascript };
@@ -0,0 +1,11 @@
import type { OxlintConfig } from 'oxlint';
const node: OxlintConfig = {
rules: {
'node/no-exports-assign': 'error',
'node/no-new-require': 'error',
'node/no-path-concat': 'error',
},
};
export { node };
@@ -0,0 +1,107 @@
import type { OxlintConfig } from 'oxlint';
const overrides: OxlintConfig = {
overrides: [
{
files: ['*.d.ts', '**/*.d.ts'],
rules: {
'import/no-unassigned-import': 'off',
'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',
'**/__tests__/**/*.cjs',
'**/__tests__/**/*.mjs',
'**/__tests__/**/*.jsx',
'**/__tests__/**/*.ts',
'**/__tests__/**/*.cts',
'**/__tests__/**/*.mts',
'**/__tests__/**/*.tsx',
'**/*.spec.js',
'**/*.spec.cjs',
'**/*.spec.mjs',
'**/*.spec.jsx',
'**/*.spec.ts',
'**/*.spec.cts',
'**/*.spec.mts',
'**/*.spec.tsx',
'**/*.test.js',
'**/*.test.cjs',
'**/*.test.mjs',
'**/*.test.jsx',
'**/*.test.ts',
'**/*.test.cts',
'**/*.test.mts',
'**/*.test.tsx',
'**/*.bench.js',
'**/*.bench.cjs',
'**/*.bench.mjs',
'**/*.bench.jsx',
'**/*.bench.ts',
'**/*.bench.cts',
'**/*.bench.mts',
'**/*.bench.tsx',
'**/*.benchmark.js',
'**/*.benchmark.cjs',
'**/*.benchmark.mjs',
'**/*.benchmark.jsx',
'**/*.benchmark.ts',
'**/*.benchmark.cts',
'**/*.benchmark.mts',
'**/*.benchmark.tsx',
],
rules: {
'no-console': 'off',
},
},
{
files: ['packages/@core/base/shared/src/utils/inference.ts'],
rules: {
'vue/prefer-import-from-vue': 'off',
},
},
{
files: ['packages/@core/ui-kit/menu-ui/src/sub-menu.vue'],
rules: {
'import/no-self-import': 'off',
},
},
{
files: [
'scripts/**/*.js',
'scripts/**/*.cjs',
'scripts/**/*.mjs',
'scripts/**/*.jsx',
'scripts/**/*.ts',
'scripts/**/*.cts',
'scripts/**/*.mts',
'scripts/**/*.tsx',
'internal/**/*.js',
'internal/**/*.cjs',
'internal/**/*.mjs',
'internal/**/*.jsx',
'internal/**/*.ts',
'internal/**/*.cts',
'internal/**/*.mts',
'internal/**/*.tsx',
],
rules: {
'no-console': 'off',
'unicorn/no-process-exit': 'off',
},
},
],
};
export { overrides };
@@ -0,0 +1,24 @@
import type { OxlintConfig } from 'oxlint';
const plugins: OxlintConfig = {
/**
* 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 };
@@ -0,0 +1,56 @@
import type { OxlintConfig } from 'oxlint';
import { fileURLToPath } from 'node:url';
import eslintPluginBetterTailwindcss from 'eslint-plugin-better-tailwindcss';
import { getDefaultSelectors } from 'eslint-plugin-better-tailwindcss/defaults';
import { SelectorKind } from 'eslint-plugin-better-tailwindcss/types';
const selectors = [
...getDefaultSelectors(),
{
kind: SelectorKind.Attribute,
match: [{ type: 'objectValues' }],
name: '^classNames$',
},
];
const entryPoint = fileURLToPath(
new URL('../../../../tailwind-config/src/theme.css', import.meta.url),
);
const settings = {
entryPoint,
selectors,
};
const tailwindcss: OxlintConfig = {
// Generated shadcn-ui internals are intentionally left unmanaged.
ignorePatterns: ['packages/@core/ui-kit/shadcn-ui/**/*'],
jsPlugins: [
{
name: 'better-tailwindcss',
specifier: 'eslint-plugin-better-tailwindcss',
},
],
rules: {
...eslintPluginBetterTailwindcss.configs.recommended.rules,
'better-tailwindcss/enforce-consistent-class-order': [
'error',
{
detectComponentClasses: true,
unknownClassOrder: 'asc',
unknownClassPosition: 'start',
},
],
// Let Prettier own wrapping decisions to avoid ping-pong formatting.
'better-tailwindcss/enforce-consistent-line-wrapping': 'off',
'better-tailwindcss/no-unknown-classes': 'off',
},
settings: {
'better-tailwindcss': settings,
'eslint-plugin-better-tailwindcss': settings,
},
};
export { tailwindcss };
@@ -0,0 +1,24 @@
import type { OxlintConfig } from 'oxlint';
const test: OxlintConfig = {
rules: {
'jest/no-conditional-expect': 'off',
'jest/require-to-throw-message': 'off',
'vitest/consistent-test-it': [
'error',
{
fn: 'it',
withinDescribe: 'it',
},
],
'vitest/hoisted-apis-on-top': 'off',
'vitest/no-focused-tests': 'error',
'vitest/no-identical-title': 'error',
'vitest/no-import-node-test': 'error',
'vitest/prefer-hooks-in-order': 'error',
'vitest/prefer-lowercase-title': 'error',
'vitest/require-mock-type-parameters': 'off',
},
};
export { test };
@@ -0,0 +1,52 @@
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',
'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-redundant-type-constituents': 'off',
'typescript/no-unnecessary-boolean-literal-compare': '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-useless-default-assignment': 'off',
'typescript/restrict-template-expressions': 'off',
'typescript/unbound-method': 'off',
},
};
export { typescript };
@@ -0,0 +1,132 @@
import type { OxlintConfig } from 'oxlint';
const unicorn: OxlintConfig = {
rules: {
'unicorn/consistent-function-scoping': 'off',
'unicorn/no-process-exit': 'error',
'unicorn/no-single-promise-in-promise-methods': 'off',
'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': 'off',
'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',
},
};
export { unicorn };
@@ -0,0 +1,10 @@
import type { OxlintConfig } from 'oxlint';
const vue: OxlintConfig = {
rules: {
'vue/no-reserved-component-names': 'off',
'vue/prefer-import-from-vue': 'error',
},
};
export { vue };
@@ -0,0 +1,21 @@
import type { OxlintConfig } from 'oxlint';
import { defineConfig as defineOxlintConfig } from 'oxlint';
import { mergeOxlintConfigs, oxlintConfig } from './configs';
type VbenOxlintConfig = Omit<OxlintConfig, 'extends'> & {
extends?: OxlintConfig[];
};
function defineConfig(config: VbenOxlintConfig = {}) {
const { extends: extendedConfigs = [], ...restConfig } = config;
return defineOxlintConfig(
mergeOxlintConfigs(oxlintConfig, ...extendedConfigs, restConfig),
);
}
export { defineConfig, oxlintConfig };
export * from './configs';
export type { OxlintConfig, VbenOxlintConfig };
@@ -0,0 +1,6 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "@vben/tsconfig/node.json",
"include": ["src"],
"exclude": ["node_modules"]
}
@@ -0,0 +1,11 @@
import { defineConfig } from 'tsdown';
export default defineConfig({
clean: true,
dts: true,
entry: ['src/index.ts'],
format: ['esm'],
outExtensions: () => ({
dts: '.d.ts',
}),
});
@@ -0,0 +1,149 @@
export default {
extends: ['stylelint-config-standard', 'stylelint-config-recess-order'],
ignoreFiles: [
'**/*.js',
'**/*.jsx',
'**/*.tsx',
'**/*.ts',
'**/*.json',
'**/*.md',
],
overrides: [
{
customSyntax: 'postcss-html',
files: ['*.(html|vue)', '**/*.(html|vue)'],
rules: {
'selector-pseudo-class-no-unknown': [
true,
{
ignorePseudoClasses: ['global', 'deep'],
},
],
'selector-pseudo-element-no-unknown': [
true,
{
ignorePseudoElements: ['v-deep', 'v-global', 'v-slotted'],
},
],
},
},
{
customSyntax: 'postcss-scss',
extends: [
'stylelint-config-recommended-scss',
'stylelint-config-recommended-vue/scss',
],
files: ['*.scss', '**/*.scss'],
},
],
plugins: ['stylelint-order', '@stylistic/stylelint-plugin', 'stylelint-scss'],
rules: {
'at-rule-no-deprecated': null,
'at-rule-no-unknown': [
true,
{
ignoreAtRules: [
'extends',
'ignores',
'include',
'mixin',
'if',
'else',
'media',
'for',
'at-root',
'tailwind',
'apply',
'variants',
'responsive',
'screen',
'function',
'each',
'use',
'forward',
'return',
'reference',
'plugin',
'source',
'theme',
'utility',
'custom-variant',
],
},
],
'font-family-no-missing-generic-family-keyword': null,
'function-no-unknown': null,
'import-notation': null,
'media-feature-range-notation': null,
'named-grid-areas-no-invalid': null,
'nesting-selector-no-missing-scoping-root': null,
'no-descending-specificity': null,
'no-empty-source': null,
'no-invalid-position-declaration': null,
'order/order': [
[
'dollar-variables',
'custom-properties',
'at-rules',
'declarations',
{
name: 'supports',
type: 'at-rule',
},
{
name: 'media',
type: 'at-rule',
},
{
name: 'include',
type: 'at-rule',
},
'rules',
],
{ severity: 'error' },
],
'rule-empty-line-before': [
'always',
{
ignore: ['after-comment', 'first-nested'],
},
],
'scss/at-rule-no-unknown': [
true,
{
ignoreAtRules: [
'extends',
'ignores',
'include',
'mixin',
'if',
'else',
'media',
'for',
'at-root',
'tailwind',
'apply',
'variants',
'responsive',
'screen',
'function',
'each',
'use',
'forward',
'return',
'reference',
'plugin',
'source',
'theme',
'utility',
'custom-variant',
],
},
],
'scss/operator-no-newline-after': null,
'selector-class-pattern':
'^-?(?:(?:o|c|u|t|s|is|has|_|js|qa)-)?[a-zA-Z0-9]+(?:-[a-zA-Z0-9]+)*(?:__[a-zA-Z0-9]+(?:-[a-zA-Z0-9]+)*)?(?:--[a-zA-Z0-9]+(?:-[a-zA-Z0-9]+)*)?(?:[.+])?$',
'selector-not-notation': null,
},
};
@@ -0,0 +1,41 @@
{
"name": "@vben/stylelint-config",
"version": "5.7.0",
"private": true,
"homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": {
"type": "git",
"url": "git+https://github.com/vbenjs/vue-vben-admin.git",
"directory": "internal/lint-configs/stylelint-config"
},
"license": "MIT",
"type": "module",
"files": [
"dist"
],
"main": "./index.mjs",
"module": "./index.mjs",
"exports": {
".": {
"import": "./index.mjs",
"default": "./index.mjs"
}
},
"dependencies": {
"@stylistic/stylelint-plugin": "catalog:",
"stylelint-config-recess-order": "catalog:",
"stylelint-scss": "catalog:"
},
"devDependencies": {
"postcss": "catalog:",
"postcss-html": "catalog:",
"postcss-scss": "catalog:",
"stylelint": "catalog:",
"stylelint-config-recommended": "catalog:",
"stylelint-config-recommended-scss": "catalog:",
"stylelint-config-recommended-vue": "catalog:",
"stylelint-config-standard": "catalog:",
"stylelint-order": "catalog:"
}
}