mirror of
https://github.com/arcodange-org/mediabunny.git
synced 2026-10-03 22:03:49 +02:00
Add "Used by" references to API docs
This commit is contained in:
+192
-26
@@ -1,6 +1,6 @@
|
|||||||
// This script has been 100% vibe-coded with Claude, meaning I literally haven't looked at any of the code. It's
|
// This script has been 100% vibe-coded with Claude (and Gemini!), meaning I literally haven't looked at any of the
|
||||||
// probably a mess, but it solves a one-off problem where only the output matters, and the output is indeed good, which
|
// code. It's probably a mess, but it solves a one-off problem where only the output matters, and the output is indeed
|
||||||
// is the point of a custom script for this: full, precise control.
|
// good, which is the point of a custom script for this: full, precise control.
|
||||||
|
|
||||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||||
@@ -386,7 +386,34 @@ const generateDocs = (entryFiles: string[], apiConfigFile: string) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Phase 2: Generate documentation for each symbol
|
// Data structures for "Used by" feature
|
||||||
|
const usedByReferences = new Map<string, Set<{ user: string; context: string; type: 'constructor' | 'method' | 'property' | 'extends' | 'type_param' | 'type_alias' | 'variable' | 'function' }>>();
|
||||||
|
const generatedDocs = new Map<string, string>();
|
||||||
|
|
||||||
|
const addUsage = (
|
||||||
|
used: string,
|
||||||
|
user: string,
|
||||||
|
context: string,
|
||||||
|
type: 'constructor' | 'method' | 'property' | 'extends' | 'type_param' | 'type_alias' | 'variable' | 'function',
|
||||||
|
) => {
|
||||||
|
// No self-references
|
||||||
|
if (used === user) return;
|
||||||
|
|
||||||
|
if (!usedByReferences.has(used)) {
|
||||||
|
usedByReferences.set(used, new Set());
|
||||||
|
}
|
||||||
|
const usageSet = usedByReferences.get(used)!;
|
||||||
|
|
||||||
|
// Check for duplicates before adding
|
||||||
|
for (const item of usageSet) {
|
||||||
|
if (item.user === user && item.context === context && item.type === type) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
usageSet.add({ user, context, type });
|
||||||
|
};
|
||||||
|
|
||||||
|
// Phase 2: Generate documentation for each symbol (and collect usage data)
|
||||||
allSymbols.forEach((exportSymbol) => {
|
allSymbols.forEach((exportSymbol) => {
|
||||||
const declaration = exportSymbol.valueDeclaration || exportSymbol.declarations?.[0];
|
const declaration = exportSymbol.valueDeclaration || exportSymbol.declarations?.[0];
|
||||||
if (!declaration) return;
|
if (!declaration) return;
|
||||||
@@ -492,9 +519,10 @@ const generateDocs = (entryFiles: string[], apiConfigFile: string) => {
|
|||||||
const allReferences = filterToExportedTypes([...new Set(allTypeStrings.flatMap(findAllTypeReferences))], variableName);
|
const allReferences = filterToExportedTypes([...new Set(allTypeStrings.flatMap(findAllTypeReferences))], variableName);
|
||||||
markdown += formatReferences(allReferences);
|
markdown += formatReferences(allReferences);
|
||||||
|
|
||||||
const outputPath = path.join(outputDir, `${variableName}.md`);
|
// In tandem: update usage map
|
||||||
fs.writeFileSync(outputPath, markdown);
|
allReferences.forEach(ref => addUsage(ref, variableName, variableName, 'function'));
|
||||||
console.log(`Generated: ${outputPath}`);
|
|
||||||
|
generatedDocs.set(variableName, markdown);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Handle regular variables
|
// Handle regular variables
|
||||||
@@ -507,9 +535,10 @@ const generateDocs = (entryFiles: string[], apiConfigFile: string) => {
|
|||||||
const references = filterToExportedTypes(findAllTypeReferences(variableValue), variableName);
|
const references = filterToExportedTypes(findAllTypeReferences(variableValue), variableName);
|
||||||
markdown += formatReferences(references);
|
markdown += formatReferences(references);
|
||||||
|
|
||||||
const outputPath = path.join(outputDir, `${variableName}.md`);
|
// In tandem: update usage map
|
||||||
fs.writeFileSync(outputPath, markdown);
|
references.forEach(ref => addUsage(ref, variableName, variableName, 'variable'));
|
||||||
console.log(`Generated: ${outputPath}`);
|
|
||||||
|
generatedDocs.set(variableName, markdown);
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
@@ -643,6 +672,9 @@ const generateDocs = (entryFiles: string[], apiConfigFile: string) => {
|
|||||||
if (typeParamReferencesText) {
|
if (typeParamReferencesText) {
|
||||||
typeParameters += typeParamReferencesText;
|
typeParameters += typeParamReferencesText;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// In tandem: update usage map
|
||||||
|
typeParamReferences.forEach(ref => addUsage(ref, className, className, 'type_param'));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for extends clause (only for classes/interfaces)
|
// Check for extends clause (only for classes/interfaces)
|
||||||
@@ -653,6 +685,11 @@ const generateDocs = (entryFiles: string[], apiConfigFile: string) => {
|
|||||||
if (extendsClauseNode && extendsClauseNode.types[0]) {
|
if (extendsClauseNode && extendsClauseNode.types[0]) {
|
||||||
const superClassName = extendsClauseNode.types[0].expression.getText();
|
const superClassName = extendsClauseNode.types[0].expression.getText();
|
||||||
extendsClause = `\n\n**Extends:** [\`${superClassName}\`](./${superClassName}.md)\n`;
|
extendsClause = `\n\n**Extends:** [\`${superClassName}\`](./${superClassName}.md)\n`;
|
||||||
|
|
||||||
|
// In tandem: update usage map
|
||||||
|
if (exportedTypes.has(superClassName)) {
|
||||||
|
addUsage(superClassName, className, className, 'extends');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -835,6 +872,9 @@ const generateDocs = (entryFiles: string[], apiConfigFile: string) => {
|
|||||||
const references = filterToExportedTypes(findAllTypeReferences(paramType), className);
|
const references = filterToExportedTypes(findAllTypeReferences(paramType), className);
|
||||||
const referencesText = formatReferences(references);
|
const referencesText = formatReferences(references);
|
||||||
|
|
||||||
|
// In tandem: update usage map
|
||||||
|
references.forEach(ref => addUsage(ref, className, paramName, 'property'));
|
||||||
|
|
||||||
const propertyContent = `### \`${paramName}\`\n\n\`\`\`ts\n${propertyDef}\n\`\`\`${desc ? `\n\n${desc}` : ''}${referencesText}`;
|
const propertyContent = `### \`${paramName}\`\n\n\`\`\`ts\n${propertyDef}\n\`\`\`${desc ? `\n\n${desc}` : ''}${referencesText}`;
|
||||||
properties.push(propertyContent);
|
properties.push(propertyContent);
|
||||||
});
|
});
|
||||||
@@ -905,6 +945,9 @@ const generateDocs = (entryFiles: string[], apiConfigFile: string) => {
|
|||||||
const overloadReferences = filterToExportedTypes([...new Set(ctorTypeStrings.flatMap(findAllTypeReferences))], className);
|
const overloadReferences = filterToExportedTypes([...new Set(ctorTypeStrings.flatMap(findAllTypeReferences))], className);
|
||||||
constructorBlock += formatReferences(overloadReferences, linkedTypes);
|
constructorBlock += formatReferences(overloadReferences, linkedTypes);
|
||||||
|
|
||||||
|
// In tandem: update usage map
|
||||||
|
overloadReferences.forEach(ref => addUsage(ref, className, className, 'constructor'));
|
||||||
|
|
||||||
constructorBlocks.push(constructorBlock);
|
constructorBlocks.push(constructorBlock);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -950,6 +993,9 @@ const generateDocs = (entryFiles: string[], apiConfigFile: string) => {
|
|||||||
const references = filterToExportedTypes(findAllTypeReferences(type), className);
|
const references = filterToExportedTypes(findAllTypeReferences(type), className);
|
||||||
const referencesText = formatReferences(references, linkedTypes);
|
const referencesText = formatReferences(references, linkedTypes);
|
||||||
|
|
||||||
|
// In tandem: update usage map
|
||||||
|
references.forEach(ref => addUsage(ref, className, name, 'property'));
|
||||||
|
|
||||||
// Check if this is an event handler (starts with "on" and can be a function)
|
// Check if this is an event handler (starts with "on" and can be a function)
|
||||||
const isEventHandler = name.startsWith('on') && (
|
const isEventHandler = name.startsWith('on') && (
|
||||||
type.includes('=>')
|
type.includes('=>')
|
||||||
@@ -993,6 +1039,9 @@ const generateDocs = (entryFiles: string[], apiConfigFile: string) => {
|
|||||||
const references = filterToExportedTypes(findAllTypeReferences(type), className);
|
const references = filterToExportedTypes(findAllTypeReferences(type), className);
|
||||||
const referencesText = formatReferences(references, linkedTypes);
|
const referencesText = formatReferences(references, linkedTypes);
|
||||||
|
|
||||||
|
// In tandem: update usage map
|
||||||
|
references.forEach(ref => addUsage(ref, className, name, 'property'));
|
||||||
|
|
||||||
const inheritedBadge = '';
|
const inheritedBadge = '';
|
||||||
properties.push(`### \`${name}\`${inheritedBadge}\n\n\`\`\`ts\n${accessorDef}\n\`\`\`${desc ? `\n\n${desc}` : ''}${referencesText}`);
|
properties.push(`### \`${name}\`${inheritedBadge}\n\n\`\`\`ts\n${accessorDef}\n\`\`\`${desc ? `\n\n${desc}` : ''}${referencesText}`);
|
||||||
} else if (ts.isSetAccessorDeclaration(member) && member.name) {
|
} else if (ts.isSetAccessorDeclaration(member) && member.name) {
|
||||||
@@ -1013,6 +1062,9 @@ const generateDocs = (entryFiles: string[], apiConfigFile: string) => {
|
|||||||
const references = filterToExportedTypes(findAllTypeReferences(paramType), className);
|
const references = filterToExportedTypes(findAllTypeReferences(paramType), className);
|
||||||
const referencesText = formatReferences(references, linkedTypes);
|
const referencesText = formatReferences(references, linkedTypes);
|
||||||
|
|
||||||
|
// In tandem: update usage map
|
||||||
|
references.forEach(ref => addUsage(ref, className, name, 'property'));
|
||||||
|
|
||||||
const inheritedBadge = '';
|
const inheritedBadge = '';
|
||||||
properties.push(`### \`${name}\`${inheritedBadge}\n\n\`\`\`ts\n${accessorDef}\n\`\`\`${desc ? `\n\n${desc}` : ''}${referencesText}`);
|
properties.push(`### \`${name}\`${inheritedBadge}\n\n\`\`\`ts\n${accessorDef}\n\`\`\`${desc ? `\n\n${desc}` : ''}${referencesText}`);
|
||||||
} else if ((ts.isMethodDeclaration(member) || ts.isMethodSignature(member)) && member.name) {
|
} else if ((ts.isMethodDeclaration(member) || ts.isMethodSignature(member)) && member.name) {
|
||||||
@@ -1106,6 +1158,9 @@ const generateDocs = (entryFiles: string[], apiConfigFile: string) => {
|
|||||||
const allReferences = filterToExportedTypes([...new Set(allTypeStrings.flatMap(findAllTypeReferences))], className);
|
const allReferences = filterToExportedTypes([...new Set(allTypeStrings.flatMap(findAllTypeReferences))], className);
|
||||||
methodContent += formatReferences(allReferences, linkedTypes);
|
methodContent += formatReferences(allReferences, linkedTypes);
|
||||||
|
|
||||||
|
// In tandem: update usage map
|
||||||
|
allReferences.forEach(ref => addUsage(ref, className, name, 'method'));
|
||||||
|
|
||||||
if (isStatic) {
|
if (isStatic) {
|
||||||
staticMethods.push(methodContent);
|
staticMethods.push(methodContent);
|
||||||
} else {
|
} else {
|
||||||
@@ -1174,6 +1229,9 @@ const generateDocs = (entryFiles: string[], apiConfigFile: string) => {
|
|||||||
const references = filterToExportedTypes(findAllTypeReferences(cleanedType), className);
|
const references = filterToExportedTypes(findAllTypeReferences(cleanedType), className);
|
||||||
const referencesText = formatReferences(references);
|
const referencesText = formatReferences(references);
|
||||||
|
|
||||||
|
// In tandem: update usage map
|
||||||
|
references.forEach(ref => addUsage(ref, className, propName, 'property'));
|
||||||
|
|
||||||
// Check if this is an event handler (starts with "on" and can be a function)
|
// Check if this is an event handler (starts with "on" and can be a function)
|
||||||
const isEventHandler = propName.startsWith('on') && (
|
const isEventHandler = propName.startsWith('on') && (
|
||||||
cleanedType.includes('=>')
|
cleanedType.includes('=>')
|
||||||
@@ -1252,19 +1310,39 @@ const generateDocs = (entryFiles: string[], apiConfigFile: string) => {
|
|||||||
|
|
||||||
// Add subclasses section for classes that have subclasses
|
// Add subclasses section for classes that have subclasses
|
||||||
if (ts.isClassDeclaration(declaration) && classHierarchy.has(className)) {
|
if (ts.isClassDeclaration(declaration) && classHierarchy.has(className)) {
|
||||||
const subclasses = classHierarchy.get(className)!;
|
// Recursively build hierarchical list
|
||||||
// Sort by definition order instead of alphabetically
|
const buildHierarchicalList = (parentClass: string, depth = 0, visited = new Set<string>()): string => {
|
||||||
subclasses.sort((a, b) => {
|
if (visited.has(parentClass)) return ''; // Prevent infinite loops
|
||||||
const orderA = symbolOrderMap.get(a);
|
visited.add(parentClass);
|
||||||
const orderB = symbolOrderMap.get(b);
|
|
||||||
if (orderA === undefined) throw new Error(`Symbol '${a}' not found in entry files export order`);
|
const directChildren = classHierarchy.get(parentClass) || [];
|
||||||
if (orderB === undefined) throw new Error(`Symbol '${b}' not found in entry files export order`);
|
if (directChildren.length === 0) return '';
|
||||||
return orderA - orderB;
|
|
||||||
});
|
// Sort children by definition order
|
||||||
markdown += `\n## Subclasses\n\n`;
|
const sortedChildren = [...directChildren].sort((a, b) => {
|
||||||
subclasses.forEach((sub) => {
|
const orderA = symbolOrderMap.get(a);
|
||||||
markdown += `- [\`${sub}\`](./${sub}.md)\n`;
|
const orderB = symbolOrderMap.get(b);
|
||||||
});
|
if (orderA === undefined) throw new Error(`Symbol '${a}' not found in entry files export order`);
|
||||||
|
if (orderB === undefined) throw new Error(`Symbol '${b}' not found in entry files export order`);
|
||||||
|
return orderA - orderB;
|
||||||
|
});
|
||||||
|
|
||||||
|
let result = '';
|
||||||
|
const indent = ' '.repeat(depth);
|
||||||
|
|
||||||
|
for (const child of sortedChildren) {
|
||||||
|
result += `${indent}- [\`${child}\`](./${child}.md)\n`;
|
||||||
|
// Recursively add children of this child
|
||||||
|
result += buildHierarchicalList(child, depth + 1, visited);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
const hierarchicalList = buildHierarchicalList(className);
|
||||||
|
if (hierarchicalList) {
|
||||||
|
markdown += `\n## Subclasses\n\n${hierarchicalList}`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add instances section for classes that have instances
|
// Add instances section for classes that have instances
|
||||||
@@ -1284,6 +1362,11 @@ const generateDocs = (entryFiles: string[], apiConfigFile: string) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add placeholder for "Used by" section for classes and interfaces
|
||||||
|
if (ts.isClassDeclaration(declaration) || ts.isInterfaceDeclaration(declaration)) {
|
||||||
|
markdown += '\n<!-- USED_BY_SECTION -->\n';
|
||||||
|
}
|
||||||
|
|
||||||
// Add type definition for type aliases
|
// Add type definition for type aliases
|
||||||
if (ts.isTypeAliasDeclaration(declaration) && declaration.type) {
|
if (ts.isTypeAliasDeclaration(declaration) && declaration.type) {
|
||||||
const resolvedType = typeChecker.getTypeAtLocation(declaration);
|
const resolvedType = typeChecker.getTypeAtLocation(declaration);
|
||||||
@@ -1346,7 +1429,13 @@ const generateDocs = (entryFiles: string[], apiConfigFile: string) => {
|
|||||||
const typeReferences = filterToExportedTypes([...new Set(allTypeRefs)], className);
|
const typeReferences = filterToExportedTypes([...new Set(allTypeRefs)], className);
|
||||||
const typeReferencesText = formatReferences(typeReferences);
|
const typeReferencesText = formatReferences(typeReferences);
|
||||||
|
|
||||||
|
// In tandem: update usage map
|
||||||
|
typeReferences.forEach(ref => addUsage(ref, className, className, 'type_alias'));
|
||||||
|
|
||||||
markdown += `\n\`\`\`ts\n${typeDefinition}\n\`\`\`${typeReferencesText}`;
|
markdown += `\n\`\`\`ts\n${typeDefinition}\n\`\`\`${typeReferencesText}`;
|
||||||
|
|
||||||
|
// Add placeholder for "Used by" section for type aliases
|
||||||
|
markdown += '\n<!-- USED_BY_SECTION -->\n';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeParameters) {
|
if (typeParameters) {
|
||||||
@@ -1372,12 +1461,89 @@ const generateDocs = (entryFiles: string[], apiConfigFile: string) => {
|
|||||||
if (methods.length > 0) {
|
if (methods.length > 0) {
|
||||||
markdown += `\n## Methods\n\n${methods.join('\n\n')}\n`;
|
markdown += `\n## Methods\n\n${methods.join('\n\n')}\n`;
|
||||||
}
|
}
|
||||||
const outputPath = path.join(outputDir, `${className}.md`);
|
|
||||||
fs.writeFileSync(outputPath, markdown);
|
generatedDocs.set(className, markdown);
|
||||||
console.log(`Generated: ${outputPath}`);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Phase 3: Assemble final docs with "Used by" sections and write files
|
||||||
|
generatedDocs.forEach((markdown, symbolName) => {
|
||||||
|
const usages = usedByReferences.get(symbolName);
|
||||||
|
let usedByMarkdown = '';
|
||||||
|
|
||||||
|
if (usages && usages.size > 0) {
|
||||||
|
const symbolSubclasses = classHierarchy.get(symbolName) || [];
|
||||||
|
|
||||||
|
const usedByLines = [...usages]
|
||||||
|
.filter((usage) => {
|
||||||
|
// Filter out subclasses from "Used by" since they already appear in "Subclasses" section
|
||||||
|
if (usage.type === 'extends' && symbolSubclasses.includes(usage.user)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter out top-level type references when there are more specific contexts available
|
||||||
|
// This prevents redundancy where both "TypeName" and "TypeName.property" appear
|
||||||
|
if (usage.type === 'type_alias' || usage.type === 'type_param') {
|
||||||
|
// Check if there are more specific usages from the same user (property, method, constructor, etc.)
|
||||||
|
const hasMoreSpecificUsage = [...usages].some(otherUsage =>
|
||||||
|
otherUsage.user === usage.user
|
||||||
|
&& otherUsage.type !== 'type_alias'
|
||||||
|
&& otherUsage.type !== 'type_param'
|
||||||
|
&& otherUsage.type !== 'extends',
|
||||||
|
);
|
||||||
|
if (hasMoreSpecificUsage) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
})
|
||||||
|
.map((usage) => {
|
||||||
|
let displayText = '';
|
||||||
|
let link = '';
|
||||||
|
|
||||||
|
switch (usage.type) {
|
||||||
|
case 'constructor':
|
||||||
|
displayText = `new ${usage.user}()`;
|
||||||
|
link = `./${usage.user}.md#constructor`;
|
||||||
|
break;
|
||||||
|
case 'method':
|
||||||
|
displayText = `${usage.user}.${usage.context}()`;
|
||||||
|
link = `./${usage.user}.md#${usage.context.toLowerCase()}`;
|
||||||
|
break;
|
||||||
|
case 'property':
|
||||||
|
case 'variable':
|
||||||
|
displayText = `${usage.user}.${usage.context}`;
|
||||||
|
link = `./${usage.user}.md#${usage.context.toLowerCase()}`;
|
||||||
|
break;
|
||||||
|
case 'function':
|
||||||
|
displayText = `${usage.user}()`;
|
||||||
|
link = `./${usage.user}.md`;
|
||||||
|
break;
|
||||||
|
case 'extends':
|
||||||
|
case 'type_param':
|
||||||
|
case 'type_alias':
|
||||||
|
displayText = usage.user;
|
||||||
|
link = `./${usage.user}.md`;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return { text: `[\`${displayText}\`](${link})`, sortKey: displayText.toLowerCase() };
|
||||||
|
});
|
||||||
|
|
||||||
|
if (usedByLines.length > 0) {
|
||||||
|
// Sort alphabetically by display text
|
||||||
|
usedByLines.sort((a, b) => a.sortKey.localeCompare(b.sortKey));
|
||||||
|
const listItems = usedByLines.map(item => `- ${item.text}`).join('\n');
|
||||||
|
usedByMarkdown = `\n## Used by\n\n${listItems}\n`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const finalMarkdown = markdown.replace('<!-- USED_BY_SECTION -->', usedByMarkdown);
|
||||||
|
const outputPath = path.join(outputDir, `${symbolName}.md`);
|
||||||
|
fs.writeFileSync(outputPath, finalMarkdown);
|
||||||
|
console.log(`Generated: ${outputPath}`);
|
||||||
|
});
|
||||||
|
|
||||||
// Generate index.md with all exported symbols grouped by group
|
// Generate index.md with all exported symbols grouped by group
|
||||||
const entriesByGroup = new Map<string, Array<{ name: string; type: string; order: number }>>();
|
const entriesByGroup = new Map<string, Array<{ name: string; type: string; order: number }>>();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user