feat: support case-sensitive attribute flag (s) per Selectors L4 - #327
feat: support case-sensitive attribute flag (s) per Selectors L4#327abhu85 wants to merge 2 commits into
Conversation
Attribute selectors accept the explicit case-sensitive flag `s`/`S` (CSS Selectors Level 4), but the parser only modeled the case-insensitive flag `i`/`I`. For `[a=b s]` the `s` was dumped into `raws.insensitiveFlag` and left `insensitive` false, so the flag was not modeled at all. Model `s` analogously to `i`: add a `sensitive` boolean and a `sensitiveFlag` getter on Attribute, recognize `s`/`S` in the parser, and serialize the flag from `sensitiveFlag` when present. The original `S` notation is preserved in `raws.sensitiveFlag`. Non-standard flags still round-trip through `raws.insensitiveFlag` unchanged. Fixes postcss#309
|
Thanks for this — the The modeling mirrors Two things before I merge. 1. The two flags aren't mutually exclusiveconst attr = parser().astSync('[a=b i]').first.first;
attr.sensitive = true;
attr.insensitive; // => true
attr.sensitive; // => true
attr.toString(); // => '[a=b s]'A node can end up claiming both case sensitivities at once, and the serializer silently picks While you're in there: 2.
|
Address review feedback on postcss#327: - `attr.insensitive` and `attr.sensitive` now clear each other, along with any capitalized notation held in raws, so a node can no longer claim both case sensitivities at once. This also fixes the pre-existing case where an `s` attribute could not be switched to `i` at all, because the stale `raws.insensitiveFlag` won during serialization. - Only the standard `i`/`I` and `s`/`S` notations are erased, so a non-standard flag preserved in `raws.insensitiveFlag` (e.g. `[a=b y]`) still round-trips. - Drop the redundant double assignment in both setters. - `offsetOf()` now accepts "sensitive". It previously returned -1 for an `s` flag, leaving that flag with no reachable offset. - Document both flags, their mutual exclusivity, the shared spaces slot and `raws.sensitiveFlag` in API.md.
|
Thanks — both points were real, and the first turned out to be worse than the repro showed. Pushed as 3d7faf3. 1. Mutual exclusivityFixed in both directions: each setter now clears the other flag along with any capitalized notation held for it in const attr = parser().astSync('[a=b i]').first.first;
attr.sensitive = true;
attr.insensitive; // => false
attr.toString(); // => '[a=b s]'Worth flagging that the reverse direction was already broken on // main
const attr = parser().astSync('[a=b s]').first.first;
attr.insensitive = true;
attr.toString(); // => '[a=b s]' ← the stale raws.insensitiveFlag winsSo there was no way to turn an One thing I deliberately did not do: the setters only erase the standard notations ( Good catch on the double assignment. It's gone, and since it was copied from 2.
|
Summary
Adds support for the CSS Selectors Level 4 case-sensitive attribute flag
s/S(e.g.[a=b s]), modeled analogously to the existing case-insensitivei/Iflag.Fixes #309
Problem
The parser only recognized
i/I. For[class="foo" s]it leftinsensitive: falseand dumped thesintoraws.insensitiveFlag, so the explicit case-sensitive flag was never modeled on the AST. (It happened to round-trip via that mis-filed raw, but the parsed node did not reflect the flag.)Solution
s/Sin the attribute parser and set a newsensitiveboolean on the node.Attribute#sensitive(get/set) andAttribute#sensitiveFlag('s' | ''), preserving the originalSnotation inraws.sensitiveFlag.raws. Only the standardi/Iands/Snotations are erased, so a non-standard flag preserved inraws.insensitiveFlag([a=b y]) still round-trips.sensitiveFlagwhen present;i/Iand non-standard flags (viaraws.insensitiveFlag) are unchanged.insensitivespace slot, sospaces.insensitiveandraws.spaces.insensitivecarry the whitespace and comments around either flag.offsetOf()additionally accepts"sensitive"— it gates on the boolean, so it returned-1for ansflag onmain, leaving that flag with no reachable offset.API.md: the booleans, their mutual exclusivity, the shared space slot, theoffsetOfbehaviour, and what eachrawsflag holds.Test Plan
Parse +
.toString()round-trip tests for[href="foo" s],[href="foo" S]and unquoted[href=test s], assertingsensitive/sensitiveFlagare set and thatsis no longer mis-filed intoraws.insensitiveFlag. Plusi↔sandI↔Smutual-exclusion round trips,offsetOffor both flags, and a test pinning that a non-standardyflag survives both setters. Full suite:795 passing; lint, typecheck and coverage gates green.Compatibility
Not purely additive, so this wants a minor release rather than a patch.
[a=b s]previously parked the flag inraws.insensitiveFlagwithinsensitive: false; it now setssensitive: trueand leavesrawsuntouched, so the AST changes for input that already parses today.Serialization is byte-identical for
s,S,i,Iand non-standard flags, and no property is removed or repurposed, so the practical impact should be limited to code that readraws.insensitiveFlagto detect an unrecognized flag — which is the bug in #309.The only other break I can find is type-level:
readonly sensitiveFlag: 's' | ''is a required member added to the exportedAttributeinterface, so it affects code that implementsAttributerather than consuming it (insensitiveFlagwas already required there).Happy to adjust the API shape if you'd prefer a different modeling.