From 348a6eccd3032bda6325f099229fab2b4191e02d Mon Sep 17 00:00:00 2001 From: ali Date: Mon, 2 Feb 2026 17:27:41 +0200 Subject: [PATCH] fix: skip module conversion only for TypeScript projects with ts-jest Co-Authored-By: Claude Opus 4.5 --- .../js/code_to_optimize_ts/package-lock.json | 2 +- codeflash/languages/current.py | 3 - .../languages/javascript/module_system.py | 85 +++++++-- codeflash/verification/verifier.py | 5 +- tests/test_languages/test_js_code_replacer.py | 175 +++++++++++++++--- 5 files changed, 223 insertions(+), 47 deletions(-) diff --git a/code_to_optimize/js/code_to_optimize_ts/package-lock.json b/code_to_optimize/js/code_to_optimize_ts/package-lock.json index d6a55a04d..e6d640f78 100644 --- a/code_to_optimize/js/code_to_optimize_ts/package-lock.json +++ b/code_to_optimize/js/code_to_optimize_ts/package-lock.json @@ -20,7 +20,7 @@ } }, "../../../packages/codeflash": { - "version": "0.3.1", + "version": "0.4.0", "dev": true, "hasInstallScript": true, "license": "MIT", diff --git a/codeflash/languages/current.py b/codeflash/languages/current.py index 212aa69eb..ecdb7315a 100644 --- a/codeflash/languages/current.py +++ b/codeflash/languages/current.py @@ -58,9 +58,6 @@ def set_current_language(language: Language | str) -> None: """ global _current_language - - if _current_language is not None: - return _current_language = Language(language) if isinstance(language, str) else language diff --git a/codeflash/languages/javascript/module_system.py b/codeflash/languages/javascript/module_system.py index e88325d24..a537bfbca 100644 --- a/codeflash/languages/javascript/module_system.py +++ b/codeflash/languages/javascript/module_system.py @@ -11,6 +11,8 @@ import re from typing import TYPE_CHECKING +from codeflash.languages.current import is_typescript + if TYPE_CHECKING: from pathlib import Path @@ -299,34 +301,87 @@ def replace_default(match) -> str: return default_import.sub(replace_default, code) -def ensure_module_system_compatibility(code: str, target_module_system: str) -> str: +def uses_ts_jest(project_root: Path) -> bool: + """Check if the project uses ts-jest for TypeScript transformation. + + ts-jest handles module interoperability internally, allowing mixed + CommonJS/ESM imports without explicit conversion. + + Args: + project_root: The project root directory. + + Returns: + True if ts-jest is being used, False otherwise. + + """ + # Check for ts-jest in devDependencies or dependencies + package_json = project_root / "package.json" + if package_json.exists(): + try: + with package_json.open("r") as f: + pkg = json.load(f) + dev_deps = pkg.get("devDependencies", {}) + deps = pkg.get("dependencies", {}) + if "ts-jest" in dev_deps or "ts-jest" in deps: + return True + except Exception as e: + logger.debug(f"Failed to read package.json for ts-jest detection: {e}") # noqa: G004 + + # Also check for jest.config with ts-jest preset + for config_file in ["jest.config.js", "jest.config.cjs", "jest.config.ts", "jest.config.mjs"]: + config_path = project_root / config_file + if config_path.exists(): + try: + content = config_path.read_text() + if "ts-jest" in content: + return True + except Exception as e: + logger.debug(f"Failed to read {config_file}: {e}") # noqa: G004 + + return False + + +def ensure_module_system_compatibility(code: str, target_module_system: str, project_root: Path | None = None) -> str: """Ensure code uses the correct module system syntax. - Detects the current module system in the code and converts if needed. - Handles mixed-style code (e.g., ESM imports with CommonJS require for npm packages). + If the project uses ts-jest, no conversion is performed because ts-jest + handles module interoperability internally. Otherwise, converts between + CommonJS and ES Modules as needed. Args: code: JavaScript code to check and potentially convert. target_module_system: Target ModuleSystem (COMMONJS or ES_MODULE). + project_root: Project root directory for ts-jest detection. Returns: - Code with correct module system syntax. + Converted code, or unchanged if ts-jest handles interop. """ + # If ts-jest is installed, skip conversion - it handles interop natively + if is_typescript() and project_root and uses_ts_jest(project_root): + logger.debug( + f"Skipping module system conversion (target was {target_module_system}). " # noqa: G004 + "ts-jest handles interop natively." + ) + return code + # Detect current module system in code has_require = "require(" in code + has_module_exports = "module.exports" in code or "exports." in code has_import = "import " in code and "from " in code + has_export = "export " in code + + is_commonjs = has_require or has_module_exports + is_esm = has_import or has_export + + # Convert if needed + if target_module_system == ModuleSystem.ES_MODULE and is_commonjs and not is_esm: + logger.debug("Converting CommonJS to ES Module syntax") + return convert_commonjs_to_esm(code) - if target_module_system == ModuleSystem.ES_MODULE: - # Convert any require() statements to imports for ESM projects - # This handles mixed code (ESM imports + CommonJS requires for npm packages) - if has_require: - logger.debug("Converting CommonJS requires to ESM imports") - return convert_commonjs_to_esm(code) - elif target_module_system == ModuleSystem.COMMONJS: - # Convert any import statements to requires for CommonJS projects - if has_import: - logger.debug("Converting ESM imports to CommonJS requires") - return convert_esm_to_commonjs(code) + if target_module_system == ModuleSystem.COMMONJS and is_esm and not is_commonjs: + logger.debug("Converting ES Module to CommonJS syntax") + return convert_esm_to_commonjs(code) + logger.debug("No module system conversion needed") return code diff --git a/codeflash/verification/verifier.py b/codeflash/verification/verifier.py index 19500b968..bc59f2a1c 100644 --- a/codeflash/verification/verifier.py +++ b/codeflash/verification/verifier.py @@ -79,7 +79,10 @@ def generate_tests( generated_test_source = validate_and_fix_import_style(generated_test_source, source_file, func_name) # Convert module system if needed (e.g., CommonJS -> ESM for ESM projects) - generated_test_source = ensure_module_system_compatibility(generated_test_source, project_module_system) + # Skip conversion if ts-jest is installed (handles interop natively) + generated_test_source = ensure_module_system_compatibility( + generated_test_source, project_module_system, test_cfg.tests_project_rootdir + ) # Instrument for behavior verification (writes to SQLite) instrumented_behavior_test_source = instrument_generated_js_test( diff --git a/tests/test_languages/test_js_code_replacer.py b/tests/test_languages/test_js_code_replacer.py index 3d703aa34..e3c9fa5ff 100644 --- a/tests/test_languages/test_js_code_replacer.py +++ b/tests/test_languages/test_js_code_replacer.py @@ -15,6 +15,8 @@ import pytest from codeflash.code_utils.code_replacer import replace_function_definitions_for_language +from codeflash.languages.base import Language +from codeflash.languages.current import set_current_language from codeflash.languages.javascript.module_system import ( ModuleSystem, convert_commonjs_to_esm, @@ -300,45 +302,161 @@ def test_preserves_function_code(self): assert "return add(x, y);" in result -class TestModuleSystemCompatibility: - """Tests for module system compatibility.""" +class TestTsJestSkipsConversion: + """Tests verifying that module system conversion is skipped when ts-jest is installed. - def test_convert_mixed_code_to_esm(self): - """Test converting mixed CJS/ESM code to pure ESM - exact output.""" - code = """\ -import { existing } from './module.js'; + When ts-jest is installed, it handles module interoperability internally, + so we skip conversion to avoid breaking valid imports. + """ + def __init__(self): + set_current_language(Language.TYPESCRIPT) + + def test_commonjs_not_converted_when_ts_jest_installed(self, tmp_path): + """Test that CommonJS is NOT converted to ESM when ts-jest is installed.""" + # Create a project with ts-jest + package_json = tmp_path / "package.json" + package_json.write_text('{"devDependencies": {"ts-jest": "^29.0.0"}}') + + commonjs_test = """\ +const Logger = require('../utils/logger'); +const { helper } = require('../utils/helpers'); + +describe('Logger', () => { + test('should work', () => { + const logger = new Logger(); + expect(logger).toBeDefined(); + }); +}); +""" + # With ts-jest, no conversion should happen + result = ensure_module_system_compatibility(commonjs_test, ModuleSystem.ES_MODULE, tmp_path) + + assert result == commonjs_test, ( + f"CommonJS should NOT be converted when ts-jest is installed.\n" + f"Expected (unchanged):\n{commonjs_test}\n\nGot:\n{result}" + ) + + def test_esm_not_converted_when_ts_jest_installed(self, tmp_path): + """Test that ESM is NOT converted to CommonJS when ts-jest is installed.""" + # Create a project with ts-jest + package_json = tmp_path / "package.json" + package_json.write_text('{"devDependencies": {"ts-jest": "^29.0.0"}}') + + esm_test = """\ +import Logger from '../utils/logger'; +import { helper } from '../utils/helpers'; + +describe('Logger', () => { + test('should work', () => { + const logger = new Logger(); + expect(logger).toBeDefined(); + }); +}); +""" + # With ts-jest, no conversion should happen + result = ensure_module_system_compatibility(esm_test, ModuleSystem.COMMONJS, tmp_path) + + assert result == esm_test, ( + f"ESM should NOT be converted when ts-jest is installed.\n" + f"Expected (unchanged):\n{esm_test}\n\nGot:\n{result}" + ) + + def test_ts_jest_detected_in_jest_config(self, tmp_path): + """Test that ts-jest is detected from jest.config.js content.""" + # Create a project with ts-jest in jest.config.js (not package.json) + package_json = tmp_path / "package.json" + package_json.write_text('{"devDependencies": {}}') + jest_config = tmp_path / "jest.config.js" + jest_config.write_text("module.exports = { preset: 'ts-jest' };") + + commonjs_test = "const x = require('./module');" + + result = ensure_module_system_compatibility(commonjs_test, ModuleSystem.ES_MODULE, tmp_path) + + assert result == commonjs_test, "Should skip conversion when ts-jest is in jest.config.js" + + +class TestModuleSystemConversion: + """Tests for module system conversion when ts-jest is NOT installed. + + Without ts-jest, we convert between CommonJS and ESM as needed. + """ + + def test_commonjs_converted_to_esm_without_ts_jest(self, tmp_path): + """Test that CommonJS is converted to ESM when ts-jest is NOT installed.""" + # Create a project WITHOUT ts-jest + package_json = tmp_path / "package.json" + package_json.write_text('{"devDependencies": {"jest": "^29.0.0"}}') + + commonjs_code = """\ const { helper } = require('./helpers'); +const logger = require('./logger'); function process() { - return existing() + helper(); + return helper(); } """ - result = ensure_module_system_compatibility(code, ModuleSystem.ES_MODULE) + result = ensure_module_system_compatibility(commonjs_code, ModuleSystem.ES_MODULE, tmp_path) - # Should convert require to import + # Should be converted to ESM assert "import { helper } from './helpers';" in result - assert "require" not in result, f"require should be converted to import. Got:\n{result}" + assert "import logger from './logger';" in result + assert "require(" not in result - def test_convert_mixed_code_to_commonjs(self): - """Test converting mixed ESM/CJS code to pure CommonJS - exact output.""" - code = """\ -const { existing } = require('./module'); -import { helper } from './helpers.js'; + def test_esm_converted_to_commonjs_without_ts_jest(self, tmp_path): + """Test that ESM is converted to CommonJS when ts-jest is NOT installed.""" + # Create a project WITHOUT ts-jest + package_json = tmp_path / "package.json" + package_json.write_text('{"devDependencies": {"jest": "^29.0.0"}}') + + esm_code = """\ +import { helper } from './helpers'; +import logger from './logger'; function process() { - return existing() + helper(); + return helper(); } """ - result = ensure_module_system_compatibility(code, ModuleSystem.COMMONJS) + result = ensure_module_system_compatibility(esm_code, ModuleSystem.COMMONJS, tmp_path) - # Should convert import to require + # Should be converted to CommonJS assert "const { helper } = require('./helpers');" in result - assert "import " not in result.split("\n")[0] or "import " not in result, ( - f"import should be converted to require. Got:\n{result}" - ) + assert "const logger = require('./logger');" in result + assert "import " not in result + + def test_no_conversion_when_project_root_is_none(self): + """Test that conversion happens when project_root is None (can't detect ts-jest).""" + commonjs_code = "const x = require('./module');" + + # Without project_root, we can't detect ts-jest, so conversion should happen + result = ensure_module_system_compatibility(commonjs_code, ModuleSystem.ES_MODULE, None) + + # Should be converted to ESM + assert "import x from './module';" in result + + def test_mixed_code_not_converted(self, tmp_path): + """Test that mixed CJS/ESM code is NOT converted (already has both).""" + package_json = tmp_path / "package.json" + package_json.write_text('{"devDependencies": {"jest": "^29.0.0"}}') - def test_pure_esm_unchanged(self): + mixed_code = """\ +import { existing } from './module.js'; +const { helper } = require('./helpers'); + +function process() { + return existing() + helper(); +} +""" + # Mixed code has both import and require, so no conversion + result = ensure_module_system_compatibility(mixed_code, ModuleSystem.ES_MODULE, tmp_path) + + assert result == mixed_code, "Mixed code should not be converted" + + def test_pure_esm_unchanged_for_esm_target(self, tmp_path): """Test that pure ESM code is unchanged when targeting ESM.""" + package_json = tmp_path / "package.json" + package_json.write_text('{"devDependencies": {"jest": "^29.0.0"}}') + code = """\ import { add } from './math.js'; @@ -346,11 +464,14 @@ def test_pure_esm_unchanged(self): return add(a, b); } """ - result = ensure_module_system_compatibility(code, ModuleSystem.ES_MODULE) - assert result == code, f"Pure ESM code should be unchanged.\nExpected:\n{code}\n\nGot:\n{result}" + result = ensure_module_system_compatibility(code, ModuleSystem.ES_MODULE, tmp_path) + assert result == code, "Pure ESM code should be unchanged for ESM target" - def test_pure_commonjs_unchanged(self): + def test_pure_commonjs_unchanged_for_commonjs_target(self, tmp_path): """Test that pure CommonJS code is unchanged when targeting CommonJS.""" + package_json = tmp_path / "package.json" + package_json.write_text('{"devDependencies": {"jest": "^29.0.0"}}') + code = """\ const { add } = require('./math'); @@ -360,8 +481,8 @@ def test_pure_commonjs_unchanged(self): module.exports = { sum }; """ - result = ensure_module_system_compatibility(code, ModuleSystem.COMMONJS) - assert result == code, f"Pure CommonJS code should be unchanged.\nExpected:\n{code}\n\nGot:\n{result}" + result = ensure_module_system_compatibility(code, ModuleSystem.COMMONJS, tmp_path) + assert result == code, "Pure CommonJS code should be unchanged for CommonJS target" class TestImportStatementGeneration: