Skip to content

Commit

Permalink
Add missing Engine API properties (#12)
Browse files Browse the repository at this point in the history
  • Loading branch information
willeastcott authored Jan 24, 2024
1 parent 0963605 commit 6a975c8
Show file tree
Hide file tree
Showing 2 changed files with 141 additions and 0 deletions.
1 change: 1 addition & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
"GitHub": "https://github.com/playcanvas"
},
"plugin": [
"./typedoc-plugin-property.mjs",
"typedoc-plugin-extras",
"typedoc-plugin-mdn-links",
"typedoc-plugin-rename-defaults"
Expand Down
140 changes: 140 additions & 0 deletions typedoc-plugin-property.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import { readFileSync } from 'fs';
import { resolve } from 'path';

// eslint-disable-next-line import/no-unresolved
import { ArrayType, Converter, DeclarationReflection, IntrinsicType, ReflectionFlag, ReflectionKind, ReferenceType, UnionType } from 'typedoc';

/**
* Extract property types from JSDoc in a .js file.
*
* @param {string} filePath - The path to the .js file.
* @returns {Map<string, string>} A map of property names to types.
*/
function getProperties(filePath) {
const data = readFileSync(resolve(process.cwd(), filePath), 'utf-8');
const docBlocks = data.match(/\/\*\*([^*]|[\r\n]|(\*+([^*/]|[\r\n])))*\*+\//g);
const properties = new Map();

if (docBlocks) {
docBlocks.forEach((block) => {
const propertyLines = block.match(/@property\s*\{[^}]+\}\s*[^*]*/g);

if (propertyLines) {
propertyLines.forEach((line) => {
const match = line.match(/@property\s*\{([^}]+)\}\s*(\w+)/);

if (match) {
let type = match[1].trim();
const name = match[2].trim();

// Simplify complex import types.
type = type.replace(/import\(['"]([^'"]+)['"]\)\.(\w+)/g, (_, p1, p2) => p2);

properties.set(name, type);
}
});
}
});
}

return properties;
}

/**
* This Typedoc plugin adds missing PlayCanvas API symbols to the Typedoc reflection graph. The
* symbols are missing because they are generated by `Object.defineProperty` in the PlayCanvas
* sourcebase. The TypeScript compiler is unable to detect them, either in the code or in the
* JSDoc comments (specified via \@property tags).
*
* @param {import('typedoc').Application} app - The Typedoc application.
*/
function load(app) {
const classes = new Map([
['ButtonComponent', './submodules/engine/src/framework/components/button/component.js'],
['CollisionComponent', './submodules/engine/src/framework/components/collision/component.js'],
['ElementComponent', './submodules/engine/src/framework/components/element/component.js'],
['LightComponent', './submodules/engine/src/framework/components/light/component.js'],
['ParticleSystemComponent', './submodules/engine/src/framework/components/particle-system/component.js'],
['ScrollbarComponent', './submodules/engine/src/framework/components/scrollbar/component.js'],
['ScrollViewComponent', './submodules/engine/src/framework/components/scroll-view/component.js'],
['StandardMaterial', './submodules/engine/src/scene/materials/standard-material.js']
]);

app.converter.on(Converter.EVENT_RESOLVE_BEGIN, (/** @type {import('typedoc').Context} */ context) => {
const getReference = (type) => {
const reflection = context.project.children[0].children.find(child => child.name === type && child.kind === ReflectionKind.Class);
if (!reflection) {
console.error(`Unable to find class ${type}`);
}
return reflection;
};

classes.forEach((filePath, className) => {
const reflection = getReference(className);

/**
* Returns the reference type matching the specified class name.
*
* @param {string} type - The class name.
* @returns {ReferenceType} The reference type.
*/
const getReferenceType = (type) => {
const reference = getReference(type);
return reference ? new ReferenceType(type, reference, context.project) : undefined;
};

/**
* Returns the Typedoc type matching the specified JSDoc type. This can include a union type (|).
*
* @param {string} type - The JSDoc type string.
* @returns {import('typedoc').Type} The Typedoc type.
*/
const getType = (type) => {
if (type.includes('|')) {
const types = type.split('|');
return new UnionType(types.map(type => getType(type)));
}

switch (type) {
case 'null':
return new IntrinsicType('null');
case 'boolean':
return new IntrinsicType('boolean');
case 'number':
return new IntrinsicType('number');
case 'number[]':
return new ArrayType(new IntrinsicType('number'));
case 'string':
return new IntrinsicType('string');
default:
return getReferenceType(type);
}
};

const properties = getProperties(filePath);

// Get just the @property definitions from the class' JSDoc block
const blockTags = reflection.comment.blockTags.filter(blockTag => blockTag.tag === '@property');

// Convert all @property tags on StandardMaterial to actual child properties of StandardMaterial
for (const blockTag of blockTags) {
const newProperty = new DeclarationReflection(blockTag.name, ReflectionKind.Property, reflection);

const type = properties.get(blockTag.name);

newProperty.type = getType(type);

// Mark the new property as public
newProperty.setFlag(ReflectionFlag.Public, true);

// Add the new property to the class
if (!reflection.children) {
reflection.children = [];
}
reflection.children.push(newProperty);
}
});
});
}

export { load };

0 comments on commit 6a975c8

Please sign in to comment.