Skip to content

Skip class strings containing template syntax (fixes 0.25.0 regression) - #141

Merged
praveenperera merged 3 commits into
avencera:masterfrom
brianfoshee:fix/erb-ternary-corruption
Jul 8, 2026
Merged

Skip class strings containing template syntax (fixes 0.25.0 regression)#141
praveenperera merged 3 commits into
avencera:masterfrom
brianfoshee:fix/erb-ternary-corruption

Conversation

@brianfoshee

@brianfoshee brianfoshee commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

I filed #140 after noticing my CI fail in a Ruby on Rails project. Here's my attempt at a fix (I worked with Claude Code, for full transparency).

Fixes #140. Also fixes the corruption reported in #124.

Fix

In sort_file_contents, leave a matched class string untouched when it contains a template opening delimiter: <% (ERB/EJS), <? (PHP), {{ / {% (Handlebars/Jinja/Liquid), #{ (Ruby string interpolation).

This matches the 0.24.x effect of leaving these attributes untouched — 0.24.x's restrictive regex simply didn't match them; this guard produces the same result via a skip after matching — while keeping fc7d701's improvement: arbitrary values with whitespace like max-w-[min(100%, 500px)] still sort correctly.

Because the guard is in the shared sort_file_contents path, it also applies to --custom-regex, which fixes the #{ ... ? ... : ... } reformatting reported in #124.

Trade-offs

  • Silent skip. rustywind now silently skips sorting the entire class attribute when any of these delimiters appear — including the static classes around the template tag. That matches 0.24.x (which skipped the same attributes, also silently), but it is not ideal long-term: real template-aware tokenization could sort the static portions while treating each tag as an opaque unit. I left that as future work (noted in a TODO above TEMPLATE_DELIMITERS) to keep this PR a targeted regression fix.
  • False positives. A class string containing a delimiter outside template code — e.g. an arbitrary value like content-['<?'] — is now skipped too.
  • Public API. The guard lives in sort_file_contents; calling sort_classes directly with a template-bearing string still splits it on whitespace. Its doc comment now states that it expects plain class names.

Testing

Added five test_cases to rustywind-core/src/app.rs covering the three repros from #140, the interpolation case from #124, and a mustache-style case.

All fail on main with exactly the corruption from the issue and pass with this change.

Each asserts sort_file_contents is byte-identical on its input, which is what makes --check-formatted pass again — closing the "CI accepts the corrupted output" symptom from #140.

Summary by CodeRabbit

  • Bug Fixes
    • Improved class sorting to detect template syntax within class/className strings and leave those segments unchanged instead of reordering tokens.
    • Preserves common template/interpolation forms (e.g., ERB/EJS-like tags, Liquid/Jinja-like blocks, mustache-style expressions, Ruby interpolation, and JavaScript template literals).
  • Documentation
    • Clarified expectations for how template code should be treated during class sorting.
  • Tests
    • Added coverage for template delimiter detection and ensured sorting remains stable for template-containing class strings.

Since fc7d701 the default finder regex captures class attributes that
embed template-language tags (previously the restrictive character
class made these attributes non-matches). split_class_tokens then
treats whitespace inside the embedded code as a class boundary, so
sorting moves tokens across quote and tag boundaries — corrupting ERB
ternaries into invalid Ruby or silently changing which classes a
branch applies (avencera#140), and reformatting Ruby string interpolation
(avencera#124).

Leave a matched class string untouched when it contains a template
opening delimiter (<%, <?, {{, {%, #{). This restores the 0.24.x
behavior of ignoring such attributes while keeping the improved
handling of arbitrary values with whitespace.

Fixes avencera#140
Note on sort_classes that it expects plain class names (the
template-syntax guard lives in sort_file_contents), and add a TODO
for tokenizing template tags as opaque units so surrounding static
classes can still be sorted.
@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: f2a852f3-a377-4803-8a9d-f82f2277e89d

📥 Commits

Reviewing files that changed from the base of the PR and between fd6a0ec and b3acfaf.

📒 Files selected for processing (1)
  • rustywind-core/src/app.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • rustywind-core/src/app.rs

📝 Walkthrough

Walkthrough

The change adds template-syntax detection for extracted class attribute strings and skips sorting when delimiters are present. It also adds unit coverage for the detection helper and for class strings containing ERB, Ruby interpolation, mustache-style braces, and JavaScript template literals.

Changes

Template Syntax Guard

Layer / File(s) Summary
Detect and skip template syntax in class sorting
rustywind-core/src/app.rs
Adds TEMPLATE_DELIMITERS and contains_template_syntax, wires the guard into sort_file_contents, and updates the inline sort_classes documentation.
Cover template cases in sort_file_contents tests
rustywind-core/src/app.rs
Adds sort_file_contents cases for ERB ternaries, Ruby interpolation, mustache-style interpolation, and JS template literal interpolation remaining unchanged.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related issues

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: skipping class strings that contain template syntax to fix the regression.
Linked Issues check ✅ Passed The change directly addresses issue #140 by leaving template-bearing class attributes unchanged and preserving normal sorting behavior.
Out of Scope Changes check ✅ Passed The added helper, docs, and tests are all directly related to the template-syntax regression fix and do not appear unrelated.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@greptile-apps

greptile-apps Bot commented Jul 7, 2026

Copy link
Copy Markdown

Greptile Summary

This PR makes template-bearing class strings opt out of sorting. The main changes are:

  • Adds a template delimiter check in sort_file_contents.
  • Leaves matched class attributes unchanged when template syntax is present.
  • Documents that sort_classes expects plain class names.
  • Adds regression tests for ERB, Ruby interpolation, and mustache-style templates.

Confidence Score: 5/5

This looks safe to merge after a small hardening fix for split template fragments.

  • The main skip path is conservative and preserves matched template-bearing class strings.
  • A quoted template expression can leave a later closing-delimiter fragment available for sorting.
  • No blocking issue was found in the changed code.

rustywind-core/src/app.rs

Important Files Changed

Filename Overview
rustywind-core/src/app.rs Adds template-syntax skipping to the shared file-sorting path, but the delimiter list can miss suffix fragments after quote-split template matches.

Reviews (1): Last reviewed commit: "Document template-syntax guard scope" | Re-trigger Greptile

Comment thread rustywind-core/src/app.rs Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
rustywind-core/src/app.rs (1)

295-308: 🎯 Functional Correctness | 🔵 Trivial

Consider whether ${...} template-literal interpolation needs the same guard.

The delimiter list covers ERB/EJS, PHP, Handlebars/Jinja/Liquid, and Ruby interpolation, but not JS/TS template-literal interpolation (${...}), which is a common pattern for conditional classes in JSX/Vue/Svelte template strings (e.g. class={`base ${cond ? 'a' : 'b'}`}). This could hit the same whitespace-splitting corruption this PR fixes for other engines. Given the existing TODO acknowledges more nuanced handling is future work, this may be intentionally out of scope for now — worth confirming if it should be added to TEMPLATE_DELIMITERS in this pass or deferred.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@rustywind-core/src/app.rs` around lines 295 - 308, The template-syntax guard
in contains_template_syntax is missing JS/TS template-literal interpolation, so
add "${" to TEMPLATE_DELIMITERS if this pass should protect JSX/Vue/Svelte class
strings like class={\`base ${cond ? 'a' : 'b'}\`}; otherwise explicitly defer it
and document that the current guard in app.rs is intentionally limited. Keep the
change scoped to the TEMPLATE_DELIMITERS constant and the
contains_template_syntax check so the same opaque-syntax protection applies
consistently.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@rustywind-core/src/app.rs`:
- Around line 295-308: The template-syntax guard in contains_template_syntax is
missing JS/TS template-literal interpolation, so add "${" to TEMPLATE_DELIMITERS
if this pass should protect JSX/Vue/Svelte class strings like class={\`base
${cond ? 'a' : 'b'}\`}; otherwise explicitly defer it and document that the
current guard in app.rs is intentionally limited. Keep the change scoped to the
TEMPLATE_DELIMITERS constant and the contains_template_syntax check so the same
opaque-syntax protection applies consistently.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: e18d97df-e52e-4950-8a6b-d2e5df5bf414

📥 Commits

Reviewing files that changed from the base of the PR and between e15d978 and fd6a0ec.

📒 Files selected for processing (1)
  • rustywind-core/src/app.rs

Add closing delimiters (%>, ?>, }}, %}) so a regex match split by a
quote inside template code is still skipped when the fragment only
contains the tail of a tag, and add ${ for JS template-literal
interpolation (e.g. Lit-style class="p-4 ${cond ? 'a b' : 'c'}"),
which the default regex captures and would corrupt the same way as
ERB.
@brianfoshee

Copy link
Copy Markdown
Contributor Author

@coderabbitai Added ${ to TEMPLATE_DELIMITERS in b3acfaf — it's not limited to JSX: Lit-style tagged templates put class="p-4 ${cond ? 'a b' : 'c'}" in plain double quotes, which the default regex captures. Includes a test reproducing the corruption. Also added the closing delimiters (%>, ?>, }}, %}) from Greptile's comment.

@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
git log --oneline -5
git show b3acfaf -- rustywind-core/src/app.rs || git log --all --oneline | grep -i b3acfaf

Length of output: 4331


@brianfoshee Thanks for the update — verified b3acfaf. Good catch on the closing delimiters: since the class-attribute regex can be split by a quote inside the template code (e.g. a' %> flex p-4), only having opening delimiters would miss those tail fragments and still risk corruption. Adding %>, ?>, }}, %} closes that gap.

The ${ addition is also a solid catch — you're right that it's not JSX-specific. Lit's html tagged templates put class="p-4 ${cond ? 'a b' : 'c'}" in plain double-quoted attributes, which the existing class-attribute regex would happily match and then corrupt via whitespace splitting, exactly like the ERB case. The new test case (makes no change to class string with js template literal interpolation) and the detects_closing_delimiter_fragments/ignores_plain_class_strings unit tests give good coverage for both changes.

One thing worth double-checking: with ${ in the delimiter list, arbitrary-value strings that legitimately contain a literal $ followed by { (unlikely in Tailwind, but worth a thought) would now also be skipped — that's almost certainly the right tradeoff here, just flagging it as a conscious scope decision for anyone reading the PR later.

This looks like a solid, well-tested fix. Nice work closing both gaps in the same pass.

(°◡°)ノ


✏️ Learnings added
Learnt from: brianfoshee
URL: https://github.com/avencera/rustywind/pull/141

Timestamp: 2026-07-07T22:32:11.281Z
Learning: In rustywind-core/src/app.rs, TEMPLATE_DELIMITERS (used by contains_template_syntax in sort_file_contents) includes both opening and closing delimiters: `<%`, `%>`, `<?`, `?>`, `{{`, `}}`, `{%`, `%}`, `#{`, `${`. Closing delimiters were added because a quote inside template code can split a regex match, leaving a fragment containing only the tail of a tag (e.g. `a' %> flex p-4`). `${` was added to handle JS/Lit-style tagged template literals like `class="p-4 ${cond ? 'a b' : 'c'}"` in plain double-quoted attributes, which is not limited to JSX.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

@praveenperera

Copy link
Copy Markdown
Member

Thanks @brianfoshee I'll get this reviewed and release 0.25.1 ASAP

@praveenperera
praveenperera merged commit 2226633 into avencera:master Jul 8, 2026
16 checks passed
@brianfoshee
brianfoshee deleted the fix/erb-ternary-corruption branch July 8, 2026 11:56
@praveenperera

Copy link
Copy Markdown
Member

@brianfoshee fix is in v0.25.1 thanks @brianfoshee

@brianfoshee

Copy link
Copy Markdown
Contributor Author

Thank you @praveenperera

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0.25.0 corrupts ERB ternaries inside class attributes

2 participants