@@ -94,6 +94,7 @@ class ModuleInfo:
9494 constants : List [Tuple [str , str ]] = field (default_factory = list )
9595 is_init : bool = False
9696 package : str = "python"
97+ source_file : str = "" # For GitHub source links
9798
9899 @property
99100 def display_name (self ) -> str :
@@ -812,6 +813,89 @@ def extract_function_calls(node) -> List[str]:
812813 return visitor .get_unique_calls ()
813814
814815
816+ def extract_ts_function_calls (source_code : str ) -> List [str ]:
817+ """Extract function calls from TypeScript source code using regex.
818+
819+ Args:
820+ source_code: TypeScript function body source code
821+
822+ Returns:
823+ List of unique function names called
824+ """
825+ # Match function calls: functionName( or obj.method(
826+ call_pattern = r'\b([a-zA-Z_][a-zA-Z0-9_]*)\s*\('
827+ method_pattern = r'\.([a-zA-Z_][a-zA-Z0-9_]*)\s*\('
828+
829+ calls = re .findall (call_pattern , source_code )
830+ methods = re .findall (method_pattern , source_code )
831+
832+ # Combine and filter
833+ excluded = {
834+ 'if' , 'for' , 'while' , 'switch' , 'catch' , 'function' , 'class' , 'new' ,
835+ 'return' , 'throw' , 'typeof' , 'instanceof' , 'constructor' , 'async' , 'await' ,
836+ 'console' , 'log' , 'error' , 'warn' , 'info' , 'debug' ,
837+ 'push' , 'pop' , 'shift' , 'unshift' , 'slice' , 'splice' , 'map' , 'filter' ,
838+ 'reduce' , 'forEach' , 'find' , 'some' , 'every' , 'includes' , 'indexOf' ,
839+ 'toString' , 'valueOf' , 'hasOwnProperty' , 'length' , 'split' , 'join' ,
840+ 'trim' , 'toLowerCase' , 'toUpperCase' , 'replace' , 'match' , 'test' ,
841+ 'parseInt' , 'parseFloat' , 'isNaN' , 'isFinite' , 'String' , 'Number' , 'Boolean' ,
842+ 'Array' , 'Object' , 'Promise' , 'Date' , 'Math' , 'JSON' , 'Error' ,
843+ }
844+
845+ seen = set ()
846+ result = []
847+ for call in calls + methods :
848+ if call not in excluded and not call .startswith ('_' ) and call not in seen :
849+ seen .add (call )
850+ result .append (call )
851+
852+ return result [:10 ]
853+
854+
855+ def extract_rust_function_calls (source_code : str ) -> List [str ]:
856+ """Extract function calls from Rust source code using regex.
857+
858+ Args:
859+ source_code: Rust function body source code
860+
861+ Returns:
862+ List of unique function names called
863+ """
864+ # Match function calls: function_name( or Type::method(
865+ call_pattern = r'\b([a-z_][a-z0-9_]*)\s*\('
866+ method_pattern = r'\.([a-z_][a-z0-9_]*)\s*\('
867+ type_method_pattern = r'([A-Z][a-zA-Z0-9_]*)::([a-z_][a-z0-9_]*)\s*\('
868+
869+ calls = re .findall (call_pattern , source_code )
870+ methods = re .findall (method_pattern , source_code )
871+ type_methods = re .findall (type_method_pattern , source_code )
872+
873+ # Combine type methods as Type::method
874+ type_method_calls = [f"{ t } ::{ m } " for t , m in type_methods ]
875+
876+ # Filter out common Rust keywords and built-ins
877+ excluded = {
878+ 'if' , 'for' , 'while' , 'match' , 'loop' , 'fn' , 'let' , 'mut' , 'const' ,
879+ 'return' , 'break' , 'continue' , 'impl' , 'struct' , 'enum' , 'trait' ,
880+ 'pub' , 'mod' , 'use' , 'crate' , 'self' , 'super' , 'async' , 'await' ,
881+ 'println' , 'print' , 'eprintln' , 'eprint' , 'format' , 'panic' , 'assert' ,
882+ 'vec' , 'Box' , 'Rc' , 'Arc' , 'RefCell' , 'Cell' , 'Option' , 'Result' ,
883+ 'Ok' , 'Err' , 'Some' , 'None' , 'unwrap' , 'expect' , 'map' , 'and_then' ,
884+ 'or_else' , 'clone' , 'into' , 'from' , 'default' , 'new' , 'push' , 'pop' ,
885+ 'len' , 'is_empty' , 'iter' , 'collect' , 'to_string' , 'as_str' ,
886+ }
887+
888+ seen = set ()
889+ result = []
890+ for call in calls + methods + type_method_calls :
891+ call_name = call .split ("::" )[- 1 ] if "::" in call else call
892+ if call_name not in excluded and not call_name .startswith ('_' ) and call not in seen :
893+ seen .add (call )
894+ result .append (call )
895+
896+ return result [:10 ]
897+
898+
815899# =============================================================================
816900# CONFIGURATION
817901# =============================================================================
@@ -1325,14 +1409,17 @@ def parse_module(self, module_name: str) -> Optional[ModuleInfo]:
13251409 try :
13261410 content = index_file .read_text ()
13271411 module_short_name = module_name
1412+ # Get relative source path for GitHub links
1413+ source_file = str (index_file .relative_to (self .source_path .parent ))
13281414 info = ModuleInfo (
13291415 name = f"praisonai.{ module_name } " ,
13301416 short_name = module_short_name ,
13311417 docstring = self ._extract_module_doc (content ),
13321418 package = "typescript" ,
1419+ source_file = source_file ,
13331420 )
1334- info .classes = self ._parse_classes (content )
1335- info .functions = self ._parse_functions (content )
1421+ info .classes = self ._parse_classes (content , source_file )
1422+ info .functions = self ._parse_functions (content , source_file )
13361423 return info
13371424 except Exception :
13381425 return None
@@ -1346,33 +1433,66 @@ def _extract_module_doc(self, content: str) -> str:
13461433 return doc .strip ()
13471434 return ""
13481435
1349- def _parse_classes (self , content : str ) -> List [ClassInfo ]:
1436+ def _parse_classes (self , content : str , source_file : str = "" ) -> List [ClassInfo ]:
13501437 classes = []
13511438 pattern = r'export\s+(?:class|interface)\s+(\w+)(?:\s+extends\s+(\w+))?'
13521439 for match in re .finditer (pattern , content ):
13531440 name = match .group (1 )
13541441 base = match .group (2 )
1442+ # Find line number
1443+ line_no = content [:match .start ()].count ('\n ' ) + 1
1444+ # Extract class body for function calls
1445+ class_body = self ._extract_block (content , match .end ())
13551446 classes .append (ClassInfo (
13561447 name = name ,
13571448 bases = [base ] if base else [],
13581449 docstring = f"TypeScript { name } class" ,
1450+ source_file = source_file ,
1451+ source_line = line_no ,
13591452 ))
13601453 return classes
13611454
1362- def _parse_functions (self , content : str ) -> List [FunctionInfo ]:
1455+ def _parse_functions (self , content : str , source_file : str = "" ) -> List [FunctionInfo ]:
13631456 functions = []
13641457 pattern = r'export\s+(?:async\s+)?function\s+(\w+)\s*\(([^)]*)\)(?:\s*:\s*([^{]+))?'
13651458 for match in re .finditer (pattern , content ):
13661459 name = match .group (1 )
13671460 params_str = match .group (2 )
13681461 return_type = match .group (3 ) or "void"
1462+ # Find line number
1463+ line_no = content [:match .start ()].count ('\n ' ) + 1
1464+ # Extract function body for calls extraction
1465+ func_body = self ._extract_block (content , match .end ())
1466+ calls = extract_ts_function_calls (func_body ) if func_body else []
13691467 functions .append (FunctionInfo (
13701468 name = name ,
13711469 signature = params_str .strip (),
13721470 return_type = return_type .strip (),
13731471 is_async = 'async' in match .group (0 ),
1472+ source_file = source_file ,
1473+ source_line = line_no ,
1474+ calls = calls ,
13741475 ))
13751476 return functions
1477+
1478+ def _extract_block (self , content : str , start_pos : int ) -> str :
1479+ """Extract a code block (between { and }) from start position."""
1480+ # Find opening brace
1481+ brace_start = content .find ('{' , start_pos )
1482+ if brace_start == - 1 :
1483+ return ""
1484+
1485+ # Count braces to find matching close
1486+ depth = 1
1487+ pos = brace_start + 1
1488+ while pos < len (content ) and depth > 0 :
1489+ if content [pos ] == '{' :
1490+ depth += 1
1491+ elif content [pos ] == '}' :
1492+ depth -= 1
1493+ pos += 1
1494+
1495+ return content [brace_start :pos ] if depth == 0 else ""
13761496
13771497
13781498# =============================================================================
0 commit comments