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
//! Plumbing for the sibling tsv crates — 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 *;
/// The Svelte-shaped HTML answers `tsv_svelte_compile` and `tsv_debug` read — the crate's own
/// `html` module, where each item's doc says where it departs from the spec.
pub use crate;
/// Parse a document the format path folded (`tsv_lang::printing::normalize_carriage_returns`)
/// — the parse every Svelte format entry point runs: the CLI, the bindings' `parse_format!`
/// and [`format()`]. A parse error is reported in the caller's own coordinates
/// (`FoldedSource::parse_with`).
///
/// **The one place the fold could change meaning, refused.** Svelte's template reader ends
/// an in-tag `//` comment (`<div // c⏎class="x">`) at the next `\n` alone, so a lone `<CR>`
/// inside one is comment text — and folding it to `<LF>` would end the comment there, turning
/// the rest of its line into markup (a real `class` attribute above, or a parse of a document
/// whose own bytes do not parse). A `<CR>` with only whitespace after it in the comment (a
/// doubled `<CR><CR><LF>` ending) ends it where the fold does, and is let through. Everywhere else the fold keeps meaning (acorn ends a line at
/// a `<CR>` too; HTML normalizes it in text and attribute values; a block comment only changes
/// its own bytes). So when the fold rewrote a lone `<CR>`
/// (`FoldedSource::folded_lone_cr`), the author's own bytes are parsed first: their parse
/// error is this one (exactly what `parse` reports for them), and an in-tag `//` comment
/// holding a lone `<CR>` is refused ([`Error::refusal`], at the `<CR>` but no syntax
/// error — the document is valid Svelte). A `<CR><LF>` is one line break in both readings,
/// and a document the fold rewrote no lone `<CR>` in pays nothing.
/// [`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 a Svelte AST back to source code — the hidden, AST-taking primitive beneath [`format()`], which
/// is 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
///
/// * `root` - The Svelte AST to format
/// * `source` - The original source code (for blank line preservation and escape sequences)
///
/// # Returns
///
/// The formatted Svelte source code as a String
///
/// # Example
///
/// ```
/// # fn main() -> tsv_svelte::Result<()> {
/// let source = "<div>Hello</div>";
/// let arena = bumpalo::Bump::new();
/// let ast = tsv_svelte::__private::parse(source, &arena)?;
/// let formatted = tsv_svelte::__private::format_ast(&ast, source);
/// # Ok(())
/// # }
/// ```
/// 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`. Embedded
/// `<style>` blocks share this same arena (the CSS renders to its own string but
/// builds its doc nodes into the host arena, not a second per-block one).
/// Parse Svelte source code into an internal AST
///
/// # Arguments
///
/// * `source` - The Svelte component source code to parse
/// * `arena` - The bump arena that owns the parsed graph (the template AST plus
/// the embedded TS `<script>`/`{expr}` ASTs, which share this one `Bump`); the
/// returned `Root<'arena>` borrows from it (caller-owns-`Bump`).
///
/// # Returns
///
/// * `Ok(Root)` - The parsed AST
/// * `Err(Error)` - If parsing fails
///
/// # Example
///
/// ```
/// # fn main() -> tsv_svelte::Result<()> {
/// let arena = bumpalo::Bump::new();
/// let ast = tsv_svelte::__private::parse("<div>Hello</div>", &arena)?;
/// # Ok(())
/// # }
/// ```
/// Convert internal AST to compact JSON wire bytes with character-based positions
///
/// The span-only wire every binding ships, and `tsv parse`'s default: the Svelte parser's
/// JSON shape with every line/column object dropped — every `loc` and the `name_loc` on
/// elements/attributes/directives — keeping only `start`/`end` offsets. All are derivable
/// from those offsets plus the source, so a consumer that has the source loses nothing (a
/// name's exact span is reconstructed from its node's offsets plus the source); nothing
/// queries the line table. Mirrors acorn's `locations: false`.
///
/// The **sole emission path** for its wire: emits the wire JSON directly during a
/// single walk of the *internal* Svelte AST — no typed public tree, no intermediate
/// `Value` for the output. A **writer-mode conversion** (`ast/convert/write.rs`) fuses
/// byte→UTF-16 offset translation into the walk: the whole document — the
/// Svelte spine (elements, blocks, tags, directives, attributes), embedded template
/// expressions and `<script>` content via `tsv_ts`'s embedded writers, `<style>`
/// children via `tsv_css`'s `write_css_children` — emits final char-space positions
/// directly (`start`, `end`). Comment-bearing islands (template expressions
/// with comments, comment-carrying `<script>`s) run acorn's attach **online** off this
/// same emit's node opens and closes (`ast/convert/comment_attachment.rs` declares each
/// island's window), so each node emits its own `leadingComments` / `trailingComments` at
/// its close — no second pass and no per-node map. The bytes are valid UTF-8 by
/// construction (every emitted byte is a source slice or ASCII fragment), and
/// byte-oriented consumers skip the O(output) validation a `String` requires.
///
/// # Example
///
/// ```
/// # fn main() -> tsv_svelte::Result<()> {
/// let source = "<div>Hello</div>";
/// let arena = bumpalo::Bump::new();
/// let ast = tsv_svelte::__private::parse(source, &arena)?;
/// let wire = tsv_svelte::__private::convert_ast_json_bytes(&ast, source);
/// # Ok(())
/// # }
/// ```
/// The `convert_ast_json_bytes` wire plus a `loc` on every object carrying `start`/`end`
/// (a superset of Svelte's, which gives `loc` to acorn-parsed nodes only) and the
/// `name_loc` on elements/attributes/directives — line (1-based) and column (0-based,
/// UTF-16 code units) under `\n` alone, the one line rule for a Svelte document and
/// everything embedded in it. `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.