236 lines
6.7 KiB
JavaScript
236 lines
6.7 KiB
JavaScript
import { createTransformerFactory } from '@shikijs/twoslash/core';
|
|
import { removeTwoslashNotations } from 'twoslash';
|
|
import { createTwoslasher } from 'twoslash-vue';
|
|
import { rendererRich } from '@shikijs/twoslash';
|
|
export { defaultHoverInfoProcessor } from '@shikijs/twoslash';
|
|
import { fromMarkdown } from 'mdast-util-from-markdown';
|
|
import { gfmFromMarkdown } from 'mdast-util-gfm';
|
|
import { toHast, defaultHandlers } from 'mdast-util-to-hast';
|
|
|
|
function rendererFloatingVue(options = {}) {
|
|
const {
|
|
classCopyIgnore = "vp-copy-ignore",
|
|
classFloatingPanel = "twoslash-floating",
|
|
classCode = "vp-code",
|
|
classMarkdown = "vp-doc",
|
|
floatingVueTheme = "twoslash",
|
|
floatingVueThemeQuery = "twoslash-query",
|
|
floatingVueThemeCompletion = "twoslash-completion"
|
|
} = options.floatingVue || {};
|
|
const {
|
|
errorRendering = "line"
|
|
} = options;
|
|
const hoverBasicProps = {
|
|
"class": "twoslash-hover",
|
|
"popper-class": ["shiki", classFloatingPanel, classCopyIgnore, classCode].join(" "),
|
|
"theme": floatingVueTheme
|
|
};
|
|
const rich = rendererRich({
|
|
classExtra: classCopyIgnore,
|
|
...options,
|
|
renderMarkdown,
|
|
renderMarkdownInline,
|
|
hast: {
|
|
hoverToken: {
|
|
tagName: "v-menu",
|
|
properties: hoverBasicProps
|
|
},
|
|
hoverCompose: compose,
|
|
queryToken: {
|
|
tagName: "v-menu",
|
|
properties: {
|
|
...hoverBasicProps,
|
|
":shown": "true",
|
|
"theme": floatingVueThemeQuery
|
|
}
|
|
},
|
|
queryCompose: compose,
|
|
popupDocs: {
|
|
class: `twoslash-popup-docs ${classMarkdown}`
|
|
},
|
|
popupDocsTags: {
|
|
class: `twoslash-popup-docs twoslash-popup-docs-tags ${classMarkdown}`
|
|
},
|
|
popupError: {
|
|
class: `twoslash-popup-error ${classMarkdown}`
|
|
},
|
|
errorToken: errorRendering === "line" ? undefined : {
|
|
tagName: "v-menu",
|
|
properties: {
|
|
...hoverBasicProps,
|
|
class: "twoslash-error twoslash-error-hover"
|
|
}
|
|
},
|
|
errorCompose: compose,
|
|
completionCompose({ popup, cursor }) {
|
|
return [
|
|
{
|
|
type: "element",
|
|
tagName: "v-menu",
|
|
properties: {
|
|
"popper-class": ["shiki twoslash-completion", classCopyIgnore, classFloatingPanel],
|
|
"theme": floatingVueThemeCompletion,
|
|
":shown": "true"
|
|
},
|
|
children: [
|
|
cursor,
|
|
{
|
|
type: "element",
|
|
tagName: "template",
|
|
properties: {
|
|
"v-slot:popper": "{}"
|
|
},
|
|
content: {
|
|
type: "root",
|
|
children: [vPre(popup)]
|
|
}
|
|
}
|
|
]
|
|
}
|
|
];
|
|
}
|
|
}
|
|
});
|
|
return rich;
|
|
}
|
|
function vPre(el) {
|
|
if (el.type === "element") {
|
|
el.properties = el.properties || {};
|
|
el.properties["v-pre"] = "";
|
|
}
|
|
return el;
|
|
}
|
|
function renderMarkdown(md) {
|
|
const mdast = fromMarkdown(
|
|
md.replace(/\{@link ([^}]*)\}/g, "$1"),
|
|
// replace jsdoc links
|
|
{ mdastExtensions: [gfmFromMarkdown()] }
|
|
);
|
|
return toHast(
|
|
mdast,
|
|
{
|
|
handlers: {
|
|
code: (state, node) => {
|
|
const lang = node.lang || "";
|
|
if (lang) {
|
|
return {
|
|
type: "element",
|
|
tagName: "code",
|
|
properties: {},
|
|
children: this.codeToHast(
|
|
node.value,
|
|
{
|
|
...this.options,
|
|
transformers: [],
|
|
lang,
|
|
structure: node.value.trim().includes("\n") ? "classic" : "inline"
|
|
}
|
|
).children
|
|
};
|
|
}
|
|
return defaultHandlers.code(state, node);
|
|
}
|
|
}
|
|
}
|
|
).children;
|
|
}
|
|
function renderMarkdownInline(md, context) {
|
|
if (context === "tag:param")
|
|
md = md.replace(/^([\w$-]+)/, "`$1` ");
|
|
const children = renderMarkdown.call(this, md);
|
|
if (children.length === 1 && children[0].type === "element" && children[0].tagName === "p")
|
|
return children[0].children;
|
|
return children;
|
|
}
|
|
function compose(parts) {
|
|
return [
|
|
{
|
|
type: "element",
|
|
tagName: "span",
|
|
properties: {},
|
|
children: [parts.token]
|
|
},
|
|
{
|
|
type: "element",
|
|
tagName: "template",
|
|
properties: {
|
|
"v-slot:popper": "{}"
|
|
},
|
|
content: {
|
|
type: "root",
|
|
children: [vPre(parts.popup)]
|
|
},
|
|
children: []
|
|
}
|
|
];
|
|
}
|
|
|
|
function transformerTwoslash(options = {}) {
|
|
const {
|
|
explicitTrigger = true,
|
|
typesCache
|
|
} = options;
|
|
const onError = (error, code) => {
|
|
const isCI = typeof process !== "undefined" && process?.env?.CI;
|
|
const isDev = typeof process !== "undefined" && process?.env?.NODE_ENV === "development";
|
|
const shouldThrow = (options.throws || isCI || !isDev) && options.throws !== false;
|
|
console.error(`
|
|
|
|
--------
|
|
Twoslash error in code:
|
|
--------
|
|
${code.split(/\n/g).slice(0, 15).join("\n").trim()}
|
|
--------
|
|
`);
|
|
if (shouldThrow)
|
|
throw error;
|
|
else
|
|
console.error(error);
|
|
removeTwoslashNotations(code);
|
|
};
|
|
const defaultTwoslasher = createTwoslasher(options.twoslashOptions);
|
|
let twoslasher = defaultTwoslasher;
|
|
if (typesCache) {
|
|
twoslasher = (code, extension, options2) => {
|
|
const cached = typesCache.read(code);
|
|
if (cached)
|
|
return cached;
|
|
const twoslashResult = defaultTwoslasher(code, extension, options2);
|
|
typesCache.write(code, twoslashResult);
|
|
return twoslashResult;
|
|
};
|
|
twoslasher.getCacheMap = defaultTwoslasher.getCacheMap;
|
|
}
|
|
const twoslash = createTransformerFactory(twoslasher)({
|
|
langs: ["ts", "tsx", "js", "jsx", "json", "vue"],
|
|
renderer: rendererFloatingVue(options),
|
|
onTwoslashError: onError,
|
|
onShikiError: onError,
|
|
...options,
|
|
explicitTrigger
|
|
});
|
|
const trigger = explicitTrigger instanceof RegExp ? explicitTrigger : /\btwoslash\b/;
|
|
typesCache?.init?.();
|
|
return {
|
|
...twoslash,
|
|
name: "@shikijs/vitepress-twoslash",
|
|
preprocess(code, options2) {
|
|
const cleanup = options2.transformers?.find((i) => i.name === "vitepress:clean-up");
|
|
if (cleanup)
|
|
options2.transformers?.splice(options2.transformers.indexOf(cleanup), 1);
|
|
if (!explicitTrigger || options2.meta?.__raw?.match(trigger)) {
|
|
const vPre = options2.transformers?.find((i) => i.name === "vitepress:v-pre");
|
|
if (vPre)
|
|
options2.transformers?.splice(options2.transformers.indexOf(vPre), 1);
|
|
}
|
|
return twoslash.preprocess.call(this, code, options2);
|
|
},
|
|
postprocess(html) {
|
|
if (this.meta.twoslash)
|
|
return html.replace(/\{/g, "{");
|
|
}
|
|
};
|
|
}
|
|
|
|
export { rendererFloatingVue, transformerTwoslash };
|