Skip to content
Open
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
a5f85a7
Fix TypeScript typing for filterColor shader hook
Kathrina-dev Mar 15, 2026
ea3b6b4
Removed formatted code
Kathrina-dev Mar 16, 2026
9de7f94
fix: typescript generation script for typedef constants
Kathrina-dev Mar 16, 2026
fdf4b55
added condition for typedefs in p5Constants
Kathrina-dev Mar 16, 2026
ce9ef51
Merge branch 'dev-2.0' into fix-filterColor-object-type
Kathrina-dev Mar 16, 2026
36f70e2
Merge branch 'dev-2.0' into fix-filterColor-object-type
Kathrina-dev Mar 17, 2026
c42ef81
fixed typescript JSDoc generator to handle unions
Kathrina-dev Mar 17, 2026
c809894
removed auto-formatting
Kathrina-dev Mar 17, 2026
51a03f0
added useful comments
Kathrina-dev Mar 17, 2026
ca4c1a4
added useful comments
Kathrina-dev Mar 17, 2026
94ae3d4
Merge branch 'dev-2.0' into fix-filterColor-object-type
Kathrina-dev Mar 20, 2026
27b0d88
revert back to original structure
Kathrina-dev Mar 20, 2026
f29c5b8
added new function to handle typedef properties
Kathrina-dev Mar 20, 2026
825d0cb
Merge branch 'dev-2.0' into fix-filterColor-object-type
Kathrina-dev Mar 23, 2026
bc62e8c
added condition to allow filterColorHook to pass through p5Constants
Kathrina-dev Mar 23, 2026
6ce04fc
Merge branch 'dev-2.0' into fix-filterColor-object-type
Kathrina-dev Mar 26, 2026
4e64619
Merge branch 'dev-2.0' into fix-filterColor-object-type
Kathrina-dev Apr 3, 2026
17281cf
Merge branch 'dev-2.0' into fix-filterColor-object-type
Kathrina-dev Apr 7, 2026
1219e71
added new typing for objects in p5.strands.js
Kathrina-dev Apr 7, 2026
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
Copy Markdown
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
Copy Markdown
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