Skip to content

Commit 5c3076a

Browse files
Merge monorepo fixes and Jest 30 support
Merged: - fix/jest-xml-path-resolution-monorepo (path resolution, monorepo detection) - fix/js-jest30-loop-runner (Jest 30 support with TestRunner class) Using Jest 30 PR's loop-runner.js which is cleaner and more maintainable. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
3 parents 3a3a187 + 0eb29bb + f337b40 commit 5c3076a

25 files changed

Lines changed: 1232 additions & 337 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/cli_cmds/cli.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -359,11 +359,13 @@ def _handle_show_config() -> None:
359359
detected = detect_project(project_root)
360360

361361
# Check if config exists or is auto-detected
362-
config_exists, _ = has_existing_config(project_root)
362+
config_exists, config_file = has_existing_config(project_root)
363363
status = "Saved config" if config_exists else "Auto-detected (not saved)"
364364

365365
console.print()
366366
console.print(f"[bold]Codeflash Configuration[/bold] ({status})")
367+
if config_exists and config_file:
368+
console.print(f"[dim]Config file: {project_root / config_file}[/dim]")
367369
console.print()
368370

369371
table = Table(show_header=True, header_style="bold cyan")

codeflash/code_utils/config_js.py

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
from pathlib import Path
77
from typing import Any
88

9+
from codeflash.setup.detector import is_build_output_dir
10+
911
PACKAGE_JSON_CACHE: dict[Path, Path] = {}
1012
PACKAGE_JSON_DATA_CACHE: dict[Path, dict[str, Any]] = {}
1113

@@ -50,12 +52,15 @@ def detect_module_root(project_root: Path, package_data: dict[str, Any]) -> str:
5052
"""Detect module root from package.json fields or directory conventions.
5153
5254
Detection order:
53-
1. package.json "exports" field (extract directory from main export)
54-
2. package.json "module" field (ESM entry point)
55-
3. package.json "main" field (CJS entry point)
56-
4. "src/" directory if it exists
55+
1. src/, lib/, source/ directories (common source directories)
56+
2. package.json "exports" field (if not in build output directory)
57+
3. package.json "module" field (ESM, if not in build output directory)
58+
4. package.json "main" field (CJS, if not in build output directory)
5759
5. Fall back to "." (project root)
5860
61+
Build output directories (build/, dist/, out/) are skipped since they contain
62+
compiled code, not source files.
63+
5964
Args:
6065
project_root: Root directory of the project.
6166
package_data: Parsed package.json data.
@@ -64,6 +69,11 @@ def detect_module_root(project_root: Path, package_data: dict[str, Any]) -> str:
6469
Detected module root path (relative to project root).
6570
6671
"""
72+
# Check for common source directories first - these are always preferred
73+
for src_dir in ["src", "lib", "source"]:
74+
if (project_root / src_dir).is_dir():
75+
return src_dir
76+
6777
# Check exports field (modern Node.js)
6878
exports = package_data.get("exports")
6979
if exports:
@@ -80,27 +90,38 @@ def detect_module_root(project_root: Path, package_data: dict[str, Any]) -> str:
8090

8191
if entry_path and isinstance(entry_path, str):
8292
parent = Path(entry_path).parent
83-
if parent != Path() and (project_root / parent).is_dir():
93+
if (
94+
parent != Path()
95+
and parent.as_posix() != "."
96+
and (project_root / parent).is_dir()
97+
and not is_build_output_dir(parent)
98+
):
8499
return parent.as_posix()
85100

86101
# Check module field (ESM)
87102
module_field = package_data.get("module")
88103
if module_field and isinstance(module_field, str):
89104
parent = Path(module_field).parent
90-
if parent != Path() and (project_root / parent).is_dir():
105+
if (
106+
parent != Path()
107+
and parent.as_posix() != "."
108+
and (project_root / parent).is_dir()
109+
and not is_build_output_dir(parent)
110+
):
91111
return parent.as_posix()
92112

93113
# Check main field (CJS)
94114
main_field = package_data.get("main")
95115
if main_field and isinstance(main_field, str):
96116
parent = Path(main_field).parent
97-
if parent != Path() and (project_root / parent).is_dir():
117+
if (
118+
parent != Path()
119+
and parent.as_posix() != "."
120+
and (project_root / parent).is_dir()
121+
and not is_build_output_dir(parent)
122+
):
98123
return parent.as_posix()
99124

100-
# Check for src/ directory convention
101-
if (project_root / "src").is_dir():
102-
return "src"
103-
104125
# Default to project root
105126
return "."
106127

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: 121 additions & 56 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

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

Comments
 (0)