diff --git a/CHANGELOG.md b/CHANGELOG.md index 559bf31..6f4444d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Don't augment global Prettier `ParserOptions` and `RequiredOptions` types ([#354](https://github.com/tailwindlabs/prettier-plugin-tailwindcss/pull/354)) - Drop support for `prettier-plugin-import-sort` ([#385](https://github.com/tailwindlabs/prettier-plugin-tailwindcss/pull/385)) - Format quotes in `@source`, `@plugin`, and `@config` ([#387](https://github.com/tailwindlabs/prettier-plugin-tailwindcss/pull/387)) +- Support sorting in callable template literals ([#367](https://github.com/tailwindlabs/prettier-plugin-tailwindcss/pull/367)) +- Support sorting in function calls mixed with property accesses ([#367](https://github.com/tailwindlabs/prettier-plugin-tailwindcss/pull/367)) ## [0.6.14] - 2025-07-09 diff --git a/src/index.ts b/src/index.ts index ceda00f..933d3ea 100644 --- a/src/index.ts +++ b/src/index.ts @@ -624,24 +624,7 @@ function isSortableTemplateExpression( | import('ast-types').namedTypes.TaggedTemplateExpression, functions: Set, ): boolean { - if (node.tag.type === 'Identifier') { - return functions.has(node.tag.name) - } - - if (node.tag.type === 'MemberExpression') { - let expr = node.tag.object - - // If the tag is a MemberExpression we should traverse all MemberExpression's until we find the leading Identifier - while (expr.type === 'MemberExpression') { - expr = expr.object - } - - if (expr.type === 'Identifier') { - return functions.has(expr.name) - } - } - - return false + return isSortableExpression(node.tag, functions) } function isSortableCallExpression( @@ -650,25 +633,29 @@ function isSortableCallExpression( | import('ast-types').namedTypes.CallExpression, functions: Set, ): boolean { - if (!node.arguments?.length) { - return false - } - - if (node.callee.type === 'Identifier') { - return functions.has(node.callee.name) - } + if (!node.arguments?.length) return false - if (node.callee.type === 'MemberExpression') { - let expr = node.callee.object + return isSortableExpression(node.callee, functions) +} - // If the tag is a MemberExpression we should traverse all MemberExpression's until we find the leading Identifier - while (expr.type === 'MemberExpression') { - expr = expr.object +function isSortableExpression( + node: + | import('@babel/types').Expression + | import('@babel/types').V8IntrinsicIdentifier + | import('ast-types').namedTypes.ASTNode, + functions: Set, +): boolean { + // Traverse property accesses and function calls to find the leading ident + while (node.type === 'CallExpression' || node.type === 'MemberExpression') { + if (node.type === 'CallExpression') { + node = node.callee + } else if (node.type === 'MemberExpression') { + node = node.object } + } - if (expr.type === 'Identifier') { - return functions.has(expr.name) - } + if (node.type === 'Identifier') { + return functions.has(node.name) } return false diff --git a/tests/fixtures/custom-jsx/index.jsx b/tests/fixtures/custom-jsx/index.jsx index 2a423f7..64bae51 100644 --- a/tests/fixtures/custom-jsx/index.jsx +++ b/tests/fixtures/custom-jsx/index.jsx @@ -14,6 +14,18 @@ const k = tw.foo('sm:p-1 p-2'); const l = tw.foo.bar('sm:p-1 p-2'); const m = no.foo('sm:p-1 p-2'); const n = no.tw('sm:p-1 p-2'); +const o = tw(Foo)`sm:p-1 p-2`; +const p = tw(Foo)(Bar)`sm:p-1 p-2`; +const q = no(Foo)`sm:p-1 p-2`; +const r = no.tw(Foo)`sm:p-1 p-2`; +const s = tw(Foo)('sm:p-1 p-2'); +const t = tw(Foo)(Bar)('sm:p-1 p-2'); +const u = no(Foo)('sm:p-1 p-2'); +const v = no.tw(Foo)('sm:p-1 p-2'); +const w = tw.div(Foo)`sm:p-1 p-2`; +const x = tw(Foo).div`sm:p-1 p-2`; +const y = no.tw(Foo)`sm:p-1 p-2`; +const z = no(Foo).tw`sm:p-1 p-2`; const A = (props) =>
; const B = () => ; diff --git a/tests/fixtures/custom-jsx/output.jsx b/tests/fixtures/custom-jsx/output.jsx index 956a7aa..4147daa 100644 --- a/tests/fixtures/custom-jsx/output.jsx +++ b/tests/fixtures/custom-jsx/output.jsx @@ -14,6 +14,18 @@ const k = tw.foo("p-2 sm:p-1"); const l = tw.foo.bar("p-2 sm:p-1"); const m = no.foo("sm:p-1 p-2"); const n = no.tw("sm:p-1 p-2"); +const o = tw(Foo)`p-2 sm:p-1`; +const p = tw(Foo)(Bar)`p-2 sm:p-1`; +const q = no(Foo)`sm:p-1 p-2`; +const r = no.tw(Foo)`sm:p-1 p-2`; +const s = tw(Foo)("p-2 sm:p-1"); +const t = tw(Foo)(Bar)("p-2 sm:p-1"); +const u = no(Foo)("sm:p-1 p-2"); +const v = no.tw(Foo)("sm:p-1 p-2"); +const w = tw.div(Foo)`p-2 sm:p-1`; +const x = tw(Foo).div`p-2 sm:p-1`; +const y = no.tw(Foo)`sm:p-1 p-2`; +const z = no(Foo).tw`sm:p-1 p-2`; const A = (props) =>
; const B = () => ;