codexa 0.4.0__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.
Files changed (189) hide show
  1. codexa-0.4.0.dist-info/METADATA +650 -0
  2. codexa-0.4.0.dist-info/RECORD +189 -0
  3. codexa-0.4.0.dist-info/WHEEL +5 -0
  4. codexa-0.4.0.dist-info/entry_points.txt +2 -0
  5. codexa-0.4.0.dist-info/licenses/LICENSE +21 -0
  6. codexa-0.4.0.dist-info/top_level.txt +1 -0
  7. semantic_code_intelligence/__init__.py +5 -0
  8. semantic_code_intelligence/analysis/__init__.py +21 -0
  9. semantic_code_intelligence/analysis/ai_features.py +351 -0
  10. semantic_code_intelligence/bridge/__init__.py +28 -0
  11. semantic_code_intelligence/bridge/context_provider.py +245 -0
  12. semantic_code_intelligence/bridge/protocol.py +167 -0
  13. semantic_code_intelligence/bridge/server.py +348 -0
  14. semantic_code_intelligence/bridge/vscode.py +271 -0
  15. semantic_code_intelligence/ci/__init__.py +13 -0
  16. semantic_code_intelligence/ci/hooks.py +98 -0
  17. semantic_code_intelligence/ci/hotspots.py +272 -0
  18. semantic_code_intelligence/ci/impact.py +246 -0
  19. semantic_code_intelligence/ci/metrics.py +591 -0
  20. semantic_code_intelligence/ci/pr.py +412 -0
  21. semantic_code_intelligence/ci/quality.py +557 -0
  22. semantic_code_intelligence/ci/templates.py +164 -0
  23. semantic_code_intelligence/ci/trace.py +224 -0
  24. semantic_code_intelligence/cli/__init__.py +0 -0
  25. semantic_code_intelligence/cli/commands/__init__.py +0 -0
  26. semantic_code_intelligence/cli/commands/ask_cmd.py +153 -0
  27. semantic_code_intelligence/cli/commands/benchmark_cmd.py +303 -0
  28. semantic_code_intelligence/cli/commands/chat_cmd.py +252 -0
  29. semantic_code_intelligence/cli/commands/ci_gen_cmd.py +74 -0
  30. semantic_code_intelligence/cli/commands/context_cmd.py +120 -0
  31. semantic_code_intelligence/cli/commands/cross_refactor_cmd.py +113 -0
  32. semantic_code_intelligence/cli/commands/deps_cmd.py +91 -0
  33. semantic_code_intelligence/cli/commands/docs_cmd.py +101 -0
  34. semantic_code_intelligence/cli/commands/doctor_cmd.py +147 -0
  35. semantic_code_intelligence/cli/commands/evolve_cmd.py +171 -0
  36. semantic_code_intelligence/cli/commands/explain_cmd.py +112 -0
  37. semantic_code_intelligence/cli/commands/gate_cmd.py +135 -0
  38. semantic_code_intelligence/cli/commands/grep_cmd.py +234 -0
  39. semantic_code_intelligence/cli/commands/hotspots_cmd.py +119 -0
  40. semantic_code_intelligence/cli/commands/impact_cmd.py +131 -0
  41. semantic_code_intelligence/cli/commands/index_cmd.py +138 -0
  42. semantic_code_intelligence/cli/commands/init_cmd.py +152 -0
  43. semantic_code_intelligence/cli/commands/investigate_cmd.py +163 -0
  44. semantic_code_intelligence/cli/commands/languages_cmd.py +101 -0
  45. semantic_code_intelligence/cli/commands/lsp_cmd.py +49 -0
  46. semantic_code_intelligence/cli/commands/mcp_cmd.py +50 -0
  47. semantic_code_intelligence/cli/commands/metrics_cmd.py +264 -0
  48. semantic_code_intelligence/cli/commands/models_cmd.py +157 -0
  49. semantic_code_intelligence/cli/commands/plugin_cmd.py +275 -0
  50. semantic_code_intelligence/cli/commands/pr_summary_cmd.py +178 -0
  51. semantic_code_intelligence/cli/commands/quality_cmd.py +208 -0
  52. semantic_code_intelligence/cli/commands/refactor_cmd.py +103 -0
  53. semantic_code_intelligence/cli/commands/review_cmd.py +88 -0
  54. semantic_code_intelligence/cli/commands/search_cmd.py +236 -0
  55. semantic_code_intelligence/cli/commands/serve_cmd.py +117 -0
  56. semantic_code_intelligence/cli/commands/suggest_cmd.py +100 -0
  57. semantic_code_intelligence/cli/commands/summary_cmd.py +78 -0
  58. semantic_code_intelligence/cli/commands/tool_cmd.py +282 -0
  59. semantic_code_intelligence/cli/commands/trace_cmd.py +123 -0
  60. semantic_code_intelligence/cli/commands/tui_cmd.py +58 -0
  61. semantic_code_intelligence/cli/commands/viz_cmd.py +127 -0
  62. semantic_code_intelligence/cli/commands/watch_cmd.py +72 -0
  63. semantic_code_intelligence/cli/commands/web_cmd.py +61 -0
  64. semantic_code_intelligence/cli/commands/workspace_cmd.py +250 -0
  65. semantic_code_intelligence/cli/main.py +65 -0
  66. semantic_code_intelligence/cli/router.py +92 -0
  67. semantic_code_intelligence/config/__init__.py +0 -0
  68. semantic_code_intelligence/config/settings.py +260 -0
  69. semantic_code_intelligence/context/__init__.py +19 -0
  70. semantic_code_intelligence/context/engine.py +429 -0
  71. semantic_code_intelligence/context/memory.py +253 -0
  72. semantic_code_intelligence/daemon/__init__.py +1 -0
  73. semantic_code_intelligence/daemon/watcher.py +515 -0
  74. semantic_code_intelligence/docs/__init__.py +1080 -0
  75. semantic_code_intelligence/embeddings/__init__.py +0 -0
  76. semantic_code_intelligence/embeddings/enhanced.py +131 -0
  77. semantic_code_intelligence/embeddings/generator.py +149 -0
  78. semantic_code_intelligence/embeddings/model_registry.py +100 -0
  79. semantic_code_intelligence/evolution/__init__.py +1 -0
  80. semantic_code_intelligence/evolution/budget_guard.py +111 -0
  81. semantic_code_intelligence/evolution/commit_manager.py +88 -0
  82. semantic_code_intelligence/evolution/context_builder.py +131 -0
  83. semantic_code_intelligence/evolution/engine.py +249 -0
  84. semantic_code_intelligence/evolution/patch_generator.py +229 -0
  85. semantic_code_intelligence/evolution/task_selector.py +214 -0
  86. semantic_code_intelligence/evolution/test_runner.py +111 -0
  87. semantic_code_intelligence/indexing/__init__.py +0 -0
  88. semantic_code_intelligence/indexing/chunker.py +174 -0
  89. semantic_code_intelligence/indexing/parallel.py +86 -0
  90. semantic_code_intelligence/indexing/scanner.py +146 -0
  91. semantic_code_intelligence/indexing/semantic_chunker.py +337 -0
  92. semantic_code_intelligence/llm/__init__.py +62 -0
  93. semantic_code_intelligence/llm/cache.py +219 -0
  94. semantic_code_intelligence/llm/cached_provider.py +145 -0
  95. semantic_code_intelligence/llm/conversation.py +190 -0
  96. semantic_code_intelligence/llm/cross_refactor.py +272 -0
  97. semantic_code_intelligence/llm/investigation.py +274 -0
  98. semantic_code_intelligence/llm/mock_provider.py +77 -0
  99. semantic_code_intelligence/llm/ollama_provider.py +122 -0
  100. semantic_code_intelligence/llm/openai_provider.py +100 -0
  101. semantic_code_intelligence/llm/provider.py +92 -0
  102. semantic_code_intelligence/llm/rate_limiter.py +164 -0
  103. semantic_code_intelligence/llm/reasoning.py +438 -0
  104. semantic_code_intelligence/llm/safety.py +110 -0
  105. semantic_code_intelligence/llm/streaming.py +251 -0
  106. semantic_code_intelligence/lsp/__init__.py +609 -0
  107. semantic_code_intelligence/mcp/__init__.py +393 -0
  108. semantic_code_intelligence/parsing/__init__.py +19 -0
  109. semantic_code_intelligence/parsing/parser.py +375 -0
  110. semantic_code_intelligence/plugins/__init__.py +255 -0
  111. semantic_code_intelligence/plugins/examples/__init__.py +1 -0
  112. semantic_code_intelligence/plugins/examples/code_quality.py +73 -0
  113. semantic_code_intelligence/plugins/examples/search_annotator.py +56 -0
  114. semantic_code_intelligence/scalability/__init__.py +205 -0
  115. semantic_code_intelligence/search/__init__.py +0 -0
  116. semantic_code_intelligence/search/formatter.py +123 -0
  117. semantic_code_intelligence/search/grep.py +361 -0
  118. semantic_code_intelligence/search/hybrid_search.py +170 -0
  119. semantic_code_intelligence/search/keyword_search.py +311 -0
  120. semantic_code_intelligence/search/section_expander.py +103 -0
  121. semantic_code_intelligence/services/__init__.py +0 -0
  122. semantic_code_intelligence/services/indexing_service.py +630 -0
  123. semantic_code_intelligence/services/search_service.py +269 -0
  124. semantic_code_intelligence/storage/__init__.py +0 -0
  125. semantic_code_intelligence/storage/chunk_hash_store.py +86 -0
  126. semantic_code_intelligence/storage/hash_store.py +66 -0
  127. semantic_code_intelligence/storage/index_manifest.py +85 -0
  128. semantic_code_intelligence/storage/index_stats.py +138 -0
  129. semantic_code_intelligence/storage/query_history.py +160 -0
  130. semantic_code_intelligence/storage/symbol_registry.py +209 -0
  131. semantic_code_intelligence/storage/vector_store.py +297 -0
  132. semantic_code_intelligence/tests/__init__.py +0 -0
  133. semantic_code_intelligence/tests/test_ai_features.py +351 -0
  134. semantic_code_intelligence/tests/test_chunker.py +119 -0
  135. semantic_code_intelligence/tests/test_cli.py +188 -0
  136. semantic_code_intelligence/tests/test_config.py +154 -0
  137. semantic_code_intelligence/tests/test_context.py +381 -0
  138. semantic_code_intelligence/tests/test_embeddings.py +73 -0
  139. semantic_code_intelligence/tests/test_endtoend.py +1142 -0
  140. semantic_code_intelligence/tests/test_enhanced_embeddings.py +92 -0
  141. semantic_code_intelligence/tests/test_hash_store.py +79 -0
  142. semantic_code_intelligence/tests/test_logging.py +55 -0
  143. semantic_code_intelligence/tests/test_new_cli.py +138 -0
  144. semantic_code_intelligence/tests/test_parser.py +495 -0
  145. semantic_code_intelligence/tests/test_phase10.py +355 -0
  146. semantic_code_intelligence/tests/test_phase11.py +593 -0
  147. semantic_code_intelligence/tests/test_phase12.py +375 -0
  148. semantic_code_intelligence/tests/test_phase13.py +663 -0
  149. semantic_code_intelligence/tests/test_phase14.py +568 -0
  150. semantic_code_intelligence/tests/test_phase15.py +814 -0
  151. semantic_code_intelligence/tests/test_phase16.py +792 -0
  152. semantic_code_intelligence/tests/test_phase17.py +815 -0
  153. semantic_code_intelligence/tests/test_phase18.py +934 -0
  154. semantic_code_intelligence/tests/test_phase19.py +986 -0
  155. semantic_code_intelligence/tests/test_phase20.py +2753 -0
  156. semantic_code_intelligence/tests/test_phase20b.py +2058 -0
  157. semantic_code_intelligence/tests/test_phase20c.py +962 -0
  158. semantic_code_intelligence/tests/test_phase21.py +428 -0
  159. semantic_code_intelligence/tests/test_phase22.py +799 -0
  160. semantic_code_intelligence/tests/test_phase23.py +783 -0
  161. semantic_code_intelligence/tests/test_phase24.py +715 -0
  162. semantic_code_intelligence/tests/test_phase25.py +496 -0
  163. semantic_code_intelligence/tests/test_phase26.py +251 -0
  164. semantic_code_intelligence/tests/test_phase27.py +531 -0
  165. semantic_code_intelligence/tests/test_phase8.py +592 -0
  166. semantic_code_intelligence/tests/test_phase9.py +643 -0
  167. semantic_code_intelligence/tests/test_plugins.py +293 -0
  168. semantic_code_intelligence/tests/test_priority_features.py +727 -0
  169. semantic_code_intelligence/tests/test_router.py +41 -0
  170. semantic_code_intelligence/tests/test_scalability.py +138 -0
  171. semantic_code_intelligence/tests/test_scanner.py +125 -0
  172. semantic_code_intelligence/tests/test_search.py +160 -0
  173. semantic_code_intelligence/tests/test_semantic_chunker.py +255 -0
  174. semantic_code_intelligence/tests/test_tools.py +182 -0
  175. semantic_code_intelligence/tests/test_vector_store.py +151 -0
  176. semantic_code_intelligence/tests/test_watcher.py +211 -0
  177. semantic_code_intelligence/tools/__init__.py +442 -0
  178. semantic_code_intelligence/tools/executor.py +232 -0
  179. semantic_code_intelligence/tools/protocol.py +200 -0
  180. semantic_code_intelligence/tui/__init__.py +454 -0
  181. semantic_code_intelligence/utils/__init__.py +0 -0
  182. semantic_code_intelligence/utils/logging.py +112 -0
  183. semantic_code_intelligence/version.py +3 -0
  184. semantic_code_intelligence/web/__init__.py +11 -0
  185. semantic_code_intelligence/web/api.py +289 -0
  186. semantic_code_intelligence/web/server.py +397 -0
  187. semantic_code_intelligence/web/ui.py +659 -0
  188. semantic_code_intelligence/web/visualize.py +226 -0
  189. semantic_code_intelligence/workspace/__init__.py +427 -0
@@ -0,0 +1,495 @@
1
+ """Tests for the tree-sitter based code parser."""
2
+
3
+ import pytest
4
+
5
+ from semantic_code_intelligence.parsing.parser import (
6
+ Symbol,
7
+ detect_language,
8
+ extract_classes,
9
+ extract_functions,
10
+ extract_imports,
11
+ get_language,
12
+ parse_file,
13
+ _get_node_text,
14
+ )
15
+
16
+
17
+ # ---------------------------------------------------------------------------
18
+ # Language detection
19
+ # ---------------------------------------------------------------------------
20
+
21
+ class TestDetectLanguage:
22
+ def test_python(self):
23
+ assert detect_language("main.py") == "python"
24
+
25
+ def test_javascript(self):
26
+ assert detect_language("app.js") == "javascript"
27
+
28
+ def test_jsx(self):
29
+ assert detect_language("component.jsx") == "javascript"
30
+
31
+ def test_java(self):
32
+ assert detect_language("Main.java") == "java"
33
+
34
+ def test_go(self):
35
+ assert detect_language("main.go") == "go"
36
+
37
+ def test_rust(self):
38
+ assert detect_language("lib.rs") == "rust"
39
+
40
+ def test_unsupported(self):
41
+ assert detect_language("style.css") is None
42
+
43
+ def test_no_extension(self):
44
+ assert detect_language("Makefile") is None
45
+
46
+ def test_case_insensitive(self):
47
+ assert detect_language("FILE.PY") == "python"
48
+ assert detect_language("APP.JS") == "javascript"
49
+
50
+
51
+ # ---------------------------------------------------------------------------
52
+ # Language loading
53
+ # ---------------------------------------------------------------------------
54
+
55
+ class TestGetLanguage:
56
+ def test_load_python(self):
57
+ lang = get_language("python")
58
+ assert lang is not None
59
+
60
+ def test_load_javascript(self):
61
+ lang = get_language("javascript")
62
+ assert lang is not None
63
+
64
+ def test_load_java(self):
65
+ lang = get_language("java")
66
+ assert lang is not None
67
+
68
+ def test_load_go(self):
69
+ lang = get_language("go")
70
+ assert lang is not None
71
+
72
+ def test_load_rust(self):
73
+ lang = get_language("rust")
74
+ assert lang is not None
75
+
76
+ def test_unsupported_language(self):
77
+ assert get_language("cobol") is None
78
+
79
+ def test_caching(self):
80
+ """Loading same language twice returns cached instance."""
81
+ lang1 = get_language("python")
82
+ lang2 = get_language("python")
83
+ assert lang1 is lang2
84
+
85
+
86
+ # ---------------------------------------------------------------------------
87
+ # Python parsing
88
+ # ---------------------------------------------------------------------------
89
+
90
+ PYTHON_CODE = '''\
91
+ import os
92
+ from pathlib import Path
93
+
94
+ def hello(name: str) -> str:
95
+ """Greet someone."""
96
+ return f"Hello, {name}!"
97
+
98
+ class Calculator:
99
+ """A simple calculator."""
100
+
101
+ def __init__(self, value: int = 0):
102
+ self.value = value
103
+
104
+ def add(self, x: int) -> int:
105
+ self.value += x
106
+ return self.value
107
+
108
+ def subtract(self, x: int) -> int:
109
+ self.value -= x
110
+ return self.value
111
+
112
+ def _private_helper():
113
+ pass
114
+ '''
115
+
116
+
117
+ class TestPythonParsing:
118
+ @pytest.fixture(autouse=True)
119
+ def setup(self):
120
+ self.symbols = parse_file("example.py", PYTHON_CODE)
121
+
122
+ def test_total_symbols_found(self):
123
+ # 2 imports + 1 function + 1 class + 3 methods + 1 private helper
124
+ assert len(self.symbols) >= 7
125
+
126
+ def test_function_extraction(self):
127
+ funcs = [s for s in self.symbols if s.kind == "function"]
128
+ names = {f.name for f in funcs}
129
+ assert "hello" in names
130
+ assert "_private_helper" in names
131
+
132
+ def test_class_extraction(self):
133
+ classes = [s for s in self.symbols if s.kind == "class"]
134
+ assert len(classes) == 1
135
+ assert classes[0].name == "Calculator"
136
+
137
+ def test_method_extraction(self):
138
+ methods = [s for s in self.symbols if s.kind == "method"]
139
+ names = {m.name for m in methods}
140
+ assert "__init__" in names
141
+ assert "add" in names
142
+ assert "subtract" in names
143
+
144
+ def test_methods_have_parent(self):
145
+ methods = [s for s in self.symbols if s.kind == "method"]
146
+ for m in methods:
147
+ assert m.parent == "Calculator"
148
+
149
+ def test_import_extraction(self):
150
+ imports = [s for s in self.symbols if s.kind == "import"]
151
+ assert len(imports) >= 2
152
+
153
+ def test_line_numbers(self):
154
+ hello = next(s for s in self.symbols if s.name == "hello")
155
+ assert hello.start_line == 4
156
+ assert hello.end_line == 6
157
+
158
+ def test_body_contains_code(self):
159
+ hello = next(s for s in self.symbols if s.name == "hello")
160
+ assert "def hello" in hello.body
161
+ assert "return" in hello.body
162
+
163
+ def test_parameters(self):
164
+ hello = next(s for s in self.symbols if s.name == "hello")
165
+ assert "name" in hello.parameters
166
+
167
+ def test_symbol_to_dict(self):
168
+ hello = next(s for s in self.symbols if s.name == "hello")
169
+ d = hello.to_dict()
170
+ assert d["name"] == "hello"
171
+ assert d["kind"] == "function"
172
+ assert d["file_path"] == "example.py"
173
+ assert "start_line" in d
174
+ assert "body" in d
175
+
176
+
177
+ # ---------------------------------------------------------------------------
178
+ # JavaScript parsing
179
+ # ---------------------------------------------------------------------------
180
+
181
+ JS_CODE = '''\
182
+ import { useState } from 'react';
183
+
184
+ function greet(name) {
185
+ return `Hello, ${name}!`;
186
+ }
187
+
188
+ const add = (a, b) => a + b;
189
+
190
+ class Counter {
191
+ constructor(initial) {
192
+ this.count = initial;
193
+ }
194
+
195
+ increment() {
196
+ this.count++;
197
+ }
198
+ }
199
+ '''
200
+
201
+
202
+ class TestJavaScriptParsing:
203
+ @pytest.fixture(autouse=True)
204
+ def setup(self):
205
+ self.symbols = parse_file("app.js", JS_CODE)
206
+
207
+ def test_function_found(self):
208
+ funcs = extract_functions("app.js", JS_CODE)
209
+ names = {f.name for f in funcs}
210
+ assert "greet" in names
211
+
212
+ def test_class_found(self):
213
+ classes = extract_classes("app.js", JS_CODE)
214
+ assert any(c.name == "Counter" for c in classes)
215
+
216
+ def test_methods_found(self):
217
+ methods = [s for s in self.symbols if s.kind == "method"]
218
+ names = {m.name for m in methods}
219
+ # constructor and increment
220
+ assert "constructor" in names or "increment" in names
221
+
222
+ def test_import_found(self):
223
+ imports = extract_imports("app.js", JS_CODE)
224
+ assert len(imports) >= 1
225
+
226
+
227
+ # ---------------------------------------------------------------------------
228
+ # Java parsing
229
+ # ---------------------------------------------------------------------------
230
+
231
+ JAVA_CODE = '''\
232
+ import java.util.List;
233
+
234
+ public class Calculator {
235
+ private int value;
236
+
237
+ public Calculator(int initial) {
238
+ this.value = initial;
239
+ }
240
+
241
+ public int add(int x) {
242
+ this.value += x;
243
+ return this.value;
244
+ }
245
+
246
+ public int getValue() {
247
+ return this.value;
248
+ }
249
+ }
250
+ '''
251
+
252
+
253
+ class TestJavaParsing:
254
+ @pytest.fixture(autouse=True)
255
+ def setup(self):
256
+ self.symbols = parse_file("Calculator.java", JAVA_CODE)
257
+
258
+ def test_class_found(self):
259
+ classes = extract_classes("Calculator.java", JAVA_CODE)
260
+ assert any(c.name == "Calculator" for c in classes)
261
+
262
+ def test_methods_found(self):
263
+ methods = [s for s in self.symbols if s.kind == "method"]
264
+ names = {m.name for m in methods}
265
+ assert "add" in names
266
+ assert "getValue" in names
267
+
268
+ def test_constructor_found(self):
269
+ methods = [s for s in self.symbols if s.kind == "method"]
270
+ names = {m.name for m in methods}
271
+ assert "Calculator" in names
272
+
273
+ def test_import_found(self):
274
+ imports = extract_imports("Calculator.java", JAVA_CODE)
275
+ assert len(imports) >= 1
276
+
277
+
278
+ # ---------------------------------------------------------------------------
279
+ # Go parsing
280
+ # ---------------------------------------------------------------------------
281
+
282
+ GO_CODE = '''\
283
+ package main
284
+
285
+ import "fmt"
286
+
287
+ func main() {
288
+ fmt.Println("Hello")
289
+ }
290
+
291
+ func add(a int, b int) int {
292
+ return a + b
293
+ }
294
+
295
+ type Calculator struct {
296
+ value int
297
+ }
298
+
299
+ func (c *Calculator) Add(x int) int {
300
+ c.value += x
301
+ return c.value
302
+ }
303
+ '''
304
+
305
+
306
+ class TestGoParsing:
307
+ @pytest.fixture(autouse=True)
308
+ def setup(self):
309
+ self.symbols = parse_file("main.go", GO_CODE)
310
+
311
+ def test_functions_found(self):
312
+ funcs = [s for s in self.symbols if s.kind == "function"]
313
+ names = {f.name for f in funcs}
314
+ assert "main" in names
315
+ assert "add" in names
316
+
317
+ def test_type_found(self):
318
+ classes = extract_classes("main.go", GO_CODE)
319
+ assert len(classes) >= 1
320
+
321
+ def test_method_found(self):
322
+ methods = [s for s in self.symbols if s.kind == "method"]
323
+ names = {m.name for m in methods}
324
+ assert "Add" in names
325
+
326
+ def test_import_found(self):
327
+ imports = extract_imports("main.go", GO_CODE)
328
+ assert len(imports) >= 1
329
+
330
+
331
+ # ---------------------------------------------------------------------------
332
+ # Rust parsing
333
+ # ---------------------------------------------------------------------------
334
+
335
+ RUST_CODE = '''\
336
+ use std::fmt;
337
+
338
+ fn main() {
339
+ println!("Hello");
340
+ }
341
+
342
+ fn add(a: i32, b: i32) -> i32 {
343
+ a + b
344
+ }
345
+
346
+ struct Calculator {
347
+ value: i32,
348
+ }
349
+
350
+ impl Calculator {
351
+ fn new(value: i32) -> Self {
352
+ Calculator { value }
353
+ }
354
+
355
+ fn add(&self, x: i32) -> i32 {
356
+ self.value + x
357
+ }
358
+ }
359
+
360
+ enum Color {
361
+ Red,
362
+ Green,
363
+ Blue,
364
+ }
365
+ '''
366
+
367
+
368
+ class TestRustParsing:
369
+ @pytest.fixture(autouse=True)
370
+ def setup(self):
371
+ self.symbols = parse_file("lib.rs", RUST_CODE)
372
+
373
+ def test_functions_found(self):
374
+ funcs = [s for s in self.symbols if s.kind == "function"]
375
+ names = {f.name for f in funcs}
376
+ assert "main" in names
377
+ assert "add" in names
378
+
379
+ def test_struct_found(self):
380
+ classes = extract_classes("lib.rs", RUST_CODE)
381
+ names = {c.name for c in classes}
382
+ assert "Calculator" in names
383
+
384
+ def test_impl_methods(self):
385
+ # Methods inside impl blocks
386
+ methods = [s for s in self.symbols if s.kind == "method"]
387
+ names = {m.name for m in methods}
388
+ assert "new" in names or "add" in names
389
+
390
+ def test_enum_found(self):
391
+ classes = extract_classes("lib.rs", RUST_CODE)
392
+ names = {c.name for c in classes}
393
+ assert "Color" in names
394
+
395
+ def test_use_found(self):
396
+ imports = extract_imports("lib.rs", RUST_CODE)
397
+ assert len(imports) >= 1
398
+
399
+
400
+ # ---------------------------------------------------------------------------
401
+ # Edge cases
402
+ # ---------------------------------------------------------------------------
403
+
404
+ class TestEdgeCases:
405
+ def test_empty_file(self):
406
+ symbols = parse_file("empty.py", "")
407
+ assert symbols == []
408
+
409
+ def test_syntax_error_still_parses(self):
410
+ """tree-sitter is error-tolerant and should still parse partial code."""
411
+ code = "def broken_func(:\n pass"
412
+ symbols = parse_file("broken.py", code)
413
+ # Should not crash — may or may not find symbols
414
+ assert isinstance(symbols, list)
415
+
416
+ def test_unsupported_extension(self):
417
+ symbols = parse_file("style.css", "body { color: red; }")
418
+ assert symbols == []
419
+
420
+ def test_unicode_content(self):
421
+ code = 'def greet():\n print("Héllo, 世界!")\n'
422
+ symbols = parse_file("unicode.py", code)
423
+ funcs = [s for s in symbols if s.kind == "function"]
424
+ assert len(funcs) == 1
425
+ assert funcs[0].name == "greet"
426
+
427
+ def test_nonexistent_file_returns_empty(self, tmp_path):
428
+ symbols = parse_file(str(tmp_path / "does_not_exist.py"))
429
+ assert symbols == []
430
+
431
+ def test_read_from_disk(self, tmp_path):
432
+ f = tmp_path / "hello.py"
433
+ f.write_text("def disk_func():\n pass\n", encoding="utf-8")
434
+ symbols = parse_file(str(f))
435
+ funcs = [s for s in symbols if s.kind == "function"]
436
+ assert any(fun.name == "disk_func" for fun in funcs)
437
+
438
+
439
+ # ---------------------------------------------------------------------------
440
+ # Helper functions
441
+ # ---------------------------------------------------------------------------
442
+
443
+ class TestExtractHelpers:
444
+ def test_extract_functions_filters(self):
445
+ funcs = extract_functions("example.py", PYTHON_CODE)
446
+ for f in funcs:
447
+ assert f.kind in ("function", "method")
448
+
449
+ def test_extract_classes_filters(self):
450
+ classes = extract_classes("example.py", PYTHON_CODE)
451
+ for c in classes:
452
+ assert c.kind == "class"
453
+
454
+ def test_extract_imports_filters(self):
455
+ imports = extract_imports("example.py", PYTHON_CODE)
456
+ for i in imports:
457
+ assert i.kind == "import"
458
+
459
+
460
+ # ---------------------------------------------------------------------------
461
+ # Python decorators
462
+ # ---------------------------------------------------------------------------
463
+
464
+ DECORATED_CODE = '''\
465
+ import functools
466
+
467
+ def my_decorator(func):
468
+ pass
469
+
470
+ @my_decorator
471
+ def decorated_function():
472
+ pass
473
+
474
+ class MyClass:
475
+ @staticmethod
476
+ def static_method():
477
+ pass
478
+ '''
479
+
480
+
481
+ class TestDecorators:
482
+ @pytest.fixture(autouse=True)
483
+ def setup(self):
484
+ self.symbols = parse_file("decorated.py", DECORATED_CODE)
485
+
486
+ def test_decorated_function_found(self):
487
+ funcs = [s for s in self.symbols if s.kind == "function"]
488
+ names = {f.name for f in funcs}
489
+ assert "decorated_function" in names
490
+ assert "my_decorator" in names
491
+
492
+ def test_static_method_found(self):
493
+ methods = [s for s in self.symbols if s.kind == "method"]
494
+ names = {m.name for m in methods}
495
+ assert "static_method" in names