Skip to content

Commit

Permalink
Only remove duplicate Tailwind classes (#277)
Browse files Browse the repository at this point in the history
* improve remove duplicate classes

* replace indexOf by Set

* Simplify code

* Refactor

* Remove duplicates after sorting

* Only remove duplicates of known classes

* Rename vars

* Tweak comment

* Tweak var names

* wip

* Move duplicate removal to sorting routine

* Refactor

* Refactor

* Tweak comment

* Cleanup

* Update changelog

---------

Co-authored-by: Jordan Pittman <[email protected]>
  • Loading branch information
WooWan and thecrypticace committed Jun 3, 2024
1 parent 731ae22 commit 82ea71a
Show file tree
Hide file tree
Showing 4 changed files with 65 additions and 31 deletions.
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

- Nothing yet!
### Changed

- Only remove duplicate Tailwind classes ([#277](https://github.com/tailwindlabs/prettier-plugin-tailwindcss/pull/277))

## [0.6.1] - 2024-05-31

Expand Down
6 changes: 5 additions & 1 deletion src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -878,7 +878,11 @@ function transformPug(ast, { env }) {
const classes = ast.tokens
.slice(startIdx, endIdx + 1)
.map((token) => token.val)
const classList = sortClassList(classes, { env })

const { classList } = sortClassList(classes, {
env,
removeDuplicates: false,
})

for (let i = startIdx; i <= endIdx; i++) {
ast.tokens[i].val = classList[i - startIdx]
Expand Down
81 changes: 52 additions & 29 deletions src/sorting.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,19 @@ function getClassOrderPolyfill(classes, { env }) {
return classNamesWithOrder
}

function reorderClasses(classList, { env }) {
let orderedClasses = env.context.getClassOrder
? env.context.getClassOrder(classList)
: getClassOrderPolyfill(classList, { env })

return orderedClasses.sort(([, a], [, z]) => {
if (a === z) return 0
if (a === null) return -1
if (z === null) return 1
return bigSign(a - z)
})
}

/**
* @param {string} classStr
* @param {object} opts
Expand Down Expand Up @@ -75,10 +88,6 @@ export function sortClasses(
collapseWhitespace = false
}

if (env.options.tailwindPreserveDuplicates) {
removeDuplicates = false
}

// This class list is purely whitespace
// Collapse it to a single space if the option is enabled
if (/^[\t\r\f\n ]+$/.test(classStr) && collapseWhitespace) {
Expand Down Expand Up @@ -108,22 +117,16 @@ export function sortClasses(
suffix = `${whitespace.pop() ?? ''}${classes.pop() ?? ''}`
}

if (removeDuplicates) {
classes = classes.filter((cls, index, arr) => {
if (arr.indexOf(cls) === index) {
return true
}

whitespace.splice(index - 1, 1)

return false
})
}
let { classList, removedIndices } = sortClassList(classes, {
env,
removeDuplicates,
})

classes = sortClassList(classes, { env })
// Remove whitespace that appeared before a removed classes
whitespace = whitespace.filter((_, index) => !removedIndices.has(index + 1))

for (let i = 0; i < classes.length; i++) {
result += `${classes[i]}${whitespace[i] ?? ''}`
for (let i = 0; i < classList.length; i++) {
result += `${classList[i]}${whitespace[i] ?? ''}`
}

if (collapseWhitespace) {
Expand All @@ -138,17 +141,37 @@ export function sortClasses(
return prefix + result + suffix
}

export function sortClassList(classList, { env }) {
let classNamesWithOrder = env.context.getClassOrder
? env.context.getClassOrder(classList)
: getClassOrderPolyfill(classList, { env })
export function sortClassList(classList, { env, removeDuplicates }) {
// Re-order classes based on the Tailwind CSS configuration
let orderedClasses = reorderClasses(classList, { env })

return classNamesWithOrder
.sort(([, a], [, z]) => {
if (a === z) return 0
if (a === null) return -1
if (z === null) return 1
return bigSign(a - z)
// Remove duplicate Tailwind classes
if (env.options.tailwindPreserveDuplicates) {
removeDuplicates = false
}

let removedIndices = new Set()

if (removeDuplicates) {
let seenClasses = new Set()

orderedClasses = orderedClasses.filter(([cls, order], index) => {
if (seenClasses.has(cls)) {
removedIndices.add(index)
return false
}

// Only consider known classes when removing duplicates
if (order !== null) {
seenClasses.add(cls)
}

return true
})
.map(([className]) => className)
}

return {
classList: orderedClasses.map(([className]) => className),
removedIndices,
}
}
5 changes: 5 additions & 0 deletions tests/format.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ let html = [
t`<div class=""></div>`,
// Ensure duplicate classes are removed
['<div class="sm:p-0 p-0 p-0"></div>', '<div class="p-0 sm:p-0"></div>'],
// Duplicates are not removed for unknown classes
[
'<div class="idonotexist sm:p-0 p-0 idonotexist p-0 idonotexist"></div>',
'<div class="idonotexist idonotexist idonotexist p-0 sm:p-0"></div>',
],
// Ensure duplicate can be kept
[
'<div class="sm:p-0 p-0 p-0"></div>',
Expand Down

0 comments on commit 82ea71a

Please sign in to comment.