From fe4437305cfab462d895ce8b4b147713657bda74 Mon Sep 17 00:00:00 2001 From: Diya Solanki Date: Sun, 12 Jan 2025 17:00:13 +0530 Subject: [PATCH 1/7] added boilerplate script --- .gitignore | 1 + package.json | 3 +- utils/generate-types.js | 554 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 557 insertions(+), 1 deletion(-) create mode 100644 utils/generate-types.js diff --git a/.gitignore b/.gitignore index 42f1e447dc..6ddaf0e001 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ docs/reference/* examples/3d/ .idea dist/ +*d.ts p5.zip bower-repo/ p5-website/ diff --git a/package.json b/package.json index 978e7d8703..ffbcf761ed 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,8 @@ "bench:report": "vitest bench --reporter=verbose", "test": "vitest", "lint": "eslint .", - "lint:fix": "eslint --fix ." + "lint:fix": "eslint --fix .", + "generate-types": "npm run docs && node utils/generate-types" }, "lint-staged": { "Gruntfile.js": "eslint", diff --git a/utils/generate-types.js b/utils/generate-types.js new file mode 100644 index 0000000000..6d6d94213d --- /dev/null +++ b/utils/generate-types.js @@ -0,0 +1,554 @@ +const fs = require('fs'); +const path = require('path'); + +// Read docs.json +const data = JSON.parse(fs.readFileSync(path.join(__dirname, '../docs/data.json'))); + +// Flatten and organize data structure +function getEntries(entry) { + if (!entry) return []; + if (!entry.members) return [entry]; + + return [ + entry, + ...getAllEntries(entry.members.global || []), + ...getAllEntries(entry.members.inner || []), + ...getAllEntries(entry.members.instance || []), + ...getAllEntries(entry.members.events || []), + ...getAllEntries(entry.members.static || []) + ]; +} + +function getAllEntries(arr) { + return arr.flatMap(getEntries); +} + +// Organize data into structured format +function organizeData(data) { + const allData = getAllEntries(data); + const organized = { + modules: {}, + classes: {}, + classitems: [], + consts: {} + }; + + // Process modules first + allData.forEach(entry => { + if (entry.tags?.some(tag => tag.title === 'module')) { + const { module, submodule } = getModuleInfo(entry); + organized.modules[module] = organized.modules[module] || { + name: module, + submodules: {}, + classes: {} + }; + if (submodule) { + organized.modules[module].submodules[submodule] = true; + } + } + }); + + // Process classes + allData.forEach(entry => { + if (entry.kind === 'class') { + const { module, submodule } = getModuleInfo(entry); + organized.classes[entry.name] = { + name: entry.name, + description: extractDescription(entry.description), + params: (entry.params || []).map(param => ({ + name: param.name, + type: generateTypeFromTag(param), + optional: param.type?.type === 'OptionalType' + })), + module, + submodule + }; + } + }); + + // Process class methods and properties + allData.forEach(entry => { + if (entry.kind === 'function' || entry.kind === 'property') { + const { module, submodule, forEntry } = getModuleInfo(entry); + const className = forEntry || 'p5'; + + if (!organized.classes[className]) return; + + // Check for static methods - directly check path[0].scope + // Todo: handle static methods + const isStatic = entry.path?.[0]?.scope === 'static'; + // Handle overloads + const overloads = entry.overloads?.map(overload => ({ + params: overload.params, + returns: overload.returns, + description: extractDescription(overload.description) + })); + + organized.classitems.push({ + name: entry.name, + kind: entry.kind, + description: extractDescription(entry.description), + params: (entry.params || []).map(param => ({ + name: param.name, + type: generateTypeFromTag(param), + optional: param.type?.type === 'OptionalType' + })), + returnType: entry.returns?.[0] ? generateTypeFromTag(entry.returns[0]) : 'void', + module, + submodule, + class: className, + isStatic, + overloads + }); + } + }); + + // Process constants and typedefs + allData.forEach(entry => { + if (entry.kind === 'constant' || entry.kind === 'typedef') { + const { module, submodule, forEntry } = getModuleInfo(entry); + organized.consts[entry.name] = { + name: entry.name, + kind: entry.kind, + description: extractDescription(entry.description), + type: entry.type ? generateTypeFromTag(entry) : 'any', + module, + submodule, + class: forEntry || 'p5' + }; + } + }); + + return organized; +} + +// Helper function to get module info +function getModuleInfo(entry) { + const moduleTag = entry.tags?.find(tag => tag.title === 'module'); + const submoduleTag = entry.tags?.find(tag => tag.title === 'submodule'); + const forTag = entry.tags?.find(tag => tag.title === 'for') + + return { + module: moduleTag?.name || 'p5', + submodule: submoduleTag?.description, + forEntry: forTag?.description || entry.memberof + }; +} + +// Function to extract text from description object or string +function extractDescription(desc) { + if (!desc) return ''; + if (typeof desc === 'string') return desc; + if (desc.children) { + return desc.children + .map(child => { + if (child.type === 'text') return child.value; + if (child.type === 'paragraph') return extractDescription(child); + if (child.type === 'inlineCode') return `\`${child.value}\``; + if (child.type === 'code') return `\`${child.value}\``; + return ''; + }) + .join('') + .trim() + .replace(/\n{3,}/g, '\n\n'); + } + return ''; +} + +// Format comment text for JSDoc +function formatJSDocComment(text, indentLevel = 0) { + if (!text) return ''; + const indent = ' '.repeat(indentLevel); + + const lines = text + .split('\n') + .map(line => line.trim()) + .reduce((acc, line) => { + // If we're starting and line is empty, skip it + if (acc.length === 0 && line === '') return acc; + // If we have content and hit an empty line, keep one empty line + if (acc.length > 0 && line === '' && acc[acc.length - 1] === '') return acc; + acc.push(line); + return acc; + }, []) + .filter((line, i, arr) => i < arr.length - 1 || line !== ''); // Remove trailing empty line + + return lines + .map(line => `${indent} * ${line}`) + .join('\n'); +} + +// Normalize type names to ensure primitive types are lowercase and handle object types +function normalizeTypeName(type) { + if (!type) return 'any'; + + // Handle object type notation + if (type === '[object Object]') return 'any'; + + const primitiveTypes = { + 'String': 'string', + 'Number': 'number', + 'Boolean': 'boolean', + 'Void': 'void', + 'Object': 'object', + 'Array': 'array', + 'Function': 'function' + }; + + return primitiveTypes[type] || type; +} + +// Generate type from tag +function generateTypeFromTag(param) { + if (!param || !param.type) return 'any'; + + switch (param.type.type) { + case 'NameExpression': + return normalizeTypeName(param.type.name); + case 'TypeApplication': + const baseType = normalizeTypeName(param.type.expression.name); + const typeParams = param.type.applications + .map(app => generateTypeFromTag({ type: app })) + .join(', '); + return `${baseType}<${typeParams}>`; + case 'UnionType': + return param.type.elements + .map(el => generateTypeFromTag({ type: el })) + .join(' | '); + case 'OptionalType': + return generateTypeFromTag({ type: param.type.expression }); + case 'AllLiteral': + return 'any'; + case 'RecordType': + return 'object'; + case 'ObjectType': + return 'object'; + default: + return 'any'; + } +} + +// Generate parameter declaration +function generateParamDeclaration(param) { + if (!param) return 'any'; + + let type = param.type; + const isOptional = param.type?.type === 'OptionalType'; + if (typeof type === 'string') { + type = normalizeTypeName(type); + } else if (param.type?.type) { + type = generateTypeFromTag(param); + } else { + type = 'any'; + } + + return `${param.name}${isOptional ? '?' : ''}: ${type}`; +} + +// Generate function declaration +function generateFunctionDeclaration(funcDoc) { + let output = ''; + + // Add Comments + if (funcDoc.description || funcDoc.tags?.length > 0) { + output += '/**\n'; + const description = extractDescription(funcDoc.description); + if (description) { + output += formatJSDocComment(description) + '\n'; + } + if (funcDoc.tags) { + if (description) { + output += ' *\n'; // Add separator between description and tags + } + funcDoc.tags.forEach(tag => { + if (tag.description) { + const tagDesc = extractDescription(tag.description); + output += formatJSDocComment(`@${tag.title} ${tagDesc}`, 0) + '\n'; + } + }); + } + output += ' */\n'; + } + + // Generate function signature + const params = (funcDoc.params || []) + .map(param => generateParamDeclaration(param)) + .join(', '); + + const returnType = funcDoc.returns?.[0]?.type + ? generateTypeFromTag(funcDoc.returns[0]) + : 'void'; + + output += `function ${funcDoc.name}(${params}): ${returnType};\n\n`; + return output; +} + +// Generate class declaration +function generateClassDeclaration(classDoc, organizedData) { + let output = ''; + + // Add comments + if (classDoc.description || classDoc.tags?.length > 0) { + output += '/**\n'; + const description = extractDescription(classDoc.description); + if (description) { + output += formatJSDocComment(description) + '\n'; + } + if (classDoc.tags) { + if (description) { + output += ' *\n'; // Add separator between description and tags + } + classDoc.tags.forEach(tag => { + if (tag.description) { + const tagDesc = extractDescription(tag.description); + output += formatJSDocComment(`@${tag.title} ${tagDesc}`, 0) + '\n'; + } + }); + } + output += ' */\n'; + } + + // Generate class declaration + const isAbstract = classDoc.tags?.some(t => t.title === 'abstract'); + output += `${isAbstract ? 'abstract ' : ''}class ${classDoc.name.replace('p5.', '')} {\n`; + + // Add constructor if there are parameters + if (classDoc.params?.length > 0) { + output += ' constructor('; + output += classDoc.params + .map(param => generateParamDeclaration(param)) + .join(', '); + output += ');\n\n'; + } + + // Get all class items for this class + const classItems = organizedData.classitems.filter(item => item.class === classDoc.name); + + + // Separate static and instance members + const staticItems = classItems.filter(item => item.isStatic); + + const instanceItems = classItems.filter(item => !item.isStatic); + + // Add static methods first + staticItems.forEach(item => { + + if (item.description) { + output += ' /**\n'; + const itemDesc = extractDescription(item.description); + output += formatJSDocComment(itemDesc, 2) + '\n'; + if (item.params?.length > 0) { + output += ' *\n'; + item.params.forEach(param => { + const paramDesc = extractDescription(param.description); + output += formatJSDocComment(`@param ${paramDesc}`, 2) + '\n'; + }); + } + if (item.returns) { + output += ' *\n'; + const returnDesc = extractDescription(item.returns[0]?.description); + output += formatJSDocComment(`@return ${returnDesc}`, 2) + '\n'; + } + output += ' */\n'; + } + + if (item.kind === 'function') { + // Handle function overloads + if (item.overloads) { + item.overloads.forEach(overload => { + const params = (overload.params || []) + .map(param => generateParamDeclaration(param)) + .join(', '); + const returnType = overload.returns?.[0]?.type + ? generateTypeFromTag(overload.returns[0]) + : 'void'; + output += ` static function ${item.name}(${params}): ${returnType};\n`; + }); + output += '\n'; + } else { + const params = (item.params || []) + .map(param => generateParamDeclaration(param)) + .join(', '); + output += ` static function ${item.name}(${params}): ${item.returnType};\n\n`; + } + } else { + output += ` static ${item.name}: ${item.returnType};\n\n`; + } + }); + + // Add instance members + instanceItems.forEach(item => { + if (item.description) { + output += ' /**\n'; + const itemDesc = extractDescription(item.description); + output += formatJSDocComment(itemDesc, 2) + '\n'; + if (item.params?.length > 0) { + output += ' *\n'; + item.params.forEach(param => { + const paramDesc = extractDescription(param.description); + output += formatJSDocComment(`@param ${paramDesc}`, 2) + '\n'; + }); + } + if (item.returns) { + output += ' *\n'; + const returnDesc = extractDescription(item.returns[0]?.description); + output += formatJSDocComment(`@return ${returnDesc}`, 2) + '\n'; + } + output += ' */\n'; + } + + if (item.kind === 'function') { + // Handle function overloads + if (item.overloads) { + item.overloads.forEach(overload => { + const params = (overload.params || []) + .map(param => generateParamDeclaration(param)) + .join(', '); + const returnType = overload.returns?.[0]?.type + ? generateTypeFromTag(overload.returns[0]) + : 'void'; + output += ` ${item.name}(${params}): ${returnType};\n`; + }); + output += '\n'; + } else { + const params = (item.params || []) + .map(param => generateParamDeclaration(param)) + .join(', '); + output += ` ${item.name}(${params}): ${item.returnType};\n\n`; + } + } else { + output += ` ${item.name}: ${item.returnType};\n\n`; + } + }); + + output += '}\n\n'; + return output; +} + +// Generate declaration file for a group of items +function generateDeclarationFile(items, filePath, organizedData) { + let output = '// This file is auto-generated from JSDoc documentation\n\n'; + + // Add imports based on dependencies + const imports = new Set([`import p5 from 'p5';`]); + + // Check for dependencies + const hasColorDependency = items.some(item => { + const typeName = item.type?.name; + const desc = extractDescription(item.description); + return typeName === 'Color' || (typeof desc === 'string' && desc.includes('Color')); + }); + + const hasVectorDependency = items.some(item => { + const typeName = item.type?.name; + const desc = extractDescription(item.description); + return typeName === 'Vector' || (typeof desc === 'string' && desc.includes('Vector')); + }); + + const hasConstantsDependency = items.some(item => + item.tags?.some(tag => tag.title === 'requires' && tag.description === 'constants') + ); + + if (hasColorDependency) { + imports.add(`import { Color } from '../color/p5.Color';`); + } + if (hasVectorDependency) { + imports.add(`import { Vector } from '../math/p5.Vector';`); + } + if (hasConstantsDependency) { + imports.add(`import * as constants from '../core/constants';`); + } + + output += Array.from(imports).join('\n') + '\n\n'; + + // Get module name + const moduleName = getModuleInfo(items[0]).module; + + // Begin module declaration + output += `declare module '${moduleName}' {\n`; + + // Add all item declarations + items.forEach(item => { + switch (item.kind) { + case 'class': + output += generateClassDeclaration(item, organizedData); + break; + case 'function': + output += generateFunctionDeclaration(item); + break; + case 'constant': + case 'typedef': + const constData = organizedData.consts[item.name]; + if (constData) { + if (constData.description) { + output += ` /**\n * ${constData.description}\n */\n`; + } + if (constData.kind === 'constant') { + output += ` const ${constData.name}: ${constData.type};\n\n`; + } else { + output += ` type ${constData.name} = ${constData.type};\n\n`; + } + } + break; + } + }); + + // Close module declaration + output += '}\n\n'; + + // Add default export + const exportName = path.basename(filePath, '.js').replace('.', '_'); + output += `export default function ${exportName}(p5: any, fn: any): void;\n`; + + return output; +} + +// Group items by file +function groupByFile(items) { + const fileGroups = new Map(); + + items.forEach(item => { + if (!item.context || !item.context.file) return; + + const filePath = item.context.file; + if (!fileGroups.has(filePath)) { + fileGroups.set(filePath, []); + } + fileGroups.get(filePath).push(item); + }); + + return fileGroups; +} + +// Main function to generate all declaration files +function generateAllDeclarationFiles() { + // Organize all data first + const organizedData = organizeData(data); + + // Group items by file + const fileGroups = groupByFile(getAllEntries(data)); + + fileGroups.forEach((items, filePath) => { + // Convert the file path to a .d.ts path + const parsedPath = path.parse(filePath); + const relativePath = path.relative(process.cwd(), filePath); + const dtsPath = path.join( + path.dirname(relativePath), + `${parsedPath.name}.d.ts` + ); + + // Generate the declaration file content + const declarationContent = generateDeclarationFile(items, filePath, organizedData); + + // Create directory if it doesn't exist + fs.mkdirSync(path.dirname(dtsPath), { recursive: true }); + + // Write the declaration file + fs.writeFileSync(dtsPath, declarationContent, 'utf8'); + + console.log(`Generated ${dtsPath}`); + }); +} + +// Run the generator +generateAllDeclarationFiles(); \ No newline at end of file From c0612e514b2783bb5aa165b61e4519ef75234a7a Mon Sep 17 00:00:00 2001 From: Diya Solanki Date: Thu, 16 Jan 2025 01:16:58 +0530 Subject: [PATCH 2/7] fix static --- utils/generate-types.js | 39 ++++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/utils/generate-types.js b/utils/generate-types.js index 6d6d94213d..d035575268 100644 --- a/utils/generate-types.js +++ b/utils/generate-types.js @@ -22,17 +22,18 @@ function getEntries(entry) { function getAllEntries(arr) { return arr.flatMap(getEntries); } - -// Organize data into structured format -function organizeData(data) { - const allData = getAllEntries(data); - const organized = { +const organized = { modules: {}, classes: {}, classitems: [], consts: {} }; +// Organize data into structured format +function organizeData(data) { + const allData = getAllEntries(data); + + // Process modules first allData.forEach(entry => { if (entry.tags?.some(tag => tag.title === 'module')) { @@ -52,8 +53,9 @@ function organizeData(data) { allData.forEach(entry => { if (entry.kind === 'class') { const { module, submodule } = getModuleInfo(entry); - organized.classes[entry.name] = { - name: entry.name, + const className = entry.name; + organized.classes[className] = { + name: className, description: extractDescription(entry.description), params: (entry.params || []).map(param => ({ name: param.name, @@ -70,9 +72,11 @@ function organizeData(data) { allData.forEach(entry => { if (entry.kind === 'function' || entry.kind === 'property') { const { module, submodule, forEntry } = getModuleInfo(entry); - const className = forEntry || 'p5'; - - if (!organized.classes[className]) return; + // Use memberof if available, fallback to forEntry, then default to 'p5' + const className = entry.memberof || forEntry || 'p5'; + + // Create the class entry if it doesn't exist + // if (!organized.classes[className]) {console.log(`returning for ${className}`); return}; // Check for static methods - directly check path[0].scope // Todo: handle static methods @@ -117,6 +121,7 @@ function organizeData(data) { class: forEntry || 'p5' }; } + fs.writeFileSync("./consts.json", JSON.stringify(organized.consts, null, 2), 'utf8'); }); return organized; @@ -308,10 +313,6 @@ function generateClassDeclaration(classDoc, organizedData) { output += ' */\n'; } - // Generate class declaration - const isAbstract = classDoc.tags?.some(t => t.title === 'abstract'); - output += `${isAbstract ? 'abstract ' : ''}class ${classDoc.name.replace('p5.', '')} {\n`; - // Add constructor if there are parameters if (classDoc.params?.length > 0) { output += ' constructor('; @@ -322,8 +323,8 @@ function generateClassDeclaration(classDoc, organizedData) { } // Get all class items for this class - const classItems = organizedData.classitems.filter(item => item.class === classDoc.name); - + const classDocName = classDoc.name.startsWith('p5.') ? classDoc.name.substring(3) : classDoc.name; + const classItems = organizedData.classitems.filter(item => item.class === classDocName); // Separate static and instance members const staticItems = classItems.filter(item => item.isStatic); @@ -362,14 +363,14 @@ function generateClassDeclaration(classDoc, organizedData) { const returnType = overload.returns?.[0]?.type ? generateTypeFromTag(overload.returns[0]) : 'void'; - output += ` static function ${item.name}(${params}): ${returnType};\n`; + output += ` static ${item.name}(${params}): ${returnType};\n`; }); output += '\n'; } else { const params = (item.params || []) .map(param => generateParamDeclaration(param)) .join(', '); - output += ` static function ${item.name}(${params}): ${item.returnType};\n\n`; + output += ` static ${item.name}(${params}): ${item.returnType};\n\n`; } } else { output += ` static ${item.name}: ${item.returnType};\n\n`; @@ -538,7 +539,7 @@ function generateAllDeclarationFiles() { ); // Generate the declaration file content - const declarationContent = generateDeclarationFile(items, filePath, organizedData); + const declarationContent = generateDeclarationFile(items, filePath, organized); // Create directory if it doesn't exist fs.mkdirSync(path.dirname(dtsPath), { recursive: true }); From bb885afb6348b52a8139bfc432ba4e560681826e Mon Sep 17 00:00:00 2001 From: Diya Solanki Date: Fri, 24 Jan 2025 02:23:30 +0530 Subject: [PATCH 3/7] fix data types and class declarations --- utils/generate-types.js | 245 +++++++++++++++++++++------------------- 1 file changed, 129 insertions(+), 116 deletions(-) diff --git a/utils/generate-types.js b/utils/generate-types.js index d035575268..038bd1bf4e 100644 --- a/utils/generate-types.js +++ b/utils/generate-types.js @@ -77,9 +77,7 @@ function organizeData(data) { // Create the class entry if it doesn't exist // if (!organized.classes[className]) {console.log(`returning for ${className}`); return}; - - // Check for static methods - directly check path[0].scope - // Todo: handle static methods + const isStatic = entry.path?.[0]?.scope === 'static'; // Handle overloads const overloads = entry.overloads?.map(overload => ({ @@ -122,7 +120,7 @@ function organizeData(data) { }; } fs.writeFileSync("./consts.json", JSON.stringify(organized.consts, null, 2), 'utf8'); - }); + }); return organized; } @@ -193,11 +191,12 @@ function normalizeTypeName(type) { const primitiveTypes = { 'String': 'string', 'Number': 'number', + 'Integer': 'number', 'Boolean': 'boolean', 'Void': 'void', 'Object': 'object', - 'Array': 'array', - 'Function': 'function' + 'Array': 'Array', + 'Function': 'Function' }; return primitiveTypes[type] || type; @@ -226,8 +225,10 @@ function generateTypeFromTag(param) { return 'any'; case 'RecordType': return 'object'; - case 'ObjectType': - return 'object'; + case 'StringLiteralType': + return `'${param.type.value}'`; + case 'UndefinedLiteralType': + return 'undefined'; default: return 'any'; } @@ -288,6 +289,60 @@ function generateFunctionDeclaration(funcDoc) { return output; } +// Helper function to generate method declarations +function generateMethodDeclarations(item, isStatic = false) { + let output = ''; + + // Add JSDoc comment + if (item.description) { + output += ' /**\n'; + const itemDesc = extractDescription(item.description); + output += formatJSDocComment(itemDesc, 2) + '\n'; + if (item.params?.length > 0) { + output += ' *\n'; + item.params.forEach(param => { + const paramDesc = extractDescription(param.description); + output += formatJSDocComment(`@param ${paramDesc}`, 2) + '\n'; + }); + } + if (item.returns) { + output += ' *\n'; + const returnDesc = extractDescription(item.returns[0]?.description); + output += formatJSDocComment(`@return ${returnDesc}`, 2) + '\n'; + } + output += ' */\n'; + } + + if (item.kind === 'function') { + const staticPrefix = isStatic ? 'static ' : ''; + + // If there are overloads, generate all overload signatures first + if (item.overloads?.length > 0) { + item.overloads.forEach(overload => { + const params = (overload.params || []) + .map(param => generateParamDeclaration(param)) + .join(', '); + const returnType = overload.returns?.[0]?.type + ? generateTypeFromTag(overload.returns[0]) + : 'void'; + output += ` ${staticPrefix}${item.name}(${params}): ${returnType};\n`; + }); + } + + // Generate the implementation signature + const params = (item.params || []) + .map(param => generateParamDeclaration(param)) + .join(', '); + output += ` ${staticPrefix}${item.name}(${params}): ${item.returnType};\n\n`; + } else { + // Handle properties + const staticPrefix = isStatic ? 'static ' : ''; + output += ` ${staticPrefix}${item.name}: ${item.returnType};\n\n`; + } + + return output; +} + // Generate class declaration function generateClassDeclaration(classDoc, organizedData) { let output = ''; @@ -324,102 +379,21 @@ function generateClassDeclaration(classDoc, organizedData) { // Get all class items for this class const classDocName = classDoc.name.startsWith('p5.') ? classDoc.name.substring(3) : classDoc.name; + const classItems = organizedData.classitems.filter(item => item.class === classDocName); // Separate static and instance members const staticItems = classItems.filter(item => item.isStatic); - const instanceItems = classItems.filter(item => !item.isStatic); - // Add static methods first + // Generate static members staticItems.forEach(item => { - - if (item.description) { - output += ' /**\n'; - const itemDesc = extractDescription(item.description); - output += formatJSDocComment(itemDesc, 2) + '\n'; - if (item.params?.length > 0) { - output += ' *\n'; - item.params.forEach(param => { - const paramDesc = extractDescription(param.description); - output += formatJSDocComment(`@param ${paramDesc}`, 2) + '\n'; - }); - } - if (item.returns) { - output += ' *\n'; - const returnDesc = extractDescription(item.returns[0]?.description); - output += formatJSDocComment(`@return ${returnDesc}`, 2) + '\n'; - } - output += ' */\n'; - } - - if (item.kind === 'function') { - // Handle function overloads - if (item.overloads) { - item.overloads.forEach(overload => { - const params = (overload.params || []) - .map(param => generateParamDeclaration(param)) - .join(', '); - const returnType = overload.returns?.[0]?.type - ? generateTypeFromTag(overload.returns[0]) - : 'void'; - output += ` static ${item.name}(${params}): ${returnType};\n`; - }); - output += '\n'; - } else { - const params = (item.params || []) - .map(param => generateParamDeclaration(param)) - .join(', '); - output += ` static ${item.name}(${params}): ${item.returnType};\n\n`; - } - } else { - output += ` static ${item.name}: ${item.returnType};\n\n`; - } + output += generateMethodDeclarations(item, true); }); - // Add instance members + // Generate instance members instanceItems.forEach(item => { - if (item.description) { - output += ' /**\n'; - const itemDesc = extractDescription(item.description); - output += formatJSDocComment(itemDesc, 2) + '\n'; - if (item.params?.length > 0) { - output += ' *\n'; - item.params.forEach(param => { - const paramDesc = extractDescription(param.description); - output += formatJSDocComment(`@param ${paramDesc}`, 2) + '\n'; - }); - } - if (item.returns) { - output += ' *\n'; - const returnDesc = extractDescription(item.returns[0]?.description); - output += formatJSDocComment(`@return ${returnDesc}`, 2) + '\n'; - } - output += ' */\n'; - } - - if (item.kind === 'function') { - // Handle function overloads - if (item.overloads) { - item.overloads.forEach(overload => { - const params = (overload.params || []) - .map(param => generateParamDeclaration(param)) - .join(', '); - const returnType = overload.returns?.[0]?.type - ? generateTypeFromTag(overload.returns[0]) - : 'void'; - output += ` ${item.name}(${params}): ${returnType};\n`; - }); - output += '\n'; - } else { - const params = (item.params || []) - .map(param => generateParamDeclaration(param)) - .join(', '); - output += ` ${item.name}(${params}): ${item.returnType};\n\n`; - } - } else { - output += ` ${item.name}: ${item.returnType};\n\n`; - } + output += generateMethodDeclarations(item, false); }); output += '}\n\n'; @@ -468,29 +442,68 @@ function generateDeclarationFile(items, filePath, organizedData) { // Begin module declaration output += `declare module '${moduleName}' {\n`; - // Add all item declarations + // Find the class documentation if it exists + const classDoc = items.find(item => item.kind === 'class'); + if (classDoc) { + const classDocName = classDoc.name.startsWith('p5.') ? classDoc.name.substring(3) : classDoc.name; + + // Start class declaration + output += ` class ${classDocName} {\n`; + + // Add constructor if there are parameters + if (classDoc.params?.length > 0) { + output += ' constructor('; + output += classDoc.params + .map(param => generateParamDeclaration(param)) + .join(', '); + output += ');\n\n'; + } + + // Get all members that belong to this class + const classItems = organizedData.classitems.filter(item => + item.memberof === classDoc.name || item.class === classDocName + ); + + // Separate static and instance members + const staticItems = classItems.filter(item => item.isStatic); + const instanceItems = classItems.filter(item => !item.isStatic); + + // Generate static members + staticItems.forEach(item => { + output += generateMethodDeclarations(item, true); + }); + + // Generate instance members + instanceItems.forEach(item => { + output += generateMethodDeclarations(item, false); + }); + + // Close class declaration + output += ' }\n\n'; + } + + // Add remaining items that aren't part of the class items.forEach(item => { - switch (item.kind) { - case 'class': - output += generateClassDeclaration(item, organizedData); - break; - case 'function': - output += generateFunctionDeclaration(item); - break; - case 'constant': - case 'typedef': - const constData = organizedData.consts[item.name]; - if (constData) { - if (constData.description) { - output += ` /**\n * ${constData.description}\n */\n`; + if (item.kind !== 'class' && (!item.memberof || item.memberof !== classDoc?.name)) { + switch (item.kind) { + case 'function': + output += generateFunctionDeclaration(item); + break; + case 'constant': + case 'typedef': + const constData = organizedData.consts[item.name]; + if (constData) { + if (constData.description) { + output += ` /**\n * ${constData.description}\n */\n`; + } + if (constData.kind === 'constant') { + output += ` const ${constData.name}: ${constData.type};\n\n`; + } else { + output += ` type ${constData.name} = ${constData.type};\n\n`; + } } - if (constData.kind === 'constant') { - output += ` const ${constData.name}: ${constData.type};\n\n`; - } else { - output += ` type ${constData.name} = ${constData.type};\n\n`; - } - } - break; + break; + } } }); @@ -539,7 +552,7 @@ function generateAllDeclarationFiles() { ); // Generate the declaration file content - const declarationContent = generateDeclarationFile(items, filePath, organized); + const declarationContent = generateDeclarationFile(items, filePath, organizedData); // Create directory if it doesn't exist fs.mkdirSync(path.dirname(dtsPath), { recursive: true }); From 8cab9ddb564ea86cff1b3afa6ef84c06971441b9 Mon Sep 17 00:00:00 2001 From: Diya Solanki Date: Fri, 24 Jan 2025 02:23:30 +0530 Subject: [PATCH 4/7] fix data types and class declarations --- utils/generate-types.js | 245 +++++++++++++++++++++------------------- 1 file changed, 129 insertions(+), 116 deletions(-) diff --git a/utils/generate-types.js b/utils/generate-types.js index d035575268..038bd1bf4e 100644 --- a/utils/generate-types.js +++ b/utils/generate-types.js @@ -77,9 +77,7 @@ function organizeData(data) { // Create the class entry if it doesn't exist // if (!organized.classes[className]) {console.log(`returning for ${className}`); return}; - - // Check for static methods - directly check path[0].scope - // Todo: handle static methods + const isStatic = entry.path?.[0]?.scope === 'static'; // Handle overloads const overloads = entry.overloads?.map(overload => ({ @@ -122,7 +120,7 @@ function organizeData(data) { }; } fs.writeFileSync("./consts.json", JSON.stringify(organized.consts, null, 2), 'utf8'); - }); + }); return organized; } @@ -193,11 +191,12 @@ function normalizeTypeName(type) { const primitiveTypes = { 'String': 'string', 'Number': 'number', + 'Integer': 'number', 'Boolean': 'boolean', 'Void': 'void', 'Object': 'object', - 'Array': 'array', - 'Function': 'function' + 'Array': 'Array', + 'Function': 'Function' }; return primitiveTypes[type] || type; @@ -226,8 +225,10 @@ function generateTypeFromTag(param) { return 'any'; case 'RecordType': return 'object'; - case 'ObjectType': - return 'object'; + case 'StringLiteralType': + return `'${param.type.value}'`; + case 'UndefinedLiteralType': + return 'undefined'; default: return 'any'; } @@ -288,6 +289,60 @@ function generateFunctionDeclaration(funcDoc) { return output; } +// Helper function to generate method declarations +function generateMethodDeclarations(item, isStatic = false) { + let output = ''; + + // Add JSDoc comment + if (item.description) { + output += ' /**\n'; + const itemDesc = extractDescription(item.description); + output += formatJSDocComment(itemDesc, 2) + '\n'; + if (item.params?.length > 0) { + output += ' *\n'; + item.params.forEach(param => { + const paramDesc = extractDescription(param.description); + output += formatJSDocComment(`@param ${paramDesc}`, 2) + '\n'; + }); + } + if (item.returns) { + output += ' *\n'; + const returnDesc = extractDescription(item.returns[0]?.description); + output += formatJSDocComment(`@return ${returnDesc}`, 2) + '\n'; + } + output += ' */\n'; + } + + if (item.kind === 'function') { + const staticPrefix = isStatic ? 'static ' : ''; + + // If there are overloads, generate all overload signatures first + if (item.overloads?.length > 0) { + item.overloads.forEach(overload => { + const params = (overload.params || []) + .map(param => generateParamDeclaration(param)) + .join(', '); + const returnType = overload.returns?.[0]?.type + ? generateTypeFromTag(overload.returns[0]) + : 'void'; + output += ` ${staticPrefix}${item.name}(${params}): ${returnType};\n`; + }); + } + + // Generate the implementation signature + const params = (item.params || []) + .map(param => generateParamDeclaration(param)) + .join(', '); + output += ` ${staticPrefix}${item.name}(${params}): ${item.returnType};\n\n`; + } else { + // Handle properties + const staticPrefix = isStatic ? 'static ' : ''; + output += ` ${staticPrefix}${item.name}: ${item.returnType};\n\n`; + } + + return output; +} + // Generate class declaration function generateClassDeclaration(classDoc, organizedData) { let output = ''; @@ -324,102 +379,21 @@ function generateClassDeclaration(classDoc, organizedData) { // Get all class items for this class const classDocName = classDoc.name.startsWith('p5.') ? classDoc.name.substring(3) : classDoc.name; + const classItems = organizedData.classitems.filter(item => item.class === classDocName); // Separate static and instance members const staticItems = classItems.filter(item => item.isStatic); - const instanceItems = classItems.filter(item => !item.isStatic); - // Add static methods first + // Generate static members staticItems.forEach(item => { - - if (item.description) { - output += ' /**\n'; - const itemDesc = extractDescription(item.description); - output += formatJSDocComment(itemDesc, 2) + '\n'; - if (item.params?.length > 0) { - output += ' *\n'; - item.params.forEach(param => { - const paramDesc = extractDescription(param.description); - output += formatJSDocComment(`@param ${paramDesc}`, 2) + '\n'; - }); - } - if (item.returns) { - output += ' *\n'; - const returnDesc = extractDescription(item.returns[0]?.description); - output += formatJSDocComment(`@return ${returnDesc}`, 2) + '\n'; - } - output += ' */\n'; - } - - if (item.kind === 'function') { - // Handle function overloads - if (item.overloads) { - item.overloads.forEach(overload => { - const params = (overload.params || []) - .map(param => generateParamDeclaration(param)) - .join(', '); - const returnType = overload.returns?.[0]?.type - ? generateTypeFromTag(overload.returns[0]) - : 'void'; - output += ` static ${item.name}(${params}): ${returnType};\n`; - }); - output += '\n'; - } else { - const params = (item.params || []) - .map(param => generateParamDeclaration(param)) - .join(', '); - output += ` static ${item.name}(${params}): ${item.returnType};\n\n`; - } - } else { - output += ` static ${item.name}: ${item.returnType};\n\n`; - } + output += generateMethodDeclarations(item, true); }); - // Add instance members + // Generate instance members instanceItems.forEach(item => { - if (item.description) { - output += ' /**\n'; - const itemDesc = extractDescription(item.description); - output += formatJSDocComment(itemDesc, 2) + '\n'; - if (item.params?.length > 0) { - output += ' *\n'; - item.params.forEach(param => { - const paramDesc = extractDescription(param.description); - output += formatJSDocComment(`@param ${paramDesc}`, 2) + '\n'; - }); - } - if (item.returns) { - output += ' *\n'; - const returnDesc = extractDescription(item.returns[0]?.description); - output += formatJSDocComment(`@return ${returnDesc}`, 2) + '\n'; - } - output += ' */\n'; - } - - if (item.kind === 'function') { - // Handle function overloads - if (item.overloads) { - item.overloads.forEach(overload => { - const params = (overload.params || []) - .map(param => generateParamDeclaration(param)) - .join(', '); - const returnType = overload.returns?.[0]?.type - ? generateTypeFromTag(overload.returns[0]) - : 'void'; - output += ` ${item.name}(${params}): ${returnType};\n`; - }); - output += '\n'; - } else { - const params = (item.params || []) - .map(param => generateParamDeclaration(param)) - .join(', '); - output += ` ${item.name}(${params}): ${item.returnType};\n\n`; - } - } else { - output += ` ${item.name}: ${item.returnType};\n\n`; - } + output += generateMethodDeclarations(item, false); }); output += '}\n\n'; @@ -468,29 +442,68 @@ function generateDeclarationFile(items, filePath, organizedData) { // Begin module declaration output += `declare module '${moduleName}' {\n`; - // Add all item declarations + // Find the class documentation if it exists + const classDoc = items.find(item => item.kind === 'class'); + if (classDoc) { + const classDocName = classDoc.name.startsWith('p5.') ? classDoc.name.substring(3) : classDoc.name; + + // Start class declaration + output += ` class ${classDocName} {\n`; + + // Add constructor if there are parameters + if (classDoc.params?.length > 0) { + output += ' constructor('; + output += classDoc.params + .map(param => generateParamDeclaration(param)) + .join(', '); + output += ');\n\n'; + } + + // Get all members that belong to this class + const classItems = organizedData.classitems.filter(item => + item.memberof === classDoc.name || item.class === classDocName + ); + + // Separate static and instance members + const staticItems = classItems.filter(item => item.isStatic); + const instanceItems = classItems.filter(item => !item.isStatic); + + // Generate static members + staticItems.forEach(item => { + output += generateMethodDeclarations(item, true); + }); + + // Generate instance members + instanceItems.forEach(item => { + output += generateMethodDeclarations(item, false); + }); + + // Close class declaration + output += ' }\n\n'; + } + + // Add remaining items that aren't part of the class items.forEach(item => { - switch (item.kind) { - case 'class': - output += generateClassDeclaration(item, organizedData); - break; - case 'function': - output += generateFunctionDeclaration(item); - break; - case 'constant': - case 'typedef': - const constData = organizedData.consts[item.name]; - if (constData) { - if (constData.description) { - output += ` /**\n * ${constData.description}\n */\n`; + if (item.kind !== 'class' && (!item.memberof || item.memberof !== classDoc?.name)) { + switch (item.kind) { + case 'function': + output += generateFunctionDeclaration(item); + break; + case 'constant': + case 'typedef': + const constData = organizedData.consts[item.name]; + if (constData) { + if (constData.description) { + output += ` /**\n * ${constData.description}\n */\n`; + } + if (constData.kind === 'constant') { + output += ` const ${constData.name}: ${constData.type};\n\n`; + } else { + output += ` type ${constData.name} = ${constData.type};\n\n`; + } } - if (constData.kind === 'constant') { - output += ` const ${constData.name}: ${constData.type};\n\n`; - } else { - output += ` type ${constData.name} = ${constData.type};\n\n`; - } - } - break; + break; + } } }); @@ -539,7 +552,7 @@ function generateAllDeclarationFiles() { ); // Generate the declaration file content - const declarationContent = generateDeclarationFile(items, filePath, organized); + const declarationContent = generateDeclarationFile(items, filePath, organizedData); // Create directory if it doesn't exist fs.mkdirSync(path.dirname(dtsPath), { recursive: true }); From a8934b9fe4967ef82c74eeb970be69db51ff76e8 Mon Sep 17 00:00:00 2001 From: Diya Solanki Date: Mon, 27 Jan 2025 16:56:35 +0530 Subject: [PATCH 5/7] Added Extends case for classes --- utils/convert.js | 2 + utils/generate-types.js | 90 ++++++++++++++++++++++++----------------- 2 files changed, 56 insertions(+), 36 deletions(-) diff --git a/utils/convert.js b/utils/convert.js index a92049298a..dcc478b2a9 100644 --- a/utils/convert.js +++ b/utils/convert.js @@ -602,3 +602,5 @@ fs.mkdirSync(path.join(__dirname, '../docs/reference'), { recursive: true }); fs.writeFileSync(path.join(__dirname, '../docs/reference/data.json'), JSON.stringify(converted, null, 2)); fs.writeFileSync(path.join(__dirname, '../docs/reference/data.min.json'), JSON.stringify(converted)); buildParamDocs(JSON.parse(JSON.stringify(converted))); + +module.exports= { getAllEntries }; diff --git a/utils/generate-types.js b/utils/generate-types.js index 038bd1bf4e..a57dd6cdb6 100644 --- a/utils/generate-types.js +++ b/utils/generate-types.js @@ -1,27 +1,10 @@ const fs = require('fs'); const path = require('path'); +const { getAllEntries } = require("./convert"); // Read docs.json const data = JSON.parse(fs.readFileSync(path.join(__dirname, '../docs/data.json'))); -// Flatten and organize data structure -function getEntries(entry) { - if (!entry) return []; - if (!entry.members) return [entry]; - - return [ - entry, - ...getAllEntries(entry.members.global || []), - ...getAllEntries(entry.members.inner || []), - ...getAllEntries(entry.members.instance || []), - ...getAllEntries(entry.members.events || []), - ...getAllEntries(entry.members.static || []) - ]; -} - -function getAllEntries(arr) { - return arr.flatMap(getEntries); -} const organized = { modules: {}, classes: {}, @@ -29,6 +12,12 @@ const organized = { consts: {} }; +// Add this helper function at the top with other helpers +function normalizeClassName(className) { + if (!className || className === 'p5') return 'p5'; + return className.startsWith('p5.') ? className : `p5.${className}`; +} + // Organize data into structured format function organizeData(data) { const allData = getAllEntries(data); @@ -54,6 +43,8 @@ function organizeData(data) { if (entry.kind === 'class') { const { module, submodule } = getModuleInfo(entry); const className = entry.name; + const extendsTag = entry.tags?.find(tag => tag.title === 'extends'); + organized.classes[className] = { name: className, description: extractDescription(entry.description), @@ -63,7 +54,8 @@ function organizeData(data) { optional: param.type?.type === 'OptionalType' })), module, - submodule + submodule, + extends: extendsTag?.name || null }; } }); @@ -72,11 +64,20 @@ function organizeData(data) { allData.forEach(entry => { if (entry.kind === 'function' || entry.kind === 'property') { const { module, submodule, forEntry } = getModuleInfo(entry); + + // Normalize memberof and forEntry + let memberof = entry.memberof; + if (memberof && memberof !== 'p5' && !memberof.startsWith('p5.')) { + memberof = 'p5.' + memberof; + } + + let normalizedForEntry = forEntry; + if (forEntry && forEntry !== 'p5' && !forEntry.startsWith('p5.')) { + normalizedForEntry = 'p5.' + forEntry; + } + // Use memberof if available, fallback to forEntry, then default to 'p5' - const className = entry.memberof || forEntry || 'p5'; - - // Create the class entry if it doesn't exist - // if (!organized.classes[className]) {console.log(`returning for ${className}`); return}; + const className = normalizeClassName(memberof || normalizedForEntry || 'p5'); const isStatic = entry.path?.[0]?.scope === 'static'; // Handle overloads @@ -356,11 +357,12 @@ function generateClassDeclaration(classDoc, organizedData) { } if (classDoc.tags) { if (description) { - output += ' *\n'; // Add separator between description and tags + output += ' *\n'; } classDoc.tags.forEach(tag => { if (tag.description) { const tagDesc = extractDescription(tag.description); + output += formatJSDocComment(`@${tag.title} ${tagDesc}`, 0) + '\n'; } }); @@ -368,6 +370,15 @@ function generateClassDeclaration(classDoc, organizedData) { output += ' */\n'; } + // Get the parent class if it exists + const parentClass = classDoc.extends; + const extendsClause = parentClass ? ` extends ${parentClass}` : ''; + + // Start class declaration with inheritance if applicable + const fullClassName = normalizeClassName(classDoc.name); + const classDocName = fullClassName.replace('p5.', ''); + output += `class ${classDocName}${extendsClause} {\n`; + // Add constructor if there are parameters if (classDoc.params?.length > 0) { output += ' constructor('; @@ -378,9 +389,10 @@ function generateClassDeclaration(classDoc, organizedData) { } // Get all class items for this class - const classDocName = classDoc.name.startsWith('p5.') ? classDoc.name.substring(3) : classDoc.name; - - const classItems = organizedData.classitems.filter(item => item.class === classDocName); + const classItems = organizedData.classitems.filter(item => + item.class === fullClassName || + item.class === fullClassName.replace('p5.', '') + ); // Separate static and instance members const staticItems = classItems.filter(item => item.isStatic); @@ -445,10 +457,16 @@ function generateDeclarationFile(items, filePath, organizedData) { // Find the class documentation if it exists const classDoc = items.find(item => item.kind === 'class'); if (classDoc) { - const classDocName = classDoc.name.startsWith('p5.') ? classDoc.name.substring(3) : classDoc.name; + const fullClassName = normalizeClassName(classDoc.name); + const classDocName = fullClassName.replace('p5.', ''); + let parentClass = classDoc.tags?.find(tag => tag.title === 'extends')?.name; + if (parentClass) { + parentClass = parentClass.replace('p5.', ''); + } + const extendsClause = parentClass ? ` extends ${parentClass}` : ''; // Start class declaration - output += ` class ${classDocName} {\n`; + output += ` class ${classDocName}${extendsClause} {\n`; // Add constructor if there are parameters if (classDoc.params?.length > 0) { @@ -458,27 +476,27 @@ function generateDeclarationFile(items, filePath, organizedData) { .join(', '); output += ');\n\n'; } - - // Get all members that belong to this class + + // Get all class items for this class const classItems = organizedData.classitems.filter(item => - item.memberof === classDoc.name || item.class === classDocName + item.class === fullClassName || + item.class === fullClassName.replace('p5.', '') ); // Separate static and instance members const staticItems = classItems.filter(item => item.isStatic); const instanceItems = classItems.filter(item => !item.isStatic); - + // Generate static members staticItems.forEach(item => { output += generateMethodDeclarations(item, true); }); - + // Generate instance members instanceItems.forEach(item => { output += generateMethodDeclarations(item, false); }); - - // Close class declaration + output += ' }\n\n'; } From c250700ad839ef246d102e91b5b8731b9cc9fa07 Mon Sep 17 00:00:00 2001 From: Diya Solanki Date: Tue, 28 Jan 2025 00:10:06 +0530 Subject: [PATCH 6/7] added patches for current exceptions --- package.json | 2 +- utils/patch.js | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 utils/patch.js diff --git a/package.json b/package.json index ffbcf761ed..270c442a58 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "test": "vitest", "lint": "eslint .", "lint:fix": "eslint --fix .", - "generate-types": "npm run docs && node utils/generate-types" + "generate-types": "npm run docs && node utils/generate-types && node utils/patch" }, "lint-staged": { "Gruntfile.js": "eslint", diff --git a/utils/patch.js b/utils/patch.js new file mode 100644 index 0000000000..2231e86361 --- /dev/null +++ b/utils/patch.js @@ -0,0 +1,47 @@ +const fs = require('fs'); + +const replace = (path, src, dest) => { + try { + const data = fs + .readFileSync(path, { encoding: 'utf-8' }) + .replace(src, dest); + fs.writeFileSync(path, data); + } catch (err) { + console.error(err); + } +}; + +replace( + "./src/core/structure.d.ts", + "function p5(sketch: object, node: string | HTMLElement): void;", + "function p5: typeof p5" +); + +replace( + "./src/webgl/p5.Geometry.d.ts", + "constructor(detailX?: number, detailY?: number, callback?: function);", + `constructor( + detailX?: number, + detailY?: number, + callback?: (this: { + detailY: number, + detailX: number, + vertices: p5.Vector[], + uvs: number[] + }) => void);` +); + +// https://github.com/p5-types/p5.ts/issues/31 +replace( + "./src/math/random.d.ts", + "function random(choices: Array): any;", + "function random(choices: T[]): T;" +); + +replace( + "./src/utilities/array_functions.d.ts", + "function append(array: Array, value: Any): Array;", + "function append(array: T[], value: T): T[];" +); + + From a844230a6852119cbc41971c1e5e4e78eeec3931 Mon Sep 17 00:00:00 2001 From: Diya Solanki Date: Thu, 30 Jan 2025 15:31:15 +0530 Subject: [PATCH 7/7] handles array of tuples --- src/color/creating_reading.js | 4 +-- utils/generate-types.js | 50 ++++++++++++++++++++++++++++++++++- 2 files changed, 51 insertions(+), 3 deletions(-) diff --git a/src/color/creating_reading.js b/src/color/creating_reading.js index f848ba3a25..f838c4ceff 100644 --- a/src/color/creating_reading.js +++ b/src/color/creating_reading.js @@ -1540,8 +1540,8 @@ function creatingReading(p5, fn){ * colorMode(). * * @method paletteLerp - * @param {[p5.Color|String|Number|Number[], Number][]} colors_stops color stops to interpolate from - * @param {Number} amt number to use to interpolate relative to color stops + * @param {Array.>} colors_stops color stops to interpolate from + * @param {Number} amt number to use to interpolate relative to color stops * @return {p5.Color} interpolated color. * * @example diff --git a/utils/generate-types.js b/utils/generate-types.js index c2dead7c2f..6992f6b41d 100644 --- a/utils/generate-types.js +++ b/utils/generate-types.js @@ -212,14 +212,52 @@ function generateTypeFromTag(param) { return normalizeTypeName(param.type.name); case 'TypeApplication': const baseType = normalizeTypeName(param.type.expression.name); + + // Handle array cases + if (baseType === 'Array') { + const innerType = param.type.applications[0]; + + // Handle nested array that represents a tuple + if (innerType.type === 'TypeApplication' && + innerType.expression.name === 'Array') { + // Get all tuple element types + const tupleTypes = innerType.applications + .map(app => generateTypeFromTag({ type: app })) + .join(', '); + return `Array<[${tupleTypes}]>`; + } + + // Handle array with union type + if (innerType.type === 'UnionType') { + const unionTypes = innerType.elements + .map(el => generateTypeFromTag({ type: el })) + .join(' | '); + // If this is part of a tuple structure (has sibling types), wrap in tuple + if (param.type.applications.length > 1) { + const remainingTypes = param.type.applications + .slice(1) + .map(app => generateTypeFromTag({ type: app })) + .join(', '); + return `Array<[${unionTypes}, ${remainingTypes}]>`; + } + return `Array<${unionTypes}>`; + } + + // Regular array + const typeParam = generateTypeFromTag({ type: innerType }); + return `Array<${typeParam}>`; + } + + // Regular type application const typeParams = param.type.applications .map(app => generateTypeFromTag({ type: app })) .join(', '); return `${baseType}<${typeParams}>`; case 'UnionType': - return param.type.elements + const unionTypes = param.type.elements .map(el => generateTypeFromTag({ type: el })) .join(' | '); + return unionTypes; case 'OptionalType': return generateTypeFromTag({ type: param.type.expression }); case 'AllLiteral': @@ -230,6 +268,16 @@ function generateTypeFromTag(param) { return `'${param.type.value}'`; case 'UndefinedLiteralType': return 'undefined'; + case 'ArrayType': + // Check if it's a tuple type (array with specific types for each position) + if (param.type.elements) { + const tupleTypes = param.type.elements + .map(el => generateTypeFromTag({ type: el })) + .join(', '); + return `[${tupleTypes}]`; + } + // Regular array type + return `${generateTypeFromTag({ type: param.type.elementType })}[]`; default: return 'any'; }