UNPKG

counterfact

Version:

a library for building a fake REST API for testing

148 lines (147 loc) 5.68 kB
import nodePath from "node:path"; import createDebugger from "debug"; import prettier from "prettier"; const debug = createDebugger("counterfact:typescript-generator:script"); export class Script { constructor(repository, path) { this.repository = repository; this.comments = []; this.exports = new Map(); this.imports = new Map(); this.externalImport = new Map(); this.cache = new Map(); this.typeCache = new Map(); this.path = path; } get relativePathToBase() { return this.path .split("/") .slice(0, -1) .map(() => "..") .join("/"); } firstUniqueName(coder) { for (const name of coder.names()) { if (!this.imports.has(name)) { return name; } } throw new Error(`could not find a unique name for ${coder.id}`); } export(coder, isType = false, isDefault = false) { const cacheKey = isDefault ? "default" : `${coder.id}:${isType}`; if (this.cache.has(cacheKey)) { return this.cache.get(cacheKey); } const name = this.firstUniqueName(coder); this.cache.set(cacheKey, name); const exportStatement = { beforeExport: coder.beforeExport(this.path), done: false, id: coder.id, isDefault, isType, typeDeclaration: coder.typeDeclaration(this.exports, this), }; exportStatement.promise = coder .delegate() .then((availableCoder) => { exportStatement.name = name; exportStatement.code = availableCoder.write(this); return availableCoder; }) .catch((error) => { exportStatement.code = `{/* error creating export "${name}" for ${this.path}: ${error.stack} */}`; exportStatement.error = error; }) .finally(() => { exportStatement.done = true; }); this.exports.set(name, exportStatement); return name; } exportDefault(coder, isType = false) { this.export(coder, isType, true); } import(coder, isType = false, isDefault = false) { debug("import coder: %s", coder.id); const modulePath = coder.modulePath(); const cacheKey = `${coder.id}@${modulePath}:${isType}:${isDefault}`; debug("cache key: %s", cacheKey); if (this.cache.has(cacheKey)) { debug("cache hit: %s", cacheKey); return this.cache.get(cacheKey); } debug("cache miss: %s", cacheKey); const name = this.firstUniqueName(coder); this.cache.set(cacheKey, name); const scriptFromWhichToExport = this.repository.get(modulePath); const exportedName = scriptFromWhichToExport.export(coder, isType, isDefault); this.imports.set(name, { isDefault, isType, name: exportedName, script: scriptFromWhichToExport, }); return name; } importType(coder) { return this.import(coder, true); } importDefault(coder, isType = false) { return this.import(coder, isType, true); } importExternal(name, modulePath, isType = false) { this.externalImport.set(name, { isType, modulePath }); return name; } importExternalType(name, modulePath) { return this.importExternal(name, modulePath, true); } importSharedType(name) { return this.importExternal(name, nodePath.join(this.relativePathToBase, "types.ts").replaceAll("\\", "/"), true); } exportType(coder) { return this.export(coder, true); } isInProgress() { return Array.from(this.exports.values()).some((exportStatement) => !exportStatement.done); } finished() { return Promise.all(Array.from(this.exports.values(), (value) => value.promise)); } externalImportStatements() { return Array.from(this.externalImport, ([name, { isDefault, isType, modulePath }]) => `import${isType ? " type" : ""} ${isDefault ? name : `{ ${name} }`} from "${modulePath}";`); } importStatements() { return Array.from(this.imports, ([name, { isDefault, isType, script }]) => { const resolvedPath = nodePath .relative(nodePath.dirname(this.path).replaceAll("\\", "/"), script.path.replace(/\.ts$/u, ".js")) .replaceAll("\\", "/"); return `import${isType ? " type" : ""} ${isDefault ? name : `{ ${name} }`} from "${resolvedPath.includes("../") ? "" : "./"}${resolvedPath}";`; }); } exportStatements() { return Array.from(this.exports.values(), ({ beforeExport, code, isDefault, isType, name, typeDeclaration }) => { if (code.raw) { return code.raw; } if (isDefault) { return `${beforeExport}export default ${code};`; } const keyword = isType ? "type" : "const"; const typeAnnotation = typeDeclaration.length === 0 ? "" : `:${typeDeclaration}`; return `${beforeExport}export ${keyword} ${name}${typeAnnotation} = ${code};`; }); } contents() { return prettier.format([ this.comments.map((comment) => `// ${comment}`).join("\n"), this.comments.length > 0 ? "\n\n" : "", this.externalImportStatements().join("\n"), this.importStatements().join("\n"), "\n\n", this.exportStatements().join("\n\n"), ].join(""), { parser: "typescript" }); } }