mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-08-28 19:47:49 +08:00
196 lines
5.8 KiB
JavaScript
196 lines
5.8 KiB
JavaScript
import js from '@eslint/js'
|
|
import sonarjs from 'eslint-plugin-sonarjs'
|
|
import pluginVue from 'eslint-plugin-vue'
|
|
import globals from 'globals'
|
|
import tseslint from 'typescript-eslint'
|
|
import { defineConfig, globalIgnores, includeIgnoreFile } from 'eslint/config'
|
|
import { fileURLToPath } from 'node:url'
|
|
|
|
const gitignorePath = fileURLToPath(new URL('.gitignore', import.meta.url))
|
|
|
|
const javascriptFiles = ['**/*.{js,mjs,cjs,jsx}']
|
|
|
|
const typescriptFiles = ['**/*.{ts,tsx,mts,cts}', '**/*.vue']
|
|
|
|
const managedScriptExtensions = 'js,mjs,cjs,jsx,ts,tsx,mts,cts'
|
|
|
|
const managedFiles = [...javascriptFiles, ...typescriptFiles]
|
|
|
|
const browserFiles = [
|
|
`src/**/*.{${managedScriptExtensions},vue}`,
|
|
`examples/**/src/**/*.{${managedScriptExtensions},vue}`,
|
|
]
|
|
|
|
const nodeFiles = ['**/*.config.{js,mjs,cjs,ts,mts,cts}', 'public/service.js', 'scripts/**/*.{js,mjs,cjs,ts,mts,cts}']
|
|
|
|
// TypeScript 关闭核心 no-undef;显式禁止浏览器域中的 Node-only globals,保证 JS、TS 与 Vue 使用同一运行时边界。
|
|
const browserGlobalNames = new Set(Object.keys(globals.browser))
|
|
const nodeOnlyGlobalRestrictions = Object.keys(globals.node)
|
|
.filter(name => !browserGlobalNames.has(name))
|
|
.sort()
|
|
.map(name => ({
|
|
name,
|
|
message: `'${name}' is only available in Node.js code.`,
|
|
}))
|
|
|
|
const restrictedCoreSourcePattern = '/(?:^|\\/)@core(?:\\/|$)/'
|
|
|
|
// @layouts 是 @core 的下游依赖;禁止反向引用,避免两层形成直接循环。
|
|
const layoutCoreImportRestrictions = [
|
|
'ImportDeclaration',
|
|
'ExportNamedDeclaration',
|
|
'ExportAllDeclaration',
|
|
'ImportExpression',
|
|
].map(nodeType => ({
|
|
selector: `${nodeType}[source.value=${restrictedCoreSourcePattern}]`,
|
|
message: "The '@layouts' module must not import from '@core'.",
|
|
}))
|
|
|
|
const nonLiteralLayoutDynamicImportRestriction = {
|
|
selector: "ImportExpression:not([source.type='Literal'])",
|
|
message: 'Dynamic imports in @layouts must use a literal path so module boundaries remain statically verifiable.',
|
|
}
|
|
|
|
const viteGlobCallSelectors = [
|
|
"CallExpression[callee.object.type='MetaProperty'][callee.object.meta.name='import'][callee.object.property.name='meta'][callee.property.name=/^glob(?:Eager)?$/]",
|
|
"CallExpression[callee.object.type='MetaProperty'][callee.object.meta.name='import'][callee.object.property.name='meta'][callee.property.value=/^glob(?:Eager)?$/]",
|
|
]
|
|
|
|
const layoutGlobRestrictions = viteGlobCallSelectors.map(callSelector => ({
|
|
selector: callSelector,
|
|
message:
|
|
'import.meta.glob is not allowed in @layouts because wildcard targets cannot be proven to stay within the module boundary.',
|
|
}))
|
|
|
|
const sonarRules = {
|
|
'sonarjs/array-callback-without-return': 'error',
|
|
'sonarjs/code-eval': 'error',
|
|
'sonarjs/empty-string-repetition': 'error',
|
|
'sonarjs/no-all-duplicated-branches': 'error',
|
|
'sonarjs/no-dead-store': 'error',
|
|
'sonarjs/no-duplicated-branches': 'error',
|
|
'sonarjs/no-element-overwrite': 'error',
|
|
'sonarjs/no-hardcoded-passwords': 'error',
|
|
'sonarjs/no-hardcoded-secrets': 'error',
|
|
'sonarjs/no-identical-conditions': 'error',
|
|
'sonarjs/no-identical-expressions': 'error',
|
|
'sonarjs/no-ignored-exceptions': 'error',
|
|
'sonarjs/no-unthrown-error': 'error',
|
|
'sonarjs/no-use-of-empty-return-value': 'error',
|
|
'sonarjs/reduce-initial-value': 'error',
|
|
'sonarjs/slow-regex': 'error',
|
|
'sonarjs/stateful-regex': 'error',
|
|
'sonarjs/super-linear-regex': 'error',
|
|
}
|
|
|
|
const typescriptConfigs = tseslint.configs.recommended.map(config => ({
|
|
...config,
|
|
files: typescriptFiles,
|
|
}))
|
|
|
|
const vueConfigs = pluginVue.configs['flat/essential'].map(config => ({
|
|
...config,
|
|
files: ['**/*.vue'],
|
|
}))
|
|
|
|
export default defineConfig([
|
|
includeIgnoreFile(gitignorePath, {
|
|
gitignoreResolution: true,
|
|
name: 'moviepilot/gitignore',
|
|
}),
|
|
globalIgnores(
|
|
['**/.worktrees/**', '**/vite.config.*.timestamp-*.mjs', 'src/@iconify/**', '**/*.d.ts'],
|
|
'moviepilot/eslint-only-ignores',
|
|
),
|
|
{
|
|
...js.configs.recommended,
|
|
name: 'moviepilot/javascript',
|
|
files: managedFiles,
|
|
},
|
|
...typescriptConfigs,
|
|
...vueConfigs,
|
|
{
|
|
name: 'moviepilot/vue-contracts',
|
|
files: ['**/*.vue'],
|
|
rules: {
|
|
'vue/multi-word-component-names': 'off',
|
|
'vue/valid-v-slot': [
|
|
'error',
|
|
{
|
|
allowModifiers: true,
|
|
},
|
|
],
|
|
},
|
|
},
|
|
{
|
|
name: 'moviepilot/language-options',
|
|
files: managedFiles,
|
|
linterOptions: {
|
|
reportUnusedDisableDirectives: 'off',
|
|
},
|
|
languageOptions: {
|
|
ecmaVersion: 'latest',
|
|
sourceType: 'module',
|
|
parserOptions: {
|
|
parser: tseslint.parser,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: 'moviepilot/browser-globals',
|
|
files: browserFiles,
|
|
languageOptions: {
|
|
globals: globals.browser,
|
|
},
|
|
rules: {
|
|
'no-restricted-globals': ['error', ...nodeOnlyGlobalRestrictions],
|
|
},
|
|
},
|
|
{
|
|
name: 'moviepilot/node-globals',
|
|
files: nodeFiles,
|
|
languageOptions: {
|
|
globals: globals.node,
|
|
},
|
|
// Node 配置可能位于浏览器源码目录;显式覆盖浏览器专用限制,避免 flat config 合并后误报。
|
|
rules: {
|
|
'no-restricted-globals': 'off',
|
|
},
|
|
},
|
|
{
|
|
name: 'moviepilot/sonarjs',
|
|
files: managedFiles,
|
|
plugins: {
|
|
sonarjs,
|
|
},
|
|
rules: sonarRules,
|
|
},
|
|
{
|
|
name: 'moviepilot/sonarjs-locales',
|
|
files: [`src/locales/**/*.{${managedScriptExtensions}}`],
|
|
rules: {
|
|
'sonarjs/no-hardcoded-passwords': 'off',
|
|
'sonarjs/no-hardcoded-secrets': 'off',
|
|
},
|
|
},
|
|
{
|
|
name: 'moviepilot/source-hygiene',
|
|
files: managedFiles,
|
|
rules: {
|
|
'no-debugger': 'error',
|
|
},
|
|
},
|
|
{
|
|
name: 'moviepilot/layout-boundary',
|
|
files: [`src/@layouts/**/*.{${managedScriptExtensions},vue}`],
|
|
rules: {
|
|
'no-restricted-syntax': [
|
|
'error',
|
|
...layoutCoreImportRestrictions,
|
|
...layoutGlobRestrictions,
|
|
nonLiteralLayoutDynamicImportRestriction,
|
|
],
|
|
},
|
|
},
|
|
])
|