Skip to content

Instantly share code, notes, and snippets.

@PhoenixIllusion
Last active May 3, 2024 16:40
Show Gist options
  • Save PhoenixIllusion/68f3796be91fdf26aa667290f6782692 to your computer and use it in GitHub Desktop.
Save PhoenixIllusion/68f3796be91fdf26aa667290f6782692 to your computer and use it in GitHub Desktop.
Jolt - Typescript NPM Usage Analysis and WebIDL rewriting
{
"class": {
"FixedConstraintSettings": {
"properties": ["mPoint1", "mPoint2"]
},
"PointConstraintSettings": {
"properties": ["mPoint1", "mPoint2"]
},
"DistanceConstraintSettings": {
"properties": ["mPoint1", "mPoint2", "mLimitsSpringSettings"]
},
"DistanceConstraint": {
"methods": ["GetLimitsSpringSettings"]
},
"SliderConstraintSettings": {
"properties": ["mPoint1", "mPoint2", "mNormalAxis1", "mNormalAxis2", "mLimitsSpringSettings","mMotorSettings"]
},
"SliderConstraint": {
"methods": ["GetLimitsSpringSettings"]
},
"HingeConstraintSettings": {
"properties": ["mPoint1", "mPoint2", "mNormalAxis1", "mNormalAxis2", "mLimitsSpringSettings","mMotorSettings"]
},
"SwingTwistConstraintSettings": {
"properties": ["mSwingMotorSettings", "mTwistMotorSettings"]
},
"HingeConstraint": {
"methods": ["GetLimitsSpringSettings"]
},
"ConeConstraintSettings": {
"properties": ["mPoint1", "mPoint2"]
},
"PathConstraintSettings": {
"properties": ["mPositionMotorSettings"]
},
"CollideShapeResult": {},
"ContactManifold": {},
"SubShapeIDPair": {},
"PathConstraintPathEm": {},
"ContactListenerEm": {},
"CharacterContactListenerEm": {},
"VehicleConstraintCallbacksEm": {}
},
"variable": { },
"function": { }
}
import { ClassDeclaration, Project, ReferenceFindableNode, Type } from "ts-morph";
const project = new Project({
tsConfigFilePath: "../tsconfig.json",
});
const joltImportFile = project.getSourceFile(f => f.getImportDeclaration('jolt-physics') !== undefined)!;
const joltImport = joltImportFile.getImportDeclaration('jolt-physics')!.getDefaultImport()!;
const joltDef = joltImport.getDefinitions()[0].getSourceFile();
const joltDeclaration = joltDef.getModule('Jolt')!;
function isUsed(v: ReferenceFindableNode) {
return v.findReferencesAsNodes().find(x => !x.getSourceFile().isDeclarationFile()) !== undefined;
}
const usedJoltDeclarations: {
'class': Record<string, {constructor: boolean, properties: string[], methods: string[]}>,
'variable': Record<string,boolean>,
'function': Record<string,boolean>} = { class: {}, variable: {}, function: {}};
function isJoltType(t: Type): ClassDeclaration|undefined {
return t.isClass()? t.getSymbol()!.getDeclarations()[0] as ClassDeclaration : undefined;
}
function parseClass(_class: ClassDeclaration) {
if(!usedJoltDeclarations.class[_class.getName()!]) {
const properties: string[] = [];
const methods: string[] = [];
const pendingClasses: Set<ClassDeclaration> = new Set<ClassDeclaration>;
let constructor = false;
const baseClass = _class.getBaseClass();
if(baseClass) {
pendingClasses.add(baseClass);
}
_class.getConstructors().forEach(x => {
if(isUsed(x)) {
constructor = true;
x.getParameters().forEach(pd => {
const pdType = isJoltType(pd.getType());
if(pdType) {
pendingClasses.add(pdType);
}
})
}
})
_class.getProperties().forEach(x => {
if(isUsed(x)) {
properties.push(x.getName());
const xType = isJoltType(x.getType());
if(xType) {
pendingClasses.add(xType);
}
}
})
_class.getMethods().forEach(x => {
if(isUsed(x)) {
methods.push(x.getName());
const retType = isJoltType(x.getReturnType());
if(retType) {
pendingClasses.add(retType);
}
x.getParameters().forEach(pd => {
const pdType = isJoltType(pd.getType());
if(pdType) {
pendingClasses.add(pdType);
}
})
}
})
usedJoltDeclarations.class[_class.getName()!] = {constructor, properties, methods};
[... pendingClasses].forEach(parseClass);
}
}
joltDeclaration.getClasses().forEach(_class => {
if(isUsed(_class)) {
parseClass(_class);
}
})
joltDeclaration.getVariableDeclarations().forEach(_var => {
if(isUsed(_var))
usedJoltDeclarations.variable[_var.getName()!] = true;
})
joltDeclaration.getFunctions().forEach(_func => {
if(isUsed(_func))
usedJoltDeclarations.function[_func.getName()!] = true;
})
console.log(JSON.stringify(usedJoltDeclarations, undefined, ' '));
import fs from 'fs';
interface RequiredClass {
constructor: boolean;
properties: string[];
methods: string[];
}
interface RequiredIDL {
class: Record<string, RequiredClass>;
variable: Record<string, boolean>;
}
const fullIDL = fs.readFileSync('JoltJS.idl').toString();
const elementRegEx = /(?:(?:\[[^\]]+\]\s*)?((?:interface|enum))\s+([^:{}\s]+)\s*(?:\:\s*(\S+))?\s*?\{(.*?)\}|(?:^(\S+?)\s+implements\s+(\S+?)))\s*;/smig;
let line: RegExpExecArray | null = null;
const requiredIDL: RequiredIDL = JSON.parse(fs.readFileSync('minimal.json').toString());
const overrideIDL: RequiredIDL = JSON.parse(fs.readFileSync('overrides.json').toString());
Object.assign(requiredIDL.variable, overrideIDL.variable);
Object.entries(overrideIDL.class).forEach(([k,v]) => {
if(requiredIDL.class[k]) {
const c = requiredIDL.class[k];
c.constructor = c.constructor || v.constructor;
c.properties = c.properties.concat(v.properties||[])
c.methods = c.methods.concat(v.methods||[])
} else {
requiredIDL.class[k] = v;
requiredIDL.class[k].methods = requiredIDL.class[k].methods || [];
requiredIDL.class[k].properties = requiredIDL.class[k].properties || [];
}
})
while(line = elementRegEx.exec(fullIDL)) {
let [text, type, name, _parent, body, subClass, _extendedClass] = line;
subClass = subClass?.trim();
name = name?.trim();
type = type?.trim();
if(type == 'enum') {
const values = body.split(',').map(s => s.trim().replaceAll('"','')).filter(s => requiredIDL.variable[s]);
if(values.length > 0) {
console.log(`enum ${name} {\n ${values.map(s => `"${s}"`).join(',\n ')}\n};\n`)
}
}
if(type == 'interface' && requiredIDL.class[name]) {
const req = requiredIDL.class[name];
const [interfaceStart, interfaceEnd] = text.split(body).map(s => s.trim());
console.log(interfaceStart);
const funcs = body.split(';').map(s => s.trim());
funcs.filter(s => s.indexOf('attribute') >=0).forEach(attrib => {
const attribName = attrib.split(/\s+/).pop()!;
if(req.properties.includes(attribName)) {
console.log(' '+attrib + ';')
}
});
funcs.filter(s => s.indexOf('(') >=0).forEach(method => {
const methodName = (/(\S+)\(/.exec(method)!)[1];
if(methodName == name && req.constructor) {
console.log(' '+method + ';')
}
if(req.methods.includes(methodName)) {
console.log(' '+method + ';')
}
});
console.log(interfaceEnd);
}
if(requiredIDL.class[subClass]) {
console.log(text);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment