Coverage for src\llm_code_lens\analyzer\javascript.py: 16%

74 statements  

« prev     ^ index     » next       coverage.py v7.7.0, created at 2025-05-25 12:07 +0300

1# src/codelens/analyzer/javascript.py 

2import re 

3from pathlib import Path 

4from typing import Dict, List 

5 

6class JavaScriptAnalyzer: 

7 """Enhanced JavaScript/TypeScript code analyzer with improved regex patterns.""" 

8 

9 def analyze_file(self, file_path: Path) -> dict: 

10 """Analyze a JavaScript/TypeScript file with enhanced patterns.""" 

11 with open(file_path, 'r', encoding='utf-8') as f: 

12 content = f.read() 

13 

14 analysis = { 

15 'type': 'javascript', 

16 'imports': [], 

17 'exports': [], 

18 'functions': [], 

19 'classes': [], 

20 'components': [], # New: React components 

21 'hooks': [], # New: React hooks 

22 'interfaces': [], # New: TypeScript interfaces 

23 'types': [], # New: TypeScript types 

24 'comments': [], 

25 'todos': [], 

26 'metrics': { 

27 'loc': len(content.splitlines()), 

28 'classes': 0, 

29 'functions': 0, 

30 'components': 0, # New metric 

31 'hooks': 0, # New metric 

32 'imports': 0, 

33 } 

34 } 

35 

36 # Enhanced import/export patterns 

37 self._extract_imports_exports(content, analysis) 

38 

39 # Enhanced function patterns (including arrow functions, async/await) 

40 self._extract_functions(content, analysis) 

41 

42 # Enhanced class patterns 

43 self._extract_classes(content, analysis) 

44 

45 # New: React component patterns 

46 self._extract_react_components(content, analysis) 

47 

48 # New: React hooks patterns 

49 self._extract_react_hooks(content, analysis) 

50 

51 # New: TypeScript interfaces and types 

52 self._extract_typescript_constructs(content, analysis) 

53 

54 # Existing comment/TODO extraction 

55 self._extract_comments_todos(content, analysis) 

56 

57 # Update metrics 

58 analysis['metrics']['classes'] = len(analysis['classes']) 

59 analysis['metrics']['functions'] = len(analysis['functions']) 

60 analysis['metrics']['components'] = len(analysis['components']) 

61 analysis['metrics']['hooks'] = len(analysis['hooks']) 

62 analysis['metrics']['imports'] = len(analysis['imports']) 

63 

64 return analysis 

65 

66 def _extract_imports_exports(self, content: str, analysis: dict) -> None: 

67 """Extract imports and exports with enhanced patterns.""" 

68 

69 # Enhanced import patterns 

70 import_patterns = [ 

71 # Standard imports: import { a, b } from 'module' 

72 r'import\s+\{([^}]+)\}\s+from\s+[\'"]([^\'"]+)[\'"]', 

73 # Default imports: import React from 'react' 

74 r'import\s+(\w+)\s+from\s+[\'"]([^\'"]+)[\'"]', 

75 # Namespace imports: import * as React from 'react' 

76 r'import\s+\*\s+as\s+(\w+)\s+from\s+[\'"]([^\'"]+)[\'"]', 

77 # Side-effect imports: import 'module' 

78 r'import\s+[\'"]([^\'"]+)[\'"]', 

79 # Dynamic imports: import() statements 

80 r'import\s*\(\s*[\'"]([^\'"]+)[\'"]\s*\)', 

81 ] 

82 

83 for pattern in import_patterns: 

84 for match in re.finditer(pattern, content, re.MULTILINE): 

85 analysis['imports'].append(match.group(0).strip()) 

86 

87 # Enhanced export patterns 

88 export_patterns = [ 

89 # Named exports: export { a, b } 

90 r'export\s+\{[^}]+\}', 

91 # Default exports: export default 

92 r'export\s+default\s+[^;]+', 

93 # Direct exports: export const/function/class 

94 r'export\s+(const|let|var|function|class|interface|type)\s+\w+', 

95 # Re-exports: export * from 'module' 

96 r'export\s+\*\s+from\s+[\'"][^\'"]+[\'"]', 

97 ] 

98 

99 for pattern in export_patterns: 

100 for match in re.finditer(pattern, content, re.MULTILINE): 

101 analysis['exports'].append(match.group(0).strip()) 

102 

103 def _extract_functions(self, content: str, analysis: dict) -> None: 

104 """Extract functions with enhanced patterns.""" 

105 

106 function_patterns = [ 

107 # Regular function declarations 

108 r'(?:async\s+)?function\s+(\w+)\s*\(([^)]*)\)\s*(?::\s*([^{]+))?\s*\{', 

109 # Arrow functions with names 

110 r'(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s+)?\(([^)]*)\)\s*(?::\s*([^=]+))?\s*=>\s*\{', 

111 # Arrow functions (simple) 

112 r'(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s+)?([^=]*)\s*=>\s*([^;]+);?', 

113 # Method definitions in classes/objects 

114 r'(?:async\s+)?(\w+)\s*\(([^)]*)\)\s*(?::\s*([^{]+))?\s*\{', 

115 ] 

116 

117 for pattern in function_patterns: 

118 for match in re.finditer(pattern, content, re.MULTILINE): 

119 func_name = match.group(1) 

120 params = match.group(2) if len(match.groups()) > 1 else '' 

121 return_type = match.group(3) if len(match.groups()) > 2 else None 

122 

123 analysis['functions'].append({ 

124 'name': func_name, 

125 'params': [p.strip() for p in params.split(',') if p.strip()], 

126 'return_type': return_type.strip() if return_type else None, 

127 'line_number': content[:match.start()].count('\n') + 1, 

128 'is_async': 'async' in match.group(0) 

129 }) 

130 

131 def _extract_classes(self, content: str, analysis: dict) -> None: 

132 """Extract classes with enhanced patterns.""" 

133 

134 class_patterns = [ 

135 # Regular class declarations 

136 r'class\s+(\w+)(?:\s+extends\s+([^{]+))?\s*\{', 

137 # Class expressions 

138 r'(?:const|let|var)\s+\w+\s*=\s*class\s+(\w+)(?:\s+extends\s+([^{]+))?\s*\{' 

139 ] 

140 

141 for pattern in class_patterns: 

142 for match in re.finditer(pattern, content, re.MULTILINE): 

143 analysis['classes'].append({ 

144 'name': match.group(1), 

145 'extends': match.group(2).strip() if match.group(2) else None, 

146 'line_number': content[:match.start()].count('\n') + 1 

147 }) 

148 

149 def _extract_react_components(self, content: str, analysis: dict) -> None: 

150 """Extract React components.""" 

151 

152 component_patterns = [ 

153 # Function components 

154 r'(?:const|function)\s+([A-Z]\w+)\s*[=:]?\s*(?:\([^)]*\))?\s*(?::\s*[^=>{]+)?\s*(?:=>)?\s*\{[^}]*return\s*\(', 

155 # Function components with JSX 

156 r'(?:const|function)\s+([A-Z]\w+)\s*[=:]?\s*(?:\([^)]*\))?\s*(?::\s*[^=>{]+)?\s*(?:=>)?\s*\{[^}]*<\w+', 

157 # Class components 

158 r'class\s+([A-Z]\w+)\s+extends\s+(?:React\.)?(?:Component|PureComponent)', 

159 ] 

160 

161 for pattern in component_patterns: 

162 for match in re.finditer(pattern, content, re.MULTILINE | re.DOTALL): 

163 component_name = match.group(1) 

164 line_number = content[:match.start()].count('\n') + 1 

165 

166 # Determine component type 

167 is_class = 'class' in match.group(0) 

168 

169 analysis['components'].append({ 

170 'name': component_name, 

171 'type': 'class' if is_class else 'function', 

172 'line_number': line_number, 

173 'has_jsx': '<' in match.group(0) and '>' in match.group(0) 

174 }) 

175 

176 def _extract_react_hooks(self, content: str, analysis: dict) -> None: 

177 """Extract React hooks usage.""" 

178 

179 # Common React hooks 

180 hook_patterns = [ 

181 r'(useState)\s*\(', 

182 r'(useEffect)\s*\(', 

183 r'(useContext)\s*\(', 

184 r'(useReducer)\s*\(', 

185 r'(useCallback)\s*\(', 

186 r'(useMemo)\s*\(', 

187 r'(useRef)\s*\(', 

188 r'(useImperativeHandle)\s*\(', 

189 r'(useLayoutEffect)\s*\(', 

190 r'(useDebugValue)\s*\(', 

191 # Custom hooks (start with 'use') 

192 r'(use[A-Z]\w*)\s*\(', 

193 ] 

194 

195 for pattern in hook_patterns: 

196 for match in re.finditer(pattern, content): 

197 hook_name = match.group(1) 

198 line_number = content[:match.start()].count('\n') + 1 

199 

200 analysis['hooks'].append({ 

201 'name': hook_name, 

202 'line_number': line_number, 

203 'is_custom': not hook_name.startswith(('useState', 'useEffect', 'useContext', 'useReducer', 'useCallback', 'useMemo', 'useRef')) 

204 }) 

205 

206 def _extract_typescript_constructs(self, content: str, analysis: dict) -> None: 

207 """Extract TypeScript interfaces and types.""" 

208 

209 # Interface pattern 

210 interface_pattern = r'interface\s+(\w+)(?:\s+extends\s+([^{]+))?\s*\{' 

211 for match in re.finditer(interface_pattern, content): 

212 analysis['interfaces'].append({ 

213 'name': match.group(1), 

214 'extends': match.group(2).strip() if match.group(2) else None, 

215 'line_number': content[:match.start()].count('\n') + 1 

216 }) 

217 

218 # Type alias pattern 

219 type_pattern = r'type\s+(\w+)(?:<[^>]*>)?\s*=\s*([^;]+);' 

220 for match in re.finditer(type_pattern, content): 

221 analysis['types'].append({ 

222 'name': match.group(1), 

223 'definition': match.group(2).strip(), 

224 'line_number': content[:match.start()].count('\n') + 1 

225 }) 

226 

227 def _extract_comments_todos(self, content: str, analysis: dict) -> None: 

228 """Extract comments and TODOs (existing functionality enhanced).""" 

229 

230 comment_patterns = [ 

231 (r'//(.*)$', False), # Single-line comments 

232 (r'/\*([^*]*\*+(?:[^/*][^*]*\*+)*/)/', True) # Multi-line comments 

233 ] 

234 

235 for pattern, is_multiline in comment_patterns: 

236 for match in re.finditer(pattern, content, re.MULTILINE if not is_multiline else re.MULTILINE | re.DOTALL): 

237 comment = match.group(1).strip() 

238 line_number = content[:match.start()].count('\n') + 1 

239 

240 # Check for TODOs/FIXMEs 

241 if any(marker in comment.upper() for marker in ['TODO', 'FIXME', 'XXX', 'HACK', 'BUG']): 

242 analysis['todos'].append({ 

243 'line': line_number, 

244 'text': comment 

245 }) 

246 else: 

247 analysis['comments'].append({ 

248 'line': line_number, 

249 'text': comment 

250 })