@pythagora.io/js-code-processing
Version:
This repository hosts the 'code-processing' npm package, which contains a set of code processing methods for generating tests.
1,178 lines (1,146 loc) • 57.3 kB
JavaScript
'use strict';
var require$$0 = require('fs');
var require$$1 = require('path');
var require$$2$1 = require('lodash');
var require$$3 = require('@babel/generator');
var require$$1$1 = require('@babel/parser');
var require$$2 = require('@babel/traverse');
var require$$1$2 = require('axios');
var require$$3$1 = require('http');
var require$$4 = require('https');
function getDefaultExportFromCjs (x) {
return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
}
var common$2 = {
PYTHAGORA_TESTS_DIR: "pythagora_tests",
PYTHAGORA_METADATA_DIR: ".pythagora",
METADATA_FILENAME: "metadata.json",
REVIEW_DATA_FILENAME: "review.json",
EXPORT_METADATA_FILENAME: "export.json",
CONFIG_FILENAME: "config.json",
PYTHAGORA_ASYNC_STORE: 42069420,
PYTHAGORA_DELIMITER: "-_-",
EXPORTED_TESTS_DIR: "pythagora_tests/exported_tests",
EXPORTED_TESTS_DATA_DIR: "pythagora_tests/exported_tests/data",
SRC_TO_ROOT: "../../../",
MIN_TOKENS_FOR_GPT_RESPONSE: 1640,
MAX_GPT_MODEL_TOKENS: 8192,
PYTHAGORA_UNIT_TESTS_VERSION: 1,
PYTHAGORA_UNIT_DIR: "pythagora_tests/unit",
PYTHAGORA_API_SERVER: "https://api.pythagora.io"
};
const path$4 = require$$1;
const {
PYTHAGORA_UNIT_DIR: PYTHAGORA_UNIT_DIR$1
} = common$2;
const fs$5 = require$$0.promises;
const fsSync = require$$0;
async function checkPathType$2(path) {
const stats = await fs$5.stat(path);
return stats.isFile() ? "file" : "directory";
}
function getRelativePath$3(filePath, referenceFolderPath) {
let relativePath = path$4.relative(path$4.resolve(referenceFolderPath), filePath);
if (!relativePath.startsWith("../") && !relativePath.startsWith("./")) {
relativePath = "./" + relativePath;
}
return relativePath;
}
function getFolderTreeItem$1(prefix, absolutePath) {
const isDirectory = absolutePath.includes(":") ? false : fsSync.statSync(absolutePath).isDirectory();
return {
line: `${prefix}${path$4.basename(absolutePath)}`,
absolutePath,
isDirectory
};
}
function isPathInside$1(basePath, targetPath) {
const relativePath = path$4.relative(basePath, targetPath);
return !relativePath || !relativePath.startsWith("..") && !path$4.isAbsolute(relativePath);
}
function getTestFolderPath$2(filePath, rootPath) {
return path$4.join(path$4.join(rootPath, PYTHAGORA_UNIT_DIR$1), path$4.dirname(filePath).replace(path$4.resolve(rootPath), ""), path$4.basename(filePath, path$4.extname(filePath)));
}
function calculateDepth$1(basePath, targetPath) {
const baseComponents = basePath.split(path$4.sep);
const targetComponents = targetPath.split(path$4.sep);
// The depth is the difference in the number of components
return targetComponents.length - baseComponents.length + 1;
}
var files = {
checkPathType: checkPathType$2,
getRelativePath: getRelativePath$3,
getFolderTreeItem: getFolderTreeItem$1,
isPathInside: isPathInside$1,
getTestFolderPath: getTestFolderPath$2,
calculateDepth: calculateDepth$1
};
const path$3 = require$$1;
const babelParser = require$$1$1;
const {
default: babelTraverse
} = require$$2;
const {
default: generator$1
} = require$$3;
const {
getRelativePath: getRelativePath$2
} = files;
const fs$4 = require$$0.promises;
const _$4 = require$$2$1;
function replaceRequirePaths$2(code, currentPath, testFilePath) {
const importRequirePathRegex = /(require\((['"`])(.+?)\2\))|(import\s+.*?\s+from\s+(['"`])(.+?)\5)/g;
return code.replace(importRequirePathRegex, (match, requireExp, requireQuote, requirePath, importExp, importQuote, importPath) => {
let quote, modulePath;
if (requireExp) {
quote = requireQuote;
modulePath = requirePath;
} else if (importExp) {
quote = importQuote;
modulePath = importPath;
}
if (!modulePath.startsWith("./") && !modulePath.startsWith("../")) {
return match;
}
const absoluteRequirePath = path$3.resolve(currentPath, modulePath);
const newRequirePath = getRelativePath$2(absoluteRequirePath, testFilePath);
if (requireExp) {
return `require(${quote}${newRequirePath}${quote})`;
} else if (importExp) {
return `${importExp.split("from")[0].trim()} from ${quote}${newRequirePath}${quote}`;
}
});
}
async function getAstFromFilePath$3(filePath) {
let data = await fs$4.readFile(filePath, "utf8");
// Remove shebang if it exists
if (data.indexOf("#!") === 0) {
data = "//" + data;
}
const ast = babelParser.parse(data, {
sourceType: "module",
// Consider input as ECMAScript module
locations: true,
plugins: ["jsx", "objectRestSpread", "typescript"] // Enable JSX, typescript and object rest/spread syntax
});
return ast;
}
async function getModuleTypeFromFilePath$2(ast) {
let moduleType = "CommonJS";
babelTraverse(ast, {
ImportDeclaration(path) {
moduleType = "ES6";
path.stop(); // Stop traversal when an ESM statement is found
},
ExportNamedDeclaration(path) {
moduleType = "ES6";
path.stop(); // Stop traversal when an ESM statement is found
},
ExportDefaultDeclaration(path) {
moduleType = "ES6";
path.stop(); // Stop traversal when an ESM statement is found
},
CallExpression(path) {
if (path.node.callee.name === "require") {
moduleType = "CommonJS";
path.stop(); // Stop traversal when a CommonJS statement is found
}
},
AssignmentExpression(path) {
if (path.node.left.type === "MemberExpression" && path.node.left.object.name === "module" && path.node.left.property.name === "exports") {
moduleType = "CommonJS";
path.stop(); // Stop traversal when a CommonJS statement is found
}
}
});
return moduleType;
}
function collectTopRequires(node) {
const requires = [];
babelTraverse(node, {
VariableDeclaration(path) {
if (path.node.declarations[0].init && path.node.declarations[0].init.callee && path.node.declarations[0].init.callee.name === "require") {
requires.push(generator$1(path.node).code);
}
},
ImportDeclaration(path) {
requires.push(generator$1(path.node).code);
}
});
return requires;
}
function insideFunctionOrMethod(nodeTypesStack) {
return nodeTypesStack.slice(0, -1).some(type => /^(FunctionDeclaration|FunctionExpression|ArrowFunctionExpression|ClassMethod)$/.test(type));
}
function getPathFromRequireOrImport(path) {
return (path.match(/require\((['"`])(.*?)\1\)|import\s+.*?\s+from\s+(['"`])(.*?)\3/) || [])[2] || (path.match(/require\((['"`])(.*?)\1\)|import\s+.*?\s+from\s+(['"`])(.*?)\3/) || [])[4];
}
function getFullPathFromRequireOrImport(importPath, filePath) {
if (importPath && (importPath.startsWith("./") || importPath.startsWith("../"))) {
importPath = path$3.resolve(filePath.substring(0, filePath.lastIndexOf("/")), importPath);
}
if (importPath.lastIndexOf(".js") + ".js".length !== importPath.length) {
importPath += ".js";
}
return importPath;
}
function getRelatedFunctions$1(node, ast, filePath, functionList) {
const relatedFunctions = [];
const requiresFromFile = collectTopRequires(ast);
function processNodeRecursively(node) {
if (node.type === "CallExpression") {
let funcName;
let callee = node.callee;
while (callee.type === "MemberExpression") {
callee = callee.object;
}
if (callee.type === "Identifier") {
funcName = callee.name;
} else if (callee.type === "MemberExpression") {
funcName = callee.property.name;
if (callee.object.type === "Identifier") {
funcName = callee.object.name + "." + funcName;
}
}
let requiredPath = requiresFromFile.find(require => require.includes(funcName));
const importPath = requiredPath;
if (!requiredPath) {
requiredPath = filePath;
} else {
requiredPath = getPathFromRequireOrImport(requiredPath);
requiredPath = getFullPathFromRequireOrImport(requiredPath, filePath);
}
const functionFromList = functionList[requiredPath + ":" + funcName];
if (functionFromList) {
relatedFunctions.push(_$4.extend(functionFromList, {
fileName: requiredPath,
importPath
}));
}
}
// Traverse child nodes
for (const key in node) {
const prop = node[key];
if (Array.isArray(prop)) {
for (const child of prop) {
if (typeof child === "object" && child !== null) {
processNodeRecursively(child);
}
}
} else if (typeof prop === "object" && prop !== null) {
processNodeRecursively(prop);
}
}
}
processNodeRecursively(node);
return relatedFunctions;
}
async function stripUnrelatedFunctions$1(filePath, targetFuncNames) {
const ast = await getAstFromFilePath$3(filePath);
// Store the node paths of unrelated functions and class methods
const unrelatedNodes = [];
processAst$2(ast, (funcName, path, type) => {
if (!targetFuncNames.includes(funcName) && type !== "exportFn" && type !== "exportObj") {
// If the function is being used as a property value, remove the property instead of the function
if (path.parentPath.isObjectProperty()) {
unrelatedNodes.push(path.parentPath);
} else {
unrelatedNodes.push(path);
}
}
});
// Remove unrelated nodes from the AST
for (const path of unrelatedNodes) {
path.remove();
}
// Generate the stripped code from the modified AST
const strippedCode = generator$1(ast).code;
return strippedCode;
}
function processAst$2(ast, cb) {
const nodeTypesStack = [];
babelTraverse(ast, {
enter(path) {
nodeTypesStack.push(path.node.type);
if (insideFunctionOrMethod(nodeTypesStack)) return;
// Handle module.exports
if (path.isExpressionStatement()) {
const expression = path.node.expression;
if (expression && expression.type === "AssignmentExpression") {
const left = expression.left;
if (left.object && left.object.type === "MemberExpression" && left.object.object.name === "module" && left.object.property.name === "exports") {
if (expression.right.type === "Identifier") {
// module.exports.func1 = func1
return cb(left.property.name, path, "exportObj");
} else if (expression.right.type === "FunctionExpression") {
// module.exports.funcName = function() { ... }
// module.exports = function() { ... }
const loc = path.node.loc.start;
const funcName = left.property.name || `anon_func_${loc.line}_${loc.column}`;
return cb(funcName, path, "exportObj");
}
} else if (left.type === "MemberExpression" && left.object.name === "module" && left.property.name === "exports") {
if (expression.right.type === "Identifier") {
// module.exports = func1
return cb(expression.right.name, path, "exportFn");
} else if (expression.right.type === "FunctionExpression") {
let funcName;
if (expression.right.id) {
// module.exports = function func1() { ... }
funcName = expression.right.id.name;
} else {
// module.exports = function() { ... }
const loc = path.node.loc.start;
funcName = `anon_func_${loc.line}_${loc.column}`;
}
return cb(funcName, path, "exportFnDef");
} else if (expression.right.type === "ObjectExpression") {
expression.right.properties.forEach(prop => {
if (prop.type === "ObjectProperty") {
// module.exports = { func1 };
return cb(prop.key.name, path, "exportObj");
}
});
}
} /* Handle TypeScript transpiled exports */else if (left.type === "MemberExpression" && left.object.name === "exports") {
// exports.func1 = function() { ... }
// exports.func1 = func1
return cb(left.property.name, path, "exportObj");
}
}
}
// Handle ES6 export statements
if (path.isExportDefaultDeclaration()) {
const declaration = path.node.declaration;
if (declaration.type === "FunctionDeclaration" || declaration.type === "Identifier") {
// export default func1;
// TODO export default function() { ... }
// TODO cover anonimous functions - add "anon_" name
return cb(declaration.id ? declaration.id.name : declaration.name, path, "exportFn");
} else if (declaration.type === "ObjectExpression") {
declaration.properties.forEach(prop => {
if (prop.type === "ObjectProperty") {
// export default { func1: func }
// export default { func1 }
return cb(prop.key.name, path, "exportObj");
}
});
} else if (declaration.type === "ClassDeclaration") {
// export default class Class1 { ... }
return cb(declaration.id ? declaration.id.name : declaration.name, path, "exportFnDef");
}
} else if (path.isExportNamedDeclaration()) {
if (path.node.declaration) {
if (path.node.declaration.type === "FunctionDeclaration") {
// export function func1 () { ... }
// export class Class1 () { ... }
return cb(path.node.declaration.id.name, path, "exportObj");
} else if (path.node.declaration.type === "VariableDeclaration") {
// export const const1 = 'constant';
// export const func1 = () => { ... }
path.node.declaration.declarations.forEach(declaration => {
return cb(declaration.id.name, path, "exportObj");
});
} else if (path.node.declaration.type === "ClassDeclaration") {
// export class Class1 { ... }
return cb(path.node.declaration.id.name, path, "exportFnDef");
}
} else if (path.node.specifiers.length > 0) {
path.node.specifiers.forEach(spec => {
// export { func as func1 }
return cb(spec.exported.name, path, "exportObj");
});
}
}
let funcName;
if (path.isFunctionDeclaration()) {
funcName = path.node.id.name;
} else if (path.isFunctionExpression() || path.isArrowFunctionExpression()) {
if (path.parentPath.isVariableDeclarator()) {
funcName = path.parentPath.node.id.name;
} else if (path.parentPath.isAssignmentExpression() || path.parentPath.isObjectProperty()) {
funcName = path.parentPath.node.left ? path.parentPath.node.left.name : path.parentPath.node.key.name;
}
} else if (path.node.type === "ClassMethod" && path.node.key.name !== "constructor") {
funcName = path.node.key.name;
if (path.parentPath.node.type === "ClassDeclaration") {
const className = path.parentPath.node.id.name;
funcName = `${className}.${funcName}`;
} else if (path.parentPath.node.type === "ClassExpression") {
const className = path.parentPath.node.id.name || "";
funcName = `${className}.${funcName}`;
} else if (path.parentPath.node.type === "ClassBody") {
// TODO: Handle classes that are not declared as a variable
const className = path.parentPath.parentPath.node.id ? path.parentPath.parentPath.node.id.name : "";
funcName = `${className}.${funcName}`;
}
}
if (funcName) cb(funcName, path);
},
exit(path) {
nodeTypesStack.pop();
}
});
}
function getSourceCodeFromAst$1(ast) {
return generator$1(ast).code;
}
function collectTestRequires(node) {
const requires = [];
babelTraverse(node, {
ImportDeclaration(path) {
if (path.node && path.node.specifiers && path.node.specifiers.length > 0) {
const requireData = {
code: generator$1(path.node).code,
functionNames: []
};
_$4.forEach(path.node.specifiers, s => {
if (s.local && s.local.name) {
requireData.functionNames.push(s.local.name);
}
});
requires.push(requireData);
}
},
CallExpression(path) {
if (path.node.callee.name === "require" && path.node.arguments && path.node.arguments.length > 0) {
const requireData = {
code: generator$1(path.node).code,
functionNames: []
};
// In case of a CommonJS require, the function name is usually the variable identifier of the parent node
if (path.parentPath && path.parentPath.node.type === "VariableDeclarator" && path.parentPath.node.id) {
requireData.functionNames.push(path.parentPath.node.id.name);
}
requires.push(requireData);
}
}
});
return requires;
}
function getRelatedTestImports$1(ast, filePath, functionList) {
const relatedCode = [];
const requiresFromFile = collectTestRequires(ast);
for (const fileImport in requiresFromFile) {
let requiredPath = getPathFromRequireOrImport(requiresFromFile[fileImport].code);
requiredPath = getFullPathFromRequireOrImport(requiredPath, filePath);
_$4.forEach(requiresFromFile[fileImport].functionNames, funcName => {
const functionFromList = functionList[requiredPath + ":" + funcName];
if (functionFromList) {
relatedCode.push(_$4.extend(functionFromList, {
fileName: requiredPath
}));
}
});
}
for (const relCode of relatedCode) {
let relatedCodeImports = "";
for (const func of relCode.relatedFunctions) {
if (func.importPath) {
relatedCodeImports += `${func.importPath}\n`;
}
}
if (relatedCodeImports) {
relCode.code = `${relatedCodeImports}\n${relCode.code}`;
}
}
return relatedCode;
}
var code = {
replaceRequirePaths: replaceRequirePaths$2,
getAstFromFilePath: getAstFromFilePath$3,
collectTopRequires,
insideFunctionOrMethod,
getRelatedFunctions: getRelatedFunctions$1,
stripUnrelatedFunctions: stripUnrelatedFunctions$1,
processAst: processAst$2,
getModuleTypeFromFilePath: getModuleTypeFromFilePath$2,
getSourceCodeFromAst: getSourceCodeFromAst$1,
getRelatedTestImports: getRelatedTestImports$1
};
/* eslint-disable space-before-function-paren */
/* eslint-disable no-useless-catch */
const fs$3 = require$$0;
const path$2 = require$$1;
const _$3 = require$$2$1;
const generator = require$$3.default;
const {
getFolderTreeItem,
isPathInside,
calculateDepth
} = files;
const {
getAstFromFilePath: getAstFromFilePath$2,
processAst: processAst$1,
getRelatedFunctions,
getModuleTypeFromFilePath: getModuleTypeFromFilePath$1
} = code;
let UnitTestsCommon$3 = class UnitTestsCommon {
static ignoreFolders = ["node_modules", "pythagora_tests"];
static ignoreFilesEndingWith = [".test.js", ".test.ts", ".test.tsx"];
static processExtensions = [".js", ".ts", ".tsx"];
static ignoreErrors = ["BABEL_PARSER_SYNTAX_ERROR"];
constructor({
pathToProcess,
pythagoraRoot,
funcName,
force
}) {
this.rootPath = pythagoraRoot;
this.queriedPath = path$2.resolve(pathToProcess);
this.funcName = funcName;
this.force = force;
this.filesToProcess = [];
this.processedFiles = [];
this.testsGenerated = [];
this.skippedFiles = [];
this.functionList = {};
this.folderStructureTree = [];
this.errors = [];
this.isFileToIgnore = fileName => {
return UnitTestsCommon.ignoreFilesEndingWith.some(ending => fileName.endsWith(ending));
};
}
async traverseAllDirectories(ignoreFilesRewrite) {
if (ignoreFilesRewrite) {
this.isFileToIgnore = ignoreFilesRewrite;
}
await this.traverseDirectory(this.queriedPath);
this.processedFiles = [];
await this.traverseDirectory(this.queriedPath);
this.processedFiles = [];
}
async traverseDirectory(file) {
if (this.processedFiles.includes(file)) {
return;
}
this.processedFiles.push(file);
const absolutePath = path$2.resolve(file);
const stat = fs$3.statSync(absolutePath);
if (!stat.isDirectory() && this.isFileToIgnore(file)) return;
if (stat.isDirectory()) {
if (UnitTestsCommon.ignoreFolders.includes(path$2.basename(absolutePath)) || path$2.basename(absolutePath).charAt(0) === ".") {
return;
}
if (isPathInside(path$2.dirname(this.queriedPath), absolutePath)) {
this.updateFolderTree(absolutePath);
}
const directoryFiles = fs$3.readdirSync(absolutePath).filter(f => {
const absoluteFilePath = path$2.join(absolutePath, f);
const fileStat = fs$3.statSync(absoluteFilePath);
if (fileStat.isDirectory()) {
const baseName = path$2.basename(absoluteFilePath);
return !UnitTestsCommon.ignoreFolders.includes(baseName) && !baseName.startsWith(".");
} else {
const ext = path$2.extname(f);
return UnitTestsCommon.processExtensions.includes(ext) && !this.isFileToIgnore(f);
}
}).map(f => path$2.join(absolutePath, f));
this.filesToProcess.push(...directoryFiles);
} else {
if (!UnitTestsCommon.processExtensions.includes(path$2.extname(absolutePath))) return;
if (isPathInside(path$2.dirname(this.queriedPath), absolutePath)) {
this.updateFolderTree(absolutePath);
}
await this.processFile(absolutePath, this.filesToProcess);
}
while (this.filesToProcess.length > 0) {
const nextFile = this.filesToProcess.shift();
if (this.processedFiles.includes(nextFile)) {
continue; // Skip processing if it has already been processed
}
await this.traverseDirectory(nextFile);
}
}
updateFolderTree(absolutePath) {
if (isPathInside(this.queriedPath, absolutePath) && !this.folderStructureTree.find(fst => fst.absolutePath === absolutePath)) {
const depth = calculateDepth(this.queriedPath, absolutePath);
let prefix = "";
for (let i = 1; i < depth; i++) {
prefix += "| ";
}
this.folderStructureTree.push(getFolderTreeItem(prefix + "├───", absolutePath));
}
}
resolveFilePath(filePath, extension) {
if (fs$3.existsSync(filePath)) {
return filePath;
}
const filePathWithExtension = `${filePath}${extension}`;
if (fs$3.existsSync(filePathWithExtension)) {
return filePathWithExtension;
}
return undefined;
}
async processFile(filePath) {
try {
const exportsFn = [];
const exportsObj = [];
const functions = [];
const ast = await getAstFromFilePath$2(filePath);
const syntaxType = await getModuleTypeFromFilePath$1(ast);
const extension = path$2.extname(filePath);
// Analyze dependencies
ast.program.body.forEach(node => {
if (node.type === "ImportDeclaration") {
let importedFile = path$2.resolve(path$2.dirname(filePath), node.source.value);
importedFile = this.resolveFilePath(importedFile, extension);
if (importedFile && !this.filesToProcess.includes(importedFile)) {
this.filesToProcess.push(importedFile);
}
} else if (node.type === "VariableDeclaration" && node.declarations.length > 0 && node.declarations[0].init && node.declarations[0].init.type === "CallExpression" && node.declarations[0].init.callee.name === "require") {
let importedFile = path$2.resolve(path$2.dirname(filePath), node.declarations[0].init.arguments[0].value);
importedFile = this.resolveFilePath(importedFile, extension);
if (importedFile && !this.filesToProcess.includes(importedFile)) {
this.filesToProcess.push(importedFile);
}
}
});
processAst$1(ast, (funcName, path, type) => {
if (type === "exportFn" || type === "exportFnDef") {
exportsFn.push(funcName);
} else if (type === "exportObj") {
exportsObj.push(funcName);
}
if (!["exportFn", "exportObj"].includes(type)) {
functions.push({
funcName,
code: generator(path.node).code,
filePath,
relatedFunctions: getRelatedFunctions(path.node, ast, filePath, this.functionList)
});
}
});
for (const f of functions) {
// TODO refactor since this is being set in code.js and here it's reverted
const classParent = exportsFn.find(e => new RegExp(`${e}\..*`).test(f.funcName)) || exportsObj.find(e => new RegExp(`${e}\..*`).test(f.funcName));
const isExportedAsObject = exportsObj.includes(f.funcName) || exportsObj.includes(classParent);
// if (classParent) f.funcName = f.funcName.replace(classParent + '.', '');
this.functionList[filePath + ":" + f.funcName] = _$3.extend(f, {
classParent,
syntaxType,
exported: exportsFn.includes(f.funcName) || isExportedAsObject || !!classParent,
exportedAsObject: isExportedAsObject,
funcName: f.funcName
});
}
} catch (err) {
throw err;
// writeLine(`Error parsing file ${filePath}: ${e}`);
}
}
sortFolderTree(tree) {
// 1. Sort the folderStructureTree
tree.sort((a, b) => {
if (a.absolutePath < b.absolutePath) {
return -1;
}
if (a.absolutePath > b.absolutePath) {
return 1;
}
return 0;
});
// 2. Set prefix according to the position in the directory
for (let i = 0; i < tree.length; i++) {
// Get the current directory path
const currentDirPath = path$2.dirname(tree[i].absolutePath);
// Check if it's the last file in the directory
if (i === tree.length - 1 || path$2.dirname(tree[i + 1].absolutePath) !== currentDirPath) {
// Update the prefix for the last file in the directory
tree[i].line = tree[i].line.replace("├───", "└───");
}
}
}
};
var unitTestsCommon = UnitTestsCommon$3;
const fs$2 = require$$0;
async function checkDirectoryExists$2(directoryPath) {
try {
const stats = await fs$2.promises.stat(directoryPath);
return stats.isDirectory();
} catch (error) {
if (error.code === "ENOENT") {
// Directory does not exist
return false;
}
// Other error occurred
throw error;
}
}
var common$1 = {
checkDirectoryExists: checkDirectoryExists$2
};
var colors$1 = {
red: "\x1b[31m",
yellow: "\x1b[33m",
green: "\x1b[32m",
blue: "\x1b[34m",
reset: "\x1b[0m",
bold: "\x1b[1m"
};
function _classPrivateMethodInitSpec$1(obj, privateSet) { _checkPrivateRedeclaration$1(obj, privateSet); privateSet.add(obj); }
function _classPrivateFieldInitSpec$1(obj, privateMap, value) { _checkPrivateRedeclaration$1(obj, privateMap); privateMap.set(obj, value); }
function _checkPrivateRedeclaration$1(obj, privateCollection) { if (privateCollection.has(obj)) { throw new TypeError("Cannot initialize the same private elements twice on an object"); } }
function _classPrivateMethodGet$1(receiver, privateSet, fn) { if (!privateSet.has(receiver)) { throw new TypeError("attempted to get private field on non-instance"); } return fn; }
function _classPrivateFieldGet$1(receiver, privateMap) { var descriptor = _classExtractFieldDescriptor$1(receiver, privateMap, "get"); return _classApplyDescriptorGet$1(receiver, descriptor); }
function _classApplyDescriptorGet$1(receiver, descriptor) { if (descriptor.get) { return descriptor.get.call(receiver); } return descriptor.value; }
function _classPrivateFieldSet$1(receiver, privateMap, value) { var descriptor = _classExtractFieldDescriptor$1(receiver, privateMap, "set"); _classApplyDescriptorSet$1(receiver, descriptor, value); return value; }
function _classExtractFieldDescriptor$1(receiver, privateMap, action) { if (!privateMap.has(receiver)) { throw new TypeError("attempted to " + action + " private field on non-instance"); } return privateMap.get(receiver); }
function _classApplyDescriptorSet$1(receiver, descriptor, value) { if (descriptor.set) { descriptor.set.call(receiver, value); } else { if (!descriptor.writable) { throw new TypeError("attempted to set read only private field"); } descriptor.value = value; } }
const fs$1 = require$$0;
const path$1 = require$$1;
const _$2 = require$$2$1;
const {
checkDirectoryExists: checkDirectoryExists$1
} = common$1;
const {
stripUnrelatedFunctions,
replaceRequirePaths: replaceRequirePaths$1,
getAstFromFilePath: getAstFromFilePath$1,
processAst
} = code;
const {
getRelativePath: getRelativePath$1,
getTestFolderPath: getTestFolderPath$1,
checkPathType: checkPathType$1
} = files;
const {
green: green$1,
red: red$2,
bold: bold$1,
reset: reset$2
} = colors$1;
const UnitTestsCommon$2 = unitTestsCommon;
var _API$1 = /*#__PURE__*/new WeakMap();
var _opts$1 = /*#__PURE__*/new WeakMap();
var _createTests = /*#__PURE__*/new WeakSet();
var _reformatDataForPythagoraAPI$1 = /*#__PURE__*/new WeakSet();
var _saveTests$1 = /*#__PURE__*/new WeakSet();
var _traverseDirectoryUnit = /*#__PURE__*/new WeakSet();
let UnitTests$1 = class UnitTests extends UnitTestsCommon$2 {
constructor(mainArgs, API, opts = {}) {
super(mainArgs);
_classPrivateMethodInitSpec$1(this, _traverseDirectoryUnit);
_classPrivateMethodInitSpec$1(this, _saveTests$1);
_classPrivateMethodInitSpec$1(this, _reformatDataForPythagoraAPI$1);
_classPrivateMethodInitSpec$1(this, _createTests);
_classPrivateFieldInitSpec$1(this, _API$1, {
writable: true,
value: void 0
});
_classPrivateFieldInitSpec$1(this, _opts$1, {
writable: true,
value: void 0
});
_classPrivateFieldSet$1(this, _API$1, API);
_classPrivateFieldSet$1(this, _opts$1, {
...opts
});
}
async runProcessing() {
await this.traverseAllDirectories();
await _classPrivateMethodGet$1(this, _traverseDirectoryUnit, _traverseDirectoryUnit2).call(this, this.queriedPath, this.funcName);
return {
errors: this.errors,
skippedFiles: this.skippedFiles,
testsGenerated: this.testsGenerated
};
}
};
async function _createTests2(filePath, funcToTest) {
try {
const extension = path$1.extname(filePath);
const ast = await getAstFromFilePath$1(filePath);
const foundFunctions = [];
processAst(ast, (funcName, path, type) => {
if (type === "exportFn" || type === "exportObj") return;
if (funcToTest && funcName !== funcToTest) return;
const functionFromTheList = this.functionList[filePath + ":" + funcName];
if (functionFromTheList && functionFromTheList.exported) {
// TODO refactor since this is being set in code.js and here it's reverted
// if (functionFromTheList.classParent) {
// funcName = funcName.replace(
// functionFromTheList.classParent + ".",
// ""
// );
// }
foundFunctions.push({
functionName: funcName,
functionCode: functionFromTheList.code,
relatedCode: functionFromTheList.relatedFunctions,
classParent: functionFromTheList.classParent,
isES6Syntax: functionFromTheList.syntaxType === "ES6",
exportedAsObject: functionFromTheList.exportedAsObject
});
}
});
const uniqueFoundFunctions = foundFunctions.filter((item, index, self) => index === self.findIndex(t => t.functionName === item.functionName && t.functionCode === item.functionCode));
this.sortFolderTree(this.folderStructureTree);
const fileIndex = this.folderStructureTree.findIndex(item => item.absolutePath === filePath);
for (const [i, funcData] of uniqueFoundFunctions.entries()) {
const indexToPush = fileIndex + 1 + i;
const prefix = this.folderStructureTree[fileIndex].line.split(path$1.basename(this.folderStructureTree[fileIndex].absolutePath))[0];
this.folderStructureTree.splice(indexToPush, 0, {
line: " ".repeat(prefix.length) + "└───" + funcData.functionName,
absolutePath: filePath + ":" + funcData.functionName
});
if (_classPrivateFieldGet$1(this, _opts$1).spinner) {
_classPrivateFieldGet$1(this, _opts$1).spinner.start(this.folderStructureTree, indexToPush);
}
const testFilePath = path$1.join(getTestFolderPath$1(filePath, this.rootPath), `/${funcData.functionName}.test${extension}`);
if (fs$1.existsSync(testFilePath) && !this.force) {
this.skippedFiles.push(testFilePath);
if (_classPrivateFieldGet$1(this, _opts$1).spinner) {
await _classPrivateFieldGet$1(this, _opts$1).spinner.stop();
}
this.folderStructureTree[indexToPush].line = `${green$1}${this.folderStructureTree[indexToPush].line}${reset$2}`;
continue;
}
const formattedData = await _classPrivateMethodGet$1(this, _reformatDataForPythagoraAPI$1, _reformatDataForPythagoraAPI2$1).call(this, funcData, filePath, _classPrivateFieldGet$1(this, _opts$1).isSaveTests ? getTestFolderPath$1(filePath, this.rootPath) : path$1.dirname(filePath));
const {
tests,
error
} = await _classPrivateFieldGet$1(this, _API$1).getUnitTests(formattedData, content => {
if (_classPrivateFieldGet$1(this, _opts$1).scrollableContent) {
_classPrivateFieldGet$1(this, _opts$1).scrollableContent.setContent(content);
_classPrivateFieldGet$1(this, _opts$1).scrollableContent.setScrollPerc(100);
}
if (_classPrivateFieldGet$1(this, _opts$1).screen) _classPrivateFieldGet$1(this, _opts$1).screen.render();
});
if (tests) {
const testGenerated = {
functionName: formattedData.functionName,
filePath,
testCode: tests
};
if (_classPrivateFieldGet$1(this, _opts$1).isSaveTests) {
const testPath = await _classPrivateMethodGet$1(this, _saveTests$1, _saveTests2$1).call(this, filePath, funcData.functionName, tests);
testGenerated.testPath = testPath;
}
this.testsGenerated.push(testGenerated);
if (_classPrivateFieldGet$1(this, _opts$1).spinner) {
await _classPrivateFieldGet$1(this, _opts$1).spinner.stop();
}
this.folderStructureTree[indexToPush].line = `${green$1}${this.folderStructureTree[indexToPush].line}${reset$2}`;
} else if (error) {
this.errors.push({
file: filePath,
function: funcData.functionName,
error: {
stack: error.stack,
message: error.message
}
});
if (_classPrivateFieldGet$1(this, _opts$1).spinner) {
await this.spinner.stop();
}
this.folderStructureTree[indexToPush].line = `${red$2}${this.folderStructureTree[indexToPush].line}${reset$2}`;
}
}
if (uniqueFoundFunctions.length > 0) {
this.folderStructureTree[fileIndex].line = `${green$1 + bold$1}${this.folderStructureTree[fileIndex].line}${reset$2}`;
}
} catch (e) {
if (!UnitTestsCommon$2.ignoreErrors.includes(e.code)) this.errors.push(e.stack);
}
}
async function _reformatDataForPythagoraAPI2$1(funcData, filePath, testFilePath) {
let relatedCode = _$2.groupBy(funcData.relatedCode, "fileName");
// TODO add check if there are more functionNames than 1 while exportedAsObject is true - this shouldn't happen ever
relatedCode = _$2.map(relatedCode, (value, key) => {
return {
fileName: key,
functionNames: value.map(item => item.funcName),
exportedAsObject: value[0].exportedAsObject,
syntaxType: value[0].syntaxType
};
});
let relatedCodeInSameFile = [funcData.functionName];
funcData.relatedCode = [];
for (const file of relatedCode) {
if (file.fileName === filePath) {
relatedCodeInSameFile = relatedCodeInSameFile.concat(file.functionNames);
} else {
const fileName = getRelativePath$1(file.fileName, path$1.dirname(filePath));
let code = await stripUnrelatedFunctions(file.fileName, file.functionNames);
const fullPath = filePath.substring(0, filePath.lastIndexOf("/")) + "/" + fileName;
code = replaceRequirePaths$1(code, filePath, getTestFolderPath$1(filePath, this.rootPath));
funcData.relatedCode.push({
fileName,
code,
functionNames: file.functionNames,
exportedAsObject: file.exportedAsObject,
syntaxType: file.syntaxType,
pathRelativeToTest: getRelativePath$1(fullPath, testFilePath)
});
}
}
funcData.functionCode = await stripUnrelatedFunctions(filePath, relatedCodeInSameFile);
funcData.functionCode = replaceRequirePaths$1(funcData.functionCode, path$1.dirname(filePath), getTestFolderPath$1(filePath, this.rootPath));
funcData.pathRelativeToTest = getRelativePath$1(filePath, testFilePath);
return funcData;
}
async function _saveTests2$1(filePath, name, testData) {
const dir = getTestFolderPath$1(filePath, this.rootPath);
const extension = path$1.extname(filePath);
if (!(await checkDirectoryExists$1(dir))) {
fs$1.mkdirSync(dir, {
recursive: true
});
}
const testPath = path$1.join(dir, `/${name}.test${extension}`);
fs$1.writeFileSync(testPath, testData);
return testPath;
}
async function _traverseDirectoryUnit2(file, funcName) {
if (this.processedFiles.includes(file)) {
return;
}
this.processedFiles.push(file);
if ((await checkPathType$1(file)) === "file") {
if (!UnitTestsCommon$2.processExtensions.includes(path$1.extname(file))) {
throw new Error("File extension is not supported");
}
return await _classPrivateMethodGet$1(this, _createTests, _createTests2).call(this, file, funcName);
}
const absolutePath = path$1.resolve(file);
const stat = fs$1.statSync(absolutePath);
if (!stat.isDirectory() && this.isFileToIgnore(file)) return;
if (stat.isDirectory()) {
if (UnitTestsCommon$2.ignoreFolders.includes(path$1.basename(absolutePath)) || path$1.basename(absolutePath).charAt(0) === ".") {
return;
}
const directoryFiles = fs$1.readdirSync(absolutePath).filter(f => {
const absoluteFilePath = path$1.join(absolutePath, f);
const fileStat = fs$1.statSync(absoluteFilePath);
if (fileStat.isDirectory()) {
const baseName = path$1.basename(absoluteFilePath);
return !UnitTestsCommon$2.ignoreFolders.includes(baseName) && !baseName.startsWith(".");
} else {
const ext = path$1.extname(f);
return UnitTestsCommon$2.processExtensions.includes(ext) && !this.isFileToIgnore(f);
}
}).map(f => path$1.join(absolutePath, f));
this.filesToProcess.push(...directoryFiles);
} else {
if (!UnitTestsCommon$2.processExtensions.includes(path$1.extname(absolutePath))) return;
await _classPrivateMethodGet$1(this, _createTests, _createTests2).call(this, absolutePath, funcName);
}
while (this.filesToProcess.length > 0) {
const nextFile = this.filesToProcess.shift();
if (this.processedFiles.includes(nextFile)) {
continue; // Skip processing if it has already been processed
}
await _classPrivateMethodGet$1(this, _traverseDirectoryUnit, _traverseDirectoryUnit2).call(this, nextFile, funcName);
}
}
var unitTests = UnitTests$1;
function _classPrivateMethodInitSpec(obj, privateSet) { _checkPrivateRedeclaration(obj, privateSet); privateSet.add(obj); }
function _classPrivateFieldInitSpec(obj, privateMap, value) { _checkPrivateRedeclaration(obj, privateMap); privateMap.set(obj, value); }
function _checkPrivateRedeclaration(obj, privateCollection) { if (privateCollection.has(obj)) { throw new TypeError("Cannot initialize the same private elements twice on an object"); } }
function _classStaticPrivateFieldSpecGet(receiver, classConstructor, descriptor) { _classCheckPrivateStaticAccess(receiver, classConstructor); _classCheckPrivateStaticFieldDescriptor(descriptor, "get"); return _classApplyDescriptorGet(receiver, descriptor); }
function _classCheckPrivateStaticFieldDescriptor(descriptor, action) { if (descriptor === undefined) { throw new TypeError("attempted to " + action + " private static field before its declaration"); } }
function _classStaticPrivateMethodGet(receiver, classConstructor, method) { _classCheckPrivateStaticAccess(receiver, classConstructor); return method; }
function _classCheckPrivateStaticAccess(receiver, classConstructor) { if (receiver !== classConstructor) { throw new TypeError("Private static access of wrong provenance"); } }
function _classPrivateFieldGet(receiver, privateMap) { var descriptor = _classExtractFieldDescriptor(receiver, privateMap, "get"); return _classApplyDescriptorGet(receiver, descriptor); }
function _classApplyDescriptorGet(receiver, descriptor) { if (descriptor.get) { return descriptor.get.call(receiver); } return descriptor.value; }
function _classPrivateMethodGet(receiver, privateSet, fn) { if (!privateSet.has(receiver)) { throw new TypeError("attempted to get private field on non-instance"); } return fn; }
function _classPrivateFieldSet(receiver, privateMap, value) { var descriptor = _classExtractFieldDescriptor(receiver, privateMap, "set"); _classApplyDescriptorSet(receiver, descriptor, value); return value; }
function _classExtractFieldDescriptor(receiver, privateMap, action) { if (!privateMap.has(receiver)) { throw new TypeError("attempted to " + action + " private field on non-instance"); } return privateMap.get(receiver); }
function _classApplyDescriptorSet(receiver, descriptor, value) { if (descriptor.set) { descriptor.set.call(receiver, value); } else { if (!descriptor.writable) { throw new TypeError("attempted to set read only private field"); } descriptor.value = value; } }
const fs = require$$0;
const path = require$$1;
const _$1 = require$$2$1;
const {
PYTHAGORA_UNIT_DIR
} = common$2;
const {
checkDirectoryExists
} = common$1;
const {
replaceRequirePaths,
getAstFromFilePath,
getRelatedTestImports,
getSourceCodeFromAst,
getModuleTypeFromFilePath
} = code;
const {
getRelativePath,
getTestFolderPath,
checkPathType
} = files;
const {
green,
red: red$1,
reset: reset$1
} = colors$1;
const UnitTestsCommon$1 = unitTestsCommon;
var _API = /*#__PURE__*/new WeakMap();
var _opts = /*#__PURE__*/new WeakMap();
var _saveTests = /*#__PURE__*/new WeakSet();
var _reformatDataForPythagoraAPI = /*#__PURE__*/new WeakSet();
var _createAdditionalTests = /*#__PURE__*/new WeakSet();
var _traverseDirectoryUnitExpanded = /*#__PURE__*/new WeakSet();
let UnitTestsExpand$1 = class UnitTestsExpand extends UnitTestsCommon$1 {
constructor(mainArgs, API, opts = {}) {
super(mainArgs);
_classPrivateMethodInitSpec(this, _traverseDirectoryUnitExpanded);
_classPrivateMethodInitSpec(this, _createAdditionalTests);
_classPrivateMethodInitSpec(this, _reformatDataForPythagoraAPI);
_classPrivateMethodInitSpec(this, _saveTests);
_classPrivateFieldInitSpec(this, _API, {
writable: true,
value: void 0
});
_classPrivateFieldInitSpec(this, _opts, {
writable: true,
value: void 0
});
_classPrivateFieldSet(this, _API, API);
_classPrivateFieldSet(this, _opts, {
...opts
});
}
async runProcessing() {
await this.traverseAllDirectories(fileName => !_classStaticPrivateFieldSpecGet(UnitTestsExpand, UnitTestsExpand, _filesEndingWith).some(ending => fileName.endsWith(ending)));
await _classPrivateMethodGet(this, _traverseDirectoryUnitExpanded, _traverseDirectoryUnitExpanded2).call(this, this.queriedPath, this.funcName);
return {
errors: this.errors,
skippedFiles: this.skippedFiles,
testsGenerated: this.testsGenerated
};
}
};
function _checkForTestFilePath(filePath) {
const pattern = /test\.(js|ts|tsx)$/;
return pattern.test(filePath);
}
async function _saveTests2(filePath, fileName, testData) {
const dir = filePath.substring(0, filePath.lastIndexOf("/"));
if (!(await checkDirectoryExists(dir))) {
fs.mkdirSync(dir, {
recursive: true
});
}
const testPath = path.join(dir, `/${fileName}`);
fs.writeFileSync(testPath, testData);
return testPath;
}
function _reformatDataForPythagoraAPI2(filePath, testCode, relatedCode, syntaxType) {
const importedFiles = [];
_$1.forEach(relatedCode, f => {
const testPath = path.join(path.resolve(PYTHAGORA_UNIT_DIR), filePath.replace(this.rootPath, ""));
const pathRelativeToTest = getRelativePath(f.filePath, testPath.substring(0, testPath.lastIndexOf("/")));
f.pathRelativeToTest = pathRelativeToTest;
if (!importedFiles.find(i => i.filePath === f.filePath)) {
importedFiles.push({
fileName: f.fileName.substring(f.fileName.lastIndexOf("/") + 1),
filePath: f.filePath,
pathRelativeToTest: f.pathRelativeToTest,
syntaxType: f.syntaxType
});
}
if (f.relatedFunctions.length) {
f.relatedFunctions = _$1.map(f.relatedFunctions, f => ({
...f,
fileName: f.fileName.substring(f.fileName.lastIndexOf("/") + 1)
}));
f.relatedFunctions.forEach(f => importedFiles.push({
...f,
pathRelativeToTest: getRelativePath(f.filePath, testPath.substring(0, testPath.lastIndexOf("/")))
}));
}
});
const testFilePath = getTestFolderPath(filePath, this.rootPath);
const pathRelativeToTest = getRelativePath(filePath, testFilePath);
return {
testFileName: filePath.substring(filePath.lastIndexOf("/") + 1),
testCode,
relatedCode,
importedFiles,
isES6Syntax: syntaxType === "ES6",
pathRelativeToTest,
filePath
};
}
async function _createAdditionalTests2(filePath) {
try {
const ast = await getAstFromFilePath(filePath);
const syntaxType = await getModuleTypeFromFilePath(ast);
const testPath = path.join(this.rootPath + PYTHAGORA_UNIT_DIR, filePath.replace(this.rootPath, ""));
let testCode = getSourceCodeFromAst(ast);
testCode = replaceRequirePaths(testCode, path.dirname(filePath), testPath.substring(0, testPath.lastIndexOf("/")));
const relatedTestCode = getRelatedTestImports(ast, filePath, this.functionList);
const formattedData = _classPrivateMethodGet(this, _reformatDataForPythagoraAPI, _reformatDataForPythagoraAPI2).call(this, filePath, testCode, relatedTestCode, syntaxType);
const fileIndex = this.folderStructureTree.findIndex(item => item.absolutePath === filePath);
if (_classPrivateFieldGet(this, _opts).spinner) {
_classPrivateFieldGet(this, _opts).spinner.start(this.folderStructureTree, fileIndex);
}
if (fs.existsSync(testPath) && !this.force) {
this.skippedFiles.push(testPath);
if (_classPrivateFieldGet(this, _opts).spinner) {
await _classPrivateFieldGet(this, _opts).spinner.stop();
}
this.folderStructureTree[fileIndex].line = `${green}${this.folderStructureTree[fileIndex].line}${reset$1}`;
return;
}
const {
tests,
error
} = await _classPrivateFieldGet(this, _API).expandUnitTests(formattedData, content => {
if (_classPrivateFieldGet(this, _opts).scrollableContent) {
_classPrivateFieldGet(this, _opts).scrollableContent.setContent(content);
_classPrivateFieldGet(this, _opts).scrollableContent.setScrollPerc(100);
}
if (_classPrivateFieldGet(this, _opts).screen) {
_classPrivateFieldGet(this, _opts).screen.render();
}
});
if (tests) {
const testGenerated = {
testName: formattedData.testFileName,
testCode: tests,
testPath
};
if (_classPrivateFieldGet(this, _opts).isSaveTests) {
await _classPrivateMethodGet(this, _saveTests, _saveTests2).call(this, testPath, formattedData.testFileName, tests);
}
this.testsGenerated.push(testGenerated);
if (_classPrivateFieldGet(this, _opts).spinner) {
await _classPrivateFieldGet(this, _opts).spinner.stop();
}
this.folderStructureTree[fileIndex].line = `${green}${this.folderStructureTree[fileIndex].line}${reset$1}`;
} else if (error) {
this.errors.push({
file: filePath,
error: {
stack: error.stack,
message: error.message
}
});
if (_classPrivateFieldGet(this, _opts).spinner) {
await _classPrivateFieldGet(this, _opts).spinner.stop();
}
this.folderStructureTree[fileIndex].line = `${red$1}${this.folderStructureTree[fileIndex].line}${reset$1}`;
}
} catch (e) {
if (!UnitTestsCommon$1.ignoreErrors.includes(e.code)) this.errors.push(e);
}
}
async function _traverseDirectoryUnitExpanded2(directory, prefix = "") {
if ((await checkPathType(directory)) === "file" && _classStaticPrivateMethodGet(UnitTestsExpand$1, UnitTestsExpand$1, _checkForTestFilePath).call(UnitTestsExpand$1, directory)) {
const newPrefix = `| ${prefix}| `;
await _classPrivateMethodGet(this, _createAdditionalTests, _createAdditionalTests2).call(this, directory, newPrefix);
return;
} else if ((await checkPathType(directory)) === "file" && !_classStaticPrivateMethodGet(UnitTestsExpand$1, UnitTestsExpand$1, _checkForTestFilePath).call(UnitTestsExpand$1, directory)) {
throw new Error("Invalid test file path");
}
const files = fs.readdirSync(directory);
for (const file of files) {
const absolutePath = path.join(directory, file);
const stat = fs.statSync(absolutePath);
if (stat.isDirectory()) {
if (UnitTestsCommon$1.ignoreFolders.includes(path.basename(absolutePath)) || path.basename(absolutePath).charAt(0) === ".") continue;
await _classPrivateMethodGet(this, _traverseDirectoryUnitExpanded, _traverseDirectoryUnitExpanded2).call(this, absolutePath, prefix);
} else {
if (!UnitTestsCommon$1.processExtensions.includes(path.extname(absolutePath)) || !_classStaticPrivateMethodGet(UnitTestsExpand$1, UnitTestsExpand$1, _checkForTestFilePath).call(UnitT