2025-07-09 13:26:51 +01:00
|
|
|
//@ts-check
|
|
|
|
/**
|
|
|
|
* @typedef {import('estree').Literal["value"]} GenericLiteralType
|
|
|
|
*/
|
|
|
|
|
2025-08-01 20:19:24 +01:00
|
|
|
import tsm, { Type } from 'ts-morph';
|
2025-08-04 13:24:09 +01:00
|
|
|
import { simpleFaker } from '@faker-js/faker'
|
2025-08-06 16:37:02 +01:00
|
|
|
|
|
|
|
const LEVEL_LIMIT = 3;
|
2025-08-02 13:55:41 +01:00
|
|
|
export class LibraryTypesRecorder {
|
2025-07-09 13:26:51 +01:00
|
|
|
/**
|
2025-08-02 13:55:41 +01:00
|
|
|
* @type {Map<string,Map<string,Type[][]>>}
|
2025-07-09 13:26:51 +01:00
|
|
|
*/
|
|
|
|
#calls = new Map();
|
|
|
|
/**
|
2025-08-02 13:55:41 +01:00
|
|
|
* @type {tsm.TypeChecker} checker
|
2025-07-09 13:26:51 +01:00
|
|
|
*/
|
2025-08-02 13:55:41 +01:00
|
|
|
checker;
|
2025-08-01 20:19:24 +01:00
|
|
|
|
|
|
|
/**
|
2025-08-02 13:55:41 +01:00
|
|
|
*
|
|
|
|
* @param {tsm.TypeChecker} checker
|
2025-08-01 20:19:24 +01:00
|
|
|
*/
|
2025-08-02 13:55:41 +01:00
|
|
|
constructor(checker) {
|
|
|
|
this.checker = checker;
|
|
|
|
}
|
2025-08-01 20:19:24 +01:00
|
|
|
/**
|
|
|
|
*
|
|
|
|
* @param {string} moduleName
|
|
|
|
* @param {string} libraryFunctionSegment
|
|
|
|
* @param {Type[]} argumentsCalled
|
|
|
|
*/
|
2025-08-02 13:55:41 +01:00
|
|
|
pushToMap(moduleName, libraryFunctionSegment, argumentsCalled) {
|
|
|
|
const modulePortion = this.#calls.get(moduleName) ?? new Map();
|
|
|
|
|
2025-08-01 20:19:24 +01:00
|
|
|
const defArgs = modulePortion.get(libraryFunctionSegment) ?? [];
|
|
|
|
defArgs.push(argumentsCalled);
|
|
|
|
|
2025-08-02 13:55:41 +01:00
|
|
|
modulePortion.set(libraryFunctionSegment, defArgs);
|
2025-08-01 20:19:24 +01:00
|
|
|
this.#calls.set(moduleName, modulePortion);
|
|
|
|
}
|
|
|
|
|
2025-08-02 13:55:41 +01:00
|
|
|
get calls() {
|
2025-08-01 20:19:24 +01:00
|
|
|
return this.#calls;
|
|
|
|
}
|
|
|
|
|
2025-08-02 13:55:41 +01:00
|
|
|
generateAllArgumentsForRecordedCalls() {
|
|
|
|
/**
|
|
|
|
* @type {Map<string,Map<string,(GenericLiteralType|null|undefined|{})[][]>>}
|
|
|
|
*/
|
2025-08-01 20:19:24 +01:00
|
|
|
const callMap = new Map();
|
2025-08-02 13:55:41 +01:00
|
|
|
for (const [moduleName, modulePortion] of this.#calls) {
|
2025-08-01 20:19:24 +01:00
|
|
|
/**
|
|
|
|
* @type {Map<string,(GenericLiteralType|null|undefined|{})[][]>}
|
|
|
|
*/
|
|
|
|
const moduleCallMap = new Map();// todo refactor
|
2025-08-02 13:55:41 +01:00
|
|
|
for (const [libraryFunctionSegment, argsList] of modulePortion) {
|
2025-08-04 13:24:09 +01:00
|
|
|
// const argsForFunctionSimple = argsList.map(args => args.map(arg => this.instantiateType(arg)));
|
2025-08-02 13:55:41 +01:00
|
|
|
const argsForFunction = argsList.flatMap(args => simpleFaker.helpers.multiple(()=> args.map(arg => this.instantiateFakerOnType(arg))));
|
|
|
|
|
|
|
|
moduleCallMap.set(libraryFunctionSegment, argsForFunction);
|
2025-08-01 20:19:24 +01:00
|
|
|
}
|
2025-08-02 13:55:41 +01:00
|
|
|
callMap.set(moduleName, moduleCallMap);
|
2025-08-01 20:19:24 +01:00
|
|
|
}
|
|
|
|
return callMap;
|
|
|
|
}
|
|
|
|
|
2025-08-02 13:55:41 +01:00
|
|
|
/**
|
|
|
|
*
|
|
|
|
* @param {Type} type
|
2025-08-06 16:37:02 +01:00
|
|
|
* @param {number} [level=1]
|
2025-08-02 13:55:41 +01:00
|
|
|
*/
|
2025-08-06 16:37:02 +01:00
|
|
|
instantiateFakerOnType(type,level=1) {
|
|
|
|
if(level>LEVEL_LIMIT) return undefined;
|
2025-08-07 17:12:04 +01:00
|
|
|
// console.log("Instantiating faker on type", type.getText(), level);
|
2025-08-02 13:55:41 +01:00
|
|
|
const literalValue = type.getLiteralValue();
|
2025-08-04 13:24:09 +01:00
|
|
|
if(type.isBooleanLiteral()){
|
|
|
|
return type.getText() === 'true';
|
|
|
|
} else if (literalValue !== undefined) {
|
2025-08-02 13:55:41 +01:00
|
|
|
return literalValue;
|
|
|
|
} else if (type.isUndefined()) {
|
|
|
|
return undefined;
|
|
|
|
} else if (type.isString()) {
|
2025-08-04 13:24:09 +01:00
|
|
|
|
2025-08-02 13:55:41 +01:00
|
|
|
return simpleFaker.string.alphanumeric();
|
|
|
|
} else if (type.isNumber()) {
|
|
|
|
return simpleFaker.number.int();
|
|
|
|
} else if (type.isBoolean()) {
|
|
|
|
return simpleFaker.datatype.boolean();
|
2025-08-06 16:37:02 +01:00
|
|
|
}else if(type.isTuple()){
|
|
|
|
return type.getTupleElements().map(t => this.instantiateFakerOnType(t,level+1));
|
2025-08-02 13:55:41 +01:00
|
|
|
} else if (type.isArray()) {
|
|
|
|
return []// TODO - handle arrays;
|
|
|
|
} else if (type.isObject()) {
|
2025-08-06 16:37:02 +01:00
|
|
|
const f = type.getCallSignatures();
|
|
|
|
if(f.length > 0) {
|
|
|
|
return simpleFaker.helpers.arrayElement(f.map(fn => ()=>this.instantiateFakerOnType(fn.getReturnType(),level+1)));
|
|
|
|
}
|
2025-08-02 13:55:41 +01:00
|
|
|
const newObj = {};
|
|
|
|
for (const prop of type.getProperties()) {
|
|
|
|
const propName = prop.getName();
|
|
|
|
const declarations = prop.getDeclarations();
|
|
|
|
let propType = prop.getDeclaredType();
|
|
|
|
if (declarations.length !== 1) {
|
2025-08-07 17:12:04 +01:00
|
|
|
// console.warn("Multiple declarations for property", propName, "in type", type.getText());
|
2025-08-02 13:55:41 +01:00
|
|
|
} else {
|
|
|
|
propType = this.checker.getTypeOfSymbolAtLocation(prop, declarations[0]);
|
|
|
|
}
|
2025-08-07 17:12:04 +01:00
|
|
|
// console.log("Instantiating faker on property", propName, "of type", propType.getText(), "in type", type.getText());
|
2025-08-06 16:37:02 +01:00
|
|
|
newObj[propName] = this.instantiateFakerOnType(propType,level+1);
|
2025-08-02 13:55:41 +01:00
|
|
|
}
|
|
|
|
// TODO - handle functions
|
|
|
|
return newObj;
|
2025-08-06 16:37:02 +01:00
|
|
|
}
|
|
|
|
else if(type.isUnion()){
|
|
|
|
const types = type.getUnionTypes();
|
|
|
|
return simpleFaker.helpers.arrayElement(types.map(t => this.instantiateFakerOnType(t)));
|
|
|
|
}
|
|
|
|
else {
|
2025-08-02 13:55:41 +01:00
|
|
|
if (type.isAny()) {
|
|
|
|
return simpleFaker.helpers.arrayElement([
|
|
|
|
simpleFaker.string.sample(),
|
|
|
|
simpleFaker.number.int(),
|
|
|
|
simpleFaker.datatype.boolean(),
|
|
|
|
{},
|
|
|
|
[]
|
|
|
|
]);
|
|
|
|
}
|
2025-08-07 17:12:04 +01:00
|
|
|
console.warn("Unknown type to instantiate", type.getText());
|
2025-08-02 13:55:41 +01:00
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
|
2025-08-01 20:19:24 +01:00
|
|
|
}
|
2025-07-09 13:26:51 +01:00
|
|
|
}
|