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
« 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
6class JavaScriptAnalyzer:
7 """Enhanced JavaScript/TypeScript code analyzer with improved regex patterns."""
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()
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 }
36 # Enhanced import/export patterns
37 self._extract_imports_exports(content, analysis)
39 # Enhanced function patterns (including arrow functions, async/await)
40 self._extract_functions(content, analysis)
42 # Enhanced class patterns
43 self._extract_classes(content, analysis)
45 # New: React component patterns
46 self._extract_react_components(content, analysis)
48 # New: React hooks patterns
49 self._extract_react_hooks(content, analysis)
51 # New: TypeScript interfaces and types
52 self._extract_typescript_constructs(content, analysis)
54 # Existing comment/TODO extraction
55 self._extract_comments_todos(content, analysis)
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'])
64 return analysis
66 def _extract_imports_exports(self, content: str, analysis: dict) -> None:
67 """Extract imports and exports with enhanced patterns."""
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 ]
83 for pattern in import_patterns:
84 for match in re.finditer(pattern, content, re.MULTILINE):
85 analysis['imports'].append(match.group(0).strip())
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 ]
99 for pattern in export_patterns:
100 for match in re.finditer(pattern, content, re.MULTILINE):
101 analysis['exports'].append(match.group(0).strip())
103 def _extract_functions(self, content: str, analysis: dict) -> None:
104 """Extract functions with enhanced patterns."""
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 ]
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
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 })
131 def _extract_classes(self, content: str, analysis: dict) -> None:
132 """Extract classes with enhanced patterns."""
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 ]
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 })
149 def _extract_react_components(self, content: str, analysis: dict) -> None:
150 """Extract React components."""
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 ]
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
166 # Determine component type
167 is_class = 'class' in match.group(0)
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 })
176 def _extract_react_hooks(self, content: str, analysis: dict) -> None:
177 """Extract React hooks usage."""
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 ]
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
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 })
206 def _extract_typescript_constructs(self, content: str, analysis: dict) -> None:
207 """Extract TypeScript interfaces and types."""
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 })
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 })
227 def _extract_comments_todos(self, content: str, analysis: dict) -> None:
228 """Extract comments and TODOs (existing functionality enhanced)."""
230 comment_patterns = [
231 (r'//(.*)$', False), # Single-line comments
232 (r'/\*([^*]*\*+(?:[^/*][^*]*\*+)*/)/', True) # Multi-line comments
233 ]
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
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 })