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.
- codexa-0.4.0.dist-info/METADATA +650 -0
- codexa-0.4.0.dist-info/RECORD +189 -0
- codexa-0.4.0.dist-info/WHEEL +5 -0
- codexa-0.4.0.dist-info/entry_points.txt +2 -0
- codexa-0.4.0.dist-info/licenses/LICENSE +21 -0
- codexa-0.4.0.dist-info/top_level.txt +1 -0
- semantic_code_intelligence/__init__.py +5 -0
- semantic_code_intelligence/analysis/__init__.py +21 -0
- semantic_code_intelligence/analysis/ai_features.py +351 -0
- semantic_code_intelligence/bridge/__init__.py +28 -0
- semantic_code_intelligence/bridge/context_provider.py +245 -0
- semantic_code_intelligence/bridge/protocol.py +167 -0
- semantic_code_intelligence/bridge/server.py +348 -0
- semantic_code_intelligence/bridge/vscode.py +271 -0
- semantic_code_intelligence/ci/__init__.py +13 -0
- semantic_code_intelligence/ci/hooks.py +98 -0
- semantic_code_intelligence/ci/hotspots.py +272 -0
- semantic_code_intelligence/ci/impact.py +246 -0
- semantic_code_intelligence/ci/metrics.py +591 -0
- semantic_code_intelligence/ci/pr.py +412 -0
- semantic_code_intelligence/ci/quality.py +557 -0
- semantic_code_intelligence/ci/templates.py +164 -0
- semantic_code_intelligence/ci/trace.py +224 -0
- semantic_code_intelligence/cli/__init__.py +0 -0
- semantic_code_intelligence/cli/commands/__init__.py +0 -0
- semantic_code_intelligence/cli/commands/ask_cmd.py +153 -0
- semantic_code_intelligence/cli/commands/benchmark_cmd.py +303 -0
- semantic_code_intelligence/cli/commands/chat_cmd.py +252 -0
- semantic_code_intelligence/cli/commands/ci_gen_cmd.py +74 -0
- semantic_code_intelligence/cli/commands/context_cmd.py +120 -0
- semantic_code_intelligence/cli/commands/cross_refactor_cmd.py +113 -0
- semantic_code_intelligence/cli/commands/deps_cmd.py +91 -0
- semantic_code_intelligence/cli/commands/docs_cmd.py +101 -0
- semantic_code_intelligence/cli/commands/doctor_cmd.py +147 -0
- semantic_code_intelligence/cli/commands/evolve_cmd.py +171 -0
- semantic_code_intelligence/cli/commands/explain_cmd.py +112 -0
- semantic_code_intelligence/cli/commands/gate_cmd.py +135 -0
- semantic_code_intelligence/cli/commands/grep_cmd.py +234 -0
- semantic_code_intelligence/cli/commands/hotspots_cmd.py +119 -0
- semantic_code_intelligence/cli/commands/impact_cmd.py +131 -0
- semantic_code_intelligence/cli/commands/index_cmd.py +138 -0
- semantic_code_intelligence/cli/commands/init_cmd.py +152 -0
- semantic_code_intelligence/cli/commands/investigate_cmd.py +163 -0
- semantic_code_intelligence/cli/commands/languages_cmd.py +101 -0
- semantic_code_intelligence/cli/commands/lsp_cmd.py +49 -0
- semantic_code_intelligence/cli/commands/mcp_cmd.py +50 -0
- semantic_code_intelligence/cli/commands/metrics_cmd.py +264 -0
- semantic_code_intelligence/cli/commands/models_cmd.py +157 -0
- semantic_code_intelligence/cli/commands/plugin_cmd.py +275 -0
- semantic_code_intelligence/cli/commands/pr_summary_cmd.py +178 -0
- semantic_code_intelligence/cli/commands/quality_cmd.py +208 -0
- semantic_code_intelligence/cli/commands/refactor_cmd.py +103 -0
- semantic_code_intelligence/cli/commands/review_cmd.py +88 -0
- semantic_code_intelligence/cli/commands/search_cmd.py +236 -0
- semantic_code_intelligence/cli/commands/serve_cmd.py +117 -0
- semantic_code_intelligence/cli/commands/suggest_cmd.py +100 -0
- semantic_code_intelligence/cli/commands/summary_cmd.py +78 -0
- semantic_code_intelligence/cli/commands/tool_cmd.py +282 -0
- semantic_code_intelligence/cli/commands/trace_cmd.py +123 -0
- semantic_code_intelligence/cli/commands/tui_cmd.py +58 -0
- semantic_code_intelligence/cli/commands/viz_cmd.py +127 -0
- semantic_code_intelligence/cli/commands/watch_cmd.py +72 -0
- semantic_code_intelligence/cli/commands/web_cmd.py +61 -0
- semantic_code_intelligence/cli/commands/workspace_cmd.py +250 -0
- semantic_code_intelligence/cli/main.py +65 -0
- semantic_code_intelligence/cli/router.py +92 -0
- semantic_code_intelligence/config/__init__.py +0 -0
- semantic_code_intelligence/config/settings.py +260 -0
- semantic_code_intelligence/context/__init__.py +19 -0
- semantic_code_intelligence/context/engine.py +429 -0
- semantic_code_intelligence/context/memory.py +253 -0
- semantic_code_intelligence/daemon/__init__.py +1 -0
- semantic_code_intelligence/daemon/watcher.py +515 -0
- semantic_code_intelligence/docs/__init__.py +1080 -0
- semantic_code_intelligence/embeddings/__init__.py +0 -0
- semantic_code_intelligence/embeddings/enhanced.py +131 -0
- semantic_code_intelligence/embeddings/generator.py +149 -0
- semantic_code_intelligence/embeddings/model_registry.py +100 -0
- semantic_code_intelligence/evolution/__init__.py +1 -0
- semantic_code_intelligence/evolution/budget_guard.py +111 -0
- semantic_code_intelligence/evolution/commit_manager.py +88 -0
- semantic_code_intelligence/evolution/context_builder.py +131 -0
- semantic_code_intelligence/evolution/engine.py +249 -0
- semantic_code_intelligence/evolution/patch_generator.py +229 -0
- semantic_code_intelligence/evolution/task_selector.py +214 -0
- semantic_code_intelligence/evolution/test_runner.py +111 -0
- semantic_code_intelligence/indexing/__init__.py +0 -0
- semantic_code_intelligence/indexing/chunker.py +174 -0
- semantic_code_intelligence/indexing/parallel.py +86 -0
- semantic_code_intelligence/indexing/scanner.py +146 -0
- semantic_code_intelligence/indexing/semantic_chunker.py +337 -0
- semantic_code_intelligence/llm/__init__.py +62 -0
- semantic_code_intelligence/llm/cache.py +219 -0
- semantic_code_intelligence/llm/cached_provider.py +145 -0
- semantic_code_intelligence/llm/conversation.py +190 -0
- semantic_code_intelligence/llm/cross_refactor.py +272 -0
- semantic_code_intelligence/llm/investigation.py +274 -0
- semantic_code_intelligence/llm/mock_provider.py +77 -0
- semantic_code_intelligence/llm/ollama_provider.py +122 -0
- semantic_code_intelligence/llm/openai_provider.py +100 -0
- semantic_code_intelligence/llm/provider.py +92 -0
- semantic_code_intelligence/llm/rate_limiter.py +164 -0
- semantic_code_intelligence/llm/reasoning.py +438 -0
- semantic_code_intelligence/llm/safety.py +110 -0
- semantic_code_intelligence/llm/streaming.py +251 -0
- semantic_code_intelligence/lsp/__init__.py +609 -0
- semantic_code_intelligence/mcp/__init__.py +393 -0
- semantic_code_intelligence/parsing/__init__.py +19 -0
- semantic_code_intelligence/parsing/parser.py +375 -0
- semantic_code_intelligence/plugins/__init__.py +255 -0
- semantic_code_intelligence/plugins/examples/__init__.py +1 -0
- semantic_code_intelligence/plugins/examples/code_quality.py +73 -0
- semantic_code_intelligence/plugins/examples/search_annotator.py +56 -0
- semantic_code_intelligence/scalability/__init__.py +205 -0
- semantic_code_intelligence/search/__init__.py +0 -0
- semantic_code_intelligence/search/formatter.py +123 -0
- semantic_code_intelligence/search/grep.py +361 -0
- semantic_code_intelligence/search/hybrid_search.py +170 -0
- semantic_code_intelligence/search/keyword_search.py +311 -0
- semantic_code_intelligence/search/section_expander.py +103 -0
- semantic_code_intelligence/services/__init__.py +0 -0
- semantic_code_intelligence/services/indexing_service.py +630 -0
- semantic_code_intelligence/services/search_service.py +269 -0
- semantic_code_intelligence/storage/__init__.py +0 -0
- semantic_code_intelligence/storage/chunk_hash_store.py +86 -0
- semantic_code_intelligence/storage/hash_store.py +66 -0
- semantic_code_intelligence/storage/index_manifest.py +85 -0
- semantic_code_intelligence/storage/index_stats.py +138 -0
- semantic_code_intelligence/storage/query_history.py +160 -0
- semantic_code_intelligence/storage/symbol_registry.py +209 -0
- semantic_code_intelligence/storage/vector_store.py +297 -0
- semantic_code_intelligence/tests/__init__.py +0 -0
- semantic_code_intelligence/tests/test_ai_features.py +351 -0
- semantic_code_intelligence/tests/test_chunker.py +119 -0
- semantic_code_intelligence/tests/test_cli.py +188 -0
- semantic_code_intelligence/tests/test_config.py +154 -0
- semantic_code_intelligence/tests/test_context.py +381 -0
- semantic_code_intelligence/tests/test_embeddings.py +73 -0
- semantic_code_intelligence/tests/test_endtoend.py +1142 -0
- semantic_code_intelligence/tests/test_enhanced_embeddings.py +92 -0
- semantic_code_intelligence/tests/test_hash_store.py +79 -0
- semantic_code_intelligence/tests/test_logging.py +55 -0
- semantic_code_intelligence/tests/test_new_cli.py +138 -0
- semantic_code_intelligence/tests/test_parser.py +495 -0
- semantic_code_intelligence/tests/test_phase10.py +355 -0
- semantic_code_intelligence/tests/test_phase11.py +593 -0
- semantic_code_intelligence/tests/test_phase12.py +375 -0
- semantic_code_intelligence/tests/test_phase13.py +663 -0
- semantic_code_intelligence/tests/test_phase14.py +568 -0
- semantic_code_intelligence/tests/test_phase15.py +814 -0
- semantic_code_intelligence/tests/test_phase16.py +792 -0
- semantic_code_intelligence/tests/test_phase17.py +815 -0
- semantic_code_intelligence/tests/test_phase18.py +934 -0
- semantic_code_intelligence/tests/test_phase19.py +986 -0
- semantic_code_intelligence/tests/test_phase20.py +2753 -0
- semantic_code_intelligence/tests/test_phase20b.py +2058 -0
- semantic_code_intelligence/tests/test_phase20c.py +962 -0
- semantic_code_intelligence/tests/test_phase21.py +428 -0
- semantic_code_intelligence/tests/test_phase22.py +799 -0
- semantic_code_intelligence/tests/test_phase23.py +783 -0
- semantic_code_intelligence/tests/test_phase24.py +715 -0
- semantic_code_intelligence/tests/test_phase25.py +496 -0
- semantic_code_intelligence/tests/test_phase26.py +251 -0
- semantic_code_intelligence/tests/test_phase27.py +531 -0
- semantic_code_intelligence/tests/test_phase8.py +592 -0
- semantic_code_intelligence/tests/test_phase9.py +643 -0
- semantic_code_intelligence/tests/test_plugins.py +293 -0
- semantic_code_intelligence/tests/test_priority_features.py +727 -0
- semantic_code_intelligence/tests/test_router.py +41 -0
- semantic_code_intelligence/tests/test_scalability.py +138 -0
- semantic_code_intelligence/tests/test_scanner.py +125 -0
- semantic_code_intelligence/tests/test_search.py +160 -0
- semantic_code_intelligence/tests/test_semantic_chunker.py +255 -0
- semantic_code_intelligence/tests/test_tools.py +182 -0
- semantic_code_intelligence/tests/test_vector_store.py +151 -0
- semantic_code_intelligence/tests/test_watcher.py +211 -0
- semantic_code_intelligence/tools/__init__.py +442 -0
- semantic_code_intelligence/tools/executor.py +232 -0
- semantic_code_intelligence/tools/protocol.py +200 -0
- semantic_code_intelligence/tui/__init__.py +454 -0
- semantic_code_intelligence/utils/__init__.py +0 -0
- semantic_code_intelligence/utils/logging.py +112 -0
- semantic_code_intelligence/version.py +3 -0
- semantic_code_intelligence/web/__init__.py +11 -0
- semantic_code_intelligence/web/api.py +289 -0
- semantic_code_intelligence/web/server.py +397 -0
- semantic_code_intelligence/web/ui.py +659 -0
- semantic_code_intelligence/web/visualize.py +226 -0
- 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
|