Files
oxc-project__oxc/apps/oxfmt/src-js/libs/prettier-plugin-oxfmt/index.ts
leaysgur a9e76e9e1f fix(oxfmt): rework tsx-in-vue support (#25106)
Follow-up to #25063.

tsx-in-vue was supported in #25063 with a parse-retry (ts fails → retry as tsx), chosen to keep the common ts path single-parse.

However, this leaves a hole: a `lang="tsx"` block without JSX parses fine as plain ts, so the retry never fires and the lone generic comma is removed:

```vue
<script setup lang="tsx">
const identity = <T,>(x: T) => x; // → <T>(x: T) => x, breaks as TS17008
</script>
```

Since parse failure fundamentally cannot detect this, the only fix is to scan the `lang` declaration itself.

The JS host now scans the SFC once per file and passes `_oxfmtVueScriptLang: "tsx"` down to `textToDoc()`. This makes the retry obsolete, so it is removed.

This PR also settles the comma policy: keep it only where it is load-bearing.

- JSX grammar (`<T>` would be a JSX opener)
- and `.mts/.cts` (TS7060, #18928)

and remove it everywhere else, keyed on the grammar, regardless of context.

Deliberate divergence: Prettier keys on filepath and keeps the comma in ts-in-vue/svelte (while its own ts-in-md embed uses `dummy.ts` as `filepath` and removes it). Plain-TS sources now behave identically wherever they live.
2026-07-30 05:27:07 +00:00

53 lines
1.5 KiB
TypeScript

/**
* Prettier plugin that uses `oxc_formatter` for (j|t)s-in-xxx part.
*
* When Prettier formats Vue/HTML (which can embed JS/TS code inside) files,
* it calls the `embed()` function for each block.
*
* By default, it uses the `babel` or `typescript` parser and `estree` printer.
* Therefore, by overriding these internally, we can use `oxc_formatter` instead.
* e.g. Now it's possible to apply our builtin sort-imports for JS/TS code inside Vue `<script>`.
*/
import { textToDoc } from "./text-to-doc";
import type { Parser, Printer, Doc, SupportOptions } from "prettier";
// NOTE: Custom options must be declared here,
// or Prettier's normalization drops them before they reach `textToDoc()`.
export const options: SupportOptions = {
_oxfmtPluginOptionsJson: {
category: "JavaScript",
type: "string",
default: "{}",
description: "Bundled JSON string for oxfmt-plugin options",
},
_oxfmtVueScriptLang: {
category: "JavaScript",
type: "string",
default: "",
description:
'Effective `lang` of Vue SFC `<script>` blocks when it needs disambiguation (currently only "tsx"), scanned by the host',
},
};
const oxfmtParser: Parser<Doc> = {
parse: textToDoc,
astFormat: "OXFMT",
// Not used but required
locStart: () => -1,
locEnd: () => -1,
};
export const parsers: Record<string, Parser> = {
// Override default JS/TS parsers
babel: oxfmtParser,
"babel-ts": oxfmtParser,
typescript: oxfmtParser,
};
export const printers: Record<string, Printer<Doc>> = {
OXFMT: {
print: ({ node }) => node,
},
};