Español
enforce-canonical
Regla de oxlint que reescribe las clases de Tailwind CSS a la forma canónica que calcula el propio Tailwind, como -m-0 → m-0, con autofix.
De un vistazo
| Autofix | Sugerencias en el editor | Design system | Opciones |
|---|---|---|---|
| Sí | Sí | Obligatorio (entryPoint) | entryPoint, reportNonEquivalent |
Qué hace esta regla
Le pregunta al design system cuál es la forma canónica de cada utility en tu código y reescribe las que no lo son. "Canónico" es lo que devuelve canonicalizeCandidates() de @tailwindcss/node — la misma fuente de verdad que usan prettier-plugin-tailwindcss, oxfmt y las herramientas oficiales de Tailwind. Ejemplos: -m-0 → m-0 (no necesitas el negativo para un cero), start-2 → inset-s-2, flex-grow-[2] → grow-2 (un valor arbitrario cuya forma nombrada emite el mismo CSS), text-[var(--color-text)]/90 → text-(--color-text)/90. El auto-fix corrige el primer hit, las sugerencias cubren el resto en el mismo string. Los renombres de v3 (bg-gradient-to-r → bg-linear-to-r, break-words → wrap-break-word) son propiedad de no-deprecated-classes, no de esta regla — ver Interacciones.
Las clases con nombre resuelven contra un canonicalMap precomputado en memoria (sub-microsegundo). Los valores arbitrarios (p-[2px], bg-(--c)) y las variantes arbitrarias (data-[open]:, min-[40rem]:) pasan por el worker canonicalize-service porque necesitan una consulta viva al DS; los resultados se cachean a nivel proceso por (entryPoint, rem, class). El worker preserva la posición del ! (prefix vs suffix vs ninguno).
DS-dependiente — requiere settings.tailwindcss.entryPoint. Si el design system no puede cargar, la regla emite un único diagnóstico fatal designSystemUnavailable por archivo en vez de pasar en silencio.
Opciones
reportNonEquivalent
boolean, default false.
Algunas formas canónicas no son el mismo CSS en tu proyecto, y la regla deja esas clases como están. Con esta opción activa, además te avisa de dos tipos de ellas, nunca con autofix:
- Una variante que tu CSS define distinto del Tailwind de stock. shadcn/ui, por ejemplo, trae
data-disabled:como una variante propia construida sobre:where([data-disabled="true"]), …, así quedata-[disabled]:opacity-50— que el Tailwind de stock reescribe adata-disabled:opacity-50— selecciona otros elementos aquí. El reporte nombra las dos variantes, y cambiar es decisión tuya. - Una forma canónica que además fija una variable que leen otras utilidades. Tailwind reescribe
[line-height:var(--x)]aleading-(--x), peroleading-(--x)también fija--tw-leading, que cada tamañotext-*lee para su interlineado, así que junto a uno las dos pueden verse distinto. Lo mismo pasa con[border-style:dashed]→border-dashed(--tw-border-style, que leeborder-2),[transition-duration:300ms]→duration-300(--tw-duration, que leetransition) y las formasfont-*,tracking-*,ease-*,outline-*ycontent-none. El reporte nombra la variable y trae una sugerencia que tu editor puede aplicar.
Una diferencia que también tiene el Tailwind de stock (has-[[data-slot=x]]: vs has-data-[slot=x]: emiten selectores distintos en cualquier proyecto) no se reporta, y tampoco un valor que lee el theme (p-[2px] vs p-0.5, mira prefer-scale-token). En apps/v4 de shadcn/ui la opción agrega 23 reportes, todos data-[disabled]:, y ninguno del segundo tipo.
jsonc
{ "tailwindcss/enforce-canonical": ["warn", { "reportNonEquivalent": true }] }entryPoint
string, opcional. Override por regla de settings.tailwindcss.entryPoint. Cuando puedas, configura el entry point en settings.tailwindcss.entryPoint para todo el proyecto.
Ejemplos
✗ Incorrecto
tsx
// El negativo-de-cero es simplemente cero
<div className="-m-0 -mt-0" />
// → <div className="m-0 mt-0" />
// Shorthand de inset lógico → canónico inset-s-* / inset-e-*
<div className="start-2 end-4" />
// → <div className="inset-s-2 inset-e-4" />
// Un valor arbitrario cuya forma nombrada emite el mismo CSS
<div className="flex-grow-[2]" />
// → <div className="grow-2" />
// Variants e important se preservan
<div className="hover:!flex-grow-[2]" />
// → <div className="hover:!grow-2" />
// Modificadores de opacidad y variantes arbitrarias, con o sin otras variantes
<div className="dark:bg-white/[.08] data-[open]:flex min-[40rem]:flex" />
// → <div className="dark:bg-white/8 data-open:flex sm:flex" />
// options: { "reportNonEquivalent": true }
// No es el mismo CSS: leading-(--x) también fija --tw-leading, que leen los tamaños text-*.
// Se reporta con una sugerencia, nunca se arregla solo
<div className="[line-height:var(--x)]" />
// → <div className="leading-(--x)" />✓ Correcto
tsx
<div className="m-0 mt-0" />
<div className="inset-s-2 inset-e-4" />
<div className="grow-2" />
<div className="hover:!grow-2" />
<div className="dark:bg-white/8 data-open:flex sm:flex" />
<div className="leading-(--x)" />Interacciones con otras reglas
no-unnecessary-arbitrary-value: complementaria, no hay doble-fire. Las dos actúan solo cuando el valor arbitrario y su reemplazo nombrado emiten CSS idéntico; se reparten por forma.no-unnecessary-arbitrary-valuees dueña de los casos donde el arbitrario mapea directo a una única utility con nombre (e.g.h-[auto]→h-auto). Un valor que solo es numéricamente igual a un paso de la escala (p-[2px]→p-0.5, cuyo texto CSS difiere) no es asunto de ninguna de las dos — eso esprefer-scale-token, solo-reporte.prefer-theme-tokens: el tercer partner del trío arbitrario→nombrado. Atrapa referencias a variables CSS comoborder-(--border)→border-borderdonde nienforce-canonical(el CSS difiere) nino-unnecessary-arbitrary-value(no hay equivalente bracket compartido) se dispararían.no-deprecated-classes: es la dueña de los renombres de v3, y esta regla los omite. Tailwind también los canonicaliza, así quebg-gradient-to-r→bg-linear-to-rantes se reportaba dos veces con el mismo fix; el mensaje de la otra regla ("deprecada en v4") es el más accionable de los dos. Aquí queda todo lo que es actual-pero-no-canónico:-m-0→m-0,start-2→inset-s-2, y las formas con valor arbitrario comoflex-grow-[2]→grow-2(la lista de renombres tiene grafías, no valores). Mantén ambas activas — con solo esta los renombres quedan sin reportar.prefer-scale-token: la mitad solo-reporte de lo que esta regla cedió en #78. Una reescritura cuyo CSS emitido difiere textualmente (p-[10px]→p-2.5, donde el token llega porvar(--spacing)) no se autofixea aquí y no lo hará nunca; esa regla lo reporta con una sugerencia. Las dos no pueden disparar juntas: esta solo reescribe pares byte-idénticos, que es exactamente lo que la otra salta.enforce-consistent-important-position: esta regla preserva la posición del!que escribiste (prefix vs suffix vs ninguno).enforce-consistent-important-positiones la única fuente de verdad para imponer una posición en particular.
Cuándo desactivarla
- No quieres ninguna reescritura de clases — por ejemplo, en un archivo legacy que estás manteniendo literal para diffear.
- Lint runs sensibles a la performance donde el costo de inicializar el worker molesta. El cache hace que las corridas siguientes sean baratas, pero el primer hit paga el startup de
@tailwindcss/node. La mayoría de proyectos no lo van a notar. - No migraste a Tailwind v4 todavía: las formas canónicas producidas aquí asumen semántica v4.