1111import re
1212from typing import TYPE_CHECKING
1313
14+ from codeflash .languages .current import is_typescript
15+
1416if TYPE_CHECKING :
1517 from pathlib import Path
1618
@@ -44,9 +46,10 @@ def detect_module_system(project_root: Path, file_path: Path | None = None) -> s
4446 """Detect the module system used by a JavaScript/TypeScript project.
4547
4648 Detection strategy:
47- 1. Check package.json for "type" field
48- 2. If file_path provided, check file extension (.mjs = ESM, .cjs = CommonJS)
49- 3. Analyze import statements in the file
49+ 1. Check file extension for explicit module type (.mjs, .cjs, .ts, .tsx, .mts)
50+ - TypeScript files always use ESM syntax regardless of package.json
51+ 2. Check package.json for explicit "type" field (only if explicitly set)
52+ 3. Analyze import/export statements in the file content
5053 4. Default to CommonJS if uncertain
5154
5255 Args:
@@ -57,58 +60,65 @@ def detect_module_system(project_root: Path, file_path: Path | None = None) -> s
5760 ModuleSystem constant (COMMONJS, ES_MODULE, or UNKNOWN).
5861
5962 """
60- # Strategy 1: Check package.json
63+ # Strategy 1: Check file extension first for explicit module type indicators
64+ # TypeScript files always use ESM syntax (import/export)
65+ if file_path :
66+ suffix = file_path .suffix .lower ()
67+ if suffix == ".mjs" :
68+ logger .debug ("Detected ES Module from .mjs extension" )
69+ return ModuleSystem .ES_MODULE
70+ if suffix == ".cjs" :
71+ logger .debug ("Detected CommonJS from .cjs extension" )
72+ return ModuleSystem .COMMONJS
73+ if suffix in (".ts" , ".tsx" , ".mts" ):
74+ # TypeScript always uses ESM syntax (import/export)
75+ # even if package.json doesn't have "type": "module"
76+ logger .debug ("Detected ES Module from TypeScript file extension" )
77+ return ModuleSystem .ES_MODULE
78+
79+ # Strategy 2: Check package.json for explicit type field
6180 package_json = project_root / "package.json"
6281 if package_json .exists ():
6382 try :
6483 with package_json .open ("r" ) as f :
6584 pkg = json .load (f )
66- pkg_type = pkg .get ("type" , "commonjs" )
85+ pkg_type = pkg .get ("type" ) # Don't default - only use if explicitly set
6786
6887 if pkg_type == "module" :
6988 logger .debug ("Detected ES Module from package.json type field" )
7089 return ModuleSystem .ES_MODULE
7190 if pkg_type == "commonjs" :
7291 logger .debug ("Detected CommonJS from package.json type field" )
7392 return ModuleSystem .COMMONJS
93+ # If type is not explicitly set, continue to file content analysis
7494
7595 except Exception as e :
7696 logger .warning ("Failed to parse package.json: %s" , e )
7797
78- # Strategy 2: Check file extension
79- if file_path :
80- suffix = file_path .suffix
81- if suffix == ".mjs" :
82- logger .debug ("Detected ES Module from .mjs extension" )
83- return ModuleSystem .ES_MODULE
84- if suffix == ".cjs" :
85- logger .debug ("Detected CommonJS from .cjs extension" )
86- return ModuleSystem .COMMONJS
87-
88- # Strategy 3: Analyze file content
89- if file_path .exists ():
90- try :
91- content = file_path .read_text ()
98+ # Strategy 3: Analyze file content for import/export patterns
99+ if file_path and file_path .exists ():
100+ try :
101+ content = file_path .read_text ()
92102
93- # Look for ES module syntax
94- has_import = "import " in content and "from " in content
95- has_export = "export " in content or "export default" in content or "export {" in content
103+ # Look for ES module syntax
104+ has_import = "import " in content and "from " in content
105+ has_export = "export " in content or "export default" in content or "export {" in content
96106
97- # Look for CommonJS syntax
98- has_require = "require(" in content
99- has_module_exports = "module.exports" in content or "exports." in content
107+ # Look for CommonJS syntax
108+ has_require = "require(" in content
109+ has_module_exports = "module.exports" in content or "exports." in content
100110
101- # Determine based on what we found
102- if (has_import or has_export ) and not (has_require or has_module_exports ):
103- logger .debug ("Detected ES Module from import/export statements" )
104- return ModuleSystem .ES_MODULE
111+ # Determine based on what we found
112+ if (has_import or has_export ) and not (has_require or has_module_exports ):
113+ logger .debug ("Detected ES Module from import/export statements" )
114+ return ModuleSystem .ES_MODULE
105115
106- if (has_require or has_module_exports ) and not (has_import or has_export ):
107- logger .debug ("Detected CommonJS from require/module.exports" )
108- return ModuleSystem .COMMONJS
116+ if (has_require or has_module_exports ) and not (has_import or has_export ):
117+ logger .debug ("Detected CommonJS from require/module.exports" )
118+ return ModuleSystem .COMMONJS
109119
110- except Exception as e :
111- logger .warning ("Failed to analyze file %s: %s" , file_path , e )
120+ except Exception as e :
121+ logger .warning ("Failed to analyze file %s: %s" , file_path , e )
112122
113123 # Default to CommonJS (more common and backward compatible)
114124 logger .debug ("Defaulting to CommonJS" )
@@ -212,6 +222,7 @@ def _convert_destructuring_to_imports(names_str: str) -> str:
212222
213223 Returns:
214224 Import names string with aliases using 'as' syntax
225+
215226 """
216227 # Split by commas and process each name
217228 parts = []
@@ -331,36 +342,89 @@ def replace_default(match) -> str:
331342 return default_import .sub (replace_default , code )
332343
333344
334- def ensure_module_system_compatibility (code : str , target_module_system : str ) -> str :
345+ def uses_ts_jest (project_root : Path ) -> bool :
346+ """Check if the project uses ts-jest for TypeScript transformation.
347+
348+ ts-jest handles module interoperability internally, allowing mixed
349+ CommonJS/ESM imports without explicit conversion.
350+
351+ Args:
352+ project_root: The project root directory.
353+
354+ Returns:
355+ True if ts-jest is being used, False otherwise.
356+
357+ """
358+ # Check for ts-jest in devDependencies or dependencies
359+ package_json = project_root / "package.json"
360+ if package_json .exists ():
361+ try :
362+ with package_json .open ("r" ) as f :
363+ pkg = json .load (f )
364+ dev_deps = pkg .get ("devDependencies" , {})
365+ deps = pkg .get ("dependencies" , {})
366+ if "ts-jest" in dev_deps or "ts-jest" in deps :
367+ return True
368+ except Exception as e :
369+ logger .debug (f"Failed to read package.json for ts-jest detection: { e } " ) # noqa: G004
370+
371+ # Also check for jest.config with ts-jest preset
372+ for config_file in ["jest.config.js" , "jest.config.cjs" , "jest.config.ts" , "jest.config.mjs" ]:
373+ config_path = project_root / config_file
374+ if config_path .exists ():
375+ try :
376+ content = config_path .read_text ()
377+ if "ts-jest" in content :
378+ return True
379+ except Exception as e :
380+ logger .debug (f"Failed to read { config_file } : { e } " ) # noqa: G004
381+
382+ return False
383+
384+
385+ def ensure_module_system_compatibility (code : str , target_module_system : str , project_root : Path | None = None ) -> str :
335386 """Ensure code uses the correct module system syntax.
336387
337- Detects the current module system in the code and converts if needed.
338- Handles mixed-style code (e.g., ESM imports with CommonJS require for npm packages).
388+ If the project uses ts-jest, no conversion is performed because ts-jest
389+ handles module interoperability internally. Otherwise, converts between
390+ CommonJS and ES Modules as needed.
339391
340392 Args:
341393 code: JavaScript code to check and potentially convert.
342394 target_module_system: Target ModuleSystem (COMMONJS or ES_MODULE).
395+ project_root: Project root directory for ts-jest detection.
343396
344397 Returns:
345- Code with correct module system syntax .
398+ Converted code, or unchanged if ts-jest handles interop .
346399
347400 """
401+ # If ts-jest is installed, skip conversion - it handles interop natively
402+ if is_typescript () and project_root and uses_ts_jest (project_root ):
403+ logger .debug (
404+ f"Skipping module system conversion (target was { target_module_system } ). " # noqa: G004
405+ "ts-jest handles interop natively."
406+ )
407+ return code
408+
348409 # Detect current module system in code
349410 has_require = "require(" in code
411+ has_module_exports = "module.exports" in code or "exports." in code
350412 has_import = "import " in code and "from " in code
413+ has_export = "export " in code
414+
415+ is_commonjs = has_require or has_module_exports
416+ is_esm = has_import or has_export
351417
352- if target_module_system == ModuleSystem .ES_MODULE :
353- # Convert any require() statements to imports for ESM projects
354- # This handles mixed code (ESM imports + CommonJS requires for npm packages)
355- if has_require :
356- logger .debug ("Converting CommonJS requires to ESM imports" )
357- return convert_commonjs_to_esm (code )
358- elif target_module_system == ModuleSystem .COMMONJS :
359- # Convert any import statements to requires for CommonJS projects
360- if has_import :
361- logger .debug ("Converting ESM imports to CommonJS requires" )
362- return convert_esm_to_commonjs (code )
418+ # Convert if needed
419+ if target_module_system == ModuleSystem .ES_MODULE and is_commonjs and not is_esm :
420+ logger .debug ("Converting CommonJS to ES Module syntax" )
421+ return convert_commonjs_to_esm (code )
363422
423+ if target_module_system == ModuleSystem .COMMONJS and is_esm and not is_commonjs :
424+ logger .debug ("Converting ES Module to CommonJS syntax" )
425+ return convert_esm_to_commonjs (code )
426+
427+ logger .debug ("No module system conversion needed" )
364428 return code
365429
366430
@@ -387,12 +451,8 @@ def ensure_vitest_imports(code: str, test_framework: str) -> str:
387451
388452 # Check if the code uses test functions that need to be imported
389453 test_globals = ["describe" , "test" , "it" , "expect" , "vi" , "beforeEach" , "afterEach" , "beforeAll" , "afterAll" ]
390- needs_import = any (f"{ global_name } (" in code or f"{ global_name } (" in code for global_name in test_globals )
391-
392- if not needs_import :
393- return code
394454
395- # Determine which globals are actually used in the code
455+ # Combine detection and collection into a single pass
396456 used_globals = [g for g in test_globals if f"{ g } (" in code or f"{ g } (" in code ]
397457 if not used_globals :
398458 return code
@@ -405,9 +465,14 @@ def ensure_vitest_imports(code: str, test_framework: str) -> str:
405465 insert_index = 0
406466 for i , line in enumerate (lines ):
407467 stripped = line .strip ()
408- if stripped and not stripped .startswith ("//" ) and not stripped .startswith ("/*" ) and not stripped .startswith ("*" ):
468+ if (
469+ stripped
470+ and not stripped .startswith ("//" )
471+ and not stripped .startswith ("/*" )
472+ and not stripped .startswith ("*" )
473+ ):
409474 # Check if this line is an import/require - insert after imports
410- if stripped .startswith ("import " ) or stripped . startswith ( "const " ) or stripped . startswith ( "let " ):
475+ if stripped .startswith (( "import " , "const " , "let " ) ):
411476 continue
412477 insert_index = i
413478 break
0 commit comments