SIGN IN SIGN UP

A utility-first CSS framework for rapid UI development.

0 0 25 TypeScript

fix(canonicalize): prevent collapse cache pollution across calls (#19675)

## Summary

fixes
https://github.com/schoero/eslint-plugin-better-tailwindcss/issues/321

This PR fixes an order-sensitive canonicalization bug. This bug caused
issues when running eslint-plugin-better-tailwindcss as the order in
which files are linted in is not consistent. This caused, in some
scenarios, `canonicalizeCandidates(..., { collapse: true,
logicalToPhysical: true, rem: 16 })` to stop collapsing valid
combinations (for example `h-4 + w-4 -> size-4`) after unrelated prior
calls.

To reproduce this issue:
```
# checkout this branch
$ git checkout c/fix-canonicalizeCandidates
# Revert the fix to the current `main` branch
$ git checkout main ./packages/tailwindcss/src/canonicalize-candidates.ts
# Run the tests
$ pnpm run test
```

This should produce a failure like so:

```

 FAIL   tailwindcss  src/canonicalize-candidates.test.ts > regressions > collapse canonicalization is not affected by previous calls
AssertionError: expected [ 'underline', 'h-4', 'w-4' ] to deeply equal [ 'underline', 'size-4' ]

- Expected
+ Received

  [
    "underline",
-   "size-4",
+   "h-4",
+   "w-4",
  ]

 ❯ src/canonicalize-candidates.test.ts:1167:66
    1165|     designSystem.canonicalizeCandidates(['underline', 'mb-4'], options)
    1166| 
    1167|     expect(designSystem.canonicalizeCandidates(target, options)).toEqual(['underline', 'size-4'])
       |                                                                  ^
    1168|   })
    1169| })
```

```
# reset all changes on this branch
git reset --hard
# run the tests again (they should now pass)
pnpm run test
```

The cause of this bug is that the canonicalization caches used
`DefaultMap` in places where lookups were expected to be read-only.
`DefaultMap.get` inserts missing entried, which mutated shared cache
state during intermediate lookups and made later canonicalization
results depend on prior calls.

By replacing the use of `DefaultMap` with a plain `Map`, it avoids
inserting into the map on lookup paths. I've polyfilled
[`Map#getOrInsert`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/getOrInsert)
as it is not widely available yet, and used that where appropriate.


## Test plan

I wrote a test that fails on `main` branch, I then fixed the issue, and
validated that the test now passes.

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
C
Cameron committed
5a4a7eba3a3db4f4a834f37a3e37624fe9c4daa7
Parent: d520e1f
Committed by GitHub <noreply@github.com> on 2/18/2026, 12:16:59 PM