Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion code_to_optimize/js/code_to_optimize_ts/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 0 additions & 3 deletions codeflash/languages/current.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down
85 changes: 70 additions & 15 deletions codeflash/languages/javascript/module_system.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -307,36 +309,89 @@ 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


Expand Down
5 changes: 4 additions & 1 deletion codeflash/verification/verifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,10 @@ def generate_tests(
)

# 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
)

# Ensure vitest imports are present when using vitest framework
generated_test_source = ensure_vitest_imports(generated_test_source, test_cfg.test_framework)
Expand Down
175 changes: 148 additions & 27 deletions tests/test_languages/test_js_code_replacer.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -300,57 +302,176 @@ 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';

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');

Expand All @@ -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:
Expand Down
Loading