@readme/markdown 15.0.2 → 15.1.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.
package/dist/main.node.js CHANGED
@@ -24807,6 +24807,9 @@ const GlossaryContext = (0,external_react_.createContext)([]);
24807
24807
 
24808
24808
 
24809
24809
 
24810
+ /** Tippy portals to document.body by default, which would break the
24811
+ * `.rm-ReadMe[data-color-mode] …` descendant selectors in style.scss. */
24812
+ const appendToReadMeRoot = (ref) => ref.closest('.rm-ReadMe') ?? document.body;
24810
24813
  const Glossary = ({ children, term: termProp, terms }) => {
24811
24814
  const term = (Array.isArray(children) ? children[0] : children) || termProp;
24812
24815
  if (!term)
@@ -24814,7 +24817,7 @@ const Glossary = ({ children, term: termProp, terms }) => {
24814
24817
  const foundTerm = terms.find(i => term.toLowerCase() === i?.term?.toLowerCase());
24815
24818
  if (!foundTerm)
24816
24819
  return external_react_default().createElement("span", null, term);
24817
- return (external_react_default().createElement(tippy_react_esm, { content: external_react_default().createElement("div", { className: "GlossaryItem-tooltip-content" },
24820
+ return (external_react_default().createElement(tippy_react_esm, { appendTo: appendToReadMeRoot, content: external_react_default().createElement("div", { className: "GlossaryItem-tooltip-content" },
24818
24821
  external_react_default().createElement("strong", { className: "GlossaryItem-term" }, foundTerm.term),
24819
24822
  " - ",
24820
24823
  foundTerm.definition), offset: [-5, 5], placement: "bottom-start" },
@@ -97297,13 +97300,36 @@ const MARKER_PATTERNS = [
97297
97300
  // Pattern for ** bold **
97298
97301
  // Groups: 1=wordBefore, 2=marker, 3=contentWithSpaceAfter, 4=trailingSpace1, 5=contentWithSpaceBefore, 6=trailingSpace2, 7=afterChar
97299
97302
  // trailingSpace1 is for "** text **" pattern, trailingSpace2 is for "**text **" pattern
97300
- const asteriskBoldRegex = /([^*\s]+)?\s*(\*\*)(?:\s+((?:[^*\n]|\*(?!\*))+?)(\s*)\2|((?:[^*\n]|\*(?!\*))+?)(\s+)\2)(\S|$)?/g;
97303
+ //
97304
+ // The wordBefore and whitespace prefixes are deliberately bounded ({1,64} /
97305
+ // {0,8}) rather than unbounded (+ / *). An unbounded prefix makes matchAll
97306
+ // re-scan an arbitrarily long run from every character position, which turns
97307
+ // the pass O(n²) on text nodes containing huge unbroken tokens (pasted base64
97308
+ // payloads, minified code). Bounding the prefix caps the backtracking per
97309
+ // position; a prefix longer than the bound just starts the match later, and
97310
+ // the cut-off chars flow into the preceding text node instead — adjacent text
97311
+ // parts are merged before splicing, so the emitted AST is unchanged.
97312
+ //
97313
+ // The content quantifiers are bounded too ({1,500}). The underscore content
97314
+ // clauses can scan across `_` (needed for snake_case content), so without a
97315
+ // bound every `_` in a marker-dense token (base64url, snake_case identifiers)
97316
+ // re-scans to end-of-line looking for a closer — O(n²) again. Content already
97317
+ // can't cross a newline, and 500 chars covers any sentence-length emphasis
97318
+ // phrase; longer spans stay unnormalized rather than costing quadratic scans.
97319
+ const asteriskBoldRegex = /([^*\s]{1,64})?\s{0,8}(\*\*)(?:\s+((?:[^*\n]|\*(?!\*)){1,500}?)(\s*)\2|((?:[^*\n]|\*(?!\*)){1,500}?)(\s+)\2)(\S|$)?/g;
97301
97320
  // Pattern for __ bold __
97302
- const underscoreBoldRegex = /([^_\s]+)?\s*(__)(?:\s+((?:__(?! )|_(?!_)|[^_\n])+?)(\s*)\2|((?:__(?! )|_(?!_)|[^_\n])+?)(\s+)\2)(\S|$)?/g;
97321
+ const underscoreBoldRegex = /([^_\s]{1,64})?\s{0,8}(__)(?:\s+((?:__(?! )|_(?!_)|[^_\n]){1,500}?)(\s*)\2|((?:__(?! )|_(?!_)|[^_\n]){1,500}?)(\s+)\2)(\S|$)?/g;
97303
97322
  // Pattern for * italic *
97304
- const asteriskItalicRegex = /([^*\s]+)?\s*(\*)(?!\*)(?:\s+([^*\n]+?)(\s*)\2|([^*\n]+?)(\s+)\2)(\S|$)?/g;
97323
+ const asteriskItalicRegex = /([^*\s]{1,64})?\s{0,8}(\*)(?!\*)(?:\s+([^*\n]{1,500}?)(\s*)\2|([^*\n]{1,500}?)(\s+)\2)(\S|$)?/g;
97305
97324
  // Pattern for _ italic _
97306
- const underscoreItalicRegex = /([^_\s]+)?\s*(_)(?!_)(?:\s+((?:[^_\n]|_(?! ))+?)(\s*)\2|((?:[^_\n]|_(?! ))+?)(\s+)\2)(\S|$)?/g;
97325
+ const underscoreItalicRegex = /([^_\s]{1,64})?\s{0,8}(_)(?!_)(?:\s+((?:[^_\n]|_(?! )){1,500}?)(\s*)\2|((?:[^_\n]|_(?! )){1,500}?)(\s+)\2)(\S|$)?/g;
97326
+ // Every loose alternation requires whitespace beside a marker — after the
97327
+ // opening (`** text**`) or before the closing (`**text **`) — so
97328
+ // marker-beside-whitespace is an exact gate for the loose families. A single
97329
+ // linear probe skips them entirely on marker-dense tokens whose markers are
97330
+ // all intraword (base64url payloads, snake_case identifiers).
97331
+ const asteriskBesideWhitespaceRegex = /\*\s|\s\*/;
97332
+ const underscoreBesideWhitespaceRegex = /_\s|\s_/;
97307
97333
  // CommonMark ignores intraword underscores or asteriks, but we want to italicize/bold the inner part
97308
97334
  // Pattern for intraword _word_ in words like hello_world_
97309
97335
  const intrawordUnderscoreItalicRegex = /(\w)_(?!_)([a-zA-Z0-9]+)_(?![\w_])/g;
@@ -97313,6 +97339,23 @@ const intrawordUnderscoreBoldRegex = /(\w)__([a-zA-Z0-9]+)__(?![\w_])/g;
97313
97339
  const intrawordAsteriskItalicRegex = /(\w)\*(?!\*)([a-zA-Z0-9]+)\*(?![\w*])/g;
97314
97340
  // Pattern for intraword **word** in words like hello**world**
97315
97341
  const intrawordAsteriskBoldRegex = /(\w)\*\*([a-zA-Z0-9]+)\*\*(?![\w*])/g;
97342
+ // All regex families, in match-precedence order: collected matches are
97343
+ // stable-sorted by match.index, so at an equal index the earlier row wins the
97344
+ // overlap filter — keep this order. `gate` names the per-node precondition
97345
+ // (see the gate record in the visitor) checked before the family's regex
97346
+ // runs, so a node that can't possibly match never pays for a full scan: the
97347
+ // intraword families need their marker character present, and the loose
97348
+ // families additionally need that marker beside whitespace.
97349
+ const REGEX_FAMILIES = [
97350
+ { regex: asteriskBoldRegex, isBold: true, marker: '**', gate: 'asteriskLoose' },
97351
+ { regex: underscoreBoldRegex, isBold: true, marker: '__', gate: 'underscoreLoose' },
97352
+ { regex: asteriskItalicRegex, isBold: false, marker: '*', gate: 'asteriskLoose' },
97353
+ { regex: underscoreItalicRegex, isBold: false, marker: '_', gate: 'underscoreLoose' },
97354
+ { regex: intrawordUnderscoreItalicRegex, isBold: false, isIntraword: true, marker: '_', gate: 'underscore' },
97355
+ { regex: intrawordUnderscoreBoldRegex, isBold: true, isIntraword: true, marker: '__', gate: 'underscore' },
97356
+ { regex: intrawordAsteriskItalicRegex, isBold: false, isIntraword: true, marker: '*', gate: 'asterisk' },
97357
+ { regex: intrawordAsteriskBoldRegex, isBold: true, isIntraword: true, marker: '**', gate: 'asterisk' },
97358
+ ];
97316
97359
  /**
97317
97360
  * Finds opening emphasis marker in a text value.
97318
97361
  * Returns marker info if found, null otherwise.
@@ -97504,6 +97547,21 @@ function isInsideInlineHtmlCode(index, parent) {
97504
97547
  * malformed emphasis syntax. This plugin post-processes the AST to handle these cases.
97505
97548
  */
97506
97549
  const normalizeEmphasisAST = () => (tree) => {
97550
+ // Back-scanning siblings for <code>…</code> html pairs costs O(children)
97551
+ // per text node — O(children²) per parent, which bites on marker-dense
97552
+ // paragraphs where micromark emits thousands of inline children. Most
97553
+ // parents have no html children at all, so cache that check per parent and
97554
+ // skip the back-scan entirely. The cached flag survives our splices: they
97555
+ // only ever swap text nodes for text/strong/emphasis, never html.
97556
+ const hasHtmlChild = new WeakMap();
97557
+ const mayBeInsideInlineHtmlCode = (index, parent) => {
97558
+ let flag = hasHtmlChild.get(parent);
97559
+ if (flag === undefined) {
97560
+ flag = parent.children.some(child => child.type === 'html');
97561
+ hasHtmlChild.set(parent, flag);
97562
+ }
97563
+ return flag && isInsideInlineHtmlCode(index, parent);
97564
+ };
97507
97565
  visit(tree, 'text', function visitor(node, index, parent) {
97508
97566
  if (index === undefined || !parent)
97509
97567
  return undefined;
@@ -97515,41 +97573,39 @@ const normalizeEmphasisAST = () => (tree) => {
97515
97573
  // raw HTML <code>...</code> inside table cells, which the table re-parser
97516
97574
  // parses as MDX JSX (not as an mdast `inlineCode` node).
97517
97575
  if ((parent.type === 'mdxJsxTextElement' || parent.type === 'mdxJsxFlowElement') &&
97518
- 'name' in parent && parent.name === 'code') {
97576
+ 'name' in parent &&
97577
+ parent.name === 'code') {
97519
97578
  return undefined;
97520
97579
  }
97580
+ const text = node.value;
97581
+ // The regexes below can't match without their marker character, but
97582
+ // running them anyway costs a scan of the whole node — ruinous on huge
97583
+ // pasted payloads (base64 attachments, minified code). Checked before the
97584
+ // html-sibling scan so marker-free text never pays for that either.
97585
+ const hasAsterisk = text.includes('*');
97586
+ const hasUnderscore = text.includes('_');
97587
+ if (!hasAsterisk && !hasUnderscore)
97588
+ return undefined;
97521
97589
  // In GFM tables, inline <code>...</code> is represented as sibling `html`
97522
97590
  // nodes rather than as an mdxJsxTextElement, so the check above doesn't
97523
97591
  // apply. Scan backwards through siblings to see if we are enclosed by a
97524
97592
  // <code>…</code> inline HTML pair.
97525
- if (isInsideInlineHtmlCode(index, parent)) {
97593
+ if (mayBeInsideInlineHtmlCode(index, parent)) {
97526
97594
  return undefined;
97527
97595
  }
97528
- const text = node.value;
97596
+ const gates = {
97597
+ asterisk: hasAsterisk,
97598
+ asteriskLoose: hasAsterisk && asteriskBesideWhitespaceRegex.test(text),
97599
+ underscore: hasUnderscore,
97600
+ underscoreLoose: hasUnderscore && underscoreBesideWhitespaceRegex.test(text),
97601
+ };
97529
97602
  const allMatches = [];
97530
- [...text.matchAll(asteriskBoldRegex)].forEach(match => {
97531
- allMatches.push({ isBold: true, marker: '**', match });
97532
- });
97533
- [...text.matchAll(underscoreBoldRegex)].forEach(match => {
97534
- allMatches.push({ isBold: true, marker: '__', match });
97535
- });
97536
- [...text.matchAll(asteriskItalicRegex)].forEach(match => {
97537
- allMatches.push({ isBold: false, marker: '*', match });
97538
- });
97539
- [...text.matchAll(underscoreItalicRegex)].forEach(match => {
97540
- allMatches.push({ isBold: false, marker: '_', match });
97541
- });
97542
- [...text.matchAll(intrawordUnderscoreItalicRegex)].forEach(match => {
97543
- allMatches.push({ isBold: false, isIntraword: true, marker: '_', match });
97544
- });
97545
- [...text.matchAll(intrawordUnderscoreBoldRegex)].forEach(match => {
97546
- allMatches.push({ isBold: true, isIntraword: true, marker: '__', match });
97547
- });
97548
- [...text.matchAll(intrawordAsteriskItalicRegex)].forEach(match => {
97549
- allMatches.push({ isBold: false, isIntraword: true, marker: '*', match });
97550
- });
97551
- [...text.matchAll(intrawordAsteriskBoldRegex)].forEach(match => {
97552
- allMatches.push({ isBold: true, isIntraword: true, marker: '**', match });
97603
+ REGEX_FAMILIES.forEach(({ regex, gate, ...info }) => {
97604
+ if (!gates[gate])
97605
+ return;
97606
+ [...text.matchAll(regex)].forEach(match => {
97607
+ allMatches.push({ ...info, match });
97608
+ });
97553
97609
  });
97554
97610
  if (allMatches.length === 0)
97555
97611
  return undefined;
@@ -97646,9 +97702,22 @@ const normalizeEmphasisAST = () => (tree) => {
97646
97702
  parts.push({ type: 'text', value: remainingText });
97647
97703
  }
97648
97704
  }
97649
- if (parts.length > 0) {
97650
- parent.children.splice(index, 1, ...parts);
97651
- return [SKIP, index + parts.length];
97705
+ // Merge adjacent text parts so the emitted AST doesn't depend on where a
97706
+ // match happened to start (the bounded prefixes above can shift a match
97707
+ // start rightward, splitting what used to be a single text node).
97708
+ const mergedParts = parts.reduce((acc, part) => {
97709
+ const prev = acc[acc.length - 1];
97710
+ if (part.type === 'text' && prev?.type === 'text') {
97711
+ prev.value += part.value;
97712
+ }
97713
+ else {
97714
+ acc.push(part);
97715
+ }
97716
+ return acc;
97717
+ }, []);
97718
+ if (mergedParts.length > 0) {
97719
+ parent.children.splice(index, 1, ...mergedParts);
97720
+ return [SKIP, index + mergedParts.length];
97652
97721
  }
97653
97722
  return undefined;
97654
97723
  });