Repository: ony3000/prettier-plugin-classnames (ts)

Issue:
Support TaggedTemplateExpression
## Is your feature request related to a problem? Please describe
Feature request.

## Describe the solution you'd like
Support tagged template expressions in addition to custom functions. For an example taken from https://github.com/tailwindlabs/prettier-plugin-tailwindcss#sorting-classes-in-template-literals:

```json
{
  "tailwindFunctions": ["tw"],
}
```

```jsx
const mySortedClasses = tw`bg-white p-4 dark:bg-black`
```

## Describe alternatives you've considered

## Additional context


Pull request:
Support tagged template
This PR closes #52.

## How to use

Since "tag" here is a function, `classNames` is supported by default, and other functions are supported by using the `customFunctions` option.

If you set the Prettier options like this:

```
{
  customFunctions: ['tw']
}
```

Class names written with the following syntax are subject to line wrapping:

```
const classes = tw`rounded-xl border border-zinc-400/30 bg-gray-100/50 px-4 py-4`;
```

```
const Callout = tw.div`rounded-xl border border-zinc-400/30 bg-gray-100/50 px-4 py-4`;
```

```
const Callout = tw(Container)`rounded-xl border border-zinc-400/30 bg-gray-100/50 px-4 py-4`;
```

Patch:
diff --git a/src/packages/core-parts/finder.ts b/src/packages/core-parts/finder.ts
index c28095b..3bd9816 100644
--- a/src/packages/core-parts/finder.ts
+++ b/src/packages/core-parts/finder.ts
@@ -430,6 +430,65 @@ export function findTargetClassNameNodes(
         }
         break;
       }
+      case 'TaggedTemplateExpression': {
+        nonCommentNodes.push(currentASTNode);
+
+        if (
+          (isTypeof(
+            node,
+            z.object({
+              tag: z.object({
+                type: z.literal('Identifier'),
+                name: z.string(),
+              }),
+            }),
+          ) &&
+            supportedFunctions.includes(node.tag.name)) ||
+          (isTypeof(
+            node,
+            z.object({
+              tag: z.object({
+                type: z.literal('MemberExpression'),
+                object: z.object({
+                  type: z.literal('Identifier'),
+                  name: z.string(),
+                }),
+              }),
+            }),
+          ) &&
+            supportedFunctions.includes(node.tag.object.name)) ||
+          (isTypeof(
+            node,
+            z.object({
+              tag: z.object({
+                type: z.literal('CallExpression'),
+                callee: z.object({
+                  type: z.literal('Identifier'),
+                  name: z.string(),
+                }),
+              }),
+            }),
+          ) &&
+            supportedFunctions.includes(node.tag.callee.name))
+        ) {
+          keywordStartingNodes.push(currentASTNode);
+
+          classNameNodes.forEach((classNameNode) => {
+            const [classNameRangeStart, classNameRangeEnd] = classNameNode.range;
+
+            if (
+              currentNodeRangeStart <= classNameRangeStart &&
+              classNameRangeEnd <= currentNodeRangeEnd
+            ) {
+              if (classNameNode.type === ClassNameType.UTL) {
+                // eslint-disable-next-line no-param-reassign
+                classNameNode.type = ClassNameType.TLPQ;
+              }
+            }
+          });
+        }
+        break;
+      }
       case 'Block':
       case 'Line': {
         if (


Test patch:
diff --git a/tests/v2-test/astro/variable-declaration.test.ts b/tests/v2-test/astro/variable-declaration.test.ts
index 498d2f0..92cbdd0 100644
--- a/tests/v2-test/astro/variable-declaration.test.ts
+++ b/tests/v2-test/astro/variable-declaration.test.ts
@@ -125,6 +125,74 @@ const combination = classNames(
       endingPosition: 'absolute-with-indent',
     },
   },
+  {
+    name: 'tagged template (1)',
+    input: `
+---
+const classes = tw\`lorem ipsum dolor sit amet consectetur adipiscing elit proin ex massa hendrerit eu posuere\`;
+---
+`,
+    output: `---
+const classes = tw\`lorem ipsum dolor sit amet consectetur adipiscing elit proin ex massa hendrerit
+eu posuere\`;
+---
+`,
+    options: {
+      customFunctions: ['tw'],
+      endingPosition: 'relative',
+    },
+  },
+  {
+    name: 'tagged template (2)',
+    input: `
+---
+const Bar = tw.foo\`lorem ipsum dolor sit amet consectetur adipiscing elit proin ex massa hendrerit eu posuere\`;
+---
+`,
+    output: `---
+const Bar = tw.foo\`lorem ipsum dolor sit amet consectetur adipiscing elit proin
+ex massa hendrerit eu posuere\`;
+---
+`,
+    options: {
+      customFunctions: ['tw'],
+      endingPosition: 'absolute',
+    },
+  },
+  {
+    name: 'tagged template (3)',
+    input: `
+---
+const Bar = tw(Foo)\`lorem ipsum dolor sit amet consectetur adipiscing elit proin ex massa hendrerit eu posuere\`;
+---
+`,
+    output: `---
+const Bar = tw(
+  Foo,
+)\`lorem ipsum dolor sit amet consectetur adipiscing elit proin ex massa
+hendrerit eu posuere\`;
+---
+`,
+    options: {
+      customFunctions: ['tw'],
+      endingPosition: 'absolute-with-indent',
+    },
+  },
+  {
+    name: 'tagged template (4) - short enough class name',
+    input: `
+---
+const classes = tw\`lorem ipsum dolor sit amet\`;
+---
+`,
+    output: `---
+const classes = tw\`lorem ipsum dolor sit amet\`;
+---
+`,
+    options: {
+      customFunctions: ['tw'],
+    },
+  },
 ];
 
 describe('astro/variable-declaration', () => {
diff --git a/tests/v2-test/babel/variable-declaration.test.ts b/tests/v2-test/babel/variable-declaration.test.ts
index 25ccbea..7697a1f 100644
--- a/tests/v2-test/babel/variable-declaration.test.ts
+++ b/tests/v2-test/babel/variable-declaration.test.ts
@@ -105,6 +105,58 @@ const { data } = useSWR(
     output: `const { data } = useSWR(\`\${process.env.NEXT_PUBLIC_API_URL}/cart/\${cartId}\`);
 `,
   },
+  {
+    name: 'tagged template (1)',
+    input: `
+const classes = tw\`lorem ipsum dolor sit amet consectetur adipiscing elit proin ex massa hendrerit eu posuere\`;
+`,
+    output: `const classes = tw\`lorem ipsum dolor sit amet consectetur adipiscing elit proin ex massa hendrerit
+eu posuere\`;
+`,
+    options: {
+      customFunctions: ['tw'],
+      endingPosition: 'relative',
+    },
+  },
+  {
+    name: 'tagged template (2)',
+    input: `
+const Bar = tw.foo\`lorem ipsum dolor sit amet consectetur adipiscing elit proin ex massa hendrerit eu posuere\`;
+`,
+    output: `const Bar = tw.foo\`lorem ipsum dolor sit amet consectetur adipiscing elit proin
+ex massa hendrerit eu posuere\`;
+`,
+    options: {
+      customFunctions: ['tw'],
+      endingPosition: 'absolute',
+    },
+  },
+  {
+    name: 'tagged template (3)',
+    input: `
+const Bar = tw(Foo)\`lorem ipsum dolor sit amet consectetur adipiscing elit proin ex massa hendrerit eu posuere\`;
+`,
+    output: `const Bar = tw(
+  Foo,
+)\`lorem ipsum dolor sit amet consectetur adipiscing elit proin ex massa
+hendrerit eu posuere\`;
+`,
+    options: {
+      customFunctions: ['tw'],
+      endingPosition: 'absolute-with-indent',
+    },
+  },
+  {
+    name: 'tagged template (4) - short enough class name',
+    input: `
+const classes = tw\`lorem ipsum dolor sit amet\`;
+`,
+    output: `const classes = tw\`lorem ipsum dolor sit amet\`;
+`,
+    options: {
+      customFunctions: ['tw'],
+    },
+  },
 ];
 
 describe('babel/variable-declaration', () => {
diff --git a/tests/v2-test/typescript/variable-declaration.test.ts b/tests/v2-test/typescript/variable-declaration.test.ts
index 262fb5c..8ee5da4 100644
--- a/tests/v2-test/typescript/variable-declaration.test.ts
+++ b/tests/v2-test/typescript/variable-declaration.test.ts
@@ -107,6 +107,58 @@ const { data } = useSWR<CartResponse>(
 );
 `,
   },
+  {
+    name: 'tagged template (1)',
+    input: `
+const classes = tw\`lorem ipsum dolor sit amet consectetur adipiscing elit proin ex massa hendrerit eu posuere\`;
+`,
+    output: `const classes = tw\`lorem ipsum dolor sit amet consectetur adipiscing elit proin ex massa hendrerit
+eu posuere\`;
+`,
+    options: {
+      customFunctions: ['tw'],
+      endingPosition: 'relative',
+    },
+  },
+  {
+    name: 'tagged template (2)',
+    input: `
+const Bar = tw.foo\`lorem ipsum dolor sit amet consectetur adipiscing elit proin ex massa hendrerit eu posuere\`;
+`,
+    output: `const Bar = tw.foo\`lorem ipsum dolor sit amet consectetur adipiscing elit proin
+ex massa hendrerit eu posuere\`;
+`,
+    options: {
+      customFunctions: ['tw'],
+      endingPosition: 'absolute',
+    },
+  },
+  {
+    name: 'tagged template (3)',
+    input: `
+const Bar = tw(Foo)\`lorem ipsum dolor sit amet consectetur adipiscing elit proin ex massa hendrerit eu posuere\`;
+`,
+    output: `const Bar = tw(
+  Foo,
+)\`lorem ipsum dolor sit amet consectetur adipiscing elit proin ex massa
+hendrerit eu posuere\`;
+`,
+    options: {
+      customFunctions: ['tw'],
+      endingPosition: 'absolute-with-indent',
+    },
+  },
+  {
+    name: 'tagged template (4) - short enough class name',
+    input: `
+const classes = tw\`lorem ipsum dolor sit amet\`;
+`,
+    output: `const classes = tw\`lorem ipsum dolor sit amet\`;
+`,
+    options: {
+      customFunctions: ['tw'],
+    },
+  },
 ];
 
 describe('typescript/variable-declaration', () => {
diff --git a/tests/v2-test/vue/variable-declaration.test.ts b/tests/v2-test/vue/variable-declaration.test.ts
index 742b1cf..a3d0595 100644
--- a/tests/v2-test/vue/variable-declaration.test.ts
+++ b/tests/v2-test/vue/variable-declaration.test.ts
@@ -69,6 +69,74 @@ const combination = classNames(
 </template>
 `,
   },
+  {
+    name: 'tagged template (1)',
+    input: `
+<script setup lang="ts">
+const classes = tw\`lorem ipsum dolor sit amet consectetur adipiscing elit proin ex massa hendrerit eu posuere\`;
+</script>
+`,
+    output: `<script setup lang="ts">
+const classes = tw\`lorem ipsum dolor sit amet consectetur adipiscing elit proin ex massa hendrerit
+eu posuere\`;
+</script>
+`,
+    options: {
+      customFunctions: ['tw'],
+      endingPosition: 'relative',
+    },
+  },
+  {
+    name: 'tagged template (2)',
+    input: `
+<script setup lang="ts">
+const Bar = tw.foo\`lorem ipsum dolor sit amet consectetur adipiscing elit proin ex massa hendrerit eu posuere\`;
+</script>
+`,
+    output: `<script setup lang="ts">
+const Bar = tw.foo\`lorem ipsum dolor sit amet consectetur adipiscing elit proin
+ex massa hendrerit eu posuere\`;
+</script>
+`,
+    options: {
+      customFunctions: ['tw'],
+      endingPosition: 'absolute',
+    },
+  },
+  {
+    name: 'tagged template (3)',
+    input: `
+<script setup lang="ts">
+const Bar = tw(Foo)\`lorem ipsum dolor sit amet consectetur adipiscing elit proin ex massa hendrerit eu posuere\`;
+</script>
+`,
+    output: `<script setup lang="ts">
+const Bar = tw(
+  Foo,
+)\`lorem ipsum dolor sit amet consectetur adipiscing elit proin ex massa
+hendrerit eu posuere\`;
+</script>
+`,
+    options: {
+      customFunctions: ['tw'],
+      endingPosition: 'absolute-with-indent',
+    },
+  },
+  {
+    name: 'tagged template (4) - short enough class name',
+    input: `
+<script setup lang="ts">
+const classes = tw\`lorem ipsum dolor sit amet\`;
+</script>
+`,
+    output: `<script setup lang="ts">
+const classes = tw\`lorem ipsum dolor sit amet\`;
+</script>
+`,
+    options: {
+      customFunctions: ['tw'],
+    },
+  },
 ];
 
 describe('vue/variable-declaration', () => {
diff --git a/tests/v3-test/astro/variable-declaration.test.ts b/tests/v3-test/astro/variable-declaration.test.ts
index c080963..14b2a24 100644
--- a/tests/v3-test/astro/variable-declaration.test.ts
+++ b/tests/v3-test/astro/variable-declaration.test.ts
@@ -125,6 +125,74 @@ const combination = classNames(
       endingPosition: 'absolute-with-indent',
     },
   },
+  {
+    name: 'tagged template (1)',
+    input: `
+---
+const classes = tw\`lorem ipsum dolor sit amet consectetur adipiscing elit proin ex massa hendrerit eu posuere\`;
+---
+`,
+    output: `---
+const classes = tw\`lorem ipsum dolor sit amet consectetur adipiscing elit proin ex massa hendrerit
+eu posuere\`;
+---
+`,
+    options: {
+      customFunctions: ['tw'],
+      endingPosition: 'relative',
+    },
+  },
+  {
+    name: 'tagged template (2)',
+    input: `
+---
+const Bar = tw.foo\`lorem ipsum dolor sit amet consectetur adipiscing elit proin ex massa hendrerit eu posuere\`;
+---
+`,
+    output: `---
+const Bar = tw.foo\`lorem ipsum dolor sit amet consectetur adipiscing elit proin
+ex massa hendrerit eu posuere\`;
+---
+`,
+    options: {
+      customFunctions: ['tw'],
+      endingPosition: 'absolute',
+    },
+  },
+  {
+    name: 'tagged template (3)',
+    input: `
+---
+const Bar = tw(Foo)\`lorem ipsum dolor sit amet consectetur adipiscing elit proin ex massa hendrerit eu posuere\`;
+---
+`,
+    output: `---
+const Bar = tw(
+  Foo,
+)\`lorem ipsum dolor sit amet consectetur adipiscing elit proin ex massa
+hendrerit eu posuere\`;
+---
+`,
+    options: {
+      customFunctions: ['tw'],
+      endingPosition: 'absolute-with-indent',
+    },
+  },
+  {
+    name: 'tagged template (4) - short enough class name',
+    input: `
+---
+const classes = tw\`lorem ipsum dolor sit amet\`;
+---
+`,
+    output: `---
+const classes = tw\`lorem ipsum dolor sit amet\`;
+---
+`,
+    options: {
+      customFunctions: ['tw'],
+    },
+  },
 ];
 
 describe('astro/variable-declaration', () => {
diff --git a/tests/v3-test/babel/variable-declaration.test.ts b/tests/v3-test/babel/variable-declaration.test.ts
index dc86ad8..e44f2dc 100644
--- a/tests/v3-test/babel/variable-declaration.test.ts
+++ b/tests/v3-test/babel/variable-declaration.test.ts
@@ -105,6 +105,58 @@ const { data } = useSWR(
     output: `const { data } = useSWR(\`\${process.env.NEXT_PUBLIC_API_URL}/cart/\${cartId}\`);
 `,
   },
+  {
+    name: 'tagged template (1)',
+    input: `
+const classes = tw\`lorem ipsum dolor sit amet consectetur adipiscing elit proin ex massa hendrerit eu posuere\`;
+`,
+    output: `const classes = tw\`lorem ipsum dolor sit amet consectetur adipiscing elit proin ex massa hendrerit
+eu posuere\`;
+`,
+    options: {
+      customFunctions: ['tw'],
+      endingPosition: 'relative',
+    },
+  },
+  {
+    name: 'tagged template (2)',
+    input: `
+const Bar = tw.foo\`lorem ipsum dolor sit amet consectetur adipiscing elit proin ex massa hendrerit eu posuere\`;
+`,
+    output: `const Bar = tw.foo\`lorem ipsum dolor sit amet consectetur adipiscing elit proin
+ex massa hendrerit eu posuere\`;
+`,
+    options: {
+      customFunctions: ['tw'],
+      endingPosition: 'absolute',
+    },
+  },
+  {
+    name: 'tagged template (3)',
+    input: `
+const Bar = tw(Foo)\`lorem ipsum dolor sit amet consectetur adipiscing elit proin ex massa hendrerit eu posuere\`;
+`,
+    output: `const Bar = tw(
+  Foo,
+)\`lorem ipsum dolor sit amet consectetur adipiscing elit proin ex massa
+hendrerit eu posuere\`;
+`,
+    options: {
+      customFunctions: ['tw'],
+      endingPosition: 'absolute-with-indent',
+    },
+  },
+  {
+    name: 'tagged template (4) - short enough class name',
+    input: `
+const classes = tw\`lorem ipsum dolor sit amet\`;
+`,
+    output: `const classes = tw\`lorem ipsum dolor sit amet\`;
+`,
+    options: {
+      customFunctions: ['tw'],
+    },
+  },
 ];
 
 describe('babel/variable-declaration', () => {
diff --git a/tests/v3-test/typescript/variable-declaration.test.ts b/tests/v3-test/typescript/variable-declaration.test.ts
index b2ffbfd..7b807c1 100644
--- a/tests/v3-test/typescript/variable-declaration.test.ts
+++ b/tests/v3-test/typescript/variable-declaration.test.ts
@@ -107,6 +107,58 @@ const { data } = useSWR<CartResponse>(
 );
 `,
   },
+  {
+    name: 'tagged template (1)',
+    input: `
+const classes = tw\`lorem ipsum dolor sit amet consectetur adipiscing elit proin ex massa hendrerit eu posuere\`;
+`,
+    output: `const classes = tw\`lorem ipsum dolor sit amet consectetur adipiscing elit proin ex massa hendrerit
+eu posuere\`;
+`,
+    options: {
+      customFunctions: ['tw'],
+      endingPosition: 'relative',
+    },
+  },
+  {
+    name: 'tagged template (2)',
+    input: `
+const Bar = tw.foo\`lorem ipsum dolor sit amet consectetur adipiscing elit proin ex massa hendrerit eu posuere\`;
+`,
+    output: `const Bar = tw.foo\`lorem ipsum dolor sit amet consectetur adipiscing elit proin
+ex massa hendrerit eu posuere\`;
+`,
+    options: {
+      customFunctions: ['tw'],
+      endingPosition: 'absolute',
+    },
+  },
+  {
+    name: 'tagged template (3)',
+    input: `
+const Bar = tw(Foo)\`lorem ipsum dolor sit amet consectetur adipiscing elit proin ex massa hendrerit eu posuere\`;
+`,
+    output: `const Bar = tw(
+  Foo,
+)\`lorem ipsum dolor sit amet consectetur adipiscing elit proin ex massa
+hendrerit eu posuere\`;
+`,
+    options: {
+      customFunctions: ['tw'],
+      endingPosition: 'absolute-with-indent',
+    },
+  },
+  {
+    name: 'tagged template (4) - short enough class name',
+    input: `
+const classes = tw\`lorem ipsum dolor sit amet\`;
+`,
+    output: `const classes = tw\`lorem ipsum dolor sit amet\`;
+`,
+    options: {
+      customFunctions: ['tw'],
+    },
+  },
 ];
 
 describe('typescript/variable-declaration', () => {
diff --git a/tests/v3-test/vue/variable-declaration.test.ts b/tests/v3-test/vue/variable-declaration.test.ts
index 8dcc2a6..c9dad5e 100644
--- a/tests/v3-test/vue/variable-declaration.test.ts
+++ b/tests/v3-test/vue/variable-declaration.test.ts
@@ -69,6 +69,74 @@ const combination = classNames(
 </template>
 `,
   },
+  {
+    name: 'tagged template (1)',
+    input: `
+<script setup lang="ts">
+const classes = tw\`lorem ipsum dolor sit amet consectetur adipiscing elit proin ex massa hendrerit eu posuere\`;
+</script>
+`,
+    output: `<script setup lang="ts">
+const classes = tw\`lorem ipsum dolor sit amet consectetur adipiscing elit proin ex massa hendrerit
+eu posuere\`;
+</script>
+`,
+    options: {
+      customFunctions: ['tw'],
+      endingPosition: 'relative',
+    },
+  },
+  {
+    name: 'tagged template (2)',
+    input: `
+<script setup lang="ts">
+const Bar = tw.foo\`lorem ipsum dolor sit amet consectetur adipiscing elit proin ex massa hendrerit eu posuere\`;
+</script>
+`,
+    output: `<script setup lang="ts">
+const Bar = tw.foo\`lorem ipsum dolor sit amet consectetur adipiscing elit proin
+ex massa hendrerit eu posuere\`;
+</script>
+`,
+    options: {
+      customFunctions: ['tw'],
+      endingPosition: 'absolute',
+    },
+  },
+  {
+    name: 'tagged template (3)',
+    input: `
+<script setup lang="ts">
+const Bar = tw(Foo)\`lorem ipsum dolor sit amet consectetur adipiscing elit proin ex massa hendrerit eu posuere\`;
+</script>
+`,
+    output: `<script setup lang="ts">
+const Bar = tw(
+  Foo,
+)\`lorem ipsum dolor sit amet consectetur adipiscing elit proin ex massa
+hendrerit eu posuere\`;
+</script>
+`,
+    options: {
+      customFunctions: ['tw'],
+      endingPosition: 'absolute-with-indent',
+    },
+  },
+  {
+    name: 'tagged template (4) - short enough class name',
+    input: `
+<script setup lang="ts">
+const classes = tw\`lorem ipsum dolor sit amet\`;
+</script>
+`,
+    output: `<script setup lang="ts">
+const classes = tw\`lorem ipsum dolor sit amet\`;
+</script>
+`,
+    options: {
+      customFunctions: ['tw'],
+    },
+  },
 ];
 
 describe('vue/variable-declaration', () => {