fix: skip module conversion only for TypeScript projects with ts-jest

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
ali 2026-02-02 17:27:41 +02:00
parent 3711aa12c1
commit 348a6eccd3
No known key found for this signature in database
GPG key ID: 44F9B42770617B9B
5 changed files with 229 additions and 53 deletions

View file

@ -20,7 +20,7 @@
}
},
"../../../packages/codeflash": {
"version": "0.3.1",
"version": "0.4.0",
"dev": true,
"hasInstallScript": true,
"license": "MIT",

View file

@ -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

View file

@ -11,6 +11,8 @@ import logging
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 convert_esm_to_commonjs(code: str) -> 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
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)
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.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

View file

@ -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(

View file

@ -15,6 +15,8 @@ from pathlib import Path
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,12 +302,144 @@ export function calculate(x, y) {
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 = """\
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 helper();
}
"""
result = ensure_module_system_compatibility(commonjs_code, ModuleSystem.ES_MODULE, tmp_path)
# Should be converted to ESM
assert "import { helper } from './helpers';" in result
assert "import logger from './logger';" in result
assert "require(" not in result
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 helper();
}
"""
result = ensure_module_system_compatibility(esm_code, ModuleSystem.COMMONJS, tmp_path)
# Should be converted to CommonJS
assert "const { helper } = require('./helpers');" in 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"}}')
mixed_code = """\
import { existing } from './module.js';
const { helper } = require('./helpers');
@ -313,32 +447,16 @@ function process() {
return existing() + helper();
}
"""
result = ensure_module_system_compatibility(code, ModuleSystem.ES_MODULE)
# Mixed code has both import and require, so no conversion
result = ensure_module_system_compatibility(mixed_code, ModuleSystem.ES_MODULE, tmp_path)
# Should convert require to import
assert "import { helper } from './helpers';" in result
assert "require" not in result, f"require should be converted to import. Got:\n{result}"
assert result == mixed_code, "Mixed code should not be converted"
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';
function process() {
return existing() + helper();
}
"""
result = ensure_module_system_compatibility(code, ModuleSystem.COMMONJS)
# Should convert import to require
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}"
)
def test_pure_esm_unchanged(self):
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 @@ export function sum(a, b) {
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 @@ function sum(a, b) {
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: