@tabnas/markdown 0.4.2 → 0.5.1

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.
package/dist/inline.js ADDED
@@ -0,0 +1,1331 @@
1
+ "use strict";
2
+ /* Copyright (c) 2021-2026 Richard Rodger, MIT License */
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.linkifyAutolinks = linkifyAutolinks;
5
+ exports.parseInlines = parseInlines;
6
+ exports.parseReference = parseReference;
7
+ // Phase 2 of the CommonMark parse (spec 0.31.2, Appendix A, "Phase 2: inline
8
+ // structure"). The block phase left the raw text of every paragraph and
9
+ // heading in `stringContent`; this module turns that text into inline
10
+ // children and clears it.
11
+ //
12
+ // The shape is the spec's own algorithm: one left-to-right scan over the
13
+ // subject that appends nodes as it goes, plus two auxiliary stacks that are
14
+ // resolved out of band, because neither construct can be decided at the
15
+ // point where its opener is seen:
16
+ //
17
+ // * the delimiter stack (§6.4) holds `*`/`_` (and, under GFM, `~`) runs,
18
+ // and is resolved by `processEmphasis`;
19
+ // * the bracket stack (§6.3) holds `[` and `![`, and is resolved by
20
+ // `parseCloseBracket` when a `]` turns up.
21
+ //
22
+ // Everything else — code spans, autolinks, raw HTML, entities, escapes,
23
+ // breaks — is decided locally at the scan position. That is what produces
24
+ // the precedence §6.3 requires: a code span, autolink or raw HTML tag is
25
+ // consumed whole during the scan, so it binds more tightly than emphasis and
26
+ // more tightly than the brackets of a link label. Conversely brackets bind
27
+ // more tightly than emphasis, because bracket matching happens during the
28
+ // scan while emphasis matching happens afterwards over the delimiter stack.
29
+ const node_ts_1 = require("./node.js");
30
+ const common_ts_1 = require("./common.js");
31
+ const C_TAB = 9;
32
+ const C_NEWLINE = 10;
33
+ const C_VTAB = 11;
34
+ const C_FORMFEED = 12;
35
+ const C_CARRIAGE_RETURN = 13;
36
+ const C_SPACE = 32;
37
+ const C_BANG = 33;
38
+ const C_AMPERSAND = 38;
39
+ const C_OPEN_PAREN = 40;
40
+ const C_CLOSE_PAREN = 41;
41
+ const C_ASTERISK = 42;
42
+ const C_PLUS = 43;
43
+ const C_COMMA = 44;
44
+ const C_HYPHEN = 45;
45
+ const C_PERIOD = 46;
46
+ const C_COLON = 58;
47
+ const C_SEMICOLON = 59;
48
+ const C_LESSTHAN = 60;
49
+ const C_QUESTION = 63;
50
+ const C_AT = 64;
51
+ const C_OPEN_BRACKET = 91;
52
+ const C_BACKSLASH = 92;
53
+ const C_CLOSE_BRACKET = 93;
54
+ const C_UNDERSCORE = 95;
55
+ const C_BACKTICK = 96;
56
+ const C_TILDE = 126;
57
+ const C_DEL = 127;
58
+ // --- §6.6 Raw HTML: the tag grammar, spelled out because it is stricter than
59
+ // `<[^>]*>` in every part. Attribute names are XML-restricted-to-ASCII;
60
+ // unquoted attribute values exclude quotes, `=`, `<`, `>`, backtick and all
61
+ // control characters; comments/PIs/declarations/CDATA each have their own
62
+ // terminator. `\s*` stands in for "spaces, tabs, and up to one line ending":
63
+ // a paragraph can never contain a blank line, so a single `\s*` can only ever
64
+ // span one line ending anyway.
65
+ const TAGNAME = '[A-Za-z][A-Za-z0-9-]*';
66
+ const ATTRIBUTENAME = '[a-zA-Z_:][a-zA-Z0-9:._-]*';
67
+ const UNQUOTEDVALUE = "[^\"'=<>`\\x00-\\x20]+";
68
+ const SINGLEQUOTEDVALUE = "'[^']*'";
69
+ const DOUBLEQUOTEDVALUE = '"[^"]*"';
70
+ const ATTRIBUTEVALUE = '(?:' + UNQUOTEDVALUE + '|' + SINGLEQUOTEDVALUE + '|' + DOUBLEQUOTEDVALUE + ')';
71
+ const ATTRIBUTEVALUESPEC = '(?:\\s*=\\s*' + ATTRIBUTEVALUE + ')';
72
+ const ATTRIBUTE = '(?:\\s+' + ATTRIBUTENAME + ATTRIBUTEVALUESPEC + '?)';
73
+ const OPENTAG = '<' + TAGNAME + ATTRIBUTE + '*\\s*/?>';
74
+ const CLOSETAG = '</' + TAGNAME + '\\s*>';
75
+ // §6.6: `<!-->` and `<!--->` are comments in their own right; otherwise the
76
+ // body simply may not contain `-->`, which the lazy quantifier enforces.
77
+ const HTMLCOMMENT = '<!-->|<!--->|<!--[\\s\\S]*?-->';
78
+ const PROCESSINGINSTRUCTION = '[<][?][\\s\\S]*?[?][>]';
79
+ const DECLARATION = '<![A-Za-z][^>]*>';
80
+ const CDATA = '<!\\[CDATA\\[[\\s\\S]*?\\]\\]>';
81
+ const HTMLTAG = '(?:' +
82
+ OPENTAG +
83
+ '|' +
84
+ CLOSETAG +
85
+ '|' +
86
+ HTMLCOMMENT +
87
+ '|' +
88
+ PROCESSINGINSTRUCTION +
89
+ '|' +
90
+ DECLARATION +
91
+ '|' +
92
+ CDATA +
93
+ ')';
94
+ const ESCAPED_CHAR = '\\\\' + common_ts_1.ESCAPABLE;
95
+ // Every regex handed to `match()` is sticky: it must match at `pos`, and
96
+ // `lastIndex` then gives the new position. (The equivalent of anchoring with
97
+ // `^` on a sliced subject, without the slicing.)
98
+ const reHtmlTag = new RegExp(HTMLTAG, 'iy');
99
+ const reEntityHere = new RegExp(common_ts_1.ENTITY, 'iy');
100
+ const reEscapable = new RegExp('^' + common_ts_1.ESCAPABLE);
101
+ const reTicksHere = /`+/y;
102
+ const reTicks = /`+/g;
103
+ // Chunks of ordinary text: everything up to the next character that could
104
+ // start an inline construct. `~` is only special under GFM, hence two.
105
+ const reMain = /[^\n`[\]\\!<&*_]+/y;
106
+ const reMainGfm = /[^\n`[\]\\!<&*_~]+/y;
107
+ /**
108
+ * Maximum `(` nesting inside a bare link destination. The spec describes
109
+ * balanced parentheses without stating a bound; cmark uses 32, and the bound
110
+ * is what keeps an unbalanced run from being quadratic (see
111
+ * parseLinkDestination).
112
+ */
113
+ const MAX_LINK_PAREN_NESTING = 32;
114
+ const reSpnl = / *(?:\n *)?/y;
115
+ const reInitialSpace = / */y;
116
+ const reIndent = / {0,3}/y;
117
+ const reFinalSpace = / *$/;
118
+ const reSpaceAtEndOfLine = / *(?:\n|$)/y;
119
+ const reWhitespaceChar = /[ \t\n\v\f\r]/;
120
+ // §4.7: at most 999 characters between the brackets, brackets inside only if
121
+ // backslash-escaped. `s` so that an escaped line ending counts as an escape.
122
+ const reLinkLabel = /\[(?:[^\\[\]]|\\.){0,1000}\]/sy;
123
+ // §6.3 link destination, pointy-bracket form: no line endings, no unescaped
124
+ // `<` or `>`.
125
+ const reLinkDestinationBraces = /<(?:[^<>\n\\\x00]|\\.)*>/y;
126
+ // §6.3 link title in `"`, `'` or `(...)` delimiters. The escape alternative
127
+ // comes first so that `\"` inside a double-quoted title is consumed as an
128
+ // escape rather than closing the title.
129
+ const reLinkTitle = new RegExp('(?:"(?:' +
130
+ ESCAPED_CHAR +
131
+ '|[^"\\x00])*"' +
132
+ "|'(?:" +
133
+ ESCAPED_CHAR +
134
+ "|[^'\\x00])*'" +
135
+ '|\\((?:' +
136
+ ESCAPED_CHAR +
137
+ '|[^()\\x00])*\\))', 'y');
138
+ // §6.5 autolinks. Scheme: an ASCII letter then 1–31 more of letter/digit/
139
+ // `+`/`.`/`-`, i.e. 2–32 characters in total, case-insensitive.
140
+ const reAutolink = /<[A-Za-z][A-Za-z0-9.+-]{1,31}:[^<>\x00-\x20]*>/y;
141
+ const reEmailAutolink = /<([a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*)>/y;
142
+ function text(literal) {
143
+ const node = new node_ts_1.MdNode('text');
144
+ node.literal = literal;
145
+ return node;
146
+ }
147
+ // §2.1 punctuation is P* ∪ S*, which `isUnicodePunctuation` already covers.
148
+ // `*$*alpha.` is the case that needs the symbol half: with `$` counted as
149
+ // punctuation the closing `*` is not right-flanking, so the line stays
150
+ // literal text.
151
+ const isPunctuation = common_ts_1.isUnicodePunctuation;
152
+ class InlineParser {
153
+ constructor(refmap, options) {
154
+ this.subject = '';
155
+ this.pos = 0;
156
+ this.delimiters = null;
157
+ this.brackets = null;
158
+ this.refmap = refmap;
159
+ this.options = options;
160
+ }
161
+ // --- scanning primitives ---
162
+ /** Character code at `pos`, or -1 at the end of the subject. */
163
+ peek() {
164
+ return this.pos < this.subject.length ? this.subject.charCodeAt(this.pos) : -1;
165
+ }
166
+ /** Match a sticky regex at `pos`; on success advance past it. */
167
+ match(re) {
168
+ re.lastIndex = this.pos;
169
+ const m = re.exec(this.subject);
170
+ if (null === m)
171
+ return null;
172
+ this.pos = re.lastIndex;
173
+ return m[0];
174
+ }
175
+ /** Spaces, and at most one line ending, between the parts of a link. */
176
+ spnl() {
177
+ this.match(reSpnl);
178
+ }
179
+ /**
180
+ * The whole code point ending at `end`, so an astral character is
181
+ * classified as one character by the flanking rules rather than as a lone
182
+ * surrogate. Start of subject counts as a line ending (§6.4).
183
+ */
184
+ charBefore(end) {
185
+ if (end <= 0)
186
+ return '\n';
187
+ const lo = this.subject.charCodeAt(end - 1);
188
+ if (lo >= 0xdc00 && lo <= 0xdfff && end >= 2) {
189
+ const hi = this.subject.charCodeAt(end - 2);
190
+ if (hi >= 0xd800 && hi <= 0xdbff)
191
+ return this.subject.slice(end - 2, end);
192
+ }
193
+ return this.subject.charAt(end - 1);
194
+ }
195
+ /** The whole code point at `i`; end of subject counts as a line ending. */
196
+ charAtPos(i) {
197
+ if (i >= this.subject.length)
198
+ return '\n';
199
+ const cp = this.subject.codePointAt(i);
200
+ return undefined === cp ? '\n' : String.fromCodePoint(cp);
201
+ }
202
+ // --- §6.3 code spans ---
203
+ /**
204
+ * A code span runs from a backtick string to the next backtick string of
205
+ * *exactly* the same length. Unmatched opening backticks are literal text.
206
+ */
207
+ parseBackticks(block) {
208
+ const ticks = this.match(reTicksHere);
209
+ if (null === ticks)
210
+ return false;
211
+ const afterOpenTicks = this.pos;
212
+ reTicks.lastIndex = this.pos;
213
+ let m;
214
+ while (null !== (m = reTicks.exec(this.subject))) {
215
+ this.pos = reTicks.lastIndex;
216
+ if (m[0] === ticks) {
217
+ const node = new node_ts_1.MdNode('code');
218
+ // Line endings become spaces; then one leading and one trailing
219
+ // space are stripped together, but only if the content is not all
220
+ // spaces (which is what lets a code span hold backticks).
221
+ const contents = this.subject
222
+ .slice(afterOpenTicks, this.pos - ticks.length)
223
+ .replace(/\n/g, ' ');
224
+ node.literal =
225
+ contents.length > 2 &&
226
+ ' ' === contents[0] &&
227
+ ' ' === contents[contents.length - 1] &&
228
+ null !== /[^ ]/.exec(contents)
229
+ ? contents.slice(1, contents.length - 1)
230
+ : contents;
231
+ block.appendChild(node);
232
+ return true;
233
+ }
234
+ }
235
+ // No closing run of the same length: the opener is literal text.
236
+ this.pos = afterOpenTicks;
237
+ block.appendChild(text(ticks));
238
+ return true;
239
+ }
240
+ // --- §6.1 backslash escapes, §6.7 hard line breaks ---
241
+ parseBackslash(block) {
242
+ this.pos += 1;
243
+ if (C_NEWLINE === this.peek()) {
244
+ this.pos += 1;
245
+ block.appendChild(new node_ts_1.MdNode('linebreak'));
246
+ this.match(reInitialSpace);
247
+ }
248
+ else if (reEscapable.test(this.subject.charAt(this.pos))) {
249
+ block.appendChild(text(this.subject.charAt(this.pos)));
250
+ this.pos += 1;
251
+ }
252
+ else {
253
+ block.appendChild(text('\\'));
254
+ }
255
+ return true;
256
+ }
257
+ // --- §6.5 autolinks ---
258
+ parseAutolink(block) {
259
+ let m = this.match(reEmailAutolink);
260
+ if (null !== m) {
261
+ const dest = m.slice(1, m.length - 1);
262
+ block.appendChild(this.makeAutolink('mailto:' + dest, dest));
263
+ return true;
264
+ }
265
+ m = this.match(reAutolink);
266
+ if (null !== m) {
267
+ const dest = m.slice(1, m.length - 1);
268
+ block.appendChild(this.makeAutolink(dest, dest));
269
+ return true;
270
+ }
271
+ return false;
272
+ }
273
+ makeAutolink(destination, label) {
274
+ // Autolink text is literal: no backslash escapes, no entity references.
275
+ const node = new node_ts_1.MdNode('link');
276
+ // Not percent-encoded here — see parseLinkDestination.
277
+ node.destination = destination;
278
+ node.title = null;
279
+ node.appendChild(text(label));
280
+ return node;
281
+ }
282
+ // --- §6.6 raw HTML ---
283
+ parseHtmlTag(block) {
284
+ const m = this.match(reHtmlTag);
285
+ if (null === m)
286
+ return false;
287
+ const node = new node_ts_1.MdNode('html_inline');
288
+ node.literal = m;
289
+ block.appendChild(node);
290
+ return true;
291
+ }
292
+ // --- §6.4 emphasis: delimiter runs and flanking ---
293
+ /**
294
+ * Classify the run of `cc` starting at `pos` without consuming it.
295
+ *
296
+ * §6.4: a run is left-flanking iff it is not followed by Unicode
297
+ * whitespace and either not followed by Unicode punctuation or else
298
+ * preceded by whitespace or punctuation; right-flanking is the mirror
299
+ * image. `_` additionally may not open inside a word (rules 2, 4, 6, 8),
300
+ * which is what keeps `snake_case_words` intact; `*` has no such
301
+ * restriction.
302
+ */
303
+ scanDelims(cc) {
304
+ const startpos = this.pos;
305
+ let numdelims = 0;
306
+ while (cc === this.peek()) {
307
+ numdelims += 1;
308
+ this.pos += 1;
309
+ }
310
+ if (0 === numdelims)
311
+ return null;
312
+ const charBefore = this.charBefore(startpos);
313
+ const charAfter = this.charAtPos(this.pos);
314
+ const afterIsWhitespace = (0, common_ts_1.isUnicodeWhitespace)(charAfter);
315
+ const afterIsPunctuation = isPunctuation(charAfter);
316
+ const beforeIsWhitespace = (0, common_ts_1.isUnicodeWhitespace)(charBefore);
317
+ const beforeIsPunctuation = isPunctuation(charBefore);
318
+ const leftFlanking = !afterIsWhitespace &&
319
+ (!afterIsPunctuation || beforeIsWhitespace || beforeIsPunctuation);
320
+ const rightFlanking = !beforeIsWhitespace &&
321
+ (!beforeIsPunctuation || afterIsWhitespace || afterIsPunctuation);
322
+ let canOpen;
323
+ let canClose;
324
+ if (C_UNDERSCORE === cc) {
325
+ canOpen = leftFlanking && (!rightFlanking || beforeIsPunctuation);
326
+ canClose = rightFlanking && (!leftFlanking || afterIsPunctuation);
327
+ }
328
+ else {
329
+ canOpen = leftFlanking;
330
+ canClose = rightFlanking;
331
+ }
332
+ this.pos = startpos;
333
+ return { numdelims, canOpen, canClose };
334
+ }
335
+ /** Emit a delimiter run as text and push it on the delimiter stack. */
336
+ handleDelim(cc, block) {
337
+ const res = this.scanDelims(cc);
338
+ if (null === res)
339
+ return false;
340
+ const numdelims = res.numdelims;
341
+ const startpos = this.pos;
342
+ this.pos += numdelims;
343
+ const node = text(this.subject.slice(startpos, this.pos));
344
+ block.appendChild(node);
345
+ // GFM strikethrough only recognises runs of one or two tildes; longer
346
+ // runs stay literal.
347
+ const stackable = C_TILDE === cc ? numdelims <= 2 : true;
348
+ if ((res.canOpen || res.canClose) && stackable) {
349
+ const delim = {
350
+ cc,
351
+ numdelims,
352
+ origdelims: numdelims,
353
+ node,
354
+ previous: this.delimiters,
355
+ next: null,
356
+ canOpen: res.canOpen,
357
+ canClose: res.canClose,
358
+ };
359
+ if (null !== delim.previous)
360
+ delim.previous.next = delim;
361
+ this.delimiters = delim;
362
+ }
363
+ return true;
364
+ }
365
+ removeDelimiter(delim) {
366
+ if (null !== delim.previous)
367
+ delim.previous.next = delim.next;
368
+ if (null === delim.next) {
369
+ // Top of stack.
370
+ this.delimiters = delim.previous;
371
+ }
372
+ else {
373
+ delim.next.previous = delim.previous;
374
+ }
375
+ }
376
+ /**
377
+ * The spec's "process emphasis" procedure. `stackBottom` is the floor: for
378
+ * the whole-block call it is null, and for the inlines of a link it is the
379
+ * delimiter that was on top when the link's `[` opened.
380
+ */
381
+ processEmphasis(stackBottom) {
382
+ // openers_bottom, keyed by (delimiter char, whether the closer can also
383
+ // open, closer run length mod 3). Once a closer of some class fails to
384
+ // find an opener, no later closer of the same class needs to look past
385
+ // that point either — this is what keeps the search linear.
386
+ const openersBottom = new Map();
387
+ // First closer above stackBottom.
388
+ let closer = this.delimiters;
389
+ while (null !== closer && closer.previous !== stackBottom) {
390
+ closer = closer.previous;
391
+ }
392
+ while (null !== closer) {
393
+ if (!closer.canClose) {
394
+ closer = closer.next;
395
+ continue;
396
+ }
397
+ const closercc = closer.cc;
398
+ const key = closercc + ':' + (closer.canOpen ? 1 : 0) + ':' + (closer.origdelims % 3);
399
+ const bottom = openersBottom.has(key)
400
+ ? openersBottom.get(key)
401
+ : stackBottom;
402
+ let opener = closer.previous;
403
+ let openerFound = false;
404
+ while (null !== opener && opener !== stackBottom && opener !== bottom) {
405
+ if (opener.cc === closercc && opener.canOpen) {
406
+ if (C_TILDE === closercc) {
407
+ // GFM strikethrough: opener and closer runs must be the same
408
+ // length, so `~~x~` is not a match.
409
+ if (opener.numdelims === closer.numdelims) {
410
+ openerFound = true;
411
+ break;
412
+ }
413
+ }
414
+ else {
415
+ // §6.4 rules 9 and 10, the "rule of three": when either side of
416
+ // the pair could both open and close, the two run lengths may
417
+ // not sum to a multiple of 3 — unless both are themselves
418
+ // multiples of 3.
419
+ const oddMatch = (closer.canOpen || opener.canClose) &&
420
+ 0 !== closer.origdelims % 3 &&
421
+ 0 === (opener.origdelims + closer.origdelims) % 3;
422
+ if (!oddMatch) {
423
+ openerFound = true;
424
+ break;
425
+ }
426
+ }
427
+ }
428
+ opener = opener.previous;
429
+ }
430
+ const oldCloser = closer;
431
+ if (openerFound && null !== opener) {
432
+ // Two delimiters make strong, one makes emph; strikethrough always
433
+ // consumes the whole (equal-length) run.
434
+ const useDelims = C_TILDE === closercc
435
+ ? closer.numdelims
436
+ : closer.numdelims >= 2 && opener.numdelims >= 2
437
+ ? 2
438
+ : 1;
439
+ const nodeType = C_TILDE === closercc ? 'del' : 1 === useDelims ? 'emph' : 'strong';
440
+ const openerInl = opener.node;
441
+ const closerInl = closer.node;
442
+ opener.numdelims -= useDelims;
443
+ closer.numdelims -= useDelims;
444
+ openerInl.literal = (openerInl.literal ?? '').slice(0, (openerInl.literal ?? '').length - useDelims);
445
+ closerInl.literal = (closerInl.literal ?? '').slice(0, (closerInl.literal ?? '').length - useDelims);
446
+ // Everything between the two delimiter text nodes becomes the
447
+ // content of the new node.
448
+ const emph = new node_ts_1.MdNode(nodeType);
449
+ let tmp = openerInl.next;
450
+ while (null !== tmp && tmp !== closerInl) {
451
+ const nxt = tmp.next;
452
+ tmp.unlink();
453
+ emph.appendChild(tmp);
454
+ tmp = nxt;
455
+ }
456
+ openerInl.insertAfter(emph);
457
+ // Delimiters between the pair can never match anything now.
458
+ if (opener.next !== closer) {
459
+ opener.next = closer;
460
+ closer.previous = opener;
461
+ }
462
+ if (0 === opener.numdelims) {
463
+ openerInl.unlink();
464
+ this.removeDelimiter(opener);
465
+ }
466
+ if (0 === closer.numdelims) {
467
+ closerInl.unlink();
468
+ const tempstack = closer.next;
469
+ this.removeDelimiter(closer);
470
+ closer = tempstack;
471
+ }
472
+ }
473
+ else {
474
+ closer = closer.next;
475
+ openersBottom.set(key, oldCloser.previous);
476
+ if (!oldCloser.canOpen) {
477
+ // It found no opener and cannot be one: it is inert.
478
+ this.removeDelimiter(oldCloser);
479
+ }
480
+ }
481
+ }
482
+ // Drop everything above the floor.
483
+ while (null !== this.delimiters && this.delimiters !== stackBottom) {
484
+ this.removeDelimiter(this.delimiters);
485
+ }
486
+ }
487
+ // --- §6.3 links and images ---
488
+ /** Link title without its delimiters, unescaped; null if none here. */
489
+ parseLinkTitle() {
490
+ const title = this.match(reLinkTitle);
491
+ if (null === title)
492
+ return null;
493
+ return (0, common_ts_1.unescapeString)(title.slice(1, title.length - 1));
494
+ }
495
+ /**
496
+ * Link destination: either `<...>`, or a bare run with balanced unescaped
497
+ * parentheses and no ASCII control characters or spaces.
498
+ */
499
+ /**
500
+ * A destination is backslash-unescaped and entity-decoded, but **not**
501
+ * percent-encoded: `[l](/ä)` yields `/ä`, not `/%C3%A4`.
502
+ *
503
+ * Encoding is a rendering concern and `html.ts` applies `normalizeURI` when
504
+ * it writes the attribute. Doing it here too would be invisible in the HTML
505
+ * (`normalizeURI` is idempotent over its own output) but would put the
506
+ * encoded form in the public AST, where mdast — and every previous release
507
+ * of this package — promises the decoded one.
508
+ */
509
+ parseLinkDestination() {
510
+ const braced = this.match(reLinkDestinationBraces);
511
+ if (null !== braced) {
512
+ return (0, common_ts_1.unescapeString)(braced.slice(1, braced.length - 1));
513
+ }
514
+ if (C_LESSTHAN === this.peek())
515
+ return null;
516
+ const savepos = this.pos;
517
+ let openparens = 0;
518
+ let c = this.peek();
519
+ while (-1 !== c) {
520
+ if (C_BACKSLASH === c && reEscapable.test(this.subject.charAt(this.pos + 1))) {
521
+ this.pos += 1;
522
+ if (-1 !== this.peek())
523
+ this.pos += 1;
524
+ }
525
+ else if (C_OPEN_PAREN === c) {
526
+ // Bail rather than scan on once nesting passes the limit. Without
527
+ // this, input like `[a](b` repeated makes every `]` scan to end of
528
+ // input looking for parens that never balance, which is quadratic in
529
+ // the document size. cmark caps nesting at the same depth, and no
530
+ // spec example nests more than two deep.
531
+ if (MAX_LINK_PAREN_NESTING <= openparens)
532
+ return null;
533
+ this.pos += 1;
534
+ openparens += 1;
535
+ }
536
+ else if (C_CLOSE_PAREN === c) {
537
+ if (openparens < 1)
538
+ break;
539
+ this.pos += 1;
540
+ openparens -= 1;
541
+ }
542
+ else if (c <= C_SPACE || C_DEL === c) {
543
+ // Spaces and ASCII control characters end the bare form.
544
+ break;
545
+ }
546
+ else {
547
+ this.pos += 1;
548
+ }
549
+ c = this.peek();
550
+ }
551
+ // An empty destination is only legal immediately before the closing
552
+ // paren of an inline link, as in `[link]()`.
553
+ if (this.pos === savepos && C_CLOSE_PAREN !== c)
554
+ return null;
555
+ if (0 !== openparens)
556
+ return null;
557
+ return (0, common_ts_1.unescapeString)(this.subject.slice(savepos, this.pos));
558
+ }
559
+ /** Length of the link label at `pos`, including brackets; 0 if invalid. */
560
+ parseLinkLabel() {
561
+ const m = this.match(reLinkLabel);
562
+ // §4.7: at most 999 characters between the brackets.
563
+ if (null === m || m.length > 1001)
564
+ return 0;
565
+ return m.length;
566
+ }
567
+ addBracket(node, index, image) {
568
+ if (null !== this.brackets)
569
+ this.brackets.bracketAfter = true;
570
+ this.brackets = {
571
+ node,
572
+ previous: this.brackets,
573
+ previousDelimiter: this.delimiters,
574
+ index,
575
+ image,
576
+ active: true,
577
+ bracketAfter: false,
578
+ };
579
+ }
580
+ removeBracket() {
581
+ if (null !== this.brackets)
582
+ this.brackets = this.brackets.previous;
583
+ }
584
+ parseOpenBracket(block) {
585
+ const startpos = this.pos;
586
+ this.pos += 1;
587
+ const node = text('[');
588
+ block.appendChild(node);
589
+ this.addBracket(node, startpos, false);
590
+ return true;
591
+ }
592
+ /** `!` only matters when it introduces an image. */
593
+ parseBang(block) {
594
+ const startpos = this.pos;
595
+ this.pos += 1;
596
+ if (C_OPEN_BRACKET === this.peek()) {
597
+ this.pos += 1;
598
+ const node = text('![');
599
+ block.appendChild(node);
600
+ this.addBracket(node, startpos + 1, true);
601
+ }
602
+ else {
603
+ block.appendChild(text('!'));
604
+ }
605
+ return true;
606
+ }
607
+ /**
608
+ * The spec's "look for link or image" procedure. On a `]`, walk back to
609
+ * the newest bracket opener and try, in order: an inline `(dest "title")`,
610
+ * a full `[label]`, a collapsed `[]` or a shortcut reference. Anything that
611
+ * fails backtracks to just after the `]` and leaves a literal `]`.
612
+ */
613
+ parseCloseBracket(block) {
614
+ this.pos += 1;
615
+ const startpos = this.pos;
616
+ const opener = this.brackets;
617
+ if (null === opener) {
618
+ block.appendChild(text(']'));
619
+ return true;
620
+ }
621
+ if (!opener.active) {
622
+ // Deactivated by an enclosing link: not an opener any more.
623
+ block.appendChild(text(']'));
624
+ this.removeBracket();
625
+ return true;
626
+ }
627
+ const isImage = opener.image;
628
+ let dest = null;
629
+ let title = null;
630
+ let matched = false;
631
+ const savepos = this.pos;
632
+ // Inline link: `](` destination title `)`.
633
+ if (C_OPEN_PAREN === this.peek()) {
634
+ this.pos += 1;
635
+ this.spnl();
636
+ const d = this.parseLinkDestination();
637
+ if (null !== d) {
638
+ this.spnl();
639
+ // A title has to be separated from the destination by whitespace,
640
+ // so `[a](/url"t")` is not a titled link.
641
+ let t = null;
642
+ if (reWhitespaceChar.test(this.subject.charAt(this.pos - 1))) {
643
+ t = this.parseLinkTitle();
644
+ }
645
+ this.spnl();
646
+ if (C_CLOSE_PAREN === this.peek()) {
647
+ this.pos += 1;
648
+ dest = d;
649
+ title = t;
650
+ matched = true;
651
+ }
652
+ }
653
+ if (!matched)
654
+ this.pos = savepos;
655
+ }
656
+ if (!matched) {
657
+ // Reference forms. A second label gives the full form; an empty or
658
+ // absent one means the link text is itself the label (collapsed and
659
+ // shortcut) — which is only possible if that text holds no bracket.
660
+ let reflabel = null;
661
+ const beforelabel = this.pos;
662
+ const n = this.parseLinkLabel();
663
+ if (n > 2) {
664
+ reflabel = this.subject.slice(beforelabel + 1, beforelabel + n - 1);
665
+ }
666
+ else if (!opener.bracketAfter) {
667
+ reflabel = this.subject.slice(opener.index + 1, startpos - 1);
668
+ }
669
+ if (0 === n)
670
+ this.pos = savepos;
671
+ if (null !== reflabel) {
672
+ const link = this.refmap[(0, common_ts_1.normalizeReference)(reflabel)];
673
+ if (undefined !== link) {
674
+ dest = link.destination;
675
+ title = link.title;
676
+ matched = true;
677
+ }
678
+ }
679
+ }
680
+ if (!matched) {
681
+ this.removeBracket();
682
+ this.pos = startpos;
683
+ block.appendChild(text(']'));
684
+ return true;
685
+ }
686
+ const node = new node_ts_1.MdNode(isImage ? 'image' : 'link');
687
+ node.destination = dest;
688
+ node.title = null === title || '' === title ? null : title;
689
+ let tmp = opener.node.next;
690
+ while (null !== tmp) {
691
+ const nxt = tmp.next;
692
+ tmp.unlink();
693
+ node.appendChild(tmp);
694
+ tmp = nxt;
695
+ }
696
+ block.appendChild(node);
697
+ // Emphasis inside the link text resolves now, and only down to the
698
+ // delimiter that was current when the `[` opened.
699
+ this.processEmphasis(opener.previousDelimiter);
700
+ this.removeBracket();
701
+ opener.node.unlink();
702
+ // Links may not contain links (§6.3): every earlier link opener is
703
+ // spent. Image openers survive, since images may nest in links.
704
+ if (!isImage) {
705
+ let b = this.brackets;
706
+ while (null !== b) {
707
+ if (!b.image)
708
+ b.active = false;
709
+ b = b.previous;
710
+ }
711
+ }
712
+ return true;
713
+ }
714
+ // --- §6.2 entities, §6.8/§6.9 breaks and plain text ---
715
+ parseEntity(block) {
716
+ const m = this.match(reEntityHere);
717
+ if (null === m)
718
+ return false;
719
+ block.appendChild(text((0, common_ts_1.decodeEntity)(m)));
720
+ return true;
721
+ }
722
+ parseString(block) {
723
+ const m = this.match(this.options.gfm ? reMainGfm : reMain);
724
+ if (null === m)
725
+ return false;
726
+ block.appendChild(text(m));
727
+ return true;
728
+ }
729
+ /**
730
+ * §6.7/§6.8: two or more spaces before the line ending make a hard break,
731
+ * anything else a soft one. Trailing spaces are dropped either way, as is
732
+ * the indentation of the next line.
733
+ */
734
+ parseNewline(block) {
735
+ this.pos += 1;
736
+ const lastc = block.lastChild;
737
+ if (null !== lastc &&
738
+ 'text' === lastc.type &&
739
+ null !== lastc.literal &&
740
+ ' ' === lastc.literal.charAt(lastc.literal.length - 1)) {
741
+ const hardbreak = ' ' === lastc.literal.charAt(lastc.literal.length - 2);
742
+ lastc.literal = lastc.literal.replace(reFinalSpace, '');
743
+ block.appendChild(new node_ts_1.MdNode(hardbreak ? 'linebreak' : 'softbreak'));
744
+ }
745
+ else {
746
+ block.appendChild(new node_ts_1.MdNode('softbreak'));
747
+ }
748
+ this.match(reInitialSpace);
749
+ return true;
750
+ }
751
+ /** One inline construct at `pos`; false only at the end of the subject. */
752
+ parseInline(block) {
753
+ const c = this.peek();
754
+ if (-1 === c)
755
+ return false;
756
+ let res = false;
757
+ switch (c) {
758
+ case C_NEWLINE:
759
+ res = this.parseNewline(block);
760
+ break;
761
+ case C_BACKSLASH:
762
+ res = this.parseBackslash(block);
763
+ break;
764
+ case C_BACKTICK:
765
+ res = this.parseBackticks(block);
766
+ break;
767
+ case C_ASTERISK:
768
+ case C_UNDERSCORE:
769
+ res = this.handleDelim(c, block);
770
+ break;
771
+ case C_TILDE:
772
+ // Only GFM gives `~` a meaning; otherwise it is ordinary text (and
773
+ // the non-GFM `reMain` lets parseString swallow whole runs of it).
774
+ res = this.options.gfm ? this.handleDelim(c, block) : this.parseString(block);
775
+ break;
776
+ case C_OPEN_BRACKET:
777
+ res = this.parseOpenBracket(block);
778
+ break;
779
+ case C_BANG:
780
+ res = this.parseBang(block);
781
+ break;
782
+ case C_CLOSE_BRACKET:
783
+ res = this.parseCloseBracket(block);
784
+ break;
785
+ case C_LESSTHAN:
786
+ res = this.parseAutolink(block) || this.parseHtmlTag(block);
787
+ break;
788
+ case C_AMPERSAND:
789
+ res = this.parseEntity(block);
790
+ break;
791
+ default:
792
+ res = this.parseString(block);
793
+ break;
794
+ }
795
+ if (!res) {
796
+ // Nothing claimed it: the character is literal text.
797
+ this.pos += 1;
798
+ block.appendChild(text(String.fromCharCode(c)));
799
+ }
800
+ return true;
801
+ }
802
+ /** Parse one paragraph or heading's raw content into inline children. */
803
+ parse(block) {
804
+ // Trimming is what stops trailing spaces at the end of a block from
805
+ // making a hard break, and drops the leading indentation of the first line.
806
+ this.subject = block.stringContent.trim();
807
+ this.pos = 0;
808
+ this.delimiters = null;
809
+ this.brackets = null;
810
+ while (this.parseInline(block))
811
+ ;
812
+ block.stringContent = '';
813
+ this.processEmphasis(null);
814
+ }
815
+ /**
816
+ * §4.7: consume leading link reference definitions. Called by the block
817
+ * phase while finalizing a paragraph, repeatedly, until it returns 0.
818
+ */
819
+ parseReference(s) {
820
+ this.subject = s;
821
+ this.pos = 0;
822
+ const startpos = this.pos;
823
+ // §4.7 allows up to three spaces of indentation before the label. The
824
+ // block phase normally strips a paragraph line's indentation before it
825
+ // gets here, so this usually consumes nothing.
826
+ this.match(reIndent);
827
+ const labelStart = this.pos;
828
+ const matchChars = this.parseLinkLabel();
829
+ if (0 === matchChars)
830
+ return 0;
831
+ const rawlabel = this.subject.slice(labelStart + 1, labelStart + matchChars - 1);
832
+ if (C_COLON === this.peek()) {
833
+ this.pos += 1;
834
+ }
835
+ else {
836
+ this.pos = startpos;
837
+ return 0;
838
+ }
839
+ this.spnl();
840
+ const dest = this.parseLinkDestination();
841
+ if (null === dest) {
842
+ this.pos = startpos;
843
+ return 0;
844
+ }
845
+ const beforetitle = this.pos;
846
+ this.spnl();
847
+ let title = null;
848
+ if (this.pos !== beforetitle) {
849
+ title = this.parseLinkTitle();
850
+ }
851
+ if (null === title) {
852
+ this.pos = beforetitle;
853
+ }
854
+ // Nothing but spaces may follow on the definition's last line.
855
+ let atLineEnd = true;
856
+ if (null === this.match(reSpaceAtEndOfLine)) {
857
+ if (null === title) {
858
+ atLineEnd = false;
859
+ }
860
+ else {
861
+ // What looked like a title is something else; the definition may
862
+ // still be valid without it.
863
+ title = null;
864
+ this.pos = beforetitle;
865
+ atLineEnd = null !== this.match(reSpaceAtEndOfLine);
866
+ }
867
+ }
868
+ if (!atLineEnd) {
869
+ this.pos = startpos;
870
+ return 0;
871
+ }
872
+ const normlabel = (0, common_ts_1.normalizeReference)(rawlabel);
873
+ if ('' === normlabel) {
874
+ // A label must hold at least one non-whitespace character.
875
+ this.pos = startpos;
876
+ return 0;
877
+ }
878
+ // First definition wins.
879
+ if (undefined === this.refmap[normlabel]) {
880
+ this.refmap[normlabel] = { destination: dest, title };
881
+ }
882
+ return this.pos - startpos;
883
+ }
884
+ }
885
+ // --- GFM extended autolinks (autolink literals) -----------------------------
886
+ //
887
+ // GFM recognises `www.x.com`, `https://x.com` and `a@x.com` without the `<…>`
888
+ // CommonMark requires. This runs as a post-pass over the finished inline tree
889
+ // rather than as another branch of the scan above, which is how cmark-gfm does
890
+ // it and is the only shape that keeps 652/652 safe: the scanner that decides
891
+ // code spans, raw HTML, emphasis and links is not touched at all, and an
892
+ // autolink can therefore never win against a construct CommonMark says comes
893
+ // first.
894
+ //
895
+ // The pass rewrites `text` nodes into text/link/text runs. It does not descend
896
+ // into `link` (links may not contain links) or `image` (its children are the
897
+ // alt text), and `code`/`html_inline` are leaves it never looks inside.
898
+ /** RFC 1035's limit on a fully-qualified domain name. See `scanDomainEnd`. */
899
+ const MAX_AUTOLINK_DOMAIN = 253;
900
+ /** RFC 5321's limit on the local part of an address, before the `@`. */
901
+ const MAX_EMAIL_LOCAL = 64;
902
+ /** The three schemes the extension recognises without angle brackets. */
903
+ const AUTOLINK_SCHEMES = ['https://', 'http://', 'ftp://'];
904
+ function isAsciiAlnum(c) {
905
+ return (c >= 48 && c <= 57) || (c >= 65 && c <= 90) || (c >= 97 && c <= 122);
906
+ }
907
+ function isAutolinkSpace(c) {
908
+ return (C_SPACE === c ||
909
+ C_TAB === c ||
910
+ C_NEWLINE === c ||
911
+ C_VTAB === c ||
912
+ C_FORMFEED === c ||
913
+ C_CARRIAGE_RETURN === c);
914
+ }
915
+ /** A domain segment holds alphanumerics, `_` and `-`; `.` separates them. */
916
+ function isDomainChar(c) {
917
+ return isAsciiAlnum(c) || C_UNDERSCORE === c || C_HYPHEN === c || C_PERIOD === c;
918
+ }
919
+ /** Before the `@`: alphanumerics and `.`, `-`, `_`, `+`. */
920
+ function isEmailLocalChar(c) {
921
+ return isAsciiAlnum(c) || C_PERIOD === c || C_HYPHEN === c || C_UNDERSCORE === c || C_PLUS === c;
922
+ }
923
+ /**
924
+ * "All such recognized autolinks can only come at the beginning of a line,
925
+ * after whitespace, or any of the delimiting characters `*`, `_`, `~`, `(`."
926
+ *
927
+ * Offset 0 has no preceding character in `s` at all, so the caller answers for
928
+ * it through `atStart` — see `startsAfterDelimiter`.
929
+ */
930
+ function isAutolinkStart(s, i, atStart) {
931
+ if (0 === i)
932
+ return atStart;
933
+ const c = s.charCodeAt(i - 1);
934
+ return (isAutolinkSpace(c) ||
935
+ C_ASTERISK === c ||
936
+ C_UNDERSCORE === c ||
937
+ C_TILDE === c ||
938
+ C_OPEN_PAREN === c);
939
+ }
940
+ /**
941
+ * Whether offset 0 of a text node may begin an autolink, given the sibling in
942
+ * front of it.
943
+ *
944
+ * The rule above is about the source character immediately before the match,
945
+ * and by the time this pass runs the tree no longer carries source offsets —
946
+ * but the previous sibling's *type* names that character exactly:
947
+ *
948
+ * * `emph` and `strong` end on the `*` or `_` that closed them, and `del` on
949
+ * its second `~`. All three are delimiters the rule allows, so
950
+ * `*a*www.b.com` links.
951
+ * * `code` ends on a backtick, `link` and `image` on `)`, `]` or `>`, and
952
+ * `html_inline` on `>`. None are delimiters, so `` `x`www.a.com ``,
953
+ * `[l](/u)www.a.com` and `<b>www.a.com` do not link.
954
+ * * a `softbreak` or `linebreak` puts offset 0 at the beginning of a line,
955
+ * and so does having no previous sibling — that is the first inline of the
956
+ * block, or of the emphasis span this pass descended into.
957
+ *
958
+ * A `text` node never appears here: `linkifyChildren` merges every run of
959
+ * adjacent text siblings before handing the first of them over.
960
+ */
961
+ function startsAfterDelimiter(prev) {
962
+ if (null === prev)
963
+ return true;
964
+ const t = prev.type;
965
+ return 'softbreak' === t || 'linebreak' === t || 'emph' === t || 'strong' === t || 'del' === t;
966
+ }
967
+ /** ASCII case-insensitive comparison against an already-lowercase literal. */
968
+ function matchesIgnoreCase(s, at, lower) {
969
+ if (at + lower.length > s.length)
970
+ return false;
971
+ for (let k = 0; k < lower.length; k++) {
972
+ let c = s.charCodeAt(at + k);
973
+ if (c >= 65 && c <= 90)
974
+ c += 32;
975
+ if (c !== lower.charCodeAt(k))
976
+ return false;
977
+ }
978
+ return true;
979
+ }
980
+ /**
981
+ * End of the run of domain characters at `from`, capped at
982
+ * `MAX_AUTOLINK_DOMAIN`.
983
+ *
984
+ * The cap is what keeps this pass linear. `_` both continues a domain and
985
+ * introduces a valid autolink start, so without a bound an input like
986
+ * `'_www.a_b.c'.repeat(n)` gives every underscore a candidate whose domain run
987
+ * reaches the end of the text — quadratic. Anything past the cap is still
988
+ * scanned, just as the path rather than as the domain, and no real domain
989
+ * comes near it.
990
+ */
991
+ function scanDomainEnd(s, from) {
992
+ const max = Math.min(s.length, from + MAX_AUTOLINK_DOMAIN);
993
+ let i = from;
994
+ while (i < max && isDomainChar(s.charCodeAt(i)))
995
+ i++;
996
+ return i;
997
+ }
998
+ /**
999
+ * A valid domain: at least one period, and no underscore in either of the last
1000
+ * two segments. `from`..`to` must already be a run of domain characters.
1001
+ */
1002
+ function isValidDomain(s, from, to) {
1003
+ if (to <= from)
1004
+ return false;
1005
+ let periods = 0;
1006
+ let underscoresInLast = 0;
1007
+ let underscoresInPrev = 0;
1008
+ for (let i = from; i < to; i++) {
1009
+ const c = s.charCodeAt(i);
1010
+ if (C_UNDERSCORE === c) {
1011
+ underscoresInLast++;
1012
+ }
1013
+ else if (C_PERIOD === c) {
1014
+ underscoresInPrev = underscoresInLast;
1015
+ underscoresInLast = 0;
1016
+ periods++;
1017
+ }
1018
+ }
1019
+ return 0 < periods && 0 === underscoresInLast && 0 === underscoresInPrev;
1020
+ }
1021
+ /**
1022
+ * "Extended autolink path validation" — pull the end of the match back off
1023
+ * trailing characters that read as sentence punctuation rather than as part of
1024
+ * a URL. One loop rather than three passes, because dropping a `)` can expose
1025
+ * a `.` and vice versa.
1026
+ *
1027
+ * * `?` `!` `.` `,` `:` `*` `_` `~` are dropped wherever they trail;
1028
+ * * a trailing `)` is dropped only while the match holds more `)` than `(`,
1029
+ * so `(www.a.com/x_(y))` keeps its inner pair and loses the outer one;
1030
+ * * a trailing `;` preceded by something shaped like an entity reference
1031
+ * takes the whole `&…;` with it.
1032
+ *
1033
+ * The parenthesis counts are taken once and then maintained, not recomputed
1034
+ * per drop: a deeply parenthesised URL is otherwise quadratic in its own
1035
+ * length.
1036
+ */
1037
+ function trimAutolinkEnd(s, start, end) {
1038
+ let opening = -1;
1039
+ let closing = -1;
1040
+ while (start < end) {
1041
+ const c = s.charCodeAt(end - 1);
1042
+ if (C_QUESTION === c ||
1043
+ C_BANG === c ||
1044
+ C_PERIOD === c ||
1045
+ C_COMMA === c ||
1046
+ C_COLON === c ||
1047
+ C_ASTERISK === c ||
1048
+ C_UNDERSCORE === c ||
1049
+ C_TILDE === c) {
1050
+ end--;
1051
+ continue;
1052
+ }
1053
+ if (C_CLOSE_PAREN === c) {
1054
+ if (opening < 0) {
1055
+ opening = 0;
1056
+ closing = 0;
1057
+ for (let i = start; i < end; i++) {
1058
+ const d = s.charCodeAt(i);
1059
+ if (C_OPEN_PAREN === d)
1060
+ opening++;
1061
+ else if (C_CLOSE_PAREN === d)
1062
+ closing++;
1063
+ }
1064
+ }
1065
+ if (closing <= opening)
1066
+ return end;
1067
+ closing--;
1068
+ end--;
1069
+ continue;
1070
+ }
1071
+ if (C_SEMICOLON === c) {
1072
+ // `&` followed by one or more alphanumerics, then this `;`.
1073
+ let j = end - 2;
1074
+ while (j > start && isAsciiAlnum(s.charCodeAt(j)))
1075
+ j--;
1076
+ if (j < end - 2 && C_AMPERSAND === s.charCodeAt(j)) {
1077
+ end = j;
1078
+ continue;
1079
+ }
1080
+ return end;
1081
+ }
1082
+ return end;
1083
+ }
1084
+ return end;
1085
+ }
1086
+ /**
1087
+ * Every autolink literal in one text run, left to right and non-overlapping.
1088
+ * Returns null when there is nothing to do, which is the overwhelmingly common
1089
+ * case and saves the caller an array and a splice.
1090
+ */
1091
+ function scanAutolinks(s, atStart) {
1092
+ const len = s.length;
1093
+ let out = null;
1094
+ // The maximal run of non-space, non-`<` characters containing the current
1095
+ // position — "zero or more non-space non-`<` characters may follow" the
1096
+ // domain. Cached because candidates are tried left to right, so each run is
1097
+ // scanned once however many candidates start inside it.
1098
+ let runStart = -1;
1099
+ let runEnd = -1;
1100
+ let i = 0;
1101
+ let lastEnd = 0;
1102
+ while (i < len) {
1103
+ const c = s.charCodeAt(i);
1104
+ // --- email: found by its `@`, then rewound to the start of the local part
1105
+ if (C_AT === c) {
1106
+ // The rewind is bounded to keep this pass linear, and RFC 5321 bounds a
1107
+ // local part in the same place anyway, so the cap costs no real address.
1108
+ const capped = i - MAX_EMAIL_LOCAL;
1109
+ const floor = Math.max(lastEnd, capped);
1110
+ let k = i;
1111
+ while (k > floor && isEmailLocalChar(s.charCodeAt(k - 1)))
1112
+ k--;
1113
+ // Stopping *on* the cap is not the same as finding a boundary. If the
1114
+ // character just outside it still belongs to the local part, the local
1115
+ // part is longer than the cap allows and there is no address here at
1116
+ // all: accepting the match would link the 64-character tail of a longer
1117
+ // run and leave the rest as text, inventing an address the source never
1118
+ // wrote. `k` can only reach `capped` when the cap, rather than `lastEnd`,
1119
+ // is what ended the loop; and `k` of 0 is the start of the string, where
1120
+ // there is no character outside the cap to disqualify anything.
1121
+ if (k === capped && 0 < k && isEmailLocalChar(s.charCodeAt(k - 1))) {
1122
+ i++;
1123
+ continue;
1124
+ }
1125
+ if (k < i && isAutolinkStart(s, k, atStart)) {
1126
+ // After the `@`: alphanumerics, `.`, `-`, `_`; at least one period; a
1127
+ // trailing `.` is not part of the address, and a trailing `-` or `_`
1128
+ // invalidates the whole thing.
1129
+ let end = i + 1;
1130
+ while (end < len && isDomainChar(s.charCodeAt(end)))
1131
+ end++;
1132
+ while (end > i + 1 && C_PERIOD === s.charCodeAt(end - 1))
1133
+ end--;
1134
+ const last = end > i + 1 ? s.charCodeAt(end - 1) : 0;
1135
+ if (end > i + 1 &&
1136
+ C_HYPHEN !== last &&
1137
+ C_UNDERSCORE !== last &&
1138
+ hasPeriod(s, i + 1, end)) {
1139
+ if (null === out)
1140
+ out = [];
1141
+ out.push({ start: k, end, href: 'mailto:' + s.slice(k, end) });
1142
+ lastEnd = end;
1143
+ i = end;
1144
+ continue;
1145
+ }
1146
+ }
1147
+ i++;
1148
+ continue;
1149
+ }
1150
+ // --- www / scheme: both need a valid start and a valid domain
1151
+ if (!couldStartUrl(c) || !isAutolinkStart(s, i, atStart)) {
1152
+ i++;
1153
+ continue;
1154
+ }
1155
+ let domainStart = -1;
1156
+ let prefix = '';
1157
+ if (matchesIgnoreCase(s, i, 'www.')) {
1158
+ // The domain of a `www.` autolink includes the `www.` itself, so the
1159
+ // required period is already there and `www.foo_bar.com` is rejected on
1160
+ // the same footing as `http://foo_bar.com`.
1161
+ domainStart = i;
1162
+ prefix = 'http://';
1163
+ }
1164
+ else {
1165
+ for (const scheme of AUTOLINK_SCHEMES) {
1166
+ if (matchesIgnoreCase(s, i, scheme)) {
1167
+ domainStart = i + scheme.length;
1168
+ break;
1169
+ }
1170
+ }
1171
+ }
1172
+ if (0 > domainStart) {
1173
+ i++;
1174
+ continue;
1175
+ }
1176
+ const domainEnd = scanDomainEnd(s, domainStart);
1177
+ // Checked before the run scan below, so a candidate that cannot possibly
1178
+ // match costs only its own domain run.
1179
+ if (!isValidDomain(s, domainStart, domainEnd)) {
1180
+ i++;
1181
+ continue;
1182
+ }
1183
+ if (i >= runEnd || i < runStart) {
1184
+ runStart = i;
1185
+ runEnd = i;
1186
+ while (runEnd < len) {
1187
+ const d = s.charCodeAt(runEnd);
1188
+ if (isAutolinkSpace(d) || C_LESSTHAN === d)
1189
+ break;
1190
+ runEnd++;
1191
+ }
1192
+ }
1193
+ const end = trimAutolinkEnd(s, i, runEnd);
1194
+ // Trailing punctuation can eat back into the domain — `www.commonmark.org.`
1195
+ // loses its last period — so the domain is validated again over what
1196
+ // actually survived.
1197
+ if (end <= domainStart || !isValidDomain(s, domainStart, Math.min(domainEnd, end))) {
1198
+ i++;
1199
+ continue;
1200
+ }
1201
+ if (null === out)
1202
+ out = [];
1203
+ out.push({ start: i, end, href: prefix + s.slice(i, end) });
1204
+ lastEnd = end;
1205
+ i = end;
1206
+ }
1207
+ return out;
1208
+ }
1209
+ /** `www.`, `http://`, `https://` and `ftp://` all start with one of these. */
1210
+ function couldStartUrl(c) {
1211
+ return 119 === c || 87 === c || 104 === c || 72 === c || 102 === c || 70 === c;
1212
+ }
1213
+ function hasPeriod(s, from, to) {
1214
+ for (let i = from; i < to; i++) {
1215
+ if (C_PERIOD === s.charCodeAt(i))
1216
+ return true;
1217
+ }
1218
+ return false;
1219
+ }
1220
+ /** Replace one text node with the text/link/text run its literal implies. */
1221
+ function linkifyTextNode(node) {
1222
+ const s = node.literal ?? '';
1223
+ const matches = scanAutolinks(s, startsAfterDelimiter(node.prev));
1224
+ if (null === matches)
1225
+ return;
1226
+ let at = 0;
1227
+ for (const m of matches) {
1228
+ if (at < m.start)
1229
+ node.insertBefore(text(s.slice(at, m.start)));
1230
+ const link = new node_ts_1.MdNode('link');
1231
+ // Not percent-encoded here, for the same reason as parseLinkDestination:
1232
+ // encoding belongs to the renderer, and the AST promises the decoded form.
1233
+ link.destination = m.href;
1234
+ link.title = null;
1235
+ link.appendChild(text(s.slice(m.start, m.end)));
1236
+ node.insertBefore(link);
1237
+ at = m.end;
1238
+ }
1239
+ if (at < s.length)
1240
+ node.insertBefore(text(s.slice(at)));
1241
+ node.unlink();
1242
+ }
1243
+ /**
1244
+ * Autolink the text children of one container, and report the child containers
1245
+ * still to visit.
1246
+ */
1247
+ function linkifyChildren(parent, pending) {
1248
+ let child = parent.firstChild;
1249
+ while (null !== child) {
1250
+ if ('text' === child.type) {
1251
+ // Consolidate the run of adjacent text nodes first. The scan above emits
1252
+ // a node per delimiter run and per character it could not claim, so
1253
+ // `a.b-c_d@a.b` arrives as three siblings and the address is only
1254
+ // visible once they are one string. Merging changes nothing downstream:
1255
+ // the renderer writes text literals back to back, and the AST projection
1256
+ // already coalesces adjacent runs.
1257
+ let sibling = child.next;
1258
+ if (null !== sibling && 'text' === sibling.type) {
1259
+ let merged = child.literal ?? '';
1260
+ while (null !== sibling && 'text' === sibling.type) {
1261
+ merged += sibling.literal ?? '';
1262
+ const next = sibling.next;
1263
+ sibling.unlink();
1264
+ sibling = next;
1265
+ }
1266
+ child.literal = merged;
1267
+ }
1268
+ const after = child.next;
1269
+ linkifyTextNode(child);
1270
+ child = after;
1271
+ continue;
1272
+ }
1273
+ // No links inside links (§6.3), and an image's children are its alt text.
1274
+ if ('link' !== child.type && 'image' !== child.type && child.isContainer()) {
1275
+ pending.push(child);
1276
+ }
1277
+ child = child.next;
1278
+ }
1279
+ }
1280
+ /**
1281
+ * Recognise GFM autolink literals throughout one finished block's inlines.
1282
+ *
1283
+ * Iterative rather than recursive: emphasis nests as deeply as the input says.
1284
+ */
1285
+ function linkifyAutolinks(block) {
1286
+ const pending = [block];
1287
+ while (0 < pending.length) {
1288
+ linkifyChildren(pending.pop(), pending);
1289
+ }
1290
+ }
1291
+ /** Blocks whose raw content the block phase left for this phase to parse. */
1292
+ const INLINE_CONTENT_BLOCKS = {
1293
+ paragraph: true,
1294
+ heading: true,
1295
+ // GFM table cells hold inlines and nothing else — "block-level elements
1296
+ // cannot be inserted in a table" — so a cell is parsed exactly as a
1297
+ // paragraph is, autolink post-pass included.
1298
+ table_cell: true,
1299
+ };
1300
+ /**
1301
+ * Phase 2 entry point: walk the block tree and parse the string content of
1302
+ * every paragraph, heading and table cell into inline children.
1303
+ */
1304
+ function parseInlines(doc, refmap, options) {
1305
+ const parser = new InlineParser(refmap, options);
1306
+ const walker = doc.walker();
1307
+ let ev = walker.next();
1308
+ while (null !== ev) {
1309
+ const node = ev.node;
1310
+ if (!ev.entering && true === INLINE_CONTENT_BLOCKS[node.type]) {
1311
+ parser.parse(node);
1312
+ if (options.gfm)
1313
+ linkifyAutolinks(node);
1314
+ }
1315
+ ev = walker.next();
1316
+ }
1317
+ }
1318
+ // Reference definitions are parsed during the block phase, before any
1319
+ // InlineParser for the document exists, so this one is kept aside for it.
1320
+ // Nothing here depends on the parse options.
1321
+ const referenceParser = new InlineParser({}, { gfm: false, breaks: false });
1322
+ /**
1323
+ * Consume link reference definitions from the front of a paragraph's raw
1324
+ * content, adding them to `refmap`. Returns the number of characters
1325
+ * consumed, or 0 if `s` does not start with a definition.
1326
+ */
1327
+ function parseReference(s, refmap) {
1328
+ referenceParser.refmap = refmap;
1329
+ return referenceParser.parseReference(s);
1330
+ }
1331
+ //# sourceMappingURL=inline.js.map