import skipFormatting from '@vue/eslint-config-prettier/skip-formatting'; import vueTsEslintConfig from '@vue/eslint-config-typescript'; import unocss from '@unocss/eslint-config/flat'; import eslint from '@eslint/js'; import pluginVitest from '@vitest/eslint-plugin'; import pluginCypress from 'eslint-plugin-cypress/flat'; import pluginImport from 'eslint-plugin-import'; import pluginJsonc from 'eslint-plugin-jsonc'; import pluginSimpleImportSort from 'eslint-plugin-simple-import-sort'; import pluginVue from 'eslint-plugin-vue'; import globals from 'globals'; import jsoncParser from 'jsonc-eslint-parser'; import tseslint from 'typescript-eslint'; export default tseslint.config( { name: 'app/files-to-lint', files: ['**/*.{ts,mts,tsx,vue}'], }, { name: 'app/files-to-ignore', ignores: ['**/dist/**', '**/dist-ssr/**', '**/coverage/**', '**/*.json', '!src/i18n/locales/*.json'], }, { languageOptions: { globals: { ...globals.browser, ...globals.commonjs, ...globals.node, }, }, }, eslint.configs.recommended, ...pluginJsonc.configs['flat/recommended-with-json'], ...pluginJsonc.configs['flat/prettier'], ...pluginVue.configs['flat/essential'], ...vueTsEslintConfig(), skipFormatting, unocss, { ...pluginVitest.configs.recommended, files: ['src/**/__tests__/*'], }, { ...pluginCypress.configs.recommended, files: ['cypress/e2e/**/*.{cy,spec}.{js,ts,jsx,tsx}', 'cypress/support/**/*.{js,ts,jsx,tsx}'], }, // import rules { files: ['**/*.{js,mjs,cjs,jsx,ts,tsx,vue}'], extends: [pluginImport.flatConfigs.recommended, pluginImport.flatConfigs.typescript], languageOptions: { ecmaVersion: 'latest', sourceType: 'module', }, rules: { 'import/no-deprecated': 'error', 'import/no-empty-named-blocks': 'error', 'import/no-mutable-exports': 'error', 'import/no-named-as-default': 'error', 'import/no-named-as-default-member': 'error', 'import/no-absolute-path': 'error', 'import/no-dynamic-require': 'error', 'import/no-self-import': 'error', 'import/no-unresolved': 'off', 'import/no-useless-path-segments': [ 'error', { noUselessIndex: true, }, ], 'import/consistent-type-specifier-style': ['error', 'prefer-top-level'], 'import/exports-last': 'error', 'import/first': 'error', 'import/newline-after-import': [ 'error', { count: 1, considerComments: true, }, ], 'import/no-anonymous-default-export': 'error', 'import/no-duplicates': 'error', 'import/no-named-default': 'error', 'import/no-unassigned-import': [ 'error', { allow: ['**/*.{css,scss}', '**/styles', 'dayjs/locale/*'], }, ], }, }, // sort rules { plugins: { 'simple-import-sort': pluginSimpleImportSort, }, rules: { 'simple-import-sort/imports': [ 'error', { groups: [ // Packages. `vue` related packages come first. ['^vue', '^@vue', 'pinia', '^vite$', '^@vitejs', '^vite-plugin', '^unocss$', '^@unocss', '^@?\\w'], // Node.js builtins. ['^node:'], // Internal packages. [ '^@/layout(/.*|$)', '^@/views(/.*|$)', '^@/components(/.*|$)', '^@/transitions(/.*|$)', '^@/hooks(/.*|$)', '^@/directives(/.*|$)', '^@/stores(/.*|$)', '^@/router(/.*|$)', '^@/i18n(/.*|$)', '^@/api(/.*|$)', '^@/utils(/.*|$)', '^@/constants(/.*|$)', '^@/icons(/.*|$)', '^@/styles(/.*|$)', ], // Assets ['^@/assets(/.*|$)'], // Parent imports. Put `..` last. ['^\\.\\.(?!/?$)', '^\\.\\./?$'], // Other relative imports. Put same-folder imports and `.` last. ['^\\./(?=.*/)(?!/?$)', '^\\.(?!/?$)', '^\\./?$'], // Type imports [ '^vue.*\\u0000$', '^@vue.*\\u0000$', '^pinia.*\\u0000$', '^vite\\u0000$', '^@vitejs.*\\u0000$', '^vite-plugin.*\\u0000$', '^unocss\\u0000$', '^@unocss.*\\u0000$', '^@?\\w.*\\u0000$', '^node:.*\\u0000$', '^@/constants.*\\u0000$', '^@/types.*\\u0000$', '^.*\\u0000$', ], // Style imports. ['^.+\\.s?css$'], // Side effect imports. ['^\\u0000'], ], }, ], 'simple-import-sort/exports': 'error', }, }, // json rules { files: ['**/*.json'], languageOptions: { parser: jsoncParser, }, rules: { 'jsonc/key-name-casing': [ 'error', { camelCase: true, }, ], 'jsonc/sort-keys': [ 'error', { pathPattern: '.*', order: { type: 'asc' }, }, ], }, }, // js rules { files: ['**/*.{js,mjs,cjs,jsx,vue}'], rules: { 'object-shorthand': ['error', 'always'], 'no-unused-vars': 'off', 'no-var': 'error', }, }, // ts rules { files: ['**/*.{ts,mts,tsx,vue}'], rules: { 'object-shorthand': ['error', 'always'], '@typescript-eslint/no-unused-vars': 'error', '@typescript-eslint/no-explicit-any': 'error', '@typescript-eslint/no-inferrable-types': 'off', '@typescript-eslint/prefer-optional-chain': 'off', }, }, // vue rules { files: ['**/*.vue'], rules: { 'vue/component-tags-order': [ 'error', { order: ['script:not([setup])', 'script[setup]', 'template', 'style:not([scoped])', 'style[scoped]'], }, ], 'vue/component-name-in-template-casing': [ 'error', 'PascalCase', { registeredComponentsOnly: false, ignores: ['/^un-/'], }, ], 'vue/multi-word-component-names': [ 'error', { ignores: ['Login'], }, ], 'vue/html-self-closing': [ 'error', { html: { void: 'always', normal: 'never', component: 'always', }, svg: 'always', math: 'always', }, ], 'vue/prop-name-casing': ['error', 'camelCase'], 'vue/attribute-hyphenation': [ 'error', 'always', { ignore: [], }, ], 'vue/padding-line-between-blocks': ['error', 'always'], 'vue/block-lang': [ 'error', { script: { lang: 'ts', }, template: {}, style: { lang: 'scss', }, }, ], 'vue/no-mutating-props': [ 'error', { shallowOnly: true, }, ], }, }, );