Skip to content

Formatter: Keep glued ERB output text together to avoid injected whitespace#1863

Open
joaoGabriel55 wants to merge 1 commit into
marcoroth:mainfrom
joaoGabriel55:fix-formatter-adds-newlines-that-produce-undesired-whitespace
Open

Formatter: Keep glued ERB output text together to avoid injected whitespace#1863
joaoGabriel55 wants to merge 1 commit into
marcoroth:mainfrom
joaoGabriel55:fix-formatter-adds-newlines-that-produce-undesired-whitespace

Conversation

@joaoGabriel55

@joaoGabriel55 joaoGabriel55 commented Jul 20, 2026

Copy link
Copy Markdown

Overview

When ERB control-flow bodies (the statements inside if, each, case, while, etc.) contained rendered output "glued" directly to adjacent text with no whitespace between them, the formatter would break the content onto separate lines. This injected whitespace into the rendered HTML that wasn't present in the source.

For example:

<p>
  Hello<% if owner %> <%= owner.name %>'s dog<% end %>!
</p>

The <%= owner.name %>'s dog fragment would get split, changing the rendered output (Peggy's dogPeggy 's dog).

Related Issue

Closes #1729.

Affected Component(s)

  • 💅 Formatter
  • 📦 JavaScript / Node.js / WebAssembly

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)

Main Technical Changes

ERB statement bodies are now routed through a new visitStatements helper. It inspects the statements for a glued text-flow boundary: two adjacent nodes that both render visible output and touch with no whitespace between them (e.g. <%= user.name %>'s dog, <%= greeting %>,, John<%= suffix %>).

  • Glued boundary present → route through the text-flow engine so the content stays together, and no whitespace is injected.
  • No glued boundary (nodes separated by whitespace, or one side is a non-output <% … %> statement that renders nothing) → keep the existing per-node visiting that breaks cleanly at ERB boundaries.

Changes:

  • format-printer.ts — Added visitStatements, which delegates to hasGluedTextFlowBoundary to decide between text-flow visiting and per-node visiting. Wired every ERB control-flow visitor (if, in, else, when, begin, while, until, for, rescue, ensure, unless) to use it.
  • text-flow-helpers.ts — Added hasGluedTextFlowBoundary and a rendersVisibleOutput helper. The latter treats non-empty text, ERB output tags (<%= %> / <%== %>), and inline elements as visible output; non-output <% … %> control statements render nothing and break the glue chain.
  • text-flow-engine.ts — Exposed hasGluedTextFlowBoundary on the engine.

Breaking is only suppressed where it would actually alter rendered output. Whitespace-separated outputs and non-output control statements are unaffected, so existing formatting behavior is preserved everywhere that gluing doesn't matter.

How to Test

A new test suite (test/erb/glued-text-flow.test.ts, 7 cases) covers:

  • text glued to ERB output inside if blocks (block and inline start)
  • punctuation glued to output alongside block content (if and capture do)
  • ERB output glued to preceding text
  • regression guards: whitespace-separated <%= … %> outputs still break at ERB boundaries, and a non-output <% i += 1 %> statement glued to an inline element still breaks

Run the formatter test suite from javascript/packages/formatter:

yarn vitest run test/erb/glued-text-flow.test.ts

Checklist

  • 🧪 Tests added or updated
  • 🔨 Ran the relevant test suites
  • 📚 Documentation updated (if applicable)
  • 🧭 Commit messages follow the Component: Description convention

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Formatter: adds newlines that produce undesired whitespace

1 participant