Skip to content

Commit a195f71

Browse files
Allow sorting classes inside function calls in Twig templates (#358)
Co-authored-by: Jordan Pittman <[email protected]>
1 parent 5916b34 commit a195f71

File tree

3 files changed

+44
-1
lines changed

3 files changed

+44
-1
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1717
- Improved monorepo support by loading v3 configs relative to the input file instead of prettier config file ([#386](https://github.com/tailwindlabs/prettier-plugin-tailwindcss/pull/386))
1818
- Fix whitespace removal inside nested concat and template expressions ([#396](https://github.com/tailwindlabs/prettier-plugin-tailwindcss/pull/396))
1919
- Fallback to Tailwind CSS v4 instead of v3 by default ([#390](https://github.com/tailwindlabs/prettier-plugin-tailwindcss/pull/390))
20+
- Support sorting in function calls in Twig ([#358](https://github.com/tailwindlabs/prettier-plugin-tailwindcss/pull/358))
2021

2122
## [0.6.14] - 2025-07-09
2223

src/index.ts

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -859,7 +859,7 @@ function transformMarko(ast: any, { env }: TransformerContext) {
859859
}
860860

861861
function transformTwig(ast: any, { env, changes }: TransformerContext) {
862-
let { staticAttrs } = env.customizations
862+
let { staticAttrs, functions } = env.customizations
863863

864864
for (let child of ast.expressions ?? []) {
865865
transformTwig(child, { env, changes })
@@ -872,6 +872,31 @@ function transformTwig(ast: any, { env, changes }: TransformerContext) {
872872
meta.sortTextNodes = true
873873
},
874874

875+
CallExpression(node, _path, meta) {
876+
// Traverse property accesses and function calls to find the *trailing* ident
877+
while (
878+
node.type === 'CallExpression' ||
879+
node.type === 'MemberExpression'
880+
) {
881+
if (node.type === 'CallExpression') {
882+
node = node.callee
883+
} else if (node.type === 'MemberExpression') {
884+
// TODO: This is *different* than `isSortableExpression` and that doesn't feel right
885+
// but they're mutually exclusive implementations
886+
//
887+
// This is to handle foo.fnNameHere(…) where `isSortableExpression` is intentionally
888+
// handling `fnNameHere.foo(…)`.
889+
node = node.property
890+
}
891+
}
892+
893+
if (node.type === 'Identifier') {
894+
if (!functions.has(node.name)) return
895+
}
896+
897+
meta.sortTextNodes = true
898+
},
899+
875900
StringLiteral(node, path, meta) {
876901
if (!meta.sortTextNodes) {
877902
return

tests/plugins.test.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ let tests: PluginTest[] = [
136136
plugins: ['@zackad/prettier-plugin-twig'],
137137
options: {
138138
twigAlwaysBreakObjects: false,
139+
tailwindFunctions: ['addClass', 'tw'],
139140
},
140141
tests: {
141142
twig: [
@@ -161,6 +162,22 @@ let tests: PluginTest[] = [
161162
`<div class="{{ ' flex ' + ' underline ' + ' block ' }}"></div>`,
162163
`<div class="{{ 'flex ' + ' underline' + ' block' }}"></div>`,
163164
],
165+
166+
// Function call tests
167+
t`<div {{ tw('${yes}') }}></div>`,
168+
t`<div {{ attributes.addClass('${yes}') }}></div>`,
169+
170+
t`{{ tw('${yes}') }}`,
171+
t`{{ attributes.addClass('${yes}') }}`,
172+
173+
t`{{ tw('${yes}').tw('${yes}').tw('${yes}') }}`,
174+
t`{{ attributes.addClass('${yes}').addClass('${yes}').addClass('${yes}') }}`,
175+
176+
t`{% set className = '${no}' %} {{ attributes.addClass(className) }}`,
177+
[
178+
`{{ attributes.addClass("sm:p-0 " ~ variant ~ " p-0") }}`,
179+
`{{ attributes.addClass('sm:p-0 ' ~ variant ~ ' p-0') }}`,
180+
],
164181
],
165182
},
166183
},

0 commit comments

Comments
 (0)