99# Tree-sitter
1010import tree_sitter_python as tspython
1111import tree_sitter_javascript as tsjavascript
12+ try :
13+ import tree_sitter_typescript as tstypescript
14+ _HAS_TS_PARSER = True
15+ except ModuleNotFoundError :
16+ _HAS_TS_PARSER = False
1217from tree_sitter import Language , Parser
1318
1419from services .observability import logger , metrics
@@ -18,13 +23,24 @@ class DependencyAnalyzer:
1823 """Analyze code dependencies and build dependency graph"""
1924
2025 def __init__ (self ):
21- # Initialize parsers
26+ js_lang = Language (tsjavascript .language ())
27+ # Use proper TS parser if available, fall back to JS parser
28+ if _HAS_TS_PARSER :
29+ ts_lang = Language (tstypescript .language_typescript ())
30+ tsx_lang = Language (tstypescript .language_tsx ())
31+ else :
32+ logger .warning ("tree-sitter-typescript not installed, falling back to JS parser for TS/TSX" )
33+ ts_lang = js_lang
34+ tsx_lang = js_lang
35+
36+ self .has_ts_parser = _HAS_TS_PARSER
2237 self .parsers = {
2338 'python' : Parser (Language (tspython .language ())),
24- 'javascript' : Parser (Language (tsjavascript .language ())),
25- 'typescript' : Parser (Language (tsjavascript .language ())),
39+ 'javascript' : Parser (js_lang ),
40+ 'typescript' : Parser (ts_lang ),
41+ 'tsx' : Parser (tsx_lang ),
2642 }
27- logger .info ("DependencyAnalyzer initialized" )
43+ logger .info ("DependencyAnalyzer initialized" , ts_parser = self . has_ts_parser )
2844
2945 def _detect_language (self , file_path : str ) -> str :
3046 """Detect language from file extension"""
@@ -34,7 +50,7 @@ def _detect_language(self, file_path: str) -> str:
3450 '.js' : 'javascript' ,
3551 '.jsx' : 'javascript' ,
3652 '.ts' : 'typescript' ,
37- '.tsx' : 'typescript ' ,
53+ '.tsx' : 'tsx ' ,
3854 }
3955 return lang_map .get (ext , 'unknown' )
4056
@@ -122,10 +138,22 @@ def analyze_file_dependencies(self, file_path: str) -> Dict:
122138 logger .error ("Error analyzing file" , file_path = file_path , error = str (e ))
123139 return {"file" : str (file_path ), "imports" : [], "language" : language , "error" : str (e )}
124140
125- def build_dependency_graph (self , repo_path : str ) -> Dict :
126- """Build complete dependency graph for repository """
141+ def build_dependency_graph (self , repo_path : str , include_paths : List [ str ] = None ) -> Dict :
142+ """Build dependency graph. If include_paths set, only analyze those dirs. """
127143 repo_path = Path (repo_path )
128144
145+ # Sanitize include_paths from DB (could be corrupt jsonb)
146+ if include_paths :
147+ cleaned = []
148+ for p in include_paths :
149+ if not isinstance (p , str ):
150+ continue
151+ p = p .replace ('\\ ' , '/' ).strip ().strip ('/' )
152+ if not p or '..' in p .split ('/' ):
153+ continue
154+ cleaned .append (p )
155+ include_paths = cleaned or None
156+
129157 # Discover code files
130158 code_files = []
131159 extensions = {'.py' , '.js' , '.jsx' , '.ts' , '.tsx' }
@@ -136,8 +164,16 @@ def build_dependency_graph(self, repo_path: str) -> Dict:
136164 continue
137165 if any (skip in file_path .parts for skip in skip_dirs ):
138166 continue
139- if file_path .suffix in extensions :
140- code_files .append (file_path )
167+ if file_path .suffix not in extensions :
168+ continue
169+ if include_paths :
170+ rel_parts = file_path .relative_to (repo_path ).parts
171+ if not any (
172+ rel_parts [:len (Path (p ).parts )] == Path (p ).parts
173+ for p in include_paths
174+ ):
175+ continue
176+ code_files .append (file_path )
141177
142178 logger .info ("Building dependency graph" , file_count = len (code_files ))
143179
@@ -236,6 +272,10 @@ def _resolve_import_to_file(
236272 source_path = Path (source_file )
237273 source_dir = source_path .parent
238274
275+ # TS imports use .js extension but actual file is .ts on disk
276+ if import_path .endswith ('.js' ) or import_path .endswith ('.jsx' ):
277+ import_path = re .sub (r'\.(jsx?)$' , '' , import_path )
278+
239279 # Relative imports
240280 if import_path .startswith ('.' ):
241281 clean_import = import_path .lstrip ('./' )
@@ -250,7 +290,7 @@ def _resolve_import_to_file(
250290 else :
251291 potential_base = source_dir / clean_import
252292
253- extensions = ['' , '.ts' , '.tsx' , '.js' , '.jsx' , '.py' ]
293+ extensions = ['' , '.ts' , '.tsx' , '.d.ts' , '. js' , '.jsx' , '.py' ]
254294
255295 for ext in extensions :
256296 # Build the potential path
@@ -269,7 +309,7 @@ def _resolve_import_to_file(
269309 if not import_path .startswith ('.' ):
270310 module_path = import_path .replace ('.' , '/' )
271311
272- for ext in ['.py ' , '.js ' , '.ts ' ]:
312+ for ext in ['' , '.ts ' , '.tsx ' , '.d.ts' , '.js' , '.jsx' , '.py ' ]:
273313 test_path = module_path + ext
274314 if test_path in internal_files :
275315 return test_path
0 commit comments