Upstream markedjs/marked state of the touched files before c8795a47c1
This commit is contained in:
commit
4e18e54fdb
1 file changed
+614
+614
@@ -0,0 +1,614 @@
|
||||
const noopTest = { exec: () => null } as unknown as RegExp;
|
||||
|
||||
function cachedIndentRegex(createRegex: (indent: number) => RegExp) {
|
||||
const cache: RegExp[] = [];
|
||||
return (indent: number) => {
|
||||
const cacheIndex = Math.max(0, Math.min(3, indent - 1));
|
||||
let regex = cache[cacheIndex];
|
||||
if (!regex) {
|
||||
regex = createRegex(cacheIndex);
|
||||
cache[cacheIndex] = regex;
|
||||
}
|
||||
return regex;
|
||||
};
|
||||
}
|
||||
|
||||
function edit(regex: string | RegExp, opt = '') {
|
||||
let source = typeof regex === 'string' ? regex : regex.source;
|
||||
const obj = {
|
||||
replace: (name: string | RegExp, val: string | RegExp) => {
|
||||
let valSource = typeof val === 'string' ? val : val.source;
|
||||
valSource = valSource.replace(other.caret, '$1');
|
||||
source = source.replace(name, valSource);
|
||||
return obj;
|
||||
},
|
||||
getRegex: () => {
|
||||
return new RegExp(source, opt);
|
||||
},
|
||||
};
|
||||
return obj;
|
||||
}
|
||||
|
||||
// use a default variable to ensure the regex is evaluated on client an not removed by minifiers
|
||||
const supportsLookbehind = ((a = '') => {
|
||||
try {
|
||||
return !!new RegExp('(?<=1)(?<!1)' + a);
|
||||
} catch {
|
||||
// See browser support here:
|
||||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Regular_expressions/Lookbehind_assertion
|
||||
return false;
|
||||
}
|
||||
})();
|
||||
|
||||
export const other = {
|
||||
codeRemoveIndent: /^(?: {0,3}\t| {1,4})/gm,
|
||||
outputLinkReplace: /\\([\[\]])/g,
|
||||
indentCodeCompensation: /^(\s+)(?:```)/,
|
||||
beginningSpace: /^\s+/,
|
||||
endingHash: /#$/,
|
||||
startingSpaceChar: /^ /,
|
||||
endingSpaceChar: / $/,
|
||||
endingSpaceTabChar: /[ \t]$/,
|
||||
nonSpaceChar: /[^ ]/,
|
||||
newLineCharGlobal: /\n/g,
|
||||
tabCharGlobal: /\t/g,
|
||||
leadingSpaceTab: /^[ \t]+/,
|
||||
multipleSpaceGlobal: /\s+/g,
|
||||
blankLine: /^[ \t]*$/,
|
||||
doubleBlankLine: /\n[ \t]*\n[ \t]*$/,
|
||||
blockquoteStart: /^ {0,3}>/,
|
||||
blockquoteSetextReplace: /\n {0,3}((?:=+|-+) *)(?=\n|$)/g,
|
||||
blockquoteSetextReplace2: /^ {0,3}>[ \t]?/gm,
|
||||
listReplaceNesting: /^ {1,4}(?=( {4})*[^ ])/g,
|
||||
listIsTask: /^\[[ xX]\] +\S/,
|
||||
listReplaceTask: /^\[[ xX]\] +/,
|
||||
listTaskCheckbox: /\[[ xX]\]/,
|
||||
anyLine: /\n.*\n/,
|
||||
hrefBrackets: /^<(.*)>$/,
|
||||
tableDelimiter: /[:|]/,
|
||||
tableAlignChars: /^\||\| *$/g,
|
||||
tableRowBlankLine: /\n[ \t]*$/,
|
||||
tableAlignRight: /^ *-+: *$/,
|
||||
tableAlignCenter: /^ *:-+: *$/,
|
||||
tableAlignLeft: /^ *:-+ *$/,
|
||||
startATag: /^<a /i,
|
||||
endATag: /^<\/a>/i,
|
||||
startPreScriptTag: /^<(pre|code|kbd|script)(\s|>)/i,
|
||||
endPreScriptTag: /^<\/(pre|code|kbd|script)(\s|>)/i,
|
||||
startAngleBracket: /^</,
|
||||
endAngleBracket: />$/,
|
||||
pedanticHrefTitle: /^([^'"]*[^\s])\s+(['"])(.*)\2/,
|
||||
unicodeAlphaNumeric: /[\p{L}\p{N}]/u,
|
||||
numericCharacterReference: /&#(?:(\d{1,7})|[Xx]([A-Fa-f0-9]{1,6}));/,
|
||||
numericCharacterReferenceOrAmpersand: /&#(?:(\d{1,7})|[Xx]([A-Fa-f0-9]{1,6}));|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/g,
|
||||
escapeTest: /[&<>"']/,
|
||||
escapeReplace: /[&<>"']/g,
|
||||
escapeTestNoEncode: /[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/,
|
||||
escapeReplaceNoEncode: /[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/g,
|
||||
caret: /(^|[^\[])\^/g,
|
||||
percentDecode: /%25/g,
|
||||
findPipe: /\|/g,
|
||||
splitPipe: / \|/,
|
||||
slashPipe: /\\\|/g,
|
||||
carriageReturn: /\r\n|\r/g,
|
||||
spaceLine: /^ +$/gm,
|
||||
notSpaceStart: /^\S*/,
|
||||
endingNewline: /\n$/,
|
||||
listItemRegex: (bull: string) => new RegExp(`^( {0,3}${bull})((?:[\t ][^\\n]*)?(?:\\n|$))`),
|
||||
nextBulletRegex: cachedIndentRegex((indent: number) => new RegExp(`^ {0,${indent}}(?:[*+-]|\\d{1,9}[.)])((?:[ \t][^\\n]*)?(?:\\n|$))`)),
|
||||
hrRegex: cachedIndentRegex((indent: number) => new RegExp(`^ {0,${indent}}((?:-[ \t]*){3,}|(?:_[ \t]*){3,}|(?:\\*[ \t]*){3,})(?:\\n+|$)`)),
|
||||
fencesBeginRegex: cachedIndentRegex((indent: number) => new RegExp(`^ {0,${indent}}(?:\`\`\`|~~~)`)),
|
||||
headingBeginRegex: cachedIndentRegex((indent: number) => new RegExp(`^ {0,${indent}}#`)),
|
||||
// a list item ends where a paragraph would be interrupted, so this mirrors the
|
||||
// html start conditions in the paragraph rule below; type 7 is excluded there
|
||||
htmlBeginRegex: cachedIndentRegex((indent: number) => new RegExp(`^ {0,${indent}}(?:</?(?:${_tag})(?: +|$|/?>)|<(?:(?:script|pre|style|textarea)(?:[\\s>]|$)|!--))`, 'i')),
|
||||
blockquoteBeginRegex: cachedIndentRegex((indent: number) => new RegExp(`^ {0,${indent}}>`)),
|
||||
};
|
||||
|
||||
/**
|
||||
* Block-Level Grammar
|
||||
*/
|
||||
|
||||
const newline = /^(?:[ \t]*(?:\n|$))+/;
|
||||
const blockCode = /^((?: {4}| {0,3}\t)[^\n]+(?:\n(?:[ \t]*(?:\n|$))*)?)+/;
|
||||
const fences = /^ {0,3}(`{3,}(?=[^`\n]*(?:\n|$))|~{3,})([^\n]*)(?:\n|$)(?:|([\s\S]*?)(?:\n|$))(?: {0,3}\1[~`]* *(?=\n|$)|$)/;
|
||||
const hr = /^ {0,3}((?:-[\t ]*){3,}|(?:_[ \t]*){3,}|(?:\*[ \t]*){3,})(?:\n+|$)/;
|
||||
const heading = /^ {0,3}(#{1,6})(?=\s|$)(.*)(?:\n+|$)/;
|
||||
const bullet = / {0,3}(?:[*+-]|\d{1,9}[.)])/;
|
||||
const lheadingCore = /^(?!bull |blockCode|fences|blockquote|heading|html|table)((?:.|\n(?!\s*?\n|bull |fences|blockquote|heading|hr|html|table))+?)\n {0,3}(=+|-+) *(?:\n+|$)/;
|
||||
const lheading = edit(lheadingCore)
|
||||
.replace(/bull/g, bullet) // lists can interrupt
|
||||
.replace(/blockCode/g, /(?: {4}| {0,3}\t)/) // indented code can start a block but cannot interrupt a paragraph
|
||||
.replace(/fences/g, / {0,3}(?:`{3,}|~{3,})/) // fenced code blocks can interrupt
|
||||
.replace(/blockquote/g, / {0,3}>/) // blockquote can interrupt
|
||||
.replace(/heading/g, / {0,3}#{1,6}(?:\s|$)/) // ATX heading can interrupt
|
||||
.replace(/hr/g, / {0,3}(?:(?:-[\t ]*){3,}|(?:_[ \t]*){3,}|(?:\*[ \t]*){3,})(?:\n+|$)/) // thematic break can interrupt
|
||||
.replace(/html/g, / {0,3}<[^\n>]+>\n/) // block html can interrupt
|
||||
.replace(/\|table/g, '') // table not in commonmark
|
||||
.getRegex();
|
||||
const lheadingGfm = edit(lheadingCore)
|
||||
.replace(/bull/g, bullet) // lists can interrupt
|
||||
.replace(/blockCode/g, /(?: {4}| {0,3}\t)/) // indented code can start a block but cannot interrupt a paragraph
|
||||
.replace(/fences/g, / {0,3}(?:`{3,}|~{3,})/) // fenced code blocks can interrupt
|
||||
.replace(/blockquote/g, / {0,3}>/) // blockquote can interrupt
|
||||
.replace(/heading/g, / {0,3}#{1,6}(?:\s|$)/) // ATX heading can interrupt
|
||||
.replace(/hr/g, / {0,3}(?:(?:-[\t ]*){3,}|(?:_[ \t]*){3,}|(?:\*[ \t]*){3,})(?:\n+|$)/) // thematic break can interrupt
|
||||
.replace(/html/g, / {0,3}<[^\n>]+>\n/) // block html can interrupt
|
||||
.replace(/table/g, / {0,3}\|?(?:[:\- ]*\|)+[\:\- ]*\n/) // table can interrupt
|
||||
.getRegex();
|
||||
const _paragraph = /^([^\n]+(?:\n(?!hr|heading|lheading|blockquote|fences|list|html|table|[ \t]+\n)[^\n]+)*)/;
|
||||
const blockText = /^[^\n]+/;
|
||||
const _blockLabel = /(?!\s*\])(?:\\[\s\S]|[^\[\]\\])+/;
|
||||
const def = edit(/^ {0,3}\[(label)\]: *(?:\n[ \t]*)?([^<\s][^\s]*|<.*?>)(?:(?: +(?:\n[ \t]*)?| *\n[ \t]*)(title))? *(?:\n+|$)/)
|
||||
.replace('label', _blockLabel)
|
||||
.replace('title', /(?:"(?:\\"?|[^"\\])*"|'[^'\n]*(?:\n[^'\n]+)*\n?'|\([^()]*\))/)
|
||||
.getRegex();
|
||||
|
||||
const list = edit(/^(bull)([ \t][^\n]*?)?(?:\n|$)/)
|
||||
.replace(/bull/g, bullet)
|
||||
.getRegex();
|
||||
|
||||
const _tag = 'address|article|aside|base|basefont|blockquote|body|caption'
|
||||
+ '|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption'
|
||||
+ '|figure|footer|form|frame|frameset|h[1-6]|head|header|hr|html|iframe'
|
||||
+ '|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|option'
|
||||
+ '|p|param|search|section|summary|table|tbody|td|tfoot|th|thead|title'
|
||||
+ '|tr|track|ul';
|
||||
const _comment = /<!--(?:-?>|[\s\S]*?(?:-->|$))/;
|
||||
const html = edit(
|
||||
'^ {0,3}(?:' // optional indentation
|
||||
+ '<(script|pre|style|textarea)[\\s>][\\s\\S]*?(?:</\\1>[^\\n]*\\n*|$)' // (1)
|
||||
+ '|comment[^\\n]*(\\n+|$)' // (2)
|
||||
+ '|<\\?[\\s\\S]*?(?:\\?>[^\\n]*\\n*|$)' // (3)
|
||||
+ '|<![A-Z][\\s\\S]*?(?:>[^\\n]*\\n*|$)' // (4)
|
||||
+ '|<!\\[CDATA\\[[\\s\\S]*?(?:\\]\\]>[^\\n]*\\n*|$)' // (5)
|
||||
+ '|</?(tag)(?: +|\\n|/?>)[\\s\\S]*?(?:(?:\\n[ \t]*)+\\n|$)' // (6)
|
||||
+ '|<(?!(?:script|pre|style|textarea)(?![a-z0-9-]))([a-z][a-z0-9-]*)(?:attribute)*? */?>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n[ \t]*)+\\n|$)' // (7) open tag
|
||||
+ '|</[a-z][a-z0-9-]*\\s*>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n[ \t]*)+\\n|$)' // (7) closing tag
|
||||
+ ')', 'i')
|
||||
.replace('comment', _comment)
|
||||
.replace('tag', _tag)
|
||||
.replace('attribute', / +[a-zA-Z:_][\w.:-]*(?: *= *"[^"\n]*"| *= *'[^'\n]*'| *= *[^\s"'=<>`]+)?/)
|
||||
.getRegex();
|
||||
|
||||
const createParagraph = (listInterrupt: RegExp) => edit(_paragraph)
|
||||
.replace('hr', hr)
|
||||
.replace('heading', ' {0,3}#{1,6}(?:\\s|$)')
|
||||
.replace('|lheading', '') // setext headings don't interrupt commonmark paragraphs
|
||||
.replace('|table', '')
|
||||
.replace('blockquote', ' {0,3}>')
|
||||
.replace('fences', ' {0,3}(?:`{3,}(?=[^`\\n]*(?:\\n|$))|~~~)[^\\n]*(?:\\n|$)')
|
||||
.replace('list', listInterrupt)
|
||||
.replace('html', '</?(?:tag)(?: +|\\n|/?>)|<(?:(?:script|pre|style|textarea)(?:[\\s>]|$)|!--)')
|
||||
.replace('tag', _tag) // pars can be interrupted by type (6) html blocks
|
||||
.getRegex();
|
||||
|
||||
// only non-empty lists starting from 1 can interrupt paragraphs
|
||||
const paragraph = createParagraph(/ {0,3}(?:[*+-]|1[.)])[ \t]+[^ \t\n]/);
|
||||
// inside a blockquote a bare list marker (any number) starts a sibling list,
|
||||
// so it must not be lazily continued as paragraph text (unlike a top level
|
||||
// paragraph, where an empty list cannot interrupt)
|
||||
const blockquoteParagraph = createParagraph(/ {0,3}(?:[*+-]|\d{1,9}[.)])(?:[ \t]|\n|$)/);
|
||||
|
||||
const blockquote = edit(/^( {0,3}> ?(paragraph|[^\n]*)(?:\n|$))+/)
|
||||
.replace('paragraph', blockquoteParagraph)
|
||||
.getRegex();
|
||||
|
||||
/**
|
||||
* Normal Block Grammar
|
||||
*/
|
||||
|
||||
const blockNormal = {
|
||||
blockquote,
|
||||
code: blockCode,
|
||||
def,
|
||||
fences,
|
||||
heading,
|
||||
hr,
|
||||
html,
|
||||
lheading,
|
||||
list,
|
||||
newline,
|
||||
paragraph,
|
||||
table: noopTest,
|
||||
text: blockText,
|
||||
};
|
||||
|
||||
type BlockKeys = keyof typeof blockNormal;
|
||||
|
||||
/**
|
||||
* GFM Block Grammar
|
||||
*/
|
||||
|
||||
const gfmTable = edit(
|
||||
'^ *([^\\n ].*)\\n' // Header
|
||||
+ ' {0,3}((?:\\| *)?:?-+:? *(?:\\| *:?-+:? *)*(?:\\| *)?)' // Align
|
||||
+ '(?:\\n((?:(?! *\\n|hr|heading|blockquote|code|fences|list|html).*(?:\\n|$))*)\\n*|$)') // Cells
|
||||
.replace('hr', hr)
|
||||
.replace('heading', ' {0,3}#{1,6}(?:\\s|$)')
|
||||
.replace('blockquote', ' {0,3}>')
|
||||
.replace('code', '(?: {4}| {0,3}\t)[^\\n]')
|
||||
.replace('fences', ' {0,3}(?:`{3,}(?=[^`\\n]*(?:\\n|$))|~~~)[^\\n]*(?:\\n|$)')
|
||||
.replace('list', ' {0,3}(?:[*+-]|1[.)])[ \\t]') // any bullet ends the table rows
|
||||
.replace('html', '</?(?:tag)(?: +|\\n|/?>)|<(?:(?:script|pre|style|textarea)(?:[\\s>]|$)|!--)')
|
||||
.replace('tag', _tag) // tables can be interrupted by type (6) html blocks
|
||||
.getRegex();
|
||||
|
||||
const blockGfm: Record<BlockKeys, RegExp> = {
|
||||
...blockNormal,
|
||||
lheading: lheadingGfm,
|
||||
table: gfmTable,
|
||||
paragraph: edit(_paragraph)
|
||||
.replace('hr', hr)
|
||||
.replace('heading', ' {0,3}#{1,6}(?:\\s|$)')
|
||||
.replace('|lheading', '') // setext headings don't interrupt commonmark paragraphs
|
||||
.replace('table', gfmTable) // interrupt paragraphs with table
|
||||
.replace('blockquote', ' {0,3}>')
|
||||
.replace('fences', ' {0,3}(?:`{3,}(?=[^`\\n]*(?:\\n|$))|~~~)[^\\n]*(?:\\n|$)')
|
||||
.replace('list', ' {0,3}(?:[*+-]|1[.)])[ \\t]+[^ \\t\\n]') // only non-empty lists starting from 1 can interrupt
|
||||
.replace('html', '</?(?:tag)(?: +|\\n|/?>)|<(?:(?:script|pre|style|textarea)(?:[\\s>]|$)|!--)')
|
||||
.replace('tag', _tag) // pars can be interrupted by type (6) html blocks
|
||||
.getRegex(),
|
||||
};
|
||||
|
||||
/**
|
||||
* Pedantic grammar (original John Gruber's loose markdown specification)
|
||||
*/
|
||||
|
||||
const blockPedantic: Record<BlockKeys, RegExp> = {
|
||||
...blockNormal,
|
||||
html: edit(
|
||||
'^ *(?:comment *(?:\\n|\\s*$)'
|
||||
+ '|<(tag)[\\s\\S]+?</\\1> *(?:\\n{2,}|\\s*$)' // closed tag
|
||||
+ '|<tag(?:"[^"]*"|\'[^\']*\'|\\s[^\'"/>\\s]*)*?/?> *(?:\\n{2,}|\\s*$))')
|
||||
.replace('comment', _comment)
|
||||
.replace(/tag/g, '(?!(?:'
|
||||
+ 'a|em|strong|small|s|cite|q|dfn|abbr|data|time|code|var|samp|kbd|sub'
|
||||
+ '|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo|span|br|wbr|ins|del|img)'
|
||||
+ '\\b)\\w+(?!:|[^\\w\\s@]*@)\\b')
|
||||
.getRegex(),
|
||||
def: /^ *\[([^\]]+)\]: *<?([^\s>]+)>?(?: +(["(][^\n]+[")]))? *(?:\n+|$)/,
|
||||
heading: /^(#{1,6})(.*)(?:\n+|$)/,
|
||||
fences: noopTest, // fences not supported
|
||||
lheading: /^(.+?)\n {0,3}(=+|-+) *(?:\n+|$)/,
|
||||
paragraph: edit(_paragraph)
|
||||
.replace('hr', hr)
|
||||
.replace('heading', ' *#{1,6} *[^\n]')
|
||||
.replace('lheading', lheading)
|
||||
.replace('|table', '')
|
||||
.replace('blockquote', ' {0,3}>')
|
||||
.replace('|fences', '')
|
||||
.replace('|list', '')
|
||||
.replace('|html', '')
|
||||
.replace('|tag', '')
|
||||
.getRegex(),
|
||||
};
|
||||
|
||||
/**
|
||||
* Inline-Level Grammar
|
||||
*/
|
||||
|
||||
const escape = /^\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/;
|
||||
const inlineCode = /^(`+)([^`]|[^`][\s\S]*?[^`])\1(?!`)/;
|
||||
const br = /^( {2,}|\\)\n(?!\s*$)[ \t]*/;
|
||||
const inlineText = /^(`+|[^`])(?:(?= {2,}\n)|[\s\S]*?(?:(?=[\\<!\[`*_]|\b_|$)|[^ ](?= {2,}\n)))/;
|
||||
|
||||
// list of unicode punctuation marks, plus any missing characters from CommonMark spec
|
||||
const _punctuation = /[\p{P}\p{S}]/u;
|
||||
const _punctuationOrSpace = /[\s\p{P}\p{S}]/u;
|
||||
const _notPunctuationOrSpace = /[^\s\p{P}\p{S}]/u;
|
||||
const punctuation = edit(/^((?![*_])punctSpace)/, 'u')
|
||||
.replace(/punctSpace/g, _punctuationOrSpace).getRegex();
|
||||
|
||||
/**
|
||||
* Opening punct/quotes excluded from pedantic LDelim nextChar so
|
||||
* `aa**"A"**bb` / `aa**(A)**bb` can start strong (Pi + Ps + ASCII straight quotes).
|
||||
*/
|
||||
const _openQuote = /[\p{Pi}\p{Ps}"']/u;
|
||||
|
||||
// GFM allows ~ inside strong and em for strikethrough
|
||||
const _punctuationGfmStrongEm = /(?!~)[\p{P}\p{S}]/u;
|
||||
const _punctuationOrSpaceGfmStrongEm = /(?!~)[\s\p{P}\p{S}]/u;
|
||||
const _notPunctuationOrSpaceGfmStrongEm = /(?:[^\s\p{P}\p{S}]|~)/u;
|
||||
|
||||
// sequences em should skip over [title](link), `code`, <html>
|
||||
const blockSkip = edit(/link|precode-code|html/, 'g')
|
||||
.replace('link', /\[(?:[^\[\]`]|(?<a>`+)[^`]+\k<a>(?!`))*?\]\((?:\\[\s\S]|[^\\\(\)]|\((?:\\[\s\S]|[^\\\(\)])*\))*\)/)
|
||||
.replace('precode-', supportsLookbehind ? '(?<!`)()' : '(^^|[^`])')
|
||||
.replace('code', /(?<b>`+)[^`]+\k<b>(?!`)/)
|
||||
.replace('html', /<(?! )[^<>]*?>/)
|
||||
.getRegex();
|
||||
|
||||
const emStrongLDelimCore = /^(?:\*+(?:((?!\*)punct)|([^\s*]))?)|^_+(?:((?!_)punct)|([^\s_]))?/;
|
||||
|
||||
const emStrongLDelim = edit(emStrongLDelimCore, 'u')
|
||||
.replace(/punct/g, _punctuation)
|
||||
.getRegex();
|
||||
|
||||
const emStrongLDelimGfm = edit(emStrongLDelimCore, 'u')
|
||||
.replace(/punct/g, _punctuationGfmStrongEm)
|
||||
.getRegex();
|
||||
|
||||
// Pedantic: do not treat opening punct/quotes as left-delim punct (e.g. **( / **").
|
||||
const emStrongLDelimPedanticCore = /^(?:\*+(?:((?!\*)(?!openQuote)punct)|([^\s*]))?)|^_+(?:((?!_)(?!openQuote)punct)|([^\s_]))?/;
|
||||
|
||||
const emStrongLDelimPedantic = edit(emStrongLDelimPedanticCore, 'u')
|
||||
.replace(/openQuote/g, _openQuote)
|
||||
.replace(/punct/g, _punctuation)
|
||||
.getRegex();
|
||||
|
||||
const emStrongRDelimAstCore =
|
||||
'^[^_*]*?__[^_*]*?\\*[^_*]*?(?=__)' // Skip orphan inside strong
|
||||
+ '|[^*]+(?=[^*])' // Consume to delim
|
||||
+ '|(?!\\*)punct(\\*+)(?=[\\s]|$)' // (1) #*** can only be a Right Delimiter
|
||||
+ '|notPunctSpace(\\*+)(?!\\*)(?=punctSpace|$)' // (2) a***#, a*** can only be a Right Delimiter
|
||||
+ '|(?!\\*)punctSpace(\\*+)(?=notPunctSpace)' // (3) #***a, ***a can only be Left Delimiter
|
||||
+ '|[\\s](\\*+)(?!\\*)(?=punct)' // (4) ***# can only be Left Delimiter
|
||||
+ '|(?!\\*)punct(\\*+)(?!\\*)(?=punct)' // (5) #***# can be either Left or Right Delimiter
|
||||
+ '|notPunctSpace(\\*+)(?=notPunctSpace)'; // (6) a***a can be either Left or Right Delimiter
|
||||
|
||||
const emStrongRDelimAst = edit(emStrongRDelimAstCore, 'gu')
|
||||
.replace(/notPunctSpace/g, _notPunctuationOrSpace)
|
||||
.replace(/punctSpace/g, _punctuationOrSpace)
|
||||
.replace(/punct/g, _punctuation)
|
||||
.getRegex();
|
||||
|
||||
const emStrongRDelimAstGfm = edit(emStrongRDelimAstCore, 'gu')
|
||||
.replace(/notPunctSpace/g, _notPunctuationOrSpaceGfmStrongEm)
|
||||
.replace(/punctSpace/g, _punctuationOrSpaceGfmStrongEm)
|
||||
.replace(/punct/g, _punctuationGfmStrongEm)
|
||||
.getRegex();
|
||||
|
||||
// Pedantic: rule 3 requires whitespace before left delim; rule 6 allows punct before close (e.g. `:**bar`).
|
||||
const emStrongRDelimAstPedanticCore =
|
||||
'^[^_*]*?__[^_*]*?\\*[^_*]*?(?=__)' // Skip orphan inside strong
|
||||
+ '|[^*]+(?=[^*])' // Consume to delim
|
||||
+ '|(?!\\*)punct(\\*+)(?=[\\s]|$)' // (1) #*** can only be a Right Delimiter
|
||||
+ '|notPunctSpace(\\*+)(?!\\*)(?=punctSpace|$)' // (2) a***#, a*** can only be a Right Delimiter
|
||||
+ '|(?!\\*)[\\s](\\*+)(?=notPunctSpace)' // (3) ***a can only be Left Delimiter (whitespace required; not #***a)
|
||||
+ '|[\\s](\\*+)(?!\\*)(?=punct)' // (4) ***# can only be Left Delimiter
|
||||
+ '|(?!\\*)punct(\\*+)(?!\\*)(?=punct)' // (5) #***# can be either Left or Right Delimiter
|
||||
+ '|(?:(?!\\*)punct|notPunctSpace)(\\*+)(?!\\*)(?=notPunctSpace)'; // (6) a***a, #***a, and :** (e.g. **foo:**bar)
|
||||
|
||||
const emStrongRDelimAstPedantic = edit(emStrongRDelimAstPedanticCore, 'gu')
|
||||
.replace(/notPunctSpace/g, _notPunctuationOrSpace)
|
||||
.replace(/punctSpace/g, _punctuationOrSpace)
|
||||
.replace(/punct/g, _punctuation)
|
||||
.getRegex();
|
||||
|
||||
// Normal: no rule 6 for _ (a___a cannot close as emphasis).
|
||||
const emStrongRDelimUnd = edit(
|
||||
'^[^_*]*?\\*\\*[^_*]*?_[^_*]*?(?=\\*\\*)' // Skip orphan inside strong
|
||||
+ '|[^_]+(?=[^_])' // Consume to delim
|
||||
+ '|(?!_)punct(_+)(?=[\\s]|$)' // (1) #___ can only be a Right Delimiter
|
||||
+ '|notPunctSpace(_+)(?!_)(?=punctSpace|$)' // (2) a___#, a___ can only be a Right Delimiter
|
||||
+ '|(?!_)punctSpace(_+)(?=notPunctSpace)' // (3) #___a, ___a can only be Left Delimiter
|
||||
+ '|[\\s](_+)(?!_)(?=punct)' // (4) ___# can only be Left Delimiter
|
||||
+ '|(?!_)punct(_+)(?!_)(?=punct)', 'gu') // (5) #___# can be either Left or Right Delimiter
|
||||
.replace(/notPunctSpace/g, _notPunctuationOrSpace)
|
||||
.replace(/punctSpace/g, _punctuationOrSpace)
|
||||
.replace(/punct/g, _punctuation)
|
||||
.getRegex();
|
||||
|
||||
// Pedantic: same rule 3/6 adjustments as emStrongRDelimAstPedantic (e.g. `_foo:_bar`).
|
||||
const emStrongRDelimUndPedanticCore =
|
||||
'^[^_*]*?\\*\\*[^_*]*?_[^_*]*?(?=\\*\\*)' // Skip orphan inside strong
|
||||
+ '|[^_]+(?=[^_])' // Consume to delim
|
||||
+ '|(?!_)punct(_+)(?=[\\s]|$)' // (1) #___ can only be a Right Delimiter
|
||||
+ '|notPunctSpace(_+)(?!_)(?=punctSpace|$)' // (2) a___#, a___ can only be a Right Delimiter
|
||||
+ '|(?!_)[\\s](_+)(?=notPunctSpace)' // (3) ___a can only be Left Delimiter (whitespace required; not #___a)
|
||||
+ '|[\\s](_+)(?!_)(?=punct)' // (4) ___# can only be Left Delimiter
|
||||
+ '|(?!_)punct(_+)(?!_)(?=punct)' // (5) #___# can be either Left or Right Delimiter
|
||||
+ '|(?:(?!_)punct|notPunctSpace)(_+)(?!_)(?=notPunctSpace)'; // (6) a___a, #___a, and :__ (e.g. _foo:_bar)
|
||||
|
||||
const emStrongRDelimUndPedantic = edit(emStrongRDelimUndPedanticCore, 'gu')
|
||||
.replace(/notPunctSpace/g, _notPunctuationOrSpace)
|
||||
.replace(/punctSpace/g, _punctuationOrSpace)
|
||||
.replace(/punct/g, _punctuation)
|
||||
.getRegex();
|
||||
|
||||
// Tilde left delimiter for strikethrough (similar to emStrongLDelim for asterisk)
|
||||
const delLDelim = edit(/^~~?(?:((?!~)punct)|[^\s~])/, 'u')
|
||||
.replace(/punct/g, _punctuation)
|
||||
.getRegex();
|
||||
|
||||
// Tilde delimiter patterns for strikethrough (similar to asterisk)
|
||||
const delRDelimCore =
|
||||
'^[^~]+(?=[^~])' // Consume to delim
|
||||
+ '|(?!~)punct(~~?)(?=[\\s]|$)' // (1) #~~ can only be a Right Delimiter
|
||||
+ '|notPunctSpace(~~?)(?!~)(?=punctSpace|$)' // (2) a~~#, a~~ can only be a Right Delimiter
|
||||
+ '|(?!~)punctSpace(~~?)(?=notPunctSpace)' // (3) #~~a, ~~a can only be Left Delimiter
|
||||
+ '|[\\s](~~?)(?!~)(?=punct)' // (4) ~~# can only be Left Delimiter
|
||||
+ '|(?!~)punct(~~?)(?!~)(?=punct)' // (5) #~~# can be either Left or Right Delimiter
|
||||
+ '|notPunctSpace(~~?)(?=notPunctSpace)'; // (6) a~~a can be either Left or Right Delimiter
|
||||
|
||||
const delRDelim = edit(delRDelimCore, 'gu')
|
||||
.replace(/notPunctSpace/g, _notPunctuationOrSpace)
|
||||
.replace(/punctSpace/g, _punctuationOrSpace)
|
||||
.replace(/punct/g, _punctuation)
|
||||
.getRegex();
|
||||
|
||||
const anyPunctuation = edit(/\\(punct)/, 'gu')
|
||||
.replace(/punct/g, _punctuation)
|
||||
.getRegex();
|
||||
|
||||
const autolink = edit(/^<(scheme:[^\s\x00-\x1f<>]*|email)>/)
|
||||
.replace('scheme', /[a-zA-Z][a-zA-Z0-9+.-]{1,31}/)
|
||||
.replace('email', /[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])?)+(?![-_])/)
|
||||
.getRegex();
|
||||
|
||||
const _inlineComment = edit(_comment).replace('(?:-->|$)', '-->').getRegex();
|
||||
const tag = edit(
|
||||
'^comment'
|
||||
+ '|^</[a-zA-Z][a-zA-Z0-9-]*\\s*>' // self-closing tag
|
||||
+ '|^<[a-zA-Z][a-zA-Z0-9-]*(?:attribute)*?\\s*/?>' // open tag
|
||||
+ '|^<\\?[\\s\\S]*?\\?>' // processing instruction, e.g. <?php ?>
|
||||
+ '|^<![a-zA-Z]+\\s[\\s\\S]*?>' // declaration, e.g. <!DOCTYPE html>
|
||||
+ '|^<!\\[CDATA\\[[\\s\\S]*?\\]\\]>') // CDATA section
|
||||
.replace('comment', _inlineComment)
|
||||
.replace('attribute', /\s+[a-zA-Z:_][\w.:-]*(?:\s*=\s*"[^"]*"|\s*=\s*'[^']*'|\s*=\s*[^\s"'=<>`]+)?/)
|
||||
.getRegex();
|
||||
|
||||
// One matched pair of brackets, holding no bracket of its own.
|
||||
const _inlineLabelBrackets = /\[(?:\\[\s\S]|[^\[\]\\])*\]/;
|
||||
// CommonMark lets the brackets in a link label nest to any depth, which a regex
|
||||
// cannot follow, so it stops at a fixed one. Two levels is what the spec suite
|
||||
// asks for: a third and a fourth flip no further example.
|
||||
const _inlineLabel = edit(/(?:\[(?:brackets|\\[\s\S]|[^\[\]\\])*\]|\\[\s\S]|`+(?!`)[^`]*?`+(?!`)|``+(?=\])|[^\[\]\\`])*?/)
|
||||
.replace('brackets', _inlineLabelBrackets)
|
||||
.getRegex();
|
||||
|
||||
const link = edit(/^!?\[(label)\]\([ \t\n]*(href)(?:(?:[ \t]+(?:\n[ \t]*)?|\n[ \t]*)(title))?[ \t\n]*\)/)
|
||||
.replace('label', _inlineLabel)
|
||||
.replace('href', /<(?:\\.|[^\n<>\\])+>|[^ \t\n\x00-\x1f]+|(?=\))/)
|
||||
.replace('title', /"(?:\\"?|[^"\\])*"|'(?:\\'?|[^'\\])*'|\((?:\\\)?|[^)\\])*\)/)
|
||||
.getRegex();
|
||||
|
||||
const reflink = edit(/^!?\[(label)\]\[(ref)\]/)
|
||||
.replace('label', _inlineLabel)
|
||||
.replace('ref', _blockLabel)
|
||||
.getRegex();
|
||||
|
||||
const nolink = edit(/^!?\[(ref)\](?:\[\])?/)
|
||||
.replace('ref', _blockLabel)
|
||||
.getRegex();
|
||||
|
||||
// `reflink` and `nolink` are anchored, so the tokenizer tries each of them at a
|
||||
// single position. `reflinkSearch` drops the anchors and runs with the global
|
||||
// flag, which makes every '[' in the source a start position. A label crosses a
|
||||
// bracket only by escaping it, and nothing caps how often it does, so a
|
||||
// candidate that can never match still scans to the end of the source and the
|
||||
// whole pass costs O(n^2).
|
||||
//
|
||||
// The labels below bound that scan. `_boundedInlineLabel` limits how many
|
||||
// escapes, code spans and nested brackets a link text may hold, leaving runs of
|
||||
// ordinary characters unbounded so link text of any length is still found.
|
||||
// `_boundedBlockLabel` limits the label itself to 999 items, which is every
|
||||
// label CommonMark allows: "A link label can have at most 999 characters
|
||||
// inside the square brackets."
|
||||
const _boundedBlockLabel = /(?!\s*\])(?:\\[\s\S]|[^\[\]\\]){1,999}/;
|
||||
const _boundedInlineLabel = edit(/(?:[^\[\]\\`]*(?:\[(?:brackets|\\[\s\S]|[^\[\]\\])*\]|\\[\s\S]|`+(?!`)[^`]*?`+(?!`)|``+(?=\]))){0,999}?[^\[\]\\`]*?/)
|
||||
.replace('brackets', _inlineLabelBrackets)
|
||||
.getRegex();
|
||||
|
||||
const reflinkSearch = edit('reflink|nolink(?!\\()', 'g')
|
||||
.replace('reflink', edit(/^!?\[(label)\]\[(ref)\]/)
|
||||
.replace('label', _boundedInlineLabel)
|
||||
.replace('ref', _boundedBlockLabel)
|
||||
.getRegex())
|
||||
.replace('nolink', edit(/^!?\[(ref)\](?:\[\])?/)
|
||||
.replace('ref', _boundedBlockLabel)
|
||||
.getRegex())
|
||||
.getRegex();
|
||||
|
||||
const _caseInsensitiveProtocol = /[hH][tT][tT][pP][sS]?|[fF][tT][pP]/;
|
||||
const _extendedEmail = /[A-Za-z0-9._+-]+@[a-zA-Z0-9-_]+(?:\.[a-zA-Z0-9-_]*[a-zA-Z0-9])+(?![\w-])/;
|
||||
const _extendedEmailProtocol = edit(/(?:mailto:email|xmpp:email(?:\/[A-Za-z0-9@.]+)?)/)
|
||||
.replace(/email/g, _extendedEmail)
|
||||
.getRegex();
|
||||
|
||||
/**
|
||||
* Normal Inline Grammar
|
||||
*/
|
||||
|
||||
const inlineNormal = {
|
||||
_backpedal: noopTest, // only used for GFM url
|
||||
anyPunctuation,
|
||||
autolink,
|
||||
blockSkip,
|
||||
br,
|
||||
code: inlineCode,
|
||||
del: noopTest,
|
||||
delLDelim: noopTest,
|
||||
delRDelim: noopTest,
|
||||
emStrongLDelim,
|
||||
emStrongRDelimAst,
|
||||
emStrongRDelimUnd,
|
||||
escape,
|
||||
link,
|
||||
nolink,
|
||||
punctuation,
|
||||
reflink,
|
||||
reflinkSearch,
|
||||
tag,
|
||||
text: inlineText,
|
||||
url: noopTest,
|
||||
};
|
||||
|
||||
type InlineKeys = keyof typeof inlineNormal;
|
||||
|
||||
/**
|
||||
* Pedantic Inline Grammar
|
||||
*/
|
||||
|
||||
const inlinePedantic: Record<InlineKeys, RegExp> = {
|
||||
...inlineNormal,
|
||||
emStrongLDelim: emStrongLDelimPedantic,
|
||||
emStrongRDelimAst: emStrongRDelimAstPedantic,
|
||||
emStrongRDelimUnd: emStrongRDelimUndPedantic,
|
||||
link: edit(/^!?\[(label)\]\((.*?)\)/)
|
||||
.replace('label', _inlineLabel)
|
||||
.getRegex(),
|
||||
reflink: edit(/^!?\[(label)\]\s*\[([^\]]*)\]/)
|
||||
.replace('label', _inlineLabel)
|
||||
.getRegex(),
|
||||
};
|
||||
|
||||
/**
|
||||
* GFM Inline Grammar
|
||||
*/
|
||||
|
||||
const inlineGfm: Record<InlineKeys, RegExp> = {
|
||||
...inlineNormal,
|
||||
emStrongRDelimAst: emStrongRDelimAstGfm,
|
||||
emStrongLDelim: emStrongLDelimGfm,
|
||||
delLDelim,
|
||||
delRDelim,
|
||||
url: edit(/^emailProtocol|^((?:protocol):\/\/|www\.)(?:[a-zA-Z0-9\-]+\.?)+[^\s<]*|^email/)
|
||||
.replace('emailProtocol', _extendedEmailProtocol)
|
||||
.replace('protocol', _caseInsensitiveProtocol)
|
||||
.replace('email', /[A-Za-z0-9._+-]+(@)[a-zA-Z0-9-_]+(?:\.[a-zA-Z0-9-_]*[a-zA-Z0-9])+(?![\w-])/)
|
||||
.getRegex(),
|
||||
_backpedal: /(?:[^?!.,:;*_'"~()&]+|\([^)]*\)|&(?![a-zA-Z0-9]+;$)|[?!.,:;*_'"~)]+(?!$))+/,
|
||||
del: /^(~~?)(?=[^\s~])((?:\\[\s\S]|[^\\])*?(?:\\[\s\S]|[^\s~\\]))\1(?=[^~]|$)/,
|
||||
text: edit(/^(?:[^a-zA-Z0-9](?=emailProtocol)|(`+|~+|[^`~])(?:(?=[`~])|(?= {2,}\n)|(?=[a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-]+@)|[\s\S]*?(?:(?=[\\<!\[`*~_]|\b_|protocol:\/\/|www\.|$)|[^ ](?= {2,}\n)|[^a-zA-Z0-9](?=emailProtocol)|[^a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-](?=[a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-]+@))))/)
|
||||
.replace('protocol', _caseInsensitiveProtocol)
|
||||
.replace(/emailProtocol/g, /(?:mailto|xmpp):/)
|
||||
.getRegex(),
|
||||
};
|
||||
|
||||
/**
|
||||
* GFM + Line Breaks Inline Grammar
|
||||
*/
|
||||
|
||||
const inlineBreaks: Record<InlineKeys, RegExp> = {
|
||||
...inlineGfm,
|
||||
br: edit(br).replace('{2,}', '*').getRegex(),
|
||||
text: edit(inlineGfm.text)
|
||||
.replace('\\b_', '\\b_| {2,}\\n')
|
||||
.replace(/\{2,\}/g, '*')
|
||||
.getRegex(),
|
||||
};
|
||||
|
||||
/**
|
||||
* exports
|
||||
*/
|
||||
|
||||
export const block = {
|
||||
normal: blockNormal,
|
||||
gfm: blockGfm,
|
||||
pedantic: blockPedantic,
|
||||
};
|
||||
|
||||
export const inline = {
|
||||
normal: inlineNormal,
|
||||
gfm: inlineGfm,
|
||||
breaks: inlineBreaks,
|
||||
pedantic: inlinePedantic,
|
||||
};
|
||||
|
||||
export interface Rules {
|
||||
other: typeof other
|
||||
block: Record<BlockKeys, RegExp>
|
||||
inline: Record<InlineKeys, RegExp>
|
||||
}
|
||||
Reference in new issue
Block a user