Files
wehub-resource-sync 311a3666c4
Build documentation / build (push) Failing after 0s
Unit tests / build (18) (push) Has been cancelled
Unit tests / build (20) (push) Has been cancelled
Unit tests / build (22) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:44:39 +08:00

130 lines
4.9 KiB
JavaScript

/**
* JSDoc plugin to transform TypeScript-style type expressions into JSDoc-compatible syntax.
*/
function extractBalancedBraces(text, start) {
if (text[start] !== "{") return null;
let depth = 1,
i = start + 1;
while (i < text.length && depth > 0) {
if (text[i] === "{") depth++;
else if (text[i] === "}") depth--;
i++;
}
return depth === 0 ? { content: text.slice(start + 1, i - 1), endIndex: i } : null;
}
function stripLeadingGenericParams(expr) {
if (expr[0] !== "<") return expr;
let depth = 1,
i = 1;
while (i < expr.length && depth > 0) {
if (expr[i] === "<") depth++;
else if (expr[i] === ">") depth--;
i++;
}
return depth === 0 ? expr.slice(i) : expr;
}
function hasTopLevelConditional(expr) {
let angle = 0,
paren = 0,
brace = 0,
bracket = 0;
for (let i = 0; i < expr.length; ++i) {
const ch = expr[i];
if (ch === "<") angle++;
else if (ch === ">") angle = Math.max(0, angle - 1);
else if (ch === "(") paren++;
else if (ch === ")") paren = Math.max(0, paren - 1);
else if (ch === "{") brace++;
else if (ch === "}") brace = Math.max(0, brace - 1);
else if (ch === "[") bracket++;
else if (ch === "]") bracket = Math.max(0, bracket - 1);
else if (ch === "?" && angle === 0 && paren === 0 && brace === 0 && bracket === 0) {
return true;
}
}
return false;
}
function transformType(expr) {
let result = expr,
prev = "";
while (result !== prev) {
prev = result;
result = stripLeadingGenericParams(result); // <T extends X>(...) -> (...)
if (/^Promise<function\s*\(/.test(result)) {
result = "Promise.<Function>";
continue;
}
if (/^function\s*\(/.test(result)) {
result = "Function";
continue;
}
if (hasTopLevelConditional(result)) {
result = "any";
continue;
}
result = result
.replace(/import\([^)]+\)((?:\.\w+)*)/g, (_, p) => p?.slice(1) || "any") // import() -> Type
.replace(/\w+\[['"][^\]]+['"]\]\s+extends\s+[^}]+/g, "any") // X['p'] extends ... -> any
.replace(/(\w+)(?:<[^>]+>)?\[[^\]]+\]/g, "$1") // Type[x] or Type<T>[x] -> Type
.replace(/keyof\s+typeof\s+\w+/g, "string") // keyof typeof X -> string
.replace(/typeof\s+\w+/g, "Object") // typeof X -> Object
.replace(/\binfer\s+\w+/g, "any") // infer K -> any
.replace(/\breadonly\s+/g, "") // readonly T -> T
.replace(/\(\s*\w[\w<>, ]*\s+extends\s+\w+\s*\?[^)]*\)/g, "any") // (X extends Y ? A : B) -> any
.replace(/(?<!\w)\(\s*(\w+)\s*\)/g, "$1") // (any) -> any (unwrap parens around simple types, not after words like "function")
.replace(/(\w+)\?\s*:/g, "$1:") // x?: T -> x: T
.replace(/;\s*([}\n])/g, " $1")
.replace(/;\s+/g, ", ") // semicolons -> commas
.replace(/\{\s*\[\w+\s+in\s+[^\]]+\][^}]*\}/g, "Object") // mapped types -> Object
.replace(/new\s*\([^)]*\)\s*=>\s*\{[^}]*\}/g, "Function") // new () => {...} -> Function
.replace(/new\s*\([^)]*\)\s*=>\s*\w+/g, "Function") // new () => T -> Function
.replace(/\([^()]*\)\s*=>\s*\w[\w<>|[\], ]*/g, "Function") // () => T -> Function
.replace(/\{[^{}]*\}\[\]/g, "Array") // {x:T}[] -> Array
.replace(/(\w+)<[^>]+>\[\]/g, "Array.<$1>") // T<U>[] -> Array.<T>
.replace(/\([^()]+\)\[\]/g, "Array") // (A|B)[] -> Array
.replace(/(\w+)\[\]/g, "Array") // T[] -> Array (simple)
.replace(/\[\w+\]/g, "Array") // [T] single-element tuple -> Array
.replace(/\[[^\[\]]*,[^\[\]]*\]/g, "Array") // tuples with commas -> Array
.replace(/\bnew\s+([A-Z]\w*)\b/g, "$1") // new Type -> Type
.replace(/,?\s*\[\s*\w+\s*:\s*\w+\s*\]\s*:\s*\w+/g, "") // [key: string]: any -> (removed)
.replace(/\(\s*(\w+)\s*&\s*\{\s*\}\s*\)/g, "$1") // (string & {}) -> string
.replace(/\s*&\s*\{\s*\}/g, ""); // string & {} -> string
if (!result.includes("=>")) result = result.replace(/\s*&\s*/g, "|"); // A & B -> A|B
}
return result;
}
function transformComment(comment) {
const tagRegex =
/@(?:type|param|returns?|typedef|property|prop|callback|template|augments|extends|class|constructor|member|var|const|enum|throws?)[^\S\n]*\{/g;
let result = "",
lastIndex = 0,
match;
while ((match = tagRegex.exec(comment)) !== null) {
const braceStart = match.index + match[0].length - 1;
result += comment.slice(lastIndex, braceStart);
const extracted = extractBalancedBraces(comment, braceStart);
if (extracted) {
result += "{" + transformType(extracted.content) + "}";
lastIndex = tagRegex.lastIndex = extracted.endIndex;
} else {
result += "{";
lastIndex = braceStart + 1;
}
}
return result + comment.slice(lastIndex);
}
export const handlers = {
beforeParse: (e) => {
e.source = e.source
.replace(/\/\*\*[\s\S]*?\*\//g, transformComment) // transform JSDoc comments
.replace(/\b(\d+)n\b/g, "BigInt($1)"); // BigInt literals
},
};