@@ -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
0 commit comments