245 lines
8.0 KiB
TypeScript
245 lines
8.0 KiB
TypeScript
import { SourceFile } from "./source";
|
|
import { Comments } from "./comments";
|
|
|
|
// @ts-ignore
|
|
import fs from "fs";
|
|
import { parsePythonDocstrings } from "./python";
|
|
|
|
export interface ReferenceDocConfiguration {
|
|
sourcePath: string;
|
|
destinationPath: string;
|
|
className?: string;
|
|
component?: string;
|
|
hook?: string;
|
|
description?: string;
|
|
title?: string;
|
|
pythonSymbols?: string[];
|
|
typescriptSymbols?: string[];
|
|
}
|
|
|
|
export class ReferenceDoc {
|
|
constructor(private readonly referenceDoc: ReferenceDocConfiguration) {}
|
|
|
|
async generate() {
|
|
let generatedDocumentation: string | null = null;
|
|
if (this.referenceDoc.pythonSymbols) {
|
|
generatedDocumentation = this.generatedDocsPythonSymbols();
|
|
} else if (this.referenceDoc.typescriptSymbols) {
|
|
generatedDocumentation = await this.generatedDocsTypeScriptSymbols();
|
|
} else {
|
|
generatedDocumentation = await this.generatedDocsTypeScript();
|
|
}
|
|
if (generatedDocumentation) {
|
|
const dest = this.referenceDoc.destinationPath;
|
|
// Ensure parent directory exists so retargeting to a fresh tree
|
|
// (e.g. shell-docs) doesn't require manual mkdir.
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
(fs as any).mkdirSync(dest.split("/").slice(0, -1).join("/"), {
|
|
recursive: true,
|
|
});
|
|
fs.writeFileSync(dest, generatedDocumentation);
|
|
console.log(
|
|
`Successfully autogenerated ${dest} from ${this.referenceDoc.sourcePath}`,
|
|
);
|
|
}
|
|
}
|
|
|
|
generateTitle(
|
|
title: string,
|
|
description: string,
|
|
sourcePath: string,
|
|
): string {
|
|
let result = `---\n`;
|
|
result += `title: "${title}"\n`;
|
|
if (description) {
|
|
result += `description: "${description}"\n`;
|
|
}
|
|
result += `---\n\n`;
|
|
|
|
result += `{\n`;
|
|
result += ` /*\n`;
|
|
result += ` * ATTENTION! DO NOT MODIFY THIS FILE!\n`;
|
|
result += ` * This page is auto-generated. If you want to make any changes to this page, changes must be made at:\n`;
|
|
result += ` * ${sourcePath}\n`;
|
|
result += ` */\n`;
|
|
result += `}\n`;
|
|
return result;
|
|
}
|
|
|
|
generatedDocsPythonSymbols(): string | null {
|
|
const content = fs.readFileSync(this.referenceDoc.sourcePath, "utf8");
|
|
const parsed = parsePythonDocstrings(
|
|
this.referenceDoc.pythonSymbols!,
|
|
content,
|
|
);
|
|
let result = this.generateTitle(
|
|
this.referenceDoc.title || "",
|
|
this.referenceDoc.description || "",
|
|
this.referenceDoc.sourcePath,
|
|
);
|
|
for (const fn of this.referenceDoc.pythonSymbols!) {
|
|
if (fn in parsed) {
|
|
const fnDoc = parsed[fn];
|
|
result += `## ${fn}\n\n`;
|
|
result += `${fnDoc.description}\n\n`;
|
|
if (fnDoc.parameters) {
|
|
result += `### Parameters\n\n`;
|
|
for (const param of fnDoc.parameters) {
|
|
const required = !param.type.startsWith("Optional");
|
|
result += `<PropertyReference name="${param.name}" type="${param.type}" ${required ? "required" : ""}> \n`;
|
|
result += `${param.description}\n`;
|
|
result += `</PropertyReference>\n\n`;
|
|
}
|
|
}
|
|
if (fnDoc.returns) {
|
|
result += `### Returns\n\n`;
|
|
result += `<PropertyReference name="returns" type="${fnDoc.returns.type}">\n`;
|
|
result += `${fnDoc.returns.description}\n`;
|
|
result += `</PropertyReference>\n\n`;
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
async generatedDocsTypeScriptSymbols(): Promise<string | null> {
|
|
const source = new SourceFile(this.referenceDoc.sourcePath);
|
|
await source.parse();
|
|
|
|
let result = this.generateTitle(
|
|
this.referenceDoc.title || "",
|
|
this.referenceDoc.description || "",
|
|
this.referenceDoc.sourcePath,
|
|
);
|
|
for (const fn of this.referenceDoc.typescriptSymbols!) {
|
|
const args = source.getFunctionArguments(fn);
|
|
if (!args) {
|
|
console.warn(`${fn} not found in ${this.referenceDoc.sourcePath}`);
|
|
continue;
|
|
}
|
|
const comment = source.getFunctionComment(fn);
|
|
|
|
result += `## ${fn}\n\n`;
|
|
if (comment) {
|
|
result += `${comment}\n\n`;
|
|
}
|
|
if (args) {
|
|
result += `### Parameters\n\n`;
|
|
for (const arg of args) {
|
|
result += `<PropertyReference name="${arg.name}" type="${arg.type}" ${arg.required ? "required" : ""} ${arg.defaultValue ? `default="${arg.defaultValue}"` : ""}> \n`;
|
|
result += `${arg.description}\n`;
|
|
result += `</PropertyReference>\n\n`;
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
async generatedDocsTypeScript(): Promise<string | null> {
|
|
const source = new SourceFile(this.referenceDoc.sourcePath);
|
|
await source.parse();
|
|
|
|
const comment = Comments.getFirstCommentBlock(source.sourceFile);
|
|
|
|
if (!comment) {
|
|
console.warn(`No comment found for ${this.referenceDoc.sourcePath}`);
|
|
console.warn("Skipping...");
|
|
return null;
|
|
}
|
|
|
|
// handle imports
|
|
const slashes = this.referenceDoc.destinationPath.split("/").length;
|
|
let importPathPrefix = "";
|
|
for (let i = 0; i < slashes - 2; i++) {
|
|
importPathPrefix += "../";
|
|
}
|
|
|
|
let result = this.generateTitle(
|
|
this.referenceDoc.className ||
|
|
this.referenceDoc.component ||
|
|
this.referenceDoc.hook ||
|
|
"",
|
|
this.referenceDoc.description || "",
|
|
this.referenceDoc.sourcePath,
|
|
);
|
|
|
|
result += `${comment}\n\n`;
|
|
|
|
const arg0Interface = await source.getArg0Interface(
|
|
this.referenceDoc.className ||
|
|
this.referenceDoc.component ||
|
|
this.referenceDoc.hook ||
|
|
"",
|
|
);
|
|
|
|
if (arg0Interface) {
|
|
const hasProperties = arg0Interface.properties.length > 0;
|
|
if (this.referenceDoc.hook && hasProperties) {
|
|
result += `## Parameters\n\n`;
|
|
} else if (this.referenceDoc.component && hasProperties) {
|
|
result += `## Properties\n\n`;
|
|
} else if (this.referenceDoc.className && hasProperties) {
|
|
result += `## Constructor Parameters\n\n`;
|
|
}
|
|
|
|
for (const property of arg0Interface.properties) {
|
|
if (property.comment.includes("@deprecated")) {
|
|
continue;
|
|
}
|
|
const type = property.type.replace(/"/g, "'");
|
|
|
|
result += `<PropertyReference name="${property.name}" type="${type}" ${property.required ? "required" : ""} ${property.defaultValue ? `default="${property.defaultValue}"` : ""}> \n`;
|
|
result += `${property.comment}\n`;
|
|
result += `</PropertyReference>\n\n`;
|
|
}
|
|
} else if (this.referenceDoc.className) {
|
|
const constr = source.getConstructorDefinition(
|
|
this.referenceDoc.className,
|
|
);
|
|
if (constr) {
|
|
result += `## ${constr.signature}\n\n`;
|
|
result += `${constr.comment}\n\n`;
|
|
for (const param of constr.parameters) {
|
|
const type = param.type.replace(/"/g, "'");
|
|
|
|
result += `<PropertyReference name="${param.name}" type="${type}" ${param.required ? "required" : ""}>\n`;
|
|
result += `${param.comment}\n`;
|
|
result += `</PropertyReference>\n\n`;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (this.referenceDoc.className) {
|
|
const methodDefinitions = await source.getPublicMethodDefinitions(
|
|
this.referenceDoc.className,
|
|
);
|
|
|
|
for (const method of methodDefinitions) {
|
|
if (
|
|
method.signature ===
|
|
"process(request: CopilotRuntimeChatCompletionRequest)" ||
|
|
method.signature === "process(request: CopilotRuntimeRequest)"
|
|
) {
|
|
// skip the process method
|
|
continue;
|
|
}
|
|
|
|
const methodName = method.signature.split("(")[0];
|
|
const methodArgs = method.signature.split("(")[1].split(")")[0];
|
|
result += `<PropertyReference name="${methodName}" type="${methodArgs}">\n`;
|
|
result += `${method.comment}\n\n`;
|
|
for (const param of method.parameters) {
|
|
const type = param.type.replace(/"/g, "'");
|
|
|
|
result += ` <PropertyReference name="${param.name}" type="${type}" ${param.required ? "required" : ""}>\n`;
|
|
result += ` ${param.comment}\n`;
|
|
result += ` </PropertyReference>\n\n`;
|
|
}
|
|
result += `</PropertyReference>\n\n`;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
}
|