From 759f04802c83983bd429ecb5ab9fdaedd5f9d42c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Az=C3=B3car?= Date: Fri, 9 Oct 2026 21:51:07 -0300 Subject: [PATCH 1/3] docs: migrating from eslint-plugin-tailwindcss MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit eslint-plugin-tailwindcss (francoismassart) reads Tailwind v4 since its 4.x releases, and had no guide here. /migration/from-eslint-plugin-tailwindcss (EN/ES) maps each of 4.4.0's 9 rules and 7 settings, with what differs, and walks from an ESLint flat config to an .oxlintrc.json. Everything it says comes from the 4.4.0 package or from running both plugins: - Their canonical rule folds in what has its own rule here (v3 renames, bg-[var(--x)], !p-4, -m-[5px]), and rewrites what this one leaves because the CSS differs ([&>*]: → *:, aspect-[16/9] → aspect-video). - Their shorthand also makes truncate; four corners fold at once here. - Their no-unnecessary-arbitrary-value turns m-[8px] / w-[20rem] into tokens: prefer-scale-token here, as a suggestion. - Settings: theirs replace the defaults, ours add to them; functions → callees + tags (classNames(…) and tw(…) as a call aren't read here); keys of an object are classes in any callee here, only in parseKeyFunctions there; a relative cssConfigPath resolves against the nearest eslint.config.* or package.json, entryPoint against the nearest oxlint config. - Both plugins are named `tailwindcss`: oxlint refuses to load the two side by side, so the guide says to remove theirs in the same step. Data-driven like the better-tailwindcss guide: data/eslint-plugin-tailwindcss.json renders the rules, settings and "rules they don't have" blocks through migration* functions in scripts/blocks.ts, which the btw* blocks now share. Sidebar and FAQ entries in both locales. Tests: blocks.test.ts for the three blocks, and eslint-plugin-tailwindcss-map.test.ts for this plugin's side (rules, settings and options exist, each mapped once, and the prose names the data's version and rule count in both locales). Claude-Session: https://claude.ai/code/session_019vTh6S5A41M1HLkmk3FaM1 --- packages/docs/.vitepress/config.ts | 8 + packages/docs/CLAUDE.md | 6 +- .../docs/data/eslint-plugin-tailwindcss.json | 141 +++++++++++++++++ packages/docs/es/faq.md | 6 + .../from-eslint-plugin-tailwindcss.md | 147 ++++++++++++++++++ packages/docs/faq.md | 6 + .../from-eslint-plugin-tailwindcss.md | 147 ++++++++++++++++++ packages/docs/scripts/blocks.ts | 64 ++++++-- packages/docs/scripts/generate-blocks.ts | 17 +- packages/docs/tests/blocks.test.ts | 35 +++++ .../eslint-plugin-tailwindcss-map.test.ts | 65 ++++++++ 11 files changed, 631 insertions(+), 11 deletions(-) create mode 100644 packages/docs/data/eslint-plugin-tailwindcss.json create mode 100644 packages/docs/es/migration/from-eslint-plugin-tailwindcss.md create mode 100644 packages/docs/migration/from-eslint-plugin-tailwindcss.md create mode 100644 packages/oxlint-tailwindcss/tests/docs/eslint-plugin-tailwindcss-map.test.ts diff --git a/packages/docs/.vitepress/config.ts b/packages/docs/.vitepress/config.ts index 165519f..45ba504 100644 --- a/packages/docs/.vitepress/config.ts +++ b/packages/docs/.vitepress/config.ts @@ -139,6 +139,10 @@ export default defineConfig({ items: [ { text: 'v0 → v1', link: '/migration/v0-to-v1' }, { text: 'From better-tailwindcss', link: '/migration/from-better-tailwindcss' }, + { + text: 'From eslint-plugin-tailwindcss', + link: '/migration/from-eslint-plugin-tailwindcss', + }, ], }, ], @@ -228,6 +232,10 @@ export default defineConfig({ items: [ { text: 'v0 → v1', link: '/es/migration/v0-to-v1' }, { text: 'Desde better-tailwindcss', link: '/es/migration/from-better-tailwindcss' }, + { + text: 'Desde eslint-plugin-tailwindcss', + link: '/es/migration/from-eslint-plugin-tailwindcss', + }, ], }, ], diff --git a/packages/docs/CLAUDE.md b/packages/docs/CLAUDE.md index 0e74fbe..a69b1b6 100644 --- a/packages/docs/CLAUDE.md +++ b/packages/docs/CLAUDE.md @@ -106,7 +106,11 @@ rule on); its @shadcn/lint column and the combined config by `bench/interop.mjs` `interop.yml`. When @shadcn/lint changes what it reports, update the data, not the page. The same goes for `/migration/from-better-tailwindcss` and `data/better-tailwindcss.json`: every row's example is reported by both of its rules (`bench/interop.mjs`), and the rules and settings it names -exist here (`tests/docs/better-tailwindcss-map.test.ts`). +exist here (`tests/docs/better-tailwindcss-map.test.ts`). And for +`/migration/from-eslint-plugin-tailwindcss` and `data/eslint-plugin-tailwindcss.json`, checked the +same way (`tests/docs/eslint-plugin-tailwindcss-map.test.ts` also holds the version and rule count +the page's prose names). Both guides render through the same `migration*` functions in +`scripts/blocks.ts`. `/benchmark` and `/comparison` hold no hand-typed number: their tables come from `bench/results/latest.json` (`scripts/bench-blocks.ts`), which `bench/score.mjs` writes and diff --git a/packages/docs/data/eslint-plugin-tailwindcss.json b/packages/docs/data/eslint-plugin-tailwindcss.json new file mode 100644 index 0000000..1f8049d --- /dev/null +++ b/packages/docs/data/eslint-plugin-tailwindcss.json @@ -0,0 +1,141 @@ +{ + "$comment": "eslint-plugin-tailwindcss (francoismassart) → oxlint-tailwindcss. Generates the tables on /migration/from-eslint-plugin-tailwindcss (scripts/blocks.ts). Every example is reported by both rules of its row, with default options: checked by bench/interop.mjs (weekly, with the latest release, in .github/workflows/interop.yml), which also checks that every rule of the pinned version has a row; that every rule and setting named here exists, by tests/docs/eslint-plugin-tailwindcss-map.test.ts.", + "eslintPluginTailwindcss": "4.4.0", + "rules": [ + { + "theirs": "classnames-order", + "ours": "enforce-sort-order", + "example": "
x
", + "notes": { + "en": "Both sort by Tailwind's own class order, the one prettier-plugin-tailwindcss uses. `mode: \"strict\"` also groups classes by variant chain.", + "es": "Las dos ordenan con el orden de clases de Tailwind, el que usa prettier-plugin-tailwindcss. `mode: \"strict\"` además agrupa las clases por cadena de variants." + } + }, + { + "theirs": "enforces-canonical-classname", + "ours": "enforce-canonical", + "example": "
x
", + "notes": { + "en": "Both ask Tailwind for the canonical form, but this rule rewrites a class only when the two print the same CSS: `[&>*]:flex-1` → `*:flex-1` (another selector) and `aspect-[16/9]` → `aspect-video` (a theme variable) stay as written. The rest of what theirs folds in has its own rule here: v3 renames (`bg-gradient-to-r`) are `no-deprecated-classes`, `bg-[var(--x)]` → `bg-(--x)` is `enforce-consistent-variable-syntax`, `!p-4` → `p-4!` is `enforce-consistent-important-position`, and `-m-[5px]` → `m-[-5px]` is `enforce-negative-arbitrary-values`.", + "es": "Las dos le piden a Tailwind la forma canónica, pero esta regla solo reescribe una clase cuando las dos imprimen el mismo CSS: `[&>*]:flex-1` → `*:flex-1` (otro selector) y `aspect-[16/9]` → `aspect-video` (una variable del tema) se quedan como están. Lo demás que la suya incluye tiene aquí su propia regla: los renombres de v3 (`bg-gradient-to-r`) son de `no-deprecated-classes`, `bg-[var(--x)]` → `bg-(--x)` de `enforce-consistent-variable-syntax`, `!p-4` → `p-4!` de `enforce-consistent-important-position` y `-m-[5px]` → `m-[-5px]` de `enforce-negative-arbitrary-values`." + } + }, + { + "theirs": "enforces-negative-arbitrary-values", + "ours": "enforce-negative-arbitrary-values", + "example": "
x
" + }, + { + "theirs": "enforces-shorthand", + "ours": "enforce-shorthand", + "example": "
x
", + "notes": { + "en": "Their `overflow-hidden text-ellipsis whitespace-nowrap` → `truncate` has no equivalent here. Four corners or sides become one class at once (`rounded-tl-md rounded-tr-md rounded-br-md rounded-bl-md` → `rounded-md`), where theirs merges them two by two.", + "es": "Su `overflow-hidden text-ellipsis whitespace-nowrap` → `truncate` no tiene equivalente aquí. Cuatro esquinas o lados se vuelven una sola clase de una vez (`rounded-tl-md rounded-tr-md rounded-br-md rounded-bl-md` → `rounded-md`), donde la suya los une de dos en dos." + } + }, + { + "theirs": "important-modifier-suffix", + "ours": "enforce-consistent-important-position", + "example": "
x
", + "notes": { + "en": "The suffix (`p-4!`) is the default here too; `position: \"prefix\"` asks for `!p-4` instead.", + "es": "Aquí el sufijo (`p-4!`) también es el default; `position: \"prefix\"` pide `!p-4`." + } + }, + { + "theirs": "no-arbitrary-value", + "ours": "no-arbitrary-value", + "example": "
x
", + "notes": { + "en": "Off in both recommended configs. Here `allow` tolerates arbitrary values under the prefixes you list (`grid-cols-`), `allowVariables` a value that is only a CSS variable, and the message names the closest values in your theme.", + "es": "Apagada en los dos configs recomendados. Aquí `allow` tolera valores arbitrarios bajo los prefijos que listes (`grid-cols-`), `allowVariables` un valor que es solo una variable CSS, y el mensaje nombra los valores más cercanos de tu tema." + } + }, + { + "theirs": "no-contradicting-classname", + "ours": "no-conflicting-classes", + "example": "
x
", + "notes": { + "en": "Decided from the CSS each class prints, and the message names the class that wins. It also reports a class another one makes redundant (`reportRedundant`), and `allow` exempts pairs. A variant that repeats its base class (`flex hover:flex`) is `no-contradicting-variants`.", + "es": "Se decide con el CSS que imprime cada clase, y el mensaje nombra la que gana. También reporta una clase que otra vuelve redundante (`reportRedundant`), y `allow` exime pares. Una variant que repite su clase base (`flex hover:flex`) es cosa de `no-contradicting-variants`." + } + }, + { + "theirs": "no-custom-classname", + "ours": "no-unknown-classes", + "example": "
x
", + "notes": { + "en": "`whitelist` → `allowlist` for exact names and `ignorePrefixes` for families (`custom-`); a regular expression that is neither has no counterpart. The classes your CSS defines (`@layer components`, `@utility`) are always known, and a typo gets the closest class as a suggestion.", + "es": "`whitelist` → `allowlist` para nombres exactos e `ignorePrefixes` para familias (`custom-`); una expresión regular que no sea ninguna de las dos no tiene equivalente. Las clases que define tu CSS (`@layer components`, `@utility`) siempre se conocen, y un typo recibe como sugerencia la clase más cercana." + } + }, + { + "theirs": "no-unnecessary-arbitrary-value", + "ours": "no-unnecessary-arbitrary-value", + "example": "
x
", + "notes": { + "en": "This rule rewrites only when the named class prints the same CSS. Theirs also turns a value equal to a scale step or a theme value into its token (`m-[8px]` → `m-2`, `w-[20rem]` → `w-xs`): here that is `prefer-scale-token`, as a suggestion, since the token reads a variable your theme can change. `z-[123]` → `z-123` is `enforce-canonical`.", + "es": "Esta regla solo reescribe cuando la clase con nombre imprime el mismo CSS. La suya además convierte en su token un valor igual a un paso de la escala o a un valor del tema (`m-[8px]` → `m-2`, `w-[20rem]` → `w-xs`): aquí eso es `prefer-scale-token`, como sugerencia, porque el token lee una variable que tu tema puede cambiar. `z-[123]` → `z-123` es de `enforce-canonical`." + } + } + ], + "settings": [ + { + "theirs": "cssConfigPath", + "ours": "entryPoint", + "notes": { + "en": "Required in both. A relative path resolves against the nearest oxlint config here, and against the nearest `eslint.config.*` or `package.json` in theirs. A glob → CSS mapping covers a monorepo.", + "es": "Obligatorio en los dos. Una ruta relativa se resuelve aquí contra el config de oxlint más cercano, y en el suyo contra el `eslint.config.*` o `package.json` más cercano. Un mapeo glob → CSS cubre un monorepo." + } + }, + { + "theirs": "attributes", + "ours": "attributes, attributePatterns", + "notes": { + "en": "Setting theirs replaces its defaults (`class`, `className`, `ngClass`, `@apply`); here `attributes` adds to `class` and `className`, and `attributePatterns` matches names with regular expressions. `ngClass` and `@apply` have nothing to match: oxlint hands JS plugins neither Angular templates nor CSS.", + "es": "Fijar el suyo reemplaza sus defaults (`class`, `className`, `ngClass`, `@apply`); aquí `attributes` se suma a `class` y `className`, y `attributePatterns` encuentra nombres con expresiones regulares. `ngClass` y `@apply` no tienen nada que encontrar: oxlint no les pasa a los JS plugins ni templates de Angular ni CSS." + } + }, + { + "theirs": "functions", + "ours": "callees, tags", + "notes": { + "en": "Theirs is one list for calls and tagged templates, and setting it replaces the defaults; `callees` and `tags` add to this plugin's. Of their defaults, `classNames(…)` and `tw(…)` as a call aren't read here: add them to `callees` if you use them.", + "es": "El suyo es una sola lista para llamadas y tagged templates, y fijarla reemplaza los defaults; `callees` y `tags` se suman a los de este plugin. De sus defaults, `classNames(…)` y `tw(…)` como llamada no se leen aquí: agrégalos a `callees` si los usas." + } + }, + { + "theirs": "parseKeyFunctions", + "ours": null, + "notes": { + "en": "The keys of an object passed to any callee are read as classes (`cn({ \"p-4\": isOpen })`), and its values aren't. Theirs reads the keys only in these functions (by default `classnames`, `classNames`, `clsx`) and the values elsewhere.", + "es": "Las claves de un objeto que recibe cualquier callee se leen como clases (`cn({ \"p-4\": isOpen })`), y sus valores no. El suyo lee las claves solo en estas funciones (por defecto `classnames`, `classNames`, `clsx`) y los valores en las demás." + } + }, + { + "theirs": "ignoredKeys", + "ours": null, + "notes": { + "en": "`cva` and `tv` configs are read by their structure: `defaultVariants` is skipped, `compoundVariants` and `compoundSlots` are read.", + "es": "Los configs de `cva` y `tv` se leen por su estructura: `defaultVariants` se salta, `compoundVariants` y `compoundSlots` se leen." + } + }, + { + "theirs": "cacheMaxSize", + "ours": null, + "notes": { + "en": "The design system is cached on disk, keyed by the content of your CSS, and rebuilt when it changes: there is nothing to tune.", + "es": "El design system se guarda en caché en disco, indexado por el contenido de tu CSS, y se reconstruye cuando cambia: no hay nada que ajustar." + } + }, + { + "theirs": "cacheMaxAge", + "ours": null, + "notes": { + "en": "Same as `cacheMaxSize`.", + "es": "Igual que `cacheMaxSize`." + } + } + ] +} diff --git a/packages/docs/es/faq.md b/packages/docs/es/faq.md index 5a90efe..9f22f11 100644 --- a/packages/docs/es/faq.md +++ b/packages/docs/es/faq.md @@ -91,6 +91,12 @@ Cada una de sus reglas tiene su equivalente aquí; la [guía de migración](/es/migration/from-better-tailwindcss) mapea las reglas y sus settings, y recorre el cambio. +## Uso eslint-plugin-tailwindcss. ¿Cómo me cambio? + +Cada una de sus reglas también tiene su equivalente aquí. La +[guía de migración](/es/migration/from-eslint-plugin-tailwindcss) mapea las reglas y sus settings, y +te lleva de un config de ESLint a un `.oxlintrc.json`. + ## ¿Cómo lo hago rápido en CI? El design system se precalcula una vez por entry point y se guarda en caché en disco. Conserva esa diff --git a/packages/docs/es/migration/from-eslint-plugin-tailwindcss.md b/packages/docs/es/migration/from-eslint-plugin-tailwindcss.md new file mode 100644 index 0000000..273112b --- /dev/null +++ b/packages/docs/es/migration/from-eslint-plugin-tailwindcss.md @@ -0,0 +1,147 @@ +--- +title: "Migrar desde eslint-plugin-tailwindcss" +description: "Pasa de eslint-plugin-tailwindcss a oxlint-tailwindcss: cada una de sus reglas y settings, qué cambia y el paso de ESLint a oxlint, paso a paso." +--- + +# Migrar desde eslint-plugin-tailwindcss + +[eslint-plugin-tailwindcss](https://github.com/francoismassart/eslint-plugin-tailwindcss) es el +plugin de ESLint para Tailwind CSS; sus versiones 4.x leen Tailwind v4. oxlint-tailwindcss está +hecho para oxlint y Tailwind v4. Cada una de las 9 reglas de eslint-plugin-tailwindcss 4.4.0 tiene +su equivalente aquí, y cada par reporta el mismo ejemplo — las tablas de abajo se verifican contra +él cada semana, con su última versión. + +## Reglas + + + +| eslint-plugin-tailwindcss | oxlint-tailwindcss | Notas | +| ------------------------------------ | ------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `classnames-order` | [`enforce-sort-order`](/es/rules/enforce-sort-order) | Las dos ordenan con el orden de clases de Tailwind, el que usa prettier-plugin-tailwindcss. `mode: "strict"` además agrupa las clases por cadena de variants. | +| `enforces-canonical-classname` | [`enforce-canonical`](/es/rules/enforce-canonical) | Las dos le piden a Tailwind la forma canónica, pero esta regla solo reescribe una clase cuando las dos imprimen el mismo CSS: `[&>*]:flex-1` → `*:flex-1` (otro selector) y `aspect-[16/9]` → `aspect-video` (una variable del tema) se quedan como están. Lo demás que la suya incluye tiene aquí su propia regla: los renombres de v3 (`bg-gradient-to-r`) son de `no-deprecated-classes`, `bg-[var(--x)]` → `bg-(--x)` de `enforce-consistent-variable-syntax`, `!p-4` → `p-4!` de `enforce-consistent-important-position` y `-m-[5px]` → `m-[-5px]` de `enforce-negative-arbitrary-values`. | +| `enforces-negative-arbitrary-values` | [`enforce-negative-arbitrary-values`](/es/rules/enforce-negative-arbitrary-values) | | +| `enforces-shorthand` | [`enforce-shorthand`](/es/rules/enforce-shorthand) | Su `overflow-hidden text-ellipsis whitespace-nowrap` → `truncate` no tiene equivalente aquí. Cuatro esquinas o lados se vuelven una sola clase de una vez (`rounded-tl-md rounded-tr-md rounded-br-md rounded-bl-md` → `rounded-md`), donde la suya los une de dos en dos. | +| `important-modifier-suffix` | [`enforce-consistent-important-position`](/es/rules/enforce-consistent-important-position) | Aquí el sufijo (`p-4!`) también es el default; `position: "prefix"` pide `!p-4`. | +| `no-arbitrary-value` | [`no-arbitrary-value`](/es/rules/no-arbitrary-value) | Apagada en los dos configs recomendados. Aquí `allow` tolera valores arbitrarios bajo los prefijos que listes (`grid-cols-`), `allowVariables` un valor que es solo una variable CSS, y el mensaje nombra los valores más cercanos de tu tema. | +| `no-contradicting-classname` | [`no-conflicting-classes`](/es/rules/no-conflicting-classes) | Se decide con el CSS que imprime cada clase, y el mensaje nombra la que gana. También reporta una clase que otra vuelve redundante (`reportRedundant`), y `allow` exime pares. Una variant que repite su clase base (`flex hover:flex`) es cosa de `no-contradicting-variants`. | +| `no-custom-classname` | [`no-unknown-classes`](/es/rules/no-unknown-classes) | `whitelist` → `allowlist` para nombres exactos e `ignorePrefixes` para familias (`custom-`); una expresión regular que no sea ninguna de las dos no tiene equivalente. Las clases que define tu CSS (`@layer components`, `@utility`) siempre se conocen, y un typo recibe como sugerencia la clase más cercana. | +| `no-unnecessary-arbitrary-value` | [`no-unnecessary-arbitrary-value`](/es/rules/no-unnecessary-arbitrary-value) | Esta regla solo reescribe cuando la clase con nombre imprime el mismo CSS. La suya además convierte en su token un valor igual a un paso de la escala o a un valor del tema (`m-[8px]` → `m-2`, `w-[20rem]` → `w-xs`): aquí eso es `prefer-scale-token`, como sugerencia, porque el token lee una variable que tu tema puede cambiar. `z-[123]` → `z-123` es de `enforce-canonical`. | + + + +## Settings + +Los dos plugins leen `settings.tailwindcss`, con claves distintas: + + + +| eslint-plugin-tailwindcss | oxlint-tailwindcss | Notas | +| ------------------------- | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `cssConfigPath` | `entryPoint` | Obligatorio en los dos. Una ruta relativa se resuelve aquí contra el config de oxlint más cercano, y en el suyo contra el `eslint.config.*` o `package.json` más cercano. Un mapeo glob → CSS cubre un monorepo. | +| `attributes` | `attributes`, `attributePatterns` | Fijar el suyo reemplaza sus defaults (`class`, `className`, `ngClass`, `@apply`); aquí `attributes` se suma a `class` y `className`, y `attributePatterns` encuentra nombres con expresiones regulares. `ngClass` y `@apply` no tienen nada que encontrar: oxlint no les pasa a los JS plugins ni templates de Angular ni CSS. | +| `functions` | `callees`, `tags` | El suyo es una sola lista para llamadas y tagged templates, y fijarla reemplaza los defaults; `callees` y `tags` se suman a los de este plugin. De sus defaults, `classNames(…)` y `tw(…)` como llamada no se leen aquí: agrégalos a `callees` si los usas. | +| `parseKeyFunctions` | — | Las claves de un objeto que recibe cualquier callee se leen como clases (`cn({ "p-4": isOpen })`), y sus valores no. El suyo lee las claves solo en estas funciones (por defecto `classnames`, `classNames`, `clsx`) y los valores en las demás. | +| `ignoredKeys` | — | Los configs de `cva` y `tv` se leen por su estructura: `defaultVariants` se salta, `compoundVariants` y `compoundSlots` se leen. | +| `cacheMaxSize` | — | El design system se guarda en caché en disco, indexado por el contenido de tu CSS, y se reconstruye cuando cambia: no hay nada que ajustar. | +| `cacheMaxAge` | — | Igual que `cacheMaxSize`. | + + + +## El cambio, paso a paso + +1. Instala oxlint y el plugin: `pnpm add -D oxlint oxlint-tailwindcss`. +2. Crea `.oxlintrc.json` con `"jsPlugins": ["oxlint-tailwindcss"]` y pasa `settings.tailwindcss` a + las claves de este plugin con la tabla de settings: `cssConfigPath` pasa a ser `entryPoint`, que + es obligatorio. +3. Renombra cada `tailwindcss/` al `tailwindcss/` de la tabla de reglas — los dos + plugins usan el prefijo `tailwindcss`. O parte del [config recomendado](/es/setup) y agrega lo + que tenías encima. +4. Quita eslint-plugin-tailwindcss de tu config de ESLint y desinstálalo. Los dos plugins se llaman + `tailwindcss`, así que oxlint no carga los dos a la vez: se detiene con + `Plugin name 'tailwindcss' is already registered`. +5. Corre `oxlint`, y después `oxlint --fix` para los autofixes. + +Un config típico, antes: + +```js +// eslint.config.mjs +import tailwindcss from "eslint-plugin-tailwindcss"; +import { defineConfig } from "eslint/config"; + +export default defineConfig([ + tailwindcss.configs.recommended, + { + settings: { + tailwindcss: { + cssConfigPath: "./src/styles.css", + functions: ["cn", "cva", "classNames"], + }, + }, + rules: { + "tailwindcss/no-custom-classname": ["warn", { whitelist: ["swiper-.*", "legacy-card"] }], + "tailwindcss/no-arbitrary-value": "warn", + }, + }, +]); +``` + +Y después, con las reglas que encendía el config recomendado: + +```jsonc +// .oxlintrc.json +{ + "jsPlugins": ["oxlint-tailwindcss"], + "settings": { + "tailwindcss": { "entryPoint": "src/styles.css", "callees": ["classNames"] } + }, + "rules": { + "tailwindcss/enforce-sort-order": "warn", + "tailwindcss/enforce-canonical": "warn", + "tailwindcss/enforce-negative-arbitrary-values": "warn", + "tailwindcss/enforce-shorthand": "warn", + "tailwindcss/enforce-consistent-important-position": "warn", + "tailwindcss/no-unknown-classes": [ + "warn", + { "ignorePrefixes": ["swiper-"], "allowlist": ["legacy-card"] } + ], + "tailwindcss/no-conflicting-classes": "error", + "tailwindcss/no-unnecessary-arbitrary-value": "warn", + "tailwindcss/no-arbitrary-value": "warn" + } +} +``` + +## Qué más cambia + +- **Templates de Vue y Svelte.** Su config recomendado lintea los archivos `.vue` y `.svelte`, + templates incluidos, con los parsers de ESLint. En oxlint, los JS plugins ven solo los bloques + `