docusaurus-twoslash
Version:
A Docusaurus plugin that adds TypeScript Twoslash integration for enhanced code blocks with type information
491 lines (481 loc) • 17.3 kB
JavaScript
;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __esm = (fn, res) => function __init() {
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
};
var __commonJS = (cb, mod) => function __require() {
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
};
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// node_modules/unist-util-is/lib/index.js
function anyFactory(tests) {
const checks = [];
let index = -1;
while (++index < tests.length) {
checks[index] = convert(tests[index]);
}
return castFactory(any);
function any(...parameters) {
let index2 = -1;
while (++index2 < checks.length) {
if (checks[index2].apply(this, parameters))
return true;
}
return false;
}
}
function propsFactory(check) {
const checkAsRecord = (
/** @type {Record<string, unknown>} */
check
);
return castFactory(all);
function all(node) {
const nodeAsRecord = (
/** @type {Record<string, unknown>} */
/** @type {unknown} */
node
);
let key;
for (key in check) {
if (nodeAsRecord[key] !== checkAsRecord[key])
return false;
}
return true;
}
}
function typeFactory(check) {
return castFactory(type);
function type(node) {
return node && node.type === check;
}
}
function castFactory(testFunction) {
return check;
function check(value, index, parent) {
return Boolean(
looksLikeANode(value) && testFunction.call(
this,
value,
typeof index === "number" ? index : void 0,
parent || void 0
)
);
}
}
function ok() {
return true;
}
function looksLikeANode(value) {
return value !== null && typeof value === "object" && "type" in value;
}
var convert;
var init_lib = __esm({
"node_modules/unist-util-is/lib/index.js"() {
convert = // Note: overloads in JSDoc can’t yet use different `@template`s.
/**
* @type {(
* (<Condition extends string>(test: Condition) => (node: unknown, index?: number | null | undefined, parent?: Parent | null | undefined, context?: unknown) => node is Node & {type: Condition}) &
* (<Condition extends Props>(test: Condition) => (node: unknown, index?: number | null | undefined, parent?: Parent | null | undefined, context?: unknown) => node is Node & Condition) &
* (<Condition extends TestFunction>(test: Condition) => (node: unknown, index?: number | null | undefined, parent?: Parent | null | undefined, context?: unknown) => node is Node & Predicate<Condition, Node>) &
* ((test?: null | undefined) => (node?: unknown, index?: number | null | undefined, parent?: Parent | null | undefined, context?: unknown) => node is Node) &
* ((test?: Test) => Check)
* )}
*/
/**
* @param {Test} [test]
* @returns {Check}
*/
function(test) {
if (test === null || test === void 0) {
return ok;
}
if (typeof test === "function") {
return castFactory(test);
}
if (typeof test === "object") {
return Array.isArray(test) ? anyFactory(test) : propsFactory(test);
}
if (typeof test === "string") {
return typeFactory(test);
}
throw new Error("Expected function, string, or object as test");
};
}
});
// node_modules/unist-util-is/index.js
var init_unist_util_is = __esm({
"node_modules/unist-util-is/index.js"() {
init_lib();
}
});
// node_modules/unist-util-visit-parents/lib/color.node.js
function color(d) {
return "\x1B[33m" + d + "\x1B[39m";
}
var init_color_node = __esm({
"node_modules/unist-util-visit-parents/lib/color.node.js"() {
}
});
// node_modules/unist-util-visit-parents/lib/index.js
function visitParents(tree, test, visitor, reverse) {
let check;
if (typeof test === "function" && typeof visitor !== "function") {
reverse = visitor;
visitor = test;
} else {
check = test;
}
const is2 = convert(check);
const step = reverse ? -1 : 1;
factory(tree, void 0, [])();
function factory(node, index, parents) {
const value = (
/** @type {Record<string, unknown>} */
node && typeof node === "object" ? node : {}
);
if (typeof value.type === "string") {
const name = (
// `hast`
typeof value.tagName === "string" ? value.tagName : (
// `xast`
typeof value.name === "string" ? value.name : void 0
)
);
Object.defineProperty(visit2, "name", {
value: "node (" + color(node.type + (name ? "<" + name + ">" : "")) + ")"
});
}
return visit2;
function visit2() {
let result = empty;
let subresult;
let offset;
let grandparents;
if (!test || is2(node, index, parents[parents.length - 1] || void 0)) {
result = toResult(visitor(node, parents));
if (result[0] === EXIT) {
return result;
}
}
if ("children" in node && node.children) {
const nodeAsParent = (
/** @type {UnistParent} */
node
);
if (nodeAsParent.children && result[0] !== SKIP) {
offset = (reverse ? nodeAsParent.children.length : -1) + step;
grandparents = parents.concat(nodeAsParent);
while (offset > -1 && offset < nodeAsParent.children.length) {
const child = nodeAsParent.children[offset];
subresult = factory(child, offset, grandparents)();
if (subresult[0] === EXIT) {
return subresult;
}
offset = typeof subresult[1] === "number" ? subresult[1] : offset + step;
}
}
}
return result;
}
}
}
function toResult(value) {
if (Array.isArray(value)) {
return value;
}
if (typeof value === "number") {
return [CONTINUE, value];
}
return value === null || value === void 0 ? empty : [value];
}
var empty, CONTINUE, EXIT, SKIP;
var init_lib2 = __esm({
"node_modules/unist-util-visit-parents/lib/index.js"() {
init_unist_util_is();
init_color_node();
empty = [];
CONTINUE = true;
EXIT = false;
SKIP = "skip";
}
});
// node_modules/unist-util-visit-parents/index.js
var init_unist_util_visit_parents = __esm({
"node_modules/unist-util-visit-parents/index.js"() {
init_lib2();
}
});
// node_modules/unist-util-visit/lib/index.js
function visit(tree, testOrVisitor, visitorOrReverse, maybeReverse) {
let reverse;
let test;
let visitor;
if (typeof testOrVisitor === "function" && typeof visitorOrReverse !== "function") {
test = void 0;
visitor = testOrVisitor;
reverse = visitorOrReverse;
} else {
test = testOrVisitor;
visitor = visitorOrReverse;
reverse = maybeReverse;
}
visitParents(tree, test, overload, reverse);
function overload(node, parents) {
const parent = parents[parents.length - 1];
const index = parent ? parent.children.indexOf(node) : void 0;
return visitor(node, index, parent);
}
}
var init_lib3 = __esm({
"node_modules/unist-util-visit/lib/index.js"() {
init_unist_util_visit_parents();
init_unist_util_visit_parents();
}
});
// node_modules/unist-util-visit/index.js
var unist_util_visit_exports = {};
__export(unist_util_visit_exports, {
CONTINUE: () => CONTINUE,
EXIT: () => EXIT,
SKIP: () => SKIP,
visit: () => visit
});
var init_unist_util_visit = __esm({
"node_modules/unist-util-visit/index.js"() {
init_lib3();
}
});
// src/remark/remarkTwoslash.js
var require_remarkTwoslash = __commonJS({
"src/remark/remarkTwoslash.js"(exports2, module2) {
"use strict";
var { visit: visit2 } = (init_unist_util_visit(), __toCommonJS(unist_util_visit_exports));
function remarkTwoslash2(options = {}) {
const {
typescript: { compilerOptions: customCompilerOptions = {} } = {},
themes = ["typescript", "javascript", "jsx", "tsx"],
cache = true,
includeDefaultLib = true
} = options;
console.log("\u{1F527} Remark Twoslash plugin initialized with themes:", themes);
let twoslashRunner;
let isInitialized = false;
async function initializeTwoslash() {
if (isInitialized)
return;
try {
const ts = require("typescript");
const { twoslasher } = require("@typescript/twoslash");
twoslashRunner = twoslasher;
isInitialized = true;
console.log("\u2705 Twoslash TypeScript runner initialized successfully");
} catch (error) {
console.warn("\u26A0\uFE0F Twoslash initialization failed, using fallback:", error.message);
twoslashRunner = {
runTwoSlash: (code) => ({
code,
queries: [],
errors: [],
staticQuickInfos: [],
highlights: []
})
};
isInitialized = true;
}
}
return async function transformer(ast) {
var _a;
await initializeTwoslash();
const codeblocks = [];
visit2(ast, "code", (node, index, parent) => {
const { lang, meta, value } = node;
console.log(`\u{1F50D} Found code block: lang="${lang}", meta="${meta}", value length=${(value == null ? void 0 : value.length) || 0}, meta type=${typeof meta}`);
const hasTwoslash = meta && typeof meta === "string" && meta.includes("twoslash") || lang && typeof lang === "string" && lang.includes("twoslash") || value && typeof value === "string" && value.includes("// ^?");
const actualLang = lang && typeof lang === "string" ? lang.replace(/\s+twoslash.*$/, "").trim() : lang;
if (lang === "typescript" || lang === "javascript" || lang === "jsx" || lang === "tsx" || actualLang === "typescript" || actualLang === "javascript") {
console.log(`\u{1F3AF} TypeScript-related block found! lang="${lang}", actualLang="${actualLang}", meta="${meta}", hasTwoslash=${hasTwoslash}`);
if (hasTwoslash) {
console.log(`\u{1F389} TWOSLASH BLOCK DETECTED! lang="${lang}", meta="${meta}"`);
} else {
console.log(`\u274C No twoslash detected: meta="${meta}" (type: ${typeof meta}), lang="${lang}"`);
}
}
if (hasTwoslash && value && themes.includes(actualLang)) {
console.log(`\u2728 Processing Twoslash block: actualLang="${actualLang}", originalLang="${lang}", meta="${meta}"`);
codeblocks.push({ node, index, parent, lang: actualLang, meta: meta || "twoslash", value });
}
});
console.log(`\u{1F4CA} Found ${codeblocks.length} Twoslash code blocks to process`);
let allCodeBlocks = [];
visit2(ast, "code", (node) => {
allCodeBlocks.push({
lang: node.lang,
meta: node.meta,
value: node.value ? node.value.substring(0, 50) + "..." : "no value"
});
});
console.log("\u{1F50D} ALL CODE BLOCKS FOUND:", JSON.stringify(allCodeBlocks.slice(0, 10), null, 2));
for (const { node, lang, meta, value } of codeblocks) {
try {
const compilerOptions = {
allowJs: true,
target: "esnext",
module: "esnext",
lib: ["esnext", "dom"],
moduleResolution: "node",
strict: false,
esModuleInterop: true,
skipLibCheck: true,
declaration: false,
allowSyntheticDefaultImports: true,
isolatedModules: false,
noEmit: true,
...customCompilerOptions
};
const lines = value.split("\n");
const expectedErrors = [];
for (const line of lines) {
const trimmed = line.trim();
if (trimmed.startsWith("// @errors:")) {
const errorsMatch = trimmed.match(/\/\/ @errors:\s*(.+)$/);
if (errorsMatch) {
expectedErrors.push(...errorsMatch[1].split(",").map((s) => parseInt(s.trim())));
}
}
}
let processedCode;
if (twoslashRunner && typeof twoslashRunner === "function") {
processedCode = twoslashRunner(value, lang || "ts", {
compilerOptions,
expectedErrors
});
} else if (twoslashRunner == null ? void 0 : twoslashRunner.runTwoSlash) {
processedCode = twoslashRunner.runTwoSlash(value, `index.${lang || "ts"}`, {
compilerOptions,
expectedErrors
});
} else {
processedCode = {
code: value,
queries: [],
errors: [],
staticQuickInfos: [],
highlights: []
};
}
console.log(`\u{1F3AF} Processed code block with ${((_a = processedCode.queries) == null ? void 0 : _a.length) || 0} queries`);
const enhancedMeta = `${meta || ""} twoslash-processed`.trim();
node.meta = enhancedMeta;
const twoslashData = {
code: processedCode.code || value,
queries: processedCode.queries || [],
errors: processedCode.errors || [],
staticQuickInfos: processedCode.staticQuickInfos || [],
highlights: processedCode.highlights || [],
lang: lang || "typescript"
};
node.data = node.data || {};
node.data.twoslash = twoslashData;
node.data.hProperties = node.data.hProperties || {};
node.data.hProperties.twoslash = twoslashData;
node.data.hProperties["data-twoslash"] = JSON.stringify(twoslashData);
node.data.hProperties["data-twoslash-queries"] = twoslashData.queries.length;
node.data.hProperties["data-twoslash-errors"] = twoslashData.errors.length;
node.data.hProperties["data-twoslash-processed"] = "true";
node.twoslash = twoslashData;
Object.assign(node, {
"data-twoslash": JSON.stringify(twoslashData),
"data-twoslash-queries": twoslashData.queries.length,
"data-twoslash-errors": twoslashData.errors.length
});
console.log(`\u2705 Successfully enhanced node with Twoslash data`);
} catch (error) {
console.warn(`\u26A0\uFE0F Failed to process twoslash for ${lang}:`, error.message);
node.meta = `${meta || ""} twoslash-error`.trim();
node.data = node.data || {};
node.data.twoslash = {
error: error.message,
code: value,
queries: [],
errors: [],
staticQuickInfos: [],
highlights: [],
lang: lang || "typescript"
};
node.data.hProperties = node.data.hProperties || {};
node.data.hProperties.twoslash = node.data.twoslash;
node.twoslash = node.data.twoslash;
}
}
};
}
module2.exports = remarkTwoslash2;
}
});
// src/index.js
var path = require("path");
var remarkTwoslash = require_remarkTwoslash();
function docusaurusTwoslashPlugin(context, options = {}) {
const {
typescript = {},
themes = ["typescript", "javascript", "jsx", "tsx"],
cache = true,
includeDefaultLib = true
} = options;
console.log("\u{1F680} Docusaurus Twoslash Plugin loaded with options:", { themes, cache });
return {
name: "docusaurus-twoslash",
getThemePath() {
return path.resolve(__dirname, "theme");
},
getTypeScriptThemePath() {
return path.resolve(__dirname, "theme");
},
async contentLoaded({ content, actions, allContent }) {
const { addRoute } = actions;
},
async loadContent() {
return null;
},
// This method configures ALL markdown processing in Docusaurus
configureMarkdownProcessor(processor) {
console.log("\u{1F4DD} Configuring markdown processor with Twoslash");
return processor.use([remarkTwoslash, {
typescript,
themes,
cache,
includeDefaultLib
}]);
},
getClientModules() {
return [
path.resolve(__dirname, "theme/styles.css")
];
},
injectHtmlTags() {
return {
headTags: [],
preBodyTags: [],
postBodyTags: []
};
}
};
}
docusaurusTwoslashPlugin.remarkPlugin = remarkTwoslash;
module.exports = docusaurusTwoslashPlugin;