{"version":3,"file":"index.cjs","names":[],"sources":["../src/clone.ts","../src/config.ts","../src/files.ts","../src/jewire.ts","../src/index.ts"],"sourcesContent":["import { CloneFn } from './types';\n\n/**\n * Checks if the provided value is a JavaScript function or a class constructor.\n * You should first check that `typeof functionOrClass === 'function'`\n *\n * @param {any} functionOrClass - value known to be either a function or class\n * @returns {boolean} - `true` if the value is a function, false if it is a class\n */\nexport const isFunction = (functionOrClass: any): boolean => {\n  const propertyNames = Object.getOwnPropertyNames(functionOrClass);\n  return !propertyNames.includes('prototype') || propertyNames.includes('arguments');\n};\n\n/**\n * Deep clones an object or array, creating a new object with the\n * same structure and values.\n *\n * @param {T} obj - The object or array to deep clone.\n * @returns {T} - A deep clone of the input object or array.\n */\nconst objectClone: CloneFn = <T>(obj: T): T => {\n  if (!obj || typeof obj !== 'object') {\n    return obj;\n  }\n  if (Array.isArray(obj)) {\n    // Empty array doesn't clone properly in Jest with just map\n    return (obj.length === 0 ? [] : [...obj.map(objectClone)]) as T;\n  }\n  const cloneObj: Record<string, any> = {};\n  for (const [key, value] of Object.entries(obj)) {\n    cloneObj[key] = objectClone(value);\n  }\n  return cloneObj as T;\n};\n\n/**\n * Deepclones the return type of a function\n *\n * A cloned function here means that arrays and objects are repacked at runtime\n * through deepcloining such that the `expect.toStrictEqual` matcher from jest\n * can accurately compare, essentially removing jest comparison false-negative\n * issues relating to \"serialises to the same string\".\n *\n * @param fn - The function to be jewirified.\n * @param clone - The deep cloning function (defaulting to `deepClone`).\n * @returns A jewirified function.\n */\nconst functionClone = <T extends (...args: any[]) => any>(fn: T, clone: CloneFn) => {\n  /**\n   * Defines a new wrapper function that deep-clones the return value at run time\n   *\n   * @param args the arguments to be forwarded to the functions we are cloning\n   * @returns the results of the function, deep copied at run time.\n   */\n  const wrapperClonedFunction = (...args: Parameters<T>): ReturnType<T> => {\n    const result = fn(...args);\n    return result && typeof result === 'object' ? clone(result) : result;\n  };\n  Object.defineProperty(wrapperClonedFunction, 'name', {\n    value: fn.name,\n    writable: false,\n    enumerable: false,\n    configurable: true,\n  });\n  return wrapperClonedFunction;\n};\n\n/**\n * For each of the method in the class, apply function/object clone at run time\n * for Jest expect.toStrictEqual to not return false negatives\n *\n * Based off this answer: https://stackoverflow.com/a/70710396/22324694\n\n * @param target object instance to decorate methods around\n */\nfunction decorateClassMethodClone(target: any, clone: CloneFn) {\n  /**\n   * Ensure that the return values of all objects are cloned\n   *\n   * @param obj object whose method return values need to be cloned\n   * @param key name of the method whose return values will be cloned\n   */\n  const decorateMethod = (obj: Record<string, any>, key: string | symbol): void => {\n    const descriptor = Reflect.getOwnPropertyDescriptor(obj, key);\n    /* istanbul ignore next */\n    if (!descriptor?.configurable) {\n      return;\n    }\n    const { value } = descriptor;\n    if (typeof value === 'function' && value !== target) {\n      descriptor.value = function (...args: any[]) {\n        return entityClone(value.apply(this, args), clone);\n      };\n      Object.defineProperty(obj, key, descriptor);\n    }\n  };\n\n  // Decorate static methods\n  Object.getOwnPropertyNames(target)\n    .filter((key) => !['length', 'name', 'prototype'].includes(key))\n    .forEach((key) => decorateMethod(target, key));\n\n  // Decorate instance methods\n  Reflect.ownKeys(target.prototype)\n    .filter((key) => key !== 'constructor')\n    .forEach((key) => decorateMethod(target.prototype, key));\n\n  return target;\n}\n\n/**\n * Deeply clones a class object, preserving the prototype chain.\n * - Modified from https://stackoverflow.com/a/43753414/22324694\n *\n * @template T\n * @param {T} obj - The object to clone\n * @returns {T} - A deep clone of the input object\n * @throws {Error} - An unsupported data type is encountered\n */\n/* istanbul ignore next */\nconst classClone = <T>(obj: T, objClone: CloneFn): T => {\n  if (obj ?? typeof obj !== 'object') {\n    return decorateClassMethodClone(obj as any, objClone);\n  }\n  const props = Object.getOwnPropertyDescriptors(obj);\n  for (const prop of Object.keys(props)) {\n    props[prop].value = classClone(props[prop].value, objClone);\n  }\n  return Object.create(Object.getPrototypeOf(obj), props);\n};\n\n/**\n * Helper function to clone functions or classes\n *\n * @param functionOrClass the functions or class to clone\n * @param objClone\n * @returns the cloned function or class\n */\nconst functionOrClassClone = (functionOrClass: any, objClone: CloneFn) =>\n  isFunction(functionOrClass)\n    ? functionClone(functionOrClass, objClone)\n    : classClone(functionOrClass, objClone);\n\n/**\n * Clones an entity for use with Jest expect.toStrictEqual\n *\n * @param entity the entity to clone, including variables/functions/classes\n * @param objClone custom function to clone objects/arrays\n * @returns the cloned entity\n */\nfunction entityClone(entity: any, objClone = objectClone) {\n  return typeof entity === 'function' ? functionOrClassClone(entity, objClone) : objClone(entity);\n}\n\nexport default entityClone;\n","export const VALID_FILE_EXTENSIONS = Object.freeze(['.js', '.cjs']);\n","import fs from 'fs';\nimport path from 'path';\nimport { parse } from 'meriyah';\nimport type { Statement } from 'meriyah/dist/types/estree';\nimport { VALID_FILE_EXTENSIONS } from './config';\nimport { HiddenExportInfo, Symbols } from './types';\n\n/**\n * Get the file path of the caller function.\n *\n * Implementation inspired by:\n * - https://www.npmjs.com/package/callsite?activeTab=code\n *\n * @returns {string} absolute path or an empty string if no caller\n */\nexport const getCallerDirname = (): string => {\n  const orig = Error.prepareStackTrace;\n  Error.prepareStackTrace = (_, stack) => stack;\n  const err = new Error();\n  Error.captureStackTrace(err, getCallerDirname);\n  const stack = err.stack as any;\n  Error.prepareStackTrace = orig;\n  const callerFilePath = stack[1].getFileName();\n  /* istanbul ignore next */\n  return path.dirname(\n    callerFilePath.startsWith('file://') ? callerFilePath.substring(7) : callerFilePath,\n  );\n};\n\n/**\n * Find the module file path by checking for available extensions\n * as defined by VALID_FILE_EXTENSIONS\n *\n * @param {string} filePath The absolute path to the file\n * @returns {string} The resolved file path with appended extension\n * @throws {Error} If the file path does not match any valid extensions\n */\nconst findFileWithExtensions = (filePath: string): string => {\n  for (const ext of VALID_FILE_EXTENSIONS) {\n    const extFilePath = `${filePath}${ext}`;\n    if (fs.existsSync(extFilePath)) {\n      return extFilePath;\n    }\n  }\n  throw new Error(`No such file '${filePath}' with matching extensions [${VALID_FILE_EXTENSIONS}]`);\n};\n\n/**\n * Find the module file path\n *\n * @param {string} modulePath - The path to the module\n * @param {string} basePath - The base path for the module\n * @returns {string} The resolved file path\n * @throws {Error} If the file is not found\n */\nexport const findModuleFile = (basePath: string, modulePath: string): string => {\n  const filePath = path.join(basePath, modulePath);\n  return fs.existsSync(filePath) ? filePath : findFileWithExtensions(filePath);\n};\n\n/**\n * Appends the name of the node given to the appropriate symbol\n *\n * @param node - a node from the Abstract Syntaxt Tree\n * @param symbols - object containing three arrays: functions/classes/variables\n * @returns symbols consisting of variables, functions and classes\n */\nconst retrieveSymbolsFromAst = (node: Statement, symbols: Symbols): void => {\n  switch (node.type) {\n    case 'VariableDeclaration':\n      node.declarations.forEach((declaration) => {\n        if (declaration.id.type === 'Identifier') {\n          symbols.variables.push(declaration.id.name);\n        }\n      });\n      break;\n    case 'FunctionDeclaration':\n      if (node.id !== null) {\n        symbols.functions.push(node.id.name);\n      }\n      break;\n    case 'ClassDeclaration':\n      if (node.id !== null) {\n        symbols.classes.push(node.id.name);\n      }\n      break;\n    default:\n      break;\n  }\n};\n\n/**\n * Parses code from a file and generates an Abstract Syntax Tree (AST)\n *\n * @param {string} filePath - The absolute path to the file\n * @throws {Error} If the file is empty or contains errors while parsing\n * @returns {Object} object with properties:\n * - `ast` (ASTProgram): The Abstract Syntax Tree (AST) representing the code\n * - `code` (string): The original code read from the file\n */\nconst createAbstractSyntaxTree = (filePath: string) => {\n  const code = fs.readFileSync(filePath, 'utf-8');\n  // if (code.length === 0) {\n  //   throw new Error(`Module '${filePath}' is an empty file`);\n  // }\n  try {\n    return { ast: parse(code), code };\n  } catch (error: unknown) {\n    throw new Error(\n      `>>> Failed to parse code:\n===============================================================================\n${filePath}\n===============================================================================\n\n${code}\n\n=============================================\nPlease double check the file:\n    ${filePath}\nfor the error: ${error}`,\n      {\n        cause: error,\n      },\n    );\n  }\n};\n\n/**\n * Retrieves the name of all global variables/functions/classes, including\n * those not exported in the file\n *\n * @param {string} filePath - The path to the JavaScript module file\n * @returns {HiddenExportInfo} - Object of string[] for hidden exports\n * @throws {Error} If the module file is empty or cannot be parsed.\n */\nexport const getModuleHiddenExports = (filePath: string): HiddenExportInfo => {\n  const symbols = {\n    variables: [],\n    functions: [],\n    classes: [],\n  };\n  const { ast, code } = createAbstractSyntaxTree(filePath);\n  for (const node of ast.body) {\n    retrieveSymbolsFromAst(node, symbols);\n  }\n  return { symbols, ast, code };\n};\n","import rewire from 'rewire';\nimport entityClone from './clone';\nimport { findModuleFile, getCallerDirname, getModuleHiddenExports } from './files';\nimport { JewireEntities, Options } from './types';\n\n/**\n * Leverages rewire to extract hidden exports from a JavaScript module, but\n * allowing named exports in the same style as require.\n * Returned objects and arrays are also deep-cloned such that they can be\n * tested with .toStrictEqual() in Jest, although introduces limitations\n *\n * @param {string} relativePath - the name or path of the module, e.g. ./arrays\n * @param {Options} [options] - options for jewire as defined in types.ts\n * @returns {JewireEntities} - Named exports from the file\n */\nconst jewire = (relativePath: string, options: Options = {}): JewireEntities => {\n  const filePath = findModuleFile(options.basePath ?? getCallerDirname(), relativePath);\n  const hiddenExportInfo = getModuleHiddenExports(filePath);\n  const hiddenExports = Object.values(hiddenExportInfo.symbols).flat();\n  const rewireModule = rewire(filePath);\n\n  /**\n   * The internal function used by jewire to retrieve objects. It has the same\n   * prototype as rewireModule.__get__, although objects are deep-cloned.\n   *\n   * @param {string} name the name of the object to retrieve\n   * @returns {any} the retrieved object\n   */\n  const jewireGetter = (name: string): any =>\n    entityClone(rewireModule.__get__(name), options.objectClone);\n\n  const entities: JewireEntities = {\n    __jewireContext__: {\n      rewire: rewireModule,\n      hiddenExportInfo,\n      jewireGetter,\n    },\n  };\n  for (const hiddenExport of hiddenExports) {\n    entities[hiddenExport] = jewireGetter(hiddenExport);\n  }\n\n  return entities;\n};\n\nexport default jewire;\n","import jewire from './jewire';\nexport type { Options, HiddenExportInfo, CloneFn } from './types';\nexport default jewire;\n"],"mappings":"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"}