* Remove pinning of eslint-plugin-markdown An issue affecting Node.js source has been fixed in eslint-plugin-markdown so we don't need to pin it to beta-4 anymore. Refs: https://github.com/eslint/eslint-plugin-markdown/issues/69 * Update eslint-plugin-markdown up to 1.0.0-beta.7 * Fix docs for eslint-plugin-markdown@1.0.0-beta.7 PR-URL: https://github.com/nodejs/node/pull/14047 Reviewed-By: Rich Trott <rtrott@gmail.com> Reviewed-By: Yuta Hiroto <hello@about-hiroppy.com> Reviewed-By: Refael Ackermann <refack@gmail.com> Reviewed-By: Luigi Pinca <luigipinca@gmail.com> Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
399 lines
8 KiB
JavaScript
399 lines
8 KiB
JavaScript
/**
|
||
* @author Titus Wormer
|
||
* @copyright 2015 Titus Wormer
|
||
* @license MIT
|
||
* @module remark:parse:tokenize:link
|
||
* @fileoverview Tokenise a link.
|
||
*/
|
||
|
||
'use strict';
|
||
|
||
var has = require('has');
|
||
var whitespace = require('is-whitespace-character');
|
||
var locate = require('../locate/link');
|
||
|
||
module.exports = link;
|
||
link.locator = locate;
|
||
|
||
var C_BACKSLASH = '\\';
|
||
var C_BRACKET_OPEN = '[';
|
||
var C_BRACKET_CLOSE = ']';
|
||
var C_PAREN_OPEN = '(';
|
||
var C_PAREN_CLOSE = ')';
|
||
var C_LT = '<';
|
||
var C_GT = '>';
|
||
var C_TICK = '`';
|
||
var C_DOUBLE_QUOTE = '"';
|
||
var C_SINGLE_QUOTE = '\'';
|
||
|
||
/* Map of characters, which can be used to mark link
|
||
* and image titles. */
|
||
var LINK_MARKERS = {};
|
||
|
||
LINK_MARKERS[C_DOUBLE_QUOTE] = C_DOUBLE_QUOTE;
|
||
LINK_MARKERS[C_SINGLE_QUOTE] = C_SINGLE_QUOTE;
|
||
|
||
/* Map of characters, which can be used to mark link
|
||
* and image titles in commonmark-mode. */
|
||
var COMMONMARK_LINK_MARKERS = {};
|
||
|
||
COMMONMARK_LINK_MARKERS[C_DOUBLE_QUOTE] = C_DOUBLE_QUOTE;
|
||
COMMONMARK_LINK_MARKERS[C_SINGLE_QUOTE] = C_SINGLE_QUOTE;
|
||
COMMONMARK_LINK_MARKERS[C_PAREN_OPEN] = C_PAREN_CLOSE;
|
||
|
||
/* Tokenise a link. */
|
||
function link(eat, value, silent) {
|
||
var self = this;
|
||
var subvalue = '';
|
||
var index = 0;
|
||
var character = value.charAt(0);
|
||
var commonmark = self.options.commonmark;
|
||
var gfm = self.options.gfm;
|
||
var closed;
|
||
var count;
|
||
var opening;
|
||
var beforeURL;
|
||
var beforeTitle;
|
||
var subqueue;
|
||
var hasMarker;
|
||
var markers;
|
||
var isImage;
|
||
var content;
|
||
var marker;
|
||
var length;
|
||
var title;
|
||
var depth;
|
||
var queue;
|
||
var url;
|
||
var now;
|
||
var exit;
|
||
var node;
|
||
|
||
/* Detect whether this is an image. */
|
||
if (character === '!') {
|
||
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 = '';
|
||
index++;
|
||
|
||
/* Eat the content. */
|
||
length = value.length;
|
||
now = eat.now();
|
||
depth = 0;
|
||
|
||
now.column += index;
|
||
now.offset += index;
|
||
|
||
while (index < length) {
|
||
character = value.charAt(index);
|
||
subqueue = character;
|
||
|
||
if (character === C_TICK) {
|
||
/* Inline-code in link content. */
|
||
count = 1;
|
||
|
||
while (value.charAt(index + 1) === C_TICK) {
|
||
subqueue += character;
|
||
index++;
|
||
count++;
|
||
}
|
||
|
||
if (!opening) {
|
||
opening = count;
|
||
} else if (count >= opening) {
|
||
opening = 0;
|
||
}
|
||
} else if (character === C_BACKSLASH) {
|
||
/* Allow brackets to be escaped. */
|
||
index++;
|
||
subqueue += value.charAt(index);
|
||
/* In GFM mode, brackets in code still count.
|
||
* In all other modes, they don’t. This empty
|
||
* block prevents the next statements are
|
||
* entered. */
|
||
} else if ((!opening || gfm) && character === C_BRACKET_OPEN) {
|
||
depth++;
|
||
} else if ((!opening || gfm) && character === C_BRACKET_CLOSE) {
|
||
if (depth) {
|
||
depth--;
|
||
} else {
|
||
/* Allow white-space between content and
|
||
* url in GFM mode. */
|
||
if (gfm) {
|
||
while (index < length) {
|
||
character = value.charAt(index + 1);
|
||
|
||
if (!whitespace(character)) {
|
||
break;
|
||
}
|
||
|
||
subqueue += character;
|
||
index++;
|
||
}
|
||
}
|
||
|
||
if (value.charAt(index + 1) !== C_PAREN_OPEN) {
|
||
return;
|
||
}
|
||
|
||
subqueue += C_PAREN_OPEN;
|
||
closed = true;
|
||
index++;
|
||
|
||
break;
|
||
}
|
||
}
|
||
|
||
queue += subqueue;
|
||
subqueue = '';
|
||
index++;
|
||
}
|
||
|
||
/* Eat the content closing. */
|
||
if (!closed) {
|
||
return;
|
||
}
|
||
|
||
content = queue;
|
||
subvalue += queue + subqueue;
|
||
index++;
|
||
|
||
/* Eat white-space. */
|
||
while (index < length) {
|
||
character = value.charAt(index);
|
||
|
||
if (!whitespace(character)) {
|
||
break;
|
||
}
|
||
|
||
subvalue += character;
|
||
index++;
|
||
}
|
||
|
||
/* Eat the URL. */
|
||
character = value.charAt(index);
|
||
markers = commonmark ? COMMONMARK_LINK_MARKERS : LINK_MARKERS;
|
||
queue = '';
|
||
beforeURL = subvalue;
|
||
|
||
if (character === C_LT) {
|
||
index++;
|
||
beforeURL += C_LT;
|
||
|
||
while (index < length) {
|
||
character = value.charAt(index);
|
||
|
||
if (character === C_GT) {
|
||
break;
|
||
}
|
||
|
||
if (commonmark && character === '\n') {
|
||
return;
|
||
}
|
||
|
||
queue += character;
|
||
index++;
|
||
}
|
||
|
||
if (value.charAt(index) !== C_GT) {
|
||
return;
|
||
}
|
||
|
||
subvalue += C_LT + queue + C_GT;
|
||
url = queue;
|
||
index++;
|
||
} else {
|
||
character = null;
|
||
subqueue = '';
|
||
|
||
while (index < length) {
|
||
character = value.charAt(index);
|
||
|
||
if (subqueue && has(markers, character)) {
|
||
break;
|
||
}
|
||
|
||
if (whitespace(character)) {
|
||
if (commonmark) {
|
||
break;
|
||
}
|
||
|
||
subqueue += character;
|
||
} else {
|
||
if (character === C_PAREN_OPEN) {
|
||
depth++;
|
||
} else if (character === C_PAREN_CLOSE) {
|
||
if (depth === 0) {
|
||
break;
|
||
}
|
||
|
||
depth--;
|
||
}
|
||
|
||
queue += subqueue;
|
||
subqueue = '';
|
||
|
||
if (character === C_BACKSLASH) {
|
||
queue += C_BACKSLASH;
|
||
character = value.charAt(++index);
|
||
}
|
||
|
||
queue += character;
|
||
}
|
||
|
||
index++;
|
||
}
|
||
|
||
subvalue += queue;
|
||
url = queue;
|
||
index = subvalue.length;
|
||
}
|
||
|
||
/* Eat white-space. */
|
||
queue = '';
|
||
|
||
while (index < length) {
|
||
character = value.charAt(index);
|
||
|
||
if (!whitespace(character)) {
|
||
break;
|
||
}
|
||
|
||
queue += character;
|
||
index++;
|
||
}
|
||
|
||
character = value.charAt(index);
|
||
subvalue += queue;
|
||
|
||
/* Eat the title. */
|
||
if (queue && has(markers, character)) {
|
||
index++;
|
||
subvalue += character;
|
||
queue = '';
|
||
marker = markers[character];
|
||
beforeTitle = subvalue;
|
||
|
||
/* 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_BACKSLASH) {
|
||
queue += C_BACKSLASH;
|
||
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 (!whitespace(character)) {
|
||
break;
|
||
}
|
||
|
||
subvalue += character;
|
||
index++;
|
||
}
|
||
} else {
|
||
subqueue = '';
|
||
|
||
while (index < length) {
|
||
character = value.charAt(index);
|
||
|
||
if (character === marker) {
|
||
if (hasMarker) {
|
||
queue += marker + subqueue;
|
||
subqueue = '';
|
||
}
|
||
|
||
hasMarker = true;
|
||
} else if (!hasMarker) {
|
||
queue += character;
|
||
} else if (character === C_PAREN_CLOSE) {
|
||
subvalue += queue + marker + subqueue;
|
||
title = queue;
|
||
break;
|
||
} else if (whitespace(character)) {
|
||
subqueue += character;
|
||
} else {
|
||
queue += marker + subqueue + character;
|
||
subqueue = '';
|
||
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;
|
||
|
||
url = self.decode.raw(self.unescape(url), eat(beforeURL).test().end);
|
||
|
||
if (title) {
|
||
beforeTitle = eat(beforeTitle).test().end;
|
||
title = self.decode.raw(self.unescape(title), beforeTitle);
|
||
}
|
||
|
||
node = {
|
||
type: isImage ? 'image' : 'link',
|
||
title: title || null,
|
||
url: url
|
||
};
|
||
|
||
if (isImage) {
|
||
node.alt = self.decode.raw(self.unescape(content), now) || null;
|
||
} else {
|
||
exit = self.enterLink();
|
||
node.children = self.tokenizeInline(content, now);
|
||
exit();
|
||
}
|
||
|
||
return eat(subvalue)(node);
|
||
}
|