Skip to content

Preserve custom regex capture whitespace#137

Closed
VrtxOmega wants to merge 1 commit into
avencera:masterfrom
VrtxOmega:fix/ruby-interpolation-spacing-124
Closed

Preserve custom regex capture whitespace#137
VrtxOmega wants to merge 1 commit into
avencera:masterfrom
VrtxOmega:fix/ruby-interpolation-spacing-124

Conversation

@VrtxOmega

@VrtxOmega VrtxOmega commented Jun 14, 2026

Copy link
Copy Markdown

Fixes #124.

Summary

  • Preserve leading and trailing whitespace from custom-regex captures before/after sorting the captured class tokens.
  • Keep the default HTML class behavior unchanged, including multiline class flattening.
  • Add a regression for the reported Ruby/Crystal interpolation case so ? "ok" is not rewritten to ?"ok".

Validation

  • cargo test -p rustywind_core test_custom_regex_preserves_ruby_interpolation_spacing
  • Manual CLI repro with the issue command now preserves the Ruby ternary spacing.
  • cargo fmt --check
  • cargo test --workspace
  • cargo test --workspace --all-features
  • cargo clippy --workspace -- -D warnings

Summary by CodeRabbit

  • Bug Fixes

    • Custom regex patterns now properly preserve leading and trailing whitespace in captured class strings while continuing to correctly sort class values. This improvement is especially beneficial for users working with Ruby interpolation and other dynamic templating syntax where spacing must be carefully maintained.
  • Tests

    • Added test case to verify correct whitespace preservation when using custom regex patterns containing Ruby interpolation.

@coderabbitai

coderabbitai Bot commented Jun 14, 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: f8549374-d80c-44dd-8773-454cf0f69d20

📥 Commits

Reviewing files that changed from the base of the PR and between 3b90677 and 1af6f0c.

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

📝 Walkthrough

Walkthrough

sort_file_contents in rustywind-core/src/app.rs is updated to route captured class strings through a new sort_capture_classes helper. For FinderRegex::CustomRegex, it uses a new sort_classes_preserving_outer_whitespace function that trims ASCII whitespace from both ends, sorts the inner content, and reassembles the original whitespace. A regression test for Ruby interpolation spacing is added.

Changes

CustomRegex whitespace preservation fix

Layer / File(s) Summary
sort_capture_classes helper, whitespace-preserving sort, and regression test
rustywind-core/src/app.rs
Adds sort_capture_classes, which dispatches to sort_classes_preserving_outer_whitespace for CustomRegex (preserving leading/trailing ASCII whitespace) and to sort_classes for all other modes. Adds a unit test asserting that a Ruby interpolation-containing class string is returned unchanged by sort_file_contents.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

🐇 A space before a ? — so dear,
The ternary lived in terror and fear.
But now with a trim and a careful sort,
The whitespace returns safe to port.
No Crystal CI breaks today, hooray!

🚥 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 accurately summarizes the main change: preserving whitespace in custom regex captures, which directly addresses the core issue.
Linked Issues check ✅ Passed The PR successfully implements the fix for issue #124 by preserving whitespace in custom regex captures to prevent removal of spaces in Ruby/Crystal ternary operators.
Out of Scope Changes check ✅ Passed All changes are scoped to addressing issue #124: custom regex whitespace preservation, with a regression test and no unrelated modifications.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

@greptile-apps

greptile-apps Bot commented Jun 15, 2026

Copy link
Copy Markdown

Greptile Summary

This PR fixes a whitespace-stripping bug (issue #124) where custom-regex captures with trailing whitespace — such as a Ruby ternary like ? "ok" — had that space removed during class sorting, corrupting the output. The fix routes custom-regex captures through a new sort_classes_preserving_outer_whitespace helper that strips, sorts, then re-attaches the original leading/trailing whitespace, while leaving the DefaultRegex path completely unchanged.

  • sort_capture_classes now dispatches to the whitespace-preserving helper only for CustomRegex, keeping existing HTML-class multiline-flattening behaviour intact.
  • sort_classes_preserving_outer_whitespace correctly handles all-whitespace and single-character inputs; the unwrap_or(start) fallback on the reverse scan is logically unreachable but silently produces a zero-length slice if ever reached.
  • A new regression test verifies the Ruby interpolation case end-to-end.

Confidence Score: 4/5

Safe to merge — the fix is narrowly scoped to the CustomRegex path and does not touch DefaultRegex behaviour.

The whitespace-preservation logic is correct for all reachable inputs. The only concern is the unwrap_or(start) fallback in sort_classes_preserving_outer_whitespace, which is unreachable given the outer guard but would produce a silently incorrect zero-length slice if ever hit; this is a defensive-coding gap rather than a live defect. Inner whitespace normalisation for custom-regex captures is a minor undocumented limitation. Both are non-blocking quality observations.

rustywind-core/src/app.rs — specifically the sort_classes_preserving_outer_whitespace helper around the unwrap_or fallback.

Important Files Changed

Filename Overview
rustywind-core/src/app.rs Adds sort_capture_classes dispatch and sort_classes_preserving_outer_whitespace helper so custom-regex captures retain their leading/trailing whitespace; also adds a regression test for the Ruby interpolation spacing bug. Logic is correct; one minor edge-case note about the unwrap_or fallback.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["sort_file_contents(file_contents)"] --> B["regex.replace_all — for each match"]
    B --> C["Extract capture group\n(classes &str)"]
    C --> D["sort_capture_classes(classes)"]
    D --> E{FinderRegex\ntype?}
    E -- CustomRegex --> F["sort_classes_preserving_outer_whitespace(classes)"]
    E -- DefaultRegex --> G["sort_classes(classes)"]
    F --> H{Any non-whitespace\ncharacters?}
    H -- No --> I["Return original string unchanged"]
    H -- Yes --> J["Find leading whitespace boundary (start)\nFind trailing whitespace boundary (end)"]
    J --> K["sort_classes(class_string[start..end])"]
    K --> L["Reassemble:\nleading_ws + sorted + trailing_ws"]
    G --> M["split_class_tokens → sort → join(' ')"]
    L --> N["caps[0].replace(classes, sorted_classes)"]
    M --> N
Loading

Reviews (1): Last reviewed commit: "Preserve custom regex capture whitespace" | Re-trigger Greptile

Comment thread rustywind-core/src/app.rs
Comment on lines +107 to +112
let end = class_string
.char_indices()
.rev()
.find(|(_, character)| !character.is_ascii_whitespace())
.map(|(index, character)| index + character.len_utf8())
.unwrap_or(start);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Unreachable fallback value may be confusing

The outer let Some((start, _)) guard guarantees at least one non-ASCII-whitespace character exists, so the reverse .find(...) will always succeed and .unwrap_or(start) is never reached. Using start as the fallback would silently produce a zero-length slice (class_string[start..start]) if it were ever hit, which would be a subtle bug. Consider replacing with .expect("at least one non-whitespace char was found by the forward scan") to make the invariant explicit.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment thread rustywind-core/src/app.rs
Comment on lines +99 to +120
fn sort_classes_preserving_outer_whitespace(&self, class_string: &str) -> String {
let Some((start, _)) = class_string
.char_indices()
.find(|(_, character)| !character.is_ascii_whitespace())
else {
return class_string.to_string();
};

let end = class_string
.char_indices()
.rev()
.find(|(_, character)| !character.is_ascii_whitespace())
.map(|(index, character)| index + character.len_utf8())
.unwrap_or(start);

format!(
"{}{}{}",
&class_string[..start],
self.sort_classes(&class_string[start..end]),
&class_string[end..]
)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Inner whitespace is still collapsed for custom regexes

sort_classes_preserving_outer_whitespace preserves only the leading and trailing whitespace of the capture, but the call to sort_classes inside will normalise any interior whitespace (e.g. "flex p-4""flex p-4") because split_class_tokens splits on whitespace and rewrap_wrapped_classes rejoins with single spaces. For the primary bug (Ruby interpolation trailing space) this is not a problem, but it could silently reformat captures from custom regexes that intentionally contain multiple consecutive spaces. A short doc-comment explaining this deliberate limitation would prevent future surprises.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@VrtxOmega VrtxOmega closed this Jun 16, 2026
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.

Ruby String interpolation in the String get formatted unexpected.

1 participant