diff --git a/.eslintignore b/.eslintignore index 950875fea..b439abfc7 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,7 +1,6 @@ build/ components/ coverage/ -lib/expressions.js build.js mdast.js mdast.min.js diff --git a/.jscs.json b/.jscs.json index 510b269a1..404fe8017 100644 --- a/.jscs.json +++ b/.jscs.json @@ -4,8 +4,6 @@ "components/", "coverage/", "node_modules/", - "lib/expressions.js", - "script/build-expressions.js", "build.js", "mdast.js", "mdast.min.js" diff --git a/component.json b/component.json index 91825df21..d768f7f29 100644 --- a/component.json +++ b/component.json @@ -32,9 +32,13 @@ "scripts": [ "index.js", "lib/defaults.js", - "lib/expressions.js", "lib/parse.js", "lib/stringify.js", "lib/utilities.js" + ], + "json": [ + "lib/block-elements.json", + "lib/escape.json", + "lib/inline-text-stop.json" ] } diff --git a/index.js b/index.js index 01925360c..8f0f323cc 100644 --- a/index.js +++ b/index.js @@ -18,6 +18,8 @@ var unified = require('unified'); var Parser = require('./lib/parse.js'); var Compiler = require('./lib/stringify.js'); +var escape = require('./lib/escape.json'); +var inlineTextStop = require('./lib/inline-text-stop.json'); /* * Exports. @@ -26,5 +28,9 @@ var Compiler = require('./lib/stringify.js'); module.exports = unified({ 'name': 'mdast', 'Parser': Parser, - 'Compiler': Compiler + 'Compiler': Compiler, + 'data': { + 'escape': escape, + 'inlineTextStop': inlineTextStop + } }); diff --git a/lib/block-elements.json b/lib/block-elements.json new file mode 100644 index 000000000..af5e657cb --- /dev/null +++ b/lib/block-elements.json @@ -0,0 +1,52 @@ +[ + "article", + "header", + "aside", + "hgroup", + "blockquote", + "hr", + "iframe", + "body", + "li", + "map", + "button", + "object", + "canvas", + "ol", + "caption", + "output", + "col", + "p", + "colgroup", + "pre", + "dd", + "progress", + "div", + "section", + "dl", + "table", + "td", + "dt", + "tbody", + "embed", + "textarea", + "fieldset", + "tfoot", + "figcaption", + "th", + "figure", + "thead", + "footer", + "tr", + "form", + "ul", + "h1", + "h2", + "h3", + "h4", + "h5", + "h6", + "video", + "script", + "style" +] diff --git a/lib/escape.json b/lib/escape.json new file mode 100644 index 000000000..f74e70cfd --- /dev/null +++ b/lib/escape.json @@ -0,0 +1,75 @@ +{ + "default": [ + "\\", + "`", + "*", + "{", + "}", + "[", + "]", + "(", + ")", + "#", + "+", + "-", + ".", + "!", + "_", + ">" + ], + "gfm": [ + "\\", + "`", + "*", + "{", + "}", + "[", + "]", + "(", + ")", + "#", + "+", + "-", + ".", + "!", + "_", + ">", + "~", + "|" + ], + "commonmark": [ + "\\", + "`", + "*", + "{", + "}", + "[", + "]", + "(", + ")", + "#", + "+", + "-", + ".", + "!", + "_", + ">", + "~", + "|", + "\n", + "\"", + "$", + "%", + "&", + "'", + ",", + "/", + ":", + ";", + "<", + "=", + "?", + "@", + "^" + ] +} diff --git a/lib/expressions.js b/lib/expressions.js deleted file mode 100644 index 4e219d06f..000000000 --- a/lib/expressions.js +++ /dev/null @@ -1,74 +0,0 @@ -/* This file is generated by `script/build-expressions.js` */ -/* eslint-env commonjs */ -module.exports = { - 'rules': { - 'newline': /^\n((?:[ \t]*\n)*)/, - 'code': /^((?:(?: {4}|\t)[^\n]*\n?((?:[ \t]*\n)*))+)/, - 'horizontalRule': /^[ \t]*([-*_])( *\1){2,} *(?=\n|$)/, - 'heading': /^([ \t]*)(#{1,6})(?:([ \t]+)([^\n]+?))??(?:[ \t]+#+)?[ \t]*(?=\n|$)/, - 'lineHeading': /^(\ {0,3})([^\n]+?)[ \t]*\n\ {0,3}(=|-){1,}[ \t]*(?=\n|$)/, - 'definition': /^[ \t]*\[((?:[^\\](?:\\|\\(?:\\{2})+)\]|[^\]])+)\]:[ \t\n]*(<[^>\[\]]+>|[^\s\[\]]+)(?:[ \t\n]+['"(]((?:[^\n]|\n(?!\n))*?)['")])?[ \t]*(?=\n|$)/, - 'bullet': /(?:[*+-]|\d+\.)/, - 'indent': /^([ \t]*)((?:[*+-]|\d+\.))( {1,4}(?! )| |\t)/, - 'item': /([ \t]*)((?:[*+-]|\d+\.))( {1,4}(?! )| |\t)[^\n]*(?:\n(?!\1(?:[*+-]|\d+\.)[ \t])[^\n]*)*/gm, - 'list': /^([ \t]*)((?:[*+-]|\d+\.))[ \t][\s\S]+?(?:(?=\n+\1?(?:[-*_][ \t]*){3,}(?:\n|$))|(?=\n+[ \t]*\[((?:[^\\](?:\\|\\(?:\\{2})+)\]|[^\]])+)\]:[ \t\n]*(<[^>\[\]]+>|[^\s\[\]]+)(?:[ \t\n]+['"(]((?:[^\n]|\n(?!\n))*?)['")])?[ \t]*(?=\n|$))|\n{2,}(?![ \t])(?!\1(?:[*+-]|\d+\.)[ \t])|$)/, - 'blockquote': /^(?=[ \t]*>)(?:(?:(?:[ \t]*>[^\n]*\n)*(?:[ \t]*>[^\n]+(?=\n|$))|(?![ \t]*>)(?![ \t]*\[((?:[^\\](?:\\|\\(?:\\{2})+)\]|[^\]])+)\]:[ \t\n]*(<[^>\[\]]+>|[^\s\[\]]+)(?:[ \t\n]+['"(]((?:[^\n]|\n(?!\n))*?)['")])?[ \t]*(?=\n|$))[^\n]+)(?:\n|$))*(?:[ \t]*>[ \t]*(?:\n[ \t]*>[ \t]*)*)?/, - 'html': /^(?:[ \t]*(?:(?:(?:<(?:article|header|aside|hgroup|blockquote|hr|iframe|body|li|map|button|object|canvas|ol|caption|output|col|p|colgroup|pre|dd|progress|div|section|dl|table|td|dt|tbody|embed|textarea|fieldset|tfoot|figcaption|th|figure|thead|footer|tr|form|ul|h1|h2|h3|h4|h5|h6|video|script|style)(?:(?:\s+)(?:[a-zA-Z_:][a-zA-Z0-9_.:-]*)(?:(?:\s+)?=(?:\s+)?(?:[^"'=<>`]+|'[^']*'|"[^"]*"))?)*(?:\s+)?\/?>?)|(?:<\/(?:article|header|aside|hgroup|blockquote|hr|iframe|body|li|map|button|object|canvas|ol|caption|output|col|p|colgroup|pre|dd|progress|div|section|dl|table|td|dt|tbody|embed|textarea|fieldset|tfoot|figcaption|th|figure|thead|footer|tr|form|ul|h1|h2|h3|h4|h5|h6|video|script|style)(?:\s+)?>))||(?:<\?(?:[^\?]|\?(?!>))+\?>)|(?:)|(?:))[\s\S]*?[ \t]*?(?:\n{2,}|\s*$))/i, - 'paragraph': /^(?:(?:[^\n]+\n?(?![ \t]*([-*_])( *\1){2,} *(?=\n|$)|([ \t]*)(#{1,6})(?:([ \t]+)([^\n]+?))??(?:[ \t]+#+)?[ \t]*(?=\n|$)|(\ {0,3})([^\n]+?)[ \t]*\n\ {0,3}(=|-){1,}[ \t]*(?=\n|$)|[ \t]*\[((?:[^\\](?:\\|\\(?:\\{2})+)\]|[^\]])+)\]:[ \t\n]*(<[^>\[\]]+>|[^\s\[\]]+)(?:[ \t\n]+['"(]((?:[^\n]|\n(?!\n))*?)['")])?[ \t]*(?=\n|$)|(?=[ \t]*>)(?:(?:(?:[ \t]*>[^\n]*\n)*(?:[ \t]*>[^\n]+(?=\n|$))|(?![ \t]*>)(?![ \t]*\[((?:[^\\](?:\\|\\(?:\\{2})+)\]|[^\]])+)\]:[ \t\n]*(<[^>\[\]]+>|[^\s\[\]]+)(?:[ \t\n]+['"(]((?:[^\n]|\n(?!\n))*?)['")])?[ \t]*(?=\n|$))[^\n]+)(?:\n|$))*(?:[ \t]*>[ \t]*(?:\n[ \t]*>[ \t]*)*)?|<(?!(?: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)(?!mailto:)\w+(?!:\/|[^\w\s@]*@)\b))+)/, - 'escape': /^\\([\\`*{}\[\]()#+\-.!_>])/, - 'autoLink': /^<([^ >]+(@|:\/)[^ >]+)>/, - 'tag': /^(?:(?:<(?:[a-zA-Z][a-zA-Z0-9]*)(?:(?:\s+)(?:[a-zA-Z_:][a-zA-Z0-9_.:-]*)(?:(?:\s+)?=(?:\s+)?(?:[^"'=<>`]+|'[^']*'|"[^"]*"))?)*(?:\s+)?\/?>)|(?:<\/(?:[a-zA-Z][a-zA-Z0-9]*)(?:\s+)?>)||(?:<\?(?:[^\?]|\?(?!>))+\?>)|(?:)|(?:))/, - 'strong': /^(_)_((?:\\[\s\S]|[^\\])+?)__(?!_)|^(\*)\*((?:\\[\s\S]|[^\\])+?)\*\*(?!\*)/, - 'emphasis': /^\b(_)((?:__|\\[\s\S]|[^\\])+?)_\b|^(\*)((?:\*\*|\\[\s\S]|[^\\])+?)\*(?!\*)/, - 'inlineCode': /^(`+)((?!`)[\s\S]*?(?:`\s+|[^`]))?(\1)(?!`)/, - 'break': /^ {2,}\n(?!\s*$)/, - 'inlineText': /^[\s\S]+?(?=[\\)(?:\s+['"]([\s\S]*?)['"])?\s*\)/, - 'shortcutReference': /^(!?\[)((?:\\[\s\S]|[^\[\]])+?)\]/, - 'reference': /^(!?\[)((?:\[[^\]]*\]|[^\[\]]|\](?=[^\[]*\]))*)\]\s*\[((?:\\[\s\S]|[^\[\]])*)\]/ - }, - 'gfm': { - 'fences': /^( *)(([`~])\3{2,})[ \t]*([^\n`~]+)?[ \t]*(?:\n([\s\S]*?))??(?:\n\ {0,3}\2\3*[ \t]*(?=\n|$)|$)/, - 'paragraph': /^(?:(?:[^\n]+\n?(?![ \t]*([-*_])( *\1){2,} *(?=\n|$)|( *)(([`~])\5{2,})[ \t]*([^\n`~]+)?[ \t]*(?:\n([\s\S]*?))??(?:\n\ {0,3}\4\5*[ \t]*(?=\n|$)|$)|([ \t]*)((?:[*+-]|\d+\.))[ \t][\s\S]+?(?:(?=\n+\8?(?:[-*_][ \t]*){3,}(?:\n|$))|(?=\n+[ \t]*\[((?:[^\\](?:\\|\\(?:\\{2})+)\]|[^\]])+)\]:[ \t\n]*(<[^>\[\]]+>|[^\s\[\]]+)(?:[ \t\n]+['"(]((?:[^\n]|\n(?!\n))*?)['")])?[ \t]*(?=\n|$))|\n{2,}(?![ \t])(?!\8(?:[*+-]|\d+\.)[ \t])|$)|([ \t]*)(#{1,6})(?:([ \t]+)([^\n]+?))??(?:[ \t]+#+)?[ \t]*(?=\n|$)|(\ {0,3})([^\n]+?)[ \t]*\n\ {0,3}(=|-){1,}[ \t]*(?=\n|$)|[ \t]*\[((?:[^\\](?:\\|\\(?:\\{2})+)\]|[^\]])+)\]:[ \t\n]*(<[^>\[\]]+>|[^\s\[\]]+)(?:[ \t\n]+['"(]((?:[^\n]|\n(?!\n))*?)['")])?[ \t]*(?=\n|$)|(?=[ \t]*>)(?:(?:(?:[ \t]*>[^\n]*\n)*(?:[ \t]*>[^\n]+(?=\n|$))|(?![ \t]*>)(?![ \t]*\[((?:[^\\](?:\\|\\(?:\\{2})+)\]|[^\]])+)\]:[ \t\n]*(<[^>\[\]]+>|[^\s\[\]]+)(?:[ \t\n]+['"(]((?:[^\n]|\n(?!\n))*?)['")])?[ \t]*(?=\n|$))[^\n]+)(?:\n|$))*(?:[ \t]*>[ \t]*(?:\n[ \t]*>[ \t]*)*)?|<(?!(?: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)(?!mailto:)\w+(?!:\/|[^\w\s@]*@)\b))+)/, - 'table': /^( *\|(.+))\n( *\|( *[-:]+[-| :]*)\n)((?: *\|.*(?:\n|$))*)/, - 'looseTable': /^( *(\S.*\|.*))\n( *([-:]+ *\|[-| :]*)\n)((?:.*\|.*(?:\n|$))*)/, - 'escape': /^\\([\\`*{}\[\]()#+\-.!_>~|])/, - 'url': /^https?:\/\/[^\s<]+[^<.,:;"')\]\s]/, - 'deletion': /^~~(?=\S)([\s\S]*?\S)~~/, - 'inlineText': /^[\s\S]+?(?=[\\\[\]]+>|[^\s\[\]]+)(?:[ \t\n]+['"(]((?:[^\n]|\n(?!\n))*?)['")])?[ \t]*(?=\n|$))|\n{2,}(?![ \t])(?!\1(?:[*+-]|\d+[\.\)])[ \t])|$)/, - 'item': /([ \t]*)((?:[*+-]|\d+[\.\)]))( {1,4}(?! )| |\t)[^\n]*(?:\n(?!\1(?:[*+-]|\d+[\.\)])[ \t])[^\n]*)*/gm, - 'bullet': /(?:[*+-]|\d+[\.\)])/, - 'indent': /^([ \t]*)((?:[*+-]|\d+[\.\)]))( {1,4}(?! )| |\t)/, - 'html': /^(?:[ \t]*(?:(?:(?:<(?:article|header|aside|hgroup|blockquote|hr|iframe|body|li|map|button|object|canvas|ol|caption|output|col|p|colgroup|pre|dd|progress|div|section|dl|table|td|dt|tbody|embed|textarea|fieldset|tfoot|figcaption|th|figure|thead|footer|tr|form|ul|h1|h2|h3|h4|h5|h6|video|script|style)(?:(?:\s+)(?:[a-zA-Z_:][a-zA-Z0-9_.:-]*)(?:(?:\s+)?=(?:\s+)?(?:[^"'=<>`]+|'[^']*'|"[^"]*"))?)*(?:\s+)?\/?>?)|(?:<\/(?:article|header|aside|hgroup|blockquote|hr|iframe|body|li|map|button|object|canvas|ol|caption|output|col|p|colgroup|pre|dd|progress|div|section|dl|table|td|dt|tbody|embed|textarea|fieldset|tfoot|figcaption|th|figure|thead|footer|tr|form|ul|h1|h2|h3|h4|h5|h6|video|script|style)(?:\s+)?>))|(?:)|(?:<\?(?:[^\?]|\?(?!>))+\?>)|(?:)|(?:))[\s\S]*?[ \t]*?(?:\n{2,}|\s*$))/i, - 'tag': /^(?:(?:<(?:[a-zA-Z][a-zA-Z0-9]*)(?:(?:\s+)(?:[a-zA-Z_:][a-zA-Z0-9_.:-]*)(?:(?:\s+)?=(?:\s+)?(?:[^"'=<>`]+|'[^']*'|"[^"]*"))?)*(?:\s+)?\/?>)|(?:<\/(?:[a-zA-Z][a-zA-Z0-9]*)(?:\s+)?>)|(?:)|(?:<\?(?:[^\?]|\?(?!>))+\?>)|(?:)|(?:))/, - 'link': /^(!?\[)((?:(?:\[(?:\[(?:\\[\s\S]|[^\[\]])*?\]|\\[\s\S]|[^\[\]])*?\])|\\[\s\S]|[^\[\]])*?)\]\(\s*(?:(?!<)((?:(?:\((?:\\[\s\S]|[^\(\)\s])*?\)|\\[\s\S]|[^\(\)\s])*?))|<([^\n]*?)>)(?:\s+(?:\'((?:\\[\s\S]|[^\'])*?)\'|"((?:\\[\s\S]|[^"])*?)"|\(((?:\\[\s\S]|[^\)])*?)\)))?\s*\)/, - 'reference': /^(!?\[)((?:(?:\[(?:\[(?:\\[\s\S]|[^\[\]])*?\]|\\[\s\S]|[^\[\]])*?\])|\\[\s\S]|[^\[\]])*?)\]\s*\[((?:\\[\s\S]|[^\[\]])*)\]/, - 'paragraph': /^(?:(?:[^\n]+\n?(?!\ {0,3}([-*_])( *\1){2,} *(?=\n|$)|(\ {0,3})(#{1,6})(?:([ \t]+)([^\n]+?))??(?:[ \t]+#+)?\ {0,3}(?=\n|$)|(?=\ {0,3}>)(?:(?:(?:\ {0,3}>[^\n]*\n)*(?:\ {0,3}>[^\n]+(?=\n|$))|(?!\ {0,3}>)(?!\ {0,3}\[((?:[^\\](?:\\|\\(?:\\{2})+)\]|[^\]])+)\]:[ \t\n]*(<[^>\[\]]+>|[^\s\[\]]+)(?:[ \t\n]+['"(]((?:[^\n]|\n(?!\n))*?)['")])?\ {0,3}(?=\n|$))[^\n]+)(?:\n|$))*(?:\ {0,3}>\ {0,3}(?:\n\ {0,3}>\ {0,3})*)?|<(?!(?: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)(?!mailto:)\w+(?!:\/|[^\w\s@]*@)\b))+)/, - 'blockquote': /^(?=[ \t]*>)(?:(?:(?:[ \t]*>[^\n]*\n)*(?:[ \t]*>[^\n]+(?=\n|$))|(?![ \t]*>)(?![ \t]*([-*_])( *\1){2,} *(?=\n|$)|([ \t]*)((?:[*+-]|\d+\.))[ \t][\s\S]+?(?:(?=\n+\3?(?:[-*_][ \t]*){3,}(?:\n|$))|(?=\n+[ \t]*\[((?:[^\\](?:\\|\\(?:\\{2})+)\]|[^\]])+)\]:[ \t\n]*(<[^>\[\]]+>|[^\s\[\]]+)(?:[ \t\n]+['"(]((?:[^\n]|\n(?!\n))*?)['")])?[ \t]*(?=\n|$))|\n{2,}(?![ \t])(?!\3(?:[*+-]|\d+\.)[ \t])|$)|( *)(([`~])\10{2,})[ \t]*([^\n`~]+)?[ \t]*(?:\n([\s\S]*?))??(?:\n\ {0,3}\9\10*[ \t]*(?=\n|$)|$)|((?:(?: {4}|\t)[^\n]*\n?((?:[ \t]*\n)*))+)|[ \t]*\[((?:[^\\](?:\\|\\(?:\\{2})+)\]|[^\]])+)\]:[ \t\n]*(<[^>\[\]]+>|[^\s\[\]]+)(?:[ \t\n]+['"(]((?:[^\n]|\n(?!\n))*?)['")])?[ \t]*(?=\n|$))[^\n]+)(?:\n|$))*(?:[ \t]*>[ \t]*(?:\n[ \t]*>[ \t]*)*)?/, - 'escape': /^\\(\n|[\\`*{}\[\]()#+\-.!_>"$%&',\/:;<=?@^~|])/ - }, - 'commonmarkGFM': { - 'paragraph': /^(?:(?:[^\n]+\n?(?!\ {0,3}([-*_])( *\1){2,} *(?=\n|$)|( *)(([`~])\5{2,})\ {0,3}([^\n`~]+)?\ {0,3}(?:\n([\s\S]*?))??(?:\n\ {0,3}\4\5*\ {0,3}(?=\n|$)|$)|(\ {0,3})((?:[*+-]|\d+\.))[ \t][\s\S]+?(?:(?=\n+\8?(?:[-*_]\ {0,3}){3,}(?:\n|$))|(?=\n+\ {0,3}\[((?:[^\\](?:\\|\\(?:\\{2})+)\]|[^\]])+)\]:[ \t\n]*(<[^>\[\]]+>|[^\s\[\]]+)(?:[ \t\n]+['"(]((?:[^\n]|\n(?!\n))*?)['")])?\ {0,3}(?=\n|$))|\n{2,}(?![ \t])(?!\8(?:[*+-]|\d+\.)[ \t])|$)|(\ {0,3})(#{1,6})(?:([ \t]+)([^\n]+?))??(?:[ \t]+#+)?\ {0,3}(?=\n|$)|(?=\ {0,3}>)(?:(?:(?:\ {0,3}>[^\n]*\n)*(?:\ {0,3}>[^\n]+(?=\n|$))|(?!\ {0,3}>)(?!\ {0,3}\[((?:[^\\](?:\\|\\(?:\\{2})+)\]|[^\]])+)\]:[ \t\n]*(<[^>\[\]]+>|[^\s\[\]]+)(?:[ \t\n]+['"(]((?:[^\n]|\n(?!\n))*?)['")])?\ {0,3}(?=\n|$))[^\n]+)(?:\n|$))*(?:\ {0,3}>\ {0,3}(?:\n\ {0,3}>\ {0,3})*)?|<(?!(?: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)(?!mailto:)\w+(?!:\/|[^\w\s@]*@)\b))+)/ - }, - 'breaks': { - 'break': /^ *\n(?!\s*$)/, - 'inlineText': /^[\s\S]+?(?=[\\/i; +var EXPRESSION_LOOSE_LIST_ITEM = /\n\n(?!\s*$)/; +var EXPRESSION_TASK_ITEM = /^\[([\ \t]|x|X)\][\ \t]/; /* * Characters. */ -var AT_SIGN = '@'; -var CARET = '^'; -var EQUALS = '='; -var EXCLAMATION_MARK = '!'; +var C_SLASH = '\\'; +var C_UNDERSCORE = '_'; +var C_ASTERISK = '*'; +var C_TICK = '`'; +var C_AT_SIGN = '@'; +var C_HASH = '#'; +var C_PLUS = '+'; +var C_DASH = '-'; +var C_DOT = '.'; +var C_PIPE = '|'; +var C_DOUBLE_QUOTE = '"'; +var C_SINGLE_QUOTE = '\''; +var C_COMMA = ','; +var C_BACKSLASH = '/'; +var C_COLON = ':'; +var C_SEMI_COLON = ';'; +var C_QUESTION_MARK = '?'; +var C_CARET = '^'; +var C_EQUALS = '='; +var C_EXCLAMATION_MARK = '!'; +var C_TILDE = '~'; +var C_LT = '<'; +var C_GT = '>'; +var C_BRACKET_OPEN = '['; +var C_BRACKET_CLOSE = ']'; +var C_PAREN_OPEN = '('; +var C_PAREN_CLOSE = ')'; +var C_SPACE = ' '; +var C_FORM_FEED = '\f'; +var C_NEWLINE = '\n'; +var C_CARRIAGE_RETURN = '\r'; +var C_TAB = '\t'; +var C_VERTICAL_TAB = '\v'; +var C_NO_BREAK_SPACE = '\u00a0'; +var C_OGHAM_SPACE = '\u1680'; +var C_MONGOLIAN_VOWEL_SEPARATOR = '\u180e'; +var C_EN_QUAD = '\u2000'; +var C_EM_QUAD = '\u2001'; +var C_EN_SPACE = '\u2002'; +var C_EM_SPACE = '\u2003'; +var C_THREE_PER_EM_SPACE = '\u2004'; +var C_FOUR_PER_EM_SPACE = '\u2005'; +var C_SIX_PER_EM_SPACE = '\u2006'; +var C_FIGURE_SPACE = '\u2007'; +var C_PUNCTUATION_SPACE = '\u2008'; +var C_THIN_SPACE = '\u2009'; +var C_HAIR_SPACE = '\u200a'; +var C_LINE_SEPARATOR = '​\u2028'; +var C_PARAGRAPH_SEPARATOR = '​\u2029'; +var C_NARROW_NO_BREAK_SPACE = '\u202f'; +var C_IDEOGRAPHIC_SPACE = '\u3000'; +var C_ZERO_WIDTH_NO_BREAK_SPACE = '\ufeff'; +var C_X_LOWER = 'x'; + +/* + * Character codes. + */ + +var CC_A_LOWER = 'a'.charCodeAt(0); +var CC_A_UPPER = 'A'.charCodeAt(0); +var CC_Z_LOWER = 'z'.charCodeAt(0); +var CC_Z_UPPER = 'Z'.charCodeAt(0); +var CC_0 = '0'.charCodeAt(0); +var CC_9 = '9'.charCodeAt(0); + +/* + * Protocols. + */ + +var HTTP_PROTOCOL = 'http://'; +var HTTPS_PROTOCOL = 'https://'; var MAILTO_PROTOCOL = 'mailto:'; -var NEW_LINE = '\n'; -var SPACE = ' '; -var TAB = '\t'; -var EMPTY = ''; -var LT = '<'; -var GT = '>'; -var BRACKET_OPEN = '['; + +var PROTOCOLS = [ + HTTP_PROTOCOL, + HTTPS_PROTOCOL, + MAILTO_PROTOCOL +]; + +var PROTOCOLS_LENGTH = PROTOCOLS.length; /* - * Types. + * Textual constants. */ +var YAML_FENCE = repeat(C_DASH, 3); +var CODE_INDENT = repeat(C_SPACE, CODE_INDENT_LENGTH); +var EMPTY = ''; var BLOCK = 'block'; var INLINE = 'inline'; -var HORIZONTAL_RULE = 'horizontalRule'; -var HTML = 'html'; -var YAML = 'yaml'; -var TABLE = 'table'; -var TABLE_CELL = 'tableCell'; -var TABLE_HEADER = 'tableHeader'; -var TABLE_ROW = 'tableRow'; -var PARAGRAPH = 'paragraph'; -var TEXT = 'text'; -var CODE = 'code'; -var LIST = 'list'; -var LIST_ITEM = 'listItem'; -var FOOTNOTE_DEFINITION = 'footnoteDefinition'; -var HEADING = 'heading'; -var BLOCKQUOTE = 'blockquote'; -var LINK = 'link'; -var IMAGE = 'image'; -var FOOTNOTE = 'footnote'; -var ESCAPE = 'escape'; -var STRONG = 'strong'; -var EMPHASIS = 'emphasis'; -var DELETE = 'delete'; -var INLINE_CODE = 'inlineCode'; -var BREAK = 'break'; -var ROOT = 'root'; - -/** - * Wrapper around he's `decode` function. +var COMMENT_START = ''; +var CDATA_START = ''; +var COMMENT_END_CHAR = COMMENT_END.charAt(0); +var CDATA_END_CHAR = CDATA_END.charAt(0); +var COMMENT_START_LENGTH = COMMENT_START.length; +var COMMENT_END_LENGTH = COMMENT_END.length; +var CDATA_START_LENGTH = CDATA_START.length; +var CDATA_END_LENGTH = CDATA_END.length; + +/* + * Node types. + */ + +var T_HORIZONTAL_RULE = 'horizontalRule'; +var T_HTML = 'html'; +var T_YAML = 'yaml'; +var T_TABLE = 'table'; +var T_TABLE_CELL = 'tableCell'; +var T_TABLE_HEADER = 'tableHeader'; +var T_TABLE_ROW = 'tableRow'; +var T_PARAGRAPH = 'paragraph'; +var T_TEXT = 'text'; +var T_CODE = 'code'; +var T_LIST = 'list'; +var T_LIST_ITEM = 'listItem'; +var T_DEFINITION = 'definition' +var T_FOOTNOTE_DEFINITION = 'footnoteDefinition'; +var T_HEADING = 'heading'; +var T_BLOCKQUOTE = 'blockquote'; +var T_LINK = 'link'; +var T_IMAGE = 'image'; +var T_FOOTNOTE = 'footnote'; +var T_ESCAPE = 'escape'; +var T_STRONG = 'strong'; +var T_EMPHASIS = 'emphasis'; +var T_DELETE = 'delete'; +var T_INLINE_CODE = 'inlineCode'; +var T_BREAK = 'break'; +var T_ROOT = 'root'; + +/* + * Available table alignments. + */ + +var TABLE_ALIGN_LEFT = 'left'; +var TABLE_ALIGN_CENTER = 'center'; +var TABLE_ALIGN_RIGHT = 'right'; +var TABLE_ALIGN_NONE = null; + +/* + * Available reference types. + */ + +var REFERENCE_TYPE_SHORTCUT = 'shortcut'; +var REFERENCE_TYPE_COLLAPSED = 'collapsed'; +var REFERENCE_TYPE_FULL = 'full'; + +/* + * A map of characters, and their column length, + * which can be used as indentation. + */ + +var INDENTATION_CHARACTERS = {}; + +INDENTATION_CHARACTERS[C_SPACE] = SPACE_SIZE; +INDENTATION_CHARACTERS[C_TAB] = TAB_SIZE; + +/* + * A map of characters, which can be used to mark emphasis. + */ + +var EMPHASIS_MARKERS = {}; + +EMPHASIS_MARKERS[C_ASTERISK] = true; +EMPHASIS_MARKERS[C_UNDERSCORE] = true; + +/* + * A map of characters, which can be used to mark rules. + */ + +var RULE_MARKERS = {}; + +RULE_MARKERS[C_ASTERISK] = true; +RULE_MARKERS[C_UNDERSCORE] = true; +RULE_MARKERS[C_DASH] = true; + +/* + * A map of characters which can be used to mark + * list-items. + */ + +var LIST_UNORDERED_MARKERS = {}; + +LIST_UNORDERED_MARKERS[C_ASTERISK] = true; +LIST_UNORDERED_MARKERS[C_PLUS] = true; +LIST_UNORDERED_MARKERS[C_DASH] = true; + +/* + * A map of characters which can be used to mark + * list-items after a digit. + */ + +var LIST_ORDERED_MARKERS = {}; + +LIST_ORDERED_MARKERS[C_DOT] = true; + +/* + * A map of characters which can be used to mark + * list-items after a digit. + */ + +var LIST_ORDERED_COMMONMARK_MARKERS = {}; + +LIST_ORDERED_COMMONMARK_MARKERS[C_DOT] = true; +LIST_ORDERED_COMMONMARK_MARKERS[C_PAREN_CLOSE] = true; + +/* + * A map of characters, which can be used to mark link + * and image titles. + */ + +var LINK_TITLE_MARKERS = {}; + +LINK_TITLE_MARKERS[C_DOUBLE_QUOTE] = C_DOUBLE_QUOTE; +LINK_TITLE_MARKERS[C_SINGLE_QUOTE] = C_SINGLE_QUOTE; + +/* + * A map of characters, which can be used to mark link + * and image titles in commonmark-mode. + */ + +var COMMONMARK_LINK_TITLE_MARKERS = {}; + +COMMONMARK_LINK_TITLE_MARKERS[C_DOUBLE_QUOTE] = C_DOUBLE_QUOTE; +COMMONMARK_LINK_TITLE_MARKERS[C_SINGLE_QUOTE] = C_SINGLE_QUOTE; +COMMONMARK_LINK_TITLE_MARKERS[C_PAREN_OPEN] = C_PAREN_CLOSE; + +/* + * A map of characters which can be used to mark setext + * headers, mapping to their corresponding depth. + */ + +var SETEXT_MARKERS = {}; + +SETEXT_MARKERS[C_EQUALS] = 1; +SETEXT_MARKERS[C_DASH] = 2; + +/* + * A map of two functions which can create list items. + */ + +var LIST_ITEM_MAP = {}; + +LIST_ITEM_MAP.true = renderPedanticListItem; +LIST_ITEM_MAP.false = renderNormalListItem; + +/* + * Define nodes of a type which can be merged. + */ + +var MERGEABLE_NODES = {}; + +/** + * Check whether `character` is alphabetic. + * + * @param {string} character - Single character to check. + * @return {boolean} - Whether or not `character` is + * alphabetic. + */ +function isAlphabetic(character) { + var code = character.charCodeAt(0); + + return (code >= CC_A_LOWER && code <= CC_Z_LOWER) || + (code >= CC_A_UPPER && code <= CC_Z_UPPER); +} + +/** + * Check whether `character` is numeric. + * + * @param {string} character - Single character to check. + * @return {boolean} - Whether or not `character` is + * numeric. + */ +function isNumeric(character) { + var code = character.charCodeAt(0); + + return code >= CC_0 && code <= CC_9; +} + +/** + * Check whether `character` is a word character. + * + * @param {string} character - Single character to check. + * @return {boolean} - Whether or not `character` is a + * word character. + */ +function isWordCharacter(character) { + return character === C_UNDERSCORE || + isAlphabetic(character) || + isNumeric(character); +} + +/** + * Check whether `character` is white-space. + * + * @param {string} character - Single character to check. + * @return {boolean} - Whether or not `character` is + * white-space. + */ +function isWhiteSpace(character) { + return character === C_SPACE || + character === C_FORM_FEED || + character === C_NEWLINE || + character === C_CARRIAGE_RETURN || + character === C_TAB || + character === C_VERTICAL_TAB || + character === C_NO_BREAK_SPACE || + character === C_OGHAM_SPACE || + character === C_MONGOLIAN_VOWEL_SEPARATOR || + character === C_EN_QUAD || + character === C_EM_QUAD || + character === C_EN_SPACE || + character === C_EM_SPACE || + character === C_THREE_PER_EM_SPACE || + character === C_FOUR_PER_EM_SPACE || + character === C_SIX_PER_EM_SPACE || + character === C_FIGURE_SPACE || + character === C_PUNCTUATION_SPACE || + character === C_THIN_SPACE || + character === C_HAIR_SPACE || + character === C_LINE_SEPARATOR || + character === C_PARAGRAPH_SEPARATOR || + character === C_NARROW_NO_BREAK_SPACE || + character === C_IDEOGRAPHIC_SPACE || + character === C_ZERO_WIDTH_NO_BREAK_SPACE; +} + +/** + * Check whether `character` can be inside an unquoted + * attribute value. + * + * @param {string} character - Single character to check. + * @return {boolean} - Whether or not `character` can be + * inside an unquoted attribute value. + */ +function isUnquotedAttributeCharacter(character) { + return character !== C_DOUBLE_QUOTE && + character !== C_SINGLE_QUOTE && + character !== C_EQUALS && + character !== C_LT && + character !== C_GT && + character !== C_TICK; +} + +/** + * Check whether `character` can be inside a double-quoted + * attribute value. + * + * @property {string} delimiter - Closing delimiter. + * @param {string} character - Single character to check. + * @return {boolean} - Whether or not `character` can be + * inside a double-quoted attribute value. + */ +function isDoubleQuotedAttributeCharacter(character) { + return character !== C_DOUBLE_QUOTE; +} + +isDoubleQuotedAttributeCharacter.delimiter = C_DOUBLE_QUOTE; + +/** + * Check whether `character` can be inside a single-quoted + * attribute value. + * + * @property {string} delimiter - Closing delimiter. + * @param {string} character - Single character to check. + * @return {boolean} - Whether or not `character` can be + * inside a single-quoted attribute value. + */ +function isSingleQuotedAttributeCharacter(character) { + return character !== C_SINGLE_QUOTE; +} + +isSingleQuotedAttributeCharacter.delimiter = C_SINGLE_QUOTE; + +/** + * Check whether `character` can be inside an enclosed + * URI. + * + * @property {string} delimiter - Closing delimiter. + * @param {string} character - Character to test. + * @return {boolean} - Whether or not `character` can be + * inside an enclosed URI. + */ +function isEnclosedURLCharacter(character) { + return character !== C_GT && + character !== C_BRACKET_OPEN && + character !== C_BRACKET_CLOSE; +} + +isEnclosedURLCharacter.delimiter = C_GT; + +/** + * Check whether `character` can be inside an unclosed + * URI. + * + * @param {string} character - Character to test. + * @return {boolean} - Whether or not `character` can be + * inside an unclosed URI. + */ +function isUnclosedURLCharacter(character) { + return character !== C_BRACKET_OPEN && + character !== C_BRACKET_CLOSE && + !isWhiteSpace(character); +} + +/** + * Wrapper around he’s `decode` function. * * @example * decode('&'); // '&' @@ -109,96 +508,63 @@ function decode(value, eat) { } /** - * Factory to de-escape a value, based on an expression - * at `key` in `scope`. + * Factory to de-escape a value, based on a list at `key` + * in `scope`. * * @example - * var expressions = {escape: /\\(a)/} - * var descape = descapeFactory(expressions, 'escape'); + * var scope = {escape: ['a']} + * var descape = descapeFactory(scope, 'escape'); * - * @param {Object} scope - Map of expressions. - * @param {string} key - Key in `map` at which the - * non-global expression exists. + * @param {Object} scope - List of escapable characters. + * @param {string} key - Key in `map` at which the list + * exists. * @return {function(string): string} - Function which * takes a value and returns its unescaped version. */ function descapeFactory(scope, key) { - var globalExpression; - var expression; - - /** - * Private method to get a global expression - * from the expression at `key` in `scope`. - * This method is smart about not recreating - * the expressions every time. - * - * @private - * @return {RegExp} - */ - function generate() { - if (scope[key] !== globalExpression) { - globalExpression = scope[key]; - expression = new RegExp( - scope[key].source.replace(CARET, EMPTY), 'g' - ); - } - - return expression; - } - /** * De-escape a string using the expression at `key` * in `scope`. * * @example - * var expressions = {escape: /\\(a)/} - * var descape = descapeFactory(expressions, 'escape'); - * descape('\a'); // 'a' + * var scope = {escape: ['a']} + * var descape = descapeFactory(scope, 'escape'); + * descape('\a \b'); // 'a \b' * * @param {string} value - Escaped string. * @return {string} - Unescaped string. */ function descape(value) { - return value.replace(generate(), '$1'); - } - - return descape; -} + var prev = 0; + var index = value.indexOf(C_SLASH); + var escape = scope[key]; + var queue = []; + var character; -/* - * Tab size. - */ + while (index !== -1) { + queue.push(value.slice(prev, index)); + prev = index + 1; + character = value.charAt(prev); -var TAB_SIZE = 4; + /* + * If the following character is not a valid escape, + * add the slash. + */ -/* - * Expressions. - */ + if (!character || escape.indexOf(character) === -1) { + queue.push(C_SLASH); + } -var EXPRESSION_RIGHT_ALIGNMENT = /^[ \t]*-+:[ \t]*$/; -var EXPRESSION_CENTER_ALIGNMENT = /^[ \t]*:-+:[ \t]*$/; -var EXPRESSION_LEFT_ALIGNMENT = /^[ \t]*:-+[ \t]*$/; -var EXPRESSION_TABLE_FENCE = /^[ \t]*|\|[ \t]*$/g; -var EXPRESSION_TABLE_BORDER = /[ \t]*\|[ \t]*/; -var EXPRESSION_BLOCK_QUOTE = /^[ \t]*>[ \t]?/gm; -var EXPRESSION_BULLET = /^([ \t]*)([*+-]|\d+[.)])( {1,4}(?! )| |\t)([^\n]*)/; -var EXPRESSION_PEDANTIC_BULLET = /^([ \t]*)([*+-]|\d+[.)])([ \t]+)/; -var EXPRESSION_INITIAL_INDENT = /^( {1,4}|\t)?/gm; -var EXPRESSION_INITIAL_TAB = /^( {4}|\t)?/gm; -var EXPRESSION_HTML_LINK_OPEN = /^/i; -var EXPRESSION_LOOSE_LIST_ITEM = /\n\n(?!\s*$)/; -var EXPRESSION_TASK_ITEM = /^\[([\ \t]|x|X)\][\ \t]/; + index = value.indexOf(C_SLASH, prev); + } -/* - * A map of characters, and their column length, - * which can be used as indentation. - */ + queue.push(value.slice(prev)); -var INDENTATION_CHARACTERS = {}; + return queue.join(EMPTY); + } -INDENTATION_CHARACTERS[SPACE] = SPACE.length; -INDENTATION_CHARACTERS[TAB] = TAB_SIZE; + return descape; +} /** * Gets indentation information for a line. @@ -263,7 +629,7 @@ function getIndent(value) { * @return {string} - Unindented `value`. */ function removeIndentation(value, maximum) { - var values = value.split(NEW_LINE); + var values = value.split(C_NEWLINE); var position = values.length + 1; var minIndent = Infinity; var matrix = []; @@ -272,7 +638,7 @@ function removeIndentation(value, maximum) { var stops; var padding; - values.unshift(repeat(SPACE, maximum) + EXCLAMATION_MARK); + values.unshift(repeat(C_SPACE, maximum) + C_EXCLAMATION_MARK); while (position--) { indentation = getIndent(values[position]); @@ -310,7 +676,7 @@ function removeIndentation(value, maximum) { minIndent && index !== minIndent ) { - padding = TAB; + padding = C_TAB; } else { padding = EMPTY; } @@ -323,77 +689,7 @@ function removeIndentation(value, maximum) { values.shift(); - return values.join(NEW_LINE); -} - -/** - * Ensure that `value` is at least indented with - * `indent` spaces. Does not support tabs. Does support - * multiple lines. - * - * @example - * ensureIndentation('foo', 2); // ' foo' - * ensureIndentation(' foo', 4); // ' foo' - * - * @param {string} value - * @param {number} indent - The maximum amount of - * spacing to insert. - * @return {string} - indented `value`. - */ -function ensureIndentation(value, indent) { - var values = value.split(NEW_LINE); - var length = values.length; - var index = -1; - var line; - var position; - - while (++index < length) { - line = values[index]; - - position = -1; - - while (++position < indent) { - if (line.charAt(position) !== SPACE) { - values[index] = repeat(SPACE, indent - position) + line; - break; - } - } - } - - return values.join(NEW_LINE); -} - -/** - * Get the alignment from a table rule. - * - * @example - * getAlignment([':-', ':-:', '-:', '--']); - * // ['left', 'center', 'right', null]; - * - * @param {Array.} cells - * @return {Array.} - */ -function getAlignment(cells) { - var results = []; - var index = -1; - var length = cells.length; - var alignment; - - while (++index < length) { - alignment = cells[index]; - - if (EXPRESSION_RIGHT_ALIGNMENT.test(alignment)) { - results[index] = 'right'; - } else if (EXPRESSION_CENTER_ALIGNMENT.test(alignment)) { - results[index] = 'center'; - } else if (EXPRESSION_LEFT_ALIGNMENT.test(alignment)) { - results[index] = 'left'; - } else { - results[index] = null; - } - } - - return results; + return values.join(C_NEWLINE); } /** @@ -447,50 +743,11 @@ function stateToggler(key, state) { } /** - * Construct a state toggler which doesn't toggle. - * - * @example - * var context = {}; - * var key = 'foo'; - * var val = true; - * context[key] = val; - * context.enter = noopToggler(); - * context[key]; // true - * var exit = context.enter(); - * context[key]; // true - * exit(); - * context[key]; // true + * Merge two text nodes: `node` into `prev`. * - * @return {function(): function()} - Enter. - */ -function noopToggler() { - /** - * No-operation. - */ - function exit() {} - - /** - * @return {Function} - */ - function enter() { - return exit; - } - - return enter; -} - -/* - * Define nodes of a type which can be merged. - */ - -var MERGEABLE_NODES = {}; - -/** - * Merge two text nodes: `node` into `prev`. - * - * @param {Object} prev - Preceding sibling. - * @param {Object} node - Following sibling. - * @return {Object} - `prev`. + * @param {Object} prev - Preceding sibling. + * @param {Object} node - Following sibling. + * @return {Object} - `prev`. */ MERGEABLE_NODES.text = function (prev, node) { prev.value += node.value; @@ -517,965 +774,2943 @@ MERGEABLE_NODES.blockquote = function (prev, node) { }; /** - * Merge two lists: `node` into `prev`. Knows, about - * which bullets were used. - * - * @param {Object} prev - Preceding sibling. - * @param {Object} node - Following sibling. - * @return {Object} - `prev`, or `node` when the lists are - * of different types (a different bullet is used). - */ -MERGEABLE_NODES.list = function (prev, node) { - if ( - !this.currentBullet || - this.currentBullet !== this.previousBullet || - this.currentBullet.length !== 1 - ) { - return node; - } - - prev.children = prev.children.concat(node.children); - - return prev; -}; - -/** - * Tokenise a line. Unsets `currentBullet` and - * `previousBullet` if more than one lines are found, thus - * preventing lists from merging when they use different - * bullets. + * Tokenise a line. * * @example * tokenizeNewline(eat, '\n\n'); * * @param {function(string)} eat - * @param {string} $0 - Lines. + * @param {string} value - Rest of content. + * @param {boolean?} [silent] - Whether this is a dry run. + * @return {boolean?} - `true` when matching. */ -function tokenizeNewline(eat, $0) { - if ($0.length > 1) { - this.currentBullet = null; - this.previousBullet = null; +function tokenizeNewline(eat, value, silent) { + var character = value.charAt(0); + var length; + var subvalue; + var queue; + var index; + + if (character !== C_NEWLINE) { + return; } - eat($0); -} + /* istanbul ignore if - never used (yet) */ + if (silent) { + return true; + } -/** - * Tokenise an indented code block. - * - * @example - * tokenizeCode(eat, '\tfoo'); - * - * @param {function(string)} eat - * @param {string} $0 - Whole code. - * @return {Node} - `code` node. - */ -function tokenizeCode(eat, $0) { - $0 = trimTrailingLines($0); + index = 1; + length = value.length; + subvalue = C_NEWLINE; + queue = EMPTY; - return eat($0)(this.renderCodeBlock( - removeIndentation($0, TAB_SIZE), null, eat) - ); -} + while (index < length) { + character = value.charAt(index); -/** - * Tokenise a fenced code block. - * - * @example - * var $0 = '```js\nfoo()\n```'; - * tokenizeFences(eat, $0, '', '```', '`', 'js', 'foo()\n'); - * - * @param {function(string)} eat - * @param {string} $0 - Whole code. - * @param {string} $1 - Initial spacing. - * @param {string} $2 - Initial fence. - * @param {string} $3 - Fence marker. - * @param {string} $4 - Programming language flag. - * @param {string} $5 - Content. - * @return {Node} - `code` node. - */ -function tokenizeFences(eat, $0, $1, $2, $3, $4, $5) { - $0 = trimTrailingLines($0); + if (!isWhiteSpace(character)) { + break; + } - /* - * If the initial fence was preceded by spaces, - * exdent that amount of white space from the code - * block. Because it's possible that the code block - * is exdented, we first have to ensure at least - * those spaces are available. - */ + queue += character; + + if (character === C_NEWLINE) { + subvalue += queue; + queue = EMPTY; + } - if ($1) { - $5 = removeIndentation(ensureIndentation($5, $1.length), $1.length); + index++; } - return eat($0)(this.renderCodeBlock($5, $4, eat)); + eat(subvalue); } /** - * Tokenise an ATX-style heading. + * Tokenise an indented code block. * * @example - * tokenizeHeading(eat, ' # foo', ' ', '#', ' ', 'foo'); + * tokenizeCode(eat, '\tfoo'); * * @param {function(string)} eat - * @param {string} $0 - Whole heading. - * @param {string} $1 - Initial spacing. - * @param {string} $2 - Hashes. - * @param {string} $3 - Internal spacing. - * @param {string} $4 - Content. - * @return {Node} - `heading` node. - */ -function tokenizeHeading(eat, $0, $1, $2, $3, $4) { - var now = eat.now(); + * @param {string} value - Rest of content. + * @param {boolean?} [silent] - Whether this is a dry run. + * @return {Node?|boolean} - `code` node. + */ +function tokenizeCode(eat, value, silent) { + var self = this; + var index = -1; + var length = value.length; + var character; + var subvalue = EMPTY; + var content = EMPTY; + var subvalueQueue = EMPTY; + var contentQueue = EMPTY; + var blankQueue; + var indent; - now.column += ($1 + $2 + ($3 || '')).length; + while (++index < length) { + character = value.charAt(index); - return eat($0)(this.renderHeading($4, $2.length, now)); -} + if (indent) { + indent = false; -/** - * Tokenise a Setext-style heading. - * - * @example - * tokenizeLineHeading(eat, 'foo\n===', '', 'foo', '='); - * - * @param {function(string)} eat - * @param {string} $0 - Whole heading. - * @param {string} $1 - Initial spacing. - * @param {string} $2 - Content. - * @param {string} $3 - Underline marker. - * @return {Node} - `heading` node. - */ -function tokenizeLineHeading(eat, $0, $1, $2, $3) { - var now = eat.now(); + subvalue += subvalueQueue; + content += contentQueue; + subvalueQueue = contentQueue = EMPTY; - now.column += $1.length; + if (character === C_NEWLINE) { + subvalueQueue = contentQueue = character; + } else { + subvalue += character; + content += character; - return eat($0)(this.renderHeading($2, $3 === EQUALS ? 1 : 2, now)); -} + while (++index < length) { + character = value.charAt(index); -/** - * Tokenise a horizontal rule. - * - * @example - * tokenizeHorizontalRule(eat, '***'); - * - * @param {function(string)} eat - * @param {string} $0 - Whole rule. - * @return {Node} - `horizontalRule` node. - */ -function tokenizeHorizontalRule(eat, $0) { - return eat($0)(this.renderVoid(HORIZONTAL_RULE)); -} + if (!character || character === C_NEWLINE) { + contentQueue = subvalueQueue = character; + break; + } -/** - * Tokenise a blockquote. - * - * @example - * tokenizeBlockquote(eat, '> Foo'); - * - * @param {function(string)} eat - * @param {string} $0 - Whole blockquote. - * @return {Node} - `blockquote` node. - */ -function tokenizeBlockquote(eat, $0) { - var now = eat.now(); - var indent = this.indent(now.line); - var value = trimTrailingLines($0); - var add = eat(value); + subvalue += character; + content += character; + } + } + } else if ( + character === C_SPACE && + value.charAt(index + 1) === C_SPACE && + value.charAt(index + 2) === C_SPACE && + value.charAt(index + 3) === C_SPACE + ) { + subvalueQueue += CODE_INDENT; + index += 3; + indent = true; + } else if (character === C_TAB) { + subvalueQueue += character; + indent = true; + } else { + blankQueue = EMPTY; - value = value.replace(EXPRESSION_BLOCK_QUOTE, function (prefix) { - indent(prefix.length); + while (character === C_TAB || character === C_SPACE) { + blankQueue += character; + character = value.charAt(++index); + } - return ''; - }); + if (character !== C_NEWLINE) { + break; + } - return add(this.renderBlockquote(value, now)); + subvalueQueue += blankQueue + character; + contentQueue += character; + } + } + + if (content) { + if (silent) { + return true; + } + + return eat(subvalue)(self.renderCodeBlock(content, null, eat)); + } } /** - * Tokenise a list. + * Tokenise a fenced code block. * * @example - * tokenizeList(eat, '- Foo', '', '-'); + * tokenizeFences(eat, '```js\nfoo()\n```'); * * @param {function(string)} eat - * @param {string} $0 - Whole list. - * @param {string} $1 - Indent. - * @param {string} $2 - Bullet. - * @return {Node} - `list` node. + * @param {string} value - Rest of content. + * @param {boolean?} [silent] - Whether this is a dry run. + * @return {Node?|boolean} - `code` node. */ -function tokenizeList(eat, $0, $1, $2) { +function tokenizeFences(eat, value, silent) { var self = this; - var firstBullet = $2; - var value = trimTrailingLines($0); - var matches = value.match(self.rules.item); - var length = matches.length; - var index = 0; - var isLoose = false; - var now; - var bullet; - var item; - var enterTop; - var exitBlockquote; - var node; + var settings = self.options; + var length = value.length + 1; + var index = -1; + var subvalue = EMPTY; + var fenceCount; + var marker; + var character; + var flag; + var queue; + var content; + var exdentedContent; + var closing; + var exdentedClosing; var indent; - var size; - var position; - var end; + + if (!settings.gfm) { + return; + } + + /** Eat zero or more space / tab characters. */ + function eatOptionalSpacing() { + while (++index < length) { + character = value.charAt(index); + + if (character !== C_SPACE && character !== C_TAB) { + index--; + break; + } + + subvalue += character; + } + } /* - * Determine if all list-items belong to the - * same list. + * Eat initial spacing. */ - if (!self.options.pedantic) { - while (++index < length) { - bullet = self.rules.bullet.exec(matches[index])[0]; + eatOptionalSpacing(); + indent = index + 1; - if ( - firstBullet !== bullet && - ( - firstBullet.length === 1 && bullet.length === 1 || - bullet.charAt(bullet.length - 1) !== - firstBullet.charAt(firstBullet.length - 1) - ) - ) { - matches = matches.slice(0, index); - matches[index - 1] = trimTrailingLines(matches[index - 1]); + /* + * Eat the fence. + */ - length = matches.length; + if (character !== C_TILDE && character !== C_TICK) { + return; + } - break; - } + index++; + marker = character; + fenceCount = 1; + subvalue += character; + + while (++index < length) { + character = value.charAt(index); + + if (character !== marker) { + index--; + break; } + + subvalue += character; + fenceCount++; } - if (self.options.commonmark) { - index = -1; + if (fenceCount < MIN_FENCE_COUNT) { + return; + } - while (++index < length) { - item = matches[index]; - indent = self.rules.indent.exec(item); - indent = indent[1] + repeat(SPACE, indent[2].length) + indent[3]; - size = getIndent(indent).indent; - position = indent.length; - end = item.length; - - while (++position < end) { - if ( - item.charAt(position) === NEW_LINE && - item.charAt(position - 1) === NEW_LINE && - getIndent(item.slice(position + 1)).indent < size - ) { - matches[index] = item.slice(0, position - 1); + /* + * Eat spacing before flag. + */ - matches = matches.slice(0, index + 1); - length = matches.length; + eatOptionalSpacing(); - break; - } + /* + * Eat flag. + */ + + flag = queue = EMPTY; + + while (++index < length) { + character = value.charAt(index); + + if ( + character === C_NEWLINE || + character === C_TILDE || + character === C_TICK + ) { + index--; + break; + } + + if (character === C_SPACE || character === C_TAB) { + queue += character; + } else { + if (queue) { + flag += queue; + queue = EMPTY; } + + flag += character; } } - self.previousBullet = self.currentBullet; - self.currentBullet = firstBullet; + if (character && character !== C_NEWLINE) { + return; + } - index = -1; + if (silent) { + return true; + } - node = eat(matches.join(NEW_LINE)).reset( - self.renderList([], firstBullet) - ); + subvalue += flag; - enterTop = self.exitTop(); - exitBlockquote = self.enterBlockquote(); + if (queue) { + subvalue += queue; + } + + queue = closing = exdentedClosing = content = exdentedContent = EMPTY; + + /* + * Eat content. + */ while (++index < length) { - item = matches[index]; - now = eat.now(); + character = value.charAt(index); + content += closing; + exdentedContent += exdentedClosing; + closing = exdentedClosing = EMPTY; + + if (character !== C_NEWLINE) { + content += character; + exdentedClosing += character; + continue; + } - item = eat(item)(self.renderListItem(item, now), node); + /* + * Add the newline to `subvalue` if its the first + * character. Otherwise, add it to the `closing` + * queue. + */ - if (item.loose) { - isLoose = true; + if (!content) { + subvalue += character; + } else { + closing += character; + exdentedClosing += character; } - if (index !== length - 1) { - eat(NEW_LINE); + queue = EMPTY; + + while (++index < length) { + character = value.charAt(index); + + if (character !== C_SPACE) { + index--; + break; + } + + queue += character; } - } - node.loose = isLoose; + closing += queue; + exdentedClosing += queue.slice(indent); - enterTop(); - exitBlockquote(); + if (queue.length >= CODE_INDENT_LENGTH) { + continue; + } - return node; -} + queue = EMPTY; -/** - * Tokenise HTML. - * - * @example - * tokenizeHtml(eat, 'foo'); - * - * @param {function(string)} eat - * @param {string} $0 - Whole HTML. - * @return {Node} - `html` node. - */ -function tokenizeHtml(eat, $0) { - $0 = trimTrailingLines($0); + while (++index < length) { + character = value.charAt(index); + + if (character !== marker) { + index--; + break; + } + + queue += character; + } + + closing += queue; + exdentedClosing += queue; + + if (queue.length < fenceCount) { + continue; + } + + eatOptionalSpacing(); + + character = value.charAt(++index); + + if (!character || character === C_NEWLINE) { + break; + } + + closing += character; + exdentedClosing += character; + } + + subvalue += content + closing; - return eat($0)(this.renderRaw(HTML, $0)); + return eat(subvalue)(self.renderCodeBlock(exdentedContent, flag, eat)); } /** - * Tokenise a definition. + * Tokenise an ATX-style heading. * * @example - * var $0 = '[foo]: http://example.com "Example Domain"'; - * var $1 = 'foo'; - * var $2 = 'http://example.com'; - * var $3 = 'Example Domain'; - * tokenizeDefinition(eat, $0, $1, $2, $3); + * tokenizeHeading(eat, ' # foo'); * - * @property {boolean} onlyAtTop - * @property {boolean} notInBlockquote * @param {function(string)} eat - * @param {string} $0 - Whole definition. - * @param {string} $1 - Key. - * @param {string} $2 - URL. - * @param {string} $3 - Title. - * @return {Node} - `definition` node. + * @param {string} value - Rest of content. + * @param {boolean?} [silent] - Whether this is a dry run. + * @return {Node?|boolean} - `heading` node. */ -function tokenizeDefinition(eat, $0, $1, $2, $3) { - var link = $2; +function tokenizeHeading(eat, value, silent) { + var self = this; + var settings = self.options; + var length = value.length + 1; + var index = -1; + var now = eat.now(); + var subvalue = EMPTY; + var content = EMPTY; + var character; + var queue; + var depth; /* - * Remove angle-brackets from `link`. + * Eat initial spacing. */ - if (link.charAt(0) === LT && link.charAt(link.length - 1) === GT) { - link = link.slice(1, -1); + while (++index < length) { + character = value.charAt(index); + + if (character !== C_SPACE && character !== C_TAB) { + index--; + break; + } + + subvalue += character; } - return eat($0)({ - 'type': 'definition', - 'identifier': normalize($1), - 'title': $3 ? decode(this.descape($3), eat) : null, - 'link': decode(this.descape(link), eat) - }); -} - -tokenizeDefinition.onlyAtTop = true; -tokenizeDefinition.notInBlockquote = true; + /* + * Eat hashes. + */ -/** - * Tokenise YAML front matter. - * - * @example - * var $0 = '---\nfoo: bar\n---'; - * var $1 = 'foo: bar'; - * tokenizeYAMLFrontMatter(eat, $0, $1); - * - * @property {boolean} onlyAtStart - * @param {function(string)} eat - * @param {string} $0 - Whole front matter. - * @param {string} $1 - Content. - * @return {Node} - `yaml` node. - */ -function tokenizeYAMLFrontMatter(eat, $0, $1) { - return eat($0)(this.renderRaw(YAML, $1 ? trimTrailingLines($1) : EMPTY)); -} + depth = 0; + length = index + MAX_ATX_COUNT + 1; -tokenizeYAMLFrontMatter.onlyAtStart = true; + while (++index <= length) { + character = value.charAt(index); -/** - * Tokenise a footnote definition. - * - * @example - * var $0 = '[foo]: Bar.'; - * var $1 = '[foo]'; - * var $2 = 'foo'; - * var $3 = 'Bar.'; - * tokenizeFootnoteDefinition(eat, $0, $1, $2, $3); - * - * @property {boolean} onlyAtTop - * @property {boolean} notInBlockquote - * @param {function(string)} eat - * @param {string} $0 - Whole definition. - * @param {string} $1 - Whole key. - * @param {string} $2 - Key. - * @param {string} $3 - Whole value. - * @return {Node} - `footnoteDefinition` node. - */ -function tokenizeFootnoteDefinition(eat, $0, $1, $2, $3) { - var self = this; - var now = eat.now(); - var indent = self.indent(now.line); + if (character !== C_HASH) { + index--; + break; + } - $3 = $3.replace(EXPRESSION_INITIAL_TAB, function (value) { - indent(value.length); + subvalue += character; + depth++; + } - return EMPTY; - }); + if ( + !depth || + (!settings.pedantic && value.charAt(index + 1) === C_HASH) + ) { + return; + } - now.column += $1.length; + length = value.length + 1; - return eat($0)(self.renderFootnoteDefinition(normalize($2), $3, now)); -} + /* + * Eat intermediate white-space. + */ -tokenizeFootnoteDefinition.onlyAtTop = true; -tokenizeFootnoteDefinition.notInBlockquote = true; + queue = EMPTY; -/** - * Tokenise a table. - * - * @example - * var $0 = ' | foo |\n | --- |\n | bar |'; - * var $1 = ' | foo |'; - * var $2 = '| foo |'; - * var $3 = ' | --- |'; - * var $4 = '| --- |'; - * var $5 = ' | bar |'; - * tokenizeTable(eat, $0, $1, $2, $3, $4, $5); - * - * @property {boolean} onlyAtTop - * @param {function(string)} eat - * @param {string} $0 - Whole table. - * @param {string} $1 - Whole heading. - * @param {string} $2 - Trimmed heading. - * @param {string} $3 - Whole alignment. - * @param {string} $4 - Trimmed alignment. - * @param {string} $5 - Rows. - * @return {Node} - `table` node. - */ -function tokenizeTable(eat, $0, $1, $2, $3, $4, $5) { - var self = this; - var length; - var index; - var node; + while (++index < length) { + character = value.charAt(index); - $0 = trimTrailingLines($0); + if (character !== C_SPACE && character !== C_TAB) { + index--; + break; + } - node = eat($0).reset({ - 'type': TABLE, - 'align': [], - 'children': [] - }); + queue += character; + } - /** - * Eat a row of type `type`. - * - * @param {string} type - Type of the returned node, - * such as `tableHeader` or `tableRow`. - * @param {string} value - Row, including initial and - * final fences. + /* + * Exit when not in pedantic mode without spacing. */ - function renderRow(type, value) { - var row = eat(value).reset(self.renderParent(type, []), node); - var length = value.length + 1; - var index = -1; - var queue = ''; - var cell = ''; - var preamble = true; - var count; - var opening; - var character; - var subvalue; - var now; - while (++index < length) { - character = value.charAt(index); + if ( + !settings.pedantic && + !queue.length && + character && + character !== C_NEWLINE + ) { + return; + } - if (character === '\t' || character === ' ') { - if (cell) { - queue += character; - } else { - eat(character); - } + if (silent) { + return true; + } - continue; - } + /* + * Eat content. + */ - if (character === '|' || character === '') { - if (preamble) { - eat(character); - } else { - if (character && opening) { - // cell += queue + character; - queue += character; - continue; - } + subvalue += queue; + queue = content = EMPTY; - if ((cell || character) && !preamble) { - subvalue = cell; + while (++index < length) { + character = value.charAt(index); - if (queue.length > 1) { - if (character) { - subvalue += queue.slice(0, queue.length - 1); - queue = queue.charAt(queue.length - 1); - } else { - subvalue += queue; - queue = ''; - } - } + if (!character || character === C_NEWLINE) { + break; + } - now = eat.now(); + if ( + character !== C_SPACE && + character !== C_TAB && + character !== C_HASH + ) { + content += queue + character; + queue = EMPTY; + continue; + } - eat(subvalue)( - self.renderInline(TABLE_CELL, cell, now), row - ); - } + while (character === C_SPACE || character === C_TAB) { + queue += character; + character = value.charAt(++index); + } - eat(queue + character); + while (character === C_HASH) { + queue += character; + character = value.charAt(++index); + } - queue = ''; - cell = ''; - } - } else { - if (queue) { - cell += queue; - queue = ''; - } + while (character === C_SPACE || character === C_TAB) { + queue += character; + character = value.charAt(++index); + } - cell += character; + index--; + } - if (character === '\\' && index !== length - 2) { - cell += value.charAt(index + 1); - index++; - } + now.column += subvalue.length; + subvalue += content + queue; - if (character === '`') { - count = 1; + return eat(subvalue)(self.renderHeading(content, depth, now)); +} - while (value.charAt(index + 1) === character) { - cell += character; - index++; - count++; - } +/** + * Tokenise a Setext-style heading. + * + * @example + * tokenizeLineHeading(eat, 'foo\n==='); + * + * @param {function(string)} eat + * @param {string} value - Rest of content. + * @param {boolean?} [silent] - Whether this is a dry run. + * @return {Node?|boolean} - `heading` node. + */ +function tokenizeLineHeading(eat, value, silent) { + var self = this; + var now = eat.now(); + var length = value.length; + var index = -1; + var subvalue = EMPTY; + var content; + var queue; + var character; + var marker; + var depth; - if (!opening) { - opening = count; - } else if (count >= opening) { - opening = 0; - } - } - } + /* + * Eat initial indentation. + */ - preamble = false; + while (++index < length) { + character = value.charAt(index); + + if (character !== C_SPACE || index >= MAX_LINE_HEADING_INDENT) { + index--; + break; } + + subvalue += character; } /* - * Add the table's header. + * Eat content. */ - renderRow(TABLE_HEADER, $1); + content = queue = EMPTY; + + while (++index < length) { + character = value.charAt(index); + + if (character === C_NEWLINE) { + index--; + break; + } + + if (character === C_SPACE || character === C_TAB) { + queue += character; + } else { + content += queue + character; + queue = EMPTY; + } + } - eat(NEW_LINE); + now.column += subvalue.length; + subvalue += content + queue; /* - * Add the table's alignment. + * Ensure the content is followed by a newline and a + * valid marker. */ - eat($3); + character = value.charAt(++index); + marker = value.charAt(++index); + + if ( + character !== C_NEWLINE || + !SETEXT_MARKERS[marker] + ) { + return; + } - $4 = $4 - .replace(EXPRESSION_TABLE_FENCE, EMPTY) - .split(EXPRESSION_TABLE_BORDER); + if (silent) { + return true; + } - node.align = getAlignment($4); + subvalue += character; /* - * Add the table rows to table's children. + * Eat Setext-line. */ - $5 = trimTrailingLines($5).split(NEW_LINE); - - index = -1; - length = $5.length; + queue = marker; + depth = SETEXT_MARKERS[marker]; while (++index < length) { - renderRow(TABLE_ROW, $5[index]); + character = value.charAt(index); - if (index !== length - 1) { - eat(NEW_LINE); + if (character !== marker) { + if (character !== C_NEWLINE) { + return; + } + + index--; + break; } + + queue += character; } - return node; + return eat(subvalue + queue)(self.renderHeading(content, depth, now)); } -tokenizeTable.onlyAtTop = true; - /** - * Tokenise a paragraph node. + * Tokenise a horizontal rule. * * @example - * tokenizeParagraph(eat, 'Foo.'); + * tokenizeHorizontalRule(eat, '***'); * * @param {function(string)} eat - * @param {string} $0 - Whole paragraph. - * @return {Node?} - `paragraph` node, when the node does - * not just contain white space. + * @param {string} value - Rest of content. + * @param {boolean?} [silent] - Whether this is a dry run. + * @return {Node?|boolean} - `horizontalRule` node. */ -function tokenizeParagraph(eat, $0) { - var now = eat.now(); +function tokenizeHorizontalRule(eat, value, silent) { + var self = this; + var index = -1; + var length = value.length + 1; + var subvalue = EMPTY; + var character; + var marker; + var markerCount; + var queue; - if (trim($0) === EMPTY) { - eat($0); + while (++index < length) { + character = value.charAt(index); - return null; + if (character !== C_TAB && character !== C_SPACE) { + break; + } + + subvalue += character; + } + + if (RULE_MARKERS[character] !== true) { + return; } - $0 = trimTrailingLines($0); + marker = character; + subvalue += character; + markerCount = 1; + queue = EMPTY; + + while (++index < length) { + character = value.charAt(index); + + if (character === marker) { + markerCount++; + subvalue += queue + marker; + queue = EMPTY; + } else if (character === C_SPACE) { + queue += character; + } else if ( + markerCount >= HORIZONTAL_RULE_MARKER_COUNT && + (!character || character === C_NEWLINE) + ) { + subvalue += queue; + + if (silent) { + return true; + } - return eat($0)(this.renderInline(PARAGRAPH, $0, now)); + return eat(subvalue)(self.renderVoid(T_HORIZONTAL_RULE)); + } else { + return; + } + } } /** - * Tokenise a text node. + * Tokenise a blockquote. * * @example - * tokenizeText(eat, 'foo'); + * tokenizeBlockquote(eat, '> Foo'); * * @param {function(string)} eat - * @param {string} $0 - Whole text. - * @return {Node} - `text` node. + * @param {string} value - Rest of content. + * @param {boolean?} [silent] - Whether this is a dry run. + * @return {Node?|boolean} - `blockquote` node. */ -function tokenizeText(eat, $0) { - return eat($0)(this.renderRaw(TEXT, $0)); -} +function tokenizeBlockquote(eat, value, silent) { + var self = this; + var commonmark = self.options.commonmark; + var now = eat.now(); + var indent = self.indent(now.line); + var length = value.length; + var values = []; + var contents = []; + var indents = []; + var add; + var tokenizers; + var index = 0; + var character; + var rest; + var nextIndex; + var content; + var line; + var startIndex; + var prefixed; -/** - * Create a code-block node. - * - * @example - * renderCodeBlock('foo()', 'js', now()); - * - * @param {string?} [value] - Code. - * @param {string?} [language] - Optional language flag. - * @param {Function} eat - * @return {Object} - `code` node. - */ -function renderCodeBlock(value, language, eat) { - return { - 'type': CODE, - 'lang': language ? decode(this.descape(language), eat) : null, - 'value': trimTrailingLines(value || EMPTY) - }; -} + while (index < length) { + character = value.charAt(index); -/** - * Create a list node. - * - * @example - * var children = [renderListItem('- foo')]; - * renderList(children, '-'); - * - * @param {string} children - Children. - * @param {string} bullet - First bullet. - * @return {Object} - `list` node. - */ -function renderList(children, bullet) { - var start = parseInt(bullet, 10); + if (character !== C_SPACE && character !== C_TAB) { + break; + } - if (start !== start) { - start = null; + index++; } - /* - * `loose` should be added later. - */ + if (value.charAt(index) !== C_GT) { + return; + } - return { - 'type': LIST, - 'ordered': bullet.length > 1, - 'start': start, - 'loose': null, - 'children': children - }; -} + if (silent) { + return true; + } -/** - * Create a list-item using overly simple mechanics. - * - * @example - * renderPedanticListItem('- _foo_', now()); - * - * @param {string} value - List-item. - * @param {Object} position - List-item location. - * @return {string} - Cleaned `value`. - */ -function renderPedanticListItem(value, position) { - var self = this; - var indent = self.indent(position.line); + tokenizers = self.blockTokenizers; - /** - * A simple replacer which removed all matches, - * and adds their length to `offset`. - * - * @param {string} $0 - * @return {string} - */ - function replacer($0) { - indent($0.length); - - return EMPTY; - } - - /* - * Remove the list-item's bullet. - */ + while (index < length) { + nextIndex = value.indexOf(C_NEWLINE, index); + startIndex = index; + prefixed = false; - value = value.replace(EXPRESSION_PEDANTIC_BULLET, replacer); - - /* - * The initial line was also matched by the below, so - * we reset the `line`. - */ + if (nextIndex === -1) { + nextIndex = length; + } - indent = self.indent(position.line); + while (index < length) { + character = value.charAt(index); - return value.replace(EXPRESSION_INITIAL_INDENT, replacer); -} + if (character !== C_SPACE && character !== C_TAB) { + break; + } -/** - * Create a list-item using sane mechanics. - * - * @example - * renderNormalListItem('- _foo_', now()); - * - * @param {string} value - List-item. - * @param {Object} position - List-item location. - * @return {string} - Cleaned `value`. - */ -function renderNormalListItem(value, position) { - var self = this; - var indent = self.indent(position.line); - var bullet; - var rest; - var lines; - var trimmedLines; - var index; - var length; - var max; + index++; + } - /* - * Remove the list-item's bullet. - */ + if (value.charAt(index) === C_GT) { + index++; + prefixed = true; - value = value.replace(EXPRESSION_BULLET, function ($0, $1, $2, $3, $4) { - bullet = $1 + $2 + $3; - rest = $4; + if (value.charAt(index) === C_SPACE) { + index++; + } + } else { + index = startIndex; + } - /* - * Make sure that the first nine numbered list items - * can indent with an extra space. That is, when - * the bullet did not receive an extra final space. - */ + content = value.slice(index, nextIndex); - if (Number($2) < 10 && bullet.length % 2 === 1) { - $2 = SPACE + $2; + if (!prefixed && !trim(content)) { + index = startIndex; + break; } - max = $1 + repeat(SPACE, $2.length) + $3; - - return max + rest; - }); + if (!prefixed) { + rest = value.slice(index); - lines = value.split(NEW_LINE); + if ( + commonmark && + ( + tokenizers.code.call(self, eat, rest, true) || + tokenizers.fences.call(self, eat, rest, true) || + tokenizers.heading.call(self, eat, rest, true) || + tokenizers.lineHeading.call(self, eat, rest, true) || + tokenizers.horizontalRule.call(self, eat, rest, true) || + tokenizers.html.call(self, eat, rest, true) || + tokenizers.list.call(self, eat, rest, true) + ) + ) { + break; + } - trimmedLines = removeIndentation( - value, getIndent(max).indent - ).split(NEW_LINE); + if ( + !commonmark && + ( + tokenizers.definition.call(self, eat, rest, true) || + tokenizers.footnoteDefinition.call(self, eat, rest, true) + ) + ) { + break; + } + } - /* - * We replaced the initial bullet with something - * else above, which was used to trick - * `removeIndentation` into removing some more - * characters when possible. However, that could - * result in the initial line to be stripped more - * than it should be. - */ + line = startIndex === index ? + content : + value.slice(startIndex, nextIndex); - trimmedLines[0] = rest; + indents.push(index - startIndex); + values.push(line); + contents.push(content); - indent(bullet.length); + index = nextIndex + 1; + } - index = 0; - length = lines.length; + index = -1; + length = indents.length; + add = eat(values.join(C_NEWLINE)); while (++index < length) { - indent(lines[index].length - trimmedLines[index].length); + indent(indents[index]); } - return trimmedLines.join(NEW_LINE); + return add(self.renderBlockquote(contents.join(C_NEWLINE), now)); } -/* - * A map of two functions which can create list items. - */ - -var LIST_ITEM_MAP = {}; - -LIST_ITEM_MAP.true = renderPedanticListItem; -LIST_ITEM_MAP.false = renderNormalListItem; - /** - * Create a list-item node. + * Tokenise a list. * * @example - * renderListItem('- _foo_', now()); + * tokenizeList(eat, '- Foo'); * - * @param {Object} value - List-item. - * @param {Object} position - List-item location. - * @return {Object} - `listItem` node. + * @param {function(string)} eat + * @param {string} value - Rest of content. + * @param {boolean?} [silent] - Whether this is a dry run. + * @return {Node?|boolean} - `list` node. */ -function renderListItem(value, position) { +function tokenizeList(eat, value, silent) { var self = this; - var checked = null; + var commonmark = self.options.commonmark; + var pedantic = self.options.pedantic; + var tokenizers = self.blockTokenizers; + var markers; + var index = 0; + var length = value.length; + var start = null; + var queue; + var ordered; + var character; + var marker; + var nextIndex; + var startIndex; + var prefixed; + var currentMarker; + var content; + var line; + var prevEmpty; + var empty; + var items; + var allLines; + var emptyLines; + var item; + var enterTop; + var exitBlockquote; + var isLoose; var node; - var task; - var indent; + var now; + var end; + var indented; + var size; - value = LIST_ITEM_MAP[self.options.pedantic].apply(self, arguments); + while (index < length) { + character = value.charAt(index); - if (self.options.gfm) { - task = value.match(EXPRESSION_TASK_ITEM); + if (character !== C_SPACE && character !== C_TAB) { + break; + } - if (task) { - indent = task[0].length; - checked = task[1].toLowerCase() === 'x'; + index++; + } - self.indent(position.line)(indent); - value = value.slice(indent); + character = value.charAt(index); + + markers = commonmark ? + LIST_ORDERED_COMMONMARK_MARKERS : + LIST_ORDERED_MARKERS; + + if (LIST_UNORDERED_MARKERS[character] === true) { + marker = character; + ordered = false; + } else { + ordered = true; + queue = EMPTY; + + while (index < length) { + character = value.charAt(index); + + if (!isNumeric(character)) { + break; + } + + queue += character; + index++; + } + + character = value.charAt(index); + + if (!queue || markers[character] !== true) { + return; } + + start = parseInt(queue, 10); + marker = character; } - node = { - 'type': LIST_ITEM, - 'loose': EXPRESSION_LOOSE_LIST_ITEM.test(value) || - value.charAt(value.length - 1) === NEW_LINE - }; + character = value.charAt(++index); - if (self.options.gfm) { - node.checked = checked; + if (character !== C_SPACE && character !== C_TAB) { + return; } - node.children = self.tokenizeBlock(value, position); + if (silent) { + return true; + } - return node; -} + index = 0; + items = []; + allLines = []; + emptyLines = []; + + while (index < length) { + nextIndex = value.indexOf(C_NEWLINE, index); + startIndex = index; + prefixed = false; + indented = false; + + if (nextIndex === -1) { + nextIndex = length; + } -/** - * Create a footnote-definition node. - * - * @example - * renderFootnoteDefinition('1', '_foo_', now()); - * - * @param {string} identifier - Unique reference. - * @param {string} value - Contents - * @param {Object} position - Definition location. - * @return {Object} - `footnoteDefinition` node. - */ -function renderFootnoteDefinition(identifier, value, position) { - var self = this; - var exitBlockquote = self.enterBlockquote(); - var node; + end = index + TAB_SIZE; + size = 0; - node = { - 'type': FOOTNOTE_DEFINITION, - 'identifier': identifier, - 'children': self.tokenizeBlock(value, position) - }; + while (index < length) { + character = value.charAt(index); - exitBlockquote(); + if (character === C_TAB) { + size += TAB_SIZE - size % TAB_SIZE; + } else if (character === C_SPACE) { + size++; + } else { + break; + } - return node; -} + index++; + } -/** - * Create a heading node. - * - * @example - * renderHeading('_foo_', 1, now()); - * - * @param {string} value - Content. - * @param {number} depth - Heading depth. - * @param {Object} position - Heading content location. - * @return {Object} - `heading` node - */ -function renderHeading(value, depth, position) { - return { - 'type': HEADING, - 'depth': depth, - 'children': this.tokenizeInline(value, position) - }; -} + if (size >= TAB_SIZE) { + indented = true; + } -/** - * Create a blockquote node. - * - * @example - * renderBlockquote('_foo_', eat); - * - * @param {string} value - Content. - * @param {Object} now - Position. - * @return {Object} - `blockquote` node. - */ -function renderBlockquote(value, now) { - var self = this; - var exitBlockquote = self.enterBlockquote(); - var node = { - 'type': BLOCKQUOTE, - 'children': this.tokenizeBlock(value, now) - }; + if (item && size >= item.indent) { + indented = true; + } - exitBlockquote(); + character = value.charAt(index); + currentMarker = null; - return node; -} + if (!indented) { + if (LIST_UNORDERED_MARKERS[character] === true) { + currentMarker = character; + index++; + size++; + } else { + queue = EMPTY; -/** - * Create a void node. - * - * @example - * renderVoid('horizontalRule'); - * - * @param {string} type - Node type. - * @return {Object} - Node of type `type`. - */ -function renderVoid(type) { - return { - 'type': type - }; -} + while (index < length) { + character = value.charAt(index); -/** - * Create a parent. - * - * @example - * renderParent('paragraph', '_foo_'); - * - * @param {string} type - Node type. - * @param {Array.} children - Child nodes. - * @return {Object} - Node of type `type`. - */ -function renderParent(type, children) { - return { - 'type': type, - 'children': children - }; -} + if (!isNumeric(character)) { + break; + } -/** - * Create a raw node. - * - * @example + queue += character; + index++; + } + + character = value.charAt(index); + index++; + + if (queue && markers[character] === true) { + currentMarker = character; + size += queue.length + 1; + } + } + + if (currentMarker) { + character = value.charAt(index); + + if (character === C_TAB) { + size += TAB_SIZE - size % TAB_SIZE; + index++; + } else if (character === C_SPACE) { + end = index + TAB_SIZE; + + while (index < end) { + if (value.charAt(index) !== C_SPACE) { + break; + } + + index++; + size++; + } + + if (index === end && value.charAt(index) === C_SPACE) { + index -= TAB_SIZE - 1; + size -= TAB_SIZE - 1; + } + } else { + currentMarker = null; + } + } + } + + if (currentMarker) { + // TODO: should be commonmark only. + // gfm sees the following as one list too: + // + // * one + // + two + // 1. three + // + if (!pedantic && marker !== currentMarker) { + break; + } + + prefixed = true; + } else { + if ( + !commonmark && + !indented && + value.charAt(startIndex) === C_SPACE + ) { + indented = true; + } else if ( + commonmark && + item + ) { + indented = size >= item.indent || size > TAB_SIZE; + } + + prefixed = false; + index = startIndex; + } + + line = value.slice(startIndex, nextIndex); + content = startIndex === index ? line : value.slice(index, nextIndex); + + if (currentMarker && RULE_MARKERS[currentMarker] === true) { + if ( + tokenizers.horizontalRule.call(self, eat, line, true) + ) { + break; + } + } + + prevEmpty = empty; + empty = !trim(content).length; + + if (indented && item) { + item.value = item.value.concat(emptyLines, line); + allLines = allLines.concat(emptyLines, line); + emptyLines = []; + } else if (prefixed) { + if (emptyLines.length) { + item.value.push(EMPTY) + item.trail = emptyLines.concat(); + } + + item = { + // 'bullet': value.slice(startIndex, index), + 'value': [line], + 'indent': size, + 'trail': [] + }; + + items.push(item); + allLines = allLines.concat(emptyLines, line); + emptyLines = []; + } else if (empty) { + // TODO: disable when in pedantic-mode. + if (prevEmpty) { + break; + } + + emptyLines.push(line); + } else { + if (prevEmpty) { + break; + } + + // TODO: Should not be possible in commonmark. + // if (!commonmark) { + if ( + tokenizers.definition.call(self, eat, line, true) || + tokenizers.footnoteDefinition.call(self, eat, line, true) + ) { + break; + } + // } + + item.value = item.value.concat(emptyLines, line); + allLines = allLines.concat(emptyLines, line); + emptyLines = []; + } + + index = nextIndex + 1; + } + + node = eat(allLines.join(C_NEWLINE)).reset({ + 'type': T_LIST, + 'ordered': ordered, + 'start': start, + 'loose': null, + 'children': [] + }); + + enterTop = self.exitTop(); + exitBlockquote = self.enterBlockquote(); + isLoose = false; + index = -1; + length = items.length; + + while (++index < length) { + item = items[index].value.join(C_NEWLINE); + now = eat.now(); + + item = eat(item)(self.renderListItem(item, now), node); + + if (item.loose) { + isLoose = true; + } + + item = items[index].trail.join(C_NEWLINE); + + if (index !== length - 1) { + item += C_NEWLINE; + } + + eat(item); + } + + enterTop(); + exitBlockquote(); + + node.loose = isLoose; + + return node; +} + +/** + * Try to match comment. + * + * @param {string} value - Value to parse. + * @return {string?} - When applicable, the comment at the + * start of `value`. + */ +function eatHTMLComment(value, settings) { + var index = COMMENT_START_LENGTH; + var queue = COMMENT_START; + var length = value.length; + var commonmark = settings.commonmark; + var character; + var hasNonDash; + + if (value.slice(0, index) === queue) { + while (index < length) { + character = value.charAt(index); + + if ( + character === COMMENT_END_CHAR && + value.slice(index, index + COMMENT_END_LENGTH) === COMMENT_END + ) { + return queue + COMMENT_END; + } + + if (commonmark) { + if (character === C_GT && !hasNonDash) { + return; + } + + if (character === C_DASH) { + if (value.charAt(index + 1) === C_DASH) { + return; + } + } else { + hasNonDash = true; + } + } + + queue += character; + index++; + } + } +} + +/** + * Try to match CDATA. + * + * @param {string} value - Value to parse. + * @return {string?} - When applicable, the CDATA at the + * start of `value`. + */ +function eatHTMLCDATA(value) { + var index = CDATA_START_LENGTH; + var queue = value.slice(0, index); + var length = value.length; + var character; + + if (queue.toUpperCase() === CDATA_START) { + while (index < length) { + character = value.charAt(index); + + if ( + character === CDATA_END_CHAR && + value.slice(index, index + CDATA_END_LENGTH) === CDATA_END + ) { + return queue + CDATA_END; + } + + queue += character; + index++; + } + } +} + +/** + * Try to match a processing instruction. + * + * @param {string} value - Value to parse. + * @return {string?} - When applicable, the processing + * instruction at the start of `value`. + */ +function eatHTMLProcessingInstruction(value) { + var index = 0; + var queue = EMPTY; + var length = value.length; + var character; + + if ( + value.charAt(index) === C_LT && + value.charAt(++index) === C_QUESTION_MARK + ) { + queue = C_LT + C_QUESTION_MARK; + index++; + + while (index < length) { + character = value.charAt(index); + + if ( + character === C_QUESTION_MARK && + value.charAt(index + 1) === C_GT + ) { + return queue + character + C_GT; + } + + queue += character; + index++; + } + } +} + +/** + * Try to match a declaration. + * + * @param {string} value - Value to parse. + * @return {string?} - When applicable, the declaration at + * the start of `value`. + */ +function eatHTMLDeclaration(value) { + var index = 0; + var length = value.length; + var queue = EMPTY; + var subqueue = EMPTY; + var character; + + if ( + value.charAt(index) === C_LT && + value.charAt(++index) === C_EXCLAMATION_MARK + ) { + queue = C_LT + C_EXCLAMATION_MARK; + index++; + + /* + * Eat as many alphabetic characters as + * possible. + */ + + while (index < length) { + character = value.charAt(index); + + if (!isAlphabetic(character)) { + break; + } + + subqueue += character; + index++; + } + + character = value.charAt(index); + + if (!subqueue || !isWhiteSpace(character)) { + return; + } + + queue += subqueue + character; + index++; + + while (index < length) { + character = value.charAt(index); + + if (character === C_GT) { + return queue; + } + + queue += character; + index++; + } + } +} + +/** + * Try to match a closing tag. + * + * @param {string} value - Value to parse. + * @param {boolean?} [isBlock] - Whether the tag-name + * must be a known block-level node to match. + * @return {string?} - When applicable, the closing tag at + * the start of `value`. + */ +function eatHTMLClosingTag(value, isBlock) { + var index = 0; + var length = value.length; + var queue = EMPTY; + var subqueue = EMPTY; + var character; + + if ( + value.charAt(index) === C_LT && + value.charAt(++index) === C_BACKSLASH + ) { + queue = C_LT + C_BACKSLASH; + subqueue = character = value.charAt(++index); + + if (!isAlphabetic(character)) { + return; + } + + index++; + + /* + * Eat as many alphabetic characters as + * possible. + */ + + while (index < length) { + character = value.charAt(index); + + if (!isAlphabetic(character) && !isNumeric(character)) { + break; + } + + subqueue += character; + index++; + } + + if (isBlock && blockElements.indexOf(subqueue.toLowerCase()) === -1) { + return; + } + + queue += subqueue; + + /* + * Eat white-space. + */ + + while (index < length) { + character = value.charAt(index); + + if (!isWhiteSpace(character)) { + break; + } + + queue += character; + index++ + } + + if (value.charAt(index) === C_GT) { + return queue + C_GT; + } + } +} + +/** + * Try to match an opening tag. + * + * @param {string} value - Value to parse. + * @param {boolean?} [isBlock] - Whether the tag-name + * must be a known block-level node to match. + * @return {string?} - When applicable, the opening tag at + * the start of `value`. + */ +function eatHTMLOpeningTag(value, isBlock) { + var index = 0; + var length = value.length; + var queue = EMPTY; + var subqueue = EMPTY; + var character = value.charAt(index); + var hasEquals; + var test; + + if (character === C_LT) { + queue = character; + subqueue = character = value.charAt(++index); + + if (!isAlphabetic(character)) { + return; + } + + index++; + + /* + * Eat as many alphabetic characters as + * possible. + */ + + while (index < length) { + character = value.charAt(index); + + if (!isAlphabetic(character) && !isNumeric(character)) { + break; + } + + subqueue += character; + index++; + } + + if (isBlock && blockElements.indexOf(subqueue.toLowerCase()) === -1) { + return; + } + + queue += subqueue; + subqueue = EMPTY; + + /* + * Find attributes. + */ + + while (index < length) { + /* + * Eat white-space. + */ + + while (index < length) { + character = value.charAt(index); + + if (!isWhiteSpace(character)) { + break; + } + + subqueue += character; + index++ + } + + if (!subqueue) { + break; + } + + /* + * Eat an attribute name. + */ + + queue += subqueue; + subqueue = EMPTY; + character = value.charAt(index); + + if ( + isAlphabetic(character) || + character === C_UNDERSCORE || + character === C_COLON + ) { + subqueue = character; + index++; + + while (index < length) { + character = value.charAt(index); + + if ( + !isAlphabetic(character) && + !isNumeric(character) && + character !== C_UNDERSCORE && + character !== C_COLON && + character !== C_DOT && + character !== C_DASH + ) { + break; + } + + subqueue += character; + index++ + } + } + + if (!subqueue) { + break; + } + + queue += subqueue; + subqueue = EMPTY; + hasEquals = false; + + /* + * Eat zero or more white-space and one + * equals sign. + */ + + while (index < length) { + character = value.charAt(index); + + if (!isWhiteSpace(character)) { + if (!hasEquals && character === C_EQUALS) { + hasEquals = true; + } else { + break; + } + } + + subqueue += character; + index++ + } + + queue += subqueue; + subqueue = EMPTY; + + if (!hasEquals) { + queue += subqueue; + } else { + character = value.charAt(index); + queue += subqueue; + + if (character === C_DOUBLE_QUOTE) { + test = isDoubleQuotedAttributeCharacter; + subqueue = character; + index++; + } else if (character === C_SINGLE_QUOTE) { + test = isSingleQuotedAttributeCharacter; + subqueue = character; + index++; + } else { + test = isUnquotedAttributeCharacter; + subqueue = EMPTY; + } + + while (index < length) { + character = value.charAt(index); + + if (!test(character)) { + break; + } + + subqueue += character; + index++; + } + + character = value.charAt(index); + index++; + + if (!test.delimiter) { + if (!subqueue.length) { + return; + } + + index--; + } else if (character === test.delimiter) { + subqueue += character; + } else { + return; + } + + queue += subqueue; + subqueue = EMPTY; + } + } + + /* + * More white-space is already eaten by the + * attributes subroutine. + */ + + character = value.charAt(index); + + /* + * Eat an optional backslash (for self-closing + * tags). + */ + + if (character === C_BACKSLASH) { + queue += character; + character = value.charAt(++index); + } + + return character === C_GT ? queue + character : null; + } +} + +/** + * Tokenise HTML. + * + * @example + * tokenizeHTML(eat, 'foo'); + * + * @param {function(string)} eat + * @param {string} value - Rest of content. + * @param {boolean?} [silent] - Whether this is a dry run. + * @return {Node?|boolean} - `html` node. + */ +function tokenizeHTML(eat, value, silent) { + var self = this; + var index = 0; + var length = value.length; + var subvalue = EMPTY; + var lineCount; + var character; + var queue; + + /* + * Eat initial spacing. + */ + + while (index < length) { + character = value.charAt(index); + + if (character !== C_TAB && character !== C_SPACE) { + break; + } + + subvalue += character; + index++; + } + + value = value.slice(index); + + /* + * Try to eat an HTML thing. + */ + + queue = eatHTMLComment(value, self.options) || + eatHTMLCDATA(value) || + eatHTMLProcessingInstruction(value) || + eatHTMLDeclaration(value) || + eatHTMLClosingTag(value, true) || + eatHTMLOpeningTag(value, true); + + if (!queue) { + return; + } + + if (silent) { + return true; + } + + subvalue += queue; + index = subvalue.length; + queue = EMPTY; + + while (index < length) { + character = value.charAt(index); + + if (character === C_NEWLINE) { + queue += character + lineCount++; + } else if (queue.length < MIN_CLOSING_HTML_NEWLINE_COUNT) { + subvalue += queue + character; + queue = EMPTY; + } else { + break; + } + + index++; + } + + return eat(subvalue)(self.renderRaw(T_HTML, subvalue)); +} + +/** + * Tokenise a definition. + * + * @example + * var value = '[foo]: http://example.com "Example Domain"'; + * tokenizeDefinition(eat, value); + * + * @property {boolean} onlyAtTop + * @property {boolean} notInBlockquote + * @param {function(string)} eat + * @param {string} value - Rest of content. + * @param {boolean?} [silent] - Whether this is a dry run. + * @return {Node?|boolean} - `definition` node. + */ +function tokenizeDefinition(eat, value, silent) { + var self = this; + var index = 0; + var length = value.length; + var subvalue = EMPTY; + var queue; + var character; + var test; + var identifier; + var url; + var title; + + while (index < length) { + character = value.charAt(index); + + if (character !== C_SPACE && character !== C_TAB) { + break; + } + + subvalue += character; + index++; + } + + character = value.charAt(index); + + if (character !== C_BRACKET_OPEN) { + return; + } + + index++; + subvalue += character; + queue = EMPTY; + + while (index < length) { + character = value.charAt(index); + + if (character === C_BRACKET_CLOSE) { + break; + } else if (character === C_SLASH) { + queue += character; + index++; + character = value.charAt(index); + } + + queue += character; + index++; + } + + if ( + !queue || + value.charAt(index) !== C_BRACKET_CLOSE || + value.charAt(index + 1) !== C_COLON + ) { + return; + } + + identifier = queue; + subvalue += queue + C_BRACKET_CLOSE + C_COLON; + index = subvalue.length; + queue = EMPTY; + + while (index < length) { + character = value.charAt(index); + + if ( + character !== C_TAB && + character !== C_SPACE && + character !== C_NEWLINE + ) { + break; + } + + subvalue += character; + index++; + } + + character = value.charAt(index); + index++; + + if (character === C_LT) { + test = isEnclosedURLCharacter; + subvalue += character; + queue = EMPTY; + } else { + test = isUnclosedURLCharacter; + queue = character; + } + + while (index < length) { + character = value.charAt(index); + + if (!test(character)) { + break; + } + + queue += character; + index++; + } + + if (test.delimiter) { + character = value.charAt(index); + + if (character !== test.delimiter) { + return; + } + + subvalue += queue + character; + index++; + } else if (queue) { + subvalue += queue; + } else { + return; + } + + url = queue; + queue = EMPTY; + + while (index < length) { + character = value.charAt(index); + + if ( + character !== C_TAB && + character !== C_SPACE && + character !== C_NEWLINE + ) { + break; + } + + queue += character; + index++; + } + + character = value.charAt(index); + test = null; + + if (character === C_DOUBLE_QUOTE) { + test = C_DOUBLE_QUOTE; + } else if (character === C_SINGLE_QUOTE) { + test = C_SINGLE_QUOTE; + } else if (character === C_PAREN_OPEN) { + test = C_PAREN_CLOSE; + } + + if (!test) { + queue = EMPTY; + index = subvalue.length; + } else if (!queue) { + return; + } else { + subvalue += queue + character; + index = subvalue.length; + queue = EMPTY; + + while (index < length) { + character = value.charAt(index); + + if (character === test) { + break; + } + + if (character === C_NEWLINE) { + index++; + character = value.charAt(index); + + if (character === C_NEWLINE || character === test) { + return; + } + + queue += C_NEWLINE; + } + + queue += character; + index++; + } + + character = value.charAt(index); + + if (character !== test) { + return; + } + + subvalue += queue + character; + index++; + title = queue; + queue = EMPTY; + } + + while (index < length) { + character = value.charAt(index); + + if (character !== C_TAB && character !== C_SPACE) { + break; + } + + subvalue += character; + index++; + } + + character = value.charAt(index); + + if (!character || character === C_NEWLINE) { + if (silent) { + return true; + } + return eat(subvalue)({ + 'type': T_DEFINITION, + 'identifier': normalize(identifier), + 'title': title ? decode(self.descape(title), eat) : null, + 'link': decode(self.descape(url), eat) + }); + } +} + +tokenizeDefinition.onlyAtTop = true; +tokenizeDefinition.notInBlockquote = true; + +/** + * Tokenise YAML front matter. + * + * @example + * tokenizeYAMLFrontMatter(eat, '---\nfoo: bar\n---'); + * + * @property {boolean} onlyAtStart + * @param {function(string)} eat + * @param {string} value - Rest of content. + * @param {boolean?} [silent] - Whether this is a dry run. + * @return {Node?|boolean} - `yaml` node. + */ +function tokenizeYAMLFrontMatter(eat, value, silent) { + var self = this; + var subvalue; + var content; + var index; + var length; + var character; + var queue; + + if ( + !self.options.yaml || + value.charAt(0) !== C_DASH || + value.charAt(1) !== C_DASH || + value.charAt(2) !== C_DASH || + value.charAt(3) !== C_NEWLINE + ) { + return; + } + + subvalue = YAML_FENCE + C_NEWLINE; + content = queue = EMPTY; + index = 3; + length = value.length; + + while (++index < length) { + character = value.charAt(index); + + if ( + character === C_DASH && + (queue || !content) && + value.charAt(index + 1) === C_DASH && + value.charAt(index + 2) === C_DASH + ) { + /* istanbul ignore if - never used (yet) */ + if (silent) { + return true; + } + + subvalue += queue + YAML_FENCE; + + return eat(subvalue)(self.renderRaw(T_YAML, content)); + } + + if (character === C_NEWLINE) { + queue += character; + } else { + subvalue += queue + character; + content += queue + character; + queue = EMPTY; + } + } +} + +tokenizeYAMLFrontMatter.onlyAtStart = true; + +/** + * Tokenise a footnote definition. + * + * @example + * tokenizeFootnoteDefinition(eat, '[^foo]: Bar.'); + * + * @property {boolean} onlyAtTop + * @property {boolean} notInBlockquote + * @param {function(string)} eat + * @param {string} value - Rest of content. + * @param {boolean?} [silent] - Whether this is a dry run. + * @return {Node?|boolean} - `footnoteDefinition` node. + */ +function tokenizeFootnoteDefinition(eat, value, silent) { + var self = this; + var index; + var length; + var subvalue; + var now; + var indent; + var content; + var queue; + var subqueue; + var character; + var identifier; + + if (!self.options.footnotes) { + return; + } + + index = 0; + length = value.length; + subvalue = EMPTY; + now = eat.now(); + indent = self.indent(now.line); + + while (index < length) { + character = value.charAt(index); + + if (!isWhiteSpace(character)) { + break; + } + + subvalue += character; + index++; + } + + if ( + value.charAt(index) !== C_BRACKET_OPEN || + value.charAt(index + 1) !== C_CARET + ) { + return; + } + + subvalue += C_BRACKET_OPEN + C_CARET; + index = subvalue.length; + queue = EMPTY; + + while (index < length) { + character = value.charAt(index); + + if (character === C_BRACKET_CLOSE) { + break; + } else if (character === C_SLASH) { + queue += character; + index++; + character = value.charAt(index); + } + + queue += character; + index++; + } + + if ( + !queue || + value.charAt(index) !== C_BRACKET_CLOSE || + value.charAt(index + 1) !== C_COLON + ) { + return; + } + + /* istanbul ignore if - never used (yet) */ + if (silent) { + return true; + } + + identifier = normalize(queue); + subvalue += queue + C_BRACKET_CLOSE + C_COLON; + index = subvalue.length; + + while (index < length) { + character = value.charAt(index); + + if ( + character !== C_TAB && + character !== C_SPACE + ) { + break; + } + + subvalue += character; + index++; + } + + now.column += subvalue.length; + queue = content = subqueue = EMPTY; + + while (index < length) { + character = value.charAt(index); + + if (character === C_NEWLINE) { + subqueue = character; + index++; + + while (index < length) { + character = value.charAt(index); + + if (character !== C_NEWLINE) { + break; + } + + subqueue += character; + index++; + } + + queue += subqueue; + subqueue = EMPTY; + + while (index < length) { + character = value.charAt(index); + + if (character !== C_SPACE) { + break; + } + + subqueue += character; + index++; + } + + if (!subqueue.length) { + break; + } + + queue += subqueue; + } + + if (queue) { + content += queue; + queue = EMPTY; + } + + content += character; + index++; + } + + subvalue += content; + + content = content.replace(EXPRESSION_INITIAL_TAB, function (line) { + indent(line.length); + + return EMPTY; + }); + + return eat(subvalue)( + self.renderFootnoteDefinition(identifier, content, now) + ); +} + +tokenizeFootnoteDefinition.onlyAtTop = true; +tokenizeFootnoteDefinition.notInBlockquote = true; + +/** + * Tokenise a table. + * + * @example + * tokenizeTable(eat, ' | foo |\n | --- |\n | bar |'); + * + * @property {boolean} onlyAtTop + * @param {function(string)} eat + * @param {string} value - Rest of content. + * @param {boolean?} [silent] - Whether this is a dry run. + * @return {Node?|boolean} - `table` node. + */ +function tokenizeTable(eat, value, silent) { + var self = this; + var index; + var alignments; + var alignment; + var subvalue; + var row; + var length; + var lines; + var queue; + var character; + var hasDash; + var align; + var cell; + var preamble; + var count; + var opening; + var now; + var position; + var lineCount; + var line; + var rows; + var table; + var lineIndex; + var pipeIndex; + var first; + + /* + * Exit when not in gfm-mode. + */ + + if (!self.options.gfm) { + return; + } + + /* + * Get the rows. + * Detecting tables soon is hard, so there are some + * checks for performance here, such as the minimum + * number of rows, and allowed characters in the + * alignment row. + */ + + index = lineCount = 0; + length = value.length + 1; + lines = []; + + while (index < length) { + lineIndex = value.indexOf(C_NEWLINE, index); + pipeIndex = value.indexOf(C_PIPE, index + 1); + + if ( + pipeIndex === -1 || + pipeIndex > lineIndex + ) { + if (lineCount < MIN_TABLE_ROWS) { + return; + } + + break; + } + + lines.push(value.slice(index, lineIndex)); + lineCount++; + index = lineIndex + 1; + } + + /* + * Parse the alignment row. + */ + + subvalue = lines.join(C_NEWLINE); + alignments = lines.splice(1, 1)[0]; + index = 0; + length = alignments.length; + lineCount--; + alignment = false; + align = []; + + while (index < length) { + character = alignments.charAt(index); + + if (character === C_PIPE) { + hasDash = null; + + if (alignment === false) { + if (first === false) { + return; + } + } else { + align.push(alignment); + alignment = false; + } + + first = false; + } else if (character === C_DASH) { + hasDash = true; + alignment = alignment || TABLE_ALIGN_NONE; + } else if (character === C_COLON) { + if (alignment === TABLE_ALIGN_LEFT) { + alignment = TABLE_ALIGN_CENTER; + } else if (hasDash && alignment === TABLE_ALIGN_NONE) { + alignment = TABLE_ALIGN_RIGHT; + } else { + alignment = TABLE_ALIGN_LEFT; + } + } else if (!isWhiteSpace(character)) { + return; + } + + index++; + } + + if (alignment !== false) { + align.push(alignment); + } + + /* + * Exit when without enough columns. + */ + + if (align.length < MIN_TABLE_COLUMNS) { + return; + } + + /* istanbul ignore if - never used (yet) */ + if (silent) { + return true; + } + + /* + * Parse the rows. + */ + + position = -1; + rows = []; + + table = eat(subvalue).reset({ + 'type': T_TABLE, + 'align': align, + 'children': rows + }); + + while (++position < lineCount) { + line = lines[position]; + row = self.renderParent(position ? T_TABLE_ROW : T_TABLE_HEADER, []); + + /* + * Eat a newline character when this is not the + * first row. + */ + + if (position) { + eat(C_NEWLINE); + } + + /* + * Eat the alignment row. + */ + + if (position === 1) { + eat(alignments + C_NEWLINE); + } + + /* + * Eat the row. + */ + + eat(line).reset(row, table); + + length = line.length + 1; + index = 0; + queue = EMPTY; + cell = EMPTY; + preamble = true; + count = opening = null; + + while (index < length) { + character = line.charAt(index); + + if (character === C_TAB || character === C_SPACE) { + if (cell) { + queue += character; + } else { + eat(character); + } + + index++; + continue; + } + + if (character === EMPTY || character === C_PIPE) { + if (preamble) { + eat(character); + } else { + if (character && opening) { + queue += character; + index++; + continue; + } + + if ((cell || character) && !preamble) { + subvalue = cell; + + if (queue.length > 1) { + if (character) { + subvalue += queue.slice(0, queue.length - 1); + queue = queue.charAt(queue.length - 1); + } else { + subvalue += queue; + queue = EMPTY; + } + } + + now = eat.now(); + + eat(subvalue)( + self.renderInline(T_TABLE_CELL, cell, now), row + ); + } + + eat(queue + character); + + queue = EMPTY; + cell = EMPTY; + } + } else { + if (queue) { + cell += queue; + queue = EMPTY; + } + + cell += character; + + if (character === C_SLASH && index !== length - 2) { + cell += line.charAt(index + 1); + index++; + } + + if (character === C_TICK) { + count = 1; + + while (line.charAt(index + 1) === character) { + cell += character; + index++; + count++; + } + + if (!opening) { + opening = count; + } else if (count >= opening) { + opening = 0; + } + } + } + + preamble = false; + index++; + } + } + + return table; +} + +tokenizeTable.onlyAtTop = true; + +/** + * Tokenise a paragraph node. + * + * @example + * tokenizeParagraph(eat, 'Foo.'); + * + * @param {function(string)} eat + * @param {string} value - Rest of content. + * @param {boolean?} [silent] - Whether this is a dry run. + * @return {Node?|boolean} - `paragraph` node. + */ +function tokenizeParagraph(eat, value, silent) { + var self = this; + var settings = self.options; + var commonmark = settings.commonmark; + var gfm = settings.gfm; + var tokenizers = self.blockTokenizers; + var index = value.indexOf(C_NEWLINE); + var length = value.length; + var position; + var subvalue; + var character; + var size; + var now; + + while (index < length) { + /* + * Eat everything if there’s no following newline. + */ + + if (index === -1) { + index = length; + break; + } + + /* + * Stop if the next character is NEWLINE. + */ + + if (value.charAt(index + 1) === C_NEWLINE) { + break; + } + + /* + * In commonmark-mode, following indented lines + * are part of the paragraph. + */ + + if (commonmark) { + size = 0; + position = index + 1; + + while (position < length) { + character = value.charAt(position); + + if (character === C_TAB) { + size = TAB_SIZE; + break; + } else if (character === C_SPACE) { + size++; + } else { + break; + } + + position++; + } + + if (size >= TAB_SIZE) { + index = value.indexOf(C_NEWLINE, index + 1); + continue; + } + } + + /* + * Check if the following code contains a possible + * block. + */ + + subvalue = value.slice(index + 1); + + if ( + tokenizers.horizontalRule.call(self, eat, subvalue, true) || + tokenizers.heading.call(self, eat, subvalue, true) || + tokenizers.fences.call(self, eat, subvalue, true) || + tokenizers.blockquote.call(self, eat, subvalue, true) || + tokenizers.html.call(self, eat, subvalue, true) + ) { + break; + } + + if (gfm && tokenizers.list.call(self, eat, subvalue, true)) { + break; + } + + if ( + !commonmark && + ( + tokenizers.lineHeading.call(self, eat, subvalue, true) || + tokenizers.definition.call(self, eat, subvalue, true) || + tokenizers.footnoteDefinition.call(self, eat, subvalue, true) + ) + ) { + break; + } + + index = value.indexOf(C_NEWLINE, index + 1); + } + + subvalue = value.slice(0, index); + + if (trim(subvalue) === EMPTY) { + eat(subvalue); + + return null; + } + + /* istanbul ignore if - never used (yet) */ + if (silent) { + return true; + } + + now = eat.now(); + subvalue = trimTrailingLines(subvalue); + + return eat(subvalue)(self.renderInline(T_PARAGRAPH, subvalue, now)); +} + +/** + * Tokenise a text node. + * + * @example + * tokenizeText(eat, 'foo'); + * + * @param {function(string)} eat + * @param {string} value - Rest of content. + * @param {boolean?} [silent] - Whether this is a dry run. + * @return {Node?|boolean} - `text` node. + */ +function tokenizeText(eat, value, silent) { + var self = this; + var textStop; + var textStopCount; + var index; + var length; + var subvalue; + var breaks; + var offset; + var character; + var position; + var stop; + + /* istanbul ignore if - never used (yet) */ + if (silent) { + return true; + } + + index = 1; + length = value.length; + breaks = self.options.breaks; + textStop = self.textStop; + textStopCount = textStop.length; + + while (index < length) { + character = value.charAt(index); + position = -1; + + while (++position < textStopCount) { + stop = textStop[position]; + + if ( + ( + stop.length === 1 && + character === stop + ) || + ( + stop.length > 1 && + stop === value.slice(index, index + stop.length) + ) + ) { + character = EMPTY; + break; + } + } + + if (character === EMPTY) { + break; + } + + if (character === C_SPACE) { + offset = index + 1; + + while (++offset < length) { + character = value.charAt(offset); + + if (character !== C_SPACE) { + break; + } + } + + if ( + character === C_NEWLINE && + (breaks || offset - index >= 2) + ) { + break; + } + } + + index++; + } + + subvalue = value.slice(0, index); + + return eat(subvalue)(self.renderRaw(T_TEXT, subvalue)); +} + +/** + * Create a code-block node. + * + * @example + * renderCodeBlock('foo()', 'js', now()); + * + * @param {string?} [value] - Code. + * @param {string?} [language] - Optional language flag. + * @param {Function} eat + * @return {Object} - `code` node. + */ +function renderCodeBlock(value, language, eat) { + return { + 'type': T_CODE, + 'lang': language ? decode(this.descape(language), eat) : null, + 'value': trimTrailingLines(value || EMPTY) + }; +} + +/** + * Create a list-item using overly simple mechanics. + * + * @example + * renderPedanticListItem('- _foo_', now()); + * + * @param {string} value - List-item. + * @param {Object} position - List-item location. + * @return {string} - Cleaned `value`. + */ +function renderPedanticListItem(value, position) { + var self = this; + var indent = self.indent(position.line); + + /** + * A simple replacer which removed all matches, + * and adds their length to `offset`. + * + * @param {string} $0 + * @return {string} + */ + function replacer($0) { + indent($0.length); + + return EMPTY; + } + + /* + * Remove the list-item’s bullet. + */ + + value = value.replace(EXPRESSION_PEDANTIC_BULLET, replacer); + + /* + * The initial line was also matched by the below, so + * we reset the `line`. + */ + + indent = self.indent(position.line); + + return value.replace(EXPRESSION_INITIAL_INDENT, replacer); +} + +/** + * Create a list-item using sane mechanics. + * + * @example + * renderNormalListItem('- _foo_', now()); + * + * @param {string} value - List-item. + * @param {Object} position - List-item location. + * @return {string} - Cleaned `value`. + */ +function renderNormalListItem(value, position) { + var self = this; + var indent = self.indent(position.line); + var bullet; + var rest; + var lines; + var trimmedLines; + var index; + var length; + var max; + + /* + * Remove the list-item’s bullet. + */ + + value = value.replace(EXPRESSION_BULLET, function ($0, $1, $2, $3, $4) { + bullet = $1 + $2 + $3; + rest = $4; + + /* + * Make sure that the first nine numbered list items + * can indent with an extra space. That is, when + * the bullet did not receive an extra final space. + */ + + if (Number($2) < 10 && bullet.length % 2 === 1) { + $2 = C_SPACE + $2; + } + + max = $1 + repeat(C_SPACE, $2.length) + $3; + + return max + rest; + }); + + lines = value.split(C_NEWLINE); + + trimmedLines = removeIndentation( + value, getIndent(max).indent + ).split(C_NEWLINE); + + /* + * We replaced the initial bullet with something + * else above, which was used to trick + * `removeIndentation` into removing some more + * characters when possible. However, that could + * result in the initial line to be stripped more + * than it should be. + */ + + trimmedLines[0] = rest; + + indent(bullet.length); + + index = 0; + length = lines.length; + + while (++index < length) { + indent(lines[index].length - trimmedLines[index].length); + } + + return trimmedLines.join(C_NEWLINE); +} + +/** + * Create a list-item node. + * + * @example + * renderListItem('- _foo_', now()); + * + * @param {Object} value - List-item. + * @param {Object} position - List-item location. + * @return {Object} - `listItem` node. + */ +function renderListItem(value, position) { + var self = this; + var checked = null; + var node; + var task; + var indent; + + value = LIST_ITEM_MAP[self.options.pedantic].apply(self, arguments); + + if (self.options.gfm) { + task = value.match(EXPRESSION_TASK_ITEM); + + if (task) { + indent = task[0].length; + checked = task[1].toLowerCase() === C_X_LOWER; + + self.indent(position.line)(indent); + value = value.slice(indent); + } + } + + node = { + 'type': T_LIST_ITEM, + 'loose': EXPRESSION_LOOSE_LIST_ITEM.test(value) || + value.charAt(value.length - 1) === C_NEWLINE + }; + + if (self.options.gfm) { + node.checked = checked; + } + + node.children = self.tokenizeBlock(value, position); + + return node; +} + +/** + * Create a footnote-definition node. + * + * @example + * renderFootnoteDefinition('1', '_foo_', now()); + * + * @param {string} identifier - Unique reference. + * @param {string} value - Contents + * @param {Object} position - Definition location. + * @return {Object} - `footnoteDefinition` node. + */ +function renderFootnoteDefinition(identifier, value, position) { + var self = this; + var exitBlockquote = self.enterBlockquote(); + var node; + + node = { + 'type': T_FOOTNOTE_DEFINITION, + 'identifier': identifier, + 'children': self.tokenizeBlock(value, position) + }; + + exitBlockquote(); + + return node; +} + +/** + * Create a heading node. + * + * @example + * renderHeading('_foo_', 1, now()); + * + * @param {string} value - Content. + * @param {number} depth - Heading depth. + * @param {Object} position - Heading content location. + * @return {Object} - `heading` node + */ +function renderHeading(value, depth, position) { + return { + 'type': T_HEADING, + 'depth': depth, + 'children': this.tokenizeInline(value, position) + }; +} + +/** + * Create a blockquote node. + * + * @example + * renderBlockquote('_foo_', eat); + * + * @param {string} value - Content. + * @param {Object} now - Position. + * @return {Object} - `blockquote` node. + */ +function renderBlockquote(value, now) { + var self = this; + var exitBlockquote = self.enterBlockquote(); + var node = { + 'type': T_BLOCKQUOTE, + 'children': self.tokenizeBlock(value, now) + }; + + exitBlockquote(); + + return node; +} + +/** + * Create a void node. + * + * @example + * renderVoid('horizontalRule'); + * + * @param {string} type - Node type. + * @return {Object} - Node of type `type`. + */ +function renderVoid(type) { + return { + 'type': type + }; +} + +/** + * Create a parent. + * + * @example + * renderParent('paragraph', '_foo_'); + * + * @param {string} type - Node type. + * @param {Array.} children - Child nodes. + * @return {Object} - Node of type `type`. + */ +function renderParent(type, children) { + return { + 'type': type, + 'children': children + }; +} + +/** + * Create a raw node. + * + * @example * renderRaw('inlineCode', 'foo()'); * * @param {string} type - Node type. @@ -1510,391 +3745,1285 @@ function renderLink(isLink, href, text, title, position, eat) { var exitLink = self.enterLink(); var node; - node = { - 'type': isLink ? LINK : IMAGE, - 'title': title ? decode(self.descape(title), eat) : null - }; + node = { + 'type': isLink ? T_LINK : T_IMAGE, + 'title': title ? decode(self.descape(title), eat) : null + }; + + href = decode(href, eat); + + if (isLink) { + node.href = href; + node.children = self.tokenizeInline(text, position); + } else { + node.src = href; + node.alt = text ? decode(self.descape(text), eat) : null; + } + + exitLink(); + + return node; +} + +/** + * Create a footnote node. + * + * @example + * renderFootnote('_foo_', now()); + * + * @param {string} value - Contents. + * @param {Object} position - Location of footnote. + * @return {Object} - `footnote` node. + */ +function renderFootnote(value, position) { + return this.renderInline(T_FOOTNOTE, value, position); +} + +/** + * Add a node with inline content. + * + * @example + * renderInline('strong', '_foo_', now()); + * + * @param {string} type - Node type. + * @param {string} value - Contents. + * @param {Object} position - Location of node. + * @return {Object} - Node of type `type`. + */ +function renderInline(type, value, position) { + return this.renderParent(type, this.tokenizeInline(value, position)); +} + +/** + * Add a node with block content. + * + * @example + * renderBlock('blockquote', 'Foo.', now()); + * + * @param {string} type - Node type. + * @param {string} value - Contents. + * @param {Object} position - Location of node. + * @return {Object} - Node of type `type`. + */ +function renderBlock(type, value, position) { + return this.renderParent(type, this.tokenizeBlock(value, position)); +} + +/** + * Tokenise an escape sequence. + * + * @example + * tokenizeEscape(eat, '\\a'); + * + * @param {function(string)} eat + * @param {string} value - Rest of content. + * @param {boolean?} [silent] - Whether this is a dry run. + * @return {Node?|boolean} - `escape` node. + */ +function tokenizeEscape(eat, value, silent) { + var self = this; + var character; + + if (value.charAt(0) === C_SLASH) { + character = value.charAt(1); + + if (self.escape.indexOf(character) !== -1) { + /* istanbul ignore if - never used (yet) */ + if (silent) { + return true; + } + + return eat(C_SLASH + character)( + self.renderRaw(T_ESCAPE, character) + ); + } + } +} + +/** + * Tokenise a URL in carets. + * + * @example + * tokenizeAutoLink(eat, ''); + * + * @property {boolean} notInLink + * @param {function(string)} eat + * @param {string} value - Rest of content. + * @param {boolean?} [silent] - Whether this is a dry run. + * @return {Node?|boolean} - `link` node. + */ +function tokenizeAutoLink(eat, value, silent) { + var self; + var subvalue; + var length; + var index; + var queue; + var character; + var hasAtCharacter; + var link; + var now; + var content; + var tokenize; + var node; + + if (value.charAt(0) !== C_LT) { + return; + } + + self = this; + subvalue = EMPTY; + length = value.length; + index = 0; + queue = EMPTY; + hasAtCharacter = false; + link = EMPTY; + now = eat.now(); + + index++; + subvalue = C_LT; + + while (index < length) { + character = value.charAt(index); + + if ( + character === C_SPACE || + character === C_GT || + character === C_AT_SIGN || + (character === C_COLON && value.charAt(index + 1) === C_BACKSLASH) + ) { + break; + } + + queue += character; + index++; + } + + if (!queue) { + return; + } + + link += queue; + queue = EMPTY; + + character = value.charAt(index); + link += character; + index++; + + if (character === C_AT_SIGN) { + hasAtCharacter = true; + } else { + if ( + character !== C_COLON || + value.charAt(index + 1) !== C_BACKSLASH + ) { + return; + } + + link += C_BACKSLASH; + index++; + } + + while (index < length) { + character = value.charAt(index); + + if (character === C_SPACE || character === C_GT) { + break; + } + + queue += character; + index++; + } + + character = value.charAt(index); + + if (!queue || character !== C_GT) { + return; + } + + /* istanbul ignore if - never used (yet) */ + if (silent) { + return true; + } + + link += queue; + content = link; + subvalue += link + character; + + if (hasAtCharacter) { + if ( + link.substr(0, MAILTO_PROTOCOL.length).toLowerCase() !== + MAILTO_PROTOCOL + ) { + link = MAILTO_PROTOCOL + link; + } else { + content = content.substr(MAILTO_PROTOCOL.length); + now.column += MAILTO_PROTOCOL.length; + } + } + + now.column++; + + /* + * Temporarily remove support for escapes in autolinks. + */ + + tokenize = self.inlineTokenizers.escape; + self.inlineTokenizers.escape = null; + + node = eat(subvalue)( + self.renderLink(true, link, content, null, now, eat) + ); + + self.inlineTokenizers.escape = tokenize; + + return node; +} + +tokenizeAutoLink.notInLink = true; + +/** + * Tokenise a URL in text. + * + * @example + * tokenizeURL(eat, 'http://foo.bar'); + * + * @property {boolean} notInLink + * @param {function(string)} eat + * @param {string} value - Rest of content. + * @param {boolean?} [silent] - Whether this is a dry run. + * @return {Node?|boolean} - `link` node. + */ +function tokenizeURL(eat, value, silent) { + var self = this; + var subvalue; + var content; + var character; + var index; + var position; + var protocol; + var match; + var length; + var queue; + var once; + var now; + + if (!self.options.gfm) { + return; + } + + subvalue = EMPTY; + index = -1; + length = PROTOCOLS_LENGTH; + + while (++index < length) { + protocol = PROTOCOLS[index]; + match = value.slice(0, protocol.length); + + if (match.toLowerCase() === protocol) { + subvalue = match; + break; + } + } + + if (!subvalue) { + return; + } + + index = subvalue.length; + length = value.length; + queue = EMPTY; + + while (index < length) { + character = value.charAt(index); + + if (isWhiteSpace(character) || character === C_LT) { + break; + } + + if ( + character === C_DOT || + character === C_COMMA || + character === C_COLON || + character === C_SEMI_COLON || + character === C_DOUBLE_QUOTE || + character === C_SINGLE_QUOTE || + character === C_PAREN_CLOSE || + character === C_BRACKET_CLOSE + ) { + if (once) { + break; + } + + once = true; + } + + queue += character; + index++; + } - href = decode(href, eat); + if (!queue) { + return; + } - if (isLink) { - node.href = href; - node.children = self.tokenizeInline(text, position); - } else { - node.src = href; - node.alt = text ? decode(self.descape(text), eat) : null; + subvalue += queue; + content = subvalue; + + if (protocol === MAILTO_PROTOCOL) { + position = queue.indexOf(C_AT_SIGN); + + if (position === -1 || position === length - 1) { + return; + } + + content = content.substr(MAILTO_PROTOCOL.length); } - exitLink(); + /* istanbul ignore if - never used (yet) */ + if (silent) { + return true; + } - return node; -} + now = eat.now(); -/** - * Create a footnote node. - * - * @example - * renderFootnote('_foo_', now()); - * - * @param {string} value - Contents. - * @param {Object} position - Location of footnote. - * @return {Object} - `footnote` node. - */ -function renderFootnote(value, position) { - return this.renderInline(FOOTNOTE, value, position); + return eat(subvalue)( + self.renderLink(true, subvalue, content, null, now, eat) + ); } -/** - * Add a node with inline content. - * - * @example - * renderInline('strong', '_foo_', now()); - * - * @param {string} type - Node type. - * @param {string} value - Contents. - * @param {Object} position - Location of node. - * @return {Object} - Node of type `type`. - */ -function renderInline(type, value, position) { - return this.renderParent(type, this.tokenizeInline(value, position)); -} +tokenizeURL.notInLink = true; /** - * Add a node with block content. + * Tokenise an HTML tag. * * @example - * renderBlock('blockquote', 'Foo.', now()); + * tokenizeTag(eat, ''); * - * @param {string} type - Node type. - * @param {string} value - Contents. - * @param {Object} position - Location of node. - * @return {Object} - Node of type `type`. + * @param {function(string)} eat + * @param {string} value - Rest of content. + * @param {boolean?} [silent] - Whether this is a dry run. + * @return {Node?|boolean} - `html` node. */ -function renderBlock(type, value, position) { - return this.renderParent(type, this.tokenizeBlock(value, position)); +function tokenizeTag(eat, value, silent) { + var self = this; + var subvalue = eatHTMLComment(value, self.options) || + eatHTMLCDATA(value) || + eatHTMLProcessingInstruction(value) || + eatHTMLDeclaration(value) || + eatHTMLClosingTag(value) || + eatHTMLOpeningTag(value); + + if (!subvalue) { + return; + } + + /* istanbul ignore if - never used (yet) */ + if (silent) { + return true; + } + + if (!self.inLink && EXPRESSION_HTML_LINK_OPEN.test(subvalue)) { + self.inLink = true; + } else if (self.inLink && EXPRESSION_HTML_LINK_CLOSE.test(subvalue)) { + self.inLink = false; + } + + return eat(subvalue)(self.renderRaw(T_HTML, subvalue)); } /** - * Tokenise an escape sequence. + * Tokenise a link. * * @example - * tokenizeEscape(eat, '\\a', 'a'); + * tokenizeLink(eat, '![foo](fav.ico "Favicon")); * * @param {function(string)} eat - * @param {string} $0 - Whole escape. - * @param {string} $1 - Escaped character. - * @return {Node} - `escape` node. + * @param {string} value - Rest of content. + * @param {boolean?} [silent] - Whether this is a dry run. + * @return {Node?|boolean} - `link` or `image` node. */ -function tokenizeEscape(eat, $0, $1) { - return eat($0)(this.renderRaw(ESCAPE, $1)); +function tokenizeLink(eat, value, silent) { + var self = this; + var subvalue = EMPTY; + var index = 0; + var character = value.charAt(0); + var whiteSpaceQueue; + var commonmark; + var openCount; + var hasMarker; + var markers; + var isImage; + var content; + var marker; + var length; + var title; + var depth; + var queue; + var url; + var now; + + /* + * Detect whether this is an image. + */ + + if (character === C_EXCLAMATION_MARK) { + isImage = true; + subvalue = character; + character = value.charAt(++index); + } + + /* + * Eat the opening. + */ + + if (character !== C_BRACKET_OPEN) { + return; + } + + /* + * Exit when this is a link and we’re already inside + * a link. + */ + + if (!isImage && self.inLink) { + return; + } + + subvalue += character; + queue = EMPTY; + index++; + + /* + * Eat the content. + */ + + commonmark = self.options.commonmark; + length = value.length; + now = eat.now(); + depth = 0; + + now.column += index; + + while (index < length) { + character = value.charAt(index); + + if (character === C_BRACKET_OPEN) { + depth++; + } else if (character === C_BRACKET_CLOSE) { + /* + * Allow a single closing bracket when not in + * commonmark-mode. + */ + + if (!commonmark && !depth) { + if (value.charAt(index + 1) === C_PAREN_OPEN) { + break; + } + + depth++; + } + + if (depth === 0) { + break; + } + + depth--; + } + + queue += character; + index++; + } + + /* + * Eat the content closing. + */ + + if ( + value.charAt(index) !== C_BRACKET_CLOSE || + value.charAt(++index) !== C_PAREN_OPEN + ) { + return; + } + + subvalue += queue + C_BRACKET_CLOSE + C_PAREN_OPEN; + index++; + content = queue; + + /* + * Eat white-space. + */ + + while (index < length) { + character = value.charAt(index); + + if (!isWhiteSpace(character)) { + break; + } + + subvalue += character; + index++; + } + + /* + * Eat the URL. + */ + + character = value.charAt(index); + markers = commonmark ? COMMONMARK_LINK_TITLE_MARKERS : LINK_TITLE_MARKERS; + openCount = 0; + queue = EMPTY; + + if (character === C_LT) { + index++; + + while (index < length) { + character = value.charAt(index); + + if (character === C_GT) { + break; + } + + if (commonmark && character === C_NEWLINE) { + return; + } + + queue += character; + index++; + } + + if (value.charAt(index) !== C_GT) { + return; + } + + subvalue += C_LT + queue + C_GT; + url = queue; + index++; + } else { + character = null; + whiteSpaceQueue = EMPTY; + + while (index < length) { + character = value.charAt(index); + + if (whiteSpaceQueue && has.call(markers, character)) { + break; + } + + if (isWhiteSpace(character)) { + if (commonmark) { + break; + } + + whiteSpaceQueue += character; + } else { + if (character === C_PAREN_OPEN) { + depth++; + openCount++ + } else if (character === C_PAREN_CLOSE) { + if (depth === 0) { + break; + } + + depth--; + } + + queue += whiteSpaceQueue; + whiteSpaceQueue = EMPTY; + + if (character === C_SLASH) { + queue += C_SLASH; + character = value.charAt(++index); + } + + queue += character; + } + + index++; + } + + queue = queue; + subvalue += queue; + url = queue; + index = subvalue.length; + } + + /* + * Eat white-space. + */ + + queue = EMPTY; + + while (index < length) { + character = value.charAt(index); + + if (!isWhiteSpace(character)) { + break; + } + + queue += character; + index++; + } + + character = value.charAt(index); + subvalue += queue; + + /* + * Eat the title. + */ + + if (queue && has.call(markers, character)) { + index++; + subvalue += character; + queue = EMPTY; + marker = markers[character]; + + /* + * In commonmark-mode, things are pretty easy: the + * marker cannot occur inside the title. + * + * Non-commonmark does, however, support nested + * delimiters. + */ + + if (commonmark) { + while (index < length) { + character = value.charAt(index); + + if (character === marker) { + break; + } + + if (character === C_SLASH) { + queue += C_SLASH; + character = value.charAt(++index); + } + + index++; + queue += character; + } + + character = value.charAt(index); + + if (character !== marker) { + return; + } + + title = queue; + subvalue += queue + character; + index++; + + while (index < length) { + character = value.charAt(index); + + if (!isWhiteSpace(character)) { + break; + } + + subvalue += character; + index++; + } + } else { + whiteSpaceQueue = EMPTY; + + while (index < length) { + character = value.charAt(index); + + if (character === marker) { + if (hasMarker) { + queue += marker + whiteSpaceQueue; + whiteSpaceQueue = EMPTY; + } + + hasMarker = true; + } else if (!hasMarker) { + queue += character; + } else if (character === C_PAREN_CLOSE) { + subvalue += queue + marker + whiteSpaceQueue; + title = queue; + break; + } else if (isWhiteSpace(character)) { + whiteSpaceQueue += character; + } else { + queue += marker + whiteSpaceQueue + character; + whiteSpaceQueue = EMPTY; + hasMarker = false; + } + + index++; + } + } + } + + if (value.charAt(index) !== C_PAREN_CLOSE) { + return; + } + + /* istanbul ignore if - never used (yet) */ + if (silent) { + return true; + } + + subvalue += C_PAREN_CLOSE; + + return eat(subvalue)( + self.renderLink(!isImage, self.descape(url), content, title, now, eat) + ); } /** - * Tokenise a URL in carets. + * Tokenise a reference link, image, or footnote; + * shortcut reference link, or footnote. * * @example - * tokenizeAutoLink(eat, '', 'http://foo.bar', ''); + * tokenizeReference(eat, '[foo]'); + * tokenizeReference(eat, '[foo][]'); + * tokenizeReference(eat, '[foo][bar]'); * - * @property {boolean} notInLink * @param {function(string)} eat - * @param {string} $0 - Whole link. - * @param {string} $1 - URL. - * @param {string?} [$2] - Protocol or at. - * @return {Node} - `link` node. + * @param {string} value - Rest of content. + * @param {boolean?} [silent] - Whether this is a dry run. + * @return {Node?|boolean} - Reference node. */ -function tokenizeAutoLink(eat, $0, $1, $2) { +function tokenizeReference(eat, value, silent) { var self = this; - var href = $1; - var text = $1; - var now = eat.now(); - var offset = 1; - var tokenize; + var character = value.charAt(0); + var index = 0; + var length = value.length; + var subvalue = EMPTY; + var intro = EMPTY; + var type = T_LINK; + var referenceType = REFERENCE_TYPE_SHORTCUT; + var text; + var identifier; + var now; var node; + var exitLink; + var queue; + var bracketed; + var depth; + + /* + * Check whether we’re eating an image. + */ + + if (character === C_EXCLAMATION_MARK) { + type = T_IMAGE; + intro = character; + character = value.charAt(++index); + } + + if (character !== C_BRACKET_OPEN) { + return; + } + + index++; + intro += character; + queue = EMPTY; + + /* + * Check whether we’re eating a footnote. + */ + + if ( + self.options.footnotes && + type === T_LINK && + value.charAt(index) === C_CARET + ) { + intro += C_CARET; + index++; + type = T_FOOTNOTE; + } + + /* + * Eat the text. + */ + + depth = 0; + + while (index < length) { + character = value.charAt(index); + + if (character === C_BRACKET_OPEN) { + bracketed = true; + depth++; + } else if (character === C_BRACKET_CLOSE) { + if (!depth) { + break; + } - if ($2 === AT_SIGN) { - if ( - text.substr(0, MAILTO_PROTOCOL.length).toLowerCase() !== - MAILTO_PROTOCOL - ) { - href = MAILTO_PROTOCOL + text; - } else { - text = text.substr(MAILTO_PROTOCOL.length); - offset += MAILTO_PROTOCOL.length; + depth--; + } + + if (character === C_SLASH) { + queue += C_SLASH; + character = value.charAt(++index); } + + queue += character; + index++; } - now.column += offset; + if (!queue) { + return; + } - /* - * Temporarily remove support for escapes in autolinks. - */ + subvalue = text = queue; + character = value.charAt(index); - tokenize = self.inlineTokenizers.escape; - self.inlineTokenizers.escape = null; + if (character !== C_BRACKET_CLOSE) { + return; + } - node = eat($0)(self.renderLink(true, href, text, null, now, eat)); + index++; + subvalue += character; + queue = EMPTY; - self.inlineTokenizers.escape = tokenize; + while (index < length) { + character = value.charAt(index); - return node; -} + if (!isWhiteSpace(character)) { + break; + } -tokenizeAutoLink.notInLink = true; + queue += character; + index++; + } -/** - * Tokenise a URL in text. - * - * @example - * tokenizeURL(eat, 'http://foo.bar'); - * - * @property {boolean} notInLink - * @param {function(string)} eat - * @param {string} $0 - Whole link. - * @return {Node} - `link` node. - */ -function tokenizeURL(eat, $0) { - var now = eat.now(); + character = value.charAt(index); - return eat($0)(this.renderLink(true, $0, $0, null, now, eat)); -} + if (character !== C_BRACKET_OPEN) { + identifier = text; + } else { + identifier = EMPTY; + queue += character; + index++; -tokenizeURL.notInLink = true; + while (index < length) { + character = value.charAt(index); -/** - * Tokenise an HTML tag. - * - * @example - * tokenizeTag(eat, ''); - * - * @param {function(string)} eat - * @param {string} $0 - Content. - * @return {Node} - `html` node. - */ -function tokenizeTag(eat, $0) { - var self = this; + if ( + character === C_BRACKET_OPEN || + character === C_BRACKET_CLOSE + ) { + break; + } - if (!self.inLink && EXPRESSION_HTML_LINK_OPEN.test($0)) { - self.inLink = true; - } else if (self.inLink && EXPRESSION_HTML_LINK_CLOSE.test($0)) { - self.inLink = false; - } + if (character === C_SLASH) { + identifier += C_SLASH; + character = value.charAt(++index); + } - return eat($0)(self.renderRaw(HTML, $0)); -} + identifier += character; + index++; + } -/** - * Tokenise a link. - * - * @example - * tokenizeLink( - * eat, '![foo](fav.ico "Favicon")', '![', 'foo', null, - * 'fav.ico', 'Foo Domain' - * ); - * - * @param {function(string)} eat - * @param {string} $0 - Whole link. - * @param {string} $1 - Prefix. - * @param {string} $2 - Text. - * @param {string?} $3 - URL wrapped in angle braces. - * @param {string?} $4 - Literal URL. - * @param {string?} $5 - Title wrapped in single or double - * quotes. - * @param {string?} [$6] - Title wrapped in double quotes. - * @param {string?} [$7] - Title wrapped in parentheses. - * @return {Node?} - `link` node, `image` node, or `null`. - */ -function tokenizeLink(eat, $0, $1, $2, $3, $4, $5, $6, $7) { - var isLink = $1 === BRACKET_OPEN; - var href = $4 || $3 || ''; - var title = $7 || $6 || $5; - var now; + character = value.charAt(index); - if (!isLink || !this.inLink) { - now = eat.now(); + if (character === C_BRACKET_CLOSE) { + queue += identifier + character; + index++; - now.column += $1.length; + referenceType = identifier ? + REFERENCE_TYPE_FULL : + REFERENCE_TYPE_COLLAPSED; + } else { + identifier = EMPTY; + } - return eat($0)(this.renderLink( - isLink, this.descape(href), $2, title, now, eat - )); + subvalue += queue; + queue = EMPTY; } - return null; -} - -/** - * Tokenise a reference link, image, or footnote; - * shortcut reference link, or footnote. - * - * @example - * tokenizeReference(eat, '[foo]', '[', 'foo'); - * tokenizeReference(eat, '[foo][]', '[', 'foo', ''); - * tokenizeReference(eat, '[foo][bar]', '[', 'foo', 'bar'); - * - * @param {function(string)} eat - * @param {string} $0 - Whole link. - * @param {string} $1 - Prefix. - * @param {string} $2 - identifier. - * @param {string} $3 - Content. - * @return {Node?} - `linkReference`, `imageReference`, or - * `footnoteReference`. Returns null when this is a link - * reference, but we're already in a link. - */ -function tokenizeReference(eat, $0, $1, $2, $3) { - var self = this; - var text = $2; - var identifier = $3 || $2; - var type = $1 === BRACKET_OPEN ? 'link' : 'image'; - var isFootnote = self.options.footnotes && identifier.charAt(0) === CARET; - var now = eat.now(); - var referenceType; - var node; - var exitLink; + /* + * Brackets cannot be inside the identifier. + */ - if ($3 === undefined) { - referenceType = 'shortcut'; - } else if ($3 === '') { - referenceType = 'collapsed'; - } else { - referenceType = 'full'; + if (referenceType !== REFERENCE_TYPE_FULL && bracketed) { + return; } - if (referenceType !== 'shortcut') { - isFootnote = false; + /* + * Inline footnotes cannot have an identifier. + */ + + if (type === T_FOOTNOTE && referenceType !== REFERENCE_TYPE_SHORTCUT) { + type = T_LINK; + intro = C_BRACKET_OPEN + C_CARET; + text = C_CARET + text; } - if (isFootnote) { - identifier = identifier.substr(1); + subvalue = intro + subvalue; + + if (type === T_LINK && self.inLink) { + return null; } - if (isFootnote) { - if (identifier.indexOf(SPACE) !== -1) { - return eat($0)(self.renderFootnote(identifier, eat.now())); - } else { - type = 'footnote'; - } + /* istanbul ignore if - never used (yet) */ + if (silent) { + return true; } - if (self.inLink && type === 'link') { - return null; + if (type === T_FOOTNOTE && text.indexOf(C_SPACE) !== -1) { + return eat(subvalue)(self.renderFootnote(text, eat.now())); } - now.column += $1.length; + now = eat.now(); + now.column += intro.length; + identifier = referenceType === REFERENCE_TYPE_FULL ? identifier : text; node = { 'type': type + 'Reference', 'identifier': normalize(identifier) }; - if (type === 'link' || type === 'image') { + if (type === T_LINK || type === T_IMAGE) { node.referenceType = referenceType; } - if (type === 'link') { + if (type === T_LINK) { exitLink = self.enterLink(); node.children = self.tokenizeInline(text, now); exitLink(); - } else if (type === 'image') { + } else if (type === T_IMAGE) { node.alt = decode(self.descape(text), eat); } - return eat($0)(node); + return eat(subvalue)(node); } /** * Tokenise strong emphasis. * * @example - * tokenizeStrong(eat, '**foo**', '**', 'foo'); - * tokenizeStrong(eat, '__foo__', null, null, '__', 'foo'); + * tokenizeStrong(eat, '**foo**'); + * tokenizeStrong(eat, '__foo__'); * * @param {function(string)} eat - * @param {string} $0 - Whole emphasis. - * @param {string?} $1 - Marker. - * @param {string?} $2 - Content. - * @param {string?} [$3] - Marker. - * @param {string?} [$4] - Content. - * @return {Node?} - `strong` node, when not empty. - */ -function tokenizeStrong(eat, $0, $1, $2, $3, $4) { - var now = eat.now(); - var value = $2 || $4; + * @param {string} value - Rest of content. + * @param {boolean?} [silent] - Whether this is a dry run. + * @return {Node?|boolean} - `strong` node. + */ +function tokenizeStrong(eat, value, silent) { + var self = this; + var index = 0; + var character = value.charAt(index); + var now; + var pedantic; + var marker; + var queue; + var subvalue; + var length; + var prev; - if (trim(value) === EMPTY) { - return null; + if ( + EMPHASIS_MARKERS[character] !== true || + value.charAt(++index) !== character + ) { + return; } - now.column += 2; + pedantic = self.options.pedantic; + marker = character; + subvalue = marker + marker; + length = value.length; + index++; + queue = character = EMPTY; + + if (pedantic && isWhiteSpace(value.charAt(index))) { + return; + } + + while (index < length) { + prev = character; + character = value.charAt(index); + + if ( + character === marker && + value.charAt(index + 1) === marker && + (!pedantic || !isWhiteSpace(prev)) + ) { + character = value.charAt(index + 2); + + if (character !== marker) { + if (!trim(queue)) { + return; + } + + /* istanbul ignore if - never used (yet) */ + if (silent) { + return true; + } + + now = eat.now(); + now.column += 2; - return eat($0)(this.renderInline(STRONG, value, now)); + return eat(subvalue + queue + subvalue)( + self.renderInline(T_STRONG, queue, now) + ); + } + } + + if (!pedantic && character === C_SLASH) { + queue += character; + character = value.charAt(++index); + } + + queue += character; + index++; + } } /** * Tokenise slight emphasis. * * @example - * tokenizeEmphasis(eat, '*foo*', '*', 'foo'); - * tokenizeEmphasis(eat, '_foo_', null, null, '_', 'foo'); + * tokenizeEmphasis(eat, '*foo*'); + * tokenizeEmphasis(eat, '_foo_'); * * @param {function(string)} eat - * @param {string} $0 - Whole emphasis. - * @param {string?} $1 - Marker. - * @param {string?} $2 - Content. - * @param {string?} [$3] - Marker. - * @param {string?} [$4] - Content. - * @return {Node?} - `emphasis` node, when not empty. - */ -function tokenizeEmphasis(eat, $0, $1, $2, $3, $4) { - var now = eat.now(); - var marker = $1 || $3; - var value = $2 || $4; + * @param {string} value - Rest of content. + * @param {boolean?} [silent] - Whether this is a dry run. + * @return {Node?|boolean} - `emphasis` node. + */ +function tokenizeEmphasis(eat, value, silent) { + var self = this; + var index = 0; + var character = value.charAt(index); + var now; + var pedantic; + var marker; + var queue; + var subvalue; + var length; + var prev; - if ( - trim(value) === EMPTY || - value.charAt(0) === marker || - value.charAt(value.length - 1) === marker - ) { - return null; + if (EMPHASIS_MARKERS[character] !== true) { + return; + } + + pedantic = self.options.pedantic; + subvalue = marker = character; + length = value.length; + index++; + queue = character = EMPTY; + + if (pedantic && isWhiteSpace(value.charAt(index))) { + return; } - now.column += 1; + while (index < length) { + prev = character; + character = value.charAt(index); + + if ( + character === marker && + (!pedantic || !isWhiteSpace(prev)) + ) { + character = value.charAt(++index); + + if (character !== marker) { + if (!trim(queue) || prev === marker) { + return; + } + + if ( + pedantic || + marker !== C_UNDERSCORE || + !isWordCharacter(character) + ) { + /* istanbul ignore if - never used (yet) */ + if (silent) { + return true; + } + + now = eat.now(); + now.column++; + + return eat(subvalue + queue + marker)( + self.renderInline(T_EMPHASIS, queue, now) + ); + } + } + + queue += marker; + } + + if (!pedantic && character === C_SLASH) { + queue += character; + character = value.charAt(++index); + } - return eat($0)(this.renderInline(EMPHASIS, value, now)); + queue += character; + index++; + } } /** * Tokenise a deletion. * * @example - * tokenizeDeletion(eat, '~~foo~~', '~~', 'foo'); + * tokenizeDeletion(eat, '~~foo~~'); * * @param {function(string)} eat - * @param {string} $0 - Whole deletion. - * @param {string} $1 - Content. - * @return {Node} - `delete` node. + * @param {string} value - Rest of content. + * @param {boolean?} [silent] - Whether this is a dry run. + * @return {Node?|boolean} - `delete` node. */ -function tokenizeDeletion(eat, $0, $1) { - var now = eat.now(); +function tokenizeDeletion(eat, value, silent) { + var self = this; + var character = EMPTY; + var previous = EMPTY; + var preceding = EMPTY; + var subvalue = EMPTY; + var index; + var length; + var now; + + if ( + !self.options.gfm || + value.charAt(0) !== C_TILDE || + value.charAt(1) !== C_TILDE || + isWhiteSpace(value.charAt(2)) + ) { + return; + } + index = 1; + length = value.length; + now = eat.now(); now.column += 2; - return eat($0)(this.renderInline(DELETE, $1, now)); + while (++index < length) { + character = value.charAt(index); + + if ( + character === C_TILDE && + previous === C_TILDE && + (!preceding || !isWhiteSpace(preceding)) + ) { + /* istanbul ignore if - never used (yet) */ + if (silent) { + return true; + } + + return eat(C_TILDE + C_TILDE + subvalue + C_TILDE + C_TILDE)( + self.renderInline(T_DELETE, subvalue, now) + ); + } + + subvalue += previous; + preceding = previous; + previous = character; + } } /** * Tokenise inline code. * * @example - * tokenizeInlineCode(eat, '`foo()`', '`', 'foo()'); + * tokenizeInlineCode(eat, '`foo()`'); * * @param {function(string)} eat - * @param {string} $0 - Whole code. - * @param {string} $1 - Initial markers. - * @param {string} $2 - Content. - * @return {Node} - `inlineCode` node. + * @param {string} value - Rest of content. + * @param {boolean?} [silent] - Whether this is a dry run. + * @return {Node?|boolean} - `inlineCode` node. */ -function tokenizeInlineCode(eat, $0, $1, $2) { - return eat($0)(this.renderRaw(INLINE_CODE, trim($2 || ''))); +function tokenizeInlineCode(eat, value, silent) { + var self = this; + var length = value.length; + var index = 0; + var queue = EMPTY; + var tickQueue = EMPTY; + var contentQueue; + var whiteSpaceQueue; + var count; + var openingCount; + var subvalue; + var character; + var found; + var next; + + while (index < length) { + if (value.charAt(index) !== C_TICK) { + break; + } + + queue += C_TICK; + index++; + } + + if (!queue) { + return; + } + + subvalue = queue; + openingCount = index; + queue = EMPTY; + next = value.charAt(index); + count = 0; + + while (index < length) { + character = next; + next = value.charAt(index + 1); + + if (character === C_TICK) { + count++; + tickQueue += character; + } else { + count = 0; + queue += character; + } + + if (count && next !== C_TICK) { + if (count === openingCount) { + subvalue += queue + tickQueue; + found = true; + break; + } + + queue += tickQueue; + tickQueue = EMPTY; + } + + index++; + } + + if (!found) { + if (openingCount % 2 !== 0) { + return; + } + + queue = EMPTY; + } + + /* istanbul ignore if - never used (yet) */ + if (silent) { + return true; + } + + contentQueue = whiteSpaceQueue = EMPTY; + length = queue.length; + index = -1; + + while (++index < length) { + character = queue.charAt(index); + + if (isWhiteSpace(character)) { + whiteSpaceQueue += character; + continue; + } + + if (whiteSpaceQueue) { + if (contentQueue) { + contentQueue += whiteSpaceQueue; + } + + whiteSpaceQueue = EMPTY; + } + + contentQueue += character; + } + + return eat(subvalue)(self.renderRaw(T_INLINE_CODE, contentQueue)); } /** @@ -1904,11 +5033,41 @@ function tokenizeInlineCode(eat, $0, $1, $2) { * tokenizeBreak(eat, ' \n'); * * @param {function(string)} eat - * @param {string} $0 - * @return {Node} - `break` node. + * @param {string} value - Rest of content. + * @param {boolean?} [silent] - Whether this is a dry run. + * @return {Node?|boolean} - `break` node. */ -function tokenizeBreak(eat, $0) { - return eat($0)(this.renderVoid(BREAK)); +function tokenizeBreak(eat, value, silent) { + var self = this; + var breaks = self.options.breaks; + var length = value.length; + var index = -1; + var queue = EMPTY; + var character; + + while (++index < length) { + character = value.charAt(index); + + if (character === C_NEWLINE) { + if (!breaks && index < MIN_BREAK_LENGTH) { + return; + } + + /* istanbul ignore if - never used (yet) */ + if (silent) { + return true; + } + + queue += character; + return eat(queue)(self.renderVoid(T_BREAK)); + } + + if (character !== C_SPACE) { + return; + } + + queue += character; + } } /** @@ -1923,18 +5082,17 @@ function tokenizeBreak(eat, $0) { * @param {Object?} [options] - Passed to * `Parser#setOptions()`. */ -function Parser(file, options) { +function Parser(file, options, processor) { var self = this; - var rules = extend({}, self.expressions.rules); self.file = file; self.inLink = false; self.atTop = true; self.atStart = true; self.inBlockquote = false; + self.data = processor.data; - self.rules = rules; - self.descape = descapeFactory(rules, 'escape'); + self.descape = descapeFactory(self, T_ESCAPE); self.options = extend({}, self.options); @@ -1956,8 +5114,9 @@ function Parser(file, options) { */ Parser.prototype.setOptions = function (options) { var self = this; - var expressions = self.expressions; - var rules = self.rules; + var escape = self.data.escape; + var inlineTextStop = self.data.inlineTextStop; + var textStop = inlineTextStop.default; var current = self.options; var key; @@ -1969,26 +5128,20 @@ Parser.prototype.setOptions = function (options) { raise(options, 'options'); } - self.options = options; - for (key in defaultOptions) { + textStop = textStop.concat(inlineTextStop[key] || []); validate.boolean(options, key, current[key]); - - if (options[key]) { - extend(rules, expressions[key]); - } } - if (options.gfm && options.breaks) { - extend(rules, expressions.breaksGFM); - } - - if (options.gfm && options.commonmark) { - extend(rules, expressions.commonmarkGFM); - } + self.options = options; + self.textStop = textStop; if (options.commonmark) { - self.enterBlockquote = noopToggler(); + self.escape = escape.commonmark; + } else if (options.gfm) { + self.escape = escape.gfm; + } else { + self.escape = escape.default; } return self; @@ -2000,12 +5153,6 @@ Parser.prototype.setOptions = function (options) { Parser.prototype.options = defaultOptions; -/* - * Expose `expressions`. - */ - -Parser.prototype.expressions = defaultExpressions; - /** * Factory to track indentation for each line corresponding * to the given `start` and the number of invocations. @@ -2023,7 +5170,7 @@ Parser.prototype.indent = function (start) { * each line for each invocation. * * @example - * indenter(2) + * indenter(2); * * @param {number} offset - Number to increment the * offset. @@ -2058,7 +5205,7 @@ Parser.prototype.parse = function () { self.offset = {}; - node = self.renderBlock(ROOT, value); + node = self.renderBlock(T_ROOT, value); if (self.options.position) { node.position = { @@ -2096,7 +5243,6 @@ Parser.prototype.renderBlock = renderBlock; Parser.prototype.renderLink = renderLink; Parser.prototype.renderCodeBlock = renderCodeBlock; Parser.prototype.renderBlockquote = renderBlockquote; -Parser.prototype.renderList = renderList; Parser.prototype.renderListItem = renderListItem; Parser.prototype.renderFootnoteDefinition = renderFootnoteDefinition; Parser.prototype.renderHeading = renderHeading; @@ -2131,7 +5277,6 @@ function tokenizeFactory(type) { var self = this; var offset = self.offset; var tokens = []; - var rules = self.rules; var methods = self[type + 'Methods']; var tokenizers = self[type + 'Tokenizers']; var line = location ? location.line : 1; @@ -2142,7 +5287,6 @@ function tokenizeFactory(type) { var length; var method; var name; - var match; var matched; var valueLength; var eater; @@ -2164,15 +5308,13 @@ function tokenizeFactory(type) { * @param {string} subvalue */ function updatePosition(subvalue) { - var character = -1; - var subvalueLength = subvalue.length; var lastIndex = -1; + var index = subvalue.indexOf(C_NEWLINE); - while (++character < subvalueLength) { - if (subvalue.charAt(character) === NEW_LINE) { - lastIndex = character; - line++; - } + while (index !== -1) { + line++; + lastIndex = index; + index = subvalue.indexOf(C_NEWLINE, index + 1) } if (lastIndex === -1) { @@ -2192,7 +5334,7 @@ function tokenizeFactory(type) { /** * Get offset. Called before the fisrt character is - * eaten to retrieve the range's offsets. + * eaten to retrieve the range’s offsets. * * @return {Function} - `done`, to be called when * the last character is eaten. @@ -2203,7 +5345,7 @@ function tokenizeFactory(type) { /** * Done. Called when the last character is - * eaten to retrieve the range's offsets. + * eaten to retrieve the range’s offsets. * * @return {Array.} - Offset. */ @@ -2272,10 +5414,7 @@ function tokenizeFactory(type) { function validateEat(subvalue) { /* istanbul ignore if */ if (value.substring(0, subvalue.length) !== subvalue) { - self.file.fail( - 'Incorrectly eaten value: please report this ' + - 'warning on http://git.io/vUYWz', now() - ); + self.file.fail(ERR_INCORRECTLY_EATEN, now()); } } @@ -2319,10 +5458,10 @@ function tokenizeFactory(type) { /* * If there was already a `position`, this - * node was merged. Fixing `start` wasn't + * node was merged. Fixing `start` wasn’t * hard, but the indent is different. * Especially because some information, the - * indent between `n` and `l` wasn't + * indent between `n` and `l` wasn’t * tracked. Luckily, that space is * (should be?) empty, so we can safely * check for it now. @@ -2364,7 +5503,6 @@ function tokenizeFactory(type) { * @return {Object} - Added or merged into node. */ add = function (node, parent) { - var isMultiple = 'length' in node; var prev; var children; @@ -2374,32 +5512,28 @@ function tokenizeFactory(type) { children = parent.children; } - if (isMultiple) { - arrayPush.apply(children, node); - } else { - if (type === INLINE && node.type === TEXT) { - node.value = decode(node.value, eater); - } + if (type === INLINE && node.type === T_TEXT) { + node.value = decode(node.value, eater); + } - prev = children[children.length - 1]; + prev = children[children.length - 1]; - if ( - prev && - node.type === prev.type && - node.type in MERGEABLE_NODES - ) { - node = MERGEABLE_NODES[node.type].call( - self, prev, node - ); - } + if ( + prev && + node.type === prev.type && + node.type in MERGEABLE_NODES + ) { + node = MERGEABLE_NODES[node.type].call( + self, prev, node + ); + } - if (node !== prev) { - children.push(node); - } + if (node !== prev) { + children.push(node); + } - if (self.atStart && tokens.length) { - self.exitStart(); - } + if (self.atStart && tokens.length) { + self.exitStart(); } return node; @@ -2540,10 +5674,10 @@ function tokenizeFactory(type) { /* * Iterate over `value`, and iterate over all - * block-expressions. When one matches, invoke - * its companion function. If no expression - * matches, something failed (should not happen) - * and an exception is thrown. + * tokenizers. When one eats something, re-iterate + * with the remaining value. If no tokenizer eats, + * something failed (should not happen) and an + * exception is thrown. */ while (value) { @@ -2557,31 +5691,26 @@ function tokenizeFactory(type) { if ( method && - rules[name] && (!method.onlyAtStart || self.atStart) && (!method.onlyAtTop || self.atTop) && (!method.notInBlockquote || !self.inBlockquote) && (!method.notInLink || !self.inLink) ) { - match = rules[name].exec(value); - - if (match) { - valueLength = value.length; + valueLength = value.length; - method.apply(self, [eater].concat(match)); + method.apply(self, [eater, value]); - matched = valueLength !== value.length; + matched = valueLength !== value.length; - if (matched) { - break; - } + if (matched) { + break; } } } /* istanbul ignore if */ if (!matched) { - self.file.fail('Infinite loop', eater.now()); + self.file.fail(ERR_INFINITE_LOOP, eater.now()); /* * Errors are not thrown on `File#fail` @@ -2614,10 +5743,9 @@ Parser.prototype.blockTokenizers = { 'horizontalRule': tokenizeHorizontalRule, 'blockquote': tokenizeBlockquote, 'list': tokenizeList, - 'html': tokenizeHtml, + 'html': tokenizeHTML, 'definition': tokenizeDefinition, 'footnoteDefinition': tokenizeFootnoteDefinition, - 'looseTable': tokenizeTable, 'table': tokenizeTable, 'paragraph': tokenizeParagraph }; @@ -2668,7 +5796,6 @@ Parser.prototype.inlineTokenizers = { 'tag': tokenizeTag, 'link': tokenizeLink, 'reference': tokenizeReference, - 'shortcutReference': tokenizeReference, 'strong': tokenizeStrong, 'emphasis': tokenizeEmphasis, 'deletion': tokenizeDeletion, diff --git a/package.json b/package.json index 5b6f7e5d5..f6fcf453e 100644 --- a/package.json +++ b/package.json @@ -92,7 +92,6 @@ }, "scripts": { "build-man": "bin/mdast doc/*.?.md --config-path .mdastrc-man --quiet", - "build-expressions": "node script/build-expressions.js", "build-md": "bin/mdast . --quiet --frail", "build-version": "node script/build-version.js", "build-options": "node script/build-options.js", @@ -100,7 +99,7 @@ "lint-api": "eslint .", "lint-style": "jscs --reporter inline .", "lint": "npm run lint-api && npm run lint-style", - "build": "npm run build-expressions && npm run build-version && npm run build-md && npm run build-options && npm run build-man && npm run build-bundle", + "build": "npm run build-version && npm run build-md && npm run build-options && npm run build-man && npm run build-bundle", "test-api": "mocha --check-leaks test/index.js", "test-api-extensive": "TEST_EXTENDED=true npm run test-api", "test-cli": "bash test/cli.sh", diff --git a/script/build-expressions.js b/script/build-expressions.js deleted file mode 100644 index 9c1249dab..000000000 --- a/script/build-expressions.js +++ /dev/null @@ -1,689 +0,0 @@ -/** - * @author Titus Wormer - * @copyright 2015 Titus Wormer - * @license MIT - * @module mdast:script - * @fileoverview Build the expressions. - */ - -'use strict'; - -/* eslint-env node */ - -/* - * Dependencies. - */ - -var fs = require('fs'); - -/* - * Methods. - */ - -var write = fs.writeFileSync; - -/* - * Expressions. - */ - -var EXPRESSION = /(^|[^\[])\^/g; - -/** - * Clean an expression. - * - * @param {RegExp|string} expression - * @return {string} - */ -function cleanExpression(expression) { - return (expression.source || expression).replace(EXPRESSION, '$1'); -} - -/** - * Create something that matches until `end` is found, - * which does allow an escaped `end`, but not `end` itself. - * - * @param {string} end - * @return {string} - */ -function groupContent(end, negate) { - return '(?:' + - - /* - * Match an escape. - */ - - '\\\\[\\s\\S]' + - - '|' + - - /* - * Match anything other than `end`. - */ - - '[^' + (negate || end) + ']' + - ')*'; -} - -/** - * Create a group: something that matches from `start` - * until end, which does allow an escaped `end`, but - * not `end` itself. - * - * @param {string} start - * @param {string?} end - Defaults to end. - * @return {string} - */ -function group(start, end, negate) { - return start + '(' + groupContent(end || start, negate) + '?)' + (end || start); -} - -/* - * Exports. - */ - -var expressions = {}; -var rules = {}; -var gfm = {}; -var footnotes = {}; -var yaml = {}; -var pedantic = {}; -var commonmark = {}; -var commonmarkGFM = {}; -var breaks = {}; -var breaksGFM = {}; - -expressions.rules = rules; -expressions.gfm = gfm; -expressions.footnotes = footnotes; -expressions.yaml = yaml; -expressions.pedantic = pedantic; -expressions.commonmark = commonmark; -expressions.commonmarkGFM = commonmarkGFM; -expressions.breaks = breaks; -expressions.breaksGFM = breaksGFM; - -/* - * HTML Block elements. - */ - -var HTML_BLOCK_ELEMENTS = '(?:' + [ - 'article', - 'header', - 'aside', - 'hgroup', - 'blockquote', - 'hr', - 'iframe', - 'body', - 'li', - 'map', - 'button', - 'object', - 'canvas', - 'ol', - 'caption', - 'output', - 'col', - 'p', - 'colgroup', - 'pre', - 'dd', - 'progress', - 'div', - 'section', - 'dl', - 'table', - 'td', - 'dt', - 'tbody', - 'embed', - 'textarea', - 'fieldset', - 'tfoot', - 'figcaption', - 'th', - 'figure', - 'thead', - 'footer', - 'tr', - 'form', - 'ul', - 'h1', - 'h2', - 'h3', - 'h4', - 'h5', - 'h6', - 'video', - 'script', - 'style' -].join('|') + ')'; - -/* - * Block helpers. - */ - -rules.newline = /^\n((?:[ \t]*\n)*)/; - -rules.code = /^((?:(?: {4}|\t)[^\n]*\n?((?:[ \t]*\n)*))+)/; - -rules.horizontalRule = /^[ \t]*([-*_])( *\1){2,} *(?=\n|$)/; - -pedantic.heading = - /^([ \t]*)(#{1,6})([ \t]*)([^\n]*?)[ \t]*#*[ \t]*(?=\n|$)/; - -rules.heading = - /^([ \t]*)(#{1,6})(?:([ \t]+)([^\n]+?))??(?:[ \t]+#+)?[ \t]*(?=\n|$)/; - -rules.lineHeading = - /^(\ {0,3})([^\n]+?)[ \t]*\n\ {0,3}(=|-){1,}[ \t]*(?=\n|$)/; - -rules.definition = - /^[ \t]*\[((?:[^\\](?:\\|\\(?:\\{2})+)\]|[^\]])+)\]:[ \t\n]*(<[^>\[\]]+>|[^\s\[\]]+)(?:[ \t\n]+['"(]((?:[^\n]|\n(?!\n))*?)['")])?[ \t]*(?=\n|$)/; - -rules.bullet = /(?:[*+-]|\d+\.)/; - -rules.indent = new RegExp( - '^([ \\t]*)(' + cleanExpression(rules.bullet) + ')( {1,4}(?! )| |\\t)' -); - -rules.item = new RegExp( - cleanExpression(rules.indent) + - '[^\\n]*(?:\\n(?!\\1' + - cleanExpression(rules.bullet) + - '[ \\t])[^\\n]*)*', -'gm'); - -rules.list = new RegExp( - '^' + - '([ \\t]*)' + - '(' + cleanExpression(rules.bullet) + ')' + - '[ \\t][\\s\\S]+?' + - '(?:' + - - /* - * Modified Horizontal rule: - */ - - '(?=\\n+\\1?(?:[-*_][ \\t]*){3,}(?:\\n|$))' + - '|' + - - /* - * Modified Link Definition: - */ - - '(?=\\n+' + cleanExpression(rules.definition) + ')' + - '|' + - - '\\n{2,}(?![ \\t])(?!\\1' + - cleanExpression(rules.bullet) + - '[ \\t])' + - '|' + - - '$' + - ')' -); - -/** Add parentheses to numbered list-item-bullets. */ -function replaceBullet(expression) { - return expression.source.replace(/\\d\+\\\./g, '\\d+[\\.\\)]'); -} - -commonmark.list = new RegExp(replaceBullet(rules.list)); -commonmark.item = new RegExp(replaceBullet(rules.item), 'gm'); -commonmark.bullet = new RegExp(replaceBullet(rules.bullet)); -commonmark.indent = new RegExp(replaceBullet(rules.indent)); - -rules.blockquote = new RegExp( - '^(?=[ \\t]*>)(?:' + - '(?:' + - '(?:' + - '[ \\t]*>[^\\n]*\\n' + - ')*' + - '(?:' + - '[ \\t]*>[^\\n]+(?=\\n|$)' + - ')' + - '|' + - '(?!' + - '[ \\t]*>' + - ')' + - '(?!' + - cleanExpression(rules.definition) + - ')' + - '[^\\n]+' + - ')' + - '(?:\\n|$)' + - ')*' + - '(?:' + - '[ \\t]*>[ \\t]*' + - '(?:' + - '\\n[ \\t]*>[ \\t]*' + - ')*' + - ')?' -); - -var inlineTags = '(?!' + - '(?:' + - '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' + - ')' + - '(?!mailto:)' + - '\\w+(?!' + - ':\\/|[^\\w\\s@]*@' + - ')\\b'; - -var tagName = '(?:[a-zA-Z][a-zA-Z0-9]*)'; -var attributeName = '(?:[a-zA-Z_:][a-zA-Z0-9_.:-]*)'; -var whitespace = '(?:\\s+)'; -var unquotedAttribute = '[^"\'=<>`]+'; -var singleQuotedAttribute = '\'[^\']*\''; -var doubleQuotedAttribute = '"[^"]*"'; -var attributeValue = '(?:' + unquotedAttribute + '|' + singleQuotedAttribute + '|' + doubleQuotedAttribute + ')'; -var attributeValueSpec = '(?:' + whitespace + '?' + '=' + whitespace + '?' + attributeValue + ')'; -var attribute = '(?:' + whitespace + attributeName + attributeValueSpec + '?)'; -var openTag = '(?:<' + tagName + attribute + '*' + whitespace + '?/?>)'; -var closingTag = '(?:)'; -var openBlockTag = '(?:<' + HTML_BLOCK_ELEMENTS + attribute + '*' + whitespace + '?/?>?)'; -var closingBlockTag = '(?:)'; -var htmlComment = ''; -var commonmarkComment = '(?:)'; -var processingInstruction = '(?:<\\?(?:[^\\?]|\\?(?!>))+\\?>)'; -var declaration = '(?:)'; -var cdata = '(?:)'; -var htmlBlockTag = '(?:' + openBlockTag + '|' + closingBlockTag + ')'; - -var inlineTag = '^(?:' + - openTag + '|' + - closingTag + '|' + - htmlComment + '|' + - processingInstruction + '|' + - declaration + '|' + - cdata + -')'; - -var commonmarkInlineTag = '^(?:' + - openTag + '|' + - closingTag + '|' + - commonmarkComment + '|' + - processingInstruction + '|' + - declaration + '|' + - cdata + -')'; - -var html = new RegExp('^(?:' + - '[ \\t]*' + - '(?:' + - htmlBlockTag + '|' + - htmlComment + '|' + - processingInstruction + '|' + - declaration + '|' + - cdata + - ')' + - '[\\s\\S]*?' + - '[ \\t]*?' + - '(?:\\n{2,}|\\s*$)' + -')', 'i'); - -var commonmarkHTML = new RegExp('^(?:' + - '[ \\t]*' + - '(?:' + - htmlBlockTag + '|' + - commonmarkComment + '|' + - processingInstruction + '|' + - declaration + '|' + - cdata + - ')' + - '[\\s\\S]*?' + - '[ \\t]*?' + - '(?:\\n{2,}|\\s*$)' + -')', 'i'); - -rules.html = html; -commonmark.html = commonmarkHTML; - -rules.paragraph = new RegExp( - '^(?:(?:' + - '[^\\n]+\\n?' + - '(?!' + - cleanExpression(rules.horizontalRule) + - '|' + - cleanExpression(rules.heading) + - '|' + - cleanExpression(rules.lineHeading) + - '|' + - cleanExpression(rules.definition) + - '|' + - cleanExpression(rules.blockquote) + - '|' + - cleanExpression('<' + inlineTags) + - ')' + - ')+)' -); - -/* - * GFM Block Grammar. - */ - -gfm.fences = - /^( *)(([`~])\3{2,})[ \t]*([^\n`~]+)?[ \t]*(?:\n([\s\S]*?))??(?:\n\ {0,3}\2\3*[ \t]*(?=\n|$)|$)/; - -gfm.paragraph = new RegExp( - rules.paragraph.source.replace('(?=\\n|$)|', '(?=\\n|$)|' + - cleanExpression(gfm.fences).replace(/\\2/g, '\\4').replace(/\\3/g, '\\5') + - '|' + - cleanExpression(rules.list).replace(/\\1/g, '\\8') + - '|' - ) -); - -gfm.table = - /^( *\|(.+))\n( *\|( *[-:]+[-| :]*)\n)((?: *\|.*(?:\n|$))*)/; - -gfm.looseTable = - /^( *(\S.*\|.*))\n( *([-:]+ *\|[-| :]*)\n)((?:.*\|.*(?:\n|$))*)/; - -/* - * Footnote block grammar - */ - -footnotes.footnoteDefinition = - /^( *\[\^([^\]]+)\]: *)([^\n]+((?:\n+ +[^\n]+)*))/; - -/* - * YAML front matter. - */ - -yaml.yamlFrontMatter = /^-{3}\n([\s\S]+?\n)?-{3}/; - -/* - * Inline-Level Grammar. - */ - -rules.escape = /^\\([\\`*{}\[\]()#+\-.!_>])/; - -rules.autoLink = /^<([^ >]+(@|:\/)[^ >]+)>/; - -rules.tag = new RegExp(inlineTag); -commonmark.tag = new RegExp(commonmarkInlineTag); - -rules.strong = /^(_)_((?:\\[\s\S]|[^\\])+?)__(?!_)|^(\*)\*((?:\\[\s\S]|[^\\])+?)\*\*(?!\*)/; - -rules.emphasis = - /^\b(_)((?:__|\\[\s\S]|[^\\])+?)_\b|^(\*)((?:\*\*|\\[\s\S]|[^\\])+?)\*(?!\*)/; - -rules.inlineCode = /^(`+)((?!`)[\s\S]*?(?:`\s+|[^`]))?(\1)(?!`)/; - -rules.break = /^ {2,}\n(?!\s*$)/; - -rules.inlineText = /^[\s\S]+?(?=[\\' + - ')'; - -var href = '(?:' + - '(?!<)' + - '(' + - '(?:' + - '\\(' + - '(?:' + - '\\\\[\\s\\S]' + - '|' + - '[^\\)]' + - ')*?' + - '\\)' + - '|' + - '\\\\[\\s\\S]' + - '|' + - '[\\s\\S]' + - ')*?' + - ')' + - '|' + - '<(' + - '[\\s\\S]*?' + - ')>' + - ')'; - -commonmark.link = new RegExp( - '^(' + - '!?\\[' + - ')' + - '(' + - commonmarkInside + - ')' + - '\\]\\(\\s*' + - commonmarkHREF + - commonmarkTitle + - '\\s*\\)' -); - -rules.link = new RegExp( - '^(' + - '!?\\[' + - ')' + - '(' + - inside + - ')' + - '\\]\\(\\s*' + - href + - title + - '\\s*\\)' -); - -rules.shortcutReference = new RegExp( - '^(' + - '!?\\[' + - ')' + - '(' + - '(?:' + - '\\\\[\\s\\S]' + - '|' + - '[^\\[\\]]' + - ')+?' + - ')' + - '\\]' -); - -rules.reference = new RegExp( - '^(' + - '!?\\[' + - ')' + - '(' + - inside + - ')' + - '\\]' + - '\\s*\\[' + - '(' + - groupContent('\\]', '\\[\\]') + - ')' + - '\\]' -); - -commonmark.reference = new RegExp( - '^(' + - '!?\\[' + - ')' + - '(' + - commonmarkInside + - ')' + - '\\]' + - '\\s*\\[' + - '(' + - groupContent('\\]', '\\[\\]') + - ')' + - '\\]' -); - -/* - * GFM inline Grammar. - */ - -gfm.escape = new RegExp(rules.escape.source.replace('])', '~|])')); - -gfm.url = /^https?:\/\/[^\s<]+[^<.,:;"')\]\s]/; - -gfm.deletion = /^~~(?=\S)([\s\S]*?\S)~~/; - -gfm.inlineText = new RegExp( - rules.inlineText.source.replace(']|', '~]|https?:\\/\\/|') -); - -/* - * Pedantic Inline Grammar. - */ - -pedantic.strong = - /^(_)_(?=\S)([\s\S]*?\S)__(?!_)|^(\*)\*(?=\S)([\s\S]*?\S)\*\*(?!\*)/; - -pedantic.emphasis = - /^(_)(?=\S)([\s\S]*?\S)_(?!_)|^(\*)(?=\S)([\s\S]*?\S)\*(?!\*)/; - -/* - * CommonMark and CommonMark + GFM. - */ - -/** - * @param {RegExp} expression - * @return {RegExp} - */ -function commonmarkParagraph(expression) { - return new RegExp(expression.source - .replace(cleanExpression(rules.lineHeading) + '|', '') - .replace(cleanExpression(rules.definition) + '|', '') - .replace(/\[ \\t\]\*/g, function () { - return '\\ {0,3}'; - }) - ); -} - -commonmark.paragraph = commonmarkParagraph(rules.paragraph); -commonmarkGFM.paragraph = commonmarkParagraph(gfm.paragraph); - -commonmark.blockquote = new RegExp( - rules.blockquote.source.replace(')(?!', ')(?!' + - cleanExpression(rules.horizontalRule) + '|' + - cleanExpression(rules.list).replace(/\\1/g, '\\3') + '|' + - cleanExpression(gfm.fences) - .replace(/\\2/g, '\\9') - .replace(/\\3/g, '\\10') + '|' + - cleanExpression(rules.code) + '|' - ) -); - -/* - * The commonmark also matches all GFM escapes, - * so we do not need to overwrite it. - */ - -commonmark.escape = new RegExp( - rules.escape.source - .replace('])', '"$%&\',/:;<=?@^~|])') - .replace('([', '(\\n|[') -); - -/* - * GFM + Line Breaks Inline Grammar - */ - -breaks.break = new RegExp(rules.break.source.replace('{2,}', '*')); -breaks.inlineText = new RegExp(rules.inlineText.source.replace('{2,}', '*')); - -breaksGFM.inlineText = new RegExp( - gfm.inlineText.source.replace('{2,}', '*') -); - -/* - * Write. - */ - -var content; - -content = '/* This file is generated by `script/build-expressions.js` */\n' + - '/* eslint-env commonjs */\n' + - 'module.exports = {\n' + - Object.keys(expressions).map(function (key) { - var map = expressions[key]; - var result; - - result = ' \'' + key + '\': {\n'; - - result += Object.keys(map).map(function (name) { - return ' \'' + name + '\': ' + map[name].toString(); - }).join(',\n'); - - return result + '\n }'; - }).join(',\n') + - '\n};\n'; - -write('lib/expressions.js', content); diff --git a/script/regenerate-fixtures.js b/script/regenerate-fixtures.js index 46531e827..fa56507af 100644 --- a/script/regenerate-fixtures.js +++ b/script/regenerate-fixtures.js @@ -11,6 +11,7 @@ 'use strict'; /* eslint-env node */ +/* eslint-disable no-console */ /* * Dependencies. @@ -32,7 +33,14 @@ fixtures.forEach(function (fixture) { Object.keys(mapping).forEach(function (key) { var filename = name + (key ? '.' + key : key) + '.json'; - var result = mdast.parse(input, fixture.possibilities[key]); + var result; + + try { + result = mdast.parse(input, fixture.possibilities[key]); + } catch (err) { + console.log('Could not regenerate `' + filename + '`'); + throw err; + } result = JSON.stringify(result, null, 2) + '\n'; diff --git a/test/index.js b/test/index.js index 975d643f4..7b9f0a291 100644 --- a/test/index.js +++ b/test/index.js @@ -127,8 +127,10 @@ describe('mdast.parse(file, options?)', function () { /** * Tokenizer. */ - function emphasis(eat) { - eat.file.fail(message, eat.now()); + function emphasis(eat, value) { + if (value.charAt(0) === '*') { + eat.file.fail(message, eat.now()); + } } processor.Parser.prototype.inlineTokenizers.emphasis = emphasis; diff --git a/test/input/auto-link-invalid.text b/test/input/auto-link-invalid.text new file mode 100644 index 000000000..9483dab1a --- /dev/null +++ b/test/input/auto-link-invalid.text @@ -0,0 +1,9 @@ + + +( + +[foo]: " + +[bar]: ' \ No newline at end of file diff --git a/test/input/definition-unclosed.text b/test/input/definition-unclosed.text new file mode 100644 index 000000000..b4a77a543 --- /dev/null +++ b/test/input/definition-unclosed.text @@ -0,0 +1,5 @@ +[foo]: + +[bar]: + +[^both][invalid], [^this too][]. diff --git a/test/input/heading-atx-closed-trailing-white-space.text b/test/input/heading-atx-closed-trailing-white-space.text new file mode 100644 index 000000000..1aacf0494 --- /dev/null +++ b/test/input/heading-atx-closed-trailing-white-space.text @@ -0,0 +1,3 @@ +# Foo # + +## Bar ## diff --git a/test/input/html-comments.text b/test/input/html-comments.text index 97b4d35cf..cfd18b5fb 100644 --- a/test/input/html-comments.text +++ b/test/input/html-comments.text @@ -6,10 +6,18 @@ Paragraph one. This is another comment. --> -What followes is not an HTML comment because it contains +What follows is not an HTML comment because it contains two consecutive dashes: . +But this is fine (in commonmark): + + + +And, this is wrong (in commonmark): + +--> + The end. diff --git a/test/input/html-declaration.text b/test/input/html-declaration.text index cf9d94fd3..b7866af6a 100644 --- a/test/input/html-declaration.text +++ b/test/input/html-declaration.text @@ -3,3 +3,7 @@ foo + + + + diff --git a/test/input/html-tags.text b/test/input/html-tags.text index bc5e27115..1031472fb 100644 --- a/test/input/html-tags.text +++ b/test/input/html-tags.text @@ -9,9 +9,30 @@
+<-article> + + +
'Hello + +![Hello](./world.html 'Hello + +![Hello](<./world.html> 'Hello + +[Hello](./world.html "Hello + +[Hello](<./world.html> "Hello + +![Hello](./world.html "Hello + +![Hello](<./world.html> "Hello + +[Hello](./world.html (Hello + +[Hello](<./world.html> (Hello + +![Hello](./world.html (Hello + +![Hello](<./world.html> (Hello diff --git a/test/input/links-url-unclosed.text b/test/input/links-url-unclosed.text new file mode 100644 index 000000000..68ea2686e --- /dev/null +++ b/test/input/links-url-unclosed.text @@ -0,0 +1,10 @@ +[Hello]( + + +[World](< + + +![Hello]( + + +![World](< diff --git a/test/input/reference-link-escape.nooutput.text b/test/input/reference-link-escape.nooutput.text new file mode 100644 index 000000000..bb0b941b3 --- /dev/null +++ b/test/input/reference-link-escape.nooutput.text @@ -0,0 +1,5 @@ +[b\*r*][b\-r], [b\*r*][], [b\*r*]. + +![foo][b\*r*], ![b\*r*][], ![b\*r*]. + +[b\*r*]: http://google.com diff --git a/test/input/reference-link-not-closed.text b/test/input/reference-link-not-closed.text new file mode 100644 index 000000000..143332dd7 --- /dev/null +++ b/test/input/reference-link-not-closed.text @@ -0,0 +1,5 @@ +[bar][bar + +[bar][ + +[bar] diff --git a/test/input/strong-initial-white-space.text b/test/input/strong-initial-white-space.text new file mode 100644 index 000000000..45e6c9eab --- /dev/null +++ b/test/input/strong-initial-white-space.text @@ -0,0 +1,4 @@ +** bar **. + + +__ bar __. \ No newline at end of file diff --git a/test/input/table-invalid-alignment.text b/test/input/table-invalid-alignment.text new file mode 100644 index 000000000..d2a042ca9 --- /dev/null +++ b/test/input/table-invalid-alignment.text @@ -0,0 +1,23 @@ +Missing alignment characters: + +| a | b | c | +| |---|---| +| d | e | f | + +* * * + +| a | b | c | +|---|---| | +| d | e | f | + +Invalid characters: + +| a | b | c | +|---|-*-|---| +| d | e | f | + +Too few columns: + +| a | +|---| +| d | diff --git a/test/mentions.js b/test/mentions.js index ec9a0ab12..722438814 100644 --- a/test/mentions.js +++ b/test/mentions.js @@ -19,61 +19,83 @@ var OVERWRITES = {}; OVERWRITES.mentions = OVERWRITES.mention = 'blog/821'; -/* +/** + * Tokenize a mention. + * * Username may only contain alphanumeric characters or * single hyphens, and cannot begin or end with a hyphen. * - * `PERSON` is either a user or an organization, but also - * matches a team: + * This matches a user, an organization, or a team: * * https://github.com/blog/1121-introducing-team-mentions - */ - -var NAME = '(?:[a-z0-9]{1,2}|[a-z0-9][a-z0-9-]{1,37}[a-z0-9])'; -var PERSON = '(' + NAME + '(?:\\/' + NAME + ')?)'; -var MENTION = new RegExp('^@' + PERSON + '\\b(?!-)', 'i'); - -/* - * Expression that matches characters not used in the above - * references. - */ - -var NON_GITHUB = /^[\s\S]+?(?:[^/.@#_a-zA-Z0-9-](?=@)|(?=$))/; - -/** - * Render a mention. * - * @param {Function} eat - * @param {string} $0 - Whole content. - * @param {Object} $1 - Username. - * @return {Node} + * @example + * tokenizeMention(eat, '@foo'); + * + * @param {function(string)} eat + * @param {string} value - Rest of content. + * @param {boolean?} [silent] - Whether this is a dry run. + * @return {Node?|boolean} - `delete` node. */ -function mention(eat, $0, $1) { - var now = eat.now(); - var href = 'https://github.com/'; - - href += has.call(OVERWRITES, $1) ? OVERWRITES[$1] : $1; - - return eat($0)(this.renderLink(true, href, $0, null, now, eat)); +function mention(eat, value, silent) { + var index = 1; + var length = value.length; + var slash = -1; + var character; + var subvalue; + var handle; + var href; + var now; + + if (value.charAt(0) !== '@' || value.charAt(1) === '-') { + return; + } + + while (index < length) { + character = value.charAt(index); + + if (character === '/') { + if (slash !== -1) { + break + } + + slash = index; + + if ( + value.charAt(index - 1) === '-' || + value.charAt(index + 1) === '-' + ) { + return; + } + } else if (!/[a-zA-Z0-9-]/.test(character)) { + break; + } + + index++; + } + + if (value.charAt(index - 1) === '-') { + return; + } + + if (silent) { + return; + } + + now = eat.now(); + href = 'https://github.com/'; + handle = value.slice(1, index); + subvalue = '@' + handle; + + href += has.call(OVERWRITES, handle) ? OVERWRITES[handle] : handle; + + return eat(subvalue)( + this.renderLink(true, href, subvalue, null, now, eat) + ); } mention.notInLink = true; -/** - * Factory to parse plain-text, and look for github - * entities. - * - * @param {Function} eat - * @param {string} $0 - Content. - * @return {Array.} - */ -function inlineText(eat, $0) { - var self = this; - var now = eat.now(); - - return eat($0)(self.augmentGitHub($0, now)); -} - /** * Attacher. * @@ -81,31 +103,20 @@ function inlineText(eat, $0) { */ function attacher(mdast) { var proto = mdast.Parser.prototype; - var scope = proto.inlineTokenizers; - var current = scope.inlineText; + var methods = proto.inlineMethods; /* - * Add a tokenizer to the `Parser`. + * Add `@` as a special inline character. */ - proto.augmentGitHub = proto.tokenizeFactory('mentions'); - - proto.mentionsMethods = ['mention']; - - proto.mentionsTokenizers = { - 'mention': mention - }; - - proto.expressions.gfm.mention = MENTION; + mdast.data.inlineTextStop.gfm.push('@'); /* - * Overwrite `inlineText`. + * Add a tokenizer to the `Parser`. */ - proto.mentionsMethods.push('mentionsText'); - proto.mentionsTokenizers.mentionsText = current; - proto.expressions.rules.mentionsText = NON_GITHUB; - scope.inlineText = inlineText; + proto.inlineTokenizers.mention = mention; + methods.splice(methods.indexOf('inlineText'), 0, 'mention'); } /* diff --git a/test/tree/auto-link-invalid.json b/test/tree/auto-link-invalid.json new file mode 100644 index 000000000..ea08a1825 --- /dev/null +++ b/test/tree/auto-link-invalid.json @@ -0,0 +1,170 @@ +{ + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "", + "position": { + "start": { + "line": 5, + "column": 1 + }, + "end": { + "line": 5, + "column": 19 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 5, + "column": 1 + }, + "end": { + "line": 5, + "column": 19 + }, + "indent": [] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "", + "position": { + "start": { + "line": 5, + "column": 1 + }, + "end": { + "line": 5, + "column": 19 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 5, + "column": 1 + }, + "end": { + "line": 5, + "column": 19 + }, + "indent": [] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "", + "position": { + "start": { + "line": 14, + "column": 8 + }, + "end": { + "line": 14, + "column": 13 + }, + "indent": [] + } + }, + { + "type": "text", + "value": "(", + "position": { + "start": { + "line": 14, + "column": 13 + }, + "end": { + "line": 14, + "column": 14 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 14, + "column": 1 + }, + "end": { + "line": 14, + "column": 14 + }, + "indent": [] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "linkReference", + "identifier": "foo", + "referenceType": "shortcut", + "children": [ + { + "type": "text", + "value": "foo", + "position": { + "start": { + "line": 16, + "column": 2 + }, + "end": { + "line": 16, + "column": 5 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 16, + "column": 1 + }, + "end": { + "line": 16, + "column": 6 + }, + "indent": [] + } + }, + { + "type": "text", + "value": ": ", + "position": { + "start": { + "line": 16, + "column": 6 + }, + "end": { + "line": 16, + "column": 8 + }, + "indent": [] + } + }, + { + "type": "html", + "value": "", + "position": { + "start": { + "line": 16, + "column": 8 + }, + "end": { + "line": 16, + "column": 13 + }, + "indent": [] + } + }, + { + "type": "text", + "value": "\"", + "position": { + "start": { + "line": 16, + "column": 13 + }, + "end": { + "line": 16, + "column": 14 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 16, + "column": 1 + }, + "end": { + "line": 16, + "column": 14 + }, + "indent": [] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "linkReference", + "identifier": "bar", + "referenceType": "shortcut", + "children": [ + { + "type": "text", + "value": "bar", + "position": { + "start": { + "line": 18, + "column": 2 + }, + "end": { + "line": 18, + "column": 5 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 18, + "column": 1 + }, + "end": { + "line": 18, + "column": 6 + }, + "indent": [] + } + }, + { + "type": "text", + "value": ": ", + "position": { + "start": { + "line": 18, + "column": 6 + }, + "end": { + "line": 18, + "column": 8 + }, + "indent": [] + } + }, + { + "type": "html", + "value": "", + "position": { + "start": { + "line": 18, + "column": 8 + }, + "end": { + "line": 18, + "column": 13 + }, + "indent": [] + } + }, + { + "type": "text", + "value": "'", + "position": { + "start": { + "line": 18, + "column": 13 + }, + "end": { + "line": 18, + "column": 14 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 18, + "column": 1 + }, + "end": { + "line": 18, + "column": 14 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1 + }, + "end": { + "line": 18, + "column": 14 + } + } +} diff --git a/test/tree/definition-unclosed.json b/test/tree/definition-unclosed.json new file mode 100644 index 000000000..0fa6475df --- /dev/null +++ b/test/tree/definition-unclosed.json @@ -0,0 +1,207 @@ +{ + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "linkReference", + "identifier": "foo", + "referenceType": "shortcut", + "children": [ + { + "type": "text", + "value": "foo", + "position": { + "start": { + "line": 1, + "column": 2 + }, + "end": { + "line": 1, + "column": 5 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1 + }, + "end": { + "line": 1, + "column": 6 + }, + "indent": [] + } + }, + { + "type": "text", + "value": ":", + "position": { + "start": { + "line": 1, + "column": 6 + }, + "end": { + "line": 1, + "column": 7 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1 + }, + "end": { + "line": 1, + "column": 7 + }, + "indent": [] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "linkReference", + "identifier": "bar", + "referenceType": "shortcut", + "children": [ + { + "type": "text", + "value": "bar", + "position": { + "start": { + "line": 3, + "column": 2 + }, + "end": { + "line": 3, + "column": 5 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 3, + "column": 1 + }, + "end": { + "line": 3, + "column": 6 + }, + "indent": [] + } + }, + { + "type": "text", + "value": ": ", + "position": { + "start": { + "line": 17, + "column": 1 + }, + "end": { + "line": 17, + "column": 41 + }, + "indent": [] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "And, this is wrong (in commonmark):", + "position": { + "start": { + "line": 19, + "column": 1 + }, + "end": { + "line": 19, + "column": 36 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 19, + "column": 1 + }, + "end": { + "line": 19, + "column": 36 + }, + "indent": [] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "-->", + "position": { + "start": { + "line": 21, + "column": 1 + }, + "end": { + "line": 21, + "column": 9 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 21, + "column": 1 + }, + "end": { + "line": 21, + "column": 9 + }, + "indent": [] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The end.", + "position": { + "start": { + "line": 23, + "column": 1 + }, + "end": { + "line": 23, + "column": 9 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 23, + "column": 1 + }, + "end": { + "line": 23, "column": 9 }, "indent": [] @@ -219,7 +327,7 @@ "column": 1 }, "end": { - "line": 16, + "line": 24, "column": 1 } } diff --git a/test/tree/html-comments.json b/test/tree/html-comments.json index 586e8fc22..042f8b4c0 100644 --- a/test/tree/html-comments.json +++ b/test/tree/html-comments.json @@ -70,7 +70,7 @@ "children": [ { "type": "text", - "value": "What followes is not an HTML comment because it contains\ntwo consecutive dashes:\n", + "value": "What follows is not an HTML comment because it contains\ntwo consecutive dashes:\n", "position": { "start": { "line": 9, @@ -170,7 +170,7 @@ "children": [ { "type": "text", - "value": "The end.", + "value": "But this is fine (in commonmark):", "position": { "start": { "line": 15, @@ -178,7 +178,7 @@ }, "end": { "line": 15, - "column": 9 + "column": 34 }, "indent": [] } @@ -191,6 +191,98 @@ }, "end": { "line": 15, + "column": 34 + }, + "indent": [] + } + }, + { + "type": "html", + "value": "", + "position": { + "start": { + "line": 17, + "column": 1 + }, + "end": { + "line": 17, + "column": 41 + }, + "indent": [] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "And, this is wrong (in commonmark):", + "position": { + "start": { + "line": 19, + "column": 1 + }, + "end": { + "line": 19, + "column": 36 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 19, + "column": 1 + }, + "end": { + "line": 19, + "column": 36 + }, + "indent": [] + } + }, + { + "type": "html", + "value": "-->", + "position": { + "start": { + "line": 21, + "column": 1 + }, + "end": { + "line": 21, + "column": 9 + }, + "indent": [] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "The end.", + "position": { + "start": { + "line": 23, + "column": 1 + }, + "end": { + "line": 23, + "column": 9 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 23, + "column": 1 + }, + "end": { + "line": 23, "column": 9 }, "indent": [] @@ -203,7 +295,7 @@ "column": 1 }, "end": { - "line": 16, + "line": 24, "column": 1 } } diff --git a/test/tree/html-declaration.json b/test/tree/html-declaration.json index 64c5d10bd..a2e2fbe25 100644 --- a/test/tree/html-declaration.json +++ b/test/tree/html-declaration.json @@ -36,12 +36,27 @@ }, { "type": "html", - "value": "", + "value": "", + "position": { + "start": { + "line": 3, + "column": 17 + }, "end": { "line": 3, "column": 18 @@ -76,6 +91,52 @@ }, "indent": [] } + }, + { + "type": "html", + "value": "", + "position": { + "start": { + "line": 7, + "column": 1 + }, + "end": { + "line": 7, + "column": 10 + }, + "indent": [] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "", + "position": { + "start": { + "line": 9, + "column": 1 + }, + "end": { + "line": 9, + "column": 11 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 9, + "column": 1 + }, + "end": { + "line": 9, + "column": 11 + }, + "indent": [] + } } ], "position": { @@ -84,7 +145,7 @@ "column": 1 }, "end": { - "line": 6, + "line": 10, "column": 1 } } diff --git a/test/tree/html-tags.json b/test/tree/html-tags.json index a6e9f0437..682d5afdb 100644 --- a/test/tree/html-tags.json +++ b/test/tree/html-tags.json @@ -79,8 +79,24 @@ } }, { - "type": "html", - "value": "
", + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "<-article>", + "position": { + "start": { + "line": 12, + "column": 1 + }, + "end": { + "line": 12, + "column": 11 + }, + "indent": [] + } + } + ], "position": { "start": { "line": 12, @@ -94,8 +110,24 @@ } }, { - "type": "html", - "value": "
", + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "
", + "position": { + "start": { + "line": 110, + "column": 1 + }, + "end": { + "line": 110, + "column": 16 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 110, + "column": 1 + }, + "end": { + "line": 110, + "column": 16 + }, + "indent": [] + } } ], "position": { @@ -440,7 +1044,7 @@ "column": 1 }, "end": { - "line": 50, + "line": 111, "column": 1 } } diff --git a/test/tree/links-reference-style.json b/test/tree/links-reference-style.json index e5363a0c1..483cd2889 100644 --- a/test/tree/links-reference-style.json +++ b/test/tree/links-reference-style.json @@ -1686,7 +1686,7 @@ }, { "type": "text", - "value": "this] and ", + "value": "this] and [this", "position": { "start": { "line": 57, @@ -1694,36 +1694,18 @@ }, "end": { "line": 57, - "column": 42 + "column": 47 }, "indent": [] } }, { - "type": "linkReference", - "identifier": "this\\", - "referenceType": "shortcut", - "children": [ - { - "type": "text", - "value": "this\\", - "position": { - "start": { - "line": 57, - "column": 43 - }, - "end": { - "line": 57, - "column": 48 - }, - "indent": [] - } - } - ], + "type": "escape", + "value": "]", "position": { "start": { "line": 57, - "column": 42 + "column": 47 }, "end": { "line": 57, diff --git a/test/tree/links-title-unclosed.commonmark.json b/test/tree/links-title-unclosed.commonmark.json new file mode 100644 index 000000000..c4770ec93 --- /dev/null +++ b/test/tree/links-title-unclosed.commonmark.json @@ -0,0 +1,687 @@ +{ + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "linkReference", + "identifier": "hello", + "referenceType": "shortcut", + "children": [ + { + "type": "text", + "value": "Hello", + "position": { + "start": { + "line": 1, + "column": 2 + }, + "end": { + "line": 1, + "column": 7 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1 + }, + "end": { + "line": 1, + "column": 8 + }, + "indent": [] + } + }, + { + "type": "text", + "value": "(./world.html 'Hello", + "position": { + "start": { + "line": 1, + "column": 8 + }, + "end": { + "line": 1, + "column": 28 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1 + }, + "end": { + "line": 1, + "column": 28 + }, + "indent": [] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "linkReference", + "identifier": "hello", + "referenceType": "shortcut", + "children": [ + { + "type": "text", + "value": "Hello", + "position": { + "start": { + "line": 3, + "column": 2 + }, + "end": { + "line": 3, + "column": 7 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 3, + "column": 1 + }, + "end": { + "line": 3, + "column": 8 + }, + "indent": [] + } + }, + { + "type": "text", + "value": "(<./world.html> 'Hello", + "position": { + "start": { + "line": 3, + "column": 8 + }, + "end": { + "line": 3, + "column": 30 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 3, + "column": 1 + }, + "end": { + "line": 3, + "column": 30 + }, + "indent": [] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "imageReference", + "identifier": "hello", + "referenceType": "shortcut", + "alt": "Hello", + "position": { + "start": { + "line": 5, + "column": 1 + }, + "end": { + "line": 5, + "column": 9 + }, + "indent": [] + } + }, + { + "type": "text", + "value": "(./world.html 'Hello", + "position": { + "start": { + "line": 5, + "column": 9 + }, + "end": { + "line": 5, + "column": 29 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 5, + "column": 1 + }, + "end": { + "line": 5, + "column": 29 + }, + "indent": [] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "imageReference", + "identifier": "hello", + "referenceType": "shortcut", + "alt": "Hello", + "position": { + "start": { + "line": 7, + "column": 1 + }, + "end": { + "line": 7, + "column": 9 + }, + "indent": [] + } + }, + { + "type": "text", + "value": "(<./world.html> 'Hello", + "position": { + "start": { + "line": 7, + "column": 9 + }, + "end": { + "line": 7, + "column": 31 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 7, + "column": 1 + }, + "end": { + "line": 7, + "column": 31 + }, + "indent": [] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "linkReference", + "identifier": "hello", + "referenceType": "shortcut", + "children": [ + { + "type": "text", + "value": "Hello", + "position": { + "start": { + "line": 9, + "column": 2 + }, + "end": { + "line": 9, + "column": 7 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 9, + "column": 1 + }, + "end": { + "line": 9, + "column": 8 + }, + "indent": [] + } + }, + { + "type": "text", + "value": "(./world.html \"Hello", + "position": { + "start": { + "line": 9, + "column": 8 + }, + "end": { + "line": 9, + "column": 28 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 9, + "column": 1 + }, + "end": { + "line": 9, + "column": 28 + }, + "indent": [] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "linkReference", + "identifier": "hello", + "referenceType": "shortcut", + "children": [ + { + "type": "text", + "value": "Hello", + "position": { + "start": { + "line": 11, + "column": 2 + }, + "end": { + "line": 11, + "column": 7 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 11, + "column": 1 + }, + "end": { + "line": 11, + "column": 8 + }, + "indent": [] + } + }, + { + "type": "text", + "value": "(<./world.html> \"Hello", + "position": { + "start": { + "line": 11, + "column": 8 + }, + "end": { + "line": 11, + "column": 30 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 11, + "column": 1 + }, + "end": { + "line": 11, + "column": 30 + }, + "indent": [] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "imageReference", + "identifier": "hello", + "referenceType": "shortcut", + "alt": "Hello", + "position": { + "start": { + "line": 13, + "column": 1 + }, + "end": { + "line": 13, + "column": 9 + }, + "indent": [] + } + }, + { + "type": "text", + "value": "(./world.html \"Hello", + "position": { + "start": { + "line": 13, + "column": 9 + }, + "end": { + "line": 13, + "column": 29 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 13, + "column": 1 + }, + "end": { + "line": 13, + "column": 29 + }, + "indent": [] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "imageReference", + "identifier": "hello", + "referenceType": "shortcut", + "alt": "Hello", + "position": { + "start": { + "line": 15, + "column": 1 + }, + "end": { + "line": 15, + "column": 9 + }, + "indent": [] + } + }, + { + "type": "text", + "value": "(<./world.html> \"Hello", + "position": { + "start": { + "line": 15, + "column": 9 + }, + "end": { + "line": 15, + "column": 31 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 15, + "column": 1 + }, + "end": { + "line": 15, + "column": 31 + }, + "indent": [] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "linkReference", + "identifier": "hello", + "referenceType": "shortcut", + "children": [ + { + "type": "text", + "value": "Hello", + "position": { + "start": { + "line": 17, + "column": 2 + }, + "end": { + "line": 17, + "column": 7 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 17, + "column": 1 + }, + "end": { + "line": 17, + "column": 8 + }, + "indent": [] + } + }, + { + "type": "text", + "value": "(./world.html (Hello", + "position": { + "start": { + "line": 17, + "column": 8 + }, + "end": { + "line": 17, + "column": 28 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 17, + "column": 1 + }, + "end": { + "line": 17, + "column": 28 + }, + "indent": [] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "linkReference", + "identifier": "hello", + "referenceType": "shortcut", + "children": [ + { + "type": "text", + "value": "Hello", + "position": { + "start": { + "line": 19, + "column": 2 + }, + "end": { + "line": 19, + "column": 7 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 19, + "column": 1 + }, + "end": { + "line": 19, + "column": 8 + }, + "indent": [] + } + }, + { + "type": "text", + "value": "(<./world.html> (Hello", + "position": { + "start": { + "line": 19, + "column": 8 + }, + "end": { + "line": 19, + "column": 30 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 19, + "column": 1 + }, + "end": { + "line": 19, + "column": 30 + }, + "indent": [] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "imageReference", + "identifier": "hello", + "referenceType": "shortcut", + "alt": "Hello", + "position": { + "start": { + "line": 21, + "column": 1 + }, + "end": { + "line": 21, + "column": 9 + }, + "indent": [] + } + }, + { + "type": "text", + "value": "(./world.html (Hello", + "position": { + "start": { + "line": 21, + "column": 9 + }, + "end": { + "line": 21, + "column": 29 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 21, + "column": 1 + }, + "end": { + "line": 21, + "column": 29 + }, + "indent": [] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "imageReference", + "identifier": "hello", + "referenceType": "shortcut", + "alt": "Hello", + "position": { + "start": { + "line": 23, + "column": 1 + }, + "end": { + "line": 23, + "column": 9 + }, + "indent": [] + } + }, + { + "type": "text", + "value": "(<./world.html> (Hello", + "position": { + "start": { + "line": 23, + "column": 9 + }, + "end": { + "line": 23, + "column": 31 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 23, + "column": 1 + }, + "end": { + "line": 23, + "column": 31 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1 + }, + "end": { + "line": 24, + "column": 1 + } + } +} diff --git a/test/tree/links-title-unclosed.json b/test/tree/links-title-unclosed.json new file mode 100644 index 000000000..c4770ec93 --- /dev/null +++ b/test/tree/links-title-unclosed.json @@ -0,0 +1,687 @@ +{ + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "linkReference", + "identifier": "hello", + "referenceType": "shortcut", + "children": [ + { + "type": "text", + "value": "Hello", + "position": { + "start": { + "line": 1, + "column": 2 + }, + "end": { + "line": 1, + "column": 7 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1 + }, + "end": { + "line": 1, + "column": 8 + }, + "indent": [] + } + }, + { + "type": "text", + "value": "(./world.html 'Hello", + "position": { + "start": { + "line": 1, + "column": 8 + }, + "end": { + "line": 1, + "column": 28 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1 + }, + "end": { + "line": 1, + "column": 28 + }, + "indent": [] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "linkReference", + "identifier": "hello", + "referenceType": "shortcut", + "children": [ + { + "type": "text", + "value": "Hello", + "position": { + "start": { + "line": 3, + "column": 2 + }, + "end": { + "line": 3, + "column": 7 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 3, + "column": 1 + }, + "end": { + "line": 3, + "column": 8 + }, + "indent": [] + } + }, + { + "type": "text", + "value": "(<./world.html> 'Hello", + "position": { + "start": { + "line": 3, + "column": 8 + }, + "end": { + "line": 3, + "column": 30 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 3, + "column": 1 + }, + "end": { + "line": 3, + "column": 30 + }, + "indent": [] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "imageReference", + "identifier": "hello", + "referenceType": "shortcut", + "alt": "Hello", + "position": { + "start": { + "line": 5, + "column": 1 + }, + "end": { + "line": 5, + "column": 9 + }, + "indent": [] + } + }, + { + "type": "text", + "value": "(./world.html 'Hello", + "position": { + "start": { + "line": 5, + "column": 9 + }, + "end": { + "line": 5, + "column": 29 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 5, + "column": 1 + }, + "end": { + "line": 5, + "column": 29 + }, + "indent": [] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "imageReference", + "identifier": "hello", + "referenceType": "shortcut", + "alt": "Hello", + "position": { + "start": { + "line": 7, + "column": 1 + }, + "end": { + "line": 7, + "column": 9 + }, + "indent": [] + } + }, + { + "type": "text", + "value": "(<./world.html> 'Hello", + "position": { + "start": { + "line": 7, + "column": 9 + }, + "end": { + "line": 7, + "column": 31 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 7, + "column": 1 + }, + "end": { + "line": 7, + "column": 31 + }, + "indent": [] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "linkReference", + "identifier": "hello", + "referenceType": "shortcut", + "children": [ + { + "type": "text", + "value": "Hello", + "position": { + "start": { + "line": 9, + "column": 2 + }, + "end": { + "line": 9, + "column": 7 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 9, + "column": 1 + }, + "end": { + "line": 9, + "column": 8 + }, + "indent": [] + } + }, + { + "type": "text", + "value": "(./world.html \"Hello", + "position": { + "start": { + "line": 9, + "column": 8 + }, + "end": { + "line": 9, + "column": 28 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 9, + "column": 1 + }, + "end": { + "line": 9, + "column": 28 + }, + "indent": [] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "linkReference", + "identifier": "hello", + "referenceType": "shortcut", + "children": [ + { + "type": "text", + "value": "Hello", + "position": { + "start": { + "line": 11, + "column": 2 + }, + "end": { + "line": 11, + "column": 7 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 11, + "column": 1 + }, + "end": { + "line": 11, + "column": 8 + }, + "indent": [] + } + }, + { + "type": "text", + "value": "(<./world.html> \"Hello", + "position": { + "start": { + "line": 11, + "column": 8 + }, + "end": { + "line": 11, + "column": 30 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 11, + "column": 1 + }, + "end": { + "line": 11, + "column": 30 + }, + "indent": [] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "imageReference", + "identifier": "hello", + "referenceType": "shortcut", + "alt": "Hello", + "position": { + "start": { + "line": 13, + "column": 1 + }, + "end": { + "line": 13, + "column": 9 + }, + "indent": [] + } + }, + { + "type": "text", + "value": "(./world.html \"Hello", + "position": { + "start": { + "line": 13, + "column": 9 + }, + "end": { + "line": 13, + "column": 29 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 13, + "column": 1 + }, + "end": { + "line": 13, + "column": 29 + }, + "indent": [] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "imageReference", + "identifier": "hello", + "referenceType": "shortcut", + "alt": "Hello", + "position": { + "start": { + "line": 15, + "column": 1 + }, + "end": { + "line": 15, + "column": 9 + }, + "indent": [] + } + }, + { + "type": "text", + "value": "(<./world.html> \"Hello", + "position": { + "start": { + "line": 15, + "column": 9 + }, + "end": { + "line": 15, + "column": 31 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 15, + "column": 1 + }, + "end": { + "line": 15, + "column": 31 + }, + "indent": [] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "linkReference", + "identifier": "hello", + "referenceType": "shortcut", + "children": [ + { + "type": "text", + "value": "Hello", + "position": { + "start": { + "line": 17, + "column": 2 + }, + "end": { + "line": 17, + "column": 7 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 17, + "column": 1 + }, + "end": { + "line": 17, + "column": 8 + }, + "indent": [] + } + }, + { + "type": "text", + "value": "(./world.html (Hello", + "position": { + "start": { + "line": 17, + "column": 8 + }, + "end": { + "line": 17, + "column": 28 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 17, + "column": 1 + }, + "end": { + "line": 17, + "column": 28 + }, + "indent": [] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "linkReference", + "identifier": "hello", + "referenceType": "shortcut", + "children": [ + { + "type": "text", + "value": "Hello", + "position": { + "start": { + "line": 19, + "column": 2 + }, + "end": { + "line": 19, + "column": 7 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 19, + "column": 1 + }, + "end": { + "line": 19, + "column": 8 + }, + "indent": [] + } + }, + { + "type": "text", + "value": "(<./world.html> (Hello", + "position": { + "start": { + "line": 19, + "column": 8 + }, + "end": { + "line": 19, + "column": 30 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 19, + "column": 1 + }, + "end": { + "line": 19, + "column": 30 + }, + "indent": [] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "imageReference", + "identifier": "hello", + "referenceType": "shortcut", + "alt": "Hello", + "position": { + "start": { + "line": 21, + "column": 1 + }, + "end": { + "line": 21, + "column": 9 + }, + "indent": [] + } + }, + { + "type": "text", + "value": "(./world.html (Hello", + "position": { + "start": { + "line": 21, + "column": 9 + }, + "end": { + "line": 21, + "column": 29 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 21, + "column": 1 + }, + "end": { + "line": 21, + "column": 29 + }, + "indent": [] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "imageReference", + "identifier": "hello", + "referenceType": "shortcut", + "alt": "Hello", + "position": { + "start": { + "line": 23, + "column": 1 + }, + "end": { + "line": 23, + "column": 9 + }, + "indent": [] + } + }, + { + "type": "text", + "value": "(<./world.html> (Hello", + "position": { + "start": { + "line": 23, + "column": 9 + }, + "end": { + "line": 23, + "column": 31 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 23, + "column": 1 + }, + "end": { + "line": 23, + "column": 31 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1 + }, + "end": { + "line": 24, + "column": 1 + } + } +} diff --git a/test/tree/links-title-unclosed.nogfm.json b/test/tree/links-title-unclosed.nogfm.json new file mode 100644 index 000000000..c4770ec93 --- /dev/null +++ b/test/tree/links-title-unclosed.nogfm.json @@ -0,0 +1,687 @@ +{ + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "linkReference", + "identifier": "hello", + "referenceType": "shortcut", + "children": [ + { + "type": "text", + "value": "Hello", + "position": { + "start": { + "line": 1, + "column": 2 + }, + "end": { + "line": 1, + "column": 7 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1 + }, + "end": { + "line": 1, + "column": 8 + }, + "indent": [] + } + }, + { + "type": "text", + "value": "(./world.html 'Hello", + "position": { + "start": { + "line": 1, + "column": 8 + }, + "end": { + "line": 1, + "column": 28 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1 + }, + "end": { + "line": 1, + "column": 28 + }, + "indent": [] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "linkReference", + "identifier": "hello", + "referenceType": "shortcut", + "children": [ + { + "type": "text", + "value": "Hello", + "position": { + "start": { + "line": 3, + "column": 2 + }, + "end": { + "line": 3, + "column": 7 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 3, + "column": 1 + }, + "end": { + "line": 3, + "column": 8 + }, + "indent": [] + } + }, + { + "type": "text", + "value": "(<./world.html> 'Hello", + "position": { + "start": { + "line": 3, + "column": 8 + }, + "end": { + "line": 3, + "column": 30 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 3, + "column": 1 + }, + "end": { + "line": 3, + "column": 30 + }, + "indent": [] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "imageReference", + "identifier": "hello", + "referenceType": "shortcut", + "alt": "Hello", + "position": { + "start": { + "line": 5, + "column": 1 + }, + "end": { + "line": 5, + "column": 9 + }, + "indent": [] + } + }, + { + "type": "text", + "value": "(./world.html 'Hello", + "position": { + "start": { + "line": 5, + "column": 9 + }, + "end": { + "line": 5, + "column": 29 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 5, + "column": 1 + }, + "end": { + "line": 5, + "column": 29 + }, + "indent": [] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "imageReference", + "identifier": "hello", + "referenceType": "shortcut", + "alt": "Hello", + "position": { + "start": { + "line": 7, + "column": 1 + }, + "end": { + "line": 7, + "column": 9 + }, + "indent": [] + } + }, + { + "type": "text", + "value": "(<./world.html> 'Hello", + "position": { + "start": { + "line": 7, + "column": 9 + }, + "end": { + "line": 7, + "column": 31 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 7, + "column": 1 + }, + "end": { + "line": 7, + "column": 31 + }, + "indent": [] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "linkReference", + "identifier": "hello", + "referenceType": "shortcut", + "children": [ + { + "type": "text", + "value": "Hello", + "position": { + "start": { + "line": 9, + "column": 2 + }, + "end": { + "line": 9, + "column": 7 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 9, + "column": 1 + }, + "end": { + "line": 9, + "column": 8 + }, + "indent": [] + } + }, + { + "type": "text", + "value": "(./world.html \"Hello", + "position": { + "start": { + "line": 9, + "column": 8 + }, + "end": { + "line": 9, + "column": 28 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 9, + "column": 1 + }, + "end": { + "line": 9, + "column": 28 + }, + "indent": [] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "linkReference", + "identifier": "hello", + "referenceType": "shortcut", + "children": [ + { + "type": "text", + "value": "Hello", + "position": { + "start": { + "line": 11, + "column": 2 + }, + "end": { + "line": 11, + "column": 7 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 11, + "column": 1 + }, + "end": { + "line": 11, + "column": 8 + }, + "indent": [] + } + }, + { + "type": "text", + "value": "(<./world.html> \"Hello", + "position": { + "start": { + "line": 11, + "column": 8 + }, + "end": { + "line": 11, + "column": 30 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 11, + "column": 1 + }, + "end": { + "line": 11, + "column": 30 + }, + "indent": [] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "imageReference", + "identifier": "hello", + "referenceType": "shortcut", + "alt": "Hello", + "position": { + "start": { + "line": 13, + "column": 1 + }, + "end": { + "line": 13, + "column": 9 + }, + "indent": [] + } + }, + { + "type": "text", + "value": "(./world.html \"Hello", + "position": { + "start": { + "line": 13, + "column": 9 + }, + "end": { + "line": 13, + "column": 29 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 13, + "column": 1 + }, + "end": { + "line": 13, + "column": 29 + }, + "indent": [] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "imageReference", + "identifier": "hello", + "referenceType": "shortcut", + "alt": "Hello", + "position": { + "start": { + "line": 15, + "column": 1 + }, + "end": { + "line": 15, + "column": 9 + }, + "indent": [] + } + }, + { + "type": "text", + "value": "(<./world.html> \"Hello", + "position": { + "start": { + "line": 15, + "column": 9 + }, + "end": { + "line": 15, + "column": 31 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 15, + "column": 1 + }, + "end": { + "line": 15, + "column": 31 + }, + "indent": [] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "linkReference", + "identifier": "hello", + "referenceType": "shortcut", + "children": [ + { + "type": "text", + "value": "Hello", + "position": { + "start": { + "line": 17, + "column": 2 + }, + "end": { + "line": 17, + "column": 7 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 17, + "column": 1 + }, + "end": { + "line": 17, + "column": 8 + }, + "indent": [] + } + }, + { + "type": "text", + "value": "(./world.html (Hello", + "position": { + "start": { + "line": 17, + "column": 8 + }, + "end": { + "line": 17, + "column": 28 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 17, + "column": 1 + }, + "end": { + "line": 17, + "column": 28 + }, + "indent": [] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "linkReference", + "identifier": "hello", + "referenceType": "shortcut", + "children": [ + { + "type": "text", + "value": "Hello", + "position": { + "start": { + "line": 19, + "column": 2 + }, + "end": { + "line": 19, + "column": 7 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 19, + "column": 1 + }, + "end": { + "line": 19, + "column": 8 + }, + "indent": [] + } + }, + { + "type": "text", + "value": "(<./world.html> (Hello", + "position": { + "start": { + "line": 19, + "column": 8 + }, + "end": { + "line": 19, + "column": 30 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 19, + "column": 1 + }, + "end": { + "line": 19, + "column": 30 + }, + "indent": [] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "imageReference", + "identifier": "hello", + "referenceType": "shortcut", + "alt": "Hello", + "position": { + "start": { + "line": 21, + "column": 1 + }, + "end": { + "line": 21, + "column": 9 + }, + "indent": [] + } + }, + { + "type": "text", + "value": "(./world.html (Hello", + "position": { + "start": { + "line": 21, + "column": 9 + }, + "end": { + "line": 21, + "column": 29 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 21, + "column": 1 + }, + "end": { + "line": 21, + "column": 29 + }, + "indent": [] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "imageReference", + "identifier": "hello", + "referenceType": "shortcut", + "alt": "Hello", + "position": { + "start": { + "line": 23, + "column": 1 + }, + "end": { + "line": 23, + "column": 9 + }, + "indent": [] + } + }, + { + "type": "text", + "value": "(<./world.html> (Hello", + "position": { + "start": { + "line": 23, + "column": 9 + }, + "end": { + "line": 23, + "column": 31 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 23, + "column": 1 + }, + "end": { + "line": 23, + "column": 31 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1 + }, + "end": { + "line": 24, + "column": 1 + } + } +} diff --git a/test/tree/links-url-mismatched-parentheses.json b/test/tree/links-url-mismatched-parentheses.json index 0d03a6c17..7f1472c44 100644 --- a/test/tree/links-url-mismatched-parentheses.json +++ b/test/tree/links-url-mismatched-parentheses.json @@ -5,9 +5,9 @@ "type": "paragraph", "children": [ { - "type": "link", - "title": null, - "href": "./world(and-hello(world)", + "type": "linkReference", + "identifier": "hello", + "referenceType": "shortcut", "children": [ { "type": "text", @@ -32,18 +32,18 @@ }, "end": { "line": 1, - "column": 34 + "column": 8 }, "indent": [] } }, { "type": "text", - "value": ".", + "value": "(./world(and-hello(world)).", "position": { "start": { "line": 1, - "column": 34 + "column": 8 }, "end": { "line": 1, @@ -261,9 +261,9 @@ "type": "paragraph", "children": [ { - "type": "image", - "title": null, - "src": "./world(and-hello(world)", + "type": "imageReference", + "identifier": "hello", + "referenceType": "shortcut", "alt": "Hello", "position": { "start": { @@ -272,18 +272,18 @@ }, "end": { "line": 9, - "column": 35 + "column": 9 }, "indent": [] } }, { "type": "text", - "value": ".", + "value": "(./world(and-hello(world)).", "position": { "start": { "line": 9, - "column": 35 + "column": 9 }, "end": { "line": 9, diff --git a/test/tree/links-url-mismatched-parentheses.nogfm.json b/test/tree/links-url-mismatched-parentheses.nogfm.json index 0d03a6c17..7f1472c44 100644 --- a/test/tree/links-url-mismatched-parentheses.nogfm.json +++ b/test/tree/links-url-mismatched-parentheses.nogfm.json @@ -5,9 +5,9 @@ "type": "paragraph", "children": [ { - "type": "link", - "title": null, - "href": "./world(and-hello(world)", + "type": "linkReference", + "identifier": "hello", + "referenceType": "shortcut", "children": [ { "type": "text", @@ -32,18 +32,18 @@ }, "end": { "line": 1, - "column": 34 + "column": 8 }, "indent": [] } }, { "type": "text", - "value": ".", + "value": "(./world(and-hello(world)).", "position": { "start": { "line": 1, - "column": 34 + "column": 8 }, "end": { "line": 1, @@ -261,9 +261,9 @@ "type": "paragraph", "children": [ { - "type": "image", - "title": null, - "src": "./world(and-hello(world)", + "type": "imageReference", + "identifier": "hello", + "referenceType": "shortcut", "alt": "Hello", "position": { "start": { @@ -272,18 +272,18 @@ }, "end": { "line": 9, - "column": 35 + "column": 9 }, "indent": [] } }, { "type": "text", - "value": ".", + "value": "(./world(and-hello(world)).", "position": { "start": { "line": 9, - "column": 35 + "column": 9 }, "end": { "line": 9, diff --git a/test/tree/links-url-unclosed.json b/test/tree/links-url-unclosed.json new file mode 100644 index 000000000..d21736be4 --- /dev/null +++ b/test/tree/links-url-unclosed.json @@ -0,0 +1,239 @@ +{ + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "linkReference", + "identifier": "hello", + "referenceType": "shortcut", + "children": [ + { + "type": "text", + "value": "Hello", + "position": { + "start": { + "line": 1, + "column": 2 + }, + "end": { + "line": 1, + "column": 7 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1 + }, + "end": { + "line": 1, + "column": 8 + }, + "indent": [] + } + }, + { + "type": "text", + "value": "(", + "position": { + "start": { + "line": 1, + "column": 8 + }, + "end": { + "line": 1, + "column": 9 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1 + }, + "end": { + "line": 1, + "column": 9 + }, + "indent": [] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "linkReference", + "identifier": "world", + "referenceType": "shortcut", + "children": [ + { + "type": "text", + "value": "World", + "position": { + "start": { + "line": 4, + "column": 2 + }, + "end": { + "line": 4, + "column": 7 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 4, + "column": 1 + }, + "end": { + "line": 4, + "column": 8 + }, + "indent": [] + } + }, + { + "type": "text", + "value": "(<", + "position": { + "start": { + "line": 4, + "column": 8 + }, + "end": { + "line": 4, + "column": 10 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 4, + "column": 1 + }, + "end": { + "line": 4, + "column": 10 + }, + "indent": [] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "imageReference", + "identifier": "hello", + "referenceType": "shortcut", + "alt": "Hello", + "position": { + "start": { + "line": 7, + "column": 1 + }, + "end": { + "line": 7, + "column": 9 + }, + "indent": [] + } + }, + { + "type": "text", + "value": "(", + "position": { + "start": { + "line": 7, + "column": 9 + }, + "end": { + "line": 7, + "column": 10 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 7, + "column": 1 + }, + "end": { + "line": 7, + "column": 10 + }, + "indent": [] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "imageReference", + "identifier": "world", + "referenceType": "shortcut", + "alt": "World", + "position": { + "start": { + "line": 10, + "column": 1 + }, + "end": { + "line": 10, + "column": 9 + }, + "indent": [] + } + }, + { + "type": "text", + "value": "(<", + "position": { + "start": { + "line": 10, + "column": 9 + }, + "end": { + "line": 10, + "column": 11 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 10, + "column": 1 + }, + "end": { + "line": 10, + "column": 11 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1 + }, + "end": { + "line": 11, + "column": 1 + } + } +} diff --git a/test/tree/markdown-documentation-basics.json b/test/tree/markdown-documentation-basics.json index df858d609..45693a12f 100644 --- a/test/tree/markdown-documentation-basics.json +++ b/test/tree/markdown-documentation-basics.json @@ -1143,14 +1143,13 @@ "column": 1 }, "end": { - "line": 119, - "column": 4 + "line": 118, + "column": 73 }, "indent": [ 1, 1, 1, - 1, 1 ] } diff --git a/test/tree/reference-link-escape.nooutput.json b/test/tree/reference-link-escape.nooutput.json new file mode 100644 index 000000000..fda1fd3a2 --- /dev/null +++ b/test/tree/reference-link-escape.nooutput.json @@ -0,0 +1,394 @@ +{ + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "linkReference", + "identifier": "b\\-r", + "referenceType": "full", + "children": [ + { + "type": "text", + "value": "b", + "position": { + "start": { + "line": 1, + "column": 2 + }, + "end": { + "line": 1, + "column": 3 + }, + "indent": [] + } + }, + { + "type": "escape", + "value": "*", + "position": { + "start": { + "line": 1, + "column": 3 + }, + "end": { + "line": 1, + "column": 5 + }, + "indent": [] + } + }, + { + "type": "text", + "value": "r*", + "position": { + "start": { + "line": 1, + "column": 5 + }, + "end": { + "line": 1, + "column": 7 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1 + }, + "end": { + "line": 1, + "column": 14 + }, + "indent": [] + } + }, + { + "type": "text", + "value": ", ", + "position": { + "start": { + "line": 1, + "column": 14 + }, + "end": { + "line": 1, + "column": 16 + }, + "indent": [] + } + }, + { + "type": "linkReference", + "identifier": "b\\*r*", + "referenceType": "collapsed", + "children": [ + { + "type": "text", + "value": "b", + "position": { + "start": { + "line": 1, + "column": 17 + }, + "end": { + "line": 1, + "column": 18 + }, + "indent": [] + } + }, + { + "type": "escape", + "value": "*", + "position": { + "start": { + "line": 1, + "column": 18 + }, + "end": { + "line": 1, + "column": 20 + }, + "indent": [] + } + }, + { + "type": "text", + "value": "r*", + "position": { + "start": { + "line": 1, + "column": 20 + }, + "end": { + "line": 1, + "column": 22 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 16 + }, + "end": { + "line": 1, + "column": 25 + }, + "indent": [] + } + }, + { + "type": "text", + "value": ", ", + "position": { + "start": { + "line": 1, + "column": 25 + }, + "end": { + "line": 1, + "column": 27 + }, + "indent": [] + } + }, + { + "type": "linkReference", + "identifier": "b\\*r*", + "referenceType": "shortcut", + "children": [ + { + "type": "text", + "value": "b", + "position": { + "start": { + "line": 1, + "column": 28 + }, + "end": { + "line": 1, + "column": 29 + }, + "indent": [] + } + }, + { + "type": "escape", + "value": "*", + "position": { + "start": { + "line": 1, + "column": 29 + }, + "end": { + "line": 1, + "column": 31 + }, + "indent": [] + } + }, + { + "type": "text", + "value": "r*", + "position": { + "start": { + "line": 1, + "column": 31 + }, + "end": { + "line": 1, + "column": 33 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 27 + }, + "end": { + "line": 1, + "column": 34 + }, + "indent": [] + } + }, + { + "type": "text", + "value": ".", + "position": { + "start": { + "line": 1, + "column": 34 + }, + "end": { + "line": 1, + "column": 35 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1 + }, + "end": { + "line": 1, + "column": 35 + }, + "indent": [] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "imageReference", + "identifier": "b\\*r*", + "referenceType": "full", + "alt": "foo", + "position": { + "start": { + "line": 3, + "column": 1 + }, + "end": { + "line": 3, + "column": 14 + }, + "indent": [] + } + }, + { + "type": "text", + "value": ", ", + "position": { + "start": { + "line": 3, + "column": 14 + }, + "end": { + "line": 3, + "column": 16 + }, + "indent": [] + } + }, + { + "type": "imageReference", + "identifier": "b\\*r*", + "referenceType": "collapsed", + "alt": "b*r*", + "position": { + "start": { + "line": 3, + "column": 16 + }, + "end": { + "line": 3, + "column": 26 + }, + "indent": [] + } + }, + { + "type": "text", + "value": ", ", + "position": { + "start": { + "line": 3, + "column": 26 + }, + "end": { + "line": 3, + "column": 28 + }, + "indent": [] + } + }, + { + "type": "imageReference", + "identifier": "b\\*r*", + "referenceType": "shortcut", + "alt": "b*r*", + "position": { + "start": { + "line": 3, + "column": 28 + }, + "end": { + "line": 3, + "column": 36 + }, + "indent": [] + } + }, + { + "type": "text", + "value": ".", + "position": { + "start": { + "line": 3, + "column": 36 + }, + "end": { + "line": 3, + "column": 37 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 3, + "column": 1 + }, + "end": { + "line": 3, + "column": 37 + }, + "indent": [] + } + }, + { + "type": "definition", + "identifier": "b\\*r*", + "title": null, + "link": "http://google.com", + "position": { + "start": { + "line": 5, + "column": 1 + }, + "end": { + "line": 5, + "column": 27 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1 + }, + "end": { + "line": 6, + "column": 1 + } + } +} diff --git a/test/tree/reference-link-not-closed.json b/test/tree/reference-link-not-closed.json new file mode 100644 index 000000000..e71a291b0 --- /dev/null +++ b/test/tree/reference-link-not-closed.json @@ -0,0 +1,177 @@ +{ + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "linkReference", + "identifier": "bar", + "referenceType": "shortcut", + "children": [ + { + "type": "text", + "value": "bar", + "position": { + "start": { + "line": 1, + "column": 2 + }, + "end": { + "line": 1, + "column": 5 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1 + }, + "end": { + "line": 1, + "column": 7 + }, + "indent": [] + } + }, + { + "type": "text", + "value": "bar", + "position": { + "start": { + "line": 1, + "column": 7 + }, + "end": { + "line": 1, + "column": 10 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1 + }, + "end": { + "line": 1, + "column": 10 + }, + "indent": [] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "linkReference", + "identifier": "bar", + "referenceType": "shortcut", + "children": [ + { + "type": "text", + "value": "bar", + "position": { + "start": { + "line": 3, + "column": 2 + }, + "end": { + "line": 3, + "column": 5 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 3, + "column": 1 + }, + "end": { + "line": 3, + "column": 7 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 3, + "column": 1 + }, + "end": { + "line": 3, + "column": 7 + }, + "indent": [] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "linkReference", + "identifier": "bar", + "referenceType": "shortcut", + "children": [ + { + "type": "text", + "value": "bar", + "position": { + "start": { + "line": 5, + "column": 2 + }, + "end": { + "line": 5, + "column": 5 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 5, + "column": 1 + }, + "end": { + "line": 5, + "column": 6 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 5, + "column": 1 + }, + "end": { + "line": 5, + "column": 6 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1 + }, + "end": { + "line": 6, + "column": 1 + } + } +} diff --git a/test/tree/strong-initial-white-space.commonmark.json b/test/tree/strong-initial-white-space.commonmark.json new file mode 100644 index 000000000..ea5142a6b --- /dev/null +++ b/test/tree/strong-initial-white-space.commonmark.json @@ -0,0 +1,139 @@ +{ + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "strong", + "children": [ + { + "type": "text", + "value": " bar ", + "position": { + "start": { + "line": 1, + "column": 3 + }, + "end": { + "line": 1, + "column": 8 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1 + }, + "end": { + "line": 1, + "column": 10 + }, + "indent": [] + } + }, + { + "type": "text", + "value": ".", + "position": { + "start": { + "line": 1, + "column": 10 + }, + "end": { + "line": 1, + "column": 11 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1 + }, + "end": { + "line": 1, + "column": 11 + }, + "indent": [] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "strong", + "children": [ + { + "type": "text", + "value": " bar ", + "position": { + "start": { + "line": 4, + "column": 3 + }, + "end": { + "line": 4, + "column": 8 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 4, + "column": 1 + }, + "end": { + "line": 4, + "column": 10 + }, + "indent": [] + } + }, + { + "type": "text", + "value": ".", + "position": { + "start": { + "line": 4, + "column": 10 + }, + "end": { + "line": 4, + "column": 11 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 4, + "column": 1 + }, + "end": { + "line": 4, + "column": 11 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1 + }, + "end": { + "line": 4, + "column": 11 + } + } +} diff --git a/test/tree/strong-initial-white-space.json b/test/tree/strong-initial-white-space.json new file mode 100644 index 000000000..ea5142a6b --- /dev/null +++ b/test/tree/strong-initial-white-space.json @@ -0,0 +1,139 @@ +{ + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "strong", + "children": [ + { + "type": "text", + "value": " bar ", + "position": { + "start": { + "line": 1, + "column": 3 + }, + "end": { + "line": 1, + "column": 8 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1 + }, + "end": { + "line": 1, + "column": 10 + }, + "indent": [] + } + }, + { + "type": "text", + "value": ".", + "position": { + "start": { + "line": 1, + "column": 10 + }, + "end": { + "line": 1, + "column": 11 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1 + }, + "end": { + "line": 1, + "column": 11 + }, + "indent": [] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "strong", + "children": [ + { + "type": "text", + "value": " bar ", + "position": { + "start": { + "line": 4, + "column": 3 + }, + "end": { + "line": 4, + "column": 8 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 4, + "column": 1 + }, + "end": { + "line": 4, + "column": 10 + }, + "indent": [] + } + }, + { + "type": "text", + "value": ".", + "position": { + "start": { + "line": 4, + "column": 10 + }, + "end": { + "line": 4, + "column": 11 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 4, + "column": 1 + }, + "end": { + "line": 4, + "column": 11 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1 + }, + "end": { + "line": 4, + "column": 11 + } + } +} diff --git a/test/tree/strong-initial-white-space.pedantic.json b/test/tree/strong-initial-white-space.pedantic.json new file mode 100644 index 000000000..869a96de8 --- /dev/null +++ b/test/tree/strong-initial-white-space.pedantic.json @@ -0,0 +1,77 @@ +{ + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "** bar **.", + "position": { + "start": { + "line": 1, + "column": 1 + }, + "end": { + "line": 1, + "column": 11 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1 + }, + "end": { + "line": 1, + "column": 11 + }, + "indent": [] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "__ bar __.", + "position": { + "start": { + "line": 4, + "column": 1 + }, + "end": { + "line": 4, + "column": 11 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 4, + "column": 1 + }, + "end": { + "line": 4, + "column": 11 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1 + }, + "end": { + "line": 4, + "column": 11 + } + } +} diff --git a/test/tree/table-invalid-alignment.json b/test/tree/table-invalid-alignment.json new file mode 100644 index 000000000..50fc29552 --- /dev/null +++ b/test/tree/table-invalid-alignment.json @@ -0,0 +1,270 @@ +{ + "type": "root", + "children": [ + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Missing alignment characters:", + "position": { + "start": { + "line": 1, + "column": 1 + }, + "end": { + "line": 1, + "column": 30 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1 + }, + "end": { + "line": 1, + "column": 30 + }, + "indent": [] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "| a | b | c |\n| |---|---|\n| d | e | f |", + "position": { + "start": { + "line": 3, + "column": 1 + }, + "end": { + "line": 5, + "column": 14 + }, + "indent": [ + 1, + 1 + ] + } + } + ], + "position": { + "start": { + "line": 3, + "column": 1 + }, + "end": { + "line": 5, + "column": 14 + }, + "indent": [ + 1, + 1 + ] + } + }, + { + "type": "horizontalRule", + "position": { + "start": { + "line": 7, + "column": 1 + }, + "end": { + "line": 7, + "column": 6 + }, + "indent": [] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "| a | b | c |\n|---|---| |\n| d | e | f |", + "position": { + "start": { + "line": 9, + "column": 1 + }, + "end": { + "line": 11, + "column": 14 + }, + "indent": [ + 1, + 1 + ] + } + } + ], + "position": { + "start": { + "line": 9, + "column": 1 + }, + "end": { + "line": 11, + "column": 14 + }, + "indent": [ + 1, + 1 + ] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Invalid characters:", + "position": { + "start": { + "line": 13, + "column": 1 + }, + "end": { + "line": 13, + "column": 20 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 13, + "column": 1 + }, + "end": { + "line": 13, + "column": 20 + }, + "indent": [] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "| a | b | c |\n|---|-*-|---|\n| d | e | f |", + "position": { + "start": { + "line": 15, + "column": 1 + }, + "end": { + "line": 17, + "column": 14 + }, + "indent": [ + 1, + 1 + ] + } + } + ], + "position": { + "start": { + "line": 15, + "column": 1 + }, + "end": { + "line": 17, + "column": 14 + }, + "indent": [ + 1, + 1 + ] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "Too few columns:", + "position": { + "start": { + "line": 19, + "column": 1 + }, + "end": { + "line": 19, + "column": 17 + }, + "indent": [] + } + } + ], + "position": { + "start": { + "line": 19, + "column": 1 + }, + "end": { + "line": 19, + "column": 17 + }, + "indent": [] + } + }, + { + "type": "paragraph", + "children": [ + { + "type": "text", + "value": "| a |\n|---|\n| d |", + "position": { + "start": { + "line": 21, + "column": 1 + }, + "end": { + "line": 23, + "column": 6 + }, + "indent": [ + 1, + 1 + ] + } + } + ], + "position": { + "start": { + "line": 21, + "column": 1 + }, + "end": { + "line": 23, + "column": 6 + }, + "indent": [ + 1, + 1 + ] + } + } + ], + "position": { + "start": { + "line": 1, + "column": 1 + }, + "end": { + "line": 24, + "column": 1 + } + } +} diff --git a/test/tree/title-attributes.json b/test/tree/title-attributes.json index 2cfbe7729..e762cb745 100644 --- a/test/tree/title-attributes.json +++ b/test/tree/title-attributes.json @@ -3138,7 +3138,7 @@ { "type": "link", "title": null, - "href": "./world.html (and (matching delimiters)", + "href": "./world.html (and (matching delimiters))", "children": [ { "type": "text", @@ -3161,21 +3161,6 @@ "line": 47, "column": 1 }, - "end": { - "line": 47, - "column": 49 - }, - "indent": [] - } - }, - { - "type": "text", - "value": ")", - "position": { - "start": { - "line": 47, - "column": 49 - }, "end": { "line": 47, "column": 50 @@ -3200,9 +3185,9 @@ "type": "paragraph", "children": [ { - "type": "link", - "title": null, - "href": "./world.html (and (mismatched delimiters)", + "type": "linkReference", + "identifier": "hello", + "referenceType": "shortcut", "children": [ { "type": "text", @@ -3225,6 +3210,21 @@ "line": 49, "column": 1 }, + "end": { + "line": 49, + "column": 8 + }, + "indent": [] + } + }, + { + "type": "text", + "value": "(./world.html (and (mismatched delimiters))", + "position": { + "start": { + "line": 49, + "column": 8 + }, "end": { "line": 49, "column": 51 @@ -3955,28 +3955,13 @@ { "type": "image", "title": null, - "src": "./world.png (and (matching delimiters)", + "src": "./world.png (and (matching delimiters))", "alt": "Hello", "position": { "start": { "line": 91, "column": 1 }, - "end": { - "line": 91, - "column": 49 - }, - "indent": [] - } - }, - { - "type": "text", - "value": ")", - "position": { - "start": { - "line": 91, - "column": 49 - }, "end": { "line": 91, "column": 50 @@ -4001,15 +3986,30 @@ "type": "paragraph", "children": [ { - "type": "image", - "title": null, - "src": "./world.png (and (mismatched delimiters)", + "type": "imageReference", + "identifier": "hello", + "referenceType": "shortcut", "alt": "Hello", "position": { "start": { "line": 93, "column": 1 }, + "end": { + "line": 93, + "column": 9 + }, + "indent": [] + } + }, + { + "type": "text", + "value": "(./world.png (and (mismatched delimiters))", + "position": { + "start": { + "line": 93, + "column": 9 + }, "end": { "line": 93, "column": 51 diff --git a/test/tree/title-attributes.nogfm.json b/test/tree/title-attributes.nogfm.json index 47a699340..1c32843ce 100644 --- a/test/tree/title-attributes.nogfm.json +++ b/test/tree/title-attributes.nogfm.json @@ -1103,7 +1103,7 @@ { "type": "link", "title": null, - "href": "./world.html (and (matching delimiters)", + "href": "./world.html (and (matching delimiters))", "children": [ { "type": "text", @@ -1126,21 +1126,6 @@ "line": 47, "column": 1 }, - "end": { - "line": 47, - "column": 49 - }, - "indent": [] - } - }, - { - "type": "text", - "value": ")", - "position": { - "start": { - "line": 47, - "column": 49 - }, "end": { "line": 47, "column": 50 @@ -1165,9 +1150,9 @@ "type": "paragraph", "children": [ { - "type": "link", - "title": null, - "href": "./world.html (and (mismatched delimiters)", + "type": "linkReference", + "identifier": "hello", + "referenceType": "shortcut", "children": [ { "type": "text", @@ -1190,6 +1175,21 @@ "line": 49, "column": 1 }, + "end": { + "line": 49, + "column": 8 + }, + "indent": [] + } + }, + { + "type": "text", + "value": "(./world.html (and (mismatched delimiters))", + "position": { + "start": { + "line": 49, + "column": 8 + }, "end": { "line": 49, "column": 51 @@ -1920,28 +1920,13 @@ { "type": "image", "title": null, - "src": "./world.png (and (matching delimiters)", + "src": "./world.png (and (matching delimiters))", "alt": "Hello", "position": { "start": { "line": 91, "column": 1 }, - "end": { - "line": 91, - "column": 49 - }, - "indent": [] - } - }, - { - "type": "text", - "value": ")", - "position": { - "start": { - "line": 91, - "column": 49 - }, "end": { "line": 91, "column": 50 @@ -1966,15 +1951,30 @@ "type": "paragraph", "children": [ { - "type": "image", - "title": null, - "src": "./world.png (and (mismatched delimiters)", + "type": "imageReference", + "identifier": "hello", + "referenceType": "shortcut", "alt": "Hello", "position": { "start": { "line": 93, "column": 1 }, + "end": { + "line": 93, + "column": 9 + }, + "indent": [] + } + }, + { + "type": "text", + "value": "(./world.png (and (mismatched delimiters))", + "position": { + "start": { + "line": 93, + "column": 9 + }, "end": { "line": 93, "column": 51