Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5cc6db21e8 | ||
|
|
d1e713b94b | ||
|
|
1906ce149b |
@@ -49,6 +49,7 @@ jobs:
|
||||
.nvmrc
|
||||
vite.config.ts
|
||||
eslint.config.mjs
|
||||
eslint.migrated-rules.mjs
|
||||
.vscode/**
|
||||
.github/workflows/autofix.yml
|
||||
.github/workflows/style.yml
|
||||
@@ -166,9 +167,10 @@ jobs:
|
||||
if: github.event_name != 'merge_group' && steps.frontend-contract-changes.outputs.any_changed == 'true'
|
||||
run: pnpm --dir packages/contracts gen-api-contract
|
||||
|
||||
- name: ESLint autofix
|
||||
- name: Frontend lint autofix
|
||||
if: github.event_name != 'merge_group' && steps.web-changes.outputs.any_changed == 'true'
|
||||
run: |
|
||||
vp lint --fix --quiet || true
|
||||
vp exec eslint --fix --concurrency=2 --prune-suppressions --quiet || true
|
||||
|
||||
- name: Format frontend files
|
||||
|
||||
@@ -164,6 +164,7 @@ jobs:
|
||||
.nvmrc
|
||||
vite.config.ts
|
||||
eslint.config.mjs
|
||||
eslint.migrated-rules.mjs
|
||||
.vscode/**
|
||||
.github/workflows/autofix.yml
|
||||
.github/workflows/style.yml
|
||||
@@ -183,9 +184,9 @@ jobs:
|
||||
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
with:
|
||||
path: .eslintcache
|
||||
key: ${{ runner.os }}-eslint-${{ hashFiles('pnpm-lock.yaml', 'eslint.config.mjs', 'packages/dify-ui/eslint.config.mjs', 'web/eslint.config.mjs', 'web/eslint.constants.mjs', 'web/plugins/eslint/**') }}-${{ github.sha }}
|
||||
key: ${{ runner.os }}-eslint-${{ hashFiles('pnpm-lock.yaml', 'eslint.config.mjs', 'eslint.migrated-rules.mjs', 'packages/dify-ui/eslint.config.mjs', 'web/eslint.config.mjs', 'web/eslint.constants.mjs', 'web/plugins/eslint/**') }}-${{ github.sha }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-eslint-${{ hashFiles('pnpm-lock.yaml', 'eslint.config.mjs', 'packages/dify-ui/eslint.config.mjs', 'web/eslint.config.mjs', 'web/eslint.constants.mjs', 'web/plugins/eslint/**') }}-
|
||||
${{ runner.os }}-eslint-${{ hashFiles('pnpm-lock.yaml', 'eslint.config.mjs', 'eslint.migrated-rules.mjs', 'packages/dify-ui/eslint.config.mjs', 'web/eslint.config.mjs', 'web/eslint.constants.mjs', 'web/plugins/eslint/**') }}-
|
||||
|
||||
- name: Style check
|
||||
if: steps.changed-files.outputs.any_changed == 'true'
|
||||
|
||||
Vendored
+1
@@ -29,6 +29,7 @@
|
||||
|
||||
// Lint fix
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.oxc": "explicit",
|
||||
"source.fixAll.eslint": "explicit"
|
||||
},
|
||||
|
||||
|
||||
@@ -977,12 +977,6 @@
|
||||
"web/app/components/base/auto-height-textarea/index.tsx": {
|
||||
"jsx-a11y/no-autofocus": {
|
||||
"count": 1
|
||||
},
|
||||
"react-hooks/rules-of-hooks": {
|
||||
"count": 1
|
||||
},
|
||||
"react/rules-of-hooks": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/base/badge/index.tsx": {
|
||||
@@ -5657,11 +5651,6 @@
|
||||
"count": 3
|
||||
}
|
||||
},
|
||||
"web/app/components/workflow/nodes/parameter-extractor/components/extract-parameter/__tests__/list.spec.tsx": {
|
||||
"unused-imports/no-unused-vars": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/workflow/nodes/parameter-extractor/components/extract-parameter/item.tsx": {
|
||||
"jsx-a11y/click-events-have-key-events": {
|
||||
"count": 2
|
||||
|
||||
+15
-1
@@ -1,8 +1,16 @@
|
||||
// @ts-check
|
||||
|
||||
import antfu, { GLOB_MARKDOWN } from '@antfu/eslint-config'
|
||||
import antfu, {
|
||||
GLOB_JS,
|
||||
GLOB_JSX,
|
||||
GLOB_MARKDOWN,
|
||||
GLOB_MARKDOWN_CODE,
|
||||
GLOB_TS,
|
||||
GLOB_TSX,
|
||||
} from '@antfu/eslint-config'
|
||||
import md from 'eslint-markdown'
|
||||
import markdownPreferences from 'eslint-plugin-markdown-preferences'
|
||||
import { rulesMigratedToOxlint } from './eslint.migrated-rules.mjs'
|
||||
|
||||
const GENERATED_IGNORES = [
|
||||
'**/storybook-static/',
|
||||
@@ -48,6 +56,7 @@ export default antfu(
|
||||
'!web/**',
|
||||
'!e2e/**',
|
||||
'!eslint.config.mjs',
|
||||
'!eslint.migrated-rules.mjs',
|
||||
'!package.json',
|
||||
'!pnpm-workspace.yaml',
|
||||
'!vite.config.ts',
|
||||
@@ -93,6 +102,11 @@ export default antfu(
|
||||
'unicorn/number-literal-case': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: [GLOB_JS, GLOB_JSX, GLOB_TS, GLOB_TSX],
|
||||
ignores: [GLOB_MARKDOWN_CODE],
|
||||
rules: rulesMigratedToOxlint,
|
||||
},
|
||||
).override('antfu/sort/package-json', {
|
||||
rules: {
|
||||
'jsonc/sort-keys': 'off',
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
// @ts-check
|
||||
|
||||
/** @satisfies {import('eslint').Linter.RulesRecord} */
|
||||
export const rulesMigratedToOxlint = {
|
||||
'no-unmodified-loop-condition': 'off',
|
||||
'react/rules-of-hooks': 'off',
|
||||
'unicorn/prefer-dom-node-text-content': 'off',
|
||||
'unicorn/prefer-includes': 'off',
|
||||
'unicorn/prefer-node-protocol': 'off',
|
||||
'unicorn/prefer-string-starts-ends-with': 'off',
|
||||
'unused-imports/no-unused-imports': 'off',
|
||||
'unused-imports/no-unused-vars': 'off',
|
||||
}
|
||||
+4
-4
@@ -8,10 +8,10 @@
|
||||
"fmt:check": "vp fmt --check",
|
||||
"prepare": "vp config",
|
||||
"type-check": "vp run -r type-check",
|
||||
"lint": "eslint --cache --concurrency=auto",
|
||||
"lint:ci": "eslint --cache --cache-strategy content --concurrency 2",
|
||||
"lint:fix": "vp run lint --fix",
|
||||
"lint:quiet": "vp run lint --quiet"
|
||||
"lint": "vp lint && eslint --cache --concurrency=auto",
|
||||
"lint:ci": "vp lint && eslint --cache --cache-strategy content --concurrency 2",
|
||||
"lint:fix": "vp lint --fix && eslint --cache --concurrency=auto --fix && vp fmt",
|
||||
"lint:quiet": "vp lint --quiet && eslint --cache --concurrency=auto --quiet"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@antfu/eslint-config": "catalog:",
|
||||
|
||||
@@ -95,7 +95,6 @@ export default antfu(
|
||||
tailwindcss,
|
||||
},
|
||||
rules: {
|
||||
'tailwindcss/no-duplicate-classes': 'error',
|
||||
'tailwindcss/no-deprecated-classes': 'error',
|
||||
'tailwindcss/no-unknown-classes': 'error',
|
||||
},
|
||||
|
||||
+47
-4
@@ -1,8 +1,10 @@
|
||||
import { defineConfig } from 'vite-plus'
|
||||
|
||||
const lintFiles = '*.{js,cjs,mjs,jsx,ts,cts,mts,tsx,json,jsonc,json5,md,yml,yaml,toml}'
|
||||
const codeFiles = '*.{js,cjs,mjs,jsx,ts,cts,mts,tsx}'
|
||||
const eslintOnlyFiles = '*.{json,jsonc,json5,md,yml,yaml,toml}'
|
||||
const formatOnlyFiles = '*.{mdx,css,scss,less,html,vue,svelte,gql,graphql,hbs,handlebars}'
|
||||
const lintFix = 'eslint --fix --pass-on-unpruned-suppressions --no-error-on-unmatched-pattern'
|
||||
const oxlintFix = 'vp lint --fix --no-error-on-unmatched-pattern'
|
||||
const eslintFix = 'eslint --fix --pass-on-unpruned-suppressions --no-error-on-unmatched-pattern'
|
||||
const format = 'vp fmt --no-error-on-unmatched-pattern'
|
||||
|
||||
const nonFrontendIgnores = [
|
||||
@@ -37,13 +39,54 @@ const generatedIgnores = [
|
||||
'web/public/vs/**',
|
||||
]
|
||||
|
||||
const lintIgnores = [
|
||||
...nonFrontendIgnores,
|
||||
...generatedIgnores,
|
||||
'.claude/**',
|
||||
'cli/**',
|
||||
'packages/dify-ui/**',
|
||||
'sdks/**',
|
||||
'web/public/**',
|
||||
'web/types/doc-paths.ts',
|
||||
]
|
||||
|
||||
const formatterUnstableInputs = ['web/app/components/develop/template/*.mdx']
|
||||
|
||||
export default defineConfig({
|
||||
staged: {
|
||||
[lintFiles]: [lintFix, format],
|
||||
[codeFiles]: [oxlintFix, eslintFix, format],
|
||||
[eslintOnlyFiles]: [eslintFix, format],
|
||||
[formatOnlyFiles]: format,
|
||||
},
|
||||
lint: {
|
||||
categories: {
|
||||
correctness: 'off',
|
||||
},
|
||||
ignorePatterns: lintIgnores,
|
||||
plugins: ['eslint', 'react', 'unicorn'],
|
||||
rules: {
|
||||
'no-unmodified-loop-condition': 'error',
|
||||
'no-unused-vars': [
|
||||
'error',
|
||||
{
|
||||
args: 'after-used',
|
||||
argsIgnorePattern: '^_',
|
||||
fix: {
|
||||
imports: 'safe-fix',
|
||||
variables: 'off',
|
||||
},
|
||||
ignoreRestSiblings: true,
|
||||
vars: 'all',
|
||||
varsIgnorePattern: '^_',
|
||||
},
|
||||
],
|
||||
'react/rules-of-hooks': 'error',
|
||||
'unicorn/prefer-dom-node-text-content': 'error',
|
||||
'unicorn/prefer-includes': 'error',
|
||||
'unicorn/prefer-node-protocol': 'error',
|
||||
'unicorn/prefer-string-starts-ends-with': 'error',
|
||||
},
|
||||
},
|
||||
fmt: {
|
||||
ignorePatterns: [...nonFrontendIgnores, ...generatedIgnores, ...formatterUnstableInputs],
|
||||
singleQuote: true,
|
||||
@@ -65,7 +108,7 @@ export default defineConfig({
|
||||
sortPackageJson: true,
|
||||
sortTailwindcss: {
|
||||
functions: ['cn', 'clsx', 'cva', 'tw', 'twMerge'],
|
||||
preserveDuplicates: true,
|
||||
preserveDuplicates: false,
|
||||
stylesheet: 'web/app/styles/globals.css',
|
||||
},
|
||||
},
|
||||
|
||||
+1
@@ -7,6 +7,7 @@ import ConfigModalFormFields from '../form-fields'
|
||||
|
||||
vi.mock('react-i18next', async () => {
|
||||
const { withSelectorKey, withSelectorKeyProps } = await import('@/test/i18n-mock')
|
||||
// oxlint-disable-next-line no-unused-vars -- Keep React available to the hoisted JSX mock factory.
|
||||
const React = await import('react')
|
||||
return {
|
||||
useTranslation: () => ({
|
||||
|
||||
@@ -70,9 +70,9 @@ const AppTypeSelector = ({ value, onChange }: AppSelectorProps) => {
|
||||
<AppTypeSelectorItem
|
||||
key={mode}
|
||||
type={mode}
|
||||
checked={Boolean(value.length > 0 && value?.indexOf(mode) !== -1)}
|
||||
checked={value.length > 0 && value.includes(mode)}
|
||||
onClick={() => {
|
||||
if (value?.indexOf(mode) !== -1) onChange(value?.filter((v) => v !== mode) ?? [])
|
||||
if (value.includes(mode)) onChange(value.filter((v) => v !== mode))
|
||||
else onChange([...(value || []), mode])
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -32,7 +32,7 @@ const AutoHeightTextarea = ({
|
||||
}: IProps & {
|
||||
ref?: React.RefObject<HTMLTextAreaElement>
|
||||
}) => {
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
// oxlint-disable-next-line react/rules-of-hooks -- Use the provided ref when available and create a local fallback otherwise.
|
||||
const ref = outerRef || useRef<HTMLTextAreaElement>(null)
|
||||
|
||||
const doFocus = () => {
|
||||
|
||||
+1
@@ -30,6 +30,7 @@ describe('parameter-extractor/extract-parameter/list', () => {
|
||||
// TODO: Fix this test.
|
||||
// This test only failed in the merge queue, and I don't know why.
|
||||
it.skip('edits and deletes parameters through the real item and modal flow', async () => {
|
||||
// oxlint-disable-next-line no-unused-vars -- Keep the interaction setup with the temporarily skipped test.
|
||||
const user = userEvent.setup()
|
||||
const handleChange = vi.fn()
|
||||
const { rerender } = render(
|
||||
|
||||
+24
-5
@@ -1,6 +1,6 @@
|
||||
# Format and Lint Guide
|
||||
|
||||
We use Vite+ Oxfmt for formatting, ESLint for code-quality rules, and TypeScript for type safety.
|
||||
We use Vite+ Oxfmt for formatting, Vite+ Oxlint for selected performance-sensitive code-quality rules, ESLint for the remaining code-quality rules, and TypeScript for type safety.
|
||||
|
||||
## Format
|
||||
|
||||
@@ -19,7 +19,26 @@ vp fmt --check
|
||||
The shared formatter options and ignore boundaries live in the root `vite.config.ts`.
|
||||
Editor format-on-save must point Oxc at that file so local edits match the commit hook and CI.
|
||||
|
||||
## ESLint
|
||||
## Lint
|
||||
|
||||
Run the complete lint pipeline from the repository root:
|
||||
|
||||
```sh
|
||||
pnpm lint
|
||||
```
|
||||
|
||||
This runs `vp lint` first for the selected rules migrated to native Oxlint, then ESLint for all remaining rules. Oxlint configuration lives in the root `vite.config.ts`; migrated rules stay disabled in the corresponding ESLint configs so they are not evaluated twice. Package-specific lint scopes that are not part of the migration continue to use ESLint.
|
||||
|
||||
Use the matching scripts when applying fixes or suppressing warning output:
|
||||
|
||||
```sh
|
||||
pnpm lint:fix
|
||||
pnpm lint:quiet
|
||||
```
|
||||
|
||||
Do not migrate the entire ESLint configuration. Before moving another rule, measure it with `TIMING=all npx eslint --quiet`, verify that Oxlint has a compatible native implementation, and compare diagnostics and fixes before disabling the ESLint rule.
|
||||
|
||||
### ESLint
|
||||
|
||||
### Common Flags
|
||||
|
||||
@@ -39,7 +58,7 @@ Keep this enabled when linting multiple files.
|
||||
- [ESLint multi-thread linting blog post]
|
||||
|
||||
**`--fix`**: Automatically fixes code-quality violations such as unused imports and preferred syntax.
|
||||
Run `vp fmt` after ESLint fixes so Oxfmt produces the final layout.
|
||||
Use `pnpm lint:fix` to apply Oxlint and ESLint fixes, then produce the final layout with Oxfmt.
|
||||
Always review the diff before committing to ensure no unintended changes.
|
||||
|
||||
**`--quiet`**: Suppresses warnings and only shows errors.
|
||||
@@ -55,8 +74,8 @@ Treat this as an escape hatch—fix these errors when time permits.
|
||||
### The Auto-Fix Workflow and Suppression Strategy
|
||||
|
||||
To streamline your development process, we recommend configuring your editor to automatically fix lint errors on save.
|
||||
Use Oxfmt for format-on-save and ESLint code actions for lint-only fixes.
|
||||
As a fallback, the commit hook runs `vp staged`, which applies ESLint fixes and then formats staged files with `vp fmt`.
|
||||
Use Oxfmt for format-on-save, with Oxc and ESLint code actions for their respective lint fixes.
|
||||
As a fallback, the commit hook runs `vp staged`, which applies Oxlint fixes, applies the remaining ESLint fixes, and then formats staged files with `vp fmt`.
|
||||
To prevent workflow disruptions, these commit hooks are intentionally bypassed when you are merging branches, rebasing, or cherry-picking.
|
||||
|
||||
Additionally, we currently track many existing legacy errors in eslint-suppressions.json.
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
import path from 'node:path'
|
||||
import antfu, {
|
||||
GLOB_JS,
|
||||
GLOB_JSX,
|
||||
GLOB_MARKDOWN,
|
||||
GLOB_MARKDOWN_CODE,
|
||||
GLOB_TESTS,
|
||||
@@ -16,6 +18,7 @@ import jsxA11y from 'eslint-plugin-jsx-a11y'
|
||||
import markdownPreferences from 'eslint-plugin-markdown-preferences'
|
||||
import noBarrelFiles from 'eslint-plugin-no-barrel-files'
|
||||
import storybook from 'eslint-plugin-storybook'
|
||||
import { rulesMigratedToOxlint } from '../eslint.migrated-rules.mjs'
|
||||
import {
|
||||
GENERATED_IGNORES,
|
||||
HYOBAN_PREFER_TAILWIND_ICONS_OPTIONS,
|
||||
@@ -118,6 +121,11 @@ export default antfu(
|
||||
'unicorn/number-literal-case': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: [GLOB_JS, GLOB_JSX, GLOB_TS, GLOB_TSX],
|
||||
ignores: [GLOB_MARKDOWN_CODE],
|
||||
rules: rulesMigratedToOxlint,
|
||||
},
|
||||
{
|
||||
files: ['**/*.ts', '**/*.tsx'],
|
||||
settings: {
|
||||
@@ -135,7 +143,6 @@ export default antfu(
|
||||
tailwindcss,
|
||||
},
|
||||
rules: {
|
||||
'tailwindcss/no-duplicate-classes': 'error',
|
||||
'tailwindcss/no-unknown-classes': 'warn',
|
||||
},
|
||||
settings: {
|
||||
|
||||
Reference in New Issue
Block a user