mirror of
https://github.com/codeflash-ai/codeflash.git
synced 2026-05-04 18:25:17 +00:00
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:
parent
3711aa12c1
commit
348a6eccd3
5 changed files with 229 additions and 53 deletions
|
|
@ -20,7 +20,7 @@
|
|||
}
|
||||
},
|
||||
"../../../packages/codeflash": {
|
||||
"version": "0.3.1",
|
||||
"version": "0.4.0",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
Loading…
Reference in a new issue