alita-sdk 0.3.465__py3-none-any.whl → 0.3.486__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of alita-sdk might be problematic. Click here for more details.
- alita_sdk/cli/agent/__init__.py +5 -0
- alita_sdk/cli/agent/default.py +83 -1
- alita_sdk/cli/agent_loader.py +6 -9
- alita_sdk/cli/agent_ui.py +13 -3
- alita_sdk/cli/agents.py +1866 -185
- alita_sdk/cli/callbacks.py +96 -25
- alita_sdk/cli/cli.py +10 -1
- alita_sdk/cli/config.py +151 -9
- alita_sdk/cli/context/__init__.py +30 -0
- alita_sdk/cli/context/cleanup.py +198 -0
- alita_sdk/cli/context/manager.py +731 -0
- alita_sdk/cli/context/message.py +285 -0
- alita_sdk/cli/context/strategies.py +289 -0
- alita_sdk/cli/context/token_estimation.py +127 -0
- alita_sdk/cli/input_handler.py +167 -4
- alita_sdk/cli/inventory.py +1256 -0
- alita_sdk/cli/toolkit.py +14 -17
- alita_sdk/cli/toolkit_loader.py +35 -5
- alita_sdk/cli/tools/__init__.py +8 -1
- alita_sdk/cli/tools/filesystem.py +815 -55
- alita_sdk/cli/tools/planning.py +143 -157
- alita_sdk/cli/tools/terminal.py +154 -20
- alita_sdk/community/__init__.py +64 -8
- alita_sdk/community/inventory/__init__.py +224 -0
- alita_sdk/community/inventory/config.py +257 -0
- alita_sdk/community/inventory/enrichment.py +2137 -0
- alita_sdk/community/inventory/extractors.py +1469 -0
- alita_sdk/community/inventory/ingestion.py +3172 -0
- alita_sdk/community/inventory/knowledge_graph.py +1457 -0
- alita_sdk/community/inventory/parsers/__init__.py +218 -0
- alita_sdk/community/inventory/parsers/base.py +295 -0
- alita_sdk/community/inventory/parsers/csharp_parser.py +907 -0
- alita_sdk/community/inventory/parsers/go_parser.py +851 -0
- alita_sdk/community/inventory/parsers/html_parser.py +389 -0
- alita_sdk/community/inventory/parsers/java_parser.py +593 -0
- alita_sdk/community/inventory/parsers/javascript_parser.py +629 -0
- alita_sdk/community/inventory/parsers/kotlin_parser.py +768 -0
- alita_sdk/community/inventory/parsers/markdown_parser.py +362 -0
- alita_sdk/community/inventory/parsers/python_parser.py +604 -0
- alita_sdk/community/inventory/parsers/rust_parser.py +858 -0
- alita_sdk/community/inventory/parsers/swift_parser.py +832 -0
- alita_sdk/community/inventory/parsers/text_parser.py +322 -0
- alita_sdk/community/inventory/parsers/yaml_parser.py +370 -0
- alita_sdk/community/inventory/patterns/__init__.py +61 -0
- alita_sdk/community/inventory/patterns/ast_adapter.py +380 -0
- alita_sdk/community/inventory/patterns/loader.py +348 -0
- alita_sdk/community/inventory/patterns/registry.py +198 -0
- alita_sdk/community/inventory/presets.py +535 -0
- alita_sdk/community/inventory/retrieval.py +1403 -0
- alita_sdk/community/inventory/toolkit.py +169 -0
- alita_sdk/community/inventory/visualize.py +1370 -0
- alita_sdk/configurations/bitbucket.py +0 -3
- alita_sdk/runtime/clients/client.py +84 -26
- alita_sdk/runtime/langchain/assistant.py +4 -2
- alita_sdk/runtime/langchain/langraph_agent.py +122 -31
- alita_sdk/runtime/llms/preloaded.py +2 -6
- alita_sdk/runtime/toolkits/__init__.py +2 -0
- alita_sdk/runtime/toolkits/application.py +1 -1
- alita_sdk/runtime/toolkits/mcp.py +46 -36
- alita_sdk/runtime/toolkits/planning.py +171 -0
- alita_sdk/runtime/toolkits/tools.py +39 -6
- alita_sdk/runtime/tools/llm.py +185 -8
- alita_sdk/runtime/tools/planning/__init__.py +36 -0
- alita_sdk/runtime/tools/planning/models.py +246 -0
- alita_sdk/runtime/tools/planning/wrapper.py +607 -0
- alita_sdk/runtime/tools/vectorstore_base.py +41 -6
- alita_sdk/runtime/utils/mcp_oauth.py +80 -0
- alita_sdk/runtime/utils/streamlit.py +6 -10
- alita_sdk/runtime/utils/toolkit_utils.py +19 -4
- alita_sdk/tools/__init__.py +54 -27
- alita_sdk/tools/ado/repos/repos_wrapper.py +1 -2
- alita_sdk/tools/base_indexer_toolkit.py +98 -19
- alita_sdk/tools/bitbucket/__init__.py +2 -2
- alita_sdk/tools/chunkers/__init__.py +3 -1
- alita_sdk/tools/chunkers/sematic/markdown_chunker.py +95 -6
- alita_sdk/tools/chunkers/universal_chunker.py +269 -0
- alita_sdk/tools/code_indexer_toolkit.py +55 -22
- alita_sdk/tools/elitea_base.py +86 -21
- alita_sdk/tools/jira/__init__.py +1 -1
- alita_sdk/tools/jira/api_wrapper.py +91 -40
- alita_sdk/tools/non_code_indexer_toolkit.py +1 -0
- alita_sdk/tools/qtest/__init__.py +1 -1
- alita_sdk/tools/vector_adapters/VectorStoreAdapter.py +8 -2
- alita_sdk/tools/zephyr_essential/api_wrapper.py +12 -13
- {alita_sdk-0.3.465.dist-info → alita_sdk-0.3.486.dist-info}/METADATA +2 -1
- {alita_sdk-0.3.465.dist-info → alita_sdk-0.3.486.dist-info}/RECORD +90 -50
- {alita_sdk-0.3.465.dist-info → alita_sdk-0.3.486.dist-info}/WHEEL +0 -0
- {alita_sdk-0.3.465.dist-info → alita_sdk-0.3.486.dist-info}/entry_points.txt +0 -0
- {alita_sdk-0.3.465.dist-info → alita_sdk-0.3.486.dist-info}/licenses/LICENSE +0 -0
- {alita_sdk-0.3.465.dist-info → alita_sdk-0.3.486.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,629 @@
|
|
|
1
|
+
"""
|
|
2
|
+
JavaScript/TypeScript Parser - Regex-based parsing.
|
|
3
|
+
|
|
4
|
+
This parser uses regex patterns for JavaScript/TypeScript analysis:
|
|
5
|
+
- Import/export detection
|
|
6
|
+
- Class and function extraction
|
|
7
|
+
- Inheritance patterns
|
|
8
|
+
- Function calls and references
|
|
9
|
+
|
|
10
|
+
Works without external dependencies like tree-sitter.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import re
|
|
14
|
+
import hashlib
|
|
15
|
+
import logging
|
|
16
|
+
import time
|
|
17
|
+
import concurrent.futures
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
from typing import Dict, List, Optional, Set, Union, Tuple
|
|
20
|
+
|
|
21
|
+
from .base import (
|
|
22
|
+
BaseParser, ParseResult, Symbol, Relationship,
|
|
23
|
+
SymbolType, RelationshipType, Scope, Position, Range,
|
|
24
|
+
parser_registry
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
logger = logging.getLogger(__name__)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
# Comprehensive regex patterns for JavaScript/TypeScript
|
|
31
|
+
PATTERNS = {
|
|
32
|
+
# ES6 imports
|
|
33
|
+
'import_default': re.compile(
|
|
34
|
+
r'import\s+(\w+)\s+from\s+[\'"]([^\'"]+)[\'"]',
|
|
35
|
+
re.MULTILINE
|
|
36
|
+
),
|
|
37
|
+
'import_named': re.compile(
|
|
38
|
+
r'import\s+\{([^}]+)\}\s+from\s+[\'"]([^\'"]+)[\'"]',
|
|
39
|
+
re.MULTILINE
|
|
40
|
+
),
|
|
41
|
+
'import_all': re.compile(
|
|
42
|
+
r'import\s+\*\s+as\s+(\w+)\s+from\s+[\'"]([^\'"]+)[\'"]',
|
|
43
|
+
re.MULTILINE
|
|
44
|
+
),
|
|
45
|
+
'import_side_effect': re.compile(
|
|
46
|
+
r'import\s+[\'"]([^\'"]+)[\'"]',
|
|
47
|
+
re.MULTILINE
|
|
48
|
+
),
|
|
49
|
+
'import_type': re.compile(
|
|
50
|
+
r'import\s+type\s+\{([^}]+)\}\s+from\s+[\'"]([^\'"]+)[\'"]',
|
|
51
|
+
re.MULTILINE
|
|
52
|
+
),
|
|
53
|
+
|
|
54
|
+
# CommonJS
|
|
55
|
+
'require': re.compile(
|
|
56
|
+
r'(?:const|let|var)\s+(\w+)\s*=\s*require\s*\(\s*[\'"]([^\'"]+)[\'"]\s*\)',
|
|
57
|
+
re.MULTILINE
|
|
58
|
+
),
|
|
59
|
+
'require_destructure': re.compile(
|
|
60
|
+
r'(?:const|let|var)\s+\{([^}]+)\}\s*=\s*require\s*\(\s*[\'"]([^\'"]+)[\'"]\s*\)',
|
|
61
|
+
re.MULTILINE
|
|
62
|
+
),
|
|
63
|
+
|
|
64
|
+
# Dynamic import
|
|
65
|
+
'dynamic_import': re.compile(
|
|
66
|
+
r'import\s*\(\s*[\'"]([^\'"]+)[\'"]\s*\)',
|
|
67
|
+
re.MULTILINE
|
|
68
|
+
),
|
|
69
|
+
|
|
70
|
+
# Exports
|
|
71
|
+
'export_default': re.compile(
|
|
72
|
+
r'export\s+default\s+(?:class|function|const|let|var)?\s*(\w+)?',
|
|
73
|
+
re.MULTILINE
|
|
74
|
+
),
|
|
75
|
+
'export_named': re.compile(
|
|
76
|
+
r'export\s+(?:const|let|var|function|class|interface|type|enum)\s+(\w+)',
|
|
77
|
+
re.MULTILINE
|
|
78
|
+
),
|
|
79
|
+
'export_from': re.compile(
|
|
80
|
+
r'export\s+\{([^}]+)\}\s+from\s+[\'"]([^\'"]+)[\'"]',
|
|
81
|
+
re.MULTILINE
|
|
82
|
+
),
|
|
83
|
+
'export_all': re.compile(
|
|
84
|
+
r'export\s+\*\s+from\s+[\'"]([^\'"]+)[\'"]',
|
|
85
|
+
re.MULTILINE
|
|
86
|
+
),
|
|
87
|
+
|
|
88
|
+
# Classes
|
|
89
|
+
'class_def': re.compile(
|
|
90
|
+
r'(?:export\s+)?(?:abstract\s+)?class\s+(\w+)(?:\s+extends\s+(\w+))?(?:\s+implements\s+([\w,\s]+))?',
|
|
91
|
+
re.MULTILINE
|
|
92
|
+
),
|
|
93
|
+
|
|
94
|
+
# Functions
|
|
95
|
+
'function_def': re.compile(
|
|
96
|
+
r'(?:export\s+)?(?:async\s+)?function\s+(\w+)\s*\([^)]*\)',
|
|
97
|
+
re.MULTILINE
|
|
98
|
+
),
|
|
99
|
+
'arrow_function': re.compile(
|
|
100
|
+
r'(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s+)?(?:\([^)]*\)|[a-zA-Z_]\w*)\s*=>',
|
|
101
|
+
re.MULTILINE
|
|
102
|
+
),
|
|
103
|
+
'method_def': re.compile(
|
|
104
|
+
r'^\s*(?:async\s+)?(\w+)\s*\([^)]*\)\s*(?::\s*\w+)?\s*\{',
|
|
105
|
+
re.MULTILINE
|
|
106
|
+
),
|
|
107
|
+
|
|
108
|
+
# Interfaces and types (TypeScript)
|
|
109
|
+
'interface_def': re.compile(
|
|
110
|
+
r'(?:export\s+)?interface\s+(\w+)(?:\s+extends\s+([\w,\s]+))?',
|
|
111
|
+
re.MULTILINE
|
|
112
|
+
),
|
|
113
|
+
'type_alias': re.compile(
|
|
114
|
+
r'(?:export\s+)?type\s+(\w+)\s*=',
|
|
115
|
+
re.MULTILINE
|
|
116
|
+
),
|
|
117
|
+
'enum_def': re.compile(
|
|
118
|
+
r'(?:export\s+)?enum\s+(\w+)',
|
|
119
|
+
re.MULTILINE
|
|
120
|
+
),
|
|
121
|
+
|
|
122
|
+
# Variables
|
|
123
|
+
'const_def': re.compile(
|
|
124
|
+
r'(?:export\s+)?const\s+(\w+)\s*(?::\s*[\w<>\[\]|&\s]+)?\s*=',
|
|
125
|
+
re.MULTILINE
|
|
126
|
+
),
|
|
127
|
+
|
|
128
|
+
# Function calls
|
|
129
|
+
'function_call': re.compile(
|
|
130
|
+
r'(?<![.\w])(\w+)\s*\(',
|
|
131
|
+
re.MULTILINE
|
|
132
|
+
),
|
|
133
|
+
|
|
134
|
+
# JSX component usage
|
|
135
|
+
'jsx_component': re.compile(
|
|
136
|
+
r'<([A-Z]\w*)[>\s/]',
|
|
137
|
+
re.MULTILINE
|
|
138
|
+
),
|
|
139
|
+
|
|
140
|
+
# Decorators (TypeScript/experimental)
|
|
141
|
+
'decorator': re.compile(
|
|
142
|
+
r'@(\w+)(?:\([^)]*\))?',
|
|
143
|
+
re.MULTILINE
|
|
144
|
+
),
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def _parse_single_file(file_path: str) -> Tuple[str, ParseResult]:
|
|
149
|
+
"""Parse a single JavaScript file."""
|
|
150
|
+
try:
|
|
151
|
+
parser = JavaScriptParser()
|
|
152
|
+
result = parser.parse_file(file_path)
|
|
153
|
+
return file_path, result
|
|
154
|
+
except Exception as e:
|
|
155
|
+
logger.warning(f"Failed to parse {file_path}: {e}")
|
|
156
|
+
return file_path, ParseResult(
|
|
157
|
+
file_path=file_path,
|
|
158
|
+
language="javascript",
|
|
159
|
+
symbols=[],
|
|
160
|
+
relationships=[]
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
class JavaScriptParser(BaseParser):
|
|
165
|
+
"""
|
|
166
|
+
JavaScript/TypeScript parser using regex patterns.
|
|
167
|
+
|
|
168
|
+
Extracts imports, exports, classes, functions, and relationships.
|
|
169
|
+
"""
|
|
170
|
+
|
|
171
|
+
def __init__(self):
|
|
172
|
+
super().__init__("javascript")
|
|
173
|
+
self._current_file = ""
|
|
174
|
+
self._current_content = ""
|
|
175
|
+
|
|
176
|
+
# Cross-file resolution
|
|
177
|
+
self._global_exports: Dict[str, str] = {} # export_name -> file_path
|
|
178
|
+
self._global_classes: Dict[str, str] = {}
|
|
179
|
+
self._global_functions: Dict[str, str] = {}
|
|
180
|
+
|
|
181
|
+
def _get_supported_extensions(self) -> Set[str]:
|
|
182
|
+
return {'.js', '.jsx', '.ts', '.tsx', '.mjs', '.cjs'}
|
|
183
|
+
|
|
184
|
+
def parse_file(self, file_path: Union[str, Path], content: Optional[str] = None) -> ParseResult:
|
|
185
|
+
"""Parse a JavaScript/TypeScript file."""
|
|
186
|
+
start_time = time.time()
|
|
187
|
+
file_path = str(file_path)
|
|
188
|
+
self._current_file = file_path
|
|
189
|
+
|
|
190
|
+
try:
|
|
191
|
+
if content is None:
|
|
192
|
+
with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
|
|
193
|
+
content = f.read()
|
|
194
|
+
|
|
195
|
+
self._current_content = content
|
|
196
|
+
|
|
197
|
+
symbols = self._extract_symbols(content, file_path)
|
|
198
|
+
relationships = self._extract_relationships(content, symbols, file_path)
|
|
199
|
+
imports = self._extract_imports(content)
|
|
200
|
+
exports = self._extract_exports(content)
|
|
201
|
+
|
|
202
|
+
result = ParseResult(
|
|
203
|
+
file_path=file_path,
|
|
204
|
+
language="javascript",
|
|
205
|
+
symbols=symbols,
|
|
206
|
+
relationships=relationships,
|
|
207
|
+
imports=imports,
|
|
208
|
+
exports=exports,
|
|
209
|
+
parse_time=time.time() - start_time
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
return self.validate_result(result)
|
|
213
|
+
|
|
214
|
+
except Exception as e:
|
|
215
|
+
logger.error(f"Failed to parse JS file {file_path}: {e}")
|
|
216
|
+
return ParseResult(
|
|
217
|
+
file_path=file_path,
|
|
218
|
+
language="javascript",
|
|
219
|
+
symbols=[],
|
|
220
|
+
relationships=[],
|
|
221
|
+
parse_time=time.time() - start_time,
|
|
222
|
+
errors=[str(e)]
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
def _extract_symbols(self, content: str, file_path: str) -> List[Symbol]:
|
|
226
|
+
"""Extract symbols from JavaScript content."""
|
|
227
|
+
symbols = []
|
|
228
|
+
lines = content.split('\n')
|
|
229
|
+
|
|
230
|
+
# Module symbol
|
|
231
|
+
symbols.append(Symbol(
|
|
232
|
+
name=Path(file_path).stem,
|
|
233
|
+
symbol_type=SymbolType.MODULE,
|
|
234
|
+
scope=Scope.GLOBAL,
|
|
235
|
+
range=Range(Position(1, 0), Position(len(lines), 0)),
|
|
236
|
+
file_path=file_path
|
|
237
|
+
))
|
|
238
|
+
|
|
239
|
+
# Classes
|
|
240
|
+
for match in PATTERNS['class_def'].finditer(content):
|
|
241
|
+
line = content[:match.start()].count('\n') + 1
|
|
242
|
+
class_name = match.group(1)
|
|
243
|
+
extends = match.group(2)
|
|
244
|
+
|
|
245
|
+
symbols.append(Symbol(
|
|
246
|
+
name=class_name,
|
|
247
|
+
symbol_type=SymbolType.CLASS,
|
|
248
|
+
scope=Scope.GLOBAL,
|
|
249
|
+
range=Range(Position(line, 0), Position(line, len(match.group(0)))),
|
|
250
|
+
file_path=file_path,
|
|
251
|
+
is_exported='export' in match.group(0),
|
|
252
|
+
metadata={'extends': extends} if extends else {}
|
|
253
|
+
))
|
|
254
|
+
|
|
255
|
+
# Interfaces (TypeScript)
|
|
256
|
+
for match in PATTERNS['interface_def'].finditer(content):
|
|
257
|
+
line = content[:match.start()].count('\n') + 1
|
|
258
|
+
symbols.append(Symbol(
|
|
259
|
+
name=match.group(1),
|
|
260
|
+
symbol_type=SymbolType.INTERFACE,
|
|
261
|
+
scope=Scope.GLOBAL,
|
|
262
|
+
range=Range(Position(line, 0), Position(line, len(match.group(0)))),
|
|
263
|
+
file_path=file_path,
|
|
264
|
+
is_exported='export' in match.group(0)
|
|
265
|
+
))
|
|
266
|
+
|
|
267
|
+
# Type aliases
|
|
268
|
+
for match in PATTERNS['type_alias'].finditer(content):
|
|
269
|
+
line = content[:match.start()].count('\n') + 1
|
|
270
|
+
symbols.append(Symbol(
|
|
271
|
+
name=match.group(1),
|
|
272
|
+
symbol_type=SymbolType.TYPE_ALIAS,
|
|
273
|
+
scope=Scope.GLOBAL,
|
|
274
|
+
range=Range(Position(line, 0), Position(line, len(match.group(0)))),
|
|
275
|
+
file_path=file_path,
|
|
276
|
+
is_exported='export' in match.group(0)
|
|
277
|
+
))
|
|
278
|
+
|
|
279
|
+
# Enums
|
|
280
|
+
for match in PATTERNS['enum_def'].finditer(content):
|
|
281
|
+
line = content[:match.start()].count('\n') + 1
|
|
282
|
+
symbols.append(Symbol(
|
|
283
|
+
name=match.group(1),
|
|
284
|
+
symbol_type=SymbolType.ENUM,
|
|
285
|
+
scope=Scope.GLOBAL,
|
|
286
|
+
range=Range(Position(line, 0), Position(line, len(match.group(0)))),
|
|
287
|
+
file_path=file_path,
|
|
288
|
+
is_exported='export' in match.group(0)
|
|
289
|
+
))
|
|
290
|
+
|
|
291
|
+
# Functions
|
|
292
|
+
for match in PATTERNS['function_def'].finditer(content):
|
|
293
|
+
line = content[:match.start()].count('\n') + 1
|
|
294
|
+
symbols.append(Symbol(
|
|
295
|
+
name=match.group(1),
|
|
296
|
+
symbol_type=SymbolType.FUNCTION,
|
|
297
|
+
scope=Scope.GLOBAL,
|
|
298
|
+
range=Range(Position(line, 0), Position(line, len(match.group(0)))),
|
|
299
|
+
file_path=file_path,
|
|
300
|
+
is_async='async' in match.group(0),
|
|
301
|
+
is_exported='export' in match.group(0)
|
|
302
|
+
))
|
|
303
|
+
|
|
304
|
+
# Arrow functions
|
|
305
|
+
for match in PATTERNS['arrow_function'].finditer(content):
|
|
306
|
+
line = content[:match.start()].count('\n') + 1
|
|
307
|
+
symbols.append(Symbol(
|
|
308
|
+
name=match.group(1),
|
|
309
|
+
symbol_type=SymbolType.FUNCTION,
|
|
310
|
+
scope=Scope.GLOBAL,
|
|
311
|
+
range=Range(Position(line, 0), Position(line, len(match.group(0)))),
|
|
312
|
+
file_path=file_path,
|
|
313
|
+
is_async='async' in match.group(0)
|
|
314
|
+
))
|
|
315
|
+
|
|
316
|
+
# Constants
|
|
317
|
+
for match in PATTERNS['const_def'].finditer(content):
|
|
318
|
+
line = content[:match.start()].count('\n') + 1
|
|
319
|
+
name = match.group(1)
|
|
320
|
+
# Skip if already added as arrow function
|
|
321
|
+
if not any(s.name == name and s.symbol_type == SymbolType.FUNCTION for s in symbols):
|
|
322
|
+
symbols.append(Symbol(
|
|
323
|
+
name=name,
|
|
324
|
+
symbol_type=SymbolType.CONSTANT if name.isupper() else SymbolType.VARIABLE,
|
|
325
|
+
scope=Scope.GLOBAL,
|
|
326
|
+
range=Range(Position(line, 0), Position(line, len(match.group(0)))),
|
|
327
|
+
file_path=file_path,
|
|
328
|
+
is_exported='export' in match.group(0)
|
|
329
|
+
))
|
|
330
|
+
|
|
331
|
+
# Imports as symbols
|
|
332
|
+
for match in PATTERNS['import_default'].finditer(content):
|
|
333
|
+
line = content[:match.start()].count('\n') + 1
|
|
334
|
+
symbols.append(Symbol(
|
|
335
|
+
name=match.group(1),
|
|
336
|
+
symbol_type=SymbolType.IMPORT,
|
|
337
|
+
scope=Scope.GLOBAL,
|
|
338
|
+
range=Range(Position(line, 0), Position(line, len(match.group(0)))),
|
|
339
|
+
file_path=file_path,
|
|
340
|
+
metadata={'from': match.group(2), 'type': 'default'}
|
|
341
|
+
))
|
|
342
|
+
|
|
343
|
+
for match in PATTERNS['import_named'].finditer(content):
|
|
344
|
+
line = content[:match.start()].count('\n') + 1
|
|
345
|
+
names = [n.strip().split(' as ')[0].strip() for n in match.group(1).split(',')]
|
|
346
|
+
for name in names:
|
|
347
|
+
if name:
|
|
348
|
+
symbols.append(Symbol(
|
|
349
|
+
name=name,
|
|
350
|
+
symbol_type=SymbolType.IMPORT,
|
|
351
|
+
scope=Scope.GLOBAL,
|
|
352
|
+
range=Range(Position(line, 0), Position(line, len(match.group(0)))),
|
|
353
|
+
file_path=file_path,
|
|
354
|
+
metadata={'from': match.group(2), 'type': 'named'}
|
|
355
|
+
))
|
|
356
|
+
|
|
357
|
+
return symbols
|
|
358
|
+
|
|
359
|
+
def _extract_relationships(self, content: str, symbols: List[Symbol], file_path: str) -> List[Relationship]:
|
|
360
|
+
"""Extract relationships from JavaScript content."""
|
|
361
|
+
relationships = []
|
|
362
|
+
module_name = Path(file_path).stem
|
|
363
|
+
symbol_names = {s.name for s in symbols}
|
|
364
|
+
|
|
365
|
+
# Import relationships
|
|
366
|
+
for match in PATTERNS['import_default'].finditer(content):
|
|
367
|
+
line = content[:match.start()].count('\n') + 1
|
|
368
|
+
relationships.append(Relationship(
|
|
369
|
+
source_symbol=module_name,
|
|
370
|
+
target_symbol=match.group(2),
|
|
371
|
+
relationship_type=RelationshipType.IMPORTS,
|
|
372
|
+
source_file=file_path,
|
|
373
|
+
source_range=Range(Position(line, 0), Position(line, len(match.group(0)))),
|
|
374
|
+
confidence=1.0
|
|
375
|
+
))
|
|
376
|
+
|
|
377
|
+
for match in PATTERNS['import_named'].finditer(content):
|
|
378
|
+
line = content[:match.start()].count('\n') + 1
|
|
379
|
+
relationships.append(Relationship(
|
|
380
|
+
source_symbol=module_name,
|
|
381
|
+
target_symbol=match.group(2),
|
|
382
|
+
relationship_type=RelationshipType.IMPORTS,
|
|
383
|
+
source_file=file_path,
|
|
384
|
+
source_range=Range(Position(line, 0), Position(line, len(match.group(0)))),
|
|
385
|
+
confidence=1.0
|
|
386
|
+
))
|
|
387
|
+
|
|
388
|
+
for match in PATTERNS['import_all'].finditer(content):
|
|
389
|
+
line = content[:match.start()].count('\n') + 1
|
|
390
|
+
relationships.append(Relationship(
|
|
391
|
+
source_symbol=module_name,
|
|
392
|
+
target_symbol=match.group(2),
|
|
393
|
+
relationship_type=RelationshipType.IMPORTS,
|
|
394
|
+
source_file=file_path,
|
|
395
|
+
source_range=Range(Position(line, 0), Position(line, len(match.group(0)))),
|
|
396
|
+
confidence=1.0
|
|
397
|
+
))
|
|
398
|
+
|
|
399
|
+
for match in PATTERNS['require'].finditer(content):
|
|
400
|
+
line = content[:match.start()].count('\n') + 1
|
|
401
|
+
relationships.append(Relationship(
|
|
402
|
+
source_symbol=module_name,
|
|
403
|
+
target_symbol=match.group(2),
|
|
404
|
+
relationship_type=RelationshipType.IMPORTS,
|
|
405
|
+
source_file=file_path,
|
|
406
|
+
source_range=Range(Position(line, 0), Position(line, len(match.group(0)))),
|
|
407
|
+
confidence=1.0
|
|
408
|
+
))
|
|
409
|
+
|
|
410
|
+
for match in PATTERNS['dynamic_import'].finditer(content):
|
|
411
|
+
line = content[:match.start()].count('\n') + 1
|
|
412
|
+
relationships.append(Relationship(
|
|
413
|
+
source_symbol=module_name,
|
|
414
|
+
target_symbol=match.group(1),
|
|
415
|
+
relationship_type=RelationshipType.IMPORTS,
|
|
416
|
+
source_file=file_path,
|
|
417
|
+
source_range=Range(Position(line, 0), Position(line, len(match.group(0)))),
|
|
418
|
+
confidence=0.9
|
|
419
|
+
))
|
|
420
|
+
|
|
421
|
+
# Inheritance relationships
|
|
422
|
+
for match in PATTERNS['class_def'].finditer(content):
|
|
423
|
+
class_name = match.group(1)
|
|
424
|
+
extends = match.group(2)
|
|
425
|
+
implements = match.group(3)
|
|
426
|
+
line = content[:match.start()].count('\n') + 1
|
|
427
|
+
|
|
428
|
+
if extends:
|
|
429
|
+
relationships.append(Relationship(
|
|
430
|
+
source_symbol=class_name,
|
|
431
|
+
target_symbol=extends,
|
|
432
|
+
relationship_type=RelationshipType.INHERITANCE,
|
|
433
|
+
source_file=file_path,
|
|
434
|
+
source_range=Range(Position(line, 0), Position(line, len(match.group(0)))),
|
|
435
|
+
confidence=0.95
|
|
436
|
+
))
|
|
437
|
+
|
|
438
|
+
if implements:
|
|
439
|
+
for iface in implements.split(','):
|
|
440
|
+
iface = iface.strip()
|
|
441
|
+
if iface:
|
|
442
|
+
relationships.append(Relationship(
|
|
443
|
+
source_symbol=class_name,
|
|
444
|
+
target_symbol=iface,
|
|
445
|
+
relationship_type=RelationshipType.IMPLEMENTATION,
|
|
446
|
+
source_file=file_path,
|
|
447
|
+
source_range=Range(Position(line, 0), Position(line, len(match.group(0)))),
|
|
448
|
+
confidence=0.95
|
|
449
|
+
))
|
|
450
|
+
|
|
451
|
+
# Interface extension
|
|
452
|
+
for match in PATTERNS['interface_def'].finditer(content):
|
|
453
|
+
iface_name = match.group(1)
|
|
454
|
+
extends = match.group(2)
|
|
455
|
+
if extends:
|
|
456
|
+
line = content[:match.start()].count('\n') + 1
|
|
457
|
+
for parent in extends.split(','):
|
|
458
|
+
parent = parent.strip()
|
|
459
|
+
if parent:
|
|
460
|
+
relationships.append(Relationship(
|
|
461
|
+
source_symbol=iface_name,
|
|
462
|
+
target_symbol=parent,
|
|
463
|
+
relationship_type=RelationshipType.INHERITANCE,
|
|
464
|
+
source_file=file_path,
|
|
465
|
+
source_range=Range(Position(line, 0), Position(line, len(match.group(0)))),
|
|
466
|
+
confidence=0.95
|
|
467
|
+
))
|
|
468
|
+
|
|
469
|
+
# JSX component usage
|
|
470
|
+
for match in PATTERNS['jsx_component'].finditer(content):
|
|
471
|
+
component = match.group(1)
|
|
472
|
+
if component in symbol_names or component[0].isupper():
|
|
473
|
+
line = content[:match.start()].count('\n') + 1
|
|
474
|
+
relationships.append(Relationship(
|
|
475
|
+
source_symbol=module_name,
|
|
476
|
+
target_symbol=component,
|
|
477
|
+
relationship_type=RelationshipType.USES,
|
|
478
|
+
source_file=file_path,
|
|
479
|
+
source_range=Range(Position(line, 0), Position(line, len(match.group(0)))),
|
|
480
|
+
confidence=0.85
|
|
481
|
+
))
|
|
482
|
+
|
|
483
|
+
# Decorator relationships
|
|
484
|
+
for match in PATTERNS['decorator'].finditer(content):
|
|
485
|
+
dec_name = match.group(1)
|
|
486
|
+
line = content[:match.start()].count('\n') + 1
|
|
487
|
+
|
|
488
|
+
# Find what's being decorated (next class/function)
|
|
489
|
+
remaining = content[match.end():]
|
|
490
|
+
class_match = PATTERNS['class_def'].search(remaining)
|
|
491
|
+
func_match = PATTERNS['function_def'].search(remaining)
|
|
492
|
+
|
|
493
|
+
target = None
|
|
494
|
+
if class_match and (not func_match or class_match.start() < func_match.start()):
|
|
495
|
+
target = class_match.group(1)
|
|
496
|
+
elif func_match:
|
|
497
|
+
target = func_match.group(1)
|
|
498
|
+
|
|
499
|
+
if target:
|
|
500
|
+
relationships.append(Relationship(
|
|
501
|
+
source_symbol=dec_name,
|
|
502
|
+
target_symbol=target,
|
|
503
|
+
relationship_type=RelationshipType.DECORATES,
|
|
504
|
+
source_file=file_path,
|
|
505
|
+
source_range=Range(Position(line, 0), Position(line, len(match.group(0)))),
|
|
506
|
+
confidence=0.90
|
|
507
|
+
))
|
|
508
|
+
|
|
509
|
+
return relationships
|
|
510
|
+
|
|
511
|
+
def _extract_imports(self, content: str) -> List[str]:
|
|
512
|
+
"""Extract import paths."""
|
|
513
|
+
imports = []
|
|
514
|
+
|
|
515
|
+
for match in PATTERNS['import_default'].finditer(content):
|
|
516
|
+
imports.append(match.group(2))
|
|
517
|
+
for match in PATTERNS['import_named'].finditer(content):
|
|
518
|
+
imports.append(match.group(2))
|
|
519
|
+
for match in PATTERNS['import_all'].finditer(content):
|
|
520
|
+
imports.append(match.group(2))
|
|
521
|
+
for match in PATTERNS['import_side_effect'].finditer(content):
|
|
522
|
+
imports.append(match.group(1))
|
|
523
|
+
for match in PATTERNS['require'].finditer(content):
|
|
524
|
+
imports.append(match.group(2))
|
|
525
|
+
for match in PATTERNS['dynamic_import'].finditer(content):
|
|
526
|
+
imports.append(match.group(1))
|
|
527
|
+
|
|
528
|
+
return list(set(imports))
|
|
529
|
+
|
|
530
|
+
def _extract_exports(self, content: str) -> List[str]:
|
|
531
|
+
"""Extract export names."""
|
|
532
|
+
exports = []
|
|
533
|
+
|
|
534
|
+
for match in PATTERNS['export_default'].finditer(content):
|
|
535
|
+
if match.group(1):
|
|
536
|
+
exports.append(match.group(1))
|
|
537
|
+
else:
|
|
538
|
+
exports.append('default')
|
|
539
|
+
|
|
540
|
+
for match in PATTERNS['export_named'].finditer(content):
|
|
541
|
+
exports.append(match.group(1))
|
|
542
|
+
|
|
543
|
+
for match in PATTERNS['export_from'].finditer(content):
|
|
544
|
+
names = [n.strip().split(' as ')[0].strip() for n in match.group(1).split(',')]
|
|
545
|
+
exports.extend([n for n in names if n])
|
|
546
|
+
|
|
547
|
+
return list(set(exports))
|
|
548
|
+
|
|
549
|
+
def parse_multiple_files(self, file_paths: List[str], max_workers: int = 4) -> Dict[str, ParseResult]:
|
|
550
|
+
"""Parse multiple JavaScript files with cross-file resolution."""
|
|
551
|
+
results: Dict[str, ParseResult] = {}
|
|
552
|
+
total = len(file_paths)
|
|
553
|
+
|
|
554
|
+
self._global_exports = {}
|
|
555
|
+
self._global_classes = {}
|
|
556
|
+
self._global_functions = {}
|
|
557
|
+
|
|
558
|
+
logger.info(f"Parsing {total} JavaScript files")
|
|
559
|
+
start_time = time.time()
|
|
560
|
+
|
|
561
|
+
# Parse in parallel
|
|
562
|
+
with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor:
|
|
563
|
+
futures = {executor.submit(_parse_single_file, fp): fp for fp in file_paths}
|
|
564
|
+
|
|
565
|
+
for future in concurrent.futures.as_completed(futures):
|
|
566
|
+
try:
|
|
567
|
+
fp, result = future.result(timeout=60)
|
|
568
|
+
results[fp] = result
|
|
569
|
+
except Exception as e:
|
|
570
|
+
fp = futures[future]
|
|
571
|
+
logger.error(f"Failed to parse {fp}: {e}")
|
|
572
|
+
results[fp] = ParseResult(
|
|
573
|
+
file_path=fp,
|
|
574
|
+
language="javascript",
|
|
575
|
+
symbols=[],
|
|
576
|
+
relationships=[],
|
|
577
|
+
errors=[str(e)]
|
|
578
|
+
)
|
|
579
|
+
|
|
580
|
+
# Build global registry
|
|
581
|
+
for fp, result in results.items():
|
|
582
|
+
self._build_global_registry(fp, result)
|
|
583
|
+
|
|
584
|
+
# Enhance relationships
|
|
585
|
+
for fp, result in results.items():
|
|
586
|
+
self._enhance_relationships(fp, result)
|
|
587
|
+
|
|
588
|
+
elapsed = time.time() - start_time
|
|
589
|
+
logger.info(f"Parsed {len(results)} JavaScript files in {elapsed:.2f}s")
|
|
590
|
+
|
|
591
|
+
return results
|
|
592
|
+
|
|
593
|
+
def _build_global_registry(self, file_path: str, result: ParseResult):
|
|
594
|
+
"""Build global export registry."""
|
|
595
|
+
for exp in result.exports:
|
|
596
|
+
self._global_exports[exp] = file_path
|
|
597
|
+
|
|
598
|
+
for symbol in result.symbols:
|
|
599
|
+
if symbol.is_exported:
|
|
600
|
+
if symbol.symbol_type == SymbolType.CLASS:
|
|
601
|
+
self._global_classes[symbol.name] = file_path
|
|
602
|
+
elif symbol.symbol_type == SymbolType.FUNCTION:
|
|
603
|
+
self._global_functions[symbol.name] = file_path
|
|
604
|
+
|
|
605
|
+
def _enhance_relationships(self, file_path: str, result: ParseResult):
|
|
606
|
+
"""Enhance relationships with cross-file info."""
|
|
607
|
+
for rel in result.relationships:
|
|
608
|
+
target = rel.target_symbol
|
|
609
|
+
|
|
610
|
+
# Check global registries
|
|
611
|
+
if target in self._global_exports:
|
|
612
|
+
target_file = self._global_exports[target]
|
|
613
|
+
if target_file != file_path:
|
|
614
|
+
rel.target_file = target_file
|
|
615
|
+
rel.is_cross_file = True
|
|
616
|
+
elif target in self._global_classes:
|
|
617
|
+
target_file = self._global_classes[target]
|
|
618
|
+
if target_file != file_path:
|
|
619
|
+
rel.target_file = target_file
|
|
620
|
+
rel.is_cross_file = True
|
|
621
|
+
elif target in self._global_functions:
|
|
622
|
+
target_file = self._global_functions[target]
|
|
623
|
+
if target_file != file_path:
|
|
624
|
+
rel.target_file = target_file
|
|
625
|
+
rel.is_cross_file = True
|
|
626
|
+
|
|
627
|
+
|
|
628
|
+
# Register the parser
|
|
629
|
+
parser_registry.register_parser(JavaScriptParser())
|