feat(markdown): support CJK and emoji (#3026)

* refactor: extract `splitText`

* fix: respcet CJK width in table

* test: add failing test

* fix: support emoji

* test: add failing test

* feat: support CJK character

* feat: enable adding whitespace between non-CJK/CJK-character by default

* fix: do not print softline at node that is sensitive to its identifier

* fix: treat full-width whitespace as CJK punctuation

* disallow leading/trailing full-width whitespace

* feat: remove `--split-cjk-text` option and enable it by default

* refactor: simplify regex and remove unnecessary `g` flag
master
Ika 2017-10-14 23:57:31 -05:00 committed by GitHub
parent c3b965145b
commit c27cc7ff45
19 changed files with 271 additions and 23 deletions

View File

@ -18,9 +18,11 @@
"babylon": "7.0.0-beta.23", "babylon": "7.0.0-beta.23",
"camelcase": "4.1.0", "camelcase": "4.1.0",
"chalk": "2.1.0", "chalk": "2.1.0",
"cjk-regex": "1.0.1",
"cosmiconfig": "3.1.0", "cosmiconfig": "3.1.0",
"dashify": "0.2.2", "dashify": "0.2.2",
"diff": "3.2.0", "diff": "3.2.0",
"emoji-regex": "6.5.1",
"escape-string-regexp": "1.0.5", "escape-string-regexp": "1.0.5",
"esutils": "2.0.2", "esutils": "2.0.2",
"flow-parser": "0.51.0", "flow-parser": "0.51.0",

View File

@ -1,7 +1,6 @@
"use strict"; "use strict";
const stringWidth = require("string-width"); const util = require("./util");
const docBuilders = require("./doc-builders"); const docBuilders = require("./doc-builders");
const concat = docBuilders.concat; const concat = docBuilders.concat;
const fill = docBuilders.fill; const fill = docBuilders.fill;
@ -68,7 +67,7 @@ function fits(next, restCommands, width, mustBeFlat) {
const doc = x[2]; const doc = x[2];
if (typeof doc === "string") { if (typeof doc === "string") {
width -= stringWidth(doc); width -= util.getStringWidth(doc);
} else { } else {
switch (doc.type) { switch (doc.type) {
case "concat": case "concat":
@ -155,7 +154,7 @@ function printDocToString(doc, options) {
if (typeof doc === "string") { if (typeof doc === "string") {
out.push(doc); out.push(doc);
pos += stringWidth(doc); pos += util.getStringWidth(doc);
} else { } else {
switch (doc.type) { switch (doc.type) {
case "cursor": case "cursor":

View File

@ -3,6 +3,7 @@
const remarkFrontmatter = require("remark-frontmatter"); const remarkFrontmatter = require("remark-frontmatter");
const remarkParse = require("remark-parse"); const remarkParse = require("remark-parse");
const unified = require("unified"); const unified = require("unified");
const util = require("./util");
/** /**
* based on [MDAST](https://github.com/syntax-tree/mdast) with following modifications: * based on [MDAST](https://github.com/syntax-tree/mdast) with following modifications:
@ -145,15 +146,7 @@ function splitText() {
return { return {
type: "sentence", type: "sentence",
position: node.position, position: node.position,
children: value children: util.splitText(value)
.split(/(\s+)/g)
.map(
(text, index) =>
index % 2 === 0
? { type: "word", value: text }
: { type: "whitespace", value: " " }
)
.filter(node => node.value !== "")
}; };
}); });
} }

View File

@ -6,6 +6,7 @@ const concat = docBuilders.concat;
const join = docBuilders.join; const join = docBuilders.join;
const line = docBuilders.line; const line = docBuilders.line;
const hardline = docBuilders.hardline; const hardline = docBuilders.hardline;
const softline = docBuilders.softline;
const fill = docBuilders.fill; const fill = docBuilders.fill;
const align = docBuilders.align; const align = docBuilders.align;
const docPrinter = require("./doc-printer"); const docPrinter = require("./doc-printer");
@ -42,11 +43,17 @@ function genericPrint(path, options, print) {
if (shouldRemainTheSameContent(path)) { if (shouldRemainTheSameContent(path)) {
return concat( return concat(
options.originalText util
.slice(node.position.start.offset, node.position.end.offset) .splitText(
.split(/(\s+)/g) options.originalText.slice(
.map((text, index) => (index % 2 === 0 ? text : line)) node.position.start.offset,
.filter(doc => doc !== "") node.position.end.offset
)
)
.map(
node =>
node.type === "word" ? node.value : node.value === "" ? "" : line
)
); );
} }
@ -68,7 +75,9 @@ function genericPrint(path, options, print) {
.replace(/(^|[^\\])\*/g, "$1\\*") // escape all unescaped `*` and `_` .replace(/(^|[^\\])\*/g, "$1\\*") // escape all unescaped `*` and `_`
.replace(/\b(^|[^\\])_\b/g, "$1\\_"); // `1_2_3` is not considered emphasis .replace(/\b(^|[^\\])_\b/g, "$1\\_"); // `1_2_3` is not considered emphasis
case "whitespace": case "whitespace":
return getAncestorNode(path, SINGLE_LINE_NODE_TYPES) ? " " : line; return getAncestorNode(path, SINGLE_LINE_NODE_TYPES)
? node.value === "" ? "" : " "
: node.value === "" ? softline : line;
case "emphasis": { case "emphasis": {
const parentNode = path.getParentNode(); const parentNode = path.getParentNode();
const index = parentNode.children.indexOf(node); const index = parentNode.children.indexOf(node);
@ -334,7 +343,7 @@ function printTable(path, options, print) {
const columnMaxWidths = contents.reduce( const columnMaxWidths = contents.reduce(
(currentWidths, rowContents) => (currentWidths, rowContents) =>
currentWidths.map((width, columnIndex) => currentWidths.map((width, columnIndex) =>
Math.max(width, rowContents[columnIndex].length) Math.max(width, util.getStringWidth(rowContents[columnIndex]))
), ),
contents[0].map(() => 3) // minimum width = 3 (---, :--, :-:, --:) contents[0].map(() => 3) // minimum width = 3 (---, :--, :-:, --:)
); );
@ -388,15 +397,15 @@ function printTable(path, options, print) {
} }
function alignLeft(text, width) { function alignLeft(text, width) {
return concat([text, " ".repeat(width - text.length)]); return concat([text, " ".repeat(width - util.getStringWidth(text))]);
} }
function alignRight(text, width) { function alignRight(text, width) {
return concat([" ".repeat(width - text.length), text]); return concat([" ".repeat(width - util.getStringWidth(text)), text]);
} }
function alignCenter(text, width) { function alignCenter(text, width) {
const spaces = width - text.length; const spaces = width - util.getStringWidth(text);
const left = Math.floor(spaces / 2); const left = Math.floor(spaces / 2);
const right = spaces - left; const right = spaces - left;
return concat([" ".repeat(left), text, " ".repeat(right)]); return concat([" ".repeat(left), text, " ".repeat(right)]);

View File

@ -1,7 +1,13 @@
"use strict"; "use strict";
const stringWidth = require("string-width");
const emojiRegex = require("emoji-regex")();
const escapeStringRegexp = require("escape-string-regexp"); const escapeStringRegexp = require("escape-string-regexp");
const getCjkRegex = require("cjk-regex");
const cjkRegex = getCjkRegex();
const cjkPunctuationRegex = getCjkRegex.punctuations();
function isExportDeclaration(node) { function isExportDeclaration(node) {
if (node) { if (node) {
switch (node.type) { switch (node.type) {
@ -636,7 +642,104 @@ function mapDoc(doc, callback) {
return callback(doc); return callback(doc);
} }
/**
* split text into whitespaces and words
* @param {string} text
* @return {Array<{ type: "whitespace", value: " " | "" } | { type: "word", value: string }>}
*/
function splitText(text) {
const KIND_NON_CJK = "non-cjk";
const KIND_CJK_CHARACTER = "cjk-character";
const KIND_CJK_PUNCTUATION = "cjk-punctuation";
const nodes = [];
text
.replace(
new RegExp(`(${cjkRegex.source})\n(${cjkRegex.source})`, "g"),
"$1$2"
)
// `\s` but exclude full-width whitspace (`\u3000`)
.split(/([^\S\u3000]+)/)
.forEach((token, index, tokens) => {
// whitespace
if (index % 2 === 1) {
nodes.push({ type: "whitespace", value: " " });
return;
}
// word separated by whitespace
if ((index === 0 || index === tokens.length - 1) && token === "") {
return;
}
token
.split(new RegExp(`(${cjkRegex.source})`))
.forEach((innerToken, innerIndex, innerTokens) => {
if (
(innerIndex === 0 || innerIndex === innerTokens.length - 1) &&
innerToken === ""
) {
return;
}
// non-CJK word
if (innerIndex % 2 === 0) {
if (innerToken !== "") {
appendNode({
type: "word",
value: innerToken,
kind: KIND_NON_CJK
});
}
return;
}
// CJK character
const kind = cjkPunctuationRegex.test(innerToken)
? KIND_CJK_PUNCTUATION
: KIND_CJK_CHARACTER;
appendNode({ type: "word", value: innerToken, kind });
});
});
return nodes;
function appendNode(node) {
const lastNode = nodes[nodes.length - 1];
if (lastNode && lastNode.type === "word") {
if (isBetween(KIND_NON_CJK, KIND_CJK_CHARACTER)) {
nodes.push({ type: "whitespace", value: " " });
} else if (
!isBetween(KIND_NON_CJK, KIND_CJK_PUNCTUATION) &&
// disallow leading/trailing full-width whitespace
![lastNode.value, node.value].some(value => /\u3000/.test(value))
) {
nodes.push({ type: "whitespace", value: "" });
}
}
nodes.push(node);
function isBetween(kind1, kind2) {
return (
(lastNode.kind === kind1 && node.kind === kind2) ||
(lastNode.kind === kind2 && node.kind === kind1)
);
}
}
}
function getStringWidth(text) {
// emojis are considered 2-char width for consistency
// see https://github.com/sindresorhus/string-width/issues/11
// for the reason why not implemented in `string-width`
return stringWidth(text.replace(emojiRegex, " "));
}
module.exports = { module.exports = {
getStringWidth,
splitText,
mapDoc, mapDoc,
getMaxContinuousCount, getMaxContinuousCount,
getPrecedence, getPrecedence,

View File

@ -1,5 +1,12 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`cjk.md 1`] = `
[這是一段很長很長很長很長很長很長很長很長很長很長很長很長很長很長很長很長很長很長很長的段落][]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
[這是一段很長很長很長很長很長很長很長很長很長很長很長很長很長很長很長很長很長很長很長的段落][]
`;
exports[`collapsed.md 1`] = ` exports[`collapsed.md 1`] = `
[hello][] [hello][]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -0,0 +1 @@
[這是一段很長很長很長很長很長很長很長很長很長很長很長很長很長很長很長很長很長很長很長的段落][]

View File

@ -1,5 +1,31 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`cjk.md 1`] = `
這是一段很長很長很長很長很長很長很長很長很長很長很長很長很長很長很長很長很長很長很長的段落
這是一個English混合著中文的一段Paragraph這是一個English混合著中文的一段Paragraph這是一個English混合著中文的一段Paragraph這是一個English混合著中文的一段Paragraph這是一個English混合著中文的一段Paragraph這是一個English混合著中文的一段Paragraph這是一個English混合著中文的一段Paragraph這是一個English混合著中文的一段Paragraph這是一個English混合著中文的一段Paragraph這是一個English混合著中文的一段Paragraph這是一個English混合著中文的一段Paragraph這是一個English混合著中文的一段Paragraph這是一個English混合著中文的一段Paragraph這是一個English混合著中文的一段Paragraph這是一個English混合著中文的一段Paragraph這是一個English混合著中文的一段Paragraph
全  形 空白全  形 空白全  形 空白全  形 空白全  形 空白全  形 空白全  形 空白全  形 空白
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
這是一段很長很長很長很長很長很長很長很長很長很長很長很長很長很長很長很長很長很長
很長的段落
這是一個 English 混合著中文的一段 Paragraph這是一個 English 混合著中文的一段
Paragraph這是一個 English 混合著中文的一段 Paragraph這是一個 English 混合著
中文的一段 Paragraph這是一個 English 混合著中文的一段 Paragraph這是一個
English 混合著中文的一段 Paragraph這是一個 English 混合著中文的一段
Paragraph這是一個 English 混合著中文的一段 Paragraph這是一個 English 混合著
中文的一段 Paragraph這是一個 English 混合著中文的一段 Paragraph這是一個
English 混合著中文的一段 Paragraph這是一個 English 混合著中文的一段
Paragraph這是一個 English 混合著中文的一段 Paragraph這是一個 English 混合著
中文的一段 Paragraph這是一個 English 混合著中文的一段 Paragraph這是一個
English 混合著中文的一段 Paragraph
全  形 空白全  形 空白全  形 空白全  形 空白全  形 空白
全  形 空白全  形 空白全  形 空白
`;
exports[`inline-nodes.md 1`] = ` exports[`inline-nodes.md 1`] = `
It removes all original styling[*](#styling-footnote) and ensures that all outputted code conforms to a consistent style. (See this [blog post](http://jlongster.com/A-Prettier-Formatter)) It removes all original styling[*](#styling-footnote) and ensures that all outputted code conforms to a consistent style. (See this [blog post](http://jlongster.com/A-Prettier-Formatter))
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -0,0 +1,5 @@
這是一段很長很長很長很長很長很長很長很長很長很長很長很長很長很長很長很長很長很長很長的段落
這是一個English混合著中文的一段Paragraph這是一個English混合著中文的一段Paragraph這是一個English混合著中文的一段Paragraph這是一個English混合著中文的一段Paragraph這是一個English混合著中文的一段Paragraph這是一個English混合著中文的一段Paragraph這是一個English混合著中文的一段Paragraph這是一個English混合著中文的一段Paragraph這是一個English混合著中文的一段Paragraph這是一個English混合著中文的一段Paragraph這是一個English混合著中文的一段Paragraph這是一個English混合著中文的一段Paragraph這是一個English混合著中文的一段Paragraph這是一個English混合著中文的一段Paragraph這是一個English混合著中文的一段Paragraph這是一個English混合著中文的一段Paragraph
全  形 空白全  形 空白全  形 空白全  形 空白全  形 空白全  形 空白全  形 空白全  形 空白

View File

@ -0,0 +1,58 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`cjk.md 1`] = `
這是一段很長很長很長很長很長很長很長很長很長很長很長很長很長很長很長很長很長很長很長的段落
全  形 空白全  形 空白全  形 空白全  形 空白全  形 空白全  形 空白全  形 空白
空白全形空白全形空白全形空白 空白全形空白全形空白全形空白 空白全形空白全形空白全形空白 空白全形空白全形空白全形空白
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
這是一段很長很長很長很長很長很長很長很長很長很長很長很長很長很長很長很長很長很長
很長的段落
全  形 空白全  形 空白全  形 空白全  形 空白全  形 空白
全  形 空白全  形 空白
空白全形空白全形空白全形空白 空白全形空白全形空白全形空白 空白全形空白全形空白
全形空白 空白全形空白全形空白全形空白
`;
exports[`link.md 1`] = `
[這是一段很長很長很長很長很長很長很長很長很長很長很長很長很長很長很長很長很長很長很長的段落][]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
[這是一段很長很長很長很長很長很長很長很長很長很長很長很長很長很長很長很長很長很長很長的段落][]
`;
exports[`mixed.md 1`] = `
這是一個English混合著中文的一段Paragraph這是一個English混合著中文的一段Paragraph這是一個English混合著中文的一段Paragraph這是一個English混合著中文的一段Paragraph這是一個English混合著中文的一段Paragraph這是一個English混合著中文的一段Paragraph這是一個English混合著中文的一段Paragraph這是一個English混合著中文的一段Paragraph這是一個English混合著中文的一段Paragraph這是一個English混合著中文的一段Paragraph這是一個English混合著中文的一段Paragraph這是一個English混合著中文的一段Paragraph這是一個English混合著中文的一段Paragraph這是一個English混合著中文的一段Paragraph這是一個English混合著中文的一段Paragraph這是一個English混合著中文的一段Paragraph
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
這是一個 English 混合著中文的一段 Paragraph這是一個 English 混合著中文的一段
Paragraph這是一個 English 混合著中文的一段 Paragraph這是一個 English 混合著
中文的一段 Paragraph這是一個 English 混合著中文的一段 Paragraph這是一個
English 混合著中文的一段 Paragraph這是一個 English 混合著中文的一段
Paragraph這是一個 English 混合著中文的一段 Paragraph這是一個 English 混合著
中文的一段 Paragraph這是一個 English 混合著中文的一段 Paragraph這是一個
English 混合著中文的一段 Paragraph這是一個 English 混合著中文的一段
Paragraph這是一個 English 混合著中文的一段 Paragraph這是一個 English 混合著
中文的一段 Paragraph這是一個 English 混合著中文的一段 Paragraph這是一個
English 混合著中文的一段 Paragraph
`;
exports[`space.md 1`] = `
這是一個 English 混合著中文的一段 Paragraph這是一個 English 混合著中文的一段 Paragraph這是一個 English 混合著中文的一段 Paragraph這是一個 English 混合著中文的一段 Paragraph這是一個 English 混合著中文的一段 Paragraph這是一個 English 混合著中文的一段 Paragraph這是一個 English 混合著中文的一段 Paragraph這是一個 English 混合著中文的一段 Paragraph這是一個 English 混合著中文的一段 Paragraph這是一個 English 混合著中文的一段 Paragraph這是一個 English 混合著中文的一段 Paragraph這是一個 English 混合著中文的一段 Paragraph這是一個 English 混合著中文的一段 Paragraph這是一個 English 混合著中文的一段 Paragraph這是一個 English 混合著中文的一段 Paragraph這是一個 English 混合著中文的一段 Paragraph
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
這是一個 English 混合著中文的一段 Paragraph這是一個 English 混合著中文的一段
Paragraph這是一個 English 混合著中文的一段 Paragraph這是一個 English 混合著
中文的一段 Paragraph這是一個 English 混合著中文的一段 Paragraph這是一個
English 混合著中文的一段 Paragraph這是一個 English 混合著中文的一段
Paragraph這是一個 English 混合著中文的一段 Paragraph這是一個 English 混合著
中文的一段 Paragraph這是一個 English 混合著中文的一段 Paragraph這是一個
English 混合著中文的一段 Paragraph這是一個 English 混合著中文的一段
Paragraph這是一個 English 混合著中文的一段 Paragraph這是一個 English 混合著
中文的一段 Paragraph這是一個 English 混合著中文的一段 Paragraph這是一個
English 混合著中文的一段 Paragraph
`;

View File

@ -0,0 +1,5 @@
這是一段很長很長很長很長很長很長很長很長很長很長很長很長很長很長很長很長很長很長很長的段落
全  形 空白全  形 空白全  形 空白全  形 空白全  形 空白全  形 空白全  形 空白
空白全形空白全形空白全形空白 空白全形空白全形空白全形空白 空白全形空白全形空白全形空白 空白全形空白全形空白全形空白

View File

@ -0,0 +1 @@
run_spec(__dirname, { parser: "markdown" });

View File

@ -0,0 +1 @@
[這是一段很長很長很長很長很長很長很長很長很長很長很長很長很長很長很長很長很長很長很長的段落][]

View File

@ -0,0 +1 @@
這是一個English混合著中文的一段Paragraph這是一個English混合著中文的一段Paragraph這是一個English混合著中文的一段Paragraph這是一個English混合著中文的一段Paragraph這是一個English混合著中文的一段Paragraph這是一個English混合著中文的一段Paragraph這是一個English混合著中文的一段Paragraph這是一個English混合著中文的一段Paragraph這是一個English混合著中文的一段Paragraph這是一個English混合著中文的一段Paragraph這是一個English混合著中文的一段Paragraph這是一個English混合著中文的一段Paragraph這是一個English混合著中文的一段Paragraph這是一個English混合著中文的一段Paragraph這是一個English混合著中文的一段Paragraph這是一個English混合著中文的一段Paragraph

View File

@ -0,0 +1 @@
這是一個 English 混合著中文的一段 Paragraph這是一個 English 混合著中文的一段 Paragraph這是一個 English 混合著中文的一段 Paragraph這是一個 English 混合著中文的一段 Paragraph這是一個 English 混合著中文的一段 Paragraph這是一個 English 混合著中文的一段 Paragraph這是一個 English 混合著中文的一段 Paragraph這是一個 English 混合著中文的一段 Paragraph這是一個 English 混合著中文的一段 Paragraph這是一個 English 混合著中文的一段 Paragraph這是一個 English 混合著中文的一段 Paragraph這是一個 English 混合著中文的一段 Paragraph這是一個 English 混合著中文的一段 Paragraph這是一個 English 混合著中文的一段 Paragraph這是一個 English 混合著中文的一段 Paragraph這是一個 English 混合著中文的一段 Paragraph

View File

@ -11,6 +11,28 @@ exports[`align.md 1`] = `
`; `;
exports[`cjk.md 1`] = `
| abc | def | ghi |
| --- | --- | --- |
| 第一欄 | 第二欄 | 第三欄 |
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
| abc | def | ghi |
| ------ | ------ | ------ |
| 第一欄 | 第二欄 | 第三欄 |
`;
exports[`emoji.md 1`] = `
| abc | def | ghi |
| --- | --- | --- |
| 👍👍👍 | 👍👍👍 | 👍👍👍 |
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
| abc | def | ghi |
| ------ | ------ | ------ |
| 👍👍👍 | 👍👍👍 | 👍👍👍 |
`;
exports[`escape.md 1`] = ` exports[`escape.md 1`] = `
| a | b | c | | a | b | c |
|:--|:-:|--:| |:--|:-:|--:|

View File

@ -0,0 +1,3 @@
| abc | def | ghi |
| --- | --- | --- |
| 第一欄 | 第二欄 | 第三欄 |

View File

@ -0,0 +1,3 @@
| abc | def | ghi |
| --- | --- | --- |
| 👍👍👍 | 👍👍👍 | 👍👍👍 |

View File

@ -1060,6 +1060,10 @@ circular-json@^0.3.1:
version "0.3.1" version "0.3.1"
resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.3.1.tgz#be8b36aefccde8b3ca7aa2d6afc07a37242c0d2d" resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.3.1.tgz#be8b36aefccde8b3ca7aa2d6afc07a37242c0d2d"
cjk-regex@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/cjk-regex/-/cjk-regex-1.0.1.tgz#9bee3087e5e4e943549ace766b5d95a9b700dd41"
cli-cursor@^2.1.0: cli-cursor@^2.1.0:
version "2.1.0" version "2.1.0"
resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5" resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5"
@ -1395,6 +1399,10 @@ elliptic@^6.0.0:
minimalistic-assert "^1.0.0" minimalistic-assert "^1.0.0"
minimalistic-crypto-utils "^1.0.0" minimalistic-crypto-utils "^1.0.0"
emoji-regex@6.5.1:
version "6.5.1"
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-6.5.1.tgz#9baea929b155565c11ea41c6626eaa65cef992c2"
emojis-list@^2.0.0: emojis-list@^2.0.0:
version "2.1.0" version "2.1.0"
resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-2.1.0.tgz#4daa4d9db00f9819880c79fa457ae5b09a1fd389" resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-2.1.0.tgz#4daa4d9db00f9819880c79fa457ae5b09a1fd389"