diff --git a/code_to_optimize_js/bubble_sort.js b/code_to_optimize/js/code_to_optimize_js/bubble_sort.js similarity index 100% rename from code_to_optimize_js/bubble_sort.js rename to code_to_optimize/js/code_to_optimize_js/bubble_sort.js diff --git a/code_to_optimize_js/calculator.js b/code_to_optimize/js/code_to_optimize_js/calculator.js similarity index 100% rename from code_to_optimize_js/calculator.js rename to code_to_optimize/js/code_to_optimize_js/calculator.js diff --git a/code_to_optimize_js/fibonacci.js b/code_to_optimize/js/code_to_optimize_js/fibonacci.js similarity index 100% rename from code_to_optimize_js/fibonacci.js rename to code_to_optimize/js/code_to_optimize_js/fibonacci.js diff --git a/code_to_optimize_js/math_helpers.js b/code_to_optimize/js/code_to_optimize_js/math_helpers.js similarity index 100% rename from code_to_optimize_js/math_helpers.js rename to code_to_optimize/js/code_to_optimize_js/math_helpers.js diff --git a/code_to_optimize_js/package-lock.json b/code_to_optimize/js/code_to_optimize_js/package-lock.json similarity index 100% rename from code_to_optimize_js/package-lock.json rename to code_to_optimize/js/code_to_optimize_js/package-lock.json diff --git a/code_to_optimize_js/package.json b/code_to_optimize/js/code_to_optimize_js/package.json similarity index 100% rename from code_to_optimize_js/package.json rename to code_to_optimize/js/code_to_optimize_js/package.json diff --git a/code_to_optimize_js/string_utils.js b/code_to_optimize/js/code_to_optimize_js/string_utils.js similarity index 100% rename from code_to_optimize_js/string_utils.js rename to code_to_optimize/js/code_to_optimize_js/string_utils.js diff --git a/code_to_optimize_js/tests/bubble_sort.test.js b/code_to_optimize/js/code_to_optimize_js/tests/bubble_sort.test.js similarity index 63% rename from code_to_optimize_js/tests/bubble_sort.test.js rename to code_to_optimize/js/code_to_optimize_js/tests/bubble_sort.test.js index 23443b1eb..c10ed0aad 100644 --- a/code_to_optimize_js/tests/bubble_sort.test.js +++ b/code_to_optimize/js/code_to_optimize_js/tests/bubble_sort.test.js @@ -30,6 +30,29 @@ describe('bubbleSort', () => { bubbleSort(original); expect(original).toEqual([3, 1, 2]); }); + + test('sorts a larger reverse sorted array for performance', () => { + const input = []; + for (let i = 500; i >= 0; i--) { + input.push(i); + } + const result = bubbleSort(input); + expect(result[0]).toBe(0); + expect(result[result.length - 1]).toBe(500); + }); + + test('sorts a larger random array for performance', () => { + const input = [ + 42, 17, 93, 8, 67, 31, 55, 22, 89, 4, + 76, 12, 39, 58, 95, 26, 71, 48, 83, 19, + 64, 3, 88, 37, 52, 11, 79, 46, 91, 28, + 63, 7, 84, 33, 57, 14, 72, 41, 96, 24, + 69, 6, 81, 36, 54, 16, 77, 44, 90, 29 + ]; + const result = bubbleSort(input); + expect(result[0]).toBe(3); + expect(result[result.length - 1]).toBe(96); + }); }); describe('bubbleSortDescending', () => { diff --git a/code_to_optimize_js/tests/e2e-behavior-comparison.test.js b/code_to_optimize/js/code_to_optimize_js/tests/e2e-behavior-comparison.test.js similarity index 100% rename from code_to_optimize_js/tests/e2e-behavior-comparison.test.js rename to code_to_optimize/js/code_to_optimize_js/tests/e2e-behavior-comparison.test.js diff --git a/code_to_optimize_js/tests/e2e-comparison-test.js b/code_to_optimize/js/code_to_optimize_js/tests/e2e-comparison-test.js similarity index 100% rename from code_to_optimize_js/tests/e2e-comparison-test.js rename to code_to_optimize/js/code_to_optimize_js/tests/e2e-comparison-test.js diff --git a/code_to_optimize_js/tests/fibonacci.test.js b/code_to_optimize/js/code_to_optimize_js/tests/fibonacci.test.js similarity index 100% rename from code_to_optimize_js/tests/fibonacci.test.js rename to code_to_optimize/js/code_to_optimize_js/tests/fibonacci.test.js diff --git a/code_to_optimize_js/tests/integration-behavior-test.js b/code_to_optimize/js/code_to_optimize_js/tests/integration-behavior-test.js similarity index 100% rename from code_to_optimize_js/tests/integration-behavior-test.js rename to code_to_optimize/js/code_to_optimize_js/tests/integration-behavior-test.js diff --git a/code_to_optimize_js/tests/looping-test/loop-runner.js b/code_to_optimize/js/code_to_optimize_js/tests/looping-test/loop-runner.js similarity index 100% rename from code_to_optimize_js/tests/looping-test/loop-runner.js rename to code_to_optimize/js/code_to_optimize_js/tests/looping-test/loop-runner.js diff --git a/code_to_optimize_js/tests/looping-test/looped-perf.test.js b/code_to_optimize/js/code_to_optimize_js/tests/looping-test/looped-perf.test.js similarity index 100% rename from code_to_optimize_js/tests/looping-test/looped-perf.test.js rename to code_to_optimize/js/code_to_optimize_js/tests/looping-test/looped-perf.test.js diff --git a/code_to_optimize_js/tests/looping-test/sample-perf.test.js b/code_to_optimize/js/code_to_optimize_js/tests/looping-test/sample-perf.test.js similarity index 100% rename from code_to_optimize_js/tests/looping-test/sample-perf.test.js rename to code_to_optimize/js/code_to_optimize_js/tests/looping-test/sample-perf.test.js diff --git a/code_to_optimize_js/tests/string_utils.test.js b/code_to_optimize/js/code_to_optimize_js/tests/string_utils.test.js similarity index 85% rename from code_to_optimize_js/tests/string_utils.test.js rename to code_to_optimize/js/code_to_optimize_js/tests/string_utils.test.js index 03753048b..94352b339 100644 --- a/code_to_optimize_js/tests/string_utils.test.js +++ b/code_to_optimize/js/code_to_optimize_js/tests/string_utils.test.js @@ -26,6 +26,20 @@ describe('reverseString', () => { test('handles spaces', () => { expect(reverseString('hello world')).toBe('dlrow olleh'); }); + + test('reverses a longer string for performance', () => { + const input = 'abcdefghijklmnopqrstuvwxyz'.repeat(20); + const result = reverseString(input); + expect(result.length).toBe(input.length); + expect(result[0]).toBe('z'); + expect(result[result.length - 1]).toBe('a'); + }); + + test('reverses a medium string', () => { + const input = 'The quick brown fox jumps over the lazy dog'; + const expected = 'god yzal eht revo spmuj xof nworb kciuq ehT'; + expect(reverseString(input)).toBe(expected); + }); }); describe('isPalindrome', () => { diff --git a/code_to_optimize_js_cjs/codeflash.yaml b/code_to_optimize/js/code_to_optimize_js_cjs/codeflash.yaml similarity index 100% rename from code_to_optimize_js_cjs/codeflash.yaml rename to code_to_optimize/js/code_to_optimize_js_cjs/codeflash.yaml diff --git a/code_to_optimize_js_cjs/fibonacci.js b/code_to_optimize/js/code_to_optimize_js_cjs/fibonacci.js similarity index 100% rename from code_to_optimize_js_cjs/fibonacci.js rename to code_to_optimize/js/code_to_optimize_js_cjs/fibonacci.js diff --git a/code_to_optimize_js_cjs/jest.config.js b/code_to_optimize/js/code_to_optimize_js_cjs/jest.config.js similarity index 100% rename from code_to_optimize_js_cjs/jest.config.js rename to code_to_optimize/js/code_to_optimize_js_cjs/jest.config.js diff --git a/code_to_optimize_js_cjs/package-lock.json b/code_to_optimize/js/code_to_optimize_js_cjs/package-lock.json similarity index 100% rename from code_to_optimize_js_cjs/package-lock.json rename to code_to_optimize/js/code_to_optimize_js_cjs/package-lock.json diff --git a/code_to_optimize_js_cjs/package.json b/code_to_optimize/js/code_to_optimize_js_cjs/package.json similarity index 100% rename from code_to_optimize_js_cjs/package.json rename to code_to_optimize/js/code_to_optimize_js_cjs/package.json diff --git a/code_to_optimize_js_cjs/tests/fibonacci.test.js b/code_to_optimize/js/code_to_optimize_js_cjs/tests/fibonacci.test.js similarity index 100% rename from code_to_optimize_js_cjs/tests/fibonacci.test.js rename to code_to_optimize/js/code_to_optimize_js_cjs/tests/fibonacci.test.js diff --git a/code_to_optimize/js/code_to_optimize_js_esm/async_utils.js b/code_to_optimize/js/code_to_optimize_js_esm/async_utils.js new file mode 100644 index 000000000..9203d7cbb --- /dev/null +++ b/code_to_optimize/js/code_to_optimize_js_esm/async_utils.js @@ -0,0 +1,64 @@ +/** + * Async utility functions - ES Module version. + * Contains intentionally inefficient implementations for optimization testing. + */ + +/** + * Simulate a delay (for testing purposes). + * @param {number} ms - Milliseconds to delay + * @returns {Promise} + */ +export function delay(ms) { + return new Promise(resolve => setTimeout(resolve, ms)); +} + +/** + * Process items sequentially when they could be parallel. + * Intentionally inefficient - processes items one at a time. + * @param {any[]} items - Items to process + * @param {function} processor - Async function to process each item + * @returns {Promise} Processed results + */ +export async function processItemsSequential(items, processor) { + const results = []; + for (let i = 0; i < items.length; i++) { + const result = await processor(items[i]); + results.push(result); + } + return results; +} + +/** + * Map over items asynchronously with a concurrency limit. + * Intentionally simple/inefficient implementation - ignores concurrency. + * @param {any[]} items - Items to process + * @param {function} mapper - Async mapper function + * @param {number} concurrency - Max concurrent operations (currently ignored) + * @returns {Promise} Mapped results + */ +export async function asyncMap(items, mapper, concurrency = 1) { + // Inefficient: ignores concurrency, processes sequentially + const results = []; + for (const item of items) { + results.push(await mapper(item)); + } + return results; +} + +/** + * Filter items asynchronously. + * Inefficient implementation that processes items one by one. + * @param {any[]} items - Items to filter + * @param {function} predicate - Async predicate function + * @returns {Promise} Filtered items + */ +export async function asyncFilter(items, predicate) { + const results = []; + for (const item of items) { + const shouldInclude = await predicate(item); + if (shouldInclude) { + results.push(item); + } + } + return results; +} diff --git a/code_to_optimize_js_esm/codeflash.yaml b/code_to_optimize/js/code_to_optimize_js_esm/codeflash.yaml similarity index 100% rename from code_to_optimize_js_esm/codeflash.yaml rename to code_to_optimize/js/code_to_optimize_js_esm/codeflash.yaml diff --git a/code_to_optimize_js_esm/fibonacci.js b/code_to_optimize/js/code_to_optimize_js_esm/fibonacci.js similarity index 100% rename from code_to_optimize_js_esm/fibonacci.js rename to code_to_optimize/js/code_to_optimize_js_esm/fibonacci.js diff --git a/code_to_optimize_js_esm/jest.config.cjs b/code_to_optimize/js/code_to_optimize_js_esm/jest.config.cjs similarity index 100% rename from code_to_optimize_js_esm/jest.config.cjs rename to code_to_optimize/js/code_to_optimize_js_esm/jest.config.cjs diff --git a/code_to_optimize_js_esm/package-lock.json b/code_to_optimize/js/code_to_optimize_js_esm/package-lock.json similarity index 82% rename from code_to_optimize_js_esm/package-lock.json rename to code_to_optimize/js/code_to_optimize_js_esm/package-lock.json index 9e648b659..49c0aee2c 100644 --- a/code_to_optimize_js_esm/package-lock.json +++ b/code_to_optimize/js/code_to_optimize_js_esm/package-lock.json @@ -8,10 +8,38 @@ "name": "code-to-optimize-js-esm", "version": "1.0.0", "devDependencies": { + "@eslint/js": "^9.39.2", + "codeflash": "file:../packages/codeflash", + "eslint": "^9.39.2", + "globals": "^17.1.0", "jest": "^29.7.0", "jest-junit": "^16.0.0" } }, + "../packages/codeflash": { + "version": "0.1.0", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "better-sqlite3": "^9.0.0" + }, + "bin": { + "codeflash": "bin/codeflash.js", + "codeflash-setup": "bin/codeflash-setup.js" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "jest": ">=27.0.0" + }, + "peerDependenciesMeta": { + "jest": { + "optional": true + } + } + }, "node_modules/@babel/code-frame": { "version": "7.28.6", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.28.6.tgz", @@ -508,6 +536,235 @@ "dev": true, "license": "MIT" }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", + "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz", + "integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.1", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/eslintrc/node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@eslint/js": { + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz", + "integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -949,6 +1206,13 @@ "@babel/types": "^7.28.2" } }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/graceful-fs": { "version": "4.1.9", "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", @@ -986,6 +1250,13 @@ "@types/istanbul-lib-report": "*" } }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/node": { "version": "25.0.10", "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.10.tgz", @@ -1020,6 +1291,46 @@ "dev": true, "license": "MIT" }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, "node_modules/ansi-escapes": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", @@ -1411,6 +1722,10 @@ "node": ">= 0.12.0" } }, + "node_modules/codeflash": { + "resolved": "../packages/codeflash", + "link": true + }, "node_modules/collect-v8-coverage": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.3.tgz", @@ -1522,6 +1837,13 @@ } } }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, "node_modules/deepmerge": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", @@ -1609,6 +1931,176 @@ "node": ">=8" } }, + "node_modules/eslint": { + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz", + "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.1", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.39.2", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", @@ -1623,6 +2115,52 @@ "node": ">=4" } }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/execa": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", @@ -1673,6 +2211,13 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", @@ -1680,6 +2225,13 @@ "dev": true, "license": "MIT" }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, "node_modules/fb-watchman": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", @@ -1690,6 +2242,19 @@ "bser": "2.1.1" } }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -1717,6 +2282,27 @@ "node": ">=8" } }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -1814,6 +2400,32 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "17.2.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.2.0.tgz", + "integrity": "sha512-tovnCz/fEq+Ripoq+p/gN1u7l6A7wwkoBT9pRCzTHzsD/LvADIzXZdjmRymh5Ztf0DYC3Rwg5cZRYjxzBmzbWg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -1861,6 +2473,43 @@ "node": ">=10.17.0" } }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-fresh/node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/import-local": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", @@ -1933,6 +2582,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -1953,6 +2612,19 @@ "node": ">=6" } }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -2713,6 +3385,13 @@ "node": ">=6" } }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", @@ -2720,6 +3399,20 @@ "dev": true, "license": "MIT" }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", @@ -2733,6 +3426,16 @@ "node": ">=6" } }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, "node_modules/kleur": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", @@ -2753,6 +3456,20 @@ "node": ">=6" } }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", @@ -2773,6 +3490,13 @@ "node": ">=8" } }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -2956,6 +3680,24 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -3011,6 +3753,19 @@ "node": ">=6" } }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/parse-json": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", @@ -3110,6 +3865,16 @@ "node": ">=8" } }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/pretty-format": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", @@ -3152,6 +3917,16 @@ "node": ">= 6" } }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/pure-rand": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", @@ -3474,6 +4249,19 @@ "node": ">=8.0" } }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/type-detect": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", @@ -3535,6 +4323,16 @@ "browserslist": ">= 4.21.0" } }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, "node_modules/uuid": { "version": "8.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", @@ -3586,6 +4384,16 @@ "node": ">= 8" } }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", diff --git a/code_to_optimize_js_esm/package.json b/code_to_optimize/js/code_to_optimize_js_esm/package.json similarity index 85% rename from code_to_optimize_js_esm/package.json rename to code_to_optimize/js/code_to_optimize_js_esm/package.json index a3b3e4aa0..d20265df0 100644 --- a/code_to_optimize_js_esm/package.json +++ b/code_to_optimize/js/code_to_optimize_js_esm/package.json @@ -10,16 +10,14 @@ "devDependencies": { "@eslint/js": "^9.39.2", "codeflash": "file:../packages/codeflash", - "jest": "^29.7.0", - "globals": "^17.1.0", "eslint": "^9.39.2", + "globals": "^17.1.0", + "jest": "^29.7.0", "jest-junit": "^16.0.0" }, "codeflash": { "moduleRoot": ".", - "formatterCmds": [ - "npx eslint --fix $file" - ], - "language": "javascript" + "testsRoot": "tests", + "disableTelemetry": true } } diff --git a/code_to_optimize/js/code_to_optimize_js_esm/tests/async_utils.test.js b/code_to_optimize/js/code_to_optimize_js_esm/tests/async_utils.test.js new file mode 100644 index 000000000..c6ad33f53 --- /dev/null +++ b/code_to_optimize/js/code_to_optimize_js_esm/tests/async_utils.test.js @@ -0,0 +1,85 @@ +/** + * Tests for async utility functions - ES Module + */ +import { delay, processItemsSequential, asyncMap, asyncFilter } from '../async_utils.js'; + +describe('processItemsSequential', () => { + test('processes all items', async () => { + const items = [1, 2, 3, 4, 5]; + const processor = async (x) => x * 2; + const results = await processItemsSequential(items, processor); + expect(results).toEqual([2, 4, 6, 8, 10]); + }); + + test('handles empty array', async () => { + const results = await processItemsSequential([], async (x) => x); + expect(results).toEqual([]); + }); + + test('handles async operations with delays', async () => { + const items = [1, 2, 3]; + const processor = async (x) => { + await delay(1); + return x + 10; + }; + const results = await processItemsSequential(items, processor); + expect(results).toEqual([11, 12, 13]); + }); + + test('preserves order', async () => { + const items = [5, 4, 3, 2, 1]; + const processor = async (x) => x.toString(); + const results = await processItemsSequential(items, processor); + expect(results).toEqual(['5', '4', '3', '2', '1']); + }); + + test('handles larger arrays', async () => { + const items = Array.from({ length: 20 }, (_, i) => i); + const processor = async (x) => x * 2; + const results = await processItemsSequential(items, processor); + expect(results.length).toBe(20); + expect(results[0]).toBe(0); + expect(results[19]).toBe(38); + }); +}); + +describe('asyncMap', () => { + test('maps all items', async () => { + const items = [1, 2, 3]; + const mapper = async (x) => x * 10; + const results = await asyncMap(items, mapper); + expect(results).toEqual([10, 20, 30]); + }); + + test('handles empty array', async () => { + const results = await asyncMap([], async (x) => x); + expect(results).toEqual([]); + }); + + test('handles objects', async () => { + const items = [{ a: 1 }, { a: 2 }]; + const mapper = async (obj) => ({ ...obj, b: obj.a * 2 }); + const results = await asyncMap(items, mapper); + expect(results).toEqual([{ a: 1, b: 2 }, { a: 2, b: 4 }]); + }); +}); + +describe('asyncFilter', () => { + test('filters items based on predicate', async () => { + const items = [1, 2, 3, 4, 5, 6]; + const predicate = async (x) => x % 2 === 0; + const results = await asyncFilter(items, predicate); + expect(results).toEqual([2, 4, 6]); + }); + + test('handles empty array', async () => { + const results = await asyncFilter([], async () => true); + expect(results).toEqual([]); + }); + + test('handles all items filtered out', async () => { + const items = [1, 2, 3]; + const results = await asyncFilter(items, async () => false); + expect(results).toEqual([]); + }); +}); diff --git a/code_to_optimize_js_esm/tests/fibonacci.test.js b/code_to_optimize/js/code_to_optimize_js_esm/tests/fibonacci.test.js similarity index 100% rename from code_to_optimize_js_esm/tests/fibonacci.test.js rename to code_to_optimize/js/code_to_optimize_js_esm/tests/fibonacci.test.js diff --git a/code_to_optimize_ts/bubble_sort.ts b/code_to_optimize/js/code_to_optimize_ts/bubble_sort.ts similarity index 100% rename from code_to_optimize_ts/bubble_sort.ts rename to code_to_optimize/js/code_to_optimize_ts/bubble_sort.ts diff --git a/code_to_optimize_ts/codeflash.yaml b/code_to_optimize/js/code_to_optimize_ts/codeflash.yaml similarity index 100% rename from code_to_optimize_ts/codeflash.yaml rename to code_to_optimize/js/code_to_optimize_ts/codeflash.yaml diff --git a/code_to_optimize/js/code_to_optimize_ts/data_processor.ts b/code_to_optimize/js/code_to_optimize_ts/data_processor.ts new file mode 100644 index 000000000..690e31104 --- /dev/null +++ b/code_to_optimize/js/code_to_optimize_ts/data_processor.ts @@ -0,0 +1,88 @@ +/** + * DataProcessor class - demonstrates class method optimization in TypeScript. + * Contains intentionally inefficient implementations for optimization testing. + */ + +/** + * A class for processing data arrays with various operations. + */ +export class DataProcessor { + private data: T[]; + + /** + * Create a DataProcessor instance. + * @param data - Initial data array + */ + constructor(data: T[] = []) { + this.data = [...data]; + } + + /** + * Find duplicates in the data array. + * Intentionally inefficient O(n²) implementation. + * @returns Array of duplicate values + */ + findDuplicates(): T[] { + const duplicates: T[] = []; + for (let i = 0; i < this.data.length; i++) { + for (let j = i + 1; j < this.data.length; j++) { + if (this.data[i] === this.data[j]) { + if (!duplicates.includes(this.data[i])) { + duplicates.push(this.data[i]); + } + } + } + } + return duplicates; + } + + /** + * Sort the data using bubble sort. + * Intentionally inefficient O(n²) implementation. + * @returns Sorted copy of the data + */ + sortData(): T[] { + const result = [...this.data]; + const n = result.length; + for (let i = 0; i < n; i++) { + for (let j = 0; j < n - 1; j++) { + if (result[j] > result[j + 1]) { + const temp = result[j]; + result[j] = result[j + 1]; + result[j + 1] = temp; + } + } + } + return result; + } + + /** + * Get unique values from the data. + * Intentionally inefficient O(n²) implementation. + * @returns Array of unique values + */ + getUnique(): T[] { + const unique: T[] = []; + for (let i = 0; i < this.data.length; i++) { + let found = false; + for (let j = 0; j < unique.length; j++) { + if (unique[j] === this.data[i]) { + found = true; + break; + } + } + if (!found) { + unique.push(this.data[i]); + } + } + return unique; + } + + /** + * Get the data array. + * @returns The data array + */ + getData(): T[] { + return [...this.data]; + } +} diff --git a/code_to_optimize_ts/fibonacci.ts b/code_to_optimize/js/code_to_optimize_ts/fibonacci.ts similarity index 100% rename from code_to_optimize_ts/fibonacci.ts rename to code_to_optimize/js/code_to_optimize_ts/fibonacci.ts diff --git a/code_to_optimize_ts/jest.config.ts b/code_to_optimize/js/code_to_optimize_ts/jest.config.ts similarity index 100% rename from code_to_optimize_ts/jest.config.ts rename to code_to_optimize/js/code_to_optimize_ts/jest.config.ts diff --git a/code_to_optimize_ts/package-lock.json b/code_to_optimize/js/code_to_optimize_ts/package-lock.json similarity index 100% rename from code_to_optimize_ts/package-lock.json rename to code_to_optimize/js/code_to_optimize_ts/package-lock.json diff --git a/code_to_optimize_ts/package.json b/code_to_optimize/js/code_to_optimize_ts/package.json similarity index 91% rename from code_to_optimize_ts/package.json rename to code_to_optimize/js/code_to_optimize_ts/package.json index c736a8f77..eb0266011 100644 --- a/code_to_optimize_ts/package.json +++ b/code_to_optimize/js/code_to_optimize_ts/package.json @@ -8,6 +8,10 @@ "test:coverage": "jest --coverage", "build": "tsc" }, + "codeflash": { + "moduleRoot": ".", + "testsRoot": "tests" + }, "keywords": [ "codeflash", "optimization", diff --git a/code_to_optimize_ts/string_utils.ts b/code_to_optimize/js/code_to_optimize_ts/string_utils.ts similarity index 83% rename from code_to_optimize_ts/string_utils.ts rename to code_to_optimize/js/code_to_optimize_ts/string_utils.ts index 180109288..e8f534e3f 100644 --- a/code_to_optimize_ts/string_utils.ts +++ b/code_to_optimize/js/code_to_optimize_ts/string_utils.ts @@ -4,14 +4,21 @@ /** * Reverse a string character by character. - * This is intentionally inefficient - uses string concatenation in a loop. + * This is intentionally inefficient O(n²) - rebuilds result string each iteration. * @param str - The string to reverse * @returns The reversed string */ export function reverseString(str: string): string { + // Intentionally inefficient O(n²) implementation for testing let result = ''; for (let i = str.length - 1; i >= 0; i--) { - result += str[i]; + // Rebuild the entire result string each iteration (very inefficient) + let temp = ''; + for (let j = 0; j < result.length; j++) { + temp += result[j]; + } + temp += str[i]; + result = temp; } return result; } diff --git a/code_to_optimize_ts/tests/bubble_sort.test.ts b/code_to_optimize/js/code_to_optimize_ts/tests/bubble_sort.test.ts similarity index 71% rename from code_to_optimize_ts/tests/bubble_sort.test.ts rename to code_to_optimize/js/code_to_optimize_ts/tests/bubble_sort.test.ts index 4750b5ee4..b0beb1403 100644 --- a/code_to_optimize_ts/tests/bubble_sort.test.ts +++ b/code_to_optimize/js/code_to_optimize_ts/tests/bubble_sort.test.ts @@ -34,6 +34,29 @@ describe('bubbleSort', () => { bubbleSort(original); expect(original).toEqual([3, 1, 2]); }); + + test('sorts a larger reverse sorted array for performance', () => { + const input: number[] = []; + for (let i = 500; i >= 0; i--) { + input.push(i); + } + const result = bubbleSort(input); + expect(result[0]).toBe(0); + expect(result[result.length - 1]).toBe(500); + }); + + test('sorts a larger random array for performance', () => { + const input = [ + 42, 17, 93, 8, 67, 31, 55, 22, 89, 4, + 76, 12, 39, 58, 95, 26, 71, 48, 83, 19, + 64, 3, 88, 37, 52, 11, 79, 46, 91, 28, + 63, 7, 84, 33, 57, 14, 72, 41, 96, 24, + 69, 6, 81, 36, 54, 16, 77, 44, 90, 29 + ]; + const result = bubbleSort(input); + expect(result[0]).toBe(3); + expect(result[result.length - 1]).toBe(96); + }); }); describe('bubbleSortDescending', () => { diff --git a/code_to_optimize/js/code_to_optimize_ts/tests/data_processor.test.ts b/code_to_optimize/js/code_to_optimize_ts/tests/data_processor.test.ts new file mode 100644 index 000000000..3344bc149 --- /dev/null +++ b/code_to_optimize/js/code_to_optimize_ts/tests/data_processor.test.ts @@ -0,0 +1,95 @@ +import { DataProcessor } from '../data_processor'; + +describe('DataProcessor', () => { + describe('findDuplicates', () => { + test('finds duplicates in array with repeated values', () => { + const processor = new DataProcessor([1, 2, 3, 2, 4, 3, 5]); + expect(processor.findDuplicates().sort()).toEqual([2, 3]); + }); + + test('returns empty array when no duplicates', () => { + const processor = new DataProcessor([1, 2, 3, 4, 5]); + expect(processor.findDuplicates()).toEqual([]); + }); + + test('handles empty array', () => { + const processor = new DataProcessor([]); + expect(processor.findDuplicates()).toEqual([]); + }); + + test('handles array with all same values', () => { + const processor = new DataProcessor([5, 5, 5, 5]); + expect(processor.findDuplicates()).toEqual([5]); + }); + + test('handles larger arrays with duplicates', () => { + const data: number[] = []; + for (let i = 0; i < 100; i++) { + data.push(i % 20); + } + const processor = new DataProcessor(data); + const duplicates = processor.findDuplicates(); + expect(duplicates.length).toBe(20); + }); + }); + + describe('sortData', () => { + test('sorts numbers in ascending order', () => { + const processor = new DataProcessor([5, 2, 8, 1, 9]); + expect(processor.sortData()).toEqual([1, 2, 5, 8, 9]); + }); + + test('handles already sorted array', () => { + const processor = new DataProcessor([1, 2, 3, 4, 5]); + expect(processor.sortData()).toEqual([1, 2, 3, 4, 5]); + }); + + test('handles reverse sorted array', () => { + const processor = new DataProcessor([5, 4, 3, 2, 1]); + expect(processor.sortData()).toEqual([1, 2, 3, 4, 5]); + }); + + test('handles array with duplicates', () => { + const processor = new DataProcessor([3, 1, 4, 1, 5, 9, 2, 6, 5]); + expect(processor.sortData()).toEqual([1, 1, 2, 3, 4, 5, 5, 6, 9]); + }); + + test('handles larger arrays', () => { + const data: number[] = []; + for (let i = 500; i >= 0; i--) { + data.push(i); + } + const processor = new DataProcessor(data); + const sorted = processor.sortData(); + expect(sorted[0]).toBe(0); + expect(sorted[sorted.length - 1]).toBe(500); + }); + }); + + describe('getUnique', () => { + test('returns unique values', () => { + const processor = new DataProcessor([1, 2, 2, 3, 3, 3, 4]); + expect(processor.getUnique()).toEqual([1, 2, 3, 4]); + }); + + test('preserves order of first occurrence', () => { + const processor = new DataProcessor([3, 1, 2, 1, 3, 2]); + expect(processor.getUnique()).toEqual([3, 1, 2]); + }); + + test('handles empty array', () => { + const processor = new DataProcessor([]); + expect(processor.getUnique()).toEqual([]); + }); + + test('handles array with all unique values', () => { + const processor = new DataProcessor([1, 2, 3, 4, 5]); + expect(processor.getUnique()).toEqual([1, 2, 3, 4, 5]); + }); + + test('handles strings', () => { + const processor = new DataProcessor(['a', 'b', 'a', 'c', 'b']); + expect(processor.getUnique()).toEqual(['a', 'b', 'c']); + }); + }); +}); diff --git a/code_to_optimize_ts/tests/fibonacci.test.ts b/code_to_optimize/js/code_to_optimize_ts/tests/fibonacci.test.ts similarity index 100% rename from code_to_optimize_ts/tests/fibonacci.test.ts rename to code_to_optimize/js/code_to_optimize_ts/tests/fibonacci.test.ts diff --git a/code_to_optimize_ts/tests/string_utils.test.ts b/code_to_optimize/js/code_to_optimize_ts/tests/string_utils.test.ts similarity index 85% rename from code_to_optimize_ts/tests/string_utils.test.ts rename to code_to_optimize/js/code_to_optimize_ts/tests/string_utils.test.ts index edf5d6d20..56888faba 100644 --- a/code_to_optimize_ts/tests/string_utils.test.ts +++ b/code_to_optimize/js/code_to_optimize_ts/tests/string_utils.test.ts @@ -20,6 +20,20 @@ describe('reverseString', () => { test('handles special characters', () => { expect(reverseString('a!b@c#')).toBe('#c@b!a'); }); + + test('reverses a longer string for performance', () => { + const input = 'abcdefghijklmnopqrstuvwxyz'.repeat(20); + const result = reverseString(input); + expect(result.length).toBe(input.length); + expect(result[0]).toBe('z'); + expect(result[result.length - 1]).toBe('a'); + }); + + test('reverses a medium string', () => { + const input = 'The quick brown fox jumps over the lazy dog'; + const expected = 'god yzal eht revo spmuj xof nworb kciuq ehT'; + expect(reverseString(input)).toBe(expected); + }); }); describe('isPalindrome', () => { diff --git a/code_to_optimize_ts/tsconfig.json b/code_to_optimize/js/code_to_optimize_ts/tsconfig.json similarity index 100% rename from code_to_optimize_ts/tsconfig.json rename to code_to_optimize/js/code_to_optimize_ts/tsconfig.json diff --git a/codeflash/version.py b/codeflash/version.py index 6225467e3..ec305ddad 100644 --- a/codeflash/version.py +++ b/codeflash/version.py @@ -1,2 +1,2 @@ # These version placeholders will be replaced by uv-dynamic-versioning during build. -__version__ = "0.20.0" +__version__ = "0.20.0.post91.dev0+28f8eb18" diff --git a/tests/code_utils/test_config_js.py b/tests/code_utils/test_config_js.py new file mode 100644 index 000000000..25648ae5d --- /dev/null +++ b/tests/code_utils/test_config_js.py @@ -0,0 +1,862 @@ +"""Tests for JavaScript/TypeScript configuration detection and parsing.""" + +from __future__ import annotations + +import json +from pathlib import Path + +import pytest + +from codeflash.code_utils.config_js import ( + PACKAGE_JSON_CACHE, + PACKAGE_JSON_DATA_CACHE, + clear_cache, + detect_formatter, + detect_language, + detect_module_root, + detect_test_runner, + find_package_json, + get_package_json_data, + parse_package_json_config, +) + + +@pytest.fixture(autouse=True) +def clear_caches() -> None: + """Clear all caches before each test.""" + clear_cache() + + +class TestGetPackageJsonData: + """Tests for get_package_json_data function.""" + + def test_loads_valid_package_json(self, tmp_path: Path) -> None: + """Should load and return valid package.json data.""" + package_json = tmp_path / "package.json" + data = {"name": "test-project", "version": "1.0.0"} + package_json.write_text(json.dumps(data)) + + result = get_package_json_data(package_json) + + assert result == data + + def test_caches_loaded_data(self, tmp_path: Path) -> None: + """Should cache package.json data after first load.""" + package_json = tmp_path / "package.json" + data = {"name": "test-project"} + package_json.write_text(json.dumps(data)) + + # First call + result1 = get_package_json_data(package_json) + # Modify file + package_json.write_text(json.dumps({"name": "modified"})) + # Second call should return cached data + result2 = get_package_json_data(package_json) + + assert result1 == result2 == data + + def test_returns_none_for_invalid_json(self, tmp_path: Path) -> None: + """Should return None for invalid JSON.""" + package_json = tmp_path / "package.json" + package_json.write_text("{ invalid json }") + + result = get_package_json_data(package_json) + + assert result is None + + def test_returns_none_for_nonexistent_file(self, tmp_path: Path) -> None: + """Should return None for non-existent file.""" + package_json = tmp_path / "package.json" + + result = get_package_json_data(package_json) + + assert result is None + + def test_returns_none_for_unreadable_file(self, tmp_path: Path) -> None: + """Should return None if file cannot be read.""" + package_json = tmp_path / "package.json" + package_json.write_text("{}") + package_json.chmod(0o000) + + try: + result = get_package_json_data(package_json) + assert result is None + finally: + package_json.chmod(0o644) + + +class TestDetectLanguage: + """Tests for detect_language function.""" + + def test_detects_typescript_with_tsconfig(self, tmp_path: Path) -> None: + """Should detect TypeScript when tsconfig.json exists.""" + (tmp_path / "tsconfig.json").write_text("{}") + + result = detect_language(tmp_path) + + assert result == "typescript" + + def test_detects_javascript_without_tsconfig(self, tmp_path: Path) -> None: + """Should detect JavaScript when no tsconfig.json exists.""" + result = detect_language(tmp_path) + + assert result == "javascript" + + def test_detects_typescript_with_complex_tsconfig(self, tmp_path: Path) -> None: + """Should detect TypeScript even with complex tsconfig.""" + tsconfig = { + "compilerOptions": {"target": "ES2020", "module": "commonjs"}, + "include": ["src/**/*"], + } + (tmp_path / "tsconfig.json").write_text(json.dumps(tsconfig)) + + result = detect_language(tmp_path) + + assert result == "typescript" + + +class TestDetectModuleRoot: + """Tests for detect_module_root function.""" + + def test_detects_from_exports_string(self, tmp_path: Path) -> None: + """Should detect module root from exports string field.""" + (tmp_path / "lib").mkdir() + package_data = {"exports": "./lib/index.js"} + + result = detect_module_root(tmp_path, package_data) + + assert result == "lib" + + def test_detects_from_exports_object_dot(self, tmp_path: Path) -> None: + """Should detect module root from exports object with '.' key.""" + (tmp_path / "dist").mkdir() + package_data = {"exports": {".": "./dist/index.js"}} + + result = detect_module_root(tmp_path, package_data) + + assert result == "dist" + + def test_detects_from_exports_object_nested(self, tmp_path: Path) -> None: + """Should detect module root from nested exports object.""" + (tmp_path / "src").mkdir() + package_data = {"exports": {".": {"import": "./src/index.mjs", "require": "./src/index.cjs"}}} + + result = detect_module_root(tmp_path, package_data) + + assert result == "src" + + def test_detects_from_exports_import_key(self, tmp_path: Path) -> None: + """Should detect from exports with direct import key.""" + (tmp_path / "esm").mkdir() + package_data = {"exports": {"import": "./esm/index.js"}} + + result = detect_module_root(tmp_path, package_data) + + assert result == "esm" + + def test_detects_from_module_field(self, tmp_path: Path) -> None: + """Should detect module root from module field (ESM entry).""" + (tmp_path / "es").mkdir() + package_data = {"module": "./es/index.js"} + + result = detect_module_root(tmp_path, package_data) + + assert result == "es" + + def test_detects_from_main_field(self, tmp_path: Path) -> None: + """Should detect module root from main field (CJS entry).""" + (tmp_path / "lib").mkdir() + package_data = {"main": "./lib/index.js"} + + result = detect_module_root(tmp_path, package_data) + + assert result == "lib" + + def test_prefers_exports_over_module(self, tmp_path: Path) -> None: + """Should prefer exports field over module field.""" + (tmp_path / "exports-dir").mkdir() + (tmp_path / "module-dir").mkdir() + package_data = {"exports": "./exports-dir/index.js", "module": "./module-dir/index.js"} + + result = detect_module_root(tmp_path, package_data) + + assert result == "exports-dir" + + def test_prefers_module_over_main(self, tmp_path: Path) -> None: + """Should prefer module field over main field.""" + (tmp_path / "esm").mkdir() + (tmp_path / "cjs").mkdir() + package_data = {"module": "./esm/index.js", "main": "./cjs/index.js"} + + result = detect_module_root(tmp_path, package_data) + + assert result == "esm" + + def test_detects_src_directory_convention(self, tmp_path: Path) -> None: + """Should detect src/ directory when no package.json fields point elsewhere.""" + (tmp_path / "src").mkdir() + package_data = {} + + result = detect_module_root(tmp_path, package_data) + + assert result == "src" + + def test_falls_back_to_current_directory(self, tmp_path: Path) -> None: + """Should fall back to '.' when nothing else matches.""" + package_data = {} + + result = detect_module_root(tmp_path, package_data) + + assert result == "." + + def test_ignores_nonexistent_directory_from_exports(self, tmp_path: Path) -> None: + """Should ignore exports pointing to non-existent directory.""" + (tmp_path / "src").mkdir() + package_data = {"exports": "./nonexistent/index.js"} + + result = detect_module_root(tmp_path, package_data) + + assert result == "src" + + def test_ignores_root_level_main(self, tmp_path: Path) -> None: + """Should ignore main that points to root level file.""" + (tmp_path / "src").mkdir() + package_data = {"main": "./index.js"} + + result = detect_module_root(tmp_path, package_data) + + assert result == "src" + + def test_handles_deeply_nested_exports(self, tmp_path: Path) -> None: + """Should handle deeply nested export paths.""" + (tmp_path / "packages" / "core" / "dist").mkdir(parents=True) + package_data = {"exports": {".": {"import": "./packages/core/dist/index.mjs"}}} + + result = detect_module_root(tmp_path, package_data) + + assert result == "packages/core/dist" + + def test_handles_empty_exports(self, tmp_path: Path) -> None: + """Should handle empty exports gracefully.""" + (tmp_path / "src").mkdir() + package_data = {"exports": {}} + + result = detect_module_root(tmp_path, package_data) + + assert result == "src" + + def test_handles_null_exports(self, tmp_path: Path) -> None: + """Should handle null/None exports gracefully.""" + package_data = {"exports": None} + + result = detect_module_root(tmp_path, package_data) + + assert result == "." + + +class TestDetectTestRunner: + """Tests for detect_test_runner function.""" + + def test_detects_vitest_from_dev_dependencies(self, tmp_path: Path) -> None: + """Should detect vitest from devDependencies.""" + package_data = {"devDependencies": {"vitest": "^1.0.0"}} + + result = detect_test_runner(tmp_path, package_data) + + assert result == "vitest" + + def test_detects_jest_from_dev_dependencies(self, tmp_path: Path) -> None: + """Should detect jest from devDependencies.""" + package_data = {"devDependencies": {"jest": "^29.0.0"}} + + result = detect_test_runner(tmp_path, package_data) + + assert result == "jest" + + def test_detects_mocha_from_dev_dependencies(self, tmp_path: Path) -> None: + """Should detect mocha from devDependencies.""" + package_data = {"devDependencies": {"mocha": "^10.0.0"}} + + result = detect_test_runner(tmp_path, package_data) + + assert result == "mocha" + + def test_detects_from_dependencies(self, tmp_path: Path) -> None: + """Should also check dependencies (not just devDependencies).""" + package_data = {"dependencies": {"jest": "^29.0.0"}} + + result = detect_test_runner(tmp_path, package_data) + + assert result == "jest" + + def test_prefers_vitest_over_jest(self, tmp_path: Path) -> None: + """Should prefer vitest when both are present.""" + package_data = {"devDependencies": {"vitest": "^1.0.0", "jest": "^29.0.0"}} + + result = detect_test_runner(tmp_path, package_data) + + assert result == "vitest" + + def test_prefers_jest_over_mocha(self, tmp_path: Path) -> None: + """Should prefer jest over mocha.""" + package_data = {"devDependencies": {"jest": "^29.0.0", "mocha": "^10.0.0"}} + + result = detect_test_runner(tmp_path, package_data) + + assert result == "jest" + + def test_detects_vitest_from_test_script(self, tmp_path: Path) -> None: + """Should detect vitest from scripts.test.""" + package_data = {"scripts": {"test": "vitest run"}} + + result = detect_test_runner(tmp_path, package_data) + + assert result == "vitest" + + def test_detects_jest_from_test_script(self, tmp_path: Path) -> None: + """Should detect jest from scripts.test.""" + package_data = {"scripts": {"test": "jest --coverage"}} + + result = detect_test_runner(tmp_path, package_data) + + assert result == "jest" + + def test_detects_mocha_from_test_script(self, tmp_path: Path) -> None: + """Should detect mocha from scripts.test.""" + package_data = {"scripts": {"test": "mocha tests/**/*.js"}} + + result = detect_test_runner(tmp_path, package_data) + + assert result == "mocha" + + def test_detects_from_npx_command(self, tmp_path: Path) -> None: + """Should detect runner from npx command in test script.""" + package_data = {"scripts": {"test": "npx jest"}} + + result = detect_test_runner(tmp_path, package_data) + + assert result == "jest" + + def test_detects_case_insensitive(self, tmp_path: Path) -> None: + """Should detect runner case-insensitively from scripts.""" + package_data = {"scripts": {"test": "JEST --ci"}} + + result = detect_test_runner(tmp_path, package_data) + + assert result == "jest" + + def test_prefers_deps_over_scripts(self, tmp_path: Path) -> None: + """Should prefer devDependencies detection over scripts.""" + package_data = {"devDependencies": {"vitest": "^1.0.0"}, "scripts": {"test": "jest"}} + + result = detect_test_runner(tmp_path, package_data) + + assert result == "vitest" + + def test_defaults_to_jest(self, tmp_path: Path) -> None: + """Should default to jest when nothing is detected.""" + package_data = {} + + result = detect_test_runner(tmp_path, package_data) + + assert result == "jest" + + def test_handles_complex_test_script(self, tmp_path: Path) -> None: + """Should detect from complex test scripts.""" + package_data = {"scripts": {"test": "NODE_OPTIONS='--experimental-vm-modules' jest --coverage"}} + + result = detect_test_runner(tmp_path, package_data) + + assert result == "jest" + + def test_handles_missing_scripts(self, tmp_path: Path) -> None: + """Should handle missing scripts gracefully.""" + package_data = {"name": "test"} + + result = detect_test_runner(tmp_path, package_data) + + assert result == "jest" + + def test_handles_non_string_test_script(self, tmp_path: Path) -> None: + """Should handle non-string test script gracefully.""" + package_data = {"scripts": {"test": 123}} + + result = detect_test_runner(tmp_path, package_data) + + assert result == "jest" + + +class TestDetectFormatter: + """Tests for detect_formatter function.""" + + def test_detects_prettier_from_dev_dependencies(self, tmp_path: Path) -> None: + """Should detect prettier from devDependencies.""" + package_data = {"devDependencies": {"prettier": "^3.0.0"}} + + result = detect_formatter(tmp_path, package_data) + + assert result == ["npx prettier --write $file"] + + def test_detects_eslint_from_dev_dependencies(self, tmp_path: Path) -> None: + """Should detect eslint from devDependencies.""" + package_data = {"devDependencies": {"eslint": "^8.0.0"}} + + result = detect_formatter(tmp_path, package_data) + + assert result == ["npx eslint --fix $file"] + + def test_detects_from_dependencies(self, tmp_path: Path) -> None: + """Should also check dependencies.""" + package_data = {"dependencies": {"prettier": "^3.0.0"}} + + result = detect_formatter(tmp_path, package_data) + + assert result == ["npx prettier --write $file"] + + def test_prefers_prettier_over_eslint(self, tmp_path: Path) -> None: + """Should prefer prettier when both are present.""" + package_data = {"devDependencies": {"prettier": "^3.0.0", "eslint": "^8.0.0"}} + + result = detect_formatter(tmp_path, package_data) + + assert result == ["npx prettier --write $file"] + + def test_returns_none_when_no_formatter(self, tmp_path: Path) -> None: + """Should return None when no formatter is detected.""" + package_data = {"devDependencies": {"typescript": "^5.0.0"}} + + result = detect_formatter(tmp_path, package_data) + + assert result is None + + def test_returns_none_for_empty_deps(self, tmp_path: Path) -> None: + """Should return None for empty dependencies.""" + package_data = {} + + result = detect_formatter(tmp_path, package_data) + + assert result is None + + def test_detects_eslint_related_packages(self, tmp_path: Path) -> None: + """Should detect eslint even with scoped packages.""" + package_data = {"devDependencies": {"eslint": "^8.0.0", "@eslint/js": "^8.0.0"}} + + result = detect_formatter(tmp_path, package_data) + + assert result == ["npx eslint --fix $file"] + + +class TestFindPackageJson: + """Tests for find_package_json function.""" + + def test_finds_explicit_package_json(self, tmp_path: Path) -> None: + """Should find explicitly provided package.json path.""" + package_json = tmp_path / "package.json" + package_json.write_text("{}") + + result = find_package_json(package_json) + + assert result == package_json + + def test_returns_none_for_wrong_filename(self, tmp_path: Path) -> None: + """Should return None if explicit path is not package.json.""" + other_file = tmp_path / "other.json" + other_file.write_text("{}") + + result = find_package_json(other_file) + + assert result is None + + def test_returns_none_for_nonexistent_explicit(self, tmp_path: Path) -> None: + """Should return None if explicit package.json doesn't exist.""" + package_json = tmp_path / "package.json" + + result = find_package_json(package_json) + + assert result is None + + +class TestParsePackageJsonConfig: + """Tests for parse_package_json_config function.""" + + def test_parses_minimal_package_json(self, tmp_path: Path) -> None: + """Should parse package.json without codeflash section.""" + package_json = tmp_path / "package.json" + package_json.write_text(json.dumps({"name": "test", "devDependencies": {"jest": "^29.0.0"}})) + + result = parse_package_json_config(package_json) + + assert result is not None + config, path = result + assert config["language"] == "javascript" + assert config["test_runner"] == "jest" + assert config["pytest_cmd"] == "jest" + assert path == package_json + + def test_parses_typescript_project(self, tmp_path: Path) -> None: + """Should detect TypeScript project.""" + package_json = tmp_path / "package.json" + package_json.write_text(json.dumps({"name": "test"})) + (tmp_path / "tsconfig.json").write_text("{}") + + result = parse_package_json_config(package_json) + + assert result is not None + config, _ = result + assert config["language"] == "typescript" + + def test_auto_detects_module_root(self, tmp_path: Path) -> None: + """Should auto-detect module root from package.json.""" + (tmp_path / "src").mkdir() + package_json = tmp_path / "package.json" + package_json.write_text(json.dumps({"name": "test", "main": "./src/index.js"})) + + result = parse_package_json_config(package_json) + + assert result is not None + config, _ = result + assert config["module_root"] == str((tmp_path / "src").resolve()) + + def test_respects_module_root_override(self, tmp_path: Path) -> None: + """Should respect moduleRoot override in codeflash config.""" + (tmp_path / "lib").mkdir() + (tmp_path / "src").mkdir() + package_json = tmp_path / "package.json" + package_json.write_text( + json.dumps({"name": "test", "main": "./src/index.js", "codeflash": {"moduleRoot": "lib"}}) + ) + + result = parse_package_json_config(package_json) + + assert result is not None + config, _ = result + assert config["module_root"] == str((tmp_path / "lib").resolve()) + + def test_auto_detects_formatter(self, tmp_path: Path) -> None: + """Should auto-detect formatter from devDependencies.""" + package_json = tmp_path / "package.json" + package_json.write_text(json.dumps({"name": "test", "devDependencies": {"prettier": "^3.0.0"}})) + + result = parse_package_json_config(package_json) + + assert result is not None + config, _ = result + assert config["formatter_cmds"] == ["npx prettier --write $file"] + + def test_respects_formatter_override(self, tmp_path: Path) -> None: + """Should respect formatterCmds override.""" + package_json = tmp_path / "package.json" + package_json.write_text( + json.dumps( + { + "name": "test", + "devDependencies": {"prettier": "^3.0.0"}, + "codeflash": {"formatterCmds": ["custom-formatter $file"]}, + } + ) + ) + + result = parse_package_json_config(package_json) + + assert result is not None + config, _ = result + assert config["formatter_cmds"] == ["custom-formatter $file"] + + def test_parses_ignore_paths(self, tmp_path: Path) -> None: + """Should parse ignorePaths from codeflash config.""" + package_json = tmp_path / "package.json" + package_json.write_text(json.dumps({"name": "test", "codeflash": {"ignorePaths": ["dist", "node_modules"]}})) + + result = parse_package_json_config(package_json) + + assert result is not None + config, _ = result + assert str((tmp_path / "dist").resolve()) in config["ignore_paths"] + assert str((tmp_path / "node_modules").resolve()) in config["ignore_paths"] + + def test_parses_benchmarks_root(self, tmp_path: Path) -> None: + """Should parse benchmarksRoot from codeflash config.""" + package_json = tmp_path / "package.json" + package_json.write_text(json.dumps({"name": "test", "codeflash": {"benchmarksRoot": "__benchmarks__"}})) + + result = parse_package_json_config(package_json) + + assert result is not None + config, _ = result + assert config["benchmarks_root"] == str((tmp_path / "__benchmarks__").resolve()) + + def test_parses_disable_telemetry(self, tmp_path: Path) -> None: + """Should parse disableTelemetry from codeflash config.""" + package_json = tmp_path / "package.json" + package_json.write_text(json.dumps({"name": "test", "codeflash": {"disableTelemetry": True}})) + + result = parse_package_json_config(package_json) + + assert result is not None + config, _ = result + assert config["disable_telemetry"] is True + + def test_defaults_disable_telemetry_to_false(self, tmp_path: Path) -> None: + """Should default disableTelemetry to False.""" + package_json = tmp_path / "package.json" + package_json.write_text(json.dumps({"name": "test"})) + + result = parse_package_json_config(package_json) + + assert result is not None + config, _ = result + assert config["disable_telemetry"] is False + + def test_sets_backwards_compat_defaults(self, tmp_path: Path) -> None: + """Should set backwards compatibility defaults.""" + package_json = tmp_path / "package.json" + package_json.write_text(json.dumps({"name": "test"})) + + result = parse_package_json_config(package_json) + + assert result is not None + config, _ = result + assert config["git_remote"] == "origin" + assert config["disable_imports_sorting"] is False + assert config["override_fixtures"] is False + + def test_returns_none_for_invalid_json(self, tmp_path: Path) -> None: + """Should return None for invalid JSON.""" + package_json = tmp_path / "package.json" + package_json.write_text("invalid json") + + result = parse_package_json_config(package_json) + + assert result is None + + def test_handles_non_dict_codeflash_config(self, tmp_path: Path) -> None: + """Should handle non-dict codeflash section.""" + package_json = tmp_path / "package.json" + package_json.write_text(json.dumps({"name": "test", "codeflash": "invalid"})) + + result = parse_package_json_config(package_json) + + assert result is not None + config, _ = result + # Should use auto-detected/default values + assert "language" in config + + def test_empty_formatter_when_none_detected(self, tmp_path: Path) -> None: + """Should have empty formatter_cmds when no formatter detected.""" + package_json = tmp_path / "package.json" + package_json.write_text(json.dumps({"name": "test", "devDependencies": {"typescript": "^5.0.0"}})) + + result = parse_package_json_config(package_json) + + assert result is not None + config, _ = result + assert config["formatter_cmds"] == [] + + def test_parses_git_remote_from_config(self, tmp_path: Path) -> None: + """Should parse gitRemote from codeflash config.""" + package_json = tmp_path / "package.json" + package_json.write_text(json.dumps({"name": "test", "codeflash": {"gitRemote": "upstream"}})) + + result = parse_package_json_config(package_json) + + assert result is not None + config, _ = result + assert config["git_remote"] == "upstream" + + def test_defaults_git_remote_to_origin(self, tmp_path: Path) -> None: + """Should default gitRemote to 'origin' when not specified.""" + package_json = tmp_path / "package.json" + package_json.write_text(json.dumps({"name": "test"})) + + result = parse_package_json_config(package_json) + + assert result is not None + config, _ = result + assert config["git_remote"] == "origin" + + def test_handles_empty_git_remote(self, tmp_path: Path) -> None: + """Should handle empty gitRemote in config.""" + package_json = tmp_path / "package.json" + package_json.write_text(json.dumps({"name": "test", "codeflash": {"gitRemote": ""}})) + + result = parse_package_json_config(package_json) + + assert result is not None + config, _ = result + # Empty string should be treated as the value (not defaulted to origin) + assert config["git_remote"] == "" + + +class TestClearCache: + """Tests for clear_cache function.""" + + def test_clears_both_caches(self, tmp_path: Path) -> None: + """Should clear both path and data caches.""" + # Populate caches + package_json = tmp_path / "package.json" + package_json.write_text(json.dumps({"name": "test"})) + get_package_json_data(package_json) + + assert len(PACKAGE_JSON_DATA_CACHE) > 0 + + clear_cache() + + assert len(PACKAGE_JSON_CACHE) == 0 + assert len(PACKAGE_JSON_DATA_CACHE) == 0 + + +class TestRealWorldPackageJsonExamples: + """Tests with real-world-like package.json configurations.""" + + def test_nextjs_project(self, tmp_path: Path) -> None: + """Should handle Next.js project configuration.""" + (tmp_path / "src").mkdir() + (tmp_path / "tsconfig.json").write_text("{}") + package_json = tmp_path / "package.json" + package_json.write_text( + json.dumps( + { + "name": "my-nextjs-app", + "scripts": {"test": "jest"}, + "devDependencies": {"jest": "^29.0.0", "prettier": "^3.0.0", "typescript": "^5.0.0"}, + } + ) + ) + + result = parse_package_json_config(package_json) + + assert result is not None + config, _ = result + assert config["language"] == "typescript" + assert config["module_root"] == str((tmp_path / "src").resolve()) + assert config["test_runner"] == "jest" + assert config["formatter_cmds"] == ["npx prettier --write $file"] + + def test_vite_react_project(self, tmp_path: Path) -> None: + """Should handle Vite + React project configuration.""" + (tmp_path / "src").mkdir() + (tmp_path / "tsconfig.json").write_text("{}") + package_json = tmp_path / "package.json" + package_json.write_text( + json.dumps( + { + "name": "vite-react-app", + "type": "module", + "scripts": {"test": "vitest"}, + "devDependencies": {"vitest": "^1.0.0", "eslint": "^8.0.0"}, + } + ) + ) + + result = parse_package_json_config(package_json) + + assert result is not None + config, _ = result + assert config["language"] == "typescript" + assert config["test_runner"] == "vitest" + assert config["formatter_cmds"] == ["npx eslint --fix $file"] + + def test_library_with_exports(self, tmp_path: Path) -> None: + """Should handle library with modern exports field.""" + (tmp_path / "dist").mkdir() + package_json = tmp_path / "package.json" + package_json.write_text( + json.dumps( + { + "name": "my-library", + "exports": {".": {"import": "./dist/index.mjs", "require": "./dist/index.cjs"}}, + "devDependencies": {"vitest": "^1.0.0", "prettier": "^3.0.0"}, + } + ) + ) + + result = parse_package_json_config(package_json) + + assert result is not None + config, _ = result + assert config["module_root"] == str((tmp_path / "dist").resolve()) + + def test_monorepo_package(self, tmp_path: Path) -> None: + """Should handle monorepo package configuration.""" + (tmp_path / "packages" / "core" / "src").mkdir(parents=True) + package_json = tmp_path / "package.json" + package_json.write_text( + json.dumps( + { + "name": "@myorg/core", + "main": "./packages/core/src/index.js", + "devDependencies": {"jest": "^29.0.0"}, + } + ) + ) + + result = parse_package_json_config(package_json) + + assert result is not None + config, _ = result + assert config["module_root"] == str((tmp_path / "packages/core/src").resolve()) + + def test_node_cli_project(self, tmp_path: Path) -> None: + """Should handle Node.js CLI project.""" + (tmp_path / "bin").mkdir() + (tmp_path / "lib").mkdir() + package_json = tmp_path / "package.json" + package_json.write_text( + json.dumps( + { + "name": "my-cli", + "bin": {"my-cli": "./bin/cli.js"}, + "main": "./lib/index.js", + "devDependencies": {"mocha": "^10.0.0"}, + } + ) + ) + + result = parse_package_json_config(package_json) + + assert result is not None + config, _ = result + assert config["module_root"] == str((tmp_path / "lib").resolve()) + assert config["test_runner"] == "mocha" + + def test_minimal_project(self, tmp_path: Path) -> None: + """Should handle minimal package.json.""" + package_json = tmp_path / "package.json" + package_json.write_text(json.dumps({"name": "minimal"})) + + result = parse_package_json_config(package_json) + + assert result is not None + config, _ = result + assert config["language"] == "javascript" + assert config["module_root"] == str(tmp_path.resolve()) + assert config["test_runner"] == "jest" + assert config["formatter_cmds"] == [] + + def test_existing_codeflash_config_with_overrides(self, tmp_path: Path) -> None: + """Should handle existing codeflash config with custom overrides.""" + (tmp_path / "custom-src").mkdir() + package_json = tmp_path / "package.json" + package_json.write_text( + json.dumps( + { + "name": "configured-project", + "devDependencies": {"jest": "^29.0.0", "prettier": "^3.0.0"}, + "codeflash": { + "moduleRoot": "custom-src", + "formatterCmds": ["npx prettier --write --single-quote $file"], + "ignorePaths": ["dist", "coverage"], + "disableTelemetry": True, + }, + } + ) + ) + + result = parse_package_json_config(package_json) + + assert result is not None + config, _ = result + assert config["module_root"] == str((tmp_path / "custom-src").resolve()) + assert config["formatter_cmds"] == ["npx prettier --write --single-quote $file"] + assert len(config["ignore_paths"]) == 2 + assert config["disable_telemetry"] is True diff --git a/tests/code_utils/test_js_workflow_helpers.py b/tests/code_utils/test_js_workflow_helpers.py new file mode 100644 index 000000000..06a2894c7 --- /dev/null +++ b/tests/code_utils/test_js_workflow_helpers.py @@ -0,0 +1,238 @@ +"""Tests for JavaScript/TypeScript GitHub Actions workflow helpers.""" + +from __future__ import annotations + +import json +from pathlib import Path + +import pytest + +from codeflash.cli_cmds.init_javascript import ( + JsPackageManager, + get_js_codeflash_install_step, + get_js_codeflash_run_command, + get_js_dependency_installation_commands, + get_js_runtime_setup_steps, + is_codeflash_dependency, +) + + +class TestIsCodeflashDependency: + """Tests for is_codeflash_dependency function.""" + + def test_returns_true_when_in_dev_dependencies(self, tmp_path: Path) -> None: + """Should return True when codeflash is in devDependencies.""" + package_json = tmp_path / "package.json" + package_json.write_text(json.dumps({"devDependencies": {"codeflash": "^1.0.0"}})) + + assert is_codeflash_dependency(tmp_path) is True + + def test_returns_true_when_in_dependencies(self, tmp_path: Path) -> None: + """Should return True when codeflash is in dependencies.""" + package_json = tmp_path / "package.json" + package_json.write_text(json.dumps({"dependencies": {"codeflash": "^1.0.0"}})) + + assert is_codeflash_dependency(tmp_path) is True + + def test_returns_false_when_not_present(self, tmp_path: Path) -> None: + """Should return False when codeflash is not in any dependencies.""" + package_json = tmp_path / "package.json" + package_json.write_text(json.dumps({"devDependencies": {"jest": "^29.0.0"}})) + + assert is_codeflash_dependency(tmp_path) is False + + def test_returns_false_when_no_package_json(self, tmp_path: Path) -> None: + """Should return False when package.json doesn't exist.""" + assert is_codeflash_dependency(tmp_path) is False + + def test_returns_false_for_invalid_json(self, tmp_path: Path) -> None: + """Should return False for invalid package.json.""" + package_json = tmp_path / "package.json" + package_json.write_text("invalid json") + + assert is_codeflash_dependency(tmp_path) is False + + def test_handles_empty_dependencies(self, tmp_path: Path) -> None: + """Should handle empty dependencies objects.""" + package_json = tmp_path / "package.json" + package_json.write_text(json.dumps({"name": "test", "dependencies": {}, "devDependencies": {}})) + + assert is_codeflash_dependency(tmp_path) is False + + +class TestGetJsRuntimeSetupSteps: + """Tests for get_js_runtime_setup_steps function.""" + + def test_npm_setup(self) -> None: + """Should generate correct setup steps for npm.""" + result = get_js_runtime_setup_steps(JsPackageManager.NPM) + + assert "Setup Node.js" in result + assert "actions/setup-node@v4" in result + assert "node-version: '22'" in result + assert "cache: 'npm'" in result + + def test_yarn_setup(self) -> None: + """Should generate correct setup steps for yarn.""" + result = get_js_runtime_setup_steps(JsPackageManager.YARN) + + assert "Setup Node.js" in result + assert "actions/setup-node@v4" in result + assert "cache: 'yarn'" in result + + def test_pnpm_setup(self) -> None: + """Should generate correct setup steps for pnpm.""" + result = get_js_runtime_setup_steps(JsPackageManager.PNPM) + + assert "Setup pnpm" in result + assert "pnpm/action-setup@v4" in result + assert "Setup Node.js" in result + assert "cache: 'pnpm'" in result + + def test_bun_setup(self) -> None: + """Should generate correct setup steps for bun.""" + result = get_js_runtime_setup_steps(JsPackageManager.BUN) + + assert "Setup Bun" in result + assert "oven-sh/setup-bun@v2" in result + assert "bun-version: latest" in result + + def test_unknown_defaults_to_npm(self) -> None: + """Should default to npm setup for unknown package manager.""" + result = get_js_runtime_setup_steps(JsPackageManager.UNKNOWN) + + assert "cache: 'npm'" in result + + +class TestGetJsDependencyInstallationCommands: + """Tests for get_js_dependency_installation_commands function.""" + + def test_npm_install(self) -> None: + """Should return npm ci for npm.""" + assert get_js_dependency_installation_commands(JsPackageManager.NPM) == "npm ci" + + def test_yarn_install(self) -> None: + """Should return yarn install for yarn.""" + assert get_js_dependency_installation_commands(JsPackageManager.YARN) == "yarn install" + + def test_pnpm_install(self) -> None: + """Should return pnpm install for pnpm.""" + assert get_js_dependency_installation_commands(JsPackageManager.PNPM) == "pnpm install" + + def test_bun_install(self) -> None: + """Should return bun install for bun.""" + assert get_js_dependency_installation_commands(JsPackageManager.BUN) == "bun install" + + +class TestGetJsCodeflashInstallStep: + """Tests for get_js_codeflash_install_step function.""" + + def test_returns_empty_when_is_dependency(self) -> None: + """Should return empty string when codeflash is a dependency.""" + result = get_js_codeflash_install_step(JsPackageManager.NPM, is_dependency=True) + + assert result == "" + + def test_npm_global_install(self) -> None: + """Should generate npm global install when not a dependency.""" + result = get_js_codeflash_install_step(JsPackageManager.NPM, is_dependency=False) + + assert "Install Codeflash" in result + assert "npm install -g codeflash" in result + + def test_yarn_global_install(self) -> None: + """Should generate yarn global install when not a dependency.""" + result = get_js_codeflash_install_step(JsPackageManager.YARN, is_dependency=False) + + assert "yarn global add codeflash" in result + + def test_pnpm_global_install(self) -> None: + """Should generate pnpm global install when not a dependency.""" + result = get_js_codeflash_install_step(JsPackageManager.PNPM, is_dependency=False) + + assert "pnpm add -g codeflash" in result + + def test_bun_global_install(self) -> None: + """Should generate bun global install when not a dependency.""" + result = get_js_codeflash_install_step(JsPackageManager.BUN, is_dependency=False) + + assert "bun add -g codeflash" in result + + +class TestGetJsCodeflashRunCommand: + """Tests for get_js_codeflash_run_command function.""" + + def test_npm_with_dependency(self) -> None: + """Should use npx when codeflash is a dependency.""" + result = get_js_codeflash_run_command(JsPackageManager.NPM, is_dependency=True) + + assert result == "npx codeflash" + + def test_npm_without_dependency(self) -> None: + """Should use direct codeflash when globally installed.""" + result = get_js_codeflash_run_command(JsPackageManager.NPM, is_dependency=False) + + assert result == "codeflash" + + def test_yarn_with_dependency(self) -> None: + """Should use yarn codeflash when it's a dependency.""" + result = get_js_codeflash_run_command(JsPackageManager.YARN, is_dependency=True) + + assert result == "yarn codeflash" + + def test_pnpm_with_dependency(self) -> None: + """Should use pnpm exec when it's a dependency.""" + result = get_js_codeflash_run_command(JsPackageManager.PNPM, is_dependency=True) + + assert result == "pnpm exec codeflash" + + def test_bun_with_dependency(self) -> None: + """Should use bun run when it's a dependency.""" + result = get_js_codeflash_run_command(JsPackageManager.BUN, is_dependency=True) + + assert result == "bun run codeflash" + + def test_all_global_installs_use_direct_command(self) -> None: + """All package managers should use direct 'codeflash' when globally installed.""" + for pm in [JsPackageManager.NPM, JsPackageManager.YARN, JsPackageManager.PNPM, JsPackageManager.BUN]: + result = get_js_codeflash_run_command(pm, is_dependency=False) + assert result == "codeflash", f"Failed for {pm}" + + +class TestWorkflowTemplateIntegration: + """Integration tests for workflow template generation.""" + + def test_workflow_template_exists(self) -> None: + """Verify the JS workflow template file exists.""" + from importlib.resources import files + + template_path = files("codeflash").joinpath("cli_cmds", "workflows", "codeflash-optimize-js.yaml") + content = template_path.read_text(encoding="utf-8") + + # Check all placeholders exist + assert "{{ codeflash_module_path }}" in content + assert "{{ working_directory }}" in content + assert "{{ setup_runtime_steps }}" in content + assert "{{ install_dependencies_command }}" in content + assert "{{ install_codeflash_step }}" in content + assert "{{ codeflash_command }}" in content + + def test_workflow_template_has_correct_structure(self) -> None: + """Verify the JS workflow template has the expected YAML structure.""" + from importlib.resources import files + + template_path = files("codeflash").joinpath("cli_cmds", "workflows", "codeflash-optimize-js.yaml") + content = template_path.read_text(encoding="utf-8") + + # Check key sections + assert "name: Codeflash" in content + assert "pull_request:" in content + assert "workflow_dispatch:" in content + assert "concurrency:" in content + assert "cancel-in-progress: true" in content + assert "jobs:" in content + assert "optimize:" in content + assert "github.actor != 'codeflash-ai[bot]'" in content + assert "CODEFLASH_API_KEY" in content + assert "actions/checkout@v4" in content + assert "fetch-depth: 0" in content diff --git a/tests/scripts/end_to_end_test_js_cjs_function.py b/tests/scripts/end_to_end_test_js_cjs_function.py new file mode 100644 index 000000000..730f99168 --- /dev/null +++ b/tests/scripts/end_to_end_test_js_cjs_function.py @@ -0,0 +1,32 @@ +"""End-to-end test for JavaScript CommonJS optimization. + +Tests optimization of a simple recursive fibonacci function using CommonJS module format. +""" + +import pathlib + +from end_to_end_test_utilities_js import JSTestConfig, run_js_codeflash_command, run_with_retries + + +def run_test() -> bool: + """Run the CommonJS fibonacci optimization test.""" + config = JSTestConfig( + file_path=pathlib.Path("fibonacci.js"), + function_name="fibonacci", + min_improvement_x=0.5, # Expect at least 50% improvement + expected_improvement_pct=50, + expected_test_files=1, + ) + + cwd = ( + pathlib.Path(__file__).parent.parent.parent + / "code_to_optimize" + / "js" + / "code_to_optimize_js" + ).resolve() + + return run_js_codeflash_command(cwd, config) + + +if __name__ == "__main__": + exit(run_with_retries(run_test)) diff --git a/tests/scripts/end_to_end_test_js_esm_async.py b/tests/scripts/end_to_end_test_js_esm_async.py new file mode 100644 index 000000000..89e75ab12 --- /dev/null +++ b/tests/scripts/end_to_end_test_js_esm_async.py @@ -0,0 +1,33 @@ +"""End-to-end test for JavaScript ES Modules async function optimization. + +Tests optimization of async functions using ES Module format. +This tests the ESM module system with async/await code patterns. +""" + +import pathlib + +from end_to_end_test_utilities_js import JSTestConfig, run_js_codeflash_command, run_with_retries + + +def run_test() -> bool: + """Run the ES Modules async function optimization test.""" + config = JSTestConfig( + file_path=pathlib.Path("async_utils.js"), + function_name="processItemsSequential", + min_improvement_x=0.1, # Async optimizations may have variable gains + expected_improvement_pct=10, + expected_test_files=1, + ) + + cwd = ( + pathlib.Path(__file__).parent.parent.parent + / "code_to_optimize" + / "js" + / "code_to_optimize_js_esm" + ).resolve() + + return run_js_codeflash_command(cwd, config) + + +if __name__ == "__main__": + exit(run_with_retries(run_test)) diff --git a/tests/scripts/end_to_end_test_js_ts_class.py b/tests/scripts/end_to_end_test_js_ts_class.py new file mode 100644 index 000000000..40a4a6abc --- /dev/null +++ b/tests/scripts/end_to_end_test_js_ts_class.py @@ -0,0 +1,32 @@ +"""End-to-end test for TypeScript class method optimization. + +Tests optimization of class methods in TypeScript. +""" + +import pathlib + +from end_to_end_test_utilities_js import JSTestConfig, run_js_codeflash_command, run_with_retries + + +def run_test() -> bool: + """Run the TypeScript class method optimization test.""" + config = JSTestConfig( + file_path=pathlib.Path("data_processor.ts"), + function_name="DataProcessor.findDuplicates", + min_improvement_x=0.3, # Expect at least 30% improvement + expected_improvement_pct=30, + expected_test_files=1, + ) + + cwd = ( + pathlib.Path(__file__).parent.parent.parent + / "code_to_optimize" + / "js" + / "code_to_optimize_ts" + ).resolve() + + return run_js_codeflash_command(cwd, config) + + +if __name__ == "__main__": + exit(run_with_retries(run_test)) diff --git a/tests/scripts/end_to_end_test_utilities_js.py b/tests/scripts/end_to_end_test_utilities_js.py new file mode 100644 index 000000000..8344a3b31 --- /dev/null +++ b/tests/scripts/end_to_end_test_utilities_js.py @@ -0,0 +1,213 @@ +"""End-to-end test utilities for JavaScript/TypeScript optimization testing. + +Similar to end_to_end_test_utilities.py but adapted for JS/TS projects. +""" + +import logging +import os +import pathlib +import re +import shutil +import subprocess +import time +from dataclasses import dataclass, field +from typing import Optional + + +@dataclass +class JSTestConfig: + """Configuration for a JavaScript/TypeScript e2e test.""" + + # Path to the source file to optimize (relative to project root) + file_path: pathlib.Path + # Function name to optimize (optional - if not specified, optimizes all in file) + function_name: Optional[str] = None + # Minimum improvement multiplier (e.g., 0.5 = 50% faster) + min_improvement_x: float = 0.1 + # Expected improvement percentage (optimization must exceed this) + expected_improvement_pct: int = 10 + # Expected number of test files discovered + expected_test_files: Optional[int] = None + + +def clear_codeflash_directory(cwd: pathlib.Path) -> None: + """Clear the .codeflash directory to avoid stale state.""" + codeflash_dir = cwd / ".codeflash" + if codeflash_dir.exists(): + shutil.rmtree(codeflash_dir) + + +def install_npm_dependencies(cwd: pathlib.Path) -> bool: + """Install npm dependencies if needed.""" + node_modules = cwd / "node_modules" + if not node_modules.exists(): + logging.info(f"Installing npm dependencies in {cwd}") + result = subprocess.run( + ["npm", "install"], + cwd=str(cwd), + capture_output=True, + text=True, + ) + if result.returncode != 0: + logging.error(f"npm install failed: {result.stderr}") + return False + return True + + +def build_js_command( + cwd: pathlib.Path, + config: JSTestConfig, +) -> list[str]: + """Build the codeflash CLI command for JS/TS optimization.""" + # Use relative path for python to find the main module + python_path = "../../codeflash/main.py" + if "code_to_optimize_js" in str(cwd): + python_path = "../../../../codeflash/main.py" + + base_command = [ + "uv", + "run", + "--no-project", + python_path, + "--file", + str(config.file_path), + "--no-pr", + ] + + if config.function_name: + base_command.extend(["--function", config.function_name]) + + return base_command + + +def validate_js_output( + stdout: str, + return_code: int, + config: JSTestConfig, +) -> bool: + """Validate the output of a JS/TS optimization run.""" + if return_code != 0: + logging.error(f"Command returned exit code {return_code} instead of 0") + return False + + if "⚡️ Optimization successful! 📄 " not in stdout: + logging.error("Failed to find performance improvement message") + return False + + improvement_match = re.search(r"📈 ([\d,]+)% (?:(\w+) )?improvement", stdout) + if not improvement_match: + logging.error("Could not find improvement percentage in output") + return False + + improvement_pct = int(improvement_match.group(1).replace(",", "")) + improvement_x = float(improvement_pct) / 100 + + logging.info(f"Performance improvement: {improvement_pct}%; Rate: {improvement_x}x") + + if improvement_pct <= config.expected_improvement_pct: + logging.error( + f"Performance improvement {improvement_pct}% not above {config.expected_improvement_pct}%" + ) + return False + + if improvement_x <= config.min_improvement_x: + logging.error( + f"Performance improvement rate {improvement_x}x not above {config.min_improvement_x}x" + ) + return False + + if config.expected_test_files is not None: + test_files_match = re.search(r"Discovered (\d+) existing unit test files?", stdout) + if not test_files_match: + logging.error("Could not find unit test file count") + return False + + num_test_files = int(test_files_match.group(1)) + if num_test_files != config.expected_test_files: + logging.error( + f"Expected {config.expected_test_files} test files, found {num_test_files}" + ) + return False + + logging.info(f"Success: Performance improvement is {improvement_pct}%") + return True + + +def run_js_codeflash_command( + cwd: pathlib.Path, + config: JSTestConfig, +) -> bool: + """Run codeflash optimization on a JavaScript/TypeScript project.""" + logging.basicConfig(level=logging.INFO) + + # Save original file contents for potential revert + path_to_file = cwd / config.file_path + file_contents = path_to_file.read_text("utf-8") + + # Clear any stale state + clear_codeflash_directory(cwd) + + # Install dependencies if needed + if not install_npm_dependencies(cwd): + return False + + # Build and run command + command = build_js_command(cwd, config) + env = os.environ.copy() + env["PYTHONIOENCODING"] = "utf-8" + + logging.info(f"Running: {' '.join(command)}") + + process = subprocess.Popen( + command, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + text=True, + cwd=str(cwd), + env=env, + encoding="utf-8", + ) + + output = [] + for line in process.stdout: + logging.info(line.strip()) + output.append(line) + + return_code = process.wait() + stdout = "".join(output) + + validated = validate_js_output(stdout, return_code, config) + if not validated: + # Revert file changes on failure + path_to_file.write_text(file_contents, "utf-8") + logging.info("Codeflash run did not meet expected requirements, reverting file changes.") + return False + + return validated + + +def run_with_retries(test_func, *args, **kwargs) -> int: + """Run a test function with retries on failure.""" + max_retries = int(os.getenv("MAX_RETRIES", 3)) + retry_delay = int(os.getenv("RETRY_DELAY", 5)) + + log = logging.getLogger() + log.setLevel(logging.DEBUG) + + for attempt in range(1, max_retries + 1): + logging.info(f"\n=== Attempt {attempt} of {max_retries} ===") + + if test_func(*args, **kwargs): + logging.info(f"Test passed on attempt {attempt}") + return 0 + + logging.error(f"Test failed on attempt {attempt}") + + if attempt < max_retries: + logging.info(f"Retrying in {retry_delay} seconds...") + time.sleep(retry_delay) + else: + logging.error("Test failed after all retries") + return 1 + + return 1 \ No newline at end of file diff --git a/tests/scripts/run_js_e2e_tests.py b/tests/scripts/run_js_e2e_tests.py new file mode 100644 index 000000000..18d82a543 --- /dev/null +++ b/tests/scripts/run_js_e2e_tests.py @@ -0,0 +1,171 @@ +#!/usr/bin/env python3 +"""Runner script for all JavaScript/TypeScript end-to-end tests. + +This script runs all JS/TS e2e tests and reports results. +Usage: + python run_js_e2e_tests.py [--test TEST_NAME] [--parallel] + +Examples: + python run_js_e2e_tests.py # Run all tests sequentially + python run_js_e2e_tests.py --test fibonacci # Run only fibonacci tests + python run_js_e2e_tests.py --parallel # Run tests in parallel +""" + +import argparse +import subprocess +import sys +import time +from concurrent.futures import ProcessPoolExecutor, as_completed +from pathlib import Path +from typing import NamedTuple + + +class TestResult(NamedTuple): + name: str + success: bool + duration: float + output: str + + +# List of all JS/TS e2e tests - one per module type, each testing different code patterns +JS_E2E_TESTS = [ + # CommonJS - Simple function optimization (recursive fibonacci) + "end_to_end_test_js_cjs_function.py", + # TypeScript - Class method optimization (DataProcessor.findDuplicates) + "end_to_end_test_js_ts_class.py", + # ES Modules - Async function optimization (processItemsSequential) + "end_to_end_test_js_esm_async.py", +] + + +def run_single_test(test_file: str) -> TestResult: + """Run a single test and return the result.""" + script_dir = Path(__file__).parent + test_path = script_dir / test_file + + start_time = time.time() + try: + result = subprocess.run( + ["python", str(test_path)], + capture_output=True, + text=True, + timeout=600, # 10 minute timeout + cwd=str(script_dir), + ) + success = result.returncode == 0 + output = result.stdout + result.stderr + except subprocess.TimeoutExpired: + success = False + output = "Test timed out after 600 seconds" + except Exception as e: + success = False + output = f"Error running test: {e}" + + duration = time.time() - start_time + return TestResult( + name=test_file.replace(".py", ""), + success=success, + duration=duration, + output=output, + ) + + +def run_tests_sequential(tests: list[str]) -> list[TestResult]: + """Run tests sequentially.""" + results = [] + for test in tests: + print(f"\n{'='*60}") + print(f"Running: {test}") + print("=" * 60) + result = run_single_test(test) + results.append(result) + status = "✅ PASSED" if result.success else "❌ FAILED" + print(f"{status} in {result.duration:.1f}s") + if not result.success: + print(f"Output:\n{result.output}") + return results + + +def run_tests_parallel(tests: list[str], max_workers: int = 4) -> list[TestResult]: + """Run tests in parallel.""" + results = [] + with ProcessPoolExecutor(max_workers=max_workers) as executor: + futures = {executor.submit(run_single_test, test): test for test in tests} + for future in as_completed(futures): + test = futures[future] + result = future.result() + results.append(result) + status = "✅ PASSED" if result.success else "❌ FAILED" + print(f"{status}: {result.name} ({result.duration:.1f}s)") + return results + + +def print_summary(results: list[TestResult]) -> None: + """Print a summary of test results.""" + print("\n" + "=" * 60) + print("TEST SUMMARY") + print("=" * 60) + + passed = [r for r in results if r.success] + failed = [r for r in results if not r.success] + + print(f"\nTotal: {len(results)}") + print(f"Passed: {len(passed)}") + print(f"Failed: {len(failed)}") + + if failed: + print("\nFailed tests:") + for r in failed: + print(f" ❌ {r.name}") + + total_duration = sum(r.duration for r in results) + print(f"\nTotal duration: {total_duration:.1f}s") + + +def main() -> int: + parser = argparse.ArgumentParser(description="Run JS/TS e2e tests") + parser.add_argument( + "--test", + type=str, + help="Run only tests matching this pattern", + ) + parser.add_argument( + "--parallel", + action="store_true", + help="Run tests in parallel", + ) + parser.add_argument( + "--workers", + type=int, + default=4, + help="Number of parallel workers (default: 4)", + ) + args = parser.parse_args() + + # Filter tests if pattern specified + tests = JS_E2E_TESTS + if args.test: + tests = [t for t in tests if args.test.lower() in t.lower()] + + if not tests: + print(f"No tests matching pattern: {args.test}") + return 1 + + print(f"Running {len(tests)} test(s)...") + + # Run tests + if args.parallel: + results = run_tests_parallel(tests, args.workers) + else: + results = run_tests_sequential(tests) + + # Print summary + print_summary(results) + + # Return exit code + failed = [r for r in results if not r.success] + return 1 if failed else 0 + + +if __name__ == "__main__": + sys.exit(main())