From e2d26584bbc8acc6be4fa53132e9dd44c20835f8 Mon Sep 17 00:00:00 2001 From: Keita Urashima Date: Tue, 2 Jun 2026 11:23:14 +0900 Subject: [PATCH] fix(attributes): `[attr~=""]` with an empty value matches nothing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `~=` handler only short-circuited to `falseFunc` for values containing whitespace. An empty value fell through to a regex `(?:^|\s)(?:$|\s)` that wrongly matched an empty attribute such as `class=""`. Per Selectors §6.3.2 an empty (or whitespace-containing) `~=` value can never be a single token and matches nothing — consistent with the empty-value short-circuit `^=`/`$=`/`*=` already have, and with browser behaviour (`[class~=""]` selects nothing in Chrome). Co-Authored-By: Claude Opus 4.8 (1M context) --- src/attributes.ts | 7 ++++++- test/attributes.ts | 6 ++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/attributes.ts b/src/attributes.ts index a8d6ef77..1c57559a 100644 --- a/src/attributes.ts +++ b/src/attributes.ts @@ -146,7 +146,12 @@ export const attributeRules: Record< element(next, data, options) { const { adapter } = options; const { name, value } = data; - if (whitespaceRe.test(value)) { + /* + * An empty value (or one containing whitespace, which can never be a + * single token) matches nothing — same as `^=`/`$=`/`*=` with an empty + * value. See https://www.w3.org/TR/selectors-4/#attribute-representation + */ + if (value === "" || whitespaceRe.test(value)) { return boolbase.falseFunc; } diff --git a/test/attributes.ts b/test/attributes.ts index 5f4419d8..4b268d04 100644 --- a/test/attributes.ts +++ b/test/attributes.ts @@ -91,6 +91,12 @@ describe("Attributes", () => { ); }); + it("should for ~= with an empty value", () => { + expect(CSSselect._compileUnsafe("[foo~='']")).toBe( + boolbase.falseFunc, + ); + }); + it("should for $=", () => { expect(CSSselect._compileUnsafe("[foo$='']")).toBe( boolbase.falseFunc,