All files / scripts/lib/parseReadme parser.js

78.31% Statements 65/83
46.15% Branches 24/52
100% Functions 4/4
82.45% Lines 47/57

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95  10x 10x     45x 45x 45x 45x 45x   45x 12x 12x 12x   45x 45x                       61x 61x 61x 61x 206x 45x 45x 45x   61x 190x 190x 190x 115x 112x 111x 13x 13x 13x 13x 22x 22x   13x 13x 13x   98x 45x   61x 61x                       54x 54x                   54x 54x           54x     10x  
// resilient parser factory: try unified/remark at runtime, fall back to a synchronous AST builder
let parserFactory = null;
try { parserFactory = require('unified'); } catch (e) { parserFactory = null; }
 
function parseParagraphLines(lines) {
  const text = lines.join(' ').trim();
  Iif (!text) return null;
  const parts = [];
  let lastIndex = 0;
  const linkRe = /\[([^\]]+)\]\((https?:\/\/[^)]+|\.?\/\/+[^)]+|[^)]+\.md)\)/g;
  let m;
  while ((m = linkRe.exec(text)) !== null) {
    if (m.index > lastIndex) parts.push({ type: 'text', value: text.slice(lastIndex, m.index) });
    parts.push({ type: 'link', url: m[2], children: [{ type: 'text', value: m[1] }] });
    lastIndex = m.index + m[0].length;
  }
  if (lastIndex < text.length) parts.push({ type: 'text', value: text.slice(lastIndex) });
  return { type: 'paragraph', children: parts.length ? parts : [{ type: 'text', value: text }] };
}
 
/**
 * Minimal synchronous AST builder used when remark is not installed.
 * Produces a root node with heading, paragraph, list, image, and html children.
 * Does not handle nested block elements — intentionally simple to stay dependency-free.
 *
 * @param {string} text - Raw markdown string
 * @returns {{ type: 'root', children: object[] }}
 */
function buildFallbackAst(text) {
  const lines = String(text || '').split(/\r?\n/);
  const children = [];
  let paraBuf = [];
  const flush = () => {
    if (!paraBuf.length) return;
    const node = parseParagraphLines(paraBuf);
    Eif (node) children.push(node);
    paraBuf = [];
  };
  for (let i = 0; i < lines.length; i++) {
    const l = lines[i];
    const h = l.match(/^\s*(#{1,6})\s*(.*)$/);
    if (h) { flush(); children.push({ type: 'heading', depth: h[1].length, children: [{ type: 'text', value: h[2].trim() }] }); continue; }
    if (l.match(/!\[[^\]]*\]\(([^)]+)\)/)) { flush(); children.push({ type: 'image', url: l.match(/!\[[^\]]*\]\(([^)]+)\)/)[1].trim(), alt: '' }); continue; }
    if (l.match(/<img[^>]+src=["']([^"']+)["'][^>]*>/i)) { flush(); children.push({ type: 'html', value: l }); continue; }
    if (/^\s*[-*+]\s+/.test(l)) {
      flush();
      const items = [];
      let j = i;
      while (j < lines.length && /^\s*[-*+]\s+/.test(lines[j])) {
        items.push({ type: 'listItem', children: [{ type: 'paragraph', children: [{ type: 'text', value: lines[j].replace(/^\s*[-*+]\s+/, '').trim() }] }] });
        j++;
      }
      children.push({ type: 'list', children: items });
      i = j - 1;
      continue;
    }
    if (l.trim() === '') { flush(); continue; }
    paraBuf.push(l.trim());
  }
  flush();
  return { type: 'root', children };
}
 
/**
 * Parses a README markdown string into a unified/remark AST.
 * Attempts unified → remark → buildFallbackAst in that order so parsing
 * succeeds even when the optional remark packages are not installed.
 *
 * @param {string} text - Raw markdown string
 * @returns {{ type: 'root', children: object[] }|null}
 */
function parseMarkdown(text) {
  Iif (!text) return null;
  Iif (parserFactory) {
    try {
      const factory = (typeof parserFactory === 'function') ? parserFactory : (parserFactory && parserFactory.default) ? parserFactory.default : null;
      if (factory && typeof factory === 'function') {
        let remarkParse = null;
        try { remarkParse = require('remark-parse'); } catch (e) { remarkParse = null; }
        if (remarkParse) return factory().use(remarkParse).parse(text);
      }
    } catch (e) { /* fall through */ }
  }
  try {
    const remarkPkg = require('remark');
    const remarkFactory = (typeof remarkPkg === 'function') ? remarkPkg : (remarkPkg && remarkPkg.default) ? remarkPkg.default : null;
    let remarkParse = null;
    try { remarkParse = require('remark-parse'); } catch (e) { remarkParse = null; }
    if (remarkFactory && remarkParse) return remarkFactory().use(remarkParse).parse(text);
  } catch (e) { /* fall through */ }
  return buildFallbackAst(text);
}
 
module.exports = { buildFallbackAst, parseMarkdown };