Skip to content

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

@vinayakkulkarni

Description

@vinayakkulkarni

Description

oxfmt is not idempotent when formatting template literals inside Vue SFC <script setup> blocks with vueIndentScriptAndStyle: true. Each formatting pass adds 2 additional spaces of indentation to lines inside template literals, causing indentation to grow indefinitely.

This is distinct from the CSS-in-JS template literal issue fixed in #18622 — this affects plain template literals (not styled-components or CSS-in-js) in Vue SFCs.

Steps to Reproduce

  1. Clone the reproduction repo: https://github.com/vinayakkulkarni/oxfmt-template-literal-repro
  2. Run bun install
  3. Run bun run format — observe the diff
  4. Run bun run format again — indentation grows further
  5. Repeat — it keeps growing
git clone https://github.com/vinayakkulkarni/oxfmt-template-literal-repro
cd oxfmt-template-literal-repro
bun install
bun run format && git diff   # first pass: +2 spaces
bun run format && git diff   # second pass: +4 spaces total
bun run format && git diff   # third pass: +6 spaces total

Input (Example.vue)

<script setup lang="ts">
  const SCRIPT_START = '<' + 'script setup lang="ts">';
  const SCRIPT_END = '</' + 'script>';

  const codeExample = `${SCRIPT_START}
    import { ref } from 'vue';

    const count = ref(0);
    const message = 'Hello World';
  ${SCRIPT_END}

  <template>
    <div class="container">
      <h1>{{ message }}</h1>
      <button @click="count++">Count: {{ count }}</button>
    </div>
  </template>`;
</script>

<template>
  <div>
    <pre><code>{{ codeExample }}</code></pre>
  </div>
</template>

After 1st format

Lines inside the template literal gain +2 spaces (4 → 6, 2 → 4):

  const codeExample = `${SCRIPT_START}
      import { ref } from 'vue';

      const count = ref(0);
      const message = 'Hello World';
    ${SCRIPT_END}

    <template>
      <div class="container">
        <h1>{{ message }}</h1>
        <button @click="count++">Count: {{ count }}</button>
      </div>
    </template>`;

After 2nd format

Another +2 spaces (6 → 8, 4 → 6):

  const codeExample = `${SCRIPT_START}
        import { ref } from 'vue';

        const count = ref(0);
        const message = 'Hello World';
      ${SCRIPT_END}

      <template>
        <div class="container">
          <h1>{{ message }}</h1>
          <button @click="count++">Count: {{ count }}</button>
        </div>
      </template>`;

After 3rd format

Another +2 spaces — grows indefinitely.

Expected Behavior

Formatting should be idempotent. Running oxfmt on already-formatted code should produce identical output.

Root Cause Hypothesis

The vueIndentScriptAndStyle: true option adds one level of indentation to the <script> block content. When oxfmt processes template literal content inside that indented script block, it appears to re-apply the Vue indentation offset to the template literal's content on each pass, rather than recognizing it as already-indented literal string content that should be preserved as-is.

Config (.oxfmtrc.jsonc)

{
  "printWidth": 80,
  "semi": true,
  "singleQuote": true,
  "tabWidth": 2,
  "trailingComma": "all",
  "bracketSpacing": true,
  "arrowParens": "always",
  "endOfLine": "lf",
  "vueIndentScriptAndStyle": true
}

Environment

Related Issues

Both were for CSS-in-JS / styled-components. This issue is for plain template literals in Vue SFCs.

Metadata

Metadata

Assignees

Labels

Type

No type

Priority

None yet

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions