{"version":3,"file":"twoslash.esm.js","sources":["../src/utils.ts","../src/validation.ts","../src/index.ts"],"sourcesContent":["import { TwoslashError } from \"./\"\n\nexport function escapeHtml(text: string) {\n return text.replace(/ = {\n js: \"js\",\n javascript: \"js\",\n ts: \"ts\",\n typescript: \"ts\",\n tsx: \"tsx\",\n jsx: \"jsx\",\n json: \"json\",\n jsn: \"json\",\n }\n\n if (map[types]) return map[types]\n\n throw new TwoslashError(\n `Unknown TypeScript extension given to Twoslash`,\n `Received ${types} but Twoslash only accepts: ${Object.keys(map)} `,\n ``\n )\n}\n\nexport function getIdentifierTextSpans(ts: typeof import(\"typescript\"), sourceFile: import(\"typescript\").SourceFile) {\n const textSpans: { span: import(\"typescript\").TextSpan; text: string }[] = []\n checkChildren(sourceFile)\n return textSpans\n\n function checkChildren(node: import(\"typescript\").Node) {\n ts.forEachChild(node, child => {\n if (ts.isIdentifier(child)) {\n const start = child.getStart(sourceFile, false)\n textSpans.push({ span: ts.createTextSpan(start, child.end - start), text: child.getText(sourceFile) })\n }\n checkChildren(child)\n })\n }\n}\n\nexport function stringAroundIndex(string: string, index: number) {\n const arr = [\n string[index - 3],\n string[index - 2],\n string[index - 1],\n \">\",\n string[index],\n \"<\",\n string[index + 1],\n string[index + 2],\n string[index + 3],\n ]\n return arr.filter(Boolean).join(\"\")\n}\n\n/** Came from https://ourcodeworld.com/articles/read/223/how-to-retrieve-the-closest-word-in-a-string-with-a-given-index-in-javascript */\nexport function getClosestWord(str: string, pos: number) {\n // Make copies\n str = String(str)\n pos = Number(pos) >>> 0\n\n // Search for the word's beginning and end.\n var left = str.slice(0, pos + 1).search(/\\S+$/),\n right = str.slice(pos).search(/\\s/)\n\n // The last word in the string is a special case.\n if (right < 0) {\n return {\n word: str.slice(left),\n startPos: left,\n }\n }\n // Return the word, using the located bounds to extract it from the string.\n return {\n word: str.slice(left, right + pos),\n startPos: left,\n }\n}\n","import { TwoslashError } from \"./\"\n\n/** To ensure that errors are matched up right */\nexport function validateCodeForErrors(\n relevantErrors: import(\"typescript\").Diagnostic[],\n handbookOptions: { errors: number[] },\n extension: string,\n originalCode: string,\n vfsRoot: string\n) {\n const inErrsButNotFoundInTheHeader = relevantErrors.filter(e => !handbookOptions.errors.includes(e.code))\n const errorsFound = Array.from(new Set(inErrsButNotFoundInTheHeader.map(e => e.code))).join(\" \")\n\n if (inErrsButNotFoundInTheHeader.length) {\n const errorsToShow = new Set(relevantErrors.map(e => e.code))\n const codeToAdd = `// @errors: ${Array.from(errorsToShow).join(\" \")}`\n\n const missing = handbookOptions.errors.length\n ? `\\nThe existing annotation specified ${handbookOptions.errors.join(\" \")}`\n : \"\\nExpected: \" + codeToAdd\n\n // These get filled by below\n const filesToErrors: Record = {}\n const noFiles: import(\"typescript\").Diagnostic[] = []\n\n inErrsButNotFoundInTheHeader.forEach(d => {\n const fileRef = d.file?.fileName && d.file.fileName.replace(vfsRoot, \"\")\n if (!fileRef) noFiles.push(d)\n else {\n const existing = filesToErrors[fileRef]\n if (existing) existing.push(d)\n else filesToErrors[fileRef] = [d]\n }\n })\n\n const showDiagnostics = (title: string, diags: import(\"typescript\").Diagnostic[]) => {\n return (\n `${title}\\n ` +\n diags\n .map(e => {\n const msg = typeof e.messageText === \"string\" ? e.messageText : e.messageText.messageText\n return `[${e.code}] ${e.start} - ${msg}`\n })\n .join(\"\\n \")\n )\n }\n\n const innerDiags: string[] = []\n if (noFiles.length) {\n innerDiags.push(showDiagnostics(\"Ambient Errors\", noFiles))\n }\n Object.keys(filesToErrors).forEach(filepath => {\n innerDiags.push(showDiagnostics(filepath, filesToErrors[filepath]))\n })\n\n const allMessages = innerDiags.join(\"\\n\\n\")\n\n const newErr = new TwoslashError(\n `Errors were thrown in the sample, but not included in an errors tag`,\n `These errors were not marked as being expected: ${errorsFound}. ${missing}`,\n `Compiler Errors:\\n\\n${allMessages}`\n )\n\n newErr.code = `## Code\\n\\n'''${extension}\\n${originalCode}\\n'''`\n throw newErr\n }\n}\n\n/** Mainly to warn myself, I've lost a good few minutes to this before */\nexport function validateInput(code: string) {\n if (code.includes(\"// @errors \")) {\n throw new TwoslashError(\n `You have '// @errors ' (with a space)`,\n `You want '// @errors: ' (with a colon)`,\n `This is a pretty common typo`\n )\n }\n\n if (code.includes(\"// @filename \")) {\n throw new TwoslashError(\n `You have '// @filename ' (with a space)`,\n `You want '// @filename: ' (with a colon)`,\n `This is a pretty common typo`\n )\n }\n}\n","let hasLocalStorage = false\ntry {\n hasLocalStorage = typeof localStorage !== `undefined`\n} catch (error) {}\nconst hasProcess = typeof process !== `undefined`\nconst shouldDebug = (hasLocalStorage && localStorage.getItem(\"DEBUG\")) || (hasProcess && process.env.DEBUG)\n\ntype LZ = typeof import(\"lz-string\")\ntype TS = typeof import(\"typescript\")\ntype CompilerOptions = import(\"typescript\").CompilerOptions\ntype CustomTransformers = import(\"typescript\").CustomTransformers\n\nimport { parsePrimitive, cleanMarkdownEscaped, typesToExtension, getIdentifierTextSpans, getClosestWord } from \"./utils\"\nimport { validateInput, validateCodeForErrors } from \"./validation\"\n\nimport { createSystem, createVirtualTypeScriptEnvironment, createFSBackedSystem } from \"@typescript/vfs\"\n\nconst log = shouldDebug ? console.log : (_message?: any, ..._optionalParams: any[]) => \"\"\n\n// Hacking in some internal stuff\ndeclare module \"typescript\" {\n type Option = {\n name: string\n type: \"list\" | \"boolean\" | \"number\" | \"string\" | import(\"typescript\").Map\n element?: Option\n }\n\n const optionDeclarations: Array