Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 30 additions & 10 deletions codeflash/languages/javascript/module_system.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,10 @@ def detect_module_system(project_root: Path, file_path: Path | None = None) -> s
"""Detect the module system used by a JavaScript/TypeScript project.

Detection strategy:
1. Check file extension for explicit module type (.mjs, .cjs, .ts, .tsx, .mts)
- TypeScript files always use ESM syntax regardless of package.json
1. Check file extension for explicit module type (.mjs, .cjs, .mts, .cts)
- .mjs and .mts always use ES Modules
- .cjs and .cts always use CommonJS
- .ts and .tsx defer to package.json "type" field
2. Check package.json for explicit "type" field (only if explicitly set)
3. Analyze import/export statements in the file content
4. Default to CommonJS if uncertain
Expand All @@ -61,40 +63,58 @@ def detect_module_system(project_root: Path, file_path: Path | None = None) -> s

"""
# Strategy 1: Check file extension first for explicit module type indicators
# TypeScript files always use ESM syntax (import/export)
if file_path:
suffix = file_path.suffix.lower()
# Explicit JavaScript module system extensions
if suffix == ".mjs":
logger.debug("Detected ES Module from .mjs extension")
return ModuleSystem.ES_MODULE
if suffix == ".cjs":
logger.debug("Detected CommonJS from .cjs extension")
return ModuleSystem.COMMONJS
if suffix in (".ts", ".tsx", ".mts"):
# TypeScript always uses ESM syntax (import/export)
# even if package.json doesn't have "type": "module"
logger.debug("Detected ES Module from TypeScript file extension")

# Explicit TypeScript module system extensions
if suffix == ".mts":
logger.debug("Detected ES Module from .mts extension")
return ModuleSystem.ES_MODULE
if suffix == ".cts":
logger.debug("Detected CommonJS from .cts extension")
return ModuleSystem.COMMONJS

# For .ts/.tsx files, defer to package.json "type" field
# TypeScript source uses ESM syntax (import/export), but the module system
# at runtime depends on package.json and tsconfig compilation settings

# Strategy 2: Check package.json for explicit type field
package_json = project_root / "package.json"
pkg_type_from_json = None
if package_json.exists():
try:
with package_json.open("r") as f:
pkg = json.load(f)
pkg_type = pkg.get("type") # Don't default - only use if explicitly set
pkg_type_from_json = pkg.get("type") # Don't default - only use if explicitly set

if pkg_type == "module":
if pkg_type_from_json == "module":
logger.debug("Detected ES Module from package.json type field")
return ModuleSystem.ES_MODULE
if pkg_type == "commonjs":
if pkg_type_from_json == "commonjs":
logger.debug("Detected CommonJS from package.json type field")
return ModuleSystem.COMMONJS
# If type is not explicitly set, continue to file content analysis

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

# For TypeScript files (.ts, .tsx), if package.json doesn't specify a type,
# default to CommonJS since that's the Node.js default.
# We skip file content analysis for TypeScript because TypeScript source
# always uses ESM syntax (import/export), but the actual module system
# depends on how TypeScript compiles and how Node.js loads the files.
if file_path and file_path.suffix.lower() in (".ts", ".tsx"):
if pkg_type_from_json is None:
logger.debug("TypeScript file without explicit package.json type field - defaulting to CommonJS")
return ModuleSystem.COMMONJS

# Strategy 3: Analyze file content for import/export patterns
if file_path and file_path.exists():
try:
Expand Down
39 changes: 21 additions & 18 deletions tests/test_languages/test_javascript_module_system.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,36 +57,39 @@ def test_detect_commonjs_from_cjs_extension(self):
assert result == ModuleSystem.COMMONJS

def test_detect_esm_from_typescript_extension(self):
"""Test detection of ES modules from TypeScript file extensions."""
"""Test detection of explicit TypeScript module extensions (.mts, .cts)."""
with tempfile.TemporaryDirectory() as tmpdir:
project_root = Path(tmpdir)

# Test .ts files
ts_file = project_root / "module.ts"
ts_file.write_text("export const foo = 'bar';")
assert detect_module_system(project_root, ts_file) == ModuleSystem.ES_MODULE

# Test .tsx files
tsx_file = project_root / "component.tsx"
tsx_file.write_text("export const Component = () => <div />;")
assert detect_module_system(project_root, tsx_file) == ModuleSystem.ES_MODULE

# Test .mts files
# .mts files should always be ESM
mts_file = project_root / "module.mts"
mts_file.write_text("export const foo = 'bar';")
assert detect_module_system(project_root, mts_file) == ModuleSystem.ES_MODULE

def test_typescript_ignores_package_json_commonjs(self):
"""Test that TypeScript files are detected as ESM even with CommonJS package.json."""
# .cts files should always be CommonJS
cts_file = project_root / "module.cts"
cts_file.write_text("export const foo = 'bar';")
assert detect_module_system(project_root, cts_file) == ModuleSystem.COMMONJS

# .ts/.tsx files without package.json should default to CommonJS
ts_file = project_root / "module.ts"
ts_file.write_text("export const foo = 'bar';")
assert detect_module_system(project_root, ts_file) == ModuleSystem.COMMONJS

def test_typescript_respects_package_json_type(self):
"""Test that TypeScript files respect package.json type field."""
with tempfile.TemporaryDirectory() as tmpdir:
project_root = Path(tmpdir)
# Create package.json with explicit commonjs type
ts_file = project_root / "module.ts"
ts_file.write_text("export const foo = 'bar';")

# With explicit "type": "commonjs"
package_json = project_root / "package.json"
package_json.write_text(json.dumps({"type": "commonjs"}))
assert detect_module_system(project_root, ts_file) == ModuleSystem.COMMONJS

# TypeScript file should still be detected as ESM
ts_file = project_root / "module.ts"
ts_file.write_text("export const foo = 'bar';")
# With explicit "type": "module"
package_json.write_text(json.dumps({"type": "module"}))
assert detect_module_system(project_root, ts_file) == ModuleSystem.ES_MODULE

def test_detect_esm_from_import_syntax(self):
Expand Down
160 changes: 160 additions & 0 deletions tests/test_languages/test_typescript_commonjs_module_detection.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
"""
Test for Issue #10: TypeScript files in CommonJS packages should not be detected as ESM

When a TypeScript file exists in a package without "type": "module" in package.json,
the module system should be detected as CommonJS, not ESM.
"""

import json
import tempfile
from pathlib import Path

import pytest

from codeflash.languages.javascript.module_system import ModuleSystem, detect_module_system


def test_typescript_file_in_commonjs_package():
"""TypeScript file in package without 'type' field should be CommonJS"""
with tempfile.TemporaryDirectory() as tmpdir:
project_root = Path(tmpdir)

# Create package.json without "type" field (defaults to CommonJS)
package_json = project_root / "package.json"
package_json.write_text(json.dumps({
"name": "test-package",
"version": "1.0.0"
}))

# Create TypeScript file with ESM source syntax
ts_file = project_root / "src" / "index.ts"
ts_file.parent.mkdir(parents=True, exist_ok=True)
ts_file.write_text("""
import { foo } from './foo';

export function bar() {
return foo();
}
""")

# Should detect as CommonJS, not ESM
result = detect_module_system(project_root, ts_file)

assert result == ModuleSystem.COMMONJS, (
f"Expected CommonJS for TypeScript file in package without 'type' field, got {result}"
)


def test_typescript_file_in_explicit_commonjs_package():
"""TypeScript file in package with 'type': 'commonjs' should be CommonJS"""
with tempfile.TemporaryDirectory() as tmpdir:
project_root = Path(tmpdir)

# Create package.json with explicit "type": "commonjs"
package_json = project_root / "package.json"
package_json.write_text(json.dumps({
"name": "test-package",
"version": "1.0.0",
"type": "commonjs"
}))

# Create TypeScript file
ts_file = project_root / "src" / "index.ts"
ts_file.parent.mkdir(parents=True, exist_ok=True)
ts_file.write_text("""
import { foo } from './foo';
export function bar() { return foo(); }
""")

# Should detect as CommonJS
result = detect_module_system(project_root, ts_file)

assert result == ModuleSystem.COMMONJS, (
f"Expected CommonJS for TypeScript file in explicit CommonJS package, got {result}"
)


def test_typescript_file_in_esm_package():
"""TypeScript file in package with 'type': 'module' should be ESM"""
with tempfile.TemporaryDirectory() as tmpdir:
project_root = Path(tmpdir)

# Create package.json with "type": "module"
package_json = project_root / "package.json"
package_json.write_text(json.dumps({
"name": "test-package",
"version": "1.0.0",
"type": "module"
}))

# Create TypeScript file
ts_file = project_root / "src" / "index.ts"
ts_file.parent.mkdir(parents=True, exist_ok=True)
ts_file.write_text("""
import { foo } from './foo';
export function bar() { return foo(); }
""")

# Should detect as ESM
result = detect_module_system(project_root, ts_file)

assert result == ModuleSystem.ES_MODULE, (
f"Expected ESM for TypeScript file in ESM package, got {result}"
)


def test_mts_file_always_esm():
""".mts files should always be ESM regardless of package.json"""
with tempfile.TemporaryDirectory() as tmpdir:
project_root = Path(tmpdir)

# Create package.json without "type" field
package_json = project_root / "package.json"
package_json.write_text(json.dumps({
"name": "test-package",
"version": "1.0.0"
}))

# Create .mts file (explicit ESM extension)
mts_file = project_root / "src" / "index.mts"
mts_file.parent.mkdir(parents=True, exist_ok=True)
mts_file.write_text("""
import { foo } from './foo';
export function bar() { return foo(); }
""")

# Should detect as ESM (explicit extension)
result = detect_module_system(project_root, mts_file)

assert result == ModuleSystem.ES_MODULE, (
f"Expected ESM for .mts file, got {result}"
)


def test_cts_file_always_commonjs():
""".cts files should always be CommonJS regardless of package.json"""
with tempfile.TemporaryDirectory() as tmpdir:
project_root = Path(tmpdir)

# Create package.json with "type": "module"
package_json = project_root / "package.json"
package_json.write_text(json.dumps({
"name": "test-package",
"version": "1.0.0",
"type": "module"
}))

# Create .cts file (explicit CommonJS extension)
cts_file = project_root / "src" / "index.cts"
cts_file.parent.mkdir(parents=True, exist_ok=True)
cts_file.write_text("""
import { foo } from './foo';
export function bar() { return foo(); }
""")

# Should detect as CommonJS (explicit extension)
result = detect_module_system(project_root, cts_file)

assert result == ModuleSystem.COMMONJS, (
f"Expected CommonJS for .cts file, got {result}"
)
Loading