{"version":3,"file":"shiki-twoslash.cjs.development.js","sources":["../src/annotations.ts","../src/utils.ts","../src/renderers/plain.ts","../src/renderers/twoslash.ts","../src/renderers/shiki.ts","../src/tsconfig-oneliners.generated.ts","../src/renderers/tsconfig.ts","../src/index.ts"],"sourcesContent":["import { TwoslashError, TwoSlashReturn } from \"@typescript/twoslash\"\n\nexport const htmlForTags = (tags: TwoSlashReturn[\"tags\"]) => {\n let html = \"\"\n tags.forEach(t => {\n if (t.name === \"annotate\" && t.annotation) {\n const meta = t.annotation.split(\" - \")\n const text = meta.pop()\n const info = (meta[0] || \"\").trim()\n const flipped = info.includes(\"right\")\n let settings = {\n flipped,\n arrowRot: flipped ? \"90deg 20px 20px\" : \"90deg 20px 20px\",\n textDegree: \"0deg\",\n top: `${t.line}em`\n }\n \n \n if (info.includes(\"{\")) {\n const theInfo = \"{\" + info.split(\"{\")[1]\n try {\n const specificSettings = JSON.parse(theInfo)\n settings = {...settings, ...specificSettings }\n } catch (error) {\n throw new TwoslashError(\"Could not parse annotation\", `The annotation ${JSON.stringify(t)} could convert '${theInfo}' into JSON`, `Look at ${(error as any).message}.`)\n }\n }\n \n const arrowSVG = arrow(settings)\n\n html += `\n
${text}
\n*/\nexport const preOpenerFromRenderingOptsWithExtras = (opts: HtmlRendererOptions, meta: Meta, classes?: string[]) => {\n const bg = opts.bg || \"#fff\"\n const fg = opts.fg || \"black\"\n const theme = opts.themeName || \"\"\n\n // shiki + `class` from fence + with-title if title exists + classes\n const classList = [\"shiki\", theme, meta.class, meta.title ? \"with-title\" : \"\", ...(classes || [])]\n .filter(Boolean)\n .join(\" \")\n .trim()\n\n const attributes = Object.entries(meta)\n .filter(entry => {\n // exclude types other than string, number, boolean\n // exclude keys class, twoslash\n // exclude falsy booleans\n return (\n [\"string\", \"number\", \"boolean\"].includes(typeof entry[1]) &&\n ![\"class\", \"twoslash\"].includes(entry[0]) &&\n entry[1] !== false\n )\n })\n .map(([key, value]) => `${key}=\"${value}\"`)\n .join(\" \")\n .trim()\n\n // prettier-ignore\n return ``\n\n // Attach annotations which live above of the code\n if (twoslash.tags && twoslash.tags.length) {\n html += htmlForTags(twoslash.tags)\n html += \"\"\n }\n\n return html\n}\n\n/** Returns a map where all the keys are the value in keyGetter */\nfunction groupBy`\n}\n\n/** You don't have a language which shiki twoslash can handle, make a DOM compatible version */\nexport function plainTextRenderer(code: string, options: HtmlRendererOptions, meta: Meta) {\n let html = \"\"\n\n html += preOpenerFromRenderingOptsWithExtras(options, meta, [])\n if (meta.title) {\n html += ``\n return html\n}\n","type Lines = import(\"shiki\").IThemedToken[][]\ntype TwoSlash = import(\"@typescript/twoslash\").TwoSlashReturn\n\nimport { TwoslashShikiOptions } from \"..\"\nimport { htmlForTags } from \"../annotations\"\nimport {\n shouldBeHighlightable,\n shouldHighlightLine,\n createHighlightedString,\n subTripleArrow,\n replaceTripleArrowEncoded,\n escapeHtml,\n Meta,\n} from \"../utils\"\nimport { HtmlRendererOptions, preOpenerFromRenderingOptsWithExtras } from \"./plain\"\n\n// OK, so - this is just straight up complex code.\n\n// What we're trying to do is merge two sets of information into a single tree for HTML\n\n// 1: Syntax highlight info from shiki\n// 2: Twoslash metadata like errors, identifiers etc\n\n// Because shiki gives use a set of lines to work from, then the first thing which happens\n// is converting twoslash data into the same format.\n\n// Things which make it hard:\n//\n// - Twoslash results can be cut, so sometimes there is edge cases between twoslash results\n// - Twoslash results can be multi-file\n// - the DOM requires a flattened graph of html elements (e.g. spans can' be interspersed)\n//\n\nexport function twoslashRenderer(lines: Lines, options: HtmlRendererOptions & TwoslashShikiOptions, twoslash: TwoSlash, meta: Meta) {\n let html = \"\"\n\n const hasHighlight = meta.highlight && shouldBeHighlightable(meta.highlight)\n const hl = shouldHighlightLine(meta.highlight)\n\n if (twoslash.tags && twoslash.tags.length) html += \"${meta.title}`\n }\n\n if (options.langId) {\n html += `${options.langId}`\n }\n\n html += ``\n html += escapeHtml(code)\n\n html = html.replace(/\\n*$/, \"\") // Get rid of final new lines\n html += `
\"\n \n html += preOpenerFromRenderingOptsWithExtras(options, meta, [\"twoslash\", \"lsp\"])\n if (meta.title) {\n html += `represents\n filePos += 1\n }\n\n // Adding error messages to the line after\n if (errors.length) {\n const messages = errors.map(e => escapeHtml(e.renderedMessage)).join(\"\")\n const codes = errors.map(e => e.code).join(\"${meta.title}`\n }\n\n if (options.langId) {\n html += `${options.langId}`\n }\n\n html += ``\n // This is the \\n which the`\n\n const errorsGroupedByLine = groupBy(twoslash.errors, e => e.line) || new Map()\n const staticQuickInfosGroupedByLine = groupBy(twoslash.staticQuickInfos, q => q.line) || new Map()\n // A query is always about the line above it!\n const queriesGroupedByLine = groupBy(twoslash.queries, q => q.line - 1) || new Map()\n const tagsGroupedByLine = groupBy(twoslash.tags, q => q.line - 1) || new Map()\n\n /**\n * This is the index of the original twoslash code reference, it is not\n * related to the HTML output\n */\n let filePos = 0\n\n lines.forEach((l, i) => {\n const errors = errorsGroupedByLine.get(i) || []\n const lspValues = staticQuickInfosGroupedByLine.get(i) || []\n const queries = queriesGroupedByLine.get(i) || []\n const tags = tagsGroupedByLine.get(i) || []\n\n const hiClass = hasHighlight ? (hl(i + 1) ? \" highlight\" : \" dim\") : \"\"\n const prefix = `
`\n\n if (l.length === 0 && i === 0) {\n // Skip the first newline if it's blank\n filePos += 1\n } else if (l.length === 0) {\n const emptyLine = `${prefix}` \n html += emptyLine\n filePos += 1\n } else {\n html += prefix\n\n // Keep track of the position of the current token in a line so we can match it up to the\n // errors and lang serv identifiers\n let tokenPos = 0\n\n l.forEach(token => {\n let targetedQueryWord: typeof twoslash.staticQuickInfos[number] | undefined\n\n let tokenContent = \"\"\n // Underlining particular words\n const findTokenFunc = (start: number) => (e: any) =>\n start <= e.character && start + token.content.length >= e.character + e.length\n\n const findTokenDebug = (start: number) => (e: any) => {\n const result = start <= e.character && start + token.content.length >= e.character + e.length\n // prettier-ignore\n console.log(result, start, '<=', e.character, '&&', start + token.content.length, '>=', e.character + e.length)\n if (result) {\n console.log(\"Found:\", e)\n console.log(\"Inside:\", token)\n }\n return result\n }\n\n const errorsInToken = errors.filter(findTokenFunc(tokenPos))\n const lspResponsesInToken = lspValues.filter(findTokenFunc(tokenPos))\n const queriesInToken = queries.filter(findTokenFunc(tokenPos))\n\n // Does this line have a word targeted by a query?\n targetedQueryWord = targetedQueryWord || lspResponsesInToken.find(response => response.text === (queries.length && queries[0].text))!\n\n const allTokens = [...errorsInToken, ...lspResponsesInToken, ...queriesInToken]\n const allTokensByStart = allTokens.sort((l, r) => {\n return (l.start || 0) - (r.start || 0)\n })\n\n if (allTokensByStart.length) {\n const ranges = allTokensByStart.map(token => {\n const range: any = {\n begin: token.start! - filePos,\n end: token.start! + token.length! - filePos,\n }\n\n // prettier-ignore\n if (range.begin < 0 || range.end < 0) {\n // prettier-ignore\n // throw new Error(`The begin range of a token is at a minus location, filePos:${filePos} current token: ${JSON.stringify(token, null, ' ')}\\n result: ${JSON.stringify(range, null, ' ')}`)\n }\n\n if (\"renderedMessage\" in token) range.classes = \"err\"\n if (\"kind\" in token) range.classes = token.kind\n if (\"targetString\" in token) {\n range.classes = \"lsp\"\n const lspText = options.includeJSDocInHover && token.docs ? `${token.docs}\\n\\n${token.text}` : token.text\n range[\"lsp\"] = lspText\n }\n return range\n })\n\n tokenContent += createHighlightedString(ranges, token.content, targetedQueryWord?.text)\n } else {\n tokenContent += subTripleArrow(token.content)\n }\n\n html += `${tokenContent}`\n tokenPos += token.content.length\n filePos += token.content.length\n })\n\n html += `
\")\n html += `${messages}${codes}`\n html += `${messages}`\n }\n\n // Add queries to the next line\n if (queries.length) {\n queries.forEach(query => {\n // This is used to wrap popovers and completions to improve styling options for users.\n html += ` \"\n })\n }\n\n // Any tags (currently that's warn/error/log)\n if (tags.length) {\n tags.forEach(tag => {\n if(![\"error\", \"warn\", \"log\"].includes(tag.name)) return\n\n // This is used to wrap popovers and completions to improve styling options for users.\n html += ` \"\n })\n }\n })\n html = replaceTripleArrowEncoded(html.replace(/\\n*$/, \"\")) // Get rid of final new lines\n\n if (options.addTryButton) {\n const playgroundLink = `Try`\n html += `${playgroundLink}`\n } else {\n html += ``\n }\n\n html += `
`\n\n lines.forEach((l, i) => {\n if (l.length === 0) {\n html += ``\n } else {\n const hiClass = hasHighlight ? (hl(i) ? \" highlight\" : \" dim\") : \"\"\n const prefix = ``\n html += prefix\n\n l.forEach(token => {\n html += `${escapeHtml(token.content)}`\n })\n html += ``\n }\n })\n\n html = html.replace(/\\n*$/, \"\") // Get rid of final new lines\n html += `
`\n\n lines.forEach(l => {\n if (l.length === 0) {\n html += ``\n } else {\n html += ``\n l.forEach(token => {\n // This means we're looking at a token which could be '\"module\"', '\"', '\"compilerOptions\"' etc\n if (tokenIsJSONKey(token) && isKeyInTSConfig(token)) {\n const key = token.content.slice(1, token.content.length - 1)\n const oneliner = (tsconfig as Record)[key]\n // prettier-ignore\n html += `\"`\n } else {\n html += ` \"${escapeHtml(token.content)}`\n }\n })\n html += ` `\n }\n })\n\n html = html.replace(/\\n*$/, \"\") // Get rid of final new lines\n html += `