Skip to content

Commit e957843

Browse files
authored
Merge pull request #1261 from codeflash-ai/fix/js-skip-module-conversion-for-ts-jest
[JS] Skip module conversion only for TypeScript projects with ts-jest
2 parents 9483ff6 + 7a9caab commit e957843

5 files changed

Lines changed: 223 additions & 47 deletions

File tree

code_to_optimize/js/code_to_optimize_ts/package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

codeflash/languages/current.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,6 @@ def set_current_language(language: Language | str) -> None:
5858
5959
"""
6060
global _current_language
61-
62-
if _current_language is not None:
63-
return
6461
_current_language = Language(language) if isinstance(language, str) else language
6562

6663

codeflash/languages/javascript/module_system.py

Lines changed: 70 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
import re
1212
from typing import TYPE_CHECKING
1313

14+
from codeflash.languages.current import is_typescript
15+
1416
if TYPE_CHECKING:
1517
from pathlib import Path
1618

@@ -307,36 +309,89 @@ def replace_default(match) -> str:
307309
return default_import.sub(replace_default, code)
308310

309311

310-
def ensure_module_system_compatibility(code: str, target_module_system: str) -> str:
312+
def uses_ts_jest(project_root: Path) -> bool:
313+
"""Check if the project uses ts-jest for TypeScript transformation.
314+
315+
ts-jest handles module interoperability internally, allowing mixed
316+
CommonJS/ESM imports without explicit conversion.
317+
318+
Args:
319+
project_root: The project root directory.
320+
321+
Returns:
322+
True if ts-jest is being used, False otherwise.
323+
324+
"""
325+
# Check for ts-jest in devDependencies or dependencies
326+
package_json = project_root / "package.json"
327+
if package_json.exists():
328+
try:
329+
with package_json.open("r") as f:
330+
pkg = json.load(f)
331+
dev_deps = pkg.get("devDependencies", {})
332+
deps = pkg.get("dependencies", {})
333+
if "ts-jest" in dev_deps or "ts-jest" in deps:
334+
return True
335+
except Exception as e:
336+
logger.debug(f"Failed to read package.json for ts-jest detection: {e}") # noqa: G004
337+
338+
# Also check for jest.config with ts-jest preset
339+
for config_file in ["jest.config.js", "jest.config.cjs", "jest.config.ts", "jest.config.mjs"]:
340+
config_path = project_root / config_file
341+
if config_path.exists():
342+
try:
343+
content = config_path.read_text()
344+
if "ts-jest" in content:
345+
return True
346+
except Exception as e:
347+
logger.debug(f"Failed to read {config_file}: {e}") # noqa: G004
348+
349+
return False
350+
351+
352+
def ensure_module_system_compatibility(code: str, target_module_system: str, project_root: Path | None = None) -> str:
311353
"""Ensure code uses the correct module system syntax.
312354
313-
Detects the current module system in the code and converts if needed.
314-
Handles mixed-style code (e.g., ESM imports with CommonJS require for npm packages).
355+
If the project uses ts-jest, no conversion is performed because ts-jest
356+
handles module interoperability internally. Otherwise, converts between
357+
CommonJS and ES Modules as needed.
315358
316359
Args:
317360
code: JavaScript code to check and potentially convert.
318361
target_module_system: Target ModuleSystem (COMMONJS or ES_MODULE).
362+
project_root: Project root directory for ts-jest detection.
319363
320364
Returns:
321-
Code with correct module system syntax.
365+
Converted code, or unchanged if ts-jest handles interop.
322366
323367
"""
368+
# If ts-jest is installed, skip conversion - it handles interop natively
369+
if is_typescript() and project_root and uses_ts_jest(project_root):
370+
logger.debug(
371+
f"Skipping module system conversion (target was {target_module_system}). " # noqa: G004
372+
"ts-jest handles interop natively."
373+
)
374+
return code
375+
324376
# Detect current module system in code
325377
has_require = "require(" in code
378+
has_module_exports = "module.exports" in code or "exports." in code
326379
has_import = "import " in code and "from " in code
380+
has_export = "export " in code
381+
382+
is_commonjs = has_require or has_module_exports
383+
is_esm = has_import or has_export
384+
385+
# Convert if needed
386+
if target_module_system == ModuleSystem.ES_MODULE and is_commonjs and not is_esm:
387+
logger.debug("Converting CommonJS to ES Module syntax")
388+
return convert_commonjs_to_esm(code)
327389

328-
if target_module_system == ModuleSystem.ES_MODULE:
329-
# Convert any require() statements to imports for ESM projects
330-
# This handles mixed code (ESM imports + CommonJS requires for npm packages)
331-
if has_require:
332-
logger.debug("Converting CommonJS requires to ESM imports")
333-
return convert_commonjs_to_esm(code)
334-
elif target_module_system == ModuleSystem.COMMONJS:
335-
# Convert any import statements to requires for CommonJS projects
336-
if has_import:
337-
logger.debug("Converting ESM imports to CommonJS requires")
338-
return convert_esm_to_commonjs(code)
390+
if target_module_system == ModuleSystem.COMMONJS and is_esm and not is_commonjs:
391+
logger.debug("Converting ES Module to CommonJS syntax")
392+
return convert_esm_to_commonjs(code)
339393

394+
logger.debug("No module system conversion needed")
340395
return code
341396

342397

codeflash/verification/verifier.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,10 @@ def generate_tests(
8282
)
8383

8484
# Convert module system if needed (e.g., CommonJS -> ESM for ESM projects)
85-
generated_test_source = ensure_module_system_compatibility(generated_test_source, project_module_system)
85+
# Skip conversion if ts-jest is installed (handles interop natively)
86+
generated_test_source = ensure_module_system_compatibility(
87+
generated_test_source, project_module_system, test_cfg.tests_project_rootdir
88+
)
8689

8790
# Ensure vitest imports are present when using vitest framework
8891
generated_test_source = ensure_vitest_imports(generated_test_source, test_cfg.test_framework)

tests/test_languages/test_js_code_replacer.py

Lines changed: 148 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
import pytest
1616

1717
from codeflash.code_utils.code_replacer import replace_function_definitions_for_language
18+
from codeflash.languages.base import Language
19+
from codeflash.languages.current import set_current_language
1820
from codeflash.languages.javascript.module_system import (
1921
ModuleSystem,
2022
convert_commonjs_to_esm,
@@ -300,57 +302,176 @@ def test_preserves_function_code(self):
300302
assert "return add(x, y);" in result
301303

302304

303-
class TestModuleSystemCompatibility:
304-
"""Tests for module system compatibility."""
305+
class TestTsJestSkipsConversion:
306+
"""Tests verifying that module system conversion is skipped when ts-jest is installed.
305307
306-
def test_convert_mixed_code_to_esm(self):
307-
"""Test converting mixed CJS/ESM code to pure ESM - exact output."""
308-
code = """\
309-
import { existing } from './module.js';
308+
When ts-jest is installed, it handles module interoperability internally,
309+
so we skip conversion to avoid breaking valid imports.
310+
"""
311+
def __init__(self):
312+
set_current_language(Language.TYPESCRIPT)
313+
314+
def test_commonjs_not_converted_when_ts_jest_installed(self, tmp_path):
315+
"""Test that CommonJS is NOT converted to ESM when ts-jest is installed."""
316+
# Create a project with ts-jest
317+
package_json = tmp_path / "package.json"
318+
package_json.write_text('{"devDependencies": {"ts-jest": "^29.0.0"}}')
319+
320+
commonjs_test = """\
321+
const Logger = require('../utils/logger');
322+
const { helper } = require('../utils/helpers');
323+
324+
describe('Logger', () => {
325+
test('should work', () => {
326+
const logger = new Logger();
327+
expect(logger).toBeDefined();
328+
});
329+
});
330+
"""
331+
# With ts-jest, no conversion should happen
332+
result = ensure_module_system_compatibility(commonjs_test, ModuleSystem.ES_MODULE, tmp_path)
333+
334+
assert result == commonjs_test, (
335+
f"CommonJS should NOT be converted when ts-jest is installed.\n"
336+
f"Expected (unchanged):\n{commonjs_test}\n\nGot:\n{result}"
337+
)
338+
339+
def test_esm_not_converted_when_ts_jest_installed(self, tmp_path):
340+
"""Test that ESM is NOT converted to CommonJS when ts-jest is installed."""
341+
# Create a project with ts-jest
342+
package_json = tmp_path / "package.json"
343+
package_json.write_text('{"devDependencies": {"ts-jest": "^29.0.0"}}')
344+
345+
esm_test = """\
346+
import Logger from '../utils/logger';
347+
import { helper } from '../utils/helpers';
348+
349+
describe('Logger', () => {
350+
test('should work', () => {
351+
const logger = new Logger();
352+
expect(logger).toBeDefined();
353+
});
354+
});
355+
"""
356+
# With ts-jest, no conversion should happen
357+
result = ensure_module_system_compatibility(esm_test, ModuleSystem.COMMONJS, tmp_path)
358+
359+
assert result == esm_test, (
360+
f"ESM should NOT be converted when ts-jest is installed.\n"
361+
f"Expected (unchanged):\n{esm_test}\n\nGot:\n{result}"
362+
)
363+
364+
def test_ts_jest_detected_in_jest_config(self, tmp_path):
365+
"""Test that ts-jest is detected from jest.config.js content."""
366+
# Create a project with ts-jest in jest.config.js (not package.json)
367+
package_json = tmp_path / "package.json"
368+
package_json.write_text('{"devDependencies": {}}')
369+
jest_config = tmp_path / "jest.config.js"
370+
jest_config.write_text("module.exports = { preset: 'ts-jest' };")
371+
372+
commonjs_test = "const x = require('./module');"
373+
374+
result = ensure_module_system_compatibility(commonjs_test, ModuleSystem.ES_MODULE, tmp_path)
375+
376+
assert result == commonjs_test, "Should skip conversion when ts-jest is in jest.config.js"
377+
378+
379+
class TestModuleSystemConversion:
380+
"""Tests for module system conversion when ts-jest is NOT installed.
381+
382+
Without ts-jest, we convert between CommonJS and ESM as needed.
383+
"""
384+
385+
def test_commonjs_converted_to_esm_without_ts_jest(self, tmp_path):
386+
"""Test that CommonJS is converted to ESM when ts-jest is NOT installed."""
387+
# Create a project WITHOUT ts-jest
388+
package_json = tmp_path / "package.json"
389+
package_json.write_text('{"devDependencies": {"jest": "^29.0.0"}}')
390+
391+
commonjs_code = """\
310392
const { helper } = require('./helpers');
393+
const logger = require('./logger');
311394
312395
function process() {
313-
return existing() + helper();
396+
return helper();
314397
}
315398
"""
316-
result = ensure_module_system_compatibility(code, ModuleSystem.ES_MODULE)
399+
result = ensure_module_system_compatibility(commonjs_code, ModuleSystem.ES_MODULE, tmp_path)
317400

318-
# Should convert require to import
401+
# Should be converted to ESM
319402
assert "import { helper } from './helpers';" in result
320-
assert "require" not in result, f"require should be converted to import. Got:\n{result}"
403+
assert "import logger from './logger';" in result
404+
assert "require(" not in result
321405

322-
def test_convert_mixed_code_to_commonjs(self):
323-
"""Test converting mixed ESM/CJS code to pure CommonJS - exact output."""
324-
code = """\
325-
const { existing } = require('./module');
326-
import { helper } from './helpers.js';
406+
def test_esm_converted_to_commonjs_without_ts_jest(self, tmp_path):
407+
"""Test that ESM is converted to CommonJS when ts-jest is NOT installed."""
408+
# Create a project WITHOUT ts-jest
409+
package_json = tmp_path / "package.json"
410+
package_json.write_text('{"devDependencies": {"jest": "^29.0.0"}}')
411+
412+
esm_code = """\
413+
import { helper } from './helpers';
414+
import logger from './logger';
327415
328416
function process() {
329-
return existing() + helper();
417+
return helper();
330418
}
331419
"""
332-
result = ensure_module_system_compatibility(code, ModuleSystem.COMMONJS)
420+
result = ensure_module_system_compatibility(esm_code, ModuleSystem.COMMONJS, tmp_path)
333421

334-
# Should convert import to require
422+
# Should be converted to CommonJS
335423
assert "const { helper } = require('./helpers');" in result
336-
assert "import " not in result.split("\n")[0] or "import " not in result, (
337-
f"import should be converted to require. Got:\n{result}"
338-
)
424+
assert "const logger = require('./logger');" in result
425+
assert "import " not in result
426+
427+
def test_no_conversion_when_project_root_is_none(self):
428+
"""Test that conversion happens when project_root is None (can't detect ts-jest)."""
429+
commonjs_code = "const x = require('./module');"
430+
431+
# Without project_root, we can't detect ts-jest, so conversion should happen
432+
result = ensure_module_system_compatibility(commonjs_code, ModuleSystem.ES_MODULE, None)
433+
434+
# Should be converted to ESM
435+
assert "import x from './module';" in result
436+
437+
def test_mixed_code_not_converted(self, tmp_path):
438+
"""Test that mixed CJS/ESM code is NOT converted (already has both)."""
439+
package_json = tmp_path / "package.json"
440+
package_json.write_text('{"devDependencies": {"jest": "^29.0.0"}}')
339441

340-
def test_pure_esm_unchanged(self):
442+
mixed_code = """\
443+
import { existing } from './module.js';
444+
const { helper } = require('./helpers');
445+
446+
function process() {
447+
return existing() + helper();
448+
}
449+
"""
450+
# Mixed code has both import and require, so no conversion
451+
result = ensure_module_system_compatibility(mixed_code, ModuleSystem.ES_MODULE, tmp_path)
452+
453+
assert result == mixed_code, "Mixed code should not be converted"
454+
455+
def test_pure_esm_unchanged_for_esm_target(self, tmp_path):
341456
"""Test that pure ESM code is unchanged when targeting ESM."""
457+
package_json = tmp_path / "package.json"
458+
package_json.write_text('{"devDependencies": {"jest": "^29.0.0"}}')
459+
342460
code = """\
343461
import { add } from './math.js';
344462
345463
export function sum(a, b) {
346464
return add(a, b);
347465
}
348466
"""
349-
result = ensure_module_system_compatibility(code, ModuleSystem.ES_MODULE)
350-
assert result == code, f"Pure ESM code should be unchanged.\nExpected:\n{code}\n\nGot:\n{result}"
467+
result = ensure_module_system_compatibility(code, ModuleSystem.ES_MODULE, tmp_path)
468+
assert result == code, "Pure ESM code should be unchanged for ESM target"
351469

352-
def test_pure_commonjs_unchanged(self):
470+
def test_pure_commonjs_unchanged_for_commonjs_target(self, tmp_path):
353471
"""Test that pure CommonJS code is unchanged when targeting CommonJS."""
472+
package_json = tmp_path / "package.json"
473+
package_json.write_text('{"devDependencies": {"jest": "^29.0.0"}}')
474+
354475
code = """\
355476
const { add } = require('./math');
356477
@@ -360,8 +481,8 @@ def test_pure_commonjs_unchanged(self):
360481
361482
module.exports = { sum };
362483
"""
363-
result = ensure_module_system_compatibility(code, ModuleSystem.COMMONJS)
364-
assert result == code, f"Pure CommonJS code should be unchanged.\nExpected:\n{code}\n\nGot:\n{result}"
484+
result = ensure_module_system_compatibility(code, ModuleSystem.COMMONJS, tmp_path)
485+
assert result == code, "Pure CommonJS code should be unchanged for CommonJS target"
365486

366487

367488
class TestImportStatementGeneration:

0 commit comments

Comments
 (0)