From 8012e8ae9eda9f6272b524ae9ba43d7ae7634c37 Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Mon, 10 Feb 2025 16:38:06 -0500 Subject: [PATCH 1/3] Fix comparison of numbers with different magnitudes --- packages/tailwindcss/src/utils/compare.ts | 25 +++++++++++++++-------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/packages/tailwindcss/src/utils/compare.ts b/packages/tailwindcss/src/utils/compare.ts index 18d100a55233..53b15d1748c9 100644 --- a/packages/tailwindcss/src/utils/compare.ts +++ b/packages/tailwindcss/src/utils/compare.ts @@ -14,9 +14,6 @@ export function compare(a: string, z: string) { let aCode = a.charCodeAt(i) let zCode = z.charCodeAt(i) - // Continue if the characters are the same - if (aCode === zCode) continue - // If both are numbers, compare them as numbers instead of strings. if (aCode >= ZERO && aCode <= NINE && zCode >= ZERO && zCode <= NINE) { let aStart = i @@ -35,14 +32,24 @@ export function compare(a: string, z: string) { let aNumber = a.slice(aStart, aEnd) let zNumber = z.slice(zStart, zEnd) - return ( - Number(aNumber) - Number(zNumber) || - // Fallback case if numbers are the same but the string representation - // is not. Fallback to string sorting. E.g.: `0123` vs `123` - (aNumber < zNumber ? -1 : 1) - ) + let diff = Number(aNumber) - Number(zNumber) + if (diff) return diff + + // Fallback case if numbers are the same but the string representation + // is not. Fallback to string sorting. E.g.: `0123` vs `123` + if (aNumber < zNumber) return -1 + if (aNumber > zNumber) return 1 + + // Continue with the next character otherwise short strings will appear + // after long ones when containing numbers. E.g.: + // - bg-red-500/70 + // - bg-red-500 + continue } + // Continue if the characters are the same + if (aCode === zCode) continue + // Otherwise, compare them as strings return aCode - zCode } From af41deec781d606bc83c0879386ca6ae19361729 Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Mon, 10 Feb 2025 16:38:39 -0500 Subject: [PATCH 2/3] Add tests --- .../tailwindcss/src/utils/compare.test.ts | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/packages/tailwindcss/src/utils/compare.test.ts b/packages/tailwindcss/src/utils/compare.test.ts index d4075d289048..329691fe56a3 100644 --- a/packages/tailwindcss/src/utils/compare.test.ts +++ b/packages/tailwindcss/src/utils/compare.test.ts @@ -21,6 +21,12 @@ it.each([ ['2', '1', GREATER], ['1', '10', LESS], ['10', '1', GREATER], + + // Numbers of different lengths + ['75', '700', LESS], + ['700', '75', GREATER], + ['75', '770', LESS], + ['770', '75', GREATER], ])('should compare "%s" with "%s" as "%d"', (a, b, expected) => { expect(Math.sign(compare(a, b))).toBe(expected) }) @@ -124,3 +130,39 @@ it('should sort strings with multiple numbers consistently using the `compare` f ] `) }) + +it('sort is stable', () => { + // Heap's algorithm for permutations + function* permutations(input: T[]) { + let pos = 1 + let stack = input.map(() => 0) + + yield input.slice() + + while (pos < input.length) { + if (stack[pos] < pos) { + let k = pos % 2 == 0 ? 0 : stack[pos] + ;[input[k], input[pos]] = [input[pos], input[k]] + yield input.slice() + ++stack[pos] + pos = 1 + } else { + stack[pos] = 0 + ++pos + } + } + } + + let classes = ['duration-initial', 'duration-75', 'duration-150', 'duration-700', 'duration-1000'] + + for (let permutation of permutations(classes)) { + let sorted = [...permutation].sort(compare) + expect(sorted).toEqual([ + 'duration-75', + 'duration-150', + 'duration-700', + 'duration-1000', + 'duration-initial', + ]) + } +}) From 2bf7ed83ba4a23ecd0b1f1e1c3131576aa05d730 Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Mon, 10 Feb 2025 16:42:50 -0500 Subject: [PATCH 3/3] Update changelog --- CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ab17ec9f263..c4336adcf038 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -- Nothing yet! +### Fixed + +- Fix sorting numeric utilities when they have different magnitudes ([#16414](https://github.com/tailwindlabs/tailwindcss/pull/16414)) ## [4.0.6] - 2025-02-10