@fuzdev/mdz 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/README.md +4 -0
  2. package/dist/Mdz.svelte +18 -9
  3. package/dist/Mdz.svelte.d.ts +9 -2
  4. package/dist/Mdz.svelte.d.ts.map +1 -1
  5. package/dist/MdzNodeView.svelte +129 -115
  6. package/dist/MdzNodeView.svelte.d.ts +4 -1
  7. package/dist/MdzNodeView.svelte.d.ts.map +1 -1
  8. package/dist/MdzPrecompiled.svelte +3 -3
  9. package/dist/MdzPrecompiled.svelte.d.ts.map +1 -1
  10. package/dist/MdzRoot.svelte +3 -3
  11. package/dist/MdzRoot.svelte.d.ts.map +1 -1
  12. package/dist/MdzStream.svelte +6 -6
  13. package/dist/MdzStream.svelte.d.ts.map +1 -1
  14. package/dist/MdzStreamNodeView.svelte +129 -118
  15. package/dist/MdzStreamNodeView.svelte.d.ts +4 -1
  16. package/dist/MdzStreamNodeView.svelte.d.ts.map +1 -1
  17. package/dist/context_helpers.js +1 -1
  18. package/dist/mdz.d.ts +6 -0
  19. package/dist/mdz.d.ts.map +1 -1
  20. package/dist/mdz.js +8 -0
  21. package/dist/mdz_contexts.d.ts.map +1 -1
  22. package/dist/mdz_debug_work.d.ts +38 -0
  23. package/dist/mdz_debug_work.d.ts.map +1 -0
  24. package/dist/mdz_debug_work.js +37 -0
  25. package/dist/mdz_helpers.d.ts +61 -0
  26. package/dist/mdz_helpers.d.ts.map +1 -1
  27. package/dist/mdz_helpers.js +74 -2
  28. package/dist/mdz_lexer.d.ts +12 -0
  29. package/dist/mdz_lexer.d.ts.map +1 -1
  30. package/dist/mdz_lexer.js +190 -40
  31. package/dist/mdz_opcodes.d.ts.map +1 -1
  32. package/dist/mdz_opcodes_to_nodes.d.ts.map +1 -1
  33. package/dist/mdz_opcodes_to_nodes.js +22 -17
  34. package/dist/mdz_stream_parser.d.ts.map +1 -1
  35. package/dist/mdz_stream_parser.js +8 -8
  36. package/dist/mdz_stream_parser_block.js +5 -5
  37. package/dist/mdz_stream_parser_inline.d.ts.map +1 -1
  38. package/dist/mdz_stream_parser_inline.js +22 -20
  39. package/dist/mdz_stream_parser_link.d.ts.map +1 -1
  40. package/dist/mdz_stream_parser_link.js +88 -56
  41. package/dist/mdz_stream_parser_list.d.ts.map +1 -1
  42. package/dist/mdz_stream_parser_list.js +23 -9
  43. package/dist/mdz_stream_parser_state.d.ts +29 -0
  44. package/dist/mdz_stream_parser_state.d.ts.map +1 -1
  45. package/dist/mdz_stream_parser_state.js +59 -10
  46. package/dist/mdz_stream_parser_table.d.ts +3 -2
  47. package/dist/mdz_stream_parser_table.d.ts.map +1 -1
  48. package/dist/mdz_stream_parser_table.js +24 -13
  49. package/dist/mdz_stream_parser_text.d.ts.map +1 -1
  50. package/dist/mdz_stream_parser_text.js +36 -25
  51. package/dist/mdz_stream_parser_url.js +7 -7
  52. package/dist/mdz_stream_state.svelte.d.ts.map +1 -1
  53. package/dist/mdz_to_svelte.d.ts +1 -1
  54. package/dist/mdz_to_svelte.d.ts.map +1 -1
  55. package/dist/mdz_to_svelte.js +28 -31
  56. package/dist/mdz_token_parser.d.ts.map +1 -1
  57. package/dist/mdz_token_parser.js +24 -25
  58. package/dist/svelte_preprocess_mdz.d.ts +2 -2
  59. package/dist/svelte_preprocess_mdz.d.ts.map +1 -1
  60. package/dist/svelte_preprocess_mdz.js +41 -47
  61. package/package.json +28 -40
  62. package/src/lib/context_helpers.ts +2 -2
  63. package/src/lib/mdz.ts +11 -2
  64. package/src/lib/mdz_contexts.ts +5 -5
  65. package/src/lib/mdz_debug_work.ts +38 -0
  66. package/src/lib/mdz_helpers.ts +126 -23
  67. package/src/lib/mdz_lexer.ts +243 -93
  68. package/src/lib/mdz_opcodes.ts +1 -1
  69. package/src/lib/mdz_opcodes_to_nodes.ts +35 -30
  70. package/src/lib/mdz_stream_parser.ts +17 -17
  71. package/src/lib/mdz_stream_parser_block.ts +14 -14
  72. package/src/lib/mdz_stream_parser_inline.ts +38 -34
  73. package/src/lib/mdz_stream_parser_link.ts +99 -66
  74. package/src/lib/mdz_stream_parser_list.ts +41 -29
  75. package/src/lib/mdz_stream_parser_state.ts +100 -28
  76. package/src/lib/mdz_stream_parser_table.ts +47 -36
  77. package/src/lib/mdz_stream_parser_text.ts +40 -39
  78. package/src/lib/mdz_stream_parser_url.ts +11 -11
  79. package/src/lib/mdz_stream_state.svelte.ts +8 -8
  80. package/src/lib/mdz_to_svelte.ts +38 -46
  81. package/src/lib/mdz_token_parser.ts +55 -58
  82. package/src/lib/svelte_preprocess_mdz.ts +92 -89
  83. package/src/lib/tsdoc_mdz.ts +1 -1
@@ -23,7 +23,7 @@ import {
23
23
  is_valid_path_char,
24
24
  is_word_char,
25
25
  match_url_prefix_case_insensitive,
26
- mdz_trim_url_punctuation,
26
+ mdz_trim_url_punctuation
27
27
  } from './mdz_helpers.ts';
28
28
  import {
29
29
  type MdzStreamParserState,
@@ -33,7 +33,7 @@ import {
33
33
  emit,
34
34
  ensure_paragraph,
35
35
  flush_text,
36
- offset,
36
+ offset
37
37
  } from './mdz_stream_parser_state.ts';
38
38
 
39
39
  const HTTPS_PREFIX = 'https://';
@@ -136,7 +136,7 @@ export const try_auto_path_relative = (state: MdzStreamParserState, forced = fal
136
136
  */
137
137
  export const start_pending_url = (
138
138
  state: MdzStreamParserState,
139
- link_type: 'external' | 'internal',
139
+ link_type: 'external' | 'internal'
140
140
  ): void => {
141
141
  flush_text(state);
142
142
  ensure_paragraph(state);
@@ -147,7 +147,7 @@ export const start_pending_url = (
147
147
  link_type,
148
148
  confirmed: true,
149
149
  viable_https: false,
150
- viable_http: false,
150
+ viable_http: false
151
151
  };
152
152
  // don't advance pos — URL chars start at current position, handled by process_url_content
153
153
  };
@@ -169,7 +169,7 @@ export const start_speculative_url = (state: MdzStreamParserState): void => {
169
169
  link_type: 'external',
170
170
  confirmed: false,
171
171
  viable_https: true,
172
- viable_http: true,
172
+ viable_http: true
173
173
  };
174
174
  };
175
175
 
@@ -313,7 +313,7 @@ export const complete_pending_url = (state: MdzStreamParserState): void => {
313
313
  start: pu.start,
314
314
  end: url_end,
315
315
  trim_end,
316
- trim_id,
316
+ trim_id
317
317
  });
318
318
  state.active_text_id = trim_id;
319
319
  } else {
@@ -325,7 +325,7 @@ export const complete_pending_url = (state: MdzStreamParserState): void => {
325
325
  reference,
326
326
  link_type: pu.link_type,
327
327
  start: pu.start,
328
- end: url_end,
328
+ end: url_end
329
329
  });
330
330
  state.active_text_id = null;
331
331
  }
@@ -339,7 +339,7 @@ export const complete_pending_url = (state: MdzStreamParserState): void => {
339
339
  */
340
340
  export const scan_auto_link_forced = (
341
341
  state: MdzStreamParserState,
342
- link_type: 'external' | 'internal',
342
+ link_type: 'external' | 'internal'
343
343
  ): TryResult => {
344
344
  let i = state.pos;
345
345
  while (i < state.buffer.length) {
@@ -366,7 +366,7 @@ export const scan_auto_link_forced = (
366
366
  ensure_paragraph(state);
367
367
 
368
368
  const link_id = alloc_id(state);
369
- emit(state, {type: 'open', id: link_id, node_type: 'Link', start: link_start});
369
+ emit(state, { type: 'open', id: link_id, node_type: 'Link', start: link_start });
370
370
  const text_id = alloc_id(state);
371
371
  emit(state, {
372
372
  type: 'text',
@@ -374,9 +374,9 @@ export const scan_auto_link_forced = (
374
374
  content: reference,
375
375
  text_type: 'Text',
376
376
  start: link_start,
377
- end: link_end,
377
+ end: link_end
378
378
  });
379
- emit(state, {type: 'close', id: link_id, end: link_end, reference, link_type});
379
+ emit(state, { type: 'close', id: link_id, end: link_end, reference, link_type });
380
380
 
381
381
  state.active_text_id = null;
382
382
  state.column += end_pos - state.pos;
@@ -8,10 +8,10 @@
8
8
  * @module
9
9
  */
10
10
 
11
- import {DEV} from 'esm-env';
11
+ import { DEV } from 'esm-env';
12
12
 
13
- import type {MdzNodeId, MdzNodeType, MdzOpcode} from './mdz_opcodes.ts';
14
- import type {MdzTableAlign} from './mdz.ts';
13
+ import type { MdzNodeId, MdzNodeType, MdzOpcode } from './mdz_opcodes.ts';
14
+ import type { MdzTableAlign } from './mdz.ts';
15
15
 
16
16
  /**
17
17
  * A reactive node in the stream renderer tree.
@@ -76,7 +76,7 @@ export class MdzStreamState {
76
76
  const parent = this.#nodes.get(this.#stack[stack_idx - 1]!);
77
77
  if (DEV && !parent) {
78
78
  throw new Error(
79
- `MdzStreamState: parent id ${this.#stack[stack_idx - 1]} on stack but missing from nodes`,
79
+ `MdzStreamState: parent id ${this.#stack[stack_idx - 1]} on stack but missing from nodes`
80
80
  );
81
81
  }
82
82
  return parent ? parent.children : this.root;
@@ -99,7 +99,7 @@ export class MdzStreamState {
99
99
  case 'open': {
100
100
  if (DEV && this.#nodes.has(opcode.id)) {
101
101
  throw new Error(
102
- `MdzStreamState: duplicate open for id ${opcode.id} (node_type '${opcode.node_type}')`,
102
+ `MdzStreamState: duplicate open for id ${opcode.id} (node_type '${opcode.node_type}')`
103
103
  );
104
104
  }
105
105
  const node = new MdzStreamNode(opcode.id, opcode.node_type);
@@ -168,7 +168,7 @@ export class MdzStreamState {
168
168
  case 'text': {
169
169
  if (DEV && this.#nodes.has(opcode.id)) {
170
170
  throw new Error(
171
- `MdzStreamState: duplicate text for id ${opcode.id} (text_type '${opcode.text_type}')`,
171
+ `MdzStreamState: duplicate text for id ${opcode.id} (text_type '${opcode.text_type}')`
172
172
  );
173
173
  }
174
174
  const node = new MdzStreamNode(opcode.id, opcode.text_type);
@@ -215,7 +215,7 @@ export class MdzStreamState {
215
215
  case 'void': {
216
216
  if (DEV && this.#nodes.has(opcode.id)) {
217
217
  throw new Error(
218
- `MdzStreamState: duplicate void for id ${opcode.id} (node_type '${opcode.node_type}')`,
218
+ `MdzStreamState: duplicate void for id ${opcode.id} (node_type '${opcode.node_type}')`
219
219
  );
220
220
  }
221
221
  const node = new MdzStreamNode(opcode.id, opcode.node_type);
@@ -255,7 +255,7 @@ export class MdzStreamState {
255
255
  if (node_idx === -1) {
256
256
  if (DEV) {
257
257
  throw new Error(
258
- `MdzStreamState: revert for id ${opcode.id} but node not found in parent's children`,
258
+ `MdzStreamState: revert for id ${opcode.id} but node not found in parent's children`
259
259
  );
260
260
  }
261
261
  break;
@@ -8,12 +8,12 @@
8
8
  * @module
9
9
  */
10
10
 
11
- import {UnreachableError} from '@fuzdev/fuz_util/error.ts';
12
- import {escape_svelte_text} from '@fuzdev/fuz_util/svelte_preprocess_helpers.ts';
13
- import {escape_js_string} from '@fuzdev/fuz_util/string.ts';
11
+ import { UnreachableError } from '@fuzdev/fuz_util/error.ts';
12
+ import { escape_svelte_text } from '@fuzdev/fuz_util/svelte_preprocess_helpers.ts';
13
+ import { escape_js_string } from '@fuzdev/fuz_util/string.ts';
14
14
 
15
- import type {MdzNode, MdzNodeTableRow} from './mdz.ts';
16
- import {mdz_resolve_relative_path, mdz_is_void_element} from './mdz_helpers.ts';
15
+ import type { MdzNode, MdzNodeTableRow } from './mdz.ts';
16
+ import { mdz_is_void_element, mdz_classify_link } from './mdz_helpers.ts';
17
17
 
18
18
  /**
19
19
  * Result of converting `MdzNode` arrays to Svelte markup.
@@ -23,7 +23,7 @@ export interface MdzToSvelteResult {
23
23
  markup: string;
24
24
 
25
25
  /** Required imports: `Map<local_name, {path, kind}>`. */
26
- imports: Map<string, {path: string; kind: 'default' | 'named'}>;
26
+ imports: Map<string, { path: string; kind: 'default' | 'named' }>;
27
27
 
28
28
  /** Whether content references unconfigured Component or Element tags. */
29
29
  has_unconfigured_tags: boolean;
@@ -38,7 +38,7 @@ const component_local_name = (import_path: string): string =>
38
38
  */
39
39
  export interface MdzToSvelteOptions {
40
40
  /**
41
- * Component name to import path mapping (e.g., `{Alert: '$lib/Alert.svelte'}`).
41
+ * Component name to import path mapping (e.g., `{Alert: '#lib/Alert.svelte'}`).
42
42
  * If content references a component not in this map, `has_unconfigured_tags` is set.
43
43
  */
44
44
  components: Record<string, string>;
@@ -85,10 +85,10 @@ export interface MdzToSvelteOptions {
85
85
  */
86
86
  export const mdz_to_svelte = (
87
87
  nodes: Array<MdzNode>,
88
- options: MdzToSvelteOptions,
88
+ options: MdzToSvelteOptions
89
89
  ): MdzToSvelteResult => {
90
- const {components, elements, base, code_component_import, codeblock_component_import} = options;
91
- const imports: Map<string, {path: string; kind: 'default' | 'named'}> = new Map();
90
+ const { components, elements, base, code_component_import, codeblock_component_import } = options;
91
+ const imports: Map<string, { path: string; kind: 'default' | 'named' }> = new Map();
92
92
  let has_unconfigured_tags = false;
93
93
 
94
94
  const render_nodes = (children: Array<MdzNode>): string => {
@@ -105,7 +105,7 @@ export const mdz_to_svelte = (
105
105
  return `<code>${escape_svelte_text(node.content)}</code>`;
106
106
  }
107
107
  const name = component_local_name(code_component_import);
108
- imports.set(name, {path: code_component_import, kind: 'default'});
108
+ imports.set(name, { path: code_component_import, kind: 'default' });
109
109
  return `<${name} reference={'${escape_js_string(node.content)}'} />`;
110
110
  }
111
111
 
@@ -116,7 +116,7 @@ export const mdz_to_svelte = (
116
116
  return `<pre><code>${escape_svelte_text(node.content)}</code></pre>`;
117
117
  }
118
118
  const name = component_local_name(codeblock_component_import);
119
- imports.set(name, {path: codeblock_component_import, kind: 'default'});
119
+ imports.set(name, { path: codeblock_component_import, kind: 'default' });
120
120
  return `<${name} ${lang_attr} content={'${escape_js_string(node.content)}'} />`;
121
121
  }
122
122
 
@@ -131,26 +131,21 @@ export const mdz_to_svelte = (
131
131
 
132
132
  case 'Link': {
133
133
  const children_markup = render_nodes(node.children);
134
- if (node.link_type === 'internal') {
135
- if (node.reference.startsWith('.') && base) {
136
- const resolved = mdz_resolve_relative_path(node.reference, base);
137
- imports.set('resolve', {path: '$app/paths', kind: 'named'});
138
- return `<a href={resolve('${escape_js_string(resolved)}')}>${children_markup}</a>`;
139
- }
140
- if (
141
- node.reference.startsWith('#') ||
142
- node.reference.startsWith('?') ||
143
- node.reference.startsWith('.') ||
144
- // Bare references (e.g. `foo`) aren't absolute — resolve() throws on them
145
- !node.reference.startsWith('/')
146
- ) {
147
- return `<a href={'${escape_js_string(node.reference)}'}>${children_markup}</a>`;
148
- }
149
- imports.set('resolve', {path: '$app/paths', kind: 'named'});
150
- return `<a href={resolve('${escape_js_string(node.reference)}')}>${children_markup}</a>`;
134
+ // safety gate + resolve-vs-raw classification shared with the runtime
135
+ // views via `mdz_classify_link`, so the three renderers can't drift
136
+ // (an unsafe `javascript:`/`data:` ref renders as plain children, no
137
+ // `<a>` — defense in depth, since `mdz_to_svelte` is a public entry
138
+ // callable with hand-built trees that never went through `mdz_parse`)
139
+ const link = mdz_classify_link(node.reference, node.link_type, base);
140
+ if (link.kind === 'unsafe') return children_markup;
141
+ if (link.kind === 'resolve') {
142
+ imports.set('resolve', { path: '$app/paths', kind: 'named' });
143
+ return `<a href={resolve('${escape_js_string(link.href)}')}>${children_markup}</a>`;
144
+ }
145
+ if (link.kind === 'external') {
146
+ return `<a href={'${escape_js_string(link.href)}'} target="_blank" rel="noopener">${children_markup}</a>`;
151
147
  }
152
- // External link — matches MdzNodeView: target="_blank" rel="noopener"
153
- return `<a href={'${escape_js_string(node.reference)}'} target="_blank" rel="noopener">${children_markup}</a>`;
148
+ return `<a href={'${escape_js_string(link.href)}'}>${children_markup}</a>`;
154
149
  }
155
150
 
156
151
  case 'Paragraph':
@@ -172,7 +167,7 @@ export const mdz_to_svelte = (
172
167
  return `<blockquote>${render_nodes(node.children)}</blockquote>`;
173
168
 
174
169
  case 'Table': {
175
- const {align} = node;
170
+ const { align } = node;
176
171
  const render_row = (row: MdzNodeTableRow, tag: 'th' | 'td'): string => {
177
172
  let cells = '';
178
173
  // normalize to the column count: pad short rows, ignore extra cells
@@ -184,19 +179,16 @@ export const mdz_to_svelte = (
184
179
  }
185
180
  return `<tr>${cells}</tr>`;
186
181
  };
182
+ // by grammar the header row is always first (and the only one) —
183
+ // read it by index and skip it in the body, mirroring the node
184
+ // views (a `.filter()` pair re-partitions every row; see
185
+ // `MdzNodeView.svelte`'s "Table rendering de-quadratified" note)
187
186
  let out = '<table>';
188
- const header_rows = node.children.filter((r) => r.header);
189
- const body_rows = node.children.filter((r) => !r.header);
190
- if (header_rows.length > 0) {
191
- out += '<thead>';
192
- for (const row of header_rows) out += render_row(row, 'th');
193
- out += '</thead>';
194
- }
195
- if (body_rows.length > 0) {
196
- out += '<tbody>';
197
- for (const row of body_rows) out += render_row(row, 'td');
198
- out += '</tbody>';
199
- }
187
+ const header_row = node.children[0]?.header ? node.children[0] : undefined;
188
+ if (header_row) out += '<thead>' + render_row(header_row, 'th') + '</thead>';
189
+ let body = '';
190
+ for (const row of node.children) if (!row.header) body += render_row(row, 'td');
191
+ if (body) out += '<tbody>' + body + '</tbody>';
200
192
  return out + '</table>';
201
193
  }
202
194
 
@@ -233,7 +225,7 @@ export const mdz_to_svelte = (
233
225
  has_unconfigured_tags = true;
234
226
  return '';
235
227
  }
236
- imports.set(node.name, {path: import_path, kind: 'default'});
228
+ imports.set(node.name, { path: import_path, kind: 'default' });
237
229
  return `<${node.name}>${render_nodes(node.children)}</${node.name}>`;
238
230
  }
239
231
 
@@ -243,5 +235,5 @@ export const mdz_to_svelte = (
243
235
  };
244
236
 
245
237
  const markup = render_nodes(nodes);
246
- return {markup, imports, has_unconfigured_tags};
238
+ return { markup, imports, has_unconfigured_tags };
247
239
  };
@@ -22,14 +22,14 @@ import type {
22
22
  MdzNodeTableRow,
23
23
  MdzNodeTableCell,
24
24
  MdzNodeElement,
25
- MdzNodeComponent,
25
+ MdzNodeComponent
26
26
  } from './mdz.ts';
27
27
  import {
28
28
  has_non_whitespace,
29
29
  mdz_extract_single_tag,
30
30
  mdz_heading_id,
31
31
  mdz_merge_adjacent_text,
32
- mdz_push_merging_text,
32
+ mdz_push_merging_text
33
33
  } from './mdz_helpers.ts';
34
34
  import {
35
35
  type MdzToken,
@@ -43,9 +43,24 @@ import {
43
43
  type MdzTokenListItemOpen,
44
44
  type MdzTokenTableOpen,
45
45
  type MdzTokenTableRowOpen,
46
- type MdzTokenTableCellOpen,
46
+ type MdzTokenTableCellOpen
47
47
  } from './mdz_lexer.ts';
48
48
 
49
+ /**
50
+ * Whether a token type ends the current inline context — the block-boundary
51
+ * set shared by the delimiter-pair and link children loops (an unclosed
52
+ * inline never swallows block structure).
53
+ */
54
+ const token_is_block_boundary = (type: MdzToken['type']): boolean =>
55
+ type === 'paragraph_break' ||
56
+ type === 'heading_start' ||
57
+ type === 'hr' ||
58
+ type === 'codeblock' ||
59
+ type === 'list_open' ||
60
+ type === 'list_item_close' ||
61
+ type === 'blockquote_open' ||
62
+ type === 'table_open';
63
+
49
64
  /**
50
65
  * Builds an `MdzNode[]` tree from a lexed `MdzToken[]` stream.
51
66
  * Used by `mdz_parse`, which should be preferred for simple usage.
@@ -119,7 +134,7 @@ export class MdzTokenParser {
119
134
  if (token.type === 'hr') {
120
135
  const flushed = this.#flush_paragraph(paragraph_children, true);
121
136
  if (flushed) nodes.push(flushed);
122
- nodes.push({type: 'Hr', start: token.start, end: token.end});
137
+ nodes.push({ type: 'Hr', start: token.start, end: token.end });
123
138
  this.#index++;
124
139
  continue;
125
140
  }
@@ -132,7 +147,7 @@ export class MdzTokenParser {
132
147
  lang: token.lang,
133
148
  content: token.content,
134
149
  start: token.start,
135
- end: token.end,
150
+ end: token.end
136
151
  });
137
152
  this.#index++;
138
153
  continue;
@@ -185,7 +200,7 @@ export class MdzTokenParser {
185
200
  // children-derived end, like List — no-empty-start means children exist,
186
201
  // with the open's position as a defensive fallback
187
202
  const end = children.length > 0 ? children[children.length - 1]!.end : open.end;
188
- return {type: 'Blockquote', children, start: open.start, end};
203
+ return { type: 'Blockquote', children, start: open.start, end };
189
204
  }
190
205
 
191
206
  #parse_table(): MdzNodeTable {
@@ -208,7 +223,7 @@ export class MdzTokenParser {
208
223
  }
209
224
 
210
225
  const end = rows.length > 0 ? rows[rows.length - 1]!.end : open.end;
211
- return {type: 'Table', align: open.align, children: rows, start: open.start, end};
226
+ return { type: 'Table', align: open.align, children: rows, start: open.start, end };
212
227
  }
213
228
 
214
229
  #parse_table_row(): MdzNodeTableRow {
@@ -226,13 +241,13 @@ export class MdzTokenParser {
226
241
  }
227
242
  if (t.type === 'table_row_close') {
228
243
  this.#index++;
229
- return {type: 'TableRow', header, children: cells, start: open.start, end: t.end};
244
+ return { type: 'TableRow', header, children: cells, start: open.start, end: t.end };
230
245
  }
231
246
  this.#index++; // skip anything unexpected (defensive)
232
247
  }
233
248
 
234
249
  const end = cells.length > 0 ? cells[cells.length - 1]!.end : open.end;
235
- return {type: 'TableRow', header, children: cells, start: open.start, end};
250
+ return { type: 'TableRow', header, children: cells, start: open.start, end };
236
251
  }
237
252
 
238
253
  #parse_table_cell(): MdzNodeTableCell {
@@ -246,14 +261,14 @@ export class MdzTokenParser {
246
261
  const t = this.#tokens[this.#index]!;
247
262
  if (t.type === 'table_cell_close') {
248
263
  this.#index++;
249
- return {type: 'TableCell', children, start, end: t.end};
264
+ return { type: 'TableCell', children, start, end: t.end };
250
265
  }
251
266
  const node = this.#parse_inline();
252
267
  if (node) mdz_push_merging_text(children, node);
253
268
  }
254
269
 
255
270
  const end = children.length > 0 ? children[children.length - 1]!.end : open.end;
256
- return {type: 'TableCell', children, start, end};
271
+ return { type: 'TableCell', children, start, end };
257
272
  }
258
273
 
259
274
  #parse_heading(): MdzNodeHeading {
@@ -278,7 +293,7 @@ export class MdzTokenParser {
278
293
  const merged = mdz_merge_adjacent_text(children);
279
294
  const end = merged.length > 0 ? merged[merged.length - 1]!.end : start + level + 1;
280
295
 
281
- return {type: 'Heading', level, id: mdz_heading_id(merged), children: merged, start, end};
296
+ return { type: 'Heading', level, id: mdz_heading_id(merged), children: merged, start, end };
282
297
  }
283
298
 
284
299
  #parse_list(): MdzNodeList {
@@ -308,10 +323,10 @@ export class MdzTokenParser {
308
323
  start_number: children[0]?.number ?? 1,
309
324
  children,
310
325
  start: open.start,
311
- end,
326
+ end
312
327
  };
313
328
  }
314
- return {type: 'List', ordered: false, children, start: open.start, end};
329
+ return { type: 'List', ordered: false, children, start: open.start, end };
315
330
  }
316
331
 
317
332
  #parse_list_item(): MdzNodeListItem {
@@ -361,7 +376,7 @@ export class MdzTokenParser {
361
376
  lang: t.lang,
362
377
  content: t.content,
363
378
  start: t.start,
364
- end: t.end,
379
+ end: t.end
365
380
  });
366
381
  this.#index++;
367
382
  continue;
@@ -385,9 +400,9 @@ export class MdzTokenParser {
385
400
 
386
401
  const end = children.length > 0 ? children[children.length - 1]!.end : open.end;
387
402
  if (open.number !== undefined) {
388
- return {type: 'ListItem', number: open.number, children, start: open.start, end};
403
+ return { type: 'ListItem', number: open.number, children, start: open.start, end };
389
404
  }
390
- return {type: 'ListItem', children, start: open.start, end};
405
+ return { type: 'ListItem', children, start: open.start, end };
391
406
  }
392
407
 
393
408
  #parse_inline(): MdzNode | null {
@@ -398,11 +413,11 @@ export class MdzTokenParser {
398
413
  switch (token.type) {
399
414
  case 'text':
400
415
  this.#index++;
401
- return {type: 'Text', content: token.content, start: token.start, end: token.end};
416
+ return { type: 'Text', content: token.content, start: token.start, end: token.end };
402
417
 
403
418
  case 'code':
404
419
  this.#index++;
405
- return {type: 'Code', content: token.content, start: token.start, end: token.end};
420
+ return { type: 'Code', content: token.content, start: token.start, end: token.end };
406
421
 
407
422
  case 'bold_open':
408
423
  return this.#parse_delimiter_pair('bold_close', 'Bold', '**');
@@ -428,19 +443,19 @@ export class MdzTokenParser {
428
443
  // Orphaned close tokens - treat as text
429
444
  case 'bold_close':
430
445
  this.#index++;
431
- return {type: 'Text', content: '**', start: token.start, end: token.end};
446
+ return { type: 'Text', content: '**', start: token.start, end: token.end };
432
447
 
433
448
  case 'italic_close':
434
449
  this.#index++;
435
- return {type: 'Text', content: '_', start: token.start, end: token.end};
450
+ return { type: 'Text', content: '_', start: token.start, end: token.end };
436
451
 
437
452
  case 'strikethrough_close':
438
453
  this.#index++;
439
- return {type: 'Text', content: '~~', start: token.start, end: token.end};
454
+ return { type: 'Text', content: '~~', start: token.start, end: token.end };
440
455
 
441
456
  case 'link_text_close':
442
457
  this.#index++;
443
- return {type: 'Text', content: ']', start: token.start, end: token.end};
458
+ return { type: 'Text', content: ']', start: token.start, end: token.end };
444
459
 
445
460
  case 'link_ref':
446
461
  this.#index++;
@@ -448,7 +463,7 @@ export class MdzTokenParser {
448
463
  type: 'Text',
449
464
  content: `(${token.reference})`,
450
465
  start: token.start,
451
- end: token.end,
466
+ end: token.end
452
467
  };
453
468
 
454
469
  case 'tag_close':
@@ -457,7 +472,7 @@ export class MdzTokenParser {
457
472
  type: 'Text',
458
473
  content: `</${token.name}>`,
459
474
  start: token.start,
460
- end: token.end,
475
+ end: token.end
461
476
  };
462
477
 
463
478
  default:
@@ -477,7 +492,7 @@ export class MdzTokenParser {
477
492
  #parse_delimiter_pair(
478
493
  close_type: 'bold_close' | 'italic_close' | 'strikethrough_close',
479
494
  node_type: 'Bold' | 'Italic' | 'Strikethrough',
480
- delimiter: '**' | '_' | '~~',
495
+ delimiter: '**' | '_' | '~~'
481
496
  ): MdzNodeBold | MdzNodeItalic | MdzNodeStrikethrough | MdzNodeText {
482
497
  const start = this.#tokens[this.#index]!.start;
483
498
  this.#index++;
@@ -488,18 +503,9 @@ export class MdzTokenParser {
488
503
  const t = this.#tokens[this.#index]!;
489
504
  if (t.type === close_type) {
490
505
  this.#index++;
491
- return {type: node_type, children, start, end: t.end};
506
+ return { type: node_type, children, start, end: t.end };
492
507
  }
493
- if (
494
- t.type === 'paragraph_break' ||
495
- t.type === 'heading_start' ||
496
- t.type === 'hr' ||
497
- t.type === 'codeblock' ||
498
- t.type === 'list_open' ||
499
- t.type === 'list_item_close' ||
500
- t.type === 'blockquote_open' ||
501
- t.type === 'table_open'
502
- ) {
508
+ if (token_is_block_boundary(t.type)) {
503
509
  break;
504
510
  }
505
511
  const node = this.#parse_inline();
@@ -507,7 +513,7 @@ export class MdzTokenParser {
507
513
  }
508
514
 
509
515
  // Unclosed - treat as text
510
- return {type: 'Text', content: delimiter, start, end: start + delimiter.length};
516
+ return { type: 'Text', content: delimiter, start, end: start + delimiter.length };
511
517
  }
512
518
 
513
519
  #parse_link(): MdzNodeLink | MdzNodeText {
@@ -532,30 +538,21 @@ export class MdzTokenParser {
532
538
  children,
533
539
  link_type: ref_token.link_type,
534
540
  start,
535
- end: ref_token.end,
541
+ end: ref_token.end
536
542
  };
537
543
  }
538
544
 
539
545
  // No link_ref - treat as text
540
- return {type: 'Text', content: '[', start, end: start + 1};
546
+ return { type: 'Text', content: '[', start, end: start + 1 };
541
547
  }
542
- if (
543
- t.type === 'paragraph_break' ||
544
- t.type === 'heading_start' ||
545
- t.type === 'hr' ||
546
- t.type === 'codeblock' ||
547
- t.type === 'list_open' ||
548
- t.type === 'list_item_close' ||
549
- t.type === 'blockquote_open' ||
550
- t.type === 'table_open'
551
- ) {
548
+ if (token_is_block_boundary(t.type)) {
552
549
  break;
553
550
  }
554
551
  const node = this.#parse_inline();
555
552
  if (node) mdz_push_merging_text(children, node);
556
553
  }
557
554
 
558
- return {type: 'Text', content: '[', start, end: start + 1};
555
+ return { type: 'Text', content: '[', start, end: start + 1 };
559
556
  }
560
557
 
561
558
  #parse_autolink(): MdzNodeLink {
@@ -564,10 +561,10 @@ export class MdzTokenParser {
564
561
  return {
565
562
  type: 'Link',
566
563
  reference: token.reference,
567
- children: [{type: 'Text', content: token.reference, start: token.start, end: token.end}],
564
+ children: [{ type: 'Text', content: token.reference, start: token.start, end: token.end }],
568
565
  link_type: token.link_type,
569
566
  start: token.start,
570
- end: token.end,
567
+ end: token.end
571
568
  };
572
569
  }
573
570
 
@@ -584,21 +581,21 @@ export class MdzTokenParser {
584
581
  const t = this.#tokens[this.#index]!;
585
582
  if (t.type === 'tag_close' && t.name === tag_name) {
586
583
  this.#index++;
587
- return {type: node_type, name: tag_name, children, start, end: t.end};
584
+ return { type: node_type, name: tag_name, children, start, end: t.end };
588
585
  }
589
586
  const node = this.#parse_inline();
590
587
  if (node) mdz_push_merging_text(children, node);
591
588
  }
592
589
 
593
590
  // Unclosed tag
594
- return {type: 'Text', content: '<', start, end: start + 1};
591
+ return { type: 'Text', content: '<', start, end: start + 1 };
595
592
  }
596
593
 
597
594
  #parse_self_close_tag(): MdzNodeElement | MdzNodeComponent {
598
595
  const token = this.#tokens[this.#index]! as MdzTokenTagSelfClose;
599
596
  const node_type: 'Component' | 'Element' = token.is_component ? 'Component' : 'Element';
600
597
  this.#index++;
601
- return {type: node_type, name: token.name, children: [], start: token.start, end: token.end};
598
+ return { type: node_type, name: token.name, children: [], start: token.start, end: token.end };
602
599
  }
603
600
 
604
601
  // -- Paragraph flushing --
@@ -630,7 +627,7 @@ export class MdzTokenParser {
630
627
  // `has_non_whitespace_content` (Unicode whitespace like NBSP is
631
628
  // content; `.trim()` here would silently drop it)
632
629
  const has_content = paragraph_children.some(
633
- (n) => n.type !== 'Text' || has_non_whitespace(n.content),
630
+ (n) => n.type !== 'Text' || has_non_whitespace(n.content)
634
631
  );
635
632
  if (!has_content) {
636
633
  paragraph_children.length = 0;
@@ -655,7 +652,7 @@ export class MdzTokenParser {
655
652
  type: 'Paragraph',
656
653
  children: merged,
657
654
  start: merged[0]!.start,
658
- end: merged[merged.length - 1]!.end,
655
+ end: merged[merged.length - 1]!.end
659
656
  };
660
657
  }
661
658
  }