Skip to content

Commit acd4028

Browse files
authored
Merge pull request #1289 from codeflash-ai/fix/js-module-detection
fix: JS/TS module system and test framework detection
2 parents c76cfaa + a4a61e9 commit acd4028

File tree

3 files changed

+84
-42
lines changed

3 files changed

+84
-42
lines changed

codeflash/languages/javascript/module_system.py

Lines changed: 50 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,10 @@ def detect_module_system(project_root: Path, file_path: Path | None = None) -> s
4444
"""Detect the module system used by a JavaScript/TypeScript project.
4545
4646
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
47+
1. Check file extension for explicit module type (.mjs, .cjs, .ts, .tsx, .mts)
48+
- TypeScript files always use ESM syntax regardless of package.json
49+
2. Check package.json for explicit "type" field (only if explicitly set)
50+
3. Analyze import/export statements in the file content
5051
4. Default to CommonJS if uncertain
5152
5253
Args:
@@ -57,58 +58,65 @@ def detect_module_system(project_root: Path, file_path: Path | None = None) -> s
5758
ModuleSystem constant (COMMONJS, ES_MODULE, or UNKNOWN).
5859
5960
"""
60-
# Strategy 1: Check package.json
61+
# Strategy 1: Check file extension first for explicit module type indicators
62+
# TypeScript files always use ESM syntax (import/export)
63+
if file_path:
64+
suffix = file_path.suffix.lower()
65+
if suffix == ".mjs":
66+
logger.debug("Detected ES Module from .mjs extension")
67+
return ModuleSystem.ES_MODULE
68+
if suffix == ".cjs":
69+
logger.debug("Detected CommonJS from .cjs extension")
70+
return ModuleSystem.COMMONJS
71+
if suffix in (".ts", ".tsx", ".mts"):
72+
# TypeScript always uses ESM syntax (import/export)
73+
# even if package.json doesn't have "type": "module"
74+
logger.debug("Detected ES Module from TypeScript file extension")
75+
return ModuleSystem.ES_MODULE
76+
77+
# Strategy 2: Check package.json for explicit type field
6178
package_json = project_root / "package.json"
6279
if package_json.exists():
6380
try:
6481
with package_json.open("r") as f:
6582
pkg = json.load(f)
66-
pkg_type = pkg.get("type", "commonjs")
83+
pkg_type = pkg.get("type") # Don't default - only use if explicitly set
6784

6885
if pkg_type == "module":
6986
logger.debug("Detected ES Module from package.json type field")
7087
return ModuleSystem.ES_MODULE
7188
if pkg_type == "commonjs":
7289
logger.debug("Detected CommonJS from package.json type field")
7390
return ModuleSystem.COMMONJS
91+
# If type is not explicitly set, continue to file content analysis
7492

7593
except Exception as e:
7694
logger.warning("Failed to parse package.json: %s", e)
7795

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
96+
# Strategy 3: Analyze file content for import/export patterns
97+
if file_path and file_path.exists():
98+
try:
99+
content = file_path.read_text()
87100

88-
# Strategy 3: Analyze file content
89-
if file_path.exists():
90-
try:
91-
content = file_path.read_text()
101+
# Look for ES module syntax
102+
has_import = "import " in content and "from " in content
103+
has_export = "export " in content or "export default" in content or "export {" in content
92104

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
105+
# Look for CommonJS syntax
106+
has_require = "require(" in content
107+
has_module_exports = "module.exports" in content or "exports." in content
96108

97-
# Look for CommonJS syntax
98-
has_require = "require(" in content
99-
has_module_exports = "module.exports" in content or "exports." in content
109+
# Determine based on what we found
110+
if (has_import or has_export) and not (has_require or has_module_exports):
111+
logger.debug("Detected ES Module from import/export statements")
112+
return ModuleSystem.ES_MODULE
100113

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
114+
if (has_require or has_module_exports) and not (has_import or has_export):
115+
logger.debug("Detected CommonJS from require/module.exports")
116+
return ModuleSystem.COMMONJS
105117

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
109-
110-
except Exception as e:
111-
logger.warning("Failed to analyze file %s: %s", file_path, e)
118+
except Exception as e:
119+
logger.warning("Failed to analyze file %s: %s", file_path, e)
112120

113121
# Default to CommonJS (more common and backward compatible)
114122
logger.debug("Defaulting to CommonJS")
@@ -355,12 +363,8 @@ def ensure_vitest_imports(code: str, test_framework: str) -> str:
355363

356364
# Check if the code uses test functions that need to be imported
357365
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
362366

363-
# Determine which globals are actually used in the code
367+
# Combine detection and collection into a single pass
364368
used_globals = [g for g in test_globals if f"{g}(" in code or f"{g} (" in code]
365369
if not used_globals:
366370
return code
@@ -373,9 +377,14 @@ def ensure_vitest_imports(code: str, test_framework: str) -> str:
373377
insert_index = 0
374378
for i, line in enumerate(lines):
375379
stripped = line.strip()
376-
if stripped and not stripped.startswith("//") and not stripped.startswith("/*") and not stripped.startswith("*"):
380+
if (
381+
stripped
382+
and not stripped.startswith("//")
383+
and not stripped.startswith("/*")
384+
and not stripped.startswith("*")
385+
):
377386
# Check if this line is an import/require - insert after imports
378-
if stripped.startswith("import ") or stripped.startswith("const ") or stripped.startswith("let "):
387+
if stripped.startswith(("import ", "const ", "let ")):
379388
continue
380389
insert_index = i
381390
break

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():

tests/test_languages/test_javascript_module_system.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,39 @@ def test_detect_commonjs_from_cjs_extension(self):
5151
result = detect_module_system(project_root, file_path)
5252
assert result == ModuleSystem.COMMONJS
5353

54+
def test_detect_esm_from_typescript_extension(self):
55+
"""Test detection of ES modules from TypeScript file extensions."""
56+
with tempfile.TemporaryDirectory() as tmpdir:
57+
project_root = Path(tmpdir)
58+
59+
# Test .ts files
60+
ts_file = project_root / "module.ts"
61+
ts_file.write_text("export const foo = 'bar';")
62+
assert detect_module_system(project_root, ts_file) == ModuleSystem.ES_MODULE
63+
64+
# Test .tsx files
65+
tsx_file = project_root / "component.tsx"
66+
tsx_file.write_text("export const Component = () => <div />;")
67+
assert detect_module_system(project_root, tsx_file) == ModuleSystem.ES_MODULE
68+
69+
# Test .mts files
70+
mts_file = project_root / "module.mts"
71+
mts_file.write_text("export const foo = 'bar';")
72+
assert detect_module_system(project_root, mts_file) == ModuleSystem.ES_MODULE
73+
74+
def test_typescript_ignores_package_json_commonjs(self):
75+
"""Test that TypeScript files are detected as ESM even with CommonJS package.json."""
76+
with tempfile.TemporaryDirectory() as tmpdir:
77+
project_root = Path(tmpdir)
78+
# Create package.json with explicit commonjs type
79+
package_json = project_root / "package.json"
80+
package_json.write_text(json.dumps({"type": "commonjs"}))
81+
82+
# TypeScript file should still be detected as ESM
83+
ts_file = project_root / "module.ts"
84+
ts_file.write_text("export const foo = 'bar';")
85+
assert detect_module_system(project_root, ts_file) == ModuleSystem.ES_MODULE
86+
5487
def test_detect_esm_from_import_syntax(self):
5588
"""Test detection of ES modules from import syntax."""
5689
with tempfile.TemporaryDirectory() as tmpdir:

0 commit comments

Comments
 (0)