interface-faker
Version:
Generate mock data for TypeScript interfaces using faker.js
183 lines (180 loc) • 6.45 kB
JavaScript
// src/index.ts
import { Project } from "ts-morph";
import { faker } from "@faker-js/faker";
import * as path from "path";
import * as fs from "fs";
var MockInterfaceGenerator = class {
constructor() {
this.project = null;
this.sourceFiles = /* @__PURE__ */ new Map();
}
/**
* Initialize TypeScript project with tsconfig
*/
initProject(filePath) {
if (this.project) return this.project;
const tsConfigPath = this.findTsConfig(filePath);
this.project = new Project({
tsConfigFilePath: tsConfigPath
});
return this.project;
}
/**
* Search for tsconfig.json starting from the given file path
*/
findTsConfig(startPath) {
let currentDir = path.dirname(startPath);
while (currentDir !== path.dirname(currentDir)) {
const tsConfigPath = path.join(currentDir, "tsconfig.json");
if (fs.existsSync(tsConfigPath)) {
return tsConfigPath;
}
currentDir = path.dirname(currentDir);
}
console.warn("\u26A0\uFE0F tsconfig.json not found, using default configuration");
return void 0;
}
/**
* Get or load a source file
*/
getSourceFile(filePath) {
if (this.sourceFiles.has(filePath)) {
return this.sourceFiles.get(filePath);
}
const project = this.initProject(filePath);
let sourceFile = project.getSourceFile(filePath);
if (!sourceFile) {
sourceFile = project.addSourceFileAtPath(filePath);
}
this.sourceFiles.set(filePath, sourceFile);
return sourceFile;
}
/**
* Generate mock value for a TypeScript type
*/
generateMockValue(type, sourceFile, options, depth = 0, visited = /* @__PURE__ */ new Set(), propName) {
const indent = " ".repeat(depth);
if (type.isArray()) {
const elementMock = this.generateMockValue(
type.getArrayElementTypeOrThrow(),
sourceFile,
options,
depth,
visited
);
const length = options.arrayLength || 2;
return `Array.from({ length: ${length} }, () => (${elementMock}))`;
}
if (type.isStringLiteral()) {
return `"${type.getLiteralValue()}"`;
}
if (type.isLiteral()) {
return JSON.stringify(type.getLiteralValue());
}
if (type.isEnumLiteral()) {
return type.getText();
}
const text = type.getText();
if (text === "string") {
if (propName) {
const lowerProp = propName.toLowerCase();
if (lowerProp.includes("email")) return "faker.internet.email()";
if (lowerProp.includes("currency")) return "faker.finance.currencyCode()";
if (lowerProp.includes("name")) return "faker.person.fullName()";
if (lowerProp.includes("phone")) return "faker.phone.number()";
if (lowerProp.includes("address")) return "faker.location.streetAddress()";
if (lowerProp.includes("city")) return "faker.location.city()";
if (lowerProp.includes("country")) return "faker.location.country()";
if (lowerProp.includes("url") || lowerProp.includes("link")) return "faker.internet.url()";
if (lowerProp.includes("id")) return "faker.string.uuid()";
}
return "faker.lorem.word()";
}
if (text === "number") {
return "faker.number.int({ min: 1, max: 100 })";
}
if (text === "boolean") {
return "faker.datatype.boolean()";
}
if (text === "Date") {
return "faker.date.recent()";
}
if (text === "null") {
return "null";
}
if (text === "undefined") {
return "undefined";
}
if (text === "any") {
return "faker.lorem.word()";
}
if (type.isUnion()) {
const unionTypes = type.getUnionTypes();
const mockValues = unionTypes.map((t) => this.generateMockValue(t, sourceFile, options, depth, visited)).join(", ");
return `faker.helpers.arrayElement([${mockValues}])`;
}
const apparent = type.getApparentType();
const key = apparent.getText();
if (visited.has(key)) return "{}";
visited.add(key);
if (type.getAliasSymbol()?.getName() === "Record") {
const [keyType, valueType] = type.getAliasTypeArguments();
if (!keyType || !valueType) {
visited.delete(key);
return "{}";
}
const keyMock = this.generateMockValue(keyType, sourceFile, options, depth + 1, visited);
const mockValue = this.generateMockValue(valueType, sourceFile, options, depth + 1, visited);
const { min, max } = options.recordLength || { min: 1, max: 5 };
return `Object.fromEntries(Array.from({ length: faker.number.int({ min: ${min}, max: ${max} }) }, () => [${keyMock}, ${mockValue}]))`;
}
const props = apparent.getProperties();
if (props.length === 0) {
visited.delete(key);
return "{}";
}
const lines = props.filter((prop) => !/^__@.+@\d+$/.test(prop.getName())).map((prop) => {
const propName2 = prop.getName();
const propType = prop.getTypeAtLocation(sourceFile);
const optional = prop.isOptional?.() ?? false;
if (optional && Math.random() > (options.optionalPropertyChance || 0.7)) {
return null;
}
const mockValue = this.generateMockValue(propType, sourceFile, options, depth + 1, visited, propName2);
return `${indent} ${propName2}: ${mockValue},`;
}).filter(Boolean);
visited.delete(key);
return `{
${lines.join("\n")}
${indent}}`;
}
/**
* Generate a mock function for a TypeScript interface
*/
generateMockFunction(interfaceName, filePath, options = {}) {
const sourceFile = this.getSourceFile(filePath);
const iface = sourceFile.getInterface(interfaceName);
if (!iface) {
throw new Error(`Interface '${interfaceName}' not found in ${path.basename(filePath)}`);
}
const mockGenerator = `
function create${interfaceName}Mock() {
return ${this.generateMockValue(iface.getType(), sourceFile, options)};
}
function create${interfaceName}Mocks(count = 1) {
return Array.from({ length: count }, () => create${interfaceName}Mock());
}
return create${interfaceName}Mocks;
`;
return new Function("faker", mockGenerator)(faker);
}
};
var generator = new MockInterfaceGenerator();
function createFakeGenerator(interfaceName, filePath, options = {}) {
return generator.generateMockFunction(interfaceName, filePath, options);
}
var index_default = createFakeGenerator;
export {
createFakeGenerator,
index_default as default
};