refactor(Util.escapeMarkdown): allow separate escaping and add tests (#3241)

* wip refactor

* add escapeMarkdown tests

* italics can be done with a single underscore too

* more refined

* fix test name

* unnecessary eslint ignores

* use jest

* make eslint less annoying in this test file

* more testing

* fix lib usage

* more tests and a small fix
This commit is contained in:
bdistin
2019-07-11 15:08:40 -05:00
committed by SpaceEEC
parent f1433a2d97
commit 00c4098bb3
5 changed files with 340 additions and 8 deletions

View File

@@ -75,14 +75,144 @@ class Util {
/**
* Escapes any Discord-flavour markdown in a string.
* @param {string} text Content to escape
* @param {boolean} [onlyCodeBlock=false] Whether to only escape codeblocks (takes priority)
* @param {boolean} [onlyInlineCode=false] Whether to only escape inline code
* @param {Object} [options={}] What types of markdown to escape
* @param {boolean} [options.codeBlock=true] Whether to escape code blocks or not
* @param {boolean} [options.inlineCode=true] Whether to escape inline code or not
* @param {boolean} [options.bold=true] Whether to escape bolds or not
* @param {boolean} [options.italic=true] Whether to escape italics or not
* @param {boolean} [options.underline=true] Whether to escape underlines or not
* @param {boolean} [options.strikethrough=true] Whether to escape strikethroughs or not
* @param {boolean} [options.spoiler=true] Whether to escape spoilers or not
* @param {boolean} [options.codeBlockContent=true] Whether to escape text inside code blocks or not
* @param {boolean} [options.inlineCodeContent=true] Whether to escape text inside inline code or not
* @returns {string}
*/
static escapeMarkdown(text, onlyCodeBlock = false, onlyInlineCode = false) {
if (onlyCodeBlock) return text.replace(/```/g, '`\u200b``');
if (onlyInlineCode) return text.replace(/\\(`|\\)/g, '$1').replace(/(`|\\)/g, '\\$1');
return text.replace(/\\(\*|_|`|~|\\)/g, '$1').replace(/(\*|_|`|~|\\)/g, '\\$1');
static escapeMarkdown(text, {
codeBlock = true,
inlineCode = true,
bold = true,
italic = true,
underline = true,
strikethrough = true,
spoiler = true,
codeBlockContent = true,
inlineCodeContent = true,
} = {}) {
if (!codeBlockContent) {
return text.split('```').map((subString, index, array) => {
if ((index % 2) && index !== array.length - 1) return subString;
return Util.escapeMarkdown(subString, {
inlineCode,
bold,
italic,
underline,
strikethrough,
spoiler,
inlineCodeContent,
});
}).join(codeBlock ? '\\`\\`\\`' : '```');
}
if (!inlineCodeContent) {
return text.split(/(?<=^|[^`])`(?=[^`]|$)/g).map((subString, index, array) => {
if ((index % 2) && index !== array.length - 1) return subString;
return Util.escapeMarkdown(subString, {
codeBlock,
bold,
italic,
underline,
strikethrough,
spoiler,
});
}).join(inlineCode ? '\\`' : '`');
}
if (inlineCode) text = Util.escapeInlineCode(text);
if (codeBlock) text = Util.escapeCodeBlock(text);
if (italic) text = Util.escapeItalic(text);
if (bold) text = Util.escapeBold(text);
if (underline) text = Util.escapeUnderline(text);
if (strikethrough) text = Util.escapeStrikethrough(text);
if (spoiler) text = Util.escapeSpoiler(text);
return text;
}
/**
* Escapes code block markdown in a string.
* @param {string} text Content to escape
* @returns {string}
*/
static escapeCodeBlock(text) {
return text.replace(/```/g, '\\`\\`\\`');
}
/**
* Escapes inline code markdown in a string.
* @param {string} text Content to escape
* @returns {string}
*/
static escapeInlineCode(text) {
return text.replace(/(?<=^|[^`])`(?=[^`]|$)/g, '\\`');
}
/**
* Escapes italic markdown in a string.
* @param {string} text Content to escape
* @returns {string}
*/
static escapeItalic(text) {
let i = 0;
text = text.replace(/(?<=^|[^*])\*([^*]|\*\*|$)/g, (_, match) => {
if (match === '**') return ++i % 2 ? `\\*${match}` : `${match}\\*`;
return `\\*${match}`;
});
i = 0;
return text.replace(/(?<=^|[^_])_([^_]|__|$)/g, (_, match) => {
if (match === '__') return ++i % 2 ? `\\_${match}` : `${match}\\_`;
return `\\_${match}`;
});
}
/**
* Escapes bold markdown in a string.
* @param {string} text Content to escape
* @returns {string}
*/
static escapeBold(text) {
let i = 0;
return text.replace(/\*\*(\*)?/g, (_, match) => {
if (match) return ++i % 2 ? `${match}\\*\\*` : `\\*\\*${match}`;
return '\\*\\*';
});
}
/**
* Escapes underline markdown in a string.
* @param {string} text Content to escape
* @returns {string}
*/
static escapeUnderline(text) {
let i = 0;
return text.replace(/__(_)?/g, (_, match) => {
if (match) return ++i % 2 ? `${match}\\_\\_` : `\\_\\_${match}`;
return '\\_\\_';
});
}
/**
* Escapes strikethrough markdown in a string.
* @param {string} text Content to escape
* @returns {string}
*/
static escapeStrikethrough(text) {
return text.replace(/~~/g, '\\~\\~');
}
/**
* Escapes spoiler markdown in a string.
* @param {string} text Content to escape
* @returns {string}
*/
static escapeSpoiler(text) {
return text.replace(/\|\|/g, '\\|\\|');
}
/**
@@ -421,6 +551,15 @@ class Util {
});
}
/**
* The content to put in a codeblock with all codeblock fences replaced by the equivalent backticks.
* @param {string} text The string to be converted
* @returns {string}
*/
static cleanCodeBlockContent(text) {
return text.replace('```', '`\u200b``');
}
/**
* Creates a Promise that resolves after a specified duration.
* @param {number} ms How long to wait before resolving (in milliseconds)