Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions .changeset/oxlint-1-86-suppressions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
---
'@sanity/assist': patch
'@sanity/block-insert-picker': patch
'@sanity/code-input': patch
'@sanity/cross-dataset-duplicator': patch
'@sanity/dashboard': patch
'@sanity/document-internationalization': patch
'@sanity/embeddings-index-ui': patch
'@sanity/personalization-plugin': patch
'@sanity/sfcc': patch
'@sanity/themer': patch
'sanity-naive-html-serializer': patch
'sanity-plugin-internationalized-array': patch
'sanity-plugin-markdown': patch
'sanity-plugin-media': patch
'sanity-plugin-mux-input': patch
'sanity-plugin-shopify-assets': patch
'sanity-translations-tab': patch
---

Update inline oxlint suppressions for the updated shared lint rules
5 changes: 5 additions & 0 deletions .changeset/plugin-kit-oxlint-1-86.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@sanity/plugin-kit': patch
---

Require oxlint ^1.86.0, and ban `export *` barrel files that load over 100 modules (`oxc/no-barrel-file`) and the `styled-components` default import in the shared oxlint config
9 changes: 5 additions & 4 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,8 @@ Note on **knip**: in-file usage keeps an exported type "used" (`ignoreExportsUse

### Lint Specifics

- **oxlint**: Type-aware linting with `--deny-warnings` (warnings are errors). React Compiler rules run natively via the [React Compiler vendored into oxlint](https://oxc.rs/blog/2026-08-18-react-compiler-support.html); the root `oxlint.config.ts` enables all of them (stricter than the plugin-kit preset) since this monorepo builds plugins with React Compiler.
- **oxlint**: Type-aware linting with `--deny-warnings` (warnings are errors). React Compiler rules run natively via the [React Compiler vendored into oxlint](https://oxc.rs/blog/2026-08-18-react-compiler-support.html); the root `oxlint.config.ts` enables all of them (stricter than the plugin-kit preset) since this monorepo builds plugins with React Compiler, except `react/exhaustive-effect-dependencies`, which only checks effects and duplicates `react/exhaustive-deps`. Keep the `oxlint`/`oxlint-tsgolint` versions and the rules in line with sanity-io/ui.
- **`oxc/no-barrel-file`** fails files whose `export *` re-exports load more than 100 modules, counting everything they import, type-only imports included. Prefer named re-exports (or `export type *` for modules that only export types); the existing `export *` entry points carry inline suppressions.
- **TypeScript type checking** is included in `pnpm lint` via oxlint — no separate `tsc` needed
- **stylelint** lints every `*.css` file (`pnpm lint:css`, part of `pnpm lint`) with the `@sanity/plugin-kit/stylelint` preset: `stylelint-config-recommended` plus `stylelint-config-css-modules`, formatting rules off since oxfmt owns formatting. The root `stylelint.config.mjs` imports and spreads the preset; it is `.mjs` on purpose, as stylelint loads `.ts` configs through the TypeScript compiler API that TypeScript 7 does not ship. stylelint 17 has no GitHub formatter, so the CI lint job registers `.github/problem-matchers/stylelint.json` to turn its default output into annotations.
- Run `pnpm lint:fix` to auto-fix issues when possible (regenerates the CSS module typings, then stylelint and oxlint with `--fix`)
Expand Down Expand Up @@ -566,10 +567,10 @@ The oxfmt version, options and ignore patterns match sanity-io/ui's `.oxfmtrc.js

Changesets are formatted like any other file, so their frontmatter uses single quotes, and Changesets formats the changesets it writes with oxfmt. Renovate's changeset action writes double quotes, so after a Renovate PR merges, the Auto format workflow opens a `chore(format): 🤖 ✨` PR that requotes it, as in sanity-io/ui.

A lefthook pre-commit hook (`lefthook.yml`, set up like sanity-io/sanity's) runs oxfmt on the staged files and re-stages them. `pnpm install` installs the hook through lefthook's own install script, which `allowBuilds` in `pnpm-workspace.yaml` permits and which skips when `CI` is set. Skip the hook once with `LEFTHOOK=0 git commit`.
A lefthook pre-commit hook (`lefthook.yml`, set up like sanity-io/sanity's) runs oxfmt on the staged files, then `oxlint --fix --quiet` on the staged scripts, and re-stages them; a lint error that `--fix` can't resolve blocks the commit. `pnpm install` installs the hook through lefthook's own install script, which `allowBuilds` in `pnpm-workspace.yaml` permits and which skips when `CI` is set. Skip the hook once with `LEFTHOOK=0 git commit`.

- **Clones that ran husky:** they still have `core.hooksPath` set to `.husky/_`, and lefthook won't install there. Run `git config --unset core.hooksPath && pnpm install` once.
- **Hook commands:** they call `node_modules/.bin/oxfmt` directly. `pnpm <bin>` in a hook runs pnpm 12's implicit install, which flips the peer toggle described under pnpm Version.
- **Hook commands:** they call `node_modules/.bin/oxfmt` and `node_modules/.bin/oxlint` directly. `pnpm <bin>` in a hook runs pnpm 12's implicit install, which flips the peer toggle described under pnpm Version.
- **Local scripts:** `pnpm --config.verify-deps-before-run=false <script>` avoids that implicit install too. Turbo tasks (`pnpm build`, the `pretest` build) run plain `pnpm run` in every package, so locally they trigger it as well, and that install is not frozen: it re-resolves the Studio `next` overrides (see `Lockfile re-resolution also moves the Studio next overrides`) and rewrites `pnpm-lock.yaml`. When the lockfile must stay put, run the package scripts through that flag instead, or export `pnpm_config_verify_deps_before_run=false` for the shell session—pnpm 12 reads `pnpm_config_*` environment variables (not `npm_config_*`), and the variable also reaches the nested `pnpm run` invocations inside turbo tasks that the CLI flag cannot. If it already happened, `git checkout pnpm-lock.yaml && pnpm install --frozen-lockfile` puts `node_modules` back on the locked versions.

### Linting
Expand All @@ -581,7 +582,7 @@ pnpm lint # Run the linter (includes type checking)
pnpm lint:fix # Auto-fix what's possible
```

The shared rules (plugins, options, categories, rules) live in the `@sanity/plugin-kit/oxlint` config (`packages/@sanity/plugin-kit/src/oxlint.ts`), which the root `oxlint.config.ts` extends; only workspace-specific ignores and overrides belong in the root config. Standalone plugins scaffolded with `plugin-kit init` re-export the same shared config. Note that `ignorePatterns` do not propagate through `extends`, so the root config spreads the shared patterns before adding its own. Like `pnpm format`, `pnpm lint` requires Node `>=22.18` to load the TypeScript config.
The shared rules (plugins, options, categories, rules) live in the `@sanity/plugin-kit/oxlint` config (`packages/@sanity/plugin-kit/src/oxlint.ts`), which the root `oxlint.config.ts` extends; only workspace-specific ignores and overrides belong in the root config. Standalone plugins scaffolded with `plugin-kit init` re-export the same shared config. Note that `ignorePatterns` do not propagate through `extends`, so the root config spreads the shared patterns before adding its own. Like `pnpm format`, `pnpm lint` requires Node `>=22.18` to load the TypeScript config. The pre-commit hook lints staged scripts with the same config (see the lefthook notes under Formatting), so it needs that Node version too.

### CSS modules

Expand Down
6 changes: 4 additions & 2 deletions lefthook.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@
pre-commit:
parallel: true
jobs:
- name: format-scripts
- name: format-and-lint-scripts
glob: '*.{js,jsx,ts,tsx,mjs,cjs,mts,cts}'
stage_fixed: true
run: node_modules/.bin/oxfmt --no-error-on-unmatched-pattern {staged_files}
run: |
node_modules/.bin/oxfmt --no-error-on-unmatched-pattern {staged_files}
node_modules/.bin/oxlint --disable-nested-config --fix --quiet --no-error-on-unmatched-pattern {staged_files}

- name: format-other
glob: '*.{md,yml,yaml,css,json,jsonc}'
Expand Down
5 changes: 3 additions & 2 deletions oxlint.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,11 @@ export default defineConfig({
],
rules: {
// This monorepo builds plugins with React Compiler, so unlike the plugin-kit preset
// (which mostly relies on category defaults) every React Compiler rule is enabled:
// (which mostly relies on category defaults) every React Compiler rule is enabled
// except `react/exhaustive-effect-dependencies`, which only checks effects and
// duplicates the preset's `react/exhaustive-deps`:
// https://oxc.rs/blog/2026-08-18-react-compiler-support.html#oxlint
'react/capitalized-calls': 'error',
'react/exhaustive-effect-dependencies': 'error',
'react/hooks': 'error',
'react/invariant': 'error',
'react/memo-dependencies': 'error',
Expand Down
10 changes: 9 additions & 1 deletion packages/@sanity/plugin-kit/src/oxlint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ import type {OxlintConfig} from 'oxlint'
* @public
*/
const config: OxlintConfig = {
plugins: ['typescript', 'unicorn', 'react', 'oxc', 'import', 'jsx-a11y', 'promise'],
plugins: ['typescript', 'unicorn', 'react', 'react-perf', 'oxc', 'import', 'jsx-a11y', 'promise'],
// oxlint resolves relative `jsPlugins` specifiers from the extending config, not from this
// one, so hand it the plugin's absolute path.
jsPlugins: [fileURLToPath(import.meta.resolve('@sanity/plugin-kit/oxlint-plugin'))],
Expand Down Expand Up @@ -74,6 +74,11 @@ const config: OxlintConfig = {
message:
'Use the official `@date-fns/tz` package (TZDate, tz, tzOffset) with date-fns v4 instead, matching sanity core.',
},
{
name: 'styled-components',
importNames: ['default'],
message: 'Please use `import {styled} from "styled-components"` instead.',
},
{
name: 'react',
importNames: ['createElement'],
Expand Down Expand Up @@ -126,6 +131,9 @@ const config: OxlintConfig = {
'import/no-commonjs': 'error',
'import/no-unassigned-import': ['error', {allow: ['**/*.css']}],
'plugin-kit/no-css-module-default-import': 'error',
// Ban `export *` barrel files that pull in large module graphs; import
// directly from the module that defines a symbol instead
'oxc/no-barrel-file': 'error',
'no-restricted-globals': [
'error',
{name: '__dirname', message: 'Use path.dirname(fileURLToPath(import.meta.url)) instead'},
Expand Down
1 change: 1 addition & 0 deletions plugins/@sanity/assist/src/_lib/form/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
// oxlint-disable-next-line oxc/no-barrel-file
export * from './DocumentForm'
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,6 @@ function FieldsInitializer({
}
onChange(event)
initialized.current = true
// oxlint-disable-next-line react/exhaustive-effect-dependencies
}, [activePath, onChange, pathKey, existingField, missingPresetInstructions])

return null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,6 @@ function useSyntheticTasks(assistableDocumentId: string) {
useEffect(() => {
// oxlint-disable-next-line react/set-state-in-effect
setSyntheticTasks([])
// oxlint-disable-next-line react/exhaustive-effect-dependencies
}, [assistableDocumentId])

return {
Expand Down
1 change: 1 addition & 0 deletions plugins/@sanity/assist/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import '@sanity/ui/styles.css'

export {assist} from './plugin'
// oxlint-disable-next-line oxc/no-barrel-file
export * from './schemas/serialize/SchemTypeTool'
export * from './schemas/typeDefExtensions'
export {defaultLanguageOutputs} from './translate/paths'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -518,7 +518,6 @@ export function BlockInsertPicker({

useEffect(() => {
highlightedRowRef.current?.scrollIntoView({block: 'nearest'})
// oxlint-disable-next-line react/exhaustive-effect-dependencies
}, [highlightedIndex])

// Stable virtual element derived from the captured rect — keeps Popover
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,6 @@ function CodeMirrorProxy(props: CodeMirrorProps & {ref?: Ref<ReactCodeMirrorRef>
if (editorView) {
setHighlightedLines(editorView, highlightLines ?? [])
}
// oxlint-disable-next-line react/exhaustive-effect-dependencies
}, [editorView, highlightLines, value])

const [initialState] = useState(() => {
Expand Down
1 change: 1 addition & 0 deletions plugins/@sanity/cross-dataset-duplicator/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import '@sanity/ui/styles.css'

// oxlint-disable-next-line oxc/no-barrel-file
export * from './types'
export * from './plugin'
export * from './actions/DuplicateToAction'
Expand Down
1 change: 1 addition & 0 deletions plugins/@sanity/dashboard/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import '@sanity/ui/styles.css'

// oxlint-disable-next-line oxc/no-barrel-file
export * from './types'
export * from './components/DashboardWidgetContainer'
export * from './widgets/projectInfo'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@ export {
export {useDocumentInternationalizationContext} from './components/DocumentInternationalizationContext'
export {DocumentInternationalizationMenu} from './components/DocumentInternationalizationMenu'
export {documentInternationalization} from './plugin'
// oxlint-disable-next-line oxc/no-barrel-file
export * from './types'
1 change: 1 addition & 0 deletions plugins/@sanity/embeddings-index-ui/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ export {embeddingsIndexReferenceInput}

export {embeddingsIndexDashboard}

// oxlint-disable-next-line oxc/no-barrel-file
export * from './api/embeddingsApi'
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// oxlint-disable-next-line oxc/no-barrel-file
export * from './Array'
export * from './Context'
export * from './Field'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// oxlint-disable-next-line oxc/no-barrel-file
export * from './Array'
export * from './Context'
export * from './Field'
Expand Down
1 change: 1 addition & 0 deletions plugins/@sanity/personalization-plugin/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import '@sanity/ui/styles.css'

// oxlint-disable-next-line oxc/no-barrel-file
export * from './fieldExperiments'
export * from './fieldPersonalization'
export * from './types'
Expand Down
1 change: 1 addition & 0 deletions plugins/@sanity/sfcc/src/structure/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@ export function defineStructure<StructureType>(
return factory
}

// oxlint-disable-next-line oxc/no-barrel-file
export * from './categoryStructure'
export * from './productStructure'
1 change: 0 additions & 1 deletion plugins/@sanity/themer/src/tool/ImagePaletteSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,6 @@ export function ImagePaletteSection(props: {
observer.observe(row)

return () => observer.disconnect()
// oxlint-disable-next-line react/exhaustive-effect-dependencies -- the row only renders with a palette, so a new palette has a new row to observe
}, [palette])

const variantRowStyle: VariantRowStyle = {
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
// oxlint-disable-next-line oxc/no-barrel-file
export * from './BaseDocumentDeserializer'
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
// oxlint-disable-next-line oxc/no-barrel-file
export * from './migrateToLanguageField'
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@ import '@sanity/ui/styles.css'
export {clear} from './cache'
export {LANGUAGE_FIELD_NAME} from './constants'
export {internationalizedArray} from './plugin'
// oxlint-disable-next-line oxc/no-barrel-file
export * from './types'
export {internationalizedArrayLanguageFilter} from './utils/internationalizedArrayLanguageFilter'
1 change: 1 addition & 0 deletions plugins/sanity-plugin-markdown/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ import '@sanity/ui/styles.css'
// @ts-expect-error - fix the missing typings later
import 'easymde/dist/easymde.min.css'

// oxlint-disable-next-line oxc/no-barrel-file
export * from './commonExports'
1 change: 1 addition & 0 deletions plugins/sanity-plugin-markdown/src/indexNext.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import '@sanity/ui/styles.css'

// oxlint-disable-next-line oxc/no-barrel-file
export * from './commonExports'
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,5 @@ export function useBrowserInit(
}),
)
}
// oxlint-disable-next-line react/exhaustive-effect-dependencies
}, [tagsFetchCount, hasMediaTags]) // eslint-disable-line react-hooks/exhaustive-deps
}
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,6 @@ export default function EditCaptionDialog({asset, track, onUpdate, onClose}: Pro
)
const foundByName = track.name ? LANGUAGE_OPTIONS.find((opt) => opt.label === track.name) : null
setSelectedLanguage(foundByCode || foundByName || null)
// oxlint-disable-next-line react/exhaustive-effect-dependencies
}, [track, asset, client])

const handleDownloadCurrentFile = async () => {
Expand Down
1 change: 1 addition & 0 deletions plugins/sanity-plugin-shopify-assets/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {shopifyAssetPreviewSchema} from './schema/shopifyAssetPreviewSchema'
import {shopifyAssetSchema} from './schema/shopifyAssetSchema'
import type {PluginConfig} from './types'

// oxlint-disable-next-line oxc/no-barrel-file
export * from './types'

// enables autocompletion and validation of document options
Expand Down
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
// oxlint-disable-next-line oxc/no-barrel-file
export * from './findDocumentAtRevision'
export * from './findLatestDraft'
2 changes: 1 addition & 1 deletion pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion pnpm-workspace.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ catalog:
nanoid: ^6.0.2
oxc-transform-react: ^0.154.0
oxfmt: ^0.73.0
oxlint: ^1.83.0
oxlint: ^1.86.0
oxlint-tsgolint: ^7.0.2003
react: ^19.3.1
react-dom: ^19.3.1
Expand Down
Loading