// @ts-check import antfu, { GLOB_MARKDOWN, GLOB_MARKDOWN_CODE, GLOB_TESTS, GLOB_TS, GLOB_TSX, isInEditorEnv, isInGitHooksOrLintStaged } from '@antfu/eslint-config' import pluginReact from '@eslint-react/eslint-plugin' import pluginQuery from '@tanstack/eslint-plugin-query' import md from 'eslint-markdown' import tailwindcss from 'eslint-plugin-better-tailwindcss' import hyoban from 'eslint-plugin-hyoban' import markdownPreferences from 'eslint-plugin-markdown-preferences' import { reactRefresh } from 'eslint-plugin-react-refresh' import sonar from 'eslint-plugin-sonarjs' import storybook from 'eslint-plugin-storybook' import { HYOBAN_PREFER_TAILWIND_ICONS_OPTIONS, NEXT_PLATFORM_RESTRICTED_IMPORT_PATHS, NEXT_PLATFORM_RESTRICTED_IMPORT_PATTERNS, OVERLAY_MIGRATION_LEGACY_BASE_FILES, OVERLAY_RESTRICTED_IMPORT_PATTERNS, } from './eslint.constants.mjs' import dify from './plugins/eslint/index.js' // Enable Tailwind CSS IntelliSense mode for ESLint runs // See: tailwind-css-plugin.ts process.env.TAILWIND_MODE ??= 'ESLINT' const disableRuleAutoFix = !(isInEditorEnv() || isInGitHooksOrLintStaged()) const plugins = pluginReact.configs.all.plugins export default antfu( { react: false, nextjs: true, ignores: ['public', 'types/doc-paths.ts', 'eslint-suppressions.json'], typescript: { overrides: { 'ts/consistent-type-definitions': ['error', 'type'], 'ts/no-explicit-any': 'error', }, erasableOnly: true, }, test: { overrides: { 'test/prefer-lowercase-title': 'off', }, }, stylistic: { overrides: { 'antfu/top-level-function': 'off', }, }, e18e: false, }, { plugins: { 'react': plugins?.['@eslint-react'], 'react-dom': plugins?.['@eslint-react/dom'], 'react-naming-convention': plugins?.['@eslint-react/naming-convention'], 'react-rsc': plugins?.['@eslint-react/rsc'], 'react-web-api': plugins?.['@eslint-react/web-api'], }, }, { files: [GLOB_TS, GLOB_TSX], rules: { ...pluginReact.configs['recommended-typescript'].rules, 'react/prefer-namespace-import': 'error', 'react/set-state-in-effect': 'error', }, }, { files: [...GLOB_TESTS, GLOB_MARKDOWN_CODE, 'vitest.setup.ts', 'test/i18n-mock.ts'], rules: { 'react/component-hook-factories': 'off', }, }, reactRefresh.configs.next(), markdownPreferences.configs.standard, { files: [GLOB_MARKDOWN], plugins: { md }, rules: { 'md/no-url-trailing-slash': 'error', 'markdown-preferences/prefer-link-reference-definitions': [ 'error', { minLinks: 1, }, ], 'markdown-preferences/ordered-list-marker-sequence': [ 'error', { increment: 'never' }, ], 'markdown-preferences/definitions-last': 'error', 'markdown-preferences/sort-definitions': 'error', }, }, { rules: { 'node/prefer-global/process': 'off', 'next/no-img-element': 'off', }, }, { files: ['**/*.ts', '**/*.tsx'], settings: { 'react-x': { additionalStateHooks: '/^use\\w*State(?:s)?|useAtom$/u', }, }, }, storybook.configs['flat/recommended'], ...pluginQuery.configs['flat/recommended'], // sonar { rules: { // Manually pick rules that are actually useful and not slow. // Or we can just drop the plugin entirely. }, plugins: { sonarjs: sonar, }, }, { files: [GLOB_TS, GLOB_TSX], ignores: GLOB_TESTS, plugins: { tailwindcss, }, rules: { 'tailwindcss/enforce-consistent-class-order': 'error', 'tailwindcss/no-duplicate-classes': 'error', 'tailwindcss/no-unnecessary-whitespace': 'error', 'tailwindcss/no-unknown-classes': 'warn', }, }, { name: 'dify/custom/setup', plugins: { dify, hyoban, }, }, { files: ['**/*.tsx'], rules: { 'hyoban/prefer-tailwind-icons': ['warn', HYOBAN_PREFER_TAILWIND_ICONS_OPTIONS], }, }, { files: ['i18n/**/*.json'], rules: { 'sonarjs/max-lines': 'off', 'max-lines': 'off', 'jsonc/sort-keys': 'error', 'hyoban/i18n-flat-key': 'error', 'dify/no-extra-keys': 'error', 'dify/consistent-placeholders': 'error', }, }, { files: ['**/package.json'], rules: { 'hyoban/no-dependency-version-prefix': 'error', }, }, { name: 'dify/base-ui-primitives', files: ['app/components/base/ui/**/*.tsx', 'app/components/base/avatar/**/*.tsx'], rules: { 'react-refresh/only-export-components': 'off', }, }, { name: 'dify/no-direct-next-imports', files: [GLOB_TS, GLOB_TSX], ignores: ['next/**'], rules: { 'no-restricted-imports': ['error', { paths: NEXT_PLATFORM_RESTRICTED_IMPORT_PATHS, patterns: NEXT_PLATFORM_RESTRICTED_IMPORT_PATTERNS, }], }, }, { name: 'dify/overlay-migration', files: [GLOB_TS, GLOB_TSX], ignores: [ 'next/**', ...GLOB_TESTS, ...OVERLAY_MIGRATION_LEGACY_BASE_FILES, ], rules: { 'no-restricted-imports': ['error', { paths: NEXT_PLATFORM_RESTRICTED_IMPORT_PATHS, patterns: [ ...NEXT_PLATFORM_RESTRICTED_IMPORT_PATTERNS, ...OVERLAY_RESTRICTED_IMPORT_PATTERNS, ], }], }, }, ) .disableRulesFix(disableRuleAutoFix ? [ 'tailwindcss/enforce-consistent-class-order', 'tailwindcss/no-duplicate-classes', 'tailwindcss/no-unnecessary-whitespace', ] : []) .renamePlugins({ '@eslint-react': 'react', '@eslint-react/dom': 'react-dom', '@eslint-react/naming-convention': 'react-naming-convention', '@eslint-react/rsc': 'react-rsc', '@eslint-react/web-api': 'react-web-api', })