Skip to content

fix(enforces-canonical-classname): keep canonical results aligned when a batch collapses two classnames - #491

Open
nimbly1 wants to merge 1 commit into
francoismassart:v4from
nimbly1:fix/issue-490-canonicalize-batch-alignment
Open

nimbly1 wants to merge 1 commit into
francoismassart:v4from
nimbly1:fix/issue-490-canonicalize-batch-alignment

Conversation

@nimbly1

@nimbly1 nimbly1 commented Oct 26, 2026

Copy link
Copy Markdown

Fixes #490.

Root cause

The canonicalize-candidates operation in tailwind-api.mjs mapped the compiler's results back onto the batch by position:

const canonical = context.canonicalizeCandidates(classNames);
return classNames.map((className, index) => canonical[index] ?? className);

canonicalizeCandidates collects its results in a Set, so when two classnames of a batch share a canonical form (hover:-mt-[0.25em] and hover:mt-[-0.25em] both resolve to hover:mt-[-0.25em]), the result is shorter than the input and every classname after the collapse is mapped onto a neighbour's canonical form. The result can also be reordered by the compiler in its regrouping path, so positional alignment is unsafe in general.

Two visible consequences:

  1. enforces-canonical-classname reports classnames that cannot be written as the suggested form — with the issue's repro, p-4 is reported as md:px-6 and --fix mangles the class list into md:px-6 grid md:px-6 md:px-6.
  2. canonicalizeCandidatesWorker (src/utils/tailwindcss-api/index.ts) caches each classname after the first batch, so the misaligned value is remembered for the rest of the lint run (p-4 → md:px-6 permanently).

Fix

Resolve one classname at a time inside the worker, which is the only mapping that makes no assumption about the shape of the compiler's output:

return classNames.map(
  (className) => context.canonicalizeCandidates([className])[0] ?? className,
);

The worker round trip stays one per node, and the compiler's per-call setup is all memoized map lookups, so the added cost is a handful of Map.get per classname; the caller's per-classname cache means each classname is resolved once per config either way.

Tests

  • canonicalize-candidates.spec.ts: a batch where two classnames share a canonical form stays positionally aligned.
  • enforces-canonical-classname.spec.ts: -mt-[0.25em] grid mt-[-0.25em] md:px-6 yields the single legitimate report and the fix preserves the already-canonical spelling instead of shifting md:px-6 onto it.

Credit for the diagnosis and the first fix goes to @MaisonnatM (see their sibling-plugin PR) — thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

enforces-canonical-classname: --fix rewrites the wrong classes when two classes share a canonical form

1 participant