Skip to content
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- 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))
- Fix whitespace removal inside nested concat and template expressions ([#396](https://github.com/tailwindlabs/prettier-plugin-tailwindcss/pull/396))
- Fallback to Tailwind CSS v4 instead of v3 by default ([#390](https://github.com/tailwindlabs/prettier-plugin-tailwindcss/pull/390))
- Support sorting in function calls in Twig ([#358](https://github.com/tailwindlabs/prettier-plugin-tailwindcss/pull/358))

## [0.6.14] - 2025-07-09

Expand Down
27 changes: 26 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -859,7 +859,7 @@ function transformMarko(ast: any, { env }: TransformerContext) {
}

function transformTwig(ast: any, { env, changes }: TransformerContext) {
let { staticAttrs } = env.customizations
let { staticAttrs, functions } = env.customizations

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

CallExpression(node, _path, meta) {
// Traverse property accesses and function calls to find the *trailing* ident
while (
node.type === 'CallExpression' ||
node.type === 'MemberExpression'
) {
if (node.type === 'CallExpression') {
node = node.callee
} else if (node.type === 'MemberExpression') {
// TODO: This is *different* than `isSortableExpression` and that doesn't feel right
// but they're mutually exclusive implementations
//
// This is to handle foo.fnNameHere(…) where `isSortableExpression` is intentionally
// handling `fnNameHere.foo(…)`.
node = node.property
}
}

if (node.type === 'Identifier') {
if (!functions.has(node.name)) return
}

meta.sortTextNodes = true
},

StringLiteral(node, path, meta) {
if (!meta.sortTextNodes) {
return
Expand Down
17 changes: 17 additions & 0 deletions tests/plugins.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ let tests: PluginTest[] = [
plugins: ['@zackad/prettier-plugin-twig'],
options: {
twigAlwaysBreakObjects: false,
tailwindFunctions: ['addClass', 'tw'],
},
tests: {
twig: [
Expand All @@ -161,6 +162,22 @@ let tests: PluginTest[] = [
`<div class="{{ ' flex ' + ' underline ' + ' block ' }}"></div>`,
`<div class="{{ 'flex ' + ' underline' + ' block' }}"></div>`,
],

// Function call tests
t`<div {{ tw('${yes}') }}></div>`,
t`<div {{ attributes.addClass('${yes}') }}></div>`,

t`{{ tw('${yes}') }}`,
t`{{ attributes.addClass('${yes}') }}`,

t`{{ tw('${yes}').tw('${yes}').tw('${yes}') }}`,
t`{{ attributes.addClass('${yes}').addClass('${yes}').addClass('${yes}') }}`,

t`{% set className = '${no}' %} {{ attributes.addClass(className) }}`,
[
`{{ attributes.addClass("sm:p-0 " ~ variant ~ " p-0") }}`,
`{{ attributes.addClass('sm:p-0 ' ~ variant ~ ' p-0') }}`,
],
],
},
},
Expand Down