1414
1515from codeflash .code_utils .git_utils import git_root_dir , mirror_path
1616from codeflash .discovery .functions_to_optimize import FunctionToOptimize
17- from codeflash .languages .base import CodeContext , FunctionFilterCriteria , HelperFunction , Language , TestInfo , TestResult
17+ from codeflash .languages .base import (
18+ CodeContext ,
19+ FunctionFilterCriteria ,
20+ HelperFunction ,
21+ Language ,
22+ SetupError ,
23+ TestInfo ,
24+ TestResult ,
25+ )
1826from codeflash .languages .javascript .treesitter import TreeSitterAnalyzer , TreeSitterLanguage , get_analyzer_for_file
1927from codeflash .languages .registry import register_language
2028from codeflash .models .models import FunctionParent
@@ -1950,11 +1958,13 @@ def prepare_module(
19501958
19511959 return prepare_javascript_module (module_code , module_path )
19521960
1953- def setup_test_config (self , test_cfg : TestConfig , file_path : Path , current_worktree : Path | None ) -> None :
1961+ def setup_test_config (self , test_cfg : TestConfig , file_path : Path , current_worktree : Path | None ) -> bool :
19541962 from codeflash .languages .javascript .optimizer import verify_js_requirements
19551963 from codeflash .languages .javascript .test_runner import find_node_project_root
19561964
19571965 test_cfg .js_project_root = find_node_project_root (file_path )
1966+ if test_cfg .js_project_root is None :
1967+ return False
19581968 if current_worktree is not None :
19591969 original_js_root = git_root_dir ()
19601970 worktree_node_modules = test_cfg .js_project_root / "node_modules"
@@ -1970,7 +1980,11 @@ def setup_test_config(self, test_cfg: TestConfig, file_path: Path, current_workt
19701980 original_root_node_modules = original_js_root / "node_modules"
19711981 if original_root_node_modules .exists () and not worktree_root_node_modules .exists ():
19721982 worktree_root_node_modules .symlink_to (original_root_node_modules )
1973- verify_js_requirements (test_cfg )
1983+ setup_errors = verify_js_requirements (test_cfg )
1984+ if any (e .should_abort for e in setup_errors ):
1985+ return False
1986+
1987+ return True
19741988
19751989 def adjust_test_config_for_discovery (self , test_cfg : TestConfig ) -> None :
19761990 test_cfg .tests_project_rootdir = test_cfg .tests_root
@@ -2164,8 +2178,9 @@ def find_test_root(self, project_root: Path) -> Path | None:
21642178 def get_module_path (self , source_file : Path , project_root : Path , tests_root : Path | None = None ) -> str :
21652179 """Get the module path for importing a JavaScript source file from tests.
21662180
2167- For JavaScript, this returns a relative path from the tests directory to the source file
2168- (e.g., '../fibonacci' for source at /project/fibonacci.js and tests at /project/tests/).
2181+ For JavaScript/TypeScript, this returns a relative path from the tests directory to
2182+ the source file. For ESM projects or TypeScript, the path includes a .js extension
2183+ (TypeScript convention). For CommonJS, no extension is added.
21692184
21702185 Args:
21712186 source_file: Path to the source file.
@@ -2179,13 +2194,15 @@ def get_module_path(self, source_file: Path, project_root: Path, tests_root: Pat
21792194 import os
21802195
21812196 from codeflash .cli_cmds .console import logger
2197+ from codeflash .languages .javascript .module_system import ModuleSystem , detect_module_system
21822198
21832199 if tests_root is None :
21842200 tests_root = self .find_test_root (project_root ) or project_root
21852201
21862202 try :
21872203 # Resolve both paths to absolute to ensure consistent relative path calculation
2188- source_file_abs = source_file .resolve ().with_suffix ("" )
2204+ # Note: Don't remove extension yet - we'll decide based on module system
2205+ source_file_abs = source_file .resolve ()
21892206 tests_root_abs = tests_root .resolve ()
21902207
21912208 # Find the project root using language support
@@ -2205,18 +2222,42 @@ def get_module_path(self, source_file: Path, project_root: Path, tests_root: Pat
22052222 if not tests_root_abs .exists ():
22062223 tests_root_abs = project_root_from_lang
22072224
2225+ # Detect module system to determine if we need to add .js extension
2226+ module_system = detect_module_system (project_root , source_file )
2227+
2228+ # Remove source file extension first
2229+ source_without_ext = source_file_abs .with_suffix ("" )
2230+
22082231 # Use os.path.relpath to compute relative path from tests_root to source file
2209- rel_path = os .path .relpath (str (source_file_abs ), str (tests_root_abs ))
2210- logger .debug (
2211- f"!lsp|Module path: source={ source_file_abs } , tests_root={ tests_root_abs } , rel_path={ rel_path } "
2212- )
2232+ rel_path = os .path .relpath (str (source_without_ext ), str (tests_root_abs ))
2233+
2234+ # For ESM, add .js extension (TypeScript convention)
2235+ # TypeScript requires imports to reference the OUTPUT file extension (.js),
2236+ # even when the source file is .ts. This is required for Node.js ESM resolution.
2237+ if module_system == ModuleSystem .ES_MODULE :
2238+ rel_path = rel_path + ".js"
2239+ logger .debug (
2240+ f"!lsp|Module path (ESM): source={ source_file_abs } , tests_root={ tests_root_abs } , "
2241+ f"rel_path={ rel_path } (added .js for ESM)"
2242+ )
2243+ else :
2244+ logger .debug (
2245+ f"!lsp|Module path (CommonJS): source={ source_file_abs } , tests_root={ tests_root_abs } , "
2246+ f"rel_path={ rel_path } "
2247+ )
2248+
22132249 return rel_path
22142250 except ValueError :
22152251 # Fallback if paths are on different drives (Windows)
22162252 rel_path = source_file .relative_to (project_root )
2217- return "../" + rel_path .with_suffix ("" ).as_posix ()
2218-
2219- def verify_requirements (self , project_root : Path , test_framework : str = "jest" ) -> tuple [bool , list [str ]]:
2253+ # For fallback, also check module system
2254+ module_system = detect_module_system (project_root , source_file )
2255+ path_without_ext = "../" + rel_path .with_suffix ("" ).as_posix ()
2256+ if module_system == ModuleSystem .ES_MODULE :
2257+ return path_without_ext + ".js"
2258+ return path_without_ext
2259+
2260+ def verify_requirements (self , project_root : Path , test_framework : str = "jest" ) -> tuple [bool , list [SetupError ]]:
22202261 """Verify that all JavaScript requirements are met.
22212262
22222263 Checks for:
@@ -2236,27 +2277,40 @@ def verify_requirements(self, project_root: Path, test_framework: str = "jest")
22362277 Tuple of (success, list of error messages).
22372278
22382279 """
2239- errors : list [str ] = []
2280+ errors : list [SetupError ] = []
22402281
22412282 # Check Node.js
22422283 try :
22432284 result = subprocess .run (["node" , "--version" ], check = False , capture_output = True , text = True , timeout = 10 )
22442285 if result .returncode != 0 :
2245- errors .append ("Node.js is not installed. Please install Node.js 18+ from https://nodejs.org/" )
2286+ errors .append (
2287+ SetupError (
2288+ "Node.js is not installed. Please install Node.js 18+ from https://nodejs.org/" ,
2289+ should_abort = True ,
2290+ )
2291+ )
22462292 except FileNotFoundError :
2247- errors .append ("Node.js is not installed. Please install Node.js 18+ from https://nodejs.org/" )
2293+ errors .append (
2294+ SetupError (
2295+ "Node.js is not installed. Please install Node.js 18+ from https://nodejs.org/" , should_abort = True
2296+ )
2297+ )
22482298 except Exception as e :
2249- errors .append (f"Failed to check Node.js: { e } " )
2299+ errors .append (SetupError ( f"Failed to check Node.js: { e } " , should_abort = True ) )
22502300
22512301 # Check npm
22522302 try :
22532303 result = subprocess .run (["npm" , "--version" ], check = False , capture_output = True , text = True , timeout = 10 )
22542304 if result .returncode != 0 :
2255- errors .append ("npm is not available. Please ensure npm is installed with Node.js." )
2305+ errors .append (
2306+ SetupError ("npm is not available. Please ensure npm is installed with Node.js." , should_abort = True )
2307+ )
22562308 except FileNotFoundError :
2257- errors .append ("npm is not available. Please ensure npm is installed with Node.js." )
2309+ errors .append (
2310+ SetupError ("npm is not available. Please ensure npm is installed with Node.js." , should_abort = True )
2311+ )
22582312 except Exception as e :
2259- errors .append (f"Failed to check npm: { e } " )
2313+ errors .append (SetupError ( f"Failed to check npm: { e } " , should_abort = True ) )
22602314
22612315 # Check test framework is installed (with monorepo support)
22622316 # Uses find_node_modules_with_package which searches up the directory tree
@@ -2270,12 +2324,17 @@ def verify_requirements(self, project_root: Path, test_framework: str = "jest")
22702324 local_node_modules = project_root / "node_modules"
22712325 if not local_node_modules .exists ():
22722326 errors .append (
2273- f"node_modules not found in { project_root } . Please run 'npm install' to install dependencies."
2327+ SetupError (
2328+ f"node_modules not found in { project_root } . Please run 'npm install' to install dependencies." ,
2329+ should_abort = True ,
2330+ )
22742331 )
22752332 else :
22762333 errors .append (
2277- f"{ test_framework } is not installed. "
2278- f"Please run 'npm install --save-dev { test_framework } ' to install it."
2334+ SetupError (
2335+ f"{ test_framework } is not installed. Please run 'npm install --save-dev { test_framework } ' to install it." ,
2336+ should_abort = True ,
2337+ )
22792338 )
22802339
22812340 return len (errors ) == 0 , errors
0 commit comments