Skip to content

Only remove duplicate Tailwind classes #277

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 16 commits into from
Jun 3, 2024
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
Loading