Skip to content
Open
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion src/strands/p5.strands.js
Original file line number Diff line number Diff line change
Expand Up @@ -612,7 +612,18 @@ if (typeof p5 !== "undefined") {
*/

/**
* @property {Object} filterColor
* @typedef {Object} FilterColorHook
* @property {any} texCoord
* @property {any} canvasSize
* @property {any} texelSize
* @property {any} canvasContent
* @property {function(): undefined} begin
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should these be : void too or is there a reason for them to be : undefined?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When I tried using void, the generated p5.d.ts ended up showing any instead. I tried using different ways to define a function but it still kept showing any. So I've defined it as undefined which showed up correctly while generating p5.js.d.ts

* @property {function(): undefined} end
* @property {function(color: any): void} set
*/

/**
* @property {FilterColorHook} filterColor
* @description
* A shader hook block that sets the color for each pixel in a filter shader. This hook can be used inside <a href="#/p5/buildFilterShader">`buildFilterShader()`</a> to control the output color for each pixel.
*
Expand Down
1 change: 0 additions & 1 deletion utils/patch.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -175,4 +175,3 @@ export function applyPatches() {
}
}
}

99 changes: 80 additions & 19 deletions utils/typescript.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,18 @@ const constantsLookup = new Set();
const typedefs = {};
const mutableProperties = new Set(['disableFriendlyErrors']); // Properties that should be mutable, not constants
allRawData.forEach(entry => {
if (entry.kind === 'constant' || entry.kind === 'typedef') {
if (entry.kind === 'constant') {
constantsLookup.add(entry.name);
if (entry.kind === 'typedef') {
typedefs[entry.name] = entry.type;
}

// Collect object typedefs so constants referencing them can resolve to proper types
if (entry.kind === 'typedef') {
if (
entry.properties &&
entry.properties.length > 0 &&
!entry.properties.every(p => p.name === entry.name)
) {
typedefs[entry.name] = entry;
}
}
});
Expand Down Expand Up @@ -201,6 +209,16 @@ function convertTypeToTypeScript(typeNode, options = {}) {
throw new Error(`convertTypeToTypeScript expects an object, got: ${typeof typeNode} - ${JSON.stringify(typeNode)}`);
}

if (typeNode.properties && typeNode.properties.length > 0) {
const props = typeNode.properties.map(prop => {
const propType = convertTypeToTypeScript(prop.type, options);
const optional = prop.optional ? '?' : '';
return `${prop.name}${optional}: ${propType}`;
});

return `{ ${props.join('; ')} }`;
}

const { currentClass = null, isInsideNamespace = false, inGlobalMode = false, isConstantDef = false } = options;

switch (typeNode.type) {
Expand Down Expand Up @@ -242,19 +260,18 @@ function convertTypeToTypeScript(typeNode, options = {}) {
}
}

// Check if this is a p5 constant - use typeof since they're defined as values
// If p5 constant: use its typedef when defining it, else reference it as a value via `typeof`
if (constantsLookup.has(typeName)) {
if (inGlobalMode) {
return `typeof P5.${typeName}`;
} else if (typedefs[typeName]) {
if (isConstantDef) {
return convertTypeToTypeScript(typedefs[typeName], options);
} else {
return `typeof p5.${typeName}`
}
} else {
return `Symbol`;
if (isConstantDef && typedefs[typeName]) {
return convertTypeToTypeScript(typedefs[typeName], options);
}
return inGlobalMode
? `typeof P5.${typeName}`
: `typeof ${typeName}`;
}

if (typedefs[typeName]) {
return typeName;
}

return typeName;
Expand Down Expand Up @@ -368,6 +385,11 @@ const typescriptStrategy = {

const processed = processData(rawData, typescriptStrategy);

// Augment constantsLookup with processed constants
Object.keys(processed.consts).forEach(name => {
constantsLookup.add(name);
});

function normalizeIdentifier(name) {
return (
'0123456789'.includes(name[0]) ||
Expand Down Expand Up @@ -595,6 +617,22 @@ function generateClassDeclaration(classData) {
function generateTypeDefinitions() {
let output = '// This file is auto-generated from JSDoc documentation\n\n';

const strandsMethods = processStrandsFunctions();

Object.entries(typedefs).forEach(([name, entry]) => {
if (entry.properties && entry.properties.length > 0) {
const props = entry.properties.map(prop => {
const propType = prop.type ? convertTypeToTypeScript(prop.type) : 'any';
const optional = prop.optional ? '?' : '';
return ` ${prop.name}${optional}: ${propType}`;
});
output += `type ${name} = {\n${props.join(';\n')};\n};\n\n`;
} else {
const tsType = convertTypeToTypeScript(entry.type || entry);
output += `type ${name} = ${tsType};\n\n`;
}
});

// First, define all constants at the top level with their actual values
const seenConstants = new Set();
const p5Constants = processed.classitems.filter(item => {
Expand All @@ -606,6 +644,9 @@ function generateTypeDefinitions() {
if (seenConstants.has(item.name)) {
return false;
}
if (item.name in typedefs) {
return false;
}
seenConstants.add(item.name);
return true;
}
Expand All @@ -618,12 +659,26 @@ function generateTypeDefinitions() {
output += formatJSDocComment(constant.description, 0) + '\n';
output += ' */\n';
}
const type = convertTypeToTypeScript(constant.type, { isInsideNamespace: false, isConstantDef: true });
let type;
// Avoid invalid self-referential types like `typeof FOO`
if (
constant.type?.type === 'NameExpression' &&
constant.type.name === constant.name
) {
// Self-referential constant → fallback
type = 'number';
} else {
type = convertTypeToTypeScript(constant.type, {
isInsideNamespace: false,
isConstantDef: true
});
}
const isMutable = mutableProperties.has(constant.name);
const declaration = isMutable ? 'declare let' : 'declare const';
output += `${declaration} ${constant.name}: ${type};\n\n`;
// Duplicate with a private identifier so we can re-export in the namespace later
output += `${declaration} __${constant.name}: typeof ${constant.name};\n\n`;

});

// Generate main p5 class
Expand All @@ -645,7 +700,6 @@ function generateTypeDefinitions() {
});

// Add strands functions to p5 instance
const strandsMethods = processStrandsFunctions();
strandsMethods.forEach(method => {
output += generateMethodDeclaration(method, p5Options);
});
Expand All @@ -667,9 +721,16 @@ function generateTypeDefinitions() {

output += '\n';


p5Constants.forEach(constant => {
output += `${mutableProperties.has(constant.name) ? 'let' : 'const'} ${constant.name}: typeof __${constant.name};\n`;
const isTypedefTyped =
constant.type?.type === 'NameExpression' &&
constant.type?.name in typedefs;

if (isTypedefTyped) {
output += `${mutableProperties.has(constant.name) ? 'let' : 'const'} ${constant.name}: ${constant.type.name};\n`;
} else {
output += `${mutableProperties.has(constant.name) ? 'let' : 'const'} ${constant.name}: typeof __${constant.name};\n`;
}
});

output += '\n';
Expand Down Expand Up @@ -820,4 +881,4 @@ console.log('TypeScript definitions generated successfully!');

// Apply patches
console.log('Applying TypeScript patches...');
applyPatches();
applyPatches();
Loading