From 82ddc736a9f2b0da59c959ea6cde0e8eb310908a Mon Sep 17 00:00:00 2001 From: Virgil Date: Fri, 3 Apr 2026 23:08:50 +0000 Subject: [PATCH] fix(html): harden selector list splitting Co-Authored-By: Virgil --- responsive.go | 16 +++++++++++++--- responsive_test.go | 8 ++++++++ 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/responsive.go b/responsive.go index 6a580a4..c9e4e3e 100644 --- a/responsive.go +++ b/responsive.go @@ -106,19 +106,29 @@ func splitSelectorList(selector string) []string { parts := make([]string, 0, 1) var b strings.Builder var quote rune + escaped := false depthParen := 0 depthBracket := 0 depthBrace := 0 for _, r := range selector { + if escaped { + b.WriteRune(r) + escaped = false + continue + } + + if r == '\\' { + b.WriteRune(r) + escaped = true + continue + } + switch { case quote != 0: b.WriteRune(r) if r == quote { quote = 0 - } else if r == '\\' { - // Keep escaped characters inside quoted strings intact. - continue } case r == '"' || r == '\'': quote = r diff --git a/responsive_test.go b/responsive_test.go index 8fca329..7600481 100644 --- a/responsive_test.go +++ b/responsive_test.go @@ -243,3 +243,11 @@ func TestScopeVariant_PreservesNestedCommas(t *testing.T) { t.Fatalf("ScopeVariant should preserve nested commas = %q, want %q", got, want) } } + +func TestScopeVariant_PreservesEscapedSelectorCharacters(t *testing.T) { + got := ScopeVariant("desktop", `.nav\,primary, [data-state="open\,expanded"]`) + want := `[data-variant="desktop"] .nav\,primary, [data-variant="desktop"] [data-state="open\,expanded"]` + if got != want { + t.Fatalf("ScopeVariant should preserve escaped selector characters = %q, want %q", got, want) + } +}