From 3ea7da05b9a42acc7e0d56217b7d96dbced3a666 Mon Sep 17 00:00:00 2001 From: akudev Date: Wed, 17 Jun 2026 13:10:07 +0200 Subject: [PATCH 1/4] fix: strip broken links from generated API docs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Address 2,235 broken links (1.46% of all local links) found by the link checker. The problems are systematic TypeDoc artifacts: - Remove cross-library module augmentation blocks in preprocessing so TypeDoc generates content in the canonical library (e.g. sap/tnt/library in sap.tnt, not sap.f) — fixes 40 broken links - Strip tags pointing to ClassInfo type alias (no useful page) — 1,677 - Strip links to jQuery/QUnit types (correctly excluded pages) — ~265 - Strip links to pseudo-types int/float (UI5 aliases for number) — 93 - Strip links with nested namespaces/ segments (dead TypeDoc paths) — ~50 - Remove TypeDoc 'References' section from library READMEs — ~88 - Prune README list entries whose target page was not generated — ~38 --- .changeset/fix-apidoc-broken-links.md | 2 + scripts/generate-api-docs/generate.mjs | 120 +++++++++++++++++++++++-- 2 files changed, 115 insertions(+), 7 deletions(-) create mode 100644 .changeset/fix-apidoc-broken-links.md diff --git a/.changeset/fix-apidoc-broken-links.md b/.changeset/fix-apidoc-broken-links.md new file mode 100644 index 0000000000..a845151cc8 --- /dev/null +++ b/.changeset/fix-apidoc-broken-links.md @@ -0,0 +1,2 @@ +--- +--- diff --git a/scripts/generate-api-docs/generate.mjs b/scripts/generate-api-docs/generate.mjs index 505f453ad6..c85335f04c 100644 --- a/scripts/generate-api-docs/generate.mjs +++ b/scripts/generate-api-docs/generate.mjs @@ -161,7 +161,66 @@ for (const file of dtsFiles) { } console.log(` Found ${defaultExports.size} default exports`); -// Second pass: rewrite the files +// Precompute namespace prefixes for all libraries (used to detect augmentations) +// e.g. "sap.f" → "sap/f/", "sap.ui.core" → "sap/ui/core/" +const libNamespacePrefixes = new Map(); +for (const lib of libraryModules.keys()) { + libNamespacePrefixes.set(lib, lib.replace(/\./g, "/") + "/"); +} + +// Remove cross-library augmentation blocks from .d.ts files. +// E.g. sap.f.d.ts augments "sap/tnt/library" — remove that block so TypeDoc +// sees "sap/tnt/library" only in sap.tnt.d.ts, generating the full content there. +let totalAugmentationsRemoved = 0; +for (const file of dtsFiles) { + const libName = file.replace(/\.d\.ts$/, ""); + const filePath = join(TYPES_DIR, file); + let content = readFileSync(filePath, "utf8"); + + const moduleRegex = /^declare module "([^"]+)"/gm; + const blocksToRemove = []; + let match; + while ((match = moduleRegex.exec(content)) !== null) { + const moduleName = match[1]; // e.g. "sap/tnt/library" + const moduleNs = moduleName + "/"; + + // Check if another library owns this module's namespace + for (const [otherLib, otherNs] of libNamespacePrefixes) { + if (otherLib !== libName && moduleNs.startsWith(otherNs)) { + // This is an augmentation of another library's module — mark for removal + const blockStart = match.index; + // Find the closing } of this declare module block + let braceDepth = 0; + let blockEnd = blockStart; + for (let i = content.indexOf("{", blockStart); i < content.length; i++) { + if (content[i] === "{") braceDepth++; + else if (content[i] === "}") { + braceDepth--; + if (braceDepth === 0) { blockEnd = i + 1; break; } + } + } + blocksToRemove.push([blockStart, blockEnd]); + break; + } + } + } + + // Remove blocks in reverse order to preserve indices + for (const [start, end] of blocksToRemove.reverse()) { + content = content.slice(0, start) + content.slice(end); + } + + if (blocksToRemove.length > 0) { + writeFileSync(filePath, content); + totalAugmentationsRemoved += blocksToRemove.length; + console.log(` Removed ${blocksToRemove.length} cross-library augmentation(s) from ${file}`); + } +} +if (totalAugmentationsRemoved > 0) { + console.log(` Total augmentations removed: ${totalAugmentationsRemoved}`); +} + +// Rewrite pass: rename default exports to named exports for (const file of dtsFiles) { const filePath = join(TYPES_DIR, file); let content = readFileSync(filePath, "utf8"); @@ -376,12 +435,16 @@ let skippedCount = 0; // Directories/files to exclude from output (not UI5-specific) const EXCLUDE_PATTERNS = ["interfaces/JQuery", "interfaces/JQuery.", "/JQueryStatic", "JQueryPromise"]; -// Precompute namespace prefixes for all libraries (used to detect augmentations) -// e.g. "sap.f" → "sap/f/", "sap.ui.core" → "sap/ui/core/" -const libNamespacePrefixes = new Map(); -for (const lib of libraryModules.keys()) { - libNamespacePrefixes.set(lib, lib.replace(/\./g, "/") + "/"); -} +// Link patterns pointing to dead targets (jQuery, QUnit, pseudo-types) +const DEAD_LINK_PATTERNS = [ + /namespaces\/JQuery\//i, + /namespaces\/jQuery\//i, + /type-aliases\/jQuery\.html/, + /variables\/jQuery\.html/, + /namespaces\/QUnit\//, + /variables\/QUnit\.html/, + /type-aliases\/(int|float)\.html/, +]; function shouldExclude(relPath) { // Exclude jQuery-related pages @@ -493,6 +556,23 @@ function renderDir(dir) { htmlFixed = htmlFixed.replace(/([^<]*)<\/a>/g, "$1"); htmlFixed = htmlFixed.replace(/([^<]*)<\/code><\/a>/g, "$1"); + // Strip links to ClassInfo (global type alias, no useful page) + htmlFixed = htmlFixed.replace( + /]*>([^<]+)<\/code><\/a>/g, + "$1" + ); + + // Strip links whose href points to dead targets (jQuery, QUnit, pseudo-types, nested namespaces) + htmlFixed = htmlFixed.replace(/]*>([^<]+<\/code>)<\/a>/g, (match, href, content) => { + if (DEAD_LINK_PATTERNS.some((p) => p.test(href))) return content; + // Links with nested namespaces/ segments are always dead TypeDoc artifacts + if ((href.match(/\/namespaces\//g) || []).length >= 2) return content; + return match; + }); + + // Remove TypeDoc "References" section (global namespace re-export artifacts) + htmlFixed = htmlFixed.replace(/

References<\/h2>[\s\S]*?(?=[^<]+<\/a><\/li>\n?/g, (match, href) => { + const target = join(dirname(full), href.split("#")[0]); + if (existsSync(target)) return match; + prunedEntries++; + return ""; + }); + if (html !== original) writeFileSync(full, html); + } + } +} +pruneReadmes(OUT_DIR); +if (prunedEntries > 0) console.log(` Removed ${prunedEntries} dead README entries`); + // --- Step 5: Generate sitemap --- console.log("\nStep 5: Generating sitemap..."); const sitemapEntries = []; From 69856d0f90525cb687e329da627b47c12a0412d0 Mon Sep 17 00:00:00 2001 From: akudev Date: Wed, 17 Jun 2026 13:19:51 +0200 Subject: [PATCH 2/4] =?UTF-8?q?fix:=20address=20review=20feedback=20?= =?UTF-8?q?=E2=80=94=20guard=20brace=20parsing,=20fix=20References=20regex?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Guard against missing opening brace in augmentation removal (indexOf returning -1 would corrupt the file) - Fix References section regex: htmlFixed is raw content before template wrapping, so