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
//! Plumbing for the sibling tsv crates — the Svelte embedding (`tsv_svelte`), the
//! bindings' shared format path (`tsv_arena`), and the dev tooling. Not public API:
//! nothing here is covered by semver, and any item may change or go away in any release.
use *;
use crateCssStyleSheet;
/// [`parse`] over a document the format path folded
/// (`tsv_lang::printing::normalize_carriage_returns`) — the parse every CSS format entry
/// point runs (the CLI, the bindings' `parse_format!`, [`format()`]), its error reported in
/// the caller's own coordinates (`FoldedSource::parse_with`). The fold keeps meaning
/// everywhere in CSS, whose syntax normalizes a `<CR>` to `<LF>` itself (css-syntax-3
/// §Preprocessing the input stream), so this is the folded parse alone;
/// `tsv_svelte::__private::parse_folded` is the sibling that is not.
/// Parse embedded CSS source into internal AST
///
/// Use this when parsing CSS embedded in another language (e.g., Svelte `<style>` tags)
/// where span positions need to reflect the offset in the parent file.
///
/// # Arguments
/// * `source` - CSS source code
/// * `base_offset` - Offset in parent file (for error reporting and span calculation)
/// * `host_nesting` - The nesting the host document has spent around the stylesheet, in
/// wire levels: the parse counts its own on top of it against `tsv_lang::NESTING_LIMIT`
///
/// # Returns
/// * `Ok(CssStyleSheet)` - Parsed AST with nodes and value comments
/// * `Err(Error)` - Parse error positioned in the parent file
///
/// `arena` owns the returned AST's nodes (shared with the host document's arena
/// when CSS is embedded in a Svelte `<style>`).
///
/// # Errors
///
/// An error is positioned in the parent file (`base_offset` included) but carries no
/// point — [`Error::wire_point`] is `None` — until the host fills its context over
/// the whole document ([`Error::with_context`], as `tsv_svelte::__private::parse` does): a
/// context taken over `source`, a slice, would read that position against the wrong text.
/// [`format_ast_in`] over a document the caller folded ahead of the parse
/// (`tsv_lang::printing::normalize_carriage_returns`) — the format entry points that fold
/// (the CLI, the bindings, [`format()`]). Identical output; the document's line verdict
/// comes from the fold's own pass instead of a second walk of the source.
/// Format an embedded CSS stylesheet into a caller-provided doc arena.
///
/// `embed.base_indent_offset` seeds the indent so wrapped lines respect the host's
/// indentation, and `source` is the whole host document (spans are absolute), so
/// `line_table` is the host's whole-source table — never island-local.
///
/// The doc IR is built into `arena` rather than a freshly allocated one, so a Svelte host
/// formatting a `<style>` block shares its own document arena instead of allocating a
/// second whole-host-sized `DocArena` per block. Nothing borrowed from `arena` escapes (the
/// embedded CSS renders to an owned `String`), and the arena is **not** reset, so the
/// host's in-flight doc nodes stay valid across the call.
///
/// `host_scan` is [`HostBoundaryScan::of`] over the same `source`, taken once per host and
/// handed to every island in it.
///
/// There is no fresh-arena twin: an embedded stylesheet always has a host arena to lend,
/// and the Svelte host is the only caller.
/// A host document's answer to the CSS printer's boundary-whitespace precondition — whether
/// the whole source holds a member of the class anywhere (the CSS printer's
/// §The document precondition, in `printer/boundary_ws.rs`).
///
/// One answer per host, not per island: every `<style>` island of a Svelte component is
/// spanned against the same host source, so the host takes it once ([`Self::of`]) and hands
/// it to each [`format_embedded_in`] call instead of paying a whole-source scan per island.
/// Opaque, so the only way to hold one is to have scanned a source.
pub use crate;
/// Format a CSS stylesheet to a formatted string — the hidden, AST-taking primitive beneath
/// [`format()`], the public one-shot (parse and format a `&str`). Unlike [`format()`] it does no fold.
///
/// `source` must hold **no carriage return**: the printers split lines on `\n` alone, so a
/// `\r` would reach the output verbatim and the same document could format two ways on two
/// passes. Parse a CR-folded text (`tsv_lang::printing::normalize_carriage_returns`, via
/// `parse_folded`) and format it with `format_folded_in`, or call [`format()`], which
/// does both. Debug builds assert it.
///
/// # Arguments
/// * `stylesheet` - CSS stylesheet (nodes + value comments)
/// * `source` - Original CSS source code (for blank line preservation)
///
/// # Returns
/// * Formatted CSS string
///
/// # Example
/// ```
/// use tsv_css::__private::{format_ast, parse};
///
/// let css = "div{color:red;}";
/// let arena = bumpalo::Bump::new();
/// let stylesheet = parse(css, &arena).expect("Failed to parse CSS");
/// let formatted = format_ast(&stylesheet, css);
/// assert_eq!(formatted, "div {\n\tcolor: red;\n}\n");
/// ```
/// Format into a caller-provided doc arena.
///
/// The same precondition on `source` as [`fn@format_ast`]: no carriage return.
///
/// Identical output to [`fn@format_ast`], but the doc IR is built into `arena` instead
/// of a freshly allocated one, so a driver that formats many files can reuse one
/// arena across them (`arena.reset()` between files retains the buffers). Nothing
/// borrowed from `arena` escapes — the result is an owned `String`.
/// Parse CSS source into internal AST
///
/// # Arguments
/// * `source` - CSS source code
///
/// # Returns
/// * `Ok(CssStyleSheet)` - Parsed AST with nodes and value comments
/// * `Err(Error)` - Parse error with position and context
///
/// # Example
/// ```
/// use tsv_css::__private::parse;
///
/// let css = "div { color: red; }";
/// let arena = bumpalo::Bump::new();
/// let stylesheet = parse(css, &arena).expect("Failed to parse CSS");
/// ```
///
/// `arena` owns every AST node of the returned [`CssStyleSheet`]
/// (caller-owns-`Bump`); the stylesheet borrows from it for `'arena`.
/// Convert CSS AST to compact JSON wire bytes — the **sole emission path** for its wire
///
/// The span-only wire every binding ships, and `tsv parse`'s default: a standalone
/// `StyleSheetFile` matching Svelte's `parseCss()` JSON shape exactly (no `attributes` or
/// `content` fields, `end` set to the full source length), `start`/`end` offsets with no
/// `loc`, and no line table built at all. The writer (`ast/convert/write.rs`) walks the
/// internal AST once and emits the wire JSON directly, never materializing a typed public
/// tree, fusing the byte→char offset translation into the walk (each position through a
/// byte→UTF-16 map; identity on ASCII). The bytes are valid UTF-8 by construction (source
/// slices + ASCII fragments), and byte-oriented consumers skip the O(output) validation a
/// `String` requires.
/// Convert internal AST to compact JSON wire bytes **with** per-node `loc`.
///
/// The `convert_ast_json_bytes` wire plus a `loc` on every object carrying `start`/`end` —
/// `parseCss` emits none; tsv's is the line (1-based, LF-only) and column (0-based, UTF-16
/// code units) of those offsets. `tsv parse --locations` writes it.
/// The `String` form of `convert_ast_json_bytes` for `&str` boundaries (the WASM
/// binding's `JSON.parse`, N-API strings): same wire bytes plus one UTF-8 validation of
/// the output. Byte-oriented consumers should prefer the bytes variant.
///
/// There is no `_with_locations` `String` twin: the bindings this form serves ship the
/// span-only wire alone, and `parse_to_json_with` validates the loc-bearing bytes itself.