diff --git a/CHANGELOG.md b/CHANGELOG.md index 271622bda..5d72b2795 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - `[]()` is no longer a valid link. - Fix performance issues when parsing links, #732. +- Fix performance issues when parsing backticks, #733. ## [12.0.2] - 2020-10-23 diff --git a/lib/rules_inline/backticks.js b/lib/rules_inline/backticks.js index 3a172f1f9..cc16b30e3 100644 --- a/lib/rules_inline/backticks.js +++ b/lib/rules_inline/backticks.js @@ -2,8 +2,18 @@ 'use strict'; + +function addCodeToken(state, marker, pos, matchStart) { + var token = state.push('code_inline', 'code', 0); + token.markup = marker; + token.content = state.src.slice(pos, matchStart) + .replace(/\n/g, ' ') + .replace(/^ (.+) $/, '$1'); +} + + module.exports = function backtick(state, silent) { - var start, max, marker, matchStart, matchEnd, token, + var start, max, marker, matchStart, matchEnd, startLength, endLength, pos = state.pos, ch = state.src.charCodeAt(pos); @@ -13,31 +23,54 @@ module.exports = function backtick(state, silent) { pos++; max = state.posMax; + // scan marker length while (pos < max && state.src.charCodeAt(pos) === 0x60/* ` */) { pos++; } marker = state.src.slice(start, pos); + startLength = marker.length; + + // Look for required marker length in the cache first + if (state.backticks[startLength] && state.backticks[startLength] > start) { + if (state.backticks[startLength] === Infinity) { + if (!silent) state.pending += marker; + state.pos += startLength; + } else { + if (!silent) addCodeToken(state, marker, pos, state.backticks[startLength]); + state.pos = matchEnd; + } + return true; + } matchStart = matchEnd = pos; + // Nothing found in the cache, scan until the end of the line (or until marker is found) while ((matchStart = state.src.indexOf('`', matchEnd)) !== -1) { matchEnd = matchStart + 1; + // scan marker length while (matchEnd < max && state.src.charCodeAt(matchEnd) === 0x60/* ` */) { matchEnd++; } - if (matchEnd - matchStart === marker.length) { - if (!silent) { - token = state.push('code_inline', 'code', 0); - token.markup = marker; - token.content = state.src.slice(pos, matchStart) - .replace(/\n/g, ' ') - .replace(/^ (.+) $/, '$1'); - } + endLength = matchEnd - matchStart; + + if (endLength === marker.length) { + // Found matching closer length. + if (!silent) addCodeToken(state, marker, pos, matchStart); state.pos = matchEnd; return true; } + + // Some different length found, put it in cache just in case + if (!state.backticks[endLength] || state.backticks[endLength] <= start) { + state.backticks[endLength] = matchStart; + } + + // Scanned through the end, didn't find anything. Mark "no matches" for this length; + if (matchEnd >= max) { + state.backticks[startLength] = Infinity; + } } - if (!silent) { state.pending += marker; } - state.pos += marker.length; + if (!silent) state.pending += marker; + state.pos += startLength; return true; }; diff --git a/lib/rules_inline/state_inline.js b/lib/rules_inline/state_inline.js index 851050494..8c458caec 100644 --- a/lib/rules_inline/state_inline.js +++ b/lib/rules_inline/state_inline.js @@ -31,6 +31,9 @@ function StateInline(src, md, env, outTokens) { // Stack of delimiter lists for upper level tags this._prev_delimiters = []; + + // backtick length => last seen position + this.backticks = {}; }