Lint: Add tailwind linter (#5984)

## Summary

Adds the [tailwind lint
plugin](https://github.com/francoismassart/eslint-plugin-tailwindcss/?tab=readme-ov-file#eslint-plugin-tailwindcss)
and fixes the currently fixable rules ([v4 is still in
beta](https://github.com/francoismassart/eslint-plugin-tailwindcss/?tab=readme-ov-file#about-tailwind-css-4-support)).

## Changes

- **What**: Enforces things like consistent class order, and eventually
can prohibit extra classes that could be utilities instead
- **Dependencies**: The plugin and its types

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5984-Lint-Add-tailwind-linter-2866d73d365081d89db0d998232533bb)
by [Unito](https://www.unito.io)

---------

Co-authored-by: GitHub Action <action@github.com>
This commit is contained in:
Alexander Brown
2025-10-08 19:39:14 -07:00
committed by GitHub
parent c9da8b200d
commit b943c0fa75
177 changed files with 731 additions and 652 deletions

View File

@@ -4,36 +4,67 @@ import pluginI18n from '@intlify/eslint-plugin-vue-i18n'
import { importX } from 'eslint-plugin-import-x'
import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended'
import storybook from 'eslint-plugin-storybook'
import tailwind from 'eslint-plugin-tailwindcss'
import unusedImports from 'eslint-plugin-unused-imports'
import pluginVue from 'eslint-plugin-vue'
import { defineConfig } from 'eslint/config'
import globals from 'globals'
import tseslint from 'typescript-eslint'
import {
configs as tseslintConfigs,
parser as tseslintParser
} from 'typescript-eslint'
import vueParser from 'vue-eslint-parser'
const extraFileExtensions = ['.vue']
const commonGlobals = {
...globals.browser,
__COMFYUI_FRONTEND_VERSION__: 'readonly'
} as const
const settings = {
'import/resolver': {
typescript: true,
node: true
},
tailwindcss: {
config: `${import.meta.dirname}/packages/design-system/src/css/style.css`,
functions: ['cn', 'clsx', 'tw']
}
} as const
const commonParserOptions = {
parser: tseslintParser,
projectService: true,
tsConfigRootDir: import.meta.dirname,
ecmaVersion: 2020,
sourceType: 'module',
extraFileExtensions
} as const
export default defineConfig([
{
ignores: [
'src/scripts/*',
'src/extensions/core/*',
'src/types/vue-shim.d.ts',
'packages/registry-types/src/comfyRegistryTypes.ts',
'src/types/generatedManagerTypes.ts',
'.i18nrc.cjs',
'components.d.ts',
'lint-staged.config.js',
'vitest.setup.ts',
'**/vite.config.*.timestamp*',
'**/vitest.config.*.timestamp*'
'**/vitest.config.*.timestamp*',
'packages/registry-types/src/comfyRegistryTypes.ts',
'src/extensions/core/*',
'src/scripts/*',
'src/types/generatedManagerTypes.ts',
'src/types/vue-shim.d.ts'
]
},
{
files: ['./**/*.{ts,mts}'],
settings,
languageOptions: {
globals: {
...globals.browser,
__COMFYUI_FRONTEND_VERSION__: 'readonly'
},
globals: commonGlobals,
parserOptions: {
parser: tseslint.parser,
...commonParserOptions,
projectService: {
allowDefaultProject: [
'vite.config.mts',
@@ -42,47 +73,26 @@ export default defineConfig([
'playwright.config.ts',
'playwright.i18n.config.ts'
]
},
tsConfigRootDir: import.meta.dirname,
ecmaVersion: 2020,
sourceType: 'module',
extraFileExtensions
}
},
settings: {
'import/resolver': {
typescript: true,
node: true
}
}
}
},
{
files: ['./**/*.vue'],
settings,
languageOptions: {
globals: {
...globals.browser,
__COMFYUI_FRONTEND_VERSION__: 'readonly'
},
globals: commonGlobals,
parser: vueParser,
parserOptions: {
parser: tseslint.parser,
projectService: true,
tsConfigRootDir: import.meta.dirname,
ecmaVersion: 2020,
sourceType: 'module',
extraFileExtensions
}
},
settings: {
'import/resolver': {
typescript: true,
node: true
}
parserOptions: commonParserOptions
}
},
pluginJs.configs.recommended,
// eslint-disable-next-line import-x/no-named-as-default-member
tseslint.configs.recommended,
tseslintConfigs.recommended,
// Difference in typecheck on CI vs Local
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore Bad types in the plugin
tailwind.configs['flat/recommended'],
pluginVue.configs['flat/recommended'],
eslintPluginPrettierRecommended,
storybook.configs['flat/recommended'],
@@ -114,6 +124,7 @@ export default defineConfig([
'import-x/no-relative-packages': 'error',
'unused-imports/no-unused-imports': 'error',
'no-console': ['error', { allow: ['warn', 'error'] }],
'tailwindcss/no-custom-classname': 'off', // TODO: fix
'vue/no-v-html': 'off',
// Enforce dark-theme: instead of dark: prefix
'vue/no-restricted-class': ['error', '/^dark:/'],