diff --git a/src/index.js b/src/index.js index 3938c5c..6fa2b62 100644 --- a/src/index.js +++ b/src/index.js @@ -29,6 +29,15 @@ export default function parse(md, prevLinks) { out = '', links = prevLinks || {}, last = 0, + // Link hrefs are resolved in a single final pass instead of rewriting the + // whole output on every closing `]` (previously O(n^2) on link-heavy input). + // `linkOpens` records the offset of every `` marker as output is + // appended; `linkPtr` consumes them left-to-right, preserving the original + // "first unresolved `` wins" semantics. Offsets stay valid because we + // only ever append to `out`. + linkOpens = [], + linkPtr = 0, + linkEdits = null, chunk, prev, token, inner, t; function tag(token) { @@ -47,6 +56,16 @@ export default function parse(md, prevLinks) { return str; } + // Append to `out`, recording the offset of every `` link marker so hrefs + // can be filled in one final pass. Generated chunks never start with `>` nor + // end with `` can straddle an append boundary. + function emit(str) { + for (let k = str.indexOf(''); k !== -1; k = str.indexOf('', k + 1)) { + linkOpens.push(out.length + k); + } + out += str; + } + md = md.replace(/^\[(.+?)\]:\s*(.+)$/gm, (s, name, url) => { links[name.toLowerCase()] = url; return ''; @@ -82,7 +101,9 @@ export default function parse(md, prevLinks) { } // Links: else if (token[10]) { - out = out.replace('', ``); + if (linkPtr < linkOpens.length) { + (linkEdits || (linkEdits = [])).push([linkOpens[linkPtr++], ``]); + } chunk = flush() + ''; } else if (token[9]) { @@ -101,8 +122,18 @@ export default function parse(md, prevLinks) { else if (token[17] || token[1]) { chunk = tag(token[17] || '--'); } - out += prev; - out += chunk; + emit(prev); + emit(chunk); + } + + // Fill in the deferred link hrefs in one left-to-right pass. + if (linkEdits) { + let merged = '', pos = 0; + for (let edit of linkEdits) { + merged += out.slice(pos, edit[0]) + edit[1]; + pos = edit[0] + 3; + } + out = merged + out.slice(pos); } return (out + md.substring(last) + flush()).replace(/^\n+|\n+$/g, '');