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
//! MDX segmentation, rendering, diagnostics, and semantic events.
//!
//! MDX combines Markdown with JSX/JavaScript. Instead of parsing the full MDX
//! syntax, this module splits the input into typed blocks. Only the Markdown
//! segments need to go through ferromark's Markdown parser; JSX, expressions,
//! and ESM statements are passed through unchanged.
//!
//! This module is gated behind the `mdx` Cargo feature.
//!
//! # Example
//! ```
//! use ferromark::mdx::{segment, Segment};
//!
//! let input = "import A from 'a'\n\n# Hello\n\n<Card>\nWorld\n</Card>\n";
//! let segments = segment(input);
//! for seg in &segments {
//! match seg {
//! Segment::Markdown(md) => {
//! // Parse with ferromark::to_html(md)
//! }
//! _ => {
//! // Pass through unchanged
//! }
//! }
//! }
//! ```
//!
//! Compiler consumers that need Markdown block boundaries and resolved inline
//! semantics can use the separate, collected event stream:
//!
//! ```
//! use ferromark::InlineEvent;
//! use ferromark::mdx::{MdxEvent, parse_events};
//!
//! let input = "# Hello {name}\n";
//! let stream = parse_events(input);
//! let translatable = stream.events.iter().filter_map(|event| match event {
//! MdxEvent::Inline(InlineEvent::Text(range)) => {
//! Some(range.slice_str(input.as_bytes()).unwrap())
//! }
//! _ => None,
//! }).collect::<Vec<_>>();
//! assert_eq!(translatable, vec!["Hello "]);
//! ```
//!
//! [`parse_events`] is opt-in and does not participate in the default HTML
//! rendering path. Its flat ordering and balancing contract is versioned by
//! [`MDX_EVENT_STREAM_VERSION`].
//!
//! # Differences from the official mdxjs compiler
//!
//! This segmenter covers the block-level MDX patterns used in real-world
//! documentation (Next.js, Docusaurus, Astro). It intentionally does **not**
//! replicate the full `@mdx-js/mdx` compiler. The differences:
//!
//! ## Block-level segmentation
//!
//! [`segment`] detects JSX and expressions at block level (start of a line).
//! Inline JSX (`paragraph with <em>JSX</em> inside`) and inline expressions
//! (`text {variable} here`) stay inside Markdown segments and are **not** split
//! out. For consumers that need typed inline constructs, the opt-in
//! [`crate::InlineParser::parse_mdx`] method emits source-ranged MDX inline
//! events while preserving the surrounding Markdown events. The official mdxjs
//! compiler handles both flow and text positions in a single parse.
//!
//! ## No JavaScript validation
//!
//! Official mdxjs pipes ESM and expressions through acorn (or swc) to validate
//! the JavaScript syntax. We use heuristics: `import`/`export` at column 0,
//! brace-depth counting for expressions. This means:
//! - We won't reject syntactically invalid JS (e.g. `export const = ;`)
//! - Multi-line ESM uses blank-line termination, not parser-driven boundaries
//! - Exotic edge cases (e.g. `export var a = 1\nvar b`) may be grouped differently
//!
//! ## No Markdown syntax modifications
//!
//! Official mdxjs alters the Markdown grammar:
//! - **Indented code blocks disabled** — 4-space indented lines are paragraphs
//! - **HTML (flow + inline) disabled** — `<div>` is always JSX, never raw HTML
//! - **Autolinks disabled** — `<https://...>` is JSX, not an autolink
//!
//! We leave the Markdown parser untouched. Markdown segments are parsed with
//! standard CommonMark/GFM rules. This is a deliberate trade-off: it keeps
//! ferromark's core parser unmodified and lets the caller decide how to handle
//! HTML-like syntax inside Markdown segments.
//!
//! ## No container awareness
//!
//! Flow JSX/ESM inside block containers is not detected:
//! ```text
//! > <Component> ← treated as blockquote + markdown, not JSX
//! - import x ← treated as list item, not ESM
//! ```
//!
//! The official compiler tracks container context (blockquote markers, list
//! indentation) and can detect JSX/ESM inside them. [`parse_events`] promotes a
//! container paragraph containing only one well-delimited tag or expression to
//! a flow event while preserving the surrounding Markdown container events.
//! Mixed prose keeps its inline MDX events. Multiline constructs split across
//! repeated container prefixes and container-local ESM remain Markdown
//! recovery.
//!
//! ## No TypeScript generics in JSX
//!
//! `<Component<T>>` with TypeScript generics is not supported by the tag
//! parser. The official compiler (when configured with acorn-jsx + TypeScript)
//! handles this.
//!
//! ## Silent fallback instead of errors
//!
//! [`segment`] and [`segment_spanned`] preserve the original permissive
//! behavior: invalid JSX or unterminated expressions are treated as Markdown.
//! [`segment_strict`] is an opt-in validation pass that returns structural MDX
//! diagnostics with source ranges instead. It does not validate JavaScript or
//! TypeScript syntax inside otherwise well-delimited ESM and expressions.
pub use ;
/// A typed segment of an MDX document.
///
/// All variants are zero-copy `&str` slices into the original input.
/// A typed MDX segment together with its exact byte range in the input.
///
/// The range covers precisely the bytes in [`Self::segment`], including
/// delimiters, indentation, and a trailing line ending when the segmenter
/// includes one. The returned ranges are ordered, contiguous, and cover the
/// complete input without gaps or overlap.
///
/// Like [`Segment`], this type borrows from the input and performs no copying.
/// A stable category for a structural MDX diagnostic.
/// A structural MDX diagnostic returned by [`segment_strict`].
///
/// `primary_range` is always a valid UTF-8 byte range into the original input.
/// For a mismatched JSX closing tag, `related_range` identifies the innermost
/// opening tag that the closing tag cannot close past.
/// A one-based source location derived from a UTF-8 byte offset.
/// Segment an MDX document into typed blocks.
///
/// This is the primary entry point. The returned segments cover the entire
/// input — no bytes are dropped.
/// Segment an MDX document and retain exact byte ranges for each segment.
///
/// This is the source-location-aware counterpart to [`segment`]. It has the
/// same segmentation semantics, while each result records its position in the
/// original UTF-8 input. The range includes every byte represented by the
/// segment, including MDX delimiters and any trailing newline owned by that
/// segment.
///
/// # Panics
///
/// Panics when `input` is larger than [`u32::MAX`] bytes, matching the size
/// limit of [`crate::Range`].
/// Validate structural MDX and return source-spanned segments on success.
///
/// This opt-in API adds diagnostics for malformed flow expressions, malformed
/// JSX tags, JSX tag nesting, and ESM blocks at invalid boundaries. The
/// permissive [`segment`] APIs deliberately retain their silent Markdown
/// fallback. JavaScript and TypeScript inside a correctly delimited expression
/// or ESM block are not parsed or type-checked.
///
/// When a malformed construct makes later segmentation ambiguous, validation
/// stops at that construct. Otherwise, independent diagnostics are collected.
///
/// # Panics
///
/// Panics when `input` is larger than [`u32::MAX`] bytes, matching the size
/// limit of [`crate::Range`].
/// Translate a UTF-8 byte offset into a one-based line and Unicode scalar column.
///
/// `byte_offset` must be at a UTF-8 character boundary and may equal
/// `input.len()`. Diagnostic range boundaries returned by [`segment_strict`]
/// always meet that requirement.
///
/// # Panics
///
/// Panics when `byte_offset` is greater than `input.len()` or is not a UTF-8
/// character boundary.
pub use ;