chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,33 @@
|
||||
import path from "path";
|
||||
import { REFERENCE_DOCS } from "./lib/files";
|
||||
import { ReferenceDoc } from "./lib/reference-doc";
|
||||
|
||||
// Resolve all source/destination paths relative to the repo root so the
|
||||
// pipeline behaves the same regardless of where it's invoked from.
|
||||
const repoRoot = path.resolve(__dirname, "..", "..");
|
||||
process.chdir(repoRoot);
|
||||
|
||||
// allSettled so one missing source (e.g. an SDK file that was renamed
|
||||
// upstream) doesn't abort the entire pipeline — we still want every other
|
||||
// reference page to land in shell-docs.
|
||||
Promise.allSettled(
|
||||
REFERENCE_DOCS.map(async (referenceDoc) => {
|
||||
const doc = new ReferenceDoc(referenceDoc);
|
||||
await doc.generate();
|
||||
}),
|
||||
).then((results) => {
|
||||
const failures = results
|
||||
.map((r, i) => ({ r, i }))
|
||||
.filter(({ r }) => r.status === "rejected");
|
||||
for (const { r, i } of failures) {
|
||||
const reason = (r as PromiseRejectedResult).reason;
|
||||
console.error(
|
||||
`[gen] Failed: ${REFERENCE_DOCS[i].destinationPath}: ${
|
||||
reason instanceof Error ? reason.message : String(reason)
|
||||
}`,
|
||||
);
|
||||
}
|
||||
console.log(
|
||||
`All reference docs processed (${results.length - failures.length}/${results.length} succeeded)`,
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,114 @@
|
||||
import * as ts from "typescript";
|
||||
|
||||
export class Comments {
|
||||
static getCleanedCommentsForNode(
|
||||
node: ts.Node,
|
||||
sourceFile: ts.SourceFile,
|
||||
): string {
|
||||
const fullText = sourceFile.getFullText();
|
||||
const commentRanges = ts.getLeadingCommentRanges(
|
||||
fullText,
|
||||
node.getFullStart(),
|
||||
);
|
||||
|
||||
if (!commentRanges) return "";
|
||||
|
||||
return commentRanges
|
||||
.map((comment) => {
|
||||
let commentText = fullText.substring(comment.pos, comment.end);
|
||||
commentText = Comments.removeCommentSyntax(commentText);
|
||||
|
||||
// for now, remove @default annotations
|
||||
commentText = commentText
|
||||
.split("\n")
|
||||
.filter((line) => !line.includes("@default"))
|
||||
.join("\n");
|
||||
|
||||
return commentText;
|
||||
})
|
||||
.join("\n")
|
||||
.trim();
|
||||
}
|
||||
|
||||
static getDefaultValueForNode(
|
||||
node: ts.Node,
|
||||
sourceFile: ts.SourceFile,
|
||||
): string | undefined {
|
||||
const fullText = sourceFile.getFullText();
|
||||
const commentRanges = ts.getLeadingCommentRanges(
|
||||
fullText,
|
||||
node.getFullStart(),
|
||||
);
|
||||
|
||||
if (!commentRanges) return "";
|
||||
let defaultValue: string | undefined = undefined;
|
||||
|
||||
for (const comment of commentRanges) {
|
||||
let commentText = fullText.substring(comment.pos, comment.end);
|
||||
commentText = Comments.removeCommentSyntax(commentText);
|
||||
|
||||
for (const line of commentText.split("\n")) {
|
||||
if (line.includes("@default")) {
|
||||
defaultValue = line.split("@default")[1].trim();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (defaultValue !== undefined) break;
|
||||
}
|
||||
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
static removeCommentSyntax(commentText: string): string {
|
||||
return commentText
|
||||
.replace(/\/\*\*|\*\/|\*|\/\* ?/gm, "")
|
||||
.replace(/^ /gm, "")
|
||||
.trim();
|
||||
}
|
||||
|
||||
static getFirstCommentBlock(sourceFile: ts.SourceFile): string | null {
|
||||
for (const statement of sourceFile.statements) {
|
||||
const comments = Comments.getCleanedCommentsForNode(
|
||||
statement,
|
||||
sourceFile,
|
||||
);
|
||||
if (comments) return comments;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
static getTsDocCommentsForFunction(node: ts.Node, sourceFile: ts.SourceFile) {
|
||||
const params: Record<string, string> = {};
|
||||
const trivia =
|
||||
ts.getLeadingCommentRanges(sourceFile.text, node.getFullStart()) || [];
|
||||
|
||||
let comment = "";
|
||||
|
||||
for (const range of trivia) {
|
||||
const commentText = Comments.removeCommentSyntax(
|
||||
sourceFile.text.substring(range.pos, range.end),
|
||||
);
|
||||
|
||||
const lines = commentText.split("\n").map((line) => line.trim());
|
||||
|
||||
if (lines.length && !lines[0].startsWith("@param")) {
|
||||
comment = lines[0];
|
||||
}
|
||||
|
||||
lines.forEach((line) => {
|
||||
if (line.startsWith("@param")) {
|
||||
const parts = line.split(/\s+/);
|
||||
if (parts.length >= 3) {
|
||||
const paramName = parts[1];
|
||||
const description = parts.slice(2).join(" ");
|
||||
params[paramName] = description.trim();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return { comment, params };
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,243 @@
|
||||
import type { ReferenceDocConfiguration } from "./reference-doc";
|
||||
|
||||
export const REFERENCE_DOCS: ReferenceDocConfiguration[] = [
|
||||
/* Runtime */
|
||||
{
|
||||
sourcePath:
|
||||
"packages/runtime/src/service-adapters/google/google-genai-adapter.ts",
|
||||
destinationPath:
|
||||
"showcase/shell-docs/src/content/reference/v1/classes/llm-adapters/GoogleGenerativeAIAdapter.mdx",
|
||||
className: "GoogleGenerativeAIAdapter",
|
||||
description:
|
||||
"Copilot Runtime adapter for Google Generative AI (e.g. Gemini).",
|
||||
},
|
||||
{
|
||||
sourcePath: "packages/runtime/src/service-adapters/groq/groq-adapter.ts",
|
||||
destinationPath:
|
||||
"showcase/shell-docs/src/content/reference/v1/classes/llm-adapters/GroqAdapter.mdx",
|
||||
className: "GroqAdapter",
|
||||
description: "Copilot Runtime adapter for Groq.",
|
||||
},
|
||||
{
|
||||
sourcePath:
|
||||
"packages/runtime/src/service-adapters/langchain/langchain-adapter.ts",
|
||||
destinationPath:
|
||||
"showcase/shell-docs/src/content/reference/v1/classes/llm-adapters/LangChainAdapter.mdx",
|
||||
className: "LangChainAdapter",
|
||||
description: "Copilot Runtime adapter for LangChain.",
|
||||
},
|
||||
{
|
||||
sourcePath:
|
||||
"packages/runtime/src/service-adapters/openai/openai-adapter.ts",
|
||||
destinationPath:
|
||||
"showcase/shell-docs/src/content/reference/v1/classes/llm-adapters/OpenAIAdapter.mdx",
|
||||
className: "OpenAIAdapter",
|
||||
description: "Copilot Runtime adapter for OpenAI.",
|
||||
},
|
||||
{
|
||||
sourcePath:
|
||||
"packages/runtime/src/service-adapters/openai/openai-assistant-adapter.ts",
|
||||
destinationPath:
|
||||
"showcase/shell-docs/src/content/reference/v1/classes/llm-adapters/OpenAIAssistantAdapter.mdx",
|
||||
className: "OpenAIAssistantAdapter",
|
||||
description: "Copilot Runtime adapter for OpenAI Assistant API.",
|
||||
},
|
||||
{
|
||||
sourcePath:
|
||||
"packages/runtime/src/service-adapters/anthropic/anthropic-adapter.ts",
|
||||
destinationPath:
|
||||
"showcase/shell-docs/src/content/reference/v1/classes/llm-adapters/AnthropicAdapter.mdx",
|
||||
className: "AnthropicAdapter",
|
||||
description: "Copilot Runtime adapter for Anthropic.",
|
||||
},
|
||||
/* Classes */
|
||||
{
|
||||
sourcePath: "packages/react-core/src/lib/copilot-task.ts",
|
||||
destinationPath:
|
||||
"showcase/shell-docs/src/content/reference/v1/classes/CopilotTask.mdx",
|
||||
className: "CopilotTask",
|
||||
description:
|
||||
"CopilotTask is used to execute one-off tasks, for example on button click.",
|
||||
},
|
||||
{
|
||||
sourcePath: "packages/runtime/src/lib/runtime/copilot-runtime.ts",
|
||||
destinationPath:
|
||||
"showcase/shell-docs/src/content/reference/v1/classes/CopilotRuntime.mdx",
|
||||
className: "CopilotRuntime",
|
||||
description:
|
||||
"Copilot Runtime is the back-end component of CopilotKit, enabling interaction with LLMs.",
|
||||
},
|
||||
/* Components */
|
||||
{
|
||||
sourcePath: "packages/react-ui/src/components/chat/Chat.tsx",
|
||||
destinationPath:
|
||||
"showcase/shell-docs/src/content/reference/v1/components/chat/CopilotChat.mdx",
|
||||
component: "CopilotChat",
|
||||
description:
|
||||
"The CopilotChat component, providing a chat interface for interacting with your copilot.",
|
||||
},
|
||||
{
|
||||
sourcePath:
|
||||
"packages/react-core/src/components/copilot-provider/copilotkit.tsx",
|
||||
destinationPath:
|
||||
"showcase/shell-docs/src/content/reference/v1/components/CopilotKit.mdx",
|
||||
component: "CopilotKit",
|
||||
description:
|
||||
"The CopilotKit provider component, wrapping your application.",
|
||||
},
|
||||
{
|
||||
sourcePath: "packages/react-ui/src/components/chat/Popup.tsx",
|
||||
destinationPath:
|
||||
"showcase/shell-docs/src/content/reference/v1/components/chat/CopilotPopup.mdx",
|
||||
component: "CopilotPopup",
|
||||
description:
|
||||
"The CopilotPopup component, providing a popup interface for interacting with your copilot.",
|
||||
},
|
||||
{
|
||||
sourcePath: "packages/react-ui/src/components/chat/Sidebar.tsx",
|
||||
destinationPath:
|
||||
"showcase/shell-docs/src/content/reference/v1/components/chat/CopilotSidebar.mdx",
|
||||
component: "CopilotSidebar",
|
||||
description:
|
||||
"The CopilotSidebar component, providing a sidebar interface for interacting with your copilot.",
|
||||
},
|
||||
{
|
||||
sourcePath:
|
||||
"packages/react-textarea/src/components/copilot-textarea/copilot-textarea.tsx",
|
||||
destinationPath:
|
||||
"showcase/shell-docs/src/content/reference/v1/components/CopilotTextarea.mdx",
|
||||
component: "CopilotTextarea",
|
||||
description:
|
||||
"An AI-powered textarea component for your application, which serves as a drop-in replacement for any textarea.",
|
||||
},
|
||||
/* Hooks */
|
||||
{
|
||||
sourcePath: "packages/react-core/src/hooks/use-copilot-chat.ts",
|
||||
destinationPath:
|
||||
"showcase/shell-docs/src/content/reference/v1/hooks/useCopilotChat.mdx",
|
||||
hook: "useCopilotChat",
|
||||
},
|
||||
{
|
||||
sourcePath: "packages/react-core/src/hooks/use-copilot-chat-headless_c.ts",
|
||||
destinationPath:
|
||||
"showcase/shell-docs/src/content/reference/v1/hooks/useCopilotChatHeadless_c.mdx",
|
||||
hook: "useCopilotChatHeadless_c",
|
||||
},
|
||||
{
|
||||
sourcePath: "packages/react-ui/src/hooks/use-copilot-chat-suggestions.tsx",
|
||||
destinationPath:
|
||||
"showcase/shell-docs/src/content/reference/v1/hooks/useCopilotChatSuggestions.mdx",
|
||||
hook: "useCopilotChatSuggestions",
|
||||
description:
|
||||
"The useCopilotChatSuggestions hook generates suggestions in the chat window based on real-time app state.",
|
||||
},
|
||||
{
|
||||
sourcePath: "packages/react-core/src/hooks/use-copilot-readable.ts",
|
||||
destinationPath:
|
||||
"showcase/shell-docs/src/content/reference/v1/hooks/useCopilotReadable.mdx",
|
||||
hook: "useCopilotReadable",
|
||||
description:
|
||||
"The useCopilotReadable hook allows you to provide knowledge to your copilot (e.g. application state).",
|
||||
},
|
||||
{
|
||||
sourcePath: "packages/react-core/src/hooks/use-coagent-state-render.ts",
|
||||
destinationPath:
|
||||
"showcase/shell-docs/src/content/reference/v1/hooks/useCoAgentStateRender.mdx",
|
||||
hook: "useCoAgentStateRender",
|
||||
description:
|
||||
"The useCoAgentStateRender hook allows you to render the state of the agent in the chat.",
|
||||
},
|
||||
{
|
||||
sourcePath: "packages/react-core/src/hooks/use-coagent.ts",
|
||||
destinationPath:
|
||||
"showcase/shell-docs/src/content/reference/v1/hooks/useCoAgent.mdx",
|
||||
hook: "useCoAgent",
|
||||
description:
|
||||
"The useCoAgent hook allows you to share state bidirectionally between your application and the agent.",
|
||||
},
|
||||
{
|
||||
sourcePath:
|
||||
"packages/react-core/src/hooks/use-copilot-additional-instructions.ts",
|
||||
destinationPath:
|
||||
"showcase/shell-docs/src/content/reference/v1/hooks/useCopilotAdditionalInstructions.mdx",
|
||||
hook: "useCopilotAdditionalInstructions",
|
||||
description:
|
||||
"The useCopilotAdditionalInstructions hook allows you to provide additional instructions to the agent.",
|
||||
},
|
||||
/* SDKs */
|
||||
|
||||
{
|
||||
sourcePath: "sdk-python/copilotkit/langgraph.py",
|
||||
destinationPath:
|
||||
"showcase/shell-docs/src/content/reference/sdk/python/LangGraph.mdx",
|
||||
title: "LangGraph SDK",
|
||||
description:
|
||||
"The CopilotKit LangGraph SDK for Python allows you to build and run LangGraph workflows with CopilotKit.",
|
||||
pythonSymbols: [
|
||||
"copilotkit_customize_config",
|
||||
"copilotkit_exit",
|
||||
"copilotkit_emit_state",
|
||||
"copilotkit_emit_message",
|
||||
"copilotkit_emit_tool_call",
|
||||
],
|
||||
},
|
||||
{
|
||||
sourcePath: "sdk-python/copilotkit/crewai/crewai_sdk.py",
|
||||
destinationPath:
|
||||
"showcase/shell-docs/src/content/reference/sdk/python/CrewAI.mdx",
|
||||
title: "CrewAI SDK",
|
||||
description:
|
||||
"The CopilotKit CrewAI SDK for Python allows you to build and run CrewAI agents with CopilotKit.",
|
||||
pythonSymbols: [
|
||||
"copilotkit_emit_state",
|
||||
"copilotkit_predict_state",
|
||||
"copilotkit_exit",
|
||||
"copilotkit_emit_message",
|
||||
"copilotkit_emit_tool_call",
|
||||
],
|
||||
},
|
||||
|
||||
/* Agents */
|
||||
{
|
||||
sourcePath: "sdk-python/copilotkit/langgraph_agui_agent.py",
|
||||
destinationPath:
|
||||
"showcase/shell-docs/src/content/reference/sdk/python/LangGraphAGUIAgent.mdx",
|
||||
title: "LangGraphAGUIAgent",
|
||||
description:
|
||||
"LangGraphAGUIAgent lets you define your agent for use with CopilotKit.",
|
||||
pythonSymbols: ["LangGraphAGUIAgent", "CopilotKitConfig"],
|
||||
},
|
||||
{
|
||||
sourcePath: "sdk-python/copilotkit/crewai/crewai_agent.py",
|
||||
destinationPath:
|
||||
"showcase/shell-docs/src/content/reference/sdk/python/CrewAIAgent.mdx",
|
||||
title: "CrewAIAgent",
|
||||
description:
|
||||
"CrewAIAgent lets you define your agent for use with CopilotKit.",
|
||||
pythonSymbols: ["CrewAIAgent", "CopilotKitConfig"],
|
||||
},
|
||||
{
|
||||
sourcePath: "sdk-python/copilotkit/sdk.py",
|
||||
destinationPath:
|
||||
"showcase/shell-docs/src/content/reference/sdk/python/RemoteEndpoints.mdx",
|
||||
title: "Remote Endpoints",
|
||||
description:
|
||||
"CopilotKit Remote Endpoints allow you to connect actions and agents written in Python to your CopilotKit application.",
|
||||
pythonSymbols: ["CopilotKitRemoteEndpoint", "CopilotKitContext"],
|
||||
},
|
||||
{
|
||||
sourcePath: "packages/sdk-js/src/langgraph/index.ts",
|
||||
destinationPath:
|
||||
"showcase/shell-docs/src/content/reference/sdk/js/LangGraph.mdx",
|
||||
title: "LangGraph SDK",
|
||||
description:
|
||||
"The CopilotKit LangGraph SDK for JavaScript allows you to build and run LangGraph workflows with CopilotKit.",
|
||||
typescriptSymbols: [
|
||||
"copilotkitCustomizeConfig",
|
||||
"copilotkitExit",
|
||||
"copilotkitEmitState",
|
||||
"copilotkitEmitMessage",
|
||||
"copilotkitEmitToolCall",
|
||||
],
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,192 @@
|
||||
interface ParsedFunctionDoc {
|
||||
description: string;
|
||||
parameters: Array<{
|
||||
name: string;
|
||||
type: string;
|
||||
description: string;
|
||||
}>;
|
||||
returns: {
|
||||
type: string;
|
||||
description: string;
|
||||
} | null;
|
||||
}
|
||||
/**
|
||||
* Return an array of parameter objects from a block of text that
|
||||
* looks like a NumPy docstring parameters section.
|
||||
*/
|
||||
function parseParameters(rawParameters: string) {
|
||||
// Split by lines.
|
||||
const lines = rawParameters.split("\n");
|
||||
|
||||
interface Param {
|
||||
name: string;
|
||||
type: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
const parameters: Param[] = [];
|
||||
let currentParam: Param | null = null;
|
||||
|
||||
// Regex for a parameter heading, e.g.:
|
||||
// base_config : Optional[RunnableConfig]
|
||||
// or with indentation, e.g.:
|
||||
// base_config : Optional[RunnableConfig]
|
||||
const paramHeadingRegex = /^[ \t]*([A-Za-z_]\w*)\s*:\s*(.*)$/;
|
||||
|
||||
for (const line of lines) {
|
||||
const trimmedLine = line.trim();
|
||||
|
||||
// 1) If line matches the heading format, that's a *new* parameter
|
||||
const headingMatch = line.match(paramHeadingRegex);
|
||||
if (headingMatch) {
|
||||
// If we were building a previous param, push it first
|
||||
if (currentParam) {
|
||||
parameters.push(currentParam);
|
||||
}
|
||||
|
||||
// Start a new param
|
||||
const pName = headingMatch[1];
|
||||
const pType = headingMatch[2];
|
||||
currentParam = {
|
||||
name: pName.trim(),
|
||||
type: pType.trim(),
|
||||
description: "", // we’ll accumulate description lines
|
||||
};
|
||||
continue;
|
||||
}
|
||||
|
||||
// 2) If it’s not a heading line and we have a current param, treat it as description
|
||||
if (currentParam && trimmedLine) {
|
||||
// Add a space if we already have some text
|
||||
if (currentParam.description.length > 0) {
|
||||
currentParam.description += " ";
|
||||
}
|
||||
currentParam.description += trimmedLine;
|
||||
}
|
||||
}
|
||||
|
||||
// Push the last param if we have one
|
||||
if (currentParam) {
|
||||
parameters.push(currentParam);
|
||||
}
|
||||
|
||||
return parameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses out function docstrings for the given Python functions (by name).
|
||||
*
|
||||
* @param functionNames - The names of the functions to parse
|
||||
* @param fileContent - The entire Python file content
|
||||
* @returns A record where each key is a function name, and the value is the parsed doc info
|
||||
*/
|
||||
export function parsePythonDocstrings(
|
||||
functionNames: string[],
|
||||
fileContent: string,
|
||||
): Record<string, ParsedFunctionDoc> {
|
||||
const results: Record<string, ParsedFunctionDoc> = {};
|
||||
|
||||
// Regex to capture:
|
||||
// 1) Optional leading "async"
|
||||
// 2) `def`
|
||||
// 3) function name
|
||||
// 4) Anything until the `"""` docstring start
|
||||
// 5) The content inside the triple quotes
|
||||
//
|
||||
// The `[\s\S]` is used so that `.` can match newlines.
|
||||
// The `?` in `[\s\S]*?` makes it non-greedy so we capture the smallest triple-quote block.
|
||||
// The `m` flag is used so ^ can match the start of lines.
|
||||
// The `g` flag is for capturing all occurrences.
|
||||
//
|
||||
// We also add a lookbehind for ) or : to ensure we match the pattern of a function signature,
|
||||
// but you can tweak as needed.
|
||||
// Updated regex to handle multiline signatures
|
||||
const functionRegex =
|
||||
/\b(?:async\s+)?(def|class)\s+([A-Za-z_]\w*)[\s\S]*?"""([\s\S]*?)"""/gm;
|
||||
let match: RegExpExecArray | null;
|
||||
while ((match = functionRegex.exec(fileContent)) !== null) {
|
||||
const fnName = match[2];
|
||||
const docstring = match[3];
|
||||
|
||||
// Only parse if the function is in functionNames
|
||||
if (!functionNames.includes(fnName)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 1) Split docstring by "Parameters" and/or "Returns" blocks
|
||||
// We'll do a very naive parse in NumPy style:
|
||||
//
|
||||
// description (until we see the line "Parameters" or "Returns")
|
||||
// (optional) Parameters
|
||||
// (optional) Returns
|
||||
//
|
||||
// Example NumPy-ish block:
|
||||
//
|
||||
// Parameters
|
||||
// ----------
|
||||
// param1 : str
|
||||
// Description...
|
||||
// param2 : int
|
||||
// ...
|
||||
//
|
||||
// Returns
|
||||
// -------
|
||||
// int
|
||||
// Some description...
|
||||
//
|
||||
// We'll break it up with a simple approach:
|
||||
const [rawDescription, maybeParamsAndBeyond = ""] = docstring.split(
|
||||
/\n\s*Parameters\s*[-=]+\s*\n/, // Splits after 'Parameters'
|
||||
);
|
||||
|
||||
let rawParameters = "";
|
||||
let rawReturns = "";
|
||||
|
||||
// Check if there's a "Returns" block in the "maybeParamsAndBeyond" chunk
|
||||
const returnsSplit = maybeParamsAndBeyond.split(
|
||||
/\n\s*Returns\s*[-=]+\s*\n/,
|
||||
);
|
||||
if (returnsSplit.length === 2) {
|
||||
// [ paramsBlock, returnsBlock ]
|
||||
rawParameters = returnsSplit[0];
|
||||
rawReturns = returnsSplit[1];
|
||||
} else {
|
||||
// no Returns block found
|
||||
rawParameters = maybeParamsAndBeyond;
|
||||
}
|
||||
|
||||
// 2) Parse description: everything up to "Parameters"
|
||||
const description = rawDescription.trim();
|
||||
|
||||
// 3) Parse parameters from rawParameters
|
||||
const parameters = parseParameters(rawParameters);
|
||||
|
||||
// 4) Parse returns from rawReturns
|
||||
//
|
||||
// Returns
|
||||
// -------
|
||||
// ReturnType
|
||||
// Description ...
|
||||
//
|
||||
// We'll do something simple: get the first line as type, the rest as the description
|
||||
let returnType = "";
|
||||
let returnDescription = "";
|
||||
if (rawReturns.trim()) {
|
||||
// The first non-empty line is the type
|
||||
const lines = rawReturns.split("\n").map((l) => l.trim());
|
||||
returnType = lines[0];
|
||||
// The rest is the description
|
||||
returnDescription = lines.slice(1).join(" ");
|
||||
}
|
||||
|
||||
results[fnName] = {
|
||||
description,
|
||||
parameters,
|
||||
returns: returnType
|
||||
? { type: returnType, description: returnDescription.trim() }
|
||||
: null,
|
||||
};
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
@@ -0,0 +1,244 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,411 @@
|
||||
import * as ts from "typescript";
|
||||
// @ts-ignore
|
||||
import * as fs from "fs";
|
||||
// @ts-ignore
|
||||
import { existsSync } from "fs";
|
||||
// @ts-ignore
|
||||
import { dirname, resolve } from "path";
|
||||
import { Comments } from "./comments";
|
||||
|
||||
export interface InterfaceDefinition {
|
||||
name: string;
|
||||
properties: {
|
||||
name: string;
|
||||
type: string;
|
||||
required: boolean;
|
||||
comment: string;
|
||||
defaultValue?: string;
|
||||
}[];
|
||||
}
|
||||
|
||||
export interface MethodDefinition {
|
||||
signature: string;
|
||||
comment: string;
|
||||
parameters: {
|
||||
name: string;
|
||||
type: string;
|
||||
required: boolean;
|
||||
comment: string;
|
||||
}[];
|
||||
}
|
||||
|
||||
export class SourceFile {
|
||||
public sourceFile!: ts.SourceFile;
|
||||
|
||||
constructor(private readonly filePath: string) {}
|
||||
|
||||
parse() {
|
||||
const fileContents = fs.readFileSync(this.filePath, "utf8");
|
||||
this.sourceFile = ts.createSourceFile(
|
||||
this.filePath,
|
||||
fileContents,
|
||||
ts.ScriptTarget.Latest,
|
||||
true,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the interface definition of the first argument of the function or class constructor.
|
||||
*/
|
||||
getArg0Interface(name: string): InterfaceDefinition | null {
|
||||
let interfaceName: string = "";
|
||||
|
||||
const visit = (node: ts.Node) => {
|
||||
// if we find a matching function declaration
|
||||
if (
|
||||
ts.isFunctionDeclaration(node) &&
|
||||
node.name?.getText() === name &&
|
||||
node.parameters.length &&
|
||||
node.parameters[0].type &&
|
||||
ts.isTypeReferenceNode(node.parameters[0].type)
|
||||
) {
|
||||
interfaceName = node.parameters[0].type.typeName.getText();
|
||||
}
|
||||
// if we find a matching class declaration
|
||||
else if (ts.isClassDeclaration(node) && node.name?.getText() === name) {
|
||||
const constructor = node.members.find((member) =>
|
||||
ts.isConstructorDeclaration(member),
|
||||
) as ts.ConstructorDeclaration;
|
||||
if (
|
||||
constructor &&
|
||||
constructor.parameters.length &&
|
||||
constructor.parameters[0].type &&
|
||||
ts.isTypeReferenceNode(constructor.parameters[0].type)
|
||||
) {
|
||||
interfaceName = constructor.parameters[0].type.typeName.getText();
|
||||
}
|
||||
}
|
||||
// if we find a matching forwardRef declaration
|
||||
else if (ts.isVariableStatement(node) || ts.isVariableDeclaration(node)) {
|
||||
const declarations = ts.isVariableStatement(node)
|
||||
? node.declarationList.declarations
|
||||
: [node];
|
||||
|
||||
declarations.forEach((declaration) => {
|
||||
if (
|
||||
ts.isVariableDeclaration(declaration) &&
|
||||
declaration.name.getText() === name &&
|
||||
declaration.initializer &&
|
||||
ts.isCallExpression(declaration.initializer) &&
|
||||
declaration.initializer.expression.getText() === "React.forwardRef"
|
||||
) {
|
||||
const func = declaration.initializer.arguments[0];
|
||||
if (
|
||||
ts.isArrowFunction(func) &&
|
||||
func.parameters.length &&
|
||||
func.parameters[0].type &&
|
||||
ts.isTypeReferenceNode(func.parameters[0].type)
|
||||
) {
|
||||
interfaceName = func.parameters[0].type.typeName.getText();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ts.forEachChild(node, visit);
|
||||
};
|
||||
|
||||
// analyze the source file
|
||||
visit(this.sourceFile);
|
||||
|
||||
if (!interfaceName) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// extract the interface definition
|
||||
let interfaceFilePath =
|
||||
this.findTypeDeclaration(interfaceName) || this.filePath;
|
||||
|
||||
const interfaceSource = new SourceFile(interfaceFilePath);
|
||||
interfaceSource.parse();
|
||||
|
||||
return interfaceSource.extractInterfaceDefinition(interfaceName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the interface definition from the source file.
|
||||
*/
|
||||
protected extractInterfaceDefinition(
|
||||
interfaceName: string,
|
||||
): InterfaceDefinition {
|
||||
const definition: InterfaceDefinition = {
|
||||
name: interfaceName,
|
||||
properties: [],
|
||||
};
|
||||
const visit = (node: ts.Node) => {
|
||||
if (ts.isInterfaceDeclaration(node) && node.name.text === interfaceName) {
|
||||
let omittedProperties: Set<string> = new Set();
|
||||
|
||||
// Check for extended interfaces
|
||||
if (node.heritageClauses && node.heritageClauses.length > 0) {
|
||||
const firstClause = node.heritageClauses[0];
|
||||
firstClause.types.forEach((type) => {
|
||||
const typeName = type.expression.getText(this.sourceFile);
|
||||
let extendedInterfaceName = typeName;
|
||||
|
||||
// Check if the type is an Omit
|
||||
if (typeName.startsWith("Omit")) {
|
||||
const omitArgs = type.typeArguments;
|
||||
if (omitArgs && omitArgs.length > 0) {
|
||||
extendedInterfaceName = omitArgs[0].getText(this.sourceFile);
|
||||
if (omitArgs.length > 1) {
|
||||
const omittedProps = omitArgs[1];
|
||||
if (ts.isUnionTypeNode(omittedProps)) {
|
||||
omittedProps.types.forEach((prop) => {
|
||||
omittedProperties.add(
|
||||
prop.getText(this.sourceFile).replace(/['"]/g, ""),
|
||||
);
|
||||
});
|
||||
} else if (ts.isLiteralTypeNode(omittedProps)) {
|
||||
omittedProperties.add(
|
||||
omittedProps
|
||||
.getText(this.sourceFile)
|
||||
.replace(/['"]/g, ""),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const extendedInterfaceFilePath = this.findTypeDeclaration(
|
||||
extendedInterfaceName,
|
||||
);
|
||||
if (extendedInterfaceFilePath) {
|
||||
// Parse the extended interface file and extract its definition
|
||||
const extendedInterfaceSource = new SourceFile(
|
||||
extendedInterfaceFilePath,
|
||||
);
|
||||
extendedInterfaceSource.parse();
|
||||
const extendedDefinition =
|
||||
extendedInterfaceSource.extractInterfaceDefinition(
|
||||
extendedInterfaceName,
|
||||
);
|
||||
// Merge properties from the extended interface, excluding omitted properties
|
||||
extendedDefinition.properties.forEach((prop) => {
|
||||
if (!omittedProperties.has(prop.name)) {
|
||||
definition.properties.push(prop);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
node.members.forEach((member) => {
|
||||
if (ts.isPropertySignature(member)) {
|
||||
const propertyName = member.name.getText(this.sourceFile);
|
||||
const comment = Comments.getCleanedCommentsForNode(
|
||||
member,
|
||||
this.sourceFile,
|
||||
);
|
||||
const defaultValue = Comments.getDefaultValueForNode(
|
||||
member,
|
||||
this.sourceFile,
|
||||
);
|
||||
|
||||
definition.properties.push({
|
||||
name: propertyName,
|
||||
type: (member.type?.getText(this.sourceFile) || "unknown")
|
||||
.replace(/\n/g, "")
|
||||
.replace(/\s+/g, " "),
|
||||
required: !member.questionToken,
|
||||
comment,
|
||||
defaultValue,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
ts.forEachChild(node, visit);
|
||||
};
|
||||
|
||||
visit(this.sourceFile);
|
||||
return definition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the absolute declaration file path of a type if imported.
|
||||
*/
|
||||
findTypeDeclaration(typeName: string): string | null {
|
||||
for (const statement of this.sourceFile.statements) {
|
||||
if (ts.isImportDeclaration(statement) && statement.importClause) {
|
||||
const namedBindings = statement.importClause.namedBindings;
|
||||
if (namedBindings && ts.isNamedImports(namedBindings)) {
|
||||
const imports = namedBindings.elements.filter(
|
||||
(element) => element.name.text === typeName,
|
||||
);
|
||||
if (imports.length > 0) {
|
||||
const moduleSpecifier = (
|
||||
statement.moduleSpecifier as ts.StringLiteral
|
||||
).text;
|
||||
// Resolve the path relative to the directory of the current source file
|
||||
let resolvedPath = resolve(
|
||||
dirname(this.sourceFile.fileName),
|
||||
moduleSpecifier,
|
||||
);
|
||||
|
||||
if (existsSync(resolvedPath + ".ts")) {
|
||||
return resolvedPath + ".ts";
|
||||
} else if (existsSync(resolvedPath + ".tsx")) {
|
||||
return resolvedPath + ".tsx";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the public method definitions of a class.
|
||||
*/
|
||||
getPublicMethodDefinitions(className: string): MethodDefinition[] {
|
||||
const methodDefinitions: MethodDefinition[] = [];
|
||||
|
||||
const visit = (node: ts.Node) => {
|
||||
if (ts.isClassDeclaration(node) && node.name?.getText() === className) {
|
||||
node.members.forEach((member) => {
|
||||
if (
|
||||
ts.isMethodDeclaration(member) &&
|
||||
member.modifiers?.every(
|
||||
(modifier) => modifier.kind !== ts.SyntaxKind.PrivateKeyword,
|
||||
)
|
||||
) {
|
||||
methodDefinitions.push(this.extractMethodDefinition(member));
|
||||
}
|
||||
});
|
||||
}
|
||||
ts.forEachChild(node, visit);
|
||||
};
|
||||
|
||||
visit(this.sourceFile);
|
||||
|
||||
return methodDefinitions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the method definition from a method declaration.
|
||||
*/
|
||||
private extractMethodDefinition(
|
||||
member: ts.MethodDeclaration,
|
||||
): MethodDefinition {
|
||||
const functionComments = Comments.getTsDocCommentsForFunction(
|
||||
member,
|
||||
this.sourceFile,
|
||||
);
|
||||
const name = ts.isConstructorDeclaration(member)
|
||||
? "constructor"
|
||||
: member.name.getText();
|
||||
let signature =
|
||||
name +
|
||||
"(" +
|
||||
member.parameters.map((param) => param.getText()).join(", ") +
|
||||
")";
|
||||
signature = signature.replace(/</g, "<").replace(/>/g, ">");
|
||||
return {
|
||||
signature,
|
||||
comment: functionComments.comment,
|
||||
parameters: member.parameters.map((param) => {
|
||||
return {
|
||||
name: param.name.getText(),
|
||||
type: param.type?.getText() || "unknown",
|
||||
required: !param.questionToken,
|
||||
comment: functionComments.params[param.name.getText()] || "",
|
||||
};
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the constructor definition of a class.
|
||||
*/
|
||||
getConstructorDefinition(className: string): MethodDefinition | null {
|
||||
let constructorDefinition: MethodDefinition | null = null;
|
||||
|
||||
const visit = (node: ts.Node) => {
|
||||
if (ts.isClassDeclaration(node) && node.name?.getText() === className) {
|
||||
const constr = node.members.find((member) =>
|
||||
ts.isConstructorDeclaration(member),
|
||||
) as any;
|
||||
if (constr) {
|
||||
constructorDefinition = this.extractMethodDefinition(constr);
|
||||
}
|
||||
}
|
||||
ts.forEachChild(node, visit);
|
||||
};
|
||||
|
||||
visit(this.sourceFile);
|
||||
|
||||
return constructorDefinition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the block comment preceding a function definition.
|
||||
*/
|
||||
getFunctionComment(functionName: string): string | null {
|
||||
let functionComment: string | null = null;
|
||||
|
||||
const visit = (node: ts.Node) => {
|
||||
if (
|
||||
ts.isFunctionDeclaration(node) &&
|
||||
node.name?.getText() === functionName
|
||||
) {
|
||||
functionComment = Comments.getCleanedCommentsForNode(
|
||||
node,
|
||||
this.sourceFile,
|
||||
);
|
||||
}
|
||||
ts.forEachChild(node, visit);
|
||||
};
|
||||
|
||||
visit(this.sourceFile);
|
||||
|
||||
return functionComment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the arguments of a function by its name.
|
||||
*/
|
||||
getFunctionArguments(functionName: string): Array<{
|
||||
name: string;
|
||||
type: string;
|
||||
required: boolean;
|
||||
defaultValue: string;
|
||||
description: string;
|
||||
}> | null {
|
||||
let functionArguments: Array<{
|
||||
name: string;
|
||||
type: string;
|
||||
required: boolean;
|
||||
defaultValue: string;
|
||||
description: string;
|
||||
}> | null = null;
|
||||
|
||||
const visit = (node: ts.Node) => {
|
||||
if (
|
||||
ts.isFunctionDeclaration(node) &&
|
||||
node.name?.getText() === functionName
|
||||
) {
|
||||
functionArguments = node.parameters.map((param) => {
|
||||
const name = param.name.getText();
|
||||
const type = param.type?.getText() || "unknown";
|
||||
const required = !param.questionToken;
|
||||
const defaultValue = param.initializer
|
||||
? param.initializer.getText()
|
||||
: "";
|
||||
|
||||
// Use the comment cleaning method
|
||||
const description = Comments.getCleanedCommentsForNode(
|
||||
param,
|
||||
this.sourceFile,
|
||||
);
|
||||
|
||||
return { name, type, required, defaultValue, description };
|
||||
});
|
||||
}
|
||||
ts.forEachChild(node, visit);
|
||||
};
|
||||
|
||||
visit(this.sourceFile);
|
||||
|
||||
return functionArguments;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user