@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
@@ -7,7 +7,13 @@
7
7
  * @module
8
8
  */
9
9
 
10
- import type {MdzNode, MdzNodeText, MdzNodeComponent, MdzNodeElement, MdzTableAlign} from './mdz.ts';
10
+ import type {
11
+ MdzNode,
12
+ MdzNodeText,
13
+ MdzNodeComponent,
14
+ MdzNodeElement,
15
+ MdzTableAlign
16
+ } from './mdz.ts';
11
17
 
12
18
  // Character codes for performance
13
19
  /** @nodocs */
@@ -99,6 +105,23 @@ export const MIN_CODEBLOCK_BACKTICKS = 3; // Code blocks require minimum 3 backt
99
105
  export const MAX_LIST_NUMBER_DIGITS = 9; // Ordered list markers cap at 9 digits (CommonMark's cap)
100
106
  /** @nodocs */
101
107
  export const MAX_HEADING_LEVEL = 6; // Headings support levels 1-6
108
+ /**
109
+ * Cap on inline link/tag nesting depth. Reached at this many open
110
+ * `Link`/`Element`/`Component` containers, a further `[` or `<tag>` opener
111
+ * renders as literal text in both parsers rather than opening — a strict
112
+ * untrusted-content dialect's bound on pathological nesting (CommonMark
113
+ * permits such a limit). It bounds both the sync lexer's recursion depth (no
114
+ * stack overflow) and, with the failure memo, its revert re-scan work (linear
115
+ * instead of quadratic) on adversarial nested `[`/`<`. Deliberately far above
116
+ * any real content (inline nesting rarely exceeds a handful); only pathological
117
+ * input reaches it. Mirrored in the streaming parser so the two agree on where
118
+ * the cap bites. Delimiters (`**`/`_`/`~~`/`` ` ``) are uncounted: first-closer-wins
119
+ * keeps them from nesting deeply on their own, and mixed delimiter/link chains
120
+ * only deep-nest through the links, which this bounds.
121
+ *
122
+ * @nodocs
123
+ */
124
+ export const MAX_INLINE_NESTING_DEPTH = 100;
102
125
  /** @nodocs */
103
126
  export const HTTPS_PREFIX_LENGTH = 8; // Length of "https://"
104
127
  /** @nodocs */
@@ -180,7 +203,7 @@ export interface MdzBlockquotePrefix {
180
203
  export const mdz_match_blockquote_prefix = (
181
204
  text: string,
182
205
  i: number,
183
- forced: boolean,
206
+ forced: boolean
184
207
  ): MdzBlockquotePrefix | 'pending' | null => {
185
208
  let depth = 0;
186
209
  let pos = i;
@@ -190,23 +213,23 @@ export const mdz_match_blockquote_prefix = (
190
213
  depth++;
191
214
  pos++;
192
215
  if (pos >= text.length) {
193
- return forced ? {depth, content_start: pos} : 'pending';
216
+ return forced ? { depth, content_start: pos } : 'pending';
194
217
  }
195
218
  const c = text.charCodeAt(pos);
196
219
  if (c === RIGHT_ANGLE) continue; // tight run
197
220
  if (c === SPACE) {
198
221
  if (pos + 1 >= text.length) {
199
222
  // could be a separator (next chunk starts with `>`) or the terminator
200
- return forced ? {depth, content_start: pos + 1} : 'pending';
223
+ return forced ? { depth, content_start: pos + 1 } : 'pending';
201
224
  }
202
225
  if (text.charCodeAt(pos + 1) === RIGHT_ANGLE) {
203
226
  pos++; // separator — continue the run at the next `>`
204
227
  continue;
205
228
  }
206
- return {depth, content_start: pos + 1}; // terminating space
229
+ return { depth, content_start: pos + 1 }; // terminating space
207
230
  }
208
231
  if (c === NEWLINE) {
209
- return {depth, content_start: pos}; // bare prefix line
232
+ return { depth, content_start: pos }; // bare prefix line
210
233
  }
211
234
  if (is_line_whitespace(c)) {
212
235
  // tab/CR is not the marker space — valid only as tolerated trailing
@@ -216,17 +239,17 @@ export const mdz_match_blockquote_prefix = (
216
239
  j++;
217
240
  }
218
241
  if (j >= text.length) {
219
- return forced ? {depth, content_start: pos} : 'pending';
242
+ return forced ? { depth, content_start: pos } : 'pending';
220
243
  }
221
244
  if (text.charCodeAt(j) === NEWLINE) {
222
- return {depth, content_start: pos};
245
+ return { depth, content_start: pos };
223
246
  }
224
247
  // trailing content after non-space whitespace — fall through to backtrack
225
248
  }
226
249
  // invalid char directly after a `>`: this `>` is content; the separator
227
250
  // space before it (if any) re-serves as the terminator
228
251
  if (depth >= 2 && text.charCodeAt(pos - 2) === SPACE) {
229
- return {depth: depth - 1, content_start: pos - 1};
252
+ return { depth: depth - 1, content_start: pos - 1 };
230
253
  }
231
254
  return null;
232
255
  }
@@ -243,7 +266,7 @@ export const mdz_match_blockquote_prefix = (
243
266
  export const mdz_blockquote_line_has_content = (
244
267
  text: string,
245
268
  content_start: number,
246
- forced: boolean,
269
+ forced: boolean
247
270
  ): boolean | 'pending' => {
248
271
  let i = content_start;
249
272
  while (i < text.length && is_line_whitespace(text.charCodeAt(i))) {
@@ -274,8 +297,8 @@ export const mdz_blockquote_strip_width = (text: string, i: number): number =>
274
297
  * @nodocs
275
298
  */
276
299
  export const mdz_remap_segments = (
277
- segments: Array<{inner_start: number; source_start: number}>,
278
- p: number,
300
+ segments: Array<{ inner_start: number; source_start: number }>,
301
+ p: number
279
302
  ): number => {
280
303
  let lo = 0;
281
304
  let hi = segments.length - 1;
@@ -297,13 +320,13 @@ export const mdz_remap_segments = (
297
320
  const trim_table_cell = (
298
321
  text: string,
299
322
  start: number,
300
- end: number,
301
- ): {start: number; end: number} => {
323
+ end: number
324
+ ): { start: number; end: number } => {
302
325
  let s = start;
303
326
  let e = end;
304
327
  while (s < e && is_line_whitespace(text.charCodeAt(s))) s++;
305
328
  while (e > s && is_line_whitespace(text.charCodeAt(e - 1))) e--;
306
- return {start: s, end: e};
329
+ return { start: s, end: e };
307
330
  };
308
331
 
309
332
  /**
@@ -326,10 +349,10 @@ const trim_table_cell = (
326
349
  export const mdz_split_table_row = (
327
350
  text: string,
328
351
  line_start: number,
329
- line_end: number,
330
- ): Array<{start: number; end: number}> | null => {
352
+ line_end: number
353
+ ): Array<{ start: number; end: number }> | null => {
331
354
  if (line_start >= line_end || text.charCodeAt(line_start) !== PIPE) return null;
332
- const cells: Array<{start: number; end: number}> = [];
355
+ const cells: Array<{ start: number; end: number }> = [];
333
356
  let i = line_start + 1; // past the leading pipe
334
357
  let cell_start = i;
335
358
  while (i < line_end) {
@@ -398,7 +421,7 @@ const table_delimiter_align = (text: string, start: number, end: number): MdzTab
398
421
  export const mdz_parse_table_delimiter = (
399
422
  text: string,
400
423
  line_start: number,
401
- line_end: number,
424
+ line_end: number
402
425
  ): Array<MdzTableAlign> | null => {
403
426
  const cells = mdz_split_table_row(text, line_start, line_end);
404
427
  if (cells === null) return null;
@@ -547,6 +570,40 @@ const PATH_CHAR_TABLE: Uint8Array = (() => {
547
570
  export const is_valid_path_char = (char_code: number): boolean =>
548
571
  char_code < 128 && PATH_CHAR_TABLE[char_code] === 1;
549
572
 
573
+ // Character classes for the plain-text run scanners (the hottest loops in
574
+ // both parsers) — one table load per character instead of a comparison chain.
575
+ // A zero class is the common case: the char can't end or interrupt a text run.
576
+
577
+ /** Always ends a text run: `` ` `` `*` `_` `~` `[` `]` `<`. @nodocs */
578
+ export const TEXT_CLASS_STOP = 1;
579
+ /** `\n` — run handling is context-dependent (soft break, paragraph break, block lookahead). @nodocs */
580
+ export const TEXT_CLASS_NEWLINE = 2;
581
+ /** `h`/`H` — potential URL scheme start, needs the prefix probe. @nodocs */
582
+ export const TEXT_CLASS_URL = 4;
583
+ /** `/` and `.` — potential path start, needs the boundary probe. @nodocs */
584
+ export const TEXT_CLASS_PATH = 8;
585
+ /** `\` — a literal-pipe escape candidate inside table cells only. @nodocs */
586
+ export const TEXT_CLASS_BACKSLASH = 16;
587
+
588
+ /** @nodocs */
589
+ export const TEXT_SCAN_CLASS: Uint8Array = (() => {
590
+ const t = new Uint8Array(128);
591
+ t[BACKTICK] = TEXT_CLASS_STOP;
592
+ t[ASTERISK] = TEXT_CLASS_STOP;
593
+ t[UNDERSCORE] = TEXT_CLASS_STOP;
594
+ t[TILDE] = TEXT_CLASS_STOP;
595
+ t[LEFT_BRACKET] = TEXT_CLASS_STOP;
596
+ t[RIGHT_BRACKET] = TEXT_CLASS_STOP;
597
+ t[LEFT_ANGLE] = TEXT_CLASS_STOP;
598
+ t[NEWLINE] = TEXT_CLASS_NEWLINE;
599
+ t[H_LOWER] = TEXT_CLASS_URL;
600
+ t[H_UPPER] = TEXT_CLASS_URL;
601
+ t[SLASH] = TEXT_CLASS_PATH;
602
+ t[PERIOD] = TEXT_CLASS_PATH;
603
+ t[BACKSLASH] = TEXT_CLASS_BACKSLASH;
604
+ return t;
605
+ })();
606
+
550
607
  /**
551
608
  * Trim trailing punctuation from URL/path per RFC 3986 and GFM rules.
552
609
  * - Trims simple trailing: .,;:!?]
@@ -718,7 +775,7 @@ const VOID_ELEMENTS = new Set([
718
775
  'meta',
719
776
  'source',
720
777
  'track',
721
- 'wbr',
778
+ 'wbr'
722
779
  ]);
723
780
 
724
781
  /**
@@ -760,6 +817,52 @@ export const mdz_resolve_relative_path = (reference: string, base: string): stri
760
817
  return segments.join('/');
761
818
  };
762
819
 
820
+ /**
821
+ * How a `Link` node's `reference` should render, shared by all three renderers
822
+ * (`MdzNodeView`, `MdzStreamNodeView`, `mdz_to_svelte`) so the safety gate and
823
+ * the resolve-vs-raw classification can't drift between them.
824
+ * @nodocs
825
+ */
826
+ export type MdzLinkRender =
827
+ /** Unsafe protocol — render children only, no `<a>`. */
828
+ | { kind: 'unsafe' }
829
+ /** Route/relative path — wrap in `resolve()` (needs `$app/paths`). */
830
+ | { kind: 'resolve'; href: string }
831
+ /** Internal fragment/query/relative/bare ref — raw `href`, no `resolve()`. */
832
+ | { kind: 'internal'; href: string }
833
+ /** External link — raw `href` plus `target="_blank" rel="noopener"`. */
834
+ | { kind: 'external'; href: string };
835
+
836
+ /**
837
+ * Classify a `Link` reference into how it should render. Pure — no rendering,
838
+ * no context; `base` is the resolved base path (or `undefined`).
839
+ * @nodocs
840
+ */
841
+ export const mdz_classify_link = (
842
+ reference: string,
843
+ link_type: 'internal' | 'external' | undefined,
844
+ base: string | undefined
845
+ ): MdzLinkRender => {
846
+ if (!mdz_is_safe_reference(reference)) return { kind: 'unsafe' };
847
+ if (link_type === 'internal') {
848
+ if (reference.startsWith('.') && base) {
849
+ return { kind: 'resolve', href: mdz_resolve_relative_path(reference, base) };
850
+ }
851
+ // fragment/query/relative/bare — `resolve()` only accepts absolute paths or
852
+ // route ids and throws on anything else
853
+ if (
854
+ reference.startsWith('#') ||
855
+ reference.startsWith('?') ||
856
+ reference.startsWith('.') ||
857
+ !reference.startsWith('/')
858
+ ) {
859
+ return { kind: 'internal', href: reference };
860
+ }
861
+ return { kind: 'resolve', href: reference };
862
+ }
863
+ return { kind: 'external', href: reference };
864
+ };
865
+
763
866
  /**
764
867
  * Push a node into a children array, coalescing with the previous Text node.
765
868
  * Mutates `prev.content` and `prev.end` when both are Text, avoiding array growth
@@ -804,10 +907,10 @@ export const mdz_merge_adjacent_text = (nodes: Array<MdzNode>): Array<MdzNode> =
804
907
  type: 'Text',
805
908
  content: pending.content + node.content,
806
909
  start: pending.start,
807
- end: node.end,
910
+ end: node.end
808
911
  };
809
912
  } else {
810
- pending = {...node};
913
+ pending = { ...node };
811
914
  }
812
915
  } else {
813
916
  if (pending) {
@@ -830,7 +933,7 @@ export const mdz_merge_adjacent_text = (nodes: Array<MdzNode>): Array<MdzNode> =
830
933
  * or `null` if the content should be wrapped in a paragraph
831
934
  */
832
935
  export const mdz_extract_single_tag = (
833
- nodes: Array<MdzNode>,
936
+ nodes: Array<MdzNode>
834
937
  ): MdzNodeComponent | MdzNodeElement | null => {
835
938
  let tag: MdzNodeComponent | MdzNodeElement | null = null;
836
939