Skip to content

Commit

Permalink
Sort class lists deterministically for Prettier plugin (#10672)
Browse files Browse the repository at this point in the history
* Ensure class sorting is deterministic for Prettier

* Update changelog
  • Loading branch information
thecrypticace authored Feb 23, 2023
1 parent 261a8b4 commit d2a95a0
Show file tree
Hide file tree
Showing 3 changed files with 39 additions and 4 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed

- Disallow multiple selectors in arbitrary variants ([#10655](https://github.com/tailwindlabs/tailwindcss/pull/10655))
- Sort class lists deterministically for Prettier plugin ([#10672](https://github.com/tailwindlabs/tailwindcss/pull/10672))

### Changed

Expand Down
11 changes: 9 additions & 2 deletions src/lib/setupContextUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -931,12 +931,19 @@ function registerPlugins(plugins, context) {
prefix(context, 'peer'),
]
context.getClassOrder = function getClassOrder(classes) {
// Sort classes so they're ordered in a deterministic manner
let sorted = [...classes].sort((a, z) => {
if (a === z) return 0
if (a < z) return -1
return 1
})

// Non-util classes won't be generated, so we default them to null
let sortedClassNames = new Map(classes.map((className) => [className, null]))
let sortedClassNames = new Map(sorted.map((className) => [className, null]))

// Sort all classes in order
// Non-tailwind classes won't be generated and will be left as `null`
let rules = generateRules(new Set(classes), context)
let rules = generateRules(new Set(sorted), context)
rules = context.offsets.sort(rules)

let idx = BigInt(parasiteUtilities.length)
Expand Down
31 changes: 29 additions & 2 deletions tests/getSortOrder.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ crosscheck(() => {
['px-3 focus:hover:p-3 hover:p-1 py-3', 'px-3 py-3 hover:p-1 focus:hover:p-3'],

// Utitlies with important
['px-3 !py-4', 'px-3 !py-4'],
['px-3 !py-4', '!py-4 px-3'],
['!py-4 px-3', '!py-4 px-3'],

// Components with variants
Expand Down Expand Up @@ -89,7 +89,7 @@ crosscheck(() => {
],

// Utitlies with important
['tw-px-3 !tw-py-4', 'tw-px-3 !tw-py-4'],
['tw-px-3 !tw-py-4', '!tw-py-4 tw-px-3'],
['!tw-py-4 tw-px-3', '!tw-py-4 tw-px-3'],

// Components with variants
Expand All @@ -115,4 +115,31 @@ crosscheck(() => {
expect(defaultSort(context.getClassOrder(input.split(' ')))).toEqual(output)
}
)

it('sorts classes deterministically across multiple class lists', () => {
let classes = [
[
'a-class px-3 p-1 b-class py-3 bg-red-500 bg-blue-500',
'a-class b-class bg-blue-500 bg-red-500 p-1 px-3 py-3',
],
[
'px-3 b-class p-1 py-3 bg-blue-500 a-class bg-red-500',
'b-class a-class bg-blue-500 bg-red-500 p-1 px-3 py-3',
],
]

let config = {}

// Same context, different class lists
let context = createContext(resolveConfig(config))
for (const [input, output] of classes) {
expect(defaultSort(context.getClassOrder(input.split(' ')))).toEqual(output)
}

// Different context, different class lists
for (const [input, output] of classes) {
context = createContext(resolveConfig(config))
expect(defaultSort(context.getClassOrder(input.split(' ')))).toEqual(output)
}
})
})

0 comments on commit d2a95a0

Please sign in to comment.