mirror of
https://github.com/aphrody-labs/tailwindcss.git
synced 2026-10-14 21:59:02 +00:00
Fix the intersection polyfill in canonicalizeCandidates (#20562)
## Summary The `intersection` helper in `canonicalize-candidates.ts` falls back to a polyfill when `Set.prototype.intersection` is missing (Node.js < 22). The polyfill copied the first set and never removed any item, so it returned the first set instead of the intersection. The collapse step gave different results on those Node.js versions. The polyfill now keeps only the items of the first set that are also in the second set. The function is exported so the polyfill path can be tested directly. ## Test plan Added unit tests for the polyfill path (the native method is stubbed out). ``` cd packages/tailwindcss pnpm vitest run src/canonicalize-candidates.test.ts ```
This commit is contained in:
1 parent
c56200d0c4
commit
ffb47984e7
3 files changed
+49
-5
No files matched your search
@@ -38,6 +38,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Improve style invalidation performance of `group-*` and `peer-*` variants ([#20513](https://github.com/tailwindlabs/tailwindcss/pull/20513))
|
||||
- Ensure cached compilers don't keep `loadModule` and `loadStylesheet` callbacks alive after compilation ([#20531](https://github.com/tailwindlabs/tailwindcss/pull/20531))
|
||||
- Canonicalization: keep the `&` when moving a pseudo-element into `*` or `**` (e.g. `[&_::before]:flex` → `**:[&::before]:flex`) ([#20550](https://github.com/tailwindlabs/tailwindcss/pull/20550))
|
||||
- Fix `Set.prototype.intersection` polyfill in `canonicalizeCandidates(…)` on Node.js < 22, which returned the first set instead of the intersection ([#20562](https://github.com/tailwindlabs/tailwindcss/pull/20562))
|
||||
|
||||
## [4.3.3] - 2026-07-16
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import { describe, expect, test } from 'vitest'
|
||||
import { __unstable__loadDesignSystem } from '.'
|
||||
import { intersection } from './canonicalize-candidates'
|
||||
import { cartesian } from './cartesian'
|
||||
import type { CanonicalizeOptions } from './intellisense'
|
||||
import plugin from './plugin'
|
||||
@@ -1666,3 +1667,45 @@ describe('regressions', () => {
|
||||
).toEqual(['text-foreground/60', 'text-default-soft-hover'])
|
||||
})
|
||||
})
|
||||
|
||||
describe('intersection', () => {
|
||||
// Shadow `Set.prototype.intersection` so that the polyfill is used, even on
|
||||
// Node.js versions that have a native implementation.
|
||||
function withoutNative<T>(set: Set<T>): Set<T> {
|
||||
return Object.assign(set, { intersection: undefined })
|
||||
}
|
||||
|
||||
test('polyfill keeps only the items that are in both sets', () => {
|
||||
let a = withoutNative(new Set([1, 2, 3, 4]))
|
||||
let b = new Set([3, 4, 5, 6])
|
||||
|
||||
expect(intersection(a, b)).toEqual(new Set([3, 4]))
|
||||
expect(intersection(withoutNative(new Set(b)), a)).toEqual(new Set([3, 4]))
|
||||
})
|
||||
|
||||
test('polyfill returns an empty set when the sets are disjoint', () => {
|
||||
expect(intersection(withoutNative(new Set([1, 2])), new Set([3, 4]))).toEqual(new Set())
|
||||
})
|
||||
|
||||
test('polyfill returns an empty set when one of the sets is empty', () => {
|
||||
expect(intersection(withoutNative(new Set<number>()), new Set([1]))).toEqual(new Set())
|
||||
expect(intersection(withoutNative(new Set([1])), new Set<number>())).toEqual(new Set())
|
||||
})
|
||||
|
||||
test('polyfill does not mutate the input sets', () => {
|
||||
let a = withoutNative(new Set([1, 2, 3]))
|
||||
let b = new Set([2, 3, 4])
|
||||
|
||||
intersection(a, b)
|
||||
|
||||
expect(a).toEqual(new Set([1, 2, 3]))
|
||||
expect(b).toEqual(new Set([2, 3, 4]))
|
||||
})
|
||||
|
||||
test('native and polyfill implementations agree', () => {
|
||||
let a = new Set(['a', 'b', 'c', 'd'])
|
||||
let b = new Set(['b', 'd', 'e'])
|
||||
|
||||
expect(intersection(withoutNative(new Set(a)), b)).toEqual(intersection(a, b))
|
||||
})
|
||||
})
|
||||
@@ -3055,17 +3055,17 @@ function* combinations<T>(arr: T[]): Generator<T[]> {
|
||||
}
|
||||
}
|
||||
|
||||
function intersection<T>(a: Set<T>, b: Set<T>): Set<T> {
|
||||
export function intersection<T>(a: Set<T>, b: Set<T>): Set<T> {
|
||||
// @ts-expect-error Set.prototype.intersection is only available in Node.js v22+
|
||||
if (typeof a.intersection === 'function') return a.intersection(b)
|
||||
|
||||
// Polyfill for environments that do not support Set.prototype.intersection yet
|
||||
if (a.size === 0 || b.size === 0) return new Set<T>()
|
||||
|
||||
let result = new Set<T>(a)
|
||||
for (let item of b) {
|
||||
if (!result.has(item)) {
|
||||
result.delete(item)
|
||||
let result = new Set<T>()
|
||||
for (let item of a) {
|
||||
if (b.has(item)) {
|
||||
result.add(item)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user