CVE-2022-36036 (published 2022-08-29T18:15Z)

mdx-mermaid provides plug and play access to Mermaid in MDX. There is a potential for an arbitrary javascript injection in versions less than 1.3.0 and 2.0.0-rc1. Modify any mermaid code blocks with arbitrary code and it will execute when the component is loaded by MDXjs. This vulnerability was patched in version(s) 1.3.0 and 2.0.0-rc2. There are currently no known workarounds.

CVSS v3 base score: 7.8, HIGH (CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H)

Weakness information:
cwe_id: CWE-94
cwe_name: Improper Control of Generation of Code ('Code Injection')
description: The product constructs all or part of a code segment using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the syntax or behavior of the intended code segment.
extended_description: 
            When a product allows a user's input to contain code syntax, it might be possible for an attacker to craft the code in such a way that it will alter the intended control flow of the product. Such an alteration could lead to arbitrary code execution.
            Injection problems encompass a wide variety of issues -- all mitigated in very different ways. For this reason, the most effective way to discuss these weaknesses is to note the distinct features which classify them as injection weaknesses. The most important issue to note is that all injection problems share one thing in common -- i.e., they allow for the injection of control plane data into the user-controlled data plane. This means that the execution of the process may be altered by sending code in through legitimate data channels, using no other mechanism. While buffer overflows, and many other flaws, involve the use of some further issue to gain execution, injection problems need only for the data to be parsed. The most classic instantiations of this category of weakness are SQL injection and format string vulnerabilities.
         
         

Fix information:
repo_url: https://github.com/sjwall/mdx-mermaid
commit_details: repo_url: https://github.com/sjwall/mdx-mermaid
author: Sam Wall
author_date: 2022-08-22 18:10:59+01:00
msg: Merge pull request from GHSA-rvgm-35jw-q628
repository: repo_url: https://github.com/sjwall/mdx-mermaid
repo_name: sjwall/mdx-mermaid
description: Plug and play Mermaid in MDX
repo_language: TypeScript
owner: sjwall
file_changes: filename: package.json
change_type: ModificationType.MODIFY
diff: @@ -53,6 +53,7 @@
     "@rollup/plugin-babel": "^5.3.1",
     "@rollup/plugin-commonjs": "^22.0.2",
     "@rollup/plugin-typescript": "^8.3.4",
+    "@testing-library/react": "^11.1.0",
     "@types/jest": "^27.4.0",
     "@types/mermaid": "^8.2.7",
     "@types/react": "^17.0.38",
@@ -69,7 +70,7 @@
     "jest": "^27.4.7",
     "mermaid": "^8.0.0",
     "react": "^17.0.1",
-    "react-test-renderer": "^17.0.2",
+    "react-dom": "^17.0.0",
     "rimraf": "^3.0.2",
     "rollup": "^2.78.1",
     "ts-jest": "^27.1.2",

programming_language: JavaScript
filename: Mermaid.spec.tsx
change_type: ModificationType.MODIFY
diff: @@ -7,9 +7,8 @@
  * This source code is licensed under the MIT license found in the
  * license file in the root directory of this source tree.
  */
-import mermaid from 'mermaid'
 import React from 'react'
-import renderer from 'react-test-renderer'
+import { act, render, RenderResult } from '@testing-library/react'
 import { Mermaid } from './Mermaid'
 import {
   DARK_THEME_KEY,
@@ -18,185 +17,126 @@ import {
 } from './theme.helper'
 import * as ThemeHelper from './theme.helper'
 
-async function waitFor (ms: number) {
-  return new Promise<void>(resolve => {
-    setTimeout(() => resolve(), ms)
-  })
-}
-
 jest.mock('mermaid')
 
+// eslint-disable-next-line import/first
+import mermaid from 'mermaid'
+
+const getThemeSpy = jest.spyOn(ThemeHelper, 'getTheme')
+
+const diagram = `graph TD;
+A-->B;
+A-->C;
+B-->D;
+C-->D;`
+
 afterEach(() => {
   jest.clearAllMocks()
 })
 
+const removeUniqueness = (element: Element) => {
+  element.querySelectorAll('style').forEach((v) => v.remove())
+  element.querySelectorAll('svg').forEach((v) => {
+    v.removeAttribute('id')
+    v.parentElement!.removeAttribute('id')
+  })
+}
+
+const expectMermaidMatch = (result: RenderResult) => {
+  removeUniqueness(result.baseElement)
+  expect(result.baseElement.parentElement).toMatchSnapshot()
+  return result
+}
+
 it('renders without diagram', () => {
-  const component = renderer.create(<Mermaid chart={''} config={{}}  />)
-  expect(mermaid.initialize).toBeCalledTimes(0)
-  expect(mermaid.render).toBeCalledTimes(0)
-  component.update()
-  expect(mermaid.render).toBeCalledTimes(1)
-  expect(mermaid.initialize).toBeCalledTimes(1)
-  component.update()
-  expect(mermaid.render).toBeCalledTimes(1)
-  expect(mermaid.initialize).toBeCalledTimes(1)
+  expectMermaidMatch(render(<Mermaid chart={''} config={{}} />))
 })
 
 it('renders with diagram', () => {
-  const component = renderer.create(<Mermaid chart={`graph TD;
-      A-->B;
-      A-->C;
-      B-->D;
-      C-->D;`} config={{}} />)
-  expect(mermaid.initialize).toBeCalledTimes(0)
-  expect(mermaid.render).toBeCalledTimes(0)
-  component.update()
-  expect(mermaid.render).toBeCalledTimes(1)
-  expect(mermaid.initialize).toBeCalledTimes(1)
-  component.update()
-  expect(mermaid.render).toBeCalledTimes(1)
-  expect(mermaid.initialize).toBeCalledTimes(1)
+  expectMermaidMatch(render(<svg><Mermaid chart={diagram} config={{}} /></svg>))
 })
 
-it('initializes only once', async () => {
-  const component = renderer.create(<>
-        <Mermaid chart={'foo'} config={{}} />
-        <Mermaid chart={'bar'} />
-      </>)
-  expect(mermaid.initialize).toBeCalledTimes(0)
-  expect(mermaid.render).toBeCalledTimes(0)
-  await waitFor(1000)
-  component.update()
-  expect(mermaid.render).toBeCalledTimes(2)
+it('renders with diagram change', () => {
+  const config = {}
+  jest.useFakeTimers()
+  const view = expectMermaidMatch(render(<Mermaid chart={diagram} config={config} />))
+  view.rerender(<Mermaid chart={`graph TD;
+D-->C;
+D-->B;
+C-->A;
+B-->A;`} config={config} />)
+  jest.advanceTimersByTime(1000)
+  expectMermaidMatch(view)
+  jest.useRealTimers()
+  expect(mermaid.contentLoaded).toBeCalledTimes(1)
   expect(mermaid.initialize).toBeCalledTimes(1)
-  component.update()
-  expect(mermaid.render).toBeCalledTimes(2)
+})
+
+it('initializes only once', () => {
+  expectMermaidMatch(render(<>
+    <Mermaid chart={'foo'} config={{}} />
+    <Mermaid chart={'bar'} />
+  </>))
+  expect(mermaid.contentLoaded).toBeCalledTimes(1)
   expect(mermaid.initialize).toBeCalledTimes(1)
 })
 
 it('renders with mermaid config', () => {
-  const component = renderer.create(<Mermaid chart={`graph TD;
-      A-->B;
-      A-->C;
-      B-->D;
-      C-->D;`} config={{ mermaid: { theme: 'dark' } } } />)
-  expect(mermaid.initialize).toBeCalledTimes(0)
-  expect(mermaid.render).toBeCalledTimes(0)
-  component.update()
-  expect(mermaid.render).toHaveBeenCalled()
-  expect(mermaid.initialize).toHaveBeenNthCalledWith(1, { startOnLoad: true, theme: 'dark' })
-  component.update()
-  expect(mermaid.render).toBeCalledTimes(1)
-  expect(mermaid.initialize).toBeCalledTimes(1)
+  expectMermaidMatch(render(<Mermaid chart={diagram} config={{ mermaid: { theme: 'dark' } }} />))
+  expect(mermaid.contentLoaded).toBeCalledTimes(1)
+  expect(mermaid.initialize).toBeCalledWith({ startOnLoad: true, theme: 'dark' })
 })
 
-it('re-renders mermaid theme on html data-theme attribute change', async () => {
-  const component = renderer.create(
-    <html data-theme='light'>
-      <Mermaid chart={`graph TD;
-            A-->B;
-            A-->C;
-            B-->D;
-            C-->D;`} config={{}} />
-    </html>)
-  expect(mermaid.initialize).toBeCalledTimes(0)
-  expect(mermaid.render).toBeCalledTimes(0)
-  component.update()
-  expect(mermaid.render).toBeCalledTimes(1)
-  expect(mermaid.initialize).toBeCalledTimes(1)
-  component.update()
-  expect(mermaid.render).toBeCalledTimes(1)
-  expect(mermaid.initialize).toBeCalledTimes(1)
+it('renders with mermaid config change', () => {
+  const view = expectMermaidMatch(render(<Mermaid chart={diagram} config={{ mermaid: { theme: 'dark' } }} />))
+  view.baseElement.querySelectorAll('div.mermaid').forEach((v) => {
+    v.setAttribute('data-processed', 'true')
+  })
+  expect(mermaid.contentLoaded).toBeCalledTimes(1)
+  expect(mermaid.initialize).toBeCalledWith({ startOnLoad: true, theme: 'dark' })
+  view.rerender(<Mermaid chart={diagram} config={{ mermaid: { theme: 'forest' } }} />)
+  // await waitFor(1000)
+  expectMermaidMatch(view)
+  expect(mermaid.contentLoaded).toBeCalledTimes(2)
+  expect(mermaid.initialize).toHaveBeenNthCalledWith(2, { startOnLoad: true, theme: 'forest' })
+})
 
-  component.update(
-    <html data-theme='dark'>
-      <Mermaid chart={`graph TD;
-            A-->B;
-            A-->C;
-            B-->D;
-            C-->D;`} config={{}} />
-        </html>)
+it('renders with string mermaid config', () => {
+  expectMermaidMatch(render(<Mermaid chart={diagram} config={JSON.stringify({ mermaid: { theme: 'dark' } })} />))
+  expect(mermaid.contentLoaded).toBeCalledTimes(1)
+  expect(mermaid.initialize).toBeCalledWith({ startOnLoad: true, theme: 'dark' })
+})
 
-  // Time for mutation observer to notice change.
-  await waitFor(2000)
+it('re-renders mermaid theme on html data-theme attribute change', () => {
+  const component = render(
+    <Mermaid chart={diagram} config={{}} />)
 
-  expect(mermaid.render).toBeCalledTimes(2)
-  expect(mermaid.initialize).toBeCalledTimes(2)
-})
+  expectMermaidMatch(component)
+  expect(mermaid.contentLoaded).toBeCalledTimes(1)
+  expect(mermaid.initialize).toBeCalledTimes(1)
+  expect(getThemeSpy).toBeCalledTimes(1)
 
-it('renders the output of mermaid into the div', async () => {
-  const expectedOutput = 'mermaid output'
-  mermaid.render = jest.fn((_, __, cb) => {
-    if (cb) cb(expectedOutput, () => 0)
-    return expectedOutput
-  })
+  act(() => document.querySelector('html')!.setAttribute(HTML_THEME_ATTRIBUTE, DARK_THEME_KEY))
 
-  let component: any
-  renderer.act(() => {
-    component = renderer.create(
-      <Mermaid chart={`graph TD;
-            A-->B;
-            A-->C;
-            B-->D;
-            C-->D;`} config={{}} />
-    )
-  })
+  expectMermaidMatch(component)
 
+  expect(mermaid.contentLoaded).toBeCalledTimes(1)
   expect(mermaid.initialize).toBeCalledTimes(1)
-  expect(mermaid.render).toBeCalledTimes(1)
-  expect(component.toJSON()).toMatchSnapshot()
-})
+  expect(getThemeSpy).toBeCalledTimes(1)
 
-describe('changing the theme at runtime', () => {
-  let useRefSpy: jest.SpyInstance
-  let html: HTMLHtmlElement
+  act(() => document.querySelector('html')!.setAttribute(HTML_THEME_ATTRIBUTE, LIGHT_THEME_KEY))
 
-  beforeEach(() => {
-    html = document.createElement('html')
-    html.setAttribute(HTML_THEME_ATTRIBUTE, LIGHT_THEME_KEY)
-    useRefSpy = jest.spyOn(document, 'querySelector').mockReturnValue(html)
-  })
+  expectMermaidMatch(component)
+})
 
-  afterEach(() => {
-    expect(useRefSpy).toHaveBeenCalled()
-  })
+it('does not react to non-theme attribute changes of html', () => {
+  const component = render(<Mermaid chart={diagram} config={{}} />)
 
-  it('reacts to changed theme', async () => {
-    const getThemeSpy = jest.spyOn(ThemeHelper, 'getTheme')
-    renderer.act(() => {
-      renderer.create(
-        <Mermaid chart={`graph TD;
-              A-->B;
-              A-->C;
-              B-->D;
-              C-->D;`} config={{}} />
-      )
-    })
-
-    await renderer.act(async () => {
-      html.setAttribute(HTML_THEME_ATTRIBUTE, DARK_THEME_KEY)
-      await waitFor(1000)
-    })
-
-    expect(getThemeSpy.mock.calls.length).toBeGreaterThan(2)
-  })
+  expectMermaidMatch(component)
+  expect(mermaid.contentLoaded).toBeCalledTimes(1)
+  expect(mermaid.initialize).toBeCalledTimes(1)
 
-  it('does not react to non-theme attribute changes of html', async () => {
-    const getThemeSpy = jest.spyOn(ThemeHelper, 'getTheme')
-    renderer.act(() => {
-      renderer.create(
-        <Mermaid chart={`graph TD;
-              A-->B;
-              A-->C;
-              B-->D;
-              C-->D;`} config={{}} />
-      )
-    })
-
-    await renderer.act(async () => {
-      html.setAttribute('manifest', 'some-value')
-      await waitFor(1000)
-    })
-    expect(getThemeSpy).toHaveBeenCalledTimes(2)
-  })
+  act(() => document.querySelector('html')!.setAttribute('manifest', 'some-value'))
+
+  expectMermaidMatch(component)
 })

programming_language: JavaScript
filename: Mermaid.tsx
change_type: ModificationType.MODIFY
diff: @@ -5,19 +5,12 @@
  * license file in the root directory of this source tree.
  */
 
-import React, { useEffect, useState, ReactElement } from 'react'
+import React, { useEffect, useState, ReactElement, useMemo } from 'react'
 import mermaid from 'mermaid'
-import mermaidAPI from 'mermaid/mermaidAPI'
 
 import { Config } from './config.model'
 import { getTheme } from './theme.helper'
 
-/**
- * Assign a unique ID to each mermaid svg as per requirements
- * of `mermaid.render`.
- */
-let id = 0
-
 /**
  * Properties for Mermaid component.
  */
@@ -30,7 +23,7 @@ export type MermaidProps = {
   /**
    * Config to initialize mermaid with.
    */
-  config?: Config
+  config?: Config | string
 }
 
 /**
@@ -40,18 +33,21 @@ export type MermaidProps = {
  * @param param1 Config.
  * @returns The component.
  */
-export const Mermaid = ({ chart, config }: MermaidProps): ReactElement<MermaidProps> => {
+export const Mermaid = ({ chart, config: configSrc }: MermaidProps): ReactElement<MermaidProps> => {
   // Due to Docusaurus not correctly parsing client-side from server-side modules, use the provided workaround
   // found in the accompanying issue: https://github.com/facebook/docusaurus/issues/4268#issuecomment-783553084
   /* istanbul ignore next */
   if (typeof window === 'undefined') {
-    return <div></div>
+    return <div className="mermaid" data-mermaid-src={chart}>{chart}</div>
   }
 
+  const config: Config = useMemo(() => typeof configSrc === 'string' ? JSON.parse(configSrc) : configSrc, [configSrc])
+
   const html: HTMLHtmlElement = document.querySelector('html')!
 
-  // Watch for changes in theme in the HTML attribute `data-theme`.
-  const [theme, setTheme] = useState<mermaidAPI.Theme>(getTheme(html, config))
+  const [rerender, setRerender] = useState<boolean>(false)
+
+  const theme = useMemo(() => getTheme(html, config), [config, rerender])
 
   useEffect(() => {
     const observer = new MutationObserver((mutations) => {
@@ -59,7 +55,7 @@ export const Mermaid = ({ chart, config }: MermaidProps): ReactElement<MermaidPr
         if (mutation.type !== 'attributes' || mutation.attributeName !== 'data-theme') {
           continue
         }
-        setTheme(getTheme(mutation.target as HTMLHtmlElement, config))
+        setRerender((cur) => !cur)
         break
       }
     })
@@ -72,28 +68,26 @@ export const Mermaid = ({ chart, config }: MermaidProps): ReactElement<MermaidPr
         // Do nothing
       }
     }
-  }, [chart, config, theme])
+  }, [])
 
-  // When theme updates, rerender the SVG.
-  const [svg, setSvg] = useState<string>('')
   useEffect(() => {
-    const render = () => {
-      mermaid.render(`mermaid-svg-${id.toString()}`, chart, (renderedSvg) => setSvg(renderedSvg))
-      id++
-    }
-
     if (config) {
       if (config.mermaid) {
         mermaid.initialize({ startOnLoad: true, ...config.mermaid, theme })
       } else {
         mermaid.initialize({ startOnLoad: true, theme })
       }
-      render()
-    } else {
-      // Is there a better way?
-      setTimeout(render, 0)
+      document.querySelectorAll('div.mermaid[data-processed="true"]').forEach((v) => {
+        v.removeAttribute('data-processed')
+        v.innerHTML = v.getAttribute('data-mermaid-src') as string
+      })
+      mermaid.contentLoaded()
     }
-  }, [theme, chart])
+  }, [config, theme])
+
+  useEffect(() => {
+    setTimeout(() => mermaid.contentLoaded, 0)
+  }, [chart])
 
-  return <div dangerouslySetInnerHTML={{ __html: svg }}></div>
+  return <div className="mermaid" data-mermaid-src={chart}>{chart}</div>
 }

programming_language: JavaScript
filename: Mermaid.spec.tsx.snap
change_type: ModificationType.MODIFY
diff: @@ -1,11 +1,336 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
-exports[`renders the output of mermaid into the div 1`] = `
-<div
-  dangerouslySetInnerHTML={
-    Object {
-      "__html": "mermaid output",
-    }
-  }
-/>
+exports[`does not react to non-theme attribute changes of html 1`] = `
+<html
+  data-theme="light"
+>
+  <head />
+  <body>
+    <div>
+      <div
+        class="mermaid"
+        data-mermaid-src="graph TD;
+A-->B;
+A-->C;
+B-->D;
+C-->D;"
+      >
+        graph TD;
+A--&gt;B;
+A--&gt;C;
+B--&gt;D;
+C--&gt;D;
+      </div>
+    </div>
+  </body>
+</html>
+`;
+
+exports[`does not react to non-theme attribute changes of html 2`] = `
+<html
+  data-theme="light"
+  manifest="some-value"
+>
+  <head />
+  <body>
+    <div>
+      <div
+        class="mermaid"
+        data-mermaid-src="graph TD;
+A-->B;
+A-->C;
+B-->D;
+C-->D;"
+      >
+        graph TD;
+A--&gt;B;
+A--&gt;C;
+B--&gt;D;
+C--&gt;D;
+      </div>
+    </div>
+  </body>
+</html>
+`;
+
+exports[`initializes only once 1`] = `
+<html>
+  <head />
+  <body>
+    <div>
+      <div
+        class="mermaid"
+        data-mermaid-src="foo"
+      >
+        foo
+      </div>
+      <div
+        class="mermaid"
+        data-mermaid-src="bar"
+      >
+        bar
+      </div>
+    </div>
+  </body>
+</html>
+`;
+
+exports[`re-renders mermaid theme on html data-theme attribute change 1`] = `
+<html>
+  <head />
+  <body>
+    <div>
+      <div
+        class="mermaid"
+        data-mermaid-src="graph TD;
+A-->B;
+A-->C;
+B-->D;
+C-->D;"
+      >
+        graph TD;
+A--&gt;B;
+A--&gt;C;
+B--&gt;D;
+C--&gt;D;
+      </div>
+    </div>
+  </body>
+</html>
+`;
+
+exports[`re-renders mermaid theme on html data-theme attribute change 2`] = `
+<html
+  data-theme="dark"
+>
+  <head />
+  <body>
+    <div>
+      <div
+        class="mermaid"
+        data-mermaid-src="graph TD;
+A-->B;
+A-->C;
+B-->D;
+C-->D;"
+      >
+        graph TD;
+A--&gt;B;
+A--&gt;C;
+B--&gt;D;
+C--&gt;D;
+      </div>
+    </div>
+  </body>
+</html>
+`;
+
+exports[`re-renders mermaid theme on html data-theme attribute change 3`] = `
+<html
+  data-theme="light"
+>
+  <head />
+  <body>
+    <div>
+      <div
+        class="mermaid"
+        data-mermaid-src="graph TD;
+A-->B;
+A-->C;
+B-->D;
+C-->D;"
+      >
+        graph TD;
+A--&gt;B;
+A--&gt;C;
+B--&gt;D;
+C--&gt;D;
+      </div>
+    </div>
+  </body>
+</html>
+`;
+
+exports[`renders with diagram 1`] = `
+<html>
+  <head />
+  <body>
+    <div>
+      <svg>
+        <div
+          class="mermaid"
+          data-mermaid-src="graph TD;
+A-->B;
+A-->C;
+B-->D;
+C-->D;"
+        >
+          graph TD;
+A--&gt;B;
+A--&gt;C;
+B--&gt;D;
+C--&gt;D;
+        </div>
+      </svg>
+    </div>
+  </body>
+</html>
+`;
+
+exports[`renders with diagram change 1`] = `
+<html>
+  <head />
+  <body>
+    <div>
+      <div
+        class="mermaid"
+        data-mermaid-src="graph TD;
+A-->B;
+A-->C;
+B-->D;
+C-->D;"
+      >
+        graph TD;
+A--&gt;B;
+A--&gt;C;
+B--&gt;D;
+C--&gt;D;
+      </div>
+    </div>
+  </body>
+</html>
+`;
+
+exports[`renders with diagram change 2`] = `
+<html>
+  <head />
+  <body>
+    <div>
+      <div
+        class="mermaid"
+        data-mermaid-src="graph TD;
+D-->C;
+D-->B;
+C-->A;
+B-->A;"
+      >
+        graph TD;
+D--&gt;C;
+D--&gt;B;
+C--&gt;A;
+B--&gt;A;
+      </div>
+    </div>
+  </body>
+</html>
+`;
+
+exports[`renders with mermaid config 1`] = `
+<html>
+  <head />
+  <body>
+    <div>
+      <div
+        class="mermaid"
+        data-mermaid-src="graph TD;
+A-->B;
+A-->C;
+B-->D;
+C-->D;"
+      >
+        graph TD;
+A--&gt;B;
+A--&gt;C;
+B--&gt;D;
+C--&gt;D;
+      </div>
+    </div>
+  </body>
+</html>
+`;
+
+exports[`renders with mermaid config change 1`] = `
+<html>
+  <head />
+  <body>
+    <div>
+      <div
+        class="mermaid"
+        data-mermaid-src="graph TD;
+A-->B;
+A-->C;
+B-->D;
+C-->D;"
+      >
+        graph TD;
+A--&gt;B;
+A--&gt;C;
+B--&gt;D;
+C--&gt;D;
+      </div>
+    </div>
+  </body>
+</html>
+`;
+
+exports[`renders with mermaid config change 2`] = `
+<html>
+  <head />
+  <body>
+    <div>
+      <div
+        class="mermaid"
+        data-mermaid-src="graph TD;
+A-->B;
+A-->C;
+B-->D;
+C-->D;"
+      >
+        graph TD;
+A--&gt;B;
+A--&gt;C;
+B--&gt;D;
+C--&gt;D;
+      </div>
+    </div>
+  </body>
+</html>
+`;
+
+exports[`renders with string mermaid config 1`] = `
+<html>
+  <head />
+  <body>
+    <div>
+      <div
+        class="mermaid"
+        data-mermaid-src="graph TD;
+A-->B;
+A-->C;
+B-->D;
+C-->D;"
+      >
+        graph TD;
+A--&gt;B;
+A--&gt;C;
+B--&gt;D;
+C--&gt;D;
+      </div>
+    </div>
+  </body>
+</html>
+`;
+
+exports[`renders without diagram 1`] = `
+<html>
+  <head />
+  <body>
+    <div>
+      <div
+        class="mermaid"
+        data-mermaid-src=""
+      />
+    </div>
+  </body>
+</html>
 `;

programming_language: HTML
filename: mdxast-mermaid.spec.ts
change_type: ModificationType.MODIFY
diff: @@ -24,7 +24,24 @@ function createTestCompiler (config?: Config) {
 test('No mermaid', async () => {
   const mdxCompiler = createTestCompiler()
   const result = await mdxCompiler.process('# Heading 1\n\nNo Mermaid diagram :(')
-  expect(result.contents).toEqual('\n\n\nconst layoutProps = {\n  \n};\nconst MDXLayout = "wrapper"\nexport default function MDXContent({\n  components,\n  ...props\n}) {\n  return <MDXLayout {...layoutProps} {...props} components={components} mdxType="MDXLayout">\n    <h1>{`Heading 1`}</h1>\n    <p>{`No Mermaid diagram :(`}</p>\n    </MDXLayout>;\n}\n\n;\nMDXContent.isMDXComponent = true;')
+  expect(result.contents).toEqual(`import { Mermaid } from 'mdx-mermaid/lib/Mermaid';
+
+
+const layoutProps = {\n  \n};
+const MDXLayout = "wrapper"
+export default function MDXContent({
+  components,
+  ...props
+}) {
+  return <MDXLayout {...layoutProps} {...props} components={components} mdxType="MDXLayout">
+
+    <h1>{\`Heading 1\`}</h1>
+    <p>{\`No Mermaid diagram :(\`}</p>
+    </MDXLayout>;
+}
+
+;
+MDXContent.isMDXComponent = true;`)
 })
 
 test('Basic', async () => {
@@ -37,7 +54,26 @@ graph TD;
     B-->D;
     C-->D;
 \`\`\``)
-  expect(result.contents).toEqual("import { Mermaid } from 'mdx-mermaid/lib/Mermaid';\n\n\nconst layoutProps = {\n  \n};\nconst MDXLayout = \"wrapper\"\nexport default function MDXContent({\n  components,\n  ...props\n}) {\n  return <MDXLayout {...layoutProps} {...props} components={components} mdxType=\"MDXLayout\">\n\n    <h1>{`Heading 1`}</h1>\n    <Mermaid config={{}} chart={`graph TD;\n    A-->B;\n    A-->C;\n    B-->D;\n    C-->D;`} mdxType=\"Mermaid\" />\n    </MDXLayout>;\n}\n\n;\nMDXContent.isMDXComponent = true;")
+  expect(result.contents).toEqual(`import { Mermaid } from 'mdx-mermaid/lib/Mermaid';
+
+
+const layoutProps = {\n  \n};
+const MDXLayout = "wrapper"
+export default function MDXContent({
+  components,
+  ...props
+}) {
+  return <MDXLayout {...layoutProps} {...props} components={components} mdxType="MDXLayout">
+
+    <h1>{\`Heading 1\`}</h1>
+    <Mermaid {...{
+      "chart": "graph TD;\\n    A-->B;\\n    A-->C;\\n    B-->D;\\n    C-->D;"
+    }} mdxType="Mermaid"></Mermaid>
+    </MDXLayout>;
+}
+
+;
+MDXContent.isMDXComponent = true;`)
 })
 
 test('Existing import', async () => {
@@ -50,7 +86,26 @@ graph TD;
     B-->D;
     C-->D;
 \`\`\``)
-  expect(result.contents).toEqual("import { Mermaid } from 'mdx-mermaid/lib/Mermaid';\n\n\nconst layoutProps = {\n  \n};\nconst MDXLayout = \"wrapper\"\nexport default function MDXContent({\n  components,\n  ...props\n}) {\n  return <MDXLayout {...layoutProps} {...props} components={components} mdxType=\"MDXLayout\">\n\n    <h1>{`Heading 1`}</h1>\n    <Mermaid config={{}} chart={`graph TD;\n    A-->B;\n    A-->C;\n    B-->D;\n    C-->D;`} mdxType=\"Mermaid\" />\n    </MDXLayout>;\n}\n\n;\nMDXContent.isMDXComponent = true;")
+  expect(result.contents).toEqual(`import { Mermaid } from 'mdx-mermaid/lib/Mermaid';
+
+
+const layoutProps = {\n  \n};
+const MDXLayout = "wrapper"
+export default function MDXContent({
+  components,
+  ...props
+}) {
+  return <MDXLayout {...layoutProps} {...props} components={components} mdxType="MDXLayout">
+
+    <h1>{\`Heading 1\`}</h1>
+    <Mermaid {...{
+      "chart": "graph TD;\\n    A-->B;\\n    A-->C;\\n    B-->D;\\n    C-->D;"
+    }} mdxType="Mermaid"></Mermaid>
+    </MDXLayout>;
+}
+
+;
+MDXContent.isMDXComponent = true;`)
 })
 
 test('Existing import from ts exports(without /lib)', async () => {
@@ -63,7 +118,26 @@ graph TD;
     B-->D;
     C-->D;
 \`\`\``)
-  expect(result.contents).toEqual("import { Mermaid } from 'mdx-mermaid/Mermaid';\n\n\nconst layoutProps = {\n  \n};\nconst MDXLayout = \"wrapper\"\nexport default function MDXContent({\n  components,\n  ...props\n}) {\n  return <MDXLayout {...layoutProps} {...props} components={components} mdxType=\"MDXLayout\">\n\n    <h1>{`Heading 1`}</h1>\n    <Mermaid config={{}} chart={`graph TD;\n    A-->B;\n    A-->C;\n    B-->D;\n    C-->D;`} mdxType=\"Mermaid\" />\n    </MDXLayout>;\n}\n\n;\nMDXContent.isMDXComponent = true;")
+  expect(result.contents).toEqual(`import { Mermaid } from 'mdx-mermaid/Mermaid';
+
+
+const layoutProps = {\n  \n};
+const MDXLayout = "wrapper"
+export default function MDXContent({
+  components,
+  ...props
+}) {
+  return <MDXLayout {...layoutProps} {...props} components={components} mdxType="MDXLayout">
+
+    <h1>{\`Heading 1\`}</h1>
+    <Mermaid {...{
+      "chart": "graph TD;\\n    A-->B;\\n    A-->C;\\n    B-->D;\\n    C-->D;"
+    }} mdxType="Mermaid"></Mermaid>
+    </MDXLayout>;
+}
+
+;
+MDXContent.isMDXComponent = true;`)
 })
 
 test('Other imports', async () => {
@@ -76,7 +150,28 @@ graph TD;
     B-->D;
     C-->D;
 \`\`\``)
-  expect(result.contents).toEqual("import { Mermaid } from 'mdx-mermaid/lib/Mermaid';\nimport { A } from 'a';\n\n\nconst layoutProps = {\n  \n};\nconst MDXLayout = \"wrapper\"\nexport default function MDXContent({\n  components,\n  ...props\n}) {\n  return <MDXLayout {...layoutProps} {...props} components={components} mdxType=\"MDXLayout\">\n\n\n    <h1>{`Heading 1`}</h1>\n    <Mermaid config={{}} chart={`graph TD;\n    A-->B;\n    A-->C;\n    B-->D;\n    C-->D;`} mdxType=\"Mermaid\" />\n    </MDXLayout>;\n}\n\n;\nMDXContent.isMDXComponent = true;")
+  expect(result.contents).toEqual(`import { Mermaid } from 'mdx-mermaid/lib/Mermaid';
+import { A } from 'a';
+
+
+const layoutProps = {\n  \n};
+const MDXLayout = "wrapper"
+export default function MDXContent({
+  components,
+  ...props
+}) {
+  return <MDXLayout {...layoutProps} {...props} components={components} mdxType="MDXLayout">
+
+
+    <h1>{\`Heading 1\`}</h1>
+    <Mermaid {...{
+      "chart": "graph TD;\\n    A-->B;\\n    A-->C;\\n    B-->D;\\n    C-->D;"
+    }} mdxType="Mermaid"></Mermaid>
+    </MDXLayout>;
+}
+
+;
+MDXContent.isMDXComponent = true;`)
 })
 
 test('Other imports component', async () => {
@@ -101,7 +196,7 @@ export default function MDXContent({
 
 
     <h1>{\`Heading 1\`}</h1>
-    <Mermaid config={{}} chart={\`graph TD;
+    <Mermaid chart={\`graph TD;
       A-->B;
       A-->C;
       B-->D;
@@ -144,7 +239,7 @@ export default function MDXContent({
     <h1>{\`Heading 1\`}</h1>
     <A mdxType="A">Hi</A>
     <h2>{\`Heading 2\`}</h2>
-    <Mermaid config={{}} chart={\`graph TD;
+    <Mermaid chart={\`graph TD;
       A-->B;
       A-->C;
       B-->D;
@@ -178,15 +273,10 @@ export default function MDXContent({
   return <MDXLayout {...layoutProps} {...props} components={components} mdxType="MDXLayout">
 
     <h1>{\`Heading 1\`}</h1>
-    <Mermaid config={{
-      "mermaid": {
-        "theme": "dark"
-      }
-    }} chart={\`graph TD;
-    A-->B;
-    A-->C;
-    B-->D;
-    C-->D;\`} mdxType="Mermaid" />
+    <Mermaid {...{
+      "config": "{\\"mermaid\\":{\\"theme\\":\\"dark\\"}}",
+      "chart": "graph TD;\\n    A-->B;\\n    A-->C;\\n    B-->D;\\n    C-->D;"
+    }} mdxType="Mermaid"></Mermaid>
     </MDXLayout>;
 }
 
@@ -214,11 +304,7 @@ export default function MDXContent({
   return <MDXLayout {...layoutProps} {...props} components={components} mdxType="MDXLayout">
 
     <h1>{\`Heading 1\`}</h1>
-    <Mermaid config={{
-      "mermaid": {
-        "theme": "dark"
-      }
-    }} chart={\`graph TD;
+    <Mermaid chart={\`graph TD;
     A-->B;
     A-->C;
     B-->D;
@@ -257,15 +343,10 @@ export default function MDXContent({
   return <MDXLayout {...layoutProps} {...props} components={components} mdxType="MDXLayout">
 
     <h1>{\`Heading 1\`}</h1>
-    <Mermaid config={{
-      "mermaid": {
-        "theme": "dark"
-      }
-    }} chart={\`graph TD;
-    A-->B;
-    A-->C;
-    B-->D;
-    C-->D;\`} mdxType="Mermaid" />
+    <Mermaid {...{
+      "config": "{\\"mermaid\\":{\\"theme\\":\\"dark\\"}}",
+      "chart": "graph TD;\\n    A-->B;\\n    A-->C;\\n    B-->D;\\n    C-->D;"
+    }} mdxType="Mermaid"></Mermaid>
     <Mermaid chart={\`graph TD;
     E-->F;
     E-->G;
@@ -277,3 +358,46 @@ export default function MDXContent({
 ;
 MDXContent.isMDXComponent = true;`)
 })
+
+test('Multiple code block', async () => {
+  const mdxCompiler = createTestCompiler({ mermaid: { theme: 'dark' } })
+  const result = await mdxCompiler.process(`# Heading 1\n
+\`\`\`mermaid
+graph TD;
+    A-->B;
+    A-->C;
+    B-->D;
+    C-->D;
+\`\`\`
+\`\`\`mermaid
+graph TD;
+E-->F;
+E-->G;
+F-->H;
+G-->H;
+\`\`\``)
+  expect(result.contents).toEqual(`import { Mermaid } from 'mdx-mermaid/lib/Mermaid';
+
+
+const layoutProps = {\n  \n};
+const MDXLayout = "wrapper"
+export default function MDXContent({
+  components,
+  ...props
+}) {
+  return <MDXLayout {...layoutProps} {...props} components={components} mdxType="MDXLayout">
+
+    <h1>{\`Heading 1\`}</h1>
+    <Mermaid {...{
+      "config": "{\\"mermaid\\":{\\"theme\\":\\"dark\\"}}",
+      "chart": "graph TD;\\n    A-->B;\\n    A-->C;\\n    B-->D;\\n    C-->D;"
+    }} mdxType="Mermaid"></Mermaid>
+    <Mermaid {...{
+      "chart": "graph TD;\\nE-->F;\\nE-->G;\\nF-->H;\\nG-->H;"
+    }} mdxType="Mermaid"></Mermaid>
+    </MDXLayout>;
+}
+
+;
+MDXContent.isMDXComponent = true;`)
+})

programming_language: TypeScript
filename: mdxast-mermaid.ts
change_type: ModificationType.MODIFY
diff: @@ -53,28 +53,20 @@ export default function plugin (config?: Config) {
     })
 
     // Replace each Mermaid code block with the Mermaid component
-    instances.forEach(([node, index, parent]) => {
+    instances.forEach(([node, index, parent], i) => {
       parent.children.splice(index, 1, {
-        type: 'jsx',
-        value: `<Mermaid chart={\`${node.value}\`}/>`,
-        position: node.position
+        type: 'mermaidCodeBlock',
+        data: {
+          hName: 'Mermaid',
+          hProperties: {
+            config: i > 0 ? undefined : JSON.stringify(config),
+            chart: node.value
+          }
+        }
       })
     })
 
-    // Look for any components
-    visit<Literal<string> & { type: 'jsx' }>(ast, { type: 'jsx' }, (node, index, parent) => {
-      if (/.*<Mermaid.*/.test(node.value)) {
-        // If the component doesn't have config
-        if (!/.*config={.*/.test(node.value)) {
-          const index = node.value.indexOf('<Mermaid') + 8
-          node.value = node.value.substring(0, index) +
-          ` config={${JSON.stringify(config || {})}}` +
-            node.value.substring(index)
-        }
-        insertImport(ast)
-        return visit.EXIT
-      }
-    })
+    insertImport(ast)
     return ast
   }
 }

programming_language: TypeScript
filename: yarn.lock
change_type: ModificationType.MODIFY
diff: @@ -1501,7 +1501,15 @@
     pirates "^4.0.0"
     source-map-support "^0.5.16"
 
-"@babel/runtime@^7.8.4":
+"@babel/runtime-corejs3@^7.10.2":
+  version "7.18.9"
+  resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.18.9.tgz#7bacecd1cb2dd694eacd32a91fcf7021c20770ae"
+  integrity sha512-qZEWeccZCrHA2Au4/X05QW5CMdm4VjUDCrGq5gf1ZDcM4hRqreKrtwAn7yci9zfgAS9apvnsFXiGBHBAxZdK9A==
+  dependencies:
+    core-js-pure "^3.20.2"
+    regenerator-runtime "^0.13.4"
+
+"@babel/runtime@^7.10.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.8.4":
   version "7.18.9"
   resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.18.9.tgz#b4fcfce55db3d2e5e080d2490f608a3b9f407f4a"
   integrity sha512-lkqXDcvlFT5rvEjiu6+QYO+1GXrEHRo2LOtS7E4GtX5ESIZOgepqsZBVIj6Pv+a6zqsya9VCgiK1KAK4BvJDAw==
@@ -1852,6 +1860,17 @@
     source-map "^0.6.1"
     write-file-atomic "^3.0.0"
 
+"@jest/types@^26.6.2":
+  version "26.6.2"
+  resolved "https://registry.yarnpkg.com/@jest/types/-/types-26.6.2.tgz#bef5a532030e1d88a2f5a6d933f84e97226ed48e"
+  integrity sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==
+  dependencies:
+    "@types/istanbul-lib-coverage" "^2.0.0"
+    "@types/istanbul-reports" "^3.0.0"
+    "@types/node" "*"
+    "@types/yargs" "^15.0.0"
+    chalk "^4.0.0"
+
 "@jest/types@^27.0.6":
   version "27.0.6"
   resolved "https://registry.yarnpkg.com/@jest/types/-/types-27.0.6.tgz#9a992bc517e0c49f035938b8549719c2de40706b"
@@ -2114,11 +2133,38 @@
   dependencies:
     "@sinonjs/commons" "^1.7.0"
 
+"@testing-library/dom@^7.28.1":
+  version "7.31.2"
+  resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-7.31.2.tgz#df361db38f5212b88555068ab8119f5d841a8c4a"
+  integrity sha512-3UqjCpey6HiTZT92vODYLPxTBWlM8ZOOjr3LX5F37/VRipW2M1kX6I/Cm4VXzteZqfGfagg8yXywpcOgQBlNsQ==
+  dependencies:
+    "@babel/code-frame" "^7.10.4"
+    "@babel/runtime" "^7.12.5"
+    "@types/aria-query" "^4.2.0"
+    aria-query "^4.2.2"
+    chalk "^4.1.0"
+    dom-accessibility-api "^0.5.6"
+    lz-string "^1.4.4"
+    pretty-format "^26.6.2"
+
+"@testing-library/react@^11.1.0":
+  version "11.2.7"
+  resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-11.2.7.tgz#b29e2e95c6765c815786c0bc1d5aed9cb2bf7818"
+  integrity sha512-tzRNp7pzd5QmbtXNG/mhdcl7Awfu/Iz1RaVHY75zTdOkmHCuzMhRL83gWHSgOAcjS3CCbyfwUHMZgRJb4kAfpA==
+  dependencies:
+    "@babel/runtime" "^7.12.5"
+    "@testing-library/dom" "^7.28.1"
+
 "@tootallnate/once@1":
   version "1.1.2"
   resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82"
   integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==
 
+"@types/aria-query@^4.2.0":
+  version "4.2.2"
+  resolved "https://registry.yarnpkg.com/@types/aria-query/-/aria-query-4.2.2.tgz#ed4e0ad92306a704f9fb132a0cfcf77486dbe2bc"
+  integrity sha512-HnYpAE1Y6kRyKM/XkEuiRQhTHvkzMBurTHnpFLYLBGPIylZNPs9jJcuOOYWxPLJCSEtmZT0Y8rHDokKN7rRTig==
+
 "@types/babel__core@^7.0.0", "@types/babel__core@^7.1.14":
   version "7.1.15"
   resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.15.tgz#2ccfb1ad55a02c83f8e0ad327cbc332f55eb1024"
@@ -2279,6 +2325,13 @@
   resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-20.2.1.tgz#3b9ce2489919d9e4fea439b76916abc34b2df129"
   integrity sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw==
 
+"@types/yargs@^15.0.0":
+  version "15.0.14"
+  resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-15.0.14.tgz#26d821ddb89e70492160b66d10a0eb6df8f6fb06"
+  integrity sha512-yEJzHoxf6SyQGhBhIYGXQDSCkJjB6HohDShto7m8vaKg9Yp0Yn8+71J9eakh2bnPg6BfsH9PRMhiRTZnd4eXGQ==
+  dependencies:
+    "@types/yargs-parser" "*"
+
 "@types/yargs@^16.0.0":
   version "16.0.4"
   resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-16.0.4.tgz#26aad98dd2c2a38e421086ea9ad42b9e51642977"
@@ -2492,6 +2545,14 @@ argparse@^2.0.1:
   resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38"
   integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==
 
+aria-query@^4.2.2:
+  version "4.2.2"
+  resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-4.2.2.tgz#0d2ca6c9aceb56b8977e9fed6aed7e15bbd2f83b"
+  integrity sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==
+  dependencies:
+    "@babel/runtime" "^7.10.2"
+    "@babel/runtime-corejs3" "^7.10.2"
+
 arr-diff@^4.0.0:
   version "4.0.0"
   resolved "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz"
@@ -3114,6 +3175,11 @@ core-js-compat@^3.21.0, core-js-compat@^3.22.1:
     browserslist "^4.21.3"
     semver "7.0.0"
 
+core-js-pure@^3.20.2:
+  version "3.24.1"
+  resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.24.1.tgz#8839dde5da545521bf282feb7dc6d0b425f39fd3"
+  integrity sha512-r1nJk41QLLPyozHUUPmILCEMtMw24NG4oWK6RbsDdjzQgg9ZvrUsPBj1MnG0wXXp1DCDU6j+wUvEmBSrtRbLXg==
+
 cosmiconfig@^7.0.0:
   version "7.0.0"
   resolved "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.0.tgz"
@@ -3563,6 +3629,11 @@ doctrine@^3.0.0:
   dependencies:
     esutils "^2.0.2"
 
+dom-accessibility-api@^0.5.6:
+  version "0.5.14"
+  resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.5.14.tgz#56082f71b1dc7aac69d83c4285eef39c15d93f56"
+  integrity sha512-NMt+m9zFMPZe0JcY9gN224Qvk6qLIdqex29clBvc/y75ZBX9YA9wNK3frsYvu2DI1xcCIwxwnX+TlsJ2DSOADg==
+
 domexception@^2.0.1:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/domexception/-/domexception-2.0.1.tgz#fb44aefba793e1574b0af6aed2801d057529f304"
@@ -5722,6 +5793,11 @@ lru-cache@^6.0.0:
   dependencies:
     yallist "^4.0.0"
 
+lz-string@^1.4.4:
+  version "1.4.4"
+  resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.4.4.tgz#c0d8eaf36059f705796e1e344811cf4c498d3a26"
+  integrity sha512-0ckx7ZHRPqb0oUm8zNr+90mtf9DQB60H1wMCjBtfi62Kl3a7JbHob6gA2bC+xRvZoOL+1hzUK8jeuEIQE8svEQ==
+
 magic-string@^0.25.7:
   version "0.25.9"
   resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.9.tgz#de7f9faf91ef8a1c91d02c2e5314c8277dbcdd1c"
@@ -6320,6 +6396,16 @@ prelude-ls@~1.1.2:
   resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54"
   integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=
 
+pretty-format@^26.6.2:
+  version "26.6.2"
+  resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-26.6.2.tgz#e35c2705f14cb7fe2fe94fa078345b444120fc93"
+  integrity sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==
+  dependencies:
+    "@jest/types" "^26.6.2"
+    ansi-regex "^5.0.0"
+    ansi-styles "^4.0.0"
+    react-is "^17.0.1"
+
 pretty-format@^27.0.0, pretty-format@^27.4.6:
   version "27.4.6"
   resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-27.4.6.tgz#1b784d2f53c68db31797b2348fa39b49e31846b7"
@@ -6373,33 +6459,24 @@ queue-microtask@^1.2.2:
   resolved "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz"
   integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==
 
-"react-is@^16.12.0 || ^17.0.0", react-is@^17.0.1, react-is@^17.0.2:
+react-dom@^17.0.0:
   version "17.0.2"
-  resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0"
-  integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==
+  resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-17.0.2.tgz#ecffb6845e3ad8dbfcdc498f0d0a939736502c23"
+  integrity sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==
+  dependencies:
+    loose-envify "^1.1.0"
+    object-assign "^4.1.1"
+    scheduler "^0.20.2"
 
 react-is@^16.8.1:
   version "16.13.1"
   resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz"
   integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
 
-react-shallow-renderer@^16.13.1:
-  version "16.14.1"
-  resolved "https://registry.yarnpkg.com/react-shallow-renderer/-/react-shallow-renderer-16.14.1.tgz#bf0d02df8a519a558fd9b8215442efa5c840e124"
-  integrity sha512-rkIMcQi01/+kxiTE9D3fdS959U1g7gs+/rborw++42m1O9FAQiNI/UNRZExVUoAOprn4umcXf+pFRou8i4zuBg==
-  dependencies:
-    object-assign "^4.1.1"
-    react-is "^16.12.0 || ^17.0.0"
-
-react-test-renderer@^17.0.2:
+react-is@^17.0.1:
   version "17.0.2"
-  resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-17.0.2.tgz#4cd4ae5ef1ad5670fc0ef776e8cc7e1231d9866c"
-  integrity sha512-yaQ9cB89c17PUb0x6UfWRs7kQCorVdHlutU1boVPEsB8IDZH6n9tHxMacc3y0JoXOJUsZb/t/Mb8FUWMKaM7iQ==
-  dependencies:
-    object-assign "^4.1.1"
-    react-is "^17.0.2"
-    react-shallow-renderer "^16.13.1"
-    scheduler "^0.20.2"
+  resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0"
+  integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==
 
 react@^17.0.1:
   version "17.0.2"

programming_language: Shell