1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
// CSS boundary-whitespace preservation — the printer half of the class named in
// `crate::whitespace::is_boundary_only_whitespace`.
//
// ## What this module is
//
// `parseCss` skips a JS-`\s` run at every `allow_whitespace()` juncture, and tsv's parser
// mirrors that skip (`CssParser::skip_boundary_whitespace`). The members at or above U+00A0
// are not ASCII whitespace, so dropping one from the OUTPUT is content loss — the corpus
// SAFETY check counts them as semantic characters. The parser decides what the AST does not
// contain; this module decides what the output must still carry, and the two are **one
// change**: a juncture added to the parser with no claim here trades a graceful
// over-rejection for content loss, the worse of the two.
//
// ## The two claims, and why they partition
//
// - [`Printer::preserved_boundary_ws`] scans BACKWARD from an anchor and takes the run
// CONTIGUOUS with it. It needs a FLOOR wherever the previous node's span can end inside the
// run (`read_identifier` folds a trailing run into the name it follows), or it emits a
// character that name already carried.
// - [`Printer::spell_gap_items`] sweeps FORWARD over a whole gap, printing its members AND its
// comments. A backward scan alone stops at a `*/` and drops whatever a comment strands
// behind it (`a <NBSP>/* c */ b`).
//
// A claim that sweeps a gap forward owns the gap's comments too, wherever the gap holds a
// member ([`Printer::gap_holds_member`]), and the site's own comment seam then stands down
// for that gap; a member-free gap keeps the site's comment spelling. [`Printer::claim_lead_gap`]
// lets a container (a list's comma, a pseudo-argument's `(`, an `of`) claim a selector's lead
// gap whole and stand the selector's own backward scan down ([`Printer::claimed_lead`]), so
// the two never print one character twice. [`Printer::gap_boundary_ws`] is the answer every
// emitter of a gap ending at a compound's first simple selector uses.
//
// ## How a claim is spelled
//
// Every claim emits its gap through one speller (`crate::whitespace::spell_run`, named there
// because the parser spells three kinds of gap into a node's text with it too — see
// `Comments::Carried`), which reads the gap as a
// SEQUENCE OF ITEMS — the members, and the comments where the claim owns them — and prints
// them in source order, in place: between two items, and at each edge, the author's ASCII
// whitespace is ONE space (a tab, a line break or a double space alike) and its absence is
// none. Three readers make that the rule. `parseCss` skips the whole run, so nothing about
// the AST depends on it. css-syntax-3, whose whitespace is ASCII only, reads every member as
// identifier content and a comment as nothing at all, so the spelling IS the tokenization:
// `<NBSP><NBSP>` is one identifier where `<NBSP> <NBSP>` is two, `a<ZWNBSP>` one where
// `a <ZWNBSP>` is a type selector and a descendant, and `a <NBSP>/* c */:hover` holds the
// compound `<NBSP>:hover` where `a /* c */ <NBSP> :hover` is a descendant — a comment moved to
// the other side of a member, or spaced off one it was glued to, changes what a browser
// reads. And the doc renderer: a line break carried inside the text the printer emits is one
// it neither indents after nor counts, so the anchor lands at column 0. It is prettier's
// answer at every juncture where prettier keeps the run.
//
// ⚠️ The rule holds at the CLAIMED junctures — the table below. A prelude the parser reads
// verbatim (`@layer`, `@page`, `@keyframes`, any at-rule on no reader's list, a condition
// prelude it cannot structure) keeps its INTERIOR as the author wrote it, tabs and line breaks
// included, exactly as the member-free spelling does (`@layer a⏎<ZWNBSP>⏎, b` and `@layer a⏎,
// b` alike): there a run is prelude text to every reader. Its two ENDS are gaps like any other
// — `parseCss` trims them away — and are spelled by the rule (`raw::PreludeEnds`).
//
// What varies per claim is only the two EDGES, and the claim says which ([`Edge`]):
//
// - **[`Edge::Flush`]** where the printer regenerates what stands there — a line's
// indentation ahead of a block child's head, the space a combinator's separator doc or a
// `::part()` join writes, the ` {` after a rule's head — or where it is a delimiter the run
// may touch: the `(` / `[` it opens after, the `,` / `)` / `]` / `}` it closes against.
// - **[`Edge::Presence`]** beside a NAME or a TERM: one space if the author separated the
// gap's outermost item from it, none if they glued it. `a <ZWNBSP>{` stays `a <ZWNBSP> {`,
// `2n <NBSP>)` stays spaced, and `2n<NBSP>)` does not grow a space the author never wrote.
// - **[`Edge::Space`]** where a MEMBER is about to be emitted after a NAME the printer cannot
// see the author's separation from (`read_identifier` takes every code point at or above
// U+00A0 as content, so the run would glue into it — `[a <NBSP>]` came back with the name
// `a<NBSP>`, an AST that changed under a format and its own fixed point, which no
// idempotency, reparse or content-count gate sees). A gap that opens on a comment takes the
// author's separation instead, since no name can take a comment in. [`Printer::name_run_edge`]
// (from a source position) and [`name_run_separator_after`] (from built text) ask the
// question. ⚠️ That is **not** one gap: the attribute selector's `[name<HERE>]`, a selector
// list's `,` (`a.x <NBSP>, c`), an explicit combinator's symbol (`a <NBSP>> b`), a
// pseudo-argument list's `)` (`:is(a <NBSP>)`), the commented attribute rebuild's every
// interior gap, a rule's pre-`{` gap, and a condition prelude's part head behind a
// CONNECTOR (`@supports and <NBSP>(a: b)` — the connector is an identifier like any other)
// all have a name on their left, and each was a live glue. A claim added anywhere new owes
// the same question. The condition one answers it structurally rather than by asking:
// `build_condition_query_doc`'s head pieces are joined and *followed* by one space, so the
// run can never land flush against the last of them; its tell is not a changed name in the
// wire but a prelude that stops reading as a condition at all
// (`a_connector_never_absorbs_the_run_behind_it`).
//
// Two emitters spell a gap's items without a claim, each for its own construct: the An+B
// normalizer (`Printer::normalize_an_plus_b`), which folds the runs around an operator into
// the term's own text (`2n<NBSP> + 1`) and keeps a comment glued to a member glued, and the
// attribute selector's tail (`Printer::push_attribute_tail`), whose flag is one more item.
//
// ## Who claims where
//
// One entry per juncture the parser steps a run at, so "a skip and a preservation are one
// change" is checkable by reading this list against `CssParser::skip_boundary_whitespace`:
//
// | juncture | claimer |
// | --- | --- |
// | a compound's first simple selector | `gap_boundary_ws` (both complex-selector builders) |
// | an explicit / anchorless combinator | `push_combinator_boundary_ws` |
// | a selector list's `,`, and the gap after it | `pre_comma_boundary_ws`, and `build_comma_list_doc` through `claim_lead_gap` |
// | a rule's pre-`{` gap, its comments included | `spell_gap_items` (`printer/rules.rs`) |
// | a pseudo-argument list's lead and its `)` | `build_pseudo_args_doc` (the lead through `claim_lead_gap`) |
// | an `:nth-*()` term's lead, its `)`, both sides of its `of`, the `)` after `S` | `build_pseudo_args_doc`'s `Nth` arm (`S`'s lead through `claim_lead_gap`) |
// | `::part()`'s inter-name gaps | `build_part_idents_doc` |
// | every attribute-selector interior gap, and its tail | `push_attribute_gap` / `push_attribute_tail` |
// | a declaration's property→colon gap | `extract_property_name`, through [`boundary_run_spelling`] |
// | a block child's rebuilt head (declaration / at-rule / comment) | [`Printer::write_head_boundary_ws`] |
// | a block's tail before `}` | [`Printer::write_block_tail_boundary_ws`] |
// | a format-ignored block (or stylesheet) child's head | the frozen slice itself, from the run's first member ([`Printer::frozen_slice_span`]) |
// | a condition prelude's gaps: its head, each side of a part's connector run, its tail | `Printer::condition_gap_side` (`build_condition_query_doc`, `condition_tail_doc`); the head ahead of a container name in `print_condition_query` |
// | a condition part's `not` gap, and an operator run's interior | the parser, spelled into the part's / run's text (`parse_condition_part`, `skip_gap_registering_comments`) |
// | a selector list in parens: a pseudo-argument list, an `@scope` clause, a `selector()` argument | `build_paren_selector_list_inner` (the lead through `claim_lead_gap`) |
// | an `@scope` prelude's structural gaps (head, both sides of `to`, before the terminator), and `@custom-selector`'s head and tail | `Printer::write_prelude_gap` |
// | `@custom-selector`'s name→list gap | `write_custom_selector_prelude` (its list's anchor stands down whatever the gap holds) |
// | an `@import` prelude's gaps between values, and before its `;` | `write_import_gap_comments`, and `Printer::write_prelude_gap` for the tail |
// | a verbatim prelude's two ends (raw, `@namespace`, a condition's verbatim fallback) | the parser, spelled into the prelude's text (`raw::PreludeEnds`) |
//
// A **rule** child needs no head claim: its selector's first compound already claims the same
// run, and a second claim would print it twice.
//
// ⚠️ [`skip_gap_trivia`] belongs to this module for the same reason: a printer scan that
// LOCATES a part by stepping trivia is retracing one of these skips, so its class must be the
// skip's. One that is narrower reports the RUN as the part's start.
//
// ## The document precondition
//
// Every claim above is a per-site question — asked once per selector, per combinator, per
// at-rule, per comment, per selector-list comma and per DECLARATION — and on real code every
// one of them answers "nothing". [`source_holds_boundary_ws`] settles that for the whole
// document ONCE, and [`Printer::holds_boundary_ws`] carries the answer: a source with no
// member of the class anywhere cannot have one inside any gap of it, so the three claim entry
// points ([`Printer::gap_boundary_ws`], [`Printer::preserved_boundary_ws`] and
// [`Printer::gap_may_hold_boundary_ws`], which every other claim routes through) return their
// empty answer before scanning anything. It is a strict SUPERSET of what any claim can read —
// the whole source, not a region — so it can only turn a claim off where the claim would have
// come back empty on its own, and the two claim shapes stay the sole deciders of what the run
// actually is.
//
// ## Where the bytes are the claim
//
// Two readers disagree about a run in a way no spacing can reconcile: to css-syntax-3 the
// run is identifier content and the ASCII space beside it the token separator, to `parseCss`
// the run is the separator. An attribute selector's tail and a declaration's property→colon
// gap are where that bites — `[a=b<NBSP>]` is the value `b` to Svelte and the ident `b<NBSP>`
// to a browser — so those two claims keep the author's bytes: a bare value glued to a run
// stays bare and unquoted, a flag glued to a run stays glued, and ASCII whitespace beside a
// run keeps its presence as one space ([`Printer::push_attribute_tail`],
// [`boundary_run_spelling`]). Re-quoting the value or hoisting the run would emit a selector
// the browser drops where it matched the input.
//
// ## Residue
//
// Known positions that drop the run: the stylesheet's own trailing whitespace (the outermost
// gap has no following construct, and a Svelte `<style>` host trims the island's tail before
// writing it), ratcheted in
// [`tests/css_boundary_whitespace.rs`](../../../../tests/css_boundary_whitespace.rs).
//
// Not a drop, but not the rule either: a media query list's entries are the media reader's
// text, so a run beside a comma or at an end is ENTRY content (kept by `media_query_list`'s
// CSS-only trim), and a comment glued to it is padded at query level like any comment there —
// the reader's cataloged query-level spacing, not a claim's.
use Printer;
use crateCssBlockChild;
use crate;
use Span;
/// The ASCII space a boundary run appended to `before` needs, or `""`.
///
/// The one answer to "would this run glue backwards?", asked of the text a claim is about to
/// extend — the [`Edge::Space`] question of this module's §How a claim is spelled: a run
/// against a `,`, a `{`, a `)`, a quote or a combinator symbol can only re-parse as the run
/// the parser skipped, while one against a NAME becomes part of it — so the test is exactly
/// the lexer's identifier-continuation predicate, read backwards over the last character.
///
/// ⚠️ The lexer's, not a re-spelling: `IDENT_CONTINUE_LUT` for ASCII and
/// `is_non_ascii_identifier_codepoint` above it, which is what `read_identifier` will do to
/// this seam on the next parse. A narrower class here silently re-opens the glue on whatever
/// it stops short of.
pub
/// A whitespace-only gap's boundary run, spelled with the author's ASCII whitespace
/// PRESENCE kept on its left and dropped on its right: [`spell_run`] with
/// [`Edge::Presence`] / [`Edge::Flush`]. Empty when the gap holds no member — which is every
/// gap in every real stylesheet — so a caller can append it unconditionally.
///
/// For a gap the printer REBUILDS between two parts whose separator it regenerates — a
/// declaration's property→colon gap, ahead of each comment in it and ahead of the colon —
/// where the run has to be put back and the ASCII beside it is not merely spelling: to
/// css-syntax-3 the run is identifier content and the space the token separator, so
/// `color<NBSP>:` is one ident and `color <NBSP>:` an ident and a stray token, while
/// `parseCss` reads the property `color` in both. Keeping the presence as one space is what
/// lets the output tokenize as the input did under both readers; the space AFTER the run is
/// the regenerated separator's to emit, which is why the trailing one is dropped.
pub
/// The byte offset of the CLOSING delimiter a bracketed construct's `span` ends one past —
/// a pseudo-argument list's `)`, an attribute selector's `]`, a block's `}`.
///
/// Named because five readers want it and the `- 1` is not self-evident at any of them: the
/// gap those constructs skipped a boundary run in stops a byte short of `span.end`, and an
/// off-by-one there either swallows the closer into the run or misses the last member. It
/// saturates so a raw subtraction — a shape that is unreachable today
/// (something always precedes the closer) and is a panic rather than a wrong answer the day
/// it isn't.
pub
/// Whether `source` holds a member of the boundary class ANYWHERE — the document-level
/// precondition named in this module's §The document precondition, asked once per
/// [`Printer`] and read by every claim.
///
/// Whole-source and unbounded on purpose. A bound would have to be argued against every claim
/// site's reach — including [`Printer::write_head_boundary_ws`], whose scan is unfloored and
/// walks *backwards* out of the first node — and an argument that is wrong anywhere silently
/// deletes the author's bytes, which is the failure mode this whole module exists to prevent.
/// Answering over more source than any claim can read is the direction that cannot be wrong:
/// it can only decline the fast path, never take it wrongly.
///
/// The scan is the class's own definition read as a byte property: every member is at or above
/// U+00A0 (`is_boundary_only_whitespace` is JS `\s` MINUS its ASCII members), so a member's
/// UTF-8 encoding always has the high bit set, and only the bytes that do need decoding at
/// all. [`next_non_ascii`] steps the ASCII a 32-byte block per branch and the decode runs on
/// what it lands on, so a stylesheet whose only non-ASCII characters are content (an arrow in
/// a `content:` string, an emoji in a comment) pays one word-wise pass and a handful of
/// decodes.
pub
/// Index of the first byte at or after `from` with its high bit set, or `bytes.len()`.
///
/// Word-at-a-time for the same reason `tsv_lang::printing`'s line-terminator candidate scan
/// is: the needle is sparse (a real stylesheet holds a handful of non-ASCII bytes in a
/// hundred kilobytes), so a per-byte compare spends all of its work confirming misses. "Is
/// any lane non-ASCII" is a bit test rather than an equality, so four words fold into one
/// branch — and where that branch fires the byte loop below locates the hit, which costs at
/// most one block and happens at most once per non-ASCII CHARACTER in the document.
/// Whether `c` continues a CSS identifier — the single predicate behind both askers.
/// The first position in `[from, to)` that begins the region's next real token — the
/// [`crate::comments::skip_trivia_forward`] scan in the printer's `u32` span
/// coordinates.
///
/// Two constructs are rebuilt from parts the printer has no span for, and both locate them by
/// stepping the trivia ahead: the attribute selector (the `|`, the matcher, the case flag)
/// and a condition prelude's part (whose own span opens on whatever token the lexer produced,
/// so its content may start past it). Comment-aware by construction, so no scan can read a
/// comment's content as structure.
///
/// ⚠️ **Boundary whitespace is trivia to this scan**, because it is trivia to the parser that
/// produced the spans it navigates between: every gap here is one the attribute selector's
/// `skip_boundary_whitespace_registering_comments` stepped a run at. A scan whose class is
/// narrower than the skip it retraces stops ON the run and reports it as the next part's
/// start — which put the case flag's separator on the wrong side of the run (emitting
/// `[a='b' i<NBSP>]`, which tsv's own parser then rejects) and, worse, made
/// `m_start + text.len()` an offset INSIDE a multi-byte character, where the next slice
/// panicked. Same rule as the parser's lookaheads: a class narrower than the skip it predicts
/// is the same defect as one wider than the lexer's.
pub