diff --git a/.vscode/settings.json b/.vscode/settings.json index b1360f3d..a2ab32e9 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -8,8 +8,8 @@ // Auto fix "editor.codeActionsOnSave": { - "source.fixAll.eslint": true, - "source.organizeImports": false + "source.fixAll.eslint": "explicit", + "source.organizeImports": "never" }, // Silent the stylistic rules in you IDE, but still auto fix them diff --git a/packages/shikiji-twoslash/README.md b/packages/shikiji-twoslash/README.md index 92721515..18723fff 100644 --- a/packages/shikiji-twoslash/README.md +++ b/packages/shikiji-twoslash/README.md @@ -43,7 +43,7 @@ We provide two renderers built-in, while you can also create your own: This is the default renderer that aligns with the output of [`shiki-twoslash`](https://shikijs.github.io/twoslash/). -You might need to reference `shiki-twoslash`'s CSS to make them look good. [Here](./style-shiki-twoslash.css) we also copied the CSS from `shiki-twoslash` but it might need some cleanup. +You might need to reference `shiki-twoslash`'s CSS to make them look good. [Here](./style-classic.css) we also copied the CSS from `shiki-twoslash` but it might need some cleanup. ### `rendererRich` @@ -55,7 +55,7 @@ This renderer provides a more explicit class name that is always prefixed with ` import { rendererRich, transformerTwoSlash } from 'shikiji-twoslash' transformerTwoSlash({ - renderer: rendererRich // <-- + renderer: rendererRich() // <-- }) ``` diff --git a/packages/shikiji-twoslash/package.json b/packages/shikiji-twoslash/package.json index 83dd1320..6052581e 100644 --- a/packages/shikiji-twoslash/package.json +++ b/packages/shikiji-twoslash/package.json @@ -23,7 +23,7 @@ "default": "./dist/index.mjs" }, "./style-rich.css": "./style-rich.css", - "./style-shiki-twoslash.css": "./style-shiki-twoslash.css", + "./style-classic.css": "./style-classic.css", "./*": "./dist/*" }, "main": "./dist/index.mjs", @@ -47,12 +47,19 @@ "prepublishOnly": "nr build", "test": "vitest" }, + "peerDependencies": { + "typescript": "^5.0.0" + }, "dependencies": { "@typescript/twoslash": "^3.2.4", "shikiji": "workspace:*" }, "devDependencies": { + "@iconify-json/carbon": "^1.1.26", + "@iconify-json/codicon": "^1.1.39", + "hast-util-from-html": "^2.0.1", "shiki": "^0.14.6", - "shiki-twoslash": "^3.1.2" + "shiki-twoslash": "^3.1.2", + "typescript": "^5.3.3" } } diff --git a/packages/shikiji-twoslash/scripts/icons.ts b/packages/shikiji-twoslash/scripts/icons.ts new file mode 100644 index 00000000..f3096529 --- /dev/null +++ b/packages/shikiji-twoslash/scripts/icons.ts @@ -0,0 +1,55 @@ +import fs from 'node:fs/promises' +import { icons as codicon } from '@iconify-json/codicon' +import { icons as carbon } from '@iconify-json/carbon' +import { fromHtml } from 'hast-util-from-html' +import type { TwoSlashReturn } from '@typescript/twoslash' + +type CompletionItem = NonNullable[0] + +async function buildIcons(filepath: string, map: Record) { + const result = Object.fromEntries( + Object.entries(map).map(([key, value]) => { + const iconset = value.startsWith('codicon:') ? codicon : carbon + const icon = iconset.icons[value.split(':')[1]] + if (!icon) + throw new Error(`icon not found: ${value}`) + const str = `${icon.body}` + const hast = fromHtml(str, { space: 'svg', fragment: true }).children[0] + return [key, hast] + }), + ) + + await fs.writeFile( + filepath, + `${JSON.stringify(result, (r, v) => { + if (v?.position) + delete v.position + return v + }, 2)}\n`, + 'utf-8', + ) +} + +await buildIcons( + './src/icons-completions.json', + { + module: 'carbon:3d-mpr-toggle', + class: 'carbon:data-class', + method: 'carbon:function', + property: 'carbon:tools', + constructor: 'carbon:3d-software', + interface: 'carbon:connect', + function: 'carbon:function', + string: 'carbon:string-text', + } satisfies Partial>, +) + +await buildIcons( + './src/icons-tags.json', + { + log: 'carbon:information-square', + error: 'carbon:warning', + warn: 'carbon:warning-alt', + annotate: 'carbon:idea', + }, +) diff --git a/packages/shikiji-twoslash/src/icons-completions.json b/packages/shikiji-twoslash/src/icons-completions.json new file mode 100644 index 00000000..cd58965c --- /dev/null +++ b/packages/shikiji-twoslash/src/icons-completions.json @@ -0,0 +1,227 @@ +{ + "module": { + "type": "element", + "tagName": "svg", + "properties": { + "viewBox": "0 0 32 32" + }, + "children": [ + { + "type": "element", + "tagName": "path", + "properties": { + "d": "M11 2H2v9h2V4h7V2z", + "fill": "currentColor" + }, + "children": [] + }, + { + "type": "element", + "tagName": "path", + "properties": { + "d": "M2 21v9h9v-2H4v-7H2z", + "fill": "currentColor" + }, + "children": [] + }, + { + "type": "element", + "tagName": "path", + "properties": { + "d": "M30 11V2h-9v2h7v7h2z", + "fill": "currentColor" + }, + "children": [] + }, + { + "type": "element", + "tagName": "path", + "properties": { + "d": "M21 30h9v-9h-2v7h-7v2z", + "fill": "currentColor" + }, + "children": [] + }, + { + "type": "element", + "tagName": "path", + "properties": { + "d": "M25.49 10.13l-9-5a1 1 0 0 0-1 0l-9 5A1 1 0 0 0 6 11v10a1 1 0 0 0 .51.87l9 5a1 1 0 0 0 1 0l9-5A1 1 0 0 0 26 21V11a1 1 0 0 0-.51-.87zM16 7.14L22.94 11L16 14.86L9.06 11zM8 12.7l7 3.89v7.71l-7-3.89zm9 11.6v-7.71l7-3.89v7.71z", + "fill": "currentColor" + }, + "children": [] + } + ] + }, + "class": { + "type": "element", + "tagName": "svg", + "properties": { + "viewBox": "0 0 32 32" + }, + "children": [ + { + "type": "element", + "tagName": "path", + "properties": { + "fill": "currentColor", + "d": "M26 16a3.961 3.961 0 0 0-2.02.566l-2.859-2.859l2.293-2.293a2 2 0 0 0 0-2.828l-6-6a2 2 0 0 0-2.828 0l-6 6a2 2 0 0 0 0 2.828l2.293 2.293l-2.859 2.859a4.043 4.043 0 1 0 1.414 1.414l2.859-2.859l2.293 2.293a1.977 1.977 0 0 0 .414.31V22h-3v8h8v-8h-3v-4.277a1.977 1.977 0 0 0 .414-.309l2.293-2.293l2.859 2.859A3.989 3.989 0 1 0 26 16M8 20a2 2 0 1 1-2-2a2.002 2.002 0 0 1 2 2m10 4v4h-4v-4zm-2-8l-6-6l6-6l6 6Zm10 6a2 2 0 1 1 2-2a2.002 2.002 0 0 1-2 2" + }, + "children": [] + } + ] + }, + "method": { + "type": "element", + "tagName": "svg", + "properties": { + "viewBox": "0 0 32 32" + }, + "children": [ + { + "type": "element", + "tagName": "path", + "properties": { + "fill": "currentColor", + "d": "m19.626 29.526l-.516-1.933a12.004 12.004 0 0 0 6.121-19.26l1.538-1.28a14.003 14.003 0 0 1-7.143 22.473" + }, + "children": [] + }, + { + "type": "element", + "tagName": "path", + "properties": { + "fill": "currentColor", + "d": "M10 29H8v-3.82l.804-.16C10.262 24.727 12 23.62 12 20v-1.382l-4-2v-2.236l4-2V12c0-5.467 3.925-9 10-9h2v3.82l-.804.16C21.738 7.273 20 8.38 20 12v.382l4 2v2.236l-4 2V20c0 5.467-3.925 9-10 9m0-2c4.935 0 8-2.682 8-7v-2.618l3.764-1.882L18 13.618V12c0-4.578 2.385-6.192 4-6.76V5c-4.935 0-8 2.682-8 7v1.618L10.236 15.5L14 17.382V20c0 4.578-2.385 6.192-4 6.76Z" + }, + "children": [] + }, + { + "type": "element", + "tagName": "path", + "properties": { + "fill": "currentColor", + "d": "M5.231 24.947a14.003 14.003 0 0 1 7.147-22.474l.516 1.932a12.004 12.004 0 0 0-6.125 19.263Z" + }, + "children": [] + } + ] + }, + "property": { + "type": "element", + "tagName": "svg", + "properties": { + "viewBox": "0 0 32 32" + }, + "children": [ + { + "type": "element", + "tagName": "path", + "properties": { + "fill": "currentColor", + "d": "M12.1 2a9.8 9.8 0 0 0-5.4 1.6l6.4 6.4a2.1 2.1 0 0 1 .2 3a2.1 2.1 0 0 1-3-.2L3.7 6.4A9.84 9.84 0 0 0 2 12.1a10.14 10.14 0 0 0 10.1 10.1a10.9 10.9 0 0 0 2.6-.3l6.7 6.7a5 5 0 0 0 7.1-7.1l-6.7-6.7a10.9 10.9 0 0 0 .3-2.6A10 10 0 0 0 12.1 2m8 10.1a7.61 7.61 0 0 1-.3 2.1l-.3 1.1l.8.8l6.7 6.7a2.88 2.88 0 0 1 .9 2.1A2.72 2.72 0 0 1 27 27a2.9 2.9 0 0 1-4.2 0l-6.7-6.7l-.8-.8l-1.1.3a7.61 7.61 0 0 1-2.1.3a8.27 8.27 0 0 1-5.7-2.3A7.63 7.63 0 0 1 4 12.1a8.33 8.33 0 0 1 .3-2.2l4.4 4.4a4.14 4.14 0 0 0 5.9.2a4.14 4.14 0 0 0-.2-5.9L10 4.2a6.45 6.45 0 0 1 2-.3a8.27 8.27 0 0 1 5.7 2.3a8.49 8.49 0 0 1 2.4 5.9" + }, + "children": [] + } + ] + }, + "constructor": { + "type": "element", + "tagName": "svg", + "properties": { + "viewBox": "0 0 32 32" + }, + "children": [ + { + "type": "element", + "tagName": "path", + "properties": { + "d": "M21.49 13.115l-9-5a1 1 0 0 0-1 0l-9 5A1.008 1.008 0 0 0 2 14v9.995a1 1 0 0 0 .52.87l9 5A1.004 1.004 0 0 0 12 30a1.056 1.056 0 0 0 .49-.135l9-5A.992.992 0 0 0 22 24V14a1.008 1.008 0 0 0-.51-.885zM11 27.295l-7-3.89v-7.72l7 3.89zm1-9.45L5.06 14L12 10.135l6.94 3.86zm8 5.56l-7 3.89v-7.72l7-3.89z", + "fill": "currentColor" + }, + "children": [] + }, + { + "type": "element", + "tagName": "path", + "properties": { + "d": "M30 6h-4V2h-2v4h-4v2h4v4h2V8h4V6z", + "fill": "currentColor" + }, + "children": [] + } + ] + }, + "interface": { + "type": "element", + "tagName": "svg", + "properties": { + "viewBox": "0 0 32 32" + }, + "children": [ + { + "type": "element", + "tagName": "path", + "properties": { + "fill": "currentColor", + "d": "M23 16.01a7 7 0 0 0-4.18 1.39l-4.22-4.22A6.86 6.86 0 0 0 16 9.01a7 7 0 1 0-2.81 5.59l4.21 4.22a7 7 0 1 0 5.6-2.81m-19-7a5 5 0 1 1 5 5a5 5 0 0 1-5-5" + }, + "children": [] + } + ] + }, + "function": { + "type": "element", + "tagName": "svg", + "properties": { + "viewBox": "0 0 32 32" + }, + "children": [ + { + "type": "element", + "tagName": "path", + "properties": { + "fill": "currentColor", + "d": "m19.626 29.526l-.516-1.933a12.004 12.004 0 0 0 6.121-19.26l1.538-1.28a14.003 14.003 0 0 1-7.143 22.473" + }, + "children": [] + }, + { + "type": "element", + "tagName": "path", + "properties": { + "fill": "currentColor", + "d": "M10 29H8v-3.82l.804-.16C10.262 24.727 12 23.62 12 20v-1.382l-4-2v-2.236l4-2V12c0-5.467 3.925-9 10-9h2v3.82l-.804.16C21.738 7.273 20 8.38 20 12v.382l4 2v2.236l-4 2V20c0 5.467-3.925 9-10 9m0-2c4.935 0 8-2.682 8-7v-2.618l3.764-1.882L18 13.618V12c0-4.578 2.385-6.192 4-6.76V5c-4.935 0-8 2.682-8 7v1.618L10.236 15.5L14 17.382V20c0 4.578-2.385 6.192-4 6.76Z" + }, + "children": [] + }, + { + "type": "element", + "tagName": "path", + "properties": { + "fill": "currentColor", + "d": "M5.231 24.947a14.003 14.003 0 0 1 7.147-22.474l.516 1.932a12.004 12.004 0 0 0-6.125 19.263Z" + }, + "children": [] + } + ] + }, + "string": { + "type": "element", + "tagName": "svg", + "properties": { + "viewBox": "0 0 32 32" + }, + "children": [ + { + "type": "element", + "tagName": "path", + "properties": { + "fill": "currentColor", + "d": "M29 22h-5a2.003 2.003 0 0 1-2-2v-6a2.002 2.002 0 0 1 2-2h5v2h-5v6h5zM18 12h-4V8h-2v14h6a2.003 2.003 0 0 0 2-2v-6a2.002 2.002 0 0 0-2-2m-4 8v-6h4v6zm-6-8H3v2h5v2H4a2 2 0 0 0-2 2v2a2 2 0 0 0 2 2h6v-8a2.002 2.002 0 0 0-2-2m0 8H4v-2h4z" + }, + "children": [] + } + ] + } +} diff --git a/packages/shikiji-twoslash/src/icons-tags.json b/packages/shikiji-twoslash/src/icons-tags.json new file mode 100644 index 00000000..28f07772 --- /dev/null +++ b/packages/shikiji-twoslash/src/icons-tags.json @@ -0,0 +1,101 @@ +{ + "log": { + "type": "element", + "tagName": "svg", + "properties": { + "viewBox": "0 0 32 32" + }, + "children": [ + { + "type": "element", + "tagName": "path", + "properties": { + "fill": "currentColor", + "d": "M17 22v-8h-4v2h2v6h-3v2h8v-2zM16 8a1.5 1.5 0 1 0 1.5 1.5A1.5 1.5 0 0 0 16 8" + }, + "children": [] + }, + { + "type": "element", + "tagName": "path", + "properties": { + "fill": "currentColor", + "d": "M26 28H6a2.002 2.002 0 0 1-2-2V6a2.002 2.002 0 0 1 2-2h20a2.002 2.002 0 0 1 2 2v20a2.002 2.002 0 0 1-2 2M6 6v20h20V6Z" + }, + "children": [] + } + ] + }, + "error": { + "type": "element", + "tagName": "svg", + "properties": { + "viewBox": "0 0 32 32" + }, + "children": [ + { + "type": "element", + "tagName": "path", + "properties": { + "fill": "currentColor", + "d": "M16 2a14 14 0 1 0 14 14A14 14 0 0 0 16 2m0 26a12 12 0 1 1 12-12a12 12 0 0 1-12 12" + }, + "children": [] + }, + { + "type": "element", + "tagName": "path", + "properties": { + "fill": "currentColor", + "d": "M15 8h2v11h-2zm1 14a1.5 1.5 0 1 0 1.5 1.5A1.5 1.5 0 0 0 16 22" + }, + "children": [] + } + ] + }, + "warn": { + "type": "element", + "tagName": "svg", + "properties": { + "viewBox": "0 0 32 32" + }, + "children": [ + { + "type": "element", + "tagName": "path", + "properties": { + "fill": "currentColor", + "d": "M16 23a1.5 1.5 0 1 0 1.5 1.5A1.5 1.5 0 0 0 16 23m-1-11h2v9h-2z" + }, + "children": [] + }, + { + "type": "element", + "tagName": "path", + "properties": { + "fill": "currentColor", + "d": "M29 30H3a1 1 0 0 1-.887-1.461l13-25a1 1 0 0 1 1.774 0l13 25A1 1 0 0 1 29 30M4.65 28h22.7l.001-.003L16.002 6.17h-.004L4.648 27.997Z" + }, + "children": [] + } + ] + }, + "annotate": { + "type": "element", + "tagName": "svg", + "properties": { + "viewBox": "0 0 32 32" + }, + "children": [ + { + "type": "element", + "tagName": "path", + "properties": { + "fill": "currentColor", + "d": "M11 24h10v2H11zm2 4h6v2h-6zm3-26A10 10 0 0 0 6 12a9.19 9.19 0 0 0 3.46 7.62c1 .93 1.54 1.46 1.54 2.38h2c0-1.84-1.11-2.87-2.19-3.86A7.2 7.2 0 0 1 8 12a8 8 0 0 1 16 0a7.2 7.2 0 0 1-2.82 6.14c-1.07 1-2.18 2-2.18 3.86h2c0-.92.53-1.45 1.54-2.39A9.18 9.18 0 0 0 26 12A10 10 0 0 0 16 2" + }, + "children": [] + } + ] + } +} diff --git a/packages/shikiji-twoslash/src/icons.ts b/packages/shikiji-twoslash/src/icons.ts new file mode 100644 index 00000000..a5efecb3 --- /dev/null +++ b/packages/shikiji-twoslash/src/icons.ts @@ -0,0 +1,9 @@ +import type { Element } from 'hast' +import type { TwoSlashReturn } from '@typescript/twoslash' +import completionIcons from './icons-completions.json' +import tagIcons from './icons-tags.json' + +export type CompletionItem = NonNullable[0] +export const defaultCompletionIcons: Record = completionIcons as any + +export const defaultCustomTagIcons: Record = tagIcons as any diff --git a/packages/shikiji-twoslash/src/index.ts b/packages/shikiji-twoslash/src/index.ts index e9a3c803..fb657132 100644 --- a/packages/shikiji-twoslash/src/index.ts +++ b/packages/shikiji-twoslash/src/index.ts @@ -2,26 +2,36 @@ import { twoslasher } from '@typescript/twoslash' import type { ShikijiTransformer } from 'shikiji' import { addClassToHast } from 'shikiji' import type { Element, ElementContent, Text } from 'hast' +import { ModuleKind, ScriptTarget } from 'typescript' import { rendererClassic } from './renderer-classic' import type { TransformerTwoSlashOptions } from './types' export * from './types' export * from './renderer-classic' export * from './renderer-rich' +export * from './icons' + +export function defaultTwoSlashOptions() { + return { + customTags: ['annotate', 'log', 'warn', 'error'], + defaultCompilerOptions: { + module: ModuleKind.ESNext, + target: ScriptTarget.ESNext, + }, + } +} export function transformerTwoSlash(options: TransformerTwoSlashOptions = {}): ShikijiTransformer { const { langs = ['ts', 'tsx'], - twoslashOptions = { - customTags: ['annotate', 'log', 'warn', 'error'], - }, + twoslashOptions = defaultTwoSlashOptions(), langAlias = { typescript: 'ts', json5: 'json', yml: 'yaml', }, explicitTrigger = false, - renderer = rendererClassic, + renderer = rendererClassic(), throws = true, } = options const filter = options.filter || ((lang, _, options) => langs.includes(lang) && (!explicitTrigger || /\btwoslash\b/.test(options.meta?.__raw || ''))) @@ -48,6 +58,8 @@ export function transformerTwoSlash(options: TransformerTwoSlashOptions = {}): S return const insertAfterLine = (line: number, nodes: ElementContent[]) => { + if (!nodes.length) + return let index: number if (line >= this.lines.length) { index = codeEl.children.length @@ -102,21 +114,44 @@ export function transformerTwoSlash(options: TransformerTwoSlashOptions = {}): S skipTokens.add(token) - const clone = { ...token } - Object.assign(token, renderer.nodeError.call(this, error, clone)) + if (renderer.nodeError) { + const clone = { ...token } + Object.assign(token, renderer.nodeError.call(this, error, clone)) + } - insertAfterLine(error.line, renderer.lineError.call(this, error)) + if (renderer.lineError) + insertAfterLine(error.line, renderer.lineError.call(this, error)) } for (const query of twoslash.queries) { if (query.kind === 'completions') { - insertAfterLine(query.line, renderer.lineCompletions.call(this, query)) + const token = locateTextToken(query.line - 1, query.offset) + if (!token) + throw new Error(`[shikiji-twoslash] Cannot find token at L${query.line}:${query.offset}`) + skipTokens.add(token) + + if (renderer.nodeCompletions) { + const clone = { ...token } + Object.assign(token, renderer.nodeCompletions.call(this, query, clone)) + } + + if (renderer.lineCompletions) + insertAfterLine(query.line, renderer.lineCompletions.call(this, query)) } else if (query.kind === 'query') { const token = locateTextToken(query.line - 1, query.offset) - if (token) - skipTokens.add(token) - insertAfterLine(query.line, renderer.lineQuery.call(this, query, token)) + if (!token) + throw new Error(`[shikiji-twoslash] Cannot find token at L${query.line}:${query.offset}`) + + skipTokens.add(token) + + if (renderer.nodeQuery) { + const clone = { ...token } + Object.assign(token, renderer.nodeQuery.call(this, query, clone)) + } + + if (renderer.lineQuery) + insertAfterLine(query.line, renderer.lineQuery.call(this, query, token)) } } @@ -133,8 +168,10 @@ export function transformerTwoSlash(options: TransformerTwoSlashOptions = {}): S Object.assign(token, renderer.nodeStaticInfo.call(this, info, clone)) } - for (const tag of twoslash.tags) - insertAfterLine(tag.line, renderer.lineCustomTag.call(this, tag)) + if (renderer.lineCustomTag) { + for (const tag of twoslash.tags) + insertAfterLine(tag.line, renderer.lineCustomTag.call(this, tag)) + } }, } } diff --git a/packages/shikiji-twoslash/src/renderer-classic.ts b/packages/shikiji-twoslash/src/renderer-classic.ts index bcf8f29f..de84113a 100644 --- a/packages/shikiji-twoslash/src/renderer-classic.ts +++ b/packages/shikiji-twoslash/src/renderer-classic.ts @@ -3,187 +3,189 @@ import type { TwoSlashRenderers } from './types' /** * The default renderer aligning with the original `shiki-twoslash` output. */ -export const rendererClassic: TwoSlashRenderers = { - nodeStaticInfo(info, node) { - return { - type: 'element', - tagName: 'data-lsp', - properties: { - lsp: info.text, - }, - children: [node], - } - }, - - nodeError(_, node) { - return { - type: 'element', - tagName: 'data-err', - properties: {}, - children: [node], - } - }, - - lineError(error) { - return [ - { +export function rendererClassic(): TwoSlashRenderers { + return { + nodeStaticInfo(info, node) { + return { type: 'element', - tagName: 'div', + tagName: 'data-lsp', properties: { - class: 'error', + lsp: info.text, }, - children: [ - { - type: 'element', - tagName: 'span', - properties: {}, - children: [ - { - type: 'text', - value: error.renderedMessage, - }, - ], + children: [node], + } + }, + + nodeError(_, node) { + return { + type: 'element', + tagName: 'data-err', + properties: {}, + children: [node], + } + }, + + lineError(error) { + return [ + { + type: 'element', + tagName: 'div', + properties: { + class: 'error', }, - { - type: 'element', - tagName: 'span', - properties: { - class: 'code', + children: [ + { + type: 'element', + tagName: 'span', + properties: {}, + children: [ + { + type: 'text', + value: error.renderedMessage, + }, + ], }, - children: [ - { - type: 'text', - value: String(error.code), + { + type: 'element', + tagName: 'span', + properties: { + class: 'code', }, - ], - }, - ], - }, - { - type: 'element', - tagName: 'span', - properties: { - class: 'error-behind', + children: [ + { + type: 'text', + value: String(error.code), + }, + ], + }, + ], }, - children: [ - { - type: 'text', - value: error.renderedMessage, + { + type: 'element', + tagName: 'span', + properties: { + class: 'error-behind', }, - ], - }, - ] - }, + children: [ + { + type: 'text', + value: error.renderedMessage, + }, + ], + }, + ] + }, - lineCompletions(query) { - return [ - { - type: 'element', - tagName: 'div', - properties: { class: 'meta-line' }, - children: [ - { type: 'text', value: ' '.repeat(query.offset) }, - { - type: 'element', - tagName: 'span', - properties: { class: 'inline-completions' }, - children: [{ + lineCompletions(query) { + return [ + { + type: 'element', + tagName: 'div', + properties: { class: 'meta-line' }, + children: [ + { type: 'text', value: ' '.repeat(query.offset) }, + { type: 'element', - tagName: 'ul', - properties: { class: 'dropdown' }, - children: query.completions! - .filter(i => i.name.startsWith(query.completionsPrefix || '____')) - .map(i => ({ - type: 'element', - tagName: 'li', - properties: { - class: i.kindModifiers?.split(',').includes('deprecated') - ? 'deprecated' - : undefined, - }, - children: [{ + tagName: 'span', + properties: { class: 'inline-completions' }, + children: [{ + type: 'element', + tagName: 'ul', + properties: { class: 'dropdown' }, + children: query.completions! + .filter(i => i.name.startsWith(query.completionsPrefix || '____')) + .map(i => ({ type: 'element', - tagName: 'span', - properties: {}, - children: [ - { - type: 'element', - tagName: 'span', - properties: { class: 'result-found' }, - children: [ - { - type: 'text', - value: query.completionsPrefix || '', - }, - ], - }, - { - type: 'text', - value: i.name.slice(query.completionsPrefix?.length || 0), - }, - ], - }], - })), - }], - }, - ], - }, - ] - }, + tagName: 'li', + properties: { + class: i.kindModifiers?.split(',').includes('deprecated') + ? 'deprecated' + : undefined, + }, + children: [{ + type: 'element', + tagName: 'span', + properties: {}, + children: [ + { + type: 'element', + tagName: 'span', + properties: { class: 'result-found' }, + children: [ + { + type: 'text', + value: query.completionsPrefix || '', + }, + ], + }, + { + type: 'text', + value: i.name.slice(query.completionsPrefix?.length || 0), + }, + ], + }], + })), + }], + }, + ], + }, + ] + }, - lineQuery(query, targetNode) { - const targetText = targetNode?.type === 'text' ? targetNode.value : '' - const offset = Math.max(0, (query.offset || 0) + Math.floor(targetText.length / 2) - 1) + lineQuery(query, targetNode) { + const targetText = targetNode?.type === 'text' ? targetNode.value : '' + const offset = Math.max(0, (query.offset || 0) + Math.floor(targetText.length / 2) - 1) - return [ - { - type: 'element', - tagName: 'div', - properties: { class: 'meta-line' }, - children: [ - { type: 'text', value: ' '.repeat(offset) }, - { - type: 'element', - tagName: 'span', - properties: { class: 'popover' }, - children: [ - { - type: 'element', - tagName: 'div', - properties: { class: 'arrow' }, - children: [], - }, - { - type: 'text', - value: query.text || '', - }, - ], - }, - ], - }, - ] - }, + return [ + { + type: 'element', + tagName: 'div', + properties: { class: 'meta-line' }, + children: [ + { type: 'text', value: ' '.repeat(offset) }, + { + type: 'element', + tagName: 'span', + properties: { class: 'popover' }, + children: [ + { + type: 'element', + tagName: 'div', + properties: { class: 'arrow' }, + children: [], + }, + { + type: 'text', + value: query.text || '', + }, + ], + }, + ], + }, + ] + }, - lineCustomTag(tag) { - return [ - { - type: 'element', - tagName: 'div', - properties: { class: `meta-line logger ${tag.name}-log` }, - children: [ - { - type: 'element', - tagName: 'span', - properties: { class: 'message' }, - children: [ - { - type: 'text', - value: tag.annotation || '', - }, - ], - }, - ], - }, - ] - }, + lineCustomTag(tag) { + return [ + { + type: 'element', + tagName: 'div', + properties: { class: `meta-line logger ${tag.name}-log` }, + children: [ + { + type: 'element', + tagName: 'span', + properties: { class: 'message' }, + children: [ + { + type: 'text', + value: tag.annotation || '', + }, + ], + }, + ], + }, + ] + }, + } } diff --git a/packages/shikiji-twoslash/src/renderer-rich.ts b/packages/shikiji-twoslash/src/renderer-rich.ts index 1b22dbe8..082fa378 100644 --- a/packages/shikiji-twoslash/src/renderer-rich.ts +++ b/packages/shikiji-twoslash/src/renderer-rich.ts @@ -1,195 +1,255 @@ import type { Element, ElementContent } from 'hast' import type { TwoSlashRenderers } from './types' +import type { CompletionItem } from './icons' +import { defaultCompletionIcons, defaultCustomTagIcons } from './icons' + +export interface RendererRichOptions { + /** + * Custom icons for completion items. + * A map from completion item kind to a HAST node. + * + * If `false`, no icons will be rendered. + * @default defaultCompletionIcons + */ + completionIcons?: Partial> | false + + /** + * Custom icons for custom tags lines. + * A map from tag name to a HAST node. + * + * If `false`, no icons will be rendered. + * @default defaultCustomTagIcons + */ + customTagIcons?: Partial> | false + + /** + * Custom formatter for the type info text. + * Note that it might not be valid TypeScript syntax. + */ + formatInfo?(info: string): string +} /** * An alternative renderer that providers better prefixed class names, * with syntax highlight for the info text. */ -export const rendererRich: TwoSlashRenderers = { - nodeStaticInfo(info, node) { - let themedContent: ElementContent[] - - try { - themedContent = ((this.codeToHast(info.text, { +export function rendererRich(options: RendererRichOptions = {}): TwoSlashRenderers { + const { + completionIcons = defaultCompletionIcons, + customTagIcons = defaultCustomTagIcons, + formatInfo = info => info, + } = options + return { + nodeStaticInfo(info, node) { + const themedContent = ((this.codeToHast(formatInfo(info.text), { ...this.options, transformers: [], transforms: undefined, }).children[0] as Element).children[0] as Element).children - } - catch (e) { - themedContent = [{ - type: 'text', - value: info.text, - }] - } - return { - type: 'element', - tagName: 'span', - properties: { - class: 'twoslash-hover', - }, - children: [ - node, - { - type: 'element', - tagName: 'span', - properties: { - class: 'twoslash-hover-info', - }, - children: themedContent, + return { + type: 'element', + tagName: 'span', + properties: { + class: 'twoslash-hover', }, - ], - } - }, + children: [ + node, + { + type: 'element', + tagName: 'span', + properties: { + class: 'twoslash-popup-info', + }, + children: themedContent, + }, + ], + } + }, + + nodeQuery(query, node) { + if (!query.text) + return {} - nodeError(_, node) { - return { - type: 'element', - tagName: 'span', - properties: { - class: 'twoslash-error', - }, - children: [node], - } - }, + const themedContent = ((this.codeToHast(formatInfo(query.text), { + ...this.options, + transformers: [], + transforms: undefined, + }).children[0] as Element).children[0] as Element).children - lineError(error) { - return [ - { + return { type: 'element', - tagName: 'div', + tagName: 'span', properties: { - class: 'twoslash-meta-line twoslash-error-line', + class: 'twoslash-hover twoslash-query-presisted', }, children: [ + node, { - type: 'text', - value: error.renderedMessage, + type: 'element', + tagName: 'span', + properties: { + class: 'twoslash-popup-info', + }, + children: [ + { + type: 'element', + tagName: 'div', + properties: { class: 'twoslash-popup-arrow' }, + children: [], + }, + ...themedContent, + ], }, ], - }, - ] - }, + } + }, + + nodeCompletions(query, node) { + if (node.type !== 'text') + throw new Error(`[shikiji-twoslash] nodeCompletions only works on text nodes, got ${node.type}`) + + const leftPart = query.completionsPrefix || '' + const rightPart = node.value.slice(leftPart.length || 0) - lineCompletions(query) { - return [ - { + return { type: 'element', - tagName: 'div', - properties: { class: 'twoslash-meta-line twoslash-completions-line' }, + tagName: 'span', + properties: {}, children: [ - { type: 'text', value: ' '.repeat(query.offset) }, + { + type: 'text', + value: leftPart, + }, { type: 'element', tagName: 'span', - properties: { class: 'twoslash-completions' }, + properties: { + class: 'twoslash-completions-list', + }, children: [{ type: 'element', tagName: 'ul', - properties: { }, + properties: {}, children: query.completions! .filter(i => i.name.startsWith(query.completionsPrefix || '____')) .map(i => ({ type: 'element', tagName: 'li', properties: { - class: i.kindModifiers?.split(',').includes('deprecated') - ? 'deprecated' - : undefined, + }, - children: [{ - type: 'element', - tagName: 'span', - properties: {}, - children: [ - { + children: [ + ...completionIcons + ? [{ type: 'element', tagName: 'span', - properties: { class: 'twoslash-completions-matched' }, + properties: { class: `twoslash-completions-icon completions-${i.kind.replace(/\s/g, '-')}` }, children: [ - { - type: 'text', - value: query.completionsPrefix || '', - }, - ], - }, - { - type: 'text', - value: i.name.slice(query.completionsPrefix?.length || 0), + completionIcons[i.kind] || completionIcons.property, + ].filter(Boolean), + }] + : [], + { + type: 'element', + tagName: 'span', + properties: { + class: i.kindModifiers?.split(',').includes('deprecated') + ? 'deprecated' + : undefined, }, - ], - }], + children: [ + { + type: 'element', + tagName: 'span', + properties: { class: 'twoslash-completions-matched' }, + children: [ + { + type: 'text', + value: query.completionsPrefix || '', + }, + ], + }, + { + type: 'element', + tagName: 'span', + properties: { class: 'twoslash-completions-unmatched' }, + children: [ + { + type: 'text', + value: i.name.slice(query.completionsPrefix?.length || 0), + }, + ], + }, + ], + }, + ], })), }], }, - ], - }, - ] - }, - - lineQuery(query, targetNode) { - if (!query.text) - return [] - - const targetText = targetNode?.type === 'text' ? targetNode.value : '' - const offset = Math.max(0, (query.offset || 0) + Math.floor(targetText.length / 2) - 1) - - let themedContent: ElementContent[] - - try { - themedContent = ((this.codeToHast(query.text, { - ...this.options, - transformers: [], - transforms: undefined, - }).children[0] as Element).children[0] as Element).children - } - catch (e) { - themedContent = [{ - type: 'text', - value: query.text, - }] - } - - return [ - { - type: 'element', - tagName: 'div', - properties: { class: 'twoslash-meta-line twoslash-popover-line' }, - children: [ - { type: 'text', value: ' '.repeat(offset) }, { - type: 'element', - tagName: 'span', - properties: { class: 'twoslash-popover' }, - children: [ - { - type: 'element', - tagName: 'div', - properties: { class: 'twoslash-popover-arrow' }, - children: [], - }, - ...themedContent, - ], + type: 'text', + value: rightPart, }, ], - }, - ] - }, + } + }, - lineCustomTag(tag) { - return [ - { + nodeError(_, node) { + return { type: 'element', - tagName: 'div', - properties: { class: `twoslash-meta-line twoslash-tag-${tag.name}-line` }, - children: [ - { - type: 'text', - value: tag.annotation || '', + tagName: 'span', + properties: { + class: 'twoslash-error', + }, + children: [node], + } + }, + + lineError(error) { + return [ + { + type: 'element', + tagName: 'div', + properties: { + class: 'twoslash-meta-line twoslash-error-line', }, - ], - }, - ] - }, + children: [ + { + type: 'text', + value: error.renderedMessage, + }, + ], + }, + ] + }, + + lineCustomTag(tag) { + return [ + { + type: 'element', + tagName: 'div', + properties: { class: `twoslash-tag-line twoslash-tag-${tag.name}-line` }, + children: [ + ...customTagIcons + ? [ + { + type: 'element', + tagName: 'span', + properties: { class: `twoslash-tag-icon tag-${tag.name}-icon` }, + children: [ + customTagIcons[tag.name], + ].filter(Boolean), + }, + ] + : [], + { + type: 'text', + value: tag.annotation || '', + }, + ], + }, + ] + }, + } } diff --git a/packages/shikiji-twoslash/src/types.ts b/packages/shikiji-twoslash/src/types.ts index 87a5c8c6..485030c8 100644 --- a/packages/shikiji-twoslash/src/types.ts +++ b/packages/shikiji-twoslash/src/types.ts @@ -44,11 +44,13 @@ export interface TransformerTwoSlashOptions { } export interface TwoSlashRenderers { - lineError(this: ShikijiTransformerContext, error: TwoSlashReturn['errors'][0]): ElementContent[] - lineCompletions(this: ShikijiTransformerContext, query: TwoSlashReturn['queries'][0]): ElementContent[] - lineQuery(this: ShikijiTransformerContext, query: TwoSlashReturn['queries'][0], targetNode?: Element | Text): ElementContent[] - lineCustomTag(this: ShikijiTransformerContext, tag: TwoSlashReturn['tags'][0]): ElementContent[] + lineError?(this: ShikijiTransformerContext, error: TwoSlashReturn['errors'][0]): ElementContent[] + lineCustomTag?(this: ShikijiTransformerContext, tag: TwoSlashReturn['tags'][0]): ElementContent[] + lineQuery?(this: ShikijiTransformerContext, query: TwoSlashReturn['queries'][0], targetNode?: Element | Text): ElementContent[] + lineCompletions?(this: ShikijiTransformerContext, query: TwoSlashReturn['queries'][0]): ElementContent[] - nodeError(this: ShikijiTransformerContext, error: TwoSlashReturn['errors'][0], node: Element | Text): ElementContent - nodeStaticInfo(this: ShikijiTransformerContext, info: TwoSlashReturn['staticQuickInfos'][0], node: Element | Text): ElementContent + nodeError?(this: ShikijiTransformerContext, error: TwoSlashReturn['errors'][0], node: Element | Text): Partial + nodeStaticInfo(this: ShikijiTransformerContext, info: TwoSlashReturn['staticQuickInfos'][0], node: Element | Text): Partial + nodeQuery?(this: ShikijiTransformerContext, query: TwoSlashReturn['queries'][0], node: Element | Text): Partial + nodeCompletions?(this: ShikijiTransformerContext, query: TwoSlashReturn['queries'][0], node: Element | Text): Partial } diff --git a/packages/shikiji-twoslash/style-shiki-twoslash.css b/packages/shikiji-twoslash/style-classic.css similarity index 100% rename from packages/shikiji-twoslash/style-shiki-twoslash.css rename to packages/shikiji-twoslash/style-classic.css diff --git a/packages/shikiji-twoslash/style-rich.css b/packages/shikiji-twoslash/style-rich.css index 47c4d13f..a3ca9e2a 100644 --- a/packages/shikiji-twoslash/style-rich.css +++ b/packages/shikiji-twoslash/style-rich.css @@ -4,11 +4,17 @@ --twoslash-underline-color: currentColor; --twoslash-popup-bg: #f8f8f8; --twoslash-popup-shadow: rgba(0, 0, 0, 0.08) 0px 1px 4px; - --twoslash-matched-color: #2e8f82; + --twoslash-matched-color: inherit; --twoslash-unmatched-color: #888; --twoslash-cursor-color: #8888; --twoslash-error-color: #d45656; --twoslash-error-bg: #d4565620; + --twoslash-tag-color: #3772cf; + --twoslash-tag-bg: #3772cf20; + --twoslash-tag-warn-color: #c37d0d; + --twoslash-tag-warn-bg: #c37d0d20; + --twoslash-tag-annotate-color: #1ba673; + --twoslash-tag-annotate-bg: #1ba67320; } /* Respect people's wishes to not have animations */ @@ -30,7 +36,7 @@ position: relative; } -.twoslash .twoslash-hover-info { +.twoslash .twoslash-popup-info { position: absolute; top: 0; left: 0; @@ -47,10 +53,32 @@ box-shadow: var(--twoslash-popup-shadow); } -.twoslash .twoslash-hover:hover .twoslash-hover-info { +.twoslash-query-presisted .twoslash-popup-info { + z-index: 9; + transform: translate(0, 1.45em); +} + +.twoslash .twoslash-hover:hover .twoslash-popup-info, +.twoslash .twoslash-query-presisted .twoslash-popup-info { opacity: 1; pointer-events: auto; - user-select: unset; +} + +.twoslash .twoslash-popup-info:hover { + user-select: auto; +} + +.twoslash .twoslash-popup-arrow { + position: absolute; + top: -4px; + left: 1em; + border-top: 1px solid var(--twoslash-border-color); + border-right: 1px solid var(--twoslash-border-color); + background: var(--twoslash-popup-bg); + transform: rotate(-45deg); + width: 6px; + height: 6px; + pointer-events: none; } /* ===== Error Line ===== */ @@ -69,67 +97,93 @@ padding-bottom: 2px; } -/* ===== Popover ===== */ -.twoslash .twoslash-popover { +/* ===== Completeions ===== */ +.twoslash .twoslash-completions-list { position: relative; - background: var(--twoslash-popup-bg); - border: 1px solid var(--twoslash-border-color); - border-radius: 4px; - padding: 4px 6px; - margin: 0 -0.5em; - z-index: 9; - user-select: none; - box-shadow: var(--twoslash-popup-shadow); } -.twoslash .twoslash-popover-arrow { +.twoslash .twoslash-completions-list ul { + user-select: none; position: absolute; - top: -5px; - left: 1em; - border-top: 1px solid var(--twoslash-border-color); - border-right: 1px solid var(--twoslash-border-color); - background: var(--twoslash-popup-bg); - transform: rotate(-45deg); - width: 8px; - height: 8px; - pointer-events: none; -} - -/* ===== Completeions ===== */ -.twoslash .twoslash-completions ul { + top: 0; + left: 0; + transform: translate(0, 1.2em); display: inline-block; - position: absolute; width: 240px; background: var(--twoslash-popup-bg); border: 1px solid var(--twoslash-border-color); font-size: 0.8rem; margin: 3px 0 0 -1px; - padding: 4px 0 0 0; - border-left: 4px solid var(--twoslash-matched-color); + padding: 4px; z-index: 8; + display: flex; + flex-direction: column; + gap: 4px; box-shadow: var(--twoslash-popup-shadow); } -.twoslash .twoslash-completions ul::before { +.twoslash .twoslash-completions-list ul:hover { + user-select: auto; +} +.twoslash .twoslash-completions-list ul::before { background-color: var(--twoslash-cursor-color); width: 2px; position: absolute; top: -1.6em; height: 1.4em; - left: -4px; + left: -1px; content: ' '; } -.twoslash .twoslash-completions ul li { - overflow-x: hidden; - padding-left: 4px; - margin-bottom: 4px; +.twoslash .twoslash-completions-list ul li { + overflow: hidden; + display: flex; + align-items: center; + gap: 0.25em; + line-height: 1em; } -.twoslash .twoslash-completions ul li span { +.twoslash .twoslash-completions-list ul li span.twoslash-completions-unmatched { color: var(--twoslash-unmatched-color); } -.twoslash .twoslash-completions ul .deprecated { +.twoslash .twoslash-completions-list ul .deprecated { text-decoration: line-through; - opacity: 0.75; + opacity: 0.5; } -.twoslash .twoslash-completions ul li span.twoslash-completions-matched { +.twoslash .twoslash-completions-list ul li span.twoslash-completions-matched { color: var(--twoslash-matched-color); } +/* Icons */ +.twoslash .twoslash-completions-list .twoslash-completions-icon { + color: var(--twoslash-unmatched-color); + width: 1em; + flex: none; +} +/* Custom Tags */ +.twoslash .twoslash-tag-line { + position: relative; + background-color: var(--twoslash-tag-bg); + border-left: 3px solid var(--twoslash-tag-color); + color: var(--twoslash-tag-color); + padding: 6px 6px; + margin: 0.2em 0; + display: flex; + align-items: center; + gap: 0.3em; +} +.twoslash .twoslash-tag-line .twoslash-tag-icon { + width: 1.1em; + color: inherit; +} +.twoslash .twoslash-tag-line.twoslash-tag-error-line { + background-color: var(--twoslash-error-bg); + border-left: 3px solid var(--twoslash-error-color); + color: var(--twoslash-error-color); +} +.twoslash .twoslash-tag-line.twoslash-tag-warn-line { + background-color: var(--twoslash-tag-warn-bg); + border-left: 3px solid var(--twoslash-tag-warn-color); + color: var(--twoslash-tag-warn-color); +} +.twoslash .twoslash-tag-line.twoslash-tag-annotate-line { + background-color: var(--twoslash-tag-annotate-bg); + border-left: 3px solid var(--twoslash-tag-annotate-color); + color: var(--twoslash-tag-annotate-color); +} diff --git a/packages/shikiji-twoslash/test/classic.test.ts b/packages/shikiji-twoslash/test/classic.test.ts index cbbb0d09..cc5dd3e5 100644 --- a/packages/shikiji-twoslash/test/classic.test.ts +++ b/packages/shikiji-twoslash/test/classic.test.ts @@ -5,7 +5,7 @@ import type { BuiltinTheme } from 'shikiji' import { transformerTwoSlash } from '../src' const styleTag = ` - + -
const a = Number.isNaN(123)
\ No newline at end of file +
const a = Number.isNaN(123)
\ No newline at end of file diff --git a/packages/shikiji-twoslash/test/out/classic/console_log.html b/packages/shikiji-twoslash/test/out/classic/console_log.html index 6f614c80..b4861698 100644 --- a/packages/shikiji-twoslash/test/out/classic/console_log.html +++ b/packages/shikiji-twoslash/test/out/classic/console_log.html @@ -1,5 +1,5 @@ - +

Hello

-
const aconst a: 123 = 123
-const bconst b: 123 = 123
-const v = 123
-
const v: 123
-
const aconst a: 123 = 123
-const bconst b: 123 = 123
-const v = 123
-
const v: 123
+
const aconst a: 123 = 123
+const bconst b: 123 = 123
+const v
const v: 123
= 123
+
+
const aconst a: 123 = 123
+const bconst b: 123 = 123
+const v
const v: 123
= 123
+
diff --git a/packages/shikiji-twoslash/test/out/markdown-it/works.html b/packages/shikiji-twoslash/test/out/markdown-it/works.html index 966e8d16..b066d78f 100644 --- a/packages/shikiji-twoslash/test/out/markdown-it/works.html +++ b/packages/shikiji-twoslash/test/out/markdown-it/works.html @@ -17,8 +17,8 @@

Hello

Code block with twoslash:

-
const a = 123
-
const a: 123
+
const a
const a: 123
= 123
+

Code block without twoslash:

const a = 123
 //    ^?
diff --git a/packages/shikiji-twoslash/test/out/rich/custom-tags.html b/packages/shikiji-twoslash/test/out/rich/custom-tags.html
new file mode 100644
index 00000000..236b0665
--- /dev/null
+++ b/packages/shikiji-twoslash/test/out/rich/custom-tags.html
@@ -0,0 +1,41 @@
+
+
+
+
import { getHighlighterCore } (alias) function getHighlighterCore(options?: HighlighterCoreOptions): Promise<HighlighterCore>
+import getHighlighterCorefrom 'shikiji/core'
+
+const shikiconst shiki: HighlighterCore = await getHighlighterCore(alias) getHighlighterCore(options?: HighlighterCoreOptions | undefined): Promise<HighlighterCore>
+import getHighlighterCore({})
+const aconst a: 1 = 1
Custom log message
const bconst b: 1 = 1
Custom error message
const cconst c: 1 = 1
Custom warning message
Custom annotation message
+ +
+ +
diff --git a/packages/shikiji-twoslash/test/out/rich/no-icons.html b/packages/shikiji-twoslash/test/out/rich/no-icons.html new file mode 100644 index 00000000..cef47cb8 --- /dev/null +++ b/packages/shikiji-twoslash/test/out/rich/no-icons.html @@ -0,0 +1,49 @@ + + + +
const objconst obj: {
+    boo: number;
+    bar: () => number;
+    baz: string;
+} = {
+  boo(property) boo: number: 1,
+  bar(property) bar: () => number: () => 2,
+  baz(property) baz: string: 'string'
+}
+objconst obj: {
+    boo: number;
+    bar: () => number;
+    baz: string;
+}.b
  • bar
  • baz
  • boo
oo
+ +
+ +
diff --git a/packages/shikiji-twoslash/test/out/rich/rich.html b/packages/shikiji-twoslash/test/out/rich/rich.html index e257cee1..a4e6dd34 100644 --- a/packages/shikiji-twoslash/test/out/rich/rich.html +++ b/packages/shikiji-twoslash/test/out/rich/rich.html @@ -7,26 +7,33 @@ .dark .shiki, .dark .shiki span { color: var(--shiki-dark, inherit); - background-color: var(--shiki-dark-bg, inherit); --twoslash-popup-bg: var(--shiki-dark-bg, inherit); } +.dark .shiki { + background-color: var(--shiki-dark-bg, inherit); +} + html:not(.dark) .shiki, html:not(.dark) .shiki span { color: var(--shiki-light, inherit); - background-color: var(--shiki-light-bg, inherit); --twoslash-popup-bg: var(--shiki-light-bg, inherit); } + +html:not(.dark) .shiki { + background-color: var(--shiki-light-bg, inherit); +} -
interface Todointerface Todo {
-  title(property) Todo.title: string: string;
+
interface Todointerface Todo {
+  title(property) Todo.title: string: string;
 }
 
-const todoconst todo: Readonly<Todo>: Readonlytype Readonly<T> = { readonly [P in keyof T]: T[P]; }<Todointerface Todo> = {
-  title: "Delete inactive users",
-};
(property) title: string
-todoconst todo: Readonly<Todo>.title = "Hello";
Cannot assign to 'title' because it is a read-only property.
-Numbervar Number: NumberConstructor.isNaN(method) NumberConstructor.isNaN(number: unknown): boolean(123)
  • isFinite
  • isInteger
  • isNaN
  • isSafeInteger
+const todoconst todo: Readonly<Todo>: Readonlytype Readonly<T> = { readonly [P in keyof T]: T[P]; }<Todointerface Todo> = { + title
(property) title: string
: "Delete inactive users".toUpperCase(method) String.toUpperCase(): string(),
+}; + +todoconst todo: Readonly<Todo>.title = "Hello";
Cannot assign to 'title' because it is a read-only property.
+Numbervar Number: NumberConstructor.p
  • parseFloat
  • parseInt
  • prototype
arseInt
("123", 10);