+): JSX.Element {
+ const {
+ sources,
+ highlightedInstruction,
+ highlightMode
+ } = useProgramExampleContext();
+
+ if (sources.length !== 1) {
+ throw new Error("Multiple sources per example not currently supported");
+ }
+
+ const source = sources[0];
+
+ const context = highlightedInstruction?.context;
+
+ const simpleDecorations = Program.Context.isCode(context)
+ ? decorateCodeContext(context, source)
+ : [];
+
+ const detailedDecorations = [
+ ...simpleDecorations,
+ ...(Program.Context.isVariables(context)
+ ? decorateVariablesContext(context, source)
+ : []
+ )
+ ];
+
+ const decorations = highlightMode === "detailed"
+ ? detailedDecorations
+ : simpleDecorations;
+
+ return ;
+}
+
+function decorateCodeContext(
+ { code }: Program.Context.Code,
+ source: Materials.Source
+): Shiki.DecorationItem[] {
+ const { offset, length } = normalizeRange(code.range, source);
+
+ return [
+ {
+ start: offset,
+ end: offset + length,
+ properties: {
+ class: "highlighted-code"
+ }
+ }
+ ];
+}
+
+function decorateVariablesContext(
+ { variables }: Program.Context.Variables,
+ source: Materials.Source
+): Shiki.DecorationItem[] {
+
+ return variables.map(({ declaration }) => {
+ const { offset, length } = normalizeRange(declaration?.range, source);
+ return {
+ start: offset,
+ end: offset + length,
+ properties: {
+ class: "highlighted-variable-declaration"
+ }
+ };
+ });
+}
+
+function normalizeRange(
+ range: Materials.SourceRange["range"],
+ source: Materials.Source
+): Materials.SourceRange["range"] & { offset: number; length: number } {
+ const { offset, length } = range
+ ? { offset: Number(range.offset), length: Number(range.length) }
+ : { offset: 0, length: source.contents.length };
+
+ return { offset, length };
+}
diff --git a/packages/web/src/theme/ProgramExample/Variables.tsx b/packages/web/src/theme/ProgramExample/Variables.tsx
new file mode 100644
index 000000000..fbdf8c873
--- /dev/null
+++ b/packages/web/src/theme/ProgramExample/Variables.tsx
@@ -0,0 +1,83 @@
+import React, { useEffect, useState } from "react";
+import Admonition from "@theme/Admonition";
+import Link from "@docusaurus/Link";
+import { useProgramExampleContext } from "./ProgramExampleContext";
+
+import { Program } from "@ethdebug/format";
+import { ShikiCodeBlock } from "@theme/ShikiCodeBlock";
+
+export function Variables(): JSX.Element {
+ const { highlightedInstruction } = useProgramExampleContext();
+
+ const link =
+ ethdebug/format/program Variables context schema
+ ;
+
+ if (highlightedInstruction === undefined) {
+ return
+ Hover or click on an offset above to see the {link} object
+ for that instruction.
+ ;
+ }
+
+ const { context } = highlightedInstruction;
+ if (!(context && "variables" in context)) {
+ return
+ The highlighted instruction does not specify any variables in context
+ information. See other tab for full instruction object.
+ ;
+ }
+
+ const { variables } = context;
+
+ if (!variables.every(variable => "identifier" in variable)) {
+ throw new Error(
+ "Unnamed variables are currently unsupported by this documentation system"
+ );
+ }
+
+ return <>
+
+ The following is the {link} object for the selected instruction.
+
+
+ {
+ variables.map((variable) =>
+
+ )
+ }
+ >;
+}
+
+interface VariableProps {
+ variable: Program.Context.Variables.Variable;
+}
+
+function Variable(props: VariableProps): JSX.Element {
+ const { variable } = props;
+
+ const details = (["type", "pointer"] as const)
+ .filter(detail => detail in variable)
+ .map((detail) =>
+
{`${detail.slice(0,1).toUpperCase()}${detail.slice(1)}`}
+
+
+ )
+ return <>
+
+ {variable.identifier}
+
+
+
+ {details}
+
+
+ >;
+}
diff --git a/packages/web/src/theme/ProgramExample/Viewer.css b/packages/web/src/theme/ProgramExample/Viewer.css
new file mode 100644
index 000000000..db4d60832
--- /dev/null
+++ b/packages/web/src/theme/ProgramExample/Viewer.css
@@ -0,0 +1,15 @@
+
+.viewer-row {
+ display: flex;
+
+ justify-content: space-between;
+ align-items: stretch;
+
+ width: 100%;
+ gap: 5px;
+
+}
+
+.viewer-row > * {
+ flex-grow:1;
+}
diff --git a/packages/web/src/theme/ProgramExample/Viewer.tsx b/packages/web/src/theme/ProgramExample/Viewer.tsx
new file mode 100644
index 000000000..91fd137be
--- /dev/null
+++ b/packages/web/src/theme/ProgramExample/Viewer.tsx
@@ -0,0 +1,72 @@
+import Admonition from "@theme/Admonition";
+import Link from "@docusaurus/Link";
+import { useProgramExampleContext } from "./ProgramExampleContext";
+import { SourceContents } from "./SourceContents";
+import { Opcodes } from "./Opcodes";
+import { HighlightedInstruction } from "./HighlightedInstruction";
+import { Variables } from "./Variables";
+
+import "./Viewer.css";
+import "./SourceContents.css";
+
+export interface Props {
+}
+
+export function Viewer(props: Props): JSX.Element {
+ const { highlightedInstruction, highlightMode } = useProgramExampleContext();
+
+ const basicAdmonition =
+ Select an instruction offset to see associated
+ ethdebug/format
+ debugging information.
+ ;
+
+ const detailedAdmonition =
+
+ The selected instruction provides the following
+ ethdebug/format Program contexts
+ :
+
+
+ -
+ Code context is highlighted in this
+ style above.
+
+ -
+ Variables context is indicated by variable declarations
+ highlighted in this
+ style above.
+
+
+ ;
+
+ const details = highlightedInstruction && highlightMode === "detailed"
+ ? <>
+ Details
+ {detailedAdmonition}
+
+ See full ethdebug/format/program/instruction object
+
+
+ >
+ : <>
+ Details
+ {basicAdmonition}
+ >;
+
+
+ return <>
+ Interactive example
+
+
+
Source contents
+
+
+
+
Compiled opcodes
+
+
+
+ {details}
+ >;
+}
diff --git a/packages/web/src/theme/ProgramExample/dynamic.ts b/packages/web/src/theme/ProgramExample/dynamic.ts
new file mode 100644
index 000000000..b8bc6a20b
--- /dev/null
+++ b/packages/web/src/theme/ProgramExample/dynamic.ts
@@ -0,0 +1,94 @@
+import { Program, Materials } from "@ethdebug/format";
+
+export type DynamicInstruction =
+ & Omit
+ & { operation: Program.Instruction.Operation; }
+ & { context: DynamicContext; };
+
+export type DynamicContext =
+ | Program.Context
+ | ContextThunk;
+
+export type ContextThunk = (props: {
+ findSourceRange(
+ query: string,
+ options?: FindSourceRangeOptions
+ ): Materials.SourceRange | undefined;
+}) => Program.Context;
+
+export interface FindSourceRangeOptions {
+ source?: Materials.Reference;
+ after?: string;
+}
+
+export interface ResolverOptions {
+ sources: Materials.Source[];
+}
+
+export function resolveDynamicInstruction(
+ dynamicInstruction: DynamicInstruction,
+ options: ResolverOptions
+): Program.Instruction {
+ const context = resolveDynamicContext(
+ dynamicInstruction.context,
+ options
+ );
+
+ const instruction = {
+ ...dynamicInstruction,
+ context
+ };
+
+ return instruction;
+}
+
+function resolveDynamicContext(
+ context: DynamicContext,
+ { sources }: ResolverOptions
+): Program.Context {
+ if (typeof context !== "function") {
+ return context;
+ }
+
+ const findSourceRange = (
+ query: string,
+ options: FindSourceRangeOptions = {}
+ ) => {
+ const source = "source" in options && options.source
+ ? sources.find(source => source.id === options.source?.id)
+ : sources[0];
+
+ if (!source) {
+ return;
+ }
+
+ const afterQuery = options.after || "";
+
+ const afterQueryOffset = source.contents.indexOf(afterQuery);
+ if (afterQueryOffset === -1) {
+ throw new Error(
+ `Unexpected could not find string ${options.after} as prior occurrence to ${query}`
+ );
+ }
+
+
+ const startOffset = afterQueryOffset + afterQuery.length;
+
+ const offset = source.contents.indexOf(query, startOffset);
+ if (offset === -1) {
+ throw new Error(`Unexpected could not find string ${query}`);
+ }
+
+ const length = query.length;
+
+ return {
+ source: { id: source.id },
+ range: {
+ offset,
+ length
+ }
+ };
+ };
+
+ return context({ findSourceRange });
+}
diff --git a/packages/web/src/theme/ProgramExample/index.ts b/packages/web/src/theme/ProgramExample/index.ts
new file mode 100644
index 000000000..a7901da1a
--- /dev/null
+++ b/packages/web/src/theme/ProgramExample/index.ts
@@ -0,0 +1,7 @@
+export * from "./ProgramExampleContext";
+export * from "./SourceContents";
+export * from "./Opcodes";
+export * from "./HighlightedInstruction";
+
+export * from "./Viewer";
+
diff --git a/packages/web/src/theme/ProgramExample/offsets.ts b/packages/web/src/theme/ProgramExample/offsets.ts
new file mode 100644
index 000000000..bb112d61d
--- /dev/null
+++ b/packages/web/src/theme/ProgramExample/offsets.ts
@@ -0,0 +1,59 @@
+import { Data, Program } from "@ethdebug/format";
+
+// define base generic instruction since other parts of this module
+// allow dynamic contexts and such
+interface OffsetComputableInstruction {
+ operation: Program.Instruction.Operation;
+}
+
+type OffsetComputedInstruction =
+ & I
+ & { offset: Data.Value; };
+
+export function computeOffsets(
+ instructions: I[]
+): OffsetComputedInstruction[] {
+ const initialResults: {
+ nextOffset: number;
+ results: OffsetComputedInstruction[];
+ } = {
+ nextOffset: 0,
+ results: []
+ };
+
+ const {
+ results
+ } = instructions.reduce(
+ ({ nextOffset, results }, instruction) => {
+ const result = {
+ offset: nextOffset,
+ ...instruction
+ };
+
+ const operationSize = (
+ 1 /* for opcode */ +
+ Math.ceil(
+ (instruction.operation.arguments || [])
+ .map(
+ value => typeof value === "number"
+ ? value.toString(16)
+ : value.slice(2)
+ )
+ .join("")
+ .length / 2
+ )
+ );
+
+ return {
+ nextOffset: nextOffset + operationSize,
+ results: [
+ ...results,
+ result
+ ]
+ };
+ },
+ initialResults
+ );
+
+ return results;
+}
diff --git a/packages/web/src/theme/ShikiCodeBlock/ShikiCodeBlock.tsx b/packages/web/src/theme/ShikiCodeBlock/ShikiCodeBlock.tsx
new file mode 100644
index 000000000..ef68a2cb6
--- /dev/null
+++ b/packages/web/src/theme/ShikiCodeBlock/ShikiCodeBlock.tsx
@@ -0,0 +1,25 @@
+import React from "react";
+import {
+ type Highlighter,
+ type HighlightOptions,
+ useHighlighter
+} from "./useHighlighter";
+
+export interface Props extends HighlightOptions {
+ code: string;
+}
+
+export function ShikiCodeBlock({
+ code,
+ ...highlightOptions
+}: Props): JSX.Element {
+ const highlighter = useHighlighter();
+
+ if (!highlighter) {
+ return <>Loading...>;
+ }
+
+ const html = highlighter.highlight(code, highlightOptions);
+
+ return ;
+}
diff --git a/packages/web/src/theme/ShikiCodeBlock/index.ts b/packages/web/src/theme/ShikiCodeBlock/index.ts
new file mode 100644
index 000000000..987f6d64f
--- /dev/null
+++ b/packages/web/src/theme/ShikiCodeBlock/index.ts
@@ -0,0 +1,6 @@
+export * from "./useHighlighter";
+export * from "./ShikiCodeBlock";
+
+import { ShikiCodeBlock } from "./ShikiCodeBlock";
+
+export default ShikiCodeBlock;
diff --git a/packages/web/src/theme/ShikiCodeBlock/useHighlighter.ts b/packages/web/src/theme/ShikiCodeBlock/useHighlighter.ts
new file mode 100644
index 000000000..c81735a70
--- /dev/null
+++ b/packages/web/src/theme/ShikiCodeBlock/useHighlighter.ts
@@ -0,0 +1,48 @@
+import { useEffect, useState } from "react";
+
+import * as Shiki from "shiki/core";
+import { createOnigurumaEngine } from "shiki/engine/oniguruma";
+
+export interface Highlighter {
+ highlight(text: string, options: HighlightOptions): string;
+}
+
+export interface HighlightOptions {
+ language?: string;
+ decorations?: Shiki.DecorationItem[];
+}
+
+export function useHighlighter() {
+ const [highlighter, setHighlighter] = useState();
+
+ useEffect(() => {
+ createHighlighter().then(setHighlighter)
+ }, [setHighlighter]);
+
+ return highlighter;
+}
+
+async function createHighlighter(): Promise {
+ const shiki = await Shiki.createHighlighterCore({
+ themes: [
+ import("@shikijs/themes/github-light"),
+ ],
+ langs: [
+ import("@shikijs/langs/solidity"),
+ import("@shikijs/langs/javascript"),
+ ],
+ engine: createOnigurumaEngine(import("shiki/wasm"))
+ });
+
+ const themeName = "github-light";
+
+ return {
+ highlight(text, { language, decorations }) {
+ return shiki.codeToHtml(text, {
+ lang: language || "text",
+ theme: themeName,
+ decorations
+ })
+ }
+ };
+}
diff --git a/yarn.lock b/yarn.lock
index 7373c649c..f1a6cd1de 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3506,6 +3506,62 @@
"@noble/hashes" "~1.4.0"
"@scure/base" "~1.1.6"
+"@shikijs/core@2.2.0":
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/@shikijs/core/-/core-2.2.0.tgz#3cdc4a4463dde8c4d3bb0a42e39376d47ac8b2ce"
+ integrity sha512-U+vpKdsQDWuX3fPTCkSc8XPX9dCaS+r+qEP1XhnU30yxRFo2OxHJmY2H5rO1q+v0zB5R2vobsxEFt5uPf31CGQ==
+ dependencies:
+ "@shikijs/engine-javascript" "2.2.0"
+ "@shikijs/engine-oniguruma" "2.2.0"
+ "@shikijs/types" "2.2.0"
+ "@shikijs/vscode-textmate" "^10.0.1"
+ "@types/hast" "^3.0.4"
+ hast-util-to-html "^9.0.4"
+
+"@shikijs/engine-javascript@2.2.0":
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/@shikijs/engine-javascript/-/engine-javascript-2.2.0.tgz#7427a0e91c0c233111ca66076b9197872378b3c5"
+ integrity sha512-96SpZ4V3UVMtpSPR5QpmU395CNrQiRPszXK62m8gKR2HMA0653ruce7omS5eX6EyAyFSYHvBWtTuspiIsHpu4A==
+ dependencies:
+ "@shikijs/types" "2.2.0"
+ "@shikijs/vscode-textmate" "^10.0.1"
+ oniguruma-to-es "^2.3.0"
+
+"@shikijs/engine-oniguruma@2.2.0":
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/@shikijs/engine-oniguruma/-/engine-oniguruma-2.2.0.tgz#9745f322571752ca5ab4f8393df24057f6f36e21"
+ integrity sha512-wowCKwkvPFFMXFkiKK/a2vs5uTCc0W9+O9Xcu/oqFP6VoDFe14T8u/D+Rl4dCJJSOyeynP9mxNPJ82T5JHTNCw==
+ dependencies:
+ "@shikijs/types" "2.2.0"
+ "@shikijs/vscode-textmate" "^10.0.1"
+
+"@shikijs/langs@2.2.0":
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/@shikijs/langs/-/langs-2.2.0.tgz#fbd3a25a8bef754a83034d3d62b66252b96b8c74"
+ integrity sha512-RSWLH3bnoyG6O1kZ2msh5jOkKKp8eENwyT30n62vUtXfp5cxkF/bpWPpO+p4+GAPhL2foBWR2kOerwkKG0HXlQ==
+ dependencies:
+ "@shikijs/types" "2.2.0"
+
+"@shikijs/themes@2.2.0", "@shikijs/themes@^2.2.0":
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/@shikijs/themes/-/themes-2.2.0.tgz#0792df404b413836c4805611382f10a7bd08c843"
+ integrity sha512-8Us9ZF2mV9kuh+4ySJ9MzrUDIpc2RIkRfKBZclkliW1z9a0PlGU2U7fCkItZZHpR5e4/ft5BzuO+GDqombC6Aw==
+ dependencies:
+ "@shikijs/types" "2.2.0"
+
+"@shikijs/types@2.2.0":
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/@shikijs/types/-/types-2.2.0.tgz#3953175a58e3ac0b25dae508fc4a85e5b5e3cc30"
+ integrity sha512-wkZZKs80NtW5Jp/7ONI1j7EdXSatX2BKMS7I01wliDa09gJKHkZyVqlEMRka/mjT5Qk9WgAyitoCKgGgbsP/9g==
+ dependencies:
+ "@shikijs/vscode-textmate" "^10.0.1"
+ "@types/hast" "^3.0.4"
+
+"@shikijs/vscode-textmate@^10.0.1":
+ version "10.0.1"
+ resolved "https://registry.yarnpkg.com/@shikijs/vscode-textmate/-/vscode-textmate-10.0.1.tgz#d06d45b67ac5e9b0088e3f67ebd3f25c6c3d711a"
+ integrity sha512-fTIQwLF+Qhuws31iw7Ncl1R3HUDtGwIipiJ9iU+UsDUwMhegFcQKQHd51nZjb7CArq0MvON8rbgCGQYWHUKAdg==
+
"@sideway/address@^4.1.3":
version "4.1.4"
resolved "https://registry.npmjs.org/@sideway/address/-/address-4.1.4.tgz"
@@ -4007,6 +4063,13 @@
dependencies:
"@types/unist" "*"
+"@types/hast@^3.0.4":
+ version "3.0.4"
+ resolved "https://registry.yarnpkg.com/@types/hast/-/hast-3.0.4.tgz#1d6b39993b82cea6ad783945b0508c25903e15aa"
+ integrity sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==
+ dependencies:
+ "@types/unist" "*"
+
"@types/history@^4.7.11":
version "4.7.11"
resolved "https://registry.npmjs.org/@types/history/-/history-4.7.11.tgz"
@@ -6519,6 +6582,11 @@ emittery@^0.13.1:
resolved "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz"
integrity sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==
+emoji-regex-xs@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/emoji-regex-xs/-/emoji-regex-xs-1.0.0.tgz#e8af22e5d9dbd7f7f22d280af3d19d2aab5b0724"
+ integrity sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg==
+
emoji-regex@^8.0.0:
version "8.0.0"
resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz"
@@ -7682,6 +7750,23 @@ hast-util-to-estree@^3.0.0:
unist-util-position "^5.0.0"
zwitch "^2.0.0"
+hast-util-to-html@^9.0.4:
+ version "9.0.4"
+ resolved "https://registry.yarnpkg.com/hast-util-to-html/-/hast-util-to-html-9.0.4.tgz#d689c118c875aab1def692c58603e34335a0f5c5"
+ integrity sha512-wxQzXtdbhiwGAUKrnQJXlOPmHnEehzphwkK7aluUPQ+lEc1xefC8pblMgpp2w5ldBTEfveRIrADcrhGIWrlTDA==
+ dependencies:
+ "@types/hast" "^3.0.0"
+ "@types/unist" "^3.0.0"
+ ccount "^2.0.0"
+ comma-separated-tokens "^2.0.0"
+ hast-util-whitespace "^3.0.0"
+ html-void-elements "^3.0.0"
+ mdast-util-to-hast "^13.0.0"
+ property-information "^6.0.0"
+ space-separated-tokens "^2.0.0"
+ stringify-entities "^4.0.0"
+ zwitch "^2.0.4"
+
hast-util-to-jsx-runtime@^2.0.0:
version "2.3.0"
resolved "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.0.tgz"
@@ -10903,6 +10988,15 @@ onetime@^5.1.0, onetime@^5.1.2:
dependencies:
mimic-fn "^2.1.0"
+oniguruma-to-es@^2.3.0:
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/oniguruma-to-es/-/oniguruma-to-es-2.3.0.tgz#35ea9104649b7c05f3963c6b3b474d964625028b"
+ integrity sha512-bwALDxriqfKGfUufKGGepCzu9x7nJQuoRoAFp4AnwehhC2crqrDIAP/uN2qdlsAvSMpeRC3+Yzhqc7hLmle5+g==
+ dependencies:
+ emoji-regex-xs "^1.0.0"
+ regex "^5.1.1"
+ regex-recursion "^5.1.1"
+
open@^8.0.9, open@^8.4.0:
version "8.4.2"
resolved "https://registry.npmjs.org/open/-/open-8.4.2.tgz"
@@ -12199,6 +12293,26 @@ regenerator-transform@^0.15.2:
dependencies:
"@babel/runtime" "^7.8.4"
+regex-recursion@^5.1.1:
+ version "5.1.1"
+ resolved "https://registry.yarnpkg.com/regex-recursion/-/regex-recursion-5.1.1.tgz#5a73772d18adbf00f57ad097bf54171b39d78f8b"
+ integrity sha512-ae7SBCbzVNrIjgSbh7wMznPcQel1DNlDtzensnFxpiNpXt1U2ju/bHugH422r+4LAVS1FpW1YCwilmnNsjum9w==
+ dependencies:
+ regex "^5.1.1"
+ regex-utilities "^2.3.0"
+
+regex-utilities@^2.3.0:
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/regex-utilities/-/regex-utilities-2.3.0.tgz#87163512a15dce2908cf079c8960d5158ff43280"
+ integrity sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==
+
+regex@^5.1.1:
+ version "5.1.1"
+ resolved "https://registry.yarnpkg.com/regex/-/regex-5.1.1.tgz#cf798903f24d6fe6e531050a36686e082b29bd03"
+ integrity sha512-dN5I359AVGPnwzJm2jN1k0W9LPZ+ePvoOeVMMfqIMFz53sSwXkxaJoxr50ptnsC771lK95BnTrVSZxq0b9yCGw==
+ dependencies:
+ regex-utilities "^2.3.0"
+
regexpu-core@^5.3.1:
version "5.3.2"
resolved "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.3.2.tgz"
@@ -12734,6 +12848,20 @@ shelljs@^0.8.5:
interpret "^1.0.0"
rechoir "^0.6.2"
+shiki@^2.2.0:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/shiki/-/shiki-2.2.0.tgz#3dab8ea41bb11a129acb70e858102df72ac73fb1"
+ integrity sha512-3uoZBmc+zpd2JOEeTvKP/vK5UVDDe8YiigkT9flq+MV5Z1MKFiUXfbLIvHfqcJ+V90StDiP1ckN97z1WlhC6cQ==
+ dependencies:
+ "@shikijs/core" "2.2.0"
+ "@shikijs/engine-javascript" "2.2.0"
+ "@shikijs/engine-oniguruma" "2.2.0"
+ "@shikijs/langs" "2.2.0"
+ "@shikijs/themes" "2.2.0"
+ "@shikijs/types" "2.2.0"
+ "@shikijs/vscode-textmate" "^10.0.1"
+ "@types/hast" "^3.0.4"
+
side-channel@^1.0.4:
version "1.0.4"
resolved "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz"
@@ -14369,7 +14497,7 @@ yocto-queue@^1.0.0:
resolved "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz"
integrity sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==
-zwitch@^2.0.0:
+zwitch@^2.0.0, zwitch@^2.0.4:
version "2.0.4"
resolved "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz"
integrity sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==