diff --git a/package.json b/package.json
index 5866a2a1..2fb94550 100644
--- a/package.json
+++ b/package.json
@@ -55,6 +55,7 @@
"react-tabs": "^3.1.0",
"react-virtualized-auto-sizer": "^1.0.2",
"react-window": "~1.8.5",
+ "refractor": "^3.0.0",
"yup": "^0.28.0"
},
"devDependencies": {
@@ -86,6 +87,7 @@
"@types/react-tabs": "^2.3.1",
"@types/react-virtualized-auto-sizer": "^1.0.0",
"@types/react-window": "^1.8.1",
+ "@types/refractor": "^2.8.0",
"@types/yup": "~0.26.27",
"autoprefixer": "^9.7.3",
"babel-loader": "^8.0.6",
diff --git a/src/CodeViewer/__tests__/Viewer.spec.tsx b/src/CodeViewer/__tests__/Viewer.spec.tsx
index 16fbc223..02ead282 100644
--- a/src/CodeViewer/__tests__/Viewer.spec.tsx
+++ b/src/CodeViewer/__tests__/Viewer.spec.tsx
@@ -1,35 +1,77 @@
-import { mount } from 'enzyme';
+import { shallow } from 'enzyme';
import 'jest-enzyme';
import * as React from 'react';
-import { CodeViewer } from '..';
+import { CodeViewer } from '../index';
+import { astToReact } from '../utils/astToReact';
+import { parseCode } from '../utils/parseCode';
+
+jest.mock('../utils/astToReact');
+jest.mock('../utils/parseCode');
describe('Code Viewer component', () => {
+ afterEach(() => {
+ (parseCode as jest.Mock).mockReset();
+ (astToReact as jest.Mock).mockReset();
+ });
+
it('renders code element with raw value for inline view', () => {
const code = '{}';
const language = 'json';
- const wrapper = mount({}
`,
- );
+ const wrapper = shallow(
{}`, - ); + const wrapper = shallow(
function
`;
+ const ast = [
+ {
+ type: 'element',
+ tagName: 'span',
+ properties: {
+ className: ['token', 'function'],
+ },
+ value: 'function',
+ },
+ ];
+ const markup = function;
+
+ (parseCode as jest.Mock).mockReturnValue(ast);
+ (astToReact as jest.Mock).mockReturnValue(() => markup);
- const wrapper = mount(
+ >
+ {value}
+
);
}
+ const markup = parseCode(value, lang, showLineNumbers);
+
return (
)} - dangerouslySetInnerHTML={{ __html: code }} - /> + {...rest} + > + {markup ? markup.map(astToReact()) : value} +); }; diff --git a/src/CodeViewer/types.ts b/src/CodeViewer/types.ts deleted file mode 100644 index e98380ac..00000000 --- a/src/CodeViewer/types.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { ReactHTML } from 'react'; -import { Dictionary } from '../types'; - -export type ASTNode = Partial<{ - type: string; - tagName: keyof ReactHTML; - children?: ASTNode[]; - properties: Dictionary
+ int + + main + + + ( + + + ) + + + + { + + + + + // some dumb code + + + + + return + + + + 0 + + + ; + + + + + } + ++`; + +exports[`astToReact util fixture javascript 1`] = ` +
+ + const + + defaultValue + + = + + stoplight + + . + + + io + + + ( + + + ) + + + ; + ++`; diff --git a/src/CodeViewer/utils/__tests__/__snapshots__/lineNumberify.spec.ts.snap b/src/CodeViewer/utils/__tests__/__snapshots__/lineNumberify.spec.ts.snap new file mode 100644 index 00000000..11086740 --- /dev/null +++ b/src/CodeViewer/utils/__tests__/__snapshots__/lineNumberify.spec.ts.snap @@ -0,0 +1,161 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`lineNumberify util handles multiple lines 1`] = ` +Array [ + Object { + "children": Array [ + Object { + "children": Array [ + Object { + "type": "text", + "value": "fn", + }, + ], + "properties": Object { + "className": Array [ + "token", + "function", + ], + }, + "tagName": "span", + "type": "element", + }, + Object { + "children": Array [ + Object { + "type": "text", + "value": "(", + }, + ], + "properties": Object { + "className": Array [ + "token", + "punctuation", + ], + }, + "tagName": "span", + "type": "element", + }, + Object { + "children": Array [ + Object { + "type": "text", + "value": ")", + }, + ], + "properties": Object { + "className": Array [ + "token", + "punctuation", + ], + }, + "tagName": "span", + "type": "element", + }, + Object { + "children": Array [ + Object { + "type": "text", + "value": ";", + }, + ], + "properties": Object { + "className": Array [ + "token", + "punctuation", + ], + }, + "tagName": "span", + "type": "element", + }, + ], + "properties": Object { + "className": Array [ + "line-number", + ], + }, + "tagName": "span", + "type": "element", + }, +] +`; + +exports[`lineNumberify util handles one-liners 1`] = ` +Array [ + Object { + "children": Array [ + Object { + "children": Array [ + Object { + "type": "text", + "value": "fn", + }, + ], + "properties": Object { + "className": Array [ + "token", + "function", + ], + }, + "tagName": "span", + "type": "element", + }, + Object { + "children": Array [ + Object { + "type": "text", + "value": "(", + }, + ], + "properties": Object { + "className": Array [ + "token", + "punctuation", + ], + }, + "tagName": "span", + "type": "element", + }, + Object { + "children": Array [ + Object { + "type": "text", + "value": ")", + }, + ], + "properties": Object { + "className": Array [ + "token", + "punctuation", + ], + }, + "tagName": "span", + "type": "element", + }, + Object { + "children": Array [ + Object { + "type": "text", + "value": ";", + }, + ], + "properties": Object { + "className": Array [ + "token", + "punctuation", + ], + }, + "tagName": "span", + "type": "element", + }, + ], + "properties": Object { + "className": Array [ + "line-number", + ], + }, + "tagName": "span", + "type": "element", + }, +] +`; diff --git a/src/CodeViewer/utils/__tests__/astToReact.spec.ts b/src/CodeViewer/utils/__tests__/astToReact.spec.ts new file mode 100644 index 00000000..1f1de151 --- /dev/null +++ b/src/CodeViewer/utils/__tests__/astToReact.spec.ts @@ -0,0 +1,12 @@ +import { shallow } from 'enzyme'; +import * as path from 'path'; +import { createElement } from 'react'; +import { astToReact } from '../astToReact'; + +describe('astToReact util', () => { + it.each(['clike', 'javascript'])('fixture %s', fixture => { + const ast = require(path.join(__dirname, '/fixtures/', `${fixture}.json`)); + const markup = ast.map(astToReact()); + expect(shallow(createElement('pre', null, markup))).toMatchSnapshot(); + }); +}); diff --git a/src/CodeViewer/utils/__tests__/fixtures/clike.json b/src/CodeViewer/utils/__tests__/fixtures/clike.json new file mode 100644 index 00000000..9bb549d3 --- /dev/null +++ b/src/CodeViewer/utils/__tests__/fixtures/clike.json @@ -0,0 +1,170 @@ +[ + { + "type": "text", + "value": "int " + }, + { + "type": "element", + "tagName": "span", + "properties": { + "className": [ + "token", + "function" + ] + }, + "children": [ + { + "type": "text", + "value": "main" + } + ] + }, + { + "type": "element", + "tagName": "span", + "properties": { + "className": [ + "token", + "punctuation" + ] + }, + "children": [ + { + "type": "text", + "value": "(" + } + ] + }, + { + "type": "element", + "tagName": "span", + "properties": { + "className": [ + "token", + "punctuation" + ] + }, + "children": [ + { + "type": "text", + "value": ")" + } + ] + }, + { + "type": "text", + "value": " " + }, + { + "type": "element", + "tagName": "span", + "properties": { + "className": [ + "token", + "punctuation" + ] + }, + "children": [ + { + "type": "text", + "value": "{" + } + ] + }, + { + "type": "text", + "value": "\n " + }, + { + "type": "element", + "tagName": "span", + "properties": { + "className": [ + "token", + "comment" + ] + }, + "children": [ + { + "type": "text", + "value": "// some dumb code" + } + ] + }, + { + "type": "text", + "value": "\n " + }, + { + "type": "element", + "tagName": "span", + "properties": { + "className": [ + "token", + "keyword" + ] + }, + "children": [ + { + "type": "text", + "value": "return" + } + ] + }, + { + "type": "text", + "value": " " + }, + { + "type": "element", + "tagName": "span", + "properties": { + "className": [ + "token", + "number" + ] + }, + "children": [ + { + "type": "text", + "value": "0" + } + ] + }, + { + "type": "element", + "tagName": "span", + "properties": { + "className": [ + "token", + "punctuation" + ] + }, + "children": [ + { + "type": "text", + "value": ";" + } + ] + }, + { + "type": "text", + "value": "\n" + }, + { + "type": "element", + "tagName": "span", + "properties": { + "className": [ + "token", + "punctuation" + ] + }, + "children": [ + { + "type": "text", + "value": "}" + } + ] + } +] diff --git a/src/CodeViewer/utils/__tests__/fixtures/javascript.json b/src/CodeViewer/utils/__tests__/fixtures/javascript.json new file mode 100644 index 00000000..0fba1485 --- /dev/null +++ b/src/CodeViewer/utils/__tests__/fixtures/javascript.json @@ -0,0 +1,122 @@ +[ + { + "type": "element", + "tagName": "span", + "properties": { + "className": [ + "token", + "keyword" + ] + }, + "children": [ + { + "type": "text", + "value": "const" + } + ] + }, + { + "type": "text", + "value": " defaultValue " + }, + { + "type": "element", + "tagName": "span", + "properties": { + "className": [ + "token", + "operator" + ] + }, + "children": [ + { + "type": "text", + "value": "=" + } + ] + }, + { + "type": "text", + "value": " stoplight" + }, + { + "type": "element", + "tagName": "span", + "properties": { + "className": [ + "token", + "punctuation" + ] + }, + "children": [ + { + "type": "text", + "value": "." + } + ] + }, + { + "type": "element", + "tagName": "span", + "properties": { + "className": [ + "token", + "function" + ] + }, + "children": [ + { + "type": "text", + "value": "io" + } + ] + }, + { + "type": "element", + "tagName": "span", + "properties": { + "className": [ + "token", + "punctuation" + ] + }, + "children": [ + { + "type": "text", + "value": "(" + } + ] + }, + { + "type": "element", + "tagName": "span", + "properties": { + "className": [ + "token", + "punctuation" + ] + }, + "children": [ + { + "type": "text", + "value": ")" + } + ] + }, + { + "type": "element", + "tagName": "span", + "properties": { + "className": [ + "token", + "punctuation" + ] + }, + "children": [ + { + "type": "text", + "value": ";" + } + ] + } +] diff --git a/src/CodeViewer/utils/__tests__/fixtures/multiple-lines.json b/src/CodeViewer/utils/__tests__/fixtures/multiple-lines.json new file mode 100644 index 00000000..490c148f --- /dev/null +++ b/src/CodeViewer/utils/__tests__/fixtures/multiple-lines.json @@ -0,0 +1,54 @@ +[ + { + "type": "text", + "value": "fn\n\n\n\nabc\n" + }, + { + "type": "element", + "tagName": "span", + "properties": { + "className": [ + "token", + "function" + ] + }, + "children": [ + { + "type": "text", + "value": "t" + } + ] + }, + { + "type": "element", + "tagName": "span", + "properties": { + "className": [ + "token", + "punctuation" + ] + }, + "children": [ + { + "type": "text", + "value": "(" + } + ] + }, + { + "type": "element", + "tagName": "span", + "properties": { + "className": [ + "token", + "punctuation" + ] + }, + "children": [ + { + "type": "text", + "value": ")" + } + ] + } +] diff --git a/src/CodeViewer/utils/__tests__/fixtures/one-liner.json b/src/CodeViewer/utils/__tests__/fixtures/one-liner.json new file mode 100644 index 00000000..e0239919 --- /dev/null +++ b/src/CodeViewer/utils/__tests__/fixtures/one-liner.json @@ -0,0 +1,66 @@ +[ + { + "type": "element", + "tagName": "span", + "properties": { + "className": [ + "token", + "function" + ] + }, + "children": [ + { + "type": "text", + "value": "fn" + } + ] + }, + { + "type": "element", + "tagName": "span", + "properties": { + "className": [ + "token", + "punctuation" + ] + }, + "children": [ + { + "type": "text", + "value": "(" + } + ] + }, + { + "type": "element", + "tagName": "span", + "properties": { + "className": [ + "token", + "punctuation" + ] + }, + "children": [ + { + "type": "text", + "value": ")" + } + ] + }, + { + "type": "element", + "tagName": "span", + "properties": { + "className": [ + "token", + "punctuation" + ] + }, + "children": [ + { + "type": "text", + "value": ";" + } + ] + } +] diff --git a/src/CodeViewer/utils/__tests__/lineNumberify.spec.ts b/src/CodeViewer/utils/__tests__/lineNumberify.spec.ts new file mode 100644 index 00000000..fb053e75 --- /dev/null +++ b/src/CodeViewer/utils/__tests__/lineNumberify.spec.ts @@ -0,0 +1,14 @@ +import * as path from 'path'; +import { lineNumberify } from '../lineNumberify'; + +describe('lineNumberify util', () => { + it('handles one-liners', () => { + const ast = require(path.join(__dirname, '/fixtures/one-liner.json')); + expect(lineNumberify(JSON.parse(JSON.stringify(ast)))).toMatchSnapshot(); + }); + + it('handles multiple lines', () => { + const ast = require(path.join(__dirname, '/fixtures/one-liner.json')); + expect(lineNumberify(JSON.parse(JSON.stringify(ast)))).toMatchSnapshot(); + }); +}); diff --git a/src/CodeViewer/utils/__tests__/parseCode.spec.ts b/src/CodeViewer/utils/__tests__/parseCode.spec.ts new file mode 100644 index 00000000..92ec29b8 --- /dev/null +++ b/src/CodeViewer/utils/__tests__/parseCode.spec.ts @@ -0,0 +1,87 @@ +import * as refractor from 'refractor/core'; + +import { parseCode } from '../parseCode'; + +jest.mock('refractor/core'); + +describe('parseCode util', () => { + afterEach(() => { + (refractor.highlight as jest.Mock).mockReset(); + }); + + it('produces basic AST of if language is falsy', () => { + expect(parseCode('foo\nbar')).toEqual([ + { + children: [ + { + type: 'text', + value: 'foo\n', + }, + ], + properties: {}, + tagName: 'span', + type: 'element', + }, + { + children: [ + { + type: 'text', + value: 'bar', + }, + ], + properties: {}, + tagName: 'span', + type: 'element', + }, + ]); + }); + + it('produces basic AST even when both code and language are falsy', () => { + expect(parseCode('')).toEqual([ + { + children: [ + { + type: 'text', + value: '', + }, + ], + properties: {}, + tagName: 'span', + type: 'element', + }, + ]); + }); + + it('calls refractor.highlight and returns its result', () => { + const code = 'foo()'; + const language = 'javscript'; + const result = { type: 'element', tagName: 'span', children: [], properties: {} }; + (refractor.highlight as jest.Mock).mockReturnValue(result); + + expect(parseCode(code, language)).toBe(result); + expect(refractor.highlight).toHaveBeenCalledWith(code, language); + }); + + it('fall backs to plain text parsing if refractor highlighting fails', () => { + const code = 'foo()'; + const language = 'javscript'; + (refractor.highlight as jest.Mock).mockImplementation(() => { + throw new Error(); + }); + + expect(parseCode(code, language)).toEqual([ + { + children: [ + { + type: 'text', + value: code, + }, + ], + properties: {}, + tagName: 'span', + type: 'element', + }, + ]); + expect(refractor.highlight).toHaveBeenCalledWith(code, language); + }); +}); diff --git a/src/CodeViewer/utils/astToReact.ts b/src/CodeViewer/utils/astToReact.ts new file mode 100644 index 00000000..af1045e0 --- /dev/null +++ b/src/CodeViewer/utils/astToReact.ts @@ -0,0 +1,25 @@ +import { createElement, ReactNode } from 'react'; +import { RefractorNode } from 'refractor/core'; + +// based on https://github.com/rexxars/react-lowlight/blob/master/src/mapChildren.js +function mapChild(child: RefractorNode, i: number, depth: number): ReactNode { + if ('tagName' in child) { + return createElement( + child.tagName, + { + key: `cv-${depth}-${i}`, + ...child.properties, + className: child.properties && (child.properties.className || []).join(' '), + }, + child.children && child.children.map(astToReact(depth + 1)), + ); + } + + return child.value; +} + +export function astToReact(depth: number = 0) { + return function mapChildrenWithDepth(child: RefractorNode, i: number) { + return mapChild(child, i, depth); + }; +} diff --git a/src/CodeViewer/utils/lineNumberify.ts b/src/CodeViewer/utils/lineNumberify.ts new file mode 100644 index 00000000..5edcb989 --- /dev/null +++ b/src/CodeViewer/utils/lineNumberify.ts @@ -0,0 +1,119 @@ +// based on https://github.com/FormidableLabs/prism-react-renderer +import { AST, RefractorNode } from 'refractor/core'; + +const newLineRegex = /\n/g; +function getNewLines(str: string) { + return str.match(newLineRegex); +} + +function createLineElement({ + children, + lineNumber, + className, +}: { + children: RefractorNode[]; + lineNumber?: number; + className?: string[]; +}): AST.Element { + return { + type: 'element', + tagName: 'span', + properties: { + className: lineNumber === void 0 ? className : ['line-number'], + }, + children, + }; +} + +function flattenCodeTree(tree: RefractorNode[], className: string[] = []): AST.Element[] { + const newTree: AST.Element[] = []; + for (const node of tree) { + if (node.type === 'text') { + newTree.push( + createLineElement({ + children: [node], + className, + }), + ); + } else if (node.children && node.properties.className !== void 0) { + const classNames = className.concat(node.properties.className); + newTree.push(...flattenCodeTree(node.children, classNames)); + } + } + + return newTree; +} + +export function lineNumberify(codeTree: RefractorNode[]): RefractorNode[] { + const tree = flattenCodeTree(codeTree); + const newTree = []; + let lastLineBreakIndex = -1; + let index = 0; + + while (index < tree.length) { + const node = tree[index]; + const value = (node.children![0] as AST.Text).value!; + const newLines = getNewLines(value); + + if (newLines) { + const splitValue = value.split('\n'); + splitValue.forEach((text, i) => { + const lineNumber = newTree.length + 1; + const newChild: AST.Text = { type: 'text', value: `${text}\n` }; + + if (i === 0) { + const children = tree.slice(lastLineBreakIndex + 1, index).concat( + createLineElement({ + children: [newChild], + className: node.properties!.className, + }), + ); + newTree.push(createLineElement({ children, lineNumber })); + } else if (i === splitValue.length - 1) { + const stringChild = tree[index + 1] && tree[index + 1].children && tree[index + 1].children![0]; + if (stringChild) { + const lastLineInPreviousSpan: AST.Text = { type: 'text', value: `${text}` }; + const newElem = createLineElement({ + children: [lastLineInPreviousSpan], + className: node.properties!.className, + }); + tree.splice(index + 1, 0, newElem); + } else { + newTree.push( + createLineElement({ + children: [newChild], + lineNumber, + className: node.properties!.className, + }), + ); + } + } else { + newTree.push( + createLineElement({ + children: [newChild], + lineNumber, + className: node.properties!.className, + }), + ); + } + }); + + lastLineBreakIndex = index; + } + index++; + } + + if (lastLineBreakIndex !== tree.length - 1) { + const children = tree.slice(lastLineBreakIndex + 1, tree.length); + if (children && children.length) { + newTree.push( + createLineElement({ + children, + lineNumber: newTree.length + 1, + }), + ); + } + } + + return newTree; +} diff --git a/src/CodeViewer/utils/parseCode.ts b/src/CodeViewer/utils/parseCode.ts new file mode 100644 index 00000000..5ab76f85 --- /dev/null +++ b/src/CodeViewer/utils/parseCode.ts @@ -0,0 +1,68 @@ +import * as refractor from 'refractor/core'; +import { lineNumberify } from './lineNumberify'; + +refractor.register(require('refractor/lang/bash')); +refractor.register(require('refractor/lang/c')); +refractor.register(require('refractor/lang/csharp')); +refractor.register(require('refractor/lang/diff')); +refractor.register(require('refractor/lang/git')); +refractor.register(require('refractor/lang/go')); +refractor.register(require('refractor/lang/graphql')); +refractor.register(require('refractor/lang/http')); +refractor.register(require('refractor/lang/java')); +refractor.register(require('refractor/lang/javascript')); +refractor.register(require('refractor/lang/json')); +refractor.register(require('refractor/lang/jsx')); +refractor.register(require('refractor/lang/markdown')); +refractor.register(require('refractor/lang/markup')); +refractor.register(require('refractor/lang/markup-templating')); +refractor.register(require('refractor/lang/objectivec')); +refractor.register(require('refractor/lang/ocaml')); +refractor.register(require('refractor/lang/php')); +refractor.register(require('refractor/lang/powershell')); +refractor.register(require('refractor/lang/protobuf')); +refractor.register(require('refractor/lang/python')); +refractor.register(require('refractor/lang/ruby')); +refractor.register(require('refractor/lang/sql')); +refractor.register(require('refractor/lang/swift')); +refractor.register(require('refractor/lang/typescript')); +refractor.register(require('refractor/lang/yaml')); + +function parsePlainText(code: string): refractor.RefractorNode[] { + return code.split('\n').map((value, i, arr) => ({ + type: 'element', + tagName: 'span', + properties: {}, + children: [ + { + type: 'text', + value: arr.length - 1 === i ? value : `${value}\n`, + }, + ], + })); +} + +function safeParse(code: string, language?: string): refractor.RefractorNode[] { + if (language) { + try { + return refractor.highlight(code, language); + } catch (ex) { + // let's fallback to plain text + } + } + + return parsePlainText(code); +} + +export function parseCode(code: string, language?: string, addLineNumbers?: boolean) { + try { + const ast = safeParse(code, language); + if (addLineNumbers) { + return lineNumberify(ast); + } + + return ast; + } catch (ex) { + return null; + } +} diff --git a/yarn.lock b/yarn.lock index ddea1421..080de33d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2494,6 +2494,11 @@ resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-1.19.0.tgz#a2502fb7ce9b6626fdbfc2e2a496f472de1bdd05" integrity sha512-gDE8JJEygpay7IjA/u3JiIURvwZW08f0cZSZLAzFoX/ZmeqvS0Sqv+97aKuHpNsalAMMhwPe+iAS6fQbfmbt7A== +"@types/prismjs@*": + version "1.16.1" + resolved "https://registry.yarnpkg.com/@types/prismjs/-/prismjs-1.16.1.tgz#50b82947207847db6abcbcd14caa89e3b897c259" + integrity sha512-RNgcK3FEc1GpeOkamGDq42EYkb6yZW5OWQwTS56NJIB8WL0QGISQglA7En7NUx9RGP8AC52DOe+squqbAckXlA== + "@types/prismjs@^1.16.0": version "1.16.0" resolved "https://registry.yarnpkg.com/@types/prismjs/-/prismjs-1.16.0.tgz#4328c9f65698e59f4feade8f4e5d928c748fd643" @@ -2588,6 +2593,13 @@ "@types/prop-types" "*" csstype "^2.2.0" +"@types/refractor@^2.8.0": + version "2.8.0" + resolved "https://registry.yarnpkg.com/@types/refractor/-/refractor-2.8.0.tgz#2e17b69f27e89c1ea076f49b599abe3567c54e01" + integrity sha512-l3wSB96RFZnvB8bnbF8UmYsDD1MQl+u7jtYq+DgI/vo3RD5pdbK3OitGEvMO3DNJhTYmCEhXLVWyyWTddzwNzQ== + dependencies: + "@types/prismjs" "*" + "@types/retry@^0.12.0": version "0.12.0" resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.0.tgz#2b35eccfcee7d38cd72ad99232fbd58bffb3c84d" @@ -11717,6 +11729,18 @@ parse-entities@^1.1.2: is-decimal "^1.0.0" is-hexadecimal "^1.0.0" +parse-entities@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/parse-entities/-/parse-entities-2.0.0.tgz#53c6eb5b9314a1f4ec99fa0fdf7ce01ecda0cbe8" + integrity sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ== + dependencies: + character-entities "^1.0.0" + character-entities-legacy "^1.0.0" + character-reference-invalid "^1.0.0" + is-alphanumerical "^1.0.0" + is-decimal "^1.0.0" + is-hexadecimal "^1.0.0" + parse-github-url@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/parse-github-url/-/parse-github-url-1.0.2.tgz#242d3b65cbcdda14bb50439e3242acf6971db395" @@ -12232,6 +12256,13 @@ prismjs@^1.17.1, prismjs@^1.8.4, prismjs@~1.17.0: optionalDependencies: clipboard "^2.0.0" +prismjs@~1.20.0: + version "1.20.0" + resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.20.0.tgz#9b685fc480a3514ee7198eac6a3bf5024319ff03" + integrity sha512-AEDjSrVNkynnw6A+B1DsFkd6AVdTnp+/WoUixFRULlCLZVRZlVQMVWio/16jv7G1FscUxQxOQhWwApgbnxr6kQ== + optionalDependencies: + clipboard "^2.0.0" + private@^0.1.6, private@~0.1.5: version "0.1.8" resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff" @@ -13192,6 +13223,15 @@ refractor@^2.4.1: parse-entities "^1.1.2" prismjs "~1.17.0" +refractor@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/refractor/-/refractor-3.0.0.tgz#7c8072eaf49dbc1b333e7acc64fb52a1c9b17c75" + integrity sha512-eCGK/oP4VuyW/ERqjMZRZHxl2QsztbkedkYy/SxqE/+Gh1gLaAF17tWIOcVJDiyGhar1NZy/0B9dFef7J0+FDw== + dependencies: + hastscript "^5.0.0" + parse-entities "^2.0.0" + prismjs "~1.20.0" + regenerate-unicode-properties@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-8.1.0.tgz#ef51e0f0ea4ad424b77bf7cb41f3e015c70a3f0e"