Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

- Nothing yet!
### Changed

- Remove duplicate classes ([#272](https://github.com/tailwindlabs/prettier-plugin-tailwindcss/pull/272))
- Remove extra whitespace around classes ([#272](https://github.com/tailwindlabs/prettier-plugin-tailwindcss/pull/272))

## [0.5.14] - 2024-04-15

Expand Down
49 changes: 46 additions & 3 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,10 @@ function transformGlimmer(ast, { env }) {
env,
ignoreFirst: siblings?.prev && !/^\s/.test(node.chars),
ignoreLast: siblings?.next && !/\s$/.test(node.chars),
collapseWhitespace: {
start: !siblings?.prev,
end: !siblings?.next,
},
})
},

Expand All @@ -248,6 +252,10 @@ function transformGlimmer(ast, { env }) {
node.value = sortClasses(node.value, {
env,
ignoreLast: isConcat && !/[^\S\r\n]$/.test(node.value),
collapseWhitespace: {
start: false,
end: !isConcat,
},
})
},
})
Expand Down Expand Up @@ -299,6 +307,10 @@ function transformLiquid(ast, { env }) {
env,
ignoreFirst: i > 0 && !/^\s/.test(node.value),
ignoreLast: i < attr.value.length - 1 && !/\s$/.test(node.value),
collapseWhitespace: {
start: i === 0,
end: i >= attr.value.length - 1,
},
})

changes.push({
Expand Down Expand Up @@ -411,8 +423,17 @@ function sortTemplateLiteral(node, { env }) {

quasi.value.raw = sortClasses(quasi.value.raw, {
env,
// Is not the first "item" and does not start with a space
ignoreFirst: i > 0 && !/^\s/.test(quasi.value.raw),

// Is between two expressions
// And does not end with a space
ignoreLast: i < node.expressions.length && !/\s$/.test(quasi.value.raw),

collapseWhitespace: {
start: i === 0,
end: i >= node.expressions.length,
},
})

quasi.value.cooked = same
Expand All @@ -422,6 +443,10 @@ function sortTemplateLiteral(node, { env }) {
ignoreFirst: i > 0 && !/^\s/.test(quasi.value.cooked),
ignoreLast:
i < node.expressions.length && !/\s$/.test(quasi.value.cooked),
collapseWhitespace: {
start: i === 0,
end: i >= node.expressions.length,
},
})

if (
Expand Down Expand Up @@ -566,11 +591,17 @@ function transformJavaScript(ast, { env }) {
function transformCss(ast, { env }) {
ast.walk((node) => {
if (node.type === 'css-atrule' && node.name === 'apply') {
let isImportant = /\s+(?:!important|#{(['"]*)!important\1})\s*$/.test(
node.params,
)

node.params = sortClasses(node.params, {
env,
ignoreLast: /\s+(?:!important|#{(['"]*)!important\1})\s*$/.test(
node.params,
),
ignoreLast: isImportant,
collapseWhitespace: {
start: false,
end: !isImportant,
},
})
}
})
Expand Down Expand Up @@ -690,6 +721,10 @@ function transformMelody(ast, { env, changes }) {
isConcat && _key === 'right' && !/^[^\S\r\n]/.test(node.value),
ignoreLast:
isConcat && _key === 'left' && !/[^\S\r\n]$/.test(node.value),
collapseWhitespace: {
start: !(isConcat && _key === 'right'),
end: !(isConcat && _key === 'left'),
},
})
},
})
Expand Down Expand Up @@ -775,13 +810,21 @@ function transformSvelte(ast, { env, changes }) {
env,
ignoreFirst: i > 0 && !/^\s/.test(value.raw),
ignoreLast: i < attr.value.length - 1 && !/\s$/.test(value.raw),
collapseWhitespace: {
start: i === 0,
end: i >= attr.value.length - 1,
},
})
value.data = same
? value.raw
: sortClasses(value.data, {
env,
ignoreFirst: i > 0 && !/^\s/.test(value.data),
ignoreLast: i < attr.value.length - 1 && !/\s$/.test(value.data),
collapseWhitespace: {
start: i === 0,
end: i >= attr.value.length - 1,
},
})
} else if (value.type === 'MustacheTag') {
visit(value.expression, {
Expand Down
7 changes: 7 additions & 0 deletions src/options.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,13 @@ export const options = {
description:
'List of functions and tagged templates that contain sortable Tailwind classes',
},
tailwindPreserveWhitespace: {
since: '0.6.0',
type: 'boolean',
default: [{ value: false }],
category: 'Tailwind CSS',
description: 'Preserve whitespace around Tailwind classes when sorting',
},
}

/** @typedef {import('prettier').RequiredOptions} RequiredOptions */
Expand Down
56 changes: 52 additions & 4 deletions src/sorting.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,20 +39,46 @@ function getClassOrderPolyfill(classes, { env }) {
return classNamesWithOrder
}

/**
* @param {string} classStr
* @param {object} opts
* @param {any} opts.env
* @param {boolean} [opts.ignoreFirst]
* @param {boolean} [opts.ignoreLast]
* @param {object} [opts.collapseWhitespace]
* @param {boolean} [opts.collapseWhitespace.start]
* @param {boolean} [opts.collapseWhitespace.end]
* @returns {string}
*/
export function sortClasses(
classStr,
{ env, ignoreFirst = false, ignoreLast = false },
{
env,
ignoreFirst = false,
ignoreLast = false,
collapseWhitespace = { start: true, end: true },
},
) {
if (typeof classStr !== 'string' || classStr === '') {
return classStr
}

// Ignore class attributes containing `{{`, to match Prettier behaviour:
// https://github.com/prettier/prettier/blob/main/src/language-html/embed.js#L83-L88
// https://github.com/prettier/prettier/blob/8a88cdce6d4605f206305ebb9204a0cabf96a070/src/language-html/embed/class-names.js#L9
if (classStr.includes('{{')) {
return classStr
}

if (env.options.tailwindPreserveWhitespace) {
collapseWhitespace = 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) {
return ' '
}

let result = ''
let parts = classStr.split(/([\t\r\f\n ]+)/)
let classes = parts.filter((_, i) => i % 2 === 0)
Expand All @@ -62,6 +88,10 @@ export function sortClasses(
classes.pop()
}

if (collapseWhitespace) {
whitespace = whitespace.map(() => ' ')
}

let prefix = ''
if (ignoreFirst) {
prefix = `${classes.shift() ?? ''}${whitespace.shift() ?? ''}`
Expand All @@ -72,12 +102,32 @@ export function sortClasses(
suffix = `${whitespace.pop() ?? ''}${classes.pop() ?? ''}`
}

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

whitespace.splice(index - 1, 1)

return false
})

classes = sortClassList(classes, { env })

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

if (collapseWhitespace) {
prefix = prefix.replace(/\s+$/g, ' ')
suffix = suffix.replace(/^\s+/g, ' ')

result = result
.replace(/^\s+/, collapseWhitespace.start ? '' : ' ')
.replace(/\s+$/, collapseWhitespace.end ? '' : ' ')
}

return prefix + result + suffix
}

Expand All @@ -89,8 +139,6 @@ export function sortClassList(classList, { env }) {
return classNamesWithOrder
.sort(([, a], [, z]) => {
if (a === z) return 0
// if (a === null) return options.unknownClassPosition === 'start' ? -1 : 1
// if (z === null) return options.unknownClassPosition === 'start' ? 1 : -1
if (a === null) return -1
if (z === null) return 1
return bigSign(a - z)
Expand Down
Loading