Skip to content

Commit 09e5cfa

Browse files
authored
Merge branch 'main' into fix/dynamic-env-var-reading
2 parents 66fadf7 + 7bc7032 commit 09e5cfa

File tree

9 files changed

+679
-95
lines changed

9 files changed

+679
-95
lines changed

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: 157 additions & 61 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")
@@ -199,11 +209,41 @@ def add_js_extension(module_path: str) -> str:
199209
return module_path
200210

201211

212+
def _convert_destructuring_to_imports(names_str: str) -> str:
213+
"""Convert destructuring aliases to import aliases.
214+
215+
Converts:
216+
a, b -> a, b
217+
a: aliasA -> a as aliasA
218+
a, b: aliasB -> a, b as aliasB
219+
220+
Args:
221+
names_str: The destructuring pattern string (e.g., "a, b: aliasB")
222+
223+
Returns:
224+
Import names string with aliases using 'as' syntax
225+
"""
226+
# Split by commas and process each name
227+
parts = []
228+
for name in names_str.split(","):
229+
name = name.strip()
230+
if ":" in name:
231+
# Convert destructuring alias to import alias
232+
# "a: aliasA" -> "a as aliasA"
233+
original, alias = name.split(":", 1)
234+
parts.append(f"{original.strip()} as {alias.strip()}")
235+
else:
236+
parts.append(name)
237+
return ", ".join(parts)
238+
239+
202240
# Replace destructured requires with named imports
203241
def replace_destructured(match: re.Match) -> str:
204242
names = match.group(2).strip()
205243
module_path = add_js_extension(match.group(3))
206-
return f"import {{ {names} }} from '{module_path}';"
244+
# Convert destructuring aliases (a: b) to import aliases (a as b)
245+
converted_names = _convert_destructuring_to_imports(names)
246+
return f"import {{ {converted_names} }} from '{module_path}';"
207247

208248

209249
# Replace property access requires with named imports with alias
@@ -234,12 +274,14 @@ def convert_commonjs_to_esm(code: str) -> str:
234274
"""Convert CommonJS require statements to ES Module imports.
235275
236276
Converts:
237-
const { foo, bar } = require('./module'); -> import { foo, bar } from './module';
238-
const foo = require('./module'); -> import foo from './module';
239-
const foo = require('./module').default; -> import foo from './module';
240-
const foo = require('./module').bar; -> import { bar as foo } from './module';
277+
const { foo, bar } = require('./module'); -> import { foo, bar } from './module';
278+
const { foo: alias } = require('./module'); -> import { foo as alias } from './module';
279+
const foo = require('./module'); -> import foo from './module';
280+
const foo = require('./module').default; -> import foo from './module';
281+
const foo = require('./module').bar; -> import { bar as foo } from './module';
241282
242283
Special handling:
284+
- Destructuring aliases (a: b) are converted to import aliases (a as b)
243285
- Local codeflash helper (./codeflash-jest-helper) is converted to npm package codeflash
244286
because the local helper uses CommonJS exports which don't work in ESM projects
245287
@@ -299,36 +341,89 @@ def replace_default(match) -> str:
299341
return default_import.sub(replace_default, code)
300342

301343

302-
def ensure_module_system_compatibility(code: str, target_module_system: str) -> str:
344+
def uses_ts_jest(project_root: Path) -> bool:
345+
"""Check if the project uses ts-jest for TypeScript transformation.
346+
347+
ts-jest handles module interoperability internally, allowing mixed
348+
CommonJS/ESM imports without explicit conversion.
349+
350+
Args:
351+
project_root: The project root directory.
352+
353+
Returns:
354+
True if ts-jest is being used, False otherwise.
355+
356+
"""
357+
# Check for ts-jest in devDependencies or dependencies
358+
package_json = project_root / "package.json"
359+
if package_json.exists():
360+
try:
361+
with package_json.open("r") as f:
362+
pkg = json.load(f)
363+
dev_deps = pkg.get("devDependencies", {})
364+
deps = pkg.get("dependencies", {})
365+
if "ts-jest" in dev_deps or "ts-jest" in deps:
366+
return True
367+
except Exception as e:
368+
logger.debug(f"Failed to read package.json for ts-jest detection: {e}") # noqa: G004
369+
370+
# Also check for jest.config with ts-jest preset
371+
for config_file in ["jest.config.js", "jest.config.cjs", "jest.config.ts", "jest.config.mjs"]:
372+
config_path = project_root / config_file
373+
if config_path.exists():
374+
try:
375+
content = config_path.read_text()
376+
if "ts-jest" in content:
377+
return True
378+
except Exception as e:
379+
logger.debug(f"Failed to read {config_file}: {e}") # noqa: G004
380+
381+
return False
382+
383+
384+
def ensure_module_system_compatibility(code: str, target_module_system: str, project_root: Path | None = None) -> str:
303385
"""Ensure code uses the correct module system syntax.
304386
305-
Detects the current module system in the code and converts if needed.
306-
Handles mixed-style code (e.g., ESM imports with CommonJS require for npm packages).
387+
If the project uses ts-jest, no conversion is performed because ts-jest
388+
handles module interoperability internally. Otherwise, converts between
389+
CommonJS and ES Modules as needed.
307390
308391
Args:
309392
code: JavaScript code to check and potentially convert.
310393
target_module_system: Target ModuleSystem (COMMONJS or ES_MODULE).
394+
project_root: Project root directory for ts-jest detection.
311395
312396
Returns:
313-
Code with correct module system syntax.
397+
Converted code, or unchanged if ts-jest handles interop.
314398
315399
"""
400+
# If ts-jest is installed, skip conversion - it handles interop natively
401+
if is_typescript() and project_root and uses_ts_jest(project_root):
402+
logger.debug(
403+
f"Skipping module system conversion (target was {target_module_system}). " # noqa: G004
404+
"ts-jest handles interop natively."
405+
)
406+
return code
407+
316408
# Detect current module system in code
317409
has_require = "require(" in code
410+
has_module_exports = "module.exports" in code or "exports." in code
318411
has_import = "import " in code and "from " in code
412+
has_export = "export " in code
413+
414+
is_commonjs = has_require or has_module_exports
415+
is_esm = has_import or has_export
319416

320-
if target_module_system == ModuleSystem.ES_MODULE:
321-
# Convert any require() statements to imports for ESM projects
322-
# This handles mixed code (ESM imports + CommonJS requires for npm packages)
323-
if has_require:
324-
logger.debug("Converting CommonJS requires to ESM imports")
325-
return convert_commonjs_to_esm(code)
326-
elif target_module_system == ModuleSystem.COMMONJS:
327-
# Convert any import statements to requires for CommonJS projects
328-
if has_import:
329-
logger.debug("Converting ESM imports to CommonJS requires")
330-
return convert_esm_to_commonjs(code)
417+
# Convert if needed
418+
if target_module_system == ModuleSystem.ES_MODULE and is_commonjs and not is_esm:
419+
logger.debug("Converting CommonJS to ES Module syntax")
420+
return convert_commonjs_to_esm(code)
331421

422+
if target_module_system == ModuleSystem.COMMONJS and is_esm and not is_commonjs:
423+
logger.debug("Converting ES Module to CommonJS syntax")
424+
return convert_esm_to_commonjs(code)
425+
426+
logger.debug("No module system conversion needed")
332427
return code
333428

334429

@@ -355,12 +450,8 @@ def ensure_vitest_imports(code: str, test_framework: str) -> str:
355450

356451
# Check if the code uses test functions that need to be imported
357452
test_globals = ["describe", "test", "it", "expect", "vi", "beforeEach", "afterEach", "beforeAll", "afterAll"]
358-
needs_import = any(f"{global_name}(" in code or f"{global_name} (" in code for global_name in test_globals)
359-
360-
if not needs_import:
361-
return code
362453

363-
# Determine which globals are actually used in the code
454+
# Combine detection and collection into a single pass
364455
used_globals = [g for g in test_globals if f"{g}(" in code or f"{g} (" in code]
365456
if not used_globals:
366457
return code
@@ -373,9 +464,14 @@ def ensure_vitest_imports(code: str, test_framework: str) -> str:
373464
insert_index = 0
374465
for i, line in enumerate(lines):
375466
stripped = line.strip()
376-
if stripped and not stripped.startswith("//") and not stripped.startswith("/*") and not stripped.startswith("*"):
467+
if (
468+
stripped
469+
and not stripped.startswith("//")
470+
and not stripped.startswith("/*")
471+
and not stripped.startswith("*")
472+
):
377473
# Check if this line is an import/require - insert after imports
378-
if stripped.startswith("import ") or stripped.startswith("const ") or stripped.startswith("let "):
474+
if stripped.startswith(("import ", "const ", "let ")):
379475
continue
380476
insert_index = i
381477
break

codeflash/languages/treesitter_utils.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,11 @@ def _walk_tree_for_functions(
258258
if func_info.is_arrow and not include_arrow_functions:
259259
should_include = False
260260

261+
# Skip arrow functions that are object properties (e.g., { foo: () => {} })
262+
# These are not standalone functions - they're values in object literals
263+
if func_info.is_arrow and node.parent and node.parent.type == "pair":
264+
should_include = False
265+
261266
if should_include:
262267
functions.append(func_info)
263268

codeflash/optimization/function_optimizer.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -566,7 +566,7 @@ def generate_and_instrument_tests(
566566

567567
# Normalize codeflash imports in JS/TS tests to use npm package
568568
if not is_python():
569-
module_system = detect_module_system(self.project_root)
569+
module_system = detect_module_system(self.project_root, self.function_to_optimize.file_path)
570570
if module_system == "esm":
571571
generated_tests = inject_test_globals(generated_tests)
572572
if is_typescript():

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)

0 commit comments

Comments
 (0)