Files

12 KiB

Static Check Guide

Vite+ provides the primary static check through vp check, which combines Oxfmt formatting, Oxlint code-quality rules, and TypeScript diagnostics. The root command also runs ESLint for non-code file types that Oxlint cannot parse.

Check

Run the complete repository check from the root before committing or pushing:

vp run -w check

Apply safe fixes before running the same checks:

vp run -w check:fix

CI and local development use the same root vite.config.ts configuration.

The root check script delegates to the check:cached Vite Task. Formatting, linting, and type checking reuse successful results when their tracked inputs are unchanged. CI, NODE_ENV, and TAILWIND_CANONICAL_CLASSES are included in the cache key; check results never restore files into the working tree. Fix commands remain uncached. To force a fresh check, run vp run -w --no-cache check.

The TS Common CI job restores the task cache after dependency installation and saves it after a successful check. This is separate from the package-manager cache in setup-web. When evaluating CI performance, compare cache transfer time with the time saved in the Vite Task summary.

Reuse successful checks for the same final changes. Repeat or expand checks only when subsequent edits, failures, or unresolved concerns require it.

To narrow formatting and linting, pass paths directly to Vite+. Type checking remains repository-wide:

vp check web/app/components packages/dify-ui/src/button
vp check --fix web/app/components packages/dify-ui/src/button

Run only the Web JSX accessibility rules for selected files or directories with lint:a11y. Quote paths that contain shell metacharacters such as parentheses:

vp run dify-web#lint:a11y 'app/(commonLayout)/app/(appDetailLayout)/layout.tsx'

Use dependency mode to resolve an entry file's transitive local imports, including path aliases, re-exports, and dynamic imports, and then lint the resulting JSX and TSX files:

vp run dify-web#lint:a11y --deps 'app/(commonLayout)/app/(appDetailLayout)/layout.tsx'

This is a local page-scoped diagnostic. The repository-wide accessibility rule baseline remains owned by lint.config.ts and is also enforced by the normal vp check path.

Run the ESLint fallback separately when targeting JSON, JSONC, JSON5, YAML, TOML, or Markdown:

vp run -w lint:eslint package.json pnpm-workspace.yaml web/docs
vp run -w lint:eslint:fix package.json pnpm-workspace.yaml web/docs

Oxlint and Vite+ type-check scope is defined by lint.config.ts ignorePatterns, and ESLint's scope is defined by eslint.config.mjs global ignores.

The primary rule baseline lives in lint.config.ts and is connected through the root vite.config.ts lint block. Oxlint-native rules are preferred, and compatible ESLint rules can run through Oxlint's jsPlugins support. The rules are explicit snapshots of the ESLint configurations that were active at migration time. Do not import an upstream preset wholesale: enable a new rule intentionally and review its existing violations first.

Tailwind canonical class cleanup is optional because loading the JavaScript plugin adds noticeable lint startup time. The default vp run -w check command does not load it. Run vp run -w lint:tailwind to inspect web/ and packages/dify-ui/, or vp run -w lint:tailwind:fix to apply safe replacements. Both commands run the complete lint configuration with the additional better-tailwindcss/enforce-canonical-classes rule, using web/app/styles/globals.css and a 16px root font size.

The non-code baseline and its repository-wide file scope live in eslint.config.mjs. ESLint checks JSON, JSONC, JSON5, YAML, TOML, and Markdown only. The configuration globally ignores JavaScript, JSX, TypeScript, TSX, and declaration files; a comment-only inventory records the removed code checks as a migration tradeoff. It does not import or depend on the Antfu ESLint config.

Type-aware Linting

The root configuration enables both typeAware and typeCheck, so vp check runs type-aware rules and full diagnostics through the repository's @typescript/native compiler.

The shared packages/tsconfig/base.json enforces erasable TypeScript syntax through erasableSyntaxOnly. Root tooling and TypeScript packages inherit this contract; enum declarations, runtime namespaces, parameter properties, and import assignments are checked by the compiler without a separate lint plugin.

The web package still runs its existing TSSLint rule separately:

vp run dify-web#lint:tss

Bulk Suppressions

Existing Oxlint error diagnostics are tracked in the root oxlint-suppressions.json baseline. Oxlint reports newly added errors beyond that per-file rule baseline. ESLint has no bulk-suppression baseline. Warnings remain visible and do not fail the normal lint command.

The bulk-suppression flags are available in the bundled Oxlint version but are currently hidden from vp lint --help. Run them from the repository root so every package uses the same baseline:

vp run -w lint:oxlint --suppress-all
vp run -w lint:oxlint --prune-suppressions

The Oxc editor extension does not yet apply the bulk-suppression baseline, so the editor may still display findings that the CLI suppresses.

Known Migration Gaps

ESLint is intentionally limited to non-code files. The remaining limitations and accepted migration tradeoffs are:

Area Current status
Code-only fallback rules ESLint globally ignores all code files. Six core fallback rules, JS dot-notation, and other code-only ESLint checks are listed only in comments rather than executable configuration.
Declaration files Oxlint excludes declaration files and ESLint no longer processes code. The former 223-rule declaration snapshot and CLI declaration import restriction are not enforced.
Generated contracts Both linters and Vite+ type checking ignore packages/contracts/**; Oxfmt remains the only staged quality step for the contracts package.
Non-JavaScript formats Oxlint plugins cannot provide custom parsers or file languages. ESLint covers JSON, JSONC, YAML, TOML, and Markdown semantic rules, while Oxfmt remains responsible for their formatting.
Markdown code blocks ESLint validates the Markdown document, but fenced JavaScript and TypeScript blocks are not passed through the former overlapping preset. This remains deferred rather than duplicating the Oxlint rule set.
Override-scoped settings The three Dify UI Tailwind rules are disabled with the rest of ESLint's code path. Oxlint still applies the web react-x.additionalStateHooks setting globally because it cannot scope settings to an override.

Suppression comments belong to exactly one linter. Use oxlint-disable for code rules from lint.config.ts, and use eslint-disable only for non-code rules from eslint.config.mjs. Oxlint deliberately sets respectEslintDisableDirectives to false, so an ESLint comment cannot hide an Oxlint finding.

Inline Disable Comments

Prefer fixing the finding. When an exception is necessary, name the specific rule and use oxlint-disable-next-line at the affected statement. For a shared exception spanning several statements, use a bounded disable/enable pair. File-wide disable comments are forbidden, including in tests. dify/no-file-wide-disable reports an error when a block disable has any rules left disabled at the end of the file; a matching enable must restore every disabled rule. The native unicorn/no-abusive-eslint-disable rule also rejects disables without rule names, which would otherwise suppress the custom check itself.

For existing violations that cannot be fixed in the current change, remove the file-wide comment and record a scoped bulk-suppression baseline instead of turning the rule off for the file:

vp run -w lint:oxlint path/to/file.spec.tsx --suppress-all

Review the oxlint-suppressions.json diff and retain only the intended file/rule counts. The rule remains active and findings beyond the recorded count are reported; this is a count baseline, not a list of specific suppressed lines. Avoid repository-wide --suppress-all for a scoped cleanup. After fixing violations, use --prune-suppressions as described above.

Use lint.config.ts overrides only when a rule is intentionally inapplicable to a file, and ignorePatterns only when the entire file must be excluded, such as generated output. Explain the reason next to the configuration. Do not migrate existing violations to a blanket rule-off override or broaden exceptions to future files with a directory glob.

Explain the concrete reason after --: which external contract, lifecycle, or rule limitation makes the exception necessary. A description that merely repeats the rule or says "fix lint" is insufficient. New or modified disables must include this explanation; existing test typing exceptions can be addressed incrementally.

dify/require-disable-directive-description uses Oxlint's parsed directives to report missing explanations, including JSX comments. It runs at error; existing undescribed exceptions are tracked in the bulk-suppression baseline for incremental cleanup. Enable comments do not need a repeated explanation. This rule does not assess whether a reason is valid and does not replace review. Do not add generic descriptions just to silence it.

reportUnusedDisableDirectives runs at error repository-wide. Remove an exception when the finding no longer exists. Keep both checks active: a described disable may still be unused, and a used disable may still lack a reason.

Translation Function Types

dify/require-i18n-namespace requires translation hook calls to use non-empty inline namespace arrays, including single namespaces: useTranslation(['common']). Strings and indirect arguments are rejected; only calls reading the i18n instance alone may omit namespaces. Both react-i18next and #i18n are checked. The shared client/server adapter accepts typed non-empty tuples and forwards them through one documented lint exception in its client implementation.

dify/require-t-function-namespace requires i18next TFunction types to declare a non-empty inline tuple of namespace string literals. Use TFunction<['common']> or TFunction<['common', 'workflow']>; readonly tuples are also supported. Omitted arguments, single strings, broad namespace types, tuple aliases, and unions or rest elements inside the tuple are rejected. Named import aliases, namespace imports, and inline import('i18next').TFunction types are checked.

Declare the namespaces the helper or component actually uses. TypeScript checks translation keys and compatibility with callers; the lint rule does not infer transitive dependencies or detect unused namespaces. Keep the first namespace compatible with the caller because it defines the default translation namespace. The rule has no automatic fix because choosing the dependencies requires reading the translation calls.

Introducing New Plugins or Rules

Prefer a native Oxlint rule. If none exists, verify that the rule works through an Oxlint JS plugin on representative files. Record unsupported code rules as migration gaps instead of adding them to ESLint; reserve the ESLint configuration for non-code languages that Oxlint cannot parse. Do not add the Antfu ESLint config as a dependency or enable rules already covered by Oxlint.