Skip to content

fix(oxfmt): use literalline for template literals in textToDoc#20085

Open
vinayakkulkarni wants to merge 1 commit intooxc-project:mainfrom
vinayakkulkarni:fix/oxfmt-template-literal-idempotency
Open

fix(oxfmt): use literalline for template literals in textToDoc#20085
vinayakkulkarni wants to merge 1 commit intooxc-project:mainfrom
vinayakkulkarni:fix/oxfmt-template-literal-idempotency

Conversation

@vinayakkulkarni
Copy link

Summary

Fixes #20084

When vueIndentScriptAndStyle: true, formatting template literals inside Vue SFC <script> blocks is non-idempotent — each oxfmt --write . pass adds +2 spaces of indentation to template literal content, growing infinitely.

Root Cause

In run_full() (text_to_doc_api.rs), the formatted code was converted to a Prettier Doc by joining all newlines with hardline. When Prettier wraps the textToDoc() result with indent() for vueIndentScriptAndStyle, hardline respects the parent indent — so template literal text content gets +2 spaces per pass.

Fix

After formatting the code to a string (preserving all formatting decisions), re-parse the output to identify TemplateElement spans via an AST visitor. Then build the Prettier Doc using:

  • literalline for newlines inside template literal spans (ignores parent indent)
  • hardline for all other newlines (respects parent indent, as before)

This matches how Prettier itself handles template literals in its own textToDoc path.

Changes

  • apps/oxfmt/Cargo.toml — Added oxc_ast_visit dependency
  • apps/oxfmt/src/api/text_to_doc_api.rs — Call new function instead of printed_string_to_hardline_doc
  • apps/oxfmt/src/prettier_compat/to_prettier_doc.rs — Added printed_string_to_doc_with_template_literals(), TemplateElementCollector visitor, removed old printed_string_to_hardline_doc()
  • apps/oxfmt/test/api/js-in-vue.test.ts — Added idempotency test

Reproduction

https://github.com/vinayakkulkarni/oxfmt-template-literal-repro

Verification

  • cargo c
  • cargo c --no-default-features
  • cargo c --features detect_code_removal
  • cargo clippy -p oxfmt
  • cargo t -p oxfmt — 32/32 passed ✅
  • pnpm build-test && pnpm t — 241/241 passed ✅

@github-actions github-actions bot added A-cli Area - CLI A-formatter Area - Formatter C-bug Category - Bug labels Mar 6, 2026
@vinayakkulkarni vinayakkulkarni force-pushed the fix/oxfmt-template-literal-idempotency branch 3 times, most recently from 7032f60 to 52b611b Compare March 6, 2026 23:07
When `vueIndentScriptAndStyle: true`, Prettier wraps the `textToDoc()`
result with `indent()`. Template literal newlines joined with `hardline`
would respect this indent, causing +2 spaces per format pass (infinite
indentation growth).

Re-parse the formatted output to identify `TemplateElement` spans and
use `literalline` (which ignores parent indent) for newlines within
template literals, while keeping `hardline` for all other newlines.

Closes oxc-project#20084.
@vinayakkulkarni vinayakkulkarni force-pushed the fix/oxfmt-template-literal-idempotency branch from 52b611b to 49c2309 Compare March 7, 2026 08:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-cli Area - CLI A-formatter Area - Formatter C-bug Category - Bug

Projects

None yet

Development

Successfully merging this pull request may close these issues.

oxfmt: non-idempotent formatting of template literals in Vue SFC with vueIndentScriptAndStyle

1 participant