Repository: prettier/plugin-pug (ts)

Issue:
Bug: Cannot format expressions in Vue using TS types
## Info

| Tool      | Version                 |
| --------- | ----------------------- |
| Plugin    | v1.20.0                  |
| Prettier  | v2.6.1                  |
| Framework | vue |
| Node      | v17.7.1                  |
| OS        | mac           |

## Prettier config

```js
// @ts-check
/// <reference types="@prettier/plugin-pug/src/prettier" />

/**
 * @type {import("prettier").Options}
 */
module.exports = {
  plugins: [require.resolve("@prettier/plugin-pug")],
  printWidth: 120,
  pugClassNotation: "attribute",
  pugFramework: "vue",
  pugSortAttributes: "asc",
  pugSortAttributesEnd: ["^:", "^@"],
  quoteProps: "consistent",
  semi: false,
  trailingComma: "all",
}

```

## Input

```vue
<template lang="pug">
n-modal(
  preset="card",
  transform-origin="center",
  v-model:show="show",
  :closable="false",
  :close-on-esc="false",
  :footer-style="{ textAlign: 'center' }",
  :header-style="{ textAlign: 'center' }",
  :mask-closable="false",
  :style="{ backgroundColor: theme.inputColorDisabled, width: '600px' }"
)
  template(#header) Enter the PIN code
  template(#default)
    n-space(justify="space-around", :wrap="false")
      n-input(
        readonly,
        size="large",
        v-for="(value, index) in pinChrs",
        v-model:value="pinChrs[index]",
        :key="index",
        :maxlength="1",
        :show-button="false",
        :style="{ textAlign: 'center', width: '60px' }",
        :theme-overrides="{ fontSizeLarge: '40px', heightLarge: '80px' }"
        :ref="(el:any) => pinRefs[index] = el",
      )
  template(#footer) You should have received by a phone call or by SMS
</template>
```

## Output or Error

```bash
The `:ref="(el:any) => pinRefs[index] = el",` doesn't get ordered, in general no ordering occurs either in the parent component
```

## Expected Output

```pug
In this case, is expected that `:ref="(el:any) => pinRefs[index] = el",` gets reordered to before `:maxlength="1",`
```

## Additional Context


Pull request:
Handle vue ts
closes #365

depends on prettier release of https://github.com/prettier/prettier/pull/12584

Patch:
diff --git a/src/printer.ts b/src/printer.ts
index db20dab..6dd2d95 100644
--- a/src/printer.ts
+++ b/src/printer.ts
@@ -158,20 +158,23 @@ interface FormatDelegatePrettierOptions {
 /**
  * Supported parsers for the `formatDelegatePrettier` function.
  */
-type FormatDelegatePrettierSupportedParser = keyof Pick<
-  typeof PrettierAngularPlugin.parsers &
-    typeof PrettierBabelPlugin.parsers &
-    typeof PrettierHtmlPlugin.parsers &
-    typeof PrettierPostcssPlugin.parsers,
-  | 'css'
-  | 'vue'
-  | '__vue_event_binding'
-  | '__vue_expression'
-  | '__js_expression'
-  | '__ng_binding'
-  | '__ng_action'
-  | '__ng_directive'
->;
+type FormatDelegatePrettierSupportedParser =
+  | keyof Pick<
+      typeof PrettierAngularPlugin.parsers &
+        typeof PrettierBabelPlugin.parsers &
+        typeof PrettierHtmlPlugin.parsers &
+        typeof PrettierPostcssPlugin.parsers,
+      | 'css'
+      | 'vue'
+      | '__vue_event_binding'
+      | '__vue_expression'
+      | '__js_expression'
+      | '__ng_binding'
+      | '__ng_action'
+      | '__ng_directive'
+    >
+  | '__vue_ts_event_binding'
+  | '__vue_ts_expression';
 
 /**
  * The printer class.
@@ -721,13 +724,31 @@ export class PugPrinter {
   }
 
   private async formatVueEventBinding(val: string): Promise<string> {
-    return this.formatDelegatePrettier(val, '__vue_event_binding', {
+    try {
+      return await this.formatDelegatePrettier(val, '__vue_event_binding', {
+        trimTrailingSemicolon: true,
+      });
+    } catch {
+      return this.formatVueTsEventBinding(val);
+    }
+  }
+
+  private async formatVueTsEventBinding(val: string): Promise<string> {
+    return this.formatDelegatePrettier(val, '__vue_ts_event_binding', {
       trimTrailingSemicolon: true,
     });
   }
 
   private async formatVueExpression(val: string): Promise<string> {
-    return this.formatDelegatePrettier(val, '__vue_expression');
+    try {
+      return await this.formatDelegatePrettier(val, '__vue_expression');
+    } catch {
+      return this.formatVueTsExpression(val);
+    }
+  }
+
+  private async formatVueTsExpression(val: string): Promise<string> {
+    return this.formatDelegatePrettier(val, '__vue_ts_expression');
   }
 
   private async formatAngularBinding(val: string): Promise<string> {


Test patch:
diff --git a/tests/issues/issue-365/formatted.vue b/tests/issues/issue-365/formatted.vue
new file mode 100644
index 0000000..aefe5a4
--- /dev/null
+++ b/tests/issues/issue-365/formatted.vue
@@ -0,0 +1,8 @@
+<script lang="ts">
+import { defineComponent } from 'vue';
+export default defineComponent({});
+</script>
+
+<template lang="pug">
+n-input(:ref="(el: any) => (pinRefs[index] = el)", @change="(val: string) => val")
+</template>
diff --git a/tests/issues/issue-365/issue-365.test.ts b/tests/issues/issue-365/issue-365.test.ts
new file mode 100644
index 0000000..cf4cc06
--- /dev/null
+++ b/tests/issues/issue-365/issue-365.test.ts
@@ -0,0 +1,15 @@
+import { compareFiles } from 'tests/common';
+import { describe, expect, it } from 'vitest';
+
+describe('Issues', () => {
+  it('should handle TS in Vue files', async () => {
+    const { actual, expected } = await compareFiles(import.meta.url, {
+      source: 'unformatted.vue',
+      target: 'formatted.vue',
+      formatOptions: {
+        pugFramework: 'vue',
+      },
+    });
+    expect(actual).toBe(expected);
+  });
+});
diff --git a/tests/issues/issue-365/unformatted.vue b/tests/issues/issue-365/unformatted.vue
new file mode 100644
index 0000000..bb31aaf
--- /dev/null
+++ b/tests/issues/issue-365/unformatted.vue
@@ -0,0 +1,8 @@
+<script lang="ts">
+import { defineComponent } from 'vue';
+export default defineComponent({});
+</script>
+
+<template lang="pug">
+n-input(:ref="(el:any)=>pinRefs[index]=el", @change="(val:string)=>val")
+</template>