interface-faker
Version:
Generate mock data for TypeScript interfaces using faker.js
218 lines (214 loc) • 8.13 kB
JavaScript
;
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var index_exports = {};
__export(index_exports, {
createFakeGenerator: () => createFakeGenerator,
default: () => index_default
});
module.exports = __toCommonJS(index_exports);
var import_ts_morph = require("ts-morph");
var import_faker = require("@faker-js/faker");
var path = __toESM(require("path"));
var fs = __toESM(require("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 import_ts_morph.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)(import_faker.faker);
}
};
var generator = new MockInterfaceGenerator();
function createFakeGenerator(interfaceName, filePath, options = {}) {
return generator.generateMockFunction(interfaceName, filePath, options);
}
var index_default = createFakeGenerator;
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
createFakeGenerator
});